thincoder 0.12.62 → 0.12.64

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.
Files changed (231) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +46 -49
  3. package/bin/thincoder.mjs +68 -34
  4. package/package.json +7 -6
  5. package/src/acp/bridge.mjs +35 -15
  6. package/src/acp/client-caps.mjs +86 -0
  7. package/src/acp/ext.mjs +86 -0
  8. package/src/acp/handlers-session.mjs +257 -0
  9. package/src/acp/handlers-slots.mjs +196 -0
  10. package/src/acp/login.mjs +48 -0
  11. package/src/acp/session.mjs +6 -4
  12. package/src/acp.mjs +67 -379
  13. package/src/cli/distill-command.mjs +3 -3
  14. package/src/cli/make-agent.mjs +60 -17
  15. package/src/cli/memory-command.mjs +3 -3
  16. package/src/cli/permission.mjs +4 -48
  17. package/src/cli/setup-wizard.mjs +1 -1
  18. package/src/completions.mjs +3 -1
  19. package/src/crash-reports.mjs +1 -1
  20. package/src/distill.mjs +4 -4
  21. package/src/heap-watch.mjs +1 -1
  22. package/src/prompt-injections.mjs +20 -0
  23. package/src/tui/agent-turn.mjs +40 -9
  24. package/src/tui/cmd-advisor.mjs +5 -5
  25. package/src/tui/cmd-config.mjs +8 -8
  26. package/src/tui/cmd-eng.mjs +35 -9
  27. package/src/tui/cmd-mcp.mjs +9 -8
  28. package/src/tui/cmd-model.mjs +1 -1
  29. package/src/tui/cmd-new.mjs +6 -5
  30. package/src/tui/cmd-plan.mjs +9 -0
  31. package/src/tui/cmd-reindex.mjs +1 -1
  32. package/src/tui/cmd-restore.mjs +2 -2
  33. package/src/tui/cmd-session.mjs +24 -1
  34. package/src/tui/cmd-skills.mjs +1 -1
  35. package/src/tui/cmd-think.mjs +22 -9
  36. package/src/tui/config-helpers.mjs +1 -1
  37. package/src/tui/display-budget.mjs +33 -11
  38. package/src/tui/index.mjs +18 -10
  39. package/src/tui/interaction.mjs +16 -7
  40. package/src/tui/key-modes.mjs +9 -4
  41. package/src/tui/ledger-surface.mjs +22 -59
  42. package/src/tui/model-catalog.mjs +4 -4
  43. package/src/tui/model-picker.mjs +8 -7
  44. package/src/tui/mouse.mjs +11 -6
  45. package/src/tui/pickers.mjs +15 -2
  46. package/src/tui/render-conversation.mjs +1 -1
  47. package/src/tui/render-frame.mjs +15 -6
  48. package/src/tui/render-loop.mjs +1 -1
  49. package/src/tui/render-segments.mjs +3 -1
  50. package/src/tui/slash-commands.mjs +1 -1
  51. package/src/tui/startup.mjs +14 -14
  52. package/src/tui/subagent-blocks.mjs +20 -3
  53. package/src/tui/subagent-freeze.mjs +73 -2
  54. package/src/tui/suspension-drive.mjs +57 -23
  55. package/src/tui/tool-events.mjs +11 -8
  56. package/src/tui/tui-lifecycle.mjs +9 -0
  57. package/src/tui/wizard.mjs +3 -3
  58. package/src/tui/wrapped-spawn.mjs +6 -2
  59. package/src/abort-provenance.mjs +0 -116
  60. package/src/advisor/citations.mjs +0 -139
  61. package/src/advisor/compaction.mjs +0 -174
  62. package/src/advisor/convergence.mjs +0 -80
  63. package/src/advisor/history.mjs +0 -77
  64. package/src/advisor/loop.mjs +0 -293
  65. package/src/advisor/messages.mjs +0 -299
  66. package/src/advisor/project-context.mjs +0 -194
  67. package/src/advisor/repos.mjs +0 -150
  68. package/src/advisor/run.mjs +0 -293
  69. package/src/advisor/truncate.mjs +0 -57
  70. package/src/advisor.mjs +0 -290
  71. package/src/agent/completion.mjs +0 -146
  72. package/src/agent/dispatch.mjs +0 -489
  73. package/src/agent/helpers.mjs +0 -384
  74. package/src/agent/post-turn.mjs +0 -70
  75. package/src/agent/record-results.mjs +0 -174
  76. package/src/agent/relay-prefix.mjs +0 -39
  77. package/src/agent/run-stages.mjs +0 -244
  78. package/src/agent/setup-reminders.mjs +0 -69
  79. package/src/agent/setup.mjs +0 -354
  80. package/src/agent/spawn-child.mjs +0 -243
  81. package/src/agent-tools/advisor-async.mjs +0 -346
  82. package/src/agent-tools/advisor-settle.mjs +0 -231
  83. package/src/agent-tools/advisor.mjs +0 -260
  84. package/src/agent-tools/async-settle.mjs +0 -204
  85. package/src/agent-tools/batch-segment.mjs +0 -195
  86. package/src/agent-tools/consult.mjs +0 -473
  87. package/src/agent-tools/design-token.mjs +0 -117
  88. package/src/agent-tools/digest-budget.mjs +0 -76
  89. package/src/agent-tools/eng.mjs +0 -67
  90. package/src/agent-tools/escalate-async.mjs +0 -295
  91. package/src/agent-tools/goal.mjs +0 -119
  92. package/src/agent-tools/plan.mjs +0 -81
  93. package/src/agent-tools/read-history.mjs +0 -309
  94. package/src/agent-tools/recent-changes.mjs +0 -24
  95. package/src/agent-tools/review-streak.mjs +0 -93
  96. package/src/agent-tools/settings.mjs +0 -265
  97. package/src/agent-tools/skill.mjs +0 -47
  98. package/src/agent-tools/subagent-actions.mjs +0 -482
  99. package/src/agent-tools/subagent-async.mjs +0 -434
  100. package/src/agent-tools/subagent-panel.mjs +0 -160
  101. package/src/agent-tools/subagent-run.mjs +0 -205
  102. package/src/agent-tools/subagent-scheduler.mjs +0 -392
  103. package/src/agent-tools/subagent-spawn.mjs +0 -459
  104. package/src/agent-tools/subagent.mjs +0 -404
  105. package/src/agent-tools/task.mjs +0 -87
  106. package/src/agent-tools/timer.mjs +0 -46
  107. package/src/agent-tools/verify.mjs +0 -271
  108. package/src/agent-tools.mjs +0 -17
  109. package/src/agent.mjs +0 -417
  110. package/src/auto-think.mjs +0 -115
  111. package/src/config-migrate.mjs +0 -70
  112. package/src/config.mjs +0 -496
  113. package/src/context.mjs +0 -392
  114. package/src/conventions.mjs +0 -223
  115. package/src/embedding.mjs +0 -120
  116. package/src/escape.mjs +0 -152
  117. package/src/expand-home.mjs +0 -16
  118. package/src/explore-distill.mjs +0 -155
  119. package/src/generate-title.mjs +0 -88
  120. package/src/git/checkpoint.mjs +0 -448
  121. package/src/git/gitmem.mjs +0 -100
  122. package/src/hooks.mjs +0 -97
  123. package/src/ledger.mjs +0 -227
  124. package/src/log.mjs +0 -195
  125. package/src/markdown.mjs +0 -106
  126. package/src/mcp/helpers.mjs +0 -51
  127. package/src/mcp/transport-http.mjs +0 -248
  128. package/src/mcp/transport-stdio.mjs +0 -140
  129. package/src/mcp/transport-ws.mjs +0 -122
  130. package/src/mcp.mjs +0 -295
  131. package/src/memory/code-index.mjs +0 -219
  132. package/src/memory/code-sync.mjs +0 -415
  133. package/src/memory/core.mjs +0 -299
  134. package/src/memory/delete.mjs +0 -236
  135. package/src/memory/docs.mjs +0 -419
  136. package/src/memory/file-walk.mjs +0 -109
  137. package/src/memory/scan.mjs +0 -95
  138. package/src/memory/schema.mjs +0 -452
  139. package/src/memory.mjs +0 -21
  140. package/src/model-ref.mjs +0 -66
  141. package/src/model-specs.mjs +0 -179
  142. package/src/peer-domains.mjs +0 -265
  143. package/src/peer-instances.mjs +0 -231
  144. package/src/prompt-overlays.mjs +0 -82
  145. package/src/prompts/advisor-design.md +0 -41
  146. package/src/prompts/advisor-round1.md +0 -41
  147. package/src/prompts/advisor-round2.md +0 -46
  148. package/src/prompts/advisor-round3.md +0 -42
  149. package/src/prompts/common.md +0 -115
  150. package/src/prompts/consult-base.md +0 -19
  151. package/src/prompts/discipline-engineering.md +0 -258
  152. package/src/prompts/discipline-normal.md +0 -185
  153. package/src/prompts/persona-coder.md +0 -21
  154. package/src/prompts/persona-eng-coder.md +0 -37
  155. package/src/prompts/persona-eng-designer.md +0 -60
  156. package/src/prompts/persona-engineering.md +0 -55
  157. package/src/prompts/persona-explore.md +0 -15
  158. package/src/prompts/persona-normal.md +0 -27
  159. package/src/prompts/persona-plan.md +0 -26
  160. package/src/provider/anthropic.mjs +0 -225
  161. package/src/provider/core.mjs +0 -476
  162. package/src/provider/errors.mjs +0 -101
  163. package/src/provider/google.mjs +0 -257
  164. package/src/provider/index.mjs +0 -7
  165. package/src/provider/list-models.mjs +0 -93
  166. package/src/provider/normalize.mjs +0 -81
  167. package/src/provider/rate.mjs +0 -108
  168. package/src/provider/responses.mjs +0 -495
  169. package/src/provider/retry.mjs +0 -88
  170. package/src/provider/sse.mjs +0 -264
  171. package/src/proxy.mjs +0 -261
  172. package/src/rules.mjs +0 -53
  173. package/src/session-gc.mjs +0 -221
  174. package/src/session-guard.mjs +0 -59
  175. package/src/session-migrate.mjs +0 -48
  176. package/src/session-rename.mjs +0 -38
  177. package/src/session-segments.mjs +0 -100
  178. package/src/session-slots.mjs +0 -492
  179. package/src/session-store.mjs +0 -441
  180. package/src/session.mjs +0 -492
  181. package/src/skills.mjs +0 -153
  182. package/src/text-budget.mjs +0 -46
  183. package/src/token-ttl.mjs +0 -274
  184. package/src/tools/apply_patch.md +0 -15
  185. package/src/tools/bash.md +0 -37
  186. package/src/tools/bash.mjs +0 -268
  187. package/src/tools/checklist-sync.mjs +0 -181
  188. package/src/tools/checklist.md +0 -13
  189. package/src/tools/checklist.mjs +0 -299
  190. package/src/tools/delete.md +0 -13
  191. package/src/tools/edit-batch.mjs +0 -191
  192. package/src/tools/edit-diff.mjs +0 -348
  193. package/src/tools/edit.md +0 -30
  194. package/src/tools/execute.md +0 -21
  195. package/src/tools/execute.mjs +0 -228
  196. package/src/tools/fetch.md +0 -12
  197. package/src/tools/file.mjs +0 -469
  198. package/src/tools/file_ops.md +0 -17
  199. package/src/tools/get_current_time.md +0 -8
  200. package/src/tools/git-checkpoint.mjs +0 -143
  201. package/src/tools/git-ext.mjs +0 -173
  202. package/src/tools/git.md +0 -54
  203. package/src/tools/git.mjs +0 -356
  204. package/src/tools/glob-dialect.mjs +0 -130
  205. package/src/tools/glob.md +0 -11
  206. package/src/tools/grep.md +0 -19
  207. package/src/tools/hashline_edit.md +0 -14
  208. package/src/tools/index.mjs +0 -36
  209. package/src/tools/insert_after.md +0 -15
  210. package/src/tools/lint.md +0 -10
  211. package/src/tools/linter.mjs +0 -128
  212. package/src/tools/ls.md +0 -12
  213. package/src/tools/lsp.md +0 -10
  214. package/src/tools/lsp.mjs +0 -316
  215. package/src/tools/ops.mjs +0 -299
  216. package/src/tools/patch.mjs +0 -282
  217. package/src/tools/process.md +0 -10
  218. package/src/tools/question.md +0 -16
  219. package/src/tools/question.mjs +0 -26
  220. package/src/tools/read.md +0 -20
  221. package/src/tools/read_image.md +0 -8
  222. package/src/tools/repomap.mjs +0 -314
  223. package/src/tools/search.mjs +0 -236
  224. package/src/tools/shared.mjs +0 -446
  225. package/src/tools/tree.md +0 -14
  226. package/src/tools/tree.mjs +0 -66
  227. package/src/tools/wait_for.md +0 -22
  228. package/src/tools/web.mjs +0 -224
  229. package/src/tools/websearch.md +0 -16
  230. package/src/tools/write.md +0 -11
  231. package/src/traces/trace-store.mjs +0 -355
@@ -1,260 +0,0 @@
1
- /**
2
- * agent-tools/advisor.mjs — advisor tool wrapper.
3
- * The agent calls this explicitly to get an independent review.
4
- * type="design" for design doc review, type="code" for code review (default).
5
- * §11.2 (R13 — async advisor): at depth 0 the review launches into the
6
- * background pool by DEFAULT (async:true / omitted; async:false forces the
7
- * blocking review); depth>0 (eng-coder self-review) stays synchronous always.
8
- */
9
- import { runAdvisorReview, MAX_ADVISOR_ROUNDS, buildCapMessage, advisorIncompleteMarker, ADVISOR_LAUNCH_REFUSAL_PREFIX, buildDesignReviewGuardMessage } from "../advisor/run.mjs"
10
- import { resolveBatchDocPath } from "./batch-segment.mjs"
11
- import { isDocPath, loadConventions } from "../conventions.mjs"
12
- import {
13
- generateDesignToken,
14
- settleDesignReview,
15
- resolveAdvisorLaunch,
16
- launchAsyncAdvisor,
17
- stripApprovedSuffix,
18
- } from "./advisor-async.mjs"
19
- // 第 33 批(§17.5):护栏消费面——工具层预检(检查点 1)+ 同步面计数(计数点 2)。
20
- import {
21
- designReviewOutcome, designReviewStreakRecord, designReviewStreakStopped, noteDesignReviewOutcome,
22
- } from "./review-streak.mjs"
23
-
24
- // Design-token utilities moved to advisor-async.mjs (the async settle shares
25
- // them — no wrapper↔runner module cycle); validateDesignToken stays exported
26
- // here for the tests' import surface (implementation re-exported).
27
- export { validateDesignToken } from "./advisor-async.mjs"
28
-
29
- export const advisorTool = {
30
- name: "advisor",
31
- description:
32
- "Run an independent review on your work. " +
33
- "Use type='design' to review design documents before implementation — pass documents=[...] with the explicit list of doc paths to review; use documents in code review too (the task's Docs involved list). " +
34
- "Use type='code' (default) to review code changes after implementation — pass paths=[...] to specify which files or directories to review, or documents=[...] for acceptance criteria context. " +
35
- "The advisor is an independent read-only sub-agent that explores the codebase, " +
36
- "reads files, and traces callers via grep/lsp. " +
37
- "For code review: round 1 does a full review, round 2 verifies the agent's fix claims, " +
38
- "round 3+ strictly checks only the fix claims — convergence, not divergence. " +
39
- "For design review: single-pass review against methodology and requirements. " +
40
- "Review criteria come from .thincoder/advisor.md (if present) or sensible defaults. " +
41
- "After the review, you MUST produce a response table (see discipline rules for format). " +
42
- "If advisor says all clear, call verify. " +
43
- "Optionally pass object={type,target,status,reason,exclude} to anchor the review target " +
44
- "(AGENT-LOOP.md §18.8 — the review-object declaration is mechanically injected into the review message); " +
45
- "absent → legacy behavior (no injection). " +
46
- "ASYNC (AGENT-LOOP.md §11.2): at depth 0 the review runs in the BACKGROUND by default " +
47
- "(async:true or omitted) — the call returns an ack immediately, the turn ends, and the report " +
48
- "arrives automatically in a digest turn when the review finishes; background reviews share one pool — " +
49
- "at most agent.poolLimits.advisor concurrent reviews (default 4 — configurable via /config 并发池 or " +
50
- "config.json; pool-full and same-scope refusals state the current limit) — launch reviews one at a time. " +
51
- "Inside a child (depth>0 — eng-coder self-review) " +
52
- "reviews are always synchronous; async:true is rejected there. " +
53
- "Returns the review report — the advisor's findings verdict: all-clear (call verify) or a findings list to fix.",
54
- parameters: {
55
- type: "object",
56
- properties: {
57
- type: { type: "string", enum: ["code", "design"], description: "Review type: 'design' for design doc review, 'code' for code review (default)" },
58
- async: {
59
- type: "boolean",
60
- description: "Background review: default at depth 0 = true (async — ack now, report via digest); async:false forces the blocking review (mechanism parameter — top-level launches are async by default). depth>0 → always sync (async:true rejected).",
61
- },
62
- object: {
63
- type: "object",
64
- properties: {
65
- type: { type: "string", description: "Review type as declared by the caller (design/code)" },
66
- target: { type: "string", description: "Review target — document + section, or file(s)" },
67
- status: { type: "string", description: "Object state: 待评审 / 已批准 / 已实现 (pending-review / approved / implemented)" },
68
- reason: { type: "string", description: "Why this review runs: user-initiated / delivery verification" },
69
- exclude: { type: "string", description: "Explicit exclusion list — approved/implemented items NOT in this review" },
70
- },
71
- description: "Review-object declaration (§18.8): mechanically injected at the start of the review user message so the advisor does not re-derive the review target. Absent → no injection (legacy behavior).",
72
- },
73
- paths: {
74
- type: "array",
75
- items: { type: "string" },
76
- description: "Code files or directories to review (for code review). Required unless documents is provided. The advisor reads the files/directories listed here — it has no git tool and never inspects diffs.",
77
- },
78
- documents: {
79
- type: "array",
80
- items: { type: "string" },
81
- description: "Explicit list of doc paths to review (design docs, requirements docs, referenced docs). The advisor reviews ONLY these — it does NOT scan git diff. Use for both design review and code review to pass the task's Docs involved list.",
82
- },
83
- batchDoc: {
84
- type: "string",
85
- description: "Design review only: path to the batch record currently in flight. Validated WHENEVER passed (any review type) — a value that is not a readable file is refused with an error rather than ignored; for design reviews the reviewer then ALSO gets the batch_segment write channel to record its findings table + VERDICT + counts into §3 (ENGINEERING-MODE.md §2.20). Omit when no batch record is in flight — the review then runs unchanged with no write channel (zero regression).",
86
- },
87
- },
88
- },
89
- readonly: true,
90
- sideEffectExempt: true,
91
- outputPanel: true,
92
- async execute(args, ctx) {
93
- const agent = ctx.agent
94
- const reviewType = args.type || "code"
95
- const documents = args.documents || null
96
- // Review-object declaration (§18.8 D-OA3): the PARENT constructs it and the
97
- // advisor tool passes it through — mechanical anchoring, not model inference.
98
- // Any non-object value (string/array/primitive, possibly from a malformed
99
- // tool call) degrades to null = no injection (legacy calls unchanged).
100
- const reviewObject = args.object && typeof args.object === "object" && !Array.isArray(args.object)
101
- ? args.object
102
- : null
103
- // Scope fallback: the runtime mutation record (zero git) covers guard-triggered
104
- // reviews where the model did not pass explicit paths.
105
- const paths = args.paths || (agent._touchedFiles?.length ? [...agent._touchedFiles] : null)
106
-
107
- // Code review must have a scope — no implicit fallback.
108
- if (reviewType !== "design" && !paths && !documents) {
109
- if (ctx._toolCallId !== undefined) (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
110
- return "Advisor: no review scope specified. Provide paths (files/directories to review) or documents (acceptance criteria context)."
111
- }
112
-
113
- // Design review: the review scope must be documentation files. Classification
114
- // comes from the single authority (src/conventions.mjs) — the old `docs/`
115
- // prefix test is retired with it (FR12: no directory-name hardcoding in the
116
- // gate; a project whose docs live elsewhere just declares its code paths).
117
- if (reviewType === "design" && documents) {
118
- const conv = loadConventions(agent.cwd)
119
- const invalidDocs = documents.filter((doc) => !isDocPath(doc, conv))
120
- if (invalidDocs.length > 0) {
121
- if (ctx._toolCallId !== undefined) (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
122
- return `Advisor: design review documents must be documentation files (per the project's conventions). Invalid: ${invalidDocs.join(", ")}`
123
- }
124
- }
125
-
126
- // §11.2 (R13 — ruling ②-3 A): async gate. Depth-0 defaults to the
127
- // background pool; depth>0 (eng-coder internal self-review) is ALWAYS sync —
128
- // an explicit async:true there is rejected, the default never flips.
129
- const depth = ctx?.depth
130
- if (args.async === true && depth !== 0) {
131
- // 拒发登记(与 cap/池满拒同款):评审未跑——不置 called/不耗轮次(record-results
132
- // 的 REFUSED 契约——advisor 评审发现 #1:拒发不得静默满足 guard)。
133
- if (ctx._toolCallId !== undefined) (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
134
- return "Advisor: async reviews are only available at depth 0 — the top-level session owns the background pool (AGENT-LOOP.md §11.2); inside a child (eng-coder self-review) reviews run synchronously. Call advisor again without async:true (or with async:false)."
135
- }
136
- const isAsync = args.async === true || (depth === 0 && args.async !== false)
137
- // (ctx.depth undefined = direct callers/tests without a dispatch context —
138
- // legacy sync semantics.)
139
-
140
- // Per-review instance resolution (§11.2 ③ — ruling ②-5 A): fix rounds
141
- // continue the same reviewId (design = the doc-set instance's designId —
142
- // slot/spawn continuity; code = the newest OPEN instance). The resolution
143
- // scopes agent._advisorRound/_lastAdvisorOutput so the message builder and
144
- // the run.mjs cap read THIS instance's round/prior (multi-review isolation).
145
- const resolved = resolveAdvisorLaunch(agent, reviewType, { documents })
146
- // §2.20.2 评审侧批次档门禁 + 实例键绑定(batch_segment 的唯一路径来源):
147
- // 口径 = **「若传则须可读」**(空/不可读 → throw;不强制必传——无批次档的在途设计评审
148
- // 零回归,N5);绑定落在 resolved.run(评审实例键,与 reviewType/round/designId 同族)——
149
- // 并发设计评审各绑各档,不用单值会话态(§2.20.8 #6)。
150
- if (args.batchDoc !== undefined && args.batchDoc !== null) {
151
- resolved.run.batchDoc = resolveBatchDocPath(agent.cwd, args.batchDoc)
152
- }
153
- // Design token minted for EVERY design round — the reviewer echoes it only on
154
- // a clean pass; on pass it is slotted under the instance's designId at settle
155
- // (sync: right here; async: the settle callback — fix #2). A NEW instance
156
- // gets a fresh designId; a continued fix round keeps the original one — and a
157
- // same-scope re-review after a pass REUSES the session's id (F2h §29.1).
158
- const designToken = reviewType === "design" ? generateDesignToken(agent) : null
159
- const designId = reviewType === "design" ? resolved.designId : null
160
-
161
- // Cap pre-check (T-24b11 — per-review ≤5 rounds, CODE REVIEWS ONLY): a 6th
162
- // launch of a capped CODE instance is refused synchronously — the review
163
- // never starts (sync and async alike; runAdvisorReview's own cap check stays
164
- // for legacy direct callers). DESIGN reviews are EXEMPT (2026-09-07 §8
165
- // ruling): their rounds keep advancing (ROUND2/3 convergence prompts + TUI
166
- // round display) but the cap never refuses them. The refusal marks no
167
- // called/round state (guard keeps pushing only while a review can still run
168
- // — at the cap the round check stops it).
169
- if (resolved.run.reviewType !== "design" && resolved.run.round >= MAX_ADVISOR_ROUNDS) {
170
- if (ctx._toolCallId !== undefined) (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
171
- return buildCapMessage(agent)
172
- }
173
-
174
- // 第 33 批(§17.5 检查点 1——工具层预检;cap 预检邻位 / sync / async 分叉前):同一 doc-set
175
- // 连续 `MAX_DESIGN_REVIEW_STREAK` 次未产出可用结算 ⇒ 拒发(登记 `_advisorRefusals` 同 cap /
176
- // 池满款——不置 called / 不耗轮次 / **零 LLM**),返回结论串。
177
- if (reviewType === "design" && designReviewStreakStopped(agent, resolved.run.docSetKey)) {
178
- if (ctx._toolCallId !== undefined) (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
179
- return buildDesignReviewGuardMessage(designReviewStreakRecord(agent, resolved.run.docSetKey), documents ?? [])
180
- }
181
-
182
- if (isAsync) {
183
- const ack = launchAsyncAdvisor(agent, ctx, {
184
- reviewType, documents, paths, object: reviewObject,
185
- designToken, designId, run: resolved.run,
186
- })
187
- if (ack.error) {
188
- // Pool-full refusal (②-6a — no queueing): the review did NOT launch — the
189
- // model must not count it as "advisor called" (the guard keeps pushing).
190
- if (ctx._toolCallId !== undefined) (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
191
- return ack.error
192
- }
193
- // Async-ack marker: recordToolResults must NOT do the launch-time
194
- // accounting (called/round) for this call — the settle owns it.
195
- if (ctx._toolCallId !== undefined) {
196
- (agent._advisorAsyncAcks ??= new Set()).add(ctx._toolCallId)
197
- }
198
- // E(F17/§14.14 E-3c):设计评审点火回执追加冻结句(代码评审 ack 零改)——窗口下界以
199
- // 可观察信号表达:报告送达 / 取消前,被审文档(含批次档)零写入。
200
- const freezeNote = reviewType === "design"
201
- ? ";D5 冻结窗口:被审文档(含批次档)在报告送达前零写入——在途写入会被拒绝,写入将使本轮结算为陈旧 (pass 不发 token)"
202
- : ""
203
- return JSON.stringify({
204
- id: ack.id, kind: "advisor", status: "running",
205
- reviewId: resolved.reviewId,
206
- note: "评审已后台启动——完成自动回来 (review started in the background — the report arrives in a digest turn automatically; pass this id to cancel if needed)" + freezeNote,
207
- })
208
- }
209
-
210
- // Sync path (depth>0 / explicit async:false / direct callers without depth):
211
- // legacy blocking review. The design token/echo handling runs below; the
212
- // instance accounting (round++ per completed attempt) lands in
213
- // recordToolResults, marker-keyed by this tool call's id.
214
- if (ctx._toolCallId !== undefined) {
215
- (agent._advisorSyncCalls ??= new Map()).set(ctx._toolCallId, resolved.reviewId)
216
- }
217
- const result = await runAdvisorReview(agent, reviewType, {
218
- onOutput: ctx.onOutput,
219
- signal: ctx.signal,
220
- // 同步路径的实例绑定传递(异步路径由池条目 run.batchDoc 取——run.mjs 自行解析)。
221
- batchDoc: resolved.run.batchDoc ?? null,
222
- }, designToken, documents, paths, reviewObject, designId)
223
-
224
- // B 启动拒绝(§14.4 #2——稳定前缀):拒发登记(同池满 / cap 款——不置 called、不耗轮次),
225
- // 可见报错照常返回(record-results 的 REFUSED 契约)。第 33 批:判定单点——同时供设计
226
- // 失败分类复用(launchRefused ⇒ neutral——无尝试发生,§17.3 #1)。
227
- const launchRefused = String(result).startsWith(ADVISOR_LAUNCH_REFUSAL_PREFIX)
228
- if (launchRefused && ctx._toolCallId !== undefined) {
229
- (agent._advisorRefusals ??= new Set()).add(ctx._toolCallId)
230
- }
231
-
232
- if (reviewType === "design") {
233
- // Design pass/fail settlement — token echo IS the verdict (prompt-enforced);
234
- // no findings-table heuristics: a design with issues never carries the token.
235
- // (session cleanup for design reviews is owned by runAdvisorReview)
236
- // Multi-design slots (2026-09-01): store under this review's designId.
237
- // DESIGN-TOKEN-SETTLEMENT D3 (2026-09-08): the single `_engDesignToken` mirror is
238
- // retired — no mirror write here; the dispatch/spawn gates read the authoritative
239
- // multi-slot Map (+ slot-file re-read). Slotting moved into settleDesignReview
240
- // (shared with the async settle — fix #2).
241
- const incomplete = advisorIncompleteMarker(result)
242
- const settled = settleDesignReview(agent, resolved.run, designToken, result, { incomplete })
243
- // 第 33 批(§17.5 计数点 2——同步面):同分类单源落账(同步面无 stale / 无落盘步骤
244
- // ⇒ persistFailed 恒 false)。
245
- noteDesignReviewOutcome(agent, resolved.run.docSetKey, designReviewOutcome({
246
- launchRefused, stale: false, hasResult: result != null, incomplete, persistFailed: false,
247
- }))
248
- // F2e (§29.1): the sync prior mirror must not carry the raw echo the runner
249
- // stored — overwrite with the clean settled form (exact-suffix truncation).
250
- if (settled.passed) {
251
- agent._lastAdvisorOutput = stripApprovedSuffix(settled.output, resolved.run.approvedSuffix)
252
- } else if (incomplete) {
253
- // 未完成 ⇒ 同步 prior 镜像覆写为清洗后输出(防未注册 token 进 prior——§14.3)。
254
- agent._lastAdvisorOutput = settled.output
255
- }
256
- return settled.output
257
- }
258
- return result
259
- },
260
- }
@@ -1,204 +0,0 @@
1
- /**
2
- * async-settle.mjs — async 结果容器统一共享 helper(ASYNC-RESULT-CONTAINER.md D1-D6,
3
- * 2026-09-08——CLI 端落地;VS Code 同名机制各自独立实现)。根治四族 settle 记账逐字
4
- * 重复(subagent/advisor/escalate/consult)+ pending 三族分叉(_pendingEscalateResults/
5
- * _pendingConsultResults 独立族废弃)+ done-in-pool 三表示 + _sessionSignal 兜底抄 3 处
6
- * (consult 无兜底):
7
- * - D1 池 accessor:`getAsyncPool(parent, role)`——role "advisor" 返 `_asyncAdvisors`,
8
- * 其余返 `_asyncSubagents`——底层保留双池(advisor 无队列独立调度),accessor 吸收差异。
9
- * - D2 pending 单容器:全族统一 `_pendingAsyncResults`,条目带 role 字段(consult 升格
10
- * 完整 entry);`parkAsyncPending` 统一停靠(`_inPending` 标记保留防重复移交——settle
11
- * 回调与 sweepSettledToPending 同一表示,done-in-pool 统一:留池 done:true + pending
12
- * 单容器)。
13
- * - D3 settle 共享 helper:`settleAsyncEntry(parent, entry, {pool, ctx, onAccounting})`——
14
- * 公共收尾单点(日志三连(ev:cancelled / child:done|error + ev:settled)→ cancelled /
15
- * parentAborted / 挂起分流 → settleSeq/`_settle` 唤醒 waiter)。族特有段作 onAccounting
16
- * hook 注入(advisor 陈旧判定/token D1 落盘记账;escalate 三分类 merge 决策 + 腾槽补位)。
17
- * - D4 守卫统一:`parentAborted`(= `ctx.signal?.aborted || entry.controller?.signal?.
18
- * aborted`——escalate 严格版)——日志三连与挂起分流共用同一守卫。
19
- * - D6 buildChildSignal:`parent._sessionSignal ?? ctx.signal ?? null` 单点(原 3 处
20
- * 逐字抄;consult 补 _sessionSignal 兜底——D5)。
21
- * 模块图:单向 import subagent-scheduler.mjs(dependentLabels——cancelled 提醒依赖者
22
- * 列表)+ log/context/helpers/spawn-child——四族 settle 回调(subagent-run/advisor-async/
23
- * escalate-async/consult)单向 import 本模块;scheduler 反向 import 本模块(getAsyncPool)
24
- * 与既有 scheduler ↔ subagent-async 同款惰性环——无求值期依赖。
25
- */
26
- import { logEvent, errText } from "../log.mjs"
27
- import { pushReal } from "../context.mjs"
28
- import { escapeXml } from "../agent/helpers.mjs"
29
- import { TURN_CAP_MARK } from "../agent/spawn-child.mjs"
30
- import { dependentLabels, maybeRefillAsync, refreshQueuedTokens } from "./subagent-scheduler.mjs"
31
-
32
- // ─── D1 池 accessor(吸收双池)────────────────────────────────────────────────
33
-
34
- /** 池 accessor(D1):role "advisor" → `_asyncAdvisors`(独立评审池——无队列独立调度);
35
- * 其余角色 → `_asyncSubagents`(子代理/飞刀共享槽位队列池)。未初始化返 null——调用方
36
- * 以 `?? new Map()` / 可选链处置。 */
37
- export function getAsyncPool(parent, role) {
38
- if (role === "advisor") return parent?._asyncAdvisors ?? null
39
- return parent?._asyncSubagents ?? null
40
- }
41
-
42
- // ─── D2 pending 单容器(+role)───────────────────────────────────────────────
43
-
44
- /** pending 单容器停靠(settle 挂起分流 / sweep 补扫共用——统一表示:条目置 `_inPending`
45
- * 防重复移交(sweep 幂等判据),includes 去重兜底)。 */
46
- export function parkAsyncPending(parent, entry) {
47
- const pend = (parent._pendingAsyncResults ??= [])
48
- if (pend.includes(entry)) return
49
- entry._inPending = true
50
- pend.push(entry)
51
- }
52
-
53
- // ─── D4 守卫统一(严格版)+ D6 信号兜底单点 ──────────────────────────────────
54
-
55
- /** D4 settle 守卫统一(escalate 严格版):父侧中止 = 回合 signal aborted 或条目
56
- * controller aborted(取消先行——cancelled 分支在守卫之前分流,定向 cancel 不算父中止)。
57
- * 日志三连与挂起分流共用本守卫。 */
58
- export function parentAborted(ctx, entry) {
59
- return Boolean(ctx?.signal?.aborted || entry?.controller?.signal?.aborted)
60
- }
61
-
62
- /** D6 child signal 构建单点(D5——consult 补 _sessionSignal 兜底):挂起会话内的回合
63
- * (digest/用户回合)子代理持会话 signal(`parent._sessionSignal`——会话 Stop 逐链中止,
64
- * 消化轮自身 Ctrl+I/Ctrl+C 不误伤);回合级 `ctx.signal` 兜底。原 subagent/advisor/
65
- * escalate 3 处逐字抄 + consult 补上统一入此。 */
66
- export function buildChildSignal(parent, ctx) {
67
- return parent?._sessionSignal ?? ctx?.signal ?? null
68
- }
69
-
70
- // ─── D3 settle 共享 helper ───────────────────────────────────────────────────
71
-
72
- /**
73
- * §23.3.1 消化窗口持有释放(TUI-OOM-ROOTCAUSE——D-SM2 表 2 候选 1):注入完成后置空条目
74
- * 对子代理对象的引用(`childAgent`/`report`——条目前此从不显式释放,挂起期 = 分钟级驻留)。
75
- * 幂等(重入不抛);池内/挂起未消化窗口语义零变(settle 时刻不释放——表 2 候选 2 否决:
76
- * status/observe 在窗口内仍读 child 摘要)。三消费点(回合尾收集 / run 起始 pending 注入 /
77
- * 挂起残差注入)在注入完成后调本 helper。
78
- */
79
- export function releaseSettledEntry(entry) {
80
- if (!entry || typeof entry !== "object") return
81
- entry.childAgent = null
82
- entry.report = null
83
- }
84
-
85
- /**
86
- * D3 settle 共享 helper——四族 settle 回调公共收尾单点(ASYNC-RESULT-CONTAINER.md
87
- * AC1:settle 记账单点,无逐字重复)。流程:
88
- * ① 落 done/status="done"(settle 即翻——不再占槽/持文件域;report/error 由各族
89
- * 在调用前落好——subagent 的未投递注记先于本调用附加);
90
- * ② 日志三连(统一守卫 !parentAborted——cancelled → ev:cancelled;非中止 →
91
- * child:done|:error(族形态:advisor 合并单事件;escalate 撞 cap 归 partial)+
92
- * 挂起期 ev:settled;consult 族跳过——per-model 日志已在子代理 settle 时记录);
93
- * ③ 分流:cancelled(出池 + cancelled 墓碑 + ⟦ev⟧stopped + 族提醒——不入 pending)→
94
- * parentAborted(守卫抑制——不落事件不入流——中止清池在回合尾/挂起中止统一做)→
95
- * settled(族 onAccounting hook 记账 → 挂起期(或 consult 族恒停靠)移交 pending
96
- * 单容器 + 出池 + ⟦ev⟧settled;回合内 ⟦ev⟧done 留池 done:true);
97
- * ④ 公共尾部:settleSeq 递增 + `_settle` 唤醒 waiter + 腾槽补位(subagent/escalate 族
98
- * 恒补——settle/cancel 释放槽 → maybeRefillAsync + refreshQueuedTokens——AGENT-LOOP.md
99
- * §10 "settle/cancel 释放槽后…启动到槽满";advisor(独立评审池——无队列)/consult
100
- * (会话池)豁免)。
101
- * opts(设计签名 `{pool, onAccounting}` + 实现参数 ctx):
102
- * - pool:条目所在池 Map(出池 delete 目标;consult 传 null——会话池无条目)。
103
- * - ctx:回合上下文(守卫读 ctx.signal;⟦ev⟧ 事件经 ctx.callbacks.onToken;腾槽补位
104
- * 的 refreshQueuedTokens 也经 ctx.callbacks.onToken;consult 传 null——无 TUI 事件
105
- * 无守卫)。
106
- * - onAccounting(parent, entry):族特有 hook——advisor 调 settleAdvisorRun 记账(D1
107
- * 落盘保留);escalate 调 classifyEscalateSettle(三分类 merge 决策)。
108
- * 设计偏差注(advisor code review 发现——交付报告同步):设计 D3 把 maybeRefillAsync
109
- * 归入 onAccounting hook(仅 settled 分支执行)——running 取消的 cancelled 分支将不再
110
- * 补位(槽释放但 queued 头停滞——挂起会话无回合尾补位覆盖),违背既有不变式
111
- * (AGENT-LOOP.md §10 + subagent-scheduler maybeRefillAsync 文档 "called from every
112
- * settle")与 VSC 镜像(无条件 refill)。修正:腾槽补位移入公共尾部(subagent/escalate
113
- * 族恒补——旧行为零回归;advisor/consult 豁免——同 VSC refill!==false 语义)。
114
- */
115
- export function settleAsyncEntry(parent, entry, opts = {}) {
116
- const { pool = null, ctx = null, onAccounting = null } = opts
117
- const role = entry.role ?? "subagent"
118
- entry.done = true
119
- entry.status = "done"
120
- const childLogId = `${role}#${entry.id}`
121
- const childMs = entry.startedAt ? Date.now() - entry.startedAt : 0
122
- const aborted = parentAborted(ctx, entry)
123
- // ② 日志三连(D4 统一守卫——中止不落错误事件——ev:stopped 已在中止清池点表达;
124
- // 定向 cancel 走 ev:cancelled)。族形态差异(LOGGING.md):
125
- // - advisor:合并单 child:done(kind error/ok);
126
- // - escalate:撞 cap 归 child:done partial(error 且非 capPartial 才 child:error);
127
- // - 其余(subagent 族):error → child:error;done kind 按报告撞 cap 判 partial。
128
- if (role !== "consult") {
129
- if (entry.cancelled) {
130
- logEvent("ev:cancelled", { id: childLogId })
131
- } else if (!aborted) {
132
- if (role === "advisor") {
133
- const err = entry.error != null ? errText(entry.error, 200) : null
134
- logEvent("child:done", { role, id: childLogId, ms: childMs, kind: err ? "error" : "ok" })
135
- } else if (role === "escalate") {
136
- const capPartial = String(entry.error ?? entry.report ?? "").includes(TURN_CAP_MARK)
137
- if (entry.error != null && !capPartial) logEvent("child:error", { role, id: childLogId, ms: childMs, err: errText(entry.error, 200) })
138
- else logEvent("child:done", { role, id: childLogId, ms: childMs, kind: capPartial ? "partial" : "ok" })
139
- } else {
140
- if (entry.error != null) logEvent("child:error", { role, id: childLogId, ms: childMs, err: errText(entry.error, 200) })
141
- else logEvent("child:done", { role, id: childLogId, ms: childMs, kind: String(entry.report ?? "").includes(TURN_CAP_MARK) ? "partial" : "ok" })
142
- }
143
- if (parent._suspended) logEvent("ev:settled", { id: childLogId, kind: "suspended" })
144
- }
145
- }
146
- // ③ 分流
147
- if (entry.cancelled) {
148
- // §19.5 cancelled settle 分支(D-M6):不入 pending、不参与回合尾直注入——清池 +
149
- // 终态墓碑(dependsOn 取消语义)+ ⟦ev⟧stopped 冻结 + 族提醒(半成品警示对模型可见)。
150
- pool?.delete(String(entry.id))
151
- const tombstones = (parent._asyncTombstones ??= new Map())
152
- tombstones.set(String(entry.id), { status: "cancelled", role })
153
- ctx?.callbacks?.onToken?.(`${entry.relayPrefix}⟦ev⟧stopped\x1e0\x1e0\x1estopped\x1e`)
154
- if (role === "advisor") {
155
- // ②-6b:评审取消——token 未签发提醒(settleAdvisorRun 不消费预算)。
156
- pushReal(parent, {
157
- role: "user",
158
- content: `[System reminder: async advisor review #${escapeXml(String(entry.id))} cancelled — the review did not settle; token not issued (评审已取消——token 未签发)]`,
159
- })
160
- } else if (role === "escalate") {
161
- pushReal(parent, {
162
- role: "user",
163
- content: `[System reminder: async escalate #${entry.id} (${entry.tag}) cancelled by user — partial changes not merged/audited]`,
164
- })
165
- } else {
166
- // §20 D-SD5:running 依赖取消——提醒列出依赖者(供模型决策)。
167
- const dependents = dependentLabels(parent, String(entry.id))
168
- const autoNote = parent.autoApprove
169
- ? " — AUTO session: they auto-start on slot availability (round2 #3)"
170
- : " — they stay queued until you cancel them or an AUTO session starts them"
171
- pushReal(parent, {
172
- role: "user",
173
- content: `[System reminder: subagent ${escapeXml(role)}#${entry.id} cancelled by user — partial changes not merged/audited${dependents.length > 0 ? `; queued dependents ${dependents.join(", ")} marked "dependency cancelled"${autoNote}` : ""}]`,
174
- })
175
- }
176
- } else if (!aborted) {
177
- // 族记账 hook(advisor 陈旧判定/token D1 落盘;escalate 三分类 merge 决策;subagent/
178
- // escalate 腾槽补位)——可改写 entry.report(settle 分支输出——digest 原样进)。
179
- onAccounting?.(parent, entry)
180
- // 挂起分流(§17 D-S8 + D-S3 记账——以读取时刻为准):挂起期 settle → 移交 pending
181
- // 单容器(digest 注入)+ 出池 + ⟦ev⟧settled 驻留;回合内 → ⟦ev⟧done 立即冻结
182
- // (条目留池——done-in-pool 统一表示——回合尾 collectSettledAsync 注入)。
183
- // consult 族恒停靠(settle 即出会话池入 pending——无 TUI 冻结事件——子块各自冻结)。
184
- if (parent._suspended || role === "consult") {
185
- parkAsyncPending(parent, entry)
186
- pool?.delete(String(entry.id))
187
- ctx?.callbacks?.onToken?.(`${entry.relayPrefix}⟦ev⟧settled\x1e0\x1e0\x1esettled\x1e`)
188
- } else {
189
- ctx?.callbacks?.onToken?.(`${entry.relayPrefix}⟦ev⟧done\x1e0\x1e0\x1edone\x1e`)
190
- }
191
- }
192
- // ④ 公共尾部:settleSeq 递增 + _settle 唤醒(never rejects)+ 唤醒挂起驱动 waiter
193
- entry._settleSeq = (parent._asyncSettleSeq = (parent._asyncSettleSeq ?? 0) + 1)
194
- entry._settle?.()
195
- for (const w of parent._asyncWaiters?.splice(0) ?? []) { try { w() } catch { /* noop */ } }
196
- // §20 D-SD4 释放点:settle/cancel 释放槽 + 依赖终态转移 → 补位(依赖满足者/域冲突
197
- // 解除者自动启动——槽 ≤4)→ 排队态面板刷新(等待块头标注随终态更新——dependency
198
- // cancelled / 位置前移)。subagent/escalate 族恒补(旧行为零回归——cancelled 分支
199
- // 同样补位);advisor(独立评审池——无队列)/consult(会话池)豁免。
200
- if (role !== "advisor" && role !== "consult") {
201
- maybeRefillAsync(parent)
202
- refreshQueuedTokens(parent, ctx?.callbacks?.onToken)
203
- }
204
- }