@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/db.ts
CHANGED
|
@@ -124,6 +124,12 @@ import {
|
|
|
124
124
|
SERVER_GENERATED_ATTACHMENT_CAPABILITY,
|
|
125
125
|
SessionCostModelBreakdownSchema,
|
|
126
126
|
} from "../types";
|
|
127
|
+
import {
|
|
128
|
+
CODEX_AUTH_FAILURE_BENCH_MS,
|
|
129
|
+
CODEX_AUTH_FAILURE_BENCH_THRESHOLD,
|
|
130
|
+
CODEX_AUTH_WATCH_NAMESPACE,
|
|
131
|
+
codexAuthBenchMarkerKey,
|
|
132
|
+
} from "../utils/codex-auth-failure";
|
|
127
133
|
import { deriveProviderFromKeyType } from "../utils/credentials";
|
|
128
134
|
import type { RateLimitWindowTelemetry } from "../utils/error-tracker";
|
|
129
135
|
import { extractGitHubPullRequestUrls } from "../utils/github-pull-request";
|
|
@@ -136,7 +142,11 @@ import {
|
|
|
136
142
|
import { activeModelBlock, type ModelFamily } from "../utils/model-rate-limit-windows";
|
|
137
143
|
import { getCurrentRequestUserId } from "../utils/request-auth-context";
|
|
138
144
|
import { registerVolatileSecret, scrubSecrets } from "../utils/secret-scrubber";
|
|
139
|
-
import {
|
|
145
|
+
import {
|
|
146
|
+
estimateClaudePlan,
|
|
147
|
+
planAllowsModelFamily,
|
|
148
|
+
SUBSCRIPTION_KEY_TYPES,
|
|
149
|
+
} from "../utils/subscription-plans";
|
|
140
150
|
import { auditAssetKeys } from "./asset-key-audit";
|
|
141
151
|
import { decryptSecret, encryptSecret, getEncryptionKey } from "./crypto";
|
|
142
152
|
import { normalizeDate, normalizeDateRequired } from "./date-utils";
|
|
@@ -161,6 +171,7 @@ import {
|
|
|
161
171
|
getLatestContextVersion,
|
|
162
172
|
} from "./db/context-versions";
|
|
163
173
|
import { getDb, getDbClient } from "./db/runtime";
|
|
174
|
+
import { classifyTaskHumanFree, reclassifyTaskHumanFree } from "./db/tasks/human-free";
|
|
164
175
|
import {
|
|
165
176
|
type AgentTaskRow,
|
|
166
177
|
configureTaskReadDependencies,
|
|
@@ -187,6 +198,7 @@ export {
|
|
|
187
198
|
getAgentById,
|
|
188
199
|
getAgentDailyTaskCounts,
|
|
189
200
|
getAgentHarnessProviders,
|
|
201
|
+
getAgentStatusCounts,
|
|
190
202
|
getAllAgents,
|
|
191
203
|
getLeadAgent,
|
|
192
204
|
getRemainingCapacity,
|
|
@@ -212,6 +224,7 @@ export {
|
|
|
212
224
|
updateAgentStatus,
|
|
213
225
|
updateAgentStatusFromCapacity,
|
|
214
226
|
} from "./db/agents";
|
|
227
|
+
export { recordKeySeatMismatch } from "./db/api-keys";
|
|
215
228
|
export { type ApprovalRequestSummary, listApprovalRequestSummaries } from "./db/approvals";
|
|
216
229
|
export {
|
|
217
230
|
computeContentHash,
|
|
@@ -260,6 +273,7 @@ export {
|
|
|
260
273
|
getRecentlyFinishedWorkerTasks,
|
|
261
274
|
getSlackTasksMissingTree,
|
|
262
275
|
getSupersededTasksWithoutResume,
|
|
276
|
+
getSupersededTasksWithUnsettledDependents,
|
|
263
277
|
getTaskById,
|
|
264
278
|
getTaskStats,
|
|
265
279
|
getTasksByAgentId,
|
|
@@ -291,6 +305,7 @@ export {
|
|
|
291
305
|
pauseTask,
|
|
292
306
|
resetOrphanedInProgressTasksForAgent,
|
|
293
307
|
resumeTask,
|
|
308
|
+
settleSupersededTaskDependents,
|
|
294
309
|
startTask,
|
|
295
310
|
supersedeTask,
|
|
296
311
|
updateTaskClaudeSessionId,
|
|
@@ -891,6 +906,44 @@ export async function getSlackTasksInThread(
|
|
|
891
906
|
return rows.map(rowToAgentTask);
|
|
892
907
|
}
|
|
893
908
|
|
|
909
|
+
/**
|
|
910
|
+
* Slack threads with an open human request raised by one of their own tasks
|
|
911
|
+
* (`request-human-input`): the swarm cannot go on until a person answers.
|
|
912
|
+
* A request past its `expiresAt` is about to become a timeout follow-up, so
|
|
913
|
+
* it no longer counts as waiting.
|
|
914
|
+
*/
|
|
915
|
+
export async function listSlackThreadsAwaitingHuman(): Promise<
|
|
916
|
+
Array<{ channelId: string; threadTs: string }>
|
|
917
|
+
> {
|
|
918
|
+
return getDbClient().query<{ channelId: string; threadTs: string }>(
|
|
919
|
+
`SELECT DISTINCT task.slackChannelId AS channelId, task.slackThreadTs AS threadTs
|
|
920
|
+
FROM approval_requests request
|
|
921
|
+
JOIN agent_tasks task ON task.id = request.sourceTaskId
|
|
922
|
+
WHERE request.status = 'pending'
|
|
923
|
+
AND (request.expiresAt IS NULL OR request.expiresAt > strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
924
|
+
AND task.slackChannelId IS NOT NULL
|
|
925
|
+
AND task.slackThreadTs IS NOT NULL`,
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
export async function isSlackThreadAwaitingHuman(
|
|
930
|
+
channelId: string,
|
|
931
|
+
threadTs: string,
|
|
932
|
+
): Promise<boolean> {
|
|
933
|
+
const row = await getDbClient().get<{ waiting: number }>(
|
|
934
|
+
`SELECT 1 AS waiting
|
|
935
|
+
FROM approval_requests request
|
|
936
|
+
JOIN agent_tasks task ON task.id = request.sourceTaskId
|
|
937
|
+
WHERE request.status = 'pending'
|
|
938
|
+
AND (request.expiresAt IS NULL OR request.expiresAt > strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
939
|
+
AND task.slackChannelId = ?
|
|
940
|
+
AND task.slackThreadTs = ?
|
|
941
|
+
LIMIT 1`,
|
|
942
|
+
[channelId, threadTs],
|
|
943
|
+
);
|
|
944
|
+
return !!row;
|
|
945
|
+
}
|
|
946
|
+
|
|
894
947
|
export async function markTaskSlackReplySent(taskId: string): Promise<void> {
|
|
895
948
|
await getDbClient().run(`UPDATE agent_tasks SET slackReplySent = 1 WHERE id = ?`, [taskId]);
|
|
896
949
|
}
|
|
@@ -2703,6 +2756,16 @@ export async function createTaskExtended(
|
|
|
2703
2756
|
logRoutingDecisionFailure(id, "capture", error);
|
|
2704
2757
|
}
|
|
2705
2758
|
|
|
2759
|
+
const isHumanFree = await classifyTaskHumanFree({
|
|
2760
|
+
taskType: options?.taskType,
|
|
2761
|
+
tags: JSON.stringify(options?.tags ?? []),
|
|
2762
|
+
source: options?.source ?? "mcp",
|
|
2763
|
+
requestedByUserId: options?.requestedByUserId,
|
|
2764
|
+
requestedByUserIdInherited,
|
|
2765
|
+
parentTaskId: options?.parentTaskId,
|
|
2766
|
+
workflowRunId: options?.workflowRunId,
|
|
2767
|
+
});
|
|
2768
|
+
|
|
2706
2769
|
const inserted = await getDbClient().get<AgentTaskRow>(
|
|
2707
2770
|
`INSERT INTO agent_tasks (
|
|
2708
2771
|
id, "key", agentId, creatorAgentId, task, status, source, routing_reason, routing_source, routing_note,
|
|
@@ -2712,8 +2775,8 @@ export async function createTaskExtended(
|
|
|
2712
2775
|
vcsInstallationId, vcsNodeId,
|
|
2713
2776
|
agentmailInboxId, agentmailMessageId, agentmailThreadId,
|
|
2714
2777
|
mentionMessageId, mentionChannelId, dir, parentTaskId, model, modelTier, effort, scheduleId,
|
|
2715
|
-
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, requestedByUserIdInherited, contextKey, routingAffinity, swarmVersion, createdAt, lastUpdatedAt, created_by, updated_by
|
|
2716
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2778
|
+
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, requestedByUserIdInherited, contextKey, routingAffinity, swarmVersion, createdAt, lastUpdatedAt, created_by, updated_by, isHumanFree
|
|
2779
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2717
2780
|
[
|
|
2718
2781
|
id,
|
|
2719
2782
|
assetKey,
|
|
@@ -2768,6 +2831,7 @@ export async function createTaskExtended(
|
|
|
2768
2831
|
now,
|
|
2769
2832
|
auditUserId,
|
|
2770
2833
|
auditUserId,
|
|
2834
|
+
isHumanFree ? 1 : 0,
|
|
2771
2835
|
],
|
|
2772
2836
|
);
|
|
2773
2837
|
if (!inserted) throw new Error("Failed to create task");
|
|
@@ -3318,9 +3382,15 @@ const ELIGIBILITY_SCAN_CAP = Number(process.env.ELIGIBILITY_SCAN_CAP) || 500;
|
|
|
3318
3382
|
* JSON-parsing `routingAffinity` in SQL) until `limit` eligible tasks are
|
|
3319
3383
|
* found or the pool is exhausted, capped at `ELIGIBILITY_SCAN_CAP` rows
|
|
3320
3384
|
* scanned so a pool full of ineligible tasks can't turn every poll into an
|
|
3321
|
-
* unbounded scan.
|
|
3385
|
+
* unbounded scan. `accept` adds a caller-side filter (e.g. harness model
|
|
3386
|
+
* compatibility) that runs inside the scan, so rejected rows do not use up
|
|
3387
|
+
* the `limit`.
|
|
3322
3388
|
*/
|
|
3323
|
-
export async function getUnassignedTaskIdsForAgent(
|
|
3389
|
+
export async function getUnassignedTaskIdsForAgent(
|
|
3390
|
+
agentId: string,
|
|
3391
|
+
limit = 10,
|
|
3392
|
+
accept?: (task: AgentTask) => boolean | Promise<boolean>,
|
|
3393
|
+
): Promise<string[]> {
|
|
3324
3394
|
const agent = await getAgentById(agentId);
|
|
3325
3395
|
if (!agent) return [];
|
|
3326
3396
|
|
|
@@ -3337,7 +3407,7 @@ export async function getUnassignedTaskIdsForAgent(agentId: string, limit = 10):
|
|
|
3337
3407
|
|
|
3338
3408
|
for (const row of rows) {
|
|
3339
3409
|
const task = rowToAgentTask(row);
|
|
3340
|
-
if (isAgentEligibleForTask(agent, task)) {
|
|
3410
|
+
if (isAgentEligibleForTask(agent, task) && (!accept || (await accept(task)))) {
|
|
3341
3411
|
eligible.push(task.id);
|
|
3342
3412
|
if (eligible.length >= limit) break;
|
|
3343
3413
|
}
|
|
@@ -4893,60 +4963,12 @@ export interface SessionCostByUserRow {
|
|
|
4893
4963
|
/** `opts.userId` sentinel selecting spend with no human requester. */
|
|
4894
4964
|
export const UNATTRIBUTED_USER_ID = "unattributed";
|
|
4895
4965
|
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
LEFT JOIN agent_tasks parent ON parent.id = task.parentTaskId
|
|
4903
|
-
WHERE COALESCE(task.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
|
|
4904
|
-
OR COALESCE(task.tags, '[]') LIKE '%"heartbeat"%'
|
|
4905
|
-
OR (COALESCE(task.source, '') = 'schedule' AND task.requestedByUserId IS NULL)
|
|
4906
|
-
OR (
|
|
4907
|
-
task.parentTaskId IS NULL
|
|
4908
|
-
AND COALESCE(task.source, '') = 'workflow'
|
|
4909
|
-
AND task.requestedByUserId IS NULL
|
|
4910
|
-
AND EXISTS (
|
|
4911
|
-
SELECT 1
|
|
4912
|
-
FROM workflow_runs run
|
|
4913
|
-
WHERE run.id = task.workflowRunId
|
|
4914
|
-
AND run.triggerType = 'schedule'
|
|
4915
|
-
AND run.created_by IS NULL
|
|
4916
|
-
)
|
|
4917
|
-
)
|
|
4918
|
-
OR (
|
|
4919
|
-
COALESCE(task.source, '') = 'system'
|
|
4920
|
-
AND parent.id IS NOT NULL
|
|
4921
|
-
AND parent.requestedByUserId IS NULL
|
|
4922
|
-
)
|
|
4923
|
-
|
|
4924
|
-
UNION
|
|
4925
|
-
|
|
4926
|
-
SELECT child.id
|
|
4927
|
-
FROM agent_tasks child
|
|
4928
|
-
JOIN human_free_tasks parent ON child.parentTaskId = parent.id
|
|
4929
|
-
WHERE child.requestedByUserId IS NULL
|
|
4930
|
-
OR child.requestedByUserIdInherited = 1
|
|
4931
|
-
)`;
|
|
4932
|
-
/** True on rows joined through `human_free_tasks hf` (see `getSessionCostSummary`). */
|
|
4933
|
-
const HUMAN_FREE_SQL = "hf.id IS NOT NULL";
|
|
4934
|
-
const ROOT_HUMAN_FREE_SQL = `(
|
|
4935
|
-
COALESCE(t.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
|
|
4936
|
-
OR COALESCE(t.tags, '[]') LIKE '%"heartbeat"%'
|
|
4937
|
-
OR (COALESCE(t.source, '') = 'schedule' AND t.requestedByUserId IS NULL)
|
|
4938
|
-
OR (
|
|
4939
|
-
COALESCE(t.source, '') = 'workflow'
|
|
4940
|
-
AND t.requestedByUserId IS NULL
|
|
4941
|
-
AND EXISTS (
|
|
4942
|
-
SELECT 1
|
|
4943
|
-
FROM workflow_runs run
|
|
4944
|
-
WHERE run.id = t.workflowRunId
|
|
4945
|
-
AND run.triggerType = 'schedule'
|
|
4946
|
-
AND run.created_by IS NULL
|
|
4947
|
-
)
|
|
4948
|
-
)
|
|
4949
|
-
)`;
|
|
4966
|
+
/**
|
|
4967
|
+
* True for a session whose task is structurally human-free (`isHumanFree`, set
|
|
4968
|
+
* when the task is created; rule in `src/be/db/tasks/human-free.ts`). A session
|
|
4969
|
+
* with no task row is not human-free.
|
|
4970
|
+
*/
|
|
4971
|
+
const HUMAN_FREE_SQL = "(COALESCE(t.isHumanFree, 0) = 1)";
|
|
4950
4972
|
|
|
4951
4973
|
export async function getSessionCostSummary(opts: {
|
|
4952
4974
|
startDate?: string;
|
|
@@ -4966,19 +4988,15 @@ export async function getSessionCostSummary(opts: {
|
|
|
4966
4988
|
// re-attributed after the fact, so the human requester is resolved by joining
|
|
4967
4989
|
// through the task (same shape as `getDailySpendForUser`). Every column is
|
|
4968
4990
|
// `sc.`-qualified because `createdAt`/`agentId` exist on both sides.
|
|
4969
|
-
|
|
4970
|
-
//
|
|
4971
|
-
//
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
//
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
// human requester. That classification propagates through descendants while
|
|
4979
|
-
// they remain unattributed, so autonomous fan-out cannot leak back into the
|
|
4980
|
-
// denominator. An explicitly attributed child is an independent handoff and
|
|
4981
|
-
// stops propagation down that branch.
|
|
4991
|
+
// INDEXED BY: without ANALYZE statistics (production has none) the planner
|
|
4992
|
+
// probes tasks through the primary key and reads each full row, which follows
|
|
4993
|
+
// the task's overflow pages. The covering index answers the same probe from
|
|
4994
|
+
// the index alone (migration 182).
|
|
4995
|
+
const from =
|
|
4996
|
+
"FROM session_costs sc LEFT JOIN agent_tasks t INDEXED BY idx_agent_tasks_usage_cover ON t.id = sc.taskId";
|
|
4997
|
+
// Only a user filter reads the task, and the join adds no rows (task id is unique).
|
|
4998
|
+
const agentFrom = opts.userId === undefined ? "FROM session_costs sc" : from;
|
|
4999
|
+
|
|
4982
5000
|
const conditions: string[] = [];
|
|
4983
5001
|
const params: string[] = [];
|
|
4984
5002
|
|
|
@@ -5002,11 +5020,6 @@ export async function getSessionCostSummary(opts: {
|
|
|
5002
5020
|
}
|
|
5003
5021
|
|
|
5004
5022
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5005
|
-
// Only a user filter makes the daily and per-agent breakdowns classify rows.
|
|
5006
|
-
// Without one they skip the CTE, which scans every task.
|
|
5007
|
-
const classify = opts.userId !== undefined;
|
|
5008
|
-
const withHf = classify ? `WITH RECURSIVE ${HUMAN_FREE_TASKS_CTE}` : "";
|
|
5009
|
-
const scopedFrom = classify ? fromHf : from;
|
|
5010
5023
|
const subscriptionSql = `t.credentialKeyType IN (${SUBSCRIPTION_KEY_TYPES.map((k) => `'${k}'`).join(", ")})`;
|
|
5011
5024
|
|
|
5012
5025
|
// Totals
|
|
@@ -5026,8 +5039,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5026
5039
|
};
|
|
5027
5040
|
|
|
5028
5041
|
const totalsRow = await getDbClient().get<TotalsRow>(
|
|
5029
|
-
`
|
|
5030
|
-
SELECT
|
|
5042
|
+
`SELECT
|
|
5031
5043
|
COALESCE(SUM(sc.totalCostUsd), 0) as totalCostUsd,
|
|
5032
5044
|
COALESCE(SUM(sc.inputTokens), 0) as totalInputTokens,
|
|
5033
5045
|
COALESCE(SUM(sc.outputTokens), 0) as totalOutputTokens,
|
|
@@ -5044,7 +5056,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5044
5056
|
COUNT(DISTINCT CASE WHEN ${HUMAN_FREE_SQL} THEN t.id END) as excludedTaskCount,
|
|
5045
5057
|
COALESCE(SUM(CASE WHEN ${subscriptionSql}
|
|
5046
5058
|
THEN sc.totalCostUsd ELSE 0 END), 0) as subscriptionCostUsd
|
|
5047
|
-
${
|
|
5059
|
+
${from} ${where}`,
|
|
5048
5060
|
params,
|
|
5049
5061
|
);
|
|
5050
5062
|
|
|
@@ -5082,8 +5094,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5082
5094
|
sessions: number;
|
|
5083
5095
|
subscriptionCostUsd: number;
|
|
5084
5096
|
}>(
|
|
5085
|
-
|
|
5086
|
-
SELECT
|
|
5097
|
+
`SELECT
|
|
5087
5098
|
DATE(sc.createdAt) as date,
|
|
5088
5099
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
5089
5100
|
COALESCE(SUM(sc.inputTokens), 0) as inputTokens,
|
|
@@ -5091,7 +5102,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5091
5102
|
COUNT(*) as sessions,
|
|
5092
5103
|
COALESCE(SUM(CASE WHEN ${subscriptionSql} THEN sc.totalCostUsd ELSE 0 END), 0)
|
|
5093
5104
|
as subscriptionCostUsd
|
|
5094
|
-
${
|
|
5105
|
+
${from} ${where}
|
|
5095
5106
|
GROUP BY DATE(sc.createdAt)
|
|
5096
5107
|
ORDER BY date ASC`,
|
|
5097
5108
|
params,
|
|
@@ -5109,15 +5120,14 @@ export async function getSessionCostSummary(opts: {
|
|
|
5109
5120
|
sessions: number;
|
|
5110
5121
|
durationMs: number;
|
|
5111
5122
|
}>(
|
|
5112
|
-
|
|
5113
|
-
SELECT
|
|
5123
|
+
`SELECT
|
|
5114
5124
|
sc.agentId as agentId,
|
|
5115
5125
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
5116
5126
|
COALESCE(SUM(sc.inputTokens), 0) as inputTokens,
|
|
5117
5127
|
COALESCE(SUM(sc.outputTokens), 0) as outputTokens,
|
|
5118
5128
|
COUNT(*) as sessions,
|
|
5119
5129
|
COALESCE(SUM(sc.durationMs), 0) as durationMs
|
|
5120
|
-
${
|
|
5130
|
+
${agentFrom} ${where}
|
|
5121
5131
|
GROUP BY sc.agentId
|
|
5122
5132
|
ORDER BY costUsd DESC`,
|
|
5123
5133
|
params,
|
|
@@ -5129,15 +5139,14 @@ export async function getSessionCostSummary(opts: {
|
|
|
5129
5139
|
let byUser: SessionCostByUserRow[] = [];
|
|
5130
5140
|
if (groupBy === "user" || groupBy === "both") {
|
|
5131
5141
|
byUser = await getDbClient().query<SessionCostByUserRow>(
|
|
5132
|
-
`
|
|
5133
|
-
SELECT
|
|
5142
|
+
`SELECT
|
|
5134
5143
|
CASE WHEN ${HUMAN_FREE_SQL} THEN NULL ELSE t.requestedByUserId END as userId,
|
|
5135
5144
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
5136
5145
|
COALESCE(SUM(sc.inputTokens), 0) as inputTokens,
|
|
5137
5146
|
COALESCE(SUM(sc.outputTokens), 0) as outputTokens,
|
|
5138
5147
|
COUNT(DISTINCT sc.taskId) as tasks,
|
|
5139
5148
|
COALESCE(SUM(sc.durationMs), 0) as durationMs
|
|
5140
|
-
${
|
|
5149
|
+
${from} ${where}
|
|
5141
5150
|
GROUP BY CASE WHEN ${HUMAN_FREE_SQL} THEN NULL ELSE t.requestedByUserId END
|
|
5142
5151
|
ORDER BY costUsd DESC`,
|
|
5143
5152
|
params,
|
|
@@ -5151,8 +5160,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5151
5160
|
"name" | "subscription" | "plan" | "planSource"
|
|
5152
5161
|
>;
|
|
5153
5162
|
const rows = await getDbClient().query<CredentialRow>(
|
|
5154
|
-
|
|
5155
|
-
SELECT
|
|
5163
|
+
`SELECT
|
|
5156
5164
|
t.credentialKeyType as keyType,
|
|
5157
5165
|
t.credentialKeySuffix as keySuffix,
|
|
5158
5166
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
@@ -5161,7 +5169,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5161
5169
|
COUNT(*) as sessions,
|
|
5162
5170
|
MIN(sc.createdAt) as firstSessionAt,
|
|
5163
5171
|
MAX(sc.createdAt) as lastSessionAt
|
|
5164
|
-
${
|
|
5172
|
+
${from} ${where}
|
|
5165
5173
|
GROUP BY t.credentialKeyType, t.credentialKeySuffix
|
|
5166
5174
|
ORDER BY costUsd DESC`,
|
|
5167
5175
|
params,
|
|
@@ -5223,18 +5231,6 @@ async function getCredentialLabels(): Promise<
|
|
|
5223
5231
|
return labels;
|
|
5224
5232
|
}
|
|
5225
5233
|
|
|
5226
|
-
/**
|
|
5227
|
-
* Changes when a session cost or a task is inserted. Part of the usage report
|
|
5228
|
-
* cache key (`src/http/usage-cache.ts`). Reads two rowid maxima, so it is cheap.
|
|
5229
|
-
*/
|
|
5230
|
-
export async function getUsageDataVersion(): Promise<string> {
|
|
5231
|
-
const row = await getDbClient().get<{ version: string }>(
|
|
5232
|
-
`SELECT COALESCE((SELECT MAX(rowid) FROM session_costs), 0) || ':' ||
|
|
5233
|
-
COALESCE((SELECT MAX(rowid) FROM agent_tasks), 0) as version`,
|
|
5234
|
-
);
|
|
5235
|
-
return row?.version ?? "0:0";
|
|
5236
|
-
}
|
|
5237
|
-
|
|
5238
5234
|
// --- Per-person attribution (four-metric view) ---
|
|
5239
5235
|
|
|
5240
5236
|
export interface AttributionByPersonRow {
|
|
@@ -5265,7 +5261,7 @@ export interface AttributionByPersonRow {
|
|
|
5265
5261
|
* a composite score. Scope is root tasks (`problemsInitiated`/`problemsShipped`)
|
|
5266
5262
|
* or the person's entire task tree (`*Reached`); `requestedByUserId IS NULL`
|
|
5267
5263
|
* (autonomous work) and structurally-human-free rows with a stale/inherited
|
|
5268
|
-
* requester (
|
|
5264
|
+
* requester (`agent_tasks.isHumanFree`, see `src/be/db/tasks/human-free.ts`) are excluded —
|
|
5269
5265
|
* neither belongs to a person.
|
|
5270
5266
|
*
|
|
5271
5267
|
* "Problems shipped" detection walks each root's full task tree, preferring a
|
|
@@ -5309,18 +5305,20 @@ export async function getAttributionByPerson(opts: {
|
|
|
5309
5305
|
|
|
5310
5306
|
type RootRow = { userId: string; initiated: number; shipped: number };
|
|
5311
5307
|
const rootRows = await getDbClient().query<RootRow>(
|
|
5312
|
-
`WITH RECURSIVE selected_roots(id, requestedByUserId, status
|
|
5313
|
-
SELECT t.id, t.requestedByUserId, t.status
|
|
5308
|
+
`WITH RECURSIVE selected_roots(id, requestedByUserId, status) AS (
|
|
5309
|
+
SELECT t.id, t.requestedByUserId, t.status
|
|
5314
5310
|
FROM agent_tasks t
|
|
5315
|
-
${where} AND t.parentTaskId IS NULL AND
|
|
5311
|
+
${where} AND t.parentTaskId IS NULL AND t.isHumanFree = 0
|
|
5316
5312
|
),
|
|
5317
|
-
|
|
5318
|
-
|
|
5313
|
+
-- Ids only: carrying each descendant's \`output\` through the recursion copied
|
|
5314
|
+
-- every task's text into the queue. The output fallback below reads it by id.
|
|
5315
|
+
task_tree(rootId, taskId) AS (
|
|
5316
|
+
SELECT id, id
|
|
5319
5317
|
FROM selected_roots
|
|
5320
5318
|
|
|
5321
5319
|
UNION ALL
|
|
5322
5320
|
|
|
5323
|
-
SELECT tree.rootId, child.id
|
|
5321
|
+
SELECT tree.rootId, child.id
|
|
5324
5322
|
FROM agent_tasks child
|
|
5325
5323
|
JOIN task_tree tree ON child.parentTaskId = tree.taskId
|
|
5326
5324
|
),
|
|
@@ -5353,8 +5351,11 @@ export async function getAttributionByPerson(opts: {
|
|
|
5353
5351
|
|
|
5354
5352
|
SELECT tree.rootId
|
|
5355
5353
|
FROM task_tree tree
|
|
5356
|
-
|
|
5357
|
-
|
|
5354
|
+
-- CROSS JOIN pins the order (tree outer, task by id). Left to itself the
|
|
5355
|
+
-- planner scans every task's output and probes the tree instead.
|
|
5356
|
+
CROSS JOIN agent_tasks described ON described.id = tree.taskId
|
|
5357
|
+
WHERE described.output LIKE '%github.com/%/pull/%'
|
|
5358
|
+
OR described.output LIKE '%/-/merge_requests/%'
|
|
5358
5359
|
)
|
|
5359
5360
|
SELECT
|
|
5360
5361
|
t.requestedByUserId as userId,
|
|
@@ -5373,65 +5374,13 @@ export async function getAttributionByPerson(opts: {
|
|
|
5373
5374
|
surfacesReached: number;
|
|
5374
5375
|
};
|
|
5375
5376
|
const reachRows = await getDbClient().query<ReachRow>(
|
|
5376
|
-
`
|
|
5377
|
-
-- Only the columns read below: a task row carries its full prompt and output.
|
|
5378
|
-
SELECT t.id, t.agentId, t.vcsRepo, t.source, t.parentTaskId, t.requestedByUserId,
|
|
5379
|
-
t.requestedByUserIdInherited, t.taskType, t.tags, t.workflowRunId
|
|
5380
|
-
FROM agent_tasks t
|
|
5381
|
-
${where}
|
|
5382
|
-
),
|
|
5383
|
-
task_ancestry(
|
|
5384
|
-
taskId, id, parentTaskId, requestedByUserId, requestedByUserIdInherited,
|
|
5385
|
-
taskType, tags, source, workflowRunId
|
|
5386
|
-
) AS (
|
|
5387
|
-
SELECT
|
|
5388
|
-
id, id, parentTaskId, requestedByUserId, requestedByUserIdInherited,
|
|
5389
|
-
taskType, tags, source, workflowRunId
|
|
5390
|
-
FROM report_tasks
|
|
5391
|
-
|
|
5392
|
-
UNION ALL
|
|
5393
|
-
|
|
5394
|
-
SELECT
|
|
5395
|
-
child.taskId, parent.id, parent.parentTaskId, parent.requestedByUserId,
|
|
5396
|
-
parent.requestedByUserIdInherited, parent.taskType, parent.tags,
|
|
5397
|
-
parent.source, parent.workflowRunId
|
|
5398
|
-
FROM agent_tasks parent
|
|
5399
|
-
JOIN task_ancestry child ON parent.id = child.parentTaskId
|
|
5400
|
-
WHERE child.requestedByUserId IS NULL
|
|
5401
|
-
OR child.requestedByUserIdInherited = 1
|
|
5402
|
-
),
|
|
5403
|
-
human_free_report_tasks(id) AS (
|
|
5404
|
-
SELECT DISTINCT ancestor.taskId
|
|
5405
|
-
FROM task_ancestry ancestor
|
|
5406
|
-
LEFT JOIN agent_tasks parent ON parent.id = ancestor.parentTaskId
|
|
5407
|
-
WHERE COALESCE(ancestor.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
|
|
5408
|
-
OR COALESCE(ancestor.tags, '[]') LIKE '%"heartbeat"%'
|
|
5409
|
-
OR (COALESCE(ancestor.source, '') = 'schedule' AND ancestor.requestedByUserId IS NULL)
|
|
5410
|
-
OR (
|
|
5411
|
-
ancestor.parentTaskId IS NULL
|
|
5412
|
-
AND COALESCE(ancestor.source, '') = 'workflow'
|
|
5413
|
-
AND ancestor.requestedByUserId IS NULL
|
|
5414
|
-
AND EXISTS (
|
|
5415
|
-
SELECT 1
|
|
5416
|
-
FROM workflow_runs run
|
|
5417
|
-
WHERE run.id = ancestor.workflowRunId
|
|
5418
|
-
AND run.triggerType = 'schedule'
|
|
5419
|
-
AND run.created_by IS NULL
|
|
5420
|
-
)
|
|
5421
|
-
)
|
|
5422
|
-
OR (
|
|
5423
|
-
COALESCE(ancestor.source, '') = 'system'
|
|
5424
|
-
AND parent.id IS NOT NULL
|
|
5425
|
-
AND parent.requestedByUserId IS NULL
|
|
5426
|
-
)
|
|
5427
|
-
)
|
|
5428
|
-
SELECT
|
|
5377
|
+
`SELECT
|
|
5429
5378
|
t.requestedByUserId as userId,
|
|
5430
5379
|
COUNT(DISTINCT t.agentId) as agentsReached,
|
|
5431
5380
|
COUNT(DISTINCT t.vcsRepo) as reposReached,
|
|
5432
5381
|
COUNT(DISTINCT t.source) as surfacesReached
|
|
5433
|
-
FROM
|
|
5434
|
-
|
|
5382
|
+
FROM agent_tasks t
|
|
5383
|
+
${where} AND t.isHumanFree = 0
|
|
5435
5384
|
GROUP BY t.requestedByUserId`,
|
|
5436
5385
|
params,
|
|
5437
5386
|
);
|
|
@@ -7596,7 +7545,19 @@ export async function updateWorkflow(
|
|
|
7596
7545
|
}
|
|
7597
7546
|
|
|
7598
7547
|
export async function deleteWorkflow(id: string, source?: "api" | "mcp"): Promise<boolean> {
|
|
7548
|
+
// One transaction: the stored human-free flags must never lag the deleted runs
|
|
7549
|
+
// (a scheduled workflow root is human-free only while its run row exists).
|
|
7550
|
+
return await getDbClient().transaction(() => deleteWorkflowRows(id, source));
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7553
|
+
async function deleteWorkflowRows(id: string, source?: "api" | "mcp"): Promise<boolean> {
|
|
7599
7554
|
const client = getDbClient();
|
|
7555
|
+
const linkedTaskIds = (
|
|
7556
|
+
await client.query<{ id: string }>(
|
|
7557
|
+
`SELECT id FROM agent_tasks WHERE workflowRunId IN (SELECT id FROM workflow_runs WHERE workflowId = ?)`,
|
|
7558
|
+
[id],
|
|
7559
|
+
)
|
|
7560
|
+
).map((row) => row.id);
|
|
7600
7561
|
// Cascade delete in FK-safe order:
|
|
7601
7562
|
// 1. Unlink agent_tasks (they reference steps and runs)
|
|
7602
7563
|
await client.run(
|
|
@@ -7610,6 +7571,7 @@ export async function deleteWorkflow(id: string, source?: "api" | "mcp"): Promis
|
|
|
7610
7571
|
);
|
|
7611
7572
|
// 3. Delete runs (they reference workflow)
|
|
7612
7573
|
await client.run("DELETE FROM workflow_runs WHERE workflowId = ?", [id]);
|
|
7574
|
+
await reclassifyTaskHumanFree(linkedTaskIds);
|
|
7613
7575
|
// 4. Delete workflow
|
|
7614
7576
|
const result = await client.run("DELETE FROM workflows WHERE id = ?", [id]);
|
|
7615
7577
|
const deleted = result.changes > 0;
|
|
@@ -11430,6 +11392,13 @@ export interface ApiKeyStatus {
|
|
|
11430
11392
|
/** Subscription plan id (`SUBSCRIPTION_PLANS`), when known. */
|
|
11431
11393
|
plan: string | null;
|
|
11432
11394
|
planSource: PlanSource | null;
|
|
11395
|
+
/** When the CLI last rejected a model with `credits_required` on this key. */
|
|
11396
|
+
lastSeatMismatchAt: string | null;
|
|
11397
|
+
/** Model family of that rejection (`fable`, `opus`, ...). */
|
|
11398
|
+
lastSeatMismatchModel: string | null;
|
|
11399
|
+
/** Auth failures in a row since the last success or clear. */
|
|
11400
|
+
consecutiveAuthFailures: number;
|
|
11401
|
+
lastAuthFailureAt: string | null;
|
|
11433
11402
|
createdAt: string;
|
|
11434
11403
|
updatedAt: string;
|
|
11435
11404
|
}
|
|
@@ -11459,6 +11428,9 @@ export interface AvailableKeyIndicesResult {
|
|
|
11459
11428
|
modelBlockedIndices: number[];
|
|
11460
11429
|
/** ISO of the earliest resetsAt among modelBlockedIndices, or null when none. */
|
|
11461
11430
|
earliestModelResetAt: string | null;
|
|
11431
|
+
/** Indices excluded because the key's subscription plan cannot run the model family. */
|
|
11432
|
+
seatBlockedIndices: number[];
|
|
11433
|
+
authFailureFence: number; // highest `authFailureSeq` among these rows
|
|
11462
11434
|
}
|
|
11463
11435
|
|
|
11464
11436
|
/**
|
|
@@ -11468,7 +11440,10 @@ export interface AvailableKeyIndicesResult {
|
|
|
11468
11440
|
* When `modelFamily` has a weekly window (fable/opus/sonnet), a key whose
|
|
11469
11441
|
* `rateLimitWindows` carries an active rejected window for that family is
|
|
11470
11442
|
* excluded from `availableIndices` and reported in `modelBlockedIndices`
|
|
11471
|
-
* instead — the key itself stays `available` for every other model.
|
|
11443
|
+
* instead — the key itself stays `available` for every other model. A key
|
|
11444
|
+
* whose `plan` cannot run the family (`planAllowsModelFamily`) is excluded
|
|
11445
|
+
* and reported in `seatBlockedIndices`, also when it is key-wide or
|
|
11446
|
+
* model-window blocked; a seat block has no reset time.
|
|
11472
11447
|
*/
|
|
11473
11448
|
export async function getAvailableKeyIndices(
|
|
11474
11449
|
keyType: string,
|
|
@@ -11482,21 +11457,23 @@ export async function getAvailableKeyIndices(
|
|
|
11482
11457
|
const client = getDbClient();
|
|
11483
11458
|
const effectiveScopeId = scopeId ?? "";
|
|
11484
11459
|
|
|
11485
|
-
// Auto-clear expired rate limits
|
|
11460
|
+
// Auto-clear expired rate limits. Never an auth bench: only `clearAuthBench` lifts it.
|
|
11486
11461
|
await client.run(
|
|
11487
11462
|
`UPDATE api_key_status
|
|
11488
11463
|
SET status = 'available', rateLimitedUntil = NULL, updatedAt = ?
|
|
11489
11464
|
WHERE keyType = ? AND scope = ? AND scopeId = ?
|
|
11490
|
-
AND status = 'rate_limited' AND rateLimitedUntil
|
|
11491
|
-
[now, keyType, scope, effectiveScopeId, now],
|
|
11465
|
+
AND status = 'rate_limited' AND rateLimitedUntil <= ? AND consecutiveAuthFailures < ?`,
|
|
11466
|
+
[now, keyType, scope, effectiveScopeId, now, CODEX_AUTH_FAILURE_BENCH_THRESHOLD],
|
|
11492
11467
|
);
|
|
11493
11468
|
|
|
11494
11469
|
const rows = await client.query<{
|
|
11495
11470
|
keyIndex: number;
|
|
11496
11471
|
status: string;
|
|
11497
11472
|
rateLimitWindows: string | null;
|
|
11473
|
+
plan: string | null;
|
|
11474
|
+
authFailureSeq: number;
|
|
11498
11475
|
}>(
|
|
11499
|
-
`SELECT keyIndex, status, rateLimitWindows FROM api_key_status
|
|
11476
|
+
`SELECT keyIndex, status, rateLimitWindows, plan, authFailureSeq FROM api_key_status
|
|
11500
11477
|
WHERE keyType = ? AND scope = ? AND scopeId = ?`,
|
|
11501
11478
|
[keyType, scope, effectiveScopeId],
|
|
11502
11479
|
);
|
|
@@ -11527,19 +11504,32 @@ export async function getAvailableKeyIndices(
|
|
|
11527
11504
|
}
|
|
11528
11505
|
const modelBlockedSet = new Set(modelBlockedIndices);
|
|
11529
11506
|
|
|
11507
|
+
// A seat block is a plan fact: report it even when the key is also key-wide
|
|
11508
|
+
// or model-window blocked, or the temporary block hides it from admission
|
|
11509
|
+
// and the fallback pick can select a key that cannot run the model.
|
|
11510
|
+
const seatBlockedIndices: number[] = [];
|
|
11511
|
+
if (modelFamily) {
|
|
11512
|
+
for (const row of rows) {
|
|
11513
|
+
if (!planAllowsModelFamily(row.plan, modelFamily)) seatBlockedIndices.push(row.keyIndex);
|
|
11514
|
+
}
|
|
11515
|
+
}
|
|
11516
|
+
const seatBlockedSet = new Set(seatBlockedIndices);
|
|
11517
|
+
|
|
11530
11518
|
const availableIndices: number[] = [];
|
|
11531
11519
|
for (let i = 0; i < totalKeys; i++) {
|
|
11532
|
-
if (blockedIndices.has(i) || modelBlockedSet.has(i)) continue;
|
|
11520
|
+
if (blockedIndices.has(i) || modelBlockedSet.has(i) || seatBlockedSet.has(i)) continue;
|
|
11533
11521
|
availableIndices.push(i);
|
|
11534
11522
|
}
|
|
11535
11523
|
|
|
11536
11524
|
return {
|
|
11537
11525
|
availableIndices,
|
|
11538
11526
|
modelBlockedIndices,
|
|
11527
|
+
seatBlockedIndices,
|
|
11539
11528
|
earliestModelResetAt:
|
|
11540
11529
|
earliestModelResetsAtSec !== undefined
|
|
11541
11530
|
? new Date(earliestModelResetsAtSec * 1000).toISOString()
|
|
11542
11531
|
: null,
|
|
11532
|
+
authFailureFence: Math.max(0, ...rows.map((r) => r.authFailureSeq)),
|
|
11543
11533
|
};
|
|
11544
11534
|
}
|
|
11545
11535
|
|
|
@@ -11627,9 +11617,7 @@ export async function setApiKeyPlan(
|
|
|
11627
11617
|
return result.changes > 0;
|
|
11628
11618
|
}
|
|
11629
11619
|
|
|
11630
|
-
/**
|
|
11631
|
-
* Mark a key as rate-limited with a retry-after timestamp.
|
|
11632
|
-
*/
|
|
11620
|
+
/** Mark a key as rate-limited until a retry-after timestamp; never shortens an auth-failure bench. */
|
|
11633
11621
|
export async function markKeyRateLimited(
|
|
11634
11622
|
keyType: string,
|
|
11635
11623
|
keySuffix: string,
|
|
@@ -11647,7 +11635,7 @@ export async function markKeyRateLimited(
|
|
|
11647
11635
|
ON CONFLICT(keyType, keySuffix, scope, scopeId)
|
|
11648
11636
|
DO UPDATE SET
|
|
11649
11637
|
status = 'rate_limited',
|
|
11650
|
-
rateLimitedUntil = excluded.rateLimitedUntil,
|
|
11638
|
+
rateLimitedUntil = CASE WHEN status = 'rate_limited' AND consecutiveAuthFailures >= ${CODEX_AUTH_FAILURE_BENCH_THRESHOLD} THEN MAX(COALESCE(rateLimitedUntil, ''), excluded.rateLimitedUntil) ELSE excluded.rateLimitedUntil END,
|
|
11651
11639
|
lastRateLimitAt = excluded.lastRateLimitAt,
|
|
11652
11640
|
rateLimitCount = rateLimitCount + 1,
|
|
11653
11641
|
keyIndex = excluded.keyIndex,
|
|
@@ -11656,6 +11644,63 @@ export async function markKeyRateLimited(
|
|
|
11656
11644
|
);
|
|
11657
11645
|
}
|
|
11658
11646
|
|
|
11647
|
+
export interface KeyAuthFailureResult {
|
|
11648
|
+
consecutiveAuthFailures: number;
|
|
11649
|
+
benched: boolean;
|
|
11650
|
+
rateLimitedUntil: string | null;
|
|
11651
|
+
}
|
|
11652
|
+
|
|
11653
|
+
/** Count an auth failure, ordered by `authFailureSeq`; at the threshold, bench (extend only). */
|
|
11654
|
+
export async function recordKeyAuthFailure(
|
|
11655
|
+
keyType: string,
|
|
11656
|
+
keySuffix: string,
|
|
11657
|
+
keyIndex: number,
|
|
11658
|
+
scope = "global",
|
|
11659
|
+
scopeId: string | null = null,
|
|
11660
|
+
): Promise<KeyAuthFailureResult> {
|
|
11661
|
+
const effectiveScopeId = scopeId ?? "";
|
|
11662
|
+
const provider = deriveProviderFromKeyType(keyType);
|
|
11663
|
+
return await getDbClient().transaction(async (tx) => {
|
|
11664
|
+
const now = new Date().toISOString();
|
|
11665
|
+
const source = "report-auth-failure";
|
|
11666
|
+
const row = await tx.get<{ consecutiveAuthFailures: number; rateLimitedUntil: string | null }>(
|
|
11667
|
+
`INSERT INTO api_key_status (keyType, keySuffix, keyIndex, scope, scopeId, status, consecutiveAuthFailures, lastAuthFailureAt, authFailureSeq, provider, updatedAt)
|
|
11668
|
+
VALUES (?, ?, ?, ?, ?, 'available', 1, ?, (SELECT COALESCE(MAX(authFailureSeq), 0) + 1 FROM api_key_status), ?, ?)
|
|
11669
|
+
ON CONFLICT(keyType, keySuffix, scope, scopeId)
|
|
11670
|
+
DO UPDATE SET
|
|
11671
|
+
consecutiveAuthFailures = consecutiveAuthFailures + 1,
|
|
11672
|
+
lastAuthFailureAt = excluded.lastAuthFailureAt, authFailureSeq = excluded.authFailureSeq,
|
|
11673
|
+
keyIndex = excluded.keyIndex, updatedAt = excluded.updatedAt
|
|
11674
|
+
RETURNING consecutiveAuthFailures, rateLimitedUntil`,
|
|
11675
|
+
[keyType, keySuffix, keyIndex, scope, effectiveScopeId, now, provider, now],
|
|
11676
|
+
);
|
|
11677
|
+
if (!row) throw new Error("Failed to record auth failure");
|
|
11678
|
+
const { consecutiveAuthFailures } = row;
|
|
11679
|
+
if (consecutiveAuthFailures < CODEX_AUTH_FAILURE_BENCH_THRESHOLD) {
|
|
11680
|
+
return { consecutiveAuthFailures, benched: false, rateLimitedUntil: row.rateLimitedUntil };
|
|
11681
|
+
}
|
|
11682
|
+
let benchedUntil = new Date(Date.parse(now) + CODEX_AUTH_FAILURE_BENCH_MS).toISOString();
|
|
11683
|
+
if (row.rateLimitedUntil && Date.parse(row.rateLimitedUntil) > Date.parse(benchedUntil)) {
|
|
11684
|
+
benchedUntil = row.rateLimitedUntil;
|
|
11685
|
+
}
|
|
11686
|
+
await tx.run(
|
|
11687
|
+
`UPDATE api_key_status
|
|
11688
|
+
SET status = 'rate_limited', rateLimitedUntil = ?, lastRateLimitAt = ?,
|
|
11689
|
+
rateLimitCount = rateLimitCount + 1, updatedAt = ?
|
|
11690
|
+
WHERE keyType = ? AND keySuffix = ? AND scope = ? AND scopeId = ?`,
|
|
11691
|
+
[benchedUntil, now, now, keyType, keySuffix, scope, effectiveScopeId],
|
|
11692
|
+
);
|
|
11693
|
+
await upsertKv({
|
|
11694
|
+
namespace: CODEX_AUTH_WATCH_NAMESPACE,
|
|
11695
|
+
key: codexAuthBenchMarkerKey(keySuffix),
|
|
11696
|
+
valueType: "json",
|
|
11697
|
+
value: { keyIndex, keyType, benchedAt: now, benchedUntil, lastFailAt: now, source },
|
|
11698
|
+
expiresAt: Date.parse(benchedUntil) + 86_400_000,
|
|
11699
|
+
});
|
|
11700
|
+
return { consecutiveAuthFailures, benched: true, rateLimitedUntil: benchedUntil };
|
|
11701
|
+
});
|
|
11702
|
+
}
|
|
11703
|
+
|
|
11659
11704
|
/**
|
|
11660
11705
|
* Merges reported window snapshots into the stored ones, one window type at a
|
|
11661
11706
|
* time. A reported entry replaces the stored entry unless the stored entry was
|
|
@@ -11797,24 +11842,48 @@ export async function setApiKeyName(
|
|
|
11797
11842
|
}
|
|
11798
11843
|
|
|
11799
11844
|
/**
|
|
11800
|
-
* Clear a stale rate-limit record after a successful use proves the key is healthy.
|
|
11845
|
+
* Clear a stale rate-limit record after a successful use proves the key is healthy. With
|
|
11846
|
+
* `clearAuthBench` + `authFence` (the `authFailureFence` read before the task or credential write),
|
|
11847
|
+
* also lift an auth bench, reset the count and delete the Codex bench marker, but never for a
|
|
11848
|
+
* failure recorded after the fence. `keyIndex` (slot re-login) also retires the slot's other logins.
|
|
11801
11849
|
*/
|
|
11802
11850
|
export async function clearKeyRateLimit(
|
|
11803
11851
|
keyType: string,
|
|
11804
11852
|
keySuffix: string,
|
|
11805
11853
|
scope = "global",
|
|
11806
11854
|
scopeId: string | null = null,
|
|
11855
|
+
opts: { clearAuthBench?: boolean; keyIndex?: number; authFence?: number } = {},
|
|
11807
11856
|
): Promise<boolean> {
|
|
11808
11857
|
const now = new Date().toISOString();
|
|
11809
11858
|
const effectiveScopeId = scopeId ?? "";
|
|
11810
|
-
const
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11859
|
+
const { keyIndex = -1, authFence = null } = opts;
|
|
11860
|
+
const clearAuthBench = opts.clearAuthBench === true && authFence !== null;
|
|
11861
|
+
const key = [keyType, keySuffix, scope, effectiveScopeId];
|
|
11862
|
+
return await getDbClient().transaction(async (tx) => {
|
|
11863
|
+
const result = await tx.run(
|
|
11864
|
+
`UPDATE api_key_status
|
|
11865
|
+
SET status = 'available', rateLimitedUntil = NULL, updatedAt = ?
|
|
11866
|
+
WHERE keyType = ? AND keySuffix = ? AND scope = ? AND scopeId = ?
|
|
11867
|
+
AND status = 'rate_limited' AND ((? AND authFailureSeq <= ?) OR consecutiveAuthFailures < ?)`,
|
|
11868
|
+
[now, ...key, clearAuthBench ? 1 : 0, authFence, CODEX_AUTH_FAILURE_BENCH_THRESHOLD],
|
|
11869
|
+
);
|
|
11870
|
+
const cleared = result.changes > 0;
|
|
11871
|
+
if (!clearAuthBench) return cleared;
|
|
11872
|
+
const lifted = await tx.query<{ keySuffix: string }>(
|
|
11873
|
+
`UPDATE api_key_status SET consecutiveAuthFailures = 0, updatedAt = ?1,
|
|
11874
|
+
status = CASE WHEN keySuffix = ?2 THEN status ELSE 'available' END, rateLimitedUntil = CASE WHEN keySuffix = ?2 THEN rateLimitedUntil END
|
|
11875
|
+
WHERE keyType = ?3 AND scope = ?4 AND scopeId = ?5 AND (consecutiveAuthFailures > 0 OR status = 'rate_limited')
|
|
11876
|
+
AND ((keySuffix = ?2 AND authFailureSeq <= ?7) OR (keySuffix != ?2 AND keyIndex = ?6)) RETURNING keySuffix`,
|
|
11877
|
+
[now, keySuffix, keyType, scope, effectiveScopeId, keyIndex, authFence],
|
|
11878
|
+
);
|
|
11879
|
+
const retired = lifted.filter((row) => row.keySuffix !== keySuffix);
|
|
11880
|
+
if (keyType === "CODEX_OAUTH") {
|
|
11881
|
+
for (const suffix of [...(cleared ? [keySuffix] : []), ...retired.map((r) => r.keySuffix)]) {
|
|
11882
|
+
await deleteKv(CODEX_AUTH_WATCH_NAMESPACE, codexAuthBenchMarkerKey(suffix));
|
|
11883
|
+
}
|
|
11884
|
+
}
|
|
11885
|
+
return cleared || retired.length > 0;
|
|
11886
|
+
});
|
|
11818
11887
|
}
|
|
11819
11888
|
|
|
11820
11889
|
/**
|
|
@@ -12112,6 +12181,21 @@ export async function deleteUser(id: string, replacementUserId?: string): Promis
|
|
|
12112
12181
|
)`,
|
|
12113
12182
|
);
|
|
12114
12183
|
const replacement = replacementUserId ?? null;
|
|
12184
|
+
// The human-free rule only asks whether a task's requester or its workflow
|
|
12185
|
+
// run's creator is NULL, so only clearing them (no replacement) can change a
|
|
12186
|
+
// stored flag. Gather the tasks now: after the rewrite nothing identifies
|
|
12187
|
+
// them. The workflow branch covers roots whose requester was never set.
|
|
12188
|
+
const reclassifySeedIds: string[] = replacementUserId
|
|
12189
|
+
? []
|
|
12190
|
+
: (
|
|
12191
|
+
await tx.query<{ id: string }>(
|
|
12192
|
+
`SELECT id FROM agent_tasks WHERE requestedByUserId = ?
|
|
12193
|
+
UNION
|
|
12194
|
+
SELECT id FROM agent_tasks
|
|
12195
|
+
WHERE workflowRunId IN (SELECT id FROM workflow_runs WHERE created_by = ?)`,
|
|
12196
|
+
[id, id],
|
|
12197
|
+
)
|
|
12198
|
+
).map((row) => row.id);
|
|
12115
12199
|
for (const reference of references) {
|
|
12116
12200
|
const table = quoteSqlIdentifier(reference.tableName);
|
|
12117
12201
|
const column = quoteSqlIdentifier(reference.columnName);
|
|
@@ -12139,6 +12223,8 @@ export async function deleteUser(id: string, replacementUserId?: string): Promis
|
|
|
12139
12223
|
);
|
|
12140
12224
|
}
|
|
12141
12225
|
|
|
12226
|
+
await reclassifyTaskHumanFree(reclassifySeedIds);
|
|
12227
|
+
|
|
12142
12228
|
const result = await tx.run("DELETE FROM users WHERE id = ?", [id]);
|
|
12143
12229
|
return result.changes > 0;
|
|
12144
12230
|
});
|