thincoder 0.12.60 → 0.12.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -1
- package/README.md +8 -6
- package/bin/thincoder.mjs +27 -122
- package/package.json +3 -2
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +38 -17
- package/src/acp.mjs +6 -1
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +36 -134
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +124 -329
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +3 -2
- package/src/agent/completion.mjs +1 -1
- package/src/agent/dispatch.mjs +47 -12
- package/src/agent/helpers.mjs +71 -13
- package/src/agent/record-results.mjs +13 -5
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +21 -6
- package/src/agent/setup-reminders.mjs +16 -9
- package/src/agent/setup.mjs +92 -128
- package/src/agent/spawn-child.mjs +26 -9
- package/src/agent-tools/advisor-async.mjs +70 -180
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +69 -20
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +23 -10
- package/src/agent-tools/design-token.mjs +14 -1
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +3 -3
- package/src/agent-tools/escalate-async.mjs +16 -13
- package/src/agent-tools/read-history.mjs +13 -3
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +130 -17
- package/src/agent-tools/subagent-actions.mjs +15 -6
- package/src/agent-tools/subagent-async.mjs +66 -14
- package/src/agent-tools/subagent-panel.mjs +22 -15
- package/src/agent-tools/subagent-run.mjs +9 -6
- package/src/agent-tools/subagent-scheduler.mjs +57 -8
- package/src/agent-tools/subagent-spawn.mjs +63 -16
- package/src/agent-tools/subagent.mjs +175 -49
- package/src/agent-tools/verify.mjs +13 -34
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +38 -21
- package/src/cli/distill-command.mjs +2 -2
- package/src/cli/make-agent.mjs +23 -7
- package/src/cli/memory-command.mjs +2 -2
- package/src/cli/setup-wizard.mjs +29 -9
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +132 -63
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +7 -2
- package/src/expand-home.mjs +16 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +70 -31
- package/src/memory/delete.mjs +2 -0
- package/src/memory/docs.mjs +10 -6
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/schema.mjs +15 -3
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/prompt-overlays.mjs +73 -16
- package/src/prompts/advisor-design.md +18 -8
- package/src/prompts/advisor-round1.md +7 -1
- package/src/prompts/advisor-round2.md +13 -2
- package/src/prompts/advisor-round3.md +13 -2
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +2 -0
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +55 -0
- package/src/prompts/persona-engineering.md +54 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +13 -32
- package/src/provider/errors.mjs +26 -1
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- package/src/session-slots.mjs +7 -2
- package/src/session.mjs +59 -38
- package/src/token-ttl.mjs +2 -1
- package/src/tools/{system.mjs → bash.mjs} +6 -243
- package/src/tools/file.mjs +30 -10
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +15 -34
- package/src/tools/index.mjs +4 -2
- package/src/tools/ops.mjs +20 -7
- package/src/tools/question.md +1 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -1
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/tui/agent-turn.mjs +32 -13
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-config.mjs +108 -37
- package/src/tui/cmd-eng.mjs +11 -27
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-undo.mjs +4 -3
- package/src/tui/index.mjs +38 -37
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +47 -10
- package/src/tui/pickers.mjs +28 -410
- package/src/tui/render-frame.mjs +32 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +5 -19
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/subagent-blocks.mjs +69 -90
- package/src/tui/subagent-children.mjs +50 -64
- package/src/tui/subagent-freeze.mjs +40 -43
- package/src/tui/subagent-panel.mjs +21 -66
- package/src/tui/suspension-drive.mjs +28 -82
- package/src/tui/tool-args.mjs +4 -2
- package/src/tui/tool-display.mjs +4 -3
- package/src/tui/tool-events.mjs +23 -16
- package/src/tui/tui-lifecycle.mjs +18 -5
- package/src/tui/wizard.mjs +52 -18
- package/src/tui/wrapped-spawn.mjs +38 -0
- package/src/prompts/coder.md +0 -13
- package/src/prompts/discipline.md +0 -84
- package/src/prompts/eng-coder.md +0 -19
- package/src/prompts/engineering-sub.md +0 -14
- package/src/prompts/engineering.md +0 -87
- package/src/prompts/explore.md +0 -12
- package/src/prompts/main.md +0 -34
- package/src/prompts/methodology-template.md +0 -38
- package/src/prompts/plan.md +0 -9
- package/src/prompts/system.md +0 -44
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* advisor-async.mjs — async advisor reviews (AGENT-LOOP.md §
|
|
2
|
+
* advisor-async.mjs — async advisor reviews (AGENT-LOOP.md §11.2 — R13, 2026-09-06).
|
|
3
3
|
*
|
|
4
4
|
* The advisor tool runs reviews in a background pool at depth 0 (default async —
|
|
5
5
|
* ruling ②-3 A): the launch returns an ack, the turn ends naturally, the session
|
|
@@ -17,36 +17,47 @@
|
|
|
17
17
|
* launch); code reviews continue the newest OPEN code instance (round 2+ of
|
|
18
18
|
* a fix loop) and CLOSE at normal user-run ends once no review is in flight
|
|
19
19
|
* (a converged/abandoned thread must not burn the cap of later tasks);
|
|
20
|
-
* 3. the async pool (agent._asyncAdvisors — ADVISOR_POOL_LIMIT =
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* 3. the async pool (agent._asyncAdvisors — ADVISOR_POOL_LIMIT = 4 default,
|
|
21
|
+
* agent.poolLimits.advisor configurable — POOL-CONFIG-UNIFIED; over-limit and
|
|
22
|
+
* same-scope-running launches are refused at once — never queued (②-6a; F-5);
|
|
23
|
+
* cancel (ruling ②-6b — directed abort → cancelled settle: no
|
|
23
24
|
* pending entry, no token, a "评审已取消——token 未签发" reminder);
|
|
24
25
|
* 4. SETTLE accounting (fixes #2/#4 — moved from the tool-result commit): a
|
|
25
26
|
* review whose targets mutated after launch settles STALE — it marks no
|
|
26
27
|
* _calledAdvisorThisRun and issues no token (the guard pushes back); a
|
|
27
28
|
* non-stale code review marks _calledAdvisorThisRun; a passing design review
|
|
28
29
|
* issues its token under the designId slot at settle time.
|
|
30
|
+
* 2026-09-11 第 11 批拆分:settle 记账 + 变更日志 + 陈旧/冻结判定迁 `advisor-settle.mjs`
|
|
31
|
+
* (本文件 500 行 = 硬帽在册);既有 import 面经本文件 re-export 保持不变。
|
|
29
32
|
*
|
|
30
33
|
* The sync path (depth>0 eng-coder self-review / explicit async:false) reuses
|
|
31
34
|
* the same instance resolution (rounds/prior/cap) — its accounting still lands
|
|
32
35
|
* in recordToolResults (agent.mjs run loop), marker-keyed by toolCall id.
|
|
33
36
|
*/
|
|
34
37
|
import { randomUUID } from "node:crypto"
|
|
35
|
-
import { join } from "node:path"
|
|
36
|
-
import { persistEngTokens } from "../token-ttl.mjs"
|
|
37
|
-
import { settleDesignReview, makeDesignTokenRegex, stripApprovedSuffix } from "./design-token.mjs"
|
|
38
38
|
// design-token 工具组(2026-09-08 自本文件迁至 design-token.mjs——再越 500 行硬限)——
|
|
39
39
|
// 既有 import 面(advisor.mjs / 测试)不变:原导出全部经此 re-export 保留。
|
|
40
40
|
export {
|
|
41
41
|
buildApprovedSuffix, stripApprovedSuffix, effectiveTokenTtlMs, generateDesignToken,
|
|
42
42
|
validateDesignToken, makeDesignTokenRegex, settleDesignReview,
|
|
43
43
|
} from "./design-token.mjs"
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// 第 11 批拆分(2026-09-11):settle 记账 + 变更日志(noteMutations)+ 陈旧判定
|
|
45
|
+
// (reviewIsStale)+ 冻结冲突(inflightDesignReviewConflict)迁 advisor-settle.mjs——
|
|
46
|
+
// 既有 import 面(dispatch / subagent-async / escalate-async / 测试)经此 re-export 不变。
|
|
47
|
+
import { normAbs, mutationSeqOf, settleAdvisorRun } from "./advisor-settle.mjs"
|
|
48
|
+
export {
|
|
49
|
+
mutationSeqOf, noteMutations, reviewIsStale, settleAdvisorRun, inflightDesignReviewConflict,
|
|
50
|
+
} from "./advisor-settle.mjs"
|
|
51
|
+
// 第 33 批(§17.5):doc-set 键迁 `review-streak.mjs`(护栏与实例续跑同锚单源;原为私有
|
|
52
|
+
// ——零 import 面)。本文件继续在实例解析 / 池 entries 上消费它。
|
|
53
|
+
import { docSetKey } from "./review-streak.mjs"
|
|
54
|
+
import { runAdvisorReview, resolveAdvisorProvider, ADVISOR_THINKING_PLACEHOLDER } from "../advisor/run.mjs"
|
|
46
55
|
import { stripEventToken } from "../agent/spawn-child.mjs"
|
|
47
56
|
import { logEvent } from "../log.mjs"
|
|
57
|
+
import { deathLine } from "../abort-provenance.mjs"
|
|
48
58
|
// ASYNC-RESULT-CONTAINER.md D3/D6:settle 公共收尾单点 + child signal 构建单点
|
|
49
59
|
import { buildChildSignal, settleAsyncEntry } from "./async-settle.mjs"
|
|
60
|
+
import { nextSubagentId } from "./subagent-scheduler.mjs"
|
|
50
61
|
|
|
51
62
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
63
|
// Review-instance registry (agent._advisorRuns — per-review rounds/prior/cap)
|
|
@@ -57,16 +68,6 @@ export function advisorRuns(agent) {
|
|
|
57
68
|
return agent._advisorRuns
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
/** Canonical scope key for design reviews — the document multi-set
|
|
61
|
-
* (order-insensitive, ABS-path normalized — launch 与 continuation 的写法差异
|
|
62
|
-
* ("./docs/x.md" vs "docs/x.md"、反斜杠) 不误建新实例). */
|
|
63
|
-
function docSetKey(documents, cwd) {
|
|
64
|
-
const list = [...new Set((documents ?? [])
|
|
65
|
-
.filter((d) => typeof d === "string" && d.trim())
|
|
66
|
-
.map((d) => normAbs(d, cwd)))]
|
|
67
|
-
return JSON.stringify(list.sort())
|
|
68
|
-
}
|
|
69
|
-
|
|
70
71
|
/** The newest OPEN code instance (the thread a fix-round launch continues), or null. */
|
|
71
72
|
export function openCodeRun(agent) {
|
|
72
73
|
const runs = agent?._advisorRuns
|
|
@@ -88,7 +89,7 @@ function openDesignRun(agent, key) {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
/**
|
|
91
|
-
* Resolve the review instance a launch continues (§
|
|
92
|
+
* Resolve the review instance a launch continues (§11.2 ③ — per-review
|
|
92
93
|
* rounds/prior; reviewId = designId for design reviews, a random id for code).
|
|
93
94
|
* - design: continue the OPEN instance of the same document set (fix rounds of
|
|
94
95
|
* a review thread keep its designId and its round/prior); none → new instance.
|
|
@@ -143,66 +144,45 @@ export function effectiveAdvisorRound(agent) {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
146
|
-
//
|
|
147
|
-
//
|
|
147
|
+
// Async pool (agent._asyncAdvisors — pool 上限 F-1/F-2:常量 = 运行时回退权威(2 → 4),
|
|
148
|
+
// config.mjs DEFAULTS.agent.poolLimits.advisor = config 层镜像——耦合锁 config-pool
|
|
149
|
+
// .test.mjs 逐键断言——勿单侧改默认)
|
|
148
150
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
149
151
|
|
|
150
|
-
export
|
|
151
|
-
return agent._mutationSeq ?? 0
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Record a file-mutation commit — bounded ring feeding the stale scan. paths = ABSOLUTE.
|
|
155
|
-
* 唯一记账点(§29 fix A):dispatch runOne 写执行成功即刻调用(取代批后段 + 中断分支——
|
|
156
|
-
* 不双计);mergeChildMutations(子代理合入)是独立事件面。 */
|
|
157
|
-
export function noteMutations(agent, paths) {
|
|
158
|
-
const list = (paths ?? []).filter((p) => typeof p === "string" && p.length > 0)
|
|
159
|
-
if (list.length === 0) return
|
|
160
|
-
agent._mutationSeq = (agent._mutationSeq ?? 0) + 1
|
|
161
|
-
const log = agent._mutLog ??= []
|
|
162
|
-
log.push({ seq: agent._mutationSeq, paths: [...new Set(list)] })
|
|
163
|
-
if (log.length > 200) log.splice(0, log.length - 200)
|
|
164
|
-
}
|
|
152
|
+
export const ADVISOR_POOL_LIMIT = 4
|
|
165
153
|
|
|
166
|
-
/**
|
|
167
|
-
|
|
168
|
-
|
|
154
|
+
/** §11.2 advisor 池生效上限(纯——读 agent.poolLimits.advisor——合法 ≥1 整数生效——
|
|
155
|
+
* 非法/缺省回退 ADVISOR_POOL_LIMIT——与 subagent 域 resolvePoolLimits 独立不共享
|
|
156
|
+
* (subagent 两键表不含本键——键表语义不同——POOL-CONFIG-UNIFIED F-2))。 */
|
|
157
|
+
export function resolveAdvisorPoolLimit(raw) {
|
|
158
|
+
const v = raw?.advisor
|
|
159
|
+
return Number.isInteger(v) && v >= 1 ? v : ADVISOR_POOL_LIMIT
|
|
169
160
|
}
|
|
170
161
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
/** launch 判定点每次读 agent.config(/config 热应用——变更即生效下个 launch——文案
|
|
163
|
+
* 如实报本值)。 */
|
|
164
|
+
export function advisorPoolLimitFor(agent) {
|
|
165
|
+
return resolveAdvisorPoolLimit(agent?.config?.agent?.poolLimits)
|
|
174
166
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
* code
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const set = new Set(scope)
|
|
187
|
-
return since.some((m) => (m.paths ?? []).some((p) => set.has(normAbs(p, agent?.cwd))))
|
|
167
|
+
/** §11.2 同 scope 守卫(F-5——用户裁①):同 reviewType+scope 有 running 评审 → true
|
|
168
|
+
* (拒——running 并行多实例歧义放大——settled 续跑语义不变)。design scope = 文档集键
|
|
169
|
+
* (docSetKey);code = 单 code 线程(记录不记路径键——与 openCodeRun 语义一致)。
|
|
170
|
+
* 与池容量守卫独立:容量 = 全局 ≤N——scope = 同 scope ≤1——两关都过才启动。 */
|
|
171
|
+
export function runningAdvisorOfScope(agent, reviewType, docSetKey) {
|
|
172
|
+
const pool = agent?._asyncAdvisors
|
|
173
|
+
if (!(pool instanceof Map)) return false
|
|
174
|
+
for (const e of pool.values()) {
|
|
175
|
+
if (e.status !== "running" || e.reviewType !== reviewType) continue
|
|
176
|
+
if (reviewType === "design" && e.run?.docSetKey !== docSetKey) continue
|
|
177
|
+
return true
|
|
188
178
|
}
|
|
189
|
-
return
|
|
179
|
+
return false
|
|
190
180
|
}
|
|
191
181
|
|
|
192
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
193
|
-
// Async pool (agent._asyncAdvisors — ADVISOR_POOL_LIMIT = 2, launch-and-refuse)
|
|
194
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
195
|
-
|
|
196
|
-
export const ADVISOR_POOL_LIMIT = 2
|
|
197
|
-
|
|
198
182
|
export function runningAdvisorCount(agent) {
|
|
199
183
|
return [...(agent?._asyncAdvisors?.values() ?? [])].filter((e) => e.status === "running").length
|
|
200
184
|
}
|
|
201
185
|
|
|
202
|
-
/** Mechanical-failure prefixes (run.mjs resolves with these — never throws): a
|
|
203
|
-
* settle carrying one produced no review verdict and must not satisfy the guard. */
|
|
204
|
-
const ADVISOR_FAILURE_TEXT = /^Advisor: (?:review failed|review timeout|stopped after|interrupted|context window limit|empty response)/
|
|
205
|
-
|
|
206
186
|
/** Any in-flight (non-done) async advisor review? — the completion guard skips
|
|
207
187
|
* its push-back while a review is pending (T-24b4: 未决不算未评审). */
|
|
208
188
|
export function advisorReviewPending(agent) {
|
|
@@ -224,114 +204,11 @@ export function cancelAsyncAdvisor(agent, id) {
|
|
|
224
204
|
}
|
|
225
205
|
if (entry.cancelled) return { id: key, status: "cancelled" } // abort already in flight — idempotent
|
|
226
206
|
entry.cancelled = true
|
|
227
|
-
|
|
207
|
+
// §20.3 站点 #11(第 24 批):定向中止 = cancel(reason 载荷)
|
|
208
|
+
entry.controller?.abort?.({ abortTrigger: "cancel", abortDetail: "advisor-cancel" })
|
|
228
209
|
return { id: key, status: "cancelled" }
|
|
229
210
|
}
|
|
230
211
|
|
|
231
|
-
/**
|
|
232
|
-
* Settle accounting (fix #2/#4 — runs when a non-cancelled review settles,
|
|
233
|
-
* BEFORE the pending transfer / digest injection):
|
|
234
|
-
* 1. round++ (attempts count — parity with the legacy _advisorRound++ budget);
|
|
235
|
-
* 2. stale determination — mutated targets since launch → no called-mark, no token;
|
|
236
|
-
* 3. non-stale: code review → _calledAdvisorThisRun = true; design review →
|
|
237
|
-
* token echo check (settleDesignReview: slot + instance close; single-value
|
|
238
|
-
* mirror retired per DESIGN-TOKEN-SETTLEMENT D3) + D1 settle-time slot persist
|
|
239
|
-
* (persistEngTokens — write failure = settle failure: no registration, no
|
|
240
|
-
* Approved echo, re-review — 评审 #1);
|
|
241
|
-
* 4. §29 fix B — branch-shaped report output (the caller writes it back to
|
|
242
|
-
* entry.report; digest injects the CLEANED form): pass → stripped +
|
|
243
|
-
* Approved/designId suffix (sync 参照形态); persist-failed → stripped +
|
|
244
|
-
* "D1: …token could NOT be durably written…" re-review notice (no Approved
|
|
245
|
-
* suffix — never an unregistered token); stale → echo stripped +
|
|
246
|
-
* "评审目标已变更——token 未签发" prefix — never an unregistered token.
|
|
247
|
-
* Cancelled / parent-aborted reviews consume nothing (the user dropped the
|
|
248
|
-
* attempt — the retry must not lose budget).
|
|
249
|
-
* @returns {{cancelled: boolean, stale: boolean, passed: boolean, report: string|null}}
|
|
250
|
-
*/
|
|
251
|
-
export function settleAdvisorRun(agent, entry) {
|
|
252
|
-
if (entry.cancelled) return { cancelled: true, stale: false, passed: false, report: null }
|
|
253
|
-
const run = entry.run
|
|
254
|
-
if (!run) return { cancelled: false, stale: false, passed: false, report: entry.report ?? null }
|
|
255
|
-
const result = entry.report ?? null
|
|
256
|
-
run.round++
|
|
257
|
-
agent._advisorRound = run.round
|
|
258
|
-
const stale = reviewIsStale(agent, entry)
|
|
259
|
-
run.stale = stale
|
|
260
|
-
let report = result
|
|
261
|
-
let passed = false
|
|
262
|
-
if (!stale) {
|
|
263
|
-
if (run.reviewType === "design" && entry.designToken && result) {
|
|
264
|
-
// settle 前 Map 快照——落盘失败时回滚用(settle 失败 = 结算未发生,不留半结算态:
|
|
265
|
-
// 重评覆盖旧槽的边角(F2h 复用 designId)也原样恢复旧 token——内存与盘一致)。
|
|
266
|
-
const preMap = agent._engDesignTokens instanceof Map
|
|
267
|
-
? new Map(agent._engDesignTokens)
|
|
268
|
-
: null
|
|
269
|
-
const settled = settleDesignReview(agent, run, entry.designToken, result)
|
|
270
|
-
if (settled.passed) {
|
|
271
|
-
// DESIGN-TOKEN-SETTLEMENT D1(2026-09-08):settle 是唯一结算点——settle 当场
|
|
272
|
-
// 同步落盘 token 字段到槽文件(persistEngTokens = engTokenSlotFields 序列化 +
|
|
273
|
-
// session 安全写/轮转——勿裸写文件),不等下个回合尾 saveSession(消除"settle→
|
|
274
|
-
// 下个 saveSession"间的重启丢 token 窗口)。
|
|
275
|
-
// 写失败即 settle 失败(评审 #1):token 不注册(Map 回滚到 settle 前快照)、无
|
|
276
|
-
// Approved 回显、可重评——不静默吞错、不产生"内存有盘上无"态(宁可结算失败
|
|
277
|
-
// 可重评,不留半结算态)。
|
|
278
|
-
let durable = false
|
|
279
|
-
try {
|
|
280
|
-
durable = persistEngTokens(agent)
|
|
281
|
-
} catch (e) {
|
|
282
|
-
logEvent("advisor:error", { id: `advisor#${entry.id}`, err: `engDesignTokens slot persist threw: ${e?.message ?? String(e)}` })
|
|
283
|
-
}
|
|
284
|
-
if (durable) {
|
|
285
|
-
passed = true
|
|
286
|
-
report = settled.output
|
|
287
|
-
} else {
|
|
288
|
-
if (preMap) agent._engDesignTokens = preMap
|
|
289
|
-
else delete agent._engDesignTokens
|
|
290
|
-
run.approvedSuffix = null
|
|
291
|
-
logEvent("advisor:error", { id: `advisor#${entry.id}`, err: "engDesignTokens slot persist failed — settle failed (re-review)" })
|
|
292
|
-
const stripped = String(result)
|
|
293
|
-
.replace(makeDesignTokenRegex(entry.designToken, "g"), "")
|
|
294
|
-
.trim()
|
|
295
|
-
report = `${stripped}\n\nD1: the design review passed but the token could NOT be durably written to the session ledger (slot persist failed) — re-run advisor(type='design') to re-issue; no eng-coder spawn is authorized for this review (评审通过但 token 未能持久化——需重评).`.trim()
|
|
296
|
-
}
|
|
297
|
-
} else {
|
|
298
|
-
report = settled.output
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// A completed review covers the code face ONLY when it actually produced a
|
|
302
|
-
// verdict: a mechanical-failure settle ("Advisor: review failed/timeout/…" —
|
|
303
|
-
// run.mjs resolves with the failure text, it never throws) must not satisfy
|
|
304
|
-
// the guard silently — the digest shows the failure and the guard pushes
|
|
305
|
-
// back for a retry (fix #2 anti-silent-skip intent; attempts still consume
|
|
306
|
-
// the per-review round budget, so repeated failures stay bounded by the
|
|
307
|
-
// cap). An error settle (rejection path — report null with an error) has no
|
|
308
|
-
// verdict either — same exclusion (advisor 复评补边). Design reviews keep
|
|
309
|
-
// the mark on any completed verdict (parity with the sync recordToolResults
|
|
310
|
-
// mark — they have no code face to cover).
|
|
311
|
-
const failureVerdict = run.reviewType !== "design" && (
|
|
312
|
-
ADVISOR_FAILURE_TEXT.test(String(report ?? "")) ||
|
|
313
|
-
(result == null && entry.error != null)
|
|
314
|
-
)
|
|
315
|
-
if (!failureVerdict) {
|
|
316
|
-
agent._calledAdvisorThisRun = true
|
|
317
|
-
}
|
|
318
|
-
} else if (run.reviewType === "design" && entry.designToken && result != null) {
|
|
319
|
-
// §29 fix B(stale 分支):陈旧评审不签发——digest 不得展示未注册 token——先剥
|
|
320
|
-
// 方括号回显 + 前置 "评审目标已变更——token 未签发"(不变式——两分支都清洗)。
|
|
321
|
-
const stripped = String(result)
|
|
322
|
-
.replace(makeDesignTokenRegex(entry.designToken, "g"), "")
|
|
323
|
-
.trim()
|
|
324
|
-
report = `评审目标已变更——token 未签发 (review target changed after launch — this review judged a stale state; no design token was issued — re-run the review on the current state)\n\n${stripped}`.trim()
|
|
325
|
-
}
|
|
326
|
-
// Prior of round 2+ = the last REVIEW-LOOKING output (mirror of run.mjs's guard).
|
|
327
|
-
// F2e (§29.1): strip the engine-approved suffix FIRST — the prior must never
|
|
328
|
-
// carry the raw token / designId (exact truncation — zero collateral).
|
|
329
|
-
if (report && looksLikeReviewOutput(report)) {
|
|
330
|
-
run.priorOutput = stripApprovedSuffix(report, run.approvedSuffix)
|
|
331
|
-
}
|
|
332
|
-
return { cancelled: false, stale, passed, report }
|
|
333
|
-
}
|
|
334
|
-
|
|
335
212
|
/** Relay an advisor onOutput chunk into the subagent block channel (`advisor#N/`
|
|
336
213
|
* prefix — kind-preserving: think via onReasoning, tool via onToolOutput, text
|
|
337
214
|
* via onToken; the wait placeholder is stripped — it is a live indicator, not
|
|
@@ -356,16 +233,27 @@ function relayAdvisorOutput(callbacks, prefix, chunk) {
|
|
|
356
233
|
* The runner wraps runAdvisorReview (promise → report/error) — the subagent
|
|
357
234
|
* pipeline is untouched (ruling ②-2 A). The entry carries role "advisor" so the
|
|
358
235
|
* digest/panel/freeze consumers route it like any other settled block.
|
|
359
|
-
* @returns {{ok: true, id: string}} — ack; or {{error: string}} —
|
|
360
|
-
* (
|
|
236
|
+
* @returns {{ok: true, id: string}} — ack; or {{error: string}} — scope guard
|
|
237
|
+
* (same reviewType+scope running — §11.2 F-5) or pool full (the effective
|
|
238
|
+
* agent.poolLimits.advisor limit, never queued) or invalid ctx.
|
|
361
239
|
*/
|
|
362
240
|
export function launchAsyncAdvisor(parent, ctx, launch) {
|
|
363
241
|
const { reviewType, documents, paths, object, designToken, designId, run } = launch
|
|
364
|
-
|
|
365
|
-
|
|
242
|
+
const limit = advisorPoolLimitFor(parent)
|
|
243
|
+
// §11.2 same-scope guard(F-5——用户裁①):同 type+scope 有 running → 拒——与池容量
|
|
244
|
+
// 守卫独立两关都过才启动——不等不排(②-6a 无排队语义保持——settled 续跑不变)。
|
|
245
|
+
if (runningAdvisorOfScope(parent, reviewType, run?.docSetKey ?? null)) {
|
|
246
|
+
const scopeNote = reviewType === "design"
|
|
247
|
+
? "a design review of this document set is still running"
|
|
248
|
+
: "a code review is still running (code reviews are a single thread — launch the next one after it settles)"
|
|
249
|
+
return { error: `Advisor: 此 scope 已有评审在跑——settle 后逐个发起 — ${scopeNote}; round/prior continuation would be ambiguous while it is in flight — wait for it to settle, then launch the next review (AGENT-LOOP.md §11.2).` }
|
|
250
|
+
}
|
|
251
|
+
if (runningAdvisorCount(parent) >= limit) {
|
|
252
|
+
return { error: `Advisor: 另有一评审在跑——逐个发起 — another review is already running in the background pool (${limit} reviews at most — agent.poolLimits.advisor, default ${ADVISOR_POOL_LIMIT}); launch them one at a time (AGENT-LOOP.md §11.2 ruling ②-6a).` }
|
|
366
253
|
}
|
|
367
254
|
parent._asyncAdvisors ??= new Map()
|
|
368
|
-
|
|
255
|
+
// SUBAGENT-ID-COUNTER-AGENT(2026-09-09):取号统一走 nextSubagentId——池活续号兜底 + 跨池共号(§11.2)。
|
|
256
|
+
const id = nextSubagentId(parent)
|
|
369
257
|
const entry = {
|
|
370
258
|
id, role: "advisor", reviewType, run,
|
|
371
259
|
reviewId: run.reviewId, designId, designToken, documents, paths, object,
|
|
@@ -389,8 +277,9 @@ export function launchAsyncAdvisor(parent, ctx, launch) {
|
|
|
389
277
|
entry.controller = ctrl
|
|
390
278
|
const baseSignal = buildChildSignal(parent, ctx)
|
|
391
279
|
if (baseSignal) {
|
|
392
|
-
|
|
393
|
-
|
|
280
|
+
// §20.3 站点 #10(第 24 批):hop 逐跳保 reason
|
|
281
|
+
if (baseSignal.aborted) ctrl.abort(baseSignal.reason)
|
|
282
|
+
else baseSignal.addEventListener("abort", () => ctrl.abort(baseSignal.reason), { once: true })
|
|
394
283
|
}
|
|
395
284
|
entry.promise = new Promise((res) => { entry._settle = res })
|
|
396
285
|
entry.start = () => {
|
|
@@ -403,7 +292,8 @@ export function launchAsyncAdvisor(parent, ctx, launch) {
|
|
|
403
292
|
signal: entry.controller.signal,
|
|
404
293
|
}, designToken, documents, paths, object, designId)
|
|
405
294
|
.then((report) => { entry.report = report })
|
|
406
|
-
.
|
|
295
|
+
// §20.3 第 3 条合成器(第 24 批):原 message 前缀逐字保留 + 来源后缀
|
|
296
|
+
.catch((err) => { entry.error = deathLine(err, entry.controller?.signal) })
|
|
407
297
|
.finally(() => {
|
|
408
298
|
// settle 公共收尾单点(ASYNC-RESULT-CONTAINER.md D3——settleAsyncEntry):日志三连
|
|
409
299
|
// /cancelled 分支(出池+墓碑+⟦ev⟧stopped+"评审已取消——token 未签发"提醒)/挂起分流
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* advisor-settle.mjs — advisor settle accounting + mutation log + stale / freeze
|
|
3
|
+
* determination(2026-09-11 第 11 批自 advisor-async.mjs 拆出——500 行 = 硬帽在册,
|
|
4
|
+
* 任何新增必越;既有 import 面经 advisor-async.mjs **re-export 保持不变**
|
|
5
|
+
* (noteMutations / mutationSeqOf / reviewIsStale / settleAdvisorRun)。
|
|
6
|
+
*
|
|
7
|
+
* 内容 = 迁入(逐字搬移含注释)+ 本批两处接线:
|
|
8
|
+
* - `settleAdvisorRun` —— 记账本体零改(轮次 / 陈旧 / token D1 落盘 / prior);A 族
|
|
9
|
+
* 未完成判定改用单谓词 `advisorIncompleteMarker`(§14.3 消费点 1/2):design 结算经
|
|
10
|
+
* `settleDesignReview(…, { incomplete })` 未完成即不签发;code 完成守卫同谓词
|
|
11
|
+
* (旧 `^` 锚首行失败正则退役——六形态语义零丢,含 review_failed 字符串
|
|
12
|
+
* resolve 形态);
|
|
13
|
+
* - `inflightDesignReviewConflict` —— E 族(F17/§14.14)D5 冻结窗口冲突检测:与
|
|
14
|
+
* `reviewIsStale` 同族(同 docAbs / 同 normAbs)、仅扫 running 且未取消的设计条目
|
|
15
|
+
* (dispatch Phase-1 预闸消费)。
|
|
16
|
+
*
|
|
17
|
+
* 第 33 批(2026-09-11——评审失败护栏 §17.5):`normAbs` 本体迁 `review-streak.mjs`
|
|
18
|
+
* (原处 re-export 保面)+ design 结算计数接线(分类单源 `designReviewOutcome` →
|
|
19
|
+
* `noteDesignReviewOutcome`,三出口)——记账本体 / 陈旧判定 / 轮次 / token 落盘零改。
|
|
20
|
+
*/
|
|
21
|
+
import { persistEngTokens } from "../token-ttl.mjs"
|
|
22
|
+
import { settleDesignReview, makeDesignTokenRegex, stripApprovedSuffix } from "./design-token.mjs"
|
|
23
|
+
import { looksLikeReviewOutput, advisorIncompleteMarker, ADVISOR_LAUNCH_REFUSAL_PREFIX } from "../advisor/run.mjs"
|
|
24
|
+
import { isCodePath, loadConventions } from "../conventions.mjs"
|
|
25
|
+
import { logEvent } from "../log.mjs"
|
|
26
|
+
// 第 33 批(§17.5):`normAbs` 迁 `review-streak.mjs`(中立模块——护栏与陈旧判定共用同一归一)
|
|
27
|
+
// + 结算分类 / 计数落账(异步结算计数点)。
|
|
28
|
+
import { normAbs, designReviewOutcome, noteDesignReviewOutcome } from "./review-streak.mjs"
|
|
29
|
+
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
// Mutation log (stale-review determination — fix #2: FILE_MUTATORS after the
|
|
32
|
+
// review launch make the settle stale: no called-mark, no token, guard re-pushes)
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export function mutationSeqOf(agent) {
|
|
36
|
+
return agent._mutationSeq ?? 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Record a file-mutation commit — bounded ring feeding the stale scan. paths = ABSOLUTE.
|
|
40
|
+
* 唯一记账点(§29 fix A):dispatch runOne 写执行成功即刻调用(取代批后段 + 中断分支——
|
|
41
|
+
* 不双计);mergeChildMutations(子代理合入)是独立事件面。 */
|
|
42
|
+
export function noteMutations(agent, paths) {
|
|
43
|
+
const list = (paths ?? []).filter((p) => typeof p === "string" && p.length > 0)
|
|
44
|
+
if (list.length === 0) return
|
|
45
|
+
agent._mutationSeq = (agent._mutationSeq ?? 0) + 1
|
|
46
|
+
const log = agent._mutLog ??= []
|
|
47
|
+
log.push({ seq: agent._mutationSeq, paths: [...new Set(list)] })
|
|
48
|
+
if (log.length > 200) log.splice(0, log.length - 200)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 第 33 批(§17.5):`normAbs` 本体迁 `review-streak.mjs`;原处 re-export 保既有 import 面
|
|
52
|
+
// (advisor-async / 测试)零变。
|
|
53
|
+
export { normAbs } from "./review-streak.mjs"
|
|
54
|
+
|
|
55
|
+
/** Stale = a mutation committed after the launch touched the review's face:
|
|
56
|
+
* code reviews judge the CODE face; design reviews judge their OWN documents. */
|
|
57
|
+
export function reviewIsStale(agent, entry) {
|
|
58
|
+
const log = agent?._mutLog
|
|
59
|
+
if (!Array.isArray(log) || log.length === 0) return false
|
|
60
|
+
const since = log.filter((m) => m.seq > (entry.launchSeq ?? -1))
|
|
61
|
+
if (since.length === 0) return false
|
|
62
|
+
if (entry.reviewType === "design") {
|
|
63
|
+
const scope = (entry.docAbs ?? []).map((p) => normAbs(p, agent?.cwd))
|
|
64
|
+
if (scope.length === 0) return false // no explicit doc scope — nothing to judge stale
|
|
65
|
+
const set = new Set(scope)
|
|
66
|
+
return since.some((m) => (m.paths ?? []).some((p) => set.has(normAbs(p, agent?.cwd))))
|
|
67
|
+
}
|
|
68
|
+
// Code face = the shared classifier (src/conventions.mjs) — one authority with
|
|
69
|
+
// the design gate and the mutation guard (PORTABILITY FR12 / PO-10).
|
|
70
|
+
const conv = loadConventions(agent?.cwd)
|
|
71
|
+
return since.some((m) => (m.paths ?? []).some((p) => isCodePath(normAbs(p, agent?.cwd), conv)))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* E 族(F17/§14.14 E-3d)——D5 冻结窗口写前拦截的冲突检测:设计评审**在途**(点火 → 结算)
|
|
76
|
+
* 期间,父侧对被审文件集(`docAbs` = 声明文档集,含批次档)的写入会被 dispatch 预闸拒绝。
|
|
77
|
+
* 判据与 `reviewIsStale` **同源**(同一 docAbs / 同一 normAbs);仅扫 **running 且未取消**
|
|
78
|
+
* 的设计条目(已结算 / 已取消条目不拦——结算后写不再致 stale、取消的结算早退不判)。
|
|
79
|
+
* 保守残余(如实注):回合中止后池清前的窗口可能拒一笔不致 stale 的写(保守方向)。
|
|
80
|
+
* @param {Object} agent
|
|
81
|
+
* @param {string[]} absPaths — 本次写将触碰的路径(ABS——调用方 resolve(agent.cwd, p))
|
|
82
|
+
* @returns {{id: string, path: string}|null} 命中评审 id + 冲突路径(ABS),无冲突 null
|
|
83
|
+
*/
|
|
84
|
+
export function inflightDesignReviewConflict(agent, absPaths) {
|
|
85
|
+
const pool = agent?._asyncAdvisors
|
|
86
|
+
if (!(pool instanceof Map) || pool.size === 0) return null
|
|
87
|
+
const wanted = (absPaths ?? [])
|
|
88
|
+
.filter((p) => typeof p === "string" && p.length > 0)
|
|
89
|
+
.map((p) => normAbs(p, agent?.cwd))
|
|
90
|
+
if (wanted.length === 0) return null
|
|
91
|
+
for (const entry of pool.values()) {
|
|
92
|
+
if (!entry || entry.reviewType !== "design") continue
|
|
93
|
+
if (entry.status !== "running" || entry.cancelled || entry.done) continue
|
|
94
|
+
const scope = (entry.docAbs ?? []).map((p) => normAbs(p, agent?.cwd))
|
|
95
|
+
if (scope.length === 0) continue
|
|
96
|
+
const set = new Set(scope)
|
|
97
|
+
for (const p of wanted) if (set.has(p)) return { id: String(entry.id), path: p }
|
|
98
|
+
}
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Settle accounting (fix #2/#4 — runs when a non-cancelled review settles,
|
|
104
|
+
* BEFORE the pending transfer / digest injection):
|
|
105
|
+
* 1. round++ (attempts count — parity with the legacy _advisorRound++ budget);
|
|
106
|
+
* 2. stale determination — mutated targets since launch → no called-mark, no token;
|
|
107
|
+
* 3. non-stale: code review → _calledAdvisorThisRun = true; design review →
|
|
108
|
+
* token echo check (settleDesignReview: slot + instance close; single-value
|
|
109
|
+
* mirror retired per DESIGN-TOKEN-SETTLEMENT D3) + D1 settle-time slot persist
|
|
110
|
+
* (persistEngTokens — write failure = settle failure: no registration, no
|
|
111
|
+
* Approved echo, re-review — 评审 #1);
|
|
112
|
+
* 4. §29 fix B — branch-shaped report output (the caller writes it back to
|
|
113
|
+
* entry.report; digest injects the CLEANED form): pass → stripped +
|
|
114
|
+
* Approved/designId suffix (sync 参照形态); persist-failed → stripped +
|
|
115
|
+
* "D1: …token could NOT be durably written…" re-review notice (no Approved
|
|
116
|
+
* suffix — never an unregistered token); stale → echo stripped +
|
|
117
|
+
* "评审目标已变更——token 未签发" prefix — never an unregistered token.
|
|
118
|
+
* 第 11 批增补(A/F11):`incomplete` = 宿主尾族判定(单谓词)——design 结算未完成即不
|
|
119
|
+
* 签发(`settleDesignReview` 的 `opts.incomplete`);code 完成守卫的失败判定改用同谓词
|
|
120
|
+
* (六形态语义零丢——旧 `^` 锚正则退役)。
|
|
121
|
+
* 第 33 批增补(§17.5 计数点 1):design 结算按分类单源(`designReviewOutcome`)落账到
|
|
122
|
+
* 会话级护栏 `agent._designReviewStreaks`(三出口:非陈旧 / 陈旧 / 无报告)——cancel 早退
|
|
123
|
+
* 与无实例路径不计(neutral 语义 = 无尝试发生)。
|
|
124
|
+
* Cancelled / parent-aborted reviews consume nothing (the user dropped the
|
|
125
|
+
* attempt — the retry must not lose budget).
|
|
126
|
+
* @returns {{cancelled: boolean, stale: boolean, passed: boolean, report: string|null}}
|
|
127
|
+
*/
|
|
128
|
+
export function settleAdvisorRun(agent, entry) {
|
|
129
|
+
if (entry.cancelled) return { cancelled: true, stale: false, passed: false, report: null }
|
|
130
|
+
const run = entry.run
|
|
131
|
+
if (!run) return { cancelled: false, stale: false, passed: false, report: entry.report ?? null }
|
|
132
|
+
const result = entry.report ?? null
|
|
133
|
+
run.round++
|
|
134
|
+
agent._advisorRound = run.round
|
|
135
|
+
const stale = reviewIsStale(agent, entry)
|
|
136
|
+
run.stale = stale
|
|
137
|
+
let report = result
|
|
138
|
+
let passed = false
|
|
139
|
+
let persistFailed = false
|
|
140
|
+
// 宿主尾族判定(单谓词——design 结算与 code 守卫共用;§14.3 消费点 1/2)。
|
|
141
|
+
const incomplete = result != null ? advisorIncompleteMarker(String(result)) : null
|
|
142
|
+
// 启动拒绝报告(未发起请求——无评审产出):第 33 批上移为本结算段**单点**——消费 = ① design
|
|
143
|
+
// 失败分类(neutral——无尝试发生,§17.3 #1)② code 守卫 failureVerdict(既有语义零变)。
|
|
144
|
+
const launchRefused = result != null && String(result).startsWith(ADVISOR_LAUNCH_REFUSAL_PREFIX)
|
|
145
|
+
if (!stale) {
|
|
146
|
+
if (run.reviewType === "design" && entry.designToken && result) {
|
|
147
|
+
// settle 前 Map 快照——落盘失败时回滚用(settle 失败 = 结算未发生,不留半结算态:
|
|
148
|
+
// 重评覆盖旧槽的边角(F2h 复用 designId)也原样恢复旧 token——内存与盘一致)。
|
|
149
|
+
const preMap = agent._engDesignTokens instanceof Map
|
|
150
|
+
? new Map(agent._engDesignTokens)
|
|
151
|
+
: null
|
|
152
|
+
const settled = settleDesignReview(agent, run, entry.designToken, result, { incomplete })
|
|
153
|
+
if (settled.passed) {
|
|
154
|
+
// DESIGN-TOKEN-SETTLEMENT D1(2026-09-08):settle 是唯一结算点——settle 当场
|
|
155
|
+
// 同步落盘 token 字段到槽文件(persistEngTokens = engTokenSlotFields 序列化 +
|
|
156
|
+
// session 安全写/轮转——勿裸写文件),不等下个回合尾 saveSession(消除"settle→
|
|
157
|
+
// 下个 saveSession"间的重启丢 token 窗口)。
|
|
158
|
+
// 写失败即 settle 失败(评审 #1):token 不注册(Map 回滚到 settle 前快照)、无
|
|
159
|
+
// Approved 回显、可重评——不静默吞错、不产生"内存有盘上无"态(宁可结算失败
|
|
160
|
+
// 可重评,不留半结算态)。
|
|
161
|
+
let durable = false
|
|
162
|
+
try {
|
|
163
|
+
durable = persistEngTokens(agent)
|
|
164
|
+
} catch (e) {
|
|
165
|
+
logEvent("advisor:error", { id: `advisor#${entry.id}`, err: `engDesignTokens slot persist threw: ${e?.message ?? String(e)}` })
|
|
166
|
+
}
|
|
167
|
+
if (durable) {
|
|
168
|
+
passed = true
|
|
169
|
+
report = settled.output
|
|
170
|
+
} else {
|
|
171
|
+
if (preMap) agent._engDesignTokens = preMap
|
|
172
|
+
else delete agent._engDesignTokens
|
|
173
|
+
run.approvedSuffix = null
|
|
174
|
+
persistFailed = true
|
|
175
|
+
logEvent("advisor:error", { id: `advisor#${entry.id}`, err: "engDesignTokens slot persist failed — settle failed (re-review)" })
|
|
176
|
+
const stripped = String(result)
|
|
177
|
+
.replace(makeDesignTokenRegex(entry.designToken, "g"), "")
|
|
178
|
+
.trim()
|
|
179
|
+
report = `${stripped}\n\nD1: the design review passed but the token could NOT be durably written to the session ledger (slot persist failed) — re-run advisor(type='design') to re-issue; no eng-coder spawn is authorized for this review (评审通过但 token 未能持久化——需重评).`.trim()
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
report = settled.output
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// A completed review covers the code face ONLY when it actually produced a
|
|
186
|
+
// verdict: a mechanical-failure settle ("Advisor: review failed/timeout/…" —
|
|
187
|
+
// run.mjs resolves with the failure text, it never throws) must not satisfy
|
|
188
|
+
// the guard silently — the digest shows the failure and the guard pushes
|
|
189
|
+
// back for a retry (fix #2 anti-silent-skip intent; attempts still consume
|
|
190
|
+
// the per-review round budget, so repeated failures stay bounded by the
|
|
191
|
+
// cap). An error settle (rejection path — report null with an error) has no
|
|
192
|
+
// verdict either — same exclusion (advisor 复评补边). Design reviews keep
|
|
193
|
+
// the mark on any completed verdict (parity with the sync recordToolResults
|
|
194
|
+
// mark — they have no code face to cover) — EXCEPT a launch-refusal report
|
|
195
|
+
// (nothing was sent, no verdict at all: see launchRefused below).
|
|
196
|
+
// 第 11 批(F16/A3):失败判定 = 单谓词六 kind(含 review_failed 字符串 resolve 形态)
|
|
197
|
+
// ——旧 `^` 锚正则只认首行形态,漏「时间线 + 尾」;语义零丢(六形态全覆盖)。
|
|
198
|
+
// 另:设计评审的**启动拒绝报告**(未发起请求——无评审产出)同样不得计为评审覆盖
|
|
199
|
+
// (§14.4 异步结算面判据;与同步工具面 _advisorRefusals 同源前缀;正常链不可达=防御纵深)
|
|
200
|
+
// ——判定已上移为本结算段单点(launchRefused,上方)。
|
|
201
|
+
const failureVerdict = (run.reviewType !== "design" && (
|
|
202
|
+
incomplete !== null ||
|
|
203
|
+
(result == null && entry.error != null)
|
|
204
|
+
)) || launchRefused
|
|
205
|
+
if (!failureVerdict) {
|
|
206
|
+
agent._calledAdvisorThisRun = true
|
|
207
|
+
}
|
|
208
|
+
} else if (run.reviewType === "design" && entry.designToken && result != null) {
|
|
209
|
+
// §29 fix B(stale 分支):陈旧评审不签发——digest 不得展示未注册 token——先剥
|
|
210
|
+
// 方括号回显 + 前置 "评审目标已变更——token 未签发"(不变式——两分支都清洗)。
|
|
211
|
+
const stripped = String(result)
|
|
212
|
+
.replace(makeDesignTokenRegex(entry.designToken, "g"), "")
|
|
213
|
+
.trim()
|
|
214
|
+
report = `评审目标已变更——token 未签发 (review target changed after launch — this review judged a stale state; no design token was issued — re-run the review on the current state)\n\n${stripped}`.trim()
|
|
215
|
+
}
|
|
216
|
+
// 第 33 批(§17.5 计数点 1——异步结算):design 三出口(非陈旧 / 陈旧 / design 无报告)统一
|
|
217
|
+
// 按分类单源落账——count/stale/no_credential/no_report 增计数,可用判决复位,取消 / 中断 /
|
|
218
|
+
// 拒发 neutral(分类表见 §17.3)。cancel 早退 / 无实例在函数头已返回(不计)。
|
|
219
|
+
if (run.reviewType === "design") {
|
|
220
|
+
noteDesignReviewOutcome(agent, run.docSetKey, designReviewOutcome({
|
|
221
|
+
launchRefused, stale, hasResult: result != null, incomplete, persistFailed,
|
|
222
|
+
}))
|
|
223
|
+
}
|
|
224
|
+
// Prior of round 2+ = the last REVIEW-LOOKING output (mirror of run.mjs's guard).
|
|
225
|
+
// F2e (§29.1): strip the engine-approved suffix FIRST — the prior must never
|
|
226
|
+
// carry the raw token / designId (exact truncation — zero collateral).
|
|
227
|
+
if (report && looksLikeReviewOutput(report)) {
|
|
228
|
+
run.priorOutput = stripApprovedSuffix(report, run.approvedSuffix)
|
|
229
|
+
}
|
|
230
|
+
return { cancelled: false, stale, passed, report }
|
|
231
|
+
}
|