@gajae-code/coding-agent 0.17.2 → 0.17.5

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 (390) hide show
  1. package/CHANGELOG.md +267 -0
  2. package/dist/types/cli/daemon-cli.d.ts +3 -1
  3. package/dist/types/cli/github-release.d.ts +17 -0
  4. package/dist/types/cli/public-command-entry.d.ts +56 -0
  5. package/dist/types/cli/public-command-errors.d.ts +126 -0
  6. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  7. package/dist/types/cli/public-command-help.d.ts +65 -0
  8. package/dist/types/cli/public-command-registry.d.ts +69 -0
  9. package/dist/types/cli/session-picker.d.ts +1 -1
  10. package/dist/types/cli/setup-cli.d.ts +1 -0
  11. package/dist/types/cli/skills-cli.d.ts +5 -0
  12. package/dist/types/cli.d.ts +3 -0
  13. package/dist/types/commands/daemon.d.ts +4 -50
  14. package/dist/types/commands/sdk.d.ts +20 -37
  15. package/dist/types/commands/setup.d.ts +3 -0
  16. package/dist/types/commands/skills.d.ts +9 -0
  17. package/dist/types/config/autorouting-tier-map.d.ts +1049 -3
  18. package/dist/types/config/file-lock.d.ts +13 -3
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +14 -3
  21. package/dist/types/config/model-resolver.d.ts +1 -1
  22. package/dist/types/config/settings-schema.d.ts +11 -0
  23. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  24. package/dist/types/daemon/operator-contract.d.ts +5 -1
  25. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  26. package/dist/types/exec/bash-executor.d.ts +2 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  28. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  29. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +11 -2
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -1
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  33. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  34. package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
  35. package/dist/types/harness-control-plane/finalize.d.ts +14 -3
  36. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  37. package/dist/types/internal-urls/index.d.ts +1 -0
  38. package/dist/types/internal-urls/parse.d.ts +9 -1
  39. package/dist/types/main.d.ts +23 -1
  40. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  41. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  42. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  43. package/dist/types/modes/components/session-selector.d.ts +5 -2
  44. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  45. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  46. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  47. package/dist/types/modes/interactive-mode.d.ts +1 -1
  48. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  49. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  50. package/dist/types/modes/types.d.ts +2 -2
  51. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  52. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  53. package/dist/types/runtime-mcp/manager.d.ts +69 -6
  54. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  55. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  56. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  57. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  58. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  59. package/dist/types/sdk/broker/broker.d.ts +17 -4
  60. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  61. package/dist/types/sdk/broker/identity.d.ts +5 -1
  62. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  63. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  64. package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
  65. package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
  66. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  67. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  68. package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
  69. package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
  70. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  71. package/dist/types/sdk/bus/index.d.ts +19 -0
  72. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  73. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  74. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  75. package/dist/types/sdk/cli/rows.d.ts +19 -1
  76. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  77. package/dist/types/sdk/client/client.d.ts +2 -0
  78. package/dist/types/sdk/guides/cli.d.ts +3 -4
  79. package/dist/types/sdk/guides/verify.d.ts +2 -0
  80. package/dist/types/sdk/host/host.d.ts +9 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  82. package/dist/types/sdk/host/types.d.ts +10 -0
  83. package/dist/types/sdk/index.d.ts +1 -0
  84. package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  86. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  87. package/dist/types/sdk/router/session-router.d.ts +2 -0
  88. package/dist/types/sdk/session.d.ts +3 -3
  89. package/dist/types/sdk/startup-capability.d.ts +2 -0
  90. package/dist/types/sdk/transport/relay.d.ts +1 -0
  91. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  92. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  93. package/dist/types/session/agent-session.d.ts +175 -23
  94. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  95. package/dist/types/session/fold-coordinator.d.ts +17 -3
  96. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  97. package/dist/types/session/session-manager.d.ts +64 -1
  98. package/dist/types/session/session-work-lease.d.ts +14 -0
  99. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  100. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  101. package/dist/types/task/types.d.ts +2 -0
  102. package/dist/types/tools/bash-interactive.d.ts +1 -0
  103. package/dist/types/tools/bash.d.ts +37 -11
  104. package/dist/types/tools/fetch.d.ts +10 -1
  105. package/dist/types/tools/job.d.ts +1 -1
  106. package/dist/types/tools/steer-fold.d.ts +31 -0
  107. package/dist/types/tools/subagent.d.ts +8 -1
  108. package/dist/types/utils/git.d.ts +28 -2
  109. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  110. package/dist/types/utils/mupdf.d.ts +4 -0
  111. package/dist/types/utils/session-title-generation.d.ts +6 -0
  112. package/dist/types/utils/title-generator.d.ts +11 -2
  113. package/dist/types/web/scrapers/types.d.ts +3 -0
  114. package/package.json +18 -10
  115. package/scripts/build-binary.ts +9 -3
  116. package/scripts/compile-args.ts +0 -9
  117. package/scripts/embed-mupdf.ts +52 -0
  118. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  119. package/scripts/vendor-markit.ts +196 -0
  120. package/src/async/job-manager.ts +22 -1
  121. package/src/cli/daemon-cli.ts +53 -18
  122. package/src/cli/github-release.ts +87 -5
  123. package/src/cli/mcp-cli.ts +38 -4
  124. package/src/cli/public-command-entry.ts +450 -0
  125. package/src/cli/public-command-errors.ts +581 -0
  126. package/src/cli/public-command-evidence.ts +717 -0
  127. package/src/cli/public-command-help.ts +360 -0
  128. package/src/cli/public-command-registry.ts +541 -0
  129. package/src/cli/session-picker.ts +2 -1
  130. package/src/cli/setup-cli.ts +10 -1
  131. package/src/cli/skills-cli.ts +46 -2
  132. package/src/cli/stats-cli.ts +11 -1
  133. package/src/cli.ts +61 -1
  134. package/src/commands/daemon.ts +65 -69
  135. package/src/commands/sdk.ts +175 -117
  136. package/src/commands/setup.ts +2 -0
  137. package/src/commands/skills.ts +11 -0
  138. package/src/config/autorouting-tier-map.ts +543 -3
  139. package/src/config/file-lock.ts +206 -65
  140. package/src/config/model-discovery-manager.ts +4 -4
  141. package/src/config/model-profile-activation.ts +417 -66
  142. package/src/config/model-registry.ts +254 -36
  143. package/src/config/model-resolver.ts +6 -1
  144. package/src/config/settings-schema.ts +26 -2
  145. package/src/coordinator-mcp/policy.ts +10 -5
  146. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  147. package/src/coordinator-mcp/question-state.ts +179 -9
  148. package/src/coordinator-mcp/server.ts +521 -87
  149. package/src/coordinator-mcp/session-reaper.ts +6 -4
  150. package/src/crash/record-loader.ts +6 -2
  151. package/src/daemon/operator-contract.ts +26 -12
  152. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  153. package/src/eval/js/context-manager.ts +8 -8
  154. package/src/eval/js/worker-core.ts +9 -4
  155. package/src/eval/js/worker-protocol.ts +2 -0
  156. package/src/exec/bash-executor.ts +3 -0
  157. package/src/extensibility/extensions/runner.ts +52 -4
  158. package/src/extensibility/extensions/types.ts +10 -0
  159. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
  161. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  162. package/src/extensibility/shared-events.ts +3 -1
  163. package/src/gjc-runtime/session-state-lock.ts +165 -24
  164. package/src/gjc-runtime/state-writer.ts +317 -6
  165. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  166. package/src/harness-control-plane/control-endpoint.ts +24 -6
  167. package/src/harness-control-plane/finalize.ts +312 -25
  168. package/src/harness-control-plane/owner.ts +323 -89
  169. package/src/internal-urls/docs-index.generated.ts +23 -20
  170. package/src/internal-urls/embedded-protocol.ts +83 -0
  171. package/src/internal-urls/index.ts +1 -0
  172. package/src/internal-urls/parse.ts +21 -1
  173. package/src/internal-urls/router.ts +4 -2
  174. package/src/main.ts +192 -74
  175. package/src/modes/acp/acp-agent.ts +973 -56
  176. package/src/modes/acp/startup-provenance.ts +59 -0
  177. package/src/modes/components/custom-provider-wizard.ts +308 -7
  178. package/src/modes/components/gjc-bundle-settings.ts +21 -1
  179. package/src/modes/components/oauth-selector.ts +9 -0
  180. package/src/modes/components/session-selector.ts +47 -4
  181. package/src/modes/components/tool-status-header.ts +72 -6
  182. package/src/modes/components/user-message-selector.ts +57 -15
  183. package/src/modes/controllers/command-controller.ts +58 -32
  184. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  185. package/src/modes/controllers/input-controller.ts +4 -0
  186. package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
  187. package/src/modes/controllers/selector-controller.ts +161 -15
  188. package/src/modes/interactive-mode.ts +21 -7
  189. package/src/modes/runtime-init.ts +1 -0
  190. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  191. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  192. package/src/modes/types.ts +2 -2
  193. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  194. package/src/prompts/system/title-system.md +1 -1
  195. package/src/prompts/tools/bash.md +1 -1
  196. package/src/prompts/tools/job.md +1 -1
  197. package/src/prompts/tools/read.md +2 -0
  198. package/src/runtime/network-prewarm-service.test.ts +36 -1
  199. package/src/runtime/network-prewarm-service.ts +28 -3
  200. package/src/runtime/process-lifecycle.ts +22 -11
  201. package/src/runtime-mcp/config.ts +16 -1
  202. package/src/runtime-mcp/loader.ts +6 -3
  203. package/src/runtime-mcp/manager.ts +845 -109
  204. package/src/runtime-mcp/pool.test.ts +31 -0
  205. package/src/runtime-mcp/pool.ts +304 -28
  206. package/src/runtime-mcp/redaction.ts +42 -0
  207. package/src/runtime-mcp/startup-policy.ts +7 -0
  208. package/src/runtime-mcp/tool-bridge.ts +71 -0
  209. package/src/runtime-mcp/transports/http.ts +108 -25
  210. package/src/sdk/acp/adapter.ts +27 -8
  211. package/src/sdk/broker/broker.ts +1431 -198
  212. package/src/sdk/broker/ensure.ts +21 -3
  213. package/src/sdk/broker/identity.ts +22 -1
  214. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  215. package/src/sdk/broker/lifecycle.ts +690 -141
  216. package/src/sdk/broker/managed-task-dag.ts +1927 -0
  217. package/src/sdk/broker/managed-task-verification.ts +642 -0
  218. package/src/sdk/broker/runtime.ts +1 -1
  219. package/src/sdk/broker/session-index.ts +97 -4
  220. package/src/sdk/broker/spawn-authority.ts +175 -14
  221. package/src/sdk/broker/startup-failure.ts +80 -1
  222. package/src/sdk/broker/stderr-drainer.ts +59 -0
  223. package/src/sdk/broker/transport.ts +1 -0
  224. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  225. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  226. package/src/sdk/bus/index.ts +707 -81
  227. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  228. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  229. package/src/sdk/bus/telegram-daemon.ts +10 -3
  230. package/src/sdk/cli/master-cli.ts +47 -36
  231. package/src/sdk/cli/rows.ts +30 -7
  232. package/src/sdk/cli/session-cli.ts +705 -184
  233. package/src/sdk/client/client.ts +18 -0
  234. package/src/sdk/guides/catalog.ts +2 -2
  235. package/src/sdk/guides/cli.ts +20 -62
  236. package/src/sdk/guides/verify.ts +14 -0
  237. package/src/sdk/host/host.ts +49 -5
  238. package/src/sdk/host/session-runtime.test.ts +184 -2
  239. package/src/sdk/host/session-runtime.ts +322 -101
  240. package/src/sdk/host/types.ts +12 -1
  241. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  242. package/src/sdk/host/websocket-transport.ts +40 -6
  243. package/src/sdk/index.ts +11 -0
  244. package/src/sdk/lifecycle/service.ts +2 -0
  245. package/src/sdk/prompt-deadline-flush.ts +356 -0
  246. package/src/sdk/prompt-deadline-manager.ts +133 -1
  247. package/src/sdk/prompt-tool-boundary.ts +121 -0
  248. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  249. package/src/sdk/router/session-router.ts +9 -1
  250. package/src/sdk/session.ts +763 -116
  251. package/src/sdk/startup-capability.ts +1 -1
  252. package/src/sdk/transport/relay.ts +71 -6
  253. package/src/sdk/transport/serve-cli.ts +397 -42
  254. package/src/sdk/transport/socket.ts +12 -11
  255. package/src/sdk/transport/stdio.ts +7 -3
  256. package/src/session/agent-session.ts +2472 -407
  257. package/src/session/fallback-chain-controller.ts +6 -7
  258. package/src/session/fold-coordinator.ts +24 -5
  259. package/src/session/internal/managed-session-scope.ts +68 -18
  260. package/src/session/internal/managed-session-storage.ts +334 -12
  261. package/src/session/session-manager.ts +382 -44
  262. package/src/session/session-work-lease.ts +34 -0
  263. package/src/setup/model-onboarding-guidance.ts +1 -1
  264. package/src/setup/provider-onboarding.ts +397 -24
  265. package/src/setup/provider-presets.json +2 -1
  266. package/src/slash-commands/builtin-registry.ts +259 -15
  267. package/src/task/executor.ts +46 -17
  268. package/src/task/index.ts +14 -2
  269. package/src/task/types.ts +2 -0
  270. package/src/tools/bash-interactive.ts +2 -0
  271. package/src/tools/bash.ts +118 -78
  272. package/src/tools/fetch.ts +140 -25
  273. package/src/tools/job.ts +151 -25
  274. package/src/tools/output-meta.ts +15 -2
  275. package/src/tools/path-utils.ts +27 -3
  276. package/src/tools/read.ts +3 -3
  277. package/src/tools/steer-fold.ts +107 -0
  278. package/src/tools/subagent-render.ts +24 -1
  279. package/src/tools/subagent.ts +27 -1
  280. package/src/tools/tool-catalog.generated.ts +29 -2
  281. package/src/tools/tool-errors.ts +4 -0
  282. package/src/utils/git.ts +53 -3
  283. package/src/utils/markit.ts +55 -41
  284. package/src/utils/mupdf-assets.d.ts +4 -0
  285. package/src/utils/mupdf-embedded.ts +4 -0
  286. package/src/utils/mupdf.ts +136 -0
  287. package/src/utils/session-title-generation.ts +29 -0
  288. package/src/utils/title-generator.ts +46 -2
  289. package/src/web/scrapers/types.ts +24 -3
  290. package/vendor/markit-ai/LICENSE +21 -0
  291. package/vendor/markit-ai/MANIFEST.json +412 -0
  292. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  293. package/vendor/markit-ai/dist/commands/config.js +139 -0
  294. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  295. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  296. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  297. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  298. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  299. package/vendor/markit-ai/dist/commands/init.js +29 -0
  300. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  301. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  302. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  303. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  304. package/vendor/markit-ai/dist/config.d.ts +26 -0
  305. package/vendor/markit-ai/dist/config.js +42 -0
  306. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  307. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  308. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  309. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  310. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  312. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  313. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  314. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  315. package/vendor/markit-ai/dist/converters/github.js +148 -0
  316. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  317. package/vendor/markit-ai/dist/converters/html.js +30 -0
  318. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  319. package/vendor/markit-ai/dist/converters/image.js +124 -0
  320. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  321. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  322. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  323. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  324. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  325. package/vendor/markit-ai/dist/converters/json.js +21 -0
  326. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  327. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  328. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  329. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  330. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  331. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  332. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  333. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  334. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  335. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  336. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  337. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  338. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  339. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  340. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  341. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  342. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  343. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  344. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  345. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  346. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  347. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  348. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  349. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  350. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  351. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  352. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  353. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  354. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  355. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  356. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  357. package/vendor/markit-ai/dist/index.d.ts +30 -0
  358. package/vendor/markit-ai/dist/index.js +26 -0
  359. package/vendor/markit-ai/dist/main.d.ts +2 -0
  360. package/vendor/markit-ai/dist/main.js +191 -0
  361. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  362. package/vendor/markit-ai/dist/markit.js +267 -0
  363. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  364. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  365. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  366. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  367. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  368. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  369. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  370. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  371. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  372. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  373. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  374. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  375. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  376. package/vendor/markit-ai/dist/providers/index.js +56 -0
  377. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  378. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  379. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  380. package/vendor/markit-ai/dist/providers/types.js +1 -0
  381. package/vendor/markit-ai/dist/types.d.ts +36 -0
  382. package/vendor/markit-ai/dist/types.js +1 -0
  383. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  384. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  385. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  386. package/vendor/markit-ai/dist/utils/output.js +31 -0
  387. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  388. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  389. package/vendor/markit-ai/package.json +74 -0
  390. package/vendor/markit-ai.patch +49 -0
@@ -59,6 +59,7 @@ import { loadCapability, reset as resetCapabilities } from "../capability";
59
59
  import { type Rule, ruleCapability, setActiveRules } from "../capability/rule";
60
60
  import type { SourceMeta } from "../capability/types";
61
61
  import { AUTOROUTING_INACTIVE_WARNING } from "../config/autorouting-contract";
62
+ import { ModelProfileCredentialError, resolveMissingSessionModelRecovery } from "../config/model-profile-activation";
62
63
  import { resolveModelProfileName } from "../config/model-profile-contract";
63
64
  import { resolveProfileBindings } from "../config/model-profiles";
64
65
  import { kNoAuth, ModelRegistry } from "../config/model-registry";
@@ -66,9 +67,11 @@ import {
66
67
  formatModelString,
67
68
  parseModelPattern,
68
69
  parseModelString,
70
+ refreshMissingQualifiedModelProviders,
69
71
  resolveAllowedModels,
70
72
  resolveModelChainWithAuth,
71
73
  resolveModelRoleValue,
74
+ resolveStartupModelRefreshSelectors,
72
75
  type ScopedModelSelection,
73
76
  } from "../config/model-resolver";
74
77
  import { normalizeModelSelectorValue } from "../config/model-selector-value";
@@ -115,9 +118,11 @@ import { resolveCurrentPhaseForParent } from "../extensibility/gjc-plugins/injec
115
118
  import { currentActivationFingerprint } from "../extensibility/gjc-plugins/lifecycle";
116
119
  import {
117
120
  buildPluginMcpConfigs,
121
+ type GjcPluginMcpServerProvenance,
118
122
  getGjcPluginToolDeclarations,
119
123
  loadAlwaysOnPluginTools,
120
124
  renderAlwaysOnSystemAppendices,
125
+ safePluginMcpDiagnostic,
121
126
  } from "../extensibility/gjc-plugins/runtime-adapters";
122
127
  import {
123
128
  GjcRuntimeFindingAccumulator,
@@ -144,8 +149,16 @@ import {
144
149
  createOptionalRuntimeServices,
145
150
  type OptionalRuntimeServicesOverrides,
146
151
  } from "../runtime/optional-runtime-services";
147
- import { loadAllMCPConfigs, MCPManager } from "../runtime-mcp";
152
+ import {
153
+ DeferredMCPTool,
154
+ isMCPStartupTimeoutError,
155
+ loadAllMCPConfigs,
156
+ MCPManager,
157
+ type MCPToolCache,
158
+ resolveMCPToolCache,
159
+ } from "../runtime-mcp";
148
160
  import type { MCPLoadResult } from "../runtime-mcp/manager";
161
+ import { MCP_STARTUP_WAIT_GRACE_MS } from "../runtime-mcp/startup-policy";
149
162
  import type { MCPServerConfig } from "../runtime-mcp/types";
150
163
  import {
151
164
  getNotificationConfig,
@@ -608,9 +621,9 @@ export interface CreateAgentSessionOptions {
608
621
  inheritedAsyncJobManager?: AsyncJobManager;
609
622
  /**
610
623
  * W6b: the parent's scope-held MCP facade, handed to a canonical sub-session so
611
- * it can inherit always-on MCP tools without owning the manager. Replaces the
612
- * removed `MCPManager.instance()` inheritance path; the sub-session never
613
- * connects, registers callbacks, or disposes this manager.
624
+ * it can inherit MCP tools without owning the manager. Replaces the removed
625
+ * `MCPManager.instance()` inheritance path; the sub-session subscribes to catalog
626
+ * changes but never connects or disposes this manager.
614
627
  */
615
628
  inheritedMcpManager?: import("../runtime-mcp/manager").MCPManager;
616
629
 
@@ -1247,6 +1260,29 @@ const DEFERRED_MCP_CONFIG_STARTUP_ERROR = "MCP tools could not be loaded.";
1247
1260
  const MAX_EXACT_MCP_TOOL_NAME_LENGTH = 100;
1248
1261
  const pluginMcpManagerServers = new WeakMap<MCPManager, ReadonlySet<string>>();
1249
1262
  const conventionalMcpManagerServers = new WeakMap<MCPManager, ReadonlySet<string>>();
1263
+ const failedPluginMcpManagerServers = new WeakMap<MCPManager, ReadonlySet<string>>();
1264
+ const pluginMcpCleanupFailures = new WeakSet<MCPManager>();
1265
+
1266
+ function sameMcpToolSnapshot(first: readonly CustomTool[], second: readonly CustomTool[]): boolean {
1267
+ return first.length === second.length && first.every((tool, index) => tool === second[index]);
1268
+ }
1269
+
1270
+ function classifyInheritedMcpToolNames(
1271
+ manager: MCPManager,
1272
+ tools: readonly CustomTool[],
1273
+ ): { pluginMcpToolNames: string[]; conventionalMcpToolNames: string[] } {
1274
+ const pluginServers = pluginMcpManagerServers.get(manager);
1275
+ const conventionalServers = conventionalMcpManagerServers.get(manager);
1276
+ const pluginMcpToolNames: string[] = [];
1277
+ const conventionalMcpToolNames: string[] = [];
1278
+ for (const tool of tools) {
1279
+ const serverName = tool.mcpServerName;
1280
+ if (serverName === undefined) continue;
1281
+ if (pluginServers?.has(serverName)) pluginMcpToolNames.push(tool.name);
1282
+ else if (conventionalServers?.has(serverName)) conventionalMcpToolNames.push(tool.name);
1283
+ }
1284
+ return { pluginMcpToolNames, conventionalMcpToolNames };
1285
+ }
1250
1286
 
1251
1287
  class ExactMcpToolNameCollisionError extends Error {
1252
1288
  constructor(toolNames: Iterable<string>) {
@@ -1261,23 +1297,43 @@ class ExactMcpToolNameCollisionError extends Error {
1261
1297
  class McpManagerCleanupError extends Error {
1262
1298
  readonly code = "MCP_MANAGER_CLEANUP_FAILED";
1263
1299
  constructor(cause: unknown) {
1264
- super(`Owned MCP manager cleanup failed: ${safeErrorDescription(cause)}`, { cause });
1300
+ super(`Owned MCP manager cleanup failed: ${safePluginMcpDiagnostic(cause)}`, { cause });
1265
1301
  this.name = "McpManagerCleanupError";
1266
1302
  }
1267
1303
  }
1268
1304
 
1305
+ class McpStartupCleanupRetryError extends Error {
1306
+ readonly primaryError: unknown;
1307
+ constructor(primaryError: unknown) {
1308
+ super("Owned MCP manager cleanup failed during startup", { cause: primaryError });
1309
+ this.name = "McpStartupCleanupRetryError";
1310
+ this.primaryError = primaryError;
1311
+ }
1312
+ }
1313
+
1269
1314
  class McpManagerCleanupDiagnosticError extends Error {
1270
1315
  readonly code = "MCP_MANAGER_CLEANUP_FAILED";
1271
1316
  readonly primaryError: unknown;
1272
1317
  readonly cleanupDiagnostic: { code: "MCP_MANAGER_CLEANUP_FAILED"; cause: unknown };
1273
1318
  constructor(primaryError: unknown, cleanupError: unknown) {
1274
- super(safeErrorDescription(primaryError), { cause: primaryError });
1319
+ super(safePluginMcpDiagnostic(primaryError), { cause: primaryError });
1275
1320
  this.name = "McpManagerCleanupDiagnosticError";
1276
1321
  this.primaryError = primaryError;
1277
1322
  this.cleanupDiagnostic = { code: "MCP_MANAGER_CLEANUP_FAILED", cause: cleanupError };
1278
1323
  }
1279
1324
  }
1280
1325
 
1326
+ class McpPluginStartupCleanupDiagnosticError extends Error {
1327
+ readonly primaryError: unknown;
1328
+ readonly cleanupDiagnostic: { code: "MCP_MANAGER_CLEANUP_FAILED"; cause: unknown };
1329
+ constructor(primaryError: unknown, cleanupError: unknown) {
1330
+ super("GJC plugin MCP startup and cleanup failed");
1331
+ this.name = "McpPluginStartupCleanupDiagnosticError";
1332
+ this.primaryError = primaryError;
1333
+ this.cleanupDiagnostic = { code: "MCP_MANAGER_CLEANUP_FAILED", cause: cleanupError };
1334
+ }
1335
+ }
1336
+
1281
1337
  function safeIsInstanceOf<T extends object>(value: unknown, ctor: abstract new (...args: any[]) => T): boolean {
1282
1338
  try {
1283
1339
  return value instanceof ctor;
@@ -1300,8 +1356,8 @@ function safeCleanupDiagnosticForLog(value: unknown): { code: string; cause: str
1300
1356
  const code = safeReadProperty(value, "code");
1301
1357
  const nestedCause = safeReadProperty(value, "cause");
1302
1358
  return {
1303
- code: typeof code === "string" ? code : "MCP_MANAGER_CLEANUP_FAILED",
1304
- cause: safeErrorDescription(nestedCause === undefined ? value : nestedCause),
1359
+ code: typeof code === "string" ? safePluginMcpDiagnostic(code) : "MCP_MANAGER_CLEANUP_FAILED",
1360
+ cause: safePluginMcpDiagnostic(nestedCause === undefined ? value : nestedCause),
1305
1361
  };
1306
1362
  }
1307
1363
  function safeReadCleanupDiagnostic(value: unknown): unknown {
@@ -1519,7 +1575,33 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1519
1575
  let asyncJobManagerOwned = false;
1520
1576
  let asyncJobManagerAdmitted = false;
1521
1577
  let priorAsyncJobManager: AsyncJobManager | undefined;
1522
- let cleanupOwnedMcpManager: (() => Promise<void>) | undefined;
1578
+ type OwnedMcpManagerCleanup = () => Promise<void>;
1579
+ let cleanupOwnedMcpManager: OwnedMcpManagerCleanup | undefined;
1580
+ let cleanupOwnedMcpManagerOwner: MCPManager | undefined;
1581
+ const registerOwnedMcpManagerCleanup = (owned: MCPManager): OwnedMcpManagerCleanup => {
1582
+ let cleanupAttempt: Promise<void> | undefined;
1583
+ const cleanup: OwnedMcpManagerCleanup = () => {
1584
+ if (cleanupAttempt) return cleanupAttempt;
1585
+ cleanupAttempt = Promise.resolve()
1586
+ .then(() => owned.disconnectAll())
1587
+ .then(
1588
+ () => {
1589
+ if (cleanupOwnedMcpManager === cleanup) {
1590
+ cleanupOwnedMcpManager = undefined;
1591
+ cleanupOwnedMcpManagerOwner = undefined;
1592
+ }
1593
+ },
1594
+ error => {
1595
+ cleanupAttempt = undefined;
1596
+ throw error;
1597
+ },
1598
+ );
1599
+ return cleanupAttempt;
1600
+ };
1601
+ cleanupOwnedMcpManager = cleanup;
1602
+ cleanupOwnedMcpManagerOwner = owned;
1603
+ return cleanup;
1604
+ };
1523
1605
  const agentRegistry = options.agentRegistry ?? AgentRegistry.global();
1524
1606
  const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
1525
1607
  const resolvedAgentDisplayName = options.agentDisplayName ?? (isCanonicalSubSession ? "sub" : "main");
@@ -1557,6 +1639,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1557
1639
  unsubscribeCredentialDisabled = undefined;
1558
1640
  unsubscribe?.();
1559
1641
  };
1642
+ let inheritedMcpToolsPublisher: ((tools: readonly CustomTool[]) => void) | undefined;
1643
+ let inheritedMcpToolsUnsubscribe: (() => void) | undefined;
1644
+ const stopInheritedMcpToolsSubscription = (): void => {
1645
+ inheritedMcpToolsPublisher = undefined;
1646
+ const unsubscribe = inheritedMcpToolsUnsubscribe;
1647
+ inheritedMcpToolsUnsubscribe = undefined;
1648
+ unsubscribe?.();
1649
+ };
1560
1650
 
1561
1651
  try {
1562
1652
  // Subscribe before any getApiKey() call so startup model probes can't fire a
@@ -1903,6 +1993,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1903
1993
  );
1904
1994
  }
1905
1995
  if (!options.modelRegistry && !attemptedStartupCacheAdmission) {
1996
+ // Match the CLI's awaited startup discovery: the background refresh below
1997
+ // is not a readiness barrier, so resolving a fresh configured default first
1998
+ // can reject a model that the provider would discover moments later.
1999
+ const startupModelSelectors = resolveStartupModelRefreshSelectors(
2000
+ {
2001
+ model: options.modelPattern ?? (options.model ? formatModelString(options.model) : undefined),
2002
+ credential: options.credentialSelector ? "session" : undefined,
2003
+ resume: hasExistingSession,
2004
+ hasStartupProfile: Boolean(options.activeModelProfile || settings.get("modelProfile.default")),
2005
+ },
2006
+ settings,
2007
+ );
2008
+ await refreshMissingQualifiedModelProviders(startupModelSelectors, modelRegistry, credentialSessionId);
1906
2009
  modelRegistry.refreshInBackground("online-if-uncached", credentialSessionId);
1907
2010
  }
1908
2011
 
@@ -1968,9 +2071,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1968
2071
  const persistedProfileOwnsDefault = acceptedPersistedProfileName
1969
2072
  ? resolveProfileBindings(persistedProfiles.get(acceptedPersistedProfileName)!).defaultSelector !== undefined
1970
2073
  : false;
1971
- const startupActiveModelProfile =
2074
+ let startupActiveModelProfile =
1972
2075
  acceptedInheritedProfileName ??
1973
2076
  (!hasExplicitModel && acceptedPersistedProfileName ? acceptedPersistedProfileName : undefined);
2077
+ let savedDefaultWasUnresolved = false;
2078
+ let deferredMissingSessionRecovery = false;
2079
+ let retainedRecoveryBindingsAfterLateRestore = false;
2080
+ let recoveredSessionDefault:
2081
+ | {
2082
+ entries: string[];
2083
+ profileName: string;
2084
+ activeIndex: number;
2085
+ thinkingLevel?: ThinkingLevel;
2086
+ explicitThinkingLevel: boolean;
2087
+ skips: Array<{ selector: string; reason: string }>;
2088
+ }
2089
+ | undefined;
1974
2090
  // If session has data, restore its configured default chain rather than the
1975
2091
  // scalar runtime model, which may be a stale fallback from the prior run.
1976
2092
  if (!hasExplicitModel && !model && hasExistingSession && defaultModelEntries.length > 0) {
@@ -1994,6 +2110,33 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1994
2110
  if (model && preferredCredentialProvider && model.provider !== preferredCredentialProvider) {
1995
2111
  model = undefined;
1996
2112
  }
2113
+ savedDefaultWasUnresolved = !model;
2114
+ if (!model && resumeModelBehavior !== "useCurrentDefault") {
2115
+ try {
2116
+ const recovery = await resolveMissingSessionModelRecovery({
2117
+ modelRegistry,
2118
+ settings,
2119
+ defaultEntries: defaultModelEntries,
2120
+ skips: restoredDefaultResolution.skips,
2121
+ savedDefault: existingSession.models.default,
2122
+ credentialSessionId,
2123
+ ...(persistedProfileOwnsDefault ? { aliasIntent: "preset-equivalent" as const } : {}),
2124
+ });
2125
+ deferredMissingSessionRecovery = true;
2126
+ if (
2127
+ recovery?.model &&
2128
+ (!preferredCredentialProvider || recovery.model.provider === preferredCredentialProvider)
2129
+ ) {
2130
+ model = recovery.model;
2131
+ recoveredSessionDefault = recovery;
2132
+ startupActiveModelProfile = undefined;
2133
+ modelFallbackMessage =
2134
+ "Saved session model is no longer registered; restored the durable default preset instead.";
2135
+ }
2136
+ } catch {
2137
+ deferredMissingSessionRecovery = true;
2138
+ }
2139
+ }
1997
2140
  if (!model) modelFallbackMessage = `Could not restore model ${defaultModelEntries.join(" -> ")}`;
1998
2141
  });
1999
2142
  }
@@ -2029,6 +2172,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2029
2172
  thinkingLevel = restoredThinkingLevel;
2030
2173
  }
2031
2174
 
2175
+ if (
2176
+ thinkingLevel === undefined &&
2177
+ (restoredThinkingLevel === undefined || restoredThinkingLevel === ThinkingLevel.Inherit) &&
2178
+ recoveredSessionDefault?.explicitThinkingLevel
2179
+ ) {
2180
+ thinkingLevel = recoveredSessionDefault.thinkingLevel;
2181
+ }
2182
+
2032
2183
  if (thinkingLevel === undefined && !hasExplicitModel && defaultRoleSpec.explicitThinkingLevel) {
2033
2184
  thinkingLevel = defaultRoleSpec.thinkingLevel;
2034
2185
  }
@@ -2308,11 +2459,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2308
2459
  };
2309
2460
  let mcpManager: MCPManager | undefined = options.mcpManager;
2310
2461
  let ownsMcpManager = false;
2462
+ let ownedMcpToolCache: MCPToolCache | null | undefined;
2463
+ const getOwnedMcpToolCache = async (): Promise<MCPToolCache | null> => {
2464
+ if (ownedMcpToolCache === undefined)
2465
+ ownedMcpToolCache = await resolveMCPToolCache(undefined, getAgentDbPath(agentDir));
2466
+ return ownedMcpToolCache;
2467
+ };
2311
2468
  const cwdCapturingToolNames: string[] = [];
2312
2469
  const ownedConventionalMcpServerNames = new Set<string>();
2313
- let ownedConventionalMcpToolNames: string[] = [];
2314
- let publishOwnedConventionalMcpTools = false;
2315
- let ownedPluginServersConnected = false;
2470
+ const cachedConventionalMcpServerNames = new Set<string>();
2471
+ let ownedMcpManagerToolNames: string[] = [];
2472
+ let ownedMcpManagerTools: readonly CustomTool[] = [];
2473
+ let publishOwnedMcpTools = false;
2316
2474
  const notificationDebounceTimers = new Map<string, Timer>();
2317
2475
  const wireMcpManagerCallbacks = (manager: MCPManager): void => {
2318
2476
  manager.setOnPromptsChanged(serverName => {
@@ -2367,6 +2525,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2367
2525
  if (previousManager) await previousManager.disconnectAll().catch(() => {});
2368
2526
  pluginMcpToolNames.length = 0;
2369
2527
  conventionalMcpToolNames.length = 0;
2528
+ ownedConventionalMcpServerNames.clear();
2529
+ cachedConventionalMcpServerNames.clear();
2530
+ ownedMcpManagerToolNames = [];
2531
+ ownedMcpManagerTools = [];
2532
+ publishOwnedMcpTools = false;
2370
2533
  let nextManager: MCPManager | undefined;
2371
2534
  try {
2372
2535
  const loaded = await loadAllMCPConfigs(to, {
@@ -2390,11 +2553,51 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2390
2553
  ]),
2391
2554
  ),
2392
2555
  };
2556
+ const conventionalCacheServerNames = new Set(
2557
+ Object.keys(loaded.configs).filter(name => !pluginNames.has(name)),
2558
+ );
2559
+ for (const name of conventionalCacheServerNames) ownedConventionalMcpServerNames.add(name);
2560
+ publishOwnedMcpTools = Object.keys(mergedConfigs).length > 0;
2393
2561
  if (Object.keys(mergedConfigs).length > 0) {
2394
- nextManager = new MCPManager(to, null, { sharedPoolIdleMs: settings.get("mcp.sharedPoolIdleMs") });
2562
+ nextManager = new MCPManager(
2563
+ to,
2564
+ conventionalCacheServerNames.size > 0 ? await getOwnedMcpToolCache() : null,
2565
+ {
2566
+ sharedPoolIdleMs: settings.get("mcp.sharedPoolIdleMs"),
2567
+ toolCacheServerNames: conventionalCacheServerNames,
2568
+ },
2569
+ );
2395
2570
  nextManager.setAuthStorage(authStorage);
2396
2571
  wireMcpManagerCallbacks(nextManager);
2397
2572
  const result = await nextManager.connectServers(mergedConfigs, mergedSources as never);
2573
+ const connectedPluginNames = new Set(result.connectedServers.filter(name => pluginNames.has(name)));
2574
+ pluginMcpManagerServers.set(nextManager, new Set(pluginNames));
2575
+ conventionalMcpManagerServers.set(nextManager, conventionalCacheServerNames);
2576
+ failedPluginMcpManagerServers.set(nextManager, new Set());
2577
+ for (const tool of result.tools) {
2578
+ const serverName = tool.mcpServerName;
2579
+ if (
2580
+ tool instanceof DeferredMCPTool &&
2581
+ serverName &&
2582
+ conventionalCacheServerNames.has(serverName)
2583
+ ) {
2584
+ cachedConventionalMcpServerNames.add(serverName);
2585
+ }
2586
+ }
2587
+ ownedMcpManagerTools = result.tools as CustomTool[];
2588
+ ownedMcpManagerToolNames = result.tools.map(tool => tool.name);
2589
+ const unsettledConventionalNames = [...conventionalCacheServerNames].filter(
2590
+ name =>
2591
+ nextManager?.getConnectionStatus(name) === "connecting" ||
2592
+ cachedConventionalMcpServerNames.has(name),
2593
+ );
2594
+ if (pluginNames.size > 0) {
2595
+ if (connectedPluginNames.size > 0 && unsettledConventionalNames.length === 0) {
2596
+ nextManager.sealConnectionSet();
2597
+ } else {
2598
+ nextManager.freezeConnectionSet();
2599
+ }
2600
+ }
2398
2601
  nextCustomTools.push(...(result.tools as CustomTool[]));
2399
2602
  nextCwdCapturing.push(...result.tools.map(tool => tool.name));
2400
2603
  for (const tool of result.tools) {
@@ -2412,14 +2615,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2412
2615
  mcpManager = nextManager;
2413
2616
  ownsMcpManager = Boolean(nextManager);
2414
2617
  await session.replaceOwnedMcpManager(nextManager);
2415
- await session.refreshMCPTools((nextManager?.getTools() ?? []) as CustomTool[]);
2618
+ await session.refreshMCPTools((nextManager?.getTools() ?? []) as CustomTool[], {
2619
+ mandatoryMCPToolNames: pluginMcpToolNames,
2620
+ });
2416
2621
  }
2417
2622
  cwdCapturingToolNames.length = 0;
2418
2623
  cwdCapturingToolNames.push(...nextCwdCapturing);
2419
2624
  await session.replaceNamedCustomTools(
2420
2625
  previousCwdCapturing.filter(name => !nextCustomTools.some(tool => tool.name === name)),
2421
2626
  nextCustomTools,
2627
+ { mandatoryMCPToolNames: pluginMcpToolNames },
2422
2628
  );
2629
+ wireOwnedMcpToolSync();
2423
2630
  };
2424
2631
 
2425
2632
  /**
@@ -2982,10 +3189,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2982
3189
  const exactMcpToolNames: string[] = [];
2983
3190
  const pluginMcpToolNames: string[] = [];
2984
3191
  const conventionalMcpToolNames: string[] = [];
3192
+ let inheritedMcpManager: MCPManager | undefined;
3193
+ let inheritedMcpManagerToolNames: string[] = [];
3194
+ let inheritedMcpToolsSnapshot: readonly CustomTool[] = [];
3195
+ let pendingInheritedMcpToolsSnapshot: readonly CustomTool[] | undefined;
2985
3196
  let deferredExactMcpConfig: { manager: MCPManager; configPath: string } | undefined;
2986
3197
  let deferredConventionalMcp:
2987
3198
  | {
2988
3199
  manager: MCPManager;
3200
+ cleanup: OwnedMcpManagerCleanup;
2989
3201
  configs: Record<string, MCPServerConfig>;
2990
3202
  sources: Record<string, SourceMeta>;
2991
3203
  conventionalConfigs: Record<string, MCPServerConfig>;
@@ -3105,74 +3317,179 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3105
3317
  mergedSources: Record<string, SourceMeta>,
3106
3318
  conventionalConfigs: Record<string, MCPServerConfig>,
3107
3319
  pluginNames: ReadonlySet<string>,
3320
+ pluginMcpProvenance: ReadonlyMap<string, GjcPluginMcpServerProvenance>,
3321
+ cleanupOwned: OwnedMcpManagerCleanup,
3108
3322
  ): Promise<MCPLoadResult> => {
3109
3323
  let result: MCPLoadResult;
3110
3324
  try {
3111
3325
  result = await owned.connectServers(mergedConfigs, mergedSources as never);
3112
3326
  } catch (error) {
3113
- if (safeIsInstanceOf(error, McpManagerCleanupError)) throw error;
3327
+ if (
3328
+ safeIsInstanceOf(error, McpManagerCleanupError) ||
3329
+ safeIsInstanceOf(error, McpManagerCleanupDiagnosticError) ||
3330
+ safeReadCleanupDiagnostic(error) !== undefined
3331
+ )
3332
+ throw error;
3114
3333
  // Avoid leaking partially-started server processes on failure.
3334
+ const retryAfterStartupCleanupFailure = options.enableMCP === false && !options.deferMcpConfigStartup;
3115
3335
  let cleanupError: unknown;
3336
+ let cleanupFailed = false;
3116
3337
  try {
3117
- await owned.disconnectAll();
3338
+ await cleanupOwned();
3118
3339
  } catch (disconnectError) {
3340
+ cleanupFailed = true;
3119
3341
  cleanupError = disconnectError;
3120
- } finally {
3121
- cleanupOwnedMcpManager = undefined;
3122
3342
  }
3123
- if (cleanupError !== undefined) throw attachMcpCleanupDiagnostic(error, cleanupError);
3343
+ if (cleanupFailed) {
3344
+ if (retryAfterStartupCleanupFailure) throw new McpStartupCleanupRetryError(error);
3345
+ if (pluginNames.size > 0) {
3346
+ throw new McpPluginStartupCleanupDiagnosticError(error, cleanupError);
3347
+ }
3348
+ throw attachMcpCleanupDiagnostic(error, cleanupError);
3349
+ }
3124
3350
  throw error;
3125
3351
  }
3352
+ const conventionalServerNames = new Set(
3353
+ Object.keys(conventionalConfigs).filter(name => !pluginNames.has(name)),
3354
+ );
3355
+ for (const tool of result.tools) {
3356
+ if (tool instanceof DeferredMCPTool && conventionalServerNames.has(tool.mcpServerName)) {
3357
+ cachedConventionalMcpServerNames.add(tool.mcpServerName);
3358
+ }
3359
+ }
3126
3360
  for (const [server, err] of result.errors) {
3127
- // A server that failed to connect leaves this generation incomplete: its
3128
- // surfaces produced no evidence, so publishing would present a partial
3129
- // pass as a clear one. On the deferred path this flag is already
3130
- // consumed (publication happens before the starter can run), which is
3131
- // acceptable only because deferral never carries plugin-bundle servers.
3132
- gjcProducersComplete = false;
3133
- logger.warn("GJC plugin MCP connect failed", {
3361
+ const provenance = pluginMcpProvenance.get(server);
3362
+ if (provenance) {
3363
+ gjcFindings.add({
3364
+ identity: provenance.identity,
3365
+ surfaceId: provenance.surfaceId,
3366
+ code: "runtime_mismatch",
3367
+ message: `MCP server connection failed: ${safePluginMcpDiagnostic(err)}`,
3368
+ decision: "error",
3369
+ provenance: {
3370
+ source: "plugin-bundle",
3371
+ plugin: provenance.identity.name,
3372
+ scope: provenance.identity.scope,
3373
+ },
3374
+ });
3375
+ }
3376
+ if (provenance) {
3377
+ logger.error("GJC plugin MCP connection failed", {
3378
+ path: `mcp:${server}`,
3379
+ error: safePluginMcpDiagnostic(err),
3380
+ });
3381
+ } else if (!isMCPStartupTimeoutError(err)) {
3382
+ logger.warn("MCP server connection failed", {
3383
+ path: `mcp:${server}`,
3384
+ // MCPLoadResult stores failures as strings, so their original
3385
+ // type is unavailable here; use the manager classifier's safe fallback.
3386
+ error: "transport-error",
3387
+ });
3388
+ }
3389
+ }
3390
+ const failedPluginNames = new Set([...result.errors.keys()].filter(name => pluginMcpProvenance.has(name)));
3391
+ const connectedPluginNames = new Set(
3392
+ result.connectedServers.filter(name => pluginNames.has(name) && !failedPluginNames.has(name)),
3393
+ );
3394
+ const trustedPluginServerNames = new Set(
3395
+ [...pluginMcpProvenance.keys()].filter(name => !failedPluginNames.has(name)),
3396
+ );
3397
+ const connectedTrustedPluginNames = new Set(
3398
+ [...connectedPluginNames].filter(name => trustedPluginServerNames.has(name)),
3399
+ );
3400
+ const unsettledPluginNames = [...pluginNames].filter(
3401
+ name =>
3402
+ !failedPluginNames.has(name) &&
3403
+ !connectedPluginNames.has(name) &&
3404
+ owned.getConnectionStatus(name) !== "disconnected",
3405
+ );
3406
+ let pluginCleanupFailed = false;
3407
+ for (const server of unsettledPluginNames) {
3408
+ const provenance = pluginMcpProvenance.get(server);
3409
+ if (!provenance) continue;
3410
+ let message =
3411
+ "MCP server did not produce a complete startup result within its declared startup window; its tools are unavailable for this session";
3412
+ let cleanupDiagnostic: string | undefined;
3413
+ failedPluginNames.add(server);
3414
+ try {
3415
+ await owned.disconnectServer(server);
3416
+ } catch (error) {
3417
+ pluginCleanupFailed = true;
3418
+ cleanupDiagnostic = safePluginMcpDiagnostic(error);
3419
+ }
3420
+ if (owned.getConnectionStatus(server) !== "disconnected") {
3421
+ pluginCleanupFailed = true;
3422
+ cleanupDiagnostic ??= "server remained connected or connecting after disconnect";
3423
+ }
3424
+ if (cleanupDiagnostic !== undefined) message += `; cleanup diagnostic: ${cleanupDiagnostic}`;
3425
+ gjcFindings.add({
3426
+ identity: provenance.identity,
3427
+ surfaceId: provenance.surfaceId,
3428
+ code: "runtime_mismatch",
3429
+ message,
3430
+ decision: "error",
3431
+ provenance: {
3432
+ source: "plugin-bundle",
3433
+ plugin: provenance.identity.name,
3434
+ scope: provenance.identity.scope,
3435
+ },
3436
+ });
3437
+ logger.error("GJC plugin MCP startup did not settle", {
3134
3438
  path: `mcp:${server}`,
3135
- error: safeErrorForLog(err),
3439
+ error: message,
3440
+ ...(cleanupDiagnostic === undefined ? {} : { cleanupDiagnostic }),
3136
3441
  });
3137
3442
  }
3138
- const connectedPluginNames = new Set(result.connectedServers.filter(name => pluginNames.has(name)));
3139
- // Retain while any conventional server is still live: "connecting"
3140
- // covers the declared-timeout window, and a server that landed in
3141
- // "connected" inside the microtask between connectServers() resolving
3142
- // and this synchronous check must not be torn down either.
3143
- const unsettledConventionalNames = Object.keys(conventionalConfigs).filter(
3144
- name => owned.getConnectionStatus(name) !== "disconnected",
3443
+ failedPluginMcpManagerServers.set(owned, new Set(failedPluginNames));
3444
+ if (pluginCleanupFailed) pluginMcpCleanupFailures.add(owned);
3445
+ const successfulTools = result.tools.filter(
3446
+ tool => tool.mcpServerName === undefined || !failedPluginNames.has(tool.mcpServerName),
3447
+ );
3448
+ // Only a connection still in its startup window or a cached fallback
3449
+ // needs the mixed manager to remain mutable. Successfully connected
3450
+ // ordinary tools do not delay the synchronous seal; cached fallbacks keep
3451
+ // their reconnect path available while they remain published.
3452
+ const unsettledConventionalNames = [...conventionalServerNames].filter(
3453
+ name => owned.getConnectionStatus(name) === "connecting" || cachedConventionalMcpServerNames.has(name),
3145
3454
  );
3146
- const retainOwnedManager = result.connectedServers.length > 0 || unsettledConventionalNames.length > 0;
3455
+ const retainOwnedManager =
3456
+ result.connectedServers.length > 0 ||
3457
+ unsettledConventionalNames.length > 0 ||
3458
+ successfulTools.length > 0 ||
3459
+ pluginCleanupFailed;
3147
3460
  if (retainOwnedManager) {
3148
3461
  mcpManager = owned;
3149
3462
  ownsMcpManager = true;
3150
- customTools.push(...(result.tools as CustomTool[]));
3151
- cwdCapturingToolNames.push(...result.tools.map(tool => tool.name));
3152
- for (const name of Object.keys(conventionalConfigs)) ownedConventionalMcpServerNames.add(name);
3153
- pluginMcpManagerServers.set(owned, connectedPluginNames);
3154
- conventionalMcpManagerServers.set(owned, new Set(Object.keys(conventionalConfigs)));
3155
- for (const tool of result.tools) {
3463
+ customTools.push(...(successfulTools as CustomTool[]));
3464
+ ownedMcpManagerTools = successfulTools as CustomTool[];
3465
+ ownedMcpManagerToolNames = successfulTools.map(tool => tool.name);
3466
+ cwdCapturingToolNames.push(...successfulTools.map(tool => tool.name));
3467
+ for (const name of conventionalServerNames) ownedConventionalMcpServerNames.add(name);
3468
+ pluginMcpManagerServers.set(owned, trustedPluginServerNames);
3469
+ conventionalMcpManagerServers.set(owned, conventionalServerNames);
3470
+ for (const tool of successfulTools) {
3156
3471
  const serverName = tool.mcpServerName;
3157
3472
  if (serverName === undefined) continue;
3158
- if (connectedPluginNames.has(serverName)) pluginMcpToolNames.push(tool.name);
3473
+ if (connectedTrustedPluginNames.has(serverName)) pluginMcpToolNames.push(tool.name);
3159
3474
  else {
3160
3475
  conventionalMcpToolNames.push(tool.name);
3161
- ownedConventionalMcpToolNames.push(tool.name);
3162
3476
  }
3163
3477
  }
3164
- publishOwnedConventionalMcpTools = ownedConventionalMcpServerNames.size > 0;
3165
- ownedPluginServersConnected = connectedPluginNames.size > 0;
3166
- // Plugin-bundle connections are fixed for the session lifetime only
3167
- // when no conventional server is still connecting in the same manager;
3168
- // otherwise the seal is re-applied once they settle (below).
3169
- if (connectedPluginNames.size > 0 && unsettledConventionalNames.length === 0) owned.sealConnectionSet();
3478
+ publishOwnedMcpTools = true;
3479
+ // Keep plugin config authority fixed even while a conventional cached
3480
+ // fallback needs reconnects; a full reload would disconnect and lose the
3481
+ // plugin-bundle configs because native discovery does not reload them.
3482
+ if (pluginNames.size > 0) {
3483
+ if (connectedTrustedPluginNames.size > 0 && unsettledConventionalNames.length === 0) {
3484
+ owned.sealConnectionSet();
3485
+ } else {
3486
+ owned.freezeConnectionSet();
3487
+ }
3488
+ }
3170
3489
  } else {
3171
3490
  try {
3172
- await owned.disconnectAll();
3173
- cleanupOwnedMcpManager = undefined;
3491
+ await cleanupOwned();
3174
3492
  } catch (cleanupError) {
3175
- cleanupOwnedMcpManager = undefined;
3176
3493
  throw new McpManagerCleanupError(cleanupError);
3177
3494
  }
3178
3495
  }
@@ -3189,7 +3506,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3189
3506
  owned.setAuthStorage(authStorage);
3190
3507
  mcpManager = owned;
3191
3508
  ownsMcpManager = true;
3192
- cleanupOwnedMcpManager = () => owned.disconnectAll();
3509
+ registerOwnedMcpManagerCleanup(owned);
3193
3510
  if (options.deferMcpConfigStartup) {
3194
3511
  deferredExactMcpConfig = { manager: owned, configPath: explicitMcpConfigPath };
3195
3512
  } else {
@@ -3257,7 +3574,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3257
3574
  // tools are surfaced as always-on tools rather than gated behind MCP
3258
3575
  // selection.
3259
3576
  try {
3260
- const { configs, quarantine } = await buildPluginMcpConfigs({ cwd });
3577
+ const { configs, quarantine, serverProvenance: pluginMcpProvenance } = await buildPluginMcpConfigs({ cwd });
3261
3578
  for (const q of quarantine) {
3262
3579
  gjcFindings.add({
3263
3580
  identity: q.identity,
@@ -3284,9 +3601,30 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3284
3601
  // own bypass of the same mismatch at the manager boundary).
3285
3602
  const mergedSourceMetas = mergedSources as Record<string, SourceMeta>;
3286
3603
  if (Object.keys(mergedConfigs).length > 0) {
3287
- const owned = new MCPManager(cwd, null, { sharedPoolIdleMs: settings.get("mcp.sharedPoolIdleMs") });
3604
+ const conventionalCacheServerNames = new Set(
3605
+ Object.keys(conventionalConfigs).filter(name => !pluginNames.has(name)),
3606
+ );
3607
+ const maxPluginMcpTimeoutMs = Object.values(configs).reduce((maximum, config) => {
3608
+ const timeout = config.timeout;
3609
+ return typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0
3610
+ ? Math.max(maximum, timeout)
3611
+ : maximum;
3612
+ }, 0);
3613
+ const maxPluginMcpStartupWaitMs =
3614
+ maxPluginMcpTimeoutMs > 0 ? maxPluginMcpTimeoutMs + MCP_STARTUP_WAIT_GRACE_MS : undefined;
3615
+ const owned = new MCPManager(
3616
+ cwd,
3617
+ conventionalCacheServerNames.size > 0 ? await getOwnedMcpToolCache() : null,
3618
+ {
3619
+ sharedPoolIdleMs: settings.get("mcp.sharedPoolIdleMs"),
3620
+ toolCacheServerNames: conventionalCacheServerNames,
3621
+ ...(maxPluginMcpStartupWaitMs === undefined
3622
+ ? {}
3623
+ : { maxStartupTimeoutMs: maxPluginMcpStartupWaitMs }),
3624
+ },
3625
+ );
3288
3626
  owned.setAuthStorage(authStorage);
3289
- cleanupOwnedMcpManager = () => owned.disconnectAll();
3627
+ const cleanupOwned = registerOwnedMcpManagerCleanup(owned);
3290
3628
  // Interactive launches may paint before MCP connects; the deferred
3291
3629
  // starter reconnects this manager after first paint and a startup
3292
3630
  // turn barrier keeps the first prompt from racing tool registration.
@@ -3296,6 +3634,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3296
3634
  if (options.deferMcpConfigStartup && pluginNames.size === 0) {
3297
3635
  deferredConventionalMcp = {
3298
3636
  manager: owned,
3637
+ cleanup: cleanupOwned,
3299
3638
  configs: mergedConfigs,
3300
3639
  sources: mergedSourceMetas,
3301
3640
  conventionalConfigs,
@@ -3312,37 +3651,60 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3312
3651
  mergedSourceMetas,
3313
3652
  conventionalConfigs,
3314
3653
  pluginNames,
3654
+ pluginMcpProvenance,
3655
+ cleanupOwned,
3315
3656
  );
3316
3657
  }
3317
3658
  }
3318
3659
  } catch (error) {
3319
- if (safeIsInstanceOf(error, McpManagerCleanupError)) throw error;
3660
+ if (safeIsInstanceOf(error, McpStartupCleanupRetryError)) {
3661
+ throw (error as McpStartupCleanupRetryError).primaryError;
3662
+ }
3663
+ const pluginStartupCleanupFailure = safeIsInstanceOf(error, McpPluginStartupCleanupDiagnosticError)
3664
+ ? (error as McpPluginStartupCleanupDiagnosticError)
3665
+ : undefined;
3666
+ if (
3667
+ !pluginStartupCleanupFailure &&
3668
+ (safeIsInstanceOf(error, McpManagerCleanupError) ||
3669
+ safeIsInstanceOf(error, McpManagerCleanupDiagnosticError) ||
3670
+ safeReadCleanupDiagnostic(error) !== undefined)
3671
+ )
3672
+ throw error;
3320
3673
  gjcProducersComplete = false;
3321
- const cleanupDiagnostic = safeReadCleanupDiagnostic(error);
3674
+ const cleanupDiagnostic =
3675
+ pluginStartupCleanupFailure?.cleanupDiagnostic ?? safeReadCleanupDiagnostic(error);
3322
3676
  logger.warn("Failed to wire GJC plugin MCP servers", {
3323
- error: safeErrorForLog(error),
3677
+ error: pluginStartupCleanupFailure
3678
+ ? safePluginMcpDiagnostic(pluginStartupCleanupFailure.primaryError)
3679
+ : safeErrorForLog(error),
3324
3680
  cleanupDiagnostic: safeCleanupDiagnosticForLog(cleanupDiagnostic),
3325
3681
  });
3326
3682
  }
3327
3683
  } else if (isCanonicalSubSession) {
3328
- // Subagents inherit the parent's always-on plugin and conventional MCP
3329
- // tools WITHOUT owning the manager (no connect, no callbacks, no
3330
- // disposal). The facade is carried explicitly by the parent session
3331
- // scope; process-global state is never consulted for MCP routing.
3684
+ // Subagents inherit the parent's MCP tools without owning the manager.
3685
+ // They subscribe to catalog changes but never connect or dispose it; the
3686
+ // facade is carried explicitly by the parent scope.
3332
3687
  const inherited = mcpManager ?? options.inheritedMcpManager;
3333
3688
  if (inherited) {
3334
3689
  try {
3335
- const inheritedTools = inherited.getTools();
3336
- if (inheritedTools.length > 0) customTools.push(...(inheritedTools as CustomTool[]));
3337
- const pluginServers = pluginMcpManagerServers.get(inherited);
3338
- const conventionalServers = conventionalMcpManagerServers.get(inherited);
3339
- for (const tool of inheritedTools) {
3340
- const serverName = tool.mcpServerName;
3341
- if (serverName === undefined) continue;
3342
- if (pluginServers?.has(serverName)) pluginMcpToolNames.push(tool.name);
3343
- else if (conventionalServers?.has(serverName)) conventionalMcpToolNames.push(tool.name);
3344
- }
3690
+ inheritedMcpManager = inherited;
3691
+ inheritedMcpToolsUnsubscribe = inherited.subscribeToToolsChanged(tools => {
3692
+ const snapshot = tools.map(tool => tool as CustomTool);
3693
+ if (inheritedMcpToolsPublisher) inheritedMcpToolsPublisher(snapshot);
3694
+ else pendingInheritedMcpToolsSnapshot = snapshot;
3695
+ });
3696
+ const inheritedTools = inherited.getTools() as CustomTool[];
3697
+ inheritedMcpToolsSnapshot = inheritedTools;
3698
+ inheritedMcpManagerToolNames = inheritedTools.map(tool => tool.name);
3699
+ customTools.push(...inheritedTools);
3700
+ const classifiedTools = classifyInheritedMcpToolNames(inherited, inheritedTools);
3701
+ pluginMcpToolNames.push(...classifiedTools.pluginMcpToolNames);
3702
+ conventionalMcpToolNames.push(...classifiedTools.conventionalMcpToolNames);
3345
3703
  } catch (error) {
3704
+ stopInheritedMcpToolsSubscription();
3705
+ inheritedMcpManager = undefined;
3706
+ inheritedMcpToolsSnapshot = [];
3707
+ pendingInheritedMcpToolsSnapshot = undefined;
3346
3708
  logger.warn("Failed to inherit MCP tools in subagent", { error: safeErrorForLog(error) });
3347
3709
  }
3348
3710
  }
@@ -3495,6 +3857,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3495
3857
  if (!session) throw new Error("Terminal abort session is not initialized.");
3496
3858
  return session.abortPromptAndWait(handle, seamOptions);
3497
3859
  },
3860
+ pendingToolExecutions: handle => {
3861
+ if (!session) throw new Error("Terminal abort session is not initialized.");
3862
+ return session.pendingToolExecutions(handle);
3863
+ },
3498
3864
  },
3499
3865
  ensureProviderDaemon: options.ensureNotificationProviderDaemon,
3500
3866
  runBtwTurn: async (question, signal) => {
@@ -3585,6 +3951,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3585
3951
  if (!session) throw new Error("Terminal abort session is not initialized.");
3586
3952
  return session.abortPromptAndWait(handle, seamOptions);
3587
3953
  },
3954
+ pendingToolExecutions: handle => {
3955
+ if (!session) throw new Error("Terminal abort session is not initialized.");
3956
+ return session.pendingToolExecutions(handle);
3957
+ },
3588
3958
  },
3589
3959
  });
3590
3960
  }
@@ -3703,6 +4073,103 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3703
4073
  extensionsResult.runtime.pendingProviderRegistrations = [];
3704
4074
  }
3705
4075
 
4076
+ // A startup extension can register the saved model after the initial
4077
+ // recovery lookup. Reconsider the saved chain against that completed
4078
+ // catalog before retaining a durable runtime fallback.
4079
+ if (savedDefaultWasUnresolved && defaultModelEntries.length > 0) {
4080
+ const restoredAfterExtensions = await resolveModelChainWithAuth(
4081
+ defaultModelEntries,
4082
+ modelRegistry,
4083
+ settings,
4084
+ credentialSessionId,
4085
+ {
4086
+ managedFallback: defaultModelEntries.length > 1,
4087
+ canonicalSessionId: providerSessionId,
4088
+ ...(persistedProfileOwnsDefault ? { aliasIntent: "preset-equivalent" as const } : {}),
4089
+ },
4090
+ );
4091
+ if (
4092
+ restoredAfterExtensions.model &&
4093
+ (!preferredCredentialProvider || restoredAfterExtensions.model.provider === preferredCredentialProvider)
4094
+ ) {
4095
+ model = restoredAfterExtensions.model;
4096
+ retainedRecoveryBindingsAfterLateRestore = savedDefaultWasUnresolved;
4097
+ if (options.thinkingLevel !== undefined) {
4098
+ thinkingLevel = resolveThinkingLevelForModel(model, options.thinkingLevel);
4099
+ } else if (restoredThinkingLevel !== undefined && restoredThinkingLevel !== ThinkingLevel.Inherit) {
4100
+ thinkingLevel = resolveThinkingLevelForModel(model, restoredThinkingLevel);
4101
+ } else {
4102
+ thinkingLevel = restoredAfterExtensions.explicitThinkingLevel
4103
+ ? restoredAfterExtensions.thinkingLevel
4104
+ : undefined;
4105
+ if (thinkingLevel === undefined && !hasExplicitModel && defaultRoleSpec.explicitThinkingLevel)
4106
+ thinkingLevel = defaultRoleSpec.thinkingLevel;
4107
+ if (thinkingLevel === undefined && hasExplicitDefaultThinkingLevel)
4108
+ thinkingLevel = settings.get("defaultThinkingLevel");
4109
+ if (thinkingLevel === undefined && model.thinking?.defaultLevel !== undefined)
4110
+ thinkingLevel = model.thinking.defaultLevel;
4111
+ if (thinkingLevel === undefined) thinkingLevel = settings.get("defaultThinkingLevel");
4112
+ thinkingLevel = resolveThinkingLevelForModel(model, thinkingLevel);
4113
+ }
4114
+ recoveredSessionDefault = undefined;
4115
+ startupActiveModelProfile =
4116
+ acceptedInheritedProfileName ??
4117
+ (!hasExplicitModel && acceptedPersistedProfileName ? acceptedPersistedProfileName : undefined);
4118
+ modelFallbackMessage = undefined;
4119
+ } else if (deferredMissingSessionRecovery) {
4120
+ const hadProvisionalRecovery = recoveredSessionDefault !== undefined;
4121
+ let lateRecoveryAccepted = false;
4122
+ try {
4123
+ const recovery = await resolveMissingSessionModelRecovery({
4124
+ modelRegistry,
4125
+ settings,
4126
+ defaultEntries: defaultModelEntries,
4127
+ skips: restoredAfterExtensions.skips,
4128
+ savedDefault: existingSession.models.default,
4129
+ credentialSessionId,
4130
+ ...(persistedProfileOwnsDefault ? { aliasIntent: "preset-equivalent" as const } : {}),
4131
+ });
4132
+ if (
4133
+ recovery?.model &&
4134
+ (!preferredCredentialProvider || recovery.model.provider === preferredCredentialProvider)
4135
+ ) {
4136
+ model = recovery.model;
4137
+ if (options.thinkingLevel !== undefined) {
4138
+ thinkingLevel = resolveThinkingLevelForModel(model, options.thinkingLevel);
4139
+ } else if (restoredThinkingLevel !== undefined && restoredThinkingLevel !== ThinkingLevel.Inherit) {
4140
+ thinkingLevel = resolveThinkingLevelForModel(model, restoredThinkingLevel);
4141
+ } else {
4142
+ const recoveredLevel = recovery.explicitThinkingLevel
4143
+ ? recovery.thinkingLevel
4144
+ : defaultRoleSpec.explicitThinkingLevel
4145
+ ? defaultRoleSpec.thinkingLevel
4146
+ : hasExplicitDefaultThinkingLevel
4147
+ ? settings.get("defaultThinkingLevel")
4148
+ : (model.thinking?.defaultLevel ?? settings.get("defaultThinkingLevel"));
4149
+ thinkingLevel = resolveThinkingLevelForModel(model, recoveredLevel);
4150
+ }
4151
+ recoveredSessionDefault = recovery;
4152
+ startupActiveModelProfile = undefined;
4153
+ modelFallbackMessage =
4154
+ "Saved session model is no longer registered; restored the durable default preset instead.";
4155
+ lateRecoveryAccepted = true;
4156
+ }
4157
+ } catch (error) {
4158
+ if (!(error instanceof ModelProfileCredentialError)) throw error;
4159
+ }
4160
+ // A completed extension catalog can make the saved selector registered
4161
+ // again without making it callable. Do not retain the provisional durable
4162
+ // fallback in that case: the saved model remains authoritative for the
4163
+ // normal unavailable-model path below.
4164
+ if (!lateRecoveryAccepted && hadProvisionalRecovery) {
4165
+ model = undefined;
4166
+ recoveredSessionDefault = undefined;
4167
+ startupActiveModelProfile = undefined;
4168
+ modelFallbackMessage = `Could not restore model ${defaultModelEntries.join(" -> ")}`;
4169
+ }
4170
+ }
4171
+ }
4172
+
3706
4173
  let startupCredentialModelRejected = false;
3707
4174
  if (startupModelReference && (startupCredentialSelector || startupCredentialProviderMismatch)) {
3708
4175
  const validated =
@@ -4790,12 +5257,25 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4790
5257
  providerSessionState: options.providerSessionState,
4791
5258
  });
4792
5259
  session.setActiveModelProfile(startupActiveModelProfile);
5260
+ if (retainedRecoveryBindingsAfterLateRestore) session.markStartupRecoveryBindingsRequired();
5261
+ if (recoveredSessionDefault)
5262
+ session.installRecoveredDefaultFallbackChain(
5263
+ recoveredSessionDefault.entries,
5264
+ recoveredSessionDefault.profileName,
5265
+ recoveredSessionDefault.activeIndex,
5266
+ recoveredSessionDefault.skips,
5267
+ );
4793
5268
  session.configWarnings.push(...contextFileWarnings);
4794
5269
  // Determined once, here, where settings are already available. Keep the
4795
5270
  // durable warning for interactive and print consumers; ACP delivery is
4796
5271
  // carried by the host replay ring through the internal runtime seam above.
4797
5272
  if (autoroutingInactive) session.configWarnings.push(AUTOROUTING_INACTIVE_WARNING);
4798
5273
  hasSession = true;
5274
+ const cleanupOwnedManager = cleanupOwnedMcpManager;
5275
+ const sessionOwnedMcpManager = ownsMcpManager ? mcpManager : undefined;
5276
+ if (cleanupOwnedManager && cleanupOwnedMcpManagerOwner !== sessionOwnedMcpManager) {
5277
+ session.registerToolSessionCleanup(cleanupOwnedManager);
5278
+ }
4799
5279
  if (masterModeContext) {
4800
5280
  // One scoped, no-probe peer snapshot immediately before the FIRST accepted
4801
5281
  // provider request; see createMasterPeerSnapshotContributor for the
@@ -4977,59 +5457,166 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4977
5457
  // createAgentSession rather than surfacing as an unhandled rejection.
4978
5458
  if (!options.deferMemoryBackendStartup) await startMemoryBackend();
4979
5459
 
4980
- // Wire the owned manager's late tool publications into the live session.
4981
- // Shared by the eager startup path below and by the deferred conventional
4982
- // starter once its connect lands. No-op when there is no owned manager or
4983
- // nothing conventional to publish.
4984
- const wireOwnedConventionalToolSync = (): void => {
4985
- if (!mcpManager || !publishOwnedConventionalMcpTools) return;
4986
- // Late conventional connections can publish near-simultaneously.
5460
+ // Wire late catalog publications from every owned manager into the live
5461
+ // session. Plugin-only managers also change after startup through
5462
+ // tools/list_changed; mixed managers additionally publish cached fallbacks.
5463
+ // Shared by eager startup and the deferred starter.
5464
+ const wireOwnedMcpToolSync = (initialTools?: readonly CustomTool[]): void => {
5465
+ const manager = mcpManager;
5466
+ if (!manager || !publishOwnedMcpTools || manager.isToolsOnly()) return;
5467
+ // Late MCP connections can publish near-simultaneously.
4987
5468
  // Serialize the swaps so an older snapshot cannot interleave with a
4988
5469
  // newer one inside replaceNamedCustomTools and leave a stale list.
4989
5470
  // Each link swallows (and logs) its own failure so one bad
4990
5471
  // publication cannot kill the chain for every later one.
4991
- let conventionalToolsSync: Promise<void> = Promise.resolve();
4992
- const syncConventionalTools = (tools: CustomTool[]): Promise<void> => {
4993
- conventionalToolsSync = conventionalToolsSync
5472
+ let ownedMcpToolsSync: Promise<void> = Promise.resolve();
5473
+ let acceptingOwnedMcpTools = true;
5474
+ const syncOwnedMcpTools = (tools: readonly CustomTool[]): Promise<void> => {
5475
+ if (!acceptingOwnedMcpTools) return ownedMcpToolsSync;
5476
+ ownedMcpToolsSync = ownedMcpToolsSync
4994
5477
  .then(async () => {
4995
- if (session.isDisposed) return;
4996
- const nextTools = tools.filter(tool =>
4997
- tool.mcpServerName ? ownedConventionalMcpServerNames.has(tool.mcpServerName) : false,
4998
- );
4999
- const previousNames = ownedConventionalMcpToolNames;
5000
- ownedConventionalMcpToolNames = nextTools.map(tool => tool.name);
5478
+ if (session.isDisposed || mcpManager !== manager) return;
5479
+ const pluginServerNames = new Set(pluginMcpManagerServers.get(manager));
5480
+ const conventionalServerNames = new Set(conventionalMcpManagerServers.get(manager));
5481
+ // Plugin authority is fixed from validated bundle provenance when
5482
+ // this manager is created. SourceMeta can be caller-supplied later.
5483
+ for (const serverName of manager.getAllServerNames()) {
5484
+ if (!pluginServerNames.has(serverName)) conventionalServerNames.add(serverName);
5485
+ }
5486
+ for (const tool of tools) {
5487
+ const serverName = tool.mcpServerName;
5488
+ if (!serverName) continue;
5489
+ if (!pluginServerNames.has(serverName)) conventionalServerNames.add(serverName);
5490
+ }
5491
+ conventionalMcpManagerServers.set(manager, conventionalServerNames);
5492
+ const failedPluginServers = failedPluginMcpManagerServers.get(manager);
5493
+ const nextCachedConventionalMcpServerNames = new Set(cachedConventionalMcpServerNames);
5494
+ for (const serverName of nextCachedConventionalMcpServerNames) {
5495
+ if (!tools.some(tool => tool.mcpServerName === serverName)) {
5496
+ nextCachedConventionalMcpServerNames.delete(serverName);
5497
+ }
5498
+ }
5499
+ for (const tool of tools) {
5500
+ if (
5501
+ tool instanceof DeferredMCPTool &&
5502
+ tool.mcpServerName &&
5503
+ conventionalServerNames.has(tool.mcpServerName)
5504
+ ) {
5505
+ nextCachedConventionalMcpServerNames.add(tool.mcpServerName);
5506
+ }
5507
+ }
5508
+ const nextTools = tools.filter(tool => {
5509
+ const serverName = tool.mcpServerName;
5510
+ if (!serverName) return false;
5511
+ if (
5512
+ failedPluginServers?.has(serverName) &&
5513
+ manager.getConnectionStatus(serverName) !== "connected"
5514
+ ) {
5515
+ return false;
5516
+ }
5517
+ return pluginServerNames.has(serverName) || conventionalServerNames.has(serverName);
5518
+ });
5519
+ const previousNames = ownedMcpManagerToolNames;
5520
+ const nextToolNames = nextTools.map(tool => tool.name);
5001
5521
  const previousSet = new Set(previousNames);
5002
- cwdCapturingToolNames.splice(
5003
- 0,
5004
- cwdCapturingToolNames.length,
5522
+ const nextPluginToolNames = nextTools
5523
+ .filter(tool => tool.mcpServerName && pluginServerNames.has(tool.mcpServerName))
5524
+ .map(tool => tool.name);
5525
+ const nextConventionalToolNames = nextTools
5526
+ .filter(tool => tool.mcpServerName && conventionalServerNames.has(tool.mcpServerName))
5527
+ .map(tool => tool.name);
5528
+ const nextMandatoryMcpToolNames = [
5529
+ ...pluginMcpToolNames.filter(name => !previousSet.has(name)),
5530
+ ...nextPluginToolNames,
5531
+ ];
5532
+ const nextConventionalMcpToolNames = [
5533
+ ...conventionalMcpToolNames.filter(name => !previousSet.has(name)),
5534
+ ...nextConventionalToolNames,
5535
+ ];
5536
+ const nextCwdCapturingToolNames = [
5005
5537
  ...cwdCapturingToolNames.filter(name => !previousSet.has(name)),
5006
- ...ownedConventionalMcpToolNames,
5538
+ ...nextToolNames,
5539
+ ];
5540
+ const nextOwnedPluginServersConnected = [...pluginServerNames].some(
5541
+ name => manager.getConnectionStatus(name) === "connected",
5542
+ );
5543
+ const nextOwnedConventionalMcpServerNames = new Set(conventionalServerNames);
5544
+ const hasPublishedCachedTool = nextTools.some(
5545
+ tool =>
5546
+ tool.mcpServerName !== undefined &&
5547
+ nextCachedConventionalMcpServerNames.has(tool.mcpServerName),
5007
5548
  );
5008
- await session.replaceNamedCustomTools(previousNames, nextTools);
5009
- // Mixed plugin + conventional sessions deferred the seal while
5010
- // a conventional server was still connecting; restore the
5011
- // fixed-connection plugin contract once every conventional
5012
- // server has reached a terminal state.
5549
+ try {
5550
+ await session.replaceNamedCustomTools(previousNames, nextTools, {
5551
+ mandatoryMCPToolNames: nextMandatoryMcpToolNames,
5552
+ activateNewTools: false,
5553
+ });
5554
+ } catch (error) {
5555
+ ownedMcpManagerToolNames = [...new Set([...previousNames, ...nextToolNames])];
5556
+ throw error;
5557
+ }
5558
+ ownedMcpManagerToolNames = nextToolNames;
5559
+ ownedMcpManagerTools = nextTools;
5560
+ pluginMcpToolNames.splice(0, pluginMcpToolNames.length, ...nextMandatoryMcpToolNames);
5561
+ conventionalMcpToolNames.splice(0, conventionalMcpToolNames.length, ...nextConventionalMcpToolNames);
5562
+ cwdCapturingToolNames.splice(0, cwdCapturingToolNames.length, ...nextCwdCapturingToolNames);
5563
+ ownedConventionalMcpServerNames.clear();
5564
+ for (const name of nextOwnedConventionalMcpServerNames) ownedConventionalMcpServerNames.add(name);
5565
+ cachedConventionalMcpServerNames.clear();
5566
+ for (const name of nextCachedConventionalMcpServerNames) cachedConventionalMcpServerNames.add(name);
5567
+ // Mixed plugin + conventional sessions defer the seal while a
5568
+ // conventional server is connecting. Cached fallback servers retain
5569
+ // their reconnect path while any of their tools remain published,
5570
+ // including after reconnect replaces DeferredMCPTool with MCPTool.
5571
+ // Derive that hold from this snapshot so removing/replacing the tools
5572
+ // restores the fixed-connection plugin contract.
5013
5573
  if (
5014
- ownedPluginServersConnected &&
5015
- mcpManager !== undefined &&
5016
- !mcpManager.isConnectionSetSealed() &&
5017
- ![...ownedConventionalMcpServerNames].some(
5018
- name => mcpManager?.getConnectionStatus(name) === "connecting",
5019
- )
5574
+ nextOwnedPluginServersConnected &&
5575
+ !manager.isConnectionSetSealed() &&
5576
+ ![...nextOwnedConventionalMcpServerNames].some(
5577
+ name => manager.getConnectionStatus(name) === "connecting",
5578
+ ) &&
5579
+ !hasPublishedCachedTool
5020
5580
  ) {
5021
- mcpManager.sealConnectionSet();
5581
+ manager.sealConnectionSet();
5022
5582
  }
5023
5583
  })
5024
5584
  .catch(error => {
5025
- logger.warn("Failed to publish conventional MCP tools", { error: safeErrorForLog(error) });
5585
+ logger.warn("Failed to publish owned MCP tools", { error: safeErrorForLog(error) });
5026
5586
  });
5027
- return conventionalToolsSync;
5587
+ return ownedMcpToolsSync;
5028
5588
  };
5029
- mcpManager.setOnToolsChanged(tools => {
5030
- void syncConventionalTools(tools as CustomTool[]);
5589
+ session.registerToolSessionCleanup(async () => {
5590
+ acceptingOwnedMcpTools = false;
5591
+ await ownedMcpToolsSync;
5031
5592
  });
5032
- void syncConventionalTools(mcpManager.getTools() as CustomTool[]);
5593
+ manager.setOnToolsChanged(tools => {
5594
+ void syncOwnedMcpTools(tools);
5595
+ });
5596
+ const catalog = manager.getToolCatalogSnapshot();
5597
+ if (initialTools !== undefined) {
5598
+ // Deferred startup already registered its captured result. Re-read
5599
+ // after that await so a publication during registration is not lost.
5600
+ const currentTools =
5601
+ catalog.publication === "published" ? catalog.tools.map(tool => tool as CustomTool) : initialTools;
5602
+ void syncOwnedMcpTools(currentTools);
5603
+ } else if (catalog.publication === "published") {
5604
+ const failedPluginServers = failedPluginMcpManagerServers.get(manager);
5605
+ const catalogTools = catalog.tools
5606
+ .filter(tool => {
5607
+ const serverName = tool.mcpServerName;
5608
+ return (
5609
+ serverName !== undefined &&
5610
+ (!failedPluginServers?.has(serverName) || manager.getConnectionStatus(serverName) === "connected")
5611
+ );
5612
+ })
5613
+ .map(tool => tool as CustomTool);
5614
+ if (!sameMcpToolSnapshot(catalogTools, ownedMcpManagerTools)) {
5615
+ void syncOwnedMcpTools(catalog.tools.map(tool => tool as CustomTool));
5616
+ }
5617
+ } else if (!pluginMcpCleanupFailures.has(manager)) {
5618
+ void syncOwnedMcpTools(ownedMcpManagerTools);
5619
+ }
5033
5620
  };
5034
5621
  const wireOwnedMcpManagerLifecycle = (): void => {
5035
5622
  if (!mcpManager) return;
@@ -5040,20 +5627,66 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
5040
5627
  postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
5041
5628
  wireMcpManagerCallbacks(mcpManager);
5042
5629
  };
5630
+ const wireInheritedMcpToolSync = async (): Promise<void> => {
5631
+ const manager = inheritedMcpManager;
5632
+ if (!manager) return;
5633
+ let inheritedMcpToolsSync: Promise<void> = Promise.resolve();
5634
+ const syncInheritedTools = (tools: readonly CustomTool[]): Promise<void> => {
5635
+ const snapshot = [...tools];
5636
+ inheritedMcpToolsSync = inheritedMcpToolsSync
5637
+ .then(async () => {
5638
+ if (session.isDisposed) return;
5639
+ const previousNames = inheritedMcpManagerToolNames;
5640
+ const nextNames = snapshot.map(tool => tool.name);
5641
+ const classifiedTools = classifyInheritedMcpToolNames(manager, snapshot);
5642
+ try {
5643
+ await session.replaceNamedCustomTools(previousNames, snapshot, {
5644
+ mandatoryMCPToolNames: classifiedTools.pluginMcpToolNames,
5645
+ activateNewTools: false,
5646
+ });
5647
+ } catch (error) {
5648
+ inheritedMcpManagerToolNames = [...new Set([...previousNames, ...nextNames])];
5649
+ throw error;
5650
+ }
5651
+ inheritedMcpManagerToolNames = nextNames;
5652
+ pluginMcpToolNames.splice(0, pluginMcpToolNames.length, ...classifiedTools.pluginMcpToolNames);
5653
+ conventionalMcpToolNames.splice(
5654
+ 0,
5655
+ conventionalMcpToolNames.length,
5656
+ ...classifiedTools.conventionalMcpToolNames,
5657
+ );
5658
+ })
5659
+ .catch(error => {
5660
+ logger.warn("Failed to publish inherited MCP tools", { error: safeErrorForLog(error) });
5661
+ });
5662
+ return inheritedMcpToolsSync;
5663
+ };
5664
+ inheritedMcpToolsPublisher = tools => {
5665
+ void syncInheritedTools(tools);
5666
+ };
5667
+ session.registerToolSessionCleanup(async () => {
5668
+ stopInheritedMcpToolsSubscription();
5669
+ await inheritedMcpToolsSync;
5670
+ });
5671
+ const initialSnapshot = pendingInheritedMcpToolsSnapshot ?? inheritedMcpToolsSnapshot;
5672
+ pendingInheritedMcpToolsSnapshot = undefined;
5673
+ await syncInheritedTools(initialSnapshot);
5674
+ };
5043
5675
  // Exact-config managers do not receive reactive callbacks; their tools are
5044
5676
  // registered once in the session-owned catalog. A pending conventional
5045
5677
  // deferral wires its own callbacks from the starter after it connects, so
5046
5678
  // nothing is wired here yet.
5047
5679
  if (mcpManager && !options.mcpManager && explicitMcpConfigPath === undefined && !deferredConventionalMcp) {
5048
- if (publishOwnedConventionalMcpTools) {
5049
- wireOwnedConventionalToolSync();
5680
+ if (publishOwnedMcpTools) {
5681
+ wireOwnedMcpToolSync();
5050
5682
  } else if (!ownsMcpManager) {
5051
5683
  mcpManager.setOnToolsChanged(tools => {
5052
- void session.refreshMCPTools(tools);
5684
+ void session.refreshMCPTools(tools.map(tool => tool as CustomTool));
5053
5685
  });
5054
5686
  }
5055
5687
  wireOwnedMcpManagerLifecycle();
5056
5688
  }
5689
+ await wireInheritedMcpToolSync();
5057
5690
 
5058
5691
  // Constructor-time workflow-gate tool restoration is deferred by one
5059
5692
  // microtask (the ToolSession closure needs `session` assigned). Await it
@@ -5084,7 +5717,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
5084
5717
  let hasErrors = false;
5085
5718
  if (exact) {
5086
5719
  const result = await exact.manager.discoverAndConnect({ configPath: exact.configPath });
5087
- const resultTools = result.tools as CustomTool[];
5720
+ // A slash control may commit while the deferred startup flight is
5721
+ // loading. Re-read the manager's fenced publication rather than
5722
+ // replaying the stale startup snapshot into the session registry. A
5723
+ // deferred manager can still be waiting to publish its first catalog,
5724
+ // though; only that explicit unpublished state may use the discovery
5725
+ // result. Empty published/fenced catalogs are authoritative and must
5726
+ // not replay stale tools across an exact control.
5727
+ const catalog = exact.manager.getToolCatalogSnapshot();
5728
+ const resultTools =
5729
+ catalog.publication === "unpublished"
5730
+ ? (result.tools as CustomTool[])
5731
+ : (catalog.tools as CustomTool[]);
5088
5732
  const toolNames = resultTools.map(tool => tool.name);
5089
5733
  const collidingToolNames = findDeferredExactMcpToolNameCollisions(
5090
5734
  toolNames,
@@ -5114,6 +5758,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
5114
5758
  conventional.sources,
5115
5759
  conventional.conventionalConfigs,
5116
5760
  new Set<string>(),
5761
+ new Map<string, GjcPluginMcpServerProvenance>(),
5762
+ conventional.cleanup,
5117
5763
  );
5118
5764
  const resultTools = result.tools as CustomTool[];
5119
5765
  if (!cancelled && !session.isDisposed) {
@@ -5123,7 +5769,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
5123
5769
  // before this registration would therefore drop a persisted conventional
5124
5770
  // MCP selection and persist the empty set.
5125
5771
  await session.refreshMCPTools(resultTools);
5126
- wireOwnedConventionalToolSync();
5772
+ wireOwnedMcpToolSync(resultTools);
5127
5773
  wireOwnedMcpManagerLifecycle();
5128
5774
  if (
5129
5775
  !session.isDisposed &&
@@ -5181,6 +5827,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
5181
5827
  } catch (error) {
5182
5828
  // Release the subscription if the throw happened after install but before the
5183
5829
  // dispose-wrap took ownership.
5830
+ stopInheritedMcpToolsSubscription();
5184
5831
  releaseCredentialDisabledSubscription();
5185
5832
  let cleanupDiagnostic: unknown;
5186
5833
  try {