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