@desplega.ai/agent-swarm 1.149.1 → 1.151.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/README.md +18 -3
- package/dist/{acp-adapter-vhkzfpjg.js → acp-adapter-ex8hzjfc.js} +5 -5
- package/dist/{actions-9d2694yk.js → actions-2bgpyhww.js} +39 -11
- package/dist/{app-fq2ggynt.js → app-1706zhr1.js} +7 -7
- package/dist/{artifact-gj9arwrr.js → artifact-td4q4dch.js} +1 -1
- package/dist/{assistant-edb6n9nn.js → assistant-bavke5p7.js} +44 -20
- package/dist/{azure-openai-responses-cqar64rc.js → azure-openai-responses-f38x2avq.js} +13 -13
- package/dist/{boot-reembed-msweabbp.js → boot-reembed-3cmrrmpa.js} +9 -9
- package/dist/{boot-reembed-q5wd3tha.js → boot-reembed-nafcmmmc.js} +14 -16
- package/dist/{boot-scrub-logs-3g1az933.js → boot-scrub-logs-cd6a2sq0.js} +5 -5
- package/dist/{claude-adapter-v3rnzsdc.js → claude-adapter-wfp2gzvz.js} +15 -16
- package/dist/{claude-managed-adapter-qvsmh6mv.js → claude-managed-adapter-dsmkcrjy.js} +3 -3
- package/dist/{claude-sdk-session-qxrpsna1.js → claude-sdk-session-bht9jjak.js} +15 -16
- package/dist/{cli-5m0qmvdp.js → cli-0qb1z6s2.js} +1 -1
- package/dist/{cli-9d8ntetj.js → cli-2658f6at.js} +1 -1
- package/dist/{cli-7evafqvy.js → cli-2tbvf1z1.js} +85 -69
- package/dist/{cli-k351hv5k.js → cli-3v9xhjcn.js} +24 -12
- package/dist/{cli-1ebpar1q.js → cli-4wppfkcm.js} +350 -20
- package/dist/{cli-4260r1tg.js → cli-5jd2z7av.js} +97 -84
- package/dist/{cli-cv7s5zdq.js → cli-5tpgq60f.js} +9865 -715
- package/dist/{cli-6skz3rk8.js → cli-7tgcstn2.js} +9 -4
- package/dist/{cli-01g3x1ca.js → cli-9004vfdf.js} +108 -56
- package/dist/{cli-1c94wvcw.js → cli-a1z3h107.js} +124 -99
- package/dist/{cli-9br8ypfv.js → cli-czjtgfys.js} +2 -2
- package/dist/{cli-5my3bjsd.js → cli-d3p82erb.js} +7 -7
- package/dist/{cli-e29gmdmw.js → cli-dv6cdjs2.js} +2 -1
- package/dist/{cli-wby1xnnb.js → cli-f1wky6zp.js} +1 -1
- package/dist/{cli-zvz62chv.js → cli-fm4ytsdk.js} +4 -4
- package/dist/{cli-tg8qj8ee.js → cli-frcta2w4.js} +6 -6
- package/dist/{cli-7xx10h0a.js → cli-g69fdc9g.js} +2 -2
- package/dist/{cli-cmp8g3sw.js → cli-gbfwcd3k.js} +44 -9
- package/dist/{handlers-nsg0bcye.js → cli-gjyvhmmh.js} +97 -45
- package/dist/{cli-08b07b4r.js → cli-ht5rs63n.js} +1 -1
- package/dist/{cli-jecc01cb.js → cli-j4nv2wqy.js} +2 -4
- package/dist/{cli-cd60fv6j.js → cli-j9xczhaq.js} +1 -1
- package/dist/cli-pf9q3696.js +172 -0
- package/dist/{cli-xm87hzbw.js → cli-pgj4a5fb.js} +1 -1
- package/dist/{cli-jezx9b9c.js → cli-ppm4975n.js} +9 -3
- package/dist/{cli-3z634pqe.js → cli-q88st9aj.js} +2 -2
- package/dist/{cli-3sysyjve.js → cli-r2q6pevn.js} +142 -49
- package/dist/{cli-07jd68a1.js → cli-re8z5p8p.js} +1 -1
- package/dist/{cli-qgd4sqz5.js → cli-s14agm5s.js} +121 -14
- package/dist/{cli-svbrpf5q.js → cli-sjaae6ff.js} +9872 -424
- package/dist/{cli-73gq0zxn.js → cli-vwm416m6.js} +13 -13
- package/dist/{cli-4wtq5jjv.js → cli-wbyk465z.js} +1 -1
- package/dist/{cli-3apbzgyk.js → cli-z61ykvam.js} +19 -14
- package/dist/{cli-e06a8a7y.js → cli-zkxymas5.js} +4 -3
- package/dist/cli.js +26 -25
- package/dist/{codex-adapter-jyw7a91z.js → codex-adapter-jjjk8w4m.js} +8 -9
- package/dist/{codex-hook-hfrt9shr.js → codex-hook-8jexc34x.js} +3 -3
- package/dist/{codex-session-runner-bmngj0s5.js → codex-session-runner-nd6ayxkx.js} +8 -9
- package/dist/{commands-n755g391.js → commands-9v4psyq1.js} +5 -5
- package/dist/{db-s1cgh5gv.js → db-wnt58tpe.js} +5 -5
- package/dist/dispatcher-05jh1aqk.js +74 -0
- package/dist/{e2b-p04vmtx0.js → e2b-413b8t4v.js} +1 -1
- package/dist/{google-generative-ai-tsskx7te.js → google-generative-ai-ndjmj5fa.js} +7 -7
- package/dist/{google-vertex-ppw8ydpa.js → google-vertex-fr665tqs.js} +10 -10
- package/dist/handlers-tw90pkh5.js +65 -0
- package/dist/{hook-jf0xe8zh.js → hook-123e2scb.js} +11 -12
- package/dist/{hook-h9cqrm8q.js → hook-sxe3r349.js} +11 -12
- package/dist/{http-nc6pydw5.js → http-ecsar8ez.js} +1442 -581
- package/dist/{index-ezxkds2n.js → index-16js521z.js} +335 -107
- package/dist/{index-769nc012.js → index-hw26vj40.js} +25 -19
- package/dist/{index-0wdy6gwc.js → index-nnrbpcjp.js} +25 -17
- package/dist/index-ymn7em0p.js +25 -0
- package/dist/{index-x1zcj09z.js → index-zj518gf9.js} +26 -20
- package/dist/{keepalive-nesm3xb6.js → keepalive-asqtcm4y.js} +7 -7
- package/dist/{lead-bwy6t2za.js → lead-xbfr7fvt.js} +40 -48
- package/dist/{maintenance-8w393zf0.js → maintenance-qma55qvw.js} +9 -9
- package/dist/{oauth-refresh-sweep-sh8xjy08.js → oauth-refresh-sweep-3s04c6qw.js} +6 -6
- package/dist/{onboard-25ps29h1.js → onboard-drskd386.js} +13 -4
- package/dist/{openai-codex-responses-g7w48y30.js → openai-codex-responses-wy6a43wd.js} +13 -13
- package/dist/{openai-responses-nmeyptke.js → openai-responses-6xwf7bzg.js} +19 -19
- package/dist/{opencode-adapter-dce4wskd.js → opencode-adapter-28axqa09.js} +4 -4
- package/dist/{otel-impl-b93gxnz5.js → otel-impl-943c96er.js} +1 -1
- package/dist/{pi-mono-adapter-zvshk5cj.js → pi-mono-adapter-jcqt71cg.js} +17 -19
- package/dist/{pricing-refresh-5v56x7gb.js → pricing-refresh-mx51az05.js} +7 -7
- package/dist/{rbac-roles-x55cnezn.js → rbac-roles-78baecqd.js} +13 -9
- package/dist/{rbac-roles-0p0s6pbj.js → rbac-roles-wcmrg6sv.js} +7 -7
- package/dist/{render-v2-hyd45w8b.js → render-v2-8zjntgf5.js} +7 -7
- package/dist/{seed-pricing-49gbgn96.js → seed-pricing-jqjs6cws.js} +6 -6
- package/dist/{setup-qvq64vzq.js → setup-4pks0vfk.js} +2 -2
- package/dist/work-objects-wangt792.js +165 -0
- package/dist/{worker-scj60cxg.js → worker-p2mr422x.js} +40 -48
- package/openapi.json +1365 -268
- package/package.json +4 -3
- package/src/agentmail/handlers.ts +95 -66
- package/src/be/budget-refusal-notify.ts +1 -0
- package/src/be/db/tasks/read.ts +3 -0
- package/src/be/db/tasks/write.ts +13 -1
- package/src/be/db-client.ts +5 -0
- package/src/be/db.ts +15 -5
- package/src/be/extensions/db.ts +461 -0
- package/src/be/extensions/validate.ts +107 -0
- package/src/be/memory/boot-reembed.ts +17 -12
- package/src/be/memory/index.ts +12 -0
- package/src/be/memory/providers/openai-embedding.ts +4 -0
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/153_deferred_task_wait_members.sql +25 -0
- package/src/be/migrations/154_routing_source.sql +3 -0
- package/src/be/migrations/155_extensions.sql +66 -0
- package/src/be/migrations/156_inbox_item_state_read_at.sql +7 -0
- package/src/be/rbac-roles.ts +3 -2
- package/src/be/scripts/typecheck.ts +59 -41
- package/src/be/seed/schedules-seeder.ts +25 -2
- package/src/be/seed/workflows-seeder.ts +17 -0
- package/src/be/seed-scripts/catalog/delegate.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/steering.ts +1 -0
- package/src/be/swarm-config-guard.ts +3 -1
- package/src/commands/credential-refresh.ts +70 -0
- package/src/commands/onboard/compose-generator.ts +14 -2
- package/src/commands/onboard/env-generator.ts +7 -0
- package/src/commands/provider-credentials.ts +2 -25
- package/src/commands/runner.ts +24 -57
- package/src/extensions/apply-task-create.ts +135 -0
- package/src/extensions/bundle-path.ts +10 -0
- package/src/extensions/contract-runtime.ts +7 -0
- package/src/extensions/contract-types.generated.ts +4 -0
- package/src/extensions/contract.ts +270 -0
- package/src/extensions/ctx.ts +186 -0
- package/src/extensions/dispatcher.ts +479 -0
- package/src/extensions/identity.ts +44 -0
- package/src/extensions/lifecycle.ts +281 -0
- package/src/extensions/loader.ts +207 -0
- package/src/extensions/post-bridge.ts +161 -0
- package/src/github/handlers.ts +185 -140
- package/src/gitlab/handlers.ts +97 -77
- package/src/heartbeat/heartbeat.ts +155 -83
- package/src/http/agents.ts +1 -1
- package/src/http/all-routes.ts +1 -0
- package/src/http/approval-requests.ts +1 -0
- package/src/http/apps.ts +14 -9
- package/src/http/config.ts +4 -1
- package/src/http/core.ts +38 -2
- package/src/http/extensions.ts +547 -0
- package/src/http/inbox-state.ts +2 -0
- package/src/http/index.ts +41 -3
- package/src/http/mcp-bridge.ts +96 -62
- package/src/http/mcp.ts +1 -1
- package/src/http/script-runs.ts +5 -0
- package/src/http/status.ts +6 -4
- package/src/http/tasks.ts +51 -30
- package/src/integrations/kapso/inbound.ts +15 -10
- package/src/jira/sync.ts +26 -16
- package/src/linear/sync.ts +26 -16
- package/src/prompts/session-templates.ts +18 -13
- package/src/providers/claude-adapter.ts +21 -8
- package/src/rbac/admission.ts +1 -1
- package/src/rbac/elevated-agents.ts +24 -0
- package/src/rbac/legacy-policy.ts +35 -6
- package/src/rbac/permissions.ts +8 -0
- package/src/scheduler/deferred-task-waits.ts +138 -27
- package/src/scheduler/schedule-task.ts +54 -24
- package/src/scheduler/scheduler.ts +108 -125
- package/src/scripts-runtime/executors/native.ts +5 -1
- package/src/scripts-runtime/executors/types.ts +5 -0
- package/src/scripts-runtime/import-allowlist.ts +79 -19
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/swarm-config.ts +8 -0
- package/src/scripts-runtime/swarm-sdk.ts +3 -0
- package/src/scripts-runtime/types/stdlib.d.ts +16 -3
- package/src/scripts-runtime/types/swarm-extension.d.ts +562 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +16 -3
- package/src/server.ts +6 -0
- package/src/slack/HEURISTICS.md +3 -1
- package/src/slack/actions.ts +21 -16
- package/src/slack/app.ts +2 -0
- package/src/slack/assistant.ts +4 -12
- package/src/slack/blocks.ts +1 -1
- package/src/slack/handlers.ts +107 -19
- package/src/slack/inbound-files.ts +9 -5
- package/src/slack/render-v2.ts +188 -38
- package/src/slack/router.ts +13 -3
- package/src/slack/steering.ts +3 -3
- package/src/slack/thread-buffer.ts +66 -23
- package/src/slack/work-objects.test.ts +252 -0
- package/src/slack/work-objects.ts +118 -0
- package/src/tasks/errors.ts +9 -0
- package/src/tasks/sibling-awareness.ts +35 -1
- package/src/tasks/worker-follow-up.ts +67 -11
- package/src/tests/acp-adapter.test.ts +10 -10
- package/src/tests/agent-fs-live-url.test.ts +40 -0
- package/src/tests/agent-fs-provision-seeder.test.ts +12 -12
- package/src/tests/api-key-tracking.test.ts +18 -18
- package/src/tests/api-key.test.ts +4 -2
- package/src/tests/apps-rbac.test.ts +1 -1
- package/src/tests/apps-spike5.test.ts +1 -1
- package/src/tests/apps-sync-engine.test.ts +8 -8
- package/src/tests/apps-user-config.test.ts +1 -1
- package/src/tests/artifact-sdk.test.ts +8 -8
- package/src/tests/asset-key-mcp.test.ts +2 -0
- package/src/tests/automation-preflight-alert.test.ts +1 -1
- package/src/tests/base-prompt.test.ts +7 -6
- package/src/tests/budget-user-scope.test.ts +1 -1
- package/src/tests/budgets-routes.test.ts +1 -1
- package/src/tests/claude-adapter-binary.test.ts +22 -40
- package/src/tests/claude-adapter-otel.test.ts +13 -7
- package/src/tests/claude-adapter.test.ts +8 -6
- package/src/tests/claude-hook-credentials.test.ts +3 -3
- package/src/tests/claude-managed-adapter.test.ts +4 -4
- package/src/tests/claude-managed-setup.test.ts +3 -3
- package/src/tests/claude-sdk-lifecycle.test.ts +1 -1
- package/src/tests/claude-sdk-transport.test.ts +6 -6
- package/src/tests/claude-stop-hook.test.ts +8 -8
- package/src/tests/claude-worker-parity.test.ts +3 -3
- package/src/tests/codex-adapter-pool-auth-revalidate.test.ts +5 -5
- package/src/tests/codex-adapter.test.ts +7 -5
- package/src/tests/codex-app-server.test.ts +2 -2
- package/src/tests/codex-login.test.ts +15 -15
- package/src/tests/codex-oauth-credential-info.test.ts +4 -4
- package/src/tests/codex-oauth-keep-warm.test.ts +4 -4
- package/src/tests/codex-oauth-refresh-lock.test.ts +1 -1
- package/src/tests/codex-oauth-storage.test.ts +7 -7
- package/src/tests/codex-pool.test.ts +1 -1
- package/src/tests/codex-steering-hook.test.ts +1 -1
- package/src/tests/codex-swarm-events.test.ts +1 -1
- package/src/tests/config-api-only-keys.test.ts +1 -1
- package/src/tests/config-session-auth.test.ts +2 -2
- package/src/tests/connection-embedded-auth.test.ts +20 -18
- package/src/tests/core-auth.test.ts +1 -1
- package/src/tests/create-page-tool.test.ts +3 -3
- package/src/tests/credential-refresh.test.ts +115 -0
- package/src/tests/credential-wait.test.ts +2 -2
- package/src/tests/credentials.test.ts +6 -4
- package/src/tests/db-queries-oauth.test.ts +40 -40
- package/src/tests/defer-task.test.ts +73 -8
- package/src/tests/deferred-task-wait-migration.test.ts +64 -0
- package/src/tests/deferred-task-wake.test.ts +349 -8
- package/src/tests/devin-adapter.test.ts +1 -1
- package/src/tests/devin-api.test.ts +1 -1
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/e2b-dispatch.test.ts +21 -19
- package/src/tests/e2e-claude-transport.test.ts +3 -3
- package/src/tests/e2e-redact.test.ts +6 -6
- package/src/tests/ensure-token.test.ts +68 -68
- package/src/tests/entrypoint-api-readiness.test.ts +1 -1
- package/src/tests/entrypoint-codex-oauth-seed.test.ts +13 -13
- package/src/tests/env-flag.test.ts +1 -0
- package/src/tests/events-http.test.ts +1 -1
- package/src/tests/extensions-db.test.ts +216 -0
- package/src/tests/extensions-dispatcher.test.ts +275 -0
- package/src/tests/extensions-example-heartbeat-remediate.test.ts +69 -0
- package/src/tests/extensions-example-mcp-install.test.ts +63 -0
- package/src/tests/extensions-example-no-exclamation-marks.test.ts +72 -0
- package/src/tests/extensions-example-rewrite-and-suppress.test.ts +70 -0
- package/src/tests/extensions-example-route-slack-channel.test.ts +217 -0
- package/src/tests/extensions-heartbeat.test.ts +291 -0
- package/src/tests/extensions-http.test.ts +431 -0
- package/src/tests/extensions-lead-equivalence.test.ts +49 -0
- package/src/tests/extensions-lifecycle.test.ts +209 -0
- package/src/tests/extensions-loader.test.ts +95 -0
- package/src/tests/extensions-mcp-tools.test.ts +245 -0
- package/src/tests/extensions-pre-task.test.ts +487 -0
- package/src/tests/extensions-run-context.test.ts +41 -0
- package/src/tests/extensions-tool-call.test.ts +288 -0
- package/src/tests/extensions-validate.test.ts +196 -0
- package/src/tests/fixtures/extensions/bad-import/hooks.ts +8 -0
- package/src/tests/fixtures/extensions/bad-import/manifest.json +9 -0
- package/src/tests/fixtures/extensions/bad-return-shape/hooks.ts +10 -0
- package/src/tests/fixtures/extensions/bad-return-shape/manifest.json +9 -0
- package/src/tests/fixtures/extensions/block-tasks-from-source/hooks.ts +22 -0
- package/src/tests/fixtures/extensions/block-tasks-from-source/manifest.json +9 -0
- package/src/tests/fixtures/extensions/ignore-channel/hooks.ts +22 -0
- package/src/tests/fixtures/extensions/ignore-channel/manifest.json +9 -0
- package/src/tests/fixtures/extensions/load.ts +10 -0
- package/src/tests/fixtures/extensions/minimal/hooks.ts +7 -0
- package/src/tests/fixtures/extensions/minimal/manifest.json +9 -0
- package/src/tests/fixtures/extensions/never-fail-long-tasks/hooks.ts +11 -0
- package/src/tests/fixtures/extensions/never-fail-long-tasks/manifest.json +9 -0
- package/src/tests/fixtures/extensions/no-exclamation-marks/hooks.ts +23 -0
- package/src/tests/fixtures/extensions/no-exclamation-marks/manifest.json +9 -0
- package/src/tests/fixtures/extensions/post-logger/hooks.ts +9 -0
- package/src/tests/fixtures/extensions/post-logger/manifest.json +9 -0
- package/src/tests/fixtures/extensions/priority-a/hooks.ts +7 -0
- package/src/tests/fixtures/extensions/priority-a/manifest.json +9 -0
- package/src/tests/fixtures/extensions/priority-b/hooks.ts +7 -0
- package/src/tests/fixtures/extensions/priority-b/manifest.json +9 -0
- package/src/tests/fixtures/extensions/record-only-on-tag/hooks.ts +16 -0
- package/src/tests/fixtures/extensions/record-only-on-tag/manifest.json +9 -0
- package/src/tests/fixtures/extensions/reserved-assets/hooks.ts +5 -0
- package/src/tests/fixtures/extensions/reserved-assets/manifest.json +10 -0
- package/src/tests/fixtures/extensions/rewrite-progress-prefix/hooks.ts +22 -0
- package/src/tests/fixtures/extensions/rewrite-progress-prefix/manifest.json +9 -0
- package/src/tests/fixtures/extensions/rewrite-task-priority/hooks.ts +7 -0
- package/src/tests/fixtures/extensions/rewrite-task-priority/manifest.json +9 -0
- package/src/tests/fixtures/extensions/route-channel-to-agent/hooks.ts +26 -0
- package/src/tests/fixtures/extensions/route-channel-to-agent/manifest.json +9 -0
- package/src/tests/fixtures/extensions/slow/hooks.ts +10 -0
- package/src/tests/fixtures/extensions/slow/manifest.json +9 -0
- package/src/tests/fixtures/extensions/suppress-lead-follow-up/hooks.ts +12 -0
- package/src/tests/fixtures/extensions/suppress-lead-follow-up/manifest.json +9 -0
- package/src/tests/fixtures/extensions/throws/hooks.ts +12 -0
- package/src/tests/fixtures/extensions/throws/manifest.json +9 -0
- package/src/tests/fixtures/extensions/worker-runtime/hooks.ts +5 -0
- package/src/tests/fixtures/extensions/worker-runtime/manifest.json +9 -0
- package/src/tests/fs-routes.test.ts +1 -1
- package/src/tests/gitlab-auth.test.ts +10 -10
- package/src/tests/harness-provider-resolution.test.ts +8 -5
- package/src/tests/heartbeat-reroute-decision.test.ts +1 -0
- package/src/tests/helper-credentials.test.ts +137 -0
- package/src/tests/http/context-routes.test.ts +2 -2
- package/src/tests/http-api-integration.test.ts +5 -2
- package/src/tests/http-users.test.ts +1 -1
- package/src/tests/identity-refresh.test.ts +2 -2
- package/src/tests/internal-ai/complete-structured.test.ts +2 -2
- package/src/tests/internal-ai/credentials.test.ts +19 -17
- package/src/tests/jira-metadata.test.ts +1 -1
- package/src/tests/jira-oauth.test.ts +5 -5
- package/src/tests/jira-sync.test.ts +1 -1
- package/src/tests/jira-webhook-lifecycle.test.ts +3 -3
- package/src/tests/jira-webhook.test.ts +2 -2
- package/src/tests/kapso-inbound.test.ts +2 -2
- package/src/tests/kv-http.test.ts +1 -1
- package/src/tests/kv-namespace-resolution.test.ts +1 -1
- package/src/tests/kv-page-proxy.test.ts +2 -2
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/linear-webhook.test.ts +1 -1
- package/src/tests/mcp-oauth-dcr-reuse.test.ts +6 -6
- package/src/tests/mcp-oauth-ensure-token.test.ts +28 -22
- package/src/tests/mcp-oauth-manual-client.test.ts +16 -12
- package/src/tests/mcp-oauth-queries.test.ts +35 -32
- package/src/tests/mcp-oauth-resolve-secrets.test.ts +8 -8
- package/src/tests/mcp-oauth-wrapper.test.ts +20 -20
- package/src/tests/mcp-user-route.test.ts +1 -1
- package/src/tests/memory-edges.test.ts +1 -1
- package/src/tests/memory-edit-authorization.test.ts +32 -0
- package/src/tests/memory-rate-endpoint.test.ts +1 -1
- package/src/tests/memory-rate-tool.test.ts +2 -2
- package/src/tests/memory-rater-e2e.test.ts +1 -1
- package/src/tests/memory-rater-llm-summarizer.test.ts +3 -3
- package/src/tests/memory-rater-llm.test.ts +1 -1
- package/src/tests/model-control.test.ts +3 -0
- package/src/tests/multi-runtime-enabled-stats.test.ts +2 -2
- package/src/tests/multi-runtime-registration.test.ts +111 -17
- package/src/tests/oauth-access-token-tool.test.ts +18 -18
- package/src/tests/oauth-callback-flow.test.ts +8 -8
- package/src/tests/oauth-credential-bindings.test.ts +29 -27
- package/src/tests/oauth-keepalive.test.ts +10 -10
- package/src/tests/oauth-presets.test.ts +13 -5
- package/src/tests/oauth-refresh-failure.test.ts +9 -9
- package/src/tests/oauth-refresh-sweep.test.ts +20 -13
- package/src/tests/oauth-wrapper.test.ts +1 -1
- package/src/tests/onboard-compose.test.ts +47 -3
- package/src/tests/onboard-env.test.ts +26 -26
- package/src/tests/onboard-non-interactive.test.ts +4 -4
- package/src/tests/opencode-adapter.test.ts +1 -1
- package/src/tests/opencode-plugin.test.ts +2 -2
- package/src/tests/otel-session-cost-metrics.test.ts +1 -1
- package/src/tests/page-proxy-authed.test.ts +2 -2
- package/src/tests/page-proxy.test.ts +2 -2
- package/src/tests/page-session.test.ts +7 -7
- package/src/tests/page-viewer-auth.test.ts +2 -2
- package/src/tests/pages-authed-mode.test.ts +1 -1
- package/src/tests/pages-http.test.ts +1 -1
- package/src/tests/pages-password-mode.test.ts +1 -1
- package/src/tests/pages-public-authed-401.test.ts +1 -1
- package/src/tests/pages-public-html.test.ts +2 -2
- package/src/tests/pages-view-count.test.ts +1 -1
- package/src/tests/pi-mono-adapter.test.ts +10 -10
- package/src/tests/pi-mono-extension.test.ts +2 -2
- package/src/tests/preload.ts +5 -0
- package/src/tests/pricing-routes.test.ts +1 -1
- package/src/tests/profile-sync-warning.test.ts +8 -8
- package/src/tests/profile-sync.test.ts +8 -8
- package/src/tests/prompt-template-session.test.ts +36 -5
- package/src/tests/provider-process-group.test.ts +95 -4
- package/src/tests/providers/pi-cost.test.ts +1 -1
- package/src/tests/queue-stall-alarm.test.ts +1 -1
- package/src/tests/rbac-admission.test.ts +19 -4
- package/src/tests/rbac-charact-http.test.ts +1 -1
- package/src/tests/rbac-e2e-helpers.ts +1 -1
- package/src/tests/rbac-engine.test.ts +39 -0
- package/src/tests/realtime-room-auth.test.ts +1 -1
- package/src/tests/realtime-room-surfaces.test.ts +1 -1
- package/src/tests/reload-config.test.ts +45 -1
- package/src/tests/routing-affinity-inventory.test.ts +1 -1
- package/src/tests/routing-reason-contract.test.ts +76 -3
- package/src/tests/routing-reason-inventory.test.ts +11 -6
- package/src/tests/routing-source.test.ts +53 -0
- package/src/tests/runner-agent-fs-provision-retry.test.ts +1 -1
- package/src/tests/runner-context-preamble.test.ts +1 -1
- package/src/tests/runner-deployment-resume.test.ts +1 -1
- package/src/tests/runner-fallback-output.test.ts +1 -1
- package/src/tests/runner-identity-refresh.test.ts +1 -1
- package/src/tests/runner-skills-refresh.test.ts +1 -1
- package/src/tests/schedule-target-type.test.ts +3 -3
- package/src/tests/scheduled-tasks.test.ts +14 -5
- package/src/tests/scheduler-occurrence.test.ts +347 -0
- package/src/tests/script-apis-mcp.test.ts +1 -1
- package/src/tests/script-connections-http.test.ts +61 -58
- package/src/tests/script-connections-mcp.test.ts +1 -1
- package/src/tests/script-connections.test.ts +3 -1
- package/src/tests/script-runs-http.test.ts +15 -7
- package/src/tests/script-workflows-runtime-e2e.test.ts +1 -1
- package/src/tests/scripts-boot-reembed.test.ts +4 -0
- package/src/tests/scripts-embeddings.test.ts +4 -0
- package/src/tests/scripts-external-api.test.ts +1 -1
- package/src/tests/scripts-http.test.ts +1 -1
- package/src/tests/scripts-import-allowlist.test.ts +44 -1
- package/src/tests/scripts-mcp-e2e.test.ts +81 -1
- package/src/tests/scripts-only-gating.test.ts +4 -2
- package/src/tests/scripts-retention.test.ts +1 -1
- package/src/tests/scripts-runtime-identity.test.ts +2 -2
- package/src/tests/scripts-runtime-secret-egress.test.ts +1 -1
- package/src/tests/scripts-runtime.test.ts +6 -4
- package/src/tests/scripts-typecheck.test.ts +21 -0
- package/src/tests/sdk-allowlist.test.ts +1 -1
- package/src/tests/secret-scrubber.test.ts +38 -31
- package/src/tests/seed-registry.test.ts +46 -8
- package/src/tests/seed-schedules.test.ts +44 -9
- package/src/tests/seed-workflows.test.ts +21 -9
- package/src/tests/send-task-output-schema.test.ts +9 -1
- package/src/tests/send-task-requested-by.test.ts +1 -0
- package/src/tests/send-task-slack-routing-guard.test.ts +2 -0
- package/src/tests/session-costs-codex-recompute.test.ts +1 -1
- package/src/tests/session-costs-golden.test.ts +2 -2
- package/src/tests/session-costs-model-key-normalize.test.ts +2 -2
- package/src/tests/session-costs-recompute-all-providers.test.ts +1 -1
- package/src/tests/session-tokens.test.ts +1 -1
- package/src/tests/shutdown-error-scrubbing.test.ts +1 -1
- package/src/tests/slack-blocks.test.ts +12 -1
- package/src/tests/slack-file-tools-attachments.test.ts +1 -1
- package/src/tests/slack-inbound-files.test.ts +222 -86
- package/src/tests/slack-reaction-config.test.ts +5 -4
- package/src/tests/slack-read-boundaries.test.ts +1 -1
- package/src/tests/slack-render-v2.test.ts +196 -4
- package/src/tests/slack-router.test.ts +17 -2
- package/src/tests/slack-steering.test.ts +19 -0
- package/src/tests/status.test.ts +19 -7
- package/src/tests/steering-core.test.ts +1 -1
- package/src/tests/steering-enabled.test.ts +6 -8
- package/src/tests/steering-transport.test.ts +4 -4
- package/src/tests/store-progress-output-guidance.test.ts +34 -0
- package/src/tests/swarm-tool-result-gate.test.ts +11 -11
- package/src/tests/swarm-x-tool.test.ts +7 -5
- package/src/tests/sync-ai-toolbox-skills.test.ts +19 -0
- package/src/tests/task-supersede-resume.test.ts +1 -1
- package/src/tests/task-tool-preload-mcp.test.ts +4 -1
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tasks-requested-by-filter.test.ts +2 -2
- package/src/tests/telemetry-init.test.ts +11 -3
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/tool-output-agent-id.test.ts +1 -0
- package/src/tests/tool-registrar-no-input.test.ts +42 -1
- package/src/tests/tracker-fold.test.ts +7 -7
- package/src/tests/tracker-tools.test.ts +3 -3
- package/src/tests/ui-followup-lead-delegation.test.ts +1 -0
- package/src/tests/unified-oauth-migration.test.ts +5 -5
- package/src/tests/user-token-rest-auth.test.ts +1 -1
- package/src/tests/user-token-routes.test.ts +1 -1
- package/src/tests/whoami-route.test.ts +1 -1
- package/src/tests/workflow-agent-task.test.ts +15 -13
- package/src/tests/workflow-ctx.test.ts +1 -1
- package/src/tests/workflow-e2e.test.ts +1 -1
- package/src/tests/workflow-engine-v2.test.ts +3 -1
- package/src/tests/workflow-executors.test.ts +2 -2
- package/src/tests/workflow-http-v2.test.ts +8 -6
- package/src/tests/workflow-input-redaction.test.ts +7 -5
- package/src/tests/workflow-integration-io.test.ts +2 -2
- package/src/tests/workflow-llm.test.ts +5 -5
- package/src/tests/workflow-registry.test.ts +1 -1
- package/src/tests/workflow-swarm-script.test.ts +1 -1
- package/src/tests/workflow-triggers-v2.test.ts +23 -21
- package/src/tests/x-composio.test.ts +9 -7
- package/src/tests/x402-client.test.ts +1 -1
- package/src/tests/x402-config.test.ts +28 -41
- package/src/tools/defer-task.ts +156 -24
- package/src/tools/extension-common.ts +84 -0
- package/src/tools/extension-install.ts +65 -0
- package/src/tools/extension-list.ts +66 -0
- package/src/tools/script-common.ts +43 -18
- package/src/tools/send-task.ts +87 -105
- package/src/tools/skills/skill-publish.ts +1 -0
- package/src/tools/store-progress.ts +1 -1
- package/src/tools/task-action.ts +43 -22
- package/src/tools/task-tool-ctx.ts +2 -0
- package/src/tools/templates.ts +2 -1
- package/src/tools/tool-config.ts +3 -1
- package/src/tools/utils.ts +140 -16
- package/src/types.ts +144 -0
- package/src/utils/constants.ts +7 -1
- package/src/utils/credentials.test.ts +16 -16
- package/src/utils/multi-runtime.ts +2 -2
- package/src/utils/steering-enabled.ts +4 -4
- package/src/workflows/executors/agent-task.ts +18 -3
- package/templates/ai-toolbox.manifest.json +7 -1
- package/templates/extensions/README.md +17 -0
- package/templates/extensions/notify-on-complete/README.md +11 -0
- package/templates/extensions/notify-on-complete/hooks.ts +61 -0
- package/templates/extensions/notify-on-complete/manifest.json +7 -0
- package/templates/extensions/require-ticket-ref/README.md +11 -0
- package/templates/extensions/require-ticket-ref/hooks.ts +30 -0
- package/templates/extensions/require-ticket-ref/manifest.json +7 -0
- package/templates/extensions/require-verification-note/README.md +11 -0
- package/templates/extensions/require-verification-note/hooks.ts +32 -0
- package/templates/extensions/require-verification-note/manifest.json +7 -0
- package/templates/schedules/daily-hn-briefing/config.json +1 -1
- package/templates/schedules/gtm-weekly-review/config.json +1 -1
- package/templates/schedules/weekly-code-health-reports/config.json +1 -1
- package/templates/schedules/weekly-dependabot-triage/config.json +1 -1
- package/templates/schedules/weekly-dora-metrics/config.json +1 -1
- package/templates/schedules/weekly-harness-upgrade-check/config.json +1 -1
- package/templates/skills/delegate-work/files/scripts/codex-exec.sh +1 -1
- package/templates/skills/scheduling/content.md +2 -0
- package/templates/skills/serply-search/SKILL.md +95 -0
- package/templates/skills/serply-search/config.json +13 -0
- package/templates/skills/serply-search/content.md +90 -0
- package/templates/skills/swarm-extensions/config.json +14 -0
- package/templates/skills/swarm-extensions/content.md +120 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/templates/workflows/claude-code-changelog-watch/config.json +1 -1
- package/templates/workflows/gsc-topic-miner/config.json +1 -1
- package/templates/workflows/linear-drain-loop/config.json +1 -1
- package/templates/workflows/llm-safe-release-context/config.json +1 -1
- package/templates/workflows/ralph-loop/config.json +1 -1
- package/dist/cli-7fehefdf.js +0 -562
- package/dist/cli-ayvy5kde.js +0 -164
- package/dist/cli-ddfw1mc4.js +0 -4322
- package/dist/cli-hzqmgcm2.js +0 -3066
- package/dist/cli-j4d4f6hv.js +0 -338
- package/dist/cli-qgm2pfpq.js +0 -6377
- package/dist/cli-sdzq01t9.js +0 -823
- package/dist/cli-xvkmedgn.js +0 -332
- package/dist/cli-y3pq4v9g.js +0 -664
- package/dist/{anthropic-messages-vs2rabzp.js → anthropic-messages-2y8nktar.js} +19 -19
- package/dist/{claude-managed-setup-f5pmsyrp.js → claude-managed-setup-48dwg9bv.js} +3 -3
- package/dist/{cli-81r683bv.js → cli-ch38q5z4.js} +0 -0
- package/dist/{cli-6664w7y4.js → cli-kj76p1ez.js} +8 -8
- package/dist/{cli-c71mg51n.js → cli-r2fpe7cx.js} +5 -5
- package/dist/{cli-4xz3pmrq.js → cli-y6x0gqtg.js} +16 -16
- package/dist/{index-aa3sa6s8.js → index-2wsdsnmm.js} +1 -1
- package/dist/{mistral-conversations-4ryk7h83.js → mistral-conversations-qhr84ph3.js} +9 -9
- package/dist/{openai-completions-0bqmsajw.js → openai-completions-32rdwmze.js} +19 -19
- package/dist/{openrouter-images-qx18fyp8.js → openrouter-images-9f9ek4rw.js} +3 -3
- package/dist/{pi-messages-79jvzz1d.js → pi-messages-b1mbr6gp.js} +4 -4
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
import {
|
|
6
|
+
closeDb,
|
|
7
|
+
createAgent,
|
|
8
|
+
createTaskExtended,
|
|
9
|
+
getDbClient,
|
|
10
|
+
getKv,
|
|
11
|
+
getTaskById,
|
|
12
|
+
initDb,
|
|
13
|
+
startTask,
|
|
14
|
+
} from "../be/db";
|
|
15
|
+
import { installExtension, listExtensionRuns } from "../be/extensions/db";
|
|
16
|
+
import { getExtensionBridgeToken, resolveBridgeCallOrigin } from "../extensions/dispatcher";
|
|
17
|
+
import { enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
|
|
18
|
+
import { registerStoreProgressTool } from "../tools/store-progress";
|
|
19
|
+
import {
|
|
20
|
+
createToolRegistrar,
|
|
21
|
+
markExtensionRequestOrigin,
|
|
22
|
+
markScriptSdkRequestOrigin,
|
|
23
|
+
swarmToolOutputSchema,
|
|
24
|
+
toolOk,
|
|
25
|
+
} from "../tools/utils";
|
|
26
|
+
import type { ExtensionInstallBody } from "../types";
|
|
27
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
28
|
+
|
|
29
|
+
const TEST_DB_PATH = "./test-extensions-tool-call.sqlite";
|
|
30
|
+
|
|
31
|
+
type RegisteredTool = {
|
|
32
|
+
handler: (argsOrExtra: unknown, extra?: unknown) => Promise<CallToolResult>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function registeredTool(server: McpServer, name: string): RegisteredTool {
|
|
36
|
+
const tools = (server as unknown as { _registeredTools: Record<string, RegisteredTool> })
|
|
37
|
+
._registeredTools;
|
|
38
|
+
const tool = tools[name];
|
|
39
|
+
if (!tool) throw new Error(`Tool "${name}" was not registered`);
|
|
40
|
+
return tool;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function meta(agentId: string) {
|
|
44
|
+
return {
|
|
45
|
+
sessionId: crypto.randomUUID(),
|
|
46
|
+
requestInfo: { headers: { "x-agent-id": agentId } },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function removeDbFiles(): Promise<void> {
|
|
51
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
52
|
+
await Bun.file(TEST_DB_PATH + suffix)
|
|
53
|
+
.delete()
|
|
54
|
+
.catch(() => {});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function enableFixture(name: string, config?: Record<string, unknown>) {
|
|
59
|
+
const bundle: ExtensionInstallBody = { ...(await loadBundleFixture(name)), config };
|
|
60
|
+
const installed = await installExtension(bundle);
|
|
61
|
+
return await enableExtension(installed.extension.id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function createProgressTarget() {
|
|
65
|
+
const agent = await createAgent({
|
|
66
|
+
name: `tool-hook-worker-${crypto.randomUUID()}`,
|
|
67
|
+
isLead: false,
|
|
68
|
+
status: "busy",
|
|
69
|
+
capabilities: [],
|
|
70
|
+
});
|
|
71
|
+
const task = await createTaskExtended("tool hook progress target", {
|
|
72
|
+
agentId: agent.id,
|
|
73
|
+
source: "mcp",
|
|
74
|
+
});
|
|
75
|
+
await startTask(task.id);
|
|
76
|
+
return { agent, task };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function waitFor(predicate: () => Promise<boolean>, timeoutMs = 10_000): Promise<void> {
|
|
80
|
+
const deadline = Date.now() + timeoutMs;
|
|
81
|
+
while (!(await predicate())) {
|
|
82
|
+
if (Date.now() >= deadline) throw new Error("Timed out while waiting for a tool hook");
|
|
83
|
+
await Bun.sleep(20);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
describe("extension tool-call hooks", () => {
|
|
88
|
+
beforeAll(async () => {
|
|
89
|
+
await removeDbFiles();
|
|
90
|
+
initDb(TEST_DB_PATH);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterAll(async () => {
|
|
94
|
+
await stopExtensionRuntime();
|
|
95
|
+
closeDb();
|
|
96
|
+
await removeDbFiles();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
beforeEach(async () => {
|
|
100
|
+
await stopExtensionRuntime();
|
|
101
|
+
const client = getDbClient();
|
|
102
|
+
await client.run("DELETE FROM agent_tasks");
|
|
103
|
+
await client.run("DELETE FROM extensions");
|
|
104
|
+
await client.run("DELETE FROM kv_entries");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("valid modified arguments reach the real store-progress tool", async () => {
|
|
108
|
+
const extension = await enableFixture("rewrite-progress-prefix", { prefix: "reviewed: " });
|
|
109
|
+
const { agent, task } = await createProgressTarget();
|
|
110
|
+
const server = new McpServer({ name: "tool-hook-rewrite", version: "1.0.0" });
|
|
111
|
+
registerStoreProgressTool(server);
|
|
112
|
+
|
|
113
|
+
const result = await registeredTool(server, "store-progress").handler(
|
|
114
|
+
{ taskId: task.id, progress: "ready" },
|
|
115
|
+
meta(agent.id),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(result.isError).toBe(false);
|
|
119
|
+
expect((await getTaskById(task.id))?.progress).toBe("reviewed: ready");
|
|
120
|
+
expect(await listExtensionRuns(extension.id)).toEqual(
|
|
121
|
+
expect.arrayContaining([
|
|
122
|
+
expect.objectContaining({ event: "pre.tool.call", action: "modify" }),
|
|
123
|
+
]),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("invalid modified arguments fail open and create an error run", async () => {
|
|
128
|
+
const extension = await enableFixture("rewrite-progress-prefix", {
|
|
129
|
+
prefix: "unused: ",
|
|
130
|
+
invalid: true,
|
|
131
|
+
});
|
|
132
|
+
const { agent, task } = await createProgressTarget();
|
|
133
|
+
const server = new McpServer({ name: "tool-hook-invalid", version: "1.0.0" });
|
|
134
|
+
registerStoreProgressTool(server);
|
|
135
|
+
const warning = spyOn(console, "warn").mockImplementation(() => {});
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const result = await registeredTool(server, "store-progress").handler(
|
|
139
|
+
{ taskId: task.id, progress: "original" },
|
|
140
|
+
meta(agent.id),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(result.isError).toBe(false);
|
|
144
|
+
expect((await getTaskById(task.id))?.progress).toBe("original");
|
|
145
|
+
expect(warning).toHaveBeenCalledWith(
|
|
146
|
+
expect.stringContaining("Ignored invalid arguments from pre.tool.call"),
|
|
147
|
+
);
|
|
148
|
+
expect(await listExtensionRuns(extension.id)).toEqual(
|
|
149
|
+
expect.arrayContaining([
|
|
150
|
+
expect.objectContaining({ event: "pre.tool.call", action: "error" }),
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
} finally {
|
|
154
|
+
warning.mockRestore();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("a compliant call dispatches post.tool.call after finalization", async () => {
|
|
159
|
+
const extension = await enableFixture("no-exclamation-marks");
|
|
160
|
+
const server = new McpServer({ name: "tool-hook-post", version: "1.0.0" });
|
|
161
|
+
let callbackFinished = false;
|
|
162
|
+
createToolRegistrar(server)(
|
|
163
|
+
"store-progress",
|
|
164
|
+
{
|
|
165
|
+
inputSchema: z.object({ progress: z.string() }),
|
|
166
|
+
outputSchema: swarmToolOutputSchema(),
|
|
167
|
+
},
|
|
168
|
+
async () => {
|
|
169
|
+
await Bun.sleep(5);
|
|
170
|
+
callbackFinished = true;
|
|
171
|
+
return toolOk("Progress accepted.");
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const result = await registeredTool(server, "store-progress").handler(
|
|
176
|
+
{ progress: "done." },
|
|
177
|
+
meta(crypto.randomUUID()),
|
|
178
|
+
);
|
|
179
|
+
expect(result.isError).toBe(false);
|
|
180
|
+
expect(callbackFinished).toBe(true);
|
|
181
|
+
|
|
182
|
+
await waitFor(async () =>
|
|
183
|
+
(await listExtensionRuns(extension.id)).some((run) => run.event === "post.tool.call"),
|
|
184
|
+
);
|
|
185
|
+
const postRun = (await listExtensionRuns(extension.id)).find(
|
|
186
|
+
(run) => run.event === "post.tool.call",
|
|
187
|
+
);
|
|
188
|
+
expect(postRun).toMatchObject({ action: "continue" });
|
|
189
|
+
expect(postRun?.durationMs).toBeNumber();
|
|
190
|
+
const state = await getKv(
|
|
191
|
+
`task:agent:${extension.agentId}`,
|
|
192
|
+
"ext:no-exclamation-marks:last-post",
|
|
193
|
+
);
|
|
194
|
+
expect(state).not.toBeNull();
|
|
195
|
+
const postPayload = state?.value as {
|
|
196
|
+
tool: string;
|
|
197
|
+
args: unknown;
|
|
198
|
+
result: unknown;
|
|
199
|
+
durationMs: number;
|
|
200
|
+
};
|
|
201
|
+
expect(typeof postPayload.durationMs).toBe("number");
|
|
202
|
+
expect(postPayload.durationMs).toBeGreaterThanOrEqual(5);
|
|
203
|
+
expect(postPayload).toMatchObject({
|
|
204
|
+
tool: "store-progress",
|
|
205
|
+
args: { progress: "done." },
|
|
206
|
+
result: { ok: true, message: "Progress accepted." },
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("script SDK and extension origins bypass both hooks", async () => {
|
|
211
|
+
const extension = await enableFixture("no-exclamation-marks");
|
|
212
|
+
const server = new McpServer({ name: "tool-hook-origin", version: "1.0.0" });
|
|
213
|
+
createToolRegistrar(server)(
|
|
214
|
+
"store-progress",
|
|
215
|
+
{
|
|
216
|
+
inputSchema: z.object({ progress: z.string() }),
|
|
217
|
+
outputSchema: swarmToolOutputSchema(),
|
|
218
|
+
},
|
|
219
|
+
async () => toolOk("Progress accepted."),
|
|
220
|
+
);
|
|
221
|
+
const tool = registeredTool(server, "store-progress");
|
|
222
|
+
|
|
223
|
+
const scriptResult = await tool.handler(
|
|
224
|
+
{ progress: "script!" },
|
|
225
|
+
markScriptSdkRequestOrigin(meta(crypto.randomUUID())),
|
|
226
|
+
);
|
|
227
|
+
const extensionResult = await tool.handler(
|
|
228
|
+
{ progress: "extension!" },
|
|
229
|
+
markExtensionRequestOrigin(meta(extension.agentId!)),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(scriptResult.isError).toBe(false);
|
|
233
|
+
expect(extensionResult.isError).toBe(false);
|
|
234
|
+
await Bun.sleep(20);
|
|
235
|
+
expect(await listExtensionRuns(extension.id)).toEqual([]);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("the bridge grants the extension origin only with the per-process token", async () => {
|
|
239
|
+
const extension = await enableFixture("no-exclamation-marks");
|
|
240
|
+
const agentId = extension.agentId!;
|
|
241
|
+
// A caller-controlled agent header alone must not claim the extension origin.
|
|
242
|
+
expect(resolveBridgeCallOrigin(agentId, undefined)).toBe("script-sdk");
|
|
243
|
+
expect(resolveBridgeCallOrigin(agentId, "forged")).toBe("script-sdk");
|
|
244
|
+
expect(resolveBridgeCallOrigin(crypto.randomUUID(), getExtensionBridgeToken())).toBe(
|
|
245
|
+
"script-sdk",
|
|
246
|
+
);
|
|
247
|
+
expect(resolveBridgeCallOrigin(agentId, getExtensionBridgeToken())).toBe("extension");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("the no-input branch ignores modified arguments and still runs post hooks", async () => {
|
|
251
|
+
const rewritingExtension = await enableFixture("rewrite-progress-prefix", {
|
|
252
|
+
prefix: "ignored",
|
|
253
|
+
tool: "no-input",
|
|
254
|
+
});
|
|
255
|
+
const observingExtension = await enableFixture("no-exclamation-marks");
|
|
256
|
+
const server = new McpServer({ name: "tool-hook-no-input", version: "1.0.0" });
|
|
257
|
+
let callbackCalls = 0;
|
|
258
|
+
createToolRegistrar(server)("no-input", { outputSchema: swarmToolOutputSchema() }, async () => {
|
|
259
|
+
callbackCalls += 1;
|
|
260
|
+
return toolOk("No input accepted.");
|
|
261
|
+
});
|
|
262
|
+
const warning = spyOn(console, "warn").mockImplementation(() => {});
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = await registeredTool(server, "no-input").handler(meta(crypto.randomUUID()));
|
|
266
|
+
|
|
267
|
+
expect(result.isError).toBe(false);
|
|
268
|
+
expect(callbackCalls).toBe(1);
|
|
269
|
+
expect(warning).toHaveBeenCalledWith(
|
|
270
|
+
expect.stringContaining(
|
|
271
|
+
'tool "no-input" from extension "rewrite-progress-prefix" because the tool has no input schema',
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
expect(await listExtensionRuns(rewritingExtension.id)).toEqual(
|
|
275
|
+
expect.arrayContaining([
|
|
276
|
+
expect.objectContaining({ event: "pre.tool.call", action: "error" }),
|
|
277
|
+
]),
|
|
278
|
+
);
|
|
279
|
+
await waitFor(async () =>
|
|
280
|
+
(await listExtensionRuns(observingExtension.id)).some(
|
|
281
|
+
(run) => run.event === "post.tool.call" && run.action === "continue",
|
|
282
|
+
),
|
|
283
|
+
);
|
|
284
|
+
} finally {
|
|
285
|
+
warning.mockRestore();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateBundle } from "../be/extensions/validate";
|
|
3
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
4
|
+
|
|
5
|
+
describe("extension bundle validation", () => {
|
|
6
|
+
test.each(["../escape.ts", "/abs.ts"])("rejects unsafe hooks path %s", async (path) => {
|
|
7
|
+
const bundle = await loadBundleFixture("minimal");
|
|
8
|
+
bundle.manifest.assets.hooks = path;
|
|
9
|
+
const result = await validateBundle(bundle);
|
|
10
|
+
expect(result.ok).toBe(false);
|
|
11
|
+
if (!result.ok) expect(result.diagnostics.join("\n")).toContain("relative POSIX file path");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("reports each unsafe file key", async () => {
|
|
15
|
+
const bundle = await loadBundleFixture("minimal");
|
|
16
|
+
bundle.files["../escape.ts"] = "";
|
|
17
|
+
bundle.files["nested/../escape.ts"] = "";
|
|
18
|
+
const result = await validateBundle(bundle);
|
|
19
|
+
expect(result).toEqual({
|
|
20
|
+
ok: false,
|
|
21
|
+
diagnostics: [
|
|
22
|
+
'Unsafe bundle file path: "../escape.ts"',
|
|
23
|
+
'Unsafe bundle file path: "nested/../escape.ts"',
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
test("accepts the minimal API extension", async () => {
|
|
28
|
+
const bundle = await loadBundleFixture("minimal");
|
|
29
|
+
expect(await validateBundle(bundle)).toEqual({ ok: true, manifest: bundle.manifest });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("accepts zod and stdlib imports", async () => {
|
|
33
|
+
const bundle = await loadBundleFixture("minimal");
|
|
34
|
+
bundle.files["hooks.ts"] = `import { table } from "stdlib";
|
|
35
|
+
import type { SwarmExtension } from "swarm-extension";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
export const config = z.object({ label: z.string() });
|
|
38
|
+
const extension: SwarmExtension = () => { void table([{ ok: true }]); };
|
|
39
|
+
export default extension;
|
|
40
|
+
`;
|
|
41
|
+
expect(await validateBundle(bundle)).toEqual({ ok: true, manifest: bundle.manifest });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("infers typed config from the extension manifest", async () => {
|
|
45
|
+
const bundle = await loadBundleFixture("minimal");
|
|
46
|
+
bundle.files["hooks.ts"] = `import type { SwarmExtension } from "swarm-extension";
|
|
47
|
+
import { z } from "zod";
|
|
48
|
+
export const config = z.object({ channelId: z.string(), agentId: z.string() });
|
|
49
|
+
const manifest = {
|
|
50
|
+
name: "minimal",
|
|
51
|
+
description: "Minimal extension",
|
|
52
|
+
version: "1.0.0",
|
|
53
|
+
runtime: "api",
|
|
54
|
+
assets: { hooks: "hooks.ts" },
|
|
55
|
+
config,
|
|
56
|
+
} as const;
|
|
57
|
+
const extension: SwarmExtension<typeof manifest> = (api) => {
|
|
58
|
+
api.on("pre.slack.route", (_event, ctx) => {
|
|
59
|
+
const channelId: string = ctx.config.channelId;
|
|
60
|
+
// @ts-expect-error channelId is inferred as a string.
|
|
61
|
+
const invalid: number = ctx.config.channelId;
|
|
62
|
+
void channelId;
|
|
63
|
+
void invalid;
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
export default extension;
|
|
67
|
+
`;
|
|
68
|
+
expect(await validateBundle(bundle)).toEqual({ ok: true, manifest: bundle.manifest });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("bare SwarmExtension uses API context", async () => {
|
|
72
|
+
const valid = await loadBundleFixture("minimal");
|
|
73
|
+
valid.files["hooks.ts"] = `import type { SwarmExtension } from "swarm-extension";
|
|
74
|
+
const extension: SwarmExtension = (api) => {
|
|
75
|
+
api.on("post.task.created", (_event, ctx) => { void ctx.swarm; });
|
|
76
|
+
};
|
|
77
|
+
export default extension;
|
|
78
|
+
`;
|
|
79
|
+
expect(await validateBundle(valid)).toEqual({ ok: true, manifest: valid.manifest });
|
|
80
|
+
|
|
81
|
+
const invalid = await loadBundleFixture("minimal");
|
|
82
|
+
invalid.files["hooks.ts"] = `import type { SwarmExtension } from "swarm-extension";
|
|
83
|
+
const extension: SwarmExtension = (api) => {
|
|
84
|
+
api.on("post.task.created", (_event, ctx) => { void ctx.worker; });
|
|
85
|
+
};
|
|
86
|
+
export default extension;
|
|
87
|
+
`;
|
|
88
|
+
const result = await validateBundle(invalid);
|
|
89
|
+
expect(result.ok).toBe(false);
|
|
90
|
+
if (!result.ok) expect(result.diagnostics.join("\n")).toContain("worker");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("explicit worker extensions keep worker context typing", async () => {
|
|
94
|
+
const bundle = await loadBundleFixture("minimal");
|
|
95
|
+
bundle.files["hooks.ts"] = `import type { SwarmExtension } from "swarm-extension";
|
|
96
|
+
type WorkerManifest = {
|
|
97
|
+
name: "worker";
|
|
98
|
+
description: "worker";
|
|
99
|
+
version: "1.0.0";
|
|
100
|
+
runtime: "worker";
|
|
101
|
+
assets: { hooks: "hooks.ts" };
|
|
102
|
+
};
|
|
103
|
+
const workerExtension: SwarmExtension<WorkerManifest> = (api) => {
|
|
104
|
+
api.on("post.task.created", (_event, ctx) => { const id: string = ctx.worker.agentId; void id; });
|
|
105
|
+
};
|
|
106
|
+
const extension: SwarmExtension = () => { void workerExtension; };
|
|
107
|
+
export default extension;
|
|
108
|
+
`;
|
|
109
|
+
expect(await validateBundle(bundle)).toEqual({ ok: true, manifest: bundle.manifest });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("rejects forbidden bare and relative imports", async () => {
|
|
113
|
+
const badImport = await validateBundle(await loadBundleFixture("bad-import"));
|
|
114
|
+
expect(badImport.ok).toBe(false);
|
|
115
|
+
if (!badImport.ok) expect(badImport.diagnostics.join("\n")).toContain("node:fs");
|
|
116
|
+
|
|
117
|
+
const relative = await loadBundleFixture("minimal");
|
|
118
|
+
relative.files["hooks.ts"] = `import "./helper";\n${relative.files["hooks.ts"]}`;
|
|
119
|
+
const relativeResult = await validateBundle(relative);
|
|
120
|
+
expect(relativeResult.ok).toBe(false);
|
|
121
|
+
if (!relativeResult.ok) {
|
|
122
|
+
expect(relativeResult.diagnostics.join("\n")).toContain("Relative imports are not allowed");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("rejects computed imports, require, and import-equals", async () => {
|
|
127
|
+
const cases = [
|
|
128
|
+
['const name = "zod"; void import(name);', "Computed import()"],
|
|
129
|
+
['const name = "zod"; void require(name);', "Computed require()"],
|
|
130
|
+
['import fs = require("node:fs"); void fs;', "node:fs"],
|
|
131
|
+
] as const;
|
|
132
|
+
|
|
133
|
+
for (const [prefix, diagnostic] of cases) {
|
|
134
|
+
const bundle = await loadBundleFixture("minimal");
|
|
135
|
+
bundle.files["hooks.ts"] = `${prefix}\n${bundle.files["hooks.ts"]}`;
|
|
136
|
+
const result = await validateBundle(bundle);
|
|
137
|
+
expect(result.ok).toBe(false);
|
|
138
|
+
if (!result.ok) expect(result.diagnostics.join("\n")).toContain(diagnostic);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("rejects worker runtime and reserved assets", async () => {
|
|
143
|
+
const worker = await validateBundle(await loadBundleFixture("worker-runtime"));
|
|
144
|
+
expect(worker.ok).toBe(false);
|
|
145
|
+
if (!worker.ok) expect(worker.diagnostics).toContain('runtime "worker" is not supported in v1');
|
|
146
|
+
|
|
147
|
+
const reserved = await validateBundle(await loadBundleFixture("reserved-assets"));
|
|
148
|
+
expect(reserved.ok).toBe(false);
|
|
149
|
+
if (!reserved.ok)
|
|
150
|
+
expect(reserved.diagnostics).toContain("assets.skills is not supported in v1");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("rejects extra files and a missing hooks file", async () => {
|
|
154
|
+
const extra = await loadBundleFixture("minimal");
|
|
155
|
+
extra.files["helper.ts"] = "export const helper = true;";
|
|
156
|
+
const extraResult = await validateBundle(extra);
|
|
157
|
+
expect(extraResult.ok).toBe(false);
|
|
158
|
+
if (!extraResult.ok) expect(extraResult.diagnostics.join("\n")).toContain("helper.ts");
|
|
159
|
+
|
|
160
|
+
const missing = await loadBundleFixture("minimal");
|
|
161
|
+
missing.manifest.assets.hooks = "missing.ts";
|
|
162
|
+
const missingResult = await validateBundle(missing);
|
|
163
|
+
expect(missingResult.ok).toBe(false);
|
|
164
|
+
if (!missingResult.ok) expect(missingResult.diagnostics.join("\n")).toContain("missing.ts");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("rejects an invalid modify result", async () => {
|
|
168
|
+
const result = await validateBundle(await loadBundleFixture("bad-return-shape"));
|
|
169
|
+
expect(result.ok).toBe(false);
|
|
170
|
+
if (!result.ok) {
|
|
171
|
+
expect(result.diagnostics.join("\n")).toContain("agentId");
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("rejects derived task fields in a modify result", async () => {
|
|
176
|
+
const bundle = await loadBundleFixture("minimal");
|
|
177
|
+
bundle.files["hooks.ts"] = `import type { SwarmExtension } from "swarm-extension";
|
|
178
|
+
const extension: SwarmExtension = (api) => {
|
|
179
|
+
api.on("pre.task.create", () => ({ action: "modify", data: { status: "draft" } }));
|
|
180
|
+
};
|
|
181
|
+
export default extension;
|
|
182
|
+
`;
|
|
183
|
+
const result = await validateBundle(bundle);
|
|
184
|
+
expect(result.ok).toBe(false);
|
|
185
|
+
if (!result.ok) expect(result.diagnostics.join("\n")).toContain("status");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("rejects a non-Zod config export", async () => {
|
|
189
|
+
const bundle = await loadBundleFixture("minimal");
|
|
190
|
+
bundle.files["hooks.ts"] = `${bundle.files["hooks.ts"]}\nexport const config = {};\n`;
|
|
191
|
+
const result = await validateBundle(bundle);
|
|
192
|
+
expect(result.ok).toBe(false);
|
|
193
|
+
if (!result.ok)
|
|
194
|
+
expect(result.diagnostics.join("\n")).toContain("not assignable to type 'false'");
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { block, type SwarmExtension } from "swarm-extension";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const config = z.object({ source: z.string() });
|
|
5
|
+
const manifest = {
|
|
6
|
+
name: "block-tasks-from-source",
|
|
7
|
+
description: "Blocks tasks from a configured source or origin",
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
runtime: "api",
|
|
10
|
+
assets: { hooks: "hooks.ts" },
|
|
11
|
+
config,
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
const extension: SwarmExtension<typeof manifest> = (api) => {
|
|
15
|
+
api.on("pre.task.create", (event, ctx) => {
|
|
16
|
+
if (event.origin === ctx.config.source || event.options.source === ctx.config.source) {
|
|
17
|
+
return block(`Task source ${ctx.config.source} is blocked`);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default extension;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SwarmExtension } from "swarm-extension";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const config = z.object({ channelId: z.string() });
|
|
5
|
+
|
|
6
|
+
const manifest = {
|
|
7
|
+
name: "ignore-channel",
|
|
8
|
+
description: "Ignores Slack messages in one configured channel",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
runtime: "api",
|
|
11
|
+
assets: { hooks: "hooks.ts" },
|
|
12
|
+
config,
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
const extension: SwarmExtension<typeof manifest> = (api) => {
|
|
16
|
+
api.on("pre.slack.route", (event, ctx) => {
|
|
17
|
+
if (event.channelId !== ctx.config.channelId) return;
|
|
18
|
+
return { action: "block", reason: "This channel is ignored" };
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default extension;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ExtensionInstallBody } from "../../../types";
|
|
2
|
+
|
|
3
|
+
export async function loadBundleFixture(name: string): Promise<ExtensionInstallBody> {
|
|
4
|
+
const directory = new URL(`./${name}/`, import.meta.url);
|
|
5
|
+
const manifest = (await Bun.file(
|
|
6
|
+
new URL("manifest.json", directory),
|
|
7
|
+
).json()) as ExtensionInstallBody["manifest"];
|
|
8
|
+
const hooks = await Bun.file(new URL(manifest.assets.hooks, directory)).text();
|
|
9
|
+
return { manifest, files: { [manifest.assets.hooks]: hooks } };
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { modify, type SwarmExtension } from "swarm-extension";
|
|
2
|
+
|
|
3
|
+
const extension: SwarmExtension = (api) => {
|
|
4
|
+
api.on("pre.heartbeat.remediate", (event) => {
|
|
5
|
+
if (event.proposedAction === "fail") {
|
|
6
|
+
return modify({ proposedAction: "record" });
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default extension;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { block, type SwarmExtension } from "swarm-extension";
|
|
2
|
+
|
|
3
|
+
const extension: SwarmExtension = (api) => {
|
|
4
|
+
api.on("pre.tool.call", (event) => {
|
|
5
|
+
if (event.tool !== "store-progress") return;
|
|
6
|
+
if (!event.args || typeof event.args !== "object") return;
|
|
7
|
+
const progress = (event.args as { progress?: unknown }).progress;
|
|
8
|
+
if (typeof progress === "string" && progress.includes("!")) {
|
|
9
|
+
return block("Progress text must not contain exclamation marks.");
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
api.on("post.tool.call", async (event, ctx) => {
|
|
14
|
+
await ctx.state.set("last-post", {
|
|
15
|
+
tool: event.tool,
|
|
16
|
+
args: event.args,
|
|
17
|
+
result: event.result,
|
|
18
|
+
durationMs: event.durationMs,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default extension;
|