@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
@@ -13,11 +13,15 @@ export const argsSchema = z.object({
13
13
  .min(0)
14
14
  .max(1)
15
15
  .optional()
16
- .describe("Quiet when Jev's p(needs action) is below this (default 0.5)"),
16
+ .describe(
17
+ "Quiet when the model's p(needs action) is below this (default: the provider's measured threshold, 0.5 for Jev, 0.2 for Clef)",
18
+ ),
17
19
  provider: z
18
- .enum(["auto", "typesafe", "openrouter"])
20
+ .enum(["auto", "typesafe", "openrouter", "cloudflare"])
19
21
  .optional()
20
- .describe("Jev host; auto prefers TypeSafe (default auto)"),
22
+ .describe(
23
+ "Decision host: typesafe or openrouter (Jev), cloudflare (Clef). auto tries them in that order (default auto)",
24
+ ),
21
25
  timeZone: z
22
26
  .string()
23
27
  .optional()
@@ -28,25 +32,56 @@ export const argsSchema = z.object({
28
32
  .min(1_000)
29
33
  .max(45_000)
30
34
  .optional()
31
- .describe("Abandon the Jev call after this many ms (default 25000); the gate deadline is 60 s"),
35
+ .describe(
36
+ "Abandon the decision call after this many ms (default 25000); the gate deadline is 60 s",
37
+ ),
32
38
  });
33
39
 
34
40
  const KV_NAMESPACE = "heartbeat-jev-gate";
35
41
  const KV_KEY = "setup";
36
- // Measured per decision in the 2026-10-15 heartbeat replay (229 states).
37
- const COST_PER_DECISION = "$0.00004";
42
+ // Jev's cost, measured per decision in the 2026-10-15 heartbeat replay (229 states).
43
+ const JEV_COST_PER_DECISION = "$0.00004";
44
+ // Clef replayed on the same 229 states and labels (2026-10-20): 0.2 is the highest
45
+ // threshold that misses none of the 9 ticks where the Lead acted (57 quiet ticks;
46
+ // Jev at 0.5 has 100). Above 0.21 Clef starts missing real asks.
47
+ const CLEF_THRESHOLD = 0.2;
38
48
 
39
- // Jev is called through `ctx.swarm.systemOne_decide`: the API server reads the
40
- // key and sends the request, so no key ever enters this script's realm. The
49
+ // The model is called through `ctx.swarm.systemOne_decide`: the API server reads
50
+ // the key and sends the request, so no key ever enters this script's realm. The
41
51
  // server also refuses OpenRouter when the effective OPENROUTER_BASE_URL (stored
42
- // config or deployment env) points at a gateway.
52
+ // config or deployment env) points at a gateway; `auto` then tries the next
53
+ // usable provider, since this script cannot read the deployment env.
54
+ //
55
+ // `credentials` lists what must be set: every group needs one of its keys.
56
+ // `threshold` is the default the provider's own replay measured (see `CLEF_THRESHOLD`).
43
57
  const PROVIDERS = {
44
- typesafe: { keyName: "TYPESAFE_API_KEY", model: "jev-latest" },
45
- openrouter: { keyName: "OPENROUTER_API_KEY", model: "~typesafe/jev-latest" },
58
+ typesafe: {
59
+ model: "jev-latest",
60
+ family: "jev",
61
+ threshold: 0.5,
62
+ credentials: [["TYPESAFE_API_KEY"]],
63
+ setup: "`TYPESAFE_API_KEY` (Jev on TypeSafe)",
64
+ },
65
+ openrouter: {
66
+ model: "~typesafe/jev-latest",
67
+ family: "jev",
68
+ threshold: 0.5,
69
+ credentials: [["OPENROUTER_API_KEY"]],
70
+ setup: "`OPENROUTER_API_KEY` (Jev on OpenRouter)",
71
+ },
72
+ cloudflare: {
73
+ model: "clef",
74
+ family: "clef",
75
+ threshold: CLEF_THRESHOLD,
76
+ credentials: [["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_KEY"], ["CLOUDFLARE_ACCOUNT_ID"]],
77
+ setup:
78
+ "`CLOUDFLARE_API_TOKEN` (or `CLOUDFLARE_API_KEY`) as a secret plus the `CLOUDFLARE_ACCOUNT_ID` config value (Clef on Cloudflare Workers AI)",
79
+ },
46
80
  } as const;
47
81
  type ProviderId = keyof typeof PROVIDERS;
82
+ const AUTO_ORDER: ProviderId[] = ["typesafe", "openrouter", "cloudflare"];
48
83
 
49
- // Worded exactly as in the replay that set the 0.5 threshold; changing it voids that result.
84
+ // Worded exactly as in the replays that set the thresholds; changing it voids those results.
50
85
  const QUESTIONS = {
51
86
  act: {
52
87
  type: "noul",
@@ -189,11 +224,20 @@ async function buildState(ctx: any, leadId: string, nowMs: number, timeZone: str
189
224
  const mmdd = new Date(nowMs).toISOString().slice(5, 10);
190
225
  const mmddLocal = stamp.slice(5, 10);
191
226
  const due: string[] = [];
227
+ // An MM-DD counts only as a bare token: "release on 10-20" is due, while a
228
+ // re-verification stamp ("2026-10-20 ~10:45Z", a UUID piece) and a suffixed
229
+ // token ("10-20s") are not.
230
+ const mentionsMmdd = (s: string, tok: string) =>
231
+ new RegExp(`(?<![\\w-])${tok}(?![\\w-])`).test(s);
192
232
  for (const line of String(lead[0]?.heartbeatMd ?? "").split("\n")) {
193
233
  const l = line.trim();
194
234
  if (!l) continue;
195
235
  const del = /DELETE <?(\d\d-\d\d)>?/.exec(l);
196
- if (l.includes(mmdd) || l.includes(mmddLocal) || (del?.[1] && del[1] <= mmddLocal)) {
236
+ if (
237
+ mentionsMmdd(l, mmdd) ||
238
+ mentionsMmdd(l, mmddLocal) ||
239
+ (del?.[1] && del[1] <= mmddLocal)
240
+ ) {
197
241
  due.push(oneLine(l).slice(0, 200));
198
242
  }
199
243
  if (due.length >= 6) break;
@@ -220,21 +264,25 @@ async function buildState(ctx: any, leadId: string, nowMs: number, timeZone: str
220
264
  };
221
265
  }
222
266
 
223
- function setupPrompt(keys: string[]) {
267
+ function setupPrompt(candidates: ProviderId[]) {
268
+ const options = candidates.map((id) => `- ${PROVIDERS[id].setup}`);
224
269
  return {
225
- title: "Heartbeat Jev gate: add a Jev credential or decline",
270
+ title: "Heartbeat gate: add a decision model credential or decline",
226
271
  detached: true,
227
272
  questions: [
228
273
  {
229
274
  id: "enable",
230
275
  type: "approval",
231
- label: "Enable the Jev heartbeat gate?",
276
+ label: "Enable the heartbeat checklist gate?",
232
277
  description: [
233
- "Before each recurring heartbeat checklist, the `heartbeat-jev-gate` script sends a short summary of swarm state (about 1,500 characters: new failures, due tracked items, task and agent counts) to Jev, a small decision model by TypeSafe, and asks whether the Lead needs to act. With `HEARTBEAT_CHECKLIST_GATE_MODE=enforce`, quiet ticks skip the Lead's checklist task.",
278
+ "Before each recurring heartbeat checklist, the `heartbeat-jev-gate` script sends a short summary of swarm state (about 1,500 characters: new failures, due tracked items, task and agent counts) to a small SystemOne decision model (Jev by TypeSafe, or Clef on Cloudflare Workers AI) and asks whether the Lead needs to act. With `HEARTBEAT_CHECKLIST_GATE_MODE=enforce`, quiet ticks skip the Lead's checklist task.",
234
279
  "",
235
- `Cost: about ${COST_PER_DECISION} per decision, one decision per checklist tick.`,
280
+ `Cost: one decision per checklist tick, about ${JEV_COST_PER_DECISION} each with Jev.`,
236
281
  "",
237
- `No Jev credential is configured. To enable, approve and add one of ${keys.map((k) => `\`${k}\``).join(" or ")} as a global secret on the Secrets page (Settings > Secrets). Do not paste the key here. The next tick picks it up.`,
282
+ "No credential for the gate is configured. To enable, approve and add one of these as a global value on the Secrets page (Settings > Secrets):",
283
+ ...options,
284
+ "",
285
+ "Do not paste a key here. The next tick picks it up.",
238
286
  "",
239
287
  "Reject to decline. The gate then stays inactive, with no further prompts, until a key is added.",
240
288
  ].join("\n"),
@@ -244,9 +292,10 @@ function setupPrompt(keys: string[]) {
244
292
  }
245
293
 
246
294
  /**
247
- * Heartbeat checklist gate backed by Jev: asks Jev whether a recurring checklist
248
- * tick needs the Lead and returns { quiet, reason, summary }. With no Jev
249
- * credential it asks a human once, then stays a no-op until a key is added.
295
+ * Heartbeat checklist gate backed by a SystemOne decision model (Jev or Clef):
296
+ * asks whether a recurring checklist tick needs the Lead and returns
297
+ * { quiet, reason, summary }. With no credential it asks a human once, then
298
+ * stays a no-op until a key is added.
250
299
  */
251
300
  export default async function heartbeatJevGate(args: any, ctx: any) {
252
301
  const parsed = argsSchema.safeParse(args ?? {});
@@ -267,25 +316,21 @@ export default async function heartbeatJevGate(args: any, ctx: any) {
267
316
  };
268
317
 
269
318
  const cfgThreshold = Number.parseFloat(cfg("HEARTBEAT_JEV_GATE_THRESHOLD"));
270
- const threshold =
271
- a.threshold ?? (cfgThreshold >= 0 && cfgThreshold <= 1 ? cfgThreshold : 0.5);
272
319
  const providerPref = a.provider ?? (cfg("HEARTBEAT_JEV_GATE_PROVIDER").toLowerCase() || "auto");
273
320
  const timeZone = a.timeZone ?? (cfg("HEARTBEAT_JEV_GATE_TIMEZONE") || "UTC");
274
321
 
275
- const usable: Record<ProviderId, boolean> = {
276
- typesafe: cfg(PROVIDERS.typesafe.keyName) !== "",
277
- openrouter: cfg(PROVIDERS.openrouter.keyName) !== "",
278
- };
322
+ const usable = (id: ProviderId) =>
323
+ PROVIDERS[id].credentials.every((group) => group.some((key) => cfg(key) !== ""));
279
324
  const candidates: ProviderId[] =
280
- providerPref === "typesafe"
281
- ? ["typesafe"]
282
- : providerPref === "openrouter"
283
- ? ["openrouter"]
284
- : ["typesafe", "openrouter"];
285
- const provider = candidates.find((id) => usable[id]);
325
+ Object.hasOwn(PROVIDERS, providerPref) ? [providerPref as ProviderId] : AUTO_ORDER;
326
+ const usableCandidates = candidates.filter(usable);
286
327
 
287
- if (!provider) {
288
- const keys = candidates.map((id) => PROVIDERS[id].keyName);
328
+ if (usableCandidates.length === 0) {
329
+ const keys = candidates.map((id) =>
330
+ PROVIDERS[id].credentials
331
+ .map((group) => (group.length > 1 ? `(${group.join(" or ")})` : group[0]))
332
+ .join(" + "),
333
+ );
289
334
  const inactive = (state: string, extra: Record<string, unknown> = {}) => ({
290
335
  quiet: false,
291
336
  reason: `jev-gate inactive: ${state}`.slice(0, 300),
@@ -296,7 +341,7 @@ export default async function heartbeatJevGate(args: any, ctx: any) {
296
341
  let setup: SetupState | null = typeof stored === "string" ? JSON.parse(stored) : (stored ?? null);
297
342
 
298
343
  if (!setup) {
299
- const prompt = setupPrompt(keys);
344
+ const prompt = setupPrompt(candidates);
300
345
  if (dryRun) {
301
346
  const state = await buildState(ctx, leadId, Date.now(), timeZone);
302
347
  return inactive("no credential, would ask a human", { prompt, state });
@@ -340,35 +385,51 @@ export default async function heartbeatJevGate(args: any, ctx: any) {
340
385
  return inactive(why, { setup });
341
386
  }
342
387
 
343
- const target = PROVIDERS[provider];
344
388
  const state = await buildState(ctx, leadId, Date.now(), timeZone);
345
389
 
346
- // The server enforces the time budget. No retries, as in the replay.
347
- const res: any = await ctx.swarm.systemOne_decide({
348
- provider,
349
- model: target.model,
350
- state,
351
- questions: QUESTIONS,
352
- timeoutMs: a.jevTimeoutMs ?? 25_000,
353
- maxRetries: 0,
354
- });
355
- const body: any = res?.data ?? res;
356
- if (res?.success === false || body?.success === false) {
357
- throw new Error(`jev ${provider}: ${String(body?.message ?? body?.error ?? `HTTP ${res?.status}`).slice(0, 250)}`);
390
+ // The server enforces the time budget. No retries, as in the replay. A provider
391
+ // this deployment refuses (OpenRouter behind a gateway) sends nothing, so the
392
+ // next usable one is tried; only an explicit choice or the last one throws.
393
+ let provider = usableCandidates[0] as ProviderId;
394
+ let body: any;
395
+ const skipped: string[] = [];
396
+ for (const [i, id] of usableCandidates.entries()) {
397
+ provider = id;
398
+ const res: any = await ctx.swarm.systemOne_decide({
399
+ provider: id,
400
+ model: PROVIDERS[id].model,
401
+ state,
402
+ questions: QUESTIONS,
403
+ timeoutMs: a.jevTimeoutMs ?? 25_000,
404
+ maxRetries: 0,
405
+ });
406
+ body = res?.data ?? res;
407
+ if (res?.success !== false && body?.success !== false) break;
408
+ const why = String(body?.message ?? body?.error ?? `HTTP ${res?.status}`).slice(0, 250);
409
+ if (body?.providerUnavailable === true && i < usableCandidates.length - 1) {
410
+ skipped.push(`${id}: ${why}`);
411
+ continue;
412
+ }
413
+ throw new Error(`${PROVIDERS[id].family} ${id}: ${why}`);
358
414
  }
415
+ const target = PROVIDERS[provider];
416
+ // A configured threshold applies to every provider; unset, each uses its measured one.
417
+ const threshold =
418
+ a.threshold ?? (cfgThreshold >= 0 && cfgThreshold <= 1 ? cfgThreshold : target.threshold);
359
419
  const p = body?.answers?.act?.noul;
360
420
  if (typeof p !== "number" || !Number.isFinite(p)) {
361
- throw new Error(`jev ${provider}: response has no numeric answers.act.noul`);
421
+ throw new Error(`${target.family} ${provider}: response has no numeric answers.act.noul`);
362
422
  }
363
423
 
364
424
  const pText = p.toFixed(3);
365
425
  const quiet = p < threshold;
366
426
  const verdict: Record<string, unknown> = {
367
427
  quiet,
368
- reason: `jev p(needs action)=${pText} ${quiet ? "<" : ">="} ${threshold} via ${provider}`,
428
+ reason: `${target.family} p(needs action)=${pText} ${quiet ? "<" : ">="} ${threshold} via ${provider}`,
369
429
  };
370
430
  if (!quiet) {
371
- const lines = [`Jev p(needs action) = ${pText} (threshold ${threshold}, ${provider}).`];
431
+ const model = target.family === "jev" ? "Jev" : "Clef";
432
+ const lines = [`${model} p(needs action) = ${pText} (threshold ${threshold}, ${provider}).`];
372
433
  const failures = state.new_failures_since_previous_heartbeat;
373
434
  const tracked = state.tracked_items_mentioning_today_or_past_delete_date;
374
435
  if (Array.isArray(failures)) {
@@ -378,9 +439,9 @@ export default async function heartbeatJevGate(args: any, ctx: any) {
378
439
  lines.push("", "Tracked items due today or past their DELETE date:", ...tracked.map((t) => `- ${t.replace(/^[-*] /, "")}`));
379
440
  }
380
441
  if (!Array.isArray(failures) && !Array.isArray(tracked)) {
381
- lines.push("", "No new failures or due tracked items; Jev flagged the overall state.");
442
+ lines.push("", `No new failures or due tracked items; ${model} flagged the overall state.`);
382
443
  }
383
444
  verdict.summary = lines.join("\n").slice(0, 4000);
384
445
  }
385
- return dryRun ? { ...verdict, dryRun: true, provider, p, threshold, state } : verdict;
446
+ return dryRun ? { ...verdict, dryRun: true, provider, skipped, p, threshold, state } : verdict;
386
447
  }
@@ -116,9 +116,9 @@ export const SEED_SCRIPTS: SeedScript[] = [
116
116
  {
117
117
  name: "heartbeat-jev-gate",
118
118
  description:
119
- "Heartbeat checklist gate backed by Jev (TypeSafe or OpenRouter decisions API): scores a compact swarm state and returns {quiet, reason, summary} for HEARTBEAT_CHECKLIST_GATE_SCRIPT.",
119
+ "Heartbeat checklist gate backed by a SystemOne decision model (Jev on TypeSafe or OpenRouter, Clef on Cloudflare Workers AI): scores a compact swarm state and returns {quiet, reason, summary} for HEARTBEAT_CHECKLIST_GATE_SCRIPT.",
120
120
  intent:
121
- "Skip quiet Lead heartbeat checklist ticks with a cheap Jev decision; with no Jev credential it asks a human once, then stays a no-op until a key is added.",
121
+ "Skip quiet Lead heartbeat checklist ticks with a cheap Jev or Clef decision; with no credential it asks a human once, then stays a no-op until a key is added.",
122
122
  source: asText(heartbeatJevGateSrc),
123
123
  },
124
124
  {
@@ -68,7 +68,11 @@ const RESERVED_KEYS = new Set([
68
68
  // the agent-fs bootstrap admin key would let a compromised worker administer the
69
69
  // shared org. The API materializes these into its own process.env at boot via
70
70
  // getInjectableGlobalConfigs, so no HTTP consumer legitimately needs them.
71
- const API_ONLY_CONFIG_KEYS = new Set(["API_AGENT_FS_API_KEY", "SLACK_SIGNING_SECRET"]);
71
+ const API_ONLY_CONFIG_KEYS = new Set([
72
+ "API_AGENT_FS_API_KEY",
73
+ "SLACK_SIGNING_SECRET",
74
+ "TRANSCRIPTION_API_KEY",
75
+ ]);
72
76
 
73
77
  export function isApiOnlyConfigKey(key: string): boolean {
74
78
  return API_ONLY_CONFIG_KEYS.has(key);
@@ -149,6 +153,31 @@ export const DB_RETENTION_TUNING_BOUNDS = {
149
153
  */
150
154
  export const APPS_JSON_COLUMN_MAX_BYTES_BOUNDS = { min: 1_024, max: 16_777_216 } as const;
151
155
 
156
+ /**
157
+ * Bounds for the Codex OAuth keep-warm age threshold
158
+ * (`CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS`).
159
+ *
160
+ * `src/http/codex-oauth-keep-warm.ts` reads the knob against exactly these at
161
+ * call time and falls back to its default outside them. The 216 h (9 day)
162
+ * ceiling stays under the 9.5 day point where task-time near-expiry refresh
163
+ * takes over, so every accepted value still changes when a slot refreshes.
164
+ */
165
+ export const CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS_BOUNDS = { min: 1, max: 216 } as const;
166
+
167
+ /**
168
+ * Decision hosts `SYSTEM_ONE_DEFAULT_PROVIDER` may name: `SYSTEM_ONE_PROVIDER_IDS`
169
+ * in `src/workflows/executors/system-one-providers.ts`, listed here because that
170
+ * module's credential readers would pull the LLM stack into every `db.ts` import.
171
+ * A test keeps the two lists equal.
172
+ */
173
+ export const SYSTEM_ONE_DEFAULT_PROVIDER_OPTIONS = [
174
+ "typesafe",
175
+ "openrouter",
176
+ "laya",
177
+ "openai",
178
+ "cloudflare",
179
+ ] as const;
180
+
152
181
  /** Build `{ KEY: validator }` entries accepting only boolean literals. */
153
182
  function booleanValidators(keys: string[]): Record<string, ConfigValidator> {
154
183
  const message = (key: string) =>
@@ -388,6 +417,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
388
417
  },
389
418
  ...enumValidator("CLAUDE_TRANSPORT", ["cli", "sdk"]),
390
419
  ...enumValidator("SCRIPT_EXECUTOR", ["native", "quickjs"]),
420
+ ...enumValidator("TRANSCRIPTION_PROVIDER", ["openai", "elevenlabs", "groq"]),
391
421
  SCRIPT_QUICKJS_POOL_SIZE: validateQuickJSPoolSize,
392
422
  ...booleanValidators(["SCRIPT_EGRESS_ALLOW_PRIVATE"]),
393
423
  SCRIPT_EGRESS_ALLOWLIST: (value) =>
@@ -396,6 +426,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
396
426
  // model's weekly window (Fable/Opus/Sonnet). fallback: legacy random pick.
397
427
  ...enumValidator("MODEL_WINDOW_EXHAUSTED_POLICY", ["fail", "fallback"]),
398
428
  ...enumValidator("HEARTBEAT_CHECKLIST_GATE_MODE", ["shadow", "enforce"]),
429
+ ...enumValidator("SYSTEM_ONE_DEFAULT_PROVIDER", [...SYSTEM_ONE_DEFAULT_PROVIDER_OPTIONS]),
399
430
  // Codex credits-exhausted cooldown (ms). Permissive on range here (positive
400
431
  // integer) — the worker clamps to [5m, 7d] via resolveCodexCreditsExhaustedCooldownMs.
401
432
  CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS: (value) => {
@@ -542,6 +573,11 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
542
573
  APPS_JSON_COLUMN_MAX_BYTES_BOUNDS.min,
543
574
  APPS_JSON_COLUMN_MAX_BYTES_BOUNDS.max,
544
575
  ),
576
+ ...boundedIntegerValidators(
577
+ ["CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS"],
578
+ CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS_BOUNDS.min,
579
+ CODEX_OAUTH_KEEP_WARM_MAX_AGE_HOURS_BOUNDS.max,
580
+ ),
545
581
  ...boundedIntegerValidators(
546
582
  [
547
583
  "SESSION_LOG_RETENTION_DAYS",
@@ -566,6 +602,9 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
566
602
  // default in thread-buffer.ts; above 10 minutes a
567
603
  // reply waits longer than any operator expects.
568
604
  ...boundedIntegerValidators(["CHAT_SDK_BUFFER_MS"], 1, 600_000),
605
+ // Minutes a Chat SDK thread keeps its owner after the owner's task ends.
606
+ // 0 keeps it only while the task is open; a day is the longest useful window.
607
+ ...boundedIntegerValidators(["CHAT_SDK_OWNER_WINDOW_MIN"], 0, 1440),
569
608
  // Below ~100 tokens the preamble can't fit a useful summary; above 20000
570
609
  // (~80k chars) it risks the SIGTERM-143 context-saturation failure mode
571
610
  // the cap exists to prevent (see context-preamble.ts).
@@ -68,3 +68,33 @@ export async function recordTaskAttachmentUpload(
68
68
  throw error;
69
69
  }
70
70
  }
71
+
72
+ // Resolve against the row's ACTUAL stored key + its own org/drive, rather than
73
+ // reconstructing `tasks/<taskId>/<name>`. Attachments written to agent-fs via the
74
+ // CLI live at arbitrary paths (`misc/…`, `smoke/…`); reconstruction mis-resolves
75
+ // them, which previously caused downloads to 404 and deletes to orphan the real
76
+ // file. New uploads have no stored key yet at scope-build time and keep the
77
+ // forward-looking layout.
78
+ export function scopeFromAttachment(attachment: TaskAttachment): FileScope {
79
+ const storedKey = attachment.providerKey ?? attachment.path ?? "";
80
+ return {
81
+ taskId: attachment.taskId,
82
+ name: attachment.name,
83
+ key: storedKey.trim() ? storedKey : undefined,
84
+ orgId: attachment.orgId,
85
+ driveId: attachment.driveId,
86
+ };
87
+ }
88
+
89
+ // Which file-storage provider (if any) actually holds this attachment's bytes.
90
+ // Only agent-fs objects and local-fs uploads are provider-backed; `shared-fs`
91
+ // worker-volume pointers, `url`, and `page` attachments have no provider object,
92
+ // so they must never be fetched or deleted through a provider (doing so would
93
+ // mis-resolve and, on delete, orphan the real target). Note local-fs uploads are
94
+ // recorded with kind "shared-fs" but an explicit local-fs providerId, so key off
95
+ // both signals rather than kind alone.
96
+ export function backingProviderId(attachment: TaskAttachment): string | null {
97
+ if (attachment.kind === "agent-fs") return attachment.providerId ?? "agent-fs";
98
+ if (attachment.providerId === "local-fs") return "local-fs";
99
+ return null;
100
+ }