one-code-extension 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +16 -5
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +58 -31
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +123 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +52 -0
  63. package/extensions/background/wakeup.ts +159 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +88 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +18 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -10,13 +10,27 @@
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 { RESPONSES_APIS } from "./system-role.ts";
18
+ import { ADDENDUM_HEAD } from "./tool-additions.ts";
16
19
  import { normalizeToolName } from "../permissions/matcher.ts";
17
20
 
18
21
  export const DEFER_CHANNEL = "one-code:defer-tool";
19
22
 
23
+ /**
24
+ * Take a deferred tool out of this session's surface (`{ name }`): it leaves the
25
+ * deferred registry and the active set, so no request carries it and
26
+ * `tool_search` cannot load it. An extension withholds its own tool when the
27
+ * session model must not have it (the task tools on frontier models) and
28
+ * re-emits DEFER_CHANNEL to bring it back after a model change. Before the
29
+ * first request the standing listing is rewritten without it; after that the
30
+ * listing stays frozen and a load reports "not found" (tool-search/announce.ts).
31
+ */
32
+ export const WITHHOLD_CHANNEL = "one-code:withhold-tool";
33
+
20
34
  export interface DeferRequest {
21
35
  name: string;
22
36
  /** Extra search terms beyond the tool's name and description. */
@@ -49,6 +63,10 @@ export class DeferredRegistry {
49
63
  has(name: string): boolean {
50
64
  return this.entries.has(name);
51
65
  }
66
+
67
+ remove(name: string): boolean {
68
+ return this.entries.delete(name);
69
+ }
52
70
  }
53
71
 
54
72
  // The one registry instance is constructed by its owner (tool-search/index.ts):
@@ -80,25 +98,39 @@ export function deferredReminderText(tools: Array<Pick<SearchableTool, "name"> &
80
98
  * this rides the tail instead, so the cached prefix holds.
81
99
  */
82
100
  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");
101
+ return [ADDENDUM_HEAD, ...added].join("\n");
87
102
  }
88
103
 
104
+ /** The loader every deferred tool sits behind. */
105
+ export const TOOL_SEARCH = "tool_search";
106
+
89
107
  /**
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.
108
+ * Whether deferral applies at all: `tool_search` is among pi's registered
109
+ * tools. A `--tools` allowlist that leaves it out (or `--exclude-tools
110
+ * tool_search`) unregisters it, and since pi 1.0.4 the allowlist keeps the
111
+ * `mcp__*` tools registered unless an entry names one (findings §66). Without
112
+ * the loader nothing can be loaded, so no tool is deferred or listed: the
113
+ * tools the allowlist kept stay as pi activated them.
114
+ */
115
+ export function deferralEnabled(tools: readonly { name: string }[]): boolean {
116
+ return tools.some((tool) => tool.name === TOOL_SEARCH);
117
+ }
118
+
119
+ /**
120
+ * Whether Anthropic accepts client-side `tool_reference` blocks for this model,
121
+ * with the model's own compat flag winning: first-party Anthropic, Claude 4.5
122
+ * or newer, Haiku 4.5 and later Haikus included (they take references with no
123
+ * beta, and an eager append there rewrites the whole cache: findings §55 P4
124
+ * for Haiku 4.5, §67 for Haiku 5.5). Older Haikus stay out.
93
125
  */
94
126
  export function supportsToolReferences(
95
127
  model: { provider?: string; id?: string; compat?: { supportsToolReferences?: boolean } } | undefined,
96
128
  ): boolean {
97
129
  if (!model) return false;
98
130
  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));
131
+ if (model.provider !== "anthropic" || !model.id) return false;
132
+ const version = parseClaudeId(model.id);
133
+ return version !== undefined && (version.major > 4 || (version.major === 4 && (version.minor ?? 0) >= 5));
102
134
  }
103
135
 
104
136
  /** The slice of pi's ToolInfo the deferred definitions need. */
@@ -127,7 +159,7 @@ export function toolSearchLoads(branch: readonly SessionEntry[]): Map<string, st
127
159
  }
128
160
  return loads;
129
161
  }
130
- const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set(["tool_search"]);
162
+ const TOOL_SEARCH_NAMES: ReadonlySet<string> = new Set([TOOL_SEARCH]);
131
163
 
132
164
  type WireTool = Record<string, unknown> & { name: string };
133
165
  type WireBlock = Record<string, unknown> & { type?: unknown };
@@ -157,7 +189,7 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
157
189
  * An Anthropic request whose `tools` array is byte-identical to request 1's,
158
190
  * whatever `tool_search` has loaded since — the client side of Anthropic's
159
191
  * deferred-tool contract, which pi 0.86 no longer supplies for One Code
160
- * (rationale and measurements: docs/decisions/caching.md "Loaded deferred
192
+ * (rationale and measurements: working-docs/decisions/caching.md "Loaded deferred
161
193
  * tools stay deferred on the wire", findings §7).
162
194
  *
163
195
  * - Every registry tool `isDeferred` that is not eager on the wire is appended
@@ -171,6 +203,9 @@ function withBreakpointOnLast<T extends Record<string, unknown>>(items: T[], cac
171
203
  * ordinary result content). Demotion happens only when that result is on the
172
204
  * wire and precedes every `tool_use` of the tool; otherwise the tool stays
173
205
  * eager (one accepted cache miss, never a request Anthropic rejects).
206
+ * - A tool the request adds mid-session by `tool_addition` (`additions`,
207
+ * lib/tool-additions.ts) is demoted the same way: its addition block
208
+ * references it, so it needs no `tool_search` result.
174
209
  * - pi's own deferred entries that are not registry tools (its
175
210
  * `__pi_deferred_placeholder__`) stay in place.
176
211
  *
@@ -184,6 +219,7 @@ export function stabilizeDeferredTools(
184
219
  tools: readonly DeferrableToolInfo[],
185
220
  isDeferred: (name: string) => boolean,
186
221
  loads: ToolSearchLoads = new Map(),
222
+ additions: ReadonlySet<string> = new Set(),
187
223
  ): Record<string, unknown> | undefined {
188
224
  const existing = payload.tools;
189
225
  if (!Array.isArray(existing) || existing.length === 0) return undefined;
@@ -220,16 +256,7 @@ export function stabilizeDeferredTools(
220
256
  }
221
257
  }
222
258
  });
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
- }
259
+ const loaderOf = earliestLoads(loads, (callId) => resultAt.get(callId));
233
260
  for (const tool of eager) {
234
261
  if (!isDeferred(tool.name)) continue;
235
262
  const loader = loaderOf.get(tool.name);
@@ -239,8 +266,15 @@ export function stabilizeDeferredTools(
239
266
  demoted.add(tool.name);
240
267
  referencesAt.set(loader.callId, [...(referencesAt.get(loader.callId) ?? []), tool.name]);
241
268
  }
269
+ // References in the order the query named them, as Claude Code lists them.
270
+ for (const [callId, names] of referencesAt) {
271
+ const order = loads.get(callId) ?? [];
272
+ referencesAt.set(callId, [...names].sort((a, b) => order.indexOf(a) - order.indexOf(b)));
273
+ }
242
274
  }
243
275
 
276
+ for (const tool of eager) if (additions.has(tool.name) && isDeferred(tool.name)) demoted.add(tool.name);
277
+
244
278
  // pi puts the tools breakpoint on the last eager tool; if that one is demoted
245
279
  // the breakpoint moves to the new last one, otherwise nothing moves.
246
280
  const last = eager.at(-1);
@@ -285,7 +319,7 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
285
319
  if (cache_control !== undefined) movedBreakpoint = cache_control;
286
320
  blocks.push({ ...rest, content: names.map((name) => ({ type: "tool_reference", tool_name: name })) });
287
321
  if (typeof original === "string") {
288
- if (original.trim().length > 0) siblings.push({ type: "text", text: original });
322
+ if (original.trim().length > 0) siblings.push({ type: "text", text: withClaudeCodeLoadText(original) });
289
323
  } else if (Array.isArray(original)) {
290
324
  siblings.push(...(original as WireBlock[]));
291
325
  }
@@ -293,6 +327,137 @@ function withToolReferences(message: WireMessage, referencesAt: ReadonlyMap<stri
293
327
  return { ...message, content: withBreakpointOnLast([...blocks, ...siblings], movedBreakpoint) };
294
328
  }
295
329
 
330
+ /** `tool_search`'s plain success sentence, which the references stand in for. */
331
+ const LOADED_SENTENCE = /^Loaded [^\n]*\. These tools are now callable\.(?=\n|$)/;
332
+
333
+ /**
334
+ * The text beside the references: Claude Code's "Tool loaded." in place of the
335
+ * plain success sentence, which names what the references already carry. A
336
+ * sentence with a not-found note, and anything after it, stays as it was.
337
+ */
338
+ function withClaudeCodeLoadText(text: string): string {
339
+ return text.replace(LOADED_SENTENCE, "Tool loaded.");
340
+ }
341
+
342
+ /**
343
+ * The earliest on-wire load of each tool: the `tool_search` call whose result
344
+ * sits first in the conversation (`resultAt` is its index, undefined when it is
345
+ * not on the wire).
346
+ */
347
+ function earliestLoads(loads: ToolSearchLoads, resultAt: (callId: string) => number | undefined): Map<string, { callId: string; at: number }> {
348
+ const loaderOf = new Map<string, { callId: string; at: number }>();
349
+ for (const [callId, names] of loads) {
350
+ const at = resultAt(callId);
351
+ if (at === undefined) continue;
352
+ for (const name of names) {
353
+ const current = loaderOf.get(name);
354
+ if (current === undefined || at < current.at) loaderOf.set(name, { callId, at });
355
+ }
356
+ }
357
+ return loaderOf;
358
+ }
359
+
360
+
361
+ /** How a Responses model takes a mid-conversation tool load (pi-ai's model `compat` fields). */
362
+ export interface ToolLoadCompat {
363
+ supportsAdditionalTools?: boolean;
364
+ supportsToolSearch?: boolean;
365
+ }
366
+
367
+ /** The model fields that say whether, and how, a Responses request can add a tool mid-conversation. */
368
+ export interface ResponsesToolLoadModel {
369
+ api?: string;
370
+ compat?: ToolLoadCompat;
371
+ }
372
+
373
+ /**
374
+ * The model's tool-load compat when it takes a mid-conversation tool load in
375
+ * pi-ai's native Responses shape, else undefined.
376
+ */
377
+ export function responsesToolLoadCompat(model: ResponsesToolLoadModel | undefined): ToolLoadCompat | undefined {
378
+ if (!model?.api || !RESPONSES_APIS.has(model.api)) return undefined;
379
+ const compat = model.compat;
380
+ return compat?.supportsAdditionalTools === true || compat?.supportsToolSearch === true ? compat : undefined;
381
+ }
382
+
383
+ type WireItem = Record<string, unknown>;
384
+
385
+ /**
386
+ * A Responses request whose `tools` array stays request 1's whatever
387
+ * `tool_search` has loaded since: the OpenAI side of
388
+ * {@link stabilizeDeferredTools}. pi 0.86 and later put a loaded tool into
389
+ * `tools` for One Code (its forced system prompt collapses tool changes into
390
+ * the head, findings §7), so every load re-cached the whole conversation (0
391
+ * cached tokens on the request after a load on `openai-codex`, findings §45).
392
+ *
393
+ * A loaded registry tool leaves `tools` and enters the input right before the
394
+ * `function_call_output` of the `tool_search` call that loaded it, in pi-ai's
395
+ * own native shape (`convertResponsesMessages` `appendSystemToolAdditions`):
396
+ * an `additional_tools` developer item when the model supports one, else a
397
+ * `tool_search_call` + `tool_search_output` pair whose tools carry
398
+ * `defer_loading: true`. A tool the model called before that output, or whose
399
+ * loading output is not on the wire, stays in `tools`.
400
+ *
401
+ * Returns undefined (leave the payload alone) when nothing moves. Never
402
+ * mutates its input.
403
+ */
404
+ export function stabilizeResponsesToolLoads(
405
+ payload: Record<string, unknown>,
406
+ isDeferred: (name: string) => boolean,
407
+ loads: ToolSearchLoads,
408
+ compat: ToolLoadCompat,
409
+ ): Record<string, unknown> | undefined {
410
+ const tools = payload.tools;
411
+ const input = payload.input;
412
+ if (!Array.isArray(tools) || !Array.isArray(input) || loads.size === 0) return undefined;
413
+
414
+ // Input index of each tool output and of each tool's first call. pi sends a
415
+ // call id as `<call_id>|<item_id>`; the wire's `call_id` is the first part.
416
+ const outputAt = new Map<string, number>();
417
+ const firstUse = new Map<string, number>();
418
+ (input as WireItem[]).forEach((item, index) => {
419
+ if (item?.type === "function_call_output" && typeof item.call_id === "string") outputAt.set(item.call_id, index);
420
+ else if ((item?.type === "function_call" || item?.type === "custom_tool_call") && typeof item.name === "string" && !firstUse.has(item.name)) {
421
+ firstUse.set(item.name, index);
422
+ }
423
+ });
424
+ const loaderOf = earliestLoads(loads, (callId) => outputAt.get(callId.split("|")[0]));
425
+
426
+ const movedAt = new Map<number, WireItem[]>();
427
+ const kept: unknown[] = [];
428
+ for (const raw of tools) {
429
+ const tool = raw as WireItem | null;
430
+ const name = typeof tool?.name === "string" ? tool.name : undefined;
431
+ const at = name !== undefined && isDeferred(name) ? loaderOf.get(name)?.at : undefined;
432
+ const use = name !== undefined ? firstUse.get(name) : undefined;
433
+ if (at === undefined || (use !== undefined && use < at)) {
434
+ kept.push(raw);
435
+ continue;
436
+ }
437
+ movedAt.set(at, [...(movedAt.get(at) ?? []), tool as WireItem]);
438
+ }
439
+ if (movedAt.size === 0) return undefined;
440
+
441
+ const nextInput: unknown[] = [];
442
+ (input as WireItem[]).forEach((item, index) => {
443
+ const moved = movedAt.get(index);
444
+ if (moved) nextInput.push(...toolLoadItems(moved, String(item.call_id), compat));
445
+ nextInput.push(item);
446
+ });
447
+ return { ...payload, tools: kept, input: nextInput };
448
+ }
449
+
450
+ /** pi-ai's native items for tools added at one point of a Responses conversation. */
451
+ function toolLoadItems(tools: WireItem[], loaderCallId: string, compat: ToolLoadCompat): WireItem[] {
452
+ if (compat.supportsAdditionalTools) return [{ type: "additional_tools", role: "developer", tools }];
453
+ const names = tools.map((tool) => String(tool.name));
454
+ const callId = `pi_tool_load_${createHash("sha256").update(`${loaderCallId}:${names.join(",")}`).digest("hex").slice(0, 16)}`;
455
+ return [
456
+ { type: "tool_search_call", call_id: callId, execution: "client", status: "completed", arguments: { query: names.join(" "), limit: names.length } },
457
+ { type: "tool_search_output", call_id: callId, execution: "client", status: "completed", tools: tools.map((tool) => ({ ...tool, defer_loading: true })) },
458
+ ];
459
+ }
460
+
296
461
  /**
297
462
  * The bare error pi's core dispatcher returns when the model calls a tool whose
298
463
  * schema isn't in the active set — including every deferred tool before
@@ -326,6 +491,26 @@ export function deferredMissReminderText(name: string): string {
326
491
  ].join(" ");
327
492
  }
328
493
 
494
+ /**
495
+ * The notice for a resumed session whose first message still lists deferred
496
+ * tools (that message keeps its bytes) while this run's tool list leaves
497
+ * tool_search out (`deferralEnabled`): nothing can load them now.
498
+ */
499
+ export function loaderUnavailableReminderText(): string {
500
+ 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.";
501
+ }
502
+
503
+ /**
504
+ * The correction for a tool WITHHOLD_CHANNEL took away (the task tools on a
505
+ * frontier or Sonnet 5+ model): unlike an ordinary deferred miss, `tool_search`
506
+ * cannot load it — the name is gone from the registry, not merely inactive —
507
+ * so the steer must NOT repeat `deferredMissReminderText`'s "load it with
508
+ * tool_search" advice, which would just fail again.
509
+ */
510
+ export function withheldMissReminderText(name: string): string {
511
+ 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.`;
512
+ }
513
+
329
514
  export interface SearchMatch {
330
515
  name: string;
331
516
  score: number;
@@ -340,6 +525,15 @@ export interface SearchMatch {
340
525
  * requested names matched nothing instead of silently dropping them.
341
526
  */
342
527
  export function selectedNames(query: string): string[] | undefined {
528
+ return selectedRequests(query)?.map((request) => request.name);
529
+ }
530
+
531
+ /**
532
+ * {@link selectedNames} with each name as the model wrote it, so a "not found"
533
+ * note quotes the model's spelling: `select:find,grep,glob` maps `glob` to
534
+ * `find`, and quoting the mapped names read "find, grep, find".
535
+ */
536
+ export function selectedRequests(query: string): { name: string; asWritten: string }[] | undefined {
343
537
  const trimmed = query.trim();
344
538
  if (!trimmed.toLowerCase().startsWith("select:")) return undefined;
345
539
  return trimmed
@@ -350,7 +544,7 @@ export function selectedNames(query: string): string[] | undefined {
350
544
  // normalizeToolName lowercases ordinary names but PRESERVES case for
351
545
  // `mcp__…` names; the trailing toLowerCase keeps select: case-insensitive
352
546
  // for those too. Don't drop it as "redundant".
353
- .map((n) => normalizeToolName(n).toLowerCase());
547
+ .map((asWritten) => ({ name: normalizeToolName(asWritten).toLowerCase(), asWritten }));
354
548
  }
355
549
 
356
550
  /**
@@ -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
+ }
@@ -1,14 +1,32 @@
1
1
  /**
2
- * fetch with a hard timeout (pure, no pi imports) — the AbortController +
3
- * unref'd timer boilerplate the plugin-marketplace fetches share.
2
+ * fetch and read the response under one hard deadline (pure, no pi imports):
3
+ * the timer runs until `read` settles, so a body that stalls after the headers
4
+ * is cut off too, whatever `fetchImpl` does with the signal. `signal` aborts
5
+ * early (a session shutting down). Shared by the plugin-marketplace fetches and
6
+ * the model-catalog refresh; `fetchImpl` is the test seam, given the same signal.
4
7
  */
5
-
6
- export async function fetchWithTimeout(url: string, timeoutMs: number, init?: RequestInit): Promise<Response> {
8
+ export async function fetchWithTimeout<T>(
9
+ url: string,
10
+ timeoutMs: number,
11
+ read: (response: Response) => Promise<T>,
12
+ options: { init?: RequestInit; signal?: AbortSignal; fetchImpl?: typeof fetch } = {},
13
+ ): Promise<T> {
7
14
  const controller = new AbortController();
8
- const timer = setTimeout(() => controller.abort(), timeoutMs);
15
+ // Parsed up front: a malformed URL fails the call, never the timer callback.
16
+ const host = new URL(url).host;
17
+ const timer = setTimeout(() => controller.abort(new Error(`${host} did not answer within ${timeoutMs} ms`)), timeoutMs);
9
18
  timer.unref?.();
19
+ const signal = options.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal;
20
+ const aborted = new Promise<never>((_, reject) => {
21
+ const fail = () => reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason ?? "aborted")));
22
+ if (signal.aborted) fail();
23
+ else signal.addEventListener("abort", fail, { once: true });
24
+ });
10
25
  try {
11
- return await fetch(url, { ...init, signal: controller.signal });
26
+ const work = (async () => read(await (options.fetchImpl ?? fetch)(url, { ...options.init, signal })))();
27
+ // Lost to the deadline, the work may still reject later: not unhandled.
28
+ work.catch(() => {});
29
+ return await Promise.race([work, aborted]);
12
30
  } finally {
13
31
  clearTimeout(timer);
14
32
  }