one-code-extension 0.4.0 → 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 +219 -219
  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 +66 -34
  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 +19 -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
@@ -2,10 +2,12 @@
2
2
  * Claude Code hook protocol (pure): the stdin payload shape, the stdout JSON
3
3
  * envelope, and the mapping from a finished hook run to what One Code should do
4
4
  * about it. Envelope semantics follow Claude Code's hooks reference; the
5
- * fail-closed choices are One Code's own (see docs/decisions.md).
5
+ * fail-closed choices are One Code's own (see working-docs/decisions.md).
6
6
  */
7
7
 
8
8
  import { isAppendedReminderText } from "../lib/reminders.ts";
9
+ import { emptyToolResultText } from "../lib/empty-tool-result.ts";
10
+ import type { SessionBackgroundTask, SessionCron } from "../lib/session-work.ts";
9
11
 
10
12
  /** The contiguous run of reminder/countdown blocks at the tail of a tool result (review M3). */
11
13
  function trailingReminderBlocks<T extends { type: string }>(content: readonly T[]): T[] {
@@ -51,6 +53,10 @@ export interface HookStdinPayload {
51
53
  tool_response?: { content: unknown; is_error: boolean };
52
54
  prompt?: string;
53
55
  stop_hook_active?: boolean;
56
+ /** Stop: in-flight background work (Claude Code's `background_tasks`). */
57
+ background_tasks?: SessionBackgroundTask[];
58
+ /** Stop: jobs that will wake the session later (Claude Code's `session_crons`). */
59
+ session_crons?: SessionCron[];
54
60
  trigger?: "manual" | "auto";
55
61
  /** PreCompact: the `/compact <instructions>` text, empty when none (Claude Code sends the string as is). */
56
62
  custom_instructions?: string;
@@ -97,6 +103,8 @@ export function parseEnvelope(stdout: string): HookEnvelope | undefined {
97
103
  /** What one finished hook run asks One Code to do. */
98
104
  export interface HookOutcome {
99
105
  block?: { reason: string };
106
+ /** Stop continue:false ends the turn rather than requesting another response. */
107
+ preventContinuation?: boolean;
100
108
  updatedInput?: Record<string, unknown>;
101
109
  updatedToolResult?: unknown;
102
110
  additionalContext?: string;
@@ -104,8 +112,24 @@ export interface HookOutcome {
104
112
  }
105
113
 
106
114
  export interface FinishedRun {
115
+ /**
116
+ * null when the process was killed (timeout) or never spawned. Normalized on
117
+ * the timeout path rather than taken from `waitpid`: a group SIGKILL is not
118
+ * atomic, so the shell can be scheduled after its foreground child is killed
119
+ * and before its own signal lands, reap the child and exit(128+9) itself —
120
+ * `close` then reports a normal exit of 137 instead of death by signal
121
+ * (roughly 1% of timeouts under load, and never when the shell had exec'd
122
+ * away, leaving no shell to reap; findings §10.20). Callers should still
123
+ * prefer `timedOut`, which says what happened rather than what it looked
124
+ * like, but they no longer have to.
125
+ */
107
126
  exitCode: number | null;
108
127
  timedOut: boolean;
128
+ /** The calling turn or child session was cancelled. */
129
+ aborted?: boolean;
130
+ /** Output is incomplete and must not be interpreted as a hook response. */
131
+ outputLimitExceeded?: boolean;
132
+ /** Set when the child could not be spawned at all. */
109
133
  spawnError?: string;
110
134
  stdout: string;
111
135
  stderr: string;
@@ -117,9 +141,19 @@ const TIMEOUT_BLOCKS: ReadonlySet<CcHookEvent> = new Set(["PreToolUse", "UserPro
117
141
  * Events where CC's plain (non-JSON) stdout becomes model-visible context
118
142
  * rather than being discarded.
119
143
  */
120
- const STDOUT_IS_CONTEXT: ReadonlySet<CcHookEvent> = new Set(["UserPromptSubmit", "SessionStart"]);
144
+ const STDOUT_IS_CONTEXT: ReadonlySet<CcHookEvent> = new Set(["UserPromptSubmit", "SessionStart", "PreCompact"]);
145
+
146
+ /** A cancelled run: the gating events block, the rest pass. */
147
+ export function cancelledOutcome(event: CcHookEvent): HookOutcome {
148
+ return TIMEOUT_BLOCKS.has(event) ? { block: { reason: "Hook cancelled" } } : {};
149
+ }
121
150
 
122
151
  export function interpretHookResult(event: CcHookEvent, run: FinishedRun): HookOutcome {
152
+ if (run.aborted) return cancelledOutcome(event);
153
+ if (run.outputLimitExceeded) {
154
+ const reason = "Hook exceeded its output limit; reduce stdout/stderr to at most 1,000,000 bytes per stream.";
155
+ return TIMEOUT_BLOCKS.has(event) ? { block: { reason } } : { systemMessage: reason };
156
+ }
123
157
  // Killed (timeout or otherwise): a null exit code must never read as a
124
158
  // clean allow. Closed for the two events that gate something; open for the
125
159
  // rest — the side effect already happened, or blocking would wedge exit.
@@ -146,6 +180,7 @@ export function interpretHookResult(event: CcHookEvent, run: FinishedRun): HookO
146
180
 
147
181
  if (envelope.continue === false) {
148
182
  outcome.block = { reason: envelope.stopReason?.trim() || "Hook stopped continuation" };
183
+ if (event === "Stop") outcome.preventContinuation = true;
149
184
  }
150
185
  const decision = specific?.permissionDecision;
151
186
  if (decision === "deny" || decision === "ask") {
@@ -191,6 +226,7 @@ interface TextBlock {
191
226
  export function applyPostToolUseOutcome<T extends { type: string }>(
192
227
  content: readonly T[],
193
228
  outcome: Pick<HookOutcome, "block" | "updatedToolResult" | "additionalContext">,
229
+ toolName: string,
194
230
  ): Array<T | TextBlock> | undefined {
195
231
  if (!outcome.block && outcome.updatedToolResult === undefined && !outcome.additionalContext) return undefined;
196
232
  let result: Array<T | TextBlock> = [...content];
@@ -201,7 +237,9 @@ export function applyPostToolUseOutcome<T extends { type: string }>(
201
237
  // tool_result hook before ours). Replacing the content must not drop
202
238
  // them, so keep the trailing run of reminder blocks (review M3).
203
239
  const reminderTail = trailingReminderBlocks(content);
204
- result = [{ type: "text", text: typeof replacement === "string" ? replacement : JSON.stringify(replacement) }, ...reminderTail];
240
+ const text = typeof replacement === "string" ? replacement : JSON.stringify(replacement);
241
+ // The hook's own text: an empty one gets the marker for every tool, read included.
242
+ result = [{ type: "text", text: text.trim() ? text : emptyToolResultText(toolName) }, ...reminderTail];
205
243
  }
206
244
  if (outcome.block) result = [{ type: "text", text: `PostToolUse hook: ${outcome.block.reason}` }, ...result];
207
245
  if (outcome.additionalContext) result = [...result, { type: "text", text: outcome.additionalContext }];
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Hook config discovery and merge (pure fs reads, no pi imports).
3
3
  *
4
- * Sources, lowest authority first: user ~/.claude/settings.json, managed
4
+ * Sources, in collection order: user ~/.claude/settings.json, managed
5
5
  * settings (Claude Code's platform paths), project .claude/settings.json,
6
- * project .claude/settings.local.json. Project and local sources are
6
+ * project .claude/settings.local.json (independent mode: One Code's user and
7
+ * per-repo settings, `hookSettingsPaths`). Project and local sources are
7
8
  * *returned flagged, not filtered* — whether they run is a trust decision
8
9
  * (hooks are arbitrary code execution) that index.ts applies via trust.ts;
9
10
  * this module stays pure.
@@ -14,7 +15,10 @@
14
15
  */
15
16
 
16
17
  import { readFileSync, statSync } from "node:fs";
18
+ import os from "node:os";
17
19
  import { join } from "node:path";
20
+ import { managedSettingsPaths, settingsSources } from "../lib/claude-settings.ts";
21
+ import { claudeSourcesOn } from "../lib/config-mode.ts";
18
22
  import { CC_HOOK_EVENTS, type CcHookEvent } from "./protocol.ts";
19
23
 
20
24
  /** Claude Code's per-hook `shell` field: which interpreter runs the command string. */
@@ -50,21 +54,23 @@ export interface HooksSource {
50
54
 
51
55
  export interface LoadedHooks {
52
56
  sources: HooksSource[];
57
+ /** Configured sources are filtered; collectors must also suppress plugin hooks when set. */
58
+ disabled?: "all" | "unmanaged";
53
59
  /** Malformed entries are skipped and reported, never fatal. */
54
60
  diagnostics: string[];
55
61
  }
56
62
 
57
- /** Managed-settings locations — Claude Code's exact per-platform paths. */
58
- export function managedSettingsPath(platform: NodeJS.Platform = process.platform): string {
59
- if (platform === "darwin") return "/Library/Application Support/ClaudeCode/managed-settings.json";
60
- if (platform === "win32") return "C:\\ProgramData\\ClaudeCode\\managed-settings.json";
61
- return "/etc/claude-code/managed-settings.json";
62
- }
63
-
64
- export function hookSettingsPaths(claudeDir: string, cwd: string): Array<{ scope: HookScope; path: string }> {
63
+ /**
64
+ * The settings files hooks are read from, in collection order. Independent
65
+ * mode (lib/config-mode.ts) reads One Code's user file and its per-repo file
66
+ * under `~/.onecode/projects/<slug>/` instead; neither ships in a repository,
67
+ * so both are user scope and need no project-trust prompt.
68
+ */
69
+ export function hookSettingsPaths(claudeDir: string, cwd: string, home: string = os.homedir()): Array<{ scope: HookScope; path: string }> {
70
+ if (!claudeSourcesOn()) return settingsSources(cwd, home).map(([, path]) => ({ scope: "user" as const, path }));
65
71
  return [
66
72
  { scope: "user", path: join(claudeDir, "settings.json") },
67
- { scope: "managed", path: managedSettingsPath() },
73
+ ...managedSettingsPaths().map((path) => ({ scope: "managed" as const, path })),
68
74
  { scope: "project", path: join(cwd, ".claude", "settings.json") },
69
75
  { scope: "local", path: join(cwd, ".claude", "settings.local.json") },
70
76
  ];
@@ -130,13 +136,14 @@ export function parseHooksBlock(raw: unknown, origin: string, diagnostics: strin
130
136
  interface CacheEntry {
131
137
  mtimeMs: number;
132
138
  config: HooksFileConfig;
139
+ disableAllHooks?: boolean;
133
140
  diagnostics: string[];
134
141
  }
135
142
 
136
143
  const cache = new Map<string, CacheEntry>();
137
144
 
138
- /** Read one settings file's hooks block, via the mtime cache. */
139
- function readHooksFile(path: string, diagnostics: string[]): HooksFileConfig | undefined {
145
+ /** Read one settings file's hooks and disabling flag, via the mtime cache. */
146
+ function readHooksFile(path: string, diagnostics: string[]): CacheEntry | undefined {
140
147
  let mtimeMs: number;
141
148
  try {
142
149
  mtimeMs = statSync(path).mtimeMs;
@@ -147,27 +154,51 @@ function readHooksFile(path: string, diagnostics: string[]): HooksFileConfig | u
147
154
  const cached = cache.get(path);
148
155
  if (cached && cached.mtimeMs === mtimeMs) {
149
156
  diagnostics.push(...cached.diagnostics);
150
- return cached.config;
157
+ return cached;
151
158
  }
152
159
  const fileDiagnostics: string[] = [];
153
160
  let config: HooksFileConfig = {};
161
+ let disableAllHooks: boolean | undefined;
154
162
  try {
155
- const parsed = JSON.parse(readFileSync(path, "utf-8")) as { hooks?: unknown };
163
+ const parsed = JSON.parse(readFileSync(path, "utf-8")) as { hooks?: unknown; disableAllHooks?: unknown };
156
164
  config = parseHooksBlock(parsed.hooks, path, fileDiagnostics);
165
+ if (typeof parsed.disableAllHooks === "boolean") disableAllHooks = parsed.disableAllHooks;
157
166
  } catch (error) {
158
167
  fileDiagnostics.push(`${path}: unreadable settings file skipped (${error instanceof Error ? error.message : error})`);
159
168
  }
160
- cache.set(path, { mtimeMs, config, diagnostics: fileDiagnostics });
169
+ const entry: CacheEntry = { mtimeMs, config, disableAllHooks, diagnostics: fileDiagnostics };
170
+ cache.set(path, entry);
161
171
  diagnostics.push(...fileDiagnostics);
162
- return config;
172
+ return entry;
163
173
  }
164
174
 
165
175
  export function loadHookSettings(claudeDir: string, cwd: string): LoadedHooks {
166
176
  const diagnostics: string[] = [];
167
177
  const sources: HooksSource[] = [];
178
+ let userDisableAllHooks: boolean | undefined;
179
+ let managedDisableAllHooks: boolean | undefined;
180
+ let repoDisableAllHooks: boolean | undefined;
168
181
  for (const { scope, path } of hookSettingsPaths(claudeDir, cwd)) {
169
- const config = readHooksFile(path, diagnostics);
170
- if (config && Object.keys(config).length > 0) sources.push({ scope, path, config });
182
+ const entry = readHooksFile(path, diagnostics);
183
+ if (!entry) continue;
184
+ if (entry.disableAllHooks !== undefined) {
185
+ if (scope === "managed") managedDisableAllHooks = entry.disableAllHooks;
186
+ else if (scope === "user") userDisableAllHooks = entry.disableAllHooks;
187
+ else repoDisableAllHooks = entry.disableAllHooks;
188
+ }
189
+ if (Object.keys(entry.config).length > 0) sources.push({ scope, path, config: entry.config });
190
+ }
191
+ // Collection order stays stable, but managed booleans have highest precedence.
192
+ // A non-managed disable must not turn off organization-managed hooks.
193
+ if (managedDisableAllHooks === true) return { sources: [], diagnostics, disabled: "all" };
194
+ if ((managedDisableAllHooks ?? userDisableAllHooks) === true) {
195
+ return { sources: sources.filter((source) => source.scope === "managed"), diagnostics, disabled: "unmanaged" };
196
+ }
197
+ // A repository's flag reaches only its own hooks: a cloned repo must not
198
+ // switch off the user's guard hooks, as project settings never set autoMode.
199
+ if (managedDisableAllHooks === undefined && repoDisableAllHooks === true) {
200
+ diagnostics.push("disableAllHooks in project settings turns off only the project's own hooks");
201
+ return { sources: sources.filter((source) => source.scope === "user" || source.scope === "managed"), diagnostics };
171
202
  }
172
203
  return { sources, diagnostics };
173
204
  }
@@ -33,6 +33,8 @@ export interface ChildHookCall {
33
33
  input: Record<string, unknown>;
34
34
  /** The child's runtime cwd (a worktree, if isolated). */
35
35
  cwd: string;
36
+ /** The child's turn signal; cancelling it must stop its hooks, not the parent's. */
37
+ signal?: AbortSignal;
36
38
  /** The child session's id — becomes the payload's `session_id` and `agent_id`. */
37
39
  sessionId?: string;
38
40
  /** The child's session file, if persisted (payload `transcript_path`). */
@@ -17,9 +17,9 @@
17
17
 
18
18
  import { createHash } from "node:crypto";
19
19
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
- import { dirname, join } from "node:path";
20
+ import { dirname } from "node:path";
21
21
  import { boundConsentItems } from "../lib/consent-preview.ts";
22
- import { oneCodeStateDir } from "../lib/paths.ts";
22
+ import { consentStorePath } from "../lib/consent-stores.ts";
23
23
  import type { HooksSource } from "./settings.ts";
24
24
 
25
25
  interface ApprovalStore {
@@ -28,7 +28,7 @@ interface ApprovalStore {
28
28
  }
29
29
 
30
30
  export function approvalStorePath(): string {
31
- return join(oneCodeStateDir(), "hooks", "project-approvals.json");
31
+ return consentStorePath("hooks");
32
32
  }
33
33
 
34
34
  /** Key order must not change the hash — two spellings of one config are one consent. */
@@ -98,6 +98,7 @@ export interface TrustDecisionDeps {
98
98
  confirm: (title: string, message: string) => Promise<boolean | undefined>;
99
99
  notify: (message: string) => void;
100
100
  storePath?: string;
101
+ signal?: AbortSignal;
101
102
  /**
102
103
  * Answer from the remembered/stored approval only: neither prompt nor record
103
104
  * a decision. For a dispatch at session shutdown — recording "declined" there
@@ -108,7 +109,8 @@ export interface TrustDecisionDeps {
108
109
 
109
110
  /** Process-lifetime memory of declined/approved hashes per project, so a dispatch storm asks once. */
110
111
  const sessionDecisions = new Map<string, boolean>();
111
- const pendingPrompts = new Map<string, Promise<boolean>>();
112
+ /** Undefined: the dispatch that opened the prompt aborted, so nobody decided. */
113
+ const pendingPrompts = new Map<string, Promise<boolean | undefined>>();
112
114
 
113
115
  /**
114
116
  * Whether the project/local hook sources may run. Resolves without prompting
@@ -120,6 +122,7 @@ export async function projectHooksApproved(
120
122
  projectSources: HooksSource[],
121
123
  deps: TrustDecisionDeps,
122
124
  ): Promise<boolean> {
125
+ if (deps.signal?.aborted) return false;
123
126
  if (projectSources.length === 0) return true;
124
127
  const configHash = hashProjectHooks(projectSources);
125
128
  const key = `${projectRoot}:${configHash}`;
@@ -145,6 +148,8 @@ export async function projectHooksApproved(
145
148
  "Run this project's hooks?",
146
149
  `This project's .claude settings define ${describeProjectHooks(projectSources)}\n\nThey run as shell commands on your machine. Approval is remembered until the hook config changes.`,
147
150
  )) === true;
151
+ // An interrupted dialog is no trust decision, even if a reply raced abort.
152
+ if (deps.signal?.aborted) return undefined;
148
153
  sessionDecisions.set(key, approved);
149
154
  if (approved) persistApproval(projectRoot, configHash, deps.storePath);
150
155
  else deps.notify("Project hooks disabled for this session (user/managed hooks still run).");
@@ -152,7 +157,10 @@ export async function projectHooksApproved(
152
157
  })().finally(() => pendingPrompts.delete(key));
153
158
  pendingPrompts.set(key, pending);
154
159
  }
155
- return pending;
160
+ const decided = await pending;
161
+ if (deps.signal?.aborted) return false;
162
+ // The opener aborted without an answer: a dispatch still live asks again.
163
+ return decided ?? projectHooksApproved(projectRoot, projectSources, deps);
156
164
  }
157
165
 
158
166
  /** Test seam. */
@@ -0,0 +1,119 @@
1
+ /**
2
+ * idle-compact extension — Claude Code's compaction of an idle session before
3
+ * its one-hour prompt cache expires (rules and refusal order: policy.ts).
4
+ *
5
+ * Wiring: the timer follows the compaction extension's capture of the
6
+ * session's last request (`LAST_REQUEST_CHANNEL`), the very request its
7
+ * cache-reading replay sends. Each capture re-arms it when the body caches for
8
+ * one hour (`hasOneHourCache`); a cleared capture (a new session, a compaction,
9
+ * a branch switch) drops it, so the timer is armed only while a replay is
10
+ * possible. The reply's usage says whether the cache was warm, the response
11
+ * headers carry Anthropic's rate-limit status, and keystrokes
12
+ * (`onTerminalInput`) mark the user present.
13
+ *
14
+ * TUI only: RPC mode also runs the one-hour cache, but its host's input never
15
+ * reaches `onTerminalInput`, so the presence check could not hold a
16
+ * compaction off while someone is there.
17
+ *
18
+ * On fire it calls `ctx.compact()`; pi's TUI shows its usual "Compacting"
19
+ * indicator, Esc cancels, and on success a display-only entry carries Claude
20
+ * Code's notice. Failures are silent, as in Claude Code.
21
+ */
22
+
23
+ import { type ExtensionAPI, type ExtensionContext, getAgentDir, SettingsManager } from "@earendil-works/pi-coding-agent";
24
+ import { hasOneHourCache } from "../lib/anthropic-payload.ts";
25
+ import { modelSpec } from "../lib/model-policy.ts";
26
+ import { LAST_REQUEST_CHANNEL, type RequestCapture } from "../lib/request-replay.ts";
27
+ import { unrefTimers } from "../lib/timer-ops.ts";
28
+ import { dimMarkedLine, liveUiCtx } from "../lib/tui-render.ts";
29
+ import { type ArmedRequest, IDLE_COMPACT_NOTICE, IdleCompactTimer, type IdleCompactConfig, idleCompactConfig, idleRefusal } from "./policy.ts";
30
+
31
+ const ENTRY_TYPE = "one-code:idle-compact";
32
+ const NOTICE_MARK = "●";
33
+ const RATE_LIMIT_HEADER = "anthropic-ratelimit-unified-status";
34
+
35
+ /** pi's auto-compaction setting; unreadable settings mean pi's default, on. */
36
+ function autoCompactionOn(cwd: string): boolean {
37
+ try {
38
+ return SettingsManager.create(cwd, getAgentDir()).getCompactionEnabled();
39
+ } catch {
40
+ return true;
41
+ }
42
+ }
43
+
44
+ export default function idleCompactExtension(pi: ExtensionAPI) {
45
+ pi.registerEntryRenderer(ENTRY_TYPE, (_entry, _options, theme) => dimMarkedLine(theme, NOTICE_MARK, IDLE_COMPACT_NOTICE));
46
+
47
+ let sessionCtx: ExtensionContext | undefined;
48
+ let warm = false;
49
+ let rateLimitStatus: string | undefined;
50
+ let lastInteractionAt: number | undefined;
51
+ let inputHookRegistered = false;
52
+
53
+ const timer = new IdleCompactTimer(unrefTimers, Date.now, () => idleCompactConfig(process.env), (armed, dueAt, config) => fire(armed, dueAt, config));
54
+
55
+ function fire(armed: ArmedRequest, dueAt: number, config: IdleCompactConfig) {
56
+ const ctx = liveUiCtx(sessionCtx);
57
+ if (!ctx) return;
58
+ const refusal = idleRefusal(armed, {
59
+ now: Date.now(),
60
+ dueAt,
61
+ config,
62
+ compactionOn: process.env.CC_COMPACTION !== "0" && autoCompactionOn(ctx.cwd),
63
+ model: ctx.model ? modelSpec(ctx.model) : undefined,
64
+ thinking: pi.getThinkingLevel(),
65
+ warm,
66
+ contextTokens: ctx.getContextUsage()?.tokens ?? 0,
67
+ rateLimitStatus,
68
+ idle: ctx.isIdle() && !ctx.hasPendingMessages(),
69
+ lastInteractionAt,
70
+ });
71
+ if (refusal !== null) return;
72
+ ctx.compact({
73
+ onComplete: () => pi.appendEntry(ENTRY_TYPE),
74
+ // Claude Code logs a failed idle compaction and shows nothing; pi's TUI
75
+ // still reports an error or an Esc cancel, as for any compaction.
76
+ onError: () => {},
77
+ });
78
+ }
79
+
80
+ pi.events.on(LAST_REQUEST_CHANNEL, (data) => {
81
+ const capture = data as RequestCapture | undefined;
82
+ const ctx = liveUiCtx(sessionCtx);
83
+ warm = false;
84
+ if (!capture || ctx?.mode !== "tui" || !hasOneHourCache(capture.payload)) {
85
+ timer.cancel();
86
+ return;
87
+ }
88
+ timer.arm({ at: Date.now(), model: modelSpec({ provider: capture.provider, id: capture.modelId }), thinking: pi.getThinkingLevel() });
89
+ });
90
+
91
+ pi.on("after_provider_response", (event) => {
92
+ if (!timer.pending) return;
93
+ const entry = Object.entries(event.headers ?? {}).find(([name]) => name.toLowerCase() === RATE_LIMIT_HEADER);
94
+ rateLimitStatus = entry?.[1];
95
+ });
96
+
97
+ pi.on("message_end", (event) => {
98
+ const message = event.message as { role?: string; usage?: { cacheRead?: number; cacheWrite?: number } };
99
+ if (message.role === "assistant") warm = (message.usage?.cacheRead ?? 0) + (message.usage?.cacheWrite ?? 0) > 0;
100
+ });
101
+
102
+ pi.on("session_start", (_event, ctx) => {
103
+ sessionCtx = ctx;
104
+ rateLimitStatus = undefined;
105
+ lastInteractionAt = undefined;
106
+ if (!ctx.hasUI || inputHookRegistered) return;
107
+ // Registered once: session_start fires again on every /clear and switch.
108
+ inputHookRegistered = true;
109
+ ctx.ui.onTerminalInput(() => {
110
+ lastInteractionAt = Date.now();
111
+ return undefined; // observe only
112
+ });
113
+ });
114
+
115
+ pi.on("session_shutdown", () => {
116
+ timer.cancel();
117
+ sessionCtx = undefined;
118
+ });
119
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Idle compaction's rules, pure (no pi imports; index.ts is the wiring).
3
+ *
4
+ * Claude Code 2.1.289 compacts an idle main session shortly before its
5
+ * one-hour prompt cache expires: the compaction reads the warm cache at the
6
+ * cached rate, where the user's next prompt after expiry would write the whole
7
+ * context to the cache again. Its rules, matched here:
8
+ * - a timer is armed after every main-session request whose cache carries a
9
+ * one-hour TTL, for 90% of the hour (`fireAtFraction`), so 54 minutes;
10
+ * - when it fires, the first refusal below that applies wins, in Claude
11
+ * Code's order: off, compaction off, prefix changed, lapsed (fired more
12
+ * than 60 s late, or the cache is no longer warm), small (under 200,000
13
+ * context tokens), near the rate limit; then the host's: busy (a turn
14
+ * running or messages queued), present (a keystroke in the last 60 s);
15
+ * - otherwise it compacts with no instructions and shows
16
+ * "Compacted while idle, before the prompt cache expired".
17
+ * Claude Code's `not_one_hour` and `newer_request` refusals need no check
18
+ * here: every request replaces the timer, and only a one-hour request arms one.
19
+ *
20
+ * Claude Code ships it behind a server flag that defaults off; One Code turns
21
+ * it on, with `CC_IDLE_COMPACT=0` to opt out (working-docs/decisions/caching.md,
22
+ * "Idle compaction before the cache expires").
23
+ */
24
+
25
+ import type { TimerOps } from "../lib/timer-ops.ts";
26
+
27
+ /** The notice Claude Code appends after an idle compaction. */
28
+ export const IDLE_COMPACT_NOTICE = "Compacted while idle, before the prompt cache expired";
29
+
30
+ /** The one-hour cache lifetime idle compaction is armed for. */
31
+ export const ONE_HOUR_MS = 60 * 60_000;
32
+ /** Claude Code's defaults (`minTokens` 200,000, `fireAtFraction` 0.9). */
33
+ export const DEFAULT_MIN_TOKENS = 200_000;
34
+ export const DEFAULT_FIRE_AT_FRACTION = 0.9;
35
+ /** A timer that fires later than this may find the cache gone (the machine slept). */
36
+ export const LATE_MS = 60_000;
37
+ /** A keystroke this recent means the user is present: no compaction under them. */
38
+ export const PRESENCE_MS = 60_000;
39
+
40
+ export interface IdleCompactConfig {
41
+ enabled: boolean;
42
+ minTokens: number;
43
+ /** Delay from the request to the fire; undefined means `fireAtFraction` of the hour. */
44
+ delayMs?: number;
45
+ }
46
+
47
+ /**
48
+ * The config from the environment: `CC_IDLE_COMPACT=0` turns it off;
49
+ * `CC_IDLE_COMPACT_MIN_TOKENS` and `CC_IDLE_COMPACT_DELAY_MS` override the
50
+ * threshold and the fire delay, for live testing (the delay stays under the
51
+ * hour, at least one second).
52
+ */
53
+ export function idleCompactConfig(env: Record<string, string | undefined>): IdleCompactConfig {
54
+ const minTokens = Number(env.CC_IDLE_COMPACT_MIN_TOKENS);
55
+ const delayMs = Number(env.CC_IDLE_COMPACT_DELAY_MS);
56
+ return {
57
+ enabled: env.CC_IDLE_COMPACT !== "0",
58
+ minTokens: Number.isFinite(minTokens) && minTokens >= 1 ? minTokens : DEFAULT_MIN_TOKENS,
59
+ ...(Number.isFinite(delayMs) && delayMs >= 1000 && delayMs < ONE_HOUR_MS ? { delayMs } : {}),
60
+ };
61
+ }
62
+
63
+ /** Why a fire did not compact. */
64
+ export type IdleRefusal = "disabled" | "compaction_off" | "prefix_changed" | "lapsed" | "small" | "near_limit" | "busy" | "present";
65
+
66
+ /** The one-hour main-session request a timer was armed for. */
67
+ export interface ArmedRequest {
68
+ /** When it went out (ms). */
69
+ at: number;
70
+ /** `provider/id` and thinking level it ran with: the cache's identity. */
71
+ model: string | undefined;
72
+ thinking: string | undefined;
73
+ }
74
+
75
+ /** What the session looks like when the timer fires. */
76
+ export interface FireProbe {
77
+ now: number;
78
+ /** When the timer was due. */
79
+ dueAt: number;
80
+ config: IdleCompactConfig;
81
+ /** pi's auto-compaction setting, and One Code's cache-reading compaction (CC_COMPACTION). */
82
+ compactionOn: boolean;
83
+ model: string | undefined;
84
+ thinking: string | undefined;
85
+ /** The armed request's reply reported cache reads or writes. */
86
+ warm: boolean;
87
+ contextTokens: number;
88
+ /** Anthropic's `anthropic-ratelimit-unified-status` from the latest response, if any. */
89
+ rateLimitStatus: string | undefined;
90
+ idle: boolean;
91
+ lastInteractionAt: number | undefined;
92
+ }
93
+
94
+ /** The first refusal that applies, in Claude Code's order, or null to compact. */
95
+ export function idleRefusal(armed: ArmedRequest, probe: FireProbe): IdleRefusal | null {
96
+ if (!probe.config.enabled) return "disabled";
97
+ if (!probe.compactionOn) return "compaction_off";
98
+ if (probe.model !== armed.model || probe.thinking !== armed.thinking) return "prefix_changed";
99
+ // The expiry half only trips under a CC_IDLE_COMPACT_DELAY_MS near the hour:
100
+ // at the default 54 minutes the lateness check trips first.
101
+ if (probe.now - probe.dueAt > LATE_MS || !probe.warm || probe.now >= armed.at + ONE_HOUR_MS) return "lapsed";
102
+ if (probe.contextTokens < probe.config.minTokens) return "small";
103
+ if (probe.rateLimitStatus !== undefined && probe.rateLimitStatus !== "allowed") return "near_limit";
104
+ if (!probe.idle) return "busy";
105
+ if (probe.lastInteractionAt !== undefined && probe.now - probe.lastInteractionAt < PRESENCE_MS) return "present";
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * One pending timer for the latest one-hour request. Each `arm` replaces it;
111
+ * `cancel()` drops it when the session's last request stops being a one-hour
112
+ * one, or there is none (a new session, a compaction, a branch switch).
113
+ */
114
+ export class IdleCompactTimer {
115
+ private handle: unknown;
116
+
117
+ constructor(
118
+ private readonly timer: TimerOps,
119
+ private readonly now: () => number,
120
+ private readonly config: () => IdleCompactConfig,
121
+ private readonly onFire: (armed: ArmedRequest, dueAt: number, config: IdleCompactConfig) => void,
122
+ ) {}
123
+
124
+ arm(request: ArmedRequest): void {
125
+ this.cancel();
126
+ const config = this.config();
127
+ if (!config.enabled) return;
128
+ const dueAt = request.at + (config.delayMs ?? ONE_HOUR_MS * DEFAULT_FIRE_AT_FRACTION);
129
+ this.handle = this.timer.set(() => {
130
+ this.handle = undefined;
131
+ this.onFire(request, dueAt, config);
132
+ }, Math.max(0, dueAt - this.now()));
133
+ }
134
+
135
+ cancel(): void {
136
+ if (this.handle !== undefined) this.timer.clear(this.handle);
137
+ this.handle = undefined;
138
+ }
139
+
140
+ get pending(): boolean {
141
+ return this.handle !== undefined;
142
+ }
143
+ }
@@ -1,26 +1,27 @@
1
1
  /**
2
2
  * init extension — Claude Code's `/init`.
3
3
  *
4
- * `/init` scans the repo and writes a concise CLAUDE.md for future sessions. It
5
- * is a prompt template, not procedural code: the handler submits INIT_PROMPT as
6
- * a user turn (pi.sendUserMessage) and the model does the work with the ordinary
7
- * tool set — subagent survey, ask_user_question, write. This mirrors how Claude
8
- * Code's `/init` is just a canned prompt.
4
+ * `/init` scans the repo and writes a CLAUDE.md for future sessions. It is a
5
+ * prompt template, not procedural code: the handler submits INIT_PROMPT
6
+ * (Claude Code's classic prompt) as a user turn (pi.sendUserMessage) and the
7
+ * model does the work with the ordinary tool set, as Claude Code's `/init`
8
+ * does.
9
9
  *
10
10
  * pi has no built-in `/init`, so a Claude Code user typing it would otherwise
11
11
  * have the word go to the model as a chat message.
12
12
  */
13
13
 
14
14
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
+ import { createUserMessageSender } from "../lib/notifications.ts";
16
+ import { promptCommandText } from "../skill/invoke.ts";
15
17
  import { INIT_PROMPT } from "./prompt.ts";
16
18
 
17
19
  export default function initExtension(pi: ExtensionAPI) {
20
+ const sendUserMessage = createUserMessageSender(pi);
18
21
  pi.registerCommand("init", {
19
- description: "Analyze the codebase and set up a CLAUDE.md for future sessions",
20
- // sendUserMessage always triggers a turn and returns void; the async
21
- // handler satisfies RegisteredCommand's Promise<void> contract.
22
- handler: async () => {
23
- pi.sendUserMessage(INIT_PROMPT, { deliverAs: "followUp" });
22
+ description: "Initialize a new CLAUDE.md file with codebase documentation",
23
+ handler: async (_args, ctx) => {
24
+ await sendUserMessage(ctx, promptCommandText("init", "", INIT_PROMPT));
24
25
  },
25
26
  });
26
27
  }