@desplega.ai/agent-swarm 1.131.1 → 1.134.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 +3 -3
- package/dist/{actions-jgcy477k.js → actions-7kast243.js} +20 -18
- package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-706yad38.js} +33 -17
- package/dist/app-0g84t8wf.js +28 -0
- package/dist/{artifact-jysj3wrg.js → artifact-dje5ke4h.js} +37 -131
- package/dist/{assistant-f2vkk1hj.js → assistant-ac4htcky.js} +25 -22
- package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-be59fbsz.js} +13 -13
- package/dist/{boot-reembed-q7mp8hcr.js → boot-reembed-b1rcqn85.js} +13 -11
- package/dist/{boot-reembed-xmw8wcx6.js → boot-reembed-kw2541c4.js} +14 -12
- package/dist/{boot-scrub-logs-dsqrv79n.js → boot-scrub-logs-ja29t92g.js} +11 -9
- package/dist/{claude-adapter-q6x7yspx.js → claude-adapter-5cg1fv8s.js} +33 -29
- package/dist/{claude-managed-adapter-wsg37bt8.js → claude-managed-adapter-9z9sdytx.js} +9 -9
- package/dist/{claude-managed-setup-52z4mq9w.js → claude-managed-setup-3zqp51cr.js} +5 -5
- package/dist/{claude-1ex232fc.js → claude-wqn3dtca.js} +1 -1
- package/dist/{cli-4wgngmwv.js → cli-00w4hbam.js} +1 -1
- package/dist/{cli-er3a4bb0.js → cli-15429qdw.js} +6 -1
- package/dist/{cli-e5dabkx0.js → cli-1cwhyvta.js} +4 -4
- package/dist/{cli-zsspvvm2.js → cli-1ybr6vw2.js} +237 -237
- package/dist/{cli-xpxde7rm.js → cli-2wv4kj0e.js} +60 -31
- package/dist/cli-34w2584r.js +530 -0
- package/dist/{cli-se2p6rkb.js → cli-38taxznr.js} +6 -6
- package/dist/{cli-9sbhdpvb.js → cli-3cgfyfzm.js} +14 -14
- package/dist/{cli-5n6e5yfn.js → cli-3zmdzfjf.js} +79 -74
- package/dist/{cli-3ty0t082.js → cli-43x36mty.js} +197 -8
- package/dist/{cli-ycrnynpw.js → cli-480nehnh.js} +29 -22
- package/dist/{cli-0258203w.js → cli-492e7ap5.js} +140 -20
- package/dist/{cli-3fj60etf.js → cli-497b685t.js} +3 -3
- package/dist/{cli-pxfxkx5m.js → cli-4bap00k0.js} +1 -1
- package/dist/{cli-w2wxd56v.js → cli-4bc8vmhm.js} +848 -770
- package/dist/{cli-aca5pz9f.js → cli-589599p9.js} +1 -1
- package/dist/{cli-t930g0xh.js → cli-5tnr64pm.js} +1 -1
- package/dist/{cli-jxg3s9ey.js → cli-65d93ves.js} +7 -7
- package/dist/{cli-6dpytdr2.js → cli-6x363v1z.js} +2 -2
- package/dist/{cli-dxak0fqd.js → cli-787ph852.js} +11 -11
- package/dist/{cli-793jvbsh.js → cli-7bv8nckk.js} +16 -11
- package/dist/{cli-zyxs93y9.js → cli-7jcc9y1r.js} +3 -3
- package/dist/{cli-f1am9szv.js → cli-87t2gstf.js} +1 -1
- package/dist/{cli-9aryf9tv.js → cli-95m51ztz.js} +1 -1
- package/dist/{cli-4w4fmq9x.js → cli-9w49enmh.js} +2 -2
- package/dist/{cli-v69vk5h0.js → cli-9y5b02vr.js} +13 -13
- package/dist/{cli-x23me2mg.js → cli-a1rrkrb4.js} +1 -1
- package/dist/{cli-psrqtw2m.js → cli-a3pkfpqs.js} +2 -2
- package/dist/{cli-h0rgxd24.js → cli-a4ajmfvp.js} +6 -6
- package/dist/{cli-ygrbmjja.js → cli-aw8bb6sf.js} +6 -6
- package/dist/{cli-g74vpjp2.js → cli-brm3et47.js} +55 -22
- package/dist/{cli-czkae1fp.js → cli-c5ddbdfj.js} +9 -9
- package/dist/{cli-114fvtmj.js → cli-c79a9zrc.js} +1 -1
- package/dist/{cli-49n9f49x.js → cli-dx3dz0ms.js} +3 -3
- package/dist/{cli-6dp3ydxa.js → cli-e07exmsb.js} +18 -14
- package/dist/cli-et1bbwqf.js +13 -0
- package/dist/{cli-yazx6hap.js → cli-f3s9dry0.js} +14 -14
- package/dist/{cli-t8d349cy.js → cli-f4wj0s3m.js} +3 -3
- package/dist/{cli-d6n0thxw.js → cli-f64gdfxc.js} +1 -1
- package/dist/{cli-pwfsqc2x.js → cli-fyd58xgb.js} +55 -55
- package/dist/{cli-7dm2zqy2.js → cli-g0j14czv.js} +2 -2
- package/dist/{cli-b0p7rfnd.js → cli-g2p9bd1z.js} +14 -14
- package/dist/{cli-z905tvmm.js → cli-g814jbst.js} +2 -2
- package/dist/{cli-5dsbcjjw.js → cli-ghexzd2d.js} +970 -970
- package/dist/{cli-jvv0g8bh.js → cli-gpt9kf01.js} +15 -15
- package/dist/{cli-rgrfzg8z.js → cli-grt6qv91.js} +380 -380
- package/dist/{cli-wz9115x0.js → cli-gtafvdy0.js} +8 -8
- package/dist/{cli-q5dzzak0.js → cli-gy07xk4s.js} +2 -2
- package/dist/{cli-sy7jk3tx.js → cli-h0djenzm.js} +2 -2
- package/dist/{cli-nrkkknat.js → cli-h37gbjbf.js} +2 -281
- package/dist/{cli-jevgrb4w.js → cli-h39xxnq6.js} +1200 -491
- package/dist/{cli-gz9sbyk3.js → cli-jepmgn7r.js} +1 -1
- package/dist/{cli-ccf9vy7q.js → cli-jmvk6et8.js} +7 -7
- package/dist/{cli-2rk0cx7s.js → cli-jxj92gkt.js} +51 -41
- package/dist/{cli-bqw0mt1n.js → cli-k46t4664.js} +18 -3
- package/dist/{cli-50vbt00w.js → cli-m07313tr.js} +1153 -696
- package/dist/{cli-4j2mrzag.js → cli-mkq1rk7b.js} +1 -1
- package/dist/{cli-1z7048dy.js → cli-mnm7w46e.js} +1 -1
- package/dist/{cli-kd1ck4rx.js → cli-mp9sg4cg.js} +14 -8
- package/dist/{cli-jbcva0xj.js → cli-mrammd8c.js} +60 -35
- package/dist/cli-nanaxrz6.js +27 -0
- package/dist/{cli-p9swy5t3.js → cli-pq2ee5ja.js} +8 -6
- package/dist/{cli-2wadx7p2.js → cli-pt43pdp1.js} +2 -2
- package/dist/{cli-7sgrxg9a.js → cli-q6d0vvey.js} +4 -4
- package/dist/{cli-k9qkf1hw.js → cli-qf9kpsve.js} +9 -9
- package/dist/cli-qh4sqfws.js +15 -0
- package/dist/{cli-e15jhafb.js → cli-qntkxysp.js} +14 -14
- package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
- package/dist/{cli-z2zcxes1.js → cli-qt5bt3vk.js} +1 -1
- package/dist/{cli-wx82tw1k.js → cli-qtekrty1.js} +3 -3
- package/dist/{cli-g1re8r05.js → cli-rh6f3s9n.js} +3 -3
- package/dist/{cli-n0krnc6d.js → cli-rzhcrw2p.js} +2 -2
- package/dist/{cli-gecy3z2s.js → cli-snw62ze5.js} +4 -4
- package/dist/{cli-n6h8qt8n.js → cli-snz3p4yf.js} +15 -10
- package/dist/{cli-r6kqqbj0.js → cli-tkf8n6cw.js} +11 -11
- package/dist/{cli-4rkfg1wx.js → cli-tkrr24mz.js} +14 -9
- package/dist/{cli-9dr5qzc0.js → cli-tv9f1kyb.js} +54 -43
- package/dist/{cli-gm9cbhbq.js → cli-vtz6yj3v.js} +28 -9
- package/dist/{cli-2f07m0xr.js → cli-wf86d5t7.js} +1 -1
- package/dist/{cli-7zd05xb4.js → cli-whrvct19.js} +45 -45
- package/dist/{cli-ap1czx8f.js → cli-wrrea8xm.js} +3 -3
- package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
- package/dist/{cli-cwy5hqbq.js → cli-yjmk7tzc.js} +1 -1
- package/dist/{cli-6pcz9h15.js → cli-zykjrwcx.js} +180 -394
- package/dist/cli.js +45 -45
- package/dist/{codex-adapter-htjkn22b.js → codex-adapter-hsq6mz3m.js} +21 -19
- package/dist/{codex-hook-hq54t9ft.js → codex-hook-8c500gq8.js} +8 -7
- package/dist/{codex-login-z6yhbkmk.js → codex-login-x7603481.js} +3 -3
- package/dist/{codex-session-runner-wjfc7smz.js → codex-session-runner-c1x3x143.js} +15 -13
- package/dist/{commands-xhrfwqsk.js → commands-rwtddtst.js} +11 -9
- package/dist/{db-anvwb22f.js → db-j2b355vz.js} +458 -444
- package/dist/{devin-adapter-v1sarqtw.js → devin-adapter-8qmhqpy3.js} +3 -3
- package/dist/{devtools-2y1r7yqk.js → devtools-j2rnj8w7.js} +4 -4
- package/dist/{e2b-jdkwemv2.js → e2b-znpx78eg.js} +16 -16
- package/dist/{es5-vjgtkgp6.js → es5-fmqrjzy3.js} +2 -2
- package/dist/{event-bus-axn823e9.js → event-bus-n8sb35pw.js} +4 -4
- package/dist/{getMachineId-bsd-6tc9wxdj.js → getMachineId-bsd-wpzbhkm6.js} +4 -4
- package/dist/{getMachineId-darwin-sjt9sgnc.js → getMachineId-darwin-45zabjy0.js} +4 -4
- package/dist/{getMachineId-linux-v5n18y9p.js → getMachineId-linux-warnp5pd.js} +3 -3
- package/dist/{getMachineId-unsupported-yrjyqw3d.js → getMachineId-unsupported-5spent2a.js} +3 -3
- package/dist/{getMachineId-win-w3r8063c.js → getMachineId-win-v4bvcw7a.js} +4 -4
- package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-b0wzss55.js} +17 -14
- package/dist/{google-vertex-p5arrga0.js → google-vertex-e8q4dpya.js} +18 -15
- package/dist/{handlers-b9kbc621.js → handlers-hchgrxkh.js} +32 -29
- package/dist/{hook-xkvn6c5h.js → hook-p49egqc0.js} +66 -52
- package/dist/hook-spb00w76.js +34 -0
- package/dist/{http-9xh6ta4r.js → http-nh14kkms.js} +663 -181
- package/dist/index-1qthq7vc.js +6 -0
- package/dist/{index-0g68f0p6.js → index-20xqmn3v.js} +3 -3
- package/dist/{index-vdkr5z2w.js → index-3e03k6e4.js} +114 -114
- package/dist/{index-ywds4812.js → index-4cbyhq2g.js} +43 -41
- package/dist/index-67pzqfrb.js +36 -0
- package/dist/{index-bccrkdkn.js → index-7m8yxkw5.js} +16 -16
- package/dist/{index-eaxyqcmj.js → index-dvsk64sm.js} +163 -83
- package/dist/{index-dqkpnmqm.js → index-e19s2m8k.js} +18 -18
- package/dist/{index-j30k5zxn.js → index-emvxcy98.js} +16 -16
- package/dist/{index-7ndw6ecy.js → index-gqnmr60d.js} +9 -9
- package/dist/{index-9znhar3x.js → index-j02xvjtd.js} +184 -185
- package/dist/{index-1s2442yw.js → index-ns5kksc1.js} +7 -7
- package/dist/{index-dpy2hth8.js → index-pdxc8kfh.js} +71 -71
- package/dist/{index-z1m7pw0v.js → index-ptcqm8bc.js} +107 -107
- package/dist/{index-070v4eyb.js → index-pyp7rexm.js} +5 -6
- package/dist/{index-86znjz1p.js → index-shne41sd.js} +13 -13
- package/dist/{index-2qn2qndv.js → index-t36q2v4t.js} +183 -183
- package/dist/index-tcnvv8w6.js +7 -0
- package/dist/index-w91qbfv1.js +7 -0
- package/dist/{index-fzc0mwvj.js → index-yqweff5n.js} +32 -32
- package/dist/index-yrfphye1.js +39 -0
- package/dist/{keepalive-vc7s2aa6.js → keepalive-9k51nqhc.js} +21 -15
- package/dist/lead-2xq3dxbz.js +88 -0
- package/dist/maintenance-0c0g6nym.js +28 -0
- package/dist/mistral-conversations-2njwdwhj.js +717 -0
- package/dist/{multipart-parser-qaktc7p8.js → multipart-parser-6gftesw0.js} +2 -2
- package/dist/{oauth-refresh-sweep-yecgjpk4.js → oauth-refresh-sweep-273a64r2.js} +14 -12
- package/dist/{onboard-xj0f73f9.js → onboard-tsv9wwhz.js} +12 -12
- package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-kt84eq86.js} +26 -26
- package/dist/{openai-completions-3vkmfz2a.js → openai-completions-mrypq7qp.js} +17 -16
- package/dist/{openai-responses-dg4t7j7a.js → openai-responses-qhrk21ez.js} +20 -17
- package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-rar9676j.js} +13 -11
- package/dist/{openrouter-images-g3gm8931.js → openrouter-images-684fysx9.js} +5 -5
- package/dist/{otel-impl-nxg0n0b3.js → otel-impl-wp1mfzd2.js} +752 -537
- package/dist/{photon_rs-ky7dj5qw.js → photon_rs-8dwt2pzy.js} +581 -581
- package/dist/{pi-messages-tqj8q6b9.js → pi-messages-txp9f97r.js} +7 -7
- package/dist/{pi-mono-adapter-4g5kby31.js → pi-mono-adapter-yz47kk8e.js} +1910 -1180
- package/dist/pricing-refresh-jw1yw2fn.js +22 -0
- package/dist/rbac-roles-dg964c8p.js +32 -0
- package/dist/{rbac-roles-1hcxdtg7.js → rbac-roles-p53cg5cv.js} +19 -17
- package/dist/{registry-02cy9z5z.js → registry-mh12y17v.js} +4 -4
- package/dist/render-v2-56vn2rk9.js +36 -0
- package/dist/seed-pricing-52xx6z63.js +19 -0
- package/dist/{setup-46xtgq3c.js → setup-e8gp79sy.js} +7 -7
- package/dist/token-util-zy908s1a.js +6 -0
- package/dist/{token-fxkn2zf2.js → token-xg5711x5.js} +4 -4
- package/dist/worker-s17f1g46.js +88 -0
- package/dist/{x-6mg4kfpe.js → x-xkfh0w0v.js} +5 -5
- package/openapi.json +566 -22
- package/package.json +6 -6
- package/src/be/budget-refusal-notify.ts +14 -7
- package/src/be/db-queries/task-artifacts.ts +182 -0
- package/src/be/db.ts +834 -171
- package/src/be/events.ts +5 -0
- package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
- package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
- package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
- package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
- package/src/be/migrations/136_task_requester_provenance.sql +37 -0
- package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
- package/src/be/migrations/runner.ts +17 -11
- package/src/be/multi-runtime.ts +430 -0
- package/src/be/scripts/extract-schema.ts +3 -1
- package/src/be/scripts/typecheck.ts +6 -2
- package/src/be/seed-prompt-templates.ts +25 -2
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/steering.ts +1 -1
- package/src/be/swarm-config-guard.ts +18 -0
- package/src/commands/credential-wait.ts +54 -0
- package/src/commands/onboard/steps/core-credentials.tsx +1 -1
- package/src/commands/onboard/steps/custom-templates.tsx +1 -1
- package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
- package/src/commands/onboard/steps/health-check.tsx +1 -1
- package/src/commands/onboard/steps/start.tsx +1 -1
- package/src/commands/profile-sync-audit.ts +56 -0
- package/src/commands/profile-sync.ts +287 -15
- package/src/commands/provider-credentials.ts +46 -19
- package/src/commands/runner.ts +199 -33
- package/src/commands/templates.ts +22 -0
- package/src/github/handlers.ts +55 -9
- package/src/heartbeat/heartbeat.ts +109 -17
- package/src/hooks/hook.ts +49 -33
- package/src/http/active-sessions.ts +11 -1
- package/src/http/agents.ts +261 -30
- package/src/http/all-routes.ts +1 -0
- package/src/http/config.ts +13 -2
- package/src/http/core.ts +102 -4
- package/src/http/db-query-bounded.ts +313 -0
- package/src/http/db-query-shared.ts +249 -0
- package/src/http/db-query.ts +54 -42
- package/src/http/events.ts +2 -0
- package/src/http/index.ts +23 -2
- package/src/http/mcp-bridge.ts +37 -3
- package/src/http/mcp.ts +12 -0
- package/src/http/memory.ts +13 -2
- package/src/http/openapi.ts +1 -0
- package/src/http/poll.ts +70 -27
- package/src/http/prompt-templates.ts +36 -13
- package/src/http/route-def.ts +43 -9
- package/src/http/schedules.ts +2 -2
- package/src/http/scripts.ts +10 -1
- package/src/http/session-data.ts +44 -0
- package/src/http/tasks.ts +2 -2
- package/src/http/users.ts +74 -52
- package/src/http/webhooks.ts +5 -2
- package/src/http/workflows.ts +1 -4
- package/src/integrations/kapso/inbound.ts +1 -1
- package/src/jira/outbound.ts +39 -8
- package/src/jira/webhook-lifecycle.ts +13 -2
- package/src/linear/outbound.ts +43 -10
- package/src/oauth/keepalive.ts +13 -2
- package/src/prompts/base-prompt.ts +1 -1
- package/src/prompts/default-drift.ts +21 -0
- package/src/prompts/session-templates.ts +56 -7
- package/src/providers/claude-adapter.ts +6 -0
- package/src/providers/codex-adapter.ts +6 -0
- package/src/providers/opencode-adapter.ts +6 -0
- package/src/providers/pi-mono-adapter.ts +8 -0
- package/src/providers/pricing-sources.md +24 -0
- package/src/queue-stall-alarm.ts +216 -0
- package/src/rbac/legacy-policy.ts +3 -0
- package/src/rbac/permissions.ts +12 -0
- package/src/scheduler/scheduler.ts +7 -0
- package/src/script-workflows/executor.ts +2 -1
- package/src/scripts-runtime/executors/native.ts +2 -1
- package/src/scripts-runtime/executors/types.ts +5 -0
- package/src/scripts-runtime/loader.ts +5 -0
- package/src/scripts-runtime/sdk-allowlist.ts +3 -0
- package/src/scripts-runtime/swarm-config.ts +8 -0
- package/src/scripts-runtime/swarm-sdk.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +4 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +4 -0
- package/src/server.ts +6 -0
- package/src/slack/channel-join.ts +22 -3
- package/src/slack/channel-lifecycle.ts +114 -0
- package/src/tests/budget-claim-gate.test.ts +20 -0
- package/src/tests/claude-adapter.test.ts +32 -0
- package/src/tests/codex-adapter.test.ts +30 -0
- package/src/tests/create-task-validation.test.ts +128 -0
- package/src/tests/credential-wait.test.ts +67 -1
- package/src/tests/db-query.test.ts +711 -2
- package/src/tests/heartbeat-checklist.test.ts +28 -0
- package/src/tests/http-api-integration.test.ts +81 -0
- package/src/tests/http-users.test.ts +166 -1
- package/src/tests/identity-field-budget.test.ts +176 -0
- package/src/tests/mcp-tools-user.test.ts +34 -0
- package/src/tests/mcp-user-route.test.ts +17 -0
- package/src/tests/metrics-http.test.ts +44 -0
- package/src/tests/migration-failure.test.ts +86 -0
- package/src/tests/multi-runtime-registration.test.ts +2810 -0
- package/src/tests/openapi-response-contract.test.ts +24 -0
- package/src/tests/openapi-route-headers.test.ts +83 -0
- package/src/tests/opencode-adapter.test.ts +40 -0
- package/src/tests/outbound-listener-rejection.test.ts +138 -0
- package/src/tests/pi-mono-adapter.test.ts +98 -0
- package/src/tests/preload.ts +2 -0
- package/src/tests/profile-sync-warning.test.ts +277 -0
- package/src/tests/profile-sync.test.ts +232 -6
- package/src/tests/prompt-template-resolver.test.ts +177 -6
- package/src/tests/prompt-template-session.test.ts +39 -3
- package/src/tests/queue-stall-alarm.test.ts +226 -0
- package/src/tests/rbac-charact-slack.test.ts +29 -0
- package/src/tests/rbac-engine.test.ts +3 -0
- package/src/tests/requester-comms.test.ts +32 -0
- package/src/tests/runner-requester-profile.test.ts +38 -0
- package/src/tests/schedule-target-type.test.ts +38 -0
- package/src/tests/script-connections.test.ts +13 -1
- package/src/tests/scripts-runtime-identity.test.ts +375 -0
- package/src/tests/scripts-runtime.test.ts +4 -3
- package/src/tests/sdk-allowlist.test.ts +47 -0
- package/src/tests/send-task-requested-by.test.ts +9 -0
- package/src/tests/session-costs.test.ts +506 -1
- package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
- package/src/tests/slack-channel-lifecycle.test.ts +241 -0
- package/src/tests/slack-render-v2.test.ts +43 -0
- package/src/tests/steering-core.test.ts +28 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +22 -0
- package/src/tests/task-artifacts.test.ts +386 -0
- package/src/tests/task-attachments-schema.test.ts +17 -1
- package/src/tests/task-completion-idempotency.test.ts +57 -0
- package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
- package/src/tests/tool-annotations.test.ts +4 -0
- package/src/tests/update-profile-api.test.ts +67 -39
- package/src/tests/update-profile-auth.test.ts +48 -4
- package/src/tests/user-attribution-migration.test.ts +54 -0
- package/src/tests/user-identity.test.ts +72 -0
- package/src/tests/workflow-async-v2.test.ts +39 -1
- package/src/tests/workflow-http-v2.test.ts +3 -2
- package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
- package/src/tests/workflow-retry-v2.test.ts +45 -1
- package/src/tools/db-query.ts +18 -8
- package/src/tools/get-task-details.ts +24 -3
- package/src/tools/manage-user.ts +36 -3
- package/src/tools/poll-task.ts +93 -14
- package/src/tools/script-common.ts +5 -0
- package/src/tools/send-task.ts +1 -4
- package/src/tools/slack-archive-channel.ts +70 -0
- package/src/tools/slack-create-channel.ts +74 -0
- package/src/tools/slack-invite-to-channel.ts +78 -0
- package/src/tools/store-progress.ts +13 -2
- package/src/tools/swarm-config/delete-config.ts +9 -0
- package/src/tools/swarm-config/set-config.ts +3 -2
- package/src/tools/task-action.ts +30 -0
- package/src/tools/task-tool-ctx.ts +8 -1
- package/src/tools/tool-config.ts +4 -1
- package/src/tools/update-profile.ts +16 -21
- package/src/tools/utils.ts +5 -0
- package/src/tools/workflows/trigger-workflow.ts +7 -1
- package/src/types.ts +165 -1
- package/src/utils/github-pull-request.ts +76 -0
- package/src/utils/identity-field-budget.ts +73 -0
- package/src/utils/multi-runtime.ts +20 -0
- package/src/utils/requester-comms.ts +24 -0
- package/src/workflows/engine.ts +22 -2
- package/src/workflows/resume.ts +19 -11
- package/src/workflows/retry-poller.ts +1 -0
- package/src/workflows/triggers.ts +4 -0
- package/templates/skills/db-query-guidance/config.json +14 -0
- package/templates/skills/db-query-guidance/content.md +57 -0
- package/dist/app-5da78bd4.js +0 -26
- package/dist/cli-3te4jdkm.js +0 -225
- package/dist/cli-bmze986d.js +0 -7
- package/dist/cli-kh3ba2zf.js +0 -607
- package/dist/hook-cgk4wxmx.js +0 -31
- package/dist/index-25wjsdv2.js +0 -34
- package/dist/index-76at2ymp.js +0 -7
- package/dist/index-hase6zbe.js +0 -6
- package/dist/index-vg4snay4.js +0 -36
- package/dist/index-vy8edt27.js +0 -7
- package/dist/lead-r2yqfmx7.js +0 -85
- package/dist/maintenance-mw70k468.js +0 -26
- package/dist/mistral-conversations-81fwav0j.js +0 -30578
- package/dist/otel-nqa12wf3.js +0 -3149
- package/dist/pricing-refresh-2cn6effr.js +0 -20
- package/dist/rbac-roles-vynday5g.js +0 -30
- package/dist/render-v2-tc0bh8kk.js +0 -34
- package/dist/seed-pricing-8kceb43f.js +0 -17
- package/dist/token-util-pqyc8kng.js +0 -6
- package/dist/worker-qrbz6hmv.js +0 -85
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { getDb } from "./be/db";
|
|
2
|
+
import { getSlackApp } from "./slack/app";
|
|
3
|
+
import { scrubSecrets } from "./utils/secret-scrubber";
|
|
4
|
+
|
|
5
|
+
export const QUEUE_STALL_THRESHOLD_MS = 30 * 60 * 1000;
|
|
6
|
+
export const QUEUE_STALL_CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
|
7
|
+
const QUEUE_STALL_STARTUP_DELAY_MS = 10_000;
|
|
8
|
+
|
|
9
|
+
export interface QueueStallSnapshot {
|
|
10
|
+
claimableCount: number;
|
|
11
|
+
oldestTaskId: string | null;
|
|
12
|
+
oldestClaimableAt: string | null;
|
|
13
|
+
oldestAgeMs: number;
|
|
14
|
+
recentPickupCount: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type QueueStallNotifier = (message: string) => Promise<void>;
|
|
18
|
+
|
|
19
|
+
let alarmInterval: ReturnType<typeof setInterval> | null = null;
|
|
20
|
+
let startupTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
21
|
+
let checkInFlight = false;
|
|
22
|
+
let alarmActive = false;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read the queue denominator and oldest age in one snapshot. Tasks with unmet
|
|
26
|
+
* dependencies are excluded because a worker cannot start them yet.
|
|
27
|
+
*/
|
|
28
|
+
export function getQueueStallSnapshot(now = new Date()): QueueStallSnapshot {
|
|
29
|
+
const oldest = getDb()
|
|
30
|
+
.prepare<
|
|
31
|
+
{ claimableCount: number; oldestTaskId: string | null; oldestClaimableAt: string | null },
|
|
32
|
+
[]
|
|
33
|
+
>(
|
|
34
|
+
`WITH queued AS (
|
|
35
|
+
SELECT
|
|
36
|
+
t.id,
|
|
37
|
+
t.dependsOn,
|
|
38
|
+
COALESCE(
|
|
39
|
+
(
|
|
40
|
+
SELECT MAX(entry.createdAt)
|
|
41
|
+
FROM agent_log entry
|
|
42
|
+
WHERE entry.taskId = t.id
|
|
43
|
+
AND entry.newValue = t.status
|
|
44
|
+
AND entry.eventType IN (
|
|
45
|
+
'task_created',
|
|
46
|
+
'task_status_change',
|
|
47
|
+
'task_released',
|
|
48
|
+
'task_accepted',
|
|
49
|
+
'task_rejected'
|
|
50
|
+
)
|
|
51
|
+
),
|
|
52
|
+
t.createdAt
|
|
53
|
+
) AS queueEnteredAt
|
|
54
|
+
FROM agent_tasks t
|
|
55
|
+
WHERE t.status IN ('pending', 'unassigned')
|
|
56
|
+
),
|
|
57
|
+
claimable AS (
|
|
58
|
+
SELECT
|
|
59
|
+
queued.id,
|
|
60
|
+
MAX(
|
|
61
|
+
queued.queueEnteredAt,
|
|
62
|
+
COALESCE(
|
|
63
|
+
(
|
|
64
|
+
SELECT MAX(COALESCE(prerequisite.finishedAt, prerequisite.createdAt))
|
|
65
|
+
FROM json_each(COALESCE(queued.dependsOn, '[]')) dep
|
|
66
|
+
JOIN agent_tasks prerequisite ON prerequisite.id = dep.value
|
|
67
|
+
),
|
|
68
|
+
queued.queueEnteredAt
|
|
69
|
+
)
|
|
70
|
+
) AS claimableAt
|
|
71
|
+
FROM queued
|
|
72
|
+
WHERE NOT EXISTS (
|
|
73
|
+
SELECT 1
|
|
74
|
+
FROM json_each(COALESCE(queued.dependsOn, '[]')) dep
|
|
75
|
+
LEFT JOIN agent_tasks prerequisite ON prerequisite.id = dep.value
|
|
76
|
+
WHERE prerequisite.id IS NULL OR prerequisite.status != 'completed'
|
|
77
|
+
)
|
|
78
|
+
),
|
|
79
|
+
oldest AS (
|
|
80
|
+
SELECT id, claimableAt FROM claimable ORDER BY claimableAt ASC LIMIT 1
|
|
81
|
+
)
|
|
82
|
+
SELECT
|
|
83
|
+
(SELECT COUNT(*) FROM claimable) AS claimableCount,
|
|
84
|
+
(SELECT id FROM oldest) AS oldestTaskId,
|
|
85
|
+
(SELECT claimableAt FROM oldest) AS oldestClaimableAt`,
|
|
86
|
+
)
|
|
87
|
+
.get();
|
|
88
|
+
|
|
89
|
+
const pickupCutoff = new Date(now.getTime() - QUEUE_STALL_THRESHOLD_MS).toISOString();
|
|
90
|
+
const recentPickupCount =
|
|
91
|
+
getDb()
|
|
92
|
+
.prepare<{ count: number }, [string]>(
|
|
93
|
+
`SELECT COUNT(*) AS count
|
|
94
|
+
FROM agent_log
|
|
95
|
+
WHERE eventType IN ('task_status_change', 'task_claimed')
|
|
96
|
+
AND oldValue IN ('pending', 'unassigned')
|
|
97
|
+
AND newValue = 'in_progress'
|
|
98
|
+
AND createdAt >= ?`,
|
|
99
|
+
)
|
|
100
|
+
.get(pickupCutoff)?.count ?? 0;
|
|
101
|
+
|
|
102
|
+
const oldestClaimableAt = oldest?.oldestClaimableAt ?? null;
|
|
103
|
+
return {
|
|
104
|
+
claimableCount: oldest?.claimableCount ?? 0,
|
|
105
|
+
oldestTaskId: oldest?.oldestTaskId ?? null,
|
|
106
|
+
oldestClaimableAt,
|
|
107
|
+
oldestAgeMs: oldestClaimableAt
|
|
108
|
+
? Math.max(0, now.getTime() - new Date(oldestClaimableAt).getTime())
|
|
109
|
+
: 0,
|
|
110
|
+
recentPickupCount,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isQueueStalled(snapshot: QueueStallSnapshot): boolean {
|
|
115
|
+
return snapshot.claimableCount > 0 && snapshot.oldestAgeMs >= QUEUE_STALL_THRESHOLD_MS;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatAge(ageMs: number): string {
|
|
119
|
+
return `${Math.floor(ageMs / 60_000)}m`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function formatCheckFailure(error: unknown): string {
|
|
123
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
124
|
+
return `[Queue Stall Alarm] Check or notification failed: ${scrubSecrets(message)}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function notifySlack(message: string): Promise<void> {
|
|
128
|
+
const channel = process.env.SLACK_ALERTS_CHANNEL?.trim();
|
|
129
|
+
if (!channel) throw new Error("SLACK_ALERTS_CHANNEL is not configured");
|
|
130
|
+
|
|
131
|
+
const app = getSlackApp();
|
|
132
|
+
if (!app) throw new Error("Slack is not connected");
|
|
133
|
+
|
|
134
|
+
await app.client.chat.postMessage({
|
|
135
|
+
channel,
|
|
136
|
+
text: message,
|
|
137
|
+
unfurl_links: false,
|
|
138
|
+
unfurl_media: false,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Run one detector tick. Exported so tests and operators can exercise it directly. */
|
|
143
|
+
export async function checkQueueStall(
|
|
144
|
+
now = new Date(),
|
|
145
|
+
notify: QueueStallNotifier = notifySlack,
|
|
146
|
+
): Promise<QueueStallSnapshot> {
|
|
147
|
+
const snapshot = getQueueStallSnapshot(now);
|
|
148
|
+
const stalled = isQueueStalled(snapshot);
|
|
149
|
+
|
|
150
|
+
if (stalled && !alarmActive) {
|
|
151
|
+
await notify(
|
|
152
|
+
`🚨 *Agent Swarm queue pickup stalled*\n` +
|
|
153
|
+
`Claimable tasks: *${snapshot.claimableCount}*\n` +
|
|
154
|
+
`Oldest wait: *${formatAge(snapshot.oldestAgeMs)}* (task \`${snapshot.oldestTaskId}\`)\n` +
|
|
155
|
+
`Pickups in the last 30m: *${snapshot.recentPickupCount}*\n\n` +
|
|
156
|
+
"This alarm runs in the API process and does not require an agent to claim a task.",
|
|
157
|
+
);
|
|
158
|
+
alarmActive = true;
|
|
159
|
+
console.error(
|
|
160
|
+
`[Queue Stall Alarm] Fired: claimable=${snapshot.claimableCount}, oldest=${snapshot.oldestTaskId}, age=${formatAge(snapshot.oldestAgeMs)}, recent_pickups=${snapshot.recentPickupCount}`,
|
|
161
|
+
);
|
|
162
|
+
} else if (!stalled && alarmActive) {
|
|
163
|
+
await notify(
|
|
164
|
+
`✅ *Agent Swarm queue pickup recovered*\nClaimable tasks: *${snapshot.claimableCount}*\nPickups in the last 30m: *${snapshot.recentPickupCount}*`,
|
|
165
|
+
);
|
|
166
|
+
alarmActive = false;
|
|
167
|
+
console.log("[Queue Stall Alarm] Recovery notification sent");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return snapshot;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function scheduleCheck(): void {
|
|
174
|
+
if (checkInFlight) return;
|
|
175
|
+
checkInFlight = true;
|
|
176
|
+
checkQueueStall()
|
|
177
|
+
.catch((error) => {
|
|
178
|
+
console.error(formatCheckFailure(error));
|
|
179
|
+
})
|
|
180
|
+
.finally(() => {
|
|
181
|
+
checkInFlight = false;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function startQueueStallAlarm(): void {
|
|
186
|
+
if (alarmInterval || startupTimeout) return;
|
|
187
|
+
|
|
188
|
+
console.log(
|
|
189
|
+
`[Queue Stall Alarm] Starting (threshold ${formatAge(QUEUE_STALL_THRESHOLD_MS)}, interval ${formatAge(QUEUE_STALL_CHECK_INTERVAL_MS)})`,
|
|
190
|
+
);
|
|
191
|
+
startupTimeout = setTimeout(() => {
|
|
192
|
+
startupTimeout = null;
|
|
193
|
+
scheduleCheck();
|
|
194
|
+
}, QUEUE_STALL_STARTUP_DELAY_MS);
|
|
195
|
+
alarmInterval = setInterval(scheduleCheck, QUEUE_STALL_CHECK_INTERVAL_MS);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function stopQueueStallAlarm(): void {
|
|
199
|
+
if (startupTimeout) {
|
|
200
|
+
clearTimeout(startupTimeout);
|
|
201
|
+
startupTimeout = null;
|
|
202
|
+
}
|
|
203
|
+
if (alarmInterval) {
|
|
204
|
+
clearInterval(alarmInterval);
|
|
205
|
+
alarmInterval = null;
|
|
206
|
+
}
|
|
207
|
+
checkInFlight = false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const _test = {
|
|
211
|
+
formatCheckFailure,
|
|
212
|
+
resetState(): void {
|
|
213
|
+
stopQueueStallAlarm();
|
|
214
|
+
alarmActive = false;
|
|
215
|
+
},
|
|
216
|
+
};
|
|
@@ -162,6 +162,9 @@ export const LEGACY_POLICY = {
|
|
|
162
162
|
"integration.slack.post": leadOnly,
|
|
163
163
|
"integration.slack.read": leadOnly,
|
|
164
164
|
"integration.slack.thread.start": leadOnly,
|
|
165
|
+
"integration.slack.channel.create": leadOnly,
|
|
166
|
+
"integration.slack.channel.invite": leadOnly,
|
|
167
|
+
"integration.slack.channel.archive": leadOnly,
|
|
165
168
|
"integration.slack.upload": leadOnly,
|
|
166
169
|
"integration.slack.delete": leadOnly,
|
|
167
170
|
"integration.slack.update": leadOnly,
|
package/src/rbac/permissions.ts
CHANGED
|
@@ -93,6 +93,18 @@ export const PERMISSIONS = {
|
|
|
93
93
|
description: "Start a thread in a direct Slack channel.",
|
|
94
94
|
namespace: "integration",
|
|
95
95
|
},
|
|
96
|
+
"integration.slack.channel.create": {
|
|
97
|
+
description: "Create a Slack channel.",
|
|
98
|
+
namespace: "integration",
|
|
99
|
+
},
|
|
100
|
+
"integration.slack.channel.invite": {
|
|
101
|
+
description: "Invite users to a Slack channel.",
|
|
102
|
+
namespace: "integration",
|
|
103
|
+
},
|
|
104
|
+
"integration.slack.channel.archive": {
|
|
105
|
+
description: "Archive a Slack channel.",
|
|
106
|
+
namespace: "integration",
|
|
107
|
+
},
|
|
96
108
|
"integration.slack.upload": {
|
|
97
109
|
description: "Upload a file to a direct Slack channel.",
|
|
98
110
|
namespace: "integration",
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getDb,
|
|
5
5
|
getDueScheduledTasks,
|
|
6
6
|
getScheduledTaskById,
|
|
7
|
+
getUserById,
|
|
7
8
|
getWorkflow,
|
|
8
9
|
updateScheduledTask,
|
|
9
10
|
} from "@/be/db";
|
|
@@ -117,10 +118,16 @@ export interface DispatchScheduleResult {
|
|
|
117
118
|
task?: AgentTask;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
function withoutDeletedRequester(schedule: ScheduledTask): ScheduledTask {
|
|
122
|
+
if (!schedule.createdBy || getUserById(schedule.createdBy)) return schedule;
|
|
123
|
+
return { ...schedule, createdBy: undefined };
|
|
124
|
+
}
|
|
125
|
+
|
|
120
126
|
export async function dispatchScheduleTarget(
|
|
121
127
|
schedule: ScheduledTask,
|
|
122
128
|
extraTags: string[] = [],
|
|
123
129
|
): Promise<DispatchScheduleResult> {
|
|
130
|
+
schedule = withoutDeletedRequester(schedule);
|
|
124
131
|
switch (schedule.targetType) {
|
|
125
132
|
case "workflow": {
|
|
126
133
|
if (!schedule.workflowId) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, rm } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import type { ScriptRun } from "../types";
|
|
5
6
|
import {
|
|
6
7
|
buildSandboxedCommand,
|
|
@@ -40,7 +41,7 @@ export interface ScriptExecutor {
|
|
|
40
41
|
|
|
41
42
|
export function getScriptWorkflowHarnessPath(): string {
|
|
42
43
|
const runtimeDir = process.env.SCRIPT_WORKFLOW_RUNTIME_DIR;
|
|
43
|
-
if (!runtimeDir) return new URL("./harness.ts", import.meta.url)
|
|
44
|
+
if (!runtimeDir) return fileURLToPath(new URL("./harness.ts", import.meta.url));
|
|
44
45
|
|
|
45
46
|
const bundledHarness = `${resolve(runtimeDir)}/harness.bundle.js`;
|
|
46
47
|
if (!existsSync(bundledHarness)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
1
2
|
import {
|
|
2
3
|
buildSandboxedCommand,
|
|
3
4
|
readStreamCapped,
|
|
@@ -120,7 +121,7 @@ export class NativeScriptExecutor implements ScriptExecutor {
|
|
|
120
121
|
// subprocesses cannot access. Use the pre-built bundle from real filesystem instead.
|
|
121
122
|
const harnessPath = process.env.SCRIPT_RUNTIME_DIR
|
|
122
123
|
? `${process.env.SCRIPT_RUNTIME_DIR}/eval-harness.bundle.js`
|
|
123
|
-
: new URL("../eval-harness.ts", import.meta.url)
|
|
124
|
+
: fileURLToPath(new URL("../eval-harness.ts", import.meta.url));
|
|
124
125
|
const controller = new AbortController();
|
|
125
126
|
let timedOut = false;
|
|
126
127
|
let killed = input.signal?.aborted ?? false;
|
|
@@ -10,6 +10,11 @@ export type SwarmConfigPayload = {
|
|
|
10
10
|
apiKey: { value: string; isSecret: true };
|
|
11
11
|
agentId: { value: string; isSecret: false };
|
|
12
12
|
mcpBaseUrl: { value: string; isSecret: false };
|
|
13
|
+
/**
|
|
14
|
+
* Per-boot runtime identity of the invoking worker process. System
|
|
15
|
+
* context like the agent identity — scripts never supply or override it.
|
|
16
|
+
*/
|
|
17
|
+
runtimeInstanceId?: { value: string; isSecret: false };
|
|
13
18
|
};
|
|
14
19
|
user: Record<string, { value: string; isSecret: boolean }>;
|
|
15
20
|
egressSecrets?: EgressSecretEntry[];
|
|
@@ -19,6 +19,8 @@ export type RunScriptInput = {
|
|
|
19
19
|
args?: unknown;
|
|
20
20
|
fsMode?: ScriptFsMode;
|
|
21
21
|
agentId: string;
|
|
22
|
+
/** Per-boot runtime identity of the invoking worker; system context, never script input. */
|
|
23
|
+
runtimeInstanceId?: string;
|
|
22
24
|
signal?: AbortSignal;
|
|
23
25
|
timeoutMs?: number;
|
|
24
26
|
mcpBaseUrl?: string;
|
|
@@ -50,6 +52,9 @@ async function buildConfigPayload(input: RunScriptInput): Promise<SwarmConfigPay
|
|
|
50
52
|
value: input.mcpBaseUrl ?? process.env.MCP_BASE_URL ?? "http://localhost:3013",
|
|
51
53
|
isSecret: false,
|
|
52
54
|
},
|
|
55
|
+
...(input.runtimeInstanceId
|
|
56
|
+
? { runtimeInstanceId: { value: input.runtimeInstanceId, isSecret: false as const } }
|
|
57
|
+
: {}),
|
|
53
58
|
},
|
|
54
59
|
user: input.userConfig ?? {},
|
|
55
60
|
egressSecrets: input.egressSecrets ?? (await buildEgressSecrets()),
|
|
@@ -69,6 +69,9 @@ export const SDK_TOOL_NAME_MAP = {
|
|
|
69
69
|
slack_post: "slack-post", // external: sends to Slack
|
|
70
70
|
slack_reply: "slack-reply", // external: sends to Slack
|
|
71
71
|
slack_startThread: "slack-start-thread", // external: sends to Slack
|
|
72
|
+
slack_createChannel: "slack-create-channel", // external: mutates Slack
|
|
73
|
+
slack_inviteToChannel: "slack-invite-to-channel", // external: mutates Slack
|
|
74
|
+
slack_archiveChannel: "slack-archive-channel", // external: mutates Slack
|
|
72
75
|
slack_uploadFile: "slack-upload-file", // external: sends to Slack
|
|
73
76
|
slack_downloadFile: "slack-download-file",
|
|
74
77
|
slack_delete: "slack-delete", // external: mutates Slack, destructive
|
|
@@ -5,6 +5,8 @@ export class SwarmConfig {
|
|
|
5
5
|
readonly apiKey: RedactedValue<string>;
|
|
6
6
|
readonly agentId: RedactedValue<string>;
|
|
7
7
|
readonly mcpBaseUrl: RedactedValue<string>;
|
|
8
|
+
/** Per-boot runtime identity of the invoking worker; system context, never script input. */
|
|
9
|
+
readonly runtimeInstanceId: RedactedValue<string> | undefined;
|
|
8
10
|
|
|
9
11
|
private readonly userValues: Map<string, RedactedValue<string>>;
|
|
10
12
|
|
|
@@ -21,6 +23,12 @@ export class SwarmConfig {
|
|
|
21
23
|
type: "system",
|
|
22
24
|
isSecret: payload.system.mcpBaseUrl.isSecret,
|
|
23
25
|
});
|
|
26
|
+
this.runtimeInstanceId = payload.system.runtimeInstanceId
|
|
27
|
+
? Redacted.make(payload.system.runtimeInstanceId.value, {
|
|
28
|
+
type: "system",
|
|
29
|
+
isSecret: payload.system.runtimeInstanceId.isSecret,
|
|
30
|
+
})
|
|
31
|
+
: undefined;
|
|
24
32
|
this.userValues = new Map(
|
|
25
33
|
Object.entries(payload.user ?? {}).map(([key, value]) => [
|
|
26
34
|
key,
|
|
@@ -14,6 +14,12 @@ function headers(config: SwarmConfig): Record<string, string> {
|
|
|
14
14
|
return {
|
|
15
15
|
Authorization: `Bearer ${Redacted.value(config.apiKey)}`,
|
|
16
16
|
"X-Agent-ID": Redacted.value(config.agentId),
|
|
17
|
+
// Per-boot identity of the invoking worker process (system context, not
|
|
18
|
+
// script input) — work-acquisition endpoints gate on it in multi-runtime
|
|
19
|
+
// mode.
|
|
20
|
+
...(config.runtimeInstanceId
|
|
21
|
+
? { "X-Runtime-Instance-ID": Redacted.value(config.runtimeInstanceId) }
|
|
22
|
+
: {}),
|
|
17
23
|
"Content-Type": "application/json",
|
|
18
24
|
};
|
|
19
25
|
}
|
|
@@ -168,6 +174,7 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
|
|
|
168
174
|
hideCompleted: body.hideCompleted,
|
|
169
175
|
consecutiveErrorsMin: body.consecutiveErrorsMin,
|
|
170
176
|
lastRunStatus: body.lastRunStatus,
|
|
177
|
+
fields: body.includeFull ? "full" : undefined,
|
|
171
178
|
}),
|
|
172
179
|
};
|
|
173
180
|
case "schedule_create":
|
|
@@ -158,6 +158,7 @@ declare module "swarm-sdk" {
|
|
|
158
158
|
hideCompleted?: boolean;
|
|
159
159
|
consecutiveErrorsMin?: number;
|
|
160
160
|
lastRunStatus?: "failed" | "succeeded";
|
|
161
|
+
includeFull?: boolean;
|
|
161
162
|
}): Promise<unknown>;
|
|
162
163
|
// --- scripts ---
|
|
163
164
|
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
@@ -325,6 +326,9 @@ declare module "swarm-sdk" {
|
|
|
325
326
|
message: string;
|
|
326
327
|
blocks?: unknown;
|
|
327
328
|
}): Promise<unknown>;
|
|
329
|
+
slack_createChannel(args: { name: string; isPrivate?: boolean }): Promise<unknown>;
|
|
330
|
+
slack_inviteToChannel(args: { channelId: string; userIds: string[] }): Promise<unknown>;
|
|
331
|
+
slack_archiveChannel(args: { channelId: string }): Promise<unknown>;
|
|
328
332
|
slack_uploadFile(args: Record<string, unknown>): Promise<unknown>;
|
|
329
333
|
slack_downloadFile(args: { url: string }): Promise<unknown>;
|
|
330
334
|
slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
|
|
@@ -140,6 +140,7 @@ declare module "swarm-sdk" {
|
|
|
140
140
|
hideCompleted?: boolean;
|
|
141
141
|
consecutiveErrorsMin?: number;
|
|
142
142
|
lastRunStatus?: "failed" | "succeeded";
|
|
143
|
+
includeFull?: boolean;
|
|
143
144
|
}): Promise<unknown>;
|
|
144
145
|
// --- scripts ---
|
|
145
146
|
script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
|
|
@@ -307,6 +308,9 @@ declare module "swarm-sdk" {
|
|
|
307
308
|
message: string;
|
|
308
309
|
blocks?: unknown;
|
|
309
310
|
}): Promise<unknown>;
|
|
311
|
+
slack_createChannel(args: { name: string; isPrivate?: boolean }): Promise<unknown>;
|
|
312
|
+
slack_inviteToChannel(args: { channelId: string; userIds: string[] }): Promise<unknown>;
|
|
313
|
+
slack_archiveChannel(args: { channelId: string }): Promise<unknown>;
|
|
310
314
|
slack_uploadFile(args: Record<string, unknown>): Promise<unknown>;
|
|
311
315
|
slack_downloadFile(args: { url: string }): Promise<unknown>;
|
|
312
316
|
slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
|
package/src/server.ts
CHANGED
|
@@ -119,8 +119,11 @@ import {
|
|
|
119
119
|
registerSkillUninstallTool,
|
|
120
120
|
registerSkillUpdateTool,
|
|
121
121
|
} from "./tools/skills";
|
|
122
|
+
import { registerSlackArchiveChannelTool } from "./tools/slack-archive-channel";
|
|
123
|
+
import { registerSlackCreateChannelTool } from "./tools/slack-create-channel";
|
|
122
124
|
import { registerSlackDeleteTool } from "./tools/slack-delete";
|
|
123
125
|
import { registerSlackDownloadFileTool } from "./tools/slack-download-file";
|
|
126
|
+
import { registerSlackInviteToChannelTool } from "./tools/slack-invite-to-channel";
|
|
124
127
|
import { registerSlackListChannelsTool } from "./tools/slack-list-channels";
|
|
125
128
|
import { registerSlackPostTool } from "./tools/slack-post";
|
|
126
129
|
import { registerSlackReadTool } from "./tools/slack-read";
|
|
@@ -504,6 +507,9 @@ export function createServer(opts: { scriptsOnly?: boolean; fullSurface?: boolea
|
|
|
504
507
|
registerSlackReadTool(server);
|
|
505
508
|
registerSlackPostTool(server);
|
|
506
509
|
registerSlackStartThreadTool(server);
|
|
510
|
+
registerSlackCreateChannelTool(server);
|
|
511
|
+
registerSlackInviteToChannelTool(server);
|
|
512
|
+
registerSlackArchiveChannelTool(server);
|
|
507
513
|
registerSlackListChannelsTool(server);
|
|
508
514
|
registerSlackUploadFileTool(server);
|
|
509
515
|
registerSlackDownloadFileTool(server);
|
|
@@ -2,14 +2,33 @@ import type { WebClient } from "@slack/web-api";
|
|
|
2
2
|
|
|
3
3
|
const logger = console;
|
|
4
4
|
|
|
5
|
+
type SlackErrorData = {
|
|
6
|
+
error?: unknown;
|
|
7
|
+
needed?: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
5
10
|
// @slack/web-api platform errors set message to "An API error occurred: <code>"
|
|
6
|
-
// and store
|
|
7
|
-
function
|
|
11
|
+
// and store Slack's full response body on error.data.
|
|
12
|
+
function slackErrorData(error: unknown): SlackErrorData | undefined {
|
|
8
13
|
if (!(error instanceof Error)) return undefined;
|
|
9
|
-
|
|
14
|
+
return (error as { data?: SlackErrorData }).data;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function slackCode(error: unknown): string | undefined {
|
|
18
|
+
const d = slackErrorData(error);
|
|
10
19
|
return typeof d?.error === "string" ? d.error : undefined;
|
|
11
20
|
}
|
|
12
21
|
|
|
22
|
+
export function slackMissingScopeMessage(error: unknown): string | undefined {
|
|
23
|
+
const data = slackErrorData(error);
|
|
24
|
+
if (data?.error !== "missing_scope" || typeof data.needed !== "string") return undefined;
|
|
25
|
+
|
|
26
|
+
const needed = data.needed.trim();
|
|
27
|
+
if (!needed) return undefined;
|
|
28
|
+
|
|
29
|
+
return `Slack requires the \`${needed}\` scope. Add it to slack-manifest.json, apply the updated manifest to the Slack app, then reinstall the app to the workspace for the change to take effect.`;
|
|
30
|
+
}
|
|
31
|
+
|
|
13
32
|
/**
|
|
14
33
|
* Returns true if the channel has any external (non-host-org) members.
|
|
15
34
|
* Uses Slack's documented flags: is_ext_shared (accepted Connect) and
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { WebClient } from "@slack/web-api";
|
|
2
|
+
import { slackCode } from "@/slack/channel-join";
|
|
3
|
+
|
|
4
|
+
export type CreateChannelResult = {
|
|
5
|
+
channelId: string;
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type InviteToChannelResult = {
|
|
10
|
+
alreadyInChannel: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ArchiveChannelResult = {
|
|
14
|
+
alreadyArchived: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function normalizeChannelName(name: string): string {
|
|
18
|
+
const normalized = name
|
|
19
|
+
.trim()
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
22
|
+
.replace(/^[-_]+|[-_]+$/g, "")
|
|
23
|
+
.slice(0, 80)
|
|
24
|
+
.replace(/[-_]+$/g, "");
|
|
25
|
+
|
|
26
|
+
if (!normalized) {
|
|
27
|
+
throw new Error("Slack channel name must contain at least one letter or number.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function createChannel(
|
|
34
|
+
client: WebClient,
|
|
35
|
+
{ name, isPrivate = false }: { name: string; isPrivate?: boolean },
|
|
36
|
+
): Promise<CreateChannelResult> {
|
|
37
|
+
const normalizedName = normalizeChannelName(name);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const result = await client.conversations.create({
|
|
41
|
+
name: normalizedName,
|
|
42
|
+
is_private: isPrivate,
|
|
43
|
+
});
|
|
44
|
+
const channelId = result.channel?.id;
|
|
45
|
+
if (!channelId) {
|
|
46
|
+
throw new Error("Slack created the channel but did not return its ID.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { channelId, name: normalizedName };
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (slackCode(error) === "name_taken") {
|
|
52
|
+
throw new Error(`Slack channel name "${normalizedName}" is already taken.`);
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function inviteToChannel(
|
|
59
|
+
client: WebClient,
|
|
60
|
+
channelId: string,
|
|
61
|
+
userIds: string[],
|
|
62
|
+
): Promise<InviteToChannelResult> {
|
|
63
|
+
if (userIds.length === 0) {
|
|
64
|
+
throw new Error("At least one Slack user ID is required.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await client.conversations.invite({
|
|
69
|
+
channel: channelId,
|
|
70
|
+
users: userIds.join(","),
|
|
71
|
+
});
|
|
72
|
+
return { alreadyInChannel: false };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (slackCode(error) !== "already_in_channel") {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let allAlreadyInChannel = true;
|
|
79
|
+
for (const userId of userIds) {
|
|
80
|
+
try {
|
|
81
|
+
await client.conversations.invite({ channel: channelId, users: userId });
|
|
82
|
+
allAlreadyInChannel = false;
|
|
83
|
+
} catch (userError) {
|
|
84
|
+
if (slackCode(userError) !== "already_in_channel") {
|
|
85
|
+
throw userError;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { alreadyInChannel: allAlreadyInChannel };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function archiveChannel(
|
|
95
|
+
client: WebClient,
|
|
96
|
+
channelId: string,
|
|
97
|
+
): Promise<ArchiveChannelResult> {
|
|
98
|
+
try {
|
|
99
|
+
await client.conversations.archive({ channel: channelId });
|
|
100
|
+
return { alreadyArchived: false };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const code = slackCode(error);
|
|
103
|
+
if (code === "already_archived") {
|
|
104
|
+
return { alreadyArchived: true };
|
|
105
|
+
}
|
|
106
|
+
if (code === "cant_archive_general") {
|
|
107
|
+
throw new Error("Slack's general channel cannot be archived.");
|
|
108
|
+
}
|
|
109
|
+
if (code === "cant_archive_required") {
|
|
110
|
+
throw new Error("This required Slack channel cannot be archived.");
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -128,6 +128,7 @@ describe("Phase 3 — /api/poll budget admission gate", () => {
|
|
|
128
128
|
if ("error" in body) throw new Error("unexpected error response");
|
|
129
129
|
expect(body.trigger?.type).toBe("task_assigned");
|
|
130
130
|
expect((body.trigger as { taskId: string }).taskId).toBe(task.id);
|
|
131
|
+
expect(getAgentById(worker.id)?.status).toBe("busy");
|
|
131
132
|
});
|
|
132
133
|
|
|
133
134
|
test("pending task trigger includes requester role and notes", async () => {
|
|
@@ -272,6 +273,25 @@ describe("Phase 3 — /api/poll budget admission gate", () => {
|
|
|
272
273
|
expect(claimedTriggers).toHaveLength(1);
|
|
273
274
|
expect(nullTriggers).toHaveLength(1);
|
|
274
275
|
});
|
|
276
|
+
|
|
277
|
+
test("pool claim marks the agent busy before the poll transaction returns", async () => {
|
|
278
|
+
const worker = createAgent({
|
|
279
|
+
name: "claim-status",
|
|
280
|
+
isLead: false,
|
|
281
|
+
status: "idle",
|
|
282
|
+
maxTasks: 1,
|
|
283
|
+
});
|
|
284
|
+
const pooled = createTaskExtended("claim status task", {});
|
|
285
|
+
|
|
286
|
+
expect(getAgentById(worker.id)?.status).toBe("idle");
|
|
287
|
+
|
|
288
|
+
const { body } = await callPoll(worker.id);
|
|
289
|
+
|
|
290
|
+
if ("error" in body) throw new Error("unexpected error response");
|
|
291
|
+
expect(body.trigger?.type).toBe("task_assigned");
|
|
292
|
+
expect((body.trigger as { taskId: string }).taskId).toBe(pooled.id);
|
|
293
|
+
expect(getAgentById(worker.id)?.status).toBe("busy");
|
|
294
|
+
});
|
|
275
295
|
});
|
|
276
296
|
|
|
277
297
|
// ─── MCP task-action accept tests ───────────────────────────────────────────
|
|
@@ -687,6 +687,38 @@ describe("mergeMcpConfig (issue #369)", () => {
|
|
|
687
687
|
expect((entry.headers as Record<string, string>)["X-Source-Task-Id"]).toBe(TASK_ID);
|
|
688
688
|
});
|
|
689
689
|
|
|
690
|
+
test("agent-swarm server gets X-Runtime-Instance-ID when a runtime identity is given", () => {
|
|
691
|
+
const base = {
|
|
692
|
+
mcpServers: {
|
|
693
|
+
"agent-swarm": {
|
|
694
|
+
type: "http",
|
|
695
|
+
url: "http://localhost:3013/mcp",
|
|
696
|
+
headers: { Authorization: "Bearer KEY", "X-Agent-ID": "a1" },
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
};
|
|
700
|
+
const merged = mergeMcpConfig(base, null, TASK_ID, undefined, "runtime-boot-1");
|
|
701
|
+
const agentSwarm = merged.mcpServers["agent-swarm"] as Record<string, unknown>;
|
|
702
|
+
expect((agentSwarm.headers as Record<string, string>)["X-Runtime-Instance-ID"]).toBe(
|
|
703
|
+
"runtime-boot-1",
|
|
704
|
+
);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("no runtime identity → no X-Runtime-Instance-ID header", () => {
|
|
708
|
+
const base = {
|
|
709
|
+
mcpServers: {
|
|
710
|
+
"agent-swarm": {
|
|
711
|
+
type: "http",
|
|
712
|
+
url: "http://localhost:3013/mcp",
|
|
713
|
+
headers: { Authorization: "Bearer KEY", "X-Agent-ID": "a1" },
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
const merged = mergeMcpConfig(base, null, TASK_ID);
|
|
718
|
+
const agentSwarm = merged.mcpServers["agent-swarm"] as Record<string, unknown>;
|
|
719
|
+
expect((agentSwarm.headers as Record<string, string>)["X-Runtime-Instance-ID"]).toBeUndefined();
|
|
720
|
+
});
|
|
721
|
+
|
|
690
722
|
test("does not mutate the input baseConfig", () => {
|
|
691
723
|
const base = {
|
|
692
724
|
mcpServers: {
|