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
@@ -3,15 +3,16 @@
3
3
 
4
4
  import { readFileSync, existsSync, readdirSync } from 'node:fs';
5
5
  import { basename, dirname, join } from 'node:path';
6
- import { maxMtimeRecursive } from '../cache-mtime.mjs';
6
+ import { maxMtimeRecursive, mtimeSignatureRecursive } from '../cache-mtime.mjs';
7
7
  import { mixdogHome, resolvePluginData, mixdogRoot } from '../../../shared/plugin-paths.mjs';
8
- import { pluginSkillsRoots } from '../../../shared/plugin-manifest.mjs';
8
+ import { PLUGIN_MANIFEST_FILES, pluginSkillsRoots } from '../../../shared/plugin-manifest.mjs';
9
9
  import {
10
10
  parseSkillDocument,
11
11
  SKILL_TRIGGER_LISTING_MAX,
12
12
  SKILL_TRIGGER_LISTING_MIN,
13
13
  } from '../../../shared/skill-document.mjs';
14
14
  import { readSkillToolDependencies, skillToolDependenciesRoot } from '../../../shared/skill-tool-dependencies.mjs';
15
+ import { SKILL_LAUNCHER_COMMAND } from '../../../shared/skill-script-runtime.mjs';
15
16
  import { loadConfig, normalizeSkillsConfig } from '../config.mjs';
16
17
  import { builtinFeatureActive, withGrandfatheredBuiltins } from '../runtime-core/builtin-features.mjs';
17
18
  import { SKILL_TOOL } from '../runtime-core/tool-defs.mjs';
@@ -88,6 +89,32 @@ function pluginSkillDirs() {
88
89
  return dirs;
89
90
  }
90
91
 
92
+ /** Paths whose own mtime (no recursion) gates the skill list for enabled
93
+ * registered plugins: every manifest file, plus the nearest existing ancestor
94
+ * of each candidate skills root (so a skills dir created later is noticed). */
95
+ function pluginShallowWatchPaths() {
96
+ const registryPath = pluginRegistryPath();
97
+ let registry;
98
+ try {
99
+ registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
100
+ } catch {
101
+ return [];
102
+ }
103
+ const paths = [];
104
+ for (const entry of Array.isArray(registry?.plugins) ? registry.plugins : []) {
105
+ if (entry?.enabled === false) continue;
106
+ const root = entry && typeof entry.root === 'string' ? entry.root : null;
107
+ if (!root || !existsSync(root)) continue;
108
+ for (const rel of PLUGIN_MANIFEST_FILES) paths.push(join(root, ...rel.split('/')));
109
+ for (const skillsDir of pluginSkillsRoots(root)) {
110
+ let dir = skillsDir;
111
+ while (!existsSync(dir) && dirname(dir) !== dir) dir = dirname(dir);
112
+ paths.push(dir);
113
+ }
114
+ }
115
+ return paths;
116
+ }
117
+
91
118
  export function readSafe(path) {
92
119
  try {
93
120
  if (!existsSync(path)) return null;
@@ -216,24 +243,39 @@ function followsBuiltinFeature(skill) {
216
243
  return skill?.source === 'builtin' && Array.isArray(skill.requires) && skill.requires.length > 0;
217
244
  }
218
245
 
219
- function findCachedSkillByKey(key) {
220
- return collectSkillsCached(null).find((entry) => normalizeSkillNameKey(entry.name) === key);
246
+ function findCachedSkillByKey(key, skills = null) {
247
+ return (skills || collectSkillsCached(null)).find((entry) => normalizeSkillNameKey(entry.name) === key);
221
248
  }
222
249
 
223
- export function isSkillDisabled(name, config = null) {
250
+ /** `skills`: optional snapshot from one collectSkillsCached() call, reused so a
251
+ * batch of per-skill checks scans the disk once. */
252
+ export function isSkillDisabled(name, config = null, skills = null) {
224
253
  const n = normalizeSkillNameKey(name);
225
254
  if (!n) return false;
226
- const skill = findCachedSkillByKey(n);
255
+ const skill = findCachedSkillByKey(n, skills);
227
256
  if (!followsBuiltinFeature(skill) && getDisabledSkillNameSet(config).has(n)) return true;
228
257
  return Boolean(skill && missingFeature(skill, featureConfig(config)));
229
258
  }
230
259
 
231
260
  /** The built-in feature a skill needs that is not active, or null. */
232
- export function skillMissingFeature(name, config = null) {
233
- const skill = findCachedSkillByKey(normalizeSkillNameKey(name));
261
+ export function skillMissingFeature(name, config = null, skills = null) {
262
+ const skill = findCachedSkillByKey(normalizeSkillNameKey(name), skills);
234
263
  return skill ? missingFeature(skill, featureConfig(config)) : null;
235
264
  }
236
265
 
266
+ /** True when an installed skill exists but its (or its plugin's) project scope
267
+ * excludes `cwd` — the same scope rule filterSkillsExcludingDisabled applies. */
268
+ export function skillOutsideScope(name, cwd, config = null, skills = null) {
269
+ const skill = findCachedSkillByKey(normalizeSkillNameKey(name), skills);
270
+ if (!skill) return false;
271
+ return !skillAllowedForCwd(extensionScopesFromConfig(featureConfig(config)), skill, cwd);
272
+ }
273
+
274
+ /** The Skill tool result for a skill skillOutsideScope refuses. */
275
+ export function skillOutsideScopeError(name) {
276
+ return `Error: skill "${name}" is not available in this project (its project scope excludes the current folder)`;
277
+ }
278
+
237
279
  /**
238
280
  * Skills a session may see: not switched off, feature available, and — when
239
281
  * `cwd` is given — inside the skill's (or its plugin's) project scope. Without
@@ -260,8 +302,6 @@ export function collectPromptSkillsCached(cwd, config = null) {
260
302
  }
261
303
  // --- Global skill cache (mtime-based) ---
262
304
  const _skillsCache = new Map();
263
- const _mtimeCache = new Map();
264
- const _MTIME_TTL_MS = 2000;
265
305
 
266
306
  export function collectSkillsCached(cwd) {
267
307
  if (skillsDisabled()) return [];
@@ -273,32 +313,41 @@ export function collectSkillsCached(cwd) {
273
313
  skillsDirs.push(...pluginSkillDirs().map(({ dir }) => dir), ...builtinSkillDirs());
274
314
  // registry.json itself gates plugin add/remove: removal deletes the
275
315
  // plugin's skills dir (so no dir mtime advances), but saveRegistry()
276
- // always rewrites this file. maxMtimeRecursive stats plain files directly.
316
+ // always rewrites this file. mtimeSignatureRecursive stats plain files directly.
277
317
  const registryPath = pluginRegistryPath();
278
318
  if (registryPath) skillsDirs.push(registryPath);
279
- const mtime = mtimeWithTtl(_mtimeCache, key, skillsDirs, _MTIME_TTL_MS);
319
+ // Exact, un-timed gate: stat every call. The shallow watch list covers
320
+ // manifest edits and skill roots that do not exist yet.
321
+ const signature = `${mtimeSignatureRecursive(skillsDirs)}|${mtimeSignatureRecursive(pluginShallowWatchPaths(), 0)}`;
280
322
  const entry = _skillsCache.get(key);
281
- if (entry && entry.mtime >= mtime) {
323
+ if (entry && entry.signature === signature) {
282
324
  return entry.value;
283
325
  }
284
326
  const skills = collectSkills(cwd);
285
- _skillsCache.set(key, { value: skills, mtime });
327
+ _skillsCache.set(key, { value: skills, signature });
286
328
  capMapSize(_skillsCache);
287
329
  return skills;
288
330
  }
289
331
  export function invalidateSkillsCache(cwd) {
290
332
  void cwd;
291
333
  _skillsCache.clear();
292
- _mtimeCache.clear();
293
334
  }
294
- /** Drop only the mtime TTL so the next call re-stats the skill roots while the
295
- * cached list stays — lets a test prove the gate itself notices an edit. */
296
- export function invalidateSkillsMtimeGate() {
297
- _mtimeCache.clear();
335
+
336
+ /**
337
+ * Directory the model hands to external processes for a skill. A packaged
338
+ * desktop runtime reads built-in skills from inside `runtime.asar`, which only
339
+ * Electron can open; the build ships them as real files beside the archive
340
+ * under `runtime.asar.unpacked`, so a built-in skill's directory points there.
341
+ * Every other source keeps its own on-disk directory.
342
+ */
343
+ export function skillExecutionDir(dir, source) {
344
+ if (source !== 'builtin') return dir;
345
+ return String(dir).replace(/([\\/])runtime\.asar(?=[\\/]|$)/, '$1runtime.asar.unpacked');
298
346
  }
299
347
 
300
348
  /**
301
- * Load full skill content plus its on-disk directory (for base-dir + ${MIXDOG_SKILL_DIR}).
349
+ * Load full skill content plus its on-disk directory (for base-dir + ${MIXDOG_SKILL_DIR};
350
+ * ${MIXDOG_SKILL_NODE} is resolved at render time in buildSkillResultEnvelope).
302
351
  */
303
352
  export function loadSkillResource(name, cwd) {
304
353
  const skillName = String(name || '').trim();
@@ -311,7 +360,7 @@ export function loadSkillResource(name, cwd) {
311
360
  try {
312
361
  return {
313
362
  content: parseSkillDocument(content).body,
314
- dir: dirname(skill.filePath),
363
+ dir: skillExecutionDir(dirname(skill.filePath), skill.source),
315
364
  filePath: skill.filePath,
316
365
  source: skill.source || 'global',
317
366
  toolDependencies: skill.toolDependencies || [],
@@ -335,6 +384,10 @@ export function buildSkillResultEnvelope(name, content, skillDir) {
335
384
  const escapedName = escapeSkillXmlText(name);
336
385
  let body = String(content == null ? '' : content);
337
386
  let dirLine = '';
387
+ // `${MIXDOG_SKILL_NODE}` runs a bundled script on this daemon's own runtime
388
+ // (plain node cannot load runtime internals from a packaged archive). It is
389
+ // one shell-neutral command name that model shells find on their PATH.
390
+ body = body.replace(/\$\{MIXDOG_SKILL_NODE\}/g, SKILL_LAUNCHER_COMMAND);
338
391
  if (skillDir) {
339
392
  const normDir = String(skillDir).replace(/\\/g, '/');
340
393
  body = body.replace(/\$\{MIXDOG_SKILL_DIR\}/g, normDir);
@@ -3,6 +3,8 @@
3
3
  * kind, URL policy, env expansion, stdio environment, HTTP headers, timeouts,
4
4
  * and the log scrubbing that keeps credentials out of connection messages.
5
5
  */
6
+ import { createHash } from 'node:crypto';
7
+ import { rememberMcpToolIdentity } from '../../../shared/tool-primitives.mjs';
6
8
 
7
9
  /** Known auto-detect targets: the discovery-advert service name and the MCP
8
10
  * endpoint path on its port.
@@ -15,8 +17,16 @@ export const AUTO_DETECT_PORTS = {
15
17
  'mixdog-memory': { discovery: 'memory', endpoint: '/mcp' },
16
18
  };
17
19
  const DEFAULT_MCP_CALL_TIMEOUT_MS = 120000;
18
- // Per-server STARTUP handshake budget (connect + listTools): 10s.
19
- const DEFAULT_MCP_STARTUP_TIMEOUT_MS = 10000;
20
+ // Per-server STARTUP handshake budget (connect + paginated listTools): 30s.
21
+ // Cold npx/uvx wrappers routinely need well over 10s to install and boot.
22
+ const DEFAULT_MCP_STARTUP_TIMEOUT_MS = 30000;
23
+ // Hard ceiling for a request that keeps reporting progress (each progress
24
+ // notification resets the per-call timeout, this caps the total): 30min.
25
+ const DEFAULT_MCP_MAX_TOTAL_TIMEOUT_MS = 1_800_000;
26
+ // Largest delay setTimeout honours; anything larger fires after 1ms. Used as
27
+ // the SDK request timeout when our own call timeout is disabled, so the SDK's
28
+ // built-in 60s default never applies.
29
+ export const MCP_MAX_TIMER_MS = 2_147_483_647;
20
30
 
21
31
  function isLoopbackMcpHost(hostname) {
22
32
  const host = String(hostname || '')
@@ -100,30 +110,106 @@ export function scrubMcpConnectionMessage(value, cfg = {}) {
100
110
  }
101
111
 
102
112
  /**
103
- * Expand `${VAR}` and `${env:VAR}` references in string values using the
104
- * provided env map (defaults to process.env). Recurses into arrays/objects.
105
- * Unknown vars expand to an empty string. No shell execution.
113
+ * Expand `${VAR}`, `${env:VAR}` and `${VAR:-default}` references in string
114
+ * values using the provided env map (defaults to process.env). Recurses into
115
+ * arrays/objects. The default (possibly empty) applies only when the variable
116
+ * is unset, and runs to the first `}`. An unset variable without a default
117
+ * expands to an empty string and, when `missing` (a Set) is given, its name is
118
+ * recorded there. No shell execution.
106
119
  */
107
- export function expandEnvVars(value, env = process.env) {
120
+ export function expandEnvVars(value, env = process.env, missing) {
108
121
  if (typeof value === 'string') {
109
- return value.replace(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) => {
122
+ return value.replace(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (_m, name, fallback) => {
110
123
  const v = env?.[name];
111
- return v == null ? '' : String(v);
124
+ if (v != null) return String(v);
125
+ if (fallback !== undefined) return fallback;
126
+ missing?.add(name);
127
+ return '';
112
128
  });
113
129
  }
114
130
  if (Array.isArray(value)) {
115
- return value.map((v) => expandEnvVars(v, env));
131
+ return value.map((v) => expandEnvVars(v, env, missing));
116
132
  }
117
133
  if (value && typeof value === 'object') {
118
134
  const out = {};
119
135
  for (const [k, v] of Object.entries(value)) {
120
- out[k] = expandEnvVars(v, env);
136
+ out[k] = expandEnvVars(v, env, missing);
121
137
  }
122
138
  return out;
123
139
  }
124
140
  return value;
125
141
  }
126
142
 
143
+ /**
144
+ * Names (deduplicated, first-seen order) of variables a server config needs
145
+ * that are unset: `${VAR}` references without a default in command, args,
146
+ * env, url, headers and cwd, plus the variables named directly by
147
+ * `bearer_token_env_var`, `env_http_headers` values and `env_vars`. Names
148
+ * only — never values.
149
+ */
150
+ export function findMissingMcpEnvVars(cfg = {}, env = process.env) {
151
+ const missing = new Set();
152
+ expandEnvVars([cfg.command, cfg.args, cfg.env, cfg.url, cfg.headers, cfg.cwd], env, missing);
153
+ const named = [
154
+ cfg.bearer_token_env_var,
155
+ ...(cfg.env_http_headers && typeof cfg.env_http_headers === 'object' && !Array.isArray(cfg.env_http_headers)
156
+ ? Object.values(cfg.env_http_headers)
157
+ : []),
158
+ ...(Array.isArray(cfg.env_vars) ? cfg.env_vars : []),
159
+ ];
160
+ for (const value of named) {
161
+ const name = typeof value === 'string' ? value.trim() : '';
162
+ if (name && envValue(env, name) == null) missing.add(name);
163
+ }
164
+ return [...missing];
165
+ }
166
+
167
+ // Windows variable names are case-insensitive.
168
+ function envValue(env, name) {
169
+ if (process.platform !== 'win32') return env?.[name];
170
+ const actual = Object.keys(env || {}).find((key) => key.toLowerCase() === name.toLowerCase());
171
+ return actual ? env[actual] : undefined;
172
+ }
173
+
174
+ // Provider tool names: at most 64 characters of [A-Za-z0-9_-] (the strictest
175
+ // limit among the providers Mixdog talks to).
176
+ const PROVIDER_TOOL_NAME_MAX = 64;
177
+ const PROVIDER_TOOL_NAME_RE = /^[A-Za-z0-9_-]+$/;
178
+ const KEPT_SERVER_PART_MAX = 40;
179
+
180
+ /**
181
+ * `mcp__<server>__<tool>` when that already fits provider limits (unchanged
182
+ * for every name that fits today). Otherwise a deterministic short form:
183
+ * the server part kept when it is safe (sanitized and cut short when not),
184
+ * the tool part sanitized and cut, and a hash of the full name appended so
185
+ * distinct tools never meet. Callers keep the original tool name alongside
186
+ * to dispatch the call.
187
+ */
188
+ export function providerSafeMcpToolName(serverName, toolName) {
189
+ const server = String(serverName || '');
190
+ const tool = String(toolName || '');
191
+ const full = `mcp__${server}__${tool}`;
192
+ if (full.length <= PROVIDER_TOOL_NAME_MAX && PROVIDER_TOOL_NAME_RE.test(full)) return full;
193
+ const hash = createHash('sha1').update(full).digest('hex').slice(0, 8);
194
+ const serverPart =
195
+ /^[A-Za-z0-9-]+(?:_[A-Za-z0-9-]+)*$/.test(server) && server.length <= KEPT_SERVER_PART_MAX
196
+ ? server
197
+ : server
198
+ .replace(/[^A-Za-z0-9-]+/g, '-')
199
+ .replace(/^-+|-+$/g, '')
200
+ .slice(0, 24)
201
+ .replace(/-+$/, '') || 'server';
202
+ const room = PROVIDER_TOOL_NAME_MAX - `mcp__${serverPart}__`.length - hash.length - 1;
203
+ const toolPart = tool
204
+ .replace(/[^A-Za-z0-9_-]+/g, '_')
205
+ .slice(0, Math.max(0, room))
206
+ .replace(/_+$/, '');
207
+ const safe = `mcp__${serverPart}__${toolPart ? `${toolPart}_` : ''}${hash}`;
208
+ // Name-only consumers in this process (display, guards) resolve it back.
209
+ rememberMcpToolIdentity(safe, server, tool);
210
+ return safe;
211
+ }
212
+
127
213
  const DEFAULT_STDIO_ENV_KEYS =
128
214
  process.platform === 'win32'
129
215
  ? [
@@ -204,8 +290,9 @@ export function resolveMcpTransportKind(cfg) {
204
290
  // (e.g. a busy editor) must not stall a tool call indefinitely. Genuinely
205
291
  // long-running tools can raise/disable it via MIXDOG_MCP_CALL_TIMEOUT_MS or a
206
292
  // per-server timeoutMs/callTimeoutMs config value (0/off/none/false disables).
207
- // On expiry we close the transport so the next dispatch reconnects fresh, but
208
- // we do not retry the timed-out call automatically (avoids side-effect dupes).
293
+ // On expiry only the timed-out request is cancelled (notifications/cancelled);
294
+ // the server and its other in-flight calls stay up, and the timed-out call is
295
+ // never retried automatically (avoids side-effect dupes).
209
296
  export function resolveMcpCallTimeoutMs(cfg = {}, env = process.env) {
210
297
  const raw =
211
298
  cfg?.timeoutMs ?? cfg?.timeout_ms ?? cfg?.callTimeoutMs ?? cfg?.call_timeout_ms ?? env?.MIXDOG_MCP_CALL_TIMEOUT_MS;
@@ -213,11 +300,42 @@ export function resolveMcpCallTimeoutMs(cfg = {}, env = process.env) {
213
300
  if (typeof raw === 'string' && /^(0|off|none|false)$/i.test(raw.trim())) return 0;
214
301
  const parsed = Number(raw);
215
302
  if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MCP_CALL_TIMEOUT_MS;
216
- return Math.round(parsed);
303
+ return Math.min(Math.round(parsed), MCP_MAX_TIMER_MS);
304
+ }
305
+
306
+ // Total cap for a call whose server keeps reporting progress. Default 30min.
307
+ // Per-server override: maxTotalTimeoutMs / max_total_timeout_ms. Global env:
308
+ // MIXDOG_MCP_MAX_TOTAL_TIMEOUT_MS. 0/off/none/false removes the cap.
309
+ export function resolveMcpMaxTotalTimeoutMs(cfg = {}, env = process.env) {
310
+ const raw = cfg?.maxTotalTimeoutMs ?? cfg?.max_total_timeout_ms ?? env?.MIXDOG_MCP_MAX_TOTAL_TIMEOUT_MS;
311
+ if (raw == null || raw === '' || raw === false) return DEFAULT_MCP_MAX_TOTAL_TIMEOUT_MS;
312
+ if (raw === 0 || (typeof raw === 'string' && /^(0|off|none|false)$/i.test(raw.trim()))) return 0;
313
+ const parsed = Number(raw);
314
+ if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MCP_MAX_TOTAL_TIMEOUT_MS;
315
+ return Math.min(Math.round(parsed), MCP_MAX_TIMER_MS);
316
+ }
317
+
318
+ /**
319
+ * The effective limits of one MCP request:
320
+ * - callTimeoutMs: our per-call timeout (0 = disabled), reset on progress;
321
+ * - sdkTimeoutMs: what the SDK gets as `timeout` — the call timeout, or the
322
+ * largest timer when disabled so the SDK's 60s default never applies;
323
+ * - maxTotalTimeoutMs: the progress-reset ceiling (never below the call
324
+ * timeout), or undefined when the call is unbounded or the cap is off.
325
+ */
326
+ export function resolveMcpRequestTimeouts(cfg = {}, env = process.env) {
327
+ const callTimeoutMs = resolveMcpCallTimeoutMs(cfg, env);
328
+ if (!(callTimeoutMs > 0)) return { callTimeoutMs: 0, sdkTimeoutMs: MCP_MAX_TIMER_MS, maxTotalTimeoutMs: undefined };
329
+ const cap = resolveMcpMaxTotalTimeoutMs(cfg, env);
330
+ return {
331
+ callTimeoutMs,
332
+ sdkTimeoutMs: callTimeoutMs,
333
+ maxTotalTimeoutMs: cap > 0 ? Math.max(cap, callTimeoutMs) : undefined,
334
+ };
217
335
  }
218
336
 
219
337
  // MCP per-server STARTUP timeout: bounds the connect + listTools handshake so a
220
- // slow or hung server can't stall boot or the first turn. Default 10s.
338
+ // slow or hung server can't stall boot or the first turn. Default 30s.
221
339
  // Per-server override: startupTimeoutMs / startupTimeoutSec. Global
222
340
  // env: MIXDOG_MCP_STARTUP_TIMEOUT_MS. A value of 0/off/none/false disables it.
223
341
  export function resolveMcpStartupTimeoutMs(cfg = {}, env = process.env) {
@@ -235,5 +353,5 @@ export function resolveMcpStartupTimeoutMs(cfg = {}, env = process.env) {
235
353
  if (raw === 0 || (typeof raw === 'string' && /^(0|off|none|false)$/i.test(raw.trim()))) return 0;
236
354
  const parsed = Number(raw) * scale;
237
355
  if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MCP_STARTUP_TIMEOUT_MS;
238
- return Math.round(parsed);
356
+ return Math.min(Math.round(parsed), MCP_MAX_TIMER_MS);
239
357
  }
@@ -4,14 +4,44 @@
4
4
  * and a discovery advert that refused the connection is distrusted.
5
5
  */
6
6
  import { markServiceUnreachable, isConnRefuseError } from '../../../shared/service-discovery.mjs';
7
- import { resolveMcpStartupTimeoutMs } from './client-config.mjs';
7
+ import { MCP_MAX_TIMER_MS, resolveMcpStartupTimeoutMs } from './client-config.mjs';
8
+ import { appendMcpStderrTail } from './client-transport.mjs';
8
9
 
9
- async function performHandshake(client, transport) {
10
- await client.connect(transport);
10
+ // tools/list pagination bounds: a server that never stops handing out cursors
11
+ // (or repeats one) cannot loop the client forever.
12
+ const MAX_MCP_TOOL_PAGES = 100;
13
+
14
+ /**
15
+ * Every tool of a server: follows `nextCursor` until it is exhausted, repeats,
16
+ * or MAX_MCP_TOOL_PAGES is reached. A page with an invalid shape is returned
17
+ * as-is so the caller's shape validation reports it.
18
+ * The SDK rebuilds its output validators from each page alone, so after a
19
+ * paginated listing they are rebuilt once from every page's tools.
20
+ */
21
+ export async function listAllMcpTools(client, requestOptions = undefined) {
22
+ const tools = [];
23
+ const seen = new Set();
24
+ let cursor = null;
25
+ let pages = 0;
26
+ for (; pages < MAX_MCP_TOOL_PAGES; pages += 1) {
27
+ const result = await client.listTools(cursor ? { cursor } : undefined, requestOptions);
28
+ if (!result || !Array.isArray(result.tools)) return result;
29
+ tools.push(...result.tools);
30
+ const next = typeof result.nextCursor === 'string' && result.nextCursor ? result.nextCursor : null;
31
+ if (!next || seen.has(next)) break;
32
+ seen.add(next);
33
+ cursor = next;
34
+ }
35
+ if (pages > 0) client.cacheToolMetadata?.(tools);
36
+ return { tools };
37
+ }
38
+
39
+ async function performHandshake(client, transport, requestOptions) {
40
+ await client.connect(transport, requestOptions);
11
41
  const instructionsRaw = typeof client.getInstructions === 'function' ? client.getInstructions() : undefined;
12
42
  const instructions = typeof instructionsRaw === 'string' ? instructionsRaw.trim() : '';
13
43
  const capabilities = client.getServerCapabilities?.() || {};
14
- const toolsResult = capabilities.tools ? await client.listTools() : { tools: [] };
44
+ const toolsResult = capabilities.tools ? await listAllMcpTools(client, requestOptions) : { tools: [] };
15
45
  return { instructions, toolsResult, capabilities };
16
46
  }
17
47
 
@@ -28,12 +58,24 @@ function startupTimeoutError(name, startupTimeoutMs) {
28
58
  * boot or the first turn. On expiry the pending transport/child is torn down
29
59
  * (nothing leaks) and this server fails like any other connect failure — the
30
60
  * parallel Promise.allSettled means other servers are unaffected.
31
- * Returns { instructions, toolsResult, capabilities }.
61
+ * The SDK's per-request timeout follows the same budget (its own 60s default
62
+ * would otherwise cut a longer or disabled budget short). Failures carry the
63
+ * server's stderr tail. Returns { instructions, toolsResult, capabilities }.
32
64
  */
33
- export async function runBoundedHandshake({ name, cfg, client, transport, autoDetectAdvert, closeServer }) {
65
+ export async function runBoundedHandshake({
66
+ name,
67
+ cfg,
68
+ client,
69
+ transport,
70
+ autoDetectAdvert,
71
+ closeServer,
72
+ stderrTail = () => '',
73
+ }) {
34
74
  const startupTimeoutMs = resolveMcpStartupTimeoutMs(cfg);
35
75
  const guard = { timer: null, timedOut: false };
36
- const handshake = performHandshake(client, transport);
76
+ const handshake = performHandshake(client, transport, {
77
+ timeout: startupTimeoutMs > 0 ? startupTimeoutMs : MCP_MAX_TIMER_MS,
78
+ });
37
79
  try {
38
80
  if (startupTimeoutMs <= 0) return await handshake;
39
81
  const deadline = new Promise((_, rej) => {
@@ -63,7 +105,12 @@ export async function runBoundedHandshake({ name, cfg, client, transport, autoDe
63
105
  // Never let the late handshake settle into an unhandled rejection.
64
106
  handshake.catch(() => {});
65
107
  }
66
- throw err;
108
+ const tail = stderrTail();
109
+ if (tail && err instanceof Error) {
110
+ err.message = appendMcpStderrTail(err.message, tail);
111
+ throw err;
112
+ }
113
+ throw tail ? new Error(appendMcpStderrTail(String(err), tail)) : err;
67
114
  } finally {
68
115
  if (guard.timer) clearTimeout(guard.timer);
69
116
  }
@@ -7,6 +7,7 @@ import { readServicePort } from '../../../shared/service-discovery.mjs';
7
7
  import {
8
8
  AUTO_DETECT_PORTS,
9
9
  expandEnvVars,
10
+ findMissingMcpEnvVars,
10
11
  mcpUrlForLog,
11
12
  normalizeMcpTransportUrl,
12
13
  resolveMcpHttpHeaders,
@@ -32,19 +33,83 @@ function resolveAutoDetectUrl(name, cfg) {
32
33
  return { url: `http://127.0.0.1:${port}${spec.endpoint}`, advert: { service: spec.discovery, port } };
33
34
  }
34
35
 
36
+ // Bounded tail of a stdio server's stderr, kept for failure messages (a server
37
+ // that dies at startup or mid-session usually says why on stderr).
38
+ const MCP_STDERR_TAIL_CHARS = 4096;
39
+ const noStderrTail = () => '';
40
+
41
+ /** Append a server's stderr tail to a failure message (unchanged when empty). */
42
+ export function appendMcpStderrTail(message, tail) {
43
+ const text = String(tail || '').trim();
44
+ return text ? `${message}\n--- server stderr (tail) ---\n${text}` : String(message);
45
+ }
46
+
35
47
  function requestInitFor(cfg) {
36
48
  const headers = resolveMcpHttpHeaders(cfg);
37
49
  return headers && Object.keys(headers).length > 0 ? { requestInit: { headers } } : undefined;
38
50
  }
39
51
 
40
- /** Returns { transport, autoDetectAdvert }. */
41
- export function createMcpTransport({ name, cfg, sdk, log }) {
52
+ // The SDK's WebSocket transport opens its socket without a way to set request
53
+ // headers. This subclass changes only socket creation: Node's WebSocket takes
54
+ // `{ protocols, headers }`; the guard and event wiring match the stock start().
55
+ function createHeaderWebSocketTransport(Base, messageSchema, url, headers) {
56
+ return new (class extends Base {
57
+ start() {
58
+ if (this._socket) {
59
+ throw new Error(
60
+ 'WebSocketClientTransport already started! If using Client class, note that connect() calls start() automatically.'
61
+ );
62
+ }
63
+ return new Promise((resolve, reject) => {
64
+ this._socket = new globalThis.WebSocket(this._url, { protocols: 'mcp', headers });
65
+ this._socket.onerror = (event) => {
66
+ const error = 'error' in event ? event.error : new Error(`WebSocket error: ${JSON.stringify(event)}`);
67
+ reject(error);
68
+ this.onerror?.(error);
69
+ };
70
+ this._socket.onopen = () => resolve();
71
+ this._socket.onclose = () => this.onclose?.();
72
+ this._socket.onmessage = (event) => {
73
+ let message;
74
+ try {
75
+ message = messageSchema.parse(JSON.parse(event.data));
76
+ } catch (error) {
77
+ this.onerror?.(error);
78
+ return;
79
+ }
80
+ this.onmessage?.(message);
81
+ };
82
+ });
83
+ }
84
+ })(url);
85
+ }
86
+ /** Returns { transport, autoDetectAdvert, stderrTail, warnings } — stderrTail()
87
+ * is the last ~4 KB of a stdio server's (scrubbed) stderr, '' for remote
88
+ * transports. `strictEnv` (the caller passes it for plugin-declared servers)
89
+ * makes unset variables without a default fail the server; otherwise they
90
+ * expand to empty and are reported in `warnings` (names only). */
91
+ export function createMcpTransport({ name, cfg, sdk, log, strictEnv }) {
42
92
  const { StdioClientTransport, StreamableHTTPClientTransport, SSEClientTransport, WebSocketClientTransport } = sdk;
43
93
  const kind = resolveMcpTransportKind(cfg);
94
+ const warnings = [];
95
+ if (kind !== 'autoDetect') {
96
+ const missing = findMissingMcpEnvVars(cfg);
97
+ if (missing.length) {
98
+ const message = `missing environment variable(s): ${missing.join(', ')}`;
99
+ if (strictEnv === true) throw new Error(message);
100
+ warnings.push(message);
101
+ log(`[mcp-client] "${name}": ${message}\n`);
102
+ }
103
+ }
44
104
  if (kind === 'autoDetect') {
45
105
  const { url, advert } = resolveAutoDetectUrl(name, cfg);
46
106
  log(`[mcp-client] Connecting "${name}" via autoDetect HTTP: ${url}\n`);
47
- return { transport: new StreamableHTTPClientTransport(new URL(url)), autoDetectAdvert: advert };
107
+ return {
108
+ transport: new StreamableHTTPClientTransport(new URL(url)),
109
+ autoDetectAdvert: advert,
110
+ stderrTail: noStderrTail,
111
+ warnings,
112
+ };
48
113
  }
49
114
  if (kind === 'http' || kind === 'sse') {
50
115
  const Transport = kind === 'http' ? StreamableHTTPClientTransport : SSEClientTransport;
@@ -54,26 +119,39 @@ export function createMcpTransport({ name, cfg, sdk, log }) {
54
119
  return {
55
120
  transport: opts ? new Transport(new URL(url), opts) : new Transport(new URL(url)),
56
121
  autoDetectAdvert: null,
122
+ stderrTail: noStderrTail,
123
+ warnings,
57
124
  };
58
125
  }
59
126
  if (kind === 'ws') {
60
- // WebSocketClientTransport ctor takes only a URL; headers are ignored.
61
127
  const url = normalizeMcpTransportUrl(expandEnvVars(String(cfg.url ?? '')), 'ws');
128
+ const opts = requestInitFor(cfg);
62
129
  log(`[mcp-client] Connecting "${name}" via WebSocket: ${mcpUrlForLog(url)}\n`);
63
- return { transport: new WebSocketClientTransport(new URL(url)), autoDetectAdvert: null };
130
+ const transport = opts
131
+ ? createHeaderWebSocketTransport(
132
+ WebSocketClientTransport,
133
+ sdk.JSONRPCMessageSchema,
134
+ new URL(url),
135
+ opts.requestInit.headers
136
+ )
137
+ : new WebSocketClientTransport(new URL(url));
138
+ return { transport, autoDetectAdvert: null, stderrTail: noStderrTail, warnings };
64
139
  }
65
140
  if (kind === 'stdio') {
66
141
  const transport = new StdioClientTransport({
67
142
  command: expandEnvVars(String(cfg.command ?? '')),
68
143
  args: Array.isArray(cfg.args) ? expandEnvVars(cfg.args) : cfg.args,
69
- cwd: cfg.cwd,
144
+ cwd: cfg.cwd == null ? cfg.cwd : expandEnvVars(String(cfg.cwd)),
70
145
  env: resolveMcpStdioEnvironment(cfg),
71
146
  stderr: cfg.stderr ?? 'pipe',
72
147
  });
148
+ let tail = '';
73
149
  transport.stderr?.on?.('data', (chunk) => {
74
- log(`[mcp:${name}:stderr] ${scrubMcpConnectionMessage(chunk, cfg)}`);
150
+ const text = scrubMcpConnectionMessage(chunk, cfg);
151
+ tail = (tail + text).slice(-MCP_STDERR_TAIL_CHARS);
152
+ log(`[mcp:${name}:stderr] ${text}`);
75
153
  });
76
- return { transport, autoDetectAdvert: null };
154
+ return { transport, autoDetectAdvert: null, stderrTail: () => tail.trim(), warnings };
77
155
  }
78
156
  throw new Error(
79
157
  `Invalid config for "${name}": need autoDetect, type (stdio/http/sse/ws), url (http), or command (stdio)`