From bf2410baee06e85673c959e604c70357fefe1895 Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Mon, 17 Aug 2026 09:26:59 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20goal=5Fmet=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E4=B8=8D=E7=BB=91=E4=BB=BB=E4=BD=95=20agent=20+=20=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=20planner=20=E7=9B=B8=E5=85=B3=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - goal_met 绕过逐个 prove_goal 直接宣布整个任务完成,权重大、有误判风险,又与 「prove_goal 标记最后一个目标→自动收官」重复,故默认不给任何 agent(仍入目录、 web 可见、可手动按需绑)。 - toolcatalog 新增 defaultUnbound 集合:goal_met seed 时绑定强制为空 [](保留在 PlannerTools base 里,手动绑回后运行时仍可用)。 - db 新增 RemoveAgentFromTool(agentKey,toolKey);server 新增一次性迁移 unbindGoalMetDefault(flag goal_met_unbind_default_v1)剥掉旧库 goal_met 的 planner 绑定,只剥 planner、不动用户手动加的其它绑定,flag 守卫故重启不重复。 - planner 提示词去掉两处 goal_met 引导(system 模板 + 每轮 input 结尾),保留 prove_goal 与自动收官说明。任务收官不受影响(prove_goal 自动置 GoalMet)。 --- agent/planner.go | 4 ++-- agent/toolcatalog.go | 19 ++++++++++++++++--- db/tools.go | 8 ++++++++ server/orchestration.go | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/agent/planner.go b/agent/planner.go index 6ecd582..8a98bd8 100644 --- a/agent/planner.go +++ b/agent/planner.go @@ -210,7 +210,7 @@ const plannerDefaultTmpl = `你是一个授权渗透测试系统的"规划者" - open_intents / running_intents / recent_done_intents——"哪些方向已经有意图在覆盖/已尝试"。每个意图还带 **parents(上游:它派生自哪些事实/意图)和 yields(下游:它产生了哪些事实/发现)**——这就是探索图的**血缘关系**,据此理解"哪些事实来自哪个方向、能否综合成新方向"。recent_facts 里每个事实带 **from_intent**(由哪个意图产生)。 - sites_without_endpoints / findings——"哪些方向【可能】需要探索"。 - 只有需要某一片的细节时,才**按需**调 list_assets(pull 模式:可用 q 关键字搜索,可叠加 type/company_id/task_id 过滤,分页 limit/offset;或用 id/ids 直接取)、asset_neighbors、list_findings。资产图全局共享,别默认拉全量。资产中可能包含非本次任务涉及到的资产,所以需要主要出现非本次任务相关的资产时忽略这些资产。 -2. 判目标(核心职责):graph_overview 的 goals 字段已含目标与状态;对已被某发现/事实证明的未达成目标,调 prove_goal(goal_id,evidence_id,reason) 标记 met。**当你用 prove_goal 标记的这一个恰好是最后一个未完成目标时,系统会自动判定整个任务完成、无需你再调 goal_met**。goal_met 仅在你想【绕过逐个 prove_goal、直接从全局判定任务已达成】时才用。 +2. 判目标(核心职责):graph_overview 的 goals 字段已含目标与状态;对已被某发现/事实证明的未达成目标,调 prove_goal(goal_id,evidence_id,reason) 标记 met。**当你用 prove_goal 标记的这一个恰好是最后一个未完成目标时,系统会自动判定整个任务完成**——收官完全由逐个 prove_goal 驱动,你无需、也没有别的“一键完成”手段。 2.5. **(可选,仅限开局、极轻量)探测理解**:你具备 Bash 等执行能力,它的**唯一正当用途**是——当**图里几乎还没有事实**(recent_facts 基本为空、任务刚开始)、仅凭态势无法把初始意图描述具体时,对目标做**极少量、只读**的探测(如 1–2 次 curl 看首页/指纹),据此产出更精准的**初始意图**。 ⚠️ **牢记你的身份边界:你是"规划者",不是"执行者"。你在这里做的一切都只为【生成/说清意图】,绝不是【在 plan 里把活干了】。** 探测的**唯一合法产物是一句更精准的意图描述**——绝不能是漏洞的发现、验证、利用,也不能是端点/目录/参数的枚举结果。任何"我顺手把这个也测了/确认了"的念头都是越界:那是 worker 在 work 阶段该做的事,你只需把它**写成一个意图派下去**。 **三条硬性边界,务必守住**: @@ -328,7 +328,7 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts if len(triggers) == 0 { lead = "本轮是**定时巡检(心跳到点)/无具体变动信号**的唤醒——图不一定有新变动。顺带复查在跑意图:长时间无进展或跑偏的用 steer_work 纠偏、方向整个错的用 kill_work 止损;再判定目标、决定是否补方向:" } - input := lead + situational + "\n\n据上面的态势,判定目标。**本轮若无未被覆盖的新方向,直接结束即可(生成 0 个意图是正常且常见的,尤其刚派完意图在等 worker 产出时)。绝不要为了“结束本轮”去调 goal_met——goal_met 会【立即结束整个任务】,只在你确认目标已【真正达成】(已拿到目标成果/已确认目标漏洞)时才调;未达成就用 prove_goal 逐个标记、或什么都不调直接结束。**" + + input := lead + situational + "\n\n据上面的态势,判定目标。**本轮若无未被覆盖的新方向,直接结束即可(生成 0 个意图是正常且常见的,尤其刚派完意图在等 worker 产出时)。目标已【真正达成】(已拿到目标成果/已确认目标漏洞)时用 prove_goal 逐个标记;未达成就什么都不调、直接结束本轮。**" + renderPlannerTodos(opts.Todos.List()) // 有 deadline 夹逼时加硬 ctx 兜底(软预算 + grace),防单轮卡死绕过轮边界软超时。 runCtx := ctx diff --git a/agent/toolcatalog.go b/agent/toolcatalog.go index 92e5c46..d564efe 100644 --- a/agent/toolcatalog.go +++ b/agent/toolcatalog.go @@ -49,8 +49,17 @@ func builtinToolsByAgent() map[string][]actool.CoreTool { } } -// BuiltinToolSeeds 把三个 agent 的内置工具集去重合并成 seed 列表:同名工具(如 -// list_assets 三者都有)合成一条,Agents 取并集。顺序稳定(mainagent→planner→worker)。 +// defaultUnbound:这些 system 工具会照常入目录(web 端可见、可手动按 agent 勾选),但 +// 默认【不绑任何 agent】——ToolResolve 对空绑定的工具对所有 agent 一律丢弃,须显式 opt-in。 +// 之所以仍留在某个 agent 的 base 工具集里(如 goal_met 在 PlannerTools):一是让 seed 能 +// 构造它拿到 desc/schema,二是用户手动绑回后运行时 base 里有它、ToolResolve 才留得住。 +// +// goal_met:绕过逐个 prove_goal、直接从全局宣布【整个任务完成】,权重大且有误判风险,又与 +// 「prove_goal 标记最后一个目标 → 自动收官」重复,故默认不给任何 agent,需要时再手动绑。 +var defaultUnbound = map[string]bool{"goal_met": true} + +// BuiltinToolSeeds 把各 agent 的内置工具集去重合并成 seed 列表:同名工具(如 list_assets +// 多个 agent 都有)合成一条,Agents 取并集;defaultUnbound 里的工具则强制绑定为空。 func BuiltinToolSeeds() []ToolSeed { byAgent := builtinToolsByAgent() order := []string{"mainagent", "goals", "planner", "worker", "auto", "pentest"} @@ -76,11 +85,15 @@ func BuiltinToolSeeds() []ToolSeed { out := make([]ToolSeed, 0, len(keys)) for _, k := range keys { a := m[k] + agents := a.agents + if defaultUnbound[k] { + agents = []string{} // 入目录、可手动绑,但默认不给任何 agent(存 [] 而非 null,与其它工具一致) + } out = append(out, ToolSeed{ Key: k, Desc: a.tool.Description(), Schema: a.tool.InputSchema(), - Agents: a.agents, + Agents: agents, }) } return out diff --git a/db/tools.go b/db/tools.go index 4338ea9..5e36d4c 100644 --- a/db/tools.go +++ b/db/tools.go @@ -61,6 +61,14 @@ func (d *DB) RemoveAgentFromToolBindings(agentKey string) error { return err } +// RemoveAgentFromTool strips one agent key from a SINGLE tool's `agents` array — +// used by one-time migrations that change a tool's default binding on existing DBs +// (SeedTool is first-insert-only, so a changed default never reaches a seeded row). +func (d *DB) RemoveAgentFromTool(agentKey, toolKey string) error { + _, err := d.Exec(`UPDATE tools SET agents = agents - $1 WHERE key=$2 AND agents ? $1`, agentKey, toolKey) + return err +} + // UpsertToolForce overwrites a tool row with the given code-default values (used by // the per-tool "reset" action). It resets description/schema/agents and re-enables // the tool, but keeps system=true. diff --git a/server/orchestration.go b/server/orchestration.go index 7f314a9..d3c7f76 100644 --- a/server/orchestration.go +++ b/server/orchestration.go @@ -397,6 +397,7 @@ func (s *Server) seedOrchestrationTools() { s.seedAutoDefaultBindings() s.seedPlannerDefaultBindings() s.seedAutoReportFindingBinding() + s.unbindGoalMetDefault() // 注:pentest 的默认工具绑定无需迁移——BuiltinToolSeeds 在全新初始化时就把 // list_assets/insert_assets/report_finding/list_findings/list_companies 连同 // pentest 一起 seed 好了(项目尚无旧库,不做迁移)。 @@ -434,6 +435,23 @@ func (s *Server) refreshBuiltinToolSchemas() { log.Printf("[tools] 已刷新 orchestration/platform 工具 schema 到代码默认(一次性)") } +// unbindGoalMetDefault removes goal_met's default "planner" binding ONCE (guarded by +// a settings flag), so existing DBs match the new default of NO agent. goal_met bypasses +// per-goal prove_goal to declare the whole task done — powerful/risky and redundant with +// the prove_goal→auto-complete path — so it ships unbound; users can re-bind it per agent +// in the UI. A user's own binding to another agent is untouched (we only strip planner). +func (s *Server) unbindGoalMetDefault() { + const flag = "goal_met_unbind_default_v1" + if v, _, _ := s.m.pg.GetSetting(flag); v == "true" { + return + } + if err := s.m.pg.RemoveAgentFromTool("planner", "goal_met"); err != nil { + log.Printf("[tools] goal_met 解绑 planner 失败: %v", err) + return + } + _ = s.m.pg.SetSetting(flag, "true") +} + // seedAutoReportFindingBinding adds "auto" to report_finding's binding ONCE so // conversation-context agents can call it without requiring an intent_id. func (s *Server) seedAutoReportFindingBinding() {