@desplega.ai/agent-swarm 1.166.0 → 1.168.0
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-zhejfecx.js +34 -0
- package/dist/{actions-qz3m692w.js → actions-7jg840nr.js} +25 -25
- package/dist/{amp-adapter-j1xbmsqz.js → amp-adapter-ye6rnetm.js} +4 -4
- package/dist/{anthropic-messages-hst62ws9.js → anthropic-messages-13yn502s.js} +4 -4
- package/dist/{app-43vrdmv8.js → app-ffes2kyf.js} +10 -9
- package/dist/{assistant-ertffmka.js → assistant-fhm5smyz.js} +26 -26
- package/dist/{azure-openai-responses-mh8tvf78.js → azure-openai-responses-9st34pfz.js} +5 -5
- package/dist/{boot-reembed-998jtpdg.js → boot-reembed-20q3nzvg.js} +9 -8
- package/dist/{boot-reembed-s7k7c6v2.js → boot-reembed-jvfadhsk.js} +9 -8
- package/dist/{boot-scrub-sweep-nepsk4t1.js → boot-scrub-sweep-jtfajzve.js} +10 -9
- package/dist/{boot-seeds-vvzs1zhk.js → boot-seeds-9qt0j8j2.js} +12 -11
- package/dist/{claude-adapter-2zvmekrp.js → claude-adapter-a8znhthz.js} +24 -22
- package/dist/{claude-managed-adapter-s49wbk8t.js → claude-managed-adapter-tzwxy803.js} +4 -4
- package/dist/{claude-managed-setup-cvhqq8qn.js → claude-managed-setup-nrhfx5b0.js} +3 -3
- package/dist/{claude-sdk-session-kkbxtw5q.js → claude-sdk-session-7x7chdrs.js} +7608 -7596
- package/dist/{cli-zsh093hk.js → cli-014vns2y.js} +63 -1
- package/dist/{cli-8kgvmkak.js → cli-0jngabtb.js} +1 -1
- package/dist/{cli-qvfk83fy.js → cli-13xwcqvj.js} +2 -2
- package/dist/{cli-0nhdwxd5.js → cli-43jg9xdw.js} +3 -3
- package/dist/{cli-cpfmqz3n.js → cli-47zfzshm.js} +5 -5
- package/dist/{cli-zy7vwsne.js → cli-5qkcdv8c.js} +16 -9
- package/dist/{cli-cwxhbf43.js → cli-5t5my0h8.js} +85 -2
- package/dist/{cli-y59h2pk9.js → cli-69egj654.js} +1 -1
- package/dist/{cli-68r1j4sg.js → cli-6bpvv8h4.js} +3 -3
- package/dist/{cli-yshaazs1.js → cli-74ez9b66.js} +63 -20
- package/dist/{cli-32bca7nr.js → cli-7avy4v8w.js} +1 -1
- package/dist/{cli-f2n17zhp.js → cli-7mhy3ths.js} +21 -18
- package/dist/{cli-q38n4bq0.js → cli-95zs84qw.js} +3 -3
- package/dist/{cli-fn697bnt.js → cli-9q0bh411.js} +21 -8
- package/dist/{cli-rggdq8wq.js → cli-9q24yyvs.js} +3 -3
- package/dist/cli-a35hzdve.js +33 -0
- package/dist/{cli-3qn4r334.js → cli-aj1e0h0d.js} +20 -14
- package/dist/{cli-rsy933yz.js → cli-bcspch6h.js} +30 -19
- package/dist/{cli-x3x0qwky.js → cli-bxqez1p0.js} +51 -23
- package/dist/{cli-ng97fjq7.js → cli-cmxkbzzs.js} +33 -7
- package/dist/{cli-df56tqdw.js → cli-ee0402n1.js} +198 -70
- package/dist/{cli-mwegs4af.js → cli-ew0c3a1j.js} +3 -3
- package/dist/{cli-ysnyhws9.js → cli-ewhwpyrb.js} +54 -51
- package/dist/{cli-j2jbr8af.js → cli-fhj4y16f.js} +1 -1
- package/dist/{cli-8t229cmr.js → cli-fqq06kkb.js} +30 -17
- package/dist/{cli-jvj6a0cv.js → cli-g6gx2dn1.js} +54 -19
- package/dist/{cli-nexwh8zg.js → cli-g7k7zbcz.js} +12 -5
- package/dist/{cli-ecknp7p1.js → cli-j05x7pnb.js} +10 -10
- package/dist/{cli-gt8azyet.js → cli-jb931ba2.js} +1 -1
- package/dist/{cli-nvvfd8bq.js → cli-jbzbr36n.js} +4091 -3793
- package/dist/{cli-3ddybbbx.js → cli-jvmtxqtc.js} +26 -14
- package/dist/{cli-pkke6geq.js → cli-km4x66j0.js} +83 -65
- package/dist/{cli-b0f70z24.js → cli-mn8qkd8v.js} +1 -1
- package/dist/{cli-0qjcsxgz.js → cli-nctfv2yy.js} +99 -9
- package/dist/{cli-4r1sjfc4.js → cli-ng1pmdx6.js} +10 -10
- package/dist/cli-nkqjj42q.js +104 -0
- package/dist/{cli-aqcey741.js → cli-p5jqk8fr.js} +1 -1
- package/dist/{cli-66zh26sp.js → cli-pgehz8e1.js} +57 -17
- package/dist/cli-q1vnz8wv.js +96 -0
- package/dist/{cli-fj86xr3c.js → cli-qd97v6z2.js} +2 -2
- package/dist/{cli-p4ztb80h.js → cli-r7eeddkq.js} +13 -8
- package/dist/{cli-8ssjez7e.js → cli-rhy7z1rp.js} +22 -11
- package/dist/{cli-vzc73wet.js → cli-rr5z1j4p.js} +87 -69
- package/dist/{cli-4mevd1hf.js → cli-s9y81s7j.js} +5 -5
- package/dist/{cli-dw2q9f7g.js → cli-svah6b6w.js} +4 -4
- package/dist/{cli-h3x613ze.js → cli-tc5zec7v.js} +6 -6
- package/dist/{cli-ttbsse3f.js → cli-tmhsxhb8.js} +1 -1
- package/dist/{cli-feh3dkah.js → cli-v1w7pbd8.js} +2 -1
- package/dist/{cli-10zmp1p7.js → cli-vr5avem2.js} +27 -32
- package/dist/{cli-bzr0ftht.js → cli-wnb9b2g3.js} +19 -0
- package/dist/{cli-krqm5f11.js → cli-xezn9nkh.js} +16 -13
- package/dist/{cli-8kt0bmgx.js → cli-xf713zz0.js} +2 -2
- package/dist/{cli-6zcfw0x7.js → cli-y195hv1a.js} +1 -1
- package/dist/{cli-x7kpr72a.js → cli-ytmmyj7f.js} +2 -2
- package/dist/{cli-v2a8x45d.js → cli-z48ggfyq.js} +11 -6
- package/dist/{cli-qbksc292.js → cli-zy39d433.js} +2 -0
- package/dist/cli.js +28 -28
- package/dist/{cloudflare-workers-ai-system-one-tt484t61.js → cloudflare-workers-ai-system-one-sta45jf9.js} +7 -5
- package/dist/{codex-adapter-dw1wxqt8.js → codex-adapter-n63k33d3.js} +18 -18
- package/dist/{codex-login-terbdsqq.js → codex-login-g1cyt5zm.js} +1 -1
- package/dist/{codex-session-runner-3cgb8y1g.js → codex-session-runner-xkxfd81a.js} +18 -18
- package/dist/{commands-mhxam6z2.js → commands-q4mfhn51.js} +9 -8
- package/dist/{cursor-adapter-2qda3st7.js → cursor-adapter-v8mm6bp5.js} +5 -5
- package/dist/{db-sjks418k.js → db-1t0bj3pc.js} +9 -8
- package/dist/{dispatcher-1es759rn.js → dispatcher-k1fhavcf.js} +24 -24
- package/dist/{dsh-adapter-bek4cyw9.js → dsh-adapter-5423p70m.js} +5 -5
- package/dist/{easter-egg-3d-mtpnw06s.js → easter-egg-3d-66zxervv.js} +2 -2
- package/dist/{execute-s4ccjq3n.js → execute-h4jgevyw.js} +61 -11
- package/dist/{google-generative-ai-d6nphg3q.js → google-generative-ai-b8zxrpax.js} +5 -5
- package/dist/{google-vertex-cpshkref.js → google-vertex-nrdd697t.js} +5 -5
- package/dist/{grok-adapter-ar9cw9jg.js → grok-adapter-phrndeb8.js} +18 -8
- package/dist/{handlers-1gkbhktr.js → handlers-59ywqwzh.js} +27 -27
- package/dist/{hook-vxa3tcsy.js → hook-atj0swc2.js} +14 -14
- package/dist/{hook-ds2zxq9v.js → hook-mew1chq9.js} +42 -31
- package/dist/{http-3h21z9md.js → http-ef2fnn9v.js} +953 -98
- package/dist/{index-n99fycq7.js → index-48pb9erw.js} +24 -24
- package/dist/{index-bn6cy8ps.js → index-9fc2h7ww.js} +24 -24
- package/dist/{index-9b65j1m5.js → index-gr69c43q.js} +9 -8
- package/dist/{index-f8xnqrye.js → index-h26wjcfy.js} +43 -24
- package/dist/{index-dkfxpkmm.js → index-w2yhye23.js} +25 -25
- package/dist/{keepalive-3xspa9jw.js → keepalive-qhcqz56v.js} +11 -10
- package/dist/{lead-6qphc124.js → lead-hpk5r3b1.js} +47 -47
- package/dist/{llama-cpp-classify-tgj35k8d.js → llama-cpp-classify-6erb7wwv.js} +3 -1
- package/dist/{maintenance-0teem1bb.js → maintenance-nvkyp2xe.js} +9 -8
- package/dist/{mistral-conversations-8445ddjn.js → mistral-conversations-059y51zt.js} +4 -4
- package/dist/{node-9sg7ajby.js → node-qm9eb6z0.js} +1 -1
- package/dist/{oauth-refresh-sweep-sh9q25xp.js → oauth-refresh-sweep-ngskthwh.js} +10 -9
- package/dist/{onboard-54qmnqxe.js → onboard-yfnkqhxc.js} +2 -2
- package/dist/{openai-codex-responses-9ynm88zy.js → openai-codex-responses-jzxkta9b.js} +8 -7
- package/dist/{openai-completions-nhqwjmaw.js → openai-completions-663yncfq.js} +4 -4
- package/dist/openai-decisions-vj9nkpf8.js +162 -0
- package/dist/{openai-responses-xx1c4kgm.js → openai-responses-m3g43rrt.js} +5 -5
- package/dist/{opencode-adapter-wjfa2wvd.js → opencode-adapter-yjyqynzf.js} +7 -7
- package/dist/{openrouter-images-8yx3487g.js → openrouter-images-kr60n6b9.js} +1 -1
- package/dist/{otel-impl-ycqzp2a1.js → otel-impl-hhm64r6m.js} +1 -1
- package/dist/{pi-messages-vxdt8v7j.js → pi-messages-xszk613n.js} +1 -1
- package/dist/{pi-mono-adapter-e77e9zst.js → pi-mono-adapter-0eg8kw2c.js} +68 -51
- package/dist/{pricing-refresh-atnkngqs.js → pricing-refresh-35f91nx2.js} +11 -10
- package/dist/{rbac-roles-j78wdksm.js → rbac-roles-97w370qj.js} +10 -9
- package/dist/{rbac-roles-9pjphh9q.js → rbac-roles-z6sje3gy.js} +9 -8
- package/dist/{render-v2-pxjs49fx.js → render-v2-n4v66bj3.js} +10 -9
- package/dist/{runtime-v2t1r79e.js → runtime-h9ha1mq6.js} +4 -4
- package/dist/{setup-ymej0xtx.js → setup-m3hhyv5f.js} +2 -2
- package/dist/{typesafe-system-one-datfr82p.js → typesafe-system-one-e988vty7.js} +7 -5
- package/dist/{virtual-modules-wm8mxdxx.js → virtual-modules-ftr9kwe8.js} +11 -11
- package/dist/{work-objects-3nh4ymzb.js → work-objects-mxf2c842.js} +27 -27
- package/dist/{worker-qa9fkz5s.js → worker-9t07z1gq.js} +47 -47
- package/openapi.json +72 -1
- package/package.json +10 -10
- package/src/be/memory/providers/openai-embedding.ts +7 -2
- package/src/be/memory/raters/llm-summarizer.ts +7 -2
- package/src/be/modelsdev-cache.json +61 -0
- package/src/be/script-connections.ts +40 -6
- package/src/be/seed-pricing.ts +84 -53
- package/src/be/swarm-config-guard.ts +14 -0
- package/src/commands/provider-credentials.ts +28 -5
- package/src/commands/runner.ts +7 -12
- package/src/extensions/catalog.generated.json +17 -0
- package/src/heartbeat/heartbeat.ts +91 -19
- package/src/hooks/hook.ts +72 -27
- package/src/http/mcp.ts +5 -0
- package/src/http/onboarding.ts +2 -0
- package/src/http/script-connections.ts +35 -0
- package/src/providers/acp-targets.ts +10 -5
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +32 -0
- package/src/providers/claude-background-keepalive.ts +100 -0
- package/src/providers/claude-sdk-session.ts +6 -0
- package/src/providers/codex-adapter.ts +30 -3
- package/src/providers/codex-oauth/auth-json.ts +21 -4
- package/src/providers/codex-oauth/types.ts +2 -0
- package/src/providers/dsh-adapter.ts +11 -2
- package/src/providers/grok-adapter.ts +18 -2
- package/src/providers/opencode-adapter.ts +21 -5
- package/src/providers/pi-mono-extension.ts +56 -25
- package/src/providers/pricing-sources.md +2 -0
- package/src/scheduler/scheduler.ts +82 -13
- package/src/scripts-runtime/executors/registry.ts +1 -1
- package/src/server.ts +63 -3
- package/src/tests/acp-adapter.test.ts +152 -12
- package/src/tests/ci-unit-test-report.test.ts +73 -0
- package/src/tests/claude-background-keepalive-wiring.test.ts +220 -0
- package/src/tests/claude-background-keepalive.fixture.ts +162 -0
- package/src/tests/claude-background-keepalive.test.ts +139 -0
- package/src/tests/claude-default-route.test.ts +75 -0
- package/src/tests/claude-hook-output.test.ts +233 -0
- package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +4 -2
- package/src/tests/codex-adapter.test.ts +166 -26
- package/src/tests/codex-oauth-auth-timestamps.test.ts +76 -0
- package/src/tests/codex-oauth.test.ts +4 -1
- package/src/tests/connection-embedded-auth.test.ts +51 -0
- package/src/tests/context-window.test.ts +2 -2
- package/src/tests/credential-check.test.ts +51 -1
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +24 -2
- package/src/tests/enabled-tools.test.ts +269 -0
- package/src/tests/entrypoint-codex-oauth-seed.test.ts +12 -0
- package/src/tests/extensions-catalog.test.ts +1 -0
- package/src/tests/extensions-example-slack-reply-gate.test.ts +756 -0
- package/src/tests/grok-adapter.test.ts +13 -0
- package/src/tests/haiku-5-5-pricing.test.ts +38 -0
- package/src/tests/heartbeat-checklist.test.ts +158 -1
- package/src/tests/onboarding.test.ts +44 -0
- package/src/tests/opencode-adapter.test.ts +66 -3
- package/src/tests/opencode-plugin.test.ts +37 -0
- package/src/tests/openrouter-attribution-call-sites.test.ts +204 -0
- package/src/tests/openrouter-base-url.test.ts +30 -39
- package/src/tests/pi-mono-extension.test.ts +161 -2
- package/src/tests/pricing-refresh.test.ts +70 -1
- package/src/tests/reasoning-effort.test.ts +3 -1
- package/src/tests/schedule-target-type.test.ts +136 -0
- package/src/tests/script-connections-http.test.ts +45 -0
- package/src/tests/script-connections-mcp.test.ts +41 -0
- package/src/tests/script-executor-registry.test.ts +18 -0
- package/src/tests/send-task-self-send.test.ts +107 -0
- package/src/tests/telemetry-trigger.test.ts +12 -1
- package/src/tests/workflow-llm.test.ts +14 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +33 -0
- package/src/tools/request-human-input.ts +7 -2
- package/src/tools/script-connections/tool.ts +35 -3
- package/src/tools/send-task.ts +9 -1
- package/src/utils/enabled-tools.ts +27 -0
- package/src/utils/internal-ai/complete-structured.ts +11 -3
- package/src/utils/openrouter-base-url.ts +23 -0
- package/src/workflows/executors/raw-llm.ts +1 -0
- package/src/workflows/executors/system-one-decision.ts +2 -0
- package/src/workflows/executors/validate.ts +1 -0
- package/src/workflows/executors/workflow-llm.ts +9 -2
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/slack-reply-gate/README.md +98 -0
- package/templates/extensions/slack-reply-gate/hooks.ts +582 -0
- package/templates/extensions/slack-reply-gate/manifest.json +8 -0
- package/templates/skills/agent-browser/SKILL.md +2 -0
- package/templates/skills/agent-browser/content.md +2 -0
- package/templates/skills/code-quality/content.md +1 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -1
- package/templates/skills/swarm-scripts/SKILL.md +10 -0
- package/templates/skills/swarm-scripts/content.md +10 -0
- package/templates/skills/work-on-task/SKILL.md +4 -0
- package/templates/skills/work-on-task/content.md +4 -0
- package/templates/skills/workflow-iterate/content.md +9 -0
- package/dist/acp-adapter-yzfxn8vj.js +0 -23
- package/dist/cli-3dpj7atn.js +0 -11
- package/dist/cli-gvcgmx69.js +0 -183
- package/dist/{cli-jhm6hzjn.js → cli-mxvnnv5q.js} +7 -7
- package/dist/{codex-hook-d2rkvf39.js → codex-hook-15dgc4cb.js} +1 -1
|
@@ -23,11 +23,11 @@ import {
|
|
|
23
23
|
} from "./cli-2pdaysab.js";
|
|
24
24
|
import {
|
|
25
25
|
reasoningCapability
|
|
26
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-tmhsxhb8.js";
|
|
27
27
|
import {
|
|
28
28
|
ensureAgentFsCredentialsForAgent,
|
|
29
29
|
inviteEmailToSharedOrg
|
|
30
|
-
} from "./cli-
|
|
30
|
+
} from "./cli-jb931ba2.js";
|
|
31
31
|
import {
|
|
32
32
|
DEFAULT_CONNECTOR_CODE_LABEL,
|
|
33
33
|
checkHeartbeatChecklist,
|
|
@@ -35,10 +35,10 @@ import {
|
|
|
35
35
|
exchangeConnectorCode,
|
|
36
36
|
runHeartbeatSweep,
|
|
37
37
|
stopHeartbeat
|
|
38
|
-
} from "./cli-
|
|
38
|
+
} from "./cli-pgehz8e1.js";
|
|
39
39
|
import {
|
|
40
40
|
Anthropic
|
|
41
|
-
} from "./cli-
|
|
41
|
+
} from "./cli-5qkcdv8c.js";
|
|
42
42
|
import {
|
|
43
43
|
AGENT_MAX_TASKS_CONFIG_KEY,
|
|
44
44
|
ALL_CAPABILITIES,
|
|
@@ -335,7 +335,7 @@ import {
|
|
|
335
335
|
workflowModelErrors,
|
|
336
336
|
workflowSaveWarnings,
|
|
337
337
|
writeUnhandledError
|
|
338
|
-
} from "./cli-
|
|
338
|
+
} from "./cli-ee0402n1.js";
|
|
339
339
|
import"./cli-xvc3q9br.js";
|
|
340
340
|
import {
|
|
341
341
|
AMP_ESTIMATE_MODELS,
|
|
@@ -350,16 +350,16 @@ import {
|
|
|
350
350
|
import {
|
|
351
351
|
isMultiRuntimeEnabled
|
|
352
352
|
} from "./cli-re8z5p8p.js";
|
|
353
|
-
import"./cli-
|
|
353
|
+
import"./cli-43jg9xdw.js";
|
|
354
354
|
import {
|
|
355
355
|
deleteModelCatalogOverlay,
|
|
356
356
|
loadModelsCatalog,
|
|
357
357
|
requestModelCatalogRefresh,
|
|
358
358
|
upsertModelCatalogOverlay
|
|
359
|
-
} from "./cli-
|
|
359
|
+
} from "./cli-9q24yyvs.js";
|
|
360
360
|
import {
|
|
361
361
|
normalizeModelKey
|
|
362
|
-
} from "./cli-
|
|
362
|
+
} from "./cli-ewhwpyrb.js";
|
|
363
363
|
import {
|
|
364
364
|
ConnectionAuthInputSchema,
|
|
365
365
|
MAX_SCRIPT_WALL_CLOCK_MS,
|
|
@@ -383,6 +383,7 @@ import {
|
|
|
383
383
|
createApprovalFollowUpTask,
|
|
384
384
|
definitionNodeIds,
|
|
385
385
|
deleteMcpOAuthToken,
|
|
386
|
+
deleteScriptConnection,
|
|
386
387
|
discoverAuthorizationServerMetadata,
|
|
387
388
|
discoverProtectedResourceMetadata,
|
|
388
389
|
ensureMcpToken,
|
|
@@ -436,19 +437,12 @@ import {
|
|
|
436
437
|
upsertMcpOAuthToken,
|
|
437
438
|
upsertScriptConnection,
|
|
438
439
|
validateDefinition
|
|
439
|
-
} from "./cli-
|
|
440
|
+
} from "./cli-g6gx2dn1.js";
|
|
440
441
|
import"./cli-epan13p1.js";
|
|
441
442
|
import {
|
|
442
443
|
getApiKey
|
|
443
444
|
} from "./cli-f14fvzag.js";
|
|
444
|
-
import
|
|
445
|
-
detachedProcessGroup,
|
|
446
|
-
forceTerminateProcessGroup,
|
|
447
|
-
registerProcessGroup,
|
|
448
|
-
signalProcessGroup,
|
|
449
|
-
terminateProcessGroup
|
|
450
|
-
} from "./cli-zt3w1cn1.js";
|
|
451
|
-
import"./cli-f2n17zhp.js";
|
|
445
|
+
import"./cli-7mhy3ths.js";
|
|
452
446
|
import {
|
|
453
447
|
CLIENT_ID,
|
|
454
448
|
MAX_CODEX_OAUTH_SLOT,
|
|
@@ -459,11 +453,24 @@ import {
|
|
|
459
453
|
getAccountId,
|
|
460
454
|
getValidCodexOAuth,
|
|
461
455
|
loadAllCodexOAuthSlots
|
|
462
|
-
} from "./cli-
|
|
463
|
-
import
|
|
464
|
-
|
|
465
|
-
|
|
456
|
+
} from "./cli-v1w7pbd8.js";
|
|
457
|
+
import {
|
|
458
|
+
detachedProcessGroup,
|
|
459
|
+
forceTerminateProcessGroup,
|
|
460
|
+
registerProcessGroup,
|
|
461
|
+
signalProcessGroup,
|
|
462
|
+
terminateProcessGroup
|
|
463
|
+
} from "./cli-zt3w1cn1.js";
|
|
464
|
+
import"./cli-km4x66j0.js";
|
|
466
465
|
import"./cli-rb9m6rpe.js";
|
|
466
|
+
import"./cli-2x0e561b.js";
|
|
467
|
+
import {
|
|
468
|
+
buildAuthorizationUrl,
|
|
469
|
+
exchangeAuthorizationCode,
|
|
470
|
+
forceRefreshAuthorizationOrThrow,
|
|
471
|
+
forceRefreshTokenOrThrow,
|
|
472
|
+
oauthAppRowToProviderConfig
|
|
473
|
+
} from "./cli-fhj4y16f.js";
|
|
467
474
|
import {
|
|
468
475
|
AgentFsProvider,
|
|
469
476
|
FilesError,
|
|
@@ -471,13 +478,6 @@ import {
|
|
|
471
478
|
getFileStorageProvider,
|
|
472
479
|
normalizeFilesError
|
|
473
480
|
} from "./cli-mzfth0bp.js";
|
|
474
|
-
import {
|
|
475
|
-
buildAuthorizationUrl,
|
|
476
|
-
exchangeAuthorizationCode,
|
|
477
|
-
forceRefreshAuthorizationOrThrow,
|
|
478
|
-
forceRefreshTokenOrThrow,
|
|
479
|
-
oauthAppRowToProviderConfig
|
|
480
|
-
} from "./cli-j2jbr8af.js";
|
|
481
481
|
import"./cli-fad8m16k.js";
|
|
482
482
|
import"./cli-bsakbbkm.js";
|
|
483
483
|
import"./cli-zxg80zs9.js";
|
|
@@ -496,33 +496,34 @@ import {
|
|
|
496
496
|
recordDbRetentionStatement,
|
|
497
497
|
recordDbRetentionSweep,
|
|
498
498
|
recordSessionCost,
|
|
499
|
+
require_content_type,
|
|
499
500
|
startSlackApp,
|
|
500
501
|
startSpan,
|
|
501
502
|
stopSlackApp,
|
|
502
503
|
withRemoteContext,
|
|
503
504
|
withSpan,
|
|
504
505
|
withSpanContext
|
|
505
|
-
} from "./cli-
|
|
506
|
+
} from "./cli-j05x7pnb.js";
|
|
506
507
|
import"./cli-q51yprfq.js";
|
|
507
508
|
import"./cli-3er6gcsn.js";
|
|
508
509
|
import"./cli-yatxqynm.js";
|
|
509
510
|
import {
|
|
510
511
|
bedrockProviderModule
|
|
511
|
-
} from "./cli-
|
|
512
|
+
} from "./cli-cmxkbzzs.js";
|
|
512
513
|
import"./cli-yn0a417a.js";
|
|
513
514
|
import"./cli-cgj4hc12.js";
|
|
514
515
|
import"./cli-txgcknmy.js";
|
|
515
516
|
import"./cli-6map995v.js";
|
|
516
|
-
import"./cli-
|
|
517
|
+
import"./cli-xf713zz0.js";
|
|
517
518
|
import"./cli-y2apkfe2.js";
|
|
518
519
|
import"./cli-7kyk9jr3.js";
|
|
519
520
|
import {
|
|
520
521
|
setBedrockProviderModule
|
|
521
|
-
} from "./cli-
|
|
522
|
-
import"./cli-
|
|
522
|
+
} from "./cli-mn8qkd8v.js";
|
|
523
|
+
import"./cli-g7k7zbcz.js";
|
|
523
524
|
import"./cli-btbzvq95.js";
|
|
524
525
|
import"./cli-1m2e086m.js";
|
|
525
|
-
import"./cli-
|
|
526
|
+
import"./cli-wnb9b2g3.js";
|
|
526
527
|
import"./cli-bd957dgb.js";
|
|
527
528
|
import {
|
|
528
529
|
import_websocket,
|
|
@@ -532,7 +533,7 @@ import"./cli-pzxba7gw.js";
|
|
|
532
533
|
import {
|
|
533
534
|
getUserGrant,
|
|
534
535
|
init_rbac_roles
|
|
535
|
-
} from "./cli-
|
|
536
|
+
} from "./cli-0jngabtb.js";
|
|
536
537
|
import {
|
|
537
538
|
LEGACY_RULES,
|
|
538
539
|
can,
|
|
@@ -925,7 +926,8 @@ import {
|
|
|
925
926
|
upsertSwarmConfig,
|
|
926
927
|
validateConfigValue,
|
|
927
928
|
withFavoriteFlags
|
|
928
|
-
} from "./cli-
|
|
929
|
+
} from "./cli-74ez9b66.js";
|
|
930
|
+
import"./cli-3qhyh31j.js";
|
|
929
931
|
import {
|
|
930
932
|
ActiveSessionSchema,
|
|
931
933
|
AgentAcpStatusSchema,
|
|
@@ -1053,12 +1055,11 @@ import {
|
|
|
1053
1055
|
isTerminalTaskStatus,
|
|
1054
1056
|
splitLegacyModelAlias
|
|
1055
1057
|
} from "./cli-xvdgasgy.js";
|
|
1056
|
-
import"./cli-3qhyh31j.js";
|
|
1057
1058
|
import {
|
|
1058
1059
|
IDENTITY_FIELD_BUDGETS,
|
|
1059
1060
|
IdentityFieldBudgetError,
|
|
1060
1061
|
init_identity_field_budget
|
|
1061
|
-
} from "./cli-
|
|
1062
|
+
} from "./cli-mxvnnv5q.js";
|
|
1062
1063
|
import {
|
|
1063
1064
|
init_steering_enabled,
|
|
1064
1065
|
isSteeringEnabled
|
|
@@ -1068,21 +1069,6 @@ import {
|
|
|
1068
1069
|
getSlackConfiguration,
|
|
1069
1070
|
init_config
|
|
1070
1071
|
} from "./cli-dmtrxqhg.js";
|
|
1071
|
-
import"./cli-tadfgd0t.js";
|
|
1072
|
-
import {
|
|
1073
|
-
MODEL_SCOPED_WINDOWS,
|
|
1074
|
-
activeModelBlocks,
|
|
1075
|
-
init_model_rate_limit_windows
|
|
1076
|
-
} from "./cli-ywybs5as.js";
|
|
1077
|
-
import {
|
|
1078
|
-
MAX_PROFILE_FILE_LENGTH,
|
|
1079
|
-
agentFsFileRoute,
|
|
1080
|
-
getAppUrl,
|
|
1081
|
-
getConfiguredAppUrls,
|
|
1082
|
-
getMcpBaseUrl,
|
|
1083
|
-
getPublicMcpBaseUrl,
|
|
1084
|
-
init_constants
|
|
1085
|
-
} from "./cli-gd4favx9.js";
|
|
1086
1072
|
import"./cli-agamgm0p.js";
|
|
1087
1073
|
import {
|
|
1088
1074
|
getAllTemplateDefinitions,
|
|
@@ -1099,7 +1085,26 @@ import {
|
|
|
1099
1085
|
init_subscription_plans,
|
|
1100
1086
|
isSubscriptionPlanId
|
|
1101
1087
|
} from "./cli-1a2j50m0.js";
|
|
1088
|
+
import"./cli-tadfgd0t.js";
|
|
1089
|
+
import {
|
|
1090
|
+
MODEL_SCOPED_WINDOWS,
|
|
1091
|
+
activeModelBlocks,
|
|
1092
|
+
init_model_rate_limit_windows
|
|
1093
|
+
} from "./cli-ywybs5as.js";
|
|
1102
1094
|
import"./cli-f9w1semm.js";
|
|
1095
|
+
import {
|
|
1096
|
+
getOpenRouterAttributionHeaders,
|
|
1097
|
+
init_openrouter_base_url
|
|
1098
|
+
} from "./cli-a35hzdve.js";
|
|
1099
|
+
import {
|
|
1100
|
+
MAX_PROFILE_FILE_LENGTH,
|
|
1101
|
+
agentFsFileRoute,
|
|
1102
|
+
getAppUrl,
|
|
1103
|
+
getConfiguredAppUrls,
|
|
1104
|
+
getMcpBaseUrl,
|
|
1105
|
+
getPublicMcpBaseUrl,
|
|
1106
|
+
init_constants
|
|
1107
|
+
} from "./cli-gd4favx9.js";
|
|
1103
1108
|
import {
|
|
1104
1109
|
init_env_flag,
|
|
1105
1110
|
isEnvFlagEnabled,
|
|
@@ -1108,7 +1113,7 @@ import {
|
|
|
1108
1113
|
import {
|
|
1109
1114
|
init_src,
|
|
1110
1115
|
isAlias
|
|
1111
|
-
} from "./cli-
|
|
1116
|
+
} from "./cli-014vns2y.js";
|
|
1112
1117
|
import {
|
|
1113
1118
|
exports_zod,
|
|
1114
1119
|
init_zod
|
|
@@ -1125,7 +1130,7 @@ import {
|
|
|
1125
1130
|
import {
|
|
1126
1131
|
init_package,
|
|
1127
1132
|
package_default
|
|
1128
|
-
} from "./cli-
|
|
1133
|
+
} from "./cli-ng1pmdx6.js";
|
|
1129
1134
|
import {
|
|
1130
1135
|
init_event_bus,
|
|
1131
1136
|
workflowEventBus
|
|
@@ -10004,6 +10009,605 @@ export default extension;
|
|
|
10004
10009
|
},
|
|
10005
10010
|
readme: '# require-verification-note\n\nGuards `store-progress` with `status: "completed"`. The call is refused until the `output` contains the configured marker (default `Verified:`). The agent sees the reason as a tool error and can retry with a corrected output.\n\nConfig:\n\n```json\n{ "marker": "Verified:" }\n```\n\nThe guard covers agent MCP calls only. REST and dashboard completions are not tool calls and pass through.\n'
|
|
10006
10011
|
},
|
|
10012
|
+
"slack-reply-gate": {
|
|
10013
|
+
manifestFile: "manifest.json",
|
|
10014
|
+
manifest: {
|
|
10015
|
+
$schema: "../manifest.schema.json",
|
|
10016
|
+
name: "slack-reply-gate",
|
|
10017
|
+
description: "Asks Jev (TypeSafe or OpenRouter) whether Lead should answer a buffered Slack thread follow-up, and drops the wake-up when it should not",
|
|
10018
|
+
version: "1.2.0",
|
|
10019
|
+
runtime: "api",
|
|
10020
|
+
assets: {
|
|
10021
|
+
hooks: "hooks.ts"
|
|
10022
|
+
}
|
|
10023
|
+
},
|
|
10024
|
+
files: {
|
|
10025
|
+
"hooks.ts": `import { type ApiCtx, block, type SwarmExtension } from "swarm-extension";
|
|
10026
|
+
import { z } from "zod";
|
|
10027
|
+
|
|
10028
|
+
/**
|
|
10029
|
+
* slack-reply-gate
|
|
10030
|
+
*
|
|
10031
|
+
* Every human message in a Slack thread the swarm takes part in is buffered and
|
|
10032
|
+
* flushed into a Lead task ("[Thread follow-up — N message(s) buffered]"), even
|
|
10033
|
+
* "lol", ":joy:" or two people bantering. This hook asks TypeSafe Jev whether
|
|
10034
|
+
* Lead should answer, and drops the wake-up when the answer is no.
|
|
10035
|
+
*
|
|
10036
|
+
* Hook: \`pre.task.create\`, origin "slack", description carrying the
|
|
10037
|
+
* thread-buffer header. \`pre.slack.route\` cannot be used: the buffered thread
|
|
10038
|
+
* path returns before that hook is dispatched, and its result can only
|
|
10039
|
+
* re-target a message, never drop it.
|
|
10040
|
+
*
|
|
10041
|
+
* Rules:
|
|
10042
|
+
* - A message that @mentions Lead passes without a Jev call.
|
|
10043
|
+
* - Otherwise one Jev \`choice\` question classifies the message into a reason;
|
|
10044
|
+
* reply=false only for reaction_only | banter_between_others |
|
|
10045
|
+
* not_addressed_to_lead, and only when the summed probability of those
|
|
10046
|
+
* three labels is >= minConfidence (a banter/reaction split is still a skip).
|
|
10047
|
+
* - Fails OPEN on every error, timeout or missing key.
|
|
10048
|
+
*
|
|
10049
|
+
* Transport (config.provider): Jev is served by TypeSafe directly and by
|
|
10050
|
+
* OpenRouter's Decisions endpoint. Both take the same {model, state, questions}
|
|
10051
|
+
* body and return the same \`answers\` shape. \`auto\` uses TYPESAFE_API_KEY when
|
|
10052
|
+
* set, else OPENROUTER_API_KEY.
|
|
10053
|
+
*
|
|
10054
|
+
* modes (config.mode):
|
|
10055
|
+
* off -> never evaluate
|
|
10056
|
+
* shadow -> evaluate + record, never block (default)
|
|
10057
|
+
* enforce -> record, block the task when reply=false, and mark the
|
|
10058
|
+
* triggering Slack message with :mute: (config.muteReaction)
|
|
10059
|
+
*
|
|
10060
|
+
* Muting: no swarm tool or ctx method adds a Slack reaction, so the hook calls
|
|
10061
|
+
* the Slack Web API (reactions.remove / reactions.add) with the bot token. It
|
|
10062
|
+
* first removes the bot's own acceptance reactions (:eyes:, :heavy_plus_sign:,
|
|
10063
|
+
* :zap:, :speech_balloon:, or their SLACK_REACTION_* overrides), the same set
|
|
10064
|
+
* the engine clears when it finalizes a task, then adds :mute:. With
|
|
10065
|
+
* muteReaction "" it only removes them: a true no-op on the message. It runs after
|
|
10066
|
+
* the block decision returns, so a slow or failing Slack call never delays or
|
|
10067
|
+
* changes the drop; its errors go to the same KV namespace.
|
|
10068
|
+
*
|
|
10069
|
+
* Every decision lands in KV namespace \`ext:slack-reply-gate\`.
|
|
10070
|
+
*/
|
|
10071
|
+
|
|
10072
|
+
export const config = z.object({
|
|
10073
|
+
mode: z.enum(["off", "shadow", "enforce"]).default("shadow"),
|
|
10074
|
+
timeoutMs: z.number().int().positive().default(3000),
|
|
10075
|
+
provider: z.enum(["auto", "typesafe", "openrouter"]).default("auto"),
|
|
10076
|
+
/** Jev model on the TypeSafe transport. */
|
|
10077
|
+
model: z.string().default("jev-latest"),
|
|
10078
|
+
/** Jev model on the OpenRouter transport. */
|
|
10079
|
+
openrouterModel: z.string().default("typesafe/jev-1.13"),
|
|
10080
|
+
minConfidence: z.number().min(0).max(1).default(0.55),
|
|
10081
|
+
contextMessages: z.number().int().min(0).max(20).default(6),
|
|
10082
|
+
leadAliases: z.array(z.string()).default(["lead"]),
|
|
10083
|
+
botSlackUserIds: z.array(z.string()).default([]),
|
|
10084
|
+
/** Explicit TypeSafe key; otherwise TYPESAFE_API_KEY from the API process env. */
|
|
10085
|
+
apiKey: z.string().optional(),
|
|
10086
|
+
/**
|
|
10087
|
+
* Reaction for a dropped message in enforce mode. "" only removes the bot's
|
|
10088
|
+
* acceptance reaction(s) and adds nothing.
|
|
10089
|
+
*/
|
|
10090
|
+
muteReaction: z.string().default("mute"),
|
|
10091
|
+
slackTimeoutMs: z.number().int().positive().default(5000),
|
|
10092
|
+
});
|
|
10093
|
+
|
|
10094
|
+
type Cfg = z.infer<typeof config>;
|
|
10095
|
+
type Swarm = ApiCtx["swarm"];
|
|
10096
|
+
|
|
10097
|
+
const NS = "ext:slack-reply-gate";
|
|
10098
|
+
const TYPESAFE_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
10099
|
+
const OPENROUTER_ENDPOINT = "https://openrouter.ai/api/alpha/decisions";
|
|
10100
|
+
const HEADER_LINE_RE = /(?:^|\\n)\\[Thread follow-up — \\d+ message\\(s\\) buffered\\]\\n/g;
|
|
10101
|
+
const CTX_OPEN = "<thread_context>";
|
|
10102
|
+
const CTX_CLOSE = "</thread_context>";
|
|
10103
|
+
// The dispatcher cancels a handler after 5 s; leave room for the KV writes.
|
|
10104
|
+
const MAX_TIMEOUT_MS = 4000;
|
|
10105
|
+
|
|
10106
|
+
export const REASONS = {
|
|
10107
|
+
direct_ask:
|
|
10108
|
+
"Asks Lead (or the swarm, the bot, 'you' meaning the AI) a question or for something, explicitly or clearly implied.",
|
|
10109
|
+
followup_to_lead:
|
|
10110
|
+
"Answers, confirms, corrects or continues something Lead or another swarm agent ([Agent]) just said or asked, so Lead needs it to proceed.",
|
|
10111
|
+
instruction_to_lead:
|
|
10112
|
+
"Gives a task, instruction, request or decision for the AI to act on, even without naming it (for example 'can you create X', 'ship it', 'do it').",
|
|
10113
|
+
reaction_only:
|
|
10114
|
+
"Only a reaction: emoji, laughter (lol, haha, jaja), 'boom!', 'nice', 'thanks', '+1', with no request and nothing new for Lead.",
|
|
10115
|
+
banter_between_others:
|
|
10116
|
+
"Chat between the humans, or between other people and other bots, that is not aimed at Lead.",
|
|
10117
|
+
not_addressed_to_lead:
|
|
10118
|
+
"Addressed to a specific other person or another assistant, or an FYI that needs no answer from Lead.",
|
|
10119
|
+
other: "None of the above, or unclear.",
|
|
10120
|
+
} as const;
|
|
10121
|
+
|
|
10122
|
+
export type Reason = keyof typeof REASONS;
|
|
10123
|
+
const SKIP_REASONS: ReadonlySet<string> = new Set([
|
|
10124
|
+
"reaction_only",
|
|
10125
|
+
"banter_between_others",
|
|
10126
|
+
"not_addressed_to_lead",
|
|
10127
|
+
]);
|
|
10128
|
+
|
|
10129
|
+
const cfgOf = (raw: unknown): Cfg => {
|
|
10130
|
+
const parsed = config.safeParse(raw ?? {});
|
|
10131
|
+
return parsed.success ? parsed.data : config.parse({});
|
|
10132
|
+
};
|
|
10133
|
+
|
|
10134
|
+
// kv keys accept [a-zA-Z0-9._:/%-]
|
|
10135
|
+
const safeKey = (raw: string | undefined | null): string =>
|
|
10136
|
+
(raw && raw.length > 0 ? raw : "unknown").replace(/[^a-zA-Z0-9._:-]/g, "_").slice(0, 120);
|
|
10137
|
+
|
|
10138
|
+
const escapeRe = (s: string): string => s.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&");
|
|
10139
|
+
|
|
10140
|
+
/**
|
|
10141
|
+
* Locate the engine's follow-up header (src/slack/thread-buffer.ts): at the start of the
|
|
10142
|
+
* description, or right after the closing \`</thread_context>\`. Thread context quotes old
|
|
10143
|
+
* messages verbatim, so a header (or a \`</thread_context>\`) quoted inside it is skipped.
|
|
10144
|
+
*/
|
|
10145
|
+
const findHeader = (description: string): { index: number; end: number } | null => {
|
|
10146
|
+
for (const m of description.matchAll(HEADER_LINE_RE)) {
|
|
10147
|
+
const index = m.index! + (m[0].startsWith("\\n") ? 1 : 0);
|
|
10148
|
+
const before = description.slice(0, index);
|
|
10149
|
+
const open = before.indexOf(CTX_OPEN);
|
|
10150
|
+
if (open < 0 || before.trimEnd().endsWith(CTX_CLOSE)) {
|
|
10151
|
+
return { index, end: m.index! + m[0].length };
|
|
10152
|
+
}
|
|
10153
|
+
}
|
|
10154
|
+
return null;
|
|
10155
|
+
};
|
|
10156
|
+
|
|
10157
|
+
/**
|
|
10158
|
+
* Split a buffered follow-up description into thread context and new messages, or null
|
|
10159
|
+
* when the description is not a buffered follow-up. Only \`messages\` is new text.
|
|
10160
|
+
*/
|
|
10161
|
+
export const parseFollowUp = (
|
|
10162
|
+
description: string,
|
|
10163
|
+
): { context: string[]; messages: string[] } | null => {
|
|
10164
|
+
const header = findHeader(description);
|
|
10165
|
+
if (!header) return null;
|
|
10166
|
+
const before = description.slice(0, header.index).trimEnd();
|
|
10167
|
+
const open = before.indexOf(CTX_OPEN);
|
|
10168
|
+
const context =
|
|
10169
|
+
open >= 0
|
|
10170
|
+
? before
|
|
10171
|
+
.slice(open + CTX_OPEN.length, before.length - CTX_CLOSE.length)
|
|
10172
|
+
.split(/\\n(?=<@[A-Z0-9]+[|>]|\\[Agent\\]:)/)
|
|
10173
|
+
.map((m) => m.trim())
|
|
10174
|
+
.filter(Boolean)
|
|
10175
|
+
: [];
|
|
10176
|
+
const messages = description
|
|
10177
|
+
.slice(header.end)
|
|
10178
|
+
.split("\\n---\\n")
|
|
10179
|
+
.map((m) => m.trim())
|
|
10180
|
+
.filter(Boolean);
|
|
10181
|
+
return { context, messages };
|
|
10182
|
+
};
|
|
10183
|
+
|
|
10184
|
+
/** True when the new text addresses Lead with an @mention. */
|
|
10185
|
+
export const mentionsLead = (text: string, aliases: readonly string[]): boolean => {
|
|
10186
|
+
// The buffer rewrites a mention of the swarm's own bot to \`<@U…> (that's you)\`.
|
|
10187
|
+
if (/<@[A-Z0-9]+>\\s*\\(that's you\\)/.test(text)) return true;
|
|
10188
|
+
const names = aliases
|
|
10189
|
+
.map((a) => a.trim())
|
|
10190
|
+
.filter(Boolean)
|
|
10191
|
+
.map(escapeRe);
|
|
10192
|
+
if (names.length === 0) return false;
|
|
10193
|
+
const alt = names.join("|");
|
|
10194
|
+
return (
|
|
10195
|
+
new RegExp(\`(^|[^\\\\w@])@(\${alt})\\\\b\`, "i").test(text) ||
|
|
10196
|
+
new RegExp(\`<@[A-Z0-9]+\\\\|(\${alt})>\`, "i").test(text)
|
|
10197
|
+
);
|
|
10198
|
+
};
|
|
10199
|
+
|
|
10200
|
+
const secretCache = new Map<string, { value: string; at: number }>();
|
|
10201
|
+
const KEY_TTL_MS = 5 * 60 * 1000;
|
|
10202
|
+
const usable = (v: string): boolean => Boolean(v) && !v.startsWith("[REDACTED");
|
|
10203
|
+
const envOf = (name: string): string => String(process.env[name] ?? "");
|
|
10204
|
+
|
|
10205
|
+
/**
|
|
10206
|
+
* Resolve a secret: explicit value, then the API process env. The API process loads
|
|
10207
|
+
* global swarm_config rows into process.env at boot and on config reload.
|
|
10208
|
+
* ctx.swarm.config_get cannot serve it: the SDK scrubs secrets to "[REDACTED:<name>]".
|
|
10209
|
+
*/
|
|
10210
|
+
const secret = (name: string, explicit?: string): string => {
|
|
10211
|
+
const cached = secretCache.get(name);
|
|
10212
|
+
if (cached && Date.now() - cached.at < KEY_TTL_MS) return cached.value;
|
|
10213
|
+
const value = [String(explicit ?? ""), envOf(name)].find(usable) ?? "";
|
|
10214
|
+
if (usable(value)) secretCache.set(name, { value, at: Date.now() });
|
|
10215
|
+
return value;
|
|
10216
|
+
};
|
|
10217
|
+
|
|
10218
|
+
/** Test hook: forget cached secrets. */
|
|
10219
|
+
export const resetKeyCache = (): void => {
|
|
10220
|
+
secretCache.clear();
|
|
10221
|
+
};
|
|
10222
|
+
|
|
10223
|
+
export type JevTransport = {
|
|
10224
|
+
provider: "typesafe" | "openrouter";
|
|
10225
|
+
endpoint: string;
|
|
10226
|
+
key: string;
|
|
10227
|
+
model: string;
|
|
10228
|
+
};
|
|
10229
|
+
|
|
10230
|
+
/**
|
|
10231
|
+
* Pick the Jev transport. \`auto\` prefers TypeSafe and falls back to OpenRouter.
|
|
10232
|
+
* Throws when the chosen provider has no usable key; the handler fails open.
|
|
10233
|
+
*/
|
|
10234
|
+
export const resolveTransport = (cfg: Cfg): JevTransport => {
|
|
10235
|
+
const typesafe = (): JevTransport | null => {
|
|
10236
|
+
const key = secret("TYPESAFE_API_KEY", cfg.apiKey);
|
|
10237
|
+
return usable(key)
|
|
10238
|
+
? { provider: "typesafe", endpoint: TYPESAFE_ENDPOINT, key, model: cfg.model }
|
|
10239
|
+
: null;
|
|
10240
|
+
};
|
|
10241
|
+
const openrouter = (): JevTransport | null => {
|
|
10242
|
+
const key = secret("OPENROUTER_API_KEY");
|
|
10243
|
+
return usable(key)
|
|
10244
|
+
? { provider: "openrouter", endpoint: OPENROUTER_ENDPOINT, key, model: cfg.openrouterModel }
|
|
10245
|
+
: null;
|
|
10246
|
+
};
|
|
10247
|
+
const transport =
|
|
10248
|
+
cfg.provider === "typesafe"
|
|
10249
|
+
? typesafe()
|
|
10250
|
+
: cfg.provider === "openrouter"
|
|
10251
|
+
? openrouter()
|
|
10252
|
+
: (typesafe() ?? openrouter());
|
|
10253
|
+
if (transport) return transport;
|
|
10254
|
+
throw new Error(
|
|
10255
|
+
cfg.provider === "typesafe"
|
|
10256
|
+
? "no TYPESAFE_API_KEY"
|
|
10257
|
+
: cfg.provider === "openrouter"
|
|
10258
|
+
? "no OPENROUTER_API_KEY"
|
|
10259
|
+
: "no TYPESAFE_API_KEY or OPENROUTER_API_KEY",
|
|
10260
|
+
);
|
|
10261
|
+
};
|
|
10262
|
+
|
|
10263
|
+
const SLACK_API = "https://slack.com/api";
|
|
10264
|
+
// The engine's acceptance reactions (src/slack/reaction-shortcode.ts), by config key.
|
|
10265
|
+
const ACCEPTANCE_REACTIONS: ReadonlyArray<[string, string]> = [
|
|
10266
|
+
["SLACK_REACTION_ACCEPTED", "eyes"],
|
|
10267
|
+
["SLACK_REACTION_BUFFERED", "heavy_plus_sign"],
|
|
10268
|
+
["SLACK_REACTION_NOW", "zap"],
|
|
10269
|
+
["SLACK_REACTION_STEERED", "speech_balloon"],
|
|
10270
|
+
];
|
|
10271
|
+
const SHORTCODE_RE = /^[a-z0-9_+'-]+(::skin-tone-[2-6])?$/;
|
|
10272
|
+
const shortcode = (raw: string): string | null => {
|
|
10273
|
+
const s = raw.trim().replace(/^:/, "").replace(/:$/, "").toLowerCase();
|
|
10274
|
+
return s && SHORTCODE_RE.test(s) ? s : null;
|
|
10275
|
+
};
|
|
10276
|
+
|
|
10277
|
+
/** The acceptance reactions the engine may have put on the message, deduplicated. */
|
|
10278
|
+
export const acceptanceReactions = (): string[] => [
|
|
10279
|
+
...new Set(ACCEPTANCE_REACTIONS.map(([key, fallback]) => shortcode(envOf(key)) ?? fallback)),
|
|
10280
|
+
];
|
|
10281
|
+
|
|
10282
|
+
const slackCall = async (
|
|
10283
|
+
method: string,
|
|
10284
|
+
token: string,
|
|
10285
|
+
body: Record<string, string>,
|
|
10286
|
+
timeoutMs: number,
|
|
10287
|
+
): Promise<{ ok: boolean; error?: string }> => {
|
|
10288
|
+
const controller = new AbortController();
|
|
10289
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
10290
|
+
try {
|
|
10291
|
+
const res = await fetch(\`\${SLACK_API}/\${method}\`, {
|
|
10292
|
+
method: "POST",
|
|
10293
|
+
headers: {
|
|
10294
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
10295
|
+
Authorization: \`Bearer \${token}\`,
|
|
10296
|
+
},
|
|
10297
|
+
body: JSON.stringify(body),
|
|
10298
|
+
signal: controller.signal,
|
|
10299
|
+
});
|
|
10300
|
+
if (!res.ok) return { ok: false, error: \`http_\${res.status}\` };
|
|
10301
|
+
const data = (await res.json()) as { ok?: boolean; error?: string };
|
|
10302
|
+
return { ok: data?.ok === true, error: data?.error };
|
|
10303
|
+
} finally {
|
|
10304
|
+
clearTimeout(timer);
|
|
10305
|
+
}
|
|
10306
|
+
};
|
|
10307
|
+
|
|
10308
|
+
export type MuteResult = { removed: string[]; added: boolean; errors: string[] };
|
|
10309
|
+
|
|
10310
|
+
/**
|
|
10311
|
+
* Replace the bot's acceptance reaction(s) on one message with \`name\`, or only
|
|
10312
|
+
* remove them when \`name\` is "". \`reactions.remove\` only removes the calling
|
|
10313
|
+
* bot's own reaction, so a human's :eyes: stays. Never throws: every failure
|
|
10314
|
+
* lands in \`errors\`.
|
|
10315
|
+
*/
|
|
10316
|
+
export const muteMessage = async (
|
|
10317
|
+
token: string,
|
|
10318
|
+
channel: string,
|
|
10319
|
+
timestamp: string,
|
|
10320
|
+
name: string,
|
|
10321
|
+
timeoutMs: number,
|
|
10322
|
+
): Promise<MuteResult> => {
|
|
10323
|
+
const result: MuteResult = { removed: [], added: false, errors: [] };
|
|
10324
|
+
const call = (method: string, reaction: string) =>
|
|
10325
|
+
slackCall(method, token, { channel, timestamp, name: reaction }, timeoutMs).catch(
|
|
10326
|
+
(err): { ok: boolean; error?: string } => ({ ok: false, error: String(err).slice(0, 120) }),
|
|
10327
|
+
);
|
|
10328
|
+
const removals = acceptanceReactions().filter((r) => r !== name);
|
|
10329
|
+
const removed = await Promise.all(removals.map((r) => call("reactions.remove", r)));
|
|
10330
|
+
removed.forEach((res, i) => {
|
|
10331
|
+
const reaction = removals[i]!;
|
|
10332
|
+
if (res.ok) result.removed.push(reaction);
|
|
10333
|
+
else if (res.error !== "no_reaction" && res.error !== "invalid_name")
|
|
10334
|
+
result.errors.push(\`remove \${reaction}: \${res.error ?? "unknown"}\`);
|
|
10335
|
+
});
|
|
10336
|
+
if (!name) return result;
|
|
10337
|
+
const added = await call("reactions.add", name);
|
|
10338
|
+
if (added.ok || added.error === "already_reacted") result.added = true;
|
|
10339
|
+
else result.errors.push(\`add \${name}: \${added.error ?? "unknown"}\`);
|
|
10340
|
+
return result;
|
|
10341
|
+
};
|
|
10342
|
+
|
|
10343
|
+
// In-flight mutes, so tests can await the work the handler does not wait for.
|
|
10344
|
+
const pendingMutes = new Set<Promise<void>>();
|
|
10345
|
+
/** Test hook: wait for every in-flight mute. */
|
|
10346
|
+
export const settleMutes = async (): Promise<void> => {
|
|
10347
|
+
await Promise.allSettled([...pendingMutes]);
|
|
10348
|
+
};
|
|
10349
|
+
|
|
10350
|
+
export type JevVerdict = {
|
|
10351
|
+
reason: string;
|
|
10352
|
+
confidence: number | null;
|
|
10353
|
+
probabilities: Record<string, number> | null;
|
|
10354
|
+
latencyMs: number;
|
|
10355
|
+
};
|
|
10356
|
+
|
|
10357
|
+
/**
|
|
10358
|
+
* One Jev call: classify the newest message into a reason. Throws on any failure.
|
|
10359
|
+
* TypeSafe and OpenRouter share the request body and the \`answers\` response shape.
|
|
10360
|
+
*/
|
|
10361
|
+
export const askJev = async (
|
|
10362
|
+
input: {
|
|
10363
|
+
messages: string[];
|
|
10364
|
+
context: string[];
|
|
10365
|
+
sender: string;
|
|
10366
|
+
leadNames: readonly string[];
|
|
10367
|
+
},
|
|
10368
|
+
transport: JevTransport,
|
|
10369
|
+
cfg: Cfg,
|
|
10370
|
+
outer?: AbortSignal,
|
|
10371
|
+
): Promise<JevVerdict> => {
|
|
10372
|
+
const controller = new AbortController();
|
|
10373
|
+
const timer = setTimeout(() => controller.abort(), Math.min(cfg.timeoutMs, MAX_TIMEOUT_MS));
|
|
10374
|
+
const onOuterAbort = () => controller.abort();
|
|
10375
|
+
outer?.addEventListener("abort", onOuterAbort);
|
|
10376
|
+
const t0 = Date.now();
|
|
10377
|
+
try {
|
|
10378
|
+
const res = await fetch(transport.endpoint, {
|
|
10379
|
+
method: "POST",
|
|
10380
|
+
headers: {
|
|
10381
|
+
"Content-Type": "application/json",
|
|
10382
|
+
Authorization: \`Bearer \${transport.key}\`,
|
|
10383
|
+
},
|
|
10384
|
+
body: JSON.stringify({
|
|
10385
|
+
state: {
|
|
10386
|
+
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.",
|
|
10387
|
+
lead_names: input.leadNames,
|
|
10388
|
+
recent_thread_messages: input.context,
|
|
10389
|
+
new_message: input.messages.join("\\n---\\n"),
|
|
10390
|
+
new_message_sender: input.sender,
|
|
10391
|
+
},
|
|
10392
|
+
model: transport.model,
|
|
10393
|
+
questions: {
|
|
10394
|
+
reason: {
|
|
10395
|
+
type: "choice",
|
|
10396
|
+
instructions:
|
|
10397
|
+
"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.",
|
|
10398
|
+
criteria: REASONS,
|
|
10399
|
+
},
|
|
10400
|
+
},
|
|
10401
|
+
}),
|
|
10402
|
+
signal: controller.signal,
|
|
10403
|
+
});
|
|
10404
|
+
if (!res.ok) throw new Error(\`\${transport.provider} \${res.status}\`);
|
|
10405
|
+
const body = (await res.json()) as {
|
|
10406
|
+
answers?: { reason?: { choice?: unknown; confidence?: unknown; probabilities?: unknown } };
|
|
10407
|
+
} | null;
|
|
10408
|
+
const answer = body?.answers?.reason;
|
|
10409
|
+
const reason = typeof answer?.choice === "string" ? answer.choice : "";
|
|
10410
|
+
if (!(reason in REASONS)) throw new Error(\`unexpected choice: \${String(reason).slice(0, 60)}\`);
|
|
10411
|
+
const confidence = typeof answer?.confidence === "number" ? answer.confidence : null;
|
|
10412
|
+
const probabilities =
|
|
10413
|
+
answer?.probabilities && typeof answer.probabilities === "object"
|
|
10414
|
+
? (answer.probabilities as Record<string, number>)
|
|
10415
|
+
: null;
|
|
10416
|
+
return { reason, confidence, probabilities, latencyMs: Date.now() - t0 };
|
|
10417
|
+
} finally {
|
|
10418
|
+
clearTimeout(timer);
|
|
10419
|
+
outer?.removeEventListener("abort", onOuterAbort);
|
|
10420
|
+
}
|
|
10421
|
+
};
|
|
10422
|
+
|
|
10423
|
+
/** Probability that the message needs no reply: the summed mass of the skip labels. */
|
|
10424
|
+
export const skipProbability = (verdict: JevVerdict): number | null => {
|
|
10425
|
+
if (verdict.probabilities) {
|
|
10426
|
+
let sum = 0;
|
|
10427
|
+
for (const [label, p] of Object.entries(verdict.probabilities)) {
|
|
10428
|
+
if (SKIP_REASONS.has(label) && typeof p === "number") sum += p;
|
|
10429
|
+
}
|
|
10430
|
+
return sum;
|
|
10431
|
+
}
|
|
10432
|
+
return verdict.confidence;
|
|
10433
|
+
};
|
|
10434
|
+
|
|
10435
|
+
/** Map a Jev verdict to the typed decision. An unsure skip verdict replies. */
|
|
10436
|
+
export const decide = (
|
|
10437
|
+
verdict: JevVerdict,
|
|
10438
|
+
minConfidence: number,
|
|
10439
|
+
): { reply: boolean; reason: Reason; skipP: number | null; lowConfidence: boolean } => {
|
|
10440
|
+
const reason = verdict.reason as Reason;
|
|
10441
|
+
const skipP = skipProbability(verdict);
|
|
10442
|
+
if (!SKIP_REASONS.has(reason)) return { reply: true, reason, skipP, lowConfidence: false };
|
|
10443
|
+
const lowConfidence = skipP !== null && skipP < minConfidence;
|
|
10444
|
+
return { reply: lowConfidence, reason, skipP, lowConfidence };
|
|
10445
|
+
};
|
|
10446
|
+
|
|
10447
|
+
const record = async (
|
|
10448
|
+
swarm: Swarm,
|
|
10449
|
+
entry: Record<string, unknown>,
|
|
10450
|
+
counters: string[],
|
|
10451
|
+
prefix: "d" | "m" = "d",
|
|
10452
|
+
) => {
|
|
10453
|
+
const at = new Date().toISOString();
|
|
10454
|
+
const thread = safeKey(\`\${entry.channelId ?? ""}_\${entry.threadTs ?? ""}\`);
|
|
10455
|
+
const value = { at, ...entry };
|
|
10456
|
+
await Promise.allSettled([
|
|
10457
|
+
swarm.kv_set({ namespace: NS, key: \`\${prefix}:\${Date.now()}:\${thread}\`, value }),
|
|
10458
|
+
// \`last\` stays the latest decision; mute outcomes only get their own row.
|
|
10459
|
+
...(prefix === "d" ? [swarm.kv_set({ namespace: NS, key: "last", value })] : []),
|
|
10460
|
+
...counters.map((c) => swarm.kv_incr({ namespace: NS, key: safeKey(c) })),
|
|
10461
|
+
]);
|
|
10462
|
+
};
|
|
10463
|
+
|
|
10464
|
+
/** Mute the triggering message and log the outcome. Never throws. */
|
|
10465
|
+
const muteTrigger = async (
|
|
10466
|
+
swarm: Swarm,
|
|
10467
|
+
cfg: Cfg,
|
|
10468
|
+
base: Record<string, unknown>,
|
|
10469
|
+
): Promise<void> => {
|
|
10470
|
+
try {
|
|
10471
|
+
const channel = String(base.channelId ?? "");
|
|
10472
|
+
const ts = String(base.triggerTs ?? "");
|
|
10473
|
+
if (!channel || !ts) throw new Error("no slackChannelId or slackTriggerMessageTs");
|
|
10474
|
+
const token = secret("SLACK_BOT_TOKEN");
|
|
10475
|
+
if (!usable(token)) throw new Error("no SLACK_BOT_TOKEN");
|
|
10476
|
+
const result = await muteMessage(token, channel, ts, cfg.muteReaction, cfg.slackTimeoutMs);
|
|
10477
|
+
// "" adds nothing: success is clearing the acceptance reaction(s).
|
|
10478
|
+
const done = cfg.muteReaction ? "muted" : "unreacted";
|
|
10479
|
+
const ok = (result.added || !cfg.muteReaction) && result.errors.length === 0;
|
|
10480
|
+
const action = ok ? done : "mute-error";
|
|
10481
|
+
await record(swarm, { ...base, action, ...result }, [action], "m");
|
|
10482
|
+
} catch (err) {
|
|
10483
|
+
await record(
|
|
10484
|
+
swarm,
|
|
10485
|
+
{ ...base, action: "mute-error", errors: [String(err).slice(0, 300)] },
|
|
10486
|
+
["mute-error"],
|
|
10487
|
+
"m",
|
|
10488
|
+
).catch(() => {});
|
|
10489
|
+
}
|
|
10490
|
+
};
|
|
10491
|
+
|
|
10492
|
+
const extension: SwarmExtension = (api) => {
|
|
10493
|
+
api.on("pre.task.create", async (event, ctx) => {
|
|
10494
|
+
if (event.origin !== "slack") return;
|
|
10495
|
+
// Only buffered follow-ups. A direct @mention task never carries the header outside
|
|
10496
|
+
// its thread context.
|
|
10497
|
+
const followUp = parseFollowUp(event.description);
|
|
10498
|
+
if (!followUp) return;
|
|
10499
|
+
|
|
10500
|
+
const cfg = cfgOf(ctx.config);
|
|
10501
|
+
if (cfg.mode === "off") return;
|
|
10502
|
+
|
|
10503
|
+
const opts = event.options;
|
|
10504
|
+
const base = {
|
|
10505
|
+
mode: cfg.mode,
|
|
10506
|
+
channelId: opts.slackChannelId,
|
|
10507
|
+
threadTs: opts.slackThreadTs,
|
|
10508
|
+
triggerTs: opts.slackTriggerMessageTs,
|
|
10509
|
+
senderSlackId: opts.slackUserId,
|
|
10510
|
+
};
|
|
10511
|
+
|
|
10512
|
+
try {
|
|
10513
|
+
// A draft follow-up carries downloaded files: always let Lead see them.
|
|
10514
|
+
if (opts.status === "draft") {
|
|
10515
|
+
await record(ctx.swarm, { ...base, action: "pass", reason: "has_files" }, [
|
|
10516
|
+
"total",
|
|
10517
|
+
"files",
|
|
10518
|
+
]);
|
|
10519
|
+
return;
|
|
10520
|
+
}
|
|
10521
|
+
|
|
10522
|
+
// The mention check reads only the new messages, never the thread context.
|
|
10523
|
+
const { context, messages } = followUp;
|
|
10524
|
+
const preview = messages.join(" | ").slice(0, 300);
|
|
10525
|
+
|
|
10526
|
+
if (messages.some((m) => mentionsLead(m, cfg.leadAliases))) {
|
|
10527
|
+
await record(ctx.swarm, { ...base, action: "pass", reason: "mention", preview }, [
|
|
10528
|
+
"total",
|
|
10529
|
+
"mention",
|
|
10530
|
+
]);
|
|
10531
|
+
return;
|
|
10532
|
+
}
|
|
10533
|
+
|
|
10534
|
+
const transport = resolveTransport(cfg);
|
|
10535
|
+
|
|
10536
|
+
const sender =
|
|
10537
|
+
opts.slackUserId && cfg.botSlackUserIds.includes(opts.slackUserId)
|
|
10538
|
+
? "bot"
|
|
10539
|
+
: opts.requestedByUserId
|
|
10540
|
+
? "human (known teammate)"
|
|
10541
|
+
: "unknown (not a known teammate; may be a bot or an external person)";
|
|
10542
|
+
|
|
10543
|
+
const verdict = await askJev(
|
|
10544
|
+
{
|
|
10545
|
+
messages,
|
|
10546
|
+
context: context.slice(-cfg.contextMessages).map((m) => m.slice(0, 500)),
|
|
10547
|
+
sender,
|
|
10548
|
+
leadNames: cfg.leadAliases,
|
|
10549
|
+
},
|
|
10550
|
+
transport,
|
|
10551
|
+
cfg,
|
|
10552
|
+
ctx.signal,
|
|
10553
|
+
);
|
|
10554
|
+
const decision = decide(verdict, cfg.minConfidence);
|
|
10555
|
+
|
|
10556
|
+
const skip = !decision.reply;
|
|
10557
|
+
const action = skip ? (cfg.mode === "enforce" ? "skipped" : "shadow-skip") : "pass";
|
|
10558
|
+
await record(
|
|
10559
|
+
ctx.swarm,
|
|
10560
|
+
{
|
|
10561
|
+
...base,
|
|
10562
|
+
action,
|
|
10563
|
+
reply: decision.reply,
|
|
10564
|
+
reason: decision.reason,
|
|
10565
|
+
confidence: verdict.confidence,
|
|
10566
|
+
skipP: decision.skipP,
|
|
10567
|
+
lowConfidence: decision.lowConfidence,
|
|
10568
|
+
latencyMs: verdict.latencyMs,
|
|
10569
|
+
provider: transport.provider,
|
|
10570
|
+
sender,
|
|
10571
|
+
preview,
|
|
10572
|
+
},
|
|
10573
|
+
["total", skip ? action : "reply", \`reason:\${decision.reason}\`],
|
|
10574
|
+
);
|
|
10575
|
+
|
|
10576
|
+
if (skip && cfg.mode === "enforce") {
|
|
10577
|
+
// Not awaited: the block must return inside the dispatcher's 5 s budget
|
|
10578
|
+
// whatever Slack does, and a failed reaction never changes the drop.
|
|
10579
|
+
// An empty (or invalid) muteReaction still clears the acceptance reaction(s).
|
|
10580
|
+
const muteReaction = shortcode(cfg.muteReaction) ?? "";
|
|
10581
|
+
const pending = muteTrigger(ctx.swarm, { ...cfg, muteReaction }, base);
|
|
10582
|
+
pendingMutes.add(pending);
|
|
10583
|
+
void pending.finally(() => pendingMutes.delete(pending));
|
|
10584
|
+
return block(
|
|
10585
|
+
\`slack-reply-gate: no reply needed (\${decision.reason}\${decision.skipP !== null ? \`, p(skip) \${decision.skipP.toFixed(2)}\` : ""})\`,
|
|
10586
|
+
);
|
|
10587
|
+
}
|
|
10588
|
+
return;
|
|
10589
|
+
} catch (err) {
|
|
10590
|
+
// Fail open, always.
|
|
10591
|
+
try {
|
|
10592
|
+
await record(
|
|
10593
|
+
ctx.swarm,
|
|
10594
|
+
{ ...base, action: "fail-open", error: String(err).slice(0, 300) },
|
|
10595
|
+
["total", "error"],
|
|
10596
|
+
);
|
|
10597
|
+
} catch {
|
|
10598
|
+
/* ignore */
|
|
10599
|
+
}
|
|
10600
|
+
ctx.log.warn("slack-reply-gate: failing open", { error: String(err) });
|
|
10601
|
+
return;
|
|
10602
|
+
}
|
|
10603
|
+
});
|
|
10604
|
+
};
|
|
10605
|
+
|
|
10606
|
+
export default extension;
|
|
10607
|
+
`
|
|
10608
|
+
},
|
|
10609
|
+
readme: '# slack-reply-gate\n\nLead is woken by every human message in a Slack thread the swarm takes part in, including "lol", an emoji, or two people chatting with each other. This extension asks Jev, [TypeSafe](https://typesafe.ai)\'s decision model, whether Lead should answer a new thread message, and drops the wake-up when it should not.\n\nInstall config:\n\n```json\n{ "mode": "shadow" }\n```\n\nStart in `shadow`, read the decisions in KV for a few days, then switch to `enforce`.\n\n## Modes\n\n| `mode` | Effect |\n|---|---|\n| `off` | Never evaluates. |\n| `shadow` (default) | Asks Jev and records the verdict. Never blocks. |\n| `enforce` | Asks Jev, records the verdict, and blocks the Lead task when the answer is no. The message gets :mute: instead: no Lead task, no reply, no outcome card. |\n\n## What it gates\n\nHook: `pre.task.create`, for tasks with origin `slack` whose description carries the thread buffer\'s header `[Thread follow-up — N message(s) buffered]`. Those are the non-mention thread replies that `src/slack/thread-buffer.ts` batches into a Lead task. A direct @mention of the bot creates a task without that header and is never gated.\n\nThe header counts only at the start of the description or right after the closing `</thread_context>`. Thread context quotes old messages verbatim, and an older Lead reply can quote the header itself, so a header inside the context is ignored. Only the text after the real header is treated as new.\n\n## Decision\n\n1. A follow-up with downloaded files (status `draft`) passes without a Jev call.\n2. **@mention pass-through.** A new message that @mentions Lead passes without a Jev call. A mention is the bot mention the engine rewrites to `<@U…> (that\'s you)`, a resolved `<@U…|lead>`, or a literal `@lead` (any name in `leadAliases`). Only the new messages are scanned, never the thread context, so an old mention earlier in the thread does not pass later banter.\n3. Otherwise one Jev call (one `choice` question, see [Jev transport](#jev-transport)) with the new message(s), the sender kind, and the last `contextMessages` thread messages. Labels: `direct_ask`, `followup_to_lead`, `instruction_to_lead`, `reaction_only`, `banter_between_others`, `not_addressed_to_lead`, `other`.\n4. The answer is no only when the top label is `reaction_only`, `banter_between_others`, or `not_addressed_to_lead`, and the summed probability of those three labels is at least `minConfidence`. A split between two skip labels is still a skip. An unsure verdict replies.\n\nSeveral buffered messages are judged together. One mention in any of them passes the batch.\n\n## Jev transport\n\nJev is reachable two ways. Both take the same request and return the same answer, so the decision is identical whichever one runs.\n\n| `provider` | Endpoint | Key | Model |\n|---|---|---|---|\n| `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` (or `apiKey`) | `model`, default `jev-latest` |\n| `openrouter` | `POST https://openrouter.ai/api/alpha/decisions` | `OPENROUTER_API_KEY` | `openrouterModel`, default `typesafe/jev-1.13` |\n| `auto` (default) | TypeSafe when its key is set, else OpenRouter | | |\n\nUnder `auto`, `TYPESAFE_API_KEY` wins when both keys are set. With neither key, every message fails open and the KV row names both secrets. Each decision row records the `provider` that answered.\n\n## Fail open\n\nEvery error lets the task through: a missing key, a TypeSafe HTTP error, a network error, a timeout, a malformed answer, or an unknown label. The handler never throws, so an outage cannot auto-disable the extension. Each failure is recorded as `fail-open` in KV and logged as a warning.\n\n## :mute: on drop (enforce)\n\nNo swarm tool or extension `ctx` method adds a Slack reaction, so the hook calls the Slack Web API with the bot token. It needs the `reactions:write` scope, which the Slack app already uses for its acceptance reactions.\n\n1. `reactions.remove` for each acceptance reaction the engine may have put on the triggering message: `eyes`, `heavy_plus_sign`, `zap`, `speech_balloon`, or their `SLACK_REACTION_ACCEPTED`, `SLACK_REACTION_BUFFERED`, `SLACK_REACTION_NOW`, and `SLACK_REACTION_STEERED` overrides. Slack only removes the bot\'s own reaction, so a human\'s :eyes: stays.\n2. `reactions.add` with `muteReaction` (default `mute`). `already_reacted` counts as success. Skipped when `muteReaction` is `""`.\n\nThe block returns first and the Slack calls run after it. A slow or failing Slack call never delays the drop or turns it back into a task. Set `muteReaction` to `""` for a true no-op: step 1 still clears the bot\'s acceptance reaction(s), and nothing is added.\n\n## Config\n\n| Key | Default | Notes |\n|---|---|---|\n| `mode` | `shadow` | `off`, `shadow`, or `enforce` |\n| `timeoutMs` | `3000` | Jev call timeout, capped at 4000 (the dispatcher cancels a handler at 5 s) |\n| `provider` | `auto` | `auto`, `typesafe`, or `openrouter`. See [Jev transport](#jev-transport) |\n| `model` | `jev-latest` | Jev model on TypeSafe |\n| `openrouterModel` | `typesafe/jev-1.13` | Jev model on OpenRouter |\n| `minConfidence` | `0.55` | Minimum summed skip probability to skip |\n| `contextMessages` | `6` | Thread messages sent to Jev, 0 to 20 |\n| `leadAliases` | `["lead"]` | A literal `@alias` in a new message counts as a mention |\n| `botSlackUserIds` | `[]` | Slack user ids labelled `bot` as the sender for Jev |\n| `apiKey` | unset | Explicit TypeSafe key. Prefer the `TYPESAFE_API_KEY` secret below |\n| `muteReaction` | `mute` | Reaction for a dropped message; `""` only removes the acceptance reaction(s) |\n| `slackTimeoutMs` | `5000` | Timeout per Slack call |\n\n## Required secrets\n\nAll are read from the API process environment at call time. Set them as global `swarm_config` secrets: the API process loads global rows into `process.env` at boot and on config reload.\n\n- `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`: one is required for any evaluation. Under `auto`, TypeSafe wins when both are set. Without either, every message fails open.\n- `SLACK_BOT_TOKEN`: the Slack app\'s bot token. A swarm with Slack connected already has it. Without it, `enforce` still drops the message but cannot touch its reactions, and logs a `mute-error`.\n\nNo key is written to config, KV, logs, or a description.\n\n## KV namespace `ext:slack-reply-gate`\n\n- `d:<epochMs>:<channel>_<threadTs>`: one row per decision, with `action` (`pass`, `shadow-skip`, `skipped`, `fail-open`), `provider`, `reason`, `confidence`, `skipP`, `latencyMs`, `sender`, and a 300-character `preview`.\n- `m:<epochMs>:<channel>_<threadTs>`: one row per mute attempt, with `action` (`muted`, `unreacted` when `muteReaction` is `""`, `mute-error`), `removed`, `added`, and `errors`.\n- `last`: the latest decision. Mute rows never overwrite it.\n- Counters: `total`, `mention`, `files`, `reply`, `shadow-skip`, `skipped`, `error`, `muted`, `unreacted`, `mute-error`, and `reason:<label>`.\n\n## Known limits\n\n- When Lead already has an active task in the thread, the buffer steers that task instead of creating one, so `pre.task.create` does not fire and the gate does not run.\n- A blocked flush makes the thread buffer log a `TaskCreationBlockedError`. Nothing is lost: the next follow-up\'s `<thread_context>` carries the earlier messages.\n- A flush of several messages mutes only the triggering (last) message. The others keep their acceptance reaction.\n'
|
|
10610
|
+
},
|
|
10007
10611
|
"task-digest": {
|
|
10008
10612
|
manifestFile: "manifest.yaml",
|
|
10009
10613
|
manifest: {
|
|
@@ -12959,6 +13563,85 @@ var getRequestListener = (fetchCallback, options = {}) => {
|
|
|
12959
13563
|
};
|
|
12960
13564
|
};
|
|
12961
13565
|
|
|
13566
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/shared/mediaType.js
|
|
13567
|
+
var import_content_type = __toESM(require_content_type(), 1);
|
|
13568
|
+
function mediaTypeEssence(header2) {
|
|
13569
|
+
if (!header2) {
|
|
13570
|
+
return;
|
|
13571
|
+
}
|
|
13572
|
+
try {
|
|
13573
|
+
return import_content_type.default.parse(header2).type;
|
|
13574
|
+
} catch {
|
|
13575
|
+
const essence = (header2.split(";", 1)[0] ?? "").trim().toLowerCase();
|
|
13576
|
+
if (essence === "" || header2.slice(essence.length).includes(",")) {
|
|
13577
|
+
return;
|
|
13578
|
+
}
|
|
13579
|
+
return essence;
|
|
13580
|
+
}
|
|
13581
|
+
}
|
|
13582
|
+
function isJsonContentType(header2) {
|
|
13583
|
+
if (header2 === "application/json") {
|
|
13584
|
+
return true;
|
|
13585
|
+
}
|
|
13586
|
+
return mediaTypeEssence(header2) === "application/json";
|
|
13587
|
+
}
|
|
13588
|
+
|
|
13589
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js
|
|
13590
|
+
var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
13591
|
+
var MAX_BATCH_SIZE2 = 100;
|
|
13592
|
+
function requestBodyTooLargeMessage(maxBytes) {
|
|
13593
|
+
return `Payload Too Large: Request body must not exceed ${maxBytes} bytes`;
|
|
13594
|
+
}
|
|
13595
|
+
function resolveMaxRequestBodySize(value) {
|
|
13596
|
+
if (value === undefined) {
|
|
13597
|
+
return DEFAULT_MAX_REQUEST_BODY_SIZE;
|
|
13598
|
+
}
|
|
13599
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
13600
|
+
throw new RangeError(`maxRequestBodySize must be a positive number of bytes, got ${String(value)}`);
|
|
13601
|
+
}
|
|
13602
|
+
return value;
|
|
13603
|
+
}
|
|
13604
|
+
async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE) {
|
|
13605
|
+
if (Number(request.headers.get("content-length")) > maxBytes) {
|
|
13606
|
+
return { tooLarge: true };
|
|
13607
|
+
}
|
|
13608
|
+
if (request.body === null) {
|
|
13609
|
+
return { tooLarge: false, text: "" };
|
|
13610
|
+
}
|
|
13611
|
+
const reader = request.body.getReader();
|
|
13612
|
+
const decoder = new TextDecoder;
|
|
13613
|
+
let received = 0;
|
|
13614
|
+
let text = "";
|
|
13615
|
+
try {
|
|
13616
|
+
for (;; ) {
|
|
13617
|
+
const { done, value } = await reader.read();
|
|
13618
|
+
if (done) {
|
|
13619
|
+
break;
|
|
13620
|
+
}
|
|
13621
|
+
received += value.byteLength;
|
|
13622
|
+
if (received > maxBytes) {
|
|
13623
|
+
return { tooLarge: true };
|
|
13624
|
+
}
|
|
13625
|
+
text += decoder.decode(value, { stream: true });
|
|
13626
|
+
}
|
|
13627
|
+
} finally {
|
|
13628
|
+
reader.releaseLock();
|
|
13629
|
+
}
|
|
13630
|
+
return { tooLarge: false, text: text + decoder.decode() };
|
|
13631
|
+
}
|
|
13632
|
+
|
|
13633
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/sseKeepAlive.js
|
|
13634
|
+
var DEFAULT_SSE_KEEP_ALIVE_MS = 15000;
|
|
13635
|
+
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
13636
|
+
function armSseKeepAlive(intervalMs, onTick) {
|
|
13637
|
+
if (!Number.isFinite(intervalMs) || intervalMs < 1) {
|
|
13638
|
+
return;
|
|
13639
|
+
}
|
|
13640
|
+
const timer = setInterval(onTick, Math.min(intervalMs, MAX_TIMER_DELAY_MS));
|
|
13641
|
+
timer.unref?.();
|
|
13642
|
+
return timer;
|
|
13643
|
+
}
|
|
13644
|
+
|
|
12962
13645
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
|
|
12963
13646
|
class WebStandardStreamableHTTPServerTransport {
|
|
12964
13647
|
constructor(options = {}) {
|
|
@@ -12966,10 +13649,12 @@ class WebStandardStreamableHTTPServerTransport {
|
|
|
12966
13649
|
this._hasHandledRequest = false;
|
|
12967
13650
|
this._streamMapping = new Map;
|
|
12968
13651
|
this._requestToStreamMapping = new Map;
|
|
13652
|
+
this._resumableStreams = new Set;
|
|
12969
13653
|
this._requestResponseMap = new Map;
|
|
12970
13654
|
this._initialized = false;
|
|
12971
13655
|
this._enableJsonResponse = false;
|
|
12972
13656
|
this._standaloneSseStreamId = "_GET_stream";
|
|
13657
|
+
this._closed = false;
|
|
12973
13658
|
this.sessionIdGenerator = options.sessionIdGenerator;
|
|
12974
13659
|
this._enableJsonResponse = options.enableJsonResponse ?? false;
|
|
12975
13660
|
this._eventStore = options.eventStore;
|
|
@@ -12979,6 +13664,23 @@ class WebStandardStreamableHTTPServerTransport {
|
|
|
12979
13664
|
this._allowedOrigins = options.allowedOrigins;
|
|
12980
13665
|
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
|
|
12981
13666
|
this._retryInterval = options.retryInterval;
|
|
13667
|
+
this._keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
|
|
13668
|
+
this._maxRequestBodySize = resolveMaxRequestBodySize(options.maxRequestBodySize);
|
|
13669
|
+
}
|
|
13670
|
+
startKeepAlive(controller, encoder) {
|
|
13671
|
+
if (this._closed)
|
|
13672
|
+
return;
|
|
13673
|
+
const timer = armSseKeepAlive(this._keepAliveMs, () => {
|
|
13674
|
+
try {
|
|
13675
|
+
controller.enqueue(encoder.encode(`: keepalive
|
|
13676
|
+
|
|
13677
|
+
`));
|
|
13678
|
+
} catch {
|
|
13679
|
+
if (timer !== undefined)
|
|
13680
|
+
clearInterval(timer);
|
|
13681
|
+
}
|
|
13682
|
+
});
|
|
13683
|
+
return timer;
|
|
12982
13684
|
}
|
|
12983
13685
|
async start() {
|
|
12984
13686
|
if (this._started) {
|
|
@@ -13026,6 +13728,9 @@ class WebStandardStreamableHTTPServerTransport {
|
|
|
13026
13728
|
return;
|
|
13027
13729
|
}
|
|
13028
13730
|
async handleRequest(req, options) {
|
|
13731
|
+
if (this._closed) {
|
|
13732
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
13733
|
+
}
|
|
13029
13734
|
if (!this.sessionIdGenerator && this._hasHandledRequest) {
|
|
13030
13735
|
throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
|
|
13031
13736
|
}
|
|
@@ -13065,6 +13770,7 @@ data:
|
|
|
13065
13770
|
`;
|
|
13066
13771
|
}
|
|
13067
13772
|
controller.enqueue(encoder.encode(primingEvent));
|
|
13773
|
+
this._resumableStreams.add(streamId);
|
|
13068
13774
|
}
|
|
13069
13775
|
async handleGetRequest(req) {
|
|
13070
13776
|
const acceptHeader = req.headers.get("accept");
|
|
@@ -13092,18 +13798,25 @@ data:
|
|
|
13092
13798
|
}
|
|
13093
13799
|
const encoder = new TextEncoder;
|
|
13094
13800
|
let streamController;
|
|
13801
|
+
let keepAliveTimer = undefined;
|
|
13095
13802
|
const readable = new ReadableStream({
|
|
13096
13803
|
start: (controller) => {
|
|
13097
13804
|
streamController = controller;
|
|
13098
13805
|
},
|
|
13099
13806
|
cancel: () => {
|
|
13100
|
-
|
|
13807
|
+
if (keepAliveTimer !== undefined) {
|
|
13808
|
+
clearInterval(keepAliveTimer);
|
|
13809
|
+
}
|
|
13810
|
+
if (this._streamMapping.get(this._standaloneSseStreamId)?.controller === streamController) {
|
|
13811
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
13812
|
+
}
|
|
13101
13813
|
}
|
|
13102
13814
|
});
|
|
13103
13815
|
const headers2 = {
|
|
13104
13816
|
"Content-Type": "text/event-stream",
|
|
13105
13817
|
"Cache-Control": "no-cache, no-transform",
|
|
13106
|
-
Connection: "keep-alive"
|
|
13818
|
+
Connection: "keep-alive",
|
|
13819
|
+
"X-Accel-Buffering": "no"
|
|
13107
13820
|
};
|
|
13108
13821
|
if (this.sessionId !== undefined) {
|
|
13109
13822
|
headers2["mcp-session-id"] = this.sessionId;
|
|
@@ -13112,12 +13825,16 @@ data:
|
|
|
13112
13825
|
controller: streamController,
|
|
13113
13826
|
encoder,
|
|
13114
13827
|
cleanup: () => {
|
|
13828
|
+
if (keepAliveTimer !== undefined) {
|
|
13829
|
+
clearInterval(keepAliveTimer);
|
|
13830
|
+
}
|
|
13115
13831
|
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
13116
13832
|
try {
|
|
13117
13833
|
streamController.close();
|
|
13118
13834
|
} catch {}
|
|
13119
13835
|
}
|
|
13120
13836
|
});
|
|
13837
|
+
keepAliveTimer = this.startKeepAlive(streamController, encoder);
|
|
13121
13838
|
return new Response(readable, { headers: headers2 });
|
|
13122
13839
|
}
|
|
13123
13840
|
async replayEvents(lastEventId) {
|
|
@@ -13141,20 +13858,33 @@ data:
|
|
|
13141
13858
|
const headers2 = {
|
|
13142
13859
|
"Content-Type": "text/event-stream",
|
|
13143
13860
|
"Cache-Control": "no-cache, no-transform",
|
|
13144
|
-
Connection: "keep-alive"
|
|
13861
|
+
Connection: "keep-alive",
|
|
13862
|
+
"X-Accel-Buffering": "no"
|
|
13145
13863
|
};
|
|
13146
13864
|
if (this.sessionId !== undefined) {
|
|
13147
13865
|
headers2["mcp-session-id"] = this.sessionId;
|
|
13148
13866
|
}
|
|
13149
13867
|
const encoder = new TextEncoder;
|
|
13150
13868
|
let streamController;
|
|
13869
|
+
let keepAliveTimer = undefined;
|
|
13870
|
+
let replayedStreamId = undefined;
|
|
13871
|
+
let cancelled = false;
|
|
13151
13872
|
const readable = new ReadableStream({
|
|
13152
13873
|
start: (controller) => {
|
|
13153
13874
|
streamController = controller;
|
|
13154
13875
|
},
|
|
13155
|
-
cancel: () => {
|
|
13876
|
+
cancel: () => {
|
|
13877
|
+
cancelled = true;
|
|
13878
|
+
if (keepAliveTimer !== undefined) {
|
|
13879
|
+
clearInterval(keepAliveTimer);
|
|
13880
|
+
}
|
|
13881
|
+
if (replayedStreamId !== undefined && this._streamMapping.get(replayedStreamId)?.controller === streamController) {
|
|
13882
|
+
this._streamMapping.delete(replayedStreamId);
|
|
13883
|
+
}
|
|
13884
|
+
}
|
|
13156
13885
|
});
|
|
13157
|
-
const
|
|
13886
|
+
const replayedEventIds = new Set;
|
|
13887
|
+
replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
|
|
13158
13888
|
send: async (eventId, message) => {
|
|
13159
13889
|
const success = this.writeSSEEvent(streamController, encoder, message, eventId);
|
|
13160
13890
|
if (!success) {
|
|
@@ -13162,19 +13892,34 @@ data:
|
|
|
13162
13892
|
try {
|
|
13163
13893
|
streamController.close();
|
|
13164
13894
|
} catch {}
|
|
13895
|
+
} else {
|
|
13896
|
+
replayedEventIds.add(eventId);
|
|
13165
13897
|
}
|
|
13166
13898
|
}
|
|
13167
13899
|
});
|
|
13900
|
+
if (this._closed || cancelled) {
|
|
13901
|
+
try {
|
|
13902
|
+
streamController.close();
|
|
13903
|
+
} catch {}
|
|
13904
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
13905
|
+
}
|
|
13906
|
+
this._streamMapping.get(replayedStreamId)?.cleanup();
|
|
13168
13907
|
this._streamMapping.set(replayedStreamId, {
|
|
13169
13908
|
controller: streamController,
|
|
13170
13909
|
encoder,
|
|
13910
|
+
replayedEventIds,
|
|
13171
13911
|
cleanup: () => {
|
|
13912
|
+
if (keepAliveTimer !== undefined) {
|
|
13913
|
+
clearInterval(keepAliveTimer);
|
|
13914
|
+
}
|
|
13172
13915
|
this._streamMapping.delete(replayedStreamId);
|
|
13173
13916
|
try {
|
|
13174
13917
|
streamController.close();
|
|
13175
13918
|
} catch {}
|
|
13176
13919
|
}
|
|
13177
13920
|
});
|
|
13921
|
+
this._resumableStreams.add(replayedStreamId);
|
|
13922
|
+
keepAliveTimer = this.startKeepAlive(streamController, encoder);
|
|
13178
13923
|
return new Response(readable, { headers: headers2 });
|
|
13179
13924
|
} catch (error) {
|
|
13180
13925
|
this.onerror?.(error);
|
|
@@ -13224,7 +13969,7 @@ data:
|
|
|
13224
13969
|
return this.createJsonErrorResponse(406, -32000, "Not Acceptable: Client must accept both application/json and text/event-stream");
|
|
13225
13970
|
}
|
|
13226
13971
|
const ct = req.headers.get("content-type");
|
|
13227
|
-
if (!ct
|
|
13972
|
+
if (!isJsonContentType(ct)) {
|
|
13228
13973
|
this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json"));
|
|
13229
13974
|
return this.createJsonErrorResponse(415, -32000, "Unsupported Media Type: Content-Type must be application/json");
|
|
13230
13975
|
}
|
|
@@ -13237,12 +13982,22 @@ data:
|
|
|
13237
13982
|
rawMessage = options.parsedBody;
|
|
13238
13983
|
} else {
|
|
13239
13984
|
try {
|
|
13240
|
-
|
|
13985
|
+
const body = await readRequestBody(req, this._maxRequestBodySize);
|
|
13986
|
+
if (body.tooLarge) {
|
|
13987
|
+
const message = requestBodyTooLargeMessage(this._maxRequestBodySize);
|
|
13988
|
+
this.onerror?.(new Error(message));
|
|
13989
|
+
return this.createJsonErrorResponse(413, -32000, message);
|
|
13990
|
+
}
|
|
13991
|
+
rawMessage = JSON.parse(body.text);
|
|
13241
13992
|
} catch {
|
|
13242
13993
|
this.onerror?.(new Error("Parse error: Invalid JSON"));
|
|
13243
13994
|
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
|
|
13244
13995
|
}
|
|
13245
13996
|
}
|
|
13997
|
+
if (Array.isArray(rawMessage) && rawMessage.length > MAX_BATCH_SIZE2) {
|
|
13998
|
+
this.onerror?.(new Error(`Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE2} messages`));
|
|
13999
|
+
return this.createJsonErrorResponse(400, -32600, `Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE2} messages`);
|
|
14000
|
+
}
|
|
13246
14001
|
let messages;
|
|
13247
14002
|
try {
|
|
13248
14003
|
if (Array.isArray(rawMessage)) {
|
|
@@ -13254,6 +14009,9 @@ data:
|
|
|
13254
14009
|
this.onerror?.(new Error("Parse error: Invalid JSON-RPC message"));
|
|
13255
14010
|
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
|
|
13256
14011
|
}
|
|
14012
|
+
if (this._closed) {
|
|
14013
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
14014
|
+
}
|
|
13257
14015
|
const isInitializationRequest = messages.some(isInitializeRequest);
|
|
13258
14016
|
if (isInitializationRequest) {
|
|
13259
14017
|
if (this._initialized && this.sessionId !== undefined) {
|
|
@@ -13280,6 +14038,9 @@ data:
|
|
|
13280
14038
|
return protocolError2;
|
|
13281
14039
|
}
|
|
13282
14040
|
}
|
|
14041
|
+
if (this._closed) {
|
|
14042
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
14043
|
+
}
|
|
13283
14044
|
const hasRequests = messages.some(isJSONRPCRequest);
|
|
13284
14045
|
if (!hasRequests) {
|
|
13285
14046
|
for (const message of messages) {
|
|
@@ -13310,18 +14071,25 @@ data:
|
|
|
13310
14071
|
}
|
|
13311
14072
|
const encoder = new TextEncoder;
|
|
13312
14073
|
let streamController;
|
|
14074
|
+
let keepAliveTimer = undefined;
|
|
13313
14075
|
const readable = new ReadableStream({
|
|
13314
14076
|
start: (controller) => {
|
|
13315
14077
|
streamController = controller;
|
|
13316
14078
|
},
|
|
13317
14079
|
cancel: () => {
|
|
13318
|
-
|
|
14080
|
+
if (keepAliveTimer !== undefined) {
|
|
14081
|
+
clearInterval(keepAliveTimer);
|
|
14082
|
+
}
|
|
14083
|
+
if (this._streamMapping.get(streamId)?.controller === streamController) {
|
|
14084
|
+
this._streamMapping.delete(streamId);
|
|
14085
|
+
}
|
|
13319
14086
|
}
|
|
13320
14087
|
});
|
|
13321
14088
|
const headers2 = {
|
|
13322
14089
|
"Content-Type": "text/event-stream",
|
|
13323
|
-
"Cache-Control": "no-cache",
|
|
13324
|
-
Connection: "keep-alive"
|
|
14090
|
+
"Cache-Control": "no-cache, no-transform",
|
|
14091
|
+
Connection: "keep-alive",
|
|
14092
|
+
"X-Accel-Buffering": "no"
|
|
13325
14093
|
};
|
|
13326
14094
|
if (this.sessionId !== undefined) {
|
|
13327
14095
|
headers2["mcp-session-id"] = this.sessionId;
|
|
@@ -13332,6 +14100,9 @@ data:
|
|
|
13332
14100
|
controller: streamController,
|
|
13333
14101
|
encoder,
|
|
13334
14102
|
cleanup: () => {
|
|
14103
|
+
if (keepAliveTimer !== undefined) {
|
|
14104
|
+
clearInterval(keepAliveTimer);
|
|
14105
|
+
}
|
|
13335
14106
|
this._streamMapping.delete(streamId);
|
|
13336
14107
|
try {
|
|
13337
14108
|
streamController.close();
|
|
@@ -13341,19 +14112,33 @@ data:
|
|
|
13341
14112
|
this._requestToStreamMapping.set(message.id, streamId);
|
|
13342
14113
|
}
|
|
13343
14114
|
}
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
14115
|
+
try {
|
|
14116
|
+
await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
|
|
14117
|
+
for (const message of messages) {
|
|
14118
|
+
let closeSSEStream;
|
|
14119
|
+
let closeStandaloneSSEStream;
|
|
14120
|
+
if (isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
|
|
14121
|
+
closeSSEStream = () => {
|
|
14122
|
+
this.closeSSEStream(message.id);
|
|
14123
|
+
};
|
|
14124
|
+
closeStandaloneSSEStream = () => {
|
|
14125
|
+
this.closeStandaloneSSEStream();
|
|
14126
|
+
};
|
|
14127
|
+
}
|
|
14128
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
|
|
14129
|
+
}
|
|
14130
|
+
} catch (error) {
|
|
14131
|
+
this._streamMapping.get(streamId)?.cleanup();
|
|
14132
|
+
this._resumableStreams.delete(streamId);
|
|
14133
|
+
for (const message of messages) {
|
|
14134
|
+
if (isJSONRPCRequest(message)) {
|
|
14135
|
+
this._requestToStreamMapping.delete(message.id);
|
|
14136
|
+
}
|
|
13355
14137
|
}
|
|
13356
|
-
|
|
14138
|
+
throw error;
|
|
14139
|
+
}
|
|
14140
|
+
if (this._streamMapping.get(streamId)?.controller === streamController) {
|
|
14141
|
+
keepAliveTimer = this.startKeepAlive(streamController, encoder);
|
|
13357
14142
|
}
|
|
13358
14143
|
return new Response(readable, { status: 200, headers: headers2 });
|
|
13359
14144
|
} catch (error) {
|
|
@@ -13370,9 +14155,12 @@ data:
|
|
|
13370
14155
|
if (protocolError2) {
|
|
13371
14156
|
return protocolError2;
|
|
13372
14157
|
}
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
14158
|
+
try {
|
|
14159
|
+
await Promise.resolve(this._onsessionclosed?.(this.sessionId));
|
|
14160
|
+
return new Response(null, { status: 200 });
|
|
14161
|
+
} finally {
|
|
14162
|
+
await this.close();
|
|
14163
|
+
}
|
|
13376
14164
|
}
|
|
13377
14165
|
validateSession(req) {
|
|
13378
14166
|
if (this.sessionIdGenerator === undefined) {
|
|
@@ -13402,11 +14190,16 @@ data:
|
|
|
13402
14190
|
return;
|
|
13403
14191
|
}
|
|
13404
14192
|
async close() {
|
|
14193
|
+
if (this._closed) {
|
|
14194
|
+
return;
|
|
14195
|
+
}
|
|
14196
|
+
this._closed = true;
|
|
13405
14197
|
this._streamMapping.forEach(({ cleanup }) => {
|
|
13406
14198
|
cleanup();
|
|
13407
14199
|
});
|
|
13408
14200
|
this._streamMapping.clear();
|
|
13409
14201
|
this._requestResponseMap.clear();
|
|
14202
|
+
this._resumableStreams.clear();
|
|
13410
14203
|
this.onclose?.();
|
|
13411
14204
|
}
|
|
13412
14205
|
closeSSEStream(requestId) {
|
|
@@ -13441,7 +14234,7 @@ data:
|
|
|
13441
14234
|
if (standaloneSse === undefined) {
|
|
13442
14235
|
return;
|
|
13443
14236
|
}
|
|
13444
|
-
if (standaloneSse.controller && standaloneSse.encoder) {
|
|
14237
|
+
if (standaloneSse.controller && standaloneSse.encoder && (eventId === undefined || !standaloneSse.replayedEventIds?.has(eventId))) {
|
|
13445
14238
|
this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
|
|
13446
14239
|
}
|
|
13447
14240
|
return;
|
|
@@ -13450,13 +14243,19 @@ data:
|
|
|
13450
14243
|
if (!streamId) {
|
|
13451
14244
|
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
13452
14245
|
}
|
|
13453
|
-
|
|
13454
|
-
if (!this._enableJsonResponse
|
|
14246
|
+
let stream = this._streamMapping.get(streamId);
|
|
14247
|
+
if (!this._enableJsonResponse) {
|
|
13455
14248
|
let eventId;
|
|
13456
14249
|
if (this._eventStore) {
|
|
13457
14250
|
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
14251
|
+
stream = this._streamMapping.get(streamId);
|
|
14252
|
+
}
|
|
14253
|
+
if (stream?.controller && stream?.encoder && (eventId === undefined || !stream.replayedEventIds?.has(eventId))) {
|
|
14254
|
+
const written = this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
|
|
14255
|
+
if (written && eventId !== undefined) {
|
|
14256
|
+
this._resumableStreams.add(streamId);
|
|
14257
|
+
}
|
|
13458
14258
|
}
|
|
13459
|
-
this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
|
|
13460
14259
|
}
|
|
13461
14260
|
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
13462
14261
|
this._requestResponseMap.set(requestId, message);
|
|
@@ -13464,6 +14263,25 @@ data:
|
|
|
13464
14263
|
const allResponsesReady = relatedIds.every((id) => this._requestResponseMap.has(id));
|
|
13465
14264
|
if (allResponsesReady) {
|
|
13466
14265
|
if (!stream) {
|
|
14266
|
+
if (this._closed) {
|
|
14267
|
+
for (const id of relatedIds) {
|
|
14268
|
+
this._requestResponseMap.delete(id);
|
|
14269
|
+
this._requestToStreamMapping.delete(id);
|
|
14270
|
+
}
|
|
14271
|
+
return;
|
|
14272
|
+
}
|
|
14273
|
+
if (!this._enableJsonResponse && this._eventStore && this._resumableStreams.has(streamId)) {
|
|
14274
|
+
for (const id of relatedIds) {
|
|
14275
|
+
this._requestResponseMap.delete(id);
|
|
14276
|
+
this._requestToStreamMapping.delete(id);
|
|
14277
|
+
}
|
|
14278
|
+
this._resumableStreams.delete(streamId);
|
|
14279
|
+
return;
|
|
14280
|
+
}
|
|
14281
|
+
for (const id of relatedIds) {
|
|
14282
|
+
this._requestResponseMap.delete(id);
|
|
14283
|
+
this._requestToStreamMapping.delete(id);
|
|
14284
|
+
}
|
|
13467
14285
|
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
13468
14286
|
}
|
|
13469
14287
|
if (this._enableJsonResponse && stream.resolveJson) {
|
|
@@ -13486,6 +14304,7 @@ data:
|
|
|
13486
14304
|
this._requestResponseMap.delete(id);
|
|
13487
14305
|
this._requestToStreamMapping.delete(id);
|
|
13488
14306
|
}
|
|
14307
|
+
this._resumableStreams.delete(streamId);
|
|
13489
14308
|
}
|
|
13490
14309
|
}
|
|
13491
14310
|
}
|
|
@@ -13720,6 +14539,7 @@ async function handleMcp(req, res, transports, sessionActivity = {}, sessionAgen
|
|
|
13720
14539
|
}
|
|
13721
14540
|
const server = await createServer({
|
|
13722
14541
|
preloadedTools,
|
|
14542
|
+
enabledTools: configs.find((config) => config.key === "SWARM_ENABLED_TOOLS")?.value ?? process.env.SWARM_ENABLED_TOOLS,
|
|
13723
14543
|
scriptsOnly: resolveScriptsOnlyMode({
|
|
13724
14544
|
env: process.env.SCRIPTS_ONLY_MCP,
|
|
13725
14545
|
configValue
|
|
@@ -17984,6 +18804,7 @@ init_db2();
|
|
|
17984
18804
|
init_memory();
|
|
17985
18805
|
init_constants2();
|
|
17986
18806
|
init_types();
|
|
18807
|
+
init_openrouter_base_url();
|
|
17987
18808
|
|
|
17988
18809
|
// src/http/status.ts
|
|
17989
18810
|
init_zod();
|
|
@@ -18707,6 +19528,7 @@ async function handleMemoryProbe(body) {
|
|
|
18707
19528
|
const client = new OpenAI({
|
|
18708
19529
|
baseURL: endpoint.toString(),
|
|
18709
19530
|
apiKey,
|
|
19531
|
+
defaultHeaders: getOpenRouterAttributionHeaders(endpoint.toString()),
|
|
18710
19532
|
timeout: 15000,
|
|
18711
19533
|
maxRetries: 0,
|
|
18712
19534
|
fetchOptions: { redirect: "manual" }
|
|
@@ -22773,6 +23595,25 @@ var setConnectionEnabledRoute = route({
|
|
|
22773
23595
|
},
|
|
22774
23596
|
rbac: { permission: "script-connection.manage" }
|
|
22775
23597
|
});
|
|
23598
|
+
var deleteConnectionRoute = route({
|
|
23599
|
+
method: "delete",
|
|
23600
|
+
path: "/api/script-connections/{id}",
|
|
23601
|
+
pattern: ["api", "script-connections", null],
|
|
23602
|
+
operationId: "script_connections_delete",
|
|
23603
|
+
summary: "Delete a script connection",
|
|
23604
|
+
description: "Hard-deletes the connection, its auto-managed credential binding, and any derived inline secret it owns.",
|
|
23605
|
+
tags: ["Script Connections"],
|
|
23606
|
+
params: idParamsSchema2,
|
|
23607
|
+
responses: {
|
|
23608
|
+
200: {
|
|
23609
|
+
description: "Script connection deleted",
|
|
23610
|
+
schema: exports_external.object({ deleted: exports_external.literal(true), id: exports_external.string() })
|
|
23611
|
+
},
|
|
23612
|
+
403: { description: "Only the lead agent can manage script connections" },
|
|
23613
|
+
404: { description: "Script connection not found" }
|
|
23614
|
+
},
|
|
23615
|
+
rbac: { permission: "script-connection.manage" }
|
|
23616
|
+
});
|
|
22776
23617
|
var listCredentialBindingsRoute = route({
|
|
22777
23618
|
method: "get",
|
|
22778
23619
|
path: "/api/credential-bindings",
|
|
@@ -23861,6 +24702,20 @@ async function handleScriptConnections(req, res, pathSegments, queryParams, agen
|
|
|
23861
24702
|
});
|
|
23862
24703
|
return true;
|
|
23863
24704
|
}
|
|
24705
|
+
if (deleteConnectionRoute.match(req.method, pathSegments)) {
|
|
24706
|
+
const parsed = await deleteConnectionRoute.parse(req, res, pathSegments, queryParams);
|
|
24707
|
+
if (!parsed)
|
|
24708
|
+
return true;
|
|
24709
|
+
if (!await ensureConnectionAdmin(req, res, agentId))
|
|
24710
|
+
return true;
|
|
24711
|
+
const deleted = await deleteScriptConnection(parsed.params.id);
|
|
24712
|
+
if (!deleted) {
|
|
24713
|
+
jsonError(res, "Script connection not found.", 404);
|
|
24714
|
+
return true;
|
|
24715
|
+
}
|
|
24716
|
+
deleteConnectionRoute.respond(res, 200, { deleted: true, id: deleted.id });
|
|
24717
|
+
return true;
|
|
24718
|
+
}
|
|
23864
24719
|
if (listCredentialBindingsRoute.match(req.method, pathSegments)) {
|
|
23865
24720
|
const includeManaged = queryParams.get("includeManaged") === "true";
|
|
23866
24721
|
listCredentialBindingsRoute.respond(res, 200, {
|
|
@@ -32848,7 +33703,7 @@ async function shutdown() {
|
|
|
32848
33703
|
sessionsProcessed: getServerSessionsProcessed()
|
|
32849
33704
|
});
|
|
32850
33705
|
if (hasCapability("scheduling")) {
|
|
32851
|
-
const { stopScheduler } = await import("./index-
|
|
33706
|
+
const { stopScheduler } = await import("./index-9fc2h7ww.js");
|
|
32852
33707
|
stopScheduler();
|
|
32853
33708
|
}
|
|
32854
33709
|
stopHeartbeat();
|
|
@@ -32857,7 +33712,7 @@ async function shutdown() {
|
|
|
32857
33712
|
await stopScriptRunSupervisor();
|
|
32858
33713
|
await stopSlackApp();
|
|
32859
33714
|
if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
|
|
32860
|
-
const { stopOAuthKeepalive } = await import("./keepalive-
|
|
33715
|
+
const { stopOAuthKeepalive } = await import("./keepalive-qhcqz56v.js");
|
|
32861
33716
|
await stopOAuthKeepalive();
|
|
32862
33717
|
}
|
|
32863
33718
|
stopOAuthPendingGc();
|
|
@@ -32932,15 +33787,15 @@ try {
|
|
|
32932
33787
|
console.warn("[startup] CAPABILITIES upgrade seed failed (non-fatal):", err);
|
|
32933
33788
|
}
|
|
32934
33789
|
try {
|
|
32935
|
-
const { ensurePricingSeeded } = await import("./boot-seeds-
|
|
33790
|
+
const { ensurePricingSeeded } = await import("./boot-seeds-9qt0j8j2.js");
|
|
32936
33791
|
ensurePricingSeeded();
|
|
32937
|
-
const { startPricingRefreshLoop } = await import("./pricing-refresh-
|
|
33792
|
+
const { startPricingRefreshLoop } = await import("./pricing-refresh-35f91nx2.js");
|
|
32938
33793
|
startPricingRefreshLoop();
|
|
32939
33794
|
} catch (err) {
|
|
32940
33795
|
console.error("[startup] Failed to seed pricing rows:", err);
|
|
32941
33796
|
}
|
|
32942
33797
|
try {
|
|
32943
|
-
const { ensureRbacSeeded } = await import("./boot-seeds-
|
|
33798
|
+
const { ensureRbacSeeded } = await import("./boot-seeds-9qt0j8j2.js");
|
|
32944
33799
|
ensureRbacSeeded();
|
|
32945
33800
|
} catch (err) {
|
|
32946
33801
|
console.error("[startup] Failed to sync RBAC seed rows:", err);
|
|
@@ -32948,7 +33803,7 @@ try {
|
|
|
32948
33803
|
throw err;
|
|
32949
33804
|
}
|
|
32950
33805
|
try {
|
|
32951
|
-
const { runAllSeeders } = await import("./index-
|
|
33806
|
+
const { runAllSeeders } = await import("./index-h26wjcfy.js");
|
|
32952
33807
|
await runAllSeeders({ scriptEmbeddingMode: "skip" });
|
|
32953
33808
|
} catch (err) {
|
|
32954
33809
|
console.error("[startup] Failed to seed built-in entities:", err);
|
|
@@ -33008,37 +33863,37 @@ httpServer.listen(port, async () => {
|
|
|
33008
33863
|
await initWorkflows();
|
|
33009
33864
|
await startScriptRunSupervisor(getMcpBaseUrl());
|
|
33010
33865
|
if (hasCapability("scheduling")) {
|
|
33011
|
-
const { startScheduler } = await import("./index-
|
|
33012
|
-
const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-
|
|
33866
|
+
const { startScheduler } = await import("./index-9fc2h7ww.js");
|
|
33867
|
+
const { getExecutorRegistry: getExecutorRegistry2 } = await import("./index-48pb9erw.js");
|
|
33013
33868
|
const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 1e4;
|
|
33014
33869
|
startScheduler(getExecutorRegistry2(), intervalMs, {
|
|
33015
33870
|
runId: globalState.__runId
|
|
33016
33871
|
});
|
|
33017
33872
|
}
|
|
33018
33873
|
if (!isEnvFlagEnabled("HEARTBEAT_DISABLE", false)) {
|
|
33019
|
-
const { startHeartbeat } = await import("./index-
|
|
33874
|
+
const { startHeartbeat } = await import("./index-w2yhye23.js");
|
|
33020
33875
|
const heartbeatMs = Number(process.env.HEARTBEAT_INTERVAL_MS) || 90000;
|
|
33021
33876
|
startHeartbeat(heartbeatMs);
|
|
33022
33877
|
}
|
|
33023
33878
|
if (process.env.OAUTH_KEEPALIVE_DISABLE !== "true") {
|
|
33024
|
-
const { startOAuthKeepalive } = await import("./keepalive-
|
|
33879
|
+
const { startOAuthKeepalive } = await import("./keepalive-qhcqz56v.js");
|
|
33025
33880
|
startOAuthKeepalive();
|
|
33026
33881
|
}
|
|
33027
33882
|
if (process.env.OAUTH_REFRESH_SWEEP_DISABLE !== "true") {
|
|
33028
|
-
const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-
|
|
33883
|
+
const { startOAuthRefreshSweep } = await import("./oauth-refresh-sweep-ngskthwh.js");
|
|
33029
33884
|
startOAuthRefreshSweep();
|
|
33030
33885
|
}
|
|
33031
33886
|
startOAuthPendingGc();
|
|
33032
33887
|
await startMemoryGc();
|
|
33033
33888
|
await startDbRetention();
|
|
33034
33889
|
startHumanFreeDrain();
|
|
33035
|
-
import("./boot-reembed-
|
|
33890
|
+
import("./boot-reembed-jvfadhsk.js").then(({ runBootReembed }) => runBootReembed()).catch((err) => {
|
|
33036
33891
|
console.error("[boot-reembed] startup backfill failed (non-fatal):", err);
|
|
33037
33892
|
});
|
|
33038
|
-
import("./boot-reembed-
|
|
33893
|
+
import("./boot-reembed-20q3nzvg.js").then(({ runBootReembedScripts }) => runBootReembedScripts()).catch((err) => {
|
|
33039
33894
|
console.error("[boot-reembed-scripts] startup backfill failed (non-fatal):", err);
|
|
33040
33895
|
});
|
|
33041
|
-
import("./boot-scrub-sweep-
|
|
33896
|
+
import("./boot-scrub-sweep-jtfajzve.js").then(({ runBootScrubSweep }) => runBootScrubSweep()).catch((err) => {
|
|
33042
33897
|
console.error("[boot-scrub-sweep] startup scrub failed (non-fatal):", err);
|
|
33043
33898
|
});
|
|
33044
33899
|
}).on("error", (err) => {
|