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
@@ -6,20 +6,28 @@
6
6
  * same mechanism the bash extension uses). Every model-facing field —
7
7
  * description, promptSnippet, promptGuidelines, parameters — is passed through
8
8
  * from pi's own definition byte-identical, so the system prompt and tool
9
- * schemas are unchanged; execution delegates to a per-cwd instance of pi's
9
+ * schemas are unchanged, with one exception: write carries Claude Code's
10
+ * description, short or long by the model's tier (write-description.ts,
11
+ * lib/tool-variants.ts), and is registered again when the form changes.
12
+ * Execution delegates to a per-cwd instance of pi's
10
13
  * real definition (they close over cwd, and enter_worktree moves the session
11
14
  * mid-run). Only the TUI renderers differ: a `●` call line, and the base
12
15
  * result component indented under a `⎿` elbow so upstream streaming,
13
16
  * truncation, and ctrl+o expansion behavior is preserved. Edit keeps its own
14
17
  * call component (the diff preview a permission prompt relies on) with just
15
- * the header line restyled.
18
+ * the header line restyled. Write renders as Claude Code's Write
19
+ * (write-render.ts): a preview of the content or the diff while it waits, then
20
+ * "Wrote N lines" or "Added N lines, removed M lines"; to show an overwrite's
21
+ * diff afterwards, its execution reads the old file first and records the diff
22
+ * on the result's details, which never reach the model.
16
23
  *
17
24
  * grep/find/ls stay tier-gated: registering them does not bypass the
18
25
  * search-tools extension, which reconciles the active-tool list on
19
26
  * session_start/model_select and removes them for frontier models.
20
27
  */
21
28
 
22
- import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
29
+ import { readFile, stat } from "node:fs/promises";
30
+ import type { ExtensionAPI, ExtensionToolContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
23
31
  import {
24
32
  createEditToolDefinition,
25
33
  createFindToolDefinition,
@@ -27,9 +35,14 @@ import {
27
35
  createLsToolDefinition,
28
36
  createReadToolDefinition,
29
37
  createWriteToolDefinition,
38
+ generateDiffString,
30
39
  } from "@earendil-works/pi-coding-agent";
31
40
  import { perCwd } from "../lib/per-cwd.ts";
32
- import { ccWrapBuiltinRenderers } from "../lib/tui-render.ts";
41
+ import { type DescriptionForm, registerFormTool } from "../lib/tool-variants.ts";
42
+ import { resolveToolPath } from "../lib/tool-path.ts";
43
+ import { ccWrapBuiltinRenderers, type RejectedFileOp } from "../lib/tui-render.ts";
44
+ import { editDescription, writeDescription } from "./write-description.ts";
45
+ import { createWriteRenderers, WRITE_DIFF_MAX_BYTES, type WriteRenderDeps, writeDetails } from "./write-render.ts";
33
46
 
34
47
  // The concrete definitions are each strongly typed to their own schema; this
35
48
  // extension treats them uniformly, so the spec is deliberately loose. Every
@@ -41,6 +54,12 @@ interface BuiltinSpec {
41
54
  create: (cwd: string) => BuiltinDefinition;
42
55
  title: (args: Record<string, unknown> | undefined) => string | undefined;
43
56
  keepBaseCall?: boolean;
57
+ /** A file tool: a rejection without a reason reads "User rejected <op> to <path>". */
58
+ rejectedAs?: RejectedFileOp;
59
+ /** Write's own Claude Code renderers and result details. */
60
+ write?: boolean;
61
+ /** A description in place of pi's, by the session's description form; `base` is pi's own. */
62
+ description?: (form: DescriptionForm, base: string) => string;
44
63
  }
45
64
 
46
65
  const asSpecCreate = (create: (cwd: string) => unknown): BuiltinSpec["create"] =>
@@ -48,24 +67,51 @@ const asSpecCreate = (create: (cwd: string) => unknown): BuiltinSpec["create"] =
48
67
 
49
68
  const BUILTINS: BuiltinSpec[] = [
50
69
  { label: "Read", create: asSpecCreate(createReadToolDefinition), title: (a) => a?.path as string | undefined },
51
- { label: "Write", create: asSpecCreate(createWriteToolDefinition), title: (a) => a?.path as string | undefined },
70
+ {
71
+ label: "Write",
72
+ create: asSpecCreate(createWriteToolDefinition),
73
+ title: (a) => a?.path as string | undefined,
74
+ rejectedAs: "write",
75
+ write: true,
76
+ description: writeDescription,
77
+ },
52
78
  {
53
79
  label: "Update",
54
80
  create: asSpecCreate(createEditToolDefinition),
55
81
  title: (a) => a?.path as string | undefined,
56
82
  keepBaseCall: true,
83
+ rejectedAs: "update",
84
+ description: (_form, base) => editDescription(base),
57
85
  },
58
86
  { label: "Grep", create: asSpecCreate(createGrepToolDefinition), title: (a) => a?.pattern as string | undefined },
59
87
  { label: "Find", create: asSpecCreate(createFindToolDefinition), title: (a) => a?.pattern as string | undefined },
60
88
  { label: "LS", create: asSpecCreate(createLsToolDefinition), title: (a) => (a?.path as string | undefined) ?? "." },
61
89
  ];
62
90
 
91
+ /**
92
+ * The file a write is about to replace, for its diff: undefined when there is
93
+ * none, null when it is not a readable file or too big to diff.
94
+ */
95
+ const readBefore: WriteRenderDeps["readBefore"] = async (raw, cwd) => {
96
+ const path = resolveToolPath(raw, cwd);
97
+ try {
98
+ const info = await stat(path);
99
+ if (!info.isFile() || info.size > WRITE_DIFF_MAX_BYTES) return null;
100
+ return await readFile(path, "utf8");
101
+ } catch (error) {
102
+ return (error as NodeJS.ErrnoException).code === "ENOENT" ? undefined : null;
103
+ }
104
+ };
105
+
106
+ const writeDeps: WriteRenderDeps = { readBefore, diff: (before, after) => generateDiffString(before, after).diff };
107
+
63
108
  export default function toolStyleExtension(pi: ExtensionAPI) {
64
109
  for (const spec of BUILTINS) {
65
110
  const base = spec.create(process.cwd());
66
111
  const forCwd = perCwd(spec.create);
112
+ const renderers = ccWrapBuiltinRenderers(spec.label, base, { title: spec.title, keepBaseCall: spec.keepBaseCall, rejectedAs: spec.rejectedAs });
67
113
 
68
- pi.registerTool({
114
+ const definition = {
69
115
  name: base.name,
70
116
  label: base.label,
71
117
  description: base.description,
@@ -75,10 +121,22 @@ export default function toolStyleExtension(pi: ExtensionAPI) {
75
121
  executionMode: base.executionMode,
76
122
  prepareArguments: base.prepareArguments,
77
123
  constrainedSampling: base.constrainedSampling,
78
- ...ccWrapBuiltinRenderers(spec.label, base, { title: spec.title, keepBaseCall: spec.keepBaseCall }),
79
- async execute(toolCallId, params, signal, onUpdate, ctx: ExtensionContext) {
80
- return forCwd(ctx.cwd).execute(toolCallId, params as never, signal, onUpdate, ctx);
124
+ ...(spec.write ? createWriteRenderers(writeDeps, renderers.renderResult) : renderers),
125
+ async execute(toolCallId, params, signal, onUpdate, ctx: ExtensionToolContext) {
126
+ const run = () => forCwd(ctx.cwd).execute(toolCallId, params as never, signal, onUpdate, ctx);
127
+ if (!spec.write) return run();
128
+ const { path, content } = params as { path?: unknown; content?: unknown };
129
+ if (typeof path !== "string" || typeof content !== "string") return run();
130
+ const before = await readBefore(path, ctx.cwd);
131
+ const result = await run();
132
+ return { ...result, details: writeDetails(before, content, writeDeps.diff) };
81
133
  },
82
- } as ToolDefinition);
134
+ } as ToolDefinition;
135
+ const describe = spec.description;
136
+ if (!describe) {
137
+ pi.registerTool(definition);
138
+ continue;
139
+ }
140
+ registerFormTool(pi, definition, (form) => describe(form, base.description));
83
141
  }
84
142
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The write tool's model-facing description: Claude Code's Write text (pure).
3
+ *
4
+ * pi's own write tool runs underneath (tool-style/index.ts); only the
5
+ * description is Claude Code's, short on frontier and workhorse, long on cheap
6
+ * and tiny (lib/tool-variants.ts), with One Code's tool names. "Overwriting an
7
+ * existing file you haven't read will fail" holds here: the file tracker
8
+ * refuses it (file-tracker/tracker.ts). Read and edit keep pi's descriptions,
9
+ * because Claude Code's describe a different call shape (`file_path`,
10
+ * numbered lines, `old_string`); edit's gets the read rule appended
11
+ * (`editDescription`).
12
+ */
13
+
14
+ import type { DescriptionForm } from "../lib/tool-variants.ts";
15
+
16
+ export const WRITE_SHORT_DESCRIPTION = `Writes a file to the local filesystem, overwriting if one exists.
17
+
18
+ When to use: creating a new file, or fully replacing one you've already read. Overwriting an existing file you haven't read will fail. For partial changes, use edit instead.`;
19
+
20
+ export const WRITE_LONG_DESCRIPTION = `Writes a file to the local filesystem.
21
+
22
+ Usage:
23
+ - This tool will overwrite the existing file if there is one at the provided path.
24
+ - If this is an existing file, you MUST use the read tool first to read the file's contents. This tool will fail if you did not read the file first.
25
+ - Prefer the edit tool for modifying existing files — it only sends the diff. Only use this tool to create new files or for complete rewrites.
26
+ - NEVER create documentation files (*.md) or README files unless explicitly requested by the User.
27
+ - Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.`;
28
+
29
+ export function writeDescription(form: DescriptionForm): string {
30
+ return form === "long" ? WRITE_LONG_DESCRIPTION : WRITE_SHORT_DESCRIPTION;
31
+ }
32
+
33
+ /**
34
+ * pi's edit description plus the rule the file tracker enforces, which pi's
35
+ * text does not state (write's does). Without it, GPT-6 models read with `cat`
36
+ * and met the refusal first (2026-10-04 Codex self-test: 16 refused edits).
37
+ */
38
+ export function editDescription(base: string): string {
39
+ return `${base} The file must have been read in this conversation, with the read tool or a shell command that printed the whole file as it is on disk (such as cat), or the edit is refused; if it changed on disk since, read it again first.`;
40
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * The write tool's transcript, as Claude Code shows a Write (pure: no pi
3
+ * imports; tool-style/index.ts wires it over pi's write tool).
4
+ *
5
+ * - While the call waits to run (a permission prompt sits under it), the call
6
+ * line carries a preview of what it will write: the new file's first lines,
7
+ * or the diff against the file it overwrites. The prompt's decision depends
8
+ * on it, the way edit's diff preview serves an edit prompt.
9
+ * - Once it ran: `⎿ Wrote N lines to <path>` and the file's first lines for a
10
+ * new file, `⎿ Added N lines, removed M lines` and the diff for an
11
+ * overwrite.
12
+ * - Rejected without a reason: `⎿ User rejected write to <path>` and the
13
+ * first lines it would have written.
14
+ *
15
+ * The content preview stops after WRITE_PREVIEW_LINES lines with a
16
+ * `… +N lines (ctrl+o to expand)` trailer (ctrl+o works inside the prompt
17
+ * too); a diff shows whole, as edit's does. Every line is wrapped to the
18
+ * width, never cut, so nothing in an overwide line hides past the edge.
19
+ */
20
+
21
+ import { hardWrapColumns } from "../lib/text-width.ts";
22
+ import { sanitizeDisplayText } from "../lib/terminal-text.ts";
23
+ import {
24
+ callLine,
25
+ countNoun,
26
+ displayToolPath,
27
+ EXPAND_HINT,
28
+ elbowIndent,
29
+ linesComponent,
30
+ rejectedFileHead,
31
+ spinnerBulletColor,
32
+ stripReminderBlocks,
33
+ summarizeArgs,
34
+ textContent,
35
+ type ThemeLike,
36
+ type TuiComponent,
37
+ } from "../lib/tui-render.ts";
38
+ import { userDenialKind } from "../lib/user-denial.ts";
39
+
40
+ /** Content lines shown before the `… +N lines` trailer (Claude Code's). */
41
+ export const WRITE_PREVIEW_LINES = 10;
42
+
43
+ /** An overwrite's diff is computed only when both sides are at most this big; past it the result reads as a create. */
44
+ export const WRITE_DIFF_MAX_BYTES = 512 * 1024;
45
+
46
+ /**
47
+ * What a finished write did, on its result's `details` (display-only: tool
48
+ * result details never reach the model). `diff` is pi's display diff
49
+ * (`+12 line`, `-12 line`, ` 12 line`), absent when it was too big to compute.
50
+ */
51
+ export type WriteDetails = { kind: "create" } | { kind: "update"; diff?: string };
52
+
53
+ /** Lines in the content, a trailing newline ending the last line rather than starting another (Claude Code's count). */
54
+ export function countLines(content: string): number {
55
+ const parts = content.split("\n");
56
+ return content.endsWith("\n") ? parts.length - 1 : parts.length;
57
+ }
58
+
59
+ /** The details a finished write carries: `before` is the file's old content, undefined when it did not exist, null when it was not read. */
60
+ export function writeDetails(before: string | null | undefined, content: string, diff: (before: string, after: string) => string): WriteDetails {
61
+ if (before === undefined) return { kind: "create" };
62
+ if (before === null || before.length > WRITE_DIFF_MAX_BYTES || content.length > WRITE_DIFF_MAX_BYTES) return { kind: "update" };
63
+ return { kind: "update", diff: diff(before, content) };
64
+ }
65
+
66
+ /** One source line made safe and wrapped: escapes and control characters removed, tabs as spaces. */
67
+ function wrapLine(line: string, width: number): string[] {
68
+ const safe = sanitizeDisplayText(line.replace(/\t/g, " "));
69
+ return safe === "" ? [""] : hardWrapColumns(safe, Math.max(1, width));
70
+ }
71
+
72
+ /** The content's first lines (all when expanded), wrapped to `width`, with the `… +N lines` trailer. */
73
+ export function contentPreviewLines(theme: ThemeLike, content: string, width: number, expanded: boolean): string[] {
74
+ const total = countLines(content);
75
+ const source = (content || "(No content)").split("\n");
76
+ if (content.endsWith("\n")) source.pop();
77
+ const shown = expanded ? source : source.slice(0, WRITE_PREVIEW_LINES);
78
+ const out = shown.flatMap((line) => wrapLine(line, width).map((part) => theme.fg("toolOutput", part)));
79
+ const hidden = total - shown.length;
80
+ if (!expanded && hidden > 0) out.push(theme.fg("dim", `… +${countNoun(hidden, "line")} (${EXPAND_HINT})`));
81
+ return out;
82
+ }
83
+
84
+ const DIFF_COLOR: Record<string, string> = { "+": "toolDiffAdded", "-": "toolDiffRemoved" };
85
+
86
+ /** pi's display diff, each line coloured by its sign and wrapped to `width`. */
87
+ export function diffPreviewLines(theme: ThemeLike, diff: string, width: number): string[] {
88
+ if (diff === "") return [];
89
+ return diff.split("\n").flatMap((line) => {
90
+ const color = DIFF_COLOR[line[0]] ?? "toolDiffContext";
91
+ return wrapLine(line, width).map((part) => theme.fg(color, part));
92
+ });
93
+ }
94
+
95
+ /** Added and removed lines in pi's display diff. */
96
+ export function diffCounts(diff: string): { added: number; removed: number } {
97
+ let added = 0;
98
+ let removed = 0;
99
+ for (const line of diff.split("\n")) {
100
+ if (line.startsWith("+")) added++;
101
+ else if (line.startsWith("-")) removed++;
102
+ }
103
+ return { added, removed };
104
+ }
105
+
106
+ /** Claude Code's "Added N lines, removed M lines" (either half alone when the other is zero). */
107
+ export function updateSummary(theme: ThemeLike, diff: string): string {
108
+ const { added, removed } = diffCounts(diff);
109
+ const plural = (n: number) => (n > 1 ? "lines" : "line");
110
+ const parts: string[] = [];
111
+ if (added > 0) parts.push(`Added ${theme.bold(String(added))} ${plural(added)}`);
112
+ if (removed > 0) parts.push(`${added > 0 ? "r" : "R"}emoved ${theme.bold(String(removed))} ${plural(removed)}`);
113
+ return parts.length > 0 ? parts.join(", ") : "No changes";
114
+ }
115
+
116
+ /** The waiting call's preview body, wrapped to `width`: the diff for an overwrite it could diff, the new content otherwise. */
117
+ export function writePreviewLines(theme: ThemeLike, preview: WriteDetails, content: string, width: number, expanded: boolean): string[] {
118
+ return preview.kind === "update" && preview.diff !== undefined
119
+ ? diffPreviewLines(theme, preview.diff, width)
120
+ : contentPreviewLines(theme, content, width, expanded);
121
+ }
122
+
123
+ /**
124
+ * A finished write's result body, before the `⎿` elbow is added: the summary
125
+ * line, then the content preview or the diff, wrapped to `width`. `path` is
126
+ * the display path.
127
+ */
128
+ export function writeResultLines(theme: ThemeLike, details: WriteDetails | undefined, path: string, content: string, width: number, expanded: boolean): string[] {
129
+ if (details?.kind === "update" && details.diff !== undefined) {
130
+ return [updateSummary(theme, details.diff), ...diffPreviewLines(theme, details.diff, width)];
131
+ }
132
+ const head = `Wrote ${theme.bold(String(countLines(content)))} lines to ${theme.bold(sanitizeDisplayText(path))}`;
133
+ return [head, ...contentPreviewLines(theme, content, width, expanded)];
134
+ }
135
+
136
+ /** A write rejected without a reason: "User rejected write to <path>" and the first lines it would have written. */
137
+ export function rejectedWriteLines(theme: ThemeLike, path: string, content: string, width: number, expanded: boolean): string[] {
138
+ return [rejectedFileHead(theme, "write", path), ...contentPreviewLines(theme, content, width, expanded)];
139
+ }
140
+
141
+ /** The I/O the renderers need, injected so this module stays pure. */
142
+ export interface WriteRenderDeps {
143
+ /** The file's content: undefined when it does not exist, null when it cannot or should not be read (too big, a directory). */
144
+ readBefore: (path: string, cwd: string) => Promise<string | null | undefined>;
145
+ /** pi's display diff of two contents. */
146
+ diff: (before: string, after: string) => string;
147
+ }
148
+
149
+ interface WriteArgs {
150
+ path?: unknown;
151
+ file_path?: unknown;
152
+ content?: unknown;
153
+ }
154
+
155
+ /** The waiting call's preview, kept on the row's `context.state` across renders. */
156
+ interface PreviewState {
157
+ path: string;
158
+ content: string;
159
+ preview?: WriteDetails;
160
+ lines?: { width: number; expanded: boolean; lines: string[] };
161
+ }
162
+
163
+ const argPath = (args: WriteArgs | undefined): string | undefined => {
164
+ const raw = args?.path ?? args?.file_path;
165
+ return typeof raw === "string" && raw !== "" ? raw : undefined;
166
+ };
167
+
168
+ type ResultRenderer = (result: any, options: { expanded: boolean; isPartial: boolean }, theme: any, context: any) => TuiComponent;
169
+
170
+ /**
171
+ * renderCall/renderResult for the write tool. `fallbackResult` renders what
172
+ * this view does not own (an error, a rejection with a reason): the generic
173
+ * wrapper around pi's own result component.
174
+ */
175
+ export function createWriteRenderers(
176
+ deps: WriteRenderDeps,
177
+ fallbackResult: ResultRenderer,
178
+ ): {
179
+ renderShell: "self";
180
+ renderCall: (args: WriteArgs, theme: any, context: any) => TuiComponent;
181
+ renderResult: ResultRenderer;
182
+ } {
183
+ /** The preview for these args, starting the read of the target the first time. */
184
+ const previewFor = (path: string, content: string, context: any): PreviewState => {
185
+ const state = context.state as { ccWritePreview?: PreviewState };
186
+ const current = state.ccWritePreview;
187
+ if (current && current.path === path && current.content === content) return current;
188
+ const next: PreviewState = { path, content };
189
+ state.ccWritePreview = next;
190
+ const cwd = typeof context.cwd === "string" ? context.cwd : process.cwd();
191
+ const settle = (preview: WriteDetails) => {
192
+ if (state.ccWritePreview !== next) return;
193
+ next.preview = preview;
194
+ context.invalidate?.();
195
+ };
196
+ deps.readBefore(path, cwd).then(
197
+ (before) => settle(writeDetails(before, content, deps.diff)),
198
+ () => settle({ kind: "create" }),
199
+ );
200
+ return next;
201
+ };
202
+
203
+ return {
204
+ renderShell: "self",
205
+ renderCall(args, theme: ThemeLike, context) {
206
+ const path = argPath(args);
207
+ const header = callLine(theme, "Write", path ? summarizeArgs(path) : "", context.isPartial, context.isError, spinnerBulletColor(context));
208
+ const content = args?.content;
209
+ // The preview belongs to the call that has not run yet: once its result
210
+ // is in, the result carries the content (or the diff) instead.
211
+ const waiting = context.isPartial && context.argsComplete && path !== undefined && typeof content === "string";
212
+ const preview = waiting ? previewFor(path, content, context) : undefined;
213
+ const expanded = context.expanded === true;
214
+ return linesComponent((width) => {
215
+ if (!preview?.preview) return [header];
216
+ const cached = preview.lines;
217
+ if (cached && cached.width === width && cached.expanded === expanded) return [header, ...cached.lines];
218
+ const body = writePreviewLines(theme, preview.preview, preview.content, width - 2, expanded).map((line) => ` ${line}`);
219
+ preview.lines = { width, expanded, lines: body };
220
+ return [header, ...body];
221
+ });
222
+ },
223
+ renderResult(result, options, theme: ThemeLike, context) {
224
+ const args = context.args as WriteArgs | undefined;
225
+ const path = argPath(args);
226
+ const content = args?.content;
227
+ if (path === undefined || typeof content !== "string") return fallbackResult(result, options, theme, context);
228
+ const shownPath = displayToolPath(path, context.cwd);
229
+ if (context.isError) {
230
+ if (userDenialKind(textContent(stripReminderBlocks(result))) !== "stop") return fallbackResult(result, options, theme, context);
231
+ return linesComponent((width) => elbowIndent(rejectedWriteLines(theme, shownPath, content, width - 5, options.expanded)));
232
+ }
233
+ const details = result?.details as WriteDetails | undefined;
234
+ return linesComponent((width) => elbowIndent(writeResultLines(theme, details, shownPath, content, width - 5, options.expanded)));
235
+ },
236
+ };
237
+ }
@@ -8,13 +8,15 @@
8
8
  * the model never sees its own timing line), rendered dim and led by the ✻ mark
9
9
  * to match CC's TurnDurationMessage. CC shows it after every response with no
10
10
  * threshold and defaults it on; CC_TURN_DURATION=0 opts out. The completion verb
11
- * is sampled once per turn from CC's 8-verb list.
11
+ * is sampled once per turn from CC's 8-verb list. The line ends with CC's
12
+ * `· done <time>` (the entry's timestamp, line.ts formatDoneAt) and, when
13
+ * background shells outlive the turn, `· N shells still running`.
12
14
  */
13
15
 
14
16
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
17
  import { trackShellTasks } from "../lib/shell-tasks.ts";
16
18
  import { dimMarkedLine } from "../lib/tui-render.ts";
17
- import { TURN_MARK, turnDurationText } from "./line.ts";
19
+ import { formatDoneAt, TURN_MARK, turnDurationText } from "./line.ts";
18
20
  import { TurnSpan } from "./span.ts";
19
21
  import { pickCompletionVerb } from "./verbs.ts";
20
22
 
@@ -33,7 +35,9 @@ export default function turnDurationExtension(pi: ExtensionAPI) {
33
35
  pi.registerEntryRenderer<TurnDurationData>(ENTRY_TYPE, (entry, _options, theme) => {
34
36
  const data = entry.data;
35
37
  if (!data) return undefined;
36
- return dimMarkedLine(theme, TURN_MARK, turnDurationText(data.verb, data.durationMs, data.runningShells ?? 0));
38
+ // The entry's own timestamp is when the turn settled: Claude Code's `· done 2:33 PM`.
39
+ const doneAt = formatDoneAt(new Date(entry.timestamp));
40
+ return dimMarkedLine(theme, TURN_MARK, turnDurationText(data.verb, data.durationMs, data.runningShells ?? 0, doneAt));
37
41
  });
38
42
 
39
43
  const span = new TurnSpan();
@@ -13,10 +13,53 @@ export const TURN_MARK = "✻";
13
13
 
14
14
  /**
15
15
  * `<Verb> for <duration>` — duration formatted like the spinner's elapsed
16
- * clock — plus Claude Code's background-shell tail (`· 2 shells still
17
- * running`) when shells outlive the turn.
16
+ * clock — then Claude Code's ` · done <time>` when the finish time is known,
17
+ * then its background-shell tail (`· 2 shells still running`) when shells
18
+ * outlive the turn.
18
19
  */
19
- export function turnDurationText(verb: string, durationMs: number, runningShells = 0): string {
20
+ export function turnDurationText(verb: string, durationMs: number, runningShells = 0, doneAt?: string): string {
21
+ const done = doneAt ? ` · done ${doneAt}` : "";
20
22
  const shells = runningShells > 0 ? ` · ${countNoun(runningShells, "shell")} still running` : "";
21
- return `${verb} for ${formatDuration(0, durationMs)}${shells}`;
23
+ return `${verb} for ${formatDuration(0, durationMs)}${done}${shells}`;
24
+ }
25
+
26
+ /**
27
+ * The locale Claude Code formats times in: `LC_ALL`, then `LC_TIME`, then
28
+ * `LANG`, with the encoding and modifier dropped (`en_US.UTF-8` → `en-US`);
29
+ * undefined for `C`, `POSIX`, unset or a tag Intl rejects, which leaves the
30
+ * runtime's default.
31
+ */
32
+ export function timeLocale(env: NodeJS.ProcessEnv = process.env): string | undefined {
33
+ const raw = env.LC_ALL || env.LC_TIME || env.LANG || "";
34
+ if (!raw || raw === "C" || raw === "POSIX") return undefined;
35
+ const tag = raw.split(".")[0]?.split("@")[0]?.replaceAll("_", "-");
36
+ if (!tag) return undefined;
37
+ try {
38
+ new Intl.DateTimeFormat(tag);
39
+ return tag;
40
+ } catch {
41
+ return undefined;
42
+ }
43
+ }
44
+
45
+ const DAY_MS = 86_400_000;
46
+
47
+ /** Midnight of `date`'s calendar day, in the runtime's local time zone. */
48
+ function localDay(date: Date): number {
49
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
50
+ }
51
+
52
+ /**
53
+ * Claude Code's "done" time (its `auto` time format): the time alone for a
54
+ * turn that finished today (`2:33 PM`), the weekday too within the last week
55
+ * (`Friday 2:33 PM`), and the date as well before that (`Friday, Sep 19, 2:33
56
+ * PM`), in the locale's own hour cycle. Empty for an invalid date.
57
+ */
58
+ export function formatDoneAt(at: Date, now: Date = new Date(), locale: string | undefined = timeLocale()): string {
59
+ if (Number.isNaN(at.getTime())) return "";
60
+ const days = Math.round((localDay(now) - localDay(at)) / DAY_MS);
61
+ const time: Intl.DateTimeFormatOptions = { hour: "numeric", minute: "2-digit" };
62
+ const options: Intl.DateTimeFormatOptions =
63
+ days === 0 ? time : days > 0 && days < 7 ? { weekday: "long", ...time } : { weekday: "long", month: "short", day: "numeric", ...time };
64
+ return new Intl.DateTimeFormat(locale, options).format(at);
22
65
  }
@@ -73,12 +73,12 @@ const KEYLESS_ADVICE =
73
73
  * these results are best-effort and what the user can configure instead.
74
74
  */
75
75
  export const KEYLESS_NOTE =
76
- "Note: these results came from Exa's free keyless endpoint (rate-limited, best-effort) because no search API key is configured. " +
76
+ "Note: these results came from Exa's free keyless endpoint (rate-limited, best-effort). " +
77
77
  `For reliable search the user can ${KEYLESS_ADVICE}`;
78
78
 
79
79
  /** User-facing one-time notice (TUI) the first time a session searches keyless. */
80
80
  export const KEYLESS_USER_NOTICE =
81
- "web_search used Exa's free keyless endpoint (rate-limited, best-effort): this provider has no native web search and no search API key is configured. " +
81
+ "web_search used Exa's free keyless endpoint (rate-limited, best-effort). " +
82
82
  `For reliable results ${KEYLESS_ADVICE}`;
83
83
 
84
84
  // ---------------------------------------------------------------------------
@@ -140,19 +140,16 @@ export function resolveChain(
140
140
 
141
141
  function hostOf(url: string): string | undefined {
142
142
  try {
143
- return new URL(url).hostname.toLowerCase();
143
+ return new URL(url).hostname.toLowerCase().replace(/\.+$/, "");
144
144
  } catch {
145
145
  return undefined;
146
146
  }
147
147
  }
148
148
 
149
149
  function normalizeDomain(domain: string): string {
150
- return domain
151
- .trim()
152
- .toLowerCase()
153
- .replace(/^https?:\/\//, "")
154
- .replace(/^www\./, "")
155
- .replace(/\/.*$/, "");
150
+ const value = domain.trim().toLowerCase();
151
+ const host = hostOf(/^https?:\/\//.test(value) ? value : `https://${value}`);
152
+ return (host ?? value).replace(/^www\./, "");
156
153
  }
157
154
 
158
155
  function hostMatches(host: string, domain: string): boolean {
@@ -0,0 +1,50 @@
1
+ /**
2
+ * web_search's model-facing description (pure).
3
+ *
4
+ * The short form, on frontier and workhorse models, is One Code's own: it
5
+ * names the backends that answer and how the domain filters apply to each.
6
+ * The long form, on cheap and tiny models (lib/tool-variants.ts), is the text
7
+ * Claude Code writes for its weakest models, addressed to the model rather
8
+ * than to Claude, with the backends line in place of "Web search is only
9
+ * available in the US".
10
+ */
11
+
12
+ import type { DescriptionForm } from "../lib/tool-variants.ts";
13
+
14
+ export const WEB_SEARCH_SHORT_DESCRIPTION =
15
+ "Search the web. Returns result blocks with titles and URLs.\n\n" +
16
+ "- `allowed_domains` / `blocked_domains` filter results (enforced on Brave/Tavily/Exa; with provider-native search they become `site:` operators in the query, best-effort).\n" +
17
+ '- After answering from results, end with a "Sources:" list of the URLs you used as markdown links.\n' +
18
+ "- Uses the model provider's own search when it has one; otherwise a configured search API (Brave, Tavily) or, with no key, a free rate-limited endpoint — the result names which.";
19
+
20
+ export const WEB_SEARCH_LONG_DESCRIPTION = `
21
+ - Allows you to search the web and use the results to inform responses
22
+ - Provides up-to-date information for current events and recent data
23
+ - Returns search result information formatted as search result blocks, including links as markdown hyperlinks
24
+ - Use this tool for accessing information beyond your knowledge cutoff
25
+ - Searches are performed automatically within a single API call
26
+
27
+ CRITICAL REQUIREMENT - You MUST follow this:
28
+ - After answering the user's question, you MUST include a "Sources:" section at the end of your response
29
+ - In the Sources section, list all relevant URLs from the search results as markdown hyperlinks: [Title](URL)
30
+ - This is MANDATORY - never skip including sources in your response
31
+ - Example format:
32
+
33
+ [Your answer here]
34
+
35
+ Sources:
36
+ - [Source Title 1](https://example.com/1)
37
+ - [Source Title 2](https://example.com/2)
38
+
39
+ Usage notes:
40
+ - Domain filtering is supported to include or block specific websites
41
+ - Uses the model provider's own search when it has one; otherwise a configured search API (Brave, Tavily) or, with no key, a free rate-limited endpoint — the result names which
42
+
43
+ IMPORTANT - Use the correct year in search queries:
44
+ - The current month is (provided in the conversation below). You MUST use this year when searching for recent information, documentation, or current events.
45
+ - Example: If the user asks for "latest React docs", search for "React documentation" with the current year, NOT last year
46
+ `;
47
+
48
+ export function webSearchDescription(form: DescriptionForm): string {
49
+ return form === "long" ? WEB_SEARCH_LONG_DESCRIPTION : WEB_SEARCH_SHORT_DESCRIPTION;
50
+ }