@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
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
2
|
import { closeSync, openSync } from "node:fs";
|
|
3
3
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import { createServer } from "node:net";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { join } from "node:path";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
makeScratchDir,
|
|
9
|
+
removeScratchDir,
|
|
10
|
+
type SwarmServer,
|
|
11
|
+
spawnSwarmServer,
|
|
12
|
+
waitForListen,
|
|
13
|
+
} from "./rbac-e2e-helpers";
|
|
14
|
+
import {
|
|
15
|
+
getFreePort,
|
|
16
|
+
listenOnFreePort,
|
|
17
|
+
PortInUseError,
|
|
18
|
+
SERVER_BOOT_HOOK_TIMEOUT_MS,
|
|
19
|
+
startOnFreePort,
|
|
20
|
+
} from "./test-net";
|
|
7
21
|
|
|
8
22
|
test("waitForListen includes a bounded server log when the process exits early", async () => {
|
|
9
23
|
const dir = await mkdtemp(join(tmpdir(), "rbac-e2e-helper-"));
|
|
@@ -51,3 +65,76 @@ test("waitForListen includes a bounded server log when the process exits early",
|
|
|
51
65
|
await rm(dir, { recursive: true, force: true });
|
|
52
66
|
}
|
|
53
67
|
});
|
|
68
|
+
|
|
69
|
+
test("startOnFreePort retries only PortInUseError, up to its attempt bound", async () => {
|
|
70
|
+
const ports = [41001, 41002, 41003, 41004];
|
|
71
|
+
const getPort = async () => ports.shift() ?? 0;
|
|
72
|
+
const tried: number[] = [];
|
|
73
|
+
|
|
74
|
+
const port = await startOnFreePort(
|
|
75
|
+
async (p) => {
|
|
76
|
+
tried.push(p);
|
|
77
|
+
if (p === 41001) throw new PortInUseError("taken");
|
|
78
|
+
return p;
|
|
79
|
+
},
|
|
80
|
+
{ getPort },
|
|
81
|
+
);
|
|
82
|
+
expect(port).toBe(41002);
|
|
83
|
+
expect(tried).toEqual([41001, 41002]);
|
|
84
|
+
|
|
85
|
+
const other = await startOnFreePort(
|
|
86
|
+
async () => {
|
|
87
|
+
throw new Error("boot crashed");
|
|
88
|
+
},
|
|
89
|
+
{ getPort },
|
|
90
|
+
).catch((error: unknown) => error);
|
|
91
|
+
expect((other as Error).message).toBe("boot crashed");
|
|
92
|
+
expect(ports).toEqual([41004]);
|
|
93
|
+
|
|
94
|
+
let calls = 0;
|
|
95
|
+
const exhausted = await startOnFreePort(
|
|
96
|
+
async () => {
|
|
97
|
+
calls++;
|
|
98
|
+
throw new PortInUseError(`taken ${calls}`);
|
|
99
|
+
},
|
|
100
|
+
{ getPort: async () => 0 },
|
|
101
|
+
).catch((error: unknown) => error);
|
|
102
|
+
expect(exhausted).toBeInstanceOf(PortInUseError);
|
|
103
|
+
expect((exhausted as Error).message).toBe("taken 3");
|
|
104
|
+
expect(calls).toBe(3);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test(
|
|
108
|
+
"spawnSwarmServer starts again on a new port when its first port is taken",
|
|
109
|
+
async () => {
|
|
110
|
+
const dir = await makeScratchDir();
|
|
111
|
+
const holder = createServer();
|
|
112
|
+
const heldPort = await listenOnFreePort(holder);
|
|
113
|
+
let server: SwarmServer | undefined;
|
|
114
|
+
try {
|
|
115
|
+
const offered: number[] = [];
|
|
116
|
+
server = await spawnSwarmServer({
|
|
117
|
+
dbPath: join(dir, "test.sqlite"),
|
|
118
|
+
logPath: join(dir, "server.log"),
|
|
119
|
+
getPort: async () => {
|
|
120
|
+
const port = offered.length === 0 ? heldPort : await getFreePort();
|
|
121
|
+
offered.push(port);
|
|
122
|
+
return port;
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(offered).toHaveLength(2);
|
|
127
|
+
expect(offered[0]).toBe(heldPort);
|
|
128
|
+
expect(server.port).toBe(offered[1]);
|
|
129
|
+
expect(server.port).not.toBe(heldPort);
|
|
130
|
+
expect((await fetch(`${server.base}/docs`)).ok).toBe(true);
|
|
131
|
+
const log = await Bun.file(server.logPath).text();
|
|
132
|
+
expect(log).toContain(`Is port ${heldPort} in use?`);
|
|
133
|
+
} finally {
|
|
134
|
+
await server?.stop();
|
|
135
|
+
holder.close();
|
|
136
|
+
await removeScratchDir(dir);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
SERVER_BOOT_HOOK_TIMEOUT_MS * 2,
|
|
140
|
+
);
|
|
@@ -14,7 +14,7 @@ import { openSync } from "node:fs";
|
|
|
14
14
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { dirname, join } from "node:path";
|
|
17
|
-
import { getFreePort } from "./test-net";
|
|
17
|
+
import { getFreePort, PORT_IN_USE_LOG, PortInUseError, startOnFreePort } from "./test-net";
|
|
18
18
|
|
|
19
19
|
// Re-exported for the suites that imported it from here before test-net.ts existed.
|
|
20
20
|
export { getFreePort };
|
|
@@ -50,6 +50,9 @@ export async function removeScratchDir(dir: string): Promise<void> {
|
|
|
50
50
|
* Spawn `bun src/http.ts` against `dbPath` on a free port. Integrations are
|
|
51
51
|
* disabled; both API-key env vars are pinned so a developer's shell env can't
|
|
52
52
|
* leak in (AGENT_SWARM_API_KEY takes precedence in getApiKey()).
|
|
53
|
+
*
|
|
54
|
+
* With waitForListen, a child that exits because another process took its
|
|
55
|
+
* port during boot is started again on a new port (startOnFreePort).
|
|
53
56
|
*/
|
|
54
57
|
export async function spawnSwarmServer(opts: {
|
|
55
58
|
dbPath: string;
|
|
@@ -57,9 +60,38 @@ export async function spawnSwarmServer(opts: {
|
|
|
57
60
|
env?: Record<string, string>;
|
|
58
61
|
/** Poll /docs until the server accepts requests (default true). */
|
|
59
62
|
waitForListen?: boolean;
|
|
63
|
+
/** Port source for each attempt (default getFreePort). Tests use it to force a taken port. */
|
|
64
|
+
getPort?: () => Promise<number>;
|
|
60
65
|
}): Promise<SwarmServer> {
|
|
61
|
-
const port = await getFreePort();
|
|
62
66
|
const logFd = openSync(opts.logPath, "a");
|
|
67
|
+
return startOnFreePort(
|
|
68
|
+
async (port) => {
|
|
69
|
+
const log = Bun.file(opts.logPath);
|
|
70
|
+
const logStart = (await log.exists()) ? log.size : 0;
|
|
71
|
+
const server = launchSwarmServer(opts, port, logFd);
|
|
72
|
+
if (opts.waitForListen === false) return server;
|
|
73
|
+
try {
|
|
74
|
+
await waitForListen(server);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (server.proc.exitCode !== null) {
|
|
77
|
+
const attemptLog = await Bun.file(opts.logPath).slice(logStart).text();
|
|
78
|
+
if (PORT_IN_USE_LOG.test(attemptLog)) {
|
|
79
|
+
throw new PortInUseError((error as Error).message, { cause: error });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
return server;
|
|
85
|
+
},
|
|
86
|
+
{ getPort: opts.getPort },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function launchSwarmServer(
|
|
91
|
+
opts: { dbPath: string; logPath: string; env?: Record<string, string> },
|
|
92
|
+
port: number,
|
|
93
|
+
logFd: number,
|
|
94
|
+
): SwarmServer {
|
|
63
95
|
const proc = Bun.spawn(["bun", "src/http.ts"], {
|
|
64
96
|
cwd: REPO_ROOT,
|
|
65
97
|
stdout: logFd,
|
|
@@ -93,12 +125,10 @@ export async function spawnSwarmServer(opts: {
|
|
|
93
125
|
...opts.env,
|
|
94
126
|
},
|
|
95
127
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const server: SwarmServer = {
|
|
128
|
+
return {
|
|
99
129
|
proc,
|
|
100
130
|
port,
|
|
101
|
-
base
|
|
131
|
+
base: `http://localhost:${port}`,
|
|
102
132
|
dbPath: opts.dbPath,
|
|
103
133
|
logPath: opts.logPath,
|
|
104
134
|
async stop() {
|
|
@@ -107,11 +137,6 @@ export async function spawnSwarmServer(opts: {
|
|
|
107
137
|
return proc.exitCode;
|
|
108
138
|
},
|
|
109
139
|
};
|
|
110
|
-
|
|
111
|
-
if (opts.waitForListen !== false) {
|
|
112
|
-
await waitForListen(server);
|
|
113
|
-
}
|
|
114
|
-
return server;
|
|
115
140
|
}
|
|
116
141
|
|
|
117
142
|
export async function waitForListen(server: SwarmServer, deadlineMs = 90_000): Promise<void> {
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
type CompositionCall,
|
|
4
|
+
type CompositionManifest,
|
|
5
|
+
compositionCallsOf,
|
|
6
|
+
deriveCallCompositions,
|
|
7
|
+
staticTokens,
|
|
8
|
+
} from "../../apps/ui/src/lib/request-composition";
|
|
9
|
+
import type { CallUsage, NormalizedItem } from "../../apps/ui/src/logs-parser/types";
|
|
10
|
+
|
|
11
|
+
const T0 = Date.parse("2026-10-26T10:00:00.000Z");
|
|
12
|
+
|
|
13
|
+
function manifest(overrides: Partial<CompositionManifest> = {}): CompositionManifest {
|
|
14
|
+
return {
|
|
15
|
+
id: "m-0",
|
|
16
|
+
taskId: "task-1",
|
|
17
|
+
agentId: "agent-1",
|
|
18
|
+
sessionId: "s-1",
|
|
19
|
+
seq: 0,
|
|
20
|
+
provider: "claude",
|
|
21
|
+
model: null,
|
|
22
|
+
source: "worker",
|
|
23
|
+
parts: [
|
|
24
|
+
{
|
|
25
|
+
kind: "system_section",
|
|
26
|
+
id: "system.agent.role",
|
|
27
|
+
label: "role",
|
|
28
|
+
chars: 4000,
|
|
29
|
+
estTokens: 1000,
|
|
30
|
+
fidelity: "exact",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
kind: "persona",
|
|
34
|
+
id: "persona",
|
|
35
|
+
label: "persona",
|
|
36
|
+
chars: 8000,
|
|
37
|
+
estTokens: 2000,
|
|
38
|
+
fidelity: "exact",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
kind: "tool_schemas",
|
|
42
|
+
id: "mcp:agent-swarm",
|
|
43
|
+
label: "tools",
|
|
44
|
+
chars: 40000,
|
|
45
|
+
estTokens: 10000,
|
|
46
|
+
fidelity: "estimate",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
kind: "skills_listing",
|
|
50
|
+
id: "skills_listing",
|
|
51
|
+
label: "skills",
|
|
52
|
+
chars: 4000,
|
|
53
|
+
estTokens: 1000,
|
|
54
|
+
fidelity: "estimate",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
kind: "task_prompt",
|
|
58
|
+
id: "task_prompt",
|
|
59
|
+
label: "task",
|
|
60
|
+
chars: 2000,
|
|
61
|
+
estTokens: 500,
|
|
62
|
+
fidelity: "exact",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
kind: "memories",
|
|
66
|
+
id: "memories",
|
|
67
|
+
label: "memories",
|
|
68
|
+
chars: 2000,
|
|
69
|
+
estTokens: 500,
|
|
70
|
+
fidelity: "exact",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
staticEstTokens: 15000,
|
|
74
|
+
tokenRatio: 1,
|
|
75
|
+
createdAt: new Date(T0 - 1000).toISOString(),
|
|
76
|
+
...overrides,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function call(
|
|
81
|
+
callId: string,
|
|
82
|
+
promptTokens: number,
|
|
83
|
+
offsetMs: number,
|
|
84
|
+
extra: Partial<CallUsage> = {},
|
|
85
|
+
sessionId = "s-1",
|
|
86
|
+
): CompositionCall {
|
|
87
|
+
return {
|
|
88
|
+
sessionId,
|
|
89
|
+
t: T0 + offsetMs,
|
|
90
|
+
usage: {
|
|
91
|
+
callId,
|
|
92
|
+
promptTokens,
|
|
93
|
+
cacheReadTokens: 0,
|
|
94
|
+
cacheWriteTokens: 0,
|
|
95
|
+
uncachedInputTokens: promptTokens,
|
|
96
|
+
outputTokens: 10,
|
|
97
|
+
sidechain: false,
|
|
98
|
+
harness: "claude",
|
|
99
|
+
reportedAt: "start",
|
|
100
|
+
...extra,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function sum(tokens: { tokens: number }[]): number {
|
|
106
|
+
return tokens.reduce((acc, s) => acc + s.tokens, 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function tokensOf(segments: { group: string; tokens: number }[], group: string): number {
|
|
110
|
+
return segments.find((s) => s.group === group)?.tokens ?? 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe("deriveCallCompositions", () => {
|
|
114
|
+
test("segments sum to each call's prompt tokens", () => {
|
|
115
|
+
const { byCall } = deriveCallCompositions(
|
|
116
|
+
[call("c1", 40_000, 0), call("c2", 52_000, 1000), call("c3", 90_000, 2000)],
|
|
117
|
+
[manifest()],
|
|
118
|
+
);
|
|
119
|
+
for (const [id, prompt] of [
|
|
120
|
+
["c1", 40_000],
|
|
121
|
+
["c2", 52_000],
|
|
122
|
+
["c3", 90_000],
|
|
123
|
+
] as const) {
|
|
124
|
+
const comp = byCall.get(id);
|
|
125
|
+
expect(comp).toBeDefined();
|
|
126
|
+
expect(sum(comp!.segments)).toBeCloseTo(prompt, 6);
|
|
127
|
+
expect(comp!.scaled).toBe(false);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("the first main call anchors the harness base; later calls add history", () => {
|
|
132
|
+
const { byCall, anchors } = deriveCallCompositions(
|
|
133
|
+
[call("c1", 40_000, 0), call("c2", 52_000, 1000)],
|
|
134
|
+
[manifest()],
|
|
135
|
+
);
|
|
136
|
+
const first = byCall.get("c1")!;
|
|
137
|
+
expect(tokensOf(first.segments, "harness")).toBe(25_000);
|
|
138
|
+
expect(tokensOf(first.segments, "history")).toBe(0);
|
|
139
|
+
expect(tokensOf(first.segments, "task")).toBe(1000);
|
|
140
|
+
const second = byCall.get("c2")!;
|
|
141
|
+
expect(tokensOf(second.segments, "harness")).toBe(25_000);
|
|
142
|
+
expect(tokensOf(second.segments, "history")).toBe(12_000);
|
|
143
|
+
expect(anchors.get("m-0")).toEqual({
|
|
144
|
+
callId: "c1",
|
|
145
|
+
promptTokens: 40_000,
|
|
146
|
+
residualTokens: 25_000,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("sub-agent calls are never the anchor and get no composition", () => {
|
|
151
|
+
const { byCall, anchors } = deriveCallCompositions(
|
|
152
|
+
[call("sub", 5_000, 0, { sidechain: true }), call("c1", 40_000, 1000)],
|
|
153
|
+
[manifest()],
|
|
154
|
+
);
|
|
155
|
+
expect(byCall.has("sub")).toBe(false);
|
|
156
|
+
expect(anchors.get("m-0")?.callId).toBe("c1");
|
|
157
|
+
expect(tokensOf(byCall.get("c1")!.segments, "harness")).toBe(25_000);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a compaction starts a new anchor whose harness base never grows", () => {
|
|
161
|
+
const compactions = [{ sessionId: "s-1", t: T0 + 1500 }];
|
|
162
|
+
// Post-compaction prompt carries a 6k summary: it is history, not harness.
|
|
163
|
+
const grown = deriveCallCompositions(
|
|
164
|
+
[call("c1", 40_000, 0), call("c2", 120_000, 1000), call("c3", 46_000, 2000)],
|
|
165
|
+
[manifest()],
|
|
166
|
+
compactions,
|
|
167
|
+
);
|
|
168
|
+
const post = grown.byCall.get("c3")!;
|
|
169
|
+
expect(tokensOf(post.segments, "harness")).toBe(25_000);
|
|
170
|
+
expect(tokensOf(post.segments, "history")).toBe(6_000);
|
|
171
|
+
// The rail's "first call" stays the session's first call.
|
|
172
|
+
expect(grown.anchors.get("m-0")?.callId).toBe("c1");
|
|
173
|
+
|
|
174
|
+
// The harness dropped part of its base after compacting: the base shrinks.
|
|
175
|
+
const shrunk = deriveCallCompositions(
|
|
176
|
+
[call("c1", 40_000, 0), call("c3", 30_000, 2000), call("c4", 33_000, 3000)],
|
|
177
|
+
[manifest()],
|
|
178
|
+
compactions,
|
|
179
|
+
);
|
|
180
|
+
expect(tokensOf(shrunk.byCall.get("c3")!.segments, "harness")).toBe(15_000);
|
|
181
|
+
expect(tokensOf(shrunk.byCall.get("c4")!.segments, "history")).toBe(3_000);
|
|
182
|
+
// Control: without the compaction, the base stays at the first call's.
|
|
183
|
+
const control = deriveCallCompositions(
|
|
184
|
+
[call("c1", 40_000, 0), call("c3", 30_000, 2000)],
|
|
185
|
+
[manifest()],
|
|
186
|
+
);
|
|
187
|
+
expect(tokensOf(control.byCall.get("c3")!.segments, "harness")).toBe(
|
|
188
|
+
25_000 * (30_000 / 40_000),
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("nothing goes negative when the estimates overshoot the prompt", () => {
|
|
193
|
+
const { byCall } = deriveCallCompositions(
|
|
194
|
+
[call("c1", 9_000, 0), call("c2", 7_500, 1000)],
|
|
195
|
+
[manifest()],
|
|
196
|
+
);
|
|
197
|
+
for (const id of ["c1", "c2"]) {
|
|
198
|
+
const comp = byCall.get(id)!;
|
|
199
|
+
expect(comp.scaled).toBe(true);
|
|
200
|
+
for (const seg of comp.segments) expect(seg.tokens).toBeGreaterThan(0);
|
|
201
|
+
expect(tokensOf(comp.segments, "harness")).toBe(0);
|
|
202
|
+
expect(tokensOf(comp.segments, "history")).toBe(0);
|
|
203
|
+
}
|
|
204
|
+
expect(sum(byCall.get("c1")!.segments)).toBeCloseTo(9_000, 6);
|
|
205
|
+
expect(sum(byCall.get("c2")!.segments)).toBeCloseTo(7_500, 6);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("the token ratio scales the static parts", () => {
|
|
209
|
+
const m = manifest({ tokenRatio: 1.2 });
|
|
210
|
+
expect(staticTokens(m)).toBeCloseTo(18_000, 6);
|
|
211
|
+
const { byCall } = deriveCallCompositions([call("c1", 40_000, 0)], [m]);
|
|
212
|
+
expect(tokensOf(byCall.get("c1")!.segments, "tools")).toBeCloseTo(12_000, 6);
|
|
213
|
+
expect(tokensOf(byCall.get("c1")!.segments, "harness")).toBeCloseTo(22_000, 6);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("each session uses its own manifest; a later seq applies from its write time", () => {
|
|
217
|
+
const s2 = manifest({ id: "m-s2", sessionId: "s-2", parts: manifest().parts.slice(0, 1) });
|
|
218
|
+
const seq1 = manifest({
|
|
219
|
+
id: "m-1",
|
|
220
|
+
seq: 1,
|
|
221
|
+
createdAt: new Date(T0 + 1500).toISOString(),
|
|
222
|
+
parts: manifest().parts.slice(0, 2),
|
|
223
|
+
});
|
|
224
|
+
const { byCall } = deriveCallCompositions(
|
|
225
|
+
[
|
|
226
|
+
call("c1", 40_000, 0),
|
|
227
|
+
call("c2", 41_000, 2000),
|
|
228
|
+
call("d1", 20_000, 0, {}, "s-2"),
|
|
229
|
+
call("x1", 20_000, 0, {}, "s-unknown"),
|
|
230
|
+
],
|
|
231
|
+
[manifest(), seq1, s2],
|
|
232
|
+
);
|
|
233
|
+
expect(byCall.get("c1")!.manifest.id).toBe("m-0");
|
|
234
|
+
expect(byCall.get("c2")!.manifest.id).toBe("m-1");
|
|
235
|
+
expect(byCall.get("d1")!.manifest.id).toBe("m-s2");
|
|
236
|
+
expect(tokensOf(byCall.get("d1")!.segments, "harness")).toBe(19_000);
|
|
237
|
+
expect(byCall.has("x1")).toBe(false);
|
|
238
|
+
for (const comp of byCall.values())
|
|
239
|
+
expect(sum(comp.segments)).toBeCloseTo(comp.promptTokens, 6);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("a hook manifest after the first call keeps later growth as history", () => {
|
|
243
|
+
// The worker manifest (15k static) anchors c1. The harness hook then posts
|
|
244
|
+
// exact parts worth 40k, the whole first prompt. c2's extra 12k is history.
|
|
245
|
+
const hook = manifest({
|
|
246
|
+
id: "m-hook",
|
|
247
|
+
seq: 1,
|
|
248
|
+
source: "harness-hook",
|
|
249
|
+
createdAt: new Date(T0 + 500).toISOString(),
|
|
250
|
+
parts: [{ ...manifest().parts[0]!, estTokens: 40_000, fidelity: "exact" }],
|
|
251
|
+
});
|
|
252
|
+
const { byCall, anchors } = deriveCallCompositions(
|
|
253
|
+
[call("c1", 40_000, 0), call("c2", 52_000, 1000)],
|
|
254
|
+
[manifest(), hook],
|
|
255
|
+
);
|
|
256
|
+
const second = byCall.get("c2")!;
|
|
257
|
+
expect(second.manifest.id).toBe("m-hook");
|
|
258
|
+
expect(tokensOf(second.segments, "harness")).toBe(0);
|
|
259
|
+
expect(tokensOf(second.segments, "history")).toBe(12_000);
|
|
260
|
+
expect(sum(second.segments)).toBeCloseTo(52_000, 6);
|
|
261
|
+
expect(anchors.get("m-hook")).toEqual({
|
|
262
|
+
callId: "c1",
|
|
263
|
+
promptTokens: 40_000,
|
|
264
|
+
residualTokens: 0,
|
|
265
|
+
});
|
|
266
|
+
// After a compaction the re-split base still caps the harness base: the
|
|
267
|
+
// 6k summary is history.
|
|
268
|
+
const compacted = deriveCallCompositions(
|
|
269
|
+
[call("c1", 40_000, 0), call("c2", 52_000, 1000), call("c3", 46_000, 2000)],
|
|
270
|
+
[manifest(), hook],
|
|
271
|
+
[{ sessionId: "s-1", t: T0 + 1500 }],
|
|
272
|
+
);
|
|
273
|
+
expect(tokensOf(compacted.byCall.get("c3")!.segments, "harness")).toBe(0);
|
|
274
|
+
expect(tokensOf(compacted.byCall.get("c3")!.segments, "history")).toBe(6_000);
|
|
275
|
+
// A hook manifest smaller than the first prompt leaves the rest as harness.
|
|
276
|
+
const partial = deriveCallCompositions(
|
|
277
|
+
[call("c1", 40_000, 0), call("c2", 52_000, 1000)],
|
|
278
|
+
[manifest(), { ...hook, parts: [{ ...hook.parts[0]!, estTokens: 30_000 }] }],
|
|
279
|
+
);
|
|
280
|
+
expect(tokensOf(partial.byCall.get("c2")!.segments, "harness")).toBe(10_000);
|
|
281
|
+
expect(tokensOf(partial.byCall.get("c2")!.segments, "history")).toBe(12_000);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("a later worker manifest keeps the harness base and swaps only the static parts", () => {
|
|
285
|
+
// m-0 has 15k static, so c1 (40k) measures a 25k harness base. A worker
|
|
286
|
+
// manifest then re-estimates the static parts as 16k. c2's history is
|
|
287
|
+
// 55k − 16k − 25k = 14k.
|
|
288
|
+
const part = manifest().parts[0]!;
|
|
289
|
+
const first = manifest({ parts: [{ ...part, estTokens: 15_000 }] });
|
|
290
|
+
const second = manifest({
|
|
291
|
+
id: "m-1",
|
|
292
|
+
seq: 1,
|
|
293
|
+
createdAt: new Date(T0 + 500).toISOString(),
|
|
294
|
+
parts: [{ ...part, estTokens: 16_000 }],
|
|
295
|
+
});
|
|
296
|
+
const { byCall, anchors } = deriveCallCompositions(
|
|
297
|
+
[call("c1", 40_000, 0), call("c2", 55_000, 1000)],
|
|
298
|
+
[first, second],
|
|
299
|
+
);
|
|
300
|
+
const c2 = byCall.get("c2")!;
|
|
301
|
+
expect(c2.manifest.id).toBe("m-1");
|
|
302
|
+
expect(tokensOf(c2.segments, "harness")).toBe(25_000);
|
|
303
|
+
expect(tokensOf(c2.segments, "history")).toBe(14_000);
|
|
304
|
+
expect(sum(c2.segments)).toBeCloseTo(55_000, 6);
|
|
305
|
+
expect(anchors.get("m-1")).toEqual({
|
|
306
|
+
callId: "c1",
|
|
307
|
+
promptTokens: 40_000,
|
|
308
|
+
residualTokens: 25_000,
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("no manifests means no compositions", () => {
|
|
313
|
+
expect(deriveCallCompositions([call("c1", 40_000, 0)], []).byCall.size).toBe(0);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe("compositionCallsOf", () => {
|
|
318
|
+
test("keeps call_usage items whose record has a session", () => {
|
|
319
|
+
const base = { fileIndex: 0, iteration: 1, lineNumber: 1, createdAt: "", t: T0, cli: "claude" };
|
|
320
|
+
const usage = call("c1", 100, 0).usage;
|
|
321
|
+
const items: NormalizedItem[] = [
|
|
322
|
+
{ ...base, recId: "r1", kind: "call_usage", usage },
|
|
323
|
+
{ ...base, recId: "r2", kind: "call_usage", usage: { ...usage, callId: "c2" } },
|
|
324
|
+
{ ...base, recId: "r1", kind: "text", text: "hi" },
|
|
325
|
+
];
|
|
326
|
+
const calls = compositionCallsOf(items, (recId) => (recId === "r1" ? "s-1" : undefined));
|
|
327
|
+
expect(calls).toEqual([{ usage, sessionId: "s-1", t: T0 }]);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -651,6 +651,10 @@ describe("script connections", () => {
|
|
|
651
651
|
markMigrationApplied(database, "240_workflow_run_parent_step_attempt.sql");
|
|
652
652
|
// 241 indexes skills, which this migration-112-only fixture does not create.
|
|
653
653
|
markMigrationApplied(database, "241_skills_meta_fts.sql");
|
|
654
|
+
// 244 alters api_key_status, which this migration-112-only fixture does not create.
|
|
655
|
+
markMigrationApplied(database, "244_api_key_validation.sql");
|
|
656
|
+
// 245 alters scripts, which this migration-112-only fixture does not create.
|
|
657
|
+
markMigrationApplied(database, "245_script_executor.sql");
|
|
654
658
|
|
|
655
659
|
const id = crypto.randomUUID();
|
|
656
660
|
const now = new Date().toISOString();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterAll, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterAll, describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
@@ -402,6 +402,41 @@ describe("native-only executor behavior", () => {
|
|
|
402
402
|
expect(output.error).toBe("killed");
|
|
403
403
|
});
|
|
404
404
|
|
|
405
|
+
// Before the fix the abort listener was added after the file writes, so an
|
|
406
|
+
// abort that landed during them was missed and the script ran to the end.
|
|
407
|
+
test.skipIf(SKIP_SANDBOX_SPAWN_TESTS)(
|
|
408
|
+
"an abort while args.json is written kills the run before the script starts",
|
|
409
|
+
async () => {
|
|
410
|
+
const dir = await mkdtemp(join(tmpdir(), "native-abort-"));
|
|
411
|
+
const marker = join(dir, "side-effect.marker");
|
|
412
|
+
const controller = new AbortController();
|
|
413
|
+
const write = Bun.write;
|
|
414
|
+
const spy = spyOn(Bun, "write").mockImplementation((async (
|
|
415
|
+
...writeArgs: Parameters<typeof Bun.write>
|
|
416
|
+
) => {
|
|
417
|
+
const written = await write(...writeArgs);
|
|
418
|
+
if (String(writeArgs[0]).endsWith("/args.json")) controller.abort();
|
|
419
|
+
return written;
|
|
420
|
+
}) as typeof Bun.write);
|
|
421
|
+
try {
|
|
422
|
+
const output = await new NativeScriptExecutor().run(
|
|
423
|
+
input({
|
|
424
|
+
source: `export default async () => { await Bun.write(${JSON.stringify(marker)}, "ran"); return "ran"; };`,
|
|
425
|
+
signal: controller.signal,
|
|
426
|
+
resources: { ...DEFAULT_SCRIPT_RESOURCES, memoryMb: 2048, wallClockMs: 10_000 },
|
|
427
|
+
}),
|
|
428
|
+
);
|
|
429
|
+
expect(output.error).toBe("killed");
|
|
430
|
+
expect(output.result).toBeUndefined();
|
|
431
|
+
expect(await Bun.file(marker).exists()).toBe(false);
|
|
432
|
+
} finally {
|
|
433
|
+
spy.mockRestore();
|
|
434
|
+
await rm(dir, { recursive: true, force: true });
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
438
|
+
);
|
|
439
|
+
|
|
405
440
|
// PR #1326 review finding: exit 134 (SIGABRT) was classified `capacity_exceeded`
|
|
406
441
|
// — and therefore retried by `runScript` — purely from the exit code, with no
|
|
407
442
|
// evidence about *when* the abort happened. A script that causes a side effect
|
|
@@ -104,3 +104,53 @@ describe("QuickJS pool configuration", () => {
|
|
|
104
104
|
);
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
|
+
|
|
108
|
+
// A fresh pool spawns its first worker, so the run waits on the worker's boot
|
|
109
|
+
// (`slot.ready`) when the abort lands. The server counts the script's fetch.
|
|
110
|
+
const abortDuringBootProbe = `
|
|
111
|
+
import { QuickJSScriptExecutor } from "./src/scripts-runtime/executors/quickjs";
|
|
112
|
+
import { DEFAULT_SCRIPT_RESOURCES } from "./src/scripts-runtime/executors/types";
|
|
113
|
+
let hits = 0;
|
|
114
|
+
const server = Bun.serve({ port: 0, fetch() { hits++; return new Response("ran"); } });
|
|
115
|
+
try {
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
const run = new QuickJSScriptExecutor().run({
|
|
118
|
+
source: 'export default async (args) => (await fetch(args.url)).text();',
|
|
119
|
+
args: { url: "http://127.0.0.1:" + server.port },
|
|
120
|
+
configPayload: { system: {
|
|
121
|
+
apiKey: { value: "pool-test", isSecret: true },
|
|
122
|
+
agentId: { value: crypto.randomUUID(), isSecret: false },
|
|
123
|
+
mcpBaseUrl: { value: "http://127.0.0.1:" + server.port, isSecret: false },
|
|
124
|
+
}, user: {} },
|
|
125
|
+
resources: DEFAULT_SCRIPT_RESOURCES,
|
|
126
|
+
fsMode: "none", network: "open", signal: controller.signal,
|
|
127
|
+
});
|
|
128
|
+
// One macrotask: the run has its slot and awaits the worker's boot.
|
|
129
|
+
await Bun.sleep(0);
|
|
130
|
+
controller.abort();
|
|
131
|
+
const output = await run;
|
|
132
|
+
await Bun.sleep(200);
|
|
133
|
+
console.log(JSON.stringify({ error: output.error ?? null, result: output.result ?? null, hits }));
|
|
134
|
+
} finally {
|
|
135
|
+
server.stop(true);
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
describe("QuickJS abort while the worker boots", () => {
|
|
140
|
+
test(
|
|
141
|
+
"an abort before the worker is ready kills the run and the script never runs",
|
|
142
|
+
async () => {
|
|
143
|
+
const result = expectChildOk(
|
|
144
|
+
await runChild([process.execPath, "--eval", abortDuringBootProbe], { env: process.env }),
|
|
145
|
+
"QuickJS abort-during-boot probe",
|
|
146
|
+
);
|
|
147
|
+
const out = JSON.parse(result.stdout.trim()) as {
|
|
148
|
+
error: string | null;
|
|
149
|
+
result: string | null;
|
|
150
|
+
hits: number;
|
|
151
|
+
};
|
|
152
|
+
expect(out).toEqual({ error: "killed", result: null, hits: 0 });
|
|
153
|
+
},
|
|
154
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
155
|
+
);
|
|
156
|
+
});
|