@desplega.ai/agent-swarm 2.5.3 → 2.6.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 (349) hide show
  1. package/README.md +2 -2
  2. package/dist/{acp-adapter-gr2fcyyj.js → acp-adapter-dgfakfpw.js} +9 -9
  3. package/dist/{actions-wv766ghf.js → actions-fbqma8aa.js} +21 -21
  4. package/dist/{amp-adapter-speb906t.js → amp-adapter-en0jy6hs.js} +4 -4
  5. package/dist/{app-ag29n14f.js → app-k8a5cgpp.js} +3 -3
  6. package/dist/{assistant-3x8bz3eg.js → assistant-a16433gr.js} +30 -26
  7. package/dist/{boot-reembed-jsmfrpzf.js → boot-reembed-6c1sjfnr.js} +1 -1
  8. package/dist/{boot-reembed-5b3d2eea.js → boot-reembed-f4nn561n.js} +1 -1
  9. package/dist/{boot-scrub-sweep-s73gerxy.js → boot-scrub-sweep-d4w9gajf.js} +4 -4
  10. package/dist/{boot-seeds-5zmax2wr.js → boot-seeds-9bmkspyw.js} +4 -4
  11. package/dist/{claude-adapter-hswdnm3t.js → claude-adapter-nys1kjxa.js} +9 -9
  12. package/dist/{claude-managed-adapter-cvbzj1h6.js → claude-managed-adapter-55207973.js} +3 -3
  13. package/dist/{claude-managed-setup-3k8m3hhc.js → claude-managed-setup-3cm35gey.js} +11 -4
  14. package/dist/{claude-sdk-session-fkbyk66x.js → claude-sdk-session-nw5yd8cp.js} +9313 -9260
  15. package/dist/{cli-210xd1f4.js → cli-03gg9pd1.js} +4 -4
  16. package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
  17. package/dist/cli-1tem6ns9.js +894 -0
  18. package/dist/{cli-25j01dpj.js → cli-24j1jeeb.js} +3 -3
  19. package/dist/{cli-ta8bd7th.js → cli-2sjhpsns.js} +2 -2
  20. package/dist/{cli-p859hcc8.js → cli-39aet655.js} +2 -2
  21. package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
  22. package/dist/{cli-e2ar4bp1.js → cli-3aqd4jaa.js} +121 -47
  23. package/dist/{cli-jjznk3f7.js → cli-3fwqy71j.js} +69 -35
  24. package/dist/{cli-p9rafdrb.js → cli-3gfvmzht.js} +7 -7
  25. package/dist/{cli-03vpq45j.js → cli-3ndmpxxv.js} +2 -2
  26. package/dist/{cli-aecdcr77.js → cli-4hxrf0p4.js} +228 -74
  27. package/dist/{cli-mz9afxxf.js → cli-4rf0f5vg.js} +15 -3
  28. package/dist/{cli-qzme5949.js → cli-5rj83bam.js} +284 -202
  29. package/dist/{cli-fsdprwrg.js → cli-5sckpqcx.js} +2 -2
  30. package/dist/{cli-cz5nmdxt.js → cli-683y0b3a.js} +2 -2
  31. package/dist/{cli-3ngdk5te.js → cli-6y51ryva.js} +2 -2
  32. package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
  33. package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
  34. package/dist/{cli-gx4rm0qx.js → cli-7enjwya9.js} +1 -1
  35. package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
  36. package/dist/{cli-avdvgcay.js → cli-9abdgn5t.js} +28 -9
  37. package/dist/{cli-99xgw26r.js → cli-a2rr0fmg.js} +1 -1
  38. package/dist/{cli-e5bxnjps.js → cli-a34pr3w1.js} +7 -7
  39. package/dist/{cli-502eqv93.js → cli-abkm71vr.js} +1 -1
  40. package/dist/{cli-mmhj8vd7.js → cli-ag2yrnt5.js} +11 -2
  41. package/dist/{cli-n45mr7zh.js → cli-aj8djyqt.js} +5 -5
  42. package/dist/{cli-1fc85mvt.js → cli-art7c1zh.js} +5 -5
  43. package/dist/{cli-40jsnrjm.js → cli-az25fn5f.js} +10 -8
  44. package/dist/{cli-9s05422w.js → cli-bqztfyve.js} +1 -1
  45. package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
  46. package/dist/{cli-svrj71pg.js → cli-c2habvsf.js} +12 -12
  47. package/dist/{cli-s49hpn37.js → cli-ca6banwz.js} +85 -19
  48. package/dist/{cli-hhxjnjzs.js → cli-cz37jqss.js} +12 -10
  49. package/dist/{cli-6n8axjv1.js → cli-d2dpf95n.js} +2 -2
  50. package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
  51. package/dist/{cli-z21whrrg.js → cli-emhjcj2y.js} +17 -17
  52. package/dist/{cli-e8jyq7d8.js → cli-esakngh8.js} +7 -3
  53. package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
  54. package/dist/{cli-35gtk1p6.js → cli-ffvsj2qw.js} +44 -13
  55. package/dist/{cli-ce0ak9ms.js → cli-g20jjwt4.js} +26 -26
  56. package/dist/{cli-kxawjrxg.js → cli-g8ka5j4j.js} +772 -194
  57. package/dist/{cli-vwn81jbs.js → cli-gf95q3bm.js} +5 -5
  58. package/dist/{cli-s53vp709.js → cli-gnsb1zxa.js} +2 -2
  59. package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
  60. package/dist/{cli-gpv60278.js → cli-hfeb68z4.js} +7 -7
  61. package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
  62. package/dist/{cli-ewm3pwbq.js → cli-k21gx5ht.js} +46 -23
  63. package/dist/{cli-egjggehm.js → cli-kf4jree9.js} +2 -2
  64. package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
  65. package/dist/{cli-p0rzjkya.js → cli-m5a9m0ak.js} +32 -13
  66. package/dist/{cli-fzcn3d87.js → cli-m70bajzr.js} +3 -3
  67. package/dist/{cli-w1zwctj2.js → cli-mtnj4fwa.js} +11 -5
  68. package/dist/{cli-gpsz7g88.js → cli-mvjz5an3.js} +16 -12
  69. package/dist/{cli-hcpj2xqz.js → cli-n7asmce6.js} +225 -3
  70. package/dist/{cli-y561h5a9.js → cli-nn6vx2mq.js} +5 -5
  71. package/dist/cli-nq677csy.js +140 -0
  72. package/dist/{cli-a5ebc95n.js → cli-pfk92eqz.js} +18 -15
  73. package/dist/{cli-p6zs9h4y.js → cli-rr7zx19j.js} +767 -249
  74. package/dist/{cli-qgf8emrn.js → cli-sqma6vw1.js} +10 -7
  75. package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
  76. package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
  77. package/dist/{cli-qqzmjxkf.js → cli-v49fkghp.js} +11 -6
  78. package/dist/{cli-x77gen2p.js → cli-w2656ka1.js} +7 -7
  79. package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
  80. package/dist/{cli-8s5wx3nz.js → cli-zqne1qcj.js} +44 -17
  81. package/dist/cli.js +22 -22
  82. package/dist/{codex-adapter-n8ka66vf.js → codex-adapter-d95q4qsn.js} +7 -7
  83. package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
  84. package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
  85. package/dist/{codex-session-runner-fsebts37.js → codex-session-runner-gdwd8h0c.js} +8 -8
  86. package/dist/{commands-t04rt2pc.js → commands-jqmgk54b.js} +2 -2
  87. package/dist/{controller-rgna04r2.js → controller-z3jq589f.js} +30 -4
  88. package/dist/{controllers-connect-bqm1rtft.js → controllers-connect-r2za39ef.js} +23 -23
  89. package/dist/{cursor-adapter-swr2k95y.js → cursor-adapter-4mr4jgkw.js} +2 -3
  90. package/dist/{db-a4m7haw0.js → db-ppv2tze7.js} +3 -3
  91. package/dist/{db-24ck09xy.js → db-w36wwb5g.js} +4 -2
  92. package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
  93. package/dist/{dispatcher-nwf9ss0q.js → dispatcher-pv5h77vf.js} +20 -20
  94. package/dist/{docker-nfy2gc8t.js → docker-fdzyewpz.js} +4 -4
  95. package/dist/{dsh-adapter-kpdnhjst.js → dsh-adapter-d4pdpb3r.js} +2 -3
  96. package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
  97. package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
  98. package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-4sp4dvmc.js} +1 -1
  99. package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
  100. package/dist/{execute-0xpz8950.js → execute-jev839y1.js} +1 -1
  101. package/dist/{external-channel-j1e2p34p.js → external-channel-y6nkzhnn.js} +2 -2
  102. package/dist/{grok-adapter-qtjz0whk.js → grok-adapter-t08b9ntm.js} +10 -10
  103. package/dist/handlers-eqsnmv95.js +41 -0
  104. package/dist/{heartbeat-vtesda5d.js → heartbeat-vn31xwdk.js} +23 -23
  105. package/dist/{hook-b2hhj6rd.js → hook-6d0zbvvt.js} +6 -5
  106. package/dist/{hook-8wqaszda.js → hook-cpjs8q51.js} +13 -7
  107. package/dist/{http-mxcqhhcs.js → http-9c6medda.js} +1085 -136
  108. package/dist/{index-spwxjgay.js → index-38t35h0n.js} +20 -20
  109. package/dist/{index-1r8wfpzs.js → index-g6k9tctj.js} +1 -1
  110. package/dist/{index-srg522m6.js → index-k0yt1tvq.js} +20 -20
  111. package/dist/{index-31ceyf42.js → index-pkma1yf8.js} +1 -1
  112. package/dist/{index-n1vbvta9.js → index-qhjf2htm.js} +29 -24
  113. package/dist/index-yqgbfgaj.js +43 -0
  114. package/dist/{keepalive-dsd8rtgx.js → keepalive-em0117bp.js} +4 -4
  115. package/dist/lead-23e7z59t.js +79 -0
  116. package/dist/{maintenance-jqmqwnet.js → maintenance-kbme3qy9.js} +1 -1
  117. package/dist/{oauth-refresh-sweep-q2c3bnvm.js → oauth-refresh-sweep-xysj4yse.js} +3 -3
  118. package/dist/{onboard-w4yd7s8t.js → onboard-p6q0vz97.js} +3 -3
  119. package/dist/{opencode-adapter-9tppvxar.js → opencode-adapter-esv3k9yn.js} +7 -7
  120. package/dist/{otel-impl-f36byzbd.js → otel-impl-54qhqh1z.js} +2 -2
  121. package/dist/{pi-mono-adapter-tqsge1ay.js → pi-mono-adapter-mymbwhj0.js} +41 -14
  122. package/dist/{pricing-refresh-xvak5p4v.js → pricing-refresh-yb75wv33.js} +3 -3
  123. package/dist/{rbac-roles-gfn30fb8.js → rbac-roles-1vp2mw4p.js} +2 -2
  124. package/dist/{rbac-roles-682k2mt5.js → rbac-roles-dexhc9x8.js} +1 -1
  125. package/dist/render-v2-tjay5hhf.js +16 -0
  126. package/dist/{resume-2dvq348g.js → resume-8ayf37g5.js} +11 -9
  127. package/dist/{runtime-bt5pkpm1.js → runtime-hqe5cqcd.js} +1 -1
  128. package/dist/{session-access-7f21ekq9.js → session-access-fdhp987s.js} +4 -4
  129. package/dist/session-source-backfill-qgtc86z1.js +110 -0
  130. package/dist/{setup-epffxtbk.js → setup-4rrmqcq3.js} +1 -1
  131. package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-52km00d6.js} +2 -2
  132. package/dist/{work-objects-xz1a5rqk.js → work-objects-p28fgd70.js} +23 -23
  133. package/dist/worker-e3w7vye1.js +79 -0
  134. package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
  135. package/openapi.json +1323 -16
  136. package/package.json +5 -5
  137. package/plugin/commands/create-pr.md +2 -2
  138. package/plugin/pi-skills/create-pr/SKILL.md +2 -2
  139. package/src/assets/source-key.ts +95 -0
  140. package/src/be/db/api-keys.ts +80 -0
  141. package/src/be/db/approvals.ts +48 -0
  142. package/src/be/db/session-labels.ts +28 -0
  143. package/src/be/db/session-moves.ts +5 -6
  144. package/src/be/db/session-tree.ts +153 -14
  145. package/src/be/db/tasks/read.ts +11 -0
  146. package/src/be/db/tasks/write.ts +21 -6
  147. package/src/be/db/workflow-idempotency.ts +183 -0
  148. package/src/be/db/workflow-runs.ts +500 -7
  149. package/src/be/db-retention.ts +61 -6
  150. package/src/be/db.ts +126 -131
  151. package/src/be/memory/providers/openai-embedding.ts +5 -1
  152. package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
  153. package/src/be/migrations/244_api_key_validation.sql +4 -0
  154. package/src/be/onboarding.ts +4 -0
  155. package/src/be/scripts/typecheck.ts +1 -1
  156. package/src/be/sealed-json.ts +1 -0
  157. package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
  158. package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
  159. package/src/be/seed-skills/bundled-files.generated.json +1 -1
  160. package/src/be/session-source-backfill.ts +163 -0
  161. package/src/be/sql-connections.ts +7 -1
  162. package/src/be/swarm-config-guard.ts +3 -0
  163. package/src/be/users.ts +9 -0
  164. package/src/comb/review-batch.ts +28 -1
  165. package/src/comb/templates.ts +7 -2
  166. package/src/commands/claude-managed-setup.ts +7 -1
  167. package/src/commands/controller.ts +33 -0
  168. package/src/commands/credential-refresh.ts +76 -1
  169. package/src/commands/credential-wait.ts +2 -2
  170. package/src/commands/provider-credentials.ts +210 -170
  171. package/src/commands/runner.ts +31 -6
  172. package/src/extensions/apply-task-create.ts +64 -11
  173. package/src/extensions/contract-types.generated.ts +1 -1
  174. package/src/extensions/contract.ts +30 -0
  175. package/src/extensions/dispatcher.ts +4 -0
  176. package/src/extensions/post-bridge.ts +8 -0
  177. package/src/hooks/context-bound-note.ts +196 -0
  178. package/src/hooks/hook.ts +12 -0
  179. package/src/http/all-routes.ts +2 -0
  180. package/src/http/api-keys.ts +53 -0
  181. package/src/http/assets.ts +15 -0
  182. package/src/http/codex-oauth-accounts.ts +191 -0
  183. package/src/http/comb.ts +15 -2
  184. package/src/http/config.ts +49 -17
  185. package/src/http/core.ts +6 -0
  186. package/src/http/fleet-benchmark.ts +5 -2
  187. package/src/http/idempotency-key.ts +117 -0
  188. package/src/http/index.ts +11 -0
  189. package/src/http/integrations.ts +5 -1
  190. package/src/http/kv.ts +8 -3
  191. package/src/http/onboarding.ts +6 -1
  192. package/src/http/pool-keys.ts +354 -0
  193. package/src/http/sessions.ts +37 -12
  194. package/src/http/stats.ts +1 -0
  195. package/src/http/status.ts +26 -12
  196. package/src/http/tasks.ts +3 -0
  197. package/src/http/used-by.ts +7 -2
  198. package/src/http/workflows.ts +207 -33
  199. package/src/integrations/chat-sdk/inbound.ts +27 -0
  200. package/src/kv-reserved-namespaces.ts +19 -0
  201. package/src/prompts/context-bound-note.ts +48 -0
  202. package/src/prompts/session-templates.ts +25 -1
  203. package/src/providers/acp-targets.ts +1 -1
  204. package/src/providers/amp-adapter.ts +30 -8
  205. package/src/providers/claude-adapter.ts +34 -5
  206. package/src/providers/claude-managed-adapter.ts +42 -9
  207. package/src/providers/claude-sdk-session.ts +10 -0
  208. package/src/providers/claude-session-events.ts +13 -0
  209. package/src/providers/codex-adapter.ts +83 -11
  210. package/src/providers/opencode-adapter.ts +18 -0
  211. package/src/providers/pi-mono-adapter.ts +35 -0
  212. package/src/runtime-providers/docker.ts +6 -2
  213. package/src/scheduler/scheduler.ts +3 -2
  214. package/src/scripts-runtime/swarm-sdk.ts +7 -1
  215. package/src/scripts-runtime/types/stdlib.d.ts +1 -1
  216. package/src/scripts-runtime/types/swarm-extension.d.ts +52 -8
  217. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -1
  218. package/src/slack/assistant.ts +7 -0
  219. package/src/slack/channel-labels.ts +74 -0
  220. package/src/slack/handlers.ts +8 -0
  221. package/src/slack/inbound-files.ts +5 -2
  222. package/src/slack/thread-buffer.ts +4 -0
  223. package/src/tasks/sibling-awareness.ts +12 -3
  224. package/src/telemetry-context.ts +5 -6
  225. package/src/telemetry-identity.ts +3 -3
  226. package/src/tests/amp-adapter.test.ts +97 -0
  227. package/src/tests/api-keys-validation.test.ts +170 -0
  228. package/src/tests/approval-requests.test.ts +10 -7
  229. package/src/tests/approval-sweeps.test.ts +12 -6
  230. package/src/tests/base-prompt.test.ts +1 -1
  231. package/src/tests/claude-hook-output.test.ts +79 -1
  232. package/src/tests/claude-managed-adapter.test.ts +321 -0
  233. package/src/tests/claude-routes-e2e.test.ts +7 -2
  234. package/src/tests/codex-adapter.test.ts +163 -0
  235. package/src/tests/codex-credential-pools.test.ts +120 -0
  236. package/src/tests/codex-gateway-route.test.ts +165 -0
  237. package/src/tests/codex-oauth-accounts.test.ts +357 -0
  238. package/src/tests/comb-review-batch.test.ts +63 -2
  239. package/src/tests/config-pool-keys.test.ts +533 -0
  240. package/src/tests/context-bound-note.test.ts +269 -0
  241. package/src/tests/credential-check.test.ts +290 -2
  242. package/src/tests/credential-refresh.test.ts +172 -1
  243. package/src/tests/db-retention.test.ts +9 -3
  244. package/src/tests/db-task-read-contract.test.ts +1 -0
  245. package/src/tests/db-task-write-contract.test.ts +1 -3
  246. package/src/tests/docs-index-footprint.test.js +3 -3
  247. package/src/tests/extensions-pre-task.test.ts +325 -0
  248. package/src/tests/fetch-resolved-env.test.ts +17 -0
  249. package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
  250. package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
  251. package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
  252. package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
  253. package/src/tests/fixtures/fake-amp.ts +22 -8
  254. package/src/tests/fixtures/held-template-render.ts +42 -0
  255. package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
  256. package/src/tests/fleet-benchmark.test.ts +8 -0
  257. package/src/tests/get-workflow-run-tool.test.ts +98 -0
  258. package/src/tests/integrations-http.test.ts +32 -2
  259. package/src/tests/internal-ai/complete-structured.test.ts +37 -1
  260. package/src/tests/internal-ai/credentials.test.ts +23 -0
  261. package/src/tests/kv-http.test.ts +32 -0
  262. package/src/tests/kv-tool.test.ts +25 -0
  263. package/src/tests/multi-runtime-registration.test.ts +12 -12
  264. package/src/tests/onboarding.test.ts +108 -1
  265. package/src/tests/openai-embedding-credentials.test.ts +35 -0
  266. package/src/tests/openapi-route-headers.test.ts +2 -0
  267. package/src/tests/opencode-adapter.test.ts +134 -0
  268. package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
  269. package/src/tests/org-website.test.ts +36 -0
  270. package/src/tests/pi-mono-adapter.test.ts +105 -0
  271. package/src/tests/prompt-template-session.test.ts +4 -3
  272. package/src/tests/provider-key-probes.test.ts +181 -0
  273. package/src/tests/rbac-e2e-helpers.test.ts +88 -1
  274. package/src/tests/rbac-e2e-helpers.ts +36 -11
  275. package/src/tests/runner-push-delivery.test.ts +25 -0
  276. package/src/tests/runtime-controller-liveness.test.ts +133 -0
  277. package/src/tests/runtime-providers-docker.test.ts +2 -0
  278. package/src/tests/script-connections.test.ts +5 -0
  279. package/src/tests/script-run-config-scope.test.ts +91 -3
  280. package/src/tests/script-runs-retention.test.ts +212 -0
  281. package/src/tests/sdk-allowlist.test.ts +39 -0
  282. package/src/tests/session-logs.test.ts +70 -0
  283. package/src/tests/session-source-backfill.test.ts +429 -0
  284. package/src/tests/session-source-keys.test.ts +236 -0
  285. package/src/tests/session-source-labels.test.ts +419 -0
  286. package/src/tests/sessions-detail-batch.test.ts +2 -0
  287. package/src/tests/sessions-move.test.ts +131 -0
  288. package/src/tests/sessions-tree.test.ts +336 -2
  289. package/src/tests/slack-inbound-files.test.ts +103 -23
  290. package/src/tests/status.test.ts +101 -18
  291. package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
  292. package/src/tests/test-net.ts +31 -0
  293. package/src/tests/transcription.test.ts +8 -0
  294. package/src/tests/used-by-route.test.ts +11 -0
  295. package/src/tests/workflow-cascade-cancel.test.ts +1341 -0
  296. package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
  297. package/src/tests/workflow-http-v2.test.ts +131 -0
  298. package/src/tests/workflow-idempotency.test.ts +915 -0
  299. package/src/tests/workflow-step-order.test.ts +92 -0
  300. package/src/tests/workflow-sub-workflow.test.ts +30 -0
  301. package/src/tools/get-tasks.ts +8 -0
  302. package/src/tools/kv/kv-read-auth.ts +4 -2
  303. package/src/tools/task-action.ts +1 -1
  304. package/src/tools/workflows/cancel-workflow-run.ts +1 -1
  305. package/src/tools/workflows/get-workflow-run.ts +14 -4
  306. package/src/types.ts +24 -0
  307. package/src/utils/company-profile.ts +51 -1
  308. package/src/utils/context-window.ts +8 -6
  309. package/src/utils/credentials.ts +5 -2
  310. package/src/utils/internal-ai/complete-structured.ts +5 -0
  311. package/src/utils/internal-ai/credentials.ts +4 -3
  312. package/src/utils/provider-key-probes.ts +328 -0
  313. package/src/utils/transcription.ts +4 -1
  314. package/src/workflows/approval-cancel.ts +15 -13
  315. package/src/workflows/approval-notifications.ts +5 -1
  316. package/src/workflows/checkpoint.ts +6 -3
  317. package/src/workflows/engine.ts +30 -8
  318. package/src/workflows/executors/foreach.ts +2 -2
  319. package/src/workflows/executors/sub-workflow.ts +8 -1
  320. package/src/workflows/foreach-driver.ts +10 -3
  321. package/src/workflows/foreach-join.ts +29 -6
  322. package/src/workflows/recovery.ts +6 -1
  323. package/src/workflows/resume.ts +168 -56
  324. package/src/workflows/retry-poller.ts +49 -3
  325. package/src/workflows/task-step-routing.ts +29 -7
  326. package/templates/ai-toolbox.manifest.json +8 -8
  327. package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
  328. package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
  329. package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
  330. package/templates/skills/agent-browser/SKILL.md +4 -4
  331. package/templates/skills/agent-browser/content.md +4 -4
  332. package/templates/skills/code-quality/config.json +1 -1
  333. package/templates/skills/code-quality/content.md +2 -2
  334. package/templates/skills/delegate-work/content.md +29 -25
  335. package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
  336. package/templates/skills/qa/content.md +2 -2
  337. package/templates/skills/researching/content.md +4 -0
  338. package/templates/skills/swarm-scripts/SKILL.md +1 -0
  339. package/templates/skills/swarm-scripts/content.md +1 -0
  340. package/templates/skills/work-on-task/SKILL.md +1 -1
  341. package/templates/skills/work-on-task/content.md +1 -1
  342. package/dist/cli-7421jzsw.js +0 -84
  343. package/dist/cli-e45e4m6b.js +0 -413
  344. package/dist/cli-zw88zjba.js +0 -307
  345. package/dist/handlers-19xr6dnz.js +0 -41
  346. package/dist/index-kw2mmat7.js +0 -43
  347. package/dist/lead-zyerk1x2.js +0 -79
  348. package/dist/render-v2-hgytz8nn.js +0 -12
  349. package/dist/worker-42t8emjt.js +0 -79
@@ -18,7 +18,7 @@ import {
18
18
  getInProgressTasksByContextKey,
19
19
  } from "../be/db";
20
20
  import { applyPreTaskCreate } from "../extensions/apply-task-create";
21
- import type { TaskCreateOrigin } from "../extensions/contract";
21
+ import type { TaskCreateEvent, TaskCreateOrigin } from "../extensions/contract";
22
22
  import type { AgentTask } from "../types";
23
23
  import { TaskCreationBlockedError } from "./errors";
24
24
  import {
@@ -151,7 +151,11 @@ export type PreparedTaskCreate = { description: string; options: CreateTaskOptio
151
151
  export async function prepareTaskWithSiblingAwareness(
152
152
  description: string,
153
153
  options: CreateTaskOptions,
154
- args: { origin?: TaskCreateOrigin; allowCustomModel?: boolean } = {},
154
+ args: {
155
+ origin?: TaskCreateOrigin;
156
+ allowCustomModel?: boolean;
157
+ slack?: TaskCreateEvent["slack"];
158
+ } = {},
155
159
  ): Promise<PreparedTaskCreate> {
156
160
  const preCreate = await applyPreTaskCreate({
157
161
  description,
@@ -159,6 +163,7 @@ export async function prepareTaskWithSiblingAwareness(
159
163
  // Preserve the plan's REST default for callers without an explicit ingress origin.
160
164
  origin: args.origin ?? "rest",
161
165
  allowCustomModel: args.allowCustomModel,
166
+ slack: args.slack,
162
167
  });
163
168
  if (preCreate.kind === "blocked") {
164
169
  throw new TaskCreationBlockedError(preCreate.reason, preCreate.extension);
@@ -169,7 +174,11 @@ export async function prepareTaskWithSiblingAwareness(
169
174
  export async function createTaskWithSiblingAwareness(
170
175
  description: string,
171
176
  options: CreateTaskOptions,
172
- args: { origin?: TaskCreateOrigin; allowCustomModel?: boolean } = {},
177
+ args: {
178
+ origin?: TaskCreateOrigin;
179
+ allowCustomModel?: boolean;
180
+ slack?: TaskCreateEvent["slack"];
181
+ } = {},
173
182
  ): Promise<AgentTask> {
174
183
  const { description: d, options: o } = await prepareTaskWithSiblingAwareness(
175
184
  description,
@@ -12,8 +12,8 @@ import catalog from "./telemetry-contract/catalog.json";
12
12
  import {
13
13
  COMPANY_ACQUISITION_SOURCES,
14
14
  COMPANY_TEAM_SIZES,
15
- COMPANY_USE_CASES,
16
- USER_FUNCTIONS,
15
+ reportedUseCase,
16
+ reportedUserFunction,
17
17
  } from "./utils/company-profile";
18
18
 
19
19
  /** `org_` + 16 lowercase hex (self-host, minted here) or 27 alphanumerics (cloud). Same pattern the proxy enforces. */
@@ -115,10 +115,9 @@ export function buildContext(input: ContextInput): EventContext {
115
115
  const orgDomain = validOrgDomain(input.orgDomain);
116
116
  const orgWebsite = validOrgDomain(input.orgWebsite);
117
117
  const teamSize = COMPANY_TEAM_SIZES.find((value) => value === input.teamSize);
118
- const useCase = COMPANY_USE_CASES.find((value) => value === input.useCase);
119
- const userFunction = userId
120
- ? USER_FUNCTIONS.find((value) => value === input.userFunction)
121
- : undefined;
118
+ // A value the catalog does not list yet is left out, as if unset.
119
+ const useCase = reportedUseCase(input.useCase);
120
+ const userFunction = userId ? reportedUserFunction(input.userFunction) : undefined;
122
121
  return {
123
122
  org_id: input.orgId,
124
123
  org_name: orgName ? orgName : null,
@@ -13,7 +13,7 @@
13
13
  import { getDbClient } from "./be/db/runtime";
14
14
  import { setTelemetryOrgDomain } from "./telemetry";
15
15
  import { emailDomain, type TelemetryUserRole } from "./telemetry-context";
16
- import { USER_FUNCTIONS } from "./utils/company-profile";
16
+ import { reportedUserFunction } from "./utils/company-profile";
17
17
 
18
18
  const ROLE_TTL_MS = 60 * 60_000;
19
19
  const ROLE_CACHE_LIMIT = 1_000;
@@ -21,7 +21,7 @@ const DOMAIN_RECOMPUTE_DEBOUNCE_MS = 2_000;
21
21
 
22
22
  interface UserRoleEntry {
23
23
  role: TelemetryUserRole;
24
- userFunction?: (typeof USER_FUNCTIONS)[number];
24
+ userFunction?: ReturnType<typeof reportedUserFunction>;
25
25
  at: number;
26
26
  }
27
27
 
@@ -40,7 +40,7 @@ async function loadUserRole(userId: string): Promise<UserRoleEntry | undefined>
40
40
  );
41
41
  return {
42
42
  role,
43
- userFunction: USER_FUNCTIONS.find((value) => value === user?.jobFunction),
43
+ userFunction: reportedUserFunction(user?.jobFunction),
44
44
  at: Date.now(),
45
45
  };
46
46
  } catch {
@@ -8,6 +8,7 @@ import {
8
8
  validateProviderCredentials,
9
9
  } from "../commands/provider-credentials";
10
10
  import { recomputeSessionCost } from "../http/session-cost-recompute";
11
+ import { renderContextBoundNote } from "../prompts/context-bound-note";
11
12
  import { createProviderAdapter } from "../providers";
12
13
  import {
13
14
  AMP_PACKAGE,
@@ -31,6 +32,7 @@ import { getModelAwareCredentialVars } from "../utils/credentials";
31
32
  import { resolveHarnessProvider } from "../utils/harness-provider";
32
33
  import { clearVolatileSecretsForTesting } from "../utils/secret-scrubber";
33
34
  import { ampPricingLookup } from "./amp-pricing-helpers";
35
+ import { holdTemplateRender } from "./fixtures/held-template-render";
34
36
 
35
37
  const KEY = "sgamp_test_credential_value";
36
38
 
@@ -769,6 +771,101 @@ describe("amp session", () => {
769
771
  }, 20_000);
770
772
  });
771
773
 
774
+ describe("amp context-bound note", () => {
775
+ // Each fake request is 4 input + 200 cache read + 1000 cache write = 1204 tokens.
776
+ async function toolResults(
777
+ mode: string,
778
+ env: Record<string, string>,
779
+ overrides: Partial<ProviderSessionConfig> = {},
780
+ ) {
781
+ const { config } = await fixture(mode, overrides, env);
782
+ const { events, result } = await runToCompletion(config);
783
+ const results = events.flatMap((e) => (e.type === "tool_end" ? [e.result] : []));
784
+ return { results, result };
785
+ }
786
+
787
+ test("queues the note into the first tool result at the threshold, then never again", async () => {
788
+ const { results, result } = await toolResults("steer-twice", {
789
+ CONTEXT_BOUND_NOTE_TOKENS: "1204",
790
+ });
791
+ expect(results).toEqual([`2\n\n${await renderContextBoundNote(1204)}`, "2"]);
792
+ // Carried inside the turn: no extra turn replays it.
793
+ expect(result).toMatchObject({ isError: false, output: "DONE1" });
794
+ expect(result.cost?.numTurns).toBe(1);
795
+ });
796
+
797
+ test("a pinned model gets the window share; a mode names no model and gets none", async () => {
798
+ const pin = "anthropic/claude-sonnet-4-6";
799
+ const pinned = await toolResults(
800
+ "steer-twice",
801
+ { CONTEXT_BOUND_NOTE_TOKENS: "1204" },
802
+ { model: pin },
803
+ );
804
+ const note = await renderContextBoundNote(1204, pin);
805
+ expect(note).toContain("of the 1,000,000-token window)");
806
+ expect(pinned.results).toEqual([`2\n\n${note}`, "2"]);
807
+ const mode = await toolResults("steer-twice", { CONTEXT_BOUND_NOTE_TOKENS: "1204" });
808
+ expect(mode.results[0]).not.toContain("token window");
809
+ });
810
+
811
+ test("no-op below the threshold and when disabled at 0", async () => {
812
+ for (const threshold of ["1205", "0"]) {
813
+ const { results } = await toolResults("steer-twice", {
814
+ CONTEXT_BOUND_NOTE_TOKENS: threshold,
815
+ });
816
+ expect(results).toEqual(["2", "2"]);
817
+ }
818
+ });
819
+
820
+ test("fails open on missing or corrupt request usage", async () => {
821
+ for (const usage of ["missing", "corrupt"]) {
822
+ const { results, result } = await toolResults("steer", {
823
+ CONTEXT_BOUND_NOTE_TOKENS: "1",
824
+ AMP_TEST_REQUEST_USAGE: usage,
825
+ });
826
+ expect(results).toEqual(["2"]);
827
+ expect(result).toMatchObject({ isError: false, output: "DONE1" });
828
+ }
829
+ });
830
+
831
+ test("a final reply with no tool call never queues it, so no new turn starts", async () => {
832
+ const { results, result } = await toolResults("steer-no-tool", {
833
+ CONTEXT_BOUND_NOTE_TOKENS: "1",
834
+ });
835
+ expect(results).toEqual([]);
836
+ expect(result).toMatchObject({ isError: false, output: "DONE1" });
837
+ expect(result.cost?.numTurns).toBe(1);
838
+ });
839
+
840
+ test("a note rendered after the last tool result is dropped, not replayed as a new turn", async () => {
841
+ const held = holdTemplateRender();
842
+ try {
843
+ const { config } = await fixture(
844
+ "steer",
845
+ {},
846
+ {
847
+ CONTEXT_BOUND_NOTE_TOKENS: "1204",
848
+ AMP_TEST_POST_RESULT_DELAY_MS: "150",
849
+ },
850
+ );
851
+ const session = await new AmpAdapter().createSession(config);
852
+ const results: unknown[] = [];
853
+ session.onEvent((event) => {
854
+ if (event.type !== "tool_end") return;
855
+ results.push(event.result);
856
+ // Ready while the final reply is pending.
857
+ held.release();
858
+ });
859
+ const result = await session.waitForCompletion();
860
+ expect(results).toEqual(["2"]);
861
+ expect(result).toMatchObject({ isError: false, output: "DONE1" });
862
+ expect(result.cost?.numTurns).toBe(1);
863
+ } finally {
864
+ held.stop();
865
+ }
866
+ });
867
+ });
868
+
772
869
  describe("amp live test", () => {
773
870
  const env = async (mode: string) => {
774
871
  const { config } = await fixture(mode);
@@ -0,0 +1,170 @@
1
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
2
+ import { Readable } from "node:stream";
3
+ import {
4
+ closeDb,
5
+ getAvailableKeyIndices,
6
+ getDbClient,
7
+ getKeyStatuses,
8
+ initDb,
9
+ recordKeyValidation,
10
+ } from "../be/db";
11
+ import { handleApiKeys } from "../http/api-keys";
12
+ import { getPathSegments, parseQueryParams } from "../http/utils";
13
+
14
+ const TEST_DB = "./test-api-keys-validation.sqlite";
15
+ const keyType = "ANTHROPIC_API_KEY";
16
+ const rejected = { keyIndex: 0, keySuffix: "aaa11", ok: false, httpStatus: 401 };
17
+
18
+ async function call(method: string, url: string, body?: unknown) {
19
+ const req = Readable.from(body === undefined ? [] : [Buffer.from(JSON.stringify(body))]);
20
+ Object.assign(req, { method, url, headers: { "content-type": "application/json" } });
21
+ let statusCode = 200;
22
+ let responseBody = "";
23
+ const res = {
24
+ setHeader: () => {},
25
+ writeHead: (code: number) => {
26
+ statusCode = code;
27
+ },
28
+ end: (chunk?: string) => {
29
+ responseBody += chunk ?? "";
30
+ },
31
+ };
32
+ const handled = await handleApiKeys(
33
+ req as never,
34
+ res as never,
35
+ getPathSegments(url),
36
+ parseQueryParams(url),
37
+ );
38
+ return { handled, statusCode, body: JSON.parse(responseBody) };
39
+ }
40
+
41
+ describe("pooled key validation", () => {
42
+ beforeAll(() => {
43
+ initDb(TEST_DB);
44
+ });
45
+ beforeEach(async () => {
46
+ await getDbClient().run("DELETE FROM api_key_status");
47
+ });
48
+ afterAll(async () => {
49
+ closeDb();
50
+ for (const suffix of ["", "-wal", "-shm"]) {
51
+ await Bun.file(TEST_DB + suffix)
52
+ .delete()
53
+ .catch(() => {});
54
+ }
55
+ });
56
+
57
+ test("HTTP 401 marks the key as rejected", async () => {
58
+ await recordKeyValidation(keyType, [{ ...rejected, httpStatus: 401 }]);
59
+ const [row] = await getKeyStatuses(keyType);
60
+ expect(row).toMatchObject({
61
+ keyIndex: 0,
62
+ keySuffix: "aaa11",
63
+ provider: "claude",
64
+ lastValidationError: "HTTP 401",
65
+ });
66
+ expect(row!.lastValidationFailedAt).not.toBeNull();
67
+ });
68
+
69
+ test("a pass clears rejection and updates the index", async () => {
70
+ await recordKeyValidation(keyType, [rejected]);
71
+ await recordKeyValidation(keyType, [{ ...rejected, keyIndex: 2, ok: true }]);
72
+ expect((await getKeyStatuses(keyType))[0]).toMatchObject({
73
+ keyIndex: 2,
74
+ lastValidationError: null,
75
+ lastValidationFailedAt: null,
76
+ });
77
+ });
78
+
79
+ // 403 included: a valid key can lack the scope of the list endpoint.
80
+ test.each([
81
+ 403,
82
+ 500,
83
+ 429,
84
+ undefined,
85
+ ])("failure with status %s preserves existing state", async (httpStatus) => {
86
+ await recordKeyValidation(keyType, [rejected]);
87
+ const before = await getKeyStatuses(keyType);
88
+ await recordKeyValidation(keyType, [{ ...rejected, keyIndex: 3, httpStatus }]);
89
+ expect(await getKeyStatuses(keyType)).toEqual(before);
90
+ await recordKeyValidation("OPENAI_API_KEY", [{ ...rejected, httpStatus }]);
91
+ expect(await getKeyStatuses("OPENAI_API_KEY")).toEqual([]);
92
+ });
93
+
94
+ test("a removed suffix cannot block a replacement at the same index", async () => {
95
+ await recordKeyValidation(keyType, [rejected]);
96
+ await recordKeyValidation(keyType, [{ keyIndex: 0, keySuffix: "bbb22", ok: true }]);
97
+ const rows = await getKeyStatuses(keyType);
98
+ expect(rows.find((row) => row.keySuffix === "aaa11")).toMatchObject({
99
+ lastValidationError: null,
100
+ lastValidationFailedAt: null,
101
+ });
102
+ expect((await getAvailableKeyIndices(keyType, 1)).availableIndices).toEqual([0]);
103
+ });
104
+
105
+ test("rejections and removed-suffix cleanup stay within their scope and key type", async () => {
106
+ await recordKeyValidation(keyType, [rejected], "agent", "agent-a");
107
+ await recordKeyValidation(keyType, [rejected], "agent", "agent-b");
108
+ await recordKeyValidation("OPENAI_API_KEY", [rejected], "agent", "agent-a");
109
+ await recordKeyValidation(
110
+ keyType,
111
+ [{ keyIndex: 0, keySuffix: "bbb22", ok: true }],
112
+ "agent",
113
+ "agent-a",
114
+ );
115
+ expect((await getAvailableKeyIndices(keyType, 1, "agent", "agent-a")).availableIndices).toEqual(
116
+ [0],
117
+ );
118
+ expect((await getAvailableKeyIndices(keyType, 1, "agent", "agent-b")).availableIndices).toEqual(
119
+ [],
120
+ );
121
+ expect(
122
+ (await getAvailableKeyIndices("OPENAI_API_KEY", 1, "agent", "agent-a")).availableIndices,
123
+ ).toEqual([]);
124
+ });
125
+
126
+ test("availability excludes rejected keys and reports their indices", async () => {
127
+ await recordKeyValidation(keyType, [rejected, { keyIndex: 1, keySuffix: "bbb22", ok: true }]);
128
+ expect(await getAvailableKeyIndices(keyType, 3)).toMatchObject({
129
+ availableIndices: [1, 2],
130
+ rejectedIndices: [0],
131
+ });
132
+ });
133
+
134
+ test("availability is empty when every key is rejected", async () => {
135
+ await recordKeyValidation(keyType, [
136
+ rejected,
137
+ { ...rejected, keyIndex: 1, keySuffix: "bbb22" },
138
+ ]);
139
+ expect(await getAvailableKeyIndices(keyType, 2)).toMatchObject({
140
+ availableIndices: [],
141
+ rejectedIndices: [0, 1],
142
+ });
143
+ });
144
+
145
+ test("the report handler records validation and exposes it in reads", async () => {
146
+ expect(
147
+ await call("POST", "/api/keys/report-validation", { keyType, keys: [rejected] }),
148
+ ).toEqual({
149
+ handled: true,
150
+ statusCode: 200,
151
+ body: { success: true, message: "Key validation recorded" },
152
+ });
153
+ expect(
154
+ (await call("GET", `/api/keys/available?keyType=${keyType}&totalKeys=2`)).body,
155
+ ).toMatchObject({
156
+ availableIndices: [1],
157
+ rejectedIndices: [0],
158
+ });
159
+ const status = await call("GET", `/api/keys/status?keyType=${keyType}`);
160
+ expect(status.body.keys[0]).toMatchObject({ lastValidationError: "HTTP 401" });
161
+ expect(status.body.keys[0].lastValidationFailedAt).not.toBeNull();
162
+ });
163
+
164
+ test("the report handler rejects an empty pool", async () => {
165
+ expect(
166
+ (await call("POST", "/api/keys/report-validation", { keyType, keys: [] })).statusCode,
167
+ ).toBe(400);
168
+ expect(await getKeyStatuses(keyType)).toEqual([]);
169
+ });
170
+ });
@@ -418,7 +418,7 @@ describe("Approval Requests", () => {
418
418
  expect(result).toBeNull();
419
419
  });
420
420
 
421
- test("does not resolve a workflow approval after its run and HITL step are cancelled", async () => {
421
+ test("does not resolve a workflow approval after its run is cancelled", async () => {
422
422
  const workflow = await createWorkflow({
423
423
  name: `approval-cancel-${crypto.randomUUID()}`,
424
424
  definition: { nodes: [] },
@@ -432,11 +432,12 @@ describe("Approval Requests", () => {
432
432
  nodeId: "approval",
433
433
  nodeType: "human-in-the-loop",
434
434
  });
435
- await updateWorkflowRun(runId, { status: "cancelled" });
436
- await updateWorkflowRunStep(stepId, { status: "cancelled" });
435
+ // Asked while the run was live; the run was cancelled after, leaving
436
+ // the request pending (a cancelled step's trigger would close it).
437
437
  const approval = await createApprovalRequest(
438
438
  makeApprovalData({ workflowRunId: runId, workflowRunStepId: stepId }),
439
439
  );
440
+ await updateWorkflowRun(runId, { status: "cancelled" });
440
441
 
441
442
  const result = await resolveApprovalRequest(
442
443
  approval.id,
@@ -1864,15 +1865,17 @@ describe("Approval Requests", () => {
1864
1865
 
1865
1866
  test("cancel on a waiting run cancels the run, its step, and its task", async () => {
1866
1867
  const { runId, stepId, approvalId } = await makeRun("waiting");
1868
+ // Linked directly: the engine binds a step's task while the step is
1869
+ // still running, and this step is already parked.
1867
1870
  const task = await createTaskExtended("step task", {
1868
1871
  agentId: owner.id,
1869
1872
  source: "mcp",
1870
1873
  workflowRunId: runId,
1871
- workflowRunStepId: stepId,
1872
1874
  });
1873
- await getDbClient().run("UPDATE agent_tasks SET status = 'in_progress' WHERE id = ?", [
1874
- task.id,
1875
- ]);
1875
+ await getDbClient().run(
1876
+ "UPDATE agent_tasks SET status = 'in_progress', workflowRunStepId = ? WHERE id = ?",
1877
+ [stepId, task.id],
1878
+ );
1876
1879
 
1877
1880
  const res = await post(
1878
1881
  `/api/approval-requests/${approvalId}/cancel`,
@@ -223,15 +223,17 @@ describe("approval sweeps", () => {
223
223
 
224
224
  test("cancels the waiting run, its step, and its linked task", async () => {
225
225
  const { runId, stepId } = await makeRun("waiting");
226
+ // Linked directly: the engine binds a step's task while the step is
227
+ // still running, and this step is already parked.
226
228
  const task = await createTaskExtended("step task", {
227
229
  agentId,
228
230
  source: "mcp",
229
231
  workflowRunId: runId,
230
- workflowRunStepId: stepId,
231
232
  });
232
- await getDbClient().run("UPDATE agent_tasks SET status = 'in_progress' WHERE id = ?", [
233
- task.id,
234
- ]);
233
+ await getDbClient().run(
234
+ "UPDATE agent_tasks SET status = 'in_progress', workflowRunStepId = ? WHERE id = ?",
235
+ [stepId, task.id],
236
+ );
235
237
  const id = await makeRequest({ ageDays: 8, workflowRunId: runId, workflowRunStepId: stepId });
236
238
 
237
239
  const result = await autoCancelStaleApprovalRequests();
@@ -300,8 +302,10 @@ describe("approval sweeps", () => {
300
302
  });
301
303
 
302
304
  test("leaves a failed run as it is", async () => {
303
- const { runId, stepId } = await makeRun("failed");
305
+ // The request was asked while the run was live; the run failed after.
306
+ const { runId, stepId } = await makeRun("waiting");
304
307
  const id = await makeRequest({ ageDays: 8, workflowRunId: runId, workflowRunStepId: stepId });
308
+ await updateWorkflowRun(runId, { status: "failed" });
305
309
 
306
310
  const result = await autoCancelStaleApprovalRequests();
307
311
 
@@ -415,13 +419,15 @@ describe("approval sweeps", () => {
415
419
  });
416
420
 
417
421
  test("times out a request on a completed run", async () => {
418
- const { runId, stepId } = await makeRun("completed");
422
+ // The request was asked while the run was live; the run completed after.
423
+ const { runId, stepId } = await makeRun("waiting");
419
424
  const id = await makeRequest({
420
425
  ageDays: 0,
421
426
  expiresInMs: -60_000,
422
427
  workflowRunId: runId,
423
428
  workflowRunStepId: stepId,
424
429
  });
430
+ await updateWorkflowRun(runId, { status: "completed" });
425
431
  await timeoutExpiredApprovalRequests();
426
432
  expect((await getApprovalRequestById(id))!.status).toBe("timeout");
427
433
  });
@@ -490,7 +490,7 @@ describe("getBasePrompt: outputs section", () => {
490
490
  expect(result).toContain("## Outputs");
491
491
  expect(result).toContain(AGENT_FS_LINE);
492
492
  expect(result).toContain(
493
- "For a file anyone outside the swarm opens, use an `agent-fs share-create` link; `AGENT_FS_LIVE_URL` links are for signed-in teammates.",
493
+ `Give people the live app link \`\${AGENT_FS_LIVE_URL}/file/~/<org>/<drive>/<path>\`. Make an \`agent-fs share-create\` link only when the requester asks for one or the reader has no agent-fs login.`,
494
494
  );
495
495
  expect(result).not.toContain(NO_AGENT_FS_LINE);
496
496
  });
@@ -82,7 +82,11 @@ afterAll(async () => {
82
82
  await rm(projectDir, { recursive: true, force: true });
83
83
  });
84
84
 
85
- async function runHook(event: Record<string, unknown>, taskId: string) {
85
+ async function runHook(
86
+ event: Record<string, unknown>,
87
+ taskId: string,
88
+ extraEnv: Record<string, string> = {},
89
+ ) {
86
90
  const taskFile = join(projectDir, `task-${taskId}.json`);
87
91
  await Bun.write(
88
92
  taskFile,
@@ -92,6 +96,8 @@ async function runHook(event: Record<string, unknown>, taskId: string) {
92
96
  ...process.env,
93
97
  CLAUDE_PROJECT_DIR: projectDir,
94
98
  TASK_FILE: taskFile,
99
+ CONTEXT_BOUND_NOTE_TOKENS: undefined,
100
+ ...extraEnv,
95
101
  };
96
102
  return runChild(["bun", HOOK_PATH], {
97
103
  cwd: projectDir,
@@ -107,6 +113,9 @@ function parseSoleJson(stdout: string): Record<string, unknown> {
107
113
  return JSON.parse(trimmed) as Record<string, unknown>;
108
114
  }
109
115
 
116
+ /** The note is off unless CONTEXT_BOUND_NOTE_TOKENS is set. */
117
+ const noteOn = { CONTEXT_BOUND_NOTE_TOKENS: "200000" };
118
+
110
119
  const bashTouch = {
111
120
  tool_name: "Bash",
112
121
  tool_input: { command: "touch /tmp/probe" },
@@ -230,4 +239,73 @@ describe("claude hook stdout", () => {
230
239
  },
231
240
  CHILD_PROCESS_TEST_BUDGET_MS,
232
241
  );
242
+
243
+ test(
244
+ "worker context-bound note fires once as PostToolUse additionalContext",
245
+ async () => {
246
+ Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
247
+ const taskId = crypto.randomUUID();
248
+ await Bun.write(
249
+ join(projectDir, `task-${taskId}.json.context.json`),
250
+ JSON.stringify({ contextTokens: 250_000, model: "claude-opus-5-5" }),
251
+ );
252
+ const event = { hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} };
253
+
254
+ const first = await runHook(event, taskId, noteOn);
255
+ const out = parseSoleJson(first.stdout) as { hookSpecificOutput: Record<string, string> };
256
+ expect(out.hookSpecificOutput.hookEventName).toBe("PostToolUse");
257
+ expect(out.hookSpecificOutput.additionalContext).toStartWith(
258
+ "Context is 250,000 tokens (25% of the 1,000,000-token window). Keep this task bounded",
259
+ );
260
+
261
+ const second = await runHook(event, taskId, noteOn);
262
+ expect(second.exitCode).toBe(0);
263
+ expect(second.stdout.trim()).toBe("");
264
+ },
265
+ CHILD_PROCESS_TEST_BUDGET_MS * 2,
266
+ );
267
+
268
+ test(
269
+ "a subagent tool call does not consume the context-bound note",
270
+ async () => {
271
+ Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
272
+ const taskId = crypto.randomUUID();
273
+ await Bun.write(
274
+ join(projectDir, `task-${taskId}.json.context.json`),
275
+ JSON.stringify({ contextTokens: 250_000, model: "made-up-model" }),
276
+ );
277
+ const event = { hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} };
278
+
279
+ const child = await runHook({ ...event, agent_id: "child" }, taskId, noteOn);
280
+ expect(child.exitCode).toBe(0);
281
+ expect(child.stdout.trim()).toBe("");
282
+
283
+ // Unknown model: no window share rather than a percent of the 200k fallback.
284
+ const main = await runHook(event, taskId, noteOn);
285
+ const out = parseSoleJson(main.stdout) as { hookSpecificOutput: Record<string, string> };
286
+ expect(out.hookSpecificOutput.additionalContext).toStartWith(
287
+ "Context is 250,000 tokens. Keep this task bounded",
288
+ );
289
+ },
290
+ CHILD_PROCESS_TEST_BUDGET_MS * 2,
291
+ );
292
+
293
+ test(
294
+ "the context-bound note is off when CONTEXT_BOUND_NOTE_TOKENS is unset",
295
+ async () => {
296
+ Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
297
+ const taskId = crypto.randomUUID();
298
+ await Bun.write(
299
+ join(projectDir, `task-${taskId}.json.context.json`),
300
+ JSON.stringify({ contextTokens: 900_000, model: "claude-opus-5-5" }),
301
+ );
302
+ const result = await runHook(
303
+ { hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} },
304
+ taskId,
305
+ );
306
+ expect(result.exitCode).toBe(0);
307
+ expect(result.stdout.trim()).toBe("");
308
+ },
309
+ CHILD_PROCESS_TEST_BUDGET_MS,
310
+ );
233
311
  });