mixdog 0.9.87 → 0.9.88

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 (160) hide show
  1. package/package.json +13 -7
  2. package/scripts/tool-failures.mjs +60 -24
  3. package/src/agents/heavy-worker/AGENT.md +3 -4
  4. package/src/agents/worker/AGENT.md +1 -2
  5. package/src/cli.mjs +8 -0
  6. package/src/defaults/cycle3-review-prompt.md +4 -4
  7. package/src/rules/agent/00-core.md +3 -0
  8. package/src/rules/agent/42-cycle3-agent.md +5 -6
  9. package/src/rules/lead/01-general.md +2 -0
  10. package/src/rules/shared/01-tool.md +5 -1
  11. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +3 -51
  12. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +59 -0
  13. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -2
  14. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +67 -0
  15. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +57 -4
  16. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +2 -3
  17. package/src/runtime/agent/orchestrator/config.mjs +29 -11
  18. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -1
  19. package/src/runtime/agent/orchestrator/internal-agents.mjs +1 -1
  20. package/src/runtime/agent/orchestrator/mcp/client.mjs +0 -24
  21. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +3 -3
  22. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +25 -17
  23. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +321 -22
  24. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -22
  25. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +28 -23
  26. package/src/runtime/agent/orchestrator/providers/gemini.mjs +10 -2
  27. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +0 -1
  28. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +0 -1
  29. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +2 -5
  30. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +27 -0
  31. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +23 -0
  32. package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +329 -0
  33. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +23 -1
  34. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +107 -20
  35. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +4 -3
  36. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +124 -12
  37. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +19 -2
  38. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +110 -75
  41. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +96 -113
  42. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +144 -10
  43. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +147 -117
  44. package/src/runtime/agent/orchestrator/session/loop/stop-hooks.mjs +88 -0
  45. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +25 -0
  46. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +47 -6
  48. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +485 -24
  49. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +100 -2
  50. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -0
  51. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +80 -14
  52. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +142 -2
  53. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +128 -32
  54. package/src/runtime/agent/orchestrator/session/manager.mjs +5 -1
  55. package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +77 -4
  56. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +70 -39
  57. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +91 -0
  58. package/src/runtime/agent/orchestrator/session/store/listing.mjs +141 -64
  59. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +253 -0
  60. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +211 -28
  61. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +313 -0
  62. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +622 -53
  63. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +43 -10
  64. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +53 -8
  65. package/src/runtime/agent/orchestrator/session/store/summary-rebuild-worker.mjs +20 -0
  66. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +113 -9
  67. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +2 -0
  68. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +476 -52
  69. package/src/runtime/agent/orchestrator/session/store.mjs +721 -196
  70. package/src/runtime/agent/orchestrator/session/token-native.mjs +2 -4
  71. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +19 -0
  72. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +4 -6
  73. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +146 -90
  74. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +6 -0
  75. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +93 -17
  76. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -0
  77. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +7 -7
  78. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +2 -0
  79. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +52 -0
  80. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +54 -51
  81. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +14 -15
  82. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +132 -8
  83. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +67 -3
  84. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +6 -1
  85. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +203 -38
  86. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +13 -1
  87. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +73 -19
  88. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  89. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +11 -8
  90. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +12 -2
  91. package/src/runtime/media/lanes.mjs +2 -2
  92. package/src/runtime/media/renditions.mjs +101 -17
  93. package/src/runtime/media/renditions.test.mjs +54 -0
  94. package/src/runtime/media/store.mjs +111 -43
  95. package/src/runtime/media/store.test.mjs +53 -11
  96. package/src/runtime/memory/index.mjs +26 -50
  97. package/src/runtime/memory/lib/core-memory-candidates.mjs +2 -7
  98. package/src/runtime/memory/lib/core-memory-store.mjs +2 -9
  99. package/src/runtime/memory/lib/cycle-scheduler.mjs +10 -0
  100. package/src/runtime/memory/lib/embedding-provider.mjs +3 -3
  101. package/src/runtime/memory/lib/embedding-worker.mjs +60 -15
  102. package/src/runtime/memory/lib/http-router.mjs +4 -0
  103. package/src/runtime/memory/lib/ko-morph.mjs +49 -6
  104. package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -18
  105. package/src/runtime/memory/lib/memory-cycle2.mjs +3 -3
  106. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -3
  107. package/src/runtime/memory/lib/memory-recall-store.mjs +24 -0
  108. package/src/runtime/memory/lib/query-handlers.mjs +26 -28
  109. package/src/runtime/memory/tool-defs.mjs +3 -3
  110. package/src/runtime/shared/atomic-file.mjs +5 -1
  111. package/src/runtime/shared/child-guardian.mjs +52 -2
  112. package/src/runtime/shared/provider-api-key.mjs +22 -5
  113. package/src/runtime/shared/tool-execution-contract.mjs +53 -0
  114. package/src/session-runtime/config-helpers.mjs +6 -5
  115. package/src/session-runtime/lifecycle-api.mjs +16 -4
  116. package/src/session-runtime/media-api.mjs +31 -16
  117. package/src/session-runtime/prewarm.mjs +2 -24
  118. package/src/session-runtime/runtime-core.mjs +33 -15
  119. package/src/session-runtime/runtime-tunables.mjs +0 -3
  120. package/src/session-runtime/session-lifecycle.mjs +0 -6
  121. package/src/session-runtime/session-title.mjs +228 -0
  122. package/src/session-runtime/session-turn-api.mjs +11 -7
  123. package/src/session-runtime/workflow-agents-api.mjs +16 -0
  124. package/src/session-runtime/workflow.mjs +7 -5
  125. package/src/standalone/agent-tool/job-views.mjs +90 -36
  126. package/src/standalone/agent-tool/spawn-flow.mjs +25 -216
  127. package/src/standalone/agent-tool/worker-index.mjs +8 -0
  128. package/src/standalone/agent-tool/worker-rows.mjs +2 -1
  129. package/src/standalone/agent-tool.mjs +17 -17
  130. package/src/standalone/agent-watchdog-registry.mjs +19 -2
  131. package/src/standalone/channel-daemon.mjs +8 -0
  132. package/src/standalone/memory-runtime-proxy.mjs +4 -7
  133. package/src/standalone/projects.mjs +4 -1
  134. package/src/standalone/usage-dashboard.mjs +25 -3
  135. package/src/tui/App.jsx +5 -3
  136. package/src/tui/app/resume-picker.mjs +7 -2
  137. package/src/tui/app/slash-dispatch.mjs +13 -3
  138. package/src/tui/app/transcript-row-estimate.mjs +1 -1
  139. package/src/tui/app/transcript-window.mjs +17 -93
  140. package/src/tui/app/use-transcript-scroll.mjs +32 -2
  141. package/src/tui/app/use-transcript-window.mjs +64 -114
  142. package/src/tui/components/Markdown.jsx +19 -2
  143. package/src/tui/components/Message.jsx +8 -3
  144. package/src/tui/components/ToolExecution.jsx +7 -3
  145. package/src/tui/dist/index.mjs +233 -121
  146. package/src/tui/engine/live-share.mjs +15 -5
  147. package/src/tui/engine/render-timing.mjs +2 -2
  148. package/src/tui/engine/session-api-ext.mjs +74 -4
  149. package/src/tui/engine/session-flow.mjs +3 -1
  150. package/src/tui/engine.mjs +2 -2
  151. package/src/tui/hooks/useSharedTick.mjs +0 -2
  152. package/src/tui/index.jsx +5 -5
  153. package/src/ui/statusline-agents.mjs +44 -11
  154. package/src/vendor/statusline/bin/statusline-route.mjs +26 -3
  155. package/src/workflows/default/WORKFLOW.md +3 -3
  156. package/src/workflows/solo/WORKFLOW.md +1 -1
  157. package/src/runtime/media/index.mjs +0 -18
  158. package/src/runtime/memory/lib/embedding-warmup.mjs +0 -68
  159. package/src/standalone/agent-shard/shard-child.mjs +0 -300
  160. package/src/standalone/agent-shard/shard-pool.mjs +0 -443
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.87",
3
+ "version": "0.9.88",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -49,6 +49,8 @@
49
49
  "smoke:all": "npm run smoke && npm run smoke:boot && npm run smoke:patch && npm run smoke:output && npm run smoke:tui && npm run smoke:live-worker",
50
50
  "smoke:boot": "node scripts/boot-smoke.mjs",
51
51
  "smoke:compact": "node scripts/compact-smoke.mjs",
52
+ "test:compact": "node --test scripts/compact-active-turn-test.mjs scripts/compact-file-reattach-test.mjs scripts/compact-prior-context-flatten-test.mjs scripts/compact-recall-digest-test.mjs scripts/compact-pressure-test.mjs",
53
+ "test:context": "node --test scripts/context-mcp-metering-test.mjs scripts/v4a-context-miss-excerpt-test.mjs",
52
54
  "smoke:loop": "node scripts/smoke-loop.mjs",
53
55
  "smoke:loop:final": "node scripts/smoke-loop-report.mjs --require-complete --min-elapsed 5h --min-iterations 400 --max-gap 60s --max-smoke-ms 10000 --max-avg-smoke-ms 8500 --max-step-ms smoke.mjs=4000 --max-step-ms boot-smoke.mjs=8000 --max-rss-mb 140 --max-rss-growth-mb 50",
54
56
  "smoke:loop:report": "node scripts/smoke-loop-report.mjs",
@@ -61,12 +63,13 @@
61
63
  "smoke:live-worker": "node scripts/live-worker-smoke.mjs",
62
64
  "smoke:agent-tag-reuse": "node scripts/agent-tag-reuse-smoke.mjs",
63
65
  "test:agent-terminal-reap": "node scripts/agent-terminal-reap-test.mjs",
66
+ "test:agent-job-views": "node --test scripts/agent-job-terminal-view-test.mjs",
64
67
  "test:agent-fanout": "node scripts/agent-parallel-smoke.mjs && node --test scripts/agent-route-batch-test.mjs scripts/execution-completion-dedup-test.mjs",
65
68
  "test:toolcall": "node --test scripts/toolcall-args-test.mjs",
66
69
  "test:shipmode": "node --test scripts/ship-mode-test.mjs",
67
- "test:shellhardening": "node --test scripts/shell-hardening-test.mjs scripts/shell-failure-diagnostics-test.mjs",
70
+ "test:shellhardening": "node --test scripts/shell-hardening-test.mjs scripts/shell-failure-diagnostics-test.mjs scripts/windows-hide-spawn-options-test.mjs",
68
71
  "test:placeholder": "node --test scripts/compacted-placeholder-scrub-test.mjs",
69
- "test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs",
72
+ "test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs scripts/provider-stream-stall-test.mjs scripts/provider-stream-outcome-test.mjs scripts/stream-frame-fault-matrix-test.mjs scripts/gemini-provider-test.mjs scripts/anthropic-transport-policy-test.mjs scripts/anthropic-native-block-replay-test.mjs scripts/openai-oauth-ws-1006-retry-test.mjs scripts/openai-end-turn-signal-test.mjs",
70
73
  "test:provider-admission": "node --test scripts/provider-admission-scheduler-test.mjs",
71
74
  "test:resource-admission": "node --test scripts/resource-admission-test.mjs",
72
75
  "test:deferred-tools": "node --test scripts/deferred-tool-loading-test.mjs",
@@ -80,20 +83,23 @@
80
83
  "test:embedding-runtime:warmup": "node scripts/verify-embedding-runtime.mjs --warmup",
81
84
  "test:code-graph-dispatch": "node --test scripts/code-graph-dispatch-test.mjs",
82
85
  "test:code-graph-clean-cache": "node --test scripts/code-graph-dispatch-test.mjs",
83
- "test:tui-queue": "node --test scripts/submit-commandbusy-race-test.mjs scripts/steering-drain-buckets-test.mjs scripts/abort-recovery-test.mjs scripts/execution-pending-resume-kick-test.mjs scripts/execution-resume-esc-integration-test.mjs",
86
+ "test:tui-queue": "node --test scripts/submit-commandbusy-race-test.mjs scripts/steering-drain-buckets-test.mjs scripts/abort-recovery-test.mjs scripts/execution-pending-resume-kick-test.mjs scripts/execution-resume-esc-integration-test.mjs scripts/pending-stale-injection-test.mjs",
84
87
  "test:tui-input-render": "node --test scripts/prompt-immediate-render-test.mjs",
85
88
  "test:tui-streaming-window": "node --test scripts/streaming-tail-window-test.mjs",
86
89
  "test:tui-ambiguous-width": "node --test scripts/tui-ambiguous-width-test.mjs",
87
90
  "test:release-assets": "node --check scripts/verify-release-assets.mjs && node --check scripts/verify-release-assets-test.mjs && node --check scripts/deploy-workflow-test.mjs && node --test scripts/verify-release-assets-test.mjs scripts/deploy-workflow-test.mjs",
88
- "test:release-focused": "npm run test:release-assets && npm run test:tool-contracts && npm run test:placeholder && npm run smoke:patch && npm run test:patch-binary-cache && npm run test:providers && npm run test:deferred-tools && npm run smoke:compact && node --test scripts/code-graph-root-federation-test.mjs scripts/code-graph-aggregate-cwd-test.mjs && npm run test:code-graph-dispatch && node --test scripts/code-graph-disk-hit-test.mjs && npm run test:shellhardening && node --test scripts/windows-hide-spawn-options-test.mjs && npm run test:session && npm run test:workflow-editor && npm run test:embedding-runtime && node --test scripts/tui-transcript-perf-test.mjs",
91
+ "test:release-focused": "npm run test:release-assets && npm run test:tool-contracts && npm run test:placeholder && npm run smoke:patch && npm run test:patch-binary-cache && npm run test:providers && npm run test:deferred-tools && npm run smoke:compact && npm run test:compact && npm run test:context && node --test scripts/code-graph-root-federation-test.mjs scripts/code-graph-aggregate-cwd-test.mjs && npm run test:code-graph-dispatch && node --test scripts/code-graph-disk-hit-test.mjs && npm run test:shellhardening && npm run test:project-registry && npm run test:session && npm run test:workflow-editor && npm run test:embedding-runtime && node --test scripts/tui-transcript-perf-test.mjs",
89
92
  "test:native-edit-wire": "node --test scripts/native-edit-wire-test.mjs",
90
93
  "test:patch-binary-cache": "node --test scripts/patch-binary-cache-test.mjs",
91
- "test:session": "node --test scripts/session-orphan-sweep-test.mjs scripts/interrupted-turn-history-test.mjs scripts/turn-checkpoint-crash-test.mjs scripts/session-heartbeat-lifecycle-test.mjs scripts/remote-transition-order-test.mjs",
94
+ "test:patch-parity": "node --test scripts/v4a-codex-parity-test.mjs",
95
+ "test:project-registry": "node --test scripts/project-registry-isolation-test.mjs",
96
+ "test:session": "node --test scripts/session-orphan-sweep-test.mjs scripts/interrupted-turn-history-test.mjs scripts/turn-checkpoint-crash-test.mjs scripts/turn-outcome-fault-matrix-test.mjs scripts/session-save-fault-store-test.mjs scripts/session-disk-authority-test.mjs scripts/session-load-cache-race-test.mjs scripts/agent-loop-complete-turn-test.mjs scripts/session-heartbeat-lifecycle-test.mjs scripts/remote-transition-order-test.mjs scripts/session-new-reset-test.mjs",
97
+ "test:live-canary": "node --test scripts/live-canary-test.mjs",
92
98
  "test:rebindtail": "node --test scripts/forwarder-rebind-tail-test.mjs",
93
99
  "test:workflow-editor": "node --test scripts/workflow-id-test.mjs scripts/workflow-pack-editor-test.mjs",
94
100
  "test:route-scope": "node --test scripts/route-scope-isolation-test.mjs",
95
101
  "test:schedule-reload": "node --test scripts/schedule-reload-arm-test.mjs",
96
- "test:media": "node --test src/runtime/media/store.test.mjs",
102
+ "test:media": "node --test src/runtime/media/store.test.mjs src/runtime/media/renditions.test.mjs",
97
103
  "failures": "node scripts/tool-failures.mjs",
98
104
  "trace:llm": "node scripts/llm-trace-summary.mjs",
99
105
  "diag:sessions": "node scripts/session-diag.mjs",
@@ -88,6 +88,7 @@ function rowCategory(row) {
88
88
  }
89
89
 
90
90
  const sinceTs = parseSince(sinceArg);
91
+ const onlyArg = String(argValue('--only', 'all') || 'all').toLowerCase();
91
92
  const rows = files.flatMap(readRows)
92
93
  .filter((row) => sinceTs == null || Number(row.ts || 0) >= sinceTs)
93
94
  .filter((row) => !toolFilter || rowTool(row) === toolFilter)
@@ -95,6 +96,11 @@ const rows = files.flatMap(readRows)
95
96
  .filter((row) => !categoryFilter || rowCategory(row) === categoryFilter)
96
97
  .sort((a, b) => Number(a.ts || 0) - Number(b.ts || 0));
97
98
  const isCommandExit = (row) => rowCategory(row) === 'command-exit';
99
+ // Absorbed-by-design outcomes (compacted-history placeholder preflight, etc.):
100
+ // retained in the log and displayed, but never counted as actionable work.
101
+ const isExpectedAbsorbed = (row) => /^expected-/.test(String(rowCategory(row)));
102
+ const isPatchFailure = (row) => /^patch\//.test(String(rowCategory(row)));
103
+ const categoryFamily = (row) => String(rowCategory(row)).split('/')[0] || '(uncategorized)';
98
104
  const rowLeadingErrorLine = (row) => [
99
105
  row.error_first_line,
100
106
  row.error_preview,
@@ -109,25 +115,41 @@ const isExpectedCancellation = (row) => {
109
115
  if (rowCategory(row) === 'expected-cancellation') return true;
110
116
  return /^Session\s+"[^"]+"\s+closed:\s*(?:aborted|closed)\s+during call\b/i.test(rowLeadingErrorLine(row));
111
117
  };
112
- const actionableRows = rows.filter((row) => !isCommandExit(row) && !isExpectedCancellation(row));
113
- const commandExitRows = rows.filter(isCommandExit);
118
+ const cancellationRows = rows.filter(isExpectedCancellation);
119
+ const liveRows = rows.filter((row) => !isExpectedCancellation(row));
120
+ const commandExitRows = liveRows.filter(isCommandExit);
121
+ // Ordinary non-zero test/command exits and absorbed preflights are reported
122
+ // separately so a green-but-noisy run never reads as N tool/patch failures.
123
+ const expectedRows = liveRows.filter((row) => !isCommandExit(row) && isExpectedAbsorbed(row));
124
+ const actionableRows = liveRows.filter((row) => !isCommandExit(row) && !isExpectedAbsorbed(row));
125
+ const patchRows = actionableRows.filter(isPatchFailure);
126
+ const wants = (kind) => onlyArg === 'all' || onlyArg === kind;
114
127
  // Limit each partition independently so a burst of ordinary command exits
115
128
  // cannot crowd runtime/actionable failures out of the displayed report.
116
- const actionableRecent = actionableRows.slice(-limit);
117
- const commandExitRecent = commandExitRows.slice(-limit);
118
- const recent = [...actionableRecent, ...commandExitRecent]
129
+ const actionableRecent = wants('actionable') ? actionableRows.slice(-limit) : [];
130
+ const commandExitRecent = wants('exits') ? commandExitRows.slice(-limit) : [];
131
+ const expectedRecent = wants('expected') ? expectedRows.slice(-limit) : [];
132
+ const recent = [...actionableRecent, ...commandExitRecent, ...expectedRecent]
119
133
  .sort((a, b) => Number(a.ts || 0) - Number(b.ts || 0));
120
- const byTool = new Map();
121
- const byCategory = new Map();
122
- const actionableByTool = new Map();
123
- const commandExitByTool = new Map();
124
- for (const row of recent) {
125
- const tool = rowTool(row);
126
- const category = rowCategory(row);
127
- inc(byTool, tool);
128
- inc(byCategory, `${tool} / ${category}`);
129
- inc(isCommandExit(row) ? commandExitByTool : actionableByTool, tool);
134
+ function tally(list, keyFn) {
135
+ const map = new Map();
136
+ for (const row of list) inc(map, keyFn(row));
137
+ return map;
130
138
  }
139
+ const sortedEntries = (map) => [...map.entries()].sort((a, b) => b[1] - a[1]);
140
+ const asObject = (map) => Object.fromEntries(sortedEntries(map));
141
+ const asText = (map) => sortedEntries(map).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)';
142
+ // Aggregates cover every MATCHED row in the window (not just the displayed
143
+ // tail) so `--since 24h` headline totals cannot be read as the whole picture
144
+ // while a truncated tail hides the rest.
145
+ const byTool = tally(recent, rowTool);
146
+ const byCategory = tally(recent, (row) => `${rowTool(row)} / ${rowCategory(row)}`);
147
+ const actionableByTool = tally(actionableRows, rowTool);
148
+ const actionableByCategory = tally(actionableRows, rowCategory);
149
+ const actionableByFamily = tally(actionableRows, categoryFamily);
150
+ const commandExitByTool = tally(commandExitRows, rowTool);
151
+ const expectedByCategory = tally(expectedRows, rowCategory);
152
+ const patchByCategory = tally(patchRows, rowCategory);
131
153
 
132
154
  if (jsonMode) {
133
155
  console.log(JSON.stringify({
@@ -135,36 +157,50 @@ if (jsonMode) {
135
157
  matched: rows.length,
136
158
  actionable_failures: { shown: actionableRecent.length, matched: actionableRows.length },
137
159
  command_exits: { shown: commandExitRecent.length, matched: commandExitRows.length },
160
+ expected_absorbed: { shown: expectedRecent.length, matched: expectedRows.length },
161
+ session_cancellations: { shown: 0, matched: cancellationRows.length },
162
+ patch_failures: { matched: patchRows.length, categories: asObject(patchByCategory) },
138
163
  since: sinceTs ? new Date(sinceTs).toISOString() : null,
139
164
  filters: {
140
165
  tool: toolFilter,
141
166
  agent: agentFilter,
142
167
  category: categoryFilter,
168
+ only: onlyArg,
143
169
  },
144
170
  sources: files.filter(existsSync),
145
- tools: Object.fromEntries([...byTool.entries()].sort((a, b) => b[1] - a[1])),
146
- actionable_tools: Object.fromEntries([...actionableByTool.entries()].sort((a, b) => b[1] - a[1])),
147
- command_exit_tools: Object.fromEntries([...commandExitByTool.entries()].sort((a, b) => b[1] - a[1])),
148
- categories: Object.fromEntries([...byCategory.entries()].sort((a, b) => b[1] - a[1])),
171
+ tools: asObject(byTool),
172
+ actionable_tools: asObject(actionableByTool),
173
+ actionable_categories: asObject(actionableByCategory),
174
+ actionable_families: asObject(actionableByFamily),
175
+ command_exit_tools: asObject(commandExitByTool),
176
+ expected_categories: asObject(expectedByCategory),
177
+ categories: asObject(byCategory),
149
178
  rows: recent,
150
179
  }, null, 2));
151
180
  process.exit(0);
152
181
  }
153
182
 
154
- console.log(`actionable failures: ${actionableRecent.length}/${actionableRows.length} shown`);
155
- console.log(`command exits: ${commandExitRecent.length}/${commandExitRows.length} shown (retained)`);
183
+ console.log(`actionable failures: ${actionableRecent.length}/${actionableRows.length} shown (excludes command exits, absorbed preflights, session cancellations)`);
184
+ console.log(`command exits: ${commandExitRecent.length}/${commandExitRows.length} shown (retained) — ordinary non-zero test/command exits, not tool failures`);
185
+ console.log(`expected/absorbed: ${expectedRecent.length}/${expectedRows.length} shown (retained) — absorbed by design, not actionable`);
186
+ console.log(`session cancellations: ${cancellationRows.length} matched (not shown)`);
156
187
  console.log(`rows: ${recent.length}/${rows.length} shown`);
157
188
  if (sinceTs) console.log(`since: ${new Date(sinceTs).toISOString()}`);
158
189
  const filterParts = [
159
190
  toolFilter ? `tool=${toolFilter}` : '',
160
191
  agentFilter ? `agent=${agentFilter}` : '',
161
192
  categoryFilter ? `category=${categoryFilter}` : '',
193
+ onlyArg !== 'all' ? `only=${onlyArg}` : '',
162
194
  ].filter(Boolean);
163
195
  if (filterParts.length) console.log(`filters: ${filterParts.join(', ')}`);
164
196
  if (files.length > 0) console.log(`sources: ${files.filter(existsSync).join(', ') || '(none)'}`);
165
- console.log(`actionable tools: ${[...actionableByTool.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)'}`);
166
- console.log(`command-exit tools: ${[...commandExitByTool.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)'}`);
167
- console.log(`categories: ${[...byCategory.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)'}`);
197
+ console.log(`actionable tools (matched): ${asText(actionableByTool)}`);
198
+ console.log(`actionable categories (matched): ${asText(actionableByCategory)}`);
199
+ console.log(`actionable families (matched): ${asText(actionableByFamily)}`);
200
+ console.log(`patch failures (matched): ${patchRows.length} — ${asText(patchByCategory)}`);
201
+ console.log(`command-exit tools (matched): ${asText(commandExitByTool)}`);
202
+ console.log(`expected/absorbed categories (matched): ${asText(expectedByCategory)}`);
203
+ console.log(`shown categories: ${asText(byCategory)}`);
168
204
  for (const row of recent) {
169
205
  const tool = rowTool(row);
170
206
  const category = rowCategory(row);
@@ -6,14 +6,13 @@ permission: read-write
6
6
  Own the assigned implementation slice through staged delivery.
7
7
 
8
8
  Break work into bounded, dependency-aware slices and execute them in sequence.
9
- At each checkpoint, run the narrowest relevant test or build before expanding
10
- the slice. Keep the smallest coherent change; control blast radius rather than
11
- rewriting adjacent systems.
9
+ Keep the smallest coherent change; control blast radius rather than rewriting
10
+ adjacent systems.
12
11
 
13
12
  EDIT-FIRST DISCIPLINE. Patch incrementally and stop at the first explicit
14
13
  boundary: unclear ownership, a missing dependency, or growing blast radius.
15
14
  Do not cross that boundary without a new bounded assignment; report blocked
16
15
  work with the relevant file:line.
17
16
 
18
- Self-verify each checkpoint and the final slice with shell (targeted test/build).
17
+ Finish the slice and report the changed `file:line`; verification belongs to the Lead and Reviewer.
19
18
 
@@ -12,6 +12,5 @@ smallest coherent patch. No drive-by cleanup or scope expansion.
12
12
  EDIT-FIRST DISCIPLINE. Patch promptly rather than repeating read-only turns;
13
13
  stop and report blocked when the assigned scope cannot be completed.
14
14
 
15
- Self-verify with a targeted check (for example, `node --check` or a focused
16
- test), then report the changed `file:line` and stop.
15
+ Patch and report the changed `file:line`; verification belongs to the Lead and Reviewer.
17
16
 
package/src/cli.mjs CHANGED
@@ -10,6 +10,14 @@ import { stagedChildExitCode } from './runtime/shared/staged-child-result.mjs';
10
10
 
11
11
  const argv = process.argv.slice(2);
12
12
 
13
+ // V8 compile cache: the whole runtime loads via dynamic import below, so
14
+ // enabling the cache here persists compiled bytecode across CLI launches
15
+ // (measurable cold-start parse savings). Best-effort on older Node builds.
16
+ try {
17
+ const { enableCompileCache } = await import('node:module');
18
+ enableCompileCache?.();
19
+ } catch { /* launch-speed optimization only */ }
20
+
13
21
  // React/ink resolve their build flavor from NODE_ENV at require time. Unset
14
22
  // NODE_ENV loads react-reconciler's DEVELOPMENT build, whose per-commit debug
15
23
  // bookkeeping consumed ~16% of TUI frame CPU under transcript load (cpuprofile
@@ -23,7 +23,7 @@ CORE is durable standing knowledge that lands in one of three layers:
23
23
  - **L3 — Current map:** one-line project-landscape summaries, live long-running
24
24
  goals, environment anchors documented nowhere else.
25
25
 
26
- Every entry must be ONE compact ENGLISH clause (≤120 chars), regardless of
26
+ Every entry must be ONE compact ENGLISH clause, regardless of
27
27
  source language. Keep identifiers, paths, and exact phrases coined by the user
28
28
  verbatim. CORE is not a log.
29
29
 
@@ -52,7 +52,7 @@ or inconclusive, keep the CORE entry.
52
52
 
53
53
  - `keep` — durable and already one compact ENGLISH clause.
54
54
  - `update` — durable but verbose, multi-sentence, or non-English → rewrite as
55
- one compact ≤120-char ENGLISH clause; keep identifiers, paths, and exact
55
+ one compact ENGLISH clause; keep identifiers, paths, and exact
56
56
  phrases coined by the user verbatim.
57
57
  - `merge` — duplicates another entry → fold into the survivor (same project pool).
58
58
  - `reclassify` — the entry is filed under the WRONG project pool. Its subject
@@ -86,7 +86,7 @@ or inconclusive, keep the CORE entry.
86
86
  "needs confirmation" and only removed on an explicit APPLY CYCLE3 run.
87
87
 
88
88
  A verbose, multi-sentence, or non-English durable entry is always `update`,
89
- never `keep`; rewrite it as a ≤120-char English clause while keeping
89
+ never `keep`; rewrite it as a compact English clause while keeping
90
90
  identifiers, paths, and exact user-coined phrases verbatim.
91
91
  Delete is the rarest verdict. Prefer `keep` for durable rules/preferences and
92
92
  `update` for compression when the current behavior is still valid.
@@ -122,5 +122,5 @@ One line per entry id, any order:
122
122
  <id>|delete|<reason>
123
123
  ```
124
124
 
125
- `summary` ≤120 chars, one clause. No literal `|` or newline inside a field
125
+ `summary` is one compact clause. No literal `|` or newline inside a field
126
126
  (replace `|` with `/`). No prose, no fences. First character is a digit.
@@ -10,3 +10,6 @@
10
10
  unrequested headings/tables, prose narration, raw logs/tool traces,
11
11
  speculative next checks, restated briefs, articles, or politeness.
12
12
  - Follow stricter role contracts and runtime wrap-up requirements.
13
+ - Your final message ends the task: emit the handoff text only when the work is
14
+ done. If a tool failed and stays unresolved, fix and re-run it, or say so
15
+ explicitly in the handoff.
@@ -13,12 +13,11 @@ prose, or preamble.
13
13
 
14
14
  CORE is durable standing knowledge: rules, preferences, identity, goals, and
15
15
  current system/structure descriptions—not a log. Each entry is one short clause
16
- (≤120 chars). Current rule/preference/live structure = durable; a past event =
17
- not durable. When unsure, keep.
16
+ without a hard character cap. Current rule/preference/live structure = durable;
17
+ a past event = not durable. When unsure, keep.
18
18
 
19
19
  - `keep`: durable, already one short clause.
20
- - `update`: durable but verbose/multi-sentence; compress to one ≤120-char
21
- clause.
20
+ - `update`: durable but verbose/multi-sentence; compress to one short clause.
22
21
  - `merge`: duplicate; fold into its survivor in the same project pool.
23
22
  - `delete`: past event, not a current rule or structure.
24
23
 
@@ -29,7 +28,7 @@ Verbose durable is always `update`, never `keep`.
29
28
  `<id>|merge|<target_id>|<source_ids_csv>`
30
29
  `<id>|delete`
31
30
 
32
- IDs match input rows; never invent them. An `update` summary is one ≤120-char
33
- clause and its `element` is short. A `merge` retains `target_id`, absorbs
31
+ IDs match input rows; never invent them. An `update` summary is one short
32
+ clause and its `element` is compact. A `merge` retains `target_id`, absorbs
34
33
  sources, and stays within one `project_id`. Replace literal `|` with `/`;
35
34
  fields contain no newlines. Emit a digit-starting verdict for every input row.
@@ -6,3 +6,5 @@
6
6
  headings, labels, or routine lookup narration.
7
7
  - Destructive/hard-to-reverse action needs explicit confirmation.
8
8
  - Act proactively; ask only for decisions.
9
+ - Your final message ends the turn: answer only when the work is done. After a
10
+ failed tool call, fix and re-run it, or state plainly that it is unresolved.
@@ -6,7 +6,8 @@
6
6
  `find`; verified root+wildcard→
7
7
  `glob`; quoted/non-identifier literal or regex→`grep`; exact code
8
8
  identifier/relation→`code_graph` before grep; known file/span→`read`
9
- directly without `grep`; verified directory→`list`; edit→`apply_patch`;
9
+ directly without `grep`; verified directory→`list`; known edit
10
+ `apply_patch` directly, with no preparatory `read`;
10
11
  program/state change→`shell`; web/current external info→`search`. Use grep
11
12
  only for a requested literal occurrence or after graph zero/error.
12
13
  - Batch compatible targets and combine variants, symbols, scopes, paths, and
@@ -38,6 +39,9 @@
38
39
  capped, or insufficient context, inspect only missing content. A nonzero
39
40
  `content_with_context` result resolves the concept; act directly without
40
41
  re-search; only zero/error results permit token or scope changes.
42
+ - `apply_patch` is the primary edit tool: send the patch as soon as the target
43
+ path and new content are known. `read` is for discovery or for recovery
44
+ after a patch failed on insufficient context.
41
45
  - Apply edits before verification, then verify in a separate shell turn and
42
46
  consume results in order. Otherwise remain parallel.
43
47
  - A long-running command promoted to background is a decision point, not a
@@ -24,8 +24,8 @@
24
24
  import { loadConfig } from '../config.mjs';
25
25
  import { resolveRuntimeSpec } from '../config.mjs';
26
26
  import { getHiddenAgent, resolveAgentSessionPermission } from '../internal-agents.mjs';
27
- import { isKnownProvider } from '../../../../standalone/provider-admin.mjs';
28
27
  import { prepareAgentSession } from './session-builder.mjs';
28
+ import { resolveMaintenanceRoute } from './maintenance-route.mjs';
29
29
  import {
30
30
  askSession,
31
31
  updateSessionStatus,
@@ -43,6 +43,8 @@ import {
43
43
  } from './agent-progress-watchdog.mjs';
44
44
  import { resourceAdmission } from '../../../shared/resource-admission.mjs';
45
45
 
46
+ export { resolveMaintenanceRoute } from './maintenance-route.mjs';
47
+
46
48
  // Cap agent role synthesis to ~3000 tokens (~12 KB at the 4 B/tok
47
49
  // working average). Pool B explore/recall/search answers occasionally land
48
50
  // 8-10k-token walls that then ride in the Lead context for the rest of the
@@ -153,56 +155,6 @@ export function resolveHiddenRoleSchemaAllowedTools(hidden) {
153
155
  * maintenance route → Main. The cycle1/2/3 agents share the memory knob via
154
156
  * their `maintKey: 'memory'` override. Scheduler and webhook are unchanged.
155
157
  */
156
- const DEFAULT_AGENT_ROUTE_PROVIDER = 'anthropic-oauth';
157
-
158
- function normalizeMaintenanceCandidate(candidate, config) {
159
- if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return candidate || null;
160
- const configuredProvider = String(config?.defaultProvider || '').trim();
161
- const fallbackProvider = isKnownProvider(configuredProvider)
162
- ? configuredProvider
163
- : DEFAULT_AGENT_ROUTE_PROVIDER;
164
- const provider = String(candidate.provider || fallbackProvider).trim();
165
- const model = String(candidate.model || '').trim();
166
- if (!provider || !model) return null;
167
- return {
168
- provider,
169
- model,
170
- effort: String(candidate.effort || '').trim() || undefined,
171
- fast: candidate.fast === true,
172
- };
173
- }
174
-
175
- export function resolveMaintenanceRoute({ preset, optsPreset, agent, config: cfgIn = null }) {
176
- if (preset) return preset;
177
- if (optsPreset) return optsPreset;
178
- if (!agent) return null;
179
- const hidden = getHiddenAgent(agent);
180
- if (hidden) {
181
- try {
182
- const config = cfgIn || loadConfig({ secrets: false });
183
- const maint = config?.maintenance || {};
184
- const key = hidden.maintKey || hidden.slot;
185
- const role = key === 'explore' ? 'explore' : (key === 'memory' ? 'maintainer' : '');
186
- const workflowSlot = key === 'explore' ? 'explorer' : (key === 'memory' ? 'memory' : '');
187
- if (!role) return maint[key] ?? null;
188
- const candidates = [
189
- role ? config?.agents?.[role] : null,
190
- key === 'memory' ? config?.agents?.maintenance : null,
191
- workflowSlot ? config?.workflowRoutes?.[workflowSlot] : null,
192
- maint[key],
193
- role ? config?.default : null,
194
- ];
195
- for (const candidate of candidates) {
196
- const route = normalizeMaintenanceCandidate(candidate, config);
197
- if (route) return route;
198
- }
199
- return null;
200
- } catch { return null; }
201
- }
202
- return null;
203
- }
204
-
205
-
206
158
  // A maintenance slot value is a direct route when it carries provider+model.
207
159
  function maintenanceRouteToPreset(routeOrName, agent) {
208
160
  if (!routeOrName || typeof routeOrName !== 'object') return null;
@@ -0,0 +1,59 @@
1
+ import { isKnownProvider } from '../../../../standalone/provider-admin.mjs';
2
+ import { loadConfig } from '../config.mjs';
3
+ import { getHiddenAgent } from '../internal-agents.mjs';
4
+
5
+ const DEFAULT_AGENT_ROUTE_PROVIDER = 'anthropic-oauth';
6
+
7
+ function normalizeMaintenanceCandidate(candidate, config) {
8
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return candidate || null;
9
+ const configuredProvider = String(config?.defaultProvider || '').trim();
10
+ const fallbackProvider = isKnownProvider(configuredProvider)
11
+ ? configuredProvider
12
+ : DEFAULT_AGENT_ROUTE_PROVIDER;
13
+ const provider = String(candidate.provider || fallbackProvider).trim();
14
+ const model = String(candidate.model || '').trim();
15
+ if (!provider || !model) return null;
16
+ return {
17
+ provider,
18
+ model,
19
+ effort: String(candidate.effort || '').trim() || undefined,
20
+ fast: candidate.fast === true,
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Resolve the maintenance route for a hidden role without creating an agent
26
+ * session. Session-backed dispatch and tiny one-shot completions share this
27
+ * model-selection boundary.
28
+ */
29
+ export function resolveMaintenanceRoute({ preset, optsPreset, agent, config: cfgIn = null }) {
30
+ if (preset) return preset;
31
+ if (optsPreset) return optsPreset;
32
+ if (!agent) return null;
33
+ const hidden = getHiddenAgent(agent);
34
+ if (hidden) {
35
+ try {
36
+ const config = cfgIn || loadConfig({ secrets: false });
37
+ const maint = config?.maintenance || {};
38
+ const key = hidden.maintKey || hidden.slot;
39
+ const role = key === 'explore' ? 'explore' : (key === 'memory' ? 'maintainer' : '');
40
+ const workflowSlot = key === 'explore' ? 'explorer' : (key === 'memory' ? 'memory' : '');
41
+ if (!role) return maint[key] ?? null;
42
+ const candidates = [
43
+ role ? config?.agents?.[role] : null,
44
+ key === 'memory' ? config?.agents?.maintenance : null,
45
+ workflowSlot ? config?.workflowRoutes?.[workflowSlot] : null,
46
+ maint[key],
47
+ role ? config?.default : null,
48
+ ];
49
+ for (const candidate of candidates) {
50
+ const route = normalizeMaintenanceCandidate(candidate, config);
51
+ if (route) return route;
52
+ }
53
+ return null;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ return null;
59
+ }
@@ -91,8 +91,8 @@ export function prepareAgentSession({
91
91
  // or fall through to the shared runaway guard (LEAD_MAX_LOOP_ITERATIONS).
92
92
  const effectiveMaxLoopIterations = maxLoopIterations;
93
93
  // Pass cwd through verbatim — null is the fixed agent sentinel meaning
94
- // "no caller workspace context" (cycle1-agent shards, etc). Upgrading
95
- // null → process.cwd() here would defeat cache-shard fork suppression.
94
+ // "no caller workspace context" (cycle1 agents, etc). Upgrading
95
+ // null → process.cwd() here would defeat cache-key fork suppression.
96
96
  // Downstream collectors (collect.mjs) handle null as "no project cwd".
97
97
  const effectiveCwd = cwd == null ? null : cwd;
98
98
  const effectiveOwnerSessionId = ownerSessionId === undefined
@@ -0,0 +1,67 @@
1
+ import { loadConfig } from '../config.mjs';
2
+ import { getProvider, initProviders } from '../providers/registry.mjs';
3
+ import { resolveMaintenanceRoute } from './maintenance-route.mjs';
4
+
5
+ export const TITLE_SYSTEM_PROMPT = "Create a concise session title from the provided message or conversation. Output only one title on one line, at most 32 characters; no quotes, markdown, or trailing period.";
6
+
7
+ export function titleSystemPrompt(locale = '') {
8
+ const systemLocale = String(locale || '').trim();
9
+ return systemLocale
10
+ ? `${TITLE_SYSTEM_PROMPT} System language/locale: ${systemLocale}. Prefer that language when the source is ambiguous; preserve a clearly different source language.`
11
+ : TITLE_SYSTEM_PROMPT;
12
+ }
13
+
14
+ function resultText(result) {
15
+ if (typeof result === 'string') return result;
16
+ if (typeof result?.content === 'string') return result.content;
17
+ if (Array.isArray(result?.content)) {
18
+ return result.content
19
+ .map((part) => part?.type === 'text' ? String(part.text || '') : '')
20
+ .filter(Boolean)
21
+ .join('\n');
22
+ }
23
+ return '';
24
+ }
25
+
26
+ export function createTitleCompletion(deps = {}) {
27
+ const load = deps.loadConfig || loadConfig;
28
+ const resolveRoute = deps.resolveMaintenanceRoute || resolveMaintenanceRoute;
29
+ const initialize = deps.initProviders || initProviders;
30
+ const providerFor = deps.getProvider || getProvider;
31
+
32
+ return async function generateSessionTitle(source, options = {}) {
33
+ const text = String(source || '').trim();
34
+ if (!text) return '';
35
+ const signal = options.signal || null;
36
+ const config = load();
37
+ const route = resolveRoute({
38
+ agent: 'title-agent',
39
+ config,
40
+ });
41
+ if (!route || typeof route !== 'object') {
42
+ throw new Error('Session title maintenance route is unresolved.');
43
+ }
44
+ const providerName = String(route.provider || '').trim();
45
+ const model = String(route.model || '').trim();
46
+ if (!providerName || !model) {
47
+ throw new Error('Session title maintenance route requires provider and model.');
48
+ }
49
+ await initialize(config.providers || {}, { signal });
50
+ const provider = providerFor(providerName);
51
+ if (!provider || typeof provider.send !== 'function') {
52
+ throw new Error(`Session title provider is unavailable: ${providerName}`);
53
+ }
54
+ const response = await provider.send([
55
+ { role: 'system', content: titleSystemPrompt(options.locale) },
56
+ { role: 'user', content: text },
57
+ ], model, undefined, {
58
+ signal,
59
+ effort: String(route.effort || '').trim() || 'low',
60
+ fast: route.fast === true,
61
+ maxOutputTokens: 128,
62
+ });
63
+ return resultText(response).trim();
64
+ };
65
+ }
66
+
67
+ export const generateSessionTitle = createTitleCompletion();