@desplega.ai/agent-swarm 2.4.3 → 2.4.5

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 (270) hide show
  1. package/dist/{acp-adapter-1885dpr7.js → acp-adapter-2wdkktv6.js} +1 -2
  2. package/dist/{actions-pn76n32q.js → actions-c40ebcm4.js} +18 -18
  3. package/dist/{amp-adapter-af84rx1d.js → amp-adapter-wb67m95z.js} +2 -2
  4. package/dist/{app-6sv8mhzm.js → app-353vre8g.js} +3 -3
  5. package/dist/{assistant-sc2tf825.js → assistant-sj1njh23.js} +21 -21
  6. package/dist/{boot-reembed-y3m0ydzj.js → boot-reembed-b9c3az3c.js} +1 -1
  7. package/dist/{boot-reembed-gvxjs2py.js → boot-reembed-qdkvxeem.js} +1 -1
  8. package/dist/{boot-scrub-sweep-1j9mk05f.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
  9. package/dist/{boot-seeds-37sp626b.js → boot-seeds-j2273s54.js} +4 -4
  10. package/dist/{claude-adapter-rp0jtnwr.js → claude-adapter-m7q0a085.js} +2 -2
  11. package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
  12. package/dist/{claude-managed-setup-rdt248k7.js → claude-managed-setup-smrqk5j5.js} +3 -3
  13. package/dist/{claude-sdk-session-53rcw5we.js → claude-sdk-session-38xnz21e.js} +8284 -8267
  14. package/dist/{cli-6eyncg0y.js → cli-018r4jq6.js} +34 -6
  15. package/dist/{cli-sds5zzs8.js → cli-1f3qzw6y.js} +78 -12
  16. package/dist/{cli-f0p0f6ja.js → cli-1sp861s4.js} +7 -7
  17. package/dist/{cli-dm8p2dpf.js → cli-1syn2xy1.js} +10 -10
  18. package/dist/{cli-tkf4x6ha.js → cli-37j04k05.js} +11 -11
  19. package/dist/{cli-v87dr6e3.js → cli-3a45zcgm.js} +1 -1
  20. package/dist/{cli-1n9gdg56.js → cli-43x64hv3.js} +4 -4
  21. package/dist/{cli-p0qerd2r.js → cli-4zfqgjc5.js} +2 -2
  22. package/dist/{cli-aa3tb29r.js → cli-7c9qs530.js} +1 -1
  23. package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
  24. package/dist/{cli-1xsraagq.js → cli-94gwyg7m.js} +8 -8
  25. package/dist/{cli-0dxxwfkt.js → cli-9hstj09h.js} +17 -14
  26. package/dist/{cli-1012xb7j.js → cli-9yqgm9nd.js} +22 -22
  27. package/dist/{cli-pygdsgh8.js → cli-a92r1127.js} +1 -1
  28. package/dist/{cli-8vverzf7.js → cli-ba8ne1es.js} +5 -0
  29. package/dist/{cli-bxrqvpg5.js → cli-be2161h1.js} +7 -7
  30. package/dist/{cli-npfe9t1s.js → cli-bnes4z6b.js} +2 -2
  31. package/dist/{cli-aypbg9r2.js → cli-cxxe61f6.js} +1 -1
  32. package/dist/{cli-vq9gqarq.js → cli-dk44dxec.js} +1 -1
  33. package/dist/{cli-836ehxmt.js → cli-eww120g8.js} +3 -2
  34. package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
  35. package/dist/{cli-mzkwkbdy.js → cli-f0g3pgvf.js} +1 -1
  36. package/dist/{cli-1ma335fz.js → cli-ftc1g5tj.js} +2 -2
  37. package/dist/{cli-4yef8ghv.js → cli-fv5hepyy.js} +3 -3
  38. package/dist/{cli-58vwn100.js → cli-g35m2xwm.js} +4 -4
  39. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  40. package/dist/{cli-gx0j2khs.js → cli-j99azg3g.js} +3 -3
  41. package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
  42. package/dist/{cli-qp2m4btv.js → cli-kpw04va0.js} +2 -2
  43. package/dist/{cli-8y99kfey.js → cli-ms85309d.js} +33 -15
  44. package/dist/{cli-kgnkyabk.js → cli-n72rg53p.js} +1555 -645
  45. package/dist/{cli-e4d78xsv.js → cli-p14m2cnp.js} +1 -1
  46. package/dist/{cli-1eszr343.js → cli-qfxc84gh.js} +1 -1
  47. package/dist/{cli-wk28hzqn.js → cli-qwhy2qma.js} +1 -1
  48. package/dist/{cli-73ezfhha.js → cli-st8aw1m6.js} +228 -11
  49. package/dist/{cli-zvbb4n0e.js → cli-tw4p90jb.js} +92 -192
  50. package/dist/{cli-038fga5k.js → cli-v0yrfmdh.js} +148 -183
  51. package/dist/{cli-6aen3ezb.js → cli-v8fjakwf.js} +1 -1
  52. package/dist/{cli-99jez72r.js → cli-w9etnnxk.js} +6 -1
  53. package/dist/{cli-vcgscaan.js → cli-wt0jf9hm.js} +217 -80
  54. package/dist/{cli-s9rr4gr2.js → cli-xpa64rek.js} +1065 -305
  55. package/dist/{cli-h0mgxann.js → cli-z7d29rw9.js} +2 -2
  56. package/dist/cli.js +45 -20
  57. package/dist/{codex-adapter-2zrmab9b.js → codex-adapter-tn6k5mkg.js} +1 -1
  58. package/dist/{codex-session-runner-kbkz62vc.js → codex-session-runner-7vjt7jbw.js} +1 -1
  59. package/dist/{commands-hd574th6.js → commands-t7rz9146.js} +2 -2
  60. package/dist/{controller-pz5mcfg4.js → controller-m2jasma5.js} +2 -2
  61. package/dist/{controllers-connect-5rgq2hfk.js → controllers-connect-9ftpex99.js} +19 -19
  62. package/dist/{cursor-adapter-mgaprq0j.js → cursor-adapter-mjv79e4z.js} +1 -1
  63. package/dist/{db-6nf1207q.js → db-6sfrn9g3.js} +1 -1
  64. package/dist/{db-h06dxp05.js → db-rn673r06.js} +3 -3
  65. package/dist/{dispatcher-zthpy4m1.js → dispatcher-c155xz0p.js} +17 -17
  66. package/dist/{dsh-adapter-64dhw0j7.js → dsh-adapter-3mmszc1x.js} +1 -1
  67. package/dist/{external-channel-x01t7ke6.js → external-channel-35k6tkv8.js} +2 -2
  68. package/dist/{grok-adapter-h21jv594.js → grok-adapter-tfrq7tg1.js} +2 -3
  69. package/dist/handlers-7gw64dxw.js +39 -0
  70. package/dist/{heartbeat-zzfksckz.js → heartbeat-dyg4npkp.js} +20 -20
  71. package/dist/{hook-z4zhzj1f.js → hook-drspbwpe.js} +1 -1
  72. package/dist/{hook-3xfqhh2s.js → hook-x26b31bg.js} +1 -1
  73. package/dist/{http-q813tmya.js → http-pk972xyw.js} +571 -294
  74. package/dist/{index-va3y63es.js → index-6srwvd5b.js} +1 -2
  75. package/dist/{index-x8xbyn1t.js → index-arytgj5q.js} +1 -1
  76. package/dist/{index-wbbs14c5.js → index-bgdyvcnv.js} +17 -17
  77. package/dist/{index-s98037v0.js → index-cbxkmfc5.js} +1 -2
  78. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  79. package/dist/{index-m0a4ydqj.js → index-gabbcdn2.js} +17 -17
  80. package/dist/{index-220tk9bw.js → index-jwxkkex2.js} +139 -76
  81. package/dist/index-tfb26zhy.js +41 -0
  82. package/dist/{keepalive-y5t1my7m.js → keepalive-7yrng7px.js} +3 -3
  83. package/dist/{lead-2xwtq66b.js → lead-7e02z839.js} +29 -30
  84. package/dist/{maintenance-ybfsaedv.js → maintenance-e6cemdqt.js} +1 -1
  85. package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
  86. package/dist/{oauth-refresh-sweep-909jmn12.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
  87. package/dist/{onboard-xn6fkkys.js → onboard-0a9y5yrc.js} +2 -2
  88. package/dist/{otel-impl-e6mn0z13.js → otel-impl-trjpkfb7.js} +1 -1
  89. package/dist/{pricing-refresh-ygd362ep.js → pricing-refresh-d2b5my0y.js} +3 -3
  90. package/dist/{rbac-roles-mb470p4e.js → rbac-roles-hey3yv9d.js} +2 -2
  91. package/dist/{rbac-roles-c9axvfnz.js → rbac-roles-nxh5ahwk.js} +2 -2
  92. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  93. package/dist/{render-v2-g0306nh1.js → render-v2-rr3p2atg.js} +3 -3
  94. package/dist/{resume-v315hnq6.js → resume-2efkf1s1.js} +5 -5
  95. package/dist/{session-access-2zg285a1.js → session-access-s2mttxpw.js} +6 -4
  96. package/dist/{setup-a4mtf02k.js → setup-vypma5yt.js} +1 -1
  97. package/dist/{work-objects-9fr0fgtn.js → work-objects-rvhckdkp.js} +20 -20
  98. package/dist/{worker-qae22jeb.js → worker-5snytpbd.js} +29 -30
  99. package/dist/worker-token-xf11yyds.js +136 -0
  100. package/openapi.json +303 -13
  101. package/package.json +8 -8
  102. package/src/apps/action-runner.ts +4 -0
  103. package/src/apps/catalog.generated.json +209 -4
  104. package/src/apps/page-validator.ts +21 -3
  105. package/src/be/chunking.ts +42 -9
  106. package/src/be/db/tasks/draft-text.ts +15 -0
  107. package/src/be/db/tasks/read.ts +14 -1
  108. package/src/be/db.ts +5 -1
  109. package/src/be/memory/index-content.ts +36 -17
  110. package/src/be/memory/providers/openai-embedding.ts +115 -61
  111. package/src/be/memory/providers/sqlite-store.ts +69 -23
  112. package/src/be/memory/reranker.ts +31 -1
  113. package/src/be/memory/search-candidates.ts +47 -0
  114. package/src/be/memory/types.ts +14 -0
  115. package/src/be/migrations/232_agent_session_tokens.sql +41 -0
  116. package/src/be/scripts/typecheck.ts +2 -0
  117. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +116 -55
  118. package/src/be/seed-scripts/index.ts +2 -2
  119. package/src/be/swarm-config-guard.ts +40 -1
  120. package/src/be/task-attachment-store.ts +30 -0
  121. package/src/be/users.ts +169 -12
  122. package/src/cli.tsx +24 -0
  123. package/src/commands/worker-token.ts +164 -0
  124. package/src/extensions/catalog.generated.json +4 -4
  125. package/src/extensions/contract-types.generated.ts +1 -1
  126. package/src/extensions/contract.ts +7 -0
  127. package/src/fs/provider.ts +13 -1
  128. package/src/heartbeat/heartbeat.ts +31 -11
  129. package/src/http/apps.ts +43 -5
  130. package/src/http/codex-oauth-keep-warm.ts +27 -9
  131. package/src/http/config.ts +5 -3
  132. package/src/http/core.ts +2 -0
  133. package/src/http/fs.ts +54 -34
  134. package/src/http/mcp-body.ts +37 -0
  135. package/src/http/mcp-user.ts +3 -5
  136. package/src/http/mcp.ts +6 -7
  137. package/src/http/memory.ts +26 -15
  138. package/src/http/page-proxy.ts +61 -5
  139. package/src/http/pages-public.ts +27 -44
  140. package/src/http/pages.ts +4 -4
  141. package/src/http/script-runs.ts +2 -0
  142. package/src/http/session-access.ts +51 -2
  143. package/src/http/sessions.ts +249 -10
  144. package/src/http/tasks.ts +4 -32
  145. package/src/http/users.ts +12 -1
  146. package/src/integrations/chat-sdk/adapters.ts +7 -3
  147. package/src/integrations/chat-sdk/attachments.ts +90 -12
  148. package/src/integrations/chat-sdk/facts.ts +120 -0
  149. package/src/integrations/chat-sdk/inbound.ts +156 -8
  150. package/src/integrations/chat-sdk/limits.ts +24 -0
  151. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  152. package/src/integrations/chat-sdk/outbound.ts +234 -4
  153. package/src/integrations/chat-sdk/relay.ts +4 -1
  154. package/src/integrations/chat-sdk/router.ts +28 -2
  155. package/src/integrations/chat-sdk/steering.ts +9 -0
  156. package/src/integrations/chat-sdk/templates.ts +14 -0
  157. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  158. package/src/mcp-limits.ts +20 -0
  159. package/src/pages/browser-sdk.ts +8 -3
  160. package/src/pages/feedback-overlay.ts +1 -1
  161. package/src/providers/acp-adapter.ts +33 -13
  162. package/src/providers/acp-swarm-events.ts +1 -1
  163. package/src/providers/amp-adapter.ts +1 -1
  164. package/src/providers/codex-adapter.ts +106 -119
  165. package/src/providers/codex-agents-md.ts +35 -229
  166. package/src/providers/codex-oauth/storage.ts +1 -1
  167. package/src/providers/cursor-adapter.ts +1 -1
  168. package/src/rbac/legacy-policy.ts +3 -1
  169. package/src/rbac/permissions.ts +4 -0
  170. package/src/realtime/transport.ts +1 -2
  171. package/src/script-workflows/workflow-ctx.ts +1 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  175. package/src/server-user.ts +2 -0
  176. package/src/server.ts +2 -0
  177. package/src/slack/assistant.ts +2 -2
  178. package/src/slack/files.ts +2 -0
  179. package/src/slack/handlers.ts +4 -4
  180. package/src/slack/inbound-files.ts +39 -6
  181. package/src/slack/thread-buffer.ts +2 -1
  182. package/src/tasks/task-terminal-effects.ts +21 -17
  183. package/src/tests/acp-adapter.test.ts +10 -23
  184. package/src/tests/acp-fake-agent.ts +36 -0
  185. package/src/tests/acp-swarm-events.test.ts +1 -1
  186. package/src/tests/agent-session-tokens.test.ts +452 -0
  187. package/src/tests/apps-action-parent-task.test.ts +295 -0
  188. package/src/tests/apps-card-grid.test.ts +197 -0
  189. package/src/tests/apps-catalog-additions.test.ts +120 -0
  190. package/src/tests/apps-tree.test.ts +129 -0
  191. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  192. package/src/tests/chat-sdk-config.test.ts +11 -0
  193. package/src/tests/chat-sdk-facts.test.ts +23 -0
  194. package/src/tests/chat-sdk-inbound.test.ts +558 -4
  195. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  196. package/src/tests/codex-adapter.test.ts +167 -242
  197. package/src/tests/codex-oauth-keep-warm.test.ts +85 -1
  198. package/src/tests/config-api-only-keys.test.ts +9 -0
  199. package/src/tests/config-session-auth.test.ts +14 -0
  200. package/src/tests/cors-allowlist.test.ts +1 -1
  201. package/src/tests/docs-index-footprint.test.js +2 -2
  202. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  203. package/src/tests/grok-adapter.test.ts +3 -3
  204. package/src/tests/heartbeat-checklist.test.ts +33 -0
  205. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  206. package/src/tests/heartbeat-jev-gate.test.ts +265 -1
  207. package/src/tests/kv-page-proxy.test.ts +91 -5
  208. package/src/tests/launch-password-rejection.test.ts +2 -2
  209. package/src/tests/mcp-input-caps.test.ts +294 -0
  210. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  211. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  212. package/src/tests/memory.test.ts +14 -0
  213. package/src/tests/migration-232-agent-session-tokens.test.ts +67 -0
  214. package/src/tests/page-guest-session.test.ts +19 -8
  215. package/src/tests/page-proxy-authed.test.ts +1 -1
  216. package/src/tests/page-proxy.test.ts +17 -10
  217. package/src/tests/page-session.test.ts +102 -1
  218. package/src/tests/pages-authed-mode.test.ts +82 -12
  219. package/src/tests/pages-password-mode.test.ts +5 -5
  220. package/src/tests/rbac-engine.test.ts +1 -0
  221. package/src/tests/realtime-transport.test.ts +13 -7
  222. package/src/tests/runtime-child-token-cleanup.test.ts +3 -3
  223. package/src/tests/script-connections.test.ts +2 -0
  224. package/src/tests/script-runs-http.test.ts +84 -0
  225. package/src/tests/scripts-http.test.ts +10 -1
  226. package/src/tests/session-route-posture.test.ts +2 -0
  227. package/src/tests/swarm-config-export-to-env.test.ts +22 -3
  228. package/src/tests/task-audio-transcription.test.ts +299 -0
  229. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  230. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  231. package/src/tests/transcription.test.ts +299 -0
  232. package/src/tests/worker-token-cli.test.ts +76 -0
  233. package/src/tests/workflow-executors.test.ts +4 -2
  234. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  235. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  236. package/src/tests/workflow-system-one-extraction.test.ts +805 -0
  237. package/src/tools/chat-access.ts +10 -0
  238. package/src/tools/chat-post.ts +31 -11
  239. package/src/tools/chat-reply.ts +32 -10
  240. package/src/tools/memory-search.ts +3 -1
  241. package/src/tools/system-one-decide.ts +17 -4
  242. package/src/types.ts +8 -1
  243. package/src/utils/config-env-export.ts +3 -1
  244. package/src/utils/page-session.ts +133 -20
  245. package/src/utils/request-auth-context.ts +2 -1
  246. package/src/utils/transcription.ts +246 -0
  247. package/src/workflows/definition.ts +7 -1
  248. package/src/workflows/engine.ts +18 -12
  249. package/src/workflows/executors/index.ts +1 -0
  250. package/src/workflows/executors/registry.ts +2 -0
  251. package/src/workflows/executors/system-one-decision.ts +84 -262
  252. package/src/workflows/executors/system-one-extract-wire.ts +57 -0
  253. package/src/workflows/executors/system-one-extraction.ts +830 -0
  254. package/src/workflows/executors/system-one-providers.ts +316 -114
  255. package/src/workflows/executors/system-one-transport.ts +264 -0
  256. package/templates/extensions/slack-reply-gate/README.md +24 -22
  257. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  258. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  259. package/templates/official/lead/HEARTBEAT.md +1 -1
  260. package/templates/skills/apps/SKILL.md +31 -7
  261. package/templates/skills/apps/content.md +31 -7
  262. package/templates/skills/chat-sdk/content.md +6 -2
  263. package/templates/skills/code-quality/content.md +2 -1
  264. package/templates/skills/heartbeat-runbook/content.md +8 -8
  265. package/templates/skills/pages/content.md +6 -4
  266. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  267. package/templates/skills/swarm-scripts/content.md +2 -2
  268. package/dist/cli-v1sjhn52.js +0 -7
  269. package/dist/handlers-2qjw434w.js +0 -39
  270. package/dist/index-d0g18bax.js +0 -41
@@ -42,6 +42,9 @@ import {
42
42
  recordUnmappedIdentity,
43
43
  MAX_TASK_ATTACHMENT_BYTES,
44
44
  recordTaskAttachmentUpload,
45
+ scopeFromAttachment,
46
+ backingProviderId,
47
+ transcribeAudioNotes,
45
48
  handleWebhookTrigger,
46
49
  verifyHmacSignature,
47
50
  WebhookError,
@@ -168,15 +171,22 @@ import {
168
171
  handleAgentSessionPrompted,
169
172
  isLinearEnabled,
170
173
  initLinear,
171
- parseCookieHeader,
174
+ isCookieSafePageId,
175
+ extractPageSession,
176
+ extractLegacyPageSession,
172
177
  extractAndVerifyCookie,
173
- issuePageSessionCookie,
178
+ pageSessionSetCookies,
174
179
  resolveHttpRequestAuth,
175
180
  loadGlobalConfigsIntoEnv,
176
181
  reloadGlobalConfigsAndIntegrations,
177
182
  scheduleIntegrationsReload,
178
183
  handleCore,
184
+ MCP_MAX_TOOL_INPUT_ELEMENTS,
185
+ MCP_MAX_REQUEST_BODY_BYTES,
179
186
  userCtx,
187
+ requestPrincipal,
188
+ agentFirstPrincipal,
189
+ rejectGuest,
180
190
  handleApps,
181
191
  cancelApprovalRequest,
182
192
  getUserCommsPrefs,
@@ -202,6 +212,9 @@ import {
202
212
  hydrateOAuthAppFromPreset,
203
213
  scopedResourceScopeIdSchema,
204
214
  resolveScopedResourceId,
215
+ MemoryKeyError,
216
+ refreshLinks,
217
+ indexMemoryContent,
205
218
  dedupeMemoryDocumentIds,
206
219
  recordMemoryAccesses,
207
220
  recordRetrievals,
@@ -218,12 +231,9 @@ import {
218
231
  reservedRoomKeyError,
219
232
  updateMcpServerChecked,
220
233
  updateTouchesStdioExecution,
221
- MemoryKeyError,
222
- refreshLinks,
223
234
  canReadMemory,
224
235
  getLinksForMemory,
225
236
  expandCandidatesWithGraph,
226
- indexMemoryContent,
227
237
  isApiDraining,
228
238
  drainApi,
229
239
  markKapsoMessageRead,
@@ -280,7 +290,7 @@ import {
280
290
  hasCapability,
281
291
  getEnabledCapabilities,
282
292
  createServer
283
- } from "./cli-kgnkyabk.js";
293
+ } from "./cli-n72rg53p.js";
284
294
  import {
285
295
  chatThreadOfContextKey,
286
296
  agentContextKey,
@@ -446,6 +456,7 @@ import {
446
456
  reservedKeyError,
447
457
  MAX_DB_RETENTION_DAYS,
448
458
  DB_RETENTION_TUNING_BOUNDS,
459
+ CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS_BOUNDS,
449
460
  validateConfigValue,
450
461
  validateExportToEnv,
451
462
  init_swarm_config_guard,
@@ -476,6 +487,8 @@ import {
476
487
  init_key_paths,
477
488
  rerank,
478
489
  init_reranker,
490
+ searchMemoryCandidates,
491
+ init_search_candidates,
479
492
  getEmbeddingProvider2,
480
493
  getMemoryStore2,
481
494
  init_memory,
@@ -716,26 +729,26 @@ import {
716
729
  countScriptRunJournalSteps2,
717
730
  countScriptRunJournalAgentTaskSteps2,
718
731
  init_db3
719
- } from "./cli-vcgscaan.js";
732
+ } from "./cli-wt0jf9hm.js";
720
733
  import {
721
734
  getUserGrant2,
722
735
  init_rbac_roles
723
- } from "./cli-qp2m4btv.js";
724
- import"./cli-1n9gdg56.js";
736
+ } from "./cli-kpw04va0.js";
737
+ import"./cli-43x64hv3.js";
725
738
  import {
726
739
  normalizeModelKey
727
- } from "./cli-vq9gqarq.js";
740
+ } from "./cli-dk44dxec.js";
728
741
  import {
729
742
  upsertModelCatalogOverlay,
730
743
  deleteModelCatalogOverlay,
731
744
  loadModelsCatalog,
732
745
  requestModelCatalogRefresh2
733
- } from "./cli-npfe9t1s.js";
746
+ } from "./cli-bnes4z6b.js";
734
747
  import {
735
748
  AMP_ESTIMATE_MODELS,
736
749
  ampBillsCacheWrites,
737
750
  resolveAmpModel
738
- } from "./cli-1eszr343.js";
751
+ } from "./cli-qfxc84gh.js";
739
752
  import {
740
753
  automationIntegrationFixUrl,
741
754
  preflightAutomation,
@@ -747,7 +760,7 @@ import {
747
760
  cancelWorkflowRun2,
748
761
  TriggerSchemaError,
749
762
  startWorkflowExecution2
750
- } from "./cli-0dxxwfkt.js";
763
+ } from "./cli-9hstj09h.js";
751
764
  import {
752
765
  require_dist2,
753
766
  isInvalidClientError,
@@ -827,14 +840,14 @@ import {
827
840
  typecheckWithAmbient,
828
841
  typecheckScript,
829
842
  parseSkillContent
830
- } from "./cli-73ezfhha.js";
843
+ } from "./cli-st8aw1m6.js";
831
844
  import {
832
845
  buildAuthorizationUrl,
833
846
  exchangeAuthorizationCode,
834
847
  oauthAppRowToProviderConfig,
835
848
  forceRefreshAuthorizationOrThrow,
836
849
  forceRefreshTokenOrThrow
837
- } from "./cli-pygdsgh8.js";
850
+ } from "./cli-a92r1127.js";
838
851
  import {
839
852
  signalProcessGroup,
840
853
  detachedProcessGroup,
@@ -862,14 +875,14 @@ import {
862
875
  getSlackApp2,
863
876
  startSlackApp2,
864
877
  stopSlackApp2
865
- } from "./cli-tkf4x6ha.js";
866
- import"./cli-v87dr6e3.js";
878
+ } from "./cli-37j04k05.js";
879
+ import"./cli-3a45zcgm.js";
867
880
  import {
868
881
  createApprovalFollowUpTask,
869
882
  generateEdges,
870
883
  definitionNodeIds,
871
884
  validateDefinition
872
- } from "./cli-s9rr4gr2.js";
885
+ } from "./cli-xpa64rek.js";
873
886
  import"./cli-8nyhf7za.js";
874
887
  import {
875
888
  _fetchHolder,
@@ -897,6 +910,7 @@ import {
897
910
  wireHttpSpanLifecycle,
898
911
  safeRequestUrlForLog,
899
912
  agentWithCapacity,
913
+ RequestBodyTooLargeError,
900
914
  parseBody,
901
915
  BODY_TOO_LARGE,
902
916
  enforceContentLengthCap,
@@ -922,12 +936,13 @@ import {
922
936
  canReadSession2,
923
937
  readableByFor2,
924
938
  SESSION_WRITE_DENIED2,
939
+ denyParentAttach2,
925
940
  runtimeInstanceHeader,
926
941
  bodyTask,
927
942
  bodyListTask,
928
943
  describeRequestRoute,
929
944
  route
930
- } from "./cli-6eyncg0y.js";
945
+ } from "./cli-018r4jq6.js";
931
946
  import {
932
947
  isMultiRuntimeEnabled
933
948
  } from "./cli-e8xyjvfc.js";
@@ -937,7 +952,7 @@ import {
937
952
  agentFsRequestTimeoutMs,
938
953
  AgentFsProvider,
939
954
  getFileStorageProvider2
940
- } from "./cli-qje9k2nh.js";
955
+ } from "./cli-g9ypd5en.js";
941
956
  import {
942
957
  RuntimePoolError2,
943
958
  mintControllerToken,
@@ -958,7 +973,7 @@ import {
958
973
  resolvePoolDelivery2,
959
974
  createPoolAgent2,
960
975
  init_db
961
- } from "./cli-gx0j2khs.js";
976
+ } from "./cli-j99azg3g.js";
962
977
  import {
963
978
  RATING_MODEL_MAX_LENGTH,
964
979
  REFERENCES_SOURCE_MAX_LENGTH,
@@ -974,8 +989,8 @@ import {
974
989
  checkHeartbeatChecklist2,
975
990
  runHeartbeatSweep2,
976
991
  stopHeartbeat2
977
- } from "./cli-8y99kfey.js";
978
- import"./cli-mzkwkbdy.js";
992
+ } from "./cli-ms85309d.js";
993
+ import"./cli-f0g3pgvf.js";
979
994
  import {
980
995
  import_websocket,
981
996
  import_websocket_server
@@ -984,7 +999,7 @@ import {
984
999
  runtimeSupervisorStatus,
985
1000
  startRuntimeSupervisor,
986
1001
  stopRuntimeSupervisor
987
- } from "./cli-f0p0f6ja.js";
1002
+ } from "./cli-1sp861s4.js";
988
1003
  import"./cli-ns59dc6r.js";
989
1004
  import {
990
1005
  ACP_TARGET_IDS
@@ -1004,14 +1019,14 @@ import {
1004
1019
  memoryRelevance,
1005
1020
  isUnknownModelError,
1006
1021
  writeSkillsToFilesystem
1007
- } from "./cli-6aen3ezb.js";
1022
+ } from "./cli-v8fjakwf.js";
1008
1023
  import {
1009
1024
  inviteEmailToSharedOrg,
1010
1025
  ensureAgentFsCredentialsForAgent
1011
- } from "./cli-p0qerd2r.js";
1026
+ } from "./cli-4zfqgjc5.js";
1012
1027
  import {
1013
1028
  Anthropic
1014
- } from "./cli-er83cjbn.js";
1029
+ } from "./cli-k8g16gz3.js";
1015
1030
  import {
1016
1031
  isCodexOAuthConfigKey
1017
1032
  } from "./cli-j4bwnpkm.js";
@@ -1041,9 +1056,13 @@ import {
1041
1056
  mintSessionToken,
1042
1057
  mintRuntimeToken,
1043
1058
  revokeSessionToken,
1059
+ AgentTokenExistsError,
1060
+ mintAgentToken,
1061
+ rotateAgentToken,
1062
+ revokeAgentTokens,
1044
1063
  fingerprintApiKey,
1045
1064
  init_users
1046
- } from "./cli-sds5zzs8.js";
1065
+ } from "./cli-1f3qzw6y.js";
1047
1066
  import"./cli-v41kn3cd.js";
1048
1067
  import"./cli-wgtk7xxr.js";
1049
1068
  import {
@@ -1057,7 +1076,7 @@ import {
1057
1076
  clearAuditSink,
1058
1077
  can,
1059
1078
  init_rbac
1060
- } from "./cli-8vverzf7.js";
1079
+ } from "./cli-ba8ne1es.js";
1061
1080
  import {
1062
1081
  isSafeBundlePath,
1063
1082
  init_bundle_path,
@@ -1199,7 +1218,7 @@ import {
1199
1218
  KvKeySchema,
1200
1219
  KvEntrySchema,
1201
1220
  init_types
1202
- } from "./cli-836ehxmt.js";
1221
+ } from "./cli-eww120g8.js";
1203
1222
  import {
1204
1223
  IDENTITY_FIELD_BUDGETS,
1205
1224
  IdentityFieldBudgetError,
@@ -1272,7 +1291,7 @@ import {
1272
1291
  import {
1273
1292
  init_package,
1274
1293
  package_default
1275
- } from "./cli-1xsraagq.js";
1294
+ } from "./cli-94gwyg7m.js";
1276
1295
  import {
1277
1296
  realtimeBus,
1278
1297
  init_bus
@@ -2454,8 +2473,7 @@ async function authenticate(req) {
2454
2473
  const page = await getPage2(pageId);
2455
2474
  if (!page)
2456
2475
  throw new Error("Page not found");
2457
- const cookie = await extractAndVerifyCookie(req);
2458
- const session = cookie?.pageId === pageId ? cookie : null;
2476
+ const session = await extractAndVerifyCookie(req, pageId);
2459
2477
  if (!session && page.authMode !== "public")
2460
2478
  throw new Error("Page session required");
2461
2479
  if (session?.uid && (await findUserById(session.uid))?.status !== "active")
@@ -6511,47 +6529,6 @@ init_db2();
6511
6529
  init_rbac();
6512
6530
  init_types();
6513
6531
  init_request_auth_context();
6514
-
6515
- // src/http/request-principal.ts
6516
- init_db3();
6517
- init_request_auth_context();
6518
- async function requestPrincipal(req, myAgentId) {
6519
- const auth = getRequestAuth(req);
6520
- if (auth?.kind === "controller")
6521
- return { kind: "guest" };
6522
- if (auth?.kind === "operator")
6523
- return { kind: "operator" };
6524
- if (auth?.kind === "user")
6525
- return { kind: "user", userId: auth.userId };
6526
- if (auth?.kind === "guest")
6527
- return { kind: "guest" };
6528
- if (!myAgentId)
6529
- return null;
6530
- const agent = await getAgentById2(myAgentId);
6531
- return { kind: "agent", agentId: myAgentId, isLead: agent?.isLead ?? false };
6532
- }
6533
- async function agentFirstPrincipal(req, myAgentId) {
6534
- const auth = getRequestAuth(req);
6535
- if (auth?.kind === "controller")
6536
- return { kind: "guest" };
6537
- if (auth?.kind === "user")
6538
- return { kind: "user", userId: auth.userId };
6539
- if (auth?.kind === "guest")
6540
- return { kind: "guest" };
6541
- const agentId = auth?.kind === "agent" ? auth.agentId : myAgentId;
6542
- if (!agentId)
6543
- return { kind: "operator" };
6544
- const agent = await getAgentById2(agentId);
6545
- return { kind: "agent", agentId, isLead: agent?.isLead === true };
6546
- }
6547
- function rejectGuest(req, res) {
6548
- if (getRequestAuth(req)?.kind !== "guest")
6549
- return false;
6550
- jsonError(res, "Forbidden", 403);
6551
- return true;
6552
- }
6553
-
6554
- // src/http/assets.ts
6555
6532
  var AssetKeyAuditIssueSchema = object2({
6556
6533
  severity: _enum2(["fatal", "warning"]),
6557
6534
  code: _enum2([
@@ -8150,7 +8127,7 @@ async function requestSecretReadScope(req, resolvedScope) {
8150
8127
  source: "http"
8151
8128
  }).allow)
8152
8129
  return "all";
8153
- if (!auth.runtimeId || resolvedScope?.agentId !== auth.agentId)
8130
+ if (auth.tokenKind === "task" || resolvedScope?.agentId !== auth.agentId)
8154
8131
  return "none";
8155
8132
  return "self";
8156
8133
  }
@@ -8168,7 +8145,7 @@ async function resolveSecretsRead(req, includeSecrets, resolvedScope) {
8168
8145
  const auth = getRequestAuth(req);
8169
8146
  return {
8170
8147
  effectiveIncludeSecrets: includeSecrets && scope !== "none",
8171
- includeRepoSecrets: !(auth?.kind === "agent" && auth.runtimeId && scope !== "all"),
8148
+ includeRepoSecrets: !(auth?.kind === "agent" && auth.tokenKind !== "task" && scope !== "all"),
8172
8149
  secretsNote: includeSecrets && scope === "none" ? SECRETS_FORCE_MASK_NOTE : ""
8173
8150
  };
8174
8151
  }
@@ -8350,7 +8327,7 @@ async function handleConfig(req, res, pathSegments, queryParams) {
8350
8327
  const key = parsed.query.key;
8351
8328
  const auth = getRequestAuth(req);
8352
8329
  const resolvedScope = {
8353
- agentId: parsed.query.agentId || (auth?.kind === "agent" && auth.runtimeId ? auth.agentId : undefined),
8330
+ agentId: parsed.query.agentId || (auth?.kind === "agent" && auth.tokenKind !== "task" ? auth.agentId : undefined),
8354
8331
  repoId: parsed.query.repoId || undefined
8355
8332
  };
8356
8333
  const { effectiveIncludeSecrets, includeRepoSecrets, secretsNote } = await resolveSecretsRead(req, includeSecrets, resolvedScope);
@@ -8824,10 +8801,18 @@ async function handleCodexOAuthDevice(req, res, pathSegments, queryParams) {
8824
8801
  // src/http/codex-oauth-keep-warm.ts
8825
8802
  init_zod();
8826
8803
  init_db3();
8804
+ init_swarm_config_guard();
8827
8805
  init_api_key();
8828
8806
  init_codex_auth_failure();
8829
8807
  init_constants2();
8830
- var KEEP_WARM_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
8808
+ var DEFAULT_KEEP_WARM_MAX_AGE_HOURS = 120;
8809
+ function keepWarmMaxAgeMs(env = process.env) {
8810
+ const raw = env.CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS?.trim();
8811
+ const { min, max } = CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS_BOUNDS;
8812
+ const value = raw && /^\d+$/.test(raw) ? Number(raw) : Number.NaN;
8813
+ const hours = value >= min && value <= max ? value : DEFAULT_KEEP_WARM_MAX_AGE_HOURS;
8814
+ return hours * 60 * 60 * 1000;
8815
+ }
8831
8816
  var slotOutcomeSchema = discriminatedUnion("outcome", [
8832
8817
  object2({ slot: number2(), keySuffix: string2(), outcome: literal2("warm") }),
8833
8818
  object2({ slot: number2(), keySuffix: string2(), outcome: literal2("refreshed") }),
@@ -8845,7 +8830,7 @@ var keepWarmRoute = route({
8845
8830
  path: "/api/oauth/keep-warm/codex",
8846
8831
  pattern: ["api", "oauth", "keep-warm", "codex"],
8847
8832
  summary: "Locked keep-warm refresh sweep across all Codex OAuth pool slots",
8848
- description: "Enumerates codex_oauth_* slots and refreshes any older than ~7 days through the same locked getValidCodexOAuth path used at task time. Skips slots with a codex-auth-watch bench marker (auth-failure bench or codex-auth-expiry-watch).",
8833
+ description: "Enumerates codex_oauth_* slots and refreshes any older than CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS (default 120 hours) through the same locked getValidCodexOAuth path used at task time. Skips slots with a codex-auth-watch bench marker (auth-failure bench or codex-auth-expiry-watch).",
8849
8834
  tags: ["OAuth"],
8850
8835
  responses: {
8851
8836
  200: {
@@ -8862,6 +8847,7 @@ async function handleCodexOAuthKeepWarm(req, res, pathSegments) {
8862
8847
  return true;
8863
8848
  const apiUrl = getMcpBaseUrl();
8864
8849
  const apiKey = getApiKey();
8850
+ const maxAgeMs = keepWarmMaxAgeMs();
8865
8851
  const slots = await loadAllCodexOAuthSlots(apiUrl, apiKey);
8866
8852
  const results = [];
8867
8853
  for (const { slot, creds } of slots) {
@@ -8872,9 +8858,7 @@ async function handleCodexOAuthKeepWarm(req, res, pathSegments) {
8872
8858
  }
8873
8859
  try {
8874
8860
  const beforeAccess = creds.access;
8875
- const refreshed = await getValidCodexOAuth(apiUrl, apiKey, slot, {
8876
- maxAgeMs: KEEP_WARM_MAX_AGE_MS
8877
- });
8861
+ const refreshed = await getValidCodexOAuth(apiUrl, apiKey, slot, { maxAgeMs });
8878
8862
  if (!refreshed) {
8879
8863
  results.push({ slot, outcome: "no-credentials" });
8880
8864
  } else {
@@ -9721,6 +9705,7 @@ declare module "swarm-extension" {
9721
9705
  sourceTaskId?: string | undefined;
9722
9706
  outputSchema?: Record<string, unknown> | undefined;
9723
9707
  inheritParentOutputSchema?: boolean | undefined;
9708
+ inheritParentRequester?: boolean | undefined;
9724
9709
  inheritParentRoutingAffinity?: boolean | undefined;
9725
9710
  inheritParentFollowUpConfig?: boolean | undefined;
9726
9711
  followUpConfig?:
@@ -10139,8 +10124,15 @@ declare module "swarm-extension" {
10139
10124
  userName?: string;
10140
10125
  /** The buffered messages, joined. */
10141
10126
  text: string;
10127
+ /** True for an @mention and for a reply to one of the bot's messages. */
10142
10128
  botMentioned: boolean;
10143
10129
  isDirectMessage: boolean;
10130
+ /** Platform chat type, such as Telegram \`private\`, \`group\`, \`supergroup\` or \`channel\`. */
10131
+ chatType?: string;
10132
+ /** Set for a Telegram supergroup with topics. */
10133
+ isForum?: boolean;
10134
+ /** A buffered message replies to one of the bot's own messages. */
10135
+ replyToBot: boolean;
10144
10136
  /** Set when earlier swarm tasks exist in this thread. */
10145
10137
  threadContext?: {
10146
10138
  adapter: string;
@@ -11467,8 +11459,8 @@ export default extension;
11467
11459
  manifest: {
11468
11460
  $schema: "../manifest.schema.json",
11469
11461
  name: "slack-reply-gate",
11470
- description: "Asks Jev (TypeSafe or OpenRouter) whether Lead should answer a buffered Slack thread follow-up, and drops the wake-up when it should not",
11471
- version: "1.2.0",
11462
+ description: "Asks a SystemOne decision model (Jev or Clef) whether Lead should answer a buffered Slack thread follow-up, and drops the wake-up when it should not",
11463
+ version: "1.3.0",
11472
11464
  runtime: "api",
11473
11465
  assets: {
11474
11466
  hooks: "hooks.ts"
@@ -11483,8 +11475,9 @@ import { z } from "zod";
11483
11475
  *
11484
11476
  * Every human message in a Slack thread the swarm takes part in is buffered and
11485
11477
  * flushed into a Lead task ("[Thread follow-up — N message(s) buffered]"), even
11486
- * "lol", ":joy:" or two people bantering. This hook asks TypeSafe Jev whether
11487
- * Lead should answer, and drops the wake-up when the answer is no.
11478
+ * "lol", ":joy:" or two people bantering. This hook asks a SystemOne decision
11479
+ * model (Jev or Clef) whether Lead should answer, and drops the wake-up when the
11480
+ * answer is no.
11488
11481
  *
11489
11482
  * Hook: \`pre.task.create\`, origin "slack", description carrying the
11490
11483
  * thread-buffer header. \`pre.slack.route\` cannot be used: the buffered thread
@@ -11492,17 +11485,20 @@ import { z } from "zod";
11492
11485
  * re-target a message, never drop it.
11493
11486
  *
11494
11487
  * Rules:
11495
- * - A message that @mentions Lead passes without a Jev call.
11496
- * - Otherwise one Jev \`choice\` question classifies the message into a reason;
11488
+ * - A message that @mentions Lead passes without a decision call.
11489
+ * - Otherwise one \`choice\` question classifies the message into a reason;
11497
11490
  * reply=false only for reaction_only | banter_between_others |
11498
11491
  * not_addressed_to_lead, and only when the summed probability of those
11499
11492
  * three labels is >= minConfidence (a banter/reaction split is still a skip).
11500
11493
  * - Fails OPEN on every error, timeout or missing key.
11501
11494
  *
11502
- * Transport (config.provider): Jev is served by TypeSafe directly and by
11503
- * OpenRouter's Decisions endpoint. Both take the same {model, state, questions}
11504
- * body and return the same \`answers\` shape. \`auto\` uses TYPESAFE_API_KEY when
11505
- * set, else OPENROUTER_API_KEY.
11495
+ * Provider (config.provider): the decision is made server-side by the
11496
+ * \`system-one-decide\` tool (\`ctx.swarm.systemOne_decide\`), the same path as a
11497
+ * \`system-one-decision\` workflow node, so every SystemOne host works and no key
11498
+ * enters this hook. \`auto\` picks the first host with a configured credential:
11499
+ * typesafe (Jev, TYPESAFE_API_KEY), openrouter (Jev, OPENROUTER_API_KEY), then
11500
+ * cloudflare (Clef, CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY, plus
11501
+ * CLOUDFLARE_ACCOUNT_ID). laya and openai are explicit choices only.
11506
11502
  *
11507
11503
  * modes (config.mode):
11508
11504
  * off -> never evaluate
@@ -11525,17 +11521,19 @@ import { z } from "zod";
11525
11521
  export const config = z.object({
11526
11522
  mode: z.enum(["off", "shadow", "enforce"]).default("shadow"),
11527
11523
  timeoutMs: z.number().int().positive().default(3000),
11528
- provider: z.enum(["auto", "typesafe", "openrouter"]).default("auto"),
11529
- /** Jev model on the TypeSafe transport. */
11524
+ provider: z
11525
+ .enum(["auto", "typesafe", "openrouter", "cloudflare", "laya", "openai"])
11526
+ .default("auto"),
11527
+ /** Jev model on TypeSafe. */
11530
11528
  model: z.string().default("jev-latest"),
11531
- /** Jev model on the OpenRouter transport. */
11529
+ /** Jev model on OpenRouter. */
11532
11530
  openrouterModel: z.string().default("typesafe/jev-1.13"),
11531
+ /** Clef model on Cloudflare Workers AI: clef or clef-flash. */
11532
+ cloudflareModel: z.string().default("clef"),
11533
11533
  minConfidence: z.number().min(0).max(1).default(0.55),
11534
11534
  contextMessages: z.number().int().min(0).max(20).default(6),
11535
11535
  leadAliases: z.array(z.string()).default(["lead"]),
11536
11536
  botSlackUserIds: z.array(z.string()).default([]),
11537
- /** Explicit TypeSafe key; otherwise TYPESAFE_API_KEY from the API process env. */
11538
- apiKey: z.string().optional(),
11539
11537
  /**
11540
11538
  * Reaction for a dropped message in enforce mode. "" only removes the bot's
11541
11539
  * acceptance reaction(s) and adds nothing.
@@ -11548,13 +11546,13 @@ type Cfg = z.infer<typeof config>;
11548
11546
  type Swarm = ApiCtx["swarm"];
11549
11547
 
11550
11548
  const NS = "ext:slack-reply-gate";
11551
- const TYPESAFE_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
11552
- const OPENROUTER_ENDPOINT = "https://openrouter.ai/api/alpha/decisions";
11553
11549
  const HEADER_LINE_RE = /(?:^|\\n)\\[Thread follow-up — \\d+ message\\(s\\) buffered\\]\\n/g;
11554
11550
  const CTX_OPEN = "<thread_context>";
11555
11551
  const CTX_CLOSE = "</thread_context>";
11556
11552
  // The dispatcher cancels a handler after 5 s; leave room for the KV writes.
11557
11553
  const MAX_TIMEOUT_MS = 4000;
11554
+ // The smallest time budget system-one-decide accepts.
11555
+ const MIN_DECIDE_TIMEOUT_MS = 1000;
11558
11556
 
11559
11557
  export const REASONS = {
11560
11558
  direct_ask:
@@ -11656,14 +11654,14 @@ const usable = (v: string): boolean => Boolean(v) && !v.startsWith("[REDACTED");
11656
11654
  const envOf = (name: string): string => String(process.env[name] ?? "");
11657
11655
 
11658
11656
  /**
11659
- * Resolve a secret: explicit value, then the API process env. The API process loads
11660
- * global swarm_config rows into process.env at boot and on config reload.
11657
+ * Resolve a secret from the API process env. The API process loads global
11658
+ * swarm_config rows into process.env at boot and on config reload.
11661
11659
  * ctx.swarm.config_get cannot serve it: the SDK scrubs secrets to "[REDACTED:<name>]".
11662
11660
  */
11663
- const secret = (name: string, explicit?: string): string => {
11661
+ const secret = (name: string): string => {
11664
11662
  const cached = secretCache.get(name);
11665
11663
  if (cached && Date.now() - cached.at < KEY_TTL_MS) return cached.value;
11666
- const value = [String(explicit ?? ""), envOf(name)].find(usable) ?? "";
11664
+ const value = usable(envOf(name)) ? envOf(name) : "";
11667
11665
  if (usable(value)) secretCache.set(name, { value, at: Date.now() });
11668
11666
  return value;
11669
11667
  };
@@ -11673,46 +11671,51 @@ export const resetKeyCache = (): void => {
11673
11671
  secretCache.clear();
11674
11672
  };
11675
11673
 
11676
- export type JevTransport = {
11677
- provider: "typesafe" | "openrouter";
11678
- endpoint: string;
11679
- key: string;
11680
- model: string;
11681
- };
11674
+ export type ProviderId = Exclude<Cfg["provider"], "auto">;
11675
+
11676
+ /** Where the decision is made. No model means the provider's own default. */
11677
+ export type DecisionTarget = { provider: ProviderId; model?: string };
11678
+
11679
+ /** What \`auto\` needs per provider: every group must have one usable value. */
11680
+ const AUTO_CREDENTIALS: ReadonlyArray<[ProviderId, string[][]]> = [
11681
+ ["typesafe", [["TYPESAFE_API_KEY"]]],
11682
+ ["openrouter", [["OPENROUTER_API_KEY"]]],
11683
+ ["cloudflare", [["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_KEY"], ["CLOUDFLARE_ACCOUNT_ID"]]],
11684
+ ];
11685
+
11686
+ const modelFor = (provider: ProviderId, cfg: Cfg): string | undefined =>
11687
+ provider === "typesafe"
11688
+ ? cfg.model
11689
+ : provider === "openrouter"
11690
+ ? cfg.openrouterModel
11691
+ : provider === "cloudflare"
11692
+ ? cfg.cloudflareModel
11693
+ : undefined;
11682
11694
 
11683
11695
  /**
11684
- * Pick the Jev transport. \`auto\` prefers TypeSafe and falls back to OpenRouter.
11685
- * Throws when the chosen provider has no usable key; the handler fails open.
11696
+ * The decision providers to try, in order. An explicit one is used as is: the
11697
+ * server checks its credential. \`auto\` lists every provider in \`AUTO_CREDENTIALS\`
11698
+ * whose credential is set and throws when none is; the handler then fails open.
11686
11699
  */
11687
- export const resolveTransport = (cfg: Cfg): JevTransport => {
11688
- const typesafe = (): JevTransport | null => {
11689
- const key = secret("TYPESAFE_API_KEY", cfg.apiKey);
11690
- return usable(key)
11691
- ? { provider: "typesafe", endpoint: TYPESAFE_ENDPOINT, key, model: cfg.model }
11692
- : null;
11693
- };
11694
- const openrouter = (): JevTransport | null => {
11695
- const key = secret("OPENROUTER_API_KEY");
11696
- return usable(key)
11697
- ? { provider: "openrouter", endpoint: OPENROUTER_ENDPOINT, key, model: cfg.openrouterModel }
11698
- : null;
11699
- };
11700
- const transport =
11701
- cfg.provider === "typesafe"
11702
- ? typesafe()
11703
- : cfg.provider === "openrouter"
11704
- ? openrouter()
11705
- : (typesafe() ?? openrouter());
11706
- if (transport) return transport;
11700
+ export const resolveProviders = (cfg: Cfg): DecisionTarget[] => {
11701
+ if (cfg.provider !== "auto") {
11702
+ return [{ provider: cfg.provider, model: modelFor(cfg.provider, cfg) }];
11703
+ }
11704
+ const targets = AUTO_CREDENTIALS.filter(([, groups]) =>
11705
+ groups.every((keys) => keys.some((key) => usable(secret(key)))),
11706
+ ).map(([provider]) => ({ provider, model: modelFor(provider, cfg) }));
11707
+ if (targets.length > 0) return targets;
11707
11708
  throw new Error(
11708
- cfg.provider === "typesafe"
11709
- ? "no TYPESAFE_API_KEY"
11710
- : cfg.provider === "openrouter"
11711
- ? "no OPENROUTER_API_KEY"
11712
- : "no TYPESAFE_API_KEY or OPENROUTER_API_KEY",
11709
+ "no TYPESAFE_API_KEY, OPENROUTER_API_KEY, or CLOUDFLARE_API_TOKEN with CLOUDFLARE_ACCOUNT_ID",
11713
11710
  );
11714
11711
  };
11715
11712
 
11713
+ /** The first provider \`resolveProviders\` would try. */
11714
+ export const resolveProvider = (cfg: Cfg): DecisionTarget => resolveProviders(cfg)[0]!;
11715
+
11716
+ /** The server refused the provider for a deployment setting and sent nothing. */
11717
+ export class ProviderUnavailableError extends Error {}
11718
+
11716
11719
  const SLACK_API = "https://slack.com/api";
11717
11720
  // The engine's acceptance reactions (src/slack/reaction-shortcode.ts), by config key.
11718
11721
  const ACCEPTANCE_REACTIONS: ReadonlyArray<[string, string]> = [
@@ -11800,7 +11803,7 @@ export const settleMutes = async (): Promise<void> => {
11800
11803
  await Promise.allSettled([...pendingMutes]);
11801
11804
  };
11802
11805
 
11803
- export type JevVerdict = {
11806
+ export type Verdict = {
11804
11807
  reason: string;
11805
11808
  confidence: number | null;
11806
11809
  probabilities: Record<string, number> | null;
@@ -11808,56 +11811,73 @@ export type JevVerdict = {
11808
11811
  };
11809
11812
 
11810
11813
  /**
11811
- * One Jev call: classify the newest message into a reason. Throws on any failure.
11812
- * TypeSafe and OpenRouter share the request body and the \`answers\` response shape.
11814
+ * One decision: classify the newest message into a reason. Throws on any failure.
11815
+ * The server makes the call (\`system-one-decide\`), so every provider returns the
11816
+ * same \`answers\` shape. The time budget is enforced here and on the server.
11813
11817
  */
11814
- export const askJev = async (
11818
+ export const askModel = async (
11819
+ swarm: Swarm,
11815
11820
  input: {
11816
11821
  messages: string[];
11817
11822
  context: string[];
11818
11823
  sender: string;
11819
11824
  leadNames: readonly string[];
11820
11825
  },
11821
- transport: JevTransport,
11826
+ target: DecisionTarget,
11822
11827
  cfg: Cfg,
11823
11828
  outer?: AbortSignal,
11824
- ): Promise<JevVerdict> => {
11825
- const controller = new AbortController();
11826
- const timer = setTimeout(() => controller.abort(), Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS));
11827
- const onOuterAbort = () => controller.abort();
11828
- outer?.addEventListener("abort", onOuterAbort);
11829
+ ): Promise<Verdict> => {
11830
+ const budgetMs = Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS);
11831
+ let timer: ReturnType<typeof setTimeout> | undefined;
11832
+ let onOuterAbort: (() => void) | undefined;
11833
+ const stop = new Promise<never>((_resolve, reject) => {
11834
+ timer = setTimeout(() => reject(new Error(\`timed out after \${budgetMs}ms\`)), budgetMs);
11835
+ onOuterAbort = () => reject(new Error("aborted"));
11836
+ outer?.addEventListener("abort", onOuterAbort);
11837
+ });
11829
11838
  const t0 = Date.now();
11830
11839
  try {
11831
- const res = await fetch(transport.endpoint, {
11832
- method: "POST",
11833
- headers: {
11834
- "Content-Type": "application/json",
11835
- Authorization: \`Bearer \${transport.key}\`,
11840
+ const call = swarm.systemOne_decide({
11841
+ provider: target.provider,
11842
+ ...(target.model ? { model: target.model } : {}),
11843
+ state: {
11844
+ role: "Decide whether Lead, the coordinating AI agent of a team's agent swarm, should answer a new Slack message in a thread it takes part in. Lead is woken by every message in the thread, including ones not meant for it. Answering noise wastes everyone's time; missing a real ask is worse.",
11845
+ lead_names: [...input.leadNames],
11846
+ recent_thread_messages: input.context,
11847
+ new_message: input.messages.join("\\n---\\n"),
11848
+ new_message_sender: input.sender,
11836
11849
  },
11837
- body: JSON.stringify({
11838
- state: {
11839
- role: "Decide whether Lead, the coordinating AI agent of a team's agent swarm, should answer a new Slack message in a thread it takes part in. Lead is woken by every message in the thread, including ones not meant for it. Answering noise wastes everyone's time; missing a real ask is worse.",
11840
- lead_names: input.leadNames,
11841
- recent_thread_messages: input.context,
11842
- new_message: input.messages.join("\\n---\\n"),
11843
- new_message_sender: input.sender,
11850
+ questions: {
11851
+ reason: {
11852
+ type: "choice",
11853
+ instructions:
11854
+ "Which label best describes new_message from Lead's point of view? Use recent_thread_messages to tell who it is talking to. Lines starting with [Agent] are swarm bots.",
11855
+ criteria: REASONS,
11844
11856
  },
11845
- model: transport.model,
11846
- questions: {
11847
- reason: {
11848
- type: "choice",
11849
- instructions:
11850
- "Which label best describes new_message from Lead's point of view? Use recent_thread_messages to tell who it is talking to. Lines starting with [Agent] are swarm bots.",
11851
- criteria: REASONS,
11852
- },
11853
- },
11854
- }),
11855
- signal: controller.signal,
11857
+ },
11858
+ timeoutMs: Math.max(budgetMs, MIN_DECIDE_TIMEOUT_MS),
11859
+ maxRetries: 0,
11856
11860
  });
11857
- if (!res.ok) throw new Error(\`\${transport.provider} \${res.status}\`);
11858
- const body = (await res.json()) as {
11861
+ // After a timeout nobody awaits the call; its late failure must not go unhandled.
11862
+ call.catch(() => {});
11863
+ const res = (await Promise.race([call, stop])) as {
11864
+ success?: boolean;
11865
+ status?: number;
11866
+ data?: unknown;
11867
+ } | null;
11868
+ const body = (res?.data ?? res) as {
11869
+ success?: boolean;
11870
+ message?: unknown;
11871
+ error?: unknown;
11872
+ providerUnavailable?: unknown;
11859
11873
  answers?: { reason?: { choice?: unknown; confidence?: unknown; probabilities?: unknown } };
11860
11874
  } | null;
11875
+ if (res?.success === false || body?.success === false) {
11876
+ const why = \`\${target.provider}: \${String(body?.message ?? body?.error ?? \`HTTP \${res?.status}\`).slice(0, 200)}\`;
11877
+ throw body?.providerUnavailable === true
11878
+ ? new ProviderUnavailableError(why)
11879
+ : new Error(why);
11880
+ }
11861
11881
  const answer = body?.answers?.reason;
11862
11882
  const reason = typeof answer?.choice === "string" ? answer.choice : "";
11863
11883
  if (!(reason in REASONS)) throw new Error(\`unexpected choice: \${String(reason).slice(0, 60)}\`);
@@ -11869,12 +11889,34 @@ export const askJev = async (
11869
11889
  return { reason, confidence, probabilities, latencyMs: Date.now() - t0 };
11870
11890
  } finally {
11871
11891
  clearTimeout(timer);
11872
- outer?.removeEventListener("abort", onOuterAbort);
11892
+ if (onOuterAbort) outer?.removeEventListener("abort", onOuterAbort);
11873
11893
  }
11874
11894
  };
11875
11895
 
11896
+ /**
11897
+ * \`askModel\` on each target in turn, moving on only when the server refused the
11898
+ * provider for a deployment setting (no request was sent). Any other failure, or
11899
+ * a refusal of the last target, throws.
11900
+ */
11901
+ export const askFirstAvailable = async (
11902
+ swarm: Swarm,
11903
+ input: Parameters<typeof askModel>[1],
11904
+ targets: readonly DecisionTarget[],
11905
+ cfg: Cfg,
11906
+ outer?: AbortSignal,
11907
+ ): Promise<{ verdict: Verdict; target: DecisionTarget }> => {
11908
+ for (const [i, target] of targets.entries()) {
11909
+ try {
11910
+ return { verdict: await askModel(swarm, input, target, cfg, outer), target };
11911
+ } catch (err) {
11912
+ if (!(err instanceof ProviderUnavailableError) || i === targets.length - 1) throw err;
11913
+ }
11914
+ }
11915
+ throw new Error("no decision provider");
11916
+ };
11917
+
11876
11918
  /** Probability that the message needs no reply: the summed mass of the skip labels. */
11877
- export const skipProbability = (verdict: JevVerdict): number | null => {
11919
+ export const skipProbability = (verdict: Verdict): number | null => {
11878
11920
  if (verdict.probabilities) {
11879
11921
  let sum = 0;
11880
11922
  for (const [label, p] of Object.entries(verdict.probabilities)) {
@@ -11885,9 +11927,9 @@ export const skipProbability = (verdict: JevVerdict): number | null => {
11885
11927
  return verdict.confidence;
11886
11928
  };
11887
11929
 
11888
- /** Map a Jev verdict to the typed decision. An unsure skip verdict replies. */
11930
+ /** Map a verdict to the typed decision. An unsure skip verdict replies. */
11889
11931
  export const decide = (
11890
- verdict: JevVerdict,
11932
+ verdict: Verdict,
11891
11933
  minConfidence: number,
11892
11934
  ): { reply: boolean; reason: Reason; skipP: number | null; lowConfidence: boolean } => {
11893
11935
  const reason = verdict.reason as Reason;
@@ -11984,7 +12026,7 @@ const extension: SwarmExtension = (api) => {
11984
12026
  return;
11985
12027
  }
11986
12028
 
11987
- const transport = resolveTransport(cfg);
12029
+ const targets = resolveProviders(cfg);
11988
12030
 
11989
12031
  const sender =
11990
12032
  opts.slackUserId && cfg.botSlackUserIds.includes(opts.slackUserId)
@@ -11993,14 +12035,15 @@ const extension: SwarmExtension = (api) => {
11993
12035
  ? "human (known teammate)"
11994
12036
  : "unknown (not a known teammate; may be a bot or an external person)";
11995
12037
 
11996
- const verdict = await askJev(
12038
+ const { verdict, target } = await askFirstAvailable(
12039
+ ctx.swarm,
11997
12040
  {
11998
12041
  messages,
11999
12042
  context: context.slice(-cfg.contextMessages).map((m) => m.slice(0, 500)),
12000
12043
  sender,
12001
12044
  leadNames: cfg.leadAliases,
12002
12045
  },
12003
- transport,
12046
+ targets,
12004
12047
  cfg,
12005
12048
  ctx.signal,
12006
12049
  );
@@ -12019,7 +12062,7 @@ const extension: SwarmExtension = (api) => {
12019
12062
  skipP: decision.skipP,
12020
12063
  lowConfidence: decision.lowConfidence,
12021
12064
  latencyMs: verdict.latencyMs,
12022
- provider: transport.provider,
12065
+ provider: target.provider,
12023
12066
  sender,
12024
12067
  preview,
12025
12068
  },
@@ -12059,7 +12102,7 @@ const extension: SwarmExtension = (api) => {
12059
12102
  export default extension;
12060
12103
  `
12061
12104
  },
12062
- readme: '# slack-reply-gate\n\nLead is woken by every human message in a Slack thread the swarm takes part in, including "lol", an emoji, or two people chatting with each other. This extension asks Jev, [TypeSafe](https://typesafe.ai)\'s decision model, whether Lead should answer a new thread message, and drops the wake-up when it should not.\n\nInstall config:\n\n```json\n{ "mode": "shadow" }\n```\n\nStart in `shadow`, read the decisions in KV for a few days, then switch to `enforce`.\n\n## Modes\n\n| `mode` | Effect |\n|---|---|\n| `off` | Never evaluates. |\n| `shadow` (default) | Asks Jev and records the verdict. Never blocks. |\n| `enforce` | Asks Jev, records the verdict, and blocks the Lead task when the answer is no. The message gets :mute: instead: no Lead task, no reply, no outcome card. |\n\n## What it gates\n\nHook: `pre.task.create`, for tasks with origin `slack` whose description carries the thread buffer\'s header `[Thread follow-up — N message(s) buffered]`. Those are the non-mention thread replies that `src/slack/thread-buffer.ts` batches into a Lead task. A direct @mention of the bot creates a task without that header and is never gated.\n\nThe header counts only at the start of the description or right after the closing `</thread_context>`. Thread context quotes old messages verbatim, and an older Lead reply can quote the header itself, so a header inside the context is ignored. Only the text after the real header is treated as new.\n\n## Decision\n\n1. A follow-up with downloaded files (status `draft`) passes without a Jev call.\n2. **@mention pass-through.** A new message that @mentions Lead passes without a Jev call. A mention is the bot mention the engine rewrites to `<@U…> (that\'s you)`, a resolved `<@U…|lead>`, or a literal `@lead` (any name in `leadAliases`). Only the new messages are scanned, never the thread context, so an old mention earlier in the thread does not pass later banter.\n3. Otherwise one Jev call (one `choice` question, see [Jev transport](#jev-transport)) with the new message(s), the sender kind, and the last `contextMessages` thread messages. Labels: `direct_ask`, `followup_to_lead`, `instruction_to_lead`, `reaction_only`, `banter_between_others`, `not_addressed_to_lead`, `other`.\n4. The answer is no only when the top label is `reaction_only`, `banter_between_others`, or `not_addressed_to_lead`, and the summed probability of those three labels is at least `minConfidence`. A split between two skip labels is still a skip. An unsure verdict replies.\n\nSeveral buffered messages are judged together. One mention in any of them passes the batch.\n\n## Jev transport\n\nJev is reachable two ways. Both take the same request and return the same answer, so the decision is identical whichever one runs.\n\n| `provider` | Endpoint | Key | Model |\n|---|---|---|---|\n| `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` (or `apiKey`) | `model`, default `jev-latest` |\n| `openrouter` | `POST https://openrouter.ai/api/alpha/decisions` | `OPENROUTER_API_KEY` | `openrouterModel`, default `typesafe/jev-1.13` |\n| `auto` (default) | TypeSafe when its key is set, else OpenRouter | | |\n\nUnder `auto`, `TYPESAFE_API_KEY` wins when both keys are set. With neither key, every message fails open and the KV row names both secrets. Each decision row records the `provider` that answered.\n\n## Fail open\n\nEvery error lets the task through: a missing key, a TypeSafe HTTP error, a network error, a timeout, a malformed answer, or an unknown label. The handler never throws, so an outage cannot auto-disable the extension. Each failure is recorded as `fail-open` in KV and logged as a warning.\n\n## :mute: on drop (enforce)\n\nNo swarm tool or extension `ctx` method adds a Slack reaction, so the hook calls the Slack Web API with the bot token. It needs the `reactions:write` scope, which the Slack app already uses for its acceptance reactions.\n\n1. `reactions.remove` for each acceptance reaction the engine may have put on the triggering message: `eyes`, `heavy_plus_sign`, `zap`, `speech_balloon`, or their `SLACK_REACTION_ACCEPTED`, `SLACK_REACTION_BUFFERED`, `SLACK_REACTION_NOW`, and `SLACK_REACTION_STEERED` overrides. Slack only removes the bot\'s own reaction, so a human\'s :eyes: stays.\n2. `reactions.add` with `muteReaction` (default `mute`). `already_reacted` counts as success. Skipped when `muteReaction` is `""`.\n\nThe block returns first and the Slack calls run after it. A slow or failing Slack call never delays the drop or turns it back into a task. Set `muteReaction` to `""` for a true no-op: step 1 still clears the bot\'s acceptance reaction(s), and nothing is added.\n\n## Config\n\n| Key | Default | Notes |\n|---|---|---|\n| `mode` | `shadow` | `off`, `shadow`, or `enforce` |\n| `timeoutMs` | `3000` | Jev call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |\n| `provider` | `auto` | `auto`, `typesafe`, or `openrouter`. See [Jev transport](#jev-transport) |\n| `model` | `jev-latest` | Jev model on TypeSafe |\n| `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |\n| `minConfidence` | `0.55` | Minimum summed skip probability to skip |\n| `contextMessages` | `6` | Thread messages sent to Jev, 0 to 20 |\n| `leadAliases` | `["lead"]` | A literal `@alias` in a new message counts as a mention |\n| `botSlackUserIds` | `[]` | Slack user ids labelled `bot` as the sender for Jev |\n| `apiKey` | unset | Explicit TypeSafe key. Prefer the `TYPESAFE_API_KEY` secret below |\n| `muteReaction` | `mute` | Reaction for a dropped message; `""` only removes the acceptance reaction(s) |\n| `slackTimeoutMs` | `5000` | Timeout per Slack call |\n\n## Required secrets\n\nAll are read from the API process environment at call time. Set them as global `swarm_config` secrets: the API process loads global rows into `process.env` at boot and on config reload.\n\n- `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`: one is required for any evaluation. Under `auto`, TypeSafe wins when both are set. Without either, every message fails open.\n- `SLACK_BOT_TOKEN`: the Slack app\'s bot token. A swarm with Slack connected already has it. Without it, `enforce` still drops the message but cannot touch its reactions, and logs a `mute-error`.\n\nNo key is written to config, KV, logs, or a description.\n\n## KV namespace `ext:slack-reply-gate`\n\n- `d:<epochMs>:<channel>_<threadTs>`: one row per decision, with `action` (`pass`, `shadow-skip`, `skipped`, `fail-open`), `provider`, `reason`, `confidence`, `skipP`, `latencyMs`, `sender`, and a 300-character `preview`.\n- `m:<epochMs>:<channel>_<threadTs>`: one row per mute attempt, with `action` (`muted`, `unreacted` when `muteReaction` is `""`, `mute-error`), `removed`, `added`, and `errors`.\n- `last`: the latest decision. Mute rows never overwrite it.\n- Counters: `total`, `mention`, `files`, `reply`, `shadow-skip`, `skipped`, `error`, `muted`, `unreacted`, `mute-error`, and `reason:<label>`.\n\n## Known limits\n\n- When Lead already has an active task in the thread, the buffer steers that task instead of creating one, so `pre.task.create` does not fire and the gate does not run.\n- A blocked flush makes the thread buffer log a `TaskCreationBlockedError`. Nothing is lost: the next follow-up\'s `<thread_context>` carries the earlier messages.\n- A flush of several messages mutes only the triggering (last) message. The others keep their acceptance reaction.\n'
12105
+ readme: '# slack-reply-gate\n\nLead is woken by every human message in a Slack thread the swarm takes part in, including "lol", an emoji, or two people chatting with each other. This extension asks a SystemOne decision model, Jev ([TypeSafe](https://typesafe.ai)) or Clef (Cloudflare Workers AI), whether Lead should answer a new thread message, and drops the wake-up when it should not.\n\nInstall config:\n\n```json\n{ "mode": "shadow" }\n```\n\nStart in `shadow`, read the decisions in KV for a few days, then switch to `enforce`.\n\n## Modes\n\n| `mode` | Effect |\n|---|---|\n| `off` | Never evaluates. |\n| `shadow` (default) | Asks the model and records the verdict. Never blocks. |\n| `enforce` | Asks the model, records the verdict, and blocks the Lead task when the answer is no. The message gets :mute: instead: no Lead task, no reply, no outcome card. |\n\n## What it gates\n\nHook: `pre.task.create`, for tasks with origin `slack` whose description carries the thread buffer\'s header `[Thread follow-up — N message(s) buffered]`. Those are the non-mention thread replies that `src/slack/thread-buffer.ts` batches into a Lead task. A direct @mention of the bot creates a task without that header and is never gated.\n\nThe header counts only at the start of the description or right after the closing `</thread_context>`. Thread context quotes old messages verbatim, and an older Lead reply can quote the header itself, so a header inside the context is ignored. Only the text after the real header is treated as new.\n\n## Decision\n\n1. A follow-up with downloaded files (status `draft`) passes without a decision call.\n2. **@mention pass-through.** A new message that @mentions Lead passes without a decision call. A mention is the bot mention the engine rewrites to `<@U…> (that\'s you)`, a resolved `<@U…|lead>`, or a literal `@lead` (any name in `leadAliases`). Only the new messages are scanned, never the thread context, so an old mention earlier in the thread does not pass later banter.\n3. Otherwise one decision call (one `choice` question, see [Provider](#provider)) with the new message(s), the sender kind, and the last `contextMessages` thread messages. Labels: `direct_ask`, `followup_to_lead`, `instruction_to_lead`, `reaction_only`, `banter_between_others`, `not_addressed_to_lead`, `other`.\n4. The answer is no only when the top label is `reaction_only`, `banter_between_others`, or `not_addressed_to_lead`, and the summed probability of those three labels is at least `minConfidence`. A split between two skip labels is still a skip. An unsure verdict replies.\n\nSeveral buffered messages are judged together. One mention in any of them passes the batch.\n\n## Provider\n\nThe decision is made on the API server by the `system-one-decide` tool (`ctx.swarm.systemOne_decide`), the same path a `system-one-decision` workflow node takes. The server reads the provider\'s credential, sends the request, and validates the answer, so every SystemOne host works and no key enters the hook.\n\n| `provider` | Model | Credential |\n|---|---|---|\n| `typesafe` | Jev, `model` (default `jev-latest`) | `TYPESAFE_API_KEY` |\n| `openrouter` | Jev, `openrouterModel` (default `typesafe/jev-1.13`) | `OPENROUTER_API_KEY` |\n| `cloudflare` | Clef, `cloudflareModel` (default `clef`, or `clef-flash`) | `CLOUDFLARE_API_TOKEN` (or `CLOUDFLARE_API_KEY`) and `CLOUDFLARE_ACCOUNT_ID` |\n| `laya`, `openai` | The host\'s default model | See the workflows guide |\n| `auto` (default) | The first of typesafe, openrouter, cloudflare whose credential is set. A provider the server refuses for a deployment setting (openrouter when `OPENROUTER_BASE_URL` points at a gateway) is skipped for the next one | |\n\nWith no credential for `auto`, every message fails open and the KV row names the secrets. Each decision row records the `provider` that answered. `minConfidence` was tuned on Jev; check Clef\'s verdicts in `shadow` before you enforce on it.\n\n## Fail open\n\nEvery error lets the task through: a missing key, a provider error relayed by the server, a network error, a timeout, a malformed answer, or an unknown label. The handler never throws, so an outage cannot auto-disable the extension. Each failure is recorded as `fail-open` in KV and logged as a warning.\n\n## :mute: on drop (enforce)\n\nNo swarm tool or extension `ctx` method adds a Slack reaction, so the hook calls the Slack Web API with the bot token. It needs the `reactions:write` scope, which the Slack app already uses for its acceptance reactions.\n\n1. `reactions.remove` for each acceptance reaction the engine may have put on the triggering message: `eyes`, `heavy_plus_sign`, `zap`, `speech_balloon`, or their `SLACK_REACTION_ACCEPTED`, `SLACK_REACTION_BUFFERED`, `SLACK_REACTION_NOW`, and `SLACK_REACTION_STEERED` overrides. Slack only removes the bot\'s own reaction, so a human\'s :eyes: stays.\n2. `reactions.add` with `muteReaction` (default `mute`). `already_reacted` counts as success. Skipped when `muteReaction` is `""`.\n\nThe block returns first and the Slack calls run after it. A slow or failing Slack call never delays the drop or turns it back into a task. Set `muteReaction` to `""` for a true no-op: step 1 still clears the bot\'s acceptance reaction(s), and nothing is added.\n\n## Config\n\n| Key | Default | Notes |\n|---|---|---|\n| `mode` | `shadow` | `off`, `shadow`, or `enforce` |\n| `timeoutMs` | `3000` | Decision call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |\n| `provider` | `auto` | `auto`, `typesafe`, `openrouter`, `cloudflare`, `laya`, or `openai`. See [Provider](#provider) |\n| `model` | `jev-latest` | Jev model on TypeSafe |\n| `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |\n| `cloudflareModel` | `clef` | Clef model on Cloudflare Workers AI |\n| `minConfidence` | `0.55` | Minimum summed skip probability to skip |\n| `contextMessages` | `6` | Thread messages sent to the model, 0 to 20 |\n| `leadAliases` | `["lead"]` | A literal `@alias` in a new message counts as a mention |\n| `botSlackUserIds` | `[]` | Slack user ids labelled `bot` as the sender for the model |\n| `muteReaction` | `mute` | Reaction for a dropped message; `""` only removes the acceptance reaction(s) |\n| `slackTimeoutMs` | `5000` | Timeout per Slack call |\n\n## Required secrets\n\nSet them as global `swarm_config` values. The server reads the provider credential from the global row when it makes the decision. `auto` and the Slack token read the API process environment, which loads global rows at boot and on config reload.\n\n- One provider credential from the [Provider](#provider) table is required for any evaluation. Without one, every message fails open. The `apiKey` config field of earlier versions is gone: the hook never holds a key, so store it as the global `TYPESAFE_API_KEY` secret.\n- `SLACK_BOT_TOKEN`: the Slack app\'s bot token. A swarm with Slack connected already has it. Without it, `enforce` still drops the message but cannot touch its reactions, and logs a `mute-error`.\n\nNo key is written to config, KV, logs, or a description.\n\n## KV namespace `ext:slack-reply-gate`\n\n- `d:<epochMs>:<channel>_<threadTs>`: one row per decision, with `action` (`pass`, `shadow-skip`, `skipped`, `fail-open`), `provider`, `reason`, `confidence`, `skipP`, `latencyMs`, `sender`, and a 300-character `preview`.\n- `m:<epochMs>:<channel>_<threadTs>`: one row per mute attempt, with `action` (`muted`, `unreacted` when `muteReaction` is `""`, `mute-error`), `removed`, `added`, and `errors`.\n- `last`: the latest decision. Mute rows never overwrite it.\n- Counters: `total`, `mention`, `files`, `reply`, `shadow-skip`, `skipped`, `error`, `muted`, `unreacted`, `mute-error`, and `reason:<label>`.\n\n## Known limits\n\n- When Lead already has an active task in the thread, the buffer steers that task instead of creating one, so `pre.task.create` does not fire and the gate does not run.\n- A blocked flush makes the thread buffer log a `TaskCreationBlockedError`. Nothing is lost: the next follow-up\'s `<thread_context>` carries the earlier messages.\n- A flush of several messages mutes only the triggering (last) message. The others keep their acceptance reaction.\n'
12063
12106
  },
12064
12107
  "task-digest": {
12065
12108
  manifestFile: "manifest.yaml",
@@ -13245,6 +13288,17 @@ async function handleFleetBenchmark(req, res, pathSegments, queryParams) {
13245
13288
  // src/http/fs.ts
13246
13289
  init_zod();
13247
13290
  init_db3();
13291
+
13292
+ // src/be/db/tasks/draft-text.ts
13293
+ init_runtime();
13294
+ async function appendDraftTaskText(taskId, block) {
13295
+ const result = await getDbClient2().run("UPDATE agent_tasks SET task = task || ?, lastUpdatedAt = ? WHERE id = ? AND status = 'draft'", [`
13296
+
13297
+ ${block}`, new Date().toISOString(), taskId]);
13298
+ return result.changes > 0;
13299
+ }
13300
+
13301
+ // src/http/fs.ts
13248
13302
  init_rbac();
13249
13303
  init_types();
13250
13304
 
@@ -13368,7 +13422,7 @@ var uploadTaskFileRoute = route({
13368
13422
  pattern: ["api", "fs", "tasks", null, "files"],
13369
13423
  session: { taskParam: "taskId" },
13370
13424
  summary: "Upload a binary task file attachment",
13371
- description: "Accepts a raw binary request body. Pass the display/path name as the `name` query parameter.",
13425
+ description: "Accepts a raw binary request body. Pass the display/path name as the `name` query parameter. An `audio/*` file uploaded to a `draft` task gets a `[File: …]` line and its transcript (or why there is none) appended to the task text before the response; a failed transcription never fails the upload.",
13372
13426
  tags: ["FS"],
13373
13427
  params: taskParams,
13374
13428
  query: uploadQuery,
@@ -13529,7 +13583,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13529
13583
  return notFound(res, "Task not found");
13530
13584
  if (!await canMutateTask(task, myAgentId, req))
13531
13585
  return forbidden(res);
13532
- return sendUpload(req, res, parsed.params.taskId, parsed.query, myAgentId ?? null);
13586
+ return sendUpload(req, res, parsed.params.taskId, parsed.query, myAgentId ?? null, task.status === "draft");
13533
13587
  }
13534
13588
  if (listTaskFilesRoute.match(req.method, pathSegments)) {
13535
13589
  const parsed = await listTaskFilesRoute.parse(req, res, pathSegments, queryParams);
@@ -13556,7 +13610,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13556
13610
  }
13557
13611
  return false;
13558
13612
  }
13559
- async function sendUpload(req, res, taskId, query, agentId) {
13613
+ async function sendUpload(req, res, taskId, query, agentId, isDraft) {
13560
13614
  const body = await readRawBody(req, MAX_TASK_ATTACHMENT_BYTES);
13561
13615
  if (body === BODY_TOO_LARGE) {
13562
13616
  jsonError(res, `Payload too large (max ${MAX_TASK_ATTACHMENT_BYTES} bytes)`, 413);
@@ -13597,9 +13651,27 @@ async function sendUpload(req, res, taskId, query, agentId) {
13597
13651
  isPrimary: query.isPrimary === "true",
13598
13652
  createdBy: auth?.kind === "user" ? auth.userId : undefined
13599
13653
  });
13654
+ const mimeType = contentType.split(";")[0].trim().toLowerCase();
13655
+ if (isDraft && mimeType.startsWith("audio/")) {
13656
+ await appendAudioTranscript(taskId, query.name, mimeType, body);
13657
+ }
13600
13658
  uploadTaskFileRoute.respond(res, 201, attachment);
13601
13659
  return true;
13602
13660
  }
13661
+ async function appendAudioTranscript(taskId, name, mimeType, body) {
13662
+ try {
13663
+ const notes = await transcribeAudioNotes([{ key: name, name, data: new Blob([body], { type: mimeType }), mimeType }], "[fs]");
13664
+ const note = notes.get(name);
13665
+ if (!note)
13666
+ return;
13667
+ if (!await appendDraftTaskText(taskId, `[File: ${name} (${mimeType})]
13668
+ ${note}`)) {
13669
+ console.warn(`[fs] task ${taskId} left draft before the transcript of ${name} was added`);
13670
+ }
13671
+ } catch (error) {
13672
+ console.warn(scrubSecrets(`[fs] could not add the transcript of ${name} to task ${taskId}: ${error instanceof Error ? error.message : String(error)}`));
13673
+ }
13674
+ }
13603
13675
  async function sendDownload(res, attachment) {
13604
13676
  const provider = getFileStorageProvider2();
13605
13677
  if (backingProviderId(attachment) !== provider.id) {
@@ -13659,23 +13731,6 @@ async function sendDelete(res, attachment) {
13659
13731
  res.end();
13660
13732
  return true;
13661
13733
  }
13662
- function scopeFromAttachment(attachment) {
13663
- const storedKey = attachment.providerKey ?? attachment.path ?? "";
13664
- return {
13665
- taskId: attachment.taskId,
13666
- name: attachment.name,
13667
- key: storedKey.trim() ? storedKey : undefined,
13668
- orgId: attachment.orgId,
13669
- driveId: attachment.driveId
13670
- };
13671
- }
13672
- function backingProviderId(attachment) {
13673
- if (attachment.kind === "agent-fs")
13674
- return attachment.providerId ?? "agent-fs";
13675
- if (attachment.providerId === "local-fs")
13676
- return "local-fs";
13677
- return null;
13678
- }
13679
13734
  async function findAttachment(req, myAgentId, taskId, attachmentId, res) {
13680
13735
  if (!await getTaskById2(taskId)) {
13681
13736
  notFound(res, "Task not found");
@@ -16433,6 +16488,29 @@ init_db3();
16433
16488
  init_env_flag();
16434
16489
  init_request_auth_context();
16435
16490
  init_task_tool_manifest();
16491
+
16492
+ // src/http/mcp-body.ts
16493
+ var MCP_BODY_TOO_LARGE = Symbol("mcp-body-too-large");
16494
+ async function readMcpBody(req, res) {
16495
+ try {
16496
+ return await parseBody(req, MCP_MAX_REQUEST_BODY_BYTES);
16497
+ } catch (error) {
16498
+ if (!(error instanceof RequestBodyTooLargeError))
16499
+ throw error;
16500
+ res.writeHead(413, { "Content-Type": "application/json", Connection: "close" });
16501
+ res.end(JSON.stringify({
16502
+ jsonrpc: "2.0",
16503
+ error: {
16504
+ code: -32000,
16505
+ message: `Payload Too Large: Request body must not exceed ${MCP_MAX_REQUEST_BODY_BYTES} bytes`
16506
+ },
16507
+ id: null
16508
+ }));
16509
+ return MCP_BODY_TOO_LARGE;
16510
+ }
16511
+ }
16512
+
16513
+ // src/http/mcp.ts
16436
16514
  var DEFAULT_MCP_TRANSPORT_IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000;
16437
16515
  function respondNoMcpSession(res, sessionId) {
16438
16516
  const notFound = Boolean(sessionId);
@@ -16532,17 +16610,16 @@ async function handleMcp(req, res, transports, sessionActivity = {}, sessionAgen
16532
16610
  if (agentMismatch)
16533
16611
  return true;
16534
16612
  if (req.method === "POST") {
16535
- const chunks = [];
16536
- for await (const chunk of req) {
16537
- chunks.push(chunk);
16538
- }
16539
- const body = JSON.parse(Buffer.concat(chunks).toString());
16540
- if (!sessionId && body.method === "server/discover") {
16613
+ const body = await readMcpBody(req, res);
16614
+ if (body === MCP_BODY_TOO_LARGE)
16615
+ return true;
16616
+ const rpc = body;
16617
+ if (!sessionId && rpc?.method === "server/discover") {
16541
16618
  res.writeHead(200, { "Content-Type": "application/json" });
16542
16619
  res.end(JSON.stringify({
16543
16620
  jsonrpc: "2.0",
16544
16621
  error: { code: -32601, message: "Method not found" },
16545
- id: body.id ?? null
16622
+ id: rpc.id ?? null
16546
16623
  }));
16547
16624
  return true;
16548
16625
  }
@@ -17879,7 +17956,8 @@ function createUserServer(user) {
17879
17956
  }, {
17880
17957
  capabilities: {
17881
17958
  logging: {}
17882
- }
17959
+ },
17960
+ maxToolInputElements: MCP_MAX_TOOL_INPUT_ELEMENTS
17883
17961
  });
17884
17962
  const registerTool = createToolRegistrar(server);
17885
17963
  const sendTaskConfig = {
@@ -17997,11 +18075,9 @@ async function handleMcpUser(req, res, transports, sessionUsers, sessionActivity
17997
18075
  return unauthorized2(res);
17998
18076
  }
17999
18077
  if (req.method === "POST") {
18000
- const chunks = [];
18001
- for await (const chunk of req) {
18002
- chunks.push(chunk);
18003
- }
18004
- const body = JSON.parse(Buffer.concat(chunks).toString());
18078
+ const body = await readMcpBody(req, res);
18079
+ if (body === MCP_BODY_TOO_LARGE)
18080
+ return true;
18005
18081
  let transport;
18006
18082
  if (sessionId && transports[sessionId]) {
18007
18083
  transport = transports[sessionId];
@@ -18143,6 +18219,9 @@ async function hasRetrievalForTask(taskId, memoryId) {
18143
18219
  return row != null;
18144
18220
  }
18145
18221
 
18222
+ // src/http/memory.ts
18223
+ init_search_candidates();
18224
+
18146
18225
  // src/be/memory/usefulness-stats.ts
18147
18226
  init_db3();
18148
18227
  async function getUsefulnessStats(options = {}) {
@@ -18983,7 +19062,7 @@ async function handleMemory(req, res, pathSegments, callerAgentId) {
18983
19062
  const store = getMemoryStore2();
18984
19063
  const queryEmbedding = await provider.embed(query);
18985
19064
  const candidateLimit = Math.min(limit, 20) * CANDIDATE_SET_MULTIPLIER;
18986
- const candidates = await store.search(queryEmbedding ?? new Float32Array(0), myAgentId, {
19065
+ const candidates = await searchMemoryCandidates(store, queryEmbedding ?? new Float32Array(0), myAgentId, {
18987
19066
  scope,
18988
19067
  limit: candidateLimit,
18989
19068
  source,
@@ -19064,7 +19143,7 @@ async function handleMemory(req, res, pathSegments, callerAgentId) {
19064
19143
  const provider = getEmbeddingProvider2();
19065
19144
  const queryEmbedding = await provider.embed(query.trim());
19066
19145
  const candidateLimit = Math.min(4096, Math.max(offset + pageLimit, pageLimit) * CANDIDATE_SET_MULTIPLIER);
19067
- let candidates = await store.search(queryEmbedding ?? new Float32Array(0), viewer.agentId, {
19146
+ let candidates = await searchMemoryCandidates(store, queryEmbedding ?? new Float32Array(0), viewer.agentId, {
19068
19147
  scope,
19069
19148
  limit: candidateLimit,
19070
19149
  isLead: viewer.seesAll,
@@ -21999,6 +22078,35 @@ route({
21999
22078
  auth: { apiKey: false }
22000
22079
  });
22001
22080
  var PROXY_PREFIX = "/@swarm/api/";
22081
+ var PAGE_ID_REQUEST_HEADER = "x-swarm-page-id";
22082
+ var PAGE_PATH_RE = /^\/p\/([^/.]+)/;
22083
+ function requestedPageId(req) {
22084
+ const raw = req.headers[PAGE_ID_REQUEST_HEADER];
22085
+ const fromHeader = Array.isArray(raw) ? raw[0] : raw;
22086
+ if (fromHeader !== undefined)
22087
+ return isCookieSafePageId(fromHeader) ? fromHeader : null;
22088
+ const referer = req.headers.referer;
22089
+ if (!referer)
22090
+ return;
22091
+ let refererUrl;
22092
+ try {
22093
+ refererUrl = new URL(referer);
22094
+ } catch {
22095
+ return;
22096
+ }
22097
+ if (refererUrl.host !== req.headers.host)
22098
+ return;
22099
+ const match = PAGE_PATH_RE.exec(refererUrl.pathname);
22100
+ if (!match)
22101
+ return;
22102
+ let fromReferer;
22103
+ try {
22104
+ fromReferer = decodeURIComponent(match[1]);
22105
+ } catch {
22106
+ return;
22107
+ }
22108
+ return isCookieSafePageId(fromReferer) ? fromReferer : undefined;
22109
+ }
22002
22110
  function isSafeProxySuffix(suffix) {
22003
22111
  if (suffix.length === 0)
22004
22112
  return true;
@@ -22037,11 +22145,13 @@ async function handlePageProxy(req, res) {
22037
22145
  jsonError(res, "not found", 404);
22038
22146
  return true;
22039
22147
  }
22040
- const payload = await extractAndVerifyCookie(req);
22041
- if (!payload) {
22148
+ const claimedPageId = requestedPageId(req);
22149
+ const session = claimedPageId === undefined ? await extractLegacyPageSession(req) : claimedPageId === null ? null : await extractPageSession(req, claimedPageId);
22150
+ if (!session) {
22042
22151
  jsonError(res, "no page session", 401);
22043
22152
  return true;
22044
22153
  }
22154
+ const { payload } = session;
22045
22155
  const page = await getPage2(payload.pageId);
22046
22156
  if (!page) {
22047
22157
  jsonError(res, "page session no longer valid", 401);
@@ -22059,7 +22169,7 @@ async function handlePageProxy(req, res) {
22059
22169
  const headers = {
22060
22170
  Authorization: `Bearer ${apiKey}`,
22061
22171
  "X-Page-Id": page.id,
22062
- "X-Page-Session": parseCookieHeader(req.headers.cookie, "page_session") ?? ""
22172
+ "X-Page-Session": session.token
22063
22173
  };
22064
22174
  const reqContentType = req.headers["content-type"];
22065
22175
  if (reqContentType) {
@@ -22736,10 +22846,6 @@ var updateTaskTitleRoute = route({
22736
22846
  ungated: "mirrors the pre-RBAC PATCH /api/tasks/{id}/vcs sibling route: bearer auth only"
22737
22847
  }
22738
22848
  });
22739
- async function hasSessionWrite(req, myAgentId, taskId) {
22740
- const access = await sessionAccessFor2(await resolveSessionActor2(req, myAgentId), taskId);
22741
- return hasSessionAccess(access, "write");
22742
- }
22743
22849
  async function canSteerTask(req, myAgentId, task, sessionAccess = "none") {
22744
22850
  const auth = getRequestAuth(req);
22745
22851
  const resource = {
@@ -22919,23 +23025,7 @@ async function handleTasks(req, res, pathSegments, queryParams, myAgentId) {
22919
23025
  }
22920
23026
  const parentTask = parsed.body.parentTaskId ? await getTaskById2(parsed.body.parentTaskId) : null;
22921
23027
  const principal = await resolveTaskWritePrincipal(req, myAgentId);
22922
- if (parentTask && !can({
22923
- principal,
22924
- verb: "task.parent.attach",
22925
- resource: {
22926
- kind: "task",
22927
- taskId: parentTask.id,
22928
- requestedByUserId: parentTask.requestedByUserId,
22929
- creatorAgentId: parentTask.creatorAgentId,
22930
- agentId: parentTask.agentId
22931
- },
22932
- source: "http"
22933
- }).allow) {
22934
- jsonError(res, `You don't have context for parent task ${parentTask.id}.`, 403);
22935
- return true;
22936
- }
22937
- if (parentTask && !await hasSessionWrite(req, myAgentId, parentTask.id)) {
22938
- jsonError(res, SESSION_WRITE_DENIED2, 403);
23028
+ if (parentTask && await denyParentAttach2(req, res, myAgentId, parentTask, principal)) {
22939
23029
  return true;
22940
23030
  }
22941
23031
  if (principal.kind === "agent" && parsed.body.contextKey && parsed.body.contextKey !== parentTask?.contextKey && chatThreadOfContextKey(parsed.body.contextKey) && !can({ principal, verb: "integration.chat.post", resource: { kind: "none" }, source: "http" }).allow) {
@@ -24423,12 +24513,12 @@ ${defuseFeedbackTags(note)}
24423
24513
  }
24424
24514
  const auth = getRequestAuth(req);
24425
24515
  const identity = auth?.kind === "user" ? { uid: auth.userId, name: auth.user.name } : auth?.kind === "operator" ? { name: `guest-${randomUUID4().slice(0, 8)}`, op: true } : undefined;
24426
- const cookie = await issuePageSessionCookie(page.id, {
24516
+ const cookies = await pageSessionSetCookies(req, page.id, {
24427
24517
  dev: isDevRequest(req),
24428
24518
  ...identity
24429
24519
  });
24430
24520
  applyLaunchCors(req, res);
24431
- res.setHeader("Set-Cookie", cookie);
24521
+ res.setHeader("Set-Cookie", cookies);
24432
24522
  res.writeHead(204);
24433
24523
  res.end();
24434
24524
  return true;
@@ -24450,10 +24540,15 @@ class SwarmSDK {
24450
24540
  this.channel = async (name) => (await loadRealtime()).channel(name);
24451
24541
 
24452
24542
  const base = '/@swarm/api';
24543
+ // Each page has its own session cookie. Naming the page tells the proxy
24544
+ // which one to read; the cookie's signed payload must still match it.
24545
+ const pathParts = typeof location === 'undefined' ? [] : location.pathname.split('/');
24546
+ const pageId = pathParts[1] === 'p' && pathParts[2] ? pathParts[2].split('.')[0] : '';
24453
24547
  const call = async (method, path, body) => {
24454
- const init = { method };
24548
+ const init = { method, headers: {} };
24549
+ if (pageId) init.headers['X-Swarm-Page-Id'] = pageId;
24455
24550
  if (body !== undefined) {
24456
- init.headers = { 'Content-Type': 'application/json' };
24551
+ init.headers['Content-Type'] = 'application/json';
24457
24552
  init.body = JSON.stringify(body);
24458
24553
  }
24459
24554
  const res = await fetch(base + path, init);
@@ -25638,7 +25733,7 @@ var FEEDBACK_OVERLAY_JS = `
25638
25733
  return fetch('/@swarm/api/pages/' + encodeURIComponent(pageId) + '/feedback', {
25639
25734
  method: 'POST',
25640
25735
  credentials: 'same-origin',
25641
- headers: { 'Content-Type': 'application/json' },
25736
+ headers: { 'Content-Type': 'application/json', 'X-Swarm-Page-Id': pageId },
25642
25737
  body: JSON.stringify(payload),
25643
25738
  }).then(function (res) {
25644
25739
  return res.text().then(function (text) {
@@ -26024,23 +26119,8 @@ function buildCsp() {
26024
26119
  function isAccessible(page, cookiePayload) {
26025
26120
  if (page.authMode === "public")
26026
26121
  return { ok: true };
26027
- if (cookiePayload) {
26028
- if (cookiePayload.pageId === page.id)
26029
- return { ok: true };
26030
- if (page.authMode === "password") {
26031
- return {
26032
- ok: false,
26033
- status: 401,
26034
- reason: "password required",
26035
- needsPassword: true
26036
- };
26037
- }
26038
- return {
26039
- ok: false,
26040
- status: 403,
26041
- reason: "page-session cookie scoped to a different page id"
26042
- };
26043
- }
26122
+ if (cookiePayload?.pageId === page.id)
26123
+ return { ok: true };
26044
26124
  if (page.authMode === "authed") {
26045
26125
  return {
26046
26126
  ok: false,
@@ -26115,9 +26195,9 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26115
26195
  res.end(JSON.stringify({ error: "Page not found" }));
26116
26196
  return true;
26117
26197
  }
26118
- const cookiePayload = await extractAndVerifyCookie(req);
26198
+ const cookiePayload = await extractAndVerifyCookie(req, page.id);
26119
26199
  const access = isAccessible(page, cookiePayload);
26120
- let inlineSetCookie = "";
26200
+ let inlineSetCookie = [];
26121
26201
  if (!access.ok) {
26122
26202
  if (access.needsPassword) {
26123
26203
  const candidate = extractPasswordCandidate(req, queryParams);
@@ -26129,7 +26209,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26129
26209
  matched = false;
26130
26210
  }
26131
26211
  if (matched) {
26132
- inlineSetCookie = await issuePageSessionCookie(page.id, {
26212
+ inlineSetCookie = await pageSessionSetCookies(req, page.id, {
26133
26213
  dev: isLocalhostRequest(req)
26134
26214
  });
26135
26215
  } else {
@@ -26156,7 +26236,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26156
26236
  }
26157
26237
  if (isJsonRoute) {
26158
26238
  res.setHeader("Cache-Control", "no-store");
26159
- if (inlineSetCookie)
26239
+ if (inlineSetCookie.length > 0)
26160
26240
  res.setHeader("Set-Cookie", inlineSetCookie);
26161
26241
  publicPageJsonRoute.respond(res, 200, {
26162
26242
  id: page.id,
@@ -26177,7 +26257,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26177
26257
  "X-Content-Type-Options": "nosniff",
26178
26258
  "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox"
26179
26259
  };
26180
- if (inlineSetCookie)
26260
+ if (inlineSetCookie.length > 0)
26181
26261
  headers["Set-Cookie"] = inlineSetCookie;
26182
26262
  res.writeHead(200, headers);
26183
26263
  res.end(page.body);
@@ -26193,15 +26273,17 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26193
26273
  "Content-Security-Policy": buildCsp(),
26194
26274
  "X-Content-Type-Options": "nosniff"
26195
26275
  };
26196
- if (inlineSetCookie)
26276
+ if (inlineSetCookie.length > 0)
26197
26277
  headers["Set-Cookie"] = inlineSetCookie;
26198
26278
  res.writeHead(200, headers);
26199
26279
  res.end(printableJsonPageHtml(page));
26200
26280
  await bumpViewCount(page.id);
26201
26281
  return true;
26202
26282
  }
26203
- const headers = { Location: `${getAppBaseUrl2()}/pages/${page.id}` };
26204
- if (inlineSetCookie)
26283
+ const headers = {
26284
+ Location: `${getAppBaseUrl2()}/pages/${page.id}`
26285
+ };
26286
+ if (inlineSetCookie.length > 0)
26205
26287
  headers["Set-Cookie"] = inlineSetCookie;
26206
26288
  res.writeHead(302, headers);
26207
26289
  res.end();
@@ -26215,7 +26297,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26215
26297
  "Content-Security-Policy": buildCsp(),
26216
26298
  "X-Content-Type-Options": "nosniff"
26217
26299
  };
26218
- if (inlineSetCookie)
26300
+ if (inlineSetCookie.length > 0)
26219
26301
  headers["Set-Cookie"] = inlineSetCookie;
26220
26302
  res.writeHead(200, headers);
26221
26303
  res.end(html);
@@ -31182,6 +31264,7 @@ var agentTaskBodySchema = object2({
31182
31264
  routingNote: string2().max(200).optional(),
31183
31265
  tags: array2(string2()).optional(),
31184
31266
  priority: number2().int().min(0).max(100).optional(),
31267
+ dependsOn: array2(uuid()).optional(),
31185
31268
  offerMode: boolean().optional(),
31186
31269
  dir: string2().min(1).optional(),
31187
31270
  vcsRepo: string2().min(1).optional(),
@@ -31662,6 +31745,7 @@ async function handleScriptRuns(req, res, pathSegments, queryParams, agentId) {
31662
31745
  routingSource: parsed.body.routingReason ? "declared" : parsed.body.agentId ? "engine_default" : undefined,
31663
31746
  tags: parsed.body.tags,
31664
31747
  priority: parsed.body.priority,
31748
+ dependsOn: parsed.body.dependsOn,
31665
31749
  offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
31666
31750
  taskType: "script-run-step",
31667
31751
  source: "mcp",
@@ -33147,6 +33231,7 @@ async function handleSessionData(req, res, pathSegments, queryParams, myAgentId)
33147
33231
  // src/http/sessions.ts
33148
33232
  init_zod();
33149
33233
  init_db3();
33234
+ init_rbac_roles();
33150
33235
  init_steering();
33151
33236
  init_users();
33152
33237
  init_rbac();
@@ -33203,7 +33288,7 @@ var mintSessionTokenRoute = route({
33203
33288
  none: "user tokens cannot mint: the handler admits only the operator key or the task's own runtime"
33204
33289
  },
33205
33290
  summary: "Mint an ephemeral task or runtime token",
33206
- description: "Operators can mint task or runtime tokens. Runtime tokens can mint task tokens for their own assigned tasks. Child tokens expire with their parent runtime.",
33291
+ description: "Operators can mint task or runtime tokens. Runtime and agent tokens can mint task tokens for their own assigned tasks. A child token never outlives its parent and is revoked with it.",
33207
33292
  tags: ["Sessions"],
33208
33293
  body: object2({
33209
33294
  agentId: string2().min(1),
@@ -33229,7 +33314,7 @@ var mintSessionTokenRoute = route({
33229
33314
  },
33230
33315
  auth: { apiKey: true },
33231
33316
  rbac: {
33232
- ungated: "operator or runtime token minting for its own assigned task, enforced in the handler"
33317
+ ungated: "operator, or a runtime or agent token minting for its own assigned task, enforced in the handler"
33233
33318
  }
33234
33319
  });
33235
33320
  var revokeSessionTokenRoute = route({
@@ -33237,7 +33322,7 @@ var revokeSessionTokenRoute = route({
33237
33322
  path: "/api/sessions/tokens/{tokenId}",
33238
33323
  pattern: ["api", "sessions", "tokens", null],
33239
33324
  summary: "Revoke an ephemeral session token",
33240
- description: "Operators can revoke any ephemeral token. With runtime pools enabled, runtime tokens can revoke their own child task tokens. Repeated revocation has no effect.",
33325
+ description: "Operators can revoke any ephemeral token. Agent tokens, and runtime tokens when runtime pools are enabled, can revoke their own child task tokens. Repeated revocation has no effect.",
33241
33326
  tags: ["Sessions"],
33242
33327
  params: object2({ tokenId: string2() }),
33243
33328
  responses: {
@@ -33247,9 +33332,188 @@ var revokeSessionTokenRoute = route({
33247
33332
  },
33248
33333
  auth: { apiKey: true },
33249
33334
  rbac: {
33250
- ungated: "operator or runtime token revoking its own child, enforced in the handler"
33335
+ ungated: "operator, or a runtime or agent token revoking its own child, enforced in the handler"
33251
33336
  }
33252
33337
  });
33338
+ var AGENT_TOKEN_ROTATE_GRACE_SEC = 300;
33339
+ var AgentTokenSchema = object2({
33340
+ agentId: string2(),
33341
+ tokenId: string2(),
33342
+ plaintext: string2(),
33343
+ expiresAt: string2().nullable().describe("null when the token does not expire")
33344
+ });
33345
+ var agentTokenTtl = number2().int().positive().optional().describe("Token lifetime in ms. Omit for the default (no expiry).");
33346
+ var mintAgentTokenRoute = route({
33347
+ method: "post",
33348
+ path: "/api/agents/{id}/token",
33349
+ pattern: ["api", "agents", null, "token"],
33350
+ operationId: "agents_mintWorkerToken",
33351
+ summary: "Mint an agent-bound worker token",
33352
+ description: "Returns the token once. A worker runs on it in place of the swarm API key and acts only as this agent. Creates the agent when it does not exist and `name` is given. Fails with 409 when the agent already holds an active token.",
33353
+ tags: ["Sessions"],
33354
+ params: object2({ id: string2().min(1) }),
33355
+ body: object2({ name: string2().min(1).optional(), ttlMs: agentTokenTtl }).strict().optional(),
33356
+ responses: {
33357
+ 200: {
33358
+ description: "Minted worker token",
33359
+ schema: AgentTokenSchema.extend({ agentCreated: boolean() })
33360
+ },
33361
+ 403: { description: "Operator or user credentials required" },
33362
+ 404: { description: "Agent not found and no name given" },
33363
+ 409: { description: "Agent already holds an active token" }
33364
+ },
33365
+ rbac: { permission: "agent.token.manage" }
33366
+ });
33367
+ var rotateAgentTokenRoute = route({
33368
+ method: "post",
33369
+ path: "/api/agents/{id}/token/rotate",
33370
+ pattern: ["api", "agents", null, "token", "rotate"],
33371
+ operationId: "agents_rotateWorkerToken",
33372
+ summary: "Rotate an agent-bound worker token",
33373
+ description: "Returns a new token once. The previous token keeps working for `graceSec` (default 300), then expires. Use revoke for a token that must stop now.",
33374
+ tags: ["Sessions"],
33375
+ params: object2({ id: string2().min(1) }),
33376
+ body: object2({ graceSec: number2().int().min(0).max(86400).optional(), ttlMs: agentTokenTtl }).strict().optional(),
33377
+ responses: {
33378
+ 200: {
33379
+ description: "New worker token",
33380
+ schema: AgentTokenSchema.extend({ previousExpiresAt: string2() })
33381
+ },
33382
+ 403: { description: "Operator or user credentials required" },
33383
+ 404: { description: "Agent holds no active token" }
33384
+ },
33385
+ rbac: { permission: "agent.token.manage" }
33386
+ });
33387
+ var revokeAgentTokenRoute = route({
33388
+ method: "delete",
33389
+ path: "/api/agents/{id}/token",
33390
+ pattern: ["api", "agents", null, "token"],
33391
+ operationId: "agents_revokeWorkerToken",
33392
+ summary: "Revoke an agent-bound worker token",
33393
+ description: "Revokes every active worker token of the agent and the task tokens they minted. Open realtime connections on them close.",
33394
+ tags: ["Sessions"],
33395
+ params: object2({ id: string2().min(1) }),
33396
+ responses: {
33397
+ 200: {
33398
+ description: "Number of worker tokens revoked (0 when none was active)",
33399
+ schema: object2({ revoked: number2().int() })
33400
+ },
33401
+ 403: { description: "Operator or user credentials required" }
33402
+ },
33403
+ rbac: { permission: "agent.token.manage" }
33404
+ });
33405
+ var AGENT_TOKEN_ROUTES = [mintAgentTokenRoute, rotateAgentTokenRoute, revokeAgentTokenRoute];
33406
+ async function ensureAgentTokenManager(req, res, agentId) {
33407
+ const auth = getRequestAuth(req);
33408
+ if (auth?.kind !== "operator" && auth?.kind !== "user") {
33409
+ jsonError(res, "Worker tokens require operator or user credentials", 403);
33410
+ return false;
33411
+ }
33412
+ const header = req.headers["x-agent-id"];
33413
+ const decision = can({
33414
+ principal: await agentFirstPrincipal(req, Array.isArray(header) ? header[0] : header),
33415
+ verb: "agent.token.manage",
33416
+ resource: { kind: "agent", agentId },
33417
+ source: "http"
33418
+ });
33419
+ if (!decision.allow) {
33420
+ jsonError(res, decision.reason, 403);
33421
+ return false;
33422
+ }
33423
+ if (auth.kind === "user" && isRbacEnabled()) {
33424
+ const grant = await getUserGrant2(auth.userId);
33425
+ if (!grant.grantsAll) {
33426
+ const target = await getAgentById2(agentId);
33427
+ const targetActsOnAny = can({
33428
+ principal: { kind: "agent", agentId, isLead: target?.isLead ?? false },
33429
+ verb: "agent.act.any",
33430
+ resource: { kind: "agent", agentId },
33431
+ source: "http"
33432
+ }).allow;
33433
+ if (targetActsOnAny) {
33434
+ jsonError(res, "A lead agent's worker token requires a grant-all role", 403);
33435
+ return false;
33436
+ }
33437
+ const missing = ["agent.act.any", "config.read.secrets"].filter((verb) => !grant.verbs.has(verb));
33438
+ if (missing.length > 0) {
33439
+ jsonError(res, `Worker tokens also require: ${missing.join(", ")}`, 403);
33440
+ return false;
33441
+ }
33442
+ }
33443
+ }
33444
+ return true;
33445
+ }
33446
+ async function handleAgentTokens(req, res, pathSegments, queryParams) {
33447
+ const matched = AGENT_TOKEN_ROUTES.find((def) => def.match(req.method, pathSegments));
33448
+ if (!matched)
33449
+ return false;
33450
+ if (!await ensureAgentTokenManager(req, res, pathSegments[2]))
33451
+ return true;
33452
+ res.setHeader("Cache-Control", "no-store");
33453
+ if (matched === mintAgentTokenRoute) {
33454
+ const parsed = await mintAgentTokenRoute.parse(req, res, pathSegments, queryParams);
33455
+ if (!parsed)
33456
+ return true;
33457
+ const agentId = parsed.params.id;
33458
+ const result = await getDbClient2().transaction(async () => {
33459
+ let agentCreated = false;
33460
+ if (!await getAgentById2(agentId)) {
33461
+ if (!parsed.body?.name)
33462
+ return { status: 404 };
33463
+ await createAgent2({
33464
+ id: agentId,
33465
+ name: parsed.body.name,
33466
+ isLead: false,
33467
+ status: "offline",
33468
+ capabilities: [],
33469
+ maxTasks: 1
33470
+ });
33471
+ agentCreated = true;
33472
+ }
33473
+ return {
33474
+ status: 200,
33475
+ agentCreated,
33476
+ token: await mintAgentToken(agentId, parsed.body?.ttlMs)
33477
+ };
33478
+ }).catch((error) => {
33479
+ if (error instanceof AgentTokenExistsError)
33480
+ return { status: 409 };
33481
+ throw error;
33482
+ });
33483
+ if (result.status === 404) {
33484
+ jsonError(res, "Agent not found; pass name to create it", 404);
33485
+ return true;
33486
+ }
33487
+ if (result.status === 409) {
33488
+ jsonError(res, "Agent already holds an active worker token; rotate or revoke it", 409);
33489
+ return true;
33490
+ }
33491
+ mintAgentTokenRoute.respond(res, 200, {
33492
+ agentId,
33493
+ ...result.token,
33494
+ agentCreated: result.agentCreated
33495
+ });
33496
+ return true;
33497
+ }
33498
+ if (matched === rotateAgentTokenRoute) {
33499
+ const parsed = await rotateAgentTokenRoute.parse(req, res, pathSegments, queryParams);
33500
+ if (!parsed)
33501
+ return true;
33502
+ const graceSec = parsed.body?.graceSec ?? AGENT_TOKEN_ROTATE_GRACE_SEC;
33503
+ const token = await rotateAgentToken(parsed.params.id, graceSec * 1000, parsed.body?.ttlMs);
33504
+ if (!token) {
33505
+ jsonError(res, "Agent holds no active worker token", 404);
33506
+ return true;
33507
+ }
33508
+ rotateAgentTokenRoute.respond(res, 200, { agentId: parsed.params.id, ...token });
33509
+ return true;
33510
+ }
33511
+ const parsed = await revokeAgentTokenRoute.parse(req, res, pathSegments, queryParams);
33512
+ if (!parsed)
33513
+ return true;
33514
+ revokeAgentTokenRoute.respond(res, 200, { revoked: await revokeAgentTokens(parsed.params.id) });
33515
+ return true;
33516
+ }
33253
33517
  var listSessions = route({
33254
33518
  method: "get",
33255
33519
  path: "/api/sessions",
@@ -33347,9 +33611,11 @@ async function accessForRoot(actor, root) {
33347
33611
  return actor.kind === "bypass" ? "owner" : sessionAccessForRoot(actor.userId, root);
33348
33612
  }
33349
33613
  async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33614
+ if (await handleAgentTokens(req, res, pathSegments, queryParams))
33615
+ return true;
33350
33616
  if (mintSessionTokenRoute.match(req.method, pathSegments)) {
33351
33617
  const auth = getRequestAuth(req);
33352
- if (auth?.kind !== "operator" && !(auth?.kind === "agent" && auth.runtimeId)) {
33618
+ if (auth?.kind !== "operator" && !(auth?.kind === "agent" && auth.tokenKind !== "task")) {
33353
33619
  jsonError(res, "Unauthorized", 401);
33354
33620
  return true;
33355
33621
  }
@@ -33394,7 +33660,7 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33394
33660
  }
33395
33661
  if (revokeSessionTokenRoute.match(req.method, pathSegments)) {
33396
33662
  const auth = getRequestAuth(req);
33397
- if (auth?.kind !== "operator" && !(isRuntimePoolsEnabled() && auth?.kind === "agent" && auth.runtimeId)) {
33663
+ if (auth?.kind !== "operator" && !(auth?.kind === "agent" && (auth.tokenKind === "agent" || auth.tokenKind === "runtime" && isRuntimePoolsEnabled()))) {
33398
33664
  jsonError(res, "Unauthorized", 401);
33399
33665
  return true;
33400
33666
  }
@@ -33403,7 +33669,8 @@ async function handleSessions(req, res, pathSegments, queryParams, myAgentId) {
33403
33669
  return true;
33404
33670
  const revoked = await getDbClient2().transaction(async (client) => {
33405
33671
  if (auth.kind === "agent") {
33406
- const child = await client.get("SELECT id FROM session_tokens WHERE id = ? AND agentId = ? AND parentRuntimeId = ? AND taskId IS NOT NULL", [parsed.params.tokenId, auth.agentId, auth.runtimeId]);
33672
+ const child = await client.get(`SELECT id FROM session_tokens WHERE id = ? AND agentId = ? AND taskId IS NOT NULL
33673
+ AND ${auth.runtimeId ? "parentRuntimeId" : "parentTokenId"} = ?`, [parsed.params.tokenId, auth.agentId, auth.runtimeId ?? auth.tokenId]);
33407
33674
  if (!child)
33408
33675
  return false;
33409
33676
  }
@@ -36431,7 +36698,17 @@ var deleteIdentityRoute = route({
36431
36698
  },
36432
36699
  auth: { apiKey: true }
36433
36700
  });
36434
- var UNMAPPED_KINDS = ["slack", "github", "gitlab", "azure-devops", "linear", "kapso"];
36701
+ var UNMAPPED_KINDS = [
36702
+ "slack",
36703
+ "github",
36704
+ "gitlab",
36705
+ "azure-devops",
36706
+ "linear",
36707
+ "kapso",
36708
+ "telegram",
36709
+ "discord",
36710
+ "teams"
36711
+ ];
36435
36712
  async function collectUnmappedForKind(kind, limit) {
36436
36713
  const namespace = `integration:unmapped:${kind}`;
36437
36714
  const rows = await listKv2(namespace, { limit: Math.min(limit * 2, 1000), offset: 0 });
@@ -39126,7 +39403,7 @@ var httpServer = createHttpServer(async (req, res) => {
39126
39403
  });
39127
39404
  });
39128
39405
  var detachRealtime = attachRealtimeTransport(httpServer);
39129
- var { attachControllerTransport } = await import("./controllers-connect-5rgq2hfk.js");
39406
+ var { attachControllerTransport } = await import("./controllers-connect-9ftpex99.js");
39130
39407
  var detachControllers = attachControllerTransport(httpServer);
39131
39408
  var stopWorkerNudges = startWorkerNudgeListener();
39132
39409
  var stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namespace) => {
@@ -39155,7 +39432,7 @@ async function shutdown() {
39155
39432
  sessionsProcessed: getServerSessionsProcessed()
39156
39433
  });
39157
39434
  if (hasCapability("scheduling")) {
39158
- const { stopScheduler } = await import("./index-wbbs14c5.js");
39435
+ const { stopScheduler } = await import("./index-bgdyvcnv.js");
39159
39436
  stopScheduler();
39160
39437
  }
39161
39438
  stopHeartbeat2();
@@ -39167,7 +39444,7 @@ async function shutdown() {
39167
39444
  await stopSlackApp2();
39168
39445
  await stopChatSdk();
39169
39446
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
39170
- const { stopOAuthKeepalive } = await import("./keepalive-y5t1my7m.js");
39447
+ const { stopOAuthKeepalive } = await import("./keepalive-7yrng7px.js");
39171
39448
  await stopOAuthKeepalive();
39172
39449
  }
39173
39450
  stopOAuthPendingGc();
@@ -39247,21 +39524,21 @@ try {
39247
39524
  console.warn("[startup] CAPABILITIES upgrade seed failed (non-fatal):", err);
39248
39525
  }
39249
39526
  try {
39250
- const { ensurePricingSeeded } = await import("./boot-seeds-37sp626b.js");
39527
+ const { ensurePricingSeeded } = await import("./boot-seeds-j2273s54.js");
39251
39528
  ensurePricingSeeded();
39252
- const { startPricingRefreshLoop } = await import("./pricing-refresh-ygd362ep.js");
39529
+ const { startPricingRefreshLoop } = await import("./pricing-refresh-d2b5my0y.js");
39253
39530
  startPricingRefreshLoop();
39254
39531
  } catch (err) {
39255
39532
  console.error("[startup] Failed to seed pricing rows:", err);
39256
39533
  }
39257
39534
  try {
39258
- const { ensureModelProfilesSeeded } = await import("./boot-seeds-37sp626b.js");
39535
+ const { ensureModelProfilesSeeded } = await import("./boot-seeds-j2273s54.js");
39259
39536
  await ensureModelProfilesSeeded();
39260
39537
  } catch (err) {
39261
39538
  console.error("[startup] Failed to seed model profiles:", err);
39262
39539
  }
39263
39540
  try {
39264
- const { ensureRbacSeeded } = await import("./boot-seeds-37sp626b.js");
39541
+ const { ensureRbacSeeded } = await import("./boot-seeds-j2273s54.js");
39265
39542
  ensureRbacSeeded();
39266
39543
  } catch (err) {
39267
39544
  console.error("[startup] Failed to sync RBAC seed rows:", err);
@@ -39269,7 +39546,7 @@ try {
39269
39546
  throw err;
39270
39547
  }
39271
39548
  try {
39272
- const { runAllSeeders } = await import("./index-220tk9bw.js");
39549
+ const { runAllSeeders } = await import("./index-jwxkkex2.js");
39273
39550
  await runAllSeeders({ scriptEmbeddingMode: "skip" });
39274
39551
  } catch (err) {
39275
39552
  console.error("[startup] Failed to seed built-in entities:", err);
@@ -39331,39 +39608,39 @@ httpServer.listen(port, async () => {
39331
39608
  await initWorkflows2();
39332
39609
  await startScriptRunSupervisor(getMcpBaseUrl());
39333
39610
  if (hasCapability("scheduling")) {
39334
- const { startScheduler } = await import("./index-wbbs14c5.js");
39335
- const { getExecutorRegistry } = await import("./index-m0a4ydqj.js");
39611
+ const { startScheduler } = await import("./index-bgdyvcnv.js");
39612
+ const { getExecutorRegistry } = await import("./index-gabbcdn2.js");
39336
39613
  const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
39337
39614
  startScheduler(getExecutorRegistry(), intervalMs, {
39338
39615
  runId: globalState.__runId
39339
39616
  });
39340
39617
  }
39341
39618
  if (!isEnvFlagEnabled("HEARTBEAT_DISABLE", false)) {
39342
- const { startHeartbeat } = await import("./index-d0g18bax.js");
39619
+ const { startHeartbeat } = await import("./index-tfb26zhy.js");
39343
39620
  const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
39344
39621
  startHeartbeat(heartbeatMs);
39345
39622
  }
39346
39623
  startPoolRouting();
39347
39624
  startRuntimeSupervisor();
39348
39625
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
39349
- const { startOAuthKeepalive } = await import("./keepalive-y5t1my7m.js");
39626
+ const { startOAuthKeepalive } = await import("./keepalive-7yrng7px.js");
39350
39627
  startOAuthKeepalive();
39351
39628
  }
39352
39629
  if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
39353
- const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-909jmn12.js");
39630
+ const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-yw3xdbcj.js");
39354
39631
  startOAuthRefreshSweep();
39355
39632
  }
39356
39633
  startOAuthPendingGc();
39357
39634
  await startMemoryGc();
39358
39635
  await startDbRetention();
39359
39636
  startHumanFreeDrain();
39360
- import("./boot-reembed-y3m0ydzj.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
39637
+ import("./boot-reembed-b9c3az3c.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
39361
39638
  console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
39362
39639
  });
39363
- import("./boot-reembed-gvxjs2py.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
39640
+ import("./boot-reembed-qdkvxeem.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
39364
39641
  console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
39365
39642
  });
39366
- import("./boot-scrub-sweep-1j9mk05f.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
39643
+ import("./boot-scrub-sweep-m3wh8eks.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
39367
39644
  console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
39368
39645
  });
39369
39646
  }).on("error", (err) => {