@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
|
@@ -16,10 +16,12 @@ import { startWorkflowExecution } from "../workflows/engine";
|
|
|
16
16
|
import { BaseExecutor, type ExecutorResult } from "../workflows/executors/base";
|
|
17
17
|
import { ExecutorRegistry } from "../workflows/executors/registry";
|
|
18
18
|
import {
|
|
19
|
+
decodeStandardWebhookSecret,
|
|
19
20
|
handleEventTrigger,
|
|
20
21
|
handleWebhookTrigger,
|
|
21
22
|
logOpenWebhookTriggers,
|
|
22
23
|
verifyHmacSignature,
|
|
24
|
+
verifyStandardWebhookSignature,
|
|
23
25
|
verifyTimestampedHmacSignature,
|
|
24
26
|
verifyTokenEquality,
|
|
25
27
|
WebhookError,
|
|
@@ -211,6 +213,169 @@ describe("verifyTokenEquality", () => {
|
|
|
211
213
|
});
|
|
212
214
|
});
|
|
213
215
|
|
|
216
|
+
// Official vector from github.com/standard-webhooks/standard-webhooks
|
|
217
|
+
// (libraries/javascript/src/webhook.test.ts, "sign function works").
|
|
218
|
+
const SW_SECRET = "MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
|
|
219
|
+
const SW_MSG_ID = "msg_p5jXN8AQM9LWM0D4loKWxJek";
|
|
220
|
+
const SW_TIMESTAMP = 1614265330;
|
|
221
|
+
const SW_PAYLOAD = '{"test": 2432232314}';
|
|
222
|
+
const SW_EXPECTED_SIGNATURE = "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=";
|
|
223
|
+
|
|
224
|
+
function signStandardWebhook(secret: string, id: string, timestamp: number, body: string): string {
|
|
225
|
+
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
|
|
226
|
+
const digest = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${body}`);
|
|
227
|
+
return `v1,${digest.digest("base64")}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
describe("verifyStandardWebhookSignature", () => {
|
|
231
|
+
const key = decodeStandardWebhookSecret(SW_SECRET)!;
|
|
232
|
+
const nowMs = SW_TIMESTAMP * 1000;
|
|
233
|
+
const headers = (signature: string, timestamp = SW_TIMESTAMP) => ({
|
|
234
|
+
id: SW_MSG_ID,
|
|
235
|
+
timestamp: String(timestamp),
|
|
236
|
+
signature,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("official test vector passes", () => {
|
|
240
|
+
expect(signStandardWebhook(SW_SECRET, SW_MSG_ID, SW_TIMESTAMP, SW_PAYLOAD)).toBe(
|
|
241
|
+
SW_EXPECTED_SIGNATURE,
|
|
242
|
+
);
|
|
243
|
+
expect(
|
|
244
|
+
verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(SW_EXPECTED_SIGNATURE), {}, nowMs),
|
|
245
|
+
).toBe(true);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("secret decodes the same with and without the whsec_ prefix", () => {
|
|
249
|
+
expect(decodeStandardWebhookSecret(`whsec_${SW_SECRET}`)).toEqual(key);
|
|
250
|
+
expect(
|
|
251
|
+
verifyStandardWebhookSignature(
|
|
252
|
+
decodeStandardWebhookSecret(`whsec_${SW_SECRET}`)!,
|
|
253
|
+
SW_PAYLOAD,
|
|
254
|
+
headers(SW_EXPECTED_SIGNATURE),
|
|
255
|
+
{},
|
|
256
|
+
nowMs,
|
|
257
|
+
),
|
|
258
|
+
).toBe(true);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("invalid or empty secrets decode to null", () => {
|
|
262
|
+
expect(decodeStandardWebhookSecret("whsec_")).toBeNull();
|
|
263
|
+
expect(decodeStandardWebhookSecret("")).toBeNull();
|
|
264
|
+
expect(decodeStandardWebhookSecret("not base64!")).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("tampered body fails", () => {
|
|
268
|
+
expect(
|
|
269
|
+
verifyStandardWebhookSignature(
|
|
270
|
+
key,
|
|
271
|
+
'{"test": 2432232315}',
|
|
272
|
+
headers(SW_EXPECTED_SIGNATURE),
|
|
273
|
+
{},
|
|
274
|
+
nowMs,
|
|
275
|
+
),
|
|
276
|
+
).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("re-serialized JSON body fails (verification is over raw bytes)", () => {
|
|
280
|
+
const reserialized = JSON.stringify(JSON.parse(SW_PAYLOAD));
|
|
281
|
+
expect(
|
|
282
|
+
verifyStandardWebhookSignature(key, reserialized, headers(SW_EXPECTED_SIGNATURE), {}, nowMs),
|
|
283
|
+
).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("wrong secret fails", () => {
|
|
287
|
+
const wrong = decodeStandardWebhookSecret(Buffer.alloc(24, 7).toString("base64"))!;
|
|
288
|
+
expect(
|
|
289
|
+
verifyStandardWebhookSignature(wrong, SW_PAYLOAD, headers(SW_EXPECTED_SIGNATURE), {}, nowMs),
|
|
290
|
+
).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("tampered webhook-id fails", () => {
|
|
294
|
+
expect(
|
|
295
|
+
verifyStandardWebhookSignature(
|
|
296
|
+
key,
|
|
297
|
+
SW_PAYLOAD,
|
|
298
|
+
{ ...headers(SW_EXPECTED_SIGNATURE), id: "msg_other" },
|
|
299
|
+
{},
|
|
300
|
+
nowMs,
|
|
301
|
+
),
|
|
302
|
+
).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("stale timestamp beyond the default 5 minute tolerance fails", () => {
|
|
306
|
+
const stale = SW_TIMESTAMP - 301;
|
|
307
|
+
const signature = signStandardWebhook(SW_SECRET, SW_MSG_ID, stale, SW_PAYLOAD);
|
|
308
|
+
expect(
|
|
309
|
+
verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(signature, stale), {}, nowMs),
|
|
310
|
+
).toBe(false);
|
|
311
|
+
const edge = SW_TIMESTAMP - 300;
|
|
312
|
+
const edgeSignature = signStandardWebhook(SW_SECRET, SW_MSG_ID, edge, SW_PAYLOAD);
|
|
313
|
+
expect(
|
|
314
|
+
verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(edgeSignature, edge), {}, nowMs),
|
|
315
|
+
).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("future timestamp beyond tolerance fails", () => {
|
|
319
|
+
const future = SW_TIMESTAMP + 301;
|
|
320
|
+
const signature = signStandardWebhook(SW_SECRET, SW_MSG_ID, future, SW_PAYLOAD);
|
|
321
|
+
expect(
|
|
322
|
+
verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(signature, future), {}, nowMs),
|
|
323
|
+
).toBe(false);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("custom toleranceSeconds is honored", () => {
|
|
327
|
+
const stale = SW_TIMESTAMP - 600;
|
|
328
|
+
const signature = signStandardWebhook(SW_SECRET, SW_MSG_ID, stale, SW_PAYLOAD);
|
|
329
|
+
expect(
|
|
330
|
+
verifyStandardWebhookSignature(
|
|
331
|
+
key,
|
|
332
|
+
SW_PAYLOAD,
|
|
333
|
+
headers(signature, stale),
|
|
334
|
+
{ toleranceSeconds: 900 },
|
|
335
|
+
nowMs,
|
|
336
|
+
),
|
|
337
|
+
).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("garbled timestamp fails", () => {
|
|
341
|
+
expect(
|
|
342
|
+
verifyStandardWebhookSignature(
|
|
343
|
+
key,
|
|
344
|
+
SW_PAYLOAD,
|
|
345
|
+
{ ...headers(SW_EXPECTED_SIGNATURE), timestamp: "not-a-number" },
|
|
346
|
+
{},
|
|
347
|
+
nowMs,
|
|
348
|
+
),
|
|
349
|
+
).toBe(false);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("multiple signatures pass when one v1 entry matches", () => {
|
|
353
|
+
const list = [
|
|
354
|
+
"v1,Ceo5qEr07ixe2NLpvHk3FH9bwy/WavXrAFQ/9tdO6mc=",
|
|
355
|
+
"v2,Ceo5qEr07ixe2NLpvHk3FH9bwy/WavXrAFQ/9tdO6mc=",
|
|
356
|
+
SW_EXPECTED_SIGNATURE,
|
|
357
|
+
"v1a,hnO3f9T8Ytu9HwrXslvumlUpqtNVqkhqw/enGzPCXe5BdqzCInXqYXFymVJaA7AZdpXwVLPo3mNl8EM+m7TBAg==",
|
|
358
|
+
].join(" ");
|
|
359
|
+
expect(verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(list), {}, nowMs)).toBe(true);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("a matching digest under a non-v1 version fails", () => {
|
|
363
|
+
const digest = SW_EXPECTED_SIGNATURE.slice("v1,".length);
|
|
364
|
+
expect(
|
|
365
|
+
verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(`v2,${digest}`), {}, nowMs),
|
|
366
|
+
).toBe(false);
|
|
367
|
+
expect(verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(digest), {}, nowMs)).toBe(false);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("empty or partial signatures fail without throwing", () => {
|
|
371
|
+
for (const signature of ["", "v1,", "v1,dawfeoifkpqwoekfpqoekf", "v1"]) {
|
|
372
|
+
expect(verifyStandardWebhookSignature(key, SW_PAYLOAD, headers(signature), {}, nowMs)).toBe(
|
|
373
|
+
false,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
214
379
|
// ─── Webhook Trigger ────────────────────────────────────────
|
|
215
380
|
|
|
216
381
|
describe("handleWebhookTrigger", () => {
|
|
@@ -614,6 +779,136 @@ describe("handleWebhookTrigger — verification formats", () => {
|
|
|
614
779
|
}
|
|
615
780
|
});
|
|
616
781
|
|
|
782
|
+
describe("standard-webhooks", () => {
|
|
783
|
+
const secret = `whsec_${Buffer.alloc(32, 3).toString("base64")}`;
|
|
784
|
+
|
|
785
|
+
async function makeStandardWorkflow(hmacSecret = secret): Promise<Workflow> {
|
|
786
|
+
return makeWorkflow({
|
|
787
|
+
triggers: [{ type: "webhook", hmacSecret, verification: { format: "standard-webhooks" } }],
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function standardHeaders(body: string, signingSecret = secret): Record<string, string> {
|
|
792
|
+
const id = `msg_${crypto.randomUUID()}`;
|
|
793
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
794
|
+
return {
|
|
795
|
+
"webhook-id": id,
|
|
796
|
+
"webhook-timestamp": String(timestamp),
|
|
797
|
+
"webhook-signature": signStandardWebhook(signingSecret, id, timestamp, body),
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async function expectRejected(
|
|
802
|
+
workflowId: string,
|
|
803
|
+
body: string,
|
|
804
|
+
headers: Record<string, string>,
|
|
805
|
+
statusCode: number,
|
|
806
|
+
): Promise<void> {
|
|
807
|
+
try {
|
|
808
|
+
await handleWebhookTrigger(workflowId, body, headers, registry);
|
|
809
|
+
expect(true).toBe(false);
|
|
810
|
+
} catch (err) {
|
|
811
|
+
expect(err).toBeInstanceOf(WebhookError);
|
|
812
|
+
expect((err as WebhookError).statusCode).toBe(statusCode);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
test("starts the workflow for a correctly signed request", async () => {
|
|
817
|
+
const workflow = await makeStandardWorkflow();
|
|
818
|
+
const body = '{"event_type":"note.updated", "data": {"id":"not_123"}}';
|
|
819
|
+
|
|
820
|
+
const result = await handleWebhookTrigger(workflow.id, body, standardHeaders(body), registry);
|
|
821
|
+
|
|
822
|
+
const run = await getWorkflowRun(result.runId);
|
|
823
|
+
expect(run!.triggerData).toEqual({ event_type: "note.updated", data: { id: "not_123" } });
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
test("accepts a secret stored without the whsec_ prefix and mixed-case headers", async () => {
|
|
827
|
+
const workflow = await makeStandardWorkflow(secret.slice("whsec_".length));
|
|
828
|
+
const body = '{"event":"plain-secret"}';
|
|
829
|
+
const headers = standardHeaders(body);
|
|
830
|
+
|
|
831
|
+
const result = await handleWebhookTrigger(
|
|
832
|
+
workflow.id,
|
|
833
|
+
body,
|
|
834
|
+
{
|
|
835
|
+
"Webhook-Id": headers["webhook-id"]!,
|
|
836
|
+
"Webhook-Timestamp": headers["webhook-timestamp"]!,
|
|
837
|
+
"Webhook-Signature": headers["webhook-signature"]!,
|
|
838
|
+
},
|
|
839
|
+
registry,
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
expect(result.runId).toBeDefined();
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
test("resolves a secret.NAME reference", async () => {
|
|
846
|
+
const name = `SW_SECRET_${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
|
|
847
|
+
await upsertSwarmConfig({ scope: "global", key: name, value: secret, isSecret: true });
|
|
848
|
+
const workflow = await makeStandardWorkflow(secretRef(name));
|
|
849
|
+
const body = '{"event":"secret-ref"}';
|
|
850
|
+
|
|
851
|
+
const result = await handleWebhookTrigger(workflow.id, body, standardHeaders(body), registry);
|
|
852
|
+
|
|
853
|
+
expect(result.runId).toBeDefined();
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
test("rejects a tampered body", async () => {
|
|
857
|
+
const workflow = await makeStandardWorkflow();
|
|
858
|
+
const headers = standardHeaders('{"event":"original"}');
|
|
859
|
+
await expectRejected(workflow.id, '{"event":"tampered"}', headers, 401);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
test("rejects a signature made with a different secret", async () => {
|
|
863
|
+
const workflow = await makeStandardWorkflow();
|
|
864
|
+
const body = '{"event":"wrong-secret"}';
|
|
865
|
+
const other = `whsec_${Buffer.alloc(32, 9).toString("base64")}`;
|
|
866
|
+
await expectRejected(workflow.id, body, standardHeaders(body, other), 401);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test("rejects stale and future timestamps", async () => {
|
|
870
|
+
const workflow = await makeStandardWorkflow();
|
|
871
|
+
const body = '{"event":"replay"}';
|
|
872
|
+
const now = Math.floor(Date.now() / 1000);
|
|
873
|
+
for (const timestamp of [now - 3600, now + 3600]) {
|
|
874
|
+
await expectRejected(
|
|
875
|
+
workflow.id,
|
|
876
|
+
body,
|
|
877
|
+
{
|
|
878
|
+
"webhook-id": "msg_replay",
|
|
879
|
+
"webhook-timestamp": String(timestamp),
|
|
880
|
+
"webhook-signature": signStandardWebhook(secret, "msg_replay", timestamp, body),
|
|
881
|
+
},
|
|
882
|
+
401,
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test("rejects a request missing any required header", async () => {
|
|
888
|
+
const workflow = await makeStandardWorkflow();
|
|
889
|
+
const body = '{"event":"missing-header"}';
|
|
890
|
+
const headers = standardHeaders(body);
|
|
891
|
+
for (const name of ["webhook-id", "webhook-timestamp", "webhook-signature"]) {
|
|
892
|
+
const partial = { ...headers };
|
|
893
|
+
delete partial[name];
|
|
894
|
+
await expectRejected(workflow.id, body, partial, 401);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("ignores signatures on legacy fallback headers", async () => {
|
|
899
|
+
const workflow = await makeStandardWorkflow();
|
|
900
|
+
const body = '{"event":"fallback"}';
|
|
901
|
+
const { "webhook-signature": signature, ...rest } = standardHeaders(body);
|
|
902
|
+
await expectRejected(workflow.id, body, { ...rest, "x-webhook-signature": signature! }, 401);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test("fails closed with 500 when the configured secret is not base64", async () => {
|
|
906
|
+
const workflow = await makeStandardWorkflow("whsec_not base64!");
|
|
907
|
+
const body = '{"event":"bad-secret"}';
|
|
908
|
+
await expectRejected(workflow.id, body, standardHeaders(body), 500);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
|
|
617
912
|
test("verification configured without hmacSecret fails closed instead of accepting the request", async () => {
|
|
618
913
|
// Bypasses the create/update Zod schema (which now also rejects this shape) to
|
|
619
914
|
// exercise the runtime guard directly — e.g. for data written before this fix,
|
|
@@ -867,6 +1162,31 @@ describe("TriggerConfigSchema", () => {
|
|
|
867
1162
|
expect(result.success).toBe(true);
|
|
868
1163
|
});
|
|
869
1164
|
|
|
1165
|
+
test("accepts standard-webhooks verification and defaults its tolerance to 300s", () => {
|
|
1166
|
+
const result = TriggerConfigSchema.safeParse({
|
|
1167
|
+
type: "webhook",
|
|
1168
|
+
hmacSecret: "secret.STANDARD_WEBHOOK_SECRET",
|
|
1169
|
+
verification: { format: "standard-webhooks" },
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
expect(result.success).toBe(true);
|
|
1173
|
+
if (result.success && result.data.type === "webhook") {
|
|
1174
|
+
expect(result.data.verification).toEqual({
|
|
1175
|
+
format: "standard-webhooks",
|
|
1176
|
+
toleranceSeconds: 300,
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
test("rejects standard-webhooks verification without hmacSecret", () => {
|
|
1182
|
+
const result = TriggerConfigSchema.safeParse({
|
|
1183
|
+
type: "webhook",
|
|
1184
|
+
verification: { format: "standard-webhooks" },
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
expect(result.success).toBe(false);
|
|
1188
|
+
});
|
|
1189
|
+
|
|
870
1190
|
test("accepts a webhook trigger with neither hmacSecret nor verification (intentionally unauthenticated)", () => {
|
|
871
1191
|
const result = TriggerConfigSchema.safeParse({ type: "webhook" });
|
|
872
1192
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentById } from "@/be/db";
|
|
4
|
+
import { upsertModelCatalogOverlay } from "@/be/model-catalog-store";
|
|
5
|
+
import { requestModelCatalogRefresh } from "@/be/pricing-refresh";
|
|
6
|
+
import { can } from "@/rbac";
|
|
7
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
8
|
+
|
|
9
|
+
async function denyUnlessCatalogWriter(agentId: string): Promise<string | null> {
|
|
10
|
+
const agent = await getAgentById(agentId);
|
|
11
|
+
const decision = can({
|
|
12
|
+
principal: { kind: "agent", agentId, isLead: agent?.isLead ?? false },
|
|
13
|
+
verb: "models.catalog.write",
|
|
14
|
+
resource: { kind: "none" },
|
|
15
|
+
source: "mcp",
|
|
16
|
+
});
|
|
17
|
+
return decision.allow ? null : "Writing the model catalog requires the lead agent.";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const registerModelCatalogRefreshTool = (server: McpServer) => {
|
|
21
|
+
createToolRegistrar(server)(
|
|
22
|
+
"model-catalog-refresh",
|
|
23
|
+
{
|
|
24
|
+
title: "Refresh Model Catalog",
|
|
25
|
+
description:
|
|
26
|
+
"Refresh the swarm model catalog (and pricing rows) from models.dev, like `pi update --models`. Without force, skips the network when the last check is under 4h old. A forced refresh is accepted once per minute; a second one inside that window returns `skipped-cooldown` with `retryAfterMs`. Lead agent only. Returns the status, model count, and newly added provider/modelId keys.",
|
|
27
|
+
annotations: { idempotentHint: true, openWorldHint: true },
|
|
28
|
+
inputSchema: z.object({
|
|
29
|
+
force: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe(
|
|
33
|
+
"Fetch even if the last check is under 4h old (default false). Rate limited to one per minute.",
|
|
34
|
+
),
|
|
35
|
+
}),
|
|
36
|
+
outputSchema: swarmToolOutputSchema({
|
|
37
|
+
status: z.string().optional(),
|
|
38
|
+
models: z.number().optional(),
|
|
39
|
+
added: z.array(z.string()).optional(),
|
|
40
|
+
checkedAt: z.number().nullable().optional(),
|
|
41
|
+
retryAfterMs: z.number().optional(),
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
async ({ force }, requestInfo) => {
|
|
45
|
+
if (!requestInfo.agentId) {
|
|
46
|
+
return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
|
|
47
|
+
}
|
|
48
|
+
const denied = await denyUnlessCatalogWriter(requestInfo.agentId);
|
|
49
|
+
if (denied) return toolErr(denied);
|
|
50
|
+
|
|
51
|
+
const result = await requestModelCatalogRefresh({ force });
|
|
52
|
+
const data = {
|
|
53
|
+
status: result.status,
|
|
54
|
+
models: result.models,
|
|
55
|
+
added: result.added,
|
|
56
|
+
checkedAt: result.checkedAt,
|
|
57
|
+
retryAfterMs: result.retryAfterMs,
|
|
58
|
+
};
|
|
59
|
+
if (result.status === "error") {
|
|
60
|
+
return toolErr(`Model catalog refresh failed: ${result.error ?? "unknown error"}`, {
|
|
61
|
+
data,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const addedText = result.added.length > 0 ? ` Added: ${result.added.join(", ")}.` : "";
|
|
65
|
+
return toolOk(
|
|
66
|
+
`Model catalog ${result.status}: ${result.models} model(s), ${result.added.length} new.`,
|
|
67
|
+
{
|
|
68
|
+
details: `status=${result.status}; models=${result.models}; added=${result.added.length}.${addedText}`,
|
|
69
|
+
data,
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const registerModelCatalogOverlayUpsertTool = (server: McpServer) => {
|
|
77
|
+
createToolRegistrar(server)(
|
|
78
|
+
"model-catalog-overlay-upsert",
|
|
79
|
+
{
|
|
80
|
+
title: "Upsert Model Catalog Overlay",
|
|
81
|
+
description:
|
|
82
|
+
"Add or update hand-verified facts for one model (e.g. a launch models.dev has not listed yet). Overlay fields win over models.dev; overlay prices fill pricing-table gaps so cost recompute prices the model. The row auto-expires once models.dev matches every fact you set.",
|
|
83
|
+
annotations: { idempotentHint: true },
|
|
84
|
+
inputSchema: z.object({
|
|
85
|
+
provider: z
|
|
86
|
+
.string()
|
|
87
|
+
.min(1)
|
|
88
|
+
.describe(
|
|
89
|
+
"models.dev provider id: anthropic, openai, openrouter, amazon-bedrock, opencode.",
|
|
90
|
+
),
|
|
91
|
+
modelId: z
|
|
92
|
+
.string()
|
|
93
|
+
.min(1)
|
|
94
|
+
.describe("Model id as models.dev keys it (e.g. claude-opus-5-5)."),
|
|
95
|
+
name: z.string().optional().describe("Display name."),
|
|
96
|
+
releaseDate: z.string().optional().describe("Release date, YYYY-MM-DD."),
|
|
97
|
+
contextWindow: z.number().int().positive().optional().describe("Context window, tokens."),
|
|
98
|
+
maxOutput: z.number().int().positive().optional().describe("Max output tokens."),
|
|
99
|
+
reasoningOptions: z
|
|
100
|
+
.array(z.object({ type: z.string(), values: z.array(z.string()).optional() }))
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("Reasoning options, models.dev shape: [{type, values?}]."),
|
|
103
|
+
pricing: z
|
|
104
|
+
.object({
|
|
105
|
+
input: z.number().nonnegative().optional(),
|
|
106
|
+
output: z.number().nonnegative().optional(),
|
|
107
|
+
cache_read: z.number().nonnegative().optional(),
|
|
108
|
+
cache_write: z.number().nonnegative().optional(),
|
|
109
|
+
})
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("USD per million tokens."),
|
|
112
|
+
reason: z.string().min(1).describe("Why this overlay exists (source of the facts)."),
|
|
113
|
+
verifiedBy: z.string().optional().describe("Who verified the facts (URL, person, agent)."),
|
|
114
|
+
}),
|
|
115
|
+
outputSchema: swarmToolOutputSchema({
|
|
116
|
+
overlay: z.looseObject({}).optional(),
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
async (input, requestInfo) => {
|
|
120
|
+
if (!requestInfo.agentId) {
|
|
121
|
+
return toolErr('Agent ID not found. Set the "X-Agent-ID" header.');
|
|
122
|
+
}
|
|
123
|
+
const denied = await denyUnlessCatalogWriter(requestInfo.agentId);
|
|
124
|
+
if (denied) return toolErr(denied);
|
|
125
|
+
try {
|
|
126
|
+
const overlay = await upsertModelCatalogOverlay(input);
|
|
127
|
+
return toolOk(`Overlay for ${input.provider}/${input.modelId} saved.`, {
|
|
128
|
+
details: `Overlay for ${input.provider}/${input.modelId} saved (reason: ${input.reason}). It expires when models.dev matches every fact set.`,
|
|
129
|
+
data: { overlay },
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
133
|
+
return toolErr(`Failed to save overlay: ${message}`);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
};
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getWorkflow,
|
|
12
12
|
isExtensionAgent,
|
|
13
13
|
} from "@/be/db";
|
|
14
|
+
import { explicitModelErrorForAgent } from "@/be/model-validation";
|
|
14
15
|
import { getScript } from "@/be/scripts/db";
|
|
15
16
|
import { calculateNextRun } from "@/scheduler";
|
|
16
17
|
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
@@ -104,11 +105,17 @@ export const createScheduleInputSchema = z.object({
|
|
|
104
105
|
.min(1)
|
|
105
106
|
.optional()
|
|
106
107
|
.describe(
|
|
107
|
-
"Concrete model override for tasks created by this schedule. Interpreted by each assignee's harness/provider and does not switch providers. Prefer modelTier for portable intent.",
|
|
108
|
+
"Concrete model override for tasks created by this schedule. Interpreted by each assignee's harness/provider and does not switch providers. Prefer modelTier for portable intent. The model must run on the assignee's harness (an Anthropic model on a Claude agent, an OpenAI model on a Codex agent); a mismatch is rejected.",
|
|
108
109
|
),
|
|
109
110
|
modelTier: ModelTierSchema.optional().describe(
|
|
110
111
|
"Portable model tier for tasks created by this schedule: 'smol', 'regular', 'smart', or 'ultra'. Resolved by each assignee's harness/provider at run time.",
|
|
111
112
|
),
|
|
113
|
+
allowCustomModel: z
|
|
114
|
+
.boolean()
|
|
115
|
+
.optional()
|
|
116
|
+
.describe(
|
|
117
|
+
"Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only for ids the catalog cannot know yet.",
|
|
118
|
+
),
|
|
112
119
|
});
|
|
113
120
|
|
|
114
121
|
const scheduleDataShape = {
|
|
@@ -177,6 +184,7 @@ export const registerCreateScheduleTool = (server: McpServer) => {
|
|
|
177
184
|
enabled,
|
|
178
185
|
model,
|
|
179
186
|
modelTier,
|
|
187
|
+
allowCustomModel,
|
|
180
188
|
},
|
|
181
189
|
requestInfo,
|
|
182
190
|
_meta,
|
|
@@ -263,8 +271,15 @@ export const registerCreateScheduleTool = (server: McpServer) => {
|
|
|
263
271
|
}
|
|
264
272
|
}
|
|
265
273
|
|
|
274
|
+
const normalizedModel = splitLegacyModelAlias({ model, modelTier });
|
|
275
|
+
const modelError = await explicitModelErrorForAgent({
|
|
276
|
+
model: normalizedModel.model,
|
|
277
|
+
allowCustomModel,
|
|
278
|
+
agentId: targetAgentId,
|
|
279
|
+
});
|
|
280
|
+
if (modelError) return toolErr(modelError);
|
|
281
|
+
|
|
266
282
|
try {
|
|
267
|
-
const normalizedModel = splitLegacyModelAlias({ model, modelTier });
|
|
268
283
|
// Calculate initial nextRunAt
|
|
269
284
|
let nextRunAt: string | undefined;
|
|
270
285
|
if (enabled === false) {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
isExtensionAgent,
|
|
13
13
|
updateScheduledTask,
|
|
14
14
|
} from "@/be/db";
|
|
15
|
+
import { explicitModelErrorForAgent } from "@/be/model-validation";
|
|
15
16
|
import { mergeScheduleTiming, validateRecurringTiming } from "@/be/schedules/validate";
|
|
16
17
|
import { getScript } from "@/be/scripts/db";
|
|
17
18
|
import { calculateNextRun } from "@/scheduler";
|
|
@@ -69,10 +70,18 @@ export const patchScheduleInputSchema = z.object({
|
|
|
69
70
|
.min(1)
|
|
70
71
|
.nullable()
|
|
71
72
|
.optional()
|
|
72
|
-
.describe(
|
|
73
|
+
.describe(
|
|
74
|
+
"Concrete model override for tasks created by this schedule. Set to null to clear. The model must run on the assignee's harness (an Anthropic model on a Claude agent, an OpenAI model on a Codex agent); a mismatch is rejected.",
|
|
75
|
+
),
|
|
73
76
|
modelTier: ModelTierSchema.nullable()
|
|
74
77
|
.optional()
|
|
75
78
|
.describe("Portable model tier for tasks created by this schedule. Set to null to clear."),
|
|
79
|
+
allowCustomModel: z
|
|
80
|
+
.boolean()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe(
|
|
83
|
+
"Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only for ids the catalog cannot know yet.",
|
|
84
|
+
),
|
|
76
85
|
});
|
|
77
86
|
|
|
78
87
|
const scheduleDataShape = {
|
|
@@ -140,6 +149,7 @@ export const registerPatchScheduleTool = (server: McpServer) => {
|
|
|
140
149
|
enabled,
|
|
141
150
|
model,
|
|
142
151
|
modelTier,
|
|
152
|
+
allowCustomModel,
|
|
143
153
|
},
|
|
144
154
|
requestInfo,
|
|
145
155
|
_meta,
|
|
@@ -247,12 +257,32 @@ export const registerPatchScheduleTool = (server: McpServer) => {
|
|
|
247
257
|
if (targetAgentId !== undefined) updateData.targetAgentId = targetAgentId;
|
|
248
258
|
if (timezone !== undefined) updateData.timezone = timezone;
|
|
249
259
|
if (enabled !== undefined) updateData.enabled = enabled;
|
|
260
|
+
// A move to another agent re-judges the stored model against the new harness.
|
|
261
|
+
const agentChanged =
|
|
262
|
+
targetAgentId !== undefined && targetAgentId !== schedule.targetAgentId;
|
|
250
263
|
if (model !== undefined || modelTier !== undefined) {
|
|
251
264
|
const normalizedModel = splitLegacyModelAlias({ model, modelTier });
|
|
265
|
+
// A model the schedule already stores is not re-judged, so an unrelated edit still saves.
|
|
266
|
+
if (normalizedModel.model !== schedule.model || agentChanged) {
|
|
267
|
+
const modelError = await explicitModelErrorForAgent({
|
|
268
|
+
model: normalizedModel.model,
|
|
269
|
+
allowCustomModel: allowCustomModel || normalizedModel.model === schedule.model,
|
|
270
|
+
agentId: targetAgentId ?? schedule.targetAgentId,
|
|
271
|
+
});
|
|
272
|
+
if (modelError) return toolErr(modelError);
|
|
273
|
+
}
|
|
252
274
|
if (model !== undefined) updateData.model = normalizedModel.model ?? null;
|
|
253
275
|
if (modelTier !== undefined || normalizedModel.modelTier) {
|
|
254
276
|
updateData.modelTier = normalizedModel.modelTier ?? null;
|
|
255
277
|
}
|
|
278
|
+
} else if (agentChanged && schedule.model) {
|
|
279
|
+
// Harness only: the stored id already passed the catalog check when it was written.
|
|
280
|
+
const modelError = await explicitModelErrorForAgent({
|
|
281
|
+
model: schedule.model,
|
|
282
|
+
allowCustomModel: true,
|
|
283
|
+
agentId: targetAgentId ?? schedule.targetAgentId,
|
|
284
|
+
});
|
|
285
|
+
if (modelError) return toolErr(modelError);
|
|
256
286
|
}
|
|
257
287
|
|
|
258
288
|
// Recalculate nextRunAt based on schedule type
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
isExtensionAgent,
|
|
13
13
|
updateScheduledTask,
|
|
14
14
|
} from "@/be/db";
|
|
15
|
+
import { explicitModelErrorForAgent } from "@/be/model-validation";
|
|
15
16
|
import { mergeScheduleTiming, validateRecurringTiming } from "@/be/schedules/validate";
|
|
16
17
|
import { getScript } from "@/be/scripts/db";
|
|
17
18
|
import { calculateNextRun } from "@/scheduler";
|
|
@@ -69,10 +70,18 @@ export const updateScheduleInputSchema = z.object({
|
|
|
69
70
|
.min(1)
|
|
70
71
|
.nullable()
|
|
71
72
|
.optional()
|
|
72
|
-
.describe(
|
|
73
|
+
.describe(
|
|
74
|
+
"Concrete model override for tasks created by this schedule. Set to null to clear. The model must run on the assignee's harness (an Anthropic model on a Claude agent, an OpenAI model on a Codex agent); a mismatch is rejected.",
|
|
75
|
+
),
|
|
73
76
|
modelTier: ModelTierSchema.nullable()
|
|
74
77
|
.optional()
|
|
75
78
|
.describe("Portable model tier for tasks created by this schedule. Set to null to clear."),
|
|
79
|
+
allowCustomModel: z
|
|
80
|
+
.boolean()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe(
|
|
83
|
+
"Accept a `model` the model catalog does not list. Without it an unknown model id is rejected. Only for ids the catalog cannot know yet.",
|
|
84
|
+
),
|
|
76
85
|
});
|
|
77
86
|
|
|
78
87
|
const scheduleDataShape = {
|
|
@@ -139,6 +148,7 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
|
|
|
139
148
|
enabled,
|
|
140
149
|
model,
|
|
141
150
|
modelTier,
|
|
151
|
+
allowCustomModel,
|
|
142
152
|
},
|
|
143
153
|
requestInfo,
|
|
144
154
|
_meta,
|
|
@@ -246,12 +256,32 @@ export const registerUpdateScheduleTool = (server: McpServer) => {
|
|
|
246
256
|
if (targetAgentId !== undefined) updateData.targetAgentId = targetAgentId;
|
|
247
257
|
if (timezone !== undefined) updateData.timezone = timezone;
|
|
248
258
|
if (enabled !== undefined) updateData.enabled = enabled;
|
|
259
|
+
// A move to another agent re-judges the stored model against the new harness.
|
|
260
|
+
const agentChanged =
|
|
261
|
+
targetAgentId !== undefined && targetAgentId !== schedule.targetAgentId;
|
|
249
262
|
if (model !== undefined || modelTier !== undefined) {
|
|
250
263
|
const normalizedModel = splitLegacyModelAlias({ model, modelTier });
|
|
264
|
+
// A model the schedule already stores is not re-judged, so an unrelated edit still saves.
|
|
265
|
+
if (normalizedModel.model !== schedule.model || agentChanged) {
|
|
266
|
+
const modelError = await explicitModelErrorForAgent({
|
|
267
|
+
model: normalizedModel.model,
|
|
268
|
+
allowCustomModel: allowCustomModel || normalizedModel.model === schedule.model,
|
|
269
|
+
agentId: targetAgentId ?? schedule.targetAgentId,
|
|
270
|
+
});
|
|
271
|
+
if (modelError) return toolErr(modelError);
|
|
272
|
+
}
|
|
251
273
|
if (model !== undefined) updateData.model = normalizedModel.model ?? null;
|
|
252
274
|
if (modelTier !== undefined || normalizedModel.modelTier) {
|
|
253
275
|
updateData.modelTier = normalizedModel.modelTier ?? null;
|
|
254
276
|
}
|
|
277
|
+
} else if (agentChanged && schedule.model) {
|
|
278
|
+
// Harness only: the stored id already passed the catalog check when it was written.
|
|
279
|
+
const modelError = await explicitModelErrorForAgent({
|
|
280
|
+
model: schedule.model,
|
|
281
|
+
allowCustomModel: true,
|
|
282
|
+
agentId: targetAgentId ?? schedule.targetAgentId,
|
|
283
|
+
});
|
|
284
|
+
if (modelError) return toolErr(modelError);
|
|
255
285
|
}
|
|
256
286
|
|
|
257
287
|
// Recalculate nextRunAt based on schedule type
|