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
@@ -22,9 +22,24 @@ import { createKeyedSingleflight } from './reconnect-singleflight.mjs';
22
22
  import { createOwnerFairGate } from '../../../shared/owner-fair-gate.mjs';
23
23
  import { currentToolExecutionOwner } from '../../../shared/tool-execution-owner.mjs';
24
24
  import { positiveInt } from '../../../shared/numbers.mjs';
25
- import { resolveMcpRequestTimeouts, resolveMcpTransportKind, scrubMcpConnectionMessage } from './client-config.mjs';
26
- import { appendMcpStderrTail, createMcpTransport } from './client-transport.mjs';
27
- import { listAllMcpTools, runBoundedHandshake } from './client-handshake.mjs';
25
+ import {
26
+ providerSafeMcpToolName,
27
+ resolveMcpOAuthUrl,
28
+ resolveMcpRequestTimeouts,
29
+ resolveMcpTransportKind,
30
+ scrubMcpConnectionMessage,
31
+ scrubMcpSecrets,
32
+ } from './client-config.mjs';
33
+ import { createLazyOutputValidator } from './lazy-output-validator.mjs';
34
+ import { appendMcpStderrTail, createMcpTransport, hasConfiguredMcpAuth } from './client-transport.mjs';
35
+ import { listAllMcpTools, MCP_CONNECT_CANCELLED, runBoundedHandshake } from './client-handshake.mjs';
36
+ import {
37
+ clearMcpOAuth,
38
+ hasStoredMcpOAuthTokens,
39
+ isMcpOAuthReauthenticationRequired,
40
+ MCP_OAUTH_CALLBACK_NOT_PENDING,
41
+ } from './oauth.mjs';
42
+ export { probeMcpServerAuth } from './oauth-probe.mjs';
28
43
 
29
44
  // Config interpretation (transport kind, URL policy, env expansion, headers,
30
45
  // timeouts, log scrubbing) lives in client-config.mjs; re-exported so every
@@ -50,6 +65,8 @@ const MCP_TOOL_NAME_RE = /^mcp__(.+?)__(.+)$/;
50
65
  const _knownMcpScopes = new Set([DEFAULT_MCP_SCOPE_ID]);
51
66
  const _connectAbortGenerations = new Map();
52
67
  const _pendingConnects = new Set();
68
+ // scopeId -> root path advertised by that scope's scope-following servers.
69
+ const _scopeRoots = new Map();
53
70
  let mcpSdkPromise = null;
54
71
  // Memo for mcpToolHasField(name, field) — keyed by `${scopeId}|${toolName}|${field}`.
55
72
  // The lookup (regex parse + servers Map get + tools.find + schema property
@@ -58,8 +75,14 @@ let mcpSdkPromise = null;
58
75
  // mutation point (connectServer / disconnectAll) so a stale positive or
59
76
  // negative can never survive a tools-list change.
60
77
  const _mcpToolFieldMemo = new Map();
78
+ let _mcpToolGeneration = 0;
61
79
  function _invalidateMcpToolFieldMemo() {
62
80
  _mcpToolFieldMemo.clear();
81
+ _mcpToolGeneration += 1;
82
+ }
83
+ /** Read-only counter bumped on every MCP tool registry mutation. */
84
+ export function getMcpToolGeneration() {
85
+ return _mcpToolGeneration;
63
86
  }
64
87
  function normalizeMcpScopeId(value) {
65
88
  const raw = value && typeof value === 'object' ? value.scopeId : value;
@@ -89,6 +112,20 @@ function mcpLog(line) {
89
112
  if (process.env.MIXDOG_QUIET_MCP_LOG) return;
90
113
  process.stderr.write(line);
91
114
  }
115
+ // OAuth tokens belong to the live connection, not to its config: a server may
116
+ // echo the request (Authorization included) into an error body or result.
117
+ function oauthSecrets(...transports) {
118
+ return transports.flatMap((transport) => transport?.oauth?.secrets() || []);
119
+ }
120
+ function scrubMcpOAuthError(error, cfg, secrets) {
121
+ if (secrets.length === 0) return error;
122
+ if (!(error instanceof Error)) return new Error(scrubMcpConnectionMessage(String(error), cfg, secrets));
123
+ error.message = scrubMcpConnectionMessage(error.message, cfg, secrets);
124
+ if (typeof error.stack === 'string') error.stack = scrubMcpConnectionMessage(error.stack, cfg, secrets);
125
+ // A JSON-RPC error carries the server's own payload (McpError.data).
126
+ if (error.data !== undefined) error.data = scrubMcpSecrets(error.data, secrets);
127
+ return error;
128
+ }
92
129
 
93
130
  async function loadMcpSdk() {
94
131
  mcpSdkPromise ??= Promise.all([
@@ -98,12 +135,15 @@ async function loadMcpSdk() {
98
135
  import('@modelcontextprotocol/sdk/client/sse.js'),
99
136
  import('@modelcontextprotocol/sdk/client/websocket.js'),
100
137
  import('@modelcontextprotocol/sdk/types.js'),
101
- ]).then(([clientMod, stdioMod, httpMod, sseMod, wsMod, typesMod]) => ({
138
+ import('@modelcontextprotocol/sdk/validation/ajv'),
139
+ ]).then(([clientMod, stdioMod, httpMod, sseMod, wsMod, typesMod, ajvMod]) => ({
102
140
  Client: clientMod.Client,
141
+ AjvJsonSchemaValidator: ajvMod.AjvJsonSchemaValidator,
103
142
  StdioClientTransport: stdioMod.StdioClientTransport,
104
143
  StreamableHTTPClientTransport: httpMod.StreamableHTTPClientTransport,
105
144
  SSEClientTransport: sseMod.SSEClientTransport,
106
145
  WebSocketClientTransport: wsMod.WebSocketClientTransport,
146
+ JSONRPCMessageSchema: typesMod.JSONRPCMessageSchema,
107
147
  ListRootsRequestSchema: typesMod.ListRootsRequestSchema,
108
148
  ToolListChangedNotificationSchema: typesMod.ToolListChangedNotificationSchema,
109
149
  }));
@@ -131,14 +171,16 @@ export async function connectMcpServers(config, options = {}) {
131
171
  });
132
172
  // Connect all servers in PARALLEL: a slow/hung server (bounded by its
133
173
  // per-server startup timeout) must never delay the others' handshakes.
134
- const settled = await Promise.allSettled(entries.map(([name, cfg]) => connectServer(name, cfg, scopeId, genAtStart)));
174
+ const settled = await Promise.allSettled(
175
+ entries.map(([name, cfg]) => connectServer(name, cfg, scopeId, genAtStart, options))
176
+ );
135
177
  settled.forEach((res, i) => {
136
178
  if (res.status !== 'rejected') return;
137
179
  const [name, cfg] = entries[i];
138
180
  const rawMessage = res.reason instanceof Error ? res.reason.message : String(res.reason);
139
181
  const msg = scrubMcpConnectionMessage(rawMessage, cfg);
140
182
  mcpLog(`[mcp-client] Failed to connect "${name}": ${msg}\n`);
141
- failures.push({ name, msg });
183
+ failures.push({ name, msg, ...(res.reason?.authRequired ? { authRequired: true } : {}) });
142
184
  });
143
185
  if (failures.length > 0) {
144
186
  const detail = failures.map((f) => `${f.name}: ${f.msg}`).join('; ');
@@ -163,8 +205,8 @@ function mcpFeatureTools(serverName, capabilities, protocolTools) {
163
205
  const tools = [];
164
206
  const used = new Set(protocolTools.map((tool) => tool.name));
165
207
  const add = (leaf, description, properties, required, operation) => {
166
- let name = `mcp__${serverName}__${leaf}`;
167
- while (used.has(name)) name += '_';
208
+ let name = providerSafeMcpToolName(serverName, leaf);
209
+ for (let suffix = '_'; used.has(name); suffix += '_') name = providerSafeMcpToolName(serverName, leaf + suffix);
168
210
  used.add(name);
169
211
  tools.push({
170
212
  name,
@@ -238,6 +280,7 @@ export function getMcpServerStatus(scopeId = DEFAULT_MCP_SCOPE_ID) {
238
280
  ),
239
281
  }
240
282
  : {}),
283
+ warnings: Array.isArray(server.warnings) ? server.warnings : [],
241
284
  toolCount: Array.isArray(server.tools) ? server.tools.length : 0,
242
285
  tools: (server.tools || []).map((tool) => ({
243
286
  name: tool.name,
@@ -312,7 +355,7 @@ export function getMcpServerInstructionsMap(scopeId = DEFAULT_MCP_SCOPE_ID) {
312
355
  // shutdown generation captured BEFORE the caller queued/closed anything: a
313
356
  // disconnectAll() landing during the close (or the connect) bumps it, and the
314
357
  // replacement must then never register (no resurrection after shutdown).
315
- async function replaceServerConnection(scopeId, name, current, cfg, genAtStart) {
358
+ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart, options = {}) {
316
359
  const registryKey = mcpServerRegistryKey(scopeId, name);
317
360
  if (current) {
318
361
  await _closeServer(current);
@@ -325,10 +368,12 @@ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart)
325
368
  try {
326
369
  // connectServer re-checks the same generation after loading the SDK and
327
370
  // again right before registering, so a shutdown mid-handshake also aborts.
328
- await connectServer(name, cfg, scopeId, genAtStart);
371
+ await connectServer(name, cfg, scopeId, genAtStart, options);
329
372
  } catch (reason) {
330
373
  const rawMessage = reason instanceof Error ? reason.message : String(reason);
331
- throw new Error(scrubMcpConnectionMessage(rawMessage, cfg));
374
+ const error = new Error(scrubMcpConnectionMessage(rawMessage, cfg));
375
+ if (reason?.authRequired) error.authRequired = true;
376
+ throw error;
332
377
  }
333
378
  }
334
379
 
@@ -338,6 +383,7 @@ async function replaceServerConnection(scopeId, name, current, cfg, genAtStart)
338
383
  // connect per server is ever running (no doubly registered transports).
339
384
  async function runExclusiveServerOp(registryKey, task) {
340
385
  for (let pending = reconnects.wait(registryKey); pending; pending = reconnects.wait(registryKey)) {
386
+ // Only waiting for the reconnect to settle; its failure belongs to its caller.
341
387
  await pending.catch(() => {});
342
388
  }
343
389
  return reconnects.run(registryKey, task);
@@ -378,7 +424,7 @@ async function reconnectMcpServer(scopeId, serverName, failedServer) {
378
424
  * rejects (credential-scrubbed message) when the connect fails, leaving the
379
425
  * server unregistered.
380
426
  */
381
- export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
427
+ export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined, options = {}) {
382
428
  const normalized = normalizeMcpScopeId(scopeId);
383
429
  _knownMcpScopes.add(normalized);
384
430
  const registryKey = mcpServerRegistryKey(normalized, name);
@@ -397,7 +443,7 @@ export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
397
443
  closeCallAdmission(normalized, name, `MCP ${name} disabled`);
398
444
  return false;
399
445
  }
400
- await replaceServerConnection(normalized, name, current, nextCfg, genAtStart);
446
+ await replaceServerConnection(normalized, name, current, nextCfg, genAtStart, options);
401
447
  return true;
402
448
  });
403
449
  }
@@ -409,106 +455,112 @@ export async function reconnectSingleMcpServer(scopeId, name, cfg = undefined) {
409
455
  export async function executeMcpTool(name, args, options = {}) {
410
456
  const match = name.match(MCP_TOOL_NAME_RE);
411
457
  if (!match) throw new Error(`Not an MCP tool name: ${name}`);
412
- const [, serverName, toolName] = match;
413
458
  const scopeId = normalizeMcpScopeId(options);
414
- const server = scopedServer(scopeId, serverName);
415
- if (!server) throw new Error(`MCP server "${serverName}" not connected`);
416
- const definition = (server.tools || []).find((tool) => tool.name === name);
459
+ const target = resolveMcpToolTarget(scopeId, name);
460
+ if (!target) throw new Error(`MCP server "${match[1]}" not connected`);
461
+ const { server, definition } = target;
462
+ const serverName = server.name;
463
+ const toolName = definition?.mcpToolName || match[2];
417
464
  const callSignal = options?.signal || null;
418
465
  const dispatch = (target) =>
419
466
  definition?.mcpOperation
420
467
  ? _callMcpFeatureWithTimeout(target, definition.mcpOperation, args, callSignal)
421
468
  : _callToolWithTimeout(target, toolName, args, callSignal);
469
+ let retryServer = null;
470
+ const secrets = () => oauthSecrets(server.transport, retryServer?.transport);
422
471
  const gate = callAdmissionFor(server);
423
- return gate.run(
424
- options?.ownerKey || currentToolExecutionOwner(),
425
- async () => {
426
- // The gate stops forwarding the caller's abort once this task is
427
- // admitted, so the task itself observes the signal for its whole run.
428
- if (callSignal?.aborted) {
429
- throw mcpAbortError(
430
- callSignal,
431
- `MCP tool call aborted before dispatch (server="${serverName}", tool="${toolName}")`
432
- );
433
- }
434
- let result;
435
- try {
436
- result = await dispatch(server);
437
- } catch (firstErr) {
438
- const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
439
- if (isMcpCallAbortError(firstErr) || callSignal?.aborted) {
440
- // A cancelled call is not a transport failure: reconnecting and
441
- // replaying it would duplicate the side effect the caller just
442
- // walked away from.
443
- mcpLog(`[mcp-client] Tool call aborted by caller for "${serverName}/${toolName}".\n`);
444
- throw firstErr;
445
- }
446
- if (isMcpToolCallTimeoutError(firstErr)) {
447
- mcpLog(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
448
- throw firstErr;
449
- }
450
- const serverDead = server.liveness?.alive === false;
451
- if (!serverDead && isMcpServerResponseError(firstErr)) {
452
- // The server answered (JSON-RPC error): it is alive — nothing to
453
- // reconnect, and the call already ran its course.
454
- throw firstErr;
455
- }
456
- // Only a failure that proves the request never left this process
457
- // may be replayed; anything else may already have run server-side.
458
- const undelivered = isMcpUndeliveredError(firstErr);
459
- const deadTail = serverDead ? server.stderrTail?.() : '';
460
- mcpLog(`[mcp-client] Tool call failed, attempting shared reconnect...\n`);
461
- let retryServer;
462
- try {
463
- // The reconnect is SHARED (singleflight): the race only stops
464
- // THIS caller from waiting on it — the reconnect keeps running
465
- // for its other waiters. Without this, an abort mid-reconnect
466
- // could not settle the call and the admission slot stayed held
467
- // until the reconnect finished, or forever if it hung.
468
- retryServer = await raceMcpAbort(
469
- reconnectMcpServer(scopeId, serverName, server),
470
- callSignal,
471
- `MCP tool call aborted during reconnect (server="${serverName}", tool="${toolName}")`
472
- );
473
- } catch (reconnectErr) {
474
- if (isMcpCallAbortError(reconnectErr) || callSignal?.aborted) throw reconnectErr;
475
- const reconnectMsg = reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr);
476
- throw new Error(
477
- appendMcpStderrTail(`Tool call failed: ${firstMsg}; reconnect also failed: ${reconnectMsg}`, deadTail)
478
- );
479
- }
480
- if (!undelivered) {
481
- throw new Error(
482
- appendMcpStderrTail(
483
- `Tool call failed: ${firstMsg}; MCP server "${serverName}" was reconnected for the next call, but this call was not replayed because the request may already have reached the server`,
484
- deadTail
485
- )
486
- );
487
- }
488
- // A reconnect that outlived the caller's abort must not dispatch the
489
- // retry: that would run the side effect after cancellation.
472
+ return gate
473
+ .run(
474
+ options?.ownerKey || currentToolExecutionOwner(),
475
+ async () => {
476
+ // The gate stops forwarding the caller's abort once this task is
477
+ // admitted, so the task itself observes the signal for its whole run.
490
478
  if (callSignal?.aborted) {
491
479
  throw mcpAbortError(
492
480
  callSignal,
493
- `MCP tool call aborted after reconnect (server="${serverName}", tool="${toolName}")`
481
+ `MCP tool call aborted before dispatch (server="${serverName}", tool="${toolName}")`
494
482
  );
495
483
  }
484
+ let result;
496
485
  try {
497
- result = await dispatch(retryServer);
498
- } catch (retryErr) {
499
- if (isMcpCallAbortError(retryErr) || callSignal?.aborted) throw retryErr;
500
- const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
501
- throw new Error(`Tool call failed: ${firstMsg}; retry after reconnect also failed: ${retryMsg}`);
486
+ result = await dispatch(server);
487
+ } catch (firstErr) {
488
+ const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
489
+ if (isMcpCallAbortError(firstErr) || callSignal?.aborted) {
490
+ // A cancelled call is not a transport failure: reconnecting and
491
+ // replaying it would duplicate the side effect the caller just
492
+ // walked away from.
493
+ mcpLog(`[mcp-client] Tool call aborted by caller for "${serverName}/${toolName}".\n`);
494
+ throw firstErr;
495
+ }
496
+ if (isMcpToolCallTimeoutError(firstErr)) {
497
+ mcpLog(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
498
+ throw firstErr;
499
+ }
500
+ const serverDead = server.liveness?.alive === false;
501
+ if (!serverDead && isMcpServerResponseError(firstErr)) {
502
+ // The server answered (JSON-RPC error): it is alive — nothing to
503
+ // reconnect, and the call already ran its course.
504
+ throw firstErr;
505
+ }
506
+ // Only a failure that proves the request never left this process
507
+ // may be replayed; anything else may already have run server-side.
508
+ const undelivered = isMcpUndeliveredError(firstErr);
509
+ const deadTail = serverDead ? server.stderrTail?.() : '';
510
+ mcpLog(`[mcp-client] Tool call failed, attempting shared reconnect...\n`);
511
+ try {
512
+ // The reconnect is SHARED (singleflight): the race only stops
513
+ // THIS caller from waiting on it — the reconnect keeps running
514
+ // for its other waiters. Without this, an abort mid-reconnect
515
+ // could not settle the call and the admission slot stayed held
516
+ // until the reconnect finished, or forever if it hung.
517
+ retryServer = await raceMcpAbort(
518
+ reconnectMcpServer(scopeId, serverName, server),
519
+ callSignal,
520
+ `MCP tool call aborted during reconnect (server="${serverName}", tool="${toolName}")`
521
+ );
522
+ } catch (reconnectErr) {
523
+ if (isMcpCallAbortError(reconnectErr) || callSignal?.aborted) throw reconnectErr;
524
+ const reconnectMsg = reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr);
525
+ throw new Error(
526
+ appendMcpStderrTail(`Tool call failed: ${firstMsg}; reconnect also failed: ${reconnectMsg}`, deadTail)
527
+ );
528
+ }
529
+ if (!undelivered) {
530
+ throw new Error(
531
+ appendMcpStderrTail(
532
+ `Tool call failed: ${firstMsg}; MCP server "${serverName}" was reconnected for the next call, but this call was not replayed because the request may already have reached the server`,
533
+ deadTail
534
+ )
535
+ );
536
+ }
537
+ // A reconnect that outlived the caller's abort must not dispatch the
538
+ // retry: that would run the side effect after cancellation.
539
+ if (callSignal?.aborted) {
540
+ throw mcpAbortError(
541
+ callSignal,
542
+ `MCP tool call aborted after reconnect (server="${serverName}", tool="${toolName}")`
543
+ );
544
+ }
545
+ try {
546
+ result = await dispatch(retryServer);
547
+ } catch (retryErr) {
548
+ if (isMcpCallAbortError(retryErr) || callSignal?.aborted) throw retryErr;
549
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
550
+ throw new Error(`Tool call failed: ${firstMsg}; retry after reconnect also failed: ${retryMsg}`);
551
+ }
502
552
  }
553
+ const normalized = normalizeToolEnvelope(await normalizeMcpToolResult(scrubMcpSecrets(result, secrets())));
554
+ const text = capMcpResult(normalized.result);
555
+ return normalized.explicitSuccess ? makeToolEnvelope(text, [], { explicitSuccess: true }) : text;
556
+ },
557
+ {
558
+ signal: callSignal,
503
559
  }
504
- const normalized = normalizeToolEnvelope(await normalizeMcpToolResult(result));
505
- const text = capMcpResult(normalized.result);
506
- return normalized.explicitSuccess ? makeToolEnvelope(text, [], { explicitSuccess: true }) : text;
507
- },
508
- {
509
- signal: callSignal,
510
- }
511
- );
560
+ )
561
+ .catch((error) => {
562
+ throw scrubMcpOAuthError(error, server.cfg, secrets());
563
+ });
512
564
  }
513
565
 
514
566
  const MCP_FEATURE_REQUESTS = {
@@ -884,6 +936,7 @@ export function raceMcpAbort(promise, signal, message) {
884
936
  if (!signal) return promise;
885
937
  allowConcurrentAbortListeners(signal);
886
938
  if (signal.aborted) {
939
+ // The abort wins; the abandoned promise's late rejection must not go unhandled.
887
940
  void Promise.resolve(promise).catch(() => {});
888
941
  return Promise.reject(mcpAbortError(signal, message));
889
942
  }
@@ -1022,12 +1075,7 @@ export function isMcpTool(name) {
1022
1075
  }
1023
1076
  /** True when the prefixed name exists on a connected MCP server. */
1024
1077
  export function isRegisteredMcpTool(name, scopeId = DEFAULT_MCP_SCOPE_ID) {
1025
- const match = name.match(MCP_TOOL_NAME_RE);
1026
- if (!match) return false;
1027
- const [, serverName] = match;
1028
- const server = scopedServer(scopeId, serverName);
1029
- if (!server || !Array.isArray(server.tools)) return false;
1030
- return server.tools.some((t) => t?.name === name);
1078
+ return Boolean(resolveMcpToolTarget(scopeId, name)?.definition);
1031
1079
  }
1032
1080
  /**
1033
1081
  * Check whether the inputSchema for an MCP tool declares the given top-level
@@ -1040,9 +1088,7 @@ export function mcpToolHasField(name, field, scopeId = DEFAULT_MCP_SCOPE_ID) {
1040
1088
  const memoKey = `${normalizedScopeId}|${name}|${field}`;
1041
1089
  const memoized = _mcpToolFieldMemo.get(memoKey);
1042
1090
  if (memoized !== undefined) return memoized;
1043
- const match = name.match(MCP_TOOL_NAME_RE);
1044
- const server = match ? scopedServer(normalizedScopeId, match[1]) : null;
1045
- const tool = server ? server.tools.find((t) => t.name === name) : null;
1091
+ const tool = resolveMcpToolTarget(normalizedScopeId, name)?.definition;
1046
1092
  const props = tool?.inputSchema?.properties;
1047
1093
  const result = Boolean(props && Object.hasOwn(props, field));
1048
1094
  _mcpToolFieldMemo.set(memoKey, result);
@@ -1078,6 +1124,7 @@ export async function disconnectAll(options = undefined) {
1078
1124
  /* ignore */
1079
1125
  }
1080
1126
  try {
1127
+ entry.transport.oauth?.close();
1081
1128
  void entry.client.close().catch(() => {
1082
1129
  /* ignore */
1083
1130
  });
@@ -1106,6 +1153,9 @@ export async function disconnectAll(options = undefined) {
1106
1153
  export async function disconnectMcpServer(name, options = {}) {
1107
1154
  const scopeId = normalizeMcpScopeId(options);
1108
1155
  const registryKey = mcpServerRegistryKey(scopeId, name);
1156
+ // An exclusive reconnect may currently be waiting for human authorization.
1157
+ // Cancel before queueing the disconnect behind that operation.
1158
+ cancelMcpLogin(name, options);
1109
1159
  return runExclusiveServerOp(registryKey, async () => {
1110
1160
  const server = servers.get(registryKey);
1111
1161
  if (!server) return false;
@@ -1120,6 +1170,73 @@ export async function disconnectMcpServer(name, options = {}) {
1120
1170
  return true;
1121
1171
  });
1122
1172
  }
1173
+ /**
1174
+ * Cancel the interactive OAuth login of a connect of `name` still in its
1175
+ * handshake; that connect then fails. Callers that queue behind the connect
1176
+ * (disable, remove) call this first so they never wait out the login.
1177
+ */
1178
+ export function cancelMcpLogin(name, options = {}) {
1179
+ const scopeId = normalizeMcpScopeId(options);
1180
+ for (const entry of _pendingConnects) {
1181
+ if (entry.scopeId === scopeId && entry.name === name) entry.transport.oauth?.close();
1182
+ }
1183
+ }
1184
+ /**
1185
+ * Answer the interactive OAuth login of a connect of `name` still in its
1186
+ * handshake with the redirect URL pasted from the browser (remote / headless
1187
+ * sign-in); that connect then proceeds as after the loopback callback. Throws
1188
+ * a fixed message, never the pasted URL, for an invalid paste (the login stays
1189
+ * pending) or when no login is waiting.
1190
+ */
1191
+ export function completeMcpLogin(name, callbackUrl, options = {}) {
1192
+ const scopeId = normalizeMcpScopeId(options);
1193
+ for (const entry of _pendingConnects) {
1194
+ if (entry.scopeId === scopeId && entry.name === name && entry.transport.oauth?.loginStarted) {
1195
+ entry.transport.oauth.completeCallback(callbackUrl);
1196
+ return;
1197
+ }
1198
+ }
1199
+ throw new Error(MCP_OAUTH_CALLBACK_NOT_PENDING);
1200
+ }
1201
+ /** Whether a connect of `name` still in its handshake is waiting for an
1202
+ * interactive (browser) OAuth login. */
1203
+ export function isMcpLoginPending(name, options = {}) {
1204
+ const scopeId = normalizeMcpScopeId(options);
1205
+ for (const entry of _pendingConnects) {
1206
+ if (entry.scopeId === scopeId && entry.name === name && entry.transport.oauth?.loginStarted) return true;
1207
+ }
1208
+ return false;
1209
+ }
1210
+ /**
1211
+ * Stored OAuth sign-in state of an HTTP/SSE server without configured
1212
+ * authentication: `{ signedIn }` (tokens stored for its exact URL), or null
1213
+ * for servers that never use MCP OAuth.
1214
+ */
1215
+ export function getMcpServerOAuthState(cfg) {
1216
+ let url;
1217
+ try {
1218
+ url = resolveMcpOAuthUrl(cfg);
1219
+ } catch {
1220
+ return null;
1221
+ }
1222
+ if (!url || hasConfiguredMcpAuth(cfg)) return null;
1223
+ return { signedIn: hasStoredMcpOAuthTokens(url) };
1224
+ }
1225
+ /**
1226
+ * Log out of an HTTP/SSE server URL: delete the OAuth tokens and client
1227
+ * registration stored for it, shared by every server configured with that
1228
+ * exact URL, and drop them from live connections.
1229
+ */
1230
+ export async function clearMcpServerOAuth(cfg) {
1231
+ let url;
1232
+ try {
1233
+ url = resolveMcpOAuthUrl(cfg);
1234
+ } catch {
1235
+ // A config without a valid remote URL never connected, so never logged in.
1236
+ return;
1237
+ }
1238
+ if (url) await clearMcpOAuth({ url });
1239
+ }
1123
1240
  /**
1124
1241
  * Close a single server: for stdio transports first shut down the full child
1125
1242
  * process tree (close stdin -> grace -> tree kill) so wrapper chains such as
@@ -1131,6 +1248,7 @@ async function _closeServer(server) {
1131
1248
  // A deliberate close is not a crash: suppress the unexpected-close report.
1132
1249
  if (server?.liveness) server.liveness.closing = true;
1133
1250
  const transport = server?.transport;
1251
+ transport?.oauth?.close();
1134
1252
  // Live stdio transports expose the spawned ChildProcess on _process.
1135
1253
  if (transport?._process) {
1136
1254
  try {
@@ -1153,7 +1271,8 @@ async function connectServer(
1153
1271
  name,
1154
1272
  cfg,
1155
1273
  scopeId = DEFAULT_MCP_SCOPE_ID,
1156
- genAtStart = currentConnectAbortGeneration(scopeId)
1274
+ genAtStart = currentConnectAbortGeneration(scopeId),
1275
+ options = {}
1157
1276
  ) {
1158
1277
  scopeId = normalizeMcpScopeId(scopeId);
1159
1278
  _knownMcpScopes.add(scopeId);
@@ -1163,9 +1282,21 @@ async function connectServer(
1163
1282
  // abort before creating a transport/child at all.
1164
1283
  throw new Error(`MCP server "${name}" connect aborted by shutdown`);
1165
1284
  }
1166
- const client = createMcpClient(sdk, name, cfg);
1167
- const { transport, autoDetectAdvert, stderrTail } = createMcpTransport({ name, cfg, sdk, log: mcpLog });
1168
- const liveness = trackMcpLiveness(client, name, cfg, stderrTail);
1285
+ const client = createMcpClient(sdk, name, cfg, scopeId);
1286
+ const { transport, autoDetectAdvert, stderrTail, warnings } = createMcpTransport({
1287
+ name,
1288
+ cfg,
1289
+ sdk,
1290
+ log: mcpLog,
1291
+ strictEnv: cfg?._mixdogStrictEnv === true,
1292
+ // Only an explicit sign-in (beginMcpOAuthLogin) is interactive; it may
1293
+ // supply its own browser launcher to capture the authorization URL.
1294
+ oauthOptions: {
1295
+ interactive: options.interactive === true,
1296
+ ...(typeof options.openBrowser === 'function' ? { openBrowser: options.openBrowser } : {}),
1297
+ },
1298
+ });
1299
+ const liveness = trackMcpLiveness(client, name, cfg, stderrTail, transport);
1169
1300
  const pending = { scopeId, name, client, transport };
1170
1301
  _pendingConnects.add(pending);
1171
1302
  try {
@@ -1195,7 +1326,7 @@ async function connectServer(
1195
1326
  }
1196
1327
  throw new Error(`MCP server "${name}" connect aborted by shutdown`);
1197
1328
  }
1198
- const tools = mcpServerTools(name, capabilities, toolsResult.tools);
1329
+ const tools = mcpServerTools(name, capabilities, toolsResult.tools, cfg);
1199
1330
  const registryKey = mcpServerRegistryKey(scopeId, name);
1200
1331
  servers.set(registryKey, {
1201
1332
  scopeId,
@@ -1209,6 +1340,7 @@ async function connectServer(
1209
1340
  capabilities,
1210
1341
  liveness,
1211
1342
  stderrTail,
1343
+ warnings: warnings || [],
1212
1344
  generation: genAtStart,
1213
1345
  });
1214
1346
  if (capabilities?.tools?.listChanged && sdk.ToolListChangedNotificationSchema) {
@@ -1218,6 +1350,22 @@ async function connectServer(
1218
1350
  }
1219
1351
  _invalidateMcpToolFieldMemo();
1220
1352
  mcpLog(`[mcp] connected: ${tools.length} tools — ${tools.map((t) => t.name).join(', ')}\n`);
1353
+ } catch (error) {
1354
+ await _closeServer({ client, transport, liveness });
1355
+ const scrubbed = scrubMcpOAuthError(error, cfg, oauthSecrets(transport));
1356
+ // A server that only needs an OAuth sign-in is not a broken server: the
1357
+ // sign-in was required, or an explicit one did not complete (cancelled,
1358
+ // denied, timed out or a failed exchange after it started, or cancelled
1359
+ // before it could). A failure after the sign-in obtained tokens is real.
1360
+ const signInIncomplete =
1361
+ transport.oauth &&
1362
+ (isMcpOAuthReauthenticationRequired(error) ||
1363
+ (transport.oauth.loginStarted && !transport.oauth.loginCompleted) ||
1364
+ (transport.oauth.interactive && error?.code === MCP_CONNECT_CANCELLED));
1365
+ if (signInIncomplete && scrubbed && typeof scrubbed === 'object') {
1366
+ scrubbed.authRequired = true;
1367
+ }
1368
+ throw scrubbed;
1221
1369
  } finally {
1222
1370
  _pendingConnects.delete(pending);
1223
1371
  }
@@ -1230,7 +1378,7 @@ async function connectServer(
1230
1378
  * records the last error (parse/send hiccups do not mean the server died).
1231
1379
  * Handlers already installed on the client are chained, not replaced.
1232
1380
  */
1233
- function trackMcpLiveness(client, name, cfg, stderrTail) {
1381
+ function trackMcpLiveness(client, name, cfg, stderrTail, transport) {
1234
1382
  const liveness = { alive: true, closing: false, closedAt: 0, lastError: '' };
1235
1383
  const priorClose = client.onclose;
1236
1384
  const priorError = client.onerror;
@@ -1244,22 +1392,35 @@ function trackMcpLiveness(client, name, cfg, stderrTail) {
1244
1392
  if (typeof priorClose === 'function') priorClose.apply(client, args);
1245
1393
  };
1246
1394
  client.onerror = (error, ...rest) => {
1247
- liveness.lastError = scrubMcpConnectionMessage(error instanceof Error ? error.message : String(error), cfg);
1395
+ liveness.lastError = scrubMcpConnectionMessage(
1396
+ error instanceof Error ? error.message : String(error),
1397
+ cfg,
1398
+ oauthSecrets(transport)
1399
+ );
1248
1400
  if (typeof priorError === 'function') priorError.call(client, error, ...rest);
1249
1401
  };
1250
1402
  return liveness;
1251
1403
  }
1252
1404
 
1253
- /** The SDK client for one server, advertising the project root when known. */
1254
- function createMcpClient(sdk, name, cfg) {
1255
- const projectRoot = String(cfg?._mixdogProjectRoot || cfg?.cwd || '').trim();
1405
+ /** The SDK client for one server, advertising the project root when known.
1406
+ * A config marked `_mixdogRootsFollowScope` advertises its scope's current
1407
+ * root (setMcpScopeRoot) and is told when it changes. */
1408
+ function createMcpClient(sdk, name, cfg, scopeId = DEFAULT_MCP_SCOPE_ID) {
1409
+ const followScope = cfg?._mixdogRootsFollowScope === true;
1410
+ const fixedRoot = String(cfg?._mixdogProjectRoot || cfg?.cwd || '').trim();
1411
+ const currentRoot = () => (followScope ? _scopeRoots.get(normalizeMcpScopeId(scopeId)) || fixedRoot : fixedRoot);
1256
1412
  const client = new sdk.Client(
1257
1413
  { name: `mixdog-agent/${name}`, version: '1.0.0' },
1258
- { capabilities: projectRoot ? { roots: { listChanged: false } } : {} }
1414
+ {
1415
+ capabilities: followScope || fixedRoot ? { roots: { listChanged: followScope } } : {},
1416
+ jsonSchemaValidator: createLazyOutputValidator(sdk.AjvJsonSchemaValidator),
1417
+ }
1259
1418
  );
1260
- if (projectRoot && sdk.ListRootsRequestSchema) {
1419
+ if ((followScope || fixedRoot) && sdk.ListRootsRequestSchema) {
1261
1420
  client.setRequestHandler(sdk.ListRootsRequestSchema, async () => {
1262
- const rootPath = resolve(projectRoot);
1421
+ const root = currentRoot();
1422
+ if (!root) return { roots: [] };
1423
+ const rootPath = resolve(root);
1263
1424
  return {
1264
1425
  roots: [
1265
1426
  {
@@ -1273,15 +1434,63 @@ function createMcpClient(sdk, name, cfg) {
1273
1434
  return client;
1274
1435
  }
1275
1436
 
1276
- /** Protocol tools under the `mcp__<server>__` prefix plus the feature tools. */
1277
- function mcpServerTools(name, capabilities, rawTools) {
1278
- const protocolTools = (rawTools || []).map((t) => ({
1279
- name: `mcp__${name}__${t.name}`,
1280
- description: t.description || '',
1281
- inputSchema: t.inputSchema || { type: 'object', properties: {} },
1282
- ...(t.annotations && typeof t.annotations === 'object' ? { annotations: t.annotations } : {}),
1283
- }));
1284
- return [...protocolTools, ...mcpFeatureTools(name, capabilities, protocolTools)];
1437
+ /**
1438
+ * Set the root advertised by `scopeId`'s scope-following servers (plugin
1439
+ * servers, whose roots track the session cwd) and notify the connected ones
1440
+ * when it changed. No reconnect: the connection and its tools stay as they are.
1441
+ */
1442
+ export function setMcpScopeRoot(scopeId, root) {
1443
+ const normalized = normalizeMcpScopeId(scopeId);
1444
+ const next = String(root || '').trim();
1445
+ if ((_scopeRoots.get(normalized) || '') === next) return;
1446
+ if (next) _scopeRoots.set(normalized, next);
1447
+ else _scopeRoots.delete(normalized);
1448
+ for (const [, server] of scopedServerEntries(normalized)) {
1449
+ if (server.cfg?._mixdogRootsFollowScope !== true) continue;
1450
+ Promise.resolve()
1451
+ .then(() => server.client?.sendRootsListChanged?.())
1452
+ // A server that rejects the roots notification keeps its previous roots.
1453
+ .catch(() => {});
1454
+ }
1455
+ }
1456
+
1457
+ /** The connected server and tool definition a prefixed tool name refers to.
1458
+ * The name's server part finds it directly; a name shortened to fit provider
1459
+ * limits is found by scanning this scope's servers. A known server without
1460
+ * the tool still resolves (definition null) so the call reaches the server. */
1461
+ function resolveMcpToolTarget(scopeId, name) {
1462
+ const match = MCP_TOOL_NAME_RE.exec(String(name || ''));
1463
+ if (!match) return null;
1464
+ const direct = scopedServer(scopeId, match[1]);
1465
+ const directTool = direct?.tools?.find((tool) => tool.name === name);
1466
+ if (directTool) return { server: direct, definition: directTool };
1467
+ for (const [, server] of scopedServerEntries(scopeId)) {
1468
+ const definition = server.tools?.find((tool) => tool.name === name);
1469
+ if (definition) return { server, definition };
1470
+ }
1471
+ return direct ? { server: direct, definition: null } : null;
1472
+ }
1473
+
1474
+ /** Protocol tools under the `mcp__<server>__` prefix plus the feature tools.
1475
+ * Names that would exceed provider limits are shortened deterministically
1476
+ * (`providerSafeMcpToolName`); a tool whose name was shortened carries the
1477
+ * server's own tool name as `mcpToolName` and its server as `mcpServerName`.
1478
+ * A plugin-declared server's tools carry its owner as `mcpPluginId`, so
1479
+ * scope filtering never has to infer ownership from the server name. */
1480
+ function mcpServerTools(name, capabilities, rawTools, cfg) {
1481
+ const protocolTools = (rawTools || []).map((t) => {
1482
+ const safeName = providerSafeMcpToolName(name, t.name);
1483
+ return {
1484
+ name: safeName,
1485
+ ...(safeName === `mcp__${name}__${t.name}` ? {} : { mcpToolName: t.name, mcpServerName: name }),
1486
+ description: t.description || '',
1487
+ inputSchema: t.inputSchema || { type: 'object', properties: {} },
1488
+ ...(t.annotations && typeof t.annotations === 'object' ? { annotations: t.annotations } : {}),
1489
+ };
1490
+ });
1491
+ const tools = [...protocolTools, ...mcpFeatureTools(name, capabilities, protocolTools)];
1492
+ const pluginId = String(cfg?._mixdogPlugin?.id || '').trim();
1493
+ return pluginId ? tools.map((tool) => ({ ...tool, mcpPluginId: pluginId })) : tools;
1285
1494
  }
1286
1495
 
1287
1496
  // tools/list_changed refreshes run one at a time per server connection, so an
@@ -1304,11 +1513,11 @@ async function runToolRefresh(registryKey, live, name, cfg, capabilities) {
1304
1513
  throw new Error('ListTools returned invalid shape: missing or non-array tools field');
1305
1514
  }
1306
1515
  if (servers.get(registryKey) !== live) return;
1307
- live.tools = mcpServerTools(name, capabilities, refreshed.tools);
1516
+ live.tools = mcpServerTools(name, capabilities, refreshed.tools, cfg);
1308
1517
  _invalidateMcpToolFieldMemo();
1309
1518
  } catch (error) {
1310
1519
  mcpLog(
1311
- `[mcp-client] Failed to refresh tools for "${name}": ${scrubMcpConnectionMessage(error?.message || error, cfg)}\n`
1520
+ `[mcp-client] Failed to refresh tools for "${name}": ${scrubMcpConnectionMessage(error?.message || error, cfg, oauthSecrets(live.transport))}\n`
1312
1521
  );
1313
1522
  }
1314
1523
  }
@@ -1317,25 +1526,34 @@ async function runToolRefresh(registryKey, live, name, cfg, capabilities) {
1317
1526
  export function _registerMcpServerForTest(scopeId, name, rawTools = [], options = {}) {
1318
1527
  const normalizedScopeId = normalizeMcpScopeId(scopeId);
1319
1528
  const registryKey = mcpServerRegistryKey(normalizedScopeId, name);
1320
- const tools = rawTools.map((tool) => ({
1321
- ...tool,
1322
- name: String(tool?.name || '').startsWith('mcp__')
1323
- ? String(tool.name)
1324
- : `mcp__${name}__${String(tool?.name || '')}`,
1325
- inputSchema: tool?.inputSchema || { type: 'object', properties: {} },
1326
- }));
1529
+ const tools = rawTools.map((tool) => {
1530
+ const prefixed = String(tool?.name || '').startsWith('mcp__');
1531
+ const leaf = String(tool?.name || '');
1532
+ const safeName = prefixed ? leaf : providerSafeMcpToolName(name, leaf);
1533
+ const shortened = !prefixed && safeName !== `mcp__${name}__${leaf}`;
1534
+ return {
1535
+ ...tool,
1536
+ name: safeName,
1537
+ ...(shortened ? { mcpToolName: leaf, mcpServerName: name } : {}),
1538
+ inputSchema: tool?.inputSchema || { type: 'object', properties: {} },
1539
+ };
1540
+ });
1327
1541
  const capabilities = options.capabilities || {};
1542
+ const pluginId = String(options.cfg?._mixdogPlugin?.id || '').trim();
1328
1543
  const server = {
1329
1544
  scopeId: normalizedScopeId,
1330
1545
  registryKey,
1331
1546
  name,
1332
- tools: [...tools, ...mcpFeatureTools(name, capabilities, tools)],
1547
+ tools: [...tools, ...mcpFeatureTools(name, capabilities, tools)].map((tool) =>
1548
+ pluginId ? { ...tool, mcpPluginId: pluginId } : tool
1549
+ ),
1550
+ warnings: options.warnings || [],
1333
1551
  cfg: options.cfg || {},
1334
1552
  instructions: options.instructions || '',
1335
1553
  capabilities,
1336
1554
  liveness: { alive: true, closing: false, closedAt: 0, lastError: '' },
1337
1555
  stderrTail: typeof options.stderrTail === 'function' ? options.stderrTail : () => '',
1338
- transport: null,
1556
+ transport: options.transport || null,
1339
1557
  client: {
1340
1558
  callTool:
1341
1559
  typeof options.callTool === 'function'