mixdog 0.9.95 → 0.9.97

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 (282) hide show
  1. package/LICENSES/Apache-2.0.txt +201 -0
  2. package/LICENSES/MIT.txt +56 -0
  3. package/LICENSES/codex-NOTICE.txt +6 -0
  4. package/NOTICE.md +46 -9
  5. package/README.md +0 -17
  6. package/package.json +16 -12
  7. package/scripts/.tmp-coverage-report.mjs +96 -0
  8. package/scripts/build-tui.mjs +17 -1
  9. package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
  10. package/scripts/fixtures/patch-replay-corpus.json +98 -0
  11. package/src/defaults/mixdog-config.template.json +5 -8
  12. package/src/defaults/skills/setup/SKILL.md +1 -1
  13. package/src/lib/keychain-cjs.cjs +1 -1
  14. package/src/lib/mixdog-debug.cjs +2 -3
  15. package/src/rules/agent/30-explorer.md +7 -4
  16. package/src/rules/lead/01-general.md +1 -2
  17. package/src/rules/lead/lead-tool.md +0 -3
  18. package/src/rules/shared/01-tool.md +21 -29
  19. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
  22. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
  23. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
  24. package/src/runtime/agent/orchestrator/config.mjs +246 -67
  25. package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
  26. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
  27. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
  29. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
  30. package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
  31. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
  32. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
  33. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
  34. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
  35. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
  36. package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
  37. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
  38. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
  39. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
  40. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
  41. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
  42. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
  43. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
  44. package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
  46. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
  47. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
  48. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
  49. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
  50. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
  51. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
  52. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
  53. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
  54. package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
  55. package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
  56. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
  57. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
  58. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
  59. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  61. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
  62. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
  63. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
  64. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
  65. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
  66. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
  67. package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
  69. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
  70. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
  71. package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
  72. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
  73. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
  74. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
  75. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
  76. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
  77. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
  78. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
  79. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
  80. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
  81. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
  82. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
  83. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
  84. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
  85. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
  87. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
  88. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
  89. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
  90. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
  91. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
  92. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
  93. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
  94. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
  95. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
  96. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
  97. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
  99. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
  100. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
  101. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
  102. package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
  104. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
  105. package/src/runtime/channels/lib/config.mjs +34 -34
  106. package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
  107. package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
  108. package/src/runtime/channels/lib/memory-client.mjs +31 -157
  109. package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
  110. package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
  111. package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
  112. package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
  113. package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
  114. package/src/runtime/channels/lib/scheduler.mjs +2 -2
  115. package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
  116. package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
  117. package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
  118. package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
  119. package/src/runtime/channels/lib/worker-main.mjs +38 -38
  120. package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
  121. package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
  122. package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
  123. package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
  124. package/src/runtime/channels/tool-defs.mjs +1 -1
  125. package/src/runtime/memory/index.mjs +76 -248
  126. package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
  127. package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
  128. package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
  129. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
  130. package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
  131. package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
  132. package/src/runtime/memory/lib/http-router.mjs +9 -6
  133. package/src/runtime/memory/lib/ko-morph.mjs +3 -20
  134. package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
  135. package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
  136. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
  137. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
  138. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
  139. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
  140. package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
  141. package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
  142. package/src/runtime/memory/lib/memory-log.mjs +1 -1
  143. package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
  144. package/src/runtime/memory/lib/memory.mjs +1 -1
  145. package/src/runtime/memory/lib/pg/process.mjs +32 -9
  146. package/src/runtime/memory/lib/session-ingest.mjs +1 -1
  147. package/src/runtime/memory/tool-defs.mjs +1 -5
  148. package/src/runtime/search/index.mjs +2 -2
  149. package/src/runtime/search/tool-defs.mjs +2 -2
  150. package/src/runtime/shared/abort-race.mjs +70 -0
  151. package/src/runtime/shared/agent-route-config.mjs +119 -0
  152. package/src/runtime/shared/atomic-file.mjs +80 -4
  153. package/src/runtime/shared/channel-notification-routing.mjs +2 -2
  154. package/src/runtime/shared/child-guardian.mjs +204 -108
  155. package/src/runtime/shared/child-spawn-gate.mjs +139 -72
  156. package/src/runtime/shared/config.mjs +120 -47
  157. package/src/runtime/shared/llm/index.mjs +1 -1
  158. package/src/runtime/shared/owner-fair-gate.mjs +134 -0
  159. package/src/runtime/shared/resource-admission.mjs +150 -100
  160. package/src/runtime/shared/stream-progress.mjs +6 -0
  161. package/src/runtime/shared/tool-execution-owner.mjs +12 -0
  162. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  163. package/src/runtime/shared/tool-surface.mjs +91 -27
  164. package/src/runtime/shared/tool-workload-gates.mjs +29 -0
  165. package/src/runtime/shared/turn-snapshot.mjs +47 -6
  166. package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
  167. package/src/session-runtime/channel-config-api.mjs +5 -5
  168. package/src/session-runtime/config-helpers.mjs +39 -37
  169. package/src/session-runtime/config-lifecycle.mjs +27 -27
  170. package/src/session-runtime/lifecycle-api.mjs +22 -3
  171. package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
  172. package/src/session-runtime/model-route-api.mjs +7 -5
  173. package/src/session-runtime/prewarm.mjs +1 -1
  174. package/src/session-runtime/provider-auth-api.mjs +0 -7
  175. package/src/session-runtime/provider-models.mjs +1 -1
  176. package/src/session-runtime/remote-control.mjs +6 -7
  177. package/src/session-runtime/runtime-core.mjs +63 -39
  178. package/src/session-runtime/runtime-tunables.mjs +4 -4
  179. package/src/session-runtime/self-update.mjs +1 -1
  180. package/src/session-runtime/session-lifecycle.mjs +45 -17
  181. package/src/session-runtime/session-title.mjs +1 -1
  182. package/src/session-runtime/session-turn-api.mjs +96 -24
  183. package/src/session-runtime/settings-api.mjs +12 -24
  184. package/src/session-runtime/tool-defs.mjs +3 -3
  185. package/src/session-runtime/workflow-agents-api.mjs +55 -81
  186. package/src/session-runtime/workflow.mjs +14 -24
  187. package/src/standalone/agent-dispatch-broker.mjs +198 -0
  188. package/src/standalone/agent-tool/helpers.mjs +2 -2
  189. package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
  190. package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
  191. package/src/standalone/agent-tool/tool-def.mjs +0 -14
  192. package/src/standalone/channel-admin.mjs +53 -64
  193. package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
  194. package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
  195. package/src/standalone/channel-worker.mjs +124 -647
  196. package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
  197. package/src/standalone/explore-tool.mjs +187 -70
  198. package/src/standalone/fair-call-scheduler.mjs +264 -0
  199. package/src/standalone/memory-runtime-proxy.mjs +58 -1
  200. package/src/standalone/session-client.mjs +1134 -0
  201. package/src/standalone/session-protocol.mjs +192 -0
  202. package/src/standalone/session-runtime-pool.mjs +393 -0
  203. package/src/standalone/session-runtime-record.mjs +20 -0
  204. package/src/standalone/session-runtime-worker.mjs +203 -0
  205. package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
  206. package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
  207. package/src/standalone/session-wire.mjs +55 -0
  208. package/src/tui/App.jsx +34 -21
  209. package/src/tui/app/app-format.mjs +1 -1
  210. package/src/tui/app/app-view.jsx +5 -2
  211. package/src/tui/app/channel-pickers.mjs +41 -41
  212. package/src/tui/app/core-memory-picker.mjs +19 -19
  213. package/src/tui/app/doctor.mjs +5 -11
  214. package/src/tui/app/input-parsers.mjs +2 -2
  215. package/src/tui/app/message-selector.mjs +103 -0
  216. package/src/tui/app/onboarding-steps.mjs +3 -4
  217. package/src/tui/app/project-picker.mjs +80 -56
  218. package/src/tui/app/prompt-submit.mjs +48 -43
  219. package/src/tui/app/resume-picker.mjs +2 -2
  220. package/src/tui/app/route-pickers.mjs +3 -1
  221. package/src/tui/app/settings-picker.mjs +31 -33
  222. package/src/tui/app/transcript-window.mjs +38 -0
  223. package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
  224. package/src/tui/app/use-prompt-handlers.mjs +86 -21
  225. package/src/tui/app/use-prompt-hint.mjs +3 -2
  226. package/src/tui/app/use-prompt-queue-history.mjs +10 -3
  227. package/src/tui/app/use-transcript-scroll.mjs +10 -3
  228. package/src/tui/app/use-transcript-window.mjs +50 -17
  229. package/src/tui/components/PromptInput.jsx +71 -38
  230. package/src/tui/components/Spinner.jsx +1 -1
  231. package/src/tui/components/ToolExecution.jsx +1 -1
  232. package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
  233. package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
  234. package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
  235. package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
  236. package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
  237. package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
  238. package/src/tui/dist/index.mjs +1031 -1676
  239. package/src/tui/hooks/useSession.mjs +16 -0
  240. package/src/tui/index.jsx +12 -13
  241. package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
  242. package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
  243. package/src/tui/{engine → session}/boot-profile.mjs +2 -2
  244. package/src/tui/{engine → session}/context-state.mjs +3 -3
  245. package/src/tui/{engine → session}/labels.mjs +2 -2
  246. package/src/tui/{engine → session}/live-share.mjs +38 -4
  247. package/src/tui/{engine → session}/notice-text.mjs +2 -2
  248. package/src/tui/{engine → session}/notification-plan.mjs +3 -3
  249. package/src/tui/session/oauth-flows.mjs +140 -0
  250. package/src/tui/{engine → session}/prompt-history.mjs +3 -3
  251. package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
  252. package/src/tui/{engine → session}/render-timing.mjs +2 -2
  253. package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
  254. package/src/tui/{engine → session}/session-api.mjs +110 -93
  255. package/src/tui/{engine → session}/session-flow.mjs +8 -2
  256. package/src/tui/{engine → session}/session-stats.mjs +3 -3
  257. package/src/tui/{engine → session}/tool-approval.mjs +3 -3
  258. package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
  259. package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
  260. package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
  261. package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
  262. package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
  263. package/src/tui/{engine → session}/turn.mjs +38 -176
  264. package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
  265. package/src/tui/session.mjs +22 -0
  266. package/src/tui/spinner-meta.mjs +4 -4
  267. package/src/tui/spinner-verbs.mjs +51 -3
  268. package/src/ui/statusline-segments.mjs +1 -1
  269. package/src/ui/statusline.mjs +1 -1
  270. package/vendor/ink/build/components/App.js +10 -1
  271. package/src/session-runtime/memory-daemon-probe.mjs +0 -61
  272. package/src/standalone/engine-daemon-client.mjs +0 -894
  273. package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
  274. package/src/standalone/engine-daemon-protocol.mjs +0 -33
  275. package/src/tui/engine/turn-watchdog.mjs +0 -92
  276. package/src/tui/engine.mjs +0 -22
  277. package/src/tui/hooks/useEngine.mjs +0 -16
  278. /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
  279. /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
  280. /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
  281. /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
  282. /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
@@ -1,24 +1,23 @@
1
1
  /**
2
- * src/tui/engine/turn.mjs - lead TUI turn engine (createRunTurn). Extracted from engine.mjs.
2
+ * src/tui/session/turn.mjs - lead TUI turn session runtime (createRunTurn). Extracted from session-local.mjs.
3
3
  */
4
- import { aggregateToolCategoryEntry, aggregateDoneCategories, classifyToolCategory, formatAggregateDetail, summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
4
+ import { aggregateToolCategoryEntries, aggregateDoneCategories, classifyToolCategory, formatAggregateDetail, summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
5
5
  import { applyUsageDelta } from './session-stats.mjs';
6
6
  import { pickVerb, pickDoneVerb, compactEventLabel, compactEventDetail } from './labels.mjs';
7
- import { toolResultText, toolErrorDisplay } from './tool-result-text.mjs';
7
+ import { toolResultText, toolErrorDisplay, stripShellExitHeader } from './tool-result-text.mjs';
8
8
  import { toolCallId, toolResultCallId, toolCallName, toolCallArgs } from './tool-call-fields.mjs';
9
9
  import { promptDisplayText, STEERING_SUPPRESSED_DISPLAY } from './queue-helpers.mjs';
10
10
  import { TUI_FRAME_MS, yieldToRenderer } from './render-timing.mjs';
11
- import { createTurnWatchdog } from './turn-watchdog.mjs';
12
11
  import { aggregateRawResult, aggregateBucketForCategory, aggregateSummaries, assignAggregateSummaryOrder, failureDetailText, toolCallOutcome } from './tool-result-status.mjs';
13
12
 
14
13
  export const STREAM_BATCH_INTERVAL_MS = TUI_FRAME_MS;
15
14
 
16
15
  export function createRunTurn(bag) {
17
16
  const {
18
- runtime, nextId, tuiDebug, LEAD_TURN_TIMEOUT_MS, flags, pending, itemIndexById, getState, set, flushEmit, flushEmitImmediate, pushItem, appendItems, patchItem, replaceItems, updateStreamingTail: updateStreamingTailFromStore, settleStreamingTail: settleStreamingTailFromStore, clearStreamingTail: clearStreamingTailFromStore, pushNotice, pushUserOrSyntheticItem, markToolCallActive, markToolCallDone, clearActiveToolSummary, agentStatusState, routeState, transcriptRouteMetadata, syncContextStats, denyAllToolApprovals, requestToolApproval, patchToolCardResult, flushToolResults, flushDeferredExecutionPendingResumeKick, drain, drainPendingSteering,
17
+ runtime, nextId, tuiDebug, flags, pending, itemIndexById, getState, set, flushEmit, flushEmitImmediate, pushItem, appendItems, patchItem, replaceItems, updateStreamingTail: updateStreamingTailFromStore, settleStreamingTail: settleStreamingTailFromStore, clearStreamingTail: clearStreamingTailFromStore, pushNotice, pushUserOrSyntheticItem, markToolCallActive, markToolCallDone, clearActiveToolSummary, agentStatusState, routeState, transcriptRouteMetadata, syncContextStats, denyAllToolApprovals, requestToolApproval, patchToolCardResult, flushToolResults, flushDeferredExecutionPendingResumeKick, drainPendingSteering,
19
18
  } = bag;
20
19
  // Small fallbacks keep isolated createRunTurn harnesses source-compatible;
21
- // the real engine supplies atomic implementations that also maintain revision.
20
+ // the real session runtime supplies atomic implementations that also maintain revision.
22
21
  const updateStreamingTail = updateStreamingTailFromStore || ((id, patch = {}, extra = {}) => {
23
22
  set({
24
23
  streamingTail: { ...(getState().streamingTail || {}), ...patch, kind: 'assistant', id, streaming: true },
@@ -48,11 +47,8 @@ export function createRunTurn(bag) {
48
47
  const turnTranscriptMeta = { ...baseTranscriptMeta, completionVerb };
49
48
  flags.pendingTranscriptMeta = null;
50
49
  const { at: _userItemAt, ...turnRouteMeta } = turnTranscriptMeta;
51
- // Per-turn epoch. Force-release (watchdog grace) bumps the shared counter so
52
- // this turn's own eventual `finally` — which may run LONG after force-release
53
- // already started a new turn that reuses the per-session mutex — can detect
54
- // it is stale and skip all shared-getState() writes (busy, flags.activePromptRestore,
55
- // turndone, drain kick). Neutralizes the stale unwind without touching the mutex.
50
+ // Per-turn epoch prevents a disposed/replaced surface from publishing a
51
+ // terminal snapshot after another turn has become the visible owner.
56
52
  const turnEpoch = ++flags.leadTurnEpoch;
57
53
  const isCurrentTurn = () => !flags.disposed && flags.leadTurnEpoch === turnEpoch;
58
54
  const inputBaseline = getState().stats.inputTokens;
@@ -79,80 +75,8 @@ export function createRunTurn(bag) {
79
75
  let assistantText = '';
80
76
  let currentAssistantId = null;
81
77
 
82
- tuiDebug(`runTurn start turn=${turnIndex} pending=${pending.length} timeoutMs=${LEAD_TURN_TIMEOUT_MS}`);
83
- // ── Idle watchdog ─────────────────────────────────────────────────────
84
- // If this turn stops making observable progress (provider call stuck),
85
- // trip after the cap: abort the in-flight run via the existing interrupt
86
- // path (runtime.abort, the same one Esc uses), which rejects the pending
87
- // runtime.ask() with a SessionClosedError so the normal cancelled-turn
88
- // teardown runs. Keep this progress-based rather than total wall-clock:
89
- // long multi-tool turns can legitimately exceed 5m while still producing
90
- // model/tool events.
91
- let watchdogGraceTimer = null;
92
- const configuredLeadToolMaxMs = Number(process.env.MIXDOG_LEAD_TOOL_MAX_MS);
93
- const leadToolMaxMs = Number.isFinite(configuredLeadToolMaxMs) && configuredLeadToolMaxMs > 0
94
- ? configuredLeadToolMaxMs
95
- : 30 * 60 * 1000;
96
- // Scheduling/liveness lives in turn-watchdog.mjs; the trip ACTION stays here
97
- // because it unwinds this turn's UI state.
98
- const watchdog = createTurnWatchdog({
99
- runtime,
100
- timeoutMs: LEAD_TURN_TIMEOUT_MS,
101
- toolMaxMs: leadToolMaxMs,
102
- isCurrentTurn,
103
- onTrip: ({ idleMs, lastProgressLabel }) => {
104
- if (_batchTimer !== null) {
105
- clearTimeout(_batchTimer);
106
- _batchTimer = null;
107
- }
108
- _pendingTextFlush = false;
109
- _pendingThinkFlush = false;
110
- _pendingThinkingLastEndedAt = 0;
111
- const elapsed = Date.now() - startedAt;
112
- tuiDebug(`runTurn WATCHDOG TRIP turn=${turnIndex} elapsedMs=${elapsed} idleMs=${idleMs} lastProgress=${lastProgressLabel} — aborting stuck turn`);
113
- pushNotice(`Turn timed out after ${Math.round(idleMs / 1000)}s idle (last progress: ${lastProgressLabel}) — aborting stuck request. Input will be released shortly if abort does not unwind.`, 'warn', { transcript: true });
114
- try { runtime.abort('watchdog'); } catch {}
115
- // Belt-and-suspenders: if runtime.abort() did not reject runtime.ask()
116
- // (unwind starved), hard-release the turn after a short grace so the
117
- // React store is never left with busy=true and no drain in flight.
118
- watchdogGraceTimer = setTimeout(() => {
119
- if (flags.disposed) return;
120
- if (!getState().busy) return;
121
- if (flags.leadTurnEpoch !== turnEpoch) return; // a newer turn already owns the store
122
- tuiDebug(`runTurn WATCHDOG FORCE-RELEASE turn=${turnIndex} — abort unwind starved`);
123
- denyAllToolApprovals('turn timed out');
124
- clearActiveToolSummary();
125
- flushToolResults([], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true, cancelled: true });
126
- finalizeToolHeaders();
127
- clearDeferredTimers();
128
- flags.flushDeferredBeforeImmediatePush = null;
129
- if (currentAssistantId && currentAssistantText.trim()) {
130
- settleStreamingTail(currentAssistantId, { text: currentAssistantText });
131
- } else {
132
- clearStreamingTail(currentAssistantId);
133
- }
134
- // Bump the epoch FIRST so this (still-stuck) turn's later finally becomes
135
- // a no-op for shared getState() and cannot corrupt the turn we hand off to.
136
- flags.leadTurnEpoch++;
137
- set({ busy: false, spinner: null, thinking: null, lastTurn: null });
138
- flags.activePromptRestore = null;
139
- if (flags.draining) flags.draining = false;
140
- if (pending.length > 0) void drain();
141
- // busy→false here bypasses both the normal turn-end flush and the
142
- // drain-finally flush, so a deferred completion kick would never re-arm.
143
- // Fire it explicitly (idempotent: guarded by deferred flag + !busy).
144
- flushDeferredExecutionPendingResumeKick();
145
- flushEmitImmediate?.();
146
- }, 5_000);
147
- watchdogGraceTimer.unref?.();
148
- },
149
- });
150
- const clearWatchdog = () => {
151
- watchdog.clear();
152
- if (watchdogGraceTimer) { clearTimeout(watchdogGraceTimer); watchdogGraceTimer = null; }
153
- };
154
- const markTurnProgress = (label) => watchdog.markProgress(label);
155
- watchdog.arm();
78
+ tuiDebug(`runTurn start turn=${turnIndex} pending=${pending.length}`);
79
+ const markTurnProgress = () => isCurrentTurn();
156
80
  let currentAssistantText = '';
157
81
  // Segments sealed as their own assistant item(s) this turn via
158
82
  // commitAssistantSegment (e.g. a tool preamble, then a no-newline tail
@@ -183,12 +107,6 @@ export function createRunTurn(bag) {
183
107
  const toolCards = [];
184
108
  const toolGroups = new Map();
185
109
  const resultsDone = new Set();
186
- // apply_patch+post_shell twin display: parent callId → synthetic shell
187
- // callId. The protocol stays ONE call/ONE result, but the transcript shows
188
- // TWO cards (edit + verify shell) so a long post_shell no longer looks
189
- // like a stuck 'Editing 1 file'. deliverToolResultMessage splits the
190
- // combined result text back between the two cards.
191
- const postShellTwins = new Map();
192
110
  // ── Live shell-output tail → running tool card ─────────────────────────
193
111
  // 1 s poll of orchestrator liveness while this turn runs. When exactly one
194
112
  // standalone (non-aggregate) card is unresolved and the runtime reports a
@@ -591,11 +509,11 @@ export function createRunTurn(bag) {
591
509
  let _lastNewlineIdx = -1; // offset of the last completed-line '\n' found so far
592
510
  let _emittedNewlineIdx = -2; // newline offset backing _emittedVisibleText (-2 forces first compute)
593
511
  let _emittedVisibleText = ''; // cached visible slice for the current newline offset
594
- // Engine-local streaming scalars. Neither responseLength nor thinkingText is
512
+ // Session runtime-local streaming scalars. Neither responseLength nor thinkingText is
595
513
  // rendered per-token by any consumer: App reads getState().thinking only as a
596
514
  // boolean (App.jsx `!!(getState().thinking || liveSpinner?.thinking)`) and the
597
515
  // Spinner takes outputTokens, not responseLength. So we keep these growing
598
- // values in engine-local vars and publish to the store only on a visible
516
+ // values in session runtime-local vars and publish to the store only on a visible
599
517
  // transition (thinking on↔off), a completed visible text line, tool/usage
600
518
  // updates, or finalization — not on every 8ms streaming flush.
601
519
  let _publishedThinkingActive = false; // last thinking boolean pushed to store
@@ -696,7 +614,7 @@ export function createRunTurn(bag) {
696
614
  // reads the thinking flag + timing anchors — none of them render the
697
615
  // growing thinkingText. So publish the thinking boolean only on the
698
616
  // OFF→ON transition (or when compacting toggles the flag), not on every
699
- // 8ms reasoning chunk. The full thinkingText stays engine-local and is
617
+ // 8ms reasoning chunk. The full thinkingText stays session runtime-local and is
700
618
  // emitted at finalize via the normal spinner/thinking teardown.
701
619
  const nextThinkingActive = !compactingActive;
702
620
  // Skip the push when the published thinking boolean is unchanged: neither
@@ -748,7 +666,9 @@ export function createRunTurn(bag) {
748
666
  // the provider's isError/error-tool envelope drives failure counts/red.
749
667
  const { exitCode, isExitError, isCallError } = toolCallOutcome(message, rawText);
750
668
  const isError = isCallError;
751
- const text = isError ? toolErrorDisplay(rawText, callRec.name || 'tool') : rawText;
669
+ const text = isError
670
+ ? toolErrorDisplay(rawText, callRec.name || 'tool')
671
+ : (isExitError ? stripShellExitHeader(rawText) : rawText);
752
672
  callRec.summary = !isError ? summarizeToolResult(callRec.name, callRec.args, rawText, isError) : null;
753
673
  assignAggregateSummaryOrder(aggregate, callRec);
754
674
  callRec.isError = isError;
@@ -805,49 +725,7 @@ export function createRunTurn(bag) {
805
725
  const earlyCallId = toolResultCallId(message);
806
726
  if (earlyCallId) {
807
727
  markToolCardCompletedState(earlyCallId, message);
808
- const earlyTwin = postShellTwins.get(earlyCallId);
809
- if (earlyTwin) markToolCardCompletedState(earlyTwin, message);
810
- }
811
- return;
812
- }
813
- const parentCallId = toolResultCallId(message);
814
- const twinCallId = parentCallId ? postShellTwins.get(parentCallId) : null;
815
- if (twinCallId && !resultsDone.has(twinCallId)) {
816
- const combined = String(toolResultText(message?.content) ?? '');
817
- // tool-exec composes `${patch}\n\n${header}\n${shell}` (or appends the
818
- // skipped marker to a failed patch). Split on those exact seams.
819
- const headerMatch = combined.match(/\n+--- post_shell( FAILED[^\n]*)? ---\n?/);
820
- const skippedMatch = headerMatch ? null : combined.match(/\n+--- post_shell skipped: patch failed ---\s*$/);
821
- let patchPart = combined;
822
- let shellPart = null;
823
- if (headerMatch) {
824
- patchPart = combined.slice(0, headerMatch.index);
825
- shellPart = combined.slice(headerMatch.index + headerMatch[0].length);
826
- // Keep the FAILED banner on the shell card so its error state and
827
- // fix-and-re-verify guidance survive the split.
828
- if (headerMatch[1]) shellPart = `Error: post_shell FAILED (patch is applied; fix and re-verify)\n${shellPart}`;
829
- } else if (skippedMatch) {
830
- patchPart = combined.slice(0, skippedMatch.index);
831
- shellPart = 'Error: post_shell skipped: patch failed';
832
728
  }
833
- if (shellPart !== null) {
834
- flushToolResults([
835
- {
836
- role: 'tool',
837
- tool_call_id: parentCallId,
838
- content: patchPart,
839
- ...(Object.hasOwn(message || {}, 'uiDiff') ? { uiDiff: message.uiDiff } : {}),
840
- },
841
- { role: 'tool', tool_call_id: twinCallId, content: shellPart },
842
- ], toolCards, cardByCallId, toolGroups, resultsDone);
843
- return;
844
- }
845
- // Unsplittable (offloaded/preview-shaped result): parent keeps the
846
- // combined text; settle the twin instead of leaving it Running.
847
- flushToolResults([
848
- message,
849
- { role: 'tool', tool_call_id: twinCallId, content: '(post_shell output included in the patch result above)' },
850
- ], toolCards, cardByCallId, toolGroups, resultsDone);
851
729
  return;
852
730
  }
853
731
  flushToolResults([message], toolCards, cardByCallId, toolGroups, resultsDone);
@@ -918,24 +796,7 @@ export function createRunTurn(bag) {
918
796
  }
919
797
  const batchCalls = (calls || []).filter(Boolean);
920
798
  if (batchCalls.length === 0) return;
921
- // Expand each apply_patch carrying post_shell into itself PLUS a
922
- // synthetic trailing shell call, so the (possibly long) verify phase
923
- // renders as its own Running command card in call order.
924
- const displayCalls = [];
925
- for (const c of batchCalls) {
926
- displayCalls.push(c);
927
- if (toolCallName(c) !== 'apply_patch') continue;
928
- let patchCallArgs = toolCallArgs(c);
929
- if (typeof patchCallArgs === 'string') {
930
- try { patchCallArgs = JSON.parse(patchCallArgs); } catch { patchCallArgs = null; }
931
- }
932
- const postShellCmd = typeof patchCallArgs?.post_shell === 'string' ? patchCallArgs.post_shell.trim() : '';
933
- const parentCallId = toolCallId(c);
934
- if (!postShellCmd || !parentCallId) continue;
935
- const twinCallId = `${parentCallId}::post_shell`;
936
- postShellTwins.set(parentCallId, twinCallId);
937
- displayCalls.push({ id: twinCallId, name: 'shell', args: { command: postShellCmd } });
938
- }
799
+ const displayCalls = batchCalls;
939
800
  const agentBatch = ++providerToolBatch;
940
801
  const committedAssistantSegment = commitAssistantSegment({ sealToolBlock: true });
941
802
  if (committedAssistantSegment) {
@@ -972,10 +833,11 @@ export function createRunTurn(bag) {
972
833
  const bucket = aggregateBucketForCategory(category, { agentBatch });
973
834
  const callId = toolCallId(c);
974
835
  const callKey = callId || `__tool_${toolCards.length}_${i}`;
975
- // The old App scan counted multi-pattern calls via
976
- // aggregateToolCategoryEntry(...).count, not a flat 1. Derive the same
836
+ // The old App scan counted multi-pattern calls via category work
837
+ // units, not a flat 1. Derive the same
977
838
  // count here so the incremental Explore/Search summary matches.
978
- const activeCount = Number(aggregateToolCategoryEntry(name, args, category)?.count || 1);
839
+ const categoryEntries = aggregateToolCategoryEntries(name, args, category);
840
+ const activeCount = categoryEntries.reduce((total, entry) => total + Number(entry.count || 1), 0);
979
841
  // Track Explore/Search calls as active for the incremental prompt-
980
842
  // line summary; cleared when their result lands or the turn ends.
981
843
  markToolCallActive(callKey, category, activeCount, Date.now());
@@ -1024,15 +886,16 @@ export function createRunTurn(bag) {
1024
886
  continue;
1025
887
  }
1026
888
 
1027
- const categoryEntry = aggregateToolCategoryEntry(name, args, category);
1028
889
  const aggregateCard = ensureAggregateCard(bucket);
1029
890
  if (shellAfterEdit) aggregateCard.verifyShell = true;
1030
- if (!aggregateCard.categories.has(categoryEntry.key)) aggregateCard.categoryOrder.push(categoryEntry.key);
1031
- const prevCategory = aggregateCard.categories.get(categoryEntry.key);
1032
- aggregateCard.categories.set(categoryEntry.key, {
1033
- ...categoryEntry,
1034
- count: Number(prevCategory?.count || 0) + Number(categoryEntry.count || 1),
1035
- });
891
+ for (const categoryEntry of categoryEntries) {
892
+ if (!aggregateCard.categories.has(categoryEntry.key)) aggregateCard.categoryOrder.push(categoryEntry.key);
893
+ const prevCategory = aggregateCard.categories.get(categoryEntry.key);
894
+ aggregateCard.categories.set(categoryEntry.key, {
895
+ ...categoryEntry,
896
+ count: Number(prevCategory?.count || 0) + Number(categoryEntry.count || 1),
897
+ });
898
+ }
1036
899
  aggregateCard.calls.set(callKey, { name, args, category, summary: null, summarySeq: null, isError: false, isCallError: false, isExitError: false, exitCode: null, resultText: null, resolved: false, completedEarly: false });
1037
900
  touchedAggregates.add(aggregateCard);
1038
901
  const card = { itemId: aggregateCard.itemId, callId: callKey, done: false, aggregate: aggregateCard };
@@ -1227,11 +1090,14 @@ export function createRunTurn(bag) {
1227
1090
  flushStreamBatch();
1228
1091
  },
1229
1092
  onReasoningDelta: (chunk) => {
1230
- if (!isCurrentTurn() || watchdog.tripped) return;
1093
+ if (!isCurrentTurn()) return;
1231
1094
  if (String(chunk ?? '')) {
1232
1095
  if (!markTurnProgress('reasoning-delta')) return;
1233
- markPromptCommitted();
1234
1096
  }
1097
+ // Reasoning is NOT a commit boundary (Claude Code parity): a turn
1098
+ // interrupted while it is still thinking produced no model-visible
1099
+ // message, so Esc must hand the prompt back to the input box. Text,
1100
+ // tool calls and turn end still commit it below.
1235
1101
  startThinkingSegment();
1236
1102
  thinkingText += String(chunk ?? '');
1237
1103
  // Accumulate reasoning text; fire at most one render per STREAM_BATCH_INTERVAL_MS.
@@ -1341,13 +1207,9 @@ export function createRunTurn(bag) {
1341
1207
  } finally {
1342
1208
  const isStaleUnwind = !isCurrentTurn();
1343
1209
  if (!isStaleUnwind) denyAllToolApprovals(cancelled ? 'turn cancelled' : 'turn finished');
1344
- // Turn is unwinding normally (or via abort) — cancel the idle watchdog and
1345
- // its force-release grace so they never fire on a live turn.
1346
- clearWatchdog();
1347
1210
  clearLiveTailTimer();
1348
- // If the watchdog force-release already fired, a NEWER turn now owns the
1349
- // shared store (busy, flags.activePromptRestore, turndone, drain). This stale
1350
- // unwind must NOT write shared getState() or it corrupts that turn.
1211
+ // A replaced/disposed surface must not write shared state owned by the
1212
+ // current visible turn.
1351
1213
  let closingItems = [];
1352
1214
  if (deferredEntries.length) {
1353
1215
  if (!isStaleUnwind) {
@@ -1367,7 +1229,7 @@ export function createRunTurn(bag) {
1367
1229
  if (!isStaleUnwind) flags.flushDeferredBeforeImmediatePush = null;
1368
1230
  closeThinkingSegment();
1369
1231
  if (isStaleUnwind) {
1370
- tuiDebug(`runTurn STALE UNWIND turn=${turnIndex} — force-released; skipping shared UI/state writes`);
1232
+ tuiDebug(`runTurn STALE UNWIND turn=${turnIndex} — skipping shared UI/state writes`);
1371
1233
  } else {
1372
1234
  if (currentAssistantId && getState().streamingTail?.id === currentAssistantId) {
1373
1235
  if (currentAssistantText.trim()) {
@@ -1383,7 +1245,7 @@ export function createRunTurn(bag) {
1383
1245
  flags.activePromptRestore = null;
1384
1246
  const elapsedMs = Date.now() - startedAt;
1385
1247
  const thinkingElapsedMs = thinkingStartedAt ? accumulatedThinkingMs : 0;
1386
- // responseLength is engine-local now (not pushed per-token), so compute the
1248
+ // responseLength is session runtime-local now (not pushed per-token), so compute the
1387
1249
  // fallback from the live accumulator instead of the possibly-stale
1388
1250
  // getState().spinner.responseLength. Final-only / non-streaming turns never
1389
1251
  // accumulate `assistantText` (only currentAssistantText is set at the
@@ -1434,7 +1296,7 @@ export function createRunTurn(bag) {
1434
1296
  flushEmit?.();
1435
1297
  _publishedThinkingActive = false; // turn teardown cleared getState().thinking
1436
1298
  const finalStatus = cancelled ? 'cancelled' : (failed ? 'failed' : 'done');
1437
- tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${watchdog.tripped ? ' watchdogTripped=1' : ''} pending=${pending.length}`);
1299
+ tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt} pending=${pending.length}`);
1438
1300
  return finalStatus;
1439
1301
  }
1440
1302