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
@@ -10,19 +10,75 @@
10
10
  * `{ name, keywords? }` while extensions are loading (before session_start).
11
11
  */
12
12
 
13
+ import { createHash } from "node:crypto";
13
14
  import type { SessionEntry } from "@earendil-works/pi-coding-agent";
14
15
  import { toolResultsOnBranch } from "./branch-restore.ts";
15
- import { parseClaudeVersion } from "./model-tier.ts";
16
+ import { parseClaudeId } from "./claude-id.ts";
17
+ import { leadingToolsItem, toolsItemFor } from "./ledger/wire.ts";
18
+ import { RESPONSES_APIS } from "./system-role.ts";
19
+ import { ADDENDUM_HEAD } from "./tool-additions.ts";
16
20
  import { normalizeToolName } from "../permissions/matcher.ts";
17
21
 
18
22
  export const DEFER_CHANNEL = "one-code:defer-tool";
19
23
 
24
+ /**
25
+ * Take a deferred tool out of this session's surface (`{ name }`): it leaves the
26
+ * deferred registry and the active set, so no request carries it and
27
+ * `tool_search` cannot load it. An extension withholds its own tool when the
28
+ * session model must not have it (the task tools on frontier models) and
29
+ * re-emits DEFER_CHANNEL to bring it back after a model change. Before the
30
+ * first request the standing listing is rewritten without it; after that the
31
+ * listing stays frozen and a load reports "not found" (tool-search/announce.ts).
32
+ */
33
+ export const WITHHOLD_CHANNEL = "one-code:withhold-tool";
34
+
20
35
  export interface DeferRequest {
21
36
  name: string;
22
37
  /** Extra search terms beyond the tool's name and description. */
23
38
  keywords?: string[];
24
39
  }
25
40
 
41
+ /**
42
+ * Ask the session's tool-search which tools it defers: emit a `DeferredQuery`
43
+ * and it adds one request per deferred tool. A subagent defers the same tools
44
+ * its parent injects into it (MCP, cron, SendMessage), whose owners run only
45
+ * in the parent.
46
+ */
47
+ export const DEFERRED_QUERY_CHANNEL = "one-code:deferred-query";
48
+
49
+ export interface DeferredQuery {
50
+ tools: DeferRequest[];
51
+ }
52
+
53
+ /** The tools this session defers, as its tool-search answers DEFERRED_QUERY_CHANNEL (none without one). */
54
+ export function queryDeferred(events: { emit(channel: string, data: unknown): void }): DeferRequest[] {
55
+ const query: DeferredQuery = { tools: [] };
56
+ events.emit(DEFERRED_QUERY_CHANNEL, query);
57
+ return query.tools;
58
+ }
59
+
60
+ /**
61
+ * The tools a child gets from its parent rather than its own extensions: the
62
+ * shared MCP tools (`mcp__*`, which no child extension owns) and `extra`, the
63
+ * ones the caller injects by name (the spawn and cron tools).
64
+ */
65
+ export function injectedByParent(extra: readonly { name: string }[]): (name: string) => boolean {
66
+ const names = new Set(extra.map((tool) => tool.name));
67
+ return (name) => name.startsWith("mcp__") || names.has(name);
68
+ }
69
+
70
+ /**
71
+ * An inline child extension that defers, at load, the parent's deferral of each
72
+ * tool `injected` names, whose owners run only in the parent. A tool the
73
+ * child's own extensions own stays theirs to defer or withhold per the child's
74
+ * model.
75
+ */
76
+ export function deferLikeParentExtension(parentDeferred: readonly DeferRequest[], injected: (name: string) => boolean) {
77
+ return (pi: { events: { emit(channel: string, data: unknown): void } }) => {
78
+ for (const request of parentDeferred) if (injected(request.name)) pi.events.emit(DEFER_CHANNEL, request);
79
+ };
80
+ }
81
+
26
82
  export interface SearchableTool {
27
83
  name: string;
28
84
  description: string;
@@ -49,6 +105,10 @@ export class DeferredRegistry {
49
105
  has(name: string): boolean {
50
106
  return this.entries.has(name);
51
107
  }
108
+
109
+ remove(name: string): boolean {
110
+ return this.entries.delete(name);
111
+ }
52
112
  }
53
113
 
54
114
  // The one registry instance is constructed by its owner (tool-search/index.ts):
@@ -80,25 +140,50 @@ export function deferredReminderText(tools: Array<Pick<SearchableTool, "name"> &
80
140
  * this rides the tail instead, so the cached prefix holds.
81
141
  */
82
142
  export function deferredAddendumText(added: readonly string[]): string {
83
- return [
84
- "Additional deferred tools became available via tool_search since this conversation started (they registered after the first request). Same rules: load with tool_search \"select:<name>\" before calling.",
85
- ...added,
86
- ].join("\n");
143
+ return [ADDENDUM_HEAD, ...added].join("\n");
144
+ }
145
+
146
+ /**
147
+ * The one-shot that says deferred tools the conversation was told about are
148
+ * gone (their MCP server disconnected or was removed, a resume without it).
149
+ * The earlier listing or addendum stays in the history as sent, and so may
150
+ * the tool's declaration, so without this the model calls it and gets "Tool
151
+ * not found".
152
+ */
153
+ export function deferredRemovedText(removed: readonly string[]): string {
154
+ return ["The following deferred tools are no longer available. tool_search cannot load them and calls to them fail; do not use them:", ...removed].join("\n");
87
155
  }
88
156
 
157
+ /** The loader every deferred tool sits behind. */
158
+ export const TOOL_SEARCH = "tool_search";
159
+
89
160
  /**
90
- * Whether Anthropic accepts client-side `tool_reference` blocks for this model —
91
- * pi-ai's `defaultSupportsToolReferences` rule, with the model's own compat flag
92
- * winning: first-party Anthropic, not Haiku, Claude 4.5 or newer.
161
+ * Whether deferral applies at all: `tool_search` is among pi's registered
162
+ * tools. A `--tools` allowlist that leaves it out (or `--exclude-tools
163
+ * tool_search`) unregisters it, and since pi 1.0.4 the allowlist keeps the
164
+ * `mcp__*` tools registered unless an entry names one (findings §66). Without
165
+ * the loader nothing can be loaded, so no tool is deferred or listed: the
166
+ * tools the allowlist kept stay as pi activated them.
167
+ */
168
+ export function deferralEnabled(tools: readonly { name: string }[]): boolean {
169
+ return tools.some((tool) => tool.name === TOOL_SEARCH);
170
+ }
171
+
172
+ /**
173
+ * Whether Anthropic accepts client-side `tool_reference` blocks for this model,
174
+ * with the model's own compat flag winning: first-party Anthropic, Claude 4.5
175
+ * or newer, Haiku 4.5 and later Haikus included (they take references with no
176
+ * beta, and an eager append there rewrites the whole cache: findings §55 P4
177
+ * for Haiku 4.5, §67 for Haiku 5.5). Older Haikus stay out.
93
178
  */
94
179
  export function supportsToolReferences(
95
180
  model: { provider?: string; id?: string; compat?: { supportsToolReferences?: boolean } } | undefined,
96
181
  ): boolean {
97
182
  if (!model) return false;
98
183
  if (typeof model.compat?.supportsToolReferences === "boolean") return model.compat.supportsToolReferences;
99
- if (model.provider !== "anthropic" || !model.id || model.id.includes("haiku")) return false;
100
- const version = parseClaudeVersion(model.id);
101
- return version !== undefined && (version.major > 4 || (version.major === 4 && version.minor >= 5));
184
+ if (model.provider !== "anthropic" || !model.id) return false;
185
+ const version = parseClaudeId(model.id);
186
+ return version !== undefined && (version.major > 4 || (version.major === 4 && (version.minor ?? 0) >= 5));
102
187
  }
103
188
 
104
189
  /** The slice of pi's ToolInfo the deferred definitions need. */
@@ -127,7 +212,74 @@ export function toolSearchLoads(branch: readonly SessionEntry[]): Map<string, st
127
212
  }
128
213
  return loads;
129
214
  }
130
- const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set(["tool_search"]);
215
+ const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set([TOOL_SEARCH]);
216
+
217
+ /**
218
+ * The tools `entries` use: loaded by a `tool_search` result in them (`loads`,
219
+ * their `toolSearchLoads`), or called by an assistant message in them. A call
220
+ * the dispatcher failed as "Tool <name> not found" does not count: the tool
221
+ * was not loaded, and the miss steers the model to `tool_search` to load it.
222
+ */
223
+ export function toolsInUse(entries: readonly SessionEntry[], loads: ToolSearchLoads = toolSearchLoads(entries)): Set<string> {
224
+ const inUse = new Set([...loads.values()].flat());
225
+ const missed = new Set<string>();
226
+ for (const entry of entries) {
227
+ const message = entry.type === "message" ? (entry.message as { role?: string; toolCallId?: unknown; isError?: unknown }) : undefined;
228
+ if (message?.role === "toolResult" && message.isError === true && typeof message.toolCallId === "string" && toolNotFoundName(resultText(message))) missed.add(message.toolCallId);
229
+ }
230
+ for (const entry of entries) {
231
+ const message = entry.type === "message" ? (entry.message as { role?: string; content?: unknown }) : undefined;
232
+ if (message?.role !== "assistant" || !Array.isArray(message.content)) continue;
233
+ for (const block of message.content as Array<{ type?: string; id?: unknown; name?: unknown }>) {
234
+ if (block?.type === "toolCall" && typeof block.name === "string" && !(typeof block.id === "string" && missed.has(block.id))) inUse.add(block.name);
235
+ }
236
+ }
237
+ return inUse;
238
+ }
239
+
240
+ /** Why a deferred tool is callable without a fresh `tool_search` load. */
241
+ export interface CallableReasons {
242
+ /** Loaded by a `tool_search` result in context, or called in context: its history names it. */
243
+ inUse: ReadonlySet<string>;
244
+ /** Announced callable by a `tool_addition` in the current epoch (the ledger's told facts). */
245
+ toldCallable: ReadonlySet<string>;
246
+ /** Declared eagerly by the recorded head the next request starts from: it stays declared, so it stays callable. */
247
+ declaredEager: ReadonlySet<string>;
248
+ /** Announced callable by the next request itself: loadable and untold inside an epoch, on a model that takes `tool_addition`. */
249
+ announcing: ReadonlySet<string>;
250
+ }
251
+
252
+ /**
253
+ * The deferred tools that are callable now, derived from the branch, the model
254
+ * and the registry (lifecycle-grid-hunt design.md section 6): every other
255
+ * deferred tool is inactive, whoever activated it. One function decides, so a
256
+ * resume, `/reload`, `/tree`, a compaction, a model switch and a late defer
257
+ * cannot disagree with what the wire declares and what the model was told.
258
+ */
259
+ export function callableDeferredTools(deferred: Iterable<string>, reasons: CallableReasons): Set<string> {
260
+ const callable = new Set<string>();
261
+ for (const name of deferred) {
262
+ if (reasons.inUse.has(name) || reasons.toldCallable.has(name) || reasons.declaredEager.has(name) || reasons.announcing.has(name)) callable.add(name);
263
+ }
264
+ return callable;
265
+ }
266
+
267
+ /**
268
+ * pi's active set with exactly the `callable` deferred tools active (those
269
+ * pi has registered) and no `withheld` tool; every other tool as it was. The
270
+ * input itself when nothing changes.
271
+ */
272
+ export function reconcileActive(
273
+ active: readonly string[],
274
+ deferred: ReadonlySet<string>,
275
+ callable: ReadonlySet<string>,
276
+ withheld: ReadonlySet<string>,
277
+ registered: ReadonlySet<string>,
278
+ ): readonly string[] {
279
+ const kept = active.filter((name) => !withheld.has(name) && (!deferred.has(name) || callable.has(name)));
280
+ const added = [...callable].filter((name) => registered.has(name) && !withheld.has(name) && !kept.includes(name));
281
+ return kept.length === active.length && added.length === 0 ? active : [...kept, ...added];
282
+ }
131
283
 
132
284
  type WireTool = Record<string, unknown> & { name: string };
133
285
  type WireBlock = Record<string, unknown> & { type?: unknown };
@@ -157,7 +309,7 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
157
309
  * An Anthropic request whose `tools` array is byte-identical to request 1's,
158
310
  * whatever `tool_search` has loaded since — the client side of Anthropic's
159
311
  * deferred-tool contract, which pi 0.86 no longer supplies for One Code
160
- * (rationale and measurements: docs/decisions/caching.md "Loaded deferred
312
+ * (rationale and measurements: working-docs/decisions/caching.md "Loaded deferred
161
313
  * tools stay deferred on the wire", findings §7).
162
314
  *
163
315
  * - Every registry tool `isDeferred` that is not eager on the wire is appended
@@ -171,6 +323,9 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
171
323
  * ordinary result content). Demotion happens only when that result is on the
172
324
  * wire and precedes every `tool_use` of the tool; otherwise the tool stays
173
325
  * eager (one accepted cache miss, never a request Anthropic rejects).
326
+ * - A tool the request adds mid-session by `tool_addition` (`additions`,
327
+ * lib/tool-additions.ts) is demoted the same way: its addition block
328
+ * references it, so it needs no `tool_search` result.
174
329
  * - pi's own deferred entries that are not registry tools (its
175
330
  * `__pi_deferred_placeholder__`) stay in place.
176
331
  *
@@ -184,6 +339,7 @@ export function stabilizeDeferredTools(
184
339
  tools: readonly DeferrableToolInfo[],
185
340
  isDeferred: (name: string) => boolean,
186
341
  loads: ToolSearchLoads = new Map(),
342
+ additions: ReadonlySet<string> = new Set(),
187
343
  ): Record<string, unknown> | undefined {
188
344
  const existing = payload.tools;
189
345
  if (!Array.isArray(existing) || existing.length === 0) return undefined;
@@ -220,16 +376,7 @@ export function stabilizeDeferredTools(
220
376
  }
221
377
  }
222
378
  });
223
- // The earliest on-wire load of each tool.
224
- const loaderOf = new Map<string, { callId: string; at: number }>();
225
- for (const [callId, names] of loads) {
226
- const at = resultAt.get(callId);
227
- if (at === undefined) continue;
228
- for (const name of names) {
229
- const current = loaderOf.get(name);
230
- if (current === undefined || at < current.at) loaderOf.set(name, { callId, at });
231
- }
232
- }
379
+ const loaderOf = earliestLoads(loads, (callId) => resultAt.get(callId));
233
380
  for (const tool of eager) {
234
381
  if (!isDeferred(tool.name)) continue;
235
382
  const loader = loaderOf.get(tool.name);
@@ -239,8 +386,15 @@ export function stabilizeDeferredTools(
239
386
  demoted.add(tool.name);
240
387
  referencesAt.set(loader.callId, [...(referencesAt.get(loader.callId) ?? []), tool.name]);
241
388
  }
389
+ // References in the order the query named them, as Claude Code lists them.
390
+ for (const [callId, names] of referencesAt) {
391
+ const order = loads.get(callId) ?? [];
392
+ referencesAt.set(callId, [...names].sort((a, b) => order.indexOf(a) - order.indexOf(b)));
393
+ }
242
394
  }
243
395
 
396
+ for (const tool of eager) if (additions.has(tool.name) && isDeferred(tool.name)) demoted.add(tool.name);
397
+
244
398
  // pi puts the tools breakpoint on the last eager tool; if that one is demoted
245
399
  // the breakpoint moves to the new last one, otherwise nothing moves.
246
400
  const last = eager.at(-1);
@@ -285,7 +439,7 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
285
439
  if (cache_control !== undefined) movedBreakpoint = cache_control;
286
440
  blocks.push({ ...rest, content: names.map((name) => ({ type: "tool_reference", tool_name: name })) });
287
441
  if (typeof original === "string") {
288
- if (original.trim().length > 0) siblings.push({ type: "text", text: original });
442
+ if (original.trim().length > 0) siblings.push({ type: "text", text: withClaudeCodeLoadText(original) });
289
443
  } else if (Array.isArray(original)) {
290
444
  siblings.push(...(original as WireBlock[]));
291
445
  }
@@ -293,6 +447,148 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
293
447
  return { ...message, content: withBreakpointOnLast([...blocks, ...siblings], movedBreakpoint) };
294
448
  }
295
449
 
450
+ /** `tool_search`'s plain success sentence, which the references stand in for. */
451
+ const LOADED_SENTENCE = /^Loaded [^\n]*\. These tools are now callable\.(?=\n|$)/;
452
+
453
+ /**
454
+ * The text beside the references: Claude Code's "Tool loaded." in place of the
455
+ * plain success sentence, which names what the references already carry. A
456
+ * sentence with a not-found note, and anything after it, stays as it was.
457
+ */
458
+ function withClaudeCodeLoadText(text: string): string {
459
+ return text.replace(LOADED_SENTENCE, "Tool loaded.");
460
+ }
461
+
462
+ /**
463
+ * The earliest on-wire load of each tool: the `tool_search` call whose result
464
+ * sits first in the conversation (`resultAt` is its index, undefined when it is
465
+ * not on the wire).
466
+ */
467
+ function earliestLoads(loads: ToolSearchLoads, resultAt: (callId: string) => number | undefined): Map<string, { callId: string; at: number }> {
468
+ const loaderOf = new Map<string, { callId: string; at: number }>();
469
+ for (const [callId, names] of loads) {
470
+ const at = resultAt(callId);
471
+ if (at === undefined) continue;
472
+ for (const name of names) {
473
+ const current = loaderOf.get(name);
474
+ if (current === undefined || at < current.at) loaderOf.set(name, { callId, at });
475
+ }
476
+ }
477
+ return loaderOf;
478
+ }
479
+
480
+
481
+ /** How a Responses model takes a mid-conversation tool load (pi-ai's model `compat` fields). */
482
+ export interface ToolLoadCompat {
483
+ supportsAdditionalTools?: boolean;
484
+ supportsToolSearch?: boolean;
485
+ }
486
+
487
+ /** The model fields that say whether, and how, a Responses request can add a tool mid-conversation. */
488
+ export interface ResponsesToolLoadModel {
489
+ api?: string;
490
+ compat?: ToolLoadCompat;
491
+ }
492
+
493
+ /**
494
+ * The model's tool-load compat when it takes a mid-conversation tool load in
495
+ * pi-ai's native Responses shape, else undefined.
496
+ */
497
+ export function responsesToolLoadCompat(model: ResponsesToolLoadModel | undefined): ToolLoadCompat | undefined {
498
+ if (!model?.api || !RESPONSES_APIS.has(model.api)) return undefined;
499
+ const compat = model.compat;
500
+ return compat?.supportsAdditionalTools === true || compat?.supportsToolSearch === true ? compat : undefined;
501
+ }
502
+
503
+ type WireItem = Record<string, unknown>;
504
+
505
+ /**
506
+ * A Responses request whose starting tools stay request 1's whatever
507
+ * `tool_search` has loaded since: the OpenAI side of
508
+ * {@link stabilizeDeferredTools}. pi 0.86 and later put a loaded tool into
509
+ * `tools` for One Code (its forced system prompt collapses tool changes into
510
+ * the head, findings §7), so every load re-cached the whole conversation (0
511
+ * cached tokens on the request after a load on `openai-codex`, findings §45).
512
+ *
513
+ * The starting tools are a top-level `tools` array (pi 1.0 to 1.3, and
514
+ * models pi does not flag) or, from pi 1.4.0, an `additional_tools` item at
515
+ * the head of the input (findings §78); this reads which from the payload.
516
+ * A loaded registry tool leaves that list and enters the input right before the
517
+ * `function_call_output` of the `tool_search` call that loaded it, in pi-ai's
518
+ * own native shape (`convertResponsesMessages` `appendSystemToolAdditions`):
519
+ * an `additional_tools` developer item when the request already declares in
520
+ * items or the model supports one, else a `tool_search_call` +
521
+ * `tool_search_output` pair whose tools carry `defer_loading: true`. A tool
522
+ * the model called before that output, or whose loading output is not on the
523
+ * wire, stays where pi declared it.
524
+ *
525
+ * Returns undefined (leave the payload alone) when nothing moves. Never
526
+ * mutates its input.
527
+ */
528
+ export function stabilizeResponsesToolLoads(
529
+ payload: Record<string, unknown>,
530
+ isDeferred: (name: string) => boolean,
531
+ loads: ToolSearchLoads,
532
+ compat: ToolLoadCompat,
533
+ ): Record<string, unknown> | undefined {
534
+ const input = payload.input;
535
+ if (!Array.isArray(input) || loads.size === 0) return undefined;
536
+ // Where the request declares its starting tools: a top-level array (pi 1.0
537
+ // to 1.3, and models pi does not flag), or an `additional_tools` item at the
538
+ // head of the input (pi 1.4.0 and later, findings §78). Read from the bytes.
539
+ const headItem = leadingToolsItem(payload.tools, input);
540
+ const tools = headItem ? headItem.tools : payload.tools;
541
+ if (!Array.isArray(tools)) return undefined;
542
+
543
+ // Input index of each tool output and of each tool's first call. pi sends a
544
+ // call id as `<call_id>|<item_id>`; the wire's `call_id` is the first part.
545
+ const outputAt = new Map<string, number>();
546
+ const firstUse = new Map<string, number>();
547
+ (input as WireItem[]).forEach((item, index) => {
548
+ if (item?.type === "function_call_output" && typeof item.call_id === "string") outputAt.set(item.call_id, index);
549
+ else if ((item?.type === "function_call" || item?.type === "custom_tool_call") && typeof item.name === "string" && !firstUse.has(item.name)) {
550
+ firstUse.set(item.name, index);
551
+ }
552
+ });
553
+ const loaderOf = earliestLoads(loads, (callId) => outputAt.get(callId.split("|")[0]));
554
+
555
+ const movedAt = new Map<number, WireItem[]>();
556
+ const kept: unknown[] = [];
557
+ for (const raw of tools) {
558
+ const tool = raw as WireItem | null;
559
+ const name = typeof tool?.name === "string" ? tool.name : undefined;
560
+ const at = name !== undefined && isDeferred(name) ? loaderOf.get(name)?.at : undefined;
561
+ const use = name !== undefined ? firstUse.get(name) : undefined;
562
+ if (at === undefined || (use !== undefined && use < at)) {
563
+ kept.push(raw);
564
+ continue;
565
+ }
566
+ movedAt.set(at, [...(movedAt.get(at) ?? []), tool as WireItem]);
567
+ }
568
+ if (movedAt.size === 0) return undefined;
569
+
570
+ // A request that declares in items takes another item, shaped like its
571
+ // head; the top-level shape takes what the model's compat supports.
572
+ const nextInput: unknown[] = [];
573
+ (input as WireItem[]).forEach((item, index) => {
574
+ const moved = movedAt.get(index);
575
+ if (moved) nextInput.push(...(headItem ? [toolsItemFor(moved, headItem)] : toolLoadItems(moved, String(item.call_id), compat)));
576
+ nextInput.push(headItem && index === 0 ? { ...headItem, tools: kept } : item);
577
+ });
578
+ return headItem ? { ...payload, input: nextInput } : { ...payload, tools: kept, input: nextInput };
579
+ }
580
+
581
+ /** pi-ai's native items for tools added at one point of a Responses conversation. */
582
+ function toolLoadItems(tools: WireItem[], loaderCallId: string, compat: ToolLoadCompat): WireItem[] {
583
+ if (compat.supportsAdditionalTools) return [toolsItemFor(tools)];
584
+ const names = tools.map((tool) => String(tool.name));
585
+ const callId = `pi_tool_load_${createHash("sha256").update(`${loaderCallId}:${names.join(",")}`).digest("hex").slice(0, 16)}`;
586
+ return [
587
+ { type: "tool_search_call", call_id: callId, execution: "client", status: "completed", arguments: { query: names.join(" "), limit: names.length } },
588
+ { type: "tool_search_output", call_id: callId, execution: "client", status: "completed", tools: tools.map((tool) => ({ ...tool, defer_loading: true })) },
589
+ ];
590
+ }
591
+
296
592
  /**
297
593
  * The bare error pi's core dispatcher returns when the model calls a tool whose
298
594
  * schema isn't in the active set — including every deferred tool before
@@ -326,6 +622,35 @@ export function deferredMissReminderText(name: string): string {
326
622
  ].join(" ");
327
623
  }
328
624
 
625
+ /**
626
+ * The notice for a resumed session whose first message still lists deferred
627
+ * tools (that message keeps its bytes) while this run's tool list leaves
628
+ * tool_search out (`deferralEnabled`): nothing can load them now.
629
+ */
630
+ export function loaderUnavailableReminderText(): string {
631
+ return "tool_search is not available in this session, so it cannot load the deferred tools listed earlier in this conversation. Use only the tools defined for this session.";
632
+ }
633
+
634
+ /**
635
+ * The correction for a tool WITHHOLD_CHANNEL took away (the task tools on a
636
+ * frontier or Sonnet 5+ model): unlike an ordinary deferred miss, `tool_search`
637
+ * cannot load it — the name is gone from the registry, not merely inactive —
638
+ * so the steer must NOT repeat `deferredMissReminderText`'s "load it with
639
+ * tool_search" advice, which would just fail again.
640
+ */
641
+ export function withheldMissReminderText(name: string): string {
642
+ return `\`${name}\` failed with "Tool ${name} not found" because it was withdrawn for the current model — it is not a deferred tool you can load with tool_search. Do not retry it; continue without it.`;
643
+ }
644
+
645
+ /**
646
+ * A call to a tool the conversation still declares (the epoch's recorded
647
+ * tools stay declared, decisions/caching.md "record once") that this session
648
+ * no longer runs: a setting turned it off, or an extension stopped offering it.
649
+ */
650
+ export function unavailableToolReminderText(name: string): string {
651
+ return `\`${name}\` failed with "Tool ${name} not found" because it is no longer available in this session, although it is still declared. Do not retry it; continue without it.`;
652
+ }
653
+
329
654
  export interface SearchMatch {
330
655
  name: string;
331
656
  score: number;
@@ -340,6 +665,15 @@ export interface SearchMatch {
340
665
  * requested names matched nothing instead of silently dropping them.
341
666
  */
342
667
  export function selectedNames(query: string): string[] | undefined {
668
+ return selectedRequests(query)?.map((request) => request.name);
669
+ }
670
+
671
+ /**
672
+ * {@link selectedNames} with each name as the model wrote it, so a "not found"
673
+ * note quotes the model's spelling: `select:find,grep,glob` maps `glob` to
674
+ * `find`, and quoting the mapped names read "find, grep, find".
675
+ */
676
+ export function selectedRequests(query: string): { name: string; asWritten: string }[] | undefined {
343
677
  const trimmed = query.trim();
344
678
  if (!trimmed.toLowerCase().startsWith("select:")) return undefined;
345
679
  return trimmed
@@ -350,7 +684,7 @@ export function selectedNames(query: string): string[] | undefined {
350
684
  // normalizeToolName lowercases ordinary names but PRESERVES case for
351
685
  // `mcp__…` names; the trailing toLowerCase keeps select: case-insensitive
352
686
  // for those too. Don't drop it as "redundant".
353
- .map((n) => normalizeToolName(n).toLowerCase());
687
+ .map((asWritten) => ({ name: normalizeToolName(asWritten).toLowerCase(), asWritten }));
354
688
  }
355
689
 
356
690
  /**
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Rows each extension paints below the editor (the "dock": the subagent strip
3
+ * and shells chip, the workflow status rows), shared over `pi.events` (pure:
4
+ * no pi imports). A panel that takes the editor's place sizes itself to what
5
+ * the dock leaves: pi cuts a component taller than its share from the bottom,
6
+ * which took the viewer's key-hint row when only its own strip was counted.
7
+ *
8
+ * Extensions are isolated (jiti), so each one keeps its own `DockRows` fed from
9
+ * the channel; a source reports through `dockRowsReporter`, which emits only
10
+ * on a change.
11
+ */
12
+
13
+ export const DOCK_ROWS_CHANNEL = "one-code:dock-rows";
14
+
15
+ export interface DockRowsEvent {
16
+ /** The reporting strip, e.g. "subagents" or "workflow". */
17
+ source: string;
18
+ /** Rows it paints now; 0 when it shows nothing. */
19
+ rows: number;
20
+ }
21
+
22
+ interface Emitter {
23
+ emit(channel: string, data: unknown): void;
24
+ }
25
+
26
+ /** A source's reporter: call it with the rows painted on each render; it emits only when the count changes. */
27
+ export function dockRowsReporter(events: Emitter, source: string): (rows: number) => void {
28
+ let last = 0;
29
+ return (rows) => {
30
+ if (rows === last) return;
31
+ last = rows;
32
+ events.emit(DOCK_ROWS_CHANNEL, { source, rows } satisfies DockRowsEvent);
33
+ };
34
+ }
35
+
36
+ /** A `DockRows` fed from `events`, for an extension that sizes a view to the dock. */
37
+ export function trackDockRows(events: { on(channel: string, handler: (data: unknown) => void): unknown }): DockRows {
38
+ const dock = new DockRows();
39
+ events.on(DOCK_ROWS_CHANNEL, (data) => dock.note(data));
40
+ return dock;
41
+ }
42
+
43
+ /** The dock's rows as reported, by source. */
44
+ export class DockRows {
45
+ private readonly rows = new Map<string, number>();
46
+
47
+ note(data: unknown): void {
48
+ const event = data as Partial<DockRowsEvent> | undefined;
49
+ if (typeof event?.source !== "string" || typeof event.rows !== "number") return;
50
+ if (event.rows > 0) this.rows.set(event.source, event.rows);
51
+ else this.rows.delete(event.source);
52
+ }
53
+
54
+ /** Rows every source other than `self` paints. */
55
+ others(self: string): number {
56
+ let total = 0;
57
+ for (const [source, rows] of this.rows) if (source !== self) total += rows;
58
+ return total;
59
+ }
60
+ }
@@ -0,0 +1,27 @@
1
+ /** Claude Code's empty tool_result guard and marker (toolResultStorage). */
2
+ import { ccToolName } from "../hooks/matcher.ts";
3
+
4
+ export function emptyToolResultText(toolName: string): string {
5
+ return `(${ccToolName(toolName)} completed with no output)`;
6
+ }
7
+
8
+ /** Images and other non-text blocks are results, even without accompanying text. */
9
+ function isEmptyToolResultContent(content: unknown): boolean {
10
+ if (!content) return true;
11
+ if (typeof content === "string") return content.trim() === "";
12
+ if (!Array.isArray(content)) return false;
13
+ return content.every((block) => block !== null && typeof block === "object" && block.type === "text" &&
14
+ "text" in block && (typeof block.text !== "string" || block.text.trim() === ""));
15
+ }
16
+
17
+ /** Replacement content, or undefined to retain the tool's own result verbatim. */
18
+ export function emptyToolResultContent(content: unknown, toolName: string): Array<{ type: "text"; text: string }> | undefined {
19
+ const only = Array.isArray(content) && content.length === 1 && content[0]?.type === "text" && typeof content[0].text === "string" ? content[0] : undefined;
20
+ // pi Read returns raw file bytes, not Claude Code's numbered lines. Leave
21
+ // blank selections alone; file-tracker supplies the genuine empty-file warning.
22
+ if (toolName === "read" && only) return undefined;
23
+ if (isEmptyToolResultContent(content)) return [{ type: "text", text: emptyToolResultText(toolName) }];
24
+ // pi's find is Claude Code's Glob; Grep already uses Claude Code's no-match text.
25
+ if (toolName === "find" && only?.text === "No files found matching pattern") return [{ ...only, text: "No files found" }];
26
+ return undefined;
27
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Claude Code's environment block and model line (pure): the first two parts of
3
+ * its mid-conversation system message, and the first two blocks of the first
4
+ * user message on a model without one (lib/system-role.ts). They left the
5
+ * system prompt, which keeps only what never varies per session.
6
+ */
7
+
8
+ import { parseClaudeId } from "./model-tier.ts";
9
+
10
+ export interface EnvironmentFacts {
11
+ cwd: string;
12
+ isGitRepo: boolean;
13
+ platform: string;
14
+ shell: string;
15
+ osVersion: string;
16
+ /** The session's private scratchpad, when one could be made (lib/scratchpad.ts). */
17
+ scratchpadDir?: string;
18
+ /** The workspace directories as the session started; listed only when there are some. */
19
+ workspaceDirs?: string[];
20
+ }
21
+
22
+ /** Claude Code's `# Environment` block, byte for byte; the trailing space after "environment:" is Claude Code's. */
23
+ export function environmentBlock(env: EnvironmentFacts): string {
24
+ const lines = [
25
+ "# Environment",
26
+ "You have been invoked in the following environment: ",
27
+ ` - Primary working directory: ${env.cwd}`,
28
+ ` - Is a git repository: ${env.isGitRepo}`,
29
+ ];
30
+ if (env.workspaceDirs?.length) lines.push(" - Additional working directories:", ...env.workspaceDirs.map((dir) => ` - ${dir}`));
31
+ lines.push(` - Platform: ${env.platform}`, ` - Shell: ${env.shell}`, ` - OS Version: ${env.osVersion}`);
32
+ if (env.scratchpadDir) {
33
+ lines.push(
34
+ ` - Scratchpad directory: ${env.scratchpadDir} — always use it for temporary files (intermediate results, scripts, outputs that don't belong in the project) instead of \`/tmp\` or other system temp directories; it is session-specific, isolated from the project, and can generally be used without permission prompts. Only use \`/tmp\` if the user explicitly asks.`,
35
+ );
36
+ }
37
+ return lines.join("\n");
38
+ }
39
+
40
+ /** Knowledge cutoffs Claude Code states, keyed by family and version. */
41
+ const CLAUDE_CUTOFFS: Record<string, string> = {
42
+ "opus 4.5": "May 2025",
43
+ "opus 4.6": "May 2025",
44
+ "opus 4.7": "January 2026",
45
+ "opus 4.8": "January 2026",
46
+ "opus 5": "May 2026",
47
+ "opus 5.5": "June 2026",
48
+ "sonnet 4.5": "January 2025",
49
+ "sonnet 4.6": "August 2025",
50
+ "sonnet 5": "January 2026",
51
+ "sonnet 5.5": "June 2026",
52
+ "haiku 4.5": "February 2025",
53
+ "haiku 5.5": "June 2026",
54
+ "fable 5.1": "June 2026",
55
+ };
56
+
57
+ /** A Claude model's name as Claude Code says it ("Opus 5.5", "Haiku 4.5"), from an id in any provider's spelling. */
58
+ export function claudeDisplayName(id: string): string | undefined {
59
+ const claude = parseClaudeId(id);
60
+ if (!claude) return undefined;
61
+ const family = claude.family[0].toUpperCase() + claude.family.slice(1);
62
+ return claude.minor === undefined ? `${family} ${claude.major}` : `${family} ${claude.major}.${claude.minor}`;
63
+ }
64
+
65
+ /**
66
+ * Claude Code's model sentence. A Claude model gets its name and, where
67
+ * Claude Code states one, its knowledge cutoff; any other model gets the
68
+ * catalog's name and no cutoff clause, since the cutoff is a per-model fact.
69
+ */
70
+ export function modelLine(model: { id: string; name?: string }): string {
71
+ const claude = claudeDisplayName(model.id);
72
+ const name = claude ?? (model.name || model.id);
73
+ const cutoff = claude ? CLAUDE_CUTOFFS[claude.toLowerCase()] : undefined;
74
+ const base = `You are powered by the model named ${name}. The exact model ID is ${model.id}.`;
75
+ return cutoff ? `${base} Assistant knowledge cutoff is ${cutoff}.` : base;
76
+ }