@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,547 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { resolveHttpAuditUserId } from "../be/audit-user";
|
|
4
|
+
import { getAgentById } from "../be/db";
|
|
5
|
+
import {
|
|
6
|
+
deleteExtension,
|
|
7
|
+
getExtensionById,
|
|
8
|
+
getExtensionByName,
|
|
9
|
+
getExtensionFiles,
|
|
10
|
+
installExtension,
|
|
11
|
+
listExtensionRuns,
|
|
12
|
+
listExtensions,
|
|
13
|
+
listExtensionVersions,
|
|
14
|
+
updateExtensionMeta,
|
|
15
|
+
} from "../be/extensions/db";
|
|
16
|
+
import { validateBundle } from "../be/extensions/validate";
|
|
17
|
+
import { EXTENSION_TYPE_DEFINITIONS } from "../extensions/contract-types.generated";
|
|
18
|
+
import {
|
|
19
|
+
activateVersion,
|
|
20
|
+
disableExtension,
|
|
21
|
+
ExtensionLifecycleError,
|
|
22
|
+
enableExtension,
|
|
23
|
+
reloadExtension,
|
|
24
|
+
} from "../extensions/lifecycle";
|
|
25
|
+
import { ExtensionConfigError } from "../extensions/loader";
|
|
26
|
+
import { can, type PermissionVerb, type RbacPrincipal } from "../rbac";
|
|
27
|
+
import {
|
|
28
|
+
ExtensionInstallBodySchema,
|
|
29
|
+
ExtensionManifestSchema,
|
|
30
|
+
ExtensionRunSchema,
|
|
31
|
+
ExtensionSchema,
|
|
32
|
+
ExtensionVersionSchema,
|
|
33
|
+
} from "../types";
|
|
34
|
+
import { getRequestAuth } from "../utils/request-auth-context";
|
|
35
|
+
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
36
|
+
import { route } from "./route-def";
|
|
37
|
+
import { json, jsonError } from "./utils";
|
|
38
|
+
|
|
39
|
+
const idParamsSchema = z.object({ id: z.string().min(1) });
|
|
40
|
+
|
|
41
|
+
const installRoute = route({
|
|
42
|
+
method: "post",
|
|
43
|
+
path: "/api/extensions/install",
|
|
44
|
+
pattern: ["api", "extensions", "install"],
|
|
45
|
+
operationId: "extensions_install",
|
|
46
|
+
summary: "Validate and install an extension bundle",
|
|
47
|
+
tags: ["Extensions"],
|
|
48
|
+
body: ExtensionInstallBodySchema,
|
|
49
|
+
responses: {
|
|
50
|
+
200: {
|
|
51
|
+
description: "Installed extension",
|
|
52
|
+
schema: z.object({
|
|
53
|
+
extension: ExtensionSchema,
|
|
54
|
+
manifest: ExtensionManifestSchema,
|
|
55
|
+
contentDeduped: z.boolean(),
|
|
56
|
+
}),
|
|
57
|
+
},
|
|
58
|
+
400: { description: "Bundle validation failed" },
|
|
59
|
+
403: { description: "Permission denied" },
|
|
60
|
+
},
|
|
61
|
+
rbac: { permission: "extension.write" },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const listRoute = route({
|
|
65
|
+
method: "get",
|
|
66
|
+
path: "/api/extensions",
|
|
67
|
+
pattern: ["api", "extensions"],
|
|
68
|
+
operationId: "extensions_list",
|
|
69
|
+
summary: "List installed extensions",
|
|
70
|
+
tags: ["Extensions"],
|
|
71
|
+
responses: {
|
|
72
|
+
200: {
|
|
73
|
+
description: "Installed extensions",
|
|
74
|
+
schema: z.object({ extensions: z.array(ExtensionSchema) }),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const typeDefsRoute = route({
|
|
80
|
+
method: "get",
|
|
81
|
+
path: "/api/extensions/type-defs",
|
|
82
|
+
pattern: ["api", "extensions", "type-defs"],
|
|
83
|
+
operationId: "extensions_type_defs",
|
|
84
|
+
summary: "Get extension authoring type definitions",
|
|
85
|
+
tags: ["Extensions"],
|
|
86
|
+
responses: {
|
|
87
|
+
200: {
|
|
88
|
+
description: "Generated swarm-extension.d.ts",
|
|
89
|
+
unstructured: "Plain TypeScript declaration text",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const getRoute = route({
|
|
95
|
+
method: "get",
|
|
96
|
+
path: "/api/extensions/{id}",
|
|
97
|
+
pattern: ["api", "extensions", null],
|
|
98
|
+
operationId: "extensions_get",
|
|
99
|
+
summary: "Get an extension bundle",
|
|
100
|
+
tags: ["Extensions"],
|
|
101
|
+
params: idParamsSchema,
|
|
102
|
+
responses: {
|
|
103
|
+
200: {
|
|
104
|
+
description: "Extension bundle",
|
|
105
|
+
schema: z.object({
|
|
106
|
+
extension: ExtensionSchema,
|
|
107
|
+
manifest: ExtensionManifestSchema,
|
|
108
|
+
files: z.record(z.string(), z.string()),
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
404: { description: "Extension not found" },
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const versionsRoute = route({
|
|
116
|
+
method: "get",
|
|
117
|
+
path: "/api/extensions/{id}/versions",
|
|
118
|
+
pattern: ["api", "extensions", null, "versions"],
|
|
119
|
+
operationId: "extensions_versions",
|
|
120
|
+
summary: "List extension versions",
|
|
121
|
+
tags: ["Extensions"],
|
|
122
|
+
params: idParamsSchema,
|
|
123
|
+
responses: {
|
|
124
|
+
200: {
|
|
125
|
+
description: "Extension versions",
|
|
126
|
+
schema: z.object({ versions: z.array(ExtensionVersionSchema) }),
|
|
127
|
+
},
|
|
128
|
+
404: { description: "Extension not found" },
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const runsRoute = route({
|
|
133
|
+
method: "get",
|
|
134
|
+
path: "/api/extensions/{id}/runs",
|
|
135
|
+
pattern: ["api", "extensions", null, "runs"],
|
|
136
|
+
operationId: "extensions_runs",
|
|
137
|
+
summary: "List extension run log entries",
|
|
138
|
+
tags: ["Extensions"],
|
|
139
|
+
params: idParamsSchema,
|
|
140
|
+
query: z.object({ limit: z.coerce.number().int().min(1).max(500).optional() }),
|
|
141
|
+
responses: {
|
|
142
|
+
200: {
|
|
143
|
+
description: "Extension run log",
|
|
144
|
+
schema: z.object({ runs: z.array(ExtensionRunSchema) }),
|
|
145
|
+
},
|
|
146
|
+
404: { description: "Extension not found" },
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const patchRoute = route({
|
|
151
|
+
method: "patch",
|
|
152
|
+
path: "/api/extensions/{id}",
|
|
153
|
+
pattern: ["api", "extensions", null],
|
|
154
|
+
operationId: "extensions_update",
|
|
155
|
+
summary: "Update extension priority, config, or description",
|
|
156
|
+
tags: ["Extensions"],
|
|
157
|
+
params: idParamsSchema,
|
|
158
|
+
body: z
|
|
159
|
+
.object({
|
|
160
|
+
priority: z.number().int().optional(),
|
|
161
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
162
|
+
description: z.string().optional(),
|
|
163
|
+
})
|
|
164
|
+
.strict(),
|
|
165
|
+
responses: {
|
|
166
|
+
200: { description: "Updated extension", schema: z.object({ extension: ExtensionSchema }) },
|
|
167
|
+
400: { description: "Extension config validation failed" },
|
|
168
|
+
403: { description: "Permission denied" },
|
|
169
|
+
404: { description: "Extension not found" },
|
|
170
|
+
},
|
|
171
|
+
rbac: { permission: "extension.write" },
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const deleteRoute = route({
|
|
175
|
+
method: "delete",
|
|
176
|
+
path: "/api/extensions/{id}",
|
|
177
|
+
pattern: ["api", "extensions", null],
|
|
178
|
+
operationId: "extensions_delete",
|
|
179
|
+
summary: "Uninstall a disabled extension",
|
|
180
|
+
tags: ["Extensions"],
|
|
181
|
+
params: idParamsSchema,
|
|
182
|
+
responses: {
|
|
183
|
+
200: {
|
|
184
|
+
description: "Extension uninstalled",
|
|
185
|
+
schema: z.object({ deleted: z.literal(true) }),
|
|
186
|
+
},
|
|
187
|
+
403: { description: "Permission denied" },
|
|
188
|
+
404: { description: "Extension not found" },
|
|
189
|
+
409: { description: "Enabled extensions cannot be uninstalled" },
|
|
190
|
+
},
|
|
191
|
+
rbac: { permission: "extension.write" },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const enableRoute = route({
|
|
195
|
+
method: "post",
|
|
196
|
+
path: "/api/extensions/{id}/enable",
|
|
197
|
+
pattern: ["api", "extensions", null, "enable"],
|
|
198
|
+
operationId: "extensions_enable",
|
|
199
|
+
summary: "Enable an extension",
|
|
200
|
+
description: "Loads and enables an extension. This route is operator-only.",
|
|
201
|
+
tags: ["Extensions"],
|
|
202
|
+
params: idParamsSchema,
|
|
203
|
+
responses: {
|
|
204
|
+
200: { description: "Enabled extension", schema: z.object({ extension: ExtensionSchema }) },
|
|
205
|
+
400: { description: "Extension load or config validation failed" },
|
|
206
|
+
403: { description: "Permission denied" },
|
|
207
|
+
404: { description: "Extension not found" },
|
|
208
|
+
},
|
|
209
|
+
rbac: { permission: "extension.activate" },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const disableRoute = route({
|
|
213
|
+
method: "post",
|
|
214
|
+
path: "/api/extensions/{id}/disable",
|
|
215
|
+
pattern: ["api", "extensions", null, "disable"],
|
|
216
|
+
operationId: "extensions_disable",
|
|
217
|
+
summary: "Disable an extension",
|
|
218
|
+
description: "Unloads and disables an extension. This route is operator-only.",
|
|
219
|
+
tags: ["Extensions"],
|
|
220
|
+
params: idParamsSchema,
|
|
221
|
+
responses: {
|
|
222
|
+
200: { description: "Disabled extension", schema: z.object({ extension: ExtensionSchema }) },
|
|
223
|
+
403: { description: "Permission denied" },
|
|
224
|
+
404: { description: "Extension not found" },
|
|
225
|
+
},
|
|
226
|
+
rbac: { permission: "extension.activate" },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const activateVersionRoute = route({
|
|
230
|
+
method: "post",
|
|
231
|
+
path: "/api/extensions/{id}/activate-version",
|
|
232
|
+
pattern: ["api", "extensions", null, "activate-version"],
|
|
233
|
+
operationId: "extensions_activate_version",
|
|
234
|
+
summary: "Activate an extension version",
|
|
235
|
+
description: "Activates a stored version. This route is operator-only.",
|
|
236
|
+
tags: ["Extensions"],
|
|
237
|
+
params: idParamsSchema,
|
|
238
|
+
body: z.object({ version: z.number().int().min(1) }).strict(),
|
|
239
|
+
responses: {
|
|
240
|
+
200: { description: "Activated extension", schema: z.object({ extension: ExtensionSchema }) },
|
|
241
|
+
400: { description: "Extension load or config validation failed" },
|
|
242
|
+
403: { description: "Permission denied" },
|
|
243
|
+
404: { description: "Extension or version not found" },
|
|
244
|
+
},
|
|
245
|
+
rbac: { permission: "extension.activate" },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/** Operator-supplied config may carry tokens; scrub before it leaves the API. */
|
|
249
|
+
function redactExtension<T extends { configJson: string }>(extension: T): T {
|
|
250
|
+
return { ...extension, configJson: scrubSecrets(extension.configJson) };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Matches every marker shape `scrubSecrets` emits: env-key names (`EXT_TOKEN`) and
|
|
254
|
+
// structural pattern names (`github_token`, `sk-ant`).
|
|
255
|
+
const REDACTED_CONFIG_VALUE = /^\[REDACTED:[A-Za-z0-9_.:-]+\]$/;
|
|
256
|
+
|
|
257
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
258
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Walk `incoming` alongside `stored`; wherever the operator submitted an unchanged
|
|
263
|
+
* redaction placeholder, restore the stored value at that path (objects and arrays
|
|
264
|
+
* are recursed so nested secrets survive a dashboard round trip).
|
|
265
|
+
*/
|
|
266
|
+
function restoreRedacted(incoming: unknown, stored: unknown): unknown {
|
|
267
|
+
if (typeof incoming === "string") {
|
|
268
|
+
return REDACTED_CONFIG_VALUE.test(incoming) && stored !== undefined ? stored : incoming;
|
|
269
|
+
}
|
|
270
|
+
if (Array.isArray(incoming)) {
|
|
271
|
+
const storedArray = Array.isArray(stored) ? stored : [];
|
|
272
|
+
return incoming.map((item, index) => restoreRedacted(item, storedArray[index]));
|
|
273
|
+
}
|
|
274
|
+
if (isPlainObject(incoming)) {
|
|
275
|
+
const storedObject = isPlainObject(stored) ? stored : {};
|
|
276
|
+
return Object.fromEntries(
|
|
277
|
+
Object.entries(incoming).map(([key, value]) => [
|
|
278
|
+
key,
|
|
279
|
+
restoreRedacted(value, storedObject[key]),
|
|
280
|
+
]),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return incoming;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function preserveRedactedConfigValues(
|
|
287
|
+
incoming: Record<string, unknown>,
|
|
288
|
+
storedJson: string,
|
|
289
|
+
): Record<string, unknown> {
|
|
290
|
+
let parsed: unknown;
|
|
291
|
+
try {
|
|
292
|
+
parsed = JSON.parse(storedJson);
|
|
293
|
+
} catch {
|
|
294
|
+
parsed = {};
|
|
295
|
+
}
|
|
296
|
+
return restoreRedacted(incoming, parsed) as Record<string, unknown>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Prefer the supplied X-Agent-ID even when the request carries the shared API key.
|
|
301
|
+
* This prevents agents from using that key to bypass the route's declared RBAC permission.
|
|
302
|
+
*/
|
|
303
|
+
async function extensionPrincipal(
|
|
304
|
+
req: IncomingMessage,
|
|
305
|
+
callerAgentId: string | undefined,
|
|
306
|
+
): Promise<RbacPrincipal> {
|
|
307
|
+
if (callerAgentId) {
|
|
308
|
+
const agent = await getAgentById(callerAgentId);
|
|
309
|
+
return { kind: "agent", agentId: callerAgentId, isLead: agent?.isLead ?? false };
|
|
310
|
+
}
|
|
311
|
+
const auth = getRequestAuth(req);
|
|
312
|
+
if (auth?.kind === "operator") return { kind: "operator" };
|
|
313
|
+
if (auth?.kind === "user") return { kind: "user", userId: auth.userId };
|
|
314
|
+
return { kind: "agent", agentId: "", isLead: false };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function requirePermission(
|
|
318
|
+
req: IncomingMessage,
|
|
319
|
+
res: ServerResponse,
|
|
320
|
+
callerAgentId: string | undefined,
|
|
321
|
+
verb: Extract<PermissionVerb, "extension.write" | "extension.activate">,
|
|
322
|
+
): Promise<RbacPrincipal | null> {
|
|
323
|
+
const principal = await extensionPrincipal(req, callerAgentId);
|
|
324
|
+
const decision = can({ principal, verb, resource: { kind: "none" }, source: "http" });
|
|
325
|
+
if (decision.allow) return principal;
|
|
326
|
+
jsonError(res, `Forbidden: ${decision.reason}`, 403);
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function respondLifecycleError(res: ServerResponse, error: unknown): void {
|
|
331
|
+
const status =
|
|
332
|
+
error instanceof ExtensionConfigError
|
|
333
|
+
? 400
|
|
334
|
+
: error instanceof ExtensionLifecycleError
|
|
335
|
+
? error.status
|
|
336
|
+
: 500;
|
|
337
|
+
jsonError(res, scrubSecrets(error instanceof Error ? error.message : String(error)), status);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function handleExtensions(
|
|
341
|
+
req: IncomingMessage,
|
|
342
|
+
res: ServerResponse,
|
|
343
|
+
pathSegments: string[],
|
|
344
|
+
queryParams: URLSearchParams,
|
|
345
|
+
agentId?: string,
|
|
346
|
+
): Promise<boolean> {
|
|
347
|
+
if (installRoute.match(req.method, pathSegments)) {
|
|
348
|
+
const parsed = await installRoute.parse(req, res, pathSegments, queryParams);
|
|
349
|
+
if (!parsed) return true;
|
|
350
|
+
const principal = await requirePermission(req, res, agentId, "extension.write");
|
|
351
|
+
if (!principal) return true;
|
|
352
|
+
const validation = await validateBundle(parsed.body);
|
|
353
|
+
if (!validation.ok) {
|
|
354
|
+
json(res, { error: "extension_validation_failed", diagnostics: validation.diagnostics }, 400);
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
const existing = await getExtensionByName(validation.manifest.name);
|
|
358
|
+
const writerAgentId = principal.kind === "agent" ? principal.agentId : undefined;
|
|
359
|
+
const updatedBy = await resolveHttpAuditUserId(req, writerAgentId);
|
|
360
|
+
const shouldActivate = principal.kind !== "agent" && existing?.enabled === true;
|
|
361
|
+
const result = await installExtension({
|
|
362
|
+
manifest: validation.manifest,
|
|
363
|
+
files: parsed.body.files,
|
|
364
|
+
priority: parsed.body.priority,
|
|
365
|
+
config: parsed.body.config,
|
|
366
|
+
agentId: writerAgentId,
|
|
367
|
+
createdBy: updatedBy,
|
|
368
|
+
activate: shouldActivate,
|
|
369
|
+
});
|
|
370
|
+
let extension = result.extension;
|
|
371
|
+
if (shouldActivate) {
|
|
372
|
+
try {
|
|
373
|
+
extension = await reloadExtension(extension.id, { by: updatedBy });
|
|
374
|
+
} catch (error) {
|
|
375
|
+
respondLifecycleError(res, error);
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
installRoute.respond(res, 200, {
|
|
380
|
+
extension: redactExtension(extension),
|
|
381
|
+
manifest: validation.manifest,
|
|
382
|
+
contentDeduped: result.contentDeduped,
|
|
383
|
+
});
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (listRoute.match(req.method, pathSegments)) {
|
|
388
|
+
const parsed = await listRoute.parse(req, res, pathSegments, queryParams);
|
|
389
|
+
if (!parsed) return true;
|
|
390
|
+
listRoute.respond(res, 200, { extensions: (await listExtensions()).map(redactExtension) });
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (typeDefsRoute.match(req.method, pathSegments)) {
|
|
395
|
+
const parsed = await typeDefsRoute.parse(req, res, pathSegments, queryParams);
|
|
396
|
+
if (!parsed) return true;
|
|
397
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
398
|
+
res.end(EXTENSION_TYPE_DEFINITIONS);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (versionsRoute.match(req.method, pathSegments)) {
|
|
403
|
+
const parsed = await versionsRoute.parse(req, res, pathSegments, queryParams);
|
|
404
|
+
if (!parsed) return true;
|
|
405
|
+
if (!(await getExtensionById(parsed.params.id))) {
|
|
406
|
+
jsonError(res, "Extension not found", 404);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
versionsRoute.respond(res, 200, {
|
|
410
|
+
versions: await listExtensionVersions(parsed.params.id),
|
|
411
|
+
});
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (runsRoute.match(req.method, pathSegments)) {
|
|
416
|
+
const parsed = await runsRoute.parse(req, res, pathSegments, queryParams);
|
|
417
|
+
if (!parsed) return true;
|
|
418
|
+
if (!(await getExtensionById(parsed.params.id))) {
|
|
419
|
+
jsonError(res, "Extension not found", 404);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
runsRoute.respond(res, 200, {
|
|
423
|
+
runs: await listExtensionRuns(parsed.params.id, parsed.query.limit),
|
|
424
|
+
});
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (getRoute.match(req.method, pathSegments)) {
|
|
429
|
+
const parsed = await getRoute.parse(req, res, pathSegments, queryParams);
|
|
430
|
+
if (!parsed) return true;
|
|
431
|
+
const extension = await getExtensionById(parsed.params.id);
|
|
432
|
+
if (!extension) {
|
|
433
|
+
jsonError(res, "Extension not found", 404);
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
getRoute.respond(res, 200, {
|
|
437
|
+
extension: redactExtension(extension),
|
|
438
|
+
manifest: ExtensionManifestSchema.parse(JSON.parse(extension.manifestJson)),
|
|
439
|
+
files: await getExtensionFiles(extension.id),
|
|
440
|
+
});
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (patchRoute.match(req.method, pathSegments)) {
|
|
445
|
+
const parsed = await patchRoute.parse(req, res, pathSegments, queryParams);
|
|
446
|
+
if (!parsed) return true;
|
|
447
|
+
const principal = await requirePermission(req, res, agentId, "extension.write");
|
|
448
|
+
if (!principal) return true;
|
|
449
|
+
const writerAgentId = principal.kind === "agent" ? principal.agentId : undefined;
|
|
450
|
+
const stored = parsed.body.config ? await getExtensionById(parsed.params.id) : null;
|
|
451
|
+
const config =
|
|
452
|
+
parsed.body.config && stored
|
|
453
|
+
? preserveRedactedConfigValues(parsed.body.config, stored.configJson)
|
|
454
|
+
: parsed.body.config;
|
|
455
|
+
let extension = await updateExtensionMeta(parsed.params.id, {
|
|
456
|
+
...parsed.body,
|
|
457
|
+
...(config === undefined ? {} : { config }),
|
|
458
|
+
updatedBy: await resolveHttpAuditUserId(req, writerAgentId),
|
|
459
|
+
});
|
|
460
|
+
if (!extension) {
|
|
461
|
+
jsonError(res, "Extension not found", 404);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
if (extension.enabled) {
|
|
465
|
+
try {
|
|
466
|
+
extension = await reloadExtension(extension.id);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
respondLifecycleError(res, error);
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
patchRoute.respond(res, 200, { extension: redactExtension(extension) });
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (deleteRoute.match(req.method, pathSegments)) {
|
|
477
|
+
const parsed = await deleteRoute.parse(req, res, pathSegments, queryParams);
|
|
478
|
+
if (!parsed) return true;
|
|
479
|
+
const principal = await requirePermission(req, res, agentId, "extension.write");
|
|
480
|
+
if (!principal) return true;
|
|
481
|
+
const extension = await getExtensionById(parsed.params.id);
|
|
482
|
+
if (!extension) {
|
|
483
|
+
jsonError(res, "Extension not found", 404);
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
if (extension.enabled) {
|
|
487
|
+
jsonError(res, "Disable the extension before uninstalling it", 409);
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
await deleteExtension(extension.id);
|
|
491
|
+
deleteRoute.respond(res, 200, { deleted: true });
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (enableRoute.match(req.method, pathSegments)) {
|
|
496
|
+
const parsed = await enableRoute.parse(req, res, pathSegments, queryParams);
|
|
497
|
+
if (!parsed) return true;
|
|
498
|
+
const principal = await requirePermission(req, res, agentId, "extension.activate");
|
|
499
|
+
if (!principal) return true;
|
|
500
|
+
const writerAgentId = principal.kind === "agent" ? principal.agentId : undefined;
|
|
501
|
+
try {
|
|
502
|
+
const extension = await enableExtension(parsed.params.id, {
|
|
503
|
+
by: await resolveHttpAuditUserId(req, writerAgentId),
|
|
504
|
+
});
|
|
505
|
+
enableRoute.respond(res, 200, { extension: redactExtension(extension) });
|
|
506
|
+
} catch (error) {
|
|
507
|
+
respondLifecycleError(res, error);
|
|
508
|
+
}
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (disableRoute.match(req.method, pathSegments)) {
|
|
513
|
+
const parsed = await disableRoute.parse(req, res, pathSegments, queryParams);
|
|
514
|
+
if (!parsed) return true;
|
|
515
|
+
const principal = await requirePermission(req, res, agentId, "extension.activate");
|
|
516
|
+
if (!principal) return true;
|
|
517
|
+
const writerAgentId = principal.kind === "agent" ? principal.agentId : undefined;
|
|
518
|
+
try {
|
|
519
|
+
const extension = await disableExtension(parsed.params.id, {
|
|
520
|
+
by: await resolveHttpAuditUserId(req, writerAgentId),
|
|
521
|
+
});
|
|
522
|
+
disableRoute.respond(res, 200, { extension: redactExtension(extension) });
|
|
523
|
+
} catch (error) {
|
|
524
|
+
respondLifecycleError(res, error);
|
|
525
|
+
}
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (activateVersionRoute.match(req.method, pathSegments)) {
|
|
530
|
+
const parsed = await activateVersionRoute.parse(req, res, pathSegments, queryParams);
|
|
531
|
+
if (!parsed) return true;
|
|
532
|
+
const principal = await requirePermission(req, res, agentId, "extension.activate");
|
|
533
|
+
if (!principal) return true;
|
|
534
|
+
const writerAgentId = principal.kind === "agent" ? principal.agentId : undefined;
|
|
535
|
+
try {
|
|
536
|
+
const extension = await activateVersion(parsed.params.id, parsed.body.version, {
|
|
537
|
+
by: await resolveHttpAuditUserId(req, writerAgentId),
|
|
538
|
+
});
|
|
539
|
+
activateVersionRoute.respond(res, 200, { extension: redactExtension(extension) });
|
|
540
|
+
} catch (error) {
|
|
541
|
+
respondLifecycleError(res, error);
|
|
542
|
+
}
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return false;
|
|
547
|
+
}
|
package/src/http/inbox-state.ts
CHANGED
|
@@ -41,6 +41,7 @@ const upsertState = route({
|
|
|
41
41
|
itemId: z.string().min(1),
|
|
42
42
|
status: InboxItemStatusSchema,
|
|
43
43
|
snoozeUntil: z.string().datetime().optional(),
|
|
44
|
+
readAt: z.string().datetime().optional(),
|
|
44
45
|
}),
|
|
45
46
|
responses: {
|
|
46
47
|
200: {
|
|
@@ -83,6 +84,7 @@ export async function handleInboxState(
|
|
|
83
84
|
itemId: parsed.body.itemId,
|
|
84
85
|
status: parsed.body.status,
|
|
85
86
|
snoozeUntil: parsed.body.snoozeUntil,
|
|
87
|
+
readAt: parsed.body.readAt,
|
|
86
88
|
});
|
|
87
89
|
upsertState.respond(res, 200, { item });
|
|
88
90
|
} catch (err) {
|
package/src/http/index.ts
CHANGED
|
@@ -25,6 +25,11 @@ import {
|
|
|
25
25
|
} from "../be/rbac-audit";
|
|
26
26
|
import { startScratchScriptGc, stopScratchScriptGc } from "../be/scripts/retention";
|
|
27
27
|
import { seedLegacyCapabilitiesConfig } from "../be/seed-capabilities";
|
|
28
|
+
import {
|
|
29
|
+
loadEnabledExtensions,
|
|
30
|
+
setExtensionLoopbackBaseUrl,
|
|
31
|
+
stopExtensionRuntime,
|
|
32
|
+
} from "../extensions/lifecycle";
|
|
28
33
|
import { initGitHub } from "../github";
|
|
29
34
|
import { initGitLab } from "../gitlab";
|
|
30
35
|
import { stopHeartbeat } from "../heartbeat";
|
|
@@ -65,6 +70,7 @@ import { handleCore, loadGlobalConfigsIntoEnv } from "./core";
|
|
|
65
70
|
import { handleDbQuery } from "./db-query";
|
|
66
71
|
import { handleEcosystem } from "./ecosystem";
|
|
67
72
|
import { handleEvents } from "./events";
|
|
73
|
+
import { handleExtensions } from "./extensions";
|
|
68
74
|
import { handleFavorites } from "./favorites";
|
|
69
75
|
import { handleFs } from "./fs";
|
|
70
76
|
import { handleHeartbeat } from "./heartbeat";
|
|
@@ -151,6 +157,7 @@ const globalState = globalThis as typeof globalThis & {
|
|
|
151
157
|
__apiGcInterval?: ReturnType<typeof setInterval>;
|
|
152
158
|
__runId?: string;
|
|
153
159
|
__closeRealtime?: () => void;
|
|
160
|
+
__stopExtensions?: () => Promise<void>;
|
|
154
161
|
};
|
|
155
162
|
|
|
156
163
|
const API_GC_INTERVAL_MS = 5 * 60 * 1000;
|
|
@@ -216,6 +223,7 @@ function startApiGcInterval() {
|
|
|
216
223
|
// Clean up previous server on hot reload
|
|
217
224
|
if (globalState.__httpServer) {
|
|
218
225
|
globalState.__closeRealtime?.();
|
|
226
|
+
await globalState.__stopExtensions?.();
|
|
219
227
|
console.log("[HTTP] Hot reload detected, closing previous server...");
|
|
220
228
|
globalState.__httpServer.close();
|
|
221
229
|
}
|
|
@@ -363,6 +371,7 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
363
371
|
() => handleApiKeys(req, res, pathSegments, queryParams),
|
|
364
372
|
() => handleHeartbeat(req, res, pathSegments),
|
|
365
373
|
() => handleEvents(req, res, pathSegments, queryParams, myAgentId),
|
|
374
|
+
() => handleExtensions(req, res, pathSegments, queryParams, myAgentId),
|
|
366
375
|
() => handleFavorites(req, res, pathSegments, queryParams, myAgentId),
|
|
367
376
|
() => handleUsers(req, res, pathSegments, queryParams),
|
|
368
377
|
() => handleSessions(req, res, pathSegments, queryParams),
|
|
@@ -431,6 +440,7 @@ globalState.__mcpSessionAgents = mcpSessionAgents;
|
|
|
431
440
|
globalState.__sessionUsers = sessionUsers;
|
|
432
441
|
globalState.__transportActivity = transportActivity;
|
|
433
442
|
globalState.__transportActivityUser = transportActivityUser;
|
|
443
|
+
globalState.__stopExtensions = stopExtensionRuntime;
|
|
434
444
|
|
|
435
445
|
async function shutdown() {
|
|
436
446
|
globalState.__closeRealtime?.();
|
|
@@ -477,6 +487,8 @@ async function shutdown() {
|
|
|
477
487
|
// Stop scratch-script retention garbage collector
|
|
478
488
|
stopScratchScriptGc();
|
|
479
489
|
|
|
490
|
+
await stopExtensionRuntime();
|
|
491
|
+
|
|
480
492
|
// Stop RBAC audit: retention GC, flush interval, final drain, detach sink
|
|
481
493
|
stopAuditGc();
|
|
482
494
|
stopAuditWriter();
|
|
@@ -549,9 +561,14 @@ startApiGcInterval();
|
|
|
549
561
|
|
|
550
562
|
// Load global swarm configs before the server starts listening so decrypt/key
|
|
551
563
|
// failures fail closed instead of leaving the runtime half-initialized.
|
|
564
|
+
// override=true: a value saved in the dashboard wins over the deployment env
|
|
565
|
+
// at boot, matching the reload path and `createServer`. Otherwise a rotated
|
|
566
|
+
// credential saved only in swarm_config silently reverts to the stale env
|
|
567
|
+
// value on the next deploy (2026-09-16 prod Slack outage). Reserved keys
|
|
568
|
+
// (API_KEY, SECRETS_ENCRYPTION_KEY, ...) stay env-only regardless.
|
|
552
569
|
let startupConfigsInjected: string[] = [];
|
|
553
570
|
try {
|
|
554
|
-
startupConfigsInjected = await loadGlobalConfigsIntoEnv(
|
|
571
|
+
startupConfigsInjected = await loadGlobalConfigsIntoEnv(true);
|
|
555
572
|
} catch (err) {
|
|
556
573
|
console.error("[startup] Failed to load global swarm configs before listen:", err);
|
|
557
574
|
process.exitCode = 1;
|
|
@@ -605,6 +622,15 @@ try {
|
|
|
605
622
|
console.error("[startup] Failed to seed built-in entities:", err);
|
|
606
623
|
}
|
|
607
624
|
|
|
625
|
+
try {
|
|
626
|
+
await loadEnabledExtensions();
|
|
627
|
+
} catch (err) {
|
|
628
|
+
console.error(
|
|
629
|
+
"[startup] Failed to initialize extensions:",
|
|
630
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
608
634
|
// Wire the RBAC permission-audit sink into can() and start the batched writer
|
|
609
635
|
// (2s flush) + retention GC (daily tick) BEFORE the server accepts traffic —
|
|
610
636
|
// installing it inside the listen callback would leave an unaudited startup
|
|
@@ -622,6 +648,10 @@ await initOtel("api");
|
|
|
622
648
|
|
|
623
649
|
httpServer
|
|
624
650
|
.listen(port, async () => {
|
|
651
|
+
const boundAddress = httpServer.address();
|
|
652
|
+
if (boundAddress && typeof boundAddress === "object") {
|
|
653
|
+
setExtensionLoopbackBaseUrl(`http://127.0.0.1:${boundAddress.port}`);
|
|
654
|
+
}
|
|
625
655
|
console.log(`MCP HTTP server running on http://localhost:${port}/mcp`);
|
|
626
656
|
|
|
627
657
|
ensure({
|
|
@@ -656,8 +686,16 @@ httpServer
|
|
|
656
686
|
await emitBuiltInIntegrationConnectedOnce("github");
|
|
657
687
|
}
|
|
658
688
|
|
|
659
|
-
// Start Slack bot (if configured)
|
|
660
|
-
|
|
689
|
+
// Start Slack bot (if configured). Never let a Slack failure abort the
|
|
690
|
+
// rest of this callback: on 2026-09-16 an invalid app token made
|
|
691
|
+
// app.start() reject here, and the scheduler, heartbeat, script-run
|
|
692
|
+
// supervisor, and OAuth sweeps below never started while /health stayed
|
|
693
|
+
// green for 7 hours. Slack is one integration, not the boot path.
|
|
694
|
+
try {
|
|
695
|
+
await startSlackApp();
|
|
696
|
+
} catch (err) {
|
|
697
|
+
console.error("[Slack] Failed to start, continuing boot without Slack:", err);
|
|
698
|
+
}
|
|
661
699
|
|
|
662
700
|
// Independent of workers, scheduler targets, and heartbeat agent tasks.
|
|
663
701
|
startQueueStallAlarm();
|