@selesai/code 0.5.19 → 0.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/dist/extensions/handoff-new.test.ts +8 -9
  2. package/dist/extensions/handoff-new.ts +6 -8
  3. package/dist/extensions/pi-intercom/broker/spawn.test.ts +31 -0
  4. package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
  5. package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
  6. package/dist/extensions/pi-subagents/README.md +602 -220
  7. package/dist/extensions/pi-subagents/banner.png +0 -0
  8. package/dist/extensions/pi-subagents/index.ts +1 -0
  9. package/dist/extensions/pi-subagents/install.mjs +3 -3
  10. package/dist/extensions/pi-subagents/package-lock.json +3405 -0
  11. package/dist/extensions/pi-subagents/package.json +26 -14
  12. package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
  13. package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
  14. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
  15. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
  16. package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
  17. package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
  18. package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
  20. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
  21. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
  22. package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
  23. package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
  24. package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
  25. package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
  26. package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
  27. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
  28. package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
  29. package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
  30. package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
  31. package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
  32. package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
  33. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
  34. package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
  35. package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
  36. package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
  37. package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
  38. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
  39. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
  40. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
  41. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
  42. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
  43. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
  44. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
  45. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
  46. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
  47. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
  48. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
  49. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
  50. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
  51. package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
  52. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
  53. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
  54. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
  55. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
  56. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
  57. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
  58. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
  59. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
  61. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
  62. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
  63. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
  64. package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
  65. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
  66. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
  67. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
  68. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
  69. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
  70. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
  71. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
  72. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
  73. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
  74. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
  75. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
  76. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
  77. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
  78. package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
  79. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
  80. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
  81. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
  84. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
  86. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
  88. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
  89. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
  90. package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
  92. package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
  95. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
  96. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
  97. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
  98. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
  99. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
  100. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
  101. package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
  102. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
  103. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
  104. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
  105. package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
  106. package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
  107. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
  108. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
  109. package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
  110. package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
  111. package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
  112. package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
  113. package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
  114. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
  115. package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
  116. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
  117. package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
  118. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
  119. package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
  120. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
  121. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
  122. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
  127. package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
  128. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
  129. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
  130. package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
  131. package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
  132. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
  133. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
  134. package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
  135. package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
  136. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
  137. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
  138. package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
  139. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
  140. package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
  141. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
  142. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
  143. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
  144. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
  145. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
  146. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
  147. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
  148. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
  149. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
  150. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
  151. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
  152. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
  153. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
  154. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
  155. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
  156. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
  157. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
  158. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
  159. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
  160. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
  161. package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
  162. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
  163. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
  164. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
  165. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
  166. package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
  167. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
  168. package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
  169. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
  170. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
  171. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
  172. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
  173. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
  175. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
  176. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
  177. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
  178. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
  179. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
  180. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
  181. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
  182. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
  183. package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
  184. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
  185. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
  186. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
  187. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
  188. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
  189. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
  190. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
  191. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
  192. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
  193. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
  194. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
  195. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
  196. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
  197. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
  198. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
  199. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
  200. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
  201. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
  202. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
  203. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
  204. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
  205. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
  206. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
  207. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
  208. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
  209. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
  210. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
  211. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
  212. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
  213. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
  214. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
  215. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
  216. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
  217. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
  218. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
  219. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
  220. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
  221. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
  222. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
  223. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
  224. package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
  225. package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
  226. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
  227. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
  228. package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
  229. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
  230. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
  231. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
  232. package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
  233. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
  234. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
  235. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
  236. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
  237. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
  238. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
  239. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
  240. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
  241. package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
  242. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
  243. package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
  244. package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
  245. package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
  246. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
  247. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
  248. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
  249. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
  250. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
  251. package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
  252. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
  253. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
  254. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
  255. package/dist/extensions/question/batch.ts +39 -0
  256. package/dist/extensions/question/dialog-adapter.ts +23 -1
  257. package/dist/extensions/question/index.ts +237 -7
  258. package/dist/extensions/question/schemas.ts +28 -15
  259. package/dist/extensions/question/tests/batch.test.ts +26 -0
  260. package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
  261. package/dist/extensions/question/tui-adapter.ts +2 -3
  262. package/dist/extensions/question/types.ts +37 -0
  263. package/dist/extensions/question/ui-protocol.ts +3 -3
  264. package/dist/skills/batch-grill-me/SKILL.md +3 -1
  265. package/package.json +2 -1
@@ -8,6 +8,7 @@ import {
8
8
  expectsImplementationMutation,
9
9
  hasMutationToolCall,
10
10
  } from "../../src/runs/shared/completion-guard.ts";
11
+ import { isMutatingTool } from "../../src/runs/shared/long-running-guard.ts";
11
12
 
12
13
  function assistantToolCall(name: string, args: Record<string, unknown> = {}): Message {
13
14
  return {
@@ -52,7 +53,28 @@ test("declared read-only builtin tools suppress implementation-word false positi
52
53
  });
53
54
  });
54
55
 
55
- test("omitted, empty, bash, unknown, write, and MCP tool capabilities stay conservative", () => {
56
+ test("read-only issue drafting tasks do not trigger on suggested fix wording", () => {
57
+ const task = "Draft GitHub issue for pi-subagents bug from current conversation. Include title, environment/context, reproduction steps, actual/expected, logs excerpt, suspected cause, suggested fix. Terse but complete. No tools needed.";
58
+ const result = evaluateCompletionMutationGuard({
59
+ agent: "delegate",
60
+ task,
61
+ messages: [assistantText("Title: completionGuard false positive\n\nSuggested fix: model read-only intent.")],
62
+ tools: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"],
63
+ });
64
+
65
+ assert.deepEqual(result, {
66
+ expectedMutation: false,
67
+ attemptedMutation: false,
68
+ triggered: false,
69
+ });
70
+ assert.equal(expectsImplementationMutation("worker", task), false);
71
+ assert.equal(
72
+ expectsImplementationMutation("worker", "Draft GitHub issue for a bug. Include suspected cause and suggested fix."),
73
+ false,
74
+ );
75
+ });
76
+
77
+ test("omitted, bash, unknown, write, and MCP tool capabilities stay conservative while empty tools are toolless", () => {
56
78
  const base = {
57
79
  agent: "architect",
58
80
  task: "Implement the approved source fix",
@@ -60,7 +82,7 @@ test("omitted, empty, bash, unknown, write, and MCP tool capabilities stay conse
60
82
  };
61
83
 
62
84
  assert.equal(evaluateCompletionMutationGuard(base).triggered, true);
63
- assert.equal(evaluateCompletionMutationGuard({ ...base, tools: [] }).triggered, true);
85
+ assert.equal(evaluateCompletionMutationGuard({ ...base, tools: [] }).triggered, false);
64
86
  assert.equal(evaluateCompletionMutationGuard({ ...base, tools: ["read", "bash", "ls"] }).triggered, true);
65
87
  assert.equal(evaluateCompletionMutationGuard({ ...base, tools: ["read", "custom_lookup"] }).triggered, true);
66
88
  assert.equal(evaluateCompletionMutationGuard({ ...base, tools: ["read", "write"] }).triggered, true);
@@ -82,6 +104,22 @@ test("worker with mutating-capable tools still triggers when no mutation is obse
82
104
  });
83
105
  });
84
106
 
107
+ test("oracle review tasks with bash available do not require mutation", () => {
108
+ const task = "Review prep findings and determine what to implement with playbooks instead of before.";
109
+ const result = evaluateCompletionMutationGuard({
110
+ agent: "oracle",
111
+ task,
112
+ messages: [assistantText("Review complete with file-backed findings.")],
113
+ tools: ["read", "grep", "find", "ls", "bash", "intercom"],
114
+ });
115
+
116
+ assert.deepEqual(result, {
117
+ expectedMutation: false,
118
+ attemptedMutation: false,
119
+ triggered: false,
120
+ });
121
+ });
122
+
85
123
  test("review-only, research, and framework output instructions do not expect mutation", () => {
86
124
  assert.equal(expectsImplementationMutation("worker", "Review only: return findings, do not edit"), false);
87
125
  assert.equal(expectsImplementationMutation("worker", "Do not edit files. Tell me how to fix the bug."), false);
@@ -105,13 +143,27 @@ test("review-only, research, and framework output instructions do not expect mut
105
143
  );
106
144
  });
107
145
 
108
- test("worker implementation verbs win over investigative wording", () => {
146
+ test("worker implementation verbs win over investigative wording and scoped prohibitions", () => {
109
147
  assert.equal(expectsImplementationMutation("worker", "Investigate why the worker did not edit files and fix it"), true);
148
+ assert.equal(expectsImplementationMutation("worker", "Do not modify tests; implement the fix"), true);
149
+ assert.equal(expectsImplementationMutation("worker", "Do not modify tests — implement the fix"), true);
110
150
  assert.equal(expectsImplementationMutation("worker", "Research the current code path and patch the bug"), true);
111
151
  assert.equal(expectsImplementationMutation("worker", "Fix the bug where no edits were made"), true);
152
+ assert.equal(expectsImplementationMutation("worker", "Fix lint"), true);
153
+ assert.equal(expectsImplementationMutation("worker", "Fix the build"), true);
154
+ assert.equal(expectsImplementationMutation("worker", "Fix TypeScript errors"), true);
155
+ assert.equal(expectsImplementationMutation("worker", "Fix CI"), true);
156
+ assert.equal(expectsImplementationMutation("worker", "Fix the failing test"), true);
157
+ assert.equal(expectsImplementationMutation("worker", "Patch the cold start test"), true);
112
158
  assert.equal(expectsImplementationMutation("worker", "Implement the fix and return findings."), true);
113
159
  });
114
160
 
161
+ test("non-worker implementation tasks still expect mutation", () => {
162
+ assert.equal(expectsImplementationMutation("delegate", "Fix the bug where no edits were made"), true);
163
+ assert.equal(expectsImplementationMutation("delegate", "Apply the suggested fix to src/runs/shared/completion-guard.ts"), true);
164
+ assert.equal(expectsImplementationMutation("worker", "Draft a GitHub issue, then implement the fix"), true);
165
+ });
166
+
115
167
  test("worker edit intent covers common docs, config, and source tasks", () => {
116
168
  assert.equal(expectsImplementationMutation("worker", "Update README to mention the native tool"), true);
117
169
  assert.equal(expectsImplementationMutation("worker", "Remove share functionality and all Vercel references"), true);
@@ -150,3 +202,123 @@ test("implementation task with mutation attempts does not trigger", () => {
150
202
 
151
203
  assert.equal(result.triggered, false);
152
204
  });
205
+
206
+ function assistantThinking(thinking: string): Message {
207
+ return {
208
+ role: "assistant",
209
+ content: [{ type: "thinking", thinking }],
210
+ } as unknown as Message;
211
+ }
212
+
213
+ test("Cursor edit/write thinking traces count as mutation attempts", () => {
214
+ assert.equal(
215
+ hasMutationToolCall([assistantThinking("Cursor edit: docs/BACKEND_ARCHITECTURE.md added 10 lines, removed 3 lines\n")]),
216
+ true,
217
+ );
218
+ assert.equal(
219
+ hasMutationToolCall([assistantThinking("Cursor write: src/file.ts created\n")]),
220
+ true,
221
+ );
222
+ assert.equal(
223
+ hasMutationToolCall([assistantThinking("I plan to edit the file next\n")]),
224
+ false,
225
+ );
226
+ assert.equal(
227
+ hasMutationToolCall([assistantThinking("Cursor read: docs/BACKEND_ARCHITECTURE.md\n")]),
228
+ false,
229
+ );
230
+ });
231
+
232
+ test("Cursor replay tool calls count only edit/write activity as mutation", () => {
233
+ const cursorEdit = { activityTitle: "Cursor edit", path: "docs/BACKEND_ARCHITECTURE.md" };
234
+ const cursorWrite = { activityTitle: "Cursor write", path: "src/file.ts" };
235
+ assert.equal(hasMutationToolCall([assistantToolCall("cursor", cursorEdit)]), true);
236
+ assert.equal(hasMutationToolCall([assistantToolCall("cursor", cursorWrite)]), true);
237
+ assert.equal(hasMutationToolCall([assistantToolCall("cursor", { activityTitle: "Cursor read" })]), false);
238
+ assert.equal(isMutatingTool("cursor", cursorEdit), true);
239
+ assert.equal(isMutatingTool("cursor", { activityTitle: "Cursor read" }), false);
240
+ });
241
+
242
+ test("claimed changedFiles without mutation evidence does not bypass the guard", () => {
243
+ const report = assistantText(`Done.\n\`\`\`acceptance-report\n{\n "changedFiles": ["docs/BACKEND_ARCHITECTURE.md"]\n}\n\`\`\``);
244
+ assert.equal(hasMutationToolCall([report]), false);
245
+ });
246
+
247
+ function checkpointDataEntry(beforeCommit: string, afterCommit: string): Message {
248
+ return {
249
+ type: "custom",
250
+ customType: "pi-checkpoint",
251
+ data: { beforeCommit, afterCommit },
252
+ } as unknown as Message;
253
+ }
254
+
255
+ function checkpointDetailsMessage(beforeCommit: string, afterCommit: string): Message {
256
+ return {
257
+ role: "custom",
258
+ customType: "pi-checkpoint",
259
+ content: [],
260
+ details: { beforeCommit, afterCommit },
261
+ } as unknown as Message;
262
+ }
263
+
264
+ function checkpointNestedDetailsMessage(beforeCommit: string, afterCommit: string): Message {
265
+ return {
266
+ role: "custom",
267
+ customType: "pi-checkpoint",
268
+ content: [],
269
+ details: { data: { beforeCommit, afterCommit } },
270
+ } as unknown as Message;
271
+ }
272
+
273
+ test("provider checkpoint with a changed commit counts as mutation evidence", () => {
274
+ for (const message of [
275
+ checkpointDataEntry("before", "after"),
276
+ checkpointDetailsMessage("before", "after"),
277
+ checkpointNestedDetailsMessage("before", "after"),
278
+ ]) {
279
+ assert.equal(hasMutationToolCall([message]), true);
280
+ assert.equal(
281
+ evaluateCompletionMutationGuard({
282
+ agent: "worker",
283
+ task: "Edit the target source file",
284
+ messages: [message],
285
+ }).triggered,
286
+ false,
287
+ );
288
+ }
289
+ });
290
+
291
+ test("unchanged provider checkpoint does not bypass the completion guard", () => {
292
+ for (const message of [
293
+ checkpointDataEntry("same", "same"),
294
+ checkpointDetailsMessage("same", "same"),
295
+ checkpointNestedDetailsMessage("same", "same"),
296
+ ]) {
297
+ assert.equal(hasMutationToolCall([message]), false);
298
+ assert.equal(
299
+ evaluateCompletionMutationGuard({
300
+ agent: "worker",
301
+ task: "Edit the target source file",
302
+ messages: [message],
303
+ }).triggered,
304
+ true,
305
+ );
306
+ }
307
+ });
308
+
309
+ test("implementation task with Cursor edit thinking does not trigger", () => {
310
+ const result = evaluateCompletionMutationGuard({
311
+ agent: "worker",
312
+ task: "Edit docs/BACKEND_ARCHITECTURE.md",
313
+ messages: [
314
+ assistantThinking("Cursor edit: docs/BACKEND_ARCHITECTURE.md added 1 line\n"),
315
+ assistantText("Implemented the six simplifications."),
316
+ ],
317
+ tools: ["read", "grep", "find", "ls", "bash", "edit", "write", "contact_supervisor"],
318
+ });
319
+ assert.deepEqual(result, {
320
+ expectedMutation: true,
321
+ attemptedMutation: true,
322
+ triggered: false,
323
+ });
324
+ });
@@ -4,20 +4,20 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { afterEach, beforeEach, describe, it } from "node:test";
6
6
  import { fileURLToPath } from "node:url";
7
- import { getConfigDirName, PI_CODING_AGENT_PACKAGE_ROOT_ENV, resolveConfigDirName } from "../../src/shared/utils.ts";
7
+ import { getConfigDirName, SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV, resolveConfigDirName, resolveWatchPath } from "../../src/shared/utils.ts";
8
8
 
9
9
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
10
10
  let previousPackageRootEnv: string | undefined;
11
11
 
12
12
  describe("config directory resolution", () => {
13
13
  beforeEach(() => {
14
- previousPackageRootEnv = process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV];
15
- delete process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV];
14
+ previousPackageRootEnv = process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV];
15
+ delete process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV];
16
16
  });
17
17
 
18
18
  afterEach(() => {
19
- if (previousPackageRootEnv === undefined) delete process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV];
20
- else process.env[PI_CODING_AGENT_PACKAGE_ROOT_ENV] = previousPackageRootEnv;
19
+ if (previousPackageRootEnv === undefined) delete process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV];
20
+ else process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV] = previousPackageRootEnv;
21
21
  });
22
22
 
23
23
  it("falls back without importing the Pi peer package at runtime", () => {
@@ -65,6 +65,11 @@ describe("config directory resolution", () => {
65
65
  }
66
66
  });
67
67
 
68
+ it("canonicalizes watcher paths and preserves the original path when native realpath fails", () => {
69
+ assert.equal(resolveWatchPath("C:\\SHORT~1\\watch", () => "C:\\Long Path\\watch"), "C:\\Long Path\\watch");
70
+ assert.equal(resolveWatchPath("C:\\SHORT~1\\watch", () => { throw new Error("missing"); }), "C:\\SHORT~1\\watch");
71
+ });
72
+
68
73
  it("does not runtime-import the coding agent peer from shared utils", () => {
69
74
  const source = fs.readFileSync(path.join(repoRoot, "src/shared/utils.ts"), "utf-8");
70
75
  assert.doesNotMatch(source, /import\s+[^;]*from\s+["']@earendil-works\/pi-coding-agent["']/);
@@ -4,13 +4,25 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { describe, it } from "node:test";
6
6
  import {
7
+ closeSteerInbox,
7
8
  consumeInterruptRequest,
9
+ consumeSteerAcks,
10
+ consumeSteerCapabilities,
8
11
  consumeSteerRequests,
12
+ consumeStopRequest,
9
13
  deliverInterruptRequest,
10
14
  enqueueStepSteer,
11
15
  interruptRequestPath,
12
16
  requestAsyncInterrupt,
13
17
  requestAsyncSteer,
18
+ requestAsyncStop,
19
+ steerAckPathFromDir,
20
+ steerAcksDir,
21
+ steerInboxClosedPath,
22
+ steerCapabilityPath,
23
+ writeSteerAck,
24
+ writeSteerCapability,
25
+ stopRequestPath,
14
26
  steerRequestsDir,
15
27
  stepSteerInboxDir,
16
28
  watchAsyncControlInbox,
@@ -40,6 +52,23 @@ describe("control channel: request file", () => {
40
52
  }
41
53
  });
42
54
 
55
+ it("writes and consumes a stop request", () => {
56
+ const asyncDir = tmpAsyncDir("pi-control-stop-");
57
+ try {
58
+ const requestPath = requestAsyncStop(asyncDir, { source: "test" }, { now: () => 1234 });
59
+ assert.equal(requestPath, stopRequestPath(asyncDir));
60
+ const data = JSON.parse(fs.readFileSync(requestPath, "utf-8"));
61
+ assert.equal(data.type, "stop");
62
+ assert.equal(data.ts, 1234);
63
+ assert.equal(data.source, "test");
64
+ assert.equal(consumeStopRequest(asyncDir), true);
65
+ assert.equal(fs.existsSync(stopRequestPath(asyncDir)), false);
66
+ assert.equal(consumeStopRequest(asyncDir), false);
67
+ } finally {
68
+ cleanup(asyncDir);
69
+ }
70
+ });
71
+
43
72
  it("keeps the request type authoritative even for untyped callers", () => {
44
73
  const asyncDir = tmpAsyncDir("pi-control-write-type-");
45
74
  try {
@@ -80,11 +109,11 @@ describe("control channel: request file", () => {
80
109
  const asyncDir = tmpAsyncDir("pi-control-steer-");
81
110
  try {
82
111
  requestAsyncSteer(asyncDir, { message: " later guidance ", targetIndex: 1, id: "b", ts: 200, source: "test" });
83
- requestAsyncSteer(asyncDir, { message: "first guidance", id: "a", ts: 100 });
112
+ requestAsyncSteer(asyncDir, { message: "first guidance", targetIndexes: [0, 2], id: "a", ts: 100 });
84
113
  assert.equal(fs.readdirSync(steerRequestsDir(asyncDir)).length, 2);
85
114
 
86
115
  assert.deepEqual(consumeSteerRequests(asyncDir), [
87
- { type: "steer", id: "a", ts: 100, message: "first guidance" },
116
+ { type: "steer", id: "a", ts: 100, message: "first guidance", targetIndexes: [0, 2] },
88
117
  { type: "steer", id: "b", ts: 200, message: "later guidance", targetIndex: 1, source: "test" },
89
118
  ]);
90
119
  assert.deepEqual(consumeSteerRequests(asyncDir), []);
@@ -93,6 +122,18 @@ describe("control channel: request file", () => {
93
122
  }
94
123
  });
95
124
 
125
+ it("rejects requests after the runner closes the steering inbox", () => {
126
+ const asyncDir = tmpAsyncDir("pi-control-steer-closed-");
127
+ try {
128
+ closeSteerInbox(asyncDir, "complete");
129
+ assert.equal(fs.existsSync(steerInboxClosedPath(asyncDir)), true);
130
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "too late", targetIndexes: [0] }), /no longer accepts steering/);
131
+ assert.equal(fs.existsSync(steerRequestsDir(asyncDir)), false);
132
+ } finally {
133
+ cleanup(asyncDir);
134
+ }
135
+ });
136
+
96
137
  it("keeps steer request ids out of filesystem paths", () => {
97
138
  const asyncDir = tmpAsyncDir("pi-control-steer-safe-name-");
98
139
  try {
@@ -132,9 +173,10 @@ describe("control channel: request file", () => {
132
173
  it("enqueues a steer request for a specific child inbox", () => {
133
174
  const asyncDir = tmpAsyncDir("pi-control-step-steer-");
134
175
  try {
135
- enqueueStepSteer(asyncDir, 2, { type: "steer", id: "s1", ts: 300, message: "focus", targetIndex: 0 });
176
+ enqueueStepSteer(asyncDir, 2, { type: "steer", id: "s1", ts: 300, message: "focus", targetIndexes: [0, 1] });
136
177
  const request = JSON.parse(fs.readFileSync(path.join(stepSteerInboxDir(asyncDir, 2), fs.readdirSync(stepSteerInboxDir(asyncDir, 2))[0]!), "utf-8"));
137
178
  assert.equal(request.targetIndex, 2);
179
+ assert.equal(request.targetIndexes, undefined);
138
180
  assert.equal(request.message, "focus");
139
181
  } finally {
140
182
  cleanup(asyncDir);
@@ -146,6 +188,44 @@ describe("control channel: request file", () => {
146
188
  try {
147
189
  assert.throws(() => requestAsyncSteer(asyncDir, { message: " " }), /steer message must not be empty/);
148
190
  assert.throws(() => requestAsyncSteer(asyncDir, { message: "ok", targetIndex: -1 }), /targetIndex/);
191
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "ok", targetIndex: 1_000_001 }), /targetIndex/);
192
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "ok", targetIndexes: [] }), /targetIndexes/);
193
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "ok", targetIndexes: [0, 0] }), /targetIndexes/);
194
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "ok", targetIndex: 0, targetIndexes: [0] }), /targetIndexes/);
195
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "ok", targetIndexes: "bad" as unknown as number[] }), /targetIndexes/);
196
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "x".repeat(128 * 1024 + 1) }), /exceeds/);
197
+ assert.throws(() => requestAsyncSteer(asyncDir, { message: "ok", id: "contains whitespace" }), /malformed/);
198
+ } finally {
199
+ cleanup(asyncDir);
200
+ }
201
+ });
202
+
203
+ it("writes strict capabilities and acknowledgments with safe paths", () => {
204
+ const asyncDir = tmpAsyncDir("pi-control-steer-ack-");
205
+ try {
206
+ const capabilityPath = writeSteerCapability(asyncDir, { index: 0, pid: 42, readyAt: 100, supported: true });
207
+ assert.equal(capabilityPath, steerCapabilityPath(asyncDir, 0));
208
+ writeSteerAck(asyncDir, { requestId: "../request", index: 0, ts: 101, state: "delivered", message: "accepted" });
209
+ assert.equal(path.dirname(steerAckPathFromDir(steerAcksDir(asyncDir, 0), "../request")), steerAcksDir(asyncDir, 0));
210
+ assert.deepEqual(consumeSteerCapabilities(asyncDir), [{ type: "steer-capability", protocolVersion: 1, index: 0, pid: 42, readyAt: 100, supported: true }]);
211
+ assert.deepEqual(consumeSteerAcks(asyncDir), [{ type: "steer-ack", protocolVersion: 1, requestId: "../request", index: 0, ts: 101, state: "delivered", message: "accepted" }]);
212
+ assert.deepEqual(consumeSteerAcks(asyncDir), []);
213
+ } finally {
214
+ cleanup(asyncDir);
215
+ }
216
+ });
217
+
218
+ it("ignores malformed capabilities and acknowledgments", () => {
219
+ const asyncDir = tmpAsyncDir("pi-control-steer-malformed-");
220
+ try {
221
+ fs.mkdirSync(path.join(asyncDir, "control", "steer-capabilities"), { recursive: true });
222
+ fs.writeFileSync(steerCapabilityPath(asyncDir, 0), JSON.stringify({ type: "steer-capability", protocolVersion: 99 }), "utf-8");
223
+ fs.mkdirSync(steerAcksDir(asyncDir, 0), { recursive: true });
224
+ fs.writeFileSync(path.join(steerAcksDir(asyncDir, 0), "bad.json"), JSON.stringify({ type: "steer-ack", protocolVersion: 1, requestId: "", index: 0, ts: 1, state: "delivered", message: "bad" }), "utf-8");
225
+ fs.writeFileSync(path.join(asyncDir, "control", "steer-acks", "1"), "not a directory", "utf-8");
226
+ writeSteerAck(asyncDir, { requestId: "valid", index: 2, ts: 2, state: "delivered", message: "accepted" });
227
+ assert.deepEqual(consumeSteerCapabilities(asyncDir), []);
228
+ assert.deepEqual(consumeSteerAcks(asyncDir), [{ type: "steer-ack", protocolVersion: 1, requestId: "valid", index: 2, ts: 2, state: "delivered", message: "accepted" }]);
149
229
  } finally {
150
230
  cleanup(asyncDir);
151
231
  }
@@ -234,18 +314,24 @@ describe("control channel: watchAsyncControlInbox", () => {
234
314
  timers: import("../../src/runs/background/control-channel.ts").ControlChannelTimers;
235
315
  trigger: () => void;
236
316
  closed: () => boolean;
317
+ watchedDir: () => string | undefined;
237
318
  };
238
319
 
239
- function harness(): WatchHarness {
320
+ function harness(nativeDir?: string): WatchHarness {
240
321
  let listener: (() => void) | undefined;
241
322
  let closed = false;
323
+ let watchedDir: string | undefined;
324
+ const realpathSync = ((target: fs.PathLike, options?: unknown) => fs.realpathSync(target, options as BufferEncoding)) as typeof fs.realpathSync;
325
+ realpathSync.native = ((target: fs.PathLike) => nativeDir ?? fs.realpathSync.native(target)) as typeof fs.realpathSync.native;
242
326
  const fsImpl = {
243
327
  mkdirSync: fs.mkdirSync,
244
328
  existsSync: fs.existsSync,
245
329
  rmSync: fs.rmSync,
246
330
  readdirSync: fs.readdirSync,
247
331
  readFileSync: fs.readFileSync,
248
- watch: ((_dir: string, cb: () => void) => {
332
+ realpathSync,
333
+ watch: ((dir: string, cb: () => void) => {
334
+ watchedDir = dir;
249
335
  listener = cb;
250
336
  return { close: () => { closed = true; }, on: () => {} };
251
337
  }),
@@ -254,9 +340,22 @@ describe("control channel: watchAsyncControlInbox", () => {
254
340
  setInterval: (() => ({ unref() {} })) as unknown as typeof setInterval,
255
341
  clearInterval: (() => {}) as unknown as typeof clearInterval,
256
342
  };
257
- return { fsImpl, timers, trigger: () => listener?.(), closed: () => closed };
343
+ return { fsImpl, timers, trigger: () => listener?.(), closed: () => closed, watchedDir: () => watchedDir };
258
344
  }
259
345
 
346
+ it("registers the native canonical control inbox path", () => {
347
+ const asyncDir = tmpAsyncDir("pi-control-watch-native-");
348
+ try {
349
+ const nativeDir = path.join(path.dirname(asyncDir), "native-control-path");
350
+ const h = harness(nativeDir);
351
+ const dispose = watchAsyncControlInbox(asyncDir, { onInterrupt() {}, fs: h.fsImpl, timers: h.timers });
352
+ assert.equal(h.watchedDir(), nativeDir);
353
+ dispose();
354
+ } finally {
355
+ cleanup(asyncDir);
356
+ }
357
+ });
358
+
260
359
  it("fires on a request that arrived before the watcher started", () => {
261
360
  const asyncDir = tmpAsyncDir("pi-control-watch-early-");
262
361
  try {
@@ -301,6 +400,27 @@ describe("control channel: watchAsyncControlInbox", () => {
301
400
  }
302
401
  });
303
402
 
403
+ it("delivers stop requests before interrupt requests", () => {
404
+ const asyncDir = tmpAsyncDir("pi-control-watch-stop-early-");
405
+ try {
406
+ requestAsyncStop(asyncDir);
407
+ requestAsyncInterrupt(asyncDir);
408
+ const events: string[] = [];
409
+ const h = harness();
410
+ const dispose = watchAsyncControlInbox(asyncDir, {
411
+ onInterrupt: () => events.push("interrupt"),
412
+ onStop: () => events.push("stop"),
413
+ fs: h.fsImpl,
414
+ timers: h.timers,
415
+ });
416
+
417
+ assert.deepEqual(events, ["stop", "interrupt"]);
418
+ dispose();
419
+ } finally {
420
+ cleanup(asyncDir);
421
+ }
422
+ });
423
+
304
424
  it("delivers steer requests without firing interrupt", () => {
305
425
  const asyncDir = tmpAsyncDir("pi-control-watch-steer-");
306
426
  try {