mixdog 1.2.0 → 1.2.2

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 (386) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/src/agents/reviewer/AGENT.md +3 -3
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +5 -0
  8. package/src/defaults/skills/pptx/SKILL.md +6 -6
  9. package/src/defaults/skills/pptx/references/review.md +6 -4
  10. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  11. package/src/defaults/skills/setup/references/actions.md +13 -3
  12. package/src/defaults/skills/visualize/SKILL.md +12 -9
  13. package/src/repl/slash-commands.mjs +16 -1
  14. package/src/rules/shared/60-verification.md +3 -1
  15. package/src/rules/shared/70-delivery.md +4 -2
  16. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  17. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  18. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  19. package/src/runtime/agent/orchestrator/config.mjs +10 -4
  20. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  21. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  22. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  23. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +122 -15
  24. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +54 -11
  25. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +104 -9
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +358 -140
  27. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  28. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  29. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  30. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  31. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  36. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  37. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +294 -44
  38. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  40. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
  41. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  42. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  46. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  49. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  50. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  51. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
  53. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  54. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  55. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +53 -18
  57. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  58. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  59. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
  60. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
  61. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
  62. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
  63. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  64. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  65. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  66. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +447 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
  68. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  70. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
  71. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
  73. package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
  74. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  75. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +86 -19
  76. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
  78. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
  79. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  80. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  81. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  82. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  83. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
  84. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
  85. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
  86. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
  87. package/src/runtime/agent/orchestrator/providers/gemini.mjs +97 -51
  88. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  89. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
  90. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  91. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  92. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  94. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
  95. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
  96. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  97. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
  98. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +5 -3
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
  109. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  110. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  111. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
  115. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +26 -6
  116. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
  117. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  119. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
  120. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  125. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
  127. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  128. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
  130. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  132. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  133. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  135. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +35 -24
  136. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
  137. package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
  138. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  139. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
  140. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  141. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  142. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  143. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  146. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  147. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  148. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  149. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  150. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +268 -181
  151. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  154. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +270 -0
  156. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  157. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  158. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
  159. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  160. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  161. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  162. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  163. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  164. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  165. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  166. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  167. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
  169. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -4
  170. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
  171. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
  172. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  173. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
  174. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +42 -5
  175. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +14 -3
  176. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
  177. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +50 -23
  178. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
  179. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +22 -14
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  182. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  183. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
  184. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
  185. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  186. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  187. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  188. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  189. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  190. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +819 -0
  191. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +12 -0
  192. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +22 -12
  193. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  194. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
  195. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  196. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  197. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +41 -3
  198. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
  199. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  200. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  201. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  202. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  203. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  205. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  206. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  208. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
  209. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
  210. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  211. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  212. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  213. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  214. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  216. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  217. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  218. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  219. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  220. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  221. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  222. package/src/runtime/attachments/store.d.mts +3 -0
  223. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  224. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  225. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  226. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  227. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  228. package/src/runtime/memory/lib/memory.mjs +1 -1
  229. package/src/runtime/memory/lib/pg/process.mjs +34 -24
  230. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
  231. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  232. package/src/runtime/office/README.md +1 -1
  233. package/src/runtime/office/shared/values.mjs +0 -5
  234. package/src/runtime/shared/atomic-file.mjs +25 -4
  235. package/src/runtime/shared/background-tasks.mjs +92 -14
  236. package/src/runtime/shared/err-text.mjs +25 -0
  237. package/src/runtime/shared/error-presentation.mjs +159 -19
  238. package/src/runtime/shared/extension-scopes.mjs +5 -3
  239. package/src/runtime/shared/file-lock.mjs +37 -11
  240. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  241. package/src/runtime/shared/llm/anthropic-effort.mjs +134 -131
  242. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  243. package/src/runtime/shared/llm/model-catalog.mjs +53 -52
  244. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  245. package/src/runtime/shared/llm/provider-catalog-registry.mjs +17 -0
  246. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  247. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  248. package/src/runtime/shared/model-display.mjs +13 -0
  249. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  250. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  251. package/src/runtime/shared/pristine-execution.mjs +28 -4
  252. package/src/runtime/shared/provider-accounts.mjs +124 -20
  253. package/src/runtime/shared/provider-api-key.mjs +12 -0
  254. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  255. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  256. package/src/runtime/shared/shell-display.mjs +9 -3
  257. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  258. package/src/runtime/shared/skill-mentions.mjs +133 -0
  259. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  260. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  261. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  262. package/src/runtime/shared/tool-primitives.mjs +35 -2
  263. package/src/runtime/shared/turn-changes.mjs +447 -219
  264. package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
  265. package/src/runtime/shared/turn-snapshot.mjs +170 -48
  266. package/src/runtime/shared/turn-worktree-snapshot.mjs +160 -51
  267. package/src/runtime/shared/worker-requests.mjs +15 -2
  268. package/src/runtime/shared/worktrees/contract.mjs +0 -24
  269. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  270. package/src/session-runtime/automation-agents.mjs +8 -2
  271. package/src/session-runtime/boot/apis.mjs +61 -0
  272. package/src/session-runtime/boot/config.mjs +5 -2
  273. package/src/session-runtime/boot/facade.mjs +1 -0
  274. package/src/session-runtime/boot/infrastructure.mjs +35 -18
  275. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  276. package/src/session-runtime/conversation-import/service.mjs +369 -0
  277. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
  278. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  279. package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
  280. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  281. package/src/session-runtime/mcp-glue/connect.mjs +83 -16
  282. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  283. package/src/session-runtime/mcp-glue/server-catalog.mjs +135 -33
  284. package/src/session-runtime/mcp-glue.mjs +25 -2
  285. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  286. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  287. package/src/session-runtime/model-route/set-route.mjs +83 -15
  288. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  289. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  290. package/src/session-runtime/new-session-config.mjs +3 -2
  291. package/src/session-runtime/notification-bus.mjs +9 -2
  292. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  293. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  294. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  295. package/src/session-runtime/provider-readiness.mjs +16 -10
  296. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  297. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  298. package/src/session-runtime/resource-extension-sync.mjs +24 -5
  299. package/src/session-runtime/resource-mcp-api.mjs +198 -78
  300. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  301. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  302. package/src/session-runtime/resource-skills-api.mjs +23 -2
  303. package/src/session-runtime/runtime-review-api.mjs +13 -0
  304. package/src/session-runtime/schedule-session-run.mjs +1 -1
  305. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  306. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  307. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  308. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  310. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  311. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  312. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  313. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  314. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  315. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  316. package/src/session-runtime/services/provider-admin.mjs +64 -18
  317. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  318. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  319. package/src/session-runtime/setup-tool/executor.mjs +12 -1
  320. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  321. package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
  322. package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
  323. package/src/session-runtime/skills-api.mjs +7 -3
  324. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  325. package/src/session-runtime/webhook-session-run.mjs +16 -4
  326. package/src/skill-script-entry.mjs +36 -0
  327. package/src/standalone/daemon-drain-policy.mjs +8 -0
  328. package/src/standalone/daemon-stored-session-views.mjs +11 -3
  329. package/src/standalone/daemon-telemetry.mjs +55 -0
  330. package/src/standalone/daemon.mjs +44 -11
  331. package/src/standalone/session-protocol.mjs +12 -0
  332. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  333. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  334. package/src/standalone/session-service/agent-tree.mjs +12 -0
  335. package/src/standalone/session-service/quota-resume.mjs +8 -0
  336. package/src/standalone/session-service/retention/busy.mjs +15 -1
  337. package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
  338. package/src/standalone/session-service-api.mjs +5 -0
  339. package/src/standalone/session-service.mjs +1 -0
  340. package/src/tui/App.jsx +9 -1
  341. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  342. package/src/tui/app/app-view.jsx +7 -0
  343. package/src/tui/app/create-app-pickers.mjs +4 -0
  344. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  345. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  346. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  347. package/src/tui/app/extension-pickers.mjs +2 -1
  348. package/src/tui/app/model-options.mjs +13 -16
  349. package/src/tui/app/skill-mention-palette.mjs +88 -0
  350. package/src/tui/app/slash-commands.mjs +7 -2
  351. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  352. package/src/tui/app/slash-palette.mjs +23 -7
  353. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  354. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  355. package/src/tui/app/use-prompt-state.mjs +5 -0
  356. package/src/tui/components/Message.jsx +22 -1
  357. package/src/tui/components/PromptInput.jsx +21 -3
  358. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  359. package/src/tui/components/StatusLine.jsx +2 -0
  360. package/src/tui/components/TranscriptItem.jsx +9 -1
  361. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  362. package/src/tui/components/status-line/local-line.mjs +5 -2
  363. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  364. package/src/tui/components/user-message-skills.mjs +38 -0
  365. package/src/tui/dist/index.mjs +1323 -214
  366. package/src/tui/session/queue-helpers.mjs +7 -2
  367. package/src/tui/session/quota-resume.mjs +46 -14
  368. package/src/tui/session/session-api/commands.mjs +33 -24
  369. package/src/tui/session/session-api/extensions.mjs +26 -2
  370. package/src/tui/session/session-api/integrations.mjs +7 -0
  371. package/src/tui/session/session-api/lifecycle.mjs +5 -1
  372. package/src/tui/session/session-api/routes.mjs +71 -7
  373. package/src/tui/session/session-api/settings.mjs +12 -14
  374. package/src/tui/session/session-flow/drain.mjs +21 -5
  375. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  376. package/src/tui/session/tui-steering-persist.mjs +25 -9
  377. package/src/tui/session/turn.mjs +55 -15
  378. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  379. package/src/ui/statusline-agents.mjs +15 -4
  380. package/src/ui/statusline.mjs +7 -3
  381. package/src/ui/streaming-markdown-heal.mjs +34 -3
  382. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  383. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  384. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
  385. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  386. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -1,4 +1,5 @@
1
1
  // Deferred-pool and MCP-instruction manifests injected into BP2.
2
+ import { parseMcpToolName } from '../../../shared/tool-primitives.mjs';
2
3
 
3
4
  export function compactPromptManifestText(value, max = 250) {
4
5
  const text = String(value || '')
@@ -100,16 +101,21 @@ function sanitizeMcpInstructionText(text, max = MCP_INSTRUCTION_MAX_CHARS) {
100
101
  * names per server doubled the MCP share of the BP2 prefix (2026-08-05 audit).
101
102
  * Server membership stays evident from the mcp__<server>__ name prefix.
102
103
  */
103
- function buildMcpInstructionsManifest(mcpServerInstructions, poolNames) {
104
+ function buildMcpInstructionsManifest(mcpServerInstructions, poolNames, catalog = []) {
104
105
  const map = mcpServerInstructions && typeof mcpServerInstructions === 'object' ? mcpServerInstructions : {};
105
106
  const pool = [
106
107
  ...new Set((Array.isArray(poolNames) ? poolNames : []).map((name) => sanitizeManifestName(name)).filter(Boolean)),
107
108
  ];
109
+ // Tool definitions carry their server (shortened names cannot be parsed).
110
+ const byName = new Map();
111
+ for (const tool of Array.isArray(catalog) ? catalog : []) {
112
+ const name = String(tool?.name || '').trim();
113
+ if (name && !byName.has(name)) byName.set(name, tool);
114
+ }
108
115
  const deferredServers = new Set();
109
116
  for (const name of pool) {
110
- const match = name.match(/^mcp__(.+?)__(.+)$/);
111
- if (!match) continue;
112
- deferredServers.add(match[1]);
117
+ const server = parseMcpToolName(byName.get(name) || name)?.server;
118
+ if (server) deferredServers.add(server);
113
119
  }
114
120
  const servers = [...deferredServers]
115
121
  .filter((server) => sanitizeManifestName(server) && sanitizeMcpInstructionText(map[server]))
@@ -170,7 +176,7 @@ function composeDeferredBp2Manifest(session, pool) {
170
176
  const parts = [];
171
177
  const deferredManifest = buildDeferredToolManifest(deferredManifestEntries(session, pool));
172
178
  if (deferredManifest) parts.push(deferredManifest);
173
- const mcpManifest = buildMcpInstructionsManifest(session.mcpServerInstructions, pool);
179
+ const mcpManifest = buildMcpInstructionsManifest(session.mcpServerInstructions, pool, session.deferredToolCatalog);
174
180
  if (mcpManifest) parts.push(mcpManifest);
175
181
  return parts.join('\n\n');
176
182
  }
@@ -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.
@@ -77,20 +79,41 @@ export function mcpUrlForLog(raw) {
77
79
 
78
80
  function replaceSecret(text, value) {
79
81
  const secret = String(value || '');
80
- return secret.length >= 4 ? text.split(secret).join('[redacted]') : text;
82
+ return secret ? text.split(secret).join('[redacted]') : text;
81
83
  }
82
84
 
83
- export function scrubMcpConnectionMessage(value, cfg = {}) {
84
- let text = String(value || '');
85
+ // Configured URL parts and header values shorter than 4 characters are left
86
+ // alone: redacting e.g. the `1` of `?v=1` would mangle every message. Known
87
+ // connection secrets (OAuth tokens) are redacted at any length.
88
+ function replaceConfiguredSecret(text, value) {
89
+ return String(value || '').length >= 4 ? replaceSecret(text, value) : text;
90
+ }
91
+
92
+ /** `value` with every one of `secrets` redacted from its strings, recursing
93
+ * into arrays and plain objects (tool results, JSON-RPC error data). */
94
+ export function scrubMcpSecrets(value, secrets) {
95
+ if (secrets.length === 0) return value;
96
+ if (typeof value === 'string') return secrets.reduce(replaceSecret, value);
97
+ if (Array.isArray(value)) return value.map((item) => scrubMcpSecrets(item, secrets));
98
+ const proto = value && typeof value === 'object' ? Object.getPrototypeOf(value) : undefined;
99
+ if (proto !== Object.prototype && proto !== null) return value;
100
+ return Object.fromEntries(
101
+ Object.entries(value).map(([key, item]) => [scrubMcpSecrets(key, secrets), scrubMcpSecrets(item, secrets)])
102
+ );
103
+ }
104
+
105
+ /** `secrets` are values known only to the live connection (its OAuth tokens). */
106
+ export function scrubMcpConnectionMessage(value, cfg = {}, secrets = []) {
107
+ let text = scrubMcpSecrets(String(value || ''), secrets);
85
108
  const rawUrl = expandEnvVars(String(cfg?.url || ''));
86
109
  if (rawUrl) {
87
110
  text = text.split(rawUrl).join(mcpUrlForLog(rawUrl));
88
111
  try {
89
112
  const parsed = new URL(rawUrl);
90
- text = replaceSecret(text, parsed.username);
91
- text = replaceSecret(text, parsed.password);
113
+ text = replaceConfiguredSecret(text, parsed.username);
114
+ text = replaceConfiguredSecret(text, parsed.password);
92
115
  for (const paramValue of parsed.searchParams.values()) {
93
- text = replaceSecret(text, paramValue);
116
+ text = replaceConfiguredSecret(text, paramValue);
94
117
  }
95
118
  } catch {
96
119
  /* invalid URL is reported without echoing it */
@@ -99,7 +122,7 @@ export function scrubMcpConnectionMessage(value, cfg = {}) {
99
122
  const headers = resolveMcpHttpHeaders(cfg);
100
123
  for (const [name, headerValue] of Object.entries(headers)) {
101
124
  if (/authorization|cookie|token|secret|api[-_]?key|signature/i.test(name)) {
102
- text = replaceSecret(text, headerValue);
125
+ text = replaceConfiguredSecret(text, headerValue);
103
126
  }
104
127
  }
105
128
  return text
@@ -108,30 +131,106 @@ export function scrubMcpConnectionMessage(value, cfg = {}) {
108
131
  }
109
132
 
110
133
  /**
111
- * Expand `${VAR}` and `${env:VAR}` references in string values using the
112
- * provided env map (defaults to process.env). Recurses into arrays/objects.
113
- * Unknown vars expand to an empty string. No shell execution.
134
+ * Expand `${VAR}`, `${env:VAR}` and `${VAR:-default}` references in string
135
+ * values using the provided env map (defaults to process.env). Recurses into
136
+ * arrays/objects. The default (possibly empty) applies only when the variable
137
+ * is unset, and runs to the first `}`. An unset variable without a default
138
+ * expands to an empty string and, when `missing` (a Set) is given, its name is
139
+ * recorded there. No shell execution.
114
140
  */
115
- export function expandEnvVars(value, env = process.env) {
141
+ export function expandEnvVars(value, env = process.env, missing) {
116
142
  if (typeof value === 'string') {
117
- return value.replace(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) => {
143
+ return value.replace(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (_m, name, fallback) => {
118
144
  const v = env?.[name];
119
- return v == null ? '' : String(v);
145
+ if (v != null) return String(v);
146
+ if (fallback !== undefined) return fallback;
147
+ missing?.add(name);
148
+ return '';
120
149
  });
121
150
  }
122
151
  if (Array.isArray(value)) {
123
- return value.map((v) => expandEnvVars(v, env));
152
+ return value.map((v) => expandEnvVars(v, env, missing));
124
153
  }
125
154
  if (value && typeof value === 'object') {
126
155
  const out = {};
127
156
  for (const [k, v] of Object.entries(value)) {
128
- out[k] = expandEnvVars(v, env);
157
+ out[k] = expandEnvVars(v, env, missing);
129
158
  }
130
159
  return out;
131
160
  }
132
161
  return value;
133
162
  }
134
163
 
164
+ /**
165
+ * Names (deduplicated, first-seen order) of variables a server config needs
166
+ * that are unset: `${VAR}` references without a default in command, args,
167
+ * env, url, headers and cwd, plus the variables named directly by
168
+ * `bearer_token_env_var`, `env_http_headers` values and `env_vars`. Names
169
+ * only — never values.
170
+ */
171
+ export function findMissingMcpEnvVars(cfg = {}, env = process.env) {
172
+ const missing = new Set();
173
+ expandEnvVars([cfg.command, cfg.args, cfg.env, cfg.url, cfg.headers, cfg.cwd], env, missing);
174
+ const named = [
175
+ cfg.bearer_token_env_var,
176
+ ...(cfg.env_http_headers && typeof cfg.env_http_headers === 'object' && !Array.isArray(cfg.env_http_headers)
177
+ ? Object.values(cfg.env_http_headers)
178
+ : []),
179
+ ...(Array.isArray(cfg.env_vars) ? cfg.env_vars : []),
180
+ ];
181
+ for (const value of named) {
182
+ const name = typeof value === 'string' ? value.trim() : '';
183
+ if (name && envValue(env, name) == null) missing.add(name);
184
+ }
185
+ return [...missing];
186
+ }
187
+
188
+ // Windows variable names are case-insensitive.
189
+ function envValue(env, name) {
190
+ if (process.platform !== 'win32') return env?.[name];
191
+ const actual = Object.keys(env || {}).find((key) => key.toLowerCase() === name.toLowerCase());
192
+ return actual ? env[actual] : undefined;
193
+ }
194
+
195
+ // Provider tool names: at most 64 characters of [A-Za-z0-9_-] (the strictest
196
+ // limit among the providers Mixdog talks to).
197
+ const PROVIDER_TOOL_NAME_MAX = 64;
198
+ const PROVIDER_TOOL_NAME_RE = /^[A-Za-z0-9_-]+$/;
199
+ const KEPT_SERVER_PART_MAX = 40;
200
+
201
+ /**
202
+ * `mcp__<server>__<tool>` when that already fits provider limits (unchanged
203
+ * for every name that fits today). Otherwise a deterministic short form:
204
+ * the server part kept when it is safe (sanitized and cut short when not),
205
+ * the tool part sanitized and cut, and a hash of the full name appended so
206
+ * distinct tools never meet. Callers keep the original tool name alongside
207
+ * to dispatch the call.
208
+ */
209
+ export function providerSafeMcpToolName(serverName, toolName) {
210
+ const server = String(serverName || '');
211
+ const tool = String(toolName || '');
212
+ const full = `mcp__${server}__${tool}`;
213
+ if (full.length <= PROVIDER_TOOL_NAME_MAX && PROVIDER_TOOL_NAME_RE.test(full)) return full;
214
+ const hash = createHash('sha1').update(full).digest('hex').slice(0, 8);
215
+ const serverPart =
216
+ /^[A-Za-z0-9-]+(?:_[A-Za-z0-9-]+)*$/.test(server) && server.length <= KEPT_SERVER_PART_MAX
217
+ ? server
218
+ : server
219
+ .replace(/[^A-Za-z0-9-]+/g, '-')
220
+ .replace(/^-+|-+$/g, '')
221
+ .slice(0, 24)
222
+ .replace(/-+$/, '') || 'server';
223
+ const room = PROVIDER_TOOL_NAME_MAX - `mcp__${serverPart}__`.length - hash.length - 1;
224
+ const toolPart = tool
225
+ .replace(/[^A-Za-z0-9_-]+/g, '_')
226
+ .slice(0, Math.max(0, room))
227
+ .replace(/_+$/, '');
228
+ const safe = `mcp__${serverPart}__${toolPart ? `${toolPart}_` : ''}${hash}`;
229
+ // Name-only consumers in this process (display, guards) resolve it back.
230
+ rememberMcpToolIdentity(safe, server, tool);
231
+ return safe;
232
+ }
233
+
135
234
  const DEFAULT_STDIO_ENV_KEYS =
136
235
  process.platform === 'win32'
137
236
  ? [
@@ -208,6 +307,14 @@ export function resolveMcpTransportKind(cfg) {
208
307
  throw new Error(`Invalid config: need autoDetect, type (stdio/http/sse/ws), url (http), or command (stdio)`);
209
308
  }
210
309
 
310
+ /** The exact URL OAuth credentials of an HTTP/SSE server config are stored
311
+ * under; null for other transports. */
312
+ export function resolveMcpOAuthUrl(cfg) {
313
+ const kind = resolveMcpTransportKind(cfg);
314
+ if (kind !== 'http' && kind !== 'sse') return null;
315
+ return normalizeMcpTransportUrl(expandEnvVars(String(cfg.url ?? '')), kind);
316
+ }
317
+
211
318
  // MCP per-tool-call timeout. Default 2min: a hung/unresponsive MCP server
212
319
  // (e.g. a busy editor) must not stall a tool call indefinitely. Genuinely
213
320
  // long-running tools can raise/disable it via MIXDOG_MCP_CALL_TIMEOUT_MS or a
@@ -6,6 +6,7 @@
6
6
  import { markServiceUnreachable, isConnRefuseError } from '../../../shared/service-discovery.mjs';
7
7
  import { MCP_MAX_TIMER_MS, resolveMcpStartupTimeoutMs } from './client-config.mjs';
8
8
  import { appendMcpStderrTail } from './client-transport.mjs';
9
+ import { MCP_OAUTH_TIMEOUT_MS } from './oauth.mjs';
9
10
 
10
11
  // tools/list pagination bounds: a server that never stops handing out cursors
11
12
  // (or repeats one) cannot loop the client forever.
@@ -15,12 +16,15 @@ const MAX_MCP_TOOL_PAGES = 100;
15
16
  * Every tool of a server: follows `nextCursor` until it is exhausted, repeats,
16
17
  * or MAX_MCP_TOOL_PAGES is reached. A page with an invalid shape is returned
17
18
  * as-is so the caller's shape validation reports it.
19
+ * The SDK rebuilds its output validators from each page alone, so after a
20
+ * paginated listing they are rebuilt once from every page's tools.
18
21
  */
19
22
  export async function listAllMcpTools(client, requestOptions = undefined) {
20
23
  const tools = [];
21
24
  const seen = new Set();
22
25
  let cursor = null;
23
- for (let page = 0; page < MAX_MCP_TOOL_PAGES; page += 1) {
26
+ let pages = 0;
27
+ for (; pages < MAX_MCP_TOOL_PAGES; pages += 1) {
24
28
  const result = await client.listTools(cursor ? { cursor } : undefined, requestOptions);
25
29
  if (!result || !Array.isArray(result.tools)) return result;
26
30
  tools.push(...result.tools);
@@ -29,6 +33,7 @@ export async function listAllMcpTools(client, requestOptions = undefined) {
29
33
  seen.add(next);
30
34
  cursor = next;
31
35
  }
36
+ if (pages > 0) client.cacheToolMetadata?.(tools);
32
37
  return { tools };
33
38
  }
34
39
 
@@ -49,6 +54,15 @@ function startupTimeoutError(name, startupTimeoutMs) {
49
54
  return err;
50
55
  }
51
56
 
57
+ const oauthLoginError = () => new Error('MCP OAuth login failed, cancelled, or timed out');
58
+
59
+ /** Code of a connect cancelled (login cancel, disconnect, shutdown) before any
60
+ * interactive login started. */
61
+ export const MCP_CONNECT_CANCELLED = 'EMCPCONNECTCANCELLED';
62
+ function connectCancelledError(name) {
63
+ return Object.assign(new Error(`MCP server "${name}" connect cancelled`), { code: MCP_CONNECT_CANCELLED });
64
+ }
65
+
52
66
  /**
53
67
  * Bound the connect + listTools handshake so a slow/hung server can't stall
54
68
  * boot or the first turn. On expiry the pending transport/child is torn down
@@ -69,17 +83,43 @@ export async function runBoundedHandshake({
69
83
  }) {
70
84
  const startupTimeoutMs = resolveMcpStartupTimeoutMs(cfg);
71
85
  const guard = { timer: null, timedOut: false };
86
+ let rejectDeadline;
87
+ // True once an interactive login actually started (the server challenged
88
+ // and a browser sign-in was allowed).
89
+ let loginStarted = false;
90
+ const armDeadline = (ms, error = () => startupTimeoutError(name, ms)) => {
91
+ if (guard.timer) clearTimeout(guard.timer);
92
+ guard.timer = setTimeout(() => {
93
+ guard.timedOut = true;
94
+ rejectDeadline(error());
95
+ }, ms);
96
+ };
97
+ const deadline = new Promise((_, reject) => {
98
+ rejectDeadline = reject;
99
+ });
100
+ // A closed OAuth connection (login cancel, disconnect, shutdown) settles a
101
+ // handshake that would otherwise wait forever (an SSE stream closed while
102
+ // starting never settles). The SDK also closes the transport — and with it
103
+ // the OAuth connection — right before rethrowing a connect failure of its
104
+ // own (e.g. connection refused): deferring past the pending microtasks lets
105
+ // that real failure settle the race first.
106
+ const cancelOAuth = () =>
107
+ setImmediate(() => rejectDeadline(loginStarted ? oauthLoginError() : connectCancelledError(name)));
108
+ transport.oauth?.signal.addEventListener('abort', cancelOAuth, { once: true });
109
+ if (transport.oauth?.signal.aborted) cancelOAuth();
110
+ if (startupTimeoutMs > 0) armDeadline(startupTimeoutMs);
111
+ // Human authorization gets a separate, finite budget. The ordinary startup
112
+ // budget still applies until the server actually challenges authentication.
113
+ if (transport.oauth) {
114
+ transport.oauth.onStart = () => {
115
+ loginStarted = true;
116
+ armDeadline(MCP_OAUTH_TIMEOUT_MS, oauthLoginError);
117
+ };
118
+ }
72
119
  const handshake = performHandshake(client, transport, {
73
- timeout: startupTimeoutMs > 0 ? startupTimeoutMs : MCP_MAX_TIMER_MS,
120
+ timeout: !transport.oauth && startupTimeoutMs > 0 ? startupTimeoutMs : MCP_MAX_TIMER_MS,
74
121
  });
75
122
  try {
76
- if (startupTimeoutMs <= 0) return await handshake;
77
- const deadline = new Promise((_, rej) => {
78
- guard.timer = setTimeout(() => {
79
- guard.timedOut = true;
80
- rej(startupTimeoutError(name, startupTimeoutMs));
81
- }, startupTimeoutMs);
82
- });
83
123
  return await Promise.race([handshake, deadline]);
84
124
  } catch (err) {
85
125
  // A discovery-advert port that fails to connect is a corpse (recycled
@@ -88,7 +128,7 @@ export async function runBoundedHandshake({
88
128
  // ONLY — a startup/handshake timeout is a slow-but-alive server.
89
129
  if (autoDetectAdvert && isConnRefuseError(err))
90
130
  markServiceUnreachable(autoDetectAdvert.service, autoDetectAdvert.port);
91
- if (guard.timedOut) {
131
+ if (guard.timedOut || transport.oauth?.signal.aborted) {
92
132
  // Tear down the pending transport/child so a hung handshake never
93
133
  // leaks a stdio process or socket — fire-and-forget (like the
94
134
  // tool-call timeout path) so a slow tree-kill never delays this
@@ -96,7 +136,7 @@ export async function runBoundedHandshake({
96
136
  try {
97
137
  closeServer({ client, transport }).catch(() => {});
98
138
  } catch {
99
- /* ignore */
139
+ // Teardown is best effort: the handshake failure below is what gets reported.
100
140
  }
101
141
  // Never let the late handshake settle into an unhandled rejection.
102
142
  handshake.catch(() => {});
@@ -108,6 +148,9 @@ export async function runBoundedHandshake({
108
148
  }
109
149
  throw tail ? new Error(appendMcpStderrTail(String(err), tail)) : err;
110
150
  } finally {
151
+ transport.oauth?.finishConnect();
111
152
  if (guard.timer) clearTimeout(guard.timer);
153
+ transport.oauth?.signal.removeEventListener('abort', cancelOAuth);
154
+ if (transport.oauth) transport.oauth.onStart = () => {};
112
155
  }
113
156
  }