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
@@ -15,8 +15,11 @@
15
15
  * every line goes through `truncateLine` — never skip it.
16
16
  */
17
17
 
18
+ import { isAbsolute, relative } from "node:path";
18
19
  import { framesToText, notificationBody, parseNotificationFrames } from "./notifications.ts";
19
20
  import { fitPainted, hardWrapColumns, sliceColumns, visibleWidth } from "./text-width.ts";
21
+ import { sanitizeDisplayText } from "./terminal-text.ts";
22
+ import { userDenialKind } from "./user-denial.ts";
20
23
  export { notificationBody };
21
24
  export { visibleWidth } from "./text-width.ts";
22
25
 
@@ -148,6 +151,41 @@ export function cutPlainText(text: string, width: number): string {
148
151
  return `${sliceColumns(text, Math.max(0, width - 1)).text}…`;
149
152
  }
150
153
 
154
+ /**
155
+ * Fit a key-hint row (`a · b · c`) into `width` columns by breaking it at its
156
+ * ` · ` separators onto as many lines as it needs, so a narrow terminal shows
157
+ * every key instead of cutting the last ones off. A single hint wider than
158
+ * the row is cut with an ellipsis.
159
+ */
160
+ export function wrapHints(text: string, width: number, separator = " · "): string[] {
161
+ if (width <= 0) return [""];
162
+ if (visibleWidth(text) <= width) return [text];
163
+ const lines: string[] = [];
164
+ let current = "";
165
+ for (const hint of text.split(separator)) {
166
+ const joined = current ? `${current}${separator}${hint}` : hint;
167
+ if (!current || visibleWidth(joined) <= width) {
168
+ current = joined;
169
+ continue;
170
+ }
171
+ lines.push(cutPlainText(current, width));
172
+ current = hint;
173
+ }
174
+ if (current) lines.push(cutPlainText(current, width));
175
+ return lines;
176
+ }
177
+
178
+ /**
179
+ * A panel's key-hint rows: each hint line wrapped at its separators to fit
180
+ * `width` after `indent`, so no key is cut off on a narrow terminal, and
181
+ * returned indented (plain; the caller paints). A list panel counts the rows
182
+ * in its body budget.
183
+ */
184
+ export function hintLines(hints: string | readonly string[], width: number, indent = ""): string[] {
185
+ const room = Math.max(1, width - visibleWidth(indent));
186
+ return (typeof hints === "string" ? [hints] : hints).flatMap((line) => wrapHints(line, room).map((part) => `${indent}${part}`));
187
+ }
188
+
151
189
  /** Cut then pad plain (unpainted) text to exactly `width` terminal columns. */
152
190
  export function padPlainText(text: string, width: number): string {
153
191
  const shortened = cutPlainText(text, width);
@@ -172,17 +210,19 @@ export function panelTopRule(paint: (color: string, text: string) => string, wid
172
210
  * A rounded, bordered single-line search input, Claude Code style. Returns three
173
211
  * lines (top border, the input row, bottom border), already indented one column.
174
212
  * The placeholder shows dim until the user types. `paint` colors the border with
175
- * the "border" token; the query text stays in the default foreground.
213
+ * the "border" token; the query text stays in the default foreground. `icon`
214
+ * leads the text (a plain text input passes ""); the box keeps its width.
176
215
  */
177
216
  export function searchBoxLines(
178
217
  query: string,
179
218
  placeholder: string,
180
219
  paint: (color: string, text: string) => string,
181
220
  width: number,
221
+ icon = "⌕ ",
182
222
  ): string[] {
183
223
  const boxWidth = Math.max(12, width - 2); // one column of margin each side
184
224
  const inner = boxWidth - 2; // space between the vertical borders
185
- const label = query ? `⌕ ${query}` : `⌕ ${placeholder}`;
225
+ const label = `${icon}${query || placeholder}`;
186
226
  const cut = cutPlainText(label, inner - 2); // one space padding each side
187
227
  const content = ` ${cut}${" ".repeat(Math.max(0, inner - 2 - visibleWidth(cut)))} `;
188
228
  const v = paint("border", "│");
@@ -251,6 +291,50 @@ export function wrapPlainText(text: string, width: number): string[] {
251
291
  return out;
252
292
  }
253
293
 
294
+ /**
295
+ * Word-aware wrap for prose (task text, assistant text, the dim transcript lines, a plan): breaks at spaces,
296
+ * hard-breaking only words longer than the width; preserves blank lines. A
297
+ * line's leading indent stays and its continuation lines hang under it (a
298
+ * nested list, an indented code line), unless the indent takes half the width.
299
+ * Plain text in, plain lines out — painting happens after, per line.
300
+ */
301
+ export function wrapProse(text: string, width: number): string[] {
302
+ const columns = Math.max(1, width);
303
+ const out: string[] = [];
304
+ for (const raw of text.replace(/\r\n/g, "\n").split("\n")) {
305
+ if (raw.trim() === "") {
306
+ out.push("");
307
+ continue;
308
+ }
309
+ const lead = /^[ \t]*/.exec(raw)![0];
310
+ const indent = lead.replace(/\t/g, " ");
311
+ const hang = visibleWidth(indent) * 2 < columns ? indent : "";
312
+ const room = columns - visibleWidth(hang);
313
+ const push = (part: string) => out.push(`${hang}${part}`);
314
+ let line = "";
315
+ for (const word of raw.slice(lead.length).split(" ")) {
316
+ if (visibleWidth(word) > room) {
317
+ // A word wider than the screen (path, hash): flush, then hard-break it.
318
+ // The last chunk becomes the running line so later words can join it.
319
+ if (line) push(line);
320
+ const chunks = hardWrapColumns(word, room);
321
+ line = chunks.pop() ?? "";
322
+ for (const chunk of chunks) push(chunk);
323
+ continue;
324
+ }
325
+ const candidate = line ? `${line} ${word}` : word;
326
+ if (visibleWidth(candidate) > room) {
327
+ push(line);
328
+ line = word;
329
+ } else {
330
+ line = candidate;
331
+ }
332
+ }
333
+ push(line);
334
+ }
335
+ return out;
336
+ }
337
+
254
338
  /**
255
339
  * Cut a painted line to `width` terminal columns without splitting ANSI escape
256
340
  * sequences or a wide/emoji grapheme, ending with an ellipsis and a reset so
@@ -296,13 +380,18 @@ export function linesComponent(build: (width: number) => string[]): TuiComponent
296
380
  }
297
381
 
298
382
  /**
299
- * A single dim transcript line led by a dim mark ("✻ Cooked for 5m 12s",
383
+ * A dim transcript line led by a dim mark ("✻ Cooked for 5m 12s",
300
384
  * "※ recap: …") — the shared shape behind the turn-duration and recap
301
- * display-only entries. Width-memoized via linesComponent.
385
+ * display-only entries. Text wider than the terminal word-wraps under itself
386
+ * (a hanging indent past the mark) instead of being cut at the edge.
387
+ * Width-memoized via linesComponent.
302
388
  */
303
389
  export function dimMarkedLine(theme: unknown, mark: string, text: string): TuiComponent {
304
390
  const paint = safeThemePaint(theme);
305
- return linesComponent(() => [`${paint("dim", mark)} ${paint("dim", text)}`]);
391
+ return linesComponent((width) => {
392
+ const indent = visibleWidth(mark) + 1;
393
+ return wrapProse(text, width - indent).map((line, index) => `${index === 0 ? paint("dim", mark) : " ".repeat(indent - 1)} ${paint("dim", line)}`);
394
+ });
306
395
  }
307
396
 
308
397
  /**
@@ -329,9 +418,9 @@ const PRIMARY_ARG_KEYS = [
329
418
  "task",
330
419
  ] as const;
331
420
 
332
- /** Collapse whitespace/newlines so a summary always fits on one line. */
421
+ /** Collapse whitespace/newlines so a summary always fits on one line (control characters removed). */
333
422
  function oneLine(text: string, max = 96): string {
334
- const flat = text.replace(/\s+/g, " ").trim();
423
+ const flat = sanitizeDisplayText(text).replace(/\s+/g, " ").trim();
335
424
  return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
336
425
  }
337
426
 
@@ -445,16 +534,20 @@ export function callLine(
445
534
  bulletColorOverride?: string,
446
535
  ): string {
447
536
  const bullet = theme.fg(bulletColorOverride ?? bulletColor(isPartial, isError), "●");
448
- const name = theme.bold(label);
449
- return summary ? `${bullet} ${name}(${theme.fg("muted", summary)})` : `${bullet} ${name}`;
537
+ // A label can carry an MCP server's or tool's own name, a summary the model's arguments.
538
+ const name = theme.bold(sanitizeDisplayText(label));
539
+ const shown = sanitizeDisplayText(summary);
540
+ return shown ? `${bullet} ${name}(${theme.fg("muted", shown)})` : `${bullet} ${name}`;
450
541
  }
451
542
 
452
543
  /**
453
544
  * ` ⎿ line…` — the result lines. First line carries the elbow, the rest are
454
545
  * aligned under it; a `… +N lines` trailer advertises ctrl+o when collapsed.
546
+ * The text is a tool's output (a shell, an MCP server, a web page), so its
547
+ * escape sequences and control characters are removed before it is painted.
455
548
  */
456
549
  export function resultLines(theme: ThemeLike, text: string, expanded: boolean, isError: boolean, maxCollapsed = 5): string[] {
457
- const { lines, hidden } = collapseLines(text, expanded, maxCollapsed);
550
+ const { lines, hidden } = collapseLines(sanitizeDisplayText(text), expanded, maxCollapsed);
458
551
  const color = isError ? "error" : "muted";
459
552
  const out = lines.map((line, i) => (i === 0 ? ` ⎿ ${theme.fg(color, line)}` : ` ${theme.fg(color, line)}`));
460
553
  if (hidden > 0) out.push(` ${theme.fg("dim", `… +${hidden} lines (${EXPAND_HINT})`)}`);
@@ -471,6 +564,47 @@ export function textContent(result: { content?: Array<{ type: string; text?: str
471
564
  .trim();
472
565
  }
473
566
 
567
+ /** The file operation a rejected file tool names: "User rejected write to …", "User rejected update to …". */
568
+ export type RejectedFileOp = "write" | "update";
569
+
570
+ /** Claude Code's transcript line for a call the user rejected with a reason, or for a tool with no line of its own. */
571
+ export const TOOL_USE_REJECTED = "Tool use rejected";
572
+
573
+ /**
574
+ * A file tool's `path` for the transcript, as Claude Code shows it: relative to
575
+ * the working directory when it is absolute, as the model wrote it otherwise.
576
+ */
577
+ export function displayToolPath(raw: string, cwd: string | undefined): string {
578
+ if (!cwd || !isAbsolute(raw)) return raw;
579
+ return relative(cwd, raw) || raw;
580
+ }
581
+
582
+ /**
583
+ * The result line for a call the user rejected, or undefined when the result
584
+ * is not a rejection. The model reads the whole rejection text; the transcript
585
+ * shows Claude Code's short line instead: "User rejected write to <path>" for a
586
+ * file tool rejected without a reason, "Tool use rejected" otherwise. A
587
+ * rejection without a reason also stops the turn, and the turn's own
588
+ * "Interrupted" line follows it.
589
+ */
590
+ export function rejectionLine(
591
+ theme: ThemeLike,
592
+ result: { content?: Array<{ type: string; text?: string }> },
593
+ isError: boolean,
594
+ file?: { op: RejectedFileOp; path: string | undefined },
595
+ ): string | undefined {
596
+ if (!isError) return undefined;
597
+ const kind = userDenialKind(textContent(result));
598
+ if (!kind) return undefined;
599
+ if (kind === "stop" && file?.path) return ` ⎿ ${rejectedFileHead(theme, file.op, file.path)}`;
600
+ return ` ⎿ ${theme.fg("dim", TOOL_USE_REJECTED)}`;
601
+ }
602
+
603
+ /** "User rejected <op> to <path>", dimmed with the path in bold: a file tool's reasonless rejection, before the `⎿` elbow. */
604
+ export function rejectedFileHead(theme: ThemeLike, op: RejectedFileOp, path: string): string {
605
+ return `${theme.fg("dim", `User rejected ${op} to `)}${theme.bold(theme.fg("dim", sanitizeDisplayText(path)))}`;
606
+ }
607
+
474
608
  export interface CcRenderSpec<TArgs = unknown, TDetails = unknown> {
475
609
  /** One-line arg summary shown in parentheses. Default: `summarizeArgs`. */
476
610
  title?: (args: TArgs | undefined) => string | undefined;
@@ -485,6 +619,12 @@ export interface CcRenderSpec<TArgs = unknown, TDetails = unknown> {
485
619
  ) => string | undefined;
486
620
  /** Lines shown before the `… +N lines` trailer kicks in. Default 5. */
487
621
  maxCollapsedLines?: number;
622
+ /**
623
+ * Already-painted text (an OSC 8 hyperlink, say) appended to the first
624
+ * result line after the result is sanitized. Build it only from values the
625
+ * tool controls, never from its output: nothing strips its escapes.
626
+ */
627
+ link?: (result: { details?: TDetails }, isError: boolean) => string | undefined;
488
628
  }
489
629
 
490
630
  /**
@@ -524,15 +664,33 @@ export function ccToolRenderers<TArgs = any, TDetails = any>(
524
664
  theme: ThemeLike,
525
665
  context: { args?: TArgs; isError: boolean },
526
666
  ) {
667
+ const stripped = stripReminderBlocks(result);
668
+ const rejected = rejectionLine(theme, stripped, context.isError);
669
+ if (rejected) return linesComponent(() => [rejected]);
527
670
  let text: string | undefined;
528
671
  try {
529
672
  text = spec.result?.(result, context.args, context.isError);
530
673
  } catch {
531
674
  text = undefined;
532
675
  }
533
- text ??= textContent(stripReminderBlocks(result));
676
+ text ??= textContent(stripped);
534
677
  if (!text) return linesComponent(() => []);
535
- return linesComponent(() => resultLines(theme, text, options.expanded, context.isError, spec.maxCollapsedLines));
678
+ let link: string | undefined;
679
+ try {
680
+ link = spec.link?.(result, context.isError);
681
+ } catch {
682
+ link = undefined;
683
+ }
684
+ return linesComponent((width) => {
685
+ const lines = resultLines(theme, text, options.expanded, context.isError, spec.maxCollapsedLines);
686
+ if (link && lines.length > 0) {
687
+ // Truncation drops a cut link's closing escape and leaves the
688
+ // hyperlink open, so a link that does not fit goes in as plain text.
689
+ const joined = `${lines[0]} ${link}`;
690
+ lines[0] = visibleWidth(joined) <= width ? joined : `${lines[0]} ${sanitizeDisplayText(link)}`;
691
+ }
692
+ return lines;
693
+ });
536
694
  },
537
695
  };
538
696
  }
@@ -558,6 +716,8 @@ export interface CcWrapOptions<TArgs = any> {
558
716
  * content below the header (edit's diff preview, shown before approval).
559
717
  */
560
718
  keepBaseCall?: boolean;
719
+ /** A file tool: a rejection without a reason reads "User rejected <op> to <path>" (the path from `title`). */
720
+ rejectedAs?: RejectedFileOp;
561
721
  }
562
722
 
563
723
  /**
@@ -620,6 +780,17 @@ export function stripReminderBlocks<T extends { content?: unknown }>(result: T):
620
780
  return changed ? { ...result, content: kept } : result;
621
781
  }
622
782
 
783
+ /**
784
+ * The context pi's built-in renderers get under the wrapper. pi 1.1.0 added
785
+ * two fields that would change our rows: the shell's `durationMs` prints a
786
+ * "Took Ns" line Claude Code's rows do not have, and `outputPad` sets the edit
787
+ * preview's and diff's indent. Hide the first and pin the second to the 1 pi
788
+ * hard-coded before, so a row looks the same on every pi and setting.
789
+ */
790
+ function baseContext(context: any, lastComponent: unknown): any {
791
+ return { ...context, durationMs: undefined, outputPad: 1, lastComponent };
792
+ }
793
+
623
794
  export function ccWrapBuiltinRenderers<TArgs = any>(
624
795
  label: string,
625
796
  base: {
@@ -632,14 +803,16 @@ export function ccWrapBuiltinRenderers<TArgs = any>(
632
803
  renderCall: (args: TArgs, theme: any, context: any) => TuiComponent;
633
804
  renderResult: (result: any, options: { expanded: boolean; isPartial: boolean }, theme: any, context: any) => TuiComponent;
634
805
  } {
635
- const titleOf = (args: TArgs | undefined): string => {
806
+ const rawTitle = (args: TArgs | undefined): string | undefined => {
636
807
  try {
637
- const custom = opts.title?.(args);
638
- if (custom !== undefined) return summarizeArgs(custom);
808
+ return opts.title?.(args);
639
809
  } catch {
640
- // fall through to the generic summary
810
+ return undefined;
641
811
  }
642
- return summarizeArgs(args);
812
+ };
813
+ const titleOf = (args: TArgs | undefined): string => {
814
+ const custom = rawTitle(args);
815
+ return summarizeArgs(custom !== undefined ? custom : args);
643
816
  };
644
817
 
645
818
  return {
@@ -652,7 +825,7 @@ export function ccWrapBuiltinRenderers<TArgs = any>(
652
825
  }
653
826
  let inner: any;
654
827
  try {
655
- inner = base.renderCall(args, theme, { ...context, lastComponent: context.state.ccInnerCall });
828
+ inner = base.renderCall(args, theme, baseContext(context, context.state.ccInnerCall));
656
829
  context.state.ccInnerCall = inner;
657
830
  } catch {
658
831
  return linesComponent(() => [head()]);
@@ -683,9 +856,15 @@ export function ccWrapBuiltinRenderers<TArgs = any>(
683
856
  },
684
857
  renderResult(rawResult: any, options: { expanded: boolean; isPartial: boolean }, theme: ThemeLike, context: any) {
685
858
  const result = stripReminderBlocks(rawResult);
859
+ const path = rawTitle(context.args);
860
+ const rejected = rejectionLine(theme, result, context.isError, opts.rejectedAs && {
861
+ op: opts.rejectedAs,
862
+ path: path ? displayToolPath(path, context.cwd) : undefined,
863
+ });
864
+ if (rejected) return linesComponent(() => [rejected]);
686
865
  let inner: any;
687
866
  try {
688
- inner = base.renderResult?.(result, options, theme, { ...context, lastComponent: context.state.ccInnerResult });
867
+ inner = base.renderResult?.(result, options, theme, baseContext(context, context.state.ccInnerResult));
689
868
  context.state.ccInnerResult = inner;
690
869
  } catch {
691
870
  inner = undefined;
@@ -744,9 +923,9 @@ export function formatFireTime(ms: number): string {
744
923
  * `✻ Running scheduled task (Sep 11 12:03pm)` line, the prompt itself under it
745
924
  * on ctrl+o (the prompt is the turn's input, verbatim — findings §21).
746
925
  */
747
- export function scheduledTaskComponent(theme: ThemeLike, prompt: string, firedAt: number, expanded: boolean): TuiComponent {
748
- const lines = prompt.trim().split("\n");
749
- const headline = `Running scheduled task (${formatFireTime(firedAt)})`;
926
+ export function scheduledTaskComponent(theme: ThemeLike, prompt: string, firedAt: number, expanded: boolean, label = "Running scheduled task", suffix = ""): TuiComponent {
927
+ const lines = sanitizeDisplayText(prompt).trim().split("\n");
928
+ const headline = `${label} (${formatFireTime(firedAt)})${suffix}`;
750
929
  return linesComponent(() => {
751
930
  if (!expanded) {
752
931
  return [`${theme.fg("dim", "✻")} ${theme.fg("dim", headline)}${theme.fg("dim", ` (+${lines.length} lines, ${EXPAND_HINT})`)}`];
@@ -764,10 +943,48 @@ export function scheduledTaskComponent(theme: ThemeLike, prompt: string, firedAt
764
943
  * wire frames (`<task-notification>`, the agent-message envelope) are
765
944
  * model-only; `parseNotificationFrames` takes them apart for display.
766
945
  */
767
- export function notificationComponent(theme: ThemeLike, text: string, expanded: boolean): TuiComponent {
946
+ /** An agent task's `<summary>`: `Agent "<description>" finished` / `failed: …` / `was stopped…` (findings §24). */
947
+ function isAgentCompletion(summary: string): boolean {
948
+ return summary.startsWith('Agent "');
949
+ }
950
+
951
+ /**
952
+ * A notification that opens a session's first turn goes out as a user message
953
+ * (lib/notifications.ts, "First turn of a session"), and pi draws a user
954
+ * message as markdown with no per-message renderer, so the wire frames would
955
+ * show in full. This is that message's display text: the headlines
956
+ * `notificationComponent` shows collapsed, one paragraph per frame, without
957
+ * the expand hint (a user message does not expand). `undefined` when the text
958
+ * is not wholly harness frames, so a real prompt renders as typed.
959
+ */
960
+ export function notificationUserMarkdown(text: string): string | undefined {
961
+ // Every user message renders through here: a typed prompt skips the parse.
962
+ if (!text.includes("<task-notification>") && !text.includes("<agent-message ")) return undefined;
963
+ const parsed = parseNotificationFrames(sanitizeDisplayText(text));
964
+ if (parsed.length === 0 || parsed.some((frame) => frame.kind === "text")) return undefined;
965
+ const frames = parsed.filter((frame) => !(frame.kind === "task" && frame.pointer));
966
+ return (frames.length > 0 ? frames : parsed)
967
+ .map((frame) => {
968
+ if (frame.kind === "agent-message") return `› Message from @${frame.from}`;
969
+ if (frame.kind === "task" && isAgentCompletion(frame.summary)) {
970
+ const took = frame.durationMs === undefined ? "" : ` · ${formatDuration(0, frame.durationMs)}`;
971
+ return `● ${oneLine(frame.summary)}${took}`;
972
+ }
973
+ return frame.kind === "task" ? `✳ ${oneLine(frame.summary)}` : "";
974
+ })
975
+ .join("\n\n");
976
+ }
977
+
978
+ /**
979
+ * `agentName` names the agent a message comes from (its task id is the
980
+ * wire address): the headline reads `› Message from @<name>`, falling back to
981
+ * the address when the name is unknown.
982
+ */
983
+ export function notificationComponent(theme: ThemeLike, text: string, expanded: boolean, agentName?: (from: string) => string | undefined): TuiComponent {
768
984
  // An agent's completion pointer says nothing the message above it does not;
769
- // CC's transcript shows the message alone.
770
- const parsed = parseNotificationFrames(text);
985
+ // CC's transcript shows the message alone. Bodies carry shell output and
986
+ // agent replies, so control characters are removed before parsing.
987
+ const parsed = parseNotificationFrames(sanitizeDisplayText(text));
771
988
  const frames = parsed.filter((frame) => !(frame.kind === "task" && frame.pointer));
772
989
  if (frames.length === 0) frames.push({ kind: "text", text: framesToText(parsed) });
773
990
  const dim = (s: string) => theme.fg("dim", s);
@@ -785,8 +1002,19 @@ export function notificationComponent(theme: ThemeLike, text: string, expanded:
785
1002
  // the header, then the body as delivered (preamble, indented report),
786
1003
  // indented under it, when expanded. The guard is never shown.
787
1004
  const bodyLines = frame.body.split("\n");
788
- if (expanded) out.push(dim(`› Message from ${frame.from}`), ...body(bodyLines));
789
- else out.push(dim(`› Message from @${frame.from}`) + hint(bodyLines));
1005
+ const sender = agentName?.(frame.from) ?? frame.from;
1006
+ if (expanded) out.push(dim(`› Message from ${sender}`), ...body(bodyLines));
1007
+ else out.push(dim(`› Message from @${sender}`) + hint(bodyLines));
1008
+ continue;
1009
+ }
1010
+ if (frame.kind === "task" && isAgentCompletion(frame.summary)) {
1011
+ // Claude Code 2.1.283's line for an agent's turn: `⏺ Agent "<desc>"
1012
+ // finished · 3s`, the dot tinted by outcome, no expand hint (findings
1013
+ // §40). ctrl+o still shows the report under it.
1014
+ const dot = theme.fg(frame.status === "failed" ? "error" : frame.status === "completed" ? "success" : "dim", "●");
1015
+ const took = frame.durationMs === undefined ? "" : dim(` · ${formatDuration(0, frame.durationMs)}`);
1016
+ out.push(`${dot} ${oneLine(frame.summary)}${took}`);
1017
+ if (expanded && frame.body) out.push(...body(frame.body.split("\n")));
790
1018
  continue;
791
1019
  }
792
1020
  const headline = frame.kind === "task" ? frame.summary : oneLine(frame.text.split("\n")[0] ?? "");
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Claude Code's per-turn `<total_tokens>` placements (pure). One Code stores
3
+ * the line the way it always has, so a resumed session rebuilds the same
4
+ * bytes: a raw marker block after the text of every user message after the
5
+ * first (context-budget's `sticky-append`), and the countdown as its own
6
+ * block at the end of every tool result (persisted by system-reminder). Two
7
+ * steps turn those into Claude Code's shapes:
8
+ *
9
+ * - at `context` time, while a tool result's blocks are still separate and
10
+ * keyed by its call id (`resolveCountdowns`): on a model that takes a
11
+ * mid-conversation system message the countdown block leaves the result and
12
+ * its value is kept by call id; elsewhere it is framed, a blank line after
13
+ * the output once pi joins the blocks (Claude Code's Haiku shape);
14
+ * - on the final body (`withTurnBudgetMessages`): on a system-role model a
15
+ * system message with the line follows each later prompt and each
16
+ * tool-result message (the lowest countdown it carries), and on first-party
17
+ * Fable the `clear_at` nudge follows the latter; elsewhere the framed block
18
+ * goes before the text of each later prompt, with a trailing newline.
19
+ *
20
+ * Nothing is parsed out of joined text, so a tool's own output that contains
21
+ * such a line is never touched.
22
+ */
23
+
24
+ import { reseatMessageMark } from "./anthropic-payload.ts";
25
+ import { wrapReminder } from "./reminders.ts";
26
+ import { messagesKey, type SystemRoleLayout, systemMessage, type WireMessage, type WirePart, byWireCallId, withoutParts } from "./system-role.ts";
27
+
28
+ /** The stored line, exactly. */
29
+ const LINE = /^<total_tokens>(\d+) tokens left<\/total_tokens>$/;
30
+
31
+ /** Claude Code's Fable nudge after a tool result's budget message. */
32
+ export const CLEAR_AT_NUDGE =
33
+ "First privately list what you need next; then request every item that doesn't depend on another's result in this one response.";
34
+
35
+ type StoredBlock = { type?: string; text?: string };
36
+ type StoredMessage = { role: string; toolCallId?: string; content?: unknown };
37
+
38
+ /**
39
+ * The context-time step over pi's messages: each tool result's countdown block
40
+ * (the last whole block that is the line) is lifted out on a system-role model,
41
+ * its value kept under the call id, or framed elsewhere. Returns the messages
42
+ * (the input when nothing changed) and the countdowns by call id.
43
+ */
44
+ export function resolveCountdowns<T extends StoredMessage>(messages: T[], systemRole: boolean): { messages: T[]; left: Map<string, number> } {
45
+ const left = new Map<string, number>();
46
+ let changed = false;
47
+ const out = messages.map((message) => {
48
+ if (message.role !== "toolResult" || !Array.isArray(message.content) || !message.toolCallId) return message;
49
+ const blocks = message.content as StoredBlock[];
50
+ const at = blocks.findLastIndex((block) => block?.type === "text" && typeof block.text === "string" && LINE.test(block.text));
51
+ if (at === -1) return message;
52
+ const line = blocks[at].text as string;
53
+ changed = true;
54
+ if (systemRole) {
55
+ left.set(message.toolCallId, Number(LINE.exec(line)?.[1]));
56
+ return { ...message, content: blocks.filter((_, index) => index !== at) };
57
+ }
58
+ return { ...message, content: blocks.map((block, index) => (index === at ? { ...block, text: `\n${wrapReminder(line)}` } : block)) };
59
+ });
60
+ return { messages: changed ? out : messages, left };
61
+ }
62
+
63
+ export interface TurnBudgetOptions {
64
+ /** The wire shape. */
65
+ shape: SystemRoleLayout;
66
+ /** True on a model that takes a mid-conversation system message. */
67
+ systemRole: boolean;
68
+ /** The role a system message takes on the OpenAI APIs. */
69
+ role: "system" | "developer";
70
+ /** The countdowns `resolveCountdowns` lifted, by call id. */
71
+ left: ReadonlyMap<string, number>;
72
+ /** Send Claude Code's `clear_at` nudge after a tool result's budget message (first-party Fable). */
73
+ nudge?: boolean;
74
+ /** False when the budget is off: no prompt carries a marker, so a user's own tag stays text. */
75
+ markers?: boolean;
76
+ }
77
+
78
+ /** The call ids a wire message answers: Anthropic `tool_result` blocks, an OpenAI output item or tool message. */
79
+ function answeredCalls(message: WireMessage): string[] {
80
+ if (message?.role === "user" && Array.isArray(message.content)) {
81
+ return (message.content as WirePart[]).flatMap((part) => (part?.type === "tool_result" && typeof part.tool_use_id === "string" ? [part.tool_use_id] : []));
82
+ }
83
+ if (message?.type === "function_call_output" && typeof message.call_id === "string") return [message.call_id];
84
+ if (message?.role === "tool" && typeof message.tool_call_id === "string") return [message.tool_call_id];
85
+ return [];
86
+ }
87
+
88
+ export function withTurnBudgetMessages(payload: Record<string, unknown>, opts: TurnBudgetOptions): Record<string, unknown> | undefined {
89
+ const key = messagesKey(opts.shape);
90
+ const messages = payload[key];
91
+ if (!Array.isArray(messages)) return undefined;
92
+ const out: WireMessage[] = [];
93
+ let changed = false;
94
+ /** The lowest countdown of the tool results not yet followed by their message. */
95
+ let pending: number | undefined;
96
+ /** The first user message carries the context stack, never a marker (context-budget's `skipStackCarrier`). */
97
+ let stackCarrierSeen = false;
98
+ for (let i = 0; i < messages.length; i++) {
99
+ let message = messages[i] as WireMessage;
100
+ const after: WireMessage[] = [];
101
+ const stackCarrier = !stackCarrierSeen && message?.role === "user";
102
+ if (stackCarrier) stackCarrierSeen = true;
103
+ // A later prompt's marker: a system message after it, or the framed block before its text.
104
+ // The marker comes after the user's text, so the last match is it: a
105
+ // prompt that is itself such a tag stays the user's, and so does the
106
+ // first prompt, which has no marker to find.
107
+ if (opts.markers !== false && !stackCarrier && message?.role === "user" && Array.isArray(message.content)) {
108
+ const parts = message.content as WirePart[];
109
+ const at = parts.findLastIndex((part) => typeof part?.text === "string" && LINE.test(part.text));
110
+ // The marker ends the message, so it carries pi's cache mark; withoutParts keeps it.
111
+ const rest = at === -1 ? undefined : withoutParts(parts, (_, index) => index === at);
112
+ if (rest) {
113
+ const line = parts[at].text as string;
114
+ if (opts.systemRole) {
115
+ message = { ...message, content: rest };
116
+ after.push(systemMessage(opts.shape, opts.role, line));
117
+ } else {
118
+ message = { ...message, content: [{ type: opts.shape === "responses" ? "input_text" : "text", text: `${wrapReminder(line)}\n` }, ...rest] };
119
+ }
120
+ }
121
+ }
122
+ if (opts.systemRole) {
123
+ for (const id of answeredCalls(message)) {
124
+ const value = byWireCallId(opts.left, id);
125
+ if (value !== undefined) pending = pending === undefined ? value : Math.min(pending, value);
126
+ }
127
+ // On the OpenAI APIs a batch of results is a run of items; one message follows the run.
128
+ const next = messages[i + 1] as WireMessage | undefined;
129
+ const runContinues = opts.shape !== "anthropic" && (next?.type === "function_call_output" || next?.role === "tool");
130
+ if (pending !== undefined && !runContinues) {
131
+ after.push(systemMessage(opts.shape, opts.role, `<total_tokens>${pending} tokens left</total_tokens>`));
132
+ if (opts.nudge) {
133
+ after.push(opts.shape === "anthropic" ? ({ role: "system", content: CLEAR_AT_NUDGE, clear_at: "next_user_message" } as WireMessage) : systemMessage(opts.shape, opts.role, CLEAR_AT_NUDGE));
134
+ }
135
+ pending = undefined;
136
+ }
137
+ }
138
+ if (message !== messages[i] || after.length > 0) changed = true;
139
+ out.push(message, ...after);
140
+ }
141
+ if (!changed) return undefined;
142
+ return { ...payload, [key]: opts.shape === "anthropic" ? reseatMessageMark(out) : out };
143
+ }
@@ -23,13 +23,26 @@ export const APP_PACKAGE = "@one-ai/one-code";
23
23
  export const EXTENSION_PACKAGE = "one-code-extension";
24
24
  /** The Homebrew formula (IsuruMaduranga/homebrew-one-ai), renamed from one-code at 0.3.0. */
25
25
  export const HOMEBREW_FORMULA = "onecode";
26
- /** How each install method upgrades the app; the doctor and the update notice quote these. */
26
+ /**
27
+ * How each install method upgrades One Code; the doctor and the update notice
28
+ * quote these. On the user's own pi a bare `pi update` updates pi itself and
29
+ * skips packages, so the package is named.
30
+ */
27
31
  export const UPGRADE_COMMANDS = {
28
32
  npm: `npm install -g ${APP_PACKAGE}`,
29
33
  brew: `brew upgrade ${HOMEBREW_FORMULA}`,
30
- "pi-package": "pi update",
34
+ "pi-package": `pi update npm:${EXTENSION_PACKAGE}`,
31
35
  };
32
36
 
37
+ /**
38
+ * The upgrade command for a running install: the pi package's, or the app's by
39
+ * the install method the launcher published (`ONECODE_INSTALL_METHOD`).
40
+ */
41
+ export function upgradeCommandFor(install, env = process.env) {
42
+ if (install !== "app") return UPGRADE_COMMANDS["pi-package"];
43
+ return env.ONECODE_INSTALL_METHOD === "brew" ? UPGRADE_COMMANDS.brew : UPGRADE_COMMANDS.npm;
44
+ }
45
+
33
46
  /**
34
47
  * The registry packument for a package (scoped slash percent-encoded). The
35
48
  * full document, not `/latest`: its `time` map dates every version, which
@@ -22,7 +22,7 @@ export const USAGE_CHANNEL = "one-code:usage-recorded";
22
22
  */
23
23
  export const USAGE_ENTRY_TYPE = "one-code:usage";
24
24
 
25
- export type UsageSource = "subagent" | "classifier" | "reader" | "recap" | "session-title" | "setup" | "btw";
25
+ export type UsageSource = "subagent" | "classifier" | "reader" | "web-fetch" | "web-search" | "recap" | "session-title" | "setup" | "btw";
26
26
 
27
27
  export interface UsageRecord {
28
28
  source: UsageSource;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The result a tool call gets when the user rejects it (pure): Claude Code's
3
+ * denial text, shared by the permission prompt and plan-mode approval. With
4
+ * the user's typed reason it carries their words (decisions/tools.md, "Denial
5
+ * feedback carries the user's words") in Claude Code's "To tell you how to
6
+ * proceed, the user said:" form; without one it tells the model to stop and
7
+ * wait for the user.
8
+ */
9
+
10
+ const REJECTED =
11
+ "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file).";
12
+
13
+ const MEMORY_NOTE =
14
+ "Note: The user's next message may contain a correction or preference. Pay close attention — if they explain what went wrong or how they'd prefer you to work, consider saving that to memory for future sessions.";
15
+
16
+ const STOP = "STOP what you are doing and wait for the user to tell you how to proceed.";
17
+ const USER_SAID = "To tell you how to proceed, the user said:\n";
18
+
19
+ /** Claude Code's rejection text, with the user's reason when they typed one. */
20
+ export function userDenialText(feedback?: string): string {
21
+ const said = feedback?.trim();
22
+ const next = said ? `${USER_SAID}${said}` : STOP;
23
+ return `${REJECTED} ${next}\n\n${MEMORY_NOTE}`;
24
+ }
25
+
26
+ /**
27
+ * Whether a tool result is the user's rejection, for the transcript: `stop`
28
+ * when they rejected without a reason, `reason` when they gave one, undefined
29
+ * for anything else. The transcript shows a short line for either (Claude
30
+ * Code's); the model still reads the whole text.
31
+ */
32
+ export function userDenialKind(text: string): "stop" | "reason" | undefined {
33
+ if (!text.startsWith(`${REJECTED} `)) return undefined;
34
+ const next = text.slice(REJECTED.length + 1);
35
+ if (next.startsWith(STOP)) return "stop";
36
+ if (next.startsWith(USER_SAID)) return "reason";
37
+ return undefined;
38
+ }