Version: 0.9.10.dev.260409

后端:
1. newAgent 运行态重置双保险落地,并补齐写工具后的实时排程预览刷新
   - 更新 model/common_state.go:新增 ResetForNextRun,统一清理 round/plan/rough_build/allow_reorder/terminal 等执行期临时状态
   - 更新 node/chat.go + service/agentsvc/agent_newagent.go:在“无 pending 且上一轮已 done”时分别于 chat 主入口与 loadOrCreateRuntimeState 冷加载处执行兜底重置,覆盖正常新一轮对话与断线恢复场景
   - 更新 model/graph_run_state.go + node/agent_nodes.go + node/execute.go:写工具执行后立即刷新 Redis 排程预览,Deliver 继续保留最终覆盖写,保证前端能及时看到最新操作结果
2. 顺序守卫从“直接中止”改为“优先自动复原 suggested 相对顺序”
   - 更新 node/order_guard.go:检测到 suggested 顺序被打乱后,不再直接 abort;改为复用当前坑位按 baseline 自动回填,并在复原失败时仅记录诊断日志后继续交付
   - 更新 tools/state.go:ScheduleState 新增 RuntimeQueue 运行态快照字段,支持队列化处理与断线恢复
3. 多任务微调工具链升级:新增筛选/队列工具并替换首空位查询口径
   - 新建 tools/read_filter_tools.go + tools/runtime_queue.go + tools/queue_tools.go:新增 query_available_slots / query_target_tasks / queue_pop_head / queue_apply_head_move / queue_skip_head / queue_status,支持“先筛选目标,再逐项处理”的稳定微调链路
   - 更新 tools/registry.go + tools/write_tools.go + tools/read_helpers.go:移除 find_first_free 注册口径;batch_move 限制为最多 2 条,超过时引导改走队列逐项处理;queue_apply_head_move 纳入写工具集合
4. 复合规划工具扩充,并改为在 newAgent/tools 本地实现以规避循环导入
   - 更新 tools/compound_tools.go + tools/registry.go:spread_even 正式接入,并与 min_context_switch 一起作为复合写工具保留在 newAgent/tools 内部实现,不再依赖外层 logic
5. prompt 与工具文档同步升级,明确当前用户诉求锚点与队列化执行约束
   - 更新 prompt/execute.go + prompt/execute_context.go + prompt/plan.go:执行提示默认引导 query_target_tasks(enqueue=true) → queue_pop_head → query_available_slots → queue_apply_head_move / queue_skip_head;补齐 batch_move 上限、spread_even 使用边界、顺序策略与工具 JSON 返回示例
   - 更新 prompt/execute_context.go:将“初始用户目标”改为“当前用户诉求”,并保留首轮目标来源;旧 observation 折叠文案改为“当前工具调用结果已经被使用过,当前无需使用,为节省上下文空间,已折叠”
   - 更新 tools/SCHEDULE_TOOLS.md:同步补齐 query_* / queue_* / spread_even / min_context_switch 的说明、限制与返回示例
6. 同步更新调试日志文件
前端:无
仓库:无
This commit is contained in:
LoveLosita
2026-04-09 16:17:56 +08:00
parent 21b864390b
commit 821c2cde5d
20 changed files with 5809 additions and 2685 deletions

View File

@@ -7,24 +7,16 @@ import (
)
// ToolHandler 是所有工具的统一执行签名。
// 接收当前 ScheduleState + LLM 输出的原始参数,返回自然语言结果。
type ToolHandler func(state *ScheduleState, args map[string]any) string
// ToolSchemaEntry 是工具描述的轻量快照,用于 LLM prompt 注入。
// 在注入 ConversationContext 时转换为 model.ToolSchemaContext。
// ToolSchemaEntry 是注入给模型的工具说明快照
type ToolSchemaEntry struct {
Name string
Desc string
SchemaText string
}
// ToolRegistry 管理所有工具注册、查找执行。
//
// 职责边界:
// 1. 负责工具名 → handler 的映射;
// 2. 负责工具 schema 的存储(供 LLM prompt 注入);
// 3. 不负责 ScheduleState 的生命周期管理;
// 4. 不负责 confirm 流程(由 execute.go 的 action 分支处理)。
// ToolRegistry 管理工具注册、查找执行。
type ToolRegistry struct {
handlers map[string]ToolHandler
schemas []ToolSchemaEntry
@@ -38,7 +30,7 @@ func NewToolRegistry() *ToolRegistry {
}
}
// Register 注册一个工具及其 schema 描述
// Register 注册一个工具及其 schema。
func (r *ToolRegistry) Register(name, desc, schemaText string, handler ToolHandler) {
r.handlers[name] = handler
r.schemas = append(r.schemas, ToolSchemaEntry{
@@ -49,7 +41,6 @@ func (r *ToolRegistry) Register(name, desc, schemaText string, handler ToolHandl
}
// Execute 执行指定工具。
// 工具名不存在时返回错误提示字符串。
func (r *ToolRegistry) Execute(state *ScheduleState, toolName string, args map[string]any) string {
handler, ok := r.handlers[toolName]
if !ok {
@@ -64,36 +55,38 @@ func (r *ToolRegistry) HasTool(name string) bool {
return ok
}
// ToolNames 返回所有已注册工具名(按注册顺序)。
// ToolNames 返回已注册工具名(按 schema 顺序)。
func (r *ToolRegistry) ToolNames() []string {
names := make([]string, 0, len(r.handlers))
for _, s := range r.schemas {
names = append(names, s.Name)
for _, item := range r.schemas {
names = append(names, item.Name)
}
return names
}
// Schemas 返回所有工具的 schema 描述(供 LLM prompt 注入)
// Schemas 返回 schema 快照
func (r *ToolRegistry) Schemas() []ToolSchemaEntry {
result := make([]ToolSchemaEntry, len(r.schemas))
copy(result, r.schemas)
return result
}
// IsWriteTool 判断指定工具是否写工具(需要 confirm 流程)。
// IsWriteTool 判断工具是否写工具(需要 confirm
func (r *ToolRegistry) IsWriteTool(name string) bool {
return writeTools[name]
}
// ==================== 写工具集合 ====================
// ==================== 写工具集合 ====================
var writeTools = map[string]bool{
"place": true,
"move": true,
"swap": true,
"batch_move": true,
"min_context_switch": true,
"unplace": true,
"place": true,
"move": true,
"swap": true,
"batch_move": true,
"queue_apply_head_move": true,
"spread_even": true,
"min_context_switch": true,
"unplace": true,
}
// ==================== 默认注册表 ====================
@@ -123,20 +116,40 @@ func NewDefaultRegistry() *ToolRegistry {
},
)
r.Register("find_first_free",
"查找首个满足时长条件的可用位置并返回该日详细负载信息。duration 必填;可用 day 指定单天,或用 day_start/day_end 指定搜索范围(互斥)。",
`{"name":"find_first_free","parameters":{"duration":{"type":"int","required":true},"day":{"type":"int"},"day_start":{"type":"int"},"day_end":{"type":"int"}}}`,
r.Register("query_available_slots",
"查询候选空位池(先返回纯空位,不足再补可嵌入位),适合 move 前的落点筛选。",
`{"name":"query_available_slots","parameters":{"span":{"type":"int"},"duration":{"type":"int"},"limit":{"type":"int"},"allow_embed":{"type":"bool"},"day":{"type":"int"},"day_start":{"type":"int"},"day_end":{"type":"int"},"day_scope":{"type":"string","enum":["all","workday","weekend"]},"day_of_week":{"type":"array","items":{"type":"int"}},"week":{"type":"int"},"week_filter":{"type":"array","items":{"type":"int"}},"week_from":{"type":"int"},"week_to":{"type":"int"},"slot_type":{"type":"string"},"slot_types":{"type":"array","items":{"type":"string"}},"exclude_sections":{"type":"array","items":{"type":"int"}},"after_section":{"type":"int"},"before_section":{"type":"int"},"section_from":{"type":"int"},"section_to":{"type":"int"}}}`,
func(state *ScheduleState, args map[string]any) string {
duration, ok := argsInt(args, "duration")
if !ok {
return "查询失败:缺少必填参数 duration。"
}
return FindFirstFree(state, duration, argsIntPtr(args, "day"), argsIntPtr(args, "day_start"), argsIntPtr(args, "day_end"))
return QueryAvailableSlots(state, args)
},
)
r.Register("query_target_tasks",
"查询候选任务集合,可按 status/week/day/task_id/category 筛选;默认自动入队,供后续 queue_pop_head 逐项处理。",
`{"name":"query_target_tasks","parameters":{"status":{"type":"string","enum":["all","existing","suggested","pending"]},"category":{"type":"string"},"limit":{"type":"int"},"day_scope":{"type":"string","enum":["all","workday","weekend"]},"day":{"type":"int"},"day_start":{"type":"int"},"day_end":{"type":"int"},"day_of_week":{"type":"array","items":{"type":"int"}},"week":{"type":"int"},"week_filter":{"type":"array","items":{"type":"int"}},"week_from":{"type":"int"},"week_to":{"type":"int"},"task_ids":{"type":"array","items":{"type":"int"}},"task_id":{"type":"int"},"task_item_ids":{"type":"array","items":{"type":"int"}},"task_item_id":{"type":"int"},"enqueue":{"type":"bool"},"reset_queue":{"type":"bool"}}}`,
func(state *ScheduleState, args map[string]any) string {
return QueryTargetTasks(state, args)
},
)
r.Register("queue_pop_head",
"弹出并返回当前队首任务;若已有 current 则复用,保证一次只处理一个任务。",
`{"name":"queue_pop_head","parameters":{}}`,
func(state *ScheduleState, args map[string]any) string {
return QueuePopHead(state, args)
},
)
r.Register("queue_status",
"查看当前待处理队列状态pending/current/completed/skipped。",
`{"name":"queue_status","parameters":{}}`,
func(state *ScheduleState, args map[string]any) string {
return QueueStatus(state, args)
},
)
r.Register("list_tasks",
"列出任务清单可按类别和状态过滤。category 传任务类名称(非 ID 列表可选status 选填(默认 all仅支持单值 all/existing/suggested/pending。",
"列出任务清单可按类别和状态过滤。category 传任务类名称status 仅支持单值 all/existing/suggested/pending。",
`{"name":"list_tasks","parameters":{"category":{"type":"string"},"status":{"type":"string","enum":["all","existing","suggested","pending"]}}}`,
func(state *ScheduleState, args map[string]any) string {
return ListTasks(state, argsStringPtr(args, "category"), argsStringPtr(args, "status"))
@@ -144,7 +157,7 @@ func NewDefaultRegistry() *ToolRegistry {
)
r.Register("get_task_info",
"查询单个任务详细信息,包括类别、状态、占用时段、嵌入关系。",
"查询单个任务详细信息,包括类别、状态、占用时段、嵌入关系。",
`{"name":"get_task_info","parameters":{"task_id":{"type":"int","required":true}}}`,
func(state *ScheduleState, args map[string]any) string {
taskID, ok := argsInt(args, "task_id")
@@ -213,7 +226,7 @@ func NewDefaultRegistry() *ToolRegistry {
)
r.Register("batch_move",
"原子性批量移动多个任务(仅 suggested全部成功才生效。若含 existing/pending 将整批失败回滚。moves 数组必填。",
"原子性批量移动多个任务(仅 suggested最多2条),全部成功才生效。若含 existing/pending 或任一冲突将整批失败回滚。",
`{"name":"batch_move","parameters":{"moves":{"type":"array","required":true,"items":{"task_id":"int","new_day":"int","new_slot_start":"int"}}}}`,
func(state *ScheduleState, args map[string]any) string {
moves, err := argsMoveList(args)
@@ -224,6 +237,22 @@ func NewDefaultRegistry() *ToolRegistry {
},
)
r.Register("queue_apply_head_move",
"将当前队首任务移动到指定位置并自动出队。仅作用于 current不接受 task_id。new_day/new_slot_start 必填。",
`{"name":"queue_apply_head_move","parameters":{"new_day":{"type":"int","required":true},"new_slot_start":{"type":"int","required":true}}}`,
func(state *ScheduleState, args map[string]any) string {
return QueueApplyHeadMove(state, args)
},
)
r.Register("queue_skip_head",
"跳过当前队首任务(不改日程),将其标记为 skipped 并继续后续队列。",
`{"name":"queue_skip_head","parameters":{"reason":{"type":"string"}}}`,
func(state *ScheduleState, args map[string]any) string {
return QueueSkipHead(state, args)
},
)
r.Register("min_context_switch",
"在指定任务集合内重排 suggested 任务尽量让同类任务连续以减少上下文切换。仅在用户明确允许打乱顺序时使用。task_ids 必填(兼容 task_id。",
`{"name":"min_context_switch","parameters":{"task_ids":{"type":"array","required":true,"items":{"type":"int"}},"task_id":{"type":"int"}}}`,
@@ -236,6 +265,18 @@ func NewDefaultRegistry() *ToolRegistry {
},
)
r.Register("spread_even",
"在给定任务集合内做均匀化铺开先按筛选条件收集候选坑位再规划并原子落地。task_ids 必填(兼容 task_id。",
`{"name":"spread_even","parameters":{"task_ids":{"type":"array","required":true,"items":{"type":"int"}},"task_id":{"type":"int"},"limit":{"type":"int"},"allow_embed":{"type":"bool"},"day":{"type":"int"},"day_start":{"type":"int"},"day_end":{"type":"int"},"day_scope":{"type":"string","enum":["all","workday","weekend"]},"day_of_week":{"type":"array","items":{"type":"int"}},"week":{"type":"int"},"week_filter":{"type":"array","items":{"type":"int"}},"week_from":{"type":"int"},"week_to":{"type":"int"},"slot_type":{"type":"string"},"slot_types":{"type":"array","items":{"type":"string"}},"exclude_sections":{"type":"array","items":{"type":"int"}},"after_section":{"type":"int"},"before_section":{"type":"int"}}}`,
func(state *ScheduleState, args map[string]any) string {
taskIDs, err := parseSpreadEvenTaskIDs(args)
if err != nil {
return fmt.Sprintf("均匀化调整失败:%s。", err.Error())
}
return SpreadEven(state, taskIDs, args)
},
)
r.Register("unplace",
"将一个已落位任务移除恢复为待安排状态。会自动清理嵌入关系。task_id 必填。",
`{"name":"unplace","parameters":{"task_id":{"type":"int","required":true}}}`,
@@ -248,7 +289,7 @@ func NewDefaultRegistry() *ToolRegistry {
},
)
// 按 schema name 排序,保输出稳定。
// 按 schema name 排序,保输出稳定。
sort.Slice(r.schemas, func(i, j int) bool {
return r.schemas[i].Name < r.schemas[j].Name
})