@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
package/src/be/db.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
2
|
import pkg from "../../package.json";
|
|
3
|
-
import { defaultAssetKey, normalizeAssetKey
|
|
3
|
+
import { defaultAssetKey, normalizeAssetKey } from "../assets/key";
|
|
4
|
+
import { sessionSourceKey, sessionThreadGroupKey } from "../assets/source-key";
|
|
4
5
|
import {
|
|
5
6
|
generateDefaultClaudeMd,
|
|
6
7
|
generateDefaultIdentityMd,
|
|
@@ -207,6 +208,7 @@ import {
|
|
|
207
208
|
sessionCountQuery,
|
|
208
209
|
sessionPageFields,
|
|
209
210
|
sessionSummaryQuery,
|
|
211
|
+
threadRootsSql,
|
|
210
212
|
} from "./db/session-tree";
|
|
211
213
|
import { searchSkillRowsRanked } from "./db/skill-search";
|
|
212
214
|
import { classifyTaskHumanFree, reclassifyTaskHumanFree } from "./db/tasks/human-free";
|
|
@@ -225,8 +227,15 @@ import { configureTaskWriteDependencies, failTask } from "./db/tasks/write";
|
|
|
225
227
|
import { tombstoneWorkflowIdempotencyKeys } from "./db/workflow-idempotency";
|
|
226
228
|
import {
|
|
227
229
|
emitChildRunFinished,
|
|
230
|
+
emitRunFinished,
|
|
228
231
|
getRunLineageBlock,
|
|
229
232
|
getStepExecutionBlock,
|
|
233
|
+
isTerminalWorkflowRunStatus,
|
|
234
|
+
rowToWorkflowRun,
|
|
235
|
+
rowToWorkflowRunStep,
|
|
236
|
+
WORKFLOW_RUN_SUMMARY_COLUMNS,
|
|
237
|
+
type WorkflowRunRow,
|
|
238
|
+
type WorkflowRunStepRow,
|
|
230
239
|
WorkflowStepNotExecutableError,
|
|
231
240
|
wakeParentsOfDeletedRuns,
|
|
232
241
|
} from "./db/workflow-runs";
|
|
@@ -242,7 +251,7 @@ import {
|
|
|
242
251
|
} from "./swarm-config-guard";
|
|
243
252
|
import { emitTaskStarted } from "./task-lifecycle-events";
|
|
244
253
|
import { publishWorkerConfigNudge, publishWorkerNudge } from "./worker-nudges";
|
|
245
|
-
import {
|
|
254
|
+
import type { WorkflowPayloadView } from "./workflow-replay";
|
|
246
255
|
|
|
247
256
|
export {
|
|
248
257
|
refreshActiveSessionOnActivity,
|
|
@@ -285,7 +294,7 @@ export {
|
|
|
285
294
|
updateAgentStatus,
|
|
286
295
|
updateAgentStatusFromCapacity,
|
|
287
296
|
} from "./db/agents";
|
|
288
|
-
export { recordKeySeatMismatch } from "./db/api-keys";
|
|
297
|
+
export { recordKeySeatMismatch, recordKeyValidation } from "./db/api-keys";
|
|
289
298
|
export {
|
|
290
299
|
type ApprovalRequestSummary,
|
|
291
300
|
type ApprovalVote,
|
|
@@ -2497,6 +2506,8 @@ export async function createTaskExtended(
|
|
|
2497
2506
|
);
|
|
2498
2507
|
}
|
|
2499
2508
|
let requestedByUserIdInherited = false;
|
|
2509
|
+
// The parent key is read again inside the INSERT transaction (see below).
|
|
2510
|
+
let keyInherited = false;
|
|
2500
2511
|
// True only when `options.routingAffinity` ends up populated purely via
|
|
2501
2512
|
// the plain parent-fallback inherit below (child declared no affinity of
|
|
2502
2513
|
// its own, the parent wasn't lead-only, AND the parent's affinity actually
|
|
@@ -2661,6 +2672,7 @@ export async function createTaskExtended(
|
|
|
2661
2672
|
}
|
|
2662
2673
|
if (parent.key && !options.key) {
|
|
2663
2674
|
options.key = parent.key;
|
|
2675
|
+
keyInherited = true;
|
|
2664
2676
|
}
|
|
2665
2677
|
if (parent.contextKey && !options.contextKey) {
|
|
2666
2678
|
options.contextKey = parent.contextKey;
|
|
@@ -2850,7 +2862,18 @@ export async function createTaskExtended(
|
|
|
2850
2862
|
}
|
|
2851
2863
|
|
|
2852
2864
|
const auditUserId = getCurrentRequestUserId() ?? null;
|
|
2853
|
-
const assetKey = normalizeAssetKey(
|
|
2865
|
+
const assetKey = normalizeAssetKey(
|
|
2866
|
+
options?.key ??
|
|
2867
|
+
(options?.parentTaskId
|
|
2868
|
+
? null
|
|
2869
|
+
: sessionSourceKey({
|
|
2870
|
+
id,
|
|
2871
|
+
slackChannelId: options?.slackChannelId,
|
|
2872
|
+
slackThreadTs: options?.slackThreadTs,
|
|
2873
|
+
contextKey: options?.contextKey,
|
|
2874
|
+
})) ??
|
|
2875
|
+
defaultAssetKey("task", id),
|
|
2876
|
+
);
|
|
2854
2877
|
// The Linear tracker dedup guard and the INSERT commit as one unit. Two
|
|
2855
2878
|
// distinct webhook deliveries for the same issue are dispatched
|
|
2856
2879
|
// fire-and-forget (src/linear/webhook.ts), nothing serializes them, and
|
|
@@ -2875,6 +2898,19 @@ export async function createTaskExtended(
|
|
|
2875
2898
|
: await findExistingLinearTrackerContextWork(options?.contextKey);
|
|
2876
2899
|
if (existingTrackerWork) return { existing: existingTrackerWork };
|
|
2877
2900
|
|
|
2901
|
+
// A chain move (a user move, the source key backfill) can commit between
|
|
2902
|
+
// the parent read above and this write lock. Its chain query did not see
|
|
2903
|
+
// this child, so take the parent's key as it is now.
|
|
2904
|
+
const parentKey =
|
|
2905
|
+
keyInherited && options?.parentTaskId
|
|
2906
|
+
? (
|
|
2907
|
+
await getDbClient().get<{ key: string }>('SELECT "key" FROM agent_tasks WHERE id = ?', [
|
|
2908
|
+
options.parentTaskId,
|
|
2909
|
+
])
|
|
2910
|
+
)?.key
|
|
2911
|
+
: undefined;
|
|
2912
|
+
const insertKey = parentKey ? normalizeAssetKey(parentKey) : assetKey;
|
|
2913
|
+
|
|
2878
2914
|
let routingDecisionSnapshot: RoutingDecisionSnapshot | null = null;
|
|
2879
2915
|
try {
|
|
2880
2916
|
routingDecisionSnapshot = await captureRoutingDecisionSnapshot(options);
|
|
@@ -2905,7 +2941,7 @@ export async function createTaskExtended(
|
|
|
2905
2941
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2906
2942
|
[
|
|
2907
2943
|
id,
|
|
2908
|
-
|
|
2944
|
+
insertKey,
|
|
2909
2945
|
options?.agentId ?? null,
|
|
2910
2946
|
options?.creatorAgentId ?? null,
|
|
2911
2947
|
// Free text from every task source; scrub at the single INSERT.
|
|
@@ -7859,53 +7895,6 @@ export async function getWorkflowsByScheduleId(scheduleId: string): Promise<Work
|
|
|
7859
7895
|
// Workflow Run CRUD
|
|
7860
7896
|
// ============================================================================
|
|
7861
7897
|
|
|
7862
|
-
type WorkflowRunRow = {
|
|
7863
|
-
id: string;
|
|
7864
|
-
workflowId: string;
|
|
7865
|
-
status: string;
|
|
7866
|
-
triggerType: string;
|
|
7867
|
-
triggerData: string | null;
|
|
7868
|
-
/** Absent from list rows: see `WORKFLOW_RUN_SUMMARY_COLUMNS`. */
|
|
7869
|
-
context?: string | null;
|
|
7870
|
-
/** Sealed exact `context` (migration 201). Absent from list rows. */
|
|
7871
|
-
context_replay?: string | null;
|
|
7872
|
-
error: string | null;
|
|
7873
|
-
created_by: string | null;
|
|
7874
|
-
startedAt: string;
|
|
7875
|
-
lastUpdatedAt: string;
|
|
7876
|
-
finishedAt: string | null;
|
|
7877
|
-
/** The `sub-workflow` step that started this run (migration 203). */
|
|
7878
|
-
parentStepId?: string | null;
|
|
7879
|
-
/** That step's attempt (`retryCount`) when it started this run (migration 240). */
|
|
7880
|
-
parentStepAttempt?: number | null;
|
|
7881
|
-
};
|
|
7882
|
-
|
|
7883
|
-
/**
|
|
7884
|
-
* `view` picks the `context` copy: `replay` (default) is the exact sealed value
|
|
7885
|
-
* the engine resumes from; `display` is the scrubbed column for API, MCP and UI
|
|
7886
|
-
* responses. See `src/be/workflow-replay.ts`.
|
|
7887
|
-
*/
|
|
7888
|
-
function rowToWorkflowRun(row: WorkflowRunRow, view: WorkflowPayloadView = "replay"): WorkflowRun {
|
|
7889
|
-
const run: WorkflowRun = {
|
|
7890
|
-
id: row.id,
|
|
7891
|
-
workflowId: row.workflowId,
|
|
7892
|
-
status: row.status as WorkflowRunStatus,
|
|
7893
|
-
triggerData: row.triggerData ? JSON.parse(row.triggerData) : undefined,
|
|
7894
|
-
context: undefined,
|
|
7895
|
-
error: row.error ?? undefined,
|
|
7896
|
-
createdBy: row.created_by ?? undefined,
|
|
7897
|
-
startedAt: normalizeDateRequired(row.startedAt),
|
|
7898
|
-
lastUpdatedAt: normalizeDateRequired(row.lastUpdatedAt),
|
|
7899
|
-
finishedAt: normalizeDate(row.finishedAt) ?? undefined,
|
|
7900
|
-
parentStepId: row.parentStepId ?? undefined,
|
|
7901
|
-
parentStepAttempt: row.parentStepId ? (row.parentStepAttempt ?? 0) : undefined,
|
|
7902
|
-
};
|
|
7903
|
-
return defineWorkflowPayload(run, "context", row.context, row.context_replay, view, {
|
|
7904
|
-
runId: row.id,
|
|
7905
|
-
target: "its context",
|
|
7906
|
-
});
|
|
7907
|
-
}
|
|
7908
|
-
|
|
7909
7898
|
export async function createWorkflowRun(data: {
|
|
7910
7899
|
id: string;
|
|
7911
7900
|
workflowId: string;
|
|
@@ -8007,16 +7996,33 @@ export async function updateWorkflowRun(
|
|
|
8007
7996
|
updates.push("lastUpdatedAt = ?");
|
|
8008
7997
|
params.push(new Date().toISOString());
|
|
8009
7998
|
params.push(id);
|
|
8010
|
-
const
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
7999
|
+
const update = () =>
|
|
8000
|
+
getDbClient().get<WorkflowRunRow>(
|
|
8001
|
+
`UPDATE workflow_runs SET ${updates.join(", ")} WHERE id = ? RETURNING *`,
|
|
8002
|
+
params,
|
|
8003
|
+
);
|
|
8004
|
+
const endsRun = data.status !== undefined && isTerminalWorkflowRunStatus(data.status);
|
|
8005
|
+
// A run that ends reads the status it replaces in the same transaction, so
|
|
8006
|
+
// only a change out of a live status announces the end: a later terminal
|
|
8007
|
+
// write, same status or not, does not announce it again.
|
|
8008
|
+
const [previousStatus, row] = endsRun
|
|
8009
|
+
? await getDbClient().transaction(async () => {
|
|
8010
|
+
const previous = await getDbClient().get<{ status: WorkflowRunStatus }>(
|
|
8011
|
+
"SELECT status FROM workflow_runs WHERE id = ?",
|
|
8012
|
+
[id],
|
|
8013
|
+
);
|
|
8014
|
+
return [previous?.status, await update()] as const;
|
|
8015
|
+
})
|
|
8016
|
+
: [undefined, await update()];
|
|
8014
8017
|
if (!row) return null;
|
|
8015
8018
|
const run = rowToWorkflowRun(row);
|
|
8016
8019
|
if (data.status === "completed" || data.status === "failed") {
|
|
8017
8020
|
emitWorkflowTerminalTelemetry(run);
|
|
8018
8021
|
}
|
|
8019
8022
|
if (row.parentStepId) emitChildRunFinished(id, row.parentStepId, run.status);
|
|
8023
|
+
if (endsRun && previousStatus && !isTerminalWorkflowRunStatus(previousStatus)) {
|
|
8024
|
+
emitRunFinished(id, run.workflowId, run.status);
|
|
8025
|
+
}
|
|
8020
8026
|
return run;
|
|
8021
8027
|
}
|
|
8022
8028
|
|
|
@@ -8031,12 +8037,6 @@ export type WorkflowRunListOptions = {
|
|
|
8031
8037
|
includeContext?: boolean;
|
|
8032
8038
|
};
|
|
8033
8039
|
|
|
8034
|
-
// Everything but `context`. `triggerData` stays: it is small, and stored
|
|
8035
|
-
// scripts read it off list rows (dedupe by trigger, failure reports). The
|
|
8036
|
-
// parent link and attempt stay so a list row identifies a child's invocation.
|
|
8037
|
-
const WORKFLOW_RUN_SUMMARY_COLUMNS =
|
|
8038
|
-
"id, workflowId, status, triggerType, triggerData, error, created_by, startedAt, lastUpdatedAt, finishedAt, parentStepId, parentStepAttempt";
|
|
8039
|
-
|
|
8040
8040
|
export type WorkflowRunPage = {
|
|
8041
8041
|
runs: WorkflowRun[];
|
|
8042
8042
|
page: {
|
|
@@ -8121,56 +8121,6 @@ export async function listWorkflowRunsPage(
|
|
|
8121
8121
|
// Workflow Run Step CRUD
|
|
8122
8122
|
// ============================================================================
|
|
8123
8123
|
|
|
8124
|
-
type WorkflowRunStepRow = {
|
|
8125
|
-
id: string;
|
|
8126
|
-
runId: string;
|
|
8127
|
-
nodeId: string;
|
|
8128
|
-
nodeType: string;
|
|
8129
|
-
status: string;
|
|
8130
|
-
input: string | null;
|
|
8131
|
-
output: string | null;
|
|
8132
|
-
/** Sealed exact `output` (migration 201). */
|
|
8133
|
-
output_replay: string | null;
|
|
8134
|
-
error: string | null;
|
|
8135
|
-
startedAt: string;
|
|
8136
|
-
finishedAt: string | null;
|
|
8137
|
-
retryCount: number;
|
|
8138
|
-
maxRetries: number;
|
|
8139
|
-
nextRetryAt: string | null;
|
|
8140
|
-
idempotencyKey: string | null;
|
|
8141
|
-
diagnostics: string | null;
|
|
8142
|
-
nextPort: string | null;
|
|
8143
|
-
};
|
|
8144
|
-
|
|
8145
|
-
/** `view` picks the `output` copy, as in `rowToWorkflowRun`. */
|
|
8146
|
-
function rowToWorkflowRunStep(
|
|
8147
|
-
row: WorkflowRunStepRow,
|
|
8148
|
-
view: WorkflowPayloadView = "replay",
|
|
8149
|
-
): WorkflowRunStep {
|
|
8150
|
-
const step: WorkflowRunStep = {
|
|
8151
|
-
id: row.id,
|
|
8152
|
-
runId: row.runId,
|
|
8153
|
-
nodeId: row.nodeId,
|
|
8154
|
-
nodeType: row.nodeType,
|
|
8155
|
-
status: row.status as WorkflowRunStepStatus,
|
|
8156
|
-
input: row.input ? JSON.parse(row.input) : undefined,
|
|
8157
|
-
output: undefined,
|
|
8158
|
-
error: row.error ?? undefined,
|
|
8159
|
-
startedAt: normalizeDateRequired(row.startedAt),
|
|
8160
|
-
finishedAt: normalizeDate(row.finishedAt) ?? undefined,
|
|
8161
|
-
retryCount: row.retryCount,
|
|
8162
|
-
maxRetries: row.maxRetries,
|
|
8163
|
-
nextRetryAt: normalizeDate(row.nextRetryAt) ?? undefined,
|
|
8164
|
-
idempotencyKey: row.idempotencyKey ?? undefined,
|
|
8165
|
-
diagnostics: row.diagnostics ?? undefined,
|
|
8166
|
-
nextPort: row.nextPort ?? undefined,
|
|
8167
|
-
};
|
|
8168
|
-
return defineWorkflowPayload(step, "output", row.output, row.output_replay, view, {
|
|
8169
|
-
runId: row.runId,
|
|
8170
|
-
target: `the output of step ${row.id} (node ${row.nodeId})`,
|
|
8171
|
-
});
|
|
8172
|
-
}
|
|
8173
|
-
|
|
8174
8124
|
export async function createWorkflowRunStep(data: {
|
|
8175
8125
|
id: string;
|
|
8176
8126
|
runId: string;
|
|
@@ -8284,7 +8234,7 @@ export async function getWorkflowRunStepsByRunId(
|
|
|
8284
8234
|
view: WorkflowPayloadView = "replay",
|
|
8285
8235
|
): Promise<WorkflowRunStep[]> {
|
|
8286
8236
|
const rows = await getDbClient().query<WorkflowRunStepRow>(
|
|
8287
|
-
"SELECT * FROM workflow_run_steps WHERE runId = ? ORDER BY startedAt ASC",
|
|
8237
|
+
"SELECT * FROM workflow_run_steps WHERE runId = ? ORDER BY startedAt ASC, rowid ASC",
|
|
8288
8238
|
[runId],
|
|
8289
8239
|
);
|
|
8290
8240
|
return rows.map((row) => rowToWorkflowRunStep(row, view));
|
|
@@ -8400,7 +8350,7 @@ export async function getLatestStepForNode(
|
|
|
8400
8350
|
nodeId: string,
|
|
8401
8351
|
): Promise<WorkflowRunStep | null> {
|
|
8402
8352
|
const row = await getDbClient().get<WorkflowRunStepRow>(
|
|
8403
|
-
"SELECT * FROM workflow_run_steps WHERE runId = ? AND nodeId = ? ORDER BY startedAt DESC LIMIT 1",
|
|
8353
|
+
"SELECT * FROM workflow_run_steps WHERE runId = ? AND nodeId = ? ORDER BY startedAt DESC, rowid DESC LIMIT 1",
|
|
8404
8354
|
[runId, nodeId],
|
|
8405
8355
|
);
|
|
8406
8356
|
return row ? rowToWorkflowRunStep(row) : null;
|
|
@@ -11742,6 +11692,8 @@ export interface ApiKeyStatus {
|
|
|
11742
11692
|
/** Auth failures in a row since the last success or clear. */
|
|
11743
11693
|
consecutiveAuthFailures: number;
|
|
11744
11694
|
lastAuthFailureAt: string | null;
|
|
11695
|
+
lastValidationError: string | null;
|
|
11696
|
+
lastValidationFailedAt: string | null;
|
|
11745
11697
|
createdAt: string;
|
|
11746
11698
|
updatedAt: string;
|
|
11747
11699
|
}
|
|
@@ -11767,6 +11719,7 @@ function rowToApiKeyStatus(row: ApiKeyStatusRow): ApiKeyStatus {
|
|
|
11767
11719
|
|
|
11768
11720
|
export interface AvailableKeyIndicesResult {
|
|
11769
11721
|
availableIndices: number[];
|
|
11722
|
+
rejectedIndices: number[];
|
|
11770
11723
|
/** Indices excluded only by an active model-scoped window block (not key-wide). */
|
|
11771
11724
|
modelBlockedIndices: number[];
|
|
11772
11725
|
/** ISO of the earliest resetsAt among modelBlockedIndices, or null when none. */
|
|
@@ -11787,6 +11740,14 @@ export interface AvailableKeyIndicesResult {
|
|
|
11787
11740
|
* whose `plan` cannot run the family (`planAllowsModelFamily`) is excluded
|
|
11788
11741
|
* and reported in `seatBlockedIndices`, also when it is key-wide or
|
|
11789
11742
|
* model-window blocked; a seat block has no reset time.
|
|
11743
|
+
*
|
|
11744
|
+
* With `keySuffixes` (the caller's own pool, in order), rows are matched by
|
|
11745
|
+
* key suffix instead of the stored `keyIndex`, across every scope: a key has
|
|
11746
|
+
* one state wherever it is pooled, and a key at the same index in another
|
|
11747
|
+
* agent's pool is never blocked by it. A suffix listed more than once is read
|
|
11748
|
+
* by `keyIndex` in `scope`/`scopeId`, as without the list. Returned indices
|
|
11749
|
+
* are positions in `keySuffixes`. Without it, rows of `scope`/`scopeId` are
|
|
11750
|
+
* read by `keyIndex`.
|
|
11790
11751
|
*/
|
|
11791
11752
|
export async function getAvailableKeyIndices(
|
|
11792
11753
|
keyType: string,
|
|
@@ -11794,36 +11755,60 @@ export async function getAvailableKeyIndices(
|
|
|
11794
11755
|
scope = "global",
|
|
11795
11756
|
scopeId: string | null = null,
|
|
11796
11757
|
modelFamily?: ModelFamily,
|
|
11758
|
+
keySuffixes?: string[],
|
|
11797
11759
|
): Promise<AvailableKeyIndicesResult> {
|
|
11798
11760
|
const now = new Date().toISOString();
|
|
11799
11761
|
const nowMs = Date.now();
|
|
11800
11762
|
const client = getDbClient();
|
|
11801
11763
|
const effectiveScopeId = scopeId ?? "";
|
|
11764
|
+
const suffixes = keySuffixes?.length ? keySuffixes : undefined;
|
|
11765
|
+
const rowFilter = suffixes
|
|
11766
|
+
? `keyType = ? AND keySuffix IN (${suffixes.map(() => "?").join(", ")})`
|
|
11767
|
+
: "keyType = ? AND scope = ? AND scopeId = ?";
|
|
11768
|
+
const rowParams = suffixes ? [keyType, ...suffixes] : [keyType, scope, effectiveScopeId];
|
|
11802
11769
|
|
|
11803
11770
|
// Auto-clear expired rate limits. Never an auth bench: only `clearAuthBench` lifts it.
|
|
11804
11771
|
await client.run(
|
|
11805
11772
|
`UPDATE api_key_status
|
|
11806
11773
|
SET status = 'available', rateLimitedUntil = NULL, updatedAt = ?
|
|
11807
|
-
WHERE
|
|
11774
|
+
WHERE ${rowFilter}
|
|
11808
11775
|
AND status = 'rate_limited' AND rateLimitedUntil <= ? AND consecutiveAuthFailures < ?`,
|
|
11809
|
-
[now,
|
|
11776
|
+
[now, ...rowParams, now, CODEX_AUTH_FAILURE_BENCH_THRESHOLD],
|
|
11810
11777
|
);
|
|
11811
11778
|
|
|
11812
11779
|
const rows = await client.query<{
|
|
11813
11780
|
keyIndex: number;
|
|
11781
|
+
keySuffix: string;
|
|
11782
|
+
scope: string;
|
|
11783
|
+
scopeId: string;
|
|
11814
11784
|
status: string;
|
|
11815
11785
|
rateLimitWindows: string | null;
|
|
11816
11786
|
plan: string | null;
|
|
11817
11787
|
authFailureSeq: number;
|
|
11788
|
+
lastValidationFailedAt: string | null;
|
|
11818
11789
|
}>(
|
|
11819
|
-
`SELECT keyIndex, status, rateLimitWindows, plan, authFailureSeq FROM api_key_status
|
|
11820
|
-
WHERE
|
|
11821
|
-
|
|
11822
|
-
);
|
|
11790
|
+
`SELECT keyIndex, keySuffix, scope, scopeId, status, rateLimitWindows, plan, authFailureSeq, lastValidationFailedAt FROM api_key_status
|
|
11791
|
+
WHERE ${rowFilter}`,
|
|
11792
|
+
rowParams,
|
|
11793
|
+
);
|
|
11794
|
+
// Pool positions a row applies to: the position holding its suffix, or its stored index.
|
|
11795
|
+
// A suffix held by several pool members cannot tell them apart, so it falls back to
|
|
11796
|
+
// the index read: only a row of the requested scope, at its stored index.
|
|
11797
|
+
const positionsOf = (row: (typeof rows)[number]): number[] => {
|
|
11798
|
+
if (!suffixes) return [row.keyIndex];
|
|
11799
|
+
const positions = suffixes.flatMap((suffix, i) => (suffix === row.keySuffix ? [i] : []));
|
|
11800
|
+
if (positions.length < 2) return positions;
|
|
11801
|
+
const sameScope = row.scope === scope && row.scopeId === effectiveScopeId;
|
|
11802
|
+
return sameScope && positions.includes(row.keyIndex) ? [row.keyIndex] : [];
|
|
11803
|
+
};
|
|
11823
11804
|
|
|
11824
11805
|
const blockedIndices = new Set(
|
|
11825
|
-
rows.filter((r) => r.status === "rate_limited").
|
|
11806
|
+
rows.filter((r) => r.status === "rate_limited").flatMap(positionsOf),
|
|
11826
11807
|
);
|
|
11808
|
+
const rejectedIndices = [
|
|
11809
|
+
...new Set(rows.filter((row) => row.lastValidationFailedAt !== null).flatMap(positionsOf)),
|
|
11810
|
+
];
|
|
11811
|
+
for (const index of rejectedIndices) blockedIndices.add(index);
|
|
11827
11812
|
|
|
11828
11813
|
const modelBlockedIndices: number[] = [];
|
|
11829
11814
|
let earliestModelResetsAtSec: number | undefined;
|
|
@@ -11832,14 +11817,17 @@ export async function getAvailableKeyIndices(
|
|
|
11832
11817
|
// A key already blocked key-wide doesn't need a separate model-block
|
|
11833
11818
|
// entry — it's excluded from availableIndices either way, and
|
|
11834
11819
|
// modelBlockedIndices means "excluded only by a model-scoped block".
|
|
11835
|
-
|
|
11820
|
+
const positions = positionsOf(row).filter(
|
|
11821
|
+
(i) => !blockedIndices.has(i) && !modelBlockedIndices.includes(i),
|
|
11822
|
+
);
|
|
11823
|
+
if (positions.length === 0) continue;
|
|
11836
11824
|
const block = activeModelBlock(
|
|
11837
11825
|
parseRateLimitWindowsJson(row.rateLimitWindows),
|
|
11838
11826
|
modelFamily,
|
|
11839
11827
|
nowMs,
|
|
11840
11828
|
);
|
|
11841
11829
|
if (!block) continue;
|
|
11842
|
-
modelBlockedIndices.push(
|
|
11830
|
+
modelBlockedIndices.push(...positions);
|
|
11843
11831
|
if (earliestModelResetsAtSec === undefined || block.resetsAt < earliestModelResetsAtSec) {
|
|
11844
11832
|
earliestModelResetsAtSec = block.resetsAt;
|
|
11845
11833
|
}
|
|
@@ -11853,7 +11841,10 @@ export async function getAvailableKeyIndices(
|
|
|
11853
11841
|
const seatBlockedIndices: number[] = [];
|
|
11854
11842
|
if (modelFamily) {
|
|
11855
11843
|
for (const row of rows) {
|
|
11856
|
-
if (
|
|
11844
|
+
if (planAllowsModelFamily(row.plan, modelFamily)) continue;
|
|
11845
|
+
for (const i of positionsOf(row)) {
|
|
11846
|
+
if (!seatBlockedIndices.includes(i)) seatBlockedIndices.push(i);
|
|
11847
|
+
}
|
|
11857
11848
|
}
|
|
11858
11849
|
}
|
|
11859
11850
|
const seatBlockedSet = new Set(seatBlockedIndices);
|
|
@@ -11866,6 +11857,7 @@ export async function getAvailableKeyIndices(
|
|
|
11866
11857
|
|
|
11867
11858
|
return {
|
|
11868
11859
|
availableIndices,
|
|
11860
|
+
rejectedIndices,
|
|
11869
11861
|
modelBlockedIndices,
|
|
11870
11862
|
seatBlockedIndices,
|
|
11871
11863
|
earliestModelResetAt:
|
|
@@ -12201,22 +12193,30 @@ export async function clearKeyRateLimit(
|
|
|
12201
12193
|
const effectiveScopeId = scopeId ?? "";
|
|
12202
12194
|
const { keyIndex = -1, authFence = null } = opts;
|
|
12203
12195
|
const clearAuthBench = opts.clearAuthBench === true && authFence !== null;
|
|
12204
|
-
const key = [keyType, keySuffix, scope, effectiveScopeId];
|
|
12205
12196
|
return await getDbClient().transaction(async (tx) => {
|
|
12197
|
+
// The key's rows in every scope: `getAvailableKeyIndices` reads them all by suffix.
|
|
12206
12198
|
const result = await tx.run(
|
|
12207
12199
|
`UPDATE api_key_status
|
|
12208
12200
|
SET status = 'available', rateLimitedUntil = NULL, updatedAt = ?
|
|
12209
|
-
WHERE keyType = ? AND keySuffix = ?
|
|
12201
|
+
WHERE keyType = ? AND keySuffix = ?
|
|
12210
12202
|
AND status = 'rate_limited' AND ((? AND authFailureSeq <= ?) OR consecutiveAuthFailures < ?)`,
|
|
12211
|
-
[
|
|
12203
|
+
[
|
|
12204
|
+
now,
|
|
12205
|
+
keyType,
|
|
12206
|
+
keySuffix,
|
|
12207
|
+
clearAuthBench ? 1 : 0,
|
|
12208
|
+
authFence,
|
|
12209
|
+
CODEX_AUTH_FAILURE_BENCH_THRESHOLD,
|
|
12210
|
+
],
|
|
12212
12211
|
);
|
|
12213
12212
|
const cleared = result.changes > 0;
|
|
12214
12213
|
if (!clearAuthBench) return cleared;
|
|
12215
12214
|
const lifted = await tx.query<{ keySuffix: string }>(
|
|
12216
12215
|
`UPDATE api_key_status SET consecutiveAuthFailures = 0, updatedAt = ?1,
|
|
12217
12216
|
status = CASE WHEN keySuffix = ?2 THEN status ELSE 'available' END, rateLimitedUntil = CASE WHEN keySuffix = ?2 THEN rateLimitedUntil END
|
|
12218
|
-
WHERE keyType = ?3 AND
|
|
12219
|
-
AND ((keySuffix = ?2 AND authFailureSeq <= ?7)
|
|
12217
|
+
WHERE keyType = ?3 AND (consecutiveAuthFailures > 0 OR status = 'rate_limited')
|
|
12218
|
+
AND ((keySuffix = ?2 AND authFailureSeq <= ?7)
|
|
12219
|
+
OR (keySuffix != ?2 AND keyIndex = ?6 AND scope = ?4 AND scopeId = ?5)) RETURNING keySuffix`,
|
|
12220
12220
|
[now, keySuffix, keyType, scope, effectiveScopeId, keyIndex, authFence],
|
|
12221
12221
|
);
|
|
12222
12222
|
const retired = lifted.filter((row) => row.keySuffix !== keySuffix);
|
|
@@ -12997,6 +12997,7 @@ export async function listRecentSessions(
|
|
|
12997
12997
|
opts: ListRecentSessionsOpts = {},
|
|
12998
12998
|
): Promise<SessionListItem[] | SessionListItemSummary[]> {
|
|
12999
12999
|
const query = sessionSummaryQuery(opts);
|
|
13000
|
+
const threadRoots = threadRootsSql();
|
|
13000
13001
|
const rootRows = await getDbClient().query<
|
|
13001
13002
|
AgentTaskRow & {
|
|
13002
13003
|
__chainCount: number;
|
|
@@ -13005,15 +13006,17 @@ export async function listRecentSessions(
|
|
|
13005
13006
|
__readState: SessionReadState | null;
|
|
13006
13007
|
__snoozeUntil: string | null;
|
|
13007
13008
|
__needsYou: number;
|
|
13009
|
+
__threadRoots: number;
|
|
13008
13010
|
}
|
|
13009
13011
|
>(
|
|
13010
13012
|
`SELECT r.*, a.chainCount AS __chainCount, a.lastActivityAt AS __lastActivityAt,
|
|
13011
13013
|
a.latestStatus AS __latestStatus, a.readState AS __readState,
|
|
13012
|
-
a.snoozeUntil AS __snoozeUntil, a.needsYou AS __needsYou
|
|
13014
|
+
a.snoozeUntil AS __snoozeUntil, a.needsYou AS __needsYou,
|
|
13015
|
+
${threadRoots.sql} AS __threadRoots
|
|
13013
13016
|
FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id
|
|
13014
13017
|
ORDER BY ${opts.triage === "later" ? "a.snoozeUntil ASC," : ""}
|
|
13015
13018
|
a.lastActivityAt DESC, r.createdAt DESC, r.id DESC LIMIT ? OFFSET ?`,
|
|
13016
|
-
[...query.params, opts.limit ?? 25, opts.offset ?? 0],
|
|
13019
|
+
[...threadRoots.params, ...query.params, opts.limit ?? 25, opts.offset ?? 0],
|
|
13017
13020
|
);
|
|
13018
13021
|
const fields = await sessionPageFields(
|
|
13019
13022
|
rootRows.map((row) => ({
|
|
@@ -13029,7 +13032,7 @@ export async function listRecentSessions(
|
|
|
13029
13032
|
...(row.__readState ? { readState: row.__readState } : {}),
|
|
13030
13033
|
snoozeUntil: row.__snoozeUntil,
|
|
13031
13034
|
needsYou: !!row.__needsYou,
|
|
13032
|
-
groupKey:
|
|
13035
|
+
groupKey: sessionThreadGroupKey(row.key, row.__threadRoots),
|
|
13033
13036
|
...fields.get(row.id),
|
|
13034
13037
|
}));
|
|
13035
13038
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
+
import { selectCredential } from "../../../utils/credentials";
|
|
2
3
|
import { getOpenRouterAttributionHeaders } from "../../../utils/openrouter-base-url";
|
|
3
4
|
import { DEFAULT_EMBEDDING_MODEL, EMBEDDING_DIMENSIONS } from "../constants";
|
|
4
5
|
import type { EmbeddingProvider } from "../types";
|
|
@@ -46,7 +47,10 @@ export class OpenAIEmbeddingProvider implements EmbeddingProvider {
|
|
|
46
47
|
private readonly apiKey: string | undefined;
|
|
47
48
|
|
|
48
49
|
constructor(config?: OpenAIEmbeddingConfig) {
|
|
49
|
-
|
|
50
|
+
const apiKey = config?.apiKey ?? process.env.EMBEDDING_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
51
|
+
this.apiKey = apiKey
|
|
52
|
+
? selectCredential(apiKey, undefined, "EMBEDDING_API_KEY").selected
|
|
53
|
+
: undefined;
|
|
50
54
|
|
|
51
55
|
this.model = config?.model ?? process.env.EMBEDDING_MODEL ?? "text-embedding-3-small";
|
|
52
56
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learnings captured through `store-progress`: one swarm-scope `manual` memory
|
|
3
|
+
* per learning, keyed `/learnings/<workstream>/<slug>` and tied to its task by
|
|
4
|
+
* `sourceTaskId`, so "what did we learn doing X" is one `keyPrefix` search.
|
|
5
|
+
*
|
|
6
|
+
* Source stays `manual` so nightly curation (memtax B1 reads only manual and
|
|
7
|
+
* file_index rows) picks learnings up without a new source value.
|
|
8
|
+
*/
|
|
9
|
+
import { getDbClient } from "@/be/db";
|
|
10
|
+
import { indexMemoryContent } from "@/be/memory/index-content";
|
|
11
|
+
import { contentSha256 } from "@/commands/profile-sync";
|
|
12
|
+
import type { AgentTask } from "@/types";
|
|
13
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
14
|
+
|
|
15
|
+
/** ~500 tokens at ceil(chars / 4); also keeps a learning to one memory chunk. */
|
|
16
|
+
export const LEARNING_MAX_CHARS = 2000;
|
|
17
|
+
export const LEARNING_MAX_TAGS = 5;
|
|
18
|
+
const LEARNINGS_ROOT = "/learnings";
|
|
19
|
+
const SEGMENT_MAX_CHARS = 60;
|
|
20
|
+
const KEY_ATTEMPTS = 4;
|
|
21
|
+
|
|
22
|
+
export type TaskLearningInput = string | { text: string; workstream?: string; tags?: string[] };
|
|
23
|
+
|
|
24
|
+
export type TaskLearningResult =
|
|
25
|
+
| { status: "stored"; memoryId: string; key: string; workstream: string }
|
|
26
|
+
| { status: "replayed"; memoryId: string; key: string }
|
|
27
|
+
| { status: "failed"; error: string };
|
|
28
|
+
|
|
29
|
+
/** Lowercase kebab slug: `Agent Swarm!` -> `agent-swarm`. Empty when nothing usable is left. */
|
|
30
|
+
export function toKebab(value: string, maxChars = SEGMENT_MAX_CHARS): string {
|
|
31
|
+
return value
|
|
32
|
+
.normalize("NFKD")
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
35
|
+
.replace(/^-+/, "")
|
|
36
|
+
.slice(0, maxChars)
|
|
37
|
+
.replace(/-+$/, "");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Explicit workstream, else the repo name from `vcsRepo` (`owner/name` or a URL), else `general`. */
|
|
41
|
+
export function resolveLearningWorkstream(
|
|
42
|
+
explicit: string | undefined,
|
|
43
|
+
vcsRepo: string | undefined,
|
|
44
|
+
): string {
|
|
45
|
+
const fromExplicit = explicit ? toKebab(explicit) : "";
|
|
46
|
+
if (fromExplicit) return fromExplicit;
|
|
47
|
+
const repoName = vcsRepo
|
|
48
|
+
?.trim()
|
|
49
|
+
.replace(/\/+$/, "")
|
|
50
|
+
.split("/")
|
|
51
|
+
.pop()
|
|
52
|
+
?.replace(/\.git$/, "");
|
|
53
|
+
const fromRepo = repoName ? toKebab(repoName) : "";
|
|
54
|
+
return fromRepo || "general";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function storeTaskLearning(params: {
|
|
58
|
+
agentId: string;
|
|
59
|
+
isLead: boolean;
|
|
60
|
+
task: Pick<AgentTask, "id" | "vcsRepo">;
|
|
61
|
+
learning: TaskLearningInput;
|
|
62
|
+
}): Promise<TaskLearningResult> {
|
|
63
|
+
const { agentId, isLead, task } = params;
|
|
64
|
+
const input = typeof params.learning === "string" ? { text: params.learning } : params.learning;
|
|
65
|
+
// Scrub before anything is derived: a token in the title, workstream or a tag
|
|
66
|
+
// would otherwise survive (kebab-cased) in the key and tags, and the replay
|
|
67
|
+
// hash must match the hash of the scrubbed content the store persists.
|
|
68
|
+
const text = scrubSecrets(input.text).trim();
|
|
69
|
+
if (!text) return { status: "failed", error: "learning text is empty" };
|
|
70
|
+
if (text.length > LEARNING_MAX_CHARS) {
|
|
71
|
+
return {
|
|
72
|
+
status: "failed",
|
|
73
|
+
error: `learning text is over ~500 tokens (${LEARNING_MAX_CHARS} characters); shorten it`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const tags = [
|
|
77
|
+
...new Set((input.tags ?? []).map((tag) => toKebab(scrubSecrets(tag))).filter(Boolean)),
|
|
78
|
+
].filter((tag) => tag !== "learning");
|
|
79
|
+
if (tags.length > LEARNING_MAX_TAGS) {
|
|
80
|
+
return { status: "failed", error: `learning takes at most ${LEARNING_MAX_TAGS} tags` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const workstream = resolveLearningWorkstream(
|
|
84
|
+
input.workstream === undefined ? undefined : scrubSecrets(input.workstream),
|
|
85
|
+
task.vcsRepo === undefined ? undefined : scrubSecrets(task.vcsRepo),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// One write transaction for the replay check and the insert, so two
|
|
90
|
+
// overlapping calls with the same learning store it once.
|
|
91
|
+
return await getDbClient().transaction(() =>
|
|
92
|
+
insertLearning({ agentId, isLead, taskId: task.id, text, tags, workstream }),
|
|
93
|
+
);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return {
|
|
96
|
+
status: "failed",
|
|
97
|
+
error: scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function insertLearning(params: {
|
|
103
|
+
agentId: string;
|
|
104
|
+
isLead: boolean;
|
|
105
|
+
taskId: string;
|
|
106
|
+
text: string;
|
|
107
|
+
tags: string[];
|
|
108
|
+
workstream: string;
|
|
109
|
+
}): Promise<TaskLearningResult> {
|
|
110
|
+
const { agentId, isLead, taskId, text, tags, workstream } = params;
|
|
111
|
+
// indexMemoryContent stores text this short as one trimmed chunk, so this
|
|
112
|
+
// hash is the row's contentHash: a replayed call finds its own earlier write.
|
|
113
|
+
const contentHash = contentSha256(text);
|
|
114
|
+
const replay = await getDbClient().get<{ id: string; key: string }>(
|
|
115
|
+
`SELECT id, key FROM agent_memory
|
|
116
|
+
WHERE sourceTaskId = ? AND contentHash = ? AND key LIKE '${LEARNINGS_ROOT}/%'
|
|
117
|
+
LIMIT 1`,
|
|
118
|
+
[taskId, contentHash],
|
|
119
|
+
);
|
|
120
|
+
if (replay) return { status: "replayed", memoryId: replay.id, key: replay.key };
|
|
121
|
+
|
|
122
|
+
const title = text.split(/\r?\n/, 1)[0]!.trim().slice(0, 200);
|
|
123
|
+
const baseKey = `${LEARNINGS_ROOT}/${workstream}/${toKebab(title) || "learning"}`;
|
|
124
|
+
|
|
125
|
+
// Keys are unique per owner and a reused key fails rather than upserts.
|
|
126
|
+
// Keep the path unique across the swarm: on a taken key, add a short suffix.
|
|
127
|
+
for (let attempt = 0; attempt < KEY_ATTEMPTS; attempt++) {
|
|
128
|
+
const key = attempt === 0 ? baseKey : `${baseKey}-${crypto.randomUUID().slice(0, 6)}`;
|
|
129
|
+
const taken = await getDbClient().get<{ id: string }>(
|
|
130
|
+
"SELECT id FROM agent_memory WHERE scope = 'swarm' AND key = ? LIMIT 1",
|
|
131
|
+
[key],
|
|
132
|
+
);
|
|
133
|
+
if (taken) continue;
|
|
134
|
+
try {
|
|
135
|
+
const result = await indexMemoryContent({
|
|
136
|
+
agentId,
|
|
137
|
+
content: text,
|
|
138
|
+
name: title,
|
|
139
|
+
scope: "swarm",
|
|
140
|
+
source: "manual",
|
|
141
|
+
sourceTaskId: taskId,
|
|
142
|
+
tags: ["learning", ...tags],
|
|
143
|
+
intent: "store-progress learning",
|
|
144
|
+
key,
|
|
145
|
+
writer: { principal: { kind: "agent", agentId, isLead }, source: "mcp" },
|
|
146
|
+
});
|
|
147
|
+
return { status: "stored", memoryId: result.memoryIds[0]!, key, workstream };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
// The key is taken after all: try a suffixed one.
|
|
150
|
+
if (/UNIQUE constraint failed/i.test((err as Error).message)) continue;
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return { status: "failed", error: `no free key under ${baseKey}` };
|
|
155
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
-- Last provider rejection of a pooled key by a worker live test (HTTP 401).
|
|
2
|
+
-- A key with lastValidationFailedAt set is left out of /api/keys/available.
|
|
3
|
+
ALTER TABLE api_key_status ADD COLUMN lastValidationError TEXT;
|
|
4
|
+
ALTER TABLE api_key_status ADD COLUMN lastValidationFailedAt TEXT;
|