From 5f0fac33aa94d2f6e7a77904c08a0a861bbd07cf Mon Sep 17 00:00:00 2001 From: Autumn-27 <53844290+Autumn-27@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:40:31 -0400 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E6=8C=89=E4=BB=BB=E5=8A=A1/?= =?UTF-8?q?=E6=84=8F=E5=9B=BE=E9=A2=84=E5=BB=BA=20agent=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E7=9B=AE=E5=BD=95=EF=BC=8Cworker=20=E5=85=8D=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=BB=BA=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ensureRunDir(base,taskID,intentID):os.MkdirAll 建 /[/i],返回路径;cmdOutDir 助手 - worker:CWD/ToolOutputDir/提示词指到 //i<意图id>/ (引擎预建),删 workerArtifactSubdir,新 workerArtifactSpec 文案 - planner/mainagent:补 WorkingDir=// + ToolOutputDir (此前根本没设 WorkingDir);artifactSpec 文案改为「本任务工作目录」 Co-Authored-By: Claude Opus 4.8 --- agent/mainagent.go | 16 ++++++++++------ agent/planner.go | 16 ++++++++++------ agent/worker.go | 43 +++++++++++++++++++++++++++++++++---------- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/agent/mainagent.go b/agent/mainagent.go index 49f6a5b..dd40a2b 100644 --- a/agent/mainagent.go +++ b/agent/mainagent.go @@ -4,12 +4,12 @@ import ( "context" "fmt" + "github.com/Autumn-27/artex/db" "github.com/Autumn-27/norma/agentcore" "github.com/Autumn-27/norma/llm" "github.com/Autumn-27/norma/permission" actool "github.com/Autumn-27/norma/tool" "github.com/Autumn-27/norma/transcript" - "github.com/Autumn-27/artex/db" ) // MainAgent is the thin human-interface orchestrator (docs §4.2 / §7). The human @@ -74,7 +74,9 @@ func (m *MainAgent) Chat(ctx context.Context, taskID int64, as *db.AssetStore, t base := append(tsx.MainAgentTools(), actool.DefaultTools()...) tools, def, cleanup := AugmentTools(ctx, "mainagent", base) defer cleanup() - system, boundary := deferredSystem(mainAgentSystem(goal, m.workDir), def) + // 本任务的工作目录 /,先建好。 + mainDir := ensureRunDir(m.workDir, taskID, 0) + system, boundary := deferredSystem(mainAgentSystem(goal, mainDir), def) opts := agentcore.Options{ Provider: m.prov, SystemPrompt: system, @@ -93,10 +95,12 @@ func (m *MainAgent) Chat(ctx context.Context, taskID int64, as *db.AssetStore, t BraveSearchAPIKey: m.webSearch.BraveKey, TavilySearchAPIKey: m.webSearch.TavilyKey, WebSearchProxy: m.webSearch.Proxy, - BashEnv: proxyEnv(m.proxyAddr, m.proxyCACert), // Bash 子命令默认走代理+信任 CA - MaxTurns: m.maxTurns, // 0 = unlimited (configurable in agent management) - Compaction: compactionConfig(m.window), // long chats stay within the window - Todos: actool.NewTodoStore(), // 会话级临时待办(TodoWrite),纯规划用,退出即丢 + BashEnv: proxyEnv(m.proxyAddr, m.proxyCACert), // Bash 子命令默认走代理+信任 CA + WorkingDir: mainDir, // 本任务工作目录 / + ToolOutputDir: cmdOutDir(mainDir), + MaxTurns: m.maxTurns, // 0 = unlimited (configurable in agent management) + Compaction: compactionConfig(m.window), // long chats stay within the window + Todos: actool.NewTodoStore(), // 会话级临时待办(TodoWrite),纯规划用,退出即丢 // 命中预算(步数)→ SDK 跑收尾:向用户输出一句进展总结。Prompt 与收尾轮数可后台编辑(默认 5 轮)。 Settlement: wrapupSettlement("mainagent", nil), } diff --git a/agent/planner.go b/agent/planner.go index d281aa8..46e6047 100644 --- a/agent/planner.go +++ b/agent/planner.go @@ -8,12 +8,12 @@ import ( "sync" "time" + "github.com/Autumn-27/artex/db" "github.com/Autumn-27/norma/agentcore" "github.com/Autumn-27/norma/llm" "github.com/Autumn-27/norma/permission" actool "github.com/Autumn-27/norma/tool" "github.com/Autumn-27/norma/transcript" - "github.com/Autumn-27/artex/db" ) // Planner is the event-driven LLM planner (docs §4.3): each time the asset or @@ -197,7 +197,9 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts if tc.Final { situational += "\n\n【任务终局收尾(本轮特殊指令,覆盖上面的常规规划流程)】:" + resolveTaskTimeoutWrapup("planner") } - system, boundary := deferredSystem(plannerSystem(goal, p.workDir)+situational, def) + // 本任务的工作目录 /,先建好。 + taskDir := ensureRunDir(p.workDir, taskID, 0) + system, boundary := deferredSystem(plannerSystem(goal, taskDir)+situational, def) // planner 无自身墙钟预算;有 deadline 时把 MaxDuration 夹逼到剩余,让在跑的规划轮在 // 任务到点时进收尾(因超时→任务超时词,因步数→per-run 词)。 maxDur, clamped := clampMaxDuration(tc.DeadlineUnix, 0) @@ -223,10 +225,12 @@ func (p *Planner) Plan(ctx context.Context, taskID int64, as *db.AssetStore, ts BraveSearchAPIKey: p.webSearch.BraveKey, TavilySearchAPIKey: p.webSearch.TavilyKey, WebSearchProxy: p.webSearch.Proxy, - BashEnv: proxyEnv(p.proxyAddr, p.proxyCACert), // Bash 子命令默认走代理+信任 CA - MaxTurns: p.maxTurns, // 0 = unlimited (configurable in agent management) - MaxDuration: maxDur, // 0=不限;有 deadline 时=距 deadline 剩余 - Compaction: compactionConfig(p.window), + BashEnv: proxyEnv(p.proxyAddr, p.proxyCACert), // Bash 子命令默认走代理+信任 CA + WorkingDir: taskDir, // 本任务工作目录 / + ToolOutputDir: cmdOutDir(taskDir), + MaxTurns: p.maxTurns, // 0 = unlimited (configurable in agent management) + MaxDuration: maxDur, // 0=不限;有 deadline 时=距 deadline 剩余 + Compaction: compactionConfig(p.window), // 跨唤醒共享的规划待办:让串行链在多轮之间保留(session 是新的,store 不是)。 Todos: p.todoFor(ts.ID()), // 命中【本轮】步数预算→ SDK 跑收尾:把本轮已想清楚的结论落地(该派的 add_intent、 diff --git a/agent/worker.go b/agent/worker.go index 9495908..03d6c96 100644 --- a/agent/worker.go +++ b/agent/worker.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "fmt" + "os" "path/filepath" + "strconv" "time" "github.com/Autumn-27/artex/db" @@ -154,17 +156,36 @@ func workerTrafficBlock(proxyAddr string) string { // artifactSpec is 段 [C]: the code-owned, non-editable tail appended to every // pentest agent's prompt — intermediate artifacts must land in the shared work // dir, never /tmp. Guaranteed present regardless of how the DB body is edited. -func artifactSpec(workDir string) string { - return "\n\n**中间产物输出规约**:脚本、payload、抓到的响应体、临时数据等一切中间产物,**一律写到共享工作目录 " + workDir + "**(这是所有 agent 共用的 CWD,相对路径即写在这里,也可用该绝对路径)——**不要写 /tmp、不要用其它绝对路径**。" +func artifactSpec(dir string) string { + return "\n\n**中间产物输出规约**:脚本、payload、抓到的响应体、临时数据等一切中间产物,**一律写到本任务工作目录 " + dir + "**(相对路径即写在这里,也可用该绝对路径)——**不要写 /tmp、不要用其它绝对路径**。" } -// workerArtifactSubdir is the worker-only addendum to artifactSpec: put a run's -// artifacts under an i/ subdir to avoid concurrent name collisions. -const workerArtifactSubdir = "为避免与其他 work 撞名,把本次产物放到子目录 i<意图id>/ 下(如 i123/exploit.py)。" +// workerArtifactSpec is the worker's 段 [C]: its per-intent run dir is pre-created +// by the engine (ensureRunDir), so it just writes relative paths there — no manual +// mkdir, no cross-worker name collisions. +func workerArtifactSpec(runDir string) string { + return "\n\n**中间产物输出规约**:脚本、payload、抓到的响应体、临时数据等一切中间产物,**一律写到本次意图的专属工作目录 " + runDir + "**(已自动建好,直接用相对路径写在这里即可,无需再手动建目录)——**不要写 /tmp、不要用其它绝对路径**。" +} -func workerSystem(proxyAddr, workDir string) string { +// ensureRunDir builds and creates an agent's working directory under base: +// / for planner/main; //i for a worker +// (intentID<=0 → task dir only). Best-effort mkdir — on failure, writes fail the +// same way an unwritable CWD would. +func ensureRunDir(base string, taskID, intentID int64) string { + dir := filepath.Join(base, strconv.FormatInt(taskID, 10)) + if intentID > 0 { + dir = filepath.Join(dir, "i"+strconv.FormatInt(intentID, 10)) + } + _ = os.MkdirAll(dir, 0o755) + return dir +} + +// cmdOutDir is the SDK large-tool-output spill dir under an agent's run dir. +func cmdOutDir(dir string) string { return filepath.Join(dir, "cmd-output") } + +func workerSystem(proxyAddr, runDir string) string { body := renderSystem("worker", workerDefaultTmpl, WorkerVars{ProxyAddr: proxyAddr}) - return body + workerTrafficBlock(proxyAddr) + artifactSpec(workDir) + workerArtifactSubdir + return body + workerTrafficBlock(proxyAddr) + workerArtifactSpec(runDir) } // renderIntentTask formats the claimed intent for the worker's SYSTEM prompt: the @@ -235,8 +256,10 @@ func (w *Worker) Execute(ctx context.Context, name string, taskID int64, as *db. // 意图是 worker 的全部任务,放进 system prompt(抗 compaction、整轮常驻),而不是 // user 输入——与 planner 把关键态势放 system 一致。落在指令之后、deferred 块之前。 // 全局态势(graphOverviewData)也带上,但仅供了解大局;意图放在最后、最醒目的位置。 + // 本次意图的专属工作目录 //i,引擎侧先建好。 + runDir := ensureRunDir(w.workDir, taskID, intent.ID) overview := renderWorkerGraphOverview(tsx.graphOverviewData()) - system, boundary := deferredSystem(workerSystem(w.proxyAddr, w.workDir)+overview+renderIntentTask(intent), def) + system, boundary := deferredSystem(workerSystem(w.proxyAddr, runDir)+overview+renderIntentTask(intent), def) // 任务级 deadline(经 ctx 注入)夹逼本 run 的墙钟预算 + 决定收尾词(见 taskclock.go)。 tc := taskClockFrom(ctx) maxDur, clamped := clampMaxDuration(tc.DeadlineUnix, w.runTimeout) @@ -266,7 +289,7 @@ func (w *Worker) Execute(ctx context.Context, name string, taskID int64, as *db. WebSearchProxy: w.webSearch.Proxy, // Bash 子命令的 HTTP 默认走记录代理 + 信任其 CA(工具无需 -x/-k)。 BashEnv: proxyEnv(w.proxyAddr, w.proxyCACert), - WorkingDir: w.workDir, + WorkingDir: runDir, MaxTurns: w.maxTurns, // 0 = unlimited (configurable in agent management) // 墙钟预算,轮边界判,不打断半路;0 = 不限。有任务级 deadline 时夹逼到 min(自身预算, // 距 deadline 剩余),让本 run 在任务到点时自然进收尾(见 taskclock.go)。 @@ -277,7 +300,7 @@ func (w *Worker) Execute(ctx context.Context, name string, taskID int64, as *db. Settlement: settle, // large tool output spills to cmd-output/ with a head + pointer (SDK tool.Capture); // full output preserved on disk. 截断上限用 SDK 默认(30000 字符)。 - ToolOutputDir: filepath.Join(w.workDir, "cmd-output"), + ToolOutputDir: cmdOutDir(runDir), Compaction: compactionConfig(w.window), // long tool-heavy runs stay within the window Todos: actool.NewTodoStore(), // 会话级临时待办(TodoWrite),纯规划用,退出即丢 }