@selesai/code 0.13.0 → 0.13.1

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 (461) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/core/agent-session.d.ts +1 -1
  3. package/dist/core/agent-session.js +1 -1
  4. package/dist/core/sdk.d.ts +2 -2
  5. package/dist/core/sdk.js +1 -1
  6. package/dist/core/system-prompt.d.ts +1 -1
  7. package/dist/core/system-prompt.js +1 -1
  8. package/dist/extensions/ascii.txt +9 -0
  9. package/dist/extensions/package.json +1 -2
  10. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +371 -0
  11. package/dist/extensions/pi-hermes-memory/LICENSE +21 -0
  12. package/dist/extensions/pi-hermes-memory/README.md +643 -0
  13. package/dist/extensions/pi-hermes-memory/docs/0.1/TASKS.md +197 -0
  14. package/dist/extensions/pi-hermes-memory/docs/0.2/PLAN.md +290 -0
  15. package/dist/extensions/pi-hermes-memory/docs/0.2/TASKS.md +134 -0
  16. package/dist/extensions/pi-hermes-memory/docs/0.2/TEST-PLAN.md +216 -0
  17. package/dist/extensions/pi-hermes-memory/docs/0.3/PLAN.md +330 -0
  18. package/dist/extensions/pi-hermes-memory/docs/0.3/TASKS.md +125 -0
  19. package/dist/extensions/pi-hermes-memory/docs/0.4/PLAN.md +160 -0
  20. package/dist/extensions/pi-hermes-memory/docs/0.4/TASKS.md +113 -0
  21. package/dist/extensions/pi-hermes-memory/docs/0.6/CHANGELOG.md +57 -0
  22. package/dist/extensions/pi-hermes-memory/docs/0.6/PLAN.md +202 -0
  23. package/dist/extensions/pi-hermes-memory/docs/0.6/TASKS.md +144 -0
  24. package/dist/extensions/pi-hermes-memory/docs/0.7/PLAN.md +349 -0
  25. package/dist/extensions/pi-hermes-memory/docs/0.7/TASKS.md +110 -0
  26. package/dist/extensions/pi-hermes-memory/docs/PUBLISHING.md +149 -0
  27. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +468 -0
  28. package/dist/extensions/pi-hermes-memory/docs/images/memory-architecture.svg +1 -0
  29. package/dist/extensions/pi-hermes-memory/docs/images/pi-logo.svg +22 -0
  30. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.png +0 -0
  31. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.svg +19 -0
  32. package/dist/extensions/pi-hermes-memory/docs/images/security-flow.svg +1 -0
  33. package/dist/extensions/pi-hermes-memory/docs/images/session-lifecycle.svg +1 -0
  34. package/dist/extensions/pi-hermes-memory/docs/images/source-architecture.svg +1 -0
  35. package/dist/extensions/pi-hermes-memory/docs/mermaid/memory-architecture.mmd +49 -0
  36. package/dist/extensions/pi-hermes-memory/docs/mermaid/security-flow.mmd +40 -0
  37. package/dist/extensions/pi-hermes-memory/docs/mermaid/session-lifecycle.mmd +56 -0
  38. package/dist/extensions/pi-hermes-memory/docs/mermaid/source-architecture.mmd +54 -0
  39. package/dist/extensions/pi-hermes-memory/package-lock.json +3799 -0
  40. package/dist/extensions/pi-hermes-memory/package.json +66 -0
  41. package/dist/extensions/pi-hermes-memory/scripts/check-min-sdk.mjs +99 -0
  42. package/dist/extensions/pi-hermes-memory/scripts/ensure-dev.mjs +57 -0
  43. package/dist/extensions/pi-hermes-memory/src/auto-consolidation-warning.ts +6 -0
  44. package/dist/extensions/pi-hermes-memory/src/config.ts +178 -0
  45. package/dist/extensions/pi-hermes-memory/src/constants.ts +424 -0
  46. package/dist/extensions/pi-hermes-memory/src/extension-root-migration.ts +639 -0
  47. package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +401 -0
  48. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +297 -0
  49. package/dist/extensions/pi-hermes-memory/src/handlers/child-process-watchdog.mjs +90 -0
  50. package/dist/extensions/pi-hermes-memory/src/handlers/correction-detector.ts +300 -0
  51. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +86 -0
  52. package/dist/extensions/pi-hermes-memory/src/handlers/insights.ts +78 -0
  53. package/dist/extensions/pi-hermes-memory/src/handlers/interview.ts +37 -0
  54. package/dist/extensions/pi-hermes-memory/src/handlers/learn-memory.ts +186 -0
  55. package/dist/extensions/pi-hermes-memory/src/handlers/message-parts.ts +27 -0
  56. package/dist/extensions/pi-hermes-memory/src/handlers/pi-child-process.ts +485 -0
  57. package/dist/extensions/pi-hermes-memory/src/handlers/preview-context.ts +105 -0
  58. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +506 -0
  59. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +145 -0
  60. package/dist/extensions/pi-hermes-memory/src/handlers/session-flush.ts +151 -0
  61. package/dist/extensions/pi-hermes-memory/src/handlers/session-live-index.ts +94 -0
  62. package/dist/extensions/pi-hermes-memory/src/handlers/skills-command.ts +1334 -0
  63. package/dist/extensions/pi-hermes-memory/src/handlers/standing-pin.ts +111 -0
  64. package/dist/extensions/pi-hermes-memory/src/handlers/switch-project.ts +75 -0
  65. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +266 -0
  66. package/dist/extensions/pi-hermes-memory/src/index.ts +378 -0
  67. package/dist/extensions/pi-hermes-memory/src/lifecycle-timing.ts +54 -0
  68. package/dist/extensions/pi-hermes-memory/src/paths.ts +61 -0
  69. package/dist/extensions/pi-hermes-memory/src/project-context.ts +13 -0
  70. package/dist/extensions/pi-hermes-memory/src/project-memory-migration.ts +93 -0
  71. package/dist/extensions/pi-hermes-memory/src/project.ts +157 -0
  72. package/dist/extensions/pi-hermes-memory/src/prompt-context.ts +55 -0
  73. package/dist/extensions/pi-hermes-memory/src/store/atomic-lock-coordinator.ts +375 -0
  74. package/dist/extensions/pi-hermes-memory/src/store/canonical-storage-path.ts +54 -0
  75. package/dist/extensions/pi-hermes-memory/src/store/content-scanner.ts +101 -0
  76. package/dist/extensions/pi-hermes-memory/src/store/db.ts +1180 -0
  77. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +101 -0
  78. package/dist/extensions/pi-hermes-memory/src/store/markdown-mutation-lock.ts +38 -0
  79. package/dist/extensions/pi-hermes-memory/src/store/memory-lookup.ts +24 -0
  80. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +1191 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/recovery-maintenance.ts +66 -0
  82. package/dist/extensions/pi-hermes-memory/src/store/schema.ts +118 -0
  83. package/dist/extensions/pi-hermes-memory/src/store/session-anchor-search.ts +472 -0
  84. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +511 -0
  85. package/dist/extensions/pi-hermes-memory/src/store/session-parser.ts +213 -0
  86. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +234 -0
  87. package/dist/extensions/pi-hermes-memory/src/store/skill-store.ts +950 -0
  88. package/dist/extensions/pi-hermes-memory/src/store/skill-utils.ts +133 -0
  89. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +1002 -0
  90. package/dist/extensions/pi-hermes-memory/src/store/sqlite-native.ts +241 -0
  91. package/dist/extensions/pi-hermes-memory/src/store/standing-instructions.ts +247 -0
  92. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +95 -0
  93. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +469 -0
  94. package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +246 -0
  95. package/dist/extensions/pi-hermes-memory/src/tools/shared-output-view.ts +170 -0
  96. package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +333 -0
  97. package/dist/extensions/pi-hermes-memory/src/tools/tool-result-views.ts +105 -0
  98. package/dist/extensions/pi-hermes-memory/src/types.ts +214 -0
  99. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +434 -0
  100. package/dist/extensions/pi-hermes-memory/tests/extension-root-migration.test.ts +368 -0
  101. package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +984 -0
  102. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +950 -0
  103. package/dist/extensions/pi-hermes-memory/tests/handlers/correction-detector.test.ts +671 -0
  104. package/dist/extensions/pi-hermes-memory/tests/handlers/insights.test.ts +157 -0
  105. package/dist/extensions/pi-hermes-memory/tests/handlers/interview.test.ts +127 -0
  106. package/dist/extensions/pi-hermes-memory/tests/handlers/pi-child-process.test.ts +905 -0
  107. package/dist/extensions/pi-hermes-memory/tests/handlers/preview-context.test.ts +152 -0
  108. package/dist/extensions/pi-hermes-memory/tests/handlers/prompt-context.test.ts +166 -0
  109. package/dist/extensions/pi-hermes-memory/tests/handlers/resources-discover.test.ts +71 -0
  110. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +614 -0
  111. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +192 -0
  112. package/dist/extensions/pi-hermes-memory/tests/handlers/session-flush.test.ts +677 -0
  113. package/dist/extensions/pi-hermes-memory/tests/handlers/session-live-index.test.ts +225 -0
  114. package/dist/extensions/pi-hermes-memory/tests/handlers/skills-command.test.ts +827 -0
  115. package/dist/extensions/pi-hermes-memory/tests/handlers/standing-pin.test.ts +134 -0
  116. package/dist/extensions/pi-hermes-memory/tests/handlers/sync-markdown-memories.test.ts +491 -0
  117. package/dist/extensions/pi-hermes-memory/tests/handlers/system-prompt.test.ts +199 -0
  118. package/dist/extensions/pi-hermes-memory/tests/handlers/thinking-max.test.ts +64 -0
  119. package/dist/extensions/pi-hermes-memory/tests/index.test.ts +30 -0
  120. package/dist/extensions/pi-hermes-memory/tests/integration/flow.test.ts +126 -0
  121. package/dist/extensions/pi-hermes-memory/tests/lifecycle-timing.test.ts +76 -0
  122. package/dist/extensions/pi-hermes-memory/tests/paths.test.ts +23 -0
  123. package/dist/extensions/pi-hermes-memory/tests/project-memory-migration.test.ts +80 -0
  124. package/dist/extensions/pi-hermes-memory/tests/project-rebinding.test.ts +115 -0
  125. package/dist/extensions/pi-hermes-memory/tests/project.test.ts +120 -0
  126. package/dist/extensions/pi-hermes-memory/tests/run-all-timeout.test.ts +107 -0
  127. package/dist/extensions/pi-hermes-memory/tests/run-all.sh +45 -0
  128. package/dist/extensions/pi-hermes-memory/tests/store/atomic-lock-coordinator.test.ts +381 -0
  129. package/dist/extensions/pi-hermes-memory/tests/store/content-scanner.test.ts +388 -0
  130. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +1046 -0
  131. package/dist/extensions/pi-hermes-memory/tests/store/markdown-mutation-lock.test.ts +34 -0
  132. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +2150 -0
  133. package/dist/extensions/pi-hermes-memory/tests/store/recovery-maintenance.test.ts +170 -0
  134. package/dist/extensions/pi-hermes-memory/tests/store/session-anchor-search.test.ts +210 -0
  135. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +643 -0
  136. package/dist/extensions/pi-hermes-memory/tests/store/session-parser.test.ts +282 -0
  137. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +287 -0
  138. package/dist/extensions/pi-hermes-memory/tests/store/skill-store.test.ts +678 -0
  139. package/dist/extensions/pi-hermes-memory/tests/store/skill-utils.test.ts +21 -0
  140. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-lazy-load.test.ts +78 -0
  141. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +543 -0
  142. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +164 -0
  143. package/dist/extensions/pi-hermes-memory/tests/store/standing-instructions.test.ts +171 -0
  144. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +84 -0
  145. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +709 -0
  146. package/dist/extensions/pi-hermes-memory/tests/tools/session-search-tool.test.ts +345 -0
  147. package/dist/extensions/pi-hermes-memory/tests/tools/shared-output-view.test.ts +362 -0
  148. package/dist/extensions/pi-hermes-memory/tests/tools/skill-tool.test.ts +478 -0
  149. package/dist/extensions/pi-hermes-memory/tests/tools/tool-result-renderer-wiring.test.ts +40 -0
  150. package/dist/extensions/pi-hermes-memory/tsconfig.json +19 -0
  151. package/dist/extensions/pi-subagents/.oxlintrc.json +43 -0
  152. package/dist/extensions/pi-subagents/AGENTS.md +5 -0
  153. package/dist/extensions/pi-subagents/CHANGELOG.md +157 -0
  154. package/dist/extensions/pi-subagents/README.md +12 -6
  155. package/dist/extensions/pi-subagents/VISION.md +81 -0
  156. package/dist/extensions/pi-subagents/agents/claude-code-writer.md +15 -0
  157. package/dist/extensions/pi-subagents/agents/claude-code.md +15 -0
  158. package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +15 -0
  159. package/dist/extensions/pi-subagents/agents/codex-exec.md +15 -0
  160. package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +14 -0
  161. package/dist/extensions/pi-subagents/agents/cursor-agent.md +14 -0
  162. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -3
  163. package/dist/extensions/pi-subagents/docs/agents.md +488 -0
  164. package/dist/extensions/pi-subagents/docs/configuration.md +46 -9
  165. package/dist/extensions/pi-subagents/docs/extension-api.md +42 -3
  166. package/dist/extensions/pi-subagents/docs/models.md +27 -4
  167. package/dist/extensions/pi-subagents/docs/observability.md +10 -9
  168. package/dist/extensions/pi-subagents/docs/tool-reference.md +102 -6
  169. package/dist/extensions/pi-subagents/docs/workflows.md +135 -0
  170. package/dist/extensions/pi-subagents/package-lock.json +389 -2
  171. package/dist/extensions/pi-subagents/package.json +3 -1
  172. package/dist/extensions/pi-subagents/prompts/review-loop.md +2 -2
  173. package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +1 -1
  174. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +3 -1
  175. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +7 -3
  176. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  177. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  178. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -22
  179. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +4 -4
  180. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +9 -2
  181. package/dist/extensions/pi-subagents/src/agents/agents.ts +322 -75
  182. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +6 -0
  183. package/dist/extensions/pi-subagents/src/agents/runtime-agent-events.ts +70 -0
  184. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +27 -20
  185. package/dist/extensions/pi-subagents/src/api/agents.ts +10 -5
  186. package/dist/extensions/pi-subagents/src/api/background-work.ts +5 -1
  187. package/dist/extensions/pi-subagents/src/api/delegation.ts +0 -7
  188. package/dist/extensions/pi-subagents/src/api/preflight.ts +34 -12
  189. package/dist/extensions/pi-subagents/src/extension/config.ts +70 -0
  190. package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -3
  192. package/dist/extensions/pi-subagents/src/extension/index.ts +83 -30
  193. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +44 -15
  194. package/dist/extensions/pi-subagents/src/extension/rpc.ts +55 -19
  195. package/dist/extensions/pi-subagents/src/extension/schemas.ts +41 -15
  196. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -31
  197. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -1
  198. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -10
  199. package/dist/extensions/pi-subagents/src/inspectors/herdr/session-roots-codec.ts +42 -0
  200. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +51 -3
  201. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +2 -0
  202. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +5 -6
  203. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +77 -10
  204. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +176 -53
  205. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +19 -12
  206. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +29 -27
  207. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +20 -3
  208. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +23 -261
  209. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +73 -7
  210. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +8 -3
  211. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +74 -8
  212. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +37 -21
  213. package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +8 -8
  214. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +184 -9
  215. package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +45 -0
  216. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +28 -14
  217. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +38 -15
  218. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +1 -1
  219. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +1 -1
  220. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +50 -13
  221. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +93 -8
  222. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +10 -4
  223. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -14
  224. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +549 -377
  225. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +58 -10
  226. package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +1 -1
  227. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +26 -1
  228. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +23 -9
  229. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +9 -2
  230. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +2 -2
  231. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +204 -129
  232. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +9 -0
  233. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +23 -1
  234. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +773 -311
  235. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +140 -115
  236. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +119 -0
  237. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +463 -0
  238. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +19 -4
  239. package/dist/extensions/pi-subagents/src/runs/shared/child-launch-plan.ts +151 -0
  240. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +21 -7
  241. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +129 -0
  242. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +129 -0
  243. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +89 -0
  244. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +5 -4
  245. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +114 -0
  246. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +3 -3
  247. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +167 -0
  248. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +122 -0
  249. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +348 -55
  250. package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +5 -5
  251. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +230 -0
  252. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +105 -0
  253. package/dist/extensions/pi-subagents/src/runs/shared/launch-cwd.ts +16 -0
  254. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +2 -1
  255. package/dist/extensions/pi-subagents/src/runs/shared/mcp-config-sources.ts +422 -0
  256. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  257. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  258. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +69 -7
  259. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +47 -6
  260. package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +7 -2
  261. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +9 -3
  262. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +9 -5
  263. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +419 -7
  264. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +15 -1
  265. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +81 -8
  266. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +13 -0
  267. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +21 -1
  268. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +44 -4
  269. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  270. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +1 -1
  271. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  272. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +18 -0
  273. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +46 -0
  274. package/dist/extensions/pi-subagents/src/shared/extension-context.ts +24 -0
  275. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +21 -0
  276. package/dist/extensions/pi-subagents/src/shared/formatters.ts +18 -3
  277. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +5 -1
  278. package/dist/extensions/pi-subagents/src/shared/pruned-fork.ts +450 -0
  279. package/dist/extensions/pi-subagents/src/shared/session-file-trust.ts +19 -0
  280. package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +14 -3
  281. package/dist/extensions/pi-subagents/src/shared/settings.ts +19 -105
  282. package/dist/extensions/pi-subagents/src/shared/shortcuts.ts +17 -0
  283. package/dist/extensions/pi-subagents/src/shared/types.ts +359 -40
  284. package/dist/extensions/pi-subagents/src/shared/utils.ts +41 -84
  285. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +116 -0
  286. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -9
  287. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +0 -4
  288. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -2
  289. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +378 -88
  290. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +22 -11
  291. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +3 -0
  292. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +147 -79
  293. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +11 -5
  294. package/dist/extensions/pi-subagents/src/tui/fleet.ts +39 -18
  295. package/dist/extensions/pi-subagents/src/tui/render.ts +369 -46
  296. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +1 -1
  297. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +6 -3
  298. package/dist/extensions/pi-subagents/src/workflows/host-command.ts +230 -0
  299. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +740 -58
  300. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +121 -0
  301. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +270 -0
  302. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +195 -6
  303. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +246 -0
  304. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  305. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1114 -260
  306. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +34 -0
  307. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +150 -3
  308. package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +52 -0
  309. package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +55 -0
  310. package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +53 -0
  311. package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +57 -0
  312. package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +59 -0
  313. package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +62 -0
  314. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +57 -1
  315. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  316. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +20 -0
  317. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +4 -3
  318. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +2 -2
  319. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +54 -4
  320. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +482 -4
  321. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +157 -9
  322. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +5 -5
  323. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1508 -291
  324. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +423 -1
  325. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +8 -0
  326. package/dist/extensions/pi-subagents/test/support/cursor-smoke-inputs.ts +51 -0
  327. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  328. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +5 -5
  329. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +6 -6
  330. package/dist/extensions/pi-subagents/test/unit/abort-recovery.test.ts +152 -0
  331. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +48 -0
  332. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +184 -6
  333. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +66 -6
  334. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +77 -0
  335. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +18 -0
  336. package/dist/extensions/pi-subagents/test/unit/anti-slop-oxlint.test.ts +86 -0
  337. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +1 -1
  338. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +101 -1
  339. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +121 -0
  340. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +28 -0
  341. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +14 -7
  342. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +53 -0
  343. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +190 -0
  344. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +42 -0
  345. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +8 -4
  346. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +23 -0
  347. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +78 -1
  348. package/dist/extensions/pi-subagents/test/unit/child-launch-plan.test.ts +94 -0
  349. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +21 -0
  350. package/dist/extensions/pi-subagents/test/unit/child-session-name.test.ts +50 -0
  351. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +245 -0
  352. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +192 -0
  353. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +27 -0
  354. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +150 -0
  355. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +21 -0
  356. package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +259 -0
  357. package/dist/extensions/pi-subagents/test/unit/cursor-smoke-inputs.test.ts +77 -0
  358. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +1 -4
  359. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  360. package/dist/extensions/pi-subagents/test/unit/extension-context.test.ts +27 -0
  361. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +223 -0
  362. package/dist/extensions/pi-subagents/test/unit/fast-mode-extension.test.ts +27 -0
  363. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +208 -2
  364. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +29 -1
  365. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +110 -1
  366. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
  367. package/dist/extensions/pi-subagents/test/unit/formatters.test.ts +14 -0
  368. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +18 -9
  369. package/dist/extensions/pi-subagents/test/unit/herdr-session-roots-codec.test.ts +63 -0
  370. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +2 -2
  371. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +132 -0
  372. package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +156 -0
  373. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +98 -0
  374. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +178 -26
  375. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +2 -1
  376. package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +21 -0
  377. package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +138 -0
  378. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  379. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +91 -0
  380. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +72 -7
  381. package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +21 -0
  382. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +1 -0
  383. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +20 -2
  384. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +129 -1
  385. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +2 -2
  386. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +4 -0
  387. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +358 -0
  388. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +574 -21
  389. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +109 -1
  390. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +71 -1
  391. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +7 -7
  392. package/dist/extensions/pi-subagents/test/unit/pruned-fork.test.ts +214 -0
  393. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +29 -8
  394. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +53 -0
  395. package/dist/extensions/pi-subagents/test/unit/result-delivery-ownership.test.ts +34 -0
  396. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +101 -3
  397. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +1 -0
  398. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +10 -8
  399. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +71 -1
  400. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +41 -7
  401. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +136 -2
  402. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +137 -0
  403. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +33 -12
  404. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +504 -1
  405. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +8 -0
  406. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  407. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +12 -1
  408. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +3 -5
  409. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +5 -6
  410. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +0 -8
  411. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +5 -0
  412. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +157 -2
  413. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +1 -1
  414. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +63 -4
  415. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +27 -23
  416. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +44 -0
  417. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -0
  418. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +7 -5
  419. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +106 -0
  420. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +484 -9
  421. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +38 -4
  422. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +155 -0
  423. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +318 -0
  424. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +365 -0
  425. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +349 -0
  426. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +21 -0
  427. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +2 -7
  428. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/index.ts +13 -0
  429. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/rules/no-service-constructor-imports.ts +52 -0
  430. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/index.ts +41 -0
  431. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts +77 -0
  432. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts +49 -0
  433. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-known-value-widening.ts +247 -0
  434. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-module-mocking.ts +91 -0
  435. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-object-parameters.ts +126 -0
  436. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-apply.ts +28 -0
  437. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-get.ts +28 -0
  438. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-runtime-typeof.ts +67 -0
  439. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts +39 -0
  440. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-parameters.ts +83 -0
  441. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-returns.ts +115 -0
  442. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts +72 -0
  443. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts +134 -0
  444. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts +366 -0
  445. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts +62 -0
  446. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/dictionary-types.ts +502 -0
  447. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts +60 -0
  448. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/reflect-method.ts +35 -0
  449. package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +1 -1
  450. package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +2 -2
  451. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +1 -1
  452. package/dist/modes/interactive/interactive-mode.js +7 -7
  453. package/dist/skills/workflow/SKILL.md +65 -50
  454. package/package.json +112 -110
  455. package/dist/extensions/enable-readonly-tools.test.ts +0 -63
  456. package/dist/extensions/enable-readonly-tools.ts +0 -24
  457. package/dist/extensions/model-prompt-injector/config.json +0 -16
  458. package/dist/extensions/model-prompt-injector/index.test.ts +0 -102
  459. package/dist/extensions/model-prompt-injector/index.ts +0 -148
  460. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +0 -98
  461. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +0 -250
@@ -0,0 +1,905 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { spawn } from "node:child_process";
3
+ import * as fs from "node:fs/promises";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import { describe, it } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import {
9
+ buildChildPiPromptArgs,
10
+ detectAuthAdapterExtensionPaths,
11
+ execChildPrompt,
12
+ inheritedExtensionArgs,
13
+ resolveChildPiInvocation,
14
+ resolveWatchedChildPiInvocation,
15
+ } from "../../src/handlers/pi-child-process.js";
16
+
17
+ function logicalChildArgs(call: { cmd: string; args: string[] }): string[] {
18
+ const underlying = { command: call.args[3], args: call.args.slice(4) };
19
+ const expected = resolveWatchedChildPiInvocation(underlying, 30000, call.args[2]);
20
+ assert.deepStrictEqual(call, { cmd: expected.command, args: expected.args });
21
+ return underlying.command === "selesai" ? underlying.args : underlying.args.slice(1);
22
+ }
23
+
24
+ // Compute the expected OWN_EXTENSION_PATH same logic as pi-child-process.ts
25
+ const OWN_EXTENSION_PATH = path.resolve(
26
+ path.dirname(fileURLToPath(import.meta.url)),
27
+ "../../src/index.ts",
28
+ );
29
+
30
+ // Adapter detection scans the real sibling/agent node_modules trees, so a
31
+ // contributor with a provider auth adapter installed gets extra -e args in
32
+ // every child invocation. Derive them instead of assuming a bare environment.
33
+ const DETECTED_ADAPTER_ARGS = detectAuthAdapterExtensionPaths().flatMap((p) => ["-e", p]);
34
+
35
+ const EXT_ARGS = ["--no-extensions", "-e", OWN_EXTENSION_PATH, ...DETECTED_ADAPTER_ARGS];
36
+
37
+ describe("inheritedExtensionArgs", () => {
38
+ it("captures explicit -e and --extension parent args", () => {
39
+ assert.deepStrictEqual(
40
+ inheritedExtensionArgs(["-e", "src/index.ts", "--extension", "/tmp/other.ts"]),
41
+ ["-e", "src/index.ts", "--extension", "/tmp/other.ts"],
42
+ );
43
+ });
44
+
45
+ it("captures --extension=... parent args", () => {
46
+ assert.deepStrictEqual(
47
+ inheritedExtensionArgs(["--extension=src/index.ts"]),
48
+ ["--extension=src/index.ts"],
49
+ );
50
+ });
51
+ });
52
+
53
+ describe("detectAuthAdapterExtensionPaths", () => {
54
+ it("returns empty when no sibling packages exist under the root", async () => {
55
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-empty-"));
56
+ try {
57
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), []);
58
+ } finally {
59
+ await fs.rm(root, { recursive: true, force: true });
60
+ }
61
+ });
62
+
63
+ it("detects a convention-named adapter via its package.json manifest (xai-oauth-adapter)", async () => {
64
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-xai-"));
65
+ const adapterDir = path.join(root, "xai-oauth-adapter");
66
+ const adapterPath = path.join(adapterDir, "extensions", "index.ts");
67
+ await fs.mkdir(path.dirname(adapterPath), { recursive: true });
68
+ await fs.writeFile(adapterPath, "export default () => {};");
69
+ await fs.writeFile(
70
+ path.join(adapterDir, "package.json"),
71
+ JSON.stringify({ name: "xai-oauth-adapter", pi: { extensions: ["extensions/index.ts"] } }),
72
+ );
73
+ try {
74
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), [adapterPath]);
75
+ } finally {
76
+ await fs.rm(root, { recursive: true, force: true });
77
+ }
78
+ });
79
+
80
+ it("detects a scoped convention-named adapter one level under @scope", async () => {
81
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-scoped-"));
82
+ const adapterDir = path.join(root, "@xai", "pi-oauth-adapter");
83
+ const adapterPath = path.join(adapterDir, "entry.ts");
84
+ await fs.mkdir(path.dirname(adapterPath), { recursive: true });
85
+ await fs.writeFile(adapterPath, "export default () => {};");
86
+ await fs.writeFile(
87
+ path.join(adapterDir, "package.json"),
88
+ JSON.stringify({ name: "@xai/pi-oauth-adapter", pi: { extensions: ["entry.ts"] } }),
89
+ );
90
+ try {
91
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), [adapterPath]);
92
+ } finally {
93
+ await fs.rm(root, { recursive: true, force: true });
94
+ }
95
+ });
96
+
97
+ it("ignores packages that do not match the auth-adapter naming convention", async () => {
98
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-nonmatch-"));
99
+ const pkgDir = path.join(root, "some-other-package");
100
+ const extPath = path.join(pkgDir, "extensions", "index.ts");
101
+ await fs.mkdir(path.dirname(extPath), { recursive: true });
102
+ await fs.writeFile(extPath, "export default () => {};");
103
+ await fs.writeFile(
104
+ path.join(pkgDir, "package.json"),
105
+ JSON.stringify({ name: "some-other-package", pi: { extensions: ["extensions/index.ts"] } }),
106
+ );
107
+ try {
108
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), []);
109
+ } finally {
110
+ await fs.rm(root, { recursive: true, force: true });
111
+ }
112
+ });
113
+
114
+ it("returns empty for a matching package name without package.json", async () => {
115
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-nopkg-"));
116
+ await fs.mkdir(path.join(root, "my-oauth-adapter"), { recursive: true });
117
+ try {
118
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), []);
119
+ } finally {
120
+ await fs.rm(root, { recursive: true, force: true });
121
+ }
122
+ });
123
+
124
+ it("returns empty when package.json lacks pi.extensions", async () => {
125
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-nopi-"));
126
+ const adapterDir = path.join(root, "my-auth-adapter");
127
+ await fs.mkdir(adapterDir, { recursive: true });
128
+ await fs.writeFile(path.join(adapterDir, "package.json"), JSON.stringify({ name: "my-auth-adapter" }));
129
+ try {
130
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), []);
131
+ } finally {
132
+ await fs.rm(root, { recursive: true, force: true });
133
+ }
134
+ });
135
+
136
+ it("filters out manifest entries whose files do not exist on disk", async () => {
137
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-missing-"));
138
+ const adapterDir = path.join(root, "pi-oauth-adapter");
139
+ const presentPath = path.join(adapterDir, "extensions", "present.ts");
140
+ await fs.mkdir(path.dirname(presentPath), { recursive: true });
141
+ await fs.writeFile(presentPath, "export default () => {};");
142
+ await fs.writeFile(
143
+ path.join(adapterDir, "package.json"),
144
+ JSON.stringify({
145
+ name: "pi-oauth-adapter",
146
+ pi: { extensions: ["extensions/missing.ts", "extensions/present.ts"] },
147
+ }),
148
+ );
149
+ try {
150
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), [presentPath]);
151
+ } finally {
152
+ await fs.rm(root, { recursive: true, force: true });
153
+ }
154
+ });
155
+
156
+ it("collects extension entries from every matching package under one root", async () => {
157
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-multi-"));
158
+ const firstDir = path.join(root, "alpha-oauth-adapter");
159
+ const firstPath = path.join(firstDir, "a.ts");
160
+ const secondDir = path.join(root, "beta-auth-adapter");
161
+ const secondPath = path.join(secondDir, "b.ts");
162
+ await fs.mkdir(path.dirname(firstPath), { recursive: true });
163
+ await fs.writeFile(firstPath, "export default () => {};");
164
+ await fs.writeFile(
165
+ path.join(firstDir, "package.json"),
166
+ JSON.stringify({ name: "alpha-oauth-adapter", pi: { extensions: ["a.ts"] } }),
167
+ );
168
+ await fs.mkdir(path.dirname(secondPath), { recursive: true });
169
+ await fs.writeFile(secondPath, "export default () => {};");
170
+ await fs.writeFile(
171
+ path.join(secondDir, "package.json"),
172
+ JSON.stringify({ name: "beta-auth-adapter", pi: { extensions: ["b.ts"] } }),
173
+ );
174
+ try {
175
+ const detected = detectAuthAdapterExtensionPaths([root]);
176
+ assert.equal(detected.length, 2);
177
+ assert.ok(detected.includes(firstPath));
178
+ assert.ok(detected.includes(secondPath));
179
+ } finally {
180
+ await fs.rm(root, { recursive: true, force: true });
181
+ }
182
+ });
183
+
184
+ it("merges matches from multiple roots passed in the roots array", async () => {
185
+ const rootA = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-roota-"));
186
+ const rootB = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-rootb-"));
187
+ const pathA = path.join(rootA, "one-oauth-adapter", "one.ts");
188
+ const pathB = path.join(rootB, "two-oauth-adapter", "two.ts");
189
+ await fs.mkdir(path.dirname(pathA), { recursive: true });
190
+ await fs.writeFile(pathA, "export default () => {};");
191
+ await fs.writeFile(
192
+ path.join(rootA, "one-oauth-adapter", "package.json"),
193
+ JSON.stringify({ name: "one-oauth-adapter", pi: { extensions: ["one.ts"] } }),
194
+ );
195
+ await fs.mkdir(path.dirname(pathB), { recursive: true });
196
+ await fs.writeFile(pathB, "export default () => {};");
197
+ await fs.writeFile(
198
+ path.join(rootB, "two-oauth-adapter", "package.json"),
199
+ JSON.stringify({ name: "two-oauth-adapter", pi: { extensions: ["two.ts"] } }),
200
+ );
201
+ try {
202
+ const detected = detectAuthAdapterExtensionPaths([rootA, rootB]);
203
+ assert.equal(detected.length, 2);
204
+ assert.ok(detected.includes(pathA));
205
+ assert.ok(detected.includes(pathB));
206
+ } finally {
207
+ await fs.rm(rootA, { recursive: true, force: true });
208
+ await fs.rm(rootB, { recursive: true, force: true });
209
+ }
210
+ });
211
+
212
+ it("detects the allowlisted pi-claude-auth package", async () => {
213
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-claude-"));
214
+ const adapterDir = path.join(root, "pi-claude-auth");
215
+ const adapterPath = path.join(adapterDir, "src", "index.ts");
216
+ await fs.mkdir(path.dirname(adapterPath), { recursive: true });
217
+ await fs.writeFile(adapterPath, "export default () => {};");
218
+ await fs.writeFile(
219
+ path.join(adapterDir, "package.json"),
220
+ JSON.stringify({ name: "pi-claude-auth", pi: { extensions: ["src/index.ts"] } }),
221
+ );
222
+ try {
223
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), [adapterPath]);
224
+ } finally {
225
+ await fs.rm(root, { recursive: true, force: true });
226
+ }
227
+ });
228
+
229
+ it("detects the allowlisted scoped @gotgenes/pi-anthropic-auth package", async () => {
230
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-anthropic-"));
231
+ const adapterDir = path.join(root, "@gotgenes", "pi-anthropic-auth");
232
+ const adapterPath = path.join(adapterDir, "src", "index.ts");
233
+ await fs.mkdir(path.dirname(adapterPath), { recursive: true });
234
+ await fs.writeFile(adapterPath, "export default () => {};");
235
+ await fs.writeFile(
236
+ path.join(adapterDir, "package.json"),
237
+ JSON.stringify({
238
+ name: "@gotgenes/pi-anthropic-auth",
239
+ pi: { extensions: ["src/index.ts"] },
240
+ }),
241
+ );
242
+ try {
243
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), [adapterPath]);
244
+ } finally {
245
+ await fs.rm(root, { recursive: true, force: true });
246
+ }
247
+ });
248
+
249
+ it("ignores a suffixless auth package that is not allowlisted", async () => {
250
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "pi-auth-detect-untrusted-"));
251
+ const pkgDir = path.join(root, "pi-vault-auth");
252
+ const extPath = path.join(pkgDir, "index.ts");
253
+ await fs.mkdir(pkgDir, { recursive: true });
254
+ await fs.writeFile(extPath, "export default () => {};");
255
+ await fs.writeFile(
256
+ path.join(pkgDir, "package.json"),
257
+ JSON.stringify({ name: "pi-vault-auth", pi: { extensions: ["index.ts"] } }),
258
+ );
259
+ try {
260
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([root]), []);
261
+ } finally {
262
+ await fs.rm(root, { recursive: true, force: true });
263
+ }
264
+ });
265
+ });
266
+
267
+
268
+ describe("buildChildPiPromptArgs", () => {
269
+ it("uses --no-extensions and only passes hermes-memory extension", () => {
270
+ assert.deepStrictEqual(
271
+ buildChildPiPromptArgs("hello", {}, []),
272
+ ["-p", "--no-session", ...EXT_ARGS, "hello"],
273
+ );
274
+ });
275
+
276
+ it("adds a model override and defaults thinking to off", () => {
277
+ assert.deepStrictEqual(
278
+ buildChildPiPromptArgs("hello", { llmModelOverride: "openrouter/deepseek/deepseek-v4-flash" }, []),
279
+ ["-p", "--no-session", "--model", "openrouter/deepseek/deepseek-v4-flash", "--thinking", "off", ...EXT_ARGS, "hello"],
280
+ );
281
+ });
282
+ it("inherits the active provider/model when no override is configured", () => {
283
+ assert.deepStrictEqual(
284
+ buildChildPiPromptArgs("hello", {}, [], { provider: "local-llama", id: "local-9b" }),
285
+ ["-p", "--no-session", "--model", "local-llama/local-9b", ...EXT_ARGS, "hello"],
286
+ );
287
+ });
288
+
289
+ it("prefers the configured model override over the active session model", () => {
290
+ assert.deepStrictEqual(
291
+ buildChildPiPromptArgs(
292
+ "hello",
293
+ { llmModelOverride: "openrouter/deepseek/deepseek-v4-flash" },
294
+ [],
295
+ { provider: "local-llama", id: "local-9b" },
296
+ ),
297
+ ["-p", "--no-session", "--model", "openrouter/deepseek/deepseek-v4-flash", "--thinking", "off", ...EXT_ARGS, "hello"],
298
+ );
299
+ });
300
+
301
+
302
+ it("allows thinking overrides without a model override", () => {
303
+ assert.deepStrictEqual(
304
+ buildChildPiPromptArgs("hello", { llmThinkingOverride: "low" }, []),
305
+ ["-p", "--no-session", "--thinking", "low", ...EXT_ARGS, "hello"],
306
+ );
307
+ });
308
+
309
+ it("ignores missing inherited extension paths", () => {
310
+ assert.deepStrictEqual(
311
+ buildChildPiPromptArgs("hello", {}, ["-e", "src/index.ts"]),
312
+ ["-p", "--no-session", ...EXT_ARGS, "hello"],
313
+ );
314
+ });
315
+
316
+ it("passes configured extension sources through Pi's -e resolver and excludes inherited extensions", async () => {
317
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-child-extensions-"));
318
+ const configured = path.join(dir, "configured.ts");
319
+ const inherited = path.join(dir, "inherited.ts");
320
+ await fs.writeFile(configured, "export default () => {};");
321
+ await fs.writeFile(inherited, "export default () => {};");
322
+ try {
323
+ assert.deepStrictEqual(
324
+ buildChildPiPromptArgs("hello", {
325
+ childExtensionPaths: [
326
+ configured,
327
+ configured,
328
+ "~/.selesai/agent/extensions/provider.ts",
329
+ "./relative-provider.ts",
330
+ "git:github.com/example/provider-extension@v1",
331
+ "npm:@example/provider-extension@1.0.0",
332
+ OWN_EXTENSION_PATH,
333
+ ],
334
+ }, ["-e", inherited, `--extension=${configured}`]),
335
+ [
336
+ "-p", "--no-session", "--no-extensions",
337
+ "-e", OWN_EXTENSION_PATH,
338
+ "-e", configured,
339
+ "-e", "~/.selesai/agent/extensions/provider.ts",
340
+ "-e", "./relative-provider.ts",
341
+ "-e", "git:github.com/example/provider-extension@v1",
342
+ "-e", "npm:@example/provider-extension@1.0.0",
343
+ ...DETECTED_ADAPTER_ARGS,
344
+ "hello",
345
+ ],
346
+ );
347
+ } finally {
348
+ await fs.rm(dir, { recursive: true, force: true });
349
+ }
350
+ });
351
+
352
+ it("detects a sibling pi-claude-oauth-adapter extension via its package.json manifest", async () => {
353
+ const nodeModules = await fs.mkdtemp(path.join(os.tmpdir(), "pi-node-modules-"));
354
+ const adapterDir = path.join(nodeModules, "pi-claude-oauth-adapter");
355
+ const adapterPath = path.join(adapterDir, "extensions", "index.ts");
356
+ await fs.mkdir(path.dirname(adapterPath), { recursive: true });
357
+ await fs.writeFile(adapterPath, "export default () => {};");
358
+ await fs.writeFile(
359
+ path.join(adapterDir, "package.json"),
360
+ JSON.stringify({ name: "pi-claude-oauth-adapter", pi: { extensions: ["extensions/index.ts"] } }),
361
+ );
362
+ try {
363
+ assert.deepStrictEqual(detectAuthAdapterExtensionPaths([nodeModules]), [adapterPath]);
364
+ } finally {
365
+ await fs.rm(nodeModules, { recursive: true, force: true });
366
+ }
367
+ });
368
+ });
369
+
370
+ describe("resolveChildPiInvocation", () => {
371
+ it("keeps non-Windows child pi invocations unchanged", () => {
372
+ const args = ["-p", "--no-session", "hello"];
373
+
374
+ assert.deepStrictEqual(
375
+ resolveChildPiInvocation(args, { platform: "linux" }),
376
+ { command: "selesai", args },
377
+ );
378
+ });
379
+
380
+ it("runs node.exe with cli.js first on Windows when a Pi CLI path is available", () => {
381
+ assert.deepStrictEqual(
382
+ resolveChildPiInvocation(["-p", "--no-session", "hello"], {
383
+ platform: "win32",
384
+ execPath: "C:\\Program Files\\nodejs\\node.exe",
385
+ piCliPath: "C:\\Users\\me\\AppData\\Roaming\\npm\\node_modules\\@selesai\\code\\dist\\cli.js",
386
+ }),
387
+ {
388
+ command: "C:\\Program Files\\nodejs\\node.exe",
389
+ args: [
390
+ "C:\\Users\\me\\AppData\\Roaming\\npm\\node_modules\\@selesai\\code\\dist\\cli.js",
391
+ "-p",
392
+ "--no-session",
393
+ "hello",
394
+ ],
395
+ },
396
+ );
397
+ });
398
+
399
+ it("resolves a standard Windows command shim to its adjacent cli.js", async () => {
400
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), "pi-windows-shim-"));
401
+ const binDirectory = path.join(directory, "bin");
402
+ const cliPath = path.join(
403
+ binDirectory,
404
+ "node_modules",
405
+ "@selesai",
406
+ "code",
407
+ "dist",
408
+ "cli.js",
409
+ );
410
+ const args = ["-p", "--no-session", "hello"];
411
+ const originalPath = process.env.PATH;
412
+
413
+ await fs.mkdir(path.dirname(cliPath), { recursive: true });
414
+ await fs.writeFile(path.join(binDirectory, "selesai.cmd"), "@echo off\r\n");
415
+ await fs.writeFile(cliPath, "");
416
+ process.env.PATH = `${binDirectory};C:\\Windows\\System32`;
417
+ try {
418
+ assert.deepStrictEqual(
419
+ resolveChildPiInvocation(args, {
420
+ platform: "win32",
421
+ execPath: "C:\\Program Files\\nodejs\\node.exe",
422
+ piCliPath: null,
423
+ }),
424
+ {
425
+ command: "C:\\Program Files\\nodejs\\node.exe",
426
+ args: [cliPath, ...args],
427
+ },
428
+ );
429
+ } finally {
430
+ if (originalPath === undefined) delete process.env.PATH;
431
+ else process.env.PATH = originalPath;
432
+ await fs.rm(directory, { recursive: true, force: true });
433
+ }
434
+ });
435
+ });
436
+
437
+ describe("execChildPrompt", () => {
438
+ it("runs child Pi behind a hard process-tree watchdog", async () => {
439
+ const calls: Array<{ cmd: string; args: string[]; timeout?: number }> = [];
440
+ await execChildPrompt({
441
+ exec: async (cmd: string, args: string[], options: { timeout?: number }) => {
442
+ calls.push({ cmd, args, timeout: options.timeout });
443
+ return { code: 0, stdout: "ok", stderr: "", killed: false };
444
+ },
445
+ } as any, "bounded child", {}, { timeoutMs: 30000 });
446
+
447
+ assert.equal(calls.length, 1);
448
+ assert.equal(calls[0].cmd, process.execPath);
449
+ assert.match(calls[0].args[0].replaceAll("\\", "/"), /child-process-watchdog\.mjs$/);
450
+ assert.equal(calls[0].args[1], "30000");
451
+ assert.match(calls[0].args[2], /cancel$/);
452
+ assert.equal(calls[0].args[3], "selesai");
453
+ assert.equal(calls[0].timeout, 35000);
454
+ });
455
+
456
+ it("uses the watchdog marker for cancellation without aborting its process", async () => {
457
+ const abortController = new AbortController();
458
+ let cancelPath = "";
459
+ const result = await execChildPrompt({
460
+ exec: async (_cmd: string, args: string[], options: { signal?: AbortSignal }) => {
461
+ cancelPath = args[2];
462
+ assert.equal(options.signal, undefined);
463
+ abortController.abort();
464
+ const deadline = Date.now() + 1000;
465
+ while (Date.now() < deadline) {
466
+ try {
467
+ await fs.access(cancelPath);
468
+ return { code: 143, stderr: "child cancelled" };
469
+ } catch {
470
+ await new Promise((resolve) => setTimeout(resolve, 10));
471
+ }
472
+ }
473
+ assert.fail("abort did not notify the watchdog");
474
+ },
475
+ } as any, "cancel child", {}, {
476
+ signal: abortController.signal,
477
+ timeoutMs: 30000,
478
+ });
479
+
480
+ assert.equal(result.code, 143);
481
+ await assert.rejects(fs.access(cancelPath), { code: "ENOENT" });
482
+ });
483
+
484
+ it("writes cancellation before launching an already-aborted child", async () => {
485
+ const abortController = new AbortController();
486
+ abortController.abort();
487
+ let cancelPath = "";
488
+ await execChildPrompt({
489
+ exec: async (_cmd: string, args: string[], options: { signal?: AbortSignal }) => {
490
+ cancelPath = args[2];
491
+ assert.equal(options.signal, undefined);
492
+ await fs.access(cancelPath);
493
+ return { code: 143, stderr: "child cancelled" };
494
+ },
495
+ } as any, "cancel before launch", {}, {
496
+ signal: abortController.signal,
497
+ timeoutMs: 30000,
498
+ });
499
+
500
+ await assert.rejects(fs.access(cancelPath), { code: "ENOENT" });
501
+ });
502
+
503
+ it("forwards explicit cwd and timeout while cancellation stays with the watchdog", async () => {
504
+ const signal = new AbortController().signal;
505
+ let received: { cwd?: string; signal?: AbortSignal; timeout?: number } | undefined;
506
+ await execChildPrompt({
507
+ exec: async (_cmd: string, _args: string[], options: typeof received) => {
508
+ received = options;
509
+ return { code: 0, stdout: "ok", stderr: "" };
510
+ },
511
+ } as any, "bounded child", {}, {
512
+ cwd: "/tmp/session",
513
+ signal,
514
+ timeoutMs: 30000,
515
+ });
516
+
517
+ assert.deepStrictEqual(received, { cwd: "/tmp/session", timeout: 35000 });
518
+ });
519
+
520
+
521
+ it("hard-kills a child that ignores graceful timeout termination", async () => {
522
+ const watchdogPath = fileURLToPath(
523
+ new URL("../../src/handlers/child-process-watchdog.mjs", import.meta.url),
524
+ );
525
+ const startedAt = Date.now();
526
+ const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => {
527
+ const child = spawn(process.execPath, [
528
+ watchdogPath,
529
+ "100",
530
+ "-",
531
+ process.execPath,
532
+ "-e",
533
+ "process.on('SIGTERM',()=>{});setInterval(()=>{},1000)",
534
+ ], { stdio: ["ignore", "ignore", "pipe"] });
535
+ let stderr = "";
536
+ child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
537
+ child.on("close", (code) => resolve({ code, stderr }));
538
+ });
539
+
540
+ assert.equal(result.code, 124);
541
+ assert.match(result.stderr, /timed out after 100ms/i);
542
+ assert.ok(Date.now() - startedAt < 3000, "watchdog must enforce a bounded hard stop");
543
+ });
544
+
545
+ it("terminates the child tree when its cancellation marker appears", async () => {
546
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), "pi-watchdog-cancel-"));
547
+ const cancelPath = path.join(directory, "cancel");
548
+ const watchdogPath = fileURLToPath(
549
+ new URL("../../src/handlers/child-process-watchdog.mjs", import.meta.url),
550
+ );
551
+ const startedAt = Date.now();
552
+ try {
553
+ const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => {
554
+ const child = spawn(process.execPath, [
555
+ watchdogPath,
556
+ "10000",
557
+ cancelPath,
558
+ process.execPath,
559
+ "-e",
560
+ "process.on('SIGTERM',()=>{});setInterval(()=>{},1000)",
561
+ ], { stdio: ["ignore", "ignore", "pipe"] });
562
+ let stderr = "";
563
+ child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
564
+ child.on("close", (code) => resolve({ code, stderr }));
565
+ setTimeout(() => void fs.writeFile(cancelPath, ""), 100);
566
+ });
567
+
568
+ assert.equal(result.code, 143);
569
+ assert.match(result.stderr, /child cancellation requested/i);
570
+ assert.ok(Date.now() - startedAt < 3000, "watchdog cancellation must be bounded");
571
+ } finally {
572
+ await fs.rm(directory, { recursive: true, force: true });
573
+ }
574
+ });
575
+
576
+ it("hard-kills descendants in the timed-out child process group", {
577
+ skip: process.platform === "win32" ? "uses taskkill /T on Windows" : false,
578
+ }, async () => {
579
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), "pi-watchdog-tree-"));
580
+ const pidPath = path.join(directory, "descendant.pid");
581
+ const watchdogPath = fileURLToPath(
582
+ new URL("../../src/handlers/child-process-watchdog.mjs", import.meta.url),
583
+ );
584
+ const descendant = "process.on('SIGTERM',()=>{});setInterval(()=>{},1000)";
585
+ const parent = [
586
+ "const {spawn}=require('node:child_process');",
587
+ "const fs=require('node:fs');",
588
+ `const child=spawn(process.execPath,['-e',${JSON.stringify(descendant)}],{stdio:'ignore'});`,
589
+ `fs.writeFileSync(${JSON.stringify(pidPath)},String(child.pid));`,
590
+ "process.on('SIGTERM',()=>{});setInterval(()=>{},1000);",
591
+ ].join("");
592
+
593
+ try {
594
+ const code = await new Promise<number | null>((resolve) => {
595
+ const child = spawn(process.execPath, [
596
+ watchdogPath,
597
+ "100",
598
+ "-",
599
+ process.execPath,
600
+ "-e",
601
+ parent,
602
+ ], { stdio: "ignore" });
603
+ child.on("close", resolve);
604
+ });
605
+ assert.equal(code, 124);
606
+ const descendantPid = Number(await fs.readFile(pidPath, "utf-8"));
607
+ const deadline = Date.now() + 1000;
608
+ while (Date.now() < deadline) {
609
+ try {
610
+ process.kill(descendantPid, 0);
611
+ await new Promise((resolve) => setTimeout(resolve, 20));
612
+ } catch (error) {
613
+ if ((error as NodeJS.ErrnoException).code === "ESRCH") return;
614
+ throw error;
615
+ }
616
+ }
617
+ assert.fail("watchdog left a descendant process alive");
618
+ } finally {
619
+ await fs.rm(directory, { recursive: true, force: true });
620
+ }
621
+ });
622
+
623
+ it("keeps a sensitive prompt out of argv and removes its mode-0600 temporary file", async () => {
624
+ const secret = "PRIVATE-MEMORY-CONTENT";
625
+ let promptPath = "";
626
+ const pi = {
627
+ exec: async (_cmd: string, args: string[]) => {
628
+ assert.ok(args.every((arg) => !arg.includes(secret)), "child argv must not contain prompt content");
629
+ const promptArg = args.at(-1)!;
630
+ assert.match(promptArg, /^@/);
631
+ promptPath = promptArg.slice(1);
632
+ assert.equal(await fs.readFile(promptPath, "utf-8"), secret);
633
+ assert.equal((await fs.stat(promptPath)).mode & 0o777, 0o600);
634
+ return { code: 0, stdout: "ok", stderr: "" };
635
+ },
636
+ };
637
+
638
+ const result = await execChildPrompt(pi as any, secret, {}, { timeoutMs: 30000 });
639
+
640
+ assert.equal(result.code, 0);
641
+ await assert.rejects(fs.access(promptPath), { code: "ENOENT" });
642
+ await assert.rejects(fs.access(path.dirname(promptPath)), { code: "ENOENT" });
643
+ });
644
+
645
+ it("removes the temporary prompt file when child execution throws", async () => {
646
+ let promptPath = "";
647
+ const pi = {
648
+ exec: async (_cmd: string, args: string[]) => {
649
+ promptPath = args.at(-1)!.slice(1);
650
+ assert.equal(await fs.readFile(promptPath, "utf-8"), "secret failure prompt");
651
+ throw new Error("child failed");
652
+ },
653
+ };
654
+
655
+ await assert.rejects(
656
+ execChildPrompt(pi as any, "secret failure prompt", {}, { timeoutMs: 30000 }),
657
+ /child failed/,
658
+ );
659
+
660
+ assert.ok(promptPath.startsWith(os.tmpdir()));
661
+ await assert.rejects(fs.access(promptPath), { code: "ENOENT" });
662
+ });
663
+
664
+ it("returns a successful child result when temporary cleanup fails", async () => {
665
+ let cleanupCalls = 0;
666
+ let promptDirectory = "";
667
+ const pi = {
668
+ exec: async (_cmd: string, args: string[]) => {
669
+ promptDirectory = path.dirname(args.at(-1)!.slice(1));
670
+ return { code: 0, stdout: "completed", stderr: "" };
671
+ },
672
+ };
673
+
674
+ try {
675
+ const result = await execChildPrompt(
676
+ pi as any,
677
+ "cleanup failure prompt",
678
+ {},
679
+ { timeoutMs: 30000 },
680
+ {
681
+ removeTemporaryDirectory: async () => {
682
+ cleanupCalls++;
683
+ throw new Error("cleanup denied");
684
+ },
685
+ },
686
+ );
687
+
688
+ assert.equal(result.code, 0);
689
+ assert.equal(result.stdout, "completed");
690
+ assert.equal(cleanupCalls, 1);
691
+ } finally {
692
+ if (promptDirectory) await fs.rm(promptDirectory, { recursive: true, force: true });
693
+ }
694
+ });
695
+
696
+ it("passes configured auth adapters to both override attempts", async () => {
697
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-child-auth-"));
698
+ const adapterPath = path.join(dir, "adapter.ts");
699
+ await fs.writeFile(adapterPath, "export default () => {};");
700
+ const calls: string[][] = [];
701
+ const pi = {
702
+ exec: async (_cmd: string, args: string[]) => {
703
+ calls.push(args);
704
+ return calls.length === 1
705
+ ? { code: 1, stdout: "", stderr: "model not found" }
706
+ : { code: 0, stdout: "ok", stderr: "" };
707
+ },
708
+ };
709
+ try {
710
+ await execChildPrompt(pi as any, "hello", {
711
+ llmModelOverride: "missing/model",
712
+ childExtensionPaths: [adapterPath],
713
+ }, { timeoutMs: 30000, retryWithoutOverrides: true });
714
+
715
+ assert.equal(calls.length, 2);
716
+ for (const args of calls) {
717
+ const index = args.indexOf(adapterPath);
718
+ assert.ok(index > 0);
719
+ assert.equal(args[index - 1], "-e");
720
+ }
721
+ } finally {
722
+ await fs.rm(dir, { recursive: true, force: true });
723
+ }
724
+ });
725
+
726
+ it("excludes unrelated inherited extensions from primary and retry attempts", async () => {
727
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-child-untrusted-"));
728
+ const inherited = path.join(dir, "unrelated-extension.ts");
729
+ await fs.writeFile(inherited, "export default () => {};");
730
+ const originalArgv = process.argv;
731
+ const calls: string[][] = [];
732
+ process.argv = [originalArgv[0], originalArgv[1], "-e", inherited];
733
+ try {
734
+ await execChildPrompt({
735
+ exec: async (_cmd: string, args: string[]) => {
736
+ calls.push(args);
737
+ return calls.length === 1
738
+ ? { code: 1, stderr: "model not found" }
739
+ : { code: 0, stdout: "ok" };
740
+ },
741
+ } as any, "private review", {
742
+ llmModelOverride: "missing/model",
743
+ }, { timeoutMs: 30000, retryWithoutOverrides: true });
744
+
745
+ assert.equal(calls.length, 2);
746
+ for (const args of calls) {
747
+ assert.equal(args.includes(inherited), false);
748
+ assert.ok(args.includes("--no-extensions"));
749
+ }
750
+ } finally {
751
+ process.argv = originalArgv;
752
+ await fs.rm(dir, { recursive: true, force: true });
753
+ }
754
+ });
755
+
756
+ it("retries once without overrides when requested and the override subprocess fails for model resolution reasons", async () => {
757
+ const calls: Array<{ cmd: string; args: string[] }> = [];
758
+ const pi = {
759
+ exec: async (cmd: string, args: string[]) => {
760
+ calls.push({ cmd, args });
761
+ if (calls.length === 1) {
762
+ return { code: 1, stdout: "", stderr: "model not found" };
763
+ }
764
+ return { code: 0, stdout: "ok", stderr: "" };
765
+ },
766
+ };
767
+
768
+ const result = await execChildPrompt(pi as any, "hello", {
769
+ llmModelOverride: "openrouter/deepseek/deepseek-v4-flash",
770
+ }, {
771
+ timeoutMs: 30000,
772
+ retryWithoutOverrides: true,
773
+ model: { provider: "local-llama", id: "local-9b" },
774
+ });
775
+
776
+ assert.strictEqual(result.code, 0);
777
+ const logicalCalls = calls.map(logicalChildArgs);
778
+ const promptReference = logicalCalls[0].at(-1)!;
779
+ assert.match(promptReference, /^@/);
780
+ assert.deepStrictEqual(logicalCalls, [
781
+ ["-p", "--no-session", "--model", "openrouter/deepseek/deepseek-v4-flash", "--thinking", "off", ...EXT_ARGS, promptReference],
782
+ // Retry drops configured overrides but preserves the active session model.
783
+ ["-p", "--no-session", "--model", "local-llama/local-9b", ...EXT_ARGS, promptReference],
784
+ ]);
785
+ });
786
+
787
+ it("uses the resolved Windows node.exe invocation for both override retry attempts", async () => {
788
+ const directory = await fs.mkdtemp(path.join(os.tmpdir(), "pi-windows-retry-"));
789
+ const binDirectory = path.join(directory, "bin");
790
+ const cliPath = path.join(
791
+ binDirectory,
792
+ "node_modules",
793
+ "@selesai",
794
+ "code",
795
+ "dist",
796
+ "cli.js",
797
+ );
798
+ const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
799
+ const originalPath = process.env.PATH;
800
+ await fs.mkdir(path.dirname(cliPath), { recursive: true });
801
+ await fs.writeFile(path.join(binDirectory, "selesai.cmd"), "@echo off\r\n");
802
+ await fs.writeFile(cliPath, "");
803
+ process.env.PATH = `${binDirectory};C:\\Windows\\System32`;
804
+ Object.defineProperty(process, "platform", { value: "win32", configurable: true });
805
+ try {
806
+ const calls: Array<{ cmd: string; args: string[] }> = [];
807
+ const pi = {
808
+ exec: async (cmd: string, args: string[]) => {
809
+ calls.push({ cmd, args });
810
+ if (calls.length === 1) {
811
+ return { code: 1, stdout: "", stderr: "model not found" };
812
+ }
813
+ return { code: 0, stdout: "ok", stderr: "" };
814
+ },
815
+ };
816
+
817
+ const result = await execChildPrompt(pi as any, "hello", {
818
+ llmModelOverride: "openrouter/deepseek/deepseek-v4-flash",
819
+ }, {
820
+ timeoutMs: 30000,
821
+ retryWithoutOverrides: true,
822
+ });
823
+
824
+ assert.strictEqual(result.code, 0);
825
+ assert.strictEqual(calls.length, 2);
826
+ assert.strictEqual(calls[0].cmd, process.execPath);
827
+ assert.strictEqual(calls[1].cmd, process.execPath);
828
+ assert.match(calls[0].args[4].replace(/\\/g, "/"), /\/cli\.js$/);
829
+ assert.match(calls[1].args[4].replace(/\\/g, "/"), /\/cli\.js$/);
830
+ const promptReference = calls[0].args.at(-1)!;
831
+ assert.match(promptReference, /^@/);
832
+ assert.deepStrictEqual(calls.map(logicalChildArgs), [
833
+ ["-p", "--no-session", "--model", "openrouter/deepseek/deepseek-v4-flash", "--thinking", "off", ...EXT_ARGS, promptReference],
834
+ ["-p", "--no-session", ...EXT_ARGS, promptReference],
835
+ ]);
836
+ } finally {
837
+ if (originalPlatform) Object.defineProperty(process, "platform", originalPlatform);
838
+ if (originalPath === undefined) delete process.env.PATH;
839
+ else process.env.PATH = originalPath;
840
+ await fs.rm(directory, { recursive: true, force: true });
841
+ }
842
+ });
843
+
844
+ it("does not retry generic non-zero child failures that are unrelated to override resolution", async () => {
845
+ const calls: Array<{ cmd: string; args: string[] }> = [];
846
+ const pi = {
847
+ exec: async (cmd: string, args: string[]) => {
848
+ calls.push({ cmd, args });
849
+ return { code: 1, stdout: "", stderr: "memory tool returned no changes" };
850
+ },
851
+ };
852
+
853
+ const result = await execChildPrompt(pi as any, "hello", {
854
+ llmModelOverride: "openrouter/deepseek/deepseek-v4-flash",
855
+ }, {
856
+ timeoutMs: 30000,
857
+ retryWithoutOverrides: true,
858
+ });
859
+
860
+ assert.strictEqual(result.code, 1);
861
+ assert.strictEqual(calls.length, 1);
862
+ });
863
+
864
+ it("does not retry generic thrown errors that are unrelated to override resolution", async () => {
865
+ const calls: Array<{ cmd: string; args: string[] }> = [];
866
+ const pi = {
867
+ exec: async (cmd: string, args: string[]) => {
868
+ calls.push({ cmd, args });
869
+ throw new Error("timed out waiting for child process");
870
+ },
871
+ };
872
+
873
+ await assert.rejects(
874
+ () => execChildPrompt(pi as any, "hello", {
875
+ llmModelOverride: "openrouter/deepseek/deepseek-v4-flash",
876
+ }, {
877
+ timeoutMs: 30000,
878
+ retryWithoutOverrides: true,
879
+ }),
880
+ /timed out waiting for child process/,
881
+ );
882
+
883
+ assert.strictEqual(calls.length, 1);
884
+ });
885
+
886
+ it("does not retry when retryWithoutOverrides is false", async () => {
887
+ const calls: Array<{ cmd: string; args: string[] }> = [];
888
+ const pi = {
889
+ exec: async (cmd: string, args: string[]) => {
890
+ calls.push({ cmd, args });
891
+ return { code: 1, stdout: "", stderr: "model not found" };
892
+ },
893
+ };
894
+
895
+ const result = await execChildPrompt(pi as any, "hello", {
896
+ llmModelOverride: "openrouter/deepseek/deepseek-v4-flash",
897
+ }, {
898
+ timeoutMs: 30000,
899
+ retryWithoutOverrides: false,
900
+ });
901
+
902
+ assert.strictEqual(result.code, 1);
903
+ assert.strictEqual(calls.length, 1);
904
+ });
905
+ });