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
@@ -26,194 +26,361 @@
26
26
  * and pi's event bus only delivers to listeners already registered.
27
27
  */
28
28
 
29
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
29
+ import type { ExtensionAPI, SessionEntry } from "@earendil-works/pi-coding-agent";
30
30
  import { Type } from "typebox";
31
31
  import {
32
32
  DEFER_CHANNEL,
33
33
  deferredAddendumText,
34
34
  deferredMissReminderText,
35
35
  DeferredRegistry,
36
+ deferralEnabled,
36
37
  deferredReminderText,
38
+ loaderUnavailableReminderText,
37
39
  type DeferRequest,
38
40
  resultText,
39
41
  searchTools,
40
- selectedNames,
42
+ selectedRequests,
43
+ type ResponsesToolLoadModel,
41
44
  stabilizeDeferredTools,
45
+ stabilizeResponsesToolLoads,
46
+ responsesToolLoadCompat,
42
47
  supportsToolReferences,
43
48
  toolNotFoundName,
44
49
  toolSearchLoads,
50
+ toolsInUse,
51
+ callableDeferredTools,
52
+ reconcileActive,
53
+ WITHHOLD_CHANNEL,
54
+ DEFERRED_QUERY_CHANNEL,
55
+ type DeferredQuery,
56
+ deferredRemovedText,
57
+ withheldMissReminderText,
58
+ unavailableToolReminderText,
45
59
  } from "../lib/deferred.ts";
46
- import { looksLikeAnthropicRequest } from "../lib/anthropic-payload.ts";
47
- import { MCP_TOOLS_CHANNEL, type McpToolsPayload } from "../lib/mcp-share.ts";
48
- import { CONTEXT_ORDER, REMINDER_CHANNEL } from "../lib/reminders.ts";
60
+ import { looksLikeAnthropicRequest, MID_CONVERSATION_SYSTEM_BETA, withBetas } from "../lib/anthropic-payload.ts";
61
+ import { liftAddenda, supportsToolAdditions, TOOL_ADDITION_BETAS, withToolAdditions } from "../lib/tool-additions.ts";
62
+ import { inContextEntries } from "../lib/compaction-boundary.ts";
63
+ import { CALLABLE_TOOLS } from "../lib/ledger/fold.ts";
64
+ import { foldOnce, sameEntries } from "../lib/ledger/fold-memo.ts";
65
+ import { recordedToolNames } from "../lib/ledger/wire.ts";
66
+ import { MCP_TOOLS_CHANNEL } from "../lib/mcp-share.ts";
67
+ import { sessionIdentity } from "../lib/anthropic-auth.ts";
68
+ import type { EntryLike } from "../lib/ledger/records.ts";
69
+ import { CONTEXT_ORDER, REMINDER_CHANNEL, RENDER_CHANNEL, type RenderRequest } from "../lib/reminders.ts";
49
70
  import { sessionAlive } from "../lib/session-lifecycle.ts";
50
71
  import { ccToolRenderers } from "../lib/tui-render.ts";
51
72
  import { normalizeToolName } from "../permissions/matcher.ts";
52
- import { applyAnnouncement, planAnnouncement } from "./announce.ts";
53
73
  import { registerLocalCommand } from "../lib/local-command.ts";
54
74
 
55
- /** Coalesces the burst of per-tool defers one server emits into one listing update. */
56
- const ANNOUNCE_DEBOUNCE_MS = 100;
75
+ /** The told mode for "this epoch's message 1 lists deferred tools this run cannot load", said once per epoch. */
76
+ const UNAVAILABLE_MODE = "deferred-unavailable";
77
+
78
+ /** A session manager as far as the reconciler reads it (a test double may lack `getBranch`). */
79
+ type SessionLike = { getBranch?: () => unknown[] };
80
+ const branchOf = (session: SessionLike | undefined) => (session?.getBranch?.() ?? []) as EntryLike[];
57
81
 
58
82
  export default function toolSearchExtension(pi: ExtensionAPI) {
59
83
  // Owned here (see lib/deferred.ts): the only instance, fed over DEFER_CHANNEL.
60
84
  const deferredRegistry = new DeferredRegistry();
85
+ // Names WITHHOLD_CHANNEL removed from the registry, kept only so a direct
86
+ // call to one still gets an explanation (tool_execution_end below) instead
87
+ // of a bare "not found": the registry itself can't answer that, since
88
+ // removal is what makes the name stop being deferred (searchable/loadable).
89
+ // A re-defer (a model change bringing it back) clears the name from here.
90
+ const withheldNames = new Set<string>();
61
91
  let sessionStarted = false;
62
- // The deferred-tools listing is a first-prepend reminder on message 1, part of
63
- // the cached prefix of every later request. Before the first request nothing
64
- // is cached, so the listing is (re)written freely; after it the listing is
65
- // FROZEN and later arrivals ride a one-shot addendum where the model reads
66
- // next (./announce.ts — a rewrite re-cached 16.7k tokens of history on
67
- // request 2 of every run whose MCP tools registered inside the debounce,
68
- // measured 2026-09-04). While the initial MCP connect is still settling the
69
- // addendum waits for settle, so one burst of per-tool defers is one notice.
70
- let requestSent = false;
71
- let mcpSettled = false;
72
- let announcePending = false;
73
- let announceTimer: NodeJS.Timeout | undefined;
74
92
  // After session_shutdown every pi.* call throws (the runner is invalidated,
75
- // findings §8); a debounced announce landing in a timer callback then would
76
- // be an uncaught exception and take pi down (LIFECYCLE-REVIEW L1).
93
+ // findings §8): an event that lands then must do nothing (LIFECYCLE-REVIEW L1).
77
94
  const alive = sessionAlive(pi);
78
- /** Names the model has been told about: the frozen listing plus every addendum. */
79
- const announced = new Set<string>();
95
+ /** The session's manager, for folding what the current epoch has told and loaded outside a hook that carries one. */
96
+ let session: SessionLike | undefined;
97
+ /** The session's model, for a decision made outside a hook that carries one. */
98
+ let currentModel: { provider?: string; api?: string; id?: string; compat?: unknown } | undefined;
99
+ /**
100
+ * The branch's fold with the live identity, so a model or auth switch
101
+ * counts. Shared with the ledger (lib/ledger/fold-memo.ts): the fold this
102
+ * turn_start takes serves the request's `context` hook, and a server's
103
+ * connect defers its tools in a burst with nothing appended between.
104
+ */
105
+ const foldOf = (owner: SessionLike | undefined, branch: EntryLike[], model: typeof currentModel) => foldOnce(owner, branch, sessionIdentity(pi, model));
80
106
 
81
- const searchableTools = () =>
82
- pi
83
- .getAllTools()
107
+ /** Whether tool_search is registered; a `--tools` list without it turns deferral off (lib/deferred.ts). */
108
+ const deferring = () => deferralEnabled(pi.getAllTools());
109
+ const searchableTools = (all = pi.getAllTools()) => {
110
+ if (!deferralEnabled(all)) return [];
111
+ return all
84
112
  .filter((tool) => deferredRegistry.has(tool.name))
85
113
  .map((tool) => ({
86
114
  name: tool.name,
87
115
  description: tool.description ?? "",
88
116
  keywords: deferredRegistry.keywordsFor(tool.name),
89
117
  }));
118
+ };
90
119
 
91
- const announce = () => {
120
+ /**
121
+ * The deferred-tools listing as live state: what an epoch's message 1 says.
122
+ * Inside an epoch message 1 is the record, so a change here is announced by
123
+ * the render step below, never by rewriting it.
124
+ */
125
+ const publishListing = () => {
92
126
  if (!alive()) return;
93
127
  const available = searchableTools();
94
- const plan = planAnnouncement({ requestSent, announced, available: available.map((t) => t.name) });
95
- if (plan.kind === "rewrite") {
96
- pi.events.emit(REMINDER_CHANNEL, {
97
- scope: "every-turn",
98
- key: "deferred-tools",
99
- text: deferredReminderText(available),
100
- placement: "first-prepend",
101
- order: CONTEXT_ORDER.deferredTools,
102
- });
103
- } else if (plan.kind === "addendum") {
104
- // Unkeyed on purpose: a keyed next-turn reminder replaces its
105
- // predecessor, and an addendum replaced before delivery would lose names.
106
- pi.events.emit(REMINDER_CHANNEL, { text: deferredAddendumText(plan.added) });
128
+ if (available.length === 0) {
129
+ pi.events.emit(REMINDER_CHANNEL, { key: "deferred-tools", remove: true });
130
+ return;
107
131
  }
108
- applyAnnouncement(announced, plan);
109
- };
110
-
111
- const clearAnnounce = () => {
112
- if (announceTimer) clearTimeout(announceTimer);
113
- announceTimer = undefined;
114
- };
115
- const scheduleAnnounce = () => {
116
- clearAnnounce();
117
- if (!alive()) return;
118
- announceTimer = setTimeout(() => {
119
- announceTimer = undefined;
120
- // Decided at fire time, not schedule time: a request may have gone out
121
- // during the debounce (the -p race).
122
- if (requestSent && !mcpSettled) {
123
- announcePending = true;
124
- return;
125
- }
126
- announce();
127
- }, ANNOUNCE_DEBOUNCE_MS);
128
- announceTimer.unref?.();
132
+ pi.events.emit(REMINDER_CHANNEL, {
133
+ key: "deferred-tools",
134
+ text: deferredReminderText(available),
135
+ placement: "first-prepend",
136
+ order: CONTEXT_ORDER.deferredTools,
137
+ facts: [{ kind: "tools", names: available.map((tool) => tool.name) }],
138
+ });
129
139
  };
130
140
 
141
+ /** Deferred tools this process has seen loadable: one of them missing later is known to be gone. */
142
+ const seenHere = new Set<string>();
143
+ /** True once the MCP extension published its settled tool set: an MCP tool missing then is gone. */
144
+ let mcpSettled = false;
131
145
  pi.events.on(MCP_TOOLS_CHANNEL, (data) => {
132
- if (!(data as McpToolsPayload | undefined)?.settled) return;
133
- mcpSettled = true;
134
- if (announcePending) {
135
- announcePending = false;
136
- announce();
146
+ if ((data as { settled?: boolean } | undefined)?.settled) mcpSettled = true;
147
+ });
148
+
149
+ // Inside an epoch, a deferred tool that registered since the epoch's listing
150
+ // is announced on the request's tail: computed when the request goes out,
151
+ // from what is loadable then and what the epoch has told, so a tool withheld
152
+ // in the meantime is never announced (the H1 class). Where the model takes
153
+ // Claude Code's tool_addition, the addendum becomes one (the context and
154
+ // payload hooks below) and the tool is callable at once.
155
+ pi.events.on(RENDER_CHANNEL, (data) => {
156
+ const request = data as RenderRequest;
157
+ if (request.epoch) return;
158
+ const told = request.told.get("tools") ?? new Set<string>();
159
+ if (!deferring()) {
160
+ // The epoch's message 1 lists deferred tools this run cannot load.
161
+ if (told.size > 0 && !request.modes.has(UNAVAILABLE_MODE)) {
162
+ request.blocks.push({ text: loaderUnavailableReminderText(), facts: [{ kind: "mode", key: UNAVAILABLE_MODE, on: true }] });
163
+ }
164
+ return;
165
+ }
166
+ // A tool told and missing now is announced gone once its absence is known
167
+ // (the fold then holds it untold, so a return is announced as new): this
168
+ // process saw it leave, or it is an MCP tool and the MCP connects have
169
+ // settled (a resume sends its first request before a slow server
170
+ // registers, and a fork never had the parent's own tools). A tool
171
+ // withdrawn for the model is not gone: a call to it says so.
172
+ const loadable = new Set(searchableTools().map((tool) => tool.name));
173
+ for (const name of loadable) seenHere.add(name);
174
+ const absent = (name: string) => seenHere.has(name) || (name.startsWith("mcp__") && mcpSettled);
175
+ const removed = [...told].filter((name) => !loadable.has(name) && !withheldNames.has(name) && absent(name));
176
+ if (removed.length > 0) request.blocks.push({ text: deferredRemovedText(removed), facts: [{ kind: "tools", names: removed, removed: true }] });
177
+ const added = [...loadable].filter((name) => !told.has(name));
178
+ if (added.length === 0) return;
179
+ if (!supportsToolAdditions(request.model)) {
180
+ request.blocks.push({ text: deferredAddendumText(added), facts: [{ kind: "tools", names: added }] });
181
+ return;
137
182
  }
183
+ // Here an addendum is a tool_addition, callable at once: only a tool the
184
+ // reconciler activated before pi fixed this turn's tools (turn_start) is
185
+ // announced; one that arrived since waits for the next request.
186
+ const active = new Set(pi.getActiveTools());
187
+ const callable = added.filter((name) => active.has(name));
188
+ if (callable.length > 0) request.blocks.push({ text: deferredAddendumText(callable), facts: [{ kind: "tools", names: callable, callable: true }] });
138
189
  });
139
190
 
140
- pi.on("context", () => {
141
- requestSent = true;
191
+ /** The addenda the last `context` pass lifted off tool results, by call id (lib/tool-additions.ts). */
192
+ let addendaByCall = new Map<string, string[]>();
193
+ pi.on("context", (event, ctx) => {
194
+ addendaByCall = new Map();
195
+ if (!supportsToolAdditions(ctx?.model) || !Array.isArray(event?.messages)) return undefined;
196
+ const lifted = liftAddenda(event.messages);
197
+ addendaByCall = lifted.byCall;
198
+ return lifted.messages === event.messages ? undefined : { messages: lifted.messages };
142
199
  });
143
200
 
144
- // Every tool_search load of this session (call id → names), seeded from the
145
- // transcript at session_start and extended by each load: the wire hook needs
146
- // it to reference a loaded tool from the result that loaded it.
201
+ // Every tool_search load still in context (call id → names), derived from the
202
+ // branch by each reconcile, plus the loads this turn made that are not on the
203
+ // branch yet: the wire hook needs it to reference a loaded tool from the
204
+ // result that loaded it.
147
205
  let loads = new Map<string, string[]>();
148
- // The loads a resumed session's transcript already holds (empty after
149
- // /clear). The transcript tells the model those tools are callable, and
150
- // Claude Code keeps a ToolSearch-loaded tool callable across a resume (its
151
- // tool_reference blocks stay in the history), so deferring them again would
152
- // send the model into a "Tool <name> not found" round trip. pi restores them
153
- // as active from the transcript before session_start; deferAll and the
154
- // late-defer handler leave those alone. A load made during this session
155
- // activates the tool directly and never needs this set.
156
- let loadedBefore = new Set<string>();
157
-
158
- // On models that take client-side tool references (first-party Claude >= 4.5,
159
- // not Haiku) the `tools` array is held byte-identical to request 1's: every
160
- // deferred tool rides every Anthropic request as a `defer_loading: true`
161
- // definition, and a tool that tool_search loaded — which pi 0.86 promotes into
162
- // the eager list, re-caching everything from the tools block down — is
163
- // demoted back to deferred and loaded through `tool_reference` blocks in the
164
- // tool_search result that activated it (lib/deferred.ts stabilizeDeferredTools
165
- // has the rules; docs/decisions/caching.md the measurements).
166
- pi.on("before_provider_request", (event, ctx) => {
167
- if (!supportsToolReferences(ctx.model as { provider?: string; id?: string } | undefined)) return undefined;
168
- if (!looksLikeAnthropicRequest(event.payload)) return undefined;
169
- return stabilizeDeferredTools(event.payload as Record<string, unknown>, pi.getAllTools(), (name) => deferredRegistry.has(name), loads);
170
- });
206
+ /** Loads `tool_search` made since the last turn started: their results reach the branch when the batch ends. */
207
+ const freshLoads = new Map<string, string[]>();
171
208
 
209
+ /** What the branch's in-context history loaded and uses, for the branch it was read from. */
210
+ let used: { entries: readonly EntryLike[]; loads: ReadonlyMap<string, string[]>; inUse: ReadonlySet<string> } | undefined;
211
+ /**
212
+ * The branch's in-context loads and tools in use, read again only when the
213
+ * branch changed (lib/ledger/fold-memo.ts `sameEntries`): the reconciler runs
214
+ * at every turn, model switch, defer and withhold, and a server's connect
215
+ * defers its tools in a burst with nothing appended between. Both are pure
216
+ * functions of the entries; the registry, the model and this turn's loads
217
+ * are applied on top at each run.
218
+ */
219
+ const branchUse = (branch: EntryLike[]) => {
220
+ if (used && sameEntries(used.entries, branch)) return used;
221
+ const inContext = inContextEntries(branch) as SessionEntry[];
222
+ const loads = toolSearchLoads(inContext);
223
+ used = { entries: [...branch], loads, inUse: toolsInUse(inContext, loads) };
224
+ return used;
225
+ };
172
226
 
173
- /** Deactivate every deferred-registry tool the transcript has not loaded, and announce the loadable set. */
174
- const deferAll = () => {
227
+ /**
228
+ * The one place deferred tools are activated and deactivated (lib/deferred.ts
229
+ * callableDeferredTools): a deferred tool is active exactly when the branch
230
+ * gives a reason. Its history uses it (a load or a call in context), the
231
+ * epoch announced it callable, the recorded head the next request starts
232
+ * from declares it eagerly, or the next request announces it callable.
233
+ * Claude Code keeps a loaded tool callable across a resume (its
234
+ * tool_reference blocks stay in the history); a compaction returns to
235
+ * deferred what its kept turns no longer use, and keeps what the last
236
+ * request declared eagerly (removing it from `tools` would be the miss). A
237
+ * withheld tool is never active. Run before pi fixes a turn's tools
238
+ * (turn_start) and whenever the inputs change.
239
+ */
240
+ const reconcile = (owner: SessionLike | undefined, model: typeof currentModel) => {
241
+ if (!alive()) return;
242
+ const all = pi.getAllTools();
243
+ const registered = new Set(all.map((tool) => tool.name));
244
+ if (!deferralEnabled(all)) {
245
+ // Nothing is deferred without the loader, but a withheld tool is still
246
+ // never active: a /tree switch restores a loadout from before it left.
247
+ const active = pi.getActiveTools();
248
+ const next = reconcileActive(active, new Set(), new Set(), withheldNames, registered);
249
+ if (next !== active) pi.setActiveTools([...next]);
250
+ return;
251
+ }
252
+ const branch = branchOf(owner);
253
+ const use = branchUse(branch);
254
+ loads = new Map([...use.loads, ...freshLoads]);
175
255
  const deferred = new Set(deferredRegistry.names);
176
- if (deferred.size === 0) return;
256
+ // With nothing deferred no deferred tool is callable: the active set
257
+ // loses only withheld tools, and the branch's fold has nothing to decide.
258
+ let callable = new Set<string>();
259
+ if (deferred.size > 0) {
260
+ const fold = foldOf(owner, branch, model);
261
+ const inUse = new Set(use.inUse);
262
+ for (const names of freshLoads.values()) for (const name of names) inUse.add(name);
263
+ const last = fold.lastRequest;
264
+ const told = fold.told.get("tools") ?? new Set<string>();
265
+ const announcing = supportsToolAdditions(model) && !fold.opensEpoch ? new Set(searchableTools(all).map((tool) => tool.name).filter((name) => !told.has(name))) : new Set<string>();
266
+ callable = callableDeferredTools(deferred, {
267
+ inUse,
268
+ toldCallable: fold.told.get(CALLABLE_TOOLS) ?? new Set(),
269
+ declaredEager: last ? recordedToolNames(last, { eager: true }) : new Set(),
270
+ announcing,
271
+ });
272
+ }
177
273
  const active = pi.getActiveTools();
178
- const next = active.filter((name) => !deferred.has(name) || loadedBefore.has(name));
179
- if (next.length !== active.length) pi.setActiveTools(next);
180
- announce();
274
+ const next = reconcileActive(active, deferred, callable, withheldNames, registered);
275
+ if (next !== active) pi.setActiveTools([...next]);
181
276
  };
277
+ const reconcileNow = () => reconcile(session, currentModel);
278
+
279
+ // On models that take client-side tool references (first-party Claude >= 4.5,
280
+ // not Haiku) every deferred tool rides every Anthropic request as a
281
+ // `defer_loading: true` definition, and a tool that tool_search loaded — which
282
+ // pi promotes into the eager list — is demoted back to deferred and loaded
283
+ // through `tool_reference` blocks in the tool_search result that activated it
284
+ // (lib/deferred.ts stabilizeDeferredTools has the rules;
285
+ // working-docs/decisions/caching.md the measurements). On OpenAI Responses
286
+ // models with native tool loading (GPT-5.4 and newer on openai and
287
+ // openai-codex) a loaded tool leaves `tools` for pi-ai's own load item at the
288
+ // tool_search result instead (stabilizeResponsesToolLoads). Inside an epoch
289
+ // the request carries only its tail here: the ledger's payload hook, later,
290
+ // puts the recorded list in front and keeps the recorded tools.
291
+ pi.on("before_provider_request", (event, ctx) => {
292
+ const allTools = pi.getAllTools();
293
+ const enabled = deferralEnabled(allTools);
294
+ const toolLoadCompat = responsesToolLoadCompat(ctx.model as ResponsesToolLoadModel | undefined);
295
+ if (toolLoadCompat) {
296
+ if (!enabled) return undefined;
297
+ return stabilizeResponsesToolLoads(event.payload as Record<string, unknown>, (name) => deferredRegistry.has(name), loads, toolLoadCompat);
298
+ }
299
+ const original = event.payload as Record<string, unknown>;
300
+ const additions = supportsToolAdditions(ctx.model) && looksLikeAnthropicRequest(original);
301
+ // The context step lifted this request's addenda off their tool results;
302
+ // whatever happens below, they go out (lib/tool-additions.ts).
303
+ const announceAsText = () => {
304
+ const text = additions ? withToolAdditions(original, addendaByCall, false) : undefined;
305
+ return text ? withBetas(text.payload, [MID_CONVERSATION_SYSTEM_BETA]) : undefined;
306
+ };
307
+ if (!enabled || !supportsToolReferences(ctx.model as { provider?: string; id?: string } | undefined) || !looksLikeAnthropicRequest(original)) return announceAsText();
308
+ const isDeferred = (name: string) => deferredRegistry.has(name);
309
+ // A tool that arrived mid-session goes out as Claude Code's tool_addition
310
+ // where the model takes one, declared deferred.
311
+ const lifted = additions ? withToolAdditions(original, addendaByCall) : undefined;
312
+ const stable = stabilizeDeferredTools(lifted?.payload ?? original, allTools, isDeferred, loads, new Set(lifted?.names ?? []));
313
+ // Renamed (OAuth) tools cannot be referenced: the active tools stay as pi
314
+ // rendered them and the addition goes as text.
315
+ if (!stable) return announceAsText();
316
+ return lifted ? withBetas(stable, TOOL_ADDITION_BETAS) : stable;
317
+ });
318
+
319
+ pi.events.on(DEFERRED_QUERY_CHANNEL, (data) => {
320
+ const query = data as DeferredQuery;
321
+ for (const name of deferredRegistry.names) query.tools.push({ name, keywords: deferredRegistry.keywordsFor(name) });
322
+ });
182
323
 
183
324
  pi.events.on(DEFER_CHANNEL, (data) => {
184
325
  const request = data as DeferRequest;
185
326
  deferredRegistry.add(request);
327
+ // A withheld tool brought back is deferred again.
328
+ if (request?.name) withheldNames.delete(request.name);
186
329
  // A defer arriving after the session_start pass (MCP servers connect
187
330
  // asynchronously and register their tools then) would otherwise leave the
188
- // tool eager AND unlisted in the reminder. Deactivate it (unless the
189
- // transcript loaded it) and refresh the keyed reminder.
190
- if (sessionStarted && request?.name) {
191
- const active = pi.getActiveTools();
192
- if (active.includes(request.name) && !loadedBefore.has(request.name)) {
193
- pi.setActiveTools(active.filter((name) => name !== request.name));
194
- }
195
- // The tool is searchable (keyword search reads the registry) from now;
196
- // only the standing listing waits.
197
- scheduleAnnounce();
331
+ // tool eager AND unlisted.
332
+ if (sessionStarted && request?.name && deferring() && alive()) {
333
+ reconcileNow();
334
+ publishListing();
198
335
  }
199
336
  });
200
337
 
338
+ pi.events.on(WITHHOLD_CHANNEL, (data) => {
339
+ const name = (data as { name?: string } | undefined)?.name;
340
+ if (!name || !deferredRegistry.remove(name)) return;
341
+ withheldNames.add(name);
342
+ if (!alive()) return;
343
+ reconcileNow();
344
+ publishListing();
345
+ });
346
+
347
+ // A /tree switch keeps the process: pi restored the selected branch's
348
+ // loadout, and the selected branch decides which deferred tools are callable
349
+ // and referenced there.
350
+ pi.on("session_tree", (_event, ctx) => {
351
+ freshLoads.clear();
352
+ reconcile(ctx?.sessionManager, ctx?.model ?? currentModel);
353
+ });
354
+
201
355
  pi.on("session_start", (_event, ctx) => {
202
356
  sessionStarted = true;
203
- // A pending debounce belongs to the previous session's listing (RPC's
204
- // new_session emits session_start twice, findings §3); deferAll below
205
- // announces the fresh one.
206
- clearAnnounce();
207
- loads = toolSearchLoads(ctx?.sessionManager?.getBranch?.() ?? []);
208
- loadedBefore = new Set([...loads.values()].flat());
209
- // A new session (/clear, or a resume in a new process) has no cached prefix
210
- // yet: its first request gets a freshly written listing.
211
- requestSent = false;
212
- announced.clear();
213
- deferAll();
357
+ currentModel = ctx?.model;
358
+ session = ctx?.sessionManager;
359
+ freshLoads.clear();
360
+ reconcileNow();
361
+ publishListing();
362
+ });
363
+ pi.on("model_select", (event) => {
364
+ currentModel = event.model;
365
+ reconcileNow();
366
+ });
367
+ // Every turn's tools are fixed right after this (pi's prepareRequest): the
368
+ // tools the request announces callable must be active by then.
369
+ pi.on("turn_start", (_event, ctx) => {
370
+ freshLoads.clear();
371
+ if (ctx?.model) currentModel = ctx.model;
372
+ reconcileNow();
214
373
  });
215
374
 
216
- pi.on("session_shutdown", clearAnnounce);
375
+ // A compaction opens an epoch, which lists every deferred tool again. The
376
+ // loads that named a loaded tool went with the summary: the tool goes back to
377
+ // deferred, so the new epoch declares it the way it declares the rest (Claude
378
+ // Code declares a deferred tool only while a reference to it is in the
379
+ // history), unless the reconciler keeps it.
380
+ pi.on("session_compact", (_event, ctx) => {
381
+ reconcile(ctx?.sessionManager, ctx?.model ?? currentModel);
382
+ publishListing();
383
+ });
217
384
 
218
385
  // The every-turn deferred-tools list tells the model to load via tool_search,
219
386
  // but if it calls a deferred tool directly anyway, pi's core dispatcher fails
@@ -228,15 +395,34 @@ export default function toolSearchExtension(pi: ExtensionAPI) {
228
395
  // Map a Claude Code spelling (a direct `NotebookEdit` call) to our name so
229
396
  // the miss is recognised and the steer names the loadable tool (M1).
230
397
  const name = raw ? normalizeToolName(raw) : undefined;
231
- if (!name || !deferredRegistry.has(name)) return;
232
- pi.events.emit(REMINDER_CHANNEL, {
233
- scope: "next-turn",
234
- key: `deferred-miss-${name}`,
235
- text: deferredMissReminderText(name),
236
- });
398
+ if (!name) return;
399
+ // Without tool_search there is nothing to steer a deferred-tool miss to.
400
+ if (deferredRegistry.has(name) && deferring()) {
401
+ pi.events.emit(REMINDER_CHANNEL, {
402
+ key: `deferred-miss-${name}`,
403
+ text: deferredMissReminderText(name),
404
+ });
405
+ } else if (withheldNames.has(name)) {
406
+ // A withheld tool is gone from the registry too (WITHHOLD_CHANNEL), so
407
+ // the ordinary steer above would send the model to tool_search for a
408
+ // name that will not be found there either.
409
+ pi.events.emit(REMINDER_CHANNEL, {
410
+ key: `deferred-miss-${name}`,
411
+ text: withheldMissReminderText(name),
412
+ });
413
+ } else if (session && recordedToolNames(foldOf(session, branchOf(session), currentModel).lastRequest ?? { head: {}, list: [] }).has(name)) {
414
+ // The epoch's recorded tools stay declared after the tool left the
415
+ // session (a setting turned it off): the call cannot be salvaged, and
416
+ // the model must not keep trying it.
417
+ pi.events.emit(REMINDER_CHANNEL, {
418
+ key: `deferred-miss-${name}`,
419
+ text: unavailableToolReminderText(name),
420
+ });
421
+ }
237
422
  });
238
423
 
239
424
  pi.registerTool({
425
+ // A literal, like every registration: agent-view-tool-labels.test.ts reads them from source.
240
426
  name: "tool_search",
241
427
  label: "Tool Search",
242
428
  ...ccToolRenderers("Tool Search"),
@@ -257,14 +443,34 @@ export default function toolSearchExtension(pi: ExtensionAPI) {
257
443
  // silently dropped before — the model was told the rest "Loaded" and only
258
444
  // discovered the miss later as an opaque InputValidationError. Always
259
445
  // surface the unmatched names.
260
- const requested = selectedNames(params.query);
261
- const notFound = requested
262
- ? requested.filter((n) => !matches.some((m) => m.name.toLowerCase() === n))
446
+ const requested = selectedRequests(params.query);
447
+ // A requested tool that is already callable (an eager tool, or one loaded
448
+ // earlier) is a harmless no-op, as in Claude Code: the model proceeds
449
+ // instead of retrying a name it was told does not exist.
450
+ const active = pi.getActiveTools();
451
+ const alreadyActive = (name: string) => active.find((tool) => tool.toLowerCase() === name);
452
+ const unmatched = requested
453
+ ? requested.filter((request) => !matches.some((m) => m.name.toLowerCase() === request.name))
263
454
  : [];
455
+ const eager = [...new Set(unmatched.flatMap((request) => alreadyActive(request.name) ?? []))];
456
+ const notFound = unmatched.filter((request) => !alreadyActive(request.name));
457
+ // A withheld name can still sit in the frozen listing (withdrawn by a model
458
+ // change after the first request), so it gets "do not retry", not a
459
+ // spelling hint. Both notes quote the model's own spelling.
460
+ const withdrawn = [...new Set(notFound.filter((n) => withheldNames.has(n.name)).map((n) => n.asWritten))];
461
+ const missing = [...new Set(notFound.filter((n) => !withheldNames.has(n.name)).map((n) => n.asWritten))];
264
462
  const notFoundNote =
265
- notFound.length > 0
266
- ? ` Not found (not deferred tool names — check spelling, or search by keyword instead of \`select:\`): ${notFound.join(", ")}.`
267
- : "";
463
+ (missing.length > 0
464
+ ? ` Not found (not deferred tool names — check spelling, or search by keyword instead of \`select:\`): ${missing.join(", ")}.`
465
+ : "") +
466
+ (withdrawn.length > 0 ? ` Withdrawn for the current model (do not retry): ${withdrawn.join(", ")}.` : "");
467
+
468
+ if (matches.length === 0 && eager.length > 0) {
469
+ return {
470
+ content: [{ type: "text", text: `Already loaded: ${eager.join(", ")}.${notFoundNote}` }],
471
+ details: { matches: eager, added: [] as string[], notFound, refused: [] as string[] },
472
+ };
473
+ }
268
474
 
269
475
  if (matches.length === 0) {
270
476
  const names = available.map((t) => t.name).join(", ") || "(none)";
@@ -272,30 +478,42 @@ export default function toolSearchExtension(pi: ExtensionAPI) {
272
478
  content: [
273
479
  { type: "text", text: `No tools matched "${params.query}".${notFoundNote} Tools that can be loaded: ${names}` },
274
480
  ],
275
- details: { matches: [] as string[], added: [] as string[], notFound },
481
+ details: { matches: [] as string[], added: [] as string[], notFound, refused: [] as string[] },
276
482
  isError: true,
277
483
  };
278
484
  }
279
485
 
280
- const active = pi.getActiveTools();
281
- const added = matches.map((m) => m.name).filter((name) => !active.includes(name));
486
+ const matchNames = matches.map((m) => m.name);
487
+ const toActivate = matchNames.filter((name) => !active.includes(name));
488
+ if (toActivate.length > 0) pi.setActiveTools([...new Set([...active, ...toActivate])]);
489
+ // pi can decline to activate a tool: since 1.0.4 a `--tools` list that
490
+ // names no `mcp__*` entry keeps the MCP tools registered but not
491
+ // activatable. Report what pi actually activated, never a plausible load.
492
+ const now = new Set(pi.getActiveTools());
493
+ const added = toActivate.filter((name) => now.has(name));
494
+ const refused = toActivate.filter((name) => !now.has(name));
282
495
  if (added.length > 0) {
283
- pi.setActiveTools([...new Set([...active, ...added])]);
496
+ freshLoads.set(toolCallId, added);
284
497
  loads.set(toolCallId, added);
285
498
  }
286
499
 
287
- const loaded = matches.map((m) => m.name);
500
+ const loaded = [...new Set([...matchNames, ...eager])].filter((name) => now.has(name));
501
+ const already = loaded.filter((name) => !added.includes(name));
502
+ const refusedNote =
503
+ refused.length > 0 ? ` Could not load ${refused.join(", ")}: this session's tool list (pi's --tools) leaves them out, so they cannot be called.` : "";
288
504
  return {
289
505
  content: [
290
506
  {
291
507
  type: "text",
292
508
  text:
293
- (added.length > 0
294
- ? `Loaded ${added.join(", ")}. These tools are now callable.`
295
- : `Already loaded: ${loaded.join(", ")}.`) + notFoundNote,
509
+ ([
510
+ added.length > 0 ? `Loaded ${added.join(", ")}. These tools are now callable.` : "",
511
+ already.length > 0 ? `Already loaded: ${already.join(", ")}.` : "",
512
+ ].filter(Boolean).join(" ") + refusedNote + notFoundNote).trim(),
296
513
  },
297
514
  ],
298
- details: { matches: loaded, added, notFound },
515
+ details: { matches: loaded, added, notFound, refused },
516
+ isError: loaded.length === 0,
299
517
  };
300
518
  },
301
519
  });
@@ -303,6 +521,10 @@ export default function toolSearchExtension(pi: ExtensionAPI) {
303
521
  registerLocalCommand(pi, "tools-deferred", {
304
522
  description: "Show which tools are deferred (loadable via tool_search)",
305
523
  handler: async (args, ctx) => {
524
+ if (!deferring()) {
525
+ ctx.ui.notify("tool_search is not registered (a --tools list leaves it out), so no tool is deferred.", "info");
526
+ return;
527
+ }
306
528
  const available = searchableTools();
307
529
  const activeSet = new Set(pi.getActiveTools());
308
530
  const lines = available.map((t) => `${activeSet.has(t.name) ? "loaded " : "deferred"} ${t.name}`);