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
package/src/agent.mjs DELETED
@@ -1,417 +0,0 @@
1
- /**
2
- * agent.mjs — Agent main loop
3
- * LLM ↔ tool-call loop, until the task is done.
4
- */
5
- import { chat } from "./provider/index.mjs"
6
- import { abortError, annotateAbort } from "./abort-provenance.mjs"
7
- import { pushReal, summarizeRunExplorations } from "./context.mjs"
8
- import { specForModel } from "./config.mjs"
9
- import { resolve } from "node:path"
10
- import { executeToolCalls } from "./agent/dispatch.mjs"
11
- import { recordToolResults } from "./agent/record-results.mjs"
12
- import { FILE_MUTATORS } from "./agent/helpers.mjs"
13
- import { prepareRun } from "./agent/setup.mjs"
14
- import { injectPostTurn } from "./agent/post-turn.mjs"
15
- import { handleCompletion } from "./agent/completion.mjs"
16
- // PROMPT-SYSTEM 施工② G1(2026-09-10):六件槽位常量装载收口 prompt-overlays.mjs
17
- // (与子代理角色常量同源——单一权威锚 D1);旧三件文件读取随本批退役。
18
- // 槽位常量通过下方 re-export 面(mod: rel — re-export 保 import 面);本文件自身零直接消费。
19
- // 主循环阶段函数(压缩检查/注入组/回合收尾)2026-09-05 实践轮迁 agent/run-stages.mjs
20
- import { runCompactionCheck, injectTurnReminders, finalizeAgentTurn, injectResponseReminders } from "./agent/run-stages.mjs"
21
- import {
22
- escapeXml, repairHistory, listWorkDir,
23
- readonlyToolNames, collectGitContext, loadProjectInstructions,
24
- ContinueError,
25
- turnFrame, // 第 19 批(TURN-ACROSS-SEGMENTS):跨段累计编号帧(设计 §19.3)
26
- DEFAULT_MAX_TURNS, DEFAULT_SUBAGENT_TURNS,
27
- MIN_REPORT_CHARS, REPORT_CONTINUATION,
28
- AUTO_TURN_DIGEST_DOMAIN,
29
- } from "./agent/helpers.mjs"
30
- // ENG 提醒族 + auto-turn domain 2026-09-05 迁 agent/helpers.mjs(agent.mjs 530 > 500 硬限)
31
- // PROMPT-SYSTEM 施工② G1(2026-09-10):六件槽位常量装载收口 prompt-overlays.mjs
32
- // (与子代理角色常量同源——单一权威锚 D1);本文件 re-export 保 import 面。
33
- export {
34
- PERSONA_ENGINEERING, PERSONA_NORMAL, COMMON,
35
- DISCIPLINE_ENGINEERING, DISCIPLINE_NORMAL,
36
- CONSULT_BASE,
37
- } from "./prompt-overlays.mjs"
38
- export { ENG_ON_REMINDER, ENG_OFF_REMINDER } from "./agent/helpers.mjs"
39
-
40
- // Role → persona slot mapping note(施工② G3): 子代理人格由 assemblePrompt 场景表按
41
- // role 承载(persona-explore/coder/plan/eng-coder.md——D1 表 = 蓝图 §3.2 1:1);
42
- // spawn 侧 overlay 概念已退役(prompt-overlays.mjs 不再导出 OVERLAY 别名常量)。
43
-
44
- // exported for consumption by agent-tools.mjs
45
- export {
46
- ContinueError,
47
- listWorkDir, loadProjectInstructions,
48
- readonlyToolNames, collectGitContext, escapeXml,
49
- MIN_REPORT_CHARS, REPORT_CONTINUATION, DEFAULT_SUBAGENT_TURNS,
50
- }
51
-
52
-
53
- // Re-exported for API compatibility (single source of truth: advisor/repos.mjs)
54
- export { hasCodeMutations } from "./advisor/repos.mjs"
55
-
56
- /** Create a new agent state object with all fields initialized to defaults */
57
- export function createAgent({
58
- provider, tools, config, cwd, memory, overlay, role,
59
- tasks = [], history = [],
60
- planMode = false, autoApprove = false,
61
- goal = null, sessionStart = null,
62
- }) {
63
- return {
64
- provider, tools, config, cwd, memory, _role: role,
65
- overlay, tasks, history,
66
- planMode, autoApprove, goal,
67
- _mutatedThisRun: false, _verifiedThisRun: false, _verifyPassed: undefined, _calledAdvisorThisRun: false,
68
- _engDesignReviewed: false, // eng-coder: design review gate passed (hard gate in dispatch.mjs)
69
- // DESIGN-TOKEN-SETTLEMENT D3 (2026-09-08): single-value `_engDesignToken` mirror retired
70
- // (AC3 零写) — no field initializer; the multi-slot Map `_engDesignTokens` is the
71
- // authoritative ledger (hydrated by restoreEngTokens / written by settle).
72
- _touchedFiles: [], _verifyRetries: 0, _advisorRound: 0, _advisorSession: null,
73
- _advisorRuns: new Map(), // §11.2 D-24b: per-review convergence instances (rounds/prior/designId)
74
- _mutationSeq: 0, _mutLog: [], // §11.2 D-24b: mutation log (in-flight review staleness scan)
75
- _lastAdvisorOutput: null, // full review output from the most recent advisor call (convergence rounds inject it verbatim)
76
- _lastEngState: false,
77
- _pendingReminders: [],
78
- _pendingTimers: [],
79
- _sessionStart: sessionStart,
80
- _lastPromptTokens: null, _usageAtLen: null,
81
- _compressFailures: 0,
82
- _emptyRetries: 0, // empty-response retry budget (per-run; reset on a fresh user turn)
83
- _runStartHistoryLen: 0, // machine-line length at the start of the current run — end-of-run exploration distillation slices from here
84
- _pendingDistill: null, // in-flight end-of-run exploration distillation (SEND-STALL-DISTILL §2.1) — awaited at next run start / TUI exit flush
85
- _currentTurn: 0, _maxTurns: DEFAULT_MAX_TURNS, // turn counter for status bar display
86
- }
87
- }
88
-
89
- /** Run the agent loop: LLM ↔ tool-call cycle until task completion or turn limit. Returns final text content. */
90
- export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false, autoTurn = false, suspDriven = false, consumeInjected = null } = {}) {
91
- // Previous run's async exploration distillation must settle before this run pushes
92
- // input (SEND-STALL-DISTILL §2.2 N1) — await first, or its history replace wipes it.
93
- if (agent._pendingDistill) {
94
- const p = agent._pendingDistill
95
- agent._pendingDistill = null
96
- await p
97
- }
98
- // §17 D-S3: suspension-settled async results inject before EVERY run's prepareRun
99
- // (user + auto-turn); spliced = consumed. collectSettledAsync owns a different
100
- // container, so no double-inject across the two consumption points.
101
- // ASYNC-RESULT-CONTAINER.md D2 (2026-09-08):pending 单容器 `_pendingAsyncResults`
102
- // +role——四族(subagent/advisor/escalate/consult)统一停靠;注入器按 role 分发
103
- // (consult → injectConsultResult;其余 → injectAsyncResult——族分支同 §25 D-R17a/b)
104
- // ——单容器一处清,不再逐族三段。
105
- const pendingAsync = agent._pendingAsyncResults
106
- if (pendingAsync?.length) {
107
- const { injectAsyncResult } = await import("./agent-tools/subagent.mjs")
108
- const { injectConsultResult } = await import("./agent-tools/consult.mjs")
109
- // TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §23.3.1 消费点②——run 起始 pending 注入):
110
- // 注入完成后释放条目对子代理对象的持有(childAgent/report 置空——幂等 helper)。
111
- const { releaseSettledEntry } = await import("./agent-tools/async-settle.mjs")
112
- for (const e of pendingAsync.splice(0)) {
113
- if (e.role === "consult") await injectConsultResult(agent, e)
114
- else await injectAsyncResult(agent, e)
115
- releaseSettledEntry(e)
116
- }
117
- }
118
- agent._inAutoTurn = autoTurn // spawn gate for manual-tier digests (§17 D-S6/N3)
119
- const { maxTurns, threshold, tools, toolSchemas, toolByName, systemPrompt } = await prepareRun(
120
- // G1/G2(施工②):prompt 装配收口 prepareRun 内部(assemblePrompt——prompt-overlays.mjs
121
- // 槽位常量,与子代理角色常量同源——单一权威锚 D1);本调用不再携带 prompt 常量。
122
- agent, input, callbacks,
123
- { depth, signal, overrideTurns, resume: resume || autoTurn },
124
- )
125
-
126
- // Exploration-distillation boundary (CONTEXT-COMPACTION §5): prepareRun already
127
- // pushed input + injections — appended from here counts as "this run's" work.
128
- agent._runStartHistoryLen = agent.history.length
129
-
130
- // Per-run bookkeeping reset — PRESERVED on `resume` (ContinueError continuation):
131
- // mutation/guard continuity and the convergence budget must survive a continuation.
132
- if (!resume) {
133
- // 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):链内累计编号
134
- // 只在链起点复位——续跑(resume:true)不重置、不回退(编号帧公式见 helpers.mjs
135
- // turnFrame)。与下方 mutation/guard 复位同条件同点(全档唯一复位点)。
136
- agent._turnSeq = 0
137
- // §17 D-S6: an auto-turn's guard marks are inherited by the next USER run (not
138
- // reset) so auto-turn changes never escape the guard silently.
139
- const g = agent._inheritedGuard
140
- if (g) {
141
- for (const k of ["_mutatedThisRun", "_verifiedThisRun", "_verifyPassed", "_calledAdvisorThisRun", "_touchedFiles", "_verifyRetries", "_advisorRound"]) agent[k] = g[k]
142
- agent._inheritedGuard = null
143
- } else {
144
- agent._mutatedThisRun = false
145
- agent._verifiedThisRun = false
146
- agent._verifyPassed = undefined
147
- agent._calledAdvisorThisRun = false
148
- agent._touchedFiles = []
149
- agent._verifyRetries = 0
150
- agent._advisorRound = 0
151
- agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
152
- agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
153
- agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
154
- }
155
- }
156
- // §17 D-S6 manual tier: digest action-domain reminder (system-driven turn — organize only).
157
- if (autoTurn && !agent.autoApprove) {
158
- agent.history.push({ role: "user", content: AUTO_TURN_DIGEST_DOMAIN, transient: true })
159
- }
160
- // eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
161
- // (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
162
- // depth-0 parent never reads or writes it (the parent gate reads anyLiveDesignSlot; the
163
- // depth-0 per-turn reset was removed 2026-09-08, ENG-SESSION-PROVIDER-CLEANUP D1.3).
164
- // Design slots (_engDesignTokens Map) survive across turns (design review → approval →
165
- // eng-coder spawn) — persisted to the session slot at settle time (DESIGN-TOKEN-
166
- // SETTLEMENT D1); lifecycle: issued on a passing review, consumed by consume-design / TTL.
167
- let guardPushbacks = 0
168
- let advisorPushbacks = 0
169
- let honestReminderInjected = false
170
- const recentCallSigs = []
171
- // "once" stream rules fire at most once per runAgent call; the set survives across
172
- // chat() calls (rule abort-retry, tool loop) within the turn.
173
- const streamRuleFired = new Set()
174
-
175
- // Compaction overhead for the pure-estimation path: system prompt + tools schema are
176
- // in every request but not in history — without them the first-turn/just-compacted
177
- // estimate under-counts and may never trigger. Measured path already includes both.
178
- const compactionOverhead = {
179
- systemPrompt,
180
- tools: toolSchemas,
181
- // §18.6 D-TR4:compress 轨迹 depth 元数据(runAgent 的 depth 在此作用域——
182
- // context.mjs compressIfNeeded 经 extras 透出到 logCtx)
183
- traceDepth: depth,
184
- }
185
-
186
- let thrownError = null
187
- try {
188
- for (let turn = 0; turn < maxTurns; turn++) {
189
- // 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):编号帧——
190
- // `_turnSeq` 每轮 +1(跨段累计,仅 `!resume` 链起点复位);面向消费面的两字段
191
- // (状态行 + ⟦ev⟧turn / ⟦ev⟧approval 事件共用)在此同点赋值(编号唯一权威;帧
192
- // 公式 = helpers.mjs turnFrame)。段内帽判定不读帧(下行循环条件只读段内
193
- // turn / maxTurns——N6)。
194
- const frame = turnFrame(++agent._turnSeq, turn, maxTurns)
195
- agent._currentTurn = frame.turn
196
- agent._maxTurns = frame.maxTurns
197
- // D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token each turn —
198
- // single emit point covering all three spawn tools; phase=llm (tool/done progress rides
199
- // the onToolCall/onToolResult relay — no token for those). 第 19 批:载荷取上方帧值
200
- // (agent._currentTurn / _maxTurns——字段形态 / 字段数 / phase 零变化,N5)。
201
- if (depth > 0 && callbacks.onToken) {
202
- callbacks.onToken(`⟦ev⟧turn\x1e${agent._currentTurn}\x1e${agent._maxTurns}\x1ellm\x1e`)
203
- }
204
-
205
- // SUBAGENT-OBSERVE-SEND D2: 子代理回合边界消费点——每轮开头把父侧经 subagent
206
- // action:'send' 注入队列(entry._injected)的消息按普通 user 回合推入子历史
207
- // (pushReal → 下一轮 chat 即含该指令)。由 executeAsyncSpawn 经 childRunOpts 贯通的
208
- // consumeInjected 回调承载(异步子代理专属——缺省 null:主会话/阻塞子代理零开销)。
209
- consumeInjected?.(agent)
210
-
211
- const lastRole = agent.history.at(-1)?.role
212
- if (lastRole === "user" || lastRole === "tool") {
213
- // 2026-09-05 实践轮:压缩检查/降级计数提为 runCompactionCheck(agent/run-stages.mjs——
214
- // CLI 对位 VS run-stages)——循环骨架此处只剩检查调用(recentCallSigs 对象引用回流)。
215
- await runCompactionCheck(agent, { threshold, callbacks, compactionOverhead, signal, recentCallSigs })
216
- }
217
-
218
- // 2026-09-05 实践轮:plan cadence + eng 状态注入提为 injectTurnReminders(run-stages)
219
- await injectTurnReminders(agent, { depth })
220
-
221
- const messages = [{ role: "system", content: systemPrompt }, ...agent.history]
222
- let response
223
-
224
- // Auto-think: classify difficulty and set reasoning effort on turn 0; silent on failure.
225
- if (agent.config?.agent?.autoThink && turn === 0) {
226
- const { classifyAndApply } = await import("./auto-think.mjs")
227
- await classifyAndApply(agent, turn).catch(() => {})
228
- }
229
-
230
- if (process.env.ADVISOR_DEBUG) console.error("[chat-call]", JSON.stringify({ turn, histLen: agent.history.length, lastRole: agent.history.at(-1)?.role }))
231
- try { response = await chat(agent.provider, {
232
- messages, tools: toolSchemas,
233
- onToken: callbacks.onToken,
234
- onReasoning: callbacks.onReasoning,
235
- onWait: callbacks.onWait,
236
- signal,
237
- streamRules: agent.config.agent?.streamRules ?? [],
238
- firedPatterns: streamRuleFired,
239
- // LOGGING(LOGGING.md):llm:* 事件的语义上下文(stage=turn 主循环回合——含
240
- // digest 消化轮 auto=true;child=子代理 id(spawn 时 stamp 于 child._logId))
241
- // §18.6 D-TR4:轨迹元数据增补(role/depth/kind/session/cwd——trace-store 只读
242
- // logCtx,签名不变);kind:depth>0 = subagent(consult 孩子 = consult)——子代理
243
- // 对回靠 role+depth+child id(children 无 _sessionStart——不经 depth-0 设置——
244
- // session 字段对子代理轨迹为 null——见 trace-store/agent.mjs 注释)。
245
- logCtx: {
246
- stage: "turn", turn: turn + 1, auto: autoTurn, child: agent._logId,
247
- role: agent._role ?? null,
248
- depth,
249
- kind: depth > 0 ? (agent._role === "consult" ? "consult" : "subagent") : "turn",
250
- session: agent._sessionStart ?? null,
251
- cwd: agent.cwd,
252
- traces: agent.config?.traces?.enabled !== false,
253
- },
254
- })
255
- } catch (e) {
256
- // User interrupt (Ctrl+I): controller.abort({ interrupt: true, message }).
257
- // Inject into history; the outer loop recreates the controller and resumes.
258
- if (e.name === "AbortError" && signal?.reason?.interrupt) {
259
- const msg = `[User interrupt: ${signal.reason.message}]`
260
- // Dedup: if already handled during tool execution (interrupt branch below),
261
- // don't push a duplicate — the outer loop still recreates the controller.
262
- if (agent.history.at(-1)?.content !== msg) {
263
- agent.history.push({ role: "user", content: msg })
264
- }
265
- }
266
- throw e
267
- }
268
-
269
- // 内置工具(Responses web_search)结果本地化:服务端已执行——入历史为 tool 消息;
270
- // 服务端 item id 是 msg_xxx 非 web_search_call_ 前缀——必须合成前缀(toItems 识别锚点),
271
- // 原始 id 存入 content(真机冒烟 2026-08-31 验证)。
272
- for (const btr of response.builtinToolResults ?? []) {
273
- if (!btr?.id) continue
274
- pushReal(agent, {
275
- role: "tool",
276
- tool_call_id: `web_search_call_${btr.id}`,
277
- content: JSON.stringify({ id: btr.id, query: btr.query ?? "", sources: btr.sources ?? [], status: btr.status ?? "completed" }),
278
- })
279
- }
280
-
281
- // Stream rule triggered mid-generation (action: "abort"): halt, inject the rule's
282
- // message as a reminder, retry from the same context.
283
- if (response.ruleTriggered) {
284
- if (response.content) {
285
- pushReal(agent, { role: "assistant", content: response.content })
286
- }
287
- const label = response.ruleName ? ` — stream rule "${response.ruleName}"` : ""
288
- agent.history.push({
289
- role: "user",
290
- content: `[System reminder${label}: ${response.ruleMessage}]`,
291
- })
292
- continue
293
- }
294
-
295
- // Stream rule warnings / finish-reason 警告(2026-09-05 实践轮——提为
296
- // injectResponseReminders,agent/run-stages.mjs——verbatim,语义零变)
297
- injectResponseReminders(agent, response)
298
-
299
- // User interrupted mid-generation (Ctrl+I): commit partial output + inject the
300
- // message, then signal the outer loop to recreate the controller and resume.
301
- if (response.interrupted) {
302
- if (response.content) {
303
- pushReal(agent, { role: "assistant", content: response.content })
304
- }
305
- agent.history.push({
306
- role: "user",
307
- content: `[User interrupt: ${response.interruptMessage}]`,
308
- })
309
- // §20.3 站点 #8(第 24 批):错误对象已自带 name/message——只补来源标注(缺 abortInfo 才补)
310
- throw annotateAbort(Object.assign(new Error("User interrupted"), { name: "AbortError" }), signal, "agent", "interrupted-response")
311
- }
312
-
313
- if (response.usage) {
314
- callbacks.onUsage?.(response.usage)
315
- if (response.usage.prompt_tokens != null) {
316
- agent._lastPromptTokens = response.usage.prompt_tokens
317
- agent._usageAtLen = agent.history.length
318
- }
319
- }
320
-
321
- // Warn on abnormal finish reasons — the response may be incomplete/truncated.
322
- // 2026-09-05 实践轮:finish-reason 注入随流规则警告提为 injectResponseReminders。
323
-
324
- if (response.toolCalls.length === 0) {
325
- const cr = handleCompletion(agent, response, depth, turn, guardPushbacks, honestReminderInjected, advisorPushbacks, callbacks)
326
- guardPushbacks = cr.guardPushbacks
327
- honestReminderInjected = cr.honestReminderInjected
328
- advisorPushbacks = cr.advisorPushbacks
329
- if (cr.action === "continue") continue
330
- if (depth === 0) {
331
- // End-of-run exploration distillation (CONTEXT-COMPACTION §5 + SEND-STALL-DISTILL
332
- // §2.1): async — the promise hangs on _pendingDistill, settling at the next run's
333
- // start or the TUI exit flush. Silent (N3): failure never blocks return/history.
334
- // §18.6 D-TR4:depth 透传(distill 轨迹元数据——与 compress 同通道)
335
- const distill = summarizeRunExplorations(agent, callbacks, signal, depth).catch(() => {})
336
- agent._pendingDistill = distill
337
- }
338
- return cr.content
339
- }
340
-
341
- // abort after chat completes, before committing history: don't commit a half-finished turn
342
- if (signal?.aborted) throw abortError(signal, "agent", "post-chat")
343
-
344
- pushReal(agent, {
345
- role: "assistant",
346
- content: response.content || null,
347
- tool_calls: response.toolCalls.map((tc) => ({
348
- id: tc.id, type: "function",
349
- function: { name: tc.name, arguments: tc.arguments },
350
- })),
351
- ...(response.reasoning && specForModel(agent.provider.model).reasoningEcho === "required"
352
- ? { reasoning_content: response.reasoning }
353
- : {}),
354
- })
355
-
356
- const results = await executeToolCalls(agent, toolByName, response.toolCalls, callbacks, depth, signal)
357
-
358
- // Ctrl+I interrupt during tool execution: skip committing partial results — inject
359
- // the interrupt and retry (placeholder results keep strict providers pairable).
360
- if (signal?.reason?.interrupt) {
361
- // 中断变更记账(2026-08-31 评审 #4):此分支的工具已全部执行完成(磁盘已变,execute 已完成),
362
- // 真实结果按语义不进历史(placeholder 替代)——但变更必须记账:否则 guard 看到
363
- // "本轮未改代码" 放行,评审/verify 门禁被绕过(文件改了却没评审)。
364
- // §29 fix A(2026-09-07):mutation-seq 记账已收敛到 dispatch runOne 执行成功即刻
365
- // (唯一记账点——本分支不再 noteMutations——不双计——中断+同批 launch seq 单计
366
- // 回归断言见 §29 T-A1i);此处仅剩 guard 标志 + touchedFiles 记账。
367
- for (const { toolCall, ok } of results) {
368
- const tool = toolByName.get(toolCall.name)
369
- if (!ok || !tool || !FILE_MUTATORS.has(toolCall.name)) continue
370
- agent._mutatedThisRun = true
371
- agent._calledAdvisorThisRun = false
372
- agent._verifiedThisRun = false
373
- agent._verifyPassed = undefined
374
- try {
375
- const args = JSON.parse(toolCall.arguments)
376
- const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
377
- for (const p of paths) {
378
- const abs = resolve(agent.cwd, p)
379
- if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
380
- }
381
- } catch { /* 畸形 args 不影响记账(touchedFiles 尽力而为) */ }
382
- }
383
- // The assistant tool_calls were committed above — synthesize placeholder tool
384
- // results BEFORE the interrupt message (strict providers 400 on dangling
385
- // tool_calls; consult P1, 2026-08-30).
386
- for (const tc of response.toolCalls) {
387
- agent.history.push({ role: "tool", tool_call_id: tc.id, content: "[Tool execution interrupted — results discarded]" })
388
- }
389
- agent.history.push({
390
- role: "user",
391
- content: `[User interrupt: ${signal.reason.message}]`,
392
- })
393
- callbacks.onTurnEnd?.(agent, turn)
394
- continue
395
- }
396
-
397
- // Model is executing tools → real work: reset guard pushback counters
398
- guardPushbacks = 0
399
- advisorPushbacks = 0
400
-
401
- // Commit tool results (pairing, multimodal deferral, mutation accounting, reindex)
402
- await recordToolResults(agent, toolByName, results)
403
-
404
- injectPostTurn(agent, results, recentCallSigs, callbacks, turn)
405
- }
406
-
407
- throw new ContinueError(maxTurns)
408
- } catch (e) {
409
- thrownError = e
410
- throw e
411
- } finally {
412
- // 2026-09-05 实践轮:回合收尾(consult 清理/async 池分流/guard 继承——原 425-462
413
- // 段 + collectSettledAsync 466-494 整体迁 agent/run-stages.mjs finalizeAgentTurn——
414
- // CLI 对位 VS run-stages——finally 只剩一行调用 + 骨架注释)。
415
- await finalizeAgentTurn(agent, { signal, autoTurn, suspDriven, thrownError, depth })
416
- }
417
- }
@@ -1,115 +0,0 @@
1
- /**
2
- * auto-think.mjs — automatic difficulty classification for reasoning effort.
3
- *
4
- * When enabled, before each user-facing turn a cheap classification call determines
5
- * the task difficulty, then maps it to the model's reasoning effort. This replaces
6
- * manual /think toggling with per-prompt automatic selection.
7
- *
8
- * Config:
9
- * { agent: { autoThink: true } }
10
- *
11
- * Mechanism:
12
- * 1. Take the last user message from history
13
- * 2. Send a minimal classification prompt (expects one-word reply)
14
- * 3. Map difficulty → reasoningEffort using the model's valid effort enum
15
- * 4. Set agent.provider.reasoningEffort before the real chat() call
16
- */
17
- import { chat } from "./provider/core.mjs"
18
- import { specForModel } from "./config.mjs"
19
-
20
- const CLASSIFY_PROMPT = `Classify this coding task's difficulty: low, medium, or high.
21
-
22
- - low — trivial: rename, typo, formatting, one-liner, direct question
23
- - medium — localized: small feature, straightforward bug fix, moderate change
24
- - high — complex: multi-file, debugging, design decisions, large refactor
25
-
26
- Reply with exactly one word.`
27
-
28
- const EFFORT_MAP = {
29
- low: ["low", "minimal", "none", "low"],
30
- medium: ["high", "medium", "high"],
31
- high: ["max", "max", "xhigh", "max"],
32
- }
33
-
34
- /**
35
- * Build classifier input from history: the latest real user message (reminders and
36
- * interrupt injections excluded), plus the previous user message as context when the
37
- * latest is too short to classify on its own (e.g. "继续" / "还有几个问题").
38
- * Exported for tests.
39
- */
40
- export function buildClassifierInput(history) {
41
- const isRealUser = (m) =>
42
- m.role === "user" && typeof m.content === "string"
43
- && !m.content.startsWith("[System reminder:") && !m.content.startsWith("[User interrupt:")
44
- const users = history.filter(isRealUser)
45
- const last = users.at(-1)
46
- if (!last) return null
47
- let prompt = last.content
48
- if (prompt.length < 200 && users.length > 1) {
49
- prompt = `Previous request (context):\n${users.at(-2).content.slice(0, 1200)}\n\nLatest message:\n${prompt}`
50
- }
51
- return prompt.slice(0, 2000)
52
- }
53
-
54
- /**
55
- * Classify the difficulty of the user's prompt and adjust reasoning effort.
56
- * Only runs on the first turn (turn === 0) of a user message.
57
- * Returns the resolved level or null if auto-thinking is disabled or classification fails.
58
- * @param {object} agent
59
- * @param {number} turn
60
- * @returns {Promise<string|null>}
61
- */
62
- export async function classifyAndApply(agent, turn) {
63
- if (!agent.config?.agent?.autoThink) return null
64
- if (turn !== 0) return null // Only classify on the first turn of user input
65
-
66
- const spec = specForModel(agent.provider.model)
67
- const validEfforts = spec.reasoningEffortEnum
68
- if (!validEfforts) return null // Model doesn't support reasoning effort
69
-
70
- const prompt = buildClassifierInput(agent.history)
71
- if (prompt == null) return null
72
-
73
- // Classification call: use same provider, minimal tokens, no tools, no streaming
74
- let level
75
- try {
76
- const classifierProvider = { ...agent.provider, maxTokens: 10 }
77
- const response = await chat(classifierProvider, {
78
- messages: [
79
- { role: "system", content: CLASSIFY_PROMPT },
80
- { role: "user", content: prompt },
81
- ],
82
- tools: [],
83
- signal: AbortSignal.timeout(5_000),
84
- // D-TS12 (AGENT-LOOP.md §18.7): full logCtx field set at the chat call
85
- // point — traces/session/cwd/role/depth/kind (this call point carried
86
- // only {stage,turn,child}). The traces field closes the D-TR6 "off = no
87
- // persist" switch: without it the tracer treated the auto-think call as
88
- // enabled and persisted even when agent.config.traces.enabled was false.
89
- logCtx: {
90
- stage: "autothink", turn, child: agent._logId,
91
- traces: agent.config?.traces?.enabled !== false,
92
- session: agent._sessionStart ?? null,
93
- cwd: agent.cwd,
94
- role: agent._role ?? null,
95
- depth: agent._depth ?? 0, // agent state carries no depth stamp (the call site passes none) — 0 for the top-level agent
96
- kind: "autothink",
97
- },
98
- })
99
- const word = (response.content ?? "").trim().toLowerCase()
100
- if (word.startsWith("low")) level = "low"
101
- else if (word.startsWith("medium") || word.startsWith("med")) level = "medium"
102
- else if (word.startsWith("high")) level = "high"
103
- else return null // Unparseable
104
- } catch {
105
- return null // Classification failure → fall back to current setting
106
- }
107
-
108
- // Map difficulty to the closest valid reasoning effort
109
- const candidates = EFFORT_MAP[level] || EFFORT_MAP.medium
110
- const matched = candidates.find(e => validEfforts.includes(e))
111
- if (!matched) return null
112
-
113
- agent.provider.reasoningEffort = matched
114
- return matched
115
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * config-migrate.mjs — model-schema legacy-shape migration core (MODEL-SELECTION v2, §16.2 M7).
3
- * Split out of config.mjs (500-line hard limit). Pure function, zero imports — the
4
- * write-back orchestration (writeConfigAtomic / persistRaw, failure tolerance) stays in
5
- * the caller (CLI: config.mjs loadConfig; VSC: config-io.mjs loadRaw — same rule, 双端各自
6
- * 独立实现——不做同步依赖).
7
- *
8
- * 目标形态 C:每渠道恰好一个默认模型(`providers[].model` 单值)+ 顶层 `defaultModel` 复合。
9
- * 老形态迁移(v2):
10
- * - A:`providers[].model` + `activeProvider/activeModel` → `p.model` **保留**(不再搬入 models);
11
- * `activeProvider/activeModel` → `defaultModel` 复合(`activeModel` 优先;AP 不存在/AM 空 → 首渠道回退)
12
- * - B:`providers[].models[]` → `p.model = defaultModel 属本渠道的模型段 ?? 现有 p.model(非空字符串)
13
- * ?? models[] 首个非空字符串`,然后 `delete p.models`
14
- * - 混合/垃圾:非数组 `p.models` / 非字符串(空串)`p.model` → 删除(清理)
15
- * - 顺序:先构造/读取有效 `defaultModel` 值(含老 active* 转换),再按它给各渠道播种 `p.model`
16
- * - 幂等:无老字段(无 `models` / 无 active* / 无垃圾值)→ 返回 false 不动;slot 旧字段
17
- * (会话文件内)不迁移——读侧容忍
18
- * - 空结果合法:渠道无模型来源 → `p.model` 不设(模型选择经 `/models` 拉取候选——M8/M9)
19
- */
20
- export function migrateLegacyModelFields(raw) {
21
- if (!raw || typeof raw !== "object") return false
22
- const providers = Array.isArray(raw.providers) ? raw.providers : []
23
- let changed = false
24
-
25
- // ── ① 有效 defaultModel 值(含老 active* 转换)──
26
- if (raw.activeProvider !== undefined || raw.activeModel !== undefined) {
27
- const rawAP = typeof raw.activeProvider === "string" && raw.activeProvider.trim() ? raw.activeProvider : ""
28
- const rawAM = typeof raw.activeModel === "string" && raw.activeModel.trim() ? raw.activeModel : ""
29
- const ap = rawAP ? providers.find((p) => p?.name === rawAP) : null
30
- if (ap && rawAM) {
31
- if (raw.defaultModel == null) { raw.defaultModel = `${rawAP}:${rawAM}`; changed = true }
32
- } else {
33
- // AP 已不存在 / AM 无法归属 → 首渠道回退(该渠道有模型来源才可用——迁移产物必可用)
34
- const first = providers.find((p) => seedModel(p) != null)
35
- if (first && raw.defaultModel == null) { raw.defaultModel = `${first.name}:${seedModel(first)}`; changed = true }
36
- }
37
- delete raw.activeProvider
38
- delete raw.activeModel
39
- changed = true
40
- }
41
-
42
- // ── ② 渠道播种单值 `p.model` + 清 models ──
43
- const dm = typeof raw.defaultModel === "string" && raw.defaultModel.trim() ? raw.defaultModel : null
44
- const dmSep = dm ? dm.indexOf(":") : -1
45
- const dmProvider = dmSep > 0 ? dm.slice(0, dmSep) : null
46
- const dmModel = dmSep > 0 && dmSep < dm.length - 1 ? dm.slice(dmSep + 1) : null
47
- for (const p of providers) {
48
- if (!p || typeof p !== "object") continue
49
- if (Array.isArray(p.models)) {
50
- const next = (p.name === dmProvider && dmModel)
51
- ? dmModel
52
- : (typeof p.model === "string" && p.model.trim() ? p.model : seedModel(p))
53
- if (next == null) delete p.model
54
- else p.model = next
55
- delete p.models
56
- changed = true
57
- continue
58
- }
59
- if (p.models !== undefined) { delete p.models; changed = true } // 非数组 models —— 垃圾清理
60
- if (p.model !== undefined && !(typeof p.model === "string" && p.model.trim())) { delete p.model; changed = true }
61
- }
62
- return changed
63
- }
64
-
65
- /** 渠道级模型来源读数(迁移用):现有 `p.model`(非空字符串优先)或 `models[]` 首个非空字符串。 */
66
- function seedModel(p) {
67
- if (typeof p?.model === "string" && p.model.trim()) return p.model
68
- const models = Array.isArray(p?.models) ? p.models : []
69
- return models.find((m) => typeof m === "string" && m.trim()) ?? null
70
- }