@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
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
getInProgressTasksByContextKey,
|
|
19
19
|
} from "../be/db";
|
|
20
20
|
import { applyPreTaskCreate } from "../extensions/apply-task-create";
|
|
21
|
-
import type { TaskCreateOrigin } from "../extensions/contract";
|
|
21
|
+
import type { TaskCreateEvent, TaskCreateOrigin } from "../extensions/contract";
|
|
22
22
|
import type { AgentTask } from "../types";
|
|
23
23
|
import { TaskCreationBlockedError } from "./errors";
|
|
24
24
|
import {
|
|
@@ -151,7 +151,11 @@ export type PreparedTaskCreate = { description: string; options: CreateTaskOptio
|
|
|
151
151
|
export async function prepareTaskWithSiblingAwareness(
|
|
152
152
|
description: string,
|
|
153
153
|
options: CreateTaskOptions,
|
|
154
|
-
args: {
|
|
154
|
+
args: {
|
|
155
|
+
origin?: TaskCreateOrigin;
|
|
156
|
+
allowCustomModel?: boolean;
|
|
157
|
+
slack?: TaskCreateEvent["slack"];
|
|
158
|
+
} = {},
|
|
155
159
|
): Promise<PreparedTaskCreate> {
|
|
156
160
|
const preCreate = await applyPreTaskCreate({
|
|
157
161
|
description,
|
|
@@ -159,6 +163,7 @@ export async function prepareTaskWithSiblingAwareness(
|
|
|
159
163
|
// Preserve the plan's REST default for callers without an explicit ingress origin.
|
|
160
164
|
origin: args.origin ?? "rest",
|
|
161
165
|
allowCustomModel: args.allowCustomModel,
|
|
166
|
+
slack: args.slack,
|
|
162
167
|
});
|
|
163
168
|
if (preCreate.kind === "blocked") {
|
|
164
169
|
throw new TaskCreationBlockedError(preCreate.reason, preCreate.extension);
|
|
@@ -169,7 +174,11 @@ export async function prepareTaskWithSiblingAwareness(
|
|
|
169
174
|
export async function createTaskWithSiblingAwareness(
|
|
170
175
|
description: string,
|
|
171
176
|
options: CreateTaskOptions,
|
|
172
|
-
args: {
|
|
177
|
+
args: {
|
|
178
|
+
origin?: TaskCreateOrigin;
|
|
179
|
+
allowCustomModel?: boolean;
|
|
180
|
+
slack?: TaskCreateEvent["slack"];
|
|
181
|
+
} = {},
|
|
173
182
|
): Promise<AgentTask> {
|
|
174
183
|
const { description: d, options: o } = await prepareTaskWithSiblingAwareness(
|
|
175
184
|
description,
|
package/src/telemetry-context.ts
CHANGED
|
@@ -12,8 +12,8 @@ import catalog from "./telemetry-contract/catalog.json";
|
|
|
12
12
|
import {
|
|
13
13
|
COMPANY_ACQUISITION_SOURCES,
|
|
14
14
|
COMPANY_TEAM_SIZES,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
reportedUseCase,
|
|
16
|
+
reportedUserFunction,
|
|
17
17
|
} from "./utils/company-profile";
|
|
18
18
|
|
|
19
19
|
/** `org_` + 16 lowercase hex (self-host, minted here) or 27 alphanumerics (cloud). Same pattern the proxy enforces. */
|
|
@@ -115,10 +115,9 @@ export function buildContext(input: ContextInput): EventContext {
|
|
|
115
115
|
const orgDomain = validOrgDomain(input.orgDomain);
|
|
116
116
|
const orgWebsite = validOrgDomain(input.orgWebsite);
|
|
117
117
|
const teamSize = COMPANY_TEAM_SIZES.find((value) => value === input.teamSize);
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
: undefined;
|
|
118
|
+
// A value the catalog does not list yet is left out, as if unset.
|
|
119
|
+
const useCase = reportedUseCase(input.useCase);
|
|
120
|
+
const userFunction = userId ? reportedUserFunction(input.userFunction) : undefined;
|
|
122
121
|
return {
|
|
123
122
|
org_id: input.orgId,
|
|
124
123
|
org_name: orgName ? orgName : null,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { getDbClient } from "./be/db/runtime";
|
|
14
14
|
import { setTelemetryOrgDomain } from "./telemetry";
|
|
15
15
|
import { emailDomain, type TelemetryUserRole } from "./telemetry-context";
|
|
16
|
-
import {
|
|
16
|
+
import { reportedUserFunction } from "./utils/company-profile";
|
|
17
17
|
|
|
18
18
|
const ROLE_TTL_MS = 60 * 60_000;
|
|
19
19
|
const ROLE_CACHE_LIMIT = 1_000;
|
|
@@ -21,7 +21,7 @@ const DOMAIN_RECOMPUTE_DEBOUNCE_MS = 2_000;
|
|
|
21
21
|
|
|
22
22
|
interface UserRoleEntry {
|
|
23
23
|
role: TelemetryUserRole;
|
|
24
|
-
userFunction?:
|
|
24
|
+
userFunction?: ReturnType<typeof reportedUserFunction>;
|
|
25
25
|
at: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -40,7 +40,7 @@ async function loadUserRole(userId: string): Promise<UserRoleEntry | undefined>
|
|
|
40
40
|
);
|
|
41
41
|
return {
|
|
42
42
|
role,
|
|
43
|
-
userFunction:
|
|
43
|
+
userFunction: reportedUserFunction(user?.jobFunction),
|
|
44
44
|
at: Date.now(),
|
|
45
45
|
};
|
|
46
46
|
} catch {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
validateProviderCredentials,
|
|
9
9
|
} from "../commands/provider-credentials";
|
|
10
10
|
import { recomputeSessionCost } from "../http/session-cost-recompute";
|
|
11
|
+
import { renderContextBoundNote } from "../prompts/context-bound-note";
|
|
11
12
|
import { createProviderAdapter } from "../providers";
|
|
12
13
|
import {
|
|
13
14
|
AMP_PACKAGE,
|
|
@@ -31,6 +32,7 @@ import { getModelAwareCredentialVars } from "../utils/credentials";
|
|
|
31
32
|
import { resolveHarnessProvider } from "../utils/harness-provider";
|
|
32
33
|
import { clearVolatileSecretsForTesting } from "../utils/secret-scrubber";
|
|
33
34
|
import { ampPricingLookup } from "./amp-pricing-helpers";
|
|
35
|
+
import { holdTemplateRender } from "./fixtures/held-template-render";
|
|
34
36
|
|
|
35
37
|
const KEY = "sgamp_test_credential_value";
|
|
36
38
|
|
|
@@ -769,6 +771,101 @@ describe("amp session", () => {
|
|
|
769
771
|
}, 20_000);
|
|
770
772
|
});
|
|
771
773
|
|
|
774
|
+
describe("amp context-bound note", () => {
|
|
775
|
+
// Each fake request is 4 input + 200 cache read + 1000 cache write = 1204 tokens.
|
|
776
|
+
async function toolResults(
|
|
777
|
+
mode: string,
|
|
778
|
+
env: Record<string, string>,
|
|
779
|
+
overrides: Partial<ProviderSessionConfig> = {},
|
|
780
|
+
) {
|
|
781
|
+
const { config } = await fixture(mode, overrides, env);
|
|
782
|
+
const { events, result } = await runToCompletion(config);
|
|
783
|
+
const results = events.flatMap((e) => (e.type === "tool_end" ? [e.result] : []));
|
|
784
|
+
return { results, result };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
test("queues the note into the first tool result at the threshold, then never again", async () => {
|
|
788
|
+
const { results, result } = await toolResults("steer-twice", {
|
|
789
|
+
CONTEXT_BOUND_NOTE_TOKENS: "1204",
|
|
790
|
+
});
|
|
791
|
+
expect(results).toEqual([`2\n\n${await renderContextBoundNote(1204)}`, "2"]);
|
|
792
|
+
// Carried inside the turn: no extra turn replays it.
|
|
793
|
+
expect(result).toMatchObject({ isError: false, output: "DONE1" });
|
|
794
|
+
expect(result.cost?.numTurns).toBe(1);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
test("a pinned model gets the window share; a mode names no model and gets none", async () => {
|
|
798
|
+
const pin = "anthropic/claude-sonnet-4-6";
|
|
799
|
+
const pinned = await toolResults(
|
|
800
|
+
"steer-twice",
|
|
801
|
+
{ CONTEXT_BOUND_NOTE_TOKENS: "1204" },
|
|
802
|
+
{ model: pin },
|
|
803
|
+
);
|
|
804
|
+
const note = await renderContextBoundNote(1204, pin);
|
|
805
|
+
expect(note).toContain("of the 1,000,000-token window)");
|
|
806
|
+
expect(pinned.results).toEqual([`2\n\n${note}`, "2"]);
|
|
807
|
+
const mode = await toolResults("steer-twice", { CONTEXT_BOUND_NOTE_TOKENS: "1204" });
|
|
808
|
+
expect(mode.results[0]).not.toContain("token window");
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
test("no-op below the threshold and when disabled at 0", async () => {
|
|
812
|
+
for (const threshold of ["1205", "0"]) {
|
|
813
|
+
const { results } = await toolResults("steer-twice", {
|
|
814
|
+
CONTEXT_BOUND_NOTE_TOKENS: threshold,
|
|
815
|
+
});
|
|
816
|
+
expect(results).toEqual(["2", "2"]);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test("fails open on missing or corrupt request usage", async () => {
|
|
821
|
+
for (const usage of ["missing", "corrupt"]) {
|
|
822
|
+
const { results, result } = await toolResults("steer", {
|
|
823
|
+
CONTEXT_BOUND_NOTE_TOKENS: "1",
|
|
824
|
+
AMP_TEST_REQUEST_USAGE: usage,
|
|
825
|
+
});
|
|
826
|
+
expect(results).toEqual(["2"]);
|
|
827
|
+
expect(result).toMatchObject({ isError: false, output: "DONE1" });
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
test("a final reply with no tool call never queues it, so no new turn starts", async () => {
|
|
832
|
+
const { results, result } = await toolResults("steer-no-tool", {
|
|
833
|
+
CONTEXT_BOUND_NOTE_TOKENS: "1",
|
|
834
|
+
});
|
|
835
|
+
expect(results).toEqual([]);
|
|
836
|
+
expect(result).toMatchObject({ isError: false, output: "DONE1" });
|
|
837
|
+
expect(result.cost?.numTurns).toBe(1);
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
test("a note rendered after the last tool result is dropped, not replayed as a new turn", async () => {
|
|
841
|
+
const held = holdTemplateRender();
|
|
842
|
+
try {
|
|
843
|
+
const { config } = await fixture(
|
|
844
|
+
"steer",
|
|
845
|
+
{},
|
|
846
|
+
{
|
|
847
|
+
CONTEXT_BOUND_NOTE_TOKENS: "1204",
|
|
848
|
+
AMP_TEST_POST_RESULT_DELAY_MS: "150",
|
|
849
|
+
},
|
|
850
|
+
);
|
|
851
|
+
const session = await new AmpAdapter().createSession(config);
|
|
852
|
+
const results: unknown[] = [];
|
|
853
|
+
session.onEvent((event) => {
|
|
854
|
+
if (event.type !== "tool_end") return;
|
|
855
|
+
results.push(event.result);
|
|
856
|
+
// Ready while the final reply is pending.
|
|
857
|
+
held.release();
|
|
858
|
+
});
|
|
859
|
+
const result = await session.waitForCompletion();
|
|
860
|
+
expect(results).toEqual(["2"]);
|
|
861
|
+
expect(result).toMatchObject({ isError: false, output: "DONE1" });
|
|
862
|
+
expect(result.cost?.numTurns).toBe(1);
|
|
863
|
+
} finally {
|
|
864
|
+
held.stop();
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
|
|
772
869
|
describe("amp live test", () => {
|
|
773
870
|
const env = async (mode: string) => {
|
|
774
871
|
const { config } = await fixture(mode);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
getAvailableKeyIndices,
|
|
6
|
+
getDbClient,
|
|
7
|
+
getKeyStatuses,
|
|
8
|
+
initDb,
|
|
9
|
+
recordKeyValidation,
|
|
10
|
+
} from "../be/db";
|
|
11
|
+
import { handleApiKeys } from "../http/api-keys";
|
|
12
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
13
|
+
|
|
14
|
+
const TEST_DB = "./test-api-keys-validation.sqlite";
|
|
15
|
+
const keyType = "ANTHROPIC_API_KEY";
|
|
16
|
+
const rejected = { keyIndex: 0, keySuffix: "aaa11", ok: false, httpStatus: 401 };
|
|
17
|
+
|
|
18
|
+
async function call(method: string, url: string, body?: unknown) {
|
|
19
|
+
const req = Readable.from(body === undefined ? [] : [Buffer.from(JSON.stringify(body))]);
|
|
20
|
+
Object.assign(req, { method, url, headers: { "content-type": "application/json" } });
|
|
21
|
+
let statusCode = 200;
|
|
22
|
+
let responseBody = "";
|
|
23
|
+
const res = {
|
|
24
|
+
setHeader: () => {},
|
|
25
|
+
writeHead: (code: number) => {
|
|
26
|
+
statusCode = code;
|
|
27
|
+
},
|
|
28
|
+
end: (chunk?: string) => {
|
|
29
|
+
responseBody += chunk ?? "";
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const handled = await handleApiKeys(
|
|
33
|
+
req as never,
|
|
34
|
+
res as never,
|
|
35
|
+
getPathSegments(url),
|
|
36
|
+
parseQueryParams(url),
|
|
37
|
+
);
|
|
38
|
+
return { handled, statusCode, body: JSON.parse(responseBody) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("pooled key validation", () => {
|
|
42
|
+
beforeAll(() => {
|
|
43
|
+
initDb(TEST_DB);
|
|
44
|
+
});
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
await getDbClient().run("DELETE FROM api_key_status");
|
|
47
|
+
});
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
closeDb();
|
|
50
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
51
|
+
await Bun.file(TEST_DB + suffix)
|
|
52
|
+
.delete()
|
|
53
|
+
.catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("HTTP 401 marks the key as rejected", async () => {
|
|
58
|
+
await recordKeyValidation(keyType, [{ ...rejected, httpStatus: 401 }]);
|
|
59
|
+
const [row] = await getKeyStatuses(keyType);
|
|
60
|
+
expect(row).toMatchObject({
|
|
61
|
+
keyIndex: 0,
|
|
62
|
+
keySuffix: "aaa11",
|
|
63
|
+
provider: "claude",
|
|
64
|
+
lastValidationError: "HTTP 401",
|
|
65
|
+
});
|
|
66
|
+
expect(row!.lastValidationFailedAt).not.toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a pass clears rejection and updates the index", async () => {
|
|
70
|
+
await recordKeyValidation(keyType, [rejected]);
|
|
71
|
+
await recordKeyValidation(keyType, [{ ...rejected, keyIndex: 2, ok: true }]);
|
|
72
|
+
expect((await getKeyStatuses(keyType))[0]).toMatchObject({
|
|
73
|
+
keyIndex: 2,
|
|
74
|
+
lastValidationError: null,
|
|
75
|
+
lastValidationFailedAt: null,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 403 included: a valid key can lack the scope of the list endpoint.
|
|
80
|
+
test.each([
|
|
81
|
+
403,
|
|
82
|
+
500,
|
|
83
|
+
429,
|
|
84
|
+
undefined,
|
|
85
|
+
])("failure with status %s preserves existing state", async (httpStatus) => {
|
|
86
|
+
await recordKeyValidation(keyType, [rejected]);
|
|
87
|
+
const before = await getKeyStatuses(keyType);
|
|
88
|
+
await recordKeyValidation(keyType, [{ ...rejected, keyIndex: 3, httpStatus }]);
|
|
89
|
+
expect(await getKeyStatuses(keyType)).toEqual(before);
|
|
90
|
+
await recordKeyValidation("OPENAI_API_KEY", [{ ...rejected, httpStatus }]);
|
|
91
|
+
expect(await getKeyStatuses("OPENAI_API_KEY")).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("a removed suffix cannot block a replacement at the same index", async () => {
|
|
95
|
+
await recordKeyValidation(keyType, [rejected]);
|
|
96
|
+
await recordKeyValidation(keyType, [{ keyIndex: 0, keySuffix: "bbb22", ok: true }]);
|
|
97
|
+
const rows = await getKeyStatuses(keyType);
|
|
98
|
+
expect(rows.find((row) => row.keySuffix === "aaa11")).toMatchObject({
|
|
99
|
+
lastValidationError: null,
|
|
100
|
+
lastValidationFailedAt: null,
|
|
101
|
+
});
|
|
102
|
+
expect((await getAvailableKeyIndices(keyType, 1)).availableIndices).toEqual([0]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("rejections and removed-suffix cleanup stay within their scope and key type", async () => {
|
|
106
|
+
await recordKeyValidation(keyType, [rejected], "agent", "agent-a");
|
|
107
|
+
await recordKeyValidation(keyType, [rejected], "agent", "agent-b");
|
|
108
|
+
await recordKeyValidation("OPENAI_API_KEY", [rejected], "agent", "agent-a");
|
|
109
|
+
await recordKeyValidation(
|
|
110
|
+
keyType,
|
|
111
|
+
[{ keyIndex: 0, keySuffix: "bbb22", ok: true }],
|
|
112
|
+
"agent",
|
|
113
|
+
"agent-a",
|
|
114
|
+
);
|
|
115
|
+
expect((await getAvailableKeyIndices(keyType, 1, "agent", "agent-a")).availableIndices).toEqual(
|
|
116
|
+
[0],
|
|
117
|
+
);
|
|
118
|
+
expect((await getAvailableKeyIndices(keyType, 1, "agent", "agent-b")).availableIndices).toEqual(
|
|
119
|
+
[],
|
|
120
|
+
);
|
|
121
|
+
expect(
|
|
122
|
+
(await getAvailableKeyIndices("OPENAI_API_KEY", 1, "agent", "agent-a")).availableIndices,
|
|
123
|
+
).toEqual([]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("availability excludes rejected keys and reports their indices", async () => {
|
|
127
|
+
await recordKeyValidation(keyType, [rejected, { keyIndex: 1, keySuffix: "bbb22", ok: true }]);
|
|
128
|
+
expect(await getAvailableKeyIndices(keyType, 3)).toMatchObject({
|
|
129
|
+
availableIndices: [1, 2],
|
|
130
|
+
rejectedIndices: [0],
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("availability is empty when every key is rejected", async () => {
|
|
135
|
+
await recordKeyValidation(keyType, [
|
|
136
|
+
rejected,
|
|
137
|
+
{ ...rejected, keyIndex: 1, keySuffix: "bbb22" },
|
|
138
|
+
]);
|
|
139
|
+
expect(await getAvailableKeyIndices(keyType, 2)).toMatchObject({
|
|
140
|
+
availableIndices: [],
|
|
141
|
+
rejectedIndices: [0, 1],
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("the report handler records validation and exposes it in reads", async () => {
|
|
146
|
+
expect(
|
|
147
|
+
await call("POST", "/api/keys/report-validation", { keyType, keys: [rejected] }),
|
|
148
|
+
).toEqual({
|
|
149
|
+
handled: true,
|
|
150
|
+
statusCode: 200,
|
|
151
|
+
body: { success: true, message: "Key validation recorded" },
|
|
152
|
+
});
|
|
153
|
+
expect(
|
|
154
|
+
(await call("GET", `/api/keys/available?keyType=${keyType}&totalKeys=2`)).body,
|
|
155
|
+
).toMatchObject({
|
|
156
|
+
availableIndices: [1],
|
|
157
|
+
rejectedIndices: [0],
|
|
158
|
+
});
|
|
159
|
+
const status = await call("GET", `/api/keys/status?keyType=${keyType}`);
|
|
160
|
+
expect(status.body.keys[0]).toMatchObject({ lastValidationError: "HTTP 401" });
|
|
161
|
+
expect(status.body.keys[0].lastValidationFailedAt).not.toBeNull();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("the report handler rejects an empty pool", async () => {
|
|
165
|
+
expect(
|
|
166
|
+
(await call("POST", "/api/keys/report-validation", { keyType, keys: [] })).statusCode,
|
|
167
|
+
).toBe(400);
|
|
168
|
+
expect(await getKeyStatuses(keyType)).toEqual([]);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -418,7 +418,7 @@ describe("Approval Requests", () => {
|
|
|
418
418
|
expect(result).toBeNull();
|
|
419
419
|
});
|
|
420
420
|
|
|
421
|
-
test("does not resolve a workflow approval after its run
|
|
421
|
+
test("does not resolve a workflow approval after its run is cancelled", async () => {
|
|
422
422
|
const workflow = await createWorkflow({
|
|
423
423
|
name: `approval-cancel-${crypto.randomUUID()}`,
|
|
424
424
|
definition: { nodes: [] },
|
|
@@ -432,11 +432,12 @@ describe("Approval Requests", () => {
|
|
|
432
432
|
nodeId: "approval",
|
|
433
433
|
nodeType: "human-in-the-loop",
|
|
434
434
|
});
|
|
435
|
-
|
|
436
|
-
|
|
435
|
+
// Asked while the run was live; the run was cancelled after, leaving
|
|
436
|
+
// the request pending (a cancelled step's trigger would close it).
|
|
437
437
|
const approval = await createApprovalRequest(
|
|
438
438
|
makeApprovalData({ workflowRunId: runId, workflowRunStepId: stepId }),
|
|
439
439
|
);
|
|
440
|
+
await updateWorkflowRun(runId, { status: "cancelled" });
|
|
440
441
|
|
|
441
442
|
const result = await resolveApprovalRequest(
|
|
442
443
|
approval.id,
|
|
@@ -1864,15 +1865,17 @@ describe("Approval Requests", () => {
|
|
|
1864
1865
|
|
|
1865
1866
|
test("cancel on a waiting run cancels the run, its step, and its task", async () => {
|
|
1866
1867
|
const { runId, stepId, approvalId } = await makeRun("waiting");
|
|
1868
|
+
// Linked directly: the engine binds a step's task while the step is
|
|
1869
|
+
// still running, and this step is already parked.
|
|
1867
1870
|
const task = await createTaskExtended("step task", {
|
|
1868
1871
|
agentId: owner.id,
|
|
1869
1872
|
source: "mcp",
|
|
1870
1873
|
workflowRunId: runId,
|
|
1871
|
-
workflowRunStepId: stepId,
|
|
1872
1874
|
});
|
|
1873
|
-
await getDbClient().run(
|
|
1874
|
-
|
|
1875
|
-
|
|
1875
|
+
await getDbClient().run(
|
|
1876
|
+
"UPDATE agent_tasks SET status = 'in_progress', workflowRunStepId = ? WHERE id = ?",
|
|
1877
|
+
[stepId, task.id],
|
|
1878
|
+
);
|
|
1876
1879
|
|
|
1877
1880
|
const res = await post(
|
|
1878
1881
|
`/api/approval-requests/${approvalId}/cancel`,
|
|
@@ -223,15 +223,17 @@ describe("approval sweeps", () => {
|
|
|
223
223
|
|
|
224
224
|
test("cancels the waiting run, its step, and its linked task", async () => {
|
|
225
225
|
const { runId, stepId } = await makeRun("waiting");
|
|
226
|
+
// Linked directly: the engine binds a step's task while the step is
|
|
227
|
+
// still running, and this step is already parked.
|
|
226
228
|
const task = await createTaskExtended("step task", {
|
|
227
229
|
agentId,
|
|
228
230
|
source: "mcp",
|
|
229
231
|
workflowRunId: runId,
|
|
230
|
-
workflowRunStepId: stepId,
|
|
231
232
|
});
|
|
232
|
-
await getDbClient().run(
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
await getDbClient().run(
|
|
234
|
+
"UPDATE agent_tasks SET status = 'in_progress', workflowRunStepId = ? WHERE id = ?",
|
|
235
|
+
[stepId, task.id],
|
|
236
|
+
);
|
|
235
237
|
const id = await makeRequest({ ageDays: 8, workflowRunId: runId, workflowRunStepId: stepId });
|
|
236
238
|
|
|
237
239
|
const result = await autoCancelStaleApprovalRequests();
|
|
@@ -300,8 +302,10 @@ describe("approval sweeps", () => {
|
|
|
300
302
|
});
|
|
301
303
|
|
|
302
304
|
test("leaves a failed run as it is", async () => {
|
|
303
|
-
|
|
305
|
+
// The request was asked while the run was live; the run failed after.
|
|
306
|
+
const { runId, stepId } = await makeRun("waiting");
|
|
304
307
|
const id = await makeRequest({ ageDays: 8, workflowRunId: runId, workflowRunStepId: stepId });
|
|
308
|
+
await updateWorkflowRun(runId, { status: "failed" });
|
|
305
309
|
|
|
306
310
|
const result = await autoCancelStaleApprovalRequests();
|
|
307
311
|
|
|
@@ -415,13 +419,15 @@ describe("approval sweeps", () => {
|
|
|
415
419
|
});
|
|
416
420
|
|
|
417
421
|
test("times out a request on a completed run", async () => {
|
|
418
|
-
|
|
422
|
+
// The request was asked while the run was live; the run completed after.
|
|
423
|
+
const { runId, stepId } = await makeRun("waiting");
|
|
419
424
|
const id = await makeRequest({
|
|
420
425
|
ageDays: 0,
|
|
421
426
|
expiresInMs: -60_000,
|
|
422
427
|
workflowRunId: runId,
|
|
423
428
|
workflowRunStepId: stepId,
|
|
424
429
|
});
|
|
430
|
+
await updateWorkflowRun(runId, { status: "completed" });
|
|
425
431
|
await timeoutExpiredApprovalRequests();
|
|
426
432
|
expect((await getApprovalRequestById(id))!.status).toBe("timeout");
|
|
427
433
|
});
|
|
@@ -490,7 +490,7 @@ describe("getBasePrompt: outputs section", () => {
|
|
|
490
490
|
expect(result).toContain("## Outputs");
|
|
491
491
|
expect(result).toContain(AGENT_FS_LINE);
|
|
492
492
|
expect(result).toContain(
|
|
493
|
-
|
|
493
|
+
`Give people the live app link \`\${AGENT_FS_LIVE_URL}/file/~/<org>/<drive>/<path>\`. Make an \`agent-fs share-create\` link only when the requester asks for one or the reader has no agent-fs login.`,
|
|
494
494
|
);
|
|
495
495
|
expect(result).not.toContain(NO_AGENT_FS_LINE);
|
|
496
496
|
});
|
|
@@ -82,7 +82,11 @@ afterAll(async () => {
|
|
|
82
82
|
await rm(projectDir, { recursive: true, force: true });
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
async function runHook(
|
|
85
|
+
async function runHook(
|
|
86
|
+
event: Record<string, unknown>,
|
|
87
|
+
taskId: string,
|
|
88
|
+
extraEnv: Record<string, string> = {},
|
|
89
|
+
) {
|
|
86
90
|
const taskFile = join(projectDir, `task-${taskId}.json`);
|
|
87
91
|
await Bun.write(
|
|
88
92
|
taskFile,
|
|
@@ -92,6 +96,8 @@ async function runHook(event: Record<string, unknown>, taskId: string) {
|
|
|
92
96
|
...process.env,
|
|
93
97
|
CLAUDE_PROJECT_DIR: projectDir,
|
|
94
98
|
TASK_FILE: taskFile,
|
|
99
|
+
CONTEXT_BOUND_NOTE_TOKENS: undefined,
|
|
100
|
+
...extraEnv,
|
|
95
101
|
};
|
|
96
102
|
return runChild(["bun", HOOK_PATH], {
|
|
97
103
|
cwd: projectDir,
|
|
@@ -107,6 +113,9 @@ function parseSoleJson(stdout: string): Record<string, unknown> {
|
|
|
107
113
|
return JSON.parse(trimmed) as Record<string, unknown>;
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
/** The note is off unless CONTEXT_BOUND_NOTE_TOKENS is set. */
|
|
117
|
+
const noteOn = { CONTEXT_BOUND_NOTE_TOKENS: "200000" };
|
|
118
|
+
|
|
110
119
|
const bashTouch = {
|
|
111
120
|
tool_name: "Bash",
|
|
112
121
|
tool_input: { command: "touch /tmp/probe" },
|
|
@@ -230,4 +239,73 @@ describe("claude hook stdout", () => {
|
|
|
230
239
|
},
|
|
231
240
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
232
241
|
);
|
|
242
|
+
|
|
243
|
+
test(
|
|
244
|
+
"worker context-bound note fires once as PostToolUse additionalContext",
|
|
245
|
+
async () => {
|
|
246
|
+
Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
|
|
247
|
+
const taskId = crypto.randomUUID();
|
|
248
|
+
await Bun.write(
|
|
249
|
+
join(projectDir, `task-${taskId}.json.context.json`),
|
|
250
|
+
JSON.stringify({ contextTokens: 250_000, model: "claude-opus-5-5" }),
|
|
251
|
+
);
|
|
252
|
+
const event = { hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} };
|
|
253
|
+
|
|
254
|
+
const first = await runHook(event, taskId, noteOn);
|
|
255
|
+
const out = parseSoleJson(first.stdout) as { hookSpecificOutput: Record<string, string> };
|
|
256
|
+
expect(out.hookSpecificOutput.hookEventName).toBe("PostToolUse");
|
|
257
|
+
expect(out.hookSpecificOutput.additionalContext).toStartWith(
|
|
258
|
+
"Context is 250,000 tokens (25% of the 1,000,000-token window). Keep this task bounded",
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const second = await runHook(event, taskId, noteOn);
|
|
262
|
+
expect(second.exitCode).toBe(0);
|
|
263
|
+
expect(second.stdout.trim()).toBe("");
|
|
264
|
+
},
|
|
265
|
+
CHILD_PROCESS_TEST_BUDGET_MS * 2,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
test(
|
|
269
|
+
"a subagent tool call does not consume the context-bound note",
|
|
270
|
+
async () => {
|
|
271
|
+
Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
|
|
272
|
+
const taskId = crypto.randomUUID();
|
|
273
|
+
await Bun.write(
|
|
274
|
+
join(projectDir, `task-${taskId}.json.context.json`),
|
|
275
|
+
JSON.stringify({ contextTokens: 250_000, model: "made-up-model" }),
|
|
276
|
+
);
|
|
277
|
+
const event = { hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} };
|
|
278
|
+
|
|
279
|
+
const child = await runHook({ ...event, agent_id: "child" }, taskId, noteOn);
|
|
280
|
+
expect(child.exitCode).toBe(0);
|
|
281
|
+
expect(child.stdout.trim()).toBe("");
|
|
282
|
+
|
|
283
|
+
// Unknown model: no window share rather than a percent of the 200k fallback.
|
|
284
|
+
const main = await runHook(event, taskId, noteOn);
|
|
285
|
+
const out = parseSoleJson(main.stdout) as { hookSpecificOutput: Record<string, string> };
|
|
286
|
+
expect(out.hookSpecificOutput.additionalContext).toStartWith(
|
|
287
|
+
"Context is 250,000 tokens. Keep this task bounded",
|
|
288
|
+
);
|
|
289
|
+
},
|
|
290
|
+
CHILD_PROCESS_TEST_BUDGET_MS * 2,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
test(
|
|
294
|
+
"the context-bound note is off when CONTEXT_BOUND_NOTE_TOKENS is unset",
|
|
295
|
+
async () => {
|
|
296
|
+
Object.assign(state, { isLead: false, cancelledTaskId: null, shouldBlockPolling: false });
|
|
297
|
+
const taskId = crypto.randomUUID();
|
|
298
|
+
await Bun.write(
|
|
299
|
+
join(projectDir, `task-${taskId}.json.context.json`),
|
|
300
|
+
JSON.stringify({ contextTokens: 900_000, model: "claude-opus-5-5" }),
|
|
301
|
+
);
|
|
302
|
+
const result = await runHook(
|
|
303
|
+
{ hook_event_name: "PostToolUse", ...bashTouch, tool_response: {} },
|
|
304
|
+
taskId,
|
|
305
|
+
);
|
|
306
|
+
expect(result.exitCode).toBe(0);
|
|
307
|
+
expect(result.stdout.trim()).toBe("");
|
|
308
|
+
},
|
|
309
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
310
|
+
);
|
|
233
311
|
});
|