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
@@ -7,10 +7,13 @@
7
7
  * Everything here is pure and paint-injected so it unit-tests with an identity
8
8
  * paint. `paint(color, text)` matches `safeThemePaint`; in tests it returns the
9
9
  * text unchanged, so assertions see the plain line. Inclusion decisions use
10
- * plain lengths only, so the painted result (ANSI adds zero display width)
11
- * always fits the width it was built for.
10
+ * the plain text's terminal columns (`visibleWidth`, never `.length`, so a CJK
11
+ * path or branch counts two columns a glyph), so the painted result (ANSI adds
12
+ * zero display width) always fits the width it was built for.
12
13
  */
13
14
 
15
+ import { cutPlainText } from "../lib/tui-render.ts";
16
+ import { tailColumns, visibleWidth } from "../lib/text-width.ts";
14
17
  import { costOf, usageEntryCost } from "../lib/usage-bus.ts";
15
18
  import type { WorktreeLocation } from "../lib/worktree-channel.ts";
16
19
 
@@ -21,8 +24,12 @@ export interface FooterData {
21
24
  cwd: string;
22
25
  home: string;
23
26
  branch?: string;
24
- /** Context tokens in use and the model's window; both needed to show fill. */
25
- contextTokens?: number;
27
+ /**
28
+ * Context tokens in use and the model's window. Tokens are unknown right
29
+ * after a compaction until the next reply; the fill then reads `?/window`,
30
+ * as pi's own footer shows it.
31
+ */
32
+ contextTokens?: number | null;
26
33
  contextWindow?: number;
27
34
  /** Context percentage, shown in parens after the fill; `null`/undefined omits it
28
35
  * (e.g. right after compaction, unknown until the next reply). */
@@ -73,12 +80,13 @@ function tilde(cwd: string, home: string): string {
73
80
 
74
81
  /**
75
82
  * Fit a path into `max` columns, keeping the tail (the deepest, most specific
76
- * folders) with a leading `…`. `max <= 1` collapses to just `…`.
83
+ * folders) with a leading `…`. `max <= 1` collapses to just `…`. Cut by
84
+ * columns on a grapheme boundary, so a wide glyph or an emoji stays whole.
77
85
  */
78
86
  function truncatePath(path: string, max: number): string {
79
- if (path.length <= max) return path;
87
+ if (visibleWidth(path) <= max) return path;
80
88
  if (max <= 1) return "…";
81
- return `…${path.slice(path.length - (max - 1))}`;
89
+ return `…${tailColumns(path, max - 1).text}`;
82
90
  }
83
91
 
84
92
  interface Part {
@@ -86,6 +94,8 @@ interface Part {
86
94
  painted: string;
87
95
  /** Higher drops first when the line is too narrow; 0 = never dropped. */
88
96
  dropPriority: number;
97
+ /** The model name: the one core part cut (with an ellipsis) when even the core overflows. */
98
+ model: boolean;
89
99
  }
90
100
 
91
101
  const SEP = " · ";
@@ -103,17 +113,19 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
103
113
  // A labelled metric: the label is muted, the value takes `color`. The plain
104
114
  // form (label + value) drives the width/drop math; the painted form colours
105
115
  // the two halves separately.
106
- const push = (label: string, value: string, color: string, dropPriority = 0) => {
116
+ const push = (label: string, value: string, color: string, dropPriority = 0, model = false) => {
107
117
  if (!value) return;
108
118
  const plain = label ? `${label} ${value}` : value;
109
119
  const painted = label ? `${paint("muted", label)} ${paint(color, value)}` : paint(color, value);
110
- parts.push({ plain, painted, dropPriority });
120
+ parts.push({ plain, painted, dropPriority, model });
111
121
  };
112
122
 
113
- // Context fill as "usage: tokens/window (pct%)".
114
- if (typeof data.contextTokens === "number" && typeof data.contextWindow === "number" && data.contextWindow > 0) {
115
- const pct = typeof data.contextPercent === "number" ? ` (${data.contextPercent.toFixed(0)}%)` : "";
116
- push("usage:", `${formatTokens(data.contextTokens)}/${formatTokens(data.contextWindow)}${pct}`, "dim");
123
+ // Context fill as "usage: tokens/window (pct%)", or "?/window" while the
124
+ // tokens are unknown (right after a compaction).
125
+ if (typeof data.contextWindow === "number" && data.contextWindow > 0) {
126
+ const tokens = typeof data.contextTokens === "number" ? formatTokens(data.contextTokens) : "?";
127
+ const pct = typeof data.contextTokens === "number" && typeof data.contextPercent === "number" ? ` (${data.contextPercent.toFixed(0)}%)` : "";
128
+ push("usage:", `${tokens}/${formatTokens(data.contextWindow)}${pct}`, "dim");
117
129
  }
118
130
  // Total cost, always shown once known.
119
131
  push("cost:", formatCost(data.cost), "dim");
@@ -123,7 +135,7 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
123
135
  push("cache-hit:", `${data.cacheHitPercent.toFixed(0)}%`, color, 2);
124
136
  }
125
137
  // Model, then effort — no labels; both self-describing.
126
- if (data.model) push("", data.model, "dim");
138
+ if (data.model) push("", data.model, "dim", 0, true);
127
139
  if (data.effort) push("", data.effort, "accent");
128
140
 
129
141
  // Left chunk: path + branch, with the open PR attached to the branch as
@@ -132,7 +144,7 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
132
144
  // branch (bare-branch fallback), and only when there is no room even for the
133
145
  // bare branch does the whole chunk go, leaving metrics alone. The three
134
146
  // segments paint separately — path dim, branch in accent, PR in the link
135
- // colour — but every width/gap decision uses their plain lengths.
147
+ // colour — but every width/gap decision uses their plain columns.
136
148
  const path = tilde(data.cwd, data.home);
137
149
  const branchSeg = data.branch ? ` ⎇ ${data.branch}` : "";
138
150
  const prSeg = data.branch && typeof data.pr === "number" ? ` ← PR #${data.pr}` : "";
@@ -140,7 +152,7 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
140
152
  // Drop optional right parts (highest priority first) until the metrics fit.
141
153
  let kept = parts.slice();
142
154
  const rightPlain = () => kept.map((p) => p.plain).join(SEP);
143
- while (rightPlain().length > width) {
155
+ while (visibleWidth(rightPlain()) > width) {
144
156
  const droppable = kept
145
157
  .map((p, i) => ({ p, i }))
146
158
  .filter((x) => x.p.dropPriority > 0)
@@ -149,25 +161,34 @@ export function buildFooterLines(data: FooterData, width: number, paint: Paint):
149
161
  kept = kept.filter((_, i) => i !== droppable.i);
150
162
  }
151
163
 
152
- const rightLen = rightPlain().length;
153
-
154
- // Even the core overflows: last resort, truncate the plain metrics to width.
155
- if (rightLen > width) {
156
- return [paint("dim", rightPlain().slice(0, width))];
164
+ // Even the core overflows: cut the model name with an ellipsis so the
165
+ // effort after it stays; when that is not enough, cut the whole line.
166
+ const overflow = visibleWidth(rightPlain()) - width;
167
+ if (overflow > 0) {
168
+ const model = kept.find((p) => p.model);
169
+ const room = model ? visibleWidth(model.plain) - overflow : 0;
170
+ if (model && room >= 4) {
171
+ const shortened = cutPlainText(model.plain, room);
172
+ kept = kept.map((p) => (p === model ? { ...p, plain: shortened, painted: paint("dim", shortened) } : p));
173
+ } else {
174
+ return [paint("dim", cutPlainText(rightPlain(), width))];
175
+ }
157
176
  }
158
177
 
178
+ const rightLen = visibleWidth(rightPlain());
179
+
159
180
  const rightPaintedStr = kept.map((p) => p.painted).join(paint("dim", SEP));
160
181
 
161
182
  // Room for the left chunk after the metrics and at least one space of gap.
162
183
  // Keep the PR only if the branch+PR both fit; otherwise drop it and keep the
163
184
  // bare branch.
164
185
  const room = width - rightLen - 1;
165
- const keptPr = room >= branchSeg.length + prSeg.length + 1 ? prSeg : "";
166
- const suffixLen = branchSeg.length + keptPr.length;
186
+ const keptPr = room >= visibleWidth(branchSeg) + visibleWidth(prSeg) + 1 ? prSeg : "";
187
+ const suffixLen = visibleWidth(branchSeg) + visibleWidth(keptPr);
167
188
  if (room >= suffixLen + 1) {
168
189
  const shownPath = truncatePath(path, room - suffixLen);
169
190
  const leftPlain = `${shownPath}${branchSeg}${keptPr}`;
170
- const gap = " ".repeat(width - leftPlain.length - rightLen);
191
+ const gap = " ".repeat(Math.max(0, width - visibleWidth(leftPlain) - rightLen));
171
192
  const leftPainted = `${paint("dim", shownPath)}${paint("accent", branchSeg)}${paint("mdLink", keptPr)}`;
172
193
  return [`${leftPainted}${gap}${rightPaintedStr}`];
173
194
  }
@@ -24,7 +24,7 @@ import { ULTRACODE_STATUS_KEY } from "../effort/slider.ts";
24
24
  import { USAGE_CHANNEL } from "../lib/usage-bus.ts";
25
25
  import { buildFooterLines, computeMainUsage, footerLocation, type FooterData } from "./footer-line.ts";
26
26
  import { WORKTREE_CHANNEL, type WorktreeLocation } from "../lib/worktree-channel.ts";
27
- import { fetchPrNumber } from "./pr.ts";
27
+ import { fetchPrNumber, nextPrPollDelay, PR_SLOW_LOOKUP_MS } from "./pr.ts";
28
28
 
29
29
  export default function footerExtension(pi: ExtensionAPI) {
30
30
  if (process.env.CC_FOOTER === "0") return;
@@ -38,6 +38,27 @@ export default function footerExtension(pi: ExtensionAPI) {
38
38
  let pr: number | undefined;
39
39
  /** Bumped per branch change so a slow gh lookup for an old branch is ignored. */
40
40
  let prToken = 0;
41
+ /**
42
+ * The PR is looked up again once a minute while the session is in use, so a
43
+ * PR merged or closed mid-session disappears (pr.ts nextPrPollDelay). A slow
44
+ * or missing `gh` turns polling off; branch changes still look up.
45
+ */
46
+ const prPoll = { lastLookupAt: 0, lastInputAt: Date.now(), disabled: false };
47
+ let prPollTimer: ReturnType<typeof setTimeout> | undefined;
48
+ const stopPrPoll = () => {
49
+ clearTimeout(prPollTimer);
50
+ prPollTimer = undefined;
51
+ };
52
+ const schedulePrPoll = () => {
53
+ stopPrPoll();
54
+ const delay = nextPrPollDelay(prPoll, Date.now());
55
+ if (delay === undefined) return;
56
+ prPollTimer = setTimeout(() => {
57
+ prPollTimer = undefined;
58
+ refreshLocation();
59
+ }, delay);
60
+ prPollTimer.unref?.();
61
+ };
41
62
 
42
63
  /** Invalidate the memoized line and repaint; set once the footer mounts. */
43
64
  let repaint = () => {};
@@ -71,20 +92,43 @@ export default function footerExtension(pi: ExtensionAPI) {
71
92
 
72
93
  const refreshPr = (cwd: string, branch: string | null) => {
73
94
  const token = ++prToken;
95
+ stopPrPoll();
74
96
  if (!branch) {
75
97
  pr = undefined;
76
98
  repaint();
77
99
  return;
78
100
  }
101
+ const startedAt = Date.now();
102
+ prPoll.lastLookupAt = startedAt;
79
103
  void fetchPrNumber(cwd, branch).then((found) => {
80
104
  if (token !== prToken) return; // a newer branch superseded this lookup
81
- pr = found;
105
+ if (found.ghMissing || Date.now() - startedAt > PR_SLOW_LOOKUP_MS) prPoll.disabled = true;
106
+ pr = found.pr;
82
107
  repaint();
108
+ schedulePrPoll();
83
109
  });
84
110
  };
85
111
 
112
+ // Input keeps polling alive; after an hour without any, the next input
113
+ // resumes it. A turn boundary asks again, never sooner than a minute after
114
+ // the last lookup.
115
+ pi.on("input", () => {
116
+ prPoll.lastInputAt = Date.now();
117
+ if (!prPollTimer) schedulePrPoll();
118
+ });
119
+ pi.on("agent_settled", () => {
120
+ if (!prPollTimer) schedulePrPoll();
121
+ });
122
+ pi.on("session_shutdown", () => {
123
+ stopPrPoll();
124
+ prToken++;
125
+ });
126
+
86
127
  pi.on("session_start", (_event, ctx) => {
87
128
  recomputeMain(ctx);
129
+ // A slow or missing gh turns polling off for one session, not the process.
130
+ prPoll.disabled = false;
131
+ prPoll.lastInputAt = Date.now();
88
132
  if (!ctx.hasUI) return;
89
133
 
90
134
  ctx.ui.setFooter((tui: unknown, theme: unknown, footerData: unknown) => {
@@ -144,6 +188,7 @@ export default function footerExtension(pi: ExtensionAPI) {
144
188
  return Object.assign(component, {
145
189
  dispose: () => {
146
190
  stopFollowingBranch();
191
+ stopPrPoll();
147
192
  repaint = () => {};
148
193
  refreshLocation = () => {};
149
194
  },
@@ -8,7 +8,9 @@
8
8
  * a PR whose head lives in the base repo, so it misses the common case of a PR
9
9
  * opened from a fork (`isCrossRepository`), even though `list` on the base repo
10
10
  * knows about it. `--state open` keeps a merged/closed PR for the same branch
11
- * name from showing.
11
+ * name from showing. The footer repeats the lookup once a minute while the
12
+ * session is in use (`nextPrPollDelay`), so a PR merged, closed or opened
13
+ * mid-session shows within a minute.
12
14
  *
13
15
  * The exec lives here but the parse is split out so it unit-tests without a
14
16
  * subprocess.
@@ -35,19 +37,46 @@ export function parsePrNumber(stdout: string): number | undefined {
35
37
 
36
38
  const LOOKUP_TIMEOUT_MS = 4000;
37
39
 
40
+ /** A lookup's outcome: the open PR, if any, and whether `gh` is not installed at all. */
41
+ export interface PrLookup {
42
+ pr: number | undefined;
43
+ ghMissing: boolean;
44
+ }
45
+
38
46
  /**
39
- * Resolve the open PR number for `branch` in `cwd`, or `undefined`. Never
40
- * rejects: any failure (no gh, no PR, detached HEAD, timeout) is `undefined`.
47
+ * Resolve the open PR number for `branch` in `cwd`. Never rejects: any failure
48
+ * (no gh, no PR, detached HEAD, timeout) has `pr` undefined, and a `gh` that
49
+ * is not on PATH says so, so the footer stops polling for it.
41
50
  */
42
- export function fetchPrNumber(cwd: string, branch: string): Promise<number | undefined> {
51
+ export function fetchPrNumber(cwd: string, branch: string): Promise<PrLookup> {
43
52
  return new Promise((resolve) => {
44
53
  execFile(
45
54
  "gh",
46
55
  ["pr", "list", "--head", branch, "--state", "open", "--json", "number"],
47
56
  { cwd, timeout: LOOKUP_TIMEOUT_MS, encoding: "utf8" },
48
57
  (error, stdout) => {
49
- resolve(error ? undefined : parsePrNumber(stdout));
58
+ const ghMissing = (error as NodeJS.ErrnoException | null)?.code === "ENOENT";
59
+ resolve({ pr: error ? undefined : parsePrNumber(stdout), ghMissing });
50
60
  },
51
61
  );
52
62
  });
53
63
  }
64
+
65
+ /** Claude Code's PR status cadence (its `usePrStatus`): one lookup a minute while the session is in use. */
66
+ export const PR_POLL_INTERVAL_MS = 60_000;
67
+ /** Polling stops after this long without user input, and resumes on the next input. */
68
+ export const PR_IDLE_STOP_MS = 60 * 60_000;
69
+ /** A lookup slower than this turns polling off for the session (branch changes still look up). */
70
+ export const PR_SLOW_LOOKUP_MS = 4_000;
71
+
72
+ /**
73
+ * When the next PR lookup is due, relative to the last one: never sooner than
74
+ * PR_POLL_INTERVAL_MS after it, so a turn boundary asking for a refresh does
75
+ * not spawn `gh` more than once a minute. Undefined when polling should stop:
76
+ * turned off by a slow lookup, or no input for PR_IDLE_STOP_MS.
77
+ */
78
+ export function nextPrPollDelay(state: { lastLookupAt: number; lastInputAt: number; disabled: boolean }, now: number): number | undefined {
79
+ if (state.disabled) return undefined;
80
+ if (now - state.lastInputAt >= PR_IDLE_STOP_MS) return undefined;
81
+ return Math.max(0, state.lastLookupAt + PR_POLL_INTERVAL_MS - now);
82
+ }
@@ -0,0 +1,7 @@
1
+ /** Successful PreCompact hook output, scoped to the compaction that requested it. */
2
+ export const PRECOMPACT_INSTRUCTIONS_CHANNEL = "one-code:precompact-instructions";
3
+
4
+ export interface PreCompactInstructions {
5
+ signal: AbortSignal;
6
+ instructions: string;
7
+ }
@@ -7,7 +7,7 @@
7
7
  * pi.exec is deliberately not used: it hardcodes shell:false and gives the
8
8
  * child no stdin, while Claude Code hooks are shell command strings that read
9
9
  * a JSON payload from stdin. (Ported from pi-code's runHookCommand, MIT — see
10
- * docs/decisions.md.)
10
+ * working-docs/decisions.md.)
11
11
  *
12
12
  * The interpreter is Claude Code's: bash (Git Bash on Windows, honouring
13
13
  * `CLAUDE_CODE_GIT_BASH_PATH`) unless the hook says `shell: "powershell"`, or
@@ -22,7 +22,8 @@
22
22
  * otherwise `close` never fires and the promise hangs past the timeout
23
23
  * (Windows: `taskkill /T /F` on the leader's pid does the same)
24
24
  * - setEncoding("utf8") so multi-byte characters can't be split across chunks
25
- * - output capped at 1MB per stream
25
+ * - a hook exceeding 1MB per stream is killed and reported as an output-limit
26
+ * failure, never parsed as a silently truncated JSON verdict
26
27
  * - stdin errors ignored (a hook that exits without reading stdin — `exit 2`
27
28
  * — would otherwise EPIPE-crash the write)
28
29
  * - stdin JSON is newline-terminated: without a trailing "\n" a hook doing
@@ -41,13 +42,14 @@
41
42
  * SIGCHLD-driven exit callback; with the process handle unref'd the loop
42
43
  * counted itself empty and node exited 0 mid-tool, silently, before the
43
44
  * hook's `close` ever fired — measured at ~55% of runs with a trivial
44
- * `exit 0` hook (docs/one-shot-lsp-event-loop-drain.md has the same
45
+ * `exit 0` hook (working-docs/one-shot-lsp-event-loop-drain.md has the same
45
46
  * mechanism for the LSP client). A ref'd child holds the loop until `close`.
46
47
  */
47
48
 
48
49
  import type { ChildProcess } from "node:child_process";
49
50
  import { detachedSpawnOptions, killProcessTree, waitForChildExit } from "../lib/process-tree.ts";
50
51
  import { bashSpawn, POWERSHELL_UTF8_PREFIX, powerShellSpawn, type ShellSpawn, spawnShellCommand } from "../lib/shell-spawn.ts";
52
+ import type { FinishedRun } from "./protocol.ts";
51
53
  import type { HookShell } from "./settings.ts";
52
54
 
53
55
  /**
@@ -76,29 +78,12 @@ export function hookShellSpawn(shell: HookShell | undefined): { spec?: ShellSpaw
76
78
  return { spec: resolved.spawn, shell: want };
77
79
  }
78
80
 
79
- export interface HookRunResult {
80
- /**
81
- * null when the process was killed (timeout) or never spawned. Normalized on
82
- * the timeout path rather than taken from `waitpid`: a group SIGKILL is not
83
- * atomic, so the shell can be scheduled after its foreground child is killed
84
- * and before its own signal lands, reap the child and exit(128+9) itself —
85
- * `close` then reports a normal exit of 137 instead of death by signal
86
- * (roughly 1% of timeouts under load, and never when the shell had exec'd
87
- * away, leaving no shell to reap; findings §10.20). Callers should still
88
- * prefer `timedOut`, which says what happened rather than what it looked
89
- * like, but they no longer have to.
90
- */
91
- exitCode: number | null;
92
- timedOut: boolean;
93
- /** Set when the child could not be spawned at all. */
94
- spawnError?: string;
95
- stdout: string;
96
- stderr: string;
97
- durationMs: number;
98
- }
81
+ /** A finished run (`protocol.ts FinishedRun`, field docs there) and how long it took. */
82
+ export type HookRunResult = FinishedRun & { durationMs: number };
99
83
 
100
84
  export interface HookRunOptions {
101
85
  cwd: string;
86
+ signal?: AbortSignal;
102
87
  /** Seconds, Claude Code convention. Clamped to [1, MAX_TIMEOUT_S]. */
103
88
  timeoutSeconds?: number;
104
89
  /** Exposed to the hook as CLAUDE_PROJECT_DIR; defaults to cwd. */
@@ -129,6 +114,10 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
129
114
  const started = Date.now();
130
115
 
131
116
  return new Promise((resolve) => {
117
+ if (opts.signal?.aborted) {
118
+ resolve({ exitCode: null, timedOut: false, aborted: true, stdout: "", stderr: "", durationMs: Date.now() - started });
119
+ return;
120
+ }
132
121
  let child: ChildProcess;
133
122
  const failed = (spawnError: string) =>
134
123
  resolve({ exitCode: null, timedOut: false, spawnError, stdout: "", stderr: "", durationMs: Date.now() - started });
@@ -175,14 +164,26 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
175
164
  let stdout = "";
176
165
  let stderr = "";
177
166
  let timedOut = false;
167
+ let aborted = false;
168
+ let outputLimitExceeded = false;
169
+ const outputBytes = { stdout: 0, stderr: 0 };
178
170
  let settled = false;
171
+ const onAbort = () => {
172
+ aborted = true;
173
+ killProcessTree(child, "SIGKILL");
174
+ };
175
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
179
176
 
180
177
  const capture = (sink: "stdout" | "stderr") => (chunk: string) => {
181
- const current = sink === "stdout" ? stdout : stderr;
182
- if (current.length >= MAX_OUTPUT_BYTES) return;
183
- const next = current + chunk.slice(0, MAX_OUTPUT_BYTES - current.length);
184
- if (sink === "stdout") stdout = next;
185
- else stderr = next;
178
+ if (outputLimitExceeded) return;
179
+ outputBytes[sink] += Buffer.byteLength(chunk, "utf8");
180
+ if (outputBytes[sink] > MAX_OUTPUT_BYTES) {
181
+ outputLimitExceeded = true;
182
+ killProcessTree(child, "SIGKILL");
183
+ return;
184
+ }
185
+ if (sink === "stdout") stdout += chunk;
186
+ else stderr += chunk;
186
187
  };
187
188
  child.stdout?.setEncoding("utf8");
188
189
  child.stderr?.setEncoding("utf8");
@@ -200,7 +201,8 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
200
201
  if (settled) return;
201
202
  settled = true;
202
203
  clearTimeout(timer);
203
- resolve({ ...result, durationMs: Date.now() - started });
204
+ opts.signal?.removeEventListener("abort", onAbort);
205
+ resolve({ ...result, ...(aborted ? { aborted: true } : {}), ...(outputLimitExceeded ? { outputLimitExceeded: true } : {}), durationMs: Date.now() - started });
204
206
  };
205
207
 
206
208
  // Settles on exit plus a short stdio grace, not on `close`: a grandchild
@@ -211,7 +213,7 @@ export function runHookCommand(command: string, stdinJson: string, opts: HookRun
211
213
  // keeps "we killed it" from ever looking like an ordinary non-zero exit,
212
214
  // which fails OPEN downstream.
213
215
  waitForChildExit(child).then(
214
- ({ code }) => finish({ exitCode: timedOut ? null : code, timedOut, stdout, stderr }),
216
+ ({ code }) => finish({ exitCode: timedOut || aborted || outputLimitExceeded ? null : code, timedOut, stdout, stderr }),
215
217
  (error: Error) => finish({ exitCode: null, timedOut, spawnError: error.message, stdout, stderr }),
216
218
  );
217
219