@selesai/code 0.5.19 → 0.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/dist/extensions/handoff-new.test.ts +8 -9
  2. package/dist/extensions/handoff-new.ts +6 -8
  3. package/dist/extensions/pi-intercom/broker/spawn.test.ts +31 -0
  4. package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
  5. package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
  6. package/dist/extensions/pi-subagents/README.md +602 -220
  7. package/dist/extensions/pi-subagents/banner.png +0 -0
  8. package/dist/extensions/pi-subagents/index.ts +1 -0
  9. package/dist/extensions/pi-subagents/install.mjs +3 -3
  10. package/dist/extensions/pi-subagents/package-lock.json +3405 -0
  11. package/dist/extensions/pi-subagents/package.json +26 -14
  12. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  13. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  14. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
  15. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
  16. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  17. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  18. package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
  20. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
  21. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  22. package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
  23. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
  24. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  25. package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
  26. package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
  27. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
  28. package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
  29. package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
  30. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
  31. package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
  32. package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
  33. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
  34. package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
  35. package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
  36. package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
  37. package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
  38. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
  39. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
  40. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
  41. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
  42. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
  43. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
  44. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
  45. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
  46. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
  47. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
  48. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
  49. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
  50. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
  51. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
  52. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
  53. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
  54. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
  55. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
  56. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
  57. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
  58. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
  59. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
  61. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
  62. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
  63. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
  64. package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
  65. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
  66. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
  67. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
  68. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
  69. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
  70. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
  71. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
  72. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
  73. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
  74. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
  75. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
  76. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
  77. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
  78. package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
  79. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
  80. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
  81. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
  84. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
  86. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
  88. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
  89. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
  90. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
  92. package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
  95. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
  96. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
  97. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
  98. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
  99. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
  100. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
  101. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
  102. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
  103. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
  104. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
  105. package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
  106. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
  107. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
  108. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
  109. package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
  110. package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
  111. package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
  112. package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
  113. package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
  114. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
  115. package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
  116. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
  117. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
  118. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
  119. package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
  120. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
  121. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
  122. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
  127. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
  128. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
  129. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
  130. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
  131. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
  132. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
  133. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
  134. package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
  135. package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
  136. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
  137. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
  138. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
  139. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
  140. package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
  141. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
  142. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
  143. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
  144. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
  145. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
  146. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
  147. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
  148. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
  149. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
  150. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
  151. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
  152. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
  153. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
  154. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
  155. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
  156. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
  157. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
  158. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
  159. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
  160. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
  161. package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
  162. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
  163. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
  164. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
  165. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
  166. package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
  167. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
  168. package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
  169. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  170. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  171. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
  172. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
  173. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
  175. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
  176. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
  177. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
  178. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
  179. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
  180. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
  181. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
  182. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
  183. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
  184. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
  185. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
  186. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
  187. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
  188. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
  189. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
  190. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
  191. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
  192. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
  193. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
  194. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
  195. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
  196. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
  197. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
  198. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
  199. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
  200. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
  201. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
  202. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
  203. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
  204. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
  205. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
  206. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
  207. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
  208. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
  209. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
  210. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
  211. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
  212. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
  213. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
  214. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
  215. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
  216. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
  217. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
  218. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
  219. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
  220. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
  221. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
  222. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
  223. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
  224. package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
  225. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
  226. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
  227. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
  228. package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
  229. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
  230. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
  231. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
  232. package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
  233. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
  234. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
  235. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
  236. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
  237. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
  238. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
  239. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
  240. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
  241. package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
  245. package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
  246. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
  247. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
  248. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
  249. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
  250. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
  251. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
  252. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
  253. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
  254. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
  255. package/dist/extensions/question/batch.ts +39 -0
  256. package/dist/extensions/question/dialog-adapter.ts +23 -1
  257. package/dist/extensions/question/index.ts +237 -7
  258. package/dist/extensions/question/schemas.ts +28 -15
  259. package/dist/extensions/question/tests/batch.test.ts +26 -0
  260. package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
  261. package/dist/extensions/question/tui-adapter.ts +2 -3
  262. package/dist/extensions/question/types.ts +37 -0
  263. package/dist/extensions/question/ui-protocol.ts +3 -3
  264. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  265. package/package.json +2 -1
@@ -5,13 +5,30 @@ import registerSubagentNotify, {
5
5
  buildCompletionDetails,
6
6
  formatGroupedCompletion,
7
7
  formatSingleCompletion,
8
+ parseSubagentNotifyContent,
8
9
  type RegisterSubagentNotifyOptions,
9
10
  type SubagentNotifyDetails,
10
11
  } from "../../src/runs/background/notify.ts";
11
- import { SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../src/shared/types.ts";
12
+ import { SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT } from "../../src/shared/types.ts";
13
+
14
+ function createEventBus() {
15
+ const emitter = new EventEmitter();
16
+ return {
17
+ on(event: string, listener: (...args: unknown[]) => void) {
18
+ emitter.on(event, listener);
19
+ return () => emitter.off(event, listener);
20
+ },
21
+ emit(event: string, ...args: unknown[]) {
22
+ return emitter.emit(event, ...args);
23
+ },
24
+ listenerCount(event: string) {
25
+ return emitter.listenerCount(event);
26
+ },
27
+ };
28
+ }
12
29
 
13
30
  function createPi(currentSessionId = "session-1", registerOptions: RegisterSubagentNotifyOptions = {}) {
14
- const events = new EventEmitter();
31
+ const events = createEventBus();
15
32
  const sent: Array<{ message: unknown; options: unknown }> = [];
16
33
  const pi = {
17
34
  events,
@@ -22,13 +39,13 @@ function createPi(currentSessionId = "session-1", registerOptions: RegisterSubag
22
39
 
23
40
  // Formatting-focused tests run with batching disabled so single completions
24
41
  // emit synchronously. Batching behavior is covered by the dedicated suite below.
25
- registerSubagentNotify(pi as never, { currentSessionId }, { batchConfig: { enabled: false }, ...registerOptions });
42
+ const notifier = registerSubagentNotify(pi as never, { currentSessionId }, { batchConfig: { enabled: false }, ...registerOptions });
26
43
 
27
- return { events, sent };
44
+ return { events, sent, notifier, dispose: () => notifier.dispose() };
28
45
  }
29
46
 
30
47
  function createBatchingPi(clock: ReturnType<typeof createFakeClock>, currentSessionId = "session-a") {
31
- const events = new EventEmitter();
48
+ const events = createEventBus();
32
49
  const sent: Array<{ message: unknown; options: unknown }> = [];
33
50
  const pi = {
34
51
  events,
@@ -36,12 +53,12 @@ function createBatchingPi(clock: ReturnType<typeof createFakeClock>, currentSess
36
53
  sent.push({ message, options });
37
54
  },
38
55
  };
39
- registerSubagentNotify(pi as never, { currentSessionId }, {
56
+ const notifier = registerSubagentNotify(pi as never, { currentSessionId }, {
40
57
  batchConfig: { enabled: true, debounceMs: 150, maxWaitMs: 1000, stragglerDebounceMs: 75, stragglerMaxWaitMs: 400, stragglerWindowMs: 2000 },
41
58
  timers: clock.api,
42
59
  now: clock.now,
43
60
  });
44
- return { events, sent };
61
+ return { events, sent, notifier, dispose: () => notifier.dispose() };
45
62
  }
46
63
 
47
64
  interface FakeJob {
@@ -117,6 +134,62 @@ describe("registerSubagentNotify", () => {
117
134
  });
118
135
  });
119
136
 
137
+ it("acknowledges direct delivery only after sendMessage accepts it", async () => {
138
+ const { notifier, sent } = createPi("session-a");
139
+ assert.equal(await notifier.deliver(completionResult({ id: "direct-accepted" })), true);
140
+ assert.equal(sent.length, 1);
141
+ });
142
+
143
+ it("rejects a pending batch when the notifier is disposed", async () => {
144
+ const clock = createFakeClock();
145
+ const { notifier, sent } = createBatchingPi(clock);
146
+ const pending = notifier.deliver(completionResult({ id: "dispose-pending" }));
147
+ notifier.dispose();
148
+ assert.equal(await pending, false);
149
+ clock.advance(1000);
150
+ assert.equal(sent.length, 0);
151
+ });
152
+
153
+ it("wakes the originating session with recovered detached foreground output", () => {
154
+ const { events, sent } = createPi();
155
+
156
+ events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
157
+ id: "foreground-run:0",
158
+ runId: "foreground-run",
159
+ source: "foreground",
160
+ agent: "reviewer",
161
+ success: true,
162
+ summary: "Recovered final review",
163
+ exitCode: 0,
164
+ timestamp: 123,
165
+ sessionId: "session-1",
166
+ });
167
+
168
+ assert.equal(sent.length, 1);
169
+ assert.deepEqual(sent[0], {
170
+ message: {
171
+ customType: "subagent-notify",
172
+ content: "Detached foreground task completed: **reviewer**\n\nRecovered final review",
173
+ display: true,
174
+ },
175
+ options: { triggerTurn: true },
176
+ });
177
+ });
178
+
179
+ it("does not deliver detached foreground completion to another active session", () => {
180
+ const { events, sent } = createPi("session-2");
181
+ events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
182
+ id: "foreground-run:0",
183
+ source: "foreground",
184
+ agent: "reviewer",
185
+ success: true,
186
+ summary: "Recovered final review",
187
+ timestamp: 123,
188
+ sessionId: "session-1",
189
+ });
190
+ assert.equal(sent.length, 0);
191
+ });
192
+
120
193
  it("preserves non-empty completion summaries", () => {
121
194
  const { events, sent } = createPi();
122
195
  const summary = " Done streaming\nAll clear ";
@@ -277,9 +350,71 @@ describe("registerSubagentNotify", () => {
277
350
  assert.match((sent[0]!.message as { content: string }).content, /^Background task completed: \*\*alpha\*\*/);
278
351
  assert.doesNotMatch((sent[0]!.message as { content: string }).content, /boom/);
279
352
  });
353
+
354
+ it("disposes queued completions without emitting and is idempotent", () => {
355
+ const clock = createFakeClock();
356
+ const { events, sent, dispose } = createBatchingPi(clock);
357
+
358
+ events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, completionResult({ id: "dispose-held-1" }));
359
+ assert.equal(sent.length, 0);
360
+ assert.equal(events.listenerCount(SUBAGENT_ASYNC_COMPLETE_EVENT), 1);
361
+ assert.equal(events.listenerCount(SUBAGENT_FOREGROUND_COMPLETE_EVENT), 1);
362
+
363
+ dispose();
364
+ dispose();
365
+ assert.equal(events.listenerCount(SUBAGENT_ASYNC_COMPLETE_EVENT), 0);
366
+ assert.equal(events.listenerCount(SUBAGENT_FOREGROUND_COMPLETE_EVENT), 0);
367
+
368
+ clock.advance(1000);
369
+ assert.equal(sent.length, 0);
370
+ });
371
+
372
+ it("does not let a disposed notifier affect another runtime", () => {
373
+ const oldClock = createFakeClock();
374
+ const oldRegistration = createBatchingPi(oldClock);
375
+ oldRegistration.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, completionResult({ id: "old-owner-held-1" }));
376
+
377
+ const newClock = createFakeClock();
378
+ const newRegistration = createBatchingPi(newClock);
379
+ oldRegistration.dispose();
380
+ oldClock.advance(1000);
381
+ assert.equal(oldRegistration.sent.length, 0);
382
+
383
+ newRegistration.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, completionResult({ id: "new-owner-1" }));
384
+ newClock.advance(150);
385
+ assert.equal(newRegistration.sent.length, 1);
386
+ newRegistration.dispose();
387
+ });
280
388
  });
281
389
 
282
390
  describe("completion formatting helpers", () => {
391
+ it("formats and parses a parallel handoff without folding it into the result preview", () => {
392
+ const content = formatSingleCompletion({
393
+ agent: "worker",
394
+ status: "completed",
395
+ resultPreview: "Done",
396
+ handoffPath: "/tmp/run/handoff.json",
397
+ sessionLabel: "Session file",
398
+ sessionValue: "/tmp/session.jsonl",
399
+ });
400
+ assert.equal(content, "Background task completed: **worker**\n\nDone\n\nParallel handoff: /tmp/run/handoff.json\n\nSession file: /tmp/session.jsonl");
401
+ assert.deepEqual(parseSubagentNotifyContent(content), {
402
+ agent: "worker",
403
+ status: "completed",
404
+ resultPreview: "Done",
405
+ handoffPath: "/tmp/run/handoff.json",
406
+ sessionLabel: "session file",
407
+ sessionValue: "/tmp/session.jsonl",
408
+ });
409
+ assert.equal(buildCompletionDetails({
410
+ id: "run",
411
+ agent: "worker",
412
+ success: true,
413
+ summary: "Done",
414
+ parallelHandoff: { path: "/tmp/run/handoff.json" },
415
+ }).handoffPath, "/tmp/run/handoff.json");
416
+ });
417
+
283
418
  it("formatSingleCompletion mirrors the in-handler single message shape", () => {
284
419
  const content = formatSingleCompletion({
285
420
  agent: "worker",
@@ -292,6 +427,25 @@ describe("completion formatting helpers", () => {
292
427
  assert.equal(content, "Background task completed: **worker** (2/3)\n\nDone\n\nSession file: /tmp/session.jsonl");
293
428
  });
294
429
 
430
+ it("parses detached foreground notification content for the custom renderer", () => {
431
+ const content = formatSingleCompletion({
432
+ agent: "reviewer",
433
+ status: "failed",
434
+ source: "foreground",
435
+ resultPreview: "Acceptance rejected",
436
+ sessionLabel: "Session file",
437
+ sessionValue: "/tmp/reviewer.jsonl",
438
+ });
439
+ assert.deepEqual(parseSubagentNotifyContent(content), {
440
+ agent: "reviewer",
441
+ status: "failed",
442
+ source: "foreground",
443
+ resultPreview: "Acceptance rejected",
444
+ sessionLabel: "session file",
445
+ sessionValue: "/tmp/reviewer.jsonl",
446
+ });
447
+ });
448
+
295
449
  it("formatGroupedCompletion lists each agent with its summary and session", () => {
296
450
  const content = formatGroupedCompletion([
297
451
  { agent: "alpha", status: "completed", resultPreview: "alpha done" },
@@ -305,6 +459,13 @@ describe("completion formatting helpers", () => {
305
459
  );
306
460
  });
307
461
 
462
+ it("reports false when Pi rejects sendMessage synchronously", async () => {
463
+ const pi = { events: createEventBus(), sendMessage() { throw new Error("runtime inactive"); } };
464
+ const notifier = registerSubagentNotify(pi as never, { currentSessionId: "session-a" }, { batchConfig: { enabled: false } });
465
+ assert.equal(await notifier.deliver(completionResult({ id: "direct-rejected" })), false);
466
+ notifier.dispose();
467
+ });
468
+
308
469
  it("buildCompletionDetails derives paused status from state and summary", () => {
309
470
  assert.equal(buildCompletionDetails({ id: "x", agent: "w", success: false, state: "paused", summary: "Paused after interrupt.", timestamp: 1 }).status, "paused");
310
471
  assert.equal(buildCompletionDetails({ id: "x", agent: "w", success: false, summary: "boom", exitCode: 1, timestamp: 1 }).status, "failed");
@@ -11,32 +11,84 @@ const oldPiScopePattern = /@mariozechner\/pi-/;
11
11
  const piPackageJsonSubpathPattern = /@earendil-works\/pi-[^"']+\/package\.json/;
12
12
  const cjsPiPackageResolutionPattern = /require(?:\.resolve)?\(\s*["']@earendil-works\/pi-/;
13
13
  const exactVersionPattern = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
14
+ const hostPeerPackages = [
15
+ "@earendil-works/pi-agent-core",
16
+ "@earendil-works/pi-ai",
17
+ "@selesai/code",
18
+ "@earendil-works/pi-tui",
19
+ ] as const;
20
+ const expectedHostPeerRanges = {
21
+ "@earendil-works/pi-agent-core": "*",
22
+ "@earendil-works/pi-ai": ">=0.80.0",
23
+ "@selesai/code": "*",
24
+ "@earendil-works/pi-tui": "*",
25
+ } satisfies Record<(typeof hostPeerPackages)[number], string>;
26
+ const expectedHostDevVersions = {
27
+ "@earendil-works/pi-agent-core": "0.81.0",
28
+ "@earendil-works/pi-ai": "0.81.0",
29
+ "@selesai/code": "0.81.0",
30
+ "@earendil-works/pi-tui": "0.81.0",
31
+ } satisfies Record<(typeof hostPeerPackages)[number], string>;
14
32
 
15
- function collectTsFiles(dir: string): string[] {
33
+ function collectSourceFiles(dir: string): string[] {
16
34
  const files: string[] = [];
17
35
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
18
36
  const entryPath = path.join(dir, entry.name);
19
37
  if (entry.isDirectory()) {
20
- collectTsFiles(entryPath).forEach((file) => files.push(file));
21
- } else if (entry.name.endsWith(".ts")) {
38
+ collectSourceFiles(entryPath).forEach((file) => files.push(file));
39
+ } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".mjs")) {
22
40
  files.push(entryPath);
23
41
  }
24
42
  }
25
43
  return files;
26
44
  }
27
45
 
46
+ test("published extension APIs use supported package entrypoints", async () => {
47
+ const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
48
+
49
+ assert.deepEqual(packageJson.pi?.extensions, ["./index.ts"]);
50
+ assert.equal(packageJson.files?.includes("index.ts"), true);
51
+ assert.equal(
52
+ fs.readFileSync(path.join(projectRoot, "index.ts"), "utf-8").trim(),
53
+ 'export { default } from "./src/extension/index.ts";',
54
+ );
55
+ assert.equal(fs.existsSync(path.join(projectRoot, "src", "api", "delegation.ts")), true);
56
+ assert.deepEqual(packageJson.exports, {
57
+ ".": "./index.ts",
58
+ "./background-work": "./src/api/background-work.ts",
59
+ "./capability-ceiling": "./src/api/capability-ceiling.ts",
60
+ "./delegation": "./src/api/delegation.ts",
61
+ "./preflight": "./src/api/preflight.ts",
62
+ });
63
+ const backgroundWork = await import("pi-subagents/background-work");
64
+ assert.equal(backgroundWork.BACKGROUND_WORK_PROTOCOL_VERSION, 1);
65
+ assert.equal(backgroundWork.BACKGROUND_WORK_REGISTRY_KEY, "pi-subagents.background-work.v1");
66
+ const capability = await import("pi-subagents/capability-ceiling");
67
+ assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_VERSION, 1);
68
+ assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY, "pi-subagents.capability-ceiling.v1");
69
+ const delegation = await import("pi-subagents/delegation");
70
+ assert.equal(delegation.SUBAGENT_DELEGATION_PROTOCOL_VERSION, 1);
71
+ assert.equal(delegation.SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION, 2);
72
+ assert.equal(delegation.SUBAGENT_DELEGATION_REQUEST_EVENT, "prompt-template:subagent:request");
73
+ const preflight = await import("pi-subagents/preflight");
74
+ assert.equal(preflight.SUBAGENT_LAUNCH_CONTRACT_VERSION, 2);
75
+ assert.equal(typeof preflight.resolveSubagentLaunchContract, "function");
76
+ });
77
+
28
78
  test("direct @earendil-works runtime imports are declared for CI installs", () => {
29
79
  const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
30
80
  const declared = new Set([
31
81
  ...Object.keys(packageJson.dependencies ?? {}),
32
82
  ...Object.keys(packageJson.devDependencies ?? {}),
83
+ ...Object.keys(packageJson.peerDependencies ?? {}),
33
84
  ]);
34
85
  const imported = new Set<string>();
35
86
 
36
- for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) {
87
+ for (const file of [...collectSourceFiles(path.join(projectRoot, "src")), ...collectSourceFiles(path.join(projectRoot, "test"))]) {
37
88
  const source = fs.readFileSync(file, "utf-8");
38
89
  for (const match of source.matchAll(sourceImportPattern)) {
39
- imported.add(match[1] ?? match[2]!);
90
+ const specifier = match[1] ?? match[2]!;
91
+ imported.add(specifier.split("/").slice(0, 2).join("/"));
40
92
  }
41
93
  }
42
94
 
@@ -54,15 +106,41 @@ test("direct dependency declarations are exact version pins", () => {
54
106
  }
55
107
  });
56
108
 
109
+ test("host-owned packages are optional peers with supported ranges, not production dependencies", () => {
110
+ const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
111
+
112
+ for (const name of hostPeerPackages) {
113
+ assert.equal(packageJson.peerDependencies?.[name], expectedHostPeerRanges[name], `${name} should use its supported peer range`);
114
+ assert.equal(packageJson.dependencies?.[name], undefined, `${name} should not be a production dependency`);
115
+ assert.deepEqual(packageJson.peerDependenciesMeta?.[name], { optional: true }, `${name} should be an optional peer`);
116
+ }
117
+ });
118
+ test("typebox is a bundled runtime dependency", () => {
119
+ const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
120
+
121
+ assert.equal(packageJson.dependencies?.typebox, "1.1.38");
122
+ assert.equal(packageJson.peerDependencies?.typebox, undefined);
123
+ assert.equal(packageJson.peerDependenciesMeta?.typebox, undefined);
124
+ assert.equal(packageJson.devDependencies?.typebox, undefined);
125
+ });
126
+
127
+ test("host-owned development packages use the supported SDK baseline", () => {
128
+ const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
129
+
130
+ for (const [name, version] of Object.entries(expectedHostDevVersions)) {
131
+ assert.equal(packageJson.devDependencies?.[name], version, `${name} should use ${version}`);
132
+ }
133
+ });
134
+
57
135
  test("old pi package scope is not used by source or tests", () => {
58
- for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) {
136
+ for (const file of [...collectSourceFiles(path.join(projectRoot, "src")), ...collectSourceFiles(path.join(projectRoot, "test"))]) {
59
137
  const source = fs.readFileSync(file, "utf-8");
60
138
  assert.equal(oldPiScopePattern.test(source), false, file);
61
139
  }
62
140
  });
63
141
 
64
142
  test("Pi package resolution stays export-map safe", () => {
65
- for (const file of [...collectTsFiles(path.join(projectRoot, "src")), ...collectTsFiles(path.join(projectRoot, "test"))]) {
143
+ for (const file of [...collectSourceFiles(path.join(projectRoot, "src")), ...collectSourceFiles(path.join(projectRoot, "test"))]) {
66
144
  const source = fs.readFileSync(file, "utf-8");
67
145
  assert.equal(piPackageJsonSubpathPattern.test(source), false, `${file} should not resolve unexported package.json subpaths`);
68
146
  assert.equal(cjsPiPackageResolutionPattern.test(source), false, `${file} should not use CommonJS resolution for ESM-only Pi packages`);
@@ -0,0 +1,175 @@
1
+ import assert from "node:assert/strict";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { describe, it } from "node:test";
6
+ import {
7
+ formatParallelHandoffReference,
8
+ writeParallelHandoffGroup,
9
+ } from "../../src/runs/shared/parallel-handoff.ts";
10
+ import type { ParallelHandoffManifest } from "../../src/shared/types.ts";
11
+ import type { WorktreeCleanupReport, WorktreeDiff, WorktreeSetup } from "../../src/runs/shared/worktree.ts";
12
+
13
+ function setup(repoRoot: string, baseCommit: string): WorktreeSetup {
14
+ return { cwd: repoRoot, baseCommit, worktrees: [] };
15
+ }
16
+
17
+ function diff(dir: string, index: number, agent: string, changed: boolean): WorktreeDiff {
18
+ const patchPath = path.join(dir, `task-${index}-${agent}.patch`);
19
+ fs.mkdirSync(path.dirname(patchPath), { recursive: true });
20
+ fs.writeFileSync(patchPath, changed ? "diff --git a/a b/a\n" : "", "utf-8");
21
+ return {
22
+ index,
23
+ agent,
24
+ branch: `branch-${index}`,
25
+ diffStat: changed ? " a | 1 +" : "",
26
+ filesChanged: changed ? 1 : 0,
27
+ insertions: changed ? 1 : 0,
28
+ deletions: 0,
29
+ patchPath,
30
+ };
31
+ }
32
+
33
+ function cleanup(state: "complete" | "partial" = "complete"): WorktreeCleanupReport {
34
+ return {
35
+ state,
36
+ pruned: state === "complete",
37
+ tasks: [{
38
+ index: 0,
39
+ path: "/tmp/worktree-0",
40
+ branch: "branch-0",
41
+ worktreeRemoved: true,
42
+ branchRemoved: state === "complete",
43
+ ...(state === "partial" ? { errors: ["branch removal failed"] } : {}),
44
+ }],
45
+ };
46
+ }
47
+
48
+ describe("parallel handoff", () => {
49
+ it("writes and aggregates versioned worktree handoff groups", () => {
50
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-parallel-handoff-"));
51
+ try {
52
+ const manifestPath = path.join(dir, "handoff.json");
53
+ const first = writeParallelHandoffGroup({
54
+ manifestPath,
55
+ runId: "run-1",
56
+ mode: "chain",
57
+ source: "async",
58
+ cwd: "/repo",
59
+ stepIndex: 2,
60
+ flatStartIndex: 3,
61
+ setup: setup("/repo", "base-1"),
62
+ diffs: [diff(dir, 0, "worker", true)],
63
+ cleanup: cleanup(),
64
+ results: [{
65
+ agent: "worker",
66
+ status: "completed",
67
+ summary: "implemented",
68
+ outputPath: "/artifacts/output.md",
69
+ structuredOutput: { ok: true },
70
+ structuredOutputPath: "/artifacts/structured.json",
71
+ sessionPath: "/sessions/worker.jsonl",
72
+ }],
73
+ now: 100,
74
+ });
75
+ assert.deepEqual(first, {
76
+ version: 1,
77
+ path: manifestPath,
78
+ groupCount: 1,
79
+ childCount: 1,
80
+ changedPatches: 1,
81
+ cleanupState: "complete",
82
+ });
83
+
84
+ const second = writeParallelHandoffGroup({
85
+ manifestPath,
86
+ runId: "run-1",
87
+ mode: "chain",
88
+ source: "async",
89
+ cwd: "/repo",
90
+ stepIndex: 1,
91
+ flatStartIndex: 1,
92
+ setup: setup("/repo", "base-1"),
93
+ diffs: [diff(dir, 1, "reviewer", false)],
94
+ cleanup: cleanup("partial"),
95
+ results: [{ agent: "reviewer", status: "failed", summary: "blocked" }],
96
+ now: 200,
97
+ });
98
+ assert.equal(second.groupCount, 2);
99
+ assert.equal(second.childCount, 2);
100
+ assert.equal(second.changedPatches, 1);
101
+ assert.equal(second.cleanupState, "partial");
102
+ assert.match(formatParallelHandoffReference(second), /2 children, 1 changed patches, cleanup partial/);
103
+
104
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as ParallelHandoffManifest;
105
+ assert.equal(manifest.createdAt, 100);
106
+ assert.equal(manifest.updatedAt, 200);
107
+ assert.deepEqual(manifest.groups.map((group) => group.stepIndex), [1, 2]);
108
+ assert.equal(manifest.groups[0]!.children[0]!.index, 1);
109
+ assert.equal(manifest.groups[1]!.children[0]!.index, 3);
110
+ assert.deepEqual(manifest.groups[1]!.children[0]!.structuredOutput, { ok: true });
111
+ assert.equal(manifest.groups[1]!.children[0]!.patch.changed, true);
112
+ } finally {
113
+ fs.rmSync(dir, { recursive: true, force: true });
114
+ }
115
+ });
116
+
117
+ it("records missing diff artifacts instead of throwing", () => {
118
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-parallel-handoff-missing-diff-"));
119
+ try {
120
+ const manifestPath = path.join(dir, "handoff.json");
121
+ const reference = writeParallelHandoffGroup({
122
+ manifestPath,
123
+ runId: "run-missing-diff",
124
+ mode: "parallel",
125
+ source: "foreground",
126
+ cwd: "/repo",
127
+ stepIndex: 0,
128
+ flatStartIndex: 0,
129
+ setup: {
130
+ ...setup("/repo", "base-1"),
131
+ worktrees: [{ path: "/tmp/worktree-0", agentCwd: "/tmp/worktree-0", branch: "branch-0", index: 0, nodeModulesLinked: false, syntheticPaths: [] }],
132
+ },
133
+ diffs: [],
134
+ cleanup: cleanup(),
135
+ results: [{ agent: "bad/name agent", status: "completed", summary: "done" }],
136
+ });
137
+ assert.equal(reference.childCount, 1);
138
+ assert.equal(reference.changedPatches, 0);
139
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as ParallelHandoffManifest;
140
+ const patch = manifest.groups[0]!.children[0]!.patch;
141
+ assert.equal(patch.changed, false);
142
+ assert.match(patch.error ?? "", /diff artifact unavailable/);
143
+ assert.equal(path.basename(patch.path), "missing-diff-step-0-task-0-bad_name_agent.patch");
144
+ assert.equal(fs.existsSync(patch.path), true);
145
+ } finally {
146
+ fs.rmSync(dir, { recursive: true, force: true });
147
+ }
148
+ });
149
+
150
+ it("rejects reusing a manifest path for another run", () => {
151
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-parallel-handoff-owner-"));
152
+ try {
153
+ const manifestPath = path.join(dir, "handoff.json");
154
+ const common = {
155
+ manifestPath,
156
+ mode: "parallel" as const,
157
+ source: "foreground" as const,
158
+ cwd: "/repo",
159
+ stepIndex: 0,
160
+ flatStartIndex: 0,
161
+ setup: setup("/repo", "base-1"),
162
+ diffs: [diff(dir, 0, "worker", false)],
163
+ cleanup: cleanup(),
164
+ results: [{ agent: "worker", status: "completed" as const, summary: "done" }],
165
+ };
166
+ writeParallelHandoffGroup({ ...common, runId: "run-1" });
167
+ assert.throws(
168
+ () => writeParallelHandoffGroup({ ...common, runId: "run-2" }),
169
+ /belongs to a different run/,
170
+ );
171
+ } finally {
172
+ fs.rmSync(dir, { recursive: true, force: true });
173
+ }
174
+ });
175
+ });
@@ -54,7 +54,7 @@ describe("Path resolution for .agents and ~/.agents", () => {
54
54
  assert.strictEqual(resolved?.path, path.join(userSkillsDir, "test-skill-2.md"));
55
55
  });
56
56
 
57
- test("should resolve project agents from both .agents and .pi/agents", () => {
57
+ test("should resolve project agents from both .agents and .selesai/agents", () => {
58
58
  const legacyDir = path.join(cwdDir, ".agents");
59
59
  const agentsDir = path.join(cwdDir, ".selesai", "agents");
60
60
  fs.mkdirSync(path.join(cwdDir, ".agents", "skills"), { recursive: true });