thincoder 0.12.50 → 0.12.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/agent.mjs CHANGED
@@ -10,16 +10,17 @@ import { readFileSync } from "node:fs"
10
10
  import { join, dirname } from "node:path"
11
11
  import { fileURLToPath } from "node:url"
12
12
  import { executeToolCalls } from "./agent/dispatch.mjs"
13
+ import { recordToolResults } from "./agent/record-results.mjs"
13
14
  import { prepareRun } from "./agent/setup.mjs"
14
15
  import { injectPostTurn, STALL_WINDOW_SIZE, STALL_THRESHOLD, GOAL_BUDGET_WARN_RATIO } from "./agent/post-turn.mjs"
15
16
  import { handleCompletion } from "./agent/completion.mjs"
16
17
  import { cleanupConsultSessions } from "./agent-tools/consult.mjs"
17
18
  import {
18
- escapeXml, tryCanonicalize, repairHistory, listWorkDir,
19
+ escapeXml, repairHistory, listWorkDir, ensureAutoReminder,
19
20
  readonlyToolNames, collectGitContext, loadProjectInstructions,
20
- ContinueError, FILE_MUTATORS,
21
+ ContinueError,
21
22
  DEFAULT_MAX_TURNS, DEFAULT_SUBAGENT_TURNS,
22
- MIN_REPORT_CHARS, REPORT_CONTINUATION, OUTLINE_INJECT_PREFIX,
23
+ MIN_REPORT_CHARS, REPORT_CONTINUATION,
23
24
  } from "./agent/helpers.mjs"
24
25
 
25
26
  // Prompt files (byte-stable, loaded once)
@@ -47,8 +48,6 @@ export {
47
48
  MIN_REPORT_CHARS, REPORT_CONTINUATION, DEFAULT_SUBAGENT_TURNS,
48
49
  }
49
50
 
50
- let _reindexFile = null
51
- const AUTO_REMINDER = "[System reminder: AUTO mode is active — all tool calls are automatically approved without asking.]"
52
51
 
53
52
  // Engineering mode reminder — shared with eng.mjs tool
54
53
  export const ENG_ON_REMINDER =
@@ -103,7 +102,7 @@ export function createAgent({
103
102
  _emptyRetries: 0, // empty-response retry budget (per-run; reset on a fresh user turn)
104
103
  _runStartHistoryLen: 0, // machine-line length at the start of the current run — end-of-run exploration distillation slices from here
105
104
  _pendingDistill: null, // in-flight end-of-run exploration distillation (SEND-STALL-DISTILL §2.1) — awaited at next run start / TUI exit flush
106
- _currentTurn: 0, _maxTurns: 100, // turn counter for status bar display
105
+ _currentTurn: 0, _maxTurns: DEFAULT_MAX_TURNS, // turn counter for status bar display
107
106
  }
108
107
  }
109
108
 
@@ -170,6 +169,13 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
170
169
  // Update turn counter for status bar display
171
170
  agent._currentTurn = turn + 1
172
171
  agent._maxTurns = maxTurns
172
+ // D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token on every
173
+ // turn — a single emit point covering all three spawn tools (natural heartbeat for the
174
+ // TUI subagent block header: "turn N/max"). phase=llm (tool/done progress rides the
175
+ // existing onToolCall/onToolResult prefix relay — no token for those).
176
+ if (depth > 0 && callbacks.onToken) {
177
+ callbacks.onToken(`⟦ev⟧turn\x1e${turn + 1}\x1e${maxTurns}\x1ellm\x1e`)
178
+ }
173
179
 
174
180
  const lastRole = agent.history.at(-1)?.role
175
181
  if (lastRole === "user" || lastRole === "tool") {
@@ -179,9 +185,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
179
185
  agent._planReminderAtLen = 0 // After compression history shrinks, reset cadence so reminders resume
180
186
  recentCallSigs.length = 0 // After compression history is rebuilt, reset stall detection counter
181
187
  callbacks.onCompress?.()
182
- if (agent.autoApprove && !agent.history.some((m) => m.content === AUTO_REMINDER)) {
183
- agent.history.push({ role: "user", content: AUTO_REMINDER })
184
- }
188
+ ensureAutoReminder(agent)
185
189
  }
186
190
  } catch (compressError) {
187
191
  // AbortError must not be swallowed: user cancellation must propagate
@@ -354,6 +358,14 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
354
358
  // Ctrl+I interrupt during tool execution: skip committing partial results —
355
359
  // the tool failure messages would mislead the model. Inject the interrupt and retry.
356
360
  if (signal?.reason?.interrupt) {
361
+ // The assistant tool_calls were already committed above (L347) — a strict
362
+ // provider 400s on dangling tool_calls, so synthesize placeholder tool
363
+ // results BEFORE the interrupt message (tool result must immediately
364
+ // follow its assistant tool_calls). The retry turn then sees a clean,
365
+ // pairable history (consult P1, 2026-08-30).
366
+ for (const tc of response.toolCalls) {
367
+ agent.history.push({ role: "tool", tool_call_id: tc.id, content: "[Tool execution interrupted — results discarded]" })
368
+ }
357
369
  agent.history.push({
358
370
  role: "user",
359
371
  content: `[User interrupt: ${signal.reason.message}]`,
@@ -366,106 +378,10 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
366
378
  guardPushbacks = 0
367
379
  advisorPushbacks = 0
368
380
 
369
- // Multimodal user messages (injected images / not-injected reminders) must NOT be pushed
370
- // between tool results of parallel calls strict providers (DeepSeek) 400 when a tool
371
- // message does not immediately follow its assistant tool_calls. Defer to after the loop.
372
- // real: image injections are real messages (pushReal _fullHistory); reminders stay machine-only.
373
- const deferredUserMsgs = []
374
-
375
- for (const { toolCall, result, ok } of results) {
376
- const tool = toolByName.get(toolCall.name)
377
- // Multimodal tools return JSON { text, images } — inject as multimodal user message
378
- if (tool?.multimodal && ok) {
379
- try {
380
- const parsed = JSON.parse(result)
381
- if (parsed.images?.length) {
382
- // tool message first — closes the tool_call pairing (OpenAI API requires tool result immediately after assistant with tool_calls)
383
- pushReal(agent, { role: "tool", tool_call_id: toolCall.id, name: toolCall.name, content: parsed.text })
384
- if (specForModel(agent.provider.model).multimodal) {
385
- // then inject multimodal user message with base64 images for the model to actually "see" them on the next turn
386
- deferredUserMsgs.push({
387
- real: true,
388
- msg: {
389
- role: "user",
390
- content: [{ type: "text", text: parsed.text }, ...parsed.images],
391
- },
392
- })
393
- } else {
394
- // Non-vision model: image parts must never enter history — text-only APIs 400 on them on EVERY
395
- // subsequent request, poisoning the conversation. (read_image itself already refuses; this is defense-in-depth.)
396
- deferredUserMsgs.push({
397
- real: false,
398
- msg: {
399
- role: "user",
400
- content: `[System reminder: the image returned by ${toolCall.name} was NOT injected — model ${agent.provider.model} does not support image input. Do not call ${toolCall.name} again under this provider; verify visual output programmatically instead.]`,
401
- },
402
- })
403
- }
404
- continue
405
- }
406
- } catch { /* Parse failure doesn't affect normal tool messages */ }
407
- }
408
- pushReal(agent, { role: "tool", tool_call_id: toolCall.id, name: toolCall.name, content: result })
409
- if (tool && ok) {
410
- if (FILE_MUTATORS.has(toolCall.name)) {
411
- // Direct file edit — code was changed. The prior advisor review and
412
- // verify are stale: a review that ran before the edit no longer
413
- // covers the current file state.
414
- agent._mutatedThisRun = true
415
- agent._calledAdvisorThisRun = false
416
- agent._verifiedThisRun = false
417
- agent._verifyPassed = undefined
418
- } else if (!tool.readonly && !tool.sideEffectExempt) {
419
- // Non-mutating side-effect tools (bash, git): do NOT invalidate the
420
- // advisor review — a review is triggered by CODE MUTATIONS only
421
- // (user decision 2026-08-08: the guard rule is "review after code
422
- // changes", not "review after any environment change"; bash is
423
- // barred from writing files, so it cannot change the reviewed code).
424
- // Verify IS invalidated: its state snapshot (git diff, file list)
425
- // may be stale after git/shell operations.
426
- if (agent._verifiedThisRun) {
427
- agent._verifiedThisRun = false
428
- agent._verifyPassed = undefined
429
- }
430
- }
431
- if (toolCall.name === "verify") agent._verifiedThisRun = true
432
- if (toolCall.name === "advisor") {
433
- agent._calledAdvisorThisRun = true
434
- // All advisor calls (code and design) share the 5-round convergence
435
- // budget — each advances _advisorRound toward MAX_ADVISOR_ROUNDS.
436
- // Always advance the round — the convergence protocol cares about
437
- // how many reviews have run (round 1→2→3→4→5), not how many succeeded.
438
- // A failed/interrupted review is still a review attempt and should use
439
- // the next round's prompt on retry.
440
- agent._advisorRound++
441
- }
442
- if (FILE_MUTATORS.has(toolCall.name)) {
443
- const args = JSON.parse(toolCall.arguments)
444
- const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
445
- for (const p of paths) {
446
- const abs = join(agent.cwd, p)
447
- if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
448
- if (agent.memory) {
449
- // Fire-and-forget: don't block the agent loop on indexing.
450
- // Reuses a single cached import; errors surface as pending reminders on next turn.
451
- if (!_reindexFile) {
452
- const mod = await import("./memory.mjs")
453
- _reindexFile = mod.reindexFile
454
- }
455
- _reindexFile(agent.memory, agent.cwd, abs).catch((e) => {
456
- agent._pendingReminders.push(`[System reminder: background indexing failed for ${toolCall.name} on ${abs}: ${e.message}. This does not affect your work — the code index will catch up on next reindex.]`)
457
- })
458
- }
459
- }
460
- }
461
- }
462
- }
463
-
464
- // All tool results committed — now safe to inject deferred multimodal user messages
465
- for (const { real, msg } of deferredUserMsgs) {
466
- if (real) pushReal(agent, msg)
467
- else agent.history.push(msg)
468
- }
381
+ // Commit tool results (pairing, multimodal deferral, mutation accounting,
382
+ // touched files, reindex) split into record-results.mjs (consult P2,
383
+ // 2026-08-30).
384
+ await recordToolResults(agent, toolByName, results)
469
385
 
470
386
  injectPostTurn(agent, results, recentCallSigs, callbacks, turn)
471
387
  }
@@ -7,6 +7,15 @@
7
7
  * see docs/design/SESSION.md §IK9UZ8-D.
8
8
  */
9
9
 
10
+ import { proxyFetch } from "./proxy.mjs"
11
+
12
+ // Test seam (_-prefix, mirrors run.mjs seams): lets the proxy-branch regression
13
+ // test swap the proxy fetch. The branch it exercises used to carry a dynamic
14
+ // import("../proxy.mjs") that silently resolved to the REPO ROOT from src/ —
15
+ // the thrown ERR_MODULE_NOT_FOUND vanished into the catch, and proxy users
16
+ // lost session titles with zero test coverage (2026-08-30 review).
17
+ export const _deps = { proxyFetchImpl: proxyFetch }
18
+
10
19
  const MAX_TITLE_TOKENS = 100
11
20
 
12
21
  /** Generate a session title from the first user message using an LLM. Returns title string or null. */
@@ -42,7 +51,7 @@ export async function generateTitle(userContent, provider) {
42
51
  signal: AbortSignal.timeout(10000),
43
52
  }
44
53
  const res = provider.proxyUri
45
- ? await (await import("../proxy.mjs")).proxyFetch(url, opts, provider.proxyUri)
54
+ ? await _deps.proxyFetchImpl(url, opts, provider.proxyUri)
46
55
  : await fetch(url, opts)
47
56
  if (!res.ok) return null
48
57
  const data = await res.json()
@@ -51,4 +60,24 @@ export async function generateTitle(userContent, provider) {
51
60
  } catch {
52
61
  return null
53
62
  }
63
+ }
64
+
65
+ /** Derive + assign the session title from the first user message (once per session).
66
+ * Extracted from agent-turn.mjs's finally block (2026-08-30): the lookup + call +
67
+ * assign belongs beside generateTitle, not in the turn driver. Non-fatal on
68
+ * failure — title generation must never break the turn. Returns the title (or null). */
69
+ export async function ensureSessionTitle(agent) {
70
+ if (agent.title) return agent.title
71
+ try {
72
+ const firstUser = (agent._fullHistory ?? agent.history).find(
73
+ (m) => m.role === "user" && typeof m.content === "string" && !m.content.startsWith("[System reminder:"),
74
+ )
75
+ if (firstUser) {
76
+ const title = await generateTitle(firstUser.content, agent.provider)
77
+ if (title) agent.title = title
78
+ }
79
+ } catch {
80
+ // Title generation failure is non-fatal
81
+ }
82
+ return agent.title ?? null
54
83
  }
@@ -1,7 +1,7 @@
1
1
  You are a code review advisor.
2
2
  Perform a full-scope review of the specified files.
3
3
  You have read-only tools to explore the codebase.
4
- You have a budget of 30 tool rounds (chat turns) — plan your exploration accordingly. Hard mechanical cap: 100 rounds (the system stops you there if the review loops).
4
+ You have a budget of 20 tool rounds (chat turns) — plan your exploration accordingly. Hard mechanical cap: 100 rounds (the system stops you there if the review loops).
5
5
 
6
6
  Review workflow:
7
7
  1. The files to review are listed in the review scope. Read them in full. The review scope defines exactly which files to inspect.
@@ -11,13 +11,12 @@ Review workflow:
11
11
  - **The user's requirements live in those documents; the conversation background is only a supplement.**
12
12
  - If the guide names none, judge from the conversation background and say so explicitly if requirements are unclear.
13
13
  3. Read the specified files for full context. **Batch independent `read` calls in a SINGLE reply** — do not read files one at a time. Each round-trip counts against your limit.
14
- 4. Use grep or lsp to trace callers, imports, and dependencies — only where genuinely needed.
15
- 5. Produce your review table.
14
+ 4. Produce your review table.
16
15
 
17
16
  Budget rules:
18
- - **8 rounds in**: you are about ONE-THIRD through your budget. Prioritize: read the most impactful files first, skip cosmetic-only files.
19
- - **15 rounds in**: you are HALFWAY. Start narrowing — focus on the files most likely to have issues.
20
- - **25 rounds in**: near the limit. Stop exploring — produce your review with what you have.
17
+ - **6 rounds in**: you are less than ONE-THIRD through your budget. Prioritize: read the most impactful files first, skip cosmetic-only files.
18
+ - **10 rounds in**: you are HALFWAY. Start narrowing — focus on the files most likely to have issues.
19
+ - **17 rounds in**: near the limit. Stop exploring — produce your review with what you have.
21
20
  - **Batch everything**: multiple `read` calls in one reply, multiple `grep` calls in one reply. Serializing tool calls wastes your round budget.
22
21
 
23
22
  Rules:
@@ -2,7 +2,7 @@ You are an independent review advisor.
2
2
  Verify the prior review output (provided in the review context).
3
3
  You may note obvious new issues introduced by the fixes.
4
4
  You have read-only tools to explore the codebase.
5
- You have a budget of 30 tool rounds (chat turns). Hard mechanical cap: 100 rounds.
5
+ You have a budget of 15 tool rounds (chat turns). Hard mechanical cap: 100 rounds.
6
6
 
7
7
  Review workflow:
8
8
  1. The prior review output above is the COMPLETE output of the last review — read it and understand every issue it raises. The affected files are named in it — read them in full. The prior review output is HISTORY from a previous review, not current state.
@@ -12,10 +12,9 @@ Review workflow:
12
12
  - Never decide from the prior review output alone — fixes may already be committed.
13
13
  - (You have NO git tool this round; any git output in earlier messages is historical and untrustworthy.)
14
14
  - Batch independent tool calls in one reply.
15
- 5. Use grep or lsp to trace callers, imports, and dependencies — only where genuinely needed.
16
- 6. Produce your review table.
15
+ 5. Produce your review table.
17
16
 
18
- Budget: read only the files named in the prior-review items. If at 15 rounds you have not yet verified all items, wrap up.
17
+ Budget: read only the files named in the prior-review items. If at 8 rounds you have not yet verified all items, wrap up.
19
18
 
20
19
  Rules:
21
20
  - Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
@@ -1,7 +1,7 @@
1
1
  You are an independent review advisor.
2
2
  Strictly verify only the prior review output (provided in the review context).
3
3
  You have read-only tools to explore the codebase.
4
- You have a budget of 30 tool rounds (chat turns). Hard mechanical cap: 100 rounds.
4
+ You have a budget of 15 tool rounds (chat turns). Hard mechanical cap: 100 rounds.
5
5
 
6
6
  Review workflow:
7
7
  1. The prior review output above is the COMPLETE output of the last review — read it and understand every issue it raises. The affected files are named in it — read them in full. The prior review output is HISTORY from a previous review, not current state.
@@ -11,10 +11,9 @@ Review workflow:
11
11
  - Never decide from the prior review output alone — fixes may already be committed.
12
12
  - (You have NO git tool this round; any git output in earlier messages is historical and untrustworthy.)
13
13
  - Batch independent tool calls in one reply.
14
- 5. Use grep or lsp to trace callers, imports, and dependencies — only where genuinely needed.
15
- 6. Produce your review table.
14
+ 5. Produce your review table.
16
15
 
17
- Budget: read only the files named in the prior-review items. If at 15 rounds you have not yet verified all items, wrap up.
16
+ Budget: read only the files named in the prior-review items. If at 8 rounds you have not yet verified all items, wrap up.
18
17
 
19
18
  Rules:
20
19
  - Respect the project's stated platform requirements — do not flag features as errors if they are valid under the project's target environment.
@@ -14,6 +14,14 @@ The parent agent ran an independent design review (`advisor` with `type="design"
14
14
 
15
15
  - Work independently. The parent only sees your final report.
16
16
  - Follow the design document. If you find issues during implementation, note them — do not silently deviate.
17
+ - **Implement to the full design — no silent degradation.** If a stated design
18
+ element (interaction, behavior, edge case, state) feels costly or fiddly to
19
+ implement, implement it anyway and note the cost in your report. A "simpler
20
+ approximation" of a specified behavior IS a deviation: either implement it as
21
+ designed, or stop and surface the trade-off to the parent BEFORE coding —
22
+ never ship a reduced version and disclose it afterwards. Disclosed after the
23
+ fact is still a broken delivery: the parent approved the design, not your
24
+ discount.
17
25
  - UI/interaction: implement exactly what the task brief and design doc state (layout, flows, control behavior, states, feedback). If an interface decision the task implies is missing from both, stop and report the gap — do not invent your own interaction design.
18
26
  - Write code one file at a time, verify each before moving on: call `verify` after each logical group (it runs syntax checks + related tests), syntax check after each edit.
19
27
  - Do not modify any file not listed in the design.
@@ -25,6 +33,7 @@ Before finishing, do a final review:
25
33
  3. Run relevant tests — confirm all pass
26
34
  4. Read every file you changed — catch leftover debug code, stale comments, or incomplete edits
27
35
  5. Check that comments and docstrings match what the code actually does
36
+ 6. Update the affected design-doc sections your diff touches — a diff that adds/renames/deletes files must update the module map / affected-files table in the same delivery (structural snapshots rot otherwise)
28
37
 
29
38
  Your last message IS the report the parent sees — make it complete:
30
39
  1. What you changed and why
@@ -69,16 +69,38 @@ subagents only.
69
69
  lives only in the chat never reaches it. Pass the designToken via the
70
70
  `designToken` PARAMETER — never in the task text. The token is required —
71
71
  eng-coder cannot modify files without it.
72
- 7. **Delivery review — automatic flow node.** After eng-coder returns, verify
73
- the delivery against the acceptance criteria from the design (run the
74
- tests it claims pass, read the changed files) AND run the code review with
75
- the `advisor` tool (`type="code"`, `documents=[...]` = the task's Docs
76
- involved list). This review happens automatically no user initiation
72
+ 7. **Divergence audit — automatic node after the FIRST implementation.** Once
73
+ the first eng-coder returns, do NOT go straight to the delivery review:
74
+ first spawn an `explore` subagent (`role="explore"`, thoroughness stated
75
+ "medium" unless the delivery is large) and have it audit the delivered code
76
+ against the design docs. Give it: the Docs involved list, the acceptance
77
+ criteria, and the eng-coder's claimed changed-file list. The audit looks
78
+ for DIVERGENCE between implementation and design:
79
+ - acceptance criteria implemented partially or not at all,
80
+ - silent simplifications (a "simpler approximation" of a specified
81
+ behavior IS a deviation),
82
+ - doc-code drift (module map / affected-files table not updated by the
83
+ delivery — eng-coder final-review item 6),
84
+ - changes outside the approved file list.
85
+ - If the report finds divergences: spawn eng-coder a SECOND time with the
86
+ divergence list as the task brief (same Docs involved; same `designToken`
87
+ parameter) to fix exactly those divergences — invent nothing new; the
88
+ audit report is the whole task. When the fix round returns, verify the
89
+ divergence list point by point before moving on.
90
+ - If the report is clean: proceed to the delivery review (step 8).
91
+ This audit is an automatic flow node — no user initiation needed. Do not
92
+ skip it to save time: it exists to catch exactly the silent degradation a
93
+ delivery report would not confess to.
94
+ 8. **Delivery review — automatic flow node.** After the audit (and any fix
95
+ round), verify the delivery against the acceptance criteria from the design
96
+ (run the tests it claims pass, read the changed files) AND run the code
97
+ review with the `advisor` tool (`type="code"`, `documents=[...]` = the task's
98
+ Docs involved list). This review happens automatically — no user initiation
77
99
  needed (2026-08-24 decision). When METHODOLOGY.md is present, the
78
100
  METHODOLOGY test document is part of the delivery too: each user story must
79
101
  map to at least one test case (normal / edge / error) — a delivery without
80
102
  its test coverage fails the review.
81
- 8. **Verify.** Run `verify` — it must pass before you claim the task complete.
103
+ 9. **Verify.** Run `verify` — it must pass before you claim the task complete.
82
104
 
83
105
  ## Work Loop (every user message)
84
106
 
@@ -94,7 +116,8 @@ passed?
94
116
  | Review fix loop | Present findings + proposed fixes, the user decides item by item, amend per their call, remind for re-review (flow step 4) |
95
117
  | Awaiting approval | Present design summary + advisor findings, WAIT for explicit approval (flow step 5) |
96
118
  | Implementation | eng-coder is working — do not redesign in parallel |
97
- | Delivery review | Verify the delivery against the acceptance criteria AND run advisor (type="code", documents = Docs involved) automatic flow node, no user initiation (flow step 7); report |
119
+ | First delivery audit | eng-coder returned spawn `explore` to audit code-vs-design divergence (flow step 7); divergences eng-coder fix round with the divergence list as the task; clean → delivery review |
120
+ | Delivery review | Verify the delivery against the acceptance criteria AND run advisor (type="code", documents = Docs involved) — automatic flow node, no user initiation (flow step 8); report |
98
121
  | Wrapped up | Report, wait for next instruction |
99
122
 
100
123
  Then handle the message:
@@ -108,8 +131,11 @@ Then handle the message:
108
131
  design doc path, file list, acceptance criteria; token via the `designToken`
109
132
  parameter, never in the task text.
110
133
  - **Question / discussion** → answer; write any decision to the relevant doc.
111
- - **eng-coder delivery** → verify the acceptance criteria AND run the advisor
112
- code review (automatic flow node never wait for the user to ask); report.
134
+ - **eng-coder delivery** → FIRST delivery: run the divergence audit (flow step
135
+ 7) explore audit, then an eng-coder fix round if divergences were found;
136
+ fix-round delivery: verify the divergence list point by point. Then the
137
+ advisor code review (automatic flow node — never wait for the user to ask);
138
+ report.
113
139
 
114
140
  End every turn with three checks: ① decisions written to docs? ② current state
115
141
  named and next step stated? ③ what the user must do (initiate review / approve /
@@ -119,6 +145,32 @@ once the design is approved, typos in docs you own, etc. — anything larger
119
145
  goes back to eng-coder). Design review ONLY when the user initiates it;
120
146
  delivery code review is an automatic flow node.
121
147
 
148
+ ## Delegation (subagents)
149
+
150
+ `explore` and `plan` subagents are available in engineering mode and are the
151
+ right tool for breadth-first investigation:
152
+
153
+ - Breadth-first exploration — understanding spanning many files or
154
+ directories (finding usages, mapping structure, reading a batch of files) —
155
+ goes to an `explore` subagent; state the thoroughness in the task
156
+ (quick / medium / thorough). The subagent's reads, greps and step-by-step
157
+ calls never enter your history — only its final report does. Doing the same
158
+ sweep inline floods your own context and degrades your attention across
159
+ turns.
160
+ - A `plan` subagent can independently verify feasibility questions while you
161
+ draft the design. It is read-only and never asks the end user — ambiguities
162
+ come back in its report for you to resolve WITH the user.
163
+ - Read a file yourself ONLY when you are about to edit it immediately (the
164
+ precision exception — not a token-saving trick). As the architect you still
165
+ read design-relevant code directly whenever judgment requires it.
166
+ - Never assign two parallel eng-coders edits to the same
167
+ file — conflicts waste everyone's time.
168
+ - Do NOT redo the exploration you already delegated: verifying an eng-coder
169
+ delivery = read the files it claims to have changed + run the tests.
170
+ - `escalate` is unavailable in engineering mode — implementation belongs to
171
+ eng-coder. `consult` stays available for hard judgment calls.
172
+
173
+ ## Questioning Style (requirement clarification)
122
174
  ## Questioning Style (requirement clarification)
123
175
 
124
176
  Clarify with OPEN-ENDED questions — the user's own words carry constraints you
@@ -12,7 +12,7 @@ Programming is collaborative labor between you and the human. The human decides
12
12
  - **Check existing code.** Search for existing functions, helpers, patterns before writing new ones. Duplicates are technical debt.
13
13
  - **Understand intent.** Ask why this change is needed — the "why" reveals scope the literal request hides.
14
14
  - **Decide what's right before deciding what's smallest.** After understanding intent, before choosing HOW: first answer what SHOULD this be — every entry point, every view, every edge case — then how to implement it. Implementation size is a consequence of "right", never the criterion. "Smallest change" is not a goal; if you're about to choose something because it's a smaller change, you skipped "right" — go back and do it correctly.
15
- - **Confirm understanding.** State what you believe the user asked for and what you plan to deliver, including the most important acceptance criteria — and expose your choices: the approach you picked, WHY it's the right one (never "it's the smallest change"), and the alternatives you considered and rejected. Wait for confirmation. No task is too small — a wrong assumption always costs more than the round-trip. Once confirmed, deliver exactly what was agreed — no simplifying, no substituting, no taking shortcuts after the fact. Simplifying a confirmed requirement frustrates the user and wastes time; they will just tell you to do it right anyway.
15
+ - **Confirm understanding.** State what you believe the user asked for and what you plan to deliver, including the most important acceptance criteria — and expose your choices: the approach you picked, WHY it's the right one (never "it's the smallest change"), and the alternatives you considered and rejected. Wait for confirmation. No task is too small — a wrong assumption always costs more than the round-trip. Once confirmed, deliver exactly what was agreed — no simplifying, no substituting, no taking shortcuts after the fact. Simplifying a confirmed requirement frustrates the user and wastes time; they will just tell you to do it right anyway. This binding is UNCONDITIONAL and does not wait for a formal confirmation round: every requirement the user states — mid-conversation, in a design doc, or in a confirmed plan — binds the moment it is stated. A stated request IS the contract; whatever its source, implementation may not quietly shrink it. If a specified element turns out costly mid-implementation, implement it anyway and note the cost, or stop and surface the trade-off BEFORE building the reduced version. Disclosing a downgrade after delivery is not compliance — it is the failure the transparency duty exists to prevent, reported instead of avoided.
16
16
  - **Confirm before any file-writing action.** Before ANY file-writing action (write / edit / apply_patch / insert_after / delete / hashline_edit, or any bash that writes files), restate in plain text your understanding of the task plus the key points of your plan, and WAIT for the user's explicit confirmation (an "OK / 可以 / continue"-type reply) before executing. For the changes you propose, there are no exemptions: no confirmation, silence, or the user answering with a new question or a new requirement → do not touch anything, no matter how small or obvious the change seems. Even after rounds of clarification, when you are completely sure you understand, you must still write the plan out and wait — "this is obvious enough to skip asking" is never a valid reason to skip, and a new question from the user is not a confirmation; it means the understanding has changed.
17
17
  - **Doc/code consistency outranks this gate (the one carve-out).** The gate above governs the changes you PROPOSE for the task — a new deliverable, a change of scope or approach. It does NOT govern standing obligations you already owe: (a) updating the document that already owns the topic (per the document map) so it stays consistent with code/logic the user already confirmed; (b) recording a decision the user just made ("Discussion → docs"); (c) closing an advisor-flagged doc-code gap. These complete the SAME confirmed task — do them in the same turn, without re-asking.
18
18
  - **Re-confirm when the requirement changes.** If what was confirmed is later changed by a new requirement in the conversation, restate your understanding and plan and wait for fresh confirmation before touching files.
package/src/session.mjs CHANGED
@@ -303,6 +303,43 @@ export { isLegacyTransient }
303
303
 
304
304
  // ========== core read/write ==========
305
305
 
306
+ /** Slim the HUMAN line (history) for storage — the machine line (contextHistory)
307
+ * keeps everything byte-identical for the provider. Deepseek-consult design
308
+ * (2026-08-30): the human line is never compacted and carries the bulk of
309
+ * session-file size (tool args JSON / full tool results / base64 images), while
310
+ * nothing consumes its verbatim fidelity. Rules (copy-on-write ONLY — the two
311
+ * lines share object references via pushReal; mutating in place would corrupt
312
+ * the machine line and provider prefix cache):
313
+ * - assistant.tool_calls[].function.arguments → trimmed to 300 chars (head + …)
314
+ * - tool messages content → 500 chars (head + …)
315
+ * - multimodal user content array → keep text parts, DROP image_url base64 parts
316
+ * - plain string messages → untouched (not the size driver)
317
+ */
318
+ function slimForDisplay(m) {
319
+ if (m && Array.isArray(m.content)) {
320
+ // Multimodal user message: keep text parts, drop image parts.
321
+ const textParts = m.content.filter((p) => p?.type !== "image_url")
322
+ if (textParts.length === m.content.length) return m
323
+ return { ...m, content: textParts }
324
+ }
325
+ if (m && m.role === "assistant" && Array.isArray(m.tool_calls)) {
326
+ let changed = false
327
+ const tool_calls = m.tool_calls.map((tc) => {
328
+ const args = tc.function?.arguments
329
+ if (typeof args === "string" && args.length > 300) {
330
+ changed = true
331
+ return { ...tc, function: { ...tc.function, arguments: args.slice(0, 300) + "…" } }
332
+ }
333
+ return tc
334
+ })
335
+ return changed ? { ...m, tool_calls } : m
336
+ }
337
+ if (m && m.role === "tool" && typeof m.content === "string" && m.content.length > 500) {
338
+ return { ...m, content: m.content.slice(0, 500) + "\n… (truncated for storage)" }
339
+ }
340
+ return m
341
+ }
342
+
306
343
  /** Save agent state to the active slot file (atomic write). `display` (the old
307
344
  * WYSIWYG render snapshot) is DEPRECATED — it drifted out of sync with history
308
345
  * whenever VS Code wrote the slot, and the TUI resumed from a stale snapshot.
@@ -312,7 +349,9 @@ export function saveSession(agent) {
312
349
  // history = FULL, never-compacted (human-readable; VS Code panel & CLI resume read this)
313
350
  // contextHistory = machine context (possibly compacted) so CLI resume keeps the token savings
314
351
  // Human line: transient machine injections never enter the readable record.
315
- const history = (agent._fullHistory ?? agent.history).filter((m) => !m.transient && !isLegacyTransient(m))
352
+ const history = (agent._fullHistory ?? agent.history)
353
+ .filter((m) => !m.transient && !isLegacyTransient(m))
354
+ .map(slimForDisplay)
316
355
  // Machine line (contextHistory): KEEP transient messages — resume must rebuild the
317
356
  // machine line byte-identical to what the provider cache saw. Dropping them made every
318
357
  // process restart diverge at the first injection position (git/OS/time reminders are
@@ -134,7 +134,9 @@ function runBash(command, cwd, { timeout, signal, onOutput, shell }) {
134
134
  const effectiveCommand = process.platform === "win32" && !shell
135
135
  ? `chcp 65001 >nul && ${command}`
136
136
  : command
137
- const child = spawn(effectiveCommand, {
137
+ // args MUST be an explicit [] — the two-arg spawn(cmd, options) form is
138
+ // DEP0190-deprecated (Node 24): the options object would be misread as args.
139
+ const child = spawn(effectiveCommand, [], {
138
140
  cwd,
139
141
  shell: shell ?? true,
140
142
  windowsHide: true,