thincoder 0.12.61 → 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 (230) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/README.md +13 -12
  3. package/bin/thincoder.mjs +63 -28
  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 -371
  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 +32 -10
  20. package/src/distill.mjs +4 -4
  21. package/src/heap-watch.mjs +88 -0
  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-clear.mjs +2 -0
  26. package/src/tui/cmd-config.mjs +8 -8
  27. package/src/tui/cmd-eng.mjs +25 -9
  28. package/src/tui/cmd-mcp.mjs +9 -8
  29. package/src/tui/cmd-model.mjs +1 -1
  30. package/src/tui/cmd-new.mjs +10 -5
  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 +31 -4
  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 +206 -0
  38. package/src/tui/index.mjs +40 -12
  39. package/src/tui/interaction.mjs +16 -7
  40. package/src/tui/key-handler-search.mjs +9 -1
  41. package/src/tui/key-modes.mjs +9 -4
  42. package/src/tui/ledger-surface.mjs +85 -0
  43. package/src/tui/model-catalog.mjs +4 -4
  44. package/src/tui/model-picker.mjs +8 -7
  45. package/src/tui/mouse.mjs +11 -6
  46. package/src/tui/pickers.mjs +15 -2
  47. package/src/tui/render-conversation.mjs +1 -1
  48. package/src/tui/render-frame.mjs +17 -6
  49. package/src/tui/render-loop.mjs +1 -1
  50. package/src/tui/render-segments.mjs +3 -1
  51. package/src/tui/slash-commands.mjs +1 -1
  52. package/src/tui/startup.mjs +49 -17
  53. package/src/tui/subagent-blocks.mjs +21 -3
  54. package/src/tui/subagent-children.mjs +86 -14
  55. package/src/tui/subagent-freeze.mjs +80 -3
  56. package/src/tui/suspension-drive.mjs +48 -22
  57. package/src/tui/tool-args.mjs +5 -2
  58. package/src/tui/tool-display.mjs +16 -2
  59. package/src/tui/tool-events.mjs +64 -22
  60. package/src/tui/tui-lifecycle.mjs +9 -2
  61. package/src/tui/wizard.mjs +3 -3
  62. package/src/tui/wrapped-spawn.mjs +21 -5
  63. package/src/abort-provenance.mjs +0 -116
  64. package/src/advisor/citations.mjs +0 -139
  65. package/src/advisor/compaction.mjs +0 -174
  66. package/src/advisor/convergence.mjs +0 -80
  67. package/src/advisor/history.mjs +0 -77
  68. package/src/advisor/loop.mjs +0 -293
  69. package/src/advisor/messages.mjs +0 -299
  70. package/src/advisor/project-context.mjs +0 -194
  71. package/src/advisor/repos.mjs +0 -150
  72. package/src/advisor/run.mjs +0 -293
  73. package/src/advisor/truncate.mjs +0 -57
  74. package/src/advisor.mjs +0 -290
  75. package/src/agent/completion.mjs +0 -146
  76. package/src/agent/dispatch.mjs +0 -489
  77. package/src/agent/helpers.mjs +0 -384
  78. package/src/agent/post-turn.mjs +0 -70
  79. package/src/agent/record-results.mjs +0 -174
  80. package/src/agent/relay-prefix.mjs +0 -39
  81. package/src/agent/run-stages.mjs +0 -242
  82. package/src/agent/setup-reminders.mjs +0 -69
  83. package/src/agent/setup.mjs +0 -354
  84. package/src/agent/spawn-child.mjs +0 -228
  85. package/src/agent-tools/advisor-async.mjs +0 -346
  86. package/src/agent-tools/advisor-settle.mjs +0 -231
  87. package/src/agent-tools/advisor.mjs +0 -260
  88. package/src/agent-tools/async-settle.mjs +0 -191
  89. package/src/agent-tools/batch-segment.mjs +0 -195
  90. package/src/agent-tools/consult.mjs +0 -468
  91. package/src/agent-tools/design-token.mjs +0 -117
  92. package/src/agent-tools/digest-budget.mjs +0 -76
  93. package/src/agent-tools/eng.mjs +0 -67
  94. package/src/agent-tools/escalate-async.mjs +0 -289
  95. package/src/agent-tools/goal.mjs +0 -119
  96. package/src/agent-tools/plan.mjs +0 -81
  97. package/src/agent-tools/read-history.mjs +0 -294
  98. package/src/agent-tools/recent-changes.mjs +0 -24
  99. package/src/agent-tools/review-streak.mjs +0 -93
  100. package/src/agent-tools/settings.mjs +0 -265
  101. package/src/agent-tools/skill.mjs +0 -47
  102. package/src/agent-tools/subagent-actions.mjs +0 -479
  103. package/src/agent-tools/subagent-async.mjs +0 -434
  104. package/src/agent-tools/subagent-panel.mjs +0 -160
  105. package/src/agent-tools/subagent-run.mjs +0 -205
  106. package/src/agent-tools/subagent-scheduler.mjs +0 -392
  107. package/src/agent-tools/subagent-spawn.mjs +0 -453
  108. package/src/agent-tools/subagent.mjs +0 -404
  109. package/src/agent-tools/task.mjs +0 -87
  110. package/src/agent-tools/timer.mjs +0 -46
  111. package/src/agent-tools/verify.mjs +0 -271
  112. package/src/agent-tools.mjs +0 -17
  113. package/src/agent.mjs +0 -413
  114. package/src/auto-think.mjs +0 -115
  115. package/src/config-migrate.mjs +0 -70
  116. package/src/config.mjs +0 -496
  117. package/src/context.mjs +0 -381
  118. package/src/conventions.mjs +0 -223
  119. package/src/embedding.mjs +0 -120
  120. package/src/escape.mjs +0 -152
  121. package/src/expand-home.mjs +0 -16
  122. package/src/explore-distill.mjs +0 -155
  123. package/src/generate-title.mjs +0 -83
  124. package/src/git/checkpoint.mjs +0 -448
  125. package/src/git/gitmem.mjs +0 -100
  126. package/src/hooks.mjs +0 -97
  127. package/src/log.mjs +0 -195
  128. package/src/markdown.mjs +0 -106
  129. package/src/mcp/helpers.mjs +0 -51
  130. package/src/mcp/transport-http.mjs +0 -248
  131. package/src/mcp/transport-stdio.mjs +0 -140
  132. package/src/mcp/transport-ws.mjs +0 -122
  133. package/src/mcp.mjs +0 -295
  134. package/src/memory/code-index.mjs +0 -219
  135. package/src/memory/code-sync.mjs +0 -413
  136. package/src/memory/core.mjs +0 -300
  137. package/src/memory/delete.mjs +0 -236
  138. package/src/memory/docs.mjs +0 -417
  139. package/src/memory/file-walk.mjs +0 -109
  140. package/src/memory/schema.mjs +0 -452
  141. package/src/memory.mjs +0 -21
  142. package/src/model-ref.mjs +0 -66
  143. package/src/model-specs.mjs +0 -179
  144. package/src/peer-domains.mjs +0 -265
  145. package/src/peer-instances.mjs +0 -231
  146. package/src/prompt-overlays.mjs +0 -82
  147. package/src/prompts/advisor-design.md +0 -41
  148. package/src/prompts/advisor-round1.md +0 -41
  149. package/src/prompts/advisor-round2.md +0 -46
  150. package/src/prompts/advisor-round3.md +0 -42
  151. package/src/prompts/common.md +0 -115
  152. package/src/prompts/consult-base.md +0 -19
  153. package/src/prompts/discipline-engineering.md +0 -217
  154. package/src/prompts/discipline-normal.md +0 -179
  155. package/src/prompts/persona-coder.md +0 -21
  156. package/src/prompts/persona-eng-coder.md +0 -37
  157. package/src/prompts/persona-eng-designer.md +0 -55
  158. package/src/prompts/persona-engineering.md +0 -54
  159. package/src/prompts/persona-explore.md +0 -15
  160. package/src/prompts/persona-normal.md +0 -27
  161. package/src/prompts/persona-plan.md +0 -26
  162. package/src/provider/anthropic.mjs +0 -225
  163. package/src/provider/core.mjs +0 -476
  164. package/src/provider/errors.mjs +0 -101
  165. package/src/provider/google.mjs +0 -257
  166. package/src/provider/index.mjs +0 -7
  167. package/src/provider/list-models.mjs +0 -93
  168. package/src/provider/normalize.mjs +0 -81
  169. package/src/provider/rate.mjs +0 -108
  170. package/src/provider/responses.mjs +0 -495
  171. package/src/provider/retry.mjs +0 -88
  172. package/src/provider/sse.mjs +0 -264
  173. package/src/proxy.mjs +0 -261
  174. package/src/rules.mjs +0 -53
  175. package/src/session-gc.mjs +0 -214
  176. package/src/session-guard.mjs +0 -47
  177. package/src/session-migrate.mjs +0 -48
  178. package/src/session-rename.mjs +0 -38
  179. package/src/session-slots.mjs +0 -489
  180. package/src/session.mjs +0 -475
  181. package/src/skills.mjs +0 -153
  182. package/src/token-ttl.mjs +0 -274
  183. package/src/tools/apply_patch.md +0 -15
  184. package/src/tools/bash.md +0 -37
  185. package/src/tools/bash.mjs +0 -268
  186. package/src/tools/checklist-sync.mjs +0 -181
  187. package/src/tools/checklist.md +0 -13
  188. package/src/tools/checklist.mjs +0 -299
  189. package/src/tools/delete.md +0 -13
  190. package/src/tools/edit-batch.mjs +0 -191
  191. package/src/tools/edit-diff.mjs +0 -348
  192. package/src/tools/edit.md +0 -30
  193. package/src/tools/execute.md +0 -21
  194. package/src/tools/execute.mjs +0 -228
  195. package/src/tools/fetch.md +0 -12
  196. package/src/tools/file.mjs +0 -469
  197. package/src/tools/file_ops.md +0 -17
  198. package/src/tools/get_current_time.md +0 -8
  199. package/src/tools/git-checkpoint.mjs +0 -143
  200. package/src/tools/git-ext.mjs +0 -173
  201. package/src/tools/git.md +0 -54
  202. package/src/tools/git.mjs +0 -356
  203. package/src/tools/glob-dialect.mjs +0 -130
  204. package/src/tools/glob.md +0 -11
  205. package/src/tools/grep.md +0 -19
  206. package/src/tools/hashline_edit.md +0 -14
  207. package/src/tools/index.mjs +0 -36
  208. package/src/tools/insert_after.md +0 -15
  209. package/src/tools/lint.md +0 -10
  210. package/src/tools/linter.mjs +0 -128
  211. package/src/tools/ls.md +0 -12
  212. package/src/tools/lsp.md +0 -10
  213. package/src/tools/lsp.mjs +0 -316
  214. package/src/tools/ops.mjs +0 -299
  215. package/src/tools/patch.mjs +0 -282
  216. package/src/tools/process.md +0 -10
  217. package/src/tools/question.md +0 -16
  218. package/src/tools/question.mjs +0 -26
  219. package/src/tools/read.md +0 -20
  220. package/src/tools/read_image.md +0 -8
  221. package/src/tools/repomap.mjs +0 -314
  222. package/src/tools/search.mjs +0 -236
  223. package/src/tools/shared.mjs +0 -446
  224. package/src/tools/tree.md +0 -14
  225. package/src/tools/tree.mjs +0 -66
  226. package/src/tools/wait_for.md +0 -22
  227. package/src/tools/web.mjs +0 -224
  228. package/src/tools/websearch.md +0 -16
  229. package/src/tools/write.md +0 -11
  230. package/src/traces/trace-store.mjs +0 -224
package/src/agent.mjs DELETED
@@ -1,413 +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
- for (const e of pendingAsync.splice(0)) {
110
- if (e.role === "consult") await injectConsultResult(agent, e)
111
- else await injectAsyncResult(agent, e)
112
- }
113
- }
114
- agent._inAutoTurn = autoTurn // spawn gate for manual-tier digests (§17 D-S6/N3)
115
- const { maxTurns, threshold, tools, toolSchemas, toolByName, systemPrompt } = await prepareRun(
116
- // G1/G2(施工②):prompt 装配收口 prepareRun 内部(assemblePrompt——prompt-overlays.mjs
117
- // 槽位常量,与子代理角色常量同源——单一权威锚 D1);本调用不再携带 prompt 常量。
118
- agent, input, callbacks,
119
- { depth, signal, overrideTurns, resume: resume || autoTurn },
120
- )
121
-
122
- // Exploration-distillation boundary (CONTEXT-COMPACTION §5): prepareRun already
123
- // pushed input + injections — appended from here counts as "this run's" work.
124
- agent._runStartHistoryLen = agent.history.length
125
-
126
- // Per-run bookkeeping reset — PRESERVED on `resume` (ContinueError continuation):
127
- // mutation/guard continuity and the convergence budget must survive a continuation.
128
- if (!resume) {
129
- // 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):链内累计编号
130
- // 只在链起点复位——续跑(resume:true)不重置、不回退(编号帧公式见 helpers.mjs
131
- // turnFrame)。与下方 mutation/guard 复位同条件同点(全档唯一复位点)。
132
- agent._turnSeq = 0
133
- // §17 D-S6: an auto-turn's guard marks are inherited by the next USER run (not
134
- // reset) so auto-turn changes never escape the guard silently.
135
- const g = agent._inheritedGuard
136
- if (g) {
137
- for (const k of ["_mutatedThisRun", "_verifiedThisRun", "_verifyPassed", "_calledAdvisorThisRun", "_touchedFiles", "_verifyRetries", "_advisorRound"]) agent[k] = g[k]
138
- agent._inheritedGuard = null
139
- } else {
140
- agent._mutatedThisRun = false
141
- agent._verifiedThisRun = false
142
- agent._verifyPassed = undefined
143
- agent._calledAdvisorThisRun = false
144
- agent._touchedFiles = []
145
- agent._verifyRetries = 0
146
- agent._advisorRound = 0
147
- agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
148
- agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
149
- agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
150
- }
151
- }
152
- // §17 D-S6 manual tier: digest action-domain reminder (system-driven turn — organize only).
153
- if (autoTurn && !agent.autoApprove) {
154
- agent.history.push({ role: "user", content: AUTO_TURN_DIGEST_DOMAIN, transient: true })
155
- }
156
- // eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
157
- // (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
158
- // depth-0 parent never reads or writes it (the parent gate reads anyLiveDesignSlot; the
159
- // depth-0 per-turn reset was removed 2026-09-08, ENG-SESSION-PROVIDER-CLEANUP D1.3).
160
- // Design slots (_engDesignTokens Map) survive across turns (design review → approval →
161
- // eng-coder spawn) — persisted to the session slot at settle time (DESIGN-TOKEN-
162
- // SETTLEMENT D1); lifecycle: issued on a passing review, consumed by consume-design / TTL.
163
- let guardPushbacks = 0
164
- let advisorPushbacks = 0
165
- let honestReminderInjected = false
166
- const recentCallSigs = []
167
- // "once" stream rules fire at most once per runAgent call; the set survives across
168
- // chat() calls (rule abort-retry, tool loop) within the turn.
169
- const streamRuleFired = new Set()
170
-
171
- // Compaction overhead for the pure-estimation path: system prompt + tools schema are
172
- // in every request but not in history — without them the first-turn/just-compacted
173
- // estimate under-counts and may never trigger. Measured path already includes both.
174
- const compactionOverhead = {
175
- systemPrompt,
176
- tools: toolSchemas,
177
- // §18.6 D-TR4:compress 轨迹 depth 元数据(runAgent 的 depth 在此作用域——
178
- // context.mjs compressIfNeeded 经 extras 透出到 logCtx)
179
- traceDepth: depth,
180
- }
181
-
182
- let thrownError = null
183
- try {
184
- for (let turn = 0; turn < maxTurns; turn++) {
185
- // 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):编号帧——
186
- // `_turnSeq` 每轮 +1(跨段累计,仅 `!resume` 链起点复位);面向消费面的两字段
187
- // (状态行 + ⟦ev⟧turn / ⟦ev⟧approval 事件共用)在此同点赋值(编号唯一权威;帧
188
- // 公式 = helpers.mjs turnFrame)。段内帽判定不读帧(下行循环条件只读段内
189
- // turn / maxTurns——N6)。
190
- const frame = turnFrame(++agent._turnSeq, turn, maxTurns)
191
- agent._currentTurn = frame.turn
192
- agent._maxTurns = frame.maxTurns
193
- // D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token each turn —
194
- // single emit point covering all three spawn tools; phase=llm (tool/done progress rides
195
- // the onToolCall/onToolResult relay — no token for those). 第 19 批:载荷取上方帧值
196
- // (agent._currentTurn / _maxTurns——字段形态 / 字段数 / phase 零变化,N5)。
197
- if (depth > 0 && callbacks.onToken) {
198
- callbacks.onToken(`⟦ev⟧turn\x1e${agent._currentTurn}\x1e${agent._maxTurns}\x1ellm\x1e`)
199
- }
200
-
201
- // SUBAGENT-OBSERVE-SEND D2: 子代理回合边界消费点——每轮开头把父侧经 subagent
202
- // action:'send' 注入队列(entry._injected)的消息按普通 user 回合推入子历史
203
- // (pushReal → 下一轮 chat 即含该指令)。由 executeAsyncSpawn 经 childRunOpts 贯通的
204
- // consumeInjected 回调承载(异步子代理专属——缺省 null:主会话/阻塞子代理零开销)。
205
- consumeInjected?.(agent)
206
-
207
- const lastRole = agent.history.at(-1)?.role
208
- if (lastRole === "user" || lastRole === "tool") {
209
- // 2026-09-05 实践轮:压缩检查/降级计数提为 runCompactionCheck(agent/run-stages.mjs——
210
- // CLI 对位 VS run-stages)——循环骨架此处只剩检查调用(recentCallSigs 对象引用回流)。
211
- await runCompactionCheck(agent, { threshold, callbacks, compactionOverhead, signal, recentCallSigs })
212
- }
213
-
214
- // 2026-09-05 实践轮:plan cadence + eng 状态注入提为 injectTurnReminders(run-stages)
215
- await injectTurnReminders(agent, { depth })
216
-
217
- const messages = [{ role: "system", content: systemPrompt }, ...agent.history]
218
- let response
219
-
220
- // Auto-think: classify difficulty and set reasoning effort on turn 0; silent on failure.
221
- if (agent.config?.agent?.autoThink && turn === 0) {
222
- const { classifyAndApply } = await import("./auto-think.mjs")
223
- await classifyAndApply(agent, turn).catch(() => {})
224
- }
225
-
226
- if (process.env.ADVISOR_DEBUG) console.error("[chat-call]", JSON.stringify({ turn, histLen: agent.history.length, lastRole: agent.history.at(-1)?.role }))
227
- try { response = await chat(agent.provider, {
228
- messages, tools: toolSchemas,
229
- onToken: callbacks.onToken,
230
- onReasoning: callbacks.onReasoning,
231
- onWait: callbacks.onWait,
232
- signal,
233
- streamRules: agent.config.agent?.streamRules ?? [],
234
- firedPatterns: streamRuleFired,
235
- // LOGGING(LOGGING.md):llm:* 事件的语义上下文(stage=turn 主循环回合——含
236
- // digest 消化轮 auto=true;child=子代理 id(spawn 时 stamp 于 child._logId))
237
- // §18.6 D-TR4:轨迹元数据增补(role/depth/kind/session/cwd——trace-store 只读
238
- // logCtx,签名不变);kind:depth>0 = subagent(consult 孩子 = consult)——子代理
239
- // 对回靠 role+depth+child id(children 无 _sessionStart——不经 depth-0 设置——
240
- // session 字段对子代理轨迹为 null——见 trace-store/agent.mjs 注释)。
241
- logCtx: {
242
- stage: "turn", turn: turn + 1, auto: autoTurn, child: agent._logId,
243
- role: agent._role ?? null,
244
- depth,
245
- kind: depth > 0 ? (agent._role === "consult" ? "consult" : "subagent") : "turn",
246
- session: agent._sessionStart ?? null,
247
- cwd: agent.cwd,
248
- traces: agent.config?.traces?.enabled !== false,
249
- },
250
- })
251
- } catch (e) {
252
- // User interrupt (Ctrl+I): controller.abort({ interrupt: true, message }).
253
- // Inject into history; the outer loop recreates the controller and resumes.
254
- if (e.name === "AbortError" && signal?.reason?.interrupt) {
255
- const msg = `[User interrupt: ${signal.reason.message}]`
256
- // Dedup: if already handled during tool execution (interrupt branch below),
257
- // don't push a duplicate — the outer loop still recreates the controller.
258
- if (agent.history.at(-1)?.content !== msg) {
259
- agent.history.push({ role: "user", content: msg })
260
- }
261
- }
262
- throw e
263
- }
264
-
265
- // 内置工具(Responses web_search)结果本地化:服务端已执行——入历史为 tool 消息;
266
- // 服务端 item id 是 msg_xxx 非 web_search_call_ 前缀——必须合成前缀(toItems 识别锚点),
267
- // 原始 id 存入 content(真机冒烟 2026-08-31 验证)。
268
- for (const btr of response.builtinToolResults ?? []) {
269
- if (!btr?.id) continue
270
- pushReal(agent, {
271
- role: "tool",
272
- tool_call_id: `web_search_call_${btr.id}`,
273
- content: JSON.stringify({ id: btr.id, query: btr.query ?? "", sources: btr.sources ?? [], status: btr.status ?? "completed" }),
274
- })
275
- }
276
-
277
- // Stream rule triggered mid-generation (action: "abort"): halt, inject the rule's
278
- // message as a reminder, retry from the same context.
279
- if (response.ruleTriggered) {
280
- if (response.content) {
281
- pushReal(agent, { role: "assistant", content: response.content })
282
- }
283
- const label = response.ruleName ? ` — stream rule "${response.ruleName}"` : ""
284
- agent.history.push({
285
- role: "user",
286
- content: `[System reminder${label}: ${response.ruleMessage}]`,
287
- })
288
- continue
289
- }
290
-
291
- // Stream rule warnings / finish-reason 警告(2026-09-05 实践轮——提为
292
- // injectResponseReminders,agent/run-stages.mjs——verbatim,语义零变)
293
- injectResponseReminders(agent, response)
294
-
295
- // User interrupted mid-generation (Ctrl+I): commit partial output + inject the
296
- // message, then signal the outer loop to recreate the controller and resume.
297
- if (response.interrupted) {
298
- if (response.content) {
299
- pushReal(agent, { role: "assistant", content: response.content })
300
- }
301
- agent.history.push({
302
- role: "user",
303
- content: `[User interrupt: ${response.interruptMessage}]`,
304
- })
305
- // §20.3 站点 #8(第 24 批):错误对象已自带 name/message——只补来源标注(缺 abortInfo 才补)
306
- throw annotateAbort(Object.assign(new Error("User interrupted"), { name: "AbortError" }), signal, "agent", "interrupted-response")
307
- }
308
-
309
- if (response.usage) {
310
- callbacks.onUsage?.(response.usage)
311
- if (response.usage.prompt_tokens != null) {
312
- agent._lastPromptTokens = response.usage.prompt_tokens
313
- agent._usageAtLen = agent.history.length
314
- }
315
- }
316
-
317
- // Warn on abnormal finish reasons — the response may be incomplete/truncated.
318
- // 2026-09-05 实践轮:finish-reason 注入随流规则警告提为 injectResponseReminders。
319
-
320
- if (response.toolCalls.length === 0) {
321
- const cr = handleCompletion(agent, response, depth, turn, guardPushbacks, honestReminderInjected, advisorPushbacks, callbacks)
322
- guardPushbacks = cr.guardPushbacks
323
- honestReminderInjected = cr.honestReminderInjected
324
- advisorPushbacks = cr.advisorPushbacks
325
- if (cr.action === "continue") continue
326
- if (depth === 0) {
327
- // End-of-run exploration distillation (CONTEXT-COMPACTION §5 + SEND-STALL-DISTILL
328
- // §2.1): async — the promise hangs on _pendingDistill, settling at the next run's
329
- // start or the TUI exit flush. Silent (N3): failure never blocks return/history.
330
- // §18.6 D-TR4:depth 透传(distill 轨迹元数据——与 compress 同通道)
331
- const distill = summarizeRunExplorations(agent, callbacks, signal, depth).catch(() => {})
332
- agent._pendingDistill = distill
333
- }
334
- return cr.content
335
- }
336
-
337
- // abort after chat completes, before committing history: don't commit a half-finished turn
338
- if (signal?.aborted) throw abortError(signal, "agent", "post-chat")
339
-
340
- pushReal(agent, {
341
- role: "assistant",
342
- content: response.content || null,
343
- tool_calls: response.toolCalls.map((tc) => ({
344
- id: tc.id, type: "function",
345
- function: { name: tc.name, arguments: tc.arguments },
346
- })),
347
- ...(response.reasoning && specForModel(agent.provider.model).reasoningEcho === "required"
348
- ? { reasoning_content: response.reasoning }
349
- : {}),
350
- })
351
-
352
- const results = await executeToolCalls(agent, toolByName, response.toolCalls, callbacks, depth, signal)
353
-
354
- // Ctrl+I interrupt during tool execution: skip committing partial results — inject
355
- // the interrupt and retry (placeholder results keep strict providers pairable).
356
- if (signal?.reason?.interrupt) {
357
- // 中断变更记账(2026-08-31 评审 #4):此分支的工具已全部执行完成(磁盘已变,execute 已完成),
358
- // 真实结果按语义不进历史(placeholder 替代)——但变更必须记账:否则 guard 看到
359
- // "本轮未改代码" 放行,评审/verify 门禁被绕过(文件改了却没评审)。
360
- // §29 fix A(2026-09-07):mutation-seq 记账已收敛到 dispatch runOne 执行成功即刻
361
- // (唯一记账点——本分支不再 noteMutations——不双计——中断+同批 launch seq 单计
362
- // 回归断言见 §29 T-A1i);此处仅剩 guard 标志 + touchedFiles 记账。
363
- for (const { toolCall, ok } of results) {
364
- const tool = toolByName.get(toolCall.name)
365
- if (!ok || !tool || !FILE_MUTATORS.has(toolCall.name)) continue
366
- agent._mutatedThisRun = true
367
- agent._calledAdvisorThisRun = false
368
- agent._verifiedThisRun = false
369
- agent._verifyPassed = undefined
370
- try {
371
- const args = JSON.parse(toolCall.arguments)
372
- const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
373
- for (const p of paths) {
374
- const abs = resolve(agent.cwd, p)
375
- if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
376
- }
377
- } catch { /* 畸形 args 不影响记账(touchedFiles 尽力而为) */ }
378
- }
379
- // The assistant tool_calls were committed above — synthesize placeholder tool
380
- // results BEFORE the interrupt message (strict providers 400 on dangling
381
- // tool_calls; consult P1, 2026-08-30).
382
- for (const tc of response.toolCalls) {
383
- agent.history.push({ role: "tool", tool_call_id: tc.id, content: "[Tool execution interrupted — results discarded]" })
384
- }
385
- agent.history.push({
386
- role: "user",
387
- content: `[User interrupt: ${signal.reason.message}]`,
388
- })
389
- callbacks.onTurnEnd?.(agent, turn)
390
- continue
391
- }
392
-
393
- // Model is executing tools → real work: reset guard pushback counters
394
- guardPushbacks = 0
395
- advisorPushbacks = 0
396
-
397
- // Commit tool results (pairing, multimodal deferral, mutation accounting, reindex)
398
- await recordToolResults(agent, toolByName, results)
399
-
400
- injectPostTurn(agent, results, recentCallSigs, callbacks, turn)
401
- }
402
-
403
- throw new ContinueError(maxTurns)
404
- } catch (e) {
405
- thrownError = e
406
- throw e
407
- } finally {
408
- // 2026-09-05 实践轮:回合收尾(consult 清理/async 池分流/guard 继承——原 425-462
409
- // 段 + collectSettledAsync 466-494 整体迁 agent/run-stages.mjs finalizeAgentTurn——
410
- // CLI 对位 VS run-stages——finally 只剩一行调用 + 骨架注释)。
411
- await finalizeAgentTurn(agent, { signal, autoTurn, suspDriven, thrownError, depth })
412
- }
413
- }
@@ -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
- }