@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
@@ -19,6 +19,7 @@ import * as fs from "node:fs";
19
19
  import * as path from "node:path";
20
20
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
21
21
  import { POLL_INTERVAL_MS } from "../../shared/types.ts";
22
+ import { resolveWatchPath } from "../../shared/utils.ts";
22
23
 
23
24
  /**
24
25
  * Opportunistic fast-path interrupt signal. On Unix `SIGUSR2` is trapped by the
@@ -27,7 +28,7 @@ import { POLL_INTERVAL_MS } from "../../shared/types.ts";
27
28
  */
28
29
  export const INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
29
30
 
30
- export type ControlChannelFs = Pick<typeof fs, "mkdirSync" | "existsSync" | "rmSync" | "watch" | "readdirSync" | "readFileSync">;
31
+ export type ControlChannelFs = Pick<typeof fs, "mkdirSync" | "existsSync" | "rmSync" | "watch" | "readdirSync" | "readFileSync" | "realpathSync">;
31
32
  export type ControlChannelTimers = { setInterval: typeof setInterval; clearInterval: typeof clearInterval };
32
33
  type KillFn = (pid: number, signal?: NodeJS.Signals | 0) => unknown;
33
34
 
@@ -45,17 +46,49 @@ export interface TimeoutRequest {
45
46
  reason?: string;
46
47
  }
47
48
 
49
+ export interface StopRequest {
50
+ type: "stop";
51
+ ts?: number;
52
+ source?: string;
53
+ reason?: string;
54
+ }
55
+
48
56
  export interface SteerRequest {
49
57
  type: "steer";
50
58
  id: string;
51
59
  ts: number;
52
60
  message: string;
53
61
  targetIndex?: number;
62
+ targetIndexes?: number[];
54
63
  source?: string;
55
64
  }
56
65
 
66
+ export interface SteerCapability {
67
+ type: "steer-capability";
68
+ protocolVersion: 1;
69
+ index: number;
70
+ pid: number;
71
+ readyAt: number;
72
+ supported: boolean;
73
+ }
74
+
75
+ export interface SteerAck {
76
+ type: "steer-ack";
77
+ protocolVersion: 1;
78
+ requestId: string;
79
+ index: number;
80
+ ts: number;
81
+ state: "delivered" | "failed";
82
+ message: string;
83
+ }
84
+
57
85
  const STEER_REQUESTS_DIR = "steer-requests";
58
86
  const STEER_TARGETS_DIR = "steer-targets";
87
+ const STEER_CAPABILITIES_DIR = "steer-capabilities";
88
+ const STEER_ACKS_DIR = "steer-acks";
89
+ const STEER_INBOX_CLOSED_FILE = "steer-inbox-closed.json";
90
+ const MAX_STEER_MESSAGE_BYTES = 128 * 1024;
91
+ const MAX_STEER_REQUEST_ID_LENGTH = 256;
59
92
 
60
93
  /** Control inbox directory inside an async run dir. */
61
94
  export function controlInboxDir(asyncDir: string): string {
@@ -72,26 +105,118 @@ export function timeoutRequestPath(asyncDir: string): string {
72
105
  return path.join(controlInboxDir(asyncDir), "timeout.json");
73
106
  }
74
107
 
108
+ /** Path of the portable manual stop request file. */
109
+ export function stopRequestPath(asyncDir: string): string {
110
+ return path.join(controlInboxDir(asyncDir), "stop.json");
111
+ }
112
+
75
113
  /** Directory of parent-to-runner steering requests. */
76
114
  export function steerRequestsDir(asyncDir: string): string {
77
115
  return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);
78
116
  }
79
117
 
118
+ export function steerInboxClosedPath(asyncDir: string): string {
119
+ return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);
120
+ }
121
+
122
+ export function closeSteerInbox(asyncDir: string, state: string): void {
123
+ writeAtomicJson(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });
124
+ }
125
+
80
126
  /** Per-child inbox consumed by the child prompt runtime inside the Pi process. */
81
127
  export function stepSteerInboxDir(asyncDir: string, index: number): string {
128
+ assertChildIndex(index);
82
129
  return path.join(controlInboxDir(asyncDir), STEER_TARGETS_DIR, String(index));
83
130
  }
84
131
 
132
+ export function steerCapabilitiesDir(asyncDir: string): string {
133
+ return path.join(controlInboxDir(asyncDir), STEER_CAPABILITIES_DIR);
134
+ }
135
+
136
+ export function steerCapabilityPath(asyncDir: string, index: number): string {
137
+ assertChildIndex(index);
138
+ return path.join(steerCapabilitiesDir(asyncDir), `${index}.json`);
139
+ }
140
+
141
+ export function steerAcksDir(asyncDir: string, index: number): string {
142
+ assertChildIndex(index);
143
+ return path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR, String(index));
144
+ }
145
+
146
+ function steerAckFileName(requestId: string): string {
147
+ return `${Buffer.from(requestId).toString("base64url")}.json`;
148
+ }
149
+
150
+ export function steerAckPathFromDir(dir: string, requestId: string): string {
151
+ if (!/^[^\s]+$/.test(requestId) || requestId.length > 256) throw new Error("steer acknowledgment requestId is invalid.");
152
+ return path.join(dir, steerAckFileName(requestId));
153
+ }
154
+
155
+ function assertChildIndex(index: number): void {
156
+ if (!Number.isInteger(index) || index < 0 || index > 1_000_000) throw new Error("steer child index must be a non-negative integer.");
157
+ }
158
+
85
159
  function steerRequestFileName(request: SteerRequest): string {
86
160
  return `${String(request.ts).padStart(13, "0")}-${Buffer.from(request.id).toString("base64url")}.json`;
87
161
  }
88
162
 
163
+ function validSteerRequest(request: Partial<SteerRequest>): request is SteerRequest {
164
+ return request.type === "steer"
165
+ && typeof request.id === "string"
166
+ && /^[^\s]+$/.test(request.id)
167
+ && request.id.length <= MAX_STEER_REQUEST_ID_LENGTH
168
+ && typeof request.ts === "number"
169
+ && Number.isFinite(request.ts)
170
+ && request.ts > 0
171
+ && typeof request.message === "string"
172
+ && Boolean(request.message.trim())
173
+ && Buffer.byteLength(request.message, "utf8") <= MAX_STEER_MESSAGE_BYTES
174
+ && (request.targetIndex === undefined || (Number.isInteger(request.targetIndex) && request.targetIndex >= 0 && request.targetIndex <= 1_000_000))
175
+ && (request.targetIndexes === undefined || (
176
+ request.targetIndex === undefined
177
+ && Array.isArray(request.targetIndexes)
178
+ && request.targetIndexes.length > 0
179
+ && request.targetIndexes.length <= 1_000
180
+ && request.targetIndexes.every((index) => Number.isInteger(index) && index >= 0 && index <= 1_000_000)
181
+ && new Set(request.targetIndexes).size === request.targetIndexes.length
182
+ ))
183
+ && (request.source === undefined || (typeof request.source === "string" && Boolean(request.source.trim()) && request.source.length <= 256));
184
+ }
185
+
89
186
  export function writeSteerRequestToDir(dir: string, request: SteerRequest): string {
187
+ if (!validSteerRequest(request)) throw new Error("steer request is malformed or exceeds transport limits.");
90
188
  const requestPath = path.join(dir, steerRequestFileName(request));
91
189
  writeAtomicJson(requestPath, request);
92
190
  return requestPath;
93
191
  }
94
192
 
193
+ export function writeSteerCapabilityAt(filePath: string, capability: Omit<SteerCapability, "type" | "protocolVersion">): string {
194
+ assertChildIndex(capability.index);
195
+ if (!Number.isInteger(capability.pid) || capability.pid <= 0) throw new Error("steer capability pid must be a positive integer.");
196
+ if (!Number.isFinite(capability.readyAt) || capability.readyAt <= 0) throw new Error("steer capability readyAt must be a finite timestamp.");
197
+ const record: SteerCapability = { type: "steer-capability", protocolVersion: 1, ...capability };
198
+ writeAtomicJson(filePath, record);
199
+ return filePath;
200
+ }
201
+
202
+ export function writeSteerCapability(asyncDir: string, capability: Omit<SteerCapability, "type" | "protocolVersion">): string {
203
+ return writeSteerCapabilityAt(steerCapabilityPath(asyncDir, capability.index), capability);
204
+ }
205
+
206
+ export function writeSteerAckAt(filePath: string, ack: Omit<SteerAck, "type" | "protocolVersion">): string {
207
+ assertChildIndex(ack.index);
208
+ if (!/^[^\s]+$/.test(ack.requestId) || ack.requestId.length > 256) throw new Error("steer acknowledgment requestId is invalid.");
209
+ if (!Number.isFinite(ack.ts) || ack.ts <= 0) throw new Error("steer acknowledgment ts must be a finite timestamp.");
210
+ if (!ack.message.trim() || ack.message.length > 1000) throw new Error("steer acknowledgment message is invalid.");
211
+ const record: SteerAck = { type: "steer-ack", protocolVersion: 1, ...ack, message: ack.message.trim() };
212
+ writeAtomicJson(filePath, record);
213
+ return filePath;
214
+ }
215
+
216
+ export function writeSteerAck(asyncDir: string, ack: Omit<SteerAck, "type" | "protocolVersion">): string {
217
+ return writeSteerAckAt(path.join(steerAcksDir(asyncDir, ack.index), steerAckFileName(ack.requestId)), ack);
218
+ }
219
+
95
220
  /**
96
221
  * Parent side: drop a portable interrupt request the runner's inbox watcher will
97
222
  * pick up regardless of OS. Written atomically (temp + rename), dir auto-created.
@@ -118,46 +243,137 @@ export function requestAsyncTimeout(
118
243
  return requestPath;
119
244
  }
120
245
 
246
+ export function requestAsyncStop(
247
+ asyncDir: string,
248
+ payload: Omit<StopRequest, "type"> = {},
249
+ deps: { now?: () => number } = {},
250
+ ): string {
251
+ const requestPath = stopRequestPath(asyncDir);
252
+ const request: StopRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "stop" };
253
+ writeAtomicJson(requestPath, request);
254
+ return requestPath;
255
+ }
256
+
121
257
  export function requestAsyncSteer(
122
258
  asyncDir: string,
123
- payload: { message: string; targetIndex?: number; source?: string; id?: string; ts?: number },
259
+ payload: { message: string; targetIndex?: number; targetIndexes?: number[]; source?: string; id?: string; ts?: number },
124
260
  deps: { now?: () => number; randomId?: () => string } = {},
125
261
  ): string {
126
262
  const message = payload.message.trim();
127
263
  if (!message) throw new Error("steer message must not be empty.");
128
- if (payload.targetIndex !== undefined && (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0)) {
129
- throw new Error("steer targetIndex must be a non-negative integer.");
264
+ if (Buffer.byteLength(message, "utf8") > MAX_STEER_MESSAGE_BYTES) throw new Error(`steer message exceeds ${MAX_STEER_MESSAGE_BYTES} UTF-8 bytes.`);
265
+ if (payload.targetIndex !== undefined && (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0 || payload.targetIndex > 1_000_000)) {
266
+ throw new Error("steer targetIndex must be an integer between 0 and 1000000.");
130
267
  }
268
+ if (payload.targetIndexes !== undefined && (
269
+ !Array.isArray(payload.targetIndexes)
270
+ || payload.targetIndex !== undefined
271
+ || payload.targetIndexes.length === 0
272
+ || payload.targetIndexes.length > 1_000
273
+ || payload.targetIndexes.some((index) => !Number.isInteger(index) || index < 0 || index > 1_000_000)
274
+ || new Set(payload.targetIndexes).size !== payload.targetIndexes.length
275
+ )) {
276
+ throw new Error("steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.");
277
+ }
278
+ const closedPath = steerInboxClosedPath(asyncDir);
279
+ if (fs.existsSync(closedPath)) throw new Error("Async run no longer accepts steering requests.");
131
280
  const request: SteerRequest = {
132
281
  type: "steer",
133
282
  id: payload.id ?? deps.randomId?.() ?? randomUUID(),
134
283
  ts: payload.ts ?? deps.now?.() ?? Date.now(),
135
284
  message,
136
285
  ...(payload.targetIndex !== undefined ? { targetIndex: payload.targetIndex } : {}),
286
+ ...(payload.targetIndexes !== undefined ? { targetIndexes: [...payload.targetIndexes] } : {}),
137
287
  ...(payload.source ? { source: payload.source } : {}),
138
288
  };
139
- return writeSteerRequestToDir(steerRequestsDir(asyncDir), request);
289
+ const requestPath = writeSteerRequestToDir(steerRequestsDir(asyncDir), request);
290
+ if (fs.existsSync(closedPath)) {
291
+ fs.rmSync(requestPath, { force: true });
292
+ throw new Error("Async run stopped accepting steering before the request was committed.");
293
+ }
294
+ return requestPath;
140
295
  }
141
296
 
142
297
  export function enqueueStepSteer(asyncDir: string, index: number, request: SteerRequest): string {
143
- if (!Number.isInteger(index) || index < 0) throw new Error("steer child index must be a non-negative integer.");
144
- return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), { ...request, targetIndex: index, type: "steer" });
298
+ assertChildIndex(index);
299
+ const { targetIndexes: _targetIndexes, ...singleTargetRequest } = request;
300
+ return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), { ...singleTargetRequest, targetIndex: index, type: "steer" });
301
+ }
302
+
303
+ function parseSteerCapability(raw: unknown): SteerCapability | undefined {
304
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
305
+ const input = raw as Partial<SteerCapability>;
306
+ if (input.type !== "steer-capability" || input.protocolVersion !== 1) return undefined;
307
+ if (!Number.isInteger(input.index) || input.index < 0 || input.index > 1_000_000) return undefined;
308
+ if (!Number.isInteger(input.pid) || input.pid <= 0 || !Number.isFinite(input.readyAt) || input.readyAt <= 0 || typeof input.supported !== "boolean") return undefined;
309
+ return { type: "steer-capability", protocolVersion: 1, index: input.index, pid: input.pid, readyAt: input.readyAt, supported: input.supported };
310
+ }
311
+
312
+ function parseSteerAck(raw: unknown): SteerAck | undefined {
313
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
314
+ const input = raw as Partial<SteerAck>;
315
+ if (input.type !== "steer-ack" || input.protocolVersion !== 1 || typeof input.requestId !== "string" || !/^[^\s]+$/.test(input.requestId) || input.requestId.length > 256) return undefined;
316
+ if (!Number.isInteger(input.index) || input.index < 0 || input.index > 1_000_000 || !Number.isFinite(input.ts) || input.ts <= 0) return undefined;
317
+ if (input.state !== "delivered" && input.state !== "failed") return undefined;
318
+ if (typeof input.message !== "string" || !input.message.trim() || input.message.length > 1000) return undefined;
319
+ return { type: "steer-ack", protocolVersion: 1, requestId: input.requestId, index: input.index, ts: input.ts, state: input.state, message: input.message.trim() };
320
+ }
321
+
322
+ export function readSteerCapability(asyncDir: string, index: number): SteerCapability | undefined {
323
+ try {
324
+ return parseSteerCapability(JSON.parse(fs.readFileSync(steerCapabilityPath(asyncDir, index), "utf-8")));
325
+ } catch {
326
+ return undefined;
327
+ }
328
+ }
329
+
330
+ export function consumeSteerCapabilities(asyncDir: string, fsImpl: Pick<typeof fs, "existsSync" | "readdirSync" | "readFileSync"> = fs): SteerCapability[] {
331
+ const dir = steerCapabilitiesDir(asyncDir);
332
+ if (!fsImpl.existsSync(dir)) return [];
333
+ const capabilities: SteerCapability[] = [];
334
+ for (const entry of fsImpl.readdirSync(dir).filter((name) => /^\d+\.json$/.test(name)).sort()) {
335
+ try {
336
+ const capability = parseSteerCapability(JSON.parse(fsImpl.readFileSync(path.join(dir, entry), "utf-8")));
337
+ if (capability) capabilities.push(capability);
338
+ } catch {
339
+ // A partially written or malformed capability is ignored until a valid one arrives.
340
+ }
341
+ }
342
+ return capabilities;
343
+ }
344
+
345
+ export function consumeSteerAcks(asyncDir: string, fsImpl: Pick<typeof fs, "existsSync" | "readdirSync" | "readFileSync" | "rmSync"> = fs): SteerAck[] {
346
+ const root = path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR);
347
+ if (!fsImpl.existsSync(root)) return [];
348
+ const acks: SteerAck[] = [];
349
+ let indexNames: string[];
350
+ try { indexNames = fsImpl.readdirSync(root).filter((name) => /^\d+$/.test(name)); } catch { return []; }
351
+ for (const indexName of indexNames) {
352
+ const dir = path.join(root, indexName);
353
+ let entries: string[];
354
+ try { entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort(); } catch { continue; }
355
+ for (const entry of entries) {
356
+ const target = path.join(dir, entry);
357
+ let ack: SteerAck | undefined;
358
+ try { ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, "utf-8"))); } catch { ack = undefined; }
359
+ try { fsImpl.rmSync(target, { force: true }); } catch { continue; }
360
+ if (ack) acks.push(ack);
361
+ }
362
+ }
363
+ return acks;
145
364
  }
146
365
 
147
366
  function parseSteerRequest(raw: unknown): SteerRequest | undefined {
148
367
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
149
368
  const input = raw as Partial<SteerRequest>;
150
- if (input.type !== "steer") return undefined;
151
- if (typeof input.id !== "string" || !input.id.trim()) return undefined;
152
- if (typeof input.ts !== "number" || !Number.isFinite(input.ts)) return undefined;
153
- if (typeof input.message !== "string" || !input.message.trim()) return undefined;
154
- if (input.targetIndex !== undefined && (!Number.isInteger(input.targetIndex) || input.targetIndex < 0)) return undefined;
369
+ if (!validSteerRequest(input)) return undefined;
155
370
  return {
156
371
  type: "steer",
157
372
  id: input.id.trim(),
158
373
  ts: input.ts,
159
374
  message: input.message.trim(),
160
375
  ...(input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {}),
376
+ ...(input.targetIndexes !== undefined ? { targetIndexes: [...input.targetIndexes] } : {}),
161
377
  ...(typeof input.source === "string" && input.source.trim() ? { source: input.source } : {}),
162
378
  };
163
379
  }
@@ -220,6 +436,20 @@ export function consumeTimeoutRequest(
220
436
  return true;
221
437
  }
222
438
 
439
+ export function consumeStopRequest(
440
+ asyncDir: string,
441
+ fsImpl: Pick<typeof fs, "existsSync" | "rmSync"> = fs,
442
+ ): boolean {
443
+ const requestPath = stopRequestPath(asyncDir);
444
+ if (!fsImpl.existsSync(requestPath)) return false;
445
+ try {
446
+ fsImpl.rmSync(requestPath, { force: true, recursive: true });
447
+ } catch {
448
+ // Already removed by a concurrent check — still counts as consumed.
449
+ }
450
+ return true;
451
+ }
452
+
223
453
  /**
224
454
  * Parent side: portable interrupt = authoritative file request + best-effort OS
225
455
  * signal. The signal is only a latency optimization on Unix; ENOSYS on Windows
@@ -265,6 +495,17 @@ export function deliverTimeoutRequest(input: {
265
495
  requestAsyncTimeout(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
266
496
  }
267
497
 
498
+ export function deliverStopRequest(input: {
499
+ asyncDir: string;
500
+ pid?: number;
501
+ kill?: KillFn;
502
+ signal?: NodeJS.Signals;
503
+ now?: () => number;
504
+ source?: string;
505
+ }): void {
506
+ requestAsyncStop(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
507
+ }
508
+
268
509
  /**
269
510
  * Runner side: watch the control inbox and route interrupt requests into
270
511
  * `onInterrupt`. Uses `fs.watch` when available plus an interval poll as a
@@ -276,7 +517,10 @@ export function watchAsyncControlInbox(
276
517
  opts: {
277
518
  onInterrupt: () => void;
278
519
  onTimeout?: () => void;
520
+ onStop?: () => void;
279
521
  onSteer?: (request: SteerRequest) => void;
522
+ onSteerCapability?: (capability: SteerCapability) => void;
523
+ onSteerAck?: (ack: SteerAck) => void;
280
524
  pollIntervalMs?: number;
281
525
  fs?: ControlChannelFs;
282
526
  timers?: ControlChannelTimers;
@@ -295,9 +539,12 @@ export function watchAsyncControlInbox(
295
539
  const check = (): void => {
296
540
  if (disposed) return;
297
541
  try {
542
+ if (consumeStopRequest(asyncDir, fsImpl)) opts.onStop?.();
298
543
  if (consumeTimeoutRequest(asyncDir, fsImpl)) opts.onTimeout?.();
299
544
  if (consumeInterruptRequest(asyncDir, fsImpl)) opts.onInterrupt();
300
545
  for (const request of consumeSteerRequests(asyncDir, fsImpl)) opts.onSteer?.(request);
546
+ for (const capability of consumeSteerCapabilities(asyncDir, fsImpl)) opts.onSteerCapability?.(capability);
547
+ for (const ack of consumeSteerAcks(asyncDir, fsImpl)) opts.onSteerAck?.(ack);
301
548
  } catch {
302
549
  // Never let inbox errors crash the runner.
303
550
  }
@@ -308,7 +555,7 @@ export function watchAsyncControlInbox(
308
555
 
309
556
  let watcher: fs.FSWatcher | undefined;
310
557
  try {
311
- watcher = fsImpl.watch(dir, () => check());
558
+ watcher = fsImpl.watch(resolveWatchPath(dir, fsImpl.realpathSync.native), () => check());
312
559
  watcher.on?.("error", () => {
313
560
  // fs.watch can emit on transient FS errors; the interval poll keeps us live.
314
561
  });
@@ -16,6 +16,7 @@ import {
16
16
  } from "../../shared/types.ts";
17
17
  import { readStatus } from "../../shared/utils.ts";
18
18
  import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
19
+ import { contextModeLabel, summarizeContextModes } from "../shared/context-mode.ts";
19
20
  import { formatAsyncRunOutputPath, formatAsyncRunProgressLabel, listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
20
21
 
21
22
  const DEFAULT_TRANSCRIPT_LINES = 80;
@@ -23,6 +24,7 @@ const MAX_TRANSCRIPT_LINES = 500;
23
24
  const TRANSCRIPT_TAIL_BYTES = 256 * 1024;
24
25
 
25
26
  type ForegroundControl = SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
27
+ type ForegroundRun = NonNullable<SubagentState["foregroundRuns"]> extends Map<string, infer T> ? T : never;
26
28
 
27
29
  interface FleetViewParams {
28
30
  lines?: number;
@@ -254,6 +256,20 @@ function formatForegroundFleetLines(controls: ForegroundControl[]): string[] {
254
256
  return lines;
255
257
  }
256
258
 
259
+ function formatDetachedForegroundFleetLines(runs: ForegroundRun[]): string[] {
260
+ if (runs.length === 0) return [];
261
+ const lines = ["Detached foreground runs:"];
262
+ const ordered = [...runs].sort((left, right) => right.updatedAt - left.updatedAt);
263
+ for (const run of ordered) {
264
+ const detachedChildren = run.children.filter((child) => child.status === "detached");
265
+ const childSummary = detachedChildren.map((child) => `${child.agent} #${child.index}`).join(", ");
266
+ lines.push(`- ${run.runId} | detached | ${run.mode}${childSummary ? ` | ${childSummary}` : ""}`);
267
+ lines.push(` status: subagent({ action: "status", id: "${run.runId}" })`);
268
+ 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.`);
269
+ }
270
+ return lines;
271
+ }
272
+
257
273
  function formatAsyncFleetLines(runs: AsyncRunSummary[]): string[] {
258
274
  if (runs.length === 0) return [];
259
275
  const lines = ["Async runs:"];
@@ -262,15 +278,17 @@ function formatAsyncFleetLines(runs: AsyncRunSummary[]): string[] {
262
278
  const activity = formatActivityFacts(run);
263
279
  const cwd = run.cwd ? shortenPath(run.cwd) : shortenPath(run.asyncDir);
264
280
  const pending = run.pendingAppends ? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? "" : "s"}` : "";
265
- lines.push(`- ${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode} | ${progress}${pending} | ${cwd}`);
281
+ const runContext = contextModeLabel(run.context);
282
+ lines.push(`- ${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode}${runContext ? ` ${runContext}` : ""} | ${progress}${pending} | ${cwd}`);
266
283
  lines.push(` status: subagent({ action: "status", id: "${run.id}" })`);
267
284
  lines.push(` transcript: subagent({ action: "status", id: "${run.id}", view: "transcript" })`);
268
285
  for (const step of run.steps) {
269
286
  const display = step.label ? `${step.label} (${step.agent})` : step.agent;
287
+ const stepContext = contextModeLabel(step.context);
270
288
  const phase = step.phase ? `[${step.phase}] ` : "";
271
289
  const stepActivity = formatActivityFacts(step);
272
290
  const modelThinking = formatModelThinking(step.model, step.thinking);
273
- const parts = [`${step.index}. ${phase}${display}`, step.status, stepActivity, modelThinking].filter(Boolean);
291
+ const parts = [`${step.index}. ${phase}${display}${stepContext ? ` ${stepContext}` : ""}`, step.status, stepActivity, modelThinking].filter(Boolean);
274
292
  lines.push(` ${parts.join(" | ")}`);
275
293
  const output = path.join(run.asyncDir, `output-${step.index}.log`);
276
294
  if (fs.existsSync(output)) lines.push(` output: ${shortenPath(output)}`);
@@ -316,7 +334,11 @@ export function inspectSubagentFleet(_params: FleetViewParams, deps: FleetViewDe
316
334
  }
317
335
 
318
336
  const foregroundControls = deps.state ? [...deps.state.foregroundControls.values()] : [];
319
- const total = foregroundControls.length + asyncRuns.length;
337
+ const activeForegroundIds = new Set(foregroundControls.map((control) => control.runId));
338
+ const detachedForegroundRuns = deps.state?.foregroundRuns
339
+ ? [...deps.state.foregroundRuns.values()].filter((run) => run.sessionId === deps.state?.currentSessionId && !activeForegroundIds.has(run.runId) && run.children.some((child) => child.status === "detached"))
340
+ : [];
341
+ const total = foregroundControls.length + detachedForegroundRuns.length + asyncRuns.length;
320
342
  if (total === 0) {
321
343
  return {
322
344
  content: [{ type: "text", text: "No active subagent fleet. Background runs that already finished are available through completion notifications or subagent({ action: \"status\", id: \"...\" })." }],
@@ -324,9 +346,11 @@ export function inspectSubagentFleet(_params: FleetViewParams, deps: FleetViewDe
324
346
  };
325
347
  }
326
348
 
327
- const lines = [`Subagent fleet: ${total} active`, ""];
349
+ const lines = [`Subagent fleet: ${total} tracked`, ""];
328
350
  const foregroundLines = formatForegroundFleetLines(foregroundControls);
329
351
  if (foregroundLines.length) lines.push(...foregroundLines, "");
352
+ const detachedForegroundLines = formatDetachedForegroundFleetLines(detachedForegroundRuns);
353
+ if (detachedForegroundLines.length) lines.push(...detachedForegroundLines, "");
330
354
  const asyncLines = formatAsyncFleetLines(asyncRuns);
331
355
  if (asyncLines.length) lines.push(...asyncLines, "");
332
356
  lines.push("Commands:");
@@ -364,8 +388,9 @@ function selectTranscriptStep(status: AsyncStatus, options: TranscriptOptions):
364
388
  function stepStateLine(mode: SubagentRunMode, index: number | undefined, step: AsyncJobStep | undefined): string | undefined {
365
389
  if (index === undefined || !step) return undefined;
366
390
  const modelThinking = formatModelThinking(step.model, step.thinking);
391
+ const context = contextModeLabel(step.context);
367
392
  const parts = [
368
- `${mode === "parallel" ? "Agent" : "Step"}: ${index} (${step.agent})`,
393
+ `${mode === "parallel" ? "Agent" : "Step"}: ${index} (${step.agent})${context ? ` ${context}` : ""}`,
369
394
  step.status,
370
395
  formatActivityFacts(step),
371
396
  modelThinking,
@@ -407,10 +432,11 @@ export function formatAsyncRunTranscript(status: AsyncStatus, asyncDir: string,
407
432
  const sessionFile = selected.index !== undefined ? selected.step?.sessionFile : status.sessionFile;
408
433
  const eventsPath = path.join(asyncDir, "events.jsonl");
409
434
 
435
+ const context = contextModeLabel(status.context ?? summarizeContextModes((status.steps ?? []).map((step) => step.context)));
410
436
  const lines = [
411
437
  `Run: ${status.runId}`,
412
438
  `State: ${status.state}`,
413
- `Mode: ${status.mode}`,
439
+ `Mode: ${status.mode}${context ? ` ${context}` : ""}`,
414
440
  stepStateLine(status.mode, selected.index, selected.step),
415
441
  selected.hint,
416
442
  ].filter((line): line is string => Boolean(line));