@desplega.ai/agent-swarm 2.5.3 → 2.6.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 +2 -2
- package/dist/{acp-adapter-gr2fcyyj.js → acp-adapter-dgfakfpw.js} +9 -9
- package/dist/{actions-wv766ghf.js → actions-fbqma8aa.js} +21 -21
- package/dist/{amp-adapter-speb906t.js → amp-adapter-en0jy6hs.js} +4 -4
- package/dist/{app-ag29n14f.js → app-k8a5cgpp.js} +3 -3
- package/dist/{assistant-3x8bz3eg.js → assistant-a16433gr.js} +30 -26
- package/dist/{boot-reembed-jsmfrpzf.js → boot-reembed-6c1sjfnr.js} +1 -1
- package/dist/{boot-reembed-5b3d2eea.js → boot-reembed-f4nn561n.js} +1 -1
- package/dist/{boot-scrub-sweep-s73gerxy.js → boot-scrub-sweep-d4w9gajf.js} +4 -4
- package/dist/{boot-seeds-5zmax2wr.js → boot-seeds-9bmkspyw.js} +4 -4
- package/dist/{claude-adapter-hswdnm3t.js → claude-adapter-nys1kjxa.js} +9 -9
- package/dist/{claude-managed-adapter-cvbzj1h6.js → claude-managed-adapter-55207973.js} +3 -3
- package/dist/{claude-managed-setup-3k8m3hhc.js → claude-managed-setup-3cm35gey.js} +11 -4
- package/dist/{claude-sdk-session-fkbyk66x.js → claude-sdk-session-nw5yd8cp.js} +9313 -9260
- package/dist/{cli-210xd1f4.js → cli-03gg9pd1.js} +4 -4
- package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
- package/dist/cli-1tem6ns9.js +894 -0
- package/dist/{cli-25j01dpj.js → cli-24j1jeeb.js} +3 -3
- package/dist/{cli-ta8bd7th.js → cli-2sjhpsns.js} +2 -2
- package/dist/{cli-p859hcc8.js → cli-39aet655.js} +2 -2
- package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
- package/dist/{cli-e2ar4bp1.js → cli-3aqd4jaa.js} +121 -47
- package/dist/{cli-jjznk3f7.js → cli-3fwqy71j.js} +69 -35
- package/dist/{cli-p9rafdrb.js → cli-3gfvmzht.js} +7 -7
- package/dist/{cli-03vpq45j.js → cli-3ndmpxxv.js} +2 -2
- package/dist/{cli-aecdcr77.js → cli-4hxrf0p4.js} +228 -74
- package/dist/{cli-mz9afxxf.js → cli-4rf0f5vg.js} +15 -3
- package/dist/{cli-qzme5949.js → cli-5rj83bam.js} +284 -202
- package/dist/{cli-fsdprwrg.js → cli-5sckpqcx.js} +2 -2
- package/dist/{cli-cz5nmdxt.js → cli-683y0b3a.js} +2 -2
- package/dist/{cli-3ngdk5te.js → cli-6y51ryva.js} +2 -2
- package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
- package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
- package/dist/{cli-gx4rm0qx.js → cli-7enjwya9.js} +1 -1
- package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
- package/dist/{cli-avdvgcay.js → cli-9abdgn5t.js} +28 -9
- package/dist/{cli-99xgw26r.js → cli-a2rr0fmg.js} +1 -1
- package/dist/{cli-e5bxnjps.js → cli-a34pr3w1.js} +7 -7
- package/dist/{cli-502eqv93.js → cli-abkm71vr.js} +1 -1
- package/dist/{cli-mmhj8vd7.js → cli-ag2yrnt5.js} +11 -2
- package/dist/{cli-n45mr7zh.js → cli-aj8djyqt.js} +5 -5
- package/dist/{cli-1fc85mvt.js → cli-art7c1zh.js} +5 -5
- package/dist/{cli-40jsnrjm.js → cli-az25fn5f.js} +10 -8
- package/dist/{cli-9s05422w.js → cli-bqztfyve.js} +1 -1
- package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
- package/dist/{cli-svrj71pg.js → cli-c2habvsf.js} +12 -12
- package/dist/{cli-s49hpn37.js → cli-ca6banwz.js} +85 -19
- package/dist/{cli-hhxjnjzs.js → cli-cz37jqss.js} +12 -10
- package/dist/{cli-6n8axjv1.js → cli-d2dpf95n.js} +2 -2
- package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
- package/dist/{cli-z21whrrg.js → cli-emhjcj2y.js} +17 -17
- package/dist/{cli-e8jyq7d8.js → cli-esakngh8.js} +7 -3
- package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
- package/dist/{cli-35gtk1p6.js → cli-ffvsj2qw.js} +44 -13
- package/dist/{cli-ce0ak9ms.js → cli-g20jjwt4.js} +26 -26
- package/dist/{cli-kxawjrxg.js → cli-g8ka5j4j.js} +772 -194
- package/dist/{cli-vwn81jbs.js → cli-gf95q3bm.js} +5 -5
- package/dist/{cli-s53vp709.js → cli-gnsb1zxa.js} +2 -2
- package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
- package/dist/{cli-gpv60278.js → cli-hfeb68z4.js} +7 -7
- package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
- package/dist/{cli-ewm3pwbq.js → cli-k21gx5ht.js} +46 -23
- package/dist/{cli-egjggehm.js → cli-kf4jree9.js} +2 -2
- package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
- package/dist/{cli-p0rzjkya.js → cli-m5a9m0ak.js} +32 -13
- package/dist/{cli-fzcn3d87.js → cli-m70bajzr.js} +3 -3
- package/dist/{cli-w1zwctj2.js → cli-mtnj4fwa.js} +11 -5
- package/dist/{cli-gpsz7g88.js → cli-mvjz5an3.js} +16 -12
- package/dist/{cli-hcpj2xqz.js → cli-n7asmce6.js} +225 -3
- package/dist/{cli-y561h5a9.js → cli-nn6vx2mq.js} +5 -5
- package/dist/cli-nq677csy.js +140 -0
- package/dist/{cli-a5ebc95n.js → cli-pfk92eqz.js} +18 -15
- package/dist/{cli-p6zs9h4y.js → cli-rr7zx19j.js} +767 -249
- package/dist/{cli-qgf8emrn.js → cli-sqma6vw1.js} +10 -7
- package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
- package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
- package/dist/{cli-qqzmjxkf.js → cli-v49fkghp.js} +11 -6
- package/dist/{cli-x77gen2p.js → cli-w2656ka1.js} +7 -7
- package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
- package/dist/{cli-8s5wx3nz.js → cli-zqne1qcj.js} +44 -17
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-n8ka66vf.js → codex-adapter-d95q4qsn.js} +7 -7
- package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
- package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
- package/dist/{codex-session-runner-fsebts37.js → codex-session-runner-gdwd8h0c.js} +8 -8
- package/dist/{commands-t04rt2pc.js → commands-jqmgk54b.js} +2 -2
- package/dist/{controller-rgna04r2.js → controller-z3jq589f.js} +30 -4
- package/dist/{controllers-connect-bqm1rtft.js → controllers-connect-r2za39ef.js} +23 -23
- package/dist/{cursor-adapter-swr2k95y.js → cursor-adapter-4mr4jgkw.js} +2 -3
- package/dist/{db-a4m7haw0.js → db-ppv2tze7.js} +3 -3
- package/dist/{db-24ck09xy.js → db-w36wwb5g.js} +4 -2
- package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
- package/dist/{dispatcher-nwf9ss0q.js → dispatcher-pv5h77vf.js} +20 -20
- package/dist/{docker-nfy2gc8t.js → docker-fdzyewpz.js} +4 -4
- package/dist/{dsh-adapter-kpdnhjst.js → dsh-adapter-d4pdpb3r.js} +2 -3
- package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
- package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
- package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-4sp4dvmc.js} +1 -1
- package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
- package/dist/{execute-0xpz8950.js → execute-jev839y1.js} +1 -1
- package/dist/{external-channel-j1e2p34p.js → external-channel-y6nkzhnn.js} +2 -2
- package/dist/{grok-adapter-qtjz0whk.js → grok-adapter-t08b9ntm.js} +10 -10
- package/dist/handlers-eqsnmv95.js +41 -0
- package/dist/{heartbeat-vtesda5d.js → heartbeat-vn31xwdk.js} +23 -23
- package/dist/{hook-b2hhj6rd.js → hook-6d0zbvvt.js} +6 -5
- package/dist/{hook-8wqaszda.js → hook-cpjs8q51.js} +13 -7
- package/dist/{http-mxcqhhcs.js → http-9c6medda.js} +1085 -136
- package/dist/{index-spwxjgay.js → index-38t35h0n.js} +20 -20
- package/dist/{index-1r8wfpzs.js → index-g6k9tctj.js} +1 -1
- package/dist/{index-srg522m6.js → index-k0yt1tvq.js} +20 -20
- package/dist/{index-31ceyf42.js → index-pkma1yf8.js} +1 -1
- package/dist/{index-n1vbvta9.js → index-qhjf2htm.js} +29 -24
- package/dist/index-yqgbfgaj.js +43 -0
- package/dist/{keepalive-dsd8rtgx.js → keepalive-em0117bp.js} +4 -4
- package/dist/lead-23e7z59t.js +79 -0
- package/dist/{maintenance-jqmqwnet.js → maintenance-kbme3qy9.js} +1 -1
- package/dist/{oauth-refresh-sweep-q2c3bnvm.js → oauth-refresh-sweep-xysj4yse.js} +3 -3
- package/dist/{onboard-w4yd7s8t.js → onboard-p6q0vz97.js} +3 -3
- package/dist/{opencode-adapter-9tppvxar.js → opencode-adapter-esv3k9yn.js} +7 -7
- package/dist/{otel-impl-f36byzbd.js → otel-impl-54qhqh1z.js} +2 -2
- package/dist/{pi-mono-adapter-tqsge1ay.js → pi-mono-adapter-mymbwhj0.js} +41 -14
- package/dist/{pricing-refresh-xvak5p4v.js → pricing-refresh-yb75wv33.js} +3 -3
- package/dist/{rbac-roles-gfn30fb8.js → rbac-roles-1vp2mw4p.js} +2 -2
- package/dist/{rbac-roles-682k2mt5.js → rbac-roles-dexhc9x8.js} +1 -1
- package/dist/render-v2-tjay5hhf.js +16 -0
- package/dist/{resume-2dvq348g.js → resume-8ayf37g5.js} +11 -9
- package/dist/{runtime-bt5pkpm1.js → runtime-hqe5cqcd.js} +1 -1
- package/dist/{session-access-7f21ekq9.js → session-access-fdhp987s.js} +4 -4
- package/dist/session-source-backfill-qgtc86z1.js +110 -0
- package/dist/{setup-epffxtbk.js → setup-4rrmqcq3.js} +1 -1
- package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-52km00d6.js} +2 -2
- package/dist/{work-objects-xz1a5rqk.js → work-objects-p28fgd70.js} +23 -23
- package/dist/worker-e3w7vye1.js +79 -0
- package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
- package/openapi.json +1323 -16
- package/package.json +5 -5
- package/plugin/commands/create-pr.md +2 -2
- package/plugin/pi-skills/create-pr/SKILL.md +2 -2
- package/src/assets/source-key.ts +95 -0
- package/src/be/db/api-keys.ts +80 -0
- package/src/be/db/approvals.ts +48 -0
- package/src/be/db/session-labels.ts +28 -0
- package/src/be/db/session-moves.ts +5 -6
- package/src/be/db/session-tree.ts +153 -14
- package/src/be/db/tasks/read.ts +11 -0
- package/src/be/db/tasks/write.ts +21 -6
- package/src/be/db/workflow-idempotency.ts +183 -0
- package/src/be/db/workflow-runs.ts +500 -7
- package/src/be/db-retention.ts +61 -6
- package/src/be/db.ts +126 -131
- package/src/be/memory/providers/openai-embedding.ts +5 -1
- package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
- package/src/be/migrations/244_api_key_validation.sql +4 -0
- package/src/be/onboarding.ts +4 -0
- package/src/be/scripts/typecheck.ts +1 -1
- package/src/be/sealed-json.ts +1 -0
- package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
- package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/session-source-backfill.ts +163 -0
- package/src/be/sql-connections.ts +7 -1
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/users.ts +9 -0
- package/src/comb/review-batch.ts +28 -1
- package/src/comb/templates.ts +7 -2
- package/src/commands/claude-managed-setup.ts +7 -1
- package/src/commands/controller.ts +33 -0
- package/src/commands/credential-refresh.ts +76 -1
- package/src/commands/credential-wait.ts +2 -2
- package/src/commands/provider-credentials.ts +210 -170
- package/src/commands/runner.ts +31 -6
- package/src/extensions/apply-task-create.ts +64 -11
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +30 -0
- package/src/extensions/dispatcher.ts +4 -0
- package/src/extensions/post-bridge.ts +8 -0
- package/src/hooks/context-bound-note.ts +196 -0
- package/src/hooks/hook.ts +12 -0
- package/src/http/all-routes.ts +2 -0
- package/src/http/api-keys.ts +53 -0
- package/src/http/assets.ts +15 -0
- package/src/http/codex-oauth-accounts.ts +191 -0
- package/src/http/comb.ts +15 -2
- package/src/http/config.ts +49 -17
- package/src/http/core.ts +6 -0
- package/src/http/fleet-benchmark.ts +5 -2
- package/src/http/idempotency-key.ts +117 -0
- package/src/http/index.ts +11 -0
- package/src/http/integrations.ts +5 -1
- package/src/http/kv.ts +8 -3
- package/src/http/onboarding.ts +6 -1
- package/src/http/pool-keys.ts +354 -0
- package/src/http/sessions.ts +37 -12
- package/src/http/stats.ts +1 -0
- package/src/http/status.ts +26 -12
- package/src/http/tasks.ts +3 -0
- package/src/http/used-by.ts +7 -2
- package/src/http/workflows.ts +207 -33
- package/src/integrations/chat-sdk/inbound.ts +27 -0
- package/src/kv-reserved-namespaces.ts +19 -0
- package/src/prompts/context-bound-note.ts +48 -0
- package/src/prompts/session-templates.ts +25 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +30 -8
- package/src/providers/claude-adapter.ts +34 -5
- package/src/providers/claude-managed-adapter.ts +42 -9
- package/src/providers/claude-sdk-session.ts +10 -0
- package/src/providers/claude-session-events.ts +13 -0
- package/src/providers/codex-adapter.ts +83 -11
- package/src/providers/opencode-adapter.ts +18 -0
- package/src/providers/pi-mono-adapter.ts +35 -0
- package/src/runtime-providers/docker.ts +6 -2
- package/src/scheduler/scheduler.ts +3 -2
- package/src/scripts-runtime/swarm-sdk.ts +7 -1
- package/src/scripts-runtime/types/stdlib.d.ts +1 -1
- package/src/scripts-runtime/types/swarm-extension.d.ts +52 -8
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -1
- package/src/slack/assistant.ts +7 -0
- package/src/slack/channel-labels.ts +74 -0
- package/src/slack/handlers.ts +8 -0
- package/src/slack/inbound-files.ts +5 -2
- package/src/slack/thread-buffer.ts +4 -0
- package/src/tasks/sibling-awareness.ts +12 -3
- package/src/telemetry-context.ts +5 -6
- package/src/telemetry-identity.ts +3 -3
- package/src/tests/amp-adapter.test.ts +97 -0
- package/src/tests/api-keys-validation.test.ts +170 -0
- package/src/tests/approval-requests.test.ts +10 -7
- package/src/tests/approval-sweeps.test.ts +12 -6
- package/src/tests/base-prompt.test.ts +1 -1
- package/src/tests/claude-hook-output.test.ts +79 -1
- package/src/tests/claude-managed-adapter.test.ts +321 -0
- package/src/tests/claude-routes-e2e.test.ts +7 -2
- package/src/tests/codex-adapter.test.ts +163 -0
- package/src/tests/codex-credential-pools.test.ts +120 -0
- package/src/tests/codex-gateway-route.test.ts +165 -0
- package/src/tests/codex-oauth-accounts.test.ts +357 -0
- package/src/tests/comb-review-batch.test.ts +63 -2
- package/src/tests/config-pool-keys.test.ts +533 -0
- package/src/tests/context-bound-note.test.ts +269 -0
- package/src/tests/credential-check.test.ts +290 -2
- package/src/tests/credential-refresh.test.ts +172 -1
- package/src/tests/db-retention.test.ts +9 -3
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -3
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/extensions-pre-task.test.ts +325 -0
- package/src/tests/fetch-resolved-env.test.ts +17 -0
- package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
- package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
- package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
- package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
- package/src/tests/fixtures/fake-amp.ts +22 -8
- package/src/tests/fixtures/held-template-render.ts +42 -0
- package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
- package/src/tests/fleet-benchmark.test.ts +8 -0
- package/src/tests/get-workflow-run-tool.test.ts +98 -0
- package/src/tests/integrations-http.test.ts +32 -2
- package/src/tests/internal-ai/complete-structured.test.ts +37 -1
- package/src/tests/internal-ai/credentials.test.ts +23 -0
- package/src/tests/kv-http.test.ts +32 -0
- package/src/tests/kv-tool.test.ts +25 -0
- package/src/tests/multi-runtime-registration.test.ts +12 -12
- package/src/tests/onboarding.test.ts +108 -1
- package/src/tests/openai-embedding-credentials.test.ts +35 -0
- package/src/tests/openapi-route-headers.test.ts +2 -0
- package/src/tests/opencode-adapter.test.ts +134 -0
- package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
- package/src/tests/org-website.test.ts +36 -0
- package/src/tests/pi-mono-adapter.test.ts +105 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/provider-key-probes.test.ts +181 -0
- package/src/tests/rbac-e2e-helpers.test.ts +88 -1
- package/src/tests/rbac-e2e-helpers.ts +36 -11
- package/src/tests/runner-push-delivery.test.ts +25 -0
- package/src/tests/runtime-controller-liveness.test.ts +133 -0
- package/src/tests/runtime-providers-docker.test.ts +2 -0
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/script-run-config-scope.test.ts +91 -3
- package/src/tests/script-runs-retention.test.ts +212 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-logs.test.ts +70 -0
- package/src/tests/session-source-backfill.test.ts +429 -0
- package/src/tests/session-source-keys.test.ts +236 -0
- package/src/tests/session-source-labels.test.ts +419 -0
- package/src/tests/sessions-detail-batch.test.ts +2 -0
- package/src/tests/sessions-move.test.ts +131 -0
- package/src/tests/sessions-tree.test.ts +336 -2
- package/src/tests/slack-inbound-files.test.ts +103 -23
- package/src/tests/status.test.ts +101 -18
- package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
- package/src/tests/test-net.ts +31 -0
- package/src/tests/transcription.test.ts +8 -0
- package/src/tests/used-by-route.test.ts +11 -0
- package/src/tests/workflow-cascade-cancel.test.ts +1341 -0
- package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
- package/src/tests/workflow-http-v2.test.ts +131 -0
- package/src/tests/workflow-idempotency.test.ts +915 -0
- package/src/tests/workflow-step-order.test.ts +92 -0
- package/src/tests/workflow-sub-workflow.test.ts +30 -0
- package/src/tools/get-tasks.ts +8 -0
- package/src/tools/kv/kv-read-auth.ts +4 -2
- package/src/tools/task-action.ts +1 -1
- package/src/tools/workflows/cancel-workflow-run.ts +1 -1
- package/src/tools/workflows/get-workflow-run.ts +14 -4
- package/src/types.ts +24 -0
- package/src/utils/company-profile.ts +51 -1
- package/src/utils/context-window.ts +8 -6
- package/src/utils/credentials.ts +5 -2
- package/src/utils/internal-ai/complete-structured.ts +5 -0
- package/src/utils/internal-ai/credentials.ts +4 -3
- package/src/utils/provider-key-probes.ts +328 -0
- package/src/utils/transcription.ts +4 -1
- package/src/workflows/approval-cancel.ts +15 -13
- package/src/workflows/approval-notifications.ts +5 -1
- package/src/workflows/checkpoint.ts +6 -3
- package/src/workflows/engine.ts +30 -8
- package/src/workflows/executors/foreach.ts +2 -2
- package/src/workflows/executors/sub-workflow.ts +8 -1
- package/src/workflows/foreach-driver.ts +10 -3
- package/src/workflows/foreach-join.ts +29 -6
- package/src/workflows/recovery.ts +6 -1
- package/src/workflows/resume.ts +168 -56
- package/src/workflows/retry-poller.ts +49 -3
- package/src/workflows/task-step-routing.ts +29 -7
- package/templates/ai-toolbox.manifest.json +8 -8
- package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
- package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
- package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
- package/templates/skills/agent-browser/SKILL.md +4 -4
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/code-quality/config.json +1 -1
- package/templates/skills/code-quality/content.md +2 -2
- package/templates/skills/delegate-work/content.md +29 -25
- package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/researching/content.md +4 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -0
- package/templates/skills/swarm-scripts/content.md +1 -0
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-7421jzsw.js +0 -84
- package/dist/cli-e45e4m6b.js +0 -413
- package/dist/cli-zw88zjba.js +0 -307
- package/dist/handlers-19xr6dnz.js +0 -41
- package/dist/index-kw2mmat7.js +0 -43
- package/dist/lead-zyerk1x2.js +0 -79
- package/dist/render-v2-hgytz8nn.js +0 -12
- package/dist/worker-42t8emjt.js +0 -79
|
@@ -30,13 +30,22 @@
|
|
|
30
30
|
|
|
31
31
|
import { existsSync as nodeExistsSync } from "node:fs";
|
|
32
32
|
import { join } from "node:path";
|
|
33
|
+
import {
|
|
34
|
+
CODEX_GATEWAY_API_KEY,
|
|
35
|
+
deriveDefaultRoute,
|
|
36
|
+
type ModelRoute,
|
|
37
|
+
routeCredentialStatus,
|
|
38
|
+
routeUnsetEnv,
|
|
39
|
+
} from "@desplega/model-routing";
|
|
33
40
|
import { buildRatingsFromLlm, fetchRetrievalsForTask, postRatings } from "../be/memory/raters/llm";
|
|
41
|
+
import { contextBoundNoteThreshold, createContextBoundNote } from "../hooks/context-bound-note";
|
|
34
42
|
import { getApiKey } from "../utils/api-key";
|
|
35
43
|
import {
|
|
36
44
|
CONTEXT_FORMULA,
|
|
37
45
|
clampContextPercent,
|
|
38
46
|
computeContextUsedUnified,
|
|
39
47
|
} from "../utils/context-window";
|
|
48
|
+
import { selectCredential } from "../utils/credentials";
|
|
40
49
|
import { isEnvFlagEnabled, isSessionSummarySkipped } from "../utils/env-flag";
|
|
41
50
|
import { SessionErrorTracker } from "../utils/error-tracker";
|
|
42
51
|
import { summarizeSession as runSummarize } from "../utils/internal-ai";
|
|
@@ -81,6 +90,32 @@ import type {
|
|
|
81
90
|
|
|
82
91
|
type CodexConfig = Record<string, CodexConfigValue | undefined>;
|
|
83
92
|
|
|
93
|
+
/** `model_provider` id of the generated gateway block. */
|
|
94
|
+
const CODEX_GATEWAY_PROVIDER_ID = "swarm-gateway";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Config overrides that send Codex through the gateway route. Codex 0.164
|
|
98
|
+
* ignores `OPENAI_BASE_URL` and env keys for its built-in `openai` provider, so
|
|
99
|
+
* the gateway needs its own provider block. Verified on 0.164 against a local
|
|
100
|
+
* gateway: Codex sends `Authorization: Bearer $CODEX_GATEWAY_API_KEY` to
|
|
101
|
+
* `<base_url>/responses` and never the auth.json OAuth token. `wire_api`
|
|
102
|
+
* defaults to `responses` (`chat` is rejected); it is set to state intent.
|
|
103
|
+
*/
|
|
104
|
+
export function codexGatewayConfig(route: ModelRoute): CodexConfig {
|
|
105
|
+
return {
|
|
106
|
+
model_provider: CODEX_GATEWAY_PROVIDER_ID,
|
|
107
|
+
model_providers: {
|
|
108
|
+
[CODEX_GATEWAY_PROVIDER_ID]: {
|
|
109
|
+
name: "Swarm gateway",
|
|
110
|
+
base_url: route.baseUrl,
|
|
111
|
+
env_key: CODEX_GATEWAY_API_KEY,
|
|
112
|
+
wire_api: "responses",
|
|
113
|
+
requires_openai_auth: false,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
84
119
|
/** Plugin id the worker image installs (`codex plugin add context-mode@context-mode`). */
|
|
85
120
|
const CONTEXT_MODE_CODEX_PLUGIN = "context-mode@context-mode";
|
|
86
121
|
|
|
@@ -186,7 +221,10 @@ export type CodexThreadFactory = (options: {
|
|
|
186
221
|
}) => Promise<CodexThread> | CodexThread;
|
|
187
222
|
|
|
188
223
|
/**
|
|
189
|
-
*
|
|
224
|
+
* A gateway route (`CODEX_GATEWAY_BASE_URL` + `CODEX_GATEWAY_API_KEY`) wins
|
|
225
|
+
* and alone decides readiness: a gateway URL without its key is not ready,
|
|
226
|
+
* whatever OAuth or auth.json is present. Otherwise Codex satisfies its
|
|
227
|
+
* credential requirement by ANY of:
|
|
190
228
|
* 1. `~/.codex/auth.json` already exists on disk (the canonical state once
|
|
191
229
|
* `codex login` has run).
|
|
192
230
|
* 2. `OPENAI_API_KEY` is set — the entrypoint will run
|
|
@@ -203,6 +241,13 @@ export function checkCodexCredentials(
|
|
|
203
241
|
env: Record<string, string | undefined>,
|
|
204
242
|
opts: CredCheckOptions = {},
|
|
205
243
|
): CredStatus {
|
|
244
|
+
const gateway = deriveDefaultRoute("codex", env);
|
|
245
|
+
if (gateway) {
|
|
246
|
+
const status = routeCredentialStatus(gateway, env);
|
|
247
|
+
return status.ready
|
|
248
|
+
? { ready: true, missing: [], satisfiedBy: "env", hint: status.hint }
|
|
249
|
+
: { ready: false, missing: status.missing, hint: status.hint };
|
|
250
|
+
}
|
|
206
251
|
const homeDir = opts.homeDir ?? env.HOME ?? "/root";
|
|
207
252
|
const existsSync = opts.fs?.existsSync ?? nodeExistsSync;
|
|
208
253
|
const authFile = `${homeDir}/.codex/auth.json`;
|
|
@@ -995,6 +1040,8 @@ export class CodexSession implements ProviderSession {
|
|
|
995
1040
|
* summary path which materially relies on this ordering for testability.
|
|
996
1041
|
*/
|
|
997
1042
|
private pendingResult: ProviderResult | null = null;
|
|
1043
|
+
/** Fed each main-thread request's prompt size; steers the one-time context-bound note. */
|
|
1044
|
+
private readonly observeContextBound: (contextTokens: number | undefined) => Promise<void>;
|
|
998
1045
|
|
|
999
1046
|
constructor(
|
|
1000
1047
|
thread: CodexThread,
|
|
@@ -1015,6 +1062,13 @@ export class CodexSession implements ProviderSession {
|
|
|
1015
1062
|
skillsDir ?? process.env.CODEX_SKILLS_DIR ?? join(codexUserHome(), ".codex", "skills");
|
|
1016
1063
|
this.summarizeDeps = summarizeDeps;
|
|
1017
1064
|
this.logFileHandle = Bun.file(config.logFile).writer();
|
|
1065
|
+
this.observeContextBound = createContextBoundNote({
|
|
1066
|
+
threshold: contextBoundNoteThreshold(config.env ?? process.env),
|
|
1067
|
+
model: () => this.resolvedModel,
|
|
1068
|
+
// `turn/steer` only joins the active turn, so a late note never starts one.
|
|
1069
|
+
canDeliver: () => this.acceptsSteering && !this.settled && !this.aborted,
|
|
1070
|
+
inject: (note) => this.deliverSteering({ mode: "steer", text: note }),
|
|
1071
|
+
});
|
|
1018
1072
|
// Mirrors the resolution `buildCodexConfig` already applied when
|
|
1019
1073
|
// constructing `mergedConfig` — cheap, pure, and keeps the applied-level
|
|
1020
1074
|
// feedback co-located with `settle()` rather than threading an extra
|
|
@@ -1425,6 +1479,8 @@ export class CodexSession implements ProviderSession {
|
|
|
1425
1479
|
break;
|
|
1426
1480
|
case "context.updated":
|
|
1427
1481
|
this.emitContextUsage(event.usage, event.contextWindow ?? this.contextWindow);
|
|
1482
|
+
// Codex input tokens already include the cached read and write subsets.
|
|
1483
|
+
void this.observeContextBound(event.usage.input_tokens);
|
|
1428
1484
|
break;
|
|
1429
1485
|
case "error": {
|
|
1430
1486
|
const { message } = this.formatTerminalError(event.message, event.codexErrorInfo);
|
|
@@ -1953,14 +2009,21 @@ export async function createInProcessCodexSession(
|
|
|
1953
2009
|
preSessionEvents.push(event);
|
|
1954
2010
|
};
|
|
1955
2011
|
|
|
1956
|
-
const
|
|
2012
|
+
const routeEnv = config.env ?? process.env;
|
|
2013
|
+
const gateway = deriveDefaultRoute("codex", routeEnv);
|
|
2014
|
+
const mergedConfig = {
|
|
2015
|
+
...(await buildCodexConfig(config, resolvedModel, bufferedEmit)),
|
|
2016
|
+
...(gateway ? codexGatewayConfig(gateway) : {}),
|
|
2017
|
+
};
|
|
1957
2018
|
|
|
1958
2019
|
// Auth resolution. `codex_oauth` (in the swarm config store) wins over
|
|
1959
2020
|
// `OPENAI_API_KEY` so users can keep an OpenAI key set for embeddings
|
|
1960
2021
|
// without it shadowing their ChatGPT login. The entrypoint already runs
|
|
1961
2022
|
// this same precedence at boot — this block handles local dev (where
|
|
1962
2023
|
// the entrypoint didn't run) and any case where auth.json is stale.
|
|
1963
|
-
|
|
2024
|
+
// A gateway route skips it: no OAuth is restored for a session whose traffic
|
|
2025
|
+
// goes to the gateway.
|
|
2026
|
+
const authMode = gateway ? null : await resolveCodexAuthMode(config, bufferedEmit);
|
|
1964
2027
|
|
|
1965
2028
|
// The app-server receives an explicit environment. Construct a
|
|
1966
2029
|
// minimal env explicitly so the spawned Codex CLI can find its binary
|
|
@@ -1987,7 +2050,14 @@ export async function createInProcessCodexSession(
|
|
|
1987
2050
|
...buildOtelTraceparentEnv(config.env ?? process.env),
|
|
1988
2051
|
SWARM_CODEX_APP_SERVER: "1",
|
|
1989
2052
|
};
|
|
1990
|
-
if (
|
|
2053
|
+
if (gateway) {
|
|
2054
|
+
for (const key of routeUnsetEnv("codex", gateway)) delete env[key];
|
|
2055
|
+
const gatewayKey = routeEnv[CODEX_GATEWAY_API_KEY];
|
|
2056
|
+
if (gatewayKey) env[CODEX_GATEWAY_API_KEY] = gatewayKey;
|
|
2057
|
+
} else if (authMode === "chatgpt") delete env.OPENAI_API_KEY;
|
|
2058
|
+
else if (env.OPENAI_API_KEY) {
|
|
2059
|
+
env.OPENAI_API_KEY = selectCredential(env.OPENAI_API_KEY, undefined, "OPENAI_API_KEY").selected;
|
|
2060
|
+
}
|
|
1991
2061
|
|
|
1992
2062
|
const codexPathOverride = process.env.CODEX_PATH_OVERRIDE;
|
|
1993
2063
|
|
|
@@ -2139,6 +2209,11 @@ class CodexSubprocessSession implements ProviderSession {
|
|
|
2139
2209
|
};
|
|
2140
2210
|
|
|
2141
2211
|
const apiKey = getApiKey();
|
|
2212
|
+
const providerKeys: Record<string, string> = {};
|
|
2213
|
+
for (const keyVar of ["OPENAI_API_KEY", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY"]) {
|
|
2214
|
+
const value = config.env?.[keyVar] ?? process.env[keyVar];
|
|
2215
|
+
if (value) providerKeys[keyVar] = selectCredential(value, undefined, keyVar).selected;
|
|
2216
|
+
}
|
|
2142
2217
|
|
|
2143
2218
|
this.proc = registerProcessGroup(
|
|
2144
2219
|
Bun.spawn(argv, {
|
|
@@ -2156,13 +2231,7 @@ class CodexSubprocessSession implements ProviderSession {
|
|
|
2156
2231
|
...(process.env.MCP_BASE_URL ? { MCP_BASE_URL: process.env.MCP_BASE_URL } : {}),
|
|
2157
2232
|
...(apiKey ? { AGENT_SWARM_API_KEY: apiKey, API_KEY: apiKey } : {}),
|
|
2158
2233
|
// Embedding / summarization paths read these:
|
|
2159
|
-
...
|
|
2160
|
-
...(process.env.OPENROUTER_API_KEY
|
|
2161
|
-
? { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY }
|
|
2162
|
-
: {}),
|
|
2163
|
-
...(process.env.ANTHROPIC_API_KEY
|
|
2164
|
-
? { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY }
|
|
2165
|
-
: {}),
|
|
2234
|
+
...providerKeys,
|
|
2166
2235
|
...(process.env.CODEX_PATH_OVERRIDE
|
|
2167
2236
|
? { CODEX_PATH_OVERRIDE: process.env.CODEX_PATH_OVERRIDE }
|
|
2168
2237
|
: {}),
|
|
@@ -2463,6 +2532,9 @@ export class CodexAdapter implements ProviderAdapter {
|
|
|
2463
2532
|
}
|
|
2464
2533
|
|
|
2465
2534
|
async createSession(config: ProviderSessionConfig): Promise<ProviderSession> {
|
|
2535
|
+
if (deriveDefaultRoute("codex", config.env ?? process.env)) {
|
|
2536
|
+
console.log("\x1b[2m[codex]\x1b[0m Using credential: gateway");
|
|
2537
|
+
}
|
|
2466
2538
|
if (this.bypassSubprocess) {
|
|
2467
2539
|
return createInProcessCodexSession(config, {
|
|
2468
2540
|
skillsDir: this.skillsDir,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
providerKeyVars,
|
|
22
22
|
} from "@desplega/model-catalog";
|
|
23
23
|
import type { OpenCode, OpenCodeEvent } from "@opencode/sdk";
|
|
24
|
+
import { contextBoundNoteThreshold, createContextBoundNote } from "../hooks/context-bound-note";
|
|
24
25
|
import {
|
|
25
26
|
CONTEXT_FORMULA,
|
|
26
27
|
clampContextPercent,
|
|
@@ -534,6 +535,8 @@ export class OpencodeSession implements ProviderSession {
|
|
|
534
535
|
// Assistant text by message, in part order; the runner keeps the last emit.
|
|
535
536
|
private textByMessage = new Map<string, string[]>();
|
|
536
537
|
private transcript: string[] = [];
|
|
538
|
+
/** Fed each step's prompt size; steers the one-time context-bound note. */
|
|
539
|
+
private readonly observeContextBound: (contextTokens: number | undefined) => Promise<void>;
|
|
537
540
|
|
|
538
541
|
constructor(
|
|
539
542
|
private readonly host: OpenCode.Interface,
|
|
@@ -547,6 +550,20 @@ export class OpencodeSession implements ProviderSession {
|
|
|
547
550
|
this.completionPromise = new Promise<ProviderResult>((resolve) => {
|
|
548
551
|
this.completionResolve = resolve;
|
|
549
552
|
});
|
|
553
|
+
this.observeContextBound = createContextBoundNote({
|
|
554
|
+
threshold: contextBoundNoteThreshold(swarm.env ?? process.env),
|
|
555
|
+
model: () => this.model,
|
|
556
|
+
canDeliver: () => !this.completed && !this.aborted,
|
|
557
|
+
// A steer is promoted at the next step boundary mid-work; `resume: false`
|
|
558
|
+
// leaves an idle session idle. Same route as OpenCode's own Plan reminder.
|
|
559
|
+
inject: (note) =>
|
|
560
|
+
host.sessions.synthetic({
|
|
561
|
+
sessionID: _sessionId,
|
|
562
|
+
text: note,
|
|
563
|
+
delivery: "steer",
|
|
564
|
+
resume: false,
|
|
565
|
+
}),
|
|
566
|
+
});
|
|
550
567
|
}
|
|
551
568
|
|
|
552
569
|
get sessionId(): string {
|
|
@@ -685,6 +702,7 @@ export class OpencodeSession implements ProviderSession {
|
|
|
685
702
|
// Per-step usage drives the dashboard's context gauge.
|
|
686
703
|
const t = ev.data.tokens;
|
|
687
704
|
const contextUsed = t.input + t.cache.read + t.cache.write + t.output;
|
|
705
|
+
void this.observeContextBound(t.input + t.cache.read + t.cache.write);
|
|
688
706
|
const contextTotal = getContextWindowSize(this.model || "default");
|
|
689
707
|
if (contextTotal > 0 && contextUsed > 0) {
|
|
690
708
|
this.emit({
|
|
@@ -61,6 +61,7 @@ import { type TSchema, Type } from "typebox";
|
|
|
61
61
|
import "./pi-codemode-runtime";
|
|
62
62
|
// Registers the reprompt template in the code registry (the runner loads it too).
|
|
63
63
|
import "../commands/templates";
|
|
64
|
+
import { contextBoundNoteThreshold, createContextBoundNote } from "../hooks/context-bound-note";
|
|
64
65
|
import { resolveTemplateAsync } from "../prompts/resolver";
|
|
65
66
|
import { CORE_TOOLS } from "../tools/tool-config";
|
|
66
67
|
import { classifyAwsSdkError } from "../utils/aws-error-classifier";
|
|
@@ -907,6 +908,17 @@ function safeLabel(value: unknown): string {
|
|
|
907
908
|
return typeof value === "string" && /^[A-Za-z0-9_-]{1,32}$/.test(value) ? value : "other";
|
|
908
909
|
}
|
|
909
910
|
|
|
911
|
+
/** Prompt size of the request behind an assistant message: input + cache read + cache write. */
|
|
912
|
+
function requestContextTokens(message: unknown): number | undefined {
|
|
913
|
+
const usage = (
|
|
914
|
+
message as { usage?: { input?: unknown; cacheRead?: unknown; cacheWrite?: unknown } }
|
|
915
|
+
)?.usage;
|
|
916
|
+
if (typeof usage?.input !== "number") return undefined;
|
|
917
|
+
const cacheRead = typeof usage.cacheRead === "number" ? usage.cacheRead : 0;
|
|
918
|
+
const cacheWrite = typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0;
|
|
919
|
+
return usage.input + cacheRead + cacheWrite;
|
|
920
|
+
}
|
|
921
|
+
|
|
910
922
|
function describeAssistantTurn(message: unknown): AssistantTurnShape {
|
|
911
923
|
const msg = (message ?? {}) as {
|
|
912
924
|
content?: unknown;
|
|
@@ -1008,6 +1020,14 @@ export class PiMonoSession implements ProviderSession {
|
|
|
1008
1020
|
private abortRequested = false;
|
|
1009
1021
|
/** Removes this session's own models.json once the session ends. */
|
|
1010
1022
|
private disposeModels: () => Promise<void>;
|
|
1023
|
+
/** Fed each assistant request's prompt size; steers the one-time context-bound note. */
|
|
1024
|
+
private readonly observeContextBound: (contextTokens: number | undefined) => Promise<void>;
|
|
1025
|
+
/**
|
|
1026
|
+
* From a tool-call reply until its turn ends. Pi reads steering after the
|
|
1027
|
+
* turn's tool results; a steer queued later waits for the next reply, and
|
|
1028
|
+
* when that is the final one it starts another turn.
|
|
1029
|
+
*/
|
|
1030
|
+
private toolTurnOpen = false;
|
|
1011
1031
|
|
|
1012
1032
|
constructor(
|
|
1013
1033
|
agentSession: AgentSession,
|
|
@@ -1024,6 +1044,12 @@ export class PiMonoSession implements ProviderSession {
|
|
|
1024
1044
|
this.logFileHandle = Bun.file(config.logFile).writer();
|
|
1025
1045
|
this._sessionId = agentSession.sessionId;
|
|
1026
1046
|
this.sessionStartedAt = Date.now();
|
|
1047
|
+
this.observeContextBound = createContextBoundNote({
|
|
1048
|
+
threshold: contextBoundNoteThreshold(config.env ?? process.env),
|
|
1049
|
+
model: () => this.reportedModel(),
|
|
1050
|
+
canDeliver: () => this.toolTurnOpen && !this.sessionEnded && !this.abortRequested,
|
|
1051
|
+
inject: (note) => this.deliverSteering({ mode: "steer", text: note }),
|
|
1052
|
+
});
|
|
1027
1053
|
|
|
1028
1054
|
// Emit session_init immediately
|
|
1029
1055
|
const piVersion = readPkgVersion("@earendil-works/pi-coding-agent");
|
|
@@ -1098,6 +1124,12 @@ export class PiMonoSession implements ProviderSession {
|
|
|
1098
1124
|
}
|
|
1099
1125
|
// A successful assistant turn means any prior error has recovered.
|
|
1100
1126
|
this.terminalError = null;
|
|
1127
|
+
// Only on tool-call turns: a steer after the final reply would start
|
|
1128
|
+
// another turn.
|
|
1129
|
+
if (turn.hasToolCall) {
|
|
1130
|
+
this.toolTurnOpen = true;
|
|
1131
|
+
void this.observeContextBound(requestContextTokens(event.message));
|
|
1132
|
+
}
|
|
1101
1133
|
if (!turn.hasText && !turn.hasToolCall) {
|
|
1102
1134
|
// Nothing else records these turns: no text and no tool call leaves
|
|
1103
1135
|
// no trace in session logs. Block types only, never content.
|
|
@@ -1152,6 +1184,9 @@ export class PiMonoSession implements ProviderSession {
|
|
|
1152
1184
|
}
|
|
1153
1185
|
break;
|
|
1154
1186
|
}
|
|
1187
|
+
case "turn_end":
|
|
1188
|
+
this.toolTurnOpen = false;
|
|
1189
|
+
break;
|
|
1155
1190
|
case "tool_execution_start": {
|
|
1156
1191
|
const status = (event.args as { status?: unknown } | null)?.status;
|
|
1157
1192
|
if (
|
|
@@ -177,7 +177,9 @@ export class DockerRuntimeProvider implements RuntimeProvider {
|
|
|
177
177
|
try {
|
|
178
178
|
await dockerApi(target, "POST", `/containers/${created.Id}/start`);
|
|
179
179
|
} catch (err) {
|
|
180
|
-
await dockerApi(target, "DELETE", `/containers/${created.Id}?force=true`).catch(
|
|
180
|
+
await dockerApi(target, "DELETE", `/containers/${created.Id}?force=true&v=true`).catch(
|
|
181
|
+
() => {},
|
|
182
|
+
);
|
|
181
183
|
throw err;
|
|
182
184
|
}
|
|
183
185
|
return { kind: "docker", externalId: created.Id };
|
|
@@ -188,7 +190,9 @@ export class DockerRuntimeProvider implements RuntimeProvider {
|
|
|
188
190
|
|
|
189
191
|
async terminate(target: RuntimeTargetConfig, handle: RuntimeHandle): Promise<void> {
|
|
190
192
|
try {
|
|
191
|
-
|
|
193
|
+
// v=true also removes the container's anonymous volumes (the worker image declares
|
|
194
|
+
// VOLUME /logs); named volumes are never removed by it.
|
|
195
|
+
await dockerApi(target, "DELETE", `/containers/${handle.externalId}?force=true&v=true`);
|
|
192
196
|
} catch (err) {
|
|
193
197
|
if (!(err instanceof DockerApiError) || err.statusCode !== 404) throw err;
|
|
194
198
|
}
|
|
@@ -91,8 +91,9 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
|
91
91
|
agentId,
|
|
92
92
|
scriptName: schedule.scriptName,
|
|
93
93
|
// A pointer, not a copy: script schedules fire as often as every minute and
|
|
94
|
-
// script_runs
|
|
95
|
-
// would grow the table by tens of MB a
|
|
94
|
+
// script_runs retention (SCRIPT_RUNS_RETENTION_DAYS) is off by default, so
|
|
95
|
+
// copying the source on every fire would grow the table by tens of MB a
|
|
96
|
+
// day. script_versions keeps the text.
|
|
96
97
|
source: `// Scheduled run of '${script.name}' v${script.version} (global, contentHash ${script.contentHash}) by schedule '${schedule.name}'.\n// Full source: script_versions row for this version.\n`,
|
|
97
98
|
args,
|
|
98
99
|
startedAt: new Date().toISOString(),
|
|
@@ -319,7 +319,13 @@ function bridgeRequestFor(name: string, args: unknown): BridgeRequest | null {
|
|
|
319
319
|
case "workflow_getRun": {
|
|
320
320
|
const id = typeof body.id === "string" ? body.id : undefined;
|
|
321
321
|
if (!id) throw new Error("workflow_getRun requires string `id`");
|
|
322
|
-
|
|
322
|
+
if (body.fields !== undefined && body.fields !== "full" && body.fields !== "slim") {
|
|
323
|
+
throw new Error('workflow_getRun `fields` must be "full" or "slim"');
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
method: "GET",
|
|
327
|
+
path: appendQuery(`/api/workflow-runs/${encodeURIComponent(id)}`, { fields: body.fields }),
|
|
328
|
+
};
|
|
323
329
|
}
|
|
324
330
|
case "workflow_delete": {
|
|
325
331
|
const id = typeof body.id === "string" ? body.id : undefined;
|
|
@@ -378,7 +378,7 @@ declare module "swarm-sdk" {
|
|
|
378
378
|
limit?: number;
|
|
379
379
|
offset?: number;
|
|
380
380
|
}): Promise<unknown>;
|
|
381
|
-
workflow_getRun(args: { id: string }): Promise<unknown>;
|
|
381
|
+
workflow_getRun(args: { id: string; fields?: "full" | "slim" }): Promise<unknown>;
|
|
382
382
|
// --- prompt templates ---
|
|
383
383
|
prompt_list(args?: {
|
|
384
384
|
eventType?: string;
|
|
@@ -9,16 +9,16 @@ declare module "swarm-extension" {
|
|
|
9
9
|
| "schedule"
|
|
10
10
|
| "slack"
|
|
11
11
|
| "agentmail"
|
|
12
|
-
| "mcp"
|
|
13
|
-
| "api"
|
|
14
|
-
| "ui"
|
|
15
12
|
| "github"
|
|
16
13
|
| "gitlab"
|
|
17
14
|
| "azure-devops"
|
|
18
15
|
| "bitbucket"
|
|
19
|
-
| "system"
|
|
20
16
|
| "linear"
|
|
21
17
|
| "jira"
|
|
18
|
+
| "mcp"
|
|
19
|
+
| "api"
|
|
20
|
+
| "ui"
|
|
21
|
+
| "system"
|
|
22
22
|
| "comb"
|
|
23
23
|
| "chat"
|
|
24
24
|
| undefined;
|
|
@@ -127,16 +127,16 @@ declare module "swarm-extension" {
|
|
|
127
127
|
| "schedule"
|
|
128
128
|
| "slack"
|
|
129
129
|
| "agentmail"
|
|
130
|
-
| "mcp"
|
|
131
|
-
| "api"
|
|
132
|
-
| "ui"
|
|
133
130
|
| "github"
|
|
134
131
|
| "gitlab"
|
|
135
132
|
| "azure-devops"
|
|
136
133
|
| "bitbucket"
|
|
137
|
-
| "system"
|
|
138
134
|
| "linear"
|
|
139
135
|
| "jira"
|
|
136
|
+
| "mcp"
|
|
137
|
+
| "api"
|
|
138
|
+
| "ui"
|
|
139
|
+
| "system"
|
|
140
140
|
| "comb"
|
|
141
141
|
| "chat";
|
|
142
142
|
tags: string[];
|
|
@@ -401,6 +401,45 @@ declare module "swarm-extension" {
|
|
|
401
401
|
description: string;
|
|
402
402
|
origin: TaskCreateOrigin;
|
|
403
403
|
requestInfo?: RequestInfo;
|
|
404
|
+
/** Slack ingress metadata; not persisted or writable through modify. */
|
|
405
|
+
slack?: {
|
|
406
|
+
trigger: "mention" | "thread-buffer";
|
|
407
|
+
messages: {
|
|
408
|
+
ts: string;
|
|
409
|
+
userId: string;
|
|
410
|
+
text: string;
|
|
411
|
+
}[];
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/** A pre.task.create hook blocked a task before it was created. */
|
|
415
|
+
export interface TaskBlockedEvent {
|
|
416
|
+
/** The extension that blocked, or whose model/agent rewrite was rejected. */
|
|
417
|
+
extension: {
|
|
418
|
+
id: string;
|
|
419
|
+
name: string;
|
|
420
|
+
};
|
|
421
|
+
/** The scrubbed block reason. */
|
|
422
|
+
reason: string;
|
|
423
|
+
origin: TaskCreateOrigin;
|
|
424
|
+
source?: AgentTask["source"];
|
|
425
|
+
requester: {
|
|
426
|
+
userId?: string;
|
|
427
|
+
agentId?: string;
|
|
428
|
+
slackUserId?: string;
|
|
429
|
+
};
|
|
430
|
+
context?: {
|
|
431
|
+
contextKey?: string;
|
|
432
|
+
scheduleId?: string;
|
|
433
|
+
workflowRunId?: string;
|
|
434
|
+
workflowRunStepId?: string;
|
|
435
|
+
parentTaskId?: string;
|
|
436
|
+
vcsRepo?: string;
|
|
437
|
+
vcsNumber?: number;
|
|
438
|
+
slackChannelId?: string;
|
|
439
|
+
slackThreadTs?: string;
|
|
440
|
+
};
|
|
441
|
+
/** The original task description, scrubbed and cut to 500 characters. */
|
|
442
|
+
description: string;
|
|
404
443
|
}
|
|
405
444
|
export type TaskCreateModify = Partial<
|
|
406
445
|
Pick<
|
|
@@ -704,6 +743,11 @@ declare module "swarm-extension" {
|
|
|
704
743
|
modify: never;
|
|
705
744
|
result: void;
|
|
706
745
|
};
|
|
746
|
+
"post.task.blocked": {
|
|
747
|
+
event: TaskBlockedEvent;
|
|
748
|
+
modify: never;
|
|
749
|
+
result: void;
|
|
750
|
+
};
|
|
707
751
|
"post.task.completed": {
|
|
708
752
|
event: TaskCompletedEvent;
|
|
709
753
|
modify: never;
|
|
@@ -360,7 +360,7 @@ declare module "swarm-sdk" {
|
|
|
360
360
|
limit?: number;
|
|
361
361
|
offset?: number;
|
|
362
362
|
}): Promise<unknown>;
|
|
363
|
-
workflow_getRun(args: { id: string }): Promise<unknown>;
|
|
363
|
+
workflow_getRun(args: { id: string; fields?: "full" | "slim" }): Promise<unknown>;
|
|
364
364
|
// --- prompt templates ---
|
|
365
365
|
prompt_list(args?: {
|
|
366
366
|
eventType?: string;
|
package/src/slack/assistant.ts
CHANGED
|
@@ -213,6 +213,10 @@ export function createAssistant(): Assistant {
|
|
|
213
213
|
buildEffectiveText(messageText, inbound.files, inbound.failed, inbound.notes),
|
|
214
214
|
cachedBotUserId ?? undefined,
|
|
215
215
|
);
|
|
216
|
+
const slack = {
|
|
217
|
+
trigger: "mention" as const,
|
|
218
|
+
messages: [{ ts: message.ts, userId, text: messageText }],
|
|
219
|
+
};
|
|
216
220
|
|
|
217
221
|
if (workingAgent && workingAgent.status !== "offline") {
|
|
218
222
|
// Otherwise, create a follow-up task for the working agent
|
|
@@ -236,6 +240,7 @@ export function createAssistant(): Assistant {
|
|
|
236
240
|
contextKey: slackContextKey({ channelId, threadTs }),
|
|
237
241
|
},
|
|
238
242
|
inbound,
|
|
243
|
+
slack,
|
|
239
244
|
);
|
|
240
245
|
await ackSlackMessage(
|
|
241
246
|
client,
|
|
@@ -285,6 +290,7 @@ export function createAssistant(): Assistant {
|
|
|
285
290
|
contextKey: slackContextKey({ channelId, threadTs }),
|
|
286
291
|
},
|
|
287
292
|
inbound,
|
|
293
|
+
slack,
|
|
288
294
|
);
|
|
289
295
|
await ackSlackMessage(
|
|
290
296
|
client,
|
|
@@ -318,6 +324,7 @@ export function createAssistant(): Assistant {
|
|
|
318
324
|
contextKey: slackContextKey({ channelId, threadTs }),
|
|
319
325
|
},
|
|
320
326
|
inbound,
|
|
327
|
+
slack,
|
|
321
328
|
);
|
|
322
329
|
await ackSlackMessage(client, channelId, message.ts, reactionName("accepted"), "accepted");
|
|
323
330
|
await notifySlackFileFailures(client, channelId, threadTs, unattached);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { WebClient } from "@slack/web-api";
|
|
2
|
+
import { rememberSessionLabel } from "../be/db/session-labels";
|
|
3
|
+
import { getSlackApp } from "./app";
|
|
4
|
+
import { callSlackWithRetry, slackErrorCode } from "./render-v2";
|
|
5
|
+
|
|
6
|
+
const LABEL_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const FAILURE_TTL_MS = 10 * 60 * 1000;
|
|
8
|
+
const FAILURE_CACHE_MAX = 500;
|
|
9
|
+
const LOOKUP_QUEUE_MAX = 50;
|
|
10
|
+
const inFlight = new Set<string>();
|
|
11
|
+
const failedUntil = new Map<string, number>();
|
|
12
|
+
let pending = Promise.resolve();
|
|
13
|
+
|
|
14
|
+
async function channelLabel(client: WebClient, id: string): Promise<string> {
|
|
15
|
+
const result = await callSlackWithRetry(client, "conversations.info", {
|
|
16
|
+
channel: id.toUpperCase(),
|
|
17
|
+
});
|
|
18
|
+
const channel = result.channel as
|
|
19
|
+
| { name?: string; is_im?: boolean; is_mpim?: boolean; user?: string }
|
|
20
|
+
| undefined;
|
|
21
|
+
if (result.ok === false || !channel) throw new Error("Channel lookup failed");
|
|
22
|
+
if (channel.is_mpim) return "Group DM";
|
|
23
|
+
if (channel.is_im && channel.user) {
|
|
24
|
+
const result = await callSlackWithRetry(client, "users.info", { user: channel.user });
|
|
25
|
+
const user = result.user as
|
|
26
|
+
| { profile?: { display_name?: string; real_name?: string }; real_name?: string }
|
|
27
|
+
| undefined;
|
|
28
|
+
const name = user?.profile?.display_name || user?.real_name || user?.profile?.real_name;
|
|
29
|
+
if (result.ok !== false && name) return `DM · ${name}`;
|
|
30
|
+
} else if (channel.name) {
|
|
31
|
+
return `#${channel.name}`;
|
|
32
|
+
}
|
|
33
|
+
throw new Error("Channel label unavailable");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const CHANNEL_GROUP_KEY = /^shared\/slack\/([^/]+)\/$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Queue a Slack lookup for each channel group key (`shared/slack/<channel>/`)
|
|
40
|
+
* without delaying the tree response. Other keys are ignored.
|
|
41
|
+
*/
|
|
42
|
+
export function warmSlackChannelLabels(groupKeys: string[]): void {
|
|
43
|
+
const client = getSlackApp()?.client;
|
|
44
|
+
if (!client) return;
|
|
45
|
+
for (const groupKey of groupKeys) {
|
|
46
|
+
const id = CHANNEL_GROUP_KEY.exec(groupKey)?.[1];
|
|
47
|
+
if (!id) continue;
|
|
48
|
+
if (inFlight.has(id) || (failedUntil.get(id) ?? 0) > Date.now()) continue;
|
|
49
|
+
failedUntil.delete(id);
|
|
50
|
+
if (inFlight.size >= LOOKUP_QUEUE_MAX) continue;
|
|
51
|
+
// Reserve failure capacity for queued work. Live failures must retain their TTL.
|
|
52
|
+
if (failedUntil.size + inFlight.size >= FAILURE_CACHE_MAX) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const [failedId, until] of failedUntil) {
|
|
55
|
+
if (until <= now) failedUntil.delete(failedId);
|
|
56
|
+
}
|
|
57
|
+
if (failedUntil.size + inFlight.size >= FAILURE_CACHE_MAX) continue;
|
|
58
|
+
}
|
|
59
|
+
inFlight.add(id);
|
|
60
|
+
pending = pending.then(async () => {
|
|
61
|
+
try {
|
|
62
|
+
const label = await channelLabel(client, id);
|
|
63
|
+
await rememberSessionLabel(groupKey, label, LABEL_TTL_MS);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
failedUntil.set(id, Date.now() + FAILURE_TTL_MS);
|
|
66
|
+
console.warn(
|
|
67
|
+
`[Slack] Could not cache a session channel label: ${slackErrorCode(error) ?? "unknown error"}`,
|
|
68
|
+
);
|
|
69
|
+
} finally {
|
|
70
|
+
inFlight.delete(id);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/slack/handlers.ts
CHANGED
|
@@ -679,6 +679,11 @@ export function registerMessageHandler(app: App): void {
|
|
|
679
679
|
routingThreadContext,
|
|
680
680
|
);
|
|
681
681
|
|
|
682
|
+
const slack = {
|
|
683
|
+
trigger: "mention" as const,
|
|
684
|
+
messages: [{ ts: msg.ts, userId: msg.user, text: msg.text ?? "" }],
|
|
685
|
+
};
|
|
686
|
+
|
|
682
687
|
if (matches.length === 0) {
|
|
683
688
|
if (!botMentioned && !isImplicitMention) return ignoreSlackInbound("not_addressed");
|
|
684
689
|
|
|
@@ -742,6 +747,7 @@ export function registerMessageHandler(app: App): void {
|
|
|
742
747
|
contextKey: slackContextKey({ channelId: msg.channel, threadTs }),
|
|
743
748
|
},
|
|
744
749
|
inbound,
|
|
750
|
+
slack,
|
|
745
751
|
);
|
|
746
752
|
await ackSlackMessage(client, msg.channel, msg.ts, reactionName("accepted"), "accepted");
|
|
747
753
|
await notifySlackFileFailures(client, msg.channel, threadTs, unattached);
|
|
@@ -864,6 +870,7 @@ export function registerMessageHandler(app: App): void {
|
|
|
864
870
|
contextKey: slackContextKey({ channelId: msg.channel, threadTs }),
|
|
865
871
|
},
|
|
866
872
|
inbound,
|
|
873
|
+
slack,
|
|
867
874
|
);
|
|
868
875
|
const { task } = created;
|
|
869
876
|
unattached.push(...created.unattached);
|
|
@@ -888,6 +895,7 @@ export function registerMessageHandler(app: App): void {
|
|
|
888
895
|
contextKey: slackContextKey({ channelId: msg.channel, threadTs }),
|
|
889
896
|
},
|
|
890
897
|
inbound,
|
|
898
|
+
slack,
|
|
891
899
|
);
|
|
892
900
|
const { task } = created;
|
|
893
901
|
unattached.push(...created.unattached);
|
|
@@ -22,6 +22,7 @@ import { join } from "node:path";
|
|
|
22
22
|
import type { WebClient } from "@slack/web-api";
|
|
23
23
|
import { getTaskAttachments, getTaskById, promoteDraftTask, refreshDraftTaskLease } from "../be/db";
|
|
24
24
|
import { MAX_TASK_ATTACHMENT_BYTES, recordTaskAttachmentUpload } from "../be/task-attachment-store";
|
|
25
|
+
import type { TaskCreateEvent } from "../extensions/contract";
|
|
25
26
|
import { providerPath } from "../fs/provider";
|
|
26
27
|
import { getFileStorageProvider } from "../fs/registry";
|
|
27
28
|
import { resolveTemplate } from "../prompts/resolver";
|
|
@@ -320,17 +321,19 @@ export async function createSlackTaskWithFiles(
|
|
|
320
321
|
description: string,
|
|
321
322
|
options: CreateTaskOptions,
|
|
322
323
|
inbound: InboundSlackFiles,
|
|
324
|
+
slack?: TaskCreateEvent["slack"],
|
|
323
325
|
): Promise<{ task: AgentTask; unattached: SlackFileFailure[] }> {
|
|
324
326
|
noteSlackInboundSideEffect("task_created");
|
|
327
|
+
const context = { origin: "slack" as const, slack };
|
|
325
328
|
if (inbound.fetched.length === 0) {
|
|
326
|
-
const task = await createTaskWithSiblingAwareness(description, options,
|
|
329
|
+
const task = await createTaskWithSiblingAwareness(description, options, context);
|
|
327
330
|
return { task, unattached: inbound.failed };
|
|
328
331
|
}
|
|
329
332
|
|
|
330
333
|
const task = await createTaskWithSiblingAwareness(
|
|
331
334
|
description,
|
|
332
335
|
{ ...options, status: "draft" },
|
|
333
|
-
|
|
336
|
+
context,
|
|
334
337
|
);
|
|
335
338
|
const unattached = [...inbound.failed];
|
|
336
339
|
// Uploads run one after another and can outlast the abandoned-draft sweep's
|
|
@@ -313,6 +313,10 @@ async function slackFlush(
|
|
|
313
313
|
contextKey: slackContextKey({ channelId, threadTs }),
|
|
314
314
|
},
|
|
315
315
|
inbound,
|
|
316
|
+
{
|
|
317
|
+
trigger: "thread-buffer",
|
|
318
|
+
messages: items.map(({ ts, userId, text }) => ({ ts, userId, text })),
|
|
319
|
+
},
|
|
316
320
|
);
|
|
317
321
|
} catch (error) {
|
|
318
322
|
if (!(error instanceof TaskCreationBlockedError)) throw error;
|