@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
|
@@ -98,6 +98,45 @@ describe("script SDK allowlist", () => {
|
|
|
98
98
|
}
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
test("workflow_getRun forwards fields to the run route and rejects an unknown value", async () => {
|
|
102
|
+
const requests: URL[] = [];
|
|
103
|
+
const httpServer = Bun.serve({
|
|
104
|
+
port: 0,
|
|
105
|
+
fetch(req) {
|
|
106
|
+
requests.push(new URL(req.url));
|
|
107
|
+
return Response.json({ run: { id: "run-1" }, steps: [] });
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
const config = new SwarmConfig({
|
|
111
|
+
system: {
|
|
112
|
+
apiKey: { value: "sdk-test-key", isSecret: true },
|
|
113
|
+
agentId: { value: "sdk-test-agent", isSecret: false },
|
|
114
|
+
mcpBaseUrl: { value: `http://127.0.0.1:${httpServer.port}`, isSecret: false },
|
|
115
|
+
},
|
|
116
|
+
user: {},
|
|
117
|
+
});
|
|
118
|
+
const sdk = createSwarmSdk(config);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
await sdk.workflow_getRun({ id: "run-1", fields: "slim" });
|
|
122
|
+
await sdk.workflow_getRun({ id: "run-1" });
|
|
123
|
+
expect(requests.map((url) => url.pathname)).toEqual([
|
|
124
|
+
"/api/workflow-runs/run-1",
|
|
125
|
+
"/api/workflow-runs/run-1",
|
|
126
|
+
]);
|
|
127
|
+
expect(requests[0]?.searchParams.get("fields")).toBe("slim");
|
|
128
|
+
// Omitted `fields` sends no query, so the route keeps its full default.
|
|
129
|
+
expect(requests[1]?.search).toBe("");
|
|
130
|
+
|
|
131
|
+
await expect(sdk.workflow_getRun({ id: "run-1", fields: "tiny" })).rejects.toThrow(
|
|
132
|
+
'`fields` must be "full" or "slim"',
|
|
133
|
+
);
|
|
134
|
+
expect(requests).toHaveLength(2);
|
|
135
|
+
} finally {
|
|
136
|
+
httpServer.stop(true);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
101
140
|
test("approval_cancel posts to the cancel route with the reason", async () => {
|
|
102
141
|
let request: { method: string; path: string; body: unknown } | undefined;
|
|
103
142
|
const httpServer = Bun.serve({
|
|
@@ -252,8 +291,9 @@ describe("script SDK allowlist", () => {
|
|
|
252
291
|
|
|
253
292
|
test("bundled swarm-sdk.d.ts uses triggerData (not input) for workflow_trigger", async () => {
|
|
254
293
|
const types = await Bun.file("src/scripts-runtime/types/swarm-sdk.d.ts").text();
|
|
255
|
-
expect(types).
|
|
256
|
-
expect(types).
|
|
294
|
+
expect(types).toMatch(/workflow_trigger\(args: \{\s*id: string;\s*triggerData\?/);
|
|
295
|
+
expect(types).toMatch(/workflow_trigger\(args: \{[^}]*\bwait\?: boolean;/);
|
|
296
|
+
expect(types).not.toMatch(/workflow_trigger\(args: \{\s*id: string;\s*input\?/);
|
|
257
297
|
});
|
|
258
298
|
});
|
|
259
299
|
|
|
@@ -503,17 +503,17 @@ describe("Self-Improvement Mechanisms", () => {
|
|
|
503
503
|
const MEMORY_SKILL_POINTER =
|
|
504
504
|
"You MUST use the `memory` skill before you store, edit, or delete a memory.";
|
|
505
505
|
|
|
506
|
-
test("a worker prompt
|
|
506
|
+
test("a worker prompt points learnings at store-progress and names the memory skill", async () => {
|
|
507
507
|
const prompt = await getBasePrompt({ role: "worker", agentId: workerId });
|
|
508
508
|
|
|
509
|
-
expect(prompt).toContain("`
|
|
509
|
+
expect(prompt).toContain("as `learning` on a `store-progress` call");
|
|
510
510
|
expect(prompt).toContain(MEMORY_SKILL_POINTER);
|
|
511
511
|
});
|
|
512
512
|
|
|
513
|
-
test("a lead prompt
|
|
513
|
+
test("a lead prompt points learnings at store-progress and names the memory skill", async () => {
|
|
514
514
|
const prompt = await getBasePrompt({ role: "lead", agentId: leadId });
|
|
515
515
|
|
|
516
|
-
expect(prompt).toContain("`
|
|
516
|
+
expect(prompt).toContain("as `learning` on a `store-progress` call");
|
|
517
517
|
expect(prompt).toContain(MEMORY_SKILL_POINTER);
|
|
518
518
|
});
|
|
519
519
|
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { defaultAssetKey } from "../assets/key";
|
|
3
|
+
import { sessionSourceKey } from "../assets/source-key";
|
|
4
|
+
import * as audit from "../be/asset-key-audit";
|
|
5
|
+
import {
|
|
6
|
+
closeDb,
|
|
7
|
+
createTaskExtended,
|
|
8
|
+
createUser,
|
|
9
|
+
getDbClient,
|
|
10
|
+
getTaskById,
|
|
11
|
+
initDb,
|
|
12
|
+
insertTaskAttachment,
|
|
13
|
+
listRecentSessions,
|
|
14
|
+
upsertInboxState,
|
|
15
|
+
} from "../be/db";
|
|
16
|
+
import * as sessionMoves from "../be/db/session-moves";
|
|
17
|
+
import type { DbParam } from "../be/db-client";
|
|
18
|
+
import { runSessionSourceBackfill } from "../be/session-source-backfill";
|
|
19
|
+
import { chatSdkContextKey } from "../tasks/context-key";
|
|
20
|
+
|
|
21
|
+
const DONE_KEY = "session-source-keys-v1";
|
|
22
|
+
const CURSOR_KEY = `${DONE_KEY}:cursor`;
|
|
23
|
+
const BEFORE = "2026-10-21T10:30:00.000Z";
|
|
24
|
+
const DONE_AT = "2026-10-21T11:00:00.000Z";
|
|
25
|
+
const slack = { slackChannelId: "C0AR967K0KZ", slackThreadTs: "1729000000.123456" };
|
|
26
|
+
let owner: string;
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
initDb(":memory:");
|
|
30
|
+
owner = (await createUser({ name: "Backfill owner" })).id;
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => closeDb());
|
|
33
|
+
|
|
34
|
+
// SQL preserves pre-source-key rows, including uppercase legacy task ids.
|
|
35
|
+
async function seedTask(
|
|
36
|
+
opts: {
|
|
37
|
+
id?: string;
|
|
38
|
+
key?: string;
|
|
39
|
+
source?: string;
|
|
40
|
+
parentTaskId?: string;
|
|
41
|
+
requestedByUserId?: string | null;
|
|
42
|
+
slackChannelId?: string;
|
|
43
|
+
slackThreadTs?: string;
|
|
44
|
+
contextKey?: string;
|
|
45
|
+
} = {},
|
|
46
|
+
) {
|
|
47
|
+
const id = opts.id ?? crypto.randomUUID();
|
|
48
|
+
await getDbClient().run(
|
|
49
|
+
`INSERT INTO agent_tasks
|
|
50
|
+
(id, task, status, source, parentTaskId, requestedByUserId, "key",
|
|
51
|
+
slackChannelId, slackThreadTs, contextKey, createdAt, lastUpdatedAt, updated_by)
|
|
52
|
+
VALUES (?, 'Old session', 'completed', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
53
|
+
[
|
|
54
|
+
id,
|
|
55
|
+
opts.source ?? "slack",
|
|
56
|
+
opts.parentTaskId ?? null,
|
|
57
|
+
opts.requestedByUserId === undefined ? owner : opts.requestedByUserId,
|
|
58
|
+
opts.key ?? defaultAssetKey("task", id),
|
|
59
|
+
opts.slackChannelId ?? null,
|
|
60
|
+
opts.slackThreadTs ?? null,
|
|
61
|
+
opts.contextKey ?? null,
|
|
62
|
+
BEFORE,
|
|
63
|
+
BEFORE,
|
|
64
|
+
owner,
|
|
65
|
+
],
|
|
66
|
+
);
|
|
67
|
+
return (await getTaskById(id))!;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function seedState(key: string) {
|
|
71
|
+
return getDbClient().get<{ seededHash: string }>(
|
|
72
|
+
"SELECT seededHash FROM seed_state WHERE kind = 'maintenance' AND key = ?",
|
|
73
|
+
[key],
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test("moves both old defaults, whole chains and mapped files with history, without changing read or Done state", async () => {
|
|
78
|
+
const roots = [
|
|
79
|
+
await seedTask({ ...slack, key: "shared/" }),
|
|
80
|
+
await seedTask({
|
|
81
|
+
...slack,
|
|
82
|
+
slackThreadTs: "1729000000.654321",
|
|
83
|
+
id: crypto.randomUUID().toUpperCase(),
|
|
84
|
+
}),
|
|
85
|
+
];
|
|
86
|
+
const expectedHistory: unknown[] = [];
|
|
87
|
+
const tasks = roots.map((task) => ({ task, key: sessionSourceKey(task) }));
|
|
88
|
+
const mappings: { id: string; key: string | null }[] = [];
|
|
89
|
+
for (const [index, root] of roots.entries()) {
|
|
90
|
+
const child = await seedTask({ parentTaskId: root.id, key: root.key, source: "mcp" });
|
|
91
|
+
const grandchild = await seedTask({
|
|
92
|
+
parentTaskId: child.id,
|
|
93
|
+
key: "shared/other-child-key/",
|
|
94
|
+
source: "mcp",
|
|
95
|
+
});
|
|
96
|
+
tasks.push(...[child, grandchild].map((task) => ({ task, key: sessionSourceKey(root) })));
|
|
97
|
+
const attachment = await insertTaskAttachment({
|
|
98
|
+
taskId: grandchild.id,
|
|
99
|
+
kind: "agent-fs",
|
|
100
|
+
name: "notes",
|
|
101
|
+
path: `backfill/${grandchild.id}`,
|
|
102
|
+
providerId: "agent-fs",
|
|
103
|
+
providerKey: `backfill/${grandchild.id}`,
|
|
104
|
+
});
|
|
105
|
+
const mapping = await getDbClient().get<{ id: string; key: string }>(
|
|
106
|
+
"SELECT id, key FROM asset_key_mappings WHERE source_entity_id = ?",
|
|
107
|
+
[attachment.id],
|
|
108
|
+
);
|
|
109
|
+
expect(mapping).not.toBeNull();
|
|
110
|
+
mappings.push({ id: mapping!.id, key: sessionSourceKey(root) });
|
|
111
|
+
await getDbClient().run("UPDATE asset_key_mappings SET updated_by = ? WHERE id = ?", [
|
|
112
|
+
owner,
|
|
113
|
+
mapping!.id,
|
|
114
|
+
]);
|
|
115
|
+
for (const task of [root, child, grandchild]) {
|
|
116
|
+
expectedHistory.push({
|
|
117
|
+
entity_type: "task",
|
|
118
|
+
entity_id: task.id,
|
|
119
|
+
previous_key: task.key,
|
|
120
|
+
new_key: sessionSourceKey(root),
|
|
121
|
+
changed_by: null,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
expectedHistory.push({
|
|
125
|
+
entity_type: "file",
|
|
126
|
+
entity_id: mapping!.id,
|
|
127
|
+
previous_key: mapping!.key,
|
|
128
|
+
new_key: sessionSourceKey(root),
|
|
129
|
+
changed_by: null,
|
|
130
|
+
});
|
|
131
|
+
await upsertInboxState({
|
|
132
|
+
userId: owner,
|
|
133
|
+
itemType: "to_read",
|
|
134
|
+
itemId: root.id,
|
|
135
|
+
status: index === 0 ? "done" : "open",
|
|
136
|
+
doneAt: index === 0 ? DONE_AT : undefined,
|
|
137
|
+
readAt: BEFORE,
|
|
138
|
+
lastReadAt: BEFORE,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const inboxBefore = await getDbClient().query("SELECT * FROM inbox_item_state ORDER BY itemId");
|
|
142
|
+
const sessionsBefore = await listRecentSessions({ viewer: { userId: owner, scope: "all" } });
|
|
143
|
+
expect(sessionsBefore.find((s) => s.root.id === roots[0]!.id)?.readState).toBe("done");
|
|
144
|
+
const historyBefore = await getDbClient().query(
|
|
145
|
+
"SELECT entity_type, entity_id, previous_key, new_key, changed_by FROM asset_key_history",
|
|
146
|
+
);
|
|
147
|
+
const auditSpy = spyOn(audit, "auditAssetKeys");
|
|
148
|
+
try {
|
|
149
|
+
expect(await runSessionSourceBackfill({ batchSize: 1 })).toEqual({
|
|
150
|
+
scanned: 2,
|
|
151
|
+
moved: 2,
|
|
152
|
+
skipped: 0,
|
|
153
|
+
failed: 0,
|
|
154
|
+
});
|
|
155
|
+
expect(auditSpy).toHaveBeenCalledTimes(1);
|
|
156
|
+
} finally {
|
|
157
|
+
auditSpy.mockRestore();
|
|
158
|
+
}
|
|
159
|
+
for (const { task, key } of tasks) {
|
|
160
|
+
const after = await getTaskById(task.id);
|
|
161
|
+
expect(after?.key).toBe(key);
|
|
162
|
+
expect(after?.lastUpdatedAt).toBe(task.lastUpdatedAt);
|
|
163
|
+
expect(
|
|
164
|
+
await getDbClient().get("SELECT updated_by FROM agent_tasks WHERE id = ?", [task.id]),
|
|
165
|
+
).toEqual({ updated_by: owner });
|
|
166
|
+
}
|
|
167
|
+
for (const { id, key } of mappings) {
|
|
168
|
+
expect(
|
|
169
|
+
await getDbClient().get("SELECT key, updated_by FROM asset_key_mappings WHERE id = ?", [id]),
|
|
170
|
+
).toEqual({ key, updated_by: owner });
|
|
171
|
+
}
|
|
172
|
+
const history = await getDbClient().query(
|
|
173
|
+
"SELECT entity_type, entity_id, previous_key, new_key, changed_by FROM asset_key_history",
|
|
174
|
+
);
|
|
175
|
+
expect(history).toHaveLength(historyBefore.length + expectedHistory.length);
|
|
176
|
+
expect(history).toEqual(expect.arrayContaining([...historyBefore, ...expectedHistory]));
|
|
177
|
+
expect(await getDbClient().query("SELECT * FROM inbox_item_state ORDER BY itemId")).toEqual(
|
|
178
|
+
inboxBefore,
|
|
179
|
+
);
|
|
180
|
+
const sessionsAfter = await listRecentSessions({ viewer: { userId: owner, scope: "all" } });
|
|
181
|
+
for (const before of sessionsBefore) {
|
|
182
|
+
expect(sessionsAfter.find((s) => s.root.id === before.root.id)?.readState).toBe(
|
|
183
|
+
before.readState,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
expect(await seedState(DONE_KEY)).toEqual({ seededHash: "done" });
|
|
187
|
+
expect(await seedState(CURSOR_KEY)).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("preserves user keys and unsupported roots, and moves mcp and ownerless Slack roots", async () => {
|
|
191
|
+
const custom = await seedTask({ ...slack, key: "shared/research/" });
|
|
192
|
+
const child = await seedTask({ ...slack, parentTaskId: custom.id, key: "shared/" });
|
|
193
|
+
const foreignDefault = await seedTask({ ...slack, key: defaultAssetKey("task", custom.id) });
|
|
194
|
+
const teams = await seedTask({ source: "chat", contextKey: chatSdkContextKey("teams:c:t") });
|
|
195
|
+
const ordinary = await seedTask({ source: "ui" });
|
|
196
|
+
const missingThread = await seedTask({ slackChannelId: slack.slackChannelId });
|
|
197
|
+
const mcp = await seedTask({ ...slack, source: "mcp" });
|
|
198
|
+
const ownerless = await seedTask({ ...slack, requestedByUserId: null });
|
|
199
|
+
expect(await runSessionSourceBackfill()).toEqual({ scanned: 3, moved: 2, skipped: 1, failed: 0 });
|
|
200
|
+
for (const task of [custom, child, foreignDefault, teams, ordinary, missingThread]) {
|
|
201
|
+
expect(await getTaskById(task.id)).toEqual(task);
|
|
202
|
+
}
|
|
203
|
+
for (const task of [mcp, ownerless]) {
|
|
204
|
+
expect((await getTaskById(task.id))?.key).toBe(sessionSourceKey(task));
|
|
205
|
+
}
|
|
206
|
+
expect((await getTaskById(ownerless.id))?.requestedByUserId).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("moves supported chat roots", async () => {
|
|
210
|
+
const threads = [
|
|
211
|
+
["telegram:-1001234567890:42", "shared/telegram/-1001234567890/thread:42/"],
|
|
212
|
+
["telegram:555", "shared/telegram/555/task:00000000-0000-4000-8000-000000000001/"],
|
|
213
|
+
["discord:guild:channel:thread", "shared/discord/channel/thread:thread/"],
|
|
214
|
+
["discord:@me:dm", "shared/discord/dm/task:00000000-0000-4000-8000-000000000003/"],
|
|
215
|
+
["slack:C123:1.2", "shared/slack/c123/thread:1.2/"],
|
|
216
|
+
["slack:D123:", "shared/slack/d123/task:00000000-0000-4000-8000-000000000005/"],
|
|
217
|
+
];
|
|
218
|
+
const roots = [];
|
|
219
|
+
for (const [index, [thread, key]] of threads.entries()) {
|
|
220
|
+
const root = await seedTask({
|
|
221
|
+
id: `00000000-0000-4000-8000-00000000000${index}`,
|
|
222
|
+
source: "chat",
|
|
223
|
+
contextKey: chatSdkContextKey(thread!),
|
|
224
|
+
});
|
|
225
|
+
roots.push({ root, key });
|
|
226
|
+
}
|
|
227
|
+
const invalid = await seedTask({ ...slack, slackChannelId: "x".repeat(255) });
|
|
228
|
+
expect(await runSessionSourceBackfill()).toEqual({ scanned: 7, moved: 6, skipped: 1, failed: 0 });
|
|
229
|
+
for (const { root, key } of roots) {
|
|
230
|
+
expect((await getTaskById(root.id))?.key).toBe(key);
|
|
231
|
+
}
|
|
232
|
+
expect(await getTaskById(invalid.id)).toEqual(invalid);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("a completed run does not scan or audit again, even if another old root appears", async () => {
|
|
236
|
+
await seedTask(slack);
|
|
237
|
+
await runSessionSourceBackfill();
|
|
238
|
+
const before = await getDbClient().query("SELECT * FROM asset_key_history ORDER BY id");
|
|
239
|
+
const later = await seedTask(slack);
|
|
240
|
+
const auditSpy = spyOn(audit, "auditAssetKeys");
|
|
241
|
+
try {
|
|
242
|
+
expect(await runSessionSourceBackfill()).toBeNull();
|
|
243
|
+
expect(auditSpy).not.toHaveBeenCalled();
|
|
244
|
+
} finally {
|
|
245
|
+
auditSpy.mockRestore();
|
|
246
|
+
}
|
|
247
|
+
expect(await getTaskById(later.id)).toEqual(later);
|
|
248
|
+
expect(await getDbClient().query("SELECT * FROM asset_key_history ORDER BY id")).toEqual(before);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("isolates a failed root and retries it from the start on the next run", async () => {
|
|
252
|
+
const roots = [];
|
|
253
|
+
for (let index = 0; index < 3; index++) {
|
|
254
|
+
roots.push(await seedTask({ ...slack, id: `00000000-0000-4000-8000-00000000000${index}` }));
|
|
255
|
+
}
|
|
256
|
+
const move = sessionMoves.moveTaskChainKeyInTx;
|
|
257
|
+
const moveSpy = spyOn(sessionMoves, "moveTaskChainKeyInTx").mockImplementationOnce(
|
|
258
|
+
async (...args) => {
|
|
259
|
+
await move(...args);
|
|
260
|
+
throw new Error("Root move failed");
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
264
|
+
try {
|
|
265
|
+
expect(await runSessionSourceBackfill({ batchSize: 1 })).toEqual({
|
|
266
|
+
scanned: 3,
|
|
267
|
+
moved: 2,
|
|
268
|
+
skipped: 0,
|
|
269
|
+
failed: 1,
|
|
270
|
+
});
|
|
271
|
+
expect(moveSpy).toHaveBeenCalledTimes(3);
|
|
272
|
+
expect(moveSpy.mock.calls[0]?.[0]).toBe(roots[0]!.id);
|
|
273
|
+
expect(warn).toHaveBeenCalledTimes(2);
|
|
274
|
+
expect(warn.mock.calls[0]?.[0]).toContain(`root=${roots[0]!.id} failed: Root move failed`);
|
|
275
|
+
expect(await getTaskById(roots[0]!.id)).toEqual(roots[0]!);
|
|
276
|
+
for (const root of roots.slice(1)) {
|
|
277
|
+
expect((await getTaskById(root.id))?.key).toBe(sessionSourceKey(root));
|
|
278
|
+
}
|
|
279
|
+
expect(await getDbClient().query("SELECT id FROM asset_key_history")).toHaveLength(2);
|
|
280
|
+
expect(await seedState(DONE_KEY)).toBeNull();
|
|
281
|
+
expect(await seedState(CURSOR_KEY)).toBeNull();
|
|
282
|
+
} finally {
|
|
283
|
+
moveSpy.mockRestore();
|
|
284
|
+
warn.mockRestore();
|
|
285
|
+
}
|
|
286
|
+
expect(await runSessionSourceBackfill()).toEqual({ scanned: 1, moved: 1, skipped: 0, failed: 0 });
|
|
287
|
+
expect((await getTaskById(roots[0]!.id))?.key).toBe(sessionSourceKey(roots[0]!));
|
|
288
|
+
expect(await getDbClient().query("SELECT id FROM asset_key_history")).toHaveLength(3);
|
|
289
|
+
expect(await seedState(DONE_KEY)).toEqual({ seededHash: "done" });
|
|
290
|
+
expect(await seedState(CURSOR_KEY)).toBeNull();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("preserves a user move and includes a child created after the batch read", async () => {
|
|
294
|
+
const movedByUser = await seedTask(slack);
|
|
295
|
+
const root = await seedTask(slack);
|
|
296
|
+
const client = getDbClient();
|
|
297
|
+
const query = client.query.bind(client);
|
|
298
|
+
let childId: string | undefined;
|
|
299
|
+
const querySpy = spyOn(client, "query").mockImplementation(
|
|
300
|
+
async <T>(sql: string, params?: DbParam[]): Promise<T[]> => {
|
|
301
|
+
const rows = await query<T>(sql, params);
|
|
302
|
+
if (sql.startsWith("SELECT id, slackChannelId") && rows.length > 0) {
|
|
303
|
+
await client.run('UPDATE agent_tasks SET "key" = ? WHERE id = ?', [
|
|
304
|
+
"shared/research/",
|
|
305
|
+
movedByUser.id,
|
|
306
|
+
]);
|
|
307
|
+
childId = (await seedTask({ parentTaskId: root.id, key: root.key, source: "mcp" })).id;
|
|
308
|
+
}
|
|
309
|
+
return rows;
|
|
310
|
+
},
|
|
311
|
+
);
|
|
312
|
+
try {
|
|
313
|
+
expect(await runSessionSourceBackfill()).toEqual({
|
|
314
|
+
scanned: 2,
|
|
315
|
+
moved: 1,
|
|
316
|
+
skipped: 1,
|
|
317
|
+
failed: 0,
|
|
318
|
+
});
|
|
319
|
+
expect(childId).toBeDefined();
|
|
320
|
+
} finally {
|
|
321
|
+
querySpy.mockRestore();
|
|
322
|
+
}
|
|
323
|
+
expect((await getTaskById(movedByUser.id))?.key).toBe("shared/research/");
|
|
324
|
+
expect((await getTaskById(root.id))?.key).toBe(sessionSourceKey(root));
|
|
325
|
+
expect((await getTaskById(childId!))?.key).toBe(sessionSourceKey(root));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("a child whose parent moves after the parent read still inherits the moved key", async () => {
|
|
329
|
+
const root = await seedTask(slack);
|
|
330
|
+
const client = getDbClient();
|
|
331
|
+
const transaction = client.transaction.bind(client);
|
|
332
|
+
let backfilled = false;
|
|
333
|
+
// The first transaction is the child's INSERT: the backfill commits first.
|
|
334
|
+
const txSpy = spyOn(client, "transaction").mockImplementationOnce(async (fn, opts) => {
|
|
335
|
+
txSpy.mockRestore();
|
|
336
|
+
backfilled = true;
|
|
337
|
+
expect(await runSessionSourceBackfill()).toMatchObject({ moved: 1, failed: 0 });
|
|
338
|
+
return transaction(fn, opts);
|
|
339
|
+
});
|
|
340
|
+
let child: Awaited<ReturnType<typeof createTaskExtended>>;
|
|
341
|
+
try {
|
|
342
|
+
child = await createTaskExtended("Child", { parentTaskId: root.id });
|
|
343
|
+
} finally {
|
|
344
|
+
txSpy.mockRestore();
|
|
345
|
+
}
|
|
346
|
+
expect(backfilled).toBe(true);
|
|
347
|
+
expect((await getTaskById(root.id))?.key).toBe(sessionSourceKey(root));
|
|
348
|
+
expect(child.key).toBe(sessionSourceKey(root));
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("resumes after a committed cursor, including skipped roots, when afterBatch throws once", async () => {
|
|
352
|
+
const roots = [
|
|
353
|
+
await seedTask({
|
|
354
|
+
id: "00000000-0000-4000-8000-000000000001",
|
|
355
|
+
source: "chat",
|
|
356
|
+
contextKey: chatSdkContextKey("teams:c:t"),
|
|
357
|
+
}),
|
|
358
|
+
await seedTask({ id: "00000000-0000-4000-8000-000000000002", ...slack }),
|
|
359
|
+
await seedTask({ id: "00000000-0000-4000-8000-000000000003", ...slack }),
|
|
360
|
+
await seedTask({ id: "00000000-0000-4000-8000-000000000004", ...slack }),
|
|
361
|
+
await seedTask({ id: "00000000-0000-4000-8000-000000000005", ...slack }),
|
|
362
|
+
];
|
|
363
|
+
let firstBatch: string[] = [];
|
|
364
|
+
await expect(
|
|
365
|
+
runSessionSourceBackfill({
|
|
366
|
+
batchSize: 2,
|
|
367
|
+
afterBatch: ({ ids }) => {
|
|
368
|
+
firstBatch = ids;
|
|
369
|
+
throw new Error("Interrupted after batch 1");
|
|
370
|
+
},
|
|
371
|
+
}),
|
|
372
|
+
).rejects.toThrow("Interrupted after batch 1");
|
|
373
|
+
expect(firstBatch).toEqual(roots.slice(0, 2).map((r) => r.id));
|
|
374
|
+
expect(await seedState(CURSOR_KEY)).toEqual({ seededHash: roots[1]!.id });
|
|
375
|
+
expect(await seedState(DONE_KEY)).toBeNull();
|
|
376
|
+
expect((await getTaskById(roots[1]!.id))?.key).toBe(sessionSourceKey(roots[1]!));
|
|
377
|
+
expect((await getTaskById(roots[2]!.id))?.key).toBe(roots[2]!.key);
|
|
378
|
+
const resumed: string[] = [];
|
|
379
|
+
expect(
|
|
380
|
+
await runSessionSourceBackfill({
|
|
381
|
+
batchSize: 2,
|
|
382
|
+
afterBatch: ({ ids }) => {
|
|
383
|
+
resumed.push(...ids);
|
|
384
|
+
},
|
|
385
|
+
}),
|
|
386
|
+
).toEqual({ scanned: 3, moved: 3, skipped: 0, failed: 0 });
|
|
387
|
+
expect(resumed).toEqual(roots.slice(2).map((r) => r.id));
|
|
388
|
+
expect(await getTaskById(roots[0]!.id)).toEqual(roots[0]!);
|
|
389
|
+
expect(await getDbClient().query("SELECT id FROM asset_key_history")).toHaveLength(4);
|
|
390
|
+
expect(await seedState(DONE_KEY)).toEqual({ seededHash: "done" });
|
|
391
|
+
expect(await seedState(CURSOR_KEY)).toBeNull();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("a blocked audit warns without a marker or cursor, then retries successfully", async () => {
|
|
395
|
+
const root = await seedTask(slack);
|
|
396
|
+
const originalAudit = audit.auditAssetKeys;
|
|
397
|
+
const auditSpy = spyOn(audit, "auditAssetKeys").mockImplementation((db) => ({
|
|
398
|
+
...originalAudit(db),
|
|
399
|
+
ok: false,
|
|
400
|
+
warningCount: 1,
|
|
401
|
+
}));
|
|
402
|
+
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
403
|
+
try {
|
|
404
|
+
expect(await runSessionSourceBackfill()).toBeNull();
|
|
405
|
+
expect(auditSpy).toHaveBeenCalledTimes(1);
|
|
406
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
407
|
+
expect(await seedState(DONE_KEY)).toBeNull();
|
|
408
|
+
expect(await seedState(CURSOR_KEY)).toBeNull();
|
|
409
|
+
expect(await getTaskById(root.id)).toEqual(root);
|
|
410
|
+
expect(await getDbClient().query("SELECT id FROM asset_key_history")).toHaveLength(0);
|
|
411
|
+
} finally {
|
|
412
|
+
auditSpy.mockRestore();
|
|
413
|
+
warn.mockRestore();
|
|
414
|
+
}
|
|
415
|
+
expect((await runSessionSourceBackfill())?.moved).toBe(1);
|
|
416
|
+
expect(await seedState(DONE_KEY)).toEqual({ seededHash: "done" });
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("unexpected audit errors propagate without a marker", async () => {
|
|
420
|
+
const auditSpy = spyOn(audit, "auditAssetKeys").mockImplementation(() => {
|
|
421
|
+
throw new TypeError("Unexpected audit failure");
|
|
422
|
+
});
|
|
423
|
+
try {
|
|
424
|
+
await expect(runSessionSourceBackfill()).rejects.toThrow("Unexpected audit failure");
|
|
425
|
+
expect(await seedState(DONE_KEY)).toBeNull();
|
|
426
|
+
} finally {
|
|
427
|
+
auditSpy.mockRestore();
|
|
428
|
+
}
|
|
429
|
+
});
|