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,21 +1,13 @@
1
1
  // Plugin/project MCP server discovery + normalization, and skill-file counting.
2
- import {
3
- chmodSync,
4
- closeSync,
5
- existsSync,
6
- fsyncSync,
7
- openSync,
8
- readFileSync,
9
- readdirSync,
10
- renameSync,
11
- statSync,
12
- unlinkSync,
13
- writeFileSync,
14
- } from 'node:fs';
15
- import { isAbsolute, join } from 'node:path';
2
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { join, relative, sep } from 'node:path';
16
4
  import { clean } from './session-text.mjs';
17
- import { readJsonSafe } from './fs-utils.mjs';
18
- import { pluginManifest, pluginSkillsRoots, resolveContainedPluginPath } from '../../../shared/plugin-manifest.mjs';
5
+ import {
6
+ PLUGIN_MANIFEST_FILES,
7
+ pluginManifest,
8
+ pluginSkillsRoots,
9
+ resolveContainedPluginPath,
10
+ } from '../../../shared/plugin-manifest.mjs';
19
11
  import { isPlainObject } from '../../../shared/object.mjs';
20
12
 
21
13
  const MCP_TRANSPORT_FIELDS = new Set([
@@ -50,105 +42,19 @@ export function mergeMcpServerConfig(existing, next) {
50
42
  return { ...merged, ...next };
51
43
  }
52
44
 
53
- function readProjectMcpDocument(cwd, allowMissing = false) {
54
- const path = join(cwd || '.', '.mcp.json');
55
- if (!existsSync(path)) {
56
- if (allowMissing) return { path, raw: { mcpServers: {} }, usesWrapper: true };
57
- throw new Error(`MCP config file not found: ${path}`);
58
- }
59
- let raw;
60
- try {
61
- raw = JSON.parse(readFileSync(path, 'utf8'));
62
- } catch (error) {
63
- throw new Error(`cannot read ${path}: ${error?.message || String(error)}`);
64
- }
65
- if (!isPlainObject(raw)) throw new Error(`unexpected .mcp.json shape at ${path}`);
66
- if (Object.hasOwn(raw, 'mcpServers') && !isPlainObject(raw.mcpServers)) {
67
- throw new Error(`unexpected .mcp.json shape at ${path}`);
68
- }
69
- const usesWrapper = isPlainObject(raw.mcpServers);
70
- return { path, raw, usesWrapper };
71
- }
72
-
73
- function writeProjectMcpDocument(path, raw) {
74
- let mode = 0o644;
75
- try {
76
- mode = statSync(path).mode;
77
- } catch {
78
- /* new file */
79
- }
80
- const tempPath = `${path}.tmp-${process.pid}-${Date.now()}`;
81
- let fd = null;
82
- try {
83
- fd = openSync(tempPath, 'w', mode);
84
- writeFileSync(fd, `${JSON.stringify(raw, null, 2)}\n`, 'utf8');
85
- fsyncSync(fd);
86
- closeSync(fd);
87
- fd = null;
88
- chmodSync(tempPath, mode);
89
- renameSync(tempPath, path);
90
- } catch (error) {
91
- if (fd !== null) {
92
- try {
93
- closeSync(fd);
94
- } catch {
95
- /* best effort */
96
- }
97
- }
98
- try {
99
- unlinkSync(tempPath);
100
- } catch {
101
- /* best effort */
102
- }
103
- throw error;
104
- }
105
- }
106
-
107
- // The stored key for `key`: an exact own key first, else one that trims to it.
108
- function mcpEntryKey(map, key) {
109
- return Object.hasOwn(map, key) ? key : Object.keys(map).find((candidate) => String(candidate || '').trim() === key);
110
- }
111
-
112
- export function readProjectMcpServerConfig(cwd, name) {
113
- const { raw, usesWrapper } = readProjectMcpDocument(cwd);
114
- const map = usesWrapper ? raw.mcpServers : raw;
115
- const entryKey = mcpEntryKey(map, String(name || '').trim());
116
- return entryKey && isPlainObject(map[entryKey]) ? { name: entryKey, config: { ...map[entryKey] } } : null;
117
- }
118
-
119
- export function saveProjectMcpServer(cwd, { originalName = '', name, config }) {
120
- const { path, raw, usesWrapper } = readProjectMcpDocument(cwd, !originalName);
121
- const map = usesWrapper ? raw.mcpServers : raw;
122
- const target = String(name || '').trim();
123
- const original = String(originalName || '').trim();
124
- if (!target) throw new Error('MCP server name is required');
125
- const entryKey = original ? mcpEntryKey(map, original) : null;
126
- if (original && !entryKey) throw new Error(`MCP server not defined in ${path}: ${original}`);
127
- if (target !== entryKey && Object.hasOwn(map, target)) {
128
- throw new Error(`MCP server already exists in ${path}: ${target}`);
129
- }
130
- const existing = entryKey ? map[entryKey] : {};
131
- if (entryKey && entryKey !== target) delete map[entryKey];
132
- map[target] = mergeMcpServerConfig(existing, config);
133
- writeProjectMcpDocument(path, raw);
134
- return { name: target, source: 'project', path, config: { ...map[target] } };
135
- }
136
-
137
45
  // Manifest + contained-path resolution live in the shared module so skill
138
46
  // discovery and this status surface read the same roots.
139
47
  export { pluginManifest, resolveContainedPluginPath };
140
48
 
141
- function mcpServersMapFromJson(raw) {
142
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
143
- if (isPlainObject(raw.mcpServers)) return raw.mcpServers;
144
- return raw;
145
- }
146
-
147
49
  function isSkillMdFile(name) {
148
50
  return /^(SKILL|skill)\.md$/i.test(name);
149
51
  }
150
52
 
151
- const PLUGIN_INLINE_MCP_SCRIPT = 'plugin.json';
53
+ const PLUGIN_MANIFEST_FORMATS = {
54
+ '.codex-plugin/plugin.json': 'codex',
55
+ 'plugin.json': 'mixdog',
56
+ '.claude-plugin/plugin.json': 'claude',
57
+ };
152
58
 
153
59
  export function countSkillFiles(root) {
154
60
  let count = 0;
@@ -177,102 +83,283 @@ export function countSkillFiles(root) {
177
83
  return count;
178
84
  }
179
85
 
180
- export function discoverPluginMcp(root) {
181
- const manifest = pluginManifest(root);
182
- const mcp = manifest.mcpServers;
183
- if (typeof mcp === 'string' && mcp.trim()) {
184
- const rel = mcp.trim();
185
- const abs = resolveContainedPluginPath(root, rel);
186
- if (abs && existsSync(abs)) return { mcpScript: rel, mcpInline: false };
187
- } else if (isPlainObject(mcp)) {
188
- const keys = Object.keys(mcp).filter((k) => isPlainObject(mcp[k]));
189
- if (keys.length) return { mcpScript: null, mcpInline: true };
86
+ const MCP_STDIO_TYPES = new Set(['stdio']);
87
+ const MCP_URL_TYPES = new Set(['http', 'streamable-http', 'sse', 'ws']);
88
+
89
+ function manifestFormat(root) {
90
+ for (const rel of PLUGIN_MANIFEST_FILES) {
91
+ const path = join(root, ...rel.split('/'));
92
+ if (existsSync(path)) return { path, format: PLUGIN_MANIFEST_FORMATS[rel] };
190
93
  }
191
- const candidates = ['.mcp.json', 'scripts/run-mcp.mjs', 'mcp/server.mjs', 'server.mjs'];
192
- for (const rel of candidates) {
193
- const abs = resolveContainedPluginPath(root, rel);
194
- if (abs && existsSync(abs)) return { mcpScript: rel, mcpInline: false };
94
+ return null;
95
+ }
96
+
97
+ /** Transport a declared server asks for, inferred the way the MCP client does
98
+ * when `type` is absent: `transport: "http"` or a `url` -> http, else stdio.
99
+ * Null for an unrecognized explicit type. */
100
+ export function declaredMcpTransport(config) {
101
+ if (config?.type !== undefined && config?.type !== null && config?.type !== '') {
102
+ if (typeof config.type !== 'string') return null;
103
+ const type = config.type.toLowerCase();
104
+ if (type === 'streamablehttp') return 'streamable-http';
105
+ return MCP_STDIO_TYPES.has(type) || MCP_URL_TYPES.has(type) ? type : null;
195
106
  }
196
- return { mcpScript: null, mcpInline: false };
107
+ if (config?.transport === 'http') return 'http';
108
+ if (config?.command !== undefined) return 'stdio';
109
+ if (config?.url !== undefined) return 'http';
110
+ return 'stdio';
197
111
  }
198
112
 
199
- export function mcpScriptForPlugin(root) {
200
- return discoverPluginMcp(root).mcpScript;
113
+ // One server entry -> a message when it cannot be launched, else null. Messages
114
+ // name fields and expected types only, never a configured value.
115
+ function mcpServerProblem(config) {
116
+ if (!isPlainObject(config)) return 'server entry must be an object';
117
+ const type = declaredMcpTransport(config);
118
+ if (!type) return '"type" must be one of stdio, http, streamable-http, sse, ws';
119
+ if (MCP_STDIO_TYPES.has(type)) {
120
+ return typeof config.command === 'string' && config.command.trim() ? null : 'stdio server requires "command"';
121
+ }
122
+ return typeof config.url === 'string' && config.url.trim() ? null : `${type} server requires "url"`;
201
123
  }
202
124
 
203
- export function pluginMcpEnableScript(root, plugin = {}) {
204
- if (plugin.mcpInline) return PLUGIN_INLINE_MCP_SCRIPT;
205
- const fromPlugin = clean(plugin.mcpScript);
206
- if (fromPlugin) return fromPlugin;
207
- const discovered = discoverPluginMcp(root);
208
- if (discovered.mcpInline) return PLUGIN_INLINE_MCP_SCRIPT;
209
- return discovered.mcpScript || null;
125
+ // A JSON parse failure as a location only: engines may quote the offending
126
+ // source text, which can hold credentials.
127
+ function jsonErrorMessage(error) {
128
+ const text = String(error?.message || '');
129
+ const lineColumn = /line (\d+) column (\d+)/.exec(text);
130
+ if (lineColumn) return `invalid JSON at line ${lineColumn[1]} column ${lineColumn[2]}`;
131
+ const position = /position (\d+)/.exec(text);
132
+ if (position) return `invalid JSON at position ${position[1]}`;
133
+ return /end of (JSON|data) input/i.test(text) ? 'invalid JSON: unexpected end of input' : 'invalid JSON';
210
134
  }
211
135
 
212
- function substitutePluginRootTokens(value, root) {
213
- if (typeof value !== 'string') return value;
214
- return value.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, root).replace(/\$\{CODEX_PLUGIN_ROOT\}/g, root);
136
+ function readJsonFile(path, errors) {
137
+ let text;
138
+ try {
139
+ text = readFileSync(path, 'utf8');
140
+ } catch (error) {
141
+ errors.push({ source: path, message: `cannot read file${error?.code ? ` (${error.code})` : ''}` });
142
+ return { ok: false };
143
+ }
144
+ try {
145
+ return { ok: true, value: JSON.parse(text) };
146
+ } catch (error) {
147
+ errors.push({ source: path, message: jsonErrorMessage(error) });
148
+ return { ok: false };
149
+ }
215
150
  }
216
151
 
217
- function substitutePluginRootTokensDeep(value, root) {
218
- if (typeof value === 'string') return substitutePluginRootTokens(value, root);
219
- if (Array.isArray(value)) return value.map((v) => substitutePluginRootTokensDeep(v, root));
220
- if (value && typeof value === 'object') {
221
- const out = {};
222
- for (const [k, v] of Object.entries(value)) out[k] = substitutePluginRootTokensDeep(v, root);
223
- return out;
152
+ // A `{ mcpServers: {...} }` wrapper or a bare server map -> the server map.
153
+ function mcpServerMap(doc, source, errors, where = '') {
154
+ if (!isPlainObject(doc)) {
155
+ errors.push({ source, message: `${where}MCP declaration must be an object` });
156
+ return null;
224
157
  }
225
- return value;
158
+ if (!Object.hasOwn(doc, 'mcpServers')) return doc;
159
+ if (!isPlainObject(doc.mcpServers)) {
160
+ errors.push({ source, message: `${where}"mcpServers" must be an object` });
161
+ return null;
162
+ }
163
+ return doc.mcpServers;
226
164
  }
227
165
 
228
- export function normalizePluginMcpServerConfig(cfg, root) {
229
- const substituted = substitutePluginRootTokensDeep(cfg, root) || {};
230
- const out = { ...substituted };
231
- if (typeof out.cwd === 'string' && out.cwd) {
232
- out.cwd = isAbsolute(out.cwd) ? out.cwd : join(root, out.cwd);
233
- } else {
234
- out.cwd = root;
166
+ function collectMcpServers(map, source, into, errors) {
167
+ for (const [key, config] of Object.entries(map)) {
168
+ const problem = mcpServerProblem(config);
169
+ if (problem) errors.push({ source, key, message: problem });
170
+ else into.set(key, { key, config, source });
235
171
  }
236
- return out;
237
172
  }
238
173
 
239
- export function pluginRawMcpServers(root, script) {
240
- const rel = clean(script);
241
- if (!rel) return null;
242
- if (rel === PLUGIN_INLINE_MCP_SCRIPT) {
243
- const manifest = pluginManifest(root);
244
- const rawServers = manifest.mcpServers;
245
- if (!isPlainObject(rawServers)) {
246
- throw new Error('plugin.json missing inline mcpServers object');
174
+ function loadMcpJsonFile(path, into, errors) {
175
+ const read = readJsonFile(path, errors);
176
+ if (!read.ok) return;
177
+ const map = mcpServerMap(read.value, path, errors);
178
+ if (map) collectMcpServers(map, path, into, errors);
179
+ }
180
+
181
+ // `where` locates the manifest field (e.g. `mcpServers[1]: `); the declared
182
+ // path itself is never echoed.
183
+ function loadDeclaredPath(root, rel, manifestPath, into, errors, touched, where) {
184
+ if (typeof rel !== 'string' || !rel.trim()) {
185
+ errors.push({ source: manifestPath, message: `${where}MCP path must be a non-empty string` });
186
+ return;
187
+ }
188
+ const abs = resolveContainedPluginPath(root, rel);
189
+ if (!abs || rel.split(/[\\/]/).includes('..')) {
190
+ errors.push({ source: manifestPath, message: `${where}MCP path must be relative and stay inside the plugin root` });
191
+ return;
192
+ }
193
+ touched.add(abs);
194
+ if (!existsSync(abs)) {
195
+ errors.push({ source: manifestPath, message: `${where}MCP file not found` });
196
+ return;
197
+ }
198
+ loadMcpJsonFile(abs, into, errors);
199
+ }
200
+
201
+ function readPluginMcpDeclarations(root, touched) {
202
+ const errors = [];
203
+ const into = new Map();
204
+ for (const rel of PLUGIN_MANIFEST_FILES) touched.add(join(root, ...rel.split('/')));
205
+ const found = manifestFormat(root);
206
+ const mcpJson = join(root, '.mcp.json');
207
+ touched.add(mcpJson);
208
+ const hasMcpJson = existsSync(mcpJson);
209
+ let format = 'none';
210
+ if (found) format = found.format;
211
+ else if (hasMcpJson) format = 'claude';
212
+
213
+ if (hasMcpJson) loadMcpJsonFile(mcpJson, into, errors);
214
+
215
+ let declaredRef;
216
+ if (found) {
217
+ const read = readJsonFile(found.path, errors);
218
+ const declared = read.ok && isPlainObject(read.value) ? read.value.mcpServers : undefined;
219
+ declaredRef = declared;
220
+ if (declared === undefined) {
221
+ /* nothing declared */
222
+ } else if (typeof declared === 'string') {
223
+ loadDeclaredPath(root, declared, found.path, into, errors, touched, '"mcpServers": ');
224
+ } else if (Array.isArray(declared)) {
225
+ // Members merge in order: a path to a JSON file, or an inline server
226
+ // map (bare or `{ mcpServers }`-wrapped).
227
+ declared.forEach((entry, index) => {
228
+ const where = `"mcpServers"[${index}]: `;
229
+ if (isPlainObject(entry)) {
230
+ const map = mcpServerMap(entry, found.path, errors, where);
231
+ if (map) collectMcpServers(map, found.path, into, errors);
232
+ } else {
233
+ loadDeclaredPath(root, entry, found.path, into, errors, touched, where);
234
+ }
235
+ });
236
+ } else if (isPlainObject(declared)) {
237
+ const map = mcpServerMap(declared, found.path, errors);
238
+ if (map) collectMcpServers(map, found.path, into, errors);
239
+ } else {
240
+ errors.push({ source: found.path, message: '"mcpServers" must be a path, a list, or an object' });
247
241
  }
248
- const keys = Object.keys(rawServers).filter((k) => isPlainObject(rawServers[k]));
249
- if (!keys.length) throw new Error('plugin.json has no mcpServers');
250
- return { rawServers, mcpRoot: root };
251
242
  }
252
- if (!/\.json$/i.test(rel)) return null;
253
- const mcpJsonPath = resolveContainedPluginPath(root, rel);
254
- if (!mcpJsonPath || !existsSync(mcpJsonPath)) {
255
- throw new Error(`plugin MCP manifest not found: ${join(root, rel)}`);
243
+ const declaredPath = typeof declaredRef === 'string' ? declaredRef : null;
244
+ return { format, servers: [...into.values()], errors, declaredPath };
245
+ }
246
+
247
+ // Parsed declarations per plugin root, valid while every file the parse looked
248
+ // at (present or absent) still has the same identity, size and timestamps.
249
+ const DECLARATION_CACHE_MAX = 256;
250
+ const declarationCache = new Map();
251
+ const declarationCacheStats = { hits: 0, misses: 0 };
252
+
253
+ function fileStamp(path) {
254
+ try {
255
+ const st = statSync(path, { bigint: true });
256
+ return `${st.ino}:${st.size}:${st.mtimeNs}:${st.ctimeNs}`;
257
+ } catch {
258
+ return '-';
256
259
  }
257
- const rawServers = mcpServersMapFromJson(readJsonSafe(mcpJsonPath) || {});
258
- if (!isPlainObject(rawServers)) {
259
- throw new Error(`plugin MCP manifest missing mcpServers object: ${mcpJsonPath}`);
260
+ }
261
+
262
+ function copyDeclarations(result) {
263
+ return {
264
+ format: result.format,
265
+ servers: result.servers.map((server) => ({ ...server, config: structuredClone(server.config) })),
266
+ errors: result.errors.map((error) => ({ ...error })),
267
+ };
268
+ }
269
+
270
+ /** What a plugin declares for MCP, read raw: `<root>/.mcp.json` first, then the
271
+ * manifest `mcpServers` (a path, an inline object, or a list mixing both).
272
+ * Later sources override earlier ones by key. Never throws for plugin
273
+ * content; errors carry locations and field names, never values. Unchanged
274
+ * declaration files are not parsed again. */
275
+ export function loadPluginMcpDeclarations(root) {
276
+ return copyDeclarations(cachedDeclarations(root));
277
+ }
278
+
279
+ function cachedDeclarations(root) {
280
+ const key = String(root || '');
281
+ const cached = declarationCache.get(key);
282
+ if (cached?.signature.every(([path, stamp]) => fileStamp(path) === stamp)) {
283
+ declarationCacheStats.hits += 1;
284
+ return cached.result;
285
+ }
286
+ declarationCacheStats.misses += 1;
287
+ // Stamps are taken before reading, so a write racing the parse invalidates.
288
+ const before = new Map();
289
+ const result = readPluginMcpDeclarations(root, {
290
+ add(path) {
291
+ if (!before.has(path)) before.set(path, fileStamp(path));
292
+ },
293
+ });
294
+ declarationCache.delete(key);
295
+ declarationCache.set(key, { signature: [...before], result });
296
+ if (declarationCache.size > DECLARATION_CACHE_MAX) declarationCache.delete(declarationCache.keys().next().value);
297
+ return result;
298
+ }
299
+
300
+ /** Test seam: cache hit/miss counters. */
301
+ export function pluginMcpDeclarationCacheStats() {
302
+ return { ...declarationCacheStats };
303
+ }
304
+
305
+ // Adapter: where the declarations live, in the shape older callers expect.
306
+ export function discoverPluginMcp(root) {
307
+ const { servers, declaredPath } = cachedDeclarations(root);
308
+ if (!servers.length) return { mcpScript: null, mcpInline: false };
309
+ const inline = servers.some((s) => PLUGIN_MANIFEST_FILES.some((rel) => s.source === join(root, ...rel.split('/'))));
310
+ if (inline) return { mcpScript: null, mcpInline: true };
311
+ if (declaredPath?.trim()) {
312
+ const abs = resolveContainedPluginPath(root, declaredPath);
313
+ if (servers.some((s) => s.source === abs)) return { mcpScript: declaredPath.trim(), mcpInline: false };
260
314
  }
261
- const keys = Object.keys(rawServers).filter((k) => isPlainObject(rawServers[k]));
262
- if (!keys.length) throw new Error(`plugin MCP manifest has no mcpServers: ${mcpJsonPath}`);
263
- return { rawServers, mcpRoot: root };
315
+ const mcpJson = join(root, '.mcp.json');
316
+ const declaredFile = servers.find((s) => s.source !== mcpJson) || servers[0];
317
+ return { mcpScript: relative(root, declaredFile.source).split(sep).join('/'), mcpInline: false };
264
318
  }
265
319
 
266
- /** Every MCP entry a plugin owns: `<serverName>` and `<serverName>--<key>`. */
267
- export function pluginServerMatcher(serverName) {
268
- const prefix = `${serverName}--`;
269
- return (name) => name === serverName || name.startsWith(prefix);
320
+ // User intent for plugin-declared servers, stored apart from the declarations
321
+ // as `pluginMcp[pluginId][declaredKey]`: `enabled: false`, a kept `alias`
322
+ // name, and connection overrides layered over the declaration.
323
+ export const PLUGIN_MCP_OVERRIDE_MAP_FIELDS = Object.freeze(['env', 'headers', 'env_http_headers']);
324
+ export const PLUGIN_MCP_OVERRIDE_VALUE_FIELDS = Object.freeze([
325
+ 'command',
326
+ 'args',
327
+ 'url',
328
+ 'bearer_token_env_var',
329
+ 'env_vars',
330
+ ...MCP_TIMEOUT_FIELD_GROUPS.flat(),
331
+ ]);
332
+
333
+ function normalizePluginMcpIntent(value) {
334
+ if (!isPlainObject(value)) return null;
335
+ const out = {};
336
+ // `enabled: true` is kept too: it turns on a server declared `enabled: false`.
337
+ if (typeof value.enabled === 'boolean') out.enabled = value.enabled;
338
+ if (typeof value.alias === 'string' && value.alias.trim()) out.alias = value.alias.trim();
339
+ for (const field of PLUGIN_MCP_OVERRIDE_MAP_FIELDS) {
340
+ if (!isPlainObject(value[field])) continue;
341
+ const map = Object.fromEntries(Object.entries(value[field]).filter(([, v]) => v !== null && v !== undefined));
342
+ if (Object.keys(map).length) out[field] = map;
343
+ }
344
+ for (const field of PLUGIN_MCP_OVERRIDE_VALUE_FIELDS) {
345
+ if (value[field] !== undefined && value[field] !== null) out[field] = value[field];
346
+ }
347
+ return Object.keys(out).length ? out : null;
270
348
  }
271
349
 
272
- export function pluginMcpServerName(plugin = {}) {
273
- const base = clean(plugin.name || plugin.title || 'plugin')
274
- .toLowerCase()
275
- .replace(/[^a-z0-9_.-]+/g, '-')
276
- .replace(/^-+|-+$/g, '');
277
- return base ? `plugin-${base}` : 'plugin-mcp';
350
+ /** `{ [pluginId]: { [declaredKey]: intent } }` with empty records dropped. */
351
+ export function normalizePluginMcpIntents(value) {
352
+ const out = {};
353
+ if (!isPlainObject(value)) return out;
354
+ for (const [pluginId, servers] of Object.entries(value)) {
355
+ const id = clean(pluginId);
356
+ if (!id || !isPlainObject(servers)) continue;
357
+ const kept = {};
358
+ for (const [key, intent] of Object.entries(servers)) {
359
+ const normalized = normalizePluginMcpIntent(intent);
360
+ if (key && normalized) kept[key] = normalized;
361
+ }
362
+ if (Object.keys(kept).length) out[id] = kept;
363
+ }
364
+ return out;
278
365
  }
@@ -2,6 +2,7 @@
2
2
  // definitions out of the eager prefix; selection never changes permission policy.
3
3
  import { deferredCatalogUnion, selectDeferredTools, toolSearchNativePayload } from './tool-catalog.mjs';
4
4
  import { preDispatchDenyForSession } from '../session/loop/pre-dispatch-deny.mjs';
5
+ import { parseMcpToolName } from '../../../shared/tool-primitives.mjs';
5
6
 
6
7
  export function loadSkillToolDependencies(envelope, session, mode) {
7
8
  if (!envelope?.__toolEnvelope) return envelope;
@@ -15,7 +16,9 @@ export function loadSkillToolDependencies(envelope, session, mode) {
15
16
  for (const { type, value } of dependencies) {
16
17
  if (type === 'tool') names.add(value);
17
18
  else if (type === 'mcp') {
18
- const matches = [...catalog.keys()].filter((name) => name.startsWith(`mcp__${value}__`));
19
+ const matches = [...catalog.values()]
20
+ .filter((tool) => parseMcpToolName(tool)?.server === value)
21
+ .map((tool) => tool.name);
19
22
  if (matches.length)
20
23
  matches.forEach((name) => {
21
24
  names.add(name);
@@ -145,11 +145,32 @@ export function sortedCatalogByMeasuredUsage(catalog) {
145
145
  .map((entry) => entry.tool);
146
146
  }
147
147
 
148
+ const IMMUTABLE_SURFACE_TOOLS = new WeakSet();
149
+
150
+ function deepFreeze(value) {
151
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
152
+ Object.freeze(value);
153
+ for (const key of Object.keys(value)) deepFreeze(value[key]);
154
+ }
155
+ return value;
156
+ }
157
+
148
158
  export function activeToolForSurface(tool) {
149
159
  if (!tool || typeof tool !== 'object') return tool;
160
+ if (IMMUTABLE_SURFACE_TOOLS.has(tool)) return tool;
150
161
  return JSON.parse(JSON.stringify(tool));
151
162
  }
152
163
 
164
+ // Clone once into a deep-frozen definition that activeToolForSurface passes
165
+ // through without re-cloning. Safe to share: it can never be mutated in place.
166
+ export function immutableToolForSurface(tool) {
167
+ if (!tool || typeof tool !== 'object') return tool;
168
+ if (IMMUTABLE_SURFACE_TOOLS.has(tool)) return tool;
169
+ const frozen = deepFreeze(JSON.parse(JSON.stringify(tool)));
170
+ IMMUTABLE_SURFACE_TOOLS.add(frozen);
171
+ return frozen;
172
+ }
173
+
153
174
  export function deferredProviderMode(provider) {
154
175
  const p = clean(provider).toLowerCase();
155
176
  if (p === 'gemini') return 'manifest';
@@ -4,7 +4,7 @@
4
4
  import { clean } from './session-text.mjs';
5
5
  import { mergePendingDeferredToolDelta } from './deferred-tool-delta.mjs';
6
6
  import { applyInitialDeferredToolManifestToBp2, stripDeferredToolManifestBlock } from '../context/collect.mjs';
7
- import { getMcpServerInstructionsMap, getMcpTools } from '../mcp/client.mjs';
7
+ import { getMcpServerInstructionsMap, getMcpTools, getMcpToolGeneration } from '../mcp/client.mjs';
8
8
  import { filterMcpToolsForSession } from './extension-scopes.mjs';
9
9
  import { isDeferredToolAvailable } from './deferred-tool-availability.mjs';
10
10
  import { isResponsesFreeformTool, toResponsesCustomTool } from '../providers/custom-tool-wire.mjs';
@@ -23,6 +23,7 @@ import {
23
23
  routeToolRank,
24
24
  sortedCatalogByMeasuredUsage,
25
25
  activeToolForSurface,
26
+ immutableToolForSurface,
26
27
  deferredProviderMode,
27
28
  nativeProviderFamily,
28
29
  } from './tool-catalog-schema.mjs';
@@ -464,6 +465,56 @@ export function refreshInitialDeferredMcpSurface(session, liveMcpTools) {
464
465
  */
465
466
  export function reconcileDeferredMcpToolCatalog(session, liveMcpTools) {
466
467
  if (!session || !Array.isArray(session.tools)) return null;
468
+ const cached = RECONCILE_STATE.get(session);
469
+ if (cached && reconcileStateMatches(cached, reconcileStateOf(session, liveMcpTools))) return null;
470
+ const result = reconcileDeferredMcpToolCatalogUncached(session, liveMcpTools);
471
+ RECONCILE_STATE.set(session, reconcileStateOf(session, liveMcpTools));
472
+ return result;
473
+ }
474
+
475
+ // Last reconciled inputs per session. A matching state means the surface that
476
+ // reconciliation would rebuild is identical, so it is skipped outright.
477
+ const RECONCILE_STATE = new WeakMap();
478
+ const LIVE_CLONES = new WeakMap();
479
+
480
+ function liveToolClone(tool) {
481
+ if (!tool || typeof tool !== 'object') return tool;
482
+ const generation = getMcpToolGeneration();
483
+ const hit = LIVE_CLONES.get(tool);
484
+ if (hit && hit.generation === generation) return hit.clone;
485
+ const clone = immutableToolForSurface(tool);
486
+ LIVE_CLONES.set(tool, { generation, clone });
487
+ return clone;
488
+ }
489
+
490
+ function reconcileStateOf(session, liveMcpTools) {
491
+ return {
492
+ generation: getMcpToolGeneration(),
493
+ live: Array.isArray(liveMcpTools) ? liveMcpTools.slice() : [],
494
+ tools: session.tools.slice(),
495
+ lateCatalog: session.deferredLateToolCatalog,
496
+ scope: clean(session.mcpScopeId),
497
+ cwd: clean(session.cwd),
498
+ mode: clean(session.deferredSurfaceMode),
499
+ providerMode: clean(session.deferredProviderMode),
500
+ provider: clean(session.provider),
501
+ model: clean(session.model),
502
+ disallowed: JSON.stringify(session.disallowedTools || []),
503
+ callable: JSON.stringify(parseToolSelection(session.deferredCallableTools)),
504
+ discovered: JSON.stringify(parseToolSelection(session.deferredDiscoveredTools)),
505
+ };
506
+ }
507
+
508
+ function reconcileStateMatches(a, b) {
509
+ for (const key of Object.keys(a)) {
510
+ if (key === 'live' || key === 'tools') {
511
+ if (a[key].length !== b[key].length || a[key].some((tool, i) => tool !== b[key][i])) return false;
512
+ } else if (a[key] !== b[key]) return false;
513
+ }
514
+ return true;
515
+ }
516
+
517
+ function reconcileDeferredMcpToolCatalogUncached(session, liveMcpTools) {
467
518
  const live = filterDisallowedTools(Array.isArray(liveMcpTools) ? liveMcpTools : [], session.disallowedTools);
468
519
  const hadSnapshot = Array.isArray(session.deferredMcpToolNames);
469
520
  const previousNames = new Set(
@@ -482,7 +533,7 @@ export function reconcileDeferredMcpToolCatalog(session, liveMcpTools) {
482
533
  }
483
534
  for (const tool of live) {
484
535
  const name = clean(tool?.name);
485
- if (name && isMcpToolName(name)) byName.set(name, activeToolForSurface(tool));
536
+ if (name && isMcpToolName(name)) byName.set(name, liveToolClone(tool));
486
537
  }
487
538
  const catalog = sortedCatalogByMeasuredUsage([...byName.values()]);
488
539
  const next = catalog.filter((tool) => session.deferredSurfaceMode !== 'readonly' || isReadonlySelectable(tool));
@@ -505,7 +556,7 @@ export function reconcileDeferredMcpToolCatalog(session, liveMcpTools) {
505
556
  for (const tool of live) {
506
557
  const name = clean(tool?.name);
507
558
  if (!name || !isMcpToolName(name) || liveMcpByName.has(name)) continue;
508
- liveMcpByName.set(name, activeToolForSurface(tool));
559
+ liveMcpByName.set(name, liveToolClone(tool));
509
560
  }
510
561
 
511
562
  // Rebuild the LATE pool only (boot catalog stays frozen). It holds live MCP