thincoder 0.12.59 → 0.12.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -4
- package/README.md +10 -8
- package/bin/thincoder.mjs +99 -133
- package/package.json +6 -4
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +45 -21
- package/src/acp.mjs +6 -1
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +59 -137
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +156 -359
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +27 -7
- package/src/agent/completion.mjs +17 -11
- package/src/agent/dispatch.mjs +145 -27
- package/src/agent/helpers.mjs +107 -13
- package/src/agent/record-results.mjs +55 -11
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +242 -0
- package/src/agent/setup-reminders.mjs +69 -0
- package/src/agent/setup.mjs +107 -127
- package/src/agent/spawn-child.mjs +55 -13
- package/src/agent-tools/advisor-async.mjs +346 -0
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +167 -116
- package/src/agent-tools/async-settle.mjs +191 -0
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +139 -107
- package/src/agent-tools/design-token.mjs +117 -0
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +24 -29
- package/src/agent-tools/escalate-async.mjs +289 -0
- package/src/agent-tools/read-history.mjs +166 -32
- package/src/agent-tools/recent-changes.mjs +2 -1
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +137 -34
- package/src/agent-tools/subagent-actions.mjs +180 -133
- package/src/agent-tools/subagent-async.mjs +184 -177
- package/src/agent-tools/subagent-panel.mjs +160 -0
- package/src/agent-tools/subagent-run.mjs +205 -0
- package/src/agent-tools/subagent-scheduler.mjs +100 -27
- package/src/agent-tools/subagent-spawn.mjs +453 -0
- package/src/agent-tools/subagent.mjs +256 -578
- package/src/agent-tools/verify.mjs +119 -292
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +89 -205
- package/src/cli/distill-command.mjs +12 -6
- package/src/cli/make-agent.mjs +26 -8
- package/src/cli/memory-command.mjs +4 -3
- package/src/cli/permission.mjs +2 -2
- package/src/cli/setup-wizard.mjs +42 -17
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +180 -63
- package/src/context.mjs +5 -147
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +128 -0
- package/src/distill.mjs +11 -11
- package/src/expand-home.mjs +16 -0
- package/src/explore-distill.mjs +155 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +72 -32
- package/src/memory/core.mjs +6 -193
- package/src/memory/delete.mjs +236 -0
- package/src/memory/docs.mjs +68 -54
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/schema.mjs +15 -3
- package/src/memory.mjs +3 -1
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/peer-domains.mjs +265 -0
- package/src/peer-instances.mjs +231 -0
- package/src/prompt-overlays.mjs +82 -0
- package/src/prompts/advisor-design.md +18 -75
- package/src/prompts/advisor-round1.md +14 -67
- package/src/prompts/advisor-round2.md +15 -51
- package/src/prompts/advisor-round3.md +15 -51
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +5 -23
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +55 -0
- package/src/prompts/persona-engineering.md +54 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +18 -98
- package/src/provider/errors.mjs +101 -0
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/retry.mjs +8 -45
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- package/src/session-gc.mjs +214 -0
- package/src/session-guard.mjs +47 -0
- package/src/session-rename.mjs +38 -0
- package/src/session-slots.mjs +188 -60
- package/src/session.mjs +104 -124
- package/src/token-ttl.mjs +274 -0
- package/src/tools/{system.mjs → bash.mjs} +19 -221
- package/src/tools/checklist-sync.mjs +181 -0
- package/src/tools/checklist.mjs +52 -39
- package/src/tools/edit-batch.mjs +109 -10
- package/src/tools/edit-diff.mjs +110 -27
- package/src/tools/edit.md +17 -12
- package/src/tools/execute.mjs +31 -4
- package/src/tools/file.mjs +41 -16
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +23 -34
- package/src/tools/glob-dialect.mjs +130 -0
- package/src/tools/glob.md +3 -3
- package/src/tools/grep.md +1 -1
- package/src/tools/index.mjs +9 -8
- package/src/tools/ops.mjs +188 -3
- package/src/tools/patch.mjs +3 -3
- package/src/tools/question.md +4 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -2
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/tools/shared.mjs +14 -13
- package/src/tools/wait_for.md +22 -0
- package/src/tui/agent-turn.mjs +36 -228
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-config.mjs +142 -30
- package/src/tui/cmd-eng.mjs +28 -40
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-mcp.mjs +8 -2
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-new.mjs +3 -2
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-session.mjs +19 -4
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-think.mjs +10 -10
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/cmd-upgrade.mjs +19 -4
- package/src/tui/config-helpers.mjs +28 -16
- package/src/tui/distill-cmd.mjs +1 -1
- package/src/tui/index.mjs +40 -38
- package/src/tui/interaction.mjs +3 -3
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +52 -9
- package/src/tui/pickers.mjs +28 -392
- package/src/tui/render-frame.mjs +32 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +12 -9
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/startup.mjs +4 -0
- package/src/tui/subagent-blocks.mjs +106 -295
- package/src/tui/subagent-children.mjs +162 -0
- package/src/tui/subagent-freeze.mjs +169 -0
- package/src/tui/subagent-panel.mjs +24 -31
- package/src/tui/suspension-drive.mjs +297 -0
- package/src/tui/tool-args.mjs +7 -5
- package/src/tui/tool-display.mjs +143 -0
- package/src/tui/tool-events.mjs +56 -185
- package/src/tui/tui-lifecycle.mjs +46 -4
- package/src/tui/update-notice.mjs +4 -0
- package/src/tui/wizard.mjs +61 -21
- package/src/tui/wrapped-spawn.mjs +38 -0
- package/src/prompts/coder.md +0 -56
- package/src/prompts/discipline.md +0 -102
- package/src/prompts/eng-coder.md +0 -44
- package/src/prompts/engineering-sub.md +0 -34
- package/src/prompts/engineering.md +0 -340
- package/src/prompts/explore.md +0 -21
- package/src/prompts/main.md +0 -56
- package/src/prompts/methodology-template.md +0 -58
- package/src/prompts/plan.md +0 -16
- package/src/prompts/system.md +0 -63
- package/src/tools/pdf-parse-text.mjs +0 -497
- package/src/tools/pdf-parse-xref.mjs +0 -499
- package/src/tools/pdf.mjs +0 -155
- package/src/tools/read_pdf.md +0 -21
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subagent-spawn.mjs — spawn 路径装配(2026-09-05 module-split:subagent.mjs 726
|
|
3
|
+
* > 500 硬限——spawn 前置 helpers(summarizeEngTaskBook/effectiveSubagentModel/
|
|
4
|
+
* resolveDesignSlot)+ §20 调度参数准入(prepareScheduling)+ child 装配
|
|
5
|
+
* (buildSpawnChild)verbatim 迁入(仅闭包变量参数化),语义零变;executeAsyncSpawn
|
|
6
|
+
* 另在 subagent-run.mjs。subagent.mjs execute 经本文件 import 调用。
|
|
7
|
+
* 2026-09-07:executeConsumeDesignAction 消费执行器(token 链终消费制——与 spawn 侧
|
|
8
|
+
* slot 族同域——ENGINEERING-MODE.md §2.6 F1——removeDesignTokenSlot 自 token-ttl.mjs)。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { resolve } from "node:path"
|
|
12
|
+
import { existsSync, statSync } from "node:fs"
|
|
13
|
+
import {
|
|
14
|
+
createAgent,
|
|
15
|
+
readonlyToolNames, escapeXml,
|
|
16
|
+
} from "../agent.mjs"
|
|
17
|
+
import { makeRelay, wrapChildCallbacks, relayPrefixOf } from "../agent/spawn-child.mjs"
|
|
18
|
+
import { validateDesignToken } from "./advisor.mjs"
|
|
19
|
+
import { tokenExpired, removeDesignTokenSlot, reconcileEngTokensFromSlot, persistEngTokens } from "../token-ttl.mjs"
|
|
20
|
+
import { resolveChildProvider, buildChildRunOpts, enqueueAsk } from "./subagent-async.mjs"
|
|
21
|
+
import { nextSubagentId } from "./subagent-scheduler.mjs"
|
|
22
|
+
import {
|
|
23
|
+
normalizeFileList, describeBlockers, assertNoDepCycle, depInfo,
|
|
24
|
+
} from "./subagent-scheduler.mjs"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* §18.7 D-TS5 (A2): mechanically summarize the parent spawn task book for the
|
|
28
|
+
* audit spawn — the three audit-relevant elements VERBATIM (design doc paths /
|
|
29
|
+
* affected-file list / acceptance criteria); verbose context/background is
|
|
30
|
+
* dropped (the auditor can read the design docs themselves — they stay
|
|
31
|
+
* available outside this input). Independence preserved: the input is
|
|
32
|
+
* _engTaskInput (mechanically kept by the parent spawn) — never the
|
|
33
|
+
* eng-coder's self-report. Sections are located by header marker, prioritizing
|
|
34
|
+
* header lines (structured task books: "## 文件清单 …") and falling back to
|
|
35
|
+
* inline markers (flat one-line task books); a section runs to the next header
|
|
36
|
+
* of the SAME OR HIGHER level ("## 文件清单" survives a "### 修改" sub-header).
|
|
37
|
+
* Marker not found → the section is reported as missing (never fabricate).
|
|
38
|
+
*/
|
|
39
|
+
function summarizeEngTaskBook(taskInput) {
|
|
40
|
+
if (!taskInput) return "(unavailable)"
|
|
41
|
+
const SECTIONS = [
|
|
42
|
+
{ name: "Design docs involved", markers: [/Docs? involved/i, /涉及文档/] },
|
|
43
|
+
{ name: "Affected-file list", markers: [/Files? (?:list|to (?:modify|change)|modified)/i, /受影响文件/, /文件清单/, /涉及文件/] },
|
|
44
|
+
{ name: "Acceptance criteria", markers: [/Acceptance(?: criteria)?/i, /验收标准/] },
|
|
45
|
+
]
|
|
46
|
+
const lines = taskInput.split("\n")
|
|
47
|
+
const headerLevel = (l) => {
|
|
48
|
+
const m = l.match(/^\s*(#{1,6})\s/)
|
|
49
|
+
return m ? m[1].length : 0
|
|
50
|
+
}
|
|
51
|
+
const headerIdx = lines.map((l, i) => (headerLevel(l) > 0 ? i : -1)).filter((i) => i >= 0)
|
|
52
|
+
const boundsFor = (from, level) => {
|
|
53
|
+
for (const j of headerIdx) {
|
|
54
|
+
if (j > from && (level === 0 || headerLevel(lines[j]) <= level)) return j
|
|
55
|
+
}
|
|
56
|
+
return lines.length
|
|
57
|
+
}
|
|
58
|
+
const out = []
|
|
59
|
+
for (const { name, markers } of SECTIONS) {
|
|
60
|
+
let from = -1
|
|
61
|
+
let level = 0
|
|
62
|
+
for (const i of headerIdx) {
|
|
63
|
+
if (markers.some((m) => m.test(lines[i]))) { from = i; level = headerLevel(lines[i]); break }
|
|
64
|
+
}
|
|
65
|
+
if (from === -1) {
|
|
66
|
+
for (let i = 0; i < lines.length; i++) {
|
|
67
|
+
if (markers.some((m) => m.test(lines[i]))) { from = i; level = 0; break }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (from === -1) { out.push(`${name}: (not found in the parent task book)`); continue }
|
|
71
|
+
const body = lines.slice(from, boundsFor(from, level)).join("\n").trim()
|
|
72
|
+
out.push(body || `${name}: (empty section)`)
|
|
73
|
+
}
|
|
74
|
+
return out.join("\n\n")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Effective subagent model override for a role (CLI parity shared with VS Code):
|
|
79
|
+
* priority — subagent tool `model` arg > config.agent.subagentModels[role] > config.agent.subagentModel > null (inherit parent).
|
|
80
|
+
* The literal "default" (case-insensitive) means "no tool-arg override → run the
|
|
81
|
+
* chain" — same as omitting the parameter (2026-09-05 user ruling).
|
|
82
|
+
*/
|
|
83
|
+
export function effectiveSubagentModel(parent, role, modelArg) {
|
|
84
|
+
// "default" alias (2026-09-05 user ruling — ARCHITECTURE.md 子 agent 模型指定):
|
|
85
|
+
// the literal "default", matched case-insensitively, explicitly declares "no
|
|
86
|
+
// override at the tool-arg level → run the default priority chain" (type-level
|
|
87
|
+
// subagentModels[role] → global subagentModel → null = inherit parent). It is
|
|
88
|
+
// equivalent to omitting the parameter / passing ""/null/undefined (which fall
|
|
89
|
+
// through below). Any other value still overrides.
|
|
90
|
+
if (modelArg && String(modelArg).toLowerCase() !== "default") return modelArg
|
|
91
|
+
const cfg = parent.config?.agent ?? {}
|
|
92
|
+
return cfg.subagentModels?.[role] ?? cfg.subagentModel ?? null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the design-token slot for an eng-coder spawn (2026-09-01 multi-design, FR3):
|
|
97
|
+
* - designId given → exact slot lookup (no match = explicit error, never a fuzzy guess)
|
|
98
|
+
* - designId omitted → exactly ONE slot must exist (single-design compatibility); with
|
|
99
|
+
* multiple slots we refuse rather than pick one (T16: never silently aim the wrong design)
|
|
100
|
+
* Returns { token } on success; throws with a parent-actionable message otherwise.
|
|
101
|
+
* R16 (2026-09-06): the slot Map is the authority; the format/TTL check itself stays in
|
|
102
|
+
* validateDesignToken (2026-09-06: HMAC 防伪层已删——token 为无签名流程凭证——见
|
|
103
|
+
* ENGINEERING-MODE.md 2026-09-06 段)。
|
|
104
|
+
* DESIGN-TOKEN-SETTLEMENT D2(2026-09-08):内存 Map miss(指定 designId 不在 / 空 Map)
|
|
105
|
+
* 时回读槽文件权威台账 reconcile + 判定(reconcileEngTokensFromSlot——与 dispatch 写门
|
|
106
|
+
* D3 同源)——覆盖"进程重启后槽有但 Map 未及回填/缓存与槽不一致"(槽文件 = 权威台账)。
|
|
107
|
+
* TTL 过滤保留(reconcile 只并入未过期项)。单值镜像 `_engDesignToken` 已随 D3 退役——
|
|
108
|
+
* 本门禁不再读镜像(AC3 零命中)。
|
|
109
|
+
*/
|
|
110
|
+
export function resolveDesignSlot(parent, designIdArg) {
|
|
111
|
+
let slots = parent._engDesignTokens
|
|
112
|
+
const hasSlots = slots instanceof Map && slots.size > 0
|
|
113
|
+
// D2:内存 miss(指定 designId 不在 或 空 Map)→ 权威槽回读 reconcile + 判定
|
|
114
|
+
if (designIdArg ? !(hasSlots && slots.has(designIdArg)) : !hasSlots) {
|
|
115
|
+
const rec = reconcileEngTokensFromSlot(parent)
|
|
116
|
+
if (rec && rec !== slots) slots = rec
|
|
117
|
+
}
|
|
118
|
+
// F2d (§29.1): both refusal branches carry the HELD id list — after a
|
|
119
|
+
// persistence restore the parent has no digest to look the id up in; the error
|
|
120
|
+
// list is the only discovery path (ids are not credentials — the token is).
|
|
121
|
+
const size = slots instanceof Map ? slots.size : 0
|
|
122
|
+
const heldIds = size > 0 ? [...slots.keys()].join(", ") : "(none)"
|
|
123
|
+
if (designIdArg) {
|
|
124
|
+
if (!(slots instanceof Map) || !slots.has(designIdArg)) {
|
|
125
|
+
throw new Error(`designId not found — no approved design review holds this id. Run advisor with type='design' again and pass the designId echoed with the token. (session holds ${size} approved design slot(s); held design ids: ${heldIds})`)
|
|
126
|
+
}
|
|
127
|
+
return { token: slots.get(designIdArg) }
|
|
128
|
+
}
|
|
129
|
+
if (size > 1) {
|
|
130
|
+
throw new Error(`Multiple approved designs in this session (${size}) — pass the designId parameter (echoed with each token) to choose which design this eng-coder spawn belongs to. Held design ids: ${heldIds}`)
|
|
131
|
+
}
|
|
132
|
+
if (size === 1) return { token: [...slots.values()][0] }
|
|
133
|
+
throw new Error("Invalid or missing design token — run advisor with type='design' first and pass the returned token as designToken.")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* consume-design 动作执行器(2026-09-07 token 链终消费制——ENGINEERING-MODE.md §2.6 F1):
|
|
138
|
+
* 父侧验收核销时显式调用——读槽值 → removeDesignTokenSlot(token-ttl.mjs——移除该
|
|
139
|
+
* designId 槽)→ 消费后同 designId 再 spawn = resolveDesignSlot not found 机械拒。调用
|
|
140
|
+
* 形态定死(评审 #3):参数 designId(单设计会话可省略——FR3 spawn 同款语义);未知
|
|
141
|
+
* designId 与重复消费同款 no-op 提示(幂等——不报错)。DESIGN-TOKEN-SETTLEMENT D3
|
|
142
|
+
* (2026-09-08):单值镜像 `_engDesignToken` 已退役——无镜像兼容值清/兜底读(AC3)。
|
|
143
|
+
* consume 落盘对称(2026-09-08 D1 段 + AC7):删内存槽后当场 persistEngTokens 同步
|
|
144
|
+
* 落盘删除(旧台账不留盘——防 D2 门禁 miss 回读复活已消费 token);落盘失败回滚
|
|
145
|
+
* 内存槽 + 抛错(不留半消费态,可重试——D1 评审 #1 同款失败语义)。
|
|
146
|
+
* dispatch 分类(评审 #7d):非只读控制动作——depth-0 + 工程模式
|
|
147
|
+
* 限定(受限变体门在 subagent.mjs 分流处;本器自持工程模式门)——planMode 拒绝
|
|
148
|
+
* (dispatch 不豁免)——不入批审批分组(dispatch 免审直行——无文件写)。
|
|
149
|
+
*/
|
|
150
|
+
export function executeConsumeDesignAction(args, ctx) {
|
|
151
|
+
const parent = ctx.agent
|
|
152
|
+
if (!parent?.config?.agent?.engineering) {
|
|
153
|
+
throw new Error("Engineering mode is not active — consume-design applies only to engineering-mode design tokens (spawn 同门).")
|
|
154
|
+
}
|
|
155
|
+
const designId = args?.designId ? String(args.designId) : undefined
|
|
156
|
+
const slots = parent?._engDesignTokens
|
|
157
|
+
const hasSlots = slots instanceof Map && slots.size > 0
|
|
158
|
+
// 多槽缺 designId → 拒(spawn 同款语义——不误消费任一槽)
|
|
159
|
+
if (!designId && hasSlots && slots.size > 1) {
|
|
160
|
+
throw new Error(`consume-design: Multiple approved designs in this session (${slots.size}) — pass the designId parameter (echoed with each token) to choose which design to close out.`)
|
|
161
|
+
}
|
|
162
|
+
// 读槽值:给定 designId → 精确槽(未知/已消费 → undefined);缺省 → 唯一槽
|
|
163
|
+
let token = null
|
|
164
|
+
if (designId && hasSlots) token = slots.get(designId) ?? null
|
|
165
|
+
else if (!designId && hasSlots) token = [...slots.values()][0]
|
|
166
|
+
// 未知 designId / 已消费 / 无任何槽 → 幂等 no-op 提示(不报错——评审 #3 定死)
|
|
167
|
+
if (!token) {
|
|
168
|
+
return `consume-design: no live slot${designId ? ` for designId ${designId}` : ""} (already consumed or never issued) — idempotent no-op, nothing changed.`
|
|
169
|
+
}
|
|
170
|
+
// consume 落盘对称(DESIGN-TOKEN-SETTLEMENT D1 段 + AC7——交付 🔴 复活洞修复):
|
|
171
|
+
// 删内存槽后当场同步落盘删除(D1 同款 persistEngTokens——空 Map → 槽文件
|
|
172
|
+
// engDesignTokens 字段删除,旧台账不留盘)。否则消费→回合尾 saveSession 窗口内
|
|
173
|
+
// spawn 门禁 miss 回读(D2)会从盘上复活已消费 token。
|
|
174
|
+
const slotId = designId ?? [...slots.keys()][0]
|
|
175
|
+
removeDesignTokenSlot(parent, designId, token)
|
|
176
|
+
try {
|
|
177
|
+
persistEngTokens(parent)
|
|
178
|
+
} catch (e) {
|
|
179
|
+
// 落盘失败 → 回滚内存槽 + 抛错(不留半消费态——盘上仍有旧台账时消费不得报
|
|
180
|
+
// 成功;可重试——D1 评审 #1 settle 同款失败语义)
|
|
181
|
+
if (slotId) parent._engDesignTokens.set(slotId, token)
|
|
182
|
+
throw new Error(`consume-design: the slot was removed in memory but could NOT be durably deleted from the slot file (${e.message}) — the slot is restored in memory; retry consume-design.`)
|
|
183
|
+
}
|
|
184
|
+
return `design slot consumed — designId ${designId ?? "(single-design session)"} is closed out; a further eng-coder spawn for this design is mechanically rejected, and any new work (including deviation fixes) requires a fresh advisor(type='design') review and token.`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── §20 spawn 调度参数准入(AGENT-LOOP.md §20 D-SD1/D-SD3 + 20.4 round2 #5/#7)──
|
|
188
|
+
// files/dependsOn 声明即契约(v1:不做任务书文本自动解析——不可靠)。缺省(两者皆
|
|
189
|
+
// 缺)= 既有语义零改动(不参与冲突检测/无校验——legacy spawn 零开销直通)。
|
|
190
|
+
// 校验序:参数形态 → 依赖 unknown id(非 consumed 墓碑——T-SD10)→ 依赖环可达
|
|
191
|
+
// (T-SD5——防御断言:自然流程不可达)→ 等待态判定。判定结果:wait/depc 阻塞 →
|
|
192
|
+
// async 入 queued 等位(spawn 返回带 reason——D-SD3b);**sync spawn(async:false)
|
|
193
|
+
// 命中阻塞 → 明确错误——不队列化 sync——sync 语义零变更(round2 #7——T-SD13)**。
|
|
194
|
+
/** §20 准入(参数化提取——2026-09-05 module-split):返回归一化 { files, dependsOn }。
|
|
195
|
+
* filesRaw 目录声明 fail-closed(检测器 throw → 错误即工具结果 JSON)。 */
|
|
196
|
+
export function prepareScheduling(parent, filesRaw, dependsRaw, wantAsync) {
|
|
197
|
+
const files = []
|
|
198
|
+
if (filesRaw !== undefined && filesRaw !== null) {
|
|
199
|
+
try {
|
|
200
|
+
files.push(...normalizeFileList(filesRaw, parent.cwd))
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return { files: [], dependsOn: [], errorJson: JSON.stringify({ status: "error", error: e.message }) }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (filesRaw !== undefined && filesRaw !== null && !Array.isArray(filesRaw)) {
|
|
206
|
+
throw new Error("subagent files must be an array of file paths (the write domain this task declares)")
|
|
207
|
+
}
|
|
208
|
+
const dependsOn = []
|
|
209
|
+
if (dependsRaw !== undefined && dependsRaw !== null) {
|
|
210
|
+
if (!Array.isArray(dependsRaw)) throw new Error("subagent dependsOn must be an array of async subagent ids (from prior spawn returns)")
|
|
211
|
+
for (const d of dependsRaw) {
|
|
212
|
+
if (typeof d !== "string" && typeof d !== "number") {
|
|
213
|
+
throw new Error(`subagent dependsOn entries must be async subagent ids — got ${JSON.stringify(d)}`)
|
|
214
|
+
}
|
|
215
|
+
dependsOn.push(String(d))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (files.length > 0 || dependsOn.length > 0) {
|
|
219
|
+
for (const d of dependsOn) {
|
|
220
|
+
if (depInfo(parent, d).state === "unknown") {
|
|
221
|
+
throw new Error(`subagent dependsOn: unknown async subagent id: ${d} — dependsOn references ids from prior async spawn returns; an id already consumed (auto-delivered to the model) counts as satisfied, anything else is a mistake (AGENT-LOOP.md §20 D-SD5)`)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
assertNoDepCycle(parent, dependsOn)
|
|
225
|
+
const block = describeBlockers(parent, { _files: files, _dependsOn: dependsOn })
|
|
226
|
+
if (!wantAsync && block.kind !== "slot") {
|
|
227
|
+
throw new Error(`sync spawn (async:false) cannot queue behind a scheduling conflict: ${block.detail} — pass async:true to queue the task (the scheduler starts it when the blockers clear), or wait for them to finish first (AGENT-LOOP.md §20 round2 #7)`)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { files, dependsOn, errorJson: null }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* §20 准入通过后的 child 装配(2026-09-05 module-split——自 execute 参数化提取,
|
|
235
|
+
* 原 318-488 段 verbatim——语义零变)。副作用保留:relay 计数器/子代理 _logId/
|
|
236
|
+
* makeRelay 注册/_engTaskInput 携带全部在此发生。返回阻塞/异步两路径共用的
|
|
237
|
+
* { child, input, childOpts, childRunOpts, relayPrefix, childProvider }。
|
|
238
|
+
*/
|
|
239
|
+
export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, dependsOn, engAuditAttempt) {
|
|
240
|
+
// Provider/model override: tool `model` arg > subagentModels[role] > subagentModel > parent provider
|
|
241
|
+
const childProvider = resolveChildProvider(parent, effectiveSubagentModel(parent, role, args.model))
|
|
242
|
+
|
|
243
|
+
// ── batchDoc gate(ENGINEERING-MODE.md §2.12 + §2.15 D1——FR20 #9 机械面)────────
|
|
244
|
+
// 工程模式下 spawn 工程角色(**eng-coder / eng-designer**——第 2 批扩为角色集合)必传
|
|
245
|
+
// 批次档路径(需求 §1.11 铁律 #5 随件传递的机械面)。落点 = 本装配点(token 门之前):
|
|
246
|
+
// sync 与 async 两条 spawn 路径都经过 buildSpawnChild——一处校验双路生效,错误出口与
|
|
247
|
+
// token 门一致(§2.13.1 选型 A)。
|
|
248
|
+
// 判据只到"参数在 + 路径可读"——**不校验内容/措辞**(不做"已收口"正则、不匹配模板、
|
|
249
|
+
// 不生成;内容够不够由执行者拒收兜底——需求 §1.14 #9 行为面)。路径语义照
|
|
250
|
+
// `files` 先例:cwd 相对或绝对均可,`\` 归一为 `/`。错误文案带**实际角色名**(§2.15
|
|
251
|
+
// 越界文案参数化——designer 撞门时不误导)。
|
|
252
|
+
let batchDocAbs = null
|
|
253
|
+
if (role === "eng-coder" || role === "eng-designer") {
|
|
254
|
+
const given = typeof args.batchDoc === "string" ? args.batchDoc.trim() : ""
|
|
255
|
+
const refusal = (suffix = "") => new Error(
|
|
256
|
+
`batchDoc is required for role='${role}' — pass the batch record path (docs/batches/<batch>-<topic>.md); spawn refused without it.` + suffix)
|
|
257
|
+
if (!given) throw refusal()
|
|
258
|
+
batchDocAbs = resolve(parent.cwd ?? process.cwd(), given.replace(/\\/g, "/"))
|
|
259
|
+
let readable = false
|
|
260
|
+
try { readable = existsSync(batchDocAbs) && statSync(batchDocAbs).isFile() } catch { readable = false }
|
|
261
|
+
if (!readable) throw refusal(" (given path is not a readable file)")
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// eng-coder token gate: the design review must have passed and the caller must
|
|
265
|
+
// present the exact token advisor issued — otherwise the child is not authorized to code.
|
|
266
|
+
// 2026-09-01: multi-design slots — the token is located by designId (exact slot,
|
|
267
|
+
// single-slot fallthrough); token validation itself is unchanged (2026-09-06:
|
|
268
|
+
// HMAC removed — validateDesignToken now checks format + TTL only).
|
|
269
|
+
let issuedToken
|
|
270
|
+
if (role === "eng-coder") {
|
|
271
|
+
issuedToken = resolveDesignSlot(parent, args.designId).token
|
|
272
|
+
// R16 D-R16c ③ — spawn-gate cleanup: only an EXPIRY rejection removes the slot
|
|
273
|
+
// (the caller passed the slot's own token and it is past TTL — 长跑不重启也清);
|
|
274
|
+
// mismatch / format rejections never delete — the slot's own token may still be
|
|
275
|
+
// valid and a caller error must not destroy it (T-R16d). 单值镜像已退役(D3)——
|
|
276
|
+
// 无镜像同步清。
|
|
277
|
+
const expiredReject = !!issuedToken && args.designToken === issuedToken && tokenExpired(issuedToken)
|
|
278
|
+
if (!issuedToken || args.designToken !== issuedToken || !validateDesignToken(args.designToken)) {
|
|
279
|
+
if (expiredReject) removeDesignTokenSlot(parent, args.designId, issuedToken)
|
|
280
|
+
throw new Error("Invalid or missing design token — run advisor with type='design' first and pass the returned token as designToken.")
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Filter tool set by role: explore/plan are read-only (plan is a planning agent, its deliverable is the plan itself)
|
|
285
|
+
let tools
|
|
286
|
+
if (role === "explore" || role === "plan") {
|
|
287
|
+
const allowed = readonlyToolNames(parent.tools)
|
|
288
|
+
tools = parent.tools.filter((t) => allowed.has(t.name))
|
|
289
|
+
} else {
|
|
290
|
+
tools = parent.tools
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// PROMPT-SYSTEM 施工② G3(2026-09-10):overlay(人格)装载随装配改造退役——人格槽
|
|
294
|
+
// 由 assemblePrompt 的 D1 场景表按 role 承载(persona-{role},explore/coder/plan =
|
|
295
|
+
// PERSONA_NORMAL 同源);child.overlay 恒空(setup 不再前缀叠加)。
|
|
296
|
+
const overlay = ""
|
|
297
|
+
|
|
298
|
+
// explore/plan: force read-only permission; coder/default: AUTO passes through directly,
|
|
299
|
+
// manual mode queues permission requests for the parent agent's approval UI (human in the loop, child agent is no longer silently rejected)
|
|
300
|
+
let childPermission
|
|
301
|
+
if (role === "explore" || role === "plan") {
|
|
302
|
+
childPermission = async () => false
|
|
303
|
+
} else if (parent.autoApprove) {
|
|
304
|
+
childPermission = async () => true
|
|
305
|
+
} else {
|
|
306
|
+
childPermission = async (name, toolArgs) => {
|
|
307
|
+
if (!ctx.onPermissionRequest) return false
|
|
308
|
+
// SYNC-CANCEL v2(用户裁——2026-09-09):工具权限 ask 加 owner key 标识——
|
|
309
|
+
// name `${key}/${tool}`(原 `${role}/${tool}` 无 key——模态 deny 归属判定 +
|
|
310
|
+
// 显示可辨 child);relayPrefix 在 buildSpawnChild 尾部定值——闭包运行于 child
|
|
311
|
+
// 实际 runAgent(buildSpawnChild 返回后)——无 TDZ 风险
|
|
312
|
+
const ownerKey = relayPrefix.slice(0, -1)
|
|
313
|
+
const ask = () => {
|
|
314
|
+
// ⏹ 后(entry.stopped)不再弹模态——直接拒绝(_permQueue 排队 ask 到达时查
|
|
315
|
+
// stopped 旗标——子代理随即在 abort 检出点解绕——v2 模态 deny)
|
|
316
|
+
if (parent._syncChildAborts?.get(ownerKey)?.stopped) return Promise.resolve(false)
|
|
317
|
+
return ctx.onPermissionRequest(`${ownerKey}/${name}`, toolArgs)
|
|
318
|
+
}
|
|
319
|
+
// Queue parallel child agent permission requests to avoid two popups simultaneously overwriting each other (lesson from question tool)
|
|
320
|
+
return enqueueAsk(parent, "_permQueue", ask)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// G6(施工②)+ §2.15 B4(第 2 批):工程角色场景即工程纪律——engineering=true 使 setup
|
|
325
|
+
// 场景映射落到 assemblePrompt("eng-coder"/"eng-designer")(persona-{role} + common +
|
|
326
|
+
// discipline-engineering);designer 也必须 engineering=true 才能装配工程纪律槽。
|
|
327
|
+
const engineeringRole = role === "eng-coder" || role === "eng-designer"
|
|
328
|
+
const childConfig = engineeringRole
|
|
329
|
+
? { ...parent.config, agent: { ...parent.config.agent, engineering: true } }
|
|
330
|
+
: parent.config
|
|
331
|
+
|
|
332
|
+
const child = createAgent({
|
|
333
|
+
provider: childProvider,
|
|
334
|
+
tools,
|
|
335
|
+
config: childConfig,
|
|
336
|
+
cwd: parent.cwd,
|
|
337
|
+
memory: parent.memory,
|
|
338
|
+
overlay,
|
|
339
|
+
role,
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// §2.20.2 批次档段写入通道绑定(第 4 批):工程角色(eng-coder/eng-designer)把批次档
|
|
343
|
+
// 绝对路径记在 child 上——setup 挂载 batch_segment 时读它。无路径参数的工具靠这条
|
|
344
|
+
// 绑定决定目标档(spawn 门已保证「参数在 + 路径可读」)。
|
|
345
|
+
if (engineeringRole) child._batchDoc = batchDocAbs
|
|
346
|
+
|
|
347
|
+
// Token-verified design review → child is authorized to modify files without re-reviewing
|
|
348
|
+
if (role === "eng-coder") child._engDesignReviewed = true
|
|
349
|
+
// §18 D-E3 task-domain authorization: approved design + spawn task = authorization.
|
|
350
|
+
// The child's OWN tools skip ONLY the onPermissionRequest ask (autoApprove
|
|
351
|
+
// equivalent — dispatch.mjs permission stage); every other gate (JSON parse /
|
|
352
|
+
// unknown tool / planMode / design-token) still applies (T-E14). Non-eng-coder
|
|
353
|
+
// children keep the manual per-write parent approval (human in the loop).
|
|
354
|
+
if (role === "eng-coder") child._engTaskAuthorized = true
|
|
355
|
+
// designId rides the child bookkeeping: the delivery report carries the designId
|
|
356
|
+
// so the divergence-audit fix round re-spawns with the SAME slot (2026-09-01 FR3).
|
|
357
|
+
// DESIGN-TOKEN-SETTLEMENT D3 (2026-09-08): the single-value `_engDesignToken` mirror
|
|
358
|
+
// is retired — no mirror copy on the child (nothing reads it; the slot ledger + the
|
|
359
|
+
// spawn-time token validation are the only authorities).
|
|
360
|
+
if (role === "eng-coder" && issuedToken) {
|
|
361
|
+
child._engDesignId = args.designId ?? null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// §18.5 子代理零 git(D-AG1——2026-09-04 用户裁定):explore/plan 一律不注入
|
|
365
|
+
// git 上下文——子代理证据链 = 任务书 ∪ 磁盘当前状态(read/glob/grep)∪(审计时)
|
|
366
|
+
// _touchedFiles,无一项来自 git;注入的全工作区脏状态快照与任务域无关,会误导
|
|
367
|
+
// 审计/探索("status 里这个文件算不算超清单?")。注入分支整体删除(B 方案
|
|
368
|
+
// git 只读变体亦随裁定废弃——D-AG5)。顶层主 agent 注入保留(§3 prepareRun——
|
|
369
|
+
// setup.mjs depth===0——D-AG7 范围边界)。
|
|
370
|
+
let input = args.context ? `Context:\n${args.context}\n\nTask:\n${args.task}` : args.task
|
|
371
|
+
// §2.11 第 1 点(FR16 载体 + FR17 铁律 #5 的机械面):批次档**绝对路径**随任务输入
|
|
372
|
+
// 下发给工程角色(eng-coder 实现 / eng-designer 写稿)——追加一行,任务文本本身不动
|
|
373
|
+
// (批次档 §2 才是任务书本体;本行让子代理"拿到本档路径")。行文不含
|
|
374
|
+
// summarizeEngTaskBook 的三组段 marker(Docs involved / Files list / Acceptance
|
|
375
|
+
// criteria)——段匹配不受影响。仅工程角色注入。
|
|
376
|
+
if (engineeringRole) input += `\n\nBatch record (batchDoc): ${batchDocAbs}`
|
|
377
|
+
// §18 D-E2 ③ (round4 #4, T-E13/T-E15): an eng-coder audit spawn's task book is
|
|
378
|
+
// the eng-coder's OWN spawn task — mechanically kept as _engTaskInput by the
|
|
379
|
+
// parent spawn and injected as the D-TS5 A2 mechanical summary (design docs /
|
|
380
|
+
// affected-file list / acceptance criteria verbatim, verbose context dropped)
|
|
381
|
+
// — ∪ the mechanically tracked _touchedFiles — NEVER the eng-coder's
|
|
382
|
+
// self-written list: a self-report could omit exactly the out-of-scope file
|
|
383
|
+
// the audit must catch.
|
|
384
|
+
if (engAuditAttempt !== null) {
|
|
385
|
+
const touched = (ctx.agent._touchedFiles ?? []).map((f) => `- ${f}`).join("\n") || "- (none yet)"
|
|
386
|
+
input += `\n\n[Audit scope — mechanical context, independent of the eng-coder's self-report:]\n` +
|
|
387
|
+
// §18.7 D-TS4 A1:审计指令模板(四类偏差 + 范围限制 + 校验清单格式)——审计语义
|
|
388
|
+
// 不再靠模型自悟;范围限制是 §18.5 D-AG3 声明(下方 Zero-git scope authority)
|
|
389
|
+
// 的同源一句指注,不重复声明。
|
|
390
|
+
`[Audit instructions — mechanical template (AGENT-LOOP.md §18.7 D-TS4 A1):]\n` +
|
|
391
|
+
`You are auditing an eng-coder delivery against its approved design — audit for EXACTLY these four deviation categories:\n` +
|
|
392
|
+
`- PARTIAL: an acceptance criterion implemented partially or not at all;\n` +
|
|
393
|
+
`- SILENT-SIMPLIFICATION: a "simpler approximation" of a specified behavior substituted for the spec;\n` +
|
|
394
|
+
`- DOC-DRIFT: code changed without the owning design-doc section (module map / affected-files table) updated in the same delivery;\n` +
|
|
395
|
+
`- OUT-OF-LIST: changes outside the approved file list.\n` +
|
|
396
|
+
`Audit scope = _touchedFiles above UNION the files confirmed by the parent task book (single source — the Zero-git scope authority note below, AGENT-LOOP.md §18.5 D-AG3; NOT a second copy): ` +
|
|
397
|
+
`workspace changes not listed there are unrelated to this delivery and are NOT grounds for an out-of-list finding.\n` +
|
|
398
|
+
`Scope discipline (F-TS6 A1): read ONLY the audited files and the design-doc sections relevant to this delivery — do NOT re-read whole documents.\n` +
|
|
399
|
+
`Every deviation item MUST be fieldized: file:line + design reference (doc path + section/AC id) + severity + evidence (quoted code or doc text).\n` +
|
|
400
|
+
// §18.7 D-TS5 A2:任务书从全量 verbatim 改机械摘要块(三要素逐字——排除冗长上下文)。
|
|
401
|
+
`[Parent spawn task book — mechanical summary (AGENT-LOOP.md §18.7 D-TS5 A2): design docs + affected-file list + acceptance criteria verbatim; verbose context/background dropped — the design docs are still available for reading outside this input:]\n` +
|
|
402
|
+
`${summarizeEngTaskBook(ctx.agent._engTaskInput)}\n` +
|
|
403
|
+
`Files actually touched by the eng-coder (mechanical union — audit these against the file list):\n${touched}\n` +
|
|
404
|
+
// §18.5 D-AG3(2026-09-04):审计零 git 范围权威声明——本审计任务零 git(不注入
|
|
405
|
+
// git 上下文——§18.5 全角色零 git);_touchedFiles 为审计范围;工作区未列于
|
|
406
|
+
// _touchedFiles 的改动与本任务无关,不作超清单依据(VS Code auditTaskBook 同款措辞)。
|
|
407
|
+
"Zero-git scope authority (AGENT-LOOP.md §18.5 D-AG3): this audit task receives NO git context — nothing is injected. " +
|
|
408
|
+
"The evidence base is the design documents, the current disk state (read/glob/grep), and the _touchedFiles list above. " +
|
|
409
|
+
"Workspace changes NOT listed in _touchedFiles are unrelated to this delivery — they are NOT grounds for an out-of-file-list finding." +
|
|
410
|
+
// §18.13 D-A1.2:审计预算句——A1 指令模板 + A2 摘要块之后、A3 报告模板之前(定序——评审 #7)。
|
|
411
|
+
// 逐字设计锚(D-A1.2 代码块):只读该读的——10 轮机械预算——超时报 PROBLEM 下结论。
|
|
412
|
+
// 前导 \n 与 A3 同款块分隔约定(上一句 Zero-git 句末无换行——不触碰既有句)。
|
|
413
|
+
`\n[Audit budget — mechanical]: read ONLY the touched files listed above and the design-doc sections the parent task book names (affected-files table, acceptance criteria, status line). Do NOT read whole documents. Budget = 10 tool rounds max — if you cannot conclude within it, report PROBLEM (inconclusive) rather than continuing to explore.\n` +
|
|
414
|
+
// §18.7 D-TS6 A3:审计输出报告格式模板(三态——字段化行——不让模型自由发挥)。
|
|
415
|
+
`\n[Audit report format — mechanical template (AGENT-LOOP.md §18.7 D-TS6 A3):]\n` +
|
|
416
|
+
`Report EXACTLY one of three states:\n` +
|
|
417
|
+
`- CLEAN — no deviation across the four categories: reply the line "Four deviation categories: none found." (四类偏差均未发现);\n` +
|
|
418
|
+
`- DEVIATIONS — one row per deviation, every row fieldized: | category | file:line | design reference | severity | evidence |;\n` +
|
|
419
|
+
`- PROBLEM — the audit itself could not run / inconclusive: state what blocked it.\n`
|
|
420
|
+
}
|
|
421
|
+
// The child's own task input rides the child object: an eng-coder's audit
|
|
422
|
+
// spawns reuse it as the task-book SOURCE — injected as the D-TS5 A2
|
|
423
|
+
// mechanical summary, not verbatim (see above).
|
|
424
|
+
if (role === "eng-coder") child._engTaskInput = input
|
|
425
|
+
|
|
426
|
+
// Relay content/reasoning/tool/output to the parent TUI via the unified spawn-child
|
|
427
|
+
// pipeline (AGENT-LOOP.md §7.2 D3). Prefix includes a unique id: parallel child agents
|
|
428
|
+
// with the same role stay independent and don't overwrite each other.
|
|
429
|
+
// Format: role#id/ → onToken("coder#2/writing..."), onToolCall("coder#2/read", args)
|
|
430
|
+
// Async id allocation (AGENT-LOOP.md §15 D-A1): reserve the relay counter at
|
|
431
|
+
// spawn time — the returned id must be stable while the item sits in the queue.
|
|
432
|
+
// The [model] token (TUI block creation) is DEFERRED to actual start so queued
|
|
433
|
+
// children don't paint an empty panel block ("queued 态不显示").
|
|
434
|
+
let relayPrefix
|
|
435
|
+
if (wantAsync) {
|
|
436
|
+
// SUBAGENT-ID-COUNTER-AGENT(2026-09-09):async id 取号统一走 nextSubagentId
|
|
437
|
+
// (池活续号兜底——counter 载体= agent 本体 _subAgentCounter——跨 run/跨压缩
|
|
438
|
+
// 存活——per-run reset 清单不含它)。sync 分支 makeRelay 不进池——照旧。
|
|
439
|
+
const id = nextSubagentId(parent)
|
|
440
|
+
relayPrefix = relayPrefixOf(role, id)
|
|
441
|
+
} else {
|
|
442
|
+
relayPrefix = makeRelay(parent, role ?? "sub", ctx.callbacks?.onToken, childProvider.model ?? "")
|
|
443
|
+
}
|
|
444
|
+
// LOGGING(LOGGING.md):子代理内部事件(子内 llm:*/tool:*)以 childId 归属——
|
|
445
|
+
// agent._logId 随 runAgent 的 logCtx 透出(主文件单文件全记、按 childId grep)。
|
|
446
|
+
child._logId = relayPrefix.slice(0, -1)
|
|
447
|
+
const childOpts = {
|
|
448
|
+
onPermissionRequest: childPermission,
|
|
449
|
+
...wrapChildCallbacks(relayPrefix, ctx.callbacks),
|
|
450
|
+
}
|
|
451
|
+
const childRunOpts = buildChildRunOpts(ctx)
|
|
452
|
+
return { child, input, childOpts, childRunOpts, relayPrefix, childProvider }
|
|
453
|
+
}
|