@desplega.ai/agent-swarm 2.5.4 → 2.7.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 +4 -2
- package/dist/{acp-adapter-ek38cwsv.js → acp-adapter-ma1js5n7.js} +9 -9
- package/dist/{actions-92mepq03.js → actions-1vhmtvg9.js} +21 -21
- package/dist/{amp-adapter-9gaxg015.js → amp-adapter-sxqqvmdk.js} +4 -4
- package/dist/{app-ctn8gsgx.js → app-mwx1d5a9.js} +3 -3
- package/dist/{assistant-zyr5kz47.js → assistant-yx1p6g82.js} +30 -26
- package/dist/{boot-reembed-w75maga2.js → boot-reembed-5h37a2c2.js} +1 -1
- package/dist/{boot-reembed-dwzfttdr.js → boot-reembed-7h78vmvv.js} +2 -1
- package/dist/{boot-scrub-sweep-e9kq43kf.js → boot-scrub-sweep-8mawd4gw.js} +4 -4
- package/dist/{boot-seeds-x28v3fhb.js → boot-seeds-ca84dhnn.js} +4 -4
- package/dist/{claude-adapter-f0afqnne.js → claude-adapter-wqt4hfkq.js} +9 -9
- package/dist/{claude-managed-adapter-x81a7r65.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
- package/dist/{claude-managed-setup-bqzwjd9x.js → claude-managed-setup-rqv98sea.js} +10 -3
- package/dist/{claude-sdk-session-7b7whkaq.js → claude-sdk-session-f29f3b2x.js} +11 -11
- package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
- package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
- package/dist/{cli-g9vd9ece.js → cli-1hhbzkbb.js} +14 -16
- package/dist/{cli-qqzmjxkf.js → cli-1m9x8dr5.js} +11 -6
- package/dist/{cli-jbnn4vxb.js → cli-2pvzmas1.js} +3 -3
- package/dist/{cli-x77gen2p.js → cli-2y7v133v.js} +7 -7
- package/dist/{cli-dew6x255.js → cli-39aet655.js} +2 -2
- package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
- package/dist/{cli-jb30dzvk.js → cli-48gxjmcz.js} +530 -190
- package/dist/{cli-pzatvw94.js → cli-54sj64fc.js} +2 -2
- package/dist/{cli-bvpgr9ny.js → cli-60sty27p.js} +5 -5
- package/dist/{cli-asq5ydm4.js → cli-6a9qfa8b.js} +411 -69
- package/dist/{cli-277c31dw.js → cli-6mgdba42.js} +225 -3
- package/dist/{cli-19202kew.js → cli-6srv653c.js} +5 -5
- package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
- package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
- package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
- package/dist/{cli-hh5we352.js → cli-86ehn4s5.js} +7 -7
- package/dist/{cli-9s05422w.js → cli-8mbc9za9.js} +1 -1
- package/dist/{cli-ff1tm9n9.js → cli-8wmj18cz.js} +1 -1
- package/dist/{cli-1r5ad7j5.js → cli-9gsm2rzp.js} +12 -12
- package/dist/{cli-mz9afxxf.js → cli-9pshx6rd.js} +17 -3
- package/dist/{cli-s682zgqz.js → cli-a6ds2epe.js} +1 -1
- package/dist/{cli-6a1xg1p7.js → cli-a89zdqz9.js} +2 -2
- package/dist/{cli-03vpq45j.js → cli-ay40dnj3.js} +2 -2
- package/dist/{cli-tsma7p8v.js → cli-aypd09nf.js} +2 -2
- package/dist/{cli-ethgy7ha.js → cli-b3yzyc57.js} +2 -2
- package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
- package/dist/{cli-mmhj8vd7.js → cli-c668vvcf.js} +11 -2
- package/dist/{cli-sbwsxvtd.js → cli-d20qvpr2.js} +2 -2
- package/dist/{cli-c952c7ac.js → cli-e1595bgx.js} +3 -3
- package/dist/{cli-621ce7kw.js → cli-f12mr20z.js} +3 -3
- package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
- package/dist/{cli-xzfq4at3.js → cli-f4dbh7a5.js} +16 -12
- package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
- package/dist/{cli-5bnsc0e1.js → cli-gsfk58nw.js} +5 -5
- package/dist/cli-gt4h8cxw.js +895 -0
- package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
- package/dist/{cli-25n3mv5d.js → cli-h5jeg46p.js} +6 -6
- package/dist/{cli-gn5af0x5.js → cli-he9gbdx0.js} +62 -12
- package/dist/{cli-gbfxp48k.js → cli-j0ppywdr.js} +42 -35
- package/dist/{cli-k4tjsdx9.js → cli-j3y0x2n4.js} +277 -199
- package/dist/{cli-7p4jxgtt.js → cli-j9bpmykh.js} +75 -27
- package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
- package/dist/{cli-wx2nj19m.js → cli-kv7sbc2c.js} +6 -6
- package/dist/{cli-abs8ndtr.js → cli-m30k9777.js} +26 -26
- package/dist/{cli-cz5nmdxt.js → cli-m5e802r7.js} +2 -2
- package/dist/{cli-0shx9j93.js → cli-pfk92eqz.js} +2 -2
- package/dist/{cli-xtrz6bzs.js → cli-ptfq202x.js} +5 -3
- package/dist/{cli-6n8axjv1.js → cli-qj442fws.js} +2 -2
- package/dist/{cli-c61mbdds.js → cli-rd5m70gj.js} +4 -4
- package/dist/{cli-eftzyh9y.js → cli-s09j9vp5.js} +2 -2
- package/dist/{cli-m4ecdxvz.js → cli-s0mgx214.js} +7 -7
- package/dist/{cli-wghjkr0e.js → cli-sgqm2582.js} +10 -7
- package/dist/{cli-2655k16q.js → cli-sjctjr48.js} +2 -2
- package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
- package/dist/{cli-x0f2v69c.js → cli-twctnkng.js} +12 -7
- package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
- package/dist/{cli-gx4rm0qx.js → cli-vz78s5wm.js} +1 -1
- package/dist/{cli-gwhjmh8g.js → cli-wqr7cea4.js} +5 -5
- package/dist/{cli-e42vss7b.js → cli-x37r7qwf.js} +16 -8
- package/dist/{cli-4b2z6n0m.js → cli-yd2nreqw.js} +17 -8
- package/dist/{cli-64mh9mf3.js → cli-ymjpkxgn.js} +69 -17
- package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
- package/dist/{cli-9j129brq.js → cli-yxbsmx56.js} +6 -6
- package/dist/cli.js +22 -20
- package/dist/{codex-adapter-wch12q99.js → codex-adapter-zk59x2zh.js} +7 -7
- package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
- package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
- package/dist/{codex-session-runner-41pfcr95.js → codex-session-runner-kh6s4j3n.js} +8 -8
- package/dist/{commands-9849dkj6.js → commands-68et1qv2.js} +2 -2
- package/dist/{controller-ja0sd95p.js → controller-fyrbnzg4.js} +4 -4
- package/dist/{controllers-connect-xp05dj6x.js → controllers-connect-nep9h2xv.js} +23 -23
- package/dist/{cursor-adapter-623qxqew.js → cursor-adapter-yss3pg7t.js} +2 -2
- package/dist/{db-w759qmx0.js → db-28gjyvxj.js} +4 -2
- package/dist/{db-47vj40dy.js → db-8ftwvzba.js} +3 -3
- package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
- package/dist/{dispatcher-7b8e4z2j.js → dispatcher-hayexmkr.js} +20 -20
- package/dist/{docker-s84hjs91.js → docker-fdzyewpz.js} +2 -2
- package/dist/{dsh-adapter-2ezw3xea.js → dsh-adapter-46wawff9.js} +2 -2
- 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-8d1e0h4m.js} +1 -1
- package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
- package/dist/{execute-0xpz8950.js → execute-cjrfxfnm.js} +1 -1
- package/dist/{external-channel-e2s028da.js → external-channel-xbmxcg09.js} +2 -2
- package/dist/{grok-adapter-ys7gaqee.js → grok-adapter-ag4wks2a.js} +10 -10
- package/dist/handlers-tzmevqb5.js +41 -0
- package/dist/{heartbeat-w3mntghj.js → heartbeat-psfvcgwp.js} +23 -23
- package/dist/{hook-g7kvv6x9.js → hook-8pz04697.js} +6 -6
- package/dist/{hook-3jjywnpb.js → hook-cyq4mryn.js} +8 -8
- package/dist/{http-tkp7d4kb.js → http-haj1nd6k.js} +921 -169
- package/dist/{index-q7m26kvs.js → index-jwt2bj6f.js} +265 -97
- package/dist/{index-3n8m9ea2.js → index-q52f47pe.js} +1 -1
- package/dist/{index-31ceyf42.js → index-syvhnvtw.js} +1 -1
- package/dist/index-y1ajx04a.js +43 -0
- package/dist/{index-1ef1myq2.js → index-z5h22pes.js} +20 -20
- package/dist/{index-xdaahthb.js → index-zyq8ct52.js} +20 -20
- package/dist/{keepalive-v4ta8wgg.js → keepalive-xdvsamvj.js} +4 -4
- package/dist/lead-hqg57kdp.js +79 -0
- package/dist/{maintenance-84n1ex21.js → maintenance-4y2xzh3a.js} +1 -1
- package/dist/{oauth-refresh-sweep-vjnnandw.js → oauth-refresh-sweep-jd4r7zbs.js} +9 -4
- package/dist/{onboard-sfmpe4np.js → onboard-18447q9q.js} +6 -6
- package/dist/{opencode-adapter-jmpst4q4.js → opencode-adapter-26b2frdf.js} +7 -7
- package/dist/{otel-impl-v6cc0zve.js → otel-impl-f9tgr8yw.js} +2 -2
- package/dist/{pi-mono-adapter-fk8fkcf6.js → pi-mono-adapter-qn6qbga1.js} +38 -17
- package/dist/{pricing-refresh-fsk1e1zc.js → pricing-refresh-dw2tgb53.js} +3 -3
- package/dist/{rbac-roles-prmkdnkw.js → rbac-roles-aygm2760.js} +2 -2
- package/dist/{rbac-roles-zxmxqa39.js → rbac-roles-s4nwj2m3.js} +1 -1
- package/dist/render-v2-yfvdfr0x.js +16 -0
- package/dist/{resume-frdtgfa0.js → resume-1bmvjbf3.js} +9 -9
- package/dist/{runtime-bt5pkpm1.js → runtime-f7tpzedy.js} +1 -1
- package/dist/{session-access-fpez4y5q.js → session-access-r5garej6.js} +4 -4
- package/dist/session-source-backfill-424wzv0r.js +110 -0
- package/dist/{setup-mq6m3m8q.js → setup-8tygvxkq.js} +1 -1
- package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-xsw323b3.js} +2 -2
- package/dist/{work-objects-kfahhjsq.js → work-objects-ggnghpgf.js} +23 -23
- package/dist/worker-sqjd1nqf.js +79 -0
- package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
- package/openapi.json +1324 -14
- package/package.json +5 -3
- package/src/assets/source-key.ts +95 -0
- package/src/be/automation-preflight.ts +2 -0
- package/src/be/db/api-keys.ts +89 -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 +6 -1
- package/src/be/db/workflow-runs.ts +219 -6
- package/src/be/db-retention.ts +61 -6
- package/src/be/db.ts +135 -132
- package/src/be/memory/providers/openai-embedding.ts +5 -1
- package/src/be/memory/task-learning.ts +155 -0
- package/src/be/migrations/244_api_key_validation.sql +4 -0
- package/src/be/migrations/245_script_executor.sql +3 -0
- package/src/be/oauth-refresh-sweep.ts +2 -1
- package/src/be/onboarding.ts +4 -0
- package/src/be/scripts/boot-reembed.ts +2 -0
- package/src/be/scripts/db.ts +14 -4
- package/src/be/scripts/embeddings.ts +2 -0
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -4
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/session-source-backfill.ts +163 -0
- package/src/be/swarm-config-guard.ts +1 -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/credential-refresh.ts +76 -1
- package/src/commands/credential-wait.ts +2 -2
- package/src/commands/onboard/compose-generator.ts +5 -3
- package/src/commands/provider-credentials.ts +212 -171
- package/src/commands/runner.ts +25 -4
- package/src/extensions/apply-task-create.ts +8 -1
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +5 -0
- package/src/heartbeat/heartbeat.ts +14 -1
- package/src/heartbeat/script-runner-probe.ts +47 -0
- package/src/http/all-routes.ts +2 -0
- package/src/http/api-keys.ts +77 -9
- 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 +1 -1
- package/src/http/core.ts +6 -0
- package/src/http/fleet-benchmark.ts +5 -2
- package/src/http/idempotency-key.ts +1 -1
- package/src/http/index.ts +14 -3
- package/src/http/integrations.ts +5 -1
- package/src/http/kv.ts +8 -3
- package/src/http/onboarding.ts +6 -1
- package/src/http/pages-public.ts +38 -5
- package/src/http/pool-keys.ts +354 -0
- package/src/http/scripts.ts +27 -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 +88 -9
- package/src/http/x.ts +5 -0
- package/src/integrations/chat-sdk/inbound.ts +27 -0
- package/src/kv-reserved-namespaces.ts +19 -0
- package/src/prompts/base-prompt.ts +21 -1
- package/src/prompts/session-templates.ts +17 -3
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +5 -1
- package/src/providers/claude-managed-adapter.ts +16 -9
- package/src/providers/codex-adapter.ts +71 -11
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +41 -11
- package/src/scheduler/scheduler.ts +5 -3
- package/src/scripts-runtime/eval-harness.ts +8 -1
- package/src/scripts-runtime/executors/native.ts +22 -16
- package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
- package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
- package/src/scripts-runtime/executors/quickjs.ts +7 -0
- package/src/scripts-runtime/executors/registry.ts +32 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/host-validated-args.ts +133 -0
- package/src/scripts-runtime/loader.ts +30 -3
- package/src/scripts-runtime/swarm-sdk.ts +16 -3
- package/src/scripts-runtime/types/stdlib.d.ts +10 -2
- package/src/scripts-runtime/types/swarm-extension.d.ts +17 -8
- package/src/scripts-runtime/types/swarm-sdk.d.ts +10 -2
- 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/api-key-status-by-suffix.test.ts +242 -0
- package/src/tests/api-keys-validation.test.ts +170 -0
- package/src/tests/base-prompt.test.ts +70 -2
- package/src/tests/claude-managed-adapter.test.ts +46 -0
- package/src/tests/claude-routes-e2e.test.ts +7 -2
- 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/community-linear-delivery-loop.test.ts +2099 -0
- package/src/tests/config-pool-keys.test.ts +533 -0
- package/src/tests/credential-check.test.ts +294 -4
- 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/docs-index.test.ts +135 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/extensions-pre-task.test.ts +53 -1
- package/src/tests/fetch-resolved-env.test.ts +16 -2
- 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/fleet-benchmark.test.ts +8 -0
- package/src/tests/get-workflow-run-tool.test.ts +98 -0
- package/src/tests/heartbeat-checklist.test.ts +89 -0
- package/src/tests/heartbeat-script-runner-probe.test.ts +83 -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/oauth-refresh-sweep.test.ts +34 -1
- package/src/tests/onboard-compose.test.ts +20 -0
- package/src/tests/onboarding.test.ts +108 -1
- package/src/tests/openai-embedding-credentials.test.ts +35 -0
- package/src/tests/opencode-adapter.test.ts +2 -1
- package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
- package/src/tests/org-website.test.ts +36 -0
- package/src/tests/pages-frame-ancestors.test.ts +162 -0
- package/src/tests/pi-codemode.test.ts +2 -1
- package/src/tests/pi-mono-adapter.test.ts +54 -0
- package/src/tests/pi-native-mcp.test.ts +13 -1
- package/src/tests/pi-tool-exposure.test.ts +8 -8
- package/src/tests/probe-tool-schema-tokens.test.ts +144 -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/request-composition-derive.test.ts +329 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/script-executor-conformance.test.ts +36 -1
- package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
- package/src/tests/script-executor-quickjs.test.ts +140 -0
- package/src/tests/script-host-validated-args-native.test.ts +196 -0
- package/src/tests/script-per-script-executor.test.ts +415 -0
- package/src/tests/script-runs-retention.test.ts +212 -0
- package/src/tests/scripts-external-api.test.ts +45 -0
- package/src/tests/sdk-allowlist.test.ts +42 -2
- package/src/tests/self-improvement.test.ts +4 -4
- 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 +299 -2
- package/src/tests/slack-inbound-files.test.ts +103 -23
- package/src/tests/status.test.ts +101 -18
- package/src/tests/store-progress-learning.test.ts +288 -0
- 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/ui-logs-parser.test.ts +180 -0
- package/src/tests/used-by-route.test.ts +11 -0
- package/src/tests/workflow-cascade-cancel.test.ts +172 -0
- package/src/tests/workflow-engine-v2.test.ts +116 -1
- package/src/tests/workflow-http-v2.test.ts +131 -0
- package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
- package/src/tests/workflow-run-finished-event.test.ts +263 -0
- package/src/tests/workflow-step-order.test.ts +92 -0
- package/src/tests/workflow-sub-workflow.test.ts +30 -0
- package/src/tests/workflow-swarm-script.test.ts +34 -0
- package/src/tools/get-tasks.ts +8 -0
- package/src/tools/kv/kv-read-auth.ts +4 -2
- package/src/tools/script-common.ts +1 -0
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-upsert.ts +7 -0
- package/src/tools/store-progress.ts +67 -1
- package/src/tools/task-action.ts +1 -1
- package/src/tools/workflows/get-workflow-run.ts +14 -4
- package/src/tools/workflows/trigger-workflow.ts +104 -10
- package/src/types.ts +36 -1
- package/src/utils/company-profile.ts +51 -1
- package/src/utils/credentials.ts +10 -4
- 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/engine.ts +18 -16
- package/src/workflows/executors/sub-workflow.ts +8 -1
- package/src/workflows/executors/swarm-script.ts +5 -0
- package/src/workflows/foreach-driver.ts +5 -11
- package/src/workflows/node-timeout.ts +30 -0
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
- package/templates/community/linear-delivery-loop/README.md +38 -0
- package/templates/community/linear-delivery-loop/config.example.json +31 -0
- package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
- package/templates/community/linear-delivery-loop/schedule.json +10 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
- package/templates/skills/researching/content.md +2 -0
- package/templates/skills/swarm-docs/config.json +14 -0
- package/templates/skills/swarm-docs/content.md +144 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -0
- package/templates/skills/swarm-scripts/content.md +1 -0
- package/dist/cli-7421jzsw.js +0 -84
- package/dist/cli-e45e4m6b.js +0 -413
- package/dist/cli-zw88zjba.js +0 -307
- package/dist/handlers-wy1dr6ja.js +0 -41
- package/dist/index-nmm79qgr.js +0 -43
- package/dist/lead-sawnyqxk.js +0 -79
- package/dist/render-v2-pjwxvnkw.js +0 -12
- package/dist/worker-v5e5rxj3.js +0 -79
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PAGES_FRAME_ANCESTORS` widens `frame-ancestors` on `/p/:id` for PUBLIC
|
|
3
|
+
* pages only, so an outside site (for example a blog) can iframe a chart.
|
|
4
|
+
* Authed and password pages keep the app-origin-only header, and an unset
|
|
5
|
+
* env leaves every header unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
8
|
+
import crypto from "node:crypto";
|
|
9
|
+
import { unlink } from "node:fs/promises";
|
|
10
|
+
import {
|
|
11
|
+
createServer as createHttpServer,
|
|
12
|
+
type IncomingMessage,
|
|
13
|
+
type Server,
|
|
14
|
+
type ServerResponse,
|
|
15
|
+
} from "node:http";
|
|
16
|
+
import { closeDb, initDb } from "../be/db";
|
|
17
|
+
import { handlePages } from "../http/pages";
|
|
18
|
+
import { getPageFrameAncestors, handlePagesPublic } from "../http/pages-public";
|
|
19
|
+
import { getPathSegments, parseQueryParams } from "../http/utils";
|
|
20
|
+
import { signPageSession } from "../utils/page-session";
|
|
21
|
+
import { listenOnFreePort } from "./test-net";
|
|
22
|
+
|
|
23
|
+
const TEST_DB_PATH = "./test-pages-frame-ancestors.sqlite";
|
|
24
|
+
const EMBEDDERS = "https://www.agent-swarm.dev https://agent-swarm.dev";
|
|
25
|
+
let BASE = "";
|
|
26
|
+
|
|
27
|
+
function createTestServer(): Server {
|
|
28
|
+
return createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
29
|
+
const pathSegments = getPathSegments(req.url || "");
|
|
30
|
+
const queryParams = parseQueryParams(req.url || "");
|
|
31
|
+
const myAgentId = req.headers["x-agent-id"] as string | undefined;
|
|
32
|
+
if (await handlePagesPublic(req, res, pathSegments, queryParams)) return;
|
|
33
|
+
if (await handlePages(req, res, pathSegments, queryParams, myAgentId)) return;
|
|
34
|
+
res.writeHead(404);
|
|
35
|
+
res.end("not found");
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function frameAncestors(res: Response): string {
|
|
40
|
+
const csp = res.headers.get("content-security-policy") ?? "";
|
|
41
|
+
return csp.split(";").find((d) => d.trim().startsWith("frame-ancestors ")) ?? "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("PAGES_FRAME_ANCESTORS on /p/:id", () => {
|
|
45
|
+
let server: Server;
|
|
46
|
+
const agentId = crypto.randomUUID();
|
|
47
|
+
const headers = { "Content-Type": "application/json", "X-Agent-ID": agentId };
|
|
48
|
+
const ORIG = {
|
|
49
|
+
PAGES_FRAME_ANCESTORS: process.env.PAGES_FRAME_ANCESTORS,
|
|
50
|
+
APP_URL: process.env.APP_URL,
|
|
51
|
+
DASHBOARD_URL: process.env.DASHBOARD_URL,
|
|
52
|
+
PAGE_SESSION_SECRET: process.env.PAGE_SESSION_SECRET,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function restore(key: keyof typeof ORIG): void {
|
|
56
|
+
if (ORIG[key] === undefined) delete process.env[key];
|
|
57
|
+
else process.env[key] = ORIG[key];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function createPage(
|
|
61
|
+
slug: string,
|
|
62
|
+
authMode: "public" | "authed" | "password",
|
|
63
|
+
): Promise<string> {
|
|
64
|
+
const post = await fetch(`${BASE}/api/pages`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers,
|
|
67
|
+
body: JSON.stringify({
|
|
68
|
+
slug,
|
|
69
|
+
title: slug,
|
|
70
|
+
contentType: "text/html",
|
|
71
|
+
authMode,
|
|
72
|
+
...(authMode === "password" ? { password: "swordfish" } : {}),
|
|
73
|
+
body: "<!doctype html><html><head></head><body><h1>chart</h1></body></html>",
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
expect(post.status).toBe(201);
|
|
77
|
+
const { id } = (await post.json()) as { id: string };
|
|
78
|
+
return id;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
beforeAll(async () => {
|
|
82
|
+
process.env.PAGE_SESSION_SECRET = "example-test-frame-ancestors-secret";
|
|
83
|
+
process.env.APP_URL = "https://app.example.test";
|
|
84
|
+
delete process.env.DASHBOARD_URL;
|
|
85
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
86
|
+
try {
|
|
87
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
initDb(TEST_DB_PATH);
|
|
91
|
+
server = createTestServer();
|
|
92
|
+
const port = await listenOnFreePort(server);
|
|
93
|
+
BASE = `http://localhost:${port}`;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
afterEach(() => restore("PAGES_FRAME_ANCESTORS"));
|
|
97
|
+
|
|
98
|
+
afterAll(async () => {
|
|
99
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
100
|
+
closeDb();
|
|
101
|
+
for (const key of Object.keys(ORIG) as (keyof typeof ORIG)[]) restore(key);
|
|
102
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
103
|
+
try {
|
|
104
|
+
await unlink(`${TEST_DB_PATH}${suffix}`);
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("public page gets the configured origins", async () => {
|
|
110
|
+
process.env.PAGES_FRAME_ANCESTORS = EMBEDDERS;
|
|
111
|
+
const id = await createPage("public-embed", "public");
|
|
112
|
+
const res = await fetch(`${BASE}/p/${id}`);
|
|
113
|
+
expect(res.status).toBe(200);
|
|
114
|
+
const fa = frameAncestors(res);
|
|
115
|
+
expect(fa).toContain("'self'");
|
|
116
|
+
expect(fa).toContain("https://app.example.test");
|
|
117
|
+
expect(fa).toContain("https://www.agent-swarm.dev");
|
|
118
|
+
expect(fa).toContain("https://agent-swarm.dev");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("authed page keeps the app-origin-only header", async () => {
|
|
122
|
+
const id = await createPage("authed-embed", "authed");
|
|
123
|
+
const exp = Math.floor(Date.now() / 1000) + 3600;
|
|
124
|
+
const cookie = `page_session_${id}=${await signPageSession({ pageId: id, exp })}`;
|
|
125
|
+
|
|
126
|
+
const before = frameAncestors(await fetch(`${BASE}/p/${id}`, { headers: { cookie } }));
|
|
127
|
+
process.env.PAGES_FRAME_ANCESTORS = EMBEDDERS;
|
|
128
|
+
const res = await fetch(`${BASE}/p/${id}`, { headers: { cookie } });
|
|
129
|
+
expect(res.status).toBe(200);
|
|
130
|
+
expect(frameAncestors(res)).toBe(before);
|
|
131
|
+
expect(frameAncestors(res)).not.toContain("agent-swarm.dev");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("password page keeps the app-origin-only header", async () => {
|
|
135
|
+
process.env.PAGES_FRAME_ANCESTORS = EMBEDDERS;
|
|
136
|
+
const id = await createPage("password-embed", "password");
|
|
137
|
+
const res = await fetch(`${BASE}/p/${id}?key=swordfish`);
|
|
138
|
+
expect(res.status).toBe(200);
|
|
139
|
+
expect(frameAncestors(res)).not.toContain("agent-swarm.dev");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("unset env leaves the public header unchanged", async () => {
|
|
143
|
+
const id = await createPage("public-unset", "public");
|
|
144
|
+
delete process.env.PAGES_FRAME_ANCESTORS;
|
|
145
|
+
const unset = frameAncestors(await fetch(`${BASE}/p/${id}`));
|
|
146
|
+
process.env.PAGES_FRAME_ANCESTORS = "";
|
|
147
|
+
const blank = frameAncestors(await fetch(`${BASE}/p/${id}`));
|
|
148
|
+
expect(unset).toContain("https://app.example.test");
|
|
149
|
+
expect(unset).not.toContain("agent-swarm.dev");
|
|
150
|
+
expect(blank).toBe(unset);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("parser accepts comma or space lists and drops invalid entries", () => {
|
|
154
|
+
process.env.PAGES_FRAME_ANCESTORS =
|
|
155
|
+
"https://www.agent-swarm.dev/, https://*.example.com:8443 blog.example.org,* 'none' https://evil.test;script-src javascript:alert(1) https://x.test/path";
|
|
156
|
+
expect(getPageFrameAncestors()).toEqual([
|
|
157
|
+
"https://www.agent-swarm.dev",
|
|
158
|
+
"https://*.example.com:8443",
|
|
159
|
+
"blog.example.org",
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -104,7 +104,8 @@ describe("PiMonoAdapter.createSession — codemode", () => {
|
|
|
104
104
|
process.env.OPENROUTER_BASE_URL = "http://127.0.0.1:9/api/v1";
|
|
105
105
|
delete process.env.PI_CODEMODE;
|
|
106
106
|
delete process.env.PI_CODEMODE_MODELS;
|
|
107
|
-
|
|
107
|
+
// Deferral off, so every swarm tool shows up as an active tool name.
|
|
108
|
+
process.env.PI_TOOL_DEFERRAL = "false";
|
|
108
109
|
});
|
|
109
110
|
afterEach(() => {
|
|
110
111
|
for (const key of envKeys) {
|
|
@@ -1859,3 +1859,57 @@ describe("PiMonoSession context-bound note", () => {
|
|
|
1859
1859
|
expect(await lateSteers([assistantTurn(usage(5000))])).toEqual([HELD_NOTE_TEXT]);
|
|
1860
1860
|
});
|
|
1861
1861
|
});
|
|
1862
|
+
|
|
1863
|
+
describe("PiMonoSession — per-call usage line", () => {
|
|
1864
|
+
function assistantEnd(content: unknown[], usage?: unknown): AgentSessionEvent {
|
|
1865
|
+
return {
|
|
1866
|
+
type: "message_end",
|
|
1867
|
+
message: { role: "assistant", content, stopReason: "stop", ...(usage ? { usage } : {}) },
|
|
1868
|
+
} as unknown as AgentSessionEvent;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
test("one call_usage raw_log line per assistant message_end with usage", async () => {
|
|
1872
|
+
const { events } = await runWithEvents([
|
|
1873
|
+
assistantEnd([{ type: "toolCall", id: "call-1", name: "bash", arguments: {} }], {
|
|
1874
|
+
input: 120,
|
|
1875
|
+
cacheRead: 9_000,
|
|
1876
|
+
cacheWrite: 400,
|
|
1877
|
+
output: 60,
|
|
1878
|
+
reasoning: 12,
|
|
1879
|
+
totalTokens: 9_580,
|
|
1880
|
+
}),
|
|
1881
|
+
{
|
|
1882
|
+
type: "message_end",
|
|
1883
|
+
message: { role: "user", content: [{ type: "text", text: "tool output" }] },
|
|
1884
|
+
} as unknown as AgentSessionEvent,
|
|
1885
|
+
// No usage (some providers omit it): no line.
|
|
1886
|
+
assistantEnd([{ type: "text", text: "thinking out loud" }]),
|
|
1887
|
+
assistantEnd([{ type: "text", text: "Done" }], { input: 5, cacheRead: 9_500, output: 3 }),
|
|
1888
|
+
]);
|
|
1889
|
+
|
|
1890
|
+
const raw = events.flatMap((e) =>
|
|
1891
|
+
e.type === "raw_log" ? [JSON.parse(e.content) as Record<string, unknown>] : [],
|
|
1892
|
+
);
|
|
1893
|
+
const usageLines = raw.filter((line) => line.type === "call_usage");
|
|
1894
|
+
expect(usageLines).toEqual([
|
|
1895
|
+
{
|
|
1896
|
+
type: "call_usage",
|
|
1897
|
+
harness: "pi",
|
|
1898
|
+
usage: { input: 120, cacheRead: 9_000, cacheWrite: 400, output: 60, reasoning: 12 },
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
type: "call_usage",
|
|
1902
|
+
harness: "pi",
|
|
1903
|
+
usage: { input: 5, cacheRead: 9_500, cacheWrite: 0, output: 3, reasoning: 0 },
|
|
1904
|
+
},
|
|
1905
|
+
]);
|
|
1906
|
+
// The usage line precedes the call's own text line.
|
|
1907
|
+
const done = raw.findIndex(
|
|
1908
|
+
(line) => line.type === "assistant" && JSON.stringify(line).includes('"Done"'),
|
|
1909
|
+
);
|
|
1910
|
+
expect(done).toBeGreaterThan(0);
|
|
1911
|
+
expect(raw[done - 1]).toEqual(usageLines[1]);
|
|
1912
|
+
// Token counts only: no message content rides on the line.
|
|
1913
|
+
expect(JSON.stringify(usageLines)).not.toContain("tool output");
|
|
1914
|
+
});
|
|
1915
|
+
});
|
|
@@ -30,6 +30,17 @@ describe("toPiMcpServers", () => {
|
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
test("deferred maps every installed server to deferred exposure", () => {
|
|
34
|
+
const servers = toPiMcpServers(
|
|
35
|
+
{
|
|
36
|
+
local: { command: "context-mode" },
|
|
37
|
+
remote: { url: "https://mcp.example.com/mcp" },
|
|
38
|
+
},
|
|
39
|
+
{ deferred: true },
|
|
40
|
+
);
|
|
41
|
+
expect(Object.values(servers).map((s) => s.exposure)).toEqual(["deferred", "deferred"]);
|
|
42
|
+
});
|
|
43
|
+
|
|
33
44
|
test("no installed servers maps to an empty set", () => {
|
|
34
45
|
expect(toPiMcpServers(null)).toEqual({});
|
|
35
46
|
});
|
|
@@ -70,7 +81,8 @@ describe("PiMonoAdapter.createSession — native MCP for installed servers", ()
|
|
|
70
81
|
process.env.OPENROUTER_API_KEY = "example-test-key";
|
|
71
82
|
// The session prompts on creation; keep that request off the network.
|
|
72
83
|
process.env.OPENROUTER_BASE_URL = "http://127.0.0.1:9/api/v1";
|
|
73
|
-
|
|
84
|
+
// Direct exposure, so installed tools show up as active tool names.
|
|
85
|
+
process.env.PI_TOOL_DEFERRAL = "false";
|
|
74
86
|
mkdirSync(join(dir, "agent"), { recursive: true });
|
|
75
87
|
});
|
|
76
88
|
afterEach(() => {
|
|
@@ -176,8 +176,8 @@ describe("pi resource loader trust", () => {
|
|
|
176
176
|
});
|
|
177
177
|
|
|
178
178
|
describe("PI_TOOL_DEFERRAL flag", () => {
|
|
179
|
-
test("defaults
|
|
180
|
-
expect(isPiToolDeferralEnabled({})).toBe(
|
|
179
|
+
test("defaults on and parses boolean literals", () => {
|
|
180
|
+
expect(isPiToolDeferralEnabled({})).toBe(true);
|
|
181
181
|
expect(isPiToolDeferralEnabled({ PI_TOOL_DEFERRAL: "true" })).toBe(true);
|
|
182
182
|
expect(isPiToolDeferralEnabled({ PI_TOOL_DEFERRAL: "1" })).toBe(true);
|
|
183
183
|
expect(isPiToolDeferralEnabled({ PI_TOOL_DEFERRAL: "false" })).toBe(false);
|
|
@@ -187,9 +187,9 @@ describe("PI_TOOL_DEFERRAL flag", () => {
|
|
|
187
187
|
"traits.hasToolSearch follows the flag live",
|
|
188
188
|
withEnv("PI_TOOL_DEFERRAL", undefined, () => {
|
|
189
189
|
const adapter = new PiMonoAdapter();
|
|
190
|
-
expect(adapter.traits.hasToolSearch).toBe(false);
|
|
191
|
-
process.env.PI_TOOL_DEFERRAL = "true";
|
|
192
190
|
expect(adapter.traits.hasToolSearch).toBe(true);
|
|
191
|
+
process.env.PI_TOOL_DEFERRAL = "false";
|
|
192
|
+
expect(adapter.traits.hasToolSearch).toBe(false);
|
|
193
193
|
}),
|
|
194
194
|
);
|
|
195
195
|
});
|
|
@@ -259,8 +259,8 @@ describe("PiMonoAdapter.createSession — tool deferral wiring", () => {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
test(
|
|
262
|
-
"flag
|
|
263
|
-
withEnv("PI_TOOL_DEFERRAL",
|
|
262
|
+
"flag unset: non-core tools deferred and tool_search enabled by default",
|
|
263
|
+
withEnv("PI_TOOL_DEFERRAL", undefined, async () => {
|
|
264
264
|
const opts = await captureSessionOptions();
|
|
265
265
|
const tools = opts.customTools as ToolDefinition[];
|
|
266
266
|
expect(Object.fromEntries(tools.map((t) => [t.name, t.exposure]))).toEqual({
|
|
@@ -274,8 +274,8 @@ describe("PiMonoAdapter.createSession — tool deferral wiring", () => {
|
|
|
274
274
|
);
|
|
275
275
|
|
|
276
276
|
test(
|
|
277
|
-
"flag
|
|
278
|
-
withEnv("PI_TOOL_DEFERRAL",
|
|
277
|
+
"flag false: every tool declared and no tool_search",
|
|
278
|
+
withEnv("PI_TOOL_DEFERRAL", "false", async () => {
|
|
279
279
|
const opts = await captureSessionOptions();
|
|
280
280
|
const tools = opts.customTools as ToolDefinition[];
|
|
281
281
|
expect(tools.map((t) => t.exposure)).toEqual([undefined, undefined, undefined]);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
bareToolName,
|
|
7
|
+
controlReached,
|
|
8
|
+
countSwarmTools,
|
|
9
|
+
hasControlResult,
|
|
10
|
+
inputTokens,
|
|
11
|
+
toolNames,
|
|
12
|
+
turnOne,
|
|
13
|
+
withSessionDirs,
|
|
14
|
+
} from "../../scripts/probe-tool-schema-tokens";
|
|
15
|
+
|
|
16
|
+
describe("probe-tool-schema-tokens", () => {
|
|
17
|
+
let dir = "";
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
if (dir) rmSync(dir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("matches swarm tools under every harness naming", () => {
|
|
23
|
+
const swarm = new Set(["get_swarm", "store_progress"]);
|
|
24
|
+
expect(bareToolName("mcp__agent_swarm__get_swarm")).toBe("get_swarm");
|
|
25
|
+
expect(bareToolName("agent-swarm_store-progress")).toBe("store_progress");
|
|
26
|
+
expect(
|
|
27
|
+
countSwarmTools(["get-swarm", "mcp__agent_swarm__store_progress", "bash", "read"], swarm),
|
|
28
|
+
).toBe(2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("lists tools in chat-completions and namespaced responses bodies", () => {
|
|
32
|
+
expect(toolNames({ tools: [{ type: "function", function: { name: "get-swarm" } }] })).toEqual([
|
|
33
|
+
"get-swarm",
|
|
34
|
+
]);
|
|
35
|
+
const responses = {
|
|
36
|
+
input: [
|
|
37
|
+
{
|
|
38
|
+
type: "additional_tools",
|
|
39
|
+
tools: [{ type: "namespace", name: "functions", tools: [{ name: "exec" }] }],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
tools: [{ name: "wait" }],
|
|
43
|
+
};
|
|
44
|
+
expect(toolNames(responses).sort()).toEqual(["exec", "wait"]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("reads the last reported input token count from a stream", () => {
|
|
48
|
+
const sse = 'data: {"usage":null}\n\ndata: {"usage":{"prompt_tokens":90776}}\n';
|
|
49
|
+
expect(inputTokens(sse)).toBe(90776);
|
|
50
|
+
expect(inputTokens('{"response":{"usage":{"input_tokens":16629}}}')).toBe(16629);
|
|
51
|
+
expect(inputTokens("data: [DONE]")).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("turn one skips side requests for other models", () => {
|
|
55
|
+
dir = mkdtempSync(join(tmpdir(), "probe-tool-schema-"));
|
|
56
|
+
writeFileSync(join(dir, "req-1.json"), JSON.stringify({ model: "openai/gpt-6-luna" }));
|
|
57
|
+
writeFileSync(join(dir, "res-1.txt"), '{"usage":{"prompt_tokens":453}}');
|
|
58
|
+
writeFileSync(
|
|
59
|
+
join(dir, "req-2.json"),
|
|
60
|
+
JSON.stringify({ model: "deepseek/deepseek-v4.1-flash", tools: [{ name: "bash" }] }),
|
|
61
|
+
);
|
|
62
|
+
writeFileSync(join(dir, "res-2.txt"), '{"usage":{"prompt_tokens":33115}}');
|
|
63
|
+
expect(turnOne(dir, "openrouter/deepseek/deepseek-v4.1-flash")).toEqual({
|
|
64
|
+
tokens: 33115,
|
|
65
|
+
names: ["bash"],
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const found = "Found 5 agent(s) in the swarm. Requested by session: s1";
|
|
70
|
+
|
|
71
|
+
test("control passes only on a get-swarm tool output", () => {
|
|
72
|
+
dir = mkdtempSync(join(tmpdir(), "probe-tool-schema-"));
|
|
73
|
+
// The schema alone carries "in the swarm" in get-swarm's description.
|
|
74
|
+
const schemaOnly = {
|
|
75
|
+
model: "m",
|
|
76
|
+
tools: [
|
|
77
|
+
{
|
|
78
|
+
type: "function",
|
|
79
|
+
function: { name: "agent-swarm_get-swarm", description: "Lists agents in the swarm." },
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
messages: [{ role: "user", content: "Call get-swarm. Found 5 agent(s) in the swarm." }],
|
|
83
|
+
};
|
|
84
|
+
expect(hasControlResult(schemaOnly)).toBe(false);
|
|
85
|
+
writeFileSync(join(dir, "req-1.json"), JSON.stringify(schemaOnly));
|
|
86
|
+
expect(controlReached(dir)).toBe(false);
|
|
87
|
+
|
|
88
|
+
const chat = {
|
|
89
|
+
...schemaOnly,
|
|
90
|
+
messages: [
|
|
91
|
+
{
|
|
92
|
+
role: "assistant",
|
|
93
|
+
tool_calls: [{ id: "c1", type: "function", function: { name: "agent-swarm_get-swarm" } }],
|
|
94
|
+
},
|
|
95
|
+
{ role: "tool", tool_call_id: "c1", content: [{ type: "text", text: found }] },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
expect(hasControlResult(chat)).toBe(true);
|
|
99
|
+
writeFileSync(join(dir, "req-2.json"), JSON.stringify(chat));
|
|
100
|
+
expect(controlReached(dir)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("control reads Responses items and rejects other tools and failed calls", () => {
|
|
104
|
+
const responses = (name: string, output: string) => ({
|
|
105
|
+
input: [
|
|
106
|
+
{ type: "function_call", name, call_id: "call_1", arguments: "{}" },
|
|
107
|
+
{ type: "function_call_output", call_id: "call_1", output },
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
expect(hasControlResult(responses("mcp__agent_swarm__get_swarm", found))).toBe(true);
|
|
111
|
+
expect(hasControlResult(responses("mcp__agent_swarm__get_tasks", found))).toBe(false);
|
|
112
|
+
expect(hasControlResult(responses("mcp__agent_swarm__get_swarm", "Agent ID required."))).toBe(
|
|
113
|
+
false,
|
|
114
|
+
);
|
|
115
|
+
// An output for a different call id does not count.
|
|
116
|
+
const mismatched = responses("mcp__agent_swarm__get_swarm", found);
|
|
117
|
+
mismatched.input[1] = { type: "function_call_output", call_id: "call_2", output: found };
|
|
118
|
+
expect(hasControlResult(mismatched)).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("session dirs: home is removed on success and on error, captures stay", async () => {
|
|
122
|
+
dir = mkdtempSync(join(tmpdir(), "probe-tool-schema-"));
|
|
123
|
+
let home = "";
|
|
124
|
+
const capDir = await withSessionDirs(dir, "ok", async (cap, h) => {
|
|
125
|
+
home = h;
|
|
126
|
+
writeFileSync(join(h, "config.toml"), "x");
|
|
127
|
+
writeFileSync(join(cap, "req-1.json"), "{}");
|
|
128
|
+
return cap;
|
|
129
|
+
});
|
|
130
|
+
expect(existsSync(home)).toBe(false);
|
|
131
|
+
expect(existsSync(join(capDir, "req-1.json"))).toBe(true);
|
|
132
|
+
|
|
133
|
+
await expect(
|
|
134
|
+
withSessionDirs(dir, "boom", async (cap, h) => {
|
|
135
|
+
home = h;
|
|
136
|
+
writeFileSync(join(h, "config.toml"), "x");
|
|
137
|
+
writeFileSync(join(cap, "req-1.json"), "{}");
|
|
138
|
+
throw new Error("codex exec exited 1");
|
|
139
|
+
}),
|
|
140
|
+
).rejects.toThrow("codex exec exited 1");
|
|
141
|
+
expect(existsSync(home)).toBe(false);
|
|
142
|
+
expect(existsSync(join(dir, "boom-cap", "req-1.json"))).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -25,6 +25,7 @@ const SYSTEM_TEMPLATES = [
|
|
|
25
25
|
"system.agent.context.bound_note",
|
|
26
26
|
"system.agent.cursor.first_message",
|
|
27
27
|
"system.agent.lead",
|
|
28
|
+
"system.agent.lead.docs",
|
|
28
29
|
"system.agent.memory",
|
|
29
30
|
"system.agent.memory.remote",
|
|
30
31
|
"system.agent.outputs",
|
|
@@ -118,11 +119,11 @@ describe("Session templates: registration", () => {
|
|
|
118
119
|
expect(sessionSystemEventTypes("session")).toEqual([...SESSION_TEMPLATES].sort());
|
|
119
120
|
});
|
|
120
121
|
|
|
121
|
-
test("registers
|
|
122
|
+
test("registers 33 system and session templates in total", () => {
|
|
122
123
|
const all = getAllTemplateDefinitions();
|
|
123
124
|
const sessionSystem = all.filter((d) => d.category === "system" || d.category === "session");
|
|
124
|
-
//
|
|
125
|
-
expect(sessionSystem.length).toBe(
|
|
125
|
+
// 28 system blocks + 5 session composites.
|
|
126
|
+
expect(sessionSystem.length).toBe(33);
|
|
126
127
|
});
|
|
127
128
|
|
|
128
129
|
test("drops the v1 blocks that prompt v2 deleted", () => {
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { checkProviderPoolKey } from "../utils/provider-key-probes";
|
|
3
|
+
|
|
4
|
+
describe("API pool key probes", () => {
|
|
5
|
+
const realFetch = globalThis.fetch;
|
|
6
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
7
|
+
const originalPrivateUrls = process.env.ALLOW_PRIVATE_NETWORK_URLS;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
globalThis.fetch = realFetch;
|
|
11
|
+
if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
|
|
12
|
+
else process.env.NODE_ENV = originalNodeEnv;
|
|
13
|
+
if (originalPrivateUrls === undefined) delete process.env.ALLOW_PRIVATE_NETWORK_URLS;
|
|
14
|
+
else process.env.ALLOW_PRIVATE_NETWORK_URLS = originalPrivateUrls;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("uses the supplied OpenRouter URL and forbids redirects", async () => {
|
|
18
|
+
const calls: Array<{ url: string; init?: RequestInit }> = [];
|
|
19
|
+
globalThis.fetch = mock(async (url: string | URL | Request, init?: RequestInit) => {
|
|
20
|
+
calls.push({ url: String(url), init });
|
|
21
|
+
return new Response("{}", { status: calls.length === 1 ? 404 : 200 });
|
|
22
|
+
}) as typeof fetch;
|
|
23
|
+
expect(
|
|
24
|
+
await checkProviderPoolKey("OPENROUTER_API_KEY", "fake-abcde", {
|
|
25
|
+
OPENROUTER_BASE_URL: "https://gateway.example/v1/",
|
|
26
|
+
}),
|
|
27
|
+
).toMatchObject({ ok: true });
|
|
28
|
+
expect(calls.map((call) => call.url)).toEqual([
|
|
29
|
+
"https://gateway.example/v1/key",
|
|
30
|
+
"https://gateway.example/v1/models",
|
|
31
|
+
]);
|
|
32
|
+
expect(calls.every((call) => call.init?.redirect === "manual")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("rejects a private registry URL before sending credentials", async () => {
|
|
36
|
+
process.env.NODE_ENV = "production";
|
|
37
|
+
delete process.env.ALLOW_PRIVATE_NETWORK_URLS;
|
|
38
|
+
const fetch = mock(async () => new Response("{}"));
|
|
39
|
+
globalThis.fetch = fetch as unknown as typeof globalThis.fetch;
|
|
40
|
+
const result = await checkProviderPoolKey("NAN_API_KEY", "fake-abcde", {
|
|
41
|
+
NAN_BASE_URL: "https://127.0.0.1/v1",
|
|
42
|
+
});
|
|
43
|
+
expect(result.ok).toBe(false);
|
|
44
|
+
expect(result.httpStatus).toBeUndefined();
|
|
45
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("supports registry aliases and configured URLs", async () => {
|
|
49
|
+
const fetch = mock(async (url: string | URL | Request, init?: RequestInit) => {
|
|
50
|
+
expect(String(url)).toBe("https://gateway.example/v1/models");
|
|
51
|
+
expect(init?.headers).toEqual({ Authorization: "Bearer fake-abcde" });
|
|
52
|
+
expect(init?.redirect).toBe("manual");
|
|
53
|
+
return new Response("Unauthorized", { status: 401 });
|
|
54
|
+
});
|
|
55
|
+
globalThis.fetch = fetch as typeof globalThis.fetch;
|
|
56
|
+
expect(
|
|
57
|
+
await checkProviderPoolKey("HELMCODE_API_NAN", "fake-abcde", {
|
|
58
|
+
NAN_BASE_URL: "https://gateway.example/v1",
|
|
59
|
+
}),
|
|
60
|
+
).toMatchObject({ ok: false, httpStatus: 401 });
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test.each([
|
|
64
|
+
[200, "{}", true, false],
|
|
65
|
+
[403, '{"error":{"type":"permission_error"}}', true, false],
|
|
66
|
+
[403, '{"error":{"type":"other"}}', false, false],
|
|
67
|
+
[401, "Unauthorized", false, false],
|
|
68
|
+
[429, "Throttled", true, true],
|
|
69
|
+
[503, "Unavailable", true, true],
|
|
70
|
+
])("preserves Claude OAuth semantics for HTTP %i", async (status, body, ok, skipped) => {
|
|
71
|
+
globalThis.fetch = mock(async (url: string | URL | Request, init?: RequestInit) => {
|
|
72
|
+
expect(String(url)).toBe("https://api.anthropic.com/api/oauth/profile");
|
|
73
|
+
expect(init?.redirect).toBe("manual");
|
|
74
|
+
expect(init?.signal).toBeInstanceOf(AbortSignal);
|
|
75
|
+
return new Response(body, { status });
|
|
76
|
+
}) as typeof fetch;
|
|
77
|
+
const result = await checkProviderPoolKey("CLAUDE_CODE_OAUTH_TOKEN", "fake-abcde", {});
|
|
78
|
+
expect(result.ok).toBe(ok);
|
|
79
|
+
expect(result.skipped === true).toBe(skipped);
|
|
80
|
+
if (!ok) expect(result.httpStatus).toBe(status);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("checks an Anthropic key at its gateway, keeping the URL path prefix", async () => {
|
|
84
|
+
const calls: Array<{ url: string; init?: RequestInit }> = [];
|
|
85
|
+
globalThis.fetch = mock(async (url: string | URL | Request, init?: RequestInit) => {
|
|
86
|
+
calls.push({ url: String(url), init });
|
|
87
|
+
return new Response("Unauthorized", { status: 401 });
|
|
88
|
+
}) as unknown as typeof fetch;
|
|
89
|
+
const result = await checkProviderPoolKey("ANTHROPIC_API_KEY", "fake-abcde", {
|
|
90
|
+
ANTHROPIC_BASE_URL: "https://gateway.example/api",
|
|
91
|
+
});
|
|
92
|
+
expect(result).toMatchObject({ ok: false, httpStatus: 401 });
|
|
93
|
+
expect(calls.map((call) => call.url)).toEqual(["https://gateway.example/api/v1/models"]);
|
|
94
|
+
expect(new Headers(calls[0]?.init?.headers).get("x-api-key")).toBe("fake-abcde");
|
|
95
|
+
expect(calls[0]?.init?.redirect).toBe("manual");
|
|
96
|
+
|
|
97
|
+
globalThis.fetch = mock(async () => new Response("{}")) as unknown as typeof fetch;
|
|
98
|
+
expect(
|
|
99
|
+
await checkProviderPoolKey("ANTHROPIC_API_KEY", "fake-abcde", {
|
|
100
|
+
ANTHROPIC_BASE_URL: "https://gateway.example/api",
|
|
101
|
+
}),
|
|
102
|
+
).toMatchObject({ ok: true });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("a gateway that lists no models leaves the Anthropic key unchecked", async () => {
|
|
106
|
+
globalThis.fetch = mock(
|
|
107
|
+
async () => new Response("Not found", { status: 404 }),
|
|
108
|
+
) as unknown as typeof fetch;
|
|
109
|
+
expect(
|
|
110
|
+
await checkProviderPoolKey("ANTHROPIC_API_KEY", "fake-abcde", {
|
|
111
|
+
ANTHROPIC_BASE_URL: "https://gateway.example",
|
|
112
|
+
}),
|
|
113
|
+
).toMatchObject({ ok: true, skipped: true });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("an Anthropic key stays on api.anthropic.com when the gateway uses ANTHROPIC_AUTH_TOKEN", async () => {
|
|
117
|
+
const urls: string[] = [];
|
|
118
|
+
globalThis.fetch = mock(async (url: string | URL | Request) => {
|
|
119
|
+
urls.push(String(url));
|
|
120
|
+
return new Response("{}");
|
|
121
|
+
}) as unknown as typeof fetch;
|
|
122
|
+
expect(
|
|
123
|
+
await checkProviderPoolKey("ANTHROPIC_API_KEY", "fake-abcde", {
|
|
124
|
+
ANTHROPIC_BASE_URL: "https://gateway.example",
|
|
125
|
+
ANTHROPIC_AUTH_TOKEN: "gateway-token",
|
|
126
|
+
}),
|
|
127
|
+
).toMatchObject({ ok: true });
|
|
128
|
+
expect(urls).toEqual(["https://api.anthropic.com/v1/models"]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a private Anthropic gateway gets no credentials and no rejection", async () => {
|
|
132
|
+
process.env.NODE_ENV = "production";
|
|
133
|
+
delete process.env.ALLOW_PRIVATE_NETWORK_URLS;
|
|
134
|
+
const fetch = mock(async () => new Response("{}"));
|
|
135
|
+
globalThis.fetch = fetch as unknown as typeof globalThis.fetch;
|
|
136
|
+
const result = await checkProviderPoolKey("ANTHROPIC_API_KEY", "fake-abcde", {
|
|
137
|
+
ANTHROPIC_BASE_URL: "http://127.0.0.1:4000",
|
|
138
|
+
});
|
|
139
|
+
expect(result.ok).toBe(false);
|
|
140
|
+
expect(result.httpStatus).toBeUndefined();
|
|
141
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("skips keys without an API-safe probe", async () => {
|
|
145
|
+
const fetch = mock(async () => new Response("{}"));
|
|
146
|
+
globalThis.fetch = fetch as unknown as typeof globalThis.fetch;
|
|
147
|
+
for (const keyType of ["DEVIN_API_KEY", "AMP_API_KEY", "CURSOR_API_KEY"]) {
|
|
148
|
+
expect(await checkProviderPoolKey(keyType, "fake-abcde", {})).toMatchObject({
|
|
149
|
+
ok: true,
|
|
150
|
+
skipped: true,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("reports network errors without exposing the submitted key", async () => {
|
|
157
|
+
globalThis.fetch = mock(async () => {
|
|
158
|
+
throw new Error("Rejected fake-abcde");
|
|
159
|
+
}) as unknown as typeof fetch;
|
|
160
|
+
const result = await checkProviderPoolKey("OPENAI_API_KEY", "fake-abcde", {});
|
|
161
|
+
expect(result.ok).toBe(false);
|
|
162
|
+
expect(result.error).not.toContain("fake-abcde");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test.each([
|
|
166
|
+
"ANTHROPIC_API_KEY",
|
|
167
|
+
"OPENAI_API_KEY",
|
|
168
|
+
"OPENROUTER_API_KEY",
|
|
169
|
+
"NAN_API_KEY",
|
|
170
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
171
|
+
])("redacts a long echoed %s before truncating the provider error", async (keyType) => {
|
|
172
|
+
const apiKey = "Z".repeat(240);
|
|
173
|
+
globalThis.fetch = mock(
|
|
174
|
+
async () => new Response(`Rejected ${apiKey}`, { status: 403 }),
|
|
175
|
+
) as unknown as typeof fetch;
|
|
176
|
+
const result = await checkProviderPoolKey(keyType, apiKey, {});
|
|
177
|
+
expect(result).toMatchObject({ ok: false, httpStatus: 403 });
|
|
178
|
+
expect(result.error).toContain("[REDACTED]");
|
|
179
|
+
expect(result.error).not.toContain(apiKey.slice(0, 40));
|
|
180
|
+
});
|
|
181
|
+
});
|