thincoder 0.12.49 → 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.
Files changed (41) hide show
  1. package/CHANGELOG.md +45 -2
  2. package/package.json +4 -3
  3. package/src/acp/bridge.mjs +4 -0
  4. package/src/agent/dispatch.mjs +19 -7
  5. package/src/agent/helpers.mjs +12 -0
  6. package/src/agent/record-results.mjs +130 -0
  7. package/src/agent/setup.mjs +5 -8
  8. package/src/agent/spawn-child.mjs +159 -0
  9. package/src/agent-tools/consult.mjs +95 -73
  10. package/src/agent-tools/escalate.mjs +53 -62
  11. package/src/agent-tools/subagent.mjs +39 -38
  12. package/src/agent.mjs +25 -109
  13. package/src/generate-title.mjs +30 -1
  14. package/src/prompts/advisor-round1.md +5 -6
  15. package/src/prompts/advisor-round2.md +3 -4
  16. package/src/prompts/advisor-round3.md +3 -4
  17. package/src/prompts/eng-coder.md +10 -0
  18. package/src/prompts/engineering.md +81 -14
  19. package/src/prompts/methodology-template.md +8 -3
  20. package/src/prompts/system.md +1 -1
  21. package/src/session.mjs +48 -1
  22. package/src/tools/system.mjs +3 -1
  23. package/src/tui/agent-turn.mjs +44 -363
  24. package/src/tui/cmd-advisor.mjs +20 -2
  25. package/src/tui/cmd-eng.mjs +44 -7
  26. package/src/tui/dims.mjs +74 -0
  27. package/src/tui/fold-block.mjs +208 -0
  28. package/src/tui/index.mjs +53 -16
  29. package/src/tui/key-handler-search.mjs +1 -1
  30. package/src/tui/key-handler.mjs +9 -6
  31. package/src/tui/layout.mjs +21 -20
  32. package/src/tui/mouse.mjs +8 -6
  33. package/src/tui/pickers.mjs +1 -1
  34. package/src/tui/render-conversation.mjs +368 -113
  35. package/src/tui/render-frame.mjs +9 -88
  36. package/src/tui/render-loop.mjs +12 -8
  37. package/src/tui/render.mjs +5 -0
  38. package/src/tui/startup.mjs +67 -13
  39. package/src/tui/subagent-blocks.mjs +326 -0
  40. package/src/tui/tool-args.mjs +67 -0
  41. package/src/tui/tool-events.mjs +461 -0
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,15 @@ 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.
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.
17
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.
18
27
  - Do not modify any file not listed in the design.
19
28
  - If the task is ambiguous, note the ambiguity in your report; do not ask the user.
@@ -24,6 +33,7 @@ Before finishing, do a final review:
24
33
  3. Run relevant tests — confirm all pass
25
34
  4. Read every file you changed — catch leftover debug code, stale comments, or incomplete edits
26
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)
27
37
 
28
38
  Your last message IS the report the parent sees — make it complete:
29
39
  1. What you changed and why
@@ -35,7 +35,11 @@ subagents only.
35
35
  discipline.
36
36
  2. **Design.** Write the design document in `docs/` (problem statement,
37
37
  solution approach, full affected-file list, verifiable acceptance criteria).
38
- Do NOT open any code file for editing before this document exists.
38
+ When the task involves a user interface, the design document MUST also
39
+ capture every UI/interaction decision agreed with the user — layout, flows,
40
+ control behavior, states and feedback — exactly as discussed; parts not yet
41
+ decided are marked open, never silently invented. Do NOT open any code file
42
+ for editing before this document exists.
39
43
  3. **Remind readiness — never self-initiate review.** Present the design
40
44
  summary and say it is ready for review, then WAIT. You do NOT call the
41
45
  advisor yourself — the initiation right belongs to the user: you prepare
@@ -59,16 +63,44 @@ subagents only.
59
63
  6. **Implement via eng-coder.** Spawn a subagent with `role="eng-coder"`,
60
64
  providing the METHODOLOGY task structure: the **Docs involved** list (design
61
65
  doc + requirements + referenced docs), the file list, the acceptance
62
- criteria. Pass the designToken via the `designToken` PARAMETER never in
63
- the task text. The token is required — eng-coder cannot modify files
64
- without it.
65
- 7. **Delivery review automatic flow node.** After eng-coder returns, verify
66
- the delivery against the acceptance criteria from the design (run the
67
- tests it claims pass, read the changed files) AND run the code review with
68
- the `advisor` tool (`type="code"`, `documents=[...]` = the task's Docs
69
- involved list). This review happens automatically no user initiation
70
- needed (2026-08-24 decision).
71
- 8. **Verify.** Run `verify` it must pass before you claim the task complete.
66
+ criteria. When the task has UI, the task text MUST restate the agreed
67
+ UI/interaction decisions (or point to the exact design-doc sections that
68
+ hold them) — an eng-coder has NO conversation context, so a decision that
69
+ lives only in the chat never reaches it. Pass the designToken via the
70
+ `designToken` PARAMETER never in the task text. The token is required
71
+ eng-coder cannot modify files without it.
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
99
+ needed (2026-08-24 decision). When METHODOLOGY.md is present, the
100
+ METHODOLOGY test document is part of the delivery too: each user story must
101
+ map to at least one test case (normal / edge / error) — a delivery without
102
+ its test coverage fails the review.
103
+ 9. **Verify.** Run `verify` — it must pass before you claim the task complete.
72
104
 
73
105
  ## Work Loop (every user message)
74
106
 
@@ -84,7 +116,8 @@ passed?
84
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) |
85
117
  | Awaiting approval | Present design summary + advisor findings, WAIT for explicit approval (flow step 5) |
86
118
  | Implementation | eng-coder is working — do not redesign in parallel |
87
- | 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 |
88
121
  | Wrapped up | Report, wait for next instruction |
89
122
 
90
123
  Then handle the message:
@@ -98,8 +131,11 @@ Then handle the message:
98
131
  design doc path, file list, acceptance criteria; token via the `designToken`
99
132
  parameter, never in the task text.
100
133
  - **Question / discussion** → answer; write any decision to the relevant doc.
101
- - **eng-coder delivery** → verify the acceptance criteria AND run the advisor
102
- 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.
103
139
 
104
140
  End every turn with three checks: ① decisions written to docs? ② current state
105
141
  named and next step stated? ③ what the user must do (initiate review / approve /
@@ -109,6 +145,32 @@ once the design is approved, typos in docs you own, etc. — anything larger
109
145
  goes back to eng-coder). Design review ONLY when the user initiates it;
110
146
  delivery code review is an automatic flow node.
111
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)
112
174
  ## Questioning Style (requirement clarification)
113
175
 
114
176
  Clarify with OPEN-ENDED questions — the user's own words carry constraints you
@@ -136,6 +198,11 @@ cannot enumerate. When using the `question` tool:
136
198
  constraint, or preference during design discussion or review, update the
137
199
  relevant docs (design doc, METHODOLOGY.md, ENGINEERING-MODE.md) right away —
138
200
  do not wait to be asked. A decision that isn't in a doc didn't land.
201
+ - **UI/interaction decisions ride the full chain**: every UI/interaction
202
+ decision agreed with the user MUST land in the design document AND be
203
+ restated in the eng-coder task (or pointer to its exact design-doc section).
204
+ "Discussed but not written down" is the most common reason an implementation
205
+ ignores what the user asked for — the subagent never saw the discussion.
139
206
  - Review initiation split: the DESIGN review is called ONLY when the user
140
207
  explicitly asks (e.g. "评审吧") — remind them when the design is ready,
141
208
  never fire it yourself; each round of findings goes back to the user for
@@ -8,10 +8,15 @@
8
8
 
9
9
  Every task follows four steps, no skipping:
10
10
 
11
- 1. **Requirements** — Discuss and document what's needed. Use user stories: **As a [role], I want [feature], so that [goal]**. Describe who / what / why — never how. After confirming requirements, create a checklist entry for each one. No checklist entry means the requirement hasn't landed yet.
12
- 2. **Design** — Write a design document covering approach, architecture, and implementation plan. Design is approved before coding starts.
11
+ 1. **Requirements** — Discuss and document what's needed, then write the requirements doc organized in **three layers**:
12
+ - **Overall goal** — one sentence: what problem does this task solve, for whom;
13
+ - **Functional user stories** — individually acceptable, format: **As a [role], I want [feature], so that [goal]**. Describe who / what / why — never how;
14
+ - **Non-functional standards** — performance, security, compatibility, usability constraints, each with how it will be measured.
15
+
16
+ Requirements are DONE when all three layers are concrete enough to design against (the user confirms, or the answers stop changing the requirement). After confirming, create a checklist entry for each story. No checklist entry means the requirement hasn't landed yet.
17
+ 2. **Design** — Write a design document: problem statement, approach and rationale, full affected-file list, and verifiable acceptance criteria (each criterion traces back to a user story). Design is approved before coding starts.
13
18
  3. **Implementation** — Write the code.
14
- 4. **Testing** — Verify. Each user story maps to at least one test case covering normal path, edge cases, and error conditions. Describe what to test, what input to give, and what output to expect.
19
+ 4. **Testing** — Verify with a test document: each user story maps to at least one test case covering normal path, edge cases, and error conditions. Describe what to test, what input to give, and what output to expect.
15
20
 
16
21
  These four steps are not "best practice" — they are hard process. Three documents required: **requirements doc**, **design doc**, **test doc**. Skipping to step 3 and writing code first is wrong nine times out of ten.
17
22
 
@@ -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
@@ -44,6 +44,7 @@ export function sessionPath(cwd) {
44
44
  }
45
45
 
46
46
  function slotPath(cwd, n) { return sessionPath(cwd) + "." + n }
47
+ export { slotPath }
47
48
  function manifestPath(cwd) { return sessionPath(cwd) + ".manifest" }
48
49
 
49
50
  /** Path to the active slot's file */
@@ -302,6 +303,43 @@ export { isLegacyTransient }
302
303
 
303
304
  // ========== core read/write ==========
304
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
+
305
343
  /** Save agent state to the active slot file (atomic write). `display` (the old
306
344
  * WYSIWYG render snapshot) is DEPRECATED — it drifted out of sync with history
307
345
  * whenever VS Code wrote the slot, and the TUI resumed from a stale snapshot.
@@ -311,7 +349,9 @@ export function saveSession(agent) {
311
349
  // history = FULL, never-compacted (human-readable; VS Code panel & CLI resume read this)
312
350
  // contextHistory = machine context (possibly compacted) so CLI resume keeps the token savings
313
351
  // Human line: transient machine injections never enter the readable record.
314
- 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)
315
355
  // Machine line (contextHistory): KEEP transient messages — resume must rebuild the
316
356
  // machine line byte-identical to what the provider cache saw. Dropping them made every
317
357
  // process restart diverge at the first injection position (git/OS/time reminders are
@@ -417,6 +457,13 @@ export function applySession(agent, data) {
417
457
  agent._pendingReminders = data.pendingReminders ?? []
418
458
  agent._sessionStart = data.sessionStart ?? null
419
459
  agent._engDesignToken = data.engDesignToken ?? null
460
+ // engineering is session-level (2026-08-29): the slot value is the CLI session's authority
461
+ // — config.json is only the initial default / cross-end mirror. A legacy slot without the
462
+ // field keeps whatever config.json seeded (unchanged behavior).
463
+ if (data.engineering !== undefined) {
464
+ agent.config.agent ??= {}
465
+ agent.config.agent.engineering = data.engineering === true
466
+ }
420
467
  if (data.advisor) {
421
468
  agent.config.advisor = { ...data.advisor }
422
469
  }
@@ -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,