@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,70 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
closeDb,
|
|
4
|
+
completeTask,
|
|
5
|
+
createAgent,
|
|
6
|
+
createTaskExtended,
|
|
7
|
+
initDb,
|
|
8
|
+
startTask,
|
|
9
|
+
} from "../be/db";
|
|
10
|
+
import { installExtension } from "../be/extensions/db";
|
|
11
|
+
import { enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
|
|
12
|
+
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
13
|
+
import { createWorkerTaskFollowUp } from "../tasks/worker-follow-up";
|
|
14
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
15
|
+
|
|
16
|
+
const TEST_DB_PATH = "./test-extensions-example-rewrite-and-suppress.sqlite";
|
|
17
|
+
|
|
18
|
+
async function removeDbFiles(): Promise<void> {
|
|
19
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
20
|
+
await Bun.file(TEST_DB_PATH + suffix)
|
|
21
|
+
.delete()
|
|
22
|
+
.catch(() => {});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function enableFixture(name: string): Promise<void> {
|
|
27
|
+
const installed = await installExtension(await loadBundleFixture(name));
|
|
28
|
+
await enableExtension(installed.extension.id);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("extension examples: rewrite and suppress", () => {
|
|
32
|
+
beforeAll(async () => {
|
|
33
|
+
await removeDbFiles();
|
|
34
|
+
initDb(TEST_DB_PATH);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await stopExtensionRuntime();
|
|
39
|
+
closeDb();
|
|
40
|
+
await removeDbFiles();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("rewrites task priority and suppresses Slack lead follow-ups", async () => {
|
|
44
|
+
await enableFixture("rewrite-task-priority");
|
|
45
|
+
await enableFixture("suppress-lead-follow-up");
|
|
46
|
+
|
|
47
|
+
const rewritten = await createTaskWithSiblingAwareness("Prioritize this task", {
|
|
48
|
+
source: "api",
|
|
49
|
+
priority: 50,
|
|
50
|
+
});
|
|
51
|
+
expect(rewritten.priority).toBe(1);
|
|
52
|
+
|
|
53
|
+
await createAgent({ name: "example-lead", isLead: true, status: "idle" });
|
|
54
|
+
const worker = await createAgent({ name: "example-worker", isLead: false, status: "idle" });
|
|
55
|
+
const slackTask = await createTaskExtended("Slack task", {
|
|
56
|
+
agentId: worker.id,
|
|
57
|
+
source: "slack",
|
|
58
|
+
});
|
|
59
|
+
await startTask(slackTask.id);
|
|
60
|
+
const completed = await completeTask(slackTask.id, "done");
|
|
61
|
+
|
|
62
|
+
expect(
|
|
63
|
+
await createWorkerTaskFollowUp({
|
|
64
|
+
task: completed!,
|
|
65
|
+
status: "completed",
|
|
66
|
+
output: "done",
|
|
67
|
+
}),
|
|
68
|
+
).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
closeDb,
|
|
4
|
+
createAgent,
|
|
5
|
+
getDbClient,
|
|
6
|
+
getKv,
|
|
7
|
+
getMostRecentTaskInThread,
|
|
8
|
+
initDb,
|
|
9
|
+
} from "../be/db";
|
|
10
|
+
import { installExtension, listExtensionRuns } from "../be/extensions/db";
|
|
11
|
+
import { enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
|
|
12
|
+
import { _resetForTests as resetSlackEventDedup } from "../slack/event-dedup";
|
|
13
|
+
import { registerMessageHandler, resetSlackHandlerCachesForTesting } from "../slack/handlers";
|
|
14
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
15
|
+
|
|
16
|
+
const TEST_DB_PATH = "./test-extensions-example-route-slack-channel.sqlite";
|
|
17
|
+
const BOT_USER_ID = "U_EXTENSION_ROUTE_BOT";
|
|
18
|
+
const BOT_ID = "B_EXTENSION_ROUTE_BOT";
|
|
19
|
+
|
|
20
|
+
let messageHandler: ((args: Record<string, unknown>) => Promise<void>) | undefined;
|
|
21
|
+
let leadAgent: Awaited<ReturnType<typeof createAgent>>;
|
|
22
|
+
let workerAgent: Awaited<ReturnType<typeof createAgent>>;
|
|
23
|
+
let eventCounter = 0;
|
|
24
|
+
|
|
25
|
+
const handlerClient = {
|
|
26
|
+
auth: { test: async () => ({ user_id: BOT_USER_ID, bot_id: BOT_ID }) },
|
|
27
|
+
conversations: { replies: async () => ({ messages: [], ok: true }) },
|
|
28
|
+
reactions: { add: async () => ({ ok: true }) },
|
|
29
|
+
users: { info: async () => ({ ok: true, user: { profile: {} } }) },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function removeDbFiles(): Promise<void> {
|
|
33
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
34
|
+
await Bun.file(TEST_DB_PATH + suffix)
|
|
35
|
+
.delete()
|
|
36
|
+
.catch(() => {});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function sendMessage(channel: string, text: string): Promise<string> {
|
|
41
|
+
const ts = `1700000000.${String(++eventCounter).padStart(6, "0")}`;
|
|
42
|
+
await messageHandler!({
|
|
43
|
+
event: { channel, ts, text, user: "U_EXTENSION_ROUTE_USER" },
|
|
44
|
+
body: { event_id: `extension-route-${eventCounter}` },
|
|
45
|
+
client: handlerClient,
|
|
46
|
+
say: async () => ({}),
|
|
47
|
+
});
|
|
48
|
+
return ts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function waitFor(predicate: () => Promise<boolean>, timeoutMs = 5_000): Promise<void> {
|
|
52
|
+
const deadline = Date.now() + timeoutMs;
|
|
53
|
+
while (!(await predicate())) {
|
|
54
|
+
if (Date.now() >= deadline) throw new Error("Timed out while waiting for extension event");
|
|
55
|
+
await Bun.sleep(20);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("extension Slack channel routing", () => {
|
|
60
|
+
const savedEnv = { ...process.env };
|
|
61
|
+
|
|
62
|
+
beforeAll(async () => {
|
|
63
|
+
await removeDbFiles();
|
|
64
|
+
initDb(TEST_DB_PATH);
|
|
65
|
+
process.env.ADDITIVE_SLACK = "false";
|
|
66
|
+
process.env.SLACK_RENDER_V2 = "false";
|
|
67
|
+
process.env.STEERING_ENABLED = "false";
|
|
68
|
+
leadAgent = await createAgent({
|
|
69
|
+
name: "extension-route-lead",
|
|
70
|
+
isLead: true,
|
|
71
|
+
status: "idle",
|
|
72
|
+
capabilities: [],
|
|
73
|
+
});
|
|
74
|
+
workerAgent = await createAgent({
|
|
75
|
+
name: "extension-route-worker",
|
|
76
|
+
isLead: false,
|
|
77
|
+
status: "idle",
|
|
78
|
+
capabilities: [],
|
|
79
|
+
});
|
|
80
|
+
registerMessageHandler({
|
|
81
|
+
event: (eventType: string, handler: (args: Record<string, unknown>) => Promise<void>) => {
|
|
82
|
+
if (eventType === "message") messageHandler = handler;
|
|
83
|
+
},
|
|
84
|
+
} as never);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterAll(async () => {
|
|
88
|
+
await stopExtensionRuntime();
|
|
89
|
+
closeDb();
|
|
90
|
+
await removeDbFiles();
|
|
91
|
+
for (const key of Object.keys(process.env)) {
|
|
92
|
+
if (!(key in savedEnv)) delete process.env[key];
|
|
93
|
+
}
|
|
94
|
+
Object.assign(process.env, savedEnv);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
beforeEach(async () => {
|
|
98
|
+
await stopExtensionRuntime();
|
|
99
|
+
await getDbClient().run("DELETE FROM agent_tasks");
|
|
100
|
+
await getDbClient().run("DELETE FROM extensions");
|
|
101
|
+
await getDbClient().run("DELETE FROM kv_entries");
|
|
102
|
+
resetSlackEventDedup();
|
|
103
|
+
resetSlackHandlerCachesForTesting();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("routes the configured channel to its worker and leaves other channels with the lead", async () => {
|
|
107
|
+
const bundle = await loadBundleFixture("route-channel-to-agent");
|
|
108
|
+
const extension = (
|
|
109
|
+
await installExtension({
|
|
110
|
+
...bundle,
|
|
111
|
+
config: { channelId: "C1", agentId: workerAgent.id },
|
|
112
|
+
})
|
|
113
|
+
).extension;
|
|
114
|
+
await enableExtension(extension.id);
|
|
115
|
+
|
|
116
|
+
const routedTs = await sendMessage("C1", `<@${BOT_USER_ID}> route this task`);
|
|
117
|
+
expect(await getMostRecentTaskInThread("C1", routedTs)).toMatchObject({
|
|
118
|
+
agentId: workerAgent.id,
|
|
119
|
+
source: "slack",
|
|
120
|
+
});
|
|
121
|
+
expect(
|
|
122
|
+
(await listExtensionRuns(extension.id)).some(
|
|
123
|
+
(run) => run.event === "pre.slack.route" && run.action === "modify",
|
|
124
|
+
),
|
|
125
|
+
).toBe(true);
|
|
126
|
+
|
|
127
|
+
const defaultTs = await sendMessage("C2", `<@${BOT_USER_ID}> use the normal route`);
|
|
128
|
+
expect(await getMostRecentTaskInThread("C2", defaultTs)).toMatchObject({
|
|
129
|
+
agentId: leadAgent.id,
|
|
130
|
+
source: "slack",
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("blocks messages in an ignored channel before task creation", async () => {
|
|
135
|
+
const bundle = await loadBundleFixture("ignore-channel");
|
|
136
|
+
const extension = (await installExtension({ ...bundle, config: { channelId: "C3" } }))
|
|
137
|
+
.extension;
|
|
138
|
+
await enableExtension(extension.id);
|
|
139
|
+
|
|
140
|
+
const ts = await sendMessage("C3", `<@${BOT_USER_ID}> do not create a task`);
|
|
141
|
+
expect(await getMostRecentTaskInThread("C3", ts)).toBeNull();
|
|
142
|
+
expect(
|
|
143
|
+
(await listExtensionRuns(extension.id)).some(
|
|
144
|
+
(run) => run.event === "pre.slack.route" && run.action === "block",
|
|
145
|
+
),
|
|
146
|
+
).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("falls back to the built-in router when an extension names an unknown agent", async () => {
|
|
150
|
+
const bundle = await loadBundleFixture("route-channel-to-agent");
|
|
151
|
+
const extension = (
|
|
152
|
+
await installExtension({
|
|
153
|
+
...bundle,
|
|
154
|
+
config: { channelId: "C4", agentId: "missing-agent" },
|
|
155
|
+
})
|
|
156
|
+
).extension;
|
|
157
|
+
await enableExtension(extension.id);
|
|
158
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const ts = await sendMessage("C4", `<@${BOT_USER_ID}> use the fallback`);
|
|
162
|
+
expect(await getMostRecentTaskInThread("C4", ts)).toMatchObject({ agentId: leadAgent.id });
|
|
163
|
+
expect(warn).toHaveBeenCalledWith(
|
|
164
|
+
"[Slack] Extension selected an unknown agent. Using the built-in router:",
|
|
165
|
+
"missing-agent",
|
|
166
|
+
);
|
|
167
|
+
} finally {
|
|
168
|
+
warn.mockRestore();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("treats a runtime-invalid route target as an extension failure and keeps routing", async () => {
|
|
173
|
+
const bundle = await loadBundleFixture("minimal");
|
|
174
|
+
const extension = (
|
|
175
|
+
await installExtension({
|
|
176
|
+
manifest: { ...bundle.manifest, name: "bad-route-target", description: "bad target" },
|
|
177
|
+
files: {
|
|
178
|
+
"hooks.ts": `import { modify, type SwarmExtension } from "swarm-extension";
|
|
179
|
+
const extension: SwarmExtension = (api) => {
|
|
180
|
+
api.on("pre.slack.route", () => modify({ target: null } as never));
|
|
181
|
+
};
|
|
182
|
+
export default extension;
|
|
183
|
+
`,
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
).extension;
|
|
187
|
+
await enableExtension(extension.id);
|
|
188
|
+
|
|
189
|
+
const ts = await sendMessage("C5", `<@${BOT_USER_ID}> survive the bad target`);
|
|
190
|
+
expect(await getMostRecentTaskInThread("C5", ts)).toMatchObject({ agentId: leadAgent.id });
|
|
191
|
+
const runs = await listExtensionRuns(extension.id);
|
|
192
|
+
expect(runs.some((run) => run.event === "pre.slack.route" && run.action === "error")).toBe(
|
|
193
|
+
true,
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("dispatches post.slack.message with the canonical channel and user fields", async () => {
|
|
198
|
+
const bundle = await loadBundleFixture("route-channel-to-agent");
|
|
199
|
+
const extension = (
|
|
200
|
+
await installExtension({
|
|
201
|
+
...bundle,
|
|
202
|
+
config: { channelId: "C5", agentId: workerAgent.id },
|
|
203
|
+
})
|
|
204
|
+
).extension;
|
|
205
|
+
const enabled = await enableExtension(extension.id);
|
|
206
|
+
|
|
207
|
+
await sendMessage("C5", `<@${BOT_USER_ID}> record this message`);
|
|
208
|
+
await waitFor(async () =>
|
|
209
|
+
(await listExtensionRuns(extension.id)).some(
|
|
210
|
+
(run) => run.event === "post.slack.message" && run.action === "continue",
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
expect(
|
|
214
|
+
await getKv(`task:agent:${enabled.agentId}`, "ext:route-channel-to-agent:slack:C5"),
|
|
215
|
+
).toMatchObject({ value: "U_EXTENSION_ROUTE_USER" });
|
|
216
|
+
});
|
|
217
|
+
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
closeDb,
|
|
4
|
+
createAgent,
|
|
5
|
+
createTaskExtended,
|
|
6
|
+
getChildTasks,
|
|
7
|
+
getDbClient,
|
|
8
|
+
getTaskById,
|
|
9
|
+
initDb,
|
|
10
|
+
insertActiveSession,
|
|
11
|
+
startTask,
|
|
12
|
+
} from "../be/db";
|
|
13
|
+
import { installExtension, listExtensionRuns } from "../be/extensions/db";
|
|
14
|
+
import { validateBundle } from "../be/extensions/validate";
|
|
15
|
+
import { disableExtension, enableExtension, stopExtensionRuntime } from "../extensions/lifecycle";
|
|
16
|
+
import { codeLevelTriage } from "../heartbeat/heartbeat";
|
|
17
|
+
import type { ExtensionInstallBody } from "../types";
|
|
18
|
+
import { loadBundleFixture } from "./fixtures/extensions/load";
|
|
19
|
+
|
|
20
|
+
const TEST_DB_PATH = "./test-extensions-heartbeat.sqlite";
|
|
21
|
+
|
|
22
|
+
async function removeDbFiles(): Promise<void> {
|
|
23
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
24
|
+
await Bun.file(TEST_DB_PATH + suffix)
|
|
25
|
+
.delete()
|
|
26
|
+
.catch(() => {});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function enableBundle(bundle: ExtensionInstallBody) {
|
|
31
|
+
const validation = await validateBundle(bundle);
|
|
32
|
+
if (!validation.ok) throw new Error(validation.diagnostics.join("\n"));
|
|
33
|
+
const installed = await installExtension(bundle);
|
|
34
|
+
return await enableExtension(installed.extension.id);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function createStalledTask(options: {
|
|
38
|
+
minutes: number;
|
|
39
|
+
tags?: string[];
|
|
40
|
+
workflowStep?: boolean;
|
|
41
|
+
session?: "fresh" | "stale";
|
|
42
|
+
creatorAgentId?: string;
|
|
43
|
+
taskType?: string;
|
|
44
|
+
}) {
|
|
45
|
+
const agent = await createAgent({
|
|
46
|
+
name: `heartbeat-worker-${crypto.randomUUID()}`,
|
|
47
|
+
isLead: false,
|
|
48
|
+
status: "busy",
|
|
49
|
+
});
|
|
50
|
+
const task = await createTaskExtended("Long-running heartbeat task", {
|
|
51
|
+
agentId: agent.id,
|
|
52
|
+
creatorAgentId: options.creatorAgentId,
|
|
53
|
+
tags: options.tags,
|
|
54
|
+
taskType: options.taskType,
|
|
55
|
+
});
|
|
56
|
+
await startTask(task.id);
|
|
57
|
+
|
|
58
|
+
if (options.workflowStep) {
|
|
59
|
+
await getDbClient().run("PRAGMA foreign_keys = OFF");
|
|
60
|
+
try {
|
|
61
|
+
await getDbClient().run("UPDATE agent_tasks SET workflowRunStepId = ? WHERE id = ?", [
|
|
62
|
+
crypto.randomUUID(),
|
|
63
|
+
task.id,
|
|
64
|
+
]);
|
|
65
|
+
} finally {
|
|
66
|
+
await getDbClient().run("PRAGMA foreign_keys = ON");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (options.session) {
|
|
71
|
+
await insertActiveSession({
|
|
72
|
+
agentId: agent.id,
|
|
73
|
+
taskId: task.id,
|
|
74
|
+
triggerType: "task_assigned",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const staleAt = new Date(Date.now() - options.minutes * 60 * 1000).toISOString();
|
|
79
|
+
await getDbClient().run("UPDATE agent_tasks SET lastUpdatedAt = ? WHERE id = ?", [
|
|
80
|
+
staleAt,
|
|
81
|
+
task.id,
|
|
82
|
+
]);
|
|
83
|
+
if (options.session === "stale") {
|
|
84
|
+
await getDbClient().run("UPDATE active_sessions SET lastHeartbeatAt = ? WHERE taskId = ?", [
|
|
85
|
+
staleAt,
|
|
86
|
+
task.id,
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { agent, task };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("pre.heartbeat.remediate extensions", () => {
|
|
94
|
+
beforeAll(async () => {
|
|
95
|
+
await removeDbFiles();
|
|
96
|
+
closeDb();
|
|
97
|
+
initDb(TEST_DB_PATH);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterAll(async () => {
|
|
101
|
+
await stopExtensionRuntime();
|
|
102
|
+
closeDb();
|
|
103
|
+
await removeDbFiles();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
beforeEach(async () => {
|
|
107
|
+
await stopExtensionRuntime();
|
|
108
|
+
const client = getDbClient();
|
|
109
|
+
await client.run("DELETE FROM active_sessions");
|
|
110
|
+
await client.run("DELETE FROM agent_tasks");
|
|
111
|
+
await client.run("DELETE FROM extensions");
|
|
112
|
+
await client.run("DELETE FROM agents");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("keeps default no-session remediation when no extension is enabled", async () => {
|
|
116
|
+
const { task } = await createStalledTask({ minutes: 10 });
|
|
117
|
+
|
|
118
|
+
const findings = await codeLevelTriage();
|
|
119
|
+
|
|
120
|
+
expect(findings.autoResumedTasks).toHaveLength(1);
|
|
121
|
+
expect(findings.autoResumedTasks[0]?.taskId).toBe(task.id);
|
|
122
|
+
expect(findings.extensionSkipped).toEqual([]);
|
|
123
|
+
expect((await getTaskById(task.id))?.status).toBe("superseded");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("changes a workflow-step fail proposal to record", async () => {
|
|
127
|
+
const extension = await enableBundle(await loadBundleFixture("never-fail-long-tasks"));
|
|
128
|
+
const { task } = await createStalledTask({ minutes: 10, workflowStep: true });
|
|
129
|
+
|
|
130
|
+
const findings = await codeLevelTriage();
|
|
131
|
+
|
|
132
|
+
expect(findings.stalledTasks.map((candidate) => candidate.id)).toContain(task.id);
|
|
133
|
+
expect(findings.autoFailedTasks).toEqual([]);
|
|
134
|
+
expect(findings.autoResumedTasks).toEqual([]);
|
|
135
|
+
expect((await getTaskById(task.id))?.status).toBe("in_progress");
|
|
136
|
+
expect(await listExtensionRuns(extension.id)).toMatchObject([
|
|
137
|
+
{ event: "pre.heartbeat.remediate", action: "modify" },
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("escalates a Case A fail proposal to supersede-resume", async () => {
|
|
142
|
+
const bundle = await loadBundleFixture("minimal");
|
|
143
|
+
bundle.manifest = { ...bundle.manifest, name: "resume-no-session-failures" };
|
|
144
|
+
bundle.files["hooks.ts"] = `
|
|
145
|
+
import { modify, type SwarmExtension } from "swarm-extension";
|
|
146
|
+
const extension: SwarmExtension = (api) => {
|
|
147
|
+
api.on("pre.heartbeat.remediate", (event) => {
|
|
148
|
+
if (event.classification === "no-session" && event.proposedAction === "fail") {
|
|
149
|
+
return modify({ proposedAction: "supersede-resume" });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
export default extension;
|
|
154
|
+
`;
|
|
155
|
+
const extension = await enableBundle(bundle);
|
|
156
|
+
const { task } = await createStalledTask({ minutes: 10, taskType: "reroute-decision" });
|
|
157
|
+
|
|
158
|
+
const findings = await codeLevelTriage();
|
|
159
|
+
|
|
160
|
+
expect(findings.autoFailedTasks).toEqual([]);
|
|
161
|
+
expect(findings.autoResumedTasks[0]?.taskId).toBe(task.id);
|
|
162
|
+
expect((await getTaskById(task.id))?.status).toBe("superseded");
|
|
163
|
+
expect((await getChildTasks(task.id))[0]?.taskType).toBe("resume");
|
|
164
|
+
expect(await listExtensionRuns(extension.id)).toMatchObject([
|
|
165
|
+
{ event: "pre.heartbeat.remediate", action: "modify" },
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("blocks remediation for a configured task tag", async () => {
|
|
170
|
+
const bundle = await loadBundleFixture("record-only-on-tag");
|
|
171
|
+
const extension = await enableBundle({ ...bundle, config: { tag: "manual" } });
|
|
172
|
+
const { task } = await createStalledTask({ minutes: 10, tags: ["manual"] });
|
|
173
|
+
|
|
174
|
+
const findings = await codeLevelTriage();
|
|
175
|
+
|
|
176
|
+
expect((await getTaskById(task.id))?.status).toBe("in_progress");
|
|
177
|
+
expect(findings.stalledTasks.map((candidate) => candidate.id)).toContain(task.id);
|
|
178
|
+
expect(findings.extensionSkipped).toEqual([
|
|
179
|
+
{
|
|
180
|
+
taskId: task.id,
|
|
181
|
+
extension: { id: extension.id, name: "record-only-on-tag" },
|
|
182
|
+
reason: "Heartbeat remediation blocked for tag manual",
|
|
183
|
+
},
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
await disableExtension(extension.id);
|
|
187
|
+
const withoutExtension = await codeLevelTriage();
|
|
188
|
+
expect(withoutExtension.autoResumedTasks[0]?.taskId).toBe(task.id);
|
|
189
|
+
expect((await getTaskById(task.id))?.status).toBe("superseded");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("provides stale-session details before remediation", async () => {
|
|
193
|
+
const bundle = await loadBundleFixture("never-fail-long-tasks");
|
|
194
|
+
bundle.manifest = { ...bundle.manifest, name: "record-stale-sessions" };
|
|
195
|
+
bundle.files["hooks.ts"] = `
|
|
196
|
+
import { modify, type SwarmExtension } from "swarm-extension";
|
|
197
|
+
const extension: SwarmExtension = (api) => {
|
|
198
|
+
api.on("pre.heartbeat.remediate", (event) => {
|
|
199
|
+
if (
|
|
200
|
+
event.classification === "stale-session" &&
|
|
201
|
+
event.session &&
|
|
202
|
+
event.sessionHeartbeatAgeMs !== undefined &&
|
|
203
|
+
event.reason.includes("heartbeat is stale")
|
|
204
|
+
) {
|
|
205
|
+
return modify({ proposedAction: "record" });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
export default extension;
|
|
210
|
+
`;
|
|
211
|
+
await enableBundle(bundle);
|
|
212
|
+
const { task } = await createStalledTask({ minutes: 20, session: "stale" });
|
|
213
|
+
|
|
214
|
+
const findings = await codeLevelTriage();
|
|
215
|
+
|
|
216
|
+
expect(findings.stalledTasks.map((candidate) => candidate.id)).toContain(task.id);
|
|
217
|
+
expect(findings.autoResumedTasks).toEqual([]);
|
|
218
|
+
expect((await getTaskById(task.id))?.status).toBe("in_progress");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("keeps the original action when an extension returns an invalid action", async () => {
|
|
222
|
+
const bundle = await loadBundleFixture("never-fail-long-tasks");
|
|
223
|
+
bundle.manifest = { ...bundle.manifest, name: "invalid-heartbeat-action" };
|
|
224
|
+
bundle.files["hooks.ts"] = `
|
|
225
|
+
import { modify, type SwarmExtension } from "swarm-extension";
|
|
226
|
+
const extension: SwarmExtension = (api) => {
|
|
227
|
+
api.on("pre.heartbeat.remediate", () =>
|
|
228
|
+
modify({ proposedAction: "invalid-action" as never }),
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
export default extension;
|
|
232
|
+
`;
|
|
233
|
+
await enableBundle(bundle);
|
|
234
|
+
const { task } = await createStalledTask({ minutes: 10 });
|
|
235
|
+
const warning = spyOn(console, "warn").mockImplementation(() => {});
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const findings = await codeLevelTriage();
|
|
239
|
+
expect(findings.autoResumedTasks[0]?.taskId).toBe(task.id);
|
|
240
|
+
expect((await getTaskById(task.id))?.status).toBe("superseded");
|
|
241
|
+
expect(warning).toHaveBeenCalledWith(
|
|
242
|
+
"[Heartbeat] Extension returned an invalid remediation action:",
|
|
243
|
+
"invalid-action",
|
|
244
|
+
);
|
|
245
|
+
} finally {
|
|
246
|
+
warning.mockRestore();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("can escalate a fresh-session stall to supersede and resume", async () => {
|
|
251
|
+
const bundle = await loadBundleFixture("never-fail-long-tasks");
|
|
252
|
+
bundle.manifest = { ...bundle.manifest, name: "resume-fresh-stalls" };
|
|
253
|
+
bundle.files["hooks.ts"] = `
|
|
254
|
+
import { modify, type SwarmExtension } from "swarm-extension";
|
|
255
|
+
const extension: SwarmExtension = (api) => {
|
|
256
|
+
api.on("pre.heartbeat.remediate", (event) => {
|
|
257
|
+
if (event.classification === "fresh-stalled") {
|
|
258
|
+
return modify({ proposedAction: "supersede-resume" });
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
export default extension;
|
|
263
|
+
`;
|
|
264
|
+
await enableBundle(bundle);
|
|
265
|
+
const { task } = await createStalledTask({ minutes: 45, session: "fresh" });
|
|
266
|
+
|
|
267
|
+
const findings = await codeLevelTriage();
|
|
268
|
+
|
|
269
|
+
expect(findings.autoResumedTasks[0]?.taskId).toBe(task.id);
|
|
270
|
+
expect(findings.stalledTasks).toEqual([]);
|
|
271
|
+
expect((await getTaskById(task.id))?.status).toBe("superseded");
|
|
272
|
+
expect((await getChildTasks(task.id))[0]?.taskType).toBe("resume");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("skips the extension that created the stalled task", async () => {
|
|
276
|
+
const bundle = await loadBundleFixture("record-only-on-tag");
|
|
277
|
+
bundle.manifest = { ...bundle.manifest, name: "skip-own-heartbeat" };
|
|
278
|
+
const extension = await enableBundle({ ...bundle, config: { tag: "manual" } });
|
|
279
|
+
const { task } = await createStalledTask({
|
|
280
|
+
minutes: 10,
|
|
281
|
+
tags: ["manual"],
|
|
282
|
+
creatorAgentId: extension.agentId ?? undefined,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const findings = await codeLevelTriage();
|
|
286
|
+
|
|
287
|
+
expect(findings.extensionSkipped).toEqual([]);
|
|
288
|
+
expect(findings.autoResumedTasks[0]?.taskId).toBe(task.id);
|
|
289
|
+
expect(await listExtensionRuns(extension.id)).toEqual([]);
|
|
290
|
+
});
|
|
291
|
+
});
|