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
@@ -1,198 +1,56 @@
1
1
  /**
2
- * Pure state + rendering for the background-shells side of the below-editor
3
- * panel — Claude Code's shell manager. Three stages, entered with the FIRST
4
- * down-arrow (a second ↓ moves on to the agent rows):
2
+ * Pure state + rendering for the background-shells chip in the below-editor
3
+ * panel, Claude Code's shell pill. The FIRST down-arrow focuses it (a second ↓
4
+ * moves on to the agent rows), and Enter opens the Background dialog that
5
+ * `/tasks` opens too (tasks-dialog.ts), where shells and agents are listed
6
+ * together:
5
7
  *
6
- * chip "2 shells · ↓ to manage" → focused: "[2 shells] · Enter to view tasks"
7
- * list the Background panel: selectable command rows, x stops, Esc backs out
8
- * details one shell: Status/Runtime/Command + a live bordered output box
8
+ * "2 shells · ↓ to manage" → focused: "[2 shells] · Enter to view tasks"
9
9
  *
10
10
  * Task objects arrive over TASK_REGISTER_CHANNEL (lib/shell-tasks.ts) and stay
11
- * live — output()/status are read at render time. Key wiring lives in
12
- * index.ts; this file owns layout and the focus reducer only.
11
+ * live; status is read at render time. Key wiring lives in index.ts; this file
12
+ * owns layout and the focus reducer only.
13
13
  */
14
14
 
15
15
  import type { BackgroundTask } from "../background/registry.ts";
16
- import { countNoun, cutPlainText as cut, formatDuration, padPlainText } from "../lib/tui-render.ts";
16
+ import { countNoun, cutPlainText as cut } from "../lib/tui-render.ts";
17
17
  import type { StripKey } from "./panel-keys.ts";
18
- import type { Paint } from "./panel-render.ts";
18
+ import { type Paint, STRIP_LINGER_MS } from "./panel-render.ts";
19
19
 
20
20
  export interface ShellPaint extends Paint {
21
21
  inverse(text: string): string;
22
22
  }
23
23
 
24
- export type ShellFocus =
25
- | { stage: "chip" }
26
- | { stage: "list"; selectedId: string }
27
- | { stage: "details"; selectedId: string };
28
-
29
- /** A finished shell stays listed this long, so its terminal status is seen. */
30
- export const SHELL_LIST_LINGER_MS = 60_000;
31
-
32
- /** Output-box height in the details view (content rows, borders excluded). */
33
- export const OUTPUT_BOX_ROWS = 10;
34
-
35
- /** Most list rows shown at once; the selection slides a window over the rest. */
36
- export const MAX_SHELL_LIST_ROWS = 8;
37
-
38
- /** Only this much of the spool is split per frame — the box shows 10 lines. */
39
- const OUTPUT_TAIL_CHARS = 8192;
40
-
41
- /** The list rows: running shells first, newest-first within each group. */
42
- export function shellRows(tasks: BackgroundTask[], now: number): BackgroundTask[] {
43
- return tasks
44
- .filter((t) => t.status === "running" || (t.finishedAt ?? now) > now - SHELL_LIST_LINGER_MS)
45
- .sort((a, b) => Number(b.status === "running") - Number(a.status === "running") || b.startedAt - a.startedAt);
46
- }
47
-
48
24
  /**
49
- * Re-anchor a focus whose selected shell left the rows (lingered out or never
50
- * existed): fall back to the first row, or to the chip when the list emptied.
25
+ * The shells the chip counts: running ones, and finished ones for as long as a
26
+ * finished agent keeps its strip row (STRIP_LINGER_MS).
51
27
  */
52
- export function anchorShellFocus(focus: ShellFocus, ids: string[]): ShellFocus {
53
- if (focus.stage === "chip" || ids.includes(focus.selectedId)) return focus;
54
- return ids.length ? { ...focus, selectedId: ids[0] } : { stage: "chip" };
55
- }
56
-
57
- export interface ShellTransition {
58
- /** The next focus; undefined closes the shell section entirely. */
59
- focus: ShellFocus | undefined;
60
- /** stopSelected: stop the selected task; toAgents: hand ↓ on to the agent
61
- * rows; passthrough: unhandled byte — drop focus and let the editor have it. */
62
- effect?: "stopSelected" | "toAgents" | "passthrough";
63
- }
64
-
65
- /**
66
- * The focus reducer — Claude Code's shell-manager keys. `ids` is the current
67
- * row order (shellRows), `focus` is already anchored against it.
68
- */
69
- export function reduceShellKey(focus: ShellFocus, key: StripKey | undefined, ids: string[]): ShellTransition {
70
- const index = focus.stage === "chip" ? -1 : ids.indexOf(focus.selectedId);
71
- switch (focus.stage) {
72
- case "chip":
73
- if (key === "down") return { focus: undefined, effect: "toAgents" };
74
- if (key === "open") return { focus: ids.length ? { stage: "list", selectedId: ids[0] } : focus };
75
- if (key === "up" || key === "leave") return { focus: undefined };
76
- return { focus: undefined, effect: "passthrough" };
77
- case "list":
78
- if (key === "up") return { focus: index > 0 ? { stage: "list", selectedId: ids[index - 1] } : focus };
79
- if (key === "down")
80
- return { focus: index < ids.length - 1 ? { stage: "list", selectedId: ids[index + 1] } : focus };
81
- if (key === "open") return { focus: { stage: "details", selectedId: focus.selectedId } };
82
- if (key === "stop") return { focus, effect: "stopSelected" };
83
- if (key === "leave" || key === "left") return { focus: { stage: "chip" } };
84
- if (key === "pageUp" || key === "pageDown") return { focus };
85
- return { focus: undefined, effect: "passthrough" };
86
- case "details":
87
- if (key === "left") return { focus: { stage: "list", selectedId: focus.selectedId } };
88
- if (key === "leave" || key === "open" || key === "space") return { focus: undefined };
89
- if (key === "stop") return { focus, effect: "stopSelected" };
90
- if (key === "up" || key === "down" || key === "pageUp" || key === "pageDown") return { focus };
91
- return { focus: undefined, effect: "passthrough" };
92
- }
93
- }
94
-
95
- export interface ShellSectionInput {
96
- /** shellRows()-ordered. */
97
- rows: BackgroundTask[];
98
- runningCount: number;
99
- focus: ShellFocus | undefined;
100
- width: number;
101
- now: number;
28
+ export function shellRows(tasks: BackgroundTask[], now: number): BackgroundTask[] {
29
+ return tasks.filter((t) => t.status === "running" || (t.finishedAt !== undefined && t.finishedAt > now - STRIP_LINGER_MS));
102
30
  }
103
31
 
104
32
  /**
105
- * Whether the section renders at all (drives widget visibility and ↓ entry).
106
- * Keyed on the ROW count, not the running count, so a finished shell's chip
107
- * stays reachable for the linger window — otherwise its outcome would only be
108
- * visible if the panel happened to be open when it finished.
33
+ * What a key does on the focused chip. Every key drops the chip's focus;
34
+ * openTasks opens the Background dialog, toAgents hands ↓ on to the agent rows,
35
+ * passthrough lets the editor have an unhandled byte, and undefined (↑, Esc)
36
+ * only leaves the chip.
109
37
  */
110
- export function shellSectionVisible(rowCount: number, focus: ShellFocus | undefined): boolean {
111
- return rowCount > 0 || focus !== undefined;
112
- }
113
-
114
- const STATUS_COLOR: Record<string, string> = { running: "accent", failed: "error", stopped: "dim" };
115
-
116
- /** The section's lines at the current stage. Caller checks visibility first. */
117
- export function renderShellSection(input: ShellSectionInput, paint: ShellPaint): string[] {
118
- const width = Math.max(20, input.width);
119
- const focus = input.focus;
120
- // The label counts ROWS (running + lingering finished), matching what the
121
- // panel actually shows — visibility keys on rows too, so a finished shell's
122
- // chip must not read "0 shells" while it is still openable.
123
- if (!focus) return [paint.fg("dim", cut(`${countNoun(input.rows.length, "shell")} · ↓ to manage`, width))];
124
- if (focus.stage === "chip") {
125
- // Cut the plain parts BEFORE painting — cutting painted text breaks escapes.
126
- const label = cut(` ${countNoun(input.rows.length, "shell")} `, width);
127
- const hint = cut("· Enter to view tasks", Math.max(0, width - label.length - 1));
128
- return [`${paint.inverse(label)}${hint ? ` ${paint.fg("dim", hint)}` : ""}`];
129
- }
130
- if (focus.stage === "list") return renderShellList(input, focus.selectedId, paint, width);
131
- return renderShellDetails(input, focus.selectedId, paint, width);
132
- }
133
-
134
- function renderShellList(input: ShellSectionInput, selectedId: string, paint: ShellPaint, width: number): string[] {
135
- const out: string[] = [];
136
- out.push(paint.fg("accent", paint.bold("Background")));
137
- out.push(cut(countNoun(input.runningCount, "active shell"), width));
138
- out.push("");
139
- // A window of MAX_SHELL_LIST_ROWS slid to keep the selection visible, so a
140
- // big shell fan-out cannot grow the panel past the terminal.
141
- const selectedIndex = Math.max(0, input.rows.findIndex((t) => t.id === selectedId));
142
- const start = Math.min(Math.max(0, selectedIndex - MAX_SHELL_LIST_ROWS + 1), Math.max(0, input.rows.length - MAX_SHELL_LIST_ROWS));
143
- const shown = input.rows.slice(start, start + MAX_SHELL_LIST_ROWS);
144
- if (start > 0) out.push(paint.fg("dim", ` +${start} more above`));
145
- for (const task of shown) {
146
- const marker = task.id === selectedId ? "❯ " : " ";
147
- const status = ` (${task.status})`;
148
- const command = cut(task.command ?? task.description, Math.max(1, width - marker.length - status.length));
149
- out.push(`${marker}${command}${paint.fg(STATUS_COLOR[task.status] ?? "dim", status)}`);
150
- }
151
- const below = input.rows.length - start - shown.length;
152
- if (below > 0) out.push(paint.fg("dim", ` +${below} more below`));
153
- if (!input.rows.length) out.push(paint.fg("dim", " no shells"));
154
- out.push("");
155
- out.push(paint.fg("dim", cut("↑/↓ to select · Enter to view · x to stop · Esc to close", width)));
156
- return out;
157
- }
158
-
159
- function renderShellDetails(input: ShellSectionInput, selectedId: string, paint: ShellPaint, width: number): string[] {
160
- const task = input.rows.find((t) => t.id === selectedId);
161
- const out: string[] = [];
162
- out.push(paint.fg("accent", paint.bold("Shell details")));
163
- out.push("");
164
- if (!task) {
165
- out.push(paint.fg("dim", "shell gone"));
166
- return out;
167
- }
168
- const field = (label: string, value: string) => `${paint.bold(padPlainText(label, 9))} ${value}`;
169
- out.push(field("Status:", paint.fg(STATUS_COLOR[task.status] ?? "dim", task.status)));
170
- out.push(field("Runtime:", formatDuration(task.startedAt, task.finishedAt, input.now)));
171
- out.push(field("Command:", cut(task.command ?? task.description, Math.max(1, width - 10))));
172
- out.push("");
173
- out.push(paint.bold("Output:"));
174
-
175
- let output = "";
176
- try {
177
- output = task.output();
178
- } catch {
179
- output = "(output unavailable)";
180
- }
181
- // Bound the split to a tail slice — re-splitting a 200KB spool every ticker
182
- // frame to keep 10 lines is O(buffer) work for nothing. A mid-line cut only
183
- // matters when the tail holds fewer lines than the box, and those are
184
- // width-cut anyway.
185
- const tail = output.length > OUTPUT_TAIL_CHARS ? output.slice(-OUTPUT_TAIL_CHARS) : output;
186
- const lines = tail ? tail.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n") : [];
187
- const shown = lines.slice(-OUTPUT_BOX_ROWS);
188
- const inner = Math.max(1, width - 4);
189
- out.push(`┌${"─".repeat(Math.max(0, width - 2))}┐`);
190
- for (let i = 0; i < OUTPUT_BOX_ROWS; i++) {
191
- out.push(`│ ${padPlainText(cut(shown[i] ?? "", inner), inner)} │`);
192
- }
193
- out.push(`└${"─".repeat(Math.max(0, width - 2))}┘`);
194
- out.push(paint.fg("dim", `Showing ${countNoun(shown.length, "line")}`));
195
- out.push("");
196
- out.push(paint.fg("dim", cut("← to go back · Esc/Enter/Space to close · x to stop", width)));
197
- return out;
38
+ export type ShellKeyEffect = "openTasks" | "toAgents" | "passthrough" | undefined;
39
+
40
+ /** The focused chip's keys. */
41
+ export function reduceShellKey(key: StripKey | undefined): ShellKeyEffect {
42
+ if (key === "down") return "toAgents";
43
+ if (key === "open") return "openTasks";
44
+ if (key === "up" || key === "leave") return undefined;
45
+ return "passthrough";
46
+ }
47
+
48
+ /** The chip's one line. Callers render it only while shellRows() is non-empty. */
49
+ export function renderShellChip(count: number, focused: boolean, width: number, paint: ShellPaint): string {
50
+ width = Math.max(20, width);
51
+ if (!focused) return paint.fg("dim", cut(`${countNoun(count, "shell")} · ↓ to manage`, width));
52
+ // Cut the plain parts BEFORE painting: cutting painted text breaks escapes.
53
+ const label = cut(` ${countNoun(count, "shell")} `, width);
54
+ const hint = cut("· Enter to view tasks", Math.max(0, width - label.length - 1));
55
+ return `${paint.inverse(label)}${hint ? ` ${paint.fg("dim", hint)}` : ""}`;
198
56
  }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * `/tasks`: Claude Code 2.1.283's Background dialog (findings §40), pure
3
+ * layout and key handling. It lists the shells the chip counts (running ones,
4
+ * and finished ones for the chip's linger window, so Enter on the chip always
5
+ * finds them), the running local agents, and the finished agents the user
6
+ * viewed:
7
+ *
8
+ * Background
9
+ * 2 active shells · 1 active agent
10
+ * Shells (2)
11
+ * ❯ ⏺ sleep 25; echo done running
12
+ * Local agents (1)
13
+ * ⏺ Read notes.txt and report after delay running · Sonnet 5
14
+ * Completed (1)
15
+ * ✔ write a 300-word poem about the word in the file done · Haiku 4.5
16
+ * ↑/↓ to select · Enter to view · x to stop · Esc to close
17
+ *
18
+ * With nothing to list it reads `No tasks currently running`. Enter on a shell
19
+ * shows its details in the same dialog (renderShellDetail), and ← goes back
20
+ * to the list. The shells chip below the editor opens this dialog too.
21
+ * index.ts owns the dialog and what Enter and x do.
22
+ */
23
+
24
+ import type { BackgroundTask } from "../background/registry.ts";
25
+ import { countNoun, cutPlainText as cut, formatDuration, hintLines, padPlainText } from "../lib/tui-render.ts";
26
+ import { sanitizeDisplayText } from "../lib/terminal-text.ts";
27
+
28
+ /** One selectable row. */
29
+ export type TasksItem =
30
+ | { kind: "shell"; id: string; text: string; running: boolean; status: BackgroundTask["status"] }
31
+ | { kind: "agent"; taskId: string; text: string; model?: string; running: boolean };
32
+
33
+ export interface TasksDialogInput {
34
+ shells: Array<Extract<TasksItem, { kind: "shell" }>>;
35
+ /** Running agents first, then the viewed finished ones (the caller splits them). */
36
+ agents: Array<Extract<TasksItem, { kind: "agent" }>>;
37
+ selected: number;
38
+ width: number;
39
+ }
40
+
41
+ export interface TasksPaint {
42
+ fg(color: string, text: string): string;
43
+ bold(text: string): string;
44
+ }
45
+
46
+ /** The agents split into running and completed, each in the caller's order. */
47
+ function splitAgents(agents: TasksDialogInput["agents"]) {
48
+ return { running: agents.filter((a) => a.running), completed: agents.filter((a) => !a.running) };
49
+ }
50
+
51
+ /** Every row in display order: shells, running agents, completed agents. */
52
+ export function tasksItems(input: Pick<TasksDialogInput, "shells" | "agents">): TasksItem[] {
53
+ const agents = splitAgents(input.agents);
54
+ return [...input.shells, ...agents.running, ...agents.completed];
55
+ }
56
+
57
+ /** The dialog's lines, every one cut to `width`. */
58
+ export function renderTasksDialog(input: TasksDialogInput, paint: TasksPaint): string[] {
59
+ const width = Math.max(20, input.width);
60
+ const line = (text: string) => cut(text, width);
61
+ const out = [paint.bold(line(" Background"))];
62
+ const items = tasksItems(input);
63
+ const current = items[input.selected];
64
+ // Hints wrap at their separators on a narrow terminal, so none is cut off.
65
+ const hint = ["↑/↓ to select", "Enter to view", ...(current?.running ? ["x to stop"] : []), "Esc to close"].join(" · ");
66
+ const pushHints = () => out.push(...hintLines(hint, width, " ").map((hintLine) => paint.fg("dim", hintLine)));
67
+ if (items.length === 0) {
68
+ out.push(line(" No tasks currently running"));
69
+ pushHints();
70
+ return out;
71
+ }
72
+ const agents = splitAgents(input.agents);
73
+ const runningShells = input.shells.filter((s) => s.running).length;
74
+ const counts = [runningShells ? countNoun(runningShells, "active shell") : "", agents.running.length ? countNoun(agents.running.length, "active agent") : ""]
75
+ .filter(Boolean)
76
+ .join(" · ");
77
+ if (counts) out.push(paint.fg("dim", line(` ${counts}`)));
78
+ const groups: Array<[title: string, rows: TasksItem[]]> = [
79
+ ["Shells", input.shells],
80
+ ["Local agents", agents.running],
81
+ ["Completed", agents.completed],
82
+ ];
83
+ let index = 0;
84
+ for (const [title, rows] of groups) {
85
+ if (rows.length === 0) continue;
86
+ out.push(paint.fg("dim", line(` ${title} (${rows.length})`)));
87
+ for (const row of rows) {
88
+ const selected = index === input.selected;
89
+ // A finished shell shows how it ended; only a clean finish reads "done",
90
+ // and only a failure gets the cross (a stop is the user's own choice).
91
+ const ended = row.kind === "shell" && !row.running && row.status !== "completed" ? row.status : undefined;
92
+ const mark = row.running ? "⏺" : ended === "failed" ? "✗" : "✔";
93
+ const status = row.running ? "running" : (ended ?? "done");
94
+ const suffix = row.kind === "agent" && row.model ? `${status} · ${row.model}` : status;
95
+ const text = line(` ${selected ? "❯" : " "} ${mark} ${sanitizeDisplayText(row.text)} ${suffix}`);
96
+ out.push(selected ? paint.fg("accent", paint.bold(text)) : text);
97
+ index++;
98
+ }
99
+ }
100
+ pushHints();
101
+ return out;
102
+ }
103
+
104
+ export type TasksKey = "up" | "down" | "view" | "stop" | "close";
105
+
106
+ /** Move the selection within `count` rows, stopping at the ends. */
107
+ export function moveTasksSelection(selected: number, key: "up" | "down", count: number): number {
108
+ if (count === 0) return 0;
109
+ return key === "up" ? Math.max(0, selected - 1) : Math.min(count - 1, selected + 1);
110
+ }
111
+
112
+ /** What the dialog shows: the list, or one shell's details. */
113
+ export type TasksView = { mode: "list" } | { mode: "shell"; id: string };
114
+
115
+ /**
116
+ * The view to render now. A shell's details last while the shell is known: one
117
+ * that finishes while shown keeps its details, with its final status and no
118
+ * `x`, and the dialog goes back to the list once the shell is gone.
119
+ */
120
+ export function settleTasksView(view: TasksView, shellKnown: boolean): TasksView {
121
+ return view.mode === "shell" && !shellKnown ? { mode: "list" } : view;
122
+ }
123
+
124
+ /** What a key does in a shell's details: ← goes back, Esc/Enter/Space close, x stops a running shell. */
125
+ export function shellDetailKey(id: string | undefined, text: string | undefined, running: boolean): "back" | "close" | "stop" | undefined {
126
+ if (id === "left") return "back";
127
+ if (id === "escape" || id === "enter" || id === "space" || text === " ") return "close";
128
+ if (running && text?.toLowerCase() === "x") return "stop";
129
+ return undefined;
130
+ }
131
+
132
+ /** Output-box height in a shell's details (content rows, borders excluded). */
133
+ export const OUTPUT_BOX_ROWS = 10;
134
+
135
+ /** Only this much of the spool is split per frame: the box shows 10 lines. */
136
+ const OUTPUT_TAIL_CHARS = 8192;
137
+
138
+ const STATUS_COLOR: Record<string, string> = { running: "accent", failed: "error", stopped: "dim" };
139
+
140
+ /** One shell's details: Status, Runtime, Command, a bordered tail of its output, and the keys it takes. */
141
+ export function renderShellDetail(task: BackgroundTask, width: number, now: number, paint: TasksPaint): string[] {
142
+ width = Math.max(20, width);
143
+ const out = [paint.bold(cut("Shell details", width)), ""];
144
+ const field = (label: string, value: string) => `${paint.bold(padPlainText(label, 9))} ${value}`;
145
+ out.push(field("Status:", paint.fg(STATUS_COLOR[task.status] ?? "dim", task.status)));
146
+ out.push(field("Runtime:", formatDuration(task.startedAt, task.finishedAt, now)));
147
+ out.push(field("Command:", cut(sanitizeDisplayText(task.command ?? task.description), Math.max(1, width - 10))));
148
+ out.push("", paint.bold("Output:"));
149
+
150
+ let output = "";
151
+ try {
152
+ output = task.output();
153
+ } catch {
154
+ output = "(output unavailable)";
155
+ }
156
+ // Bound the split to a tail slice: re-splitting a 200KB spool every frame to
157
+ // keep 10 lines is O(buffer) work for nothing. A mid-line cut only matters
158
+ // when the tail holds fewer lines than the box, and those are width-cut anyway.
159
+ const tail = output.length > OUTPUT_TAIL_CHARS ? output.slice(-OUTPUT_TAIL_CHARS) : output;
160
+ const lines = tail ? tail.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n") : [];
161
+ const shown = lines.slice(-OUTPUT_BOX_ROWS);
162
+ const inner = Math.max(1, width - 4);
163
+ out.push(`┌${"─".repeat(Math.max(0, width - 2))}┐`);
164
+ for (let i = 0; i < OUTPUT_BOX_ROWS; i++) out.push(`│ ${padPlainText(cut(shown[i] ?? "", inner), inner)} │`);
165
+ out.push(`└${"─".repeat(Math.max(0, width - 2))}┘`);
166
+ out.push(paint.fg("dim", `Showing ${countNoun(shown.length, "line")}`), "");
167
+ const hint = ["← to go back", "Esc/Enter/Space to close", ...(task.status === "running" ? ["x to stop"] : [])].join(" · ");
168
+ out.push(...hintLines(hint, width).map((hintLine) => paint.fg("dim", hintLine)));
169
+ return out;
170
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Viewport mirror for the agent view (pure; no pi imports).
3
+ *
4
+ * In pi's fullscreen mode the terminal's scroll input (PageUp/PageDown, the
5
+ * mouse wheel, Ctrl+Home/Ctrl+End, plain Home/End before pi 1.0.3) is taken
6
+ * by pi-tui's own viewport handler before any extension input listener runs
7
+ * (findings §10), and it scrolls the main transcript. The agent view is an
8
+ * overlay over that transcript, so the user saw nothing move in the view while
9
+ * main, hidden below it, scrolled away and stayed scrolled up after the view
10
+ * closed.
11
+ *
12
+ * The mirror turns those moves into agent-view scrolling, as Claude Code
13
+ * scrolls whatever transcript is on screen: each frame it reads how far the
14
+ * main viewport moved since it was last parked, hands that distance to the
15
+ * view, and parks main again. Main is parked above its bottom by up to the
16
+ * view's own scroll (at most half main's range), so a later move down has room
17
+ * to register too. `restore()` puts main back where it was when the view
18
+ * opened.
19
+ *
20
+ * Only the main transcript's range can register a move: when main is shorter
21
+ * than one screen, the page keys move nothing and the view cannot see them.
22
+ */
23
+
24
+ /** The slice of pi-tui's fullscreen TUI the mirror drives (structural). */
25
+ export interface ScrollableViewport {
26
+ readonly viewportTop: number;
27
+ readonly isFollowingOutput: boolean;
28
+ scrollBy(lines: number): void;
29
+ scrollToBottom(): void;
30
+ }
31
+
32
+ /** The TUI's fullscreen viewport, or undefined in regular (main-screen) mode. */
33
+ export function asScrollableViewport(tui: unknown): ScrollableViewport | undefined {
34
+ const candidate = tui as Partial<ScrollableViewport> | undefined;
35
+ if (!candidate || typeof candidate.viewportTop !== "number" || typeof candidate.isFollowingOutput !== "boolean") return undefined;
36
+ if (typeof candidate.scrollBy !== "function" || typeof candidate.scrollToBottom !== "function") return undefined;
37
+ return candidate as ScrollableViewport;
38
+ }
39
+
40
+ export class ViewportMirror {
41
+ private readonly openedFollowing: boolean;
42
+ private readonly openedTop: number;
43
+ private parkedTop: number;
44
+ private parkedAtBottom: boolean;
45
+
46
+ constructor(private readonly viewport: ScrollableViewport) {
47
+ this.openedFollowing = viewport.isFollowingOutput;
48
+ this.openedTop = viewport.viewportTop;
49
+ this.parkedTop = this.openedTop;
50
+ this.parkedAtBottom = this.openedFollowing;
51
+ }
52
+
53
+ /**
54
+ * Fold any main-viewport move since the last park into the view's scroll
55
+ * (lines from the bottom, up is positive) and park main again. Returns the
56
+ * new scroll, clamped to `[0, maxScroll]`.
57
+ */
58
+ absorb(scroll: number, maxScroll: number): number {
59
+ const { viewport } = this;
60
+ // Parked at the bottom and still following: main only grew, nobody scrolled.
61
+ if (this.parkedAtBottom && viewport.isFollowingOutput) {
62
+ this.parkedTop = viewport.viewportTop;
63
+ return scroll;
64
+ }
65
+ const top = viewport.viewportTop;
66
+ if (top === this.parkedTop) return scroll;
67
+ const next = clamp(scroll + (this.parkedTop - top), maxScroll);
68
+ this.park(next);
69
+ return next;
70
+ }
71
+
72
+ /** Park main for a view scroll of `scroll` lines (after a retarget, say). */
73
+ park(scroll: number): void {
74
+ const { viewport } = this;
75
+ viewport.scrollToBottom();
76
+ const bottom = viewport.viewportTop;
77
+ const lift = Math.min(Math.max(0, scroll), Math.floor(bottom / 2));
78
+ if (lift > 0) viewport.scrollBy(-lift);
79
+ this.parkedTop = viewport.viewportTop;
80
+ this.parkedAtBottom = lift === 0;
81
+ }
82
+
83
+ /** Put main back where it was when the view opened. */
84
+ restore(): void {
85
+ const { viewport } = this;
86
+ viewport.scrollToBottom();
87
+ if (this.openedFollowing) return;
88
+ const delta = this.openedTop - viewport.viewportTop;
89
+ if (delta !== 0) viewport.scrollBy(delta);
90
+ }
91
+ }
92
+
93
+ function clamp(value: number, max: number): number {
94
+ return Math.max(0, Math.min(Math.max(0, max), value));
95
+ }
@@ -3,25 +3,31 @@
3
3
  *
4
4
  * Claude Code's `isolation: "worktree"` gives an agent its own checkout so
5
5
  * parallel agents editing files cannot collide, and removes it again if the agent
6
- * changed nothing.
6
+ * changed nothing: no uncommitted change and no commit past the HEAD it was
7
+ * created at. A worktree whose agent committed its work is kept like one with
8
+ * uncommitted edits, so the commits stay reachable on its branch.
7
9
  */
8
10
 
9
11
  import { execFile } from "node:child_process";
10
12
  import { mkdtempSync } from "node:fs";
11
- import { tmpdir } from "node:os";
12
- import { join } from "node:path";
13
+ import { homedir, tmpdir } from "node:os";
14
+ import { basename, join, resolve } from "node:path";
13
15
  import { promisify } from "node:util";
14
16
  import { registerWorktreeIsolation, releaseWorktreeIsolation } from "../lib/worktree-isolation.ts";
17
+ import { findProjectRoot, HARNESS_GIT_CONFIG, HARNESS_GIT_NO_HOOKS } from "../lib/git.ts";
18
+ import { checkoutGitRunsProgram } from "../auto-mode/git-checkout-programs.ts";
15
19
 
16
20
  const run = promisify(execFile);
17
21
 
18
22
  export interface Worktree {
19
23
  path: string;
20
24
  branch: string;
25
+ /** The commit the worktree was created at; commits past it are the agent's work. */
26
+ baseCommit: string;
21
27
  }
22
28
 
23
29
  async function git(args: string[], cwd: string): Promise<string> {
24
- const { stdout } = await run("git", args, { cwd, maxBuffer: 10 * 1024 * 1024 });
30
+ const { stdout } = await run("git", [...HARNESS_GIT_CONFIG, ...HARNESS_GIT_NO_HOOKS, ...args], { cwd, maxBuffer: 10 * 1024 * 1024 });
25
31
  return stdout.trim();
26
32
  }
27
33
 
@@ -33,31 +39,57 @@ export async function isGitRepo(cwd: string): Promise<boolean> {
33
39
  }
34
40
  }
35
41
 
36
- /** Creates a detached worktree at the current HEAD. */
42
+ /** Creates a worktree on a new throwaway branch at the current HEAD. */
37
43
  export async function createWorktree(cwd: string, label: string): Promise<Worktree> {
44
+ const program = checkoutGitRunsProgram(cwd, homedir(), []);
45
+ if (program) {
46
+ throw new Error(`Cannot create an isolation worktree: ${program}. Create the worktree with an explicitly approved git worktree add command, then start a session there without automatic isolation.`);
47
+ }
38
48
  const safeLabel = label.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 24) || "agent";
39
49
  const dir = mkdtempSync(join(tmpdir(), `cc-wt-${safeLabel}-`));
40
50
  const path = join(dir, "tree");
41
- const branch = `cc-subagent/${safeLabel}-${Date.now().toString(36)}`;
51
+ const branch = `cc-subagent/${basename(dir).slice("cc-wt-".length)}`;
42
52
  await git(["worktree", "add", "-b", branch, path, "HEAD"], cwd);
43
53
  // Register for the child permission gate's git-isolation guard. The shared
44
- // checkout's ROOT, not cwd — the spawn may run from a subdirectory.
54
+ // checkout's ROOT, not cwd — the spawn may run from a subdirectory — and the
55
+ // MAIN checkout's root when it runs from a linked worktree (an entered
56
+ // `enter_worktree` session), so git into either is refused.
45
57
  let sharedRoot = cwd;
46
58
  try {
47
- sharedRoot = await git(["rev-parse", "--show-toplevel"], cwd);
59
+ // git prints `C:/…` on Windows; resolve gives the native form.
60
+ const top = resolve(await git(["rev-parse", "--show-toplevel"], cwd));
61
+ sharedRoot = findProjectRoot(top) ?? top;
48
62
  } catch {
49
63
  // Keep cwd as the best available anchor.
50
64
  }
51
65
  registerWorktreeIsolation(path, sharedRoot);
52
- return { path, branch };
66
+ let baseCommit: string;
67
+ try {
68
+ baseCommit = await git(["rev-parse", "HEAD"], path);
69
+ } catch (error) {
70
+ // Without the base, cleanup could not tell the agent's commits apart;
71
+ // remove the fresh worktree now rather than run in one we cannot judge.
72
+ await git(["worktree", "remove", "--force", path], cwd).catch(() => undefined);
73
+ await git(["branch", "-D", branch], cwd).catch(() => undefined);
74
+ releaseWorktreeIsolation(path);
75
+ throw error;
76
+ }
77
+ return { path, branch, baseCommit };
53
78
  }
54
79
 
80
+ /**
81
+ * Whether the worktree holds work: uncommitted changes, or commits reachable
82
+ * from its HEAD or its branch that the base commit does not contain (an agent
83
+ * that commits leaves a clean status). Any git failure counts as work.
84
+ */
55
85
  export async function worktreeHasChanges(worktree: Worktree): Promise<boolean> {
56
86
  try {
57
- const status = await git(["status", "--porcelain"], worktree.path);
58
- return status.length > 0;
87
+ const status = await git(["status", "--porcelain", "--untracked-files=all", "--ignore-submodules=none"], worktree.path);
88
+ if (status.length > 0) return true;
89
+ const ahead = await git(["rev-list", "--count", "HEAD", `refs/heads/${worktree.branch}`, `^${worktree.baseCommit}`], worktree.path);
90
+ return ahead !== "0";
59
91
  } catch {
60
- // If status fails, assume there is something worth keeping.
92
+ // If git fails, assume there is something worth keeping.
61
93
  return true;
62
94
  }
63
95
  }
@@ -70,13 +102,25 @@ export async function cleanupWorktree(cwd: string, worktree: Worktree): Promise<
70
102
  if (await worktreeHasChanges(worktree)) return false;
71
103
  try {
72
104
  await git(["worktree", "remove", "--force", worktree.path], cwd);
73
- await git(["branch", "-D", worktree.branch], cwd);
74
105
  } catch {
75
106
  // Leave it behind rather than failing the run.
76
107
  return false;
77
108
  }
109
+ try {
110
+ // `-d`, not `-D`: git itself refuses to drop a branch with unmerged work,
111
+ // a second check behind worktreeHasChanges. A refused delete leaves a
112
+ // branch at the base commit, which holds none of the agent's work.
113
+ await git(["branch", "-d", worktree.branch], cwd);
114
+ } catch {
115
+ // The worktree is gone; the branch stays for the user to inspect.
116
+ }
78
117
  // Release only when the worktree is actually gone — a kept worktree can
79
118
  // still host a resumed session (SendMessage), which must stay guarded.
80
119
  releaseWorktreeIsolation(worktree.path);
81
120
  return true;
82
121
  }
122
+
123
+ /** The note a result carries for a worktree kept because it holds the agent's work. */
124
+ export function keptWorktreeNote(path: string, branch: string | undefined): string {
125
+ return `(Changes or commits left in worktree ${path}${branch ? ` on branch ${branch}` : ""} — review or merge them.)`;
126
+ }