@thincoder/core 0.10.2 → 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 +65 -0
- package/advisor/citations.mjs +40 -4
- package/advisor/compaction.mjs +21 -2
- package/advisor/convergence.mjs +1 -1
- package/advisor/history.mjs +8 -6
- package/advisor/loop.mjs +29 -27
- package/advisor/messages.mjs +5 -4
- 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/dispatch-run.mjs +29 -12
- package/agent/family-tools.mjs +11 -11
- package/agent/helpers.mjs +8 -0
- package/agent/record-results.mjs +10 -1
- package/agent/run-stages.mjs +27 -4
- package/agent/run-start.mjs +49 -3
- package/agent/spawn-child.mjs +2 -1
- package/agent/subagent-event.mjs +71 -0
- package/agent/suspension.mjs +60 -12
- package/agent/turn-loop.mjs +31 -2
- package/agent/write-gate.mjs +13 -25
- package/agent-tools/advisor-async.mjs +25 -7
- package/agent-tools/advisor-settle.mjs +7 -9
- package/agent-tools/advisor.mjs +34 -33
- package/agent-tools/async-settle.mjs +29 -5
- package/agent-tools/batch-lifecycle.mjs +29 -7
- package/agent-tools/batch-paths.mjs +22 -2
- package/agent-tools/batch-read.mjs +36 -0
- package/agent-tools/batch-skeleton.mjs +59 -33
- package/agent-tools/batch.mjs +16 -6
- package/agent-tools/design-token.mjs +76 -16
- 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 +16 -4
- package/agent-tools/subagent-panel.mjs +121 -37
- package/agent-tools/subagent-spawn.mjs +1 -1
- package/agent-tools/subagent.mjs +5 -3
- package/agent-tools/verify.mjs +5 -0
- package/agent-tools/zero-write-watch.mjs +77 -0
- package/agent.mjs +1 -1
- package/auto-think.mjs +26 -2
- package/config-presets.mjs +8 -0
- package/config.mjs +30 -0
- package/conventions.mjs +26 -6
- package/declaration.mjs +26 -2
- package/embedding.mjs +35 -3
- package/escape.mjs +1 -1
- package/i18n.mjs +5 -0
- package/ledger-cmd.mjs +13 -21
- package/ledger-db.mjs +34 -1
- package/ledger-migrate.mjs +4 -3
- package/ledger-read.mjs +94 -0
- package/ledger-surface.mjs +15 -46
- package/ledger-tools.mjs +148 -85
- package/ledger-variant-notice.mjs +47 -0
- package/ledger.mjs +38 -64
- package/manifest-schema.mjs +19 -7
- package/manifest.mjs +32 -11
- package/mcp/transport-http.mjs +52 -4
- package/mcp/transport-stdio.mjs +33 -8
- package/mcp.mjs +13 -5
- package/memory/code-search.mjs +5 -3
- package/memory/core.mjs +6 -4
- package/memory/docs.mjs +5 -3
- package/memory/schema.mjs +2 -0
- package/package.json +1 -1
- package/prompts/advisor-design.md +3 -2
- package/prompts/advisor-round1.md +1 -0
- package/prompts/advisor-round2.md +8 -3
- package/prompts/advisor-round3.md +7 -2
- package/prompts/common.md +2 -2
- package/prompts/discipline-normal.md +2 -1
- package/prompts/persona-engineering.md +7 -4
- package/provider/anthropic.mjs +14 -2
- package/provider/core.mjs +10 -4
- package/provider/errors.mjs +23 -0
- package/provider/google.mjs +31 -4
- package/provider/normalize.mjs +57 -0
- package/provider/responses-request.mjs +3 -1
- package/provider/responses.mjs +38 -4
- package/provider/sse.mjs +45 -13
- package/proxy.mjs +13 -3
- package/session.mjs +13 -2
- package/shell-identity.mjs +107 -0
- package/stream-destroy.mjs +62 -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/tool-docs/verify.md +1 -1
- package/tools/bash.mjs +9 -4
- package/tools/edit-batch.mjs +7 -2
- package/tools/index.mjs +1 -4
- package/tools/patch.mjs +8 -2
- package/tools/write-path.mjs +6 -4
- package/undo-stack.mjs +23 -9
- 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
|
@@ -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
|
|
|
@@ -125,6 +121,34 @@ export function readBatchStatusLine(src) {
|
|
|
125
121
|
return "unknown"
|
|
126
122
|
}
|
|
127
123
|
|
|
124
|
+
/**
|
|
125
|
+
* §N 段状态词解析(read-data-interface 批——`batch/list` 读面单源,设计档 §2.5):段头
|
|
126
|
+
* (sectionHeaderRe)/ 状态行正则(STATUS_LINE_RE)/ 词表(STATUS_WORDS)全消费既有单源常量——
|
|
127
|
+
* 与冻结门字面零漂移。`§1`–`§3` / `§5`:段内首个状态行的值按**终态词优先**(closed / done 先于
|
|
128
|
+
* open——镜像冻结门取值序)命中已识别关键词 ⇒ 返回该关键词;段头缺失 / 无状态行 / 无命中 ⇒ null。
|
|
129
|
+
* `§4` / `§6` 无词条 ⇒ 字面 `无状态词`(不造词)。
|
|
130
|
+
* @param {string} src — 档全文
|
|
131
|
+
* @param {number|string} seg — 段号(`2` / `"2"`——同 sectionHeaderRe 消费形)
|
|
132
|
+
* @returns {string|null} 已识别关键词 / null / 字面「无状态词」(§4 / §6)
|
|
133
|
+
*/
|
|
134
|
+
export function readSectionStatusWord(src, seg) {
|
|
135
|
+
const words = STATUS_WORDS[seg]
|
|
136
|
+
if (!words) return "无状态词"
|
|
137
|
+
const hdr = sectionHeaderRe(seg).exec(src)
|
|
138
|
+
if (!hdr) return null
|
|
139
|
+
const nextRe = /^## §\d/gm
|
|
140
|
+
nextRe.lastIndex = hdr.index + hdr[0].length
|
|
141
|
+
const next = nextRe.exec(src)
|
|
142
|
+
const body = src.slice(hdr.index + hdr[0].length, next ? next.index : src.length)
|
|
143
|
+
const m = body.split(/\r?\n/).map((line) => STATUS_LINE_RE.exec(line)).find(Boolean)
|
|
144
|
+
if (!m) return null
|
|
145
|
+
const value = m[1].trim()
|
|
146
|
+
for (const w of [words.closed, words.done, words.open]) {
|
|
147
|
+
if (w !== undefined && value.includes(w)) return w
|
|
148
|
+
}
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
128
152
|
/**
|
|
129
153
|
* §N 段内「`**状态行**:` 行存在性」判据(#422③——close 对无状态行档自建机读位的**采集面**:
|
|
130
154
|
* 与 readBatchStatusLine 的「值可解析」正交——无行 ⇒ 可自建;有行而值不可解析 ⇒ 仍 fail-closed)。
|
|
@@ -149,16 +173,18 @@ export function sectionHasStatusLine(src, seg) {
|
|
|
149
173
|
* 编制行 + 台账/前情指针行)+ §1 段内**占位状态行**(含 gate 合法关键字「进行中」——建档即过
|
|
150
174
|
* gate;整行不含「已收口」——gate 已收口优先,占位行不得携带冻结词)+ 各段模板子标题占位。
|
|
151
175
|
* 已知实参替换:date / topic / **source** / prev(§4.11 参数面 + F11-B——source 必填、prev 传
|
|
152
|
-
* 入值过幂等剥前缀,归一化在 lifecycle
|
|
153
|
-
*
|
|
176
|
+
* 入值过幂等剥前缀,归一化在 lifecycle 面);`ledger` / `board` = 可选参(2026-10-05 建即填——
|
|
177
|
+
* 给则台账行实参化、未给保持占位字面;4 参旧调用形输出零变)。档头 `#<编号>` / `<板块>` 占位
|
|
178
|
+
* 留存时(未给参)由主 agent 文件编辑填(台账登记 = 主 agent 既有义务,create 不代建——填充
|
|
179
|
+
* 时点 = 建档后、本档首个 append/status 之前)。
|
|
154
180
|
* 本批改形删除(F11-B):旧 `# …(<BATCH-ID>)` 后缀与 `来源 = <讨论来源>` 占位实参化。
|
|
155
181
|
*/
|
|
156
|
-
export function batchSkeleton({ date, topic, source, prev }) {
|
|
182
|
+
export function batchSkeleton({ date, topic, source, prev, ledger = null, board = null }) {
|
|
157
183
|
return [
|
|
158
184
|
`# ${date} · ${topic}`,
|
|
159
185
|
"> 六段 append-only,一段一作者:§1 讨论(主 agent)· §2 批次任务与设计(eng-designer)· §3 设计评审(评审子代理)· §4 用户批准(主 agent)· §5 实施记录(eng-coder)· §6 验证与收口(父代理)。",
|
|
160
186
|
`> 编制:主 agent · ${date} · 来源 = ${source}。`,
|
|
161
|
-
`> 台账 =
|
|
187
|
+
`> 台账 = ${ledger ?? "#<编号>"}(${board ?? "<板块>"} · 归批)。前情 = ${prev}。`,
|
|
162
188
|
"## §1 讨论(主 agent)",
|
|
163
189
|
`**状态行**:🔄 ${STATUS_WORDS[1].open}(…)`,
|
|
164
190
|
"<§1 模板占位:本批条目 / 关键判据 / 授权口径>",
|
package/agent-tools/batch.mjs
CHANGED
|
@@ -40,9 +40,11 @@ export { SEGMENT_BY_ROLE, batchDocBases, resolveBatchDocPath }
|
|
|
40
40
|
export const MAX_TEXT_CHARS = 20000
|
|
41
41
|
/** 工具写入的轮次节标题形态(N 计数口径 = 该形态行;`### 轮次与发现(…)` 骨架行不匹配)。 */
|
|
42
42
|
const ROUND_HEADING_RE = /^### 轮次 \d+(评审子代理)/
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
/** 凭证形态(`BATCH-RECORD.md` §4.1——三形):`[DESIGN-TOKEN:…]` 闭括号形 ∥ `designId: …` 键形 ∥ 无标两段
|
|
44
|
+
* token 值形 `⟨uuid⟩:⟨epoch⟩`(与单源 `design-token.mjs` 判据对表);裸 uuid 非凭证不剥(KD-7)。两字面勿
|
|
45
|
+
* 合并:`CRED_RE` 走 `.replace()` 须 `/g` ∥ `CRED_TEST_RE` 逐行 `.test()` 须非 `/g`(lastIndex 有状态会漏剥)。 */
|
|
46
|
+
const CRED_RE = /\[DESIGN-TOKEN:[^\]]*\]|designId\s*:\s*\S+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:\d{10,16}/g
|
|
47
|
+
const CRED_TEST_RE = /\[DESIGN-TOKEN:[^\]]*\]|designId\s*:\s*\S+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:\d{10,16}/
|
|
46
48
|
|
|
47
49
|
// ─── #84 记账面注入缝(「记账面按端注入」,形态参 §2.13.5 注入缝)───────────────
|
|
48
50
|
/**
|
|
@@ -209,9 +211,9 @@ function appendBatchRecord({ args, ctx, review, batchDoc, onWritten }) {
|
|
|
209
211
|
if (!body.trim()) {
|
|
210
212
|
throw new Error("batch_segment: nothing to append — the text is empty after credential stripping (credential values never reach the record).")
|
|
211
213
|
}
|
|
212
|
-
// F11-C(挂点:gate / text 校验后 · insertIntoSection
|
|
213
|
-
//
|
|
214
|
-
const residues = findPlaceholderResidue(src
|
|
214
|
+
// F11-C(挂点:gate / text 校验后 · insertIntoSection 前):档头结构行死占位残留 ⇒ 拒
|
|
215
|
+
// (段体引用豁免——判据可辨性收正,`TOOLS.md` §6.20;骨架枚举单源 = skeleton 档 TEMPLATE_PLACEHOLDERS)。
|
|
216
|
+
const residues = findPlaceholderResidue(src)
|
|
215
217
|
if (residues.length) throw new Error(placeholderResidueError(residues))
|
|
216
218
|
const { written, roundN } = insertIntoSection(src, n, body)
|
|
217
219
|
writeFileSync(abs, written)
|
|
@@ -266,6 +268,14 @@ export function batchTool(batchDoc = null, { review = false } = {}) {
|
|
|
266
268
|
type: "string",
|
|
267
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.",
|
|
268
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
|
+
},
|
|
269
279
|
date: {
|
|
270
280
|
type: "string",
|
|
271
281
|
description: "create only — the record date (YYYY-MM-DD); defaults to today.",
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 沿革:初由 agent-tools/advisor.mjs 迁至 advisor-async.mjs(sync wrapper 与 async
|
|
7
7
|
* settle 共享一套实现——无 wrapper↔runner 环),再由 advisor-async.mjs 迁入本文件。
|
|
8
|
-
* 既有 import 面不变:advisor-async.mjs re-export
|
|
9
|
-
* 的 import 路径原样保留——advisor.mjs 再转发 validateDesignToken 给 spawn
|
|
8
|
+
* 既有 import 面不变:advisor-async.mjs re-export 原 7 个导出(advisor.mjs 与测试
|
|
9
|
+
* 的 import 路径原样保留——advisor.mjs 再转发 validateDesignToken 给 spawn 门禁);
|
|
10
|
+
* 本批 §5.1 新助手(stripDesignTokenEcho / makeDesignTokenPrefixRegex)为新增直连面
|
|
11
|
+
* (设计档 ENG-TOKEN-BINDING.md §5.1——不经 advisor-async re-export)。
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { randomUUID } from "node:crypto"
|
|
@@ -64,15 +66,59 @@ export function makeDesignTokenRegex(token, flags = "") {
|
|
|
64
66
|
)
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
/** Build a [DESIGN-TOKEN:...] regex matching the token's uuid PREFIX form — the
|
|
70
|
+
* truncated echo (tail missing/inexact, §5.1③; same boundary discipline as the full regex). */
|
|
71
|
+
export function makeDesignTokenPrefixRegex(token, flags = "") {
|
|
72
|
+
const uuid = String(token).split(":")[0]
|
|
73
|
+
const escaped = uuid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
74
|
+
return new RegExp(
|
|
75
|
+
`(?:^|\\s|\`|\\*)\\[DESIGN-TOKEN:\\s*${escaped}[^\\]\\s]*\\s*\\](?:\\s|$|\`|\\*)`,
|
|
76
|
+
flags + "ms"
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** §5.1③ stripping single source: strip every echo form of THIS token — the full
|
|
81
|
+
* [DESIGN-TOKEN:uuid:expiresAt] form, the truncated prefix form, and the bare
|
|
82
|
+
* uuid value (token-unique random ⇒ zero collateral; empty uuid → bare pass
|
|
83
|
+
* skipped — defensive). Other tokens / other uuids pass through untouched. */
|
|
84
|
+
export function stripDesignTokenEcho(text, designToken) {
|
|
85
|
+
if (typeof text !== "string" || !designToken) return text
|
|
86
|
+
const uuid = String(designToken).split(":")[0]
|
|
87
|
+
let out = text.replace(makeDesignTokenRegex(designToken, "g"), "")
|
|
88
|
+
out = out.replace(makeDesignTokenPrefixRegex(designToken, "g"), "")
|
|
89
|
+
if (uuid) out = out.split(uuid).join("")
|
|
90
|
+
return out
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** §5.1② constant settlement markers (verbatim — machine-greppable; M1 is
|
|
94
|
+
* emitted even when the review body is empty). */
|
|
95
|
+
const NO_VALID_ECHO_MARK = "未回显有效 token——未签发 (no valid token echo — no design token was issued)"
|
|
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
|
+
|
|
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
|
+
|
|
67
111
|
/**
|
|
68
112
|
* Shared design-review settlement (sync wrapper + async settle): the token echo
|
|
69
|
-
* IS the verdict — the advisor echoes it only on approval. On echo
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* the clean output with the Approved
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
113
|
+
* IS the verdict — the advisor echoes it only on approval. On echo (full string
|
|
114
|
+
* OR truncated — uuid present; §5.1①): slot the token under designId (always the
|
|
115
|
+
* FULL token; + eng-coder gate flag; single-value mirror retired per
|
|
116
|
+
* DESIGN-TOKEN-SETTLEMENT D3) and return the clean output with the Approved
|
|
117
|
+
* suffix (truncated echo: the M2 marker sits before the suffix); the review
|
|
118
|
+
* instance CLOSES (a later review of the same doc-set starts a fresh full
|
|
119
|
+
* review). On non-echo: strip every echo form (single source §5.1③) and return
|
|
120
|
+
* the findings text + the constant M1 marker — slots stay untouched
|
|
121
|
+
* (方案 ②: a failed re-review revokes nothing).
|
|
76
122
|
* @param {Object} [opts] — 第 11 批(A/F11):`opts.incomplete` = 宿主尾族判定 kind
|
|
77
123
|
* (单谓词 `advisorIncompleteMarker`——结算方透传)。非空 ⇒ **一律不签发**(无论评审文本
|
|
78
124
|
* 是否回显 token):剥除全部 token 回显 + 追加未签发提示(原因 + 恢复指引),不写槽、
|
|
@@ -86,16 +132,27 @@ export function settleDesignReview(agent, run, designToken, rawResult, opts = {}
|
|
|
86
132
|
// A(F11/ADVISOR-GUARDS.md §1 消费点 1):未完成即不签发——判据与 code 完成守卫同源(单谓词)。
|
|
87
133
|
const incomplete = opts?.incomplete ?? null
|
|
88
134
|
if (incomplete) {
|
|
89
|
-
const stripped = rawResult
|
|
135
|
+
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
90
136
|
return {
|
|
91
137
|
passed: false,
|
|
92
138
|
output: `${stripped}\n\n评审未完成——token 未签发 (review incomplete — no design token issued; reason: ${incomplete})\n以更小范围重跑设计评审(逐档 / 逐节拆分,或拆到两次评审),或调大 agent.advisor.timeoutMs 后重试;补充检查未完成的部分不得按已核处理。`.trim(),
|
|
93
139
|
}
|
|
94
140
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
141
|
+
// §5.1① 截断容忍:全串匹配 ∨ 截断形(本 token 的 uuid 值出现——空 uuid 不构成「值」)
|
|
142
|
+
// ⇒ 认 pass;两者皆无 ⇒ fail + 恒定 M1(含正文为空——N7 零静默)。
|
|
143
|
+
const uuid = String(designToken).split(":")[0]
|
|
144
|
+
const fullEcho = makeDesignTokenRegex(designToken).test(rawResult)
|
|
145
|
+
const truncatedEcho = !fullEcho && uuid.length > 0 && rawResult.includes(uuid)
|
|
146
|
+
if (!fullEcho && !truncatedEcho) {
|
|
147
|
+
const stripped = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
148
|
+
return { passed: false, output: [stripped, NO_VALID_ECHO_MARK].filter(Boolean).join("\n\n") }
|
|
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") }
|
|
99
156
|
}
|
|
100
157
|
// Echoed the token → review passed. Issue it to the parent for eng-coder.
|
|
101
158
|
agent._engDesignTokens ??= new Map()
|
|
@@ -106,12 +163,15 @@ export function settleDesignReview(agent, run, designToken, rawResult, opts = {}
|
|
|
106
163
|
// see the sync wrapper's note: unreachable today, kept for parity).
|
|
107
164
|
if (agent._role === "eng-coder") agent._engDesignReviewed = true
|
|
108
165
|
run.open = false // approval closes this doc-set instance — next review is fresh
|
|
109
|
-
|
|
166
|
+
run.priorOutput = null // #863 关闭点轻量化:审批关同拍释放(closed 实例零消费面——续跑仅取 open)
|
|
167
|
+
const clean = stripDesignTokenEcho(rawResult, designToken).trim()
|
|
110
168
|
// F2c: id echo + omission guidance + point-in-time slot snapshot — the
|
|
111
169
|
// same suffix the F2e prior stores strip with (stored for the exact truncation).
|
|
112
170
|
run.approvedSuffix = buildApprovedSuffix(designToken, run.designId, agent._engDesignTokens.size)
|
|
113
171
|
return {
|
|
114
172
|
passed: true,
|
|
115
|
-
|
|
173
|
+
// §5.1② 标记位置不变式:M2 恒居 approvedSuffix 之前、suffix 恒居文末
|
|
174
|
+
// (stripApprovedSuffix 的精确截断依赖此形——尾部追加改动不得破坏)。
|
|
175
|
+
output: [clean, truncatedEcho ? TRUNCATED_ECHO_MARK : null, run.approvedSuffix].filter(Boolean).join("\n\n"),
|
|
116
176
|
}
|
|
117
177
|
}
|
|
@@ -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
|
|
|
@@ -139,7 +141,12 @@ export function runningPoolCount(parent, pool) {
|
|
|
139
141
|
* API keys come from config.json only (env vars are not a key source).
|
|
140
142
|
*/
|
|
141
143
|
export function resolveChildProvider(parent, modelArg) {
|
|
142
|
-
if (
|
|
144
|
+
if (modelArg == null || modelArg === "") return { ...parent.provider } // null ∕ undefined ∕ 空串 = 不覆盖(既有等价语义)
|
|
145
|
+
// #861(AGENT-LOOP-SUBAGENT.md §6.7.1 ②)运行期防御——四消费链汇点(config ∥ tool model 参数 ∥
|
|
146
|
+
// consult ∥ vision-reader):其余非字符串(对象 ∕ 数组 ∕ 数字 ∕ 布尔)明确报错(不裸 TypeError——可自纠)。
|
|
147
|
+
if (typeof modelArg !== "string") {
|
|
148
|
+
throw new Error(`subagent model override must be a string ("provider:model" | provider | model); got ${Array.isArray(modelArg) ? "array" : typeof modelArg}`)
|
|
149
|
+
}
|
|
143
150
|
// "default" alias (2026-09-05 user ruling — ARCHITECTURE.md 子 agent 模型指定):
|
|
144
151
|
// the literal "default", matched case-insensitively, declares "no override →
|
|
145
152
|
// inherit the default model" — equivalent to null/omission (parent provider
|
|
@@ -389,10 +396,15 @@ export async function injectAsyncResult(agent, entry) {
|
|
|
389
396
|
role: "user",
|
|
390
397
|
content: label,
|
|
391
398
|
})
|
|
399
|
+
// #949:评审族投递水印落点(响应表取件下界 = 投递刻下标;非评审族零写)。
|
|
400
|
+
if (entry.role === "advisor") noteReviewDelivered(agent, entry.run)
|
|
392
401
|
// §6.9 D-SD5 终态墓碑:本函数是全部自动注入路径的共享形态(回合尾 collect + 挂起
|
|
393
|
-
// digest
|
|
394
|
-
//
|
|
395
|
-
//
|
|
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——依赖取消/失败分支照旧,不误标成功)。
|
|
396
408
|
writeTombstone(agent, entry.id, entry.error != null ? "failed" : "consumed", entry.role)
|
|
397
409
|
}
|
|
398
410
|
|