one-code-extension 0.4.1 → 0.5.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 (419) 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 +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -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 +123 -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 +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -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 +52 -0
  63. package/extensions/background/wakeup.ts +159 -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 +88 -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 +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +18 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -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> {
@@ -2,17 +2,36 @@
2
2
  * MCP server configuration in Claude Code's format (pure).
3
3
  *
4
4
  * Sources, lowest to highest precedence:
5
+ * plugins' .mcp.json (bare server maps)
6
+ * pi.registerMcpServer() (servers other pi extensions register, pi 0.99+)
7
+ * <agentDir>/mcp.json (pi's own user file, ~/.pi/agent/mcp.json)
8
+ * <cwd>/.pi/mcp.json (pi's own project file)
5
9
  * ~/.claude.json (user, global)
6
10
  * <cwd>/.mcp.json (project, checked in — walked up to the repo root)
7
11
  * <cwd>/.claude/settings.local.json (project, personal)
8
12
  *
13
+ * Independent mode (lib/config-mode.ts) reads none of the three Claude Code
14
+ * files: plugins, registered servers and pi's two files only.
15
+ *
9
16
  * Every file holds an `mcpServers` object. A stdio server has `command` (plus
10
17
  * optional `args`, `env`); a remote one has `url` and optional `type`/`headers`.
18
+ *
19
+ * pi's two files are read because One Code's `/mcp` replaces pi 0.99's
20
+ * built-in MCP, which would otherwise have read them (decisions/mcp.md). Their
21
+ * format is pi's: `enabled: false` turns a server off, a stdio server may set
22
+ * `cwd` (relative to the session directory), and a leading `~/` in `command`,
23
+ * an argument or `cwd` names the home directory. A server with a `!command`
24
+ * value in `env` or `headers` is skipped with a note: running a shell command
25
+ * from a config file to build a credential is not supported. pi's
26
+ * `oauth`/`auth` settings, `exposure`, `timeout` and `description` are
27
+ * ignored. A Claude Code file's server of the same name wins, with a note.
11
28
  */
12
29
 
13
30
  import { existsSync, readFileSync } from "node:fs";
14
- import { dirname, join } from "node:path";
15
- import { claudeJsonPath } from "../lib/paths.ts";
31
+ import { dirname, join, resolve } from "node:path";
32
+ import { claudeSourcesOn } from "../lib/config-mode.ts";
33
+ import { claudeJsonPath, expandTilde } from "../lib/paths.ts";
34
+ import { hashServerConfig } from "./trust.ts";
16
35
 
17
36
  export interface StdioServer {
18
37
  kind: "stdio";
@@ -20,7 +39,11 @@ export interface StdioServer {
20
39
  command: string;
21
40
  args: string[];
22
41
  env?: Record<string, string>;
42
+ /** Working directory (pi's `mcp.json` only); the session's directory when absent. */
43
+ cwd?: string;
23
44
  source: string;
45
+ /** Set for a server read in pi's format: which of pi's sources defined it. */
46
+ piOrigin?: PiOrigin;
24
47
  /** Config referenced these environment variables and they are not set. */
25
48
  missingEnv?: string[];
26
49
  /** Every env var the config references (set or not), for the consent dialog — values are expanded away by parse time. */
@@ -29,10 +52,13 @@ export interface StdioServer {
29
52
 
30
53
  export interface HttpServer {
31
54
  kind: "http";
55
+ /** Legacy HTTP+SSE when explicitly configured; otherwise Streamable HTTP. */
56
+ transport?: "sse";
32
57
  name: string;
33
58
  url: string;
34
59
  headers?: Record<string, string>;
35
60
  source: string;
61
+ piOrigin?: PiOrigin;
36
62
  /** Config referenced these environment variables and they are not set. */
37
63
  missingEnv?: string[];
38
64
  /** Every env var the config references (set or not), for the consent dialog — values are expanded away by parse time. */
@@ -41,6 +67,9 @@ export interface HttpServer {
41
67
 
42
68
  export type McpServer = StdioServer | HttpServer;
43
69
 
70
+ /** pi's user `mcp.json`, its project `mcp.json`, or an extension's `pi.registerMcpServer()`. */
71
+ export type PiOrigin = "user" | "project" | "extension";
72
+
44
73
  interface RawServer {
45
74
  command?: unknown;
46
75
  args?: unknown;
@@ -49,6 +78,26 @@ interface RawServer {
49
78
  type?: unknown;
50
79
  headers?: unknown;
51
80
  disabled?: unknown;
81
+ /** pi's `mcp.json`: `false` keeps the entry without connecting. */
82
+ enabled?: unknown;
83
+ /** pi's `mcp.json`: a stdio server's working directory. */
84
+ cwd?: unknown;
85
+ }
86
+
87
+ /** How to read a server from one of pi's own `mcp.json` files. */
88
+ export interface PiFormat {
89
+ home: string;
90
+ /** The session directory a relative `cwd` resolves against. */
91
+ cwd: string;
92
+ }
93
+
94
+ /**
95
+ * Why a pi-format server cannot be loaded, or undefined: a `!command` value
96
+ * in `env` or `headers` (pi runs it in a shell to build the value).
97
+ */
98
+ export function piFormatProblem(raw: RawServer): string | undefined {
99
+ const values = [...Object.values(asStringRecord(raw.env) ?? {}), ...Object.values(asStringRecord(raw.headers) ?? {})];
100
+ return values.some((value) => value.startsWith("!")) ? "a `!command` value in env or headers is not supported" : undefined;
52
101
  }
53
102
 
54
103
  function asStringRecord(value: unknown): Record<string, string> | undefined {
@@ -100,8 +149,11 @@ export function parseServer(
100
149
  raw: RawServer,
101
150
  source: string,
102
151
  env: Record<string, string | undefined>,
152
+ pi?: PiFormat,
103
153
  ): McpServer | undefined {
104
154
  if (raw.disabled === true) return undefined;
155
+ if (pi && raw.enabled === false) return undefined;
156
+ const home = (value: string) => (pi ? expandTilde(value, pi.home) : value);
105
157
 
106
158
  if (typeof raw.url === "string" && raw.url.trim()) {
107
159
  const headers = asStringRecord(raw.headers);
@@ -112,6 +164,7 @@ export function parseServer(
112
164
  const referenced = referencedEnvVars(raw.url, ...Object.values(headers ?? {}));
113
165
  return {
114
166
  kind: "http",
167
+ ...(raw.type === "sse" ? { transport: "sse" as const } : {}),
115
168
  name,
116
169
  url: expandEnv(raw.url, env),
117
170
  headers: headers
@@ -125,11 +178,14 @@ export function parseServer(
125
178
 
126
179
  if (typeof raw.command === "string" && raw.command.trim()) {
127
180
  const args = Array.isArray(raw.args)
128
- ? raw.args.filter((a): a is string => typeof a === "string").map((a) => expandEnv(a, env))
181
+ ? raw.args.filter((a): a is string => typeof a === "string").map((a) => home(expandEnv(a, env)))
129
182
  : [];
183
+ const rawCwd = pi && typeof raw.cwd === "string" && raw.cwd.trim() ? raw.cwd : undefined;
184
+ const cwd = rawCwd && pi ? resolve(pi.cwd, home(expandEnv(rawCwd, env))) : undefined;
130
185
  const rawEnv = asStringRecord(raw.env);
131
186
  const missing = [
132
187
  ...missingEnvVars(raw.command, env),
188
+ ...missingEnvVars(rawCwd ?? "", env),
133
189
  ...(Array.isArray(raw.args) ? raw.args : [])
134
190
  .filter((a): a is string => typeof a === "string")
135
191
  .flatMap((a) => missingEnvVars(a, env)),
@@ -137,15 +193,17 @@ export function parseServer(
137
193
  ];
138
194
  const referenced = referencedEnvVars(
139
195
  raw.command,
196
+ rawCwd ?? "",
140
197
  ...(Array.isArray(raw.args) ? raw.args.filter((a): a is string => typeof a === "string") : []),
141
198
  ...Object.values(rawEnv ?? {}),
142
199
  );
143
200
  return {
144
201
  kind: "stdio",
145
202
  name,
146
- command: expandEnv(raw.command, env),
203
+ command: home(expandEnv(raw.command, env)),
147
204
  args,
148
205
  env: rawEnv ? Object.fromEntries(Object.entries(rawEnv).map(([k, v]) => [k, expandEnv(v, env)])) : undefined,
206
+ ...(cwd ? { cwd } : {}),
149
207
  source,
150
208
  missingEnv: missing.length > 0 ? [...new Set(missing)] : undefined,
151
209
  referencedEnv: referenced.length > 0 ? referenced : undefined,
@@ -171,7 +229,29 @@ export function findProjectConfigs(cwd: string): string[] {
171
229
  return found.reverse();
172
230
  }
173
231
 
232
+ /**
233
+ * `/mcp` with no servers: where to look when that is unexpected (`/doctor`
234
+ * lists config files that failed validation) and where servers go, by mode.
235
+ * Claude Code names its own CLI and docs here; One Code names its own.
236
+ */
237
+ export const MCP_GUIDE_URL = "https://github.com/IsuruMaduranga/one-code/blob/master/docs/guide/skills-plugins-and-mcp.md#mcp-servers";
238
+ export function noMcpServersMessage(claudeSources: boolean): string {
239
+ const where = claudeSources ? ".mcp.json in the project, ~/.claude.json, .pi/mcp.json or the agent dir's mcp.json" : ".pi/mcp.json or the agent dir's mcp.json";
240
+ return `No MCP servers configured. Run /doctor if this is unexpected — it lists MCP config files that failed validation. Otherwise, add servers to ${where}; see ${MCP_GUIDE_URL} to learn more.`;
241
+ }
242
+
243
+ /**
244
+ * pi's own `mcp.json` files: the agent dir's and the project's. `configDirName`
245
+ * is pi's `CONFIG_DIR_NAME` (`.pi` unless pi itself is rebranded); callers
246
+ * that import pi pass it.
247
+ */
248
+ export function piMcpConfigPaths(agentDir: string, cwd: string, configDirName = ".pi"): { user: string; project: string } {
249
+ return { user: join(agentDir, "mcp.json"), project: join(cwd, configDirName, "mcp.json") };
250
+ }
251
+
252
+ /** Claude Code's MCP files; none in independent mode (lib/config-mode.ts), where pi's two `mcp.json` files remain. */
174
253
  export function configPaths(cwd: string, home: string): string[] {
254
+ if (!claudeSourcesOn()) return [];
175
255
  return [claudeJsonPath(home), ...findProjectConfigs(cwd), join(cwd, ".claude", "settings.local.json")];
176
256
  }
177
257
 
@@ -196,6 +276,25 @@ export interface LoadServersOptions {
196
276
  pluginNames?: ReadonlyMap<string, string>;
197
277
  /** Called for a config file that exists but is not valid JSON (review M11: a silent drop read as "no servers configured"). */
198
278
  onError?: (path: string, message: string) => void;
279
+ /** pi's own `mcp.json` files (user, then project): read in pi's format, below every Claude Code file. */
280
+ piConfigs?: { user: string; project: string };
281
+ /** Called for a pi-format server that is skipped, or overridden by a Claude Code file's server of the same name. */
282
+ onNote?: (message: string) => void;
283
+ /** Servers other pi extensions registered (`pi.getMcpServers()`): pi's format, below every file; `source` is the extension's path. */
284
+ registered?: readonly RegisteredServer[];
285
+ }
286
+
287
+ /** One `pi.getMcpServers()` entry, structurally (pi 0.99's `RegisteredMcpServer`). */
288
+ export interface RegisteredServer {
289
+ name: string;
290
+ config: unknown;
291
+ extensionPath: string;
292
+ }
293
+
294
+ /** The servers other extensions registered (`pi.getMcpServers()`, pi 0.99+); none on an older pi. */
295
+ export function registeredMcpServers(pi: object): RegisteredServer[] {
296
+ const host = pi as { getMcpServers?: () => RegisteredServer[] };
297
+ return typeof host.getMcpServers === "function" ? host.getMcpServers() : [];
199
298
  }
200
299
 
201
300
  export function loadServers(
@@ -206,8 +305,21 @@ export function loadServers(
206
305
  options: LoadServersOptions = {},
207
306
  ): McpServer[] {
208
307
  const byName = new Map<string, McpServer>();
209
- // Plugin configs come first so project and user files can override them.
210
- for (const path of [...extraPaths, ...configPaths(cwd, home)]) {
308
+ const piPaths = options.piConfigs ? [options.piConfigs.user, options.piConfigs.project] : [];
309
+ const piFormat: PiFormat = { home, cwd };
310
+ for (const { name, config, extensionPath } of options.registered ?? []) {
311
+ const raw = (config && typeof config === "object" ? config : {}) as RawServer;
312
+ const problem = piFormatProblem(raw);
313
+ if (problem) {
314
+ options.onNote?.(`MCP server "${name}" registered by ${extensionPath} is skipped: ${problem}.`);
315
+ continue;
316
+ }
317
+ const server = parseServer(name, raw, extensionPath, env, piFormat);
318
+ if (server) byName.set(name, { ...server, piOrigin: "extension" });
319
+ }
320
+ // Plugin configs come first so project and user files can override them,
321
+ // then pi's own files, then Claude Code's.
322
+ for (const path of [...extraPaths, ...piPaths, ...configPaths(cwd, home)]) {
211
323
  if (!existsSync(path)) continue;
212
324
  let file: Record<string, unknown> | undefined;
213
325
  try {
@@ -219,10 +331,22 @@ export function loadServers(
219
331
  const servers = serverMapOf(file);
220
332
  if (!servers) continue;
221
333
  const plugin = options.pluginNames?.get(path);
334
+ const piOrigin: PiOrigin | undefined =
335
+ path === options.piConfigs?.user ? "user" : path === options.piConfigs?.project ? "project" : undefined;
222
336
  for (const [rawName, raw] of Object.entries(servers)) {
223
337
  const name = plugin ? `plugin:${plugin}:${rawName}` : rawName;
224
- const server = parseServer(name, raw ?? {}, path, env);
225
- if (server) byName.set(name, server);
338
+ const problem = piOrigin ? piFormatProblem(raw ?? {}) : undefined;
339
+ if (problem) {
340
+ options.onNote?.(`MCP server "${name}" in ${path} is skipped: ${problem}.`);
341
+ continue;
342
+ }
343
+ const server = parseServer(name, raw ?? {}, path, env, piOrigin ? piFormat : undefined);
344
+ const inherited = byName.get(name);
345
+ // The same server kept in both (common while moving between the two) is not worth a warning.
346
+ if (!piOrigin && server && inherited?.piOrigin && hashServerConfig(server) !== hashServerConfig(inherited)) {
347
+ options.onNote?.(`MCP server "${name}" in ${inherited.source} is overridden by the one in ${path}.`);
348
+ }
349
+ if (server) byName.set(name, piOrigin ? { ...server, piOrigin } : server);
226
350
  else byName.delete(name); // an explicit `disabled` entry removes an inherited one
227
351
  }
228
352
  }
@@ -0,0 +1,19 @@
1
+ /** Collect an MCP list response's cursor pages without silently dropping later entries. */
2
+ export async function collectPages<T>(
3
+ fetchPage: (cursor?: string) => Promise<{ items: T[]; nextCursor?: string }>,
4
+ signal?: AbortSignal,
5
+ ): Promise<T[]> {
6
+ const items: T[] = [];
7
+ const seen = new Set<string>();
8
+ let cursor: string | undefined;
9
+ while (true) {
10
+ signal?.throwIfAborted();
11
+ const page = await fetchPage(cursor);
12
+ signal?.throwIfAborted();
13
+ for (const item of page.items) items.push(item);
14
+ if (page.nextCursor === undefined) return items;
15
+ if (seen.has(page.nextCursor)) throw new Error("MCP server repeated a pagination cursor; check the server's list implementation.");
16
+ seen.add(page.nextCursor);
17
+ cursor = page.nextCursor;
18
+ }
19
+ }