@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,252 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
|
|
2
|
+
import type { SlackEventMiddlewareArgs } from "@slack/bolt";
|
|
3
|
+
import type { EntityPresentDetailsArguments } from "@slack/web-api";
|
|
4
|
+
import * as db from "../be/db";
|
|
5
|
+
import { AgentTaskSchema } from "../types";
|
|
6
|
+
import * as handlers from "./handlers";
|
|
7
|
+
import { registerWorkObjectHandlers } from "./work-objects";
|
|
8
|
+
|
|
9
|
+
const taskId = "11111111-2222-4333-8444-555555555555";
|
|
10
|
+
const task = AgentTaskSchema.parse({
|
|
11
|
+
id: taskId,
|
|
12
|
+
key: `shared/task:${taskId}/`,
|
|
13
|
+
agentId: null,
|
|
14
|
+
task: "Implement Work Object details",
|
|
15
|
+
title: "Slack flexpane support",
|
|
16
|
+
status: "completed",
|
|
17
|
+
output: "Implemented and tested.",
|
|
18
|
+
createdAt: "2026-09-15T09:00:00.000Z",
|
|
19
|
+
lastUpdatedAt: "2026-09-15T10:00:00.000Z",
|
|
20
|
+
slackChannelId: "C_TASK",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
type DetailsEvent = SlackEventMiddlewareArgs<"entity_details_requested">["event"];
|
|
24
|
+
const event: DetailsEvent = {
|
|
25
|
+
type: "entity_details_requested",
|
|
26
|
+
user: "U_ALLOWED",
|
|
27
|
+
trigger_id: "trigger-123",
|
|
28
|
+
external_ref: { type: "task", id: taskId },
|
|
29
|
+
entity_url: `https://swarm.example/tasks/${taskId}`,
|
|
30
|
+
link: { url: `https://swarm.example/tasks/${taskId}`, domain: "swarm.example" },
|
|
31
|
+
user_locale: "en-US",
|
|
32
|
+
event_ts: "1757923200.000001",
|
|
33
|
+
channel: "C_TASK",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe("Work Object details handler", () => {
|
|
37
|
+
const originalFlag = process.env.SLACK_WORK_OBJECTS_ENABLED;
|
|
38
|
+
const originalAppUrl = process.env.APP_URL;
|
|
39
|
+
const presentDetails = mock(async (_args: EntityPresentDetailsArguments) => ({ ok: true }));
|
|
40
|
+
const client = { entity: { presentDetails } };
|
|
41
|
+
const logger = { error: mock(() => {}) };
|
|
42
|
+
let listener: (args: {
|
|
43
|
+
event: DetailsEvent;
|
|
44
|
+
client: typeof client;
|
|
45
|
+
logger: typeof logger;
|
|
46
|
+
}) => Promise<void>;
|
|
47
|
+
let getTask: ReturnType<typeof spyOn<typeof db, "getTaskById">>;
|
|
48
|
+
let getAgent: ReturnType<typeof spyOn<typeof db, "getAgentById">>;
|
|
49
|
+
let userAllowed: ReturnType<typeof spyOn<typeof handlers, "isUserAllowed">>;
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
process.env.SLACK_WORK_OBJECTS_ENABLED = "true";
|
|
53
|
+
process.env.APP_URL = "https://swarm.example";
|
|
54
|
+
getTask = spyOn(db, "getTaskById").mockResolvedValue(task);
|
|
55
|
+
getAgent = spyOn(db, "getAgentById").mockResolvedValue(null);
|
|
56
|
+
userAllowed = spyOn(handlers, "isUserAllowed").mockResolvedValue(true);
|
|
57
|
+
presentDetails.mockReset();
|
|
58
|
+
presentDetails.mockResolvedValue({ ok: true });
|
|
59
|
+
logger.error.mockClear();
|
|
60
|
+
registerWorkObjectHandlers({
|
|
61
|
+
event: (name: string, callback: typeof listener) => {
|
|
62
|
+
expect(name).toBe("entity_details_requested");
|
|
63
|
+
listener = callback;
|
|
64
|
+
},
|
|
65
|
+
} as never);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
getTask.mockRestore();
|
|
70
|
+
getAgent.mockRestore();
|
|
71
|
+
userAllowed.mockRestore();
|
|
72
|
+
if (originalFlag === undefined) delete process.env.SLACK_WORK_OBJECTS_ENABLED;
|
|
73
|
+
else process.env.SLACK_WORK_OBJECTS_ENABLED = originalFlag;
|
|
74
|
+
if (originalAppUrl === undefined) delete process.env.APP_URL;
|
|
75
|
+
else process.env.APP_URL = originalAppUrl;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
async function request(overrides: Partial<DetailsEvent> = {}) {
|
|
79
|
+
await listener({ event: { ...event, ...overrides }, client, logger });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const flag of [undefined, "false", "0", "typo"]) {
|
|
83
|
+
test(`does no work when flag is ${flag}`, async () => {
|
|
84
|
+
if (flag === undefined) delete process.env.SLACK_WORK_OBJECTS_ENABLED;
|
|
85
|
+
else process.env.SLACK_WORK_OBJECTS_ENABLED = flag;
|
|
86
|
+
await request();
|
|
87
|
+
expect(getTask).not.toHaveBeenCalled();
|
|
88
|
+
expect(userAllowed).not.toHaveBeenCalled();
|
|
89
|
+
expect(presentDetails).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
test("responds with current task data and the requesting trigger", async () => {
|
|
94
|
+
await request();
|
|
95
|
+
expect(presentDetails).toHaveBeenCalledTimes(1);
|
|
96
|
+
const response = presentDetails.mock.calls[0]?.[0];
|
|
97
|
+
expect(response?.trigger_id).toBe(event.trigger_id);
|
|
98
|
+
expect(response?.metadata).toMatchObject({
|
|
99
|
+
entity_type: "slack#/entities/task",
|
|
100
|
+
external_ref: event.external_ref,
|
|
101
|
+
url: event.entity_url,
|
|
102
|
+
entity_payload: {
|
|
103
|
+
attributes: { title: { text: task.title }, product_name: "Agent Swarm" },
|
|
104
|
+
fields: {
|
|
105
|
+
description: { value: task.task },
|
|
106
|
+
status: { value: "completed" },
|
|
107
|
+
assignee: { value: "Unassigned" },
|
|
108
|
+
},
|
|
109
|
+
custom_fields: [{ key: "result", label: "Result", value: task.output }],
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
expect(response?.metadata).not.toHaveProperty("entities");
|
|
113
|
+
expect(response?.metadata).not.toHaveProperty("app_unfurl_url");
|
|
114
|
+
expect(response).not.toHaveProperty("error");
|
|
115
|
+
expect(userAllowed).toHaveBeenCalledWith(client, event.user);
|
|
116
|
+
expect(getTask).toHaveBeenCalledWith(taskId);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("reloads the task for each refresh and accepts 1 as the flag", async () => {
|
|
120
|
+
process.env.SLACK_WORK_OBJECTS_ENABLED = "1";
|
|
121
|
+
await request();
|
|
122
|
+
getTask.mockResolvedValue({
|
|
123
|
+
...task,
|
|
124
|
+
status: "in_progress",
|
|
125
|
+
output: undefined,
|
|
126
|
+
progress: "Testing",
|
|
127
|
+
});
|
|
128
|
+
await request({ trigger_id: "refresh-trigger", app_unfurl_url: event.entity_url });
|
|
129
|
+
expect(getTask).toHaveBeenCalledTimes(2);
|
|
130
|
+
expect(presentDetails.mock.calls[1]?.[0]).toMatchObject({
|
|
131
|
+
trigger_id: "refresh-trigger",
|
|
132
|
+
metadata: {
|
|
133
|
+
entity_payload: {
|
|
134
|
+
fields: { status: { value: "in_progress" } },
|
|
135
|
+
custom_fields: [{ label: "Progress", value: "Testing" }],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("shows failures and bounds long task text", async () => {
|
|
142
|
+
getTask.mockResolvedValue({
|
|
143
|
+
...task,
|
|
144
|
+
title: undefined,
|
|
145
|
+
task: "x".repeat(10000),
|
|
146
|
+
status: "failed",
|
|
147
|
+
output: undefined,
|
|
148
|
+
failureReason: "y".repeat(10000),
|
|
149
|
+
});
|
|
150
|
+
await request();
|
|
151
|
+
const payload = presentDetails.mock.calls[0]?.[0].metadata?.entity_payload;
|
|
152
|
+
expect(payload?.attributes.title.text.length).toBe(200);
|
|
153
|
+
expect(payload?.fields).toMatchObject({ description: { value: `${"x".repeat(2799)}…` } });
|
|
154
|
+
expect(payload?.custom_fields?.[0]).toMatchObject({
|
|
155
|
+
label: "Failure",
|
|
156
|
+
value: `${"y".repeat(2799)}…`,
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
for (const type of ["file", "item", "content_item", "unknown", undefined]) {
|
|
161
|
+
test(`presents an error for unsupported reference type ${type}`, async () => {
|
|
162
|
+
await request({ external_ref: { id: taskId, type } });
|
|
163
|
+
expect(presentDetails).toHaveBeenCalledWith({
|
|
164
|
+
trigger_id: event.trigger_id,
|
|
165
|
+
error: {
|
|
166
|
+
status: "custom",
|
|
167
|
+
custom_title: "Work Object unavailable",
|
|
168
|
+
custom_message: "Agent Swarm does not support this Work Object reference.",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
expect(getTask).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
test("presents an error when external_ref is absent or empty", async () => {
|
|
176
|
+
await request({ external_ref: undefined });
|
|
177
|
+
await request({ external_ref: { type: "task", id: "" } });
|
|
178
|
+
expect(presentDetails).toHaveBeenCalledTimes(2);
|
|
179
|
+
expect(getTask).not.toHaveBeenCalled();
|
|
180
|
+
for (const [response] of presentDetails.mock.calls)
|
|
181
|
+
expect(response.error?.status).toBe("custom");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("presents not_found when the task was deleted", async () => {
|
|
185
|
+
getTask.mockResolvedValue(null);
|
|
186
|
+
await request();
|
|
187
|
+
expect(presentDetails).toHaveBeenCalledWith({
|
|
188
|
+
trigger_id: event.trigger_id,
|
|
189
|
+
error: { status: "not_found" },
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("denies filtered users before reading the task", async () => {
|
|
194
|
+
userAllowed.mockResolvedValue(false);
|
|
195
|
+
await request();
|
|
196
|
+
expect(getTask).not.toHaveBeenCalled();
|
|
197
|
+
expect(presentDetails).toHaveBeenCalledWith({
|
|
198
|
+
trigger_id: event.trigger_id,
|
|
199
|
+
error: { status: "restricted" },
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
for (const channel of [undefined, "C_OTHER"]) {
|
|
204
|
+
test(`does not disclose task details to channel ${channel}`, async () => {
|
|
205
|
+
await request({ channel });
|
|
206
|
+
expect(presentDetails).toHaveBeenCalledWith({
|
|
207
|
+
trigger_id: event.trigger_id,
|
|
208
|
+
error: { status: "restricted" },
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
test("does not disclose tasks without a Slack channel", async () => {
|
|
214
|
+
getTask.mockResolvedValue({ ...task, slackChannelId: undefined });
|
|
215
|
+
await request();
|
|
216
|
+
expect(presentDetails.mock.calls[0]?.[0].error?.status).toBe("restricted");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("returns an error view when a task lookup fails", async () => {
|
|
220
|
+
getTask.mockRejectedValue(new Error("database unavailable"));
|
|
221
|
+
await request();
|
|
222
|
+
expect(presentDetails).toHaveBeenCalledWith({
|
|
223
|
+
trigger_id: event.trigger_id,
|
|
224
|
+
error: { status: "internal_error" },
|
|
225
|
+
});
|
|
226
|
+
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("falls back to an error view when Slack rejects the metadata", async () => {
|
|
230
|
+
presentDetails.mockRejectedValueOnce(new Error("invalid_metadata"));
|
|
231
|
+
await request();
|
|
232
|
+
expect(presentDetails).toHaveBeenCalledTimes(2);
|
|
233
|
+
expect(presentDetails.mock.calls[1]?.[0]).toEqual({
|
|
234
|
+
trigger_id: event.trigger_id,
|
|
235
|
+
error: { status: "internal_error" },
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("logs a failed fallback without an unhandled rejection or retry loop", async () => {
|
|
240
|
+
presentDetails.mockRejectedValue(new Error("Slack unavailable"));
|
|
241
|
+
await request();
|
|
242
|
+
expect(presentDetails).toHaveBeenCalledTimes(2);
|
|
243
|
+
expect(logger.error).toHaveBeenCalledTimes(2);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("logs rejection of an unknown object's error response", async () => {
|
|
247
|
+
presentDetails.mockRejectedValue(new Error("Slack unavailable"));
|
|
248
|
+
await request({ external_ref: undefined });
|
|
249
|
+
expect(presentDetails).toHaveBeenCalledTimes(1);
|
|
250
|
+
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { App, SlackEventMiddlewareArgs } from "@slack/bolt";
|
|
2
|
+
import type { EntityPresentDetailsArguments, WebClient } from "@slack/web-api";
|
|
3
|
+
import { getAgentById, getTaskById } from "../be/db";
|
|
4
|
+
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
5
|
+
import { getTaskUrl } from "./blocks";
|
|
6
|
+
import { isUserAllowed } from "./handlers";
|
|
7
|
+
|
|
8
|
+
type DetailsEvent = SlackEventMiddlewareArgs<"entity_details_requested">["event"];
|
|
9
|
+
type Details = Omit<EntityPresentDetailsArguments, "trigger_id" | "token">;
|
|
10
|
+
|
|
11
|
+
function truncate(text: string, limit: number): string {
|
|
12
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function getDetails(event: DetailsEvent, client: WebClient): Promise<Details> {
|
|
16
|
+
const ref = event.external_ref;
|
|
17
|
+
// external_ref.type is our resource type, not Slack's entity_type. Never fetch event URLs.
|
|
18
|
+
if (ref?.type !== "task" || !ref.id) {
|
|
19
|
+
return {
|
|
20
|
+
error: {
|
|
21
|
+
status: "custom",
|
|
22
|
+
custom_title: "Work Object unavailable",
|
|
23
|
+
custom_message: "Agent Swarm does not support this Work Object reference.",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!event.user || !event.channel || !(await isUserAllowed(client, event.user))) {
|
|
29
|
+
return { error: { status: "restricted" } };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const task = await getTaskById(ref.id);
|
|
33
|
+
if (!task) return { error: { status: "not_found" } };
|
|
34
|
+
|
|
35
|
+
// A forwarded card must not expose a private task to another conversation.
|
|
36
|
+
if (task.slackChannelId !== event.channel) return { error: { status: "restricted" } };
|
|
37
|
+
|
|
38
|
+
const agent = task.agentId ? await getAgentById(task.agentId) : null;
|
|
39
|
+
const result = task.output || task.failureReason || task.progress;
|
|
40
|
+
return {
|
|
41
|
+
metadata: {
|
|
42
|
+
entity_type: "slack#/entities/task",
|
|
43
|
+
external_ref: ref,
|
|
44
|
+
url: getTaskUrl(task.id),
|
|
45
|
+
entity_payload: {
|
|
46
|
+
attributes: {
|
|
47
|
+
title: { text: truncate(task.title?.trim() || task.task, 200) },
|
|
48
|
+
display_id: task.id.slice(0, 8),
|
|
49
|
+
display_type: "Task",
|
|
50
|
+
product_name: "Agent Swarm",
|
|
51
|
+
metadata_last_modified: Math.floor(Date.parse(task.lastUpdatedAt) / 1000),
|
|
52
|
+
},
|
|
53
|
+
fields: {
|
|
54
|
+
description: { value: truncate(task.task, 2800), format: "markdown" },
|
|
55
|
+
status: { value: task.status },
|
|
56
|
+
assignee: { type: "string", value: agent?.name || task.agentId || "Unassigned" },
|
|
57
|
+
priority: { value: String(task.priority) },
|
|
58
|
+
date_created: { value: Math.floor(Date.parse(task.createdAt) / 1000) },
|
|
59
|
+
date_updated: { value: Math.floor(Date.parse(task.lastUpdatedAt) / 1000) },
|
|
60
|
+
},
|
|
61
|
+
...(result
|
|
62
|
+
? {
|
|
63
|
+
custom_fields: [
|
|
64
|
+
{
|
|
65
|
+
key: "result",
|
|
66
|
+
label: task.output ? "Result" : task.failureReason ? "Failure" : "Progress",
|
|
67
|
+
type: "string",
|
|
68
|
+
value: truncate(result, 2800),
|
|
69
|
+
format: "markdown",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
display_order: [
|
|
75
|
+
"status",
|
|
76
|
+
"assignee",
|
|
77
|
+
"priority",
|
|
78
|
+
"description",
|
|
79
|
+
...(result ? ["result"] : []),
|
|
80
|
+
"date_created",
|
|
81
|
+
"date_updated",
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function registerWorkObjectHandlers(app: App): void {
|
|
89
|
+
// Bolt acknowledges Events API deliveries; the flexpane needs a separate API response.
|
|
90
|
+
app.event("entity_details_requested", async ({ event, client, logger }) => {
|
|
91
|
+
if (!isEnvFlagEnabled("SLACK_WORK_OBJECTS_ENABLED", false)) return;
|
|
92
|
+
|
|
93
|
+
let details: Details;
|
|
94
|
+
try {
|
|
95
|
+
details = await getDetails(event, client);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logger.error("[Slack] Failed to load Work Object details", error);
|
|
98
|
+
details = { error: { status: "internal_error" } };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await client.entity.presentDetails({ trigger_id: event.trigger_id, ...details });
|
|
103
|
+
} catch (error) {
|
|
104
|
+
logger.error("[Slack] Failed to present Work Object details", error);
|
|
105
|
+
// If Slack rejects the metadata, still try to replace the spinner with an error view.
|
|
106
|
+
if (details.metadata) {
|
|
107
|
+
try {
|
|
108
|
+
await client.entity.presentDetails({
|
|
109
|
+
trigger_id: event.trigger_id,
|
|
110
|
+
error: { status: "internal_error" },
|
|
111
|
+
});
|
|
112
|
+
} catch (fallbackError) {
|
|
113
|
+
logger.error("[Slack] Failed to present Work Object error", fallbackError);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
getAgentById,
|
|
18
18
|
getInProgressTasksByContextKey,
|
|
19
19
|
} from "../be/db";
|
|
20
|
+
import { applyPreTaskCreate } from "../extensions/apply-task-create";
|
|
21
|
+
import type { TaskCreateOrigin } from "../extensions/contract";
|
|
20
22
|
import type { AgentTask } from "../types";
|
|
23
|
+
import { TaskCreationBlockedError } from "./errors";
|
|
21
24
|
import {
|
|
22
25
|
pickResumeParent,
|
|
23
26
|
prependSiblingBlock,
|
|
@@ -136,10 +139,41 @@ export async function withSiblingAwareness(
|
|
|
136
139
|
* first. Use this from every ingress that has a `contextKey` so cross-ingress
|
|
137
140
|
* sibling coordination is uniform without duplicating the wrapper boilerplate.
|
|
138
141
|
*/
|
|
142
|
+
/** Final description and options after extension pre-hooks and sibling wiring. */
|
|
143
|
+
export type PreparedTaskCreate = { description: string; options: CreateTaskOptions };
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Run the `pre.task.create` extension hooks and sibling-awareness wiring without
|
|
147
|
+
* inserting the task. Callers that must create the task inside a transaction call
|
|
148
|
+
* this first, outside the transaction, so extension hooks are not skipped by the
|
|
149
|
+
* dispatcher's `isInTransaction()` guard.
|
|
150
|
+
*/
|
|
151
|
+
export async function prepareTaskWithSiblingAwareness(
|
|
152
|
+
description: string,
|
|
153
|
+
options: CreateTaskOptions,
|
|
154
|
+
args: { origin?: TaskCreateOrigin } = {},
|
|
155
|
+
): Promise<PreparedTaskCreate> {
|
|
156
|
+
const preCreate = await applyPreTaskCreate({
|
|
157
|
+
description,
|
|
158
|
+
options,
|
|
159
|
+
// Preserve the plan's REST default for callers without an explicit ingress origin.
|
|
160
|
+
origin: args.origin ?? "rest",
|
|
161
|
+
});
|
|
162
|
+
if (preCreate.kind === "blocked") {
|
|
163
|
+
throw new TaskCreationBlockedError(preCreate.reason, preCreate.extension);
|
|
164
|
+
}
|
|
165
|
+
return await withSiblingAwareness(preCreate.description, preCreate.options);
|
|
166
|
+
}
|
|
167
|
+
|
|
139
168
|
export async function createTaskWithSiblingAwareness(
|
|
140
169
|
description: string,
|
|
141
170
|
options: CreateTaskOptions,
|
|
171
|
+
args: { origin?: TaskCreateOrigin } = {},
|
|
142
172
|
): Promise<AgentTask> {
|
|
143
|
-
const { description: d, options: o } = await
|
|
173
|
+
const { description: d, options: o } = await prepareTaskWithSiblingAwareness(
|
|
174
|
+
description,
|
|
175
|
+
options,
|
|
176
|
+
args,
|
|
177
|
+
);
|
|
144
178
|
return await createTaskExtended(d, o);
|
|
145
179
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import {
|
|
2
3
|
buildRoutingAffinityFromAgent,
|
|
3
4
|
createLogEntry,
|
|
@@ -12,13 +13,32 @@ import {
|
|
|
12
13
|
isAgentEligibleForTask,
|
|
13
14
|
} from "../be/db";
|
|
14
15
|
import { repointTrackerSyncBySwarmId } from "../be/db-queries/tracker";
|
|
16
|
+
import { dispatchPre } from "../extensions/dispatcher";
|
|
15
17
|
import { resolveTemplate } from "../prompts/resolver";
|
|
16
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
type Agent,
|
|
20
|
+
type AgentTask,
|
|
21
|
+
CreateTaskOptionsSchema,
|
|
22
|
+
type ResumeReason,
|
|
23
|
+
type RoutingAffinity,
|
|
24
|
+
type TaskAttachment,
|
|
25
|
+
} from "../types";
|
|
17
26
|
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
18
27
|
import { taskAttachmentDisplayUrl } from "../utils/task-attachment-links";
|
|
28
|
+
import { createTaskWithSiblingAwareness } from "./sibling-awareness";
|
|
19
29
|
// Side-effect import: registers task lifecycle templates in the in-memory registry.
|
|
20
30
|
import "../tools/templates";
|
|
21
31
|
|
|
32
|
+
/** Runtime check for `pre.task.followUp` modifications (mirrors `TaskFollowUpModify`). */
|
|
33
|
+
const TaskFollowUpModifySchema = CreateTaskOptionsSchema.pick({
|
|
34
|
+
agentId: true,
|
|
35
|
+
priority: true,
|
|
36
|
+
followUpConfig: true,
|
|
37
|
+
})
|
|
38
|
+
.partial()
|
|
39
|
+
.extend({ description: z.string().trim().min(1).optional() })
|
|
40
|
+
.strict();
|
|
41
|
+
|
|
22
42
|
/**
|
|
23
43
|
* Liveness window (seconds) for considering a worker "online" enough to
|
|
24
44
|
* pre-assign a resume task. Defaults to 30s; override via env. The worker
|
|
@@ -217,16 +237,49 @@ export async function createWorkerTaskFollowUp(args: {
|
|
|
217
237
|
}
|
|
218
238
|
}
|
|
219
239
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
const preFollowUp = await dispatchPre(
|
|
241
|
+
"pre.task.followUp",
|
|
242
|
+
{
|
|
243
|
+
completedTask: task,
|
|
244
|
+
status,
|
|
245
|
+
output,
|
|
246
|
+
failureReason,
|
|
247
|
+
workerAgentId: taskAgent.id,
|
|
248
|
+
leadAgentId: leadAgent.id,
|
|
249
|
+
summary: followUpDescription,
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
// A modification that violates the task schema (for example `priority: -1`)
|
|
253
|
+
// is attributed to the extension and ignored, so the follow-up is still created.
|
|
254
|
+
validateModify: (data) => {
|
|
255
|
+
const parsed = TaskFollowUpModifySchema.safeParse(data);
|
|
256
|
+
return parsed.success
|
|
257
|
+
? { success: true, data: parsed.data }
|
|
258
|
+
: { success: false, error: new Error(parsed.error.message) };
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
if (preFollowUp.action === "block") return null;
|
|
263
|
+
|
|
264
|
+
const changes = preFollowUp.action === "modify" ? preFollowUp.data : {};
|
|
265
|
+
const followUpAgentId = changes.agentId === undefined ? leadAgent.id : changes.agentId;
|
|
266
|
+
return await createTaskWithSiblingAwareness(
|
|
267
|
+
changes.description ?? followUpDescription,
|
|
268
|
+
{
|
|
269
|
+
agentId: followUpAgentId,
|
|
270
|
+
routingReason: followUpAgentId ? "skill" : undefined,
|
|
271
|
+
routingSource: followUpAgentId ? "engine_default" : undefined,
|
|
272
|
+
source: "system",
|
|
273
|
+
taskType: "follow-up",
|
|
274
|
+
priority: changes.priority,
|
|
275
|
+
parentTaskId: task.id,
|
|
276
|
+
slackChannelId: task.slackChannelId,
|
|
277
|
+
slackThreadTs: task.slackThreadTs,
|
|
278
|
+
slackUserId: task.slackUserId,
|
|
279
|
+
followUpConfig: changes.followUpConfig,
|
|
280
|
+
},
|
|
281
|
+
{ origin: "followUp" },
|
|
282
|
+
);
|
|
230
283
|
}
|
|
231
284
|
|
|
232
285
|
/** Result of `createResumeFollowUp`. */
|
|
@@ -431,6 +484,7 @@ export async function createResumeFollowUp(args: {
|
|
|
431
484
|
: preferredAgentId === parent.agentId
|
|
432
485
|
? "continuity"
|
|
433
486
|
: "reroute_fault",
|
|
487
|
+
routingSource: preferredAgentId === undefined ? undefined : "engine_default",
|
|
434
488
|
creatorAgentId: parent.creatorAgentId,
|
|
435
489
|
source: "system",
|
|
436
490
|
taskType: "resume",
|
|
@@ -550,6 +604,7 @@ export async function createRerouteDecisionTask(args: {
|
|
|
550
604
|
const created = await createTaskExtended(decision.text, {
|
|
551
605
|
agentId: leadAgent.id,
|
|
552
606
|
routingReason: "reroute_fault",
|
|
607
|
+
routingSource: "engine_default",
|
|
553
608
|
creatorAgentId: original.creatorAgentId,
|
|
554
609
|
source: "system",
|
|
555
610
|
taskType: "reroute-decision",
|
|
@@ -630,6 +685,7 @@ export async function createPoolStarvationDecisionTask(args: {
|
|
|
630
685
|
const created = await createTaskExtended(decision.text, {
|
|
631
686
|
agentId: leadAgent.id,
|
|
632
687
|
routingReason: "overflow",
|
|
688
|
+
routingSource: "engine_default",
|
|
633
689
|
creatorAgentId: original.creatorAgentId,
|
|
634
690
|
source: "system",
|
|
635
691
|
taskType: "reroute-decision",
|
|
@@ -264,7 +264,7 @@ class FakeAgent {
|
|
|
264
264
|
},
|
|
265
265
|
},
|
|
266
266
|
},
|
|
267
|
-
diagnosticToken: "ghp_abcdefghijklmnopqrstuvwxyz0123456789",
|
|
267
|
+
diagnosticToken: "example-ghp_abcdefghijklmnopqrstuvwxyz0123456789",
|
|
268
268
|
chunks: Array.from({ length: 20 }, () => "y".repeat(2_000)),
|
|
269
269
|
},
|
|
270
270
|
},
|
|
@@ -283,7 +283,7 @@ class FakeAgent {
|
|
|
283
283
|
stopReason: "end_turn",
|
|
284
284
|
usage,
|
|
285
285
|
_meta: {
|
|
286
|
-
debug: "ghp_abcdefghijklmnopqrstuvwxyz0123456789",
|
|
286
|
+
debug: "example-ghp_abcdefghijklmnopqrstuvwxyz0123456789",
|
|
287
287
|
authorization: "opaque-response-credential",
|
|
288
288
|
note: "response metadata",
|
|
289
289
|
},
|
|
@@ -475,7 +475,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
|
|
|
475
475
|
}
|
|
476
476
|
expect(persistedJson).toContain("X-Debug");
|
|
477
477
|
expect(persistedJson).toContain("X-Debug-Map");
|
|
478
|
-
expect(persistedJson).not.toContain("ghp_abcdefghijklmnopqrstuvwxyz0123456789");
|
|
478
|
+
expect(persistedJson).not.toContain("example-ghp_abcdefghijklmnopqrstuvwxyz0123456789");
|
|
479
479
|
expect(persistedJson).toContain("[REDACTED:github_token]");
|
|
480
480
|
expect(persistedJson).toContain("… [truncated]");
|
|
481
481
|
expect(persistedJson).not.toContain("x".repeat(30_001));
|
|
@@ -510,7 +510,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
|
|
|
510
510
|
PATH: "/bin",
|
|
511
511
|
HOME: "/home/test",
|
|
512
512
|
ACP_TARGET: "opencode",
|
|
513
|
-
OPENAI_API_KEY: "test-key",
|
|
513
|
+
OPENAI_API_KEY: "example-test-key",
|
|
514
514
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({ theme: "dark" }),
|
|
515
515
|
},
|
|
516
516
|
}),
|
|
@@ -525,13 +525,13 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
|
|
|
525
525
|
PATH: "/bin",
|
|
526
526
|
HOME: "/home/test",
|
|
527
527
|
ACP_TARGET: "opencode",
|
|
528
|
-
OPENAI_API_KEY: "test-key",
|
|
528
|
+
OPENAI_API_KEY: "example-test-key",
|
|
529
529
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({ theme: "dark" }),
|
|
530
530
|
},
|
|
531
531
|
}),
|
|
532
532
|
),
|
|
533
533
|
).toMatchObject({
|
|
534
|
-
OPENAI_API_KEY: "test-key",
|
|
534
|
+
OPENAI_API_KEY: "example-test-key",
|
|
535
535
|
OPENCODE_CONFIG_CONTENT: JSON.stringify({ theme: "dark", model: "opencode/model" }),
|
|
536
536
|
});
|
|
537
537
|
});
|
|
@@ -596,8 +596,8 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
|
|
|
596
596
|
]),
|
|
597
597
|
).toEqual([{ type: "boolean", id: "flag", name: "Flag", currentValue: true }]);
|
|
598
598
|
|
|
599
|
-
const secret = "ghp_abcdefghijklmnopqrstuvwxyz1234567890";
|
|
600
|
-
const redacted = "[REDACTED:github_token]";
|
|
599
|
+
const secret = "example-ghp_abcdefghijklmnopqrstuvwxyz1234567890";
|
|
600
|
+
const redacted = "example-[REDACTED:github_token]";
|
|
601
601
|
const sanitized = sanitizeAcpConfigOptions([
|
|
602
602
|
{
|
|
603
603
|
type: "select",
|
|
@@ -729,7 +729,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
|
|
|
729
729
|
);
|
|
730
730
|
|
|
731
731
|
// Minimal swarm API stub: tracks calls to the session token endpoints.
|
|
732
|
-
const FAKE_TOKEN = "aseph_ephemtesttoken12345678901234";
|
|
732
|
+
const FAKE_TOKEN = "example-aseph_ephemtesttoken12345678901234";
|
|
733
733
|
const FAKE_TOKEN_ID = "fake-ephem-token-id";
|
|
734
734
|
let mintCalled = false;
|
|
735
735
|
let revokeCalled = false;
|
|
@@ -781,7 +781,7 @@ new AgentSideConnection((connection) => new FakeAgent(connection), stream);
|
|
|
781
781
|
baseConfig({
|
|
782
782
|
cwd,
|
|
783
783
|
apiUrl: `http://127.0.0.1:${swarmPort}`,
|
|
784
|
-
apiKey: "real-operator-key",
|
|
784
|
+
apiKey: "example-real-operator-key",
|
|
785
785
|
env: {
|
|
786
786
|
PATH: process.env.PATH ?? "",
|
|
787
787
|
HOME: process.env.HOME ?? "",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildAgentFsLiveUrl } from "../utils/constants";
|
|
3
|
+
|
|
4
|
+
const originalHost = process.env.AGENT_FS_LIVE_URL;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
process.env.AGENT_FS_LIVE_URL = "https://files.example.test/";
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
if (originalHost === undefined) delete process.env.AGENT_FS_LIVE_URL;
|
|
12
|
+
else process.env.AGENT_FS_LIVE_URL = originalHost;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("buildAgentFsLiveUrl path encoding", () => {
|
|
16
|
+
test.each([
|
|
17
|
+
["plain ASCII positive control", "/reports/report-2026_v1.txt", "reports/report-2026_v1.txt"],
|
|
18
|
+
[
|
|
19
|
+
"spaces in every segment",
|
|
20
|
+
"/shared reports/final report.md",
|
|
21
|
+
"shared%20reports/final%20report.md",
|
|
22
|
+
],
|
|
23
|
+
["Unicode and URL syntax", "/reports/café #1?.md", "reports/caf%C3%A9%20%231%3F.md"],
|
|
24
|
+
[
|
|
25
|
+
"already encoded positive control",
|
|
26
|
+
"/reports/final%20report%2f%2520.md",
|
|
27
|
+
"reports/final%20report%2f%2520.md",
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"mixed raw and encoded text",
|
|
31
|
+
"/shared%20reports/final%20report 100%.md",
|
|
32
|
+
"shared%20reports/final%20report%20100%25.md",
|
|
33
|
+
],
|
|
34
|
+
["malformed percent escapes", "/reports/100% %2 %GG.md", "reports/100%25%20%252%20%25GG.md"],
|
|
35
|
+
])("%s", (_label, path, expectedPath) => {
|
|
36
|
+
expect(buildAgentFsLiveUrl({ path, orgId: "org-1", driveId: "drive-1" })).toBe(
|
|
37
|
+
`https://files.example.test/file/~/org-1/drive-1/${expectedPath}`,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});
|