@desplega.ai/agent-swarm 2.7.4 → 2.7.6
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 +1 -1
- package/dist/acp-adapter-bp91d37f.js +15 -0
- package/dist/{actions-gfrr9vb7.js → actions-yj0eeaqh.js} +19 -18
- package/dist/{amp-adapter-0s58gm46.js → amp-adapter-4pg9h34q.js} +3 -3
- package/dist/{app-stvq692g.js → app-909e97cr.js} +3 -3
- package/dist/{assistant-5s37fr9n.js → assistant-qdyb5fyy.js} +32 -23
- package/dist/{boot-reembed-yzy1841n.js → boot-reembed-fk0s9z18.js} +1 -1
- package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gs1rzrnh.js} +1 -1
- package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-4qfd4kfx.js} +4 -4
- package/dist/{boot-seeds-ftt9htah.js → boot-seeds-yr00k13d.js} +4 -4
- package/dist/{boxd-515v1t8z.js → boxd-vdmfssd9.js} +5 -5
- package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-qta1f1xn.js} +7 -7
- package/dist/{claude-managed-adapter-g6vmjccb.js → claude-managed-adapter-dvs2xw5n.js} +2 -2
- package/dist/{claude-managed-setup-afpa82f7.js → claude-managed-setup-sc6j4dm0.js} +3 -3
- package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-zgrz6yvc.js} +9175 -9076
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-edmm9d9y.js → cli-0at8w34w.js} +7 -7
- package/dist/{cli-a81qevyg.js → cli-0qatpy8q.js} +20 -36
- package/dist/{cli-bcz074zx.js → cli-0yv5z2pg.js} +1 -1
- package/dist/{cli-a4btqqft.js → cli-1b7btd38.js} +1 -1
- package/dist/{cli-a2jw6ee5.js → cli-1p89xny0.js} +1 -1
- package/dist/{cli-v726afdm.js → cli-2pnswgh8.js} +1 -1
- package/dist/{cli-jkt52mgt.js → cli-386tyxxg.js} +2 -2
- package/dist/{cli-az0bpf8p.js → cli-3ypk9vk1.js} +3 -3
- package/dist/{cli-wk9md5px.js → cli-436532ck.js} +4 -4
- package/dist/{cli-7n5sbw0z.js → cli-4necwtsv.js} +5 -5
- package/dist/cli-54t00gt8.js +151 -0
- package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
- package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
- package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
- package/dist/{cli-wcmnan2g.js → cli-89knwags.js} +5 -5
- package/dist/{cli-pwyevs29.js → cli-8bc1w0tw.js} +22 -21
- package/dist/{cli-t282jgab.js → cli-8snvgs5r.js} +41 -16
- package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
- package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
- package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
- package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
- package/dist/{cli-je8yeyc5.js → cli-bjb3z51z.js} +5 -5
- package/dist/{cli-mc97xy2s.js → cli-c60vetmm.js} +16 -4
- package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
- package/dist/{cli-wndv9p7g.js → cli-decfb6v8.js} +6 -6
- package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
- package/dist/{cli-8yy525ad.js → cli-drr5v6av.js} +2 -2
- package/dist/{cli-5gr1g711.js → cli-dxae2yg7.js} +2 -2
- package/dist/{cli-pe09p6dv.js → cli-e3r2neck.js} +8 -7
- package/dist/{cli-13jvmew3.js → cli-ekwks2vs.js} +7 -7
- package/dist/{cli-jg4p5t36.js → cli-es6va8jk.js} +121 -51
- package/dist/{cli-j4kt1mpb.js → cli-faxe6thg.js} +3 -3
- package/dist/{cli-tzrpv5sc.js → cli-ftyxyspd.js} +1 -1
- package/dist/cli-fw2w0kva.js +47 -0
- package/dist/{cli-2dfbv8qr.js → cli-fy6x31ps.js} +1 -0
- package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
- package/dist/cli-gdhpcbhf.js +55 -0
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-8veytvbh.js → cli-he9wchme.js} +1 -1
- package/dist/{cli-k76tgq03.js → cli-hgaazk69.js} +2 -2
- package/dist/{cli-d2pzqej1.js → cli-hr2e2fft.js} +741 -7
- package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
- package/dist/{cli-1s1dqn1j.js → cli-k2gzwsj2.js} +297 -140
- package/dist/{cli-b1afgtj3.js → cli-kqdg1bsb.js} +3 -3
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-mvckssyq.js → cli-m48g6p95.js} +5 -5
- package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
- package/dist/{cli-41z8xwj0.js → cli-nhrvpaj7.js} +34 -8
- package/dist/{cli-ahd90bwt.js → cli-nwj0ee2w.js} +6 -6
- package/dist/{cli-kvtc7aez.js → cli-p5gp8pcx.js} +44 -14
- package/dist/{cli-avgcz4ra.js → cli-pd7vagab.js} +436 -171
- package/dist/{cli-zn15c46z.js → cli-q0k1w5zd.js} +1 -1
- package/dist/{cli-bq2pcphn.js → cli-rmmmrjkd.js} +1 -1
- package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1yekbyqm.js → cli-shpbx5ee.js} +283 -82
- package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
- package/dist/{cli-byqfbnjw.js → cli-svj5090z.js} +9 -9
- package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
- package/dist/{cli-v1yw3wha.js → cli-vvpx3gsx.js} +4 -4
- package/dist/{cli-a1fhj4n9.js → cli-w6dt9ged.js} +2 -2
- package/dist/{cli-1gm8jcyr.js → cli-waqbxg08.js} +5232 -68
- package/dist/{cli-5hjced2v.js → cli-wp9sp1qb.js} +6 -6
- package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
- package/dist/{cli-s0exq74r.js → cli-xbbndvjy.js} +3 -3
- package/dist/{cli-f255yaqn.js → cli-xds5f77d.js} +3 -3
- package/dist/{cli-0sbkss7h.js → cli-xnqgpshy.js} +2 -2
- package/dist/{cli-0fgr1w9b.js → cli-xnrwazav.js} +20 -3
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-cq1pf336.js → cli-yn4kxs06.js} +124 -11
- package/dist/{cli-224epwwf.js → cli-yx4kfngt.js} +3 -3
- package/dist/{cli-eqx6k993.js → cli-yy3rhsdw.js} +47 -13
- package/dist/{cli-36g23z21.js → cli-zbbwez0r.js} +4 -4
- package/dist/{cli-ktmvb20s.js → cli-zm72anhg.js} +12 -12
- package/dist/cli.js +26 -25
- package/dist/{codex-adapter-g0gctw29.js → codex-adapter-ayd1fb6h.js} +5 -5
- package/dist/{codex-hook-vrzz2xhr.js → codex-hook-9efm2x1m.js} +1 -1
- package/dist/{codex-login-0gg46ygn.js → codex-login-y0pmff1c.js} +1 -1
- package/dist/{codex-session-runner-dr2cxe1c.js → codex-session-runner-jesa32xc.js} +6 -6
- package/dist/{commands-1p07cvff.js → commands-hanewgf2.js} +9 -3
- package/dist/{controller-z0s7sv6r.js → controller-dt1n3c1q.js} +9 -4
- package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-xx8x64n5.js} +22 -21
- package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-9s4dwwdw.js} +2 -2
- package/dist/{db-capk2raj.js → db-3vttcbv4.js} +3 -3
- package/dist/{db-rsh04kh3.js → db-9dv0g1ab.js} +12 -4
- package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-304q6fwa.js → dispatcher-tx9hd1sv.js} +18 -17
- package/dist/{docker-7rxt0rry.js → docker-c1a3gqmf.js} +91 -24
- package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-007y4ejs.js} +2 -2
- package/dist/{e2b-renc0k8q.js → e2b-a19d28rz.js} +4 -4
- package/dist/{e2b-7fc9b9bd.js → e2b-we6hs6q5.js} +6 -6
- package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-rgbrqb8s.js} +1 -1
- package/dist/{embedding-space-s68v49ct.js → embedding-space-ny1e2940.js} +6 -6
- package/dist/{embeddings-4ms3604w.js → embeddings-2t386t13.js} +1 -1
- package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
- package/dist/{execute-mcdgccjs.js → execute-pksh0ceh.js} +1 -1
- package/dist/{external-channel-9dxwfztn.js → external-channel-phgmhmd7.js} +2 -2
- package/dist/fx-adapter-fy8rr921.js +18 -0
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-8y3m3k4b.js} +1 -1
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-w3v98n7b.js} +1 -1
- package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-q0abvpaj.js} +8 -8
- package/dist/handlers-by3sfxyx.js +32 -0
- package/dist/{heartbeat-3n94heh3.js → heartbeat-kadyek8p.js} +21 -20
- package/dist/{hook-aadrn69x.js → hook-5zs3msf6.js} +6 -6
- package/dist/{hook-zxwje86c.js → hook-9k551xnj.js} +4 -4
- package/dist/{http-xzxk9mst.js → http-mhz9vw1f.js} +272 -103
- package/dist/index-4f2pvxae.js +37 -0
- package/dist/index-98gz7tmp.js +30 -0
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k08z2cak.js +33 -0
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/{index-d34f6r87.js → index-q361cg2f.js} +1 -1
- package/dist/{index-9j4zwcht.js → index-rvr00sg7.js} +168 -675
- package/dist/{index-qeb82z37.js → index-vdygbq10.js} +1 -1
- package/dist/{keepalive-w4aakbyk.js → keepalive-w56f32cg.js} +4 -4
- package/dist/lead-enq35nqp.js +70 -0
- package/dist/{maintenance-hbptpk6p.js → maintenance-7mdgxcea.js} +1 -1
- package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-29txy9vk.js} +3 -3
- package/dist/{onboard-vz2s5df3.js → onboard-kpsrkarj.js} +5 -5
- package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-j25vm3rr.js} +5 -5
- package/dist/{otel-impl-aefave37.js → otel-impl-6kkct7a8.js} +2 -2
- package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-exhkzhn2.js} +117 -23
- package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-n2x7jh6k.js} +3 -3
- package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-81ypx6es.js} +1 -1
- package/dist/{rbac-roles-svj72cdf.js → rbac-roles-b5htkjby.js} +2 -2
- package/dist/{render-v2-amwbnfxh.js → render-v2-qn6jmpk4.js} +3 -3
- package/dist/{resume-9cd6wf6z.js → resume-wd30wx23.js} +7 -7
- package/dist/{runtime-fp4vrx7m.js → runtime-4hsg7h60.js} +1 -1
- package/dist/{session-access-21t6yjyg.js → session-access-5qvfevhk.js} +4 -4
- package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-n1czavbr.js} +3 -3
- package/dist/{setup-4592tqz4.js → setup-dnb56qy5.js} +3 -3
- package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-76xxv9fs.js} +2 -2
- package/dist/{work-objects-t9tcf174.js → work-objects-4n21tfkm.js} +21 -20
- package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-77hvzxww.js} +18 -17
- package/dist/worker-gg1576j3.js +70 -0
- package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
- package/openapi.json +189 -32
- package/package.json +8 -7
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/session-summaries.ts +8 -2
- package/src/be/db/session-tree.ts +174 -61
- package/src/be/db/session-visibility.ts +5 -4
- package/src/be/db/tasks/read.ts +122 -3
- package/src/be/db-client.ts +8 -5
- package/src/be/db.ts +23 -8
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
- package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
- package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
- package/src/be/migrations/263_task_list_filter_index.sql +11 -0
- package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
- package/src/be/migrations/266_script_runs_source_task.sql +11 -0
- package/src/be/model-validation.ts +5 -1
- package/src/be/pricing-normalize.ts +7 -2
- package/src/be/runtime-pools/cache-cleanup.ts +109 -0
- package/src/be/runtime-pools/db.ts +65 -6
- package/src/be/runtime-pools/remote-controller.ts +5 -0
- package/src/be/runtime-pools/supervisor.ts +33 -2
- package/src/be/scripts/typecheck.ts +23 -0
- package/src/be/seed/schedules-seeder.ts +135 -9
- package/src/be/seed/workflows-seeder.ts +65 -1
- package/src/be/seed-pricing.ts +23 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/be/steering.ts +50 -3
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/commands/provider-credentials.ts +29 -2
- package/src/commands/runner.ts +86 -5
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +27 -1
- package/src/extensions/dispatcher.ts +4 -0
- package/src/http/agents.ts +1 -0
- package/src/http/index.ts +1 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/mcp-bridge.ts +4 -0
- package/src/http/memory.ts +38 -9
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/integrations/chat-sdk/inbound.ts +15 -5
- package/src/integrations/chat-sdk/steering.ts +3 -1
- package/src/kv-reserved-namespaces.ts +2 -0
- package/src/prompts/base-prompt.ts +4 -1
- package/src/prompts/session-templates.ts +26 -0
- package/src/providers/acp-adapter.ts +190 -26
- package/src/providers/acp-targets.ts +306 -6
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +9 -44
- package/src/providers/codex-oauth/flow.ts +24 -3
- package/src/providers/codex-oauth/storage.ts +56 -17
- package/src/providers/codex-oauth/subscription.ts +67 -0
- package/src/providers/fx-adapter.ts +211 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/mcp-loopback-proxy.ts +94 -0
- package/src/providers/pi-mono-adapter.ts +98 -13
- package/src/providers/pricing-sources.md +8 -0
- package/src/providers/reasoning-effort.ts +17 -1
- package/src/providers/types.ts +2 -1
- package/src/runtime-providers/controller-protocol.ts +14 -1
- package/src/runtime-providers/controller.ts +5 -0
- package/src/runtime-providers/docker.ts +108 -23
- package/src/runtime-providers/types.ts +16 -0
- package/src/scheduler/scheduler.ts +36 -3
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +27 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -0
- package/src/server-user.ts +1 -0
- package/src/server.ts +7 -0
- package/src/slack/assistant.ts +17 -1
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +53 -5
- package/src/slack/left-threads.ts +88 -0
- package/src/slack/steering.ts +3 -0
- package/src/slack/thread-buffer.ts +40 -16
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tests/acp-adapter.test.ts +79 -0
- package/src/tests/base-prompt.test.ts +12 -0
- package/src/tests/chat-sdk-inbound.test.ts +30 -0
- package/src/tests/chatgpt-subscription-model-groups.test.ts +62 -0
- package/src/tests/chatgpt-subscription-pi.test.ts +170 -0
- package/src/tests/chatgpt-subscription-session-bench.test.ts +198 -0
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/codex-oauth-credential-info.test.ts +40 -0
- package/src/tests/codex-oauth-refresh-egress.test.ts +194 -0
- package/src/tests/context-window.test.ts +7 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +8 -2
- package/src/tests/db-task-write-contract.test.ts +5 -1
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +113 -8
- package/src/tests/extensions-run-context.test.ts +11 -0
- package/src/tests/extensions-steering.test.ts +353 -0
- package/src/tests/extensions-validate.test.ts +5 -0
- package/src/tests/fixtures/extensions/steering-policy/hooks.ts +39 -0
- package/src/tests/fixtures/extensions/steering-policy/manifest.json +7 -0
- package/src/tests/fixtures/fake-fx.ts +167 -0
- package/src/tests/fixtures/fx/acp-responses-recorded.json +157 -0
- package/src/tests/fixtures/fx/acp-updates-recorded.jsonl +15 -0
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/fx-adapter.test.ts +771 -0
- package/src/tests/grok-adapter.test.ts +36 -0
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/mcp-loopback-proxy.test.ts +193 -0
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/model-tier-resolution.test.ts +3 -3
- package/src/tests/pricing-refresh.test.ts +5 -5
- package/src/tests/prompt-template-session.test.ts +5 -3
- package/src/tests/provider-registration.test.ts +2 -1
- package/src/tests/query-audit-plans.test.ts +30 -2
- package/src/tests/runtime-cache-cleanup.test.ts +367 -0
- package/src/tests/runtime-e2b-pools.test.ts +41 -0
- package/src/tests/runtime-pools-db.test.ts +98 -0
- package/src/tests/runtime-pools-http.test.ts +29 -0
- package/src/tests/runtime-providers-docker.test.ts +253 -0
- package/src/tests/runtime-remote-controller.test.ts +66 -2
- package/src/tests/runtime-supervisor.test.ts +66 -2
- package/src/tests/script-connections.test.ts +13 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-model-profiles.test.ts +2 -2
- package/src/tests/seed-schedules.test.ts +138 -0
- package/src/tests/seed-scripts.test.ts +26 -0
- package/src/tests/session-cost-recompute-unit.test.ts +39 -0
- package/src/tests/session-costs-golden.test.ts +26 -0
- package/src/tests/session-summaries-plans.test.ts +120 -0
- package/src/tests/session-summaries.test.ts +72 -12
- package/src/tests/sessions-tree.test.ts +11 -3
- package/src/tests/slack-leave-thread.test.ts +571 -0
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tests/tool-annotations.test.ts +2 -1
- package/src/tests/workflow-engine-v2.test.ts +42 -0
- package/src/tests/workflow-script-principal.test.ts +207 -7
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +104 -0
- package/src/tools/steer-task.ts +3 -2
- package/src/tools/task-dedup.ts +45 -21
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +57 -0
- package/src/utils/codex-auth-failure.ts +30 -0
- package/src/utils/context-window.ts +4 -1
- package/src/utils/credentials.ts +7 -0
- package/src/utils/fx-models.ts +43 -0
- package/src/workflows/cooldown.ts +3 -2
- package/src/workflows/engine.ts +24 -9
- package/templates/schedules/dreaming-daily/config.json +1 -1
- package/templates/schedules/dreaming-daily/content.md +6 -14
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/templates/workflows/dreaming/content.md +5 -1
- package/dist/acp-adapter-96dc64m2.js +0 -15
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-jg7v74ty.js +0 -31
- package/dist/index-8c1zbz59.js +0 -29
- package/dist/index-8k8tmfxd.js +0 -32
- package/dist/index-hq617xdj.js +0 -36
- package/dist/lead-567kw7x3.js +0 -68
- package/dist/worker-fjdqra6q.js +0 -68
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A list total is a COUNT over the same filters as the page, and on the large
|
|
5
5
|
* tables behind the sessions and task lists it costs as much as the page
|
|
6
|
-
* itself.
|
|
7
|
-
* triage move) shows at once where the user looks first.
|
|
6
|
+
* itself. By default page 1 (offset 0) always counts, so a write (a new
|
|
7
|
+
* session, a triage move) shows at once where the user looks first. A list
|
|
8
|
+
* whose count is expensive and whose total may lag can pass
|
|
9
|
+
* `firstPageTtlMs`: page 1 then reuses a count younger than that, so
|
|
10
|
+
* dashboard tabs that poll the same list share one count. Later pages reuse
|
|
8
11
|
* that count for 30 s. A cold later page (deep link, expired entry) counts
|
|
9
12
|
* too, so `total` is always set.
|
|
10
13
|
*/
|
|
@@ -13,7 +16,7 @@ export const LIST_TOTAL_TTL_MS = 30_000;
|
|
|
13
16
|
const MAX_ENTRIES = 500;
|
|
14
17
|
|
|
15
18
|
interface Entry {
|
|
16
|
-
|
|
19
|
+
countedAt: number;
|
|
17
20
|
total: Promise<number>;
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -34,19 +37,23 @@ function filterKey(value: unknown): string {
|
|
|
34
37
|
);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export function createListTotalCache(
|
|
40
|
+
export function createListTotalCache(
|
|
41
|
+
ttlMs = LIST_TOTAL_TTL_MS,
|
|
42
|
+
firstPageTtlMs = 0,
|
|
43
|
+
): ListTotalCache {
|
|
38
44
|
const entries = new Map<string, Entry>();
|
|
39
45
|
return {
|
|
40
46
|
get(filters, offset, compute) {
|
|
41
47
|
const key = filterKey(filters);
|
|
42
48
|
const now = Date.now();
|
|
43
49
|
const hit = entries.get(key);
|
|
44
|
-
|
|
50
|
+
const maxAge = (offset ?? 0) > 0 ? ttlMs : Math.min(ttlMs, firstPageTtlMs);
|
|
51
|
+
if (hit && hit.countedAt + maxAge > now) return hit.total;
|
|
45
52
|
if (hit) entries.delete(key);
|
|
46
53
|
|
|
47
|
-
// Cache the promise, so a
|
|
54
|
+
// Cache the promise, so a request that arrives mid-count shares it.
|
|
48
55
|
const total = compute();
|
|
49
|
-
entries.set(key, {
|
|
56
|
+
entries.set(key, { countedAt: now, total });
|
|
50
57
|
total.catch(() => {
|
|
51
58
|
if (entries.get(key)?.total === total) entries.delete(key);
|
|
52
59
|
});
|
package/src/http/mcp-bridge.ts
CHANGED
|
@@ -32,6 +32,10 @@ async function getBridgeServer(): Promise<McpServer> {
|
|
|
32
32
|
return _bridgeServer;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export function resetBridgeServerForTesting(): void {
|
|
36
|
+
_bridgeServer = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
type RegisteredTool = {
|
|
36
40
|
handler: (argsOrExtra: unknown, extra?: unknown) => unknown | Promise<unknown>;
|
|
37
41
|
inputSchema?: AnySchema;
|
package/src/http/memory.ts
CHANGED
|
@@ -1568,17 +1568,31 @@ const MEMORY_GC_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
|
1568
1568
|
let memoryGcTimer: ReturnType<typeof setInterval> | null = null;
|
|
1569
1569
|
|
|
1570
1570
|
const SEARCH_RETRIEVAL_TTL_DAYS = 90;
|
|
1571
|
+
/** Rows per purge statement. The purge yields the event loop between batches. */
|
|
1572
|
+
const SEARCH_RETRIEVAL_PURGE_BATCH = 2000;
|
|
1571
1573
|
|
|
1572
|
-
async function purgeStaleSearchRetrievals(
|
|
1574
|
+
export async function purgeStaleSearchRetrievals(
|
|
1575
|
+
batchSize = SEARCH_RETRIEVAL_PURGE_BATCH,
|
|
1576
|
+
): Promise<number> {
|
|
1573
1577
|
try {
|
|
1574
1578
|
const cutoff = new Date(
|
|
1575
1579
|
Date.now() - SEARCH_RETRIEVAL_TTL_DAYS * 24 * 60 * 60 * 1000,
|
|
1576
1580
|
).toISOString();
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1581
|
+
let purged = 0;
|
|
1582
|
+
for (;;) {
|
|
1583
|
+
// `+eventType` keeps the planner on the retrievedAt range. Without
|
|
1584
|
+
// sqlite_stat1 it prefers the eventType equality, which reads every
|
|
1585
|
+
// search row to test its date, even when nothing is stale.
|
|
1586
|
+
const result = await getDbClient().run(
|
|
1587
|
+
`DELETE FROM memory_retrieval WHERE rowid IN (
|
|
1588
|
+
SELECT rowid FROM memory_retrieval
|
|
1589
|
+
WHERE +eventType = 'search' AND retrievedAt < ? LIMIT ?)`,
|
|
1590
|
+
[cutoff, batchSize],
|
|
1591
|
+
);
|
|
1592
|
+
purged += result.changes;
|
|
1593
|
+
if (result.changes < batchSize) return purged;
|
|
1594
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
1595
|
+
}
|
|
1582
1596
|
} catch (err) {
|
|
1583
1597
|
console.error("[memory-gc] Search retrieval purge failed:", (err as Error).message);
|
|
1584
1598
|
return 0;
|
|
@@ -1602,11 +1616,22 @@ async function runMemoryGcTick(label: "Initial" | "Periodic"): Promise<void> {
|
|
|
1602
1616
|
}
|
|
1603
1617
|
}
|
|
1604
1618
|
|
|
1605
|
-
|
|
1619
|
+
/** The first purge waits this long, so it does not compete with boot work. */
|
|
1620
|
+
const MEMORY_GC_INITIAL_DELAY_MS = 2 * 60 * 1000;
|
|
1621
|
+
let memoryGcInitialTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1622
|
+
|
|
1623
|
+
export async function startMemoryGc(
|
|
1624
|
+
intervalMs = MEMORY_GC_INTERVAL_MS,
|
|
1625
|
+
initialDelayMs = MEMORY_GC_INITIAL_DELAY_MS,
|
|
1626
|
+
): Promise<void> {
|
|
1606
1627
|
if (memoryGcTimer) return;
|
|
1607
1628
|
|
|
1608
|
-
//
|
|
1609
|
-
|
|
1629
|
+
// Clear any backlog soon after startup, but off the boot path.
|
|
1630
|
+
memoryGcInitialTimer = setTimeout(() => {
|
|
1631
|
+
memoryGcInitialTimer = null;
|
|
1632
|
+
void runMemoryGcTick("Initial");
|
|
1633
|
+
}, initialDelayMs);
|
|
1634
|
+
if (typeof memoryGcInitialTimer?.unref === "function") memoryGcInitialTimer.unref();
|
|
1610
1635
|
|
|
1611
1636
|
memoryGcTimer = setInterval(() => {
|
|
1612
1637
|
void runMemoryGcTick("Periodic");
|
|
@@ -1615,6 +1640,10 @@ export async function startMemoryGc(intervalMs = MEMORY_GC_INTERVAL_MS): Promise
|
|
|
1615
1640
|
}
|
|
1616
1641
|
|
|
1617
1642
|
export function stopMemoryGc(): void {
|
|
1643
|
+
if (memoryGcInitialTimer) {
|
|
1644
|
+
clearTimeout(memoryGcInitialTimer);
|
|
1645
|
+
memoryGcInitialTimer = null;
|
|
1646
|
+
}
|
|
1618
1647
|
if (memoryGcTimer) {
|
|
1619
1648
|
clearInterval(memoryGcTimer);
|
|
1620
1649
|
memoryGcTimer = null;
|
package/src/http/script-runs.ts
CHANGED
|
@@ -13,10 +13,12 @@ import {
|
|
|
13
13
|
getLatestScriptRunStepTaskByContextKey,
|
|
14
14
|
getScriptRun,
|
|
15
15
|
getScriptRunByIdempotencyKey,
|
|
16
|
+
getScriptRunFollowUpGuard,
|
|
16
17
|
getScriptRunJournalStep,
|
|
17
18
|
getTaskById,
|
|
18
19
|
listScriptRunJournalSteps,
|
|
19
20
|
listScriptRuns,
|
|
21
|
+
setScriptRunFollowUpGuard,
|
|
20
22
|
updateScriptRun,
|
|
21
23
|
updateScriptRunIfNotTerminal,
|
|
22
24
|
updateScriptRunIfRunning,
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
} from "../be/db";
|
|
25
27
|
import { explicitModelErrorForAgent } from "../be/model-validation";
|
|
26
28
|
import { refuseSourceWithSecrets } from "../be/scripts/source-secrets";
|
|
29
|
+
import { can } from "../rbac";
|
|
27
30
|
import { lintWorkflowLabels } from "../script-workflows/label-lint";
|
|
28
31
|
import { scriptRunMaxAgentTasks, scriptRunMaxSteps } from "../script-workflows/limits";
|
|
29
32
|
import {
|
|
@@ -32,6 +35,12 @@ import {
|
|
|
32
35
|
terminateScriptRunProcess,
|
|
33
36
|
} from "../script-workflows/supervisor";
|
|
34
37
|
import {
|
|
38
|
+
evaluateFollowUpRedelegation,
|
|
39
|
+
type FollowUpRedelegationBlock,
|
|
40
|
+
followUpThreadForSourceTask,
|
|
41
|
+
} from "../tasks/follow-up-redelegation-guard";
|
|
42
|
+
import {
|
|
43
|
+
type AgentTask,
|
|
35
44
|
AgentTaskStatusSchema,
|
|
36
45
|
RoutingReasonSchema,
|
|
37
46
|
ScriptRunJournalEntrySchema,
|
|
@@ -111,6 +120,8 @@ const agentTaskBodySchema = z.object({
|
|
|
111
120
|
parentTaskId: z.string().uuid().optional(),
|
|
112
121
|
requestedByUserId: z.string().optional(),
|
|
113
122
|
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
123
|
+
/** Dispatch even when the follow-up re-delegation guard blocks it (same flag as send-task). */
|
|
124
|
+
allowRedelegation: z.boolean().optional(),
|
|
114
125
|
});
|
|
115
126
|
|
|
116
127
|
const scriptRunCreatedSchema = z.object({
|
|
@@ -298,10 +309,70 @@ const agentTaskRoute = route({
|
|
|
298
309
|
description: "Agent task created or still running",
|
|
299
310
|
schema: z.object({ taskId: z.string(), status: AgentTaskStatusSchema }),
|
|
300
311
|
},
|
|
312
|
+
403: {
|
|
313
|
+
description:
|
|
314
|
+
"Script run belongs to another agent, or the run's agent may not create a child task under parentTaskId",
|
|
315
|
+
},
|
|
301
316
|
404: { description: "Script run not found" },
|
|
317
|
+
409: { description: "Agent task failed, was cancelled, or was superseded" },
|
|
318
|
+
422: { description: "Blocked by the follow-up re-delegation guard" },
|
|
302
319
|
},
|
|
303
320
|
});
|
|
304
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Send-task's task.parent.attach decision for a `ctx.step.agentTask` step: a
|
|
324
|
+
* step inherits its parent's contextKey (its Slack or chat thread) and
|
|
325
|
+
* requester, so the run's agent needs the right to hold a child under it.
|
|
326
|
+
* Returns the 403 message, or null when allowed. A parent id with no task row
|
|
327
|
+
* passes: a step under it inherits nothing, as on send-task.
|
|
328
|
+
*/
|
|
329
|
+
async function stepParentRefusal(runAgentId: string, parentTaskId: string): Promise<string | null> {
|
|
330
|
+
const parentTask = await getTaskById(parentTaskId);
|
|
331
|
+
if (!parentTask) return null;
|
|
332
|
+
const runAgent = await getAgentById(runAgentId);
|
|
333
|
+
const decision = can({
|
|
334
|
+
principal: { kind: "agent", agentId: runAgentId, isLead: runAgent?.isLead ?? false },
|
|
335
|
+
verb: "task.parent.attach",
|
|
336
|
+
resource: {
|
|
337
|
+
kind: "task",
|
|
338
|
+
taskId: parentTask.id,
|
|
339
|
+
requestedByUserId: parentTask.requestedByUserId,
|
|
340
|
+
creatorAgentId: parentTask.creatorAgentId,
|
|
341
|
+
agentId: parentTask.agentId,
|
|
342
|
+
},
|
|
343
|
+
source: "http",
|
|
344
|
+
});
|
|
345
|
+
return decision.allow
|
|
346
|
+
? null
|
|
347
|
+
: `You don't have context for parent task ${parentTaskId}. Only its assignee, its creator, or a lead can create a child task under it.`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The launching session's task (X-Source-Task-Id), kept only when it belongs
|
|
352
|
+
* to the launching agent so a run cannot borrow another agent's session.
|
|
353
|
+
*/
|
|
354
|
+
async function resolveRunSourceTaskId(
|
|
355
|
+
req: IncomingMessage,
|
|
356
|
+
agentId: string,
|
|
357
|
+
): Promise<string | undefined> {
|
|
358
|
+
const raw = req.headers["x-source-task-id"];
|
|
359
|
+
const sourceTaskId = Array.isArray(raw) ? raw[0] : raw;
|
|
360
|
+
if (!sourceTaskId) return undefined;
|
|
361
|
+
const task = await getTaskById(sourceTaskId);
|
|
362
|
+
return task?.agentId === agentId ? task.id : undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** One `ctx.step.agentTask` dispatch: the step task, or the guard rule that blocked it. */
|
|
366
|
+
type StepDispatch =
|
|
367
|
+
| {
|
|
368
|
+
task: AgentTask;
|
|
369
|
+
/** True when this call inserted the step, false on a replay. */
|
|
370
|
+
created?: boolean;
|
|
371
|
+
blocked?: undefined;
|
|
372
|
+
override?: FollowUpRedelegationBlock["rule"];
|
|
373
|
+
}
|
|
374
|
+
| { task?: undefined; blocked: FollowUpRedelegationBlock; override?: undefined };
|
|
375
|
+
|
|
305
376
|
async function requireAgent(res: ServerResponse, agentId: string | undefined) {
|
|
306
377
|
if (!agentId) {
|
|
307
378
|
jsonError(res, "X-Agent-ID required for script runs API", 400);
|
|
@@ -408,6 +479,7 @@ export async function handleScriptRuns(
|
|
|
408
479
|
// Cap check and insert commit together: N concurrent POSTs otherwise all
|
|
409
480
|
// read cap-1 active runs and all insert, exceeding the cap.
|
|
410
481
|
const cap = scriptRunConcurrencyCap();
|
|
482
|
+
const sourceTaskId = await resolveRunSourceTaskId(req, agent.id);
|
|
411
483
|
const creation = await getDbClient().transaction(async () => {
|
|
412
484
|
if ((await countActiveScriptRuns()) >= cap) return null;
|
|
413
485
|
return await createScriptRun({
|
|
@@ -418,6 +490,7 @@ export async function handleScriptRuns(
|
|
|
418
490
|
scriptName: parsed.body.scriptName,
|
|
419
491
|
idempotencyKey: parsed.body.idempotencyKey,
|
|
420
492
|
requestedByUserId: parsed.body.requestedByUserId,
|
|
493
|
+
sourceTaskId,
|
|
421
494
|
createdBy: parsed.body.requestedByUserId,
|
|
422
495
|
});
|
|
423
496
|
});
|
|
@@ -629,9 +702,29 @@ export async function handleScriptRuns(
|
|
|
629
702
|
return true;
|
|
630
703
|
}
|
|
631
704
|
|
|
705
|
+
// Only the run's own harness dispatches its steps (it sends the run's
|
|
706
|
+
// agent id). Step tasks carry that agent as creator, so another agent
|
|
707
|
+
// must not create them.
|
|
708
|
+
if (agentId !== run.agentId) {
|
|
709
|
+
jsonError(res, "Script run belongs to another agent", 403);
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// The requested parent, checked before any lookup or insert so a refused
|
|
714
|
+
// call creates nothing.
|
|
715
|
+
if (parsed.body.parentTaskId) {
|
|
716
|
+
const refusal = await stepParentRefusal(run.agentId, parsed.body.parentTaskId);
|
|
717
|
+
if (refusal) {
|
|
718
|
+
jsonError(res, refusal, 403);
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
632
723
|
const contextKey = `script-run:${run.id}:${parsed.body.stepKey}`;
|
|
633
724
|
let task = await getLatestScriptRunStepTaskByContextKey(contextKey);
|
|
725
|
+
let dispatchedHere = false;
|
|
634
726
|
if (!task) {
|
|
727
|
+
const taskDescription = parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey;
|
|
635
728
|
const modelError = await explicitModelErrorForAgent({
|
|
636
729
|
model: parsed.body.model,
|
|
637
730
|
allowCustomModel: parsed.body.allowCustomModel,
|
|
@@ -641,10 +734,47 @@ export async function handleScriptRuns(
|
|
|
641
734
|
jsonError(res, modelError, 400);
|
|
642
735
|
return true;
|
|
643
736
|
}
|
|
737
|
+
const followUpThread = await followUpThreadForSourceTask(run.sourceTaskId);
|
|
738
|
+
// The replay lookup, the guard decision and the insert share one write
|
|
739
|
+
// transaction. It serializes this step against the run's other steps,
|
|
740
|
+
// other runs and send-task dispatches into the same thread.
|
|
741
|
+
let dispatched: StepDispatch;
|
|
644
742
|
try {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
{
|
|
743
|
+
dispatched = await getDbClient().transaction(async (): Promise<StepDispatch> => {
|
|
744
|
+
const replayed = await getLatestScriptRunStepTaskByContextKey(contextKey);
|
|
745
|
+
if (replayed) return { task: replayed };
|
|
746
|
+
|
|
747
|
+
// Follow-up re-delegation guard, as in send-task. A run is one
|
|
748
|
+
// dispatch: its first new step decides for the whole run, and every
|
|
749
|
+
// later step follows that decision. Later steps never compare against
|
|
750
|
+
// the run's own completed steps, a long run cannot use up the thread's
|
|
751
|
+
// loop cap, and parallel steps are dispatched all or none.
|
|
752
|
+
let override: FollowUpRedelegationBlock["rule"] | undefined;
|
|
753
|
+
if (followUpThread) {
|
|
754
|
+
const decision = await getScriptRunFollowUpGuard(run.id);
|
|
755
|
+
if (decision !== "passed") {
|
|
756
|
+
const blocked: FollowUpRedelegationBlock | null =
|
|
757
|
+
decision === null
|
|
758
|
+
? await evaluateFollowUpRedelegation({
|
|
759
|
+
...followUpThread,
|
|
760
|
+
creatorAgentId: run.agentId,
|
|
761
|
+
taskDescription,
|
|
762
|
+
targetAgentId: parsed.body.agentId,
|
|
763
|
+
})
|
|
764
|
+
: {
|
|
765
|
+
rule: decision,
|
|
766
|
+
message: `Blocked by the follow-up re-delegation guard (${decision} rule): an earlier step of this script run was blocked, and a run counts as one dispatch. If it must run, pass allowRedelegation: true.`,
|
|
767
|
+
};
|
|
768
|
+
if (blocked && !parsed.body.allowRedelegation) {
|
|
769
|
+
if (decision === null) await setScriptRunFollowUpGuard(run.id, blocked.rule);
|
|
770
|
+
return { blocked };
|
|
771
|
+
}
|
|
772
|
+
override = blocked?.rule;
|
|
773
|
+
await setScriptRunFollowUpGuard(run.id, "passed");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const created = await createTaskExtended(taskDescription, {
|
|
648
778
|
agentId: parsed.body.agentId,
|
|
649
779
|
routingReason:
|
|
650
780
|
parsed.body.routingReason ?? (parsed.body.agentId ? "human_pinned" : undefined),
|
|
@@ -660,6 +790,20 @@ export async function handleScriptRuns(
|
|
|
660
790
|
offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
|
|
661
791
|
taskType: "script-run-step",
|
|
662
792
|
source: "mcp",
|
|
793
|
+
// Counts the run toward the launcher's follow-up loop cap.
|
|
794
|
+
creatorAgentId: run.agentId,
|
|
795
|
+
// With no parent, the step inherits the launching session's Slack
|
|
796
|
+
// thread, as a send-task from that session does.
|
|
797
|
+
sourceTaskId: run.sourceTaskId,
|
|
798
|
+
// A guarded run's steps are its dispatches into the follow-up's
|
|
799
|
+
// thread, whatever their parent, so the guard sees them there.
|
|
800
|
+
...(followUpThread
|
|
801
|
+
? {
|
|
802
|
+
slackChannelId: followUpThread.channelId,
|
|
803
|
+
slackThreadTs: followUpThread.threadTs,
|
|
804
|
+
slackUserId: followUpThread.userId,
|
|
805
|
+
}
|
|
806
|
+
: {}),
|
|
663
807
|
dir: parsed.body.dir,
|
|
664
808
|
vcsRepo: parsed.body.vcsRepo,
|
|
665
809
|
model: parsed.body.model,
|
|
@@ -667,8 +811,9 @@ export async function handleScriptRuns(
|
|
|
667
811
|
requestedByUserId: parsed.body.requestedByUserId ?? run.requestedByUserId,
|
|
668
812
|
outputSchema: parsed.body.outputSchema,
|
|
669
813
|
contextKey,
|
|
670
|
-
}
|
|
671
|
-
|
|
814
|
+
});
|
|
815
|
+
return { task: created, created: true, override };
|
|
816
|
+
});
|
|
672
817
|
} catch (error) {
|
|
673
818
|
if (error instanceof ExtensionAgentAssignmentError) {
|
|
674
819
|
jsonError(res, error.message, 400);
|
|
@@ -676,6 +821,30 @@ export async function handleScriptRuns(
|
|
|
676
821
|
}
|
|
677
822
|
throw error;
|
|
678
823
|
}
|
|
824
|
+
if (dispatched.override) {
|
|
825
|
+
console.log(
|
|
826
|
+
`[script-runs] follow-up re-delegation override: rule=${dispatched.override} runId=${run.id} creatorAgentId=${run.agentId} sourceTaskId=${run.sourceTaskId} thread=${followUpThread?.channelId}/${followUpThread?.threadTs}`,
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
if (!dispatched.task) {
|
|
830
|
+
json(res, { error: dispatched.blocked.message, rule: dispatched.blocked.rule }, 422);
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
task = dispatched.task;
|
|
834
|
+
dispatchedHere = dispatched.created === true;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// A replay answers from the selected step, whatever parent this call sent
|
|
838
|
+
// (or omitted), so authorize the parent the step actually holds. Every
|
|
839
|
+
// replay lookup above lands here before any output is returned. A step this
|
|
840
|
+
// call just dispatched was authorized above; refusing it now would leave
|
|
841
|
+
// the work running behind a 403.
|
|
842
|
+
if (!dispatchedHere && task.parentTaskId) {
|
|
843
|
+
const refusal = await stepParentRefusal(run.agentId, task.parentTaskId);
|
|
844
|
+
if (refusal) {
|
|
845
|
+
jsonError(res, refusal, 403);
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
679
848
|
}
|
|
680
849
|
|
|
681
850
|
const deadline = Date.now() + 30_000;
|
package/src/http/status.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { getSlackConnectionState } from "../slack/connection-state";
|
|
|
45
45
|
import { isTelemetryEnabled } from "../telemetry";
|
|
46
46
|
import { type AgentCredStatus, AutomationIntegrationIdSchema, ProviderNameSchema } from "../types";
|
|
47
47
|
import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
48
|
+
import { resolveAppUrl } from "./config-values";
|
|
48
49
|
import { route } from "./route-def";
|
|
49
50
|
import { json, jsonError } from "./utils";
|
|
50
51
|
|
|
@@ -116,6 +117,12 @@ export const StatusIdentitySchema = z.object({
|
|
|
116
117
|
* cloud installs can be sliced server-side.
|
|
117
118
|
*/
|
|
118
119
|
org_id: z.string().nullable(),
|
|
120
|
+
/**
|
|
121
|
+
* The swarm's public dashboard URL (`APP_URL`, then `DASHBOARD_URL`, with
|
|
122
|
+
* swarm_config precedence). Null when neither is set. The dashboard reads
|
|
123
|
+
* links to it in agent output as its own. Absent on older APIs.
|
|
124
|
+
*/
|
|
125
|
+
dashboard_url: z.string().nullable().optional(),
|
|
119
126
|
});
|
|
120
127
|
export type StatusIdentity = z.infer<typeof StatusIdentitySchema>;
|
|
121
128
|
|
|
@@ -334,7 +341,7 @@ export async function rollupCredStatusForProvider(provider: string): Promise<Cre
|
|
|
334
341
|
|
|
335
342
|
// ─── Identity ────────────────────────────────────────────────────────────────
|
|
336
343
|
|
|
337
|
-
function buildIdentity(): StatusIdentity {
|
|
344
|
+
function buildIdentity(dashboardUrl: string | null): StatusIdentity {
|
|
338
345
|
const cloudRaw = process.env.SWARM_CLOUD;
|
|
339
346
|
const hideRaw = process.env.SWARM_HIDE_CLOUD_PROMO;
|
|
340
347
|
return {
|
|
@@ -345,6 +352,7 @@ function buildIdentity(): StatusIdentity {
|
|
|
345
352
|
marketing_url: process.env.SWARM_MARKETING_URL?.trim() || null,
|
|
346
353
|
hide_cloud_promo: hideRaw === "true" || hideRaw === "1",
|
|
347
354
|
org_id: process.env.SWARM_ORG_ID?.trim() || null,
|
|
355
|
+
dashboard_url: dashboardUrl,
|
|
348
356
|
};
|
|
349
357
|
}
|
|
350
358
|
|
|
@@ -759,7 +767,7 @@ export async function buildStatusPayload(): Promise<StatusResponse> {
|
|
|
759
767
|
const instance = await getInstanceFeedbackSignals();
|
|
760
768
|
return {
|
|
761
769
|
runtime_pools: { enabled: isRuntimePoolsEnabled(), supervisor: runtimeSupervisorStatus() },
|
|
762
|
-
identity: buildIdentity(),
|
|
770
|
+
identity: buildIdentity(await resolveAppUrl()),
|
|
763
771
|
setup,
|
|
764
772
|
activity: { ...activity, leads_ready: readyLeads },
|
|
765
773
|
agent_fs: {
|
package/src/http/tasks.ts
CHANGED
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
import { ensure } from "../utils/business-use";
|
|
85
85
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
86
86
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
87
|
-
import { createListTotalCache } from "./list-total-cache";
|
|
87
|
+
import { createListTotalCache, LIST_TOTAL_TTL_MS } from "./list-total-cache";
|
|
88
88
|
import {
|
|
89
89
|
agentFirstPrincipal,
|
|
90
90
|
authUserId,
|
|
@@ -224,7 +224,9 @@ const SupersedeTaskResponseSchema = z.object({
|
|
|
224
224
|
|
|
225
225
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
// The task list total scans every visible task, and only the pager shows it,
|
|
228
|
+
// so page 1 polls reuse a total younger than 15 s.
|
|
229
|
+
const taskTotals = createListTotalCache(LIST_TOTAL_TTL_MS, 15_000);
|
|
228
230
|
|
|
229
231
|
const listTasks = route({
|
|
230
232
|
method: "get",
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "@/be/db";
|
|
14
14
|
import { rememberSessionLabel } from "@/be/db/session-labels";
|
|
15
15
|
import { resolveIdentity } from "@/be/identity";
|
|
16
|
+
import { SteeringBlockedError } from "@/be/steering";
|
|
16
17
|
import { recordUnmappedIdentity } from "@/be/unmapped-identities";
|
|
17
18
|
import { dispatchPre } from "@/extensions/dispatcher";
|
|
18
19
|
import { resolveTemplate } from "@/prompts/resolver";
|
|
@@ -154,11 +155,20 @@ export async function handleChatInbound(
|
|
|
154
155
|
// A thread with messages already waiting keeps its order: append, do not steer past them.
|
|
155
156
|
// Another sender's message takes the buffer, so it gets its own header and requester.
|
|
156
157
|
if (!immediate && attachments.length === 0 && text && !isChatThreadBuffered(thread.id)) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
let steering: Awaited<ReturnType<typeof requestChatThreadSteering>>;
|
|
159
|
+
try {
|
|
160
|
+
steering = await requestChatThreadSteering({
|
|
161
|
+
contextKey: chatSdkContextKey(thread.id),
|
|
162
|
+
message: text,
|
|
163
|
+
resolveSender: () => resolveChatSender(adapter, message.author.userId),
|
|
164
|
+
});
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (!(error instanceof SteeringBlockedError)) throw error;
|
|
167
|
+
console.info(`[chat-sdk] ${thread.id}: steering blocked: ${error.reason}`);
|
|
168
|
+
stopTypingKeepalive(thread.id);
|
|
169
|
+
await setChatMessageReaction(adapter, thread.id, message.id, "failed");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
162
172
|
if (steering) {
|
|
163
173
|
console.log(
|
|
164
174
|
`[chat-sdk] ${adapter} message steered task ${steering.task.id} (${steering.result.outcome})`,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getLatestLeadTaskByContextKey } from "@/be/db";
|
|
2
|
-
import { requestSteering, SteeringRequestError } from "@/be/steering";
|
|
2
|
+
import { requestSteering, SteeringBlockedError, SteeringRequestError } from "@/be/steering";
|
|
3
3
|
import type { AgentTask, SteerResult } from "@/types";
|
|
4
4
|
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
5
5
|
import { isSteeringEnabled } from "@/utils/steering-enabled";
|
|
@@ -44,6 +44,8 @@ export async function requestChatThreadSteering(args: {
|
|
|
44
44
|
});
|
|
45
45
|
return { task, result };
|
|
46
46
|
} catch (error) {
|
|
47
|
+
// A policy block must not fall back to task creation.
|
|
48
|
+
if (error instanceof SteeringBlockedError) throw error;
|
|
47
49
|
const detail = error instanceof Error ? error.message : String(error);
|
|
48
50
|
const what = error instanceof SteeringRequestError ? "was refused" : "failed";
|
|
49
51
|
console.warn(
|
|
@@ -12,6 +12,8 @@ const RESERVED_FAMILIES = new Map([
|
|
|
12
12
|
["worker", "namespace is reserved for worker nudges"],
|
|
13
13
|
// Durable dedupe for SLACK_BLOCK_EXTERNAL_CHANNELS alerts (src/slack/external-channel.ts).
|
|
14
14
|
["slack-external-alert", "namespace is reserved for Slack external channel alerts"],
|
|
15
|
+
// Threads the swarm left via slack-leave-thread (src/slack/left-threads.ts).
|
|
16
|
+
["slack-left-thread", "namespace is reserved for left Slack threads; use slack-leave-thread"],
|
|
15
17
|
// Source group labels (src/be/db/session-labels.ts). Private, see PRIVATE_FAMILIES.
|
|
16
18
|
[
|
|
17
19
|
"session-labels",
|
|
@@ -78,12 +78,15 @@ const REPO_CLAUDE_MD_INLINE_PROVIDERS: ReadonlySet<string> = new Set(["opencode"
|
|
|
78
78
|
/**
|
|
79
79
|
* Template event for the tool-discovery line. Amp, Codex and OpenCode run MCP
|
|
80
80
|
* tools inside their own code sandbox (`code_exec`, `exec`, `execute`), each
|
|
81
|
-
* with its own call shape,
|
|
81
|
+
* with its own call shape, and fx defers them behind `capability_search` under
|
|
82
|
+
* `mcp_<server>_<tool>` names, so the generic lines are wrong for them.
|
|
82
83
|
*/
|
|
83
84
|
function toolDiscoveryEvent(provider: string, hasToolSearch: boolean): string {
|
|
84
85
|
switch (provider) {
|
|
85
86
|
case "amp":
|
|
86
87
|
return "system.agent.tool_discovery.amp";
|
|
88
|
+
case "fx":
|
|
89
|
+
return "system.agent.tool_discovery.fx";
|
|
87
90
|
case "codex":
|
|
88
91
|
return "system.agent.tool_discovery.codex";
|
|
89
92
|
case "opencode":
|
|
@@ -314,6 +314,7 @@ The engine posts the thread tree and the outcome card. You post at most one mess
|
|
|
314
314
|
The outcome card publishes your \`output\` verbatim. Keep free-text \`output\` under 120 words by default, with the exceptions in How you write.
|
|
315
315
|
A Slack task from an unknown user: register them with \`manage-user\` first.
|
|
316
316
|
You MUST use the \`slack-interaction\` skill before you post to Slack.
|
|
317
|
+
Call \`slack-leave-thread\` with your taskId when a thread no longer needs you: people are talking among themselves, someone tells you to stop or back off, or the thread's work is done. The swarm then ignores the thread until someone @mentions it. Leave quietly, with no goodbye message.
|
|
317
318
|
`,
|
|
318
319
|
variables: [],
|
|
319
320
|
category: "system",
|
|
@@ -397,6 +398,21 @@ registerTemplate({
|
|
|
397
398
|
category: "system",
|
|
398
399
|
});
|
|
399
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Last line of an fx system prompt that exceeds the 64 KiB `systemPrompt` cap.
|
|
403
|
+
* The adapter sends the first part and writes the whole prompt to a file.
|
|
404
|
+
*/
|
|
405
|
+
registerTemplate({
|
|
406
|
+
eventType: "system.agent.fx.system_prompt_file",
|
|
407
|
+
header: "",
|
|
408
|
+
defaultBody:
|
|
409
|
+
"These instructions are cut short. Read {{path}} before you start: it holds the complete swarm instructions, of which the text above is only the first part.",
|
|
410
|
+
variables: [
|
|
411
|
+
{ name: "path", description: "Absolute path of the file with the full system prompt" },
|
|
412
|
+
],
|
|
413
|
+
category: "system",
|
|
414
|
+
});
|
|
415
|
+
|
|
400
416
|
/**
|
|
401
417
|
* First message of a cursor session on the first-message path. Cursor keeps
|
|
402
418
|
* its own system prompt there, so the swarm's rides on top of the task prompt.
|
|
@@ -448,6 +464,15 @@ registerTemplate({
|
|
|
448
464
|
category: "system",
|
|
449
465
|
});
|
|
450
466
|
|
|
467
|
+
registerTemplate({
|
|
468
|
+
eventType: "system.agent.tool_discovery.fx",
|
|
469
|
+
header: "",
|
|
470
|
+
defaultBody:
|
|
471
|
+
"Most swarm tools are deferred behind `capability_search`. Each one is named `mcp_swarm_<tool>`, so `store-progress` is `mcp_swarm_store-progress`. Call a tool you know by that name directly. To find one, use `capability_search`.",
|
|
472
|
+
variables: [],
|
|
473
|
+
category: "system",
|
|
474
|
+
});
|
|
475
|
+
|
|
451
476
|
registerTemplate({
|
|
452
477
|
eventType: "system.agent.tool_discovery.codex",
|
|
453
478
|
header: "",
|
|
@@ -574,6 +599,7 @@ registerTemplate({
|
|
|
574
599
|
## Slack (scripts-only)
|
|
575
600
|
|
|
576
601
|
This task originated from Slack (channel: \`{{slackChannelId}}\`). The engine maintains the thread tree and publishes the top-level outcome card. Post at most one distinct agent-authored message per task, concrete, sized to what the user asked for. Progress, start, completion, failure, acknowledgment messages, and relayed raw task output stay out of Slack. Named Slack tools are not exposed in scripts-only mode, so use \`script-run\` with inline source calling \`ctx.swarm.slack_reply({ taskId, message })\` (your taskId carries the thread context).
|
|
602
|
+
When the thread no longer needs you (people are talking among themselves, someone tells you to stop, or the work is done), call \`ctx.swarm.slack_leaveThread({ taskId })\` the same way. The swarm then ignores the thread until someone @mentions it. Leave quietly, with no goodbye message.
|
|
577
603
|
The outcome card publishes your \`output\` verbatim. Keep free-text \`output\` under 120 words by default, with the exceptions in How you write.
|
|
578
604
|
`,
|
|
579
605
|
variables: [
|