@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
@@ -1,2347 +0,0 @@
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 { afterEach, describe, it } from "node:test";
6
- import { discoverAgents } from "../../src/agents/agents.ts";
7
- import { computeMcpServerHash } from "../../src/runs/shared/mcp-direct-tool-allowlist.ts";
8
- import {
9
- MCP_RUNTIME_SNAPSHOT_EVENT,
10
- MCP_RUNTIME_SNAPSHOT_VERSION,
11
- type McpRuntimeSnapshotHost,
12
- } from "../../src/runs/shared/mcp-direct-tool-allowlist.ts";
13
- import {
14
- TOOL_BUDGET_ENV,
15
- TOOL_BUDGET_ZERO_AUTH_ENV,
16
- } from "../../src/runs/shared/tool-budget.ts";
17
- import { WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV, WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/wait-config.ts";
18
- import { SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV } from "../../src/shared/utils.ts";
19
- import { STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV, STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV } from "../../src/runs/shared/structured-output.ts";
20
-
21
- import {
22
- CHILD_TOOL_DIAGNOSTIC_PATH_ENV,
23
- MCP_DIRECT_CHILD_TOOLS_ENV,
24
- REQUIRED_CHILD_TOOLS_ENV,
25
- } from "../../src/runs/shared/tool-availability.ts";
26
- import { CHILD_WATCHDOG_CONFIG_ENV } from "../../src/watchdog/child-status.ts";
27
- import {
28
- PERMISSION_AUDIT_PATH_ENV,
29
- PERMISSION_POLICY_ENV,
30
- } from "../../src/runs/shared/permissions.ts";
31
- import {
32
- SUBAGENT_FANOUT_CHILD_ENV,
33
- SUBAGENT_PARENT_CHILD_INDEX_ENV,
34
- SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV,
35
- SUBAGENT_PARENT_CONTROL_INBOX_ENV,
36
- SUBAGENT_PARENT_DEPTH_ENV,
37
- SUBAGENT_PARENT_EVENT_SINK_ENV,
38
- SUBAGENT_PARENT_PATH_ENV,
39
- SUBAGENT_PARENT_ROOT_RUN_ID_ENV,
40
- SUBAGENT_PARENT_RUN_ID_ENV,
41
- SUBAGENT_PARENT_SESSION_ENV,
42
- SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV,
43
- SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV,
44
- SUBAGENT_RUN_ID_ENV,
45
- PI_INTERCOM_STABLE_ID_ENV,
46
- PI_INTERCOM_SESSION_ID_ENV,
47
- applyThinkingSuffix,
48
- buildPiArgs,
49
- projectLaunchResolvedChildExtensions,
50
- resolvePiLaunchToolPlan,
51
- shouldDeliverTaskViaFile,
52
- } from "../../src/runs/shared/pi-args.ts";
53
-
54
- const originalEnv = {
55
- HOME: process.env.HOME,
56
- USERPROFILE: process.env.USERPROFILE,
57
- SELESAI_CODING_AGENT_DIR: process.env.SELESAI_CODING_AGENT_DIR,
58
- SELESAI_SUBAGENT_FANOUT_CHILD: process.env.SELESAI_SUBAGENT_FANOUT_CHILD,
59
- SELESAI_SUBAGENT_PARENT_EVENT_SINK: process.env.SELESAI_SUBAGENT_PARENT_EVENT_SINK,
60
- SELESAI_SUBAGENT_PARENT_CONTROL_INBOX:
61
- process.env.SELESAI_SUBAGENT_PARENT_CONTROL_INBOX,
62
- SELESAI_SUBAGENT_PARENT_ROOT_RUN_ID: process.env.SELESAI_SUBAGENT_PARENT_ROOT_RUN_ID,
63
- SELESAI_SUBAGENT_PARENT_RUN_ID: process.env.SELESAI_SUBAGENT_PARENT_RUN_ID,
64
- SELESAI_SUBAGENT_PARENT_CHILD_INDEX: process.env.SELESAI_SUBAGENT_PARENT_CHILD_INDEX,
65
- SELESAI_SUBAGENT_PARENT_DEPTH: process.env.SELESAI_SUBAGENT_PARENT_DEPTH,
66
- SELESAI_SUBAGENT_PARENT_PATH: process.env.SELESAI_SUBAGENT_PARENT_PATH,
67
- SELESAI_SUBAGENT_PARENT_CAPABILITY_TOKEN:
68
- process.env.SELESAI_SUBAGENT_PARENT_CAPABILITY_TOKEN,
69
- SELESAI_SUBAGENT_PARENT_SESSION: process.env.SELESAI_SUBAGENT_PARENT_SESSION,
70
- SELESAI_SUBAGENT_RUN_ID: process.env.SELESAI_SUBAGENT_RUN_ID,
71
- [MCP_DIRECT_CHILD_TOOLS_ENV]: process.env[MCP_DIRECT_CHILD_TOOLS_ENV],
72
- [TOOL_BUDGET_ZERO_AUTH_ENV]: process.env[TOOL_BUDGET_ZERO_AUTH_ENV],
73
- [SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]:
74
- process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV],
75
- [PI_INTERCOM_STABLE_ID_ENV]: process.env[PI_INTERCOM_STABLE_ID_ENV],
76
- [PI_INTERCOM_SESSION_ID_ENV]: process.env[PI_INTERCOM_SESSION_ID_ENV],
77
- MCP_HASH_ROOT: process.env.MCP_HASH_ROOT,
78
- MCP_HASH_TOKEN: process.env.MCP_HASH_TOKEN,
79
- SELESAI_SUBAGENT_TASK_DELIVERY: process.env.SELESAI_SUBAGENT_TASK_DELIVERY,
80
- };
81
- const originalCwd = process.cwd();
82
- const tempRoots: string[] = [];
83
-
84
- interface McpFixture {
85
- root: string;
86
- agentDir: string;
87
- projectDir: string;
88
- }
89
-
90
- function createMcpFixture(): McpFixture {
91
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-args-mcp-"));
92
- tempRoots.push(root);
93
- const home = path.join(root, "home");
94
- const agentDir = path.join(home, ".selesai", "agent");
95
- const projectDir = path.join(root, "project");
96
- fs.mkdirSync(agentDir, { recursive: true });
97
- fs.mkdirSync(projectDir, { recursive: true });
98
- process.env.HOME = home;
99
- process.env.USERPROFILE = home;
100
- process.env.SELESAI_CODING_AGENT_DIR = agentDir;
101
- process.chdir(projectDir);
102
- return { root, agentDir, projectDir };
103
- }
104
-
105
- function writeJson(filePath: string, value: unknown): void {
106
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
107
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
108
- }
109
-
110
- function writeMcpFixture(
111
- fixture: McpFixture,
112
- options: {
113
- serverName?: string;
114
- definition?: Record<string, unknown>;
115
- settings?: Record<string, unknown>;
116
- tools?: Array<{ name: string; description?: string }>;
117
- resources?: Array<{ name: string; uri: string; description?: string }>;
118
- configPath?: string;
119
- cachedAt?: number;
120
- configHash?: string;
121
- } = {},
122
- ): void {
123
- const serverName = options.serverName ?? "chrome-devtools";
124
- const definition = {
125
- command: "npx",
126
- args: ["chrome-devtools-mcp"],
127
- ...(options.definition ?? {}),
128
- };
129
- writeJson(options.configPath ?? path.join(fixture.agentDir, "mcp.json"), {
130
- ...(options.settings ? { settings: options.settings } : {}),
131
- mcpServers: {
132
- [serverName]: definition,
133
- },
134
- });
135
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
136
- version: 1,
137
- servers: {
138
- [serverName]: {
139
- configHash: options.configHash ?? computeMcpServerHash(definition),
140
- cachedAt: options.cachedAt ?? Date.now(),
141
- tools: options.tools ?? [
142
- { name: "take_screenshot" },
143
- { name: "click" },
144
- ],
145
- resources: options.resources ?? [],
146
- },
147
- },
148
- });
149
- }
150
-
151
- type RuntimeMcpDefinition = { command: string; args: string[] };
152
-
153
- function buildLegacyAliasLaunch(params: {
154
- definitions: Record<string, RuntimeMcpDefinition>;
155
- mcpDirectTools: string[];
156
- allowedTools: string[];
157
- }) {
158
- const fixture = createMcpFixture();
159
- writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} });
160
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
161
- version: 1,
162
- servers: Object.fromEntries(Object.entries(params.definitions).map(([name, definition]) => [
163
- name,
164
- { configHash: computeMcpServerHash(definition), cachedAt: Date.now(), tools: [{ name: "search" }] },
165
- ])),
166
- });
167
- const runtimeSnapshotHost: McpRuntimeSnapshotHost = {
168
- events: {
169
- emit(_event, request) {
170
- const definition = params.definitions[request.name];
171
- if (!definition) {
172
- request.result = { ok: false, error: new Error(`unknown runtime server ${request.name}`) };
173
- return;
174
- }
175
- request.result = { ok: true, snapshot: { name: request.name, definition, runtime: true, persisted: false } };
176
- },
177
- },
178
- };
179
- return buildPiArgs({
180
- baseArgs: ["-p"],
181
- task: "hello",
182
- sessionEnabled: false,
183
- inheritProjectContext: false,
184
- inheritSkills: false,
185
- tools: ["read"],
186
- mcpDirectTools: params.mcpDirectTools,
187
- capabilityCeiling: { version: 1, allowedTools: params.allowedTools, denyExtensions: false, sources: ["test"] },
188
- runtimeSnapshotHost,
189
- });
190
- }
191
-
192
- afterEach(() => {
193
- process.chdir(originalCwd);
194
- for (const [key, value] of Object.entries(originalEnv)) {
195
- if (value === undefined) {
196
- delete process.env[key];
197
- } else {
198
- process.env[key] = value;
199
- }
200
- }
201
- for (const root of tempRoots.splice(0)) {
202
- fs.rmSync(root, { recursive: true, force: true });
203
- }
204
- });
205
-
206
- describe("buildPiArgs session wiring", () => {
207
- it("keeps empty-extension warnings in the resolved plan without logging", () => {
208
- const originalWarn = console.warn;
209
- const warnCalls: string[] = [];
210
- console.warn = (...args: unknown[]) => {
211
- warnCalls.push(args.map(String).join(" "));
212
- };
213
- try {
214
- const plan = resolvePiLaunchToolPlan({
215
- extensions: [],
216
- agentName: "quota-reviewer",
217
- });
218
- assert.equal(plan.disableAmbientExtensions, true);
219
- assert.equal(plan.warnings.length, 1);
220
- assert.match(plan.warnings[0]!, /extensions: \[\] override for agent 'quota-reviewer'/);
221
- assert.match(plan.warnings[0]!, /disables ALL ambient extensions/);
222
- assert.deepEqual(warnCalls, []);
223
- } finally {
224
- console.warn = originalWarn;
225
- }
226
- });
227
-
228
- it("keeps empty-extension warnings quiet across repeated argument builds", () => {
229
- const originalWarn = console.warn;
230
- const warnCalls: string[] = [];
231
- console.warn = (...args: unknown[]) => {
232
- warnCalls.push(args.map(String).join(" "));
233
- };
234
- try {
235
- buildPiArgs({
236
- baseArgs: [],
237
- task: "Test warning output.",
238
- inheritProjectContext: true,
239
- inheritSkills: true,
240
- extensions: [],
241
- childAgentName: "quota-reviewer",
242
- });
243
- buildPiArgs({
244
- baseArgs: [],
245
- task: "Retry warning output.",
246
- inheritProjectContext: true,
247
- inheritSkills: true,
248
- extensions: [],
249
- childAgentName: "quota-reviewer",
250
- });
251
- assert.deepEqual(warnCalls, []);
252
- } finally {
253
- console.warn = originalWarn;
254
- }
255
- });
256
-
257
- it("does not warn when extensions are omitted (ambient extensions inherited normally)", () => {
258
- const plan = resolvePiLaunchToolPlan({});
259
- assert.equal(plan.disableAmbientExtensions, false);
260
- assert.deepEqual(plan.warnings, []);
261
- });
262
-
263
- it("does not warn when extensions is a non-empty explicit list", () => {
264
- const plan = resolvePiLaunchToolPlan({ extensions: ["./tools/provider-ext.ts"] });
265
- assert.equal(plan.disableAmbientExtensions, true);
266
- assert.deepEqual(plan.warnings, []);
267
- });
268
-
269
- it("projects launch-resolved extension identifiers without raw paths", () => {
270
- const privateExt = path.join(
271
- os.tmpdir(),
272
- "private-extension-root",
273
- "secret-extension.ts",
274
- );
275
- const toolExt = path.join(
276
- os.tmpdir(),
277
- "tool-extension-root",
278
- "tool-extension.ts",
279
- );
280
- const plan = resolvePiLaunchToolPlan({
281
- tools: ["read", toolExt],
282
- extensions: [privateExt],
283
- subagentOnlyExtensions: ["package-extension"],
284
- });
285
-
286
- const projection = projectLaunchResolvedChildExtensions(plan);
287
-
288
- assert.equal(projection.version, 1);
289
- assert.equal(projection.source, "launch-resolved");
290
- assert.equal(projection.disableAmbientExtensions, true);
291
- assert.ok(
292
- projection.runtime.length >= 1,
293
- `expected at least 1 runtime extension, got ${projection.runtime.length}`,
294
- );
295
- assert.equal(projection.configured.length, 3);
296
- assert.ok(
297
- projection.effective.length >= 4,
298
- `expected at least 4 effective extensions, got ${projection.effective.length}`,
299
- );
300
- for (const id of [
301
- ...projection.runtime,
302
- ...projection.configured,
303
- ...projection.effective,
304
- ]) {
305
- assert.match(id, /^sha256:[a-f0-9]{16}$/);
306
- }
307
- assert.ok(
308
- !JSON.stringify(projection).includes(os.tmpdir()),
309
- "projection should not expose raw extension paths",
310
- );
311
- });
312
-
313
- it("uses --session when sessionFile is provided", () => {
314
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-args-session-"));
315
- try {
316
- const sessionFile = path.join(tempDir, "nested", "session.jsonl");
317
- const { args } = buildPiArgs({
318
- baseArgs: ["-p"],
319
- task: "hello",
320
- sessionEnabled: true,
321
- sessionFile,
322
- sessionDir: "/tmp/should-not-be-used",
323
- inheritProjectContext: false,
324
- inheritSkills: false,
325
- });
326
-
327
- assert.ok(args.includes("--session"));
328
- assert.ok(args.includes(sessionFile));
329
- assert.ok(fs.existsSync(path.dirname(sessionFile)));
330
- assert.ok(
331
- !args.includes("--session-dir"),
332
- "--session-dir should not be emitted with --session",
333
- );
334
- assert.ok(
335
- !args.includes("--no-session"),
336
- "--no-session should not be emitted with --session",
337
- );
338
- } finally {
339
- fs.rmSync(tempDir, { recursive: true, force: true });
340
- }
341
- });
342
-
343
- it("keeps fresh mode behavior (sessionDir + no session file)", () => {
344
- const { args } = buildPiArgs({
345
- baseArgs: ["-p"],
346
- task: "hello",
347
- sessionEnabled: true,
348
- sessionDir: "/tmp/subagent-sessions",
349
- inheritProjectContext: false,
350
- inheritSkills: false,
351
- });
352
-
353
- assert.ok(args.includes("--session-dir"));
354
- assert.ok(args.includes("/tmp/subagent-sessions"));
355
- assert.ok(!args.includes("--session"));
356
- });
357
-
358
- it("emits explicit parent session env for permission forwarding", () => {
359
- process.env.SELESAI_SUBAGENT_PARENT_SESSION = "inherited-parent";
360
- const { env } = buildPiArgs({
361
- parentSessionId: "direct-parent",
362
- baseArgs: ["-p"],
363
- task: "hello",
364
- sessionEnabled: false,
365
- inheritProjectContext: false,
366
- inheritSkills: false,
367
- });
368
-
369
- assert.equal(env[SUBAGENT_PARENT_SESSION_ENV], "direct-parent");
370
- });
371
-
372
- it("passes the child display session name through as SELESAI_SUBAGENT_SESSION_NAME", () => {
373
- const { env } = buildPiArgs({
374
- baseArgs: ["-p"],
375
- task: "hello",
376
- sessionEnabled: false,
377
- inheritProjectContext: false,
378
- inheritSkills: false,
379
- sessionName: "worker: Fix the flaky test",
380
- });
381
-
382
- assert.equal(env.SELESAI_SUBAGENT_SESSION_NAME, "worker: Fix the flaky test");
383
- });
384
-
385
- it("omits SELESAI_SUBAGENT_SESSION_NAME when no session name is provided", () => {
386
- const { env } = buildPiArgs({
387
- baseArgs: ["-p"],
388
- task: "hello",
389
- sessionEnabled: false,
390
- inheritProjectContext: false,
391
- inheritSkills: false,
392
- });
393
-
394
- assert.equal(env.SELESAI_SUBAGENT_SESSION_NAME, undefined);
395
- });
396
-
397
- it("falls back to inherited parent session env for permission forwarding", () => {
398
- process.env.SELESAI_SUBAGENT_PARENT_SESSION = "inherited-parent";
399
- const { env } = buildPiArgs({
400
- baseArgs: ["-p"],
401
- task: "hello",
402
- sessionEnabled: false,
403
- inheritProjectContext: false,
404
- inheritSkills: false,
405
- });
406
-
407
- assert.equal(env[SUBAGENT_PARENT_SESSION_ENV], "inherited-parent");
408
- });
409
-
410
- it("passes the effective wait-tool setting explicitly to children", () => {
411
- assert.equal(
412
- buildPiArgs({
413
- baseArgs: [],
414
- task: "test",
415
- sessionEnabled: false,
416
- inheritProjectContext: true,
417
- inheritSkills: true,
418
- waitToolEnabled: false,
419
- }).env[WAIT_TOOL_ENABLED_ENV],
420
- "false",
421
- );
422
- assert.equal(
423
- buildPiArgs({
424
- baseArgs: [],
425
- task: "test",
426
- sessionEnabled: false,
427
- inheritProjectContext: true,
428
- inheritSkills: true,
429
- waitToolEnabled: true,
430
- }).env[WAIT_TOOL_ENABLED_ENV],
431
- "true",
432
- );
433
- assert.equal(
434
- buildPiArgs({
435
- baseArgs: [],
436
- task: "test",
437
- sessionEnabled: false,
438
- inheritProjectContext: true,
439
- inheritSkills: true,
440
- waitToolDefaultTimeoutMs: 12_000,
441
- }).env[WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV],
442
- "12000",
443
- );
444
- });
445
-
446
- it("passes child watchdog config only when explicitly provided", () => {
447
- const withoutWatchdog = buildPiArgs({
448
- baseArgs: ["-p"],
449
- task: "hello",
450
- sessionEnabled: false,
451
- inheritProjectContext: false,
452
- inheritSkills: false,
453
- });
454
- assert.equal(withoutWatchdog.env[CHILD_WATCHDOG_CONFIG_ENV], undefined);
455
-
456
- const withWatchdog = buildPiArgs({
457
- baseArgs: ["-p"],
458
- task: "hello",
459
- sessionEnabled: false,
460
- inheritProjectContext: false,
461
- inheritSkills: false,
462
- childWatchdog: {
463
- runId: "run-1",
464
- agent: "worker",
465
- childIndex: 2,
466
- watchdogTailTimeoutMs: 1234,
467
- agentEndTimeoutMs: 500,
468
- maxWarnings: 1,
469
- lsp: { enabled: false, timeoutMs: 50, maxFiles: 2, maxDiagnostics: 3 },
470
- stalemateRepeats: 2,
471
- cadence: { everyNTools: null },
472
- },
473
- });
474
- const encoded = withWatchdog.env[CHILD_WATCHDOG_CONFIG_ENV];
475
- assert.equal(typeof encoded, "string");
476
- assert.deepEqual(JSON.parse(encoded ?? "{}"), {
477
- runId: "run-1",
478
- agent: "worker",
479
- childIndex: 2,
480
- watchdogTailTimeoutMs: 1234,
481
- agentEndTimeoutMs: 500,
482
- maxWarnings: 1,
483
- lsp: { enabled: false, timeoutMs: 50, maxFiles: 2, maxDiagnostics: 3 },
484
- stalemateRepeats: 2,
485
- cadence: { everyNTools: null },
486
- });
487
- });
488
- });
489
-
490
- describe("excludeTools resolution", () => {
491
- it("filters excluded names from explicit allowlists and runtime-injected tools", () => {
492
- const plan = resolvePiLaunchToolPlan({
493
- tools: ["read", "write"],
494
- excludeTools: ["write", "structured_output"],
495
- structuredOutput: true,
496
- });
497
- assert.deepEqual(plan.declaredBuiltinTools, ["read", "write"]);
498
- assert.deepEqual(plan.excludeTools, ["write", "structured_output"]);
499
- assert.deepEqual(plan.effectiveToolAllowlist, ["read"]);
500
- assert.deepEqual(plan.internalTools, []);
501
- assert.deepEqual(plan.requiredChildTools, ["read"]);
502
-
503
- const built = buildPiArgs({
504
- baseArgs: [],
505
- task: "hello",
506
- sessionEnabled: true,
507
- inheritProjectContext: true,
508
- inheritSkills: true,
509
- tools: ["read", "write"],
510
- excludeTools: ["write"],
511
- });
512
- assert.deepEqual(built.args.slice(0, 2), ["--tools", "read"]);
513
- assert.equal(built.args.includes("--exclude-tools"), false);
514
- });
515
-
516
- it("forwards exclusions to Pi without inventing an ambient allowlist", () => {
517
- const built = buildPiArgs({
518
- baseArgs: [],
519
- task: "hello",
520
- sessionEnabled: true,
521
- inheritProjectContext: true,
522
- inheritSkills: true,
523
- excludeTools: ["write", "unknown_tool"],
524
- });
525
- assert.deepEqual(built.args.slice(0, 2), ["--exclude-tools", "write,unknown_tool"]);
526
- assert.equal(built.args.includes("--tools"), false);
527
- assert.equal(built.args.includes("--no-tools"), false);
528
- });
529
-
530
- it("keeps empty tool sets all-off and makes excluded nested fanout unavailable", () => {
531
- const empty = buildPiArgs({
532
- baseArgs: [],
533
- task: "hello",
534
- sessionEnabled: true,
535
- inheritProjectContext: true,
536
- inheritSkills: true,
537
- tools: [],
538
- excludeTools: ["write"],
539
- });
540
- assert.equal(empty.args.includes("--no-tools"), true);
541
- assert.equal(empty.args.includes("--exclude-tools"), false);
542
-
543
- const plan = resolvePiLaunchToolPlan({ allowNestedSubagents: true, excludeTools: ["subagent"] });
544
- assert.equal(plan.fanoutAuthorized, false);
545
- });
546
- });
547
-
548
- describe("buildPiArgs model wiring", () => {
549
- it("uses --model for provider-qualified model ids", () => {
550
- const { args } = buildPiArgs({
551
- baseArgs: ["-p"],
552
- task: "hello",
553
- sessionEnabled: false,
554
- model: "openai-codex/gpt-5.4-mini",
555
- inheritProjectContext: false,
556
- inheritSkills: false,
557
- });
558
-
559
- assert.ok(args.includes("--model"));
560
- assert.ok(args.includes("openai-codex/gpt-5.4-mini"));
561
- assert.ok(!args.includes("--models"));
562
- });
563
-
564
- it("uses --model for bare model ids too", () => {
565
- const { args } = buildPiArgs({
566
- baseArgs: ["-p"],
567
- task: "hello",
568
- sessionEnabled: false,
569
- model: "kimi-k2.5",
570
- inheritProjectContext: false,
571
- inheritSkills: false,
572
- });
573
-
574
- assert.ok(args.includes("--model"));
575
- assert.ok(args.includes("kimi-k2.5"));
576
- assert.ok(!args.includes("--models"));
577
- });
578
-
579
- it("preserves thinking suffixes on model args", () => {
580
- const { args } = buildPiArgs({
581
- baseArgs: ["-p"],
582
- task: "hello",
583
- sessionEnabled: false,
584
- model: "openai-codex/gpt-5.4-mini",
585
- thinking: "high",
586
- inheritProjectContext: false,
587
- inheritSkills: false,
588
- });
589
-
590
- assert.equal(
591
- applyThinkingSuffix("openai-codex/gpt-5.4-mini", "high"),
592
- "openai-codex/gpt-5.4-mini:high",
593
- );
594
- assert.ok(args.includes("--model"));
595
- assert.ok(args.includes("openai-codex/gpt-5.4-mini:high"));
596
- });
597
-
598
- it("passes max thinking through to the model argument", () => {
599
- const { args } = buildPiArgs({
600
- baseArgs: ["-p"],
601
- task: "hello",
602
- model: "openai/gpt-5",
603
- thinking: "max",
604
- sessionEnabled: false,
605
- inheritProjectContext: false,
606
- inheritSkills: false,
607
- });
608
-
609
- assert.equal(
610
- applyThinkingSuffix("openai/gpt-5", "max"),
611
- "openai/gpt-5:max",
612
- );
613
- assert.equal(
614
- applyThinkingSuffix("openai/gpt-5:max", "high"),
615
- "openai/gpt-5:max",
616
- );
617
- assert.equal(
618
- applyThinkingSuffix("openai/gpt-5:max", "high", true),
619
- "openai/gpt-5:high",
620
- );
621
- assert.ok(args.includes("--model"));
622
- assert.ok(args.includes("openai/gpt-5:max"));
623
- });
624
-
625
- it("passes explicit thinking off through to the model arg", () => {
626
- const { args } = buildPiArgs({
627
- baseArgs: ["-p"],
628
- task: "hello",
629
- sessionEnabled: false,
630
- model: "anthropic/claude-haiku-4-5",
631
- thinking: "off",
632
- inheritProjectContext: false,
633
- inheritSkills: false,
634
- });
635
-
636
- assert.equal(
637
- applyThinkingSuffix("anthropic/claude-haiku-4-5", "off"),
638
- "anthropic/claude-haiku-4-5:off",
639
- );
640
- assert.equal(
641
- applyThinkingSuffix("anthropic/claude-haiku-4-5:high", "off", true),
642
- "anthropic/claude-haiku-4-5:off",
643
- );
644
- assert.ok(args.includes("--model"));
645
- assert.ok(args.includes("anthropic/claude-haiku-4-5:off"));
646
- });
647
-
648
- it("does not append a thinking suffix for boolean false", () => {
649
- const model = "glm-5.2-short-fast";
650
- const once = applyThinkingSuffix(model, false);
651
- assert.equal(once, model);
652
- assert.equal(applyThinkingSuffix(once, false), model);
653
-
654
- const { args } = buildPiArgs({
655
- baseArgs: ["-p"],
656
- task: "hello",
657
- sessionEnabled: false,
658
- model,
659
- thinking: false,
660
- inheritProjectContext: false,
661
- inheritSkills: false,
662
- });
663
-
664
- assert.ok(args.includes("--model"));
665
- assert.ok(args.includes(model));
666
- assert.ok(!args.some((arg) => arg.includes(":false")));
667
- });
668
-
669
- it("leaves provider-specific model suffixes untouched when thinking is disabled", () => {
670
- const model = "openai-compatible/qwen2.5-coder:7b";
671
- const { args } = buildPiArgs({
672
- baseArgs: ["-p"],
673
- task: "hello",
674
- sessionEnabled: false,
675
- model,
676
- inheritProjectContext: false,
677
- inheritSkills: false,
678
- });
679
-
680
- assert.ok(args.includes("--model"));
681
- assert.ok(args.includes(model));
682
- assert.ok(!args.includes(`${model}:high`));
683
- });
684
- });
685
-
686
- describe("buildPiArgs task delivery", () => {
687
- const longTask = "x".repeat(8001);
688
-
689
- function taskFileFromArgs(args: string[]): string | undefined {
690
- const ref = args.find((arg) => arg.startsWith("@") && arg.endsWith("task.md"));
691
- return ref ? ref.slice(1) : undefined;
692
- }
693
-
694
- it("delivers short tasks through the platform default", () => {
695
- const { args } = buildPiArgs({
696
- baseArgs: ["-p"],
697
- task: "hello",
698
- sessionEnabled: false,
699
- inheritProjectContext: false,
700
- inheritSkills: false,
701
- });
702
-
703
- if (process.platform === "darwin") {
704
- assert.ok(taskFileFromArgs(args), "expected an @task.md argv reference on macOS");
705
- assert.ok(!args.includes("Task: hello"));
706
- } else {
707
- assert.ok(args.includes("Task: hello"));
708
- assert.equal(taskFileFromArgs(args), undefined);
709
- }
710
- });
711
-
712
- it("delivers tasks over the argv limit via a temp file by default", () => {
713
- const { args } = buildPiArgs({
714
- baseArgs: ["-p"],
715
- task: longTask,
716
- sessionEnabled: false,
717
- inheritProjectContext: false,
718
- inheritSkills: false,
719
- });
720
-
721
- const taskFile = taskFileFromArgs(args);
722
- assert.ok(taskFile, "expected an @task.md argv reference");
723
- assert.equal(fs.readFileSync(taskFile, "utf-8"), `Task: ${longTask}`);
724
- assert.ok(!args.includes(`Task: ${longTask}`));
725
- });
726
-
727
- it("delivers short tasks via file when SELESAI_SUBAGENT_TASK_DELIVERY=file", () => {
728
- process.env.SELESAI_SUBAGENT_TASK_DELIVERY = "file";
729
- const { args } = buildPiArgs({
730
- baseArgs: ["-p"],
731
- task: "hello",
732
- sessionEnabled: false,
733
- inheritProjectContext: false,
734
- inheritSkills: false,
735
- });
736
-
737
- const taskFile = taskFileFromArgs(args);
738
- assert.ok(taskFile, "expected an @task.md argv reference");
739
- assert.equal(fs.readFileSync(taskFile, "utf-8"), "Task: hello");
740
- assert.ok(!args.includes("Task: hello"));
741
- });
742
-
743
-
744
- it("falls back to auto when SELESAI_SUBAGENT_TASK_DELIVERY is invalid", () => {
745
- process.env.SELESAI_SUBAGENT_TASK_DELIVERY = "carrier-pigeon";
746
-
747
- const { args } = buildPiArgs({
748
- baseArgs: ["-p"],
749
- task: longTask,
750
- sessionEnabled: false,
751
- inheritProjectContext: false,
752
- inheritSkills: false,
753
- });
754
-
755
- assert.ok(taskFileFromArgs(args), "expected file delivery for over-limit task");
756
- });
757
-
758
- it("lets the per-launch taskDelivery override beat the env setting", () => {
759
- delete process.env.SELESAI_SUBAGENT_TASK_DELIVERY;
760
- const { args } = buildPiArgs({
761
- baseArgs: ["-p"],
762
- task: "hello",
763
- taskDelivery: "file",
764
- sessionEnabled: false,
765
- inheritProjectContext: false,
766
- inheritSkills: false,
767
- });
768
-
769
- assert.ok(taskFileFromArgs(args), "expected an @task.md argv reference");
770
- assert.ok(!args.includes("Task: hello"));
771
- });
772
-
773
- it("selects file delivery for macOS or over-limit tasks", () => {
774
- assert.equal(shouldDeliverTaskViaFile("short", "auto", "darwin"), true);
775
- assert.equal(shouldDeliverTaskViaFile("short", "auto", "linux"), false);
776
- assert.equal(shouldDeliverTaskViaFile("short", "auto", "win32"), false);
777
- assert.equal(shouldDeliverTaskViaFile("x".repeat(8001), "auto", "linux"), true);
778
- });
779
- });
780
-
781
- describe("buildPiArgs system prompt mode wiring", () => {
782
- it("uses --append-system-prompt by default", () => {
783
- const { args } = buildPiArgs({
784
- baseArgs: ["-p"],
785
- task: "hello",
786
- sessionEnabled: false,
787
- systemPrompt: "You are a worker",
788
- inheritProjectContext: false,
789
- inheritSkills: false,
790
- });
791
-
792
- assert.ok(args.includes("--append-system-prompt"));
793
- assert.ok(!args.includes("--system-prompt"));
794
- });
795
-
796
- it("uses --system-prompt when systemPromptMode=replace", () => {
797
- const { args } = buildPiArgs({
798
- baseArgs: ["-p"],
799
- task: "hello",
800
- sessionEnabled: false,
801
- systemPrompt: "You are a worker",
802
- systemPromptMode: "replace",
803
- inheritProjectContext: false,
804
- inheritSkills: false,
805
- });
806
-
807
- assert.ok(args.includes("--system-prompt"));
808
- assert.ok(!args.includes("--append-system-prompt"));
809
- });
810
-
811
- it("injects the subagent prompt runtime extension and env flags", () => {
812
- const { args, env } = buildPiArgs({
813
- baseArgs: ["-p"],
814
- task: "hello",
815
- sessionEnabled: false,
816
- inheritProjectContext: false,
817
- inheritGlobalContext: false,
818
- inheritSkills: true,
819
- });
820
-
821
- const extensionArgs = args.filter(
822
- (arg, index) => args[index - 1] === "--extension",
823
- );
824
- assert.ok(
825
- extensionArgs.some((arg) =>
826
- arg.endsWith(
827
- path.join("src", "runs", "shared", "subagent-prompt-runtime.ts"),
828
- ),
829
- ),
830
- );
831
- assert.ok(args.includes("--no-context-files"));
832
- assert.equal(env.SELESAI_SUBAGENT_CHILD, "1");
833
- assert.equal(env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT, "0");
834
- assert.equal(env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT, "0");
835
- assert.equal(env.SELESAI_SUBAGENT_INHERIT_SKILLS, "1");
836
- });
837
-
838
- it("propagates the global context inheritance flag through env", () => {
839
- const { env } = buildPiArgs({
840
- baseArgs: ["-p"],
841
- task: "hello",
842
- sessionEnabled: false,
843
- inheritProjectContext: true,
844
- inheritGlobalContext: true,
845
- inheritSkills: true,
846
- });
847
- assert.equal(env.SELESAI_SUBAGENT_INHERIT_PROJECT_CONTEXT, "1");
848
- assert.equal(env.SELESAI_SUBAGENT_INHERIT_GLOBAL_CONTEXT, "1");
849
- });
850
-
851
- it("keeps context file loading enabled when project context is inherited", () => {
852
- const { args } = buildPiArgs({
853
- baseArgs: ["-p"],
854
- task: "hello",
855
- sessionEnabled: false,
856
- inheritProjectContext: true,
857
- inheritSkills: true,
858
- });
859
-
860
- assert.equal(args.includes("--no-context-files"), false);
861
- });
862
-
863
- it("passes tool budget through env", () => {
864
- const { env } = buildPiArgs({
865
- baseArgs: ["-p"],
866
- task: "hello",
867
- sessionEnabled: false,
868
- inheritProjectContext: false,
869
- inheritSkills: false,
870
- toolBudget: { soft: 2, hard: 3, block: ["read"] },
871
- });
872
-
873
- assert.deepEqual(JSON.parse(env[TOOL_BUDGET_ENV] ?? "{}"), {
874
- soft: 2,
875
- hard: 3,
876
- block: ["read"],
877
- });
878
- assert.equal(env[TOOL_BUDGET_ZERO_AUTH_ENV], undefined);
879
- });
880
-
881
- it("clears inherited zero tool-budget authorization unless this launch owns it", () => {
882
- process.env[TOOL_BUDGET_ZERO_AUTH_ENV] = "1";
883
- const inherited = buildPiArgs({
884
- baseArgs: ["-p"],
885
- task: "hello",
886
- sessionEnabled: false,
887
- inheritProjectContext: false,
888
- inheritSkills: false,
889
- toolBudget: { hard: 1, block: ["read"] },
890
- });
891
- assert.equal(inherited.env[TOOL_BUDGET_ZERO_AUTH_ENV], undefined);
892
-
893
- const owned = buildPiArgs({
894
- baseArgs: ["-p"],
895
- task: "hello",
896
- sessionEnabled: false,
897
- inheritProjectContext: false,
898
- inheritSkills: false,
899
- toolBudget: { hard: 0, block: "*" },
900
- allowZeroToolBudget: true,
901
- });
902
- assert.equal(owned.env[TOOL_BUDGET_ZERO_AUTH_ENV], "1");
903
- });
904
-
905
- it("clears inherited MCP direct-tool metadata for non-MCP launches", () => {
906
- for (const staleValue of [JSON.stringify(["fixture_search"]), "not-json"]) {
907
- process.env[MCP_DIRECT_CHILD_TOOLS_ENV] = staleValue;
908
- const { env } = buildPiArgs({
909
- baseArgs: ["-p"],
910
- task: "hello",
911
- sessionEnabled: false,
912
- inheritProjectContext: false,
913
- inheritSkills: false,
914
- tools: ["read", "fixture_search"],
915
- });
916
-
917
- assert.equal(env[MCP_DIRECT_CHILD_TOOLS_ENV], undefined);
918
- }
919
- });
920
-
921
- it("passes child intercom and orchestrator metadata through env", () => {
922
- process.env[PI_INTERCOM_STABLE_ID_ENV] = "subagent-chat-parent";
923
- process.env[PI_INTERCOM_SESSION_ID_ENV] = "session-parent-runtime";
924
- const { env } = buildPiArgs({
925
- baseArgs: ["-p"],
926
- task: "hello",
927
- sessionEnabled: false,
928
- inheritProjectContext: true,
929
- inheritSkills: true,
930
- intercomSessionName: "subagent-worker-78f659a3",
931
- orchestratorIntercomTarget: "subagent-chat-parent",
932
- parentSessionId: "session-parent-123",
933
- runId: "78f659a3",
934
- childAgentName: "worker",
935
- childIndex: 2,
936
- });
937
-
938
- assert.equal(
939
- env.SELESAI_SUBAGENT_INTERCOM_SESSION_NAME,
940
- "subagent-worker-78f659a3",
941
- );
942
- assert.equal(env[PI_INTERCOM_STABLE_ID_ENV], "subagent-worker-78f659a3");
943
- assert.equal(env[PI_INTERCOM_SESSION_ID_ENV], undefined);
944
- assert.equal(env.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET, "subagent-chat-parent");
945
- assert.equal(
946
- env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV],
947
- "session-parent-123",
948
- );
949
- assert.equal(env.SELESAI_SUBAGENT_RUN_ID, "78f659a3");
950
- assert.equal(env.SELESAI_SUBAGENT_CHILD_AGENT, "worker");
951
- assert.equal(env.SELESAI_SUBAGENT_CHILD_INDEX, "2");
952
- assert.equal(typeof env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV], "string");
953
- assert.match(
954
- env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV] ?? "",
955
- /supervisor-channels/,
956
- );
957
- });
958
-
959
- it("clears inherited pi-intercom identity when no child intercom session name is set", () => {
960
- process.env[PI_INTERCOM_STABLE_ID_ENV] = "subagent-chat-parent";
961
- process.env[PI_INTERCOM_SESSION_ID_ENV] = "session-parent-runtime";
962
- const { env } = buildPiArgs({
963
- baseArgs: ["-p"],
964
- task: "hello",
965
- sessionEnabled: false,
966
- inheritProjectContext: true,
967
- inheritSkills: true,
968
- });
969
-
970
- assert.equal(env[PI_INTERCOM_STABLE_ID_ENV], undefined);
971
- assert.equal(env[PI_INTERCOM_SESSION_ID_ENV], undefined);
972
- });
973
-
974
- it("creates a private permission audit path without enabling the supervisor channel", () => {
975
- const { env, tempDir } = buildPiArgs({
976
- baseArgs: ["-p"],
977
- task: "hello",
978
- sessionEnabled: false,
979
- inheritProjectContext: true,
980
- inheritSkills: true,
981
- parentSessionId: "session-parent-123",
982
- runId: "permission-run",
983
- childAgentName: "worker",
984
- childIndex: 3,
985
- permissionRules: { write: "ask" },
986
- });
987
-
988
- assert.equal(env.SELESAI_SUBAGENT_ORCHESTRATOR_TARGET, undefined);
989
- assert.equal(env[PERMISSION_POLICY_ENV], JSON.stringify({ write: "ask" }));
990
- assert.equal(env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV], undefined);
991
- assert.equal(
992
- env[PERMISSION_AUDIT_PATH_ENV],
993
- path.join(tempDir!, "permission-audit.jsonl"),
994
- );
995
- });
996
-
997
- it("does not create a supervisor channel without an exact parent session id", () => {
998
- const { env } = buildPiArgs({
999
- baseArgs: ["-p"],
1000
- task: "hello",
1001
- sessionEnabled: false,
1002
- inheritProjectContext: true,
1003
- inheritSkills: true,
1004
- orchestratorIntercomTarget: "subagent-chat-parent",
1005
- runId: "78f659a3",
1006
- childAgentName: "worker",
1007
- childIndex: 2,
1008
- });
1009
-
1010
- assert.equal(env[SUBAGENT_ORCHESTRATOR_SESSION_ID_ENV], undefined);
1011
- assert.equal(env[SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV], undefined);
1012
- });
1013
-
1014
- it("emits explicit builtin tool allowlists", () => {
1015
- const { args, env, toolDiagnosticPath } = buildPiArgs({
1016
- baseArgs: ["-p"],
1017
- task: "hello",
1018
- sessionEnabled: false,
1019
- inheritProjectContext: false,
1020
- inheritSkills: false,
1021
- tools: [
1022
- "read",
1023
- "grep",
1024
- "find",
1025
- "ls",
1026
- "bash",
1027
- "edit",
1028
- "write",
1029
- "contact_supervisor",
1030
- ],
1031
- });
1032
-
1033
- const toolsArg = args[args.indexOf("--tools") + 1];
1034
- assert.equal(
1035
- toolsArg,
1036
- "read,grep,find,ls,bash,edit,write,contact_supervisor",
1037
- );
1038
- // Supervisor-coordination names are runtime-registered in children, so
1039
- // they are never strict requirements even when named explicitly (#1207).
1040
- assert.deepEqual(
1041
- JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"),
1042
- ["read", "grep", "find", "ls", "bash", "edit", "write"],
1043
- );
1044
- assert.equal(env[CHILD_TOOL_DIAGNOSTIC_PATH_ENV], toolDiagnosticPath);
1045
- });
1046
-
1047
- it("clears inherited tool diagnostics for nested zero-tool children", () => {
1048
- const inheritedDiagnosticPath = "/tmp/parent-tool-diagnostic.json";
1049
- const { env, toolDiagnosticPath } = buildPiArgs({
1050
- baseArgs: ["-p"],
1051
- task: "repair retained output",
1052
- sessionEnabled: false,
1053
- inheritProjectContext: false,
1054
- inheritSkills: false,
1055
- tools: [],
1056
- });
1057
- const spawnEnv = {
1058
- [REQUIRED_CHILD_TOOLS_ENV]: JSON.stringify(["read", "bash"]),
1059
- [CHILD_TOOL_DIAGNOSTIC_PATH_ENV]: inheritedDiagnosticPath,
1060
- ...env,
1061
- };
1062
-
1063
- assert.equal(toolDiagnosticPath, undefined);
1064
- assert.equal(spawnEnv[REQUIRED_CHILD_TOOLS_ENV], undefined);
1065
- assert.equal(spawnEnv[CHILD_TOOL_DIAGNOSTIC_PATH_ENV], undefined);
1066
- });
1067
-
1068
- it("strips the legacy supervisor pairing from requirements", () => {
1069
- const { args, env } = buildPiArgs({
1070
- baseArgs: ["-p"],
1071
- task: "hello",
1072
- sessionEnabled: false,
1073
- inheritProjectContext: false,
1074
- inheritSkills: false,
1075
- tools: ["read", "intercom", "contact_supervisor"],
1076
- });
1077
-
1078
- assert.equal(
1079
- args[args.indexOf("--tools") + 1],
1080
- "read,intercom,contact_supervisor",
1081
- );
1082
- assert.deepEqual(
1083
- JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"),
1084
- ["read"],
1085
- );
1086
- });
1087
-
1088
- it("keeps a lone explicit intercom tool strict", () => {
1089
- const { args, env } = buildPiArgs({
1090
- baseArgs: ["-p"],
1091
- task: "hello",
1092
- sessionEnabled: false,
1093
- inheritProjectContext: false,
1094
- inheritSkills: false,
1095
- tools: ["read", "intercom"],
1096
- });
1097
-
1098
- assert.equal(
1099
- args[args.indexOf("--tools") + 1],
1100
- "read,intercom",
1101
- );
1102
- assert.deepEqual(
1103
- JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"),
1104
- ["read", "intercom"],
1105
- );
1106
- });
1107
-
1108
- it("launches the bundled reviewer without mutation-capable tools", () => {
1109
- const reviewer = discoverAgents(process.cwd(), "project").agents.find((agent) => agent.name === "reviewer");
1110
- assert.ok(reviewer, "expected bundled reviewer");
1111
- const { args } = buildPiArgs({
1112
- baseArgs: ["-p"],
1113
- task: "Review this change.",
1114
- sessionEnabled: false,
1115
- inheritProjectContext: false,
1116
- inheritSkills: false,
1117
- tools: reviewer.tools,
1118
- });
1119
-
1120
- assert.equal(args[args.indexOf("--tools") + 1], "read,grep,find,ls");
1121
- assert.doesNotMatch(args[args.indexOf("--tools") + 1] ?? "", /\b(?:bash|edit|write)\b/);
1122
- });
1123
-
1124
- it("keeps structured_output available under explicit tool allowlists", () => {
1125
- const { args, env } = buildPiArgs({
1126
- baseArgs: ["-p"],
1127
- task: "hello",
1128
- sessionEnabled: false,
1129
- inheritProjectContext: false,
1130
- inheritSkills: false,
1131
- tools: ["read", "fixture_search"],
1132
- structuredOutput: {
1133
- schema: { type: "object", properties: {}, additionalProperties: false },
1134
- schemaPath: "/tmp/schema.json",
1135
- outputPath: "/tmp/output.json",
1136
- acceptanceReportPath: "/tmp/acceptance.json",
1137
- acceptanceReportRequired: true,
1138
- },
1139
- });
1140
-
1141
- assert.equal(
1142
- args[args.indexOf("--tools") + 1],
1143
- "read,fixture_search,structured_output",
1144
- );
1145
- assert.deepEqual(JSON.parse(env[REQUIRED_CHILD_TOOLS_ENV] ?? "[]"), [
1146
- "read",
1147
- "fixture_search",
1148
- "structured_output",
1149
- ]);
1150
- assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV], "/tmp/acceptance.json");
1151
- assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV], "1");
1152
- });
1153
-
1154
- it("clears inherited acceptance capture for launches without outputSchema", () => {
1155
- const { env } = buildPiArgs({
1156
- baseArgs: ["-p"], task: "hello", sessionEnabled: false,
1157
- inheritProjectContext: false, inheritSkills: false,
1158
- });
1159
-
1160
- assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_CAPTURE_ENV], undefined);
1161
- assert.equal(env[STRUCTURED_OUTPUT_ACCEPTANCE_REQUIRED_ENV], undefined);
1162
- });
1163
-
1164
- it("forwards the Pi package root to child processes for host peer resolution", () => {
1165
- process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV] = "/opt/pi-coding-agent";
1166
- const { env } = buildPiArgs({
1167
- baseArgs: ["-p"],
1168
- task: "hello",
1169
- sessionEnabled: false,
1170
- inheritProjectContext: false,
1171
- inheritSkills: false,
1172
- });
1173
-
1174
- assert.equal(env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV], "/opt/pi-coding-agent");
1175
- });
1176
-
1177
- it("adds read to explicit tool allowlists when skills must be loaded lazily", () => {
1178
- const { args } = buildPiArgs({
1179
- baseArgs: ["-p"],
1180
- task: "hello",
1181
- sessionEnabled: false,
1182
- inheritProjectContext: false,
1183
- inheritSkills: false,
1184
- requireReadTool: true,
1185
- tools: ["bash"],
1186
- });
1187
-
1188
- assert.equal(args[args.indexOf("--tools") + 1], "read,bash");
1189
- });
1190
-
1191
- it("does not duplicate read in explicit tool allowlists for lazy skills", () => {
1192
- const { args } = buildPiArgs({
1193
- baseArgs: ["-p"],
1194
- task: "hello",
1195
- sessionEnabled: false,
1196
- inheritProjectContext: false,
1197
- inheritSkills: false,
1198
- requireReadTool: true,
1199
- tools: ["read", "bash"],
1200
- });
1201
-
1202
- assert.equal(args[args.indexOf("--tools") + 1], "read,bash");
1203
- });
1204
-
1205
- it("includes adapter tool filters and protocol version in MCP cache identity", () => {
1206
- const base = { command: "npx", args: ["browser-mcp"] };
1207
-
1208
- assert.notEqual(
1209
- computeMcpServerHash(base),
1210
- computeMcpServerHash({ ...base, includeTools: ["browser_navigate"] }),
1211
- );
1212
- assert.notEqual(
1213
- computeMcpServerHash(base),
1214
- computeMcpServerHash({ ...base, protocolVersion: "2025-03-26" }),
1215
- );
1216
- });
1217
-
1218
- it("matches pi-mcp-adapter 2.26.0 metadata cache hashes", () => {
1219
- process.env.MCP_HASH_ROOT = "/tmp/mcp-root";
1220
- process.env.MCP_HASH_TOKEN = "token-value";
1221
-
1222
- assert.deepEqual(
1223
- [
1224
- computeMcpServerHash({
1225
- command: "npx",
1226
- args: ["-y", "browser-mcp"],
1227
- env: { ROOT: "{env:MCP_HASH_ROOT}", SECRET_COMMAND: "!op read test" },
1228
- cwd: "${MCP_HASH_ROOT}/server",
1229
- exposeResources: false,
1230
- includeTools: ["browser_navigate"],
1231
- excludeTools: ["browser_close"],
1232
- }),
1233
- computeMcpServerHash({
1234
- url: "https://example.test/$env:MCP_HASH_TOKEN",
1235
- headers: {
1236
- Authorization: "Bearer ${MCP_HASH_TOKEN}",
1237
- Secret: "!op read test",
1238
- },
1239
- requestHeadersCommand: {
1240
- command: "headers --token $env:MCP_HASH_TOKEN",
1241
- args: ["--root", "{env:MCP_HASH_ROOT}"],
1242
- env: { TOKEN: "${MCP_HASH_TOKEN}", SECRET_COMMAND: "!op read test" },
1243
- timeoutMs: 2500,
1244
- },
1245
- auth: "bearer",
1246
- bearerTokenEnv: "MCP_HASH_TOKEN",
1247
- }),
1248
- computeMcpServerHash({ socket: "{env:MCP_HASH_ROOT}/rmcp.sock" }),
1249
- ],
1250
- [
1251
- "2c6d629872df1d4243906b17c57ebf688d8be0426e471bc2b0c956d952823c63",
1252
- "a7d142f0300b3fc6cce3039823eab3d9da9635a20f8d0c5d1c414d6c2da83968",
1253
- "592c6a094c7ba78133bffa5498e268e70dac7b9c450f9c23d9a46585a54edb50",
1254
- ],
1255
- );
1256
- });
1257
-
1258
- it("augments explicit builtin allowlists with selected direct MCP tool names", () => {
1259
- const fixture = createMcpFixture();
1260
- writeMcpFixture(fixture);
1261
-
1262
- const { args, env } = buildPiArgs({
1263
- baseArgs: ["-p"],
1264
- task: "hello",
1265
- sessionEnabled: false,
1266
- inheritProjectContext: false,
1267
- inheritSkills: false,
1268
- tools: ["read", "bash"],
1269
- mcpDirectTools: ["chrome-devtools"],
1270
- });
1271
-
1272
- assert.equal(
1273
- args[args.indexOf("--tools") + 1],
1274
- "read,bash,chrome-devtools_take_screenshot,chrome-devtools_click",
1275
- );
1276
- assert.equal(env.MCP_DIRECT_TOOLS, "chrome-devtools");
1277
- assert.equal(
1278
- env[REQUIRED_CHILD_TOOLS_ENV],
1279
- JSON.stringify([
1280
- "read",
1281
- "bash",
1282
- "chrome-devtools_take_screenshot",
1283
- "chrome-devtools_click",
1284
- ]),
1285
- );
1286
- assert.equal(
1287
- env[MCP_DIRECT_CHILD_TOOLS_ENV],
1288
- JSON.stringify([
1289
- "chrome-devtools_take_screenshot",
1290
- "chrome-devtools_click",
1291
- ]),
1292
- );
1293
- });
1294
-
1295
- it("resolves direct MCP tool selections from adapter-style protocol version cache entries", () => {
1296
- const fixture = createMcpFixture();
1297
- writeMcpFixture(fixture, {
1298
- serverName: "github",
1299
- definition: { command: "github-mcp", protocolVersion: "2025-03-26" },
1300
- configHash: "e2be19d9c42c791c8c125397cc9a5c1b592effe15c422a7f7d5fbf2eb6397251",
1301
- tools: [{ name: "search_repositories" }],
1302
- });
1303
-
1304
- const { args } = buildPiArgs({
1305
- baseArgs: ["-p"],
1306
- task: "hello",
1307
- sessionEnabled: false,
1308
- inheritProjectContext: false,
1309
- inheritSkills: false,
1310
- tools: ["read"],
1311
- mcpDirectTools: ["github/search_repositories"],
1312
- });
1313
-
1314
- assert.equal(args[args.indexOf("--tools") + 1], "read,github_search_repositories");
1315
- });
1316
-
1317
- it("hands runtime MCP snapshots to child launches for server and server/tool selectors", () => {
1318
- const fixture = createMcpFixture();
1319
- const serverName = "runtime-github";
1320
- const definition = { command: "runtime-github", args: ["--stdio"] };
1321
- writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} });
1322
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1323
- version: 1,
1324
- servers: {
1325
- [serverName]: {
1326
- configHash: computeMcpServerHash(definition),
1327
- cachedAt: Date.now(),
1328
- tools: [{ name: "search_repositories" }, { name: "create_issue" }],
1329
- },
1330
- },
1331
- });
1332
- const requestedNames: string[] = [];
1333
- const runtimeSnapshotHost: McpRuntimeSnapshotHost = {
1334
- events: {
1335
- emit(event, request) {
1336
- assert.equal(event, MCP_RUNTIME_SNAPSHOT_EVENT);
1337
- assert.equal(request.version, MCP_RUNTIME_SNAPSHOT_VERSION);
1338
- requestedNames.push(request.name);
1339
- request.result = {
1340
- ok: true,
1341
- snapshot: {
1342
- name: request.name,
1343
- definition: structuredClone(definition),
1344
- runtime: true,
1345
- persisted: false,
1346
- },
1347
- };
1348
- },
1349
- },
1350
- };
1351
- const serverLaunch = buildPiArgs({
1352
- baseArgs: ["-p"],
1353
- task: "hello",
1354
- sessionEnabled: false,
1355
- inheritProjectContext: false,
1356
- inheritSkills: false,
1357
- tools: ["read"],
1358
- mcpDirectTools: [serverName],
1359
- systemPrompt: "system",
1360
- runtimeSnapshotHost,
1361
- });
1362
- const toolLaunch = buildPiArgs({
1363
- baseArgs: ["-p"],
1364
- task: "hello",
1365
- sessionEnabled: false,
1366
- inheritProjectContext: false,
1367
- inheritSkills: false,
1368
- tools: ["read"],
1369
- mcpDirectTools: [`${serverName}/search_repositories`],
1370
- runtimeSnapshotHost,
1371
- });
1372
- assert.deepEqual(requestedNames, [serverName, serverName]);
1373
- assert.equal(serverLaunch.args[serverLaunch.args.indexOf("--tools") + 1], "read,runtime-github_search_repositories,runtime-github_create_issue");
1374
- assert.equal(toolLaunch.args[toolLaunch.args.indexOf("--tools") + 1], "read,runtime-github_search_repositories");
1375
- const serverTaskArgIndex = serverLaunch.args.findIndex((arg) => arg === "Task: hello" || arg.endsWith("task.md"));
1376
- assert.ok(serverLaunch.args.indexOf("--mcp-config") < serverTaskArgIndex);
1377
- for (const launch of [serverLaunch, toolLaunch]) {
1378
- const configPath = launch.args[launch.args.indexOf("--mcp-config") + 1];
1379
- assert.ok(configPath);
1380
- assert.equal(path.dirname(configPath), launch.tempDir);
1381
- assert.deepEqual(JSON.parse(fs.readFileSync(configPath, "utf-8")), {
1382
- mcpServers: { [serverName]: definition },
1383
- });
1384
- }
1385
- assert.deepEqual(
1386
- [serverLaunch.env.MCP_DIRECT_TOOLS, toolLaunch.env.MCP_DIRECT_TOOLS],
1387
- [serverName, `${serverName}/search_repositories`],
1388
- );
1389
- assert.deepEqual(JSON.parse(serverLaunch.env[MCP_DIRECT_CHILD_TOOLS_ENV]!), [
1390
- "runtime-github_search_repositories",
1391
- "runtime-github_create_issue",
1392
- ]);
1393
- assert.deepEqual(JSON.parse(toolLaunch.env[MCP_DIRECT_CHILD_TOOLS_ENV]!), [
1394
- "runtime-github_search_repositories",
1395
- ]);
1396
- });
1397
-
1398
- it("fails closed when a selected runtime MCP server has no snapshot", () => {
1399
- const fixture = createMcpFixture();
1400
- const serverName = "runtime-missing";
1401
- const definition = { command: "runtime-missing", args: ["--stdio"] };
1402
- writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} });
1403
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1404
- version: 1,
1405
- servers: {
1406
- [serverName]: {
1407
- configHash: computeMcpServerHash(definition),
1408
- cachedAt: Date.now(),
1409
- tools: [{ name: "search" }],
1410
- },
1411
- },
1412
- });
1413
- const runtimeSnapshotHost: McpRuntimeSnapshotHost = {
1414
- events: {
1415
- emit(_event, request) {
1416
- request.result = { ok: false, error: new Error("runtime server is unavailable") };
1417
- },
1418
- },
1419
- };
1420
- assert.throws(() => buildPiArgs({
1421
- baseArgs: ["-p"],
1422
- task: "hello",
1423
- sessionEnabled: false,
1424
- inheritProjectContext: false,
1425
- inheritSkills: false,
1426
- tools: ["read"],
1427
- mcpDirectTools: [serverName],
1428
- runtimeSnapshotHost,
1429
- }), /Unresolved MCP direct-tool selectors: runtime-missing\./);
1430
- });
1431
-
1432
- it("fails closed on malformed selected runtime MCP server fields", () => {
1433
- const fixture = createMcpFixture();
1434
- const serverName = "runtime-filtered";
1435
- const definition = { command: "runtime-filtered", includeTools: "safe_only" } as unknown as Parameters<typeof computeMcpServerHash>[0];
1436
- writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} });
1437
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1438
- version: 1,
1439
- servers: {
1440
- [serverName]: {
1441
- configHash: computeMcpServerHash(definition),
1442
- cachedAt: Date.now(),
1443
- tools: [{ name: "safe_only" }, { name: "dangerous" }],
1444
- },
1445
- },
1446
- });
1447
- const runtimeSnapshotHost: McpRuntimeSnapshotHost = {
1448
- events: {
1449
- emit(_event, request) {
1450
- request.result = { ok: true, snapshot: { name: request.name, definition, runtime: true, persisted: false } };
1451
- },
1452
- },
1453
- };
1454
-
1455
- assert.throws(() => buildPiArgs({
1456
- baseArgs: ["-p"],
1457
- task: "hello",
1458
- sessionEnabled: false,
1459
- inheritProjectContext: false,
1460
- inheritSkills: false,
1461
- tools: ["read"],
1462
- mcpDirectTools: [serverName],
1463
- runtimeSnapshotHost,
1464
- }), /Unresolved MCP direct-tool selectors: runtime-filtered\./);
1465
- });
1466
-
1467
- it("does not serialize runtime MCP servers denied by a capability ceiling", () => {
1468
- const fixture = createMcpFixture();
1469
- const definitions = {
1470
- "runtime-a": { command: "runtime-a", args: ["--stdio"] },
1471
- "runtime-b": { command: "runtime-b", args: ["--stdio"] },
1472
- };
1473
- writeJson(path.join(fixture.agentDir, "mcp.json"), { mcpServers: {} });
1474
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1475
- version: 1,
1476
- servers: {
1477
- "runtime-a": { configHash: computeMcpServerHash(definitions["runtime-a"]), cachedAt: Date.now(), tools: [{ name: "search" }] },
1478
- "runtime-b": { configHash: computeMcpServerHash(definitions["runtime-b"]), cachedAt: Date.now(), tools: [{ name: "secret" }] },
1479
- },
1480
- });
1481
- const runtimeSnapshotHost: McpRuntimeSnapshotHost = {
1482
- events: {
1483
- emit(_event, request) {
1484
- const definition = definitions[request.name as keyof typeof definitions];
1485
- if (!definition) {
1486
- request.result = { ok: false, error: new Error(`unknown runtime server ${request.name}`) };
1487
- return;
1488
- }
1489
- request.result = { ok: true, snapshot: { name: request.name, definition, runtime: true, persisted: false } };
1490
- },
1491
- },
1492
- };
1493
- const launch = buildPiArgs({
1494
- baseArgs: ["-p"],
1495
- task: "hello",
1496
- sessionEnabled: false,
1497
- inheritProjectContext: false,
1498
- inheritSkills: false,
1499
- tools: ["read"],
1500
- mcpDirectTools: ["runtime-a", "runtime-b"],
1501
- capabilityCeiling: { version: 1, allowedTools: ["read", "runtime_a_search"], denyExtensions: false, sources: ["test"] },
1502
- runtimeSnapshotHost,
1503
- });
1504
- assert.equal(launch.args[launch.args.indexOf("--tools") + 1], "read,runtime-a_search");
1505
- const configPath = launch.args[launch.args.indexOf("--mcp-config") + 1];
1506
- assert.ok(configPath);
1507
- assert.deepEqual(JSON.parse(fs.readFileSync(configPath, "utf-8")), {
1508
- mcpServers: { "runtime-a": definitions["runtime-a"] },
1509
- });
1510
- assert.deepEqual(JSON.parse(launch.env[MCP_DIRECT_CHILD_TOOLS_ENV]!), ["runtime-a_search"]);
1511
- });
1512
-
1513
- it("does not let legacy MCP ceiling aliases cross server boundaries", () => {
1514
- const definitions = {
1515
- "runtime-a": { command: "runtime-a", args: ["--stdio"] },
1516
- runtime_a: { command: "runtime_a", args: ["--stdio"] },
1517
- };
1518
- const launch = buildLegacyAliasLaunch({
1519
- definitions,
1520
- mcpDirectTools: ["runtime-a", "runtime_a"],
1521
- allowedTools: ["read", "runtime_a_search"],
1522
- });
1523
-
1524
- assert.equal(launch.args[launch.args.indexOf("--tools") + 1], "read,runtime_a_search");
1525
- const configPath = launch.args[launch.args.indexOf("--mcp-config") + 1];
1526
- assert.ok(configPath);
1527
- assert.deepEqual(JSON.parse(fs.readFileSync(configPath, "utf-8")), {
1528
- mcpServers: { runtime_a: definitions.runtime_a },
1529
- });
1530
- assert.deepEqual(JSON.parse(launch.env[MCP_DIRECT_CHILD_TOOLS_ENV]!), ["runtime_a_search"]);
1531
- });
1532
-
1533
- it("does not allow ambiguous legacy MCP ceiling aliases", () => {
1534
- const launch = buildLegacyAliasLaunch({
1535
- definitions: {
1536
- "runtime-a-b": { command: "runtime-a-b", args: ["--stdio"] },
1537
- "runtime_a-b": { command: "runtime_a-b", args: ["--stdio"] },
1538
- },
1539
- mcpDirectTools: ["runtime-a-b", "runtime_a-b"],
1540
- allowedTools: ["read", "runtime_a_b_search"],
1541
- });
1542
-
1543
- assert.equal(launch.args[launch.args.indexOf("--tools") + 1], "read");
1544
- assert.equal(launch.args.includes("--mcp-config"), false);
1545
- assert.equal(launch.env[MCP_DIRECT_CHILD_TOOLS_ENV], undefined);
1546
- });
1547
-
1548
- it("emits --no-tools for explicit empty tool allowlists", () => {
1549
- for (const requireReadTool of [false, true]) {
1550
- const { args, env } = buildPiArgs({
1551
- baseArgs: ["-p"],
1552
- task: "hello",
1553
- sessionEnabled: false,
1554
- inheritProjectContext: false,
1555
- inheritSkills: false,
1556
- requireReadTool,
1557
- tools: [],
1558
- });
1559
-
1560
- assert.ok(args.includes("--no-tools"));
1561
- assert.equal(args.includes("--tools"), false);
1562
- assert.equal(env.MCP_DIRECT_TOOLS, "__none__");
1563
- }
1564
- });
1565
-
1566
- it("restricts MCP-only agents to selected direct MCP tool names", () => {
1567
- for (const requireReadTool of [false, true]) {
1568
- const fixture = createMcpFixture();
1569
- writeMcpFixture(fixture);
1570
-
1571
- const { args, env } = buildPiArgs({
1572
- baseArgs: ["-p"],
1573
- task: "hello",
1574
- sessionEnabled: false,
1575
- inheritProjectContext: false,
1576
- inheritSkills: false,
1577
- requireReadTool,
1578
- mcpDirectTools: ["chrome-devtools"],
1579
- });
1580
-
1581
- assert.equal(
1582
- args[args.indexOf("--tools") + 1],
1583
- "chrome-devtools_take_screenshot,chrome-devtools_click",
1584
- );
1585
- assert.equal(env.MCP_DIRECT_TOOLS, "chrome-devtools");
1586
- }
1587
- });
1588
-
1589
- it("fails closed with a deterministic diagnostic when MCP selectors cannot be resolved", () => {
1590
- for (const requireReadTool of [false, true]) {
1591
- const fixture = createMcpFixture();
1592
- writeJson(path.join(fixture.agentDir, "mcp.json"), {
1593
- mcpServers: {
1594
- "chrome-devtools": { command: "npx", args: ["chrome-devtools-mcp"] },
1595
- },
1596
- });
1597
-
1598
- assert.throws(() => buildPiArgs({
1599
- baseArgs: ["-p"],
1600
- task: "hello",
1601
- sessionEnabled: false,
1602
- inheritProjectContext: false,
1603
- inheritSkills: false,
1604
- requireReadTool,
1605
- mcpDirectTools: ["chrome-devtools"],
1606
- }), /Unresolved MCP direct-tool selectors: chrome-devtools\./);
1607
- }
1608
- });
1609
-
1610
- it("supports direct MCP server/tool filters", () => {
1611
- const fixture = createMcpFixture();
1612
- writeMcpFixture(fixture, {
1613
- serverName: "github",
1614
- definition: { command: "github-mcp" },
1615
- tools: [{ name: "search_repositories" }, { name: "create_issue" }],
1616
- });
1617
-
1618
- const { args } = buildPiArgs({
1619
- baseArgs: ["-p"],
1620
- task: "hello",
1621
- sessionEnabled: false,
1622
- inheritProjectContext: false,
1623
- inheritSkills: false,
1624
- tools: ["read"],
1625
- mcpDirectTools: ["github/search_repositories"],
1626
- });
1627
-
1628
- assert.equal(
1629
- args[args.indexOf("--tools") + 1],
1630
- "read,github_search_repositories",
1631
- );
1632
- });
1633
-
1634
- it("matches adapter prefix modes for direct MCP names", () => {
1635
- for (const [prefix, expected] of [
1636
- ["server", "read,linear-mcp_list_issues"],
1637
- ["short", "read,linear_list_issues"],
1638
- ["none", "read,list_issues"],
1639
- ] as const) {
1640
- const fixture = createMcpFixture();
1641
- writeMcpFixture(fixture, {
1642
- serverName: "linear-mcp",
1643
- settings: { toolPrefix: prefix },
1644
- tools: [{ name: "list_issues" }],
1645
- });
1646
-
1647
- const { args } = buildPiArgs({
1648
- baseArgs: ["-p"],
1649
- task: "hello",
1650
- sessionEnabled: false,
1651
- inheritProjectContext: false,
1652
- inheritSkills: false,
1653
- tools: ["read"],
1654
- mcpDirectTools: ["linear-mcp"],
1655
- });
1656
-
1657
- assert.equal(args[args.indexOf("--tools") + 1], expected);
1658
- }
1659
- });
1660
-
1661
- it("includes resource tools and respects excludeTools", () => {
1662
- const fixture = createMcpFixture();
1663
- writeMcpFixture(fixture, {
1664
- serverName: "browser-mcp",
1665
- definition: { excludeTools: ["browser_click"] },
1666
- tools: [{ name: "click" }, { name: "navigate" }],
1667
- resources: [{ name: "Console Logs", uri: "resource://console" }],
1668
- });
1669
-
1670
- const { args } = buildPiArgs({
1671
- baseArgs: ["-p"],
1672
- task: "hello",
1673
- sessionEnabled: false,
1674
- inheritProjectContext: false,
1675
- inheritSkills: false,
1676
- tools: ["read"],
1677
- mcpDirectTools: ["browser-mcp"],
1678
- });
1679
-
1680
- assert.equal(
1681
- args[args.indexOf("--tools") + 1],
1682
- "read,browser-mcp_navigate,browser-mcp_get_console_logs",
1683
- );
1684
- });
1685
-
1686
- it("fails closed when direct MCP cache or config is missing or invalid", () => {
1687
- const missingFixture = createMcpFixture();
1688
- writeJson(path.join(missingFixture.agentDir, "mcp.json"), {
1689
- mcpServers: {
1690
- "chrome-devtools": { command: "npx", args: ["chrome-devtools-mcp"] },
1691
- },
1692
- });
1693
- assert.throws(() => buildPiArgs({
1694
- baseArgs: ["-p"],
1695
- task: "hello",
1696
- sessionEnabled: false,
1697
- inheritProjectContext: false,
1698
- inheritSkills: false,
1699
- tools: ["read", "bash"],
1700
- mcpDirectTools: ["chrome-devtools"],
1701
- }), /Unresolved MCP direct-tool selectors: chrome-devtools\./);
1702
-
1703
- const invalidFixture = createMcpFixture();
1704
- writeMcpFixture(invalidFixture, {
1705
- cachedAt: Date.now() - 8 * 24 * 60 * 60 * 1000,
1706
- });
1707
- assert.throws(() => buildPiArgs({
1708
- baseArgs: ["-p"],
1709
- task: "hello",
1710
- sessionEnabled: false,
1711
- inheritProjectContext: false,
1712
- inheritSkills: false,
1713
- tools: ["read", "bash"],
1714
- mcpDirectTools: ["chrome-devtools"],
1715
- }), /Unresolved MCP direct-tool selectors: chrome-devtools\./);
1716
- });
1717
-
1718
- it("rejects malformed MCP server and metadata entry fields at their load boundaries", () => {
1719
- const malformedConfig = createMcpFixture();
1720
- writeJson(path.join(malformedConfig.agentDir, "mcp.json"), {
1721
- mcpServers: {
1722
- "unsafe-server": { command: "unsafe-server", env: { TOKEN: 42 } },
1723
- },
1724
- });
1725
- assert.throws(() => buildPiArgs({
1726
- baseArgs: ["-p"],
1727
- task: "hello",
1728
- sessionEnabled: false,
1729
- inheritProjectContext: false,
1730
- inheritSkills: false,
1731
- tools: ["read"],
1732
- mcpDirectTools: ["unsafe-server"],
1733
- }), /Unresolved MCP direct-tool selectors: unsafe-server\./);
1734
-
1735
- const malformedCache = createMcpFixture();
1736
- const definition = { command: "cached-server" };
1737
- writeJson(path.join(malformedCache.agentDir, "mcp.json"), { mcpServers: { "cached-server": definition } });
1738
- writeJson(path.join(malformedCache.agentDir, "mcp-cache.json"), {
1739
- version: 1,
1740
- servers: {
1741
- "cached-server": {
1742
- configHash: computeMcpServerHash(definition),
1743
- cachedAt: Date.now(),
1744
- tools: [{ name: 42 }],
1745
- },
1746
- },
1747
- });
1748
- assert.throws(() => buildPiArgs({
1749
- baseArgs: ["-p"],
1750
- task: "hello",
1751
- sessionEnabled: false,
1752
- inheritProjectContext: false,
1753
- inheritSkills: false,
1754
- tools: ["read"],
1755
- mcpDirectTools: ["cached-server"],
1756
- }), /Unresolved MCP direct-tool selectors: cached-server\./);
1757
- });
1758
-
1759
- it("preserves MCP configuration errors during direct-tool resolution", () => {
1760
- const fixture = createMcpFixture();
1761
- writeJson(path.join(fixture.agentDir, "mcp.json"), {
1762
- mcpServers: {
1763
- "remote-mcp": { url: "https://example.test/${MISSING_MCP_TOKEN}" },
1764
- },
1765
- });
1766
- assert.throws(() => buildPiArgs({
1767
- baseArgs: ["-p"],
1768
- task: "hello",
1769
- sessionEnabled: false,
1770
- inheritProjectContext: false,
1771
- inheritSkills: false,
1772
- tools: ["read"],
1773
- mcpDirectTools: ["remote-mcp"],
1774
- }), /Missing environment variable in MCP server URL: MISSING_MCP_TOKEN/);
1775
- });
1776
-
1777
- it("resolves project MCP config from the child cwd and expands SELESAI_CODING_AGENT_DIR", () => {
1778
- const fixture = createMcpFixture();
1779
- process.env.SELESAI_CODING_AGENT_DIR = "~/.selesai/agent";
1780
- process.chdir(fixture.root);
1781
- fs.mkdirSync(path.join(fixture.projectDir, ".selesai"));
1782
- writeMcpFixture(fixture, {
1783
- serverName: "project-mcp",
1784
- configPath: path.join(fixture.projectDir, ".mcp.json"),
1785
- tools: [{ name: "inspect" }],
1786
- });
1787
-
1788
- const { args } = buildPiArgs({
1789
- baseArgs: ["-p"],
1790
- task: "hello",
1791
- sessionEnabled: false,
1792
- inheritProjectContext: false,
1793
- inheritSkills: false,
1794
- tools: ["read"],
1795
- mcpDirectTools: ["project-mcp"],
1796
- cwd: fixture.projectDir,
1797
- });
1798
-
1799
- assert.equal(args[args.indexOf("--tools") + 1], "read,project-mcp_inspect");
1800
- });
1801
-
1802
- it("resolves direct MCP tools from Pi package manifests", () => {
1803
- const fixture = createMcpFixture();
1804
- const packageRoot = path.join(fixture.agentDir, "npm", "node_modules", "@acme", "tools");
1805
- const definition = { command: "node", args: ["package-mcp"] };
1806
- writeJson(path.join(fixture.agentDir, "settings.json"), { packages: ["npm:@acme/tools@1.0.0"] });
1807
- writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } });
1808
- writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } });
1809
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1810
- version: 1,
1811
- servers: {
1812
- "acme_tools__wiki": {
1813
- configHash: computeMcpServerHash(definition),
1814
- cachedAt: Date.now(),
1815
- tools: [{ name: "read_wiki_structure" }],
1816
- },
1817
- },
1818
- });
1819
-
1820
- const { args } = buildPiArgs({
1821
- baseArgs: ["-p"],
1822
- task: "hello",
1823
- sessionEnabled: false,
1824
- inheritProjectContext: false,
1825
- inheritSkills: false,
1826
- tools: ["read"],
1827
- mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"],
1828
- cwd: fixture.projectDir,
1829
- });
1830
-
1831
- assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure");
1832
- });
1833
-
1834
- it("fails closed on malformed MCP server fields from Pi package manifests", () => {
1835
- const fixture = createMcpFixture();
1836
- const packageRoot = path.join(fixture.agentDir, "npm", "node_modules", "@acme", "tools");
1837
- const malformedFields: Record<string, unknown> = { includeTools: "read_wiki_structure" };
1838
- const definition = { command: "node", args: ["package-mcp"], ...malformedFields };
1839
- writeJson(path.join(fixture.agentDir, "settings.json"), { packages: ["npm:@acme/tools@1.0.0"] });
1840
- writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } });
1841
- writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } });
1842
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1843
- version: 1,
1844
- servers: {
1845
- "acme_tools__wiki": {
1846
- configHash: computeMcpServerHash(definition),
1847
- cachedAt: Date.now(),
1848
- tools: [{ name: "read_wiki_structure" }, { name: "delete_wiki" }],
1849
- },
1850
- },
1851
- });
1852
-
1853
- assert.throws(() => buildPiArgs({
1854
- baseArgs: ["-p"],
1855
- task: "hello",
1856
- sessionEnabled: false,
1857
- inheritProjectContext: false,
1858
- inheritSkills: false,
1859
- tools: ["read"],
1860
- mcpDirectTools: ["acme_tools__wiki"],
1861
- cwd: fixture.projectDir,
1862
- }), /Unresolved MCP direct-tool selectors: acme_tools__wiki\./);
1863
- });
1864
-
1865
- it("resolves direct MCP tools from the git-root package when child cwd has an incidental .selesai directory", () => {
1866
- const fixture = createMcpFixture();
1867
- const nestedCwd = path.join(fixture.projectDir, "packages", "app");
1868
- const packageRoot = path.join(fixture.projectDir, ".selesai", "npm", "node_modules", "@acme", "tools");
1869
- const definition = { command: "node", args: ["package-mcp"] };
1870
- fs.mkdirSync(nestedCwd, { recursive: true });
1871
- fs.mkdirSync(path.join(nestedCwd, ".selesai"));
1872
- fs.mkdirSync(path.join(fixture.projectDir, ".git"));
1873
- writeJson(path.join(fixture.projectDir, ".selesai", "settings.json"), {
1874
- packages: ["npm:@acme/tools@1.0.0"],
1875
- subagents: { projectRootResolution: "git-root" },
1876
- });
1877
- writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } });
1878
- writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } });
1879
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1880
- version: 1,
1881
- servers: {
1882
- "acme_tools__wiki": {
1883
- configHash: computeMcpServerHash(definition),
1884
- cachedAt: Date.now(),
1885
- tools: [{ name: "read_wiki_structure" }],
1886
- },
1887
- },
1888
- });
1889
-
1890
- const { args } = buildPiArgs({
1891
- baseArgs: ["-p"],
1892
- task: "hello",
1893
- sessionEnabled: false,
1894
- inheritProjectContext: false,
1895
- inheritSkills: false,
1896
- tools: ["read"],
1897
- mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"],
1898
- cwd: nestedCwd,
1899
- });
1900
-
1901
- assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure");
1902
- });
1903
-
1904
- it("resolves direct MCP tools from pinned Git package manifests", () => {
1905
- const fixture = createMcpFixture();
1906
- const packageRoot = path.join(fixture.agentDir, "git", "github.com", "acme", "tools");
1907
- const definition = { command: "node", args: ["git-package-mcp"] };
1908
- writeJson(path.join(fixture.agentDir, "settings.json"), { packages: ["git:https://github.com/acme/tools.git#main"] });
1909
- writeJson(path.join(packageRoot, "package.json"), { name: "@acme/tools", pi: { mcp: "./mcp.json" } });
1910
- writeJson(path.join(packageRoot, "mcp.json"), { mcpServers: { wiki: definition } });
1911
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1912
- version: 1,
1913
- servers: {
1914
- "acme_tools__wiki": {
1915
- configHash: computeMcpServerHash(definition),
1916
- cachedAt: Date.now(),
1917
- tools: [{ name: "read_wiki_structure" }],
1918
- },
1919
- },
1920
- });
1921
-
1922
- const { args } = buildPiArgs({
1923
- baseArgs: ["-p"],
1924
- task: "hello",
1925
- sessionEnabled: false,
1926
- inheritProjectContext: false,
1927
- inheritSkills: false,
1928
- tools: ["read"],
1929
- mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"],
1930
- cwd: fixture.projectDir,
1931
- });
1932
-
1933
- assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure");
1934
- });
1935
-
1936
- it("resolves direct MCP tools from Agent Plugin MCP config", () => {
1937
- const fixture = createMcpFixture();
1938
- const pluginRoot = path.join(fixture.projectDir, "plugins", "acme-tools");
1939
- const definition = { url: "https://example.test/mcp", headers: { "X-Tenant": "public" } };
1940
- fs.mkdirSync(path.join(fixture.projectDir, ".selesai"));
1941
- writeJson(path.join(pluginRoot, "plugin.json"), {
1942
- $schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
1943
- name: "acme.tools",
1944
- });
1945
- writeJson(path.join(pluginRoot, "mcp.json"), {
1946
- $schema: "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
1947
- mcpServers: { wiki: { type: "streamable-http", ...definition } },
1948
- });
1949
- writeJson(path.join(fixture.projectDir, ".mcp.json"), {
1950
- settings: { agentPluginPaths: ["./plugins/acme-tools"] },
1951
- mcpServers: {},
1952
- });
1953
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
1954
- version: 1,
1955
- servers: {
1956
- "acme_tools__wiki": {
1957
- configHash: computeMcpServerHash(definition),
1958
- cachedAt: Date.now(),
1959
- tools: [{ name: "read_wiki_structure" }],
1960
- },
1961
- },
1962
- });
1963
-
1964
- const { args } = buildPiArgs({
1965
- baseArgs: ["-p"],
1966
- task: "hello",
1967
- sessionEnabled: false,
1968
- inheritProjectContext: false,
1969
- inheritSkills: false,
1970
- tools: ["read"],
1971
- mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"],
1972
- cwd: fixture.projectDir,
1973
- });
1974
-
1975
- assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure");
1976
- });
1977
-
1978
- it("resolves root Agent Plugin MCP tools when child cwd is nested", () => {
1979
- const fixture = createMcpFixture();
1980
- const nestedCwd = path.join(fixture.projectDir, "packages", "app");
1981
- const pluginRoot = path.join(fixture.projectDir, "plugins", "acme-tools");
1982
- const definition = { url: "https://example.test/mcp", headers: { "X-Tenant": "public" } };
1983
- fs.mkdirSync(nestedCwd, { recursive: true });
1984
- fs.mkdirSync(path.join(nestedCwd, ".selesai"));
1985
- fs.mkdirSync(path.join(fixture.projectDir, ".git"));
1986
- writeJson(path.join(fixture.projectDir, ".selesai", "settings.json"), {
1987
- subagents: { projectRootResolution: "git-root" },
1988
- });
1989
- writeJson(path.join(pluginRoot, "plugin.json"), {
1990
- $schema: "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
1991
- name: "acme.tools",
1992
- });
1993
- writeJson(path.join(pluginRoot, "mcp.json"), {
1994
- $schema: "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
1995
- mcpServers: { wiki: { type: "streamable-http", ...definition } },
1996
- });
1997
- writeJson(path.join(fixture.projectDir, ".mcp.json"), {
1998
- settings: { agentPluginPaths: ["./plugins/acme-tools"] },
1999
- mcpServers: {},
2000
- });
2001
- writeJson(path.join(fixture.agentDir, "mcp-cache.json"), {
2002
- version: 1,
2003
- servers: {
2004
- "acme_tools__wiki": {
2005
- configHash: computeMcpServerHash(definition),
2006
- cachedAt: Date.now(),
2007
- tools: [{ name: "read_wiki_structure" }],
2008
- },
2009
- },
2010
- });
2011
-
2012
- const { args } = buildPiArgs({
2013
- baseArgs: ["-p"],
2014
- task: "hello",
2015
- sessionEnabled: false,
2016
- inheritProjectContext: false,
2017
- inheritSkills: false,
2018
- tools: ["read"],
2019
- mcpDirectTools: ["acme_tools__wiki/read_wiki_structure"],
2020
- cwd: nestedCwd,
2021
- });
2022
-
2023
- assert.equal(args[args.indexOf("--tools") + 1], "read,acme_tools__wiki_read_wiki_structure");
2024
- });
2025
-
2026
- it("keeps tool extension paths when explicit extensions are allowlisted", () => {
2027
- const fixture = createMcpFixture();
2028
- writeMcpFixture(fixture, { tools: [{ name: "take_screenshot" }] });
2029
-
2030
- const { args } = buildPiArgs({
2031
- baseArgs: ["-p"],
2032
- task: "hello",
2033
- sessionEnabled: false,
2034
- inheritProjectContext: false,
2035
- inheritSkills: false,
2036
- tools: ["read", "./custom-tool.ts"],
2037
- extensions: ["./allowed-ext.ts"],
2038
- mcpDirectTools: ["chrome-devtools"],
2039
- });
2040
-
2041
- const extensionArgs = args.filter(
2042
- (arg, index) => args[index - 1] === "--extension",
2043
- );
2044
- assert.equal(
2045
- args[args.indexOf("--tools") + 1],
2046
- "read,chrome-devtools_take_screenshot",
2047
- );
2048
- assert.ok(
2049
- extensionArgs.some((arg) =>
2050
- arg.endsWith(
2051
- path.join("src", "runs", "shared", "subagent-prompt-runtime.ts"),
2052
- ),
2053
- ),
2054
- );
2055
- assert.ok(extensionArgs.includes("./custom-tool.ts"));
2056
- assert.ok(extensionArgs.includes("./allowed-ext.ts"));
2057
- });
2058
-
2059
- it("loads subagent-only extension paths only through child process extension args", () => {
2060
- const { args } = buildPiArgs({
2061
- baseArgs: ["-p"],
2062
- task: "hello",
2063
- sessionEnabled: false,
2064
- inheritProjectContext: false,
2065
- inheritSkills: false,
2066
- tools: ["read"],
2067
- extensions: ["./main-allowed-ext.ts"],
2068
- subagentOnlyExtensions: ["./child-tool.ts"],
2069
- });
2070
-
2071
- const extensionArgs = args.filter(
2072
- (arg, index) => args[index - 1] === "--extension",
2073
- );
2074
- assert.ok(args.includes("--no-extensions"));
2075
- assert.equal(args[args.indexOf("--tools") + 1], "read");
2076
- assert.ok(extensionArgs.includes("./main-allowed-ext.ts"));
2077
- assert.ok(extensionArgs.includes("./child-tool.ts"));
2078
- });
2079
-
2080
- it("authorizes child fanout from an exact declared builtin subagent", () => {
2081
- const { args, env } = buildPiArgs({
2082
- baseArgs: ["-p"],
2083
- task: "hello",
2084
- sessionEnabled: false,
2085
- inheritProjectContext: false,
2086
- inheritSkills: false,
2087
- tools: ["read", "subagent"],
2088
- runId: "parent-run",
2089
- childIndex: 1,
2090
- parentEventSink: "/tmp/root/events",
2091
- parentControlInbox: "/tmp/root/control",
2092
- parentRootRunId: "root-run",
2093
- parentCapabilityToken: "token-1",
2094
- });
2095
-
2096
- const extensionArgs = args.filter(
2097
- (arg, index) => args[index - 1] === "--extension",
2098
- );
2099
- assert.equal(args[args.indexOf("--tools") + 1], "read,subagent");
2100
- assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "1");
2101
- assert.equal(env[SUBAGENT_PARENT_EVENT_SINK_ENV], "/tmp/root/events");
2102
- assert.equal(env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], "/tmp/root/control");
2103
- assert.equal(env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], "root-run");
2104
- assert.equal(env[SUBAGENT_PARENT_RUN_ID_ENV], "parent-run");
2105
- assert.equal(env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "1");
2106
- assert.equal(env[SUBAGENT_PARENT_DEPTH_ENV], "1");
2107
- assert.deepEqual(JSON.parse(env[SUBAGENT_PARENT_PATH_ENV] ?? "[]"), [
2108
- { runId: "parent-run", stepIndex: 1 },
2109
- ]);
2110
- assert.equal(env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], "token-1");
2111
- assert.ok(
2112
- extensionArgs.some((arg) =>
2113
- arg.endsWith(path.join("src", "extension", "fanout-child.ts")),
2114
- ),
2115
- );
2116
- });
2117
-
2118
- it("authorizes explicit nested fanout without creating a tool allowlist", () => {
2119
- const inherited = buildPiArgs({
2120
- baseArgs: ["-p"],
2121
- task: "hello",
2122
- sessionEnabled: false,
2123
- inheritProjectContext: false,
2124
- inheritSkills: false,
2125
- allowNestedSubagents: true,
2126
- });
2127
- const extensionArgs = inherited.args.filter((arg, index) => inherited.args[index - 1] === "--extension");
2128
- assert.ok(!inherited.args.includes("--tools"));
2129
- assert.ok(!inherited.args.includes("--no-tools"));
2130
- assert.ok(!inherited.args.includes("--no-extensions"));
2131
- assert.equal(inherited.env[SUBAGENT_FANOUT_CHILD_ENV], "1");
2132
- assert.ok(extensionArgs.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts"))));
2133
-
2134
- const ceilingDenied = buildPiArgs({
2135
- baseArgs: ["-p"],
2136
- task: "hello",
2137
- sessionEnabled: false,
2138
- inheritProjectContext: false,
2139
- inheritSkills: false,
2140
- allowNestedSubagents: true,
2141
- capabilityCeiling: { version: 1, allowedTools: ["read"], denyExtensions: false, sources: ["test"] },
2142
- });
2143
- const deniedExtensions = ceilingDenied.args.filter((arg, index) => ceilingDenied.args[index - 1] === "--extension");
2144
- assert.equal(ceilingDenied.env[SUBAGENT_FANOUT_CHILD_ENV], "0");
2145
- assert.ok(!deniedExtensions.some((arg) => arg.endsWith(path.join("src", "extension", "fanout-child.ts"))));
2146
- });
2147
-
2148
- it("clears all fanout routing env values for non-fanout children", () => {
2149
- const { args, env } = buildPiArgs({
2150
- baseArgs: ["-p"],
2151
- task: "hello",
2152
- sessionEnabled: false,
2153
- inheritProjectContext: false,
2154
- inheritSkills: false,
2155
- tools: ["read", "mcp:server/subagent"],
2156
- parentEventSink: "/tmp/should-not-leak/events",
2157
- parentControlInbox: "/tmp/should-not-leak/control",
2158
- parentRootRunId: "root-should-not-leak",
2159
- parentRunId: "should-not-leak",
2160
- parentChildIndex: 9,
2161
- parentCapabilityToken: "token-should-not-leak",
2162
- });
2163
-
2164
- const extensionArgs = args.filter(
2165
- (arg, index) => args[index - 1] === "--extension",
2166
- );
2167
- assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "0");
2168
- assert.equal(env[SUBAGENT_PARENT_EVENT_SINK_ENV], "");
2169
- assert.equal(env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], "");
2170
- assert.equal(env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], "");
2171
- assert.equal(env[SUBAGENT_PARENT_RUN_ID_ENV], "");
2172
- assert.equal(env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "");
2173
- assert.equal(env[SUBAGENT_PARENT_DEPTH_ENV], "");
2174
- assert.equal(env[SUBAGENT_PARENT_PATH_ENV], "");
2175
- assert.equal(env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], "");
2176
- assert.ok(
2177
- !extensionArgs.some((arg) =>
2178
- arg.endsWith(path.join("src", "extension", "fanout-child.ts")),
2179
- ),
2180
- );
2181
- });
2182
-
2183
- it("inherits routing env only for authorized fanout children", () => {
2184
- process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] = "/tmp/inherited/events";
2185
- process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = "/tmp/inherited/control";
2186
- process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = "inherited-root";
2187
- process.env[SUBAGENT_PARENT_RUN_ID_ENV] = "inherited-run";
2188
- process.env[SUBAGENT_RUN_ID_ENV] = "owner-run";
2189
- process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "4";
2190
- process.env[SUBAGENT_PARENT_DEPTH_ENV] = "2";
2191
- process.env[SUBAGENT_PARENT_PATH_ENV] = JSON.stringify([
2192
- { runId: "root-run", stepIndex: 0 },
2193
- { runId: "../unsafe", stepIndex: 1 },
2194
- { runId: "owner-run", stepIndex: 1 },
2195
- ]);
2196
- process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = "inherited-token";
2197
-
2198
- const fanout = buildPiArgs({
2199
- baseArgs: ["-p"],
2200
- task: "hello",
2201
- sessionEnabled: false,
2202
- inheritProjectContext: false,
2203
- inheritSkills: false,
2204
- tools: ["subagent"],
2205
- });
2206
- assert.equal(
2207
- fanout.env[SUBAGENT_PARENT_EVENT_SINK_ENV],
2208
- "/tmp/inherited/events",
2209
- );
2210
- assert.equal(
2211
- fanout.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV],
2212
- "/tmp/inherited/control",
2213
- );
2214
- assert.equal(fanout.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], "inherited-root");
2215
- assert.equal(fanout.env[SUBAGENT_PARENT_RUN_ID_ENV], "owner-run");
2216
- assert.equal(fanout.env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "4");
2217
- assert.equal(fanout.env[SUBAGENT_PARENT_DEPTH_ENV], "3");
2218
- assert.deepEqual(JSON.parse(fanout.env[SUBAGENT_PARENT_PATH_ENV] ?? "[]"), [
2219
- { runId: "root-run", stepIndex: 0 },
2220
- { runId: "owner-run", stepIndex: 1 },
2221
- { runId: "owner-run", stepIndex: 4 },
2222
- ]);
2223
- assert.equal(
2224
- fanout.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV],
2225
- "inherited-token",
2226
- );
2227
-
2228
- const nonFanout = buildPiArgs({
2229
- baseArgs: ["-p"],
2230
- task: "hello",
2231
- sessionEnabled: false,
2232
- inheritProjectContext: false,
2233
- inheritSkills: false,
2234
- tools: ["read"],
2235
- });
2236
- assert.equal(nonFanout.env[SUBAGENT_FANOUT_CHILD_ENV], "0");
2237
- assert.equal(nonFanout.env[SUBAGENT_PARENT_EVENT_SINK_ENV], "");
2238
- assert.equal(nonFanout.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV], "");
2239
- assert.equal(nonFanout.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV], "");
2240
- assert.equal(nonFanout.env[SUBAGENT_PARENT_RUN_ID_ENV], "");
2241
- assert.equal(nonFanout.env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "");
2242
- assert.equal(nonFanout.env[SUBAGENT_PARENT_DEPTH_ENV], "");
2243
- assert.equal(nonFanout.env[SUBAGENT_PARENT_PATH_ENV], "");
2244
- assert.equal(nonFanout.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV], "");
2245
- });
2246
-
2247
- it("prefers the current subagent run id over inherited ancestor ids for nested fanout routing", () => {
2248
- process.env[SUBAGENT_PARENT_EVENT_SINK_ENV] = "/tmp/inherited/events";
2249
- process.env[SUBAGENT_PARENT_CONTROL_INBOX_ENV] = "/tmp/inherited/control";
2250
- process.env[SUBAGENT_PARENT_ROOT_RUN_ID_ENV] = "root-run";
2251
- process.env[SUBAGENT_PARENT_RUN_ID_ENV] = "older-parent";
2252
- process.env[SUBAGENT_RUN_ID_ENV] = "ancestor-run";
2253
- process.env[SUBAGENT_PARENT_CHILD_INDEX_ENV] = "4";
2254
- process.env[SUBAGENT_PARENT_DEPTH_ENV] = "1";
2255
- process.env[SUBAGENT_PARENT_PATH_ENV] = JSON.stringify([
2256
- { runId: "root-run", stepIndex: 0 },
2257
- ]);
2258
- process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = "inherited-token";
2259
-
2260
- const { env } = buildPiArgs({
2261
- baseArgs: ["-p"],
2262
- task: "hello",
2263
- sessionEnabled: false,
2264
- inheritProjectContext: false,
2265
- inheritSkills: false,
2266
- tools: ["subagent"],
2267
- runId: "current-nested-run",
2268
- childIndex: 2,
2269
- });
2270
-
2271
- assert.equal(env[SUBAGENT_PARENT_RUN_ID_ENV], "current-nested-run");
2272
- assert.equal(env[SUBAGENT_PARENT_CHILD_INDEX_ENV], "2");
2273
- assert.equal(env[SUBAGENT_PARENT_DEPTH_ENV], "2");
2274
- assert.deepEqual(JSON.parse(env[SUBAGENT_PARENT_PATH_ENV] ?? "[]"), [
2275
- { runId: "root-run", stepIndex: 0 },
2276
- { runId: "current-nested-run", stepIndex: 2 },
2277
- ]);
2278
- });
2279
-
2280
- it("does not let direct MCP tools authorize child fanout", () => {
2281
- const fixture = createMcpFixture();
2282
- writeMcpFixture(fixture, {
2283
- serverName: "delegator",
2284
- definition: { command: "delegator-mcp" },
2285
- tools: [{ name: "subagent" }],
2286
- });
2287
-
2288
- const { args, env } = buildPiArgs({
2289
- baseArgs: ["-p"],
2290
- task: "hello",
2291
- sessionEnabled: false,
2292
- inheritProjectContext: false,
2293
- inheritSkills: false,
2294
- tools: ["read"],
2295
- mcpDirectTools: ["delegator"],
2296
- });
2297
-
2298
- const extensionArgs = args.filter(
2299
- (arg, index) => args[index - 1] === "--extension",
2300
- );
2301
- assert.equal(args[args.indexOf("--tools") + 1], "read,delegator_subagent");
2302
- assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "0");
2303
- assert.ok(
2304
- !extensionArgs.some((arg) =>
2305
- arg.endsWith(path.join("src", "extension", "fanout-child.ts")),
2306
- ),
2307
- );
2308
- });
2309
-
2310
- it("keeps child-safe fanout registration in explicit extensions mode", () => {
2311
- const { args, env } = buildPiArgs({
2312
- baseArgs: ["-p"],
2313
- task: "hello",
2314
- sessionEnabled: false,
2315
- inheritProjectContext: false,
2316
- inheritSkills: false,
2317
- tools: ["subagent"],
2318
- extensions: ["./agent-allowed-ext.ts"],
2319
- });
2320
-
2321
- const extensionArgs = args.filter(
2322
- (arg, index) => args[index - 1] === "--extension",
2323
- );
2324
- assert.ok(args.includes("--no-extensions"));
2325
- assert.equal(env[SUBAGENT_FANOUT_CHILD_ENV], "1");
2326
- assert.ok(
2327
- extensionArgs.some((arg) =>
2328
- arg.endsWith(path.join("src", "extension", "fanout-child.ts")),
2329
- ),
2330
- );
2331
- assert.ok(extensionArgs.includes("./agent-allowed-ext.ts"));
2332
- });
2333
-
2334
- it("emits an empty prompt file when replace mode is used with an empty prompt", () => {
2335
- const { args } = buildPiArgs({
2336
- baseArgs: ["-p"],
2337
- task: "hello",
2338
- sessionEnabled: false,
2339
- systemPrompt: "",
2340
- systemPromptMode: "replace",
2341
- inheritProjectContext: false,
2342
- inheritSkills: false,
2343
- });
2344
-
2345
- assert.ok(args.includes("--system-prompt"));
2346
- });
2347
- });