@selesai/code 0.5.20 → 0.5.21

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 (263) hide show
  1. package/dist/extensions/handoff-new.test.ts +8 -9
  2. package/dist/extensions/handoff-new.ts +6 -8
  3. package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
  4. package/dist/extensions/pi-subagents/README.md +602 -220
  5. package/dist/extensions/pi-subagents/banner.png +0 -0
  6. package/dist/extensions/pi-subagents/index.ts +1 -0
  7. package/dist/extensions/pi-subagents/install.mjs +3 -3
  8. package/dist/extensions/pi-subagents/package-lock.json +3405 -0
  9. package/dist/extensions/pi-subagents/package.json +26 -14
  10. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  11. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  12. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
  13. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
  14. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  15. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  16. package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
  17. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
  18. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
  19. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  20. package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
  21. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
  22. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  23. package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
  24. package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
  25. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
  26. package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
  27. package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
  28. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
  29. package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
  30. package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
  31. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
  32. package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
  33. package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
  34. package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
  35. package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
  36. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
  37. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
  38. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
  39. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
  40. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
  41. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
  42. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
  43. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
  44. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
  45. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
  46. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
  47. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
  48. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
  49. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
  50. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
  51. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
  52. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
  53. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
  54. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
  55. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
  56. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
  57. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
  58. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
  59. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
  60. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
  61. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
  62. package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
  63. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
  64. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
  65. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
  66. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
  67. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
  68. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
  69. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
  70. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
  71. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
  72. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
  73. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
  74. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
  75. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
  76. package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
  77. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
  78. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
  79. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  80. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
  81. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
  82. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
  83. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
  84. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
  85. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
  86. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
  87. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
  88. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
  89. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
  90. package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
  92. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
  93. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
  94. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
  95. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
  96. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
  97. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
  98. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
  99. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
  100. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
  101. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
  102. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
  103. package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
  104. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
  105. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
  106. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
  107. package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
  108. package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
  109. package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
  110. package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
  111. package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
  112. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
  113. package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
  114. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
  115. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
  116. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
  117. package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
  118. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
  119. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
  120. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
  121. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
  122. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
  124. package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
  125. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
  126. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
  127. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
  128. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
  129. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
  130. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
  131. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
  132. package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
  133. package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
  134. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
  135. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
  136. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
  137. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
  138. package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
  139. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
  140. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
  141. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
  142. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
  143. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
  144. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
  145. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
  146. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
  147. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
  148. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
  149. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
  150. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
  151. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
  152. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
  153. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
  154. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
  155. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
  156. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
  157. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
  158. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
  159. package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
  160. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
  161. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
  162. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
  163. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
  164. package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
  165. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
  166. package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
  167. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  168. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  169. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
  170. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
  171. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
  172. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
  173. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
  174. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
  175. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
  176. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
  177. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
  178. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
  179. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
  180. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
  181. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
  182. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
  183. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
  184. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
  185. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
  186. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
  187. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
  188. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
  189. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
  190. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
  191. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
  192. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
  193. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
  194. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
  195. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
  196. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
  197. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
  198. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
  199. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
  200. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
  201. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
  202. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
  203. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
  204. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
  205. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
  206. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
  207. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
  208. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
  209. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
  210. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
  211. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
  212. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
  213. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
  214. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
  215. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
  216. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
  217. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
  218. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
  219. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
  220. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
  221. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
  222. package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
  223. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
  224. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
  225. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
  226. package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
  227. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
  228. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
  229. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
  230. package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
  231. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
  232. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
  233. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
  234. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
  235. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
  236. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
  237. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
  238. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
  239. package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
  240. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
  241. package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
  245. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
  246. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
  247. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
  248. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
  249. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
  250. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
  251. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
  252. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
  253. package/dist/extensions/question/batch.ts +39 -0
  254. package/dist/extensions/question/dialog-adapter.ts +23 -1
  255. package/dist/extensions/question/index.ts +237 -7
  256. package/dist/extensions/question/schemas.ts +28 -15
  257. package/dist/extensions/question/tests/batch.test.ts +26 -0
  258. package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
  259. package/dist/extensions/question/tui-adapter.ts +2 -3
  260. package/dist/extensions/question/types.ts +37 -0
  261. package/dist/extensions/question/ui-protocol.ts +3 -3
  262. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  263. package/package.json +1 -1
@@ -5,8 +5,8 @@ import { createFileCoalescer } from "../../shared/file-coalescer.ts";
5
5
  import {
6
6
  SUBAGENT_ASYNC_COMPLETE_EVENT,
7
7
  type IntercomEventBus,
8
- type Usage,
9
8
  type NestedRunSummary,
9
+ type ParallelHandoffReference,
10
10
  type SubagentResultIntercomChild,
11
11
  type SubagentState,
12
12
  } from "../../shared/types.ts";
@@ -18,9 +18,12 @@ import {
18
18
  resolveSubagentResultStatus,
19
19
  } from "../../intercom/result-intercom.ts";
20
20
  import { projectNestedRegistryForRoot, sanitizeSummary } from "../shared/nested-events.ts";
21
+ import { resolveWatchPath } from "../../shared/utils.ts";
22
+ import type { CompletionNotifier, CompletionNotification } from "./notify.ts";
21
23
 
22
24
  const WATCHER_RESTART_DELAY_MS = 3000;
23
25
  const POLL_INTERVAL_MS = 3000;
26
+ const RETRY_DELAY_MS = 100;
24
27
 
25
28
  type ResultWatcherFs = Pick<typeof fs, "existsSync" | "readFileSync" | "unlinkSync" | "readdirSync" | "mkdirSync" | "realpathSync" | "watch">;
26
29
 
@@ -34,6 +37,7 @@ type ResultWatcherTimers = {
34
37
  type ResultWatcherDeps = {
35
38
  fs?: ResultWatcherFs;
36
39
  timers?: ResultWatcherTimers;
40
+ notifier?: Pick<CompletionNotifier, "deliver">;
37
41
  };
38
42
 
39
43
  type ResultFileChild = {
@@ -41,28 +45,22 @@ type ResultFileChild = {
41
45
  output?: string;
42
46
  error?: string;
43
47
  success?: boolean;
48
+ state?: string;
49
+ stopped?: boolean;
44
50
  sessionFile?: string;
45
51
  artifactPaths?: { outputPath?: string };
46
52
  intercomTarget?: string;
47
53
  children?: unknown;
48
54
  };
49
55
 
50
- type ResultFileData = {
51
- id?: string;
56
+ type ResultFileData = CompletionNotification & {
52
57
  runId?: string;
53
- agent?: string;
54
- success?: boolean;
55
- state?: string;
56
58
  mode?: string;
57
- summary?: string;
58
59
  results?: ResultFileChild[];
59
60
  nestedChildren?: unknown;
60
- sessionId?: string;
61
- cwd?: string;
62
- sessionFile?: string;
63
61
  asyncDir?: string;
64
62
  intercomTarget?: string;
65
- totalChildUsage?: Usage;
63
+ parallelHandoff?: ParallelHandoffReference;
66
64
  };
67
65
 
68
66
  function sanitizeNestedResultChildren(value: unknown, resultPath: string, label: string): NestedRunSummary[] | undefined {
@@ -78,29 +76,24 @@ function sanitizeNestedResultChildren(value: unknown, resultPath: string, label:
78
76
  return children.length ? children : undefined;
79
77
  }
80
78
 
81
- function getErrorCode(error: unknown): string | undefined {
82
- return typeof error === "object" && error !== null && "code" in error
83
- ? (error as NodeJS.ErrnoException).code
84
- : undefined;
79
+ function errorCode(error: unknown): string | undefined {
80
+ return typeof error === "object" && error !== null && "code" in error ? (error as NodeJS.ErrnoException).code : undefined;
85
81
  }
86
82
 
87
- function isNotFoundError(error: unknown): boolean {
88
- return getErrorCode(error) === "ENOENT";
83
+ function isNotFound(error: unknown): boolean {
84
+ return errorCode(error) === "ENOENT";
89
85
  }
90
86
 
91
- function shouldFallBackToPolling(error: unknown): boolean {
92
- const code = getErrorCode(error);
87
+ function shouldPoll(error: unknown): boolean {
88
+ const code = errorCode(error);
93
89
  return code === "EMFILE" || code === "ENOSPC";
94
90
  }
95
91
 
96
- function resolveNativeWatchDir(fsApi: ResultWatcherFs, resultsDir: string): string {
97
- try {
98
- return fsApi.realpathSync.native(resultsDir);
99
- } catch {
100
- return resultsDir;
101
- }
102
- }
103
-
92
+ /**
93
+ * Watches persisted async results for the session currently owned by this
94
+ * runtime. `stopResultWatcher()` revokes ownership before closing resources,
95
+ * so old callbacks can never emit or delete after reload/session replacement.
96
+ */
104
97
  export function createResultWatcher(
105
98
  pi: { events: IntercomEventBus },
106
99
  state: SubagentState,
@@ -109,18 +102,41 @@ export function createResultWatcher(
109
102
  deps: ResultWatcherDeps = {},
110
103
  ): {
111
104
  startResultWatcher: () => void;
112
- primeExistingResults: () => void;
105
+ primeExistingResults: (options?: { triggerTurn?: boolean }) => void;
113
106
  stopResultWatcher: () => void;
114
107
  } {
115
108
  const fsApi = deps.fs ?? fs;
116
109
  const timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval };
110
+ const notifier = deps.notifier ?? { deliver: async () => true };
111
+ const pendingTriggerTurn = new Map<string, boolean>();
112
+ const processing = new Set<string>();
113
+ let deliveryActive = true;
114
+ let deliveryEpoch = 0;
115
+ // The sole in-memory ownership lease. It is acquired for one active session
116
+ // and revoked before the watcher, queues, or callbacks are torn down.
117
+ let activeSessionId: string | null = null;
117
118
 
118
- const handleResult = async (file: string) => {
119
+ const ownsSession = (sessionId: string, epoch: number) => {
120
+ if (!deliveryActive || epoch !== deliveryEpoch) return false;
121
+ if (!activeSessionId && state.currentSessionId) activeSessionId = state.currentSessionId;
122
+ return activeSessionId === sessionId && state.currentSessionId === sessionId;
123
+ };
124
+
125
+ const scheduleResult = (file: string, triggerTurn: boolean, delayMs = 0) => {
126
+ const pendingMode = pendingTriggerTurn.get(file);
127
+ pendingTriggerTurn.set(file, pendingMode === false || !triggerTurn ? false : true);
128
+ state.resultFileCoalescer.schedule(file, delayMs);
129
+ };
130
+
131
+ const handleResult = async (file: string, triggerTurn: boolean) => {
119
132
  const resultPath = path.join(resultsDir, file);
120
- if (!fsApi.existsSync(resultPath)) return;
133
+ if (processing.has(file) || !fsApi.existsSync(resultPath)) return;
134
+ processing.add(file);
121
135
  try {
122
136
  const data = JSON.parse(fsApi.readFileSync(resultPath, "utf-8")) as ResultFileData;
123
- if (typeof data.sessionId !== "string" || data.sessionId !== state.currentSessionId) return;
137
+ if (typeof data.sessionId !== "string" || !data.sessionId) return;
138
+ const epoch = deliveryEpoch;
139
+ if (!ownsSession(data.sessionId, epoch)) return;
124
140
 
125
141
  const runId = data.runId ?? data.id ?? file.replace(/\.json$/i, "");
126
142
  const hasExplicitNestedChildren = data.nestedChildren !== undefined;
@@ -130,24 +146,32 @@ export function createResultWatcher(
130
146
  nestedChildren = compactNestedResultChildren(projectNestedRegistryForRoot(runId)?.children);
131
147
  } catch (error) {
132
148
  console.error(`Failed to enrich subagent result file '${resultPath}' with nested registry children; will retry later:`, error);
149
+ scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
133
150
  return;
134
151
  }
135
152
  }
136
- const now = Date.now();
153
+
137
154
  const completionKey = buildCompletionKey(data, `result:${file}`);
138
- if (markSeenWithTtl(state.completionSeen, completionKey, now, completionTtlMs)) {
139
- fsApi.unlinkSync(resultPath);
155
+ const lastSeenAt = state.completionSeen.get(completionKey);
156
+ if (lastSeenAt !== undefined && Date.now() - lastSeenAt > completionTtlMs) {
157
+ state.completionSeen.delete(completionKey);
158
+ } else if (lastSeenAt !== undefined) {
159
+ if (!ownsSession(data.sessionId, epoch) || !fsApi.existsSync(resultPath)) return;
160
+ try {
161
+ fsApi.unlinkSync(resultPath);
162
+ } catch (error) {
163
+ if (!isNotFound(error)) {
164
+ console.error(`Failed to remove delivered subagent result '${resultPath}'; will retry:`, error);
165
+ scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
166
+ }
167
+ }
140
168
  return;
141
169
  }
142
170
 
143
171
  const hasResultChildren = Array.isArray(data.results) && data.results.length > 0;
144
- const resultChildren = hasResultChildren
172
+ const resultChildren: ResultFileChild[] = hasResultChildren
145
173
  ? data.results!
146
- : [{
147
- agent: data.agent,
148
- output: data.summary,
149
- success: data.success,
150
- }];
174
+ : [{ agent: data.agent ?? undefined, output: data.summary, success: data.success }];
151
175
  const normalizedChildren = attachNestedChildrenToResultChildren(runId, resultChildren.map((result = {}, index): SubagentResultIntercomChild => {
152
176
  const baseOutput = result.output ?? data.summary;
153
177
  const hasRealOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0;
@@ -157,12 +181,16 @@ export function createResultWatcher(
157
181
  : output;
158
182
  const sessionPath = result.sessionFile ?? (resultChildren.length === 1 ? data.sessionFile : undefined);
159
183
  const childNestedChildren = sanitizeNestedResultChildren(result.children, resultPath, `results[${index}].children`);
184
+ const childState = result.state === "paused" || result.state === "stopped"
185
+ ? result.state
186
+ : result.stopped === true
187
+ ? "stopped"
188
+ : data.state === "paused" || (!hasResultChildren && (data.state === "stopped" || typeof result.success !== "boolean"))
189
+ ? data.state
190
+ : undefined;
160
191
  return {
161
192
  agent: result.agent ?? data.agent ?? `step-${index + 1}`,
162
- status: resolveSubagentResultStatus({
163
- success: result.success,
164
- state: data.state === "paused" || typeof result.success !== "boolean" ? data.state : undefined,
165
- }),
193
+ status: resolveSubagentResultStatus({ success: result.success, state: childState }),
166
194
  summary,
167
195
  index,
168
196
  artifactPath: result.artifactPaths?.outputPath,
@@ -173,11 +201,11 @@ export function createResultWatcher(
173
201
  }), nestedChildren);
174
202
 
175
203
  const intercomTarget = data.intercomTarget?.trim();
176
- if (intercomTarget) {
204
+ if (intercomTarget && triggerTurn) {
177
205
  const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain"
178
206
  ? data.mode
179
207
  : resultChildren.length > 1 ? "chain" : "single";
180
- const payload = buildSubagentResultIntercomPayload({
208
+ const delivered = await deliverSubagentResultIntercomEvent(pi.events, buildSubagentResultIntercomPayload({
181
209
  to: intercomTarget,
182
210
  runId,
183
211
  mode,
@@ -185,20 +213,45 @@ export function createResultWatcher(
185
213
  children: normalizedChildren,
186
214
  asyncId: data.id,
187
215
  asyncDir: data.asyncDir,
188
- });
189
- const delivered = await deliverSubagentResultIntercomEvent(pi.events, payload);
190
- if (!delivered) {
191
- console.error(`Subagent async grouped result intercom delivery was not acknowledged for '${resultPath}'.`);
192
- }
216
+ ...(data.parallelHandoff ? { parallelHandoff: data.parallelHandoff } : {}),
217
+ }));
218
+ if (!ownsSession(data.sessionId, epoch)) return;
219
+ if (!delivered) console.error(`Subagent async grouped result intercom delivery was not acknowledged for '${resultPath}'.`);
193
220
  }
194
221
 
195
- pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
222
+ const accepted = await notifier.deliver({
196
223
  ...data,
224
+ id: data.id ?? runId,
197
225
  runId,
226
+ triggerTurn,
198
227
  ...(nestedChildren?.length ? { nestedChildren } : {}),
199
228
  ...(Array.isArray(data.results) ? {
200
- results: hasResultChildren
201
- ? normalizedChildren.map((child, index) => ({
229
+ results: hasResultChildren ? normalizedChildren.map((child, index) => ({
230
+ ...data.results![index],
231
+ agent: child.agent,
232
+ status: child.status,
233
+ summary: child.summary,
234
+ index: child.index,
235
+ artifactPath: child.artifactPath,
236
+ sessionPath: child.sessionPath,
237
+ children: child.children,
238
+ })) : [],
239
+ } : {}),
240
+ });
241
+ if (!ownsSession(data.sessionId, epoch)) return;
242
+ if (!accepted) {
243
+ scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
244
+ return;
245
+ }
246
+ markSeenWithTtl(state.completionSeen, completionKey, Date.now(), completionTtlMs);
247
+ try {
248
+ pi.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, {
249
+ ...data,
250
+ runId,
251
+ triggerTurn,
252
+ ...(nestedChildren?.length ? { nestedChildren } : {}),
253
+ ...(Array.isArray(data.results) ? {
254
+ results: hasResultChildren ? normalizedChildren.map((child, index) => ({
202
255
  ...data.results![index],
203
256
  agent: child.agent,
204
257
  status: child.status,
@@ -207,40 +260,50 @@ export function createResultWatcher(
207
260
  artifactPath: child.artifactPath,
208
261
  sessionPath: child.sessionPath,
209
262
  children: child.children,
210
- }))
211
- : [],
212
- } : {}),
213
- });
214
- fsApi.unlinkSync(resultPath);
263
+ })) : [],
264
+ } : {}),
265
+ });
266
+ } catch (error) {
267
+ console.error(`Completion observer failed for '${resultPath}':`, error);
268
+ }
269
+ if (!ownsSession(data.sessionId, epoch) || !fsApi.existsSync(resultPath)) return;
270
+ try {
271
+ fsApi.unlinkSync(resultPath);
272
+ } catch (error) {
273
+ if (!isNotFound(error)) {
274
+ console.error(`Failed to remove delivered subagent result '${resultPath}'; will retry:`, error);
275
+ scheduleResult(file, triggerTurn, RETRY_DELAY_MS);
276
+ }
277
+ }
215
278
  } catch (error) {
216
- if (isNotFoundError(error)) return;
217
- console.error(`Failed to process subagent result file '${resultPath}':`, error);
279
+ if (!isNotFound(error)) console.error(`Failed to process subagent result file '${resultPath}':`, error);
280
+ } finally {
281
+ processing.delete(file);
218
282
  }
219
283
  };
220
284
 
221
285
  state.resultFileCoalescer = createFileCoalescer((file) => {
222
- void handleResult(file);
286
+ const triggerTurn = pendingTriggerTurn.get(file) !== false;
287
+ pendingTriggerTurn.delete(file);
288
+ void handleResult(file, triggerTurn);
223
289
  }, 50);
224
290
 
225
- const primeExistingResults = () => {
291
+ const primeExistingResults = (options: { triggerTurn?: boolean } = {}) => {
226
292
  try {
293
+ const triggerTurn = options.triggerTurn !== false;
227
294
  fsApi.readdirSync(resultsDir)
228
295
  .filter((f) => f.endsWith(".json"))
229
- .forEach((file) => state.resultFileCoalescer.schedule(file, 0));
296
+ .forEach((file) => scheduleResult(file, triggerTurn));
230
297
  } catch (error) {
231
- if (isNotFoundError(error)) return;
232
- console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
298
+ if (!isNotFound(error)) console.error(`Failed to scan subagent result directory '${resultsDir}':`, error);
233
299
  }
234
300
  };
235
301
 
236
- const startPollingFallback = (reason: unknown) => {
302
+ const startPolling = (reason: unknown) => {
237
303
  state.watcher?.close();
238
304
  state.watcher = null;
239
305
  if (state.watcherRestartTimer) return;
240
-
241
- console.error(
242
- `Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).`,
243
- );
306
+ console.error(`Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${errorCode(reason) ?? "unknown error"}).`);
244
307
  primeExistingResults();
245
308
  state.watcherRestartTimer = timers.setInterval(primeExistingResults, POLL_INTERVAL_MS);
246
309
  state.watcherRestartTimer.unref?.();
@@ -254,10 +317,7 @@ export function createResultWatcher(
254
317
  fsApi.mkdirSync(resultsDir, { recursive: true });
255
318
  startResultWatcher();
256
319
  } catch (error) {
257
- if (shouldFallBackToPolling(error)) {
258
- startPollingFallback(error);
259
- return;
260
- }
320
+ if (shouldPoll(error)) return startPolling(error);
261
321
  console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error);
262
322
  scheduleRestart();
263
323
  }
@@ -267,24 +327,23 @@ export function createResultWatcher(
267
327
 
268
328
  const startResultWatcher = () => {
269
329
  if (state.watcher) return;
330
+ activeSessionId = state.currentSessionId;
331
+ deliveryActive = true;
332
+ deliveryEpoch += 1;
270
333
  if (state.watcherRestartTimer) {
271
334
  timers.clearTimeout(state.watcherRestartTimer);
272
335
  timers.clearInterval(state.watcherRestartTimer);
273
336
  state.watcherRestartTimer = null;
274
337
  }
275
338
  try {
276
- const watchDir = resolveNativeWatchDir(fsApi, resultsDir);
277
- state.watcher = fsApi.watch(watchDir, (ev, file) => {
278
- if (ev !== "rename" || !file) return;
339
+ const watchDir = resolveWatchPath(resultsDir, fsApi.realpathSync.native);
340
+ state.watcher = fsApi.watch(watchDir, (event, file) => {
341
+ if (event !== "rename" || !file) return;
279
342
  const fileName = file.toString();
280
- if (!fileName.endsWith(".json")) return;
281
- state.resultFileCoalescer.schedule(fileName);
343
+ if (fileName.endsWith(".json")) scheduleResult(fileName, true);
282
344
  });
283
345
  state.watcher.on("error", (error) => {
284
- if (shouldFallBackToPolling(error)) {
285
- startPollingFallback(error);
286
- return;
287
- }
346
+ if (shouldPoll(error)) return startPolling(error);
288
347
  console.error(`Subagent result watcher failed for '${resultsDir}':`, error);
289
348
  state.watcher?.close();
290
349
  state.watcher = null;
@@ -292,10 +351,7 @@ export function createResultWatcher(
292
351
  });
293
352
  state.watcher.unref?.();
294
353
  } catch (error) {
295
- if (shouldFallBackToPolling(error)) {
296
- startPollingFallback(error);
297
- return;
298
- }
354
+ if (shouldPoll(error)) return startPolling(error);
299
355
  console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error);
300
356
  state.watcher = null;
301
357
  scheduleRestart();
@@ -303,6 +359,9 @@ export function createResultWatcher(
303
359
  };
304
360
 
305
361
  const stopResultWatcher = () => {
362
+ deliveryActive = false;
363
+ activeSessionId = null;
364
+ deliveryEpoch += 1;
306
365
  state.watcher?.close();
307
366
  state.watcher = null;
308
367
  if (state.watcherRestartTimer) {
@@ -311,6 +370,8 @@ export function createResultWatcher(
311
370
  }
312
371
  state.watcherRestartTimer = null;
313
372
  state.resultFileCoalescer.clear();
373
+ pendingTriggerTurn.clear();
374
+ processing.clear();
314
375
  };
315
376
 
316
377
  return { startResultWatcher, primeExistingResults, stopResultWatcher };
@@ -29,7 +29,19 @@ function exactAsyncLocation(id: string, asyncDirRoot: string, resultsDir: string
29
29
 
30
30
  function foregroundIds(state: SubagentState | undefined): string[] {
31
31
  if (!state) return [];
32
- return [...new Set([...state.foregroundControls.keys(), ...(state.foregroundRuns?.keys() ?? [])])];
32
+ const remembered = state.currentSessionId
33
+ ? [...(state.foregroundRuns?.values() ?? [])]
34
+ .filter((run) => run.sessionId === state.currentSessionId)
35
+ .map((run) => run.runId)
36
+ : [];
37
+ return [...new Set([...state.foregroundControls.keys(), ...remembered])];
38
+ }
39
+
40
+ function hasExactForegroundId(state: SubagentState | undefined, id: string): boolean {
41
+ if (!state) return false;
42
+ if (state.foregroundControls.has(id)) return true;
43
+ const remembered = state.foregroundRuns?.get(id);
44
+ return Boolean(remembered && state.currentSessionId && remembered.sessionId === state.currentSessionId);
33
45
  }
34
46
 
35
47
  function nestedScopeFromState(state: SubagentState | undefined): NestedRunResolutionScope | undefined {
@@ -58,7 +70,7 @@ export function resolveSubagentRunId(id: string, deps: ResolveSubagentRunIdDeps
58
70
  const resultsDir = deps.resultsDir ?? RESULTS_DIR;
59
71
 
60
72
  const nestedScope = deps.nested ?? nestedScopeFromState(deps.state);
61
- if (deps.state?.foregroundControls.has(id) || deps.state?.foregroundRuns?.has(id)) return { kind: "foreground", id };
73
+ if (hasExactForegroundId(deps.state, id)) return { kind: "foreground", id };
62
74
  const exactAsync = exactAsyncLocation(id, asyncDirRoot, resultsDir);
63
75
  if (exactAsync) return { kind: "async", id, location: exactAsync };
64
76
  const exactNested = findNestedRunMatchesById(id, nestedScope ? { scope: nestedScope } : {});
@@ -6,8 +6,9 @@ import { formatAsyncResultTranscript, formatAsyncRunTranscript, formatNestedRunT
6
6
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
7
7
  import { formatModelThinking } from "../../shared/formatters.ts";
8
8
  import { formatActivityLabel } from "../../shared/status-format.ts";
9
- import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SubagentState } from "../../shared/types.ts";
9
+ import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SteeringStatus, type SubagentState } from "../../shared/types.ts";
10
10
  import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
11
+ import { readProcessTerminal, sanitizeProcessTerminal } from "./process-terminal.ts";
11
12
  import { resolveAsyncRunLocation } from "./async-resume.ts";
12
13
  import { resolveSubagentRunId } from "./run-id-resolver.ts";
13
14
  import { flatToLogicalStepIndex, normalizeParallelGroups } from "./parallel-groups.ts";
@@ -38,7 +39,8 @@ function hasExistingSessionFile(value: unknown): value is string {
38
39
  return typeof value === "string" && fs.existsSync(value);
39
40
  }
40
41
 
41
- function formatResumeGuidance(runId: string | undefined, children: Array<{ agent?: unknown; sessionFile?: unknown }>, fallbackSessionFile?: unknown): string {
42
+ function formatResumeGuidance(runId: string | undefined, children: Array<{ agent?: unknown; sessionFile?: unknown }>, fallbackSessionFile?: unknown, options: { stopped?: boolean } = {}): string {
43
+ if (options.stopped) return "Resume: unavailable; stopped runs are not resumable. Start a new run instead.";
42
44
  const knownChildren = children
43
45
  .map((child, index) => ({ child, index }))
44
46
  .filter(({ child }) => typeof child.agent === "string");
@@ -73,15 +75,15 @@ function nestedRunDisplayName(run: NestedRunSummary): string {
73
75
  return run.id;
74
76
  }
75
77
 
76
- function formatSteeringSummary(input: { steerCount?: number; lastSteerAt?: number }): string | undefined {
77
- const parts: string[] = [];
78
- if (input.steerCount !== undefined) parts.push(`${input.steerCount} steer${input.steerCount === 1 ? "" : "s"}`);
79
- if (typeof input.lastSteerAt === "number" && Number.isFinite(input.lastSteerAt)) parts.push(`last ${new Date(input.lastSteerAt).toISOString()}`);
80
- return parts.length ? parts.join(", ") : undefined;
78
+ function formatSteeringSummary(input: { steering?: SteeringStatus }): string | undefined {
79
+ const steering = input.steering;
80
+ if (!steering || steering.requested === 0) return undefined;
81
+ const lateAcknowledgments = steering.recent.reduce((count, request) => count + request.targets.filter((target) => target.lateDeliveredAt !== undefined).length, 0);
82
+ return `${steering.requested} requested, ${steering.scheduled} scheduled, ${steering.pending} pending, ${steering.delivered} delivered, ${steering.failed} failed, ${steering.recovered} recovered${lateAcknowledgments ? `, ${lateAcknowledgments} late acknowledged` : ""}`;
81
83
  }
82
84
 
83
85
  function rememberedForegroundChildOutput(child: ForegroundResumeRun["children"][number]): string {
84
- const outputPath = child.artifactPaths?.outputPath;
86
+ const outputPath = child.artifactPaths?.outputPath ?? child.savedOutputPath;
85
87
  if (outputPath && fs.existsSync(outputPath)) {
86
88
  try {
87
89
  const artifactOutput = fs.readFileSync(outputPath, "utf-8").trim();
@@ -107,24 +109,29 @@ function formatRememberedForegroundStatus(run: ForegroundResumeRun): string {
107
109
  `${child.index + 1}. ${child.agent} ${child.status}`,
108
110
  child.exitCode !== undefined ? `exit ${child.exitCode}` : undefined,
109
111
  child.detachedReason ? `detached: ${child.detachedReason}` : undefined,
112
+ child.acceptance ? `acceptance: ${child.acceptance.status}` : undefined,
113
+ child.error ? `error: ${child.error}` : undefined,
110
114
  output ? `output: ${output.slice(0, 160)}` : undefined,
111
115
  ].filter(Boolean);
112
116
  lines.push(parts.join(", "));
113
117
  if (child.sessionFile) lines.push(` Session: ${child.sessionFile}`);
114
118
  if (child.transcriptPath) lines.push(` Transcript: ${child.transcriptPath}`);
115
119
  if (child.artifactPaths?.outputPath) lines.push(` Output: ${child.artifactPaths.outputPath}`);
120
+ if (child.savedOutputPath && child.savedOutputPath !== child.artifactPaths?.outputPath) lines.push(` Saved output: ${child.savedOutputPath}`);
121
+ if (child.outputSaveError) lines.push(` Output warning: ${child.outputSaveError}`);
116
122
  if (child.transcriptError) lines.push(` Transcript warning: ${child.transcriptError}`);
117
123
  }
118
124
  lines.push("", `Status: subagent({ action: "status", id: "${run.runId}" })`);
119
125
  if (run.children.length === 1) lines.push(`Transcript: subagent({ action: "status", id: "${run.runId}", view: "transcript" })`);
120
126
  else lines.push(`Transcript: subagent({ action: "status", id: "${run.runId}", index: 0, view: "transcript" })`);
121
- const resumable = run.children.find((child) => child.status !== "detached" && hasExistingSessionFile(child.sessionFile));
122
- if (resumable) {
127
+ const detached = run.children.some((child) => child.status === "detached");
128
+ const resumable = run.children.find((child) => hasExistingSessionFile(child.sessionFile));
129
+ if (detached) {
130
+ lines.push(`Recovery: reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); do not resume or launch a replacement while any child remains detached.`);
131
+ } else if (resumable) {
123
132
  lines.push(run.children.length === 1
124
133
  ? `Revive: subagent({ action: "resume", id: "${run.runId}", message: "..." })`
125
134
  : `Revive child: subagent({ action: "resume", id: "${run.runId}", index: ${resumable.index}, message: "..." })`);
126
- } else if (run.children.some((child) => child.status === "detached")) {
127
- lines.push("Recovery: child detached for intercom coordination; status will show recovered output after the child exits when Pi can observe it.");
128
135
  } else {
129
136
  lines.push("Resume: unavailable; no child session file was persisted.");
130
137
  }
@@ -147,6 +154,8 @@ function formatRememberedForegroundTranscript(run: ForegroundResumeRun, options:
147
154
  child.sessionFile ? `Session: ${child.sessionFile}` : undefined,
148
155
  child.transcriptPath ? `Transcript: ${child.transcriptPath}` : undefined,
149
156
  child.artifactPaths?.outputPath ? `Output: ${child.artifactPaths.outputPath}` : undefined,
157
+ child.savedOutputPath && child.savedOutputPath !== child.artifactPaths?.outputPath ? `Saved output: ${child.savedOutputPath}` : undefined,
158
+ child.outputSaveError ? `Output warning: ${child.outputSaveError}` : undefined,
150
159
  ].filter((line): line is string => Boolean(line));
151
160
  lines.push("Result transcript tail:");
152
161
  if (outputLines.length === 0) lines.push(" (no recovered final output available yet)");
@@ -343,10 +352,15 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
343
352
  const updated = status.lastUpdate ? new Date(status.lastUpdate).toISOString() : "n/a";
344
353
  const statusActivityText = status.state === "running" ? formatActivityLabel(status.lastActivityAt, status.activityState) : undefined;
345
354
  const steeringText = formatSteeringSummary(status);
355
+ const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
356
+ ?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
346
357
 
347
358
  const lines = [
348
359
  `Run: ${status.runId}`,
349
360
  `State: ${status.state}`,
361
+ processTerminal ? `Process terminal: ${processTerminal.state}${processTerminal.reason ? ` (${processTerminal.reason})` : ""}` : undefined,
362
+ status.capabilityCeiling ? `Capability ceiling: ${status.capabilityCeiling.allowedTools === undefined ? "names unrestricted" : status.capabilityCeiling.allowedTools.length === 0 ? "none" : status.capabilityCeiling.allowedTools.join(", ")}\nExtensions denied: ${status.capabilityCeiling.denyExtensions ? "yes" : "no"} (sources: ${status.capabilityCeiling.sources.join(", ")})` : undefined,
363
+ status.capabilityAudit ? `Capability audit: ${status.capabilityAudit.removedTools.length} tools removed, ${status.capabilityAudit.removedExtensionCount} extension entries removed` : undefined,
350
364
  status.error ? `Error: ${status.error}` : undefined,
351
365
  statusActivityText ? `Activity: ${statusActivityText}` : undefined,
352
366
  steeringText ? `Steering: ${steeringText}` : undefined,
@@ -358,6 +372,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
358
372
  status.turnBudget ? `Turn budget: ${status.turnBudget.turnCount}/${status.turnBudget.maxTurns}+${status.turnBudget.graceTurns} (${status.turnBudget.outcome})` : undefined,
359
373
  `Dir: ${asyncDir}`,
360
374
  outputPath ? `Output: ${outputPath}` : undefined,
375
+ status.parallelHandoff ? `Parallel handoff: ${status.parallelHandoff.path}` : undefined,
361
376
  reconciliation.message ? `Diagnosis: ${reconciliation.message}` : undefined,
362
377
  reconciliation.resultPath && fs.existsSync(reconciliation.resultPath) ? `Result: ${reconciliation.resultPath}` : undefined,
363
378
  ].filter((line): line is string => Boolean(line));
@@ -388,19 +403,19 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
388
403
  if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
389
404
  if (status.state === "running") lines.push(`Steer running child: subagent({ action: "steer", id: "${status.runId}", message: "..." })`);
390
405
  if (status.state !== "running") {
391
- lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile));
406
+ lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile, { stopped: status.state === "stopped" || status.stopped === true }));
392
407
  }
393
408
  if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`);
394
409
  if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`);
395
410
 
396
- return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } };
411
+ return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
397
412
  }
398
413
  }
399
414
 
400
415
  if (resultPath) {
401
416
  try {
402
417
  const raw = fs.readFileSync(resultPath, "utf-8");
403
- const data = JSON.parse(raw) as { id?: string; runId?: string; agent?: string; success?: boolean; summary?: string; output?: string; exitCode?: number; state?: string; sessionFile?: string; results?: Array<{ agent?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }> };
418
+ const data = JSON.parse(raw) as { id?: string; runId?: string; agent?: string; success?: boolean; summary?: string; output?: string; exitCode?: number; state?: string; sessionFile?: string; parallelHandoff?: { path?: string }; results?: Array<{ agent?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }> };
404
419
  if (params.view === "transcript") {
405
420
  try {
406
421
  return { content: [{ type: "text", text: formatAsyncResultTranscript(data, resultPath, { index: params.index, lines: params.lines }) }], details: { mode: "single", results: [] } };
@@ -409,11 +424,12 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
409
424
  return { content: [{ type: "text", text: message }], isError: true, details: { mode: "single", results: [] } };
410
425
  }
411
426
  }
412
- const status = data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
427
+ const status = data.state === "stopped" ? "stopped" : data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
413
428
  const runId = data.runId ?? data.id ?? resolvedId;
414
429
  const lines = [`Run: ${runId}`, `State: ${status}`, `Result: ${resultPath}`];
430
+ if (data.parallelHandoff?.path) lines.push(`Parallel handoff: ${data.parallelHandoff.path}`);
415
431
  const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
416
- lines.push(formatResumeGuidance(runId, children, data.sessionFile));
432
+ lines.push(formatResumeGuidance(runId, children, data.sessionFile, { stopped: status === "stopped" }));
417
433
  if (data.summary) lines.push("", data.summary);
418
434
  return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } };
419
435
  } catch (error) {
@@ -12,6 +12,8 @@ import {
12
12
  type ExtensionConfig,
13
13
  } from "../../shared/types.ts";
14
14
  import type { SubagentParamsLike } from "../foreground/subagent-executor.ts";
15
+ import { validateExecutionAcceptance } from "../shared/acceptance.ts";
16
+ import type { ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
15
17
 
16
18
  export const SCHEDULED_RUNS_DIR = path.join(TEMP_ROOT_DIR, "scheduled-subagent-runs");
17
19
  export const SCHEDULED_RUN_ACTIONS = ["schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
@@ -56,6 +58,7 @@ type ScheduledRunManagerDeps = {
56
58
  storeRoot?: string;
57
59
  now?: () => number;
58
60
  randomId?: () => string;
61
+ resolveCapabilityCeiling?: (sessionId: string) => ResolvedSubagentCapabilityCeiling | undefined;
59
62
  timers?: ScheduledRunTimers;
60
63
  };
61
64
 
@@ -228,6 +231,8 @@ function sanitizeScheduledParams(params: SubagentParamsLike): { params?: Subagen
228
231
  if (params.context === "fork") return { error: "Scheduled subagent runs require fresh context. Forked parent-session context is not safe at fire time." };
229
232
  if (params.async === false) return { error: "Scheduled subagent runs are always async; omit async or set async: true." };
230
233
  if (params.clarify === true) return { error: "Scheduled subagent runs cannot open clarify UI; omit clarify or set clarify: false." };
234
+ const acceptanceErrors = validateExecutionAcceptance(params);
235
+ if (acceptanceErrors.length > 0) return { error: acceptanceErrors.join(" ") };
231
236
 
232
237
  const {
233
238
  action: _action,
@@ -307,12 +312,15 @@ export class ScheduledRunManager {
307
312
  const sanitized = sanitizeScheduledParams(params);
308
313
  if (sanitized.error) return textResult(sanitized.error, true);
309
314
  const scheduleInput = params.schedule!.trim();
315
+ const sessionId = resolveCurrentSessionId(ctx.sessionManager);
316
+ if (this.deps.resolveCapabilityCeiling?.(sessionId)) {
317
+ return textResult("Cannot schedule a capability-ceiling-restricted run because this store does not yet persist ceilings. Remove the active parent restriction or run it immediately.", true);
318
+ }
310
319
  const runAt = parseScheduledRunTime(scheduleInput, this.now());
311
320
  const pendingCount = store.list().filter((job) => job.state === "scheduled" || job.state === "running").length;
312
321
  const maxPending = resolveMaxPending(this.deps.config);
313
322
  if (pendingCount >= maxPending) return textResult(`Scheduled subagent limit reached (${pendingCount}/${maxPending} pending or running). Cancel an existing scheduled run before adding another.`, true);
314
323
  const id = this.randomId();
315
- const sessionId = resolveCurrentSessionId(ctx.sessionManager);
316
324
  const scheduleName = params.scheduleName?.trim();
317
325
  const executionParams = sanitized.params!;
318
326
  const now = this.now();