@thincoder/core 0.10.3 → 0.10.5
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 -0
- package/README.md +1 -1
- package/advisor/citations.mjs +29 -3
- package/advisor/compaction.mjs +31 -7
- package/advisor/history.mjs +8 -6
- package/advisor/loop.mjs +29 -27
- package/advisor/messages.mjs +2 -1
- package/advisor/run.mjs +9 -5
- package/advisor/timeline.mjs +26 -0
- package/advisor.mjs +4 -2
- package/agent/assemble.mjs +1 -1
- package/agent/chat-call.mjs +1 -1
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +3 -1
- package/agent/digest-account.mjs +189 -0
- package/agent/dispatch-gates.mjs +8 -0
- package/agent/dispatch.mjs +39 -18
- package/agent/family-tools.mjs +27 -16
- package/agent/helpers.mjs +12 -3
- package/agent/light-round.mjs +66 -0
- package/agent/post-turn.mjs +8 -4
- package/agent/record-results.mjs +4 -0
- package/agent/run-stages.mjs +32 -7
- package/agent/run-start.mjs +50 -3
- package/agent/setup-reminders.mjs +1 -1
- package/agent/setup.mjs +18 -3
- package/agent/subagent-event.mjs +1 -1
- package/agent/suspension.mjs +37 -12
- package/agent/timers.mjs +56 -5
- package/agent/turn-loop.mjs +30 -5
- package/agent/write-gate.mjs +20 -32
- package/agent-tools/advisor-async.mjs +15 -7
- package/agent-tools/advisor-settle.mjs +2 -2
- package/agent-tools/advisor.mjs +34 -33
- package/agent-tools/async-discard.mjs +34 -0
- package/agent-tools/async-settle.mjs +32 -7
- package/agent-tools/audit-block.mjs +1 -1
- package/agent-tools/bash-async.mjs +1 -1
- package/agent-tools/batch-lifecycle.mjs +59 -17
- package/agent-tools/batch-paths.mjs +24 -4
- package/agent-tools/batch-skeleton.mjs +59 -43
- package/agent-tools/batch.mjs +39 -18
- package/agent-tools/browser-async.mjs +198 -0
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/design-slots.mjs +232 -0
- package/agent-tools/design-token.mjs +20 -0
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/escalate-async.mjs +4 -2
- package/agent-tools/manifest.mjs +193 -0
- package/agent-tools/parent-channel.mjs +7 -4
- package/agent-tools/review-facts.mjs +143 -16
- package/agent-tools/settings.mjs +1 -1
- package/agent-tools/skill.mjs +1 -1
- package/agent-tools/subagent-actions-query.mjs +13 -6
- package/agent-tools/subagent-async.mjs +31 -15
- package/agent-tools/subagent-panel.mjs +2 -1
- package/agent-tools/subagent-scheduler.mjs +8 -5
- package/agent-tools/subagent-spawn.mjs +30 -56
- package/agent-tools/subagent.mjs +21 -8
- package/agent-tools/timer.mjs +186 -12
- package/agent-tools/zero-write-watch.mjs +77 -0
- package/agent-tools.mjs +2 -1
- package/agent.mjs +2 -2
- package/attachments.mjs +1 -1
- package/config-io.mjs +18 -13
- package/config-migrate.mjs +13 -29
- package/config-presets.mjs +33 -29
- package/config.mjs +68 -25
- package/context-degrade.mjs +1 -1
- package/context-echo.mjs +1 -1
- package/context-push.mjs +2 -2
- package/context.mjs +1 -1
- package/conventions.mjs +28 -8
- package/declaration.mjs +18 -2
- package/escape.mjs +1 -1
- package/file-links.mjs +1 -1
- package/file-refs.mjs +1 -1
- package/history-window.mjs +1 -1
- package/i18n.mjs +7 -0
- package/ledger-cmd.mjs +62 -16
- package/ledger-db.mjs +6 -0
- package/ledger-surface.mjs +49 -5
- package/ledger-tools.mjs +158 -87
- package/ledger-variant-notice.mjs +47 -0
- package/ledger.mjs +11 -18
- package/log.mjs +1 -1
- package/manifest-discovery.mjs +1 -1
- package/manifest-schema.mjs +7 -5
- package/manifest.mjs +5 -3
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +1 -1
- package/memory/code-index.mjs +1 -1
- package/memory/code-search.mjs +1 -1
- package/memory/code-sync.mjs +1 -1
- package/memory/docs.mjs +6 -2
- package/memory/file-list.mjs +1 -1
- package/memory/file-walk.mjs +17 -5
- package/memory/maintain.mjs +410 -0
- package/memory/memory-tool.mjs +21 -6
- package/memory/schema.mjs +19 -7
- package/memory/sweep.mjs +8 -5
- package/memory/sync-tail.mjs +16 -10
- package/model-ref.mjs +16 -13
- package/model-specs.mjs +21 -9
- package/package.json +1 -1
- package/peer-instances.mjs +1 -1
- package/permission.mjs +33 -0
- package/process-probe.mjs +2 -2
- package/prompts/advisor-design.md +4 -2
- package/prompts/advisor-round1.md +1 -1
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +38 -4
- package/prompts/consult-base.md +1 -1
- package/prompts/discipline-engineering.md +32 -15
- package/prompts/discipline-normal.md +46 -2
- package/prompts/persona-eng-designer.md +6 -6
- package/prompts/persona-engineering.md +18 -9
- package/prompts/persona-normal.md +2 -0
- package/prompts/subagent-base.md +3 -0
- package/provider/anthropic.mjs +14 -2
- package/provider/core.mjs +6 -0
- package/provider/errors.mjs +28 -3
- package/provider/google.mjs +22 -10
- package/provider/list-models.mjs +3 -2
- package/provider/responses-request.mjs +3 -1
- package/provider/responses.mjs +38 -4
- package/provider/sse.mjs +3 -0
- package/provider-flows.mjs +14 -15
- package/proxy-chunked.mjs +102 -0
- package/proxy-target.mjs +54 -0
- package/proxy-transport.mjs +298 -0
- package/proxy.mjs +17 -248
- package/session-gc.mjs +2 -2
- package/session-lifecycle.mjs +18 -14
- package/session-slot-claims.mjs +2 -2
- package/session-slots-manifest.mjs +1 -1
- package/session-slots.mjs +3 -3
- package/shell-identity.mjs +107 -0
- package/team-ledger.mjs +282 -0
- package/team.mjs +223 -0
- package/tool-docs/advisor.md +1 -1
- package/tool-docs/bash.md +2 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/browser.md +48 -0
- package/tool-docs/delete.md +7 -5
- package/tool-docs/ledger.md +6 -0
- package/tool-docs/manifest.md +16 -0
- package/tool-docs/memory.md +1 -0
- package/tool-docs/notify_parent.md +5 -0
- package/tool-docs/process.md +4 -4
- package/tool-docs/question.md +1 -0
- package/tool-docs/subagent.md +14 -3
- package/tool-docs/team_ledger.md +9 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/wait_for.md +1 -0
- package/tools/bash.mjs +9 -4
- package/tools/browser.mjs +161 -0
- package/tools/git.mjs +1 -1
- package/tools/index.mjs +4 -4
- package/tools/ops.mjs +26 -10
- package/tools/patch.mjs +8 -2
- package/tools/repomap.mjs +1 -1
- package/tools/write-path.mjs +8 -6
- package/traces/trace-cleanup.mjs +1 -1
- package/traces/trace-store.mjs +1 -1
- package/vision-reader.mjs +10 -16
- 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
|
@@ -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
|
+
}
|
package/agent-tools/settings.mjs
CHANGED
|
@@ -44,7 +44,7 @@ const _NULL_LEAF_SHAPES = {
|
|
|
44
44
|
"memory.team": { kind: "team", expects: "object { repo: string, name?, dir? }" },
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/** 同族/跨端形状表(**存在性断言**——不参与完备性锁的集合相等)。W3 同族:`agent.subagentModels`
|
|
48
48
|
* 默认 `{}` ⇒ 派生表零条目 ⇒ 零约束(字符串被静默忽略——回落 subagentModel)。 */
|
|
49
49
|
const _SIBLING_SHAPES = {
|
|
50
50
|
"agent.subagentModels": { kind: "roleMap", expects: "object of role→non-empty string" },
|
package/agent-tools/skill.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { loadSkills, readSkill } from "../skills.mjs"
|
|
|
2
2
|
import { escapeXml } from "../agent.mjs"
|
|
3
3
|
import { DESC } from "../tools/shared.mjs"
|
|
4
4
|
|
|
5
|
-
// ─── skill loader 注入缝(#88——「同步 loader
|
|
5
|
+
// ─── skill loader 注入缝(#88——「同步 loader 面按核内结构归一」的端侧覆盖范围)──────────
|
|
6
6
|
/**
|
|
7
7
|
* skill loader 注入位(**缺省不覆盖** = 核内异步 loader——CLI 语义,零行为变;端装配层
|
|
8
8
|
* 可覆盖为端形态(如核内同步面 `loadSkillsSync` / `readSkillSync` 的绑定)。核内零端名分支
|
|
@@ -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,8 +34,12 @@ 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"
|
|
41
|
+
// 批 browser-async-fix:后台浏览器动作注入器(同族——role="browser" 分发到 browser-async.mjs)。
|
|
42
|
+
import { injectBrowserResult } from "./browser-async.mjs"
|
|
39
43
|
|
|
40
44
|
// agent-tools 共享:并行子代理的审批/继续弹窗经 owner 上命名 promise 链串行——
|
|
41
45
|
// 永不叠弹窗(返回链供调用方 .then 续接)。
|
|
@@ -129,13 +133,15 @@ export function runningPoolCount(parent, pool) {
|
|
|
129
133
|
/**
|
|
130
134
|
* Resolve the sub-agent's provider from a model override string (shared with the
|
|
131
135
|
* VS Code port). Forms accepted:
|
|
132
|
-
* "provider:model" → the named provider with the named model
|
|
133
|
-
*
|
|
136
|
+
* "provider:model" → the named provider with the named model (first-colon split —
|
|
137
|
+
* "a:b:c" keeps model "b:c"; multi-colon model names intact)
|
|
134
138
|
* "model" → same provider as the parent, different model
|
|
135
139
|
* "default" → same as null: the parent's provider unchanged (explicit
|
|
136
140
|
* "use the default model" — 2026-09-05 user ruling; matched
|
|
137
141
|
* case-insensitively — parser-layer alias guard)
|
|
138
142
|
* null → parent's provider unchanged.
|
|
143
|
+
* Rejected: bare provider names (2026-10-09 清除批——渠道无默认模型,无渠道级 model 可派生;
|
|
144
|
+
* 可读错误「渠道无默认模型,请用 provider:model」) ∥ "provider:" 空模型段。
|
|
139
145
|
* API keys come from config.json only (env vars are not a key source).
|
|
140
146
|
*/
|
|
141
147
|
export function resolveChildProvider(parent, modelArg) {
|
|
@@ -143,29 +149,32 @@ export function resolveChildProvider(parent, modelArg) {
|
|
|
143
149
|
// #861(AGENT-LOOP-SUBAGENT.md §6.7.1 ②)运行期防御——四消费链汇点(config ∥ tool model 参数 ∥
|
|
144
150
|
// consult ∥ vision-reader):其余非字符串(对象 ∕ 数组 ∕ 数字 ∕ 布尔)明确报错(不裸 TypeError——可自纠)。
|
|
145
151
|
if (typeof modelArg !== "string") {
|
|
146
|
-
throw new Error(`subagent model override must be a string ("provider:model" |
|
|
152
|
+
throw new Error(`subagent model override must be a string ("provider:model" | model); got ${Array.isArray(modelArg) ? "array" : typeof modelArg}`)
|
|
147
153
|
}
|
|
148
154
|
// "default" alias (2026-09-05 user ruling — ARCHITECTURE.md 子 agent 模型指定):
|
|
149
155
|
// the literal "default", matched case-insensitively, declares "no override →
|
|
150
156
|
// inherit the default model" — equivalent to null/omission (parent provider
|
|
151
157
|
// unchanged). Checked BEFORE the provider-name lookup so the alias is reserved
|
|
152
158
|
// (≡ omission — providers are not consulted) and never falls through to the
|
|
153
|
-
//
|
|
154
|
-
// semantics below.
|
|
159
|
+
// resolution branches below. Any other value runs the resolution below.
|
|
155
160
|
if (String(modelArg).toLowerCase() === "default") return { ...parent.provider }
|
|
156
161
|
const providers = parent.config?.providersList ?? []
|
|
157
162
|
const withKey = (p) => (p.apiKey?.trim() ? { ...p, apiKey: p.apiKey.trim() } : { ...p })
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
// 首冒号分割(2026-10-09 清除批):多冒号模型名不截断——`a:b:c` ⇒ provider `a` ∥ model `b:c`。
|
|
164
|
+
const sep = modelArg.indexOf(":")
|
|
165
|
+
if (sep >= 0) {
|
|
166
|
+
const pname = modelArg.slice(0, sep)
|
|
167
|
+
const mname = modelArg.slice(sep + 1)
|
|
160
168
|
const p = providers.find((x) => x.name === pname)
|
|
161
169
|
if (!p) throw new Error(`subagent model: unknown provider "${pname}" (available: ${providers.map((x) => x.name).join(", ") || "none"})`)
|
|
162
|
-
|
|
170
|
+
// 空 mname 一律拒(2026-10-09 清除批):渠道不携模型,无渠道级 model 可回落。
|
|
171
|
+
if (!mname) throw new Error(`subagent model: "${modelArg}" — model part is empty (expected provider:model)`)
|
|
172
|
+
return { ...withKey(p), model: mname }
|
|
163
173
|
}
|
|
164
174
|
const byName = providers.find((x) => x.name === modelArg)
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
if (byName) return { ...withKey(byName), model: byName.model ?? parent.provider?.model }
|
|
175
|
+
// 2026-10-09 清除批(PROVIDER.md §6.16 M3):渠道单值模型退场——裸渠名无从派生 model,
|
|
176
|
+
// 一律拒(不回落任何渠道级 model)。
|
|
177
|
+
if (byName) throw new Error("渠道无默认模型,请用 provider:model")
|
|
169
178
|
return { ...parent.provider, model: modelArg }
|
|
170
179
|
}
|
|
171
180
|
|
|
@@ -366,6 +375,8 @@ export async function injectAsyncResult(agent, entry) {
|
|
|
366
375
|
// #9:后台 bash 任务族(role="bg")单列——专用注入器(状态行 + 尾截 + 全量在 log);
|
|
367
376
|
// 本函为 digest 注入单点族成员,消费面(run-start pending / 端壳残余注入)零改。
|
|
368
377
|
if (entry.role === "bg") return injectBgResult(agent, entry)
|
|
378
|
+
// 批 browser-async-fix:后台浏览器动作族(role="browser")同式单列(摘要文法 = ack 族同源)。
|
|
379
|
+
if (entry.role === "browser") return injectBrowserResult(agent, entry)
|
|
369
380
|
const body = entry.error ?? entry.report ?? "(no report)"
|
|
370
381
|
// F-2(BATCH-3-STRUCTURE)+ AGENT-LOOP.md §6.14 D-DG2(群 B 批 B5):注入前查轮累计(单源)——超限条目不
|
|
371
382
|
// inline 全文,改清单行(全文经 persistOverflowReport 落盘——path 随行)。首条豁免
|
|
@@ -394,10 +405,15 @@ export async function injectAsyncResult(agent, entry) {
|
|
|
394
405
|
role: "user",
|
|
395
406
|
content: label,
|
|
396
407
|
})
|
|
408
|
+
// #949:评审族投递水印落点(响应表取件下界 = 投递刻下标;非评审族零写)。
|
|
409
|
+
if (entry.role === "advisor") noteReviewDelivered(agent, entry.run)
|
|
397
410
|
// §6.9 D-SD5 终态墓碑:本函数是全部自动注入路径的共享形态(回合尾 collect + 挂起
|
|
398
|
-
// digest
|
|
399
|
-
//
|
|
400
|
-
//
|
|
411
|
+
// digest 首行注入)——注入即消费(墓碑落注入时点——依赖满足判据不随销账后移)——容器侧:
|
|
412
|
+
// 会话态(`_daDelivered`)注入后**不随即从容器移除**(留容器至销账 / 升级;移除 =
|
|
413
|
+
// `accountDigestRound` 的 splice,§6.31),fallback 面照旧(缺省条取尽离容器——先离容后注入;
|
|
414
|
+
// `_daDelivered` 条同判据跳过且留容器)——
|
|
415
|
+
// dependsOn 引用该 id 的后续 spawn 视为已满足(T-SD14 消费终态语义——AGENT-LOOP-SUBAGENT.md §6.7.5 后 check
|
|
416
|
+
// 消费路径删除,本自动通道为唯一消费方;error 条目记 failed——依赖取消/失败分支照旧,不误标成功)。
|
|
401
417
|
writeTombstone(agent, entry.id, entry.error != null ? "failed" : "consumed", entry.role)
|
|
402
418
|
}
|
|
403
419
|
|
|
@@ -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 ⇒ 未消化)——与门控同源。
|
|
@@ -397,14 +397,15 @@ export function maybeRefillAsync(parent) {
|
|
|
397
397
|
* 域 = 进程内(不做槽持久化——reload 后池清块消失,新进程从 1 无冲突——设计范围边界)。
|
|
398
398
|
* spawn(subagent-spawn.mjs async 分支)、escalate(escalate-async.mjs)、async-advisor
|
|
399
399
|
* 池(advisor-async.mjs——§6.10 跨池共号)、**后台 bash 任务池**(bash-async.mjs——#9:
|
|
400
|
-
* `_bgTasks` 同入扫描域——VSC 形计数器不随 run 存活,缺此扫描会与在途 bg
|
|
400
|
+
* `_bgTasks` 同入扫描域——VSC 形计数器不随 run 存活,缺此扫描会与在途 bg 条目撞号)、
|
|
401
|
+
* **后台浏览器动作池**(browser-async.mjs——批 browser-async-fix:`_browserTasks` 同入)共用;
|
|
401
402
|
* executeAsyncSpawn 直读 counter(分配与
|
|
402
403
|
* 消费同步——无 await 间隙)。CLI 两池键均为字符串(set(String(id))——解析分支防御保留)。
|
|
403
404
|
* @returns {number} 全池唯一的下一 id(单调——进程内)
|
|
404
405
|
*/
|
|
405
406
|
export function nextSubagentId(parent) {
|
|
406
407
|
let poolMax = 0
|
|
407
|
-
for (const pool of [carrierField(parent, "_asyncSubagents"), carrierField(parent, "_asyncAdvisors"), carrierField(parent, "_bgTasks")]) {
|
|
408
|
+
for (const pool of [carrierField(parent, "_asyncSubagents"), carrierField(parent, "_asyncAdvisors"), carrierField(parent, "_bgTasks"), carrierField(parent, "_browserTasks")]) {
|
|
408
409
|
if (!pool || pool.size === 0) continue
|
|
409
410
|
for (const k of pool.keys()) {
|
|
410
411
|
const n = typeof k === "number" ? k : Number.parseInt(k, 10)
|
|
@@ -426,7 +427,8 @@ export function nextSubagentId(parent) {
|
|
|
426
427
|
* nextSubagentId ⇒ 令牌缺位/错号 ⇒ 抛错——防直读陈旧 counter 的静默覆写),断言
|
|
427
428
|
* 通过即置 `undefined`(一次性——同一令牌跨站点复用第二消费必抛)。
|
|
428
429
|
* 调用点:executeAsyncSpawn(subagent-run.mjs)· launchEscalateAsync(escalate-async.mjs)·
|
|
429
|
-
* launchAsyncAdvisor(advisor-async.mjs
|
|
430
|
+
* launchAsyncAdvisor(advisor-async.mjs)· launchBrowserTask(browser-async.mjs——批 browser-async-fix
|
|
431
|
+
* 第四站点)——site = 站点名(错误文案定位面)。
|
|
430
432
|
*/
|
|
431
433
|
export function consumeSubagentToken(parent, id, site, role) {
|
|
432
434
|
if (parent?._lastSubagentId !== id) {
|
|
@@ -436,9 +438,10 @@ export function consumeSubagentToken(parent, id, site, role) {
|
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
/**
|
|
439
|
-
* ED-5(§6.21
|
|
441
|
+
* ED-5(§6.21)入池键守卫——四入池点同族:`set(String(id))` 前断言键不存在;命中 =
|
|
440
442
|
* 覆写(静默丢报告 + status/cancel 错址)⇒ 抛错。调用点:子代理池(subagent-run.mjs)·
|
|
441
|
-
* escalate(escalate-async.mjs)· advisor 池(advisor-async.mjs
|
|
443
|
+
* escalate(escalate-async.mjs)· advisor 池(advisor-async.mjs)· browser 池(browser-async.mjs——
|
|
444
|
+
* 批 browser-async-fix 同族实例)。
|
|
442
445
|
*/
|
|
443
446
|
export function assertPoolKeyFree(pool, id, role) {
|
|
444
447
|
if (pool.has(String(id))) {
|
|
@@ -20,7 +20,7 @@ import { allocRelay, wrapChildCallbacks, relayPrefixOf } from "../agent/spawn-ch
|
|
|
20
20
|
// TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15):子代理人读线窗口常量单源(store 零依赖)。
|
|
21
21
|
import { RECORD_WINDOW_MESSAGES } from "../session-store.mjs"
|
|
22
22
|
import { validateDesignToken } from "./advisor.mjs"
|
|
23
|
-
import { tokenExpired, removeDesignTokenSlot, reconcileEngTokensFromSlot
|
|
23
|
+
import { tokenExpired, removeDesignTokenSlot, reconcileEngTokensFromSlot } from "../token-ttl.mjs"
|
|
24
24
|
import { resolveChildProvider, buildChildRunOpts, enqueueAsk } from "./subagent-async.mjs"
|
|
25
25
|
import { nextSubagentId } from "./subagent-scheduler.mjs"
|
|
26
26
|
import {
|
|
@@ -50,7 +50,7 @@ function logBatchDocRefs(parent, role, task, boundAbs) {
|
|
|
50
50
|
const cwd = parent?.cwd ?? process.cwd()
|
|
51
51
|
const text = typeof task === "string" ? task : ""
|
|
52
52
|
const key = (p) => (process.platform === "win32" ? p.toLowerCase() : p)
|
|
53
|
-
const bases = batchDocBases(cwd) // 歧义锚 ⇒ 候选项目基底并集(#828
|
|
53
|
+
const bases = batchDocBases(cwd) // 歧义锚 ⇒ 候选项目基底并集(#828——入基底判据覆盖范围随之对齐)
|
|
54
54
|
const seen = new Set()
|
|
55
55
|
for (const m of text.matchAll(MD_PATH_TOKEN)) {
|
|
56
56
|
const token = m[0]
|
|
@@ -125,54 +125,14 @@ export function resolveDesignSlot(parent, designIdArg) {
|
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* consume-design 动作执行器(2026-09-07 token 链终消费制——ENGINEERING-MODE.md §2.6 F1):
|
|
128
|
-
* 父侧验收核销时显式调用——读槽值 → removeDesignTokenSlot
|
|
129
|
-
* designId
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* 落盘删除(旧台账不留盘——防 D2 门禁 miss 回读复活已消费 token);落盘失败回滚
|
|
135
|
-
* 内存槽 + 抛错(不留半消费态,可重试——D1 评审 #1 同款失败语义)。
|
|
136
|
-
* dispatch 分类(评审 #7d):非只读控制动作——depth-0 + 工程模式
|
|
137
|
-
* 限定(受限变体门在 subagent.mjs 分流处;本器自持工程模式门)——planMode 拒绝
|
|
138
|
-
* (dispatch 不豁免)——不入批审批分组(dispatch 免审直行——无文件写)。
|
|
128
|
+
* 父侧验收核销时显式调用——读槽值 → removeDesignTokenSlot(移除该 designId 槽)→ 消费后同
|
|
129
|
+
* designId 再 spawn = resolveDesignSlot not found 机械拒;落盘失败回滚内存槽 + 抛错
|
|
130
|
+
* (不留半消费态,可重试——D1 评审 #1 同款失败语义)。
|
|
131
|
+
* 2026-10-07 批 ledger-tool(#927 并入):选择器扩展(designIds / expired / olderThanDays——
|
|
132
|
+
* 恰一,fail-closed)+ 清点面 `design-slots` 落 `./design-slots.mjs`(清点 / 批量选择 / 消费核
|
|
133
|
+
* 单源迁入——单枚形态拒文 / 回执逐字不变);本处仅保本档 import 面(subagent.mjs / 既有消费点零改)。
|
|
139
134
|
*/
|
|
140
|
-
export
|
|
141
|
-
const parent = ctx.agent
|
|
142
|
-
if (!parent?.config?.agent?.engineering) {
|
|
143
|
-
throw new Error("Engineering mode is not active — consume-design applies only to engineering-mode design tokens (spawn 同门).")
|
|
144
|
-
}
|
|
145
|
-
const designId = args?.designId ? String(args.designId) : undefined
|
|
146
|
-
const slots = parent?._engDesignTokens
|
|
147
|
-
const hasSlots = slots instanceof Map && slots.size > 0
|
|
148
|
-
// 多槽缺 designId → 拒(spawn 同款语义——不误消费任一槽)
|
|
149
|
-
if (!designId && hasSlots && slots.size > 1) {
|
|
150
|
-
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.`)
|
|
151
|
-
}
|
|
152
|
-
// 读槽值:给定 designId → 精确槽(未知/已消费 → undefined);缺省 → 唯一槽
|
|
153
|
-
let token = null
|
|
154
|
-
if (designId && hasSlots) token = slots.get(designId) ?? null
|
|
155
|
-
else if (!designId && hasSlots) token = [...slots.values()][0]
|
|
156
|
-
// 未知 designId / 已消费 / 无任何槽 → 幂等 no-op 提示(不报错——评审 #3 定死)
|
|
157
|
-
if (!token) {
|
|
158
|
-
return `consume-design: no live slot${designId ? ` for designId ${designId}` : ""} (already consumed or never issued) — idempotent no-op, nothing changed.`
|
|
159
|
-
}
|
|
160
|
-
// consume 落盘对称(DESIGN-TOKEN-SETTLEMENT D1 段 + AC7——交付 🔴 复活洞修复):
|
|
161
|
-
// 删内存槽后当场同步落盘删除(D1 同款 persistEngTokens——空 Map → 槽文件
|
|
162
|
-
// engDesignTokens 字段删除,旧台账不留盘)。否则消费→回合尾 saveSession 窗口内
|
|
163
|
-
// spawn 门禁 miss 回读(D2)会从盘上复活已消费 token。
|
|
164
|
-
const slotId = designId ?? [...slots.keys()][0]
|
|
165
|
-
removeDesignTokenSlot(parent, designId, token)
|
|
166
|
-
try {
|
|
167
|
-
persistEngTokens(parent)
|
|
168
|
-
} catch (e) {
|
|
169
|
-
// 落盘失败 → 回滚内存槽 + 抛错(不留半消费态——盘上仍有旧台账时消费不得报
|
|
170
|
-
// 成功;可重试——D1 评审 #1 settle 同款失败语义)
|
|
171
|
-
if (slotId) parent._engDesignTokens.set(slotId, token)
|
|
172
|
-
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.`)
|
|
173
|
-
}
|
|
174
|
-
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.`
|
|
175
|
-
}
|
|
135
|
+
export { executeConsumeDesignAction } from "./design-slots.mjs"
|
|
176
136
|
|
|
177
137
|
// ── spawn 调度参数准入(AGENT-LOOP-SUBAGENT.md §6.9 D-SD1/D-SD3)────────────
|
|
178
138
|
// files/dependsOn 声明即契约(v1:不做任务书文本自动解析——不可靠)。缺省(两者皆
|
|
@@ -239,16 +199,27 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
|
|
|
239
199
|
// 路径语义照 `files` 先例:cwd 相对或绝对均可,`\` 归一为 `/`;解析序单源 = §4.15(cwd → 项目根 →
|
|
240
200
|
// 逐基底,读面取首个可读)——cwd 非项目根时根相对串不再必拒(BR-29/BR-33)。错误文案带
|
|
241
201
|
// **实际角色名**(§2.15 越界文案参数化——designer 撞门时不误导)与逐字不可读后缀。
|
|
202
|
+
// §4.16(2026-10-09):coder(普通模式)**可选**携 `batchDoc`——非强制(「spawn 强制 batchDoc」=
|
|
203
|
+
// 工程专属、不移植);传则须可读(同评审面判据)⇒ `child._batchDoc` 绑定 §5 记录通道;
|
|
204
|
+
// 未携 ⇒ 不绑定、照常 spawn。
|
|
242
205
|
let batchDocAbs = null
|
|
243
206
|
if (role === "eng-coder" || role === "eng-designer") {
|
|
244
207
|
const given = typeof args.batchDoc === "string" ? args.batchDoc.trim() : ""
|
|
245
208
|
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)
|
|
209
|
+
`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
210
|
if (!given) throw refusal()
|
|
248
211
|
batchDocAbs = resolveBatchReadPath(parent.cwd ?? process.cwd(), given)
|
|
249
212
|
if (!batchDocAbs) throw refusal(" (given path is not a readable file)")
|
|
250
213
|
// #309 形三:任务书中的他批批次档引用观测留痕(不阻断——放行不受影响)。
|
|
251
214
|
logBatchDocRefs(parent, role, args.task, batchDocAbs)
|
|
215
|
+
} else if (role === "coder") {
|
|
216
|
+
const given = typeof args.batchDoc === "string" ? args.batchDoc.trim() : ""
|
|
217
|
+
if (given) {
|
|
218
|
+
batchDocAbs = resolveBatchReadPath(parent.cwd ?? process.cwd(), given)
|
|
219
|
+
if (!batchDocAbs) {
|
|
220
|
+
throw new Error("batchDoc for role='coder' — 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); when provided it must resolve to a readable file (omit batchDoc to spawn without the §5 record-write binding).")
|
|
221
|
+
}
|
|
222
|
+
}
|
|
252
223
|
}
|
|
253
224
|
|
|
254
225
|
// M5 F2(ENGINEERING-MODE-V2-MODULE-DELEGATION §2.2):任务书六强制字段门——
|
|
@@ -344,10 +315,12 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
|
|
|
344
315
|
// 每个 child 原先各自一份永不压缩的 _fullHistory(勘察 C2 乘数面)。
|
|
345
316
|
child._historyWindow = RECORD_WINDOW_MESSAGES
|
|
346
317
|
|
|
347
|
-
// BATCH-RECORD.md §2 批次档段写入通道绑定(第 4
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
|
|
318
|
+
// BATCH-RECORD.md §2 批次档段写入通道绑定(第 4 批;§4.16 扩 coder 可选分支):工程角色
|
|
319
|
+
// (eng-coder/eng-designer)把批次档绝对路径记在 child 上——setup 挂载 `batch` 时读它;
|
|
320
|
+
// 携绑定的 coder(普通模式,可选)同点绑定。无路径参数的工具靠这条绑定决定目标档
|
|
321
|
+
// (spawn 门已保证「参数在 + 路径可读」)。
|
|
322
|
+
const batchBound = engineeringRole || (role === "coder" && !!batchDocAbs)
|
|
323
|
+
if (batchBound) child._batchDoc = batchDocAbs
|
|
351
324
|
|
|
352
325
|
// Token-verified design review → child is authorized to modify files without re-reviewing
|
|
353
326
|
if (role === "eng-coder") child._engDesignReviewed = true
|
|
@@ -383,8 +356,9 @@ export function buildSpawnChild(parent, ctx, args, role, wantAsync, files, depen
|
|
|
383
356
|
// §2.11 第 1 点(FR16 载体 + FR17 铁律 #5 的机械面):批次档**绝对路径**下发给工程角色
|
|
384
357
|
// (eng-coder 实现 / eng-designer 写稿)——任务文本本身不动(批次档 §2 才是任务书本体;
|
|
385
358
|
// 本行让子代理"拿到本档路径")。行文不含 summarizeEngTaskBook 的三组段 marker
|
|
386
|
-
// (Docs involved / Files list / Acceptance criteria
|
|
387
|
-
|
|
359
|
+
// (Docs involved / Files list / Acceptance criteria)——段匹配不受影响。工程角色恒注入;
|
|
360
|
+
// 携绑定 coder(§4.16)随绑定注入(绑定 coder 要能读到本档)。
|
|
361
|
+
if (batchBound) spawnBlocks.push(`Batch record (batchDoc): ${batchDocAbs}`)
|
|
388
362
|
// AGENT-LOOP-SUBAGENT.md §6.7.6 D-E2 ③ (round4 #4, T-E13/T-E15): an eng-coder audit spawn's task book is
|
|
389
363
|
// the eng-coder's OWN spawn task — mechanically kept as _engTaskInput by the
|
|
390
364
|
// parent spawn and injected as the D-TS5 A2 mechanical summary (design docs /
|