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,26 +1,16 @@
1
1
  /**
2
- * src/tui/engine/session-api.mjs - part of the public engine session object.
2
+ * src/tui/session/session-api.mjs - part of the public session runtime session object.
3
3
  */
4
4
  import { compactEventDetail, projectNameFromPath } from './labels.mjs';
5
5
  import { toolErrorDisplay } from './tool-result-text.mjs';
6
6
  import { isQueuedEntryEditable, promptDisplayText } from './queue-helpers.mjs';
7
- import { createEngineApiB } from './session-api-ext.mjs';
7
+ import { createSessionApiB } from './session-api-ext.mjs';
8
8
  import { buildDoctorReport } from '../app/doctor.mjs';
9
9
  import { recomputePromptHistory } from './prompt-history.mjs';
10
- import { buildMergedPromptHistory, loadPromptHistory } from '../prompt-history-store.mjs';
11
- import { touchProjectSelected } from '../../standalone/projects.mjs';
10
+ import { appendPromptHistory, buildMergedPromptHistory, loadPromptHistory, promptHistoryKey } from '../prompt-history-store.mjs';
12
11
 
13
- // Upper bound on how long a manual Esc abort may leave the store busy while the
14
- // in-flight runtime.ask() unwinds. runtime.abort() normally rejects the pending
15
- // ask within a tick; this is the belt-and-suspenders window before the bounded
16
- // recovery timer hard-releases busy so Esc can never wedge input forever.
17
- const MANUAL_ABORT_RECOVERY_MS = (() => {
18
- const v = Number(process.env.MIXDOG_MANUAL_ABORT_RECOVERY_MS);
19
- return Number.isFinite(v) && v > 0 ? v : 4000;
20
- })();
21
-
22
- export function createEngineApi(bag) {
23
- const api = { ...createEngineApiA(bag), ...createEngineApiB(bag) };
12
+ export function createSessionApi(bag) {
13
+ const api = { ...createSessionApiA(bag), ...createSessionApiB(bag) };
24
14
  // One-shot settings/status bundle. On a daemon-backed store every getter is
25
15
  // its own serialized round-trip and the settings panel needs a dozen of them,
26
16
  // so the panel asks for this instead of fanning out. It calls the SAME
@@ -33,7 +23,7 @@ export function createEngineApi(bag) {
33
23
  const snapshot = {
34
24
  autoClear: await read(api.getAutoClear),
35
25
  compaction: await read(api.getCompactionSettings),
36
- memory: await read(api.getMemorySettings),
26
+ recap: await read(api.getRecapSettings),
37
27
  channels: await read(api.getChannelSettings, { includeStatus: false }),
38
28
  systemShell: await read(api.getSystemShell),
39
29
  outputStyle: (await read(api.getOutputStyle)) || (await read(api.listOutputStyles)),
@@ -43,7 +33,7 @@ export function createEngineApi(bag) {
43
33
  updateSettings: await read(api.getUpdateSettings),
44
34
  };
45
35
  if (heavy) {
46
- snapshot.channelBackend = (await read(api.getChannelSetup))?.backend || 'discord';
36
+ snapshot.channelProvider = (await read(api.getChannelSetup))?.provider || 'discord';
47
37
  snapshot.mcp = await read(api.mcpStatus);
48
38
  snapshot.hooks = await read(api.hooksStatus);
49
39
  snapshot.plugins = await read(api.pluginsStatus);
@@ -54,31 +44,38 @@ export function createEngineApi(bag) {
54
44
  return api;
55
45
  }
56
46
 
57
- export function createEngineApiA(bag) {
47
+ export function createSessionApiA(bag) {
58
48
  const {
59
49
  runtime, nextId, flags, pending, listeners, getState, getPublishedState = getState, set, flushEmitImmediate, pushItem, patchItem, replaceItems, restoreOlderTranscript, restoreNewerTranscript, settleStreamingTail, clearStreamingTail, pushNotice, autoClearState, agentStatusState, routeState, syncContextStats, denyAllToolApprovals, updateAgentJobCard, requeueEntriesFront, enqueue, autoClearBeforeSubmit, restoreQueued, resetStatsAndSyncContext, drain, flushDeferredExecutionPendingResumeKick, discardExecutionPendingResume,
60
50
  } = bag;
61
- const submit = (text, options = {}) => {
51
+ const submission = (text, options = {}) => {
62
52
  const t = promptDisplayText(text, options).trim();
63
- if (!t) return false;
53
+ if (!t) return null;
64
54
  const mode = options.mode || 'prompt';
65
- // Prompt input queued while a turn is active keeps the
66
- // default `next` priority, so it is injected at the next tool/model
67
- // boundary. Explicit options.priority still wins.
55
+ // Prompt input queued while a turn is active keeps the default `next`
56
+ // priority, so it is injected at the next tool/model boundary. Explicit
57
+ // options.priority still wins.
68
58
  const priority = options.priority;
69
- const queueOptions = {
70
- ...options,
71
- // Always mint a submission id so daemon retries / dual-path intake
72
- // can dedupe instead of booking the same prompt twice.
73
- id: String(options.id || '').trim() || nextId(),
74
- mode,
75
- displayText: promptDisplayText(text, options),
76
- priority,
59
+ return {
60
+ text,
61
+ queueOptions: {
62
+ ...options,
63
+ // Always mint a submission id so daemon retries / dual-path intake
64
+ // can dedupe instead of booking the same prompt twice.
65
+ id: String(options.id || '').trim() || nextId(),
66
+ mode,
67
+ displayText: promptDisplayText(text, options),
68
+ priority,
69
+ },
77
70
  };
71
+ };
72
+ const submit = (text, options = {}) => {
73
+ const intake = submission(text, options);
74
+ if (!intake) return false;
78
75
  // A running clear (idle auto-clear or session_manage) sets commandBusy;
79
76
  // queue the prompt instead of dropping it — it drains after the clear.
80
77
  if (flags.autoClearRunning) {
81
- return enqueue(text, queueOptions) !== false;
78
+ return enqueue(intake.text, intake.queueOptions) !== false;
82
79
  }
83
80
  // Any in-flight session command (clear/setModel/newSession/resume/...)
84
81
  // holds commandBusy. Previously the prompt was dropped here and only the
@@ -86,17 +83,31 @@ export function createEngineApiA(bag) {
86
83
  // commandBusy, and the central release hook re-kicks drain once the
87
84
  // command settles, so the prompt runs afterwards rather than vanishing.
88
85
  if (getState().commandBusy) {
89
- return enqueue(text, queueOptions) !== false;
86
+ return enqueue(intake.text, intake.queueOptions) !== false;
90
87
  }
91
88
  if (getState().busy) {
92
- return enqueue(text, queueOptions) !== false;
89
+ return enqueue(intake.text, intake.queueOptions) !== false;
93
90
  }
94
91
  // If autoClearBeforeSubmit rejects (e.g. compaction timeout throws), the
95
92
  // prompt must still be queued — swallow the rejection so enqueue always
96
93
  // runs and the submit is never silently lost.
97
- void autoClearBeforeSubmit().catch(() => {}).then(() => enqueue(text, queueOptions));
94
+ void autoClearBeforeSubmit().catch(() => {}).then(
95
+ () => enqueue(intake.text, intake.queueOptions),
96
+ );
98
97
  return true;
99
98
  };
99
+ const submitAsync = async (text, options = {}) => {
100
+ const intake = submission(text, options);
101
+ if (!intake) return false;
102
+ // Daemon intake needs an acknowledgement boundary stronger than the TUI's
103
+ // synchronous submit(): by the time this Promise resolves, the prompt is
104
+ // present either as a visible queue entry or as the first durable user row.
105
+ // Provider execution still runs detached through drain().
106
+ if (!flags.autoClearRunning && !getState().commandBusy && !getState().busy) {
107
+ await autoClearBeforeSubmit().catch(() => {});
108
+ }
109
+ return enqueue(intake.text, intake.queueOptions) !== false;
110
+ };
100
111
  return {
101
112
  getState: () => getPublishedState(),
102
113
  patchItem,
@@ -107,7 +118,7 @@ export function createEngineApiA(bag) {
107
118
  return () => listeners.delete(listener);
108
119
  },
109
120
  submit,
110
- submitAsync: async (text, options = {}) => submit(text, options),
121
+ submitAsync,
111
122
  reserveSession: (sessionId) => {
112
123
  const id = runtime.reserveSessionId?.(sessionId);
113
124
  if (!id) return false;
@@ -116,6 +127,46 @@ export function createEngineApiA(bag) {
116
127
  return String(getState().sessionId || '') === String(id);
117
128
  },
118
129
  restoreQueued,
130
+ // Claude Code's message selector ("jump back to a previous message"):
131
+ // rewind the conversation to just before a user prompt and hand its text
132
+ // back for editing. Idle-only — a live turn must be interrupted first.
133
+ rewindToItem: async (itemId) => {
134
+ const target = String(itemId ?? '').trim();
135
+ if (!target) return null;
136
+ if (getState().busy || getState().commandBusy) return null;
137
+ const items = getState().items || [];
138
+ const index = items.findIndex(
139
+ (item) => item?.kind === 'user' && String(item?.id) === target,
140
+ );
141
+ if (index < 0) return null;
142
+ const text = String(items[index]?.text || '').trim();
143
+ if (!text) return null;
144
+ set({ commandBusy: true });
145
+ try {
146
+ const rewound = await runtime.rewindMessages?.({ text });
147
+ if (!rewound) return null;
148
+ const restoreKey = promptHistoryKey(text);
149
+ set({
150
+ items: replaceItems(items.slice(0, index), {
151
+ preserveSpill: true,
152
+ preserveTranscriptView: true,
153
+ }),
154
+ spinner: null,
155
+ thinking: null,
156
+ lastTurn: null,
157
+ // The prompt returns to the draft, so it must not ALSO sit in the
158
+ // Up-arrow history — otherwise it shows up twice.
159
+ promptHistoryList: (getState().promptHistoryList || [])
160
+ .filter((entry) => promptHistoryKey(entry) !== restoreKey),
161
+ });
162
+ syncContextStats({ allowEstimated: true });
163
+ set({ stats: { ...getState().stats } });
164
+ flushEmitImmediate?.();
165
+ return { text, removed: items.length - index, messages: rewound.removed };
166
+ } finally {
167
+ set({ commandBusy: false });
168
+ }
169
+ },
119
170
  setModel: async (m) => {
120
171
  if (getState().commandBusy) return false;
121
172
  set({ commandBusy: true });
@@ -209,14 +260,14 @@ export function createEngineApiA(bag) {
209
260
  set({ commandBusy: false });
210
261
  }
211
262
  },
212
- getMemorySettings: () => {
213
- return runtime.getMemorySettings?.() || { enabled: true };
263
+ getRecapSettings: () => {
264
+ return runtime.getRecapSettings?.() || { enabled: true };
214
265
  },
215
- setMemoryEnabled: async (enabled) => {
266
+ setRecapEnabled: async (enabled) => {
216
267
  if (getState().commandBusy) return null;
217
268
  set({ commandBusy: true });
218
269
  try {
219
- const next = await runtime.setMemoryEnabled?.(enabled);
270
+ const next = await runtime.setRecapEnabled?.(enabled);
220
271
  set({ ...routeState(), stats: { ...getState().stats } });
221
272
  // Deferred for the same reason as setCompactionSettings above: keep
222
273
  // the recompute off the key-handler tick so the toggle repaints
@@ -296,12 +347,6 @@ export function createEngineApiA(bag) {
296
347
  },
297
348
  setCwd: (path, options = {}) => {
298
349
  const next = runtime.setCwd(path);
299
- // "Last opened" recency belongs to the ACTUAL cwd switch, not to one
300
- // caller: the project picker, its Current Path row and the /cwd prompt
301
- // all land here, so the project list stays ordered by what was really
302
- // opened last. Unregistered folders are ignored — this never registers
303
- // a project on its own (addProject remains the only registration path).
304
- try { touchProjectSelected(next); } catch { /* best-effort recency */ }
305
350
  // Republish up-arrow history for the NEW project: current session prompts
306
351
  // merged with the cwd-scoped persisted store for the new cwd.
307
352
  const sessionList = recomputePromptHistory(getState().items);
@@ -311,6 +356,15 @@ export function createEngineApiA(bag) {
311
356
  }
312
357
  return next;
313
358
  },
359
+ rememberPromptHistory: (value) => {
360
+ const text = String(value || '').trim();
361
+ if (!text) return false;
362
+ const persisted = appendPromptHistory(getState().cwd, text);
363
+ if (!persisted) return false;
364
+ const sessionList = recomputePromptHistory(getState().items);
365
+ set({ promptHistoryList: buildMergedPromptHistory(sessionList, persisted) });
366
+ return true;
367
+ },
314
368
  getSystemShell: () => {
315
369
  return runtime.getSystemShell?.() || runtime.systemShell || { source: 'auto', command: '', effective: '' };
316
370
  },
@@ -608,7 +662,7 @@ export function createEngineApiA(bag) {
608
662
  set({ commandBusy: false, commandStatus: null });
609
663
  }
610
664
  },
611
- abort: () => {
665
+ abort: (options = {}) => {
612
666
  if (!getState().busy) return false;
613
667
  denyAllToolApprovals('interrupted by user');
614
668
  const restoreState = flags.activePromptRestore;
@@ -616,7 +670,9 @@ export function createEngineApiA(bag) {
616
670
  // interrupting should just cancel the running turn and let the steering
617
671
  // prompt run next, NOT resurrect the in-flight prompt back into the draft.
618
672
  const hasPendingSteering = pending.some((entry) => isQueuedEntryEditable(entry));
619
- const canRestore = restoreState?.restorable && !hasPendingSteering;
673
+ const canRestore = options?.restorePrompt !== false
674
+ && restoreState?.restorable
675
+ && !hasPendingSteering;
620
676
  const restoreText = canRestore ? restoreState.text : '';
621
677
  const restorePastedImages = canRestore && restoreState?.pastedImages ? restoreState.pastedImages : null;
622
678
  const restorePastedTexts = canRestore && restoreState?.pastedTexts ? restoreState.pastedTexts : null;
@@ -644,6 +700,11 @@ export function createEngineApiA(bag) {
644
700
  restoreState.reclaimed = true;
645
701
  const idSet = new Set((restoreState.submittedIds || []).filter((id) => id != null));
646
702
  const patch = { spinner: null, thinking: null, lastTurn: null };
703
+ if (restoreText) {
704
+ const restoreKey = promptHistoryKey(restoreText);
705
+ patch.promptHistoryList = (getState().promptHistoryList || [])
706
+ .filter((entry) => promptHistoryKey(entry) !== restoreKey);
707
+ }
647
708
  if (idSet.size > 0) {
648
709
  const items = getState().items.filter((item) => !idSet.has(item?.id));
649
710
  if (items.length !== getState().items.length) {
@@ -660,11 +721,7 @@ export function createEngineApiA(bag) {
660
721
  restoreState.requeueEntries = [];
661
722
  restoreState.discardExecutionPendingResumeKeys = [];
662
723
  }
663
- // ── Bounded manual-abort recovery ───────────────────────────────────
664
- // runtime.abort() above normally rejects the in-flight runtime.ask() so
665
- // the turn's own finally clears busy within a tick. If that unwind is
666
- // starved the recovery timer below hard-releases busy. Separately from
667
- // recovery: queued work must never strand behind a cancelled turn
724
+ // Queued work must never strand behind a cancelled turn
668
725
  // (abort preserves pending input for the next turn, and the command
669
726
  // queue survives cancel and fires when idle). The
670
727
  // drain loop that owns a normal turn continues on its own; this bounded
@@ -679,46 +736,6 @@ export function createEngineApiA(bag) {
679
736
  } catch { /* best-effort */ }
680
737
  }, 150);
681
738
  pendingAfterAbortKick.unref?.();
682
- // If that unwind is
683
- // starved — e.g. a provider abort that never settles after a post-tool
684
- // fetch stall — busy would stay true until the far-larger turn watchdog
685
- // trips, wedging the TUI with dead input. Arm a short grace timer that
686
- // hard-releases busy exactly like the watchdog force-release, but ONLY
687
- // when this same turn is still the active owner (epoch unchanged) AND
688
- // still busy — so a normal cancellation that settles in time is never
689
- // masked and a newer turn's store is never corrupted.
690
- const abortEpoch = flags.leadTurnEpoch;
691
- const recoveryMs = Number(flags.manualAbortRecoveryMs) > 0
692
- ? Number(flags.manualAbortRecoveryMs)
693
- : MANUAL_ABORT_RECOVERY_MS;
694
- const recoveryTimer = setTimeout(() => {
695
- if (flags.disposed) return;
696
- if (!getState().busy) return; // normal abort settled
697
- if (flags.leadTurnEpoch !== abortEpoch) return; // a newer turn owns the store
698
- const streamingTail = getState().streamingTail;
699
- if (streamingTail?.text?.trim()) {
700
- settleStreamingTail?.(streamingTail.id, {});
701
- } else {
702
- clearStreamingTail?.();
703
- }
704
- // Bump the epoch FIRST so the still-stuck turn's eventual finally becomes
705
- // a no-op for shared getState() writes and cannot corrupt the handoff.
706
- flags.leadTurnEpoch = (Number(flags.leadTurnEpoch) || 0) + 1;
707
- set({ busy: false, spinner: null, thinking: null, lastTurn: null });
708
- flags.activePromptRestore = null;
709
- // Abandon the drain loop that is still awaiting the stuck turn before
710
- // releasing the drain lock. Its eventual finally observes the stale
711
- // epoch and cannot clear a newer drain's ownership or continue work.
712
- flags.drainEpoch = (Number(flags.drainEpoch) || 0) + 1;
713
- if (flags.draining) flags.draining = false;
714
- pushNotice('Interrupt did not settle — input restored.', 'warn', { transcript: true });
715
- flushEmitImmediate?.();
716
- if (pending.length > 0 && typeof drain === 'function') void drain();
717
- // busy→false here bypasses the normal turn-end + drain-finally flushes,
718
- // so re-arm any deferred completion kick explicitly (idempotent).
719
- if (typeof flushDeferredExecutionPendingResumeKick === 'function') flushDeferredExecutionPendingResumeKick();
720
- }, recoveryMs);
721
- recoveryTimer.unref?.();
722
739
  return { aborted, restoreText, pastedImages: restorePastedImages, discardPastedImages, pastedTexts: restorePastedTexts, discardPastedTexts };
723
740
  },
724
741
  };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * src/tui/engine/session-flow.mjs - prompt queue drain + session clear/reset. Extracted from engine.mjs.
2
+ * src/tui/session/session-flow.mjs - prompt queue drain + session clear/reset. Extracted from session-local.mjs.
3
3
  */
4
4
  import { presentErrorText } from '../../runtime/shared/err-text.mjs';
5
5
  import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-markdown.mjs';
@@ -560,7 +560,13 @@ export function createSessionFlow(bag) {
560
560
  removeQueuedEntries(queued);
561
561
  const queuedText = queued.map((item) => item.text).filter((text) => String(text || '').trim()).join('\n');
562
562
  const combinedText = [queuedText, String(currentText || '')].filter((text) => text.trim()).join('\n');
563
- return { count: queued.length, text: combinedText, pastedImages: mergePastedImages(queued), pastedTexts: mergePastedTexts(queued) };
563
+ return {
564
+ count: queued.length,
565
+ ids: queued.map((item) => String(item.id || '')).filter(Boolean),
566
+ text: combinedText,
567
+ pastedImages: mergePastedImages(queued),
568
+ pastedTexts: mergePastedTexts(queued),
569
+ };
564
570
  }
565
571
 
566
572
  const resetStats = () => {
@@ -1,8 +1,8 @@
1
1
  /**
2
- * src/tui/engine/session-stats.mjs - session-usage accumulator.
2
+ * src/tui/session/session-stats.mjs - session-usage accumulator.
3
3
  *
4
- * Inlined (not imported from ui/statusline.mjs) so the engine has no static
5
- * dependency on the vendored statusline closure. Extracted from engine.mjs.
4
+ * Inlined (not imported from ui/statusline.mjs) so the session runtime has no static
5
+ * dependency on the vendored statusline closure. Extracted from session-local.mjs.
6
6
  */
7
7
  export function createSessionStats() {
8
8
  return {
@@ -1,12 +1,12 @@
1
1
  /**
2
- * src/tui/engine/tool-approval.mjs — tool-approval queue plumbing extracted
3
- * from createEngineSession (engine.mjs) as a dependency-injection factory.
2
+ * src/tui/session/tool-approval.mjs — tool-approval queue plumbing extracted
3
+ * from the session runtime as a dependency-injection factory.
4
4
  *
5
5
  * These handlers drive a small FIFO of hook approval requests and reflect the
6
6
  * active one into store state (state.toolApproval). They need live session
7
7
  * state, so the closure identifiers (state/set/nextId/disposed/timeout) are
8
8
  * threaded through the factory argument via getters — never stale snapshots.
9
- * Every body is the original engine.mjs logic verbatim.
9
+ * Every body is the original session-local.mjs logic verbatim.
10
10
  */
11
11
  export function createToolApproval({ getState, set, nextId, getDisposed, timeoutMs }) {
12
12
  const toolApprovalQueue = [];
@@ -1,6 +1,6 @@
1
1
  /**
2
- * src/tui/engine/tool-call-fields.mjs - accessors for the varied tool-call /
3
- * tool-result id/name/arg shapes across providers. Extracted from engine.mjs.
2
+ * src/tui/session/tool-call-fields.mjs - accessors for the varied tool-call /
3
+ * tool-result id/name/arg shapes across providers. Extracted from session-local.mjs.
4
4
  */
5
5
  export function toolCallId(call) {
6
6
  return call?.id ?? call?.toolCallId ?? call?.tool_call_id ?? call?.call_id;
@@ -1,17 +1,17 @@
1
1
  /**
2
- * src/tui/engine/tool-card-results.mjs — the tool-card result state machine
3
- * (patchToolCardResult + flushToolResults) extracted from createEngineSession
4
- * (engine.mjs) as a dependency-injection factory.
2
+ * src/tui/session/tool-card-results.mjs — the tool-card result state machine
3
+ * (patchToolCardResult + flushToolResults) extracted from the session runtime
4
+ * (session-local.mjs) as a dependency-injection factory.
5
5
  *
6
6
  * These handlers own the per-turn accounting that reflects tool results into
7
7
  * store items: aggregate cards, non-aggregate/legacy agent-job cards, grouped
8
8
  * fallbacks, and the finalize/cancelled sweeps. They mutate live session state,
9
9
  * so state/set/patchItem/markToolCallDone/updateAgentJobCard are threaded via
10
10
  * the factory argument (getters/callbacks) — never stale snapshots. Every body
11
- * is the original engine.mjs logic verbatim.
11
+ * is the original session-local.mjs logic verbatim.
12
12
  */
13
13
  import { summarizeToolResult, aggregateDoneCategories } from '../../runtime/shared/tool-surface.mjs';
14
- import { toolResultText, toolErrorDisplay, toolGroupedDisplayFallback } from './tool-result-text.mjs';
14
+ import { toolResultText, toolErrorDisplay, toolGroupedDisplayFallback, stripShellExitHeader } from './tool-result-text.mjs';
15
15
  import { toolResultCallId } from './tool-call-fields.mjs';
16
16
  import { parseAgentJob } from './agent-envelope.mjs';
17
17
  import {
@@ -82,7 +82,9 @@ export function createToolCardResults({
82
82
  // outcomes remain successful calls with their raw/semantic result detail.
83
83
  const { exitCode, isExitError, isCallError } = toolCallOutcome(message, rawText);
84
84
  const isError = isCallError;
85
- const text = isError ? toolErrorDisplay(rawText, card?.name || 'tool') : rawText;
85
+ const text = isError
86
+ ? toolErrorDisplay(rawText, card?.name || 'tool')
87
+ : (isExitError ? stripShellExitHeader(rawText) : rawText);
86
88
 
87
89
  if (aggregate && card.itemId === aggregate.itemId) {
88
90
  if (!callRec) return false;
@@ -1,10 +1,10 @@
1
1
  /**
2
- * src/tui/engine/tool-result-status.mjs — pure tool-result status + aggregate
3
- * text helpers extracted from createEngineSession (engine.mjs).
2
+ * src/tui/session/tool-result-status.mjs — pure tool-result status + aggregate
3
+ * text helpers extracted from the session runtime.
4
4
  *
5
5
  * These functions were function-declared inside the closure but reference only
6
6
  * their arguments plus toolErrorDisplay (imported below) — no session state —
7
- * so they move out verbatim as free functions. engine.mjs imports them and
7
+ * so they move out verbatim as free functions. session-local.mjs imports them and
8
8
  * keeps calling them unchanged.
9
9
  */
10
10
  import { toolErrorDisplay } from './tool-result-text.mjs';
@@ -28,7 +28,7 @@ export function shellCommandExitCode(text) {
28
28
  // `Error: [shell-run-failed] [exit code: N]` as the leading marker header.
29
29
  if (!/^\s*(?:Error:\s*)?\[shell-run-failed\]/i.test(body)) return null;
30
30
  // Restrict marker parsing to the header region (first line) so only the
31
- // engine-emitted status header — not quoted command output below — counts.
31
+ // session runtime-emitted status header — not quoted command output below — counts.
32
32
  const header = body.split('\n', 1)[0] || '';
33
33
  // Timeout / signal / abort are NOT a plain command exit — keep them "Failed".
34
34
  if (/\[timeout:|\[signal:|timed out|aborted|interrupted/i.test(header)) return null;
@@ -93,7 +93,7 @@ export function withCancelledResultMarker(text, item) {
93
93
  // Do NOT inspect item.rawResult here: aggregate rawResult is child tool
94
94
  // output (`1. grep\n<result>…`) that can incidentally contain a `status:`
95
95
  // line, which would false-positive as an already-terminal status and skip
96
- // the cancelled marker. Only result/text/body are engine-controlled
96
+ // the cancelled marker. Only result/text/body are session runtime-controlled
97
97
  // collapsed detail (empty / status word / an existing marker), so they are
98
98
  // the trustworthy terminal-status sources.
99
99
  const sources = [item?.result, item?.text, body];
@@ -1,8 +1,8 @@
1
1
  /**
2
- * src/tui/engine/tool-result-text.mjs - flatten tool-result content shapes into
2
+ * src/tui/session/tool-result-text.mjs - flatten tool-result content shapes into
3
3
  * display text, plus collapsed-detail/grouped fallbacks and error framing.
4
- * Extracted from engine.mjs; toolResultText/toolAggregateDetailFallback/
5
- * toolGroupedDisplayFallback remain part of engine.mjs's public surface.
4
+ * Extracted from session-local.mjs; toolResultText/toolAggregateDetailFallback/
5
+ * toolGroupedDisplayFallback remain part of session-local.mjs's public surface.
6
6
  */
7
7
  import { presentErrorText } from '../../runtime/shared/err-text.mjs';
8
8
 
@@ -124,3 +124,13 @@ export function toolErrorDisplay(value, surface = 'tool') {
124
124
  }
125
125
  return /^error\s*:/i.test(text) ? text : `Error: ${text}`;
126
126
  }
127
+
128
+ // Display-only: a command-exit card already renders `Exit N` as its state, so
129
+ // the machine header (`Error: [shell-run-failed] [exit code: N]`) is redundant
130
+ // in the body — and the bare word "Error" reads as a tool failure even though
131
+ // the tool ran fine and the command merely exited non-zero. The model-facing
132
+ // result keeps the header untouched; only the rendered body drops it.
133
+ export function stripShellExitHeader(text) {
134
+ const body = String(text ?? '');
135
+ return body.replace(/(^|\n)(?:Error:\s*)?\[shell-run-failed\][^\n]*\n{0,2}/i, '$1');
136
+ }
@@ -1,15 +1,15 @@
1
- // Transcript spill buffer + engine item state, extracted from engine.mjs.
1
+ // Transcript spill buffer + session runtime item state, extracted from session-local.mjs.
2
2
  /**
3
- * src/tui/engine.mjs - the engine<->React bridge (React-free).
3
+ * src/tui/session-local.mjs - the session runtime<->React bridge (React-free).
4
4
  *
5
5
  * Runs mixdog's session manager outside React and exposes a tiny subscribable
6
6
  * store. The React/ink layer consumes it via useSyncExternalStore
7
- * (see hooks/useEngine.mjs).
7
+ * (see hooks/useSession.mjs).
8
8
  *
9
- * Pure/stateless helpers live in ./engine/* (boot-profile, session-stats,
9
+ * Pure/stateless helpers live in ./session/* (boot-profile, session-stats,
10
10
  * labels, notice-text, tool-result-text, tool-call-fields, agent-envelope,
11
11
  * queue-helpers) and are re-exported here so the public surface is unchanged.
12
- * This file keeps the stateful createEngineSession store + notification plan.
12
+ * This file keeps the stateful session store + notification plan.
13
13
  */
14
14
  import { performance } from 'node:perf_hooks';
15
15
  import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync, watch, writeFileSync } from 'node:fs';
@@ -32,8 +32,8 @@ import { presentErrorText } from '../../runtime/shared/err-text.mjs';
32
32
  import { sessionPath } from '../../runtime/agent/orchestrator/session/store/paths-heartbeat.mjs';
33
33
  import { listThemes, getThemeSetting, setThemeSetting } from '../theme.mjs';
34
34
  import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-markdown.mjs';
35
- import { bootProfile } from '../engine/boot-profile.mjs';
36
- import { createSessionStats, applyUsageDelta } from '../engine/session-stats.mjs';
35
+ import { bootProfile } from '../session/boot-profile.mjs';
36
+ import { createSessionStats, applyUsageDelta } from '../session/session-stats.mjs';
37
37
  import {
38
38
  pickVerb,
39
39
  pickDoneVerb,
@@ -41,26 +41,26 @@ import {
41
41
  compactEventLabel,
42
42
  compactEventDetail,
43
43
  projectNameFromPath,
44
- } from '../engine/labels.mjs';
45
- import { polishNoticeText } from '../engine/notice-text.mjs';
44
+ } from '../session/labels.mjs';
45
+ import { polishNoticeText } from '../session/notice-text.mjs';
46
46
  import {
47
47
  toolResultText,
48
48
  toolAggregateDetailFallback,
49
49
  toolGroupedDisplayFallback,
50
50
  toolErrorDisplay,
51
- } from '../engine/tool-result-text.mjs';
51
+ } from '../session/tool-result-text.mjs';
52
52
  import {
53
53
  toolCallId,
54
54
  toolResultCallId,
55
55
  toolCallName,
56
56
  toolCallArgs,
57
- } from '../engine/tool-call-fields.mjs';
57
+ } from '../session/tool-call-fields.mjs';
58
58
  import {
59
59
  parseBackgroundTaskEnvelope,
60
60
  parseSyntheticAgentMessage,
61
61
  toolResultStatus,
62
62
  isErrorToolStatus,
63
- } from '../engine/agent-envelope.mjs';
63
+ } from '../session/agent-envelope.mjs';
64
64
  import {
65
65
  queuePriorityValue,
66
66
  defaultQueuePriority,
@@ -74,39 +74,39 @@ import {
74
74
  mergePastedImages,
75
75
  mergePastedTexts,
76
76
  callCommitCallbacks,
77
- } from '../engine/queue-helpers.mjs';
77
+ } from '../session/queue-helpers.mjs';
78
78
  import {
79
79
  resolveTuiRuntimeNotificationDelivery,
80
- } from '../engine/notification-plan.mjs';
81
- import { yieldToRenderer } from '../engine/render-timing.mjs';
80
+ } from '../session/notification-plan.mjs';
81
+ import { yieldToRenderer } from '../session/render-timing.mjs';
82
82
  import {
83
83
  aggregateRawResult,
84
84
  aggregateBucketForCategory,
85
85
  aggregateSummaries,
86
86
  assignAggregateSummaryOrder,
87
- } from '../engine/tool-result-status.mjs';
88
- import { createToolApproval } from '../engine/tool-approval.mjs';
89
- import { createToolCardResults } from '../engine/tool-card-results.mjs';
90
- import { createAgentJobFeed } from '../engine/agent-job-feed.mjs';
91
- import { appendAgentResponseTail } from '../engine/agent-response-tail.mjs';
87
+ } from '../session/tool-result-status.mjs';
88
+ import { createToolApproval } from '../session/tool-approval.mjs';
89
+ import { createToolCardResults } from '../session/tool-card-results.mjs';
90
+ import { createAgentJobFeed } from '../session/agent-job-feed.mjs';
91
+ import { appendAgentResponseTail } from '../session/agent-response-tail.mjs';
92
92
  import {
93
93
  appendTuiSteeringPersist,
94
94
  dropTuiSteeringPersist,
95
95
  drainTuiSteeringPersist,
96
96
  flushTuiSteeringPersist,
97
- } from '../engine/tui-steering-persist.mjs';
98
- import { createContextState } from '../engine/context-state.mjs';
99
- import { recomputePromptHistory } from '../engine/prompt-history.mjs';
97
+ } from '../session/tui-steering-persist.mjs';
98
+ import { createContextState } from '../session/context-state.mjs';
99
+ import { recomputePromptHistory } from '../session/prompt-history.mjs';
100
100
  import {
101
101
  appendPromptHistory,
102
102
  buildMergedPromptHistory,
103
103
  loadPromptHistory,
104
104
  } from '../prompt-history-store.mjs';
105
- import { createSessionFlow } from '../engine/session-flow.mjs';
106
- import { createRunTurn } from '../engine/turn.mjs';
107
- import { createEngineApi } from '../engine/session-api.mjs';
108
- import { createFrameBatchedStorePublisher } from '../engine/frame-batched-store.mjs';
109
- import { createLiveShare, liveSharePipePath } from '../engine/live-share.mjs';
105
+ import { createSessionFlow } from '../session/session-flow.mjs';
106
+ import { createRunTurn } from '../session/turn.mjs';
107
+ import { createSessionApi } from '../session/session-api.mjs';
108
+ import { createFrameBatchedStorePublisher } from '../session/frame-batched-store.mjs';
109
+ import { createLiveShare, liveSharePipePath } from '../session/live-share.mjs';
110
110
  import { displayModelName } from '../../ui/model-display.mjs';
111
111
 
112
112
  export const TUI_DEBUG = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ''));
@@ -484,14 +484,14 @@ export function refillTranscriptViewOverlap(viewItems, previousLiveItems, nextLi
484
484
  }
485
485
 
486
486
  // Re-export the shared tool-result/notification helpers so importers (and tests)
487
- // keep resolving them from engine.mjs unchanged.
487
+ // keep resolving them from session-local.mjs unchanged.
488
488
  export { toolResultText, toolAggregateDetailFallback, toolGroupedDisplayFallback };
489
489
  export { parseBackgroundTaskEnvelope };
490
- // Re-export the pure notification delivery plan (moved to ./engine/notification-plan.mjs)
491
- // so importers/tests keep resolving resolveTuiRuntimeNotificationDelivery from engine.mjs.
490
+ // Re-export the pure notification delivery plan (moved to ./session/notification-plan.mjs)
491
+ // so importers/tests keep resolving resolveTuiRuntimeNotificationDelivery from session-local.mjs.
492
492
  export { resolveTuiRuntimeNotificationDelivery };
493
493
 
494
- export function replaceEngineItemsState({
494
+ export function replaceSessionItemsState({
495
495
  state,
496
496
  items,
497
497
  itemIndexById,
@@ -513,9 +513,9 @@ export function replaceEngineItemsState({
513
513
  };
514
514
  }
515
515
 
516
- // Shared by the live engine and focused transcript tests so revision/tail
516
+ // Shared by the live session runtime and focused transcript tests so revision/tail
517
517
  // regressions exercise the exact mutation implementation used in production.
518
- export function createEngineItemMutators({
518
+ export function createSessionItemMutators({
519
519
  getState,
520
520
  set,
521
521
  itemIndexById,