@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
@@ -0,0 +1,137 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { visibleWidth } from "@earendil-works/pi-tui";
4
+ import {
5
+ renderSupervisorReply,
6
+ renderSupervisorRequest,
7
+ type SupervisorReplyEntryData,
8
+ type SupervisorRequestMessageDetails,
9
+ } from "../../src/intercom/supervisor-ui.ts";
10
+
11
+ const identityTheme = {
12
+ fg: (_color: string, text: string) => text,
13
+ };
14
+
15
+ function renderRequest(details: SupervisorRequestMessageDetails, content: string, width = 100, expanded = true): string[] {
16
+ const component = renderSupervisorRequest({ content, details }, { expanded }, identityTheme as never);
17
+ assert.ok(component);
18
+ return component.render(width);
19
+ }
20
+
21
+ function renderReply(data: SupervisorReplyEntryData, width = 100): string[] {
22
+ const component = renderSupervisorReply({ data }, { expanded: true }, identityTheme as never);
23
+ assert.ok(component);
24
+ return component.render(width);
25
+ }
26
+
27
+ describe("native supervisor UI", () => {
28
+ it("renders request metadata, interview shape, and an exact reply hint", () => {
29
+ const requestId = "request-123456";
30
+ const output = renderRequest({
31
+ id: requestId,
32
+ reason: "interview_request",
33
+ expectsReply: true,
34
+ runId: "run-1845",
35
+ agent: "worker",
36
+ childIndex: 2,
37
+ childTarget: "child-worker",
38
+ replyHint: `subagent_supervisor({ action: "reply", replyTo: "${requestId}", message: "..." })`,
39
+ interview: { approved: "boolean", rationale: "string" },
40
+ requestBody: "Should this change be applied?",
41
+ }, [
42
+ "Subagent requests a structured supervisor interview.",
43
+ "Run: run-1845",
44
+ "Agent: worker",
45
+ "Child index: 2",
46
+ "Should this change be applied?",
47
+ `Reply with: subagent_supervisor({ action: "reply", replyTo: "${requestId}", message: "..." })`,
48
+ ].join("\n"));
49
+ const text = output.join("\n");
50
+
51
+ assert.match(text, /Supervisor interview request/);
52
+ assert.match(text, /Reason: interview_request/);
53
+ assert.match(text, /Run: run-1845/);
54
+ assert.match(text, /Agent: worker/);
55
+ assert.match(text, /Child index: 2/);
56
+ assert.match(text, /Child target: child-worker/);
57
+ assert.match(text, new RegExp(`Request ID: ${requestId}`));
58
+ assert.match(text, new RegExp(`replyTo: "${requestId}"`));
59
+ assert.match(text, /Interview shape:/);
60
+ assert.match(text, /approved/);
61
+ assert.match(text, /Should this change be applied/);
62
+ assert.equal(text.match(/Run: run-1845/g)?.length, 1);
63
+ assert.equal(text.match(/Agent: worker/g)?.length, 1);
64
+ assert.equal(text.match(/Child index: 2/g)?.length, 1);
65
+ assert.equal(text.match(/Reply with:/g)?.length, 1);
66
+ });
67
+
68
+ it("omits reply hints for progress updates and renders durable reply metadata", () => {
69
+ const requestOutput = renderRequest({
70
+ id: "progress-1",
71
+ reason: "progress_update",
72
+ expectsReply: false,
73
+ runId: "run-progress",
74
+ agent: "reviewer",
75
+ childIndex: 0,
76
+ }, "Progress is continuing.");
77
+ assert.equal(requestOutput.join("\n").includes("Reply with:"), false);
78
+
79
+ const replyOutput = renderReply({
80
+ requestId: "request-1",
81
+ reason: "need_decision",
82
+ runId: "run-1",
83
+ agent: "worker",
84
+ childIndex: 1,
85
+ childTarget: "child-worker",
86
+ message: "Approved; continue.",
87
+ createdAt: 123,
88
+ });
89
+ const text = replyOutput.join("\n");
90
+ assert.match(text, /Supervisor reply to child/);
91
+ assert.match(text, /Reply to: request-1/);
92
+ assert.match(text, /Approved; continue\./);
93
+ assert.match(text, /child-worker/);
94
+ });
95
+
96
+ it("ignores malformed request and reply metadata", () => {
97
+ assert.equal(renderSupervisorRequest({ content: "request", details: { id: "request-1", expectsReply: true, replyHint: ["bad"] } }, { expanded: true }, identityTheme as never), undefined);
98
+ assert.equal(renderSupervisorReply({ data: { requestId: "request-1", runId: "run-1", agent: "worker", childIndex: 0, message: ["bad"], createdAt: 123 } }, { expanded: true }, identityTheme as never), undefined);
99
+ });
100
+
101
+ it("sanitizes and bounds collapsed untrusted content at narrow widths", () => {
102
+ const output = renderRequest({
103
+ id: "request-long",
104
+ reason: "need_decision",
105
+ expectsReply: true,
106
+ runId: "run-long",
107
+ agent: "worker",
108
+ childIndex: 0,
109
+ }, `\u0001${"untrusted ".repeat(400)}`, 24, false);
110
+ assert.ok(output.length <= 39, `expected bounded rows, got ${output.length}`);
111
+ for (const line of output) assert.ok(visibleWidth(line) <= 24, `line exceeds width: ${line}`);
112
+ const text = output.join("\n");
113
+ assert.match(text, /truncated/);
114
+ assert.equal(text.includes("\u0001"), false);
115
+ assert.match(text, /\[U\+0001\]/);
116
+ });
117
+
118
+ it("keeps long blocking requests visible when expanded", () => {
119
+ const content = `${Array.from({ length: 45 }, (_, index) => `decision line ${index + 1}`).join("\n")}\n${"body-detail ".repeat(800)}END_BODY`;
120
+ const output = renderRequest({
121
+ id: "request-expanded",
122
+ reason: "need_decision",
123
+ expectsReply: true,
124
+ runId: "run-expanded",
125
+ agent: "worker",
126
+ childIndex: 0,
127
+ interview: { questions: Array.from({ length: 12 }, (_, index) => ({ id: `q${index + 1}`, question: `${"Question detail ".repeat(40)}${index === 11 ? "END_INTERVIEW" : index + 1}` })) },
128
+ }, content, 120, true);
129
+ const text = output.join("\n");
130
+
131
+ assert.ok(output.length > 39, `expected expanded rows, got ${output.length}`);
132
+ assert.match(text, /decision line 45/);
133
+ assert.match(text, /END_BODY/);
134
+ assert.match(text, /END_INTERVIEW/);
135
+ assert.doesNotMatch(text, /\[truncated\]/);
136
+ });
137
+ });
@@ -2,8 +2,6 @@ import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
3
  import {
4
4
  DEFAULT_TOOL_BUDGET_BLOCK,
5
- decodeToolBudgetEnv,
6
- encodeToolBudgetEnv,
7
5
  initialToolBudgetState,
8
6
  shouldBlockToolForBudget,
9
7
  toolBudgetBlockedMessage,
@@ -41,14 +39,6 @@ describe("tool-budget module", () => {
41
39
  assert.equal(validateToolBudgetConfig({ hard: 4, block: [""] }).error, "toolBudget.block must contain non-empty tool names.");
42
40
  });
43
41
 
44
- it("serializes and decodes env config", () => {
45
- const budget = { soft: 2, hard: 4, block: ["read"] };
46
- assert.deepEqual(decodeToolBudgetEnv(encodeToolBudgetEnv(budget)), budget);
47
- const zeroBudget = { hard: 0, block: "*" as const };
48
- assert.throws(() => decodeToolBudgetEnv(encodeToolBudgetEnv(zeroBudget)), /SELESAI_SUBAGENT_TOOL_BUDGET\.hard must be an integer >= 1/);
49
- assert.deepEqual(decodeToolBudgetEnv(encodeToolBudgetEnv(zeroBudget), { allowZero: true }), zeroBudget);
50
- });
51
-
52
42
  it("tracks state and block decisions", () => {
53
43
  const budget = { soft: 2, hard: 3, block: ["read"] };
54
44
  assert.deepEqual(initialToolBudgetState(budget), { soft: 2, hard: 3, block: ["read"], toolCount: 0, outcome: "within-budget" });
@@ -64,4 +54,4 @@ describe("tool-budget module", () => {
64
54
  assert.match(toolBudgetSoftNudge(budget, 2), /soft limit reached after 2 tool calls/);
65
55
  assert.match(toolBudgetBlockedMessage(budget, "read", 4), /'read' tool is blocked/);
66
56
  });
67
- });
57
+ });
@@ -12,7 +12,7 @@ import {
12
12
  FULL_SUBAGENT_TOOL_DESCRIPTION,
13
13
  SUBAGENT_SAFETY_GUIDANCE,
14
14
  } from "../../src/extension/tool-description.ts";
15
- import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../../src/runs/shared/pi-args.ts";
15
+ import { SUBAGENT_CHILD_ENV } from "../../src/runs/shared/child-runtime-config.ts";
16
16
 
17
17
  const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
18
18
 
@@ -23,7 +23,6 @@ function escapeRegex(value: string): string {
23
23
  function parentToolEnv(agentDir?: string): NodeJS.ProcessEnv {
24
24
  const env = { ...process.env };
25
25
  delete env[SUBAGENT_CHILD_ENV];
26
- delete env[SUBAGENT_FANOUT_CHILD_ENV];
27
26
  if (agentDir) env.SELESAI_CODING_AGENT_DIR = agentDir;
28
27
  return env;
29
28
  }
@@ -39,6 +38,13 @@ describe("registered subagent tool description", () => {
39
38
  assert.equal(metadata.promptGuidelines, undefined);
40
39
  });
41
40
 
41
+ it("explains managed worktree baseRef restrictions in every built-in description", () => {
42
+ for (const description of [FULL_SUBAGENT_TOOL_DESCRIPTION, COMPACT_SUBAGENT_TOOL_DESCRIPTION]) {
43
+ assert.match(description, /baseRef.*HEAD.*named ref.*40\/64-character commit IDs.*revision expressions.*unsupported/);
44
+ assert.match(description, /Omitted baseRef defaults to HEAD resolved at worktree allocation/);
45
+ }
46
+ });
47
+
42
48
  it("keeps the full description when configured", () => {
43
49
  const description = buildSubagentToolDescription({ toolDescriptionMode: "full" });
44
50
  assert.equal(buildSubagentToolPromptMetadata({ toolDescriptionMode: "full" }).promptSnippet, undefined);
@@ -77,6 +83,7 @@ describe("registered subagent tool description", () => {
77
83
  assert.match(description, /suffix on the model string.*off\/minimal\/low\/medium\/high\/xhigh\/max/i);
78
84
  assert.match(description, /suffix wins over the agent's thinking default/i);
79
85
  assert.match(description, /watchdog\.configure' and is ignored on dispatch/i);
86
+ assert.match(description, /lane infrastructure blocker.*explicit owner approval/i);
80
87
  });
81
88
 
82
89
  it("offers a compact mode that keeps the two-tier contract and safety guidance", () => {
@@ -108,6 +115,7 @@ describe("registered subagent tool description", () => {
108
115
  assert.match(description, /Per-run thinking is a suffix on the model string.*off\/minimal\/low\/medium\/high\/xhigh\/max/i);
109
116
  assert.match(description, /suffix wins over the agent's thinking default/i);
110
117
  assert.match(description, /watchdog\.configure' and is ignored on dispatch/i);
118
+ assert.match(description, /lane infrastructure blocker.*explicit owner approval/i);
111
119
  assert.ok(description.length < FULL_SUBAGENT_TOOL_DESCRIPTION.length);
112
120
  });
113
121
 
@@ -153,6 +161,18 @@ describe("registered subagent tool description", () => {
153
161
  assert.match(description, /status\.json/);
154
162
  });
155
163
 
164
+ it("deduplicates compact placeholder safety guidance in custom descriptions", () => {
165
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-compact-custom-"));
166
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-agent-"));
167
+ fs.mkdirSync(path.join(cwd, ".selesai"), { recursive: true });
168
+ fs.writeFileSync(path.join(cwd, ".selesai", "subagent-tool-description.md"), "{{compactDescription}}", "utf-8");
169
+
170
+ const description = buildSubagentToolDescription({ toolDescriptionMode: "custom" }, { cwd, agentDir });
171
+
172
+ assert.equal(description.split("lane infrastructure blocker").length - 1, 1);
173
+ assert.ok(description.endsWith(SUBAGENT_SAFETY_GUIDANCE));
174
+ });
175
+
156
176
  it("keeps mandatory safety guidance last when custom prose embeds it before an override", () => {
157
177
  const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-injection-"));
158
178
  const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-agent-"));
@@ -261,7 +281,7 @@ describe("registered subagent tool description", () => {
261
281
  fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify(config), "utf-8");
262
282
  }
263
283
 
264
- it("registers full, compact, custom, and fallback descriptions from extension config", () => {
284
+ it("registers split, full, compact, custom, and fallback descriptions from extension config", () => {
265
285
  const defaultAgentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-tool-desc-default-"));
266
286
  writeExtensionConfig(defaultAgentDir, {});
267
287
  const defaultTool = readRegisteredTool(defaultAgentDir);
@@ -3,10 +3,16 @@ import { describe, it } from "node:test";
3
3
  import { toWaitCompletion } from "../../src/runs/background/wait-completions.ts";
4
4
 
5
5
  describe("workflow wait completion projection", () => {
6
+ it("omits absent and malformed receipt references", () => {
7
+ for (const workflowReceipt of [undefined, null, [], "path", { path: "" }, { path: 42 }]) {
8
+ assert.equal("workflowReceiptPath" in toWaitCompletion({ workflowReceipt }, "run"), false);
9
+ }
10
+ });
6
11
  it("retains the bounded workflow-child summary and excludes result output", () => {
7
12
  const completion = toWaitCompletion({
8
13
  agent: "workflow",
9
14
  mode: "workflow",
15
+ workflowReceipt: { path: "/opaque/published-receipt.json", receipt: { output: "must not be copied" } },
10
16
  state: "complete",
11
17
  success: true,
12
18
  workflowChildren: {
@@ -29,6 +35,7 @@ describe("workflow wait completion projection", () => {
29
35
  }, "workflow-1");
30
36
 
31
37
  assert.equal(completion.workflowChildren?.children[0]?.childId, "review");
38
+ assert.equal(completion.workflowReceiptPath, "/opaque/published-receipt.json");
32
39
  assert.deepEqual(completion.results?.[0]?.usage, { input: 10, output: 2, cacheRead: 30, cacheWrite: 0, cost: 0.04, turns: 1 });
33
40
  assert.equal(completion.results?.[0]?.sessionFile, "/sessions/run-1.jsonl");
34
41
  assert.doesNotMatch(JSON.stringify(completion), /must not be copied/);
@@ -107,4 +114,4 @@ describe("workflow wait completion projection", () => {
107
114
  it("rejects a summary bound to another completion", () => {
108
115
  assert.throws(() => toWaitCompletion({ workflowChildren: { version: 1, parentToolCallId: "tool", workflowRunId: "other", inventoryComplete: true, workflowState: "completed", children: [] } }, "run"), /does not match its completion run id/);
109
116
  });
110
- });
117
+ });
@@ -77,6 +77,24 @@ describe("watchdog tool actions", () => {
77
77
  assert.equal(result.isError, undefined);
78
78
  assert.equal(runtime.getSnapshot(tempProject).config.main.model, "anthropic/claude-opus-4-8");
79
79
  assert.equal(runtime.getSnapshot(tempProject).config.main.thinking, "low");
80
+ const off = handleWatchdogToolAction("watchdog.configure", { thinking: false }, ctx, runtime);
81
+ assert.equal(off.isError, undefined);
82
+ assert.equal(runtime.getSnapshot(tempProject).config.main.thinking, false);
83
+ });
84
+
85
+ it("rejects transport-permitted true thinking without changing session or settings", () => {
86
+ const runtime = new MainWatchdogRuntime({ cwd: tempProject });
87
+ const ctx = createCtx({ provider: "openai-codex", id: "gpt-5.5" });
88
+ const before = runtime.getSnapshot(tempProject).config.main;
89
+ for (const scope of ["session", "user"]) {
90
+ // SAFETY: Deliberately pass invalid tool JSON to prove the false-only runtime contract.
91
+ const result = handleWatchdogToolAction("watchdog.configure", { scope, thinking: true as never }, ctx, runtime);
92
+ assert.equal(result.isError, true);
93
+ assert.match(text(result), /Unsupported watchdog thinking 'true'/);
94
+ assert.deepEqual(runtime.getSnapshot(tempProject).config.main, before);
95
+ }
96
+ assert.equal(fs.existsSync(path.join(tempHome, ".selesai", "agent", "settings.json")), false);
97
+ assert.equal(fs.existsSync(path.join(tempProject, ".selesai", "settings.json")), false);
80
98
  });
81
99
 
82
100
  it("persists the recommended model when user scope is explicit", () => {
@@ -88,4 +106,4 @@ describe("watchdog tool actions", () => {
88
106
  assert.equal(settings.subagents.watchdog.main.model, "openai-codex/gpt-5.5");
89
107
  assert.equal(settings.subagents.watchdog.main.thinking, "high");
90
108
  });
91
- });
109
+ });
@@ -7,6 +7,7 @@ import { describe, it } from "node:test";
7
7
  import { buildWorkflowChatProgressRows, isSameGitRepository, resolveWorkflowChatProgress } from "../../src/workflows/chat-progress.ts";
8
8
  import { renderSubagentResult } from "../../src/tui/render.ts";
9
9
  import { bindMissionWorkflowChildAsyncLaunch, createSubagentExecutor, foregroundResultIntercomStatus, missionWorkflowChildStatus, runMissionWorkflowChild, shouldSuppressRoutineResultIntercom } from "../../src/runs/foreground/subagent-executor.ts";
10
+ import { encodeIndexSegment } from "../../src/runs/background/index-segment.ts";
10
11
  import { readMissionBinding } from "../../src/missions/lifecycle.ts";
11
12
  import { createMission, readMission } from "../../src/missions/store.ts";
12
13
  import { DIRS, type Details, type SingleResult, type SubagentState } from "../../src/shared/types.ts";
@@ -110,12 +111,76 @@ describe("workflow chat progress policy", () => {
110
111
  });
111
112
 
112
113
  describe("workflow chat progress rendering", () => {
113
- it("emits live-card updates for same-repo watched workflowScript runs", async () => {
114
+ for (const scenario of ["explicit off", "non-repository auto", "cross-repository auto"] as const) {
115
+ it(`emits foreground lifecycle updates before settlement with ${scenario}`, async () => {
116
+ const root = scenario === "non-repository auto"
117
+ ? fs.mkdtempSync(path.join(os.tmpdir(), "pi-workflow-progress-headless-"))
118
+ : createRepo("pi-workflow-progress-off-");
119
+ const other = scenario === "cross-repository auto" ? createRepo("pi-workflow-progress-cross-") : undefined;
120
+ try {
121
+ const updates: Details[] = [];
122
+ let settled = false;
123
+ const result = await createExecutor().execute(
124
+ "wf-headless",
125
+ {
126
+ workflowScript: `return await runs.run("scout", { agent: "missing-agent", task: "scan" });`,
127
+ async: false,
128
+ chatProgress: scenario === "explicit off" ? "off" : "auto",
129
+ ...(other ? { cwd: other } : {}),
130
+ },
131
+ new AbortController().signal,
132
+ (update) => {
133
+ assert.equal(settled, false);
134
+ updates.push(structuredClone(update.details));
135
+ },
136
+ ctx(root),
137
+ ).then((result) => { settled = true; return result; });
138
+ assert.equal(result.isError, true);
139
+ assert.ok(updates.every((details) => details.chatProgress?.mode === "off"));
140
+ const started = updates.find((details) => details.workflowChildren?.children[0]?.state === "running");
141
+ assert.ok(started, "expected running child inventory before failure");
142
+ assert.equal(started.mode, "workflow");
143
+ assert.equal(started.runId, result.details.runId);
144
+ assert.equal(started.workflowChildren?.parentToolCallId, "wf-headless");
145
+ assert.equal(started.workflowChildren?.workflowRunId, result.details.runId);
146
+ assert.equal(started.workflowChildren?.inventoryComplete, false);
147
+ assert.equal(started.workflow?.trace[0]?.key, "scout");
148
+ assert.ok(updates.some((details) => details.workflow?.trace.some((entry) => entry.state === "failed")));
149
+ assert.equal(result.details.workflowChildren?.inventoryComplete, true);
150
+ } finally {
151
+ fs.rmSync(root, { recursive: true, force: true });
152
+ if (other) fs.rmSync(other, { recursive: true, force: true });
153
+ }
154
+ });
155
+ }
156
+
157
+ it("emits headless workflow values before returning final output without an update callback requirement", async () => {
158
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-workflow-progress-emits-"));
159
+ try {
160
+ const updates: Details[] = [];
161
+ const params = { workflowScript: `emit({ phase: "checking" }); return "done";`, async: false, chatProgress: "off" as const };
162
+ const result = await createExecutor().execute("wf-emits", params, undefined, (update) => {
163
+ assert.equal(update.details.workflow?.value, undefined);
164
+ updates.push(structuredClone(update.details));
165
+ }, ctx(root));
166
+ assert.ok(updates.some((details) => details.workflow?.emits.some((value) => (value as { phase?: string }).phase === "checking")));
167
+ assert.equal(result.details.workflow?.value, "done");
168
+ assert.equal(result.isError, undefined);
169
+ const withoutCallback = await createExecutor().execute("wf-no-callback", params, undefined, undefined, ctx(root));
170
+ assert.equal(withoutCallback.details.workflow?.value, "done");
171
+ } finally {
172
+ fs.rmSync(root, { recursive: true, force: true });
173
+ }
174
+ });
175
+
176
+ it("emits live-card updates with bounded workflow ids", async () => {
114
177
  const repo = createRepo("pi-workflow-progress-executor-");
178
+ const toolCallId = `wf-live-${"x".repeat(300)}`;
179
+ const workflowRunId = encodeIndexSegment(toolCallId);
115
180
  try {
116
181
  const updates: Array<{ details?: Details }> = [];
117
182
  const result = await createExecutor().execute(
118
- "wf-live",
183
+ toolCallId,
119
184
  { workflowScript: `return await runs.run("scout", { agent: "missing-agent", task: "scan", phase: "Validation", label: "Find renderer seam" });`, async: false },
120
185
  new AbortController().signal,
121
186
  (update) => updates.push(update),
@@ -124,16 +189,19 @@ describe("workflow chat progress rendering", () => {
124
189
  assert.equal(result.isError, true);
125
190
  const liveUpdate = updates.find((update) => update.details?.chatProgress?.mode === "live-card");
126
191
  assert.ok(liveUpdate, "expected a live-card update");
192
+ assert.equal(liveUpdate.details?.runId, workflowRunId);
193
+ assert.equal(liveUpdate.details?.workflowChildren?.workflowRunId, workflowRunId);
127
194
  assert.equal(liveUpdate.details?.workflow?.trace[0]?.key, "scout");
128
195
  assert.equal(liveUpdate.details?.workflow?.trace[0]?.phase, "Validation");
129
196
  assert.equal(liveUpdate.details?.workflow?.trace[0]?.label, "Find renderer seam");
130
197
  const ledgerChild = result.details.mission?.workflowChildren[0];
131
198
  assert.equal(ledgerChild?.key, "scout");
132
- assert.equal(ledgerChild?.workflowRunId, "wf-live");
199
+ assert.equal(ledgerChild?.workflowRunId, workflowRunId);
133
200
  assert.equal(ledgerChild?.agent, "missing-agent");
134
201
  assert.equal(ledgerChild?.phase, "Validation");
135
202
  assert.equal(ledgerChild?.status, "failed");
136
203
  assert.equal(ledgerChild?.heartbeat?.status, "failed");
204
+ assert.equal(result.details.workflowChildren?.workflowRunId, workflowRunId);
137
205
  } finally {
138
206
  fs.rmSync(repo, { recursive: true, force: true });
139
207
  }
@@ -567,4 +635,4 @@ describe("workflow chat progress rendering", () => {
567
635
  assert.match(text, /final answer/);
568
636
  assert.doesNotMatch(text, /workflow wf_done/);
569
637
  });
570
- });
638
+ });
@@ -2,7 +2,7 @@ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
 
4
4
  import { formatWorkflowChecklistBottleneck, formatWorkflowChecklistText, projectWorkflowChecklist } from "../../src/workflows/workflow-checklist.ts";
5
- import type { HostStepNodeV1, WorkflowGraphSnapshot } from "../../src/shared/types.ts";
5
+ import type { HostStepNode, WorkflowGraphSnapshot } from "../../src/shared/types.ts";
6
6
 
7
7
  function graph(): WorkflowGraphSnapshot {
8
8
  return {
@@ -50,7 +50,7 @@ test("workflow checklist fuses graph phases with loaded child state without dupl
50
50
  });
51
51
 
52
52
  test("workflow checklist preserves explicit host monitor verdicts and trace lane identity", () => {
53
- const host: HostStepNodeV1 = {
53
+ const host: HostStepNode = {
54
54
  version: 1,
55
55
  kind: "host-step",
56
56
  monitorKind: "ci",
@@ -81,8 +81,22 @@ test("workflow checklist preserves explicit host monitor verdicts and trace lane
81
81
  assert.match(formatWorkflowChecklistBottleneck(projection.bottleneck) ?? "", /CI/);
82
82
  });
83
83
 
84
+ test("workflow checklist renders a failed item error only once when item details are included", () => {
85
+ const projection = projectWorkflowChecklist({
86
+ steps: [{ workflowKey: "review", agent: "reviewer", status: "failed", error: "review failed with details" }],
87
+ });
88
+
89
+ const expanded = formatWorkflowChecklistText(projection).join("\n");
90
+ assert.equal(expanded.match(/review failed with details/g)?.length, 1);
91
+ assert.doesNotMatch(expanded, /bottleneck .*error:review failed with details/);
92
+
93
+ const collapsed = formatWorkflowChecklistText(projection, "", { includeItems: false }).join("\n");
94
+ assert.equal(collapsed.match(/review failed with details/g)?.length, 1);
95
+ assert.match(collapsed, /bottleneck .*error:review failed with details/);
96
+ });
97
+
84
98
  test("workflow checklist counts one host monitor when host status and trace share an id", () => {
85
- const host: HostStepNodeV1 = {
99
+ const host: HostStepNode = {
86
100
  version: 1,
87
101
  kind: "host-step",
88
102
  monitorKind: "ci",
@@ -0,0 +1,34 @@
1
+ import assert from "node:assert/strict";
2
+ import { it } from "node:test";
3
+ import { workflowChildActivity, workflowChildProgress, workflowChildSummary, parseWorkflowChildSummary } from "../../src/workflows/workflow-child-summary.ts";
4
+ import type { AgentProgress } from "../../src/shared/types.ts";
5
+
6
+ const progress = { agent: "worker", status: "running", model: "provider/model", thinking: "high", currentTool: "read", currentToolStartedAt: 100, lastActivityAt: 120, durationMs: 20, toolCount: 1, turnCount: 2, tokens: 10, inputTokens: 8, outputTokens: 2, task: "private", currentToolArgs: "private", recentOutput: ["private"] } as AgentProgress;
7
+ const input = { parentToolCallId: "tool", workflowRunId: "wf", workflowState: "running" as const, inventoryComplete: false, trace: ["a", "b"].map((key) => ({ operation: "run" as const, key, state: "started" as const })) };
8
+
9
+ it("projects independent bounded snapshots by stable key and clears terminal activity", () => {
10
+ const snapshots = new Map([["b", workflowChildProgress(progress)], ["a", workflowChildProgress({ ...progress, currentTool: "bash", toolCount: 9 })]]);
11
+ const summary = workflowChildSummary({ ...input, progress: snapshots });
12
+ assert.deepEqual(summary.children.map((row) => [row.childId, row.activity?.currentTool, row.activity?.toolCount]), [["a", "bash", 9], ["b", "read", 1]]);
13
+ assert.equal(summary.children[0].model, progress.model);
14
+ assert.equal(summary.children[0].thinking, "high");
15
+ assert.equal(JSON.stringify(summary).includes("private"), false);
16
+ assert.deepEqual(parseWorkflowChildSummary(summary), summary);
17
+ snapshots.set("a", workflowChildProgress({ ...progress, currentTool: undefined, currentToolStartedAt: undefined }));
18
+ assert.equal(workflowChildSummary({ ...input, progress: snapshots }).children[0].activity?.currentTool, undefined);
19
+ assert.equal(summary.children[0].activity?.currentTool, "bash", "published snapshots stay immutable");
20
+ assert.ok(workflowChildSummary({ ...input, inventoryComplete: true, workflowState: "failed", progress: snapshots }).children.every((row) => row.activity === undefined));
21
+ });
22
+
23
+ it("bounds UTF-8 and JSON bytes and rejects malformed activity strictly", () => {
24
+ assert.equal(workflowChildActivity({ currentTool: "界".repeat(86) }).currentTool, undefined);
25
+ assert.equal(workflowChildActivity({ currentTool: "界".repeat(85) }).currentTool?.length, 85);
26
+ const activity = workflowChildActivity({ ...progress, currentTool: "\u0000".repeat(256), tokens: Number.MAX_VALUE });
27
+ assert.ok(Buffer.byteLength(JSON.stringify(activity)) < 2048);
28
+ assert.deepEqual(workflowChildActivity({ tokens: NaN, toolCount: -1, durationMs: Infinity }), {});
29
+ const summary = workflowChildSummary({ ...input, progress: new Map([["a", workflowChildProgress(progress)]]) });
30
+ for (const invalid of [null, [], { tokens: -1 }, { tokens: Infinity }, { tokens: "1" }, { currentTool: "界".repeat(86) }, { recentOutput: [] }, { currentToolArgs: "secret" }]) {
31
+ assert.throws(() => parseWorkflowChildSummary({ ...summary, children: [{ ...summary.children[0], activity: invalid }] }), /activity/);
32
+ }
33
+ assert.throws(() => parseWorkflowChildSummary({ ...summary, children: [{ ...summary.children[0], state: "completed" }] }), /running state/);
34
+ });
@@ -5,6 +5,7 @@ import { describe, it } from "node:test";
5
5
  import { applyDetachedChildToPausedWorkflow, promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "../../src/runs/foreground/workflow-detach-reconcile.ts";
6
6
  import { DIRS, type AsyncStatus, type IntercomEventBus, type SubagentState } from "../../src/shared/types.ts";
7
7
  import { buildWorkflowReceipt, writeWorkflowReceipt } from "../../src/workflows/workflow-receipt.ts";
8
+ import { buildCompletionDetails, formatSingleCompletion, parseSubagentNotifyContent, type CompletionNotification } from "../../src/runs/background/notify.ts";
8
9
 
9
10
  function pausedWorkflow(childRunId: string, extra?: Partial<NonNullable<AsyncStatus["steps"]>[number]>): AsyncStatus {
10
11
  return {
@@ -176,10 +177,12 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
176
177
  const state = {
177
178
  asyncJobs: new Map([[workflowRunId, { asyncId: workflowRunId, asyncDir, status: "paused" as const }]]),
178
179
  } as SubagentState;
180
+ let emitted: CompletionNotification | undefined;
179
181
  assert.equal(reconcileDetachedWorkflowChildCompletion({
180
182
  state,
181
183
  workflowRunId,
182
184
  childRunId: "child-1",
185
+ events: { emit: (_name, payload) => { emitted = payload as CompletionNotification; } } as IntercomEventBus,
183
186
  result: { index: 0, agent: "worker", task: `Write your findings to exactly this path: ${requestedPath}`, exitCode: 0, sessionFile, savedOutputPath: savedPath, usage: { input: 100, output: 50, cacheRead: 25, cacheWrite: 5, cost: 0.001, turns: 1 } },
184
187
  }), true);
185
188
  const published = JSON.parse(fs.readFileSync(path.join(DIRS.results, `${workflowRunId}.json`), "utf-8")) as { state?: string; success?: boolean; summary?: string; error?: string; sessionId?: string; workflowResolution?: string; recovery?: unknown[]; results?: Array<{ outputReference?: string; outputPathMapping?: unknown }>; workflowReceipt?: { receipt?: { state?: string; workflowResolution?: string; recovery?: unknown[]; entries?: Record<string, { resumability?: { state?: string; reason?: string } }> } } };
@@ -200,6 +203,11 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
200
203
  assert.equal(published.workflowReceipt?.receipt?.workflowResolution, "settled-awaiting-resume");
201
204
  assert.deepEqual(published.workflowReceipt?.receipt?.recovery, published.recovery);
202
205
  assert.equal(published.workflowReceipt?.receipt?.entries?.detaches?.resumability?.state, "resumable");
206
+ assert.ok(emitted);
207
+ const notification = parseSubagentNotifyContent(formatSingleCompletion(buildCompletionDetails(emitted)));
208
+ const publishedPath = (published.workflowReceipt as { path: string }).path;
209
+ assert.equal(notification?.workflowReceiptPath, publishedPath);
210
+ assert.deepEqual(JSON.parse(fs.readFileSync(notification.workflowReceiptPath!, "utf8")), published.workflowReceipt?.receipt);
203
211
  const events = fs.readFileSync(path.join(asyncDir, "events.jsonl"), "utf-8");
204
212
  assert.match(events, /"type":"subagent.workflow.completed"/);
205
213
  assert.match(events, /"state":"failed"/);
@@ -578,7 +586,8 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
578
586
  const asyncDir = path.join(DIRS.async, workflowRunId);
579
587
  fs.mkdirSync(asyncDir, { recursive: true });
580
588
  fs.mkdirSync(DIRS.results, { recursive: true });
581
- const status = { ...pausedWorkflow("child-1"), runId: workflowRunId, sessionId: "session-1" };
589
+ const status = { ...pausedWorkflow("child-1"), runId: workflowRunId, sessionId: "session-1", workflowReceiptPath: "/stale/receipt.json" };
590
+ fs.writeFileSync(path.join(DIRS.results, `${workflowRunId}.json`), JSON.stringify({ workflowReceipt: { path: status.workflowReceiptPath, receipt: {} } }));
582
591
  fs.writeFileSync(path.join(asyncDir, "status.json"), JSON.stringify(status), "utf-8");
583
592
  fs.writeFileSync(path.join(asyncDir, "workflow-receipt.json"), JSON.stringify({ version: 1, workflowRunId, state: "paused", createdAt: 1, entries: { detaches: { key: "wrong" } } }), "utf-8");
584
593
  const state = {
@@ -597,6 +606,7 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
597
606
  assert.equal(published.success, false);
598
607
  assert.match(published.error ?? "", /evidence-persistence-failed/);
599
608
  assert.equal(published.workflowReceipt, undefined);
609
+ assert.equal(JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf8")).workflowReceiptPath, undefined);
600
610
  const events = fs.readFileSync(path.join(asyncDir, "events.jsonl"), "utf-8");
601
611
  assert.match(events, /"type":"subagent.workflow.receipt_write_failed"/);
602
612
  assert.match(events, /"type":"subagent.workflow.completed"/);
@@ -774,4 +784,4 @@ describe("reconcileDetachedWorkflowChildCompletion", () => {
774
784
  assert.equal(published.workflowResolution, undefined);
775
785
  assert.equal(published.recovery, undefined);
776
786
  });
777
- });
787
+ });
@@ -4,7 +4,7 @@ import { prepareWorkflowLaunchParams, promptAuditRedoParams, resolveRevivalContr
4
4
  import { resolveControlConfig } from "../../src/runs/shared/subagent-control.ts";
5
5
 
6
6
  describe("workflow launch params", () => {
7
- it("keeps omitted workflow child async foreground", () => {
7
+ it("preserves omitted workflow child async defaults and awaits background resolution", () => {
8
8
  assert.deepEqual(
9
9
  prepareWorkflowLaunchParams(
10
10
  {},
@@ -15,13 +15,52 @@ describe("workflow launch params", () => {
15
15
  {
16
16
  agent: "worker",
17
17
  task: "Run",
18
- async: false,
18
+ workflowAwaitAsync: true,
19
19
  workflowParentRunId: "workflow-run",
20
20
  workflowKey: "run",
21
21
  },
22
22
  );
23
23
  });
24
24
 
25
+ it("forwards workflow baseRef only to launches where it can affect allocation", () => {
26
+ assert.equal(
27
+ prepareWorkflowLaunchParams(
28
+ { baseRef: "refs/heads/release" },
29
+ { agent: "worker", task: "Run" },
30
+ "workflow-run",
31
+ "run",
32
+ ).baseRef,
33
+ "refs/heads/release",
34
+ );
35
+ assert.equal(
36
+ prepareWorkflowLaunchParams(
37
+ { baseRef: "refs/heads/release" },
38
+ { resume: "retained-run", task: "Continue" },
39
+ "workflow-run",
40
+ "resume",
41
+ ).baseRef,
42
+ undefined,
43
+ );
44
+ assert.equal(
45
+ prepareWorkflowLaunchParams(
46
+ { baseRef: "refs/heads/release" },
47
+ { resume: "retained-run", task: "Continue", baseRef: "refs/heads/topic" },
48
+ "workflow-run",
49
+ "resume-explicit",
50
+ ).baseRef,
51
+ "refs/heads/topic",
52
+ );
53
+ assert.equal(
54
+ prepareWorkflowLaunchParams(
55
+ { baseRef: "refs/heads/release" },
56
+ { agent: "worker", task: "Run", baseRef: "refs/heads/topic" },
57
+ "workflow-run",
58
+ "override",
59
+ ).baseRef,
60
+ "refs/heads/topic",
61
+ );
62
+ });
63
+
25
64
  it("does not forward workflow capacity overrides to children", () => {
26
65
  const params = prepareWorkflowLaunchParams(
27
66
  { globalConcurrencyLimit: 2, maxSubagentSpawnsPerRun: 3 },
@@ -92,7 +131,8 @@ describe("workflow launch params", () => {
92
131
  "run",
93
132
  { parentDeadlineAt },
94
133
  );
95
- assert.equal(params.async, false);
134
+ assert.equal(params.async, undefined);
135
+ assert.equal(params.workflowAwaitAsync, true);
96
136
  assert.equal(params.timeoutMs, undefined);
97
137
  assert.equal(params.workflowParentDeadlineAt, parentDeadlineAt);
98
138
  });
@@ -199,7 +239,7 @@ describe("workflow launch params", () => {
199
239
  agent: "worker",
200
240
  task: "Run",
201
241
  intercomBridge: { mode: "off" },
202
- async: false,
242
+ workflowAwaitAsync: true,
203
243
  workflowParentRunId: "workflow-run",
204
244
  workflowKey: "isolated",
205
245
  },
@@ -226,7 +266,7 @@ describe("workflow launch params", () => {
226
266
  agent: "worker",
227
267
  task: "Implement",
228
268
  worktree: true,
229
- async: false,
269
+ workflowAwaitAsync: true,
230
270
  workflowParentRunId: "workflow-run",
231
271
  workflowKey: "gated",
232
272
  acceptance: { level: "verified", verify: [{ id: "gate", command: "npm test" }] },
@@ -404,4 +444,4 @@ describe("workflow launch params", () => {
404
444
  assert.equal(sanitized, "a".repeat(120));
405
445
  });
406
446
  });
407
- });
447
+ });