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
@@ -20,6 +20,8 @@ process.env.MIXDOG_DAEMON_HOST = '1';
20
20
  // Size the libuv threadpool before any async fs work spins it up (see
21
21
  // uv-threadpool-boot.mjs) — imports below already touch fs.
22
22
  await import('../runtime/shared/uv-threadpool-boot.mjs');
23
+ // Before anything can import sharp: Electron on Linux must use its wasm build.
24
+ await import('../runtime/shared/sharp-electron-linux.mjs');
23
25
 
24
26
  // No V8 compile cache here, on purpose. Measured on this runtime: a cache hit
25
27
  // saves nothing (spawn→ready 316-347ms with the cache vs 318-323ms without,
@@ -37,8 +39,17 @@ import { writeJsonAtomicSync } from '../runtime/shared/atomic-file.mjs';
37
39
  import { ensurePrivateRuntimeRoot, resolveRuntimeRoot } from '../runtime/shared/runtime-root.mjs';
38
40
  import { ensureProcessListenerHeadroom } from '../runtime/shared/process-listener-headroom.mjs';
39
41
  import { listenerLeakWarningLine } from '../runtime/shared/listener-leak-warning.mjs';
40
- import { claimSingletonOwner, releaseSingletonOwner } from '../runtime/shared/singleton-owner.mjs';
42
+ import { claimSingletonOwner, readSingletonOwner, releaseSingletonOwner } from '../runtime/shared/singleton-owner.mjs';
43
+ import {
44
+ createOwnerHeartbeat,
45
+ daemonOwnerIsStale,
46
+ daemonOwnerTimings,
47
+ discoveryPid,
48
+ proveDaemonOwnerRecord,
49
+ startOwnerLivenessListener,
50
+ } from './daemon-owner.mjs';
41
51
  import { remoteIntentPath } from '../runtime/shared/remote-intent.mjs';
52
+ import { modelProxyClientMode } from '../runtime/shared/model-proxy-mode.mjs';
42
53
  import { setChannelNotifySink } from '../runtime/channels/lib/parent-bridge.mjs';
43
54
  import { setOwnerContext } from '../runtime/channels/lib/runtime-paths.mjs';
44
55
  import { safeIpcSend } from '../runtime/shared/safe-ipc-send.mjs';
@@ -61,6 +72,7 @@ import { createSessionService } from './session-service.mjs';
61
72
  import { createLocalSessionBridge } from './daemon-local-session-bridge.mjs';
62
73
  import { createStoredSessionViews } from './daemon-stored-session-views.mjs';
63
74
  import * as quotaResumeStore from '../runtime/shared/quota-resume-store.mjs';
75
+ import { registerWorktreeCacheCleanups } from '../runtime/shared/worktrees/cache-cleanups.mjs';
64
76
  import { createInlineSessionRuntimeHost } from './session-runtime-inline-host.mjs';
65
77
  import { getStandaloneMemoryRuntime } from '../session-runtime/services/memory-runtime-proxy.mjs';
66
78
  import { createBootPhaseProfiler } from './boot-phase-profiler.mjs';
@@ -126,6 +138,18 @@ let remoteSessionState = { enabled: false, sessionId: null };
126
138
  let shuttingDown = false;
127
139
  let shutdownRecheckTimer = null;
128
140
  let replacementRequested = null;
141
+ let ownerHeartbeat = null;
142
+ // Answers /health with this pid on the port the owner record names, from
143
+ // before the claim until exit (daemon-owner.mjs).
144
+ let ownerLiveness = null;
145
+ // The endpoints daemon.json carries once published; a heartbeat re-publishes
146
+ // them when the file goes missing (TEMP cleanup).
147
+ let publishedEndpoints = null;
148
+ // Remote mode only: the loopback relay that lets headless runs started from
149
+ // model shells (skill scripts) reach the PC behind this daemon's model proxy
150
+ // channel (providers/model-proxy-bridge.mjs), and its running-call count.
151
+ let modelProxyBridge = null;
152
+ let modelProxyBridgeActive = 0;
129
153
  const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
130
154
  eventLoopDelay.enable();
131
155
  let idleGc = null;
@@ -181,6 +205,7 @@ const SHUTDOWN_BUDGET_MS = 15_000;
181
205
  async function shutdown(reason, code = 0) {
182
206
  if (shuttingDown) return;
183
207
  shuttingDown = true;
208
+ ownerHeartbeat?.stop();
184
209
  exitRecorder.setReason(`shutdown: ${reason}`, code);
185
210
  if (shutdownRecheckTimer) {
186
211
  clearTimeout(shutdownRecheckTimer);
@@ -199,6 +224,13 @@ async function shutdown(reason, code = 0) {
199
224
  try {
200
225
  setChannelNotifySink(null);
201
226
  } catch {}
227
+ // Abort every relayed model call, close the bridge sockets and wait for the
228
+ // calls to settle before anything they depend on goes away.
229
+ try {
230
+ await modelProxyBridge?.close(`daemon shutdown: ${reason}`);
231
+ } catch (e) {
232
+ log(`model proxy bridge stop failed: ${e?.message || e}`);
233
+ }
202
234
  try {
203
235
  await sessionService?.stop?.(reason);
204
236
  } catch (e) {
@@ -258,7 +290,9 @@ async function shutdown(reason, code = 0) {
258
290
  daemonTelemetry?.stop();
259
291
  eventLoopDelay.disable();
260
292
  try {
261
- rmSync(DAEMON_DISCOVERY_PATH, { force: true });
293
+ // Only our own: a successor that reclaimed a stale owner record may
294
+ // already have published its endpoint here.
295
+ if (discoveryPid(DAEMON_DISCOVERY_PATH) === process.pid) rmSync(DAEMON_DISCOVERY_PATH, { force: true });
262
296
  } catch {}
263
297
  try {
264
298
  releaseSingletonOwner(OWNER_PATH, process.pid);
@@ -271,7 +305,7 @@ async function shutdown(reason, code = 0) {
271
305
  * asks whether this process may exit; idle GC asks whether a pause is safe. */
272
306
  function inFlightWork() {
273
307
  return {
274
- activeCalls: (transport?.activeCount ?? 0) + (sessionTransport?.activeCount ?? 0),
308
+ activeCalls: (transport?.activeCount ?? 0) + (sessionTransport?.activeCount ?? 0) + modelProxyBridgeActive,
275
309
  queuedCalls: (transport?.queuedCount ?? 0) + (sessionTransport?.queuedCount ?? 0),
276
310
  busySessions: sessionService?.busyCount ?? 0,
277
311
  busyMemoryAgents: agentDispatchBroker?.snapshot?.().inFlight ?? 0,
@@ -421,7 +455,18 @@ function requestDaemonReplacement({ protocol, revision, version, force } = {}) {
421
455
  /** Pid-verified singleton claim (claimSingletonOwner reclaims a dead-pid owner
422
456
  * file and refuses only a LIVE peer). Loser exits so the spawner attaches to
423
457
  * the winner instead of running a second daemon. */
424
- function claimDaemonOwnership(bootPhases) {
458
+ async function claimDaemonOwnership(bootPhases) {
459
+ const timings = daemonOwnerTimings();
460
+ // A live-pid record is reclaimable only when its holder's endpoint does not
461
+ // answer with its pid (daemon-owner.mjs); probed before the sync claim.
462
+ const proof = await proveDaemonOwnerRecord(OWNER_PATH, {
463
+ discoveryPath: DAEMON_DISCOVERY_PATH,
464
+ staleMs: timings.staleMs,
465
+ });
466
+ // Up BEFORE the claim, so even the first record names a port that answers
467
+ // with this pid: a contender never reclaims a booting daemon on a stale-
468
+ // looking stamp (a clock jump) alone.
469
+ ownerLiveness = await startOwnerLivenessListener({ log });
425
470
  const claim = claimSingletonOwner(OWNER_PATH, {
426
471
  kind: 'mixdog-daemon',
427
472
  pid: process.pid,
@@ -430,14 +475,47 @@ function claimDaemonOwnership(bootPhases) {
430
475
  protocol: SESSION_PROTOCOL,
431
476
  revision: SESSION_REVISION,
432
477
  version: runtimeVersion(),
478
+ heartbeatAt: new Date().toISOString(),
479
+ ...(ownerLiveness ? { livenessPort: ownerLiveness.port } : {}),
480
+ },
481
+ // A live pid that neither beats nor answers is a reused pid, not a peer.
482
+ isStale: (owner) => {
483
+ const stale = daemonOwnerIsStale(owner, { proof, staleMs: timings.staleMs });
484
+ if (stale) {
485
+ log(
486
+ `reclaiming stale owner record pid=${owner.pid} claimedAt=${owner.claimedAt}` +
487
+ ` heartbeatAt=${owner.heartbeatAt ?? '-'} probe=${proof?.verdict ?? '-'}`
488
+ );
489
+ }
490
+ return stale;
433
491
  },
434
492
  });
435
493
  if (!claim.owned) {
436
494
  log(`live peer holds owner lock (pid=${claim.owner?.pid}) — exiting for attach`);
495
+ void ownerLiveness?.close();
496
+ ownerLiveness = null;
437
497
  exitRecorder.setReason(`lost singleton claim to live peer pid=${claim.owner?.pid}`, 0);
438
498
  process.exit(0);
439
499
  }
440
500
  bootPhases.mark('owner-claimed');
501
+ // Beats from here on, through boot: a slow boot is never mistaken for a
502
+ // reused pid. Losing the record to another pid, or the lease running out
503
+ // unrefreshed, means another daemon owns (or may now claim) this data dir —
504
+ // at most one may keep serving.
505
+ ownerHeartbeat = createOwnerHeartbeat({
506
+ ownerPath: OWNER_PATH,
507
+ owner: claim.owner,
508
+ heartbeatMs: timings.heartbeatMs,
509
+ fenceMs: timings.fenceMs,
510
+ tickMs: timings.tickMs,
511
+ stallMs: timings.stallMs,
512
+ log,
513
+ onBeat: republishMissingDiscovery,
514
+ onSuperseded: (record) => fenceDaemon(`owner claim superseded by pid=${record?.pid}`),
515
+ onFenced: ({ lastRefreshAt, sinceMs }) =>
516
+ fenceDaemon(`owner lease expired (no refresh for ${sinceMs}ms, last ${lastRefreshAt})`),
517
+ });
518
+ ownerHeartbeat.start();
441
519
  process.on('exit', () => {
442
520
  try {
443
521
  releaseSingletonOwner(OWNER_PATH, process.pid);
@@ -497,7 +575,9 @@ function daemonStatusSnapshot() {
497
575
 
498
576
  /** The file every attacher reads to find this daemon: wire identity plus both
499
577
  * loopback endpoints and their privileged tokens. */
500
- function publishDaemonDiscovery({ channel, session }) {
578
+ function publishDaemonDiscovery({ channel, session, modelProxy = null }) {
579
+ // A re-publish keeps the first publication's startedAt.
580
+ publishedEndpoints = { channel, session, modelProxy, startedAt: publishedEndpoints?.startedAt ?? Date.now() };
501
581
  writeJsonAtomicSync(
502
582
  DAEMON_DISCOVERY_PATH,
503
583
  {
@@ -506,16 +586,75 @@ function publishDaemonDiscovery({ channel, session }) {
506
586
  version: runtimeVersion(),
507
587
  capabilityFingerprint: SESSION_CAPABILITY_FINGERPRINT,
508
588
  pid: process.pid,
509
- startedAt: Date.now(),
589
+ startedAt: publishedEndpoints.startedAt,
510
590
  endpoints: {
511
591
  channel: { port: channel.port, token: channel.token },
512
592
  session: { port: session.port, token: session.token },
593
+ ...(modelProxy ? { modelProxy: { port: modelProxy.port, token: modelProxy.token } } : {}),
513
594
  },
514
595
  },
515
596
  { compact: true, secret: true }
516
597
  );
517
598
  }
518
599
 
600
+ /** Remote mode only: the model proxy bridge endpoint to publish, or null. */
601
+ async function startModelProxyBridge(bootPhases) {
602
+ if (!modelProxyClientMode()) return null;
603
+ const [{ createModelProxyBridgeServer }, { getModelProxyChannel }] = await Promise.all([
604
+ import('../runtime/agent/orchestrator/providers/model-proxy-bridge.mjs'),
605
+ import('../runtime/agent/orchestrator/providers/model-proxy-channel.mjs'),
606
+ ]);
607
+ modelProxyBridge = createModelProxyBridgeServer({
608
+ channel: getModelProxyChannel(),
609
+ // Fenced or shutting down: no new relayed call.
610
+ admit: () => !shuttingDown && admitOwnerWork(),
611
+ trackWork: (active) => {
612
+ modelProxyBridgeActive = active;
613
+ },
614
+ log,
615
+ });
616
+ const endpoint = await bootPhases.measure('model-proxy-bridge-start', () => modelProxyBridge.start());
617
+ log(`model proxy bridge on 127.0.0.1:${endpoint.port}`);
618
+ return endpoint;
619
+ }
620
+
621
+ /** Asked by both front doors before every register/call: an expired lease
622
+ * fences here (answered 503), and after an event-loop stall nothing is
623
+ * served until the record is re-verified as ours. */
624
+ function admitOwnerWork() {
625
+ return ownerHeartbeat ? ownerHeartbeat.admit() : true;
626
+ }
627
+
628
+ /** This process no longer holds (or is about to lose) the owner lease: refuse
629
+ * new work at once, unpublish its endpoint so launchers stop finding it, then
630
+ * shut down within the shutdown budget — before a contender may reclaim. */
631
+ function fenceDaemon(reason) {
632
+ if (shuttingDown) return;
633
+ log(`${reason} — refusing new work and stopping so only one daemon serves`);
634
+ sessionTransport?.commitDrain?.(reason);
635
+ transport?.commitDrain?.(reason);
636
+ modelProxyBridge?.abortAll(reason);
637
+ try {
638
+ if (discoveryPid(DAEMON_DISCOVERY_PATH) === process.pid) rmSync(DAEMON_DISCOVERY_PATH, { force: true });
639
+ } catch (e) {
640
+ log(`daemon.json unpublish failed: ${e?.message || e}`);
641
+ }
642
+ void shutdown(reason);
643
+ }
644
+
645
+ /** Heartbeat follow-up while this process still owns the lock: a daemon.json
646
+ * deleted (TEMP cleanup) or overwritten while it runs is written again, so
647
+ * new clients keep finding the one daemon that serves this data dir. */
648
+ function republishMissingDiscovery() {
649
+ if (!publishedEndpoints || shuttingDown || discoveryPid(DAEMON_DISCOVERY_PATH) === process.pid) return;
650
+ try {
651
+ publishDaemonDiscovery(publishedEndpoints);
652
+ log('daemon.json was missing or named another pid — re-published');
653
+ } catch (error) {
654
+ log(`daemon.json re-publish failed: ${error?.message || error}`);
655
+ }
656
+ }
657
+
519
658
  /** Ready handshake for the spawner, plus the log-sink handoff pinned to the
520
659
  * same boundary. Transport is already listening; signal ready before the heavy
521
660
  * channel-worker connect so the spawner's ready wait never blocks on service
@@ -569,6 +708,7 @@ async function startChannelFrontDoor({ channelsRuntime, bootPhases }) {
569
708
  transport = createChannelTransport({
570
709
  handleCall: channelsRuntime.handleCall,
571
710
  agentBroker: agentDispatchBroker,
711
+ admitWork: admitOwnerWork,
572
712
  log,
573
713
  // The durable channel link lives in this file. Without it a restart loses
574
714
  // the pinned session (nothing to restore) and every catalog reports Remote
@@ -617,8 +757,27 @@ async function main() {
617
757
  // are per-user and fail closed if another account owns the configured path.
618
758
  ensurePrivateRuntimeRoot(RUNTIME_ROOT);
619
759
 
620
- claimDaemonOwnership(bootPhases);
760
+ await claimDaemonOwnership(bootPhases);
621
761
  registerMemoryRuntimeLazy();
762
+ // Every in-process session runtime removes managed worktrees through the
763
+ // shared manager; its root cleanups drop the path-keyed caches (turn
764
+ // snapshots, code graph) of a removed worktree. Modules load on first use.
765
+ registerWorktreeCacheCleanups();
766
+ // Managed worktrees, off the boot path: finish interrupted deletions and
767
+ // creations, release stale leases and, with autoCleanup on, remove clean,
768
+ // merged worktrees no stored session retains (never one a session keeps).
769
+ void import('../runtime/shared/worktrees/maintenance.mjs')
770
+ .then((maintenance) => maintenance.reconcileWorktreesAtStartup())
771
+ .then((report) => {
772
+ const changed = report.removed.length + report.recovered.length + report.pruned.length;
773
+ if (changed || report.errors.length) {
774
+ log(
775
+ `worktree reconcile: removed ${report.removed.length}, recovered ${report.recovered.length}, ` +
776
+ `pruned ${report.pruned.length}, errors ${report.errors.length}`
777
+ );
778
+ }
779
+ })
780
+ .catch((error) => log(`worktree reconcile failed: ${error?.message || error}`));
622
781
  // The daemon owns prompt attachments; its census runs off the boot path
623
782
  // (first attempt after the store's start delay, then per its interval).
624
783
  void import('../runtime/attachments/store.mjs').then((store) => store.startAttachmentGc()).catch(() => {});
@@ -764,9 +923,21 @@ async function main() {
764
923
  } catch {}
765
924
  },
766
925
  onUpgradeRequested: requestDaemonReplacement,
926
+ admitWork: admitOwnerWork,
767
927
  });
768
928
  const sessionEndpoint = await bootPhases.measure('session-transport-start', () => sessionTransport.start());
769
- publishDaemonDiscovery({ channel: { port, token }, session: sessionEndpoint });
929
+ const modelProxyEndpoint = await startModelProxyBridge(bootPhases);
930
+ // A boot whose holder stopped answering may have been superseded by a
931
+ // contender between beats; never publish over the winner's endpoint. An
932
+ // unreadable record (an antivirus lock) is not a takeover.
933
+ const ownerPid = Number(readSingletonOwner(OWNER_PATH).owner?.pid) || 0;
934
+ if (shuttingDown) return;
935
+ if (ownerPid && ownerPid !== process.pid) {
936
+ log(`owner record now names pid=${ownerPid} — exiting for attach before publishing`);
937
+ exitRecorder.setReason(`owner claim superseded by pid=${ownerPid} before publish`, 0);
938
+ process.exit(0);
939
+ }
940
+ publishDaemonDiscovery({ channel: { port, token }, session: sessionEndpoint, modelProxy: modelProxyEndpoint });
770
941
  bootPhases.mark('discovery-published');
771
942
  log(`session front door on 127.0.0.1:${sessionEndpoint.port}`);
772
943
 
@@ -1,6 +1,8 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { sanitizeForWire } from './session-wire-values.mjs';
4
+ import { modelProxyClientMode } from '../runtime/shared/model-proxy-mode.mjs';
5
+ import { getModelProxyChannel } from '../runtime/agent/orchestrator/providers/model-proxy-channel.mjs';
4
6
 
5
7
  const requireDesktopModule = createRequire(import.meta.url);
6
8
 
@@ -185,9 +187,24 @@ export class DesktopServiceRegistry {
185
187
  if (this.#closed) throw new Error('session service is closed');
186
188
  const token = subscriberToken(ctx);
187
189
  if (token) service.subscribers.add(token);
190
+ this.#syncModelProxyPresence();
188
191
  return { desktopId: service.desktopId };
189
192
  }
190
193
 
194
+ // Proxied model calls need a live desktop subscriber; tell the channel the
195
+ // moment the last one leaves instead of waiting for its claim timeout.
196
+ #syncModelProxyPresence() {
197
+ if (!modelProxyClientMode()) return;
198
+ let present = false;
199
+ for (const service of this.#servicesById.values()) {
200
+ if (service.subscribers.size) {
201
+ present = true;
202
+ break;
203
+ }
204
+ }
205
+ getModelProxyChannel().setClientPresent(present);
206
+ }
207
+
191
208
  // Every addressed call also (re)subscribes its caller to the service lane.
192
209
  #requireSubscribed(desktopId, ctx) {
193
210
  const service = this.#require(desktopId);
@@ -211,6 +228,7 @@ export class DesktopServiceRegistry {
211
228
 
212
229
  ready({ desktopId } = {}, ctx = null) {
213
230
  const { service, token } = this.#requireSubscribed(desktopId, ctx);
231
+ this.#syncModelProxyPresence();
214
232
  this.#onReady({ desktopId: service.desktopId, clientToken: token || null });
215
233
  return { ok: true };
216
234
  }
@@ -219,6 +237,7 @@ export class DesktopServiceRegistry {
219
237
  const service = this.#require(desktopId);
220
238
  const token = subscriberToken(ctx);
221
239
  if (token) service.subscribers.delete(token);
240
+ this.#syncModelProxyPresence();
222
241
  return { ok: true, unsubscribed: true };
223
242
  }
224
243
 
@@ -228,6 +247,7 @@ export class DesktopServiceRegistry {
228
247
  for (const service of this.#servicesById.values()) {
229
248
  service.subscribers.delete(token);
230
249
  }
250
+ this.#syncModelProxyPresence();
231
251
  }
232
252
 
233
253
  notifySessionRuntimeReleased(sessionId, reason) {
@@ -122,14 +122,17 @@ export async function createLocalSessionRuntime({
122
122
  model,
123
123
  effort,
124
124
  fast,
125
+ autoEffort,
125
126
  modelParameters,
126
127
  contextPercent,
128
+ contextTokens,
127
129
  selectedContextWindow,
128
130
  toolMode = 'full',
129
131
  remote = false,
130
132
  cwd,
131
133
  desktopSession,
132
134
  sessionProfile,
135
+ worktree,
133
136
  executeAgentControl,
134
137
  } = {}) {
135
138
  const startedAt = performance.now();
@@ -150,14 +153,18 @@ export async function createLocalSessionRuntime({
150
153
  model,
151
154
  effort,
152
155
  fast,
156
+ autoEffort,
153
157
  modelParameters,
154
158
  contextPercent,
159
+ contextTokens,
155
160
  selectedContextWindow,
156
161
  toolMode,
157
162
  remote,
158
163
  ...(cwd ? { cwd } : {}),
159
164
  ...(desktopSession ? { desktopSession } : {}),
160
165
  ...(sessionProfile && typeof sessionProfile === 'object' ? { sessionProfile } : {}),
166
+ // Session creation worktree intent; consumed by the workspace transition scope.
167
+ ...(worktree && typeof worktree === 'object' ? { worktree } : {}),
161
168
  ...(typeof executeAgentControl === 'function' ? { executeAgentControl } : {}),
162
169
  });
163
170
  bootProfile('session:create:runtime-ready', { ms: (performance.now() - startedAt).toFixed(1) });
@@ -396,6 +403,10 @@ export async function createLocalSessionRuntime({
396
403
  subscribeRuntimeNotifications,
397
404
  });
398
405
  Object.assign(bag, createSessionFlow(bag));
406
+ // Runtime-driven completion wakes (no attached surface acked the completion)
407
+ // get the same idle auto-clear as a user submit.
408
+ lifecycle.unsubscribeCompletionWakeGate =
409
+ runtime.setCompletionWakeGate?.(() => bag.autoClearBeforeWake()) || null;
399
410
  Object.assign(
400
411
  bag,
401
412
  createGoalContinuation({
@@ -13,7 +13,7 @@ const URGENT_CALLS = new Set([
13
13
  'desktop.unsubscribe',
14
14
  ]);
15
15
 
16
- export function createAttachmentCalls({ request, port, serverToken, clientToken, stopStream, log }) {
16
+ export function createAttachmentCalls({ request, port, serverToken, clientToken, stopStream, stopHeartbeat, log }) {
17
17
  let closed = false;
18
18
 
19
19
  async function call(name, args = {}, { timeoutMs = 300_000, callId = null } = {}) {
@@ -38,6 +38,7 @@ export function createAttachmentCalls({ request, port, serverToken, clientToken,
38
38
  // Preserve the daemon's machine-readable classification across the wire.
39
39
  const err = new Error(out.error);
40
40
  if (out.code) err.code = String(out.code);
41
+ if (out.details && typeof out.details === 'object') err.details = out.details;
41
42
  throw err;
42
43
  }
43
44
  return out?.result;
@@ -47,6 +48,7 @@ export function createAttachmentCalls({ request, port, serverToken, clientToken,
47
48
  if (closed) return;
48
49
  closed = true;
49
50
  stopStream();
51
+ stopHeartbeat?.();
50
52
  try {
51
53
  await request({
52
54
  port,
@@ -0,0 +1,62 @@
1
+ // The error ensureDaemon throws when no session endpoint appeared before its
2
+ // deadline. The first line is the stable message callers and tests match; the
3
+ // lines below carry what the last pass observed — an unanswered discovery, an
4
+ // owner that never published, the newest candidate's exit and first stderr
5
+ // error — so the desktop error card's details name the cause, not only the
6
+ // symptom.
7
+ export const DAEMON_UNAVAILABLE_MESSAGE = 'daemon session endpoint is unavailable';
8
+
9
+ function exitCodeText(code) {
10
+ // Windows reports NTSTATUS crash codes as large unsigned values.
11
+ return Number.isSafeInteger(code) && code > 0x7fffffff ? `0x${code.toString(16).toUpperCase()}` : String(code);
12
+ }
13
+
14
+ function candidateLines(candidate, { attempt, maxAttempts, now }) {
15
+ const head = `last daemon candidate (attempt ${attempt} of ${maxAttempts})`;
16
+ if (!candidate.pid) return [`${head} failed to spawn: ${candidate.error || 'unknown error'}`];
17
+ let state;
18
+ if (candidate.exitedAt) {
19
+ state =
20
+ `exited code=${candidate.exitCode === null ? '-' : exitCodeText(candidate.exitCode)}` +
21
+ ` signal=${candidate.exitSignal || '-'} after ${candidate.uptimeMs ?? '?'}ms` +
22
+ (candidate.exitReason ? ` (${candidate.exitReason})` : '');
23
+ } else if (candidate.ready) {
24
+ state = 'reported ready but its endpoint never answered';
25
+ } else {
26
+ const spawnedAt = Date.parse(candidate.spawnedAt || '');
27
+ state = `still running, not ready ${Number.isFinite(spawnedAt) ? now - spawnedAt : '?'}ms after spawn`;
28
+ }
29
+ const lines = [`${head}: pid=${candidate.pid} ${state}`];
30
+ if (candidate.stderrErrorLine) lines.push(`first stderr error: ${candidate.stderrErrorLine}`);
31
+ else if (candidate.stderrLastLine) lines.push(`last stderr line: ${candidate.stderrLastLine}`);
32
+ if (candidate.stderrPath) lines.push(`stderr capture: ${candidate.stderrPath}`);
33
+ return lines;
34
+ }
35
+
36
+ export function daemonUnavailableError({
37
+ discovery = null,
38
+ owner = null,
39
+ candidate = null,
40
+ spawnAttempts = 0,
41
+ maxSpawnAttempts = 0,
42
+ captureDir,
43
+ now = Date.now(),
44
+ }) {
45
+ const lines = [DAEMON_UNAVAILABLE_MESSAGE];
46
+ if (discovery) lines.push(`discovery: pid=${discovery.pid} port=${discovery.port} did not answer its health check`);
47
+ if (owner) {
48
+ lines.push(
49
+ `daemon owner: pid=${owner.pid} (claimed ${owner.claimedAt || 'at an unknown time'}) held the lock` +
50
+ ' but never published an endpoint'
51
+ );
52
+ }
53
+ if (candidate) {
54
+ lines.push(...candidateLines(candidate, { attempt: spawnAttempts, maxAttempts: maxSpawnAttempts, now }));
55
+ } else {
56
+ lines.push(`no daemon candidate was spawned (spawn attempts allowed: ${maxSpawnAttempts})`);
57
+ }
58
+ lines.push(`daemon crash captures: ${captureDir}`);
59
+ const error = new Error(lines.join('\n'));
60
+ error.daemonUnavailable = { discovery, owner, candidate, spawnAttempts, captureDir };
61
+ return error;
62
+ }
@@ -0,0 +1,47 @@
1
+ // session-client/lease-heartbeat.mjs
2
+ // A remote attachment has no local pid the daemon can probe, so its liveness
3
+ // is a lease the client renews: POST /client/heartbeat every `heartbeatMs`,
4
+ // one request in flight at a time, each bounded by heartbeatMs / 2. A failed
5
+ // beat is only logged — the next tick retries, and the stream's own recovery
6
+ // decides when the attachment is truly lost.
7
+ import { clampLeaseMs } from '../session-transport/client-registry/client-grace.mjs';
8
+
9
+ export function createLeaseHeartbeat({ request, port, serverToken, clientToken, heartbeatMs = 10_000, leaseMs, log }) {
10
+ // Always beat at least three times per (server-clamped) lease.
11
+ heartbeatMs = Math.min(heartbeatMs, Math.floor(clampLeaseMs(leaseMs) / 3));
12
+ let timer = null;
13
+ let inFlight = false;
14
+
15
+ async function beat() {
16
+ if (inFlight) return;
17
+ inFlight = true;
18
+ try {
19
+ await request({
20
+ port,
21
+ token: serverToken,
22
+ method: 'POST',
23
+ path: '/client/heartbeat',
24
+ body: { token: clientToken },
25
+ timeoutMs: heartbeatMs / 2,
26
+ control: true,
27
+ });
28
+ } catch (error) {
29
+ log(`heartbeat failed: ${error?.message || error}`);
30
+ } finally {
31
+ inFlight = false;
32
+ }
33
+ }
34
+
35
+ function start() {
36
+ if (timer) return;
37
+ timer = setInterval(beat, heartbeatMs);
38
+ timer.unref?.();
39
+ }
40
+
41
+ function stop() {
42
+ if (timer) clearInterval(timer);
43
+ timer = null;
44
+ }
45
+
46
+ return { start, stop };
47
+ }