@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,326 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { describe, it } from "node:test";
6
+ import { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from "../../src/runs/shared/pi-args.ts";
7
+ import { WAIT_TOOL_ENABLED_ENV } from "../../src/runs/background/subagent-wait.ts";
8
+
9
+ type JsonSchemaNode = Record<string, unknown>;
10
+
11
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
12
+
13
+ function parentToolEnv(): NodeJS.ProcessEnv {
14
+ const env = { ...process.env };
15
+ delete env[SUBAGENT_CHILD_ENV];
16
+ delete env[SUBAGENT_FANOUT_CHILD_ENV];
17
+ delete env[WAIT_TOOL_ENABLED_ENV];
18
+ return env;
19
+ }
20
+
21
+ function missingPackageName(error: unknown): string | undefined {
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ return message.match(/Cannot find package ['"]([^'"]+)['"]/i)?.[1];
24
+ }
25
+
26
+ let validateChainInput: (args: unknown) => void = () => {};
27
+ let validateSubagentParams: (args: unknown) => boolean = () => true;
28
+ let chainItemProperties: Record<string, unknown> | undefined;
29
+ let dynamicTemplateProperties: Record<string, unknown> | undefined;
30
+ let schemasAvailable = true;
31
+ try {
32
+ const mod = await import("../../src/extension/chain-validation.ts") as {
33
+ validateChainInput: (args: unknown) => void;
34
+ CHAIN_STEP_KEYS: string[];
35
+ PARALLEL_TASK_KEYS: string[];
36
+ DYNAMIC_TEMPLATE_KEYS: string[];
37
+ };
38
+ validateChainInput = mod.validateChainInput;
39
+ const schemas = await import("../../src/extension/schemas.ts") as {
40
+ ChainItem: JsonSchemaNode;
41
+ DynamicParallelTemplateSchema: JsonSchemaNode;
42
+ SubagentParams: JsonSchemaNode;
43
+ };
44
+ const { Compile } = await import("typebox/compile") as unknown as {
45
+ Compile: (schema: JsonSchemaNode) => { Check(value: unknown): boolean };
46
+ };
47
+ const subagentParamsValidator = Compile(schemas.SubagentParams);
48
+ validateSubagentParams = (args) => subagentParamsValidator.Check(args);
49
+ chainItemProperties = schemas.ChainItem.properties as Record<string, unknown> | undefined;
50
+ dynamicTemplateProperties = schemas.DynamicParallelTemplateSchema.properties as Record<string, unknown> | undefined;
51
+ } catch (error) {
52
+ if (missingPackageName(error) !== "typebox") throw error;
53
+ schemasAvailable = false;
54
+ }
55
+
56
+ function expectInvalid(args: unknown, ...patterns: RegExp[]): void {
57
+ assert.throws(
58
+ () => validateChainInput(args),
59
+ (error) => {
60
+ assert.ok(error instanceof Error, "validation should throw an Error");
61
+ const message = error.message;
62
+ for (const pattern of patterns) {
63
+ assert.match(message, pattern, `expected message to match ${pattern}; got: ${message}`);
64
+ }
65
+ return true;
66
+ },
67
+ `expected validation to fail for ${JSON.stringify(args)}`,
68
+ );
69
+ }
70
+
71
+ describe("chain input validation", { skip: !schemasAvailable ? "typebox not available" : undefined }, () => {
72
+ it("is a no-op when chain is absent or not an array", () => {
73
+ assert.doesNotThrow(() => validateChainInput({}));
74
+ assert.doesNotThrow(() => validateChainInput({ agent: "worker", task: "do X" }));
75
+ assert.doesNotThrow(() => validateChainInput({ chain: "not-an-array" }));
76
+ assert.doesNotThrow(() => validateChainInput({ action: "status" }));
77
+ assert.doesNotThrow(() => validateChainInput(undefined));
78
+ assert.doesNotThrow(() => validateChainInput(null));
79
+ });
80
+
81
+ it("accepts valid chain steps without throwing", () => {
82
+ assert.doesNotThrow(() => validateChainInput({ chain: [{ agent: "worker", task: "do X" }] }));
83
+ assert.doesNotThrow(() =>
84
+ validateChainInput({ chain: [{ agent: "worker", task: "do X", parallel: [{ agent: "reviewer", task: "review" }] }] }),
85
+ );
86
+ assert.doesNotThrow(() =>
87
+ validateChainInput({
88
+ chain: [{
89
+ expand: { from: { output: "targets", path: "/items" }, maxItems: 4 },
90
+ parallel: { agent: "reviewer", task: "Review {item.path}" },
91
+ collect: { as: "reviews" },
92
+ }],
93
+ }),
94
+ );
95
+ });
96
+
97
+ it("names the disallowed property and lists allowed properties for a chain step", () => {
98
+ expectInvalid(
99
+ { chain: [{ agent: "worker", task: "do X", bogus: true }] },
100
+ /chain\[0\]/,
101
+ /"bogus" is not allowed/,
102
+ /Allowed properties:/,
103
+ /Example:/,
104
+ );
105
+ });
106
+
107
+ it("lists every allowed chain step property from the schema", () => {
108
+ const allowed = Object.keys(chainItemProperties ?? {});
109
+ assert.ok(allowed.includes("agent"));
110
+ assert.ok(allowed.includes("parallel"));
111
+ assert.ok(allowed.includes("expand"));
112
+ try {
113
+ validateChainInput({ chain: [{ notReal: 1 }] });
114
+ assert.fail("should have thrown");
115
+ } catch (error) {
116
+ assert.ok(error instanceof Error);
117
+ for (const key of allowed) {
118
+ assert.match(error.message, new RegExp(`\\b${key}\\b`), `allowed key ${key} should appear in message: ${error.message}`);
119
+ }
120
+ }
121
+ });
122
+
123
+ it("reports multiple disallowed properties at once", () => {
124
+ expectInvalid(
125
+ { chain: [{ agent: "worker", foo: 1, bar: 2 }] },
126
+ /chain\[0\]/,
127
+ /"foo"/,
128
+ /"bar"/,
129
+ );
130
+ });
131
+
132
+ it("explains expected shape when a chain step is not an object", () => {
133
+ expectInvalid(
134
+ { chain: ["not-an-object"] },
135
+ /chain\[0\]/,
136
+ /expected an object/,
137
+ /string/,
138
+ /Allowed properties:/,
139
+ /Example:/,
140
+ );
141
+ expectInvalid(
142
+ { chain: [42] },
143
+ /chain\[0\]/,
144
+ /expected an object/,
145
+ /number/,
146
+ );
147
+ });
148
+
149
+ it("preserves static parallel extra properties accepted by the TypeBox schema", () => {
150
+ const args = { chain: [{ parallel: [{ agent: "worker", extensionField: true }] }] };
151
+ assert.equal(validateSubagentParams(args), true);
152
+ assert.doesNotThrow(() => validateChainInput(args));
153
+ });
154
+
155
+ it("names disallowed properties on a dynamic fanout template", () => {
156
+ expectInvalid(
157
+ {
158
+ chain: [{
159
+ expand: { from: { output: "targets", path: "/items" }, maxItems: 4 },
160
+ parallel: { agent: "worker", bogus: true },
161
+ collect: { as: "reviews" },
162
+ }],
163
+ },
164
+ /chain\[0\]\.parallel/,
165
+ /"bogus" is not allowed/,
166
+ /Allowed properties:/,
167
+ );
168
+ });
169
+
170
+ it("lists every allowed dynamic template property from the schema", () => {
171
+ const allowed = Object.keys(dynamicTemplateProperties ?? {});
172
+ assert.ok(allowed.includes("agent"));
173
+ assert.ok(allowed.includes("outputSchema"));
174
+ try {
175
+ validateChainInput({
176
+ chain: [{
177
+ expand: { from: { output: "x", path: "/items" }, maxItems: 4 },
178
+ parallel: { notReal: 1 },
179
+ collect: { as: "reviews" },
180
+ }],
181
+ });
182
+ assert.fail("should have thrown");
183
+ } catch (error) {
184
+ assert.ok(error instanceof Error);
185
+ for (const key of allowed) {
186
+ assert.match(error.message, new RegExp(`\\b${key}\\b`), `allowed key ${key} should appear: ${error.message}`);
187
+ }
188
+ }
189
+ });
190
+
191
+ it("guides the agent when parallel is neither an array nor an object", () => {
192
+ expectInvalid(
193
+ { chain: [{ parallel: "nope" }] },
194
+ /chain\[0\]\.parallel/,
195
+ /expected an array of task objects or a dynamic fanout template object/,
196
+ /string/,
197
+ );
198
+ });
199
+
200
+ it("names disallowed properties on expand", () => {
201
+ expectInvalid(
202
+ {
203
+ chain: [{
204
+ expand: { from: { output: "x", path: "/items" }, maxItems: 4, bogus: true },
205
+ parallel: { agent: "worker" },
206
+ collect: { as: "reviews" },
207
+ }],
208
+ },
209
+ /chain\[0\]\.expand/,
210
+ /"bogus" is not allowed/,
211
+ /Allowed properties:/,
212
+ );
213
+ });
214
+
215
+ it("names disallowed properties on expand.from", () => {
216
+ expectInvalid(
217
+ {
218
+ chain: [{
219
+ expand: { from: { output: "x", path: "/items", bogus: true }, maxItems: 4 },
220
+ parallel: { agent: "worker" },
221
+ collect: { as: "reviews" },
222
+ }],
223
+ },
224
+ /chain\[0\]\.expand\.from/,
225
+ /"bogus" is not allowed/,
226
+ /Allowed properties:/,
227
+ );
228
+ });
229
+
230
+ it("names disallowed properties on collect", () => {
231
+ expectInvalid(
232
+ {
233
+ chain: [{
234
+ expand: { from: { output: "x", path: "/items" }, maxItems: 4 },
235
+ parallel: { agent: "worker" },
236
+ collect: { as: "reviews", bogus: true },
237
+ }],
238
+ },
239
+ /chain\[0\]\.collect/,
240
+ /"bogus" is not allowed/,
241
+ /Allowed properties:/,
242
+ );
243
+ });
244
+
245
+ it("reports the correct chain index for multi-step chains", () => {
246
+ expectInvalid(
247
+ { chain: [{ agent: "worker", task: "ok" }, { agent: "worker", bogus: true }] },
248
+ /chain\[1\]/,
249
+ /"bogus" is not allowed/,
250
+ );
251
+ });
252
+ });
253
+
254
+ describe("registered subagent tool prepareArguments", { skip: !schemasAvailable ? "typebox not available" : undefined }, () => {
255
+ function runPrepare(args: unknown): { error?: string; ok: true } | { error: string; ok: false } {
256
+ const script = String.raw`
257
+ import registerSubagentExtension from "./src/extension/index.ts";
258
+ const events = { on() { return () => {}; }, emit() {} };
259
+ let registeredTool;
260
+ const fakePi = new Proxy({
261
+ events,
262
+ registerTool(tool) { if (tool.name === "subagent") registeredTool = tool; },
263
+ registerCommand() {},
264
+ registerShortcut() {},
265
+ registerMessageRenderer() {},
266
+ sendMessage() {},
267
+ getSessionName() { return undefined; },
268
+ }, {
269
+ get(target, prop) {
270
+ if (prop in target) return target[prop];
271
+ return () => undefined;
272
+ },
273
+ });
274
+ registerSubagentExtension(fakePi);
275
+ if (!registeredTool) throw new Error("tool not registered");
276
+ if (typeof registeredTool.prepareArguments !== "function") throw new Error("prepareArguments not attached");
277
+ const args = JSON.parse(process.argv[process.argv.length - 1] ?? "{}");
278
+ try {
279
+ registeredTool.prepareArguments(args);
280
+ process.stdout.write(JSON.stringify({ ok: true }));
281
+ } catch (error) {
282
+ process.stdout.write(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }));
283
+ }
284
+ `;
285
+ const output = execFileSync(
286
+ process.execPath,
287
+ [
288
+ "--experimental-transform-types",
289
+ "--import",
290
+ "./test/support/register-loader.mjs",
291
+ "--input-type=module",
292
+ "--eval",
293
+ script,
294
+ "--",
295
+ JSON.stringify(args),
296
+ ],
297
+ { cwd: projectRoot, env: parentToolEnv(), encoding: "utf-8", stdio: "pipe" },
298
+ );
299
+ return JSON.parse(output) as { error?: string; ok: boolean };
300
+ }
301
+
302
+ it("throws a friendly chain error before schema validation when a step has a disallowed property", () => {
303
+ const result = runPrepare({ chain: [{ agent: "worker", task: "do X", bogus: true }] });
304
+ assert.equal(result.ok, false);
305
+ assert.match(result.error ?? "", /chain\[0\]/);
306
+ assert.match(result.error ?? "", /"bogus" is not allowed/);
307
+ assert.match(result.error ?? "", /Allowed properties:/);
308
+ assert.match(result.error ?? "", /Example:/);
309
+ });
310
+
311
+ it("passes valid chain arguments through unchanged", () => {
312
+ const result = runPrepare({ chain: [{ agent: "worker", task: "do X" }] });
313
+ assert.equal(result.ok, true);
314
+ });
315
+
316
+ it("preserves static parallel extra properties accepted by the registered schema", () => {
317
+ const result = runPrepare({ chain: [{ parallel: [{ agent: "worker", extensionField: true }] }] });
318
+ assert.equal(result.ok, true);
319
+ });
320
+
321
+ it("is a no-op for management actions and single/parallel calls", () => {
322
+ assert.equal(runPrepare({ action: "status" }).ok, true);
323
+ assert.equal(runPrepare({ agent: "worker", task: "do X" }).ok, true);
324
+ assert.equal(runPrepare({ tasks: [{ agent: "worker", task: "do X" }] }).ok, true);
325
+ });
326
+ });
@@ -0,0 +1,82 @@
1
+ import assert from "node:assert/strict";
2
+ import { Buffer } from "node:buffer";
3
+ import { describe, it } from "node:test";
4
+ import {
5
+ createBoundedByteTail,
6
+ createBoundedLineReader,
7
+ formatProtocolOutputLimit,
8
+ MAX_CHILD_PENDING_LINE_BYTES,
9
+ projectChildLifecycle,
10
+ type ProtocolOutputLimit,
11
+ } from "../../src/runs/shared/child-protocol.ts";
12
+
13
+ describe("bounded child protocol reader", () => {
14
+ it("reassembles fragmented lines and flushes a final unterminated line", () => {
15
+ const lines: string[] = [];
16
+ const reader = createBoundedLineReader({ maxPendingLineBytes: 100, onLine: (line) => lines.push(line), onLimit: () => assert.fail("unexpected limit") });
17
+ reader.push('{"type":"message');
18
+ reader.push('_end"}\n{"type":"agent_settled"}');
19
+ reader.end();
20
+ assert.deepEqual(lines, ['{"type":"message_end"}', '{"type":"agent_settled"}']);
21
+ });
22
+
23
+ it("preserves UTF-8 characters split across byte chunks", () => {
24
+ const lines: string[] = [];
25
+ const bytes = Buffer.from('{"text":"你好"}\n');
26
+ const split = bytes.indexOf(Buffer.from("你")) + 1;
27
+ const reader = createBoundedLineReader({ maxPendingLineBytes: 100, onLine: (line) => lines.push(line), onLimit: () => assert.fail("unexpected limit") });
28
+ reader.push(bytes.subarray(0, split));
29
+ reader.push(bytes.subarray(split));
30
+ reader.end();
31
+ assert.deepEqual(lines, ['{"text":"你好"}']);
32
+ });
33
+
34
+ it("accepts Pi-sized image payload lines by default", () => {
35
+ const lines: string[] = [];
36
+ const reader = createBoundedLineReader({ onLine: (line) => lines.push(line), onLimit: () => assert.fail("unexpected limit") });
37
+ const imageSizedLine = "x".repeat(5 * 1024 * 1024);
38
+ reader.push(imageSizedLine);
39
+ reader.push("\n");
40
+ reader.end();
41
+ assert.equal(MAX_CHILD_PENDING_LINE_BYTES, 16 * 1024 * 1024);
42
+ assert.equal(lines.length, 1);
43
+ assert.equal(lines[0]?.length, imageSizedLine.length);
44
+ });
45
+
46
+ it("stops buffering an oversized line and returns bounded diagnostics", () => {
47
+ let failure: ProtocolOutputLimit | undefined;
48
+ const reader = createBoundedLineReader({ maxPendingLineBytes: 8, onLine: () => assert.fail("oversized line must not emit"), onLimit: (limit) => { failure = limit; } });
49
+ reader.push("prefix-");
50
+ reader.push("oversized-tail");
51
+ reader.push("ignored");
52
+ reader.end();
53
+ assert.equal(reader.exceeded(), true);
54
+ assert.equal(failure?.code, "protocol_output_limit");
55
+ assert.equal(failure?.limitBytes, 8);
56
+ assert.equal(failure?.observedBytes, 21);
57
+ assert.match(failure?.diagnosticPrefix ?? "", /^prefix-/);
58
+ assert.match(failure?.diagnosticTail ?? "", /tail$/);
59
+ assert.match(formatProtocolOutputLimit(failure!), /protocol_output_limit.*exceeded 8 bytes/);
60
+ });
61
+ });
62
+
63
+ describe("bounded child stderr tail", () => {
64
+ it("keeps only the configured UTF-8-safe byte tail", () => {
65
+ const tail = createBoundedByteTail(8);
66
+ const bytes = Buffer.from("old-你好-tail");
67
+ tail.push(bytes.subarray(0, 7));
68
+ tail.push(bytes.subarray(7));
69
+ assert.ok(tail.byteLength() <= 8);
70
+ assert.equal(tail.text(), "好-tail");
71
+ });
72
+ });
73
+
74
+ describe("child lifecycle projection", () => {
75
+ it("cancels legacy drain for retries and starts it when settled", () => {
76
+ assert.equal(projectChildLifecycle({ type: "message_end" }, true), "start-drain");
77
+ assert.equal(projectChildLifecycle({ type: "agent_end", willRetry: true }), "cancel-drain");
78
+ assert.equal(projectChildLifecycle({ type: "agent_end", willRetry: false }), "none");
79
+ assert.equal(projectChildLifecycle({ type: "agent_settled" }), "start-drain");
80
+ assert.equal(projectChildLifecycle({ type: "tool_execution_start" }), "none");
81
+ });
82
+ });
@@ -84,7 +84,7 @@ describe("createChildTranscriptWriter", () => {
84
84
  });
85
85
  writer.writeChildEvent({
86
86
  type: "tool_result_end",
87
- message: { role: "toolResult", toolName: "bash", content: [{ type: "text", text: "ok" }] },
87
+ message: { role: "toolResult", toolCallId: "call-1", toolName: "bash", content: [{ type: "text", text: "ok" }], isError: false },
88
88
  });
89
89
 
90
90
  const records = readRecords(transcriptPath);
@@ -99,6 +99,17 @@ describe("createChildTranscriptWriter", () => {
99
99
  assert.equal(records[1]!.recordType, "message");
100
100
  assert.equal(records[1]!.sourceEventType, "tool_result_end");
101
101
  assert.equal(records[1]!.role, "toolResult");
102
+ assert.equal(records[1]!.toolCallId, "call-1");
103
+ assert.equal(records[1]!.toolName, "bash");
104
+ assert.equal(records[1]!.isError, false);
105
+ assert.equal(records[1]!.text, "ok");
106
+ assert.deepEqual(records[1]!.message, {
107
+ role: "toolResult",
108
+ toolCallId: "call-1",
109
+ toolName: "bash",
110
+ isError: false,
111
+ content: [{ type: "text", text: "ok" }],
112
+ });
102
113
  });
103
114
 
104
115
  it("writes tool_start and tool_end records and ignores unhandled event types", () => {
@@ -111,22 +122,53 @@ describe("createChildTranscriptWriter", () => {
111
122
  agent: "worker",
112
123
  cwd: "/repo",
113
124
  });
114
- writer.writeChildEvent({ type: "tool_execution_start", toolName: "bash", args: { command: "ls" } });
115
- writer.writeChildEvent({ type: "tool_execution_start", toolName: "read" });
116
- writer.writeChildEvent({ type: "tool_execution_end", toolName: "bash" });
125
+ writer.writeChildEvent({ type: "tool_execution_start", toolCallId: "call-1", toolName: "bash", args: { command: "ls" } });
126
+ writer.writeChildEvent({ type: "tool_execution_start", toolCallId: "call-2", toolName: "read" });
127
+ writer.writeChildEvent({ type: "tool_execution_end", toolCallId: "call-1", toolName: "bash", isError: false });
117
128
  writer.writeChildEvent({ type: "some_unhandled_event" });
118
129
 
119
130
  const records = readRecords(transcriptPath);
120
131
  assert.equal(records.length, 3);
121
132
  assert.equal(records[0]!.recordType, "tool_start");
122
133
  assert.equal(records[0]!.sourceEventType, "tool_execution_start");
134
+ assert.equal(records[0]!.toolCallId, "call-1");
123
135
  assert.equal(records[0]!.toolName, "bash");
124
136
  assert.equal(typeof records[0]!.argsPreview, "string");
125
137
  assert.ok(String(records[0]!.argsPreview).length > 0);
138
+ assert.equal(records[0]!.argsPayload, JSON.stringify({ command: "ls" }, null, 2));
126
139
  assert.equal(records[1]!.recordType, "tool_start");
140
+ assert.equal(records[1]!.toolCallId, "call-2");
127
141
  assert.equal(Object.prototype.hasOwnProperty.call(records[1]!, "argsPreview"), false);
128
142
  assert.equal(records[2]!.recordType, "tool_end");
143
+ assert.equal(records[2]!.toolCallId, "call-1");
129
144
  assert.equal(records[2]!.toolName, "bash");
145
+ assert.equal(records[2]!.isError, false);
146
+ });
147
+
148
+ it("bounds persisted tool arguments and successful output", () => {
149
+ const dir = tmpDir();
150
+ const transcriptPath = path.join(dir, "transcript.jsonl");
151
+ const writer = createChildTranscriptWriter({
152
+ transcriptPath,
153
+ source: "foreground",
154
+ runId: "run-bounded",
155
+ agent: "worker",
156
+ cwd: "/repo",
157
+ });
158
+ writer.writeChildEvent({ type: "tool_execution_start", toolCallId: "large", toolName: "bash", args: { command: "🧪".repeat(10_000) } });
159
+ writer.writeChildEvent({
160
+ type: "tool_result_end",
161
+ message: { role: "toolResult", toolCallId: "large", toolName: "bash", content: [{ type: "text", text: "🧪".repeat(10_000) }], isError: false },
162
+ });
163
+
164
+ const records = readRecords(transcriptPath);
165
+ assert.ok(Buffer.byteLength(String(records[0]!.argsPayload), "utf-8") <= 32 * 1024);
166
+ assert.match(String(records[0]!.argsPayload), /… payload truncated$/);
167
+ assert.doesNotMatch(String(records[0]!.argsPayload), /�/);
168
+ assert.ok(Buffer.byteLength(String(records[1]!.text), "utf-8") <= 32 * 1024);
169
+ assert.match(String(records[1]!.text), /… payload truncated$/);
170
+ assert.doesNotMatch(String(records[1]!.text), /�/);
171
+ assert.equal(records[1]!.outputTruncated, true);
130
172
  });
131
173
 
132
174
  it("skips blank stdout/stderr lines and splits multi-line stderr text", () => {
@@ -170,7 +170,9 @@ describe("createCompletionBatcher", () => {
170
170
  });
171
171
 
172
172
  batcher.push(item("a"));
173
- batcher.dispose();
173
+ const abandoned = batcher.dispose();
174
+ assert.deepEqual(abandoned.map((entry) => entry.label), ["a"]);
175
+ assert.deepEqual(batcher.dispose(), []);
174
176
  assert.deepEqual(emitted, []);
175
177
  assert.equal(clock.pendingCount(), 0);
176
178
  clock.advance(1000);
@@ -1,17 +1,19 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { describe, it } from "node:test";
3
- import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "../../src/runs/background/completion-dedupe.ts";
3
+ import { buildCompletionKey, markSeenWithTtl } from "../../src/runs/background/completion-dedupe.ts";
4
4
 
5
5
  describe("buildCompletionKey", () => {
6
- it("uses id as canonical key when present", () => {
7
- const key = buildCompletionKey({ id: "run-123", agent: "reviewer", timestamp: 123 }, "fallback");
8
- assert.equal(key, "id:run-123");
6
+ it("scopes ids to their owning session", () => {
7
+ const first = buildCompletionKey({ id: "run-123", sessionId: "session-a" }, "fallback");
8
+ const second = buildCompletionKey({ id: "run-123", sessionId: "session-b" }, "fallback");
9
+ assert.equal(first, "session:session-a:id:run-123");
10
+ assert.notEqual(first, second);
9
11
  });
10
12
 
11
13
  it("builds deterministic fallback key when id is missing", () => {
12
- const a = buildCompletionKey({ agent: "reviewer", timestamp: 123, taskIndex: 1, totalTasks: 2, success: true }, "x");
13
- const b = buildCompletionKey({ agent: "reviewer", timestamp: 123, taskIndex: 1, totalTasks: 2, success: true }, "x");
14
- assert.equal(a, b);
14
+ const first = buildCompletionKey({ agent: "reviewer", timestamp: 123, taskIndex: 1, totalTasks: 2, success: true }, "x");
15
+ const second = buildCompletionKey({ agent: "reviewer", timestamp: 123, taskIndex: 1, totalTasks: 2, success: true }, "x");
16
+ assert.equal(first, second);
15
17
  });
16
18
  });
17
19
 
@@ -24,13 +26,3 @@ describe("markSeenWithTtl", () => {
24
26
  assert.equal(markSeenWithTtl(seen, "k", 1201, ttlMs), false);
25
27
  });
26
28
  });
27
-
28
- describe("getGlobalSeenMap", () => {
29
- it("returns the same map for the same global store key", () => {
30
- const a = getGlobalSeenMap("__test_seen_key__");
31
- a.set("x", 1);
32
- const b = getGlobalSeenMap("__test_seen_key__");
33
- assert.equal(b.get("x"), 1);
34
- assert.equal(a, b);
35
- });
36
- });