@desplega.ai/agent-swarm 2.4.4 → 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 (221) hide show
  1. package/dist/{acp-adapter-thq04adh.js → acp-adapter-2wdkktv6.js} +1 -1
  2. package/dist/{actions-j6ywh6x5.js → actions-c40ebcm4.js} +18 -18
  3. package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-wb67m95z.js} +2 -2
  4. package/dist/{app-rb7vc02s.js → app-353vre8g.js} +3 -3
  5. package/dist/{assistant-ftta1zjf.js → assistant-sj1njh23.js} +21 -21
  6. package/dist/{boot-reembed-srp6v6af.js → boot-reembed-b9c3az3c.js} +1 -1
  7. package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-qdkvxeem.js} +1 -1
  8. package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
  9. package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-j2273s54.js} +4 -4
  10. package/dist/{claude-adapter-k5ksyres.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-n5a5eqks.js → claude-managed-setup-smrqk5j5.js} +3 -3
  13. package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-38xnz21e.js} +8284 -8267
  14. package/dist/{cli-cynvc9fj.js → cli-018r4jq6.js} +5 -5
  15. package/dist/{cli-9fhkkpj5.js → cli-1f3qzw6y.js} +1 -1
  16. package/dist/{cli-dqb4n629.js → cli-1sp861s4.js} +7 -7
  17. package/dist/{cli-gqvh8d8a.js → cli-1syn2xy1.js} +10 -10
  18. package/dist/{cli-qkhc5aqy.js → cli-37j04k05.js} +11 -11
  19. package/dist/{cli-1d70crsy.js → cli-3a45zcgm.js} +1 -1
  20. package/dist/{cli-t7cyef5g.js → cli-43x64hv3.js} +4 -4
  21. package/dist/{cli-hj3debgw.js → cli-4zfqgjc5.js} +2 -2
  22. package/dist/{cli-55qmbp6z.js → cli-7c9qs530.js} +1 -1
  23. package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
  24. package/dist/{cli-pccfmtb5.js → cli-94gwyg7m.js} +7 -7
  25. package/dist/{cli-51y09c6p.js → cli-9hstj09h.js} +5 -5
  26. package/dist/{cli-zrwpctp4.js → cli-9yqgm9nd.js} +22 -22
  27. package/dist/{cli-esxerzg7.js → cli-a92r1127.js} +1 -1
  28. package/dist/{cli-b8k7epj1.js → cli-be2161h1.js} +7 -7
  29. package/dist/{cli-xc27fwzg.js → cli-bnes4z6b.js} +2 -2
  30. package/dist/{cli-zrk6jxv8.js → cli-cxxe61f6.js} +1 -1
  31. package/dist/{cli-amyhm3gj.js → cli-dk44dxec.js} +1 -1
  32. package/dist/{cli-nc35pxb7.js → cli-eww120g8.js} +1 -0
  33. package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
  34. package/dist/{cli-82d3nkc3.js → cli-f0g3pgvf.js} +1 -1
  35. package/dist/{cli-1ephgwnq.js → cli-ftc1g5tj.js} +2 -2
  36. package/dist/{cli-2v045xyf.js → cli-fv5hepyy.js} +3 -3
  37. package/dist/{cli-bnz0r3kc.js → cli-g35m2xwm.js} +4 -4
  38. package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
  39. package/dist/{cli-w6nadkh4.js → cli-j99azg3g.js} +3 -3
  40. package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
  41. package/dist/{cli-4dg55wsk.js → cli-kpw04va0.js} +1 -1
  42. package/dist/{cli-3cgngyd8.js → cli-ms85309d.js} +33 -15
  43. package/dist/{cli-v3yr2gbg.js → cli-n72rg53p.js} +1471 -635
  44. package/dist/{cli-37yb7307.js → cli-p14m2cnp.js} +1 -1
  45. package/dist/{cli-2zqpkvtt.js → cli-qfxc84gh.js} +1 -1
  46. package/dist/{cli-44159k28.js → cli-qwhy2qma.js} +1 -1
  47. package/dist/{cli-2y91wnbh.js → cli-st8aw1m6.js} +221 -9
  48. package/dist/{cli-vq97g1jq.js → cli-tw4p90jb.js} +92 -192
  49. package/dist/{cli-te6b5wvg.js → cli-v0yrfmdh.js} +1 -1
  50. package/dist/{cli-xj0re5tm.js → cli-v8fjakwf.js} +1 -1
  51. package/dist/{cli-p20r8w42.js → cli-wt0jf9hm.js} +215 -80
  52. package/dist/{cli-q6r7t299.js → cli-xpa64rek.js} +33 -11
  53. package/dist/{cli-vdm20wej.js → cli-z7d29rw9.js} +2 -2
  54. package/dist/cli.js +19 -19
  55. package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tn6k5mkg.js} +1 -1
  56. package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-7vjt7jbw.js} +1 -1
  57. package/dist/{commands-pcymaeg9.js → commands-t7rz9146.js} +2 -2
  58. package/dist/{controller-fpjyk46r.js → controller-m2jasma5.js} +2 -2
  59. package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-9ftpex99.js} +19 -19
  60. package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-mjv79e4z.js} +1 -1
  61. package/dist/{db-xsjg9jqq.js → db-6sfrn9g3.js} +1 -1
  62. package/dist/{db-sgd12bt9.js → db-rn673r06.js} +3 -3
  63. package/dist/{dispatcher-x0jfw2c9.js → dispatcher-c155xz0p.js} +17 -17
  64. package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-3mmszc1x.js} +1 -1
  65. package/dist/{external-channel-8njj4h3b.js → external-channel-35k6tkv8.js} +2 -2
  66. package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-tfrq7tg1.js} +2 -2
  67. package/dist/handlers-7gw64dxw.js +39 -0
  68. package/dist/{heartbeat-wd03zhn0.js → heartbeat-dyg4npkp.js} +20 -20
  69. package/dist/{hook-f0vghj42.js → hook-drspbwpe.js} +1 -1
  70. package/dist/{hook-xzj2ts2p.js → hook-x26b31bg.js} +1 -1
  71. package/dist/{http-4hwxv62p.js → http-pk972xyw.js} +355 -216
  72. package/dist/{index-ww2vznkp.js → index-arytgj5q.js} +1 -1
  73. package/dist/{index-hg0x2cdv.js → index-bgdyvcnv.js} +17 -17
  74. package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
  75. package/dist/{index-gdh8jrdm.js → index-gabbcdn2.js} +17 -17
  76. package/dist/{index-97q4xn03.js → index-jwxkkex2.js} +129 -75
  77. package/dist/index-tfb26zhy.js +41 -0
  78. package/dist/{keepalive-hrh7xnr9.js → keepalive-7yrng7px.js} +3 -3
  79. package/dist/{lead-ee8vazz2.js → lead-7e02z839.js} +29 -29
  80. package/dist/{maintenance-e8wnwnqx.js → maintenance-e6cemdqt.js} +1 -1
  81. package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
  82. package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
  83. package/dist/{onboard-dtwcq603.js → onboard-0a9y5yrc.js} +2 -2
  84. package/dist/{otel-impl-w3fbhpn0.js → otel-impl-trjpkfb7.js} +1 -1
  85. package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-d2b5my0y.js} +3 -3
  86. package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-hey3yv9d.js} +2 -2
  87. package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-nxh5ahwk.js} +1 -1
  88. package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
  89. package/dist/{render-v2-n16n0phe.js → render-v2-rr3p2atg.js} +3 -3
  90. package/dist/{resume-z0fgjc38.js → resume-2efkf1s1.js} +5 -5
  91. package/dist/{session-access-cv3mq6mc.js → session-access-s2mttxpw.js} +4 -4
  92. package/dist/{setup-mafhj3yt.js → setup-vypma5yt.js} +1 -1
  93. package/dist/{work-objects-8b86mgjp.js → work-objects-rvhckdkp.js} +20 -20
  94. package/dist/{worker-16s75hm5.js → worker-5snytpbd.js} +29 -29
  95. package/openapi.json +9 -2
  96. package/package.json +7 -7
  97. package/src/apps/catalog.generated.json +206 -4
  98. package/src/apps/page-validator.ts +13 -1
  99. package/src/be/chunking.ts +42 -9
  100. package/src/be/db/tasks/draft-text.ts +15 -0
  101. package/src/be/db/tasks/read.ts +14 -1
  102. package/src/be/db.ts +5 -1
  103. package/src/be/memory/index-content.ts +36 -17
  104. package/src/be/memory/providers/openai-embedding.ts +115 -61
  105. package/src/be/memory/providers/sqlite-store.ts +69 -23
  106. package/src/be/memory/reranker.ts +31 -1
  107. package/src/be/memory/search-candidates.ts +47 -0
  108. package/src/be/memory/types.ts +14 -0
  109. package/src/be/scripts/typecheck.ts +2 -0
  110. package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
  111. package/src/be/seed-scripts/index.ts +2 -2
  112. package/src/be/swarm-config-guard.ts +24 -1
  113. package/src/be/task-attachment-store.ts +30 -0
  114. package/src/extensions/catalog.generated.json +4 -4
  115. package/src/extensions/contract-types.generated.ts +1 -1
  116. package/src/extensions/contract.ts +7 -0
  117. package/src/fs/provider.ts +13 -1
  118. package/src/heartbeat/heartbeat.ts +31 -11
  119. package/src/http/fs.ts +54 -34
  120. package/src/http/mcp-body.ts +37 -0
  121. package/src/http/mcp-user.ts +3 -5
  122. package/src/http/mcp.ts +6 -7
  123. package/src/http/memory.ts +26 -15
  124. package/src/http/page-proxy.ts +61 -5
  125. package/src/http/pages-public.ts +27 -44
  126. package/src/http/pages.ts +4 -4
  127. package/src/http/script-runs.ts +2 -0
  128. package/src/http/users.ts +12 -1
  129. package/src/integrations/chat-sdk/adapters.ts +7 -3
  130. package/src/integrations/chat-sdk/attachments.ts +90 -12
  131. package/src/integrations/chat-sdk/facts.ts +120 -0
  132. package/src/integrations/chat-sdk/inbound.ts +156 -8
  133. package/src/integrations/chat-sdk/limits.ts +24 -0
  134. package/src/integrations/chat-sdk/outbound-files.ts +186 -0
  135. package/src/integrations/chat-sdk/outbound.ts +234 -4
  136. package/src/integrations/chat-sdk/relay.ts +4 -1
  137. package/src/integrations/chat-sdk/router.ts +28 -2
  138. package/src/integrations/chat-sdk/steering.ts +9 -0
  139. package/src/integrations/chat-sdk/templates.ts +14 -0
  140. package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
  141. package/src/mcp-limits.ts +20 -0
  142. package/src/pages/browser-sdk.ts +8 -3
  143. package/src/pages/feedback-overlay.ts +1 -1
  144. package/src/providers/amp-adapter.ts +1 -1
  145. package/src/providers/codex-adapter.ts +106 -119
  146. package/src/providers/codex-agents-md.ts +35 -229
  147. package/src/providers/cursor-adapter.ts +1 -1
  148. package/src/realtime/transport.ts +1 -2
  149. package/src/script-workflows/workflow-ctx.ts +1 -0
  150. package/src/scripts-runtime/types/stdlib.d.ts +2 -0
  151. package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
  152. package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
  153. package/src/server-user.ts +2 -0
  154. package/src/server.ts +2 -0
  155. package/src/slack/assistant.ts +2 -2
  156. package/src/slack/files.ts +2 -0
  157. package/src/slack/handlers.ts +4 -4
  158. package/src/slack/inbound-files.ts +39 -6
  159. package/src/slack/thread-buffer.ts +2 -1
  160. package/src/tasks/task-terminal-effects.ts +21 -17
  161. package/src/tests/apps-card-grid.test.ts +197 -0
  162. package/src/tests/apps-catalog-additions.test.ts +120 -0
  163. package/src/tests/apps-tree.test.ts +129 -0
  164. package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
  165. package/src/tests/chat-sdk-config.test.ts +11 -0
  166. package/src/tests/chat-sdk-facts.test.ts +23 -0
  167. package/src/tests/chat-sdk-inbound.test.ts +558 -4
  168. package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
  169. package/src/tests/codex-adapter.test.ts +167 -242
  170. package/src/tests/config-api-only-keys.test.ts +9 -0
  171. package/src/tests/cors-allowlist.test.ts +1 -1
  172. package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
  173. package/src/tests/heartbeat-checklist.test.ts +33 -0
  174. package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
  175. package/src/tests/heartbeat-jev-gate.test.ts +242 -1
  176. package/src/tests/kv-page-proxy.test.ts +91 -5
  177. package/src/tests/launch-password-rejection.test.ts +2 -2
  178. package/src/tests/mcp-input-caps.test.ts +294 -0
  179. package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
  180. package/src/tests/memory-embedding-input-cap.test.ts +114 -0
  181. package/src/tests/memory.test.ts +14 -0
  182. package/src/tests/page-guest-session.test.ts +19 -8
  183. package/src/tests/page-proxy-authed.test.ts +1 -1
  184. package/src/tests/page-proxy.test.ts +17 -10
  185. package/src/tests/page-session.test.ts +102 -1
  186. package/src/tests/pages-authed-mode.test.ts +82 -12
  187. package/src/tests/pages-password-mode.test.ts +5 -5
  188. package/src/tests/realtime-transport.test.ts +13 -7
  189. package/src/tests/script-runs-http.test.ts +84 -0
  190. package/src/tests/scripts-http.test.ts +10 -1
  191. package/src/tests/swarm-config-export-to-env.test.ts +22 -3
  192. package/src/tests/task-audio-transcription.test.ts +299 -0
  193. package/src/tests/task-completion-memory-chunking.test.ts +434 -0
  194. package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
  195. package/src/tests/transcription.test.ts +299 -0
  196. package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
  197. package/src/tests/workflow-system-one-decision.test.ts +2 -1
  198. package/src/tools/chat-access.ts +10 -0
  199. package/src/tools/chat-post.ts +31 -11
  200. package/src/tools/chat-reply.ts +32 -10
  201. package/src/tools/memory-search.ts +3 -1
  202. package/src/tools/system-one-decide.ts +17 -4
  203. package/src/types.ts +6 -0
  204. package/src/utils/config-env-export.ts +3 -1
  205. package/src/utils/page-session.ts +133 -20
  206. package/src/utils/transcription.ts +246 -0
  207. package/src/workflows/executors/system-one-decision.ts +28 -7
  208. package/src/workflows/executors/system-one-providers.ts +39 -4
  209. package/templates/extensions/slack-reply-gate/README.md +24 -22
  210. package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
  211. package/templates/extensions/slack-reply-gate/manifest.json +2 -2
  212. package/templates/skills/apps/SKILL.md +28 -6
  213. package/templates/skills/apps/content.md +28 -6
  214. package/templates/skills/chat-sdk/content.md +6 -2
  215. package/templates/skills/code-quality/content.md +2 -1
  216. package/templates/skills/heartbeat-runbook/content.md +5 -5
  217. package/templates/skills/pages/content.md +6 -4
  218. package/templates/skills/swarm-scripts/SKILL.md +2 -2
  219. package/templates/skills/swarm-scripts/content.md +2 -2
  220. package/dist/handlers-0pvee9nw.js +0 -39
  221. package/dist/index-thm0y6a8.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,14 +171,18 @@ 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,
180
187
  requestPrincipal,
181
188
  agentFirstPrincipal,
@@ -205,6 +212,9 @@ import {
205
212
  hydrateOAuthAppFromPreset,
206
213
  scopedResourceScopeIdSchema,
207
214
  resolveScopedResourceId,
215
+ MemoryKeyError,
216
+ refreshLinks,
217
+ indexMemoryContent,
208
218
  dedupeMemoryDocumentIds,
209
219
  recordMemoryAccesses,
210
220
  recordRetrievals,
@@ -221,12 +231,9 @@ import {
221
231
  reservedRoomKeyError,
222
232
  updateMcpServerChecked,
223
233
  updateTouchesStdioExecution,
224
- MemoryKeyError,
225
- refreshLinks,
226
234
  canReadMemory,
227
235
  getLinksForMemory,
228
236
  expandCandidatesWithGraph,
229
- indexMemoryContent,
230
237
  isApiDraining,
231
238
  drainApi,
232
239
  markKapsoMessageRead,
@@ -283,7 +290,7 @@ import {
283
290
  hasCapability,
284
291
  getEnabledCapabilities,
285
292
  createServer
286
- } from "./cli-v3yr2gbg.js";
293
+ } from "./cli-n72rg53p.js";
287
294
  import {
288
295
  chatThreadOfContextKey,
289
296
  agentContextKey,
@@ -480,6 +487,8 @@ import {
480
487
  init_key_paths,
481
488
  rerank,
482
489
  init_reranker,
490
+ searchMemoryCandidates,
491
+ init_search_candidates,
483
492
  getEmbeddingProvider2,
484
493
  getMemoryStore2,
485
494
  init_memory,
@@ -720,26 +729,26 @@ import {
720
729
  countScriptRunJournalSteps2,
721
730
  countScriptRunJournalAgentTaskSteps2,
722
731
  init_db3
723
- } from "./cli-p20r8w42.js";
732
+ } from "./cli-wt0jf9hm.js";
724
733
  import {
725
734
  getUserGrant2,
726
735
  init_rbac_roles
727
- } from "./cli-4dg55wsk.js";
728
- import"./cli-t7cyef5g.js";
736
+ } from "./cli-kpw04va0.js";
737
+ import"./cli-43x64hv3.js";
729
738
  import {
730
739
  normalizeModelKey
731
- } from "./cli-amyhm3gj.js";
740
+ } from "./cli-dk44dxec.js";
732
741
  import {
733
742
  upsertModelCatalogOverlay,
734
743
  deleteModelCatalogOverlay,
735
744
  loadModelsCatalog,
736
745
  requestModelCatalogRefresh2
737
- } from "./cli-xc27fwzg.js";
746
+ } from "./cli-bnes4z6b.js";
738
747
  import {
739
748
  AMP_ESTIMATE_MODELS,
740
749
  ampBillsCacheWrites,
741
750
  resolveAmpModel
742
- } from "./cli-2zqpkvtt.js";
751
+ } from "./cli-qfxc84gh.js";
743
752
  import {
744
753
  automationIntegrationFixUrl,
745
754
  preflightAutomation,
@@ -751,7 +760,7 @@ import {
751
760
  cancelWorkflowRun2,
752
761
  TriggerSchemaError,
753
762
  startWorkflowExecution2
754
- } from "./cli-51y09c6p.js";
763
+ } from "./cli-9hstj09h.js";
755
764
  import {
756
765
  require_dist2,
757
766
  isInvalidClientError,
@@ -831,14 +840,14 @@ import {
831
840
  typecheckWithAmbient,
832
841
  typecheckScript,
833
842
  parseSkillContent
834
- } from "./cli-2y91wnbh.js";
843
+ } from "./cli-st8aw1m6.js";
835
844
  import {
836
845
  buildAuthorizationUrl,
837
846
  exchangeAuthorizationCode,
838
847
  oauthAppRowToProviderConfig,
839
848
  forceRefreshAuthorizationOrThrow,
840
849
  forceRefreshTokenOrThrow
841
- } from "./cli-esxerzg7.js";
850
+ } from "./cli-a92r1127.js";
842
851
  import {
843
852
  signalProcessGroup,
844
853
  detachedProcessGroup,
@@ -866,14 +875,14 @@ import {
866
875
  getSlackApp2,
867
876
  startSlackApp2,
868
877
  stopSlackApp2
869
- } from "./cli-qkhc5aqy.js";
870
- import"./cli-1d70crsy.js";
878
+ } from "./cli-37j04k05.js";
879
+ import"./cli-3a45zcgm.js";
871
880
  import {
872
881
  createApprovalFollowUpTask,
873
882
  generateEdges,
874
883
  definitionNodeIds,
875
884
  validateDefinition
876
- } from "./cli-q6r7t299.js";
885
+ } from "./cli-xpa64rek.js";
877
886
  import"./cli-8nyhf7za.js";
878
887
  import {
879
888
  _fetchHolder,
@@ -901,6 +910,7 @@ import {
901
910
  wireHttpSpanLifecycle,
902
911
  safeRequestUrlForLog,
903
912
  agentWithCapacity,
913
+ RequestBodyTooLargeError,
904
914
  parseBody,
905
915
  BODY_TOO_LARGE,
906
916
  enforceContentLengthCap,
@@ -932,7 +942,7 @@ import {
932
942
  bodyListTask,
933
943
  describeRequestRoute,
934
944
  route
935
- } from "./cli-cynvc9fj.js";
945
+ } from "./cli-018r4jq6.js";
936
946
  import {
937
947
  isMultiRuntimeEnabled
938
948
  } from "./cli-e8xyjvfc.js";
@@ -942,7 +952,7 @@ import {
942
952
  agentFsRequestTimeoutMs,
943
953
  AgentFsProvider,
944
954
  getFileStorageProvider2
945
- } from "./cli-qje9k2nh.js";
955
+ } from "./cli-g9ypd5en.js";
946
956
  import {
947
957
  RuntimePoolError2,
948
958
  mintControllerToken,
@@ -963,7 +973,7 @@ import {
963
973
  resolvePoolDelivery2,
964
974
  createPoolAgent2,
965
975
  init_db
966
- } from "./cli-w6nadkh4.js";
976
+ } from "./cli-j99azg3g.js";
967
977
  import {
968
978
  RATING_MODEL_MAX_LENGTH,
969
979
  REFERENCES_SOURCE_MAX_LENGTH,
@@ -979,8 +989,8 @@ import {
979
989
  checkHeartbeatChecklist2,
980
990
  runHeartbeatSweep2,
981
991
  stopHeartbeat2
982
- } from "./cli-3cgngyd8.js";
983
- import"./cli-82d3nkc3.js";
992
+ } from "./cli-ms85309d.js";
993
+ import"./cli-f0g3pgvf.js";
984
994
  import {
985
995
  import_websocket,
986
996
  import_websocket_server
@@ -989,7 +999,7 @@ import {
989
999
  runtimeSupervisorStatus,
990
1000
  startRuntimeSupervisor,
991
1001
  stopRuntimeSupervisor
992
- } from "./cli-dqb4n629.js";
1002
+ } from "./cli-1sp861s4.js";
993
1003
  import"./cli-ns59dc6r.js";
994
1004
  import {
995
1005
  ACP_TARGET_IDS
@@ -1009,14 +1019,14 @@ import {
1009
1019
  memoryRelevance,
1010
1020
  isUnknownModelError,
1011
1021
  writeSkillsToFilesystem
1012
- } from "./cli-xj0re5tm.js";
1022
+ } from "./cli-v8fjakwf.js";
1013
1023
  import {
1014
1024
  inviteEmailToSharedOrg,
1015
1025
  ensureAgentFsCredentialsForAgent
1016
- } from "./cli-hj3debgw.js";
1026
+ } from "./cli-4zfqgjc5.js";
1017
1027
  import {
1018
1028
  Anthropic
1019
- } from "./cli-er83cjbn.js";
1029
+ } from "./cli-k8g16gz3.js";
1020
1030
  import {
1021
1031
  isCodexOAuthConfigKey
1022
1032
  } from "./cli-j4bwnpkm.js";
@@ -1052,7 +1062,7 @@ import {
1052
1062
  revokeAgentTokens,
1053
1063
  fingerprintApiKey,
1054
1064
  init_users
1055
- } from "./cli-9fhkkpj5.js";
1065
+ } from "./cli-1f3qzw6y.js";
1056
1066
  import"./cli-v41kn3cd.js";
1057
1067
  import"./cli-wgtk7xxr.js";
1058
1068
  import {
@@ -1208,7 +1218,7 @@ import {
1208
1218
  KvKeySchema,
1209
1219
  KvEntrySchema,
1210
1220
  init_types
1211
- } from "./cli-nc35pxb7.js";
1221
+ } from "./cli-eww120g8.js";
1212
1222
  import {
1213
1223
  IDENTITY_FIELD_BUDGETS,
1214
1224
  IdentityFieldBudgetError,
@@ -1281,7 +1291,7 @@ import {
1281
1291
  import {
1282
1292
  init_package,
1283
1293
  package_default
1284
- } from "./cli-pccfmtb5.js";
1294
+ } from "./cli-94gwyg7m.js";
1285
1295
  import {
1286
1296
  realtimeBus,
1287
1297
  init_bus
@@ -2463,8 +2473,7 @@ async function authenticate(req) {
2463
2473
  const page = await getPage2(pageId);
2464
2474
  if (!page)
2465
2475
  throw new Error("Page not found");
2466
- const cookie = await extractAndVerifyCookie(req);
2467
- const session = cookie?.pageId === pageId ? cookie : null;
2476
+ const session = await extractAndVerifyCookie(req, pageId);
2468
2477
  if (!session && page.authMode !== "public")
2469
2478
  throw new Error("Page session required");
2470
2479
  if (session?.uid && (await findUserById(session.uid))?.status !== "active")
@@ -9696,6 +9705,7 @@ declare module "swarm-extension" {
9696
9705
  sourceTaskId?: string | undefined;
9697
9706
  outputSchema?: Record<string, unknown> | undefined;
9698
9707
  inheritParentOutputSchema?: boolean | undefined;
9708
+ inheritParentRequester?: boolean | undefined;
9699
9709
  inheritParentRoutingAffinity?: boolean | undefined;
9700
9710
  inheritParentFollowUpConfig?: boolean | undefined;
9701
9711
  followUpConfig?:
@@ -10114,8 +10124,15 @@ declare module "swarm-extension" {
10114
10124
  userName?: string;
10115
10125
  /** The buffered messages, joined. */
10116
10126
  text: string;
10127
+ /** True for an @mention and for a reply to one of the bot's messages. */
10117
10128
  botMentioned: boolean;
10118
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;
10119
10136
  /** Set when earlier swarm tasks exist in this thread. */
10120
10137
  threadContext?: {
10121
10138
  adapter: string;
@@ -11442,8 +11459,8 @@ export default extension;
11442
11459
  manifest: {
11443
11460
  $schema: "../manifest.schema.json",
11444
11461
  name: "slack-reply-gate",
11445
- 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",
11446
- 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",
11447
11464
  runtime: "api",
11448
11465
  assets: {
11449
11466
  hooks: "hooks.ts"
@@ -11458,8 +11475,9 @@ import { z } from "zod";
11458
11475
  *
11459
11476
  * Every human message in a Slack thread the swarm takes part in is buffered and
11460
11477
  * flushed into a Lead task ("[Thread follow-up — N message(s) buffered]"), even
11461
- * "lol", ":joy:" or two people bantering. This hook asks TypeSafe Jev whether
11462
- * 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.
11463
11481
  *
11464
11482
  * Hook: \`pre.task.create\`, origin "slack", description carrying the
11465
11483
  * thread-buffer header. \`pre.slack.route\` cannot be used: the buffered thread
@@ -11467,17 +11485,20 @@ import { z } from "zod";
11467
11485
  * re-target a message, never drop it.
11468
11486
  *
11469
11487
  * Rules:
11470
- * - A message that @mentions Lead passes without a Jev call.
11471
- * - 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;
11472
11490
  * reply=false only for reaction_only | banter_between_others |
11473
11491
  * not_addressed_to_lead, and only when the summed probability of those
11474
11492
  * three labels is >= minConfidence (a banter/reaction split is still a skip).
11475
11493
  * - Fails OPEN on every error, timeout or missing key.
11476
11494
  *
11477
- * Transport (config.provider): Jev is served by TypeSafe directly and by
11478
- * OpenRouter's Decisions endpoint. Both take the same {model, state, questions}
11479
- * body and return the same \`answers\` shape. \`auto\` uses TYPESAFE_API_KEY when
11480
- * 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.
11481
11502
  *
11482
11503
  * modes (config.mode):
11483
11504
  * off -> never evaluate
@@ -11500,17 +11521,19 @@ import { z } from "zod";
11500
11521
  export const config = z.object({
11501
11522
  mode: z.enum(["off", "shadow", "enforce"]).default("shadow"),
11502
11523
  timeoutMs: z.number().int().positive().default(3000),
11503
- provider: z.enum(["auto", "typesafe", "openrouter"]).default("auto"),
11504
- /** 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. */
11505
11528
  model: z.string().default("jev-latest"),
11506
- /** Jev model on the OpenRouter transport. */
11529
+ /** Jev model on OpenRouter. */
11507
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"),
11508
11533
  minConfidence: z.number().min(0).max(1).default(0.55),
11509
11534
  contextMessages: z.number().int().min(0).max(20).default(6),
11510
11535
  leadAliases: z.array(z.string()).default(["lead"]),
11511
11536
  botSlackUserIds: z.array(z.string()).default([]),
11512
- /** Explicit TypeSafe key; otherwise TYPESAFE_API_KEY from the API process env. */
11513
- apiKey: z.string().optional(),
11514
11537
  /**
11515
11538
  * Reaction for a dropped message in enforce mode. "" only removes the bot's
11516
11539
  * acceptance reaction(s) and adds nothing.
@@ -11523,13 +11546,13 @@ type Cfg = z.infer<typeof config>;
11523
11546
  type Swarm = ApiCtx["swarm"];
11524
11547
 
11525
11548
  const NS = "ext:slack-reply-gate";
11526
- const TYPESAFE_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
11527
- const OPENROUTER_ENDPOINT = "https://openrouter.ai/api/alpha/decisions";
11528
11549
  const HEADER_LINE_RE = /(?:^|\\n)\\[Thread follow-up — \\d+ message\\(s\\) buffered\\]\\n/g;
11529
11550
  const CTX_OPEN = "<thread_context>";
11530
11551
  const CTX_CLOSE = "</thread_context>";
11531
11552
  // The dispatcher cancels a handler after 5 s; leave room for the KV writes.
11532
11553
  const MAX_TIMEOUT_MS = 4000;
11554
+ // The smallest time budget system-one-decide accepts.
11555
+ const MIN_DECIDE_TIMEOUT_MS = 1000;
11533
11556
 
11534
11557
  export const REASONS = {
11535
11558
  direct_ask:
@@ -11631,14 +11654,14 @@ const usable = (v: string): boolean => Boolean(v) && !v.startsWith("[REDACTED");
11631
11654
  const envOf = (name: string): string => String(process.env[name] ?? "");
11632
11655
 
11633
11656
  /**
11634
- * Resolve a secret: explicit value, then the API process env. The API process loads
11635
- * 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.
11636
11659
  * ctx.swarm.config_get cannot serve it: the SDK scrubs secrets to "[REDACTED:<name>]".
11637
11660
  */
11638
- const secret = (name: string, explicit?: string): string => {
11661
+ const secret = (name: string): string => {
11639
11662
  const cached = secretCache.get(name);
11640
11663
  if (cached && Date.now() - cached.at < KEY_TTL_MS) return cached.value;
11641
- const value = [String(explicit ?? ""), envOf(name)].find(usable) ?? "";
11664
+ const value = usable(envOf(name)) ? envOf(name) : "";
11642
11665
  if (usable(value)) secretCache.set(name, { value, at: Date.now() });
11643
11666
  return value;
11644
11667
  };
@@ -11648,46 +11671,51 @@ export const resetKeyCache = (): void => {
11648
11671
  secretCache.clear();
11649
11672
  };
11650
11673
 
11651
- export type JevTransport = {
11652
- provider: "typesafe" | "openrouter";
11653
- endpoint: string;
11654
- key: string;
11655
- model: string;
11656
- };
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;
11657
11694
 
11658
11695
  /**
11659
- * Pick the Jev transport. \`auto\` prefers TypeSafe and falls back to OpenRouter.
11660
- * 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.
11661
11699
  */
11662
- export const resolveTransport = (cfg: Cfg): JevTransport => {
11663
- const typesafe = (): JevTransport | null => {
11664
- const key = secret("TYPESAFE_API_KEY", cfg.apiKey);
11665
- return usable(key)
11666
- ? { provider: "typesafe", endpoint: TYPESAFE_ENDPOINT, key, model: cfg.model }
11667
- : null;
11668
- };
11669
- const openrouter = (): JevTransport | null => {
11670
- const key = secret("OPENROUTER_API_KEY");
11671
- return usable(key)
11672
- ? { provider: "openrouter", endpoint: OPENROUTER_ENDPOINT, key, model: cfg.openrouterModel }
11673
- : null;
11674
- };
11675
- const transport =
11676
- cfg.provider === "typesafe"
11677
- ? typesafe()
11678
- : cfg.provider === "openrouter"
11679
- ? openrouter()
11680
- : (typesafe() ?? openrouter());
11681
- 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;
11682
11708
  throw new Error(
11683
- cfg.provider === "typesafe"
11684
- ? "no TYPESAFE_API_KEY"
11685
- : cfg.provider === "openrouter"
11686
- ? "no OPENROUTER_API_KEY"
11687
- : "no TYPESAFE_API_KEY or OPENROUTER_API_KEY",
11709
+ "no TYPESAFE_API_KEY, OPENROUTER_API_KEY, or CLOUDFLARE_API_TOKEN with CLOUDFLARE_ACCOUNT_ID",
11688
11710
  );
11689
11711
  };
11690
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
+
11691
11719
  const SLACK_API = "https://slack.com/api";
11692
11720
  // The engine's acceptance reactions (src/slack/reaction-shortcode.ts), by config key.
11693
11721
  const ACCEPTANCE_REACTIONS: ReadonlyArray<[string, string]> = [
@@ -11775,7 +11803,7 @@ export const settleMutes = async (): Promise<void> => {
11775
11803
  await Promise.allSettled([...pendingMutes]);
11776
11804
  };
11777
11805
 
11778
- export type JevVerdict = {
11806
+ export type Verdict = {
11779
11807
  reason: string;
11780
11808
  confidence: number | null;
11781
11809
  probabilities: Record<string, number> | null;
@@ -11783,56 +11811,73 @@ export type JevVerdict = {
11783
11811
  };
11784
11812
 
11785
11813
  /**
11786
- * One Jev call: classify the newest message into a reason. Throws on any failure.
11787
- * 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.
11788
11817
  */
11789
- export const askJev = async (
11818
+ export const askModel = async (
11819
+ swarm: Swarm,
11790
11820
  input: {
11791
11821
  messages: string[];
11792
11822
  context: string[];
11793
11823
  sender: string;
11794
11824
  leadNames: readonly string[];
11795
11825
  },
11796
- transport: JevTransport,
11826
+ target: DecisionTarget,
11797
11827
  cfg: Cfg,
11798
11828
  outer?: AbortSignal,
11799
- ): Promise<JevVerdict> => {
11800
- const controller = new AbortController();
11801
- const timer = setTimeout(() => controller.abort(), Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS));
11802
- const onOuterAbort = () => controller.abort();
11803
- 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
+ });
11804
11838
  const t0 = Date.now();
11805
11839
  try {
11806
- const res = await fetch(transport.endpoint, {
11807
- method: "POST",
11808
- headers: {
11809
- "Content-Type": "application/json",
11810
- 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,
11811
11849
  },
11812
- body: JSON.stringify({
11813
- state: {
11814
- 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.",
11815
- lead_names: input.leadNames,
11816
- recent_thread_messages: input.context,
11817
- new_message: input.messages.join("\\n---\\n"),
11818
- new_message_sender: input.sender,
11819
- },
11820
- model: transport.model,
11821
- questions: {
11822
- reason: {
11823
- type: "choice",
11824
- instructions:
11825
- "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.",
11826
- criteria: REASONS,
11827
- },
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,
11828
11856
  },
11829
- }),
11830
- signal: controller.signal,
11857
+ },
11858
+ timeoutMs: Math.max(budgetMs, MIN_DECIDE_TIMEOUT_MS),
11859
+ maxRetries: 0,
11831
11860
  });
11832
- if (!res.ok) throw new Error(\`\${transport.provider} \${res.status}\`);
11833
- 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;
11834
11873
  answers?: { reason?: { choice?: unknown; confidence?: unknown; probabilities?: unknown } };
11835
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
+ }
11836
11881
  const answer = body?.answers?.reason;
11837
11882
  const reason = typeof answer?.choice === "string" ? answer.choice : "";
11838
11883
  if (!(reason in REASONS)) throw new Error(\`unexpected choice: \${String(reason).slice(0, 60)}\`);
@@ -11844,12 +11889,34 @@ export const askJev = async (
11844
11889
  return { reason, confidence, probabilities, latencyMs: Date.now() - t0 };
11845
11890
  } finally {
11846
11891
  clearTimeout(timer);
11847
- outer?.removeEventListener("abort", onOuterAbort);
11892
+ if (onOuterAbort) outer?.removeEventListener("abort", onOuterAbort);
11848
11893
  }
11849
11894
  };
11850
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
+
11851
11918
  /** Probability that the message needs no reply: the summed mass of the skip labels. */
11852
- export const skipProbability = (verdict: JevVerdict): number | null => {
11919
+ export const skipProbability = (verdict: Verdict): number | null => {
11853
11920
  if (verdict.probabilities) {
11854
11921
  let sum = 0;
11855
11922
  for (const [label, p] of Object.entries(verdict.probabilities)) {
@@ -11860,9 +11927,9 @@ export const skipProbability = (verdict: JevVerdict): number | null => {
11860
11927
  return verdict.confidence;
11861
11928
  };
11862
11929
 
11863
- /** 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. */
11864
11931
  export const decide = (
11865
- verdict: JevVerdict,
11932
+ verdict: Verdict,
11866
11933
  minConfidence: number,
11867
11934
  ): { reply: boolean; reason: Reason; skipP: number | null; lowConfidence: boolean } => {
11868
11935
  const reason = verdict.reason as Reason;
@@ -11959,7 +12026,7 @@ const extension: SwarmExtension = (api) => {
11959
12026
  return;
11960
12027
  }
11961
12028
 
11962
- const transport = resolveTransport(cfg);
12029
+ const targets = resolveProviders(cfg);
11963
12030
 
11964
12031
  const sender =
11965
12032
  opts.slackUserId && cfg.botSlackUserIds.includes(opts.slackUserId)
@@ -11968,14 +12035,15 @@ const extension: SwarmExtension = (api) => {
11968
12035
  ? "human (known teammate)"
11969
12036
  : "unknown (not a known teammate; may be a bot or an external person)";
11970
12037
 
11971
- const verdict = await askJev(
12038
+ const { verdict, target } = await askFirstAvailable(
12039
+ ctx.swarm,
11972
12040
  {
11973
12041
  messages,
11974
12042
  context: context.slice(-cfg.contextMessages).map((m) => m.slice(0, 500)),
11975
12043
  sender,
11976
12044
  leadNames: cfg.leadAliases,
11977
12045
  },
11978
- transport,
12046
+ targets,
11979
12047
  cfg,
11980
12048
  ctx.signal,
11981
12049
  );
@@ -11994,7 +12062,7 @@ const extension: SwarmExtension = (api) => {
11994
12062
  skipP: decision.skipP,
11995
12063
  lowConfidence: decision.lowConfidence,
11996
12064
  latencyMs: verdict.latencyMs,
11997
- provider: transport.provider,
12065
+ provider: target.provider,
11998
12066
  sender,
11999
12067
  preview,
12000
12068
  },
@@ -12034,7 +12102,7 @@ const extension: SwarmExtension = (api) => {
12034
12102
  export default extension;
12035
12103
  `
12036
12104
  },
12037
- 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'
12038
12106
  },
12039
12107
  "task-digest": {
12040
12108
  manifestFile: "manifest.yaml",
@@ -13220,6 +13288,17 @@ async function handleFleetBenchmark(req, res, pathSegments, queryParams) {
13220
13288
  // src/http/fs.ts
13221
13289
  init_zod();
13222
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
13223
13302
  init_rbac();
13224
13303
  init_types();
13225
13304
 
@@ -13343,7 +13422,7 @@ var uploadTaskFileRoute = route({
13343
13422
  pattern: ["api", "fs", "tasks", null, "files"],
13344
13423
  session: { taskParam: "taskId" },
13345
13424
  summary: "Upload a binary task file attachment",
13346
- 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.",
13347
13426
  tags: ["FS"],
13348
13427
  params: taskParams,
13349
13428
  query: uploadQuery,
@@ -13504,7 +13583,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13504
13583
  return notFound(res, "Task not found");
13505
13584
  if (!await canMutateTask(task, myAgentId, req))
13506
13585
  return forbidden(res);
13507
- 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");
13508
13587
  }
13509
13588
  if (listTaskFilesRoute.match(req.method, pathSegments)) {
13510
13589
  const parsed = await listTaskFilesRoute.parse(req, res, pathSegments, queryParams);
@@ -13531,7 +13610,7 @@ async function handleFs(req, res, pathSegments, queryParams, myAgentId) {
13531
13610
  }
13532
13611
  return false;
13533
13612
  }
13534
- async function sendUpload(req, res, taskId, query, agentId) {
13613
+ async function sendUpload(req, res, taskId, query, agentId, isDraft) {
13535
13614
  const body = await readRawBody(req, MAX_TASK_ATTACHMENT_BYTES);
13536
13615
  if (body === BODY_TOO_LARGE) {
13537
13616
  jsonError(res, `Payload too large (max ${MAX_TASK_ATTACHMENT_BYTES} bytes)`, 413);
@@ -13572,9 +13651,27 @@ async function sendUpload(req, res, taskId, query, agentId) {
13572
13651
  isPrimary: query.isPrimary === "true",
13573
13652
  createdBy: auth?.kind === "user" ? auth.userId : undefined
13574
13653
  });
13654
+ const mimeType = contentType.split(";")[0].trim().toLowerCase();
13655
+ if (isDraft && mimeType.startsWith("audio/")) {
13656
+ await appendAudioTranscript(taskId, query.name, mimeType, body);
13657
+ }
13575
13658
  uploadTaskFileRoute.respond(res, 201, attachment);
13576
13659
  return true;
13577
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
+ }
13578
13675
  async function sendDownload(res, attachment) {
13579
13676
  const provider = getFileStorageProvider2();
13580
13677
  if (backingProviderId(attachment) !== provider.id) {
@@ -13634,23 +13731,6 @@ async function sendDelete(res, attachment) {
13634
13731
  res.end();
13635
13732
  return true;
13636
13733
  }
13637
- function scopeFromAttachment(attachment) {
13638
- const storedKey = attachment.providerKey ?? attachment.path ?? "";
13639
- return {
13640
- taskId: attachment.taskId,
13641
- name: attachment.name,
13642
- key: storedKey.trim() ? storedKey : undefined,
13643
- orgId: attachment.orgId,
13644
- driveId: attachment.driveId
13645
- };
13646
- }
13647
- function backingProviderId(attachment) {
13648
- if (attachment.kind === "agent-fs")
13649
- return attachment.providerId ?? "agent-fs";
13650
- if (attachment.providerId === "local-fs")
13651
- return "local-fs";
13652
- return null;
13653
- }
13654
13734
  async function findAttachment(req, myAgentId, taskId, attachmentId, res) {
13655
13735
  if (!await getTaskById2(taskId)) {
13656
13736
  notFound(res, "Task not found");
@@ -16408,6 +16488,29 @@ init_db3();
16408
16488
  init_env_flag();
16409
16489
  init_request_auth_context();
16410
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
16411
16514
  var DEFAULT_MCP_TRANSPORT_IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000;
16412
16515
  function respondNoMcpSession(res, sessionId) {
16413
16516
  const notFound = Boolean(sessionId);
@@ -16507,17 +16610,16 @@ async function handleMcp(req, res, transports, sessionActivity = {}, sessionAgen
16507
16610
  if (agentMismatch)
16508
16611
  return true;
16509
16612
  if (req.method === "POST") {
16510
- const chunks = [];
16511
- for await (const chunk of req) {
16512
- chunks.push(chunk);
16513
- }
16514
- const body = JSON.parse(Buffer.concat(chunks).toString());
16515
- 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") {
16516
16618
  res.writeHead(200, { "Content-Type": "application/json" });
16517
16619
  res.end(JSON.stringify({
16518
16620
  jsonrpc: "2.0",
16519
16621
  error: { code: -32601, message: "Method not found" },
16520
- id: body.id ?? null
16622
+ id: rpc.id ?? null
16521
16623
  }));
16522
16624
  return true;
16523
16625
  }
@@ -17854,7 +17956,8 @@ function createUserServer(user) {
17854
17956
  }, {
17855
17957
  capabilities: {
17856
17958
  logging: {}
17857
- }
17959
+ },
17960
+ maxToolInputElements: MCP_MAX_TOOL_INPUT_ELEMENTS
17858
17961
  });
17859
17962
  const registerTool = createToolRegistrar(server);
17860
17963
  const sendTaskConfig = {
@@ -17972,11 +18075,9 @@ async function handleMcpUser(req, res, transports, sessionUsers, sessionActivity
17972
18075
  return unauthorized2(res);
17973
18076
  }
17974
18077
  if (req.method === "POST") {
17975
- const chunks = [];
17976
- for await (const chunk of req) {
17977
- chunks.push(chunk);
17978
- }
17979
- 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;
17980
18081
  let transport;
17981
18082
  if (sessionId && transports[sessionId]) {
17982
18083
  transport = transports[sessionId];
@@ -18118,6 +18219,9 @@ async function hasRetrievalForTask(taskId, memoryId) {
18118
18219
  return row != null;
18119
18220
  }
18120
18221
 
18222
+ // src/http/memory.ts
18223
+ init_search_candidates();
18224
+
18121
18225
  // src/be/memory/usefulness-stats.ts
18122
18226
  init_db3();
18123
18227
  async function getUsefulnessStats(options = {}) {
@@ -18958,7 +19062,7 @@ async function handleMemory(req, res, pathSegments, callerAgentId) {
18958
19062
  const store = getMemoryStore2();
18959
19063
  const queryEmbedding = await provider.embed(query);
18960
19064
  const candidateLimit = Math.min(limit, 20) * CANDIDATE_SET_MULTIPLIER;
18961
- const candidates = await store.search(queryEmbedding ?? new Float32Array(0), myAgentId, {
19065
+ const candidates = await searchMemoryCandidates(store, queryEmbedding ?? new Float32Array(0), myAgentId, {
18962
19066
  scope,
18963
19067
  limit: candidateLimit,
18964
19068
  source,
@@ -19039,7 +19143,7 @@ async function handleMemory(req, res, pathSegments, callerAgentId) {
19039
19143
  const provider = getEmbeddingProvider2();
19040
19144
  const queryEmbedding = await provider.embed(query.trim());
19041
19145
  const candidateLimit = Math.min(4096, Math.max(offset + pageLimit, pageLimit) * CANDIDATE_SET_MULTIPLIER);
19042
- let candidates = await store.search(queryEmbedding ?? new Float32Array(0), viewer.agentId, {
19146
+ let candidates = await searchMemoryCandidates(store, queryEmbedding ?? new Float32Array(0), viewer.agentId, {
19043
19147
  scope,
19044
19148
  limit: candidateLimit,
19045
19149
  isLead: viewer.seesAll,
@@ -21974,6 +22078,35 @@ route({
21974
22078
  auth: { apiKey: false }
21975
22079
  });
21976
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
+ }
21977
22110
  function isSafeProxySuffix(suffix) {
21978
22111
  if (suffix.length === 0)
21979
22112
  return true;
@@ -22012,11 +22145,13 @@ async function handlePageProxy(req, res) {
22012
22145
  jsonError(res, "not found", 404);
22013
22146
  return true;
22014
22147
  }
22015
- const payload = await extractAndVerifyCookie(req);
22016
- 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) {
22017
22151
  jsonError(res, "no page session", 401);
22018
22152
  return true;
22019
22153
  }
22154
+ const { payload } = session;
22020
22155
  const page = await getPage2(payload.pageId);
22021
22156
  if (!page) {
22022
22157
  jsonError(res, "page session no longer valid", 401);
@@ -22034,7 +22169,7 @@ async function handlePageProxy(req, res) {
22034
22169
  const headers = {
22035
22170
  Authorization: `Bearer ${apiKey}`,
22036
22171
  "X-Page-Id": page.id,
22037
- "X-Page-Session": parseCookieHeader(req.headers.cookie, "page_session") ?? ""
22172
+ "X-Page-Session": session.token
22038
22173
  };
22039
22174
  const reqContentType = req.headers["content-type"];
22040
22175
  if (reqContentType) {
@@ -24378,12 +24513,12 @@ ${defuseFeedbackTags(note)}
24378
24513
  }
24379
24514
  const auth = getRequestAuth(req);
24380
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;
24381
- const cookie = await issuePageSessionCookie(page.id, {
24516
+ const cookies = await pageSessionSetCookies(req, page.id, {
24382
24517
  dev: isDevRequest(req),
24383
24518
  ...identity
24384
24519
  });
24385
24520
  applyLaunchCors(req, res);
24386
- res.setHeader("Set-Cookie", cookie);
24521
+ res.setHeader("Set-Cookie", cookies);
24387
24522
  res.writeHead(204);
24388
24523
  res.end();
24389
24524
  return true;
@@ -24405,10 +24540,15 @@ class SwarmSDK {
24405
24540
  this.channel = async (name) => (await loadRealtime()).channel(name);
24406
24541
 
24407
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] : '';
24408
24547
  const call = async (method, path, body) => {
24409
- const init = { method };
24548
+ const init = { method, headers: {} };
24549
+ if (pageId) init.headers['X-Swarm-Page-Id'] = pageId;
24410
24550
  if (body !== undefined) {
24411
- init.headers = { 'Content-Type': 'application/json' };
24551
+ init.headers['Content-Type'] = 'application/json';
24412
24552
  init.body = JSON.stringify(body);
24413
24553
  }
24414
24554
  const res = await fetch(base + path, init);
@@ -25593,7 +25733,7 @@ var FEEDBACK_OVERLAY_JS = `
25593
25733
  return fetch('/@swarm/api/pages/' + encodeURIComponent(pageId) + '/feedback', {
25594
25734
  method: 'POST',
25595
25735
  credentials: 'same-origin',
25596
- headers: { 'Content-Type': 'application/json' },
25736
+ headers: { 'Content-Type': 'application/json', 'X-Swarm-Page-Id': pageId },
25597
25737
  body: JSON.stringify(payload),
25598
25738
  }).then(function (res) {
25599
25739
  return res.text().then(function (text) {
@@ -25979,23 +26119,8 @@ function buildCsp() {
25979
26119
  function isAccessible(page, cookiePayload) {
25980
26120
  if (page.authMode === "public")
25981
26121
  return { ok: true };
25982
- if (cookiePayload) {
25983
- if (cookiePayload.pageId === page.id)
25984
- return { ok: true };
25985
- if (page.authMode === "password") {
25986
- return {
25987
- ok: false,
25988
- status: 401,
25989
- reason: "password required",
25990
- needsPassword: true
25991
- };
25992
- }
25993
- return {
25994
- ok: false,
25995
- status: 403,
25996
- reason: "page-session cookie scoped to a different page id"
25997
- };
25998
- }
26122
+ if (cookiePayload?.pageId === page.id)
26123
+ return { ok: true };
25999
26124
  if (page.authMode === "authed") {
26000
26125
  return {
26001
26126
  ok: false,
@@ -26070,9 +26195,9 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26070
26195
  res.end(JSON.stringify({ error: "Page not found" }));
26071
26196
  return true;
26072
26197
  }
26073
- const cookiePayload = await extractAndVerifyCookie(req);
26198
+ const cookiePayload = await extractAndVerifyCookie(req, page.id);
26074
26199
  const access = isAccessible(page, cookiePayload);
26075
- let inlineSetCookie = "";
26200
+ let inlineSetCookie = [];
26076
26201
  if (!access.ok) {
26077
26202
  if (access.needsPassword) {
26078
26203
  const candidate = extractPasswordCandidate(req, queryParams);
@@ -26084,7 +26209,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26084
26209
  matched = false;
26085
26210
  }
26086
26211
  if (matched) {
26087
- inlineSetCookie = await issuePageSessionCookie(page.id, {
26212
+ inlineSetCookie = await pageSessionSetCookies(req, page.id, {
26088
26213
  dev: isLocalhostRequest(req)
26089
26214
  });
26090
26215
  } else {
@@ -26111,7 +26236,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26111
26236
  }
26112
26237
  if (isJsonRoute) {
26113
26238
  res.setHeader("Cache-Control", "no-store");
26114
- if (inlineSetCookie)
26239
+ if (inlineSetCookie.length > 0)
26115
26240
  res.setHeader("Set-Cookie", inlineSetCookie);
26116
26241
  publicPageJsonRoute.respond(res, 200, {
26117
26242
  id: page.id,
@@ -26132,7 +26257,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26132
26257
  "X-Content-Type-Options": "nosniff",
26133
26258
  "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox"
26134
26259
  };
26135
- if (inlineSetCookie)
26260
+ if (inlineSetCookie.length > 0)
26136
26261
  headers["Set-Cookie"] = inlineSetCookie;
26137
26262
  res.writeHead(200, headers);
26138
26263
  res.end(page.body);
@@ -26148,15 +26273,17 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26148
26273
  "Content-Security-Policy": buildCsp(),
26149
26274
  "X-Content-Type-Options": "nosniff"
26150
26275
  };
26151
- if (inlineSetCookie)
26276
+ if (inlineSetCookie.length > 0)
26152
26277
  headers["Set-Cookie"] = inlineSetCookie;
26153
26278
  res.writeHead(200, headers);
26154
26279
  res.end(printableJsonPageHtml(page));
26155
26280
  await bumpViewCount(page.id);
26156
26281
  return true;
26157
26282
  }
26158
- const headers = { Location: `${getAppBaseUrl2()}/pages/${page.id}` };
26159
- if (inlineSetCookie)
26283
+ const headers = {
26284
+ Location: `${getAppBaseUrl2()}/pages/${page.id}`
26285
+ };
26286
+ if (inlineSetCookie.length > 0)
26160
26287
  headers["Set-Cookie"] = inlineSetCookie;
26161
26288
  res.writeHead(302, headers);
26162
26289
  res.end();
@@ -26170,7 +26297,7 @@ async function handlePagesPublic(req, res, pathSegments, queryParams) {
26170
26297
  "Content-Security-Policy": buildCsp(),
26171
26298
  "X-Content-Type-Options": "nosniff"
26172
26299
  };
26173
- if (inlineSetCookie)
26300
+ if (inlineSetCookie.length > 0)
26174
26301
  headers["Set-Cookie"] = inlineSetCookie;
26175
26302
  res.writeHead(200, headers);
26176
26303
  res.end(html);
@@ -31137,6 +31264,7 @@ var agentTaskBodySchema = object2({
31137
31264
  routingNote: string2().max(200).optional(),
31138
31265
  tags: array2(string2()).optional(),
31139
31266
  priority: number2().int().min(0).max(100).optional(),
31267
+ dependsOn: array2(uuid()).optional(),
31140
31268
  offerMode: boolean().optional(),
31141
31269
  dir: string2().min(1).optional(),
31142
31270
  vcsRepo: string2().min(1).optional(),
@@ -31617,6 +31745,7 @@ async function handleScriptRuns(req, res, pathSegments, queryParams, agentId) {
31617
31745
  routingSource: parsed.body.routingReason ? "declared" : parsed.body.agentId ? "engine_default" : undefined,
31618
31746
  tags: parsed.body.tags,
31619
31747
  priority: parsed.body.priority,
31748
+ dependsOn: parsed.body.dependsOn,
31620
31749
  offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
31621
31750
  taskType: "script-run-step",
31622
31751
  source: "mcp",
@@ -36569,7 +36698,17 @@ var deleteIdentityRoute = route({
36569
36698
  },
36570
36699
  auth: { apiKey: true }
36571
36700
  });
36572
- 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
+ ];
36573
36712
  async function collectUnmappedForKind(kind, limit) {
36574
36713
  const namespace = `integration:unmapped:${kind}`;
36575
36714
  const rows = await listKv2(namespace, { limit: Math.min(limit * 2, 1000), offset: 0 });
@@ -39264,7 +39403,7 @@ var httpServer = createHttpServer(async (req, res) => {
39264
39403
  });
39265
39404
  });
39266
39405
  var detachRealtime = attachRealtimeTransport(httpServer);
39267
- var { attachControllerTransport } = await import("./controllers-connect-mwzx1fh6.js");
39406
+ var { attachControllerTransport } = await import("./controllers-connect-9ftpex99.js");
39268
39407
  var detachControllers = attachControllerTransport(httpServer);
39269
39408
  var stopWorkerNudges = startWorkerNudgeListener();
39270
39409
  var stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namespace) => {
@@ -39293,7 +39432,7 @@ async function shutdown() {
39293
39432
  sessionsProcessed: getServerSessionsProcessed()
39294
39433
  });
39295
39434
  if (hasCapability("scheduling")) {
39296
- const { stopScheduler } = await import("./index-hg0x2cdv.js");
39435
+ const { stopScheduler } = await import("./index-bgdyvcnv.js");
39297
39436
  stopScheduler();
39298
39437
  }
39299
39438
  stopHeartbeat2();
@@ -39305,7 +39444,7 @@ async function shutdown() {
39305
39444
  await stopSlackApp2();
39306
39445
  await stopChatSdk();
39307
39446
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
39308
- const { stopOAuthKeepalive } = await import("./keepalive-hrh7xnr9.js");
39447
+ const { stopOAuthKeepalive } = await import("./keepalive-7yrng7px.js");
39309
39448
  await stopOAuthKeepalive();
39310
39449
  }
39311
39450
  stopOAuthPendingGc();
@@ -39385,21 +39524,21 @@ try {
39385
39524
  console.warn("[startup] CAPABILITIES upgrade seed failed (non-fatal):", err);
39386
39525
  }
39387
39526
  try {
39388
- const { ensurePricingSeeded } = await import("./boot-seeds-9ks8hce7.js");
39527
+ const { ensurePricingSeeded } = await import("./boot-seeds-j2273s54.js");
39389
39528
  ensurePricingSeeded();
39390
- const { startPricingRefreshLoop } = await import("./pricing-refresh-j38dehfs.js");
39529
+ const { startPricingRefreshLoop } = await import("./pricing-refresh-d2b5my0y.js");
39391
39530
  startPricingRefreshLoop();
39392
39531
  } catch (err) {
39393
39532
  console.error("[startup] Failed to seed pricing rows:", err);
39394
39533
  }
39395
39534
  try {
39396
- const { ensureModelProfilesSeeded } = await import("./boot-seeds-9ks8hce7.js");
39535
+ const { ensureModelProfilesSeeded } = await import("./boot-seeds-j2273s54.js");
39397
39536
  await ensureModelProfilesSeeded();
39398
39537
  } catch (err) {
39399
39538
  console.error("[startup] Failed to seed model profiles:", err);
39400
39539
  }
39401
39540
  try {
39402
- const { ensureRbacSeeded } = await import("./boot-seeds-9ks8hce7.js");
39541
+ const { ensureRbacSeeded } = await import("./boot-seeds-j2273s54.js");
39403
39542
  ensureRbacSeeded();
39404
39543
  } catch (err) {
39405
39544
  console.error("[startup] Failed to sync RBAC seed rows:", err);
@@ -39407,7 +39546,7 @@ try {
39407
39546
  throw err;
39408
39547
  }
39409
39548
  try {
39410
- const { runAllSeeders } = await import("./index-97q4xn03.js");
39549
+ const { runAllSeeders } = await import("./index-jwxkkex2.js");
39411
39550
  await runAllSeeders({ scriptEmbeddingMode: "skip" });
39412
39551
  } catch (err) {
39413
39552
  console.error("[startup] Failed to seed built-in entities:", err);
@@ -39469,39 +39608,39 @@ httpServer.listen(port, async () => {
39469
39608
  await initWorkflows2();
39470
39609
  await startScriptRunSupervisor(getMcpBaseUrl());
39471
39610
  if (hasCapability("scheduling")) {
39472
- const { startScheduler } = await import("./index-hg0x2cdv.js");
39473
- const { getExecutorRegistry } = await import("./index-gdh8jrdm.js");
39611
+ const { startScheduler } = await import("./index-bgdyvcnv.js");
39612
+ const { getExecutorRegistry } = await import("./index-gabbcdn2.js");
39474
39613
  const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
39475
39614
  startScheduler(getExecutorRegistry(), intervalMs, {
39476
39615
  runId: globalState.__runId
39477
39616
  });
39478
39617
  }
39479
39618
  if (!isEnvFlagEnabled("HEARTBEAT_DISABLE", false)) {
39480
- const { startHeartbeat } = await import("./index-thm0y6a8.js");
39619
+ const { startHeartbeat } = await import("./index-tfb26zhy.js");
39481
39620
  const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
39482
39621
  startHeartbeat(heartbeatMs);
39483
39622
  }
39484
39623
  startPoolRouting();
39485
39624
  startRuntimeSupervisor();
39486
39625
  if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
39487
- const { startOAuthKeepalive } = await import("./keepalive-hrh7xnr9.js");
39626
+ const { startOAuthKeepalive } = await import("./keepalive-7yrng7px.js");
39488
39627
  startOAuthKeepalive();
39489
39628
  }
39490
39629
  if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
39491
- const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-zm0yj4sj.js");
39630
+ const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-yw3xdbcj.js");
39492
39631
  startOAuthRefreshSweep();
39493
39632
  }
39494
39633
  startOAuthPendingGc();
39495
39634
  await startMemoryGc();
39496
39635
  await startDbRetention();
39497
39636
  startHumanFreeDrain();
39498
- import("./boot-reembed-srp6v6af.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
39637
+ import("./boot-reembed-b9c3az3c.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
39499
39638
  console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
39500
39639
  });
39501
- import("./boot-reembed-xns7h5h9.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
39640
+ import("./boot-reembed-qdkvxeem.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
39502
39641
  console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
39503
39642
  });
39504
- import("./boot-scrub-sweep-3qqrh2mz.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
39643
+ import("./boot-scrub-sweep-m3wh8eks.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
39505
39644
  console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
39506
39645
  });
39507
39646
  }).on("error", (err) => {