mixdog 1.1.2 → 1.2.1

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 (424) hide show
  1. package/README.md +8 -5
  2. package/package.json +2 -1
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/test-direct.mjs +4 -2
  6. package/src/defaults/shipped-history.json +10 -0
  7. package/src/defaults/skills/pptx/SKILL.md +6 -6
  8. package/src/defaults/skills/pptx/references/review.md +6 -4
  9. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  10. package/src/defaults/skills/setup/references/actions.md +18 -5
  11. package/src/defaults/skills/visualize/SKILL.md +160 -0
  12. package/src/defaults/skills/visualize/references/craft.md +88 -0
  13. package/src/headless-command.mjs +56 -3
  14. package/src/headless-exec.mjs +17 -1
  15. package/src/help.mjs +2 -0
  16. package/src/rules/shared/60-verification.md +3 -1
  17. package/src/rules/shared/70-delivery.md +4 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  22. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  23. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  24. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +19 -5
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +133 -15
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +55 -8
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +86 -8
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +474 -164
  33. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  34. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  35. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  36. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -12
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  43. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  45. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  50. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +21 -6
  51. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  52. package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
  53. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  56. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  57. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  58. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  59. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  60. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  61. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +82 -0
  62. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +556 -0
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  64. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  66. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +15 -0
  67. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  70. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +15 -4
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +47 -7
  78. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  79. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  80. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -11
  81. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  82. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  83. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +278 -84
  85. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  86. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  87. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  88. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -3
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  90. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  91. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  92. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  93. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
  94. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  95. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  96. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  97. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +24 -13
  98. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  99. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  100. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  101. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  102. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  103. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  104. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  105. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  106. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  107. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  108. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  109. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  112. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  114. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +5 -3
  115. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -4
  116. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +21 -10
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  119. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +15 -3
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +22 -12
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +9 -3
  124. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  125. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  126. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
  128. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  129. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  130. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  131. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  132. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  133. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  136. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  137. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  138. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +33 -22
  139. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  140. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  141. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  142. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  143. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  144. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
  145. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +36 -0
  146. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +23 -12
  147. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  148. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +202 -374
  149. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  150. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  151. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  152. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +48 -9
  153. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +161 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  155. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  157. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  158. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  159. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  160. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  161. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  162. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +203 -33
  163. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  164. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  165. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  166. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  167. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -68
  168. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  169. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +20 -3
  170. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  171. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  175. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  176. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  177. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  178. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  179. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  180. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  181. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  182. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  183. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  184. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  185. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  186. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  187. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  188. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  189. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  190. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  191. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  192. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  193. package/src/runtime/attachments/store.d.mts +3 -0
  194. package/src/runtime/attachments/store.mjs +38 -0
  195. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  196. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  197. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  198. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  199. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  200. package/src/runtime/memory/lib/memory.mjs +1 -1
  201. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  202. package/src/runtime/shared/atomic-file.mjs +33 -6
  203. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  204. package/src/runtime/shared/code-graph-state.mjs +12 -0
  205. package/src/runtime/shared/defaults-separation.mjs +2 -0
  206. package/src/runtime/shared/extension-scopes.mjs +5 -3
  207. package/src/runtime/shared/file-lock.mjs +37 -11
  208. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  209. package/src/runtime/shared/git-runner.mjs +4 -3
  210. package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
  211. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  212. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  213. package/src/runtime/shared/llm/model-catalog.mjs +240 -130
  214. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  215. package/src/runtime/shared/llm/provider-catalog-registry.mjs +162 -0
  216. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  217. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  218. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  219. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  220. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  221. package/src/runtime/shared/pristine-execution.mjs +28 -4
  222. package/src/runtime/shared/provider-accounts.mjs +125 -21
  223. package/src/runtime/shared/provider-api-key.mjs +33 -1
  224. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  225. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  226. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  227. package/src/runtime/shared/shell-display.mjs +9 -3
  228. package/src/runtime/shared/singleton-owner.mjs +36 -6
  229. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  230. package/src/runtime/shared/skill-mentions.mjs +133 -0
  231. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  232. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  233. package/src/runtime/shared/tool-primitives.mjs +35 -2
  234. package/src/runtime/shared/tool-surface.mjs +7 -0
  235. package/src/runtime/shared/tool-work-units.mjs +14 -0
  236. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  237. package/src/runtime/shared/turn-changes.mjs +791 -0
  238. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  239. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  240. package/src/runtime/shared/turn-worktree-snapshot.mjs +185 -15
  241. package/src/runtime/shared/worker-requests.mjs +15 -2
  242. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  243. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  244. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  245. package/src/runtime/shared/worktrees/git.mjs +247 -0
  246. package/src/runtime/shared/worktrees/include.mjs +480 -0
  247. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  248. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  249. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  250. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  251. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  252. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  253. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  254. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  255. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  256. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  257. package/src/runtime/visualize-bridge/client.mjs +181 -0
  258. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  259. package/src/session-runtime/automation-agents.mjs +8 -2
  260. package/src/session-runtime/boot/apis.mjs +90 -0
  261. package/src/session-runtime/boot/config.mjs +7 -2
  262. package/src/session-runtime/boot/facade.mjs +21 -0
  263. package/src/session-runtime/boot/infrastructure.mjs +30 -18
  264. package/src/session-runtime/boot/session.mjs +1 -0
  265. package/src/session-runtime/boot/tools.mjs +4 -0
  266. package/src/session-runtime/config-lifecycle.mjs +10 -1
  267. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  268. package/src/session-runtime/conversation-import/service.mjs +369 -0
  269. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +21 -6
  270. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  271. package/src/session-runtime/global-extensions.mjs +2 -2
  272. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  273. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  274. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  275. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  276. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  277. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  278. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  279. package/src/session-runtime/mcp-glue/connect.mjs +68 -16
  280. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  281. package/src/session-runtime/mcp-glue/server-catalog.mjs +122 -33
  282. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  283. package/src/session-runtime/mcp-glue.mjs +22 -2
  284. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  285. package/src/session-runtime/model-route/set-route.mjs +15 -2
  286. package/src/session-runtime/new-session-config.mjs +3 -2
  287. package/src/session-runtime/notification-bus.mjs +18 -3
  288. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  289. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  290. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  291. package/src/session-runtime/provider-readiness.mjs +16 -10
  292. package/src/session-runtime/resource-extension-sync.mjs +45 -7
  293. package/src/session-runtime/resource-mcp-api.mjs +121 -84
  294. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  295. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  296. package/src/session-runtime/resource-skills-api.mjs +23 -2
  297. package/src/session-runtime/route-resolution.mjs +10 -1
  298. package/src/session-runtime/route-state.mjs +16 -2
  299. package/src/session-runtime/runtime-core.mjs +5 -0
  300. package/src/session-runtime/runtime-facade.mjs +8 -0
  301. package/src/session-runtime/runtime-review-api.mjs +9 -0
  302. package/src/session-runtime/schedule-session-run.mjs +1 -1
  303. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  304. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  305. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  306. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  307. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  308. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  310. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  311. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  312. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  313. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  314. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  315. package/src/session-runtime/services/agent-tool.mjs +1 -0
  316. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  317. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  318. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  319. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  320. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  321. package/src/session-runtime/services/provider-admin.mjs +64 -18
  322. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  323. package/src/session-runtime/session-create/session-options.mjs +10 -1
  324. package/src/session-runtime/session-turn-api.mjs +1 -0
  325. package/src/session-runtime/settings-api.mjs +4 -2
  326. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  327. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  328. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  329. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  330. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -1
  331. package/src/session-runtime/setup-tool/tool-defs.mjs +38 -4
  332. package/src/session-runtime/skills-api.mjs +7 -3
  333. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  334. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +14 -3
  335. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  336. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  337. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  338. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  339. package/src/session-runtime/turn/turn-run.mjs +6 -7
  340. package/src/session-runtime/webhook-session-run.mjs +16 -4
  341. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  342. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  343. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  344. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  345. package/src/session-runtime/workspace/transitions.mjs +651 -0
  346. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  347. package/src/skill-script-entry.mjs +36 -0
  348. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  349. package/src/standalone/channel-transport.mjs +13 -1
  350. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  351. package/src/standalone/daemon-owner.mjs +372 -0
  352. package/src/standalone/daemon-stored-session-views.mjs +15 -4
  353. package/src/standalone/daemon.mjs +179 -8
  354. package/src/standalone/desktop-service-registry.mjs +20 -0
  355. package/src/standalone/local-session-runtime.mjs +11 -0
  356. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  357. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  358. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  359. package/src/standalone/session-client.mjs +104 -13
  360. package/src/standalone/session-protocol.mjs +21 -0
  361. package/src/standalone/session-proxy.mjs +3 -0
  362. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  363. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  364. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  365. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  366. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  367. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  368. package/src/standalone/session-service/session-calls/session-action.mjs +17 -4
  369. package/src/standalone/session-transport/call-route.mjs +13 -0
  370. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  371. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  372. package/src/standalone/session-transport/client-registry.mjs +20 -3
  373. package/src/standalone/session-transport/routes.mjs +79 -2
  374. package/src/standalone/session-transport/stack.mjs +2 -0
  375. package/src/standalone/session-transport.mjs +24 -1
  376. package/src/tui/App.jsx +9 -1
  377. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  378. package/src/tui/app/app-view.jsx +6 -0
  379. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  380. package/src/tui/app/message-selector.mjs +2 -1
  381. package/src/tui/app/model-options.mjs +37 -5
  382. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  383. package/src/tui/app/model-picker.mjs +4 -2
  384. package/src/tui/app/skill-mention-palette.mjs +88 -0
  385. package/src/tui/app/slash-palette.mjs +23 -7
  386. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  387. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  388. package/src/tui/app/use-prompt-state.mjs +5 -0
  389. package/src/tui/components/Message.jsx +22 -1
  390. package/src/tui/components/PromptInput.jsx +21 -3
  391. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  392. package/src/tui/components/TranscriptItem.jsx +9 -1
  393. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  394. package/src/tui/components/user-message-skills.mjs +38 -0
  395. package/src/tui/dist/index.mjs +839 -207
  396. package/src/tui/index.jsx +10 -2
  397. package/src/tui/session/compaction-view.mjs +147 -0
  398. package/src/tui/session/context-state.mjs +6 -0
  399. package/src/tui/session/initial-state.mjs +3 -0
  400. package/src/tui/session/queue-helpers.mjs +7 -2
  401. package/src/tui/session/quota-resume.mjs +2 -1
  402. package/src/tui/session/session-api/commands.mjs +10 -2
  403. package/src/tui/session/session-api/controls.mjs +4 -0
  404. package/src/tui/session/session-api/extensions.mjs +4 -2
  405. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  406. package/src/tui/session/session-api/integrations.mjs +17 -0
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  408. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  409. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  410. package/src/tui/session/session-api/routes.mjs +10 -1
  411. package/src/tui/session/session-api/settings.mjs +9 -0
  412. package/src/tui/session/session-api/worktree.mjs +60 -0
  413. package/src/tui/session/session-api.mjs +2 -0
  414. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  415. package/src/tui/session/session-flow/drain.mjs +27 -3
  416. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  417. package/src/tui/session/session-flow.mjs +2 -0
  418. package/src/tui/session/transcript-intake.mjs +14 -5
  419. package/src/tui/session/tui-steering-persist.mjs +25 -9
  420. package/src/tui/session/turn-stream.mjs +32 -4
  421. package/src/tui/session/turn-tool-cards.mjs +10 -2
  422. package/src/tui/session/turn.mjs +56 -10
  423. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  424. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
@@ -0,0 +1,82 @@
1
+ export const MODEL_PROXY_REQUEST_EVENT: 'model-proxy-request';
2
+ export const MODEL_PROXY_OPERATIONS: Readonly<{
3
+ claim: 'modelProxyClaim';
4
+ event: 'modelProxyEvent';
5
+ resolve: 'modelProxyResolve';
6
+ heartbeat: 'modelProxyHeartbeat';
7
+ }>;
8
+ export type ModelProxyMethod = 'send' | 'listModels' | 'refreshModels' | 'isAvailable' | 'providerConfig';
9
+ export const MODEL_PROXY_METHODS: ReadonlyArray<ModelProxyMethod>;
10
+ export const MODEL_PROXY_HEARTBEAT_MS: number;
11
+ export const MODEL_PROXY_LEASE_MS: number;
12
+
13
+ export interface ModelProxyRequestDescriptor {
14
+ id: string;
15
+ provider: string;
16
+ method: string;
17
+ model: string | null;
18
+ }
19
+ export interface ModelProxySnapshot {
20
+ requests: ModelProxyRequestDescriptor[];
21
+ cancelled: string[];
22
+ }
23
+ export interface ModelProxyErrorRecord {
24
+ name: string;
25
+ message: string;
26
+ props: Record<string, unknown>;
27
+ }
28
+
29
+ export class ModelProxyUnavailableError extends Error {
30
+ code: 'MODEL_PROXY_UNAVAILABLE';
31
+ constructor(detail: string);
32
+ }
33
+
34
+ export function modelProxyWireValue(value: unknown): unknown;
35
+ export function encodeModelProxyWire(value: unknown): string;
36
+ export function decodeModelProxyWire(text: unknown): unknown;
37
+ export function modelProxyErrorToWire(error: unknown): ModelProxyErrorRecord;
38
+ export function modelProxyErrorFromWire(record: unknown): Error;
39
+ export function splitModelProxySendOptions(opts: unknown): {
40
+ options: Record<string, unknown>;
41
+ callbacks: Record<string, (...args: unknown[]) => unknown>;
42
+ };
43
+ export function stripModelProxySecrets<T>(value: T): T;
44
+
45
+ export class ModelProxyChannel {
46
+ constructor(options?: { now?: () => number; claimTimeoutMs?: number; leaseMs?: number; sweepMs?: number });
47
+ readonly connected: boolean;
48
+ readonly generation: number;
49
+ whenConnected(timeoutMs: number): Promise<boolean>;
50
+ attachPublisher(publish: (snapshot: ModelProxySnapshot) => void): () => void;
51
+ disconnect(reason?: string): void;
52
+ setClientPresent(present: boolean): void;
53
+ onStateChange(listener: () => void): () => void;
54
+ request(
55
+ call: Record<string, unknown>,
56
+ options?: { signal?: AbortSignal | null; callbacks?: Record<string, (...args: unknown[]) => unknown> }
57
+ ): Promise<unknown>;
58
+ handlesOperation(name: string): boolean;
59
+ handleOperation(name: string, args?: unknown[]): unknown;
60
+ claim(id: unknown): { payload: string } | null;
61
+ event(id: unknown, eventsText: unknown): Promise<{ cancelled: boolean; results: string }>;
62
+ resolve(id: unknown, ok: boolean, valueText: unknown, errorText: unknown): boolean;
63
+ heartbeat(ids: unknown): { cancelled: string[] };
64
+ snapshot(): ModelProxySnapshot;
65
+ sweep(): void;
66
+ }
67
+
68
+ export function getModelProxyChannel(): ModelProxyChannel;
69
+
70
+ /** The channel surface model-proxy providers use; the bridge child implements only this. */
71
+ export interface ModelProxyChannelLike {
72
+ readonly connected: boolean;
73
+ readonly generation: number;
74
+ whenConnected(timeoutMs: number): Promise<boolean>;
75
+ request(
76
+ call: Record<string, unknown>,
77
+ options?: { signal?: AbortSignal | null; callbacks?: Record<string, (...args: unknown[]) => unknown> }
78
+ ): Promise<unknown>;
79
+ }
80
+
81
+ /** Swap the process-wide channel; the returned function restores the previous one. */
82
+ export function installModelProxyChannel(channel: ModelProxyChannel | ModelProxyChannelLike): () => void;
@@ -0,0 +1,556 @@
1
+ // Model proxy channel: the daemon half of "model calls via the client PC".
2
+ //
3
+ // In remote mode (MIXDOG_MODEL_PROXY=client) the daemon never holds model
4
+ // credentials. A provider send becomes a request here; the desktop client on
5
+ // the user's PC runs it with its own provider registry and streams back.
6
+ //
7
+ // Wire (all over the authenticated session connection):
8
+ // daemon -> PC desktop-event `model-proxy-request`: the FULL current state,
9
+ // { requests: [{ id, provider, method, model }], cancelled: [id] }.
10
+ // Desktop events are latest-wins per name under backpressure,
11
+ // so a snapshot (never a delta) is the only safe shape.
12
+ // PC -> daemon desktop operations
13
+ // modelProxyClaim(id) -> { payload } | null
14
+ // modelProxyEvent(id, eventsJson) -> { results, cancelled }
15
+ // modelProxyResolve(id, ok, valueJson, errorJson) -> boolean
16
+ // modelProxyHeartbeat(ids) -> { cancelled: [id] }
17
+ // Payloads, events and results travel as JSON text so the generic wire
18
+ // sanitizer (depth cap, Error flattening) never reshapes a model request.
19
+ //
20
+ // Deliberately dependency-free: the desktop service bundle imports this file
21
+ // too, and both copies share one channel through a global symbol.
22
+ import { randomUUID } from 'node:crypto';
23
+
24
+ export const MODEL_PROXY_REQUEST_EVENT = 'model-proxy-request';
25
+ export const MODEL_PROXY_OPERATIONS = Object.freeze({
26
+ claim: 'modelProxyClaim',
27
+ event: 'modelProxyEvent',
28
+ resolve: 'modelProxyResolve',
29
+ heartbeat: 'modelProxyHeartbeat',
30
+ });
31
+ /** Calls the PC executes. `providerConfig` is registry-wide (no provider). */
32
+ export const MODEL_PROXY_METHODS = Object.freeze([
33
+ 'send',
34
+ 'listModels',
35
+ 'refreshModels',
36
+ 'isAvailable',
37
+ 'providerConfig',
38
+ ]);
39
+ /** PC heartbeat cadence while it runs calls; well inside the lease. */
40
+ export const MODEL_PROXY_HEARTBEAT_MS = 5_000;
41
+
42
+ /** Daemon-side liveness lease of a claimed call; the PC gives up on the same clock. */
43
+ export const MODEL_PROXY_LEASE_MS = 20_000;
44
+ const CLAIM_TIMEOUT_MS = 10_000;
45
+ const LEASE_MS = MODEL_PROXY_LEASE_MS;
46
+ const SWEEP_MS = 2_000;
47
+ const WIRE_MAX_DEPTH = 64;
48
+ const BYTES_TAG = '__mixdogWireBytes';
49
+ const ERROR_TAG = '__mixdogWireError';
50
+ const OPERATION_NAMES = new Set(Object.values(MODEL_PROXY_OPERATIONS));
51
+
52
+ export class ModelProxyUnavailableError extends Error {
53
+ constructor(detail) {
54
+ super(`model proxy unavailable: ${detail}`);
55
+ this.name = 'ModelProxyUnavailableError';
56
+ this.code = 'MODEL_PROXY_UNAVAILABLE';
57
+ }
58
+ }
59
+
60
+ // ── Wire codec ──────────────────────────────────────────────────────────────
61
+
62
+ function errorToWireRecord(error, depth, seen) {
63
+ const record = { name: String(error?.name || 'Error'), message: String(error?.message || ''), props: {} };
64
+ for (const key of Object.getOwnPropertyNames(error)) {
65
+ if (key === 'name' || key === 'message' || key === 'stack') continue;
66
+ let value;
67
+ try {
68
+ value = error[key];
69
+ } catch {
70
+ continue;
71
+ }
72
+ const wire = wireValue(value, depth + 1, seen);
73
+ if (wire !== undefined) record.props[key] = wire;
74
+ }
75
+ return record;
76
+ }
77
+
78
+ function wireValue(value, depth, seen) {
79
+ if (value === null) return null;
80
+ const type = typeof value;
81
+ if (type === 'string' || type === 'boolean') return value;
82
+ if (type === 'number') return Number.isFinite(value) ? value : null;
83
+ if (type === 'bigint') return Number(value);
84
+ if (type !== 'object') return undefined;
85
+ if (depth >= WIRE_MAX_DEPTH || seen.has(value)) return undefined;
86
+ if (value instanceof Uint8Array) {
87
+ return { [BYTES_TAG]: Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString('base64') };
88
+ }
89
+ if (value instanceof Date) return value.toISOString();
90
+ seen.add(value);
91
+ try {
92
+ if (value instanceof Error) return { [ERROR_TAG]: errorToWireRecord(value, depth, seen) };
93
+ if (Array.isArray(value)) return value.map((entry) => wireValue(entry, depth + 1, seen) ?? null);
94
+ if (value instanceof Set) {
95
+ return [...value].map((entry) => wireValue(entry, depth + 1, seen)).filter((entry) => entry !== undefined);
96
+ }
97
+ const out = {};
98
+ const entries =
99
+ value instanceof Map ? [...value].map(([key, entry]) => [String(key), entry]) : Object.entries(value);
100
+ for (const [key, entry] of entries) {
101
+ const wire = wireValue(entry, depth + 1, seen);
102
+ if (wire !== undefined) out[key] = wire;
103
+ }
104
+ return out;
105
+ } finally {
106
+ seen.delete(value);
107
+ }
108
+ }
109
+
110
+ /** JSON-safe projection: bytes and errors are tagged, cycles and functions dropped. */
111
+ export function modelProxyWireValue(value) {
112
+ return wireValue(value, 0, new WeakSet());
113
+ }
114
+
115
+ export function encodeModelProxyWire(value) {
116
+ return JSON.stringify(modelProxyWireValue(value) ?? null);
117
+ }
118
+
119
+ /** Rebuild an Error with every own property it carried (status, code, partials...). */
120
+ export function modelProxyErrorFromWire(record) {
121
+ const error = new Error(String(record?.message || 'model proxy call failed'));
122
+ if (record?.name) error.name = String(record.name);
123
+ for (const [key, value] of Object.entries(record?.props || {})) {
124
+ try {
125
+ error[key] = value;
126
+ } catch {
127
+ /* read-only on Error: keep the rest */
128
+ }
129
+ }
130
+ return error;
131
+ }
132
+
133
+ export function modelProxyErrorToWire(error) {
134
+ return error instanceof Error
135
+ ? errorToWireRecord(error, 0, new WeakSet())
136
+ : { name: 'Error', message: String(error ?? 'model proxy call failed'), props: {} };
137
+ }
138
+
139
+ export function decodeModelProxyWire(text) {
140
+ if (typeof text !== 'string' || !text) return null;
141
+ return JSON.parse(text, (_key, value) => {
142
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return value;
143
+ if (typeof value[BYTES_TAG] === 'string' && Object.keys(value).length === 1) {
144
+ return Buffer.from(value[BYTES_TAG], 'base64');
145
+ }
146
+ if (value[ERROR_TAG] && typeof value[ERROR_TAG] === 'object' && Object.keys(value).length === 1) {
147
+ return modelProxyErrorFromWire(value[ERROR_TAG]);
148
+ }
149
+ return value;
150
+ });
151
+ }
152
+
153
+ // ── Send options ────────────────────────────────────────────────────────────
154
+
155
+ /**
156
+ * Split provider send options into wire data and callbacks. The abort signal
157
+ * stays on the daemon (cancellation travels as a channel cancel), `_`-prefixed
158
+ * keys are process-local seams (admission metrics, stall budgets, test hooks),
159
+ * and only `on*` functions are callbacks the PC may invoke back.
160
+ */
161
+ export function splitModelProxySendOptions(opts) {
162
+ const options = {};
163
+ const callbacks = {};
164
+ for (const [key, value] of Object.entries(opts || {})) {
165
+ if (key === 'signal' || key.startsWith('_')) continue;
166
+ if (typeof value === 'function') {
167
+ if (/^on[A-Z]/.test(key)) callbacks[key] = value;
168
+ continue;
169
+ }
170
+ options[key] = value;
171
+ }
172
+ return { options, callbacks };
173
+ }
174
+
175
+ const SECRET_KEY =
176
+ /api[-_]?key|secret|password|passwd|authorization|cookie|bearer|credential|^headers$|(?:^|[-_])token$|^(?:access|refresh|id)[-_]?token$|^tokens$/i;
177
+
178
+ /** Provider config without any credential-bearing field, at any depth. */
179
+ export function stripModelProxySecrets(value, depth = 0) {
180
+ if (!value || typeof value !== 'object' || depth > WIRE_MAX_DEPTH) return value;
181
+ if (Array.isArray(value)) return value.map((entry) => stripModelProxySecrets(entry, depth + 1));
182
+ const out = {};
183
+ for (const [key, entry] of Object.entries(value)) {
184
+ if (SECRET_KEY.test(key)) continue;
185
+ out[key] = stripModelProxySecrets(entry, depth + 1);
186
+ }
187
+ return out;
188
+ }
189
+
190
+ // ── Channel ─────────────────────────────────────────────────────────────────
191
+
192
+ function abortReason(signal) {
193
+ if (signal?.reason instanceof Error) return signal.reason;
194
+ const error = new Error(String(signal?.reason || 'model call aborted'));
195
+ error.name = 'AbortError';
196
+ return error;
197
+ }
198
+
199
+ export class ModelProxyChannel {
200
+ #publisher = null;
201
+ #entries = new Map();
202
+ #sweepTimer = null;
203
+ #now;
204
+ #claimTimeoutMs;
205
+ #leaseMs;
206
+ #sweepMs;
207
+
208
+ constructor({ now = Date.now, claimTimeoutMs = CLAIM_TIMEOUT_MS, leaseMs = LEASE_MS, sweepMs = SWEEP_MS } = {}) {
209
+ this.#now = now;
210
+ this.#claimTimeoutMs = claimTimeoutMs;
211
+ this.#leaseMs = leaseMs;
212
+ this.#sweepMs = sweepMs;
213
+ }
214
+
215
+ #clientPresent = true;
216
+ #generation = 0;
217
+ #connectionWaiters = new Set();
218
+
219
+ /** True while a desktop client service can receive model-proxy requests. */
220
+ get connected() {
221
+ return this.#publisher !== null && this.#clientPresent;
222
+ }
223
+
224
+ /** Bumps whenever the client behind the channel may have changed: an answer
225
+ * learned under one generation (the PC's provider config) must not be
226
+ * served to the next client, which may be another PC. */
227
+ get generation() {
228
+ return this.#generation;
229
+ }
230
+
231
+ /** Resolves true once a client is connected, false after `timeoutMs`. */
232
+ whenConnected(timeoutMs) {
233
+ if (this.connected) return Promise.resolve(true);
234
+ return new Promise((resolve) => {
235
+ const waiter = (connected) => {
236
+ clearTimeout(timer);
237
+ this.#connectionWaiters.delete(waiter);
238
+ resolve(connected);
239
+ };
240
+ const timer = setTimeout(() => waiter(false), Math.max(0, Number(timeoutMs) || 0));
241
+ timer.unref?.();
242
+ this.#connectionWaiters.add(waiter);
243
+ });
244
+ }
245
+
246
+ #stateListeners = new Set();
247
+
248
+ /** `listener()` runs after every presence/generation change (the model
249
+ * proxy bridge pushes it to its clients). Returns the unsubscribe. */
250
+ onStateChange(listener) {
251
+ this.#stateListeners.add(listener);
252
+ return () => this.#stateListeners.delete(listener);
253
+ }
254
+
255
+ #connectionChanged() {
256
+ this.#generation += 1;
257
+ for (const listener of [...this.#stateListeners]) {
258
+ try {
259
+ listener();
260
+ } catch {
261
+ /* a listener never breaks the transport that reported the change */
262
+ }
263
+ }
264
+ if (!this.connected) return;
265
+ for (const waiter of [...this.#connectionWaiters]) waiter(true);
266
+ }
267
+
268
+ /**
269
+ * Transport hook: the session connection knows when the last desktop client
270
+ * is gone. Absent, every outstanding call fails and new ones fail fast;
271
+ * without this hook an absent client is detected by the claim timeout.
272
+ */
273
+ setClientPresent(present) {
274
+ const next = present !== false;
275
+ const changed = next !== this.#clientPresent;
276
+ this.#clientPresent = next;
277
+ if (!this.#clientPresent) this.disconnect('the desktop client disconnected');
278
+ if (changed) this.#connectionChanged();
279
+ }
280
+
281
+ /**
282
+ * Register the desktop-event publisher of the connected client service. The
283
+ * returned detach fails every outstanding call: a client that went away can
284
+ * never answer them.
285
+ */
286
+ attachPublisher(publish) {
287
+ this.#publisher = publish;
288
+ this.#connectionChanged();
289
+ if (this.#entries.size) this.#publish();
290
+ return () => {
291
+ if (this.#publisher !== publish) return;
292
+ this.#publisher = null;
293
+ this.disconnect('the desktop client disconnected');
294
+ this.#connectionChanged();
295
+ };
296
+ }
297
+
298
+ /** Fail every outstanding call now (client gone). Never falls back. */
299
+ disconnect(reason = 'the desktop client disconnected') {
300
+ const entries = [...this.#entries.values()];
301
+ this.#entries.clear();
302
+ this.#stopSweep();
303
+ for (const entry of entries) this.#settle(entry, false, new ModelProxyUnavailableError(reason));
304
+ }
305
+
306
+ /**
307
+ * One call executed by the PC. `callbacks` are invoked, in order, for every
308
+ * event the PC streams back; the promise settles with the PC's final result.
309
+ */
310
+ request(call, { signal = null, callbacks = {} } = {}) {
311
+ if (signal?.aborted) return Promise.reject(abortReason(signal));
312
+ if (!this.connected) {
313
+ return Promise.reject(new ModelProxyUnavailableError('no desktop client is connected to run model calls'));
314
+ }
315
+ let payload;
316
+ try {
317
+ payload = encodeModelProxyWire({ ...call, callbacks: Object.keys(callbacks) });
318
+ } catch (error) {
319
+ return Promise.reject(error);
320
+ }
321
+ const now = this.#now();
322
+ return new Promise((resolve, reject) => {
323
+ const entry = {
324
+ id: `mp_${randomUUID().replaceAll('-', '')}`,
325
+ state: 'pending',
326
+ provider: String(call?.provider || ''),
327
+ method: String(call?.method || ''),
328
+ model: typeof call?.model === 'string' ? call.model : null,
329
+ payload,
330
+ callbacks,
331
+ resolve,
332
+ reject,
333
+ signal,
334
+ onAbort: null,
335
+ settled: false,
336
+ claimDeadline: now + this.#claimTimeoutMs,
337
+ lastSeenAt: now,
338
+ cancelledAt: 0,
339
+ };
340
+ if (signal) {
341
+ entry.onAbort = () => this.#cancel(entry, abortReason(signal));
342
+ signal.addEventListener('abort', entry.onAbort, { once: true });
343
+ }
344
+ this.#entries.set(entry.id, entry);
345
+ this.#ensureSweep();
346
+ this.#publish();
347
+ });
348
+ }
349
+
350
+ handlesOperation(name) {
351
+ return OPERATION_NAMES.has(String(name || ''));
352
+ }
353
+
354
+ /** Desktop-operation entry point for the PC's answers. */
355
+ handleOperation(name, args = []) {
356
+ const list = Array.isArray(args) ? args : [];
357
+ switch (name) {
358
+ case MODEL_PROXY_OPERATIONS.claim:
359
+ return this.claim(list[0]);
360
+ case MODEL_PROXY_OPERATIONS.event:
361
+ return this.event(list[0], list[1]);
362
+ case MODEL_PROXY_OPERATIONS.resolve:
363
+ return this.resolve(list[0], list[1] === true, list[2], list[3]);
364
+ case MODEL_PROXY_OPERATIONS.heartbeat:
365
+ return this.heartbeat(list[0]);
366
+ default:
367
+ throw new TypeError(`unknown model proxy operation: ${name}`);
368
+ }
369
+ }
370
+
371
+ claim(id) {
372
+ const entry = this.#entries.get(String(id || ''));
373
+ if (entry?.state !== 'pending') return null;
374
+ entry.state = 'claimed';
375
+ entry.lastSeenAt = this.#now();
376
+ const payload = entry.payload;
377
+ entry.payload = null;
378
+ this.#publish();
379
+ return { payload };
380
+ }
381
+
382
+ async event(id, eventsText) {
383
+ const entry = this.#entries.get(String(id || ''));
384
+ if (!entry || entry.state === 'pending') return { cancelled: true, results: '[]' };
385
+ entry.lastSeenAt = this.#now();
386
+ if (entry.state === 'cancelled') return { cancelled: true, results: '[]' };
387
+ const events = decodeModelProxyWire(eventsText);
388
+ const results = [];
389
+ for (const event of Array.isArray(events) ? events : []) {
390
+ const callback = entry.state === 'claimed' ? entry.callbacks[String(event?.name || '')] : null;
391
+ if (typeof callback !== 'function') {
392
+ results.push({ ok: true, value: null });
393
+ continue;
394
+ }
395
+ try {
396
+ const value = await callback(...(Array.isArray(event.args) ? event.args : []));
397
+ results.push({ ok: true, value: modelProxyWireValue(value) ?? null });
398
+ } catch (error) {
399
+ results.push({ ok: false, error: modelProxyErrorToWire(error) });
400
+ }
401
+ }
402
+ entry.lastSeenAt = this.#now();
403
+ return { cancelled: entry.state !== 'claimed', results: encodeModelProxyWire(results) };
404
+ }
405
+
406
+ resolve(id, ok, valueText, errorText) {
407
+ const entry = this.#entries.get(String(id || ''));
408
+ if (!entry || entry.state === 'pending') return false;
409
+ this.#entries.delete(entry.id);
410
+ if (entry.state === 'cancelled') {
411
+ this.#publish();
412
+ return true;
413
+ }
414
+ if (ok) {
415
+ let value;
416
+ try {
417
+ value = decodeModelProxyWire(valueText);
418
+ } catch (error) {
419
+ this.#settle(entry, false, error);
420
+ return true;
421
+ }
422
+ this.#settle(entry, true, value);
423
+ } else {
424
+ let error;
425
+ try {
426
+ error = modelProxyErrorFromWire(decodeModelProxyWire(errorText));
427
+ } catch {
428
+ error = new Error('model proxy call failed on the desktop client');
429
+ }
430
+ this.#settle(entry, false, error);
431
+ }
432
+ return true;
433
+ }
434
+
435
+ /** PC liveness for its running calls; answers which of them to stop. */
436
+ heartbeat(ids) {
437
+ const now = this.#now();
438
+ const cancelled = [];
439
+ for (const raw of Array.isArray(ids) ? ids : []) {
440
+ const id = String(raw || '');
441
+ const entry = this.#entries.get(id);
442
+ if (entry?.state !== 'claimed') {
443
+ cancelled.push(id);
444
+ continue;
445
+ }
446
+ entry.lastSeenAt = now;
447
+ }
448
+ return { cancelled };
449
+ }
450
+
451
+ snapshot() {
452
+ const requests = [];
453
+ const cancelled = [];
454
+ for (const entry of this.#entries.values()) {
455
+ if (entry.state === 'pending') {
456
+ requests.push({ id: entry.id, provider: entry.provider, method: entry.method, model: entry.model });
457
+ } else if (entry.state === 'cancelled') cancelled.push(entry.id);
458
+ }
459
+ return { requests, cancelled };
460
+ }
461
+
462
+ #publish() {
463
+ try {
464
+ this.#publisher?.(this.snapshot());
465
+ } catch {
466
+ /* the sweep fails unclaimed calls if the client never hears of them */
467
+ }
468
+ }
469
+
470
+ #settle(entry, ok, value) {
471
+ if (entry.settled) return;
472
+ entry.settled = true;
473
+ if (entry.onAbort) {
474
+ try {
475
+ entry.signal.removeEventListener('abort', entry.onAbort);
476
+ } catch {
477
+ /* best effort: the call is settled whether or not the signal drops the listener */
478
+ }
479
+ }
480
+ entry.callbacks = {};
481
+ if (ok) entry.resolve(value);
482
+ else entry.reject(value);
483
+ }
484
+
485
+ #cancel(entry, reason) {
486
+ if (entry.settled) return;
487
+ this.#settle(entry, false, reason);
488
+ if (entry.state === 'pending') this.#entries.delete(entry.id);
489
+ else {
490
+ entry.state = 'cancelled';
491
+ entry.cancelledAt = this.#now();
492
+ }
493
+ this.#publish();
494
+ }
495
+
496
+ #ensureSweep() {
497
+ if (this.#sweepTimer) return;
498
+ this.#sweepTimer = setInterval(() => this.sweep(), this.#sweepMs);
499
+ this.#sweepTimer.unref?.();
500
+ }
501
+
502
+ #stopSweep() {
503
+ if (!this.#sweepTimer) return;
504
+ clearInterval(this.#sweepTimer);
505
+ this.#sweepTimer = null;
506
+ }
507
+
508
+ /** Expire unclaimed calls, silent clients and acknowledged-never cancels. */
509
+ sweep() {
510
+ const now = this.#now();
511
+ let changed = false;
512
+ for (const entry of [...this.#entries.values()]) {
513
+ if (entry.state === 'pending' && now >= entry.claimDeadline) {
514
+ this.#entries.delete(entry.id);
515
+ this.#settle(entry, false, new ModelProxyUnavailableError('the desktop client did not pick up the model call'));
516
+ changed = true;
517
+ } else if (entry.state === 'claimed' && now - entry.lastSeenAt > this.#leaseMs) {
518
+ this.#entries.delete(entry.id);
519
+ this.#settle(entry, false, new ModelProxyUnavailableError('the desktop client stopped responding'));
520
+ changed = true;
521
+ } else if (entry.state === 'cancelled' && now - entry.cancelledAt > this.#leaseMs) {
522
+ this.#entries.delete(entry.id);
523
+ changed = true;
524
+ }
525
+ }
526
+ if (changed) this.#publish();
527
+ if (!this.#entries.size) this.#stopSweep();
528
+ }
529
+ }
530
+
531
+ const CHANNEL_KEY = Symbol.for('mixdog.modelProxyChannel.v1');
532
+
533
+ /** The process-wide channel shared by the provider registry and the desktop service. */
534
+ export function getModelProxyChannel() {
535
+ globalThis[CHANNEL_KEY] ??= new ModelProxyChannel();
536
+ return globalThis[CHANNEL_KEY];
537
+ }
538
+
539
+ /**
540
+ * Make `channel` the process-wide channel (a headless child talks through the
541
+ * daemon's model proxy bridge instead). The returned restore puts the previous
542
+ * channel back once, unless another install replaced this one since.
543
+ */
544
+ export function installModelProxyChannel(channel) {
545
+ const hadPrevious = Object.hasOwn(globalThis, CHANNEL_KEY);
546
+ const previous = globalThis[CHANNEL_KEY];
547
+ globalThis[CHANNEL_KEY] = channel;
548
+ let restored = false;
549
+ return () => {
550
+ if (restored) return;
551
+ restored = true;
552
+ if (globalThis[CHANNEL_KEY] !== channel) return;
553
+ if (hadPrevious) globalThis[CHANNEL_KEY] = previous;
554
+ else delete globalThis[CHANNEL_KEY];
555
+ };
556
+ }