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
@@ -0,0 +1,16 @@
1
+ /**
2
+ * src/tui/hooks/useSession.mjs — subscribe React to the session store.
3
+ *
4
+ * The store (session-local.mjs) lives outside React and emits immutable snapshots;
5
+ * useSyncExternalStore re-renders the tree whenever a snapshot changes. This
6
+ * keeps the agentLoop fully decoupled from React's lifecycle.
7
+ */
8
+ import { useSyncExternalStore } from 'react';
9
+
10
+ /**
11
+ * @param {object} store returned by createSessionRuntime()
12
+ * @returns the current session runtime state snapshot
13
+ */
14
+ export function useSession(store) {
15
+ return useSyncExternalStore(store.subscribe, store.getState, store.getState);
16
+ }
package/src/tui/index.jsx CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * src/tui/index.jsx — entry that mounts the React/ink TUI.
3
3
  *
4
- * Creates the engine session (runs OUR agentLoop outside React) and ink-renders
4
+ * Creates the session runtime session (runs OUR agentLoop outside React) and ink-renders
5
5
  * <App store={...}/>. Resolves when the app exits (/exit or /quit).
6
6
  */
7
7
  import React from 'react';
@@ -13,8 +13,8 @@ import { performance } from 'node:perf_hooks';
13
13
  import { format } from 'node:util';
14
14
  import { App } from './App.jsx';
15
15
  import { cancelPendingMouseTrackingRestores } from './app/use-mouse-input.mjs';
16
- import { createEngineSession } from './engine.mjs';
17
- import { scheduleRenderFrameAck, TUI_RENDER_FPS } from './engine/render-timing.mjs';
16
+ import { createSessionRuntime } from './session.mjs';
17
+ import { scheduleRenderFrameAck, TUI_RENDER_FPS } from './session/render-timing.mjs';
18
18
  import { installProcessSignalCleanup } from '../runtime/shared/process-shutdown.mjs';
19
19
  import { finishProcessLifecycle } from '../runtime/shared/process-lifecycle.mjs';
20
20
  import { rgbSgr } from '../ui/ansi.mjs';
@@ -30,7 +30,6 @@ import { POP_KITTY, DISABLE_MODIFY_OTHER_KEYS } from './keyboard-protocol.mjs';
30
30
  import { displayWidth } from './display-width.mjs';
31
31
  import { rotateBoundedLog, PLUGIN_LOG_MAX_BYTES, PLUGIN_LOG_KEEP_BYTES } from '../lib/mixdog-debug.cjs';
32
32
  import { localPackageVersion } from '../runtime/shared/update-checker.mjs';
33
- import { touchProjectSelected } from '../standalone/projects.mjs';
34
33
 
35
34
  // Trailing `\x1b[>0s` restores XTSHIFTESCAPE (shift-to-select-extend) to its
36
35
  // terminal default; MOUSE_TRACKING_ON opts into `\x1b[>1s`, so every mouse/alt
@@ -473,7 +472,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
473
472
 
474
473
  // Enter the alternate screen buffer before session/runtime boot so no stale
475
474
  // shell rows are visible while the statusline/TUI data warms up. The first
476
- // real Ink frame may still arrive after createEngineSession(), but the user
475
+ // real Ink frame may still arrive after createSessionRuntime(), but the user
477
476
  // sees a clean fullscreen surface immediately instead of the previous terminal
478
477
  // contents bleeding through the bottom status area.
479
478
  process.stdout.write(`${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1b[?1049h${TERMINAL_MODE_RESET_HIDDEN_CURSOR}\x1b[2J\x1b[H`);
@@ -489,10 +488,10 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
489
488
 
490
489
  process.on('exit', restoreTerminal);
491
490
 
492
- // Engine startup is independent from palette selection. Start it now and
491
+ // Session runtime startup is independent from palette selection. Start it now and
493
492
  // overlap its runtime import/config work with the theme read, while still
494
493
  // awaiting the theme before the first React frame.
495
- const storeOutcomePromise = createEngineSession({ provider, model, toolMode, remote })
494
+ const storeOutcomePromise = createSessionRuntime({ provider, model, toolMode, remote })
496
495
  .then((store) => ({ store, error: null }), (error) => ({ store: null, error }));
497
496
 
498
497
  // Apply the persisted UI theme (ui.theme in mixdog-config.json) before the
@@ -502,7 +501,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
502
501
  try { await loadThemeSettingFromConfig(); } catch { /* default theme stays */ }
503
502
  emitTerminalBackground(theme.background);
504
503
 
505
- // Static splash + loading spinner while createEngineSession() warms up
504
+ // Static splash + loading spinner while createSessionRuntime() warms up
506
505
  // (config, providers, sessions). Without this the user stares at an empty
507
506
  // alt screen for the whole boot, then the UI "pops in" — the splash makes
508
507
  // the first ink frame an in-place repaint instead.
@@ -517,15 +516,15 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
517
516
  // this, a project entered straight from the shell never moved in the
518
517
  // picker's most-recent-first order. Unregistered folders are ignored.
519
518
  try {
520
- touchProjectSelected(store.getState?.()?.cwd || process.cwd());
519
+ await store.touchProjectSelected(store.getState?.()?.cwd || process.cwd());
521
520
  } catch { /* best-effort recency */ }
522
- // The palette lives in THIS process: on a daemon-backed store the engine's
521
+ // The palette lives in THIS process: on a daemon-backed store the session runtime's
523
522
  // theme methods would run inside the daemon, so the picker saw a promise
524
523
  // instead of the list and a switch repainted nothing here. Bind the three
525
524
  // theme methods locally (same pattern as the ink selection helpers below)
526
- // and mirror the switch to the engine with persist:false, purely so its
525
+ // and mirror the switch to the session runtime with persist:false, purely so its
527
526
  // themeEpoch bump re-renders this tree.
528
- if (store.isRemoteEngine === true) {
527
+ if (store.isRemoteSession === true) {
529
528
  const remoteSetTheme = store.setTheme;
530
529
  store.listThemes = () => listThemes();
531
530
  store.getTheme = () => getThemeSetting();
@@ -548,7 +547,7 @@ export async function runTui({ provider, model, toolMode, remote, forceOnboardin
548
547
  process.stderr.write(`mixdog: ${error?.message || error}\n`);
549
548
  return 1;
550
549
  }
551
- // The engine store is a daemon-backed Proxy: every method it does not
550
+ // The session runtime store is a daemon-backed Proxy: every method it does not
552
551
  // implement locally comes back as an ASYNC remote call. A synchronous
553
552
  // getOnboardingStatus() probe inside App therefore sees a Promise, never
554
553
  // `.completed`, and re-opens the finished wizard on every launch. Resolve
@@ -1,8 +1,8 @@
1
1
  /**
2
- * src/tui/engine/agent-envelope.mjs - parse agent/background-task/shell
2
+ * src/tui/session/agent-envelope.mjs - parse agent/background-task/shell
3
3
  * notification envelopes into synthetic tool-item shapes, and derive
4
- * status/result text. Extracted from engine.mjs; parseBackgroundTaskEnvelope
5
- * remains part of engine.mjs's public surface.
4
+ * status/result text. Extracted from session-local.mjs; parseBackgroundTaskEnvelope
5
+ * remains part of session-local.mjs's public surface.
6
6
  */
7
7
  import { isBackgroundErrorOnlyBody } from '../../runtime/shared/err-text.mjs';
8
8
 
@@ -1,6 +1,6 @@
1
1
  /**
2
- * src/tui/engine/agent-job-feed.mjs — agent-job / runtime-notification plumbing
3
- * extracted from createEngineSession (engine.mjs) as a dependency-injection
2
+ * src/tui/session/agent-job-feed.mjs — agent-job / runtime-notification plumbing
3
+ * extracted from the session runtime as a dependency-injection
4
4
  * factory.
5
5
  *
6
6
  * Owns: the agent-job card result patch (updateAgentJobCard), the
@@ -11,7 +11,7 @@
11
11
  * These handlers mutate live session state and drive the queue, so
12
12
  * state/set/enqueue/drain/pushUserOrSyntheticItem/patchItem/etc are threaded via
13
13
  * the factory argument (getters/callbacks) — never stale snapshots. Every body
14
- * is the original engine.mjs logic verbatim.
14
+ * is the original session-local.mjs logic verbatim.
15
15
  */
16
16
  import {
17
17
  parseAgentJob,
@@ -314,7 +314,7 @@ export function createAgentJobFeed({
314
314
  if (firstDelivery && !successfulPreview && !bodyAlreadyDisplayed) {
315
315
  if (cardKey) rememberDisplayedExecutionNotificationKey(cardKey, terminal, executionId);
316
316
  if (executionId) rememberDisplayedExecutionResponseState(executionId, hasBody ? 'body' : 'preview', terminal);
317
- // Execution completions are inbound agent responses. The engine keeps
317
+ // Execution completions are inbound agent responses. The session runtime keeps
318
318
  // their aggregation tail-safe (only an immediately-adjacent inbound
319
319
  // response of the same preview/body phase can merge); the fallback
320
320
  // preserves the standalone path for minimal/test harnesses.
@@ -1,7 +1,7 @@
1
1
  /**
2
- * src/tui/engine/boot-profile.mjs - opt-in boot timing trace.
2
+ * src/tui/session/boot-profile.mjs - opt-in boot timing trace.
3
3
  *
4
- * Extracted from engine.mjs. Emits `[mixdog-boot]` stderr lines when
4
+ * Extracted from session-local.mjs. Emits `[mixdog-boot]` stderr lines when
5
5
  * MIXDOG_BOOT_PROFILE is truthy; a no-op otherwise.
6
6
  */
7
7
  import { performance } from 'node:perf_hooks';
@@ -1,7 +1,7 @@
1
1
  /**
2
- * src/tui/engine/context-state.mjs - route/context/agent-status derivations.
2
+ * src/tui/session/context-state.mjs - route/context/agent-status derivations.
3
3
  *
4
- * Extracted from engine.mjs unchanged. These read the live runtime + store
4
+ * Extracted from session-local.mjs unchanged. These read the live runtime + store
5
5
  * snapshot. The two sync helpers stage immutable draft patches through
6
6
  * updateState; callers still follow with set(...) to schedule publication.
7
7
  */
@@ -24,7 +24,7 @@ export function createContextState({ runtime, getState, updateState, getPendingS
24
24
  };
25
25
  const baseRouteState = () => ({
26
26
  sessionId: runtime.id,
27
- // Fork-on-resume marker: when the engine opened a live session as a fork
27
+ // Fork-on-resume marker: when the session runtime opened a live session as a fork
28
28
  // (fresh id, copied transcript), hosts validating "resume returned the
29
29
  // requested session" must accept the fork by its origin id.
30
30
  sessionForkedFrom: runtime.session?.forkedFrom || null,
@@ -1,6 +1,6 @@
1
1
  /**
2
- * src/tui/engine/labels.mjs - spinner verbs, elapsed formatting, and compact
3
- * event label/detail helpers. Extracted from engine.mjs.
2
+ * src/tui/session/labels.mjs - spinner verbs, elapsed formatting, and compact
3
+ * event label/detail helpers. Extracted from session-local.mjs.
4
4
  */
5
5
  import { SPINNER_VERBS } from '../spinner-verbs.mjs';
6
6
 
@@ -1,12 +1,12 @@
1
1
  /**
2
- * src/tui/engine/live-share.mjs — real-time cross-surface session mirroring.
2
+ * src/tui/session/live-share.mjs — real-time cross-surface session mirroring.
3
3
  *
4
4
  * OWNER leg: hosts a local named-pipe (win32) / unix-socket server for the
5
5
  * session it owns and pushes frame-batched transcript deltas (items /
6
6
  * streaming tail / spinner) to attached viewers. Push-only: zero polling,
7
7
  * idle cost is one open listener.
8
8
  * VIEWER leg: connects to the owner's pipe and mirrors those deltas into the
9
- * local engine store, so streaming output appears live on every co-open
9
+ * local session runtime store, so streaming output appears live on every co-open
10
10
  * surface (desktop <-> terminal). Viewer submits travel over the same pipe
11
11
  * into the owner's normal submit queue (concurrent input from both sides is
12
12
  * serialized there); the durable pending spool remains the fallback when the
@@ -25,7 +25,7 @@ const MAX_BUFFER_BYTES = 8 * 1024 * 1024;
25
25
  const SYNC_REQUEST_MIN_INTERVAL_MS = 500;
26
26
  // Session entry is latency-sensitive: the owner pipe can be a few event-loop
27
27
  // turns behind the viewer resume. Retry locally instead of waiting for the
28
- // coarse 3s engine safety tick.
28
+ // coarse 3s session runtime safety tick.
29
29
  const LIVE_CONNECT_RETRY_MIN_MS = 10;
30
30
  const LIVE_CONNECT_RETRY_MAX_MS = 160;
31
31
 
@@ -640,7 +640,7 @@ export function createLiveShare({
640
640
  };
641
641
 
642
642
  // Reconciles both legs against the current session role. Failed pipe opens
643
- // continue through the local retry timers; the engine's 3s call is only a
643
+ // continue through the local retry timers; the session runtime's 3s call is only a
644
644
  // safety net, never the normal connection cadence.
645
645
  const ensureShare = () => {
646
646
  if (disposed) return;
@@ -709,3 +709,37 @@ export function createLiveShare({
709
709
  },
710
710
  };
711
711
  }
712
+
713
+ /**
714
+ * ONE viewer-submit intake for EVERY session runtime entry point. The TUI store calls
715
+ * submit(); the daemon awaits submitAsync() (session-service prefers it).
716
+ * Wrapping only one of them let the other path enqueue the prompt on the VIEWER
717
+ * session runtime, so that surface rendered its own local user row beside the owner's
718
+ * mirrored one — the same message twice on screen
719
+ * (user: 데스크탑에서만 입력이 두 개로 들어간다).
720
+ *
721
+ * `spool(submissionId)` is the durable fallback. The submission id is minted
722
+ * here when the caller has none, so the pipe frame and the spool entry carry
723
+ * the SAME identity and the owner reuses it for the settled user row — which is
724
+ * what releases the submitting surface's optimistic bubble.
725
+ */
726
+ export function forwardViewerSubmit({ text, options = {}, share, spool, pid = process.pid }) {
727
+ const value = String(text || '').trim();
728
+ if (!value) return false;
729
+ // Reconcile first so a session that became attachable this event-loop turn
730
+ // takes the instant pipe path instead of the durable detour.
731
+ try { share.ensure?.(); } catch { /* durable fallback below */ }
732
+ const submissionId = options.id != null && String(options.id).trim()
733
+ ? String(options.id).trim()
734
+ : `view-submit-${pid}-${Date.now()}`;
735
+ const deliverDurably = () => spool?.(submissionId) === true;
736
+ // A pipe WRITE is not a delivery: the owner may refuse the prompt and the
737
+ // socket may die between write and read, so an unacknowledged submit is
738
+ // re-delivered through the spool — late, never lost.
739
+ if (share.sendSubmit(value, {
740
+ id: submissionId,
741
+ submittedAt: options.submittedAt,
742
+ onUndelivered: deliverDurably,
743
+ })) return true;
744
+ return deliverDurably();
745
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * src/tui/engine/notice-text.mjs - polish user-facing failure/notice text.
3
- * Extracted from engine.mjs.
2
+ * src/tui/session/notice-text.mjs - polish user-facing failure/notice text.
3
+ * Extracted from session-local.mjs.
4
4
  */
5
5
  const FAILED_NOTICE_ACTIONS = new Map([
6
6
  ['api key save', 'save API key'],
@@ -1,9 +1,9 @@
1
1
  /**
2
- * src/tui/engine/notification-plan.mjs - pure runtime.onNotification delivery plan.
2
+ * src/tui/session/notification-plan.mjs - pure runtime.onNotification delivery plan.
3
3
  *
4
- * Extracted from engine.mjs (no behavior change). These are stateless helpers
4
+ * Extracted from session-local.mjs (no behavior change). These are stateless helpers
5
5
  * that decide how a runtime notification envelope should be delivered to the
6
- * TUI store (dedupe key + display/model routing). No closures over engine state.
6
+ * TUI store (dedupe key + display/model routing). No closures over session runtime state.
7
7
  */
8
8
  import {
9
9
  isStatusOnlyAgentCompletionNotification,
@@ -0,0 +1,140 @@
1
+ // Daemon-owned provider OAuth flows. The provider runtime returns live
2
+ // promises/functions, so the session surface must retain them locally and
3
+ // expose only serializable status records to desktop clients.
4
+ const DEFAULT_FLOW_TTL_MS = 10 * 60 * 1_000;
5
+
6
+ export function oauthFlowStatus(flow) {
7
+ return {
8
+ flowId: flow.id,
9
+ provider: flow.provider,
10
+ url: flow.url,
11
+ manualUrl: flow.manualUrl,
12
+ state: flow.state,
13
+ completed: flow.state === 'complete',
14
+ error: flow.error,
15
+ manualCodeSupported: typeof flow.completeCode === 'function',
16
+ };
17
+ }
18
+
19
+ function requiredFlowId(value) {
20
+ const id = String(value || '').trim();
21
+ if (!/^oauth_[a-z0-9_]+$/i.test(id)) throw new TypeError('OAuth flow id is invalid.');
22
+ return id;
23
+ }
24
+
25
+ function cancelFlow(flow, reason) {
26
+ try {
27
+ const result = flow.cancel?.();
28
+ if (result && typeof result.then === 'function') {
29
+ void Promise.resolve(result).catch((error) => {
30
+ console.warn(`OAuth flow could not be ${reason}:`, error);
31
+ });
32
+ }
33
+ } catch (error) {
34
+ console.warn(`OAuth flow could not be ${reason}:`, error);
35
+ }
36
+ }
37
+
38
+ export function createSessionOAuthFlowRegistry({ ttlMs = DEFAULT_FLOW_TTL_MS } = {}) {
39
+ const flows = new Map();
40
+ let sequence = 0;
41
+
42
+ function requireFlow(id) {
43
+ const key = requiredFlowId(id);
44
+ const flow = flows.get(key);
45
+ if (!flow) throw new Error('OAuth login flow is no longer available.');
46
+ return flow;
47
+ }
48
+
49
+ function register(value) {
50
+ const started = value && typeof value === 'object' ? value : null;
51
+ if (!started) throw new Error('OAuth provider did not return a login flow.');
52
+ const provider = String(started.provider || '').trim();
53
+ if (!provider) throw new Error('OAuth provider login is missing its provider id.');
54
+ const id = `oauth_${Date.now().toString(36)}_${(++sequence).toString(36)}`;
55
+ const flow = {
56
+ id,
57
+ provider,
58
+ url: typeof started.url === 'string' ? started.url : null,
59
+ manualUrl: typeof started.manualUrl === 'string' ? started.manualUrl : null,
60
+ state: 'pending',
61
+ result: null,
62
+ error: null,
63
+ completeCode: typeof started.completeCode === 'function' ? started.completeCode : undefined,
64
+ cancel: typeof started.cancel === 'function' ? started.cancel : undefined,
65
+ timeout: null,
66
+ };
67
+ flow.timeout = setTimeout(() => {
68
+ const current = flows.get(id);
69
+ if (!current) return;
70
+ cancelFlow(current, 'expired');
71
+ flows.delete(id);
72
+ }, Math.max(1, Number(ttlMs) || DEFAULT_FLOW_TTL_MS));
73
+ flow.timeout.unref?.();
74
+ flows.set(id, flow);
75
+ if (started.waitForCallback && typeof started.waitForCallback.then === 'function') {
76
+ void Promise.resolve(started.waitForCallback).then((result) => {
77
+ const current = flows.get(id);
78
+ if (!current || current.state !== 'pending' || !result) return;
79
+ current.state = 'complete';
80
+ current.result = true;
81
+ }).catch((error) => {
82
+ const current = flows.get(id);
83
+ if (!current || current.state !== 'pending') return;
84
+ current.state = 'failed';
85
+ current.error = error instanceof Error ? error.message : String(error);
86
+ });
87
+ }
88
+ return oauthFlowStatus(flow);
89
+ }
90
+
91
+ function status(id) {
92
+ return oauthFlowStatus(requireFlow(id));
93
+ }
94
+
95
+ async function complete(id, rawCode) {
96
+ const flow = requireFlow(id);
97
+ if (typeof flow.completeCode !== 'function') {
98
+ throw new Error('This OAuth provider does not accept a manual code.');
99
+ }
100
+ const code = String(rawCode || '').trim();
101
+ if (!code || code.length > 16_384) throw new TypeError('OAuth code is invalid.');
102
+ try {
103
+ const completed = await flow.completeCode(code);
104
+ flow.result = Boolean(completed);
105
+ flow.state = completed ? 'complete' : 'failed';
106
+ flow.error = completed ? null : 'OAuth code did not complete the login.';
107
+ } catch (error) {
108
+ flow.state = 'failed';
109
+ flow.error = error instanceof Error ? error.message : String(error);
110
+ throw error;
111
+ }
112
+ return oauthFlowStatus(flow);
113
+ }
114
+
115
+ async function cancel(id) {
116
+ const flow = requireFlow(id);
117
+ let failure = null;
118
+ try {
119
+ await flow.cancel?.();
120
+ } catch (error) {
121
+ failure = error;
122
+ } finally {
123
+ flow.state = 'cancelled';
124
+ clearTimeout(flow.timeout);
125
+ flows.delete(flow.id);
126
+ }
127
+ if (failure) throw failure;
128
+ return oauthFlowStatus(flow);
129
+ }
130
+
131
+ function cancelAll() {
132
+ for (const flow of flows.values()) {
133
+ clearTimeout(flow.timeout);
134
+ cancelFlow(flow, 'cancelled');
135
+ }
136
+ flows.clear();
137
+ }
138
+
139
+ return Object.freeze({ register, status, complete, cancel, cancelAll });
140
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * src/tui/engine/prompt-history.mjs - pure prompt-history derivation.
2
+ * src/tui/session/prompt-history.mjs - pure prompt-history derivation.
3
3
  *
4
- * Extracted from engine.mjs unchanged: the newest-first, deduped user-prompt
5
- * history the engine publishes on state.promptHistoryList. Pure (input items
4
+ * Extracted from session-local.mjs unchanged: the newest-first, deduped user-prompt
5
+ * history the session runtime publishes on state.promptHistoryList. Pure (input items
6
6
  * → array); callers decide when/whether to publish so the store's
7
7
  * immutable-emit contract is preserved.
8
8
  */
@@ -1,8 +1,8 @@
1
1
  /**
2
- * src/tui/engine/queue-helpers.mjs - pure helpers for the prompt/notification
2
+ * src/tui/session/queue-helpers.mjs - pure helpers for the prompt/notification
3
3
  * queue: priority ordering, entry visibility/editability, prompt content
4
4
  * flattening, session activity timestamps, and batch merging. Extracted from
5
- * engine.mjs.
5
+ * session-local.mjs.
6
6
  */
7
7
  import {
8
8
  agentJobResultText,
@@ -1,7 +1,7 @@
1
1
  /**
2
- * src/tui/engine/render-timing.mjs - render-throttle timing helper.
2
+ * src/tui/session/render-timing.mjs - render-throttle timing helper.
3
3
  *
4
- * Extracted from engine.mjs (no behavior change originally).
4
+ * Extracted from session-local.mjs (no behavior change originally).
5
5
  *
6
6
  * Ink renders through a maxFps throttle (120fps in index.jsx, ≈8.3ms). A plain
7
7
  * setImmediate only yields to the event loop; if Ink already painted within the
@@ -1,5 +1,5 @@
1
1
  /**
2
- * src/tui/engine/session-api-ext.mjs - part of the public engine session object.
2
+ * src/tui/session/session-api-ext.mjs - part of the public session runtime session object.
3
3
  */
4
4
  import { listThemes, getThemeSetting, setThemeSetting } from '../theme.mjs';
5
5
  import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-markdown.mjs';
@@ -7,7 +7,8 @@ import { toolResultText } from './tool-result-text.mjs';
7
7
  import { parseSyntheticAgentMessage } from './agent-envelope.mjs';
8
8
  import { flushTuiSteeringPersist } from './tui-steering-persist.mjs';
9
9
  import { getVoiceStatus, toggleVoice } from '../lib/voice-setup.mjs';
10
- import { aggregateToolCategoryEntry, aggregateDoneCategories, classifyToolCategory, formatAggregateDetail, summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
10
+ import { createSessionOAuthFlowRegistry } from './oauth-flows.mjs';
11
+ import { aggregateToolCategoryEntries, aggregateDoneCategories, classifyToolCategory, formatAggregateDetail, summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
11
12
  import { aggregateBucketForCategory, aggregateRawResult, failureDetailText, shellCommandExitCode } from './tool-result-status.mjs';
12
13
  import { isInternalTranscriptDisplayText } from '../../runtime/shared/tool-execution-contract.mjs';
13
14
  import { toolResultTerminalStatus } from '../../runtime/shared/tool-status.mjs';
@@ -121,10 +122,11 @@ function buildRestoredAggregateItem(members) {
121
122
  const categoryOrder = [];
122
123
  const calls = [];
123
124
  for (const { item, category } of members) {
124
- const entry = aggregateToolCategoryEntry(item.name, item.args, category);
125
- if (!categories.has(entry.key)) categoryOrder.push(entry.key);
126
- const prev = categories.get(entry.key);
127
- categories.set(entry.key, { ...entry, count: Number(prev?.count || 0) + Number(entry.count || 1) });
125
+ for (const entry of aggregateToolCategoryEntries(item.name, item.args, category)) {
126
+ if (!categories.has(entry.key)) categoryOrder.push(entry.key);
127
+ const prev = categories.get(entry.key);
128
+ categories.set(entry.key, { ...entry, count: Number(prev?.count || 0) + Number(entry.count || 1) });
129
+ }
128
130
  const resultText = String(item.result ?? '');
129
131
  // Mirror live outcome semantics: a plain non-zero shell exit is the
130
132
  // neutral 'Exit N' state, not a red failure; only real call errors count.
@@ -186,7 +188,7 @@ function buildRestoredAggregateItem(members) {
186
188
  }
187
189
 
188
190
  // Restored transcripts must mirror the live turn's category merging: the live
189
- // engine (turn.mjs) collapses consecutive same-bucket tool calls into one
191
+ // session runtime (turn.mjs) collapses consecutive same-bucket tool calls into one
190
192
  // aggregate card, but resume rebuilt one card per call, so a reopened session
191
193
  // un-merged every run (user bug). Walk the restored items and merge adjacent
192
194
  // tool cards whose aggregateBucketForCategory matches; any non-tool item
@@ -381,10 +383,11 @@ export function sessionContextSnapshotProjection(session, contextStatus) {
381
383
  };
382
384
  }
383
385
 
384
- export function createEngineApiB(bag) {
386
+ export function createSessionApiB(bag) {
385
387
  const {
386
388
  runtime, nextId, flags, lifecycle, listeners, getState, set, flushEmitImmediate, disposeEmit, replaceItems, pushNotice, removeNotice, setProgressHint, clearToastTimers, disposeTranscriptSpill, routeState, syncContextStats, finishToolApproval, denyAllToolApprovals, restoreLeadSteeringFromDisk, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, commitTuiSessionReset, resetStatsAndSyncContext,
387
389
  } = bag;
390
+ const oauthFlows = createSessionOAuthFlowRegistry();
388
391
  return {
389
392
  resolveToolApproval: (id, decision = {}) => {
390
393
  const approved = decision === true || decision?.approved === true;
@@ -519,7 +522,7 @@ export function createEngineApiB(bag) {
519
522
  // `/remote` slash command — repeated /remote just re-claims (idempotent).
520
523
  claimRemote: async () => {
521
524
  // Desired state paints before daemon cold boot. The final read in
522
- // `finally` reconciles a skipped/failed claim to the backend truth.
525
+ // `finally` reconciles a skipped/failed claim to the session truth.
523
526
  set({
524
527
  remoteEnabled: true,
525
528
  remoteSessionId: runtime.getRemoteSessionId?.() || getState().sessionId || null,
@@ -623,9 +626,6 @@ export function createEngineApiB(bag) {
623
626
  setAgentRoute: async (agentId, opts) => {
624
627
  return await runtime.setAgentRoute?.(agentId, opts);
625
628
  },
626
- setDefaultProvider: async (provider) => {
627
- return await runtime.setDefaultProvider?.(provider);
628
- },
629
629
  listProviders: () => {
630
630
  return runtime.listProviders();
631
631
  },
@@ -636,7 +636,7 @@ export function createEngineApiB(bag) {
636
636
  return await runtime.getUsageDashboard?.(options);
637
637
  },
638
638
  consumeCodexRateLimitResetCredit: async (options = {}) => {
639
- // Desktop capability parity: without this delegation the engine surface
639
+ // Desktop capability parity: without this delegation the session runtime surface
640
640
  // rejects the sidebar's reset-credit invoke as unsupported even though
641
641
  // the session runtime implements it.
642
642
  if (typeof runtime.consumeCodexRateLimitResetCredit !== 'function') {
@@ -644,8 +644,14 @@ export function createEngineApiB(bag) {
644
644
  }
645
645
  return await runtime.consumeCodexRateLimitResetCredit(options);
646
646
  },
647
- getTurnReviewDiff: async () => {
648
- return (await runtime.getTurnReviewDiff?.()) ?? { supported: false, files: [], patch: '' };
647
+ getTurnReviewDiff: async (options = {}) => {
648
+ return (await runtime.getTurnReviewDiff?.(options)) ?? { supported: false, files: [], patch: '' };
649
+ },
650
+ revertTurnReviewFile: async (file) => {
651
+ if (typeof runtime.revertTurnReviewFile !== 'function') {
652
+ throw new Error('Turn review revert is unavailable');
653
+ }
654
+ return await runtime.revertTurnReviewFile(file);
649
655
  },
650
656
  getOnboardingStatus: () => {
651
657
  return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
@@ -682,13 +688,16 @@ export function createEngineApiB(bag) {
682
688
  if (getState().commandBusy) throw new Error('command busy');
683
689
  set({ commandBusy: true });
684
690
  try {
685
- const result = await runtime.beginOAuthProviderLogin(provider);
691
+ const result = oauthFlows.register(await runtime.beginOAuthProviderLogin(provider));
686
692
  pushNotice(`provider oauth started: ${result.provider}`, 'info');
687
693
  return result;
688
694
  } finally {
689
695
  set({ commandBusy: false });
690
696
  }
691
697
  },
698
+ getOAuthProviderLoginStatus: (flowId) => oauthFlows.status(flowId),
699
+ completeOAuthProviderLogin: async (flowId, code) => oauthFlows.complete(flowId, code),
700
+ cancelOAuthProviderLogin: async (flowId) => oauthFlows.cancel(flowId),
692
701
  saveProviderApiKey: (provider, secret) => {
693
702
  const result = runtime.saveProviderApiKey(provider, secret);
694
703
  pushNotice(`provider api key saved: ${result.provider}`, 'info');
@@ -741,7 +750,7 @@ export function createEngineApiB(bag) {
741
750
  return runtime.getChannelSetup();
742
751
  },
743
752
  getChannelWorkerStatus: () => runtime.getChannelWorkerStatus?.(),
744
- setBackend: (name) => runtime.setBackend?.(name),
753
+ setChannelProvider: (name) => runtime.setChannelProvider?.(name),
745
754
  saveDiscordToken: (token) => {
746
755
  const result = runtime.saveDiscordToken(token);
747
756
  pushNotice('discord token saved', 'info');
@@ -883,7 +892,7 @@ export function createEngineApiB(bag) {
883
892
  renameSessionTitle: (id, title) => {
884
893
  return runtime.renameSessionTitle?.(id, title) ?? false;
885
894
  },
886
- // Desktop sidebar watcher hook: EngineHost fs.watches this directory so
895
+ // Desktop sidebar watcher hook: the daemon watches this directory so
887
896
  // heartbeat sidecar create/delete pushes instant working/dot updates.
888
897
  // Without it the watcher silently no-ops and the sidebar falls back to
889
898
  // the 60s safety poll (user: spinner kept spinning after the turn ended).
@@ -1005,7 +1014,7 @@ export function createEngineApiB(bag) {
1005
1014
  }
1006
1015
  },
1007
1016
  // Desktop split panes: read-only transcript + route peek of an idle
1008
- // session — no resume, no ownership claim, no engine state mutation.
1017
+ // session — no resume, no ownership claim, no session runtime state mutation.
1009
1018
  // Panes render every visible session as foreground from this frame.
1010
1019
  peekSessionTranscript: (id, options = {}) => {
1011
1020
  const session = runtime.peekSession?.(id);
@@ -1031,7 +1040,7 @@ export function createEngineApiB(bag) {
1031
1040
  },
1032
1041
  resume: async (id, options = {}) => {
1033
1042
  if (getState().commandBusy) return false;
1034
- // quiet: viewer-follow refreshes (engine share tick) re-resume on every
1043
+ // quiet: viewer-follow refreshes (session runtime share tick) re-resume on every
1035
1044
  // owner turn — they must not flash the "Resuming conversation" status.
1036
1045
  set({
1037
1046
  commandBusy: true,
@@ -1098,6 +1107,7 @@ export function createEngineApiB(bag) {
1098
1107
  try { lifecycle.unsubscribeRemoteState?.(); } catch {}
1099
1108
  lifecycle.unsubscribeRemoteState = null;
1100
1109
  denyAllToolApprovals('runtime closing');
1110
+ oauthFlows.cancelAll();
1101
1111
  await flushTuiSteeringPersist();
1102
1112
  await runtime.close(reason, options);
1103
1113
  listeners.clear();