@selesai/code 0.13.15 → 0.13.18

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 (473) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +2 -14
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.js +3 -9
  5. package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
  6. package/dist/bun/sandbox-env-setup.d.ts +1 -0
  7. package/dist/bun/sandbox-env-setup.js +3 -0
  8. package/dist/cli/auth-command.d.ts +23 -0
  9. package/dist/cli/auth-command.js +102 -0
  10. package/dist/cli/config-selector.js +2 -1
  11. package/dist/cli/setup.d.ts +1 -0
  12. package/dist/cli/setup.js +11 -0
  13. package/dist/cli.js +2 -14
  14. package/dist/config.d.ts +3 -2
  15. package/dist/config.js +9 -4
  16. package/dist/core/agent-session-runtime.js +15 -7
  17. package/dist/core/agent-session.d.ts +2 -6
  18. package/dist/core/agent-session.js +15 -4
  19. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  20. package/dist/core/compaction/branch-summarization.js +2 -1
  21. package/dist/core/extensions/loader.js +3 -0
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/http-dispatcher.js +2 -0
  24. package/dist/core/keybindings.d.ts +5 -0
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/messages.d.ts +1 -1
  27. package/dist/core/model-config.d.ts +15 -0
  28. package/dist/core/model-config.js +3 -0
  29. package/dist/core/model-runtime.d.ts +1 -0
  30. package/dist/core/model-runtime.js +5 -2
  31. package/dist/core/provider-composer.js +8 -2
  32. package/dist/core/session-export.d.ts +3 -0
  33. package/dist/core/session-export.js +31 -0
  34. package/dist/core/session-manager.d.ts +3 -2
  35. package/dist/core/session-manager.js +40 -13
  36. package/dist/core/skills.d.ts +1 -1
  37. package/dist/core/skills.js +4 -2
  38. package/dist/core/system-prompt.js +10 -10
  39. package/dist/core/tools/bash.js +3 -117
  40. package/dist/core/tools/edit.d.ts +1 -12
  41. package/dist/core/tools/edit.js +5 -163
  42. package/dist/core/tools/find.js +4 -55
  43. package/dist/core/tools/grep.js +4 -60
  44. package/dist/core/tools/ls.js +4 -49
  45. package/dist/core/tools/read.d.ts +4 -0
  46. package/dist/core/tools/read.js +10 -122
  47. package/dist/core/tools/renderers/bash.d.ts +11 -0
  48. package/dist/core/tools/renderers/bash.js +129 -0
  49. package/dist/core/tools/renderers/edit.d.ts +22 -0
  50. package/dist/core/tools/renderers/edit.js +166 -0
  51. package/dist/core/tools/renderers/find.d.ts +9 -0
  52. package/dist/core/tools/renderers/find.js +63 -0
  53. package/dist/core/tools/renderers/grep.d.ts +9 -0
  54. package/dist/core/tools/renderers/grep.js +68 -0
  55. package/dist/core/tools/renderers/index.d.ts +27 -0
  56. package/dist/core/tools/renderers/index.js +46 -0
  57. package/dist/core/tools/renderers/ls.d.ts +9 -0
  58. package/dist/core/tools/renderers/ls.js +57 -0
  59. package/dist/core/tools/renderers/read.d.ts +10 -0
  60. package/dist/core/tools/renderers/read.js +128 -0
  61. package/dist/core/tools/renderers/write.d.ts +9 -0
  62. package/dist/core/tools/renderers/write.js +152 -0
  63. package/dist/core/tools/write.js +5 -146
  64. package/dist/extensions/capability-gateway/index.ts +12 -1
  65. package/dist/extensions/capability-gateway/integration.test.ts +23 -0
  66. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
  67. package/dist/extensions/pi-hermes-memory/README.md +16 -12
  68. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
  69. package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
  70. package/dist/extensions/pi-hermes-memory/package.json +4 -4
  71. package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
  72. package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
  73. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
  74. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
  75. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
  76. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
  77. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
  78. package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
  79. package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
  80. package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
  83. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
  84. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
  85. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
  86. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
  87. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
  88. package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
  89. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
  90. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
  91. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
  92. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
  93. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
  94. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
  95. package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
  96. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
  97. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
  98. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
  99. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
  100. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
  101. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
  102. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
  103. package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
  104. package/dist/extensions/pi-intercom/README.md +16 -2
  105. package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
  106. package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
  107. package/dist/extensions/pi-intercom/index.ts +1 -1
  108. package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
  109. package/dist/extensions/pi-intercom/package.json +1 -1
  110. package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
  111. package/dist/extensions/pi-intercom/project-agent.ts +1 -6
  112. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
  113. package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
  114. package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
  115. package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
  116. package/dist/extensions/pi-subagents/README.md +3 -3
  117. package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
  118. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
  119. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  120. package/dist/extensions/pi-subagents/docs/agents.md +32 -16
  121. package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
  122. package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
  123. package/dist/extensions/pi-subagents/docs/missions.md +2 -0
  124. package/dist/extensions/pi-subagents/docs/models.md +58 -1
  125. package/dist/extensions/pi-subagents/docs/observability.md +50 -12
  126. package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
  127. package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
  128. package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
  129. package/dist/extensions/pi-subagents/package-lock.json +747 -177
  130. package/dist/extensions/pi-subagents/package.json +6 -4
  131. package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
  132. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
  133. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  137. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
  138. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
  139. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  140. package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
  141. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
  142. package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  143. package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
  144. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
  145. package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
  146. package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
  147. package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
  148. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
  149. package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
  150. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
  151. package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
  152. package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
  153. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
  154. package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
  155. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
  156. package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
  157. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
  158. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
  159. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
  160. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
  161. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
  164. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
  165. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
  166. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
  167. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
  168. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
  169. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
  170. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
  171. package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
  172. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
  173. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
  174. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
  175. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
  176. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
  177. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
  178. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
  179. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
  180. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
  181. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
  182. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
  183. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
  184. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
  185. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
  186. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
  187. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
  188. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  189. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
  190. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
  192. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
  194. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
  195. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
  196. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
  197. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
  198. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
  199. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
  200. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
  201. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
  202. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
  203. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
  204. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
  205. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
  206. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
  207. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  208. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
  209. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
  210. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
  211. package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
  212. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
  213. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
  214. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
  215. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
  216. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
  217. package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
  218. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
  219. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
  220. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
  221. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
  222. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
  223. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
  224. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
  225. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
  226. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
  227. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
  228. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
  229. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
  230. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
  231. package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
  232. package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
  233. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
  234. package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
  235. package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
  236. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
  237. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
  238. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
  239. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
  240. package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
  241. package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
  242. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
  243. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
  244. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  245. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
  246. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
  247. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
  248. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
  249. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
  250. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
  251. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
  253. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
  254. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
  255. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
  256. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
  257. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
  258. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
  259. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
  260. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
  261. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
  262. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
  263. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
  264. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
  265. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
  266. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
  267. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
  268. package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
  269. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
  270. package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
  271. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
  272. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
  273. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
  274. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
  275. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
  276. package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
  277. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
  278. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
  279. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
  280. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
  281. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
  282. package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
  283. package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
  284. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
  285. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
  286. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
  287. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
  288. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
  289. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
  290. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
  291. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
  292. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
  293. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
  294. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
  295. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
  296. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
  297. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
  298. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
  299. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
  300. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
  301. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
  302. package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
  303. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
  304. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
  305. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
  306. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
  307. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
  308. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
  309. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
  310. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
  311. package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
  312. package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
  313. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
  314. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
  315. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
  316. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
  317. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
  318. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  319. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
  320. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
  321. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
  322. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
  323. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
  324. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
  325. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
  326. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
  327. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
  328. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
  329. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
  330. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
  331. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
  332. package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
  333. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
  334. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
  335. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
  336. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
  337. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
  338. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
  339. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
  340. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
  341. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
  342. package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
  343. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
  344. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
  345. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
  346. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
  347. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
  348. package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
  349. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
  350. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
  351. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
  352. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
  353. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
  354. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
  355. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
  356. package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
  357. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
  358. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
  359. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
  360. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
  361. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
  362. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
  363. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
  364. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
  365. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
  366. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
  367. package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
  368. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
  369. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
  370. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
  371. package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
  372. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
  373. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
  374. package/dist/extensions/pi-web-agent/src/types.ts +1 -1
  375. package/dist/extensions/pi-zentui/README.md +17 -0
  376. package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
  377. package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
  378. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
  379. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
  380. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
  381. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
  382. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
  383. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
  384. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
  385. package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
  386. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
  387. package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
  388. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
  389. package/dist/extensions/pi-zentui/package-lock.json +277 -517
  390. package/dist/extensions/pi-zentui/package.json +7 -4
  391. package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
  392. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
  393. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
  394. package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
  395. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
  396. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  397. package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
  398. package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
  399. package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
  400. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
  401. package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
  402. package/dist/index.d.ts +1 -1
  403. package/dist/main.js +4 -1
  404. package/dist/modes/interactive/chat-viewport.d.ts +19 -0
  405. package/dist/modes/interactive/chat-viewport.js +27 -0
  406. package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
  407. package/dist/modes/interactive/components/assistant-message.js +30 -13
  408. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  409. package/dist/modes/interactive/components/custom-editor.js +39 -1
  410. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  411. package/dist/modes/interactive/components/index.d.ts +1 -1
  412. package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
  413. package/dist/modes/interactive/components/markdown-transform.js +18 -0
  414. package/dist/modes/interactive/components/model-selector.js +19 -17
  415. package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
  416. package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
  417. package/dist/modes/interactive/components/settings-selector.js +128 -20
  418. package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
  419. package/dist/modes/interactive/components/settings-submenu.js +164 -0
  420. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  421. package/dist/modes/interactive/components/status-indicator.js +10 -3
  422. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  423. package/dist/modes/interactive/components/thinking-selector.js +75 -18
  424. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  425. package/dist/modes/interactive/components/tool-execution.js +35 -33
  426. package/dist/modes/interactive/components/trust-selector.js +2 -2
  427. package/dist/modes/interactive/interactive-mode.d.ts +13 -15
  428. package/dist/modes/interactive/interactive-mode.js +144 -80
  429. package/dist/modes/interactive/session-share.d.ts +15 -0
  430. package/dist/modes/interactive/session-share.js +183 -0
  431. package/dist/modes/interactive/theme/dark.json +2 -1
  432. package/dist/modes/interactive/theme/light.json +2 -1
  433. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  434. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  435. package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
  436. package/dist/modes/interactive/theme/theme-json.js +129 -0
  437. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  438. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  439. package/dist/modes/interactive/theme/theme.js +18 -119
  440. package/dist/modes/interactive/tui-renderer.d.ts +20 -0
  441. package/dist/modes/interactive/tui-renderer.js +65 -0
  442. package/dist/utils/exif-orientation.js +2 -3
  443. package/dist/utils/management-http.d.ts +24 -0
  444. package/dist/utils/management-http.js +53 -0
  445. package/dist/utils/text.d.ts +7 -0
  446. package/dist/utils/text.js +8 -0
  447. package/dist/utils/tools-manager.d.ts +1 -0
  448. package/dist/utils/tools-manager.js +43 -21
  449. package/docs/docs.json +0 -4
  450. package/package.json +4 -4
  451. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
  452. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
  453. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
  454. package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  455. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
  456. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
  457. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
  458. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
  459. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
  460. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
  461. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
  462. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
  463. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
  464. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
  465. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
  466. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
  467. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
  468. package/dist/skills/implanger/SKILL.md +0 -70
  469. package/dist/skills/output/SKILL.md +0 -49
  470. package/dist/skills/planger/SKILL.md +0 -167
  471. package/dist/skills/workflow/SKILL.md +0 -95
  472. package/docs/workflows.md +0 -18
  473. /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
@@ -6,31 +6,13 @@ import { afterEach, describe, it } from "node:test";
6
6
  import registerFanoutChildSubagentExtension from "../../src/extension/fanout-child.ts";
7
7
  import { createSubagentExecutor, readNestedRecoveryDescriptor } from "../../src/runs/foreground/subagent-executor.ts";
8
8
  import { createNestedRoute, findNestedControlResult, projectNestedEvents, readNestedControlRequests, readNestedControlResults, snapshotNestedEventFiles, writeNestedControlRequest, writeNestedControlResult, writeNestedEvent } from "../../src/runs/shared/nested-events.ts";
9
- import {
10
- SUBAGENT_CHILD_ENV,
11
- SUBAGENT_FANOUT_CHILD_ENV,
12
- SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV,
13
- SUBAGENT_PARENT_CHILD_INDEX_ENV,
14
- SUBAGENT_PARENT_CONTROL_INBOX_ENV,
15
- SUBAGENT_PARENT_EVENT_SINK_ENV,
16
- SUBAGENT_PARENT_ROOT_RUN_ID_ENV,
17
- SUBAGENT_PARENT_RUN_ID_ENV,
18
- } from "../../src/runs/shared/pi-args.ts";
9
+ import type { ChildRuntimeConfig } from "../../src/runs/shared/child-runtime-config.ts";
19
10
  import { ASYNC_DIR, type SubagentState } from "../../src/shared/types.ts";
20
11
  import { createRunFanoutBudget } from "../../src/runs/shared/run-fanout-budget.ts";
12
+ import { getArtifactPaths, getArtifactsDir } from "../../src/shared/artifacts.ts";
21
13
 
22
14
  const routeRoots: string[] = [];
23
15
  const fanoutListenerCleanupKey = "__piSubagentFanoutChildNestedControlInboxCleanups";
24
- const savedEnv = {
25
- [SUBAGENT_CHILD_ENV]: process.env[SUBAGENT_CHILD_ENV],
26
- [SUBAGENT_FANOUT_CHILD_ENV]: process.env[SUBAGENT_FANOUT_CHILD_ENV],
27
- [SUBAGENT_PARENT_EVENT_SINK_ENV]: process.env[SUBAGENT_PARENT_EVENT_SINK_ENV],
28
- [SUBAGENT_PARENT_CONTROL_INBOX_ENV]: process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV],
29
- [SUBAGENT_PARENT_ROOT_RUN_ID_ENV]: process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV],
30
- [SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]: process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV],
31
- [SUBAGENT_PARENT_RUN_ID_ENV]: process.env[SUBAGENT_PARENT_RUN_ID_ENV],
32
- [SUBAGENT_PARENT_CHILD_INDEX_ENV]: process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV],
33
- };
34
16
 
35
17
  afterEach(() => {
36
18
  const globalStore = globalThis as Record<string, unknown>;
@@ -43,10 +25,6 @@ afterEach(() => {
43
25
  }
44
26
  delete globalStore[fanoutListenerCleanupKey];
45
27
  for (const root of routeRoots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
46
- for (const [key, value] of Object.entries(savedEnv)) {
47
- if (value === undefined) delete process.env[key];
48
- else process.env[key] = value;
49
- }
50
28
  });
51
29
 
52
30
  function createState(): SubagentState {
@@ -68,7 +46,7 @@ function createState(): SubagentState {
68
46
  };
69
47
  }
70
48
 
71
- function createExecutor(state = createState(), agents: Array<Record<string, unknown>> = [], allowMutatingManagementActions = true, events: any = { emit() {}, on() { return () => {}; } }) {
49
+ function createExecutor(state = createState(), agents: Array<Record<string, unknown>> = [], allowMutatingManagementActions = true, events: any = { emit() {}, on() { return () => {}; } }, childRuntime?: ChildRuntimeConfig) {
72
50
  return createSubagentExecutor({
73
51
  pi: { events, getSessionName() { return "parent"; } } as any,
74
52
  state,
@@ -79,6 +57,7 @@ function createExecutor(state = createState(), agents: Array<Record<string, unkn
79
57
  expandTilde: (value) => value,
80
58
  discoverAgents: () => ({ agents: agents as any }),
81
59
  allowMutatingManagementActions,
60
+ ...(childRuntime ? { childRuntime } : {}),
82
61
  });
83
62
  }
84
63
 
@@ -117,13 +96,16 @@ function stateWithNestedRoute(route: ReturnType<typeof createNestedRoute>): Suba
117
96
  return state;
118
97
  }
119
98
 
120
- function setNestedRouteEnv(route: ReturnType<typeof createNestedRoute>, parentRunId = route.rootRunId) {
121
- process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] = route.eventSink;
122
- process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = route.controlInbox;
123
- process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = route.rootRunId;
124
- process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = route.capabilityToken;
125
- process.env[SUBAGENT_PARENT_RUN_ID_ENV] = parentRunId;
126
- process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "0";
99
+ /** The child runtime config of a fanout child launched under `route` by `parentRunId`. */
100
+ function fanoutChildRuntime(route: ReturnType<typeof createNestedRoute>, parentRunId = route.rootRunId): ChildRuntimeConfig {
101
+ return {
102
+ fanoutChild: true,
103
+ depth: 1,
104
+ waitTool: { enabled: true },
105
+ fast: false,
106
+ nestedRoute: route,
107
+ nestedParent: { parentRunId, parentChildIndex: 0, depth: 1, path: [{ runId: parentRunId, stepIndex: 0 }] },
108
+ };
127
109
  }
128
110
 
129
111
  function text(result: Awaited<ReturnType<ReturnType<typeof createExecutor>["execute"]>>): string {
@@ -183,8 +165,15 @@ describe("nested control routing", () => {
183
165
  try {
184
166
  const route = createNestedRun("nested-foreground");
185
167
  const state = createState();
168
+ state.artifactDirPreference = "project";
169
+ const artifactRoot = getArtifactsDir(null, root, "project");
170
+ fs.mkdirSync(artifactRoot, { recursive: true });
171
+ fs.writeFileSync(getArtifactPaths(artifactRoot, "root-control", "orchestrator", 0).transcriptPath,
172
+ `${JSON.stringify({ recordType: "message", role: "assistant", text: "LIVE_FOREGROUND_ACTIVITY" })}\n`);
186
173
  state.foregroundControls.set("root-control", {
187
174
  runId: "root-control",
175
+ sessionId: "session",
176
+ cwd: root,
188
177
  mode: "single",
189
178
  startedAt: 1,
190
179
  updatedAt: 1,
@@ -205,6 +194,12 @@ describe("nested control routing", () => {
205
194
  const transcript = await createExecutor(state).execute("transcript", { action: "status", id: "root-control", index: 0, view: "transcript" }, new AbortController().signal, undefined, ctx(root));
206
195
  assert.equal(transcript.isError, undefined);
207
196
  assert.match(text(transcript), /^Transcript target: run root-control · child 0\nSpawn budget:/);
197
+ assert.match(text(transcript), /LIVE_FOREGROUND_ACTIVITY/);
198
+ for (const hasUI of [false, true]) {
199
+ const implicit = await createExecutor(state).execute("implicit-transcript", { action: "status", view: "transcript" }, new AbortController().signal, undefined, { ...ctx(root), hasUI });
200
+ assert.equal(implicit.isError, undefined);
201
+ assert.match(text(implicit), /LIVE_FOREGROUND_ACTIVITY/);
202
+ }
208
203
  } finally {
209
204
  fs.rmSync(root, { recursive: true, force: true });
210
205
  }
@@ -250,7 +245,6 @@ describe("nested control routing", () => {
250
245
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-child-scope-"));
251
246
  try {
252
247
  const allowedRoute = createNestedRun("shared-nested");
253
- setNestedRouteEnv(allowedRoute, "root-control");
254
248
  const outsideRoute = createNestedRoute("root-outside");
255
249
  routeRoots.push(path.dirname(outsideRoute.eventSink));
256
250
  writeNestedEvent(outsideRoute, {
@@ -261,7 +255,7 @@ describe("nested control routing", () => {
261
255
  child: { id: "shared-nested", parentRunId: "root-outside", parentStepIndex: 0, depth: 1, path: [{ runId: "root-outside", stepIndex: 0 }], state: "running", agent: "outside" },
262
256
  });
263
257
 
264
- const result = await createExecutor(createState(), [], false).execute("status", { action: "status", id: "shared-nested" }, new AbortController().signal, undefined, ctx(root));
258
+ const result = await createExecutor(createState(), [], false, undefined, fanoutChildRuntime(allowedRoute, "root-control")).execute("status", { action: "status", id: "shared-nested" }, new AbortController().signal, undefined, ctx(root));
265
259
 
266
260
  assert.equal(result.isError, undefined);
267
261
  assert.match(text(result), /Nested run: shared-nested/);
@@ -337,22 +331,33 @@ describe("nested control routing", () => {
337
331
  }
338
332
  });
339
333
 
340
- it("routes resume for live nested runs through the control inbox", async () => {
334
+ for (const workflow of [false, true]) it(`routes ${workflow ? "workflow string" : "action"} resume for live nested runs through the control inbox`, async () => {
341
335
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-live-resume-"));
342
336
  try {
343
337
  const emitted: Array<{ name: string; payload: unknown }> = [];
344
338
  const events = { emit(name: string, payload: unknown) { emitted.push({ name, payload }); }, on() { return () => {}; } };
345
339
  const route = createNestedRun("nested-live-resume", "running", { intercomTarget: "attacker-target", leafIntercomTarget: "attacker-leaf" });
346
340
  const executor = createExecutor(stateWithNestedRoute(route), [], true, events);
347
- setTimeout(() => {
348
- const request = readNestedControlRequests(route)[0];
341
+ const responder = (async () => {
342
+ const deadline = Date.now() + 2_000;
343
+ let request = readNestedControlRequests(route)[0];
344
+ while (!request && Date.now() < deadline) {
345
+ await new Promise((resolve) => setTimeout(resolve, 10));
346
+ request = readNestedControlRequests(route)[0];
347
+ }
349
348
  assert.ok(request, "expected a nested resume request");
350
349
  assert.equal(request.action, "resume");
351
350
  assert.equal(request.message, "continue please");
352
351
  writeNestedControlResult(route, { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok: true, message: "nested resume accepted" });
353
- }, 50);
352
+ })();
354
353
 
355
- const result = await executor.execute("resume", { action: "resume", id: "nested-live-resume", message: "continue please" }, new AbortController().signal, undefined, ctx(root));
354
+ const execution = Promise.resolve().then(() => executor.execute("resume", workflow
355
+ ? { async: false, workflowScript: `return runs.run("live", { resume: "nested-live-resume", task: "continue please", output: false });` }
356
+ : { action: "resume", id: "nested-live-resume", message: "continue please" }, new AbortController().signal, undefined, ctx(root)));
357
+ const [response, executed] = await Promise.allSettled([responder, execution]);
358
+ if (response.status === "rejected") throw response.reason;
359
+ if (executed.status === "rejected") throw executed.reason;
360
+ const result = executed.value;
356
361
 
357
362
  assert.equal(result.isError, undefined);
358
363
  assert.match(text(result), /nested resume accepted/);
@@ -407,13 +412,15 @@ describe("nested control routing", () => {
407
412
  }
408
413
  });
409
414
 
410
- it("rejects stopped nested runs before attempting revival", async () => {
415
+ for (const workflow of [false, true]) it(`rejects stopped nested runs before ${workflow ? "workflow string resume" : "action revival"}`, async () => {
411
416
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-nested-stopped-resume-"));
412
417
  try {
413
418
  const route = createNestedRun("nested-stopped-resume", "stopped", { sessionFile: path.join(root, "missing-session.jsonl") });
414
419
 
415
420
  const result = await createExecutor(stateWithNestedRoute(route), [{ name: "worker", description: "Worker", prompt: "Do work" }])
416
- .execute("resume", { action: "resume", id: "nested-stopped-resume", message: "continue" }, new AbortController().signal, undefined, ctx(root));
421
+ .execute("resume", workflow
422
+ ? { async: false, workflowScript: `return runs.run("stopped", { resume: "nested-stopped-resume", task: "continue", output: false });` }
423
+ : { action: "resume", id: "nested-stopped-resume", message: "continue" }, new AbortController().signal, undefined, ctx(root));
417
424
 
418
425
  assert.equal(result.isError, true);
419
426
  assert.match(text(result), /was stopped and cannot be resumed/);
@@ -468,13 +475,12 @@ describe("nested control routing", () => {
468
475
  try {
469
476
  const route = createNestedRoute("root-parent");
470
477
  routeRoots.push(path.dirname(route.eventSink));
471
- setNestedRouteEnv(route, "root-parent");
472
478
  const throwingCtx = {
473
479
  ...ctx(root),
474
480
  modelRegistry: { getAvailable() { throw new Error("model registry exploded"); } },
475
481
  };
476
482
 
477
- const result = await createExecutor(createState(), [{ name: "worker", description: "Worker", prompt: "Do work" }])
483
+ const result = await createExecutor(createState(), [{ name: "worker", description: "Worker", prompt: "Do work" }], true, undefined, fanoutChildRuntime(route, "root-parent"))
478
484
  .execute("run", { agent: "worker", task: "go" }, new AbortController().signal, undefined, throwingCtx);
479
485
 
480
486
  assert.equal(result.isError, true);
@@ -491,9 +497,7 @@ describe("nested control routing", () => {
491
497
  it("keeps the fanout child control listener alive after control inbox polling errors", async () => {
492
498
  const route = createNestedRoute("root-poll-error");
493
499
  routeRoots.push(path.dirname(route.eventSink));
494
- setNestedRouteEnv(route, "root-poll-error");
495
- process.env[SUBAGENT_CHILD_ENV] = "1";
496
- process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
500
+ const childRuntime = fanoutChildRuntime(route, "root-poll-error");
497
501
  const pi = {
498
502
  events: { emit() {}, on() { return () => {}; } },
499
503
  registerTool() {},
@@ -507,7 +511,7 @@ describe("nested control routing", () => {
507
511
  logged.push(args);
508
512
  };
509
513
  try {
510
- registerFanoutChildSubagentExtension(pi);
514
+ registerFanoutChildSubagentExtension(pi, childRuntime);
511
515
  await waitFor(() => logged.some((entry) => String(entry[0] ?? "").includes(route.controlInbox) && String(entry[0] ?? "").includes("root-poll-error")));
512
516
 
513
517
  fs.rmSync(route.controlInbox, { force: true });
@@ -529,9 +533,7 @@ describe("nested control routing", () => {
529
533
  it("keeps fanout child control requests when result writing fails and retries after recovery", async () => {
530
534
  const route = createNestedRoute("root-result-write-fails");
531
535
  routeRoots.push(path.dirname(route.eventSink));
532
- setNestedRouteEnv(route, "root-result-write-fails");
533
- process.env[SUBAGENT_CHILD_ENV] = "1";
534
- process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
536
+ const childRuntime = fanoutChildRuntime(route, "root-result-write-fails");
535
537
  const pi = {
536
538
  events: { emit() {}, on() { return () => {}; } },
537
539
  registerTool() {},
@@ -551,7 +553,7 @@ describe("nested control routing", () => {
551
553
  logged.push(args);
552
554
  };
553
555
  try {
554
- registerFanoutChildSubagentExtension(pi);
556
+ registerFanoutChildSubagentExtension(pi, childRuntime);
555
557
  await waitFor(() => logged.some((entry) => String(entry[0] ?? "").includes("result-write-fails") && /keeping request for retry/.test(String(entry[0] ?? ""))));
556
558
  assert.equal(fs.existsSync(requestPath), true);
557
559
 
@@ -567,9 +569,7 @@ describe("nested control routing", () => {
567
569
  it("cleans the prior listener on reload and processes a resume request once", async () => {
568
570
  const route = createNestedRoute("root-reload-listener");
569
571
  routeRoots.push(path.dirname(route.eventSink));
570
- setNestedRouteEnv(route, "root-reload-listener");
571
- process.env[SUBAGENT_CHILD_ENV] = "1";
572
- process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
572
+ const childRuntime = fanoutChildRuntime(route, "root-reload-listener");
573
573
  const registrations: string[] = [];
574
574
  const activeIntervals = new Set<ReturnType<typeof setInterval>>();
575
575
  const originalSetInterval = globalThis.setInterval;
@@ -591,25 +591,19 @@ describe("nested control routing", () => {
591
591
  }) as any;
592
592
  const firstPi = makePi();
593
593
 
594
- registerFanoutChildSubagentExtension(firstPi);
595
- registerFanoutChildSubagentExtension(firstPi);
596
- registerFanoutChildSubagentExtension(makePi());
594
+ registerFanoutChildSubagentExtension(firstPi, childRuntime);
595
+ registerFanoutChildSubagentExtension(firstPi, childRuntime);
596
+ registerFanoutChildSubagentExtension(makePi(), childRuntime);
597
597
  assert.equal(activeIntervals.size, 1);
598
598
 
599
599
  const unrelatedRoute = createNestedRoute("root-unrelated-listener");
600
600
  routeRoots.push(path.dirname(unrelatedRoute.eventSink));
601
- setNestedRouteEnv(unrelatedRoute, "root-unrelated-listener");
602
- registerFanoutChildSubagentExtension(makePi());
601
+ registerFanoutChildSubagentExtension(makePi(), fanoutChildRuntime(unrelatedRoute, "root-unrelated-listener"));
603
602
  assert.equal(activeIntervals.size, 2);
604
603
 
605
- delete process.env[SUBAGENT_PARENT_EVENT_SINK_ENV];
606
- delete process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV];
607
- delete process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV];
608
- delete process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV];
609
- registerFanoutChildSubagentExtension(makePi());
604
+ registerFanoutChildSubagentExtension(makePi(), { ...childRuntime, nestedRoute: undefined });
610
605
  assert.equal(activeIntervals.size, 2);
611
606
 
612
- setNestedRouteEnv(route, "root-reload-listener");
613
607
  writeNestedControlRequest(route, {
614
608
  ts: Date.now(),
615
609
  requestId: "reload-resume-once",
@@ -630,9 +624,7 @@ describe("nested control routing", () => {
630
624
  it("negatively acknowledges ownerless fanout child control requests and removes them", async () => {
631
625
  const route = createNestedRoute("root-ownerless");
632
626
  routeRoots.push(path.dirname(route.eventSink));
633
- setNestedRouteEnv(route, "root-ownerless");
634
- process.env[SUBAGENT_CHILD_ENV] = "1";
635
- process.env[SUBAGENT_FANOUT_CHILD_ENV] = "1";
627
+ const childRuntime = fanoutChildRuntime(route, "root-ownerless");
636
628
  const pi = {
637
629
  events: { emit() {}, on() { return () => {}; } },
638
630
  registerTool() {},
@@ -645,11 +637,11 @@ describe("nested control routing", () => {
645
637
  action: "interrupt",
646
638
  });
647
639
 
648
- registerFanoutChildSubagentExtension(pi);
640
+ registerFanoutChildSubagentExtension(pi, childRuntime);
649
641
  await waitFor(() => readNestedControlResults(route).some((result) => result.requestId === "ownerless-request" && result.ok === false));
650
642
 
651
643
  assert.equal(fs.existsSync(requestPath), false);
652
644
  const result = readNestedControlResults(route).find((item) => item.requestId === "ownerless-request");
653
645
  assert.match(result?.message ?? "", /not active/);
654
646
  });
655
- });
647
+ });
@@ -9,35 +9,15 @@ import {
9
9
  hasLiveNestedDescendants,
10
10
  nestedSummaryFromAsyncStatus,
11
11
  parseNestedEventRecords,
12
+ inheritedNestedParentAddressOf,
13
+ inheritedNestedRouteOf,
12
14
  projectNestedEvents,
13
- resolveNestedParentAddressFromEnv,
14
- resolveNestedRouteFromEnv,
15
15
  updateAsyncJobNestedProjection,
16
16
  updateForegroundNestedProjection,
17
17
  writeNestedEvent,
18
18
  } from "../../src/runs/shared/nested-events.ts";
19
- import {
20
- SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV,
21
- SUBAGENT_PARENT_CHILD_INDEX_ENV,
22
- SUBAGENT_PARENT_CONTROL_INBOX_ENV,
23
- SUBAGENT_PARENT_DEPTH_ENV,
24
- SUBAGENT_PARENT_EVENT_SINK_ENV,
25
- SUBAGENT_PARENT_PATH_ENV,
26
- SUBAGENT_PARENT_ROOT_RUN_ID_ENV,
27
- SUBAGENT_PARENT_RUN_ID_ENV,
28
- } from "../../src/runs/shared/pi-args.ts";
29
19
 
30
20
  const routes: Array<{ eventSink: string }> = [];
31
- const savedEnv = {
32
- [SUBAGENT_PARENT_EVENT_SINK_ENV]: process.env[SUBAGENT_PARENT_EVENT_SINK_ENV],
33
- [SUBAGENT_PARENT_CONTROL_INBOX_ENV]: process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV],
34
- [SUBAGENT_PARENT_ROOT_RUN_ID_ENV]: process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV],
35
- [SUBAGENT_PARENT_RUN_ID_ENV]: process.env[SUBAGENT_PARENT_RUN_ID_ENV],
36
- [SUBAGENT_PARENT_CHILD_INDEX_ENV]: process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV],
37
- [SUBAGENT_PARENT_DEPTH_ENV]: process.env[SUBAGENT_PARENT_DEPTH_ENV],
38
- [SUBAGENT_PARENT_PATH_ENV]: process.env[SUBAGENT_PARENT_PATH_ENV],
39
- [SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]: process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV],
40
- };
41
21
 
42
22
  async function removeDirWithRetry(dir: string): Promise<void> {
43
23
  let lastError: unknown;
@@ -57,10 +37,6 @@ afterEach(async () => {
57
37
  for (const route of routes.splice(0)) {
58
38
  await removeDirWithRetry(path.dirname(route.eventSink));
59
39
  }
60
- for (const [key, value] of Object.entries(savedEnv)) {
61
- if (value === undefined) delete process.env[key];
62
- else process.env[key] = value;
63
- }
64
40
  });
65
41
 
66
42
  function trackRoute(rootRunId = "root-run") {
@@ -69,7 +45,7 @@ function trackRoute(rootRunId = "root-run") {
69
45
  return route;
70
46
  }
71
47
 
72
- function child(id: string, state: "queued" | "running" | "complete" | "failed" | "paused", ts: number, parentRunId = "root-run") {
48
+ function child(id: string, state: "queued" | "running" | "complete" | "failed" | "paused" | "rejected", ts: number, parentRunId = "root-run") {
73
49
  return {
74
50
  id,
75
51
  parentRunId,
@@ -123,17 +99,18 @@ describe("nested route index", () => {
123
99
  });
124
100
 
125
101
  describe("nested event route validation", () => {
126
- it("resolves nested parent addresses with full inherited path", () => {
127
- process.env[SUBAGENT_PARENT_RUN_ID_ENV] = "nested-parent";
128
- process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "2";
129
- process.env[SUBAGENT_PARENT_DEPTH_ENV] = "3";
130
- process.env[SUBAGENT_PARENT_PATH_ENV] = JSON.stringify([
131
- { runId: "root-run", stepIndex: 0, agent: "root-agent" },
132
- { runId: "../unsafe", stepIndex: 1, agent: "bad" },
133
- { runId: "nested-parent", stepIndex: 2, agent: "nested-agent" },
134
- ]);
135
-
136
- assert.deepEqual(resolveNestedParentAddressFromEnv(), {
102
+ it("resolves nested parent addresses from the inherited child runtime", () => {
103
+ assert.deepEqual(inheritedNestedParentAddressOf({
104
+ nestedParent: {
105
+ parentRunId: "nested-parent",
106
+ parentChildIndex: 2,
107
+ depth: 3,
108
+ path: [
109
+ { runId: "root-run", stepIndex: 0, agent: "root-agent" },
110
+ { runId: "nested-parent", stepIndex: 2, agent: "nested-agent" },
111
+ ],
112
+ },
113
+ }), {
137
114
  parentRunId: "nested-parent",
138
115
  parentStepIndex: 2,
139
116
  depth: 3,
@@ -142,26 +119,33 @@ describe("nested event route validation", () => {
142
119
  { runId: "nested-parent", stepIndex: 2, agent: "nested-agent" },
143
120
  ],
144
121
  });
122
+ assert.deepEqual(inheritedNestedParentAddressOf({ nestedParent: { parentRunId: "nested-parent", depth: 1, path: [] } }), {
123
+ parentRunId: "nested-parent",
124
+ depth: 1,
125
+ path: [{ runId: "nested-parent" }],
126
+ });
145
127
  });
146
128
 
147
- it("ignores unsafe nested parent ids from env", () => {
148
- process.env[SUBAGENT_PARENT_RUN_ID_ENV] = "../unsafe";
149
- process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "2";
150
-
151
- assert.equal(resolveNestedParentAddressFromEnv(), undefined);
129
+ it("ignores unsafe nested parent ids and missing parents", () => {
130
+ assert.equal(inheritedNestedParentAddressOf({ nestedParent: { parentRunId: "../unsafe", parentChildIndex: 2, depth: 1, path: [] } }), undefined);
131
+ assert.equal(inheritedNestedParentAddressOf({}), undefined);
132
+ assert.equal(inheritedNestedParentAddressOf(undefined), undefined);
152
133
  });
153
134
 
154
- it("resolves only matching contained routes from env", () => {
135
+ it("resolves only matching contained routes from the inherited child runtime", () => {
155
136
  const route = trackRoute();
156
- process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] = route.eventSink;
157
- process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = route.controlInbox;
158
- process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = route.rootRunId;
159
- process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = route.capabilityToken;
160
-
161
- assert.deepEqual(resolveNestedRouteFromEnv(), route);
137
+ assert.deepEqual(inheritedNestedRouteOf({ nestedRoute: route }), route);
138
+ assert.equal(inheritedNestedRouteOf({}), undefined);
162
139
 
163
- process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = "wrong-token";
164
- assert.throws(() => resolveNestedRouteFromEnv(), /capability token/);
140
+ const originalError = console.error;
141
+ const logged: unknown[] = [];
142
+ console.error = (...args: unknown[]) => { logged.push(args[1]); };
143
+ try {
144
+ assert.equal(inheritedNestedRouteOf({ nestedRoute: { ...route, capabilityToken: "wrong-token" } }), undefined);
145
+ } finally {
146
+ console.error = originalError;
147
+ }
148
+ assert.match(String(logged[0]), /capability token/);
165
149
  });
166
150
  });
167
151
 
@@ -326,6 +310,27 @@ describe("nested event parsing and projection", () => {
326
310
  }]), true);
327
311
  });
328
312
 
313
+ it("preserves rejected nested states across the event and registry boundary", () => {
314
+ const route = trackRoute("rejected-root");
315
+ writeNestedEvent(route, {
316
+ type: "subagent.nested.completed",
317
+ ts: 300,
318
+ parentRunId: route.rootRunId,
319
+ parentStepIndex: 0,
320
+ child: {
321
+ ...child("rejected-child", "rejected", 300, route.rootRunId),
322
+ parentStepIndex: 0,
323
+ path: [{ runId: route.rootRunId, stepIndex: 0 }],
324
+ steps: [{ agent: "leaf", status: "rejected" }],
325
+ },
326
+ });
327
+
328
+ const registry = projectNestedEvents(route);
329
+ assert.equal(registry.children[0]?.state, "rejected");
330
+ assert.equal(registry.children[0]?.steps?.[0]?.status, "rejected");
331
+ assert.equal(hasLiveNestedDescendants(registry.children), false);
332
+ });
333
+
329
334
  it("accepts only complete numeric token usage at the nested event boundary", () => {
330
335
  const route = trackRoute();
331
336
  writeNestedEvent(route, {
@@ -489,4 +494,4 @@ describe("nested session-name projection", () => {
489
494
  assert.equal(summary.sessionName, "reviewer: Inspect the changed auth middleware");
490
495
  assert.equal(summary.steps?.[0]?.sessionName, "reviewer: Inspect the changed auth middleware");
491
496
  });
492
- });
497
+ });
@@ -0,0 +1,42 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { it } from "node:test";
5
+
6
+ it("traces bounded completion reasons through NODE_DEBUG without changing delivery", () => {
7
+ const run = (debug: string) => {
8
+ const child = spawnSync(process.execPath, [
9
+ "--experimental-strip-types",
10
+ "--import", new URL("../support/isolated-temp-root.mjs", import.meta.url).href,
11
+ fileURLToPath(new URL("../support/notify-diagnostics-fixture.ts", import.meta.url)),
12
+ ], { env: { ...process.env, NODE_DEBUG: debug, NODE_NO_WARNINGS: "1" }, encoding: "utf8" });
13
+ assert.equal(child.status, 0, child.stderr);
14
+ return child;
15
+ };
16
+ const disabled = run("");
17
+ const enabled = run("pi-subagents-notify");
18
+ assert.equal(disabled.stderr, "");
19
+ assert.equal(enabled.stdout, disabled.stdout);
20
+ assert.equal(JSON.parse(enabled.stdout).length, 7);
21
+ assert.deepEqual(JSON.parse(enabled.stdout).at(-1).options, { triggerTurn: false });
22
+ const records = enabled.stderr.trim().split("\n").map((line) => {
23
+ assert.ok(line.length < 512, line);
24
+ assert.match(line, /^PI-SUBAGENTS-NOTIFY \d+: /);
25
+ return JSON.parse(line.slice(line.indexOf("{")));
26
+ });
27
+ assert.deepEqual([...new Set(records.map((row) => row.reason))].sort(), [
28
+ "missing_session", "foreground_session_mismatch", "not_owned", "intercom_delivered",
29
+ "batch_deferred", "deduped_pending", "send_accepted", "deduped_ttl", "emit_not_owned",
30
+ "send_failed", "dispose_pending", "disposed",
31
+ ].sort());
32
+ assert.deepEqual(records.filter((row) => row.id === "first").map((row) => row.reason), [
33
+ "batch_deferred", "deduped_pending", "send_accepted", "deduped_ttl", "batch_deferred", "send_accepted",
34
+ ]);
35
+ assert.deepEqual(records.filter((row) => row.id === "unbatched").map((row) => row.reason), ["send_accepted"]);
36
+ for (const row of records) {
37
+ assert.deepEqual(Object.keys(row).sort(), ["id", "reason", "runId", "source"]);
38
+ assert.ok(row.id.length <= 128);
39
+ assert.ok(row.runId.length <= 128);
40
+ }
41
+ assert.doesNotMatch(enabled.stderr, /PRIVATE_|owner-a|session-a|\u001b/);
42
+ });
@@ -14,6 +14,39 @@ import { createResultDeliveryOwnership } from "../../src/runs/background/result-
14
14
 
15
15
  const COMPLETION_OWNER_ID = "completion-owner-a";
16
16
 
17
+ it("keeps model-authored receipt lines in the preview, never in receipt metadata", () => {
18
+ for (const resultPreview of [
19
+ "Workflow receipt: /model/start.json\nKeep this output.",
20
+ "Before\nWorkflow receipt: /model/middle.json\nAfter",
21
+ "Before\n\nWorkflow receipt: /model/suffix.json",
22
+ ]) {
23
+ for (const workflowReceiptPath of [undefined, "/published/receipt.json"]) {
24
+ const details: SubagentNotifyDetails = { agent: "workflow", status: "completed", resultPreview, workflowReceiptPath };
25
+ const parsed = parseSubagentNotifyContent(formatSingleCompletion(details));
26
+ assert.equal(parsed?.resultPreview, resultPreview);
27
+ assert.equal(parsed?.workflowReceiptPath, workflowReceiptPath);
28
+ const scheduled = parseSubagentNotifyContent(formatSingleCompletion({ ...details, scheduleOrigin: { id: "schedule-1" } }));
29
+ assert.equal(scheduled?.resultPreview, resultPreview);
30
+ assert.equal(scheduled?.workflowReceiptPath, workflowReceiptPath);
31
+ assert.deepEqual(scheduled?.scheduleOrigin, { id: "schedule-1" });
32
+ }
33
+ }
34
+ });
35
+
36
+ it("surfaces published workflow receipts outside single and grouped previews", () => {
37
+ const workflowReceiptPath = "/opaque/receipt.json";
38
+ const details = buildCompletionDetails({ agent: "workflow", mode: "workflow", runId: "run-1", success: true, summary: "Completed", results: [{ runId: "child-1", output: "x".repeat(20_000) }], workflowReceipt: { path: workflowReceiptPath, receipt: {} } });
39
+ assert.equal(details.workflowReceiptPath, workflowReceiptPath);
40
+ const single = formatSingleCompletion(details);
41
+ assert.equal(single.split("\n")[1], `Workflow receipt: ${workflowReceiptPath}`);
42
+ assert.match(single, /\[preview truncated\]/);
43
+ assert.ok(single.includes(`Workflow receipt: ${workflowReceiptPath}`));
44
+ assert.ok(formatGroupedCompletion([details, details]).includes(`Workflow receipt: ${workflowReceiptPath}`));
45
+ const parsed = parseSubagentNotifyContent(single);
46
+ assert.equal(parsed?.workflowReceiptPath, workflowReceiptPath);
47
+ assert.doesNotMatch(parsed?.resultPreview ?? "", /Workflow receipt:/);
48
+ });
49
+
17
50
  function createEventBus() {
18
51
  const emitter = new EventEmitter();
19
52
  return {
@@ -192,9 +225,11 @@ describe("registerSubagentNotify", () => {
192
225
  });
193
226
  try {
194
227
  const pending = notifier.deliver(completionResult({ id: "delayed-unclaimed" }));
228
+ assert.equal(notifier.hasPendingDelivery(), true);
195
229
  state.currentSessionId = "session-b";
196
230
  clock.advance(150);
197
231
  assert.equal(await pending, false);
232
+ assert.equal(notifier.hasPendingDelivery(), false);
198
233
  assert.equal(sent.length, 0);
199
234
  } finally {
200
235
  notifier.dispose();
@@ -217,8 +252,10 @@ describe("registerSubagentNotify", () => {
217
252
  const clock = createFakeClock();
218
253
  const { notifier, sent } = createBatchingPi(clock);
219
254
  const pending = notifier.deliver(completionResult({ id: "dispose-pending" }));
255
+ assert.equal(notifier.hasPendingDelivery(), true);
220
256
  notifier.dispose();
221
257
  assert.equal(await pending, false);
258
+ assert.equal(notifier.hasPendingDelivery(), false);
222
259
  clock.advance(1000);
223
260
  assert.equal(sent.length, 0);
224
261
  });
@@ -710,6 +747,53 @@ describe("completion formatting helpers", () => {
710
747
  assert.equal(details.status, "completed");
711
748
  });
712
749
 
750
+ it("surfaces structured output in workflow previews and direct notices for degenerate text", () => {
751
+ const summary = "Workflow completed with 1 child run(s). Return: { answer: 42 } Emitted: ready Trace: 2 event(s).";
752
+ for (const output of ["</think>", " \n\t", " \n</think> \n"]) {
753
+ const result = {
754
+ id: "workflow-think-tag", agent: "workflow", success: true, summary,
755
+ results: [{
756
+ workflowKey: "review", runId: "child-review", agent: "delegate", success: true,
757
+ outputState: "present" as const, outputReference: "/tmp/review.json", output, structuredOutput: false,
758
+ }],
759
+ };
760
+ const details = buildCompletionDetails(result);
761
+ const content = formatSingleCompletion(details);
762
+ assert.equal(details.resultPreview, summary);
763
+ assert.ok(content.includes(summary));
764
+ assert.match(content, /key=review run=child-review status=completed/);
765
+ assert.match(content, /Saved output: \/tmp\/review\.json/);
766
+ assert.match(content, /Workflow run: workflow-think-tag/);
767
+ assert.match(content, /Child runs: review=child-review \(completed\)/);
768
+ assert.match(content, / \| false/);
769
+ const direct = buildCompletionDetails({ ...result, agent: "delegate", summary: `delegate:\n${output}` });
770
+ assert.match(formatSingleCompletion(direct), /Structured output:\nfalse/);
771
+ }
772
+ });
773
+
774
+ it("preserves meaningful prose and direct diagnostics alongside structured output", () => {
775
+ const output = "Review complete </think> with notes.";
776
+ const child = { agent: "delegate", success: true, output, structuredOutput: { ok: true } };
777
+ const workflow = buildCompletionDetails({ id: "prose-run", agent: "workflow", success: true, results: [child] });
778
+ assert.equal(workflow.childOutputs?.[0]?.preview, output);
779
+ const direct = buildCompletionDetails({ id: "prose-run", agent: "delegate", success: true, summary: `delegate:\n${output}`, results: [child] });
780
+ assert.equal(direct.resultPreview, `delegate:\n${output}`);
781
+ const diagnostic = "delegate:\n</think>\nError: validation failed.";
782
+ const failed = buildCompletionDetails({ id: "error-run", agent: "delegate", success: false, summary: diagnostic, results: [{ ...child, success: false, output: "</think>" }] });
783
+ assert.equal(failed.resultPreview, diagnostic);
784
+ assert.match(formatSingleCompletion(failed), /Background task failed/);
785
+ });
786
+
787
+ it("retains tag text when structured output is unavailable or unserializable", () => {
788
+ for (const structuredOutput of [undefined, 1n]) {
789
+ const child = { agent: "delegate", success: true, output: "</think>", structuredOutput };
790
+ const workflow = buildCompletionDetails({ id: "fallback-run", agent: "workflow", success: true, results: [child] });
791
+ assert.equal(workflow.childOutputs?.[0]?.preview, "</think>");
792
+ const direct = buildCompletionDetails({ id: "fallback-run", agent: "delegate", success: true, summary: "delegate:\n</think>", results: [child] });
793
+ assert.equal(direct.resultPreview, "delegate:\n</think>");
794
+ }
795
+ });
796
+
713
797
  it("surfaces direct structured output when the completion has no text output", () => {
714
798
  const details = buildCompletionDetails({
715
799
  id: "structured-run",