one-code-extension 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/README.md +16 -5
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +40 -28
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +26 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +58 -31
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +124 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +642 -0
  56. package/extensions/background/index.ts +614 -248
  57. package/extensions/background/loop-fire.ts +173 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +56 -0
  63. package/extensions/background/wakeup.ts +178 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +66 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +250 -78
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/cache-marks/index.ts +42 -0
  80. package/extensions/claude-compat/index.ts +7 -11
  81. package/extensions/claude-context/index.ts +436 -63
  82. package/extensions/compaction/cut.ts +157 -0
  83. package/extensions/compaction/index.ts +219 -75
  84. package/extensions/compaction/kept-thinking.ts +62 -0
  85. package/extensions/compaction/prompt.ts +15 -12
  86. package/extensions/context-budget/budget.ts +16 -2
  87. package/extensions/context-budget/index.ts +35 -15
  88. package/extensions/context-management/index.ts +36 -28
  89. package/extensions/doctor/build.ts +59 -8
  90. package/extensions/doctor/builtins.ts +84 -0
  91. package/extensions/doctor/cli.ts +7 -1
  92. package/extensions/doctor/compat.ts +88 -23
  93. package/extensions/doctor/dependencies.ts +41 -5
  94. package/extensions/doctor/fix-prompt.ts +9 -2
  95. package/extensions/doctor/index.ts +102 -58
  96. package/extensions/doctor/models.ts +102 -63
  97. package/extensions/doctor/presets.ts +42 -27
  98. package/extensions/doctor/report.ts +19 -2
  99. package/extensions/doctor/viewer.ts +49 -27
  100. package/extensions/effort/index.ts +141 -25
  101. package/extensions/effort/settings.ts +21 -0
  102. package/extensions/effort/slider.ts +24 -20
  103. package/extensions/exit/index.ts +32 -1
  104. package/extensions/file-tracker/index.ts +420 -42
  105. package/extensions/file-tracker/replay.ts +137 -24
  106. package/extensions/file-tracker/restore.ts +66 -0
  107. package/extensions/file-tracker/results.ts +66 -0
  108. package/extensions/file-tracker/shell-reads.ts +211 -0
  109. package/extensions/file-tracker/tracker.ts +45 -15
  110. package/extensions/footer/footer-line.ts +45 -24
  111. package/extensions/footer/index.ts +47 -2
  112. package/extensions/footer/pr.ts +34 -5
  113. package/extensions/hooks/compaction.ts +7 -0
  114. package/extensions/hooks/executor.ts +32 -30
  115. package/extensions/hooks/index.ts +209 -58
  116. package/extensions/hooks/matcher.ts +3 -0
  117. package/extensions/hooks/prompt-context.ts +59 -0
  118. package/extensions/hooks/protocol.ts +41 -3
  119. package/extensions/hooks/settings.ts +50 -19
  120. package/extensions/hooks/subagent-bridge.ts +2 -0
  121. package/extensions/hooks/trust.ts +26 -12
  122. package/extensions/idle-compact/index.ts +119 -0
  123. package/extensions/idle-compact/policy.ts +143 -0
  124. package/extensions/init/index.ts +11 -10
  125. package/extensions/init/prompt.ts +25 -32
  126. package/extensions/interrupted/index.ts +13 -1
  127. package/extensions/kept-thinking/index.ts +27 -0
  128. package/extensions/ledger/index.ts +46 -0
  129. package/extensions/ledger/wire.ts +154 -0
  130. package/extensions/lib/agent-cron.ts +146 -0
  131. package/extensions/lib/agent-loader.ts +18 -3
  132. package/extensions/lib/agent-view.ts +85 -0
  133. package/extensions/lib/anthropic-auth.ts +98 -0
  134. package/extensions/lib/anthropic-payload.ts +82 -1
  135. package/extensions/lib/anthropic-server-call.ts +230 -0
  136. package/extensions/lib/anthropic-server-tools.ts +485 -0
  137. package/extensions/lib/app-launch.mjs +94 -0
  138. package/extensions/lib/argument-hints.ts +190 -0
  139. package/extensions/lib/atomic-write.ts +6 -2
  140. package/extensions/lib/auto-memory.ts +75 -0
  141. package/extensions/lib/bash-parser.ts +84 -0
  142. package/extensions/lib/btw-fork.ts +93 -0
  143. package/extensions/lib/child-extensions.ts +52 -0
  144. package/extensions/lib/child-writes.ts +45 -0
  145. package/extensions/lib/claude-context.ts +515 -100
  146. package/extensions/lib/claude-external-includes.ts +68 -0
  147. package/extensions/lib/claude-id.ts +22 -0
  148. package/extensions/lib/claude-rules.ts +323 -0
  149. package/extensions/lib/claude-settings.ts +76 -4
  150. package/extensions/lib/compaction-boundary.ts +36 -0
  151. package/extensions/lib/compaction-keep.mjs +83 -0
  152. package/extensions/lib/config-mode.ts +112 -0
  153. package/extensions/lib/consent-dialogs.ts +63 -0
  154. package/extensions/lib/consent-preview.ts +5 -1
  155. package/extensions/lib/consent-stores.ts +48 -0
  156. package/extensions/lib/context-facts.ts +97 -0
  157. package/extensions/lib/covered-messages.ts +23 -0
  158. package/extensions/lib/deferred.ts +359 -25
  159. package/extensions/lib/dock-rows.ts +60 -0
  160. package/extensions/lib/empty-tool-result.ts +27 -0
  161. package/extensions/lib/environment-block.ts +76 -0
  162. package/extensions/lib/fetch-timeout.ts +24 -6
  163. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  164. package/extensions/lib/git.ts +75 -13
  165. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  166. package/extensions/lib/grammars/README.md +2 -0
  167. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  168. package/extensions/lib/guide-docs.ts +45 -0
  169. package/extensions/lib/headless-output.ts +46 -0
  170. package/extensions/lib/hook-gate.ts +7 -7
  171. package/extensions/lib/interrupt.ts +20 -4
  172. package/extensions/lib/key-input.ts +455 -0
  173. package/extensions/lib/keys.ts +14 -1
  174. package/extensions/lib/ledger/fold-memo.ts +49 -0
  175. package/extensions/lib/ledger/fold.ts +265 -0
  176. package/extensions/lib/ledger/layout.ts +72 -0
  177. package/extensions/lib/ledger/plan.ts +102 -0
  178. package/extensions/lib/ledger/records.ts +276 -0
  179. package/extensions/lib/ledger/state.ts +47 -0
  180. package/extensions/lib/ledger/validate.ts +141 -0
  181. package/extensions/lib/ledger/wire.ts +632 -0
  182. package/extensions/lib/local-command.ts +71 -19
  183. package/extensions/lib/mcp-overrides.ts +1 -1
  184. package/extensions/lib/mcp-share.ts +46 -0
  185. package/extensions/lib/memory-content.ts +146 -0
  186. package/extensions/lib/memory-index.ts +18 -0
  187. package/extensions/lib/memory.ts +134 -123
  188. package/extensions/lib/model-catalog/huggingface.json +178 -0
  189. package/extensions/lib/model-catalog/models-dev.json +18533 -0
  190. package/extensions/lib/model-catalog/openrouter.json +5050 -0
  191. package/extensions/lib/model-catalog-data.ts +513 -0
  192. package/extensions/lib/model-catalog.ts +462 -0
  193. package/extensions/lib/model-choice-warnings.ts +137 -0
  194. package/extensions/lib/model-pairings.ts +152 -0
  195. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  196. package/extensions/lib/model-policy.ts +112 -15
  197. package/extensions/lib/model-tier.ts +283 -355
  198. package/extensions/lib/model-unusable.ts +1 -1
  199. package/extensions/lib/newer-model.ts +100 -0
  200. package/extensions/lib/notices.ts +87 -0
  201. package/extensions/lib/notifications.ts +357 -73
  202. package/extensions/lib/one-code-settings.ts +74 -8
  203. package/extensions/lib/open-browser.ts +63 -0
  204. package/extensions/lib/openrouter-generation.ts +94 -0
  205. package/extensions/lib/operation-deadline.ts +30 -0
  206. package/extensions/lib/original-command.ts +4 -4
  207. package/extensions/lib/package-version.ts +1 -1
  208. package/extensions/lib/panel-focus.ts +134 -0
  209. package/extensions/lib/paths.ts +17 -2
  210. package/extensions/lib/permission-gate.ts +72 -6
  211. package/extensions/lib/persisted-output.ts +60 -8
  212. package/extensions/lib/pi-ai-estimate.ts +19 -3
  213. package/extensions/lib/pi-version.ts +38 -2
  214. package/extensions/lib/plan-mode-channels.ts +42 -5
  215. package/extensions/lib/plugins.ts +26 -17
  216. package/extensions/lib/powershell-parse-script.ts +208 -0
  217. package/extensions/lib/powershell-parser.ts +344 -0
  218. package/extensions/lib/previous-mark.ts +101 -0
  219. package/extensions/lib/process-state.ts +48 -0
  220. package/extensions/lib/process-tree.ts +132 -8
  221. package/extensions/lib/prompt-options.ts +82 -0
  222. package/extensions/lib/queued-delivery.ts +44 -0
  223. package/extensions/lib/reminders.ts +370 -358
  224. package/extensions/lib/replaced-builtins.mjs +199 -0
  225. package/extensions/lib/replay-call.ts +70 -0
  226. package/extensions/lib/request-replay.ts +547 -0
  227. package/extensions/lib/scratchpad.ts +81 -14
  228. package/extensions/lib/send-now-channel.ts +7 -0
  229. package/extensions/lib/session-model-tier.ts +51 -0
  230. package/extensions/lib/session-work.ts +64 -0
  231. package/extensions/lib/settings-channels.ts +4 -4
  232. package/extensions/lib/shell-exit-semantics.ts +170 -0
  233. package/extensions/lib/shell-quote.ts +11 -0
  234. package/extensions/lib/shell-result.ts +70 -0
  235. package/extensions/lib/shell-spawn.ts +70 -6
  236. package/extensions/lib/shell-tool.ts +75 -25
  237. package/extensions/lib/side-call-cache.ts +124 -0
  238. package/extensions/lib/side-call-run.ts +96 -0
  239. package/extensions/lib/side-call-usage.ts +38 -0
  240. package/extensions/lib/side-call.ts +24 -1
  241. package/extensions/lib/single-flight.ts +18 -0
  242. package/extensions/lib/skill-body.ts +42 -0
  243. package/extensions/lib/skill-scan.ts +63 -24
  244. package/extensions/lib/spill-file.ts +140 -0
  245. package/extensions/lib/subagent-handback.ts +112 -0
  246. package/extensions/lib/system-role.ts +156 -0
  247. package/extensions/lib/terminal-text.ts +114 -0
  248. package/extensions/lib/text-width.ts +47 -16
  249. package/extensions/lib/timer-ops.ts +20 -0
  250. package/extensions/lib/tool-additions.ts +129 -0
  251. package/extensions/lib/tool-call-corruption.ts +134 -0
  252. package/extensions/lib/tool-path.ts +66 -0
  253. package/extensions/lib/tool-variants.ts +69 -0
  254. package/extensions/lib/tui-render.ts +255 -27
  255. package/extensions/lib/turn-budget-layout.ts +159 -0
  256. package/extensions/lib/update-check.mjs +15 -2
  257. package/extensions/lib/usage-bus.ts +1 -1
  258. package/extensions/lib/user-denial.ts +38 -0
  259. package/extensions/lib/workspace-channel.ts +16 -0
  260. package/extensions/lib/worktree-channel.ts +31 -3
  261. package/extensions/lib/worktree-isolation.ts +40 -10
  262. package/extensions/lsp/client.ts +8 -9
  263. package/extensions/lsp/index.ts +76 -12
  264. package/extensions/lsp/keep-alive.ts +1 -1
  265. package/extensions/lsp/trust.ts +119 -0
  266. package/extensions/lsp/watcher.ts +1 -1
  267. package/extensions/mcp/announce.ts +90 -43
  268. package/extensions/mcp/client.ts +161 -48
  269. package/extensions/mcp/config.ts +132 -8
  270. package/extensions/mcp/discovery.ts +19 -0
  271. package/extensions/mcp/index.ts +220 -121
  272. package/extensions/mcp/oauth/browser.ts +38 -17
  273. package/extensions/mcp/oauth/flow.ts +25 -17
  274. package/extensions/mcp/oauth/provider.ts +14 -8
  275. package/extensions/mcp/oauth/store.ts +34 -16
  276. package/extensions/mcp/panel/keys.ts +12 -11
  277. package/extensions/mcp/panel/render.ts +9 -6
  278. package/extensions/mcp/schema.ts +104 -5
  279. package/extensions/mcp/trust.ts +164 -73
  280. package/extensions/memory/entries.ts +23 -14
  281. package/extensions/memory/index.ts +134 -28
  282. package/extensions/memory/open-external.ts +33 -18
  283. package/extensions/memory/panel.ts +135 -26
  284. package/extensions/model-default/index.ts +86 -19
  285. package/extensions/newer-model/index.ts +31 -0
  286. package/extensions/notebook/description.ts +18 -0
  287. package/extensions/notebook/index.ts +6 -5
  288. package/extensions/notebook/notebook.ts +1 -1
  289. package/extensions/permissions/index.ts +1426 -398
  290. package/extensions/permissions/matcher.ts +425 -74
  291. package/extensions/permissions/modes.ts +11 -4
  292. package/extensions/permissions/panel/host.ts +130 -0
  293. package/extensions/permissions/panel/keys.ts +48 -0
  294. package/extensions/permissions/panel/render.ts +450 -0
  295. package/extensions/permissions/panel/state.ts +550 -0
  296. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  297. package/extensions/permissions/powershell-paths.ts +197 -0
  298. package/extensions/permissions/powershell-rules.ts +207 -280
  299. package/extensions/permissions/powershell-tree.ts +557 -0
  300. package/extensions/permissions/project-trust.ts +27 -11
  301. package/extensions/permissions/protected-paths.ts +14 -2
  302. package/extensions/permissions/session-grant.ts +6 -0
  303. package/extensions/permissions/settings.ts +166 -31
  304. package/extensions/permissions/subagent-gate.ts +25 -0
  305. package/extensions/permissions/workspace.ts +78 -0
  306. package/extensions/plan-mode/index.ts +276 -92
  307. package/extensions/plan-mode/reminder.ts +89 -21
  308. package/extensions/plan-mode/run-state.ts +26 -0
  309. package/extensions/plan-mode/texts.ts +177 -0
  310. package/extensions/plan-mode/viewer.ts +92 -27
  311. package/extensions/plugins/counts.ts +3 -3
  312. package/extensions/plugins/index.ts +64 -46
  313. package/extensions/plugins/install/install.ts +16 -0
  314. package/extensions/plugins/marketplace/sync.ts +9 -3
  315. package/extensions/plugins/marketplace/types.ts +3 -1
  316. package/extensions/plugins/panel/keys.ts +21 -24
  317. package/extensions/plugins/panel/render.ts +12 -6
  318. package/extensions/plugins/shell-expand.ts +107 -0
  319. package/extensions/plugins/template.ts +32 -15
  320. package/extensions/powershell/child.ts +7 -0
  321. package/extensions/powershell/description.ts +38 -6
  322. package/extensions/powershell/guards.ts +1 -1
  323. package/extensions/powershell/index.ts +14 -7
  324. package/extensions/recap/index.ts +73 -71
  325. package/extensions/recap/prompt.ts +2 -2
  326. package/extensions/recap/scheduler.ts +3 -5
  327. package/extensions/search-tools/grep-merge.ts +39 -0
  328. package/extensions/search-tools/index.ts +0 -0
  329. package/extensions/send-now/chord.ts +62 -0
  330. package/extensions/send-now/index.ts +133 -0
  331. package/extensions/session-title/index.ts +3 -0
  332. package/extensions/session-title/title.ts +4 -1
  333. package/extensions/skill/announce.ts +30 -0
  334. package/extensions/skill/code-review.ts +97 -0
  335. package/extensions/skill/index.ts +469 -132
  336. package/extensions/skill/invoke.ts +118 -75
  337. package/extensions/skill/listing.ts +133 -9
  338. package/extensions/skill/panel/keys.ts +15 -16
  339. package/extensions/skill/panel/render.ts +5 -2
  340. package/extensions/skill/panel/state.ts +12 -1
  341. package/extensions/subagents/agent-args.ts +39 -0
  342. package/extensions/subagents/agent-description.ts +132 -0
  343. package/extensions/subagents/agents.ts +146 -31
  344. package/extensions/subagents/default-model.ts +2 -3
  345. package/extensions/subagents/delegation-steer.ts +4 -4
  346. package/extensions/subagents/fork-cache.ts +82 -0
  347. package/extensions/subagents/guide-agent.ts +189 -0
  348. package/extensions/subagents/hand-back-review.ts +3 -0
  349. package/extensions/subagents/index.ts +1594 -852
  350. package/extensions/subagents/live-runs.ts +26 -6
  351. package/extensions/subagents/model-select.ts +38 -25
  352. package/extensions/subagents/outcome.ts +5 -2
  353. package/extensions/subagents/panel-keys.ts +73 -31
  354. package/extensions/subagents/panel-render.ts +120 -96
  355. package/extensions/subagents/panel-widget.ts +60 -89
  356. package/extensions/subagents/parent-mode.ts +50 -0
  357. package/extensions/subagents/pending-claim.ts +18 -1
  358. package/extensions/subagents/runner.ts +135 -80
  359. package/extensions/subagents/runs.ts +44 -5
  360. package/extensions/subagents/send-to-main-tool.ts +1 -1
  361. package/extensions/subagents/session-turns.ts +32 -8
  362. package/extensions/subagents/shell-panel.ts +36 -178
  363. package/extensions/subagents/tasks-dialog.ts +170 -0
  364. package/extensions/subagents/viewport-mirror.ts +95 -0
  365. package/extensions/subagents/worktree.ts +57 -13
  366. package/extensions/system-prompt/environment.ts +19 -19
  367. package/extensions/system-prompt/idle-turn.ts +65 -0
  368. package/extensions/system-prompt/index.ts +165 -47
  369. package/extensions/system-prompt/template.ts +34 -51
  370. package/extensions/system-prompt/tiers/common.ts +23 -64
  371. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  372. package/extensions/system-prompt/tiers/low.ts +67 -45
  373. package/extensions/system-prompt/tiers/mid.ts +96 -38
  374. package/extensions/system-reminder/index.ts +204 -70
  375. package/extensions/tasks/descriptions.ts +190 -0
  376. package/extensions/tasks/index.ts +120 -41
  377. package/extensions/tasks/store.ts +83 -16
  378. package/extensions/tool-call-corruption/child.ts +7 -0
  379. package/extensions/tool-call-corruption/index.ts +237 -0
  380. package/extensions/tool-search/index.ts +366 -144
  381. package/extensions/tool-style/index.ts +68 -10
  382. package/extensions/tool-style/write-description.ts +40 -0
  383. package/extensions/tool-style/write-render.ts +237 -0
  384. package/extensions/turn-duration/index.ts +7 -3
  385. package/extensions/turn-duration/line.ts +47 -4
  386. package/extensions/web/backends.ts +6 -9
  387. package/extensions/web/description.ts +50 -0
  388. package/extensions/web/index.ts +118 -53
  389. package/extensions/web/native-call.ts +15 -0
  390. package/extensions/web/native-results.ts +41 -0
  391. package/extensions/web-fetch/description.ts +42 -0
  392. package/extensions/web-fetch/extract.ts +53 -6
  393. package/extensions/web-fetch/index.ts +125 -76
  394. package/extensions/web-fetch/page-content.ts +19 -0
  395. package/extensions/web-fetch/response.ts +49 -0
  396. package/extensions/web-fetch/summarize.ts +9 -10
  397. package/extensions/workflow/agent-session.ts +108 -18
  398. package/extensions/workflow/args.ts +24 -0
  399. package/extensions/workflow/description.ts +67 -0
  400. package/extensions/workflow/globals.ts +17 -3
  401. package/extensions/workflow/index.ts +157 -162
  402. package/extensions/workflow/records.ts +1 -0
  403. package/extensions/workflow/run-manager.ts +33 -6
  404. package/extensions/workflow/saved-workflows.ts +3 -2
  405. package/extensions/workflow/types.ts +22 -1
  406. package/extensions/workflow/viewer.ts +54 -24
  407. package/extensions/workflow/widget.ts +20 -0
  408. package/extensions/worktree/descriptions.ts +91 -0
  409. package/extensions/worktree/guards.ts +316 -54
  410. package/extensions/worktree/index.ts +162 -44
  411. package/extensions/worktree/powershell-guards.ts +167 -0
  412. package/extensions/worktree/rewrite.ts +14 -16
  413. package/package.json +21 -7
  414. package/skills/artifact-design/SKILL.md +85 -0
  415. package/skills/code-review/SKILL.md +20 -20
  416. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  417. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  418. package/skills/code-review/cells/high-8-inline.md +122 -0
  419. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  420. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  421. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  422. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  423. package/skills/code-review/cells/minimal-prompt.md +32 -0
  424. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  425. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  426. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  427. package/skills/loop/SKILL.md +11 -0
  428. package/skills/security-review/SKILL.md +2 -5
  429. package/skills/simplify/SKILL.md +7 -5
  430. package/skills/workflow-authoring/SKILL.md +143 -0
  431. package/themes/onecode-light.json +1 -1
  432. package/themes/onecode.json +1 -1
  433. package/types/app.d.ts +58 -0
  434. package/types/community-packages.d.ts +26 -1
  435. package/extensions/auto-mode/recoverability.ts +0 -229
  436. package/extensions/lib/capability-index.ts +0 -466
  437. package/extensions/lib/model-facts.generated.json +0 -2675
  438. package/extensions/lib/model-facts.ts +0 -162
  439. package/extensions/tool-search/announce.ts +0 -42
@@ -28,6 +28,23 @@ export interface PendingRun {
28
28
  taskId: string;
29
29
  }
30
30
 
31
+ /** The runs by name and task id, comma-separated. */
32
+ function namedRuns(runs: readonly PendingRun[]): string {
33
+ return runs.map((run) => `${run.name} (task ${run.taskId})`).join(", ");
34
+ }
35
+
36
+ /**
37
+ * The background agents still working when a compaction summarized the calls
38
+ * that started them: the new epoch's history no longer shows them, so a model
39
+ * that is not told could start the same work again or report a result it
40
+ * never got (lifecycle-grid-hunt round 3, R3-M27; Claude Code re-attaches its
41
+ * task status after a compaction).
42
+ */
43
+ export function runningAfterCompactionReminder(runs: readonly PendingRun[]): string | undefined {
44
+ if (runs.length === 0) return undefined;
45
+ return `Background agents you started before this conversation was compacted are still running: ${namedRuns(runs)}. Each one notifies you when it finishes; do not start the same work again. Use task_output to check on one, or task_stop to end it.`;
46
+ }
47
+
31
48
  /**
32
49
  * The one-shot for a turn that ended with runs still pending, or undefined when
33
50
  * there is nothing to say. Named runs, so a model with several in flight knows
@@ -35,7 +52,7 @@ export interface PendingRun {
35
52
  */
36
53
  export function pendingClaimReminder(runs: readonly PendingRun[]): string | undefined {
37
54
  if (runs.length === 0) return undefined;
38
- const named = runs.map((run) => `${run.name} (task ${run.taskId})`).join(", ");
55
+ const named = namedRuns(runs);
39
56
  const subject = runs.length === 1 ? "It has" : "They have";
40
57
  return (
41
58
  `You ended that turn while ${named} ${runs.length === 1 ? "was" : "were"} still running. ${subject} not reported yet, ` +
@@ -12,19 +12,25 @@
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";
32
+ import { parentModeExtension } from "./parent-mode.ts";
33
+ import { type DeferRequest, deferLikeParentExtension, injectedByParent } from "../lib/deferred.ts";
28
34
  import type { ChildHandle, ChildOutcome, RpcChildHandle } from "./outcome.ts";
29
35
  import { sendToMainTool } from "./send-to-main-tool.ts";
30
36
  import { SessionTurnTracker } from "./session-turns.ts";
@@ -53,34 +59,12 @@ const WALL_CLOCK_CAP_MS = 30 * 60 * 1000;
53
59
 
54
60
  /**
55
61
  * 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
62
+ * them. `SubagentHandback` only returns the child's report to its caller.
63
+ * `Agent` is the injected child-spawn tool (index.ts) — the spawn itself
57
64
  * is free (matching the main conversation's auto-allowed Agent tool); every
58
65
  * tool call the spawned grandchild makes still runs through the real gate.
59
66
  */
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"));
67
+ const NEVER_GATE = new Set(["structured_output", "SendMessage", SUBAGENT_HANDBACK, "Agent"]);
84
68
 
85
69
  type Session = AgentSession;
86
70
 
@@ -92,8 +76,15 @@ interface ChildSessionSpec {
92
76
  agent?: AgentDefinition;
93
77
  /** Parent session file — present for a fork run (inherit the parent transcript). */
94
78
  forkFrom?: string;
79
+ /** Messages appended to a fork's transcript before its task (e.g. `/btw`'s side exchange). */
80
+ forkMessages?: Message[];
95
81
  /** The parent's current system prompt, applied to a fork so it continues as the parent. */
96
82
  parentSystemPrompt?: string;
83
+ /**
84
+ * The parent's last provider request, for a fork: every fork request is
85
+ * built from it so the fork reads the parent's prompt cache (fork-cache.ts).
86
+ */
87
+ parentRequest?: RequestCapture;
97
88
  /** Existing persisted session to resume (SendMessage to a finished agent). */
98
89
  sessionFile?: string;
99
90
  /** Where a new run's persisted session lands; falls back to an in-memory session. */
@@ -111,6 +102,12 @@ interface ChildSessionSpec {
111
102
  onMessageToMain?: (message: string, summary?: string) => void;
112
103
  /** Extra injected tools (e.g. the child-spawn Agent tool for depth-0 children). */
113
104
  extraTools?: ToolDefinition[];
105
+ /**
106
+ * The main session's tool call that started the run's current turn, read
107
+ * per gated call (a SendMessage can start a later turn). The permission
108
+ * bridge passes it on as `ChildToolCall.parentToolCallId`.
109
+ */
110
+ mainToolCallId?: () => string | undefined;
114
111
  }
115
112
 
116
113
  export interface SubagentRunOptions extends ChildSessionSpec {
@@ -125,11 +122,33 @@ export interface ResidentRunOptions extends Omit<ChildSessionSpec, "sessionFile"
125
122
  onProgress: (toolCalls: number, lastText: string, usage: UsageTotals) => void;
126
123
  /** Fires at the end of EVERY turn (initial task and later messages alike). */
127
124
  onTurnEnd: (outcome: ChildOutcome) => void;
128
- onExit?: () => void;
125
+ /** Fires once when the resident exits; a returned promise (worktree cleanup) is part of kill()'s settle. */
126
+ onExit?: () => void | Promise<unknown>;
129
127
  /** Optional live sink for the subagent panel (activity + transcript blocks). */
130
128
  sink?: LiveSink;
131
129
  }
132
130
 
131
+ /**
132
+ * A child's session manager: a resume reopens its file; otherwise a fork
133
+ * clones the parent transcript (when the parent has written one) and anything
134
+ * else starts empty, and `forkMessages` are appended so the child sees them before
135
+ * its task and a later resume finds them in its file. Exported for tests.
136
+ */
137
+ export function newChildSessionManager(spec: Pick<ChildSessionSpec, "cwd" | "forkFrom" | "forkMessages" | "sessionFile" | "sessionDir">): SessionManager {
138
+ // The resume cwd is passed explicitly: a worktree run's persisted cwd may
139
+ // be gone by the time it is messaged (index.ts substitutes the parent cwd).
140
+ if (spec.sessionFile) return SessionManager.open(spec.sessionFile, undefined, spec.cwd);
141
+ // pi writes a session file with its first assistant reply, so a parent with
142
+ // no transcript yet (or run with --no-session) has nothing to clone.
143
+ const manager = spec.forkFrom && existsSync(spec.forkFrom)
144
+ ? SessionManager.forkFrom(spec.forkFrom, spec.cwd, spec.sessionDir)
145
+ : spec.sessionDir
146
+ ? SessionManager.create(spec.cwd, spec.sessionDir)
147
+ : SessionManager.inMemory(spec.cwd);
148
+ for (const message of spec.forkMessages ?? []) manager.appendMessage(message);
149
+ return manager;
150
+ }
151
+
133
152
  /** Prepend a one-time spawn note (e.g. a model fallback) to an outcome's output. */
134
153
  function withNote(outcome: ChildOutcome, note: string | undefined): ChildOutcome {
135
154
  return note ? { ...outcome, output: `${note}\n\n${outcome.output}` } : outcome;
@@ -145,6 +164,10 @@ export class SubagentRuntime {
145
164
  * still-connecting (possibly empty) one.
146
165
  */
147
166
  private readonly getMcpTools: () => ToolDefinition[] | Promise<ToolDefinition[]>;
167
+ /** The tools the parent session defers (lib/deferred.ts queryDeferred): a child defers the ones injected into it the same way. */
168
+ private readonly getParentDeferred: () => DeferRequest[];
169
+ /** The main session's live permission mode, for a fork: told to it when it differs from what it was told (parent-mode.ts). */
170
+ private readonly getParentMode: (() => string) | undefined;
148
171
  /**
149
172
  * The parent's permission decision closure (undefined until permissions
150
173
  * publishes it), wrapped so every call carries the asking run's name: the
@@ -158,10 +181,14 @@ export class SubagentRuntime {
158
181
  /** A child extension handler threw (pi swallows it otherwise); surfaced to the user by index.ts. */
159
182
  private readonly onExtensionError: (runName: string | undefined, error: ExtensionError) => void;
160
183
  private readonly onModelUnusable: (model: string, reason: string) => void;
184
+ /** A child's edit or write changed a file (lib/child-writes.ts); index.ts tells the parent's file-tracker. */
185
+ private readonly onChildWrite: (path: string, runName: string | undefined) => void;
161
186
  /** Child session id → run name, for the bridge wrapper above. */
162
187
  private readonly runNames = new Map<string, string>();
163
188
  /** Child session id → agent type (`explore`, …), for the hook payload's `agent_type`. */
164
189
  private readonly agentTypes = new Map<string, string>();
190
+ /** Child session id → the main-session call that started its turn, for the bridge wrapper. */
191
+ private readonly mainToolCalls = new Map<string, () => string | undefined>();
165
192
  /**
166
193
  * Parallel children with the same request prefix (same agent prompt and
167
194
  * model; forks share the parent's) let the first one start streaming before
@@ -178,8 +205,14 @@ export class SubagentRuntime {
178
205
  getHookBridge: () => HookBridge | undefined,
179
206
  onExtensionError: (runName: string | undefined, error: ExtensionError) => void,
180
207
  onModelUnusable: (model: string, reason: string) => void,
208
+ onChildWrite: (path: string, runName: string | undefined) => void,
209
+ getParentDeferred: () => DeferRequest[],
210
+ getParentMode: (() => string) | undefined,
181
211
  ) {
182
212
  this.modelRuntime = modelRuntime;
213
+ this.getParentDeferred = getParentDeferred;
214
+ this.getParentMode = getParentMode;
215
+ this.onChildWrite = onChildWrite;
183
216
  this.baseCwd = baseCwd;
184
217
  this.getMcpTools = getMcpTools;
185
218
  this.getHookBridge = getHookBridge;
@@ -191,7 +224,11 @@ export class SubagentRuntime {
191
224
  const namedBridge: PermissionBridge = (call) => {
192
225
  const bridge = getPermissionBridge();
193
226
  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) });
227
+ return bridge({
228
+ ...call,
229
+ agent: call.agent ?? (call.sessionId ? this.runNames.get(call.sessionId) : undefined),
230
+ parentToolCallId: call.parentToolCallId ?? (call.sessionId ? this.mainToolCalls.get(call.sessionId)?.() : undefined),
231
+ });
195
232
  };
196
233
  this.getPermissionBridge = () => (getPermissionBridge() ? namedBridge : undefined);
197
234
  }
@@ -203,11 +240,14 @@ export class SubagentRuntime {
203
240
  getHookBridge: () => HookBridge | undefined = () => undefined,
204
241
  onExtensionError: (runName: string | undefined, error: ExtensionError) => void = () => {},
205
242
  onModelUnusable: (model: string, reason: string) => void = () => {},
243
+ onChildWrite: (path: string, runName: string | undefined) => void = () => {},
244
+ getParentDeferred: () => DeferRequest[] = () => [],
245
+ getParentMode?: () => string,
206
246
  ): Promise<SubagentRuntime> {
207
247
  const modelRuntime = await createSharedModelRuntime(getAgentDir());
208
248
  // Prime the catalog once; spawns read the live snapshot (see resolveModel).
209
249
  await modelRuntime.getAvailable();
210
- return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable);
250
+ return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable, onChildWrite, getParentDeferred, getParentMode);
211
251
  }
212
252
 
213
253
  /**
@@ -234,15 +274,27 @@ export class SubagentRuntime {
234
274
  * prompt and default tools, review S6); a named agent carries its own; a
235
275
  * plain run gets pi's base prompt. Built fresh per session (agent-loader.ts).
236
276
  */
237
- private childLoaderOptions(spec: ChildSessionSpec): AgentLoaderOptions {
238
- const systemPrompt = SubagentRuntime.isFork(spec) ? spec.parentSystemPrompt : spec.agent?.systemPrompt;
277
+ private childLoaderOptions(spec: ChildSessionSpec, handback: HandbackSlot): AgentLoaderOptions {
278
+ const fork = SubagentRuntime.isFork(spec);
279
+ const systemPrompt = fork ? spec.parentSystemPrompt : spec.agent?.systemPrompt;
239
280
  return {
240
281
  cwd: this.baseCwd,
241
282
  agentDir: getAgentDir(),
242
283
  systemPrompt,
243
284
  neverGate: NEVER_GATE,
244
285
  extraExtensionPaths: CHILD_EXTENSION_PATHS,
245
- // claude-context (above) injects # claudeMd on the child's session_start;
286
+ // Every child hands back through SubagentHandback (deferred in a fork,
287
+ // lib/subagent-handback.ts). Inline extensions load after the paths
288
+ // above, so the fork-cache one, last, sees the final body.
289
+ extraFactories: [
290
+ // The tools injected below (the spawn and cron tools, the shared MCP
291
+ // tools) are deferred as the parent defers them: their owners run there.
292
+ deferLikeParentExtension(this.getParentDeferred(), injectedByParent(spec.extraTools ?? [])),
293
+ subagentHandbackExtension(handback, { deferred: fork }),
294
+ ...(fork && this.getParentMode ? [parentModeExtension(this.getParentMode)] : []),
295
+ ...(fork && spec.parentRequest ? [forkCacheExtension(spec.parentRequest)] : []),
296
+ ],
297
+ // claude-context (above) injects the CLAUDE.md instructions on the child's session_start;
246
298
  // pi must not append the same files to the system prompt as well.
247
299
  noContextFiles: true,
248
300
  getPermissionBridge: this.getPermissionBridge,
@@ -271,7 +323,7 @@ export class SubagentRuntime {
271
323
  /**
272
324
  * Hold a run until a sibling with the same request prefix has started
273
325
  * streaming (see `warmGate`): same prompt identity, same cwd (the child's
274
- * `# claudeMd` block names its paths) and same model. Resolves to the release
326
+ * instructions block names its paths) and same model. Resolves to the release
275
327
  * for the caller's `finally`.
276
328
  */
277
329
  private admitPrefix(spec: Pick<ChildSessionSpec, "cwd" | "forkFrom" | "parentSystemPrompt" | "agent">, session: Session): Promise<Release> {
@@ -288,9 +340,12 @@ export class SubagentRuntime {
288
340
  onSettled?: () => void,
289
341
  sink?: LiveSink,
290
342
  ): () => void {
343
+ const childWrote = childWriteWatcher(session.sessionManager?.getCwd?.() ?? this.baseCwd);
291
344
  return session.subscribe((event) => {
292
345
  try {
293
346
  const settled = tracker.process(event as never);
347
+ const wrote = childWrote(event as never);
348
+ if (wrote) this.onChildWrite(wrote, this.runNames.get(session.sessionId));
294
349
  const e = event as { type?: string; toolName?: string; args?: unknown; isError?: boolean; result?: unknown; message?: { role?: string; content?: unknown } };
295
350
  if (e.type === "tool_execution_start") {
296
351
  sink?.onBlock?.({ kind: "call", tool: e.toolName ?? "tool", text: summarizeArgs(e.args) });
@@ -350,20 +405,14 @@ export class SubagentRuntime {
350
405
  * inherits the parent transcript + system prompt; a fresh named run gets the
351
406
  * agent's own prompt and toolset.
352
407
  */
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;
408
+ private async buildChildSession(spec: ChildSessionSpec, handback: HandbackSlot): Promise<{ session: Session; note?: string }> {
409
+ // The MCP wait first: the loader takes the parent's deferrals, which a server
410
+ // connecting during the wait adds to (it is instant once the connects settled).
411
+ const mcpTools = await this.getMcpTools();
412
+ const loader = await buildAgentLoader(this.childLoaderOptions(spec, handback));
413
+ const newSessionManager = () => newChildSessionManager(spec);
414
+ // A fork keeps the parent's toolset; only a named agent carries tool lists.
415
+ const agent = spec.forkFrom ? undefined : spec.agent;
367
416
  const make = async (model: string | undefined): Promise<Session> => {
368
417
  const resolvedModel = model ? await this.resolveModel(model) : undefined;
369
418
  const session = await openChildSession({
@@ -374,10 +423,9 @@ export class SubagentRuntime {
374
423
  modelRuntime: this.modelRuntime,
375
424
  model: resolvedModel as never,
376
425
  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,
426
+ // The allowlist with the plumbing re-added, and the denylist (CC's "All
427
+ // tools except …" shape), which filters injected customTools too.
428
+ ...agentToolOptions(agent),
381
429
  customTools: [sendToMainTool((m, s) => spec.onMessageToMain?.(m, s)), ...(spec.extraTools ?? []), ...mcpTools],
382
430
  sessionManager: newSessionManager(),
383
431
  },
@@ -386,21 +434,17 @@ export class SubagentRuntime {
386
434
  });
387
435
  // An allowlist that matched nothing real would run a tool-less agent
388
436
  // (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
- ) {
437
+ const unusable = unusableAllowlistError(
438
+ agent,
439
+ session.getAllTools().map((t) => t.name),
440
+ );
441
+ if (unusable) {
396
442
  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
- );
443
+ throw new Error(unusable);
401
444
  }
402
445
  if (spec.name) this.runNames.set(session.sessionManager.getSessionId(), spec.name);
403
446
  if (spec.agent) this.agentTypes.set(session.sessionManager.getSessionId(), spec.agent.name);
447
+ if (spec.mainToolCallId) this.mainToolCalls.set(session.sessionManager.getSessionId(), spec.mainToolCallId);
404
448
  return session;
405
449
  };
406
450
  try {
@@ -421,7 +465,8 @@ export class SubagentRuntime {
421
465
  private discard(session: Session): void {
422
466
  this.runNames.delete(session.sessionManager.getSessionId());
423
467
  this.agentTypes.delete(session.sessionManager.getSessionId());
424
- session.dispose();
468
+ this.mainToolCalls.delete(session.sessionManager.getSessionId());
469
+ disposeChildSession(session);
425
470
  }
426
471
 
427
472
  /**
@@ -452,17 +497,17 @@ export class SubagentRuntime {
452
497
  // corrupt session file, loader error) must resolve to a failed
453
498
  // outcome, never reject handle.result — call sites like the
454
499
  // SendMessage resume path consume it with a bare .then().
455
- const built = await this.buildChildSession(options);
500
+ const built = await this.buildChildSession(options, tracker);
456
501
  session = built.session;
457
502
  spawnNote = built.note;
458
503
  unsubscribe = this.wireTracker(session, tracker, options.onProgress, undefined, options.sink);
459
504
  // A resume continues a unique history; only a fresh run shares a prefix.
460
505
  const releasePrefix = options.sessionFile ? undefined : await this.admitPrefix(options, session);
461
506
  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");
507
+ // abort() is a no-op before the prompt starts. Every cancellation
508
+ // source (signal, kill, wall-clock cap) marks the tracker first,
509
+ // including one that landed during construction or the prefix wait.
510
+ if (tracker.aborted) throw new Error("cancelled before the first request was sent");
466
511
  // No template/command expansion: a task starting with "/" is text, not
467
512
  // a command lookup (pi's own sendUserMessage does the same — L2).
468
513
  await session.prompt(options.task, { expandPromptTemplates: false });
@@ -504,11 +549,12 @@ export class SubagentRuntime {
504
549
  * while an idle resident lives until task_stop / session shutdown.
505
550
  */
506
551
  async runResident(options: ResidentRunOptions): Promise<RpcChildHandle> {
507
- const built = await this.buildChildSession(options);
552
+ const tracker = new SessionTurnTracker();
553
+ const built = await this.buildChildSession(options, tracker);
508
554
  const session = built.session;
509
555
  let spawnNote = built.note;
510
- const tracker = new SessionTurnTracker();
511
556
  let exited = false;
557
+ let exitPromise: Promise<void> | undefined;
512
558
  let turnActive = false;
513
559
  let firstTurn = true;
514
560
  let turnTimer: ReturnType<typeof setTimeout> | undefined;
@@ -632,10 +678,18 @@ export class SubagentRuntime {
632
678
  await session.steer(message);
633
679
  return "steered";
634
680
  },
681
+ // The background extension is not loaded in a child, so the parent's
682
+ // cron fires record what they sent straight into the child's session.
683
+ conversation: {
684
+ getBranch: () => session.sessionManager.getBranch(),
685
+ appendEntry: (customType, data) => {
686
+ session.sessionManager.appendCustomEntry(customType, data);
687
+ },
688
+ },
635
689
  busy: () => turnActive || !session.isIdle,
636
690
  exited: () => exited,
637
691
  kill: () => {
638
- if (exited) return Promise.resolve();
692
+ if (exited) return exitPromise ?? Promise.resolve();
639
693
  exited = true;
640
694
  pendingSend = undefined;
641
695
  // Report an in-flight turn as terminated, not as a normal completion:
@@ -645,14 +699,15 @@ export class SubagentRuntime {
645
699
  finishTurn(tracker.turnOutcome());
646
700
  // onExit only after the abort settles: it triggers worktree cleanup,
647
701
  // which must not race an aborting session still writing files. The
648
- // returned promise lets session_shutdown wait for exactly that.
649
- return session
702
+ // returned promise settles after that cleanup too, so session_shutdown
703
+ // waits for exactly that.
704
+ return (exitPromise = session
650
705
  .abort()
651
706
  .catch(() => undefined)
652
- .finally(() => {
707
+ .then(async () => {
653
708
  discard();
654
- options.onExit?.();
655
- });
709
+ await Promise.resolve(options.onExit?.()).catch(() => undefined);
710
+ }));
656
711
  },
657
712
  release: () => {
658
713
  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,21 @@ 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
+ /** Keep only the records `keep` accepts; names held for runs being spawned stay held. */
144
+ retain(keep: (record: AgentRunRecord) => boolean): void {
145
+ for (const record of [...this.byId.values()]) if (!keep(record)) this.remove(record.taskId);
146
+ }
147
+
148
+ /** Hold a name until its record is added. */
123
149
  reserve(name: string): void {
124
150
  this.reserved.add(name);
125
151
  }
126
152
 
153
+ /** Free a held name whose spawn failed validation, so a corrected retry gets it. */
154
+ release(name: string): void {
155
+ this.reserved.delete(name);
156
+ }
157
+
127
158
  /** Latest wins per name, matching Claude Code's semantics. */
128
159
  add(record: AgentRunRecord): void {
129
160
  this.reserved.delete(record.name);
@@ -131,6 +162,14 @@ export class RunRegistry {
131
162
  this.byId.set(record.taskId, record);
132
163
  }
133
164
 
165
+ /** Forget a run that never started, so neither list_agents nor a resume offers it. */
166
+ remove(taskId: string): void {
167
+ const record = this.byId.get(taskId);
168
+ if (!record) return;
169
+ this.byId.delete(taskId);
170
+ if (this.byName.get(record.name) === record) this.byName.delete(record.name);
171
+ }
172
+
134
173
  /** Every name in use: recorded runs plus names reserved for runs being spawned. */
135
174
  names(): string[] {
136
175
  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
  }