@desplega.ai/agent-swarm 1.158.0 → 1.160.0

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 (378) hide show
  1. package/README.md +1 -1
  2. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-2mjwcsfh.js} +124 -27
  3. package/dist/{actions-p9hz5yat.js → actions-e43rxskr.js} +35 -30
  4. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-5qx80j9k.js} +106 -16
  5. package/dist/{app-g26wdcz6.js → app-mc7xfagn.js} +12 -10
  6. package/dist/{assistant-wydfqkb3.js → assistant-sedkzv3b.js} +42 -31
  7. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-nfb21z9a.js} +13 -14
  8. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-cqz9swn5.js} +13 -12
  9. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-m3656j2n.js} +14 -13
  10. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-xbxf5d7w.js} +11 -9
  11. package/dist/boot-seeds-gsetbs5y.js +28 -0
  12. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-dc6289mc.js} +28 -23
  13. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-naaw51me.js} +4 -3
  14. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-eacvchx0.js} +12 -3
  15. package/dist/claude-sdk-session-bqp2xvym.js +29722 -0
  16. package/dist/{cli-987hg0aa.js → cli-0jwe9034.js} +2 -2
  17. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  18. package/dist/{cli-c07r9a2m.js → cli-0q3avq3s.js} +12 -11
  19. package/dist/{cli-19jenss3.js → cli-0wgxmwf6.js} +31 -10
  20. package/dist/cli-0x01kwrd.js +491 -0
  21. package/dist/cli-0xqde53b.js +176 -0
  22. package/dist/cli-10nhj6xq.js +8355 -0
  23. package/dist/{cli-tt9k8r35.js → cli-12tszpk9.js} +479 -910
  24. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  25. package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
  26. package/dist/{cli-xberxa1r.js → cli-25fcq4b4.js} +2 -2
  27. package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
  28. package/dist/cli-3qhyh31j.js +166 -0
  29. package/dist/cli-447dt1h3.js +3828 -0
  30. package/dist/cli-4derpgyk.js +115 -0
  31. package/dist/{cli-00w2c66p.js → cli-523679eh.js} +13 -2
  32. package/dist/{cli-y95wp3tg.js → cli-58e8yr0d.js} +1 -1
  33. package/dist/{cli-24k806sc.js → cli-5nbmpy8w.js} +3 -3
  34. package/dist/{cli-53sz8gkz.js → cli-6cs6wzxt.js} +1 -1
  35. package/dist/cli-741yb0xx.js +1027 -0
  36. package/dist/cli-74h9hscv.js +69 -0
  37. package/dist/cli-7cxw8b60.js +14910 -0
  38. package/dist/{cli-xhaeg2nc.js → cli-7e398j1h.js} +1 -1
  39. package/dist/cli-7kyk9jr3.js +22 -0
  40. package/dist/cli-89s4ngw3.js +183 -0
  41. package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
  42. package/dist/{cli-ftfvggzx.js → cli-9hsgwz4w.js} +1045 -581
  43. package/dist/{cli-rqz8qbw7.js → cli-a8dvhzrt.js} +59 -20
  44. package/dist/{cli-j57qkrpq.js → cli-azhgzetg.js} +395 -56
  45. package/dist/cli-br0qdrnp.js +2150 -0
  46. package/dist/{cli-rj19hc34.js → cli-btfv8y88.js} +1 -1
  47. package/dist/{cli-8qmme6vd.js → cli-c8a36vcc.js} +2 -2
  48. package/dist/cli-cpvsaqsw.js +333 -0
  49. package/dist/cli-d5czme6m.js +17805 -0
  50. package/dist/{cli-h38x0qhg.js → cli-et8m9xsv.js} +4 -3
  51. package/dist/{cli-x24pshtq.js → cli-evvtk35y.js} +6 -6
  52. package/dist/{cli-7s3syk22.js → cli-f8gy6r2r.js} +5 -5
  53. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  54. package/dist/{cli-9httdpyq.js → cli-g2whhdrf.js} +1 -1
  55. package/dist/cli-gt4hxb8p.js +99 -0
  56. package/dist/{cli-s8eet254.js → cli-jajnsm8q.js} +37184 -53838
  57. package/dist/{cli-7gtvvpbh.js → cli-jayjpjmb.js} +21 -12
  58. package/dist/{cli-68w09sjw.js → cli-jyk0ynw2.js} +27 -89
  59. package/dist/{cli-aehm1rbq.js → cli-jz4w45qt.js} +1152 -61
  60. package/dist/cli-kmcpfwn2.js +333856 -0
  61. package/dist/{cli-4198qc9v.js → cli-ktmyrtpn.js} +19 -3
  62. package/dist/{cli-j55n64b6.js → cli-mfe5btp9.js} +505 -189
  63. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  64. package/dist/{cli-1hkrch3k.js → cli-pm1rst0h.js} +46 -7
  65. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  66. package/dist/{cli-ggm92wcn.js → cli-ptr8h7kp.js} +20 -1
  67. package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
  68. package/dist/{cli-47m6dkd3.js → cli-qydf22qq.js} +597 -346
  69. package/dist/{cli-3gmcwfyx.js → cli-r1mxd1xh.js} +13 -6
  70. package/dist/cli-rb9m6rpe.js +4980 -0
  71. package/dist/cli-rykfmgwg.js +102 -0
  72. package/dist/{cli-5d10vd2m.js → cli-shkxm1vn.js} +6 -5
  73. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  74. package/dist/cli-tfky2838.js +756 -0
  75. package/dist/{cli-g8djp6qe.js → cli-tmqt7dpj.js} +40 -0
  76. package/dist/{cli-fgqxwrpv.js → cli-tpq83z89.js} +1631 -127
  77. package/dist/cli-trc24wk2.js +143 -0
  78. package/dist/{cli-aky8tctm.js → cli-tzx2ke1v.js} +3 -3
  79. package/dist/cli-v7fn5gms.js +1425 -0
  80. package/dist/{cli-wscdvcqz.js → cli-vctdqs5h.js} +1 -1
  81. package/dist/{cli-cw9zck8j.js → cli-xjcywfsf.js} +8 -4
  82. package/dist/{cli-k0g1sjzm.js → cli-ybgv4kw2.js} +18 -11
  83. package/dist/{cli-rjehvrts.js → cli-ydmcgve1.js} +8 -8
  84. package/dist/{cli-vhjwh6cz.js → cli-ygwm91nt.js} +1 -1
  85. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  86. package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
  87. package/dist/cli-zpep9q5e.js +491 -0
  88. package/dist/cli.js +41 -32
  89. package/dist/cloudflare-workers-ai-system-one-k6fbxw9d.js +46 -0
  90. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-nmxcatww.js} +26 -17
  91. package/dist/{codex-hook-9hptmdhj.js → codex-hook-20me6byw.js} +2 -2
  92. package/dist/{codex-login-x3jk4etn.js → codex-login-w6fz4xbz.js} +50 -5
  93. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-90aa80ev.js} +24 -17
  94. package/dist/{commands-e3bn0p75.js → commands-8wcq1kn7.js} +11 -9
  95. package/dist/{db-03v9kzr2.js → db-j40kc4se.js} +25 -11
  96. package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
  97. package/dist/{dispatcher-nrmg8yht.js → dispatcher-4zqy768d.js} +34 -29
  98. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-mpnzgztj.js} +3 -3
  99. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  100. package/dist/execute-6e7qeyk4.js +1017 -0
  101. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-k6n5nbpp.js} +6 -5
  102. package/dist/{google-vertex-809af514.js → google-vertex-44xxtcvp.js} +6 -5
  103. package/dist/{handlers-aqcedbyb.js → handlers-kjdce1w5.js} +37 -32
  104. package/dist/{hook-ybvmrmhq.js → hook-czx13n12.js} +19 -15
  105. package/dist/{hook-7vkskkre.js → hook-r2t4bqfg.js} +18 -14
  106. package/dist/{http-2eqa8t24.js → http-3bbb19be.js} +900 -145
  107. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  108. package/dist/{index-x71bvq0p.js → index-668me3f0.js} +35 -30
  109. package/dist/{index-c6zsv1bd.js → index-7aj368tw.js} +13 -12
  110. package/dist/{index-1yks3qcr.js → index-fg0dsp6c.js} +34 -29
  111. package/dist/{index-chgdkq36.js → index-kkv84jq9.js} +34 -29
  112. package/dist/{index-a3vn96n8.js → index-zg7vp46x.js} +198 -54
  113. package/dist/{keepalive-vtkf5n99.js → keepalive-yra8dy57.js} +13 -11
  114. package/dist/{lead-h3sp357x.js → lead-va0pad2c.js} +54 -49
  115. package/dist/llama-cpp-classify-tgj35k8d.js +335 -0
  116. package/dist/{maintenance-jzeqtk3q.js → maintenance-hqewtmyg.js} +14 -13
  117. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-pc3ntnxt.js} +14 -17
  118. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-7rdt1m9n.js} +12 -10
  119. package/dist/{onboard-tmsp0qbz.js → onboard-j6ms6d0x.js} +6 -5
  120. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-bbancrr0.js} +21 -8
  121. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-jpaj5g2b.js} +9 -12
  122. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-200k1wx4.js} +23 -18
  123. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-89rabkw2.js} +8 -7
  124. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-8yx3487g.js} +5 -7
  125. package/dist/{otel-impl-9mz73q65.js → otel-impl-a792rjz0.js} +1 -1
  126. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  127. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-g7qzvr3v.js} +295 -70
  128. package/dist/pricing-refresh-wx0tp7cb.js +39 -0
  129. package/dist/quickjs-r1pq2107.wasm +0 -0
  130. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-8tzawacv.js} +12 -10
  131. package/dist/{render-v2-gryrh9h8.js → render-v2-mm7zswb8.js} +12 -10
  132. package/dist/runtime-zyssgse0.js +24 -0
  133. package/dist/{setup-q9720bt6.js → setup-8y70ys6t.js} +6 -5
  134. package/dist/typesafe-system-one-2ef30pec.js +27 -0
  135. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-6nmk7h3n.js} +22 -13
  136. package/dist/{work-objects-b76x7deb.js → work-objects-fg6xkb0n.js} +37 -32
  137. package/dist/{worker-6vrxbv41.js → worker-wmwhxsph.js} +54 -49
  138. package/openapi.json +1314 -91
  139. package/package.json +12 -11
  140. package/src/be/db/agents.ts +20 -0
  141. package/src/be/db/api-keys.ts +70 -0
  142. package/src/be/db/tasks/human-free.ts +442 -0
  143. package/src/be/db/tasks/read.ts +56 -1
  144. package/src/be/db/tasks/write.ts +107 -11
  145. package/src/be/db.ts +274 -188
  146. package/src/be/harness-model-support.ts +182 -0
  147. package/src/be/human-free-drain.ts +67 -0
  148. package/src/be/memory/providers/sqlite-store.ts +51 -15
  149. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  150. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  151. package/src/be/migrations/184_model_catalog.sql +120 -0
  152. package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
  153. package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
  154. package/src/be/model-catalog-store.ts +644 -0
  155. package/src/be/model-tier-keys.ts +46 -0
  156. package/src/be/model-tier-resolution.ts +534 -0
  157. package/src/be/model-validation.ts +188 -0
  158. package/src/be/models-catalog.ts +61 -7
  159. package/src/be/modelsdev-cache.json +82 -0
  160. package/src/be/modelsdev-cache.ts +2 -0
  161. package/src/be/pricing-refresh.ts +154 -12
  162. package/src/be/scripts/typecheck.ts +15 -0
  163. package/src/be/seed/runner.ts +18 -12
  164. package/src/be/seed/types.ts +1 -0
  165. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  166. package/src/be/swarm-config-guard.ts +8 -0
  167. package/src/cli.tsx +10 -0
  168. package/src/commands/claude-managed-setup.ts +3 -1
  169. package/src/commands/codex-login.ts +67 -0
  170. package/src/commands/credential-outcome-report.ts +191 -0
  171. package/src/commands/rate-limit-outcome.ts +34 -4
  172. package/src/commands/runner.ts +357 -186
  173. package/src/commands/seat-mismatch-report.ts +46 -0
  174. package/src/extensions/apply-task-create.ts +27 -1
  175. package/src/extensions/contract-types.generated.ts +1 -1
  176. package/src/heartbeat/heartbeat.ts +79 -17
  177. package/src/heartbeat/templates.ts +2 -2
  178. package/src/http/agents.ts +62 -1
  179. package/src/http/api-keys.ts +140 -2
  180. package/src/http/codex-oauth-keep-warm.ts +3 -5
  181. package/src/http/index.ts +8 -0
  182. package/src/http/memory.ts +12 -5
  183. package/src/http/models-catalog.ts +357 -8
  184. package/src/http/poll.ts +54 -3
  185. package/src/http/schedules.ts +51 -6
  186. package/src/http/script-runs.ts +12 -0
  187. package/src/http/session-data.ts +2 -5
  188. package/src/http/stats.ts +6 -8
  189. package/src/http/status.ts +15 -1
  190. package/src/http/tasks.ts +90 -7
  191. package/src/http/usage-cache.ts +56 -15
  192. package/src/http/workflows.ts +46 -8
  193. package/src/mcp-client/http-client.ts +5 -0
  194. package/src/memory/recall-query.ts +74 -0
  195. package/src/prompts/base-prompt.ts +15 -4
  196. package/src/prompts/session-templates.ts +23 -1
  197. package/src/providers/acp-adapter.ts +1 -0
  198. package/src/providers/claude-adapter.ts +14 -5
  199. package/src/providers/claude-managed-adapter.ts +1 -0
  200. package/src/providers/claude-managed-models.ts +47 -161
  201. package/src/providers/claude-sdk-session.ts +3 -1
  202. package/src/providers/codex-adapter.ts +14 -14
  203. package/src/providers/codex-models.ts +65 -198
  204. package/src/providers/devin-adapter.ts +1 -0
  205. package/src/providers/dsh-adapter.ts +2 -1
  206. package/src/providers/harness-version.ts +83 -7
  207. package/src/providers/opencode-adapter.ts +53 -1
  208. package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
  209. package/src/providers/pi-codemode-runtime.ts +17 -0
  210. package/src/providers/pi-mono-adapter.ts +361 -83
  211. package/src/providers/pi-mono-extension.ts +16 -1
  212. package/src/providers/reasoning-effort.ts +44 -140
  213. package/src/providers/types.ts +8 -0
  214. package/src/rbac/legacy-policy.ts +43 -0
  215. package/src/rbac/permissions.ts +15 -0
  216. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  217. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  218. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  219. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  220. package/src/server.ts +6 -0
  221. package/src/slack/assistant.ts +13 -0
  222. package/src/slack/message-text.ts +56 -2
  223. package/src/slack/render-v2.ts +87 -37
  224. package/src/slack/session-status.ts +541 -0
  225. package/src/slack/thread-buffer.ts +10 -2
  226. package/src/tasks/sibling-awareness.ts +3 -2
  227. package/src/telemetry.ts +5 -4
  228. package/src/templates.d.ts +6 -0
  229. package/src/tests/agents-list-model-display.test.ts +4 -6
  230. package/src/tests/api-key-auth-failure-route.test.ts +142 -0
  231. package/src/tests/api-key-tracking.test.ts +368 -1
  232. package/src/tests/base-prompt.test.ts +43 -2
  233. package/src/tests/claude-adapter.test.ts +68 -0
  234. package/src/tests/claude-managed-adapter.test.ts +12 -18
  235. package/src/tests/codex-adapter.test.ts +2 -1
  236. package/src/tests/codex-auth-failure.test.ts +52 -0
  237. package/src/tests/codex-gpt6-release.test.ts +66 -5
  238. package/src/tests/codex-login.test.ts +116 -0
  239. package/src/tests/context-window.test.ts +7 -5
  240. package/src/tests/credential-exhaustion-policy.test.ts +118 -3
  241. package/src/tests/credential-outcome-report.test.ts +171 -0
  242. package/src/tests/credential-status-api.test.ts +103 -2
  243. package/src/tests/db-extraction-contract.test.ts +1 -0
  244. package/src/tests/db-task-read-contract.test.ts +10 -1
  245. package/src/tests/db-task-write-contract.test.ts +535 -2
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/dokploy-deploy.test.ts +315 -0
  248. package/src/tests/dsh-adapter.test.ts +1 -1
  249. package/src/tests/explicit-model-validation.test.ts +583 -0
  250. package/src/tests/extensions-pre-task.test.ts +73 -0
  251. package/src/tests/fetch-resolved-env.test.ts +46 -0
  252. package/src/tests/hang-watchdog-worker.ts +47 -0
  253. package/src/tests/hang-watchdog.test.ts +45 -0
  254. package/src/tests/hang-watchdog.ts +30 -0
  255. package/src/tests/harness-model-guard.test.ts +123 -0
  256. package/src/tests/harness-model-support.test.ts +374 -0
  257. package/src/tests/harness-version.test.ts +70 -3
  258. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  259. package/src/tests/heartbeat-checklist.test.ts +4 -2
  260. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  261. package/src/tests/http-task-write-ownership.test.ts +172 -0
  262. package/src/tests/human-free-flag.test.ts +279 -0
  263. package/src/tests/human-free-oracle.ts +57 -0
  264. package/src/tests/human-free-reclassify.test.ts +669 -0
  265. package/src/tests/memory-fts-populate.test.ts +139 -0
  266. package/src/tests/memory-http-recall-gating.test.ts +125 -1
  267. package/src/tests/migration-046-budgets.test.ts +3 -3
  268. package/src/tests/model-catalog-migration.test.ts +134 -0
  269. package/src/tests/model-catalog-table.test.ts +249 -0
  270. package/src/tests/model-catalog-write-access.test.ts +387 -0
  271. package/src/tests/model-rate-limit-windows.test.ts +19 -0
  272. package/src/tests/model-tier-resolution.test.ts +611 -0
  273. package/src/tests/models-catalog.test.ts +1 -1
  274. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  275. package/src/tests/opencode-adapter.test.ts +157 -2
  276. package/src/tests/package-publish.test.ts +137 -36
  277. package/src/tests/pi-codemode.test.ts +246 -0
  278. package/src/tests/pi-mono-adapter.test.ts +3 -0
  279. package/src/tests/pi-native-mcp.test.ts +193 -0
  280. package/src/tests/pi-tool-exposure.test.ts +374 -0
  281. package/src/tests/preload.ts +3 -0
  282. package/src/tests/pricing-routes.test.ts +2 -2
  283. package/src/tests/prompt-template-session.test.ts +5 -3
  284. package/src/tests/rate-limit-event.test.ts +65 -0
  285. package/src/tests/rate-limit-outcome.test.ts +90 -0
  286. package/src/tests/rbac-engine.test.ts +77 -3
  287. package/src/tests/reasoning-effort.test.ts +109 -0
  288. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  289. package/src/tests/runner-key-completion-outcome.test.ts +112 -0
  290. package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
  291. package/src/tests/runner-registration-retry.test.ts +149 -0
  292. package/src/tests/runtime-model-catalog.test.ts +86 -0
  293. package/src/tests/script-connections.test.ts +9 -0
  294. package/src/tests/seat-mismatch-report.test.ts +85 -0
  295. package/src/tests/seed-scripts.test.ts +138 -0
  296. package/src/tests/seed.test.ts +25 -0
  297. package/src/tests/session-costs.test.ts +14 -11
  298. package/src/tests/slack-message-text.test.ts +101 -0
  299. package/src/tests/slack-render-v2.test.ts +96 -0
  300. package/src/tests/slack-session-status.test.ts +626 -0
  301. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  302. package/src/tests/status.test.ts +29 -0
  303. package/src/tests/steering-promotion.test.ts +51 -0
  304. package/src/tests/store-progress-ownership.test.ts +136 -0
  305. package/src/tests/subscription-plans.test.ts +28 -0
  306. package/src/tests/task-cascade-fail.test.ts +148 -2
  307. package/src/tests/task-citations.test.ts +3 -2
  308. package/src/tests/tool-annotations.test.ts +1 -1
  309. package/src/tests/usage-cache.test.ts +166 -0
  310. package/src/tests/workflow-agent-task.test.ts +30 -0
  311. package/src/tests/workflow-executors.test.ts +5 -2
  312. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  313. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  314. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  315. package/src/tests/workflow-triggers-v2.test.ts +320 -0
  316. package/src/tools/model-catalog/index.ts +137 -0
  317. package/src/tools/schedules/create-schedule.ts +17 -2
  318. package/src/tools/schedules/patch-schedule.ts +31 -1
  319. package/src/tools/schedules/update-schedule.ts +31 -1
  320. package/src/tools/send-task.ts +18 -1
  321. package/src/tools/store-progress.ts +49 -4
  322. package/src/tools/task-action.ts +14 -1
  323. package/src/tools/tool-config.ts +3 -1
  324. package/src/tools/workflows/create-workflow.ts +17 -4
  325. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  326. package/src/tools/workflows/patch-workflow.ts +9 -2
  327. package/src/tools/workflows/update-workflow.ts +17 -4
  328. package/src/types.ts +41 -1
  329. package/src/utils/codex-auth-failure.ts +34 -0
  330. package/src/utils/context-window.ts +47 -53
  331. package/src/utils/credentials.ts +72 -30
  332. package/src/utils/error-tracker.ts +80 -0
  333. package/src/utils/harness-cli-version.ts +115 -0
  334. package/src/utils/harness-model-error.ts +33 -0
  335. package/src/utils/model-rate-limit-windows.ts +12 -0
  336. package/src/utils/runtime-model-catalog.ts +152 -0
  337. package/src/utils/spawn-model-guard.ts +43 -0
  338. package/src/utils/subscription-plans.ts +22 -0
  339. package/src/workflows/approval-resolution.ts +39 -0
  340. package/src/workflows/definition.ts +7 -0
  341. package/src/workflows/engine.ts +69 -1
  342. package/src/workflows/executors/agent-task.ts +16 -6
  343. package/src/workflows/executors/base.ts +53 -0
  344. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  345. package/src/workflows/executors/index.ts +1 -0
  346. package/src/workflows/executors/registry.ts +2 -0
  347. package/src/workflows/executors/system-one-decision.ts +1232 -0
  348. package/src/workflows/executors/system-one-providers.ts +301 -0
  349. package/src/workflows/executors/workflow-llm.ts +13 -1
  350. package/src/workflows/model-validation.ts +45 -0
  351. package/src/workflows/patch-definition.ts +5 -0
  352. package/src/workflows/readiness.ts +86 -0
  353. package/src/workflows/recovery.ts +12 -12
  354. package/src/workflows/resume.ts +23 -9
  355. package/src/workflows/triggers.ts +85 -0
  356. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  357. package/templates/skills/agent-browser/SKILL.md +6 -5
  358. package/templates/skills/agent-browser/content.md +6 -5
  359. package/templates/skills/heartbeat-runbook/config.json +1 -1
  360. package/templates/skills/heartbeat-runbook/content.md +2 -2
  361. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  362. package/templates/skills/swarm-scripts/content.md +1 -1
  363. package/templates/skills/workflow-iterate/content.md +82 -1
  364. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  365. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  366. package/dist/boot-seeds-s3q01xj3.js +0 -26
  367. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  368. package/dist/cli-0xjkj1j7.js +0 -61
  369. package/dist/cli-20cj37vt.js +0 -335561
  370. package/dist/cli-2djtr51b.js +0 -48
  371. package/dist/cli-65hjmev5.js +0 -1141
  372. package/dist/cli-aw1jwp1r.js +0 -14075
  373. package/dist/cli-h967nb6r.js +0 -6603
  374. package/dist/cli-vapyk8sw.js +0 -20
  375. package/dist/cli-ws6rqycm.js +0 -247
  376. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  377. package/src/providers/modelsdev-reasoning.json +0 -6453
  378. package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
@@ -1,20 +1,43 @@
1
- import { describe, expect, mock, test } from "bun:test";
2
- import { readPkgVersion } from "../providers/harness-version";
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { readInstalledCliPackageVersion, readPkgVersion } from "../providers/harness-version";
3
6
 
4
7
  describe("readPkgVersion", () => {
5
- test("reads pi version from the CLI before package.json probes", () => {
8
+ test("reads the pi version from the installed package without spawning", () => {
9
+ const spawn = mock(() => ({ stdout: "9.9.9\n", stderr: "", status: 0 }));
10
+ const requirePackageJson = mock(() => ({ version: "0.0.0" }));
11
+ const readInstalledVersion = mock(() => "0.79.1");
12
+
13
+ const version = readPkgVersion("@earendil-works/pi-coding-agent", {
14
+ requirePackageJson,
15
+ spawn,
16
+ readInstalledVersion,
17
+ });
18
+
19
+ expect(version).toBe("0.79.1");
20
+ expect(readInstalledVersion).toHaveBeenCalledWith("pi", "@earendil-works/pi-coding-agent");
21
+ expect(spawn).not.toHaveBeenCalled();
22
+ expect(requirePackageJson).not.toHaveBeenCalled();
23
+ });
24
+
25
+ test("reads pi version from the CLI when the installed package cannot be found", () => {
6
26
  const spawn = mock(() => ({ stdout: "0.79.1\n", stderr: "", status: 0 }));
7
27
  const requirePackageJson = mock(() => ({ version: "0.0.0" }));
8
28
 
9
29
  const version = readPkgVersion("@earendil-works/pi-coding-agent", {
10
30
  requirePackageJson,
11
31
  spawn,
32
+ readInstalledVersion: () => undefined,
12
33
  });
13
34
 
14
35
  expect(version).toBe("0.79.1");
15
36
  expect(spawn).toHaveBeenCalledWith("pi", ["--version"], {
16
37
  encoding: "utf8",
17
38
  stdio: ["ignore", "pipe", "pipe"],
39
+ timeout: 5_000,
40
+ killSignal: "SIGKILL",
18
41
  });
19
42
  expect(requirePackageJson).not.toHaveBeenCalled();
20
43
  });
@@ -26,12 +49,15 @@ describe("readPkgVersion", () => {
26
49
  const version = readPkgVersion("@opencode-ai/sdk", {
27
50
  requirePackageJson,
28
51
  spawn,
52
+ readInstalledVersion: () => undefined,
29
53
  });
30
54
 
31
55
  expect(version).toBe("1.16.2");
32
56
  expect(spawn).toHaveBeenCalledWith("opencode", ["--version"], {
33
57
  encoding: "utf8",
34
58
  stdio: ["ignore", "pipe", "pipe"],
59
+ timeout: 5_000,
60
+ killSignal: "SIGKILL",
35
61
  });
36
62
  expect(requirePackageJson).not.toHaveBeenCalled();
37
63
  });
@@ -40,8 +66,49 @@ describe("readPkgVersion", () => {
40
66
  const version = readPkgVersion("@earendil-works/pi-coding-agent", {
41
67
  requirePackageJson: () => ({ version: "0.79.1" }),
42
68
  spawn: mock(() => ({ stdout: "", stderr: "", status: 0 })),
69
+ readInstalledVersion: () => undefined,
43
70
  });
44
71
 
45
72
  expect(version).toBe("0.79.1");
46
73
  });
47
74
  });
75
+
76
+ describe("readInstalledCliPackageVersion", () => {
77
+ const originalPath = process.env.PATH;
78
+ const tmpRoots: string[] = [];
79
+
80
+ afterEach(() => {
81
+ process.env.PATH = originalPath;
82
+ for (const dir of tmpRoots.splice(0)) rmSync(dir, { recursive: true, force: true });
83
+ });
84
+
85
+ /** A global-install layout: `<root>/bin/fake-cli` -> `<root>/lib/node_modules/@acme/fake/dist/bundle/cli.js`. */
86
+ function installFakeCli(packageName: string, version: string): string {
87
+ const root = mkdtempSync(join(tmpdir(), "harness-version-"));
88
+ tmpRoots.push(root);
89
+ const pkgDir = join(root, "lib", "node_modules", "@acme", "fake");
90
+ mkdirSync(join(pkgDir, "dist", "bundle"), { recursive: true });
91
+ mkdirSync(join(root, "bin"), { recursive: true });
92
+ writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ name: packageName, version }));
93
+ // A nested package.json without a matching name must be walked past.
94
+ writeFileSync(join(pkgDir, "dist", "package.json"), JSON.stringify({ type: "module" }));
95
+ writeFileSync(join(pkgDir, "dist", "bundle", "cli.js"), "#!/usr/bin/env node\n");
96
+ symlinkSync(join(pkgDir, "dist", "bundle", "cli.js"), join(root, "bin", "fake-cli"));
97
+ return join(root, "bin");
98
+ }
99
+
100
+ test("resolves the owning package version through the PATH symlink", () => {
101
+ process.env.PATH = installFakeCli("@acme/fake", "1.2.3");
102
+ expect(readInstalledCliPackageVersion("fake-cli", "@acme/fake")).toBe("1.2.3");
103
+ });
104
+
105
+ test("returns undefined when the owning package is a different one", () => {
106
+ process.env.PATH = installFakeCli("@acme/other", "1.2.3");
107
+ expect(readInstalledCliPackageVersion("fake-cli", "@acme/fake")).toBeUndefined();
108
+ });
109
+
110
+ test("returns undefined when the command is not on PATH", () => {
111
+ process.env.PATH = installFakeCli("@acme/fake", "1.2.3");
112
+ expect(readInstalledCliPackageVersion("not-installed-cli", "@acme/fake")).toBeUndefined();
113
+ });
114
+ });
@@ -0,0 +1,71 @@
1
+ import { afterEach, describe, expect, spyOn, test } from "bun:test";
2
+ import { startHeartbeatChecklist, stopHeartbeatChecklist } from "../heartbeat/heartbeat";
3
+
4
+ // startHeartbeatChecklist reads HEARTBEAT_CHECKLIST_DISABLE and
5
+ // HEARTBEAT_CHECKLIST_INTERVAL_MS at call time. These cases pin how each raw
6
+ // env string is interpreted, observed through the startup log line.
7
+ const ENV_KEYS = ["HEARTBEAT_CHECKLIST_DISABLE", "HEARTBEAT_CHECKLIST_INTERVAL_MS"] as const;
8
+ const saved = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]]));
9
+
10
+ function startWithEnv(env: Partial<Record<(typeof ENV_KEYS)[number], string>>): string[] {
11
+ for (const key of ENV_KEYS) {
12
+ if (env[key] === undefined) delete process.env[key];
13
+ else process.env[key] = env[key];
14
+ }
15
+ const spy = spyOn(console, "log").mockImplementation(() => {});
16
+ try {
17
+ startHeartbeatChecklist();
18
+ return spy.mock.calls.map((args) => String(args[0]));
19
+ } finally {
20
+ spy.mockRestore();
21
+ }
22
+ }
23
+
24
+ const DISABLED = "[Heartbeat] Checklist disabled via HEARTBEAT_CHECKLIST_DISABLE";
25
+ const TICK_OFF =
26
+ "[Heartbeat] Recurring checklist off (HEARTBEAT_CHECKLIST_INTERVAL_MS=0); boot triage still scheduled";
27
+ const starting = (ms: number) => `[Heartbeat] Checklist starting with ${ms}ms interval`;
28
+
29
+ describe("startHeartbeatChecklist env parsing", () => {
30
+ afterEach(() => {
31
+ stopHeartbeatChecklist();
32
+ for (const key of ENV_KEYS) {
33
+ if (saved[key] === undefined) delete process.env[key];
34
+ else process.env[key] = saved[key];
35
+ }
36
+ });
37
+
38
+ test.each([
39
+ ["unset", undefined],
40
+ ["empty", ""],
41
+ ["false", "false"],
42
+ ["0", "0"],
43
+ ["FALSE", "FALSE"],
44
+ ["typo falls back to on", "treu"],
45
+ ])("HEARTBEAT_CHECKLIST_DISABLE %s keeps the checklist on", (_label, value) => {
46
+ expect(startWithEnv({ HEARTBEAT_CHECKLIST_DISABLE: value })).toEqual([starting(1_800_000)]);
47
+ });
48
+
49
+ test.each([
50
+ ["true"],
51
+ ["1"],
52
+ ["TRUE"],
53
+ ])("HEARTBEAT_CHECKLIST_DISABLE %s turns the checklist off", (value) => {
54
+ expect(startWithEnv({ HEARTBEAT_CHECKLIST_DISABLE: value })).toEqual([DISABLED]);
55
+ });
56
+
57
+ test.each([
58
+ ["0", [TICK_OFF]],
59
+ ["-5", [TICK_OFF]],
60
+ ["60000", [starting(60_000)]],
61
+ ["abc", [starting(1_800_000)]],
62
+ ["", [starting(1_800_000)]],
63
+ ])("HEARTBEAT_CHECKLIST_INTERVAL_MS=%p", (value, expected) => {
64
+ expect(startWithEnv({ HEARTBEAT_CHECKLIST_INTERVAL_MS: value })).toEqual(expected);
65
+ });
66
+
67
+ test("interval 0: a second start does not schedule boot triage again", () => {
68
+ expect(startWithEnv({ HEARTBEAT_CHECKLIST_INTERVAL_MS: "0" })).toEqual([TICK_OFF]);
69
+ expect(startWithEnv({ HEARTBEAT_CHECKLIST_INTERVAL_MS: "0" })).toEqual([]);
70
+ });
71
+ });
@@ -290,8 +290,10 @@ describe("Heartbeat Checklist", () => {
290
290
  expect(tasks[0]!.task).toContain("≤10 items");
291
291
  expect(tasks[0]!.task).toContain("20 is the absolute max");
292
292
  expect(tasks[0]!.task).toContain("script-run");
293
- expect(tasks[0]!.task).toContain("Heartbeat Audit");
294
- expect(tasks[0]!.task).toContain("Rule #11");
293
+ expect(tasks[0]!.task).toContain("schedule-health");
294
+ expect(tasks[0]!.task).toContain("task-failure-audit");
295
+ expect(tasks[0]!.task).not.toContain("Heartbeat Audit");
296
+ expect(tasks[0]!.task).not.toMatch(/Rules? #\d/);
295
297
  });
296
298
 
297
299
  test("created task has correct tags", async () => {
@@ -24,6 +24,7 @@ import {
24
24
  runRebootSweep,
25
25
  setBeforeHeartbeatSupersedeForTests,
26
26
  } from "../heartbeat/heartbeat";
27
+ import { createResumeFollowUp } from "../tasks/worker-follow-up";
27
28
 
28
29
  const TEST_DB_PATH = "./test-heartbeat-orphan-resume.sqlite";
29
30
 
@@ -81,4 +82,51 @@ describe("Heartbeat orphaned supersede repair (TLA+ counterexample)", () => {
81
82
  expect((await getTaskById(task.id))?.status).toBe("superseded");
82
83
  expect(await getChildTasks(task.id)).toHaveLength(1);
83
84
  });
85
+
86
+ // Crash after createResumeFollowUp, before backfillSupersedeTaskResumeTaskId.
87
+ test("a dependent left waiting on a superseded task with a resume is re-pointed on the next sweep", async () => {
88
+ const agent = await createAgent({ name: "crashed-worker", isLead: false, status: "idle" });
89
+ const task = await createTaskExtended("Work interrupted mid-backfill", { agentId: agent.id });
90
+ const dependent = await createTaskExtended("Waits on the interrupted work", {
91
+ agentId: agent.id,
92
+ dependsOn: [task.id],
93
+ });
94
+ await startTask(task.id);
95
+
96
+ await supersedeTask(task.id, { reason: "crash_recovery", resumeTaskId: null });
97
+ const resume = await createResumeFollowUp({ parentId: task.id, reason: "crash_recovery" });
98
+ if (resume.kind !== "created") throw new Error(`resume not created: ${resume.kind}`);
99
+ await getDbClient().run("UPDATE agent_tasks SET finishedAt = ? WHERE id = ?", [
100
+ new Date(Date.now() - 10 * 60 * 1000).toISOString(),
101
+ task.id,
102
+ ]);
103
+
104
+ await runRebootSweep();
105
+ await codeLevelTriage();
106
+
107
+ const after = await getTaskById(dependent.id);
108
+ expect(after?.status).toBe("pending");
109
+ expect(after?.dependsOn).toEqual([resume.task.id]);
110
+ expect(await getChildTasks(task.id)).toHaveLength(1);
111
+ });
112
+
113
+ test("dependents of a superseded task that gets no resume are cascade-failed by the sweep", async () => {
114
+ const task = await createTaskExtended("Unassigned work superseded before any resume");
115
+ const dependent = await createTaskExtended("Waits on unassigned work", {
116
+ dependsOn: [task.id],
117
+ });
118
+
119
+ await supersedeTask(task.id, { reason: "manual", resumeTaskId: null });
120
+ expect((await getTaskById(dependent.id))?.status).toBe("unassigned");
121
+ await getDbClient().run("UPDATE agent_tasks SET finishedAt = ? WHERE id = ?", [
122
+ new Date(Date.now() - 10 * 60 * 1000).toISOString(),
123
+ task.id,
124
+ ]);
125
+
126
+ await codeLevelTriage();
127
+
128
+ const after = await getTaskById(dependent.id);
129
+ expect(after?.status).toBe("failed");
130
+ expect(after?.failureReason).toContain("was superseded");
131
+ });
84
132
  });
@@ -0,0 +1,172 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import {
3
+ createServer as createHttpServer,
4
+ type IncomingMessage,
5
+ type Server,
6
+ type ServerResponse,
7
+ } from "node:http";
8
+ import { closeDb, createAgent, createTaskExtended, getTaskById, initDb, startTask } from "../be/db";
9
+ import { handleTasks } from "../http/tasks";
10
+ import { getPathSegments, parseQueryParams } from "../http/utils";
11
+ import { type HttpRequestAuth, setRequestAuth } from "../utils/request-auth-context";
12
+ import { listenOnFreePort } from "./test-net";
13
+
14
+ // Regression: an agent got 200 from HTTP progress and HTTP cancel on another
15
+ // agent's in-progress task. Progress follows task.progress.write (assignee,
16
+ // lead, human); cancel follows the MCP cancel-task policy for agents (lead or
17
+ // creator). Humans and the keyed runner wrapper keep access.
18
+ const ownerId = "aaaa0000-0000-4000-8000-000000000b01";
19
+ const otherId = "bbbb0000-0000-4000-8000-000000000b02";
20
+ const creatorId = "cccc0000-0000-4000-8000-000000000b03";
21
+ const leadId = "dddd0000-0000-4000-8000-000000000b04";
22
+ const userId = "eeee0000-0000-4000-8000-000000000b05";
23
+
24
+ type Caller =
25
+ | { as: "operator"; agentId?: string }
26
+ | { as: "session"; agentId: string }
27
+ | { as: "user" };
28
+
29
+ let server: Server;
30
+ let baseUrl: string;
31
+
32
+ function authFor(caller: Caller): HttpRequestAuth {
33
+ if (caller.as === "session") {
34
+ return { kind: "agent", agentId: caller.agentId, taskId: "session-task" };
35
+ }
36
+ if (caller.as === "user") {
37
+ return { kind: "user", userId, user: { id: userId } as never };
38
+ }
39
+ return { kind: "operator", fingerprint: "http-task-write-ownership" };
40
+ }
41
+
42
+ async function post(path: string, caller: Caller, body: unknown) {
43
+ const headers: Record<string, string> = {
44
+ "Content-Type": "application/json",
45
+ "x-test-caller": JSON.stringify(caller),
46
+ };
47
+ if (caller.as === "operator" && caller.agentId) headers["X-Agent-ID"] = caller.agentId;
48
+ const response = await fetch(`${baseUrl}${path}`, {
49
+ method: "POST",
50
+ headers,
51
+ body: JSON.stringify(body),
52
+ });
53
+ return response.status;
54
+ }
55
+
56
+ async function inProgressTask() {
57
+ const row = await createTaskExtended("http write ownership", {
58
+ agentId: ownerId,
59
+ creatorAgentId: creatorId,
60
+ source: "system",
61
+ followUpConfig: { disabled: true },
62
+ });
63
+ await startTask(row.id);
64
+ return row;
65
+ }
66
+
67
+ beforeAll(async () => {
68
+ initDb(":memory:");
69
+ await createAgent({ id: ownerId, name: "Owner", isLead: false, status: "idle" });
70
+ await createAgent({ id: otherId, name: "Other", isLead: false, status: "idle" });
71
+ await createAgent({ id: creatorId, name: "Creator", isLead: false, status: "idle" });
72
+ await createAgent({ id: leadId, name: "Lead", isLead: true, status: "idle" });
73
+
74
+ server = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
75
+ const caller = JSON.parse(String(req.headers["x-test-caller"])) as Caller;
76
+ setRequestAuth(req, authFor(caller));
77
+ res.setHeader("Content-Type", "application/json");
78
+ const pathSegments = getPathSegments(req.url ?? "");
79
+ const query = parseQueryParams(req.url ?? "");
80
+ const myAgentId = req.headers["x-agent-id"] as string | undefined;
81
+ if (await handleTasks(req, res, pathSegments, query, myAgentId)) return;
82
+ res.writeHead(404);
83
+ res.end(JSON.stringify({ error: "Not found" }));
84
+ });
85
+ const port = await listenOnFreePort(server);
86
+ baseUrl = `http://127.0.0.1:${port}`;
87
+ });
88
+
89
+ afterAll(async () => {
90
+ await new Promise<void>((resolve) => server.close(() => resolve()));
91
+ closeDb();
92
+ });
93
+
94
+ describe("POST /api/tasks/{id}/progress ownership", () => {
95
+ test("another agent on the shared key gets 403 and writes nothing", async () => {
96
+ const task = await inProgressTask();
97
+ const status = await post(
98
+ `/api/tasks/${task.id}/progress`,
99
+ { as: "operator", agentId: otherId },
100
+ { progress: "not mine" },
101
+ );
102
+ expect(status).toBe(403);
103
+ expect((await getTaskById(task.id))?.progress).toBeUndefined();
104
+ });
105
+
106
+ test("another agent on a session token gets 403", async () => {
107
+ const task = await inProgressTask();
108
+ const status = await post(
109
+ `/api/tasks/${task.id}/progress`,
110
+ { as: "session", agentId: otherId },
111
+ { progress: "not mine" },
112
+ );
113
+ expect(status).toBe(403);
114
+ expect((await getTaskById(task.id))?.progress).toBeUndefined();
115
+ });
116
+
117
+ test("the assignee, a lead, the keyed runner wrapper, and a user may write", async () => {
118
+ const task = await inProgressTask();
119
+ const callers: Caller[] = [
120
+ { as: "operator", agentId: ownerId },
121
+ { as: "operator", agentId: leadId },
122
+ { as: "operator" },
123
+ { as: "user" },
124
+ ];
125
+ for (const [i, caller] of callers.entries()) {
126
+ const status = await post(`/api/tasks/${task.id}/progress`, caller, {
127
+ progress: `step ${i}`,
128
+ });
129
+ expect(status, JSON.stringify(caller)).toBe(200);
130
+ expect((await getTaskById(task.id))?.progress).toBe(`step ${i}`);
131
+ }
132
+ });
133
+ });
134
+
135
+ describe("POST /api/tasks/{id}/cancel ownership", () => {
136
+ test("an agent that is neither lead nor creator gets 403 and the task keeps running", async () => {
137
+ const task = await inProgressTask();
138
+ const status = await post(
139
+ `/api/tasks/${task.id}/cancel`,
140
+ { as: "operator", agentId: otherId },
141
+ { reason: "not mine" },
142
+ );
143
+ expect(status).toBe(403);
144
+ expect((await getTaskById(task.id))?.status).toBe("in_progress");
145
+ });
146
+
147
+ test("the assignee alone cannot cancel, matching MCP cancel-task", async () => {
148
+ const task = await inProgressTask();
149
+ const status = await post(
150
+ `/api/tasks/${task.id}/cancel`,
151
+ { as: "operator", agentId: ownerId },
152
+ {},
153
+ );
154
+ expect(status).toBe(403);
155
+ expect((await getTaskById(task.id))?.status).toBe("in_progress");
156
+ });
157
+
158
+ test("the creator, a lead, the operator, and a user may cancel", async () => {
159
+ const callers: Caller[] = [
160
+ { as: "operator", agentId: creatorId },
161
+ { as: "session", agentId: leadId },
162
+ { as: "operator" },
163
+ { as: "user" },
164
+ ];
165
+ for (const caller of callers) {
166
+ const task = await inProgressTask();
167
+ const status = await post(`/api/tasks/${task.id}/cancel`, caller, { reason: "stop" });
168
+ expect(status, JSON.stringify(caller)).toBe(200);
169
+ expect((await getTaskById(task.id))?.status).toBe("cancelled");
170
+ }
171
+ });
172
+ });