@desplega.ai/agent-swarm 1.158.0 → 1.160.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 +1 -1
- package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-2mjwcsfh.js} +124 -27
- package/dist/{actions-p9hz5yat.js → actions-e43rxskr.js} +35 -30
- package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-5qx80j9k.js} +106 -16
- package/dist/{app-g26wdcz6.js → app-mc7xfagn.js} +12 -10
- package/dist/{assistant-wydfqkb3.js → assistant-sedkzv3b.js} +42 -31
- package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-nfb21z9a.js} +13 -14
- package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-cqz9swn5.js} +13 -12
- package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-m3656j2n.js} +14 -13
- package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-xbxf5d7w.js} +11 -9
- package/dist/boot-seeds-gsetbs5y.js +28 -0
- package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-dc6289mc.js} +28 -23
- package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-naaw51me.js} +4 -3
- package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-eacvchx0.js} +12 -3
- package/dist/claude-sdk-session-bqp2xvym.js +29722 -0
- package/dist/{cli-987hg0aa.js → cli-0jwe9034.js} +2 -2
- package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
- package/dist/{cli-c07r9a2m.js → cli-0q3avq3s.js} +12 -11
- package/dist/{cli-19jenss3.js → cli-0wgxmwf6.js} +31 -10
- package/dist/cli-0x01kwrd.js +491 -0
- package/dist/cli-0xqde53b.js +176 -0
- package/dist/cli-10nhj6xq.js +8355 -0
- package/dist/{cli-tt9k8r35.js → cli-12tszpk9.js} +479 -910
- package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
- package/dist/{cli-cqt19wg6.js → cli-1a2j50m0.js} +11 -2
- package/dist/{cli-xberxa1r.js → cli-25fcq4b4.js} +2 -2
- package/dist/{cli-npssxgaz.js → cli-2pdaysab.js} +52 -2
- package/dist/cli-3qhyh31j.js +166 -0
- package/dist/cli-447dt1h3.js +3828 -0
- package/dist/cli-4derpgyk.js +115 -0
- package/dist/{cli-00w2c66p.js → cli-523679eh.js} +13 -2
- package/dist/{cli-y95wp3tg.js → cli-58e8yr0d.js} +1 -1
- package/dist/{cli-24k806sc.js → cli-5nbmpy8w.js} +3 -3
- package/dist/{cli-53sz8gkz.js → cli-6cs6wzxt.js} +1 -1
- package/dist/cli-741yb0xx.js +1027 -0
- package/dist/cli-74h9hscv.js +69 -0
- package/dist/cli-7cxw8b60.js +14910 -0
- package/dist/{cli-xhaeg2nc.js → cli-7e398j1h.js} +1 -1
- package/dist/cli-7kyk9jr3.js +22 -0
- package/dist/cli-89s4ngw3.js +183 -0
- package/dist/{cli-9b13ggd1.js → cli-89v6kyq7.js} +1 -0
- package/dist/{cli-ftfvggzx.js → cli-9hsgwz4w.js} +1045 -581
- package/dist/{cli-rqz8qbw7.js → cli-a8dvhzrt.js} +59 -20
- package/dist/{cli-j57qkrpq.js → cli-azhgzetg.js} +395 -56
- package/dist/cli-br0qdrnp.js +2150 -0
- package/dist/{cli-rj19hc34.js → cli-btfv8y88.js} +1 -1
- package/dist/{cli-8qmme6vd.js → cli-c8a36vcc.js} +2 -2
- package/dist/cli-cpvsaqsw.js +333 -0
- package/dist/cli-d5czme6m.js +17805 -0
- package/dist/{cli-h38x0qhg.js → cli-et8m9xsv.js} +4 -3
- package/dist/{cli-x24pshtq.js → cli-evvtk35y.js} +6 -6
- package/dist/{cli-7s3syk22.js → cli-f8gy6r2r.js} +5 -5
- package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
- package/dist/{cli-9httdpyq.js → cli-g2whhdrf.js} +1 -1
- package/dist/cli-gt4hxb8p.js +99 -0
- package/dist/{cli-s8eet254.js → cli-jajnsm8q.js} +37184 -53838
- package/dist/{cli-7gtvvpbh.js → cli-jayjpjmb.js} +21 -12
- package/dist/{cli-68w09sjw.js → cli-jyk0ynw2.js} +27 -89
- package/dist/{cli-aehm1rbq.js → cli-jz4w45qt.js} +1152 -61
- package/dist/cli-kmcpfwn2.js +333856 -0
- package/dist/{cli-4198qc9v.js → cli-ktmyrtpn.js} +19 -3
- package/dist/{cli-j55n64b6.js → cli-mfe5btp9.js} +505 -189
- package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
- package/dist/{cli-1hkrch3k.js → cli-pm1rst0h.js} +46 -7
- package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
- package/dist/{cli-ggm92wcn.js → cli-ptr8h7kp.js} +20 -1
- package/dist/{cli-s8s499xy.js → cli-qbksc292.js} +3 -2
- package/dist/{cli-47m6dkd3.js → cli-qydf22qq.js} +597 -346
- package/dist/{cli-3gmcwfyx.js → cli-r1mxd1xh.js} +13 -6
- package/dist/cli-rb9m6rpe.js +4980 -0
- package/dist/cli-rykfmgwg.js +102 -0
- package/dist/{cli-5d10vd2m.js → cli-shkxm1vn.js} +6 -5
- package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
- package/dist/cli-tfky2838.js +756 -0
- package/dist/{cli-g8djp6qe.js → cli-tmqt7dpj.js} +40 -0
- package/dist/{cli-fgqxwrpv.js → cli-tpq83z89.js} +1631 -127
- package/dist/cli-trc24wk2.js +143 -0
- package/dist/{cli-aky8tctm.js → cli-tzx2ke1v.js} +3 -3
- package/dist/cli-v7fn5gms.js +1425 -0
- package/dist/{cli-wscdvcqz.js → cli-vctdqs5h.js} +1 -1
- package/dist/{cli-cw9zck8j.js → cli-xjcywfsf.js} +8 -4
- package/dist/{cli-k0g1sjzm.js → cli-ybgv4kw2.js} +18 -11
- package/dist/{cli-rjehvrts.js → cli-ydmcgve1.js} +8 -8
- package/dist/{cli-vhjwh6cz.js → cli-ygwm91nt.js} +1 -1
- package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
- package/dist/{cli-de1396j2.js → cli-ywybs5as.js} +7 -1
- package/dist/cli-zpep9q5e.js +491 -0
- package/dist/cli.js +41 -32
- package/dist/cloudflare-workers-ai-system-one-k6fbxw9d.js +46 -0
- package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-nmxcatww.js} +26 -17
- package/dist/{codex-hook-9hptmdhj.js → codex-hook-20me6byw.js} +2 -2
- package/dist/{codex-login-x3jk4etn.js → codex-login-w6fz4xbz.js} +50 -5
- package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-90aa80ev.js} +24 -17
- package/dist/{commands-e3bn0p75.js → commands-8wcq1kn7.js} +11 -9
- package/dist/{db-03v9kzr2.js → db-j40kc4se.js} +25 -11
- package/dist/{devin-adapter-8vgkwh29.js → devin-adapter-dpsv6hd9.js} +1 -1
- package/dist/{dispatcher-nrmg8yht.js → dispatcher-4zqy768d.js} +34 -29
- package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-mpnzgztj.js} +3 -3
- package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
- package/dist/execute-6e7qeyk4.js +1017 -0
- package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-k6n5nbpp.js} +6 -5
- package/dist/{google-vertex-809af514.js → google-vertex-44xxtcvp.js} +6 -5
- package/dist/{handlers-aqcedbyb.js → handlers-kjdce1w5.js} +37 -32
- package/dist/{hook-ybvmrmhq.js → hook-czx13n12.js} +19 -15
- package/dist/{hook-7vkskkre.js → hook-r2t4bqfg.js} +18 -14
- package/dist/{http-2eqa8t24.js → http-3bbb19be.js} +900 -145
- package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
- package/dist/{index-x71bvq0p.js → index-668me3f0.js} +35 -30
- package/dist/{index-c6zsv1bd.js → index-7aj368tw.js} +13 -12
- package/dist/{index-1yks3qcr.js → index-fg0dsp6c.js} +34 -29
- package/dist/{index-chgdkq36.js → index-kkv84jq9.js} +34 -29
- package/dist/{index-a3vn96n8.js → index-zg7vp46x.js} +198 -54
- package/dist/{keepalive-vtkf5n99.js → keepalive-yra8dy57.js} +13 -11
- package/dist/{lead-h3sp357x.js → lead-va0pad2c.js} +54 -49
- package/dist/llama-cpp-classify-tgj35k8d.js +335 -0
- package/dist/{maintenance-jzeqtk3q.js → maintenance-hqewtmyg.js} +14 -13
- package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-pc3ntnxt.js} +14 -17
- package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-7rdt1m9n.js} +12 -10
- package/dist/{onboard-tmsp0qbz.js → onboard-j6ms6d0x.js} +6 -5
- package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-bbancrr0.js} +21 -8
- package/dist/{openai-completions-9hjrqbw8.js → openai-completions-jpaj5g2b.js} +9 -12
- package/dist/{openai-responses-7b1pn0xt.js → openai-responses-200k1wx4.js} +23 -18
- package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-89rabkw2.js} +8 -7
- package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-8yx3487g.js} +5 -7
- package/dist/{otel-impl-9mz73q65.js → otel-impl-a792rjz0.js} +1 -1
- package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
- package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-g7qzvr3v.js} +295 -70
- package/dist/pricing-refresh-wx0tp7cb.js +39 -0
- package/dist/quickjs-r1pq2107.wasm +0 -0
- package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-8tzawacv.js} +12 -10
- package/dist/{render-v2-gryrh9h8.js → render-v2-mm7zswb8.js} +12 -10
- package/dist/runtime-zyssgse0.js +24 -0
- package/dist/{setup-q9720bt6.js → setup-8y70ys6t.js} +6 -5
- package/dist/typesafe-system-one-2ef30pec.js +27 -0
- package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-6nmk7h3n.js} +22 -13
- package/dist/{work-objects-b76x7deb.js → work-objects-fg6xkb0n.js} +37 -32
- package/dist/{worker-6vrxbv41.js → worker-wmwhxsph.js} +54 -49
- package/openapi.json +1314 -91
- package/package.json +12 -11
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/api-keys.ts +70 -0
- package/src/be/db/tasks/human-free.ts +442 -0
- package/src/be/db/tasks/read.ts +56 -1
- package/src/be/db/tasks/write.ts +107 -11
- package/src/be/db.ts +274 -188
- package/src/be/harness-model-support.ts +182 -0
- package/src/be/human-free-drain.ts +67 -0
- package/src/be/memory/providers/sqlite-store.ts +51 -15
- package/src/be/migrations/182_task_human_free_flag.sql +63 -0
- package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
- package/src/be/migrations/184_model_catalog.sql +120 -0
- package/src/be/migrations/185_api_key_auth_failures.sql +6 -0
- package/src/be/migrations/186_api_key_seat_mismatch.sql +8 -0
- package/src/be/model-catalog-store.ts +644 -0
- package/src/be/model-tier-keys.ts +46 -0
- package/src/be/model-tier-resolution.ts +534 -0
- package/src/be/model-validation.ts +188 -0
- package/src/be/models-catalog.ts +61 -7
- package/src/be/modelsdev-cache.json +82 -0
- package/src/be/modelsdev-cache.ts +2 -0
- package/src/be/pricing-refresh.ts +154 -12
- package/src/be/scripts/typecheck.ts +15 -0
- package/src/be/seed/runner.ts +18 -12
- package/src/be/seed/types.ts +1 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
- package/src/be/swarm-config-guard.ts +8 -0
- package/src/cli.tsx +10 -0
- package/src/commands/claude-managed-setup.ts +3 -1
- package/src/commands/codex-login.ts +67 -0
- package/src/commands/credential-outcome-report.ts +191 -0
- package/src/commands/rate-limit-outcome.ts +34 -4
- package/src/commands/runner.ts +357 -186
- package/src/commands/seat-mismatch-report.ts +46 -0
- package/src/extensions/apply-task-create.ts +27 -1
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +79 -17
- package/src/heartbeat/templates.ts +2 -2
- package/src/http/agents.ts +62 -1
- package/src/http/api-keys.ts +140 -2
- package/src/http/codex-oauth-keep-warm.ts +3 -5
- package/src/http/index.ts +8 -0
- package/src/http/memory.ts +12 -5
- package/src/http/models-catalog.ts +357 -8
- package/src/http/poll.ts +54 -3
- package/src/http/schedules.ts +51 -6
- package/src/http/script-runs.ts +12 -0
- package/src/http/session-data.ts +2 -5
- package/src/http/stats.ts +6 -8
- package/src/http/status.ts +15 -1
- package/src/http/tasks.ts +90 -7
- package/src/http/usage-cache.ts +56 -15
- package/src/http/workflows.ts +46 -8
- package/src/mcp-client/http-client.ts +5 -0
- package/src/memory/recall-query.ts +74 -0
- package/src/prompts/base-prompt.ts +15 -4
- package/src/prompts/session-templates.ts +23 -1
- package/src/providers/acp-adapter.ts +1 -0
- package/src/providers/claude-adapter.ts +14 -5
- package/src/providers/claude-managed-adapter.ts +1 -0
- package/src/providers/claude-managed-models.ts +47 -161
- package/src/providers/claude-sdk-session.ts +3 -1
- package/src/providers/codex-adapter.ts +14 -14
- package/src/providers/codex-models.ts +65 -198
- package/src/providers/devin-adapter.ts +1 -0
- package/src/providers/dsh-adapter.ts +2 -1
- package/src/providers/harness-version.ts +83 -7
- package/src/providers/opencode-adapter.ts +53 -1
- package/src/providers/pi-binary-root/src/extensions/codemode/worker.ts +11 -0
- package/src/providers/pi-codemode-runtime.ts +17 -0
- package/src/providers/pi-mono-adapter.ts +361 -83
- package/src/providers/pi-mono-extension.ts +16 -1
- package/src/providers/reasoning-effort.ts +44 -140
- package/src/providers/types.ts +8 -0
- package/src/rbac/legacy-policy.ts +43 -0
- package/src/rbac/permissions.ts +15 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/types/stdlib.d.ts +15 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
- package/src/server.ts +6 -0
- package/src/slack/assistant.ts +13 -0
- package/src/slack/message-text.ts +56 -2
- package/src/slack/render-v2.ts +87 -37
- package/src/slack/session-status.ts +541 -0
- package/src/slack/thread-buffer.ts +10 -2
- package/src/tasks/sibling-awareness.ts +3 -2
- package/src/telemetry.ts +5 -4
- package/src/templates.d.ts +6 -0
- package/src/tests/agents-list-model-display.test.ts +4 -6
- package/src/tests/api-key-auth-failure-route.test.ts +142 -0
- package/src/tests/api-key-tracking.test.ts +368 -1
- package/src/tests/base-prompt.test.ts +43 -2
- package/src/tests/claude-adapter.test.ts +68 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -18
- package/src/tests/codex-adapter.test.ts +2 -1
- package/src/tests/codex-auth-failure.test.ts +52 -0
- package/src/tests/codex-gpt6-release.test.ts +66 -5
- package/src/tests/codex-login.test.ts +116 -0
- package/src/tests/context-window.test.ts +7 -5
- package/src/tests/credential-exhaustion-policy.test.ts +118 -3
- package/src/tests/credential-outcome-report.test.ts +171 -0
- package/src/tests/credential-status-api.test.ts +103 -2
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +10 -1
- package/src/tests/db-task-write-contract.test.ts +535 -2
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dokploy-deploy.test.ts +315 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/explicit-model-validation.test.ts +583 -0
- package/src/tests/extensions-pre-task.test.ts +73 -0
- package/src/tests/fetch-resolved-env.test.ts +46 -0
- package/src/tests/hang-watchdog-worker.ts +47 -0
- package/src/tests/hang-watchdog.test.ts +45 -0
- package/src/tests/hang-watchdog.ts +30 -0
- package/src/tests/harness-model-guard.test.ts +123 -0
- package/src/tests/harness-model-support.test.ts +374 -0
- package/src/tests/harness-version.test.ts +70 -3
- package/src/tests/heartbeat-checklist-config.test.ts +71 -0
- package/src/tests/heartbeat-checklist.test.ts +4 -2
- package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
- package/src/tests/http-task-write-ownership.test.ts +172 -0
- package/src/tests/human-free-flag.test.ts +279 -0
- package/src/tests/human-free-oracle.ts +57 -0
- package/src/tests/human-free-reclassify.test.ts +669 -0
- package/src/tests/memory-fts-populate.test.ts +139 -0
- package/src/tests/memory-http-recall-gating.test.ts +125 -1
- package/src/tests/migration-046-budgets.test.ts +3 -3
- package/src/tests/model-catalog-migration.test.ts +134 -0
- package/src/tests/model-catalog-table.test.ts +249 -0
- package/src/tests/model-catalog-write-access.test.ts +387 -0
- package/src/tests/model-rate-limit-windows.test.ts +19 -0
- package/src/tests/model-tier-resolution.test.ts +611 -0
- package/src/tests/models-catalog.test.ts +1 -1
- package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
- package/src/tests/opencode-adapter.test.ts +157 -2
- package/src/tests/package-publish.test.ts +137 -36
- package/src/tests/pi-codemode.test.ts +246 -0
- package/src/tests/pi-mono-adapter.test.ts +3 -0
- package/src/tests/pi-native-mcp.test.ts +193 -0
- package/src/tests/pi-tool-exposure.test.ts +374 -0
- package/src/tests/preload.ts +3 -0
- package/src/tests/pricing-routes.test.ts +2 -2
- package/src/tests/prompt-template-session.test.ts +5 -3
- package/src/tests/rate-limit-event.test.ts +65 -0
- package/src/tests/rate-limit-outcome.test.ts +90 -0
- package/src/tests/rbac-engine.test.ts +77 -3
- package/src/tests/reasoning-effort.test.ts +109 -0
- package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
- package/src/tests/runner-key-completion-outcome.test.ts +112 -0
- package/src/tests/runner-key-outcome-late-reset.test.ts +224 -0
- package/src/tests/runner-registration-retry.test.ts +149 -0
- package/src/tests/runtime-model-catalog.test.ts +86 -0
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/seat-mismatch-report.test.ts +85 -0
- package/src/tests/seed-scripts.test.ts +138 -0
- package/src/tests/seed.test.ts +25 -0
- package/src/tests/session-costs.test.ts +14 -11
- package/src/tests/slack-message-text.test.ts +101 -0
- package/src/tests/slack-render-v2.test.ts +96 -0
- package/src/tests/slack-session-status.test.ts +626 -0
- package/src/tests/slack-thread-buffer-context.test.ts +93 -0
- package/src/tests/status.test.ts +29 -0
- package/src/tests/steering-promotion.test.ts +51 -0
- package/src/tests/store-progress-ownership.test.ts +136 -0
- package/src/tests/subscription-plans.test.ts +28 -0
- package/src/tests/task-cascade-fail.test.ts +148 -2
- package/src/tests/task-citations.test.ts +3 -2
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/usage-cache.test.ts +166 -0
- package/src/tests/workflow-agent-task.test.ts +30 -0
- package/src/tests/workflow-executors.test.ts +5 -2
- package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
- package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
- package/src/tests/workflow-system-one-decision.test.ts +2063 -0
- package/src/tests/workflow-triggers-v2.test.ts +320 -0
- package/src/tools/model-catalog/index.ts +137 -0
- package/src/tools/schedules/create-schedule.ts +17 -2
- package/src/tools/schedules/patch-schedule.ts +31 -1
- package/src/tools/schedules/update-schedule.ts +31 -1
- package/src/tools/send-task.ts +18 -1
- package/src/tools/store-progress.ts +49 -4
- package/src/tools/task-action.ts +14 -1
- package/src/tools/tool-config.ts +3 -1
- package/src/tools/workflows/create-workflow.ts +17 -4
- package/src/tools/workflows/patch-workflow-node.ts +17 -7
- package/src/tools/workflows/patch-workflow.ts +9 -2
- package/src/tools/workflows/update-workflow.ts +17 -4
- package/src/types.ts +41 -1
- package/src/utils/codex-auth-failure.ts +34 -0
- package/src/utils/context-window.ts +47 -53
- package/src/utils/credentials.ts +72 -30
- package/src/utils/error-tracker.ts +80 -0
- package/src/utils/harness-cli-version.ts +115 -0
- package/src/utils/harness-model-error.ts +33 -0
- package/src/utils/model-rate-limit-windows.ts +12 -0
- package/src/utils/runtime-model-catalog.ts +152 -0
- package/src/utils/spawn-model-guard.ts +43 -0
- package/src/utils/subscription-plans.ts +22 -0
- package/src/workflows/approval-resolution.ts +39 -0
- package/src/workflows/definition.ts +7 -0
- package/src/workflows/engine.ts +69 -1
- package/src/workflows/executors/agent-task.ts +16 -6
- package/src/workflows/executors/base.ts +53 -0
- package/src/workflows/executors/human-in-the-loop.ts +8 -8
- package/src/workflows/executors/index.ts +1 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/executors/system-one-decision.ts +1232 -0
- package/src/workflows/executors/system-one-providers.ts +301 -0
- package/src/workflows/executors/workflow-llm.ts +13 -1
- package/src/workflows/model-validation.ts +45 -0
- package/src/workflows/patch-definition.ts +5 -0
- package/src/workflows/readiness.ts +86 -0
- package/src/workflows/recovery.ts +12 -12
- package/src/workflows/resume.ts +23 -9
- package/src/workflows/triggers.ts +85 -0
- package/templates/schedules/daily-blocker-digest/content.md +4 -5
- package/templates/skills/agent-browser/SKILL.md +6 -5
- package/templates/skills/agent-browser/content.md +6 -5
- package/templates/skills/heartbeat-runbook/config.json +1 -1
- package/templates/skills/heartbeat-runbook/content.md +2 -2
- package/templates/skills/swarm-scripts/SKILL.md +1 -1
- package/templates/skills/swarm-scripts/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +82 -1
- package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
- package/templates/workflows/pr-review-status-sweep/content.md +2 -0
- package/dist/boot-seeds-s3q01xj3.js +0 -26
- package/dist/claude-sdk-session-t442qz6f.js +0 -39108
- package/dist/cli-0xjkj1j7.js +0 -61
- package/dist/cli-20cj37vt.js +0 -335561
- package/dist/cli-2djtr51b.js +0 -48
- package/dist/cli-65hjmev5.js +0 -1141
- package/dist/cli-aw1jwp1r.js +0 -14075
- package/dist/cli-h967nb6r.js +0 -6603
- package/dist/cli-vapyk8sw.js +0 -20
- package/dist/cli-ws6rqycm.js +0 -247
- package/dist/pricing-refresh-v1cbr9te.js +0 -27
- package/src/providers/modelsdev-reasoning.json +0 -6453
- package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP contract for the Codex pool auth-failure bench:
|
|
3
|
+
* POST /api/keys/report-auth-failure and the clearAuthBench flag on
|
|
4
|
+
* POST /api/keys/clear-rate-limit.
|
|
5
|
+
*/
|
|
6
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
7
|
+
import { unlink } from "node:fs/promises";
|
|
8
|
+
import { createServer as createHttpServer, type Server } from "node:http";
|
|
9
|
+
import { closeDb, initDb } from "../be/db";
|
|
10
|
+
import { clearCodexAuthBench, readCodexAuthFailureFence } from "../commands/codex-login";
|
|
11
|
+
import { handleApiKeys } from "../http/api-keys";
|
|
12
|
+
import { listenOnFreePort } from "./test-net";
|
|
13
|
+
|
|
14
|
+
const TEST_DB = `./test-api-key-auth-failure-route-${Date.now()}.sqlite`;
|
|
15
|
+
|
|
16
|
+
describe("API key auth-failure routes", () => {
|
|
17
|
+
let server: Server;
|
|
18
|
+
let baseUrl = "";
|
|
19
|
+
|
|
20
|
+
const post = async (path: string, body: unknown) => {
|
|
21
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
|
+
body: JSON.stringify(body),
|
|
25
|
+
});
|
|
26
|
+
expect(res.status).toBe(200);
|
|
27
|
+
return (await res.json()) as Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
const get = async (path: string) => {
|
|
30
|
+
const res = await fetch(`${baseUrl}${path}`);
|
|
31
|
+
expect(res.status).toBe(200);
|
|
32
|
+
return (await res.json()) as Record<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
const key = { keyType: "CODEX_OAUTH", keySuffix: "qa001" };
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
process.env.DB_PATH = TEST_DB;
|
|
38
|
+
initDb(TEST_DB);
|
|
39
|
+
server = createHttpServer(async (req, res) => {
|
|
40
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
41
|
+
const pathSegments = url.pathname.split("/").filter(Boolean);
|
|
42
|
+
const handled = await handleApiKeys(req, res, pathSegments, url.searchParams);
|
|
43
|
+
if (!handled) {
|
|
44
|
+
res.writeHead(404);
|
|
45
|
+
res.end();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const port = await listenOnFreePort(server);
|
|
49
|
+
baseUrl = `http://127.0.0.1:${port}`;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(async () => {
|
|
53
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
54
|
+
closeDb();
|
|
55
|
+
await unlink(TEST_DB).catch(() => {});
|
|
56
|
+
await unlink(`${TEST_DB}-wal`).catch(() => {});
|
|
57
|
+
await unlink(`${TEST_DB}-shm`).catch(() => {});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("benches on the second report in a row", async () => {
|
|
61
|
+
const first = await post("/api/keys/report-auth-failure", { ...key, keyIndex: 1 });
|
|
62
|
+
expect(first).toMatchObject({ success: true, consecutiveAuthFailures: 1, benched: false });
|
|
63
|
+
|
|
64
|
+
const second = await post("/api/keys/report-auth-failure", { ...key, keyIndex: 1 });
|
|
65
|
+
expect(second).toMatchObject({ success: true, consecutiveAuthFailures: 2, benched: true });
|
|
66
|
+
expect(typeof second.rateLimitedUntil).toBe("string");
|
|
67
|
+
|
|
68
|
+
const available = await get("/api/keys/available?keyType=CODEX_OAUTH&totalKeys=3");
|
|
69
|
+
expect(available.availableIndices).toEqual([0, 2]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("clear-rate-limit without clearAuthBench leaves the auth bench", async () => {
|
|
73
|
+
const result = await post("/api/keys/clear-rate-limit", key);
|
|
74
|
+
expect(result).toMatchObject({ success: true, cleared: false });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("clear-rate-limit with clearAuthBench but no authFence leaves the auth bench", async () => {
|
|
78
|
+
const result = await post("/api/keys/clear-rate-limit", { ...key, clearAuthBench: true });
|
|
79
|
+
expect(result).toMatchObject({ success: true, cleared: false });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("clear-rate-limit with clearAuthBench and a current authFence lifts the bench", async () => {
|
|
83
|
+
const { authFailureFence } = await get("/api/keys/available?keyType=CODEX_OAUTH&totalKeys=3");
|
|
84
|
+
expect(typeof authFailureFence).toBe("number");
|
|
85
|
+
const result = await post("/api/keys/clear-rate-limit", {
|
|
86
|
+
...key,
|
|
87
|
+
clearAuthBench: true,
|
|
88
|
+
authFence: authFailureFence,
|
|
89
|
+
});
|
|
90
|
+
expect(result).toMatchObject({ success: true, cleared: true });
|
|
91
|
+
|
|
92
|
+
const status = await get("/api/keys/status?keyType=CODEX_OAUTH");
|
|
93
|
+
const row = (status.keys as Array<Record<string, unknown>>).find(
|
|
94
|
+
(k) => k.keySuffix === "qa001",
|
|
95
|
+
);
|
|
96
|
+
expect(row).toMatchObject({ status: "available", consecutiveAuthFailures: 0 });
|
|
97
|
+
expect(row!.lastAuthFailureAt).not.toBeNull();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("a codex-login re-login lifts the auth bench", async () => {
|
|
101
|
+
const relog = { keyType: "CODEX_OAUTH", keySuffix: "relog" };
|
|
102
|
+
await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 4 });
|
|
103
|
+
const benched = await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 4 });
|
|
104
|
+
expect(benched).toMatchObject({ benched: true });
|
|
105
|
+
|
|
106
|
+
const fence = await readCodexAuthFailureFence(baseUrl, "test-key");
|
|
107
|
+
expect(await clearCodexAuthBench(baseUrl, "test-key", "relog", 4, fence)).toBe(true);
|
|
108
|
+
|
|
109
|
+
const status = await get("/api/keys/status?keyType=CODEX_OAUTH");
|
|
110
|
+
const row = (status.keys as Array<Record<string, unknown>>).find(
|
|
111
|
+
(k) => k.keySuffix === "relog",
|
|
112
|
+
);
|
|
113
|
+
expect(row).toMatchObject({ status: "available", consecutiveAuthFailures: 0 });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("a late codex-login clear keeps failures on the fresh login", async () => {
|
|
117
|
+
const relog = { keyType: "CODEX_OAUTH", keySuffix: "relo2" };
|
|
118
|
+
await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
|
|
119
|
+
await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
|
|
120
|
+
// codex-login reads the fence, then stores the fresh credentials.
|
|
121
|
+
const fence = await readCodexAuthFailureFence(baseUrl, "test-key");
|
|
122
|
+
// Two tasks fail on the fresh login before the clear lands.
|
|
123
|
+
await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
|
|
124
|
+
await post("/api/keys/report-auth-failure", { ...relog, keyIndex: 5 });
|
|
125
|
+
expect(await clearCodexAuthBench(baseUrl, "test-key", "relo2", 5, fence)).toBe(false);
|
|
126
|
+
|
|
127
|
+
const status = await get("/api/keys/status?keyType=CODEX_OAUTH");
|
|
128
|
+
const row = (status.keys as Array<Record<string, unknown>>).find(
|
|
129
|
+
(k) => k.keySuffix === "relo2",
|
|
130
|
+
);
|
|
131
|
+
expect(row).toMatchObject({ status: "rate_limited", consecutiveAuthFailures: 4 });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("rejects an invalid body", async () => {
|
|
135
|
+
const res = await fetch(`${baseUrl}/api/keys/report-auth-failure`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Content-Type": "application/json" },
|
|
138
|
+
body: JSON.stringify({ keyType: "CODEX_OAUTH", keySuffix: "qa001" }),
|
|
139
|
+
});
|
|
140
|
+
expect(res.status).toBe(400);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -8,14 +8,22 @@ import {
|
|
|
8
8
|
clearKeyRateLimit,
|
|
9
9
|
closeDb,
|
|
10
10
|
getAvailableKeyIndices,
|
|
11
|
+
getDbClient,
|
|
11
12
|
getKeyStatuses,
|
|
13
|
+
getKv,
|
|
12
14
|
initDb,
|
|
13
15
|
markKeyRateLimited,
|
|
16
|
+
recordKeyAuthFailure,
|
|
14
17
|
recordKeyRateLimitWindows,
|
|
15
18
|
recordKeyUsage,
|
|
19
|
+
setApiKeyPlan,
|
|
16
20
|
} from "../be/db";
|
|
17
21
|
import type { CredentialSelection } from "../utils/credentials";
|
|
18
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
ModelWindowExhaustedError,
|
|
24
|
+
resolveCredentialPools,
|
|
25
|
+
selectCredential,
|
|
26
|
+
} from "../utils/credentials";
|
|
19
27
|
|
|
20
28
|
// ─── Credential Selection Unit Tests ────────────────────────────────────────
|
|
21
29
|
|
|
@@ -242,6 +250,258 @@ describe("API key tracking DB queries", () => {
|
|
|
242
250
|
expect(cleared).toBe(false);
|
|
243
251
|
});
|
|
244
252
|
|
|
253
|
+
const codexStatus = async (keySuffix: string) =>
|
|
254
|
+
(await getKeyStatuses("CODEX_OAUTH")).find((s) => s.keySuffix === keySuffix)!;
|
|
255
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
256
|
+
/** The fence a runner reads with its key draw, or codex-login before its credential write. */
|
|
257
|
+
const readFence = async () => (await getAvailableKeyIndices("CODEX_OAUTH", 1)).authFailureFence;
|
|
258
|
+
const clearWithFence = async (keySuffix: string, authFence: number, keyIndex?: number) =>
|
|
259
|
+
clearKeyRateLimit("CODEX_OAUTH", keySuffix, "global", null, {
|
|
260
|
+
clearAuthBench: true,
|
|
261
|
+
authFence,
|
|
262
|
+
keyIndex,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("recordKeyAuthFailure: 1 failure counts but does not bench", async () => {
|
|
266
|
+
const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx01", 0);
|
|
267
|
+
expect(result).toEqual({ consecutiveAuthFailures: 1, benched: false, rateLimitedUntil: null });
|
|
268
|
+
|
|
269
|
+
const row = await codexStatus("cdx01");
|
|
270
|
+
expect(row.consecutiveAuthFailures).toBe(1);
|
|
271
|
+
expect(row.lastAuthFailureAt).not.toBeNull();
|
|
272
|
+
expect(row.status).toBe("available");
|
|
273
|
+
const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 3);
|
|
274
|
+
expect(availableIndices).toContain(0);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("recordKeyAuthFailure: 2 failures in a row bench for 365 days", async () => {
|
|
278
|
+
const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx01", 0);
|
|
279
|
+
expect(result.consecutiveAuthFailures).toBe(2);
|
|
280
|
+
expect(result.benched).toBe(true);
|
|
281
|
+
|
|
282
|
+
const row = await codexStatus("cdx01");
|
|
283
|
+
expect(row.status).toBe("rate_limited");
|
|
284
|
+
expect(row.rateLimitedUntil).toBe(result.rateLimitedUntil);
|
|
285
|
+
expect(Math.abs(Date.parse(row.rateLimitedUntil!) - (Date.now() + 365 * DAY_MS))).toBeLessThan(
|
|
286
|
+
60_000,
|
|
287
|
+
);
|
|
288
|
+
expect(row.lastRateLimitAt).not.toBeNull();
|
|
289
|
+
|
|
290
|
+
const marker = await getKv("codex-auth-watch", "bench:cdx01");
|
|
291
|
+
expect(marker).not.toBeNull();
|
|
292
|
+
expect(marker!.value).toMatchObject({
|
|
293
|
+
keyIndex: 0,
|
|
294
|
+
keyType: "CODEX_OAUTH",
|
|
295
|
+
benchedUntil: row.rateLimitedUntil,
|
|
296
|
+
source: "report-auth-failure",
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 3);
|
|
300
|
+
expect(availableIndices).not.toContain(0);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("recordKeyAuthFailure only extends an existing longer bench", async () => {
|
|
304
|
+
const farUntil = new Date(Date.now() + 400 * DAY_MS).toISOString();
|
|
305
|
+
await markKeyRateLimited("CODEX_OAUTH", "cdx02", 1, farUntil);
|
|
306
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx02", 1);
|
|
307
|
+
const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx02", 1);
|
|
308
|
+
expect(result.benched).toBe(true);
|
|
309
|
+
expect(result.rateLimitedUntil).toBe(farUntil);
|
|
310
|
+
expect((await codexStatus("cdx02")).rateLimitedUntil).toBe(farUntil);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("a success between auth failures resets the run", async () => {
|
|
314
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx03", 2);
|
|
315
|
+
await clearWithFence("cdx03", await readFence());
|
|
316
|
+
const result = await recordKeyAuthFailure("CODEX_OAUTH", "cdx03", 2);
|
|
317
|
+
expect(result.benched).toBe(false);
|
|
318
|
+
|
|
319
|
+
const row = await codexStatus("cdx03");
|
|
320
|
+
expect(row.status).toBe("available");
|
|
321
|
+
expect(row.consecutiveAuthFailures).toBe(1);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("clearKeyRateLimit without clearAuthBench cannot lift an auth bench", async () => {
|
|
325
|
+
const cleared = await clearKeyRateLimit("CODEX_OAUTH", "cdx01");
|
|
326
|
+
expect(cleared).toBe(false);
|
|
327
|
+
expect((await codexStatus("cdx01")).status).toBe("rate_limited");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("clearKeyRateLimit with clearAuthBench but no fence cannot lift an auth bench", async () => {
|
|
331
|
+
const cleared = await clearKeyRateLimit("CODEX_OAUTH", "cdx01", "global", null, {
|
|
332
|
+
clearAuthBench: true,
|
|
333
|
+
});
|
|
334
|
+
expect(cleared).toBe(false);
|
|
335
|
+
const row = await codexStatus("cdx01");
|
|
336
|
+
expect(row.status).toBe("rate_limited");
|
|
337
|
+
expect(row.consecutiveAuthFailures).toBe(2);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("clearKeyRateLimit with clearAuthBench and a current fence lifts an auth bench", async () => {
|
|
341
|
+
const cleared = await clearWithFence("cdx01", await readFence());
|
|
342
|
+
expect(cleared).toBe(true);
|
|
343
|
+
|
|
344
|
+
const row = await codexStatus("cdx01");
|
|
345
|
+
expect(row.status).toBe("available");
|
|
346
|
+
expect(row.rateLimitedUntil).toBeNull();
|
|
347
|
+
expect(row.consecutiveAuthFailures).toBe(0);
|
|
348
|
+
expect(await getKv("codex-auth-watch", "bench:cdx01")).toBeNull();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("clearKeyRateLimit without clearAuthBench still clears a plain rate limit", async () => {
|
|
352
|
+
const until = new Date(Date.now() + 300_000).toISOString();
|
|
353
|
+
await markKeyRateLimited("CODEX_OAUTH", "cdx04", 3, until);
|
|
354
|
+
const cleared = await clearKeyRateLimit("CODEX_OAUTH", "cdx04");
|
|
355
|
+
expect(cleared).toBe(true);
|
|
356
|
+
expect((await codexStatus("cdx04")).status).toBe("available");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("an ordinary rate limit and its expiry do not undo an auth bench", async () => {
|
|
360
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx05", 4);
|
|
361
|
+
const benched = await recordKeyAuthFailure("CODEX_OAUTH", "cdx05", 4);
|
|
362
|
+
expect(benched.benched).toBe(true);
|
|
363
|
+
|
|
364
|
+
const expired = new Date(Date.now() - 1_000).toISOString();
|
|
365
|
+
await markKeyRateLimited("CODEX_OAUTH", "cdx05", 4, expired);
|
|
366
|
+
|
|
367
|
+
const row = await codexStatus("cdx05");
|
|
368
|
+
expect(row.status).toBe("rate_limited");
|
|
369
|
+
expect(row.rateLimitedUntil).toBe(benched.rateLimitedUntil);
|
|
370
|
+
const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 5);
|
|
371
|
+
expect(availableIndices).not.toContain(4);
|
|
372
|
+
expect((await codexStatus("cdx05")).status).toBe("rate_limited");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("an expired stored auth bench does not auto-clear", async () => {
|
|
376
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx06", 1);
|
|
377
|
+
expect((await recordKeyAuthFailure("CODEX_OAUTH", "cdx06", 1)).benched).toBe(true);
|
|
378
|
+
const past = new Date(Date.now() - 60_000).toISOString();
|
|
379
|
+
await getDbClient().run(
|
|
380
|
+
`UPDATE api_key_status SET rateLimitedUntil = ? WHERE keyType = 'CODEX_OAUTH' AND keySuffix = 'cdx06'`,
|
|
381
|
+
[past],
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 5);
|
|
385
|
+
expect(availableIndices).not.toContain(1);
|
|
386
|
+
const row = await codexStatus("cdx06");
|
|
387
|
+
expect(row.status).toBe("rate_limited");
|
|
388
|
+
expect(row.rateLimitedUntil).toBe(past);
|
|
389
|
+
expect(row.consecutiveAuthFailures).toBe(2);
|
|
390
|
+
expect(await getKv("codex-auth-watch", "bench:cdx06")).not.toBeNull();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("each auth failure gets a higher server-side order than the last fence", async () => {
|
|
394
|
+
const before = await readFence();
|
|
395
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "seq01", 4);
|
|
396
|
+
const afterFirst = await readFence();
|
|
397
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "seq02", 4);
|
|
398
|
+
expect(afterFirst).toBeGreaterThan(before);
|
|
399
|
+
expect(await readFence()).toBeGreaterThan(afterFirst);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("two runners: a late success fenced before the other runner's failures keeps the bench", async () => {
|
|
403
|
+
// Runner A draws the key (reads the fence) and later succeeds. Worker clocks play no
|
|
404
|
+
// part: whatever A's clock says, only the server-side order decides.
|
|
405
|
+
const fenceA = await readFence();
|
|
406
|
+
// Runner B then fails twice on the same login and benches it.
|
|
407
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx07", 2);
|
|
408
|
+
expect((await recordKeyAuthFailure("CODEX_OAUTH", "cdx07", 2)).benched).toBe(true);
|
|
409
|
+
// A's success reset lands last.
|
|
410
|
+
expect(await clearWithFence("cdx07", fenceA)).toBe(false);
|
|
411
|
+
let row = await codexStatus("cdx07");
|
|
412
|
+
expect(row.status).toBe("rate_limited");
|
|
413
|
+
expect(row.consecutiveAuthFailures).toBe(2);
|
|
414
|
+
expect(await getKv("codex-auth-watch", "bench:cdx07")).not.toBeNull();
|
|
415
|
+
|
|
416
|
+
// A success from a task drawn after the failures still lifts it.
|
|
417
|
+
expect(await clearWithFence("cdx07", await readFence())).toBe(true);
|
|
418
|
+
row = await codexStatus("cdx07");
|
|
419
|
+
expect(row.status).toBe("available");
|
|
420
|
+
expect(row.consecutiveAuthFailures).toBe(0);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("a clock rollback (restart or skew) cannot reorder a success before later failures", async () => {
|
|
424
|
+
const staleFence = await readFence();
|
|
425
|
+
// The failures are recorded while every clock in the process reads an hour earlier.
|
|
426
|
+
const realNow = Date.now;
|
|
427
|
+
Date.now = () => realNow() - 3_600_000;
|
|
428
|
+
try {
|
|
429
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx09", 4);
|
|
430
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx09", 4);
|
|
431
|
+
} finally {
|
|
432
|
+
Date.now = realNow;
|
|
433
|
+
}
|
|
434
|
+
expect(await readFence()).toBeGreaterThan(staleFence);
|
|
435
|
+
expect(await clearWithFence("cdx09", staleFence)).toBe(false);
|
|
436
|
+
const row = await codexStatus("cdx09");
|
|
437
|
+
expect(row.status).toBe("rate_limited");
|
|
438
|
+
expect(row.consecutiveAuthFailures).toBe(2);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test("a stale success does not reset a count below the threshold", async () => {
|
|
442
|
+
const staleFence = await readFence();
|
|
443
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "cdx08", 3);
|
|
444
|
+
await clearWithFence("cdx08", staleFence);
|
|
445
|
+
expect((await codexStatus("cdx08")).consecutiveAuthFailures).toBe(1);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test("a late re-login clear keeps failures recorded after the credential write", async () => {
|
|
449
|
+
// The login was benched before the re-login.
|
|
450
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
|
|
451
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
|
|
452
|
+
// codex-login reads the fence, then stores the fresh credentials.
|
|
453
|
+
const fence = await readFence();
|
|
454
|
+
// Two tasks fail on the fresh login before the clear request lands.
|
|
455
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
|
|
456
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "rel01", 1);
|
|
457
|
+
await clearWithFence("rel01", fence, 1);
|
|
458
|
+
const row = await codexStatus("rel01");
|
|
459
|
+
expect(row.status).toBe("rate_limited");
|
|
460
|
+
expect(row.consecutiveAuthFailures).toBe(4);
|
|
461
|
+
expect(await getKv("codex-auth-watch", "bench:rel01")).not.toBeNull();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test("a re-login clear lifts the bench recorded before the credential write", async () => {
|
|
465
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "rel02", 2);
|
|
466
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "rel02", 2);
|
|
467
|
+
expect(await clearWithFence("rel02", await readFence(), 2)).toBe(true);
|
|
468
|
+
const row = await codexStatus("rel02");
|
|
469
|
+
expect(row.status).toBe("available");
|
|
470
|
+
expect(row.consecutiveAuthFailures).toBe(0);
|
|
471
|
+
expect(await getKv("codex-auth-watch", "bench:rel02")).toBeNull();
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("a slot re-login with a different account retires the previous login's bench", async () => {
|
|
475
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "old01", 0);
|
|
476
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "old01", 0);
|
|
477
|
+
expect(await getKv("codex-auth-watch", "bench:old01")).not.toBeNull();
|
|
478
|
+
|
|
479
|
+
const cleared = await clearWithFence("new01", await readFence(), 0);
|
|
480
|
+
expect(cleared).toBe(true);
|
|
481
|
+
await recordKeyUsage("CODEX_OAUTH", "new01", 0, null);
|
|
482
|
+
|
|
483
|
+
const old = await codexStatus("old01");
|
|
484
|
+
expect(old.status).toBe("available");
|
|
485
|
+
expect(old.rateLimitedUntil).toBeNull();
|
|
486
|
+
expect(old.consecutiveAuthFailures).toBe(0);
|
|
487
|
+
expect(await getKv("codex-auth-watch", "bench:old01")).toBeNull();
|
|
488
|
+
const { availableIndices } = await getAvailableKeyIndices("CODEX_OAUTH", 5);
|
|
489
|
+
expect(availableIndices).toContain(0);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("a clear without keyIndex leaves other logins at the same index alone", async () => {
|
|
493
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "old02", 3);
|
|
494
|
+
await recordKeyAuthFailure("CODEX_OAUTH", "old02", 3);
|
|
495
|
+
await clearWithFence("new02", await readFence());
|
|
496
|
+
expect((await codexStatus("old02")).status).toBe("rate_limited");
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("an ordinary rate limit longer than the auth bench still applies", async () => {
|
|
500
|
+
const farUntil = new Date(Date.now() + 500 * DAY_MS).toISOString();
|
|
501
|
+
await markKeyRateLimited("CODEX_OAUTH", "cdx05", 4, farUntil);
|
|
502
|
+
expect((await codexStatus("cdx05")).rateLimitedUntil).toBe(farUntil);
|
|
503
|
+
});
|
|
504
|
+
|
|
245
505
|
test("recordKeyRateLimitWindows persists latest provider windows", async () => {
|
|
246
506
|
await recordKeyRateLimitWindows("ANTHROPIC_API_KEY", "aaa11", 0, {
|
|
247
507
|
seven_day: {
|
|
@@ -474,6 +734,113 @@ describe("API key tracking DB queries", () => {
|
|
|
474
734
|
const recovered = await getAvailableKeyIndices(KEY_TYPE, 1, "agent", scopeId, "fable");
|
|
475
735
|
expect(recovered.availableIndices).toEqual([0]);
|
|
476
736
|
});
|
|
737
|
+
|
|
738
|
+
describe("seat filtering", () => {
|
|
739
|
+
// Own scope so the rows above do not leak into these counts.
|
|
740
|
+
const scope = "agent";
|
|
741
|
+
const scopeId = "seat-filter";
|
|
742
|
+
|
|
743
|
+
beforeAll(async () => {
|
|
744
|
+
await recordKeyUsage(KEY_TYPE, "seat0", 0, null, scope, scopeId);
|
|
745
|
+
await setApiKeyPlan(KEY_TYPE, "seat0", "claude_team_standard");
|
|
746
|
+
await recordKeyUsage(KEY_TYPE, "seat1", 1, null, scope, scopeId);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
test("a claude_team_standard key is seat-blocked for fable", async () => {
|
|
750
|
+
const result = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "fable");
|
|
751
|
+
expect(result.availableIndices).toEqual([1]);
|
|
752
|
+
expect(result.seatBlockedIndices).toEqual([0]);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test("the same key is available for opus and for no model", async () => {
|
|
756
|
+
const opus = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "opus");
|
|
757
|
+
expect(opus.availableIndices).toEqual([0, 1]);
|
|
758
|
+
expect(opus.seatBlockedIndices).toEqual([]);
|
|
759
|
+
const none = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId);
|
|
760
|
+
expect(none.availableIndices).toEqual([0, 1]);
|
|
761
|
+
expect(none.seatBlockedIndices).toEqual([]);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
test("a key with no plan is available for fable", async () => {
|
|
765
|
+
const result = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "fable");
|
|
766
|
+
expect(result.availableIndices).toContain(1);
|
|
767
|
+
expect(result.seatBlockedIndices).not.toContain(1);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test("a rate-limited standard-seat key stays seat-blocked", async () => {
|
|
771
|
+
const otherScopeId = "seat-filter-rl";
|
|
772
|
+
await recordKeyUsage(KEY_TYPE, "seat2", 0, null, scope, otherScopeId);
|
|
773
|
+
await setApiKeyPlan(KEY_TYPE, "seat2", "claude_team_standard");
|
|
774
|
+
await markKeyRateLimited(
|
|
775
|
+
KEY_TYPE,
|
|
776
|
+
"seat2",
|
|
777
|
+
0,
|
|
778
|
+
new Date(Date.now() + 3600_000).toISOString(),
|
|
779
|
+
scope,
|
|
780
|
+
otherScopeId,
|
|
781
|
+
);
|
|
782
|
+
const result = await getAvailableKeyIndices(KEY_TYPE, 1, scope, otherScopeId, "fable");
|
|
783
|
+
expect(result.availableIndices).toEqual([]);
|
|
784
|
+
expect(result.seatBlockedIndices).toEqual([0]);
|
|
785
|
+
expect(result.modelBlockedIndices).toEqual([]);
|
|
786
|
+
|
|
787
|
+
// Admission must not fall back to the key-wide-blocked seat.
|
|
788
|
+
await expect(
|
|
789
|
+
resolveCredentialPools(
|
|
790
|
+
{ CLAUDE_CODE_OAUTH_TOKEN: "tok-seat2" },
|
|
791
|
+
{
|
|
792
|
+
provider: "claude",
|
|
793
|
+
model: "claude-fable-5-1",
|
|
794
|
+
availableIndicesMap: { CLAUDE_CODE_OAUTH_TOKEN: result },
|
|
795
|
+
enforceModelCapacity: true,
|
|
796
|
+
},
|
|
797
|
+
),
|
|
798
|
+
).rejects.toBeInstanceOf(ModelWindowExhaustedError);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test("a Fable-window-blocked standard-seat key stays seat-blocked", async () => {
|
|
802
|
+
const otherScopeId = "seat-filter-window";
|
|
803
|
+
await recordKeyUsage(KEY_TYPE, "seat3", 0, null, scope, otherScopeId);
|
|
804
|
+
await setApiKeyPlan(KEY_TYPE, "seat3", "claude_team_standard");
|
|
805
|
+
await recordKeyRateLimitWindows(
|
|
806
|
+
KEY_TYPE,
|
|
807
|
+
"seat3",
|
|
808
|
+
0,
|
|
809
|
+
{
|
|
810
|
+
seven_day_overage_included: {
|
|
811
|
+
status: "rejected",
|
|
812
|
+
resetsAt: Math.floor(Date.now() / 1000) + 3600,
|
|
813
|
+
lastSeenAt: new Date().toISOString(),
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
scope,
|
|
817
|
+
otherScopeId,
|
|
818
|
+
);
|
|
819
|
+
const result = await getAvailableKeyIndices(KEY_TYPE, 1, scope, otherScopeId, "fable");
|
|
820
|
+
expect(result.availableIndices).toEqual([]);
|
|
821
|
+
expect(result.modelBlockedIndices).toEqual([0]);
|
|
822
|
+
expect(result.seatBlockedIndices).toEqual([0]);
|
|
823
|
+
|
|
824
|
+
// The window-block fallback policy never applies to a seat block.
|
|
825
|
+
await expect(
|
|
826
|
+
resolveCredentialPools(
|
|
827
|
+
{ CLAUDE_CODE_OAUTH_TOKEN: "tok-seat3", MODEL_WINDOW_EXHAUSTED_POLICY: "fallback" },
|
|
828
|
+
{
|
|
829
|
+
provider: "claude",
|
|
830
|
+
model: "claude-fable-5-1",
|
|
831
|
+
availableIndicesMap: { CLAUDE_CODE_OAUTH_TOKEN: result },
|
|
832
|
+
enforceModelCapacity: true,
|
|
833
|
+
},
|
|
834
|
+
),
|
|
835
|
+
).rejects.toBeInstanceOf(ModelWindowExhaustedError);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
test("earliestModelResetAt stays null when only seat blocks exist", async () => {
|
|
839
|
+
const result = await getAvailableKeyIndices(KEY_TYPE, 2, scope, scopeId, "fable");
|
|
840
|
+
expect(result.seatBlockedIndices).toEqual([0]);
|
|
841
|
+
expect(result.earliestModelResetAt).toBeNull();
|
|
842
|
+
});
|
|
843
|
+
});
|
|
477
844
|
});
|
|
478
845
|
});
|
|
479
846
|
|
|
@@ -482,6 +482,9 @@ describe("getBasePrompt: outputs section", () => {
|
|
|
482
482
|
const result = await getBasePrompt({ ...minimalArgs, traits: localTraits });
|
|
483
483
|
expect(result).toContain("## Outputs");
|
|
484
484
|
expect(result).toContain(AGENT_FS_LINE);
|
|
485
|
+
expect(result).toContain(
|
|
486
|
+
"For a file anyone outside the swarm opens, use an `agent-fs share-create` link; `AGENT_FS_LIVE_URL` links are for signed-in teammates.",
|
|
487
|
+
);
|
|
485
488
|
expect(result).not.toContain(NO_AGENT_FS_LINE);
|
|
486
489
|
});
|
|
487
490
|
|
|
@@ -747,10 +750,11 @@ describe("getBasePrompt: tools and skills section", () => {
|
|
|
747
750
|
expect(result).not.toContain("skills directory");
|
|
748
751
|
});
|
|
749
752
|
|
|
750
|
-
test("keeps the section with
|
|
753
|
+
test("keeps the section with direct-tool guidance when no skills are installed", async () => {
|
|
751
754
|
const result = await getBasePrompt({ ...minimalArgs, skillsSummary: [] });
|
|
752
755
|
expect(result).toContain(HEADER);
|
|
753
|
-
expect(result).toContain(
|
|
756
|
+
expect(result).toContain("Swarm tools are already in your tool list.");
|
|
757
|
+
expect(result).not.toContain(DEFERRED_LINE);
|
|
754
758
|
expect(result).not.toContain("Installed skills.");
|
|
755
759
|
expect(result).not.toContain(DISCOVERY_LINE);
|
|
756
760
|
});
|
|
@@ -1100,3 +1104,40 @@ describe("getBasePrompt: output hygiene", () => {
|
|
|
1100
1104
|
});
|
|
1101
1105
|
}
|
|
1102
1106
|
});
|
|
1107
|
+
|
|
1108
|
+
describe("getBasePrompt: harness tool discovery", () => {
|
|
1109
|
+
const deferred =
|
|
1110
|
+
"Most swarm tools are deferred. Load one with your harness tool search before the first call.";
|
|
1111
|
+
|
|
1112
|
+
test("claude with native ToolSearch gets deferred-tool guidance", async () => {
|
|
1113
|
+
const result = await getBasePrompt({
|
|
1114
|
+
...minimalArgs,
|
|
1115
|
+
provider: "claude",
|
|
1116
|
+
traits: { ...localTraits, hasToolSearch: true },
|
|
1117
|
+
});
|
|
1118
|
+
expect(result).toContain(deferred);
|
|
1119
|
+
expect(result).not.toContain("Swarm tools are already in your tool list.");
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
test.each([
|
|
1123
|
+
"pi",
|
|
1124
|
+
"codex",
|
|
1125
|
+
"claude-managed",
|
|
1126
|
+
"opencode",
|
|
1127
|
+
"acp",
|
|
1128
|
+
])("%s without tool search gets direct-tool guidance", async (provider) => {
|
|
1129
|
+
const result = await getBasePrompt({
|
|
1130
|
+
...minimalArgs,
|
|
1131
|
+
provider,
|
|
1132
|
+
traits: { ...localTraits, hasToolSearch: false },
|
|
1133
|
+
});
|
|
1134
|
+
expect(result).not.toContain(deferred);
|
|
1135
|
+
expect(result).toContain("Swarm tools are already in your tool list.");
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
test("omitted tool-search capability defaults to direct tools", async () => {
|
|
1139
|
+
const result = await getBasePrompt({ ...minimalArgs, traits: localTraits });
|
|
1140
|
+
expect(result).not.toContain(deferred);
|
|
1141
|
+
expect(result).toContain("Swarm tools are already in your tool list.");
|
|
1142
|
+
});
|
|
1143
|
+
});
|
|
@@ -124,6 +124,16 @@ describe("ClaudeSession spawn env — reasoning_effort", () => {
|
|
|
124
124
|
expect(spawnedEnvs[0]?.CLAUDE_CODE_EFFORT_LEVEL).toBe("high");
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
test("reasoningEffort: 'high' on the tier-default shortname opus sets CLAUDE_CODE_EFFORT_LEVEL", async () => {
|
|
128
|
+
const adapter = new ClaudeAdapter(async () => {});
|
|
129
|
+
await adapter.createSession(
|
|
130
|
+
makeConfig({ model: "opus", reasoningEffort: "high", env: CLEAN_ENV }),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(spawnedEnvs).toHaveLength(1);
|
|
134
|
+
expect(spawnedEnvs[0]?.CLAUDE_CODE_EFFORT_LEVEL).toBe("high");
|
|
135
|
+
});
|
|
136
|
+
|
|
127
137
|
test("reasoningEffort: 'off' on a legacy budget_tokens-capable model sets MAX_THINKING_TOKENS=0, no effort env", async () => {
|
|
128
138
|
const adapter = new ClaudeAdapter(async () => {});
|
|
129
139
|
await adapter.createSession(
|
|
@@ -291,6 +301,64 @@ describe("ClaudeSession processStreams — ProviderResult.output capture", () =>
|
|
|
291
301
|
expect(summaryOpts!.env?.AGENT_SWARM_TASK_ID).toBe("test-task-id");
|
|
292
302
|
});
|
|
293
303
|
|
|
304
|
+
test("a credits_required rate_limit_event reaches the log file and raw_log without the seat payload", async () => {
|
|
305
|
+
const seatEvent = JSON.stringify({
|
|
306
|
+
type: "rate_limit_event",
|
|
307
|
+
rate_limit_info: {
|
|
308
|
+
status: "rejected",
|
|
309
|
+
resetsAt: 1790812800,
|
|
310
|
+
overageDisabledReason: "member_zero_credit_limit",
|
|
311
|
+
isUsingOverage: false,
|
|
312
|
+
errorCode: "credits_required",
|
|
313
|
+
canUserPurchaseCredits: false,
|
|
314
|
+
hasChargeableSavedPaymentMethod: true,
|
|
315
|
+
},
|
|
316
|
+
uuid: "bff51e54-9777-458a-aceb-b50aaa4255b5",
|
|
317
|
+
session_id: "9d326326-0c59-4b00-b819-b9b7dd3bf99f",
|
|
318
|
+
});
|
|
319
|
+
spawnSpy.mockImplementation((() =>
|
|
320
|
+
makeStreamingFakeProc([
|
|
321
|
+
seatEvent,
|
|
322
|
+
assistantLine([{ type: "text", text: "done" }]),
|
|
323
|
+
])) as typeof Bun.spawn);
|
|
324
|
+
|
|
325
|
+
const logDir = await mkdtemp(join(tmpdir(), "claude-seat-egress-"));
|
|
326
|
+
const logFile = join(logDir, "session.jsonl");
|
|
327
|
+
try {
|
|
328
|
+
const adapter = new ClaudeAdapter(async () => {});
|
|
329
|
+
const session = await adapter.createSession(makeConfig({ env: CLEAN_ENV, logFile }));
|
|
330
|
+
const rawLogs: string[] = [];
|
|
331
|
+
session.onEvent((event) => {
|
|
332
|
+
if (event.type === "raw_log") rawLogs.push(event.content);
|
|
333
|
+
});
|
|
334
|
+
const result = await session.waitForCompletion();
|
|
335
|
+
|
|
336
|
+
// Error tracking still reads the original event.
|
|
337
|
+
expect(result.creditsRequired?.overageDisabledReason).toBe("member_zero_credit_limit");
|
|
338
|
+
|
|
339
|
+
const fileLog = await Bun.file(logFile).text();
|
|
340
|
+
const egress = [fileLog, ...rawLogs];
|
|
341
|
+
for (const content of egress) {
|
|
342
|
+
expect(content).not.toContain("overageDisabledReason");
|
|
343
|
+
expect(content).not.toContain("member_zero_credit_limit");
|
|
344
|
+
expect(content).not.toContain("canUserPurchaseCredits");
|
|
345
|
+
expect(content).not.toContain("hasChargeableSavedPaymentMethod");
|
|
346
|
+
}
|
|
347
|
+
const loggedEvent = rawLogs.find((line) => line.includes('"rate_limit_event"'));
|
|
348
|
+
expect(loggedEvent).toBeDefined();
|
|
349
|
+
expect(JSON.parse(loggedEvent!).rate_limit_info).toEqual({
|
|
350
|
+
status: "rejected",
|
|
351
|
+
resetsAt: 1790812800,
|
|
352
|
+
isUsingOverage: false,
|
|
353
|
+
errorCode: "credits_required",
|
|
354
|
+
});
|
|
355
|
+
expect(fileLog).toContain('"errorCode":"credits_required"');
|
|
356
|
+
expect(fileLog).toContain('"text":"done"');
|
|
357
|
+
} finally {
|
|
358
|
+
await rm(logDir, { recursive: true, force: true });
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
294
362
|
test("tool_use-only, thinking-only, and empty-text turns are skipped, not captured", async () => {
|
|
295
363
|
const lines = [
|
|
296
364
|
assistantLine([{ type: "text", text: "Real answer" }]),
|