@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
|
@@ -362,7 +362,14 @@ describe("onboarding state", () => {
|
|
|
362
362
|
signals: { providers: Array<Record<string, unknown>> };
|
|
363
363
|
};
|
|
364
364
|
expect(body.signals.providers).toEqual([
|
|
365
|
-
{
|
|
365
|
+
{
|
|
366
|
+
provider: "claude",
|
|
367
|
+
state: "verified",
|
|
368
|
+
workers: 2,
|
|
369
|
+
verifiedWorkers: 2,
|
|
370
|
+
error: null,
|
|
371
|
+
warning: null,
|
|
372
|
+
},
|
|
366
373
|
]);
|
|
367
374
|
expect(body.state.steps.ai).toMatchObject({
|
|
368
375
|
status: "done",
|
|
@@ -370,6 +377,57 @@ describe("onboarding state", () => {
|
|
|
370
377
|
});
|
|
371
378
|
});
|
|
372
379
|
|
|
380
|
+
test("GET carries the latest live test error and the pool warning", async () => {
|
|
381
|
+
const report = (liveTest: {
|
|
382
|
+
ok: boolean;
|
|
383
|
+
error: string | null;
|
|
384
|
+
latency_ms: number;
|
|
385
|
+
testedAt: number;
|
|
386
|
+
warning?: string;
|
|
387
|
+
}) => ({
|
|
388
|
+
ready: true,
|
|
389
|
+
missing: [],
|
|
390
|
+
satisfiedBy: "env" as const,
|
|
391
|
+
hint: null,
|
|
392
|
+
liveTest,
|
|
393
|
+
reportedAt: Date.now(),
|
|
394
|
+
reportKind: "boot" as const,
|
|
395
|
+
});
|
|
396
|
+
const failing = await createAgent({ name: "codex-worker", isLead: false, status: "idle" });
|
|
397
|
+
await setAgentHarnessProvider(failing.id, "codex");
|
|
398
|
+
await updateAgentCredStatus(
|
|
399
|
+
failing.id,
|
|
400
|
+
report({ ok: false, error: "HTTP 401: bad key", latency_ms: 5, testedAt: Date.now() }),
|
|
401
|
+
);
|
|
402
|
+
const warned = await createAgent({ name: "pi-worker", isLead: false, status: "idle" });
|
|
403
|
+
await setAgentHarnessProvider(warned.id, "pi");
|
|
404
|
+
await updateAgentCredStatus(
|
|
405
|
+
warned.id,
|
|
406
|
+
report({
|
|
407
|
+
ok: true,
|
|
408
|
+
error: null,
|
|
409
|
+
latency_ms: 5,
|
|
410
|
+
testedAt: Date.now(),
|
|
411
|
+
warning: "1 of 2 keys failed: key ending abcde (HTTP 401).",
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const body = (await (await request("/api/onboarding")).json()) as {
|
|
416
|
+
signals: { providers: Array<Record<string, unknown>> };
|
|
417
|
+
};
|
|
418
|
+
const byProvider = Object.fromEntries(body.signals.providers.map((p) => [p.provider, p]));
|
|
419
|
+
expect(byProvider.codex).toMatchObject({
|
|
420
|
+
state: "configured",
|
|
421
|
+
error: "HTTP 401: bad key",
|
|
422
|
+
warning: null,
|
|
423
|
+
});
|
|
424
|
+
expect(byProvider.pi).toMatchObject({
|
|
425
|
+
state: "verified",
|
|
426
|
+
error: null,
|
|
427
|
+
warning: "1 of 2 keys failed: key ending abcde (HTTP 401).",
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
373
431
|
test("GET derives integrations from environment configuration", async () => {
|
|
374
432
|
process.env.GITHUB_TOKEN = "example-github-token";
|
|
375
433
|
const body = (await (await request("/api/onboarding")).json()) as {
|
|
@@ -853,6 +911,55 @@ describe("onboarding memory probe", () => {
|
|
|
853
911
|
expect((await getState()).steps.memory.status).toBe("todo");
|
|
854
912
|
});
|
|
855
913
|
|
|
914
|
+
test("probes and saves the first member of a reused OpenAI pool", async () => {
|
|
915
|
+
const originalFetch = globalThis.fetch;
|
|
916
|
+
let authorization: string | null = null;
|
|
917
|
+
process.env.OPENAI_API_KEY = "example-first, example-second";
|
|
918
|
+
globalThis.fetch = (async (input, init) => {
|
|
919
|
+
const req = new Request(input, init);
|
|
920
|
+
if (new URL(req.url).hostname !== "api.openai.com") return originalFetch(input, init);
|
|
921
|
+
authorization = req.headers.get("authorization");
|
|
922
|
+
return embeddingResponse(512);
|
|
923
|
+
}) as typeof fetch;
|
|
924
|
+
try {
|
|
925
|
+
const response = await request("/api/onboarding/memory", {
|
|
926
|
+
method: "POST",
|
|
927
|
+
body: { preset: "openai", reuseKey: "OPENAI_API_KEY" },
|
|
928
|
+
});
|
|
929
|
+
expect(await response.json()).toMatchObject({ ok: true, dimensions: 512 });
|
|
930
|
+
expect(authorization).toBe("Bearer example-first");
|
|
931
|
+
const configs = await getSwarmConfigs({ scope: "global", key: "EMBEDDING_API_KEY" });
|
|
932
|
+
expect(configs[0]?.value).toBe("example-first");
|
|
933
|
+
} finally {
|
|
934
|
+
globalThis.fetch = originalFetch;
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
for (const keyVar of ["EMBEDDING_API_KEY", "OPENAI_API_KEY"]) {
|
|
939
|
+
test(`existing memory probes the first member of ${keyVar}`, async () => {
|
|
940
|
+
const originalFetch = globalThis.fetch;
|
|
941
|
+
let authorization: string | null = null;
|
|
942
|
+
process.env[keyVar] = "example-first, example-second";
|
|
943
|
+
process.env.EMBEDDING_API_BASE_URL = "https://api.openai.com/v1";
|
|
944
|
+
globalThis.fetch = (async (input, init) => {
|
|
945
|
+
const req = new Request(input, init);
|
|
946
|
+
if (new URL(req.url).hostname !== "api.openai.com") return originalFetch(input, init);
|
|
947
|
+
authorization = req.headers.get("authorization");
|
|
948
|
+
return embeddingResponse(512);
|
|
949
|
+
}) as typeof fetch;
|
|
950
|
+
try {
|
|
951
|
+
const response = await request("/api/onboarding/memory", {
|
|
952
|
+
method: "POST",
|
|
953
|
+
body: { preset: "existing" },
|
|
954
|
+
});
|
|
955
|
+
expect(await response.json()).toMatchObject({ ok: true, dimensions: 512 });
|
|
956
|
+
expect(authorization).toBe("Bearer example-first");
|
|
957
|
+
} finally {
|
|
958
|
+
globalThis.fetch = originalFetch;
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
856
963
|
test("does not send a reused OpenAI key to another host", async () => {
|
|
857
964
|
let hits = 0;
|
|
858
965
|
const fake = startEmbeddingServer(() => {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { OpenAIEmbeddingProvider } from "../be/memory/providers/openai-embedding";
|
|
3
|
+
|
|
4
|
+
for (const source of ["config", "EMBEDDING_API_KEY", "OPENAI_API_KEY"] as const) {
|
|
5
|
+
test(`embedding selects one key from ${source}`, async () => {
|
|
6
|
+
const originalFetch = globalThis.fetch;
|
|
7
|
+
const saved = {
|
|
8
|
+
EMBEDDING_API_KEY: process.env.EMBEDDING_API_KEY,
|
|
9
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
10
|
+
};
|
|
11
|
+
delete process.env.EMBEDDING_API_KEY;
|
|
12
|
+
delete process.env.OPENAI_API_KEY;
|
|
13
|
+
const pool = "example-first, example-second";
|
|
14
|
+
if (source !== "config") process.env[source] = pool;
|
|
15
|
+
let authorization: string | null = null;
|
|
16
|
+
globalThis.fetch = (async (input, init) => {
|
|
17
|
+
authorization = new Request(input, init).headers.get("authorization");
|
|
18
|
+
return Response.json({ data: [{ embedding: [0.5] }] });
|
|
19
|
+
}) as typeof fetch;
|
|
20
|
+
try {
|
|
21
|
+
const provider = new OpenAIEmbeddingProvider({
|
|
22
|
+
dimensions: 1,
|
|
23
|
+
...(source === "config" ? { apiKey: pool } : {}),
|
|
24
|
+
});
|
|
25
|
+
expect(await provider.embed("test input")).toEqual(new Float32Array([0.5]));
|
|
26
|
+
expect(["Bearer example-first", "Bearer example-second"]).toContain(authorization);
|
|
27
|
+
} finally {
|
|
28
|
+
globalThis.fetch = originalFetch;
|
|
29
|
+
for (const [key, value] of Object.entries(saved)) {
|
|
30
|
+
if (value === undefined) delete process.env[key];
|
|
31
|
+
else process.env[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -75,6 +75,8 @@ describe("runtime identity header in OpenAPI", () => {
|
|
|
75
75
|
"GET /api/poll",
|
|
76
76
|
"POST /api/mcp-bridge",
|
|
77
77
|
"POST /api/scripts/run",
|
|
78
|
+
"POST /api/workflows",
|
|
79
|
+
"POST /api/workflows/{id}/trigger",
|
|
78
80
|
"POST /close",
|
|
79
81
|
"POST /ping",
|
|
80
82
|
"PUT /api/agents/{id}/credential-status",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { afterAll, afterEach, beforeAll, describe, expect, spyOn, test } from "bun:test";
|
|
11
11
|
import type { OpenCodeEvent } from "@opencode/sdk";
|
|
12
12
|
import { normalizeSessionLogs, type SessionLogRecord } from "../../apps/ui/src/logs-parser";
|
|
13
|
+
import { renderContextBoundNote } from "../prompts/context-bound-note";
|
|
13
14
|
import {
|
|
14
15
|
_setOpencodeSdkLoaderForTests,
|
|
15
16
|
_setOpencodeSessionHooksForTests,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import { createSkillInvokeTracker, type SkillInvoke } from "../providers/skill-invoke";
|
|
23
24
|
import type { ProviderEvent, ProviderSessionConfig } from "../providers/types";
|
|
24
25
|
import { clearVolatileSecretsForTesting } from "../utils/secret-scrubber";
|
|
26
|
+
import { HELD_NOTE_TEXT, holdTemplateRender } from "./fixtures/held-template-render";
|
|
25
27
|
|
|
26
28
|
const SESSION_ID = "ses_test";
|
|
27
29
|
|
|
@@ -68,6 +70,7 @@ type FakeHostOptions = {
|
|
|
68
70
|
tools?: string[];
|
|
69
71
|
/** Model responses handed to the plugin's `http.response` hook at startup. */
|
|
70
72
|
responses?: Response[];
|
|
73
|
+
syntheticError?: Error;
|
|
71
74
|
};
|
|
72
75
|
|
|
73
76
|
/**
|
|
@@ -85,6 +88,7 @@ function makeFakeHost(events: OpenCodeEvent[], opts: FakeHostOptions = {}) {
|
|
|
85
88
|
createOptions: undefined as unknown as Record<string, unknown>,
|
|
86
89
|
created: [] as unknown[],
|
|
87
90
|
prompts: [] as Array<Record<string, unknown>>,
|
|
91
|
+
synthetics: [] as Array<Record<string, unknown>>,
|
|
88
92
|
interrupts: [] as unknown[],
|
|
89
93
|
closeCalls: 0,
|
|
90
94
|
mcpListCalls: 0,
|
|
@@ -116,6 +120,11 @@ function makeFakeHost(events: OpenCodeEvent[], opts: FakeHostOptions = {}) {
|
|
|
116
120
|
host.interrupts.push(input);
|
|
117
121
|
return {};
|
|
118
122
|
},
|
|
123
|
+
synthetic: async (input: Record<string, unknown>) => {
|
|
124
|
+
host.synthetics.push(input);
|
|
125
|
+
if (opts.syntheticError) throw opts.syntheticError;
|
|
126
|
+
return {};
|
|
127
|
+
},
|
|
119
128
|
},
|
|
120
129
|
events: {
|
|
121
130
|
async *subscribe() {
|
|
@@ -815,6 +824,131 @@ describe("OpencodeSession — abort and steering", () => {
|
|
|
815
824
|
});
|
|
816
825
|
});
|
|
817
826
|
|
|
827
|
+
describe("OpencodeSession — context-bound note", () => {
|
|
828
|
+
const boundConfig = (threshold: string) =>
|
|
829
|
+
testConfig({ env: { CONTEXT_BOUND_NOTE_TOKENS: threshold } });
|
|
830
|
+
const step = (sessionID: string, input: number, read = 0, write = 0) =>
|
|
831
|
+
ev("session.step.ended", { sessionID, tokens: tokens(input, 10, read, write) });
|
|
832
|
+
|
|
833
|
+
test("steers the note once at the threshold, not again on a larger step", async () => {
|
|
834
|
+
const { session, host } = await startSession([], boundConfig("1000"));
|
|
835
|
+
// 600 input + 300 cache read + 100 cache write = 1,000 (output does not count).
|
|
836
|
+
session.handleOpencodeEvent(step(SESSION_ID, 500, 300, 100));
|
|
837
|
+
session.handleOpencodeEvent(step(SESSION_ID, 600, 300, 100));
|
|
838
|
+
session.handleOpencodeEvent(step(SESSION_ID, 5_000));
|
|
839
|
+
await waitFor(() => host.synthetics.length > 0);
|
|
840
|
+
await Bun.sleep(20);
|
|
841
|
+
expect(host.synthetics).toEqual([
|
|
842
|
+
{
|
|
843
|
+
sessionID: SESSION_ID,
|
|
844
|
+
text: await renderContextBoundNote(1_000, "openrouter/google/gemini-3-flash-preview"),
|
|
845
|
+
delivery: "steer",
|
|
846
|
+
resume: false,
|
|
847
|
+
},
|
|
848
|
+
]);
|
|
849
|
+
expect(host.synthetics[0]?.text).toContain("1,000");
|
|
850
|
+
session.handleOpencodeEvent(succeeded);
|
|
851
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test("the note shows the window share for a known model and omits it for an unknown one", async () => {
|
|
855
|
+
const noteFor = async (model: string) => {
|
|
856
|
+
const { session, host } = await startSession(
|
|
857
|
+
[],
|
|
858
|
+
testConfig({ model, env: { CONTEXT_BOUND_NOTE_TOKENS: "1000" } }),
|
|
859
|
+
);
|
|
860
|
+
session.handleOpencodeEvent(step(SESSION_ID, 524_288));
|
|
861
|
+
await waitFor(() => host.synthetics.length > 0);
|
|
862
|
+
session.handleOpencodeEvent(succeeded);
|
|
863
|
+
await session.waitForCompletion();
|
|
864
|
+
return host.synthetics[0]?.text;
|
|
865
|
+
};
|
|
866
|
+
const rest =
|
|
867
|
+
"Keep this task bounded: finish the current step, store-progress, and complete or defer instead of starting new exploration.";
|
|
868
|
+
expect(await noteFor("openrouter/google/gemini-3-flash-preview")).toBe(
|
|
869
|
+
`Context is 524,288 tokens (50% of the 1,048,576-token window). ${rest}`,
|
|
870
|
+
);
|
|
871
|
+
expect(await noteFor("openrouter/made-up/model")).toBe(`Context is 524,288 tokens. ${rest}`);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
test("off when CONTEXT_BOUND_NOTE_TOKENS is unset", async () => {
|
|
875
|
+
const { session, host } = await startSession([]);
|
|
876
|
+
session.handleOpencodeEvent(step(SESSION_ID, 900_000));
|
|
877
|
+
await Bun.sleep(20);
|
|
878
|
+
expect(host.synthetics).toEqual([]);
|
|
879
|
+
session.handleOpencodeEvent(succeeded);
|
|
880
|
+
await session.waitForCompletion();
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test("no-op below the threshold and for a child session's steps", async () => {
|
|
884
|
+
const { session, host } = await startSession([], boundConfig("1000"));
|
|
885
|
+
session.handleOpencodeEvent(step(SESSION_ID, 999));
|
|
886
|
+
session.handleOpencodeEvent(step("ses_child", 50_000));
|
|
887
|
+
await Bun.sleep(20);
|
|
888
|
+
expect(host.synthetics).toEqual([]);
|
|
889
|
+
session.handleOpencodeEvent(succeeded);
|
|
890
|
+
await session.waitForCompletion();
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
test("off at 0", async () => {
|
|
894
|
+
const { session, host } = await startSession([], boundConfig("0"));
|
|
895
|
+
session.handleOpencodeEvent(step(SESSION_ID, 250_000));
|
|
896
|
+
await Bun.sleep(20);
|
|
897
|
+
expect(host.synthetics).toEqual([]);
|
|
898
|
+
session.handleOpencodeEvent(succeeded);
|
|
899
|
+
await session.waitForCompletion();
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
test("fails open on corrupt step tokens", async () => {
|
|
903
|
+
const { session, host } = await startSession([], boundConfig("1000"));
|
|
904
|
+
session.handleOpencodeEvent(
|
|
905
|
+
ev("session.step.ended", {
|
|
906
|
+
sessionID: SESSION_ID,
|
|
907
|
+
tokens: { input: "lots", output: 1, reasoning: 0, cache: { read: null, write: {} } },
|
|
908
|
+
}),
|
|
909
|
+
);
|
|
910
|
+
await Bun.sleep(20);
|
|
911
|
+
expect(host.synthetics).toEqual([]);
|
|
912
|
+
session.handleOpencodeEvent(succeeded);
|
|
913
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test("a rejected steer still lets the session complete", async () => {
|
|
917
|
+
const { session, host } = await startSession([], boundConfig("1000"), {
|
|
918
|
+
syntheticError: new Error("synthetic refused"),
|
|
919
|
+
});
|
|
920
|
+
session.handleOpencodeEvent(step(SESSION_ID, 5_000));
|
|
921
|
+
await waitFor(() => host.synthetics.length > 0);
|
|
922
|
+
session.handleOpencodeEvent(succeeded);
|
|
923
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
test("a note rendered after the session completed is dropped", async () => {
|
|
927
|
+
const lateSynthetics = async (releaseInSession: boolean) => {
|
|
928
|
+
const held = holdTemplateRender();
|
|
929
|
+
try {
|
|
930
|
+
const { session, host } = await startSession([], boundConfig("1000"));
|
|
931
|
+
session.handleOpencodeEvent(step(SESSION_ID, 5_000));
|
|
932
|
+
await held.requested;
|
|
933
|
+
if (releaseInSession) {
|
|
934
|
+
held.release();
|
|
935
|
+
await waitFor(() => host.synthetics.length > 0);
|
|
936
|
+
}
|
|
937
|
+
session.handleOpencodeEvent(succeeded);
|
|
938
|
+
expect((await session.waitForCompletion()).isError).toBe(false);
|
|
939
|
+
held.release();
|
|
940
|
+
await Bun.sleep(50);
|
|
941
|
+
return host.synthetics.map((s) => s.text);
|
|
942
|
+
} finally {
|
|
943
|
+
held.stop();
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
expect(await lateSynthetics(false)).toEqual([]);
|
|
947
|
+
// Control: ready while the session runs.
|
|
948
|
+
expect(await lateSynthetics(true)).toEqual([HELD_NOTE_TEXT]);
|
|
949
|
+
});
|
|
950
|
+
});
|
|
951
|
+
|
|
818
952
|
describe("OpenRouter attribution config", () => {
|
|
819
953
|
test("overrides harness attribution while preserving custom headers and options", () => {
|
|
820
954
|
const config = {
|
|
@@ -164,12 +164,12 @@ describe("workflow raw-llm and validate nodes", () => {
|
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
-
describe("OpenRouter credential check (GET /
|
|
167
|
+
describe("OpenRouter credential check (GET /key)", () => {
|
|
168
168
|
for (const c of CASES) {
|
|
169
169
|
test(`${c.name}`, async () => {
|
|
170
170
|
setEnv(c.env);
|
|
171
171
|
await validateProviderCredentials("grok", { OPENROUTER_API_KEY: "example-or-key" });
|
|
172
|
-
expect(requests[0]?.url.endsWith("/
|
|
172
|
+
expect(requests[0]?.url.endsWith("/key")).toBe(true);
|
|
173
173
|
expectAttribution(requests[0]?.headers, c.attributed);
|
|
174
174
|
});
|
|
175
175
|
}
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
COMPANY_ACQUISITION_SOURCES,
|
|
6
6
|
COMPANY_TEAM_SIZES,
|
|
7
7
|
COMPANY_USE_CASES,
|
|
8
|
+
reportedUseCase,
|
|
9
|
+
reportedUserFunction,
|
|
8
10
|
} from "../utils/company-profile";
|
|
9
11
|
import {
|
|
10
12
|
isFilledOrgLogoUrl,
|
|
@@ -157,6 +159,40 @@ describe("company profile config guards", () => {
|
|
|
157
159
|
}
|
|
158
160
|
});
|
|
159
161
|
|
|
162
|
+
describe("profile values the telemetry catalog does not list yet", () => {
|
|
163
|
+
const input = {
|
|
164
|
+
orgId: "org_0123456789abcdef",
|
|
165
|
+
installationId: "install_0123456789abcdef",
|
|
166
|
+
isCloud: false,
|
|
167
|
+
isE2b: false,
|
|
168
|
+
swarmVersion: "1.0.0",
|
|
169
|
+
actor: { userId: "user-1" },
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
test("the config guards accept them", () => {
|
|
173
|
+
for (const value of ["content", "research", "seo", "admin"]) {
|
|
174
|
+
expect(validateConfigValue("SWARM_ORG_USE_CASE", value)).toBeNull();
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("stay out of the telemetry context, as if unset", () => {
|
|
179
|
+
for (const useCase of ["content", "research", "seo", "admin"]) {
|
|
180
|
+
expect(reportedUseCase(useCase)).toBeUndefined();
|
|
181
|
+
expect(buildContext({ ...input, useCase })).not.toHaveProperty("use_case");
|
|
182
|
+
}
|
|
183
|
+
for (const userFunction of ["marketing", "sales", "research"]) {
|
|
184
|
+
expect(reportedUserFunction(userFunction)).toBeUndefined();
|
|
185
|
+
expect(buildContext({ ...input, userFunction })).not.toHaveProperty("user_function");
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("the catalog values still report", () => {
|
|
190
|
+
const context = buildContext({ ...input, useCase: "docs", userFunction: "founder" });
|
|
191
|
+
expect(context.use_case).toBe("docs");
|
|
192
|
+
expect(context.user_function).toBe("founder");
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
160
196
|
describe("org_website in the telemetry context", () => {
|
|
161
197
|
const input = {
|
|
162
198
|
orgId: "org_0123456789abcdef",
|
|
@@ -12,6 +12,7 @@ import { tmpdir } from "node:os";
|
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { OPENAI_COMPATIBLE_PROVIDERS } from "@desplega/model-catalog";
|
|
14
14
|
import * as piCodingAgent from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { renderContextBoundNote } from "../prompts/context-bound-note";
|
|
15
16
|
import { configureHttpResolver, resetHttpResolver } from "../prompts/resolver";
|
|
16
17
|
import {
|
|
17
18
|
checkPiMonoCredentials,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
writeSessionModelsJson,
|
|
25
26
|
} from "../providers/pi-mono-adapter";
|
|
26
27
|
import type { ProviderSessionConfig } from "../providers/types";
|
|
28
|
+
import { HELD_NOTE_TEXT, holdTemplateRender } from "./fixtures/held-template-render";
|
|
27
29
|
|
|
28
30
|
describe("PiMonoAdapter", () => {
|
|
29
31
|
test("name is 'pi'", () => {
|
|
@@ -1187,6 +1189,8 @@ function makeMockAgentSession(opts: {
|
|
|
1187
1189
|
steerCalls?: string[];
|
|
1188
1190
|
followUpCalls?: string[];
|
|
1189
1191
|
steeringError?: string;
|
|
1192
|
+
/** Awaited after each call's events, as a real turn keeps running its tools. */
|
|
1193
|
+
afterEvents?: () => Promise<unknown>;
|
|
1190
1194
|
}): AgentSession {
|
|
1191
1195
|
const listeners: Array<(event: AgentSessionEvent) => void> = [];
|
|
1192
1196
|
const scripts = opts.turns ?? [opts.events ?? []];
|
|
@@ -1210,6 +1214,7 @@ function makeMockAgentSession(opts: {
|
|
|
1210
1214
|
for (const event of scripts[call] ?? []) {
|
|
1211
1215
|
for (const l of listeners) l(event);
|
|
1212
1216
|
}
|
|
1217
|
+
await opts.afterEvents?.();
|
|
1213
1218
|
if (opts.throwError) throw new Error(opts.throwError);
|
|
1214
1219
|
},
|
|
1215
1220
|
getContextUsage: () => null,
|
|
@@ -1754,3 +1759,103 @@ describe("classifyAwsSdkError — all 4 categories (quick summary)", () => {
|
|
|
1754
1759
|
}
|
|
1755
1760
|
});
|
|
1756
1761
|
});
|
|
1762
|
+
|
|
1763
|
+
describe("PiMonoSession context-bound note", () => {
|
|
1764
|
+
/** An assistant `message_end` with this request usage; a tool call unless `final`. */
|
|
1765
|
+
function assistantTurn(usage: unknown, final = false): AgentSessionEvent {
|
|
1766
|
+
return {
|
|
1767
|
+
type: "message_end",
|
|
1768
|
+
message: {
|
|
1769
|
+
role: "assistant",
|
|
1770
|
+
content: final
|
|
1771
|
+
? [{ type: "text", text: "Done" }]
|
|
1772
|
+
: [{ type: "toolCall", id: "call-1", name: "bash", arguments: {} }],
|
|
1773
|
+
stopReason: final ? "stop" : "toolUse",
|
|
1774
|
+
usage,
|
|
1775
|
+
},
|
|
1776
|
+
} as unknown as AgentSessionEvent;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
const usage = (input: number) => ({ input, cacheRead: 300, cacheWrite: 200, output: 50 });
|
|
1780
|
+
|
|
1781
|
+
async function steersFor(
|
|
1782
|
+
threshold: string,
|
|
1783
|
+
events: AgentSessionEvent[],
|
|
1784
|
+
afterEvents: () => Promise<unknown> = () => Bun.sleep(50),
|
|
1785
|
+
model?: string,
|
|
1786
|
+
): Promise<string[]> {
|
|
1787
|
+
const steerCalls: string[] = [];
|
|
1788
|
+
const session = new PiMonoSession(
|
|
1789
|
+
makeMockAgentSession({ events, steerCalls, afterEvents }),
|
|
1790
|
+
{
|
|
1791
|
+
...makeSessionConfig(join(tmpLogDir, `bound-note-${crypto.randomUUID()}.log`)),
|
|
1792
|
+
env: { CONTEXT_BOUND_NOTE_TOKENS: threshold },
|
|
1793
|
+
...(model ? { model } : {}),
|
|
1794
|
+
},
|
|
1795
|
+
false,
|
|
1796
|
+
);
|
|
1797
|
+
await session.waitForCompletion();
|
|
1798
|
+
return steerCalls;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
test("steers the note once when a tool-call request reaches the threshold", async () => {
|
|
1802
|
+
const steers = await steersFor("1000", [
|
|
1803
|
+
assistantTurn(usage(400)),
|
|
1804
|
+
assistantTurn(usage(500)),
|
|
1805
|
+
assistantTurn(usage(900)),
|
|
1806
|
+
assistantTurn(usage(1500), true),
|
|
1807
|
+
]);
|
|
1808
|
+
// 500 + 300 + 200 reaches 1000; the larger later request does not steer again.
|
|
1809
|
+
expect(steers).toEqual([await renderContextBoundNote(1000)]);
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
test("the note shows the window share for a known model and omits it for an unknown one", async () => {
|
|
1813
|
+
const events = () => [assistantTurn(usage(209_500)), assistantTurn(usage(400), true)];
|
|
1814
|
+
const rest =
|
|
1815
|
+
"Keep this task bounded: finish the current step, store-progress, and complete or defer instead of starting new exploration.";
|
|
1816
|
+
expect(await steersFor("200000", events(), undefined, "anthropic/claude-sonnet-4-6")).toEqual([
|
|
1817
|
+
`Context is 210,000 tokens (21% of the 1,000,000-token window). ${rest}`,
|
|
1818
|
+
]);
|
|
1819
|
+
expect(await steersFor("200000", events(), undefined, "made-up/model")).toEqual([
|
|
1820
|
+
`Context is 210,000 tokens. ${rest}`,
|
|
1821
|
+
]);
|
|
1822
|
+
});
|
|
1823
|
+
|
|
1824
|
+
test("no-op below the threshold and when disabled at 0", async () => {
|
|
1825
|
+
const events = [assistantTurn(usage(400)), assistantTurn(usage(400), true)];
|
|
1826
|
+
expect(await steersFor("1000", events)).toEqual([]);
|
|
1827
|
+
expect(await steersFor("0", events)).toEqual([]);
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
test("fails open on missing or corrupt usage", async () => {
|
|
1831
|
+
const events = [
|
|
1832
|
+
assistantTurn(undefined),
|
|
1833
|
+
assistantTurn({ input: "lots", cacheRead: {}, cacheWrite: null }),
|
|
1834
|
+
assistantTurn(undefined, true),
|
|
1835
|
+
];
|
|
1836
|
+
expect(await steersFor("1", events)).toEqual([]);
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
test("never steers after the final reply, which would start another turn", async () => {
|
|
1840
|
+
expect(await steersFor("1", [assistantTurn(usage(5000), true)])).toEqual([]);
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
test("a note rendered after its tool turn ended is dropped", async () => {
|
|
1844
|
+
const turnEnd = { type: "turn_end" } as unknown as AgentSessionEvent;
|
|
1845
|
+
const lateSteers = async (events: AgentSessionEvent[]) => {
|
|
1846
|
+
const held = holdTemplateRender();
|
|
1847
|
+
try {
|
|
1848
|
+
return await steersFor("1", events, async () => {
|
|
1849
|
+
held.release();
|
|
1850
|
+
await Bun.sleep(50);
|
|
1851
|
+
});
|
|
1852
|
+
} finally {
|
|
1853
|
+
held.stop();
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
// Pi already read steering for that turn; the next reply could be the final one.
|
|
1857
|
+
expect(await lateSteers([assistantTurn(usage(5000)), turnEnd])).toEqual([]);
|
|
1858
|
+
// Control: ready while the tool turn is still running.
|
|
1859
|
+
expect(await lateSteers([assistantTurn(usage(5000))])).toEqual([HELD_NOTE_TEXT]);
|
|
1860
|
+
});
|
|
1861
|
+
});
|
|
@@ -22,6 +22,7 @@ const SYSTEM_TEMPLATES = [
|
|
|
22
22
|
"system.agent.communication",
|
|
23
23
|
"system.agent.communication.remote",
|
|
24
24
|
"system.agent.compaction.goal_reminder",
|
|
25
|
+
"system.agent.context.bound_note",
|
|
25
26
|
"system.agent.cursor.first_message",
|
|
26
27
|
"system.agent.lead",
|
|
27
28
|
"system.agent.memory",
|
|
@@ -117,11 +118,11 @@ describe("Session templates: registration", () => {
|
|
|
117
118
|
expect(sessionSystemEventTypes("session")).toEqual([...SESSION_TEMPLATES].sort());
|
|
118
119
|
});
|
|
119
120
|
|
|
120
|
-
test("registers
|
|
121
|
+
test("registers 32 system and session templates in total", () => {
|
|
121
122
|
const all = getAllTemplateDefinitions();
|
|
122
123
|
const sessionSystem = all.filter((d) => d.category === "system" || d.category === "session");
|
|
123
|
-
//
|
|
124
|
-
expect(sessionSystem.length).toBe(
|
|
124
|
+
// 27 system blocks + 5 session composites.
|
|
125
|
+
expect(sessionSystem.length).toBe(32);
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
test("drops the v1 blocks that prompt v2 deleted", () => {
|