thincoder 0.12.62 → 0.12.63

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 (228) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +13 -12
  3. package/bin/thincoder.mjs +53 -27
  4. package/package.json +6 -5
  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 +240 -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 +59 -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 +25 -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-reindex.mjs +1 -1
  31. package/src/tui/cmd-restore.mjs +2 -2
  32. package/src/tui/cmd-session.mjs +24 -1
  33. package/src/tui/cmd-skills.mjs +1 -1
  34. package/src/tui/cmd-think.mjs +22 -9
  35. package/src/tui/config-helpers.mjs +1 -1
  36. package/src/tui/display-budget.mjs +33 -11
  37. package/src/tui/index.mjs +9 -9
  38. package/src/tui/interaction.mjs +16 -7
  39. package/src/tui/key-modes.mjs +9 -4
  40. package/src/tui/ledger-surface.mjs +26 -10
  41. package/src/tui/model-catalog.mjs +4 -4
  42. package/src/tui/model-picker.mjs +8 -7
  43. package/src/tui/mouse.mjs +11 -6
  44. package/src/tui/pickers.mjs +15 -2
  45. package/src/tui/render-conversation.mjs +1 -1
  46. package/src/tui/render-frame.mjs +10 -5
  47. package/src/tui/render-loop.mjs +1 -1
  48. package/src/tui/render-segments.mjs +3 -1
  49. package/src/tui/slash-commands.mjs +1 -1
  50. package/src/tui/startup.mjs +14 -14
  51. package/src/tui/subagent-blocks.mjs +20 -3
  52. package/src/tui/subagent-freeze.mjs +73 -2
  53. package/src/tui/suspension-drive.mjs +46 -22
  54. package/src/tui/tool-events.mjs +11 -8
  55. package/src/tui/wizard.mjs +3 -3
  56. package/src/abort-provenance.mjs +0 -116
  57. package/src/advisor/citations.mjs +0 -139
  58. package/src/advisor/compaction.mjs +0 -174
  59. package/src/advisor/convergence.mjs +0 -80
  60. package/src/advisor/history.mjs +0 -77
  61. package/src/advisor/loop.mjs +0 -293
  62. package/src/advisor/messages.mjs +0 -299
  63. package/src/advisor/project-context.mjs +0 -194
  64. package/src/advisor/repos.mjs +0 -150
  65. package/src/advisor/run.mjs +0 -293
  66. package/src/advisor/truncate.mjs +0 -57
  67. package/src/advisor.mjs +0 -290
  68. package/src/agent/completion.mjs +0 -146
  69. package/src/agent/dispatch.mjs +0 -489
  70. package/src/agent/helpers.mjs +0 -384
  71. package/src/agent/post-turn.mjs +0 -70
  72. package/src/agent/record-results.mjs +0 -174
  73. package/src/agent/relay-prefix.mjs +0 -39
  74. package/src/agent/run-stages.mjs +0 -244
  75. package/src/agent/setup-reminders.mjs +0 -69
  76. package/src/agent/setup.mjs +0 -354
  77. package/src/agent/spawn-child.mjs +0 -243
  78. package/src/agent-tools/advisor-async.mjs +0 -346
  79. package/src/agent-tools/advisor-settle.mjs +0 -231
  80. package/src/agent-tools/advisor.mjs +0 -260
  81. package/src/agent-tools/async-settle.mjs +0 -204
  82. package/src/agent-tools/batch-segment.mjs +0 -195
  83. package/src/agent-tools/consult.mjs +0 -473
  84. package/src/agent-tools/design-token.mjs +0 -117
  85. package/src/agent-tools/digest-budget.mjs +0 -76
  86. package/src/agent-tools/eng.mjs +0 -67
  87. package/src/agent-tools/escalate-async.mjs +0 -295
  88. package/src/agent-tools/goal.mjs +0 -119
  89. package/src/agent-tools/plan.mjs +0 -81
  90. package/src/agent-tools/read-history.mjs +0 -309
  91. package/src/agent-tools/recent-changes.mjs +0 -24
  92. package/src/agent-tools/review-streak.mjs +0 -93
  93. package/src/agent-tools/settings.mjs +0 -265
  94. package/src/agent-tools/skill.mjs +0 -47
  95. package/src/agent-tools/subagent-actions.mjs +0 -482
  96. package/src/agent-tools/subagent-async.mjs +0 -434
  97. package/src/agent-tools/subagent-panel.mjs +0 -160
  98. package/src/agent-tools/subagent-run.mjs +0 -205
  99. package/src/agent-tools/subagent-scheduler.mjs +0 -392
  100. package/src/agent-tools/subagent-spawn.mjs +0 -459
  101. package/src/agent-tools/subagent.mjs +0 -404
  102. package/src/agent-tools/task.mjs +0 -87
  103. package/src/agent-tools/timer.mjs +0 -46
  104. package/src/agent-tools/verify.mjs +0 -271
  105. package/src/agent-tools.mjs +0 -17
  106. package/src/agent.mjs +0 -417
  107. package/src/auto-think.mjs +0 -115
  108. package/src/config-migrate.mjs +0 -70
  109. package/src/config.mjs +0 -496
  110. package/src/context.mjs +0 -392
  111. package/src/conventions.mjs +0 -223
  112. package/src/embedding.mjs +0 -120
  113. package/src/escape.mjs +0 -152
  114. package/src/expand-home.mjs +0 -16
  115. package/src/explore-distill.mjs +0 -155
  116. package/src/generate-title.mjs +0 -88
  117. package/src/git/checkpoint.mjs +0 -448
  118. package/src/git/gitmem.mjs +0 -100
  119. package/src/hooks.mjs +0 -97
  120. package/src/ledger.mjs +0 -227
  121. package/src/log.mjs +0 -195
  122. package/src/markdown.mjs +0 -106
  123. package/src/mcp/helpers.mjs +0 -51
  124. package/src/mcp/transport-http.mjs +0 -248
  125. package/src/mcp/transport-stdio.mjs +0 -140
  126. package/src/mcp/transport-ws.mjs +0 -122
  127. package/src/mcp.mjs +0 -295
  128. package/src/memory/code-index.mjs +0 -219
  129. package/src/memory/code-sync.mjs +0 -415
  130. package/src/memory/core.mjs +0 -299
  131. package/src/memory/delete.mjs +0 -236
  132. package/src/memory/docs.mjs +0 -419
  133. package/src/memory/file-walk.mjs +0 -109
  134. package/src/memory/scan.mjs +0 -95
  135. package/src/memory/schema.mjs +0 -452
  136. package/src/memory.mjs +0 -21
  137. package/src/model-ref.mjs +0 -66
  138. package/src/model-specs.mjs +0 -179
  139. package/src/peer-domains.mjs +0 -265
  140. package/src/peer-instances.mjs +0 -231
  141. package/src/prompt-overlays.mjs +0 -82
  142. package/src/prompts/advisor-design.md +0 -41
  143. package/src/prompts/advisor-round1.md +0 -41
  144. package/src/prompts/advisor-round2.md +0 -46
  145. package/src/prompts/advisor-round3.md +0 -42
  146. package/src/prompts/common.md +0 -115
  147. package/src/prompts/consult-base.md +0 -19
  148. package/src/prompts/discipline-engineering.md +0 -258
  149. package/src/prompts/discipline-normal.md +0 -185
  150. package/src/prompts/persona-coder.md +0 -21
  151. package/src/prompts/persona-eng-coder.md +0 -37
  152. package/src/prompts/persona-eng-designer.md +0 -60
  153. package/src/prompts/persona-engineering.md +0 -55
  154. package/src/prompts/persona-explore.md +0 -15
  155. package/src/prompts/persona-normal.md +0 -27
  156. package/src/prompts/persona-plan.md +0 -26
  157. package/src/provider/anthropic.mjs +0 -225
  158. package/src/provider/core.mjs +0 -476
  159. package/src/provider/errors.mjs +0 -101
  160. package/src/provider/google.mjs +0 -257
  161. package/src/provider/index.mjs +0 -7
  162. package/src/provider/list-models.mjs +0 -93
  163. package/src/provider/normalize.mjs +0 -81
  164. package/src/provider/rate.mjs +0 -108
  165. package/src/provider/responses.mjs +0 -495
  166. package/src/provider/retry.mjs +0 -88
  167. package/src/provider/sse.mjs +0 -264
  168. package/src/proxy.mjs +0 -261
  169. package/src/rules.mjs +0 -53
  170. package/src/session-gc.mjs +0 -221
  171. package/src/session-guard.mjs +0 -59
  172. package/src/session-migrate.mjs +0 -48
  173. package/src/session-rename.mjs +0 -38
  174. package/src/session-segments.mjs +0 -100
  175. package/src/session-slots.mjs +0 -492
  176. package/src/session-store.mjs +0 -441
  177. package/src/session.mjs +0 -492
  178. package/src/skills.mjs +0 -153
  179. package/src/text-budget.mjs +0 -46
  180. package/src/token-ttl.mjs +0 -274
  181. package/src/tools/apply_patch.md +0 -15
  182. package/src/tools/bash.md +0 -37
  183. package/src/tools/bash.mjs +0 -268
  184. package/src/tools/checklist-sync.mjs +0 -181
  185. package/src/tools/checklist.md +0 -13
  186. package/src/tools/checklist.mjs +0 -299
  187. package/src/tools/delete.md +0 -13
  188. package/src/tools/edit-batch.mjs +0 -191
  189. package/src/tools/edit-diff.mjs +0 -348
  190. package/src/tools/edit.md +0 -30
  191. package/src/tools/execute.md +0 -21
  192. package/src/tools/execute.mjs +0 -228
  193. package/src/tools/fetch.md +0 -12
  194. package/src/tools/file.mjs +0 -469
  195. package/src/tools/file_ops.md +0 -17
  196. package/src/tools/get_current_time.md +0 -8
  197. package/src/tools/git-checkpoint.mjs +0 -143
  198. package/src/tools/git-ext.mjs +0 -173
  199. package/src/tools/git.md +0 -54
  200. package/src/tools/git.mjs +0 -356
  201. package/src/tools/glob-dialect.mjs +0 -130
  202. package/src/tools/glob.md +0 -11
  203. package/src/tools/grep.md +0 -19
  204. package/src/tools/hashline_edit.md +0 -14
  205. package/src/tools/index.mjs +0 -36
  206. package/src/tools/insert_after.md +0 -15
  207. package/src/tools/lint.md +0 -10
  208. package/src/tools/linter.mjs +0 -128
  209. package/src/tools/ls.md +0 -12
  210. package/src/tools/lsp.md +0 -10
  211. package/src/tools/lsp.mjs +0 -316
  212. package/src/tools/ops.mjs +0 -299
  213. package/src/tools/patch.mjs +0 -282
  214. package/src/tools/process.md +0 -10
  215. package/src/tools/question.md +0 -16
  216. package/src/tools/question.mjs +0 -26
  217. package/src/tools/read.md +0 -20
  218. package/src/tools/read_image.md +0 -8
  219. package/src/tools/repomap.mjs +0 -314
  220. package/src/tools/search.mjs +0 -236
  221. package/src/tools/shared.mjs +0 -446
  222. package/src/tools/tree.md +0 -14
  223. package/src/tools/tree.mjs +0 -66
  224. package/src/tools/wait_for.md +0 -22
  225. package/src/tools/web.mjs +0 -224
  226. package/src/tools/websearch.md +0 -16
  227. package/src/tools/write.md +0 -11
  228. package/src/traces/trace-store.mjs +0 -355
@@ -1,57 +0,0 @@
1
- /**
2
- * truncate.mjs — advisor 工具结果的行感知双端截断(DUAL-END-TRUNCATION F-2,
3
- * DUAL-END-TRUNCATION.md,2026-09-09——CLI/VSC 双端逐字同构镜像)。
4
- *
5
- * 原 run.mjs 内联截断为纯头向(line-aware 从首行累加至 64K break)——评审尾部
6
- * 结论/裁决被切。现改头尾双保:头行累加至预算 ~60% → 中段省略注 → 尾行累加至
7
- * 剩余 ~40%(保尾结论——与 read 工具 C 方案 / offload 双端预览同构的
8
- * "头+尾保留、中段截断"策略——共享设计语言、分别实现)。
9
- *
10
- * 纯函数无导入:maxChars 由调用方传入(run.mjs 传 MAX_RESULT_CHARS)。
11
- */
12
-
13
- /** advisor 截断头占比——头行累加至此份额后切中段,尾自动取剩余预算(设计 §4 ≈0.6)。 */
14
- export const ADVISOR_HEAD_RATIO = 0.6
15
-
16
- /** 尾部预算预留(字符):中段省略注 + offset 续读提示的保守预留——注的位数只减不增
17
- * (middle ≤ totalChars——超大结果位数可达 ~10 位——200 字符仍宽松覆盖注+提示 ≤ ~140;
18
- * 差额留给尾行)。 */
19
- const NOTE_RESERVE_CHARS = 200
20
-
21
- /**
22
- * 行感知双端截断:result > maxChars → 头 + `… (truncated: K more lines, TOTAL chars
23
- * total)` + 尾 + offset 续读提示;≤ maxChars 原样返回(无假截断注)。K = 头尾之间
24
- * 的中段行数——头(≤60%) + 尾(≤余预算) 均行级累加、绝不半行切开;中段存在时 K ≥ 1
25
- * (防御:K ≤ 0 时静默返回头尾拼合——不谎报截断)。
26
- */
27
- export function truncateAdvisorResult(result, maxChars) {
28
- if (result.length <= maxChars) return result
29
- const totalChars = result.length
30
- const lines = result.split("\n")
31
- const headBudget = Math.floor(maxChars * ADVISOR_HEAD_RATIO)
32
- let head = ""
33
- let headLines = 0
34
- let chars = 0
35
- while (headLines < lines.length && chars + lines[headLines].length + 1 <= headBudget) {
36
- chars += lines[headLines].length + 1
37
- head += lines[headLines] + "\n"
38
- headLines++
39
- }
40
- const tailBudget = Math.max(0, maxChars - chars - NOTE_RESERVE_CHARS)
41
- let tail = ""
42
- let tailChars = 0
43
- let tailStart = lines.length
44
- while (tailStart > headLines && tailChars + lines[tailStart - 1].length + 1 <= tailBudget) {
45
- tailChars += lines[tailStart - 1].length + 1
46
- tailStart--
47
- tail = lines[tailStart] + "\n" + tail
48
- }
49
- const middle = tailStart - headLines
50
- if (middle <= 0) return head + tail // 头尾已覆盖全部行——绝不产生假截断注
51
- return (
52
- head +
53
- `\n… (truncated: ${middle} more lines, ${totalChars} chars total)\n` +
54
- tail +
55
- `To see more content, use: read(path, offset=${headLines + 1}, limit=200)`
56
- )
57
- }
package/src/advisor.mjs DELETED
@@ -1,290 +0,0 @@
1
- /**
2
- * advisor.mjs — advisor system-prompt selection, follow-up building, session assembly.
3
- * User-message building lives in advisor/messages.mjs; execution (tool loop, provider
4
- * resolution, review entry) in advisor/run.mjs; history extraction in advisor/history.mjs.
5
- * Path classification (code / doc / temp) and the project declaration surface live in
6
- * ../conventions.mjs — the single authority every gate consumes (repos.mjs included).
7
- *
8
- * The advisor runs as a read-only exploration sub-agent with tools
9
- * (read, glob, grep, ls, lsp, code_search) — ZERO git, every round. The change
10
- * surface comes from the review scope (paths / _touchedFiles), never from git;
11
- * verification is `read`-only with quoted-line evidence (7d49a52, d3be613).
12
- *
13
- * Config:
14
- * { advisor: { enabled: true, provider: "deepseek", model: "deepseek-chat" } }
15
- * provider + model are optional — defaults to the main agent's provider/model.
16
- *
17
- * Convergence protocol:
18
- * Round 1: full review → produces a numbered issue table.
19
- * Agent responds with a response table per issue (fix claims).
20
- * Round 2: semi-convergence — verifies the prior table + can flag obvious new issues.
21
- * Round 3+: strict convergence — only checks the prior issue table.
22
- * The prior issue table IS injected into rounds 2+ (decision 2026-08-05,
23
- * reversed) — it is the ONLY complete verification list: the agent response
24
- * table covers only issues the agent chose to answer, so skipped issues would
25
- * silently escape convergence without it. The fix-claim table travels as a
26
- * focus reference only. Restatement risk is handled mechanically:
27
- * host-verified citations reject references that do not match the current
28
- * disk state, and fresh sessions exclude old read data.
29
- * Each round replaces the system prompt (ROUND1 → ROUND2 → ROUND3) so the
30
- * round-1 full-scope mandate can't bleed into later rounds, plus a mechanical
31
- * cap (MAX_ADVISOR_ROUNDS in run.mjs) refuses a 6th review call outright.
32
- * Rounds 2+ also declare all earlier diffs STALE and require read-verified
33
- * file:line evidence for any unfixed/new finding — see docs/design/ADVISOR-CONVERGENCE.md.
34
- *
35
- * Session memory (agent._advisorSession):
36
- * RETAINED for initialization compatibility but NEVER read (decision d698434):
37
- * every review round builds a fresh [system, user] session — round 2+ must not
38
- * reuse round 1's messages, because the old read outputs are the anchoring
39
- * source of re-review false reports and a token sink. Convergence data (prior
40
- * issue table + agent response table) travels via buildAdvisorFollowUp.
41
- * The field is reset by runAgent; the write sites are harmless leftovers.
42
- *
43
- * Project customisation: .thincoder/advisor.md in the project root.
44
- */
45
- import { readFileSync } from "node:fs"
46
- import { join, dirname } from "node:path"
47
- import { fileURLToPath } from "node:url"
48
- import { extractAgentResponseTable } from "./advisor/history.mjs"
49
- import { buildAdvisorUserMessage, resolveScopeFiles, buildObjectDeclarationBlock, buildDesignApprovalBlock } from "./advisor/messages.mjs"
50
- import { buildConvergenceBody } from "./advisor/convergence.mjs"
51
- import { escapeLiteralEscapes } from "./escape.mjs"
52
- // Re-export for run.mjs and tests (keeps their imports from "../advisor.mjs" stable)
53
- export { ADVISOR_MD_PATH, extractAgentResponseTable, extractConversationBackground } from "./advisor/history.mjs"
54
- export { buildAdvisorUserMessage } from "./advisor/messages.mjs"
55
- export { buildObjectDeclarationBlock, buildDesignApprovalBlock } from "./advisor/messages.mjs"
56
-
57
- const __dirname = dirname(fileURLToPath(import.meta.url))
58
-
59
- // ────────────────────────────────────────
60
- // Prompt files — loaded at module init
61
- // ────────────────────────────────────────
62
-
63
- function loadPrompt(file, name) {
64
- try {
65
- return readFileSync(join(__dirname, "prompts", file), "utf8")
66
- } catch {
67
- throw new Error(`${name} missing from the installation (prompts/${file}) — reinstall thincoder or restore the file`)
68
- }
69
- }
70
-
71
- const ADVISOR_ROUND1 = loadPrompt("advisor-round1.md", "advisor-round1.md")
72
- // ROUND2/3 are used whenever a convergence round (round 2+) is being built:
73
- // in-run session continuation replaces the system prompt with them, and a
74
- // rebuilt fresh session (e.g. after a failed review) also selects them via
75
- // buildAdvisorSystemPrompt when _advisorRound > 0.
76
- const ADVISOR_ROUND2 = loadPrompt("advisor-round2.md", "advisor-round2.md")
77
- const ADVISOR_ROUND3 = loadPrompt("advisor-round3.md", "advisor-round3.md")
78
- // Design-review prompt — hard-loaded like the round prompts (decision
79
- // 2026-08-21): a missing file means a broken installation, and silently
80
- // degrading to a lesser in-code prompt would quietly strip the approval-signal
81
- // and citation rules, disabling design approval entirely. loadPrompt throws.
82
- const ADVISOR_DESIGN = loadPrompt("advisor-design.md", "advisor-design.md")
83
-
84
- // ────────────────────────────────────────
85
- // System prompt building
86
- // ────────────────────────────────────────
87
-
88
- /**
89
- * Build the system prompt for an advisor review session.
90
- * @param {Object} agent — the parent agent
91
- * @param {Object|null} [prior] — prior review output (full text; decision 2026-08-08)
92
- * @param {string} [reviewType] — "design" for design review, undefined/"code" for code review
93
- * @returns {string} the system prompt
94
- */
95
- /** Append local time so the reviewer knows "now" (same grounding as the agent loop). */
96
- function withTime(prompt) {
97
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "local"
98
- return prompt + `\n\nCurrent time: ${new Date().toLocaleString("sv-SE")} (${timeZone}).`
99
- }
100
-
101
- export function buildAdvisorSystemPrompt(agent, prior, reviewType) {
102
- // Round decision is DETERMINISTIC (decision 2026-08-08): _advisorRound > 0
103
- // with a stored review output means convergence (round 2+); 0 means round 1.
104
- // No prior-table parsing, no all-clear phrase matching — the round counter
105
- // and the stored output are the only inputs. A restarted process has
106
- // _advisorRound 0 → conservative full re-review.
107
- const hasPrior = (agent._advisorRound || 0) > 0 && (prior ?? agent._lastAdvisorOutput)
108
- // Design review: round 1 uses the dedicated design-review prompt (full scope +
109
- // approval token); rounds 2+ converge like code reviews (verify agent fix claims).
110
- if (reviewType === "design") {
111
- if (!hasPrior) {
112
- return ADVISOR_DESIGN
113
- }
114
- const round = (agent._advisorRound || 0) + 1
115
- if (round === 2) return ADVISOR_ROUND2
116
- return ADVISOR_ROUND3
117
- }
118
- if (!hasPrior) return ADVISOR_ROUND1
119
- const round = (agent._advisorRound || 0) + 1
120
- if (round === 2) return ADVISOR_ROUND2
121
- return ADVISOR_ROUND3
122
- }
123
-
124
- // ────────────────────────────────────────
125
- // Follow-up building (round 2+)
126
- // ────────────────────────────────────────
127
-
128
- /**
129
- * Build a follow-up user message for round 2+ — the agent's response table +
130
- * round-aware instructions, without re-sending the full round-1 context.
131
- * Deliberately NO git information injected (no diff snapshot, no git context):
132
- * git output misled re-reviews — committed fixes never show in `git diff HEAD`,
133
- * so the model read "no changes" as "no fixes". Verification is `read`-only.
134
- * NOTE: the caller (prepareAdvisorMessages) applies escapeLiteralEscapes to
135
- * the return value — direct callers must do the same (the prior table and
136
- * agent response can quote literal "\x"/"\u" sequences).
137
- * @param {Object} agent — the parent agent (history used for the response table)
138
- * @param {Object|null} prior — prior issue table (extracted from history when null)
139
- * @param {string[]|null} [scopeFiles] — review surface for the no-response fallback (cwd-relative)
140
- * @param {Object|null} [object] — review-object declaration (§18.8): mechanically
141
- * prepended to the round-2+ follow-up so every round stays anchored (T-OA2).
142
- * @returns {string} the follow-up user message — or a plain "System reminder: …"
143
- * fresh-review fallback (NO brackets — some OpenAI-compatible servers parse
144
- * '['-prefixed content as structured data / expand escapes) when no prior
145
- * review exists at all (caller misuse; the response-table extraction would
146
- * otherwise scan history from index 0 and could match an unrelated stale table)
147
- */
148
- export function buildAdvisorFollowUp(agent, prior, scopeFiles = null, object = null) {
149
- // Convergence follow-up REQUIRES a prior review record — the full output of
150
- // the last review, injected VERBATIM (decision 2026-08-08: the model
151
- // understands the review output; no table/header/phrase parsing). The caller
152
- // usually passes it; fall back to the stored agent._lastAdvisorOutput.
153
- const p = prior ?? agent._lastAdvisorOutput
154
- if (!p) {
155
- // Plain "System reminder:" prefix (no brackets) — same convention as the
156
- // round-1 path (some OpenAI-compatible servers parse '['-prefixed content
157
- // as structured data / expand escapes; see prepareAdvisorMessages).
158
- return "System reminder: convergence follow-up requested without a prior review — perform a fresh full review."
159
- }
160
- // Convergence semantics require round >= 2 (round 1 is the full review, not
161
- // verification). A direct caller with _advisorRound 0 would otherwise get a
162
- // meaningless "Round 1 — Strict Verification".
163
- if ((agent._advisorRound || 0) < 1) {
164
- return "System reminder: convergence follow-up requested at round 1 — a full review is already in progress; no prior verification exists yet."
165
- }
166
- const noResponseFallback = scopeFiles?.length
167
- ? "(Agent did not provide a response table — perform a fresh review of: " + scopeFiles.slice(0, 10).join(", ") + ")"
168
- : "(Agent did not provide a response table — perform a fresh full review; the review surface is unknown, ask the user for the file list)"
169
- const response = extractAgentResponseTable(agent.history) || noResponseFallback
170
- const round = (agent._advisorRound || 0) + 1
171
- // Review-object declaration FIRST (T-OA2 — round 2+ stays anchored, no re-archaeology).
172
- const declaration = buildObjectDeclarationBlock(object)
173
- return (declaration ? declaration + "\n" : "") + buildConvergenceBody(p, response, round, scopeFiles)
174
- }
175
-
176
- /**
177
- * Resolve the review surface for the convergence fallback — moved to
178
- * messages.mjs so the legacy path shares it (see there).
179
- */
180
-
181
- // escapeLiteralEscapes 已抽到 ./escape.mjs(advisor 与主 agent 发送路径共用),
182
- // 这里 re-export 保持既有 import 稳定。
183
- export { escapeLiteralEscapes }
184
-
185
-
186
- /**
187
- * Build the advisor conversation for this run.
188
- * EVERY call builds a fresh [system, user] session (decision d698434) — no
189
- * session reuse across rounds: round 1 = full scope (ROUND1 prompt), rounds
190
- * 2+ = convergence (ROUND2/ROUND3 prompt + fix-claims follow-up).
191
- * @param {Object} agent — the parent agent
192
- * @param {string} [reviewType] — "design" or "code" (default)
193
- * @param {string|null} [designToken] — design-review approval token (design only)
194
- * @param {string[]|null} [documents] — design review only: explicit list of doc paths to review (passed through to buildAdvisorUserMessage)
195
- * @param {string[]|null} [paths] — code review only: explicit list of file/dir paths to review
196
- * @param {Object|null} [object] — review-object declaration (§18.8 D-OA1): passed through
197
- * to the user-message builders; mechanically injected at the start of every review
198
- * round (round 1 design/code + round 2+ follow-up). Absent → legacy behavior.
199
- * @param {string|null} [designId] — §29.1 F2a: injected next to the design token in
200
- * the Approval Signal (round 1 + round 2+ — both values, verbatim anchor).
201
- */
202
- export function prepareAdvisorMessages(agent, reviewType, designToken = null, documents = null, paths = null, priorParam = null, object = null, designId = null) {
203
- // Deterministic convergence state (decision 2026-08-08): round 2+ requires
204
- // _advisorRound > 0 AND a stored prior review output. No history parsing.
205
- // priorParam (direct callers) wins over the stored output — same derivation
206
- // as buildAdvisorSystemPrompt (single source of truth for round semantics).
207
- const prior = (agent._advisorRound || 0) > 0 ? (priorParam ?? agent._lastAdvisorOutput) : null
208
-
209
- // Design review round 1: the dedicated full-scope review with the approval
210
- // token (an independent gate — it runs even when a prior review exists, e.g.
211
- // after a failed design review). Fresh session.
212
- if (reviewType === "design" && (agent._advisorRound || 0) === 0) {
213
- return [
214
- { role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
215
- { role: "user", content: escapeLiteralEscapes(buildAdvisorUserMessage(agent, prior, reviewType, designToken, documents, paths, object, designId)) },
216
- ]
217
- }
218
-
219
- // Every round is a FRESH session (decision d698434): round 2+ must NOT reuse
220
- // round 1's messages — the old read outputs are the top anchoring source of
221
- // re-review false reports (the model quoted pre-fix file content instead of
222
- // re-reading) and a token sink. The agent response table (fix claims) is
223
- // injected through buildAdvisorFollowUp instead; the system prompt carries
224
- // the round (ROUND2/ROUND3) via buildAdvisorSystemPrompt.
225
- // No prior review output: reset ONLY when this run made no code changes (user
226
- // decision 2026-08-05: any loop that modified code must NOT reset — the
227
- // advisor guard WILL push back, so the convergence round must keep advancing
228
- // toward the cap; a run with no mutations has no push-back risk and a reset
229
- // is safe). Deterministic runtime state (`_mutatedThisRun`) decides — never
230
- // model output (phrases/table headers drift; three rounds of false reports
231
- // proved it). Either way the message is a fresh full review (no prior output
232
- // exists without a completed review) — only the round counter differs.
233
- if (!prior || (agent._advisorRound || 0) === 0) {
234
- if (!(agent._mutatedThisRun ?? false)) {
235
- // New review cycle (first review, all-clear, or no code changes): reset
236
- // the round so the cycle gets its own 5-round budget.
237
- agent._advisorRound = 0
238
- }
239
- // Mutations exist → KEEP the round (cap keeps advancing through retries).
240
- const user = buildAdvisorUserMessage(agent, prior, reviewType, designToken, documents, paths, object, designId)
241
- return [
242
- { role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
243
- {
244
- role: "user",
245
- // NOTE (2026-08-06): the leading prefix is a PLAIN "System reminder:",
246
- // NOT "[System reminder: ...]" — some OpenAI-compatible servers try to
247
- // parse content that STARTS with '[' as structured content (or expand
248
- // escape sequences in it). A literal "\x" inside the conversation
249
- // background (e.g. the parent agent quoting escape sequences) then
250
- // fails server-side as "unexpected end of hex escape" → 400. Plain
251
- // prefix keeps the review message a plain string everywhere.
252
- // The whole content also passes through escapeLiteralEscapes (below)
253
- // so literal "\x"/"\u" quoted by the parent agent can never form an
254
- // invalid escape when the server expands them.
255
- content: escapeLiteralEscapes(`System reminder: no prior issue table is being carried into this review (first review, app restart, or session clear) — start with a fresh full review.\n\n${user}`),
256
- },
257
- ]
258
- }
259
-
260
- // Convergence rounds (2+): fresh [system(ROUND2/3), user(prior table + fix
261
- // claims)]. buildAdvisorFollowUp carries BOTH the prior issue table (the
262
- // only complete verification list — decision 2026-08-05, reversed) and the
263
- // agent's fix-claim table (focus reference). buildAdvisorSystemPrompt
264
- // selects ROUND2 for round 2, ROUND3 for rounds 3+ — a failed review retry
265
- // keeps _advisorRound so the convergence prompt matches the attempt count.
266
- // scopeFiles gives the fallback (agent gave no response table) a concrete
267
- // review surface.
268
- const scopeFiles = resolveScopeFiles(agent, paths)
269
- const followUp = buildAdvisorFollowUp(agent, prior, scopeFiles, object)
270
- // §11.2 D-24b (design round 2+ — async fix-round continuations must be able to
271
- // re-approve): re-anchor the review scope (the convergence follow-up carries no
272
- // document list) and inject the round's design token with the approval signal.
273
- if (reviewType === "design") {
274
- const docList = Array.isArray(documents)
275
- ? documents.filter((d) => typeof d === "string" && d.trim())
276
- : []
277
- const scopeBlock = docList.length > 0
278
- ? `\n\n## Documents to Review\nThe documents below are the review scope. Review ONLY these files — do not scan git diff or read any other files.\n${docList.map((d) => `- ${d} — Read this file in full`).join("\n")}`
279
- : ""
280
- const tokenBlock = designToken ? `\n\n${buildDesignApprovalBlock(designToken, designId)}` : ""
281
- return [
282
- { role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
283
- { role: "user", content: escapeLiteralEscapes(followUp + scopeBlock + tokenBlock) },
284
- ]
285
- }
286
- return [
287
- { role: "system", content: withTime(buildAdvisorSystemPrompt(agent, prior, reviewType)) },
288
- { role: "user", content: escapeLiteralEscapes(followUp) },
289
- ]
290
- }
@@ -1,146 +0,0 @@
1
- /**
2
- * agent/completion.mjs — handle model response with no tool calls
3
- *
4
- * Checks: pending tasks, verify guard, advisor guard.
5
- * Returns { action: 'continue' | 'done', content?, guardPushbacks, honestReminderInjected, advisorPushbacks }
6
- */
7
- import { hasCodeMutations } from "../advisor/repos.mjs"
8
- import { pushReal } from "../context.mjs"
9
- import { MAX_ADVISOR_ROUNDS } from "../advisor/run.mjs"
10
- import { advisorReviewPending, effectiveAdvisorRound } from "../agent-tools/advisor-async.mjs"
11
-
12
- const MAX_VERIFY_PUSHBACKS = 2
13
- const MAX_VERIFY_RETRIES = 3
14
- const MAX_ADVISOR_PUSHBACKS = 3
15
- const MAX_EMPTY_RETRIES = 2
16
-
17
- /**
18
- * Handle a model turn with zero tool calls. May push back (verify/advisor/pending tasks)
19
- * or accept the completion.
20
- *
21
- * @param {object} agent
22
- * @param {object} response - chat response with .content, .toolCalls
23
- * @param {number} depth - agent nesting depth (0 = top-level)
24
- * @param {number} turn - current turn index
25
- * @param {number} guardPushbacks - verify guard pushback count (mutated)
26
- * @param {boolean} honestReminderInjected - whether exhausted-verify reminder was already sent (mutated)
27
- * @param {number} advisorPushbacks - advisor guard pushback count (mutated)
28
- * @param {object} callbacks - { onTurnEnd }
29
- */
30
- export function handleCompletion(agent, response, depth, turn, guardPushbacks, honestReminderInjected, advisorPushbacks, callbacks) {
31
- if (!response.content) {
32
- // Transient empty response (reasoning exhausted / output truncated): instead of
33
- // aborting the whole turn, inject a reminder and let the model respond again.
34
- // Bounded — after MAX_EMPTY_RETRIES consecutive empties, surface the original error.
35
- const retries = agent._emptyRetries ?? 0
36
- if (retries < MAX_EMPTY_RETRIES) {
37
- agent._emptyRetries = retries + 1
38
- agent.history.push({
39
- role: "user",
40
- content: "[System reminder: your last response was empty — the provider returned no content (likely reasoning was exhausted or output was truncated). Respond again, continuing your work from where you left off.]",
41
- })
42
- callbacks.onTurnEnd?.(agent, turn)
43
- return { action: "continue", guardPushbacks, honestReminderInjected, advisorPushbacks }
44
- }
45
- throw new Error(
46
- "LLM returned empty response (likely reasoning exhausted or output truncated). " +
47
- "Try lowering reasoning effort if this persists (/think in TUI). " +
48
- `Provider: ${agent.provider.model}`
49
- )
50
- }
51
-
52
- // Pending tasks: remind the model ONCE before it declares itself done.
53
- // Deliberately capped at one pushback (reported pain: unbounded looping when a
54
- // pending item can't be resolved). After the single reminder the model is free
55
- // to finish — updating the task list (task tool) resets the budget, so a fresh
56
- // list state earns one fresh reminder.
57
- if (depth === 0 && agent.tasks.some((t) => t.status === "pending") && (agent._taskPushbacks ?? 0) < 1) {
58
- agent._taskPushbacks = (agent._taskPushbacks ?? 0) + 1
59
- const pending = agent.tasks.filter((t) => t.status === "pending").map((t) => t.title).join(", ")
60
- pushReal(agent, { role: "assistant", content: response.content })
61
- agent.history.push({
62
- role: "user",
63
- content: `[System reminder: you still have pending tasks: ${pending}. Update their status with the task tool before finishing — if they're done, mark them done; if they're not applicable, remove them. (This is your only reminder — if you choose not to, finish anyway.)]`,
64
- })
65
- callbacks.onTurnEnd?.(agent, turn)
66
- return { action: "continue", guardPushbacks, honestReminderInjected, advisorPushbacks }
67
- }
68
-
69
- // --- verify guard: push model to verify mutated files before completion ---
70
- // OPT-IN ONLY (verifyGuard: true). Engineering mode is excluded because it
71
- // uses flow-driven review, not per-turn mechanical pushback (ENGINEERING-MODE.md §2.3).
72
- // Backward compat: also accept root-level verifyGuard
73
- const verifyGuard = agent.config?.agent?.verifyGuard ?? agent.config?.verifyGuard
74
- if (depth === 0 && verifyGuard === true && !agent.config?.agent?.engineering) {
75
- // Not verified yet → pushback to run verify
76
- if (agent._mutatedThisRun && !agent._verifiedThisRun && hasCodeMutations(agent) && guardPushbacks < MAX_VERIFY_PUSHBACKS) {
77
- guardPushbacks++
78
- pushReal(agent, { role: "assistant", content: response.content })
79
- agent.history.push({
80
- role: "user",
81
- content: "[System reminder: you modified files in this run but have not verified the changes. Before finishing: run the project's verification yourself (per its AGENTS.md test method), then call verify declaring the outcome via verification.status. verify mechanically gates on your declaration. If verification is genuinely impossible here, say so explicitly in your reply.]",
82
- })
83
- callbacks.onTurnEnd?.(agent, turn)
84
- return { action: "continue", guardPushbacks, honestReminderInjected, advisorPushbacks }
85
- }
86
- // Verified but not passed → pushback to fix/complete (up to MAX_VERIFY_RETRIES).
87
- // _verifyPassed === false means the declaration was failed, skipped without a
88
- // reason, or not declared — not necessarily that a test failed.
89
- if (agent._verifiedThisRun && agent._verifyPassed === false && agent._verifyRetries < MAX_VERIFY_RETRIES) {
90
- agent._verifyRetries++
91
- pushReal(agent, { role: "assistant", content: response.content })
92
- agent.history.push({
93
- role: "user",
94
- content: `[System reminder: (retry ${agent._verifyRetries}/${MAX_VERIFY_RETRIES}) verify was not passed — either your verification declared failed, was skipped without a reason, or was not declared. Fix or complete your verification, then call verify again declaring the outcome. If you cannot fix after ${MAX_VERIFY_RETRIES} attempts, explain honestly what's blocking you.]`,
95
- })
96
- callbacks.onTurnEnd?.(agent, turn)
97
- return { action: "continue", guardPushbacks, honestReminderInjected, advisorPushbacks }
98
- }
99
- // Exhausted retries — inject honesty reminder once
100
- if (agent._verifiedThisRun && agent._verifyPassed === false && agent._verifyRetries >= MAX_VERIFY_RETRIES) {
101
- if (honestReminderInjected) {
102
- pushReal(agent, { role: "assistant", content: response.content })
103
- return { action: "done", content: response.content, guardPushbacks, honestReminderInjected, advisorPushbacks }
104
- }
105
- honestReminderInjected = true
106
- pushReal(agent, { role: "assistant", content: response.content })
107
- agent.history.push({
108
- role: "user",
109
- content: `[System reminder: ${MAX_VERIFY_RETRIES} verify attempts exhausted. You have not passed verification. Either state explicitly that your verification could not be completed, or run verify again once it is. If your verification could not be completed, say so explicitly in your reply to the user — state what you tried and what you believe is blocking you, and do not present the work as complete; the user needs to know it is unfinished.]`,
110
- })
111
- callbacks.onTurnEnd?.(agent, turn)
112
- return { action: "continue", guardPushbacks, honestReminderInjected, advisorPushbacks }
113
- }
114
- }
115
-
116
- // --- advisor guard: review of mutated files before completion ---
117
- // OPT-IN ONLY (advisor.guard === true, default OFF — 2026-08-21 semantic
118
- // refactor), and NEVER in engineering mode. The advisor tool itself is always
119
- // available; this guard only controls whether completion is pushed back.
120
- const cfg = agent.config?.advisor
121
- const advisorReview = cfg?.guard === true
122
- if (depth === 0 && advisorReview && !agent.config?.agent?.engineering) {
123
- // §11.2 D-24b (T-24b4 — guard timing): an async review that is still in flight
124
- // (or queued in the advisor pool) means the review was launched — the guard
125
- // does NOT push back while it is pending (未决不算未评审); once it settles
126
- // non-stale it marks _calledAdvisorThisRun, and a STALE settle leaves the
127
- // mark unset so the guard pushes back here again (fix #2 — no silent skip).
128
- const pending = advisorReviewPending(agent)
129
- const rounds = effectiveAdvisorRound(agent)
130
- if (!pending && agent._mutatedThisRun && !agent._calledAdvisorThisRun && hasCodeMutations(agent)
131
- && advisorPushbacks < MAX_ADVISOR_PUSHBACKS
132
- && rounds < MAX_ADVISOR_ROUNDS) {
133
- advisorPushbacks++
134
- pushReal(agent, { role: "assistant", content: response.content })
135
- agent.history.push({
136
- role: "user",
137
- content: `[System reminder: you changed code in this run and MUST get an advisor review before finishing (round ${rounds + 1}). Call the \`advisor\` tool now. This is required, not optional — do not skip it even if you believe the changes are trivial — the review will be quick either way. After the review, produce a response table for every issue found (see discipline rules for format).]`,
138
- })
139
- callbacks.onTurnEnd?.(agent, turn)
140
- return { action: "continue", guardPushbacks, honestReminderInjected, advisorPushbacks }
141
- }
142
- }
143
-
144
- pushReal(agent, { role: "assistant", content: response.content })
145
- return { action: "done", content: response.content, guardPushbacks, honestReminderInjected, advisorPushbacks }
146
- }