feat(agent): add_hint 触发的规划轮显式播报「本轮由新增 hint 触发」

此前 add_hint 走裸 notify,触发的规划轮 triggers 为空,开场白说"无具体变动",
hint 只折在 graph_overview 里让 planner 自己发现。仿照 set_goals/NotifyGoal,
让 add_hint 记一条 hint 触发事件并带上提示文本。

- TriggerEvent 增加 Hints 字段;renderTriggers 增加 hint 分支,播报「人新增了
  N 条战略提示:…」
- ToolSet 增加 notifyHint 回调 + SetNotifyHint;add_hint 整批写完统一唤醒一次
  (优先 notifyHint,未接则退回裸 notify),notify 从 addOneHint 上移到 handler
- 主 agent(server.go) 与跨任务编排(orchestration.go) 均接上 Task.NotifyHint
This commit is contained in:
Autumn-27 committed 2026-09-13 11:36:31 -04:00
1 parent 0b9341ac8b
commit edbc296e29
6 files changed
+55 -9

No files matched your search

+3 -2
View File
@@ -102,7 +102,7 @@ func mainAgentSystem(goal, dataDir, workDir string) string {
// non-nil, receives each execution step (thinking / tool_use / tool_result /
// text / result) so the main-agent session shows its work — exactly like the
// worker/planner sessions — not just the final answer.
func (m *MainAgent) Chat(ctx context.Context, taskID int64, mainSeg int, as *db.AssetStore, ts *db.ExplorationStore, goal, message string, emit func(db.Activity), notify, resume func(), notifyGoal func([]string)) (string, error) {
func (m *MainAgent) Chat(ctx context.Context, taskID int64, mainSeg int, as *db.AssetStore, ts *db.ExplorationStore, goal, message string, emit func(db.Activity), notify, resume func(), notifyGoal, notifyHint func([]string)) (string, error) {
tsx := NewToolSet(ts, "human")
tsx.SetFindingRecorder(m.findingRecorder)
if as != nil {
@@ -110,9 +110,10 @@ func (m *MainAgent) Chat(ctx context.Context, taskID int64, mainSeg int, as *db.
}
tsx.SetTaskID(taskID)
tsx.SetCoverageEnabled(as == nil || as.CoverageEnabled(taskID))
tsx.SetNotify(notify) // add_hint wakes this task's planner (debounced)
tsx.SetNotify(notify) // 通用唤醒(无专用回调的写操作走它,debounced)
tsx.SetResumeTask(resume) // set_goals 新增目标 → 把已完成/暂停的任务拉回 running
tsx.SetNotifyGoal(notifyGoal) // set_goals 新增目标 → 给 planner 记一条「人新增了目标:…」触发
tsx.SetNotifyHint(notifyHint) // add_hint 新增提示 → 给 planner 记一条「人新增了 N 条战略提示:…」触发
tsx.steerWork = m.steerWork // enable steer_work tool (nil = unavailable)
// 领域工具 + 基础默认工具集(Read/Write/Edit/MultiEdit/LS/Glob/Grep/Bash)
// 资产覆盖度功能关闭时剔除 add_task_scope/list_untested_assets(不入 prompt)。
+7
View File
@@ -155,6 +155,7 @@ type TriggerEvent struct {
Goals []string // Kind=="goal" 专用:本次 set_goals 新增的目标文本(1 条或多条)
OldGoal string // Kind=="goal_edited" 专用:修改前的目标文本
NewGoal string // Kind=="goal_edited" 专用:修改后的目标文本
Hints []string // Kind=="hint" 专用:本次 add_hint 新增的提示文本(1 条或多条)
}
// renderTriggers spells out the change(s) that fired this round: for a finished
@@ -175,6 +176,12 @@ func renderTriggers(ts *db.ExplorationStore, evs []TriggerEvent) string {
} else {
b.WriteString(fmt.Sprintf("\n- 人(主 agent)新增了 %d 个目标:%s —— 均为新的待达成目标,请逐一为尚无对应意图的目标补充探索方向。", len(ev.Goals), strings.Join(ev.Goals, ";")))
}
case "hint":
if len(ev.Hints) == 1 {
b.WriteString(fmt.Sprintf("\n- 人(主 agent)新增了一条战略提示:%s —— 已挂到探索图上,请据此调整/补充探索方向(若尚无对应意图)。", ev.Hints[0]))
} else {
b.WriteString(fmt.Sprintf("\n- 人(主 agent)新增了 %d 条战略提示:%s —— 均已挂到探索图上,请逐一据此调整/补充探索方向。", len(ev.Hints), strings.Join(ev.Hints, ";")))
}
case "goal_deleted":
b.WriteString(fmt.Sprintf("\n- 人删除了该目标:%s —— 该目标已移除,请据此重判剩余目标/方向(不必再为它派意图)。", ev.Detail))
case "goal_edited":
+27 -5
View File
@@ -123,12 +123,23 @@ type ToolSet struct {
// goals decomposer (round-0 has no running planner to inform) and workers → those
// fall back to the bare notify.
notifyGoal func(texts []string)
// notifyHint, if set, wakes the planner AND records ONE "人新增了 N 条战略提示:…"
// trigger for a whole add_hint call (batch-aware — one call, one trigger) so the next
// round is told the round was fired by a new hint and spells the hint out, instead of
// the planner having to spot it folded into the graph overview. Wired for the main
// agent + cross-task orchestration; nil elsewhere → falls back to the bare notify.
notifyHint func(texts []string)
}
// SetNotifyGoal wires the goal-add trigger callback (see ToolSet.notifyGoal). Set only
// by the main-agent chat, so runtime-added goals are announced to the planner by name.
func (t *ToolSet) SetNotifyGoal(fn func([]string)) { t.notifyGoal = fn }
// SetNotifyHint wires the hint-add trigger callback (see ToolSet.notifyHint). Set by
// the main-agent chat and cross-task orchestration, so a runtime-added hint fires a
// planner round announced by name instead of a bare wake.
func (t *ToolSet) SetNotifyHint(fn func([]string)) { t.notifyHint = fn }
// SetResumeTask wires the task-revive callback (see ToolSet.resumeTask). Set only by
// the main-agent chat, so runtime-added goals can pull a finished task back to running.
func (t *ToolSet) SetResumeTask(fn func()) { t.resumeTask = fn }
@@ -1540,11 +1551,9 @@ func (t *ToolSet) addOneHint(it hintItem) (int64, error) {
if len(refs) > 0 {
payload["traffic_refs"] = refs
}
id, err := t.ts.AddNode(db.KindHint, payload, 0, "active", "human", anchors)
if err == nil && t.notify != nil {
t.notify() // wake the planner so the new hint is read promptly (debounced)
}
return id, err
// 唤醒 planner 不在此处逐条做——由 addHint 在整批写完后统一触发一次(带上提示文本),
// 避免一次 add_hint 多条提示逐条刷屏 planner 的触发行。
return t.ts.AddNode(db.KindHint, payload, 0, "active", "human", anchors)
}
type goalItem struct {
@@ -1745,6 +1754,7 @@ func (t *ToolSet) addHint() actool.CoreTool {
ids := make([]int64, len(items))
errs := map[string]string{}
var addedTexts []string
for i, it := range items {
id, err := t.addOneHint(it)
if err != nil {
@@ -1752,6 +1762,18 @@ func (t *ToolSet) addHint() actool.CoreTool {
continue
}
ids[i] = id
addedTexts = append(addedTexts, strings.TrimSpace(it.Text))
}
if len(addedTexts) > 0 {
// 唤醒 planner(整批一次)。优先 notifyHint:一次 add_hint 记一条「人新增了
// N 条战略提示:…」触发,让 planner 明确"本轮由新增 hint 触发"并看到提示内容;
// 未接该回调时退回纯 notify(bare wake,hint 仍折在图里供其自行读取)。
switch {
case t.notifyHint != nil:
t.notifyHint(addedTexts)
case t.notify != nil:
t.notify()
}
}
if !batch { // 单条:保持原返回