@selesai/code 0.13.40 → 0.14.3

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 (1360) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/bun/runtime-setup.js +4 -1
  3. package/dist/cli/args.d.ts +3 -1
  4. package/dist/cli/args.js +76 -33
  5. package/dist/cli/auth-check.d.ts +18 -0
  6. package/dist/cli/auth-check.js +49 -0
  7. package/dist/cli/credential-print.d.ts +5 -15
  8. package/dist/cli/credential-print.js +42 -82
  9. package/dist/cli/file-processor.d.ts +1 -1
  10. package/dist/cli/file-processor.js +2 -1
  11. package/dist/cli/list-models.d.ts +1 -1
  12. package/dist/cli/list-models.js +2 -2
  13. package/dist/cli/startup-ui.js +38 -30
  14. package/dist/config.d.ts +45 -7
  15. package/dist/config.js +134 -18
  16. package/dist/core/agent-session-custom-tools.test.js +64 -0
  17. package/dist/core/agent-session-length-continuation.test.js +7 -3
  18. package/dist/core/agent-session-runtime.js +2 -2
  19. package/dist/core/agent-session-services.d.ts +1 -0
  20. package/dist/core/agent-session-services.js +14 -0
  21. package/dist/core/agent-session.d.ts +180 -40
  22. package/dist/core/agent-session.js +1061 -485
  23. package/dist/core/auth-storage.d.ts +25 -15
  24. package/dist/core/auth-storage.js +242 -66
  25. package/dist/core/bash-executor.js +42 -52
  26. package/dist/core/bug-report.js +1 -1
  27. package/dist/core/cache-warmer.d.ts +1 -0
  28. package/dist/core/cache-warmer.js +15 -1
  29. package/dist/core/compaction/branch-summarization.js +3 -0
  30. package/dist/core/compaction/compaction.d.ts +3 -1
  31. package/dist/core/compaction/compaction.js +156 -79
  32. package/dist/core/compaction/utils.d.ts +2 -1
  33. package/dist/core/compaction/utils.js +25 -18
  34. package/dist/core/crash-log.d.ts +5 -0
  35. package/dist/core/crash-log.js +69 -0
  36. package/dist/core/defaults.d.ts +1 -1
  37. package/dist/core/defaults.js +1 -1
  38. package/dist/core/export-html/template.css +10 -0
  39. package/dist/core/export-html/template.js +72 -18
  40. package/dist/core/export-html/tool-renderer.d.ts +3 -3
  41. package/dist/core/export-html/tool-renderer.js +5 -3
  42. package/dist/core/extensions/index.d.ts +2 -2
  43. package/dist/core/extensions/index.js +1 -1
  44. package/dist/core/extensions/loader.js +138 -55
  45. package/dist/core/extensions/runner.d.ts +38 -5
  46. package/dist/core/extensions/runner.js +220 -7
  47. package/dist/core/extensions/types.d.ts +438 -51
  48. package/dist/core/extensions/types.js +3 -0
  49. package/dist/core/extensions/virtual-modules.js +6 -0
  50. package/dist/core/extensions/wrapper.d.ts +2 -2
  51. package/dist/core/extensions/wrapper.js +3 -3
  52. package/dist/core/footer-data-provider.d.ts +1 -1
  53. package/dist/core/footer-data-provider.js +1 -1
  54. package/dist/core/handoff.d.ts +0 -1
  55. package/dist/core/handoff.js +0 -1
  56. package/dist/core/http-dispatcher.js +5 -0
  57. package/dist/core/index.d.ts +1 -1
  58. package/dist/core/keybindings.d.ts +25 -24
  59. package/dist/core/keybindings.js +27 -6
  60. package/dist/core/mcp-servers.d.ts +134 -0
  61. package/dist/core/mcp-servers.js +223 -0
  62. package/dist/core/model-config.d.ts +122 -5
  63. package/dist/core/model-config.js +35 -1
  64. package/dist/core/model-registry.d.ts +23 -15
  65. package/dist/core/model-registry.js +46 -22
  66. package/dist/core/model-resolver.d.ts +6 -5
  67. package/dist/core/model-resolver.js +51 -21
  68. package/dist/core/model-runtime.d.ts +73 -14
  69. package/dist/core/model-runtime.js +398 -104
  70. package/dist/core/models-store.d.ts +12 -7
  71. package/dist/core/models-store.js +85 -15
  72. package/dist/core/nested-tool-calls.d.ts +102 -0
  73. package/dist/core/nested-tool-calls.js +178 -0
  74. package/dist/core/package-manager.d.ts +8 -1
  75. package/dist/core/package-manager.js +144 -61
  76. package/dist/core/package-manager.test.js +5 -5
  77. package/dist/core/pi-manifest.d.ts +9 -0
  78. package/dist/core/pi-manifest.js +25 -0
  79. package/dist/core/prompt-templates.d.ts +6 -1
  80. package/dist/core/prompt-templates.js +61 -35
  81. package/dist/core/provider-composer.d.ts +44 -19
  82. package/dist/core/provider-composer.js +166 -64
  83. package/dist/core/radius.d.ts +2 -0
  84. package/dist/core/radius.js +2 -0
  85. package/dist/core/remote-catalog-provider.d.ts +8 -1
  86. package/dist/core/remote-catalog-provider.js +25 -13
  87. package/dist/core/resource-loader.d.ts +9 -1
  88. package/dist/core/resource-loader.js +179 -45
  89. package/dist/core/runtime-credentials.d.ts +5 -5
  90. package/dist/core/runtime-credentials.js +11 -8
  91. package/dist/core/sdk.d.ts +16 -7
  92. package/dist/core/sdk.js +57 -24
  93. package/dist/core/session-manager.d.ts +45 -9
  94. package/dist/core/session-manager.js +124 -31
  95. package/dist/core/settings-diagnostics.d.ts +8 -0
  96. package/dist/core/settings-diagnostics.js +20 -0
  97. package/dist/core/settings-manager.d.ts +62 -32
  98. package/dist/core/settings-manager.js +147 -69
  99. package/dist/core/skills.d.ts +5 -2
  100. package/dist/core/skills.js +58 -35
  101. package/dist/core/slash-commands.js +2 -1
  102. package/dist/core/source-info.d.ts +8 -0
  103. package/dist/core/source-info.js +16 -0
  104. package/dist/core/system-prompt.d.ts +9 -3
  105. package/dist/core/system-prompt.js +9 -12
  106. package/dist/core/system-prompt.test.js +2 -7
  107. package/dist/core/tools/bash.d.ts +12 -0
  108. package/dist/core/tools/bash.js +33 -2
  109. package/dist/core/tools/edit-diff.d.ts +0 -5
  110. package/dist/core/tools/edit-diff.js +2 -5
  111. package/dist/core/tools/edit.d.ts +4 -0
  112. package/dist/core/tools/edit.js +31 -11
  113. package/dist/core/tools/find.d.ts +7 -0
  114. package/dist/core/tools/find.js +16 -19
  115. package/dist/core/tools/grep.d.ts +4 -0
  116. package/dist/core/tools/grep.js +5 -1
  117. package/dist/core/tools/index.d.ts +1 -1
  118. package/dist/core/tools/index.js +11 -2
  119. package/dist/core/tools/ls.d.ts +4 -0
  120. package/dist/core/tools/ls.js +5 -1
  121. package/dist/core/tools/output-accumulator.d.ts +11 -0
  122. package/dist/core/tools/output-accumulator.js +40 -10
  123. package/dist/core/tools/read.d.ts +17 -2
  124. package/dist/core/tools/read.js +23 -2
  125. package/dist/core/tools/render-utils.d.ts +6 -0
  126. package/dist/core/tools/render-utils.js +26 -0
  127. package/dist/core/tools/renderers/bash.js +19 -35
  128. package/dist/core/tools/renderers/edit.js +5 -4
  129. package/dist/core/tools/renderers/index.d.ts +2 -2
  130. package/dist/core/tools/renderers/read.js +3 -2
  131. package/dist/core/tools/tool-definition-wrapper.d.ts +5 -3
  132. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  133. package/dist/core/tools/truncate.d.ts +14 -0
  134. package/dist/core/tools/truncate.js +29 -0
  135. package/dist/core/tools/write.d.ts +4 -0
  136. package/dist/core/tools/write.js +6 -2
  137. package/dist/core/trust-manager.js +3 -1
  138. package/dist/core/usage-totals.d.ts +2 -0
  139. package/dist/core/usage-totals.js +23 -0
  140. package/dist/core/virtual-models.d.ts +116 -0
  141. package/dist/core/virtual-models.js +123 -0
  142. package/dist/defaults/models.json +32 -0
  143. package/dist/defaults/settings.json +1 -9
  144. package/dist/extensions/codemode/execute.d.ts +15 -0
  145. package/dist/extensions/codemode/execute.js +645 -0
  146. package/dist/extensions/codemode/execute.lazy.d.ts +2 -0
  147. package/dist/extensions/codemode/execute.lazy.js +2 -0
  148. package/dist/extensions/codemode/index.d.ts +20 -0
  149. package/dist/extensions/codemode/index.js +31 -0
  150. package/dist/extensions/codemode/renderer.d.ts +11 -0
  151. package/dist/extensions/codemode/renderer.js +136 -0
  152. package/dist/extensions/codemode/tool.d.ts +129 -0
  153. package/dist/extensions/codemode/tool.js +275 -0
  154. package/dist/extensions/context-compaction-reminder.ts +1 -1
  155. package/dist/extensions/grep-app/index.ts +2 -10
  156. package/dist/extensions/herdr-agent-state.ts +1 -1
  157. package/dist/extensions/{pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/index.d.ts → index.d.ts} +0 -1
  158. package/dist/extensions/index.js +12 -0
  159. package/dist/extensions/jev/classifier-context.ts +54 -0
  160. package/dist/extensions/jev/classifier.test.ts +150 -0
  161. package/dist/extensions/jev/classifier.ts +191 -0
  162. package/dist/extensions/jev/decisions.test.ts +56 -47
  163. package/dist/extensions/jev/decisions.ts +94 -109
  164. package/dist/extensions/jev/test-support.ts +33 -38
  165. package/dist/extensions/jev-advisory-lifecycle.test.ts +12 -12
  166. package/dist/extensions/jev-advisory-memory.test.ts +9 -9
  167. package/dist/extensions/jev-advisory-recommendations.test.ts +8 -8
  168. package/dist/extensions/jev-ask-tool.test.ts +41 -45
  169. package/dist/extensions/jev-ask-tool.ts +11 -39
  170. package/dist/extensions/jev-find-source.test.ts +26 -0
  171. package/dist/extensions/jev-find-source.ts +58 -19
  172. package/dist/extensions/jev-tool-ranker.test.ts +185 -0
  173. package/dist/extensions/jev-tool-ranker.ts +169 -0
  174. package/dist/{core → extensions}/llama/client.d.ts +2 -0
  175. package/dist/{core → extensions}/llama/client.js +7 -3
  176. package/dist/{core → extensions}/llama/index.js +13 -2
  177. package/dist/{core → extensions}/llama/provider.d.ts +1 -1
  178. package/dist/extensions/llama/provider.js +235 -0
  179. package/dist/extensions/mcp/cli.d.ts +20 -0
  180. package/dist/extensions/mcp/cli.js +496 -0
  181. package/dist/extensions/mcp/cli.lazy.d.ts +2 -0
  182. package/dist/extensions/mcp/cli.lazy.js +2 -0
  183. package/dist/extensions/mcp/config.d.ts +83 -0
  184. package/dist/extensions/mcp/config.js +193 -0
  185. package/dist/extensions/mcp/index.d.ts +78 -0
  186. package/dist/extensions/mcp/index.js +1201 -0
  187. package/dist/extensions/mcp/log.d.ts +15 -0
  188. package/dist/extensions/mcp/log.js +65 -0
  189. package/dist/extensions/mcp/oauth.d.ts +97 -0
  190. package/dist/extensions/mcp/oauth.js +401 -0
  191. package/dist/extensions/mcp/resources.d.ts +41 -0
  192. package/dist/extensions/mcp/resources.js +260 -0
  193. package/dist/extensions/mcp/runtime.d.ts +96 -0
  194. package/dist/extensions/mcp/runtime.js +412 -0
  195. package/dist/extensions/mcp/runtime.lazy.d.ts +2 -0
  196. package/dist/extensions/mcp/runtime.lazy.js +2 -0
  197. package/dist/extensions/mcp/tools.d.ts +86 -0
  198. package/dist/extensions/mcp/tools.js +252 -0
  199. package/dist/extensions/mcp/ui.d.ts +59 -0
  200. package/dist/extensions/mcp/ui.js +185 -0
  201. package/dist/extensions/package.json +2 -1
  202. package/dist/extensions/pi-graft/index.ts +4 -19
  203. package/dist/extensions/pi-graft/lifecycle.test.ts +32 -25
  204. package/dist/extensions/pi-graft/tools.ts +6 -0
  205. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +25 -0
  206. package/dist/extensions/pi-hermes-memory/README.md +140 -4
  207. package/dist/extensions/pi-hermes-memory/package-lock.json +624 -2720
  208. package/dist/extensions/pi-hermes-memory/package.json +9 -7
  209. package/dist/extensions/pi-hermes-memory/scripts/benchmark-memory-startup.mjs +72 -0
  210. package/dist/extensions/pi-hermes-memory/scripts/check-production-install.mjs +107 -0
  211. package/dist/extensions/pi-hermes-memory/src/auto-consolidation-warning.ts +2 -1
  212. package/dist/extensions/pi-hermes-memory/src/config.ts +35 -0
  213. package/dist/extensions/pi-hermes-memory/src/constants.ts +62 -15
  214. package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +374 -50
  215. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +7 -4
  216. package/dist/extensions/pi-hermes-memory/src/handlers/correction-detector.ts +19 -14
  217. package/dist/extensions/pi-hermes-memory/src/handlers/insights.ts +75 -1
  218. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +322 -22
  219. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +8 -0
  220. package/dist/extensions/pi-hermes-memory/src/handlers/session-flush.ts +193 -54
  221. package/dist/extensions/pi-hermes-memory/src/handlers/skills-command.ts +6 -2
  222. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +33 -5
  223. package/dist/extensions/pi-hermes-memory/src/index.ts +116 -47
  224. package/dist/extensions/pi-hermes-memory/src/memory-initialization.ts +101 -0
  225. package/dist/extensions/pi-hermes-memory/src/store/atomic-lock-coordinator.ts +10 -2
  226. package/dist/extensions/pi-hermes-memory/src/store/db.ts +34 -9
  227. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +44 -8
  228. package/dist/extensions/pi-hermes-memory/src/store/schema.ts +3 -1
  229. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +141 -0
  230. package/dist/extensions/pi-hermes-memory/src/store/standing-instructions.ts +17 -6
  231. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +20 -10
  232. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +1 -17
  233. package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +2 -10
  234. package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +4 -8
  235. package/dist/extensions/pi-hermes-memory/src/types.ts +48 -0
  236. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +108 -0
  237. package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +1185 -42
  238. package/dist/extensions/pi-hermes-memory/tests/handlers/insights.test.ts +57 -1
  239. package/dist/extensions/pi-hermes-memory/tests/handlers/lazy-memory-hooks.test.ts +128 -0
  240. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +844 -1
  241. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +16 -0
  242. package/dist/extensions/pi-hermes-memory/tests/handlers/session-flush.test.ts +512 -3
  243. package/dist/extensions/pi-hermes-memory/tests/handlers/skills-command.test.ts +38 -0
  244. package/dist/extensions/pi-hermes-memory/tests/handlers/sync-markdown-memories.test.ts +183 -2
  245. package/dist/extensions/pi-hermes-memory/tests/handlers/system-prompt.test.ts +1 -3
  246. package/dist/extensions/pi-hermes-memory/tests/index.test.ts +17 -0
  247. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +21 -0
  248. package/dist/extensions/pi-hermes-memory/tests/lazy-startup.test.ts +313 -0
  249. package/dist/extensions/pi-hermes-memory/tests/memory-initialization.test.ts +182 -0
  250. package/dist/extensions/pi-hermes-memory/tests/prompt-injection.test.ts +184 -0
  251. package/dist/extensions/pi-hermes-memory/tests/skill-discovery-contract.test.ts +60 -0
  252. package/dist/extensions/pi-hermes-memory/tests/store/atomic-lock-lazy-probe.test.ts +80 -0
  253. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +2 -0
  254. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +79 -0
  255. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +268 -0
  256. package/dist/extensions/pi-hermes-memory/tests/store/standing-instructions.test.ts +29 -0
  257. package/dist/extensions/pi-hermes-memory/tests/store/usage-tracking.test.ts +170 -0
  258. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +80 -1
  259. package/dist/extensions/pi-hermes-memory/tests/tools/skill-tool.test.ts +51 -0
  260. package/dist/extensions/pi-intercom/CHANGELOG.md +66 -0
  261. package/dist/extensions/pi-intercom/README.md +100 -12
  262. package/dist/extensions/pi-intercom/broker/broker.ts +28 -12
  263. package/dist/extensions/pi-intercom/broker/client.ts +3 -0
  264. package/dist/extensions/pi-intercom/broker/cross-machine-provenance.test.ts +63 -0
  265. package/dist/extensions/pi-intercom/broker/protocol.ts +64 -0
  266. package/dist/extensions/pi-intercom/broker/spawn.test.ts +58 -167
  267. package/dist/extensions/pi-intercom/broker/spawn.ts +40 -87
  268. package/dist/extensions/pi-intercom/cli.mjs +6 -0
  269. package/dist/extensions/pi-intercom/cli.test.ts +428 -0
  270. package/dist/extensions/pi-intercom/cli.ts +343 -0
  271. package/dist/extensions/pi-intercom/config.test.ts +61 -1
  272. package/dist/extensions/pi-intercom/config.ts +53 -2
  273. package/dist/extensions/pi-intercom/cross-machine-discovery.test.ts +218 -0
  274. package/dist/extensions/pi-intercom/cross-machine-discovery.ts +140 -0
  275. package/dist/extensions/pi-intercom/cross-machine-envelope.test.ts +106 -0
  276. package/dist/extensions/pi-intercom/cross-machine-envelope.ts +92 -0
  277. package/dist/extensions/pi-intercom/cross-machine-integration.test.ts +25 -0
  278. package/dist/extensions/pi-intercom/cross-machine-transport.test.ts +106 -0
  279. package/dist/extensions/pi-intercom/cross-machine-transport.ts +110 -0
  280. package/dist/extensions/pi-intercom/extension-api.ts +14 -0
  281. package/dist/extensions/pi-intercom/handover.test.ts +88 -0
  282. package/dist/extensions/pi-intercom/handover.ts +115 -0
  283. package/dist/extensions/pi-intercom/herdr-location.test.ts +173 -0
  284. package/dist/extensions/pi-intercom/herdr-location.ts +137 -0
  285. package/dist/extensions/pi-intercom/human-priority.pi.test.ts +489 -0
  286. package/dist/extensions/pi-intercom/index.ts +582 -164
  287. package/dist/extensions/pi-intercom/intercom.integration.test.ts +590 -22
  288. package/dist/extensions/pi-intercom/package.json +12 -2
  289. package/dist/extensions/pi-intercom/project-agent.ts +1 -1
  290. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +26 -2
  291. package/dist/extensions/pi-intercom/test/handover-picker.test.ts +134 -0
  292. package/dist/extensions/pi-intercom/test/inline-message.test.ts +43 -0
  293. package/dist/extensions/pi-intercom/types.d.ts +247 -0
  294. package/dist/extensions/pi-intercom/types.js +2 -0
  295. package/dist/extensions/pi-intercom/types.ts +39 -1
  296. package/dist/extensions/pi-intercom/ui/handover-picker.ts +318 -0
  297. package/dist/extensions/pi-intercom/ui/inline-message.ts +10 -3
  298. package/dist/extensions/pi-intercom/ui/session-list.ts +28 -15
  299. package/dist/extensions/pi-subagents/.github/workflows/release.yml +5 -1
  300. package/dist/extensions/pi-subagents/.github/workflows/test.yml +188 -18
  301. package/dist/extensions/pi-subagents/CHANGELOG.md +514 -1
  302. package/dist/extensions/pi-subagents/README.md +7 -5
  303. package/dist/extensions/pi-subagents/VISION.md +6 -0
  304. package/dist/extensions/pi-subagents/agents/reviewer.md +7 -5
  305. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  306. package/dist/extensions/pi-subagents/docs/agents.md +75 -35
  307. package/dist/extensions/pi-subagents/docs/configuration.md +156 -25
  308. package/dist/extensions/pi-subagents/docs/extension-api.md +113 -29
  309. package/dist/extensions/pi-subagents/docs/missions.md +46 -14
  310. package/dist/extensions/pi-subagents/docs/models.md +22 -84
  311. package/dist/extensions/pi-subagents/docs/observability.md +49 -24
  312. package/dist/extensions/pi-subagents/docs/standalone-background.md +59 -0
  313. package/dist/extensions/pi-subagents/docs/tool-reference.md +167 -116
  314. package/dist/extensions/pi-subagents/docs/watchdog.md +41 -10
  315. package/dist/extensions/pi-subagents/docs/workflows.md +239 -163
  316. package/dist/extensions/pi-subagents/examples/typed-gate/.pi/agents/classifier.md +14 -0
  317. package/dist/extensions/pi-subagents/examples/typed-gate/README.md +42 -0
  318. package/dist/extensions/pi-subagents/examples/typed-gate/classify +38 -0
  319. package/dist/extensions/pi-subagents/examples/typed-gate/workflow.js +32 -0
  320. package/dist/extensions/pi-subagents/index.ts +11 -3
  321. package/dist/extensions/pi-subagents/inspector-runner.mjs +3 -3
  322. package/dist/extensions/pi-subagents/package-lock.json +517 -1129
  323. package/dist/extensions/pi-subagents/package.json +22 -11
  324. package/dist/extensions/pi-subagents/prompts/council.md +7 -4
  325. package/dist/extensions/pi-subagents/prompts/parallel-review.md +2 -2
  326. package/dist/extensions/pi-subagents/prompts/review-loop.md +2 -2
  327. package/dist/extensions/pi-subagents/runner-peer-loader.mjs +21 -0
  328. package/dist/extensions/pi-subagents/runner-peer-preload.mjs +30 -0
  329. package/dist/extensions/pi-subagents/scripts/build-package.mjs +79 -0
  330. package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +2 -2
  331. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +46 -27
  332. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +11 -7
  333. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +78 -73
  334. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +4 -5
  335. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +10 -2
  336. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +69 -67
  337. package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +3 -3
  338. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +21 -10
  339. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +113 -113
  340. package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +91 -4
  341. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +15 -9
  342. package/dist/extensions/pi-subagents/src/agents/agents.ts +432 -193
  343. package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +6 -1
  344. package/dist/extensions/pi-subagents/src/agents/global-npm-root.ts +58 -0
  345. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +5 -2
  346. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +22 -18
  347. package/dist/extensions/pi-subagents/src/agents/skills.ts +43 -10
  348. package/dist/extensions/pi-subagents/src/api/delegation.ts +20 -1
  349. package/dist/extensions/pi-subagents/src/api/inspectors.ts +27 -0
  350. package/dist/extensions/pi-subagents/src/api/preflight.ts +140 -62
  351. package/dist/extensions/pi-subagents/src/api/required-child-extensions.ts +6 -0
  352. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
  353. package/dist/extensions/pi-subagents/src/extension/config.ts +24 -40
  354. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +4 -1
  355. package/dist/extensions/pi-subagents/src/extension/doctor.ts +18 -15
  356. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +87 -13
  357. package/dist/extensions/pi-subagents/src/extension/herdr-pi-bridge.ts +162 -0
  358. package/dist/extensions/pi-subagents/src/extension/index.ts +280 -268
  359. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +76 -37
  360. package/dist/extensions/pi-subagents/src/extension/reply-workflow-script.ts +67 -0
  361. package/dist/extensions/pi-subagents/src/extension/rpc.ts +34 -11
  362. package/dist/extensions/pi-subagents/src/extension/schemas.ts +116 -205
  363. package/dist/extensions/pi-subagents/src/extension/subagent-guide.ts +13 -5
  364. package/dist/extensions/pi-subagents/src/extension/tool-activation.ts +146 -0
  365. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +77 -90
  366. package/dist/extensions/pi-subagents/src/extension/upgrade-notice.ts +40 -0
  367. package/dist/extensions/pi-subagents/src/inspectors/actions.ts +148 -0
  368. package/dist/extensions/pi-subagents/src/inspectors/ghostty/actions.ts +74 -0
  369. package/dist/extensions/pi-subagents/src/inspectors/ghostty/plugin.ts +29 -0
  370. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +100 -180
  371. package/dist/extensions/pi-subagents/src/inspectors/herdr/plugin.ts +20 -0
  372. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -2
  373. package/dist/extensions/pi-subagents/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +13 -13
  374. package/dist/extensions/pi-subagents/src/inspectors/plugins.ts +75 -0
  375. package/dist/extensions/pi-subagents/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +4 -15
  376. package/dist/extensions/pi-subagents/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +1 -1
  377. package/dist/extensions/pi-subagents/src/inspectors/types.ts +51 -0
  378. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +38 -53
  379. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +53 -9
  380. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +69 -55
  381. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +7 -2
  382. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +45 -32
  383. package/dist/extensions/pi-subagents/src/missions/store.ts +11 -2
  384. package/dist/extensions/pi-subagents/src/policy/authority.ts +5 -1
  385. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +30 -18
  386. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +59 -9
  387. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +18 -2
  388. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +463 -248
  389. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +106 -66
  390. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +33 -20
  391. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +47 -17
  392. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -5
  393. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +24 -11
  394. package/dist/extensions/pi-subagents/src/runs/background/await-async-run.ts +78 -0
  395. package/dist/extensions/pi-subagents/src/runs/background/binary-bootstrap.ts +66 -0
  396. package/dist/extensions/pi-subagents/src/runs/background/calendar-schedule.ts +90 -0
  397. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -2
  398. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +19 -38
  399. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +62 -3
  400. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +82 -10
  401. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +259 -21
  402. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +30 -3
  403. package/dist/extensions/pi-subagents/src/runs/background/pid-namespace.ts +18 -0
  404. package/dist/extensions/pi-subagents/src/runs/background/process-terminal-candidate.ts +98 -0
  405. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +18 -97
  406. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +14 -5
  407. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +66 -12
  408. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +4 -3
  409. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +126 -45
  410. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +11 -6
  411. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +18 -7
  412. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +43 -51
  413. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +8 -1
  414. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +5 -4
  415. package/dist/extensions/pi-subagents/src/runs/background/runner-http-dispatcher.ts +119 -0
  416. package/dist/extensions/pi-subagents/src/runs/background/runner-startup-failure.ts +56 -0
  417. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +233 -69
  418. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +67 -16
  419. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +21 -3
  420. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner-bootstrap.ts +276 -0
  421. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +592 -573
  422. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +177 -29
  423. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +77 -28
  424. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +10 -4
  425. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +7 -13
  426. package/dist/extensions/pi-subagents/src/runs/background/workflow-terminal-proof.ts +41 -23
  427. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +19 -8
  428. package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +130 -7
  429. package/dist/extensions/pi-subagents/src/runs/foreground/command-action.ts +41 -0
  430. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +267 -385
  431. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +4 -2
  432. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +6 -10
  433. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1158 -413
  434. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +3 -1
  435. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  436. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +200 -74
  437. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +133 -34
  438. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +6 -1
  439. package/dist/extensions/pi-subagents/src/runs/shared/child-commands.ts +228 -0
  440. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +18 -18
  441. package/dist/extensions/pi-subagents/src/runs/shared/child-launch-plan.ts +16 -4
  442. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +55 -12
  443. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +6 -3
  444. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +11 -1
  445. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +367 -74
  446. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +50 -86
  447. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +288 -0
  448. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +129 -0
  449. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +114 -0
  450. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +5 -5
  451. package/dist/extensions/pi-subagents/src/runs/shared/effective-system-prompt.ts +33 -0
  452. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +49 -10
  453. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +61 -9
  454. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +16 -9
  455. package/dist/extensions/pi-subagents/src/runs/shared/external-job-bridge.ts +55 -1
  456. package/dist/extensions/pi-subagents/src/runs/shared/git-environment.ts +1 -1
  457. package/dist/extensions/pi-subagents/src/runs/shared/herdr-connection.ts +134 -0
  458. package/dist/extensions/pi-subagents/src/runs/shared/herdr-external-adapters.ts +169 -0
  459. package/dist/extensions/pi-subagents/src/runs/shared/herdr-machine.ts +279 -0
  460. package/dist/extensions/pi-subagents/src/runs/shared/herdr-pi-protocol.ts +59 -0
  461. package/dist/extensions/pi-subagents/src/runs/shared/herdr-placed-run.ts +263 -0
  462. package/dist/extensions/pi-subagents/src/runs/shared/mcp-config-sources.ts +6 -2
  463. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +108 -13
  464. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +23 -7
  465. package/dist/extensions/pi-subagents/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  466. package/dist/extensions/pi-subagents/src/runs/shared/{model-fallback.ts → model-resolution.ts} +65 -245
  467. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +30 -11
  468. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +24 -3
  469. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +5 -4
  470. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +55 -7
  471. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +14 -4
  472. package/dist/extensions/pi-subagents/src/runs/shared/partial-output.ts +48 -0
  473. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +12 -4
  474. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +88 -1
  475. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +12 -3
  476. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +160 -9
  477. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +35 -34
  478. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +58 -19
  479. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +2 -2
  480. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +21 -0
  481. package/dist/extensions/pi-subagents/src/runs/shared/usage-reconciliation.ts +73 -0
  482. package/dist/extensions/pi-subagents/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  483. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-apply.ts +160 -0
  484. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +42 -20
  485. package/dist/extensions/pi-subagents/src/runs/shared/worktree-lock.ts +29 -0
  486. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +27 -4
  487. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +94 -26
  488. package/dist/extensions/pi-subagents/src/shared/child-cache-retention.ts +43 -0
  489. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +5 -3
  490. package/dist/extensions/pi-subagents/src/shared/disabled-features.ts +123 -0
  491. package/dist/extensions/pi-subagents/src/shared/extension-context.ts +14 -1
  492. package/dist/extensions/pi-subagents/src/shared/file-lease.ts +74 -0
  493. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +27 -77
  494. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +70 -10
  495. package/dist/extensions/pi-subagents/src/shared/model-info.ts +10 -1
  496. package/dist/extensions/pi-subagents/src/shared/opencode-session-headers.ts +30 -0
  497. package/dist/extensions/pi-subagents/src/shared/package-version.ts +17 -0
  498. package/dist/extensions/pi-subagents/src/shared/parent-wake.ts +66 -0
  499. package/dist/extensions/pi-subagents/src/shared/pruned-fork.ts +71 -58
  500. package/dist/extensions/pi-subagents/src/shared/required-child-extensions.ts +115 -0
  501. package/dist/extensions/pi-subagents/src/shared/settings-file-lease.ts +36 -0
  502. package/dist/extensions/pi-subagents/src/shared/settings.ts +7 -4
  503. package/dist/extensions/pi-subagents/src/shared/shortcuts.ts +1 -5
  504. package/dist/extensions/pi-subagents/src/shared/types.ts +146 -48
  505. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +28 -2
  506. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +15 -1
  507. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +19 -4
  508. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +13 -2
  509. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +15 -15
  510. package/dist/extensions/pi-subagents/src/slash/subagent-cost.ts +46 -9
  511. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +29 -16
  512. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +201 -26
  513. package/dist/extensions/pi-subagents/src/tui/fleet.ts +33 -16
  514. package/dist/extensions/pi-subagents/src/tui/render.ts +334 -103
  515. package/dist/extensions/pi-subagents/src/tui/running-tone.ts +28 -0
  516. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +27 -28
  517. package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +47 -12
  518. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +2 -2
  519. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +22 -2
  520. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +17 -17
  521. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +24 -8
  522. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +43 -39
  523. package/dist/extensions/pi-subagents/src/watchdog/render.ts +2 -2
  524. package/dist/extensions/pi-subagents/src/watchdog/review.ts +127 -78
  525. package/dist/extensions/pi-subagents/src/watchdog/rules.ts +1 -1
  526. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +107 -31
  527. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +1 -1
  528. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +22 -14
  529. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +14 -13
  530. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +35 -6
  531. package/dist/extensions/pi-subagents/src/watchdog/types.ts +6 -4
  532. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +2 -2
  533. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  534. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +500 -65
  535. package/dist/extensions/pi-subagents/src/workflows/structured-workflow-scripts.ts +177 -0
  536. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +8 -2
  537. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +5 -3
  538. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +70 -10
  539. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +57 -18
  540. package/dist/extensions/pi-subagents/src/workflows/workflow-reuse.ts +120 -0
  541. package/dist/extensions/pi-subagents/src/workflows/workflow-revival.ts +123 -0
  542. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +5 -2
  543. package/dist/extensions/pi-subagents/test/fixtures/cwd-workflow-child.ts +37 -0
  544. package/dist/extensions/pi-subagents/test/fixtures/herdr-allocation-lock-child.ts +16 -0
  545. package/dist/extensions/pi-subagents/test/fixtures/herdr-provision-child.ts +15 -0
  546. package/dist/extensions/pi-subagents/test/fixtures/native-nested-wait-provider.ts +76 -0
  547. package/dist/extensions/pi-subagents/test/fixtures/runner-bootstrap-harness.ts +36 -0
  548. package/dist/extensions/pi-subagents/test/fixtures/schedule-history-writer.mjs +48 -0
  549. package/dist/extensions/pi-subagents/test/fixtures/settings-race-writer.mjs +57 -0
  550. package/dist/extensions/pi-subagents/test/fixtures/workflow-cwd-access-failure.ts +33 -0
  551. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +40 -7
  552. package/dist/extensions/pi-subagents/test/integration/async-attention-clearing.test.ts +9 -8
  553. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +435 -276
  554. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +435 -397
  555. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +918 -497
  556. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +264 -17
  557. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +185 -22
  558. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +3 -2
  559. package/dist/extensions/pi-subagents/test/integration/claude-code-dispatch.test.ts +127 -0
  560. package/dist/extensions/pi-subagents/test/integration/deadline-checkpoint.test.ts +126 -0
  561. package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +29 -1
  562. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +88 -0
  563. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +450 -13
  564. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +10 -6
  565. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +105 -86
  566. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +403 -18
  567. package/dist/extensions/pi-subagents/test/integration/inline-workflow-visibility.test.ts +351 -0
  568. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +195 -10
  569. package/dist/extensions/pi-subagents/test/integration/model-resolution-diagnostic.test.ts +108 -0
  570. package/dist/extensions/pi-subagents/test/integration/nested-async-wait.test.ts +61 -0
  571. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +5 -4
  572. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +1 -1
  573. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +6 -4
  574. package/dist/extensions/pi-subagents/test/integration/render-widget-mouse.test.ts +84 -0
  575. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +242 -16
  576. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +2 -2
  577. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +158 -4
  578. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +1435 -248
  579. package/dist/extensions/pi-subagents/test/integration/single-execution.part-2.test.ts +454 -515
  580. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +140 -21
  581. package/dist/extensions/pi-subagents/test/integration/structured-workflows.test.ts +105 -0
  582. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +90 -11
  583. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +18 -15
  584. package/dist/extensions/pi-subagents/test/integration/workflow-worktree-admission.test.ts +100 -0
  585. package/dist/extensions/pi-subagents/test/smoke/clean-install.mjs +106 -0
  586. package/dist/extensions/pi-subagents/test/smoke/npm-background.mjs +43 -0
  587. package/dist/extensions/pi-subagents/test/smoke/packed-runner-selection.mjs +42 -0
  588. package/dist/extensions/pi-subagents/test/smoke/runner-loader-capability.mjs +50 -0
  589. package/dist/extensions/pi-subagents/test/smoke/sdk-child.ts +57 -0
  590. package/dist/extensions/pi-subagents/test/smoke/{pi085-extension.ts → sdk-extension.ts} +2 -2
  591. package/dist/extensions/pi-subagents/test/smoke/standalone-background.mjs +140 -0
  592. package/dist/extensions/pi-subagents/test/smoke/standalone-fault.ts +43 -0
  593. package/dist/extensions/pi-subagents/test/smoke/standalone-matrix.mjs +65 -0
  594. package/dist/extensions/pi-subagents/test/smoke/standalone-observer.ts +8 -0
  595. package/dist/extensions/pi-subagents/test/smoke/standalone-parent.ts +220 -0
  596. package/dist/extensions/pi-subagents/test/smoke/standalone-provider.ts +58 -0
  597. package/dist/extensions/pi-subagents/test/smoke/standalone-release.json +8 -0
  598. package/dist/extensions/pi-subagents/test/smoke/standalone-revival.ts +109 -0
  599. package/dist/extensions/pi-subagents/test/smoke/standalone-shared.ts +79 -0
  600. package/dist/extensions/pi-subagents/test/smoke/tool-activation.test.ts +128 -0
  601. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +6 -25
  602. package/dist/extensions/pi-subagents/test/support/async-result-timeout-evidence.ts +61 -0
  603. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +122 -9
  604. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +15 -7
  605. package/dist/extensions/pi-subagents/test/support/helpers.ts +11 -14
  606. package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +21 -3
  607. package/dist/extensions/pi-subagents/test/support/native-peer-loader.mjs +15 -0
  608. package/dist/extensions/pi-subagents/test/support/node-command.ts +29 -6
  609. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +5 -3
  610. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +7 -15
  611. package/dist/extensions/pi-subagents/test/support/watchdog-stable-host-loader.mjs +23 -0
  612. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +3 -3
  613. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +158 -155
  614. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +52 -1
  615. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +21 -17
  616. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +105 -36
  617. package/dist/extensions/pi-subagents/test/unit/advertisement-barrier.test.ts +151 -0
  618. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -2
  619. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +33 -3
  620. package/dist/extensions/pi-subagents/test/unit/agent-end-auto-drain.test.ts +149 -0
  621. package/dist/extensions/pi-subagents/test/unit/agent-exclude-dirs.test.ts +246 -0
  622. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +62 -87
  623. package/dist/extensions/pi-subagents/test/unit/agent-global-root-override.test.ts +86 -0
  624. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +398 -48
  625. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +126 -1
  626. package/dist/extensions/pi-subagents/test/unit/agent-name-resolution.test.ts +75 -0
  627. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +94 -42
  628. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +73 -3
  629. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +428 -2
  630. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +4 -4
  631. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +112 -1
  632. package/dist/extensions/pi-subagents/test/unit/async-result-timeout-evidence.test.ts +53 -0
  633. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +92 -4
  634. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +58 -6
  635. package/dist/extensions/pi-subagents/test/unit/async-run-history.test.ts +123 -0
  636. package/dist/extensions/pi-subagents/test/unit/async-spawn-package-dir.test.ts +203 -0
  637. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +26 -35
  638. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +185 -0
  639. package/dist/extensions/pi-subagents/test/unit/authority-policy.test.ts +9 -1
  640. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +106 -1
  641. package/dist/extensions/pi-subagents/test/unit/await-async-run.test.ts +202 -0
  642. package/dist/extensions/pi-subagents/test/unit/binary-async-spawn.test.ts +115 -0
  643. package/dist/extensions/pi-subagents/test/unit/binary-pi-spawn.test.ts +27 -0
  644. package/dist/extensions/pi-subagents/test/unit/calendar-schedule.test.ts +70 -0
  645. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +38 -0
  646. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +10 -2
  647. package/dist/extensions/pi-subagents/test/unit/changelog-format.test.ts +11 -0
  648. package/dist/extensions/pi-subagents/test/unit/child-cache-retention.test.ts +87 -0
  649. package/dist/extensions/pi-subagents/test/unit/child-commands.test.ts +243 -0
  650. package/dist/extensions/pi-subagents/test/unit/child-external-job-bridge.test.ts +126 -0
  651. package/dist/extensions/pi-subagents/test/unit/child-launch-plan.test.ts +12 -1
  652. package/dist/extensions/pi-subagents/test/unit/child-lifecycle.test.ts +36 -0
  653. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +24 -0
  654. package/dist/extensions/pi-subagents/test/unit/child-session-commands.test.ts +162 -0
  655. package/dist/extensions/pi-subagents/test/unit/child-session-host-sdk.test.ts +226 -0
  656. package/dist/extensions/pi-subagents/test/unit/child-session-parent-providers.test.ts +200 -0
  657. package/dist/extensions/pi-subagents/test/unit/child-session-queued.test.ts +260 -0
  658. package/dist/extensions/pi-subagents/test/unit/child-tool-plan-permission-system.test.ts +3 -98
  659. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +222 -1
  660. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +314 -0
  661. package/dist/extensions/pi-subagents/test/unit/claude-code-dispatch.test.ts +182 -0
  662. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +157 -0
  663. package/dist/extensions/pi-subagents/test/unit/command-action.test.ts +50 -0
  664. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +25 -11
  665. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +3 -1
  666. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +18 -0
  667. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +7 -1
  668. package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +220 -0
  669. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +104 -32
  670. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +138 -1
  671. package/dist/extensions/pi-subagents/test/unit/disabled-features.test.ts +227 -0
  672. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +48 -9
  673. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +7 -4
  674. package/dist/extensions/pi-subagents/test/unit/extension-lazy-loading.test.ts +21 -0
  675. package/dist/extensions/pi-subagents/test/unit/extension-maintenance-lifecycle.test.ts +66 -0
  676. package/dist/extensions/pi-subagents/test/unit/extension-module-preload.test.ts +49 -0
  677. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +104 -3
  678. package/dist/extensions/pi-subagents/test/unit/file-lease.test.ts +17 -0
  679. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +316 -0
  680. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +58 -7
  681. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +25 -96
  682. package/dist/extensions/pi-subagents/test/unit/ghostty-inspector.test.ts +92 -0
  683. package/dist/extensions/pi-subagents/test/unit/global-npm-root.test.ts +108 -0
  684. package/dist/extensions/pi-subagents/test/unit/herdr-connection.test.ts +43 -0
  685. package/dist/extensions/pi-subagents/test/unit/herdr-external-adapters.test.ts +89 -0
  686. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +11 -20
  687. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +113 -19
  688. package/dist/extensions/pi-subagents/test/unit/herdr-machine.test.ts +161 -0
  689. package/dist/extensions/pi-subagents/test/unit/herdr-native-placement.test.ts +187 -0
  690. package/dist/extensions/pi-subagents/test/unit/herdr-session-roots-codec.test.ts +7 -12
  691. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +2 -2
  692. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +182 -87
  693. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +68 -49
  694. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +295 -135
  695. package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +3 -3
  696. package/dist/extensions/pi-subagents/test/unit/inspector-authority-policy.test.ts +144 -0
  697. package/dist/extensions/pi-subagents/test/unit/inspector-registration.test.ts +128 -0
  698. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +49 -1
  699. package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +74 -1
  700. package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-resolution.test.ts +112 -0
  701. package/dist/extensions/pi-subagents/test/unit/mission-global-projection.test.ts +215 -0
  702. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +65 -1
  703. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +2 -1
  704. package/dist/extensions/pi-subagents/test/unit/model-resolution-diagnostic.test.ts +78 -0
  705. package/dist/extensions/pi-subagents/test/unit/model-resolution.test.ts +153 -0
  706. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +43 -1
  707. package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +1 -21
  708. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +279 -2
  709. package/dist/extensions/pi-subagents/test/unit/nested-control-fanout.test.ts +73 -0
  710. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +297 -3
  711. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +1 -1
  712. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +431 -23
  713. package/dist/extensions/pi-subagents/test/unit/opencode-session-headers.test.ts +32 -0
  714. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +89 -21
  715. package/dist/extensions/pi-subagents/test/unit/owned-process-tree.test.ts +31 -1
  716. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +18 -24
  717. package/dist/extensions/pi-subagents/test/unit/parent-wake.test.ts +206 -0
  718. package/dist/extensions/pi-subagents/test/unit/partial-output.test.ts +53 -0
  719. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +216 -32
  720. package/dist/extensions/pi-subagents/test/unit/pi-spawn-host-root.test.ts +82 -0
  721. package/dist/extensions/pi-subagents/test/unit/pi-subagents-policy-guidance.test.ts +28 -0
  722. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +193 -104
  723. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +16 -1
  724. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +17 -1
  725. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +89 -9
  726. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +6 -0
  727. package/dist/extensions/pi-subagents/test/unit/prompt-audit-provider-routing.test.ts +69 -0
  728. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +2 -2
  729. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +23 -1
  730. package/dist/extensions/pi-subagents/test/unit/pruned-fork.test.ts +117 -3
  731. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +35 -14
  732. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +3 -2
  733. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +261 -9
  734. package/dist/extensions/pi-subagents/test/unit/reply-workflow-script.test.ts +37 -0
  735. package/dist/extensions/pi-subagents/test/unit/required-child-extensions.test.ts +114 -0
  736. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +11 -1
  737. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +22 -2
  738. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +125 -18
  739. package/dist/extensions/pi-subagents/test/unit/run-child-session.test.ts +230 -0
  740. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +50 -1
  741. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +301 -4
  742. package/dist/extensions/pi-subagents/test/unit/runner-bootstrap.test.ts +182 -0
  743. package/dist/extensions/pi-subagents/test/unit/runner-http-dispatcher.test.ts +136 -0
  744. package/dist/extensions/pi-subagents/test/unit/runner-peer-loader.test.ts +68 -0
  745. package/dist/extensions/pi-subagents/test/unit/runner-peer-symlinks.test.ts +50 -0
  746. package/dist/extensions/pi-subagents/test/unit/running-tone.test.ts +13 -0
  747. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +155 -4
  748. package/dist/extensions/pi-subagents/test/unit/schedule-claim-initialization.test.ts +71 -0
  749. package/dist/extensions/pi-subagents/test/unit/schedule-claim-preservation.test.ts +98 -0
  750. package/dist/extensions/pi-subagents/test/unit/schedule-history-transactions.test.ts +201 -0
  751. package/dist/extensions/pi-subagents/test/unit/scheduled-mission.test.ts +317 -0
  752. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +412 -11
  753. package/dist/extensions/pi-subagents/test/unit/scheduled-store-root.test.ts +2 -8
  754. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +70 -71
  755. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +748 -5
  756. package/dist/extensions/pi-subagents/test/unit/session-liveness-delivery.test.ts +337 -0
  757. package/dist/extensions/pi-subagents/test/unit/settings-atomic-save.test.ts +419 -0
  758. package/dist/extensions/pi-subagents/test/unit/settings-transactions.test.ts +88 -0
  759. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +13 -1
  760. package/dist/extensions/pi-subagents/test/unit/skills-disable-model-invocation.test.ts +164 -0
  761. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +35 -2
  762. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +2 -2
  763. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +334 -9
  764. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +170 -0
  765. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +34 -5
  766. package/dist/extensions/pi-subagents/test/unit/structured-output-validation.test.ts +199 -0
  767. package/dist/extensions/pi-subagents/test/unit/structured-workflow-resources.test.ts +241 -0
  768. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +47 -38
  769. package/dist/extensions/pi-subagents/test/unit/subagent-cost.test.ts +97 -0
  770. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +10 -0
  771. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +343 -44
  772. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +410 -11
  773. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +459 -22
  774. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +43 -3
  775. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +11 -1
  776. package/dist/extensions/pi-subagents/test/unit/tool-activation-stale-peer.test.ts +32 -0
  777. package/dist/extensions/pi-subagents/test/unit/tool-activation.test.ts +313 -0
  778. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +14 -0
  779. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +76 -89
  780. package/dist/extensions/pi-subagents/test/unit/typed-gate-output.test.ts +141 -0
  781. package/dist/extensions/pi-subagents/test/unit/upgrade-notice.test.ts +31 -0
  782. package/dist/extensions/pi-subagents/test/unit/usage-reconciliation-owners.test.ts +66 -0
  783. package/dist/extensions/pi-subagents/test/unit/usage-reconciliation.test.ts +23 -0
  784. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +195 -1
  785. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +3 -1
  786. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +48 -45
  787. package/dist/extensions/pi-subagents/test/unit/watchdog-compaction-scope.test.ts +94 -0
  788. package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +48 -3
  789. package/dist/extensions/pi-subagents/test/unit/watchdog-native-scheduling.test.ts +107 -0
  790. package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +95 -7
  791. package/dist/extensions/pi-subagents/test/unit/watchdog-register-child.test.ts +79 -0
  792. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -4
  793. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +111 -24
  794. package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +1 -1
  795. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +283 -2
  796. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +3 -0
  797. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +12 -1
  798. package/dist/extensions/pi-subagents/test/unit/watchdog-stable-host-layout.test.ts +23 -0
  799. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +30 -0
  800. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +39 -2
  801. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +18 -1
  802. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +8 -4
  803. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +10 -0
  804. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +6 -3
  805. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +44 -1
  806. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +28 -1
  807. package/dist/extensions/pi-subagents/test/unit/workflow-revival.test.ts +85 -0
  808. package/dist/extensions/pi-subagents/test/unit/workflow-scripts-feature.test.ts +177 -0
  809. package/dist/extensions/pi-subagents/test/unit/workflow-terminal-proof.test.ts +8 -0
  810. package/dist/extensions/pi-subagents/test/unit/workflow-worktree-admission.test.ts +29 -0
  811. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +343 -4
  812. package/dist/extensions/pi-subagents/test/unit/worktree-lock.test.ts +123 -0
  813. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +252 -22
  814. package/dist/extensions/pi-subagents/tsconfig.build.json +13 -0
  815. package/dist/extensions/pi-tool-display/tests/custom-tool-overrides.test.ts +1 -1
  816. package/dist/extensions/pi-tool-display/tests/tool-overrides-config.test.ts +1 -1
  817. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +24 -1
  818. package/dist/extensions/pi-tool-display/tests/tool-overrides-registration.test.ts +6 -2
  819. package/dist/extensions/pi-web-agent/src/extension.ts +1 -5
  820. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +67 -4
  821. package/dist/extensions/pi-zentui/AGENTS.md +3 -2
  822. package/dist/extensions/pi-zentui/README.md +60 -32
  823. package/dist/extensions/pi-zentui/docs/configuration.md +662 -51
  824. package/dist/extensions/pi-zentui/docs/footer-format.md +174 -5
  825. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +15 -4
  826. package/dist/extensions/pi-zentui/extensions/zentui/average-token-rate.ts +146 -0
  827. package/dist/extensions/pi-zentui/extensions/zentui/codex-quota-display.ts +34 -0
  828. package/dist/extensions/pi-zentui/extensions/zentui/codex-quota.ts +298 -0
  829. package/dist/extensions/pi-zentui/extensions/zentui/component-colors.ts +172 -0
  830. package/dist/extensions/pi-zentui/extensions/zentui/component-settings.ts +243 -0
  831. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +851 -114
  832. package/dist/extensions/pi-zentui/extensions/zentui/custom-value-colors.ts +93 -0
  833. package/dist/extensions/pi-zentui/extensions/zentui/custom-variable-demand.ts +61 -0
  834. package/dist/extensions/pi-zentui/extensions/zentui/custom-variable-format.ts +80 -0
  835. package/dist/extensions/pi-zentui/extensions/zentui/custom-variables.ts +152 -0
  836. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +138 -23
  837. package/dist/extensions/pi-zentui/extensions/zentui/editor-mouse.ts +89 -0
  838. package/dist/extensions/pi-zentui/extensions/zentui/extension-status-controller.ts +148 -0
  839. package/dist/extensions/pi-zentui/extensions/zentui/extension-status-settings.ts +40 -0
  840. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +109 -1
  841. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +203 -23
  842. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +80 -3
  843. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +1 -1
  844. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +432 -125
  845. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +92 -4
  846. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +47 -30
  847. package/dist/extensions/pi-zentui/extensions/zentui/github-status.ts +575 -0
  848. package/dist/extensions/pi-zentui/extensions/zentui/host-project-changes.ts +25 -0
  849. package/dist/extensions/pi-zentui/extensions/zentui/host-template-values.ts +29 -0
  850. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +53 -13
  851. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +778 -258
  852. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +70 -5
  853. package/dist/extensions/pi-zentui/extensions/zentui/layered-editor.ts +55 -0
  854. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +6 -1
  855. package/dist/extensions/pi-zentui/extensions/zentui/live-metadata-demand.ts +72 -0
  856. package/dist/extensions/pi-zentui/extensions/zentui/live-metadata-display.ts +21 -0
  857. package/dist/extensions/pi-zentui/extensions/zentui/live-metadata.ts +255 -0
  858. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +476 -36
  859. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-settings.ts +118 -0
  860. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-template.ts +177 -0
  861. package/dist/extensions/pi-zentui/extensions/zentui/omp-editor-frame.ts +148 -0
  862. package/dist/extensions/pi-zentui/extensions/zentui/omp-statusline.ts +569 -0
  863. package/dist/extensions/pi-zentui/extensions/zentui/omp-template-lookups.ts +344 -0
  864. package/dist/extensions/pi-zentui/extensions/zentui/omp-template-metrics.ts +91 -0
  865. package/dist/extensions/pi-zentui/extensions/zentui/omp-template-modes.ts +323 -0
  866. package/dist/extensions/pi-zentui/extensions/zentui/omp-ui.ts +210 -0
  867. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +30 -12
  868. package/dist/extensions/pi-zentui/extensions/zentui/project-demand.ts +46 -0
  869. package/dist/extensions/pi-zentui/extensions/zentui/project-discovery.ts +58 -0
  870. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +10 -3
  871. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +37 -0
  872. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +1 -0
  873. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +119 -43
  874. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +40 -29
  875. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1268 -722
  876. package/dist/extensions/pi-zentui/extensions/zentui/settings-keys.ts +59 -0
  877. package/dist/extensions/pi-zentui/extensions/zentui/settings-list-selection.ts +13 -0
  878. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +11 -4
  879. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +24 -11
  880. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +6 -0
  881. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +113 -11
  882. package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +234 -46
  883. package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +20 -5
  884. package/dist/extensions/pi-zentui/extensions/zentui/token-rate.ts +313 -0
  885. package/dist/extensions/pi-zentui/extensions/zentui/turn-summary-format.ts +59 -0
  886. package/dist/extensions/pi-zentui/extensions/zentui/turn-summary-rate.ts +52 -0
  887. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +640 -172
  888. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +72 -34
  889. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +219 -34
  890. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +495 -119
  891. package/dist/extensions/pi-zentui/omp.ts +157 -0
  892. package/dist/extensions/pi-zentui/package-lock.json +3762 -1507
  893. package/dist/extensions/pi-zentui/package.json +50 -12
  894. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +13 -2
  895. package/dist/extensions/pi-zentui/test/average-token-rate.test.ts +297 -0
  896. package/dist/extensions/pi-zentui/test/behavior-compatibility.mjs +89 -0
  897. package/dist/extensions/pi-zentui/test/codex-quota-lifecycle.test.ts +284 -0
  898. package/dist/extensions/pi-zentui/test/codex-quota-rendering.test.ts +466 -0
  899. package/dist/extensions/pi-zentui/test/codex-quota.test.ts +440 -0
  900. package/dist/extensions/pi-zentui/test/component-colors.test.ts +554 -0
  901. package/dist/extensions/pi-zentui/test/component-settings.test.ts +364 -0
  902. package/dist/extensions/pi-zentui/test/config-component-independence.test.ts +402 -0
  903. package/dist/extensions/pi-zentui/test/config-extension-status-choice.test.ts +233 -0
  904. package/dist/extensions/pi-zentui/test/config-hidden-extension-status-colors.test.ts +118 -0
  905. package/dist/extensions/pi-zentui/test/config-hidden-extension-status-placement.test.ts +142 -0
  906. package/dist/extensions/pi-zentui/test/config-minimalist-templates.test.ts +240 -0
  907. package/dist/extensions/pi-zentui/test/config-template-variables.test.ts +85 -0
  908. package/dist/extensions/pi-zentui/test/config.test.ts +80 -17
  909. package/dist/extensions/pi-zentui/test/custom-value-colors-integration.test.ts +453 -0
  910. package/dist/extensions/pi-zentui/test/custom-value-colors.test.ts +399 -0
  911. package/dist/extensions/pi-zentui/test/custom-variable-alias-prototypes.test.ts +177 -0
  912. package/dist/extensions/pi-zentui/test/custom-variable-consumers.test.ts +194 -0
  913. package/dist/extensions/pi-zentui/test/custom-variable-demand.test.ts +169 -0
  914. package/dist/extensions/pi-zentui/test/custom-variable-probe-reservation.test.ts +141 -0
  915. package/dist/extensions/pi-zentui/test/custom-variables-lifecycle.integration.test.ts +365 -0
  916. package/dist/extensions/pi-zentui/test/custom-variables.test.ts +330 -0
  917. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +121 -0
  918. package/dist/extensions/pi-zentui/test/editor-mouse.test.ts +507 -0
  919. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +247 -20
  920. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +204 -260
  921. package/dist/extensions/pi-zentui/test/extension-status-controller.test.ts +323 -0
  922. package/dist/extensions/pi-zentui/test/extension-status-lifecycle.integration.test.ts +466 -0
  923. package/dist/extensions/pi-zentui/test/extension-status-settings.test.ts +79 -0
  924. package/dist/extensions/pi-zentui/test/footer-alignment.test.ts +135 -0
  925. package/dist/extensions/pi-zentui/test/footer-authority.test.ts +69 -0
  926. package/dist/extensions/pi-zentui/test/footer-auto-compaction.test.ts +148 -0
  927. package/dist/extensions/pi-zentui/test/footer-format-cache.test.ts +186 -0
  928. package/dist/extensions/pi-zentui/test/footer-format.test.ts +154 -4
  929. package/dist/extensions/pi-zentui/test/footer-hidden.test.ts +312 -0
  930. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +22 -0
  931. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +80 -0
  932. package/dist/extensions/pi-zentui/test/footer-thinking-level.test.ts +123 -0
  933. package/dist/extensions/pi-zentui/test/format.test.ts +75 -1
  934. package/dist/extensions/pi-zentui/test/git-probes.test.ts +182 -0
  935. package/dist/extensions/pi-zentui/test/github-status.test.ts +931 -0
  936. package/dist/extensions/pi-zentui/test/host-template-surfaces.test.ts +287 -0
  937. package/dist/extensions/pi-zentui/test/icons.test.ts +86 -16
  938. package/dist/extensions/pi-zentui/test/layered-editor.test.ts +53 -0
  939. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +154 -7
  940. package/dist/extensions/pi-zentui/test/live-context.test.ts +45 -0
  941. package/dist/extensions/pi-zentui/test/live-metadata-lifecycle.integration.test.ts +963 -0
  942. package/dist/extensions/pi-zentui/test/live-metadata.test.ts +767 -0
  943. package/dist/extensions/pi-zentui/test/minimalist-cache-hit-colors.test.ts +183 -0
  944. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +222 -4
  945. package/dist/extensions/pi-zentui/test/minimalist-settings.test.ts +103 -0
  946. package/dist/extensions/pi-zentui/test/minimalist-templates.test.ts +489 -0
  947. package/dist/extensions/pi-zentui/test/omp-editor-composers.test.ts +324 -0
  948. package/dist/extensions/pi-zentui/test/omp-statusline.test.ts +579 -0
  949. package/dist/extensions/pi-zentui/test/omp-template-lookups.test.ts +630 -0
  950. package/dist/extensions/pi-zentui/test/omp-template-metrics.test.ts +72 -0
  951. package/dist/extensions/pi-zentui/test/omp-template-modes.test.ts +557 -0
  952. package/dist/extensions/pi-zentui/test/omp-ui.test.ts +93 -0
  953. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  954. package/dist/extensions/pi-zentui/test/pi-version-policy.test.ts +37 -0
  955. package/dist/extensions/pi-zentui/test/project-demand-integration.test.ts +542 -0
  956. package/dist/extensions/pi-zentui/test/project-discovery.test.ts +412 -0
  957. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +44 -0
  958. package/dist/extensions/pi-zentui/test/project-state.test.ts +38 -1
  959. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +7 -7
  960. package/dist/extensions/pi-zentui/test/runtime.test.ts +127 -2
  961. package/dist/extensions/pi-zentui/test/selesai-v030-gate.json +939 -0
  962. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1330 -82
  963. package/dist/extensions/pi-zentui/test/settings-keys.test.ts +41 -0
  964. package/dist/extensions/pi-zentui/test/settings-list-selection.test.ts +88 -0
  965. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +4 -1
  966. package/dist/extensions/pi-zentui/test/state.test.ts +15 -1
  967. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +214 -12
  968. package/dist/extensions/pi-zentui/test/telemetry-snapshot.test.ts +246 -0
  969. package/dist/extensions/pi-zentui/test/thinking-experimental-cache.test.ts +246 -0
  970. package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +22 -0
  971. package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +113 -51
  972. package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +576 -76
  973. package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +23 -0
  974. package/dist/extensions/pi-zentui/test/token-rate.test.ts +450 -0
  975. package/dist/extensions/pi-zentui/test/turn-summary-format.test.ts +207 -0
  976. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +72 -5
  977. package/dist/extensions/pi-zentui/test/turn-summary-rate.test.ts +158 -0
  978. package/dist/extensions/pi-zentui/test/usage-lifecycle-integration.test.ts +421 -0
  979. package/dist/extensions/pi-zentui/test/usage-snapshot.test.ts +154 -0
  980. package/dist/extensions/pi-zentui/test/user-message-cache.test.ts +239 -0
  981. package/dist/extensions/pi-zentui/test/user-message-native-reduced-capabilities.test.ts +61 -0
  982. package/dist/extensions/pi-zentui/test/user-message-native.test.ts +447 -0
  983. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +2 -2
  984. package/dist/extensions/pi-zentui/test/working-line-editor-placement.test.ts +317 -0
  985. package/dist/extensions/pi-zentui/test/working-line-generation-cache.test.ts +238 -0
  986. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +674 -179
  987. package/dist/extensions/pi-zentui/test/working-line-message-ownership.test.ts +239 -0
  988. package/dist/extensions/pi-zentui/test/working-line-metric-animation.test.ts +284 -0
  989. package/dist/extensions/pi-zentui/test/working-line-placement.test.ts +298 -0
  990. package/dist/extensions/pi-zentui/test/working-line.test.ts +386 -18
  991. package/dist/extensions/pi-zentui/test/wrapped-editor-consumers.integration.test.ts +579 -0
  992. package/dist/extensions/pi-zentui/test/wrapped-editor-usage.integration.test.ts +298 -0
  993. package/dist/extensions/pi-zentui/tsconfig.json +1 -1
  994. package/dist/extensions/pi-zentui/vitest.host.config.ts +33 -0
  995. package/dist/extensions/question/index.ts +1 -5
  996. package/dist/extensions/rtk.test.ts +28 -15
  997. package/dist/extensions/rtk.ts +22 -22
  998. package/dist/extensions/skills-index/index.test.ts +99 -0
  999. package/dist/extensions/skills-index/index.ts +105 -0
  1000. package/dist/extensions/skills-index/package.json +9 -0
  1001. package/dist/extensions/tokenin-onboarding.ts +48 -208
  1002. package/dist/extensions/tool-search/index.d.ts +11 -0
  1003. package/dist/extensions/tool-search/index.js +14 -0
  1004. package/dist/extensions/tool-search/tool.d.ts +91 -0
  1005. package/dist/extensions/tool-search/tool.js +214 -0
  1006. package/dist/index.d.ts +17 -9
  1007. package/dist/index.js +11 -5
  1008. package/dist/main.d.ts +4 -0
  1009. package/dist/main.js +106 -42
  1010. package/dist/migrations.js +4 -3
  1011. package/dist/modes/index.d.ts +1 -0
  1012. package/dist/modes/interactive/bug-report.js +4 -0
  1013. package/dist/modes/interactive/components/armin.d.ts +3 -0
  1014. package/dist/modes/interactive/components/armin.js +7 -5
  1015. package/dist/modes/interactive/components/assistant-message.js +1 -1
  1016. package/dist/modes/interactive/components/auth-url.d.ts +13 -0
  1017. package/dist/modes/interactive/components/auth-url.js +36 -0
  1018. package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
  1019. package/dist/modes/interactive/components/bash-execution.js +17 -14
  1020. package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
  1021. package/dist/modes/interactive/components/branch-summary-message.js +5 -2
  1022. package/dist/modes/interactive/components/compaction-summary-message.d.ts +2 -1
  1023. package/dist/modes/interactive/components/compaction-summary-message.js +5 -2
  1024. package/dist/modes/interactive/components/config-selector.js +13 -3
  1025. package/dist/modes/interactive/components/custom-editor.js +7 -0
  1026. package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
  1027. package/dist/modes/interactive/components/custom-entry.js +8 -2
  1028. package/dist/modes/interactive/components/custom-message.js +1 -0
  1029. package/dist/modes/interactive/components/first-time-setup.d.ts +4 -4
  1030. package/dist/modes/interactive/components/first-time-setup.js +8 -2
  1031. package/dist/modes/interactive/components/footer.d.ts +7 -0
  1032. package/dist/modes/interactive/components/footer.js +44 -4
  1033. package/dist/modes/interactive/components/index.d.ts +0 -1
  1034. package/dist/modes/interactive/components/index.js +0 -1
  1035. package/dist/modes/interactive/components/login-dialog.d.ts +2 -0
  1036. package/dist/modes/interactive/components/login-dialog.js +11 -5
  1037. package/dist/modes/interactive/components/mermaid.d.ts +10 -0
  1038. package/dist/modes/interactive/components/mermaid.js +74 -0
  1039. package/dist/modes/interactive/components/model-selector.d.ts +14 -9
  1040. package/dist/modes/interactive/components/model-selector.js +79 -31
  1041. package/dist/modes/interactive/components/oauth-selector.d.ts +8 -2
  1042. package/dist/modes/interactive/components/oauth-selector.js +24 -21
  1043. package/dist/modes/interactive/components/pi-logo.d.ts +18 -0
  1044. package/dist/modes/interactive/components/pi-logo.js +36 -0
  1045. package/dist/modes/interactive/components/radius-login-selector.d.ts +14 -0
  1046. package/dist/modes/interactive/components/radius-login-selector.js +84 -0
  1047. package/dist/modes/interactive/components/scoped-models-selector.d.ts +4 -0
  1048. package/dist/modes/interactive/components/scoped-models-selector.js +26 -0
  1049. package/dist/modes/interactive/components/settings-selector.d.ts +8 -11
  1050. package/dist/modes/interactive/components/settings-selector.js +60 -102
  1051. package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
  1052. package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
  1053. package/dist/modes/interactive/components/themed-text.d.ts +16 -0
  1054. package/dist/modes/interactive/components/themed-text.js +28 -0
  1055. package/dist/modes/interactive/components/tool-execution.d.ts +11 -18
  1056. package/dist/modes/interactive/components/tool-execution.js +34 -47
  1057. package/dist/modes/interactive/components/tree-selector.js +7 -0
  1058. package/dist/modes/interactive/components/user-message.d.ts +3 -1
  1059. package/dist/modes/interactive/components/user-message.js +14 -6
  1060. package/dist/modes/interactive/components/visual-truncate.d.ts +27 -3
  1061. package/dist/modes/interactive/components/visual-truncate.js +39 -6
  1062. package/dist/modes/interactive/external-editor.js +2 -1
  1063. package/dist/modes/interactive/interactive-mode.d.ts +41 -8
  1064. package/dist/modes/interactive/interactive-mode.js +854 -492
  1065. package/dist/modes/interactive/program-status-reporter.d.ts +33 -0
  1066. package/dist/modes/interactive/program-status-reporter.js +109 -0
  1067. package/dist/modes/interactive/theme/dark.json +59 -72
  1068. package/dist/modes/interactive/theme/light.json +57 -69
  1069. package/dist/modes/interactive/theme/system-theme.d.ts +54 -0
  1070. package/dist/modes/interactive/theme/system-theme.js +545 -0
  1071. package/dist/modes/interactive/theme/theme-controller.d.ts +46 -6
  1072. package/dist/modes/interactive/theme/theme-controller.js +127 -47
  1073. package/dist/modes/interactive/theme/theme-json.d.ts +1 -0
  1074. package/dist/modes/interactive/theme/theme-json.js +3 -1
  1075. package/dist/modes/interactive/theme/theme-schema.json +7 -2
  1076. package/dist/modes/interactive/theme/theme.d.ts +66 -39
  1077. package/dist/modes/interactive/theme/theme.js +243 -320
  1078. package/dist/modes/interactive/tui-renderer.d.ts +2 -1
  1079. package/dist/modes/interactive/tui-renderer.js +1 -0
  1080. package/dist/modes/json-event.d.ts +33 -0
  1081. package/dist/modes/json-event.js +28 -0
  1082. package/dist/modes/print-mode.js +12 -2
  1083. package/dist/modes/rpc/rpc-client.d.ts +11 -19
  1084. package/dist/modes/rpc/rpc-client.js +52 -35
  1085. package/dist/modes/rpc/rpc-mode.js +11 -26
  1086. package/dist/modes/rpc/rpc-types.d.ts +12 -23
  1087. package/dist/package-manager-cli.js +16 -5
  1088. package/dist/rpc-entry.js +1 -0
  1089. package/dist/skills/pi-subagents/SKILL.md +109 -35
  1090. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +38 -238
  1091. package/dist/skills/pi-subagents/references/execution-controls.md +181 -83
  1092. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +9 -9
  1093. package/dist/skills/pi-subagents/references/multi-lane-orchestration.md +24 -2
  1094. package/dist/skills/pi-subagents/references/prompting-and-roles.md +121 -91
  1095. package/dist/skills/pi-subagents/references/review-and-validation.md +73 -0
  1096. package/dist/utils/ansi.d.ts +8 -0
  1097. package/dist/utils/ansi.js +30 -11
  1098. package/dist/utils/clipboard-image.js +14 -17
  1099. package/dist/utils/clipboard.d.ts +2 -0
  1100. package/dist/utils/clipboard.js +17 -11
  1101. package/dist/utils/frontmatter.js +2 -1
  1102. package/dist/utils/image-convert.d.ts +12 -0
  1103. package/dist/utils/image-convert.js +45 -6
  1104. package/dist/utils/image-resize-core.d.ts +12 -0
  1105. package/dist/utils/image-resize-core.js +5 -0
  1106. package/dist/utils/image-resize-worker.js +3 -2
  1107. package/dist/utils/image-resize.js +9 -10
  1108. package/dist/utils/mime.js +1 -1
  1109. package/dist/utils/output-files.d.ts +14 -0
  1110. package/dist/utils/output-files.js +29 -0
  1111. package/dist/utils/paths.d.ts +6 -3
  1112. package/dist/utils/paths.js +28 -5
  1113. package/dist/utils/shell.d.ts +1 -3
  1114. package/dist/utils/shell.js +3 -30
  1115. package/dist/utils/syntax-highlight.d.ts +1 -0
  1116. package/dist/utils/syntax-highlight.js +68 -2
  1117. package/dist/utils/tool-result-images.d.ts +20 -0
  1118. package/dist/utils/tool-result-images.js +47 -0
  1119. package/dist/utils/tools-manager.d.ts +5 -0
  1120. package/dist/utils/tools-manager.js +6 -6
  1121. package/dist/utils/version-check.d.ts +4 -0
  1122. package/dist/utils/version-check.js +20 -2
  1123. package/docs/cli-integration.md +104 -0
  1124. package/docs/cli.md +333 -0
  1125. package/docs/codemode.md +230 -0
  1126. package/docs/configuration.md +47 -0
  1127. package/docs/environment-variables.md +5 -0
  1128. package/docs/extensions.md +107 -30
  1129. package/docs/json.md +8 -4
  1130. package/docs/keybindings.md +10 -10
  1131. package/docs/mcp.md +262 -0
  1132. package/docs/models.md +104 -3
  1133. package/docs/providers.md +20 -2
  1134. package/docs/rpc.md +13 -12
  1135. package/docs/sdk.md +41 -33
  1136. package/docs/security.md +21 -20
  1137. package/docs/settings.md +18 -46
  1138. package/docs/terminal-setup.md +14 -0
  1139. package/docs/virtual-models.md +114 -0
  1140. package/examples/extensions/jev-router.ts +113 -0
  1141. package/examples/extensions/tokenin-usage-widget.ts +124 -0
  1142. package/examples/rpc-client.ts +35 -0
  1143. package/examples/sdk/14-codemode-mcp.ts +67 -0
  1144. package/package.json +15 -8
  1145. package/dist/core/agent-session-auto-handoff.test.js +0 -195
  1146. package/dist/core/built-in-extensions.d.ts +0 -2
  1147. package/dist/core/built-in-extensions.js +0 -2
  1148. package/dist/core/llama/provider.js +0 -143
  1149. package/dist/core/settings-manager-auto-handoff.test.d.ts +0 -1
  1150. package/dist/core/settings-manager-auto-handoff.test.js +0 -53
  1151. package/dist/extensions/capability-gateway/catalog.ts +0 -177
  1152. package/dist/extensions/capability-gateway/index.test.ts +0 -226
  1153. package/dist/extensions/capability-gateway/index.ts +0 -669
  1154. package/dist/extensions/capability-gateway/integration.test.ts +0 -803
  1155. package/dist/extensions/capability-gateway/package.json +0 -16
  1156. package/dist/extensions/capability-gateway/routing.test.ts +0 -574
  1157. package/dist/extensions/capability-gateway/routing.ts +0 -403
  1158. package/dist/extensions/pi-subagents/runner-server-preload.mjs +0 -13
  1159. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +0 -89
  1160. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +0 -259
  1161. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +0 -312
  1162. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +0 -374
  1163. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +0 -69
  1164. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +0 -307
  1165. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +0 -211
  1166. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/cli.d.ts +0 -3
  1167. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/register-bedrock.d.ts +0 -2
  1168. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/bun/restore-sandbox-env.d.ts +0 -17
  1169. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/args.d.ts +0 -57
  1170. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/config-selector.d.ts +0 -16
  1171. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/file-processor.d.ts +0 -15
  1172. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/initial-message.d.ts +0 -18
  1173. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/list-models.d.ts +0 -9
  1174. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/project-trust.d.ts +0 -10
  1175. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/session-picker.d.ts +0 -10
  1176. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli/startup-ui.d.ts +0 -20
  1177. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/cli.d.ts +0 -3
  1178. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/config.d.ts +0 -96
  1179. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-runtime.d.ts +0 -119
  1180. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session-services.d.ts +0 -85
  1181. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/agent-session.d.ts +0 -620
  1182. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-guidance.d.ts +0 -5
  1183. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/auth-storage.d.ts +0 -56
  1184. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/bash-executor.d.ts +0 -32
  1185. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/cache-stats.d.ts +0 -49
  1186. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/branch-summarization.d.ts +0 -93
  1187. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/compaction.d.ts +0 -128
  1188. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/index.d.ts +0 -7
  1189. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/compaction/utils.d.ts +0 -38
  1190. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/defaults.d.ts +0 -3
  1191. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/diagnostics.d.ts +0 -15
  1192. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/event-bus.d.ts +0 -9
  1193. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/exec.d.ts +0 -29
  1194. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/experimental.d.ts +0 -2
  1195. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/ansi-to-html.d.ts +0 -22
  1196. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/index.d.ts +0 -37
  1197. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/export-html/tool-renderer.d.ts +0 -34
  1198. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/index.d.ts +0 -12
  1199. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/loader.d.ts +0 -23
  1200. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/runner.d.ts +0 -171
  1201. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +0 -1261
  1202. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/wrapper.d.ts +0 -20
  1203. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/footer-data-provider.d.ts +0 -54
  1204. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/http-dispatcher.d.ts +0 -22
  1205. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/index.d.ts +0 -13
  1206. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/keybindings.d.ts +0 -358
  1207. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/messages.d.ts +0 -77
  1208. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-config.d.ts +0 -512
  1209. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-registry.d.ts +0 -43
  1210. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-resolver.d.ts +0 -121
  1211. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/model-runtime.d.ts +0 -82
  1212. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/models-store.d.ts +0 -17
  1213. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/output-guard.d.ts +0 -7
  1214. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/package-manager.d.ts +0 -210
  1215. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/project-trust.d.ts +0 -15
  1216. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/prompt-templates.d.ts +0 -54
  1217. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-attribution.d.ts +0 -4
  1218. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/provider-composer.d.ts +0 -55
  1219. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/radius.d.ts +0 -2
  1220. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/remote-catalog-provider.d.ts +0 -5
  1221. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resolve-config-value.d.ts +0 -30
  1222. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/resource-loader.d.ts +0 -206
  1223. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/runtime-credentials.d.ts +0 -15
  1224. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/sdk.d.ts +0 -106
  1225. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-cwd.d.ts +0 -19
  1226. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/session-manager.d.ts +0 -356
  1227. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/settings-manager.d.ts +0 -297
  1228. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/skills.d.ts +0 -60
  1229. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/slash-commands.d.ts +0 -15
  1230. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/source-info.d.ts +0 -18
  1231. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/system-prompt.d.ts +0 -28
  1232. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/telemetry.d.ts +0 -3
  1233. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/timings.d.ts +0 -10
  1234. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/bash.d.ts +0 -68
  1235. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit-diff.d.ts +0 -106
  1236. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/edit.d.ts +0 -51
  1237. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/file-mutation-queue.d.ts +0 -6
  1238. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/find.d.ts +0 -35
  1239. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/grep.d.ts +0 -37
  1240. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/index.d.ts +0 -40
  1241. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/ls.d.ts +0 -37
  1242. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/output-accumulator.d.ts +0 -52
  1243. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/path-utils.d.ts +0 -10
  1244. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/read.d.ts +0 -35
  1245. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/render-utils.d.ts +0 -24
  1246. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/tool-definition-wrapper.d.ts +0 -14
  1247. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/truncate.d.ts +0 -70
  1248. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/tools/write.d.ts +0 -26
  1249. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/trust-manager.d.ts +0 -36
  1250. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/usage-totals.d.ts +0 -19
  1251. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/client.d.ts +0 -61
  1252. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/huggingface.d.ts +0 -23
  1253. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/index.d.ts +0 -3
  1254. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/provider.d.ts +0 -10
  1255. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/extensions/llama/ui.d.ts +0 -42
  1256. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/index.d.ts +0 -35
  1257. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/main.d.ts +0 -12
  1258. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/migrations.d.ts +0 -33
  1259. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/index.d.ts +0 -9
  1260. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/armin.d.ts +0 -34
  1261. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/assistant-message.d.ts +0 -22
  1262. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bash-execution.d.ts +0 -34
  1263. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/bordered-loader.d.ts +0 -16
  1264. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/branch-summary-message.d.ts +0 -16
  1265. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/compaction-summary-message.d.ts +0 -16
  1266. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/config-selector.d.ts +0 -102
  1267. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/countdown-timer.d.ts +0 -14
  1268. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-editor.d.ts +0 -21
  1269. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-entry.d.ts +0 -19
  1270. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/custom-message.d.ts +0 -20
  1271. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/daxnuts.d.ts +0 -23
  1272. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/diff.d.ts +0 -12
  1273. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/dynamic-border.d.ts +0 -15
  1274. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/earendil-announcement.d.ts +0 -5
  1275. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-editor.d.ts +0 -22
  1276. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-input.d.ts +0 -23
  1277. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/extension-selector.d.ts +0 -26
  1278. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/first-time-setup.d.ts +0 -25
  1279. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/footer.d.ts +0 -32
  1280. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/index.d.ts +0 -34
  1281. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/keybinding-hints.d.ts +0 -13
  1282. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/login-dialog.d.ts +0 -52
  1283. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/model-selector.d.ts +0 -54
  1284. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/oauth-selector.d.ts +0 -33
  1285. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/scoped-models-selector.d.ts +0 -42
  1286. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector-search.d.ts +0 -23
  1287. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/session-selector.d.ts +0 -95
  1288. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/settings-selector.d.ts +0 -77
  1289. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/show-images-selector.d.ts +0 -10
  1290. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/skill-invocation-message.d.ts +0 -17
  1291. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/status-indicator.d.ts +0 -28
  1292. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/theme-selector.d.ts +0 -11
  1293. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/thinking-selector.d.ts +0 -11
  1294. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tool-execution.d.ts +0 -63
  1295. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/tree-selector.d.ts +0 -94
  1296. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/trust-selector.d.ts +0 -23
  1297. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message-selector.d.ts +0 -30
  1298. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/user-message.d.ts +0 -14
  1299. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/components/visual-truncate.d.ts +0 -24
  1300. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/interactive-mode.d.ts +0 -394
  1301. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/model-search.d.ts +0 -12
  1302. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme-controller.d.ts +0 -29
  1303. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/interactive/theme/theme.d.ts +0 -120
  1304. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/print-mode.d.ts +0 -28
  1305. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/jsonl.d.ts +0 -17
  1306. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-client.d.ts +0 -246
  1307. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-mode.d.ts +0 -20
  1308. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/modes/rpc/rpc-types.d.ts +0 -457
  1309. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/package-manager-cli.d.ts +0 -8
  1310. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/rpc-entry.d.ts +0 -3
  1311. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/ansi.d.ts +0 -2
  1312. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/changelog.d.ts +0 -22
  1313. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/child-process.d.ts +0 -18
  1314. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-image.d.ts +0 -11
  1315. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard-native.d.ts +0 -11
  1316. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/clipboard.d.ts +0 -4
  1317. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/deprecation.d.ts +0 -4
  1318. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/exif-orientation.d.ts +0 -5
  1319. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/frontmatter.d.ts +0 -8
  1320. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/fs-watch.d.ts +0 -5
  1321. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/git.d.ts +0 -26
  1322. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/html.d.ts +0 -7
  1323. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-convert.d.ts +0 -10
  1324. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-process.d.ts +0 -18
  1325. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-core.d.ts +0 -30
  1326. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize-worker.d.ts +0 -2
  1327. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/image-resize.d.ts +0 -16
  1328. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/json.d.ts +0 -3
  1329. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/mime.d.ts +0 -3
  1330. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/open-browser.d.ts +0 -9
  1331. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/paths.d.ts +0 -31
  1332. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/photon.d.ts +0 -21
  1333. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/pi-user-agent.d.ts +0 -2
  1334. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/shell.d.ts +0 -31
  1335. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/sleep.d.ts +0 -5
  1336. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/syntax-highlight.d.ts +0 -12
  1337. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/tools-manager.d.ts +0 -3
  1338. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/version-check.d.ts +0 -15
  1339. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/utils/windows-self-update.d.ts +0 -3
  1340. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +0 -31
  1341. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +0 -74
  1342. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +0 -150
  1343. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +0 -766
  1344. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +0 -327
  1345. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +0 -309
  1346. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +0 -1093
  1347. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +0 -94
  1348. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +0 -1329
  1349. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +0 -153
  1350. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +0 -457
  1351. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +0 -669
  1352. package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +0 -51
  1353. package/dist/modes/interactive/components/daxnuts.d.ts +0 -22
  1354. package/dist/modes/interactive/components/daxnuts.js +0 -139
  1355. /package/dist/core/{agent-session-auto-handoff.test.d.ts → agent-session-custom-tools.test.d.ts} +0 -0
  1356. /package/dist/{core → extensions}/llama/huggingface.d.ts +0 -0
  1357. /package/dist/{core → extensions}/llama/huggingface.js +0 -0
  1358. /package/dist/{core → extensions}/llama/index.d.ts +0 -0
  1359. /package/dist/{core → extensions}/llama/ui.d.ts +0 -0
  1360. /package/dist/{core → extensions}/llama/ui.js +0 -0
@@ -16,10 +16,13 @@ import {
16
16
  mockAssistantMessage, readCall, readCallArgs, readAllCallArgs, makeExecutor,
17
17
  installSingleExecutionHooks,
18
18
  } from "../support/single-execution-fixture.ts";
19
+ import { waitForAsyncResultFile, waitForAsyncState } from "../support/async-execution-fixture.ts";
19
20
  import assert from "node:assert/strict";
20
- import * as fs from "node:fs";
21
+ import fsDefault, * as fs from "node:fs";
22
+ import { syncBuiltinESMExports } from "node:module";
21
23
  import * as path from "node:path";
22
24
  import { execFileSync } from "node:child_process";
25
+ import { createHash } from "node:crypto";
23
26
  import {
24
27
  createEventBus,
25
28
  makeAgentConfigs,
@@ -30,6 +33,11 @@ import {
30
33
  import registerSubagentExtension from "../../src/extension/index.ts";
31
34
  import { handleSubagentControlNotice } from "../../src/extension/control-notices.ts";
32
35
  import { discoverAgents } from "../../src/agents/agents.ts";
36
+ import { resolveSubagentLaunchContract } from "../../src/api/preflight.ts";
37
+ import { INTERCOM_BRIDGE_MARKER, resolveIntercomSessionTarget } from "../../src/intercom/intercom-bridge.ts";
38
+ import { stableJsonDigest } from "../../src/shared/launch-contract.ts";
39
+ import { cleanupOldArtifacts } from "../../src/shared/artifacts.ts";
40
+ import { createStructuredOutputRuntime } from "../../src/runs/shared/structured-output.ts";
33
41
  import {
34
42
  SUBAGENT_DELEGATION_REQUEST_EVENT,
35
43
  SUBAGENT_DELEGATION_RESPONSE_EVENT,
@@ -38,9 +46,10 @@ import {
38
46
  type SubagentDelegationResponse,
39
47
  type SubagentDelegationStarted,
40
48
  } from "../../src/api/delegation.ts";
41
- import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, SUBAGENT_CONTROL_EVENT, TEMP_ARTIFACTS_DIR, type AsyncStatus, type ChildWatchdogProgress, type ControlEvent, type SubagentState } from "../../src/shared/types.ts";
49
+ import { CHAIN_RUNS_DIR, DIRS, INTERCOM_DETACH_REQUEST_EVENT, INTERCOM_DETACH_RESPONSE_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_PROCESS_TERMINAL_EVENT, TEMP_ARTIFACTS_DIR, type AsyncStatus, type ChildWatchdogProgress, type ControlEvent, type SubagentState } from "../../src/shared/types.ts";
42
50
  import { ACTIVE_RUN_INDEX_DIR } from "../../src/runs/background/active-run-index.ts";
43
51
  import { encodeIndexSegment } from "../../src/runs/background/index-segment.ts";
52
+ import { removeResultIndex, writeAsyncResultFile, writePendingAsyncResultFile } from "../../src/runs/background/result-files.ts";
44
53
  import { listAsyncRuns } from "../../src/runs/background/async-status.ts";
45
54
  import { CHILD_WATCHDOG_STATUS_EVENT } from "../../src/watchdog/child-status.ts";
46
55
  import { createRunFanoutBudget } from "../../src/runs/shared/run-fanout-budget.ts";
@@ -53,11 +62,12 @@ import { discardPreservedWorktrees } from "../../src/runs/shared/parallel-handof
53
62
  import { createWorktrees } from "../../src/runs/shared/worktree.ts";
54
63
  import { resolveAsyncResumeTarget } from "../../src/runs/background/async-resume.ts";
55
64
  import { createResultWatcher } from "../../src/runs/background/result-watcher.ts";
56
- import { clearExclusions, recordModelFailure } from "../../src/runs/shared/model-exclusions.ts";
57
65
  import { createWorkflowChildPermit, workflowChildPermitConsumed } from "../../src/shared/workflow-child-permit.ts";
58
66
  import { toSubagentDelegationExecutionParams } from "../../src/slash/delegation-adapters.ts";
59
67
  import { registerWorkflowResource } from "../../src/api/workflow-resources.ts";
60
68
  import { registerSubagentCapabilityCeiling } from "../../src/api/capability-ceiling.ts";
69
+ import { appendWorkflowChildJournal, runtimeReplacedAbortReason, workflowChildFingerprint } from "../../src/workflows/workflow-reuse.ts";
70
+ import { updateTerminalRunIndex } from "../../src/runs/background/terminal-run-index.ts";
61
71
 
62
72
  describe("single sync execution", { skip: !available ? "pi packages not available" : undefined }, () => {
63
73
  installSingleExecutionHooks();
@@ -92,6 +102,66 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
92
102
  assert.equal(updates.length, count, "no trailing timer after settlement");
93
103
  });
94
104
 
105
+ it("names the workflow child that has no agent", async () => {
106
+ const result = await makeExecutor([makeAgent("worker")]).execute("wf-missing-agent", {
107
+ workflowScript: `const [child] = await runs.all([{ key: "r1", task: "Review", async: false }]); return child.ok ? "ok" : child.error;`,
108
+ async: false,
109
+ }, undefined, undefined, makeMinimalCtx(tempDir));
110
+ const text = JSON.stringify(result.content);
111
+ assert.match(text, /Workflow child 'r1' has no agent\. Pass \{ key, agent, task \}\. Agents: worker/);
112
+ assert.doesNotMatch(text, /Provide exactly one mode/);
113
+ });
114
+
115
+ for (const { name, asyncByDefault, id, reason } of [
116
+ { name: "foreground", asyncByDefault: false, id: "workflow-foreground-sibling-stop", reason: /^Workflow child stopped: \*\*a\*\*\n[\s\S]*\nError: Subagent stopped before completion\.\nStatus: workflow finished$/ },
117
+ { name: "async", asyncByDefault: true, id: "workflow-sibling-stop", reason: /^Workflow child stopped: \*\*a\*\*\n[\s\S]*\nError: Workflow stopped before async child completed\.\nStatus: workflow finished$/ },
118
+ ]) {
119
+ it(`reports ${name} workflow siblings stopped by a failed script as stopped`, { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
120
+ // hangUntilAbort still emits agent_settled first, which lets the 1s final-drain grace finish the sibling as failed before the stop lands.
121
+ // A release path that never appears keeps it mid-run, and its polling keeps a detached runner process alive, until the stop aborts it.
122
+ mockPi.onCall({ matchArgIncludes: "Slow sibling", waitForPath: path.join(tempDir, "never-released") });
123
+ mockPi.onCall({ matchArgIncludes: "Fails at launch", createError: "launch failed" });
124
+ const sent: Array<{ customType?: string; content?: string }> = [];
125
+ const executor = makeExecutor([makeAgent("echo")], {}, asyncByDefault, undefined, true, new Map(), undefined, undefined, createEventBus(), undefined, undefined, (message: unknown) => sent.push(message as { customType?: string; content?: string }));
126
+ const launch = await executor.execute(id, {
127
+ async: true,
128
+ workflowScript: `await Promise.all([runs.run("a", { agent: "echo", task: "Slow sibling" }), runs.run("b", { agent: "echo", task: "Fails at launch" })]);`,
129
+ }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
130
+ assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "workflow launch failed");
131
+ const notice = () => sent.find((message) => message.customType === "subagent-incremental-child-notify" && message.content?.includes("**a**"));
132
+ for (let attempt = 0; attempt < 250 && !notice(); attempt++) await new Promise((resolve) => setTimeout(resolve, 20));
133
+ assert.match(notice()?.content ?? "", reason);
134
+ });
135
+ }
136
+
137
+ it("emits successful async workflow child settlements without provider turns", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
138
+ mockPi.onCall({ matchArgIncludes: "Child A", output: "A done" });
139
+ mockPi.onCall({ matchArgIncludes: "Child B", output: "B done" });
140
+ const sent: Array<{ message: { customType?: string; content?: string }; options?: { triggerTurn?: boolean } }> = [];
141
+ const sendMessage = (message: unknown, options?: unknown) => {
142
+ sent.push({
143
+ message: message as { customType?: string; content?: string },
144
+ options: options as { triggerTurn?: boolean } | undefined,
145
+ });
146
+ };
147
+ const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), undefined, undefined, sendMessage);
148
+ const launch = await executor.execute("workflow-child-wakes", {
149
+ async: true,
150
+ workflowScript: `return await runs.all([{ key: "a", agent: "echo", task: "Child A" }, { key: "b", agent: "echo", task: "Child B" }]);`,
151
+ }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
152
+ assert.equal(launch.isError, undefined, launch.content[0]?.text ?? "workflow launch failed");
153
+
154
+ const childMessages = () => sent.filter(({ message }) => message.customType === "subagent-incremental-child-notify");
155
+ for (let attempt = 0; attempt < 250 && childMessages().length < 2; attempt++) {
156
+ await new Promise((resolve) => setTimeout(resolve, 20));
157
+ }
158
+ assert.deepEqual(
159
+ childMessages().map(({ message }) => message.content?.split("\n", 1)[0]).sort(),
160
+ ["Workflow child completed: **a**", "Workflow child completed: **b**"],
161
+ );
162
+ assert.ok(childMessages().every(({ options }) => options?.triggerTurn === false));
163
+ });
164
+
95
165
  it("spawns agent and captures output", async () => {
96
166
  mockPi.onCall({ output: "Hello from mock agent" });
97
167
  const agents = makeAgentConfigs(["echo"]);
@@ -120,6 +190,17 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
120
190
  assert.equal(readCall().runtime?.sessionName, "echo: Say hello to the world");
121
191
  });
122
192
 
193
+ it("addresses a nested child's supervisor by the parent child's intercom route, not its session name", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
194
+ mockPi.onCall({ output: "ok" });
195
+ const parentChild = { intercomSessionName: "subagent-planner-run1-1" } as ChildRuntimeConfig;
196
+ const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), undefined, parentChild);
197
+
198
+ const result = await executor.execute("nested-route", { agent: "echo", task: "Hi", intercomBridge: { mode: "always" } }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
199
+
200
+ assert.equal(result.isError, undefined, result.content[0]?.text);
201
+ assert.equal(readCall().runtime?.orchestratorTarget, "subagent-planner-run1-1");
202
+ });
203
+
123
204
  it("rejects invalid foreground cwd before spawning Pi", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
124
205
  const executor = makeExecutor([makeAgent("echo")]);
125
206
  const requestedCwd = "missing-local-cwd";
@@ -188,6 +269,24 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
188
269
  assert.doesNotMatch(result.content[0]?.text ?? "", /Console:/);
189
270
  });
190
271
 
272
+ it("binds each public foreground launch to its invoking model registry", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
273
+ mockPi.onCall({ output: "A completed" });
274
+ mockPi.onCall({ output: "B completed" });
275
+ const executor = makeExecutor([makeAgent("echo")]);
276
+ const ctxA = makeMinimalCtx(tempDir);
277
+ const ctxB = makeMinimalCtx(tempDir);
278
+
279
+ const resultA = await executor.executePublic("provider-owner-a", { agent: "echo", task: "Run A", async: false }, new AbortController().signal, undefined, ctxA);
280
+ const resultB = await executor.executePublic("provider-owner-b", { agent: "echo", task: "Run B", async: false }, new AbortController().signal, undefined, ctxB);
281
+
282
+ assert.equal(resultA.isError, undefined, resultA.content[0]?.text ?? "");
283
+ assert.equal(resultB.isError, undefined, resultB.content[0]?.text ?? "");
284
+ assert.equal(mockPi.sessions.length, 2);
285
+ assert.equal(mockPi.sessions[0]!.launch.parentProviderRegistry, ctxA.modelRegistry);
286
+ assert.equal(mockPi.sessions[1]!.launch.parentProviderRegistry, ctxB.modelRegistry);
287
+ assert.notEqual(mockPi.sessions[0]!.launch.parentProviderRegistry, mockPi.sessions[1]!.launch.parentProviderRegistry);
288
+ });
289
+
191
290
  it("keeps public structured children alive when tool results backfill without execution_end", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
192
291
  mockPi.onCall({
193
292
  steps: [
@@ -214,6 +313,96 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
214
313
  assert.equal(result.details.results[0]?.timedOut, undefined);
215
314
  });
216
315
 
316
+ it("preserves a blocked foreground delegated tool attempt without an execution-start event", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
317
+ const blockedMessage = "Tool budget hard limit reached after 1 tool call (hard 0). The 'bash' tool is blocked so you can finalize from the context you already have.";
318
+ mockPi.onCall({
319
+ steps: [
320
+ { jsonl: [
321
+ // A still-pending earlier call must not be attributed as the blocked tool.
322
+ { type: "tool_execution_start", toolCallId: "read-1", toolName: "read", args: { path: "notes.md" } },
323
+ { type: "tool_result_end", message: { role: "toolResult", toolCallId: "bash-1", toolName: "bash", isError: true, content: [{ type: "text", text: blockedMessage }] } },
324
+ { type: "tool_result_end", message: { role: "toolResult", toolCallId: "read-1", toolName: "read", isError: false, content: [{ type: "text", text: "ordinary notes" }] } },
325
+ ] },
326
+ { jsonl: [events.assistantMessage("I could not read the required canary because bash was blocked.")] },
327
+ ],
328
+ });
329
+ const request: SubagentDelegationRequest = {
330
+ requestId: "delegated-tool-budget-blocked",
331
+ ownerRunId: "owner-1",
332
+ nodeId: "node-1",
333
+ agent: "bash-worker",
334
+ task: "Use bash to read the required canary.",
335
+ context: "fresh",
336
+ cwd: tempDir,
337
+ model: "mock/model",
338
+ toolBudget: { hard: 0, block: "*" },
339
+ result: { kind: "text" },
340
+ };
341
+ const result = await makeExecutor([makeAgent("bash-worker")]).executeDelegated(
342
+ request.requestId,
343
+ toSubagentDelegationExecutionParams(request),
344
+ new AbortController().signal,
345
+ undefined,
346
+ makeMinimalCtx(tempDir),
347
+ );
348
+
349
+ const child = result.details?.results?.[0];
350
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "delegated execution failed");
351
+ assert.equal(child?.toolBudgetBlocked, true);
352
+ assert.equal(child?.toolBudget?.blockedTool, "bash");
353
+ assert.equal(child?.finalOutput, "I could not read the required canary because bash was blocked.");
354
+
355
+ mockPi.onCall({ output: "No tool needed." });
356
+ const normal = await makeExecutor([makeAgent("bash-worker")]).executeDelegated(
357
+ "delegated-no-tool",
358
+ toSubagentDelegationExecutionParams({ ...request, requestId: "delegated-no-tool", task: "Answer without tools." }),
359
+ new AbortController().signal,
360
+ undefined,
361
+ makeMinimalCtx(tempDir),
362
+ );
363
+ assert.equal(normal.details?.results?.[0]?.toolBudgetBlocked, undefined);
364
+ assert.equal(normal.details?.results?.[0]?.finalOutput, "No tool needed.");
365
+ });
366
+
367
+ it("does not classify ordinary tool output that merely quotes the block message", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
368
+ const quoted = `src/log.md:12:Tool budget hard limit reached after 1 tool call (hard 0). The 'bash' tool is blocked so you can finalize from the context you already have.`;
369
+ mockPi.onCall({
370
+ steps: [
371
+ { jsonl: [
372
+ { type: "tool_execution_start", toolCallId: "bash-1", toolName: "bash", args: { command: "grep -rn 'Tool budget hard limit reached' src/" } },
373
+ { type: "tool_result_end", message: { role: "toolResult", toolCallId: "bash-1", toolName: "bash", isError: false, content: [{ type: "text", text: quoted }] } },
374
+ { type: "tool_execution_end", toolName: "bash" },
375
+ ] },
376
+ { jsonl: [events.assistantMessage("The phrase appears in a log; nothing was blocked.")] },
377
+ ],
378
+ });
379
+ const request: SubagentDelegationRequest = {
380
+ requestId: "delegated-tool-budget-quoted",
381
+ ownerRunId: "owner-1",
382
+ nodeId: "node-1",
383
+ agent: "bash-worker",
384
+ task: "Search the repository for budget log lines.",
385
+ context: "fresh",
386
+ cwd: tempDir,
387
+ model: "mock/model",
388
+ toolBudget: { hard: 5, block: "*" },
389
+ result: { kind: "text" },
390
+ };
391
+ const result = await makeExecutor([makeAgent("bash-worker")]).executeDelegated(
392
+ request.requestId,
393
+ toSubagentDelegationExecutionParams(request),
394
+ new AbortController().signal,
395
+ undefined,
396
+ makeMinimalCtx(tempDir),
397
+ );
398
+
399
+ const child = result.details?.results?.[0];
400
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "delegated execution failed");
401
+ assert.equal(child?.toolBudgetBlocked, undefined);
402
+ assert.equal(child?.toolBudget?.outcome, "within-budget");
403
+ assert.equal(child?.finalOutput, "The phrase appears in a log; nothing was blocked.");
404
+ });
405
+
217
406
  it("keeps public structured single-child calls foreground when async is disabled by default", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
218
407
  mockPi.onCall({ output: "Structured child used the foreground default" });
219
408
  const executor = makeExecutor([makeAgent("echo")], {}, false);
@@ -305,6 +494,269 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
305
494
  assert.deepEqual(JSON.parse(fs.readFileSync(configuredPath, "utf-8")), { ok: true });
306
495
  });
307
496
 
497
+ it("matches preflight launch digest for structured foreground execution with a project-local refinement", { skip: !runSync ? "execution not importable" : undefined }, async () => {
498
+ const agentName = `digest-probe-${Date.now().toString(36)}`;
499
+ const task = "Answer only from the supplied synthetic text and return the requested structured result.";
500
+ const outputSchema = { type: "object" as const, required: ["ok"], properties: { ok: { type: "boolean" }, note: { type: "string" } } };
501
+ const permissionExtDir = path.join(agentDir, "extensions", "pi-permission-system");
502
+ fs.mkdirSync(path.join(permissionExtDir, "src"), { recursive: true });
503
+ fs.writeFileSync(path.join(permissionExtDir, "src", "index.ts"), "export default () => {};", "utf-8");
504
+ fs.writeFileSync(path.join(permissionExtDir, "package.json"), JSON.stringify({ name: "test", pi: { extensions: ["./src/index.ts"] } }), "utf-8");
505
+ const agentPath = path.join(tempDir, ".selesai", "agents", `${agentName}.md`);
506
+ fs.mkdirSync(path.dirname(agentPath), { recursive: true });
507
+ fs.writeFileSync(agentPath, `---
508
+ name: ${agentName}
509
+ description: Structured digest probe
510
+ tools:
511
+ extensions:
512
+ systemPromptMode: replace
513
+ inheritProjectContext: false
514
+ inheritSkills: false
515
+ defaultContext: fresh
516
+ ---
517
+
518
+ Answer only from the supplied synthetic text and return the requested structured result.
519
+ `, "utf-8");
520
+ const refinementPath = path.join(tempDir, ".pi-subagents", "refinements", `${agentName}.md`);
521
+ fs.mkdirSync(path.dirname(refinementPath), { recursive: true });
522
+ fs.writeFileSync(refinementPath, `<!-- pi-subagents-refinement:v1
523
+ {
524
+ "agent": ${JSON.stringify(agentName)},
525
+ "revision": 1,
526
+ "updatedAt": "2026-01-01T00:00:00.000Z",
527
+ "base": {
528
+ "source": "project",
529
+ "filePath": ${JSON.stringify(agentPath)},
530
+ "systemPromptSha256": "0000000000000000000000000000000000000000000000000000000000000000"
531
+ },
532
+ "evidence": {
533
+ "maxItems": 8,
534
+ "maxAgeDays": 14,
535
+ "itemBytes": 2048,
536
+ "totalBytes": 16384
537
+ }
538
+ }
539
+ -->
540
+
541
+ # Current refinement for \`${agentName}\`
542
+
543
+ \`\`\`pi-subagents-refinement-current
544
+ When the task asks for a structured result, keep field names exactly as requested.
545
+ \`\`\`
546
+
547
+ # Snapshots
548
+
549
+ \`\`\`pi-subagents-refinement-snapshots-json
550
+ []
551
+ \`\`\`
552
+ `, "utf-8");
553
+
554
+ const discovered = discoverAgents(tempDir).agents.find((agent) => agent.name === agentName);
555
+ assert.ok(discovered, "expected temporary agent definition to be discovered");
556
+ const launchInput = {
557
+ agent: agentName,
558
+ cwd: tempDir,
559
+ task,
560
+ context: "fresh" as const,
561
+ outputSchema,
562
+ skill: false,
563
+ output: false,
564
+ artifacts: false,
565
+ // runSync sits below the executor step that applies the bridge.
566
+ intercomBridge: { mode: "off" as const },
567
+ };
568
+ const preflight = await resolveSubagentLaunchContract(launchInput);
569
+ assert.equal(preflight.ok, true);
570
+ if (!preflight.ok) return;
571
+ const overlayMarkdown = fs.readFileSync(refinementPath, "utf-8");
572
+ fs.rmSync(refinementPath);
573
+ const withoutOverlay = await resolveSubagentLaunchContract(launchInput);
574
+ assert.equal(withoutOverlay.ok, true);
575
+ if (!withoutOverlay.ok) return;
576
+ assert.notEqual(withoutOverlay.contract.launchContractDigest, preflight.contract.launchContractDigest);
577
+ fs.writeFileSync(refinementPath, overlayMarkdown, "utf-8");
578
+
579
+ const structured = createStructuredOutputRuntime(outputSchema, tempDir);
580
+ mockPi.onCall({ structuredOutput: { ok: true, note: "captured" } });
581
+ const foreground = await runSync(tempDir, [discovered], agentName, task, {
582
+ runId: "digest-probe-foreground",
583
+ acceptance: false,
584
+ structuredOutput: structured,
585
+ });
586
+ assert.equal(foreground.exitCode, 0, foreground.error);
587
+ assert.deepEqual(foreground.structuredOutput, { ok: true, note: "captured" });
588
+ assert.equal((foreground as { launchContractDigest?: string }).launchContractDigest, preflight.contract.launchContractDigest);
589
+ });
590
+
591
+ it("matches preflight launch digest for structured delegation with the Intercom bridge active", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
592
+ const outputSchema = { type: "object" as const, required: ["ok"], properties: { ok: { type: "boolean" } } };
593
+ const task = "Return the requested structured result.";
594
+ const structuredCall = {
595
+ stdoutRaw: [
596
+ { type: "tool_execution_start", toolName: "structured_output", args: { value: { ok: true } } },
597
+ { type: "tool_result_end", message: { role: "toolResult", toolName: "structured_output", content: [{ type: "text", text: "Structured output captured." }] } },
598
+ { type: "tool_execution_end", toolName: "structured_output" },
599
+ ].map((entry) => JSON.stringify(entry)).join("\n") + "\n",
600
+ structuredOutputCapture: { ok: true },
601
+ };
602
+ // Both shapes matter: the bridge rewrites the prompt for every agent and
603
+ // widens the tool list only when the agent declares one.
604
+ for (const declaredTools of ["", "\n - read"]) {
605
+ const agentName = `bridge-digest-${declaredTools ? "tools" : "prompt"}-${Date.now().toString(36)}`;
606
+ const agentPath = path.join(tempDir, ".selesai", "agents", `${agentName}.md`);
607
+ fs.mkdirSync(path.dirname(agentPath), { recursive: true });
608
+ fs.writeFileSync(agentPath, `---
609
+ name: ${agentName}
610
+ description: Bridge digest probe
611
+ tools:${declaredTools}
612
+ extensions:
613
+ systemPromptMode: replace
614
+ inheritProjectContext: false
615
+ inheritSkills: false
616
+ defaultContext: fresh
617
+ ---
618
+
619
+ Answer only from the supplied synthetic text.
620
+ `, "utf-8");
621
+ const discovered = discoverAgents(tempDir).agents.find((agent) => agent.name === agentName);
622
+ assert.ok(discovered, "expected temporary agent definition to be discovered");
623
+
624
+ const preflight = await resolveSubagentLaunchContract({
625
+ agent: agentName,
626
+ cwd: tempDir,
627
+ task,
628
+ context: "fresh",
629
+ model: "mock/model",
630
+ outputSchema,
631
+ skill: false,
632
+ output: false,
633
+ artifacts: false,
634
+ });
635
+ assert.equal(preflight.ok, true);
636
+ if (!preflight.ok) return;
637
+ assert.deepEqual(preflight.contract.intercomBridge, { mode: "always", active: true });
638
+ assert.equal(preflight.contract.tools.effectiveAllowlist.includes("contact_supervisor"), Boolean(declaredTools));
639
+
640
+ mockPi.onCall(structuredCall);
641
+ const request: SubagentDelegationRequest = {
642
+ requestId: `${agentName}-attempt`,
643
+ ownerRunId: "owner-bridge-digest",
644
+ nodeId: agentName,
645
+ agent: agentName,
646
+ task,
647
+ context: "fresh",
648
+ cwd: tempDir,
649
+ model: "mock/model",
650
+ skill: false,
651
+ artifacts: false,
652
+ result: { kind: "structured", schema: outputSchema },
653
+ };
654
+ const result = await makeExecutor([discovered]).executeDelegated(
655
+ request.requestId,
656
+ toSubagentDelegationExecutionParams(request),
657
+ new AbortController().signal,
658
+ undefined,
659
+ makeMinimalCtx(tempDir),
660
+ );
661
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "delegated execution failed");
662
+ const child = result.details?.results?.[0];
663
+ assert.deepEqual(child?.structuredOutput, { ok: true });
664
+ assert.equal(child?.launchContractDigest, preflight.contract.launchContractDigest);
665
+
666
+ const call = readCall();
667
+ const childPrompt = call.systemPrompts.map((entry) => entry.text ?? (entry.path ? fs.readFileSync(entry.path, "utf-8") : "")).join("\n");
668
+ assert.ok(childPrompt.includes(INTERCOM_BRIDGE_MARKER), "child prompt should carry the bridge instruction");
669
+ assert.equal(call.launch?.tools?.includes("contact_supervisor") ?? false, Boolean(declaredTools));
670
+ }
671
+ });
672
+
673
+ it("matches preflight launch digest when a delegation override turns the Intercom bridge off", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
674
+ const agentName = `bridge-off-${Date.now().toString(36)}`;
675
+ const task = "Return the plain result.";
676
+ const agentPath = path.join(tempDir, ".selesai", "agents", `${agentName}.md`);
677
+ fs.mkdirSync(path.dirname(agentPath), { recursive: true });
678
+ fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Bridge override probe\ntools:\n - read\n---\nAnswer from the task only.\n`, "utf-8");
679
+ const discovered = discoverAgents(tempDir).agents.find((agent) => agent.name === agentName);
680
+ assert.ok(discovered, "expected temporary agent definition to be discovered");
681
+ const intercomBridge = { mode: "off" as const };
682
+
683
+ const preflight = await resolveSubagentLaunchContract({ agent: agentName, cwd: tempDir, task, context: "fresh", model: "mock/model", skill: false, output: false, artifacts: false, intercomBridge });
684
+ assert.equal(preflight.ok, true);
685
+ if (!preflight.ok) return;
686
+ assert.deepEqual(preflight.contract.intercomBridge, { active: false, mode: "off" });
687
+ assert.equal(preflight.contract.tools.effectiveAllowlist.includes("contact_supervisor"), false);
688
+
689
+ mockPi.onCall({ output: "bridge off" });
690
+ const request: SubagentDelegationRequest = {
691
+ requestId: `${agentName}-attempt`,
692
+ ownerRunId: "owner-bridge-off",
693
+ nodeId: agentName,
694
+ agent: agentName,
695
+ task,
696
+ context: "fresh",
697
+ cwd: tempDir,
698
+ model: "mock/model",
699
+ skill: false,
700
+ artifacts: false,
701
+ intercomBridge,
702
+ result: { kind: "text" },
703
+ };
704
+ const result = await makeExecutor([discovered]).executeDelegated(request.requestId, toSubagentDelegationExecutionParams(request), new AbortController().signal, undefined, makeMinimalCtx(tempDir));
705
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "delegated execution failed");
706
+ assert.equal(result.details?.results?.[0]?.launchContractDigest, preflight.contract.launchContractDigest);
707
+ const call = readCall();
708
+ const childPrompt = call.systemPrompts.map((entry) => entry.text ?? (entry.path ? fs.readFileSync(entry.path, "utf-8") : "")).join("\n");
709
+ assert.equal(childPrompt.includes(INTERCOM_BRIDGE_MARKER), false, "override must keep the bridge out of the child prompt");
710
+ assert.equal(call.launch?.tools?.includes("contact_supervisor") ?? false, false);
711
+ });
712
+
713
+ it("matches preflight launch digest for a custom bridge template when the host supplies the session target", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
714
+ const agentName = `bridge-template-${Date.now().toString(36)}`;
715
+ const task = "Return the plain result.";
716
+ const agentPath = path.join(tempDir, ".selesai", "agents", `${agentName}.md`);
717
+ fs.mkdirSync(path.dirname(agentPath), { recursive: true });
718
+ fs.writeFileSync(agentPath, `---\nname: ${agentName}\ndescription: Bridge template probe\n---\nAnswer from the task only.\n`, "utf-8");
719
+ const discovered = discoverAgents(tempDir).agents.find((agent) => agent.name === agentName);
720
+ assert.ok(discovered, "expected temporary agent definition to be discovered");
721
+ const instructionFile = path.join(tempDir, "custom-bridge.md");
722
+ fs.writeFileSync(instructionFile, "Custom bridge for {orchestratorTarget}\nUse ask then send.\n", "utf-8");
723
+ const intercomBridge = { mode: "always" as const, instructionFile };
724
+ // The executor derives this from the parent session; a host reproduces it with the public helper.
725
+ const ctx = makeMinimalCtx(tempDir);
726
+ const orchestratorTarget = resolveIntercomSessionTarget(undefined, ctx.sessionManager.getSessionId());
727
+
728
+ const withoutTarget = await resolveSubagentLaunchContract({ agent: agentName, cwd: tempDir, task, context: "fresh", model: "mock/model", skill: false, output: false, artifacts: false, intercomBridge });
729
+ assert.equal(withoutTarget.ok, true);
730
+ if (!withoutTarget.ok) return;
731
+ assert.ok(withoutTarget.contract.diagnostics.some((diagnostic) => diagnostic.code === "host_required" && /orchestratorTarget/.test(diagnostic.message)));
732
+ const preflight = await resolveSubagentLaunchContract({ agent: agentName, cwd: tempDir, task, context: "fresh", model: "mock/model", skill: false, output: false, artifacts: false, intercomBridge, orchestratorTarget });
733
+ assert.equal(preflight.ok, true);
734
+ if (!preflight.ok) return;
735
+ assert.deepEqual(preflight.contract.intercomBridge, { active: true, mode: "always" });
736
+
737
+ mockPi.onCall({ output: "custom bridge" });
738
+ const request: SubagentDelegationRequest = {
739
+ requestId: `${agentName}-attempt`,
740
+ ownerRunId: "owner-bridge-template",
741
+ nodeId: agentName,
742
+ agent: agentName,
743
+ task,
744
+ context: "fresh",
745
+ cwd: tempDir,
746
+ model: "mock/model",
747
+ skill: false,
748
+ artifacts: false,
749
+ intercomBridge,
750
+ result: { kind: "text" },
751
+ };
752
+ const result = await makeExecutor([discovered]).executeDelegated(request.requestId, toSubagentDelegationExecutionParams(request), new AbortController().signal, undefined, ctx);
753
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "delegated execution failed");
754
+ assert.equal(result.details?.results?.[0]?.launchContractDigest, preflight.contract.launchContractDigest);
755
+ assert.notEqual(result.details?.results?.[0]?.launchContractDigest, withoutTarget.contract.launchContractDigest);
756
+ const childPrompt = readCall().systemPrompts.map((entry) => entry.text ?? (entry.path ? fs.readFileSync(entry.path, "utf-8") : "")).join("\n");
757
+ assert.ok(childPrompt.includes(`Custom bridge for ${orchestratorTarget}`), "child prompt should carry the session-named custom instruction");
758
+ });
759
+
308
760
  it("does not inject a workflow child output without an aggregate or explicit output", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
309
761
  mockPi.onCall({ output: "Workflow child completed" });
310
762
  const result = await makeExecutor([makeAgent("echo")]).execute(
@@ -319,38 +771,6 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
319
771
  assert.doesNotMatch(readCallArgs().join("\n"), /This path is authoritative for this run/);
320
772
  });
321
773
 
322
- it("keeps escaped read-only delegate tasks from triggering the completion guard", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
323
- mockPi.onCall({ output: "The exact user-facing response" });
324
- const task = [
325
- "This is a read-only skill compliance scenario, not an implementation assignment.",
326
- "Read the supplied skill and write the exact user-facing response.",
327
- "Do not edit files.",
328
- "Use a scenario that discusses selection for an implementation task or closeout of an implementation assignment.",
329
- ].join("\\n");
330
- const result = await makeExecutor([makeAgent("delegate", {
331
- tools: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"],
332
- inheritProjectContext: true,
333
- systemPromptMode: "append",
334
- })]).execute(
335
- "workflow-read-only-delegate",
336
- {
337
- async: false,
338
- acceptance: false,
339
- preflight: { version: 1, coverage: "complete", lanes: [{ key: "main", mode: "review" }] },
340
- workflowScript: `return runs.all([{ key: "main", agent: "delegate", task: ${JSON.stringify(task)} }]);`,
341
- },
342
- new AbortController().signal,
343
- undefined,
344
- makeMinimalCtx(tempDir),
345
- );
346
-
347
- assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
348
- const child = (result.details as { results?: Array<{ exitCode?: number; error?: string; output?: string }> } | undefined)?.results?.[0];
349
- assert.equal(child?.exitCode, 0);
350
- assert.equal(child?.error, undefined);
351
- assert.match(result.content[0]?.text ?? "", /The exact user-facing response/);
352
- });
353
-
354
774
  it("consumes one exact host-only workflow child permit before spawn", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
355
775
  const executor = makeExecutor([makeAgent("echo"), makeAgent("other"), makeAgent("external", { runner: { type: "external-cli", command: "external" } })]);
356
776
  const ctx = makeMinimalCtx(tempDir);
@@ -420,15 +840,6 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
420
840
  assert.match(wrongThenRight.content[0]?.text ?? "", /already consumed/);
421
841
  assert.equal(workflowChildPermitConsumed(wrongThenRightPermit), true);
422
842
  assert.equal(mockPi.callCount(), 3, "wrong-then-right must not spawn");
423
- const fallback = await makeExecutor([makeAgent("echo", { model: "mock/primary", fallbackModels: ["mock/backup"] })]).executeDelegated(
424
- "fallback",
425
- { async: false, workflowScript: script, delegatedWorkflowPermit: permitFor("fallback") },
426
- new AbortController().signal,
427
- undefined,
428
- ctx,
429
- );
430
- assert.match(fallback.content[0]?.text ?? "", /does not support model fallback/);
431
- assert.equal(mockPi.callCount(), 3);
432
843
  });
433
844
 
434
845
  it("resolves workflow child profile context from its agent default", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
@@ -545,7 +956,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
545
956
  assert.equal(mockPi.callCount(), 1);
546
957
  });
547
958
 
548
- it("allows schedule.create to load its workflowScript target from a path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
959
+ it("allows schedule.create to load its workflow script target from a path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
549
960
  let forwarded;
550
961
  const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, async (params) => {
551
962
  forwarded = params;
@@ -555,7 +966,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
555
966
 
556
967
  const result = await executor.executePublic(
557
968
  "schedule-create",
558
- { action: "schedule.create", id: "nightly", every: "1h", workflowScriptPath: "scheduled.js" },
969
+ { action: "schedule.create", id: "nightly", every: "1h", workflow: "./scheduled.js", args: { task: "nightly review" } },
559
970
  new AbortController().signal,
560
971
  undefined,
561
972
  makeMinimalCtx(tempDir),
@@ -564,13 +975,44 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
564
975
  assert.equal(result.isError, undefined);
565
976
  assert.equal(result.content[0]?.text, "created");
566
977
  assert.equal(forwarded?.workflowScript, "return runs.run('main', { agent: 'echo' })");
978
+ assert.deepEqual(forwarded?.args, { task: "nightly review" });
979
+ });
980
+
981
+ it("rejects a static spawn-budget mismatch before launching children", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
982
+ const before = fs.readdirSync(tempDir).sort();
983
+ const executor = makeExecutor([makeAgent("echo")]);
984
+ const script = [
985
+ `const results = await runs.all([`,
986
+ ` { key: "a", agent: "echo", task: "A" },`,
987
+ ` { key: "b", agent: "echo", task: "B" },`,
988
+ ` { key: "c", agent: "echo", task: "C" },`,
989
+ `]);`,
990
+ `const owner = await runs.run("owner", { agent: "echo", task: results[0].output });`,
991
+ `const review = await runs.run("review", { agent: "echo", task: owner.output });`,
992
+ `return runs.run("owner-fix", { resume: owner.runId, task: review.output });`,
993
+ ].join("\n");
994
+
995
+ const result = await executor.executePublic(
996
+ "static-budget-mismatch",
997
+ { async: false, workflowScript: script, maxSubagentSpawnsPerRun: 5 },
998
+ new AbortController().signal,
999
+ undefined,
1000
+ makeMinimalCtx(tempDir),
1001
+ );
1002
+
1003
+ assert.equal(result.isError, true);
1004
+ const payload = JSON.parse(result.content[0]?.text ?? "null") as { ok?: boolean; errors?: Array<{ kind?: string; message?: string }> };
1005
+ assert.equal(payload.ok, false);
1006
+ assert.equal(payload.errors?.[0]?.kind, "spawn-budget");
1007
+ assert.match(payload.errors?.[0]?.message ?? "", /'a', 'b', 'c', 'owner', 'review', 'owner-fix'/);
1008
+ assert.match(payload.errors?.[0]?.message ?? "", /minimum required: 6; configured: 5/);
1009
+ assert.equal(mockPi.callCount(), 0);
1010
+ assert.deepEqual(fs.readdirSync(tempDir).sort(), before);
567
1011
  });
568
1012
 
569
1013
  it("validates workflow scripts without launching children or creating artifacts", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
570
1014
  const before = fs.readdirSync(tempDir).sort();
571
- const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), () => {
572
- throw new Error("validate must not discover or launch agents");
573
- });
1015
+ const executor = makeExecutor([makeAgent("echo")]);
574
1016
 
575
1017
  const result = await executor.executePublic(
576
1018
  "offline-validation",
@@ -581,10 +1023,22 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
581
1023
  );
582
1024
 
583
1025
  assert.equal(result.isError, true);
1026
+ assert.equal(result.details.mode, "management");
584
1027
  assert.deepEqual(JSON.parse(result.content[0]?.text ?? "null"), {
585
1028
  ok: false,
586
1029
  errors: [{ message: "runs.run key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.", line: 1, column: 17 }],
587
1030
  });
1031
+ const budgetValidation = await executor.executePublic(
1032
+ "offline-budget-validation",
1033
+ { action: "validate", maxSubagentSpawnsPerRun: 1, workflowScript: `await runs.run("first", { agent: "echo" }); return runs.run("second", { agent: "echo" });` },
1034
+ new AbortController().signal,
1035
+ undefined,
1036
+ makeMinimalCtx(tempDir),
1037
+ );
1038
+ assert.equal(budgetValidation.isError, true);
1039
+ const budgetPayload = JSON.parse(budgetValidation.content[0]?.text ?? "null") as { errors?: Array<{ kind?: string; message?: string }> };
1040
+ assert.equal(budgetPayload.errors?.[0]?.kind, "spawn-budget");
1041
+ assert.match(budgetPayload.errors?.[0]?.message ?? "", /'first', 'second'.*minimum required: 2; configured: 1/);
588
1042
  const invalidPreflight = await executor.executePublic(
589
1043
  "invalid-preflight",
590
1044
  { workflowScript: `return runs.run("child", { agent: "echo" });`, preflight: { version: 1, lanes: [{ key: "bad key" }] } },
@@ -609,6 +1063,29 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
609
1063
  assert.deepEqual(fs.readdirSync(tempDir).sort(), before);
610
1064
  });
611
1065
 
1066
+ it("rejects malformed public async workflows before creating run state", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1067
+ const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), () => {
1068
+ throw new Error("invalid workflows must not discover or launch agents");
1069
+ });
1070
+ const script = ["const value = 1;", "return (;"].join("\n");
1071
+ const asyncDirsBefore = fs.existsSync(DIRS.async) ? fs.readdirSync(DIRS.async).sort() : [];
1072
+ const callsBefore = mockPi.callCount();
1073
+ const ctx = makeMinimalCtx(tempDir);
1074
+ const validation = await executor.executePublic("malformed-validation", { action: "validate", workflowScript: script }, new AbortController().signal, undefined, ctx);
1075
+ const result = await executor.executePublic("malformed-async", { workflowScript: script }, new AbortController().signal, undefined, ctx);
1076
+
1077
+ assert.equal(result.isError, true);
1078
+ assert.equal(validation.details.mode, "management");
1079
+ assert.equal(result.details.mode, "workflow");
1080
+ assert.deepEqual(JSON.parse(result.content[0]?.text ?? "null"), JSON.parse(validation.content[0]?.text ?? "null"));
1081
+ const payload = JSON.parse(result.content[0]?.text ?? "null") as { errors?: Array<{ line?: number; column?: number }> };
1082
+ assert.deepEqual(payload.errors?.map(({ line, column }) => ({ line, column })), [{ line: 2, column: 9 }]);
1083
+ assert.equal(mockPi.callCount(), callsBefore);
1084
+ assert.equal(result.details.asyncId, undefined);
1085
+ assert.equal(result.details.workflow?.receipt, undefined);
1086
+ assert.deepEqual(fs.existsSync(DIRS.async) ? fs.readdirSync(DIRS.async).sort() : [], asyncDirsBefore);
1087
+ });
1088
+
612
1089
  it("rejects invalid public workflow acceptance defaults before mission or script work", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
613
1090
  const executor = makeExecutor([makeAgent("echo")]);
614
1091
  const ctx = makeMinimalCtx(tempDir);
@@ -743,7 +1220,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
743
1220
  try {
744
1221
  for (const params of [
745
1222
  { workflowScript: script },
746
- { workflowScriptPath: "raw-workflow.js" },
1223
+ { workflow: "./raw-workflow.js" },
747
1224
  { workflow: "test.denied", workflowResourcePermit: {} },
748
1225
  { workflow: "test.denied" },
749
1226
  ]) {
@@ -757,17 +1234,21 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
757
1234
  });
758
1235
 
759
1236
  it("denies host calls from raw public workflow scripts without resource authority", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
760
- const result = await makeExecutor([makeAgent("echo")]).executePublic(
761
- "raw-host-denied",
762
- { workflowScript: `return await runs.host("ci", { kind: "command", command: "npm test", timeoutMs: 1000 });`, async: false },
763
- new AbortController().signal,
764
- undefined,
765
- makeMinimalCtx(tempDir),
766
- );
767
- assert.equal(result.isError, true);
768
- assert.match(result.content[0]?.text ?? "", /runs\.host is unavailable/);
769
- assert.equal(result.details.workflow?.resource, undefined);
770
- assert.equal(result.details.workflow?.receipt?.resource, undefined);
1237
+ const script = `return await runs.host("ci", { kind: "command", command: "npm test", timeoutMs: 1000 });`;
1238
+ fs.writeFileSync(path.join(tempDir, "raw-host.js"), script);
1239
+ for (const source of [{ workflowScript: script }, { workflow: "./raw-host.js" }]) {
1240
+ const result = await makeExecutor([makeAgent("echo")]).executePublic(
1241
+ "raw-host-denied",
1242
+ { ...source, args: { resource: "trusted", permit: true, hostCommands: ["npm test"] }, async: false },
1243
+ new AbortController().signal,
1244
+ undefined,
1245
+ makeMinimalCtx(tempDir),
1246
+ );
1247
+ assert.equal(result.isError, true);
1248
+ assert.match(result.content[0]?.text ?? "", /runs\.host is unavailable/);
1249
+ assert.equal(result.details.workflow?.resource, undefined);
1250
+ assert.equal(result.details.workflow?.receipt?.resource, undefined);
1251
+ }
771
1252
  assert.equal(mockPi.callCount(), 0);
772
1253
  });
773
1254
 
@@ -976,13 +1457,14 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
976
1457
  );
977
1458
  assert.ok(failed.details.asyncDir);
978
1459
  const failedStatusPath = path.join(failed.details.asyncDir!, "status.json");
979
- let failedStatus: { state?: string; workflowGraph?: { nodes?: Array<{ hostStep?: { state?: string; reasonCode?: string; exitCode?: number | null } }> } } = {};
1460
+ let failedStatus: Partial<Pick<AsyncStatus, "state" | "workflow" | "workflowGraph">> = {};
980
1461
  for (let attempt = 0; attempt < 100; attempt += 1) {
981
1462
  failedStatus = JSON.parse(fs.readFileSync(failedStatusPath, "utf8"));
982
1463
  if (failedStatus.state === "complete" || failedStatus.state === "failed") break;
983
1464
  await new Promise((resolve) => setTimeout(resolve, 20));
984
1465
  }
985
1466
  assert.equal(failedStatus.state, "failed");
1467
+ assert.equal(failedStatus.workflow?.failureKind, "script");
986
1468
  assert.deepEqual(failedStatus.workflowGraph?.nodes?.map((node) => node.hostStep && { state: node.hostStep.state, reasonCode: node.hostStep.reasonCode, exitCode: node.hostStep.exitCode }), [{ state: "error", reasonCode: "command_failed", exitCode: 3 }]);
987
1469
  const failedReceipt = JSON.parse(fs.readFileSync(path.join(failed.details.asyncDir!, "workflow-receipt.json"), "utf8")) as { state?: string; hostSteps?: Array<{ state?: string; reasonCode?: string; exitCode?: number | null }> };
988
1470
  assert.equal(failedReceipt.state, "failed");
@@ -992,17 +1474,18 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
992
1474
  if (failed.details.asyncId) fs.rmSync(path.join(DIRS.results, `${failed.details.asyncId}.json`), { force: true });
993
1475
  });
994
1476
 
995
- it("loads workflowScriptPath from the request cwd for validation without launching", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1477
+ it("loads a workflow script path from the request cwd for validation without launching", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
996
1478
  const requestCwd = path.join(tempDir, "request-cwd");
997
1479
  fs.mkdirSync(requestCwd);
998
1480
  fs.writeFileSync(path.join(requestCwd, "workflow.js"), `return runs.run("bad key", { agent: "echo" });`);
999
- const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), () => {
1000
- throw new Error("validate must not discover or launch agents");
1481
+ const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), (cwd) => {
1482
+ assert.equal(cwd, requestCwd);
1483
+ return [makeAgent("echo")];
1001
1484
  });
1002
1485
 
1003
1486
  const result = await executor.executePublic(
1004
1487
  "file-validation",
1005
- { action: "validate", cwd: "request-cwd", workflowScriptPath: "workflow.js" },
1488
+ { action: "validate", cwd: "request-cwd", workflow: "./workflow.js", args: { task: "review" } },
1006
1489
  new AbortController().signal,
1007
1490
  undefined,
1008
1491
  makeMinimalCtx(tempDir),
@@ -1016,32 +1499,139 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1016
1499
  assert.equal(mockPi.callCount(), 0);
1017
1500
  });
1018
1501
 
1019
- it("reports missing and empty workflowScriptPath files before validation", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1502
+ it("validate reports workflow args errors beside script errors without launching (#2608)", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1503
+ const executor = makeExecutor([makeAgent("echo")]);
1504
+ const workflowScript = `return runs.run("bad key", { agent: "echo" });`;
1505
+ const tooManyFields = Object.fromEntries(Array.from({ length: 17 }, (_, index) => [`field${index}`, "x"]));
1506
+ const oversize = { a: "x".repeat(9000), b: "y".repeat(9000) };
1507
+ const scriptError = { message: "runs.run key must be 1-128 characters using letters, numbers, '.', '_' or '-', and start with a letter or number.", line: 1, column: 17 };
1508
+
1509
+ for (const [args, argsError] of [[tooManyFields, "workflow args contains too many fields."], [oversize, "workflow args exceed 16384 bytes."]] as const) {
1510
+ const result = await executor.executePublic("args-validation", { action: "validate", workflowScript, args }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1511
+ assert.equal(result.isError, true);
1512
+ assert.equal(result.details.mode, "management");
1513
+ assert.deepEqual(JSON.parse(result.content[0]?.text ?? "null"), { ok: false, errors: [scriptError, { message: argsError }] });
1514
+ }
1515
+ const argsOnly = await executor.executePublic("args-only-validation", { action: "validate", workflowScript: "return 1;", args: oversize }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1516
+ assert.deepEqual(JSON.parse(argsOnly.content[0]?.text ?? "null"), { ok: false, errors: [{ message: "workflow args exceed 16384 bytes." }] });
1517
+ assert.equal(mockPi.callCount(), 0);
1518
+ });
1519
+
1520
+ it("reports missing and empty workflow script files before validation", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1020
1521
  fs.writeFileSync(path.join(tempDir, "empty.js"), " \n");
1021
1522
  const executor = makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), () => {
1022
1523
  throw new Error("file input errors must not discover or launch agents");
1023
1524
  });
1024
1525
 
1025
- const missing = await executor.executePublic("missing-file", { action: "validate", workflowScriptPath: "missing.js" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1526
+ const missing = await executor.executePublic("missing-file", { action: "validate", workflow: "./missing.js" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1026
1527
  assert.equal(missing.isError, true);
1027
- assert.match(missing.content[0]?.text ?? "", /Failed to read workflowScriptPath.*missing\.js/);
1528
+ assert.match(missing.content[0]?.text ?? "", /Failed to read workflow script.*missing\.js/);
1028
1529
  assert.doesNotMatch(missing.content[0]?.text ?? "", /validation failed|valid JavaScript/);
1029
1530
 
1030
- const empty = await executor.executePublic("empty-file", { action: "validate", workflowScriptPath: "empty.js" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1531
+ const empty = await executor.executePublic("empty-file", { action: "validate", workflow: "./empty.js" }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1031
1532
  assert.equal(empty.isError, true);
1032
- assert.match(empty.content[0]?.text ?? "", /workflowScriptPath file .*empty\.js.* is empty/);
1533
+ assert.match(empty.content[0]?.text ?? "", /Workflow script file .*empty\.js.* is empty/);
1534
+
1535
+ // Seen live: a model put the script itself in the workflow string.
1536
+ const scriptText = await executor.executePublic("script-text", { workflow: "return 1", async: false }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1537
+ assert.equal(scriptText.isError, true);
1538
+ assert.match(scriptText.content[0]?.text ?? "", /write it in one ```js workflow block in the same reply and call subagent\(\{ workflow: true \}\)/);
1033
1539
  assert.doesNotMatch(empty.content[0]?.text ?? "", /validation failed|valid JavaScript/);
1034
1540
  assert.equal(mockPi.callCount(), 0);
1035
1541
  });
1036
1542
 
1037
- it("executes a workflow loaded from workflowScriptPath", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1543
+ it("rejects invalid raw workflow arguments before reading a script path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1544
+ const result = await makeExecutor([makeAgent("echo")]).executePublic(
1545
+ "invalid-workflow-args",
1546
+ { workflow: "./missing.js", args: { task: "" } },
1547
+ new AbortController().signal,
1548
+ undefined,
1549
+ makeMinimalCtx(tempDir),
1550
+ );
1551
+ assert.equal(result.isError, true);
1552
+ assert.match(result.content[0]?.text ?? "", /args\.task must not be empty/);
1553
+ assert.doesNotMatch(result.content[0]?.text ?? "", /missing\.js/);
1554
+ });
1555
+
1556
+ it("executes the ```js workflow block from the reply that issued the call", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1557
+ mockPi.onCall({ output: "reply workflow" });
1558
+ const ctx = makeMinimalCtx(tempDir);
1559
+ const reply = {
1560
+ type: "message",
1561
+ message: {
1562
+ role: "assistant",
1563
+ content: [
1564
+ { type: "text", text: "Plan:\n```js workflow\nconst child = await runs.run(\"main\", { agent: \"echo\", task: args.task });\nreturn { task: args.task, child: child.output };\n```" },
1565
+ { type: "toolCall", id: "call-reply", name: "subagent", arguments: { workflow: true } },
1566
+ ],
1567
+ },
1568
+ };
1569
+ const result = await makeExecutor([makeAgent("echo")]).executePublic(
1570
+ "call-reply",
1571
+ { workflow: true, args: { task: "from reply" }, async: false },
1572
+ new AbortController().signal,
1573
+ undefined,
1574
+ { ...ctx, sessionManager: { ...ctx.sessionManager, getBranch: () => [reply] } },
1575
+ );
1576
+
1577
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "reply workflow failed");
1578
+ assert.deepEqual(result.details.workflow?.value, { task: "from reply", child: "reply workflow" });
1579
+ assert.equal(mockPi.callCount(), 1);
1580
+ });
1581
+
1582
+ it("treats workflow: \"true\" from MCP clients as the reply block, not a resource named 'true' (#2600)", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1583
+ mockPi.onCall({ output: "reply workflow" });
1584
+ const ctx = makeMinimalCtx(tempDir);
1585
+ const reply = {
1586
+ type: "message",
1587
+ message: {
1588
+ role: "assistant",
1589
+ content: [
1590
+ { type: "text", text: "```js workflow\nconst child = await runs.run(\"main\", { agent: \"echo\", task: args.task });\nreturn child.output;\n```" },
1591
+ { type: "toolCall", id: "call-string-true", name: "subagent", arguments: { workflow: "true" } },
1592
+ ],
1593
+ },
1594
+ };
1595
+ const replyCtx = { ...ctx, sessionManager: { ...ctx.sessionManager, getBranch: () => [reply] } };
1596
+ const executor = makeExecutor([makeAgent("echo")]);
1597
+
1598
+ const validation = await executor.executePublic("call-string-true", { action: "validate", workflow: "true", args: { task: "from reply" } }, new AbortController().signal, undefined, replyCtx);
1599
+ assert.equal(validation.isError, undefined, validation.content[0]?.text ?? "reply workflow validation failed");
1600
+
1601
+ const result = await executor.executePublic("call-string-true", { workflow: "true", args: { task: "from reply" }, async: false }, new AbortController().signal, undefined, replyCtx);
1602
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "reply workflow failed");
1603
+ assert.equal(result.details.workflow?.value, "reply workflow");
1604
+ assert.equal(mockPi.callCount(), 1);
1605
+ });
1606
+
1607
+ it("tags each foreground workflow result with its child run id", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1608
+ mockPi.onCall({ output: "first" });
1609
+ mockPi.onCall({ output: "second" });
1610
+ const result = await makeExecutor([makeAgent("echo")]).execute(
1611
+ "workflow-result-run-ids",
1612
+ {
1613
+ async: false,
1614
+ workflowScript: `const a = await runs.run("a", { agent: "echo", task: "A" }); const b = await runs.run("b", { agent: "echo", task: "B" }); return { a: a.runId, b: b.runId };`,
1615
+ },
1616
+ new AbortController().signal,
1617
+ undefined,
1618
+ makeMinimalCtx(tempDir),
1619
+ );
1620
+
1621
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1622
+ const value = result.details.workflow?.value as { a?: string; b?: string };
1623
+ assert.ok(value.a && value.b && value.a !== value.b);
1624
+ assert.deepEqual(result.details.results.map((entry) => ({ key: entry.workflowKey, runId: entry.runId })), [{ key: "a", runId: value.a }, { key: "b", runId: value.b }]);
1625
+ });
1626
+
1627
+ it("executes a workflow loaded from a script path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1038
1628
  fs.writeFileSync(path.join(tempDir, "workflow.js"), `return runs.run("main", { agent: "echo", task: "from file" });`);
1039
1629
  mockPi.onCall({ output: "loaded workflow" });
1040
1630
  const executor = makeExecutor([makeAgent("echo")]);
1041
1631
 
1042
1632
  const result = await executor.executePublic(
1043
1633
  "file-execution",
1044
- { async: false, workflowScriptPath: path.join(tempDir, "workflow.js"), preflight: { version: 1, coverage: "complete", lanes: [{ key: "main", mode: "mutation" }] } },
1634
+ { async: false, workflow: path.join(tempDir, "workflow.js"), preflight: { version: 1, coverage: "complete", lanes: [{ key: "main", mode: "mutation" }] } },
1045
1635
  new AbortController().signal,
1046
1636
  undefined,
1047
1637
  makeMinimalCtx(tempDir),
@@ -1049,9 +1639,52 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1049
1639
 
1050
1640
  assert.equal(result.isError, undefined, result.content[0]?.text ?? "file workflow failed");
1051
1641
  assert.deepEqual(result.details.preflight, { version: 1, coverage: "complete", lanes: [{ key: "main", mode: "mutation" }] });
1642
+ assert.deepEqual(result.details.workflow?.args, {});
1643
+ assert.equal(result.details.workflow?.argsDigest, stableJsonDigest({}));
1052
1644
  assert.equal(mockPi.callCount(), 1);
1053
1645
  });
1054
1646
 
1647
+ it("passes normalized arguments to inline and file-backed workflow sandboxes with bound receipt evidence", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1648
+ const script = `const child = await runs.run("main", { agent: "echo", task: args.task }); return { input: args, child: child.output };`;
1649
+ fs.writeFileSync(path.join(tempDir, "parameterized-workflow.js"), script);
1650
+ const args = { task: "from args", options: { labels: ["one"] } };
1651
+ const digests: string[] = [];
1652
+
1653
+ for (const { source, invocationArgs } of [
1654
+ { source: { workflowScript: script }, invocationArgs: args },
1655
+ { source: { workflow: "./parameterized-workflow.js" }, invocationArgs: { options: { labels: ["one"] }, task: "from args" } },
1656
+ { source: { workflowScript: script }, invocationArgs: { task: "from args", options: { labels: ["two"] } } },
1657
+ ]) {
1658
+ mockPi.onCall({ output: "parameterized workflow" });
1659
+ const result = await makeExecutor([makeAgent("echo")]).executePublic(
1660
+ "parameterized-workflow",
1661
+ { ...source, args: invocationArgs, async: false },
1662
+ new AbortController().signal,
1663
+ undefined,
1664
+ makeMinimalCtx(tempDir),
1665
+ );
1666
+
1667
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
1668
+ assert.deepEqual(result.details.workflow?.value, { input: invocationArgs, child: "parameterized workflow" });
1669
+ assert.deepEqual(result.details.workflow?.args, invocationArgs);
1670
+ assert.equal(result.details.workflow?.argsDigest, stableJsonDigest(invocationArgs));
1671
+ assert.equal(result.details.workflow?.receipt?.argsDigest, stableJsonDigest(invocationArgs));
1672
+ digests.push(result.details.workflow!.argsDigest!);
1673
+ }
1674
+ assert.equal(digests[0], digests[1], "object key order must not affect the canonical digest");
1675
+ assert.notEqual(digests[1], digests[2], "nested argument changes must affect the canonical digest");
1676
+ const failed = await makeExecutor([makeAgent("echo")]).executePublic(
1677
+ "parameterized-workflow-failure",
1678
+ { workflowScript: `throw new Error("expected failure");`, args, async: false },
1679
+ new AbortController().signal,
1680
+ undefined,
1681
+ makeMinimalCtx(tempDir),
1682
+ );
1683
+ assert.equal(failed.isError, true);
1684
+ assert.deepEqual(failed.details.workflow?.args, args);
1685
+ assert.equal(failed.details.workflow?.receipt?.argsDigest, stableJsonDigest(args));
1686
+ });
1687
+
1055
1688
  it("starts workflow scripts asynchronously with a portable internal run id", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1056
1689
  mockPi.onCall({ output: "async child done" });
1057
1690
  const asyncJobs: SubagentState["asyncJobs"] = new Map();
@@ -1059,6 +1692,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1059
1692
  const workflowCwd = path.join(tempDir, "workflow-cwd");
1060
1693
  fs.mkdirSync(workflowCwd);
1061
1694
  const toolCallId = "call_demo|fc_demo";
1695
+ const workflowArgs = { batch: "argument-sentinel-2233" };
1062
1696
  const context = makeMinimalCtx(tempDir);
1063
1697
  context.sessionManager.getSessionFile = () => path.join(tempDir, "parent-session.jsonl");
1064
1698
 
@@ -1066,6 +1700,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1066
1700
  toolCallId,
1067
1701
  {
1068
1702
  cwd: workflowCwd,
1703
+ args: workflowArgs,
1069
1704
  workflowScript: `emit("starting"); await runs.run("work", { agent: "helper", label: "Run async child", phase: "Execution", task: "Async work" }); return { answer: 42 };`,
1070
1705
  preflight: { version: 1, coverage: "complete", lanes: [{ key: "work", mode: "mutation", claims: ["src/work.ts"], expectedOutput: "child report" }] },
1071
1706
  mission: { summary: "Review the active backlog", labels: ["github-backlog", "review"] },
@@ -1093,13 +1728,13 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1093
1728
  assert.equal(fs.existsSync(path.join(DIRS.async, toolCallId)), false);
1094
1729
  assert.match(result.content[0]?.text ?? "", /Preflight: v1 · complete · 1 lane/);
1095
1730
  assert.match(result.content[0]?.text ?? "", /Async workflow/);
1096
- const statusPath = path.join(result.details.asyncDir!, "status.json");
1097
- let status: { runId?: string; toolCallId?: string; cwd?: string; sessionRoot?: string; state?: string; preflight?: unknown; steps?: Array<{ agent?: string; sessionName?: string; label?: string; phase?: string; workflowKey?: string; parentWorkflowRunId?: string; async?: boolean }>; workflow?: { value?: unknown; emits?: unknown[]; trace?: Array<{ key?: string; agent?: string; label?: string; phase?: string; state?: string }> } } = {};
1098
- for (let attempt = 0; attempt < 300; attempt++) {
1099
- status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
1100
- if (status.state === "complete" || status.state === "failed") break;
1101
- await new Promise((resolve) => setTimeout(resolve, 20));
1102
- }
1731
+ assert.doesNotMatch(result.content[0]?.text ?? "", /argument-sentinel-2233/);
1732
+ type WorkflowStatus = { runId?: string; toolCallId?: string; cwd?: string; sessionRoot?: string; state?: string; preflight?: unknown; steps?: Array<{ agent?: string; sessionName?: string; label?: string; phase?: string; workflowKey?: string; parentWorkflowRunId?: string; async?: boolean }>; workflow?: { value?: unknown; args?: Record<string, unknown>; argsDigest?: string; emits?: unknown[]; trace?: Array<{ key?: string; agent?: string; label?: string; phase?: string; state?: string }> } };
1733
+ const status = await waitForAsyncState(
1734
+ workflowRunId,
1735
+ (candidate) => ["complete", "failed", "partial", "paused", "stopped", "rejected"].includes(candidate.state ?? ""),
1736
+ 60_000,
1737
+ ) as WorkflowStatus;
1103
1738
  assert.equal(status.state, "complete");
1104
1739
  assert.equal(status.runId, workflowRunId);
1105
1740
  assert.equal(status.toolCallId, toolCallId);
@@ -1114,6 +1749,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1114
1749
  context,
1115
1750
  );
1116
1751
  assert.match(statusResult.content[0]?.text ?? "", /Plan: 1 lane · work/);
1752
+ assert.doesNotMatch(statusResult.content[0]?.text ?? "", /argument-sentinel-2233/);
1117
1753
  assert.doesNotMatch(statusResult.content[0]?.text ?? "", /key \| mode \| decision \| claims \| expected output \| independence/);
1118
1754
  assert.deepEqual(statusResult.details.preflight, { version: 1, coverage: "complete", lanes: [{ key: "work", mode: "mutation", claims: ["src/work.ts"], expectedOutput: "child report" }] });
1119
1755
  assert.equal(status.steps?.length, 1);
@@ -1123,14 +1759,18 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1123
1759
  assert.ok(status.steps?.every((step) => step.parentWorkflowRunId === workflowRunId));
1124
1760
  assert.equal(status.steps?.[0]?.async, true);
1125
1761
  assert.deepEqual(status.workflow?.value, { answer: 42 });
1762
+ assert.deepEqual(status.workflow?.args, workflowArgs);
1763
+ assert.equal(status.workflow?.argsDigest, stableJsonDigest(workflowArgs));
1126
1764
  assert.deepEqual(status.workflow?.emits, ["starting"]);
1127
1765
  assert.equal(mockPi.callCount(), 1);
1128
1766
  assert.ok(status.workflow?.trace?.some((entry) => entry.key === "work" && entry.agent === "echo" && entry.label === "Run async child" && entry.phase === "Execution" && entry.state === "completed"));
1129
- const traceEvents = fs.readFileSync(path.join(result.details.asyncDir!, "events.jsonl"), "utf-8")
1767
+ const workflowEvents = fs.readFileSync(path.join(result.details.asyncDir!, "events.jsonl"), "utf-8")
1130
1768
  .trim()
1131
1769
  .split("\n")
1132
- .map((line) => JSON.parse(line) as { type?: string; trace?: Array<{ key?: string; state?: string }> })
1133
- .filter((event) => event.type === "subagent.workflow.trace");
1770
+ .map((line) => JSON.parse(line) as { type?: string; argsDigest?: string; trace?: Array<{ key?: string; state?: string }> });
1771
+ assert.equal(workflowEvents.find((event) => event.type === "subagent.workflow.started")?.argsDigest, stableJsonDigest(workflowArgs));
1772
+ assert.equal(workflowEvents.find((event) => event.type === "subagent.workflow.completed")?.argsDigest, stableJsonDigest(workflowArgs));
1773
+ const traceEvents = workflowEvents.filter((event) => event.type === "subagent.workflow.trace");
1134
1774
  assert.equal(traceEvents.length, 2);
1135
1775
  assert.deepEqual(traceEvents[0]?.trace?.map(({ key, state }) => ({ key, state })), [{ key: "work", state: "started" }]);
1136
1776
  assert.deepEqual(traceEvents[1]?.trace?.map(({ key, state }) => ({ key, state })), [
@@ -1138,7 +1778,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1138
1778
  { key: "work", state: "completed" },
1139
1779
  ]);
1140
1780
  const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
1141
- const persistedResult = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { id?: string; runId?: string; toolCallId?: string; agent?: string; cwd?: string; summary?: string; workflow?: { value?: unknown; receipt?: unknown }; workflowReceipt?: { path?: string; receipt?: { workflowRunId?: string; entries?: Record<string, { key?: string; agent?: string; latestRunId?: string; resumability?: { state?: string; reason?: string }; continuation?: { runIds?: string[] } }> } }; results?: Array<{ agent?: string; sessionName?: string; workflowKey?: string; runId?: string; output?: string; usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; turns: number } }> };
1781
+ const persistedResult = JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { id?: string; runId?: string; toolCallId?: string; agent?: string; cwd?: string; summary?: string; workflow?: { value?: unknown; args?: Record<string, unknown>; argsDigest?: string; receipt?: unknown }; workflowReceipt?: { path?: string; receipt?: { workflowRunId?: string; argsDigest?: string; entries?: Record<string, { key?: string; agent?: string; latestRunId?: string; resumability?: { state?: string; reason?: string }; continuation?: { runIds?: string[] } }> } }; results?: Array<{ agent?: string; sessionName?: string; workflowKey?: string; runId?: string; output?: string; usage?: { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number; turns: number } }> };
1142
1782
  assert.equal(persistedResult.id, workflowRunId);
1143
1783
  assert.equal(persistedResult.runId, workflowRunId);
1144
1784
  assert.equal(persistedResult.toolCallId, toolCallId);
@@ -1152,9 +1792,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1152
1792
  assert.equal(fs.existsSync(path.join(result.details.asyncDir!, "control", "workflow-foreground")), false);
1153
1793
  assert.match(persistedResult.summary ?? "", /Return: \{\n "answer": 42\n\}/);
1154
1794
  assert.deepEqual(persistedResult.workflow?.value, { answer: 42 });
1795
+ assert.deepEqual(persistedResult.workflow?.args, workflowArgs);
1796
+ assert.equal(persistedResult.workflow?.argsDigest, stableJsonDigest(workflowArgs));
1155
1797
  assert.equal(persistedResult.workflow?.receipt, undefined, "status/result workflow projection must stay receipt-free");
1156
1798
  assert.equal(persistedResult.workflowReceipt?.path, path.join(result.details.asyncDir!, "workflow-receipt.json"));
1157
1799
  assert.equal(persistedResult.workflowReceipt?.receipt?.workflowRunId, workflowRunId);
1800
+ assert.equal(persistedResult.workflowReceipt?.receipt?.argsDigest, stableJsonDigest(workflowArgs));
1158
1801
  assert.equal(persistedResult.workflowReceipt?.receipt?.entries?.work?.key, "work");
1159
1802
  assert.equal(persistedResult.workflowReceipt?.receipt?.entries?.work?.agent, "echo");
1160
1803
  assert.equal(persistedResult.workflowReceipt?.receipt?.entries?.work?.latestRunId, persistedResult.results?.[0]?.runId);
@@ -1201,7 +1844,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1201
1844
  assert.equal(childCompleted, true, "expected the child to settle before simulating session cleanup");
1202
1845
  const controller = workflowControllers.get(workflowRunId);
1203
1846
  assert.ok(controller, "expected a live workflow controller before simulated cleanup");
1204
- controller.abort(new Error("Workflow stopped because the extension session was replaced or reloaded."));
1847
+ controller.abort(runtimeReplacedAbortReason());
1205
1848
  workflowControllers.clear();
1206
1849
  asyncJobs.clear();
1207
1850
 
@@ -1221,6 +1864,277 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1221
1864
  fs.rmSync(resultPath, { force: true });
1222
1865
  });
1223
1866
 
1867
+ describe("relaunching a runtime-replaced workflow", () => {
1868
+ const terminalStates = ["complete", "failed", "partial", "paused", "stopped", "rejected"];
1869
+ // SAFETY: the executor and runner write these status.json files from AsyncStatus values.
1870
+ const readStatusFile = (runId: string) => JSON.parse(fs.readFileSync(path.join(DIRS.async, runId, "status.json"), "utf-8")) as AsyncStatus;
1871
+ const setup = () => {
1872
+ const workflowControllers = new Map<string, AbortController>();
1873
+ const executor = makeExecutor([makeAgent("echo"), makeAgent("bg", { defaultAsync: true })], {}, false, undefined, true, new Map(), workflowControllers, undefined, undefined, undefined, undefined, () => {});
1874
+ const launch = async (params: Record<string, unknown>): Promise<string> => {
1875
+ const started = await executor.execute(`workflow-reuse-${Date.now()}-${Math.random().toString(16).slice(2)}`, { async: true, mission: false, ...params }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
1876
+ assert.equal(started.isError, undefined, started.content[0]?.text ?? "workflow launch failed");
1877
+ return started.details.asyncId!;
1878
+ };
1879
+ const waitFor = async (runId: string, predicate: (status: AsyncStatus) => boolean) => {
1880
+ await waitForAsyncState(runId, () => predicate(readStatusFile(runId)), 30_000);
1881
+ return readStatusFile(runId);
1882
+ };
1883
+ const settled = async (runId: string) => {
1884
+ const status = await waitFor(runId, (candidate) => terminalStates.includes(candidate.state));
1885
+ await waitForAsyncResultFile(runId, 30_000);
1886
+ return status;
1887
+ };
1888
+ const stopWhenStage2Starts = async (runId: string, reason: Error, ready = (status: AsyncStatus) => status.workflow?.trace.some((entry) => entry.key === "stage2" && entry.state === "started") ?? false) => {
1889
+ await waitFor(runId, ready);
1890
+ workflowControllers.get(runId)!.abort(reason);
1891
+ return await settled(runId);
1892
+ };
1893
+ const stopAction = (target: { id: string } | { dir: string }, sessionId?: string) => {
1894
+ const ctx = makeMinimalCtx(tempDir);
1895
+ if (sessionId) ctx.sessionManager.getSessionId = () => sessionId;
1896
+ return executor.execute(`workflow-stop-${Date.now()}`, { action: "stop", ...target }, new AbortController().signal, undefined, ctx);
1897
+ };
1898
+ return { launch, waitFor, settled, stopWhenStage2Starts, stopAction };
1899
+ };
1900
+ const resultFileText = (runId: string) => fs.readFileSync(path.join(DIRS.results, `${runId}.json`), "utf-8");
1901
+
1902
+ it("records the stop cause and script digest, then reuses the finished stage on relaunch", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1903
+ const nonce = `reuse-${Date.now()}`;
1904
+ const script = `const first = await runs.run("stage1", { agent: "echo", task: "Stage one ${nonce}" }); const second = await runs.run("stage2", { agent: "echo", task: "Stage two ${nonce}" }); return { first: first.output, second: second.output, reused: first.reused === true };`;
1905
+ const args = { nonce };
1906
+ const { launch, settled, stopWhenStage2Starts } = setup();
1907
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one done" });
1908
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, hangUntilAbort: true });
1909
+ const firstId = await launch({ workflowScript: script, args });
1910
+ const stopped = await stopWhenStage2Starts(firstId, runtimeReplacedAbortReason());
1911
+ assert.equal(stopped.state, "stopped");
1912
+ assert.equal(stopped.workflow?.stopCause, "runtime-replaced");
1913
+ assert.equal(stopped.workflow?.scriptDigest, createHash("sha256").update(script).digest("hex"));
1914
+ assert.match(resultFileText(firstId), /Async children that were still running keep running; relaunch the same workflow script with the same args to reuse finished children and re-attach to running ones\./);
1915
+ const stage1RunId = stopped.steps?.find((step) => step.workflowKey === "stage1")?.runId;
1916
+ assert.ok(stage1RunId);
1917
+ const callsAfterStop = mockPi.callCount();
1918
+
1919
+ for (const variant of [{ workflowScript: script, args: { nonce, changed: true } }, { workflowScript: `${script}\n// changed`, args }]) {
1920
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one again" });
1921
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, output: "stage two done" });
1922
+ const status = await settled(await launch(variant));
1923
+ assert.equal(status.state, "complete");
1924
+ assert.equal(status.workflow?.reusedFrom, undefined);
1925
+ }
1926
+ assert.equal(mockPi.callCount(), callsAfterStop + 4, "different args or script must launch every child");
1927
+
1928
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, output: "stage two done" });
1929
+ const relaunched = await settled(await launch({ workflowScript: script, args }));
1930
+ assert.equal(relaunched.state, "complete");
1931
+ assert.equal(mockPi.callCount(), callsAfterStop + 5, "only stage2 launches");
1932
+ assert.equal(relaunched.workflow?.reusedFrom, firstId);
1933
+ assert.deepEqual(relaunched.workflow?.value, { first: "stage one done", second: "stage two done", reused: true });
1934
+ assert.deepEqual(relaunched.steps?.map(({ workflowKey, runId, reused }) => ({ workflowKey, runId: workflowKey === "stage1" ? runId : undefined, reused })), [
1935
+ { workflowKey: "stage1", runId: stage1RunId, reused: true },
1936
+ { workflowKey: "stage2", runId: undefined, reused: undefined },
1937
+ ]);
1938
+ assert.ok(relaunched.workflow?.trace.some((entry) => entry.key === "stage1" && entry.state === "completed" && entry.reused === true));
1939
+ });
1940
+
1941
+ it("relaunches failed children, and a user stop records no stop cause and ends reuse", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1942
+ const nonce = `reuse-failed-${Date.now()}`;
1943
+ const script = `let first = null; try { first = await runs.run("stage1", { agent: "echo", task: "Stage one ${nonce}" }); } catch {} const second = await runs.run("stage2", { agent: "echo", task: "Stage two ${nonce}" }); return { first: first?.output ?? null, second: second.output };`;
1944
+ const args = { nonce };
1945
+ const { launch, settled, stopWhenStage2Starts } = setup();
1946
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, exitCode: 1, stderr: "stage one broke" });
1947
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, hangUntilAbort: true });
1948
+ const firstId = await launch({ workflowScript: script, args });
1949
+ assert.equal((await stopWhenStage2Starts(firstId, runtimeReplacedAbortReason())).workflow?.stopCause, "runtime-replaced");
1950
+ const callsAfterStop = mockPi.callCount();
1951
+
1952
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one fixed" });
1953
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, hangUntilAbort: true });
1954
+ const secondId = await launch({ workflowScript: script, args });
1955
+ const userStopped = await stopWhenStage2Starts(secondId, new Error("Stopped by user."));
1956
+ assert.equal(mockPi.callCount(), callsAfterStop + 2, "the failed stage1 launches again");
1957
+ assert.equal(userStopped.workflow?.reusedFrom, firstId);
1958
+ assert.equal(userStopped.workflow?.stopCause, undefined);
1959
+ assert.doesNotMatch(resultFileText(secondId), /relaunch the same workflow script/);
1960
+
1961
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one fixed" });
1962
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, output: "stage two done" });
1963
+ const third = await settled(await launch({ workflowScript: script, args }));
1964
+ assert.equal(third.state, "complete");
1965
+ assert.equal(third.workflow?.reusedFrom, undefined, "the newest same-script run was user-stopped");
1966
+ assert.equal(mockPi.callCount(), callsAfterStop + 4);
1967
+ });
1968
+
1969
+ const awaitedStage2Ready = (status: AsyncStatus) => Boolean(status.steps?.find((step) => step.workflowKey === "stage2" && step.async === true)?.runId);
1970
+
1971
+ it("keeps a running awaited async child alive and re-attaches to it on relaunch", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1972
+ const nonce = `reattach-${Date.now()}`;
1973
+ const release = path.join(tempDir, `${nonce}.release`);
1974
+ const script = `const first = await runs.run("stage1", { agent: "echo", task: "Stage one ${nonce}" }); const second = await runs.run("stage2", { agent: "bg", task: "Stage two ${nonce}" }); const third = await runs.run("stage3", { agent: "echo", task: "Stage three ${nonce}" }); return { first: first.output, second: second.output, secondReused: second.reused === true, third: third.output };`;
1975
+ const args = { nonce };
1976
+ const { launch, waitFor, settled, stopWhenStage2Starts } = setup();
1977
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one done" });
1978
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, waitForPath: release, output: "stage two done" });
1979
+ const callsBefore = mockPi.callCount();
1980
+ const firstId = await launch({ workflowScript: script, args });
1981
+ const stopped = await stopWhenStage2Starts(firstId, runtimeReplacedAbortReason(), awaitedStage2Ready);
1982
+ assert.equal(stopped.workflow?.stopCause, "runtime-replaced");
1983
+ const stage2RunId = stopped.steps?.find((step) => step.workflowKey === "stage2")?.runId;
1984
+ assert.ok(stage2RunId);
1985
+ for (const deadline = Date.now() + 15_000; mockPi.callCount() < callsBefore + 2 && Date.now() < deadline;) await new Promise((resolve) => setTimeout(resolve, 50));
1986
+ assert.equal(mockPi.callCount(), callsBefore + 2, "the awaited child's runner starts its prompt after the abort");
1987
+ assert.equal(readStatusFile(stage2RunId).state, "running", "a runtime-replaced abort must not stop the awaited child");
1988
+
1989
+ mockPi.onCall({ matchArgIncludes: `Stage three ${nonce}`, output: "stage three done" });
1990
+ const relaunchId = await launch({ workflowScript: script, args });
1991
+ await waitFor(relaunchId, (status) => status.workflow?.trace.some((entry) => entry.key === "stage2" && entry.state === "started") ?? false);
1992
+ fs.writeFileSync(release, "go");
1993
+ const relaunched = await settled(relaunchId);
1994
+ assert.equal(relaunched.state, "complete", relaunched.error);
1995
+ assert.equal(mockPi.callCount(), callsBefore + 3, "only stage3 launches after the relaunch");
1996
+ assert.equal(relaunched.workflow?.reusedFrom, firstId);
1997
+ assert.deepEqual(relaunched.workflow?.value, { first: "stage one done", second: "stage two done", secondReused: true, third: "stage three done" });
1998
+ assert.deepEqual(relaunched.steps?.map(({ workflowKey, reused }) => ({ workflowKey, reused })), [
1999
+ { workflowKey: "stage1", reused: true },
2000
+ { workflowKey: "stage2", reused: true },
2001
+ { workflowKey: "stage3", reused: undefined },
2002
+ ]);
2003
+ assert.equal(relaunched.steps?.[1]?.runId, stage2RunId);
2004
+ assert.equal(fs.existsSync(path.join(DIRS.async, stage2RunId, "workflow-result.json")), false, "the relaunch consumes the awaited result like the normal path");
2005
+ });
2006
+
2007
+ it("launches fresh when a re-attached child fails after the reload", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2008
+ const nonce = `reattach-failed-${Date.now()}`;
2009
+ const release = path.join(tempDir, `${nonce}.release`);
2010
+ const script = `await runs.run("stage1", { agent: "echo", task: "Stage one ${nonce}" }); let second = null; try { second = await runs.run("stage2", { agent: "bg", task: "Stage two ${nonce}" }); } catch {} return { second: second?.output ?? null, secondReused: second?.reused === true };`;
2011
+ const args = { nonce };
2012
+ const { launch, settled, stopWhenStage2Starts } = setup();
2013
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one done" });
2014
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, waitForPath: release, exitCode: 1, stderr: "stage two broke" });
2015
+ const callsBefore = mockPi.callCount();
2016
+ const firstId = await launch({ workflowScript: script, args });
2017
+ const stage2RunId = (await stopWhenStage2Starts(firstId, runtimeReplacedAbortReason(), awaitedStage2Ready)).steps?.find((step) => step.workflowKey === "stage2")?.runId;
2018
+ assert.ok(stage2RunId);
2019
+ for (const deadline = Date.now() + 15_000; mockPi.callCount() < callsBefore + 2 && Date.now() < deadline;) await new Promise((resolve) => setTimeout(resolve, 50));
2020
+ fs.writeFileSync(release, "go");
2021
+ assert.equal((await waitForAsyncState(stage2RunId, (status) => terminalStates.includes(status.state ?? ""), 30_000)).state, "failed");
2022
+
2023
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, output: "stage two fresh" });
2024
+ const relaunched = await settled(await launch({ workflowScript: script, args }));
2025
+ assert.equal(relaunched.state, "complete", relaunched.error);
2026
+ assert.equal(mockPi.callCount(), callsBefore + 3, "the failed stage2 launches again");
2027
+ assert.deepEqual(relaunched.workflow?.value, { second: "stage two fresh", secondReused: false });
2028
+ assert.equal(relaunched.steps?.find((step) => step.workflowKey === "stage2")?.reused, undefined);
2029
+ });
2030
+
2031
+ it("still stops a running awaited async child when the user stops the workflow", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2032
+ const nonce = `user-stop-awaited-${Date.now()}`;
2033
+ const { launch, stopWhenStage2Starts } = setup();
2034
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one done" });
2035
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, waitForPath: path.join(tempDir, `${nonce}.never`), output: "never" });
2036
+ const runId = await launch({ workflowScript: `await runs.run("stage1", { agent: "echo", task: "Stage one ${nonce}" }); await runs.run("stage2", { agent: "bg", task: "Stage two ${nonce}" });` });
2037
+ const stopped = await stopWhenStage2Starts(runId, new Error("Stopped by user."), awaitedStage2Ready);
2038
+ const stage2RunId = stopped.steps?.find((step) => step.workflowKey === "stage2")?.runId;
2039
+ assert.ok(stage2RunId);
2040
+ const child = await waitForAsyncState(stage2RunId, (status) => terminalStates.includes(status.state ?? ""), 30_000);
2041
+ assert.equal(child.state, "stopped");
2042
+ });
2043
+
2044
+ it("stops the workflow and its async and foreground children when the stop action names it by id prefix or dir", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2045
+ const { launch, waitFor, settled, stopAction } = setup();
2046
+ for (const form of ["prefix", "dir"] as const) {
2047
+ const nonce = `stop-action-${form}-${Date.now()}`;
2048
+ mockPi.onCall({ matchArgIncludes: `Async child ${nonce}`, waitForPath: path.join(tempDir, `${nonce}.never`), output: "never" });
2049
+ mockPi.onCall({ matchArgIncludes: `Foreground child ${nonce}`, hangUntilAbort: true });
2050
+ const runId = await launch({ workflowScript: `await Promise.all([runs.run("bg", { agent: "bg", task: "Async child ${nonce}" }), runs.run("fg", { agent: "echo", task: "Foreground child ${nonce}" })]);` });
2051
+ const running = await waitFor(runId, (status) => Boolean(status.steps?.find((step) => step.workflowKey === "bg" && step.async === true)?.runId)
2052
+ && status.steps?.find((step) => step.workflowKey === "fg")?.status === "running");
2053
+ const bgRunId = running.steps?.find((step) => step.workflowKey === "bg")?.runId;
2054
+ assert.ok(bgRunId);
2055
+
2056
+ const target = form === "prefix" ? { id: runId.slice(0, 8) } : { dir: path.join(DIRS.async, runId) };
2057
+ const foreign = await stopAction(target, "other-session");
2058
+ assert.equal(foreign.isError, true, `${form}: another session cannot stop the workflow`);
2059
+ assert.equal(readStatusFile(runId).state, "running", form);
2060
+ const result = await stopAction(target);
2061
+ assert.equal(result.isError, undefined, result.content[0]?.text);
2062
+ assert.equal(result.content[0]?.text, `Stop requested for async workflow ${runId}.`, form);
2063
+ const stopped = await settled(runId);
2064
+ assert.equal(stopped.state, "stopped", form);
2065
+ assert.equal(stopped.steps?.find((step) => step.workflowKey === "fg")?.status, "stopped", form);
2066
+ assert.doesNotMatch(resultFileText(runId), /relaunch the same workflow script/, form);
2067
+ assert.equal((await waitForAsyncState(bgRunId, (status) => terminalStates.includes(status.state ?? ""), 30_000)).state, "stopped", form);
2068
+ const workflowStopRequests = path.join(DIRS.async, runId, "control", "stop-requests");
2069
+ assert.deepEqual(fs.existsSync(workflowStopRequests) ? fs.readdirSync(workflowStopRequests) : [], [], `${form}: no unread stop request is left for the in-process workflow`);
2070
+ }
2071
+ });
2072
+
2073
+ it("stops a re-attached child when the user stops the relaunched workflow", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2074
+ const nonce = `reattach-user-stop-${Date.now()}`;
2075
+ const script = `await runs.run("stage1", { agent: "echo", task: "Stage one ${nonce}" }); await runs.run("stage2", { agent: "bg", task: "Stage two ${nonce}" });`;
2076
+ const args = { nonce };
2077
+ const { launch, stopWhenStage2Starts } = setup();
2078
+ mockPi.onCall({ matchArgIncludes: `Stage one ${nonce}`, output: "stage one done" });
2079
+ mockPi.onCall({ matchArgIncludes: `Stage two ${nonce}`, waitForPath: path.join(tempDir, `${nonce}.never`), output: "never" });
2080
+ const firstId = await launch({ workflowScript: script, args });
2081
+ const stage2RunId = (await stopWhenStage2Starts(firstId, runtimeReplacedAbortReason(), awaitedStage2Ready)).steps?.find((step) => step.workflowKey === "stage2")?.runId;
2082
+ assert.ok(stage2RunId);
2083
+ assert.equal(readStatusFile(stage2RunId).state, "running");
2084
+
2085
+ const relaunchId = await launch({ workflowScript: script, args });
2086
+ const journalPath = path.join(DIRS.async, relaunchId, "workflow-children.jsonl");
2087
+ await stopWhenStage2Starts(relaunchId, new Error("Stopped by user."), () => fs.existsSync(journalPath) && fs.readFileSync(journalPath, "utf-8").includes(`"runId":"${stage2RunId}"`));
2088
+ const child = await waitForAsyncState(stage2RunId, (status) => terminalStates.includes(status.state ?? ""), 30_000);
2089
+ assert.equal(child.state, "stopped");
2090
+ });
2091
+
2092
+ it("ends the wait on a revived awaited child when the runtime is replaced, leaving its result for the relaunch", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2093
+ const nonce = `revived-${Date.now()}`;
2094
+ const release = path.join(tempDir, `${nonce}.release`);
2095
+ mockPi.onCall({ exitCode: 1, jsonl: [{ type: "message_end", message: { role: "assistant", content: [], model: "openai-codex/gpt-5.6-luna", stopReason: "error", errorMessage: "This operation was aborted", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: { total: 0 } } } }] });
2096
+ mockPi.onCall({ waitForPath: release, output: "revived done" });
2097
+ const callsBefore = mockPi.callCount();
2098
+ const { launch, stopWhenStage2Starts } = setup();
2099
+ const runId = await launch({ workflowScript: `await runs.run("stage2", { agent: "echo", task: "Revive ${nonce}" });` });
2100
+ const stopped = await stopWhenStage2Starts(runId, runtimeReplacedAbortReason(), (status) => mockPi.callCount() >= callsBefore + 2 && Boolean(status.steps?.find((step) => step.workflowKey === "stage2")?.async));
2101
+ assert.equal(stopped.workflow?.stopCause, "runtime-replaced");
2102
+ const revivedId = stopped.steps?.find((step) => step.workflowKey === "stage2")?.runId;
2103
+ assert.ok(revivedId);
2104
+ await new Promise((resolve) => setTimeout(resolve, 700));
2105
+ fs.writeFileSync(release, "go");
2106
+ await waitForAsyncState(revivedId, (status) => status.state === "complete", 30_000);
2107
+ await new Promise((resolve) => setTimeout(resolve, 1_200));
2108
+ assert.equal(fs.existsSync(path.join(DIRS.async, revivedId, "workflow-result.json")), true, "the stopped run must not import the revived child's result");
2109
+ });
2110
+
2111
+ it("lets exactly one of two concurrent relaunches import a finished child", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2112
+ const nonce = `claim-${Date.now()}`;
2113
+ const script = `const work = await runs.run("work", { agent: "echo", task: "Seeded ${nonce}" }); return { output: work.output, reused: work.reused === true };`;
2114
+ const args = { nonce };
2115
+ const sessionId = "session-123";
2116
+ const priorDir = path.join(DIRS.async, `prior-${nonce}`);
2117
+ const childDir = path.join(DIRS.async, `child-${nonce}`);
2118
+ const priorStatus = { runId: path.basename(priorDir), mode: "workflow", sessionId, state: "stopped", startedAt: 1, endedAt: Date.now(), steps: [], workflow: { trace: [], emits: [], console: [], args, argsDigest: stableJsonDigest(args), scriptDigest: createHash("sha256").update(script).digest("hex"), stopCause: "runtime-replaced" } } satisfies AsyncStatus;
2119
+ fs.mkdirSync(priorDir, { recursive: true });
2120
+ fs.writeFileSync(path.join(priorDir, "status.json"), JSON.stringify(priorStatus));
2121
+ appendWorkflowChildJournal(priorDir, { type: "start", key: "work", fingerprint: workflowChildFingerprint({ agent: "echo", task: `Seeded ${nonce}` }), runId: path.basename(childDir) });
2122
+ updateTerminalRunIndex(priorDir, priorStatus);
2123
+ fs.mkdirSync(childDir, { recursive: true });
2124
+ fs.writeFileSync(path.join(childDir, "status.json"), JSON.stringify({ runId: path.basename(childDir), mode: "single", sessionId, parentWorkflowRunId: priorStatus.runId, state: "complete", startedAt: 1, steps: [{ agent: "echo", status: "complete" }] }));
2125
+ writeAsyncResultFile(path.join(childDir, "workflow-result.json"), { id: path.basename(childDir), runId: path.basename(childDir), sessionId, toolCallId: "seeded-call", asyncDir: childDir, state: "complete", success: true, results: [{ agent: "echo", output: "seeded output", success: true }] });
2126
+ mockPi.onCall({ matchArgIncludes: `Seeded ${nonce}`, output: "fresh output" });
2127
+ const callsBefore = mockPi.callCount();
2128
+ const { launch, settled } = setup();
2129
+ const relaunched = await Promise.all([launch({ workflowScript: script, args }), launch({ workflowScript: script, args })].map(async (id) => await settled(await id)));
2130
+ assert.deepEqual(relaunched.map((status) => status.workflow?.value).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))), [
2131
+ { output: "fresh output", reused: false },
2132
+ { output: "seeded output", reused: true },
2133
+ ]);
2134
+ assert.equal(mockPi.callCount(), callsBefore + 1);
2135
+ });
2136
+ });
2137
+
1224
2138
  it("delivers a terminal Darwin workflow failure after demand disappears", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1225
2139
  const state: SubagentState = {
1226
2140
  baseCwd: tempDir,
@@ -1378,7 +2292,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1378
2292
  await new Promise((resolve) => setTimeout(resolve, 20));
1379
2293
  }
1380
2294
  assert.equal(workflowResult.state, "complete");
1381
- assert.match(workflowResult.results?.[0]?.output ?? "", /Async: external/);
2295
+ assert.equal(workflowResult.results?.[0]?.state, "running");
2296
+ assert.equal(workflowResult.results?.[0]?.output, "");
2297
+ assert.equal(workflowResult.results?.[0]?.success, undefined);
1382
2298
  assert.equal(await waitForFileContent(markerPath, "started"), "started");
1383
2299
  assert.equal(mockPi.callCount(), 0);
1384
2300
 
@@ -1429,53 +2345,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1429
2345
 
1430
2346
  assert.equal(result.isError, undefined, result.content[0]?.text ?? "launch failed");
1431
2347
  assert.ok(result.details.asyncId);
1432
- assert.match(result.content[0]?.text ?? "", /Async: external/);
1433
- assert.equal(await waitForFileContent(markerPath, "started"), "started");
1434
- assert.equal(mockPi.callCount(), 0);
1435
-
1436
- const resultPath = path.join(DIRS.results, `${result.details.asyncId}.json`);
1437
- let runResult: { state?: string } = {};
1438
- for (let attempt = 0; attempt < 100; attempt++) {
1439
- if (fs.existsSync(resultPath)) runResult = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
1440
- if (runResult.state === "complete" || runResult.state === "failed") break;
1441
- await new Promise((resolve) => setTimeout(resolve, 20));
1442
- }
1443
- assert.equal(runResult.state, "complete");
1444
-
1445
- fs.rmSync(result.details.asyncDir!, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
1446
- fs.rmSync(resultPath, { force: true });
1447
- });
1448
-
1449
- it("lets explicit fast false opt out external CLI agents from inherited fast mode", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1450
- const markerPath = path.join(tempDir, "external-fast-false-started");
1451
- const executor = makeExecutor([
1452
- makeAgent("external", {
1453
- fast: true,
1454
- runner: { type: "external-cli", command: process.execPath, args: ["-e", `require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "started"); process.stdout.write("external fast false result")`] },
1455
- }),
1456
- ]);
1457
-
1458
- const rejected = await executor.execute(
1459
- "external-fast-inherited",
1460
- { agent: "external", task: "Run external", async: true },
1461
- new AbortController().signal,
1462
- undefined,
1463
- makeMinimalCtx(tempDir),
1464
- );
1465
- assert.equal(rejected.isError, true);
1466
- assert.match(rejected.content[0]?.text ?? "", /does not support fast mode/);
1467
- assert.equal(fs.existsSync(markerPath), false);
1468
-
1469
- const result = await executor.execute(
1470
- "external-fast-false",
1471
- { agent: "external", task: "Run external", async: true, fast: false },
1472
- new AbortController().signal,
1473
- undefined,
1474
- makeMinimalCtx(tempDir),
1475
- );
1476
-
1477
- assert.equal(result.isError, undefined, result.content[0]?.text ?? "launch failed");
1478
- assert.ok(result.details.asyncId);
2348
+ assert.match(result.content[0]?.text ?? "", /Async: external/);
1479
2349
  assert.equal(await waitForFileContent(markerPath, "started"), "started");
1480
2350
  assert.equal(mockPi.callCount(), 0);
1481
2351
 
@@ -1492,31 +2362,39 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1492
2362
  fs.rmSync(resultPath, { force: true });
1493
2363
  });
1494
2364
 
1495
- it("runs external CLI agents with fallback models without registry validation", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1496
- const markerPath = path.join(tempDir, "external-fallback-started");
2365
+ it("lets explicit fast false opt out external CLI agents from inherited fast mode", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2366
+ const markerPath = path.join(tempDir, "external-fast-false-started");
1497
2367
  const executor = makeExecutor([
1498
2368
  makeAgent("external", {
1499
- runner: { type: "external-cli", command: process.execPath, args: ["-e", `require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "started")`] },
1500
- fallbackModels: ["mock/fallback"],
2369
+ fast: true,
2370
+ runner: { type: "external-cli", command: process.execPath, args: ["-e", `require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "started"); process.stdout.write("external fast false result")`] },
1501
2371
  }),
1502
2372
  ]);
1503
- const result = await executor.execute(
1504
- "external-fallback-model",
2373
+
2374
+ const rejected = await executor.execute(
2375
+ "external-fast-inherited",
1505
2376
  { agent: "external", task: "Run external", async: true },
1506
2377
  new AbortController().signal,
1507
2378
  undefined,
1508
- {
1509
- ...makeMinimalCtx(tempDir),
1510
- modelRegistry: { getAvailable: () => [{ provider: "other", id: "known" }] },
1511
- },
2379
+ makeMinimalCtx(tempDir),
2380
+ );
2381
+ assert.equal(rejected.isError, true);
2382
+ assert.match(rejected.content[0]?.text ?? "", /does not support fast mode/);
2383
+ assert.equal(fs.existsSync(markerPath), false);
2384
+
2385
+ const result = await executor.execute(
2386
+ "external-fast-false",
2387
+ { agent: "external", task: "Run external", async: true, fast: false },
2388
+ new AbortController().signal,
2389
+ undefined,
2390
+ makeMinimalCtx(tempDir),
1512
2391
  );
1513
2392
 
1514
- assert.equal(result.isError, undefined);
1515
- assert.doesNotMatch(result.content[0]?.text ?? "", /Unknown subagent model/);
2393
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "launch failed");
2394
+ assert.ok(result.details.asyncId);
1516
2395
  assert.equal(await waitForFileContent(markerPath, "started"), "started");
1517
2396
  assert.equal(mockPi.callCount(), 0);
1518
2397
 
1519
- assert.ok(result.details.asyncId);
1520
2398
  const resultPath = path.join(DIRS.results, `${result.details.asyncId}.json`);
1521
2399
  let runResult: { state?: string } = {};
1522
2400
  for (let attempt = 0; attempt < 100; attempt++) {
@@ -1530,43 +2408,6 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1530
2408
  fs.rmSync(resultPath, { force: true });
1531
2409
  });
1532
2410
 
1533
- it("rejects external CLI fork context before fallback model validation", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1534
- const markerPath = path.join(tempDir, "external-fork-started");
1535
- const parentSessionFile = path.join(mockPi.dir, "external-fork-parent.jsonl");
1536
- fs.writeFileSync(parentSessionFile, `${JSON.stringify({ type: "session", version: 3, id: "parent", cwd: tempDir })}\n`, "utf-8");
1537
- const ctx = makeMinimalCtx(tempDir);
1538
- Object.assign(ctx.sessionManager, {
1539
- getSessionFile: () => parentSessionFile,
1540
- getLeafId: () => "parent-leaf",
1541
- openSession: () => ({
1542
- createBranchedSession: () => parentSessionFile,
1543
- }),
1544
- });
1545
- const executor = makeExecutor([
1546
- makeAgent("external", {
1547
- runner: { type: "external-cli", command: process.execPath, args: ["-e", `require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "started")`] },
1548
- defaultContext: "fork",
1549
- fallbackModels: ["mock/fallback"],
1550
- }),
1551
- ]);
1552
- const result = await executor.execute(
1553
- "external-fork-fallback",
1554
- { agent: "external", task: "Run external", async: true },
1555
- new AbortController().signal,
1556
- undefined,
1557
- {
1558
- ...ctx,
1559
- modelRegistry: { getAvailable: () => [{ provider: "other", id: "known" }] },
1560
- },
1561
- );
1562
-
1563
- assert.equal(result.isError, true);
1564
- assert.match(result.content[0]?.text ?? "", /does not support: fork context/);
1565
- assert.doesNotMatch(result.content[0]?.text ?? "", /Unknown subagent model/);
1566
- assert.equal(mockPi.callCount(), 0);
1567
- assert.equal(fs.existsSync(markerPath), false);
1568
- });
1569
-
1570
2411
  it("rejects explicit model overrides for external CLI agents", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1571
2412
  const executor = makeExecutor([
1572
2413
  makeAgent("external", {
@@ -1703,11 +2544,15 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1703
2544
  fs.rmSync(resultPath, { force: true });
1704
2545
  });
1705
2546
 
1706
- it("notifies the parent when an async workflow child needs attention", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2547
+ it("notifies the parent when an async workflow child needs attention", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async (t) => {
2548
+ const releasePath = path.join(mockPi.dir, "attention-observed");
2549
+ // Keep the child idle until the parent has observed both status and delivery.
2550
+ // Also unblock it on assertion failure, rather than leaking a waiting runner.
2551
+ t.after(() => fs.writeFileSync(releasePath, "release"));
1707
2552
  mockPi.onCall({
1708
2553
  steps: [
1709
2554
  { jsonl: [events.toolStart("read", { path: "src/example.ts" }), events.toolEnd("read"), events.toolResult("read", "contents"), mockAssistantMessage("Started", "tool_use")] },
1710
- { delay: 2_500, jsonl: [events.assistantMessage("Done")] },
2555
+ { waitForPath: releasePath, jsonl: [events.assistantMessage("Done")] },
1711
2556
  ],
1712
2557
  });
1713
2558
  const asyncJobs: SubagentState["asyncJobs"] = new Map();
@@ -1738,23 +2583,15 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1738
2583
  const { asyncId: workflowRunId, asyncDir } = result.details;
1739
2584
  assert.ok(workflowRunId);
1740
2585
  assert.ok(asyncDir);
1741
- const statusPath = path.join(asyncDir, "status.json");
1742
2586
  const eventsPath = path.join(asyncDir, "events.jsonl");
1743
2587
  const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
1744
- let liveStatus: AsyncStatus | undefined;
1745
- const activityDeadline = Date.now() + 5_000;
1746
- while (Date.now() < activityDeadline && !fs.existsSync(resultPath)) {
1747
- if (fs.existsSync(statusPath)) {
1748
- const candidate = JSON.parse(fs.readFileSync(statusPath, "utf-8")) as AsyncStatus;
1749
- if (candidate.activityState === "needs_attention" && !candidate.steps?.[0]?.currentTool) {
1750
- liveStatus = candidate;
1751
- break;
1752
- }
1753
- }
1754
- await new Promise((resolve) => setTimeout(resolve, 50));
1755
- }
2588
+ const liveStatus = await waitForAsyncState(
2589
+ workflowRunId,
2590
+ (candidate) => candidate.activityState === "needs_attention" && !candidate.steps?.[0]?.currentTool
2591
+ && controlPayloads.some((payload) => payload.event?.type === "needs_attention"),
2592
+ 60_000,
2593
+ ) as AsyncStatus;
1756
2594
 
1757
- assert.ok(liveStatus, "expected workflow status to expose idle child attention");
1758
2595
  assert.equal(liveStatus.activityState, "needs_attention");
1759
2596
  assert.equal(liveStatus.steps?.[0]?.activityState, "needs_attention");
1760
2597
  assert.equal(liveStatus.steps?.[0]?.workflowKey, "stalled-review");
@@ -1786,11 +2623,9 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1786
2623
  assert.equal(persisted.event?.workflowKey, "stalled-review");
1787
2624
  assert.equal(controlRecords.filter((record) => record.event?.type === "needs_attention").length, 1);
1788
2625
 
1789
- const completionDeadline = Date.now() + 5_000;
1790
- while (!fs.existsSync(resultPath)) {
1791
- if (Date.now() > completionDeadline) assert.fail("Timed out waiting for async workflow completion");
1792
- await new Promise((resolve) => setTimeout(resolve, 50));
1793
- }
2626
+ assert.equal(fs.existsSync(resultPath), false, "child must remain live until attention is observed");
2627
+ fs.writeFileSync(releasePath, "release");
2628
+ await waitForAsyncResultFile(workflowRunId, 60_000);
1794
2629
  fs.rmSync(asyncDir, { recursive: true, force: true });
1795
2630
  fs.rmSync(resultPath, { force: true });
1796
2631
  });
@@ -1867,11 +2702,133 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1867
2702
  assert.equal(child?.output, "default async child done");
1868
2703
  assert.ok(child?.runId);
1869
2704
  assert.equal(result.details.results[0]?.finalOutput, "default async child done");
1870
- assert.equal(fs.existsSync(path.join(DIRS.async, child.runId)), true);
1871
- fs.rmSync(path.join(DIRS.async, child.runId), { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
2705
+ const childDir = path.join(DIRS.async, child.runId);
2706
+ assert.equal(fs.existsSync(childDir), true);
2707
+ assert.equal(fs.existsSync(path.join(childDir, "workflow-result.json")), false);
2708
+ for (const localResultDir of ["result-pending", "result-index"]) {
2709
+ const localPath = path.join(childDir, localResultDir);
2710
+ const jsonFiles = fs.existsSync(localPath)
2711
+ ? fs.readdirSync(localPath, { recursive: true }).filter((entry) => String(entry).endsWith(".json"))
2712
+ : [];
2713
+ assert.deepEqual(jsonFiles, [], `${localResultDir} retained result metadata: ${jsonFiles.join(", ")}`);
2714
+ }
2715
+ fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
1872
2716
  fs.rmSync(path.join(DIRS.results, `${child.runId}.json`), { force: true });
1873
2717
  });
1874
2718
 
2719
+ it("retains workflow publication recovery files when the executor await is already aborted", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2720
+ const runId = `abort-await-${Date.now()}`;
2721
+ const sessionId = "abort-session";
2722
+ const toolCallId = "abort-call";
2723
+ const childDir = path.join(DIRS.async, runId);
2724
+ const resultPath = path.join(childDir, "workflow-result.json");
2725
+ fs.mkdirSync(childDir, { recursive: true });
2726
+ fs.writeFileSync(path.join(childDir, "mission.json"), "{}", "utf-8");
2727
+ const payload = { id: runId, runId, sessionId, toolCallId, asyncDir: childDir, state: "complete", success: true, results: [{ agent: "echo", output: "recoverable publication—not imported", success: true }] };
2728
+ assert.deepEqual(writeAsyncResultFile(resultPath, payload), { state: "public" });
2729
+ writePendingAsyncResultFile(resultPath, payload);
2730
+ const encodedRun = encodeIndexSegment(runId);
2731
+ const seededPaths = [
2732
+ resultPath,
2733
+ path.join(childDir, "result-pending", encodeIndexSegment(sessionId), `${encodedRun}.json`),
2734
+ path.join(childDir, "result-index", "sessions", encodeIndexSegment(sessionId), `${encodedRun}.json`),
2735
+ path.join(childDir, "result-index", "runs", `${encodedRun}.json`),
2736
+ path.join(childDir, "result-index", "tool-calls", encodeIndexSegment(toolCallId), `${encodedRun}.json`),
2737
+ path.join(childDir, "result-index", "observers", "mission", `${encodedRun}.json`),
2738
+ ];
2739
+ for (const file of seededPaths) assert.equal(fs.statSync(file).isFile(), true, file);
2740
+ const before = seededPaths.map((file) => fs.readFileSync(file));
2741
+ const piEvents = createEventBus();
2742
+ let unsubscribe = () => {};
2743
+ const terminal = new Promise<void>((resolve, reject) => {
2744
+ const timer = setTimeout(() => reject(new Error(`runner ${runId} did not terminate`)), 5_000);
2745
+ unsubscribe = piEvents.on(SUBAGENT_PROCESS_TERMINAL_EVENT, (value) => {
2746
+ if ((value as { runId?: string }).runId !== runId) return;
2747
+ clearTimeout(timer);
2748
+ resolve();
2749
+ });
2750
+ });
2751
+ mockPi.onCall({ output: "runner output" });
2752
+ const controller = new AbortController();
2753
+ controller.abort();
2754
+ try {
2755
+ const result = await makeExecutor([makeAgent("echo")], {}, false, undefined, true, new Map(), undefined, undefined, piEvents).execute(
2756
+ "abort-workflow-await",
2757
+ { agent: "echo", task: "Do not import the seeded result", async: true, workflowAwaitAsync: true, workflowChildAsyncId: runId },
2758
+ controller.signal,
2759
+ undefined,
2760
+ makeMinimalCtx(tempDir),
2761
+ );
2762
+ assert.equal(result.isError, true);
2763
+ assert.equal(result.details.runId, runId);
2764
+ assert.equal(result.details.asyncDir, childDir);
2765
+ assert.equal(result.details.results.length, 1);
2766
+ assert.equal(result.details.results[0]?.exitCode, 1);
2767
+ assert.equal(result.details.results[0]?.stopped, true);
2768
+ assert.equal(result.details.results[0]?.timedOut, undefined);
2769
+ assert.equal(result.details.results[0]?.error, "Workflow stopped before async child completed.");
2770
+ assert.equal(result.details.results[0]?.finalOutput, "Workflow stopped before async child completed.");
2771
+ assert.doesNotMatch(result.content[0]?.text ?? "", /recoverable publication—not imported/);
2772
+ seededPaths.forEach((file, index) => assert.deepEqual(fs.readFileSync(file), before[index], file));
2773
+ await terminal;
2774
+ } finally {
2775
+ unsubscribe();
2776
+ fs.rmSync(childDir, { recursive: true, force: true });
2777
+ fs.rmSync(path.join(DIRS.results, `${runId}.json`), { force: true });
2778
+ removeResultIndex(DIRS.results, sessionId, runId, toolCallId);
2779
+ }
2780
+ });
2781
+
2782
+ it("preserves imported workflow delivery when payload cleanup fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async (t) => {
2783
+ const runId = `cleanup-failure-${Date.now()}`;
2784
+ const sessionId = "cleanup-session";
2785
+ const toolCallId = "cleanup-call";
2786
+ const childDir = path.join(DIRS.async, runId);
2787
+ const resultPath = path.join(childDir, "workflow-result.json");
2788
+ fs.mkdirSync(childDir, { recursive: true });
2789
+ fs.writeFileSync(path.join(childDir, "mission.json"), "{}", "utf-8");
2790
+ const payload = { id: runId, runId, sessionId, toolCallId, asyncDir: childDir, state: "complete", success: true, results: [{ agent: "echo", output: "imported despite cleanup failure", success: true, usage: { input: 3, output: 2, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 1 } }] };
2791
+ writeAsyncResultFile(resultPath, payload);
2792
+ writePendingAsyncResultFile(resultPath, payload);
2793
+ const originalRmSync = fsDefault.rmSync;
2794
+ let attempted = false;
2795
+ t.mock.method(fsDefault, "rmSync", ((target: fs.PathLike, options?: fs.RmDirOptions) => {
2796
+ if (path.resolve(String(target)) === path.resolve(resultPath)) {
2797
+ attempted = true;
2798
+ const error = new Error("denied") as NodeJS.ErrnoException;
2799
+ error.code = "EACCES";
2800
+ throw error;
2801
+ }
2802
+ return originalRmSync(target, options);
2803
+ }) as typeof fsDefault.rmSync);
2804
+ syncBuiltinESMExports();
2805
+ mockPi.onCall({ output: "runner output" });
2806
+ try {
2807
+ const result = await makeExecutor([makeAgent("echo")]).execute(
2808
+ "cleanup-failure-await",
2809
+ { agent: "echo", task: "Import seeded result", async: true, workflowAwaitAsync: true, workflowChildAsyncId: runId },
2810
+ new AbortController().signal,
2811
+ undefined,
2812
+ makeMinimalCtx(tempDir),
2813
+ );
2814
+ assert.equal(attempted, true);
2815
+ assert.equal(result.isError, undefined, result.content[0]?.text);
2816
+ assert.equal(result.details.results[0]?.finalOutput, "imported despite cleanup failure");
2817
+ assert.equal(result.details.results[0]?.exitCode, 0);
2818
+ assert.deepEqual(result.details.results[0]?.usage, payload.results[0].usage);
2819
+ assert.equal(fs.existsSync(resultPath), true);
2820
+ for (const localResultDir of ["result-pending", "result-index"]) {
2821
+ const localPath = path.join(childDir, localResultDir);
2822
+ assert.deepEqual(fs.existsSync(localPath) ? fs.readdirSync(localPath, { recursive: true }).filter((entry) => String(entry).endsWith(".json")) : [], []);
2823
+ }
2824
+ } finally {
2825
+ t.mock.restoreAll();
2826
+ syncBuiltinESMExports();
2827
+ fs.rmSync(childDir, { recursive: true, force: true });
2828
+ fs.rmSync(path.join(DIRS.results, `${runId}.json`), { force: true });
2829
+ }
2830
+ });
2831
+
1875
2832
  it("keeps ordinary async workflow child results in the watcher-owned path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
1876
2833
  mockPi.onCall({ output: "async child done" });
1877
2834
  const executor = makeExecutor([makeAgent("echo")]);
@@ -1886,10 +2843,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
1886
2843
  assert.ok(childRunId);
1887
2844
  const childDir = path.join(DIRS.async, childRunId);
1888
2845
  const childResultPath = path.join(DIRS.results, `${childRunId}.json`);
1889
- for (let attempt = 0; attempt < 200 && !fs.existsSync(childResultPath); attempt++) {
1890
- await new Promise((resolve) => setTimeout(resolve, 20));
1891
- }
1892
- assert.equal(fs.existsSync(childResultPath), true);
2846
+ await waitForAsyncResultFile(childRunId, 60_000);
1893
2847
  assert.equal(fs.existsSync(path.join(childDir, "workflow-result.json")), false);
1894
2848
  fs.rmSync(childDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
1895
2849
  fs.rmSync(childResultPath, { force: true });
@@ -2348,7 +3302,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2348
3302
  execFileSync("git", ["add", "base.txt"], { cwd: tempDir });
2349
3303
  execFileSync("git", ["commit", "-m", "base"], { cwd: tempDir, stdio: "ignore" });
2350
3304
  mockPi.onCall({ output: "isolated feature", writeFiles: [{ path: "feature.txt", content: "feature\n" }] });
2351
- const executor = makeExecutor([makeAgent("worker", { completionGuard: false })]);
3305
+ const executor = makeExecutor([makeAgent("worker")]);
2352
3306
 
2353
3307
  const result = await executor.execute(
2354
3308
  "direct-worktree",
@@ -2424,28 +3378,6 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2424
3378
  assert.equal(path.basename(sessionHeader.cwd), path.basename(callCwd));
2425
3379
  });
2426
3380
 
2427
- it("rejects workflowScript implementation children under a read-only capability ceiling before spawn", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2428
- mockPi.onCall({ output: "completed without edits" });
2429
- const executor = makeExecutor([makeAgent("worker")]);
2430
-
2431
- const result = await executor.execute(
2432
- "workflow-readonly-implementation-contract",
2433
- {
2434
- async: false,
2435
- workflowScript: `return await runs.run("impl", { agent: "worker", task: "Implement the requested source fix" });`,
2436
- capabilityCeiling: { version: 1, allowedTools: ["read", "grep", "find", "ls", "contact_supervisor"], denyExtensions: true, sources: ["test"] },
2437
- },
2438
- new AbortController().signal,
2439
- undefined,
2440
- makeMinimalCtx(tempDir),
2441
- );
2442
-
2443
- assert.equal(result.isError, true);
2444
- assert.match(result.content[0]?.text ?? "", /no mutation-capable tools/);
2445
- assert.doesNotMatch(result.content[0]?.text ?? "", /completed without making edits/);
2446
- assert.equal(mockPi.callCount(), 0);
2447
- });
2448
-
2449
3381
  it("stringifies workflow child results without object placeholders", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
2450
3382
  mockPi.onCall({ output: "first report", matchArgIncludes: "Review" });
2451
3383
  mockPi.onCall({ output: "second report", matchArgIncludes: "Monitor" });
@@ -2939,7 +3871,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2939
3871
  const usefulReport = "# Review findings\n\nThe implementation loses the final report.";
2940
3872
  const sharedOutput = path.join(tempDir, "review.md");
2941
3873
  mockPi.onCall({ stdoutRaw: `${JSON.stringify(events.assistantMessage(usefulReport))}\n` });
2942
- const executor = makeExecutor([makeAgent("reviewer", { tools: ["read"], completionGuard: false })]);
3874
+ const executor = makeExecutor([makeAgent("reviewer", { tools: ["read"] })]);
2943
3875
 
2944
3876
  const result = await executor.execute(
2945
3877
  "scripted-workflow-file-only-acceptance-collision",
@@ -2982,8 +3914,8 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
2982
3914
  });
2983
3915
  mockPi.onCall({ output: "Read-only review completed.", matchArgIncludes: "Review the persisted implementation report without editing it" });
2984
3916
  const executor = makeExecutor([
2985
- makeAgent("worker", { tools: ["read", "write"], completionGuard: false }),
2986
- makeAgent("reviewer", { tools: ["read"], completionGuard: false }),
3917
+ makeAgent("worker", { tools: ["read", "write"] }),
3918
+ makeAgent("reviewer", { tools: ["read"] }),
2987
3919
  ]);
2988
3920
 
2989
3921
  const result = await executor.execute(
@@ -3044,13 +3976,14 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3044
3976
  assert.match(result.content[0]?.text ?? "", new RegExp(`Workflow '${workflowId}' validation failed before child launch; no children launched`));
3045
3977
  assert.match(result.content[0]?.text ?? "", /Parallel plus sequential rewrite/);
3046
3978
  assert.deepEqual(result.details.results, []);
3979
+ assert.equal(result.details.workflow?.failureKind, "validation");
3047
3980
  });
3048
3981
 
3049
3982
  it("replaces stale workflow output when a child claims its path but writes no report", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3050
3983
  const sharedOutput = path.join(tempDir, "failed-review.md");
3051
3984
  fs.writeFileSync(sharedOutput, "stale workflow output", "utf-8");
3052
3985
  mockPi.onCall({ exitCode: 1, stderr: "review child failed before writing output" });
3053
- const executor = makeExecutor([makeAgent("reviewer", { completionGuard: false })]);
3986
+ const executor = makeExecutor([makeAgent("reviewer")]);
3054
3987
 
3055
3988
  const result = await executor.execute(
3056
3989
  "scripted-workflow-missing-child-output-collision",
@@ -3173,12 +4106,12 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3173
4106
  assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state !== "started").map(({ state }) => state).sort(), ["completed", "failed"]);
3174
4107
  });
3175
4108
 
3176
- it("reports keyed runs.all result access after siblings settle", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4109
+ it("rejects keyed runs.all result access before launching children", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3177
4110
  mockPi.onCall({ output: "first child completed", matchArgIncludes: "First task" });
3178
4111
  mockPi.onCall({ output: "second child completed", matchArgIncludes: "Second task" });
3179
4112
  const executor = makeExecutor([makeAgent("echo")]);
3180
4113
 
3181
- const result = await executor.execute(
4114
+ const result = await executor.executePublic(
3182
4115
  "scripted-workflow-runs-all-keyed-result-access",
3183
4116
  {
3184
4117
  async: false,
@@ -3196,10 +4129,42 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3196
4129
  );
3197
4130
 
3198
4131
  assert.equal(result.isError, true);
3199
- assert.equal(mockPi.callCount(), 2);
3200
- assert.match(result.content[0]?.text ?? "", /runs\.all resolves to an ordered array, not a key map/);
3201
- assert.match(result.content[0]?.text ?? "", /Use results\[0\], array destructuring, or results\.map/);
3202
- assert.deepEqual(result.details.workflow?.trace.filter((entry) => entry.state === "completed").map(({ key }) => key).sort(), ["first", "second"]);
4132
+ assert.equal(mockPi.callCount(), 0);
4133
+ const payload = JSON.parse(result.content[0]?.text ?? "null") as { ok?: boolean; errors?: Array<{ message?: string }> };
4134
+ assert.equal(payload.ok, false);
4135
+ assert.match(payload.errors?.[0]?.message ?? "", /runs\.all returns an ordered array/);
4136
+ assert.match(payload.errors?.[0]?.message ?? "", /'children\.first' is keyed access/);
4137
+ assert.equal(result.details.workflow, undefined);
4138
+ });
4139
+
4140
+ it("allows keyed access on a nested binding that shadows a runs.all result", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4141
+ mockPi.onCall({ output: "first child completed", matchArgIncludes: "First task" });
4142
+ const executor = makeExecutor([makeAgent("echo")]);
4143
+
4144
+ const result = await executor.executePublic(
4145
+ "scripted-workflow-runs-all-shadowed-result-access",
4146
+ {
4147
+ async: false,
4148
+ workflowScript: `
4149
+ const children = await runs.all([
4150
+ { key: "first", agent: "echo", task: "First task" }
4151
+ ]);
4152
+ let local;
4153
+ {
4154
+ const children = { first: { output: "shadowed" } };
4155
+ local = children.first.output;
4156
+ }
4157
+ return { local, child: children[0].output };
4158
+ `,
4159
+ },
4160
+ new AbortController().signal,
4161
+ undefined,
4162
+ makeMinimalCtx(tempDir),
4163
+ );
4164
+
4165
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
4166
+ assert.equal(mockPi.callCount(), 1);
4167
+ assert.deepEqual(result.details.workflow?.value, { local: "shadowed", child: "first child completed" });
3203
4168
  });
3204
4169
 
3205
4170
  it("keeps array access working when runs.all child keys collide with array properties", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
@@ -3207,7 +4172,7 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3207
4172
  mockPi.onCall({ output: "map child completed", matchArgIncludes: "Map task" });
3208
4173
  const executor = makeExecutor([makeAgent("echo")]);
3209
4174
 
3210
- const result = await executor.execute(
4175
+ const result = await executor.executePublic(
3211
4176
  "scripted-workflow-runs-all-colliding-key-access",
3212
4177
  {
3213
4178
  async: false,
@@ -3276,6 +4241,97 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3276
4241
  assert.deepEqual(result.details.workflow?.value, ["first child completed", "second child completed"]);
3277
4242
  });
3278
4243
 
4244
+ it("cuts an oversized foreground workflow return, keeps the call trace, and saves the full result", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4245
+ mockPi.onCall({ output: "scan done" });
4246
+ const result = await makeExecutor([makeAgent("echo")]).execute(
4247
+ "scripted-workflow-large-return",
4248
+ { async: false, workflowScript: `await runs.run("scan", { agent: "echo", task: "Scan" }); return "x".repeat(210000);` },
4249
+ new AbortController().signal,
4250
+ undefined,
4251
+ makeMinimalCtx(tempDir),
4252
+ );
4253
+
4254
+ const text = result.content[0]?.text ?? "";
4255
+ assert.equal(result.isError, undefined, text);
4256
+ assert.ok(Buffer.byteLength(text, "utf-8") < 210_000);
4257
+ assert.match(text.slice(-500), /Call trace:\n- run scan: started\n- run scan: completed/);
4258
+ const savedPath = /\[TRUNCATED: .* - full output at (.+)\]/.exec(text)?.[1];
4259
+ assert.ok(savedPath, text.slice(0, 500));
4260
+ assert.ok(!savedPath.includes(`${path.sep}outputs${path.sep}`), `full result must not share the child outputs tree: ${savedPath}`);
4261
+ assert.ok(fs.readFileSync(savedPath, "utf-8").includes(`Return:\n${"x".repeat(210000)}`));
4262
+ // Age-based artifact retention must remove the saved full result like other run artifacts.
4263
+ const old = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
4264
+ fs.utimesSync(savedPath, old, old);
4265
+ fs.rmSync(path.join(TEMP_ARTIFACTS_DIR, ".last-cleanup"), { force: true });
4266
+ cleanupOldArtifacts(TEMP_ARTIFACTS_DIR, 1);
4267
+ assert.equal(fs.existsSync(savedPath), false);
4268
+ });
4269
+
4270
+ it("marks cut async workflow return previews and points to the full value", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4271
+ const executor = makeExecutor([makeAgent("echo")]);
4272
+ const result = await executor.execute("scripted-workflow-large-async-return", { async: true, workflowScript: `return "y".repeat(2000);` }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
4273
+ const workflowRunId = result.details.asyncId!;
4274
+ const statusPath = path.join(result.details.asyncDir!, "status.json");
4275
+ let status: { state?: string } = {};
4276
+ for (let attempt = 0; attempt < 300; attempt++) {
4277
+ status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
4278
+ if (status.state === "complete" || status.state === "failed") break;
4279
+ await new Promise((resolve) => setTimeout(resolve, 20));
4280
+ }
4281
+ assert.equal(status.state, "complete");
4282
+ const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
4283
+ const summary = (JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { summary?: string }).summary ?? "";
4284
+ assert.ok(summary.includes(`Return: ${"y".repeat(1000)}… (truncated; full return value and emits: ${statusPath} (workflow.value, workflow.emits))`), summary);
4285
+ const statusText = (await executor.execute("status-large-return", { action: "status", id: workflowRunId }, new AbortController().signal, undefined, makeMinimalCtx(tempDir))).content[0]?.text ?? "";
4286
+ assert.match(statusText, /Return: "y{239}…/);
4287
+ assert.ok(statusText.includes(`Full return value and emits: ${statusPath} (workflow.value, workflow.emits)`), statusText);
4288
+ fs.rmSync(result.details.asyncDir!, { recursive: true, force: true });
4289
+ fs.rmSync(resultPath, { force: true });
4290
+ });
4291
+
4292
+ it("bounds a large child error in the workflow call trace", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4293
+ mockPi.onCall({ exitCode: 1, stderr: "c".repeat(300000) });
4294
+ const result = await makeExecutor([makeAgent("echo")]).execute(
4295
+ "scripted-workflow-large-child-error",
4296
+ { async: false, workflowScript: `try { await runs.run("scan", { agent: "echo", task: "Scan" }); } catch {} return "done";` },
4297
+ new AbortController().signal,
4298
+ undefined,
4299
+ makeMinimalCtx(tempDir),
4300
+ );
4301
+ const text = result.content[0]?.text ?? "";
4302
+ assert.ok(Buffer.byteLength(text, "utf-8") < 50_000, `${Buffer.byteLength(text, "utf-8")} bytes`);
4303
+ assert.match(text, /- run scan: failed [\s\S]*c… \(\+\d+ chars\)/);
4304
+ const savedPath = /\[TRUNCATED: trace errors shortened - full output at (.+)\]/.exec(text)?.[1];
4305
+ assert.ok(savedPath && fs.readFileSync(savedPath, "utf-8").includes("c".repeat(300000)), text.slice(-400));
4306
+ });
4307
+
4308
+ it("caps an oversized thrown workflow error in foreground text and async summaries", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4309
+ const executor = makeExecutor([makeAgent("echo")]);
4310
+ const workflowScript = `throw new Error("e".repeat(300000));`;
4311
+ const foreground = await executor.execute("scripted-workflow-large-error", { async: false, workflowScript }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
4312
+ const text = foreground.content[0]?.text ?? "";
4313
+ assert.equal(foreground.isError, true);
4314
+ assert.ok(Buffer.byteLength(text, "utf-8") < 300_000);
4315
+ const savedPath = /\[TRUNCATED: .* - full output at (.+)\]/.exec(text)?.[1];
4316
+ assert.ok(savedPath && fs.readFileSync(savedPath, "utf-8").includes("e".repeat(300000)), text.slice(0, 500));
4317
+
4318
+ const started = await executor.execute("scripted-workflow-large-async-error", { async: true, workflowScript }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
4319
+ const statusPath = path.join(started.details.asyncDir!, "status.json");
4320
+ let status: { state?: string; error?: string } = {};
4321
+ for (let attempt = 0; attempt < 300; attempt++) {
4322
+ status = JSON.parse(fs.readFileSync(statusPath, "utf-8"));
4323
+ if (status.state === "failed") break;
4324
+ await new Promise((resolve) => setTimeout(resolve, 20));
4325
+ }
4326
+ assert.equal(status.state, "failed");
4327
+ assert.ok(status.error?.includes("e".repeat(300000)));
4328
+ const resultPath = path.join(DIRS.results, `${started.details.asyncId}.json`);
4329
+ const summary = (JSON.parse(fs.readFileSync(resultPath, "utf-8")) as { summary?: string }).summary ?? "";
4330
+ assert.ok(summary.length < 2_000 && summary.includes(`… (truncated; full error: ${statusPath} (error))`), summary.slice(0, 300));
4331
+ fs.rmSync(started.details.asyncDir!, { recursive: true, force: true });
4332
+ fs.rmSync(resultPath, { force: true });
4333
+ });
4334
+
3279
4335
  it("rejects an over-limit runs.all batch before launching any workflow child", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3280
4336
  const executor = makeExecutor([makeAgent("echo")], { maxSubagentSpawnsPerRun: 1 });
3281
4337
 
@@ -3293,11 +4349,32 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3293
4349
  makeMinimalCtx(tempDir),
3294
4350
  );
3295
4351
 
3296
- assert.equal(result.isError, undefined, result.content[0]?.text ?? "workflow failed");
4352
+ assert.equal(result.isError, true);
3297
4353
  assert.equal(mockPi.callCount(), 0);
3298
- const children = result.details.workflow?.value as Array<{ ok: boolean; error?: string }>;
3299
- assert.deepEqual(children.map(({ ok }) => ok), [false, false]);
3300
- for (const child of children) assert.match(child.error ?? "", /workflow\[second\].*0\/1 used; 2 requested, 1 remaining/);
4354
+ assert.match(result.content[0]?.text ?? "", /validation failed before child launch; no children launched/);
4355
+ assert.match(result.content[0]?.text ?? "", /'first', 'second'.*minimum required: 2; configured: 1/);
4356
+ assert.equal(result.details.workflow, undefined);
4357
+ });
4358
+
4359
+ it("rejects an unknown literal agent before launching any workflow child", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4360
+ const executor = makeExecutor([makeAgent("reviewer")]);
4361
+ const workflowScript = `const scan = await runs.run("scan", { agent: "reviewer", task: "Scan" });\nreturn runs.run("review", { agent: "reviwer", task: scan.output });`;
4362
+
4363
+ const result = await executor.execute("scripted-workflow-unknown-agent", { async: false, workflowScript }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
4364
+ assert.equal(result.isError, true);
4365
+ assert.equal(mockPi.callCount(), 0);
4366
+ assert.match(result.content[0]?.text ?? "", /no children launched\. runs\.run: Unknown agent 'reviwer'\. Did you mean 'reviewer'\?/);
4367
+
4368
+ const validation = await executor.execute("scripted-workflow-unknown-agent-validate", { action: "validate", workflowScript }, new AbortController().signal, undefined, makeMinimalCtx(tempDir));
4369
+ assert.equal(validation.isError, true);
4370
+ assert.deepEqual(JSON.parse(validation.content[0]?.text ?? "").errors.map((error: { kind?: string; line?: number }) => ({ kind: error.kind, line: error.line })), [{ kind: "agent", line: 2 }]);
4371
+ });
4372
+
4373
+ it("checks literal agents with the parent model's provider-specific discovery", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4374
+ const executor = makeExecutor([], {}, false, undefined, true, new Map(), undefined, undefined, createEventBus(), (_cwd, provider) => provider === "openai-codex" ? [makeAgent("reviewer")] : []);
4375
+ const ctx = { ...makeMinimalCtx(tempDir), model: { provider: "openai-codex", id: "gpt-test" } };
4376
+ const validation = await executor.execute("provider-agent-validate", { action: "validate", workflowScript: `return runs.run("review", { agent: "reviewer", task: "Review" });` }, new AbortController().signal, undefined, ctx as ReturnType<typeof makeMinimalCtx>);
4377
+ assert.equal(validation.isError, undefined, validation.content[0]?.text);
3301
4378
  });
3302
4379
 
3303
4380
  it("lets an explicit workflow spawn override exceed config", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
@@ -3355,6 +4432,116 @@ describe("single sync execution", { skip: !available ? "pi packages not availabl
3355
4432
  assert.equal(result.details.results[0]?.acceptance?.verifyRuns[0]?.id, "gate");
3356
4433
  });
3357
4434
 
4435
+ it("bridges a typed gate's json stdout into the child's structuredOutput", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4436
+ mockPi.onCall({ output: "Review complete. See report." });
4437
+ const executor = makeExecutor([makeAgent("echo")]);
4438
+ const gate = {
4439
+ command: `${process.execPath} -e "process.stdout.write(JSON.stringify({ verdict: 'blocked', action: 'writer-fix' }))"`,
4440
+ output: "json",
4441
+ schema: { type: "object", properties: { verdict: { type: "string", enum: ["ok", "blocked"] } }, required: ["verdict"] },
4442
+ };
4443
+
4444
+ const result = await executor.execute(
4445
+ "typed-gate",
4446
+ { async: false, agent: "echo", task: "Review the report without edits", gate },
4447
+ new AbortController().signal,
4448
+ undefined,
4449
+ makeMinimalCtx(tempDir),
4450
+ );
4451
+
4452
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "typed gate failed");
4453
+ const child = result.details.results[0];
4454
+ assert.equal(child?.acceptance?.status, "verified");
4455
+ assert.equal(child?.acceptance?.verifyRuns[0]?.status, "passed");
4456
+ assert.deepEqual(child?.structuredOutput, { verdict: "blocked", action: "writer-fix" });
4457
+ });
4458
+
4459
+ it("fails the run when a typed gate prints something other than schema-valid json", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4460
+ mockPi.onCall({ output: "Review complete." });
4461
+ const executor = makeExecutor([makeAgent("echo")]);
4462
+ const result = await executor.execute(
4463
+ "typed-gate-invalid",
4464
+ { async: false, agent: "echo", task: "Review the report without edits", gate: { command: `${process.execPath} -e "process.stdout.write('WRITER-FIX report=r.md')"`, output: "json" } },
4465
+ new AbortController().signal,
4466
+ undefined,
4467
+ makeMinimalCtx(tempDir),
4468
+ );
4469
+
4470
+ assert.equal(result.isError, true);
4471
+ assert.match(result.content[0]?.text ?? "", /verification 'gate' failed: output: "json" stdout is not valid JSON/);
4472
+ assert.equal(result.details.results[0]?.structuredOutput, undefined);
4473
+ });
4474
+
4475
+ it("rejects a typed gate combined with outputSchema before launch, in both spellings", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4476
+ const executor = makeExecutor([makeAgent("echo")]);
4477
+ const shorthand = await executor.execute(
4478
+ "typed-gate-conflict",
4479
+ { async: false, agent: "echo", task: "Review", gate: { command: "true", output: "json" }, outputSchema: { type: "object" } },
4480
+ new AbortController().signal,
4481
+ undefined,
4482
+ makeMinimalCtx(tempDir),
4483
+ );
4484
+ assert.equal(shorthand.isError, true);
4485
+ assert.match(shorthand.content[0]?.text ?? "", /gate\.output: .*cannot be combined with outputSchema/);
4486
+
4487
+ const explicit = await executor.execute(
4488
+ "typed-verify-conflict",
4489
+ { async: false, agent: "echo", task: "Review", acceptance: { level: "verified", verify: [{ id: "v", command: "true", output: "json" }] }, outputSchema: { type: "object" } },
4490
+ new AbortController().signal,
4491
+ undefined,
4492
+ makeMinimalCtx(tempDir),
4493
+ );
4494
+ assert.equal(explicit.isError, true);
4495
+ assert.match(explicit.content[0]?.text ?? "", /acceptance\.verify: .*cannot be combined with outputSchema/);
4496
+
4497
+ const declared = makeExecutor([makeAgent("typed", { outputSchema: { type: "object" } })]);
4498
+ const frontmatter = await declared.execute(
4499
+ "typed-gate-agent-schema",
4500
+ { async: false, agent: "typed", task: "Review", gate: { command: "true", output: "json" } },
4501
+ new AbortController().signal,
4502
+ undefined,
4503
+ makeMinimalCtx(tempDir),
4504
+ );
4505
+ assert.equal(frontmatter.isError, true);
4506
+ assert.match(frontmatter.content[0]?.text ?? "", /gate\.output: .*cannot be combined with agent 'typed' outputSchema/);
4507
+ assert.equal(mockPi.callCount(), 0);
4508
+ });
4509
+
4510
+ it("preserves an explicitly bound staged index through a foreground launch", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
4511
+ const cwd = fs.mkdtempSync(path.join(tempDir, "preserved-index-"));
4512
+ execFileSync("git", ["init", "-q"], { cwd });
4513
+ fs.writeFileSync(path.join(cwd, "owned.txt"), "parent staged\n", "utf-8");
4514
+ execFileSync("git", ["add", "owned.txt"], { cwd });
4515
+ const before = execFileSync("git", ["write-tree"], { cwd, encoding: "utf-8" }).trim();
4516
+ mockPi.onCall({ output: [
4517
+ "review complete",
4518
+ "```acceptance-report",
4519
+ JSON.stringify({
4520
+ criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "implemented" }],
4521
+ changedFiles: [],
4522
+ testsAddedOrUpdated: [],
4523
+ commandsRun: [{ command: "npm test", result: "passed", summary: "passed" }],
4524
+ validationOutput: ["tests passed"],
4525
+ residualRisks: [],
4526
+ noStagedFiles: false,
4527
+ }),
4528
+ "```",
4529
+ ].join("\n") });
4530
+ const executor = makeExecutor([makeAgent("worker")]);
4531
+
4532
+ const result = await executor.execute(
4533
+ "preserved-index",
4534
+ { async: false, agent: "worker", task: "Review the fix without edits", cwd, acceptance: { level: "checked", preserveStagedIndex: true } },
4535
+ new AbortController().signal,
4536
+ undefined,
4537
+ makeMinimalCtx(cwd),
4538
+ );
4539
+
4540
+ assert.equal(result.isError, undefined, result.content[0]?.text ?? "preserved index run failed");
4541
+ assert.equal(result.details.results[0]?.acceptance?.status, "checked");
4542
+ assert.equal(execFileSync("git", ["write-tree"], { cwd, encoding: "utf-8" }).trim(), before);
4543
+ });
4544
+
3358
4545
  it("lets runs.all siblings settle when one verified gate fails", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
3359
4546
  const acceptedReport = [
3360
4547
  "done",