ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
docs / links (push) Waiting to run
web / web (push) Waiting to run
174 lines
8.8 KiB
Go
174 lines
8.8 KiB
Go
package agent
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"github.com/Autumn-27/norma/harness"
|
||
)
|
||
|
||
// 收尾提示词(wrap-up / settlement prompt):当 agent 因【步数耗尽(MaxTurns)】或
|
||
// 【超时(run_seconds/MaxDuration)】被终止时,SDK 的 settlement 阶段会注入这段提示,
|
||
// 让 agent 先把已识别但未写回的内容落库、再输出一句总结,避免烂尾。
|
||
//
|
||
// 每个 agent 的收尾提示词可在后台按需覆盖(存 agents.wrapup_prompt),留空则用这里的
|
||
// 内置默认。仅【提示词正文】可编辑;禁用哪些工具、收尾自身给几轮预算属代码固定策略。
|
||
|
||
// WrapupOverride, if set, returns the stored wrap-up prompt for an agent key and
|
||
// whether a non-empty one exists. Wired by the server to the agents table (like
|
||
// PromptOverride for system prompts). nil / empty → the built-in default is used.
|
||
var WrapupOverride func(agentKey string) (string, bool)
|
||
|
||
// WrapupMaxTurnsOverride, if set, returns the admin-configured turn budget for the
|
||
// wrap-up phase of an agent and whether a positive one exists. Wired to the agents
|
||
// table. nil / ≤0 → the built-in per-agent default (wrapupTurnDefaults) is used.
|
||
var WrapupMaxTurnsOverride func(agentKey string) (int, bool)
|
||
|
||
// 内置默认收尾提示词,按 agent key 索引。worker 复用历史上硬编码的 settleWrapUpPrompt
|
||
// (定义在 worker.go),planner/mainagent 各有一版;未命中的(自定义 agent)走通用兜底。
|
||
var wrapupDefaults = map[string]string{
|
||
"worker": settleWrapUpPrompt,
|
||
"planner": plannerWrapUpDefault,
|
||
"mainagent": mainAgentWrapUpDefault,
|
||
}
|
||
|
||
// wrapupTurnDefaults: 各 agent 收尾阶段【自身】的轮数预算内置默认(可被后台 >0 覆盖)。
|
||
// 均给 10 轮,保证收尾阶段有足够步数落库。未命中走 genericWrapupTurns。
|
||
var wrapupTurnDefaults = map[string]int{
|
||
"worker": 10,
|
||
"planner": 10,
|
||
"mainagent": 10,
|
||
}
|
||
|
||
const genericWrapupTurns = 10
|
||
|
||
const plannerWrapUpDefault = "本轮计划的步骤预算即将耗尽。不过仅仅【本轮】结束,系统之后仍会随情况变化再次唤醒你继续规划。这不是整个任务结束,因此这里无需为整体计划收尾。本轮已明确判断的结论应实际落实,使本轮不白费,但【不要为了收尾而勉强编造意图】(本轮意图为 0 个也是完全正常的结果)。(1) 若已判断出【现在就该派发】的探索方向,用一次 add_intent 打包提交(已确定的不要压着,立即发出)。(2) 已被某个发现或事实证明达成的目标,用 prove_goal 标记为 met(不要遗漏)。(3) 若识别出需要分步的串行利用链,用 TodoWrite 记录,以便下次唤醒时继续派发。全部完成后直接结束本轮,不输出总结文本。"
|
||
|
||
const mainAgentWrapUpDefault = "步骤预算即将耗尽,本次交互即将结束。不要再开始任何新的探索或操作。**只用一句纯文本**用简体中文向用户总结当前进展、核心结论以及建议的下一步。"
|
||
|
||
const genericWrapUpDefault = "预算耗尽,即将结束。先记录已完成但尚未保存的结果,然后**只用一句纯文本**用简体中文总结你做了什么、得到什么核心结论(这一句将作为本次运行的结果显示)。"
|
||
|
||
// WrapupDefault returns the built-in default wrap-up prompt for an agent key —
|
||
// used by the admin UI as the "restore default" value and empty-field placeholder.
|
||
func WrapupDefault(agentKey string) string {
|
||
if d, ok := wrapupDefaults[agentKey]; ok {
|
||
return d
|
||
}
|
||
return genericWrapUpDefault
|
||
}
|
||
|
||
// WrapupTurnsDefault returns the built-in wrap-up turn budget for an agent key —
|
||
// used by the admin UI as the "0 = default N" hint.
|
||
func WrapupTurnsDefault(agentKey string) int {
|
||
if n, ok := wrapupTurnDefaults[agentKey]; ok {
|
||
return n
|
||
}
|
||
return genericWrapupTurns
|
||
}
|
||
|
||
// resolveWrapup returns the effective wrap-up prompt: the DB override (if set and
|
||
// non-empty) over the built-in default.
|
||
func resolveWrapup(agentKey string) string {
|
||
if WrapupOverride != nil {
|
||
if t, ok := WrapupOverride(agentKey); ok && strings.TrimSpace(t) != "" {
|
||
return t
|
||
}
|
||
}
|
||
return WrapupDefault(agentKey)
|
||
}
|
||
|
||
// resolveWrapupTurns returns the effective wrap-up turn budget: a positive DB
|
||
// override over the built-in per-agent default.
|
||
func resolveWrapupTurns(agentKey string) int {
|
||
if WrapupMaxTurnsOverride != nil {
|
||
if v, ok := WrapupMaxTurnsOverride(agentKey); ok && v > 0 {
|
||
return v
|
||
}
|
||
}
|
||
return WrapupTurnsDefault(agentKey)
|
||
}
|
||
|
||
// wrapupSettlement builds the settlement config for an agent's run. Prompt and the
|
||
// turn budget are admin-editable per agent; disabled tools are code-owned policy so
|
||
// a user can't edit away the "stop probing" guardrail. Resolved fresh each run
|
||
// (reads DB live), so edits apply on the next run without a restart.
|
||
func wrapupSettlement(agentKey string, disabledTools []string) *harness.Settlement {
|
||
return &harness.Settlement{
|
||
Prompt: resolveWrapup(agentKey),
|
||
DisabledTools: disabledTools,
|
||
MaxTurns: resolveWrapupTurns(agentKey),
|
||
}
|
||
}
|
||
|
||
// ---------- 任务级超时收尾词(见 docs/任务级超时与收尾设计.md)----------
|
||
//
|
||
// 与 per-run 收尾词是【两套】:per-run 是"你这一次 run 的预算用完了";任务超时是
|
||
// "整个任务到点、即将结束"。语义常相反(尤其 planner:per-run 说"别停继续规划",
|
||
// 任务超时说"到点停止规划、做最后判定")。只给 worker/planner 配置。
|
||
|
||
// WrapupTaskTimeoutOverride / …TurnsOverride:任务超时收尾词与轮数的 DB 覆盖
|
||
// (wire 到 agents.task_timeout_wrapup_prompt / _max_turns,仅 worker/planner)。
|
||
var (
|
||
WrapupTaskTimeoutOverride func(agentKey string) (string, bool)
|
||
WrapupTaskTimeoutTurnsOverride func(agentKey string) (int, bool)
|
||
)
|
||
|
||
var taskTimeoutWrapupDefaults = map[string]string{
|
||
"worker": workerTaskTimeoutDefault,
|
||
"planner": plannerTaskTimeoutDefault,
|
||
}
|
||
|
||
const workerTaskTimeoutDefault = "**整个任务已达到时间上限,即将结束**(不是本次 run 的预算,而是整个探索结束的时刻)。这是最后一次机会。(1) 把已识别但尚未记录的内容【全部】保存。新资产用 insert_assets,探索结论与事实用 record_fact,确认的漏洞用 report_finding 记录。(2) 不要再开始任何新命令或探测。(3) **最后只用一句纯文本**用简体中文总结本次意图得到的核心结论。"
|
||
|
||
const plannerTaskTimeoutDefault = "**整个任务已达到时间上限,即将结束**(不是本轮,而是整个任务结束)。依据目前为止的【全部】事实与发现做最后一次目标判定。证据已证明达成的目标用 prove_goal 标记为 met(不要遗漏)。**不要再生成任何新意图**(现在派发意图也不会再被执行)。判定完成后立即结束,不输出总结文本。"
|
||
|
||
// TaskTimeoutWrapupDefault 返回某 agent 的任务超时内置默认收尾词(供后台占位/恢复默认)。
|
||
func TaskTimeoutWrapupDefault(agentKey string) string {
|
||
return taskTimeoutWrapupDefaults[agentKey] // 未配置(mainagent/chat)返回空串
|
||
}
|
||
|
||
// resolveTaskTimeoutWrapup:DB 覆盖(非空) > 内置默认。空串表示该 agent 无任务超时词
|
||
// (非 worker/planner),此时调用方应回退 per-run 词。
|
||
func resolveTaskTimeoutWrapup(agentKey string) string {
|
||
if WrapupTaskTimeoutOverride != nil {
|
||
if t, ok := WrapupTaskTimeoutOverride(agentKey); ok && strings.TrimSpace(t) != "" {
|
||
return t
|
||
}
|
||
}
|
||
return TaskTimeoutWrapupDefault(agentKey)
|
||
}
|
||
|
||
func resolveTaskTimeoutTurns(agentKey string) int {
|
||
if WrapupTaskTimeoutTurnsOverride != nil {
|
||
if v, ok := WrapupTaskTimeoutTurnsOverride(agentKey); ok && v > 0 {
|
||
return v
|
||
}
|
||
}
|
||
return resolveWrapupTurns(agentKey) // 默认沿用 per-run 轮数
|
||
}
|
||
|
||
// wrapupSettlementForTask builds settlement for a worker/planner run that is aware
|
||
// of the task deadline. See §5 of the design doc:
|
||
// - clamped=true → 本次 run 被任务 deadline 夹逼:因 Timeout 收尾=任务到点→任务超时词;
|
||
// 因 MaxTurns 收尾=夹逼窗口内步数先耗尽、任务还剩几分钟→回落 per-run 词。
|
||
// - clamped=false → 任务还早:两种 reason 都用 per-run 词(即退化为 wrapupSettlement)。
|
||
//
|
||
// 交给 harness 的 PromptByReason 在收尾时按【实际】reason 现场挑,无 build 时错配。
|
||
func wrapupSettlementForTask(agentKey string, disabledTools []string, clamped bool) *harness.Settlement {
|
||
perRun := resolveWrapup(agentKey)
|
||
st := &harness.Settlement{
|
||
Prompt: perRun, // 兜底(也是非 clamped 时两种 reason 的取值)
|
||
DisabledTools: disabledTools,
|
||
MaxTurns: resolveWrapupTurns(agentKey),
|
||
}
|
||
if clamped {
|
||
if tt := resolveTaskTimeoutWrapup(agentKey); tt != "" {
|
||
st.PromptByReason = map[harness.TerminalReason]string{
|
||
harness.ReasonTimeout: tt, // 任务到点
|
||
harness.ReasonMaxTurns: perRun, // 步数先耗尽、任务还剩时间
|
||
}
|
||
st.MaxTurns = resolveTaskTimeoutTurns(agentKey)
|
||
}
|
||
}
|
||
return st
|
||
}
|