@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
@@ -5,7 +5,7 @@ import * as path from "node:path";
5
5
  import type { MockPi } from "../support/helpers.ts";
6
6
  import { createEventBus, createMockPi, createTempDir, events, removeTempDir, tryImport } from "../support/helpers.ts";
7
7
  import { discoverAgents } from "../../src/agents/agents.ts";
8
- import { INTERCOM_DETACH_REQUEST_EVENT } from "../../src/shared/types.ts";
8
+ import { DEFAULT_FORK_PREAMBLE, INTERCOM_DETACH_REQUEST_EVENT, SUBAGENT_ASYNC_STARTED_EVENT } from "../../src/shared/types.ts";
9
9
 
10
10
  interface ExecutorModule {
11
11
  createSubagentExecutor?: (...args: unknown[]) => {
@@ -19,10 +19,10 @@ interface ExecutorModule {
19
19
  isError?: boolean;
20
20
  content: Array<{ text?: string }>;
21
21
  details?: {
22
- context?: "fresh" | "fork";
22
+ context?: "fresh" | "fork" | "mixed";
23
23
  mode?: "single" | "parallel" | "chain";
24
24
  asyncId?: string;
25
- results?: Array<{ detached?: boolean; exitCode?: number; skills?: string[] }>;
25
+ results?: Array<{ context?: "fresh" | "fork"; detached?: boolean; exitCode?: number; skills?: string[] }>;
26
26
  };
27
27
  }>;
28
28
  };
@@ -277,6 +277,25 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
277
277
  };
278
278
  }
279
279
 
280
+ function makeSignedThinkingSessionManager(childSessionFile: string) {
281
+ const parentSessionFile = path.join(tempDir, "parent.jsonl");
282
+ fs.writeFileSync(parentSessionFile, '{"type":"session","version":1,"id":"parent","timestamp":"2026-04-16T00:00:00.000Z","cwd":"/tmp"}\n', "utf-8");
283
+ return {
284
+ getSessionId: () => "session-123",
285
+ getSessionFile: () => parentSessionFile,
286
+ getLeafId: () => "assistant-1",
287
+ openSession: () => ({
288
+ createBranchedSession: () => {
289
+ fs.writeFileSync(childSessionFile, [
290
+ { type: "session", version: 1, id: "child", timestamp: "2026-04-16T00:00:00.000Z", cwd: "/tmp", parentSession: parentSessionFile },
291
+ { type: "message", id: "assistant-1", parentId: null, timestamp: "2026-04-16T00:00:02.000Z", message: { role: "assistant", provider: "anthropic", api: "anthropic-messages", model: "anthropic/claude-sonnet-4-5", content: [{ type: "thinking", thinking: "private chain", thinkingSignature: "signed" }, { type: "text", text: "answer" }] } },
292
+ ].map((entry) => JSON.stringify(entry)).join("\n") + "\n", "utf-8");
293
+ return childSessionFile;
294
+ },
295
+ }),
296
+ };
297
+ }
298
+
280
299
  it("runs a single agent when task is omitted", async () => {
281
300
  const { manager } = makeSessionManagerRecorder();
282
301
  const executor = makeExecutor();
@@ -419,12 +438,21 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
419
438
  projectAgentsDir: null,
420
439
  }));
421
440
 
441
+ const ctx = {
442
+ ...makeCtx(manager),
443
+ modelRegistry: {
444
+ getAvailable: () => [
445
+ { provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true },
446
+ { provider: "anthropic", id: "claude-sonnet-4", api: "anthropic-messages", reasoning: true },
447
+ ],
448
+ },
449
+ };
422
450
  const result = await executor.execute(
423
451
  "id",
424
452
  { agent: "worker", task: "test" },
425
453
  new AbortController().signal,
426
454
  undefined,
427
- makeCtx(manager),
455
+ ctx,
428
456
  );
429
457
 
430
458
  assert.equal(result.isError, undefined);
@@ -432,6 +460,353 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
432
460
  assert.deepEqual(modelArgs, ["openai/gpt-5-mini:off", "anthropic/claude-sonnet-4:off"]);
433
461
  });
434
462
 
463
+ it("keeps requested thinking for non-Anthropic forked children without Anthropic fallbacks", async () => {
464
+ mockPi.reset();
465
+ mockPi.onCall({ output: "done" });
466
+ const parentSessionFile = path.join(tempDir, "parent.jsonl");
467
+ const childSessionFile = path.join(tempDir, "fork-keep-thinking.jsonl");
468
+ fs.writeFileSync(parentSessionFile, '{"type":"session","version":1,"id":"parent","timestamp":"2026-04-16T00:00:00.000Z","cwd":"/tmp"}\n', "utf-8");
469
+ const manager = {
470
+ getSessionId: () => "session-123",
471
+ getSessionFile: () => parentSessionFile,
472
+ getLeafId: () => "assistant-1",
473
+ openSession: () => ({
474
+ createBranchedSession: () => {
475
+ fs.writeFileSync(childSessionFile, [
476
+ { type: "session", version: 1, id: "child", timestamp: "2026-04-16T00:00:00.000Z", cwd: "/tmp", parentSession: parentSessionFile },
477
+ { type: "message", id: "assistant-1", parentId: null, timestamp: "2026-04-16T00:00:02.000Z", message: { role: "assistant", provider: "anthropic", api: "anthropic-messages", model: "anthropic/claude-sonnet-4-5", content: [{ type: "thinking", thinking: "private chain", thinkingSignature: "signed" }, { type: "text", text: "answer" }] } },
478
+ ].map((entry) => JSON.stringify(entry)).join("\n") + "\n", "utf-8");
479
+ return childSessionFile;
480
+ },
481
+ }),
482
+ };
483
+ const executor = makeExecutorWithDiscoverAgents(() => ({
484
+ agents: [
485
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "openai/gpt-5-mini:high", thinking: "high" },
486
+ ],
487
+ projectAgentsDir: null,
488
+ }));
489
+
490
+ const ctx = {
491
+ ...makeCtx(manager),
492
+ modelRegistry: {
493
+ getAvailable: () => [{ provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true }],
494
+ },
495
+ };
496
+ const result = await executor.execute(
497
+ "id",
498
+ { agent: "worker", task: "test" },
499
+ new AbortController().signal,
500
+ undefined,
501
+ ctx,
502
+ );
503
+
504
+ assert.equal(result.isError, undefined);
505
+ const args = readCallArgs();
506
+ assert.equal(args[args.indexOf("--model") + 1], "openai/gpt-5-mini:high");
507
+ const entries = fs.readFileSync(childSessionFile, "utf-8").trim().split("\n").map((line) => JSON.parse(line));
508
+ assert.deepEqual(entries[1].message.content, [{ type: "text", text: "answer" }]);
509
+ assert.equal(entries.length, 2);
510
+ const text = result.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
511
+ assert.equal(text.includes("fork context forced thinking off"), false);
512
+ });
513
+
514
+ it("keeps inherited model-scope violations as warnings in fork mode", async () => {
515
+ const childSessionFile = path.join(tempDir, "fork-model-scope-warning.jsonl");
516
+ const manager = makeSignedThinkingSessionManager(childSessionFile);
517
+ const executor = makeExecutorWithDiscoverAgents(() => ({
518
+ agents: [
519
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "openai/gpt-5-mini:high", thinking: "high" },
520
+ ],
521
+ projectAgentsDir: null,
522
+ modelScope: { enforce: true, allow: ["anthropic/*"] },
523
+ }));
524
+ const ctx = {
525
+ ...makeCtx(manager),
526
+ modelRegistry: {
527
+ getAvailable: () => [{ provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true }],
528
+ },
529
+ };
530
+ const warnings: string[] = [];
531
+ const originalWarn = console.warn;
532
+ console.warn = (message?: unknown) => warnings.push(String(message));
533
+ try {
534
+ const result = await executor.execute(
535
+ "id",
536
+ { agent: "worker", task: "test" },
537
+ new AbortController().signal,
538
+ undefined,
539
+ ctx,
540
+ );
541
+
542
+ assert.equal(result.isError, undefined);
543
+ assert.equal(warnings.length, 1);
544
+ assert.match(warnings[0]!, /outside the configured subagent model scope/);
545
+ const args = readCallArgs();
546
+ assert.equal(args[args.indexOf("--model") + 1], "openai/gpt-5-mini:high");
547
+ } finally {
548
+ console.warn = originalWarn;
549
+ }
550
+ });
551
+
552
+ it("warns when empty inheritance falls back to an out-of-scope agent model", async () => {
553
+ const childSessionFile = path.join(tempDir, "fork-empty-model-scope-warning.jsonl");
554
+ const manager = makeSignedThinkingSessionManager(childSessionFile);
555
+ const executor = makeExecutorWithDiscoverAgents(() => ({
556
+ agents: [
557
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "openai/gpt-5-mini", thinking: "high" },
558
+ ],
559
+ projectAgentsDir: null,
560
+ modelScope: { enforce: true, allow: ["anthropic/*"] },
561
+ }));
562
+ const ctx = {
563
+ ...makeCtx(manager),
564
+ modelRegistry: {
565
+ getAvailable: () => [{ provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true }],
566
+ },
567
+ };
568
+ const warnings: string[] = [];
569
+ const originalWarn = console.warn;
570
+ console.warn = (message?: unknown) => warnings.push(String(message));
571
+ try {
572
+ for (const model of ["", "inherit"]) {
573
+ const result = await executor.execute(
574
+ "id",
575
+ { agent: "worker", task: "test", model },
576
+ new AbortController().signal,
577
+ undefined,
578
+ ctx,
579
+ );
580
+ assert.equal(result.isError, undefined);
581
+ }
582
+
583
+ assert.equal(warnings.length, 2);
584
+ assert.equal(warnings.every((warning) => warning.includes("outside the configured subagent model scope")), true);
585
+ for (const args of readAllCallArgs()) {
586
+ assert.equal(args[args.indexOf("--model") + 1], "openai/gpt-5-mini:high");
587
+ }
588
+ } finally {
589
+ console.warn = originalWarn;
590
+ }
591
+ });
592
+
593
+ it("notes the forced thinking downgrade in the result for Anthropic forked children", async () => {
594
+ mockPi.reset();
595
+ mockPi.onCall({ output: "done" });
596
+ const parentSessionFile = path.join(tempDir, "parent.jsonl");
597
+ const childSessionFile = path.join(tempDir, "fork-note-downgrade.jsonl");
598
+ fs.writeFileSync(parentSessionFile, '{"type":"session","version":1,"id":"parent","timestamp":"2026-04-16T00:00:00.000Z","cwd":"/tmp"}\n', "utf-8");
599
+ const manager = {
600
+ getSessionId: () => "session-123",
601
+ getSessionFile: () => parentSessionFile,
602
+ getLeafId: () => "assistant-1",
603
+ openSession: () => ({
604
+ createBranchedSession: () => {
605
+ fs.writeFileSync(childSessionFile, [
606
+ { type: "session", version: 1, id: "child", timestamp: "2026-04-16T00:00:00.000Z", cwd: "/tmp", parentSession: parentSessionFile },
607
+ { type: "message", id: "assistant-1", parentId: null, timestamp: "2026-04-16T00:00:02.000Z", message: { role: "assistant", provider: "anthropic", api: "anthropic-messages", model: "anthropic/claude-sonnet-4-5", content: [{ type: "thinking", thinking: "private chain", thinkingSignature: "signed" }, { type: "text", text: "answer" }] } },
608
+ ].map((entry) => JSON.stringify(entry)).join("\n") + "\n", "utf-8");
609
+ return childSessionFile;
610
+ },
611
+ }),
612
+ };
613
+ const executor = makeExecutorWithDiscoverAgents(() => ({
614
+ agents: [
615
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "anthropic/claude-sonnet-4-5:high", thinking: "high" },
616
+ ],
617
+ projectAgentsDir: null,
618
+ }));
619
+
620
+ const ctx = {
621
+ ...makeCtx(manager),
622
+ modelRegistry: {
623
+ getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5", api: "anthropic-messages", reasoning: true }],
624
+ },
625
+ };
626
+ const result = await executor.execute(
627
+ "id",
628
+ { agent: "worker", task: "test" },
629
+ new AbortController().signal,
630
+ undefined,
631
+ ctx,
632
+ );
633
+
634
+ assert.equal(result.isError, undefined);
635
+ const text = result.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
636
+ assert.equal(text.includes("fork context forced thinking off for worker (child 0)"), true);
637
+ });
638
+
639
+ it("resolves inherit before classifying a forked child", async () => {
640
+ const childSessionFile = path.join(tempDir, "fork-inherit-thinking.jsonl");
641
+ const manager = makeSignedThinkingSessionManager(childSessionFile);
642
+ const executor = makeExecutorWithDiscoverAgents(() => ({
643
+ agents: [
644
+ { name: "worker", description: "Worker", defaultContext: "fork", thinking: "high" },
645
+ ],
646
+ projectAgentsDir: null,
647
+ }));
648
+ const ctx = {
649
+ ...makeCtx(manager),
650
+ model: { provider: "anthropic", id: "claude-sonnet-4-5" },
651
+ modelRegistry: {
652
+ getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5", api: "anthropic-messages", reasoning: true }],
653
+ },
654
+ };
655
+
656
+ const result = await executor.execute(
657
+ "id",
658
+ { agent: "worker", task: "test", model: "inherit" },
659
+ new AbortController().signal,
660
+ undefined,
661
+ ctx,
662
+ );
663
+
664
+ assert.equal(result.isError, undefined);
665
+ const args = readCallArgs();
666
+ assert.equal(args[args.indexOf("--model") + 1], "anthropic/claude-sonnet-4-5:off");
667
+ });
668
+
669
+ it("classifies empty parallel model overrides as parent inheritance", async () => {
670
+ const childSessionFile = path.join(tempDir, "fork-empty-parallel-model.jsonl");
671
+ const manager = makeSignedThinkingSessionManager(childSessionFile);
672
+ const executor = makeExecutorWithDiscoverAgents(() => ({
673
+ agents: [
674
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "anthropic/claude-sonnet-4-5", thinking: "high" },
675
+ ],
676
+ projectAgentsDir: null,
677
+ }));
678
+ const ctx = {
679
+ ...makeCtx(manager),
680
+ model: { provider: "openai", id: "gpt-5-mini" },
681
+ modelRegistry: {
682
+ getAvailable: () => [
683
+ { provider: "anthropic", id: "claude-sonnet-4-5", api: "anthropic-messages", reasoning: true },
684
+ { provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true },
685
+ ],
686
+ },
687
+ };
688
+
689
+ const result = await executor.execute(
690
+ "id",
691
+ { tasks: [{ agent: "worker", task: "test", model: "" }] },
692
+ new AbortController().signal,
693
+ undefined,
694
+ ctx,
695
+ );
696
+
697
+ assert.equal(result.isError, undefined);
698
+ const args = readCallArgs();
699
+ assert.equal(args[args.indexOf("--model") + 1], "openai/gpt-5-mini:high");
700
+ const text = result.content.filter((block) => block.text).map((block) => block.text).join("\n");
701
+ assert.equal(text.includes("fork context forced thinking off"), false);
702
+ });
703
+
704
+ it("classifies the agent model when empty inheritance has no parent model", async () => {
705
+ const childSessionFile = path.join(tempDir, "fork-empty-model-without-parent.jsonl");
706
+ const manager = makeSignedThinkingSessionManager(childSessionFile);
707
+ const executor = makeExecutorWithDiscoverAgents(() => ({
708
+ agents: [
709
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "openai/gpt-5-mini", thinking: "high" },
710
+ ],
711
+ projectAgentsDir: null,
712
+ }));
713
+ const ctx = {
714
+ ...makeCtx(manager),
715
+ modelRegistry: {
716
+ getAvailable: () => [{ provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true }],
717
+ },
718
+ };
719
+
720
+ const result = await executor.execute(
721
+ "id",
722
+ { tasks: [{ agent: "worker", task: "test", model: "" }] },
723
+ new AbortController().signal,
724
+ undefined,
725
+ ctx,
726
+ );
727
+
728
+ assert.equal(result.isError, undefined);
729
+ const args = readCallArgs();
730
+ assert.equal(args[args.indexOf("--model") + 1], "openai/gpt-5-mini:high");
731
+ const text = result.content.filter((block) => block.text).map((block) => block.text).join("\n");
732
+ assert.equal(text.includes("fork context forced thinking off"), false);
733
+ });
734
+
735
+ it("classifies the effective model selected in Clarify", async () => {
736
+ const childSessionFile = path.join(tempDir, "fork-clarify-thinking.jsonl");
737
+ const manager = makeSignedThinkingSessionManager(childSessionFile);
738
+ const executor = makeExecutorWithDiscoverAgents(() => ({
739
+ agents: [
740
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "anthropic/claude-sonnet-4-5:high", thinking: "high" },
741
+ ],
742
+ projectAgentsDir: null,
743
+ }));
744
+ const ctx = {
745
+ ...makeCtx(manager),
746
+ hasUI: true,
747
+ ui: {
748
+ custom: async () => ({
749
+ confirmed: true,
750
+ runInBackground: false,
751
+ templates: ["test"],
752
+ behaviorOverrides: [{ model: "openai-codex/gpt-5.6-sol:high" }],
753
+ }),
754
+ },
755
+ model: { provider: "anthropic", id: "claude-sonnet-4-5" },
756
+ modelRegistry: {
757
+ getAvailable: () => [
758
+ { provider: "anthropic", id: "claude-sonnet-4-5", api: "anthropic-messages", reasoning: true },
759
+ { provider: "openai-codex", id: "gpt-5.6-sol", api: "openai-responses", reasoning: true },
760
+ ],
761
+ },
762
+ };
763
+
764
+ const result = await executor.execute(
765
+ "id",
766
+ { agent: "worker", task: "test", clarify: true },
767
+ new AbortController().signal,
768
+ undefined,
769
+ ctx,
770
+ );
771
+
772
+ assert.equal(result.isError, undefined);
773
+ const args = readCallArgs();
774
+ assert.equal(args[args.indexOf("--model") + 1], "openai-codex/gpt-5.6-sol:high");
775
+ const text = result.content.filter((block) => block.text).map((block) => block.text).join("\n");
776
+ assert.equal(text.includes("fork context forced thinking off"), false);
777
+ });
778
+
779
+ it("includes the fork-thinking downgrade note on failed results", async () => {
780
+ mockPi.reset();
781
+ mockPi.onCall({ stderr: "task failed", exitCode: 1 });
782
+ const childSessionFile = path.join(tempDir, "fork-failed-thinking.jsonl");
783
+ const manager = makeSignedThinkingSessionManager(childSessionFile);
784
+ const executor = makeExecutorWithDiscoverAgents(() => ({
785
+ agents: [
786
+ { name: "worker", description: "Worker", defaultContext: "fork", model: "anthropic/claude-sonnet-4-5:high", thinking: "high" },
787
+ ],
788
+ projectAgentsDir: null,
789
+ }));
790
+ const ctx = {
791
+ ...makeCtx(manager),
792
+ modelRegistry: {
793
+ getAvailable: () => [{ provider: "anthropic", id: "claude-sonnet-4-5", api: "anthropic-messages", reasoning: true }],
794
+ },
795
+ };
796
+
797
+ const result = await executor.execute(
798
+ "id",
799
+ { agent: "worker", task: "test" },
800
+ new AbortController().signal,
801
+ undefined,
802
+ ctx,
803
+ );
804
+
805
+ assert.equal(result.isError, true);
806
+ const text = result.content.filter((block) => block.text).map((block) => block.text).join("\n");
807
+ assert.equal(text.includes("fork context forced thinking off for worker (child 0)"), true);
808
+ });
809
+
435
810
  it("keeps default-fork context on run-path errors", async () => {
436
811
  const parentSessionFile = path.join(tempDir, "parent.jsonl");
437
812
  const { manager } = makeForkingSessionManagerRecorder({ sessionFile: parentSessionFile, leafId: "leaf-current" });
@@ -479,7 +854,8 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
479
854
  );
480
855
 
481
856
  assert.equal(result.isError, undefined);
482
- assert.equal(result.details?.context, undefined);
857
+ assert.equal(result.details?.context, "fresh");
858
+ assert.equal(result.details?.results?.[0]?.context, "fresh");
483
859
  assert.deepEqual(openedPaths, []);
484
860
  assert.deepEqual(branchedLeafIds, []);
485
861
  assert.notEqual(readSessionArgsFromCalls()[0], path.join(tempDir, "fork-1.jsonl"));
@@ -505,7 +881,8 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
505
881
  );
506
882
 
507
883
  assert.equal(result.isError, undefined);
508
- assert.equal(result.details?.context, "fork");
884
+ assert.equal(result.details?.context, "mixed");
885
+ assert.deepEqual(result.details?.results?.map((entry) => entry.context), ["fork", "fresh"]);
509
886
  assert.deepEqual(openedPaths, [parentSessionFile]);
510
887
  assert.deepEqual(branchedLeafIds, ["leaf-current"]);
511
888
  const workerArgs = readCallArgsForTask("one");
@@ -536,7 +913,8 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
536
913
  );
537
914
 
538
915
  assert.equal(result.isError, undefined);
539
- assert.equal(result.details?.context, undefined);
916
+ assert.equal(result.details?.context, "fresh");
917
+ assert.deepEqual(result.details?.results?.map((entry) => entry.context), ["fresh", "fresh"]);
540
918
  assert.deepEqual(openedPaths, []);
541
919
  });
542
920
 
@@ -560,7 +938,8 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
560
938
  );
561
939
 
562
940
  assert.equal(result.isError, undefined);
563
- assert.equal(result.details?.context, "fork");
941
+ assert.equal(result.details?.context, "mixed");
942
+ assert.deepEqual(result.details?.results?.map((entry) => entry.context), ["fresh", "fork"]);
564
943
  assert.deepEqual(openedPaths, [parentSessionFile]);
565
944
  assert.deepEqual(branchedLeafIds, ["leaf-current"]);
566
945
  const scanArgs = readCallArgsForTask("scan");
@@ -1038,7 +1417,7 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
1038
1417
  assert.equal(result.isError, undefined);
1039
1418
  assert.match(result.content[0]?.text ?? "", /Parallel run detached for intercom coordination/);
1040
1419
  assert.equal(detachEmitted, true);
1041
- assert.equal(result.details?.results?.some((entry) => entry.detached === true && entry.exitCode === 0), true);
1420
+ assert.equal(result.details?.results?.some((entry) => entry.detached === true && entry.exitCode === -2), true);
1042
1421
  });
1043
1422
 
1044
1423
  it("runs top-level parallel async requests in the background", { skip: !asyncAvailable ? "jiti not available" : undefined }, async () => {
@@ -1065,6 +1444,207 @@ describe("fork context execution wiring", { skip: !available ? "subagent executo
1065
1444
  assert.match(result.content[0]?.text ?? "", /Async parallel:/);
1066
1445
  });
1067
1446
 
1447
+ it("keeps inherited model-scope warnings non-fatal in async parallel conversion", { skip: !asyncAvailable ? "jiti not available" : undefined }, async () => {
1448
+ const executor = makeExecutorWithDiscoverAgents(() => ({
1449
+ agents: [{ name: "worker", description: "Worker", model: "openai/gpt-5-mini" }],
1450
+ projectAgentsDir: null,
1451
+ modelScope: { enforce: true, allow: ["anthropic/*"] },
1452
+ }));
1453
+ const ctx = {
1454
+ ...makeCtx(makeSessionManagerRecorder().manager),
1455
+ modelRegistry: {
1456
+ getAvailable: () => [{ provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true }],
1457
+ },
1458
+ };
1459
+ const warnings: string[] = [];
1460
+ const originalWarn = console.warn;
1461
+ console.warn = (message?: unknown) => warnings.push(String(message));
1462
+ try {
1463
+ const result = await executor.execute(
1464
+ "id",
1465
+ { tasks: [{ agent: "worker", task: "test" }], async: true },
1466
+ new AbortController().signal,
1467
+ undefined,
1468
+ ctx,
1469
+ );
1470
+ assert.equal(result.isError, undefined);
1471
+ assert.ok(result.details?.asyncId);
1472
+ assert.equal(warnings.length, 1);
1473
+ assert.match(warnings[0]!, /outside the configured subagent model scope/);
1474
+ } finally {
1475
+ console.warn = originalWarn;
1476
+ }
1477
+ });
1478
+
1479
+ it("keeps inherited model-scope warnings non-fatal after Clarify switches parallel work to background", { skip: !asyncAvailable ? "jiti not available" : undefined }, async () => {
1480
+ const executor = makeExecutorWithDiscoverAgents(() => ({
1481
+ agents: [{ name: "worker", description: "Worker", model: "openai/gpt-5-mini" }],
1482
+ projectAgentsDir: null,
1483
+ modelScope: { enforce: true, allow: ["anthropic/*"] },
1484
+ }));
1485
+ const ctx = {
1486
+ ...makeCtx(makeSessionManagerRecorder().manager),
1487
+ hasUI: true,
1488
+ ui: {
1489
+ custom: async () => ({
1490
+ confirmed: true,
1491
+ runInBackground: true,
1492
+ templates: ["test"],
1493
+ behaviorOverrides: [{}],
1494
+ }),
1495
+ },
1496
+ modelRegistry: {
1497
+ getAvailable: () => [{ provider: "openai", id: "gpt-5-mini", api: "openai-responses", reasoning: true }],
1498
+ },
1499
+ };
1500
+ const warnings: string[] = [];
1501
+ const originalWarn = console.warn;
1502
+ console.warn = (message?: unknown) => warnings.push(String(message));
1503
+ try {
1504
+ const result = await executor.execute(
1505
+ "id",
1506
+ { tasks: [{ agent: "worker", task: "test" }], clarify: true },
1507
+ new AbortController().signal,
1508
+ undefined,
1509
+ ctx,
1510
+ );
1511
+ assert.equal(result.isError, undefined);
1512
+ assert.ok(result.details?.asyncId);
1513
+ assert.equal(warnings.length, 2);
1514
+ assert.equal(warnings.every((warning) => warning.includes("outside the configured subagent model scope")), true);
1515
+ } finally {
1516
+ console.warn = originalWarn;
1517
+ }
1518
+ });
1519
+
1520
+ it("keeps raw goals at fork-wrapped async executor boundaries", { skip: !asyncAvailable ? "jiti not available" : undefined }, async () => {
1521
+ const rawSingleGoal = "Direct forked single raw goal";
1522
+ const rawParallelGoal = "Top-level forked first child raw goal";
1523
+ const rawParallelChainGoal = "Parallel chain first child raw goal";
1524
+ const paddedWorkflowGoal = " preserve this workflow padding ";
1525
+ const literalPreambleGoal = `${DEFAULT_FORK_PREAMBLE}\n\nTask:\nliteral fresh-context text`;
1526
+ const executor = makeExecutorWithDiscoverAgents(() => ({
1527
+ agents: [
1528
+ { name: "echo", description: "Echo", defaultContext: "fork" },
1529
+ { name: "second", description: "Second" },
1530
+ ],
1531
+ projectAgentsDir: null,
1532
+ }));
1533
+ const started: Array<{ id?: string; goal?: string }> = [];
1534
+ executor.eventsApi.on(SUBAGENT_ASYNC_STARTED_EVENT, (event: unknown) => started.push(event as { id?: string; goal?: string }));
1535
+ const forkManager = () => makeForkingSessionManagerRecorder({
1536
+ sessionFile: path.join(tempDir, "parent-fork-goal.jsonl"),
1537
+ leafId: "fork-goal-leaf",
1538
+ }).manager;
1539
+
1540
+ for (const testCase of [
1541
+ {
1542
+ name: "direct single",
1543
+ goal: rawSingleGoal,
1544
+ params: { agent: "echo", task: rawSingleGoal, async: true, clarify: false },
1545
+ },
1546
+ {
1547
+ name: "top-level parallel",
1548
+ goal: rawParallelGoal,
1549
+ params: { tasks: [{ agent: "echo", task: rawParallelGoal }, { agent: "second", task: "Second child" }], async: true, clarify: false },
1550
+ },
1551
+ {
1552
+ name: "parallel chain whitespace workflow fallback",
1553
+ goal: rawParallelChainGoal,
1554
+ params: { task: " ", chain: [{ parallel: [{ agent: "echo", task: rawParallelChainGoal }, { agent: "second", task: "Second chain child" }] }], async: true, clarify: false },
1555
+ },
1556
+ {
1557
+ name: "padded workflow goal",
1558
+ goal: paddedWorkflowGoal,
1559
+ params: { task: paddedWorkflowGoal, chain: [{ agent: "echo", task: "Forked child" }], async: true, clarify: false },
1560
+ },
1561
+ {
1562
+ name: "literal preamble in fresh child",
1563
+ goal: literalPreambleGoal.slice(0, 120),
1564
+ allowsLiteralPreamble: true,
1565
+ params: { chain: [{ agent: "second", task: literalPreambleGoal }], async: true, clarify: false },
1566
+ },
1567
+ ]) {
1568
+ const result = await executor.execute(
1569
+ `fork-goal-${testCase.name}`,
1570
+ testCase.params,
1571
+ new AbortController().signal,
1572
+ undefined,
1573
+ makeCtx(forkManager()),
1574
+ );
1575
+ assert.equal(result.isError, undefined, testCase.name);
1576
+ assert.ok(result.details?.asyncId, `${testCase.name}: expected an async id`);
1577
+ const event = started.find((entry) => entry.id === result.details?.asyncId);
1578
+ assert.ok(event, `${testCase.name}: missing async-started event for ${result.details?.asyncId}`);
1579
+ assert.equal(event.goal, testCase.goal, testCase.name);
1580
+ if (!("allowsLiteralPreamble" in testCase)) {
1581
+ assert.doesNotMatch(event.goal ?? "", /delegated subagent running from a fork/, testCase.name);
1582
+ }
1583
+ }
1584
+ });
1585
+
1586
+ it("uses edited raw goals when clarification switches each mode to background", { skip: !asyncAvailable ? "jiti not available" : undefined }, async () => {
1587
+ const executor = makeExecutorWithDiscoverAgents(() => ({
1588
+ agents: [
1589
+ { name: "echo", description: "Echo", defaultContext: "fork" },
1590
+ { name: "second", description: "Second" },
1591
+ ],
1592
+ projectAgentsDir: null,
1593
+ }));
1594
+ const started: Array<{ id?: string; goal?: string }> = [];
1595
+ executor.eventsApi.on(SUBAGENT_ASYNC_STARTED_EVENT, (event: unknown) => started.push(event as { id?: string; goal?: string }));
1596
+
1597
+ for (const testCase of [
1598
+ {
1599
+ name: "single",
1600
+ goal: "Edited single goal",
1601
+ params: { agent: "echo", task: "Original single", async: true, clarify: true },
1602
+ templates: ["Edited single goal"],
1603
+ },
1604
+ {
1605
+ name: "parallel",
1606
+ goal: "Edited parallel goal",
1607
+ params: { tasks: [{ agent: "echo", task: "Original parallel" }, { agent: "second", task: "Second child" }], async: true, clarify: true },
1608
+ templates: ["Edited parallel goal", "Second child"],
1609
+ },
1610
+ {
1611
+ name: "chain",
1612
+ goal: "Edited chain goal",
1613
+ params: { chain: [{ agent: "echo", task: "Original chain" }], async: true, clarify: true },
1614
+ templates: ["Edited chain goal"],
1615
+ },
1616
+ ]) {
1617
+ const forkManager = makeForkingSessionManagerRecorder({
1618
+ sessionFile: path.join(tempDir, `parent-clarify-${testCase.name}.jsonl`),
1619
+ leafId: `clarify-${testCase.name}-leaf`,
1620
+ }).manager;
1621
+ const ctx = {
1622
+ ...makeCtx(forkManager),
1623
+ hasUI: true,
1624
+ ui: {
1625
+ custom: async () => ({
1626
+ confirmed: true,
1627
+ templates: testCase.templates,
1628
+ behaviorOverrides: testCase.templates.map(() => undefined),
1629
+ runInBackground: true,
1630
+ }),
1631
+ },
1632
+ };
1633
+ const result = await executor.execute(
1634
+ `clarify-goal-${testCase.name}`,
1635
+ testCase.params,
1636
+ new AbortController().signal,
1637
+ undefined,
1638
+ ctx,
1639
+ );
1640
+ assert.equal(result.isError, undefined, testCase.name);
1641
+ assert.ok(result.details?.asyncId, `${testCase.name}: expected an async id`);
1642
+ const event = started.find((entry) => entry.id === result.details?.asyncId);
1643
+ assert.equal(event?.goal, testCase.goal, testCase.name);
1644
+ assert.doesNotMatch(event?.goal ?? "", /delegated subagent running from a fork/, testCase.name);
1645
+ }
1646
+ });
1647
+
1068
1648
  it("runs async chain requests in the background when clarify is omitted", { skip: !asyncAvailable ? "jiti not available" : undefined }, async () => {
1069
1649
  const executor = makeExecutor();
1070
1650