@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
@@ -0,0 +1,38 @@
1
+ import type { AgentContract, EffectsProjection, ExecutionProjection, ReviewProjection, SingleResult } from "../../shared/types.ts";
2
+
3
+ export function isAgentContractV1(contract: AgentContract | undefined): boolean {
4
+ return contract?.version === 1;
5
+ }
6
+
7
+ export function buildExecutionProjection(result: Pick<SingleResult, "exitCode" | "error" | "interrupted" | "timedOut" | "stopped" | "detached">): ExecutionProjection {
8
+ if (result.detached) {
9
+ return { status: "detached", success: false, exitCode: result.exitCode, detached: true, ...(result.error ? { error: result.error } : {}) };
10
+ }
11
+ if (result.stopped) {
12
+ return { status: "stopped", success: false, exitCode: result.exitCode, stopped: true, ...(result.error ? { error: result.error } : {}) };
13
+ }
14
+ if (result.interrupted) {
15
+ return { status: "paused", success: true, exitCode: result.exitCode, interrupted: true, ...(result.error ? { error: result.error } : {}) };
16
+ }
17
+ const success = result.exitCode === 0 && !result.error && !result.timedOut;
18
+ return {
19
+ status: success ? "completed" : "failed",
20
+ success,
21
+ exitCode: result.exitCode,
22
+ ...(result.error ? { error: result.error } : {}),
23
+ ...(result.timedOut ? { timedOut: true } : {}),
24
+ };
25
+ }
26
+
27
+ export function buildReviewProjection(result: Pick<SingleResult, "acceptance">): ReviewProjection {
28
+ const review = result.acceptance?.reviewResult;
29
+ if (!review) return { status: "not-requested" };
30
+ return { status: review.status, findings: review.findings };
31
+ }
32
+
33
+ export function attachContractProjections<T extends SingleResult>(result: T): T {
34
+ result.execution = buildExecutionProjection(result);
35
+ result.review = buildReviewProjection(result);
36
+ if (!result.effects) result.effects = {} satisfies EffectsProjection;
37
+ return result;
38
+ }
@@ -0,0 +1,178 @@
1
+ import { Buffer } from "node:buffer";
2
+ import "../../shared/env.ts";
3
+
4
+ export const SUBAGENT_CAPABILITY_CEILING_VERSION = 1 as const;
5
+ export const SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY = "pi-subagents.capability-ceiling.v1";
6
+ export const SUBAGENT_CAPABILITY_CEILING_ENV = "SELESAI_SUBAGENT_CAPABILITY_CEILING_V1";
7
+
8
+ export type SubagentCapabilityCeiling =
9
+ | { allowedTools: readonly string[]; denyExtensions?: boolean }
10
+ | { allowedTools?: readonly string[]; denyExtensions: boolean };
11
+
12
+ export interface ResolvedSubagentCapabilityCeiling {
13
+ version: typeof SUBAGENT_CAPABILITY_CEILING_VERSION;
14
+ allowedTools?: string[];
15
+ denyExtensions: boolean;
16
+ sources: string[];
17
+ }
18
+
19
+ export interface SubagentCapabilityAudit {
20
+ ceiling: ResolvedSubagentCapabilityCeiling;
21
+ requestedTools?: string[];
22
+ effectiveTools: string[];
23
+ removedTools: string[];
24
+ internalTools: string[];
25
+ extensionsDenied: boolean;
26
+ removedExtensionCount: number;
27
+ requestedMcpToolCount: number;
28
+ effectiveMcpTools: string[];
29
+ }
30
+
31
+ export interface RegisterSubagentCapabilityCeilingOptions {
32
+ sessionId: string;
33
+ source: string;
34
+ ceiling: SubagentCapabilityCeiling;
35
+ }
36
+
37
+ export interface SubagentCapabilityCeilingHandle {
38
+ update(ceiling: SubagentCapabilityCeiling): void;
39
+ dispose(): void;
40
+ }
41
+
42
+ type Registration = { source: string; ceiling: ResolvedSubagentCapabilityCeiling };
43
+ type Registry = Map<string, Map<symbol, Registration>>;
44
+
45
+ function registry(): Registry {
46
+ const key = Symbol.for(SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY);
47
+ const store = globalThis as typeof globalThis & { [key: symbol]: unknown };
48
+ const existing = store[key];
49
+ if (existing instanceof Map) return existing as Registry;
50
+ const created: Registry = new Map();
51
+ store[key] = created;
52
+ return created;
53
+ }
54
+
55
+ function validateText(value: unknown, field: string): string {
56
+ if (typeof value !== "string" || !value.trim() || /[\u0000-\u001f\u007f]/u.test(value) || Buffer.byteLength(value.trim(), "utf8") > 256) {
57
+ throw new Error(`Invalid capability ceiling ${field}; expected a non-empty string without control characters (max 256 UTF-8 bytes).`);
58
+ }
59
+ return value.trim();
60
+ }
61
+
62
+ function normalizeCeiling(ceiling: SubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling {
63
+ if (!ceiling || typeof ceiling !== "object" || Array.isArray(ceiling)) throw new Error("Invalid capability ceiling; expected an object.");
64
+ const hasAllowedTools = Object.hasOwn(ceiling, "allowedTools");
65
+ const hasDenyExtensions = Object.hasOwn(ceiling, "denyExtensions");
66
+ if (!hasAllowedTools && !hasDenyExtensions) throw new Error("Invalid capability ceiling; expected allowedTools or denyExtensions.");
67
+ if (hasDenyExtensions && typeof ceiling.denyExtensions !== "boolean") throw new Error("Invalid capability ceiling denyExtensions; expected a boolean.");
68
+ let allowedTools: string[] | undefined;
69
+ if (hasAllowedTools) {
70
+ if (!Array.isArray(ceiling.allowedTools)) throw new Error("Invalid capability ceiling allowedTools; expected an array.");
71
+ if (ceiling.allowedTools.length > 256) throw new Error("Invalid capability ceiling allowedTools; expected at most 256 names.");
72
+ allowedTools = [...new Set(ceiling.allowedTools.map((tool) => {
73
+ const name = validateText(tool, "allowedTools entry");
74
+ if (!/^[A-Za-z0-9_.:-]+$/u.test(name)) throw new Error(`Invalid capability ceiling allowedTools entry '${name}'.`);
75
+ if (Buffer.byteLength(name, "utf8") > 128) throw new Error(`Invalid capability ceiling allowedTools entry '${name}'; max 128 UTF-8 bytes.`);
76
+ return name;
77
+ }))].sort();
78
+ }
79
+ return {
80
+ version: SUBAGENT_CAPABILITY_CEILING_VERSION,
81
+ ...(allowedTools ? { allowedTools } : {}),
82
+ denyExtensions: ceiling.denyExtensions === true,
83
+ sources: [],
84
+ };
85
+ }
86
+
87
+ export function parseSubagentCapabilityCeiling(value: unknown, field = "capability ceiling"): ResolvedSubagentCapabilityCeiling {
88
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid ${field}; expected an object.`);
89
+ const record = value as Record<string, unknown>;
90
+ if (record.version !== SUBAGENT_CAPABILITY_CEILING_VERSION) throw new Error(`Invalid ${field} version.`);
91
+ const normalized = normalizeCeiling(record as SubagentCapabilityCeiling);
92
+ const sources = record.sources;
93
+ if (!Array.isArray(sources) || sources.some((source) => typeof source !== "string")) throw new Error(`Invalid ${field} sources; expected an array of strings.`);
94
+ normalized.sources = [...new Set(sources.map((source) => validateText(source, `${field} source`)))].sort();
95
+ return normalized;
96
+ }
97
+
98
+ export function registerSubagentCapabilityCeiling(options: RegisterSubagentCapabilityCeilingOptions): SubagentCapabilityCeilingHandle {
99
+ const sessionId = validateText(options.sessionId, "sessionId");
100
+ const source = validateText(options.source, "source");
101
+ let normalized = normalizeCeiling(options.ceiling);
102
+ const token = Symbol(source);
103
+ const store = registry();
104
+ let session = store.get(sessionId);
105
+ if (!session) {
106
+ session = new Map();
107
+ store.set(sessionId, session);
108
+ }
109
+ const setRegistration = () => {
110
+ normalized = normalizeCeiling(normalized);
111
+ normalized.sources = [source];
112
+ session!.set(token, { source, ceiling: normalized });
113
+ };
114
+ setRegistration();
115
+ let disposed = false;
116
+ return {
117
+ update(ceiling) {
118
+ if (disposed) throw new Error("Cannot update a disposed capability ceiling handle.");
119
+ normalized = normalizeCeiling(ceiling);
120
+ normalized.sources = [source];
121
+ session!.set(token, { source, ceiling: normalized });
122
+ },
123
+ dispose() {
124
+ if (disposed) return;
125
+ disposed = true;
126
+ session!.delete(token);
127
+ if (session!.size === 0) store.delete(sessionId);
128
+ },
129
+ };
130
+ }
131
+
132
+ export function intersectSubagentCapabilityCeilings(...ceilings: Array<ResolvedSubagentCapabilityCeiling | undefined>): ResolvedSubagentCapabilityCeiling | undefined {
133
+ const active = ceilings.filter((ceiling): ceiling is ResolvedSubagentCapabilityCeiling => ceiling !== undefined);
134
+ if (active.length === 0) return undefined;
135
+ const definedLists = active.filter((ceiling) => ceiling.allowedTools !== undefined).map((ceiling) => new Set(ceiling.allowedTools));
136
+ let allowedTools: string[] | undefined;
137
+ if (definedLists.length > 0) {
138
+ allowedTools = [...definedLists[0]!].filter((tool) => definedLists.every((list) => list.has(tool))).sort();
139
+ }
140
+ return {
141
+ version: SUBAGENT_CAPABILITY_CEILING_VERSION,
142
+ ...(allowedTools ? { allowedTools } : {}),
143
+ denyExtensions: active.some((ceiling) => ceiling.denyExtensions),
144
+ sources: [...new Set(active.flatMap((ceiling) => ceiling.sources))].sort(),
145
+ };
146
+ }
147
+
148
+ export function resolveSubagentCapabilityCeiling(sessionId: string | undefined, inherited?: ResolvedSubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling | undefined {
149
+ const active: ResolvedSubagentCapabilityCeiling[] = [];
150
+ if (sessionId) {
151
+ const registrations = registry().get(sessionId);
152
+ if (registrations) active.push(...Array.from(registrations.values(), ({ ceiling }) => ceiling));
153
+ }
154
+ return intersectSubagentCapabilityCeilings(inherited, ...active);
155
+ }
156
+
157
+ export function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {
158
+ return resolveSubagentCapabilityCeiling(sessionId, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
159
+ }
160
+
161
+ export function encodeSubagentCapabilityCeiling(ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined {
162
+ if (!ceiling) return undefined;
163
+ return Buffer.from(JSON.stringify(ceiling), "utf8").toString("base64url");
164
+ }
165
+
166
+ export function decodeSubagentCapabilityCeiling(value: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {
167
+ if (value === undefined || value === "") return undefined;
168
+ let parsed: unknown;
169
+ try {
170
+ parsed = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
171
+ } catch (error) {
172
+ throw new Error(`Invalid inherited capability ceiling: ${error instanceof Error ? error.message : String(error)}`);
173
+ }
174
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || (parsed as { version?: unknown }).version !== SUBAGENT_CAPABILITY_CEILING_VERSION) {
175
+ throw new Error("Invalid inherited capability ceiling version.");
176
+ }
177
+ return parseSubagentCapabilityCeiling(parsed, "inherited capability ceiling");
178
+ }
@@ -0,0 +1,121 @@
1
+ import { Buffer } from "node:buffer";
2
+ import type { ProtocolOutputLimit } from "../../shared/types.ts";
3
+
4
+ export type { ProtocolOutputLimit } from "../../shared/types.ts";
5
+
6
+ export const MAX_CHILD_PENDING_LINE_BYTES = 16 * 1024 * 1024;
7
+ export const MAX_CHILD_STDERR_BYTES = 128 * 1024;
8
+ const MAX_PROTOCOL_DIAGNOSTIC_BYTES = 4096;
9
+
10
+ export function formatProtocolOutputLimit(limit: ProtocolOutputLimit): string {
11
+ return `${limit.code}: child ${limit.stream} line exceeded ${limit.limitBytes} bytes (observed at least ${limit.observedBytes} bytes without a newline).`;
12
+ }
13
+
14
+ export function createBoundedLineReader(options: {
15
+ stream?: "stdout" | "stderr";
16
+ maxPendingLineBytes?: number;
17
+ onLine: (line: string) => void;
18
+ onLimit: (limit: ProtocolOutputLimit) => void;
19
+ }): {
20
+ push(chunk: Buffer | string): void;
21
+ end(): void;
22
+ exceeded(): boolean;
23
+ } {
24
+ const maxPendingLineBytes = options.maxPendingLineBytes ?? MAX_CHILD_PENDING_LINE_BYTES;
25
+ if (!Number.isInteger(maxPendingLineBytes) || maxPendingLineBytes < 1) {
26
+ throw new Error("maxPendingLineBytes must be a positive integer.");
27
+ }
28
+ let pending: Buffer[] = [];
29
+ let pendingBytes = 0;
30
+ let limitExceeded = false;
31
+
32
+ const emitPending = (): void => {
33
+ if (pendingBytes === 0) return;
34
+ options.onLine(Buffer.concat(pending, pendingBytes).toString("utf8"));
35
+ pending = [];
36
+ pendingBytes = 0;
37
+ };
38
+
39
+ const append = (segment: Buffer): boolean => {
40
+ if (segment.length === 0) return true;
41
+ const observedBytes = pendingBytes + segment.length;
42
+ if (observedBytes > maxPendingLineBytes) {
43
+ const prior = pendingBytes > 0 ? Buffer.concat(pending, pendingBytes) : Buffer.alloc(0);
44
+ const prefixFromPrior = prior.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES);
45
+ const prefix = prefixFromPrior.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES
46
+ ? prefixFromPrior
47
+ : Buffer.concat([prefixFromPrior, segment.subarray(0, MAX_PROTOCOL_DIAGNOSTIC_BYTES - prefixFromPrior.length)]);
48
+ const tailFromSegment = segment.subarray(Math.max(0, segment.length - MAX_PROTOCOL_DIAGNOSTIC_BYTES));
49
+ const tail = tailFromSegment.length === MAX_PROTOCOL_DIAGNOSTIC_BYTES
50
+ ? tailFromSegment
51
+ : Buffer.concat([prior.subarray(Math.max(0, prior.length - (MAX_PROTOCOL_DIAGNOSTIC_BYTES - tailFromSegment.length))), tailFromSegment]);
52
+ limitExceeded = true;
53
+ pending = [];
54
+ pendingBytes = 0;
55
+ options.onLimit({
56
+ code: "protocol_output_limit",
57
+ stream: options.stream ?? "stdout",
58
+ limitBytes: maxPendingLineBytes,
59
+ observedBytes,
60
+ diagnosticPrefix: prefix.toString("utf8"),
61
+ diagnosticTail: tail.toString("utf8"),
62
+ });
63
+ return false;
64
+ }
65
+ pending.push(segment);
66
+ pendingBytes = observedBytes;
67
+ return true;
68
+ };
69
+
70
+ return {
71
+ push(chunk) {
72
+ if (limitExceeded) return;
73
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
74
+ let start = 0;
75
+ for (let index = 0; index < bytes.length; index++) {
76
+ if (bytes[index] !== 0x0a) continue;
77
+ if (!append(bytes.subarray(start, index))) return;
78
+ emitPending();
79
+ start = index + 1;
80
+ }
81
+ append(bytes.subarray(start));
82
+ },
83
+ end() {
84
+ if (!limitExceeded) emitPending();
85
+ },
86
+ exceeded: () => limitExceeded,
87
+ };
88
+ }
89
+
90
+ function trimToUtf8Boundary(buffer: Buffer, maxBytes: number): Buffer {
91
+ if (buffer.length <= maxBytes) return buffer;
92
+ let start = buffer.length - maxBytes;
93
+ while (start < buffer.length && (buffer[start]! & 0xc0) === 0x80) start++;
94
+ return buffer.subarray(start);
95
+ }
96
+
97
+ export function createBoundedByteTail(maxBytes = MAX_CHILD_STDERR_BYTES): {
98
+ push(chunk: Buffer | string): void;
99
+ text(): string;
100
+ byteLength(): number;
101
+ } {
102
+ if (!Number.isInteger(maxBytes) || maxBytes < 1) throw new Error("maxBytes must be a positive integer.");
103
+ let tail = Buffer.alloc(0);
104
+ return {
105
+ push(chunk) {
106
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
107
+ tail = trimToUtf8Boundary(Buffer.concat([tail, bytes]), maxBytes);
108
+ },
109
+ text: () => tail.toString("utf8"),
110
+ byteLength: () => tail.length,
111
+ };
112
+ }
113
+
114
+ export type ChildLifecycleAction = "start-drain" | "cancel-drain" | "none";
115
+
116
+ export function projectChildLifecycle(event: { type?: string; willRetry?: unknown }, terminalAssistantStop = false): ChildLifecycleAction {
117
+ if (event.type === "agent_end" && event.willRetry === true) return "cancel-drain";
118
+ if (event.type === "agent_settled") return "start-drain";
119
+ if (terminalAssistantStop) return "start-drain";
120
+ return "none";
121
+ }
@@ -1,58 +1,8 @@
1
1
  import type { Message } from "@earendil-works/pi-ai";
2
- import { isMutatingBashCommand } from "./long-running-guard.ts";
2
+ import { isMutatingTool } from "./long-running-guard.ts";
3
+ import { expectsImplementationMutation } from "./task-intent.ts";
3
4
 
4
- const REVIEW_ONLY_PATTERNS = [
5
- /\breview only\b/i,
6
- /\bsuggest fixes only\b/i,
7
- /\bonly return findings\b/i,
8
- /\breturn findings only\b/i,
9
- ];
10
-
11
- const REVIEWER_REQUIRED_EDIT_PATTERNS = [
12
- /\bmust\s+(?:edit|modify|change|fix|patch|apply)\b/i,
13
- /\brequired\s+to\s+(?:edit|modify|change|fix|patch|apply)\b/i,
14
- /\bregardless\s+of\s+findings\b/i,
15
- /\balways\s+(?:edit|modify|change|fix|patch|apply)\b/i,
16
- /\bapply\s+(?:the\s+)?fix(?:es)?\s+directly\b/i,
17
- /\bmake\s+(?:the\s+)?code\s+changes\b/i,
18
- ];
19
-
20
- const EXPLICIT_NO_EDIT_PATTERNS = [
21
- /\bdo not edit\b/i,
22
- /\bdon't edit\b/i,
23
- /\bdo not modify\b/i,
24
- /\bdo not change files\b/i,
25
- ];
26
-
27
- const SCOPED_NO_EDIT_CONSTRAINT_PATTERNS = [
28
- /\bdo not edit files?\s+outside\b/i,
29
- /\bdo not edit\s+outside\b/i,
30
- /\bdo not edit\s+unrelated files?\b/i,
31
- /\bdo not change\s+unrelated files?\b/i,
32
- /\bdo not modify\s+unrelated files?\b/i,
33
- ];
34
-
35
- const RESEARCH_AGENT_PATTERNS = [
36
- /\binvestigate\b/i,
37
- /\bscout\b/i,
38
- /\bresearch(?:er)?\b/i,
39
- ];
40
-
41
- const WORKER_IMPLEMENTATION_PATTERNS = [
42
- /\b(?:implement|fix|edit|modify|patch|refactor|delete)\b/i,
43
- /\b(?:update|add|remove|replace|create)\b(?!\s+(?:(?:a|an|the)\s+)?(?:report|summary|findings?)(?:\b|$))/i,
44
- /\bapply\s+(?:the\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
45
- /\bmake\s+(?:the\s+)?changes\b/i,
46
- /\bdo those fixes\b/i,
47
- ];
48
-
49
- const GENERAL_IMPLEMENTATION_PATTERNS = [
50
- /\b(?:implement|fix|edit|modify|patch|refactor)\b/i,
51
- /\bapply\s+(?:the\s+)?(?:changes?|fix(?:es)?|patch)\b/i,
52
- /\bmake\s+(?:the\s+)?changes\b/i,
53
- /\bdo those fixes\b/i,
54
- /\b(?:update|add|remove|replace|delete|create)\s+(?:the\s+)?(?:file|files|code|source|implementation|test|tests|component|function|module|class|method|logic|import|imports|readme|docs?|changelog|package\.json|config|manifest|extension|prompt|command)\b/i,
55
- ];
5
+ export { expectsImplementationMutation };
56
6
 
57
7
  const READ_ONLY_BUILTIN_TOOLS = new Set([
58
8
  "read",
@@ -66,6 +16,12 @@ const READ_ONLY_BUILTIN_TOOLS = new Set([
66
16
  "contact_supervisor",
67
17
  ]);
68
18
 
19
+ // Cursor native edit/write often land as thinking traces (inactive_trace /
20
+ // transcript_trace) rather than toolCall parts when native tool replay is off
21
+ // or the tool is inactive in context. Match pi-cursor-sdk display labels.
22
+ const CURSOR_FILE_MUTATION_THINKING =
23
+ /(?:^|\n)\s*Cursor (?:edit|write)\s*:/i;
24
+
69
25
  interface CompletionMutationGuardInput {
70
26
  agent: string;
71
27
  task: string;
@@ -80,64 +36,56 @@ interface CompletionMutationGuardResult {
80
36
  triggered: boolean;
81
37
  }
82
38
 
83
- function stripFrameworkInstructions(task: string): string {
84
- return task
85
- .split("\n")
86
- .filter((line) => !/^\s*\[(?:Write to|Read from):/i.test(line))
87
- .filter((line) => !/^\s*(?:Create and maintain progress at:|Update progress at:|\*\*Output:\*\*|Write your findings to(?: exactly this path)?:|This path is authoritative for this run\.|Ignore any other output filename or output path mentioned elsewhere)/i.test(line))
88
- .join("\n");
89
- }
90
-
91
- function stripScopedNoEditConstraints(task: string): string {
92
- let stripped = task;
93
- for (const pattern of SCOPED_NO_EDIT_CONSTRAINT_PATTERNS) {
94
- stripped = stripped.replace(pattern, " ");
95
- }
96
- return stripped;
97
- }
98
-
99
- function declaresOnlyReadOnlyTools(tools: string[] | undefined, mcpDirectTools: string[] | undefined): boolean {
100
- return tools !== undefined
101
- && tools.length > 0
102
- && (mcpDirectTools?.length ?? 0) === 0
103
- && tools.every((tool) => READ_ONLY_BUILTIN_TOOLS.has(tool));
39
+ export function hasMutationToolCapability(tools: string[] | undefined, mcpDirectTools: string[] | undefined): boolean {
40
+ if ((mcpDirectTools?.length ?? 0) > 0) return true;
41
+ if (tools === undefined) return true;
42
+ return !tools.every((tool) => READ_ONLY_BUILTIN_TOOLS.has(tool));
104
43
  }
105
44
 
106
- export function expectsImplementationMutation(agent: string, task: string): boolean {
107
- const taskText = stripFrameworkInstructions(task);
108
- const taskTextWithoutScopedConstraints = stripScopedNoEditConstraints(taskText);
109
- if (REVIEW_ONLY_PATTERNS.some((pattern) => pattern.test(taskTextWithoutScopedConstraints))) return false;
110
- if (EXPLICIT_NO_EDIT_PATTERNS.some((pattern) => pattern.test(taskTextWithoutScopedConstraints))) return false;
111
-
112
- if (RESEARCH_AGENT_PATTERNS.some((pattern) => pattern.test(agent))) return false;
113
- if (/\breviewer\b/i.test(agent)) return REVIEWER_REQUIRED_EDIT_PATTERNS.some((pattern) => pattern.test(taskText));
114
-
115
- const workerIntent = agent === "worker" && WORKER_IMPLEMENTATION_PATTERNS.some((pattern) => pattern.test(taskText));
116
- if (workerIntent) return true;
117
-
118
- return GENERAL_IMPLEMENTATION_PATTERNS.some((pattern) => pattern.test(taskText));
45
+ function hasCheckpointMutationEvidence(message: Message): boolean {
46
+ const record = message as unknown as {
47
+ role?: string;
48
+ type?: string;
49
+ customType?: unknown;
50
+ data?: unknown;
51
+ details?: unknown;
52
+ };
53
+ if ((record.role !== "custom" && record.type !== "custom") || record.customType !== "pi-checkpoint") return false;
54
+ const details = typeof record.details === "object" && record.details !== null && !Array.isArray(record.details)
55
+ ? record.details as Record<string, unknown>
56
+ : undefined;
57
+ const data = typeof record.data === "object" && record.data !== null && !Array.isArray(record.data)
58
+ ? record.data as Record<string, unknown>
59
+ : typeof details?.beforeCommit === "string" || typeof details?.afterCommit === "string"
60
+ ? details
61
+ : details?.data && typeof details.data === "object" && !Array.isArray(details.data)
62
+ ? details.data as Record<string, unknown>
63
+ : undefined;
64
+ return typeof data?.beforeCommit === "string"
65
+ && typeof data.afterCommit === "string"
66
+ && data.beforeCommit !== data.afterCommit;
119
67
  }
120
68
 
121
69
  export function hasMutationToolCall(messages: Message[]): boolean {
122
70
  for (const message of messages) {
71
+ if (hasCheckpointMutationEvidence(message)) return true;
123
72
  if (message.role !== "assistant") continue;
124
73
  for (const part of message.content) {
74
+ if (part.type === "thinking" && CURSOR_FILE_MUTATION_THINKING.test(part.thinking)) return true;
125
75
  if (part.type !== "toolCall") continue;
126
- if (part.name === "edit" || part.name === "write") return true;
127
- if (part.name !== "bash") continue;
128
76
  const args = typeof part.arguments === "object" && part.arguments !== null && !Array.isArray(part.arguments)
129
77
  ? part.arguments as Record<string, unknown>
130
78
  : {};
131
- if (typeof args.command === "string" && isMutatingBashCommand(args.command)) return true;
79
+ if (isMutatingTool(part.name, args)) return true;
132
80
  }
133
81
  }
134
82
  return false;
135
83
  }
136
84
 
137
85
  export function evaluateCompletionMutationGuard(input: CompletionMutationGuardInput): CompletionMutationGuardResult {
138
- const expectedMutation = declaresOnlyReadOnlyTools(input.tools, input.mcpDirectTools)
139
- ? false
140
- : expectsImplementationMutation(input.agent, input.task);
86
+ const expectedMutation = hasMutationToolCapability(input.tools, input.mcpDirectTools)
87
+ ? expectsImplementationMutation(input.agent, input.task)
88
+ : false;
141
89
  const attemptedMutation = hasMutationToolCall(input.messages);
142
90
  return {
143
91
  expectedMutation,
@@ -0,0 +1,44 @@
1
+ export type ContextMode = "fresh" | "fork";
2
+ export type ContextSummary = ContextMode | "mixed";
3
+
4
+ export function isContextMode(value: unknown): value is ContextMode {
5
+ return value === "fresh" || value === "fork";
6
+ }
7
+
8
+ export function isContextSummary(value: unknown): value is ContextSummary {
9
+ return isContextMode(value) || value === "mixed";
10
+ }
11
+
12
+ export function summarizeContextModes(modes: Array<ContextMode | undefined>): ContextSummary | undefined {
13
+ const resolved = modes.filter(isContextMode);
14
+ if (resolved.length === 0) return undefined;
15
+ const first = resolved[0]!;
16
+ return resolved.every((mode) => mode === first) ? first : "mixed";
17
+ }
18
+
19
+ export function contextModeLabel(mode: ContextMode | ContextSummary | undefined): string {
20
+ if (mode === "fork") return "[fork]";
21
+ if (mode === "fresh") return "[fresh]";
22
+ if (mode === "mixed") return "[mixed]";
23
+ return "";
24
+ }
25
+
26
+ export function contextModeBadge(
27
+ theme: { fg(name: string, text: string): string },
28
+ mode: ContextMode | ContextSummary | undefined,
29
+ ): string {
30
+ const label = contextModeLabel(mode);
31
+ if (!label) return "";
32
+ if (mode === "fork") return theme.fg("warning", ` ${label}`);
33
+ return theme.fg("dim", ` ${label}`);
34
+ }
35
+
36
+ export function contextModePrefix(
37
+ theme: { fg(name: string, text: string): string },
38
+ mode: ContextMode | ContextSummary | undefined,
39
+ ): string {
40
+ const label = contextModeLabel(mode);
41
+ if (!label) return "";
42
+ if (mode === "fork") return `${theme.fg("warning", label)} `;
43
+ return `${theme.fg("dim", label)} `;
44
+ }
@@ -27,6 +27,7 @@ export interface DynamicCollectedResult {
27
27
  structured?: unknown;
28
28
  error?: string;
29
29
  timedOut?: boolean;
30
+ stopped?: boolean;
30
31
  outputPath?: string;
31
32
  artifactPaths?: ArtifactPaths;
32
33
  }
@@ -41,16 +42,16 @@ const SAFE_OUTPUT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
41
42
  const ITEM_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
42
43
  const ITEM_REF_PATTERN = /\{([A-Za-z_][A-Za-z0-9_]*)(?:\.([^{}]+))?\}/g;
43
44
  const RESERVED_TEMPLATE_NAMES = new Set(["task", "previous", "chain_dir", "outputs"]);
44
- const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label", "acceptance"]);
45
- const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance", "sessionFiles", "thinkingOverrides"]);
45
+ const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label", "acceptance", "agentContract", "gateOn"]);
46
+ const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "sessionFiles", "thinkingOverrides"]);
46
47
  const DYNAMIC_EXPAND_KEYS = new Set(["from", "item", "key", "maxItems", "onEmpty"]);
47
48
  const DYNAMIC_EXPAND_FROM_KEYS = new Set(["output", "path"]);
48
- const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model", "toolBudget", "acceptance"]);
49
+ const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model", "toolBudget", "acceptance", "agentContract", "gateOn"]);
49
50
  const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
50
51
  ...DYNAMIC_PARALLEL_KEYS,
51
- "outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "maxSubagentDepth",
52
- "structuredOutput", "structuredOutputSchema", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "completionGuard", "systemPrompt",
53
- "systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "parentSessionId",
52
+ "outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "namespaceOutputPath", "maxSubagentDepth", "waitToolEnabled",
53
+ "structuredOutput", "structuredOutputSchema", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "capabilityCeiling", "completionGuard", "systemPrompt",
54
+ "systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance", "acceptanceInput", "acceptanceRole", "parentSessionId",
54
55
  ]);
55
56
  const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
56
57
 
@@ -263,7 +264,7 @@ export function materializeDynamicParallelStep(step: DynamicParallelStep, output
263
264
  export function collectDynamicResults(
264
265
  step: DynamicParallelStep,
265
266
  items: DynamicMaterializedItem[],
266
- results: Array<Pick<SingleResult, "agent" | "exitCode" | "error" | "timedOut" | "structuredOutput" | "artifactPaths" | "savedOutputPath"> & { output?: string; finalOutput?: string }>,
267
+ results: Array<Pick<SingleResult, "agent" | "exitCode" | "error" | "timedOut" | "stopped" | "structuredOutput" | "artifactPaths" | "savedOutputPath"> & { output?: string; finalOutput?: string }>,
267
268
  ): DynamicCollectedResult[] {
268
269
  return items.map((entry, index) => {
269
270
  const result = results[index];
@@ -280,15 +281,16 @@ export function collectDynamicResults(
280
281
  ...(result?.structuredOutput !== undefined ? { structured: result.structuredOutput } : {}),
281
282
  ...(result?.error ? { error: result.error } : {}),
282
283
  ...(result?.timedOut ? { timedOut: true } : {}),
284
+ ...(result?.stopped ? { stopped: true } : {}),
283
285
  ...(result?.savedOutputPath ? { outputPath: result.savedOutputPath } : {}),
284
286
  ...(result?.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
285
287
  };
286
288
  });
287
289
  }
288
290
 
289
- export function validateDynamicCollection(schema: JsonSchemaObject | undefined, value: DynamicCollectedResult[]): void {
291
+ export async function validateDynamicCollection(schema: JsonSchemaObject | undefined, value: DynamicCollectedResult[]): Promise<void> {
290
292
  if (!schema) return;
291
- const validation = validateStructuredOutputValue(schema, value);
293
+ const validation = await validateStructuredOutputValue(schema, value);
292
294
  if (validation.status === "invalid") {
293
295
  throw new DynamicFanoutError(`Collected output validation failed: ${validation.message}`);
294
296
  }
@@ -103,6 +103,10 @@ export function isMutatingBashCommand(command: string): boolean {
103
103
  export function isMutatingTool(toolName: string | undefined, args: Record<string, unknown> | undefined): boolean {
104
104
  if (!toolName) return false;
105
105
  if (toolName === "edit" || toolName === "write") return true;
106
+ if (toolName === "cursor") {
107
+ const activityTitle = typeof args?.activityTitle === "string" ? args.activityTitle : "";
108
+ return /^Cursor (?:edit|write)\b/i.test(activityTitle);
109
+ }
106
110
  if (toolName !== "bash") return false;
107
111
  const command = typeof args?.command === "string" ? args.command : "";
108
112
  if (!command.trim()) return false;