@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
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { unlink } from "node:fs/promises";
|
|
3
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
3
5
|
import { closeDb, getPromptTemplates, initDb, upsertPromptTemplate } from "../be/db";
|
|
4
6
|
import { seedDefaultTemplates } from "../be/seed-prompt-templates";
|
|
7
|
+
import { handlePromptTemplates } from "../http/prompt-templates";
|
|
8
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
9
|
+
import { getPromptTemplateDefaultDrift } from "../prompts/default-drift";
|
|
5
10
|
import {
|
|
6
11
|
clearTemplateDefinitions,
|
|
7
12
|
getAllTemplateDefinitions,
|
|
@@ -13,6 +18,37 @@ import { restoreAllTemplateDefinitions } from "./template-registry-helpers";
|
|
|
13
18
|
|
|
14
19
|
const TEST_DB_PATH = "./test-prompt-resolver.sqlite";
|
|
15
20
|
|
|
21
|
+
async function dispatchPromptTemplates(path: string): Promise<unknown> {
|
|
22
|
+
const req = Readable.from([]) as IncomingMessage;
|
|
23
|
+
req.method = "GET";
|
|
24
|
+
req.url = path;
|
|
25
|
+
|
|
26
|
+
let body = "";
|
|
27
|
+
const res = {
|
|
28
|
+
headersSent: false,
|
|
29
|
+
writableEnded: false,
|
|
30
|
+
setHeader() {},
|
|
31
|
+
writeHead() {
|
|
32
|
+
this.headersSent = true;
|
|
33
|
+
return this;
|
|
34
|
+
},
|
|
35
|
+
end(chunk?: unknown) {
|
|
36
|
+
if (chunk !== undefined) body += String(chunk);
|
|
37
|
+
this.writableEnded = true;
|
|
38
|
+
return this;
|
|
39
|
+
},
|
|
40
|
+
} as unknown as ServerResponse;
|
|
41
|
+
|
|
42
|
+
const handled = await handlePromptTemplates(
|
|
43
|
+
req,
|
|
44
|
+
res,
|
|
45
|
+
getPathSegments(path),
|
|
46
|
+
parseQueryParams(path),
|
|
47
|
+
);
|
|
48
|
+
expect(handled).toBe(true);
|
|
49
|
+
return JSON.parse(body);
|
|
50
|
+
}
|
|
51
|
+
|
|
16
52
|
describe("Prompt Template Resolver", () => {
|
|
17
53
|
beforeAll(async () => {
|
|
18
54
|
for (const suffix of ["", "-wal", "-shm"]) {
|
|
@@ -568,7 +604,8 @@ describe("Prompt Template Resolver", () => {
|
|
|
568
604
|
expect(after[0].body).toBe("Updated default");
|
|
569
605
|
});
|
|
570
606
|
|
|
571
|
-
test("re-seeding does not touch user customizations (isDefault=false)", () => {
|
|
607
|
+
test("re-seeding does not touch user customizations (isDefault=false)", async () => {
|
|
608
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
572
609
|
registerTemplate({
|
|
573
610
|
eventType: "seed.test.custom",
|
|
574
611
|
header: "",
|
|
@@ -583,7 +620,7 @@ describe("Prompt Template Resolver", () => {
|
|
|
583
620
|
upsertPromptTemplate({
|
|
584
621
|
eventType: "seed.test.custom",
|
|
585
622
|
scope: "global",
|
|
586
|
-
body: "
|
|
623
|
+
body: "Default body v1",
|
|
587
624
|
changedBy: "user-123",
|
|
588
625
|
});
|
|
589
626
|
|
|
@@ -594,14 +631,14 @@ describe("Prompt Template Resolver", () => {
|
|
|
594
631
|
});
|
|
595
632
|
expect(customized.length).toBe(1);
|
|
596
633
|
expect(customized[0].isDefault).toBe(false);
|
|
597
|
-
expect(customized[0].body).toBe("
|
|
634
|
+
expect(customized[0].body).toBe("Default body v1");
|
|
598
635
|
|
|
599
636
|
// Re-seed with updated code
|
|
600
637
|
clearTemplateDefinitions();
|
|
601
638
|
registerTemplate({
|
|
602
639
|
eventType: "seed.test.custom",
|
|
603
640
|
header: "",
|
|
604
|
-
defaultBody: "Default body
|
|
641
|
+
defaultBody: "Default body version 2",
|
|
605
642
|
variables: [],
|
|
606
643
|
category: "event",
|
|
607
644
|
});
|
|
@@ -614,8 +651,142 @@ describe("Prompt Template Resolver", () => {
|
|
|
614
651
|
scope: "global",
|
|
615
652
|
});
|
|
616
653
|
expect(afterReseed.length).toBe(1);
|
|
617
|
-
expect(afterReseed[0].body).toBe("
|
|
654
|
+
expect(afterReseed[0].body).toBe("Default body v1");
|
|
655
|
+
expect(afterReseed[0].isDefault).toBe(false);
|
|
656
|
+
expect(
|
|
657
|
+
getPromptTemplateDefaultDrift(afterReseed[0], "Default body version 2").defaultDrifted,
|
|
658
|
+
).toBe(true);
|
|
659
|
+
const response = (await dispatchPromptTemplates(
|
|
660
|
+
"/api/prompt-templates?eventType=seed.test.custom",
|
|
661
|
+
)) as { templates: Array<{ defaultDrifted: boolean }> };
|
|
662
|
+
expect(response.templates).toHaveLength(1);
|
|
663
|
+
expect(response.templates[0]?.defaultDrifted).toBe(true);
|
|
664
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
665
|
+
expect(warn.mock.calls[0]?.[0]).toContain('"seed.test.custom"');
|
|
666
|
+
expect(warn.mock.calls[0]?.[0]).toContain("frozen for 0 hours");
|
|
667
|
+
expect(warn.mock.calls[0]?.[0]).toContain(
|
|
668
|
+
"byte delta +7 (code default 22 bytes, customization 15 bytes)",
|
|
669
|
+
);
|
|
670
|
+
warn.mockRestore();
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test("customized templates matching the code default self-heal as defaults", async () => {
|
|
674
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
675
|
+
registerTemplate({
|
|
676
|
+
eventType: "seed.test.custom-matching",
|
|
677
|
+
header: "",
|
|
678
|
+
defaultBody: "Still current",
|
|
679
|
+
variables: [],
|
|
680
|
+
category: "event",
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
seedDefaultTemplates();
|
|
684
|
+
upsertPromptTemplate({
|
|
685
|
+
eventType: "seed.test.custom-matching",
|
|
686
|
+
scope: "global",
|
|
687
|
+
body: "Still current",
|
|
688
|
+
changedBy: "user-123",
|
|
689
|
+
});
|
|
690
|
+
seedDefaultTemplates();
|
|
691
|
+
|
|
692
|
+
const customized = getPromptTemplates({
|
|
693
|
+
eventType: "seed.test.custom-matching",
|
|
694
|
+
scope: "global",
|
|
695
|
+
});
|
|
696
|
+
expect(customized).toHaveLength(1);
|
|
697
|
+
expect(customized[0].isDefault).toBe(true);
|
|
698
|
+
expect(getPromptTemplateDefaultDrift(customized[0], "Still current").defaultDrifted).toBe(
|
|
699
|
+
false,
|
|
700
|
+
);
|
|
701
|
+
const response = (await dispatchPromptTemplates(
|
|
702
|
+
"/api/prompt-templates?eventType=seed.test.custom-matching",
|
|
703
|
+
)) as { templates: Array<{ defaultDrifted: boolean; isDefault: boolean }> };
|
|
704
|
+
expect(response.templates).toHaveLength(1);
|
|
705
|
+
expect(response.templates[0]?.defaultDrifted).toBe(false);
|
|
706
|
+
expect(response.templates[0]?.isDefault).toBe(true);
|
|
707
|
+
|
|
708
|
+
clearTemplateDefinitions();
|
|
709
|
+
registerTemplate({
|
|
710
|
+
eventType: "seed.test.custom-matching",
|
|
711
|
+
header: "",
|
|
712
|
+
defaultBody: "Next code default",
|
|
713
|
+
variables: [],
|
|
714
|
+
category: "event",
|
|
715
|
+
});
|
|
716
|
+
seedDefaultTemplates();
|
|
717
|
+
const reconciled = getPromptTemplates({
|
|
718
|
+
eventType: "seed.test.custom-matching",
|
|
719
|
+
scope: "global",
|
|
720
|
+
});
|
|
721
|
+
expect(reconciled).toHaveLength(1);
|
|
722
|
+
expect(reconciled[0].body).toBe("Next code default");
|
|
723
|
+
expect(reconciled[0].isDefault).toBe(true);
|
|
724
|
+
expect(warn).not.toHaveBeenCalled();
|
|
725
|
+
warn.mockRestore();
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test("customized templates matching the code default preserve skip_event state", () => {
|
|
729
|
+
registerTemplate({
|
|
730
|
+
eventType: "seed.test.custom-matching-skipped",
|
|
731
|
+
header: "",
|
|
732
|
+
defaultBody: "Still current",
|
|
733
|
+
variables: [],
|
|
734
|
+
category: "event",
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
seedDefaultTemplates();
|
|
738
|
+
const skipped = upsertPromptTemplate({
|
|
739
|
+
eventType: "seed.test.custom-matching-skipped",
|
|
740
|
+
scope: "global",
|
|
741
|
+
state: "skip_event",
|
|
742
|
+
body: "Still current",
|
|
743
|
+
changedBy: "user-123",
|
|
744
|
+
});
|
|
745
|
+
expect(skipped.isDefault).toBe(false);
|
|
746
|
+
expect(skipped.state).toBe("skip_event");
|
|
747
|
+
|
|
748
|
+
seedDefaultTemplates();
|
|
749
|
+
|
|
750
|
+
const afterReseed = getPromptTemplates({
|
|
751
|
+
eventType: "seed.test.custom-matching-skipped",
|
|
752
|
+
scope: "global",
|
|
753
|
+
});
|
|
754
|
+
expect(afterReseed).toHaveLength(1);
|
|
755
|
+
expect(afterReseed[0].body).toBe("Still current");
|
|
756
|
+
expect(afterReseed[0].state).toBe("skip_event");
|
|
618
757
|
expect(afterReseed[0].isDefault).toBe(false);
|
|
758
|
+
expect(afterReseed[0].version).toBe(skipped.version);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test("unchanged default templates remain defaults without warnings", () => {
|
|
762
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
763
|
+
registerTemplate({
|
|
764
|
+
eventType: "seed.test.default-unchanged",
|
|
765
|
+
header: "",
|
|
766
|
+
defaultBody: "Current default",
|
|
767
|
+
variables: [],
|
|
768
|
+
category: "event",
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
seedDefaultTemplates();
|
|
772
|
+
const seeded = getPromptTemplates({
|
|
773
|
+
eventType: "seed.test.default-unchanged",
|
|
774
|
+
scope: "global",
|
|
775
|
+
});
|
|
776
|
+
expect(seeded).toHaveLength(1);
|
|
777
|
+
expect(seeded[0].body).toBe("Current default");
|
|
778
|
+
expect(seeded[0].isDefault).toBe(true);
|
|
779
|
+
|
|
780
|
+
seedDefaultTemplates();
|
|
781
|
+
const unchanged = getPromptTemplates({
|
|
782
|
+
eventType: "seed.test.default-unchanged",
|
|
783
|
+
scope: "global",
|
|
784
|
+
});
|
|
785
|
+
expect(unchanged).toHaveLength(1);
|
|
786
|
+
expect(unchanged[0].body).toBe("Current default");
|
|
787
|
+
expect(unchanged[0].isDefault).toBe(true);
|
|
788
|
+
expect(warn).not.toHaveBeenCalled();
|
|
789
|
+
warn.mockRestore();
|
|
619
790
|
});
|
|
620
791
|
|
|
621
792
|
test("seeding with no registered templates is a no-op", () => {
|
|
@@ -68,7 +68,7 @@ describe("Session templates — registration", () => {
|
|
|
68
68
|
await ensureTemplatesRegistered();
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
test("all
|
|
71
|
+
test("all 19 system templates are registered", () => {
|
|
72
72
|
const systemTemplates = [
|
|
73
73
|
"system.agent.role",
|
|
74
74
|
"system.agent.register",
|
|
@@ -89,6 +89,7 @@ describe("Session templates — registration", () => {
|
|
|
89
89
|
"system.agent.services",
|
|
90
90
|
"system.agent.artifacts",
|
|
91
91
|
"system.agent.share_urls",
|
|
92
|
+
"system.agent.communication_style",
|
|
92
93
|
];
|
|
93
94
|
|
|
94
95
|
for (const eventType of systemTemplates) {
|
|
@@ -113,7 +114,7 @@ describe("Session templates — registration", () => {
|
|
|
113
114
|
}
|
|
114
115
|
});
|
|
115
116
|
|
|
116
|
-
test("total of
|
|
117
|
+
test("total of 32 session/system templates registered", () => {
|
|
117
118
|
const all = getAllTemplateDefinitions();
|
|
118
119
|
const sessionSystem = all.filter((d) => d.category === "system" || d.category === "session");
|
|
119
120
|
// 26 = the original 19 + `system.session.worker.pi` + `system.agent.seed_scripts`
|
|
@@ -124,7 +125,8 @@ describe("Session templates — registration", () => {
|
|
|
124
125
|
// 30 = 29 + `system.agent.scheduling` (split out of context_mode so the
|
|
125
126
|
// pi composites keep the scheduling rules without the ctx_* tool block).
|
|
126
127
|
// 31 = 30 + `system.agent.apps`.
|
|
127
|
-
|
|
128
|
+
// 32 = 31 + `system.agent.communication_style`.
|
|
129
|
+
expect(sessionSystem.length).toBe(32);
|
|
128
130
|
});
|
|
129
131
|
});
|
|
130
132
|
|
|
@@ -221,8 +223,15 @@ describe("Session templates — individual resolution", () => {
|
|
|
221
223
|
|
|
222
224
|
test("system.agent.worker contains worker tools", () => {
|
|
223
225
|
const result = resolveTemplate("system.agent.worker", {});
|
|
226
|
+
expect(result.text).toContain("Skills (CHECK BEFORE STARTING WORK)");
|
|
227
|
+
expect(result.text).toContain("researching");
|
|
228
|
+
expect(result.text).toContain("planning");
|
|
229
|
+
expect(result.text).toContain("code-reviewing");
|
|
224
230
|
expect(result.text).toContain("store-progress");
|
|
225
231
|
expect(result.text).toContain("task-action");
|
|
232
|
+
expect(result.text).toContain("Credential Hygiene");
|
|
233
|
+
expect(result.text).not.toContain("Slack Thread Updates");
|
|
234
|
+
expect(result.text).not.toContain("On start");
|
|
226
235
|
});
|
|
227
236
|
|
|
228
237
|
test("system.agent.register contains join-swarm", () => {
|
|
@@ -332,6 +341,9 @@ describe("Session templates — composite resolution", () => {
|
|
|
332
341
|
// Contains register section
|
|
333
342
|
expect(result.text).toContain("join-swarm");
|
|
334
343
|
|
|
344
|
+
// Lead is hinted to persist learned requester comms preferences
|
|
345
|
+
expect(result.text).toContain("persist them with its `comms` field");
|
|
346
|
+
|
|
335
347
|
// Contains lead-specific section (not worker)
|
|
336
348
|
expect(result.text).toContain("CRITICAL: You are a coordinator");
|
|
337
349
|
expect(result.text).toContain("coordinator");
|
|
@@ -351,6 +363,9 @@ describe("Session templates — composite resolution", () => {
|
|
|
351
363
|
|
|
352
364
|
// Contains system
|
|
353
365
|
expect(result.text).toContain("System packages available");
|
|
366
|
+
|
|
367
|
+
// Contains communication style
|
|
368
|
+
expect(result.text).toContain("### Communication Style");
|
|
354
369
|
});
|
|
355
370
|
|
|
356
371
|
test("system.session.worker resolves all template references", () => {
|
|
@@ -387,6 +402,27 @@ describe("Session templates — composite resolution", () => {
|
|
|
387
402
|
|
|
388
403
|
// Contains system
|
|
389
404
|
expect(result.text).toContain("System packages available");
|
|
405
|
+
|
|
406
|
+
// Contains communication style
|
|
407
|
+
expect(result.text).toContain("### Communication Style");
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("communication style block resolves in every session composite", () => {
|
|
411
|
+
const composites = [
|
|
412
|
+
"system.session.lead",
|
|
413
|
+
"system.session.worker",
|
|
414
|
+
"system.session.worker.pi",
|
|
415
|
+
"system.session.lead.pi",
|
|
416
|
+
"system.session.worker.remote",
|
|
417
|
+
];
|
|
418
|
+
|
|
419
|
+
for (const eventType of composites) {
|
|
420
|
+
const result = resolveTemplate(eventType, { role: "worker", agentId: "agent-001" });
|
|
421
|
+
expect(result.skipped).toBe(false);
|
|
422
|
+
expect(result.text).toContain("### Communication Style");
|
|
423
|
+
expect(result.text).toContain("Mirror the requester");
|
|
424
|
+
expect(result.text).toContain("Never use em dashes");
|
|
425
|
+
}
|
|
390
426
|
});
|
|
391
427
|
|
|
392
428
|
test("composite does NOT include conditional sections (agent_fs, services, artifacts)", () => {
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { closeDb, createAgent, createTaskExtended, getDb, initDb, updateTaskVcs } from "../be/db";
|
|
4
|
+
import {
|
|
5
|
+
_test,
|
|
6
|
+
checkQueueStall,
|
|
7
|
+
getQueueStallSnapshot,
|
|
8
|
+
isQueueStalled,
|
|
9
|
+
QUEUE_STALL_THRESHOLD_MS,
|
|
10
|
+
} from "../queue-stall-alarm";
|
|
11
|
+
|
|
12
|
+
const TEST_DB_PATH = "./test-queue-stall-alarm.sqlite";
|
|
13
|
+
const NOW = new Date("2026-08-17T17:00:00.000Z");
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
closeDb();
|
|
17
|
+
initDb(TEST_DB_PATH);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
_test.resetState();
|
|
22
|
+
getDb().run("DELETE FROM agent_log");
|
|
23
|
+
getDb().run("DELETE FROM agent_tasks");
|
|
24
|
+
getDb().run("DELETE FROM agents");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(async () => {
|
|
28
|
+
_test.resetState();
|
|
29
|
+
closeDb();
|
|
30
|
+
await unlink(TEST_DB_PATH).catch(() => {});
|
|
31
|
+
await unlink(`${TEST_DB_PATH}-wal`).catch(() => {});
|
|
32
|
+
await unlink(`${TEST_DB_PATH}-shm`).catch(() => {});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function setCreatedAt(taskId: string, createdAt: string): void {
|
|
36
|
+
getDb().run("UPDATE agent_tasks SET createdAt = ?, lastUpdatedAt = ? WHERE id = ?", [
|
|
37
|
+
createdAt,
|
|
38
|
+
createdAt,
|
|
39
|
+
taskId,
|
|
40
|
+
]);
|
|
41
|
+
getDb().run(
|
|
42
|
+
"UPDATE agent_log SET createdAt = ? WHERE taskId = ? AND eventType = 'task_created'",
|
|
43
|
+
[createdAt, taskId],
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe("queue stall alarm", () => {
|
|
48
|
+
test("positive control: alerts when a claimable task is older than 30 minutes", async () => {
|
|
49
|
+
const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
50
|
+
const task = createTaskExtended("Old claimable task", { agentId: worker.id });
|
|
51
|
+
setCreatedAt(task.id, "2026-08-17T16:29:59.000Z");
|
|
52
|
+
const notify = mock(async (_message: string) => {});
|
|
53
|
+
|
|
54
|
+
const snapshot = await checkQueueStall(NOW, notify);
|
|
55
|
+
|
|
56
|
+
expect(isQueueStalled(snapshot)).toBe(true);
|
|
57
|
+
expect(snapshot.claimableCount).toBe(1);
|
|
58
|
+
expect(snapshot.oldestTaskId).toBe(task.id);
|
|
59
|
+
expect(snapshot.oldestAgeMs).toBeGreaterThan(QUEUE_STALL_THRESHOLD_MS);
|
|
60
|
+
expect(notify).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(notify.mock.calls[0]?.[0]).toContain("queue pickup stalled");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("negative control: an empty queue never alerts", async () => {
|
|
65
|
+
const notify = mock(async (_message: string) => {});
|
|
66
|
+
|
|
67
|
+
const snapshot = await checkQueueStall(NOW, notify);
|
|
68
|
+
|
|
69
|
+
expect(snapshot.claimableCount).toBe(0);
|
|
70
|
+
expect(isQueueStalled(snapshot)).toBe(false);
|
|
71
|
+
expect(notify).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("does not alert for a non-empty but fresh queue", async () => {
|
|
75
|
+
const task = createTaskExtended("Fresh pool task");
|
|
76
|
+
setCreatedAt(task.id, "2026-08-17T16:45:00.000Z");
|
|
77
|
+
const notify = mock(async (_message: string) => {});
|
|
78
|
+
|
|
79
|
+
const snapshot = await checkQueueStall(NOW, notify);
|
|
80
|
+
|
|
81
|
+
expect(snapshot.claimableCount).toBe(1);
|
|
82
|
+
expect(isQueueStalled(snapshot)).toBe(false);
|
|
83
|
+
expect(notify).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("measures queue age from a backlog task becoming claimable", async () => {
|
|
87
|
+
const task = createTaskExtended("Released backlog task");
|
|
88
|
+
setCreatedAt(task.id, "2026-08-17T12:00:00.000Z");
|
|
89
|
+
getDb().run("UPDATE agent_tasks SET status = 'backlog' WHERE id = ?", [task.id]);
|
|
90
|
+
getDb().run("UPDATE agent_tasks SET status = 'unassigned', lastUpdatedAt = ? WHERE id = ?", [
|
|
91
|
+
"2026-08-17T16:59:00.000Z",
|
|
92
|
+
task.id,
|
|
93
|
+
]);
|
|
94
|
+
getDb().run(
|
|
95
|
+
`INSERT INTO agent_log (id, eventType, taskId, oldValue, newValue, createdAt)
|
|
96
|
+
VALUES (?, 'task_status_change', ?, 'backlog', 'unassigned', ?)`,
|
|
97
|
+
[crypto.randomUUID(), task.id, "2026-08-17T16:59:00.000Z"],
|
|
98
|
+
);
|
|
99
|
+
const notify = mock(async (_message: string) => {});
|
|
100
|
+
|
|
101
|
+
const snapshot = await checkQueueStall(NOW, notify);
|
|
102
|
+
|
|
103
|
+
expect(snapshot.oldestAgeMs).toBe(60_000);
|
|
104
|
+
expect(isQueueStalled(snapshot)).toBe(false);
|
|
105
|
+
expect(notify).not.toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("measures queue age from the final dependency completing", async () => {
|
|
109
|
+
const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
110
|
+
const prerequisite = createTaskExtended("Prerequisite", { agentId: worker.id });
|
|
111
|
+
const blocked = createTaskExtended("Newly unblocked", {
|
|
112
|
+
agentId: worker.id,
|
|
113
|
+
dependsOn: [prerequisite.id],
|
|
114
|
+
});
|
|
115
|
+
setCreatedAt(blocked.id, "2026-08-17T12:00:00.000Z");
|
|
116
|
+
getDb().run(
|
|
117
|
+
"UPDATE agent_tasks SET status = 'completed', finishedAt = ?, lastUpdatedAt = ? WHERE id = ?",
|
|
118
|
+
["2026-08-17T16:59:00.000Z", "2026-08-17T16:59:00.000Z", prerequisite.id],
|
|
119
|
+
);
|
|
120
|
+
const notify = mock(async (_message: string) => {});
|
|
121
|
+
|
|
122
|
+
const snapshot = await checkQueueStall(NOW, notify);
|
|
123
|
+
|
|
124
|
+
expect(snapshot.oldestTaskId).toBe(blocked.id);
|
|
125
|
+
expect(snapshot.oldestAgeMs).toBe(60_000);
|
|
126
|
+
expect(isQueueStalled(snapshot)).toBe(false);
|
|
127
|
+
expect(notify).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("does not reset queue age when VCS metadata updates a claimable task", () => {
|
|
131
|
+
const task = createTaskExtended("Old task with new VCS metadata");
|
|
132
|
+
setCreatedAt(task.id, "2026-08-17T16:00:00.000Z");
|
|
133
|
+
|
|
134
|
+
updateTaskVcs(task.id, {
|
|
135
|
+
vcsProvider: "github",
|
|
136
|
+
vcsRepo: "desplega-ai/agent-swarm",
|
|
137
|
+
vcsNumber: 1177,
|
|
138
|
+
vcsUrl: "https://github.com/desplega-ai/agent-swarm/pull/1177",
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const snapshot = getQueueStallSnapshot(NOW);
|
|
142
|
+
expect(snapshot.oldestAgeMs).toBe(60 * 60 * 1000);
|
|
143
|
+
expect(isQueueStalled(snapshot)).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("does not reset claimable age when VCS metadata updates a completed dependency", () => {
|
|
147
|
+
const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
148
|
+
const prerequisite = createTaskExtended("Completed prerequisite", { agentId: worker.id });
|
|
149
|
+
const blocked = createTaskExtended("Waiting after prerequisite", {
|
|
150
|
+
agentId: worker.id,
|
|
151
|
+
dependsOn: [prerequisite.id],
|
|
152
|
+
});
|
|
153
|
+
setCreatedAt(blocked.id, "2026-08-17T12:00:00.000Z");
|
|
154
|
+
getDb().run(
|
|
155
|
+
"UPDATE agent_tasks SET status = 'completed', finishedAt = ?, lastUpdatedAt = ? WHERE id = ?",
|
|
156
|
+
["2026-08-17T16:00:00.000Z", "2026-08-17T16:00:00.000Z", prerequisite.id],
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
updateTaskVcs(prerequisite.id, {
|
|
160
|
+
vcsProvider: "github",
|
|
161
|
+
vcsRepo: "desplega-ai/agent-swarm",
|
|
162
|
+
vcsNumber: 1177,
|
|
163
|
+
vcsUrl: "https://github.com/desplega-ai/agent-swarm/pull/1177",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const snapshot = getQueueStallSnapshot(NOW);
|
|
167
|
+
expect(snapshot.oldestTaskId).toBe(blocked.id);
|
|
168
|
+
expect(snapshot.oldestAgeMs).toBe(60 * 60 * 1000);
|
|
169
|
+
expect(isQueueStalled(snapshot)).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("excludes pending tasks whose dependencies are not complete", () => {
|
|
173
|
+
const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
174
|
+
const prerequisite = createTaskExtended("Prerequisite", { agentId: worker.id });
|
|
175
|
+
const blocked = createTaskExtended("Blocked", {
|
|
176
|
+
agentId: worker.id,
|
|
177
|
+
dependsOn: [prerequisite.id],
|
|
178
|
+
});
|
|
179
|
+
setCreatedAt(blocked.id, "2026-08-17T12:00:00.000Z");
|
|
180
|
+
|
|
181
|
+
const snapshot = getQueueStallSnapshot(NOW);
|
|
182
|
+
|
|
183
|
+
expect(snapshot.claimableCount).toBe(1);
|
|
184
|
+
expect(snapshot.oldestTaskId).toBe(prerequisite.id);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("reports direct and pool pickup transitions as diagnostic context", () => {
|
|
188
|
+
const worker = createAgent({ name: "worker", isLead: false, status: "idle" });
|
|
189
|
+
const task = createTaskExtended("Queued", { agentId: worker.id });
|
|
190
|
+
getDb().run(
|
|
191
|
+
`INSERT INTO agent_log (id, eventType, taskId, agentId, oldValue, newValue, createdAt)
|
|
192
|
+
VALUES (?, 'task_status_change', ?, ?, 'pending', 'in_progress', ?)`,
|
|
193
|
+
[crypto.randomUUID(), task.id, worker.id, "2026-08-17T16:50:00.000Z"],
|
|
194
|
+
);
|
|
195
|
+
getDb().run(
|
|
196
|
+
`INSERT INTO agent_log (id, eventType, taskId, agentId, oldValue, newValue, createdAt)
|
|
197
|
+
VALUES (?, 'task_claimed', ?, ?, 'unassigned', 'in_progress', ?)`,
|
|
198
|
+
[crypto.randomUUID(), task.id, worker.id, "2026-08-17T16:55:00.000Z"],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(getQueueStallSnapshot(NOW).recentPickupCount).toBe(2);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("scrubs secrets from notification failures before logging", () => {
|
|
205
|
+
const token = "ghp_1234567890abcdefABCDEF1234567890ABCD";
|
|
206
|
+
|
|
207
|
+
const output = _test.formatCheckFailure(new Error(`Slack rejected token ${token}`));
|
|
208
|
+
|
|
209
|
+
expect(output).not.toContain(token);
|
|
210
|
+
expect(output).toContain("[REDACTED:github_token]");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("deduplicates an active alarm and sends recovery", async () => {
|
|
214
|
+
const task = createTaskExtended("Old pool task");
|
|
215
|
+
setCreatedAt(task.id, "2026-08-17T16:00:00.000Z");
|
|
216
|
+
const notify = mock(async (_message: string) => {});
|
|
217
|
+
|
|
218
|
+
await checkQueueStall(NOW, notify);
|
|
219
|
+
await checkQueueStall(NOW, notify);
|
|
220
|
+
getDb().run("UPDATE agent_tasks SET status = 'completed' WHERE id = ?", [task.id]);
|
|
221
|
+
await checkQueueStall(NOW, notify);
|
|
222
|
+
|
|
223
|
+
expect(notify).toHaveBeenCalledTimes(2);
|
|
224
|
+
expect(notify.mock.calls[1]?.[0]).toContain("queue pickup recovered");
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -28,6 +28,9 @@ import {
|
|
|
28
28
|
registerRegisterKapsoNumberTool,
|
|
29
29
|
registerUnregisterKapsoNumberTool,
|
|
30
30
|
} from "../tools/register-kapso-number";
|
|
31
|
+
import { registerSlackArchiveChannelTool } from "../tools/slack-archive-channel";
|
|
32
|
+
import { registerSlackCreateChannelTool } from "../tools/slack-create-channel";
|
|
33
|
+
import { registerSlackInviteToChannelTool } from "../tools/slack-invite-to-channel";
|
|
31
34
|
import { registerSlackPostTool } from "../tools/slack-post";
|
|
32
35
|
import { registerSlackReadTool } from "../tools/slack-read";
|
|
33
36
|
import { registerSlackStartThreadTool } from "../tools/slack-start-thread";
|
|
@@ -102,6 +105,9 @@ beforeAll(async () => {
|
|
|
102
105
|
server = new McpServer({ name: "test-rbac-charact-slack", version: "1.0.0" });
|
|
103
106
|
registerSlackPostTool(server);
|
|
104
107
|
registerSlackStartThreadTool(server);
|
|
108
|
+
registerSlackCreateChannelTool(server);
|
|
109
|
+
registerSlackInviteToChannelTool(server);
|
|
110
|
+
registerSlackArchiveChannelTool(server);
|
|
105
111
|
registerSlackReadTool(server);
|
|
106
112
|
registerSlackUploadFileTool(server);
|
|
107
113
|
registerDeleteChannelTool(server);
|
|
@@ -167,6 +173,29 @@ describe("slack tool gates (characterization)", () => {
|
|
|
167
173
|
expect(result.structuredContent.message).not.toContain("requires lead privileges");
|
|
168
174
|
});
|
|
169
175
|
|
|
176
|
+
test.each([
|
|
177
|
+
["slack-create-channel", { name: "project-channel" }],
|
|
178
|
+
["slack-invite-to-channel", { channelId: "C0CHARACT01", userIds: ["U0CHARACT01"] }],
|
|
179
|
+
["slack-archive-channel", { channelId: "C0CHARACT01" }],
|
|
180
|
+
])("worker cannot use %s", async (toolName, args) => {
|
|
181
|
+
const result = await callTool(toolName, WORKER_ID, args);
|
|
182
|
+
|
|
183
|
+
expect(result.structuredContent.success).toBe(false);
|
|
184
|
+
expect(result.structuredContent.message).toBe(
|
|
185
|
+
"Managing Slack channels requires lead privileges.",
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test.each([
|
|
190
|
+
["slack-create-channel", { name: "project-channel" }],
|
|
191
|
+
["slack-invite-to-channel", { channelId: "C0CHARACT01", userIds: ["U0CHARACT01"] }],
|
|
192
|
+
["slack-archive-channel", { channelId: "C0CHARACT01" }],
|
|
193
|
+
])("lead %s is not blocked by the lead gate", async (toolName, args) => {
|
|
194
|
+
const result = await callTool(toolName, LEAD_ID, args);
|
|
195
|
+
|
|
196
|
+
expect(result.structuredContent.message).not.toContain("requires lead privileges");
|
|
197
|
+
});
|
|
198
|
+
|
|
170
199
|
// slack-read.ts:146 — direct channel read requires lead
|
|
171
200
|
test("worker cannot read a Slack channel directly", async () => {
|
|
172
201
|
const result = await callTool("slack-read", WORKER_ID, {
|
|
@@ -50,6 +50,9 @@ const LEAD_ONLY_VERBS: PermissionVerb[] = [
|
|
|
50
50
|
"integration.slack.post",
|
|
51
51
|
"integration.slack.read",
|
|
52
52
|
"integration.slack.thread.start",
|
|
53
|
+
"integration.slack.channel.create",
|
|
54
|
+
"integration.slack.channel.invite",
|
|
55
|
+
"integration.slack.channel.archive",
|
|
53
56
|
"integration.slack.upload",
|
|
54
57
|
"integration.slack.delete",
|
|
55
58
|
"integration.slack.update",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { getUserCommsPrefs } from "../utils/requester-comms";
|
|
3
|
+
|
|
4
|
+
describe("getUserCommsPrefs", () => {
|
|
5
|
+
test("returns undefined for missing user or metadata", () => {
|
|
6
|
+
expect(getUserCommsPrefs(undefined)).toBeUndefined();
|
|
7
|
+
expect(getUserCommsPrefs({ metadata: undefined })).toBeUndefined();
|
|
8
|
+
expect(getUserCommsPrefs({ metadata: {} })).toBeUndefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("returns undefined when comms is not an object", () => {
|
|
12
|
+
expect(getUserCommsPrefs({ metadata: { comms: "casual" } })).toBeUndefined();
|
|
13
|
+
expect(getUserCommsPrefs({ metadata: { comms: ["casual"] } })).toBeUndefined();
|
|
14
|
+
expect(getUserCommsPrefs({ metadata: { comms: null } })).toBeUndefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("picks trimmed string fields and drops the rest", () => {
|
|
18
|
+
expect(
|
|
19
|
+
getUserCommsPrefs({
|
|
20
|
+
metadata: {
|
|
21
|
+
comms: { tone: " casual ", language: "uk", verbosity: 3, extra: "ignored" },
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
).toEqual({ tone: "casual", language: "uk", verbosity: undefined });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("returns undefined when all comms fields are empty or non-string", () => {
|
|
28
|
+
expect(
|
|
29
|
+
getUserCommsPrefs({ metadata: { comms: { tone: " ", language: 7 } } }),
|
|
30
|
+
).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
});
|