@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
package/src/be/seed/runner.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { Seeder, SeederResult, SeederRunOptions } from "./types";
|
|
|
10
10
|
/**
|
|
11
11
|
* Apply one seeder. Idempotent and version-aware:
|
|
12
12
|
* - upstream absent -> create
|
|
13
|
+
* - upstream identical to src -> no-op (re-baselines seed state)
|
|
13
14
|
* - upstream pristine, src moved -> update
|
|
14
15
|
* - upstream pristine, src same -> no-op
|
|
15
16
|
* - upstream user-modified -> preserve (never overwrite)
|
|
@@ -38,12 +39,25 @@ export async function runSeeder(seeder: Seeder, opts?: SeederRunOptions): Promis
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
const state = await getSeedState(seeder.kind, item.key);
|
|
42
|
+
|
|
43
|
+
if (upstream === item.contentHash) {
|
|
44
|
+
// Live copy is byte-identical to the current source, so there is nothing
|
|
45
|
+
// user-authored to lose. Treat it as in sync regardless of seed state:
|
|
46
|
+
// adopt an unrecorded entity, and re-baseline one whose recorded hash
|
|
47
|
+
// drifted (edited, then reset to source) so the next source change is
|
|
48
|
+
// detectable as an update instead of being preserved forever.
|
|
49
|
+
if (state?.seededHash !== item.contentHash) {
|
|
50
|
+
await recordSeedState(seeder.kind, item.key, item.contentHash);
|
|
51
|
+
}
|
|
52
|
+
result.skippedUnchanged += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
// "Pristine" = the live upstream copy still matches what we last seeded.
|
|
42
57
|
// With no recorded state (first run after this framework landed, or a
|
|
43
|
-
// pre-existing entity)
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
const pristine = state ? upstream === state.seededHash : upstream === item.contentHash;
|
|
58
|
+
// pre-existing entity) the live copy already differs from source here, so
|
|
59
|
+
// we conservatively assume a user authored it and must not clobber it.
|
|
60
|
+
const pristine = state ? upstream === state.seededHash : false;
|
|
47
61
|
|
|
48
62
|
if (!pristine) {
|
|
49
63
|
// A user changed the upstream copy since our last seed — preserve it.
|
|
@@ -51,14 +65,6 @@ export async function runSeeder(seeder: Seeder, opts?: SeederRunOptions): Promis
|
|
|
51
65
|
continue;
|
|
52
66
|
}
|
|
53
67
|
|
|
54
|
-
if (upstream === item.contentHash) {
|
|
55
|
-
// Neither side changed. Adopt an unrecorded-but-identical entity so the
|
|
56
|
-
// next source change is correctly detectable as an update.
|
|
57
|
-
if (!state) await recordSeedState(seeder.kind, item.key, item.contentHash);
|
|
58
|
-
result.skippedUnchanged += 1;
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
68
|
// Pristine upstream + changed source -> update to the new source version.
|
|
63
69
|
await seeder.apply(item, "update", opts);
|
|
64
70
|
await recordSeedState(seeder.kind, item.key, item.contentHash);
|
package/src/be/seed/types.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Versioning rule (per item, evaluated against the `seed_state` table):
|
|
11
11
|
* - upstream entity absent -> create
|
|
12
|
+
* - upstream identical to source -> no-op (seed state re-baselined)
|
|
12
13
|
* - upstream pristine + source changed -> update
|
|
13
14
|
* - upstream pristine + source same -> no-op
|
|
14
15
|
* - upstream user-modified -> never overwrite (preserve)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { publishCatalogReportPage } from "./catalog-report";
|
|
3
3
|
|
|
4
|
+
const MAX_TASK_SCAN_LIMIT = 500;
|
|
5
|
+
|
|
4
6
|
export const argsSchema = z.object({
|
|
5
7
|
days: z
|
|
6
8
|
.number()
|
|
@@ -16,8 +18,9 @@ export const argsSchema = z.object({
|
|
|
16
18
|
.number()
|
|
17
19
|
.int()
|
|
18
20
|
.positive()
|
|
21
|
+
.max(MAX_TASK_SCAN_LIMIT)
|
|
19
22
|
.optional()
|
|
20
|
-
.describe("Max failed tasks to scan (default 500)"),
|
|
23
|
+
.describe("Max failed tasks to scan (default 500, max 500)"),
|
|
21
24
|
publishPage: z.boolean().optional().describe("Publish an authed HTML page (default true)"),
|
|
22
25
|
});
|
|
23
26
|
|
|
@@ -25,20 +28,37 @@ const REASON_PATTERNS: any[] = [
|
|
|
25
28
|
{ key: "sigterm/killed", re: /sigterm|sigkill|killed|143|137/i },
|
|
26
29
|
{ key: "timeout", re: /time?d?\s*out|timeout|deadline/i },
|
|
27
30
|
{ key: "context-window", re: /context (window|limit|saturat)|peakcontext|compact/i },
|
|
31
|
+
{ key: "reboot-sweep", re: /reboot sweep/i },
|
|
28
32
|
{ key: "not-found", re: /not found|404|missing|no such/i },
|
|
29
33
|
{ key: "auth/credentials", re: /unauthorized|401|403|credential|token|forbidden/i },
|
|
30
|
-
{ key: "ci/checks-failed", re:
|
|
34
|
+
{ key: "ci/checks-failed", re: /\bci\b|check.?s? fail|lint|tsc|test.?s? fail/i },
|
|
31
35
|
{ key: "network", re: /network|econn|fetch failed|socket|dns|502|503|504/i },
|
|
32
36
|
{ key: "cancelled", re: /cancel|aborted/i },
|
|
33
37
|
];
|
|
34
38
|
|
|
39
|
+
function sanitizeUntrustedReasonSample(reason: unknown): string {
|
|
40
|
+
return String(reason)
|
|
41
|
+
.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
|
|
42
|
+
.replace(/\s+/g, " ")
|
|
43
|
+
.trim()
|
|
44
|
+
.slice(0, 200);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rowsToObjects(result: any): any[] {
|
|
48
|
+
const payload = result?.data ?? result;
|
|
49
|
+
const columns: string[] = payload?.columns ?? [];
|
|
50
|
+
return (payload?.rows ?? []).map((row: any) =>
|
|
51
|
+
Array.isArray(row) ? Object.fromEntries(columns.map((column, i) => [column, row[i]])) : row,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
function reasonCluster(reason: string): string {
|
|
36
56
|
const r = (reason || "").trim();
|
|
37
57
|
if (!r) return "(no reason given)";
|
|
38
58
|
for (const p of REASON_PATTERNS) {
|
|
39
59
|
if (p.re.test(r)) return p.key;
|
|
40
60
|
}
|
|
41
|
-
return
|
|
61
|
+
return "other";
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
/** Cluster recently failed swarm tasks by reason, agent, or schedule. */
|
|
@@ -47,7 +67,7 @@ export default async function taskFailureAudit(args: any, ctx: any) {
|
|
|
47
67
|
if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
|
|
48
68
|
const days = parsed.data.days || 7;
|
|
49
69
|
const groupBy = parsed.data.groupBy || "reason";
|
|
50
|
-
const limit = parsed.data.limit
|
|
70
|
+
const limit = Math.min(parsed.data.limit ?? MAX_TASK_SCAN_LIMIT, MAX_TASK_SCAN_LIMIT);
|
|
51
71
|
const publishPage = parsed.data.publishPage !== false;
|
|
52
72
|
|
|
53
73
|
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
@@ -62,17 +82,49 @@ export default async function taskFailureAudit(args: any, ctx: any) {
|
|
|
62
82
|
const payload: any = res && res.data ? res.data : res;
|
|
63
83
|
const tasks: any = payload && Array.isArray(payload.tasks) ? payload.tasks : [];
|
|
64
84
|
|
|
85
|
+
const failureReasons = new Map<string, string>();
|
|
86
|
+
// Projected for every groupBy mode: task_list omits failureReason, and
|
|
87
|
+
// schedule/agent groups still need a populated sample.
|
|
88
|
+
const tasksNeedingReason = tasks.filter((task: any) => !task.failureReason && task.id);
|
|
89
|
+
const taskIds = tasksNeedingReason.map((task: any) => task.id as string);
|
|
90
|
+
if (taskIds.length > 0) {
|
|
91
|
+
const placeholders = taskIds.map(() => "?").join(", ");
|
|
92
|
+
const reasonResult: any = await ctx.swarm.db_query({
|
|
93
|
+
sql: `SELECT id, failureReason FROM agent_tasks WHERE id IN (${placeholders})`,
|
|
94
|
+
params: taskIds,
|
|
95
|
+
});
|
|
96
|
+
const reasonPayload: any = reasonResult?.data ?? reasonResult;
|
|
97
|
+
if (
|
|
98
|
+
reasonResult?.success === false ||
|
|
99
|
+
reasonPayload?.success === false ||
|
|
100
|
+
reasonPayload?.error
|
|
101
|
+
) {
|
|
102
|
+
return { error: "failure reason projection failed with status " + reasonResult?.status };
|
|
103
|
+
}
|
|
104
|
+
if (reasonPayload?.truncated) {
|
|
105
|
+
return {
|
|
106
|
+
error: `failure reason projection truncated (${reasonPayload.rows?.length ?? 0} of ${reasonPayload.total ?? "unknown"} rows)`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
for (const row of rowsToObjects(reasonResult)) {
|
|
110
|
+
if (typeof row?.id === "string" && typeof row.failureReason === "string") {
|
|
111
|
+
failureReasons.set(row.id, row.failureReason);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
const groups: any = {};
|
|
66
117
|
for (const t of tasks) {
|
|
67
118
|
let key: string;
|
|
68
119
|
if (groupBy === "agent") key = t.agentId || "(unassigned)";
|
|
69
120
|
else if (groupBy === "schedule") key = t.scheduleId || "(not scheduled)";
|
|
70
|
-
else key = reasonCluster(t.failureReason || "");
|
|
71
|
-
if (!groups[key]) groups[key] = { key, count: 0, taskIds: [],
|
|
121
|
+
else key = reasonCluster(failureReasons.get(t.id) || t.failureReason || "");
|
|
122
|
+
if (!groups[key]) groups[key] = { key, count: 0, taskIds: [], untrustedWorkerTextSample: "" };
|
|
72
123
|
groups[key].count++;
|
|
73
124
|
if (groups[key].taskIds.length < 5) groups[key].taskIds.push(t.id);
|
|
74
|
-
|
|
75
|
-
|
|
125
|
+
const failureReason = failureReasons.get(t.id) || t.failureReason;
|
|
126
|
+
if (!groups[key].untrustedWorkerTextSample && failureReason) {
|
|
127
|
+
groups[key].untrustedWorkerTextSample = sanitizeUntrustedReasonSample(failureReason);
|
|
76
128
|
}
|
|
77
129
|
}
|
|
78
130
|
|
|
@@ -118,7 +170,7 @@ export default async function taskFailureAudit(args: any, ctx: any) {
|
|
|
118
170
|
key: group.key,
|
|
119
171
|
count: group.count,
|
|
120
172
|
taskIds: group.taskIds,
|
|
121
|
-
|
|
173
|
+
untrustedWorkerTextSample: group.untrustedWorkerTextSample,
|
|
122
174
|
},
|
|
123
175
|
],
|
|
124
176
|
})),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { normalizeSlackReactionShortcode } from "../slack/reaction-shortcode";
|
|
2
2
|
import { ProviderNameSchema } from "../types";
|
|
3
3
|
import { parseTaskToolManifest } from "../utils/task-tool-manifest";
|
|
4
|
+
import { isTierConfigKey, validateTierConfigValue } from "./model-tier-keys";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Guards against storing reserved keys in the swarm_config table.
|
|
@@ -274,6 +275,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
274
275
|
return null;
|
|
275
276
|
},
|
|
276
277
|
...booleanValidators([
|
|
278
|
+
"SWARM_DEV_MODE",
|
|
277
279
|
"MULTI_RUNTIME_ENABLED",
|
|
278
280
|
"STEERING_ENABLED",
|
|
279
281
|
"MEMORY_HYBRID_SEARCH",
|
|
@@ -283,6 +285,8 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
283
285
|
"POOL_AFFINITY_ENFORCEMENT",
|
|
284
286
|
"SCRIPTS_ONLY_MCP",
|
|
285
287
|
"TASK_TOOL_PRELOAD_ENABLED",
|
|
288
|
+
"PI_TOOL_DEFERRAL",
|
|
289
|
+
"PI_CODEMODE",
|
|
286
290
|
"SLACK_DISABLE",
|
|
287
291
|
"SLACK_RENDER_V2",
|
|
288
292
|
"SLACK_RENDER_V2_DELEGATION",
|
|
@@ -304,6 +308,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
304
308
|
"SWARM_HIDE_CLOUD_PROMO",
|
|
305
309
|
"DB_QUERY_BOUNDED_ENABLED",
|
|
306
310
|
"DB_RETENTION_DRY_RUN",
|
|
311
|
+
"MODEL_AUTO_UPGRADE",
|
|
307
312
|
]),
|
|
308
313
|
...enumValidator("SLACK_MODE", ["socket", "http"]),
|
|
309
314
|
...enumValidator("SLACK_THREAD_STEERING", ["off", "lead", "all"]),
|
|
@@ -359,6 +364,8 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
359
364
|
),
|
|
360
365
|
// 0 is meaningful here: "auto-assign nothing this sweep".
|
|
361
366
|
...integerValidators(["HEARTBEAT_MAX_AUTO_ASSIGN"], 0),
|
|
367
|
+
// 0 disables the `latest:...@stable` soak window.
|
|
368
|
+
...integerValidators(["MODEL_LATEST_SOAK_DAYS"], 0),
|
|
362
369
|
// 0 turns approval auto-cancellation off.
|
|
363
370
|
...integerValidators(["APPROVAL_REQUEST_AUTO_CANCELLATION_DAYS"], 0),
|
|
364
371
|
// Below ~100 tokens the preamble can't fit a useful summary; above 20000
|
|
@@ -378,6 +385,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
378
385
|
};
|
|
379
386
|
|
|
380
387
|
export function validateConfigValue(key: string, value: unknown): string | null {
|
|
388
|
+
if (isTierConfigKey(key)) return validateTierConfigValue(key.toUpperCase(), value);
|
|
381
389
|
const validator = VALIDATED_KEYS[key.toUpperCase()];
|
|
382
390
|
return validator ? validator(value) : null;
|
|
383
391
|
}
|
package/src/cli.tsx
CHANGED
|
@@ -717,6 +717,16 @@ if (args.showHelp || args.command === "help" || args.command === undefined) {
|
|
|
717
717
|
} else if (args.command === "version") {
|
|
718
718
|
console.log(`${binName} v${pkg.version}`);
|
|
719
719
|
process.exit(0);
|
|
720
|
+
} else if (args.command === "onboard" && !process.versions.bun) {
|
|
721
|
+
// The published bin uses Node, but the onboarding wizard requires Bun APIs.
|
|
722
|
+
const { spawnSync } = await import("node:child_process");
|
|
723
|
+
const result = spawnSync("bun", process.argv.slice(1), { stdio: "inherit" });
|
|
724
|
+
if (result.error) {
|
|
725
|
+
console.error(
|
|
726
|
+
"Onboarding requires Bun. Install Bun from https://bun.sh, then retry this command.",
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
process.exit(result.status ?? 1);
|
|
720
730
|
} else if (args.command === "docs") {
|
|
721
731
|
const docsUrl = "https://docs.agent-swarm.dev";
|
|
722
732
|
console.log(`\n${binName} docs — v${pkg.version}\n`);
|
|
@@ -34,6 +34,7 @@ import type { SkillCreateResponse } from "@anthropic-ai/sdk/resources/beta/skill
|
|
|
34
34
|
import { toFile } from "@anthropic-ai/sdk/uploads";
|
|
35
35
|
|
|
36
36
|
import { buildSkillContent, type SkillTemplateConfig } from "../be/seed-skills/render";
|
|
37
|
+
import { DEFAULT_MODEL_TIER_MAP } from "../types";
|
|
37
38
|
import { getApiKey } from "../utils/api-key";
|
|
38
39
|
import { promptHiddenInput } from "./codex-login.js";
|
|
39
40
|
|
|
@@ -65,7 +66,8 @@ export type ClaudeManagedSetupResult = {
|
|
|
65
66
|
|
|
66
67
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
/** The claude-managed `regular` tier default — no per-model constant here. */
|
|
70
|
+
const DEFAULT_AGENT_MODEL = DEFAULT_MODEL_TIER_MAP["claude-managed"].regular;
|
|
69
71
|
const SKILLS_DIR_RELATIVE = "plugin/commands";
|
|
70
72
|
const SEEDED_SKILLS_DIR_RELATIVE = "templates/skills";
|
|
71
73
|
const SKILLS_BETA_HEADER = "skills-2025-10-02";
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { exec } from "node:child_process";
|
|
13
13
|
import { emitKeypressEvents } from "node:readline";
|
|
14
14
|
|
|
15
|
+
import { deriveCodexKeySuffix } from "../providers/codex-oauth/auth-json.js";
|
|
15
16
|
import { loginCodexOAuth } from "../providers/codex-oauth/flow.js";
|
|
16
17
|
import {
|
|
17
18
|
loadAllCodexOAuthSlots,
|
|
@@ -35,11 +36,59 @@ type RunCodexLoginDeps = {
|
|
|
35
36
|
login?: typeof loginCodexOAuth;
|
|
36
37
|
store?: typeof storeCodexOAuth;
|
|
37
38
|
loadAllSlots?: typeof loadAllCodexOAuthSlots;
|
|
39
|
+
clearAuthBench?: typeof clearCodexAuthBench;
|
|
40
|
+
readAuthFence?: typeof readCodexAuthFailureFence;
|
|
38
41
|
log?: (message: string) => void;
|
|
39
42
|
error?: (message: string) => void;
|
|
40
43
|
exit?: (code: number) => void;
|
|
41
44
|
};
|
|
42
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Read the API's `authFailureFence` for the Codex pool. `runCodexLogin` reads it
|
|
48
|
+
* BEFORE it stores the fresh credentials, so the clear below lifts only failures
|
|
49
|
+
* recorded before that write.
|
|
50
|
+
*/
|
|
51
|
+
export async function readCodexAuthFailureFence(apiUrl: string, apiKey: string): Promise<number> {
|
|
52
|
+
const resp = await fetch(`${apiUrl}/api/keys/available?keyType=CODEX_OAUTH&totalKeys=1`, {
|
|
53
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
54
|
+
});
|
|
55
|
+
if (!resp.ok) throw new Error(`keys/available returned ${resp.status}`);
|
|
56
|
+
const data = (await resp.json()) as { authFailureFence?: number };
|
|
57
|
+
if (typeof data.authFailureFence !== "number") throw new Error("no authFailureFence in response");
|
|
58
|
+
return data.authFailureFence;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Lift an auth-failure bench on the re-logged key. A fresh login is proof of
|
|
63
|
+
* health, so it passes `clearAuthBench: true` (the API also deletes the
|
|
64
|
+
* `codex-auth-watch` bench marker). `authFence` binds the clear to the credential
|
|
65
|
+
* write: failures recorded after the fresh login survive a late clear. It also
|
|
66
|
+
* passes the slot, so the API retires a different login that the slot held
|
|
67
|
+
* before. Returns `true` when a bench was lifted.
|
|
68
|
+
*/
|
|
69
|
+
export async function clearCodexAuthBench(
|
|
70
|
+
apiUrl: string,
|
|
71
|
+
apiKey: string,
|
|
72
|
+
keySuffix: string,
|
|
73
|
+
slot: number,
|
|
74
|
+
authFence: number,
|
|
75
|
+
): Promise<boolean> {
|
|
76
|
+
const resp = await fetch(`${apiUrl}/api/keys/clear-rate-limit`, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
keyType: "CODEX_OAUTH",
|
|
81
|
+
keySuffix,
|
|
82
|
+
clearAuthBench: true,
|
|
83
|
+
keyIndex: slot,
|
|
84
|
+
authFence,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
if (!resp.ok) throw new Error(`clear-rate-limit returned ${resp.status}`);
|
|
88
|
+
const data = (await resp.json()) as { cleared?: boolean };
|
|
89
|
+
return data.cleared === true;
|
|
90
|
+
}
|
|
91
|
+
|
|
43
92
|
type ParsedCodexLoginArgs = {
|
|
44
93
|
apiUrl?: string;
|
|
45
94
|
apiKey?: string;
|
|
@@ -207,6 +256,8 @@ export async function runCodexLogin(args: string[], deps: RunCodexLoginDeps = {}
|
|
|
207
256
|
const login = deps.login ?? loginCodexOAuth;
|
|
208
257
|
const store = deps.store ?? storeCodexOAuth;
|
|
209
258
|
const loadAllSlots = deps.loadAllSlots ?? loadAllCodexOAuthSlots;
|
|
259
|
+
const clearAuthBench = deps.clearAuthBench ?? clearCodexAuthBench;
|
|
260
|
+
const readAuthFence = deps.readAuthFence ?? readCodexAuthFailureFence;
|
|
210
261
|
const log = deps.log ?? console.log;
|
|
211
262
|
const error = deps.error ?? console.error;
|
|
212
263
|
const exit = deps.exit ?? ((code: number) => process.exit(code));
|
|
@@ -283,11 +334,27 @@ export async function runCodexLogin(args: string[], deps: RunCodexLoginDeps = {}
|
|
|
283
334
|
log(` Account ID: ${creds.accountId}`);
|
|
284
335
|
log(` Expires: ${new Date(creds.expires).toISOString()}`);
|
|
285
336
|
|
|
337
|
+
// Read the auth-failure fence before the credential write (see clearCodexAuthBench).
|
|
338
|
+
const authFence = await readAuthFence(apiUrl, apiKey).catch((err: unknown) => err);
|
|
339
|
+
|
|
286
340
|
// Store credentials in the swarm API config store
|
|
287
341
|
log("\nStoring credentials in swarm API config store...");
|
|
288
342
|
await store(apiUrl, apiKey, creds, slot);
|
|
289
343
|
log(`Credentials stored successfully in slot ${slot}!`);
|
|
290
344
|
|
|
345
|
+
// A re-login returns an auth-benched slot to the pool right away.
|
|
346
|
+
// Non-fatal: the credentials are stored; a later re-login also lifts it.
|
|
347
|
+
const keySuffix = deriveCodexKeySuffix(creds.access, creds.accountId);
|
|
348
|
+
try {
|
|
349
|
+
if (typeof authFence !== "number") throw authFence;
|
|
350
|
+
if (await clearAuthBench(apiUrl, apiKey, keySuffix, slot, authFence)) {
|
|
351
|
+
log(`Lifted the auth-failure bench on key ...${keySuffix}.`);
|
|
352
|
+
}
|
|
353
|
+
} catch (err) {
|
|
354
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
355
|
+
error(`Warning: could not lift the auth-failure bench on key ...${keySuffix}: ${message}`);
|
|
356
|
+
}
|
|
357
|
+
|
|
291
358
|
log("\nDeployed Codex workers will automatically restore these credentials at boot.");
|
|
292
359
|
} catch (err) {
|
|
293
360
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { RateLimitWindowTelemetry } from "../utils/error-tracker";
|
|
2
|
+
import { modelFamilyOf } from "../utils/model-rate-limit-windows";
|
|
3
|
+
import { buildFinalRateLimitWindows, classifyRateLimitOutcome } from "./rate-limit-outcome";
|
|
4
|
+
import { reportSeatMismatchOutcome } from "./seat-mismatch-report";
|
|
5
|
+
|
|
6
|
+
/** Report a rate-limited key to the API (fire-and-forget) */
|
|
7
|
+
async function reportKeyRateLimit(
|
|
8
|
+
apiUrl: string,
|
|
9
|
+
apiKey: string,
|
|
10
|
+
keyType: string,
|
|
11
|
+
keySuffix: string,
|
|
12
|
+
keyIndex: number,
|
|
13
|
+
rateLimitedUntil: string,
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
try {
|
|
16
|
+
await fetch(`${apiUrl}/api/keys/report-rate-limit`, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: {
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
Authorization: `Bearer ${apiKey}`,
|
|
21
|
+
},
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
keyType,
|
|
24
|
+
keySuffix,
|
|
25
|
+
keyIndex,
|
|
26
|
+
rateLimitedUntil,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
console.log(
|
|
30
|
+
`[credentials] Reported key ...${keySuffix} as rate-limited until ${rateLimitedUntil}`,
|
|
31
|
+
);
|
|
32
|
+
} catch {
|
|
33
|
+
// Non-blocking
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Reports rate-limit window telemetry for a key. Returns the underlying
|
|
39
|
+
* fetch promise (does not swallow errors) so a caller that needs the post to
|
|
40
|
+
* complete before the task finishes (a model-scoped block) can await it and
|
|
41
|
+
* decide how to handle a failure; a caller that wants the legacy
|
|
42
|
+
* fire-and-forget behavior appends `.catch(() => {})`.
|
|
43
|
+
*
|
|
44
|
+
* Throws on a non-2xx response so a failed persistence surfaces to the
|
|
45
|
+
* caller instead of logging success while only the in-process guard took
|
|
46
|
+
* effect — otherwise other workers redraw the same exhausted key at once.
|
|
47
|
+
*
|
|
48
|
+
* `logKeySuffix` defaults to true for the legacy full-telemetry call site;
|
|
49
|
+
* the model-scoped call site passes false since it already logs the model
|
|
50
|
+
* family and key index itself (see the `[credential] model window ...`
|
|
51
|
+
* log above the call).
|
|
52
|
+
*/
|
|
53
|
+
export async function reportKeyRateLimitWindows(
|
|
54
|
+
apiUrl: string,
|
|
55
|
+
apiKey: string,
|
|
56
|
+
keyType: string,
|
|
57
|
+
keySuffix: string,
|
|
58
|
+
keyIndex: number,
|
|
59
|
+
windows: RateLimitWindowTelemetry,
|
|
60
|
+
logKeySuffix = true,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
if (Object.keys(windows).length === 0) return;
|
|
63
|
+
const response = await fetch(`${apiUrl}/api/keys/report-rate-limit-windows`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
Authorization: `Bearer ${apiKey}`,
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
keyType,
|
|
71
|
+
keySuffix,
|
|
72
|
+
keyIndex,
|
|
73
|
+
windows,
|
|
74
|
+
}),
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Failed to report rate-limit windows for key #${keyIndex}: HTTP ${response.status}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (logKeySuffix) {
|
|
82
|
+
console.log(`[credentials] Reported rate-limit windows for key ...${keySuffix}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CredentialOutcomeInput {
|
|
87
|
+
apiUrl: string;
|
|
88
|
+
apiKey: string;
|
|
89
|
+
credentialInfo?: { keyType: string; keySuffix: string; keyIndex: number };
|
|
90
|
+
/** The finished session's `ProviderResult`. */
|
|
91
|
+
result: Parameters<typeof classifyRateLimitOutcome>[0];
|
|
92
|
+
failureReason: string | undefined;
|
|
93
|
+
model: string | undefined;
|
|
94
|
+
codexCreditsExhaustedCooldownMs: number;
|
|
95
|
+
/** Runner-local model window guard, keyed `keyType:keyIndex:window`. */
|
|
96
|
+
modelWindowBlocks: Map<string, number>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Reports a finished session's credential outcome, then calls `finish`.
|
|
101
|
+
*
|
|
102
|
+
* If rate-limited and we know which key was used, report it. Codex adapter
|
|
103
|
+
* prefixes failure reasons with `[rate-limit]` / `[usage-limit]` (see
|
|
104
|
+
* codex-adapter.formatTerminalError); Claude surfaces "rate limit" / "hit
|
|
105
|
+
* your limit" via SessionErrorTracker.
|
|
106
|
+
*
|
|
107
|
+
* classifyRateLimitOutcome tests model-scoped windows (Fable/Opus/Sonnet
|
|
108
|
+
* weekly limits) before the legacy key-wide gate, so a model-scoped
|
|
109
|
+
* rejection blocks only that model family on this key — never the whole
|
|
110
|
+
* key — via report-rate-limit-windows instead of report-rate-limit. A
|
|
111
|
+
* key-wide rejection seen in the same session is still reported alongside
|
|
112
|
+
* it (windows are independent). The session's window telemetry and the
|
|
113
|
+
* classified model rejection go out as ONE payload, so an older `allowed`
|
|
114
|
+
* snapshot never overwrites the terminal rejection.
|
|
115
|
+
*
|
|
116
|
+
* Reports that gate admission on other workers (a seat mismatch, a model
|
|
117
|
+
* window rejection) land before `finish` runs. Plain telemetry and the
|
|
118
|
+
* key-wide report stay fire-and-forget.
|
|
119
|
+
*/
|
|
120
|
+
export async function reportCredentialOutcomeThenFinish(
|
|
121
|
+
input: CredentialOutcomeInput,
|
|
122
|
+
finish: () => Promise<void>,
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
const { apiUrl, apiKey, credentialInfo, result, failureReason, model } = input;
|
|
125
|
+
if (credentialInfo) {
|
|
126
|
+
const outcome = classifyRateLimitOutcome(
|
|
127
|
+
result,
|
|
128
|
+
failureReason,
|
|
129
|
+
Date.now(),
|
|
130
|
+
input.codexCreditsExhaustedCooldownMs,
|
|
131
|
+
modelFamilyOf(model),
|
|
132
|
+
);
|
|
133
|
+
// A seat mismatch is not a rate limit: the key stays available for
|
|
134
|
+
// every model its seat can run. A seat outcome carries
|
|
135
|
+
// `keyRateLimitedUntil` only for an independent key-wide rejection seen
|
|
136
|
+
// earlier in the same session, which is still reported below.
|
|
137
|
+
if (outcome.kind === "seat") {
|
|
138
|
+
await reportSeatMismatchOutcome(apiUrl, apiKey, credentialInfo, outcome.model);
|
|
139
|
+
}
|
|
140
|
+
const keyRateLimitedUntil =
|
|
141
|
+
outcome.kind === "key"
|
|
142
|
+
? outcome.rateLimitedUntil
|
|
143
|
+
: outcome.kind === "model" || outcome.kind === "seat"
|
|
144
|
+
? outcome.keyRateLimitedUntil
|
|
145
|
+
: undefined;
|
|
146
|
+
if (keyRateLimitedUntil) {
|
|
147
|
+
console.log(`[credentials] Rate limit reset: ${keyRateLimitedUntil}`);
|
|
148
|
+
reportKeyRateLimit(
|
|
149
|
+
apiUrl,
|
|
150
|
+
apiKey,
|
|
151
|
+
credentialInfo.keyType,
|
|
152
|
+
credentialInfo.keySuffix,
|
|
153
|
+
credentialInfo.keyIndex,
|
|
154
|
+
keyRateLimitedUntil,
|
|
155
|
+
).catch(() => {});
|
|
156
|
+
}
|
|
157
|
+
if (outcome.kind === "model") {
|
|
158
|
+
const resetsAtIso = new Date(outcome.resetsAtSec * 1000).toISOString();
|
|
159
|
+
const blockKey = `${credentialInfo.keyType}:${credentialInfo.keyIndex}:${outcome.window}`;
|
|
160
|
+
input.modelWindowBlocks.set(blockKey, outcome.resetsAtSec * 1000);
|
|
161
|
+
console.log(
|
|
162
|
+
`[credential] ${outcome.model} weekly window exhausted (${outcome.window}) on key #${credentialInfo.keyIndex} until ${resetsAtIso}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const finalWindows = buildFinalRateLimitWindows(
|
|
167
|
+
result.rateLimitWindows,
|
|
168
|
+
outcome,
|
|
169
|
+
new Date().toISOString(),
|
|
170
|
+
);
|
|
171
|
+
if (finalWindows) {
|
|
172
|
+
const report = reportKeyRateLimitWindows(
|
|
173
|
+
apiUrl,
|
|
174
|
+
apiKey,
|
|
175
|
+
credentialInfo.keyType,
|
|
176
|
+
credentialInfo.keySuffix,
|
|
177
|
+
credentialInfo.keyIndex,
|
|
178
|
+
finalWindows,
|
|
179
|
+
outcome.kind !== "model",
|
|
180
|
+
).catch((err) => {
|
|
181
|
+
console.warn(
|
|
182
|
+
`[credential] Failed to report rate-limit windows: ${err instanceof Error ? err.message : String(err)}`,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
// A model rejection gates admission on other workers: land it before
|
|
186
|
+
// the task finishes. Plain telemetry stays fire-and-forget.
|
|
187
|
+
if (outcome.kind === "model") await report;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await finish();
|
|
191
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "../utils/error-tracker";
|
|
9
9
|
import {
|
|
10
10
|
type ModelFamily,
|
|
11
|
+
parseCreditsRequiredMessage,
|
|
11
12
|
parseModelLimitMessage,
|
|
12
13
|
windowForModelFamily,
|
|
13
14
|
} from "../utils/model-rate-limit-windows";
|
|
@@ -15,6 +16,20 @@ import {
|
|
|
15
16
|
export type RateLimitOutcome =
|
|
16
17
|
| { kind: "none" }
|
|
17
18
|
| { kind: "key"; rateLimitedUntil: string }
|
|
19
|
+
/**
|
|
20
|
+
* The key's seat cannot run the model (`credits_required`). Not a rate
|
|
21
|
+
* limit: the seat rejection never marks the key by itself.
|
|
22
|
+
*/
|
|
23
|
+
| {
|
|
24
|
+
kind: "seat";
|
|
25
|
+
model: ModelFamily;
|
|
26
|
+
source: "event" | "text";
|
|
27
|
+
/**
|
|
28
|
+
* An independent key-wide rejection observed earlier in the same
|
|
29
|
+
* session, still to enforce. The seat outcome never erases it.
|
|
30
|
+
*/
|
|
31
|
+
keyRateLimitedUntil?: string;
|
|
32
|
+
}
|
|
18
33
|
| {
|
|
19
34
|
kind: "model";
|
|
20
35
|
model: ModelFamily;
|
|
@@ -38,6 +53,7 @@ interface ClassifiableResult {
|
|
|
38
53
|
rateLimitResetAt?: string;
|
|
39
54
|
rateLimitWindows?: RateLimitWindowTelemetry;
|
|
40
55
|
modelRateLimit?: { window: string; model: ModelFamily; resetAt: string; observedAt?: string };
|
|
56
|
+
creditsRequired?: { observedAt: string; overageDisabledReason?: string };
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
function clampMs(candidateMs: number, nowMs: number): number {
|
|
@@ -48,10 +64,15 @@ function clampMs(candidateMs: number, nowMs: number): number {
|
|
|
48
64
|
|
|
49
65
|
/**
|
|
50
66
|
* Classifies a finished provider session's rate-limit signal into one of
|
|
51
|
-
*
|
|
52
|
-
* model-scoped weekly-window rejection (Fable/Opus/Sonnet)
|
|
53
|
-
*
|
|
54
|
-
*
|
|
67
|
+
* four outcomes: no rate limit, a key-wide rate limit (legacy path), a
|
|
68
|
+
* model-scoped weekly-window rejection (Fable/Opus/Sonnet), or a seat
|
|
69
|
+
* mismatch (`credits_required`: the seat cannot run the model). Seat
|
|
70
|
+
* outcomes are tested first and model outcomes before key outcomes, so
|
|
71
|
+
* neither falls into the legacy key-wide gate and marks the whole key.
|
|
72
|
+
*
|
|
73
|
+
* `taskModelFamily` is the family of the task's model. A structured
|
|
74
|
+
* `credits_required` event names no model, so without it (and without the
|
|
75
|
+
* failure text) the event cannot be attributed and is not a seat outcome.
|
|
55
76
|
*
|
|
56
77
|
* `codexCreditsExhaustedCooldownMs` defaults to the fixed constant so the
|
|
57
78
|
* function stays pure and testable with 3 args; the runner call site passes
|
|
@@ -62,12 +83,21 @@ export function classifyRateLimitOutcome(
|
|
|
62
83
|
failureReason: string | undefined,
|
|
63
84
|
nowMs: number,
|
|
64
85
|
codexCreditsExhaustedCooldownMs: number = CODEX_CREDITS_EXHAUSTED_COOLDOWN_MS,
|
|
86
|
+
taskModelFamily?: ModelFamily,
|
|
65
87
|
): RateLimitOutcome {
|
|
66
88
|
const keyRateLimitedUntil = result.rateLimitResetAt
|
|
67
89
|
? new Date(clampMs(new Date(result.rateLimitResetAt).getTime(), nowMs)).toISOString()
|
|
68
90
|
: undefined;
|
|
69
91
|
const keyExtra = keyRateLimitedUntil ? { keyRateLimitedUntil } : {};
|
|
70
92
|
|
|
93
|
+
if (failureReason != null) {
|
|
94
|
+
const seatFamily = parseCreditsRequiredMessage(failureReason);
|
|
95
|
+
if (seatFamily) return { kind: "seat", model: seatFamily, source: "text", ...keyExtra };
|
|
96
|
+
}
|
|
97
|
+
if (result.creditsRequired && taskModelFamily) {
|
|
98
|
+
return { kind: "seat", model: taskModelFamily, source: "event", ...keyExtra };
|
|
99
|
+
}
|
|
100
|
+
|
|
71
101
|
if (result.modelRateLimit) {
|
|
72
102
|
const resetsAtSec = Math.floor(new Date(result.modelRateLimit.resetAt).getTime() / 1000);
|
|
73
103
|
const { observedAt } = result.modelRateLimit;
|