@thincoder/core 0.10.3 → 0.10.4
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 +32 -0
- package/advisor/citations.mjs +29 -3
- package/advisor/compaction.mjs +21 -2
- package/advisor/history.mjs +8 -6
- package/advisor/loop.mjs +29 -27
- package/advisor/messages.mjs +2 -1
- package/advisor/run.mjs +5 -1
- package/advisor/timeline.mjs +26 -0
- package/advisor.mjs +4 -2
- package/agent/completion.mjs +3 -1
- package/agent/digest-account.mjs +188 -0
- package/agent/family-tools.mjs +11 -11
- package/agent/helpers.mjs +8 -0
- package/agent/record-results.mjs +4 -0
- package/agent/run-stages.mjs +27 -4
- package/agent/run-start.mjs +49 -3
- package/agent/suspension.mjs +30 -8
- package/agent/turn-loop.mjs +25 -2
- package/agent/write-gate.mjs +13 -25
- package/agent-tools/advisor-async.mjs +14 -6
- package/agent-tools/advisor.mjs +34 -33
- package/agent-tools/async-settle.mjs +29 -5
- package/agent-tools/batch-lifecycle.mjs +26 -5
- package/agent-tools/batch-paths.mjs +22 -2
- package/agent-tools/batch-skeleton.mjs +31 -33
- package/agent-tools/batch.mjs +11 -3
- package/agent-tools/design-token.mjs +20 -0
- package/agent-tools/escalate-async.mjs +4 -2
- package/agent-tools/review-facts.mjs +143 -16
- package/agent-tools/subagent-actions-query.mjs +13 -6
- package/agent-tools/subagent-async.mjs +10 -3
- package/agent-tools/subagent-panel.mjs +2 -1
- package/agent-tools/subagent-spawn.mjs +1 -1
- package/agent-tools/subagent.mjs +1 -1
- package/agent-tools/zero-write-watch.mjs +77 -0
- package/agent.mjs +1 -1
- package/conventions.mjs +26 -6
- package/escape.mjs +1 -1
- package/i18n.mjs +5 -0
- package/ledger-tools.mjs +148 -85
- package/ledger-variant-notice.mjs +47 -0
- package/ledger.mjs +2 -2
- package/manifest-schema.mjs +6 -4
- package/manifest.mjs +5 -3
- package/package.json +1 -1
- package/prompts/common.md +2 -2
- package/prompts/persona-engineering.md +4 -3
- package/provider/anthropic.mjs +14 -2
- package/provider/errors.mjs +23 -0
- package/provider/google.mjs +13 -2
- package/provider/responses-request.mjs +3 -1
- package/provider/responses.mjs +38 -4
- package/shell-identity.mjs +107 -0
- package/tool-docs/advisor.md +1 -1
- package/tool-docs/bash.md +1 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/delete.md +7 -5
- package/tool-docs/ledger.md +6 -0
- package/tool-docs/subagent.md +10 -0
- package/tools/bash.mjs +9 -4
- package/tools/index.mjs +1 -4
- package/tools/patch.mjs +8 -2
- package/tools/write-path.mjs +6 -4
- package/tool-docs/ledger_add.md +0 -1
- package/tool-docs/ledger_close.md +0 -1
- package/tool-docs/ledger_count.md +0 -1
- package/tool-docs/ledger_query.md +0 -1
- package/tool-docs/ledger_update.md +0 -1
|
@@ -49,6 +49,18 @@ function segmentNumber(raw) {
|
|
|
49
49
|
return m ? Number(m[1]) : null
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** 档头两可选参归一(`BATCH-RECORD.md` §4.11 契约——逐字文本面 = `TOOLS.md` §6.20):trim ∥
|
|
53
|
+
* 单行(拒句逐字)∥ 空串 ≡ 未给(null);`ledger` 前导 `#` 补齐(board 无补齐)。给参 ⇒ 建即填
|
|
54
|
+
* 台账行(batchSkeleton 实参面);未给(任一)⇒ 占位留存 + 回执填法句。 */
|
|
55
|
+
function normalizeHeaderParam(name, raw) {
|
|
56
|
+
const v = typeof raw === "string" ? raw.trim() : ""
|
|
57
|
+
if (!v) return null
|
|
58
|
+
if (/\r?\n/.test(v)) {
|
|
59
|
+
throw new Error("batch: create ledger / board must be single lines — a multi-line value would break the header's 台账 line. Nothing was written.")
|
|
60
|
+
}
|
|
61
|
+
return name === "ledger" && !v.startsWith("#") ? `#${v}` : v
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
/** main-agent-only 门(BR-19/BR-24):create/close 仅 depth-0 放行(eng 子代理 / 评审皆拒)。 */
|
|
53
65
|
function assertMainAgentOnly(ctx, action, review) {
|
|
54
66
|
if (review || !isDepthZero(ctx)) {
|
|
@@ -108,6 +120,8 @@ export function findInFlightBatch(cwd, bases) {
|
|
|
108
120
|
* 路径解析 = `resolveBatchCreatePath`(§4.15 单源:候选序首个落基底内者 / 锚定串只解析项目根形)。
|
|
109
121
|
* F11-B:`source` **必填**(topic 同款空拒——骨架编制行实参化;不传 = 死占位残留必被 F11-C 拒
|
|
110
122
|
* ⇒ create 即拒,不留死锁);`prev` 传入值**幂等剥**「前情 = 」前缀(值规范化;默认值零变)。
|
|
123
|
+
* 2026-10-05(A-2):`ledger` / `board` 可选参**建即填**台账行(归一契约 = §4.11;回执两态 =
|
|
124
|
+
* 两参皆给 ⇒ 标注已填 ∥ 未给(任一)⇒ 填法句)。
|
|
111
125
|
* @returns {string} 成功消息(含落盘绝对路径)
|
|
112
126
|
*/
|
|
113
127
|
export function createBatchRecord({ args, ctx, review, cwd, bases, onWritten }) {
|
|
@@ -138,15 +152,22 @@ export function createBatchRecord({ args, ctx, review, cwd, bases, onWritten })
|
|
|
138
152
|
if (/\r?\n/.test(source)) {
|
|
139
153
|
throw new Error("batch: create source must be a single line — a multi-line source would break the header's 编制 line. Nothing was written.")
|
|
140
154
|
}
|
|
155
|
+
// 档头两可选参(§4.11 契约——给则建即填台账行;未给任一 ⇒ 占位留存 + 回执填法句)
|
|
156
|
+
const ledger = normalizeHeaderParam("ledger", args?.ledger)
|
|
157
|
+
const board = normalizeHeaderParam("board", args?.board)
|
|
141
158
|
const date = typeof args?.date === "string" && args.date.trim() ? args.date.trim() : todayLocal()
|
|
142
159
|
// F11-B:prev 幂等 strip——调用方自带「前情 = 」前缀(单或重复)一并剥净(`+` 量词;归一化目的
|
|
143
160
|
// = 值规范化,无前缀值原样通过)。默认值形态「无(独立批)」零变。
|
|
144
161
|
const prevRaw = typeof args?.prev === "string" ? args.prev.trim() : ""
|
|
145
162
|
const prev = prevRaw ? (prevRaw.replace(/^(?:前情\s*[=::]\s*)+/g, "").trim() || "无(独立批)") : "无(独立批)"
|
|
146
163
|
mkdirSync(dirname(abs), { recursive: true })
|
|
147
|
-
writeFileSync(abs, batchSkeleton({ date, topic, source, prev }))
|
|
164
|
+
writeFileSync(abs, batchSkeleton({ date, topic, source, prev, ledger, board }))
|
|
148
165
|
onWritten?.(ctx?.agent ?? {}, abs)
|
|
149
|
-
|
|
166
|
+
// 回执两态(`TOOLS.md` §6.20 逐字):两参皆给 ⇒ 标注已填;未给(任一)⇒ 填法句(填法 = 文件编辑)
|
|
167
|
+
const headerNote = ledger !== null && board !== null
|
|
168
|
+
? `header 台账 line filled (${ledger} · ${board})`
|
|
169
|
+
: "header 台账 placeholder(s) remain — fill them by file edit before the record's first append/status opens"
|
|
170
|
+
return `batch: created ${abs} — six-section skeleton written (§1 status line carries the gate-legal 「进行中」 placeholder; register the ledger entry yourself — create does not). ${headerNote}`
|
|
150
171
|
}
|
|
151
172
|
|
|
152
173
|
/**
|
|
@@ -265,7 +286,7 @@ function assertStatusNote(note) {
|
|
|
265
286
|
* 恰一词(**余核 = 关键词**——F11-A 谓词收紧),散文说明走独立 `note` 字段(落状态行括注);
|
|
266
287
|
* 冻结真值不变(gate 只读 §1)。path 参数 = 仅 depth-0(D-BR21)——子代理/评审传 path
|
|
267
288
|
* ⇒ 拒(目标 = spawn/实例注入,语法上写不到别处)。F11-C 挂点 = 写盘前(assertStatusValue 后):
|
|
268
|
-
*
|
|
289
|
+
* 档头结构行含骨架死占位 ⇒ 拒(段体引用豁免;close 不拦——收口是主 agent 终态动作)。
|
|
269
290
|
* @returns {string} 成功消息
|
|
270
291
|
*/
|
|
271
292
|
export function statusBatchRecord({ args, ctx, review, pickTarget, onWritten }) {
|
|
@@ -298,8 +319,8 @@ export function statusBatchRecord({ args, ctx, review, pickTarget, onWritten })
|
|
|
298
319
|
const abs = pickTarget(args?.path)
|
|
299
320
|
const src = readFileSync(abs, "utf8")
|
|
300
321
|
assertGateOpen(src)
|
|
301
|
-
// F11-C
|
|
302
|
-
const residues = findPlaceholderResidue(src
|
|
322
|
+
// F11-C(写盘前):档头结构行死占位残留 ⇒ 拒(段体引用豁免——判据可辨性收正,`TOOLS.md` §6.20)
|
|
323
|
+
const residues = findPlaceholderResidue(src)
|
|
303
324
|
if (residues.length) throw new Error(placeholderResidueError(residues))
|
|
304
325
|
const written = updateSectionStatusLine(src, seg, lineValue)
|
|
305
326
|
writeFileSync(abs, written)
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* **歧义锚**(条 5 · 2026-10-02 · #828)= create 按目标所属项目落基底 ∥ 无所属显式拒;读面候选腿追加;
|
|
11
11
|
* 在飞 ∥ 写门基底集 = 候选并集(`batchDocBases`);**零变面** = 绝对路径照旧、三个错误文案逐字、
|
|
12
12
|
* 「参数在 + 路径可读」判据句、`ok` / `none` 两态。
|
|
13
|
+
* **基底自有项目根腿 + 基底相对单段收窄**(条 6 · 2026-10-05 · #942)= 逐基底 `owningProject` 恰一落点收 ∥ 多落点显式拒(`ambiguousLandingError`);基底相对腿限单段(多段串不二次拼接)。
|
|
13
14
|
*
|
|
14
15
|
* 依赖(KD-4 单向):本档为**叶档**——只 import `node:*` 与 `../manifest.mjs`;主档 re-export 保既有面。
|
|
15
16
|
*/
|
|
@@ -98,6 +99,7 @@ const outsideBasesError = (raw) => new Error(`batch: create path resolves outsid
|
|
|
98
99
|
const anchoredEscapeError = (raw) => new Error(`batch: create path is anchored at a batch base root but does not resolve under the declared docRoot.batches root(s) — refusing to nest it (fail-closed). Path: ${raw}`)
|
|
99
100
|
/** 歧义锚 create 拒面(**文案逐字** = `BATCH-RECORD.md` §4.15 条 5——机检锚 `ambiguous session anchor`)。 */
|
|
100
101
|
const ambiguousCreateError = (raw, candidates) => new Error(`batch: ambiguous session anchor — create target "${raw}" belongs to no candidate project; pass an explicit project path (e.g. "${candidates[0]}/docs/batches/<file>.md"). Candidates: ${candidates.join(", ")}`)
|
|
102
|
+
const ambiguousLandingError = (raw, landings) => new Error(`batch: create path resolves under more than one declared batch-record base — refusing to pick one (fail-closed); pass an explicit path (absolute preferred). Path: ${raw}. Candidates: ${landings.join(", ")}`)
|
|
101
103
|
|
|
102
104
|
/** 歧义锚读面候选腿(条 5):逐候选项目——项目根形(`resolve(candidate, p)`)∥ 声明基底形
|
|
103
105
|
* (候选按名排序、并集保序——「首个可读」在此即为确定判读)。 */
|
|
@@ -149,7 +151,7 @@ export function resolveBatchDocPath(cwd, given) {
|
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
/**
|
|
152
|
-
* **create 面**解析(§4.15 条 2/3):绝对路径照旧取用(仍过越基底判据);相对路径 = 候选序中首个**落基底
|
|
154
|
+
* **create 面**解析(§4.15 条 2/3/6):绝对路径照旧取用(仍过越基底判据);相对路径 = 候选序中首个**落基底
|
|
153
155
|
* 根内**者(新档不存在——可读性无判别力);锚定串只解析项目根形,不落基底内 ⇒ fail-closed 新文案。
|
|
154
156
|
* **歧义锚**(条 5):目标所属项目(`owningProject`)∈ 锚候选集 ⇒ 基底 = 该项目声明面(落其基底内;
|
|
155
157
|
* 越出 ⇒ 既有 fail-closed);无所属 ⇒ **显式拒**(列候选 + 显式路径指引——不静默落锚)。
|
|
@@ -179,8 +181,26 @@ export function resolveBatchCreatePath(cwd, given, bases) {
|
|
|
179
181
|
if (!insideBases(abs, roots)) throw anchoredEscapeError(raw)
|
|
180
182
|
return abs
|
|
181
183
|
}
|
|
182
|
-
for (const abs of [resolve(base, p), resolve(batchProjectRoot(base), p)
|
|
184
|
+
for (const abs of [resolve(base, p), resolve(batchProjectRoot(base), p)]) {
|
|
183
185
|
if (insideBases(abs, roots)) return abs
|
|
184
186
|
}
|
|
187
|
+
// ③ 基底自有项目根形(#942 条 6):owningProject 可判者逐基底取值;distinct 落点 ≥2 ⇒ 拒
|
|
188
|
+
const owned = []
|
|
189
|
+
for (const b of roots) {
|
|
190
|
+
const ownRoot = owningProject(b)
|
|
191
|
+
if (!ownRoot) continue
|
|
192
|
+
const abs = resolve(ownRoot, p)
|
|
193
|
+
if (insideBases(abs, roots) && !owned.some((x) => samePath(x, abs))) owned.push(abs)
|
|
194
|
+
}
|
|
195
|
+
if (owned.length > 1) throw ambiguousLandingError(raw, owned)
|
|
196
|
+
if (owned.length === 1) return owned[0]
|
|
197
|
+
// ④ 基底相对形收窄单段(归一后无 `/` 才走原基底腿——多段串不二次拼接)
|
|
198
|
+
const leafRel = roots.length ? relative(roots[0], resolve(roots[0], p)) : ""
|
|
199
|
+
if (leafRel && !leafRel.startsWith("..") && !leafRel.includes(sep)) {
|
|
200
|
+
for (const b of roots) {
|
|
201
|
+
const abs = resolve(b, p)
|
|
202
|
+
if (insideBases(abs, roots)) return abs
|
|
203
|
+
}
|
|
204
|
+
}
|
|
185
205
|
throw outsideBasesError(raw)
|
|
186
206
|
}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* 段标题定位(sectionHeaderRe)同住本档:append 段尾定位与 status/close 段内状态行定位
|
|
14
14
|
* 消费**同一个段头正则**——`## §N` 形态单源(`## §20` 不误命中 §2)。
|
|
15
15
|
*
|
|
16
|
-
* F11-C
|
|
17
|
-
*
|
|
16
|
+
* F11-C:**骨架死占位枚举**(TEMPLATE_PLACEHOLDERS)与残留扫描纯函数(findPlaceholderResidue——
|
|
17
|
+
* 判定域 = 档头结构行,2026-10-05 判据收正)同住本档——与骨架模板共址即判据不会与模板脱节。
|
|
18
18
|
*/
|
|
19
19
|
/**
|
|
20
20
|
* 段标题定位(`## §N` 独立标题——`## §20` 不误命中 §2)。
|
|
@@ -58,46 +58,42 @@ export const STATUS_LINE_RE = /^\s*\*\*状态行\*\*[::]\s*(.*)$/
|
|
|
58
58
|
*/
|
|
59
59
|
export const TEMPLATE_PLACEHOLDERS = ["<BATCH-ID>", "<讨论来源>", "#<编号>", "<板块>"]
|
|
60
60
|
|
|
61
|
+
/** 档头结构行值形(骨架三类——标题行 `# …` / 编制行 `> 编制` / 台账行 `> 台账`):判据可辨性收正
|
|
62
|
+
* (`TOOLS.md` §6.20)后只有这三类行参与死占位判定;骨架↔结构行不变量 = `batchSkeleton` 产出中
|
|
63
|
+
* 枚举字面 ⊆ 本三类行(骨架改形与判据同拍——结构行外新增占位 = 静默漏检)。 */
|
|
64
|
+
const HEADER_STRUCTURAL_RE = [/^#\s/, /^>\s*编制/, /^>\s*台账/]
|
|
65
|
+
|
|
61
66
|
/**
|
|
62
67
|
* 死占位残留扫描(F11-C——append/status 落笔前的机检;**纯函数 · 零 fs**)。
|
|
63
|
-
* 判定域 =
|
|
64
|
-
*
|
|
68
|
+
* 判定域 = **档头结构行**(区域 = 档首至首个 `## §N` 段头前;值形 = 骨架三类结构行:标题行
|
|
69
|
+
* `# …` / 编制行 `> 编制` / 台账行 `> 台账`)——行内出现枚举字面 ⇒ 残留;段体(首段头起)
|
|
70
|
+
* 任意位置出现字面 = 正文引用 ⇒ **豁免**(引用非未填——2026-10-05 判据可辨性收正,`TOOLS.md`
|
|
71
|
+
* §6.20;原「档头 + 目标段」全域子串扫描分不清「未填占位」与「正文引用」——本批 §1 首投实害)。
|
|
65
72
|
* @param {string} src — 档全文
|
|
66
|
-
* @
|
|
67
|
-
*
|
|
68
|
-
* `line` = 所在判定域内 1-based 行号(档头域自档首行起 · 段域自 `## §N` 标题行起——便于定位),
|
|
69
|
-
* `text` = 命中行 trim 原文,`where` = 命中域(档头 / 目标段——错误句标注位置用)
|
|
73
|
+
* @returns {Array<{line: number, text: string}>} 残留列表(空 = 无残留);
|
|
74
|
+
* `line` = 档头域内 1-based 行号(档头域自档首行起——便于定位),`text` = 命中行 trim 原文
|
|
70
75
|
*/
|
|
71
|
-
export function findPlaceholderResidue(src
|
|
72
|
-
const out = []
|
|
73
|
-
const scan = (text, where) => {
|
|
74
|
-
text.split("\n").forEach((line, i) => {
|
|
75
|
-
if (TEMPLATE_PLACEHOLDERS.some((ph) => line.includes(ph))) out.push({ line: i + 1, text: line.trim(), where })
|
|
76
|
-
})
|
|
77
|
-
}
|
|
76
|
+
export function findPlaceholderResidue(src) {
|
|
78
77
|
const firstSection = /^## §\d/m.exec(src)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const next = nextRe.exec(src)
|
|
85
|
-
scan(src.slice(hdr.index, next ? next.index : src.length), "section")
|
|
86
|
-
}
|
|
78
|
+
const out = []
|
|
79
|
+
src.slice(0, firstSection ? firstSection.index : 0).split("\n").forEach((line, i) => {
|
|
80
|
+
if (!HEADER_STRUCTURAL_RE.some((re) => re.test(line))) return
|
|
81
|
+
if (TEMPLATE_PLACEHOLDERS.some((ph) => line.includes(ph))) out.push({ line: i + 1, text: line.trim() })
|
|
82
|
+
})
|
|
87
83
|
return out
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
/**
|
|
91
|
-
* 占位残留拒绝句(F11-C——append / status
|
|
87
|
+
* 占位残留拒绝句(F11-C——append / status 两个挂点共用单源文案;逐行列残留:行号 + 原文)。
|
|
92
88
|
* 错误句前缀 = `batch:`(本批新增错误面——与 append 迁移面的 `batch_segment:` 旧面不混)。
|
|
93
|
-
*
|
|
89
|
+
* 逐字 = `TOOLS.md` §6.20 拒句表(「or your target section」半句随判定域收正退场)。
|
|
90
|
+
* @param {Array<{line:number,text:string}>} residues — findPlaceholderResidue 的返回值(非空)
|
|
94
91
|
* @returns {string} 完整拒绝句(调用方 throw new Error(...))
|
|
95
92
|
*/
|
|
96
93
|
export function placeholderResidueError(residues) {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"Fill them before writing: create ⇒ the main agent fills the header's 台账编号 / 板块 placeholders ⇒ the record's first append/status opens. " +
|
|
94
|
+
const list = residues.map((r) => ` 档头 line ${r.line}: ${r.text}`).join("\n")
|
|
95
|
+
return "batch: 骨架死占位残留 — the batch record's header still carries skeleton placeholders. " +
|
|
96
|
+
"Fill them before writing: create with ledger / board fills the 台账 line at creation; otherwise the main agent fills the header by file edit ⇒ the record's first append/status opens. " +
|
|
101
97
|
`Nothing was written. Residue:\n${list}`
|
|
102
98
|
}
|
|
103
99
|
|
|
@@ -177,16 +173,18 @@ export function sectionHasStatusLine(src, seg) {
|
|
|
177
173
|
* 编制行 + 台账/前情指针行)+ §1 段内**占位状态行**(含 gate 合法关键字「进行中」——建档即过
|
|
178
174
|
* gate;整行不含「已收口」——gate 已收口优先,占位行不得携带冻结词)+ 各段模板子标题占位。
|
|
179
175
|
* 已知实参替换:date / topic / **source** / prev(§4.11 参数面 + F11-B——source 必填、prev 传
|
|
180
|
-
* 入值过幂等剥前缀,归一化在 lifecycle
|
|
181
|
-
*
|
|
176
|
+
* 入值过幂等剥前缀,归一化在 lifecycle 面);`ledger` / `board` = 可选参(2026-10-05 建即填——
|
|
177
|
+
* 给则台账行实参化、未给保持占位字面;4 参旧调用形输出零变)。档头 `#<编号>` / `<板块>` 占位
|
|
178
|
+
* 留存时(未给参)由主 agent 文件编辑填(台账登记 = 主 agent 既有义务,create 不代建——填充
|
|
179
|
+
* 时点 = 建档后、本档首个 append/status 之前)。
|
|
182
180
|
* 本批改形删除(F11-B):旧 `# …(<BATCH-ID>)` 后缀与 `来源 = <讨论来源>` 占位实参化。
|
|
183
181
|
*/
|
|
184
|
-
export function batchSkeleton({ date, topic, source, prev }) {
|
|
182
|
+
export function batchSkeleton({ date, topic, source, prev, ledger = null, board = null }) {
|
|
185
183
|
return [
|
|
186
184
|
`# ${date} · ${topic}`,
|
|
187
185
|
"> 六段 append-only,一段一作者:§1 讨论(主 agent)· §2 批次任务与设计(eng-designer)· §3 设计评审(评审子代理)· §4 用户批准(主 agent)· §5 实施记录(eng-coder)· §6 验证与收口(父代理)。",
|
|
188
186
|
`> 编制:主 agent · ${date} · 来源 = ${source}。`,
|
|
189
|
-
`> 台账 =
|
|
187
|
+
`> 台账 = ${ledger ?? "#<编号>"}(${board ?? "<板块>"} · 归批)。前情 = ${prev}。`,
|
|
190
188
|
"## §1 讨论(主 agent)",
|
|
191
189
|
`**状态行**:🔄 ${STATUS_WORDS[1].open}(…)`,
|
|
192
190
|
"<§1 模板占位:本批条目 / 关键判据 / 授权口径>",
|
package/agent-tools/batch.mjs
CHANGED
|
@@ -211,9 +211,9 @@ function appendBatchRecord({ args, ctx, review, batchDoc, onWritten }) {
|
|
|
211
211
|
if (!body.trim()) {
|
|
212
212
|
throw new Error("batch_segment: nothing to append — the text is empty after credential stripping (credential values never reach the record).")
|
|
213
213
|
}
|
|
214
|
-
// F11-C(挂点:gate / text 校验后 · insertIntoSection
|
|
215
|
-
//
|
|
216
|
-
const residues = findPlaceholderResidue(src
|
|
214
|
+
// F11-C(挂点:gate / text 校验后 · insertIntoSection 前):档头结构行死占位残留 ⇒ 拒
|
|
215
|
+
// (段体引用豁免——判据可辨性收正,`TOOLS.md` §6.20;骨架枚举单源 = skeleton 档 TEMPLATE_PLACEHOLDERS)。
|
|
216
|
+
const residues = findPlaceholderResidue(src)
|
|
217
217
|
if (residues.length) throw new Error(placeholderResidueError(residues))
|
|
218
218
|
const { written, roundN } = insertIntoSection(src, n, body)
|
|
219
219
|
writeFileSync(abs, written)
|
|
@@ -268,6 +268,14 @@ export function batchTool(batchDoc = null, { review = false } = {}) {
|
|
|
268
268
|
type: "string",
|
|
269
269
|
description: "create only — REQUIRED: the record's origin line (来源 = …; single line). It fills the header's 编制 line; without it the skeleton's placeholder would block the record's first append/status (topic-style fail-closed). Nothing is written when it is missing.",
|
|
270
270
|
},
|
|
271
|
+
ledger: {
|
|
272
|
+
type: "string",
|
|
273
|
+
description: "create only — optional: the ledger entry number for the header's 台账 line (e.g. \"#938\"; a leading # is added if missing). Passed together with board, it fills the header at creation; when omitted, the placeholder stays and must be filled by file edit before the record's first append/status.",
|
|
274
|
+
},
|
|
275
|
+
board: {
|
|
276
|
+
type: "string",
|
|
277
|
+
description: "create only — optional: the owning board for the header's 台账 line (e.g. \"core\"). See ledger — both fill the header at creation.",
|
|
278
|
+
},
|
|
271
279
|
date: {
|
|
272
280
|
type: "string",
|
|
273
281
|
description: "create only — the record date (YYYY-MM-DD); defaults to today.",
|
|
@@ -95,6 +95,19 @@ export function stripDesignTokenEcho(text, designToken) {
|
|
|
95
95
|
const NO_VALID_ECHO_MARK = "未回显有效 token——未签发 (no valid token echo — no design token was issued)"
|
|
96
96
|
const TRUNCATED_ECHO_MARK = "注:回显被截断(uuid 之后缺失或不符)——已按批准结算;全串 token 已签发 (note: truncated echo — the part after the uuid was missing or inexact; settled as approved; the full token was issued)"
|
|
97
97
|
|
|
98
|
+
/** §5.2 verdict gate (#940): the echo alone is NOT the verdict — the receipt must
|
|
99
|
+
* carry a `VERDICT: pass` line and no `VERDICT: changes-required` line (fail-closed). */
|
|
100
|
+
const VERDICT_PASS_LINE = /^[ \t]*[*_`]*[ \t]*VERDICT:[ \t]*pass[ \t]*[*_`]*[ \t]*$/m
|
|
101
|
+
const VERDICT_CHANGES_LINE = /^[ \t]*[*_`]*[ \t]*VERDICT:[ \t]*changes-required[ \t]*[*_`]*[ \t]*$/m
|
|
102
|
+
export function verdictGateFailure(rawResult) {
|
|
103
|
+
if (typeof rawResult !== "string") return "no-pass-line"
|
|
104
|
+
if (VERDICT_CHANGES_LINE.test(rawResult)) return "changes-required"
|
|
105
|
+
return VERDICT_PASS_LINE.test(rawResult) ? null : "no-pass-line"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** §5.2 rejection mark (verbatim — machine-greppable; reason = no-pass-line | changes-required). */
|
|
109
|
+
const VERDICT_GATE_MARK = (reason) => `裁定非通过——token 未签发 (review verdict is not pass — no design token was issued; reason: ${reason}; the echoed token was stripped)`
|
|
110
|
+
|
|
98
111
|
/**
|
|
99
112
|
* Shared design-review settlement (sync wrapper + async settle): the token echo
|
|
100
113
|
* IS the verdict — the advisor echoes it only on approval. On echo (full string
|
|
@@ -134,6 +147,13 @@ export function settleDesignReview(agent, run, designToken, rawResult, opts = {}
|
|
|
134
147
|
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
135
148
|
return { passed: false, output: [stripped, NO_VALID_ECHO_MARK].filter(Boolean).join("\n\n") }
|
|
136
149
|
}
|
|
150
|
+
// §5.2 裁定闸(#940):签发 = 回显 ∧ 裁定行——缺 `VERDICT: pass` 行或含 `VERDICT: changes-required` 行
|
|
151
|
+
// ⇒ 拒签(与 M1 径同形:剥净回显 + 恒定标记;槽零写 ∥ 实例保持——修后重评)。
|
|
152
|
+
const gateFailure = verdictGateFailure(rawResult)
|
|
153
|
+
if (gateFailure !== null) {
|
|
154
|
+
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
155
|
+
return { passed: false, output: [stripped, VERDICT_GATE_MARK(gateFailure)].filter(Boolean).join("\n\n") }
|
|
156
|
+
}
|
|
137
157
|
// Echoed the token → review passed. Issue it to the parent for eng-coder.
|
|
138
158
|
agent._engDesignTokens ??= new Map()
|
|
139
159
|
agent._engDesignTokens.set(run.designId, designToken)
|
|
@@ -107,8 +107,10 @@ function touchedFilesNote(child, cwd) {
|
|
|
107
107
|
export function classifyEscalateSettle(parent, entry) {
|
|
108
108
|
if (entry.cancelled) return { cancelled: true }
|
|
109
109
|
// AGENT-LOOP.md §6.15(TUI-OOM-ROOTCAUSE)——childAgent 语义面:settle 分类(touched 摘要/partial
|
|
110
|
-
// merge)与 status/observe
|
|
111
|
-
//
|
|
110
|
+
// merge)与 status/observe 摘要的读取源;**消化注入完成后释放**(池内未消化窗口不释放——表 2 候选 2 否决);
|
|
111
|
+
// fallback 面(无 `_daSession`)由 `releaseSettledEntry`(async-settle.mjs)置 null(逐字沿用);会话态
|
|
112
|
+
// (`_daSession`)注入后改调 `releaseChildHold`(仅释放 `childAgent`——`report` 保留至销账 / 升级;
|
|
113
|
+
// §6.31.4 ∥ KD-DA7)。
|
|
112
114
|
const child = entry.childAgent
|
|
113
115
|
const touched = child?._touchedFiles ?? []
|
|
114
116
|
const overlap = overlapPaths(parent, entry.launchSeq, touched)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* review-facts.mjs — design-review 事实面(纯事实:路径归一 + 评审范围键)。
|
|
3
|
-
*
|
|
4
|
-
* 本档前身 = 第 33 批(2026-09-11)的失败计数护栏档;其计数载体 / 停止谓词 / 两级检查点随
|
|
5
|
-
* 「撤除全部会话级轮次计数器」**整体退场**(2026-09-18 用户裁定;设计权威 =
|
|
6
|
-
* `docs/core/design/ADVISOR-CONVERGENCE.md` §3.1/§3.4 + `ADVISOR-GUARDS.md` §7)。
|
|
7
|
-
* 结算分类迁出为纯函数(输出判据名、零状态)= `thincoder-core/advisor/notice.mjs`。
|
|
1
|
+
/** 本档前身 = 第 33 批失败计数护栏档(载体随 2026-09-18 计数器整体退场;结算分类 = `advisor/notice.mjs`)。
|
|
8
2
|
*
|
|
9
|
-
* 本档 = 中立模块(**不 import 任何 src/
|
|
10
|
-
* - `normAbs` 自 advisor-settle.mjs 迁入(原处 re-export——既有 import
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
3
|
+
* 本档 = 中立模块(**不 import 任何 src/ 模块**——打断潜在环;2026-10-04 边界收窄):
|
|
4
|
+
* - `normAbs` 自 advisor-settle.mjs 迁入(原处 re-export——既有 import 面零变);
|
|
5
|
+
* - `docSetKey` 自 advisor-async.mjs 迁入(原为私有——零 import 面);
|
|
6
|
+
* - 2026-10-04(工具路径基面根治批 · #921):import 面扩为「node:* + manifest 发现/声明族
|
|
7
|
+
* (叶向——manifest 族零依赖 advisor 面,零新模块边;KD-2)」——`REVIEW_ROOT_KEYS` +
|
|
8
|
+
* `resolveReviewRootsFor` 判定本体自 `agent/write-gate.mjs` 迁入(write-gate 同名再出口保
|
|
9
|
+
* 既有 import 面),新增 `resolveReviewDocPaths` 文档解析四腿单源。
|
|
13
10
|
*/
|
|
14
|
-
import {
|
|
11
|
+
import { existsSync, statSync } from "node:fs"
|
|
12
|
+
import { dirname, join, resolve } from "node:path"
|
|
13
|
+
import { DEFAULT_MANIFEST, docRootPaths, readManifest } from "../manifest.mjs"
|
|
14
|
+
import { owningProject, projectRootView } from "../manifest-discovery.mjs"
|
|
15
15
|
|
|
16
16
|
/** ABS 归一(cwd 相对 → cwd 拼接)——陈旧判定 / 冻结拦截 / 评审范围键同源(ADVISOR-GUARDS.md §5 E-4)。 */
|
|
17
17
|
export function normAbs(p, cwd) {
|
|
@@ -19,13 +19,140 @@ export function normAbs(p, cwd) {
|
|
|
19
19
|
return /^[a-zA-Z]:[\\/]/.test(s) || s.startsWith("/") || s.startsWith("\\\\") ? s : join(cwd, s)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/** Canonical scope key for design reviews
|
|
23
|
-
* (order-insensitive, ABS-path normalized — launch 与 continuation 的写法差异
|
|
24
|
-
* ("./docs/x.md" vs "docs/x.md"、反斜杠) 不误建新实例).
|
|
25
|
-
* 第 33 批自 advisor-async.mjs 逐字迁入(实例续跑与事实面同锚单源)。 */
|
|
22
|
+
/** Canonical scope key for design reviews(写法差异不误建新实例;第 33 批自 advisor-async.mjs 迁入)。 */
|
|
26
23
|
export function docSetKey(documents, cwd) {
|
|
27
24
|
const list = [...new Set((documents ?? [])
|
|
28
25
|
.filter((d) => typeof d === "string" && d.trim())
|
|
29
26
|
.map((d) => normAbs(d, cwd)))]
|
|
30
27
|
return JSON.stringify(list.sort())
|
|
31
28
|
}
|
|
29
|
+
|
|
30
|
+
/** 评审目标解析读的 docRoot 键集(评审对象/被审文件可住的所有文档层——M6 分类据此判定)。
|
|
31
|
+
* 2026-10-04 自 `agent/write-gate.mjs` 迁入(键集本体零变;write-gate 同名再出口)。 */
|
|
32
|
+
export const REVIEW_ROOT_KEYS = ["requirements", "specs", "design", "modules", "batches"]
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 评审目标解析单点(按用点形——2026-09-21 收口 · 台账 #828;2026-10-04 自 `agent/write-gate.mjs`
|
|
36
|
+
* 迁入,逐字零变;write-gate 同名再出口保既有 import 面)。
|
|
37
|
+
*/
|
|
38
|
+
export function resolveReviewRootsFor(dir) {
|
|
39
|
+
const man = readManifest(dir)
|
|
40
|
+
const root = man.ok && man.manifest?.docRoot && typeof man.manifest.docRoot === "object"
|
|
41
|
+
? man.manifest.docRoot
|
|
42
|
+
: DEFAULT_MANIFEST.docRoot
|
|
43
|
+
const out = []
|
|
44
|
+
for (const key of REVIEW_ROOT_KEYS) out.push(...docRootPaths(root?.[key], dir))
|
|
45
|
+
return [...new Set(out)]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 可读文件判据(存在且为文件——目录 / 缺失同判不可读;同 batch-paths.mjs 读面先例)。 */
|
|
49
|
+
function readableFile(abs) {
|
|
50
|
+
try { return existsSync(abs) && statSync(abs).isFile() } catch { return false }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 会话根集(腿①判据集——既有 advisor 面 `resolveReviewTargetPaths(agent)` 目录形态的函数式封装;档内私有——零外部消费面)。 */
|
|
54
|
+
function sessionReviewRoots(cwd) {
|
|
55
|
+
return resolveReviewRootsFor(cwd ?? process.cwd())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* **文档解析四腿单源**(2026-10-04 · 批档 §2.2 A 本体):逐文档
|
|
60
|
+
* 腿① 零变:normAbs(doc, cwd) 落会话根集 ⇒ 受理;腿② 零变:所属项目根集命中 ⇒ 受理;
|
|
61
|
+
* 腿③ 根治面:相对形 ∧ 前两腿未中 ⇒ 候选底座逐个试探(底座 = projectRootView(cwd) 现行集 +
|
|
62
|
+
* cwd 祖先链全量目录——自父目录起至盘根、最近→远、去重、现行集在前)——resolve(c, doc)
|
|
63
|
+
* 所属项目根集命中 = 结构命中;绝对形不进本腿(未中 = 真越界,无候选可试);
|
|
64
|
+
* 腿④ 歧义归一(KD-3):结构命中 ≥2 ⇒ 可读文件存在性唯一化(batch-paths 读面「首个可读」
|
|
65
|
+
* 同款判据):恰一可读 ⇒ 胜;≥2 可读 ⇒ fail-closed 拒(scope-doc-ambiguous 列全部
|
|
66
|
+
* 可读命中);0 可读 ⇒ not-doc 拒(诊断列全部结构命中)。
|
|
67
|
+
* @returns {{ resolved: Map<string,string>, invalid: Array<{doc, attempted:string[]}>,
|
|
68
|
+
* ambiguous: {doc, hits:string[]}|null, candidates: string[] }} ambiguous = 首个歧义文档
|
|
69
|
+
* (fail-closed 首拒——消费面拒发即止);candidates = 腿③候选集(B 文案候选提示同源)。
|
|
70
|
+
* 非字符串 / 空白串项**静默跳过**(同 docSetKey 既有过滤语义——不进 resolved/invalid)。
|
|
71
|
+
*/
|
|
72
|
+
export function resolveReviewDocPaths(documents, cwd) {
|
|
73
|
+
const fwd = (p) => p.replace(/[\\/]/g, "/")
|
|
74
|
+
const sessionRoots = sessionReviewRoots(cwd).map(fwd)
|
|
75
|
+
const view = projectRootView(cwd ?? process.cwd())
|
|
76
|
+
const candidates = candidateRoots(view, cwd ?? process.cwd())
|
|
77
|
+
const ownRootsCache = new Map()
|
|
78
|
+
const ownRoots = (owner) => {
|
|
79
|
+
let r = ownRootsCache.get(owner)
|
|
80
|
+
if (!r) {
|
|
81
|
+
r = resolveReviewRootsFor(owner).map(fwd)
|
|
82
|
+
ownRootsCache.set(owner, r)
|
|
83
|
+
}
|
|
84
|
+
return r
|
|
85
|
+
}
|
|
86
|
+
// under = 段边界判(比较面用 `/` 归一形;**产出面保原生形态**——docAbs / resolved 与 normAbs
|
|
87
|
+
// 既有形态同族,冻结窗/陈旧谓词零改照比对——win32 反斜杠面不破)。
|
|
88
|
+
const under = (pNative, rootsFwd) => {
|
|
89
|
+
const p = fwd(pNative)
|
|
90
|
+
return rootsFwd.some((r) => p === r || p.startsWith(r + "/"))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const resolved = new Map()
|
|
94
|
+
const invalid = []
|
|
95
|
+
let ambiguous = null
|
|
96
|
+
for (const doc of (documents ?? [])) {
|
|
97
|
+
if (typeof doc !== "string" || !doc.trim()) continue
|
|
98
|
+
const isAbs = /^[a-zA-Z]:[\\/]/.test(doc) || doc.startsWith("/") || doc.startsWith("\\\\")
|
|
99
|
+
const n = normAbs(doc, cwd)
|
|
100
|
+
// 腿① 会话根集 / 腿② 所属项目根集(既有行为零变——判定序同 advisor.mjs 旧面)。
|
|
101
|
+
if (under(n, sessionRoots)) { resolved.set(doc, n); continue }
|
|
102
|
+
const owner = owningProject(n)
|
|
103
|
+
if (owner && under(n, ownRoots(owner))) { resolved.set(doc, n); continue }
|
|
104
|
+
// 腿③ 候选项目根试探(相对形 only——绝对形未中 = 真越界,无候选可试)。
|
|
105
|
+
let hits = []
|
|
106
|
+
if (!isAbs && candidates.length > 0) {
|
|
107
|
+
for (const c of candidates) {
|
|
108
|
+
const t = resolve(c, doc)
|
|
109
|
+
const cOwner = owningProject(t)
|
|
110
|
+
if (cOwner && under(t, ownRoots(cOwner))) hits.push(t)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 腿④ 歧义归一:结构命中 ≥2 ⇒ 可读文件存在性唯一化;恰 1 命中 ⇒ 受理(存在性仅作
|
|
114
|
+
// 歧义唯一化判据——单命中无判别力,同腿①声明根集受理面一致不查存在)。
|
|
115
|
+
if (hits.length >= 2) {
|
|
116
|
+
const readable = hits.filter((h) => readableFile(h))
|
|
117
|
+
if (readable.length === 1) { resolved.set(doc, readable[0]); continue }
|
|
118
|
+
if (readable.length >= 2) { ambiguous = { doc, hits: readable }; break }
|
|
119
|
+
} else if (hits.length === 1) {
|
|
120
|
+
resolved.set(doc, hits[0]); continue
|
|
121
|
+
}
|
|
122
|
+
invalid.push({ doc, attempted: hits })
|
|
123
|
+
}
|
|
124
|
+
return { resolved, invalid, ambiguous, candidates }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** #949(评审 history 串台面 ① 镜像键控):评审实例镜像三值单点定域(round ∥ prior ∥
|
|
128
|
+
* 响应表取件下界 `_advisorResponseAnchor`)——`resolveAdvisorLaunch` 发起定域 +
|
|
129
|
+
* `entry.start` 排队 / 延迟窗口消费点复核(构建前幂等重落);非排队 / 同步语义零变。 */
|
|
130
|
+
export function scopeAdvisorMirror(agent, run) {
|
|
131
|
+
agent._advisorRound = run.priorOutput ? run.round : 0
|
|
132
|
+
agent._lastAdvisorOutput = run.priorOutput
|
|
133
|
+
agent._advisorResponseAnchor = run.historyAnchorIdx ?? null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** #949(② 投递水印):响应表取件下界 = 投递刻 history 下标(消费面前向取首表)。
|
|
137
|
+
* 调用点:async `injectAsyncResult`(pushReal 后)∥ sync record-results 记账块;
|
|
138
|
+
* run 缺省 / 非对象(legacy 直调)⇒ 零写(回落全文倒扫)。 */
|
|
139
|
+
export function noteReviewDelivered(agent, run) {
|
|
140
|
+
if (!run || typeof run !== "object") return
|
|
141
|
+
run.historyAnchorIdx = Array.isArray(agent?.history) ? agent.history.length : 0
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** 腿③候选底座(`MANIFEST.md` §2.5「路径归一增量」族第四笔 · 2026-10-05 · #945):现行
|
|
145
|
+
* `projectRootView(cwd)` 集(ok → [root];ambiguous → candidates 按名序;none → 空集)+
|
|
146
|
+
* cwd 祖先链全量目录(自父目录起至盘根、最近→远、去重、现行集在前)——容器相对形在子仓锚下
|
|
147
|
+
* 可解析;受理判据(目标解析位最近 manifest 声明面)∥ 围栏 ∥ 可读唯一化 ∥ fail-closed 歧义零改。 */
|
|
148
|
+
function candidateRoots(view, cwd) {
|
|
149
|
+
const current = view.state === "ok" ? [view.root] : view.state === "ambiguous" ? view.candidates : []
|
|
150
|
+
const ancestors = []
|
|
151
|
+
for (let d = dirname(resolve(cwd)); ; ) {
|
|
152
|
+
ancestors.push(d)
|
|
153
|
+
const p = dirname(d)
|
|
154
|
+
if (p === d) break
|
|
155
|
+
d = p
|
|
156
|
+
}
|
|
157
|
+
return [...new Set([...current, ...ancestors])]
|
|
158
|
+
}
|
|
@@ -13,6 +13,8 @@ import { describeBlockers, detectStall, STALL_NOTE } from "./subagent-scheduler.
|
|
|
13
13
|
// ASYNC-RESULT-CONTAINER.md D1:池 accessor(absorb 双池——advisor 独立池无队列)
|
|
14
14
|
import { getAsyncPool } from "./async-settle.mjs"
|
|
15
15
|
import { turnCapTrace } from "./checkpoint.mjs"
|
|
16
|
+
// §6.31.6 未清算可见面(批 digest-accounting · 2026-10-05 · 台账 #930):unsettled 段 + 单查续查
|
|
17
|
+
import { unsettledRow, unsettledRows } from "../agent/digest-account.mjs"
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* subagent action:"status" (AGENT-LOOP-SUBAGENT.md §6.7 D-M2, new): NON-BLOCKING async-pool query —
|
|
@@ -22,11 +24,13 @@ import { turnCapTrace } from "./checkpoint.mjs"
|
|
|
22
24
|
* (AGENT-LOOP-ASYNC-POOL.md §6.8 D-S3 ② — injected at the next run start) are no longer in the pool and
|
|
23
25
|
* are NOT counted as done-waiting.
|
|
24
26
|
* - id given → { id, role, status, model?, elapsedSec?, turn?, maxTurns?,
|
|
25
|
-
* touchedFiles?/touchedMore?/touched? ... } for that entry;
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* touchedFiles?/touchedMore?/touched? ... } for that entry; when the id matches neither pool it
|
|
28
|
+
* falls through to `_pendingAsyncResults` / `_unsettledDigests` (§6.31.6 — same row shape);
|
|
29
|
+
* unknown everywhere → error (T12 semantics — unknown async-subagent id error, same wording as the pool).
|
|
30
|
+
* - id omitted → { overview: { running: [...], queued: [...], done: [...],
|
|
31
|
+
* unsettled: [{id, role, state, attempts, preview?}] } } — live queue positions (index in
|
|
32
|
+
* _asyncQueue + 1); the unsettled section lists every entry still awaiting settlement
|
|
33
|
+
* (awaiting-digest / retrying) plus the upgraded ledger (§6.31.6).
|
|
30
34
|
* AGENT-LOOP-SUBAGENT.md §6.7.2: running 条目带 touched files 摘要(touchedFiles 前 5 + touchedMore 超出
|
|
31
35
|
* 计数——相对查询方 cwd;0 改动 → touched 占位);queued 条目带 touched 占位
|
|
32
36
|
* "—(未启动)";done/error/取消条目无 touched 字段(round3 #9)。
|
|
@@ -113,6 +117,9 @@ export function executeStatusAction(args, ctx) {
|
|
|
113
117
|
// ids are unique across both pools; role identifies the kind).
|
|
114
118
|
const entry = map.get(key) ?? advisors.get(key)
|
|
115
119
|
if (!entry) {
|
|
120
|
+
// §6.31.6(消化账务批):续查 pending / 升级账本同解析——三处皆无 ⇒ 原错误文案不变。
|
|
121
|
+
const row = unsettledRow(agent, key)
|
|
122
|
+
if (row) return JSON.stringify(row)
|
|
116
123
|
// Wording kept exact — locked by subagent-async/tool tests (unknown-id error
|
|
117
124
|
// semantics); an unknown id simply names neither pool (the pools share the
|
|
118
125
|
// id counter, so the message stays unambiguous).
|
|
@@ -146,7 +153,7 @@ export function executeStatusAction(args, ctx) {
|
|
|
146
153
|
target.note = "settled, not yet consumed — delivered by the auto channel (turn-end collection or the suspension digest injects it)"
|
|
147
154
|
return JSON.stringify(target)
|
|
148
155
|
}
|
|
149
|
-
const overview = { running: [], queued: [], done: [] }
|
|
156
|
+
const overview = { running: [], queued: [], done: [], unsettled: unsettledRows(agent) }
|
|
150
157
|
const mapEntries = [...map.values(), ...advisors.values()]
|
|
151
158
|
for (const entry of mapEntries) {
|
|
152
159
|
if (entry.status === "running") overview.running.push(statusFields(entry, agent.cwd))
|
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
import { cancelAsyncAdvisor, noteMutations, refreshAdvisorQueuedTokens } from "./advisor-async.mjs"
|
|
35
35
|
// #94(VSC 侧并入——异步机械族):池读取载体吸收 + 墓碑写入单点(原 inline 写收口)。
|
|
36
36
|
import { getAsyncPool, tombstoneOf, writeTombstone } from "./async-settle.mjs"
|
|
37
|
+
// #949(评审 history 串台面):评审实例投递水印单点(叶档——零环)。
|
|
38
|
+
import { noteReviewDelivered } from "./review-facts.mjs"
|
|
37
39
|
// #9(TOOLS.md §6.19):后台 bash 任务注入器(digest 单点族按 role="bg" 分发到本档)。
|
|
38
40
|
import { injectBgResult } from "./bash-async.mjs"
|
|
39
41
|
|
|
@@ -394,10 +396,15 @@ export async function injectAsyncResult(agent, entry) {
|
|
|
394
396
|
role: "user",
|
|
395
397
|
content: label,
|
|
396
398
|
})
|
|
399
|
+
// #949:评审族投递水印落点(响应表取件下界 = 投递刻下标;非评审族零写)。
|
|
400
|
+
if (entry.role === "advisor") noteReviewDelivered(agent, entry.run)
|
|
397
401
|
// §6.9 D-SD5 终态墓碑:本函数是全部自动注入路径的共享形态(回合尾 collect + 挂起
|
|
398
|
-
// digest
|
|
399
|
-
//
|
|
400
|
-
//
|
|
402
|
+
// digest 首行注入)——注入即消费(墓碑落注入时点——依赖满足判据不随销账后移)——容器侧:
|
|
403
|
+
// 会话态(`_daDelivered`)注入后**不随即从容器移除**(留容器至销账 / 升级;移除 =
|
|
404
|
+
// `accountDigestRound` 的 splice,§6.31),fallback 面照旧(缺省条取尽离容器——先离容后注入;
|
|
405
|
+
// `_daDelivered` 条同判据跳过且留容器)——
|
|
406
|
+
// dependsOn 引用该 id 的后续 spawn 视为已满足(T-SD14 消费终态语义——AGENT-LOOP-SUBAGENT.md §6.7.5 后 check
|
|
407
|
+
// 消费路径删除,本自动通道为唯一消费方;error 条目记 failed——依赖取消/失败分支照旧,不误标成功)。
|
|
401
408
|
writeTombstone(agent, entry.id, entry.error != null ? "failed" : "consumed", entry.role)
|
|
402
409
|
}
|
|
403
410
|
|
|
@@ -257,7 +257,8 @@ export function executePanelAction(args, ctx) {
|
|
|
257
257
|
if (b.status === "running") {
|
|
258
258
|
out.elapsedSec = b.startedAt ? Math.max(0, Math.floor((Date.now() - b.startedAt) / 1000)) : 0
|
|
259
259
|
} else if (b.status === "awaitingDigest") {
|
|
260
|
-
// 读时交叉(round1 #3):pending/池均无对应 =
|
|
260
|
+
// 读时交叉(round1 #3):pending/池均无对应 = 报告已消化(fallback 面注入即从两者移除;
|
|
261
|
+
// 会话面注入后不随即移除——留容器至销账 / 升级,移除 = `accountDigestRound` 的 splice;§6.31)——
|
|
261
262
|
// 块驻留 = 状态滞后——digested:true(freeze 候选——模型可定位异常块)。
|
|
262
263
|
// ASYNC-RESULT-CONTAINER.md D2:pending 单容器参与比对(四族统一)。
|
|
263
264
|
// #748:consult 子块按消费判据(会话在跑 ∥ 会话条目在 pending ⇒ 未消化)——与门控同源。
|
|
@@ -243,7 +243,7 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
|
|
|
243
243
|
if (role === "eng-coder" || role === "eng-designer") {
|
|
244
244
|
const given = typeof args.batchDoc === "string" ? args.batchDoc.trim() : ""
|
|
245
245
|
const refusal = (suffix = "") => new Error(
|
|
246
|
-
`batchDoc is required for role='${role}' — pass the batch record path (docs/batches/<batch>-<topic>.md); spawn refused without it.` + suffix)
|
|
246
|
+
`batchDoc is required for role='${role}' — pass the batch record path (e.g. <repo>/docs/batches/<batch>-<topic>.md; relative paths resolve against the session cwd first, then candidate project roots — or absolute); spawn refused without it.` + suffix)
|
|
247
247
|
if (!given) throw refusal()
|
|
248
248
|
batchDocAbs = resolveBatchReadPath(parent.cwd ?? process.cwd(), given)
|
|
249
249
|
if (!batchDocAbs) throw refusal(" (given path is not a readable file)")
|
package/agent-tools/subagent.mjs
CHANGED
|
@@ -139,7 +139,7 @@ export const subagentTool = {
|
|
|
139
139
|
model: { type: "string", description: "action:'spawn': provider/model override ('provider:model', a provider, or a model name on the parent's provider) — defaults config.agent.subagentModels[role] → config.agent.subagentModel → the parent's provider; \"default\" inherits. action:'escalate': a consult candidate (default = the first)." },
|
|
140
140
|
designToken: { type: "string", description: "Required when role='eng-coder': the token from advisor(type='design') after the review passed — without it, eng-coder cannot modify files." },
|
|
141
141
|
designId: { type: "string", description: "role='eng-coder': the designId echoed with the approved token — required to pick between concurrent designs (optional for one). action:'consume-design': the slot to close out (required to pick between several; the gate refuses to guess)." },
|
|
142
|
-
batchDoc: { type: "string", description: "REQUIRED for role='eng-coder'/'eng-designer': the batch record path (docs/batches/<batch>-<topic>.md) — the task book this spawn implements/writes. Refused when absent or when the path does not resolve to a readable file; the content is never validated." },
|
|
142
|
+
batchDoc: { type: "string", description: "REQUIRED for role='eng-coder'/'eng-designer': the batch record path (e.g. <repo>/docs/batches/<batch>-<topic>.md; relative paths resolve against the session cwd first, then candidate project roots — or absolute) — the task book this spawn implements/writes. Refused when absent or when the path does not resolve to a readable file; the content is never validated." },
|
|
143
143
|
round: { type: "string", enum: ROUND_VALUES, description: "REQUIRED for role='eng-coder'/'eng-designer': initial (first round for this design) or fix (a correction round reusing the same designId+designToken; docs FIRST). No default — missing = refused." },
|
|
144
144
|
async: { type: "boolean", description: "action:'spawn'/'escalate': default at depth 0 = true (async; the report arrives automatically); depth>0 always sync. async:false forces the blocking flight." },
|
|
145
145
|
files: { type: "array", items: { type: "string" }, description: "action:'spawn' only: the file write-domain this task declares — file-level paths only (cwd-relative or absolute; directories rejected — they bypass the conflict detector). Overlapping-file tasks serialize automatically. Omit = no conflict detection." },
|