@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
@@ -0,0 +1,264 @@
1
+ import { getOpenRouterAttributionHeaders } from "../../utils/openrouter-base-url";
2
+ import {
3
+ type SystemOneCapability,
4
+ type SystemOneHostId,
5
+ systemOneKeyRejectedMessage,
6
+ } from "./system-one-providers";
7
+ import { cloudflareErrorCode, isRecord } from "./system-one-wire";
8
+
9
+ /**
10
+ * One POST to a System One host, shared by every capability (`system-one-decision`,
11
+ * `system-one-extraction`): one time budget, transient retries with backoff,
12
+ * `Retry-After`, redirect refusal, a response size cap, and the same error
13
+ * sentences. The credential goes only into the `authorization` header.
14
+ */
15
+
16
+ export const SYSTEM_ONE_DEFAULT_TIMEOUT_MS = 30_000;
17
+ export const SYSTEM_ONE_MIN_TIMEOUT_MS = 1_000;
18
+ export const SYSTEM_ONE_MAX_TIMEOUT_MS = 300_000;
19
+ export const SYSTEM_ONE_DEFAULT_MAX_RETRIES = 2;
20
+ export const SYSTEM_ONE_MAX_RETRIES_LIMIT = 3;
21
+
22
+ /** Room left before the engine watchdog fires so the executor can report its own failure. */
23
+ const DEADLINE_MARGIN_MS = 250;
24
+ const BACKOFF_BASE_MS = 500;
25
+ const BACKOFF_CAP_MS = 8_000;
26
+ const MAX_RESPONSE_CHARS = 1_048_576;
27
+ const MAX_ERROR_BODY_CHARS = 65_536;
28
+
29
+ export interface SystemOneTransportOptions {
30
+ /** Transport. Tests inject a fake; production uses the global `fetch`. */
31
+ fetch?: typeof fetch;
32
+ sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
33
+ now?: () => number;
34
+ random?: () => number;
35
+ }
36
+
37
+ export interface SystemOneRequest {
38
+ providerId: SystemOneHostId;
39
+ capability: SystemOneCapability;
40
+ endpoint: string;
41
+ apiKey: string;
42
+ /** The secret name the key was read under, named when the host rejects it. */
43
+ keyName: string;
44
+ body: string;
45
+ timeoutMs: number;
46
+ maxRetries: number;
47
+ env: NodeJS.ProcessEnv;
48
+ }
49
+
50
+ export type SystemOneSendResult =
51
+ | { ok: true; text: string; requestId?: string }
52
+ | { ok: false; error: string };
53
+
54
+ type Attempt =
55
+ | { kind: "ok"; text: string; requestId?: string }
56
+ | { kind: "timeout" }
57
+ | { kind: "fail"; message: string; retryable: boolean; retryAfterMs?: number };
58
+
59
+ const REQUEST_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/;
60
+ const ERROR_CODE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
61
+
62
+ function readRequestId(headers: Headers): string | undefined {
63
+ // Cloudflare marks every response with `cf-ray` and sends no request id.
64
+ const value = headers.get("x-request-id") ?? headers.get("request-id") ?? headers.get("cf-ray");
65
+ return value && REQUEST_ID_RE.test(value) ? value : undefined;
66
+ }
67
+
68
+ /** Retry-After as delta-seconds or an HTTP date, in ms. Undefined when absent or unparseable. */
69
+ function parseRetryAfter(value: string | null, now: number): number | undefined {
70
+ if (!value) return undefined;
71
+ const trimmed = value.trim();
72
+ if (/^\d+(\.\d+)?$/.test(trimmed)) return Math.round(Number(trimmed) * 1000);
73
+ const at = Date.parse(trimmed);
74
+ return Number.isNaN(at) ? undefined : Math.max(0, at - now);
75
+ }
76
+
77
+ /** Only a short identifier from the provider's error envelope. Never its message or echoed request. */
78
+ function readErrorCode(text: string): string | undefined {
79
+ if (text.length === 0 || text.length > MAX_ERROR_BODY_CHARS) return undefined;
80
+ let parsed: unknown;
81
+ try {
82
+ parsed = JSON.parse(text);
83
+ } catch {
84
+ return undefined;
85
+ }
86
+ if (!isRecord(parsed)) return undefined;
87
+ const nested = isRecord(parsed.error) ? parsed.error : undefined;
88
+ const detail = isRecord(parsed.detail) ? parsed.detail : undefined;
89
+ for (const candidate of [
90
+ nested?.code,
91
+ nested?.type,
92
+ detail?.error_type,
93
+ parsed.code,
94
+ parsed.type,
95
+ ]) {
96
+ if (typeof candidate === "string" && ERROR_CODE_RE.test(candidate)) return candidate;
97
+ }
98
+ // Cloudflare's envelope: `{ success: false, errors: [{ code: 10000, message }] }`.
99
+ return cloudflareErrorCode(parsed);
100
+ }
101
+
102
+ function defaultSleep(ms: number, signal: AbortSignal): Promise<void> {
103
+ return new Promise((resolve) => {
104
+ if (signal.aborted) return resolve();
105
+ const timer = setTimeout(done, ms);
106
+ function done() {
107
+ clearTimeout(timer);
108
+ signal.removeEventListener("abort", done);
109
+ resolve();
110
+ }
111
+ signal.addEventListener("abort", done, { once: true });
112
+ });
113
+ }
114
+
115
+ /**
116
+ * POST once, retrying only transient failures inside one time budget.
117
+ * Retries: connection errors, HTTP 408 / 429 / 5xx (incl. 529). Never: 401, 422,
118
+ * other 4xx, redirects, or a bad success body (the caller validates that).
119
+ */
120
+ export async function sendSystemOneRequest(
121
+ request: SystemOneRequest,
122
+ options: SystemOneTransportOptions = {},
123
+ ): Promise<SystemOneSendResult> {
124
+ const { timeoutMs, maxRetries } = request;
125
+ const doFetch = options.fetch ?? fetch;
126
+ const sleep = options.sleep ?? defaultSleep;
127
+ const now = options.now ?? Date.now;
128
+ const random = options.random ?? Math.random;
129
+
130
+ const deadlineAt = now() + timeoutMs - DEADLINE_MARGIN_MS;
131
+ const controller = new AbortController();
132
+ const timer = setTimeout(() => controller.abort(), Math.max(0, deadlineAt - now()));
133
+ const timedOut = () =>
134
+ ({
135
+ ok: false,
136
+ error: `SystemOne request exceeded its ${timeoutMs}ms time budget`,
137
+ }) as const;
138
+
139
+ try {
140
+ for (let attempt = 0; ; attempt++) {
141
+ const result = await attemptOnce(doFetch, request, controller.signal, now);
142
+ if (result.kind === "ok") {
143
+ return { ok: true, text: result.text, requestId: result.requestId };
144
+ }
145
+ if (result.kind === "timeout" || controller.signal.aborted) return timedOut();
146
+
147
+ const attempts = attempt + 1;
148
+ const suffix = attempts > 1 ? ` after ${attempts} attempts` : "";
149
+ if (!result.retryable || attempt >= maxRetries) {
150
+ return { ok: false, error: `${result.message}${suffix}` };
151
+ }
152
+
153
+ const backoff = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** attempt);
154
+ const delay = result.retryAfterMs ?? Math.round(backoff * (0.5 + random() * 0.5));
155
+ if (delay >= deadlineAt - now()) {
156
+ return {
157
+ ok: false,
158
+ error: `${result.message}${suffix}; the ${delay}ms retry delay does not fit the ${timeoutMs}ms time budget`,
159
+ };
160
+ }
161
+ await sleep(delay, controller.signal);
162
+ if (controller.signal.aborted) return timedOut();
163
+ }
164
+ } finally {
165
+ clearTimeout(timer);
166
+ }
167
+ }
168
+
169
+ async function attemptOnce(
170
+ doFetch: typeof fetch,
171
+ request: SystemOneRequest,
172
+ signal: AbortSignal,
173
+ now: () => number,
174
+ ): Promise<Attempt> {
175
+ const { endpoint, apiKey, body } = request;
176
+ let res: Response;
177
+ try {
178
+ res = await doFetch(endpoint, {
179
+ method: "POST",
180
+ headers: {
181
+ ...getOpenRouterAttributionHeaders(endpoint, request.env),
182
+ "content-type": "application/json",
183
+ accept: "application/json",
184
+ authorization: `Bearer ${apiKey}`,
185
+ },
186
+ body,
187
+ redirect: "manual",
188
+ signal,
189
+ });
190
+ } catch (err) {
191
+ if (signal.aborted) return { kind: "timeout" };
192
+ const code = (err as { code?: unknown })?.code;
193
+ const detail = typeof code === "string" && /^[A-Z0-9_]{3,40}$/.test(code) ? ` (${code})` : "";
194
+ return {
195
+ kind: "fail",
196
+ message: `SystemOne request failed: network error${detail}`,
197
+ retryable: true,
198
+ };
199
+ }
200
+
201
+ const requestId = readRequestId(res.headers);
202
+ const withId = (message: string) => (requestId ? `${message} [request ${requestId}]` : message);
203
+
204
+ if (res.status >= 200 && res.status < 300) {
205
+ let text: string;
206
+ try {
207
+ text = await res.text();
208
+ } catch {
209
+ if (signal.aborted) return { kind: "timeout" };
210
+ return {
211
+ kind: "fail",
212
+ message: withId("SystemOne response body could not be read"),
213
+ retryable: true,
214
+ };
215
+ }
216
+ if (text.length > MAX_RESPONSE_CHARS) {
217
+ return {
218
+ kind: "fail",
219
+ message: withId("SystemOne response exceeded the size limit"),
220
+ retryable: false,
221
+ };
222
+ }
223
+ return { kind: "ok", text, requestId };
224
+ }
225
+
226
+ if (res.status >= 300 && res.status < 400) {
227
+ return {
228
+ kind: "fail",
229
+ message: withId(
230
+ `SystemOne request was redirected (HTTP ${res.status}); redirects are refused`,
231
+ ),
232
+ retryable: false,
233
+ };
234
+ }
235
+
236
+ let errorText = "";
237
+ try {
238
+ errorText = await res.text();
239
+ } catch {
240
+ if (signal.aborted) return { kind: "timeout" };
241
+ }
242
+ const code = readErrorCode(errorText);
243
+ // The host answered but does not accept the key: name it, never retry.
244
+ if (res.status === 401 || res.status === 403) {
245
+ return {
246
+ kind: "fail",
247
+ message: withId(
248
+ systemOneKeyRejectedMessage(request.providerId, res.status, code, {
249
+ keyName: request.keyName,
250
+ capability: request.capability,
251
+ }),
252
+ ),
253
+ retryable: false,
254
+ };
255
+ }
256
+ const detail = code ? ` (${code})` : "";
257
+ const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
258
+ return {
259
+ kind: "fail",
260
+ message: withId(`SystemOne API returned HTTP ${res.status}${detail}`),
261
+ retryable,
262
+ retryAfterMs: retryable ? parseRetryAfter(res.headers.get("retry-after"), now()) : undefined,
263
+ };
264
+ }
@@ -1,6 +1,6 @@
1
1
  # slack-reply-gate
2
2
 
3
- Lead 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.
3
+ Lead 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.
4
4
 
5
5
  Install config:
6
6
 
@@ -15,8 +15,8 @@ Start in `shadow`, read the decisions in KV for a few days, then switch to `enfo
15
15
  | `mode` | Effect |
16
16
  |---|---|
17
17
  | `off` | Never evaluates. |
18
- | `shadow` (default) | Asks Jev and records the verdict. Never blocks. |
19
- | `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. |
18
+ | `shadow` (default) | Asks the model and records the verdict. Never blocks. |
19
+ | `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. |
20
20
 
21
21
  ## What it gates
22
22
 
@@ -26,28 +26,30 @@ The header counts only at the start of the description or right after the closin
26
26
 
27
27
  ## Decision
28
28
 
29
- 1. A follow-up with downloaded files (status `draft`) passes without a Jev call.
30
- 2. **@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.
31
- 3. 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`.
29
+ 1. A follow-up with downloaded files (status `draft`) passes without a decision call.
30
+ 2. **@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.
31
+ 3. 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`.
32
32
  4. 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.
33
33
 
34
34
  Several buffered messages are judged together. One mention in any of them passes the batch.
35
35
 
36
- ## Jev transport
36
+ ## Provider
37
37
 
38
- Jev is reachable two ways. Both take the same request and return the same answer, so the decision is identical whichever one runs.
38
+ The 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.
39
39
 
40
- | `provider` | Endpoint | Key | Model |
41
- |---|---|---|---|
42
- | `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` (or `apiKey`) | `model`, default `jev-latest` |
43
- | `openrouter` | `POST https://openrouter.ai/api/alpha/decisions` | `OPENROUTER_API_KEY` | `openrouterModel`, default `typesafe/jev-1.13` |
44
- | `auto` (default) | TypeSafe when its key is set, else OpenRouter | | |
40
+ | `provider` | Model | Credential |
41
+ |---|---|---|
42
+ | `typesafe` | Jev, `model` (default `jev-latest`) | `TYPESAFE_API_KEY` |
43
+ | `openrouter` | Jev, `openrouterModel` (default `typesafe/jev-1.13`) | `OPENROUTER_API_KEY` |
44
+ | `cloudflare` | Clef, `cloudflareModel` (default `clef`, or `clef-flash`) | `CLOUDFLARE_API_TOKEN` (or `CLOUDFLARE_API_KEY`) and `CLOUDFLARE_ACCOUNT_ID` |
45
+ | `laya`, `openai` | The host's default model | See the workflows guide |
46
+ | `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 | |
45
47
 
46
- Under `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.
48
+ With 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.
47
49
 
48
50
  ## Fail open
49
51
 
50
- Every 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.
52
+ Every 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.
51
53
 
52
54
  ## :mute: on drop (enforce)
53
55
 
@@ -63,23 +65,23 @@ The block returns first and the Slack calls run after it. A slow or failing Slac
63
65
  | Key | Default | Notes |
64
66
  |---|---|---|
65
67
  | `mode` | `shadow` | `off`, `shadow`, or `enforce` |
66
- | `timeoutMs` | `3000` | Jev call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |
67
- | `provider` | `auto` | `auto`, `typesafe`, or `openrouter`. See [Jev transport](#jev-transport) |
68
+ | `timeoutMs` | `3000` | Decision call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |
69
+ | `provider` | `auto` | `auto`, `typesafe`, `openrouter`, `cloudflare`, `laya`, or `openai`. See [Provider](#provider) |
68
70
  | `model` | `jev-latest` | Jev model on TypeSafe |
69
71
  | `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |
72
+ | `cloudflareModel` | `clef` | Clef model on Cloudflare Workers AI |
70
73
  | `minConfidence` | `0.55` | Minimum summed skip probability to skip |
71
- | `contextMessages` | `6` | Thread messages sent to Jev, 0 to 20 |
74
+ | `contextMessages` | `6` | Thread messages sent to the model, 0 to 20 |
72
75
  | `leadAliases` | `["lead"]` | A literal `@alias` in a new message counts as a mention |
73
- | `botSlackUserIds` | `[]` | Slack user ids labelled `bot` as the sender for Jev |
74
- | `apiKey` | unset | Explicit TypeSafe key. Prefer the `TYPESAFE_API_KEY` secret below |
76
+ | `botSlackUserIds` | `[]` | Slack user ids labelled `bot` as the sender for the model |
75
77
  | `muteReaction` | `mute` | Reaction for a dropped message; `""` only removes the acceptance reaction(s) |
76
78
  | `slackTimeoutMs` | `5000` | Timeout per Slack call |
77
79
 
78
80
  ## Required secrets
79
81
 
80
- All 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.
82
+ Set 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.
81
83
 
82
- - `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.
84
+ - 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.
83
85
  - `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`.
84
86
 
85
87
  No key is written to config, KV, logs, or a description.