@selesai/code 0.5.28 → 0.6.1

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 (341) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +9 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/cli/credential-print.d.ts +23 -0
  10. package/dist/cli/credential-print.d.ts.map +1 -0
  11. package/dist/cli/credential-print.js +117 -0
  12. package/dist/cli/credential-print.js.map +1 -0
  13. package/dist/cli/startup-ui.d.ts.map +1 -1
  14. package/dist/cli/startup-ui.js +1 -1
  15. package/dist/cli/startup-ui.js.map +1 -1
  16. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  17. package/dist/core/agent-session-runtime.js +3 -0
  18. package/dist/core/agent-session-runtime.js.map +1 -1
  19. package/dist/core/agent-session.d.ts +12 -1
  20. package/dist/core/agent-session.d.ts.map +1 -1
  21. package/dist/core/agent-session.js +20 -14
  22. package/dist/core/agent-session.js.map +1 -1
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +11 -3
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/extensions/runner.d.ts +1 -0
  27. package/dist/core/extensions/runner.d.ts.map +1 -1
  28. package/dist/core/extensions/runner.js +11 -0
  29. package/dist/core/extensions/runner.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +14 -1
  31. package/dist/core/extensions/types.d.ts.map +1 -1
  32. package/dist/core/extensions/types.js.map +1 -1
  33. package/dist/core/footer-data-provider.d.ts +10 -0
  34. package/dist/core/footer-data-provider.d.ts.map +1 -1
  35. package/dist/core/footer-data-provider.js +1 -1
  36. package/dist/core/footer-data-provider.js.map +1 -1
  37. package/dist/core/llama/provider.d.ts.map +1 -1
  38. package/dist/core/llama/provider.js +8 -3
  39. package/dist/core/llama/provider.js.map +1 -1
  40. package/dist/core/model-config.d.ts +30 -0
  41. package/dist/core/model-config.d.ts.map +1 -1
  42. package/dist/core/model-config.js +6 -0
  43. package/dist/core/model-config.js.map +1 -1
  44. package/dist/core/model-registry.d.ts.map +1 -1
  45. package/dist/core/model-registry.js +2 -2
  46. package/dist/core/model-registry.js.map +1 -1
  47. package/dist/core/model-resolver.d.ts +1 -0
  48. package/dist/core/model-resolver.d.ts.map +1 -1
  49. package/dist/core/model-resolver.js +20 -3
  50. package/dist/core/model-resolver.js.map +1 -1
  51. package/dist/core/model-runtime.d.ts +2 -0
  52. package/dist/core/model-runtime.d.ts.map +1 -1
  53. package/dist/core/model-runtime.js +5 -4
  54. package/dist/core/model-runtime.js.map +1 -1
  55. package/dist/core/package-manager.d.ts.map +1 -1
  56. package/dist/core/package-manager.js +13 -6
  57. package/dist/core/package-manager.js.map +1 -1
  58. package/dist/core/remote-catalog-provider.d.ts +1 -1
  59. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  60. package/dist/core/remote-catalog-provider.js +24 -11
  61. package/dist/core/remote-catalog-provider.js.map +1 -1
  62. package/dist/core/resource-loader.d.ts +15 -0
  63. package/dist/core/resource-loader.d.ts.map +1 -1
  64. package/dist/core/resource-loader.js +66 -9
  65. package/dist/core/resource-loader.js.map +1 -1
  66. package/dist/core/settings-manager.d.ts +1 -1
  67. package/dist/core/settings-manager.d.ts.map +1 -1
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/system-prompt.d.ts.map +1 -1
  70. package/dist/core/system-prompt.js +19 -1
  71. package/dist/core/system-prompt.js.map +1 -1
  72. package/dist/core/system-prompt.test.d.ts +2 -0
  73. package/dist/core/system-prompt.test.d.ts.map +1 -0
  74. package/dist/core/system-prompt.test.js +89 -0
  75. package/dist/core/system-prompt.test.js.map +1 -0
  76. package/dist/core/tools/bash.d.ts +2 -0
  77. package/dist/core/tools/bash.d.ts.map +1 -1
  78. package/dist/core/tools/bash.js +34 -5
  79. package/dist/core/tools/bash.js.map +1 -1
  80. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  81. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  82. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  83. package/dist/defaults/models.json +13 -45
  84. package/dist/defaults/settings.json +1 -2
  85. package/dist/extensions/copy-turn.test.ts +131 -0
  86. package/dist/extensions/copy-turn.ts +6 -1
  87. package/dist/extensions/package.json +0 -1
  88. package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
  89. package/dist/extensions/pi-intercom/README.md +102 -39
  90. package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
  91. package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
  92. package/dist/extensions/pi-intercom/broker/client.ts +315 -12
  93. package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
  94. package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
  95. package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
  96. package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
  97. package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
  98. package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
  99. package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
  100. package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
  101. package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
  102. package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
  103. package/dist/extensions/pi-intercom/config.test.ts +93 -0
  104. package/dist/extensions/pi-intercom/config.ts +55 -6
  105. package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
  106. package/dist/extensions/pi-intercom/cwd.ts +31 -0
  107. package/dist/extensions/pi-intercom/extension-api.ts +44 -0
  108. package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
  109. package/dist/extensions/pi-intercom/format-context.ts +32 -0
  110. package/dist/extensions/pi-intercom/index.ts +742 -145
  111. package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
  112. package/dist/extensions/pi-intercom/package.json +15 -5
  113. package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
  114. package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
  115. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
  116. package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
  117. package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
  118. package/dist/extensions/pi-intercom/types.ts +94 -4
  119. package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
  120. package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
  121. package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
  122. package/dist/extensions/pi-subagents/CHANGELOG.md +88 -0
  123. package/dist/extensions/pi-subagents/LICENSE +21 -0
  124. package/dist/extensions/pi-subagents/README.md +158 -69
  125. package/dist/extensions/pi-subagents/agents/architect.md +5 -4
  126. package/dist/extensions/pi-subagents/agents/builder.md +6 -4
  127. package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
  128. package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
  129. package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
  130. package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
  131. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  132. package/dist/extensions/pi-subagents/package.json +3 -3
  133. package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -988
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +257 -0
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +431 -0
  137. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
  138. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +281 -0
  139. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +125 -37
  140. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
  141. package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
  142. package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
  143. package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +125 -0
  144. package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
  145. package/dist/extensions/pi-subagents/src/api/preflight.ts +17 -13
  146. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
  147. package/dist/extensions/pi-subagents/src/extension/index.ts +22 -8
  148. package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
  149. package/dist/extensions/pi-subagents/src/extension/schemas.ts +33 -8
  150. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +32 -14
  151. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
  152. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
  153. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +86 -21
  154. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
  155. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
  156. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  157. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
  158. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
  159. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +56 -5
  160. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +92 -11
  161. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +388 -29
  163. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
  164. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
  165. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +214 -60
  166. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +546 -252
  167. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
  168. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +553 -154
  169. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +19 -13
  170. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
  171. package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
  172. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  173. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
  174. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
  175. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
  176. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
  177. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
  178. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
  179. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
  180. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
  181. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +63 -9
  182. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
  183. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
  184. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +22 -1
  185. package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
  186. package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
  187. package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
  188. package/dist/extensions/pi-subagents/src/shared/types.ts +196 -12
  189. package/dist/extensions/pi-subagents/src/shared/utils.ts +102 -5
  190. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +10 -1
  191. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
  192. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
  193. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
  194. package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
  195. package/dist/extensions/pi-subagents/src/tui/render.ts +55 -34
  196. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
  197. package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
  198. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
  199. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
  200. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
  201. package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
  202. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +113 -8
  203. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +419 -47
  204. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
  205. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
  206. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
  207. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +62 -22
  208. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
  209. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +8 -6
  210. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +231 -19
  211. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +48 -7
  212. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
  213. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +199 -17
  214. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +898 -16
  215. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
  216. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
  217. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
  218. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +18 -2
  219. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
  220. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
  221. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
  222. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +198 -6
  223. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
  224. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
  225. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
  226. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
  227. package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +63 -0
  228. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +136 -0
  229. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
  230. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
  231. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
  232. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
  233. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
  234. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
  235. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +28 -2
  236. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
  237. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
  238. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
  239. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
  240. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +17 -12
  241. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
  242. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
  243. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
  244. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +39 -2
  245. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
  246. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
  247. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
  248. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +48 -0
  249. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
  250. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
  251. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
  252. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
  253. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
  254. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +46 -2
  255. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
  256. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  257. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
  258. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
  259. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
  260. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
  261. package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +213 -0
  262. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -1
  263. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +61 -10
  264. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
  265. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
  266. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
  267. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
  268. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
  269. package/dist/main.d.ts.map +1 -1
  270. package/dist/main.js +43 -0
  271. package/dist/main.js.map +1 -1
  272. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  273. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  274. package/dist/modes/interactive/components/custom-message.js +10 -2
  275. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  276. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  277. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  278. package/dist/modes/interactive/components/extension-editor.js +16 -46
  279. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  280. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  281. package/dist/modes/interactive/components/model-selector.js +4 -1
  282. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  283. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  284. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  285. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  286. package/dist/modes/interactive/external-editor.d.ts +12 -0
  287. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  288. package/dist/modes/interactive/external-editor.js +37 -0
  289. package/dist/modes/interactive/external-editor.js.map +1 -0
  290. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  291. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  292. package/dist/modes/interactive/interactive-mode.js +72 -64
  293. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  294. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  295. package/dist/modes/rpc/rpc-mode.js +14 -0
  296. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  297. package/dist/skills/ponytail/SKILL.md +1 -3
  298. package/dist/utils/clipboard.d.ts.map +1 -1
  299. package/dist/utils/clipboard.js +19 -8
  300. package/dist/utils/clipboard.js.map +1 -1
  301. package/dist/utils/version-check.d.ts.map +1 -1
  302. package/dist/utils/version-check.js +1 -1
  303. package/dist/utils/version-check.js.map +1 -1
  304. package/docs/compaction.md +1 -1
  305. package/docs/custom-provider.md +14 -5
  306. package/docs/environment-variables.md +86 -0
  307. package/docs/extensions.md +15 -5
  308. package/docs/index.md +1 -0
  309. package/docs/models.md +9 -2
  310. package/docs/plans/subagent-delegation/phase-0-correctness.md +265 -0
  311. package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
  312. package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
  313. package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
  314. package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
  315. package/docs/providers.md +21 -2
  316. package/docs/rpc.md +23 -6
  317. package/docs/session-format.md +2 -0
  318. package/docs/settings.md +1 -1
  319. package/docs/usage.md +0 -13
  320. package/examples/extensions/custom-compaction.ts +5 -2
  321. package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
  322. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  323. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  324. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  325. package/examples/extensions/gondolin/package-lock.json +2 -2
  326. package/examples/extensions/gondolin/package.json +1 -1
  327. package/examples/extensions/handoff.ts +11 -3
  328. package/examples/extensions/message-renderer.ts +3 -3
  329. package/examples/extensions/sandbox/package-lock.json +2 -2
  330. package/examples/extensions/sandbox/package.json +1 -1
  331. package/examples/extensions/summarize.ts +5 -2
  332. package/examples/extensions/with-deps/package-lock.json +2 -2
  333. package/examples/extensions/with-deps/package.json +1 -1
  334. package/examples/sdk/12-full-control.ts +3 -1
  335. package/package.json +16 -7
  336. package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
  337. package/dist/extensions/caveman/index.js +0 -118
  338. package/dist/extensions/caveman/package.json +0 -8
  339. package/dist/extensions/caveman/test/extension.test.js +0 -203
  340. package/dist/extensions/caveman/test/helpers.test.js +0 -58
  341. package/dist/skills/caveman/SKILL.md +0 -50
@@ -144,6 +144,14 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
144
144
  }
145
145
  }
146
146
 
147
+ async function waitForCallCount(minimum: number, timeoutMs = 10_000): Promise<void> {
148
+ const deadline = Date.now() + timeoutMs;
149
+ while (mockPi.callCount() < minimum) {
150
+ if (Date.now() > deadline) assert.fail(`Timed out waiting for ${minimum} mock pi calls; saw ${mockPi.callCount()}`);
151
+ await new Promise((resolve) => setTimeout(resolve, 50));
152
+ }
153
+ }
154
+
147
155
  async function waitForMissingPath(filePath: string, timeoutMs = 10_000): Promise<void> {
148
156
  const deadline = Date.now() + timeoutMs;
149
157
  while (fs.existsSync(filePath)) {
@@ -152,7 +160,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
152
160
  }
153
161
  }
154
162
 
155
- function makeExecutor(options: { bridgeMode?: "always" | "off"; agents?: ReturnType<typeof makeAgent>[]; acknowledgeResults?: boolean; kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean } = {}) {
163
+ function makeExecutor(options: { bridgeMode?: "always" | "off"; resultDelivery?: boolean; agents?: ReturnType<typeof makeAgent>[]; acknowledgeResults?: boolean; kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean } = {}) {
156
164
  const events = createRecordingEventBus({ acknowledgeResults: options.acknowledgeResults ?? true });
157
165
  const state = {
158
166
  baseCwd: tempDir,
@@ -180,7 +188,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
180
188
  },
181
189
  state,
182
190
  config: {
183
- intercomBridge: { mode: options.bridgeMode ?? "always" },
191
+ intercomBridge: {
192
+ mode: options.bridgeMode ?? "always",
193
+ ...(options.resultDelivery === undefined ? {} : { resultDelivery: options.resultDelivery }),
194
+ },
184
195
  },
185
196
  asyncByDefault: false,
186
197
  tempArtifactsDir: tempDir,
@@ -216,10 +227,13 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
216
227
  assert.match(result.content[0]?.text ?? "", /Delivered single subagent result via intercom\./);
217
228
  assert.doesNotMatch(result.content[0]?.text ?? "", /Full child output from worker/);
218
229
  assert.equal(result.details?.results?.[0]?.finalOutput, undefined);
219
- assert.match(String(payload.message ?? ""), /Full child output from worker/);
230
+ // Reference-first intercom delivery: the child summary is the saved-output
231
+ // reference (error/status first for failed children), never raw child prose.
232
+ assert.match(String(payload.message ?? ""), /Output saved to: /);
233
+ assert.doesNotMatch(String(payload.message ?? ""), /Full child output from worker/);
220
234
  });
221
235
 
222
- it("falls back to legacy foreground output when the bridge is inactive", async () => {
236
+ it("returns the native saved-output receipt when the bridge is inactive", async () => {
223
237
  mockPi.onCall({ output: "Legacy foreground output" });
224
238
  const { executor, events } = makeExecutor({ bridgeMode: "off" });
225
239
 
@@ -232,10 +246,31 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
232
246
  );
233
247
 
234
248
  assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), false);
235
- assert.match(result.content[0]?.text ?? "", /Legacy foreground output/);
249
+ assert.match(result.content[0]?.text ?? "", /Output saved to: /);
250
+ assert.doesNotMatch(result.content[0]?.text ?? "", /Legacy foreground output/);
251
+ const runId = result.details?.runId;
252
+ assert.ok(runId);
253
+ assert.equal(fs.readFileSync(path.join(tempDir, ".pi-subagents", "artifacts", "outputs", runId, "result.md"), "utf-8"), "Legacy foreground output");
236
254
  });
237
255
 
238
- it("falls back to legacy foreground output when grouped delivery is not acknowledged", async () => {
256
+ it("keeps the native saved-output receipt without attempting external grouped delivery when disabled", async () => {
257
+ mockPi.onCall({ output: "Native foreground output" });
258
+ const { executor, events } = makeExecutor({ resultDelivery: false });
259
+
260
+ const result = await executor.execute(
261
+ "single-native-delivery",
262
+ { agent: "worker", task: "Summarize feature" },
263
+ new AbortController().signal,
264
+ undefined,
265
+ makeMinimalCtx(tempDir),
266
+ );
267
+
268
+ assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), false);
269
+ assert.match(result.content[0]?.text ?? "", /Output saved to: /);
270
+ assert.doesNotMatch(result.content[0]?.text ?? "", /Native foreground output/);
271
+ });
272
+
273
+ it("falls back to the compact native saved-output receipt when grouped delivery is not acknowledged", async () => {
239
274
  mockPi.onCall({ output: "Unacknowledged foreground output" });
240
275
  const { executor, events } = makeExecutor({ acknowledgeResults: false });
241
276
 
@@ -248,7 +283,8 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
248
283
  );
249
284
 
250
285
  assert.equal(events.emitted.some((entry) => entry.channel === "subagent:result-intercom"), true);
251
- assert.match(result.content[0]?.text ?? "", /Unacknowledged foreground output/);
286
+ assert.match(result.content[0]?.text ?? "", /Output saved to: /);
287
+ assert.doesNotMatch(result.content[0]?.text ?? "", /Unacknowledged foreground output/);
252
288
  });
253
289
 
254
290
  it("top-level parallel runs emit one grouped event containing all children", async () => {
@@ -271,12 +307,145 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
271
307
  assert.equal((payload.children ?? []).every((child) => /^subagent-[ab]-[a-f0-9]+-[12]$/.test(child.intercomTarget ?? "")), true);
272
308
  assert.match(String(payload.message ?? ""), /Intercom targets below identify child sessions used while they were running/);
273
309
  assert.match(String(payload.message ?? ""), /Run intercom target: subagent-a-[a-f0-9]+-1/);
274
- assert.match(String(payload.message ?? ""), /1\. a — completed/);
275
- assert.match(String(payload.message ?? ""), /2\. b — completed/);
310
+ assert.match(String(payload.message ?? ""), /1\. a — process completed · output present/);
311
+ assert.match(String(payload.message ?? ""), /2\. b — process completed · output present/);
276
312
  assert.match(result.content[0]?.text ?? "", /Delivered parallel subagent results via intercom\./);
277
313
  assert.equal(result.details?.results?.every((entry) => entry.finalOutput === undefined), true);
278
314
  });
279
315
 
316
+ it("keeps a queued top-level parallel child controlled after an early outer rejection", async () => {
317
+ mockPi.onCall({ matchArgIncludes: "task-a", output: "early child", delay: 100 });
318
+ mockPi.onCall({ matchArgIncludes: "task-b", output: "middle child", delay: 400 });
319
+ mockPi.onCall({ matchArgIncludes: "task-c", output: "too late", delay: 10_000 });
320
+ const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
321
+ let rejected = false;
322
+ const result = await executor.execute(
323
+ "parallel-rejection-cleanup",
324
+ { concurrency: 2, tasks: [{ agent: "a", task: "task-a" }, { agent: "b", task: "task-b" }, { agent: "c", task: "task-c" }] },
325
+ new AbortController().signal,
326
+ (update: { details?: { progress?: Array<{ agent?: string; status?: string }> } }) => {
327
+ if (!rejected && update.details?.progress?.some((entry) => entry.agent === "a" && entry.status === "completed")) {
328
+ rejected = true;
329
+ throw new Error("reject early parallel completion");
330
+ }
331
+ },
332
+ makeMinimalCtx(tempDir),
333
+ );
334
+ assert.equal(rejected, true);
335
+ assert.equal(result.isError, true);
336
+ assert.equal(state.foregroundControls.size, 1, "remaining worker still owns queued scheduling");
337
+ await waitForCallCount(3);
338
+ const liveControl = [...state.foregroundControls.values()][0];
339
+ assert.equal(liveControl?.currentAgent, "c", "later queued child must remain discoverable after launch");
340
+ assert.ok(liveControl?.interrupt, "later queued child must remain interruptible");
341
+ assert.equal(liveControl.interrupt(), true);
342
+ for (let attempt = 0; attempt < 100 && state.foregroundControls.size > 0; attempt++) await new Promise((resolve) => setTimeout(resolve, 20));
343
+ assert.equal(state.foregroundControls.size, 0, "control must disappear after workers and children settle");
344
+ });
345
+
346
+ it("keeps a queued chain-parallel child controlled after an early outer rejection", async () => {
347
+ mockPi.onCall({ matchArgIncludes: "chain-task-a", output: "early chain child", delay: 100 });
348
+ mockPi.onCall({ matchArgIncludes: "chain-task-b", output: "middle chain child", delay: 400 });
349
+ mockPi.onCall({ matchArgIncludes: "chain-task-c", output: "too late", delay: 10_000 });
350
+ const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
351
+ let rejected = false;
352
+ const result = await executor.execute(
353
+ "chain-parallel-rejection-cleanup",
354
+ { chain: [{ concurrency: 2, parallel: [{ agent: "a", task: "chain-task-a" }, { agent: "b", task: "chain-task-b" }, { agent: "c", task: "chain-task-c" }] }] },
355
+ new AbortController().signal,
356
+ (update: { details?: { progress?: Array<{ agent?: string; status?: string }> } }) => {
357
+ if (!rejected && update.details?.progress?.some((entry) => entry.agent === "a" && entry.status === "completed")) {
358
+ rejected = true;
359
+ throw new Error("reject early chain completion");
360
+ }
361
+ },
362
+ makeMinimalCtx(tempDir),
363
+ );
364
+ assert.equal(rejected, true);
365
+ assert.equal(result.isError, true);
366
+ assert.equal(state.foregroundControls.size, 1, "remaining chain worker still owns queued scheduling");
367
+ await waitForCallCount(3);
368
+ const liveControl = [...state.foregroundControls.values()][0];
369
+ assert.equal(liveControl?.currentAgent, "c", "later queued chain child must remain discoverable after launch");
370
+ assert.ok(liveControl?.interrupt, "later queued chain child must remain interruptible");
371
+ assert.equal(liveControl.interrupt(), true);
372
+ for (let attempt = 0; attempt < 100 && state.foregroundControls.size > 0; attempt++) await new Promise((resolve) => setTimeout(resolve, 20));
373
+ assert.equal(state.foregroundControls.size, 0, "chain control must disappear after workers and children settle");
374
+ });
375
+
376
+ it("cleans a rejected sequential chain child and releases foreground ownership", async () => {
377
+ mockPi.onCall({ output: "sequential child output" });
378
+ const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
379
+ let rejected = false;
380
+ let ownedControl: { schedulingOwners?: number; activeChildren?: Map<number, unknown> } | undefined;
381
+ const result = await executor.execute(
382
+ "chain-sequential-rejection-cleanup",
383
+ { chain: [{ agent: "worker", task: "sequential task" }] },
384
+ new AbortController().signal,
385
+ (update: { details?: { progress?: Array<{ status?: string }> } }) => {
386
+ if (rejected || !update.details?.progress?.some((entry) => entry.status === "completed")) return;
387
+ rejected = true;
388
+ ownedControl = [...state.foregroundControls.values()][0];
389
+ throw new Error("reject sequential chain completion");
390
+ },
391
+ makeMinimalCtx(tempDir),
392
+ );
393
+ assert.equal(rejected, true);
394
+ assert.equal(result.isError, true);
395
+ assert.ok(ownedControl);
396
+ assert.equal(ownedControl.schedulingOwners, 0);
397
+ assert.equal(ownedControl.activeChildren?.size, 0);
398
+ assert.equal(state.foregroundControls.size, 0);
399
+ });
400
+
401
+ it("cleans a sequential chain child after an early tool-budget validation return", async () => {
402
+ const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
403
+ const execution = executor.execute(
404
+ "chain-sequential-budget-cleanup",
405
+ { chain: [{ agent: "worker", task: "invalid budget task", toolBudget: { hard: 0 } }] },
406
+ new AbortController().signal,
407
+ undefined,
408
+ makeMinimalCtx(tempDir),
409
+ );
410
+ const ownedControl = [...state.foregroundControls.values()][0];
411
+ assert.ok(ownedControl, "outer scheduling owner should remain visible until execution settles");
412
+ const result = await execution;
413
+ assert.equal(result.isError, true);
414
+ assert.match(result.content[0]?.text ?? "", /toolBudget\.hard must be an integer >= 1/);
415
+ assert.equal(mockPi.callCount(), 0);
416
+ assert.equal(ownedControl.schedulingOwners, 0);
417
+ assert.equal(ownedControl.activeChildren?.size, 0);
418
+ assert.equal(state.foregroundControls.size, 0);
419
+ });
420
+
421
+ it("cleans an attached single rejection and its temporary structured runtime", async () => {
422
+ mockPi.onCall({
423
+ stdoutRaw: [
424
+ { type: "tool_execution_start", toolName: "structured_output", args: { value: { ok: true } } },
425
+ { type: "tool_result_end", message: { role: "toolResult", toolName: "structured_output", content: [{ type: "text", text: "Structured output captured." }] } },
426
+ { type: "tool_execution_end", toolName: "structured_output" },
427
+ ].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
428
+ structuredOutputCapture: { ok: true },
429
+ });
430
+ const { executor, state } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("worker")] });
431
+ let structuredDir: string | undefined;
432
+ const result = await executor.execute(
433
+ "single-rejection-cleanup",
434
+ { agent: "worker", task: "structured task", artifacts: false, outputSchema: { type: "object", properties: { ok: { type: "boolean" } }, required: ["ok"] } },
435
+ new AbortController().signal,
436
+ (update: { details?: { results?: Array<{ structuredOutputPath?: string }>; progress?: Array<{ status?: string }> } }) => {
437
+ const outputPath = update.details?.results?.[0]?.structuredOutputPath;
438
+ if (outputPath) structuredDir = path.dirname(outputPath);
439
+ if (update.details?.progress?.[0]?.status === "completed") throw new Error("reject attached single completion");
440
+ },
441
+ makeMinimalCtx(tempDir),
442
+ );
443
+ assert.equal(result.isError, true);
444
+ assert.equal(state.foregroundControls.size, 0);
445
+ assert.ok(structuredDir);
446
+ assert.equal(fs.existsSync(structuredDir), false, "temporary structured runtime must be removed on rejection");
447
+ });
448
+
280
449
  it("chain runs emit one grouped event containing all executed children", async () => {
281
450
  mockPi.onCall({ output: "Chain child output" });
282
451
  const { executor, events } = makeExecutor({ agents: [makeAgent("a"), makeAgent("b"), makeAgent("c")] });
@@ -300,9 +469,9 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
300
469
  assert.equal(payload.mode, "chain");
301
470
  assert.deepEqual((payload.children ?? []).map((child) => child.agent).sort(), ["a", "b", "c"]);
302
471
  assert.equal((payload.children ?? []).every((child) => /^subagent-[abc]-[a-f0-9]+-[123]$/.test(child.intercomTarget ?? "")), true);
303
- assert.match(String(payload.message ?? ""), /1\. a — completed/);
304
- assert.match(String(payload.message ?? ""), /2\. b — completed/);
305
- assert.match(String(payload.message ?? ""), /3\. c — completed/);
472
+ assert.match(String(payload.message ?? ""), /1\. a — process completed · output present/);
473
+ assert.match(String(payload.message ?? ""), /2\. b — process completed · output present/);
474
+ assert.match(String(payload.message ?? ""), /3\. c — process completed · output present/);
306
475
  assert.match(result.content[0]?.text ?? "", /Delivered chain subagent results via intercom\./);
307
476
  assert.equal(result.details?.results?.every((entry) => entry.finalOutput === undefined), true);
308
477
  });
@@ -631,7 +800,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
631
800
  lastUpdate: 200,
632
801
  cwd: tempDir,
633
802
  sessionFile,
634
- steps: [{ agent: "worker", status: "complete" }],
803
+ steps: [{ agent: "worker", status: "complete", launchContractDigest: "source-launch-contract-digest" }],
635
804
  }, null, 2), "utf-8");
636
805
  const { executor, events } = makeExecutor();
637
806
 
@@ -651,6 +820,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
651
820
  assert.doesNotMatch(result.content[0]?.text ?? "", /Follow:/);
652
821
  const revivedId = result.details?.asyncId;
653
822
  assert.ok(revivedId, "expected revived async id");
823
+ assert.equal(result.details?.sourceLaunchContractDigest, "source-launch-contract-digest");
654
824
  const startedEvent = events.emitted.find((entry) => entry.channel === SUBAGENT_ASYNC_STARTED_EVENT && (entry.payload as { id?: string }).id === revivedId)?.payload as { goal?: string } | undefined;
655
825
  assert.equal(startedEvent?.goal, "What changed?");
656
826
  const resultPath = path.join(RESULTS_DIR, `${revivedId}.json`);
@@ -785,7 +955,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
785
955
  mockPi.onCall({ output: "first child done" });
786
956
  mockPi.onCall({ output: "second child done" });
787
957
  mockPi.onCall({ output: "revived foreground answer" });
788
- const { executor } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b")] });
958
+ const { executor } = makeExecutor({ bridgeMode: "off", agents: [makeAgent("a"), makeAgent("b", { model: "anthropic/claude-sonnet-4", thinking: "high" })] });
789
959
 
790
960
  const original = await executor.execute(
791
961
  "foreground-resume-original",
@@ -797,6 +967,17 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
797
967
  const runId = original.details?.runId;
798
968
  assert.ok(runId, "expected foreground run id");
799
969
 
970
+ const overridden = await executor.execute(
971
+ "foreground-resume-model-override",
972
+ { action: "resume", id: runId, index: 1, message: "Follow up with b", model: "openai/gpt-5" },
973
+ new AbortController().signal,
974
+ undefined,
975
+ makeMinimalCtx(tempDir),
976
+ );
977
+ assert.equal(overridden.isError, true);
978
+ assert.match(overridden.content[0]?.text ?? "", /reuses the persisted child model/);
979
+ assert.equal(mockPi.callCount(), 2, "rejected model override must not spawn a revival");
980
+
800
981
  const revived = await executor.execute(
801
982
  "foreground-resume",
802
983
  { action: "resume", id: runId, index: 1, message: "Follow up with b" },
@@ -808,10 +989,13 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
808
989
  assert.equal(revived.isError, undefined);
809
990
  assert.match(revived.content[0]?.text ?? "", /Revived foreground subagent from/);
810
991
  assert.match(revived.content[0]?.text ?? "", /Agent: b/);
992
+ assert.equal(revived.details?.sourceLaunchContractDigest, original.details?.results?.[1]?.launchContractDigest);
993
+ assert.ok(revived.details?.sourceLaunchContractDigest, "expected foreground source launch contract digest");
811
994
  const reviveArgs = await readMockCallArgs(2);
812
995
  const selectedSession = original.details?.results?.[1]?.sessionFile;
813
996
  assert.ok(selectedSession, "expected selected child session file");
814
997
  assert.equal(reviveArgs[reviveArgs.indexOf("--session") + 1], selectedSession);
998
+ assert.equal(reviveArgs[reviveArgs.indexOf("--model") + 1], original.details?.results?.[1]?.model);
815
999
  const revivedId = revived.details?.asyncId;
816
1000
  assert.ok(revivedId, "expected revived async id");
817
1001
  const resultPath = path.join(RESULTS_DIR, `${revivedId}.json`);
@@ -861,7 +1045,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
861
1045
  );
862
1046
  assert.equal(blocked.isError, true);
863
1047
  assert.match(blocked.content[0]?.text ?? "", new RegExp(`already owned by run '${firstRevivedId}'`));
864
- assert.equal(fs.readdirSync(mockPi.dir).filter((name) => name.startsWith("call-")).length, 2, "contending foreground revival must not spawn Pi");
1048
+ // The original and the first revival each spawn Pi asynchronously, so wait for both markers
1049
+ // before asserting the contending revival added no third spawn.
1050
+ await waitForCallCount(2);
1051
+ assert.equal(mockPi.callCount(), 2, "contending foreground revival must not spawn Pi");
865
1052
 
866
1053
  fs.writeFileSync(releasePath, "release", "utf-8");
867
1054
  await waitForFile(path.join(RESULTS_DIR, `${firstRevivedId}.json`));
@@ -1072,7 +1259,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
1072
1259
  const payload = completion.payload as { success?: boolean; summary?: string };
1073
1260
  assert.equal(payload.success, false);
1074
1261
  assert.match(payload.summary ?? "", /Acceptance rejected/);
1075
- assert.match(payload.summary ?? "", /final answer with rejected acceptance evidence/);
1262
+ // Reference-first foreground completion: the recovery summary carries the
1263
+ // saved-output reference (with error/status), never raw child prose.
1264
+ assert.match(payload.summary ?? "", /Output saved to: /);
1265
+ assert.doesNotMatch(payload.summary ?? "", /final answer with rejected acceptance evidence/);
1076
1266
 
1077
1267
  const status = await executor.execute(
1078
1268
  "foreground-detached-failed-status",
@@ -1273,9 +1463,10 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
1273
1463
  makeMinimalCtx(tempDir),
1274
1464
  );
1275
1465
  const fleetText = fleet.content[0]?.text ?? "";
1276
- assert.match(fleetText, /Detached foreground runs:/);
1466
+ assert.match(fleetText, /Foreground runs:/);
1277
1467
  assert.ok(fleetText.includes(runId));
1278
- assert.match(fleetText, /recovery: reply to the supervisor request first/);
1468
+ assert.match(fleetText, /running/);
1469
+ assert.match(fleetText, /contact_supervisor/);
1279
1470
 
1280
1471
  const resumed = await executor.execute(
1281
1472
  "foreground-detached-resume",
@@ -1534,7 +1725,28 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
1534
1725
  const payload = intercomEvents[0]!.payload as { status?: string; summary?: string; message?: string };
1535
1726
  assert.equal(payload.status, "failed");
1536
1727
  assert.match(String(payload.summary ?? ""), /1 completed, 1 failed/);
1537
- assert.match(String(payload.message ?? ""), /Status: failed/);
1728
+ assert.match(String(payload.message ?? ""), /Process status: failed/);
1729
+ assert.match(String(payload.message ?? ""), /Outputs: 2 present \(semantic adequacy unassessed\)/);
1730
+ assert.match(String(payload.message ?? ""), /Inspect that output before retrying/);
1538
1731
  assert.match(result.content[0]?.text ?? "", /Children: 1 completed, 1 failed/);
1539
1732
  });
1733
+
1734
+ it("does not treat a synthetic foreground startup error as child output", async () => {
1735
+ mockPi.onCall({ output: "", stderr: "mock startup failure", exitCode: 1 });
1736
+ const { executor, events } = makeExecutor({ agents: [makeAgent("worker")] });
1737
+
1738
+ await executor.execute(
1739
+ "foreground-synthetic-error",
1740
+ { agent: "worker", task: "synthetic-error-task" },
1741
+ new AbortController().signal,
1742
+ undefined,
1743
+ makeMinimalCtx(tempDir),
1744
+ );
1745
+
1746
+ const intercomEvents = events.emitted.filter((entry) => entry.channel === "subagent:result-intercom");
1747
+ assert.equal(intercomEvents.length, 1);
1748
+ const message = String((intercomEvents[0]!.payload as { message?: string }).message ?? "");
1749
+ assert.match(message, /process failed · output absent/);
1750
+ assert.doesNotMatch(message, /Inspect that output before retrying/);
1751
+ });
1540
1752
  });
@@ -227,6 +227,40 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
227
227
  assert.equal(state.foregroundControls.size, 0);
228
228
  });
229
229
 
230
+ it("exposes stable child indexes for counted top-level parallel results", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
231
+ mockPi.onCall({
232
+ steps: [
233
+ { jsonl: [{ type: "tool_execution_start", toolName: "read", args: { path: "a.md" } }], delay: 10 },
234
+ { jsonl: [{ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "Counted A done" }], model: "mock/test-model", stopReason: "stop", usage: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, cost: { total: 0.001 } } } }] },
235
+ ],
236
+ });
237
+ mockPi.onCall({
238
+ steps: [
239
+ { jsonl: [{ type: "tool_execution_start", toolName: "read", args: { path: "b.md" } }], delay: 10 },
240
+ { jsonl: [{ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "Counted B done" }], model: "mock/test-model", stopReason: "stop", usage: { input: 100, output: 50, cacheRead: 0, cacheWrite: 0, cost: { total: 0.001 } } } }] },
241
+ ],
242
+ });
243
+ const executor = makeExecutor();
244
+ const updateIndexes: number[] = [];
245
+
246
+ const result = await executor.execute(
247
+ "counted-parallel-indexes",
248
+ { tasks: [{ agent: "echo", task: "Counted inspection", count: 2 }] },
249
+ new AbortController().signal,
250
+ (update) => {
251
+ for (const row of update.details?.results ?? []) updateIndexes.push(row.index);
252
+ },
253
+ makeMinimalCtx(tempDir),
254
+ );
255
+
256
+ assert.equal(result.isError, undefined);
257
+ assert.deepEqual(result.details?.results.map((row: { index: number }) => row.index), [0, 1]);
258
+ assert.deepEqual([...new Set(result.details?.results.map((row: { index: number }) => row.index))], [0, 1]);
259
+ assert.ok(updateIndexes.includes(0));
260
+ assert.ok(updateIndexes.includes(1));
261
+ assert.ok(updateIndexes.every((index) => Number.isInteger(index)));
262
+ });
263
+
230
264
  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
265
  git(["init"]);
232
266
  git(["config", "user.email", "test@example.com"]);
@@ -253,7 +287,9 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
253
287
  groups: Array<{ children: Array<{ agent: string; summary: string; patch: { path: string } }>; cleanup: { state: string; tasks: Array<{ path: string; worktreeRemoved: boolean; branchRemoved: boolean }> } }>;
254
288
  };
255
289
  assert.equal(handoff.groups[0]!.children[0]!.agent, "echo");
256
- assert.equal(handoff.groups[0]!.children[0]!.summary, "Worktree task complete");
290
+ // Reference-first handoff: the summary carries the saved-output reference.
291
+ assert.match(handoff.groups[0]!.children[0]!.summary, /Output saved to: /);
292
+ assert.doesNotMatch(handoff.groups[0]!.children[0]!.summary, /Worktree task complete/);
257
293
  assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true);
258
294
  assert.ok(result.details?.runId);
259
295
  assert.ok(handoff.groups[0]!.children[0]!.patch.path.includes(`${path.sep}worktree-diffs${path.sep}${result.details.runId}${path.sep}`));
@@ -317,7 +353,8 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
317
353
 
318
354
  assert.equal(result.isError, undefined);
319
355
  assert.equal(result.details?.mode, "parallel");
320
- assert.match(result.content[0]?.text ?? "", new RegExp(`${action} alias finished`));
356
+ assert.match(result.content[0]?.text ?? "", /Output saved to: /);
357
+ assert.doesNotMatch(result.content[0]?.text ?? "", new RegExp(`${action} alias finished`));
321
358
  }
322
359
  });
323
360
 
@@ -431,7 +468,9 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
431
468
  assert.equal(result.details?.results?.[0]?.timedOut, true);
432
469
  assert.equal(result.details?.results?.[0]?.error, "Subagent timed out after 300ms.");
433
470
  assert.equal(result.details?.results?.[1]?.exitCode, 0);
434
- assert.equal(result.details?.results?.[1]?.finalOutput, "fast done");
471
+ assert.equal(result.details?.results?.[1]?.finalOutput, undefined);
472
+ assert.ok(result.details?.results?.[1]?.savedOutputPath);
473
+ assert.equal(fs.readFileSync(result.details?.results?.[1]?.savedOutputPath as string, "utf-8"), "fast done");
435
474
  assert.match(result.content[0]?.text ?? "", /1\/2 succeeded/);
436
475
  assert.match(result.content[0]?.text ?? "", /TIMED OUT: Subagent timed out after 300ms\./);
437
476
  });
@@ -456,8 +495,9 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
456
495
  assert.match(text, /Output saved to:/);
457
496
  assert.match(text, /2 lines/);
458
497
  assert.doesNotMatch(text, /Parallel full report/);
459
- assert.match(result.details?.results?.[0]?.finalOutput ?? "", /Output saved to:/);
460
- assert.doesNotMatch(result.details?.results?.[0]?.finalOutput ?? "", /Parallel full report/);
498
+ assert.equal(result.details?.results?.[0]?.finalOutput, undefined);
499
+ assert.match(result.details?.results?.[0]?.outputReference?.message ?? "", /Output saved to:/);
500
+ assert.doesNotMatch(result.details?.results?.[0]?.outputReference?.message ?? "", /Parallel full report/);
461
501
  assert.equal(fs.readFileSync(outputPath, "utf-8"), "Parallel full report\nwith details");
462
502
  });
463
503
 
@@ -466,7 +506,7 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
466
506
 
467
507
  const result = await executor.execute(
468
508
  "parallel-file-only-missing-output",
469
- { tasks: [{ agent: "echo", task: "Write report", outputMode: "file-only" }] },
509
+ { tasks: [{ agent: "echo", task: "Write report", output: false, outputMode: "file-only" }] },
470
510
  new AbortController().signal,
471
511
  undefined,
472
512
  makeMinimalCtx(tempDir),
@@ -570,7 +610,8 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
570
610
 
571
611
  Inspect
572
612
 
573
- ## Acceptance Contract`));
613
+ ---\n**Output:**`));
614
+ assert.match(taskArg, /## Acceptance Contract/);
574
615
  });
575
616
 
576
617
  it("top-level parallel defaultProgress uses isolated run storage", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
@@ -454,6 +454,50 @@ describe("renderSubagentResult fork indicator", () => {
454
454
  assert.match(text, /output: \/tmp\/reviewer_output\.md/);
455
455
  });
456
456
 
457
+ it("shows model and thinking on running compact single and multi rows", () => {
458
+ const single = renderSubagentResult!({
459
+ content: [{ type: "text", text: "(running...)" }],
460
+ details: {
461
+ mode: "single",
462
+ results: [{
463
+ agent: "reviewer",
464
+ task: "review",
465
+ exitCode: 0,
466
+ messages: [],
467
+ usage: emptyUsage,
468
+ progress: {
469
+ index: 0,
470
+ agent: "reviewer",
471
+ status: "running",
472
+ task: "review",
473
+ model: "openai-codex/gpt-5.5",
474
+ thinking: "high",
475
+ recentTools: [],
476
+ recentOutput: [],
477
+ toolCount: 1,
478
+ tokens: 42,
479
+ durationMs: 3_000,
480
+ },
481
+ }],
482
+ },
483
+ }, { expanded: false }, theme).render(160).join("\n");
484
+ assert.match(single, /reviewer \(gpt-5\.5 · thinking high\)/);
485
+
486
+ const multi = renderSubagentResult!({
487
+ content: [{ type: "text", text: "(running...)" }],
488
+ details: {
489
+ mode: "parallel",
490
+ totalSteps: 2,
491
+ results: [
492
+ { agent: "scout", task: "scan", exitCode: 0, messages: [], usage: emptyUsage, model: "anthropic/claude-haiku-4-5", thinking: "low", progress: { index: 0, agent: "scout", status: "running", task: "scan", recentTools: [], recentOutput: [], toolCount: 0, tokens: 0, durationMs: 0 } },
493
+ { agent: "worker", task: "fix", exitCode: 0, messages: [], usage: emptyUsage, progress: { index: 1, agent: "worker", status: "running", task: "fix", model: "openai/gpt-5-mini", recentTools: [], recentOutput: [], toolCount: 0, tokens: 0, durationMs: 0 } },
494
+ ],
495
+ },
496
+ }, { expanded: false }, theme).render(160).join("\n");
497
+ assert.match(multi, /Agent 1\/2: scout \(claude-haiku-4-5 · thinking low\)/);
498
+ assert.match(multi, /Agent 2\/2: worker \(gpt-5-mini\)/);
499
+ });
500
+
457
501
  it("keeps running compact result output stable when progress is unchanged", async () => {
458
502
  const result = {
459
503
  content: [{ type: "text" as const, text: "(running...)" }],