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,192 @@
1
+ /**
2
+ * Explicit session protocol shared by every Mixdog client.
3
+ *
4
+ * Session capabilities are finite and split into read/configure lanes; clients
5
+ * cannot discover or invoke arbitrary properties on the daemon-hosted runtime.
6
+ */
7
+
8
+ export const SESSION_READ_ACTIONS = Object.freeze([
9
+ 'contextStatus',
10
+ 'getAgentDefinition',
11
+ 'getAutoClear',
12
+ 'getChannelSettings',
13
+ 'getChannelSetup',
14
+ 'getChannelWorkerStatus',
15
+ 'getCompactionSettings',
16
+ 'getDisabledSkills',
17
+ 'getMediaJob',
18
+ 'getOnboardingStatus',
19
+ 'getOAuthProviderLoginStatus',
20
+ 'getOutputStyle',
21
+ 'getProfile',
22
+ 'getProviderSetup',
23
+ 'getRecapSettings',
24
+ 'getSearchRoute',
25
+ 'getSettingsSnapshot',
26
+ 'getSystemShell',
27
+ 'getTheme',
28
+ 'getTurnReviewDiff',
29
+ 'getUpdateSettings',
30
+ 'getUpdateStatus',
31
+ 'getUsageDashboard',
32
+ 'getVoiceStatus',
33
+ 'getWorkflowPack',
34
+ 'hooksStatus',
35
+ 'isRemoteEnabled',
36
+ 'listAgents',
37
+ 'listMediaAssets',
38
+ 'listMediaLanes',
39
+ 'listOutputStyles',
40
+ 'listPresets',
41
+ 'listProviderModels',
42
+ 'listProviders',
43
+ 'listSearchModels',
44
+ 'listThemes',
45
+ 'listWorkflows',
46
+ 'mcpStatus',
47
+ 'peekSessionTranscript',
48
+ 'pluginsStatus',
49
+ 'readMediaAsset',
50
+ 'resolveMediaFile',
51
+ 'skillContent',
52
+ 'skillsStatus',
53
+ 'toolsStatus',
54
+ ]);
55
+
56
+ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
57
+ 'addHookRule',
58
+ 'addMcpServer',
59
+ 'addPlugin',
60
+ 'addSkill',
61
+ 'agentControl',
62
+ 'authenticateProvider',
63
+ 'beginOAuthProviderLogin',
64
+ 'cacheMediaThumbnail',
65
+ 'cancelMediaJob',
66
+ 'cancelOAuthProviderLogin',
67
+ 'checkForUpdate',
68
+ 'claimRemote',
69
+ 'clear',
70
+ 'compact',
71
+ 'completeOnboarding',
72
+ 'completeOAuthProviderLogin',
73
+ 'consumeCodexRateLimitResetCredit',
74
+ 'createWorkflow',
75
+ 'deleteAgentDefinition',
76
+ 'deleteHookRule',
77
+ 'deleteMediaAsset',
78
+ 'deleteSchedule',
79
+ 'deleteSession',
80
+ 'deleteWebhook',
81
+ 'deleteWorkflow',
82
+ 'enablePluginMcp',
83
+ 'forgetDiscordToken',
84
+ 'forgetProviderAuth',
85
+ 'forgetTelegramToken',
86
+ 'loginOAuthProvider',
87
+ 'loginOpenCodeGoUsage',
88
+ 'memoryControl',
89
+ 'openMediaAsset',
90
+ 'openMediaFolder',
91
+ 'pushNotice',
92
+ 'recall',
93
+ 'reconnectMcp',
94
+ 'releaseRemote',
95
+ 'reloadPlugins',
96
+ 'reloadSkills',
97
+ 'rememberPromptHistory',
98
+ 'removeMcpServer',
99
+ 'removeNotice',
100
+ 'removePlugin',
101
+ 'renameSessionTitle',
102
+ 'resizeImage',
103
+ 'restoreNewerTranscript',
104
+ 'restoreOlderTranscript',
105
+ 'restoreQueued',
106
+ 'revertTurnReviewFile',
107
+ 'rewindToItem',
108
+ 'runDoctor',
109
+ 'runScheduleNow',
110
+ 'runUpdateNow',
111
+ 'saveAgentDefinition',
112
+ 'saveDiscordToken',
113
+ 'saveOpenAIUsageSessionKey',
114
+ 'saveOpenCodeGoUsageAuth',
115
+ 'saveProviderApiKey',
116
+ 'saveSchedule',
117
+ 'saveTelegramToken',
118
+ 'saveWebhook',
119
+ 'saveWorkflowPack',
120
+ 'selectTools',
121
+ 'setAgentRoute',
122
+ 'setAutoClear',
123
+ 'setAutoUpdate',
124
+ 'setChannelProvider',
125
+ 'setChannel',
126
+ 'setChannelsEnabled',
127
+ 'setCompactionSettings',
128
+ 'setCwd',
129
+ 'setDisabledSkills',
130
+ 'setEffort',
131
+ 'setFast',
132
+ 'setHookRuleEnabled',
133
+ 'setLocalProvider',
134
+ 'setMcpServerEnabled',
135
+ 'setModel',
136
+ 'setOutputStyle',
137
+ 'setProfile',
138
+ 'setProgressHint',
139
+ 'setRecapEnabled',
140
+ 'setRoute',
141
+ 'setScheduleEnabled',
142
+ 'setSearchRoute',
143
+ 'setSystemShell',
144
+ 'setTheme',
145
+ 'setToolMode',
146
+ 'setWebhookConfig',
147
+ 'setWebhookEnabled',
148
+ 'setWorkflow',
149
+ 'skipOnboarding',
150
+ 'startMediaJob',
151
+ 'switchContext',
152
+ 'toggleFast',
153
+ 'toggleRemote',
154
+ 'toggleVoice',
155
+ 'transcribeAudio',
156
+ 'updatePlugin',
157
+ ]);
158
+
159
+ export const SESSION_READ_ACTION_SET = new Set(SESSION_READ_ACTIONS);
160
+ export const SESSION_CONFIGURE_ACTION_SET = new Set(SESSION_CONFIGURE_ACTIONS);
161
+
162
+ function requireTransport(transport) {
163
+ if (!transport || typeof transport.call !== 'function') {
164
+ throw new TypeError('session transport is invalid');
165
+ }
166
+ return transport;
167
+ }
168
+
169
+ /** Build the one finite client used by TUI, desktop, and in-daemon adapters. */
170
+ export function createSessionProtocolClient(rawTransport) {
171
+ const transport = requireTransport(rawTransport);
172
+ const route = (name, params = {}, options = {}) => transport.call(name, params, options);
173
+ return Object.freeze({
174
+ list: (params = {}, options = {}) => route('session.list', params, options),
175
+ create: (params = {}, options = {}) => route('session.create', params, options),
176
+ read: (params = {}, options = {}) => route('session.read', params, options),
177
+ subscribe: (params = {}, options = {}) => route('session.subscribe', params, options),
178
+ unsubscribe: (params = {}, options = {}) => route('session.unsubscribe', params, options),
179
+ submit: (params = {}, options = {}) => route('session.submit', params, options),
180
+ abort: (params = {}, options = {}) => route('session.abort', params, options),
181
+ approve: (params = {}, options = {}) => route('session.approve', params, options),
182
+ configure: (params = {}, options = {}) => route('session.configure', params, options),
183
+ projectList: (params = {}, options = {}) => route('project.list', params, options),
184
+ projectInspect: (params = {}, options = {}) => route('project.inspect', params, options),
185
+ projectAdd: (params = {}, options = {}) => route('project.add', params, options),
186
+ projectTouch: (params = {}, options = {}) => route('project.touch', params, options),
187
+ projectRename: (params = {}, options = {}) => route('project.rename', params, options),
188
+ projectRemove: (params = {}, options = {}) => route('project.remove', params, options),
189
+ projectEnsureDirectory: (params = {}, options = {}) => route('project.ensureDirectory', params, options),
190
+ close: (reason) => Promise.resolve(transport.close?.(reason)),
191
+ });
192
+ }
@@ -0,0 +1,393 @@
1
+ import { fork } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import os from 'node:os';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import { safeIpcSend } from '../runtime/shared/safe-ipc-send.mjs';
7
+ import { hiddenSpawnOpts } from '../runtime/shared/spawn-flags.mjs';
8
+ import {
9
+ SESSION_CONFIGURE_ACTIONS,
10
+ SESSION_READ_ACTIONS,
11
+ } from './session-protocol.mjs';
12
+
13
+ const RUNTIME_METHODS = new Set([
14
+ ...SESSION_READ_ACTIONS,
15
+ ...SESSION_CONFIGURE_ACTIONS,
16
+ 'reserveSession',
17
+ 'resume',
18
+ 'submitAsync',
19
+ 'abort',
20
+ 'resolveToolApproval',
21
+ 'dispose',
22
+ ]);
23
+
24
+ function configuredShardCount() {
25
+ const explicit = Math.floor(Number(process.env.MIXDOG_SESSION_SHARDS));
26
+ if (Number.isFinite(explicit) && explicit >= 1) return Math.min(32, explicit);
27
+ const parallelism = typeof os.availableParallelism === 'function'
28
+ ? os.availableParallelism()
29
+ : os.cpus().length;
30
+ return Math.max(2, Math.min(8, parallelism - 1));
31
+ }
32
+
33
+ export function sessionShardIndex(sessionId, shardCount) {
34
+ const count = Math.max(1, Math.floor(Number(shardCount)) || 1);
35
+ const text = String(sessionId || '');
36
+ let hash = 2166136261;
37
+ for (let index = 0; index < text.length; index += 1) {
38
+ hash ^= text.charCodeAt(index);
39
+ hash = Math.imul(hash, 16777619);
40
+ }
41
+ return (hash >>> 0) % count;
42
+ }
43
+
44
+ export function applyShardStateFrame(previous, frame) {
45
+ if (frame?.full && typeof frame.full === 'object') return frame.full;
46
+ const patch = frame?.patch;
47
+ if (!patch || typeof patch !== 'object') return previous || {};
48
+ const base = previous && typeof previous === 'object' ? previous : {};
49
+ const next = { ...base, ...(patch.set || {}) };
50
+ if (patch.itemsAppend) {
51
+ const items = Array.isArray(base.items) ? base.items : [];
52
+ const from = Math.max(0, Math.floor(Number(patch.itemsAppend.from) || 0));
53
+ next.items = items.slice(0, from).concat(patch.itemsAppend.values || []);
54
+ }
55
+ for (const key of patch.remove || []) delete next[key];
56
+ return next;
57
+ }
58
+
59
+ class SessionShard {
60
+ constructor(index, { workerEntry, cwd, env, log }) {
61
+ this.index = index;
62
+ this.workerEntry = workerEntry;
63
+ this.cwd = cwd;
64
+ this.env = env;
65
+ this.log = log;
66
+ this.child = null;
67
+ this.pending = new Map();
68
+ this.proxies = new Map();
69
+ this.sequence = 0;
70
+ this.closed = false;
71
+ this.recovery = null;
72
+ this.failedChildren = new WeakSet();
73
+ }
74
+
75
+ ensureChild() {
76
+ if (this.child && !this.child.killed) return this.child;
77
+ if (this.closed) throw new Error(`session shard ${this.index} is closed`);
78
+ const child = fork(this.workerEntry, [], {
79
+ cwd: this.cwd,
80
+ execArgv: [],
81
+ stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
82
+ env: {
83
+ ...this.env,
84
+ MIXDOG_SESSION_SHARD_INDEX: String(this.index),
85
+ },
86
+ ...hiddenSpawnOpts,
87
+ });
88
+ this.child = child;
89
+ child.stderr?.on('data', (chunk) => {
90
+ const text = String(chunk || '').trimEnd();
91
+ if (text) this.log(`session shard ${this.index}: ${text}`);
92
+ });
93
+ child.on('message', (message) => this.onMessage(message));
94
+ child.on('error', (error) => this.handleChildFailure(child, error));
95
+ child.on('exit', (code, signal) => {
96
+ if (this.child === child) this.child = null;
97
+ this.handleChildFailure(
98
+ child,
99
+ new Error(`session shard ${this.index} exited (${signal || code || 'unknown'})`),
100
+ );
101
+ });
102
+ return child;
103
+ }
104
+
105
+ onMessage(message) {
106
+ if (!message || typeof message !== 'object') return;
107
+ if (message.type === 'state') {
108
+ this.proxies.get(String(message.runtimeId || ''))?.applyFrame(message);
109
+ return;
110
+ }
111
+ if (message.type !== 'response') return;
112
+ const request = this.pending.get(String(message.requestId || ''));
113
+ if (!request) return;
114
+ this.pending.delete(request.id);
115
+ if (request.timer) clearTimeout(request.timer);
116
+ if (message.ok === false) {
117
+ const error = new Error(String(message.error?.message || 'session shard call failed'));
118
+ if (message.error?.name) error.name = String(message.error.name);
119
+ if (message.error?.stack) error.stack = String(message.error.stack);
120
+ if (message.error?.statusCode) error.statusCode = Number(message.error.statusCode);
121
+ request.reject(error);
122
+ } else {
123
+ request.resolve(message.value);
124
+ }
125
+ }
126
+
127
+ rejectPending(error) {
128
+ for (const request of this.pending.values()) {
129
+ if (request.timer) clearTimeout(request.timer);
130
+ request.reject(error);
131
+ }
132
+ this.pending.clear();
133
+ }
134
+
135
+ handleChildFailure(child, error) {
136
+ if (this.failedChildren.has(child)) return;
137
+ this.failedChildren.add(child);
138
+ if (this.child === child) this.child = null;
139
+ this.rejectPending(error);
140
+ if (this.closed || this.proxies.size === 0) {
141
+ for (const proxy of this.proxies.values()) proxy.fail(error);
142
+ return;
143
+ }
144
+ for (const proxy of this.proxies.values()) proxy.beginRecovery(error);
145
+ this.recovery ||= this.recover().finally(() => { this.recovery = null; });
146
+ }
147
+
148
+ async recover() {
149
+ let lastError = null;
150
+ for (let attempt = 0; attempt < 3 && !this.closed; attempt += 1) {
151
+ try {
152
+ this.ensureChild();
153
+ for (const proxy of this.proxies.values()) await proxy.recreate();
154
+ this.log(`session shard ${this.index} recovered ${this.proxies.size} runtime(s)`);
155
+ return true;
156
+ } catch (error) {
157
+ lastError = error;
158
+ const child = this.child;
159
+ this.child = null;
160
+ try { child?.kill?.(); } catch {}
161
+ if (attempt < 2) {
162
+ await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
163
+ }
164
+ }
165
+ }
166
+ const error = lastError || new Error(`session shard ${this.index} recovery failed`);
167
+ for (const proxy of this.proxies.values()) proxy.fail(error);
168
+ return false;
169
+ }
170
+
171
+ requestRaw(type, payload = {}, timeoutMs = 10 * 60_000) {
172
+ const child = this.ensureChild();
173
+ const requestId = `shard-${this.index}-${process.pid}-${++this.sequence}`;
174
+ return new Promise((resolve, reject) => {
175
+ const request = { id: requestId, resolve, reject, timer: null };
176
+ if (timeoutMs > 0) {
177
+ request.timer = setTimeout(() => {
178
+ if (!this.pending.delete(requestId)) return;
179
+ reject(new Error(`session shard ${this.index} ${type} timed out`));
180
+ }, timeoutMs);
181
+ request.timer.unref?.();
182
+ }
183
+ this.pending.set(requestId, request);
184
+ if (!safeIpcSend(child, { type, requestId, ...payload }, {
185
+ onError: (error) => {
186
+ if (!this.pending.delete(requestId)) return;
187
+ if (request.timer) clearTimeout(request.timer);
188
+ reject(error);
189
+ },
190
+ })) {
191
+ this.pending.delete(requestId);
192
+ if (request.timer) clearTimeout(request.timer);
193
+ reject(new Error(`session shard ${this.index} IPC is unavailable`));
194
+ }
195
+ });
196
+ }
197
+
198
+ async request(type, payload = {}, timeoutMs = 10 * 60_000) {
199
+ if (this.recovery) await this.recovery;
200
+ return this.requestRaw(type, payload, timeoutMs);
201
+ }
202
+
203
+ async create(options) {
204
+ const runtimeId = randomUUID();
205
+ const proxy = new SessionRuntimeProxy(runtimeId, this, options);
206
+ this.proxies.set(runtimeId, proxy);
207
+ try {
208
+ await this.request('create', { runtimeId, options }, 120_000);
209
+ return proxy;
210
+ } catch (error) {
211
+ this.proxies.delete(runtimeId);
212
+ throw error;
213
+ }
214
+ }
215
+
216
+ async close(reason) {
217
+ this.closed = true;
218
+ const child = this.child;
219
+ if (!child) return;
220
+ let exited = false;
221
+ const exitPromise = new Promise((resolve) => {
222
+ child.once('exit', () => {
223
+ exited = true;
224
+ resolve();
225
+ });
226
+ });
227
+ try { await this.requestRaw('shutdown', { reason }, 8_000); } catch {}
228
+ try { child.disconnect?.(); } catch {}
229
+ await Promise.race([
230
+ exitPromise,
231
+ new Promise((resolve) => setTimeout(resolve, 500)),
232
+ ]);
233
+ if (!exited) {
234
+ try { child.kill?.(); } catch {}
235
+ await Promise.race([
236
+ exitPromise,
237
+ new Promise((resolve) => setTimeout(resolve, 2_000)),
238
+ ]);
239
+ }
240
+ this.child = null;
241
+ }
242
+
243
+ get status() {
244
+ return {
245
+ index: this.index,
246
+ pid: this.child?.pid || null,
247
+ runtimes: this.proxies.size,
248
+ pending: this.pending.size,
249
+ };
250
+ }
251
+ }
252
+
253
+ class SessionRuntimeProxy {
254
+ constructor(id, shard, options) {
255
+ this.id = id;
256
+ this.shard = shard;
257
+ this.options = { ...(options || {}) };
258
+ this.state = {};
259
+ this.revision = 0;
260
+ this.listeners = new Set();
261
+ this.failure = null;
262
+ this.recovering = false;
263
+ this.isWireSafe = true;
264
+ for (const method of RUNTIME_METHODS) {
265
+ this[method] = (...args) => this.call(method, args);
266
+ }
267
+ }
268
+
269
+ getState() {
270
+ return this.state;
271
+ }
272
+
273
+ subscribe(listener) {
274
+ this.listeners.add(listener);
275
+ return () => this.listeners.delete(listener);
276
+ }
277
+
278
+ applyFrame(frame) {
279
+ const revision = Number(frame.revision) || 0;
280
+ if (revision <= this.revision) return;
281
+ if (frame.patch && revision !== this.revision + 1) {
282
+ void this.shard.request('snapshot', { runtimeId: this.id }, 5_000).catch(() => {});
283
+ return;
284
+ }
285
+ this.state = applyShardStateFrame(this.state, frame);
286
+ this.revision = revision;
287
+ this.failure = null;
288
+ this.recovering = false;
289
+ for (const listener of [...this.listeners]) {
290
+ try { listener(); } catch {}
291
+ }
292
+ }
293
+
294
+ fail(error) {
295
+ this.failure = error instanceof Error ? error : new Error(String(error));
296
+ this.recovering = false;
297
+ for (const listener of [...this.listeners]) {
298
+ try { listener(); } catch {}
299
+ }
300
+ }
301
+
302
+ beginRecovery(error) {
303
+ this.failure = error instanceof Error ? error : new Error(String(error));
304
+ this.recovering = true;
305
+ }
306
+
307
+ async recreate() {
308
+ const previousSessionId = String(this.state?.sessionId || this.options.sessionId || '');
309
+ this.revision = 0;
310
+ await this.shard.requestRaw('create', {
311
+ runtimeId: this.id,
312
+ options: this.options,
313
+ }, 120_000);
314
+ if (previousSessionId) {
315
+ const resumed = await this.shard.requestRaw('call', {
316
+ runtimeId: this.id,
317
+ method: 'resume',
318
+ args: [previousSessionId],
319
+ }, 120_000);
320
+ if (resumed !== true) {
321
+ await this.shard.requestRaw('call', {
322
+ runtimeId: this.id,
323
+ method: 'reserveSession',
324
+ args: [previousSessionId],
325
+ }, 30_000);
326
+ }
327
+ }
328
+ await this.shard.requestRaw('snapshot', { runtimeId: this.id }, 5_000);
329
+ this.failure = null;
330
+ this.recovering = false;
331
+ }
332
+
333
+ async call(method, args) {
334
+ if (this.recovering && this.shard.recovery) await this.shard.recovery;
335
+ if (this.failure) throw this.failure;
336
+ try {
337
+ const value = await this.shard.request('call', {
338
+ runtimeId: this.id,
339
+ method,
340
+ args,
341
+ }, method === 'abort' ? 15_000 : 10 * 60_000);
342
+ if (method === 'dispose') this.shard.proxies.delete(this.id);
343
+ return value;
344
+ } catch (error) {
345
+ if (method === 'dispose') this.shard.proxies.delete(this.id);
346
+ throw error;
347
+ }
348
+ }
349
+ }
350
+
351
+ export function createSessionRuntimePool({
352
+ shardCount = configuredShardCount(),
353
+ workerEntry = fileURLToPath(new URL('./session-runtime-worker.mjs', import.meta.url)),
354
+ cwd = process.cwd(),
355
+ env = process.env,
356
+ log = () => {},
357
+ } = {}) {
358
+ const count = Math.max(1, Math.min(32, Math.floor(Number(shardCount)) || 1));
359
+ const shards = Array.from({ length: count }, (_, index) => new SessionShard(index, {
360
+ workerEntry,
361
+ cwd,
362
+ env,
363
+ log,
364
+ }));
365
+ let roundRobin = 0;
366
+ let closed = false;
367
+
368
+ return {
369
+ async create(options = {}) {
370
+ if (closed) throw new Error('session runtime pool is closed');
371
+ const sessionId = String(options.sessionId || '');
372
+ const index = sessionId
373
+ ? sessionShardIndex(sessionId, count)
374
+ : (roundRobin++ % count);
375
+ return shards[index].create(options);
376
+ },
377
+ prewarm() {
378
+ return shards[0].request('prewarm', {}, 120_000);
379
+ },
380
+ async close(reason = 'session runtime pool closed') {
381
+ if (closed) return;
382
+ closed = true;
383
+ await Promise.allSettled(shards.map((shard) => shard.close(reason)));
384
+ },
385
+ get status() {
386
+ return {
387
+ count,
388
+ active: shards.filter((shard) => shard.child).length,
389
+ shards: shards.map((shard) => shard.status),
390
+ };
391
+ },
392
+ };
393
+ }
@@ -0,0 +1,20 @@
1
+ /** Dispose one shard-owned runtime and release every projection even when the
2
+ * runtime's own dispose hook fails. The parent drops its proxy after a dispose
3
+ * response, so leaving the worker record behind would make it unreachable and
4
+ * retain the transcript for the shard process lifetime. */
5
+ export async function disposeSessionRuntimeRecord(records, record, args = []) {
6
+ if (!record || record.disposed) return undefined;
7
+ record.disposed = true;
8
+ try {
9
+ return await record.runtime?.dispose?.(...args);
10
+ } finally {
11
+ try { record.unsubscribe?.(); } catch {}
12
+ record.unsubscribe = null;
13
+ record.source = null;
14
+ record.projected = null;
15
+ record.published = null;
16
+ record.fields?.clear?.();
17
+ record.items?.clear?.();
18
+ records?.delete?.(record.id);
19
+ }
20
+ }