one-code-extension 0.4.1 → 0.6.0

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 (439) hide show
  1. package/README.md +16 -5
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +40 -28
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +26 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +58 -31
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +124 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +642 -0
  56. package/extensions/background/index.ts +614 -248
  57. package/extensions/background/loop-fire.ts +173 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +56 -0
  63. package/extensions/background/wakeup.ts +178 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +66 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +250 -78
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/cache-marks/index.ts +42 -0
  80. package/extensions/claude-compat/index.ts +7 -11
  81. package/extensions/claude-context/index.ts +436 -63
  82. package/extensions/compaction/cut.ts +157 -0
  83. package/extensions/compaction/index.ts +219 -75
  84. package/extensions/compaction/kept-thinking.ts +62 -0
  85. package/extensions/compaction/prompt.ts +15 -12
  86. package/extensions/context-budget/budget.ts +16 -2
  87. package/extensions/context-budget/index.ts +35 -15
  88. package/extensions/context-management/index.ts +36 -28
  89. package/extensions/doctor/build.ts +59 -8
  90. package/extensions/doctor/builtins.ts +84 -0
  91. package/extensions/doctor/cli.ts +7 -1
  92. package/extensions/doctor/compat.ts +88 -23
  93. package/extensions/doctor/dependencies.ts +41 -5
  94. package/extensions/doctor/fix-prompt.ts +9 -2
  95. package/extensions/doctor/index.ts +102 -58
  96. package/extensions/doctor/models.ts +102 -63
  97. package/extensions/doctor/presets.ts +42 -27
  98. package/extensions/doctor/report.ts +19 -2
  99. package/extensions/doctor/viewer.ts +49 -27
  100. package/extensions/effort/index.ts +141 -25
  101. package/extensions/effort/settings.ts +21 -0
  102. package/extensions/effort/slider.ts +24 -20
  103. package/extensions/exit/index.ts +32 -1
  104. package/extensions/file-tracker/index.ts +420 -42
  105. package/extensions/file-tracker/replay.ts +137 -24
  106. package/extensions/file-tracker/restore.ts +66 -0
  107. package/extensions/file-tracker/results.ts +66 -0
  108. package/extensions/file-tracker/shell-reads.ts +211 -0
  109. package/extensions/file-tracker/tracker.ts +45 -15
  110. package/extensions/footer/footer-line.ts +45 -24
  111. package/extensions/footer/index.ts +47 -2
  112. package/extensions/footer/pr.ts +34 -5
  113. package/extensions/hooks/compaction.ts +7 -0
  114. package/extensions/hooks/executor.ts +32 -30
  115. package/extensions/hooks/index.ts +209 -58
  116. package/extensions/hooks/matcher.ts +3 -0
  117. package/extensions/hooks/prompt-context.ts +59 -0
  118. package/extensions/hooks/protocol.ts +41 -3
  119. package/extensions/hooks/settings.ts +50 -19
  120. package/extensions/hooks/subagent-bridge.ts +2 -0
  121. package/extensions/hooks/trust.ts +26 -12
  122. package/extensions/idle-compact/index.ts +119 -0
  123. package/extensions/idle-compact/policy.ts +143 -0
  124. package/extensions/init/index.ts +11 -10
  125. package/extensions/init/prompt.ts +25 -32
  126. package/extensions/interrupted/index.ts +13 -1
  127. package/extensions/kept-thinking/index.ts +27 -0
  128. package/extensions/ledger/index.ts +46 -0
  129. package/extensions/ledger/wire.ts +154 -0
  130. package/extensions/lib/agent-cron.ts +146 -0
  131. package/extensions/lib/agent-loader.ts +18 -3
  132. package/extensions/lib/agent-view.ts +85 -0
  133. package/extensions/lib/anthropic-auth.ts +98 -0
  134. package/extensions/lib/anthropic-payload.ts +82 -1
  135. package/extensions/lib/anthropic-server-call.ts +230 -0
  136. package/extensions/lib/anthropic-server-tools.ts +485 -0
  137. package/extensions/lib/app-launch.mjs +94 -0
  138. package/extensions/lib/argument-hints.ts +190 -0
  139. package/extensions/lib/atomic-write.ts +6 -2
  140. package/extensions/lib/auto-memory.ts +75 -0
  141. package/extensions/lib/bash-parser.ts +84 -0
  142. package/extensions/lib/btw-fork.ts +93 -0
  143. package/extensions/lib/child-extensions.ts +52 -0
  144. package/extensions/lib/child-writes.ts +45 -0
  145. package/extensions/lib/claude-context.ts +515 -100
  146. package/extensions/lib/claude-external-includes.ts +68 -0
  147. package/extensions/lib/claude-id.ts +22 -0
  148. package/extensions/lib/claude-rules.ts +323 -0
  149. package/extensions/lib/claude-settings.ts +76 -4
  150. package/extensions/lib/compaction-boundary.ts +36 -0
  151. package/extensions/lib/compaction-keep.mjs +83 -0
  152. package/extensions/lib/config-mode.ts +112 -0
  153. package/extensions/lib/consent-dialogs.ts +63 -0
  154. package/extensions/lib/consent-preview.ts +5 -1
  155. package/extensions/lib/consent-stores.ts +48 -0
  156. package/extensions/lib/context-facts.ts +97 -0
  157. package/extensions/lib/covered-messages.ts +23 -0
  158. package/extensions/lib/deferred.ts +359 -25
  159. package/extensions/lib/dock-rows.ts +60 -0
  160. package/extensions/lib/empty-tool-result.ts +27 -0
  161. package/extensions/lib/environment-block.ts +76 -0
  162. package/extensions/lib/fetch-timeout.ts +24 -6
  163. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  164. package/extensions/lib/git.ts +75 -13
  165. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  166. package/extensions/lib/grammars/README.md +2 -0
  167. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  168. package/extensions/lib/guide-docs.ts +45 -0
  169. package/extensions/lib/headless-output.ts +46 -0
  170. package/extensions/lib/hook-gate.ts +7 -7
  171. package/extensions/lib/interrupt.ts +20 -4
  172. package/extensions/lib/key-input.ts +455 -0
  173. package/extensions/lib/keys.ts +14 -1
  174. package/extensions/lib/ledger/fold-memo.ts +49 -0
  175. package/extensions/lib/ledger/fold.ts +265 -0
  176. package/extensions/lib/ledger/layout.ts +72 -0
  177. package/extensions/lib/ledger/plan.ts +102 -0
  178. package/extensions/lib/ledger/records.ts +276 -0
  179. package/extensions/lib/ledger/state.ts +47 -0
  180. package/extensions/lib/ledger/validate.ts +141 -0
  181. package/extensions/lib/ledger/wire.ts +632 -0
  182. package/extensions/lib/local-command.ts +71 -19
  183. package/extensions/lib/mcp-overrides.ts +1 -1
  184. package/extensions/lib/mcp-share.ts +46 -0
  185. package/extensions/lib/memory-content.ts +146 -0
  186. package/extensions/lib/memory-index.ts +18 -0
  187. package/extensions/lib/memory.ts +134 -123
  188. package/extensions/lib/model-catalog/huggingface.json +178 -0
  189. package/extensions/lib/model-catalog/models-dev.json +18533 -0
  190. package/extensions/lib/model-catalog/openrouter.json +5050 -0
  191. package/extensions/lib/model-catalog-data.ts +513 -0
  192. package/extensions/lib/model-catalog.ts +462 -0
  193. package/extensions/lib/model-choice-warnings.ts +137 -0
  194. package/extensions/lib/model-pairings.ts +152 -0
  195. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  196. package/extensions/lib/model-policy.ts +112 -15
  197. package/extensions/lib/model-tier.ts +283 -355
  198. package/extensions/lib/model-unusable.ts +1 -1
  199. package/extensions/lib/newer-model.ts +100 -0
  200. package/extensions/lib/notices.ts +87 -0
  201. package/extensions/lib/notifications.ts +357 -73
  202. package/extensions/lib/one-code-settings.ts +74 -8
  203. package/extensions/lib/open-browser.ts +63 -0
  204. package/extensions/lib/openrouter-generation.ts +94 -0
  205. package/extensions/lib/operation-deadline.ts +30 -0
  206. package/extensions/lib/original-command.ts +4 -4
  207. package/extensions/lib/package-version.ts +1 -1
  208. package/extensions/lib/panel-focus.ts +134 -0
  209. package/extensions/lib/paths.ts +17 -2
  210. package/extensions/lib/permission-gate.ts +72 -6
  211. package/extensions/lib/persisted-output.ts +60 -8
  212. package/extensions/lib/pi-ai-estimate.ts +19 -3
  213. package/extensions/lib/pi-version.ts +38 -2
  214. package/extensions/lib/plan-mode-channels.ts +42 -5
  215. package/extensions/lib/plugins.ts +26 -17
  216. package/extensions/lib/powershell-parse-script.ts +208 -0
  217. package/extensions/lib/powershell-parser.ts +344 -0
  218. package/extensions/lib/previous-mark.ts +101 -0
  219. package/extensions/lib/process-state.ts +48 -0
  220. package/extensions/lib/process-tree.ts +132 -8
  221. package/extensions/lib/prompt-options.ts +82 -0
  222. package/extensions/lib/queued-delivery.ts +44 -0
  223. package/extensions/lib/reminders.ts +370 -358
  224. package/extensions/lib/replaced-builtins.mjs +199 -0
  225. package/extensions/lib/replay-call.ts +70 -0
  226. package/extensions/lib/request-replay.ts +547 -0
  227. package/extensions/lib/scratchpad.ts +81 -14
  228. package/extensions/lib/send-now-channel.ts +7 -0
  229. package/extensions/lib/session-model-tier.ts +51 -0
  230. package/extensions/lib/session-work.ts +64 -0
  231. package/extensions/lib/settings-channels.ts +4 -4
  232. package/extensions/lib/shell-exit-semantics.ts +170 -0
  233. package/extensions/lib/shell-quote.ts +11 -0
  234. package/extensions/lib/shell-result.ts +70 -0
  235. package/extensions/lib/shell-spawn.ts +70 -6
  236. package/extensions/lib/shell-tool.ts +75 -25
  237. package/extensions/lib/side-call-cache.ts +124 -0
  238. package/extensions/lib/side-call-run.ts +96 -0
  239. package/extensions/lib/side-call-usage.ts +38 -0
  240. package/extensions/lib/side-call.ts +24 -1
  241. package/extensions/lib/single-flight.ts +18 -0
  242. package/extensions/lib/skill-body.ts +42 -0
  243. package/extensions/lib/skill-scan.ts +63 -24
  244. package/extensions/lib/spill-file.ts +140 -0
  245. package/extensions/lib/subagent-handback.ts +112 -0
  246. package/extensions/lib/system-role.ts +156 -0
  247. package/extensions/lib/terminal-text.ts +114 -0
  248. package/extensions/lib/text-width.ts +47 -16
  249. package/extensions/lib/timer-ops.ts +20 -0
  250. package/extensions/lib/tool-additions.ts +129 -0
  251. package/extensions/lib/tool-call-corruption.ts +134 -0
  252. package/extensions/lib/tool-path.ts +66 -0
  253. package/extensions/lib/tool-variants.ts +69 -0
  254. package/extensions/lib/tui-render.ts +255 -27
  255. package/extensions/lib/turn-budget-layout.ts +159 -0
  256. package/extensions/lib/update-check.mjs +15 -2
  257. package/extensions/lib/usage-bus.ts +1 -1
  258. package/extensions/lib/user-denial.ts +38 -0
  259. package/extensions/lib/workspace-channel.ts +16 -0
  260. package/extensions/lib/worktree-channel.ts +31 -3
  261. package/extensions/lib/worktree-isolation.ts +40 -10
  262. package/extensions/lsp/client.ts +8 -9
  263. package/extensions/lsp/index.ts +76 -12
  264. package/extensions/lsp/keep-alive.ts +1 -1
  265. package/extensions/lsp/trust.ts +119 -0
  266. package/extensions/lsp/watcher.ts +1 -1
  267. package/extensions/mcp/announce.ts +90 -43
  268. package/extensions/mcp/client.ts +161 -48
  269. package/extensions/mcp/config.ts +132 -8
  270. package/extensions/mcp/discovery.ts +19 -0
  271. package/extensions/mcp/index.ts +220 -121
  272. package/extensions/mcp/oauth/browser.ts +38 -17
  273. package/extensions/mcp/oauth/flow.ts +25 -17
  274. package/extensions/mcp/oauth/provider.ts +14 -8
  275. package/extensions/mcp/oauth/store.ts +34 -16
  276. package/extensions/mcp/panel/keys.ts +12 -11
  277. package/extensions/mcp/panel/render.ts +9 -6
  278. package/extensions/mcp/schema.ts +104 -5
  279. package/extensions/mcp/trust.ts +164 -73
  280. package/extensions/memory/entries.ts +23 -14
  281. package/extensions/memory/index.ts +134 -28
  282. package/extensions/memory/open-external.ts +33 -18
  283. package/extensions/memory/panel.ts +135 -26
  284. package/extensions/model-default/index.ts +86 -19
  285. package/extensions/newer-model/index.ts +31 -0
  286. package/extensions/notebook/description.ts +18 -0
  287. package/extensions/notebook/index.ts +6 -5
  288. package/extensions/notebook/notebook.ts +1 -1
  289. package/extensions/permissions/index.ts +1426 -398
  290. package/extensions/permissions/matcher.ts +425 -74
  291. package/extensions/permissions/modes.ts +11 -4
  292. package/extensions/permissions/panel/host.ts +130 -0
  293. package/extensions/permissions/panel/keys.ts +48 -0
  294. package/extensions/permissions/panel/render.ts +450 -0
  295. package/extensions/permissions/panel/state.ts +550 -0
  296. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  297. package/extensions/permissions/powershell-paths.ts +197 -0
  298. package/extensions/permissions/powershell-rules.ts +207 -280
  299. package/extensions/permissions/powershell-tree.ts +557 -0
  300. package/extensions/permissions/project-trust.ts +27 -11
  301. package/extensions/permissions/protected-paths.ts +14 -2
  302. package/extensions/permissions/session-grant.ts +6 -0
  303. package/extensions/permissions/settings.ts +166 -31
  304. package/extensions/permissions/subagent-gate.ts +25 -0
  305. package/extensions/permissions/workspace.ts +78 -0
  306. package/extensions/plan-mode/index.ts +276 -92
  307. package/extensions/plan-mode/reminder.ts +89 -21
  308. package/extensions/plan-mode/run-state.ts +26 -0
  309. package/extensions/plan-mode/texts.ts +177 -0
  310. package/extensions/plan-mode/viewer.ts +92 -27
  311. package/extensions/plugins/counts.ts +3 -3
  312. package/extensions/plugins/index.ts +64 -46
  313. package/extensions/plugins/install/install.ts +16 -0
  314. package/extensions/plugins/marketplace/sync.ts +9 -3
  315. package/extensions/plugins/marketplace/types.ts +3 -1
  316. package/extensions/plugins/panel/keys.ts +21 -24
  317. package/extensions/plugins/panel/render.ts +12 -6
  318. package/extensions/plugins/shell-expand.ts +107 -0
  319. package/extensions/plugins/template.ts +32 -15
  320. package/extensions/powershell/child.ts +7 -0
  321. package/extensions/powershell/description.ts +38 -6
  322. package/extensions/powershell/guards.ts +1 -1
  323. package/extensions/powershell/index.ts +14 -7
  324. package/extensions/recap/index.ts +73 -71
  325. package/extensions/recap/prompt.ts +2 -2
  326. package/extensions/recap/scheduler.ts +3 -5
  327. package/extensions/search-tools/grep-merge.ts +39 -0
  328. package/extensions/search-tools/index.ts +0 -0
  329. package/extensions/send-now/chord.ts +62 -0
  330. package/extensions/send-now/index.ts +133 -0
  331. package/extensions/session-title/index.ts +3 -0
  332. package/extensions/session-title/title.ts +4 -1
  333. package/extensions/skill/announce.ts +30 -0
  334. package/extensions/skill/code-review.ts +97 -0
  335. package/extensions/skill/index.ts +469 -132
  336. package/extensions/skill/invoke.ts +118 -75
  337. package/extensions/skill/listing.ts +133 -9
  338. package/extensions/skill/panel/keys.ts +15 -16
  339. package/extensions/skill/panel/render.ts +5 -2
  340. package/extensions/skill/panel/state.ts +12 -1
  341. package/extensions/subagents/agent-args.ts +39 -0
  342. package/extensions/subagents/agent-description.ts +132 -0
  343. package/extensions/subagents/agents.ts +146 -31
  344. package/extensions/subagents/default-model.ts +2 -3
  345. package/extensions/subagents/delegation-steer.ts +4 -4
  346. package/extensions/subagents/fork-cache.ts +82 -0
  347. package/extensions/subagents/guide-agent.ts +189 -0
  348. package/extensions/subagents/hand-back-review.ts +3 -0
  349. package/extensions/subagents/index.ts +1594 -852
  350. package/extensions/subagents/live-runs.ts +26 -6
  351. package/extensions/subagents/model-select.ts +38 -25
  352. package/extensions/subagents/outcome.ts +5 -2
  353. package/extensions/subagents/panel-keys.ts +73 -31
  354. package/extensions/subagents/panel-render.ts +120 -96
  355. package/extensions/subagents/panel-widget.ts +60 -89
  356. package/extensions/subagents/parent-mode.ts +50 -0
  357. package/extensions/subagents/pending-claim.ts +18 -1
  358. package/extensions/subagents/runner.ts +135 -80
  359. package/extensions/subagents/runs.ts +44 -5
  360. package/extensions/subagents/send-to-main-tool.ts +1 -1
  361. package/extensions/subagents/session-turns.ts +32 -8
  362. package/extensions/subagents/shell-panel.ts +36 -178
  363. package/extensions/subagents/tasks-dialog.ts +170 -0
  364. package/extensions/subagents/viewport-mirror.ts +95 -0
  365. package/extensions/subagents/worktree.ts +57 -13
  366. package/extensions/system-prompt/environment.ts +19 -19
  367. package/extensions/system-prompt/idle-turn.ts +65 -0
  368. package/extensions/system-prompt/index.ts +165 -47
  369. package/extensions/system-prompt/template.ts +34 -51
  370. package/extensions/system-prompt/tiers/common.ts +23 -64
  371. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  372. package/extensions/system-prompt/tiers/low.ts +67 -45
  373. package/extensions/system-prompt/tiers/mid.ts +96 -38
  374. package/extensions/system-reminder/index.ts +204 -70
  375. package/extensions/tasks/descriptions.ts +190 -0
  376. package/extensions/tasks/index.ts +120 -41
  377. package/extensions/tasks/store.ts +83 -16
  378. package/extensions/tool-call-corruption/child.ts +7 -0
  379. package/extensions/tool-call-corruption/index.ts +237 -0
  380. package/extensions/tool-search/index.ts +366 -144
  381. package/extensions/tool-style/index.ts +68 -10
  382. package/extensions/tool-style/write-description.ts +40 -0
  383. package/extensions/tool-style/write-render.ts +237 -0
  384. package/extensions/turn-duration/index.ts +7 -3
  385. package/extensions/turn-duration/line.ts +47 -4
  386. package/extensions/web/backends.ts +6 -9
  387. package/extensions/web/description.ts +50 -0
  388. package/extensions/web/index.ts +118 -53
  389. package/extensions/web/native-call.ts +15 -0
  390. package/extensions/web/native-results.ts +41 -0
  391. package/extensions/web-fetch/description.ts +42 -0
  392. package/extensions/web-fetch/extract.ts +53 -6
  393. package/extensions/web-fetch/index.ts +125 -76
  394. package/extensions/web-fetch/page-content.ts +19 -0
  395. package/extensions/web-fetch/response.ts +49 -0
  396. package/extensions/web-fetch/summarize.ts +9 -10
  397. package/extensions/workflow/agent-session.ts +108 -18
  398. package/extensions/workflow/args.ts +24 -0
  399. package/extensions/workflow/description.ts +67 -0
  400. package/extensions/workflow/globals.ts +17 -3
  401. package/extensions/workflow/index.ts +157 -162
  402. package/extensions/workflow/records.ts +1 -0
  403. package/extensions/workflow/run-manager.ts +33 -6
  404. package/extensions/workflow/saved-workflows.ts +3 -2
  405. package/extensions/workflow/types.ts +22 -1
  406. package/extensions/workflow/viewer.ts +54 -24
  407. package/extensions/workflow/widget.ts +20 -0
  408. package/extensions/worktree/descriptions.ts +91 -0
  409. package/extensions/worktree/guards.ts +316 -54
  410. package/extensions/worktree/index.ts +162 -44
  411. package/extensions/worktree/powershell-guards.ts +167 -0
  412. package/extensions/worktree/rewrite.ts +14 -16
  413. package/package.json +21 -7
  414. package/skills/artifact-design/SKILL.md +85 -0
  415. package/skills/code-review/SKILL.md +20 -20
  416. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  417. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  418. package/skills/code-review/cells/high-8-inline.md +122 -0
  419. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  420. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  421. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  422. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  423. package/skills/code-review/cells/minimal-prompt.md +32 -0
  424. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  425. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  426. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  427. package/skills/loop/SKILL.md +11 -0
  428. package/skills/security-review/SKILL.md +2 -5
  429. package/skills/simplify/SKILL.md +7 -5
  430. package/skills/workflow-authoring/SKILL.md +143 -0
  431. package/themes/onecode-light.json +1 -1
  432. package/themes/onecode.json +1 -1
  433. package/types/app.d.ts +58 -0
  434. package/types/community-packages.d.ts +26 -1
  435. package/extensions/auto-mode/recoverability.ts +0 -229
  436. package/extensions/lib/capability-index.ts +0 -466
  437. package/extensions/lib/model-facts.generated.json +0 -2675
  438. package/extensions/lib/model-facts.ts +0 -162
  439. package/extensions/tool-search/announce.ts +0 -42
@@ -1,22 +1,20 @@
1
1
  /**
2
- * What to tell the model when MCP state changes after the first request (pure).
2
+ * What to tell the model when MCP state changes inside an epoch (pure).
3
3
  *
4
4
  * The MCP instructions and failed-servers reminders are `first-prepend` blocks
5
- * on message 1. Before the first request they are simply (re)written; after it
6
- * message 1 is part of every request's cached prefix, so they are FROZEN and
7
- * changes ride one-shot notices where the model reads next (same rule as the
8
- * deferred-tools listing, tool-search/announce.ts). `McpAnnounced` records what
9
- * the model has been told; `mcpDelta` compares it with the live state.
5
+ * on message 1. A request that opens an epoch renders them from live state;
6
+ * inside an epoch message 1 is the record (extensions/ledger), so changes ride
7
+ * notices on the request's tail, decided when the request goes out from what
8
+ * the epoch has told (the same rule as the deferred-tools listing). What was
9
+ * told is a whole-text block fact per server (`mcp-instructed:<name>`,
10
+ * `mcp-failed:<name>`): the hash of the instructions or error last told, or of
11
+ * the empty text once a drop or a recovery was announced.
10
12
  */
11
13
 
12
14
  import { mcpFailuresReminder, mcpInstructionsReminder } from "./client.ts";
13
-
14
- export interface McpAnnounced {
15
- /** Servers whose instructions the model has been given, with the text it saw. */
16
- instructed: Map<string, string>;
17
- /** Servers the model has been told failed, with the error it saw. */
18
- failed: Map<string, string>;
19
- }
15
+ import type { BlockFact, ToldFact } from "../lib/ledger/records.ts";
16
+ import { toldBlockFact } from "../lib/reminders.ts";
17
+ import { countNoun } from "../lib/tui-render.ts";
20
18
 
21
19
  export interface McpSnapshot {
22
20
  connected: Array<{ name: string; instructions?: string }>;
@@ -32,47 +30,83 @@ export interface McpDelta {
32
30
  recovered: string[];
33
31
  /** Servers whose instructions the model saw that are no longer connected and not reported failed. */
34
32
  dropped: string[];
33
+ /** What announcing this delta tells, for the request that carries it. */
34
+ facts: ToldFact[];
35
35
  }
36
36
 
37
- export function emptyAnnounced(): McpAnnounced {
38
- return { instructed: new Map(), failed: new Map() };
37
+ const INSTRUCTED = "mcp-instructed:";
38
+ const FAILED = "mcp-failed:";
39
+ /** The told hash of "nothing": a drop or a recovery was announced. */
40
+ const NONE = toldBlockFact("", "").hash;
41
+
42
+ /**
43
+ * Store and compare the exact bounded values the reminder puts on the wire.
44
+ * A changing tail beyond either cap is not a model-visible capability change
45
+ * and must not become a false notice.
46
+ */
47
+ function displayedInstructions(name: string, instructions: string): string {
48
+ const rendered = mcpInstructionsReminder([{ server: { name }, instructions }]);
49
+ const prefix = "# MCP Server Instructions\n\nThe following MCP servers have provided instructions for how to use their tools and resources:\n\n" + `## ${name}\n`;
50
+ return rendered?.startsWith(prefix) ? rendered.slice(prefix.length) : instructions;
51
+ }
52
+ function displayedFailure(error: string): string {
53
+ return error.split("\n")[0].slice(0, 300);
39
54
  }
40
55
 
41
- /** What message 1 says after it was (re)written from `snapshot`, before the first request. */
42
- export function announcedFrom(snapshot: McpSnapshot): McpAnnounced {
43
- return {
44
- instructed: new Map(
45
- snapshot.connected.filter((c) => c.instructions).map((c) => [c.name, c.instructions as string]),
46
- ),
47
- failed: new Map(snapshot.failed.map((f) => [f.name, f.error])),
48
- };
56
+ const instructedFact = (name: string, instructions: string): BlockFact => toldBlockFact(INSTRUCTED + name, displayedInstructions(name, instructions));
57
+ const failedFact = (name: string, error: string): BlockFact => toldBlockFact(FAILED + name, displayedFailure(error));
58
+
59
+ /** What message 1's two MCP blocks rendered from `snapshot` tell. */
60
+ export function mcpFacts(snapshot: McpSnapshot): ToldFact[] {
61
+ return [
62
+ ...snapshot.connected.flatMap((c) => (c.instructions ? [instructedFact(c.name, c.instructions)] : [])),
63
+ ...snapshot.failed.map((f) => failedFact(f.name, f.error)),
64
+ ];
49
65
  }
50
66
 
51
- export function mcpDelta(announced: McpAnnounced, snapshot: McpSnapshot): McpDelta {
67
+ /**
68
+ * What changed since the epoch told `told` (whole-text block hashes by key),
69
+ * and the facts announcing it tells. While the startup connections are still
70
+ * `settling`, a server not connected yet is not a drop, and neither is one in
71
+ * `connecting` (a reconnect in flight).
72
+ */
73
+ export function mcpDelta(told: ReadonlyMap<string, string>, snapshot: McpSnapshot, settling = false, connecting: ReadonlySet<string> = new Set()): McpDelta {
52
74
  const connectedNames = new Set(snapshot.connected.map((c) => c.name));
53
- const failedNames = new Set(snapshot.failed.map((f) => f.name));
75
+ // One failure per server (the latest): two kept errors would alternate as the told fact.
76
+ const failed = [...new Map(snapshot.failed.map((f) => [f.name, f])).values()];
77
+ const failedNames = new Set(failed.map((f) => f.name));
78
+ const toldNames = (prefix: string) => [...told].filter(([key, hash]) => key.startsWith(prefix) && hash !== NONE).map(([key]) => key.slice(prefix.length));
79
+ const newInstructions = snapshot.connected
80
+ .filter((c) => c.instructions && told.get(INSTRUCTED + c.name) !== instructedFact(c.name, c.instructions).hash)
81
+ .map((c) => ({ name: c.name, instructions: displayedInstructions(c.name, c.instructions as string) }));
82
+ const newFailures = failed
83
+ .filter((f) => told.get(FAILED + f.name) !== failedFact(f.name, f.error).hash)
84
+ .map((f) => ({ name: f.name, error: displayedFailure(f.error) }));
85
+ const recovered = toldNames(FAILED).filter((name) => connectedNames.has(name));
86
+ const dropped = settling ? [] : toldNames(INSTRUCTED).filter((name) => !connectedNames.has(name) && !failedNames.has(name) && !connecting.has(name));
54
87
  return {
55
- newInstructions: snapshot.connected
56
- .filter((c) => c.instructions && announced.instructed.get(c.name) !== c.instructions)
57
- .map((c) => ({ name: c.name, instructions: c.instructions as string })),
58
- newFailures: snapshot.failed.filter((f) => announced.failed.get(f.name) !== f.error),
59
- recovered: [...announced.failed.keys()].filter((name) => connectedNames.has(name)),
60
- dropped: [...announced.instructed.keys()].filter((name) => !connectedNames.has(name) && !failedNames.has(name)),
88
+ newInstructions,
89
+ newFailures,
90
+ recovered,
91
+ dropped,
92
+ facts: [
93
+ ...newInstructions.map((s) => toldBlockFact(INSTRUCTED + s.name, s.instructions)),
94
+ ...newFailures.map((f) => toldBlockFact(FAILED + f.name, f.error)),
95
+ ...recovered.map((name) => toldBlockFact(FAILED + name, "")),
96
+ ...dropped.map((name) => toldBlockFact(INSTRUCTED + name, "")),
97
+ ],
61
98
  };
62
99
  }
63
100
 
64
- export function applyMcpDelta(announced: McpAnnounced, delta: McpDelta): void {
65
- for (const s of delta.newInstructions) announced.instructed.set(s.name, s.instructions);
66
- for (const f of delta.newFailures) announced.failed.set(f.name, f.error);
67
- for (const name of delta.recovered) announced.failed.delete(name);
68
- for (const name of delta.dropped) announced.instructed.delete(name);
69
- }
70
-
71
- /** One-shot notice texts for a delta, in the order they should be queued. Empty when nothing changed. */
101
+ /**
102
+ * The notices for a delta, in the order they go on the tail. Empty when
103
+ * nothing changed.
104
+ */
72
105
  export function mcpChangeNotices(delta: McpDelta): string[] {
73
106
  const notices: string[] = [];
107
+ const add = (text: string) => notices.push(text);
74
108
  if (delta.recovered.length > 0) {
75
- notices.push(
109
+ add(
76
110
  `MCP server${delta.recovered.length === 1 ? "" : "s"} ${delta.recovered.join(", ")} ` +
77
111
  "connected after this conversation started (an earlier notice reported a connection failure). " +
78
112
  "The server's tools are available as deferred tools — load them with tool_search.",
@@ -80,20 +114,33 @@ export function mcpChangeNotices(delta: McpDelta): string[] {
80
114
  }
81
115
  if (delta.newInstructions.length > 0) {
82
116
  const names = delta.newInstructions.map((s) => s.name);
83
- notices.push(
117
+ add(
84
118
  `MCP server${names.length === 1 ? "" : "s"} ${names.join(", ")} connected after this conversation started; ` +
85
119
  "the tools are available as deferred tools (load with tool_search). Server instructions:\n\n" +
86
120
  mcpInstructionsReminder(delta.newInstructions.map((s) => ({ server: { name: s.name }, instructions: s.instructions }))),
87
121
  );
88
122
  }
89
123
  if (delta.newFailures.length > 0) {
90
- notices.push(mcpFailuresReminder(delta.newFailures));
124
+ add(mcpFailuresReminder(delta.newFailures));
91
125
  }
92
126
  if (delta.dropped.length > 0) {
93
- notices.push(
127
+ add(
94
128
  `MCP server${delta.dropped.length === 1 ? "" : "s"} ${delta.dropped.join(", ")} disconnected; ` +
95
129
  'their tools answer "not connected" until the server is reconnected (the user can do that in /mcp).',
96
130
  );
97
131
  }
98
132
  return notices;
99
133
  }
134
+
135
+ /**
136
+ * The user's startup notices, Claude Code's wording (`useMcpConnectivityStatus`):
137
+ * `2 MCP servers failed · /mcp` and `1 MCP server needs auth · /mcp`, with each
138
+ * server's error left to /mcp. Printing every server's raw error ("fetch
139
+ * failed" per remote server when offline) buried the prompt.
140
+ */
141
+ export function mcpStartupNotices(failedServers: number, needsAuth: number): string[] {
142
+ const out: string[] = [];
143
+ if (failedServers > 0) out.push(`${countNoun(failedServers, "MCP server")} failed · /mcp`);
144
+ if (needsAuth > 0) out.push(`${countNoun(needsAuth, "MCP server")} ${needsAuth === 1 ? "needs" : "need"} auth · /mcp`);
145
+ return out;
146
+ }
@@ -5,9 +5,13 @@
5
5
 
6
6
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
7
  import type { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8
+ import type { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
8
9
  import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
9
10
  import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
11
+ import { childProcessEnv } from "../lib/app-launch.mjs";
10
12
  import type { McpServer } from "./config.ts";
13
+ import { collectPages } from "./discovery.ts";
14
+ import { McpAuthenticationRequiredError } from "./oauth/provider.ts";
11
15
 
12
16
  /**
13
17
  * The MCP SDK costs ~70-80ms to load (findings §15), so it is imported on the
@@ -19,6 +23,7 @@ let sdkPromise:
19
23
  Client: typeof import("@modelcontextprotocol/sdk/client/index.js").Client;
20
24
  StdioClientTransport: typeof import("@modelcontextprotocol/sdk/client/stdio.js").StdioClientTransport;
21
25
  StreamableHTTPClientTransport: typeof import("@modelcontextprotocol/sdk/client/streamableHttp.js").StreamableHTTPClientTransport;
26
+ SSEClientTransport: typeof import("@modelcontextprotocol/sdk/client/sse.js").SSEClientTransport;
22
27
  UnauthorizedError: typeof import("@modelcontextprotocol/sdk/client/auth.js").UnauthorizedError;
23
28
  }>
24
29
  | undefined;
@@ -27,6 +32,10 @@ let sdkPromise:
27
32
  // (the SDK's UnauthorizedError does not set `.name`, so a name/message check is
28
33
  // unreliable — a custom-message instance would be missed).
29
34
  let unauthorizedClass: typeof import("@modelcontextprotocol/sdk/client/auth.js").UnauthorizedError | undefined;
35
+ let httpErrorClass: typeof import("@modelcontextprotocol/sdk/client/streamableHttp.js").StreamableHTTPError | undefined;
36
+ let sseErrorClass: typeof import("@modelcontextprotocol/sdk/client/sse.js").SseError | undefined;
37
+
38
+ type HttpTransport = StreamableHTTPClientTransport | SSEClientTransport;
30
39
 
31
40
  function loadSdk() {
32
41
  sdkPromise ??= Promise.all([
@@ -34,12 +43,16 @@ function loadSdk() {
34
43
  import("@modelcontextprotocol/sdk/client/stdio.js"),
35
44
  import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
36
45
  import("@modelcontextprotocol/sdk/client/auth.js"),
37
- ]).then(([client, stdio, http, auth]) => {
46
+ import("@modelcontextprotocol/sdk/client/sse.js"),
47
+ ]).then(([client, stdio, http, auth, sse]) => {
38
48
  unauthorizedClass = auth.UnauthorizedError;
49
+ httpErrorClass = http.StreamableHTTPError;
50
+ sseErrorClass = sse.SseError;
39
51
  return {
40
52
  Client: client.Client,
41
53
  StdioClientTransport: stdio.StdioClientTransport,
42
54
  StreamableHTTPClientTransport: http.StreamableHTTPClientTransport,
55
+ SSEClientTransport: sse.SSEClientTransport,
43
56
  UnauthorizedError: auth.UnauthorizedError,
44
57
  };
45
58
  });
@@ -62,11 +75,21 @@ export interface McpResource {
62
75
  mimeType?: string;
63
76
  }
64
77
 
78
+ export interface McpResourceTemplate {
79
+ uriTemplate: string;
80
+ name?: string;
81
+ description?: string;
82
+ mimeType?: string;
83
+ }
84
+
65
85
  export interface Connection {
66
86
  server: McpServer;
67
87
  client: Client;
68
88
  tools: McpTool[];
69
89
  resources: McpResource[];
90
+ resourceTemplates?: McpResourceTemplate[];
91
+ /** Called after a tool-list refresh, or with its error while the last good list stays intact. */
92
+ onToolsChanged?: (error?: Error) => void;
70
93
  /** The server's own usage instructions from its initialize result, if any. */
71
94
  instructions?: string;
72
95
  /** Non-fatal problems after a successful connect (e.g. listTools failed). */
@@ -126,8 +149,11 @@ function withTimeout<T>(promise: Promise<T>, ms: number, what: string): Promise<
126
149
  });
127
150
  }
128
151
 
129
- /** True when the error is the SDK's "authorization required" signal (an HTTP 401). */
152
+ /** True when the server requires authentication, including an expired stored grant. */
130
153
  export function isUnauthorized(error: unknown): boolean {
154
+ if (error instanceof McpAuthenticationRequiredError) return true;
155
+ if (httpErrorClass && error instanceof httpErrorClass && error.code === 401) return true;
156
+ if (sseErrorClass && error instanceof sseErrorClass && error.code === 401) return true;
131
157
  // The class is captured on the first loadSdk(); every caller runs after a
132
158
  // connect (which awaits it), so it is set. The message fallback only covers
133
159
  // the impossible pre-load case.
@@ -140,61 +166,140 @@ function buildTransport(
140
166
  server: McpServer,
141
167
  sdk: Awaited<ReturnType<typeof loadSdk>>,
142
168
  authProvider?: OAuthClientProvider,
169
+ keepOAuthOpen?: () => boolean,
143
170
  ): { transport: Transport; stderrTail: () => string } {
144
171
  const tail = createTailBuffer();
145
172
  if (server.kind === "stdio") {
146
173
  const transport = new sdk.StdioClientTransport({
147
174
  command: server.command,
148
175
  args: server.args,
149
- env: { ...(process.env as Record<string, string>), ...(server.env ?? {}) },
176
+ // The user's own environment under the bundled app (lib/app-launch.mjs), then the server's.
177
+ env: { ...(childProcessEnv(process.env) as Record<string, string>), ...(server.env ?? {}) },
178
+ ...(server.cwd ? { cwd: server.cwd } : {}),
150
179
  stderr: "pipe",
151
180
  });
152
181
  // The PassThrough exists before start(); drain it from the first byte.
153
182
  transport.stderr?.on("data", (chunk: string | Uint8Array) => tail.push(chunk));
154
183
  return { transport, stderrTail: tail.text };
155
184
  }
156
- const transport = new sdk.StreamableHTTPClientTransport(new URL(server.url), {
185
+ const lifetime = new AbortController();
186
+ const RemoteTransport = server.transport === "sse" ? sdk.SSEClientTransport : sdk.StreamableHTTPClientTransport;
187
+ const transport = new RemoteTransport(new URL(server.url), {
157
188
  authProvider,
158
189
  requestInit: server.headers ? { headers: server.headers } : undefined,
190
+ // The SDK's close signal covers MCP requests, but not the discovery and
191
+ // token fetches it starts during OAuth. Those must stop with the client too.
192
+ fetch: (input, init) => fetch(input, {
193
+ ...init,
194
+ signal: init?.signal ? AbortSignal.any([lifetime.signal, init.signal]) : lifetime.signal,
195
+ }),
159
196
  });
197
+ const closeTransport = transport.close.bind(transport);
198
+ transport.close = async () => {
199
+ if (!keepOAuthOpen?.()) lifetime.abort();
200
+ await closeTransport();
201
+ };
160
202
  return { transport, stderrTail: tail.text };
161
203
  }
162
204
 
163
- /** List a connected client's tools and resources into a Connection. */
205
+ /** Bound the complete list, not each page, and cancel its last request on timeout. */
206
+ async function discoverPages<T>(
207
+ fetchPage: (cursor: string | undefined, signal: AbortSignal) => Promise<{ items: T[]; nextCursor?: string }>,
208
+ what: string,
209
+ ): Promise<T[]> {
210
+ const controller = new AbortController();
211
+ try {
212
+ return await withTimeout(collectPages((cursor) => fetchPage(cursor, controller.signal), controller.signal), CONNECT_TIMEOUT_MS, what);
213
+ } catch (error) {
214
+ controller.abort();
215
+ throw error;
216
+ }
217
+ }
218
+
219
+ /** A fresh, complete tool snapshot. */
220
+ export function discoverTools(client: Client, serverName: string): Promise<McpTool[]> {
221
+ return discoverPages(async (cursor, signal) => {
222
+ const result = await client.listTools(cursor === undefined ? undefined : { cursor }, { signal });
223
+ return { items: result.tools, nextCursor: result.nextCursor };
224
+ }, `listing tools of "${serverName}"`);
225
+ }
226
+
227
+ /** List a connected client's tools, resources and URI templates into a Connection. */
164
228
  async function finalizeConnection(client: Client, server: McpServer, stderrTail: () => string = () => ""): Promise<Connection> {
165
229
  const warnings: string[] = [];
166
230
 
167
- // Each list call can take up to CONNECT_TIMEOUT_MS on a slow server; run
168
- // them concurrently instead of stacking their worst-case latencies.
169
- const [toolsResult, resourcesResult] = await Promise.allSettled([
170
- withTimeout(client.listTools(), CONNECT_TIMEOUT_MS, `listing tools of "${server.name}"`),
171
- withTimeout(client.listResources(), CONNECT_TIMEOUT_MS, `listing resources of "${server.name}"`),
231
+ // Each complete list has one deadline; run them concurrently instead of
232
+ // stacking their worst-case latencies or granting each page a fresh timeout.
233
+ const [toolsResult, resourcesResult, templatesResult] = await Promise.allSettled([
234
+ discoverTools(client, server.name),
235
+ discoverPages(async (cursor, signal) => {
236
+ const result = await client.listResources(cursor === undefined ? undefined : { cursor }, { signal });
237
+ return { items: result.resources, nextCursor: result.nextCursor };
238
+ }, `listing resources of "${server.name}"`),
239
+ discoverPages(async (cursor, signal) => {
240
+ const result = await client.listResourceTemplates(cursor === undefined ? undefined : { cursor }, { signal });
241
+ return { items: result.resourceTemplates, nextCursor: result.nextCursor };
242
+ }, `listing resource templates of "${server.name}"`),
172
243
  ]);
173
244
 
174
245
  let tools: McpTool[] = [];
175
246
  if (toolsResult.status === "fulfilled") {
176
- tools = (toolsResult.value.tools ?? []) as McpTool[];
247
+ tools = toolsResult.value;
177
248
  } else {
178
249
  // A server that connects but fails listTools would otherwise register
179
250
  // zero tools with no signal anywhere — surface it as a warning.
180
251
  warnings.push(`connected, but listing tools failed: ${(toolsResult.reason as Error).message}`);
181
252
  }
182
253
 
183
- let resources: McpResource[] = [];
184
- if (resourcesResult.status === "fulfilled") {
185
- resources = (resourcesResult.value.resources ?? []) as McpResource[];
186
- } else {
187
- // Resources are optional in MCP; "method not found" is normal. Anything
188
- // else (a timeout, a protocol error) is worth a warning.
189
- const message = (resourcesResult.reason as Error).message;
190
- if (!/method not found|-32601/i.test(message)) {
191
- warnings.push(`connected, but listing resources failed: ${message}`);
192
- }
193
- }
194
-
254
+ // Resources and templates are optional in MCP; "method not found" is
255
+ // normal. A timeout or protocol error must still surface as a warning.
256
+ const optionalList = <T>(result: PromiseSettledResult<T[]>, label: string): T[] => {
257
+ if (result.status === "fulfilled") return result.value;
258
+ const message = (result.reason as Error).message;
259
+ if (!/method not found|-32601/i.test(message)) warnings.push(`connected, but listing ${label} failed: ${message}`);
260
+ return [];
261
+ };
262
+ const resources = optionalList(resourcesResult, "resources");
263
+ const resourceTemplates = optionalList(templatesResult, "resource templates");
195
264
  const instructions = client.getInstructions()?.trim() || undefined;
196
265
 
197
- return { server, client, tools, resources, instructions, warnings, stderrTail };
266
+ const connection: Connection = { server, client, tools, resources, resourceTemplates, instructions, warnings, stderrTail };
267
+ const { ToolListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
268
+ let refreshing = false;
269
+ let pending = false;
270
+ client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
271
+ if (connection.closing) return;
272
+ pending = true;
273
+ if (refreshing) return;
274
+ refreshing = true;
275
+ try {
276
+ // A burst during an in-flight list gets one follow-up refresh, never
277
+ // concurrent snapshots that could overwrite a newer list with an old one.
278
+ while (pending && !connection.closing) {
279
+ pending = false;
280
+ let refreshed: McpTool[];
281
+ try {
282
+ refreshed = await discoverTools(client, server.name);
283
+ } catch (cause) {
284
+ if (connection.closing) return;
285
+ const error = cause instanceof Error ? cause : new Error(String(cause));
286
+ const warning = `connected, but refreshing tools failed: ${error.message}`;
287
+ if (!connection.warnings.includes(warning)) connection.warnings.push(warning);
288
+ connection.onToolsChanged?.(error);
289
+ continue;
290
+ }
291
+ if (connection.closing) return;
292
+ connection.tools = refreshed;
293
+ connection.onToolsChanged?.();
294
+ }
295
+ } finally {
296
+ refreshing = false;
297
+ }
298
+ });
299
+ // A server can exit after initialize but before discovery finishes. Its
300
+ // onclose already fired, before index.ts can adopt the connection.
301
+ if (!client.transport) throw new Error(withStderr(`connection to "${server.name}" closed during discovery`, stderrTail()));
302
+ return connection;
198
303
  }
199
304
 
200
305
  /**
@@ -208,10 +313,11 @@ async function finalizeConnection(client: Client, server: McpServer, stderrTail:
208
313
  async function openClient(
209
314
  server: McpServer,
210
315
  authProvider?: OAuthClientProvider,
316
+ keepOAuthOpen?: () => boolean,
211
317
  ): Promise<{ client: Client; transport: Transport; stderrTail: () => string }> {
212
318
  const sdk = await loadSdk();
213
319
  const client = new sdk.Client({ name: "one-code", version: "0.1.0" }, { capabilities: {} });
214
- return { client, ...buildTransport(server, sdk, authProvider) };
320
+ return { client, ...buildTransport(server, sdk, authProvider, keepOAuthOpen) };
215
321
  }
216
322
 
217
323
  /**
@@ -254,22 +360,35 @@ export async function connect(server: McpServer, authProvider?: OAuthClientProvi
254
360
  export async function beginInteractiveAuth(
255
361
  server: McpServer,
256
362
  authProvider: OAuthClientProvider,
257
- ): Promise<{ transport: StreamableHTTPClientTransport } | { connection: Connection }> {
363
+ ): Promise<{ transport: HttpTransport } | { connection: Connection }> {
258
364
  if (server.kind !== "http") throw new Error(`OAuth is only available for http MCP servers; "${server.name}" is stdio.`);
259
- const { client, transport: baseTransport, stderrTail } = await openClient(server, authProvider);
260
- const transport = baseTransport as StreamableHTTPClientTransport;
365
+ // Client.connect closes the transport after the expected UnauthorizedError.
366
+ // Keep OAuth fetches usable until the redirect has been handed to the caller.
367
+ let awaitingRedirect = true;
368
+ const { client, transport: baseTransport, stderrTail } = await openClient(server, authProvider, () => awaitingRedirect);
369
+ const transport = baseTransport as HttpTransport;
261
370
  try {
262
371
  // Not connectOrClose: on a 401 the transport must stay open — it carries
263
372
  // the OAuth flow state the caller finishes with finishAuth(code).
264
373
  await withTimeout(client.connect(transport), CONNECT_TIMEOUT_MS, `connecting to "${server.name}"`);
265
374
  return { connection: await finalizeConnection(client, server, stderrTail) };
266
375
  } catch (error) {
267
- if (isUnauthorized(error)) return { transport };
376
+ // Only the SDK's redirect signal means a browser callback is coming.
377
+ // A raw 401 after refreshing tokens is an auth failure, not a redirect.
378
+ if (unauthorizedClass && error instanceof unauthorizedClass) return { transport };
379
+ awaitingRedirect = false;
268
380
  await transport.close().catch(() => {});
269
381
  throw error;
382
+ } finally {
383
+ awaitingRedirect = false;
270
384
  }
271
385
  }
272
386
 
387
+ /** Bound the token exchange too; the callback deadline only covers waiting for the browser. */
388
+ export async function finishInteractiveAuth(transport: HttpTransport, code: string): Promise<void> {
389
+ await withTimeout(transport.finishAuth(code), CONNECT_TIMEOUT_MS, "exchanging the authorization code");
390
+ }
391
+
273
392
  const INSTRUCTIONS_CAP = 3000;
274
393
 
275
394
  /**
@@ -322,21 +441,15 @@ export async function callTool(
322
441
  connection: Connection,
323
442
  toolName: string,
324
443
  args: Record<string, unknown>,
325
- ): Promise<{ content?: unknown[]; isError?: boolean }> {
326
- const result = await withTimeout(
327
- connection.client.callTool({ name: toolName, arguments: args }),
328
- CALL_TIMEOUT_MS,
329
- `calling "${toolName}"`,
330
- );
331
- return result as { content?: unknown[]; isError?: boolean };
444
+ signal?: AbortSignal,
445
+ ): Promise<{ content?: unknown[]; isError?: boolean; structuredContent?: Record<string, unknown> }> {
446
+ return await connection.client.callTool({ name: toolName, arguments: args }, undefined, { signal, timeout: CALL_TIMEOUT_MS }) as {
447
+ content?: unknown[]; isError?: boolean; structuredContent?: Record<string, unknown>;
448
+ };
332
449
  }
333
450
 
334
- export async function readResource(connection: Connection, uri: string): Promise<{ contents?: unknown[] }> {
335
- return (await withTimeout(
336
- connection.client.readResource({ uri }),
337
- CALL_TIMEOUT_MS,
338
- `reading resource "${uri}"`,
339
- )) as { contents?: unknown[] };
451
+ export async function readResource(connection: Connection, uri: string, signal?: AbortSignal): Promise<{ contents?: unknown[] }> {
452
+ return await connection.client.readResource({ uri }, { signal, timeout: CALL_TIMEOUT_MS });
340
453
  }
341
454
 
342
455
  /**
@@ -345,13 +458,13 @@ export async function readResource(connection: Connection, uri: string): Promise
345
458
  * The SDK only calls `safeParse` on the result schema, and the response shape is
346
459
  * server-defined, so a passthrough schema beats pinning one the spec hasn't settled.
347
460
  */
348
- export async function readResourceDir(connection: Connection, uri: string): Promise<Record<string, unknown>> {
461
+ export async function readResourceDir(connection: Connection, uri: string, signal?: AbortSignal): Promise<Record<string, unknown>> {
349
462
  const passthrough = { safeParse: (data: unknown) => ({ success: true as const, data }) };
350
- return (await withTimeout(
351
- connection.client.request({ method: "resources/directory/read", params: { uri } }, passthrough as never),
352
- CALL_TIMEOUT_MS,
353
- `listing directory "${uri}"`,
354
- )) as Record<string, unknown>;
463
+ return await connection.client.request(
464
+ { method: "resources/directory/read", params: { uri } },
465
+ passthrough as never,
466
+ { signal, timeout: CALL_TIMEOUT_MS },
467
+ ) as Record<string, unknown>;
355
468
  }
356
469
 
357
470
  export async function close(connection: Connection): Promise<void> {