@selesai/code 0.13.15 → 0.13.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +2 -14
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.js +3 -9
  5. package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
  6. package/dist/bun/sandbox-env-setup.d.ts +1 -0
  7. package/dist/bun/sandbox-env-setup.js +3 -0
  8. package/dist/cli/auth-command.d.ts +23 -0
  9. package/dist/cli/auth-command.js +102 -0
  10. package/dist/cli/config-selector.js +2 -1
  11. package/dist/cli/setup.d.ts +1 -0
  12. package/dist/cli/setup.js +11 -0
  13. package/dist/cli.js +2 -14
  14. package/dist/config.d.ts +3 -2
  15. package/dist/config.js +9 -4
  16. package/dist/core/agent-session-runtime.js +15 -7
  17. package/dist/core/agent-session.d.ts +2 -6
  18. package/dist/core/agent-session.js +15 -4
  19. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  20. package/dist/core/compaction/branch-summarization.js +2 -1
  21. package/dist/core/extensions/loader.js +3 -0
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/http-dispatcher.js +2 -0
  24. package/dist/core/keybindings.d.ts +5 -0
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/messages.d.ts +1 -1
  27. package/dist/core/model-config.d.ts +15 -0
  28. package/dist/core/model-config.js +3 -0
  29. package/dist/core/model-runtime.d.ts +1 -0
  30. package/dist/core/model-runtime.js +5 -2
  31. package/dist/core/provider-composer.js +8 -2
  32. package/dist/core/session-export.d.ts +3 -0
  33. package/dist/core/session-export.js +31 -0
  34. package/dist/core/session-manager.d.ts +3 -2
  35. package/dist/core/session-manager.js +40 -13
  36. package/dist/core/skills.d.ts +1 -1
  37. package/dist/core/skills.js +4 -2
  38. package/dist/core/system-prompt.js +10 -10
  39. package/dist/core/tools/bash.js +3 -117
  40. package/dist/core/tools/edit.d.ts +1 -12
  41. package/dist/core/tools/edit.js +5 -163
  42. package/dist/core/tools/find.js +4 -55
  43. package/dist/core/tools/grep.js +4 -60
  44. package/dist/core/tools/ls.js +4 -49
  45. package/dist/core/tools/read.d.ts +4 -0
  46. package/dist/core/tools/read.js +10 -122
  47. package/dist/core/tools/renderers/bash.d.ts +11 -0
  48. package/dist/core/tools/renderers/bash.js +129 -0
  49. package/dist/core/tools/renderers/edit.d.ts +22 -0
  50. package/dist/core/tools/renderers/edit.js +166 -0
  51. package/dist/core/tools/renderers/find.d.ts +9 -0
  52. package/dist/core/tools/renderers/find.js +63 -0
  53. package/dist/core/tools/renderers/grep.d.ts +9 -0
  54. package/dist/core/tools/renderers/grep.js +68 -0
  55. package/dist/core/tools/renderers/index.d.ts +27 -0
  56. package/dist/core/tools/renderers/index.js +46 -0
  57. package/dist/core/tools/renderers/ls.d.ts +9 -0
  58. package/dist/core/tools/renderers/ls.js +57 -0
  59. package/dist/core/tools/renderers/read.d.ts +10 -0
  60. package/dist/core/tools/renderers/read.js +128 -0
  61. package/dist/core/tools/renderers/write.d.ts +9 -0
  62. package/dist/core/tools/renderers/write.js +152 -0
  63. package/dist/core/tools/write.js +5 -146
  64. package/dist/extensions/capability-gateway/index.ts +12 -1
  65. package/dist/extensions/capability-gateway/integration.test.ts +23 -0
  66. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
  67. package/dist/extensions/pi-hermes-memory/README.md +16 -12
  68. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
  69. package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
  70. package/dist/extensions/pi-hermes-memory/package.json +4 -4
  71. package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
  72. package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
  73. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
  74. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
  75. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
  76. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
  77. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
  78. package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
  79. package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
  80. package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
  83. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
  84. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
  85. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
  86. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
  87. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
  88. package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
  89. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
  90. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
  91. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
  92. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
  93. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
  94. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
  95. package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
  96. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
  97. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
  98. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
  99. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
  100. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
  101. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
  102. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
  103. package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
  104. package/dist/extensions/pi-intercom/README.md +16 -2
  105. package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
  106. package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
  107. package/dist/extensions/pi-intercom/index.ts +1 -1
  108. package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
  109. package/dist/extensions/pi-intercom/package.json +1 -1
  110. package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
  111. package/dist/extensions/pi-intercom/project-agent.ts +1 -6
  112. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
  113. package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
  114. package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
  115. package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
  116. package/dist/extensions/pi-subagents/README.md +3 -3
  117. package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
  118. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
  119. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  120. package/dist/extensions/pi-subagents/docs/agents.md +32 -16
  121. package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
  122. package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
  123. package/dist/extensions/pi-subagents/docs/missions.md +2 -0
  124. package/dist/extensions/pi-subagents/docs/models.md +58 -1
  125. package/dist/extensions/pi-subagents/docs/observability.md +50 -12
  126. package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
  127. package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
  128. package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
  129. package/dist/extensions/pi-subagents/package-lock.json +747 -177
  130. package/dist/extensions/pi-subagents/package.json +6 -4
  131. package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
  132. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
  133. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  137. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
  138. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
  139. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  140. package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
  141. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
  142. package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  143. package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
  144. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
  145. package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
  146. package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
  147. package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
  148. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
  149. package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
  150. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
  151. package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
  152. package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
  153. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
  154. package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
  155. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
  156. package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
  157. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
  158. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
  159. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
  160. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
  161. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
  164. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
  165. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
  166. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
  167. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
  168. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
  169. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
  170. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
  171. package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
  172. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
  173. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
  174. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
  175. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
  176. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
  177. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
  178. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
  179. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
  180. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
  181. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
  182. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
  183. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
  184. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
  185. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
  186. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
  187. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
  188. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  189. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
  190. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
  192. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
  194. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
  195. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
  196. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
  197. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
  198. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
  199. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
  200. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
  201. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
  202. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
  203. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
  204. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
  205. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
  206. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
  207. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  208. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
  209. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
  210. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
  211. package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
  212. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
  213. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
  214. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
  215. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
  216. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
  217. package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
  218. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
  219. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
  220. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
  221. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
  222. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
  223. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
  224. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
  225. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
  226. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
  227. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
  228. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
  229. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
  230. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
  231. package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
  232. package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
  233. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
  234. package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
  235. package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
  236. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
  237. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
  238. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
  239. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
  240. package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
  241. package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
  242. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
  243. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
  244. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  245. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
  246. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
  247. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
  248. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
  249. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
  250. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
  251. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
  253. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
  254. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
  255. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
  256. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
  257. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
  258. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
  259. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
  260. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
  261. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
  262. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
  263. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
  264. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
  265. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
  266. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
  267. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
  268. package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
  269. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
  270. package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
  271. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
  272. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
  273. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
  274. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
  275. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
  276. package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
  277. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
  278. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
  279. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
  280. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
  281. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
  282. package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
  283. package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
  284. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
  285. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
  286. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
  287. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
  288. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
  289. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
  290. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
  291. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
  292. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
  293. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
  294. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
  295. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
  296. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
  297. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
  298. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
  299. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
  300. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
  301. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
  302. package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
  303. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
  304. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
  305. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
  306. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
  307. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
  308. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
  309. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
  310. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
  311. package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
  312. package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
  313. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
  314. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
  315. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
  316. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
  317. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
  318. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  319. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
  320. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
  321. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
  322. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
  323. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
  324. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
  325. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
  326. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
  327. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
  328. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
  329. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
  330. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
  331. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
  332. package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
  333. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
  334. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
  335. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
  336. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
  337. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
  338. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
  339. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
  340. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
  341. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
  342. package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
  343. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
  344. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
  345. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
  346. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
  347. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
  348. package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
  349. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
  350. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
  351. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
  352. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
  353. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
  354. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
  355. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
  356. package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
  357. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
  358. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
  359. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
  360. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
  361. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
  362. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
  363. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
  364. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
  365. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
  366. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
  367. package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
  368. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
  369. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
  370. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
  371. package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
  372. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
  373. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
  374. package/dist/extensions/pi-web-agent/src/types.ts +1 -1
  375. package/dist/extensions/pi-zentui/README.md +17 -0
  376. package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
  377. package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
  378. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
  379. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
  380. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
  381. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
  382. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
  383. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
  384. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
  385. package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
  386. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
  387. package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
  388. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
  389. package/dist/extensions/pi-zentui/package-lock.json +277 -517
  390. package/dist/extensions/pi-zentui/package.json +7 -4
  391. package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
  392. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
  393. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
  394. package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
  395. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
  396. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  397. package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
  398. package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
  399. package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
  400. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
  401. package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
  402. package/dist/index.d.ts +1 -1
  403. package/dist/main.js +4 -1
  404. package/dist/modes/interactive/chat-viewport.d.ts +19 -0
  405. package/dist/modes/interactive/chat-viewport.js +27 -0
  406. package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
  407. package/dist/modes/interactive/components/assistant-message.js +30 -13
  408. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  409. package/dist/modes/interactive/components/custom-editor.js +39 -1
  410. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  411. package/dist/modes/interactive/components/index.d.ts +1 -1
  412. package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
  413. package/dist/modes/interactive/components/markdown-transform.js +18 -0
  414. package/dist/modes/interactive/components/model-selector.js +19 -17
  415. package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
  416. package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
  417. package/dist/modes/interactive/components/settings-selector.js +128 -20
  418. package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
  419. package/dist/modes/interactive/components/settings-submenu.js +164 -0
  420. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  421. package/dist/modes/interactive/components/status-indicator.js +10 -3
  422. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  423. package/dist/modes/interactive/components/thinking-selector.js +75 -18
  424. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  425. package/dist/modes/interactive/components/tool-execution.js +35 -33
  426. package/dist/modes/interactive/components/trust-selector.js +2 -2
  427. package/dist/modes/interactive/interactive-mode.d.ts +13 -15
  428. package/dist/modes/interactive/interactive-mode.js +144 -80
  429. package/dist/modes/interactive/session-share.d.ts +15 -0
  430. package/dist/modes/interactive/session-share.js +183 -0
  431. package/dist/modes/interactive/theme/dark.json +2 -1
  432. package/dist/modes/interactive/theme/light.json +2 -1
  433. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  434. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  435. package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
  436. package/dist/modes/interactive/theme/theme-json.js +129 -0
  437. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  438. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  439. package/dist/modes/interactive/theme/theme.js +18 -119
  440. package/dist/modes/interactive/tui-renderer.d.ts +20 -0
  441. package/dist/modes/interactive/tui-renderer.js +65 -0
  442. package/dist/utils/exif-orientation.js +2 -3
  443. package/dist/utils/management-http.d.ts +24 -0
  444. package/dist/utils/management-http.js +53 -0
  445. package/dist/utils/text.d.ts +7 -0
  446. package/dist/utils/text.js +8 -0
  447. package/dist/utils/tools-manager.d.ts +1 -0
  448. package/dist/utils/tools-manager.js +43 -21
  449. package/docs/docs.json +0 -4
  450. package/package.json +4 -4
  451. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
  452. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
  453. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
  454. package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  455. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
  456. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
  457. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
  458. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
  459. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
  460. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
  461. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
  462. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
  463. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
  464. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
  465. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
  466. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
  467. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
  468. package/dist/skills/implanger/SKILL.md +0 -70
  469. package/dist/skills/output/SKILL.md +0 -49
  470. package/dist/skills/planger/SKILL.md +0 -167
  471. package/dist/skills/workflow/SKILL.md +0 -95
  472. package/docs/workflows.md +0 -18
  473. /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
@@ -6,7 +6,7 @@ import { afterEach, describe, it } from "node:test";
6
6
  import { buildWorkflowReceipt, readWorkflowReceipt, resolveWorkflowReceiptResume, workflowReceiptPath, writeWorkflowReceipt } from "../../src/workflows/workflow-receipt.ts";
7
7
  import { externalCliReceiptMetadata, resolveExternalCliRunnerStatus } from "../../src/runs/shared/external-cli-contract.ts";
8
8
  import type { WorkflowScriptChildResult } from "../../src/workflows/scripted-workflow.ts";
9
- import type { HostStepNodeV1 } from "../../src/shared/types.ts";
9
+ import type { HostStepNode } from "../../src/shared/types.ts";
10
10
  import { parseWorkflowChildSummary, workflowChildSummary } from "../../src/workflows/workflow-child-summary.ts";
11
11
 
12
12
  const roots: string[] = [];
@@ -39,7 +39,7 @@ function child(key: string, overrides: Partial<WorkflowScriptChildResult> = {}):
39
39
  };
40
40
  }
41
41
 
42
- function hostStep(overrides: Partial<HostStepNodeV1> = {}): HostStepNodeV1 {
42
+ function hostStep(overrides: Partial<HostStepNode> = {}): HostStepNode {
43
43
  return {
44
44
  version: 1,
45
45
  kind: "host-step",
@@ -458,4 +458,4 @@ describe("workflow child session names", () => {
458
458
  });
459
459
  assert.throws(() => readWorkflowReceipt(root, "workflow-1"), /sessionName is invalid/);
460
460
  });
461
- });
461
+ });
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
+ import { registerWorkflowResource, type WorkflowResourceDefinition } from "../../src/api/workflow-resources.ts";
3
4
  import {
4
5
  authorizeWorkflowResourceHost,
5
6
  consumeWorkflowResourcePermit,
@@ -8,6 +9,128 @@ import { resolveWorkflowResource } from "../../src/workflows/workflow-resources.
8
9
  import { runWorkflowScript, WorkflowScriptError } from "../../src/workflows/scripted-workflow.ts";
9
10
 
10
11
  describe("named workflow resources", () => {
12
+ it("scopes registrations by session and snapshots definitions, args and issued grants", () => {
13
+ const args = { nested: { task: "original" } };
14
+ const expansion = { script: "return 'original';", hostCommands: [{ key: "check", command: " node check.mjs " }, { key: "other", command: "node other.mjs" }] };
15
+ const definition: WorkflowResourceDefinition = {
16
+ name: "acme.check", version: 2,
17
+ resolve(input) {
18
+ (input.nested as { task: string }).task = "changed";
19
+ return expansion;
20
+ },
21
+ };
22
+ const registration = registerWorkflowResource({ sessionId: "one", definition });
23
+ const other = registerWorkflowResource({ sessionId: "two", definition: { ...definition, resolve: () => ({ script: "return 'two';" }) } });
24
+ try {
25
+ definition.version = 99;
26
+ definition.resolve = () => ({ error: "replacement must not run" });
27
+ assert.throws(() => registerWorkflowResource({ sessionId: "one", definition }), /already registered/);
28
+ assert.equal(resolveWorkflowResource("acme.check", args).ok, false);
29
+ assert.equal(resolveWorkflowResource("acme.check", args, "wrong").ok, false);
30
+ const resolved = resolveWorkflowResource("acme.check", args, "one");
31
+ assert.equal(resolved.ok, true);
32
+ if (!resolved.ok) return;
33
+ assert.equal(args.nested.task, "original");
34
+ assert.equal(resolved.resource.provenance.version, 2);
35
+ expansion.script = "return 'changed';";
36
+ expansion.hostCommands[0].command = "node changed.mjs";
37
+ registration.dispose();
38
+ assert.equal(resolveWorkflowResource("acme.check", args, "one").ok, false);
39
+ const replacement = registerWorkflowResource({ sessionId: "one", definition: { ...definition, resolve: () => ({ script: "return 'new';" }) } });
40
+ try {
41
+ registration.dispose();
42
+ assert.equal(resolveWorkflowResource("acme.check", {}, "one").ok, true);
43
+ assert.equal(resolveWorkflowResource("acme.check", {}, "two").ok, true);
44
+ const consumed = consumeWorkflowResourcePermit(resolved.resource.permit, resolved.resource.script);
45
+ assert.equal(typeof consumed, "object");
46
+ assert.equal(resolved.resource.script, "return 'original';");
47
+ assert.equal(authorizeWorkflowResourceHost(resolved.resource.permit, "check", "node check.mjs"), undefined);
48
+ assert.equal(authorizeWorkflowResourceHost(resolved.resource.permit, "other", "node other.mjs"), undefined);
49
+ assert.match(authorizeWorkflowResourceHost(resolved.resource.permit, "check", "node other.mjs")!, /not allowed/);
50
+ assert.match(authorizeWorkflowResourceHost(resolved.resource.permit, "other", "node check.mjs")!, /not allowed/);
51
+ assert.match(authorizeWorkflowResourceHost(resolved.resource.permit, "check", "node changed.mjs")!, /not allowed/);
52
+ assert.match(consumeWorkflowResourcePermit(resolved.resource.permit, resolved.resource.script) as string, /already consumed/);
53
+ } finally { replacement.dispose(); }
54
+ } finally { registration.dispose(); other.dispose(); }
55
+ });
56
+
57
+ it("lets trusted validation select fixed commands while task text remains data", async () => {
58
+ const registration = registerWorkflowResource({ sessionId: "binding", definition: {
59
+ name: "acme.review-check", version: 1,
60
+ resolve(args) {
61
+ if (Object.keys(args).some((key) => key !== "task" && key !== "check") || typeof args.task !== "string" || args.check !== "quick") return { error: "Only task and check=quick are supported." };
62
+ return { script: `return ${JSON.stringify(args.task)};`, hostCommands: [{ key: "check", command: "node check.mjs --quick" }] };
63
+ },
64
+ } });
65
+ try {
66
+ const task = `\"); await runs.host("injected", { command: "bad" }); //`;
67
+ const resolved = resolveWorkflowResource("acme.review-check", { task, check: "quick" }, "binding");
68
+ assert.equal(resolved.ok, true);
69
+ if (!resolved.ok) return;
70
+ const execution = await runWorkflowScript({
71
+ script: resolved.resource.script,
72
+ async launch() { throw new Error("No child expected"); },
73
+ async status() { throw new Error("No status expected"); },
74
+ });
75
+ assert.equal(execution.value, task);
76
+ for (const args of [{ task, check: "quick; bad" }, { task, check: "quick", flags: "--extra" }, { task, check: "quick", command: "bad" }, { task: new Date(), check: "quick" }, { task: "x".repeat(16385), check: "quick" }]) {
77
+ assert.equal(resolveWorkflowResource("acme.review-check", args, "binding").ok, false);
78
+ }
79
+ } finally { registration.dispose(); }
80
+ });
81
+
82
+ it("rejects invalid registrations and protects builtin names", () => {
83
+ const valid = { sessionId: "validation", definition: { name: "acme.check", version: 1, resolve: () => ({ script: "return true;" }) } };
84
+ for (const input of [
85
+ { ...valid, sessionId: " " },
86
+ { ...valid, trusted: true },
87
+ { ...valid, definition: { ...valid.definition, name: "run-ci" } },
88
+ { ...valid, definition: { ...valid.definition, name: "review" } },
89
+ { ...valid, definition: { ...valid.definition, name: "bad name" } },
90
+ { ...valid, definition: { ...valid.definition, version: 0 } },
91
+ { ...valid, definition: { ...valid.definition, resolve: undefined } },
92
+ { ...valid, definition: { ...valid.definition, issuerPackage: "trusted" } },
93
+ ]) assert.throws(() => registerWorkflowResource(input as typeof valid));
94
+ });
95
+
96
+ it("contains throws, async results and malformed expansions without issuing permits", async () => {
97
+ const invalid = [
98
+ () => { throw new Error("x".repeat(5000)); },
99
+ () => Promise.reject(new Error("async rejection")),
100
+ () => ({ then(resolve: (value: unknown) => void) { resolve({ script: "return true;" }); } }),
101
+ () => null,
102
+ () => ({ script: " " }),
103
+ () => ({ script: "return true;", authority: {} }),
104
+ () => ({ error: 42 }),
105
+ () => ({ script: "return true;", hostCommands: { keys: ["a"], commands: ["node a.mjs"] } }),
106
+ () => ({ script: "return true;", hostCommands: [{ key: "a", command: "node a.mjs" }, { key: "a", command: "node b.mjs" }] }),
107
+ () => ({ script: "return true;", hostCommands: [{ key: "../a", command: "node a.mjs" }] }),
108
+ () => ({ script: "return true;", hostCommands: [{ key: "a", command: "node\0a" }] }),
109
+ ];
110
+ for (const resolve of invalid) {
111
+ const registration = registerWorkflowResource({ sessionId: "invalid", definition: { name: "acme.invalid", version: 1, resolve: resolve as WorkflowResourceDefinition["resolve"] } });
112
+ try {
113
+ const result = resolveWorkflowResource("acme.invalid", {}, "invalid");
114
+ assert.equal(result.ok, false);
115
+ assert.equal("resource" in result, false);
116
+ if (!result.ok) assert.ok(result.error.length <= 4096);
117
+ } finally { registration.dispose(); }
118
+ }
119
+ await new Promise<void>((resolve) => setImmediate(resolve));
120
+ });
121
+
122
+ it("shares registrations across evaluated module copies", async () => {
123
+ const copy = await import(`../../src/workflows/workflow-resources.ts?copy=registration-test`);
124
+ const registration = copy.registerWorkflowResource({ sessionId: "copies", definition: { name: "acme.copy", version: 1, resolve: () => ({ script: "return true;" }) } });
125
+ try {
126
+ assert.notEqual(copy.resolveWorkflowResource, resolveWorkflowResource);
127
+ const resolved = resolveWorkflowResource("acme.copy", {}, "copies");
128
+ assert.equal(resolved.ok, true);
129
+ if (resolved.ok) assert.equal(typeof consumeWorkflowResourcePermit(resolved.resource.permit, resolved.resource.script), "object");
130
+ } finally { registration.dispose(); }
131
+ assert.equal(resolveWorkflowResource("acme.copy", {}, "copies").ok, false);
132
+ });
133
+
11
134
  it("resolves and executes an extension-owned named workflow script", async () => {
12
135
  const resolved = resolveWorkflowResource("run-ci", { command: "npm test", timeoutMs: 1_000 });
13
136
  assert.equal(resolved.ok, true);
@@ -85,6 +208,15 @@ describe("named workflow resources", () => {
85
208
  }
86
209
  });
87
210
 
211
+ it("rejects argument validation failures even when the error message is empty", () => {
212
+ const registration = registerWorkflowResource({ sessionId: "invalid-args", definition: {
213
+ name: "check-args", version: 1, resolve: () => assert.fail("invalid args reached the resolver"),
214
+ } });
215
+ try {
216
+ assert.deepEqual(resolveWorkflowResource("check-args", { get task() { throw new Error(""); } }, "invalid-args"), { ok: false, error: "" });
217
+ } finally { registration.dispose(); }
218
+ });
219
+
88
220
  it("reports unauthorized raw host execution through the workflow primitive when no host is supplied", async () => {
89
221
  await assert.rejects(
90
222
  runWorkflowScript({
@@ -95,4 +227,4 @@ describe("named workflow resources", () => {
95
227
  (error: unknown) => error instanceof WorkflowScriptError && /runs\.host is unavailable/.test(error.message),
96
228
  );
97
229
  });
98
- });
230
+ });
@@ -5,6 +5,7 @@ import * as path from "node:path";
5
5
  import { afterEach, describe, it } from "node:test";
6
6
  import { createSubagentExecutor } from "../../src/runs/foreground/subagent-executor.ts";
7
7
  import { resolveAsyncResumeTarget } from "../../src/runs/background/async-resume.ts";
8
+ import { parallelHandoffPath } from "../../src/runs/shared/parallel-handoff.ts";
8
9
  import { DIRS, type AsyncStatus, type SubagentState } from "../../src/shared/types.ts";
9
10
 
10
11
  const cleanupRoots: string[] = [];
@@ -65,6 +66,7 @@ function writeRetainedChild(input: {
65
66
  sessionId: string;
66
67
  state?: AsyncStatus["state"];
67
68
  stepStatus?: NonNullable<AsyncStatus["steps"]>[number]["status"];
69
+ mode?: "single" | "workflow";
68
70
  writeSession?: boolean;
69
71
  }): void {
70
72
  const childDir = path.join(DIRS.async, input.childRunId);
@@ -76,7 +78,7 @@ function writeRetainedChild(input: {
76
78
  fs.writeFileSync(path.join(childDir, "status.json"), JSON.stringify({
77
79
  runId: input.childRunId,
78
80
  sessionId: input.sessionId,
79
- mode: "single",
81
+ mode: input.mode ?? "single",
80
82
  state,
81
83
  startedAt: 100,
82
84
  endedAt: state === "complete" ? 200 : undefined,
@@ -86,6 +88,35 @@ function writeRetainedChild(input: {
86
88
  }), "utf-8");
87
89
  }
88
90
 
91
+ function writeManagedWorktreeHandoff(root: string, childRunId: string): void {
92
+ const childDir = path.join(DIRS.async, childRunId);
93
+ const worktreePath = path.join(root, `${childRunId}-worktree`);
94
+ fs.mkdirSync(worktreePath, { recursive: true });
95
+ fs.writeFileSync(parallelHandoffPath(childDir), JSON.stringify({
96
+ version: 1,
97
+ runId: childRunId,
98
+ mode: "single",
99
+ source: "async",
100
+ cwd: root,
101
+ createdAt: 100,
102
+ updatedAt: 200,
103
+ groups: [{
104
+ stepIndex: 0,
105
+ baseCommit: "a".repeat(40),
106
+ repoRoot: root,
107
+ children: [{
108
+ index: 0,
109
+ taskIndex: 0,
110
+ agent: "worker",
111
+ status: "completed",
112
+ summary: "done",
113
+ patch: { path: path.join(root, "patch.diff"), branch: "work", changed: false, diffStat: "", filesChanged: 0, insertions: 0, deletions: 0 },
114
+ }],
115
+ cleanup: { state: "partial", pruned: false, tasks: [{ index: 0, path: worktreePath, branch: "work", worktreeRemoved: false, branchRemoved: false }] },
116
+ }],
117
+ }), "utf-8");
118
+ }
119
+
89
120
  function writeWorkflowStatus(input: {
90
121
  parentRunId: string;
91
122
  childRunId: string;
@@ -175,6 +206,26 @@ describe("workflow keyed resume recovery hint", () => {
175
206
  cleanupRun(childRunId);
176
207
  });
177
208
 
209
+ it("rejects explicit baseRef when resuming a retained managed-worktree child", async () => {
210
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-managed-worktree-resume-base-ref-"));
211
+ cleanupRoots.push(root);
212
+ const { childRunId } = makeIds("managed-base-ref");
213
+ writeRetainedChild({ root, childRunId, sessionId: "session-1644", mode: "workflow" });
214
+ writeManagedWorktreeHandoff(root, childRunId);
215
+
216
+ const result = await createExecutor(createState("session-1644"), root).execute(
217
+ `managed-worktree-resume-${Date.now()}-${Math.random().toString(16).slice(2)}`,
218
+ { action: "resume", id: childRunId, message: "Continue", baseRef: "refs/heads/release", worktree: true },
219
+ new AbortController().signal,
220
+ undefined,
221
+ createContext(root),
222
+ );
223
+
224
+ assert.equal(result.isError, true);
225
+ assert.match(result.content.map((part) => part.text ?? "").join("\n"), /retained managed-worktree children continue in their existing worktree/);
226
+ cleanupRun(childRunId);
227
+ });
228
+
178
229
  it("does not hint when status proof or direct resumability is not safe", async () => {
179
230
  const cases: Array<{
180
231
  name: string;
@@ -362,4 +413,4 @@ describe("workflow keyed resume recovery hint", () => {
362
413
  cleanupRun(childRunId);
363
414
  }
364
415
  });
365
- });
416
+ });
@@ -30,6 +30,18 @@ function createRepo(prefix: string): string {
30
30
  return repo;
31
31
  }
32
32
 
33
+ function createHookScript(fileName: string, source: string): string {
34
+ const hooksDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-hook-script-"));
35
+ const hookPath = path.join(hooksDir, fileName);
36
+ fs.writeFileSync(hookPath, `#!/usr/bin/env node\n${source}\n`, "utf-8");
37
+ fs.chmodSync(hookPath, 0o755);
38
+ return hookPath;
39
+ }
40
+
41
+ const hookScriptSkip = process.platform === "win32"
42
+ ? "Hook script execution differs on Windows CI environments."
43
+ : undefined;
44
+
33
45
  function removeGeneratedWorktrees(repo: string, setup: WorktreeSetup | undefined): void {
34
46
  for (const worktree of setup?.worktrees ?? []) {
35
47
  try { execFileSync("git", ["-C", repo, "worktree", "remove", "--force", worktree.path], { stdio: "ignore" }); } catch {}
@@ -101,7 +113,7 @@ function writeManifest(input: {
101
113
  },
102
114
  }],
103
115
  }, null, 2), "utf-8");
104
- if (patch.changed) fs.writeFileSync(patch.path, "captured patch\n", "utf-8");
116
+ if (patch.changed) fs.writeFileSync(patch.path, execFileSync("git", ["-C", worktree.path, "diff", "--binary", baseCommit, "HEAD"], { encoding: "utf-8" }), "utf-8");
105
117
  }
106
118
 
107
119
  function entriesByPath(plan: WorktreeCleanupPlan): Map<string, WorktreeCleanupPlan["entries"][number]> {
@@ -136,12 +148,12 @@ describe("worktree cleanup plan", () => {
136
148
  assert.equal(fallbackCalls, 1);
137
149
  });
138
150
 
139
- it("builds and persists a deterministic metadata-backed plan without removing worktrees", () => {
151
+ it("builds and persists a deterministic metadata-backed plan without removing worktrees", async () => {
140
152
  const repo = createRepo("pi-cleanup-plan-safe-");
141
153
  const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-base-"));
142
154
  let setup: WorktreeSetup | undefined;
143
155
  try {
144
- setup = createWorktrees(repo, "safe", 1, { baseDir });
156
+ setup = await createWorktrees(repo, "safe", 1, { baseDir });
145
157
  const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
146
158
  writeManifest({ repo, manifestPath, setup });
147
159
  fs.mkdirSync(path.join(baseDir, "unrelated-directory"));
@@ -182,12 +194,12 @@ describe("worktree cleanup plan", () => {
182
194
  }
183
195
  });
184
196
 
185
- it("keeps dirty and unowned worktrees out of the removable set", () => {
197
+ it("keeps dirty and unowned worktrees out of the removable set", async () => {
186
198
  const repo = createRepo("pi-cleanup-plan-unknown-");
187
199
  const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-base-"));
188
200
  let setup: WorktreeSetup | undefined;
189
201
  try {
190
- setup = createWorktrees(repo, "unknown", 2, { baseDir });
202
+ setup = await createWorktrees(repo, "unknown", 2, { baseDir });
191
203
  const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
192
204
  writeManifest({ repo, manifestPath, setup });
193
205
  fs.writeFileSync(path.join(setup.worktrees[0]!.path, "tracked.txt"), "dirty\n", "utf-8");
@@ -211,12 +223,12 @@ describe("worktree cleanup plan", () => {
211
223
  }
212
224
  });
213
225
 
214
- it("keeps active async ownership and reports missing Git worktrees as stale", () => {
226
+ it("keeps active async ownership and reports missing Git worktrees as stale", async () => {
215
227
  const repo = createRepo("pi-cleanup-plan-active-");
216
228
  const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-base-"));
217
229
  let setup: WorktreeSetup | undefined;
218
230
  try {
219
- setup = createWorktrees(repo, "active", 1, { baseDir });
231
+ setup = await createWorktrees(repo, "active", 1, { baseDir });
220
232
  const asyncDir = path.join(repo, ".pi-subagents", "async", "active-run");
221
233
  const manifestPath = path.join(asyncDir, "handoff.json");
222
234
  writeManifest({ repo, manifestPath, setup, runId: "active-run", source: "async" });
@@ -240,12 +252,12 @@ describe("worktree cleanup plan", () => {
240
252
  }
241
253
  });
242
254
 
243
- it("requires foreground ownership proof instead of inferring activity from the artifacts directory", () => {
255
+ it("requires foreground ownership proof instead of inferring activity from the artifacts directory", async () => {
244
256
  const repo = createRepo("pi-cleanup-plan-foreground-");
245
257
  const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-base-"));
246
258
  let setup: WorktreeSetup | undefined;
247
259
  try {
248
- setup = createWorktrees(repo, "foreground", 1, { baseDir });
260
+ setup = await createWorktrees(repo, "foreground", 1, { baseDir });
249
261
  const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
250
262
  writeManifest({ repo, manifestPath, setup, runId: "foreground-run", source: "foreground" });
251
263
  const noProof = buildWorktreeCleanupPlan({ repo, handoffPath: manifestPath, worktreeBaseDir: baseDir, now: 35_000, planId: "foreground-no-proof" });
@@ -263,12 +275,12 @@ describe("worktree cleanup plan", () => {
263
275
  }
264
276
  });
265
277
 
266
- it("requires a recorded patch or local target ancestry for committed divergence", () => {
278
+ it("requires a recorded patch or local target ancestry for committed divergence", async () => {
267
279
  const repo = createRepo("pi-cleanup-plan-divergence-");
268
280
  const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-base-"));
269
281
  let setup: WorktreeSetup | undefined;
270
282
  try {
271
- setup = createWorktrees(repo, "divergence", 1, { baseDir });
283
+ setup = await createWorktrees(repo, "divergence", 1, { baseDir });
272
284
  const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
273
285
  writeManifest({ repo, manifestPath, setup });
274
286
  const worktree = setup.worktrees[0]!;
@@ -294,12 +306,39 @@ describe("worktree cleanup plan", () => {
294
306
  }
295
307
  });
296
308
 
297
- it("keeps stale markers, pending captures, and inconsistent cleanup metadata non-removable", () => {
309
+ it("does not let trusted external diff hide cleanup-plan divergence", { skip: hookScriptSkip }, async () => {
310
+ const repo = createRepo("pi-cleanup-plan-external-diff-");
311
+ const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-base-"));
312
+ const externalDiffPath = createHookScript("external-diff-plan.mjs", "process.exit(0);");
313
+ let setup: WorktreeSetup | undefined;
314
+ try {
315
+ setup = await createWorktrees(repo, "external-diff", 1, { baseDir });
316
+ const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
317
+ writeManifest({ repo, manifestPath, setup });
318
+ const worktree = setup.worktrees[0]!;
319
+ fs.writeFileSync(path.join(worktree.path, "unmerged.txt"), "unmerged\n", "utf-8");
320
+ git(worktree.path, ["add", "unmerged.txt"]);
321
+ git(worktree.path, ["commit", "-m", "unmerged"]);
322
+ git(repo, ["config", "diff.external", externalDiffPath]);
323
+ git(repo, ["config", "diff.trustExitCode", "true"]);
324
+
325
+ const plan = buildPlan({ repo, worktreeBaseDir: baseDir, now: 40_000, planId: "external-diff-plan" });
326
+ assert.equal(plan.entries[0]?.decision, "keep");
327
+ assert.equal(plan.entries[0]?.state, "ineligible");
328
+ assert.match(plan.entries[0]?.reasons.join(" ") ?? "", /neither preserved.*nor merged/i);
329
+ } finally {
330
+ removeGeneratedWorktrees(repo, setup);
331
+ fs.rmSync(repo, { recursive: true, force: true });
332
+ fs.rmSync(baseDir, { recursive: true, force: true });
333
+ }
334
+ });
335
+
336
+ it("keeps stale markers, pending captures, and inconsistent cleanup metadata non-removable", async () => {
298
337
  const repo = createRepo("pi-cleanup-plan-stale-");
299
338
  const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-base-"));
300
339
  let setup: WorktreeSetup | undefined;
301
340
  try {
302
- setup = createWorktrees(repo, "stale", 1, { baseDir });
341
+ setup = await createWorktrees(repo, "stale", 1, { baseDir });
303
342
  const asyncDir = path.join(repo, ".pi-subagents", "async", "stale-run");
304
343
  const manifestPath = path.join(asyncDir, "handoff.json");
305
344
  writeManifest({ repo, manifestPath, setup, runId: "stale-run", source: "async" });
@@ -346,4 +385,173 @@ describe("worktree cleanup plan", () => {
346
385
  fs.rmSync(baseDir, { recursive: true, force: true });
347
386
  }
348
387
  });
349
- });
388
+
389
+ it("treats nested project directory as cleanup containment root", async () => {
390
+ const repo = createRepo("pi-cleanup-plan-nested-root-");
391
+ const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-nested-base-"));
392
+ let setup: WorktreeSetup | undefined;
393
+ try {
394
+ setup = await createWorktrees(repo, "nested-root", 1, { baseDir });
395
+ const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
396
+ writeManifest({ repo, manifestPath, setup });
397
+ const plan = buildPlan({ repo, worktreeBaseDir: baseDir, now: 60_000, planId: "nested-root-plan" });
398
+ assert.equal(plan.baseDirs[0], path.join(baseDir, path.basename(repo)));
399
+ assert.equal(plan.entries[0]?.decision, "remove");
400
+ assert.equal(plan.entries[0]?.state, "safe");
401
+ } finally {
402
+ removeGeneratedWorktrees(repo, setup);
403
+ fs.rmSync(repo, { recursive: true, force: true });
404
+ fs.rmSync(baseDir, { recursive: true, force: true });
405
+ }
406
+ });
407
+
408
+ it("uses git toplevel parent as default cleanup root when input.repo is a subdirectory", async () => {
409
+ const repo = createRepo("pi-cleanup-plan-subdir-root-");
410
+ const previous = process.env.SELESAI_SUBAGENTS_WORKTREE_DIR;
411
+ delete process.env.SELESAI_SUBAGENTS_WORKTREE_DIR;
412
+ let setup: WorktreeSetup | undefined;
413
+ try {
414
+ fs.mkdirSync(path.join(repo, "packages", "app"), { recursive: true });
415
+ setup = await createWorktrees(repo, "from-subdir", 1, { provider: "native" });
416
+ const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
417
+ writeManifest({ repo, manifestPath, setup });
418
+ const plan = buildPlan({ repo: path.join(repo, "packages", "app"), now: 61_500, planId: "subdir-root-plan" });
419
+ const realRepo = __testables.realpathExisting(repo);
420
+ assert.equal(plan.baseDirs[0], path.join(path.dirname(realRepo), "worktrees", path.basename(realRepo)));
421
+ const entry = entriesByPath(plan).get(__testables.realpathExisting(setup.worktrees[0]!.path)) ?? plan.entries[0];
422
+ assert.equal(entry?.decision, "remove");
423
+ assert.equal(entry?.state, "safe");
424
+ } finally {
425
+ if (previous === undefined) delete process.env.SELESAI_SUBAGENTS_WORKTREE_DIR;
426
+ else process.env.SELESAI_SUBAGENTS_WORKTREE_DIR = previous;
427
+ removeGeneratedWorktrees(repo, setup);
428
+ fs.rmSync(repo, { recursive: true, force: true });
429
+ }
430
+ });
431
+
432
+ it("marks flat dedicatedRoot leaves ineligible", () => {
433
+ const repo = createRepo("pi-cleanup-plan-flat-leaf-");
434
+ const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-flat-base-"));
435
+ const worktreePath = path.join(baseDir, "pi-worktree-flat-0");
436
+ const branch = "pi-parallel-flat-0";
437
+ let setup: WorktreeSetup | undefined;
438
+ try {
439
+ git(repo, ["worktree", "add", "-b", branch, worktreePath]);
440
+ setup = {
441
+ cwd: repo,
442
+ worktrees: [{
443
+ path: worktreePath,
444
+ agentCwd: worktreePath,
445
+ branch,
446
+ index: 0,
447
+ nodeModulesLinked: false,
448
+ syntheticPaths: [],
449
+ }],
450
+ baseCommit: git(repo, ["rev-parse", "HEAD"]),
451
+ };
452
+ const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
453
+ writeManifest({ repo, manifestPath, setup, preserved: true });
454
+ const plan = buildPlan({ repo, worktreeBaseDir: baseDir, now: 62_000, planId: "flat-leaf-plan" });
455
+ const entry = entriesByPath(plan).get(__testables.realpathExisting(worktreePath)) ?? plan.entries[0];
456
+ assert.equal(entry?.decision, "keep");
457
+ assert.equal(entry?.state, "ineligible");
458
+ assert.match(entry?.reasons.join(" ") ?? "", /outside configured base directory|outside the project worktree directory/i);
459
+ } finally {
460
+ removeGeneratedWorktrees(repo, setup);
461
+ fs.rmSync(repo, { recursive: true, force: true });
462
+ fs.rmSync(baseDir, { recursive: true, force: true });
463
+ }
464
+ });
465
+
466
+ it("marks checkout-internal worktrees ineligible when base is the repos parent", () => {
467
+ const repo = createRepo("pi-cleanup-plan-checkout-internal-");
468
+ const worktreePath = path.join(repo, "pi-worktree-internal-0");
469
+ const branch = "pi-parallel-internal-0";
470
+ let setup: WorktreeSetup | undefined;
471
+ try {
472
+ git(repo, ["worktree", "add", "-b", branch, worktreePath]);
473
+ setup = {
474
+ cwd: repo,
475
+ worktrees: [{
476
+ path: worktreePath,
477
+ agentCwd: worktreePath,
478
+ branch,
479
+ index: 0,
480
+ nodeModulesLinked: false,
481
+ syntheticPaths: [],
482
+ }],
483
+ baseCommit: git(repo, ["rev-parse", "HEAD"]),
484
+ };
485
+ const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
486
+ writeManifest({ repo, manifestPath, setup, preserved: true });
487
+ const plan = buildPlan({ repo, worktreeBaseDir: path.dirname(repo), now: 63_000, planId: "checkout-internal-plan" });
488
+ const entry = entriesByPath(plan).get(__testables.realpathExisting(worktreePath)) ?? plan.entries[0];
489
+ assert.equal(entry?.decision, "keep");
490
+ assert.equal(entry?.state, "ineligible");
491
+ assert.notEqual(entry?.decision, "remove");
492
+ } finally {
493
+ removeGeneratedWorktrees(repo, setup);
494
+ fs.rmSync(repo, { recursive: true, force: true });
495
+ }
496
+ });
497
+
498
+ it("keeps a worktree whose project directory resolves into Pi extensions", () => {
499
+ const repo = createRepo("pi-cleanup-plan-extension-project-");
500
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "pi-cleanup-plan-home-"));
501
+ const agentDir = path.join(tempHome, ".selesai", "agent");
502
+ const extensionsDir = path.join(agentDir, "extensions");
503
+ const baseDir = path.join(tempHome, "worktree-root");
504
+ const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
505
+ const previousHome = process.env.HOME;
506
+ const previousUserProfile = process.env.USERPROFILE;
507
+ const projectDir = path.join(baseDir, path.basename(repo));
508
+ const worktreePath = path.join(projectDir, "pi-worktree-extension-project-0");
509
+ const branch = "pi-parallel-extension-project-0";
510
+ let setup: WorktreeSetup | undefined;
511
+ try {
512
+ fs.mkdirSync(extensionsDir, { recursive: true });
513
+ fs.mkdirSync(baseDir, { recursive: true });
514
+ fs.symlinkSync(extensionsDir, projectDir, process.platform === "win32" ? "junction" : "dir");
515
+ process.env.SELESAI_CODING_AGENT_DIR = agentDir;
516
+ process.env.HOME = tempHome;
517
+ process.env.USERPROFILE = tempHome;
518
+
519
+ git(repo, ["worktree", "add", "-b", branch, worktreePath]);
520
+ setup = {
521
+ cwd: repo,
522
+ worktrees: [{ path: worktreePath, agentCwd: worktreePath, branch, index: 0, nodeModulesLinked: false, syntheticPaths: [] }],
523
+ baseCommit: git(repo, ["rev-parse", "HEAD"]),
524
+ };
525
+ const manifestPath = path.join(repo, ".pi-subagents", "artifacts", "handoff.json");
526
+ writeManifest({ repo, manifestPath, setup, preserved: true });
527
+ const plan = buildPlan({ repo, worktreeBaseDir: baseDir, now: 64_000, planId: "extension-project-plan" });
528
+ const entry = entriesByPath(plan).get(__testables.realpathExisting(worktreePath)) ?? plan.entries[0];
529
+ assert.equal(entry?.decision, "keep");
530
+ assert.equal(entry?.state, "ineligible");
531
+ assert.match(entry?.reasons.join(" ") ?? "", /Pi extensions directory/i);
532
+ } finally {
533
+ if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
534
+ else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
535
+ if (previousHome === undefined) delete process.env.HOME;
536
+ else process.env.HOME = previousHome;
537
+ if (previousUserProfile === undefined) delete process.env.USERPROFILE;
538
+ else process.env.USERPROFILE = previousUserProfile;
539
+ removeGeneratedWorktrees(repo, setup);
540
+ fs.rmSync(repo, { recursive: true, force: true });
541
+ fs.rmSync(baseDir, { recursive: true, force: true });
542
+ fs.rmSync(tempHome, { recursive: true, force: true });
543
+ }
544
+ });
545
+
546
+ it("rejects empty cleanup worktree base directory", () => {
547
+ const repo = createRepo("pi-cleanup-plan-empty-base-");
548
+ try {
549
+ assert.throws(
550
+ () => buildWorktreeCleanupPlan({ repo, worktreeBaseDir: " " }),
551
+ /worktree base directory cannot be empty/,
552
+ );
553
+ } finally {
554
+ fs.rmSync(repo, { recursive: true, force: true });
555
+ }
556
+ });
557
+ });