@desplega.ai/agent-swarm 2.4.4 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-thq04adh.js → acp-adapter-2wdkktv6.js} +1 -1
- package/dist/{actions-j6ywh6x5.js → actions-c40ebcm4.js} +18 -18
- package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-wb67m95z.js} +2 -2
- package/dist/{app-rb7vc02s.js → app-353vre8g.js} +3 -3
- package/dist/{assistant-ftta1zjf.js → assistant-sj1njh23.js} +21 -21
- package/dist/{boot-reembed-srp6v6af.js → boot-reembed-b9c3az3c.js} +1 -1
- package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-qdkvxeem.js} +1 -1
- package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
- package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-j2273s54.js} +4 -4
- package/dist/{claude-adapter-k5ksyres.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-n5a5eqks.js → claude-managed-setup-smrqk5j5.js} +3 -3
- package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-38xnz21e.js} +8284 -8267
- package/dist/{cli-cynvc9fj.js → cli-018r4jq6.js} +5 -5
- package/dist/{cli-9fhkkpj5.js → cli-1f3qzw6y.js} +1 -1
- package/dist/{cli-dqb4n629.js → cli-1sp861s4.js} +7 -7
- package/dist/{cli-gqvh8d8a.js → cli-1syn2xy1.js} +10 -10
- package/dist/{cli-qkhc5aqy.js → cli-37j04k05.js} +11 -11
- package/dist/{cli-1d70crsy.js → cli-3a45zcgm.js} +1 -1
- package/dist/{cli-t7cyef5g.js → cli-43x64hv3.js} +4 -4
- package/dist/{cli-hj3debgw.js → cli-4zfqgjc5.js} +2 -2
- package/dist/{cli-55qmbp6z.js → cli-7c9qs530.js} +1 -1
- package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
- package/dist/{cli-pccfmtb5.js → cli-94gwyg7m.js} +7 -7
- package/dist/{cli-51y09c6p.js → cli-9hstj09h.js} +5 -5
- package/dist/{cli-zrwpctp4.js → cli-9yqgm9nd.js} +22 -22
- package/dist/{cli-esxerzg7.js → cli-a92r1127.js} +1 -1
- package/dist/{cli-b8k7epj1.js → cli-be2161h1.js} +7 -7
- package/dist/{cli-xc27fwzg.js → cli-bnes4z6b.js} +2 -2
- package/dist/{cli-zrk6jxv8.js → cli-cxxe61f6.js} +1 -1
- package/dist/{cli-amyhm3gj.js → cli-dk44dxec.js} +1 -1
- package/dist/{cli-nc35pxb7.js → cli-eww120g8.js} +1 -0
- package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
- package/dist/{cli-82d3nkc3.js → cli-f0g3pgvf.js} +1 -1
- package/dist/{cli-1ephgwnq.js → cli-ftc1g5tj.js} +2 -2
- package/dist/{cli-2v045xyf.js → cli-fv5hepyy.js} +3 -3
- package/dist/{cli-bnz0r3kc.js → cli-g35m2xwm.js} +4 -4
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-w6nadkh4.js → cli-j99azg3g.js} +3 -3
- package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
- package/dist/{cli-4dg55wsk.js → cli-kpw04va0.js} +1 -1
- package/dist/{cli-3cgngyd8.js → cli-ms85309d.js} +33 -15
- package/dist/{cli-v3yr2gbg.js → cli-n72rg53p.js} +1471 -635
- package/dist/{cli-37yb7307.js → cli-p14m2cnp.js} +1 -1
- package/dist/{cli-2zqpkvtt.js → cli-qfxc84gh.js} +1 -1
- package/dist/{cli-44159k28.js → cli-qwhy2qma.js} +1 -1
- package/dist/{cli-2y91wnbh.js → cli-st8aw1m6.js} +221 -9
- package/dist/{cli-vq97g1jq.js → cli-tw4p90jb.js} +92 -192
- package/dist/{cli-te6b5wvg.js → cli-v0yrfmdh.js} +1 -1
- package/dist/{cli-xj0re5tm.js → cli-v8fjakwf.js} +1 -1
- package/dist/{cli-p20r8w42.js → cli-wt0jf9hm.js} +215 -80
- package/dist/{cli-q6r7t299.js → cli-xpa64rek.js} +33 -11
- package/dist/{cli-vdm20wej.js → cli-z7d29rw9.js} +2 -2
- package/dist/cli.js +19 -19
- package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tn6k5mkg.js} +1 -1
- package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-7vjt7jbw.js} +1 -1
- package/dist/{commands-pcymaeg9.js → commands-t7rz9146.js} +2 -2
- package/dist/{controller-fpjyk46r.js → controller-m2jasma5.js} +2 -2
- package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-9ftpex99.js} +19 -19
- package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-mjv79e4z.js} +1 -1
- package/dist/{db-xsjg9jqq.js → db-6sfrn9g3.js} +1 -1
- package/dist/{db-sgd12bt9.js → db-rn673r06.js} +3 -3
- package/dist/{dispatcher-x0jfw2c9.js → dispatcher-c155xz0p.js} +17 -17
- package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-3mmszc1x.js} +1 -1
- package/dist/{external-channel-8njj4h3b.js → external-channel-35k6tkv8.js} +2 -2
- package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-tfrq7tg1.js} +2 -2
- package/dist/handlers-7gw64dxw.js +39 -0
- package/dist/{heartbeat-wd03zhn0.js → heartbeat-dyg4npkp.js} +20 -20
- package/dist/{hook-f0vghj42.js → hook-drspbwpe.js} +1 -1
- package/dist/{hook-xzj2ts2p.js → hook-x26b31bg.js} +1 -1
- package/dist/{http-4hwxv62p.js → http-pk972xyw.js} +355 -216
- package/dist/{index-ww2vznkp.js → index-arytgj5q.js} +1 -1
- package/dist/{index-hg0x2cdv.js → index-bgdyvcnv.js} +17 -17
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/{index-gdh8jrdm.js → index-gabbcdn2.js} +17 -17
- package/dist/{index-97q4xn03.js → index-jwxkkex2.js} +129 -75
- package/dist/index-tfb26zhy.js +41 -0
- package/dist/{keepalive-hrh7xnr9.js → keepalive-7yrng7px.js} +3 -3
- package/dist/{lead-ee8vazz2.js → lead-7e02z839.js} +29 -29
- package/dist/{maintenance-e8wnwnqx.js → maintenance-e6cemdqt.js} +1 -1
- package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
- package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
- package/dist/{onboard-dtwcq603.js → onboard-0a9y5yrc.js} +2 -2
- package/dist/{otel-impl-w3fbhpn0.js → otel-impl-trjpkfb7.js} +1 -1
- package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-d2b5my0y.js} +3 -3
- package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-hey3yv9d.js} +2 -2
- package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-nxh5ahwk.js} +1 -1
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-n16n0phe.js → render-v2-rr3p2atg.js} +3 -3
- package/dist/{resume-z0fgjc38.js → resume-2efkf1s1.js} +5 -5
- package/dist/{session-access-cv3mq6mc.js → session-access-s2mttxpw.js} +4 -4
- package/dist/{setup-mafhj3yt.js → setup-vypma5yt.js} +1 -1
- package/dist/{work-objects-8b86mgjp.js → work-objects-rvhckdkp.js} +20 -20
- package/dist/{worker-16s75hm5.js → worker-5snytpbd.js} +29 -29
- package/openapi.json +9 -2
- package/package.json +7 -7
- package/src/apps/catalog.generated.json +206 -4
- package/src/apps/page-validator.ts +13 -1
- 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/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/swarm-config-guard.ts +24 -1
- package/src/be/task-attachment-store.ts +30 -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/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/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/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/cursor-adapter.ts +1 -1
- 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/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/config-api-only-keys.test.ts +9 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- 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 +242 -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/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/realtime-transport.test.ts +13 -7
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- 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/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- 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 +6 -0
- package/src/utils/config-env-export.ts +3 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/transcription.ts +246 -0
- package/src/workflows/executors/system-one-decision.ts +28 -7
- package/src/workflows/executors/system-one-providers.ts +39 -4
- 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/skills/apps/SKILL.md +28 -6
- package/templates/skills/apps/content.md +28 -6
- 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 +5 -5
- 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/handlers-0pvee9nw.js +0 -39
- package/dist/index-thm0y6a8.js +0 -41
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Generated by scripts/bundle-extension-types.ts. Do not edit.
|
|
2
2
|
// biome-ignore-all lint/suspicious/noTemplateCurlyInString: Type declarations contain literal template placeholders.
|
|
3
3
|
export const EXTENSION_TYPE_DEFINITIONS =
|
|
4
|
-
'// biome-ignore-all lint/suspicious/noConfusingVoidType: Extension handlers may explicitly return no result.\ndeclare module "swarm-extension" {\n export type CreateTaskOptions = {\n key?: string | undefined;\n agentId?: string | null | undefined;\n creatorAgentId?: string | undefined;\n source?:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "azure-devops"\n | "system"\n | "linear"\n | "jira"\n | "comb"\n | "chat"\n | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n taskType?: string | undefined;\n tags?: string[] | undefined;\n priority?: number | undefined;\n dependsOn?: string[] | undefined;\n offeredTo?: string | undefined;\n status?: "draft" | "backlog" | "unassigned" | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n overrideSlackContext?: boolean | undefined;\n vcsProvider?: "github" | "gitlab" | "azure-devops" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n profile?: string | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | undefined;\n workflowRunStepId?: string | undefined;\n sourceTaskId?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n inheritParentOutputSchema?: boolean | undefined;\n inheritParentRoutingAffinity?: boolean | undefined;\n inheritParentFollowUpConfig?: boolean | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n requestedByUserId?: string | undefined;\n contextKey?: string | undefined;\n bypassTrackerContextDedup?: boolean | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | "amp"\n | "cursor"\n | "grok"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n };\n\n export type AgentTask = {\n id: string;\n key: string;\n agentId: string | null;\n task: string;\n status:\n | "draft"\n | "backlog"\n | "unassigned"\n | "offered"\n | "reviewing"\n | "pending"\n | "in_progress"\n | "paused"\n | "completed"\n | "failed"\n | "cancelled"\n | "superseded";\n source:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "azure-devops"\n | "system"\n | "linear"\n | "jira"\n | "comb"\n | "chat";\n tags: string[];\n priority: number;\n dependsOn: string[];\n createdAt: string;\n lastUpdatedAt: string;\n slackReplySent: boolean;\n wasPaused: boolean;\n creatorAgentId?: string | undefined;\n title?: string | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n poolWakeAt?: string | undefined;\n taskType?: string | undefined;\n offeredTo?: string | undefined;\n offeredAt?: string | undefined;\n acceptedAt?: string | undefined;\n rejectionReason?: string | undefined;\n finishedAt?: string | undefined;\n notifiedAt?: string | undefined;\n failureReason?: string | undefined;\n output?: string | undefined;\n progress?: string | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n slackProgressMessageTs?: string | undefined;\n slackTreeRootMessageTs?: string | undefined;\n vcsProvider?: "github" | "gitlab" | "azure-devops" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n claudeSessionId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n resolvedModel?: string | undefined;\n modelSource?: string | undefined;\n modelAlias?: string | undefined;\n resolvedEffort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n profile?: string | undefined;\n resolvedProfile?: string | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | null | undefined;\n workflowRunStepId?: string | null | undefined;\n contextKey?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n deferredAt?: string | undefined;\n compactionCount?: number | undefined;\n peakContextPercent?: number | undefined;\n peakContextTokens?: number | undefined;\n contextWindowSize?: number | undefined;\n credentialKeySuffix?: string | undefined;\n credentialKeyType?: string | undefined;\n requestedByUserId?: string | undefined;\n swarmVersion?: string | undefined;\n provider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | "amp"\n | "cursor"\n | "grok"\n | undefined;\n providerMeta?: Record<string, unknown> | undefined;\n harnessVariant?: string | undefined;\n harnessVariantMeta?: Record<string, unknown> | undefined;\n totalCostUsd?: number | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | "amp"\n | "cursor"\n | "grok"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n routingAffinityInvalid?: boolean | undefined;\n };\n\n export type ActiveSession = {\n id: string;\n agentId: string;\n taskId: string | null;\n triggerType: string;\n inboxMessageId: string | null;\n taskDescription: string | null;\n runnerSessionId: string | null;\n providerSessionId: string | null;\n startedAt: string;\n lastHeartbeatAt: string;\n runtimeInstanceId?: string | null | undefined;\n };\n\n export type RequestInfo = {\n sessionId: string | undefined;\n agentId: string | undefined;\n runtimeInstanceId: string | undefined;\n sourceTaskId: string | undefined;\n contextKey: string | undefined;\n callOrigin: "mcp" | "extension" | "script-sdk";\n };\n\n import type { SwarmSdk } from "swarm-sdk";\n import type { z } from "zod";\n export type Runtime = "api" | "worker";\n export interface ExtensionScriptAsset {\n name: string;\n file: string;\n description: string;\n intent?: string;\n }\n export interface ExtensionScheduleAsset {\n name: string;\n description?: string;\n script: string;\n cronExpression?: string;\n intervalMs?: number;\n timezone?: string;\n args?: Record<string, unknown>;\n }\n export interface ExtensionWorkflowAsset {\n file: string;\n }\n export interface ExtensionSkillAsset {\n dir: string;\n }\n export interface ExtensionManifest {\n $schema?: string;\n name: string;\n description: string;\n version: string;\n runtime: Runtime;\n assets: {\n hooks: string;\n scripts?: readonly ExtensionScriptAsset[];\n schedules?: readonly ExtensionScheduleAsset[];\n workflows?: readonly ExtensionWorkflowAsset[];\n skills?: readonly ExtensionSkillAsset[];\n };\n homepage?: string;\n author?: string;\n }\n export interface HooksModule {\n default: SwarmExtension;\n config?: z.ZodTypeAny;\n }\n export interface ExtensionState {\n get<T = unknown>(key: string): Promise<T | null>;\n set<T>(key: string, value: T): Promise<void>;\n incr(key: string, by?: number): Promise<number>;\n del(key: string): Promise<void>;\n }\n export interface ExtensionLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n }\n type ConfigFor<M extends ExtensionManifest> = M extends {\n config: infer C extends z.ZodTypeAny;\n }\n ? z.infer<C>\n : Record<string, unknown>;\n export interface ApiCtx<Config = Record<string, unknown>> {\n swarm: SwarmSdk;\n state: ExtensionState;\n config: Config;\n log: ExtensionLogger;\n signal: AbortSignal;\n event: {\n name: keyof SwarmEventMap;\n at: string;\n extension: {\n id: string;\n version: number;\n };\n };\n }\n export interface WorkerCtx<Config = Record<string, unknown>>\n extends Omit<ApiCtx<Config>, "swarm"> {\n swarm: SwarmSdk;\n worker: {\n agentId: string;\n taskId: string;\n harness: string;\n };\n }\n export type CtxFor<M extends ExtensionManifest> = M["runtime"] extends "api"\n ? ApiCtx<ConfigFor<M>>\n : WorkerCtx<ConfigFor<M>>;\n export type PreResult<Modify> =\n | void\n | {\n action: "continue";\n }\n | {\n action: "modify";\n data: Modify;\n }\n | {\n action: "block";\n reason: string;\n };\n export type TaskCreateOrigin =\n | "rest"\n | "app"\n | "mcp"\n | "slack"\n | "schedule"\n | "workflow"\n | "webhook"\n | "followUp"\n | `extension:${string}`;\n export interface TaskCreateEvent {\n options: CreateTaskOptions;\n description: string;\n origin: TaskCreateOrigin;\n requestInfo?: RequestInfo;\n }\n export type TaskCreateModify = Partial<\n Pick<\n CreateTaskOptions,\n | "agentId"\n | "creatorAgentId"\n | "source"\n | "taskType"\n | "tags"\n | "priority"\n | "dependsOn"\n | "offeredTo"\n | "vcsProvider"\n | "vcsRepo"\n | "vcsEventType"\n | "vcsNumber"\n | "vcsCommentId"\n | "vcsAuthor"\n | "vcsUrl"\n | "vcsInstallationId"\n | "vcsNodeId"\n | "agentmailInboxId"\n | "agentmailMessageId"\n | "agentmailThreadId"\n | "mentionMessageId"\n | "mentionChannelId"\n | "dir"\n | "model"\n | "modelTier"\n | "effort"\n | "outputSchema"\n | "followUpConfig"\n | "bypassTrackerContextDedup"\n >\n > & {\n description?: string;\n };\n export interface TaskFollowUpEvent {\n completedTask: AgentTask;\n status: "completed" | "failed";\n output?: string;\n failureReason?: string;\n workerAgentId: string;\n leadAgentId: string;\n summary: string;\n }\n export type TaskFollowUpModify = Partial<\n Pick<CreateTaskOptions, "agentId" | "priority" | "followUpConfig">\n > & {\n description?: string;\n };\n export interface SlackRouteEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n botMentioned: boolean;\n threadContext?: {\n channelId: string;\n threadTs: string;\n };\n }\n export interface SlackRouteModify {\n target:\n | {\n kind: "agent";\n agentId: string;\n }\n | {\n kind: "lead";\n }\n | {\n kind: "broadcast";\n };\n }\n /** A Chat SDK message after its thread buffer flushed, before routing. */\n export interface ChatRouteEvent {\n /** Adapter name, such as `telegram` or `slack`. */\n adapter: string;\n channelId: string;\n /** The Chat SDK thread id, prefixed with the adapter name. */\n threadId: string;\n /** The first buffered sender. */\n userId: string;\n userName?: string;\n /** The buffered messages, joined. */\n text: string;\n botMentioned: boolean;\n isDirectMessage: boolean;\n /** Set when earlier swarm tasks exist in this thread. */\n threadContext?: {\n adapter: string;\n threadId: string;\n };\n }\n export interface ChatRouteModify {\n target:\n | {\n kind: "agent";\n agentId: string;\n }\n | {\n kind: "lead";\n }\n | {\n kind: "broadcast";\n }\n | {\n kind: "workflow";\n workflowId: string;\n };\n }\n export type HeartbeatClassification = "no-session" | "stale-session" | "fresh-stalled";\n export type HeartbeatAction = "supersede-resume" | "fail" | "record";\n export interface HeartbeatRemediateEvent {\n task: AgentTask;\n session?: ActiveSession;\n classification: HeartbeatClassification;\n proposedAction: HeartbeatAction;\n reason: string;\n taskAgeMs: number;\n sessionHeartbeatAgeMs?: number;\n }\n export interface HeartbeatRemediateModify {\n proposedAction: HeartbeatAction;\n }\n export interface ToolCallEvent {\n tool: string;\n args: unknown;\n requestInfo: RequestInfo;\n }\n export interface ToolCallModify {\n args: unknown;\n }\n export interface TaskEvent {\n task: AgentTask;\n }\n export interface TaskCompletedEvent extends TaskEvent {\n output: string;\n }\n export interface TaskFailedEvent extends TaskEvent {\n failureReason: string;\n }\n export interface TaskSupersededEvent extends TaskEvent {\n supersededBy: string;\n }\n export interface TaskProgressEvent extends TaskEvent {\n progress: string;\n }\n export interface SlackMessageEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n taskId?: string;\n }\n /** A Chat SDK message the swarm received or sent. */\n export interface ChatMessageEvent {\n adapter: string;\n channelId: string;\n threadId: string;\n /** The sender, for an inbound message. */\n userId?: string;\n text: string;\n direction: "inbound" | "outbound";\n taskId?: string;\n messageId?: string;\n }\n /**\n * A person answered an approval request. Fires for workflow and standalone\n * requests. Timeouts and cancellations do not emit it today.\n */\n export interface ApprovalResolvedEvent {\n requestId: string;\n status: "approved" | "rejected" | "timeout" | "cancelled";\n responses: Record<string, unknown> | null;\n /** Set when a workflow human-in-the-loop step asked. Absent for a standalone request. */\n workflowRunId?: string;\n workflowRunStepId?: string;\n /** The task whose agent asked, for a standalone request. */\n sourceTaskId?: string;\n }\n /** A task was refused because a daily spend budget is used up. */\n export interface TaskBudgetRefusedEvent extends TaskEvent {\n /** The agent the task was refused for. */\n agentId: string;\n /** Which budget refused it. The matching spend and budget fields are set. */\n cause: "agent" | "global" | "user";\n agentSpendUsd?: number;\n agentBudgetUsd?: number;\n globalSpendUsd?: number;\n globalBudgetUsd?: number;\n userSpendUsd?: number;\n userBudgetUsd?: number;\n /** ISO 8601 time the daily budget resets. */\n resetAt: string;\n }\n /** An inbound AgentMail message. */\n export interface EmailReceivedEvent {\n inboxId: string;\n /** The From header as sent. */\n from: string;\n subject: string;\n /** The text body, or the HTML when there is no text, cut to 500 characters with `...` appended. */\n body: string;\n threadId: string;\n messageId: string;\n }\n /** An inbound Kapso (WhatsApp) message. */\n export interface KapsoMessageEvent {\n phoneNumberId: string;\n conversationId?: string;\n messageId: string;\n from?: string;\n /** Kapso message type, such as `text` or `image`. */\n type?: string;\n /** The text, or a placeholder naming the type for a non-text message. */\n text: string;\n }\n export type VcsProvider = "github" | "gitlab" | "azure-devops";\n /**\n * The provider object the event is about, as the provider names it. GitLab\n * calls a pull request a merge request, and a comment a `note`.\n */\n export type VcsEventKind =\n | "pull_request"\n | "merge_request"\n | "issue"\n | "issue_comment"\n | "note"\n | "pull_request_review"\n | "pipeline";\n /**\n * A webhook event from a connected code host. `action` is the provider\'s own\n * value: `opened`, `closed`, `synchronize`, `merge`, `created`, `commented`, or,\n * for a pipeline, its status such as `success` or `failed`. Fields a kind does\n * not carry are absent.\n */\n export interface VcsEvent {\n provider: VcsProvider;\n kind: VcsEventKind;\n action: string;\n /** `owner/name` on GitHub and GitLab, the repository URL on Azure DevOps. */\n repo: string;\n /** The PR, merge request, or issue number. Absent for a pipeline outside a merge request. */\n number?: number;\n title?: string;\n body?: string | null;\n /** Login of the user behind the event. Set for pull requests, merge requests, and Azure DevOps comments. */\n author?: string;\n url?: string;\n merged?: boolean;\n changedFiles?: number;\n /** The review verdict, on a pull request review. */\n reviewState?: string;\n }\n export interface ToolCallCompletedEvent extends ToolCallEvent {\n result: unknown;\n durationMs: number;\n }\n export interface SwarmEventMap {\n "pre.task.create": {\n event: TaskCreateEvent;\n modify: TaskCreateModify;\n result: PreResult<TaskCreateModify>;\n };\n "pre.task.followUp": {\n event: TaskFollowUpEvent;\n modify: TaskFollowUpModify;\n result: PreResult<TaskFollowUpModify>;\n };\n "pre.slack.route": {\n event: SlackRouteEvent;\n modify: SlackRouteModify;\n result: PreResult<SlackRouteModify>;\n };\n "pre.chat.route": {\n event: ChatRouteEvent;\n modify: ChatRouteModify;\n result: PreResult<ChatRouteModify>;\n };\n "pre.heartbeat.remediate": {\n event: HeartbeatRemediateEvent;\n modify: HeartbeatRemediateModify;\n result: PreResult<HeartbeatRemediateModify>;\n };\n "pre.tool.call": {\n event: ToolCallEvent;\n modify: ToolCallModify;\n result: PreResult<ToolCallModify>;\n };\n "post.task.created": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.completed": {\n event: TaskCompletedEvent;\n modify: never;\n result: void;\n };\n "post.task.failed": {\n event: TaskFailedEvent;\n modify: never;\n result: void;\n };\n "post.task.cancelled": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.superseded": {\n event: TaskSupersededEvent;\n modify: never;\n result: void;\n };\n "post.task.progress": {\n event: TaskProgressEvent;\n modify: never;\n result: void;\n };\n "post.slack.message": {\n event: SlackMessageEvent;\n modify: never;\n result: void;\n };\n "post.chat.message": {\n event: ChatMessageEvent;\n modify: never;\n result: void;\n };\n "post.approval.resolved": {\n event: ApprovalResolvedEvent;\n modify: never;\n result: void;\n };\n "post.task.budgetRefused": {\n event: TaskBudgetRefusedEvent;\n modify: never;\n result: void;\n };\n "post.email.received": {\n event: EmailReceivedEvent;\n modify: never;\n result: void;\n };\n "post.kapso.message": {\n event: KapsoMessageEvent;\n modify: never;\n result: void;\n };\n "post.vcs.event": {\n event: VcsEvent;\n modify: never;\n result: void;\n };\n "post.tool.call": {\n event: ToolCallCompletedEvent;\n modify: never;\n result: void;\n };\n }\n export interface ExtensionApi<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > {\n on<E extends keyof SwarmEventMap>(\n event: E,\n handler: (\n event: SwarmEventMap[E]["event"],\n ctx: CtxFor<M>,\n ) => Promise<SwarmEventMap[E]["result"]> | SwarmEventMap[E]["result"],\n opts?: {\n priority?: number;\n },\n ): void;\n }\n export type SwarmExtension<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > = (api: ExtensionApi<M>) => void;\n export { block, modify } from "./contract-runtime";\n}\n';
|
|
4
|
+
'// biome-ignore-all lint/suspicious/noConfusingVoidType: Extension handlers may explicitly return no result.\ndeclare module "swarm-extension" {\n export type CreateTaskOptions = {\n key?: string | undefined;\n agentId?: string | null | undefined;\n creatorAgentId?: string | undefined;\n source?:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "azure-devops"\n | "system"\n | "linear"\n | "jira"\n | "comb"\n | "chat"\n | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n taskType?: string | undefined;\n tags?: string[] | undefined;\n priority?: number | undefined;\n dependsOn?: string[] | undefined;\n offeredTo?: string | undefined;\n status?: "draft" | "backlog" | "unassigned" | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n overrideSlackContext?: boolean | undefined;\n vcsProvider?: "github" | "gitlab" | "azure-devops" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n profile?: string | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | undefined;\n workflowRunStepId?: string | undefined;\n sourceTaskId?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n inheritParentOutputSchema?: boolean | undefined;\n inheritParentRequester?: boolean | undefined;\n inheritParentRoutingAffinity?: boolean | undefined;\n inheritParentFollowUpConfig?: boolean | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n requestedByUserId?: string | undefined;\n contextKey?: string | undefined;\n bypassTrackerContextDedup?: boolean | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | "amp"\n | "cursor"\n | "grok"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n };\n\n export type AgentTask = {\n id: string;\n key: string;\n agentId: string | null;\n task: string;\n status:\n | "draft"\n | "backlog"\n | "unassigned"\n | "offered"\n | "reviewing"\n | "pending"\n | "in_progress"\n | "paused"\n | "completed"\n | "failed"\n | "cancelled"\n | "superseded";\n source:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "azure-devops"\n | "system"\n | "linear"\n | "jira"\n | "comb"\n | "chat";\n tags: string[];\n priority: number;\n dependsOn: string[];\n createdAt: string;\n lastUpdatedAt: string;\n slackReplySent: boolean;\n wasPaused: boolean;\n creatorAgentId?: string | undefined;\n title?: string | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n poolWakeAt?: string | undefined;\n taskType?: string | undefined;\n offeredTo?: string | undefined;\n offeredAt?: string | undefined;\n acceptedAt?: string | undefined;\n rejectionReason?: string | undefined;\n finishedAt?: string | undefined;\n notifiedAt?: string | undefined;\n failureReason?: string | undefined;\n output?: string | undefined;\n progress?: string | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n slackProgressMessageTs?: string | undefined;\n slackTreeRootMessageTs?: string | undefined;\n vcsProvider?: "github" | "gitlab" | "azure-devops" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n claudeSessionId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n resolvedModel?: string | undefined;\n modelSource?: string | undefined;\n modelAlias?: string | undefined;\n resolvedEffort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n profile?: string | undefined;\n resolvedProfile?: string | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | null | undefined;\n workflowRunStepId?: string | null | undefined;\n contextKey?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n deferredAt?: string | undefined;\n compactionCount?: number | undefined;\n peakContextPercent?: number | undefined;\n peakContextTokens?: number | undefined;\n contextWindowSize?: number | undefined;\n credentialKeySuffix?: string | undefined;\n credentialKeyType?: string | undefined;\n requestedByUserId?: string | undefined;\n swarmVersion?: string | undefined;\n provider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | "amp"\n | "cursor"\n | "grok"\n | undefined;\n providerMeta?: Record<string, unknown> | undefined;\n harnessVariant?: string | undefined;\n harnessVariantMeta?: Record<string, unknown> | undefined;\n totalCostUsd?: number | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | "amp"\n | "cursor"\n | "grok"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n routingAffinityInvalid?: boolean | undefined;\n };\n\n export type ActiveSession = {\n id: string;\n agentId: string;\n taskId: string | null;\n triggerType: string;\n inboxMessageId: string | null;\n taskDescription: string | null;\n runnerSessionId: string | null;\n providerSessionId: string | null;\n startedAt: string;\n lastHeartbeatAt: string;\n runtimeInstanceId?: string | null | undefined;\n };\n\n export type RequestInfo = {\n sessionId: string | undefined;\n agentId: string | undefined;\n runtimeInstanceId: string | undefined;\n sourceTaskId: string | undefined;\n contextKey: string | undefined;\n callOrigin: "mcp" | "extension" | "script-sdk";\n };\n\n import type { SwarmSdk } from "swarm-sdk";\n import type { z } from "zod";\n export type Runtime = "api" | "worker";\n export interface ExtensionScriptAsset {\n name: string;\n file: string;\n description: string;\n intent?: string;\n }\n export interface ExtensionScheduleAsset {\n name: string;\n description?: string;\n script: string;\n cronExpression?: string;\n intervalMs?: number;\n timezone?: string;\n args?: Record<string, unknown>;\n }\n export interface ExtensionWorkflowAsset {\n file: string;\n }\n export interface ExtensionSkillAsset {\n dir: string;\n }\n export interface ExtensionManifest {\n $schema?: string;\n name: string;\n description: string;\n version: string;\n runtime: Runtime;\n assets: {\n hooks: string;\n scripts?: readonly ExtensionScriptAsset[];\n schedules?: readonly ExtensionScheduleAsset[];\n workflows?: readonly ExtensionWorkflowAsset[];\n skills?: readonly ExtensionSkillAsset[];\n };\n homepage?: string;\n author?: string;\n }\n export interface HooksModule {\n default: SwarmExtension;\n config?: z.ZodTypeAny;\n }\n export interface ExtensionState {\n get<T = unknown>(key: string): Promise<T | null>;\n set<T>(key: string, value: T): Promise<void>;\n incr(key: string, by?: number): Promise<number>;\n del(key: string): Promise<void>;\n }\n export interface ExtensionLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n }\n type ConfigFor<M extends ExtensionManifest> = M extends {\n config: infer C extends z.ZodTypeAny;\n }\n ? z.infer<C>\n : Record<string, unknown>;\n export interface ApiCtx<Config = Record<string, unknown>> {\n swarm: SwarmSdk;\n state: ExtensionState;\n config: Config;\n log: ExtensionLogger;\n signal: AbortSignal;\n event: {\n name: keyof SwarmEventMap;\n at: string;\n extension: {\n id: string;\n version: number;\n };\n };\n }\n export interface WorkerCtx<Config = Record<string, unknown>>\n extends Omit<ApiCtx<Config>, "swarm"> {\n swarm: SwarmSdk;\n worker: {\n agentId: string;\n taskId: string;\n harness: string;\n };\n }\n export type CtxFor<M extends ExtensionManifest> = M["runtime"] extends "api"\n ? ApiCtx<ConfigFor<M>>\n : WorkerCtx<ConfigFor<M>>;\n export type PreResult<Modify> =\n | void\n | {\n action: "continue";\n }\n | {\n action: "modify";\n data: Modify;\n }\n | {\n action: "block";\n reason: string;\n };\n export type TaskCreateOrigin =\n | "rest"\n | "app"\n | "mcp"\n | "slack"\n | "schedule"\n | "workflow"\n | "webhook"\n | "followUp"\n | `extension:${string}`;\n export interface TaskCreateEvent {\n options: CreateTaskOptions;\n description: string;\n origin: TaskCreateOrigin;\n requestInfo?: RequestInfo;\n }\n export type TaskCreateModify = Partial<\n Pick<\n CreateTaskOptions,\n | "agentId"\n | "creatorAgentId"\n | "source"\n | "taskType"\n | "tags"\n | "priority"\n | "dependsOn"\n | "offeredTo"\n | "vcsProvider"\n | "vcsRepo"\n | "vcsEventType"\n | "vcsNumber"\n | "vcsCommentId"\n | "vcsAuthor"\n | "vcsUrl"\n | "vcsInstallationId"\n | "vcsNodeId"\n | "agentmailInboxId"\n | "agentmailMessageId"\n | "agentmailThreadId"\n | "mentionMessageId"\n | "mentionChannelId"\n | "dir"\n | "model"\n | "modelTier"\n | "effort"\n | "outputSchema"\n | "followUpConfig"\n | "bypassTrackerContextDedup"\n >\n > & {\n description?: string;\n };\n export interface TaskFollowUpEvent {\n completedTask: AgentTask;\n status: "completed" | "failed";\n output?: string;\n failureReason?: string;\n workerAgentId: string;\n leadAgentId: string;\n summary: string;\n }\n export type TaskFollowUpModify = Partial<\n Pick<CreateTaskOptions, "agentId" | "priority" | "followUpConfig">\n > & {\n description?: string;\n };\n export interface SlackRouteEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n botMentioned: boolean;\n threadContext?: {\n channelId: string;\n threadTs: string;\n };\n }\n export interface SlackRouteModify {\n target:\n | {\n kind: "agent";\n agentId: string;\n }\n | {\n kind: "lead";\n }\n | {\n kind: "broadcast";\n };\n }\n /** A Chat SDK message after its thread buffer flushed, before routing. */\n export interface ChatRouteEvent {\n /** Adapter name, such as `telegram` or `slack`. */\n adapter: string;\n channelId: string;\n /** The Chat SDK thread id, prefixed with the adapter name. */\n threadId: string;\n /** The first buffered sender. */\n userId: string;\n userName?: string;\n /** The buffered messages, joined. */\n text: string;\n /** True for an @mention and for a reply to one of the bot\'s messages. */\n botMentioned: boolean;\n isDirectMessage: boolean;\n /** Platform chat type, such as Telegram `private`, `group`, `supergroup` or `channel`. */\n chatType?: string;\n /** Set for a Telegram supergroup with topics. */\n isForum?: boolean;\n /** A buffered message replies to one of the bot\'s own messages. */\n replyToBot: boolean;\n /** Set when earlier swarm tasks exist in this thread. */\n threadContext?: {\n adapter: string;\n threadId: string;\n };\n }\n export interface ChatRouteModify {\n target:\n | {\n kind: "agent";\n agentId: string;\n }\n | {\n kind: "lead";\n }\n | {\n kind: "broadcast";\n }\n | {\n kind: "workflow";\n workflowId: string;\n };\n }\n export type HeartbeatClassification = "no-session" | "stale-session" | "fresh-stalled";\n export type HeartbeatAction = "supersede-resume" | "fail" | "record";\n export interface HeartbeatRemediateEvent {\n task: AgentTask;\n session?: ActiveSession;\n classification: HeartbeatClassification;\n proposedAction: HeartbeatAction;\n reason: string;\n taskAgeMs: number;\n sessionHeartbeatAgeMs?: number;\n }\n export interface HeartbeatRemediateModify {\n proposedAction: HeartbeatAction;\n }\n export interface ToolCallEvent {\n tool: string;\n args: unknown;\n requestInfo: RequestInfo;\n }\n export interface ToolCallModify {\n args: unknown;\n }\n export interface TaskEvent {\n task: AgentTask;\n }\n export interface TaskCompletedEvent extends TaskEvent {\n output: string;\n }\n export interface TaskFailedEvent extends TaskEvent {\n failureReason: string;\n }\n export interface TaskSupersededEvent extends TaskEvent {\n supersededBy: string;\n }\n export interface TaskProgressEvent extends TaskEvent {\n progress: string;\n }\n export interface SlackMessageEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n taskId?: string;\n }\n /** A Chat SDK message the swarm received or sent. */\n export interface ChatMessageEvent {\n adapter: string;\n channelId: string;\n threadId: string;\n /** The sender, for an inbound message. */\n userId?: string;\n text: string;\n direction: "inbound" | "outbound";\n taskId?: string;\n messageId?: string;\n }\n /**\n * A person answered an approval request. Fires for workflow and standalone\n * requests. Timeouts and cancellations do not emit it today.\n */\n export interface ApprovalResolvedEvent {\n requestId: string;\n status: "approved" | "rejected" | "timeout" | "cancelled";\n responses: Record<string, unknown> | null;\n /** Set when a workflow human-in-the-loop step asked. Absent for a standalone request. */\n workflowRunId?: string;\n workflowRunStepId?: string;\n /** The task whose agent asked, for a standalone request. */\n sourceTaskId?: string;\n }\n /** A task was refused because a daily spend budget is used up. */\n export interface TaskBudgetRefusedEvent extends TaskEvent {\n /** The agent the task was refused for. */\n agentId: string;\n /** Which budget refused it. The matching spend and budget fields are set. */\n cause: "agent" | "global" | "user";\n agentSpendUsd?: number;\n agentBudgetUsd?: number;\n globalSpendUsd?: number;\n globalBudgetUsd?: number;\n userSpendUsd?: number;\n userBudgetUsd?: number;\n /** ISO 8601 time the daily budget resets. */\n resetAt: string;\n }\n /** An inbound AgentMail message. */\n export interface EmailReceivedEvent {\n inboxId: string;\n /** The From header as sent. */\n from: string;\n subject: string;\n /** The text body, or the HTML when there is no text, cut to 500 characters with `...` appended. */\n body: string;\n threadId: string;\n messageId: string;\n }\n /** An inbound Kapso (WhatsApp) message. */\n export interface KapsoMessageEvent {\n phoneNumberId: string;\n conversationId?: string;\n messageId: string;\n from?: string;\n /** Kapso message type, such as `text` or `image`. */\n type?: string;\n /** The text, or a placeholder naming the type for a non-text message. */\n text: string;\n }\n export type VcsProvider = "github" | "gitlab" | "azure-devops";\n /**\n * The provider object the event is about, as the provider names it. GitLab\n * calls a pull request a merge request, and a comment a `note`.\n */\n export type VcsEventKind =\n | "pull_request"\n | "merge_request"\n | "issue"\n | "issue_comment"\n | "note"\n | "pull_request_review"\n | "pipeline";\n /**\n * A webhook event from a connected code host. `action` is the provider\'s own\n * value: `opened`, `closed`, `synchronize`, `merge`, `created`, `commented`, or,\n * for a pipeline, its status such as `success` or `failed`. Fields a kind does\n * not carry are absent.\n */\n export interface VcsEvent {\n provider: VcsProvider;\n kind: VcsEventKind;\n action: string;\n /** `owner/name` on GitHub and GitLab, the repository URL on Azure DevOps. */\n repo: string;\n /** The PR, merge request, or issue number. Absent for a pipeline outside a merge request. */\n number?: number;\n title?: string;\n body?: string | null;\n /** Login of the user behind the event. Set for pull requests, merge requests, and Azure DevOps comments. */\n author?: string;\n url?: string;\n merged?: boolean;\n changedFiles?: number;\n /** The review verdict, on a pull request review. */\n reviewState?: string;\n }\n export interface ToolCallCompletedEvent extends ToolCallEvent {\n result: unknown;\n durationMs: number;\n }\n export interface SwarmEventMap {\n "pre.task.create": {\n event: TaskCreateEvent;\n modify: TaskCreateModify;\n result: PreResult<TaskCreateModify>;\n };\n "pre.task.followUp": {\n event: TaskFollowUpEvent;\n modify: TaskFollowUpModify;\n result: PreResult<TaskFollowUpModify>;\n };\n "pre.slack.route": {\n event: SlackRouteEvent;\n modify: SlackRouteModify;\n result: PreResult<SlackRouteModify>;\n };\n "pre.chat.route": {\n event: ChatRouteEvent;\n modify: ChatRouteModify;\n result: PreResult<ChatRouteModify>;\n };\n "pre.heartbeat.remediate": {\n event: HeartbeatRemediateEvent;\n modify: HeartbeatRemediateModify;\n result: PreResult<HeartbeatRemediateModify>;\n };\n "pre.tool.call": {\n event: ToolCallEvent;\n modify: ToolCallModify;\n result: PreResult<ToolCallModify>;\n };\n "post.task.created": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.completed": {\n event: TaskCompletedEvent;\n modify: never;\n result: void;\n };\n "post.task.failed": {\n event: TaskFailedEvent;\n modify: never;\n result: void;\n };\n "post.task.cancelled": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.superseded": {\n event: TaskSupersededEvent;\n modify: never;\n result: void;\n };\n "post.task.progress": {\n event: TaskProgressEvent;\n modify: never;\n result: void;\n };\n "post.slack.message": {\n event: SlackMessageEvent;\n modify: never;\n result: void;\n };\n "post.chat.message": {\n event: ChatMessageEvent;\n modify: never;\n result: void;\n };\n "post.approval.resolved": {\n event: ApprovalResolvedEvent;\n modify: never;\n result: void;\n };\n "post.task.budgetRefused": {\n event: TaskBudgetRefusedEvent;\n modify: never;\n result: void;\n };\n "post.email.received": {\n event: EmailReceivedEvent;\n modify: never;\n result: void;\n };\n "post.kapso.message": {\n event: KapsoMessageEvent;\n modify: never;\n result: void;\n };\n "post.vcs.event": {\n event: VcsEvent;\n modify: never;\n result: void;\n };\n "post.tool.call": {\n event: ToolCallCompletedEvent;\n modify: never;\n result: void;\n };\n }\n export interface ExtensionApi<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > {\n on<E extends keyof SwarmEventMap>(\n event: E,\n handler: (\n event: SwarmEventMap[E]["event"],\n ctx: CtxFor<M>,\n ) => Promise<SwarmEventMap[E]["result"]> | SwarmEventMap[E]["result"],\n opts?: {\n priority?: number;\n },\n ): void;\n }\n export type SwarmExtension<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > = (api: ExtensionApi<M>) => void;\n export { block, modify } from "./contract-runtime";\n}\n';
|
|
@@ -193,8 +193,15 @@ export interface ChatRouteEvent {
|
|
|
193
193
|
userName?: string;
|
|
194
194
|
/** The buffered messages, joined. */
|
|
195
195
|
text: string;
|
|
196
|
+
/** True for an @mention and for a reply to one of the bot's messages. */
|
|
196
197
|
botMentioned: boolean;
|
|
197
198
|
isDirectMessage: boolean;
|
|
199
|
+
/** Platform chat type, such as Telegram `private`, `group`, `supergroup` or `channel`. */
|
|
200
|
+
chatType?: string;
|
|
201
|
+
/** Set for a Telegram supergroup with topics. */
|
|
202
|
+
isForum?: boolean;
|
|
203
|
+
/** A buffered message replies to one of the bot's own messages. */
|
|
204
|
+
replyToBot: boolean;
|
|
198
205
|
/** Set when earlier swarm tasks exist in this thread. */
|
|
199
206
|
threadContext?: { adapter: string; threadId: string };
|
|
200
207
|
}
|
package/src/fs/provider.ts
CHANGED
|
@@ -117,12 +117,24 @@ export function normalizeStoredKey(key: string): string {
|
|
|
117
117
|
if (!trimmed || trimmed.includes("\0")) {
|
|
118
118
|
throw new FilesError("Provider", "Stored file key must be a non-empty path");
|
|
119
119
|
}
|
|
120
|
-
if (trimmed.split("/").some(
|
|
120
|
+
if (trimmed.split("/").some(isParentSegment)) {
|
|
121
121
|
throw new FilesError("Provider", "Stored file key must not contain parent path segments");
|
|
122
122
|
}
|
|
123
123
|
return trimmed;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// `..` itself, or a segment a URL decoder turns into `..` or a separator
|
|
127
|
+
// (`%2e%2e`, `..%2f..`); normalizeName never produces either form.
|
|
128
|
+
function isParentSegment(part: string): boolean {
|
|
129
|
+
if (part === "..") return true;
|
|
130
|
+
try {
|
|
131
|
+
const decoded = decodeURIComponent(part);
|
|
132
|
+
return decoded === ".." || decoded.includes("/");
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
126
138
|
export function normalizeName(name: string): string {
|
|
127
139
|
const trimmed = name.trim().replace(/^\/+/, "");
|
|
128
140
|
if (!trimmed || trimmed === "." || trimmed.includes("\0")) {
|
|
@@ -248,14 +248,15 @@ function isBootTriageAlways(): boolean {
|
|
|
248
248
|
const HEARTBEAT_CHECKLIST_GATE_TIMEOUT_MS = 60_000;
|
|
249
249
|
let checklistGateTimeoutMs = HEARTBEAT_CHECKLIST_GATE_TIMEOUT_MS;
|
|
250
250
|
|
|
251
|
-
/** Seeded gate used when `HEARTBEAT_CHECKLIST_GATE_SCRIPT` is unset and
|
|
251
|
+
/** Seeded gate used when `HEARTBEAT_CHECKLIST_GATE_SCRIPT` is unset and a gate provider has a key. */
|
|
252
252
|
const DEFAULT_CHECKLIST_GATE_SCRIPT = "heartbeat-jev-gate";
|
|
253
253
|
|
|
254
254
|
/**
|
|
255
255
|
* Name of a global-scope script that decides whether a checklist tick needs
|
|
256
256
|
* the Lead. An empty value or `off` turns the gate off. Unset falls back to
|
|
257
|
-
* the seeded
|
|
258
|
-
* one the script would open a setup approval request on the
|
|
257
|
+
* the seeded gate, but only when one of its providers has a usable global
|
|
258
|
+
* key: without one the script would open a setup approval request on the
|
|
259
|
+
* first tick.
|
|
259
260
|
*/
|
|
260
261
|
async function heartbeatChecklistGateScript(): Promise<string | null> {
|
|
261
262
|
const raw = process.env.HEARTBEAT_CHECKLIST_GATE_SCRIPT;
|
|
@@ -268,22 +269,41 @@ async function heartbeatChecklistGateScript(): Promise<string | null> {
|
|
|
268
269
|
|
|
269
270
|
/**
|
|
270
271
|
* Mirrors the provider pick in `heartbeat-jev-gate`: a global TypeSafe or
|
|
271
|
-
* OpenRouter key,
|
|
272
|
-
*
|
|
273
|
-
*
|
|
272
|
+
* OpenRouter key, or a Cloudflare token plus account id, honoring
|
|
273
|
+
* `HEARTBEAT_JEV_GATE_PROVIDER`. An OpenRouter key behind an
|
|
274
|
+
* `OPENROUTER_BASE_URL` gateway does not count, because the decisions call
|
|
275
|
+
* refuses it and every tick would be an `error` verdict.
|
|
274
276
|
*/
|
|
275
277
|
async function hasJevGateKey(): Promise<boolean> {
|
|
276
278
|
const pref = process.env.HEARTBEAT_JEV_GATE_PROVIDER?.trim().toLowerCase() || "auto";
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
const auto = pref !== "typesafe" && pref !== "openrouter" && pref !== "cloudflare";
|
|
280
|
+
// Each entry is one provider: every key group must have a set value.
|
|
281
|
+
const providers: string[][][] = [];
|
|
282
|
+
if (auto || pref === "typesafe") providers.push([["TYPESAFE_API_KEY"]]);
|
|
283
|
+
if ((auto || pref === "openrouter") && getOpenRouterBaseUrl() === DEFAULT_OPENROUTER_BASE_URL) {
|
|
284
|
+
providers.push([["OPENROUTER_API_KEY"]]);
|
|
281
285
|
}
|
|
282
|
-
|
|
286
|
+
if (auto || pref === "cloudflare") {
|
|
287
|
+
providers.push([["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_KEY"], ["CLOUDFLARE_ACCOUNT_ID"]]);
|
|
288
|
+
}
|
|
289
|
+
const anySet = async (keys: string[]) => {
|
|
283
290
|
for (const key of keys) {
|
|
284
291
|
const rows = await getSwarmConfigs({ scope: "global", key });
|
|
285
292
|
if (rows.some((row) => row.value.trim() !== "")) return true;
|
|
286
293
|
}
|
|
294
|
+
return false;
|
|
295
|
+
};
|
|
296
|
+
try {
|
|
297
|
+
for (const groups of providers) {
|
|
298
|
+
let usable = true;
|
|
299
|
+
for (const group of groups) {
|
|
300
|
+
if (!(await anySet(group))) {
|
|
301
|
+
usable = false;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (usable) return true;
|
|
306
|
+
}
|
|
287
307
|
} catch {
|
|
288
308
|
// An unreadable secret row leaves the gate off; the checklist still runs.
|
|
289
309
|
}
|
package/src/http/fs.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { deleteTaskAttachment, getTaskAttachments, getTaskById } from "../be/db";
|
|
4
|
+
import { appendDraftTaskText } from "../be/db/tasks/draft-text";
|
|
4
5
|
import {
|
|
5
6
|
ensureAgentFsCredentialsForAgent,
|
|
6
7
|
inviteEmailToSharedOrg,
|
|
7
8
|
} from "../be/seed/agent-fs-provision";
|
|
8
|
-
import {
|
|
9
|
-
|
|
9
|
+
import {
|
|
10
|
+
backingProviderId,
|
|
11
|
+
MAX_TASK_ATTACHMENT_BYTES,
|
|
12
|
+
recordTaskAttachmentUpload,
|
|
13
|
+
scopeFromAttachment,
|
|
14
|
+
} from "../be/task-attachment-store";
|
|
15
|
+
import { type FileObject, FilesError, normalizeFilesError } from "../fs/provider";
|
|
10
16
|
import { getFileStorageProvider } from "../fs/registry";
|
|
11
17
|
import { can, type RbacResource } from "../rbac";
|
|
12
18
|
import { type TaskAttachment, TaskAttachmentSchema } from "../types";
|
|
13
19
|
import { attachmentContentDisposition } from "../utils/content-disposition";
|
|
14
20
|
import { getCurrentRequestAuth } from "../utils/request-auth-context";
|
|
15
21
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
22
|
+
import { transcribeAudioNotes } from "../utils/transcription";
|
|
16
23
|
import { requestPrincipal } from "./request-principal";
|
|
17
24
|
import { route } from "./route-def";
|
|
18
25
|
import { canReadSession, SESSION_READ_DENIED } from "./session-access";
|
|
@@ -147,7 +154,7 @@ const uploadTaskFileRoute = route({
|
|
|
147
154
|
session: { taskParam: "taskId" },
|
|
148
155
|
summary: "Upload a binary task file attachment",
|
|
149
156
|
description:
|
|
150
|
-
"Accepts a raw binary request body. Pass the display/path name as the `name` query parameter.",
|
|
157
|
+
"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.",
|
|
151
158
|
tags: ["FS"],
|
|
152
159
|
params: taskParams,
|
|
153
160
|
query: uploadQuery,
|
|
@@ -331,7 +338,14 @@ export async function handleFs(
|
|
|
331
338
|
const task = await getTaskById(parsed.params.taskId);
|
|
332
339
|
if (!task) return notFound(res, "Task not found");
|
|
333
340
|
if (!(await canMutateTask(task, myAgentId, req))) return forbidden(res);
|
|
334
|
-
return sendUpload(
|
|
341
|
+
return sendUpload(
|
|
342
|
+
req,
|
|
343
|
+
res,
|
|
344
|
+
parsed.params.taskId,
|
|
345
|
+
parsed.query,
|
|
346
|
+
myAgentId ?? null,
|
|
347
|
+
task.status === "draft",
|
|
348
|
+
);
|
|
335
349
|
}
|
|
336
350
|
|
|
337
351
|
if (listTaskFilesRoute.match(req.method, pathSegments)) {
|
|
@@ -366,6 +380,7 @@ async function sendUpload(
|
|
|
366
380
|
taskId: string,
|
|
367
381
|
query: z.infer<typeof uploadQuery>,
|
|
368
382
|
agentId: string | null,
|
|
383
|
+
isDraft: boolean,
|
|
369
384
|
): Promise<boolean> {
|
|
370
385
|
const body = await readRawBody(req, MAX_TASK_ATTACHMENT_BYTES);
|
|
371
386
|
if (body === BODY_TOO_LARGE) {
|
|
@@ -417,10 +432,45 @@ async function sendUpload(
|
|
|
417
432
|
isPrimary: query.isPrimary === "true",
|
|
418
433
|
createdBy: auth?.kind === "user" ? auth.userId : undefined,
|
|
419
434
|
});
|
|
435
|
+
const mimeType = contentType.split(";")[0]!.trim().toLowerCase();
|
|
436
|
+
if (isDraft && mimeType.startsWith("audio/")) {
|
|
437
|
+
await appendAudioTranscript(taskId, query.name, mimeType, body);
|
|
438
|
+
}
|
|
420
439
|
uploadTaskFileRoute.respond(res, 201, attachment);
|
|
421
440
|
return true;
|
|
422
441
|
}
|
|
423
442
|
|
|
443
|
+
/**
|
|
444
|
+
* A voice note uploaded to a draft (the composer's pre-dispatch window) gets
|
|
445
|
+
* its transcript in the task text, as one sent over Slack or a chat app does.
|
|
446
|
+
* Runs before the upload responds, so the composer promotes the draft only
|
|
447
|
+
* after the text is final. Never throws: the attachment has already landed.
|
|
448
|
+
*/
|
|
449
|
+
async function appendAudioTranscript(
|
|
450
|
+
taskId: string,
|
|
451
|
+
name: string,
|
|
452
|
+
mimeType: string,
|
|
453
|
+
body: Buffer,
|
|
454
|
+
): Promise<void> {
|
|
455
|
+
try {
|
|
456
|
+
const notes = await transcribeAudioNotes(
|
|
457
|
+
[{ key: name, name, data: new Blob([body], { type: mimeType }), mimeType }],
|
|
458
|
+
"[fs]",
|
|
459
|
+
);
|
|
460
|
+
const note = notes.get(name);
|
|
461
|
+
if (!note) return;
|
|
462
|
+
if (!(await appendDraftTaskText(taskId, `[File: ${name} (${mimeType})]\n${note}`))) {
|
|
463
|
+
console.warn(`[fs] task ${taskId} left draft before the transcript of ${name} was added`);
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.warn(
|
|
467
|
+
scrubSecrets(
|
|
468
|
+
`[fs] could not add the transcript of ${name} to task ${taskId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
469
|
+
),
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
424
474
|
async function sendDownload(res: ServerResponse, attachment: TaskAttachment): Promise<boolean> {
|
|
425
475
|
const provider = getFileStorageProvider();
|
|
426
476
|
if (backingProviderId(attachment) !== provider.id) {
|
|
@@ -491,36 +541,6 @@ async function sendDelete(res: ServerResponse, attachment: TaskAttachment): Prom
|
|
|
491
541
|
return true;
|
|
492
542
|
}
|
|
493
543
|
|
|
494
|
-
// Resolve against the row's ACTUAL stored key + its own org/drive, rather than
|
|
495
|
-
// reconstructing `tasks/<taskId>/<name>`. Attachments written to agent-fs via the
|
|
496
|
-
// CLI live at arbitrary paths (`misc/…`, `smoke/…`); reconstruction mis-resolves
|
|
497
|
-
// them, which previously caused downloads to 404 and deletes to orphan the real
|
|
498
|
-
// file. New uploads have no stored key yet at scope-build time and keep the
|
|
499
|
-
// forward-looking layout.
|
|
500
|
-
function scopeFromAttachment(attachment: TaskAttachment): FileScope {
|
|
501
|
-
const storedKey = attachment.providerKey ?? attachment.path ?? "";
|
|
502
|
-
return {
|
|
503
|
-
taskId: attachment.taskId,
|
|
504
|
-
name: attachment.name,
|
|
505
|
-
key: storedKey.trim() ? storedKey : undefined,
|
|
506
|
-
orgId: attachment.orgId,
|
|
507
|
-
driveId: attachment.driveId,
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Which file-storage provider (if any) actually holds this attachment's bytes.
|
|
512
|
-
// Only agent-fs objects and local-fs uploads are provider-backed; `shared-fs`
|
|
513
|
-
// worker-volume pointers, `url`, and `page` attachments have no provider object,
|
|
514
|
-
// so they must never be fetched or deleted through a provider (doing so would
|
|
515
|
-
// mis-resolve and, on delete, orphan the real target). Note local-fs uploads are
|
|
516
|
-
// recorded with kind "shared-fs" but an explicit local-fs providerId, so key off
|
|
517
|
-
// both signals rather than kind alone.
|
|
518
|
-
function backingProviderId(attachment: TaskAttachment): string | null {
|
|
519
|
-
if (attachment.kind === "agent-fs") return attachment.providerId ?? "agent-fs";
|
|
520
|
-
if (attachment.providerId === "local-fs") return "local-fs";
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
544
|
async function findAttachment(
|
|
525
545
|
req: IncomingMessage,
|
|
526
546
|
myAgentId: string | undefined,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { MCP_MAX_REQUEST_BODY_BYTES } from "@/mcp-limits";
|
|
3
|
+
import { parseBody, RequestBodyTooLargeError } from "./utils";
|
|
4
|
+
|
|
5
|
+
/** Returned by `readMcpBody` after it has answered the request with 413. */
|
|
6
|
+
export const MCP_BODY_TOO_LARGE = Symbol("mcp-body-too-large");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read and parse a JSON-RPC request body for an MCP HTTP handler, capped at
|
|
10
|
+
* `MCP_MAX_REQUEST_BODY_BYTES`. A declared Content-Length over the cap is
|
|
11
|
+
* refused before reading; otherwise the read stops once the cap is crossed.
|
|
12
|
+
* Either way the caller gets `MCP_BODY_TOO_LARGE` after a 413 with a JSON-RPC
|
|
13
|
+
* error body (the SDK transport's shape), and the body never reaches
|
|
14
|
+
* `JSON.parse`.
|
|
15
|
+
*/
|
|
16
|
+
export async function readMcpBody(
|
|
17
|
+
req: IncomingMessage,
|
|
18
|
+
res: ServerResponse,
|
|
19
|
+
): Promise<unknown | typeof MCP_BODY_TOO_LARGE> {
|
|
20
|
+
try {
|
|
21
|
+
return await parseBody(req, MCP_MAX_REQUEST_BODY_BYTES);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (!(error instanceof RequestBodyTooLargeError)) throw error;
|
|
24
|
+
res.writeHead(413, { "Content-Type": "application/json", Connection: "close" });
|
|
25
|
+
res.end(
|
|
26
|
+
JSON.stringify({
|
|
27
|
+
jsonrpc: "2.0",
|
|
28
|
+
error: {
|
|
29
|
+
code: -32000,
|
|
30
|
+
message: `Payload Too Large: Request body must not exceed ${MCP_MAX_REQUEST_BODY_BYTES} bytes`,
|
|
31
|
+
},
|
|
32
|
+
id: null,
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
return MCP_BODY_TOO_LARGE;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/http/mcp-user.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
markMcpTransportActivity,
|
|
13
13
|
respondNoMcpSession,
|
|
14
14
|
} from "./mcp";
|
|
15
|
+
import { MCP_BODY_TOO_LARGE, readMcpBody } from "./mcp-body";
|
|
15
16
|
|
|
16
17
|
function unauthorized(res: ServerResponse): true {
|
|
17
18
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -59,11 +60,8 @@ export async function handleMcpUser(
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
if (req.method === "POST") {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
chunks.push(chunk);
|
|
65
|
-
}
|
|
66
|
-
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
63
|
+
const body = await readMcpBody(req, res);
|
|
64
|
+
if (body === MCP_BODY_TOO_LARGE) return true;
|
|
67
65
|
|
|
68
66
|
let transport: StreamableHTTPServerTransport;
|
|
69
67
|
|
package/src/http/mcp.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
parseTaskToolManifest,
|
|
14
14
|
selectTaskTools,
|
|
15
15
|
} from "@/utils/task-tool-manifest";
|
|
16
|
+
import { MCP_BODY_TOO_LARGE, readMcpBody } from "./mcp-body";
|
|
16
17
|
|
|
17
18
|
export type McpTransportActivity = Record<string, number>;
|
|
18
19
|
export type McpSessionAgents = Record<string, string>;
|
|
@@ -166,19 +167,17 @@ export async function handleMcp(
|
|
|
166
167
|
if (agentMismatch) return true;
|
|
167
168
|
|
|
168
169
|
if (req.method === "POST") {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
170
|
+
const body = await readMcpBody(req, res);
|
|
171
|
+
if (body === MCP_BODY_TOO_LARGE) return true;
|
|
172
|
+
const rpc = body as { method?: unknown; id?: unknown } | null;
|
|
174
173
|
|
|
175
|
-
if (!sessionId &&
|
|
174
|
+
if (!sessionId && rpc?.method === "server/discover") {
|
|
176
175
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
177
176
|
res.end(
|
|
178
177
|
JSON.stringify({
|
|
179
178
|
jsonrpc: "2.0",
|
|
180
179
|
error: { code: -32601, message: "Method not found" },
|
|
181
|
-
id:
|
|
180
|
+
id: rpc.id ?? null,
|
|
182
181
|
}),
|
|
183
182
|
);
|
|
184
183
|
return true;
|
package/src/http/memory.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from "../be/memory/raters/types";
|
|
36
36
|
import { rerank } from "../be/memory/reranker";
|
|
37
37
|
import { getRetrievalsForAgent, hasRetrievalForTask } from "../be/memory/retrieval-store";
|
|
38
|
+
import { searchMemoryCandidates } from "../be/memory/search-candidates";
|
|
38
39
|
import { getUsefulnessStats } from "../be/memory/usefulness-stats";
|
|
39
40
|
import { shouldPersistAutomaticTaskMemory } from "../memory/automatic-task-gate";
|
|
40
41
|
import { buildRecallQuery } from "../memory/recall-query";
|
|
@@ -916,14 +917,19 @@ export async function handleMemory(
|
|
|
916
917
|
const queryEmbedding = await provider.embed(query);
|
|
917
918
|
|
|
918
919
|
const candidateLimit = Math.min(limit, 20) * CANDIDATE_SET_MULTIPLIER;
|
|
919
|
-
const candidates = await
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
920
|
+
const candidates = await searchMemoryCandidates(
|
|
921
|
+
store,
|
|
922
|
+
queryEmbedding ?? new Float32Array(0),
|
|
923
|
+
myAgentId,
|
|
924
|
+
{
|
|
925
|
+
scope,
|
|
926
|
+
limit: candidateLimit,
|
|
927
|
+
source,
|
|
928
|
+
isLead: false,
|
|
929
|
+
queryText: query,
|
|
930
|
+
keyPrefix,
|
|
931
|
+
},
|
|
932
|
+
);
|
|
927
933
|
// Default-on 1-hop memory_link neighbor expansion (disable with
|
|
928
934
|
// MEMORY_GRAPH_EXPANSION=0|false).
|
|
929
935
|
const expanded = await expandCandidatesWithGraph(candidates, myAgentId, {
|
|
@@ -1039,13 +1045,18 @@ export async function handleMemory(
|
|
|
1039
1045
|
4096,
|
|
1040
1046
|
Math.max(offset + pageLimit, pageLimit) * CANDIDATE_SET_MULTIPLIER,
|
|
1041
1047
|
);
|
|
1042
|
-
let candidates = await
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1048
|
+
let candidates = await searchMemoryCandidates(
|
|
1049
|
+
store,
|
|
1050
|
+
queryEmbedding ?? new Float32Array(0),
|
|
1051
|
+
viewer.agentId,
|
|
1052
|
+
{
|
|
1053
|
+
scope,
|
|
1054
|
+
limit: candidateLimit,
|
|
1055
|
+
isLead: viewer.seesAll,
|
|
1056
|
+
source,
|
|
1057
|
+
queryText: query.trim(),
|
|
1058
|
+
},
|
|
1059
|
+
);
|
|
1049
1060
|
if (agentId) {
|
|
1050
1061
|
candidates = candidates.filter((c) => c.agentId === agentId);
|
|
1051
1062
|
}
|
package/src/http/page-proxy.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { getPage } from "../be/db";
|
|
3
3
|
import { getApiKey } from "../utils/api-key";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
extractLegacyPageSession,
|
|
6
|
+
extractPageSession,
|
|
7
|
+
isCookieSafePageId,
|
|
8
|
+
} from "../utils/page-session";
|
|
5
9
|
import { route } from "./route-def";
|
|
6
10
|
import { jsonError } from "./utils";
|
|
7
11
|
|
|
@@ -11,7 +15,10 @@ import { jsonError } from "./utils";
|
|
|
11
15
|
*
|
|
12
16
|
* Flow:
|
|
13
17
|
* 1. Browser hits `/@swarm/api/<rest>` from inside an iframe of `/p/:id`.
|
|
14
|
-
* 2.
|
|
18
|
+
* 2. The browser SDK names its page in `X-Swarm-Page-Id` (else we read the
|
|
19
|
+
* same-host `/p/<id>` Referer). We read that page's `page_session_<id>`
|
|
20
|
+
* cookie (or the legacy `page_session`), verify HMAC + expiry, and
|
|
21
|
+
* require the signed payload to name the same page.
|
|
15
22
|
* 3. Look up the page and recover the signed viewer identity.
|
|
16
23
|
* 4. Re-issue the request to the same API server's `/api/<rest>` with
|
|
17
24
|
* the operator bearer plus the original signed session token. The bearer NEVER
|
|
@@ -119,6 +126,48 @@ route({
|
|
|
119
126
|
|
|
120
127
|
const PROXY_PREFIX = "/@swarm/api/";
|
|
121
128
|
|
|
129
|
+
/** Request header the browser SDK uses to say which page's session to use. */
|
|
130
|
+
export const PAGE_ID_REQUEST_HEADER = "x-swarm-page-id";
|
|
131
|
+
|
|
132
|
+
const PAGE_PATH_RE = /^\/p\/([^/.]+)/;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Which page the caller claims to be: the `X-Swarm-Page-Id` header, else the
|
|
136
|
+
* `/p/<id>` path of a same-host Referer (pages that call `fetch` directly
|
|
137
|
+
* instead of the SDK). This only selects which cookie to read. It grants
|
|
138
|
+
* nothing: the cookie's signed payload must still name this page, and the
|
|
139
|
+
* forwarded `X-Page-Id` comes from that payload. `undefined` means the caller
|
|
140
|
+
* named no page (a client predating per-page cookies). `null` means it named
|
|
141
|
+
* one that cannot be a page id.
|
|
142
|
+
* Exported for tests.
|
|
143
|
+
*/
|
|
144
|
+
export function requestedPageId(req: IncomingMessage): string | null | undefined {
|
|
145
|
+
const raw = req.headers[PAGE_ID_REQUEST_HEADER];
|
|
146
|
+
const fromHeader = Array.isArray(raw) ? raw[0] : raw;
|
|
147
|
+
if (fromHeader !== undefined) return isCookieSafePageId(fromHeader) ? fromHeader : null;
|
|
148
|
+
|
|
149
|
+
const referer = req.headers.referer;
|
|
150
|
+
if (!referer) return undefined;
|
|
151
|
+
let refererUrl: URL;
|
|
152
|
+
try {
|
|
153
|
+
refererUrl = new URL(referer);
|
|
154
|
+
} catch {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
// A cross-site Referer must not pick a cookie: a custom header would force a
|
|
158
|
+
// CORS preflight, a Referer does not.
|
|
159
|
+
if (refererUrl.host !== req.headers.host) return undefined;
|
|
160
|
+
const match = PAGE_PATH_RE.exec(refererUrl.pathname);
|
|
161
|
+
if (!match) return undefined;
|
|
162
|
+
let fromReferer: string;
|
|
163
|
+
try {
|
|
164
|
+
fromReferer = decodeURIComponent(match[1]!);
|
|
165
|
+
} catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return isCookieSafePageId(fromReferer) ? fromReferer : undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
122
171
|
/**
|
|
123
172
|
* Reject a proxied suffix that smells like path traversal or an attempt to
|
|
124
173
|
* smuggle an extra path segment via percent-encoding (e.g. `%2e%2e`, `%2f`).
|
|
@@ -194,11 +243,18 @@ export async function handlePageProxy(req: IncomingMessage, res: ServerResponse)
|
|
|
194
243
|
}
|
|
195
244
|
|
|
196
245
|
// ─── Cookie validation ────────────────────────────────────────────────────
|
|
197
|
-
const
|
|
198
|
-
|
|
246
|
+
const claimedPageId = requestedPageId(req);
|
|
247
|
+
const session =
|
|
248
|
+
claimedPageId === undefined
|
|
249
|
+
? await extractLegacyPageSession(req)
|
|
250
|
+
: claimedPageId === null
|
|
251
|
+
? null
|
|
252
|
+
: await extractPageSession(req, claimedPageId);
|
|
253
|
+
if (!session) {
|
|
199
254
|
jsonError(res, "no page session", 401);
|
|
200
255
|
return true;
|
|
201
256
|
}
|
|
257
|
+
const { payload } = session;
|
|
202
258
|
|
|
203
259
|
const page = await getPage(payload.pageId);
|
|
204
260
|
if (!page) {
|
|
@@ -236,7 +292,7 @@ export async function handlePageProxy(req: IncomingMessage, res: ServerResponse)
|
|
|
236
292
|
const headers: Record<string, string> = {
|
|
237
293
|
Authorization: `Bearer ${apiKey}`,
|
|
238
294
|
"X-Page-Id": page.id,
|
|
239
|
-
"X-Page-Session":
|
|
295
|
+
"X-Page-Session": session.token,
|
|
240
296
|
};
|
|
241
297
|
// The page owner is not the browser viewer. The receiving auth layer
|
|
242
298
|
// verifies this original signed token before resolving any user identity.
|