@selesai/code 0.5.19 → 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 (265) 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-intercom/broker/spawn.test.ts +31 -0
  4. package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
  5. package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
  6. package/dist/extensions/pi-subagents/README.md +602 -220
  7. package/dist/extensions/pi-subagents/banner.png +0 -0
  8. package/dist/extensions/pi-subagents/index.ts +1 -0
  9. package/dist/extensions/pi-subagents/install.mjs +3 -3
  10. package/dist/extensions/pi-subagents/package-lock.json +3405 -0
  11. package/dist/extensions/pi-subagents/package.json +26 -14
  12. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  13. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  14. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
  15. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
  16. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  17. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  18. package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
  20. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
  21. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  22. package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
  23. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
  24. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  25. package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
  26. package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
  27. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
  28. package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
  29. package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
  30. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
  31. package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
  32. package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
  33. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
  34. package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
  35. package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
  36. package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
  37. package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
  38. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
  39. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
  40. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
  41. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
  42. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
  43. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
  44. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
  45. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
  46. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
  47. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
  48. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
  49. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
  50. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
  51. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
  52. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
  53. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
  54. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
  55. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
  56. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
  57. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
  58. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
  59. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
  61. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
  62. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
  63. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
  64. package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
  65. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
  66. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
  67. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
  68. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
  69. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
  70. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
  71. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
  72. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
  73. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
  74. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
  75. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
  76. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
  77. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
  78. package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
  79. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
  80. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
  81. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
  84. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
  86. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
  88. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
  89. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
  90. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
  92. package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
  95. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
  96. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
  97. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
  98. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
  99. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
  100. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
  101. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
  102. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
  103. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
  104. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
  105. package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
  106. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
  107. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
  108. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
  109. package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
  110. package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
  111. package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
  112. package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
  113. package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
  114. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
  115. package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
  116. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
  117. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
  118. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
  119. package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
  120. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
  121. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
  122. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
  127. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
  128. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
  129. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
  130. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
  131. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
  132. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
  133. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
  134. package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
  135. package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
  136. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
  137. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
  138. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
  139. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
  140. package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
  141. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
  142. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
  143. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
  144. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
  145. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
  146. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
  147. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
  148. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
  149. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
  150. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
  151. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
  152. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
  153. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
  154. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
  155. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
  156. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
  157. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
  158. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
  159. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
  160. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
  161. package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
  162. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
  163. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
  164. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
  165. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
  166. package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
  167. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
  168. package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
  169. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  170. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  171. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
  172. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
  173. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
  175. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
  176. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
  177. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
  178. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
  179. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
  180. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
  181. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
  182. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
  183. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
  184. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
  185. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
  186. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
  187. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
  188. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
  189. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
  190. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
  191. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
  192. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
  193. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
  194. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
  195. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
  196. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
  197. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
  198. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
  199. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
  200. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
  201. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
  202. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
  203. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
  204. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
  205. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
  206. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
  207. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
  208. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
  209. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
  210. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
  211. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
  212. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
  213. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
  214. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
  215. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
  216. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
  217. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
  218. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
  219. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
  220. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
  221. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
  222. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
  223. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
  224. package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
  225. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
  226. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
  227. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
  228. package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
  229. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
  230. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
  231. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
  232. package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
  233. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
  234. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
  235. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
  236. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
  237. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
  238. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
  239. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
  240. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
  241. package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
  245. package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
  246. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
  247. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
  248. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
  249. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
  250. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
  251. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
  252. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
  253. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
  254. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
  255. package/dist/extensions/question/batch.ts +39 -0
  256. package/dist/extensions/question/dialog-adapter.ts +23 -1
  257. package/dist/extensions/question/index.ts +237 -7
  258. package/dist/extensions/question/schemas.ts +28 -15
  259. package/dist/extensions/question/tests/batch.test.ts +26 -0
  260. package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
  261. package/dist/extensions/question/tui-adapter.ts +2 -3
  262. package/dist/extensions/question/types.ts +37 -0
  263. package/dist/extensions/question/ui-protocol.ts +3 -3
  264. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  265. package/package.json +2 -1
@@ -10,7 +10,9 @@
10
10
 
11
11
  import { describe, it, before, after, beforeEach, afterEach } from "node:test";
12
12
  import assert from "node:assert/strict";
13
+ import { spawnSync } from "node:child_process";
13
14
  import * as fs from "node:fs";
15
+ import * as os from "node:os";
14
16
  import * as path from "node:path";
15
17
  import type { MockPi } from "../support/helpers.ts";
16
18
  import {
@@ -112,11 +114,15 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
112
114
  removeTempDir(tempDir);
113
115
  });
114
116
 
115
- function makeExecutor(agents = [makeAgent("echo")]) {
117
+ function makeExecutor(
118
+ agents = [makeAgent("echo")],
119
+ state = { baseCwd: tempDir, currentSessionId: null, asyncJobs: new Map(), foregroundControls: new Map(), lastForegroundControlId: null },
120
+ config: Record<string, unknown> = {},
121
+ ) {
116
122
  return createSubagentExecutor({
117
123
  pi: { events: createEventBus(), getSessionName: () => undefined },
118
- state: { baseCwd: tempDir, currentSessionId: null, asyncJobs: new Map(), foregroundControls: new Map(), lastForegroundControlId: null },
119
- config: {},
124
+ state,
125
+ config,
120
126
  asyncByDefault: false,
121
127
  tempArtifactsDir: tempDir,
122
128
  getSubagentSessionRoot: () => tempDir,
@@ -125,6 +131,12 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
125
131
  });
126
132
  }
127
133
 
134
+ function git(args: string[]): string {
135
+ const result = spawnSync("git", args, { cwd: tempDir, encoding: "utf-8" });
136
+ assert.equal(result.status, 0, result.stderr || result.stdout);
137
+ return result.stdout.trim();
138
+ }
139
+
128
140
  function readLastCallArgs(): string[] {
129
141
  const callFile = fs.readdirSync(mockPi.dir).find((name) => name.startsWith("call-"));
130
142
  assert.ok(callFile, "expected a recorded mock pi call");
@@ -171,6 +183,124 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
171
183
  assert.equal(ok, 2);
172
184
  });
173
185
 
186
+ it("tracks every concurrently running foreground child", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
187
+ for (let index = 0; index < 3; index++) mockPi.onCall({ output: `Review ${index} complete`, delay: 250 });
188
+ const agents = [makeAgent("reviewer")];
189
+ const state = {
190
+ baseCwd: tempDir,
191
+ currentSessionId: null,
192
+ asyncJobs: new Map(),
193
+ foregroundControls: new Map(),
194
+ lastForegroundControlId: null,
195
+ };
196
+ const executor = makeExecutor(agents, state);
197
+ const executionPromise = executor.execute(
198
+ "parallel-foreground-fleet",
199
+ {
200
+ tasks: [
201
+ { agent: "reviewer", task: "Review correctness" },
202
+ { agent: "reviewer", task: "Review quality" },
203
+ { agent: "reviewer", task: "Review tests" },
204
+ ],
205
+ },
206
+ new AbortController().signal,
207
+ undefined,
208
+ makeMinimalCtx(tempDir),
209
+ );
210
+
211
+ for (let attempt = 0; attempt < 40; attempt++) {
212
+ const active = [...state.foregroundControls.values()][0]?.activeChildren;
213
+ if (active?.size === 3) break;
214
+ await new Promise((resolve) => setTimeout(resolve, 10));
215
+ }
216
+ const control = [...state.foregroundControls.values()][0];
217
+ assert.ok(control);
218
+ assert.deepEqual([...control.activeChildren.values()].map((child) => child.description), [
219
+ "Review correctness",
220
+ "Review quality",
221
+ "Review tests",
222
+ ]);
223
+ assert.equal(control.activeChildren.size, 3);
224
+
225
+ const result = await executionPromise;
226
+ assert.equal(result.isError, undefined);
227
+ assert.equal(state.foregroundControls.size, 0);
228
+ });
229
+
230
+ it("publishes a durable handoff before cleaning foreground parallel worktrees", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
231
+ git(["init"]);
232
+ git(["config", "user.email", "test@example.com"]);
233
+ git(["config", "user.name", "Test User"]);
234
+ fs.writeFileSync(path.join(tempDir, "tracked.txt"), "base\n", "utf-8");
235
+ git(["add", "tracked.txt"]);
236
+ git(["commit", "-m", "initial"]);
237
+ mockPi.onCall({ output: "Worktree task complete" });
238
+ const executor = makeExecutor();
239
+ const result = await executor.execute(
240
+ "foreground-worktree-handoff",
241
+ { tasks: [{ agent: "echo", task: "Work in isolation" }], worktree: true },
242
+ new AbortController().signal,
243
+ undefined,
244
+ makeMinimalCtx(tempDir),
245
+ );
246
+
247
+ assert.equal(result.isError, undefined);
248
+ assert.equal(result.details?.parallelHandoff?.version, 1);
249
+ assert.equal(result.details?.parallelHandoff?.cleanupState, "complete");
250
+ assert.match(result.content[0]?.text ?? "", /Parallel handoff:/);
251
+ const handoffPath = result.details!.parallelHandoff!.path;
252
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
253
+ groups: Array<{ children: Array<{ agent: string; summary: string; patch: { path: string } }>; cleanup: { state: string; tasks: Array<{ path: string; worktreeRemoved: boolean; branchRemoved: boolean }> } }>;
254
+ };
255
+ assert.equal(handoff.groups[0]!.children[0]!.agent, "echo");
256
+ assert.equal(handoff.groups[0]!.children[0]!.summary, "Worktree task complete");
257
+ assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true);
258
+ assert.ok(result.details?.runId);
259
+ assert.ok(handoff.groups[0]!.children[0]!.patch.path.includes(`${path.sep}worktree-diffs${path.sep}${result.details.runId}${path.sep}`));
260
+ assert.equal(handoff.groups[0]!.cleanup.state, "complete");
261
+ assert.equal(handoff.groups[0]!.cleanup.tasks[0]!.worktreeRemoved, true);
262
+ assert.equal(handoff.groups[0]!.cleanup.tasks[0]!.branchRemoved, true);
263
+ assert.equal(fs.existsSync(handoff.groups[0]!.cleanup.tasks[0]!.path), false);
264
+ });
265
+
266
+ it("keeps worktree parallel runs successful when handoff manifest writing fails", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
267
+ git(["init"]);
268
+ git(["config", "user.email", "test@example.com"]);
269
+ git(["config", "user.name", "Test User"]);
270
+ fs.writeFileSync(path.join(tempDir, "tracked.txt"), "base\n", "utf-8");
271
+ git(["add", "tracked.txt"]);
272
+ git(["commit", "-m", "initial"]);
273
+ const sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-session-"));
274
+ try {
275
+ const artifactsDir = path.join(sessionDir, "subagent-artifacts");
276
+ fs.mkdirSync(artifactsDir, { recursive: true });
277
+ fs.writeFileSync(path.join(artifactsDir, "handoffs"), "not a directory", "utf-8");
278
+ mockPi.onCall({ output: "Worktree task complete" });
279
+ const executor = makeExecutor([makeAgent("echo")], undefined, { artifactDir: "session" });
280
+ const ctx = {
281
+ ...makeMinimalCtx(tempDir),
282
+ sessionManager: {
283
+ getSessionId: () => "session-123",
284
+ getSessionFile: () => path.join(sessionDir, "session.jsonl"),
285
+ },
286
+ };
287
+ const result = await executor.execute(
288
+ "foreground-worktree-handoff-collision",
289
+ { tasks: [{ agent: "echo", task: "Work in isolation" }], worktree: true },
290
+ new AbortController().signal,
291
+ undefined,
292
+ ctx,
293
+ );
294
+
295
+ assert.equal(result.isError, undefined);
296
+ assert.equal(result.details?.parallelHandoff, undefined);
297
+ assert.match(result.content[0]?.text ?? "", /Parallel handoff unavailable:/);
298
+ assert.doesNotMatch(git(["worktree", "list", "--porcelain"]), /pi-parallel-/);
299
+ } finally {
300
+ fs.rmSync(sessionDir, { recursive: true, force: true });
301
+ }
302
+ });
303
+
174
304
  it("treats parallel action aliases with tasks as top-level parallel execution", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
175
305
  for (const action of ["parallel", "PARALLEL", "tasks"]) {
176
306
  mockPi.reset();
@@ -191,6 +321,42 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
191
321
  }
192
322
  });
193
323
 
324
+ it("does not apply agent acceptance defaults to top-level parallel tasks", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
325
+ mockPi.onCall({ output: "parallel response without explicit acceptance" });
326
+ const executor = makeExecutor([
327
+ makeAgent("echo", { defaultAcceptance: { level: "none", reason: "single launches only" } }),
328
+ ]);
329
+
330
+ const result = await executor.execute(
331
+ "parallel-agent-acceptance-default",
332
+ { tasks: [{ agent: "echo", task: "Return a concise answer" }] },
333
+ new AbortController().signal,
334
+ undefined,
335
+ makeMinimalCtx(tempDir),
336
+ );
337
+
338
+ assert.equal(result.isError, undefined);
339
+ assert.equal(result.details?.results?.[0]?.acceptance?.explicit, false);
340
+ assert.equal(result.details?.results?.[0]?.acceptance?.effectiveAcceptance.level, "attested");
341
+ });
342
+
343
+ it("applies agent acceptance roles to inferred parallel acceptance", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
344
+ mockPi.onCall({ output: "exploration complete" });
345
+ const executor = makeExecutor([
346
+ makeAgent("worker", { acceptanceRole: "read-only" }),
347
+ ]);
348
+
349
+ const result = await executor.execute(
350
+ "parallel-agent-acceptance-role",
351
+ { tasks: [{ agent: "worker", task: "Explore the authentication flow" }] },
352
+ new AbortController().signal,
353
+ undefined,
354
+ makeMinimalCtx(tempDir),
355
+ );
356
+
357
+ assert.equal(result.details?.results?.[0]?.acceptance?.effectiveAcceptance.level, "attested");
358
+ });
359
+
194
360
  it("top-level parallel output saves use per-task output paths", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
195
361
  mockPi.onCall({ output: "Saved report" });
196
362
  const executor = makeExecutor();
@@ -211,6 +377,32 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
211
377
  assert.equal(result.details?.results?.[0]?.savedOutputPath, outputPath);
212
378
  });
213
379
 
380
+ it("top-level parallel tasks support outputSchema", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
381
+ mockPi.onCall({ matchArgIncludes: "First structured", output: "first", structuredOutput: { ok: true, item: "first" } });
382
+ mockPi.onCall({ matchArgIncludes: "Second structured", output: "second", structuredOutput: { ok: true, item: "second" } });
383
+ const executor = makeExecutor();
384
+
385
+ const result = await executor.execute(
386
+ "parallel-schema",
387
+ {
388
+ tasks: [
389
+ { agent: "echo", task: "First structured", outputSchema: { type: "object", required: ["ok", "item"], properties: { ok: { type: "boolean" }, item: { type: "string" } } } },
390
+ { agent: "echo", task: "Second structured", outputSchema: { type: "object", required: ["ok", "item"], properties: { ok: { type: "boolean" }, item: { type: "string" } } } },
391
+ ],
392
+ acceptance: false,
393
+ },
394
+ new AbortController().signal,
395
+ undefined,
396
+ makeMinimalCtx(tempDir),
397
+ );
398
+
399
+ assert.equal(result.isError, undefined);
400
+ assert.deepEqual(result.details?.results?.map((item: { structuredOutput?: unknown }) => item.structuredOutput), [
401
+ { ok: true, item: "first" },
402
+ { ok: true, item: "second" },
403
+ ]);
404
+ });
405
+
214
406
  it("top-level parallel preserves completed siblings and marks timed-out children", { skip: !createSubagentExecutor ? "executor not importable" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
215
407
  mockPi.onCall({ matchArgIncludes: "Slow review", delay: 10000 });
216
408
  mockPi.onCall({ matchArgIncludes: "Fast review", output: "fast done" });
@@ -306,6 +498,38 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
306
498
  assert.equal(mockPi.callCount(), 0);
307
499
  });
308
500
 
501
+ for (const outputOverride of [undefined, true] as const) {
502
+ it(`isolates foreground inherited output (${outputOverride === true ? "output:true" : "omitted output"})`, { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
503
+ mockPi.onCall({ matchArgIncludes: "Write A", output: "first foreground report" });
504
+ mockPi.onCall({ matchArgIncludes: "Write B", output: "second foreground report" });
505
+ const executor = makeExecutor([makeAgent("echo", { output: "context.md" })]);
506
+ const tasks = [
507
+ { agent: "echo", task: "Write A", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
508
+ { agent: "echo", task: "Write B", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
509
+ ];
510
+
511
+ const result = await executor.execute(
512
+ `parallel-inherited-output-${outputOverride === true ? "true" : "omitted"}`,
513
+ { tasks },
514
+ new AbortController().signal,
515
+ undefined,
516
+ makeMinimalCtx(tempDir),
517
+ );
518
+
519
+ const runId = result.details?.runId;
520
+ assert.ok(runId, "expected run id in details");
521
+ const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", runId);
522
+ const firstOutputPath = path.join(outputDir, "parallel-0", "0-echo", "context.md");
523
+ const secondOutputPath = path.join(outputDir, "parallel-0", "1-echo", "context.md");
524
+ assert.equal(result.isError, undefined);
525
+ assert.equal(mockPi.callCount(), 2);
526
+ assert.equal(fs.readFileSync(firstOutputPath, "utf-8"), "first foreground report");
527
+ assert.equal(fs.readFileSync(secondOutputPath, "utf-8"), "second foreground report");
528
+ assert.equal(result.details?.results?.[0]?.savedOutputPath, firstOutputPath);
529
+ assert.equal(result.details?.results?.[1]?.savedOutputPath, secondOutputPath);
530
+ });
531
+ }
532
+
309
533
  it("treats string false as disabled output in top-level parallel runs", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
310
534
  mockPi.onCall({ output: "Review done" });
311
535
  const executor = makeExecutor();
@@ -1,12 +1,14 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
+ import { keyText } from "@selesai/code";
3
4
 
4
5
  type RenderSubagentResult = (
5
6
  result: {
6
7
  content: Array<{ type: "text"; text: string }>;
8
+ isError?: boolean;
7
9
  details?: {
8
10
  mode: "single" | "parallel" | "chain" | "management";
9
- context?: "fresh" | "fork";
11
+ context?: "fresh" | "fork" | "mixed";
10
12
  results: unknown[];
11
13
  };
12
14
  },
@@ -26,9 +28,25 @@ const theme = {
26
28
  fg: (_name: string, text: string) => text,
27
29
  bold: (text: string) => text,
28
30
  };
31
+ const expandKey = keyText("app.tools.expand");
32
+ const expandHint = `Press ${expandKey} for full output`;
29
33
 
30
34
  const emptyUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
31
35
 
36
+ function nestedChild(id: string, state: "running" | "complete" | "failed" = "running") {
37
+ return {
38
+ id,
39
+ parentRunId: "root",
40
+ parentStepIndex: 0,
41
+ depth: 1,
42
+ path: [{ runId: "root", stepIndex: 0, agent: "owner" }],
43
+ state,
44
+ agent: id,
45
+ lastUpdate: 1_700_000_002_000,
46
+ ...(state === "running" ? { currentTool: "bash" } : {}),
47
+ };
48
+ }
49
+
32
50
  function firstGrapheme(text: string): string {
33
51
  return Array.from(text.trimStart())[0] ?? "";
34
52
  }
@@ -50,6 +68,148 @@ function withTerminalWidth<T>(columns: number, fn: () => T): T {
50
68
  }
51
69
 
52
70
  describe("renderSubagentResult fork indicator", () => {
71
+ it("renders result-owned nested children for foreground single, parallel, and chain runs", () => {
72
+ const cases = [
73
+ {
74
+ mode: "single" as const,
75
+ results: [{ agent: "single", task: "run", exitCode: 0, messages: [], usage: emptyUsage, children: [nestedChild("single-child")] }],
76
+ },
77
+ {
78
+ mode: "parallel" as const,
79
+ totalSteps: 2,
80
+ results: [
81
+ { agent: "parallel-a", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 0, agent: "parallel-a", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("parallel-child-a")] },
82
+ { agent: "parallel-b", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 1, agent: "parallel-b", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("parallel-child-b")] },
83
+ ],
84
+ },
85
+ {
86
+ mode: "chain" as const,
87
+ chainAgents: ["chain-a", "chain-b"],
88
+ results: [
89
+ { agent: "chain-a", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 0, agent: "chain-a", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("chain-child-a")] },
90
+ { agent: "chain-b", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 1, agent: "chain-b", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("chain-child-b")] },
91
+ ],
92
+ },
93
+ ];
94
+
95
+ for (const details of cases) {
96
+ const widget = renderSubagentResult!({ content: [{ type: "text", text: "running" }], details }, { expanded: true }, theme);
97
+ const text = widget.render(160).join("\n");
98
+ for (const result of details.results) {
99
+ assert.match(text, new RegExp(`${result.children[0].id} · running`));
100
+ }
101
+ }
102
+ });
103
+
104
+ it("renders result-owned nested children for terminal foreground results", () => {
105
+ const widget = renderSubagentResult!({
106
+ content: [{ type: "text", text: "failed" }],
107
+ details: {
108
+ mode: "parallel",
109
+ totalSteps: 1,
110
+ results: [{
111
+ agent: "owner",
112
+ task: "run",
113
+ exitCode: 1,
114
+ error: "failed",
115
+ messages: [],
116
+ usage: emptyUsage,
117
+ children: [nestedChild("terminal-child", "failed")],
118
+ }],
119
+ },
120
+ }, { expanded: true }, theme);
121
+
122
+ assert.match(widget.render(160).join("\n"), /terminal-child · failed/);
123
+
124
+ const compact = renderSubagentResult!({
125
+ content: [{ type: "text", text: "done" }],
126
+ details: {
127
+ mode: "single",
128
+ results: [{
129
+ agent: "owner",
130
+ task: "run",
131
+ exitCode: 0,
132
+ messages: [],
133
+ usage: emptyUsage,
134
+ children: [nestedChild("compact-terminal-child", "complete")],
135
+ }],
136
+ },
137
+ }, { expanded: false }, theme).render(160).join("\n");
138
+ assert.match(compact, /↳ \[\d{2}:\d{2}:\d{2}\] \+1 nested run \(1 complete\)/);
139
+ assert.doesNotMatch(compact, /compact-terminal-child · complete/);
140
+ });
141
+ it("collapses multiline structured management output to a first-line summary", () => {
142
+ const output = `\n${"Managed agents: ".padEnd(220, "x")}\n- reviewer\n- writer`;
143
+ const widget = renderSubagentResult!({
144
+ content: [{ type: "text", text: output }],
145
+ details: { mode: "management", context: "fork", results: [] },
146
+ }, { expanded: false }, theme);
147
+
148
+ const lines = widget.render(120).map((line) => line.trimEnd());
149
+ assert.match(lines[0]!, /^\[fork\] Managed agents:/);
150
+ assert.match(lines[0]!, /…$/);
151
+ const hintLineIndex = lines.findIndex((line) => line.includes(expandHint) || (expandKey === "" && line.includes("Press ") && line.includes(" for full output")));
152
+ assert.ok(hintLineIndex > 0);
153
+ assert.doesNotMatch(lines[0]!, /reviewer/);
154
+ });
155
+
156
+ it("keeps multiline structured zero-result errors visible", () => {
157
+ const widget = renderSubagentResult!({
158
+ content: [{ type: "text", text: "Error: management failed\nfirst diagnostic\nsecond diagnostic" }],
159
+ isError: true,
160
+ details: { mode: "management", results: [] },
161
+ }, { expanded: false }, theme);
162
+
163
+ const text = widget.render(120).join("\n");
164
+ assert.match(text, /Error: management failed/);
165
+ assert.match(text, /first diagnostic/);
166
+ assert.match(text, /second diagnostic/);
167
+ assert.ok(!text.includes(expandHint));
168
+ });
169
+
170
+ it("keeps full multiline structured output when expanded", () => {
171
+ const output = "Managed agents:\n- reviewer\n- writer";
172
+ const widget = renderSubagentResult!({
173
+ content: [{ type: "text", text: output }],
174
+ details: { mode: "management", results: [] },
175
+ }, { expanded: true }, theme);
176
+
177
+ const text = widget.render(120).join("\n");
178
+ assert.match(text, /Managed agents:/);
179
+ assert.match(text, /- reviewer/);
180
+ assert.match(text, /- writer/);
181
+ assert.ok(!text.includes(expandHint));
182
+ });
183
+
184
+ it("collapses multiline structured single output using the same contract", () => {
185
+ const widget = renderSubagentResult!({
186
+ content: [{ type: "text", text: "Run status:\nState: running\nTranscript: available" }],
187
+ details: { mode: "single", results: [] },
188
+ }, { expanded: false }, theme);
189
+
190
+ const text = widget.render(120).join("\n");
191
+ assert.match(text, /^Run status:/);
192
+ assert.match(text, /3 lines/);
193
+ assert.ok(text.includes(expandHint) || (expandKey === "" && text.includes("Press ") && text.includes(" for full output")));
194
+ assert.doesNotMatch(text, /State: running/);
195
+ });
196
+
197
+ it("preserves unstructured multiline and structured single-line output", () => {
198
+ const unstructured = renderSubagentResult!({
199
+ content: [{ type: "text", text: "Error:\nfirst detail\nsecond detail" }],
200
+ }, { expanded: false }, theme).render(120).join("\n");
201
+ assert.match(unstructured, /first detail/);
202
+ assert.match(unstructured, /second detail/);
203
+ assert.ok(!unstructured.includes(expandHint));
204
+
205
+ const singleLine = renderSubagentResult!({
206
+ content: [{ type: "text", text: "No active async run transcript is available." }],
207
+ details: { mode: "single", results: [] },
208
+ }, { expanded: false }, theme).render(120).map((line) => line.trimEnd()).join("\n");
209
+ assert.equal(singleLine, "No active async run transcript is available.");
210
+ assert.ok(!singleLine.includes(expandHint));
211
+ });
212
+
53
213
  it("shows [fork] when details are empty but context is fork", () => {
54
214
  const widget = renderSubagentResult!({
55
215
  content: [{ type: "text", text: "Async: reviewer [abc123]" }],
@@ -60,24 +220,92 @@ describe("renderSubagentResult fork indicator", () => {
60
220
  assert.match(text, /\[fork\]/);
61
221
  });
62
222
 
63
- it("shows [fork] on single-result header", () => {
223
+ it("shows nested foreground children with timestamps on running single results", () => {
64
224
  const widget = renderSubagentResult!({
65
- content: [{ type: "text", text: "done" }],
225
+ content: [{ type: "text", text: "running" }],
66
226
  details: {
67
227
  mode: "single",
68
- context: "fork",
69
228
  results: [{
70
- agent: "reviewer",
71
- task: "review",
229
+ agent: "execute",
230
+ task: "run plan",
72
231
  exitCode: 0,
73
232
  messages: [],
74
233
  usage: emptyUsage,
234
+ progress: { status: "running", index: 0, agent: "execute", toolCount: 1, tokens: 10, durationMs: 1_000, lastActivityAt: 1_700_000_001_000 },
235
+ children: [{
236
+ id: "impl-1",
237
+ parentRunId: "root",
238
+ parentStepIndex: 0,
239
+ depth: 1,
240
+ path: [{ runId: "root", stepIndex: 0, agent: "execute" }],
241
+ state: "running",
242
+ agent: "implement",
243
+ lastUpdate: 1_700_000_002_000,
244
+ currentTool: "bash",
245
+ }],
75
246
  }],
76
247
  },
77
248
  }, { expanded: false }, theme);
78
249
 
79
250
  const text = widget.render(120).join("\n");
80
- assert.match(text, /\[fork\]/);
251
+ assert.match(text, /↳ \[\d{2}:\d{2}:\d{2}\] \+1 nested run \(1 running\)/);
252
+ });
253
+
254
+ it("shows [fresh] and [fork] on single-result headers", () => {
255
+ for (const context of ["fresh", "fork"] as const) {
256
+ const widget = renderSubagentResult!({
257
+ content: [{ type: "text", text: "done" }],
258
+ details: {
259
+ mode: "single",
260
+ context,
261
+ results: [{
262
+ agent: "reviewer",
263
+ task: "review",
264
+ context,
265
+ exitCode: 0,
266
+ messages: [],
267
+ usage: emptyUsage,
268
+ }],
269
+ },
270
+ }, { expanded: false }, theme);
271
+
272
+ const text = widget.render(120).join("\n");
273
+ assert.match(text, new RegExp(`\\[${context}\\]`));
274
+ }
275
+ });
276
+
277
+ it("shows [mixed] on mixed runs and per-child context badges", () => {
278
+ const compact = renderSubagentResult!({
279
+ content: [{ type: "text", text: "done" }],
280
+ details: {
281
+ mode: "parallel",
282
+ context: "mixed",
283
+ results: [
284
+ { agent: "scout", task: "scan", context: "fresh", exitCode: 0, messages: [], usage: emptyUsage },
285
+ { agent: "worker", task: "fix", context: "fork", exitCode: 0, messages: [], usage: emptyUsage },
286
+ ],
287
+ },
288
+ }, { expanded: false }, theme).render(160).join("\n");
289
+
290
+ assert.match(compact, /parallel \[mixed\]/);
291
+ assert.match(compact, /scout \[fresh\]/);
292
+ assert.match(compact, /worker \[fork\]/);
293
+
294
+ const expanded = renderSubagentResult!({
295
+ content: [{ type: "text", text: "done" }],
296
+ details: {
297
+ mode: "parallel",
298
+ context: "mixed",
299
+ results: [
300
+ { agent: "scout", task: "scan", context: "fresh", exitCode: 0, messages: [], usage: emptyUsage },
301
+ { agent: "worker", task: "fix", context: "fork", exitCode: 0, messages: [], usage: emptyUsage },
302
+ ],
303
+ },
304
+ }, { expanded: true }, theme).render(160).join("\n");
305
+
306
+ assert.match(expanded, /parallel \[mixed\]/);
307
+ assert.match(expanded, /scout \[fresh\]/);
308
+ assert.match(expanded, /worker \[fork\]/);
81
309
  });
82
310
 
83
311
  it("uses compacted tool-call summaries when messages were stripped", () => {
@@ -333,6 +333,33 @@ describe("subagent async widget rendering", () => {
333
333
  assert.match(text, /Agent 3\/3: reviewer · complete · 1\.5k token/);
334
334
  });
335
335
 
336
+ it("shows async job and step context badges", () => {
337
+ const now = Date.now();
338
+ const text = buildWidgetLines([
339
+ {
340
+ asyncId: "run-1",
341
+ asyncDir: "/tmp/1",
342
+ status: "running",
343
+ mode: "parallel",
344
+ context: "mixed",
345
+ agents: ["scout", "worker"],
346
+ activeParallelGroup: true,
347
+ runningSteps: 2,
348
+ completedSteps: 0,
349
+ stepsTotal: 2,
350
+ updatedAt: now,
351
+ steps: [
352
+ { agent: "scout", context: "fresh", status: "running", lastActivityAt: now },
353
+ { agent: "worker", context: "fork", status: "running", currentTool: "bash", currentToolStartedAt: now - 1000 },
354
+ ],
355
+ },
356
+ ], theme, 160).join("\n");
357
+
358
+ assert.match(text, /parallel \[mixed\]/);
359
+ assert.match(text, /Agent 1\/2: scout \[fresh\] · running/);
360
+ assert.match(text, /Agent 2\/2: worker \[fork\] · running/);
361
+ });
362
+
336
363
  it("shows model and thinking for active async widget rows", () => {
337
364
  const lines = buildWidgetLines([
338
365
  {