one-code-extension 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +16 -5
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +58 -31
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +123 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +52 -0
  63. package/extensions/background/wakeup.ts +159 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +88 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +18 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -12,19 +12,23 @@
12
12
  * `<sessionDir>/subagents/<taskId>/`, so a finished run can be resumed from disk.
13
13
  */
14
14
 
15
+ import { childWriteWatcher } from "../lib/child-writes.ts";
15
16
  import { createHash } from "node:crypto";
16
- import { dirname, join } from "node:path";
17
- import { fileURLToPath } from "node:url";
17
+ import { existsSync } from "node:fs";
18
18
  import { type AgentSession, type ExtensionError, getAgentDir, SessionManager, type ToolDefinition } from "@earendil-works/pi-coding-agent";
19
- import type { Api, Model } from "@earendil-works/pi-ai";
19
+ import type { Api, Message, Model } from "@earendil-works/pi-ai";
20
20
  import { whenAborted } from "../lib/abort.ts";
21
- import { type AgentLoaderOptions, buildAgentLoader, createSharedModelRuntime, openChildSession } from "../lib/agent-loader.ts";
21
+ import { type AgentLoaderOptions, buildAgentLoader, createSharedModelRuntime, disposeChildSession, openChildSession } from "../lib/agent-loader.ts";
22
22
  import { agentPromptIdentity, PrefixWarmGate, prefixWarmKey, type Release } from "../lib/prefix-warm-gate.ts";
23
23
  import type { PermissionBridge } from "../permissions/subagent-gate.ts";
24
24
  import type { HookBridge } from "../hooks/subagent-bridge.ts";
25
25
  import { findConfigured, modelSpec } from "../lib/model-policy.ts";
26
26
  import { isModelUnavailableError } from "../auto-mode/model-select.ts";
27
- import { type AgentDefinition, childToolAllowlist, usableAllowlistedTools } from "./agents.ts";
27
+ import { type AgentDefinition, agentToolOptions, unusableAllowlistError } from "./agents.ts";
28
+ import { CHILD_EXTENSION_PATHS } from "../lib/child-extensions.ts";
29
+ import { type HandbackSlot, SUBAGENT_HANDBACK, subagentHandbackExtension } from "../lib/subagent-handback.ts";
30
+ import type { RequestCapture } from "../lib/request-replay.ts";
31
+ import { forkCacheExtension } from "./fork-cache.ts";
28
32
  import type { ChildHandle, ChildOutcome, RpcChildHandle } from "./outcome.ts";
29
33
  import { sendToMainTool } from "./send-to-main-tool.ts";
30
34
  import { SessionTurnTracker } from "./session-turns.ts";
@@ -53,34 +57,12 @@ const WALL_CLOCK_CAP_MS = 30 * 60 * 1000;
53
57
 
54
58
  /**
55
59
  * Tools the runtime injects into a child; the permission gate must never gate
56
- * them. `Agent` is the injected child-spawn tool (index.ts) — the spawn itself
60
+ * them. `SubagentHandback` only returns the child's report to its caller.
61
+ * `Agent` is the injected child-spawn tool (index.ts) — the spawn itself
57
62
  * is free (matching the main conversation's auto-allowed Agent tool); every
58
63
  * tool call the spawned grandchild makes still runs through the real gate.
59
64
  */
60
- const NEVER_GATE = new Set(["structured_output", "SendMessage", "Agent"]);
61
-
62
- /**
63
- * The curated extensions loaded into a subagent session (via additionalExtensionPaths,
64
- * which the loader loads even under noExtensions). Broadly Claude Code's model — a
65
- * subagent gets project context + freshness + a working toolset, but NOT the frontier
66
- * chrome (banner/spinner/recap) or the orchestration EXTENSIONS. Nested spawning
67
- * (CC parity: subagents spawn subagents) is instead an injected `Agent` custom tool
68
- * (index.ts childAgentTool, passed via extraTools) that delegates to the PARENT
69
- * extension's runtime — one registry, one panel, one permission gate — and is
70
- * depth-capped there.
71
- *
72
- * `lsp` is deliberately NOT here (matching CC, findings §17.3): a child session is torn
73
- * down with the raw AgentSession.dispose(), which never fires session_shutdown, so lsp's
74
- * cleanup would never run and any language server it started would leak for the life of
75
- * the parent session. MCP is also not listed: its
76
- * tools are shared in from the parent as customTools (getMcpTools), not reconnected.
77
- *
78
- * Order mirrors the package's load order: reminder/deferral sinks (system-reminder,
79
- * tool-search) first, before anything emitting on their channels (the bus does not replay).
80
- */
81
- const EXTENSIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
82
- const CHILD_EXTENSIONS = ["system-reminder", "tool-search", "claude-context", "file-tracker", "search-tools", "skill", "web", "web-fetch", "notebook"];
83
- const CHILD_EXTENSION_PATHS = CHILD_EXTENSIONS.map((name) => join(EXTENSIONS_DIR, name, "index.ts"));
65
+ const NEVER_GATE = new Set(["structured_output", "SendMessage", SUBAGENT_HANDBACK, "Agent"]);
84
66
 
85
67
  type Session = AgentSession;
86
68
 
@@ -92,8 +74,15 @@ interface ChildSessionSpec {
92
74
  agent?: AgentDefinition;
93
75
  /** Parent session file — present for a fork run (inherit the parent transcript). */
94
76
  forkFrom?: string;
77
+ /** Messages appended to a fork's transcript before its task (e.g. `/btw`'s side exchange). */
78
+ forkMessages?: Message[];
95
79
  /** The parent's current system prompt, applied to a fork so it continues as the parent. */
96
80
  parentSystemPrompt?: string;
81
+ /**
82
+ * The parent's last provider request, for a fork: every fork request is
83
+ * built from it so the fork reads the parent's prompt cache (fork-cache.ts).
84
+ */
85
+ parentRequest?: RequestCapture;
97
86
  /** Existing persisted session to resume (SendMessage to a finished agent). */
98
87
  sessionFile?: string;
99
88
  /** Where a new run's persisted session lands; falls back to an in-memory session. */
@@ -111,6 +100,12 @@ interface ChildSessionSpec {
111
100
  onMessageToMain?: (message: string, summary?: string) => void;
112
101
  /** Extra injected tools (e.g. the child-spawn Agent tool for depth-0 children). */
113
102
  extraTools?: ToolDefinition[];
103
+ /**
104
+ * The main session's tool call that started the run's current turn, read
105
+ * per gated call (a SendMessage can start a later turn). The permission
106
+ * bridge passes it on as `ChildToolCall.parentToolCallId`.
107
+ */
108
+ mainToolCallId?: () => string | undefined;
114
109
  }
115
110
 
116
111
  export interface SubagentRunOptions extends ChildSessionSpec {
@@ -125,11 +120,33 @@ export interface ResidentRunOptions extends Omit<ChildSessionSpec, "sessionFile"
125
120
  onProgress: (toolCalls: number, lastText: string, usage: UsageTotals) => void;
126
121
  /** Fires at the end of EVERY turn (initial task and later messages alike). */
127
122
  onTurnEnd: (outcome: ChildOutcome) => void;
128
- onExit?: () => void;
123
+ /** Fires once when the resident exits; a returned promise (worktree cleanup) is part of kill()'s settle. */
124
+ onExit?: () => void | Promise<unknown>;
129
125
  /** Optional live sink for the subagent panel (activity + transcript blocks). */
130
126
  sink?: LiveSink;
131
127
  }
132
128
 
129
+ /**
130
+ * A child's session manager: a resume reopens its file; otherwise a fork
131
+ * clones the parent transcript (when the parent has written one) and anything
132
+ * else starts empty, and `forkMessages` are appended so the child sees them before
133
+ * its task and a later resume finds them in its file. Exported for tests.
134
+ */
135
+ export function newChildSessionManager(spec: Pick<ChildSessionSpec, "cwd" | "forkFrom" | "forkMessages" | "sessionFile" | "sessionDir">): SessionManager {
136
+ // The resume cwd is passed explicitly: a worktree run's persisted cwd may
137
+ // be gone by the time it is messaged (index.ts substitutes the parent cwd).
138
+ if (spec.sessionFile) return SessionManager.open(spec.sessionFile, undefined, spec.cwd);
139
+ // pi writes a session file with its first assistant reply, so a parent with
140
+ // no transcript yet (or run with --no-session) has nothing to clone.
141
+ const manager = spec.forkFrom && existsSync(spec.forkFrom)
142
+ ? SessionManager.forkFrom(spec.forkFrom, spec.cwd, spec.sessionDir)
143
+ : spec.sessionDir
144
+ ? SessionManager.create(spec.cwd, spec.sessionDir)
145
+ : SessionManager.inMemory(spec.cwd);
146
+ for (const message of spec.forkMessages ?? []) manager.appendMessage(message);
147
+ return manager;
148
+ }
149
+
133
150
  /** Prepend a one-time spawn note (e.g. a model fallback) to an outcome's output. */
134
151
  function withNote(outcome: ChildOutcome, note: string | undefined): ChildOutcome {
135
152
  return note ? { ...outcome, output: `${note}\n\n${outcome.output}` } : outcome;
@@ -158,10 +175,14 @@ export class SubagentRuntime {
158
175
  /** A child extension handler threw (pi swallows it otherwise); surfaced to the user by index.ts. */
159
176
  private readonly onExtensionError: (runName: string | undefined, error: ExtensionError) => void;
160
177
  private readonly onModelUnusable: (model: string, reason: string) => void;
178
+ /** A child's edit or write changed a file (lib/child-writes.ts); index.ts tells the parent's file-tracker. */
179
+ private readonly onChildWrite: (path: string, runName: string | undefined) => void;
161
180
  /** Child session id → run name, for the bridge wrapper above. */
162
181
  private readonly runNames = new Map<string, string>();
163
182
  /** Child session id → agent type (`explore`, …), for the hook payload's `agent_type`. */
164
183
  private readonly agentTypes = new Map<string, string>();
184
+ /** Child session id → the main-session call that started its turn, for the bridge wrapper. */
185
+ private readonly mainToolCalls = new Map<string, () => string | undefined>();
165
186
  /**
166
187
  * Parallel children with the same request prefix (same agent prompt and
167
188
  * model; forks share the parent's) let the first one start streaming before
@@ -178,8 +199,10 @@ export class SubagentRuntime {
178
199
  getHookBridge: () => HookBridge | undefined,
179
200
  onExtensionError: (runName: string | undefined, error: ExtensionError) => void,
180
201
  onModelUnusable: (model: string, reason: string) => void,
202
+ onChildWrite: (path: string, runName: string | undefined) => void,
181
203
  ) {
182
204
  this.modelRuntime = modelRuntime;
205
+ this.onChildWrite = onChildWrite;
183
206
  this.baseCwd = baseCwd;
184
207
  this.getMcpTools = getMcpTools;
185
208
  this.getHookBridge = getHookBridge;
@@ -191,7 +214,11 @@ export class SubagentRuntime {
191
214
  const namedBridge: PermissionBridge = (call) => {
192
215
  const bridge = getPermissionBridge();
193
216
  if (!bridge) throw new Error("the parent's permission bridge is no longer available");
194
- return bridge({ ...call, agent: call.agent ?? (call.sessionId ? this.runNames.get(call.sessionId) : undefined) });
217
+ return bridge({
218
+ ...call,
219
+ agent: call.agent ?? (call.sessionId ? this.runNames.get(call.sessionId) : undefined),
220
+ parentToolCallId: call.parentToolCallId ?? (call.sessionId ? this.mainToolCalls.get(call.sessionId)?.() : undefined),
221
+ });
195
222
  };
196
223
  this.getPermissionBridge = () => (getPermissionBridge() ? namedBridge : undefined);
197
224
  }
@@ -203,11 +230,12 @@ export class SubagentRuntime {
203
230
  getHookBridge: () => HookBridge | undefined = () => undefined,
204
231
  onExtensionError: (runName: string | undefined, error: ExtensionError) => void = () => {},
205
232
  onModelUnusable: (model: string, reason: string) => void = () => {},
233
+ onChildWrite: (path: string, runName: string | undefined) => void = () => {},
206
234
  ): Promise<SubagentRuntime> {
207
235
  const modelRuntime = await createSharedModelRuntime(getAgentDir());
208
236
  // Prime the catalog once; spawns read the live snapshot (see resolveModel).
209
237
  await modelRuntime.getAvailable();
210
- return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable);
238
+ return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable, onChildWrite);
211
239
  }
212
240
 
213
241
  /**
@@ -234,15 +262,23 @@ export class SubagentRuntime {
234
262
  * prompt and default tools, review S6); a named agent carries its own; a
235
263
  * plain run gets pi's base prompt. Built fresh per session (agent-loader.ts).
236
264
  */
237
- private childLoaderOptions(spec: ChildSessionSpec): AgentLoaderOptions {
238
- const systemPrompt = SubagentRuntime.isFork(spec) ? spec.parentSystemPrompt : spec.agent?.systemPrompt;
265
+ private childLoaderOptions(spec: ChildSessionSpec, handback: HandbackSlot): AgentLoaderOptions {
266
+ const fork = SubagentRuntime.isFork(spec);
267
+ const systemPrompt = fork ? spec.parentSystemPrompt : spec.agent?.systemPrompt;
239
268
  return {
240
269
  cwd: this.baseCwd,
241
270
  agentDir: getAgentDir(),
242
271
  systemPrompt,
243
272
  neverGate: NEVER_GATE,
244
273
  extraExtensionPaths: CHILD_EXTENSION_PATHS,
245
- // claude-context (above) injects # claudeMd on the child's session_start;
274
+ // Every child hands back through SubagentHandback (deferred in a fork,
275
+ // lib/subagent-handback.ts). Inline extensions load after the paths
276
+ // above, so the fork-cache one, last, sees the final body.
277
+ extraFactories: [
278
+ subagentHandbackExtension(handback, { deferred: fork }),
279
+ ...(fork && spec.parentRequest ? [forkCacheExtension(spec.parentRequest)] : []),
280
+ ],
281
+ // claude-context (above) injects the CLAUDE.md instructions on the child's session_start;
246
282
  // pi must not append the same files to the system prompt as well.
247
283
  noContextFiles: true,
248
284
  getPermissionBridge: this.getPermissionBridge,
@@ -271,7 +307,7 @@ export class SubagentRuntime {
271
307
  /**
272
308
  * Hold a run until a sibling with the same request prefix has started
273
309
  * streaming (see `warmGate`): same prompt identity, same cwd (the child's
274
- * `# claudeMd` block names its paths) and same model. Resolves to the release
310
+ * instructions block names its paths) and same model. Resolves to the release
275
311
  * for the caller's `finally`.
276
312
  */
277
313
  private admitPrefix(spec: Pick<ChildSessionSpec, "cwd" | "forkFrom" | "parentSystemPrompt" | "agent">, session: Session): Promise<Release> {
@@ -288,9 +324,12 @@ export class SubagentRuntime {
288
324
  onSettled?: () => void,
289
325
  sink?: LiveSink,
290
326
  ): () => void {
327
+ const childWrote = childWriteWatcher(session.sessionManager?.getCwd?.() ?? this.baseCwd);
291
328
  return session.subscribe((event) => {
292
329
  try {
293
330
  const settled = tracker.process(event as never);
331
+ const wrote = childWrote(event as never);
332
+ if (wrote) this.onChildWrite(wrote, this.runNames.get(session.sessionId));
294
333
  const e = event as { type?: string; toolName?: string; args?: unknown; isError?: boolean; result?: unknown; message?: { role?: string; content?: unknown } };
295
334
  if (e.type === "tool_execution_start") {
296
335
  sink?.onBlock?.({ kind: "call", tool: e.toolName ?? "tool", text: summarizeArgs(e.args) });
@@ -350,20 +389,11 @@ export class SubagentRuntime {
350
389
  * inherits the parent transcript + system prompt; a fresh named run gets the
351
390
  * agent's own prompt and toolset.
352
391
  */
353
- private async buildChildSession(spec: ChildSessionSpec): Promise<{ session: Session; note?: string }> {
354
- const [loader, mcpTools] = await Promise.all([buildAgentLoader(this.childLoaderOptions(spec)), this.getMcpTools()]);
355
- const newSessionManager = () =>
356
- // The resume cwd is passed explicitly: a worktree run's persisted cwd may
357
- // be gone by the time it is messaged (index.ts substitutes the parent cwd).
358
- spec.sessionFile
359
- ? SessionManager.open(spec.sessionFile, undefined, spec.cwd)
360
- : spec.forkFrom
361
- ? SessionManager.forkFrom(spec.forkFrom, spec.cwd, spec.sessionDir)
362
- : spec.sessionDir
363
- ? SessionManager.create(spec.cwd, spec.sessionDir)
364
- : SessionManager.inMemory(spec.cwd);
365
- // A fork keeps the parent's toolset; only a named agent carries an allowlist.
366
- const allowlist = spec.forkFrom ? undefined : spec.agent?.tools;
392
+ private async buildChildSession(spec: ChildSessionSpec, handback: HandbackSlot): Promise<{ session: Session; note?: string }> {
393
+ const [loader, mcpTools] = await Promise.all([buildAgentLoader(this.childLoaderOptions(spec, handback)), this.getMcpTools()]);
394
+ const newSessionManager = () => newChildSessionManager(spec);
395
+ // A fork keeps the parent's toolset; only a named agent carries tool lists.
396
+ const agent = spec.forkFrom ? undefined : spec.agent;
367
397
  const make = async (model: string | undefined): Promise<Session> => {
368
398
  const resolvedModel = model ? await this.resolveModel(model) : undefined;
369
399
  const session = await openChildSession({
@@ -374,10 +404,9 @@ export class SubagentRuntime {
374
404
  modelRuntime: this.modelRuntime,
375
405
  model: resolvedModel as never,
376
406
  thinkingLevel: spec.thinking as never,
377
- tools: childToolAllowlist(allowlist),
378
- // Denylist grants (CC's "All tools except …" shape) — filters built-ins,
379
- // extension tools, and injected customTools alike.
380
- excludeTools: spec.forkFrom ? undefined : spec.agent?.excludeTools,
407
+ // The allowlist with the plumbing re-added, and the denylist (CC's "All
408
+ // tools except …" shape), which filters injected customTools too.
409
+ ...agentToolOptions(agent),
381
410
  customTools: [sendToMainTool((m, s) => spec.onMessageToMain?.(m, s)), ...(spec.extraTools ?? []), ...mcpTools],
382
411
  sessionManager: newSessionManager(),
383
412
  },
@@ -386,21 +415,17 @@ export class SubagentRuntime {
386
415
  });
387
416
  // An allowlist that matched nothing real would run a tool-less agent
388
417
  // (pi drops unknown names silently). Fail loud with the fix named.
389
- if (
390
- allowlist &&
391
- usableAllowlistedTools(
392
- session.getAllTools().map((t) => t.name),
393
- allowlist,
394
- ).length === 0
395
- ) {
418
+ const unusable = unusableAllowlistError(
419
+ agent,
420
+ session.getAllTools().map((t) => t.name),
421
+ );
422
+ if (unusable) {
396
423
  this.discard(session);
397
- throw new Error(
398
- `Agent "${spec.agent?.name}" lists tools (${allowlist.join(", ")}) that match no available tool. ` +
399
- "Use Claude Code names (Read, Edit, Write, Bash, Grep, Glob, WebFetch, WebSearch, NotebookEdit, Skill, Agent, SendMessage) or pi names (read, edit, write, bash, grep, find, ls, …) in the agent file's `tools` list.",
400
- );
424
+ throw new Error(unusable);
401
425
  }
402
426
  if (spec.name) this.runNames.set(session.sessionManager.getSessionId(), spec.name);
403
427
  if (spec.agent) this.agentTypes.set(session.sessionManager.getSessionId(), spec.agent.name);
428
+ if (spec.mainToolCallId) this.mainToolCalls.set(session.sessionManager.getSessionId(), spec.mainToolCallId);
404
429
  return session;
405
430
  };
406
431
  try {
@@ -421,7 +446,8 @@ export class SubagentRuntime {
421
446
  private discard(session: Session): void {
422
447
  this.runNames.delete(session.sessionManager.getSessionId());
423
448
  this.agentTypes.delete(session.sessionManager.getSessionId());
424
- session.dispose();
449
+ this.mainToolCalls.delete(session.sessionManager.getSessionId());
450
+ disposeChildSession(session);
425
451
  }
426
452
 
427
453
  /**
@@ -452,17 +478,17 @@ export class SubagentRuntime {
452
478
  // corrupt session file, loader error) must resolve to a failed
453
479
  // outcome, never reject handle.result — call sites like the
454
480
  // SendMessage resume path consume it with a bare .then().
455
- const built = await this.buildChildSession(options);
481
+ const built = await this.buildChildSession(options, tracker);
456
482
  session = built.session;
457
483
  spawnNote = built.note;
458
484
  unsubscribe = this.wireTracker(session, tracker, options.onProgress, undefined, options.sink);
459
485
  // A resume continues a unique history; only a fresh run shares a prefix.
460
486
  const releasePrefix = options.sessionFile ? undefined : await this.admitPrefix(options, session);
461
487
  try {
462
- // The abort listener calls session.abort(), a no-op while nothing runs
463
- // yet: a cancellation that landed during the gate wait must stop the
464
- // turn from starting at all, not let it run to completion unnoticed.
465
- if (options.signal?.aborted) throw new Error("cancelled before the first request was sent");
488
+ // abort() is a no-op before the prompt starts. Every cancellation
489
+ // source (signal, kill, wall-clock cap) marks the tracker first,
490
+ // including one that landed during construction or the prefix wait.
491
+ if (tracker.aborted) throw new Error("cancelled before the first request was sent");
466
492
  // No template/command expansion: a task starting with "/" is text, not
467
493
  // a command lookup (pi's own sendUserMessage does the same — L2).
468
494
  await session.prompt(options.task, { expandPromptTemplates: false });
@@ -504,11 +530,12 @@ export class SubagentRuntime {
504
530
  * while an idle resident lives until task_stop / session shutdown.
505
531
  */
506
532
  async runResident(options: ResidentRunOptions): Promise<RpcChildHandle> {
507
- const built = await this.buildChildSession(options);
533
+ const tracker = new SessionTurnTracker();
534
+ const built = await this.buildChildSession(options, tracker);
508
535
  const session = built.session;
509
536
  let spawnNote = built.note;
510
- const tracker = new SessionTurnTracker();
511
537
  let exited = false;
538
+ let exitPromise: Promise<void> | undefined;
512
539
  let turnActive = false;
513
540
  let firstTurn = true;
514
541
  let turnTimer: ReturnType<typeof setTimeout> | undefined;
@@ -635,7 +662,7 @@ export class SubagentRuntime {
635
662
  busy: () => turnActive || !session.isIdle,
636
663
  exited: () => exited,
637
664
  kill: () => {
638
- if (exited) return Promise.resolve();
665
+ if (exited) return exitPromise ?? Promise.resolve();
639
666
  exited = true;
640
667
  pendingSend = undefined;
641
668
  // Report an in-flight turn as terminated, not as a normal completion:
@@ -645,14 +672,15 @@ export class SubagentRuntime {
645
672
  finishTurn(tracker.turnOutcome());
646
673
  // onExit only after the abort settles: it triggers worktree cleanup,
647
674
  // which must not race an aborting session still writing files. The
648
- // returned promise lets session_shutdown wait for exactly that.
649
- return session
675
+ // returned promise settles after that cleanup too, so session_shutdown
676
+ // waits for exactly that.
677
+ return (exitPromise = session
650
678
  .abort()
651
679
  .catch(() => undefined)
652
- .finally(() => {
680
+ .then(async () => {
653
681
  discard();
654
- options.onExit?.();
655
- });
682
+ await Promise.resolve(options.onExit?.()).catch(() => undefined);
683
+ }));
656
684
  },
657
685
  release: () => {
658
686
  if (exited || turnActive || !session.isIdle) return;
@@ -10,6 +10,11 @@
10
10
  import { readdirSync, statSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
 
13
+ /** The synthetic agent type of a run that clones the calling conversation. */
14
+ export const FORK_AGENT = "fork";
15
+ /** The agent a run that names none starts, as in Claude Code. */
16
+ export const DEFAULT_AGENT = "general-purpose";
17
+
13
18
  export interface AgentRunRecord {
14
19
  name: string;
15
20
  agent: string;
@@ -23,6 +28,12 @@ export interface AgentRunRecord {
23
28
  worktree?: boolean;
24
29
  model?: string;
25
30
  thinking?: string;
31
+ /**
32
+ * The run's short title, Claude Code's `description`: the Agent call's
33
+ * `description`, or a `/btw` fork's question. The strip, `/tasks` and the
34
+ * notification summary show it (findings §40); absent, they show the name.
35
+ */
36
+ description?: string;
26
37
  /**
27
38
  * Nesting level: 0 = spawned by main, 1 = spawned by a child's own Agent
28
39
  * tool. Set once at creation and read by every spawn-tool handout (the
@@ -38,13 +49,23 @@ export interface AgentRunRecord {
38
49
  inline?: boolean;
39
50
  }
40
51
 
52
+ /** `<base>-<n>` with the lowest n ≥ `from` not in `taken`. */
53
+ function firstFreeSuffixed(taken: ReadonlySet<string>, base: string, from: number): string {
54
+ for (let n = from; ; n++) {
55
+ const candidate = `${base}-${n}`;
56
+ if (!taken.has(candidate)) return candidate;
57
+ }
58
+ }
59
+
41
60
  /** `<agent>-<n>` with the lowest n not already taken. */
42
61
  export function nextRunName(existing: Iterable<string>, agent: string): string {
62
+ return firstFreeSuffixed(new Set(existing), agent, 1);
63
+ }
64
+
65
+ /** `base` when free, else `<base>-<n>` with the lowest n ≥ 2 not taken (Claude Code's fork naming). */
66
+ export function freeRunName(existing: Iterable<string>, base: string): string {
43
67
  const taken = new Set(existing);
44
- for (let n = 1; ; n++) {
45
- const candidate = `${agent}-${n}`;
46
- if (!taken.has(candidate)) return candidate;
47
- }
68
+ return taken.has(base) ? firstFreeSuffixed(taken, base, 2) : base;
48
69
  }
49
70
 
50
71
  /**
@@ -119,11 +140,16 @@ export class RunRegistry {
119
140
  this.reserved.clear();
120
141
  }
121
142
 
122
- /** Hold a name until its record is added (a spawn that fails validation leaves it held — harmless). */
143
+ /** Hold a name until its record is added. */
123
144
  reserve(name: string): void {
124
145
  this.reserved.add(name);
125
146
  }
126
147
 
148
+ /** Free a held name whose spawn failed validation, so a corrected retry gets it. */
149
+ release(name: string): void {
150
+ this.reserved.delete(name);
151
+ }
152
+
127
153
  /** Latest wins per name, matching Claude Code's semantics. */
128
154
  add(record: AgentRunRecord): void {
129
155
  this.reserved.delete(record.name);
@@ -131,6 +157,14 @@ export class RunRegistry {
131
157
  this.byId.set(record.taskId, record);
132
158
  }
133
159
 
160
+ /** Forget a run that never started, so neither list_agents nor a resume offers it. */
161
+ remove(taskId: string): void {
162
+ const record = this.byId.get(taskId);
163
+ if (!record) return;
164
+ this.byId.delete(taskId);
165
+ if (this.byName.get(record.name) === record) this.byName.delete(record.name);
166
+ }
167
+
134
168
  /** Every name in use: recorded runs plus names reserved for runs being spawned. */
135
169
  names(): string[] {
136
170
  return [...new Set([...this.byName.keys(), ...this.reserved])];
@@ -27,7 +27,7 @@ export function sendToMainTool(onMessage: (message: string, summary?: string) =>
27
27
  }) as never,
28
28
  async execute(_toolCallId: string, params: unknown) {
29
29
  const p = (params ?? {}) as { to?: unknown; message?: unknown; summary?: unknown };
30
- // Fail loud (docs/decisions/tools.md): a wrong recipient or a missing
30
+ // Fail loud (working-docs/decisions/tools.md): a wrong recipient or a missing
31
31
  // message returns isError with the fix named, never a silent success.
32
32
  if (p.to !== undefined && p.to !== "main") {
33
33
  return {
@@ -12,7 +12,8 @@
12
12
  */
13
13
 
14
14
  import { recordAction, type ChildAction } from "../auto-mode/actions.ts";
15
- import { type ChildOutcome, OUTPUT_CAP } from "./outcome.ts";
15
+ import type { HandbackSlot } from "../lib/subagent-handback.ts";
16
+ import type { ChildOutcome } from "./outcome.ts";
16
17
  import { addUsage, emptyUsage, type UsageTotals } from "./usage.ts";
17
18
 
18
19
  /** The subset of AgentSession events this tracker reads. */
@@ -23,7 +24,12 @@ interface TrackedEvent {
23
24
  message?: { role?: string; content?: unknown; usage?: unknown; stopReason?: string; errorMessage?: string };
24
25
  }
25
26
 
26
- export class SessionTurnTracker {
27
+ /**
28
+ * The tracker is also the run's `HandbackSlot`: a `SubagentHandback` call
29
+ * records the turn's report here, and the turn's outcome is that report, or
30
+ * the turn's final assistant text when the child never called the tool.
31
+ */
32
+ export class SessionTurnTracker implements HandbackSlot {
27
33
  toolCalls = 0;
28
34
  /** What the child did this turn, for auto mode's return review. */
29
35
  actions: ChildAction[] = [];
@@ -43,12 +49,15 @@ export class SessionTurnTracker {
43
49
  aborted: string | undefined;
44
50
  /** Every turn's final text joined, for task_output on a resident agent. */
45
51
  transcript = "";
52
+ /** The report this turn handed back through SubagentHandback, if it did. */
53
+ handback: string | undefined;
46
54
 
47
55
  /** Call when a new turn is started (a prompt sent while idle). */
48
56
  beginTurn(): void {
49
57
  this.turnText = "";
50
58
  this.providerError = undefined;
51
59
  this.aborted = undefined;
60
+ this.handback = undefined;
52
61
  // Per turn, not per session: the review that reads this judges the turn just finished.
53
62
  this.actions = [];
54
63
  }
@@ -60,6 +69,17 @@ export class SessionTurnTracker {
60
69
  this.aborted ??= reason;
61
70
  }
62
71
 
72
+ recordHandback(message: string): boolean {
73
+ if (this.handback !== undefined) return false;
74
+ this.handback = message;
75
+ return true;
76
+ }
77
+
78
+ /** What the turn returns: the handed-back report, else the final assistant text. */
79
+ private get result(): string {
80
+ return this.handback ?? this.turnText;
81
+ }
82
+
63
83
  /** Feed one subscribed event. Returns true when a turn just settled. */
64
84
  process(event: TrackedEvent): boolean {
65
85
  switch (event.type) {
@@ -83,8 +103,8 @@ export class SessionTurnTracker {
83
103
  return false;
84
104
  }
85
105
  case "agent_settled": {
86
- if (this.turnText.trim()) {
87
- this.transcript = this.transcript ? `${this.transcript}\n\n---\n\n${this.turnText}` : this.turnText;
106
+ if (this.result.trim()) {
107
+ this.transcript = this.transcript ? `${this.transcript}\n\n---\n\n${this.result}` : this.result;
88
108
  }
89
109
  return true;
90
110
  }
@@ -95,7 +115,7 @@ export class SessionTurnTracker {
95
115
 
96
116
  /** The outcome of the turn that just settled. */
97
117
  turnOutcome(): ChildOutcome {
98
- return finishOutcome(this.turnText, this.providerError, this.toolCalls, this.usage, this.actions, this.aborted);
118
+ return finishOutcome(this.result, this.providerError, this.toolCalls, this.usage, this.actions, this.aborted);
99
119
  }
100
120
  }
101
121
 
@@ -103,16 +123,20 @@ export class SessionTurnTracker {
103
123
  * Shape a run/turn's collected state into a ChildOutcome, matching the spawned
104
124
  * child's messages. `aborted` (a reason) wins over everything: an aborted turn's
105
125
  * partial text is reported as terminated and failed, never as the answer.
126
+ *
127
+ * The text is kept whole. Every consumer bounds it with `persistIfLarge`, which
128
+ * saves an oversized report to a file and points at it; a cut here left the
129
+ * persist step nothing over its limit, so a long report lost its end with no
130
+ * marker (SUBAGENTS-WORKFLOWS-REVIEW-2026-09-26 M1).
106
131
  */
107
132
  export function finishOutcome(
108
- rawOutput: string,
133
+ output: string,
109
134
  providerError: string | undefined,
110
135
  toolCalls: number,
111
136
  usage: UsageTotals,
112
137
  actions: ChildAction[],
113
138
  aborted?: string,
114
139
  ): ChildOutcome {
115
- const output = rawOutput.slice(0, OUTPUT_CAP);
116
140
  if (aborted) {
117
141
  return {
118
142
  output: output.trim() ? `${output}\n\n[${aborted}]` : `Subagent ${aborted}.`,
@@ -134,5 +158,5 @@ export function finishOutcome(
134
158
  };
135
159
  }
136
160
  if (output.trim()) return { output, toolCalls, usage, actions };
137
- return { output: "Subagent produced no output.", toolCalls, usage, actions, failed: true };
161
+ return { output: "(Subagent completed but returned no output.)", toolCalls, usage, actions };
138
162
  }