@desplega.ai/agent-swarm 1.169.0 → 1.171.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 +11 -2
  2. package/dist/{acp-adapter-m47zfx4a.js → acp-adapter-1z9p3fvx.js} +6 -6
  3. package/dist/{actions-ws7k3tvq.js → actions-rc2yjk8a.js} +31 -29
  4. package/dist/{amp-adapter-nvv8k8j5.js → amp-adapter-ypyf5s1x.js} +6 -6
  5. package/dist/{anthropic-messages-13yn502s.js → anthropic-messages-etkbxhhy.js} +11 -11
  6. package/dist/{app-80w6qspx.js → app-04ab2s0z.js} +9 -9
  7. package/dist/{artifact-fey7dvj9.js → artifact-eczsknc3.js} +6 -3
  8. package/dist/{assistant-jtq439kw.js → assistant-vhmjd8wf.js} +34 -32
  9. package/dist/{azure-openai-responses-9st34pfz.js → azure-openai-responses-xap57x4b.js} +7 -7
  10. package/dist/{boot-reembed-feda8wn8.js → boot-reembed-3p0m259x.js} +8 -8
  11. package/dist/{boot-reembed-3nc0aaed.js → boot-reembed-wew3gwk8.js} +8 -8
  12. package/dist/{boot-scrub-sweep-grsa2er0.js → boot-scrub-sweep-5ytwtq24.js} +9 -9
  13. package/dist/{boot-seeds-gcfgq44f.js → boot-seeds-s8p2xry8.js} +12 -12
  14. package/dist/{claude-adapter-pjv2q7t8.js → claude-adapter-akrzg6jh.js} +15 -15
  15. package/dist/{claude-managed-adapter-0wa8se82.js → claude-managed-adapter-kqqqhp2q.js} +2 -2
  16. package/dist/{claude-managed-setup-zxa9p9tx.js → claude-managed-setup-bnmjarjc.js} +11 -8
  17. package/dist/{claude-sdk-session-c9j6k9kx.js → claude-sdk-session-exq1jadx.js} +15 -15
  18. package/dist/{cli-a8vps458.js → cli-01qndrvw.js} +17274 -17242
  19. package/dist/{cli-rg1c8ccw.js → cli-0d2rqhvq.js} +497 -73
  20. package/dist/cli-0p3enk35.js +188 -0
  21. package/dist/cli-1srmzpr3.js +16 -0
  22. package/dist/{cli-zzev32jw.js → cli-231av4y6.js} +6 -6
  23. package/dist/{cli-kp3vfknv.js → cli-280gnz4v.js} +2 -2
  24. package/dist/{cli-mv2520nm.js → cli-2bw3w47j.js} +1 -1
  25. package/dist/{cli-km8559dm.js → cli-2qbs2tvf.js} +18 -10
  26. package/dist/{cli-efnd2kce.js → cli-2rbt7at8.js} +6 -1
  27. package/dist/{cli-xvc3q9br.js → cli-451ks6aw.js} +1 -1
  28. package/dist/{cli-jkfxpg1b.js → cli-4bm25xtw.js} +2 -2
  29. package/dist/{cli-zp38xvtm.js → cli-5rqrj7q0.js} +1 -1
  30. package/dist/{cli-km4x66j0.js → cli-5y67yrt6.js} +13 -13
  31. package/dist/{cli-qg2zhjfc.js → cli-62bwwc2q.js} +8 -8
  32. package/dist/{cli-s0k2j7q9.js → cli-6xxxb8qt.js} +10 -10
  33. package/dist/cli-6zx22mv6.js +0 -0
  34. package/dist/{cli-zp67qg5x.js → cli-7a0sde2x.js} +72 -1
  35. package/dist/{cli-v1w7pbd8.js → cli-7jykv7ef.js} +1 -1
  36. package/dist/{cli-rfphkzh5.js → cli-7pz3b7x5.js} +24 -4
  37. package/dist/{cli-mb75wpxf.js → cli-81r1wjyv.js} +1 -1
  38. package/dist/{cli-rbr91whd.js → cli-8mr695sb.js} +9 -5
  39. package/dist/{cli-f14fvzag.js → cli-a1b9b32n.js} +6 -1
  40. package/dist/cli-cd0hv9nh.js +32 -0
  41. package/dist/{cli-dv76vq5d.js → cli-ctret352.js} +685 -209
  42. package/dist/{cli-wt7ccy4r.js → cli-d161g69q.js} +9 -1
  43. package/dist/cli-db3m6zdt.js +362 -0
  44. package/dist/{cli-hf5s9pgf.js → cli-e3pmyt39.js} +1 -1
  45. package/dist/{cli-ysnkvdx6.js → cli-ec7hrgqp.js} +313 -91
  46. package/dist/{cli-m23r88re.js → cli-erbp6b4g.js} +3 -3
  47. package/dist/{cli-9k7mqz00.js → cli-ge593j7e.js} +30 -3
  48. package/dist/cli-h8w3fg83.js +17 -0
  49. package/dist/{cli-xvdgasgy.js → cli-hsem16hb.js} +114 -9
  50. package/dist/{cli-yvxfaem2.js → cli-hsk5gd15.js} +9 -5
  51. package/dist/{cli-7vz49g7h.js → cli-jxsgrnmq.js} +101 -4
  52. package/dist/{cli-b7y8113e.js → cli-k2v5x13t.js} +1 -1
  53. package/dist/{cli-bqrs5ycj.js → cli-m4csbs67.js} +2 -2
  54. package/dist/{cli-91r17hb1.js → cli-mzj3aqac.js} +3 -3
  55. package/dist/{cli-swrz3mj8.js → cli-n805bb98.js} +1 -1
  56. package/dist/{cli-sajr098b.js → cli-n8ybnv1k.js} +3 -3
  57. package/dist/{cli-e5qk3m2a.js → cli-nkjwe4z7.js} +19 -19
  58. package/dist/{cli-3ry5gx4f.js → cli-p2pjfq1r.js} +2 -2
  59. package/dist/{cli-61tbnjpf.js → cli-p8f7dqgt.js} +767 -541
  60. package/dist/{cli-r7eeddkq.js → cli-ph3145b7.js} +3 -3
  61. package/dist/cli-ppy4nnnh.js +168 -0
  62. package/dist/cli-r455yd58.js +832 -0
  63. package/dist/{cli-jv36y8wz.js → cli-rrbctenq.js} +1 -1
  64. package/dist/{cli-8xjst8q2.js → cli-s88my3br.js} +11 -11
  65. package/dist/{cli-vejds37t.js → cli-srmej5tp.js} +3 -3
  66. package/dist/{cli-eqfbsxsn.js → cli-tg8q0b5y.js} +14 -11
  67. package/dist/cli-w0zx9ya1.js +1351 -0
  68. package/dist/{cli-rh44xx46.js → cli-wfvedq5w.js} +15 -15
  69. package/dist/{cli-n4z21pt8.js → cli-x207g4w1.js} +1 -1
  70. package/dist/{cli-fyeft04q.js → cli-x3fy2tgp.js} +2 -2
  71. package/dist/{cli-7mhy3ths.js → cli-x61srfpp.js} +5 -5
  72. package/dist/{cli-w6xa5nf5.js → cli-y1j1ggbf.js} +8 -8
  73. package/dist/{cli-jvmtxqtc.js → cli-ywh2a87k.js} +7 -2
  74. package/dist/{cli-3qhyh31j.js → cli-ywm1azg9.js} +6 -0
  75. package/dist/{cli-z48ggfyq.js → cli-zxm9p3x4.js} +2 -2
  76. package/dist/cli.js +42 -24
  77. package/dist/{codex-adapter-0kpg09ca.js → codex-adapter-9ag75whm.js} +13 -13
  78. package/dist/{codex-hook-295jpbfa.js → codex-hook-7ba15hjd.js} +5 -3
  79. package/dist/{codex-login-g1cyt5zm.js → codex-login-841kv5fk.js} +7 -5
  80. package/dist/{codex-session-runner-8n672hxh.js → codex-session-runner-9fp7y4cf.js} +13 -13
  81. package/dist/{commands-mhmg5n0x.js → commands-d7eah0y9.js} +10 -10
  82. package/dist/controller-hq5ggpcp.js +193 -0
  83. package/dist/controllers-connect-8t0k65x2.js +265 -0
  84. package/dist/{cursor-adapter-wn8n5zzk.js → cursor-adapter-t4pr1m3c.js} +5 -5
  85. package/dist/db-2rmd23wq.js +71 -0
  86. package/dist/{db-wa6s1mwv.js → db-sp39qv72.js} +8 -8
  87. package/dist/{devin-adapter-bffjj5n4.js → devin-adapter-xgtj680j.js} +2 -2
  88. package/dist/{dispatcher-nhs2pkr8.js → dispatcher-g7fsybsd.js} +30 -28
  89. package/dist/docker-f503k9p0.js +201 -0
  90. package/dist/{dsh-adapter-5423p70m.js → dsh-adapter-2h8m4cab.js} +5 -5
  91. package/dist/e2b-66xra4az.js +202 -0
  92. package/dist/{e2b-nd50eccg.js → e2b-xwhyj8cw.js} +43 -527
  93. package/dist/{event-bus-vnfthafg.js → event-bus-vjtv82gm.js} +2 -2
  94. package/dist/{grok-adapter-28pkdt80.js → grok-adapter-1err1me7.js} +7 -7
  95. package/dist/{handlers-5rtf9nng.js → handlers-c7yggm37.js} +33 -31
  96. package/dist/heartbeat-dkm924fk.js +124 -0
  97. package/dist/{hook-qkm73zk6.js → hook-g8e7sfnf.js} +13 -13
  98. package/dist/{hook-krjfkpzy.js → hook-wgx4exw6.js} +20 -17
  99. package/dist/{http-a397xcn4.js → http-1d23k928.js} +1958 -205
  100. package/dist/{index-kqre45fr.js → index-gm10tmye.js} +30 -28
  101. package/dist/{index-rtxg15ah.js → index-q7cj9bn2.js} +39 -29
  102. package/dist/{index-vvsq7v45.js → index-va4zbvzp.js} +30 -28
  103. package/dist/{index-ac0v8v9d.js → index-vertaxs1.js} +8 -8
  104. package/dist/{index-ytdhtdry.js → index-y5vvczrs.js} +31 -22
  105. package/dist/{keepalive-psfh8ysx.js → keepalive-1jc5e7ct.js} +10 -10
  106. package/dist/{lead-m6rppjn7.js → lead-yxw2f1sg.js} +48 -46
  107. package/dist/{maintenance-41f837ke.js → maintenance-h7w2m10y.js} +8 -8
  108. package/dist/{node-8zkdq42h.js → node-c9we95xf.js} +9 -9
  109. package/dist/{oauth-refresh-sweep-6hr55hm8.js → oauth-refresh-sweep-wh9ypd1b.js} +9 -9
  110. package/dist/{onboard-ktrtd7cz.js → onboard-dfr9j762.js} +25 -15
  111. package/dist/{openai-codex-responses-jzxkta9b.js → openai-codex-responses-pgvtddvv.js} +3 -3
  112. package/dist/{openai-responses-m3g43rrt.js → openai-responses-9dyzwzkr.js} +9 -9
  113. package/dist/{opencode-adapter-yjyqynzf.js → opencode-adapter-f1zby94p.js} +3 -3
  114. package/dist/{otel-impl-dq4f2k3m.js → otel-impl-and31m5s.js} +5 -5
  115. package/dist/{pi-mono-adapter-e5v9w0hv.js → pi-mono-adapter-fx6k5wwh.js} +38 -27
  116. package/dist/{pricing-refresh-kmk7j1ra.js → pricing-refresh-23zr6j3w.js} +10 -10
  117. package/dist/{rbac-roles-9ykx9tc5.js → rbac-roles-nc7k9d0t.js} +10 -10
  118. package/dist/{rbac-roles-9w1dkkxd.js → rbac-roles-wnx35x4x.js} +11 -11
  119. package/dist/{render-v2-ycqm53np.js → render-v2-81cg1y52.js} +9 -9
  120. package/dist/{resume-cq2pkp8q.js → resume-d4b6j2n2.js} +16 -16
  121. package/dist/{setup-0dcmdvbn.js → setup-g68r1w28.js} +7 -5
  122. package/dist/static-t1gr2cr7.js +30 -0
  123. package/dist/{virtual-modules-1x9k4jcs.js → virtual-modules-rdjmcg7q.js} +3 -3
  124. package/dist/{work-objects-x01x2fh8.js → work-objects-0zzdnbt9.js} +35 -33
  125. package/dist/{worker-py1ssb1h.js → worker-p1g8qm6h.js} +48 -46
  126. package/dist/{x-ma4gnfgy.js → x-qfmygqqv.js} +2 -2
  127. package/openapi.json +3382 -167
  128. package/package.json +3 -3
  129. package/src/be/db/agents.ts +6 -0
  130. package/src/be/db/instance-signals.ts +30 -0
  131. package/src/be/db/tasks/read.ts +2 -0
  132. package/src/be/db/tasks/write.ts +39 -5
  133. package/src/be/db/workflow-runs.ts +22 -0
  134. package/src/be/db.ts +96 -40
  135. package/src/be/fleet-benchmark-stats.ts +39 -0
  136. package/src/be/migrations/205_favorites_per_dashboard_user.sql +29 -0
  137. package/src/be/migrations/206_runtime_pools.sql +62 -0
  138. package/src/be/migrations/207_runtime_session_tokens.sql +31 -0
  139. package/src/be/migrations/208_task_runtime_dispatch.sql +4 -0
  140. package/src/be/model-tier-resolution.ts +9 -2
  141. package/src/be/multi-runtime.ts +102 -13
  142. package/src/be/org-profile.ts +35 -0
  143. package/src/be/rbac-audit.ts +4 -0
  144. package/src/be/runtime-pools/controllers.ts +150 -0
  145. package/src/be/runtime-pools/db.ts +526 -0
  146. package/src/be/runtime-pools/demand.ts +65 -0
  147. package/src/be/runtime-pools/e2b-images.ts +109 -0
  148. package/src/be/runtime-pools/errors.ts +8 -0
  149. package/src/be/runtime-pools/lifecycle.ts +44 -0
  150. package/src/be/runtime-pools/reachability.ts +30 -0
  151. package/src/be/runtime-pools/recovery.ts +248 -0
  152. package/src/be/runtime-pools/remote-controller.ts +144 -0
  153. package/src/be/runtime-pools/routing.ts +304 -0
  154. package/src/be/runtime-pools/seed.ts +110 -0
  155. package/src/be/runtime-pools/supervisor.ts +981 -0
  156. package/src/be/runtime-pools/tokens.ts +57 -0
  157. package/src/be/scripts/typecheck.ts +48 -3
  158. package/src/be/secret-registry.ts +17 -1
  159. package/src/be/skill-usage.ts +467 -0
  160. package/src/be/swarm-config-guard.ts +59 -2
  161. package/src/be/telemetry-enable.ts +66 -0
  162. package/src/be/telemetry-ingest-anchor.ts +77 -0
  163. package/src/be/users.ts +173 -23
  164. package/src/be/worker-nudge-listener.ts +28 -0
  165. package/src/be/worker-nudges.ts +22 -0
  166. package/src/cli.tsx +22 -3
  167. package/src/commands/controller.ts +180 -0
  168. package/src/commands/onboard/non-interactive.ts +36 -12
  169. package/src/commands/onboard/types.ts +1 -1
  170. package/src/commands/onboard.tsx +3 -0
  171. package/src/commands/push-delivery.ts +230 -0
  172. package/src/commands/runner.ts +560 -276
  173. package/src/e2b/dispatch.ts +15 -1
  174. package/src/extensions/contract-types.generated.ts +1 -1
  175. package/src/fleet-benchmark.ts +157 -0
  176. package/src/heartbeat/heartbeat.ts +53 -135
  177. package/src/hooks/hook.ts +3 -2
  178. package/src/http/agents.ts +21 -0
  179. package/src/http/all-routes.ts +6 -0
  180. package/src/http/apps.ts +4 -2
  181. package/src/http/auth.ts +4 -7
  182. package/src/http/config.ts +58 -21
  183. package/src/http/controllers-connect.ts +206 -0
  184. package/src/http/core.ts +108 -14
  185. package/src/http/favorite-owner.ts +31 -1
  186. package/src/http/fleet-benchmark.ts +206 -0
  187. package/src/http/index.ts +38 -6
  188. package/src/http/mcp.ts +3 -8
  189. package/src/http/poll.ts +31 -2
  190. package/src/http/realtime.ts +1 -1
  191. package/src/http/request-principal.ts +2 -0
  192. package/src/http/runtime-pools.ts +527 -0
  193. package/src/http/session-data.ts +2 -0
  194. package/src/http/sessions.ts +100 -23
  195. package/src/http/skills.ts +173 -0
  196. package/src/http/status.ts +25 -1
  197. package/src/http/tasks.ts +14 -1
  198. package/src/http/telemetry-ui.ts +102 -0
  199. package/src/http/used-by.ts +140 -0
  200. package/src/http/utils.ts +7 -1
  201. package/src/http/workflows.ts +10 -2
  202. package/src/jira/app.ts +1 -2
  203. package/src/kv-reserved-namespaces.ts +1 -0
  204. package/src/linear/app.ts +1 -2
  205. package/src/providers/acp-target-catalog.ts +75 -1
  206. package/src/providers/acp-targets.ts +114 -0
  207. package/src/providers/amp-adapter.ts +1 -1
  208. package/src/providers/claude-adapter.ts +1 -1
  209. package/src/providers/codex-adapter.ts +5 -1
  210. package/src/providers/opencode-adapter.ts +2 -1
  211. package/src/providers/pi-mono-adapter.ts +41 -26
  212. package/src/providers/pricing-sources.md +2 -0
  213. package/src/providers/skill-invoke.ts +39 -3
  214. package/src/rbac/legacy-policy.ts +8 -0
  215. package/src/rbac/permissions.ts +16 -0
  216. package/src/rbac/types.ts +2 -0
  217. package/src/realtime/auth.ts +13 -0
  218. package/src/realtime/channels.ts +24 -0
  219. package/src/realtime/transport.ts +94 -15
  220. package/src/realtime/worker-client.ts +176 -0
  221. package/src/runtime-providers/controller-protocol.ts +97 -0
  222. package/src/runtime-providers/controller.ts +86 -0
  223. package/src/runtime-providers/docker.ts +235 -0
  224. package/src/runtime-providers/e2b.ts +217 -0
  225. package/src/runtime-providers/index.ts +37 -0
  226. package/src/runtime-providers/static.ts +26 -0
  227. package/src/runtime-providers/types.ts +92 -0
  228. package/src/runtime-providers/worker-api-url.ts +11 -0
  229. package/src/scripts-runtime/sdk-allowlist.ts +6 -0
  230. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  231. package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
  232. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  233. package/src/server.ts +12 -0
  234. package/src/slack/router.ts +7 -5
  235. package/src/tasks/worker-follow-up.ts +80 -52
  236. package/src/telemetry-context.ts +6 -0
  237. package/src/telemetry-contract/EVENTS.md +14 -1
  238. package/src/telemetry-contract/SOURCE.json +4 -4
  239. package/src/telemetry-contract/catalog.json +155 -0
  240. package/src/telemetry-contract/types.gen.ts +248 -4
  241. package/src/telemetry-ui-events.ts +130 -0
  242. package/src/telemetry.ts +43 -3
  243. package/src/tests/acp-adapter.test.ts +129 -0
  244. package/src/tests/claude-adapter.test.ts +6 -2
  245. package/src/tests/codex-adapter.test.ts +6 -2
  246. package/src/tests/config-internal-keys.test.ts +27 -0
  247. package/src/tests/config-session-auth.test.ts +19 -3
  248. package/src/tests/db-retention.test.ts +50 -10
  249. package/src/tests/docs-index-footprint.test.js +3 -3
  250. package/src/tests/e2b-dispatch.test.ts +28 -1
  251. package/src/tests/e2e-runtime-pools.test.ts +51 -0
  252. package/src/tests/entrypoint-acp-target-binary.test.ts +38 -1
  253. package/src/tests/favorites.test.ts +188 -0
  254. package/src/tests/fetch-resolved-env.test.ts +59 -1
  255. package/src/tests/fleet-benchmark.test.ts +453 -0
  256. package/src/tests/harness-model-support.test.ts +70 -2
  257. package/src/tests/heartbeat-orphan-resume.test.ts +24 -0
  258. package/src/tests/heartbeat-reboot-live.test.ts +31 -0
  259. package/src/tests/heartbeat-supersede-resume.test.ts +21 -0
  260. package/src/tests/hook-runtime-close.test.ts +64 -0
  261. package/src/tests/http-task-write-ownership.test.ts +8 -1
  262. package/src/tests/integration-disable-flags.test.ts +45 -0
  263. package/src/tests/migration-206-runtime-pools.test.ts +134 -0
  264. package/src/tests/migration-207-runtime-session-tokens.test.ts +45 -0
  265. package/src/tests/migration-208-task-runtime-dispatch.test.ts +30 -0
  266. package/src/tests/multi-runtime-registration.test.ts +219 -4
  267. package/src/tests/onboard-non-interactive.test.ts +104 -6
  268. package/src/tests/opencode-adapter.test.ts +6 -2
  269. package/src/tests/org-website.test.ts +115 -0
  270. package/src/tests/pi-mono-adapter.test.ts +74 -10
  271. package/src/tests/rbac-admission-e2e.test.ts +4 -4
  272. package/src/tests/rbac-engine.test.ts +8 -1
  273. package/src/tests/realtime-room-auth.test.ts +49 -0
  274. package/src/tests/repo-allow-merge-gate.test.ts +8 -1
  275. package/src/tests/runner-exit-after-tasks.test.ts +163 -0
  276. package/src/tests/runner-finish-retry.test.ts +466 -0
  277. package/src/tests/runner-harness-telemetry-env.test.ts +105 -0
  278. package/src/tests/runner-push-delivery.test.ts +670 -0
  279. package/src/tests/runner-slot-reconcile.test.ts +89 -1
  280. package/src/tests/runtime-child-token-cleanup.test.ts +172 -0
  281. package/src/tests/runtime-controller-token.test.ts +254 -0
  282. package/src/tests/runtime-demand.test.ts +119 -0
  283. package/src/tests/runtime-e2b-pools.test.ts +847 -0
  284. package/src/tests/runtime-pool-reachability.test.ts +328 -0
  285. package/src/tests/runtime-pool-routing.test.ts +817 -0
  286. package/src/tests/runtime-pools-db.test.ts +275 -0
  287. package/src/tests/runtime-pools-http.test.ts +506 -0
  288. package/src/tests/runtime-pools-ownership-guards.test.ts +235 -0
  289. package/src/tests/runtime-pools-seed.test.ts +203 -0
  290. package/src/tests/runtime-pools-test-utils.ts +128 -0
  291. package/src/tests/runtime-provider-registration.test.ts +133 -0
  292. package/src/tests/runtime-providers-docker.test.ts +400 -0
  293. package/src/tests/runtime-providers-e2b-transport.test.ts +50 -0
  294. package/src/tests/runtime-providers-e2b.test.ts +335 -0
  295. package/src/tests/runtime-recovery.test.ts +745 -0
  296. package/src/tests/runtime-remote-controller.test.ts +846 -0
  297. package/src/tests/runtime-supervisor-failures.test.ts +387 -0
  298. package/src/tests/runtime-supervisor.test.ts +1039 -0
  299. package/src/tests/runtime-token-auth.test.ts +897 -0
  300. package/src/tests/script-connections.test.ts +7 -0
  301. package/src/tests/secret-scrubber.test.ts +17 -2
  302. package/src/tests/session-costs-codex-recompute.test.ts +49 -0
  303. package/src/tests/session-tokens.test.ts +13 -3
  304. package/src/tests/skill-invoke.test.ts +142 -0
  305. package/src/tests/skill-usage-cohorts.test.ts +232 -0
  306. package/src/tests/skill-usage.test.ts +348 -0
  307. package/src/tests/slack-attachments-block.test.ts +23 -0
  308. package/src/tests/slack-render-v2.test.ts +6 -0
  309. package/src/tests/status-instance.test.ts +71 -0
  310. package/src/tests/task-supersede-resume.test.ts +87 -4
  311. package/src/tests/telemetry-enable.test.ts +113 -0
  312. package/src/tests/telemetry-ui-route.test.ts +195 -0
  313. package/src/tests/tool-annotations.test.ts +1 -1
  314. package/src/tests/used-by-route.test.ts +237 -0
  315. package/src/tests/worker-nudges.test.ts +653 -0
  316. package/src/tests/worker-realtime-client.test.ts +188 -0
  317. package/src/tests/workflow-sub-workflow.test.ts +22 -0
  318. package/src/tools/app-upsert.ts +1 -1
  319. package/src/tools/runtime-controller-list.ts +28 -0
  320. package/src/tools/runtime-pool-bind-agent.ts +29 -0
  321. package/src/tools/runtime-pool-common.ts +37 -0
  322. package/src/tools/runtime-pool-create-agent.ts +29 -0
  323. package/src/tools/runtime-pool-delete.ts +27 -0
  324. package/src/tools/runtime-pool-list.ts +27 -0
  325. package/src/tools/runtime-pool-upsert.ts +31 -0
  326. package/src/tools/script-common.ts +1 -1
  327. package/src/tools/send-task.ts +3 -2
  328. package/src/tools/tool-config.ts +6 -0
  329. package/src/types.ts +137 -2
  330. package/src/utils/config-secrets.ts +1 -0
  331. package/src/utils/lead-monitor-channels.ts +3 -0
  332. package/src/utils/org-website.ts +104 -0
  333. package/src/utils/request-auth-context.ts +10 -2
  334. package/src/utils/runtime-pools-flag.ts +11 -0
  335. package/src/utils/secret-scrubber.ts +9 -0
  336. package/src/utils/session-token-connections.ts +20 -0
  337. package/templates/skills/apps/content.md +1 -1
  338. package/templates/skills/code-quality/content.md +9 -1
  339. package/templates/skills/db-query-guidance/content.md +9 -0
  340. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  341. package/templates/skills/swarm-scripts/content.md +1 -1
  342. package/templates/skills/work-on-task/SKILL.md +1 -1
  343. package/templates/skills/work-on-task/content.md +1 -1
  344. package/dist/{cli-qd97v6z2.js → cli-1qtgd1h1.js} +3 -3
  345. package/dist/{cli-cmxkbzzs.js → cli-j0w59p15.js} +6 -6
  346. package/dist/{google-generative-ai-b8zxrpax.js → google-generative-ai-pvhm0dq1.js} +1 -1
  347. package/dist/{google-vertex-nrdd697t.js → google-vertex-tek9sqe2.js} +4 -4
  348. package/dist/{mistral-conversations-059y51zt.js → mistral-conversations-rjxa9s1j.js} +3 -3
  349. package/dist/{openai-completions-663yncfq.js → openai-completions-44gb0zsk.js} +12 -12
@@ -3,44 +3,45 @@ import {
3
3
  } from "./cli-x6z90nq9.js";
4
4
  import {
5
5
  checkCursorCredentials
6
- } from "./cli-zzev32jw.js";
6
+ } from "./cli-231av4y6.js";
7
7
  import {
8
8
  checkGrokCredentials
9
- } from "./cli-kp3vfknv.js";
9
+ } from "./cli-280gnz4v.js";
10
10
  import {
11
11
  createSkillInvokeTracker,
12
12
  isSkillLoaderCall,
13
13
  skillFromToolArgs
14
- } from "./cli-9k7mqz00.js";
14
+ } from "./cli-ge593j7e.js";
15
15
  import {
16
16
  checkCodexCredentials,
17
17
  codexUserHome
18
- } from "./cli-km8559dm.js";
18
+ } from "./cli-2qbs2tvf.js";
19
19
  import {
20
20
  checkClaudeManagedCredentials
21
- } from "./cli-b7y8113e.js";
21
+ } from "./cli-k2v5x13t.js";
22
22
  import {
23
23
  checkDevinCredentials
24
- } from "./cli-hf5s9pgf.js";
24
+ } from "./cli-e3pmyt39.js";
25
25
  import {
26
26
  checkOpencodeCredentials
27
- } from "./cli-jvmtxqtc.js";
27
+ } from "./cli-ywh2a87k.js";
28
28
  import {
29
29
  checkDshCredentials
30
- } from "./cli-z48ggfyq.js";
30
+ } from "./cli-zxm9p3x4.js";
31
31
  import {
32
32
  checkAmpCredentials,
33
33
  liveTestAmpCredentials
34
- } from "./cli-sajr098b.js";
34
+ } from "./cli-n8ybnv1k.js";
35
35
  import {
36
+ isLeadChannelMonitoringEnabled,
36
37
  isUnknownModelError,
37
38
  renderMemoriesPrompt,
38
39
  writeSkillsToFilesystem
39
- } from "./cli-efnd2kce.js";
40
+ } from "./cli-2rbt7at8.js";
40
41
  import {
41
42
  ACP_TARGET_IDS,
42
43
  isAcpTarget
43
- } from "./cli-zp67qg5x.js";
44
+ } from "./cli-7a0sde2x.js";
44
45
  import {
45
46
  CODEX_OAUTH_POOL_SLOTS_ENV,
46
47
  countCodexOAuthPoolSlots,
@@ -49,7 +50,7 @@ import {
49
50
  } from "./cli-bqjfjex4.js";
50
51
  import {
51
52
  checkClaudeCredentials
52
- } from "./cli-qg2zhjfc.js";
53
+ } from "./cli-62bwwc2q.js";
53
54
  import {
54
55
  getContextWindowSize
55
56
  } from "./cli-7avy4v8w.js";
@@ -66,21 +67,18 @@ import {
66
67
  isAzureDevOpsUrl,
67
68
  parseAzureDevOpsRepoUrl,
68
69
  resolveScriptsOnlyMode
69
- } from "./cli-61tbnjpf.js";
70
- import {
71
- getApiKey
72
- } from "./cli-f14fvzag.js";
70
+ } from "./cli-p8f7dqgt.js";
73
71
  import {
74
72
  terminateRegisteredProcessGroups
75
73
  } from "./cli-zt3w1cn1.js";
76
74
  import {
77
75
  validateJsonSchema
78
- } from "./cli-rh44xx46.js";
76
+ } from "./cli-wfvedq5w.js";
79
77
  import {
80
78
  authJsonToCredentialSelection,
81
79
  credentialsToAuthJson,
82
80
  loadAllCodexOAuthSlots
83
- } from "./cli-v1w7pbd8.js";
81
+ } from "./cli-7jykv7ef.js";
84
82
  import {
85
83
  ensure,
86
84
  initOtel,
@@ -90,25 +88,28 @@ import {
90
88
  startSpan,
91
89
  withSpan,
92
90
  withSpanContext
93
- } from "./cli-e5qk3m2a.js";
91
+ } from "./cli-nkjwe4z7.js";
94
92
  import {
93
+ MASKED_SECRET_VALUE,
95
94
  initTelemetry,
96
95
  init_api_drain,
97
96
  init_codex_auth_failure,
97
+ init_config_secrets,
98
98
  init_telemetry,
99
99
  init_telemetry_context,
100
100
  isApiDrainingResponse,
101
101
  isCodexAuthFailureReason,
102
102
  mapTriggerSurface,
103
103
  telemetry
104
- } from "./cli-rg1c8ccw.js";
104
+ } from "./cli-0d2rqhvq.js";
105
105
  import {
106
106
  ProviderNameSchema,
107
+ WorkerNudgeSchema,
107
108
  init_types,
108
109
  isTerminalTaskStatus,
109
110
  parseWorkerModelTierOverrides,
110
111
  resolveTaskModelSelection
111
- } from "./cli-xvdgasgy.js";
112
+ } from "./cli-hsem16hb.js";
112
113
  import {
113
114
  buildContextPreamble,
114
115
  buildResumeContextPreamble,
@@ -130,7 +131,7 @@ import {
130
131
  taskAttachmentFetchCommand,
131
132
  writeIdentityBaselines,
132
133
  writeProfileFileFromDb
133
- } from "./cli-jv36y8wz.js";
134
+ } from "./cli-rrbctenq.js";
134
135
  import {
135
136
  init_steering_enabled,
136
137
  isSteeringEnabled
@@ -168,10 +169,6 @@ import {
168
169
  getOpenRouterBaseUrl,
169
170
  init_openrouter_base_url
170
171
  } from "./cli-a35hzdve.js";
171
- import {
172
- getMcpBaseUrl,
173
- init_constants
174
- } from "./cli-gd4favx9.js";
175
172
  import {
176
173
  harnessModelMismatch,
177
174
  init_runtime_model_catalog,
@@ -179,11 +176,19 @@ import {
179
176
  refreshRuntimeModelCatalog,
180
177
  runtimeHarnessSections
181
178
  } from "./cli-014vns2y.js";
179
+ import {
180
+ getMcpBaseUrl,
181
+ init_constants
182
+ } from "./cli-gd4favx9.js";
183
+ import {
184
+ getApiKey,
185
+ init_api_key
186
+ } from "./cli-a1b9b32n.js";
182
187
  import {
183
188
  init_secret_scrubber,
184
189
  registerSensitiveKeyName,
185
190
  scrubSecrets
186
- } from "./cli-wt7ccy4r.js";
191
+ } from "./cli-d161g69q.js";
187
192
  import {
188
193
  __require
189
194
  } from "./cli-bc05qbss.js";
@@ -220,47 +225,47 @@ async function materializeCodexAuthJson(_slot, creds, deps = {}) {
220
225
  async function createProviderAdapter(provider) {
221
226
  switch (provider) {
222
227
  case "claude": {
223
- const { ClaudeAdapter } = await import("./claude-adapter-pjv2q7t8.js");
228
+ const { ClaudeAdapter } = await import("./claude-adapter-akrzg6jh.js");
224
229
  return new ClaudeAdapter;
225
230
  }
226
231
  case "pi": {
227
- const { PiMonoAdapter } = await import("./pi-mono-adapter-e5v9w0hv.js");
232
+ const { PiMonoAdapter } = await import("./pi-mono-adapter-fx6k5wwh.js");
228
233
  return new PiMonoAdapter;
229
234
  }
230
235
  case "codex": {
231
- const { CodexAdapter } = await import("./codex-adapter-0kpg09ca.js");
236
+ const { CodexAdapter } = await import("./codex-adapter-9ag75whm.js");
232
237
  return new CodexAdapter;
233
238
  }
234
239
  case "claude-managed": {
235
- const { ClaudeManagedAdapter } = await import("./claude-managed-adapter-0wa8se82.js");
240
+ const { ClaudeManagedAdapter } = await import("./claude-managed-adapter-kqqqhp2q.js");
236
241
  return new ClaudeManagedAdapter;
237
242
  }
238
243
  case "devin": {
239
- const { DevinAdapter } = await import("./devin-adapter-bffjj5n4.js");
244
+ const { DevinAdapter } = await import("./devin-adapter-xgtj680j.js");
240
245
  return new DevinAdapter;
241
246
  }
242
247
  case "opencode": {
243
- const { OpencodeAdapter } = await import("./opencode-adapter-yjyqynzf.js");
248
+ const { OpencodeAdapter } = await import("./opencode-adapter-f1zby94p.js");
244
249
  return new OpencodeAdapter;
245
250
  }
246
251
  case "dsh": {
247
- const { DshAdapter } = await import("./dsh-adapter-5423p70m.js");
252
+ const { DshAdapter } = await import("./dsh-adapter-2h8m4cab.js");
248
253
  return new DshAdapter;
249
254
  }
250
255
  case "amp": {
251
- const { AmpAdapter } = await import("./amp-adapter-nvv8k8j5.js");
256
+ const { AmpAdapter } = await import("./amp-adapter-ypyf5s1x.js");
252
257
  return new AmpAdapter;
253
258
  }
254
259
  case "cursor": {
255
- const { CursorAdapter } = await import("./cursor-adapter-wn8n5zzk.js");
260
+ const { CursorAdapter } = await import("./cursor-adapter-t4pr1m3c.js");
256
261
  return new CursorAdapter;
257
262
  }
258
263
  case "grok": {
259
- const { GrokAdapter } = await import("./grok-adapter-28pkdt80.js");
264
+ const { GrokAdapter } = await import("./grok-adapter-1err1me7.js");
260
265
  return new GrokAdapter;
261
266
  }
262
267
  case "acp": {
263
- const { ACPAdapter } = await import("./acp-adapter-m47zfx4a.js");
268
+ const { ACPAdapter } = await import("./acp-adapter-1z9p3fvx.js");
264
269
  return new ACPAdapter;
265
270
  }
266
271
  default:
@@ -268,11 +273,174 @@ async function createProviderAdapter(provider) {
268
273
  }
269
274
  }
270
275
 
276
+ // src/realtime/worker-client.ts
277
+ init_types();
278
+ init_api_key();
279
+ init_secret_scrubber();
280
+ function workerReconnectDelay(attempt, random = Math.random()) {
281
+ return Math.min(30000, Math.max(1000, 1000 * 2 ** Math.min(attempt, 5) * (0.8 + random * 0.4)));
282
+ }
283
+
284
+ class WorkerRealtimeClient {
285
+ config;
286
+ socket;
287
+ retry;
288
+ connectTimeout;
289
+ stopped = true;
290
+ connected = false;
291
+ connectedAt = 0;
292
+ disconnectedAt = Date.now();
293
+ attempt = 0;
294
+ listeners = new Set;
295
+ pending = new Map;
296
+ waiters = new Set;
297
+ constructor(config) {
298
+ this.config = config;
299
+ }
300
+ start() {
301
+ if (!this.stopped)
302
+ return;
303
+ this.stopped = false;
304
+ this.attempt = 0;
305
+ this.disconnectedAt = Date.now();
306
+ this.connect();
307
+ }
308
+ stop() {
309
+ this.stopped = true;
310
+ this.connected = false;
311
+ clearTimeout(this.retry);
312
+ clearTimeout(this.connectTimeout);
313
+ const socket = this.socket;
314
+ this.socket = undefined;
315
+ socket?.close();
316
+ this.pending.clear();
317
+ for (const waiter of this.waiters)
318
+ waiter.finish(null);
319
+ }
320
+ isConnected() {
321
+ return this.connected;
322
+ }
323
+ disconnectedSince() {
324
+ return this.disconnectedAt;
325
+ }
326
+ onNudge(fn) {
327
+ this.listeners.add(fn);
328
+ return () => this.listeners.delete(fn);
329
+ }
330
+ waitForNudge(kinds, timeoutMs) {
331
+ for (const [key, nudge] of this.pending) {
332
+ if (!kinds.includes(nudge.kind))
333
+ continue;
334
+ this.pending.delete(key);
335
+ return Promise.resolve(nudge);
336
+ }
337
+ if (this.stopped)
338
+ return Promise.resolve(null);
339
+ return new Promise((resolve) => {
340
+ const waiter = {
341
+ kinds,
342
+ finish: (nudge) => {
343
+ clearTimeout(timer);
344
+ this.waiters.delete(waiter);
345
+ resolve(nudge);
346
+ }
347
+ };
348
+ const timer = setTimeout(() => waiter.finish(null), timeoutMs);
349
+ this.waiters.add(waiter);
350
+ });
351
+ }
352
+ emit(nudge) {
353
+ let delivered = this.listeners.size > 0;
354
+ for (const listener of this.listeners)
355
+ listener(nudge);
356
+ for (const waiter of this.waiters) {
357
+ if (!waiter.kinds.includes(nudge.kind))
358
+ continue;
359
+ delivered = true;
360
+ waiter.finish(nudge);
361
+ }
362
+ if (!delivered) {
363
+ this.pending.set(`${nudge.kind}:${nudge.taskId ?? ""}`, nudge);
364
+ if (this.pending.size > 128)
365
+ this.pending.delete(this.pending.keys().next().value);
366
+ }
367
+ }
368
+ connect() {
369
+ if (this.stopped)
370
+ return;
371
+ const url = new URL("/api/realtime", this.config.apiUrl);
372
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
373
+ try {
374
+ const socket = new WebSocket(url, {
375
+ headers: {
376
+ Authorization: `Bearer ${this.config.apiKey ?? getApiKey()}`,
377
+ "X-Agent-ID": this.config.agentId,
378
+ "X-Runtime-Instance-ID": this.config.runtimeInstanceId
379
+ }
380
+ });
381
+ this.socket = socket;
382
+ this.connectTimeout = setTimeout(() => socket.close(), 15000);
383
+ socket.onopen = () => socket.send(JSON.stringify({
384
+ id: 1,
385
+ op: "subscribe",
386
+ namespace: `worker:${this.config.agentId}`,
387
+ name: "nudge"
388
+ }));
389
+ socket.onmessage = (event) => {
390
+ if (this.socket !== socket || this.stopped)
391
+ return;
392
+ try {
393
+ const frame = JSON.parse(String(event.data));
394
+ if (typeof frame.seq === "number")
395
+ socket.send(JSON.stringify({ ack: frame.seq }));
396
+ if (frame.id === 1 && frame.type === "result") {
397
+ clearTimeout(this.connectTimeout);
398
+ this.connected = true;
399
+ this.connectedAt = Date.now();
400
+ for (const kind of ["work", "config"])
401
+ this.emit({ v: 1, kind, at: new Date().toISOString() });
402
+ } else if (frame.id === 1 && frame.type === "error") {
403
+ console.warn(scrubSecrets("[worker-realtime] Subscription failed. Reconnecting."));
404
+ socket.close();
405
+ } else if (frame.type === "channel" && frame.namespace === `worker:${this.config.agentId}` && frame.name === "nudge") {
406
+ const parsed = WorkerNudgeSchema.safeParse(frame.data);
407
+ if (parsed.success)
408
+ this.emit(parsed.data);
409
+ }
410
+ } catch {}
411
+ };
412
+ socket.onerror = () => socket.close();
413
+ socket.onclose = () => {
414
+ if (this.socket !== socket)
415
+ return;
416
+ clearTimeout(this.connectTimeout);
417
+ if (this.connected) {
418
+ this.disconnectedAt = Date.now();
419
+ if (this.disconnectedAt - this.connectedAt >= 30000)
420
+ this.attempt = 0;
421
+ }
422
+ this.connected = false;
423
+ this.socket = undefined;
424
+ this.scheduleReconnect();
425
+ };
426
+ } catch {
427
+ this.scheduleReconnect();
428
+ }
429
+ }
430
+ scheduleReconnect() {
431
+ if (this.stopped)
432
+ return;
433
+ const delay = workerReconnectDelay(this.attempt++);
434
+ this.retry = setTimeout(() => this.connect(), delay);
435
+ }
436
+ }
437
+
271
438
  // src/commands/runner.ts
272
439
  init_telemetry();
273
440
  init_telemetry_context();
274
441
  init_types();
275
442
  init_api_drain();
443
+ init_api_key();
276
444
 
277
445
  // src/utils/budget-backoff.ts
278
446
  var BUDGET_BACKOFF_CAP_MS = 5 * 60 * 1000;
@@ -285,6 +453,7 @@ function computeBudgetBackoffMs(consecutiveRefusals, basePollMs) {
285
453
 
286
454
  // src/commands/runner.ts
287
455
  init_codex_auth_failure();
456
+ init_config_secrets();
288
457
  init_constants();
289
458
  init_credentials();
290
459
 
@@ -961,7 +1130,7 @@ var CREDENTIAL_PROVIDER_CHECKERS = {
961
1130
  devin: (env) => checkDevinCredentials(env),
962
1131
  opencode: (env, opts) => checkOpencodeCredentials(env, opts),
963
1132
  pi: async (env, opts) => {
964
- const { checkPiMonoCredentials } = await import("./pi-mono-adapter-e5v9w0hv.js");
1133
+ const { checkPiMonoCredentials } = await import("./pi-mono-adapter-fx6k5wwh.js");
965
1134
  return checkPiMonoCredentials(env, opts);
966
1135
  },
967
1136
  acp: (env) => {
@@ -1211,7 +1380,7 @@ async function validateProviderCredentials(provider, env = process.env, presence
1211
1380
  latency_ms: Date.now() - startedAt
1212
1381
  };
1213
1382
  }
1214
- const { liveTestCursorKey } = await import("./cursor-adapter-wn8n5zzk.js");
1383
+ const { liveTestCursorKey } = await import("./cursor-adapter-t4pr1m3c.js");
1215
1384
  const result = await liveTestCursorKey(apiKey);
1216
1385
  return result.ok ? { ok: true, latency_ms: Date.now() - startedAt } : { ok: false, error: result.error, latency_ms: Date.now() - startedAt };
1217
1386
  }
@@ -1556,6 +1725,150 @@ async function refreshIdentityIfChanged(ctx, cached) {
1556
1725
  // src/commands/runner.ts
1557
1726
  init_profile_sync();
1558
1727
 
1728
+ // src/commands/push-delivery.ts
1729
+ init_secret_scrubber();
1730
+ function resolveWorkerDeliveryMode(serverMode, isLead, env = process.env, pollRequired = false) {
1731
+ const mode = env.SWARM_DELIVERY_MODE === "push" || env.SWARM_DELIVERY_MODE === "poll" ? env.SWARM_DELIVERY_MODE : serverMode ?? "poll";
1732
+ if ((mode === "push" || pollRequired) && isLead && (pollRequired || isLeadChannelMonitoringEnabled(env))) {
1733
+ console.warn(scrubSecrets("[runner] LEAD_MONITOR_CHANNELS requires poll delivery. Using poll."));
1734
+ return "poll";
1735
+ }
1736
+ return mode;
1737
+ }
1738
+ function updateWorkerDeliveryMode(previousMode, registration, isLead, env = process.env) {
1739
+ const mode = resolveWorkerDeliveryMode(registration.deliveryMode, isLead, env, registration.pollRequired);
1740
+ if (mode !== previousMode) {
1741
+ const override = env.SWARM_DELIVERY_MODE;
1742
+ const source = override === "poll" || override === "push" ? `SWARM_DELIVERY_MODE override: ${override}` : registration.deliveryMode ? `register response: ${registration.deliveryMode}` : "register response omitted deliveryMode, default: poll";
1743
+ const required = isLead && (registration.pollRequired || isLeadChannelMonitoringEnabled(env)) ? ", LEAD_MONITOR_CHANNELS requires poll" : "";
1744
+ console.log(scrubSecrets(`[runner] Delivery mode: ${mode} (${source}${required}).`));
1745
+ }
1746
+ return mode;
1747
+ }
1748
+ function taskReconciliation(checks) {
1749
+ return {
1750
+ cancelTaskIds: checks.push && !checks.safety ? checks.cancelTaskIds : undefined,
1751
+ checkStatus: !checks.push || checks.safety
1752
+ };
1753
+ }
1754
+ function shouldRunDeliveryLoop(mode, shuttingDown) {
1755
+ return mode !== "push" || !shuttingDown;
1756
+ }
1757
+ function deliverySchedule(options) {
1758
+ const blocked = options.draining || options.activeCount >= options.maxConcurrent;
1759
+ return {
1760
+ poll: !blocked && (!options.push || options.work),
1761
+ singlePoll: options.mode === "push",
1762
+ waitKind: options.push ? "push" : options.mode === "push" && !blocked ? "fallback" : "sleep",
1763
+ waitMs: blocked || options.push && options.activeCount > 0 ? 1000 : options.push ? Math.max(0, Math.min(resolvePushSafetyPollMs(), 30000 - ((options.now ?? Date.now()) - options.lastPushPingAt))) : options.pollInterval
1764
+ };
1765
+ }
1766
+ function resolvePushSafetyPollMs(env = process.env) {
1767
+ const value = Number(env.RUNNER_PUSH_SAFETY_POLL_MS);
1768
+ return Number.isFinite(value) && value > 0 ? value : 30000;
1769
+ }
1770
+
1771
+ class RunnerPushDelivery {
1772
+ client;
1773
+ lastSafetyAt = -Infinity;
1774
+ fallback = false;
1775
+ work = true;
1776
+ config = false;
1777
+ cancel = new Set;
1778
+ steer = new Set;
1779
+ constructor(client) {
1780
+ this.client = client;
1781
+ client.onNudge((nudge) => this.receive(nudge));
1782
+ }
1783
+ receive(nudge) {
1784
+ if (nudge.kind === "work")
1785
+ this.work = true;
1786
+ if (nudge.kind === "config")
1787
+ this.config = true;
1788
+ if (nudge.kind === "cancel" && nudge.taskId)
1789
+ this.cancel.add(nudge.taskId);
1790
+ if (nudge.kind === "steer" && nudge.taskId)
1791
+ this.steer.add(nudge.taskId);
1792
+ }
1793
+ checks(mode, now = Date.now(), safetyMs = resolvePushSafetyPollMs()) {
1794
+ if (this.client.isConnected()) {
1795
+ if (this.fallback && mode === "push")
1796
+ console.log(scrubSecrets("[runner] Delivery mode: push (push socket reconnected)."));
1797
+ this.fallback = false;
1798
+ } else {
1799
+ if (mode === "push" && now - this.client.disconnectedSince() > 60000 && !this.fallback) {
1800
+ this.fallback = true;
1801
+ console.warn(scrubSecrets("[runner] Delivery mode: poll (push socket unavailable for 60 seconds). Using poll until reconnect."));
1802
+ }
1803
+ }
1804
+ const push = mode === "push" && !this.fallback;
1805
+ const safety = push && now - this.lastSafetyAt >= safetyMs;
1806
+ if (safety)
1807
+ this.lastSafetyAt = now;
1808
+ if (safety)
1809
+ this.work = true;
1810
+ const checks = {
1811
+ push,
1812
+ safety,
1813
+ work: !push || this.work,
1814
+ config: !push || safety || this.config,
1815
+ cancelTaskIds: new Set(this.cancel),
1816
+ steerTaskIds: new Set(this.steer)
1817
+ };
1818
+ this.config = false;
1819
+ this.cancel.clear();
1820
+ this.steer.clear();
1821
+ return checks;
1822
+ }
1823
+ beginPoll() {
1824
+ this.work = false;
1825
+ }
1826
+ wakeWork() {
1827
+ this.work = true;
1828
+ }
1829
+ schedule(options) {
1830
+ return deliverySchedule({ ...options, work: this.work });
1831
+ }
1832
+ dispatchSteering(checks, taskId, dispatch) {
1833
+ if (checks.push && !checks.safety && !checks.steerTaskIds.has(taskId))
1834
+ return;
1835
+ if (!dispatch() && checks.push)
1836
+ this.steer.add(taskId);
1837
+ }
1838
+ async poll(singlePoll, fetch2) {
1839
+ if (singlePoll)
1840
+ this.beginPoll();
1841
+ let failed = false;
1842
+ const trigger = await fetch2(() => {
1843
+ failed = true;
1844
+ });
1845
+ if (singlePoll && (failed || trigger))
1846
+ this.wakeWork();
1847
+ return { trigger, failed };
1848
+ }
1849
+ async waitForSchedule(schedule, failed = false, pollInterval = 2000) {
1850
+ if (failed) {
1851
+ await Bun.sleep(pollInterval);
1852
+ return;
1853
+ }
1854
+ if (schedule.poll && schedule.waitKind === "push" && (this.work || this.config || this.cancel.size > 0 || this.steer.size > 0))
1855
+ return;
1856
+ const timeout = schedule.waitMs;
1857
+ if (schedule.waitKind === "push")
1858
+ await this.wait(timeout);
1859
+ else if (schedule.waitKind === "fallback")
1860
+ await this.client.waitForNudge(["work", "cancel", "steer", "config"], timeout);
1861
+ else
1862
+ await Bun.sleep(timeout);
1863
+ }
1864
+ async wait(timeoutMs) {
1865
+ const now = Date.now();
1866
+ const untilSafety = this.lastSafetyAt + resolvePushSafetyPollMs() - now;
1867
+ const untilFallback = !this.client.isConnected() && !this.fallback ? this.client.disconnectedSince() + 60001 - now : Infinity;
1868
+ await this.client.waitForNudge(["work", "cancel", "steer", "config"], Math.max(0, Math.min(timeoutMs, untilSafety, untilFallback)));
1869
+ }
1870
+ }
1871
+
1559
1872
  // src/commands/resume-session.ts
1560
1873
  var RESUME_DEPRECATED_REASON = "native resume deprecated — using context preamble";
1561
1874
  function resolveResumeSession(_currentProvider, candidates) {
@@ -1922,11 +2235,23 @@ async function closeAgent(config, role) {
1922
2235
  console.log(`[${role}] Marking agent as offline...`);
1923
2236
  await fetch(`${config.apiUrl}/close`, {
1924
2237
  method: "POST",
1925
- headers
2238
+ headers: { ...headers, "Content-Type": "application/json" },
2239
+ body: JSON.stringify({ runtimeShutdown: true })
1926
2240
  });
1927
2241
  console.log(`[${role}] Agent marked as offline`);
1928
2242
  } catch {}
1929
2243
  }
2244
+ function resolveExitAfterTasks(env = process.env) {
2245
+ const raw = env.SWARM_EXIT_AFTER_TASKS?.trim();
2246
+ const value = Number(raw);
2247
+ return raw && Number.isInteger(value) && value > 0 ? value : null;
2248
+ }
2249
+ async function exitAfterTasksIfFinished(state, limit, shutdown) {
2250
+ if (limit === null || state.tasksProcessed < limit || state.activeTasks.size > 0)
2251
+ return false;
2252
+ await shutdown("SWARM_EXIT_AFTER_TASKS");
2253
+ return true;
2254
+ }
1930
2255
  var deploymentMemoryRaters = process.env.MEMORY_RATERS;
1931
2256
  var rowOverriddenBootEnv = new Map;
1932
2257
  async function fetchResolvedEnv(apiUrl, apiKey, agentId, baseEnv = process.env, taskModel, sessionContext) {
@@ -1942,6 +2267,8 @@ async function fetchResolvedEnv(apiUrl, apiKey, agentId, baseEnv = process.env,
1942
2267
  const headers = { "X-Agent-ID": agentId };
1943
2268
  if (apiKey)
1944
2269
  headers.Authorization = `Bearer ${apiKey}`;
2270
+ if (sessionContext?.taskId)
2271
+ headers["X-Source-Task-Id"] = sessionContext.taskId;
1945
2272
  const url = `${apiUrl}/api/config/resolved?agentId=${encodeURIComponent(agentId)}&includeSecrets=true${repoId ? `&repoId=${encodeURIComponent(repoId)}` : ""}`;
1946
2273
  const response = await fetch(url, { headers });
1947
2274
  if (!response.ok) {
@@ -1954,8 +2281,13 @@ async function fetchResolvedEnv(apiUrl, apiKey, agentId, baseEnv = process.env,
1954
2281
  env[key] = bootValue;
1955
2282
  }
1956
2283
  if (data.configs?.length) {
2284
+ const skippedSecretKeys = [];
1957
2285
  scriptsOnlyConfigValue = data.configs.find((config) => config.key === "SCRIPTS_ONLY_MCP")?.value;
1958
2286
  for (const config of data.configs) {
2287
+ if (config.isSecret && config.value === MASKED_SECRET_VALUE) {
2288
+ skippedSecretKeys.push(config.key);
2289
+ continue;
2290
+ }
1959
2291
  if (isCodexOAuthConfigKey(config.key))
1960
2292
  continue;
1961
2293
  if (config.isSecret)
@@ -1966,6 +2298,9 @@ async function fetchResolvedEnv(apiUrl, apiKey, agentId, baseEnv = process.env,
1966
2298
  }
1967
2299
  env[config.key] = config.value;
1968
2300
  }
2301
+ if (skippedSecretKeys.length) {
2302
+ console.warn(scrubSecrets(`[env-reload] Skipped masked secrets: ${skippedSecretKeys.join(", ")}`));
2303
+ }
1969
2304
  console.log(`[env-reload] Loaded ${data.configs.length} config entries from API`);
1970
2305
  }
1971
2306
  const codexPoolSlots = countCodexOAuthPoolSlots(data.configs ?? []);
@@ -2407,7 +2742,37 @@ function jsonOutputCandidates(text) {
2407
2742
  }
2408
2743
  return [...new Set(candidates)];
2409
2744
  }
2410
- async function ensureTaskFinished(config, role, taskId, exitCode, failureReason, providerOutput, provider, failureDiagnostics) {
2745
+ function finalUpdateDeadline() {
2746
+ const maxSec = Number(process.env.RUNNER_FINISH_RETRY_MAX_SEC);
2747
+ return Date.now() + (Number.isFinite(maxSec) && maxSec > 0 ? maxSec : 300) * 1000;
2748
+ }
2749
+ async function postWithRetry(url, headers, body, deadline, label) {
2750
+ let delay = 1000;
2751
+ for (;; ) {
2752
+ try {
2753
+ const response = await fetch(url, {
2754
+ method: "POST",
2755
+ headers,
2756
+ body: JSON.stringify(body),
2757
+ signal: AbortSignal.timeout(Math.max(1, Math.min(30000, deadline - Date.now())))
2758
+ });
2759
+ if (response.status < 500)
2760
+ return response;
2761
+ throw new Error(`HTTP ${response.status}: ${scrubSecrets(await response.text()).slice(0, 300)}`);
2762
+ } catch (error) {
2763
+ const remaining = deadline - Date.now();
2764
+ if (remaining <= 0)
2765
+ throw error;
2766
+ const wait = Math.min(delay, remaining);
2767
+ console.warn(scrubSecrets(`[runner] ${label} failed. Retry in ${wait}ms: ${error}`));
2768
+ await Bun.sleep(wait);
2769
+ if (Date.now() >= deadline)
2770
+ throw error;
2771
+ delay = Math.min(30000, delay * 2);
2772
+ }
2773
+ }
2774
+ }
2775
+ async function ensureTaskFinished(config, role, taskId, exitCode, failureReason, providerOutput, provider, failureDiagnostics, finalProgress, retryDeadline) {
2411
2776
  const headers = {
2412
2777
  "X-Agent-ID": config.agentId,
2413
2778
  "Content-Type": "application/json"
@@ -2463,11 +2828,15 @@ ${failureDiagnostics}`;
2463
2828
  applyFallback(fallback);
2464
2829
  }
2465
2830
  try {
2466
- const response = await fetch(`${config.apiUrl}/api/tasks/${taskId}/finish`, {
2467
- method: "POST",
2468
- headers,
2469
- body: JSON.stringify(body)
2470
- });
2831
+ const deadline = retryDeadline ?? finalUpdateDeadline();
2832
+ if (finalProgress) {
2833
+ const progress = await postWithRetry(`${config.apiUrl}/api/tasks/${taskId}/progress`, headers, { progress: scrubSecrets(finalProgress) }, deadline, "Final task progress POST");
2834
+ if (progress.status === 401) {
2835
+ console.warn(`[${role}] Final task update rejected: HTTP 401`);
2836
+ return;
2837
+ }
2838
+ }
2839
+ const response = await postWithRetry(`${config.apiUrl}/api/tasks/${taskId}/finish`, headers, body, deadline, "Final task finish POST");
2471
2840
  if (response.ok) {
2472
2841
  const result = await response.json();
2473
2842
  if (result.alreadyFinished) {
@@ -2475,17 +2844,19 @@ ${failureDiagnostics}`;
2475
2844
  } else {
2476
2845
  console.log(`[${role}] Runner marked task ${taskId.slice(0, 8)} as ${status} (exit code: ${exitCode})`);
2477
2846
  }
2847
+ } else if (response.status === 401) {
2848
+ console.warn(scrubSecrets(`[${role}] Task ${taskId.slice(0, 8)}: runtime token revoked or expired; giving up on finish`));
2478
2849
  } else if (response.status === 409) {
2479
2850
  const error = await response.text();
2480
- console.warn(`[${role}] Runner result for task ${taskId.slice(0, 8)} was discarded because the task is already terminal; continuing: ${error}`);
2851
+ console.warn(scrubSecrets(`[${role}] Runner result for task ${taskId.slice(0, 8)} was discarded because the task is already terminal; continuing: ${error}`));
2481
2852
  } else if (response.status === 404) {
2482
2853
  console.log(`[${role}] Task ${taskId.slice(0, 8)} already finalized (not found), skipping`);
2483
2854
  } else {
2484
2855
  const error = await response.text();
2485
- console.warn(`[${role}] Failed to finish task ${taskId.slice(0, 8)}: ${response.status} ${error}`);
2856
+ console.warn(scrubSecrets(`[${role}] Failed to finish task ${taskId.slice(0, 8)}: ${response.status} ${error}`));
2486
2857
  }
2487
2858
  } catch (err) {
2488
- console.warn(`[${role}] Error finishing task ${taskId.slice(0, 8)}: ${err}`);
2859
+ console.warn(scrubSecrets(`[${role}] Error finishing task ${taskId.slice(0, 8)}: ${err}`));
2489
2860
  }
2490
2861
  }
2491
2862
  async function reportKeyUsage(apiUrl, apiKey, keyType, selection, taskId) {
@@ -2750,12 +3121,13 @@ async function buildResumePrompt(task, fmt = (cmd) => `/${cmd}`, options) {
2750
3121
  });
2751
3122
  return result.text;
2752
3123
  }
2753
- function setupShutdownHandlers(role, apiConfig, getRunnerState) {
3124
+ function setupShutdownHandlers(role, apiConfig, getRunnerState, onShutdown) {
2754
3125
  let shutdownInProgress = false;
2755
3126
  const shutdown = async (signal, exitCode = 0) => {
2756
3127
  if (shutdownInProgress)
2757
3128
  return;
2758
3129
  shutdownInProgress = true;
3130
+ onShutdown?.();
2759
3131
  console.log(`
2760
3132
  [${role}] Received ${signal}, shutting down...`);
2761
3133
  const state = getRunnerState?.();
@@ -2804,15 +3176,13 @@ function setupShutdownHandlers(role, apiConfig, getRunnerState) {
2804
3176
  await savePm2State(role);
2805
3177
  process.exit(exitCode);
2806
3178
  };
2807
- const beginShutdown = (signal, exitCode = 0) => {
2808
- shutdown(signal, exitCode).catch(async (error) => {
2809
- console.error(`[${role}] Shutdown failed after ${signal}:`, error);
2810
- await terminateRegisteredProcessGroups();
2811
- process.exit(1);
2812
- });
2813
- };
2814
- process.on("SIGINT", () => beginShutdown("SIGINT"));
2815
- process.on("SIGTERM", () => beginShutdown("SIGTERM"));
3179
+ const beginShutdown = (signal, exitCode = 0) => shutdown(signal, exitCode).catch(async (error) => {
3180
+ console.error(`[${role}] Shutdown failed after ${signal}:`, error);
3181
+ await terminateRegisteredProcessGroups();
3182
+ process.exit(1);
3183
+ });
3184
+ process.on("SIGINT", () => void beginShutdown("SIGINT"));
3185
+ process.on("SIGTERM", () => void beginShutdown("SIGTERM"));
2816
3186
  let fatalShutdownInProgress = false;
2817
3187
  const fatalShutdown = (kind, error) => {
2818
3188
  if (fatalShutdownInProgress)
@@ -2827,6 +3197,7 @@ function setupShutdownHandlers(role, apiConfig, getRunnerState) {
2827
3197
  process.on("unhandledRejection", (reason) => {
2828
3198
  fatalShutdown("Unhandled rejection", reason);
2829
3199
  });
3200
+ return beginShutdown;
2830
3201
  }
2831
3202
  var LOG_BUFFER_SIZE = 50;
2832
3203
  var LOG_FLUSH_INTERVAL_MS = 5000;
@@ -2862,7 +3233,7 @@ async function flushLogBuffer(buffer, opts) {
2862
3233
  console.warn(`[runner] Error pushing logs: ${error}`);
2863
3234
  }
2864
3235
  }
2865
- async function saveCostData(cost, apiUrl, apiKey) {
3236
+ async function saveCostData(cost, apiUrl, apiKey, deadline) {
2866
3237
  const headers = {
2867
3238
  "Content-Type": "application/json",
2868
3239
  "X-Agent-ID": cost.agentId
@@ -2871,16 +3242,15 @@ async function saveCostData(cost, apiUrl, apiKey) {
2871
3242
  headers.Authorization = `Bearer ${apiKey}`;
2872
3243
  }
2873
3244
  try {
2874
- const response = await fetch(`${apiUrl}/api/session-costs`, {
2875
- method: "POST",
2876
- headers,
2877
- body: JSON.stringify(cost)
2878
- });
2879
- if (!response.ok) {
3245
+ const body = { ...cost, id: crypto.randomUUID() };
3246
+ const response = await postWithRetry(`${apiUrl}/api/session-costs`, headers, body, deadline, "Session cost POST");
3247
+ if (response.status === 401) {
3248
+ console.warn("[runner] Cost data: runtime token revoked or expired. Cost saving stopped.");
3249
+ } else if (!response.ok) {
2880
3250
  console.warn(`[runner] Failed to save cost data: ${response.status}`);
2881
3251
  }
2882
3252
  } catch (error) {
2883
- console.warn(`[runner] Error saving cost data: ${error}`);
3253
+ console.warn(scrubSecrets(`[runner] Error saving cost data: ${error}`));
2884
3254
  }
2885
3255
  }
2886
3256
  async function saveProviderSessionId(apiUrl, apiKey, taskId, claudeSessionId, provider, providerMeta, model, harnessVariant, harnessVariantMeta) {
@@ -3069,12 +3439,24 @@ async function removeActiveSession(config, taskId) {
3069
3439
  const headers = { "X-Agent-ID": config.agentId };
3070
3440
  if (config.apiKey)
3071
3441
  headers.Authorization = `Bearer ${config.apiKey}`;
3072
- try {
3073
- await fetch(`${config.apiUrl}/api/active-sessions/by-task/${taskId}`, {
3074
- method: "DELETE",
3075
- headers
3076
- });
3077
- } catch {}
3442
+ for (let attempt = 1;attempt <= 3; attempt++) {
3443
+ try {
3444
+ const response = await fetch(`${config.apiUrl}/api/active-sessions/by-task/${taskId}`, {
3445
+ method: "DELETE",
3446
+ headers,
3447
+ signal: AbortSignal.timeout(1000)
3448
+ });
3449
+ if (!response.ok)
3450
+ throw new Error(`HTTP ${response.status}`);
3451
+ return;
3452
+ } catch (error) {
3453
+ if (attempt === 3) {
3454
+ console.warn(scrubSecrets(`[runner] Active-session removal failed for task ${taskId} after 3 attempts: ${error instanceof Error ? error.message : String(error)}`));
3455
+ } else {
3456
+ await Bun.sleep(attempt * 100);
3457
+ }
3458
+ }
3459
+ }
3078
3460
  }
3079
3461
  async function cleanupActiveSessions(config) {
3080
3462
  const headers = {
@@ -3239,10 +3621,16 @@ async function registerAgent(opts) {
3239
3621
  }
3240
3622
  try {
3241
3623
  const body = await response.json();
3624
+ const deliveryMode = body.deliveryMode === "push" || body.deliveryMode === "poll" ? body.deliveryMode : undefined;
3625
+ const mode = {
3626
+ ...deliveryMode ? { deliveryMode } : {},
3627
+ ...typeof body.pollRequired === "boolean" ? { pollRequired: body.pollRequired } : {}
3628
+ };
3242
3629
  const caps = body.enabledCapabilities;
3243
3630
  if (Array.isArray(caps) && caps.every((c2) => typeof c2 === "string")) {
3244
- return { serverCapabilities: caps };
3631
+ return { serverCapabilities: caps, ...mode };
3245
3632
  }
3633
+ return mode;
3246
3634
  } catch {}
3247
3635
  return {};
3248
3636
  }
@@ -3292,6 +3680,9 @@ async function pollForTriggerOnce(opts) {
3292
3680
  });
3293
3681
  if (!response.ok) {
3294
3682
  console.warn(`[runner] Poll request failed: ${response.status}`);
3683
+ opts.onPollFailure?.();
3684
+ if (opts.singlePoll)
3685
+ return null;
3295
3686
  await Bun.sleep(opts.pollInterval);
3296
3687
  continue;
3297
3688
  }
@@ -3304,8 +3695,11 @@ async function pollForTriggerOnce(opts) {
3304
3695
  if (draining)
3305
3696
  return null;
3306
3697
  } catch (error) {
3307
- console.warn(`[runner] Poll request error: ${error}`);
3698
+ console.warn(scrubSecrets(`[runner] Poll request error: ${error}`));
3699
+ opts.onPollFailure?.();
3308
3700
  }
3701
+ if (opts.singlePoll)
3702
+ return null;
3309
3703
  await Bun.sleep(opts.pollInterval);
3310
3704
  }
3311
3705
  return null;
@@ -3602,6 +3996,11 @@ function resolveSessionTelemetryEvent(result) {
3602
3996
  return "failure";
3603
3997
  return;
3604
3998
  }
3999
+ function withHarnessTelemetryEnv(env) {
4000
+ if (env.DESPLEGA_TELEMETRY_ENV?.trim())
4001
+ return { ...env };
4002
+ return { ...env, DESPLEGA_TELEMETRY_ENV: "test" };
4003
+ }
3605
4004
  function implicitCloseActiveToolSpans(activeToolSpans, now = Date.now()) {
3606
4005
  let closed = 0;
3607
4006
  for (const [toolCallId, active] of activeToolSpans) {
@@ -3626,6 +4025,7 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
3626
4025
  try {
3627
4026
  ({ env: freshEnv, credentialSelections } = await fetchResolvedEnv(opts.apiUrl, opts.apiKey, opts.agentId, process.env, opts.resolvedModel || opts.model, {
3628
4027
  repoId: sessionRepo?.id,
4028
+ taskId: realTaskId,
3629
4029
  provider: adapter.name,
3630
4030
  modelTier: opts.modelTier,
3631
4031
  localBlocks: opts.localBlocks,
@@ -3703,7 +4103,7 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
3703
4103
  additionalArgs: opts.additionalArgs,
3704
4104
  resumeSessionId: opts.resumeSessionId,
3705
4105
  iteration: opts.iteration,
3706
- env: freshEnv,
4106
+ env: withHarnessTelemetryEnv(freshEnv),
3707
4107
  codexSlot: oauthIsPoolBacked ? oauthSelection?.index : undefined,
3708
4108
  contextKey: opts.contextKey,
3709
4109
  reasoningEffort: reasoningEffortOverride,
@@ -3803,6 +4203,7 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
3803
4203
  }
3804
4204
  let lastProgressTime = 0;
3805
4205
  const assistantTextHolder = {};
4206
+ const finalProgressHolder = {};
3806
4207
  let lastContextPostTime = 0;
3807
4208
  const CONTEXT_THROTTLE_MS = 30000;
3808
4209
  let cumulativeProgressOutputTokens = 0;
@@ -3894,8 +4295,10 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
3894
4295
  startedAt: Date.now()
3895
4296
  });
3896
4297
  const now = Date.now();
4298
+ const progress = toolCallToProgress(event.toolName, event.args);
4299
+ if (progress)
4300
+ finalProgressHolder.value = scrubSecrets(progress);
3897
4301
  if (effectiveTaskId && opts.apiUrl && now - lastProgressTime >= PROGRESS_THROTTLE_MS) {
3898
- const progress = toolCallToProgress(event.toolName, event.args);
3899
4302
  if (progress) {
3900
4303
  lastProgressTime = now;
3901
4304
  updateProgressViaAPI(opts.apiUrl, opts.apiKey, effectiveTaskId, progress).catch(() => {});
@@ -4051,6 +4454,7 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
4051
4454
  }));
4052
4455
  break;
4053
4456
  case "progress": {
4457
+ finalProgressHolder.value = scrubSecrets(event.message);
4054
4458
  if (effectiveTaskId && opts.apiUrl) {
4055
4459
  const now = Date.now();
4056
4460
  if (now - lastProgressTime >= PROGRESS_THROTTLE_MS) {
@@ -4111,6 +4515,7 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
4111
4515
  }
4112
4516
  }
4113
4517
  if (result.cost) {
4518
+ result.cost = { ...result.cost, taskId: realTaskId, sessionId: opts.runnerSessionId };
4114
4519
  sessionSpan.setAttributes({
4115
4520
  "gen_ai.response.model": result.cost.model,
4116
4521
  "gen_ai.usage.input_tokens": result.cost.inputTokens ?? 0,
@@ -4134,11 +4539,6 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
4134
4539
  numTurns: result.cost.numTurns,
4135
4540
  isError: result.cost.isError
4136
4541
  }, { userId: opts.requestedByUserId ?? null });
4137
- try {
4138
- await saveCostData({ ...result.cost, taskId: realTaskId, sessionId: opts.runnerSessionId }, opts.apiUrl, opts.apiKey);
4139
- } catch (err) {
4140
- console.warn(`[runner] Failed to save cost: ${err}`);
4141
- }
4142
4542
  }
4143
4543
  if (result.cost && realTaskId) {
4144
4544
  fetch(`${opts.apiUrl}/api/tasks/${realTaskId}/context`, {
@@ -4230,7 +4630,8 @@ async function spawnProviderProcess(adapter, opts, logDir, isYolo) {
4230
4630
  harnessProvider: opts.harnessProvider,
4231
4631
  hasLocalEnvironment: adapter.traits.hasLocalEnvironment,
4232
4632
  model: model || undefined,
4233
- assistantText: assistantTextHolder
4633
+ assistantText: assistantTextHolder,
4634
+ finalProgress: finalProgressHolder
4234
4635
  };
4235
4636
  runningTaskForSessionInit = runningTask;
4236
4637
  if (pendingHarnessVariant !== undefined) {
@@ -4294,7 +4695,7 @@ async function reconcileActiveTasks(state, role, opts) {
4294
4695
  for (const [taskId, task] of state.activeTasks) {
4295
4696
  if (task.result !== null)
4296
4697
  continue;
4297
- if (!opts.cancelledSignaled.has(taskId)) {
4698
+ if (!opts.cancelledSignaled.has(taskId) && (!opts.cancelTaskIds || opts.cancelTaskIds.has(taskId))) {
4298
4699
  try {
4299
4700
  if (await opts.isCancelled(taskId)) {
4300
4701
  console.log(`[${role}] Task ${taskId.slice(0, 8)} was cancelled — sending SIGTERM to subprocess`);
@@ -4303,7 +4704,7 @@ async function reconcileActiveTasks(state, role, opts) {
4303
4704
  }
4304
4705
  } catch {}
4305
4706
  }
4306
- if (task.abortRequestedAt === undefined) {
4707
+ if (task.abortRequestedAt === undefined && opts.checkStatus !== false) {
4307
4708
  const lastCheck = task.lastReconcileAt ?? task.startTime.getTime();
4308
4709
  if (now() - lastCheck >= opts.intervalMs) {
4309
4710
  task.lastReconcileAt = now();
@@ -4359,13 +4760,16 @@ async function checkCompletedProcesses(state, role, apiConfig, cancelledSignaled
4359
4760
  model: task.model,
4360
4761
  durationMs: Date.now() - task.startTime.getTime(),
4361
4762
  assistantText: task.assistantText,
4763
+ finalProgress: task.finalProgress,
4362
4764
  serverTerminalStatus: task.serverTerminalStatus
4363
4765
  });
4364
4766
  }
4365
4767
  }
4366
- for (const {
4768
+ let credentialReports = Promise.resolve();
4769
+ const completions = await Promise.allSettled(completedTasks.map(async ({
4367
4770
  taskId,
4368
4771
  result,
4772
+ triggerType,
4369
4773
  triggerSurface,
4370
4774
  requestedByUserId,
4371
4775
  cursorUpdates,
@@ -4377,131 +4781,149 @@ async function checkCompletedProcesses(state, role, apiConfig, cancelledSignaled
4377
4781
  model,
4378
4782
  durationMs,
4379
4783
  assistantText,
4784
+ finalProgress,
4380
4785
  serverTerminalStatus
4381
- } of completedTasks) {
4382
- state.activeTasks.delete(taskId);
4383
- vcsDetectedTasks.delete(taskId);
4384
- vcsCheckTimestamps.delete(taskId);
4385
- cancelledSignaled?.delete(taskId);
4386
- if (apiConfig) {
4387
- removeActiveSession(apiConfig, taskId).catch((err) => console.error("[runner] active-session removal failed:", scrubSecrets(err instanceof Error ? err.message : String(err))));
4388
- }
4389
- if (serverTerminalStatus) {
4390
- console.log(`[${role}] Task ${taskId.slice(0, 8)} was already ${serverTerminalStatus} server-side — slot released, server result kept`);
4391
- state.tasksProcessed += 1;
4392
- continue;
4393
- }
4394
- if (apiConfig && workingDir && !vcsDetectedTasks.has(taskId)) {
4395
- await detectVcsForTask(apiConfig.apiUrl, apiConfig.apiKey, taskId, workingDir);
4396
- }
4397
- if (apiConfig) {
4398
- let failureReason;
4399
- if (result.exitCode !== 0 && result.failureReason) {
4400
- failureReason = result.failureReason;
4401
- console.log(`[${role}] Detected error for task ${taskId.slice(0, 8)}: ${failureReason}`);
4402
- }
4403
- reportHarnessModelOutcome({
4404
- apiUrl: apiConfig.apiUrl,
4405
- apiKey: apiConfig.apiKey,
4406
- agentId: apiConfig.agentId,
4407
- harness: harnessProvider,
4408
- model,
4409
- exitCode: result.exitCode,
4410
- failureReason
4411
- });
4412
- if (credentialInfo) {
4786
+ }) => {
4787
+ const deadline = finalUpdateDeadline();
4788
+ const costSave = apiConfig && result.cost ? saveCostData(result.cost, apiConfig.apiUrl, apiConfig.apiKey, deadline) : Promise.resolve();
4789
+ const credentialReport = credentialReports.then(async () => {
4790
+ if (apiConfig && credentialInfo && !serverTerminalStatus) {
4413
4791
  await reportKeyCompletionOutcome({
4414
4792
  apiUrl: apiConfig.apiUrl,
4415
4793
  apiKey: apiConfig.apiKey,
4416
4794
  credential: credentialInfo,
4417
4795
  taskId,
4418
4796
  exitCode: result.exitCode,
4419
- failureReason
4797
+ failureReason: result.exitCode !== 0 ? result.failureReason : undefined
4420
4798
  });
4421
4799
  }
4422
- let bridgeDiagnostics;
4423
- if (result.exitCode !== 0 && harnessProvider === "claude" && workingDir) {
4424
- bridgeDiagnostics = await getBridgeFailureDiagnostics(workingDir);
4425
- if (bridgeDiagnostics?.artifactPath && result.sessionId) {
4426
- console.log(`[${role}] Bridge failure artifact found: ${bridgeDiagnostics.artifactPath}`);
4427
- updateHarnessVariantMeta(apiConfig.apiUrl, apiConfig.apiKey, taskId, result.sessionId, {
4428
- failureArtifact: bridgeDiagnostics.artifactPath
4429
- }).catch((err) => console.warn(`[runner] Failed to update harness variant meta: ${err}`));
4430
- }
4800
+ });
4801
+ credentialReports = credentialReport.catch(() => {});
4802
+ try {
4803
+ vcsDetectedTasks.delete(taskId);
4804
+ vcsCheckTimestamps.delete(taskId);
4805
+ cancelledSignaled?.delete(taskId);
4806
+ if (serverTerminalStatus) {
4807
+ console.log(`[${role}] Task ${taskId.slice(0, 8)} was already ${serverTerminalStatus} server-side — slot released, server result kept`);
4808
+ return;
4431
4809
  }
4432
- const bridgeFailureDiagnostics = bridgeDiagnostics?.paneTail != null ? `Claude bridge final tmux pane tail (${bridgeDiagnostics.artifactPath}):
4433
- ${bridgeDiagnostics.paneTail}` : undefined;
4434
- await reportCredentialOutcomeThenFinish({
4435
- apiUrl: apiConfig.apiUrl,
4436
- apiKey: apiConfig.apiKey,
4437
- credentialInfo,
4438
- result,
4439
- failureReason,
4440
- model,
4441
- codexCreditsExhaustedCooldownMs: state.codexCreditsExhaustedCooldownMs,
4442
- modelWindowBlocks: state.modelWindowBlocks
4443
- }, () => ensureTaskFinished(apiConfig, role, taskId, result.exitCode, failureReason, resolveProviderOutput(result, assistantText), harnessProvider, bridgeFailureDiagnostics));
4444
- const sessionTriggerSurface = workerTriggerSurface(triggerSurface);
4445
- const sessionActor = { userId: requestedByUserId ?? null };
4446
- telemetry.taskEvent("session_completed", {
4447
- taskId,
4448
- trigger_surface: sessionTriggerSurface,
4449
- agentId: apiConfig.agentId,
4450
- provider: result.cost?.provider ?? harnessProvider,
4451
- model: result.cost?.model ?? model ?? "unknown",
4452
- harnessVariant,
4453
- harnessVersion: typeof harnessVariantMeta?.version === "string" || typeof harnessVariantMeta?.version === "number" ? String(harnessVariantMeta.version) : undefined,
4454
- exitCode: result.exitCode,
4455
- isError: result.exitCode !== 0,
4456
- durationMs
4457
- }, sessionActor);
4458
- const sessionTelemetryEvent = resolveSessionTelemetryEvent(result);
4459
- if (sessionTelemetryEvent) {
4460
- telemetry.session(sessionTelemetryEvent, {
4810
+ if (apiConfig && workingDir && !vcsDetectedTasks.has(taskId)) {
4811
+ await detectVcsForTask(apiConfig.apiUrl, apiConfig.apiKey, taskId, workingDir);
4812
+ }
4813
+ if (apiConfig) {
4814
+ let failureReason;
4815
+ if (result.exitCode !== 0 && result.failureReason) {
4816
+ failureReason = result.failureReason;
4817
+ console.log(`[${role}] Detected error for task ${taskId.slice(0, 8)}: ${failureReason}`);
4818
+ }
4819
+ reportHarnessModelOutcome({
4820
+ apiUrl: apiConfig.apiUrl,
4821
+ apiKey: apiConfig.apiKey,
4461
4822
  agentId: apiConfig.agentId,
4823
+ harness: harnessProvider,
4824
+ model,
4825
+ exitCode: result.exitCode,
4826
+ failureReason
4827
+ });
4828
+ await credentialReport;
4829
+ let bridgeDiagnostics;
4830
+ if (result.exitCode !== 0 && harnessProvider === "claude" && workingDir) {
4831
+ bridgeDiagnostics = await getBridgeFailureDiagnostics(workingDir);
4832
+ if (bridgeDiagnostics?.artifactPath && result.sessionId) {
4833
+ console.log(`[${role}] Bridge failure artifact found: ${bridgeDiagnostics.artifactPath}`);
4834
+ updateHarnessVariantMeta(apiConfig.apiUrl, apiConfig.apiKey, taskId, result.sessionId, {
4835
+ failureArtifact: bridgeDiagnostics.artifactPath
4836
+ }).catch((err) => console.warn(`[runner] Failed to update harness variant meta: ${err}`));
4837
+ }
4838
+ }
4839
+ const bridgeFailureDiagnostics = bridgeDiagnostics?.paneTail != null ? `Claude bridge final tmux pane tail (${bridgeDiagnostics.artifactPath}):
4840
+ ${bridgeDiagnostics.paneTail}` : undefined;
4841
+ await reportCredentialOutcomeThenFinish({
4842
+ apiUrl: apiConfig.apiUrl,
4843
+ apiKey: apiConfig.apiKey,
4844
+ credentialInfo,
4845
+ result,
4846
+ failureReason,
4847
+ model,
4848
+ codexCreditsExhaustedCooldownMs: state.codexCreditsExhaustedCooldownMs,
4849
+ modelWindowBlocks: state.modelWindowBlocks
4850
+ }, () => ensureTaskFinished(apiConfig, role, taskId, result.exitCode, failureReason, resolveProviderOutput(result, assistantText), harnessProvider, bridgeFailureDiagnostics, finalProgress?.value, deadline));
4851
+ const sessionTriggerSurface = workerTriggerSurface(triggerSurface);
4852
+ const sessionActor = { userId: requestedByUserId ?? null };
4853
+ telemetry.taskEvent("session_completed", {
4462
4854
  taskId,
4463
4855
  trigger_surface: sessionTriggerSurface,
4464
- errorCategory: normalizeSessionErrorCategory(result.errorCategory),
4856
+ agentId: apiConfig.agentId,
4465
4857
  provider: result.cost?.provider ?? harnessProvider,
4466
4858
  model: result.cost?.model ?? model ?? "unknown",
4467
- durationMs: result.cost?.durationMs ?? durationMs,
4468
- wasRateLimited: result.rateLimitResetAt != null
4859
+ harnessVariant,
4860
+ harnessVersion: typeof harnessVariantMeta?.version === "string" || typeof harnessVariantMeta?.version === "number" ? String(harnessVariantMeta.version) : undefined,
4861
+ exitCode: result.exitCode,
4862
+ isError: result.exitCode !== 0,
4863
+ durationMs
4469
4864
  }, sessionActor);
4865
+ const sessionTelemetryEvent = resolveSessionTelemetryEvent(result);
4866
+ if (sessionTelemetryEvent) {
4867
+ telemetry.session(sessionTelemetryEvent, {
4868
+ agentId: apiConfig.agentId,
4869
+ taskId,
4870
+ trigger_surface: sessionTriggerSurface,
4871
+ errorCategory: normalizeSessionErrorCategory(result.errorCategory),
4872
+ provider: result.cost?.provider ?? harnessProvider,
4873
+ model: result.cost?.model ?? model ?? "unknown",
4874
+ durationMs: result.cost?.durationMs ?? durationMs,
4875
+ wasRateLimited: result.rateLimitResetAt != null
4876
+ }, sessionActor);
4877
+ }
4878
+ ensure({
4879
+ id: "worker_process_finished",
4880
+ flow: "task",
4881
+ runId: taskId,
4882
+ depIds: ["worker_process_spawned"],
4883
+ data: {
4884
+ taskId,
4885
+ agentId: apiConfig.agentId,
4886
+ role,
4887
+ exitCode: result.exitCode,
4888
+ success: result.exitCode === 0,
4889
+ failureReason
4890
+ },
4891
+ validator: (data) => data.exitCode === 0,
4892
+ filter: ({}, ctx) => ctx.deps.length > 0,
4893
+ conditions: [{ timeout_ms: 3600000 }]
4894
+ });
4895
+ if (cursorUpdates && cursorUpdates.length > 0 && result.exitCode === 0) {
4896
+ try {
4897
+ const headers = { "Content-Type": "application/json" };
4898
+ if (apiConfig.apiKey)
4899
+ headers.Authorization = `Bearer ${apiConfig.apiKey}`;
4900
+ await fetch(`${apiConfig.apiUrl}/api/channel-activity/commit-cursors`, {
4901
+ method: "POST",
4902
+ headers,
4903
+ body: JSON.stringify({ cursorUpdates })
4904
+ });
4905
+ console.log(`[${role}] Committed ${cursorUpdates.length} channel activity cursor(s) for task ${taskId.slice(0, 8)}`);
4906
+ } catch (err) {
4907
+ console.warn(`[${role}] Failed to commit channel activity cursors: ${err}`);
4908
+ }
4909
+ }
4470
4910
  }
4471
- state.tasksProcessed += 1;
4472
- ensure({
4473
- id: "worker_process_finished",
4474
- flow: "task",
4475
- runId: taskId,
4476
- depIds: ["worker_process_spawned"],
4477
- data: {
4478
- taskId,
4479
- agentId: apiConfig.agentId,
4480
- role,
4481
- exitCode: result.exitCode,
4482
- success: result.exitCode === 0,
4483
- failureReason
4484
- },
4485
- validator: (data) => data.exitCode === 0,
4486
- filter: ({}, ctx) => ctx.deps.length > 0,
4487
- conditions: [{ timeout_ms: 3600000 }]
4488
- });
4489
- if (cursorUpdates && cursorUpdates.length > 0 && result.exitCode === 0) {
4490
- try {
4491
- const headers = { "Content-Type": "application/json" };
4492
- if (apiConfig.apiKey)
4493
- headers.Authorization = `Bearer ${apiConfig.apiKey}`;
4494
- await fetch(`${apiConfig.apiUrl}/api/channel-activity/commit-cursors`, {
4495
- method: "POST",
4496
- headers,
4497
- body: JSON.stringify({ cursorUpdates })
4498
- });
4499
- console.log(`[${role}] Committed ${cursorUpdates.length} channel activity cursor(s) for task ${taskId.slice(0, 8)}`);
4500
- } catch (err) {
4501
- console.warn(`[${role}] Failed to commit channel activity cursors: ${err}`);
4911
+ } finally {
4912
+ try {
4913
+ await costSave;
4914
+ if (apiConfig)
4915
+ await removeActiveSession(apiConfig, taskId);
4916
+ } finally {
4917
+ state.activeTasks.delete(taskId);
4918
+ if (triggerType === "task_assigned" || triggerType === "task_resumed") {
4919
+ state.tasksProcessed += 1;
4502
4920
  }
4503
4921
  }
4504
4922
  }
4923
+ }));
4924
+ for (const completion of completions) {
4925
+ if (completion.status === "rejected")
4926
+ console.warn(scrubSecrets(`[${role}] Task completion failed: ${completion.reason}`));
4505
4927
  }
4506
4928
  const localCompleted = completedTasks.filter((t) => t.hasLocalEnvironment);
4507
4929
  if (apiConfig && localCompleted.length > 0) {
@@ -4744,10 +5166,12 @@ ${additionalSystemPrompt}` : basePrompt;
4744
5166
  const defaultMaxTasks = isLead ? 2 : 1;
4745
5167
  const templateMaxTasks = cachedTemplate?.config.agentDefaults?.maxTasks;
4746
5168
  const maxConcurrent = resolveMaxConcurrent(process.env, templateMaxTasks, defaultMaxTasks);
4747
- console.log(`[${role}] Mode: runner-level polling`);
5169
+ const exitAfterTasks = resolveExitAfterTasks();
5170
+ console.log(`[${role}] Mode: runner-level delivery`);
4748
5171
  console.log(`[${role}] Max concurrent tasks: ${maxConcurrent}`);
4749
5172
  const state = {
4750
5173
  activeTasks: new Map,
5174
+ deliveryMode: "poll",
4751
5175
  maxConcurrent,
4752
5176
  startedAt: Date.now(),
4753
5177
  tasksProcessed: 0,
@@ -4767,10 +5191,38 @@ ${additionalSystemPrompt}` : basePrompt;
4767
5191
  const HARNESS_RECONCILE_INTERVAL_MS = 1e4;
4768
5192
  let lastServerCapsRefreshAt = 0;
4769
5193
  const SERVER_CAPS_REFRESH_INTERVAL_MS = 300000;
4770
- const runtimeInstanceId = crypto.randomUUID();
5194
+ const runtimeInstanceId = process.env.SWARM_RUNTIME_INSTANCE_ID || crypto.randomUUID();
4771
5195
  process.env.SWARM_RUNTIME_INSTANCE_ID = runtimeInstanceId;
4772
5196
  const apiConfig = { apiUrl, apiKey, agentId, runtimeInstanceId };
4773
- setupShutdownHandlers(role, apiConfig, () => state);
5197
+ const pushDelivery = new RunnerPushDelivery(new WorkerRealtimeClient({ apiUrl, apiKey, agentId, runtimeInstanceId }));
5198
+ let shuttingDown = false;
5199
+ let lastPushPingAt = 0;
5200
+ let pushPingTimer;
5201
+ let lastLoggedDeliveryMode;
5202
+ const applyDeliveryMode = (registration) => {
5203
+ const mode = updateWorkerDeliveryMode(lastLoggedDeliveryMode, registration, isLead, process.env);
5204
+ lastLoggedDeliveryMode = mode;
5205
+ if (mode !== state.deliveryMode) {
5206
+ state.deliveryMode = mode;
5207
+ pushDelivery.wakeWork();
5208
+ clearInterval(pushPingTimer);
5209
+ if (mode === "push") {
5210
+ pushDelivery.client.start();
5211
+ lastPushPingAt = Date.now();
5212
+ pushPingTimer = setInterval(() => {
5213
+ lastPushPingAt = Date.now();
5214
+ pingServer(apiConfig, role).then((draining) => applyApiDrainSignal(state, role, draining));
5215
+ }, 30000);
5216
+ pushPingTimer.unref();
5217
+ } else
5218
+ pushDelivery.client.stop();
5219
+ }
5220
+ };
5221
+ const shutdown = setupShutdownHandlers(role, apiConfig, () => state, () => {
5222
+ shuttingDown = true;
5223
+ clearInterval(pushPingTimer);
5224
+ pushDelivery.client.stop();
5225
+ });
4774
5226
  const agentName = process.env.AGENT_NAME || cachedTemplate?.config.displayName || `${role}-${agentId.slice(0, 8)}`;
4775
5227
  const applySwarmConfigDrift = async (freshEnv, resolvedProvider, nextScriptsOnly) => {
4776
5228
  let agentVisibleChanged = false;
@@ -4847,6 +5299,7 @@ ${additionalSystemPrompt}` : basePrompt;
4847
5299
  });
4848
5300
  lastServerCapsRefreshAt = Date.now();
4849
5301
  await applyServerCapabilities(reg.serverCapabilities);
5302
+ applyDeliveryMode(reg);
4850
5303
  };
4851
5304
  const reregisterAgent = async () => {
4852
5305
  try {
@@ -4870,6 +5323,7 @@ ${additionalSystemPrompt}` : basePrompt;
4870
5323
  }), { label: role });
4871
5324
  lastServerCapsRefreshAt = Date.now();
4872
5325
  await applyServerCapabilities(reg.serverCapabilities);
5326
+ applyDeliveryMode(reg);
4873
5327
  console.log(`[${role}] Registered as "${agentName}" (ID: ${agentId})`);
4874
5328
  } catch (error) {
4875
5329
  console.error(`[${role}] Failed to register: ${error}`);
@@ -5336,13 +5790,21 @@ ${additionalSystemPrompt}` : basePrompt;
5336
5790
  let lastOrphanRecoveryAt = 0;
5337
5791
  const ORPHAN_RECOVERY_INTERVAL_MS = 60000;
5338
5792
  const taskReconcileIntervalMs = resolveTaskReconcileIntervalMs();
5339
- while (true) {
5340
- applyApiDrainSignal(state, role, await pingServer(apiConfig, role));
5793
+ while (shouldRunDeliveryLoop(state.deliveryMode, shuttingDown)) {
5794
+ const deliveryChecks = pushDelivery.checks(state.deliveryMode ?? "poll");
5795
+ if (state.deliveryMode !== "push") {
5796
+ applyApiDrainSignal(state, role, await pingServer(apiConfig, role));
5797
+ }
5341
5798
  if (state.apiDraining) {
5342
5799
  await handOffTasksForApiDrain(state, role, apiConfig);
5343
5800
  }
5801
+ const activeBeforeCompletion = state.activeTasks.size;
5344
5802
  await checkCompletedProcesses(state, role, apiConfig, cancelledSignaled);
5345
- if (Date.now() - lastHarnessReconcileAt > HARNESS_RECONCILE_INTERVAL_MS) {
5803
+ if (state.activeTasks.size < activeBeforeCompletion)
5804
+ pushDelivery.wakeWork();
5805
+ if (await exitAfterTasksIfFinished(state, exitAfterTasks, shutdown))
5806
+ return;
5807
+ if (deliveryChecks.config && (deliveryChecks.push || Date.now() - lastHarnessReconcileAt > HARNESS_RECONCILE_INTERVAL_MS)) {
5346
5808
  lastHarnessReconcileAt = Date.now();
5347
5809
  try {
5348
5810
  const {
@@ -5380,20 +5842,33 @@ ${additionalSystemPrompt}` : basePrompt;
5380
5842
  cancelledSignaled,
5381
5843
  isCancelled: (taskId) => fetchTaskCancelled(apiUrl, apiKey, agentId, taskId),
5382
5844
  fetchStatus: (taskId) => fetchTaskStatus(apiUrl, apiKey, taskId),
5383
- intervalMs: taskReconcileIntervalMs
5845
+ intervalMs: taskReconcileIntervalMs,
5846
+ ...taskReconciliation(deliveryChecks)
5384
5847
  });
5385
5848
  }
5386
5849
  if (steeringDispatchState && state.activeTasks.size > 0) {
5387
5850
  const dispatchState = steeringDispatchState;
5388
5851
  for (const [taskId, task] of state.activeTasks) {
5389
- scheduleSteeringDispatch(apiConfig, taskId, task.session, dispatchState, (error) => {
5852
+ pushDelivery.dispatchSteering(deliveryChecks, taskId, () => scheduleSteeringDispatch(apiConfig, taskId, task.session, dispatchState, (error) => {
5390
5853
  console.warn(`[${role}] Steering dispatch failed for task ${taskId.slice(0, 8)} (non-fatal): ${scrubSecrets(error.message)}`);
5391
- });
5854
+ }));
5392
5855
  }
5393
5856
  }
5394
- if (state.apiDraining) {
5395
- await Bun.sleep(1000);
5396
- } else if (state.activeTasks.size < state.maxConcurrent) {
5857
+ const schedule = pushDelivery.schedule({
5858
+ mode: state.deliveryMode ?? "poll",
5859
+ push: deliveryChecks.push,
5860
+ activeCount: state.activeTasks.size,
5861
+ maxConcurrent: exitAfterTasks === null ? state.maxConcurrent : Math.min(state.maxConcurrent, Math.max(0, exitAfterTasks - state.tasksProcessed)),
5862
+ draining: state.apiDraining,
5863
+ lastPushPingAt,
5864
+ pollInterval: PollIntervalMs
5865
+ });
5866
+ if (!schedule.poll) {
5867
+ if (!state.apiDraining && state.activeTasks.size >= state.maxConcurrent) {
5868
+ console.log(`[${role}] At capacity (${state.activeTasks.size}/${state.maxConcurrent}), waiting for completion...`);
5869
+ }
5870
+ await pushDelivery.waitForSchedule(schedule);
5871
+ } else {
5397
5872
  if (Date.now() - lastOrphanRecoveryAt > ORPHAN_RECOVERY_INTERVAL_MS) {
5398
5873
  lastOrphanRecoveryAt = Date.now();
5399
5874
  const recoveredOrphans = await recoverOrphanedInProgressTasks(apiConfig);
@@ -5403,16 +5878,18 @@ ${additionalSystemPrompt}` : basePrompt;
5403
5878
  }
5404
5879
  console.log(`[${role}] Polling for triggers (${state.activeTasks.size}/${state.maxConcurrent} active)...`);
5405
5880
  const effectiveTimeout = state.activeTasks.size > 0 ? 5000 : PollTimeoutMs;
5406
- const trigger = await pollForTrigger({
5881
+ const { trigger, failed } = await pushDelivery.poll(schedule.singlePoll, (onPollFailure) => pollForTrigger({
5407
5882
  apiUrl,
5408
5883
  apiKey,
5409
5884
  agentId,
5410
5885
  pollInterval: PollIntervalMs,
5411
5886
  runtimeInstanceId,
5412
5887
  pollTimeout: effectiveTimeout,
5888
+ singlePoll: schedule.singlePoll,
5889
+ onPollFailure,
5413
5890
  harnessProvider: state.harnessProvider,
5414
5891
  onApiDrainSignal: (draining) => applyApiDrainSignal(state, role, draining)
5415
- });
5892
+ }));
5416
5893
  if (trigger) {
5417
5894
  if (trigger.type === "budget_refused") {
5418
5895
  consecutiveBudgetRefusals++;
@@ -5691,10 +6168,9 @@ Note: The task requested working directory "${taskDir}" but it does not exist. F
5691
6168
  }).catch((err) => console.error("[runner] active-session registration failed:", scrubSecrets(err instanceof Error ? err.message : String(err))));
5692
6169
  }
5693
6170
  console.log(`[${role}] Started task ${runningTask.taskId.slice(0, 8)} (${state.activeTasks.size}/${state.maxConcurrent} active, trigger: ${trigger.type})`);
6171
+ } else if (schedule.singlePoll) {
6172
+ await pushDelivery.waitForSchedule(schedule, failed, PollIntervalMs);
5694
6173
  }
5695
- } else {
5696
- console.log(`[${role}] At capacity (${state.activeTasks.size}/${state.maxConcurrent}), waiting for completion...`);
5697
- await Bun.sleep(1000);
5698
6174
  }
5699
6175
  }
5700
6176
  }