@desplega.ai/agent-swarm 1.158.0 → 1.159.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/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
- package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
- package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
- package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
- package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
- package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
- package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
- package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
- package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
- package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
- package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
- package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
- package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
- package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
- package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
- package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
- package/dist/cli-0xqde53b.js +176 -0
- package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
- package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
- package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
- package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
- package/dist/cli-3gvrr8vj.js +1422 -0
- package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
- package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
- package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
- package/dist/cli-503ya656.js +455 -0
- package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
- package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
- package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
- package/dist/cli-7cxw8b60.js +14910 -0
- package/dist/cli-7kyk9jr3.js +22 -0
- package/dist/cli-7zttzetj.js +324 -0
- package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
- package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
- package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
- package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
- package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
- package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
- package/dist/cli-ca2w7zv7.js +115 -0
- package/dist/cli-cpvsaqsw.js +333 -0
- package/dist/cli-cygyqj3b.js +754 -0
- package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
- package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
- package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
- package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
- package/dist/cli-ejpygnqz.js +3815 -0
- package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
- package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
- package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
- package/dist/cli-gzyh0j0g.js +8455 -0
- package/dist/cli-hgqfttfc.js +183 -0
- package/dist/cli-hj65wsts.js +17755 -0
- package/dist/cli-j9z8mfx3.js +476 -0
- package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
- package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
- package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
- package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
- package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
- package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
- package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
- package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
- package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
- package/dist/cli-qvnnx4ct.js +1051 -0
- package/dist/cli-rb9m6rpe.js +4980 -0
- package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
- package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
- package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
- package/dist/cli-rykfmgwg.js +102 -0
- package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
- package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
- package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
- package/dist/cli-wd9hz40d.js +69 -0
- package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
- package/dist/cli-xgpf00sk.js +2149 -0
- package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
- package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
- package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
- package/dist/cli.js +38 -29
- package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
- package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
- package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
- package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
- package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
- package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
- package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
- package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
- package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
- package/dist/execute-71ysgv35.js +965 -0
- package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
- package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
- package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
- package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
- package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
- package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
- package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
- package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
- package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
- package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
- package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
- package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
- package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
- package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
- package/dist/llama-cpp-classify-t2tccams.js +335 -0
- package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
- package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
- package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
- package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
- package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
- package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
- package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
- package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
- package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
- package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.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-n0me28wt.js} +29 -17
- package/dist/pricing-refresh-0v77385v.js +39 -0
- package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
- package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
- package/dist/runtime-ktjysacx.js +24 -0
- package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
- package/dist/typesafe-system-one-96960jxp.js +27 -0
- package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
- package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
- package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
- package/openapi.json +863 -14
- package/package.json +9 -8
- package/src/be/db/agents.ts +20 -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 +89 -165
- package/src/be/harness-model-support.ts +182 -0
- package/src/be/human-free-drain.ts +67 -0
- 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/model-catalog-store.ts +644 -0
- package/src/be/model-tier-keys.ts +31 -0
- package/src/be/model-tier-resolution.ts +489 -0
- package/src/be/model-validation.ts +112 -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-scripts/catalog/task-failure-audit.ts +61 -9
- package/src/be/swarm-config-guard.ts +6 -0
- package/src/cli.tsx +10 -0
- package/src/commands/claude-managed-setup.ts +3 -1
- package/src/commands/runner.ts +76 -2
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +64 -15
- package/src/heartbeat/templates.ts +2 -2
- package/src/http/agents.ts +49 -1
- package/src/http/index.ts +8 -0
- package/src/http/models-catalog.ts +357 -8
- package/src/http/poll.ts +45 -2
- package/src/http/schedules.ts +36 -6
- package/src/http/session-data.ts +2 -5
- package/src/http/stats.ts +6 -8
- package/src/http/tasks.ts +26 -1
- package/src/http/usage-cache.ts +56 -15
- package/src/http/workflows.ts +35 -8
- package/src/prompts/session-templates.ts +1 -0
- package/src/providers/claude-managed-models.ts +47 -161
- package/src/providers/codex-adapter.ts +4 -4
- package/src/providers/codex-models.ts +65 -198
- package/src/providers/reasoning-effort.ts +44 -140
- package/src/rbac/legacy-policy.ts +25 -0
- package/src/rbac/permissions.ts +10 -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/message-text.ts +56 -2
- package/src/slack/thread-buffer.ts +10 -2
- package/src/tests/agents-list-model-display.test.ts +4 -6
- package/src/tests/base-prompt.test.ts +3 -0
- package/src/tests/claude-adapter.test.ts +10 -0
- package/src/tests/claude-managed-adapter.test.ts +12 -18
- package/src/tests/codex-adapter.test.ts +2 -1
- package/src/tests/codex-gpt6-release.test.ts +66 -5
- package/src/tests/context-window.test.ts +7 -5
- package/src/tests/db-extraction-contract.test.ts +1 -0
- package/src/tests/db-task-read-contract.test.ts +5 -1
- package/src/tests/db-task-write-contract.test.ts +4 -1
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/explicit-model-validation.test.ts +332 -0
- package/src/tests/harness-model-support.test.ts +374 -0
- 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/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/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-tier-resolution.test.ts +519 -0
- package/src/tests/models-catalog.test.ts +1 -1
- package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
- package/src/tests/package-publish.test.ts +137 -36
- package/src/tests/pricing-routes.test.ts +2 -2
- package/src/tests/rbac-engine.test.ts +42 -2
- package/src/tests/reasoning-effort.test.ts +109 -0
- package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
- package/src/tests/runtime-model-catalog.test.ts +86 -0
- package/src/tests/script-connections.test.ts +5 -0
- package/src/tests/seed-scripts.test.ts +89 -0
- package/src/tests/session-costs.test.ts +14 -11
- package/src/tests/slack-message-text.test.ts +101 -0
- package/src/tests/slack-thread-buffer-context.test.ts +93 -0
- package/src/tests/task-cascade-fail.test.ts +148 -2
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/usage-cache.test.ts +166 -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/tools/model-catalog/index.ts +137 -0
- package/src/tools/schedules/create-schedule.ts +16 -1
- package/src/tools/schedules/patch-schedule.ts +17 -0
- package/src/tools/schedules/update-schedule.ts +17 -0
- package/src/tools/send-task.ts +14 -0
- package/src/tools/task-action.ts +12 -0
- package/src/tools/tool-config.ts +3 -1
- package/src/tools/workflows/create-workflow.ts +9 -2
- 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 +9 -2
- package/src/types.ts +30 -1
- package/src/utils/context-window.ts +47 -53
- package/src/utils/harness-cli-version.ts +115 -0
- package/src/utils/harness-model-error.ts +33 -0
- package/src/utils/runtime-model-catalog.ts +137 -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 +9 -0
- 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/readiness.ts +86 -0
- package/src/workflows/recovery.ts +12 -12
- package/src/workflows/resume.ts +23 -9
- 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 +81 -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/claude-sdk-session-t442qz6f.js +0 -39108
- package/dist/cli-0xjkj1j7.js +0 -61
- 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/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
- package/dist/{index-f24115ge.js → index-2wsdsnmm.js} +3 -3
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* This module is safe for both API and worker code — it has NO database imports.
|
|
5
5
|
*
|
|
6
6
|
* Phase 4 + Phase 9 of the cost-tracking plan:
|
|
7
|
-
* -
|
|
7
|
+
* - Model-catalog phase 4: windows come from the model catalog (no
|
|
8
|
+
* per-model table). A new model gets the right window as soon as it
|
|
9
|
+
* lands in `model_catalog`.
|
|
10
|
+
* - `getContextWindowSize` resolves shortnames, family-versioned ids
|
|
8
11
|
* (`claude-sonnet-4-6`), AND dated full ids (`claude-sonnet-4-6-20251004`)
|
|
9
12
|
* by stripping the trailing date suffix. Previously the dated form fell
|
|
10
13
|
* to the 200k default — wildly wrong for sonnet/opus 4.x.
|
|
@@ -17,6 +20,9 @@
|
|
|
17
20
|
* deprecated; new code should use `computeContextUsedUnified`.
|
|
18
21
|
*/
|
|
19
22
|
|
|
23
|
+
import { buildClaudeShortnameMap } from "@desplega/model-catalog";
|
|
24
|
+
import { runtimeCatalogModel, runtimeCatalogSection } from "./runtime-model-catalog";
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* Phase 9: stamp this onto every `context_usage` event the adapter emits.
|
|
22
28
|
* Callers that compute their own number for legacy reasons (e.g. pi-mono
|
|
@@ -25,43 +31,6 @@
|
|
|
25
31
|
*/
|
|
26
32
|
export const CONTEXT_FORMULA = "input-cache-output" as const;
|
|
27
33
|
|
|
28
|
-
const CONTEXT_WINDOW_DEFAULTS: Record<string, number> = {
|
|
29
|
-
// Anthropic Fable / Mythos tier
|
|
30
|
-
"claude-fable-5-1": 1_000_000,
|
|
31
|
-
"claude-mythos-5-1": 1_000_000,
|
|
32
|
-
"claude-opus-5-5": 1_000_000,
|
|
33
|
-
"claude-opus-5": 1_000_000,
|
|
34
|
-
"claude-fable-5": 1_000_000,
|
|
35
|
-
"claude-mythos-5": 1_000_000,
|
|
36
|
-
"claude-sonnet-5-5": 1_000_000,
|
|
37
|
-
"claude-sonnet-5": 1_000_000,
|
|
38
|
-
// Anthropic 4.x family
|
|
39
|
-
"claude-opus-4-8": 1_000_000,
|
|
40
|
-
"claude-opus-4-7": 1_000_000,
|
|
41
|
-
"claude-opus-4-6": 1_000_000,
|
|
42
|
-
"claude-opus-4-5": 1_000_000,
|
|
43
|
-
"claude-opus-4-1": 200_000,
|
|
44
|
-
"claude-opus-4-0": 200_000,
|
|
45
|
-
"claude-sonnet-4-6": 1_000_000,
|
|
46
|
-
"claude-sonnet-4-5": 1_000_000,
|
|
47
|
-
"claude-sonnet-4-0": 200_000,
|
|
48
|
-
"claude-haiku-4-5": 200_000,
|
|
49
|
-
// Anthropic 3.x family (legacy)
|
|
50
|
-
"claude-3-7-sonnet": 200_000,
|
|
51
|
-
"claude-3-5-sonnet": 200_000,
|
|
52
|
-
"claude-3-5-haiku": 200_000,
|
|
53
|
-
"claude-3-opus": 200_000,
|
|
54
|
-
"claude-3-sonnet": 200_000,
|
|
55
|
-
"claude-3-haiku": 200_000,
|
|
56
|
-
// Shortnames used by the local-CLI adapter and pi-mono OpenRouter mirror.
|
|
57
|
-
fable: 1_000_000,
|
|
58
|
-
mythos: 1_000_000,
|
|
59
|
-
opus: 1_000_000,
|
|
60
|
-
sonnet: 1_000_000,
|
|
61
|
-
haiku: 200_000,
|
|
62
|
-
default: 200_000,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
34
|
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
66
35
|
|
|
67
36
|
/**
|
|
@@ -69,30 +38,55 @@ const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
|
69
38
|
* resolve to the same window as the family-versioned id.
|
|
70
39
|
*
|
|
71
40
|
* `claude-sonnet-4-6-20251004` → `claude-sonnet-4-6`
|
|
72
|
-
* `claude-haiku-4-5-20251001` → `claude-haiku-4-5`
|
|
73
|
-
*
|
|
74
|
-
* Anthropic's dated full ids are always `${family}-${major}-${minor}-${YYYYMMDD}`,
|
|
75
|
-
* so an 8-digit trailing date is a reliable signal.
|
|
76
41
|
*/
|
|
77
42
|
function stripAnthropicDateSuffix(model: string): string {
|
|
78
43
|
return model.replace(/-(\d{8})$/, "");
|
|
79
44
|
}
|
|
80
45
|
|
|
46
|
+
let shortnameMap: { section: Record<string, unknown>; map: Record<string, string> } | null = null;
|
|
47
|
+
|
|
48
|
+
/** Claude CLI shortnames (`opus`, `sonnet`, ...) → newest catalog id. */
|
|
49
|
+
function resolveClaudeShortname(model: string): string | undefined {
|
|
50
|
+
const section = runtimeCatalogSection("anthropic");
|
|
51
|
+
if (!shortnameMap || shortnameMap.section !== section) {
|
|
52
|
+
shortnameMap = { section, map: buildClaudeShortnameMap(section) };
|
|
53
|
+
}
|
|
54
|
+
return shortnameMap.map[model.trim().toLowerCase()];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function catalogContext(provider: string, model: string): number | undefined {
|
|
58
|
+
const context = runtimeCatalogModel(provider, model)?.limit?.context;
|
|
59
|
+
return typeof context === "number" && context > 0 ? context : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Context window (tokens) for a model id, read from the model catalog
|
|
64
|
+
* (`src/utils/runtime-model-catalog.ts`: live `model_catalog` + overlay,
|
|
65
|
+
* vendored models.dev snapshot offline). Accepts Anthropic ids (dated or
|
|
66
|
+
* not), Claude CLI shortnames, OpenAI/Codex ids and `provider/model` ids.
|
|
67
|
+
* Unknown models get a conservative 200k so percent math stays finite.
|
|
68
|
+
*/
|
|
81
69
|
export function getContextWindowSize(model: string): number {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
if (!model) return DEFAULT_CONTEXT_WINDOW;
|
|
71
|
+
const slash = model.indexOf("/");
|
|
72
|
+
if (slash > 0) {
|
|
73
|
+
const scoped = catalogContext(model.slice(0, slash), model.slice(slash + 1));
|
|
74
|
+
if (scoped) return scoped;
|
|
75
|
+
}
|
|
76
|
+
for (const provider of ["anthropic", "openai"]) {
|
|
77
|
+
const exact = catalogContext(provider, model);
|
|
78
|
+
if (exact) return exact;
|
|
85
79
|
}
|
|
86
|
-
// Dated full id → strip suffix and retry.
|
|
87
80
|
const stripped = stripAnthropicDateSuffix(model);
|
|
88
|
-
if (stripped !== model
|
|
89
|
-
|
|
81
|
+
if (stripped !== model) {
|
|
82
|
+
const dated = catalogContext("anthropic", stripped);
|
|
83
|
+
if (dated) return dated;
|
|
84
|
+
}
|
|
85
|
+
const shortname = resolveClaudeShortname(model);
|
|
86
|
+
if (shortname) {
|
|
87
|
+
const resolved = catalogContext("anthropic", shortname);
|
|
88
|
+
if (resolved) return resolved;
|
|
90
89
|
}
|
|
91
|
-
// OpenAI / GPT family — most reasoning models have 200k+; we keep this
|
|
92
|
-
// conservative and let callers override via models.dev rates if they want.
|
|
93
|
-
// Specific gpt-5.x context windows are >1M but the local-CLI adapter
|
|
94
|
-
// generally doesn't surface those; the API recompute path uses the rate
|
|
95
|
-
// table, not the window. The 200k default keeps the math safe.
|
|
96
90
|
return DEFAULT_CONTEXT_WINDOW;
|
|
97
91
|
}
|
|
98
92
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worker's harness CLI version (`claude --version` / `codex --version`),
|
|
3
|
+
* reported on register and used to key harness_model_support rows. Worker-safe
|
|
4
|
+
* (no DB). Probed once per process; null when the CLI is missing or silent.
|
|
5
|
+
*/
|
|
6
|
+
import { isUnknownModelError } from "./harness-model-error";
|
|
7
|
+
import { scrubSecrets } from "./secret-scrubber";
|
|
8
|
+
|
|
9
|
+
const CLI_BINARY: Record<string, string> = { claude: "claude", codex: "codex" };
|
|
10
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
11
|
+
const VERSION_RE = /\d+\.\d+\.\d+(?:[-+][\w.]+)?/;
|
|
12
|
+
|
|
13
|
+
const probed = new Map<string, Promise<string | null>>();
|
|
14
|
+
|
|
15
|
+
export function parseCliVersion(output: string): string | null {
|
|
16
|
+
return VERSION_RE.exec(output)?.[0] ?? null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function probeHarnessCliVersion(harness: string): Promise<string | null> {
|
|
20
|
+
const binary = CLI_BINARY[harness];
|
|
21
|
+
if (!binary) return Promise.resolve(null);
|
|
22
|
+
let pending = probed.get(harness);
|
|
23
|
+
if (!pending) {
|
|
24
|
+
pending = (async () => {
|
|
25
|
+
try {
|
|
26
|
+
const proc = Bun.spawn([binary, "--version"], {
|
|
27
|
+
stdin: "ignore",
|
|
28
|
+
stdout: "pipe",
|
|
29
|
+
stderr: "pipe",
|
|
30
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
31
|
+
killSignal: "SIGKILL",
|
|
32
|
+
});
|
|
33
|
+
const [stdout, exitCode] = await Promise.all([
|
|
34
|
+
new Response(proc.stdout).text(),
|
|
35
|
+
proc.exited,
|
|
36
|
+
]);
|
|
37
|
+
return exitCode === 0 ? parseCliVersion(stdout) : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
probed.set(harness, pending);
|
|
43
|
+
}
|
|
44
|
+
return pending;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Last probed version, without waiting; null until the probe finished. */
|
|
48
|
+
export async function cachedHarnessCliVersion(harness: string): Promise<string | null> {
|
|
49
|
+
return (await probed.get(harness)) ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const reported = new Set<string>();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Record a model's run outcome on this worker's CLI. `ok` is sent once per
|
|
56
|
+
* model per process; `unsupported` whenever the CLI rejects the id. Other
|
|
57
|
+
* failures say nothing about support and are not sent. Never throws.
|
|
58
|
+
*/
|
|
59
|
+
export async function reportHarnessModelOutcome(opts: {
|
|
60
|
+
apiUrl: string;
|
|
61
|
+
apiKey?: string;
|
|
62
|
+
agentId: string;
|
|
63
|
+
harness: string;
|
|
64
|
+
model: string | undefined;
|
|
65
|
+
exitCode: number;
|
|
66
|
+
failureReason?: string;
|
|
67
|
+
fetchImpl?: typeof fetch;
|
|
68
|
+
}): Promise<void> {
|
|
69
|
+
if (!opts.model || !CLI_BINARY[opts.harness]) return;
|
|
70
|
+
const cliVersion = await cachedHarnessCliVersion(opts.harness);
|
|
71
|
+
if (!cliVersion) return;
|
|
72
|
+
const status =
|
|
73
|
+
opts.exitCode === 0
|
|
74
|
+
? "ok"
|
|
75
|
+
: isUnknownModelError(opts.failureReason, opts.model)
|
|
76
|
+
? "unsupported"
|
|
77
|
+
: null;
|
|
78
|
+
if (!status) return;
|
|
79
|
+
const key = `${opts.harness}|${cliVersion}|${opts.model}`;
|
|
80
|
+
if (status === "ok" && reported.has(key)) return;
|
|
81
|
+
try {
|
|
82
|
+
const headers: Record<string, string> = {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
"X-Agent-ID": opts.agentId,
|
|
85
|
+
};
|
|
86
|
+
if (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;
|
|
87
|
+
const res = await (opts.fetchImpl ?? fetch)(
|
|
88
|
+
`${opts.apiUrl}/api/models-catalog/harness-support`,
|
|
89
|
+
{
|
|
90
|
+
method: "PUT",
|
|
91
|
+
headers,
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
harness: opts.harness,
|
|
94
|
+
cliVersion,
|
|
95
|
+
modelId: opts.model,
|
|
96
|
+
status,
|
|
97
|
+
...(status === "unsupported" && opts.failureReason
|
|
98
|
+
? { error: scrubSecrets(opts.failureReason).slice(0, 2000) }
|
|
99
|
+
: {}),
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
if (res.ok && status === "ok") reported.add(key);
|
|
104
|
+
} catch {
|
|
105
|
+
// Best effort: the next run reports again.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function resetHarnessCliVersionForTests(seed?: Record<string, string | null>): void {
|
|
110
|
+
probed.clear();
|
|
111
|
+
reported.clear();
|
|
112
|
+
for (const [harness, version] of Object.entries(seed ?? {})) {
|
|
113
|
+
probed.set(harness, Promise.resolve(version));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognizes a harness CLI rejecting a model id. Worker-safe (no DB).
|
|
3
|
+
*
|
|
4
|
+
* Claude Code: "There's an issue with the selected model (x). It may not exist
|
|
5
|
+
* or you may not have access to it", or the API's `not_found_error` "model: x".
|
|
6
|
+
* Codex: "The model `x` does not exist or you do not have access to it",
|
|
7
|
+
* "model is not supported", "unknown model".
|
|
8
|
+
*
|
|
9
|
+
* A model-rejection phrase can also appear in an unrelated failure ("invalid model output"),
|
|
10
|
+
* and a false positive marks the model unsupported for the whole (harness, CLI version). So
|
|
11
|
+
* when `modelId` is given, the rejection line must name that model.
|
|
12
|
+
*/
|
|
13
|
+
const UNKNOWN_MODEL_PATTERNS: RegExp[] = [
|
|
14
|
+
/issue with the selected model/i,
|
|
15
|
+
/model[`'" ]*[\w.:/-]*[`'" ]*(?:does not exist|not found)/i,
|
|
16
|
+
/not_found_error[^\n]*model/i,
|
|
17
|
+
/model[^\n]{0,80}is not supported/i,
|
|
18
|
+
/unsupported model/i,
|
|
19
|
+
/unknown model/i,
|
|
20
|
+
/invalid model/i,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export function isUnknownModelError(
|
|
24
|
+
text: string | null | undefined,
|
|
25
|
+
modelId?: string | null,
|
|
26
|
+
): boolean {
|
|
27
|
+
if (!text) return false;
|
|
28
|
+
const id = modelId?.trim().toLowerCase();
|
|
29
|
+
return text.split("\n").some((line) => {
|
|
30
|
+
if (!UNKNOWN_MODEL_PATTERNS.some((re) => re.test(line))) return false;
|
|
31
|
+
return !id || line.toLowerCase().includes(id);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide model catalog for code that runs on BOTH sides of the
|
|
3
|
+
* API/worker boundary (provider adapters, context-window math, reasoning
|
|
4
|
+
* gating). Model-catalog phase 4: this replaces the hand-maintained per-model
|
|
5
|
+
* tables (codex allowlist/windows/pricing, claude-managed allowlist/pricing,
|
|
6
|
+
* per-model context windows, the slim reasoning snapshot).
|
|
7
|
+
*
|
|
8
|
+
* Sources, highest first:
|
|
9
|
+
* 1. the live catalog: the API server's `model_catalog` + overlay rows.
|
|
10
|
+
* Workers pull it over HTTP (`GET /api/models-catalog`, see
|
|
11
|
+
* `refreshRuntimeModelCatalog`); the API process sets it directly after
|
|
12
|
+
* every catalog reload.
|
|
13
|
+
* 2. the vendored models.dev snapshot (`src/be/modelsdev-cache.json`) —
|
|
14
|
+
* offline fallback, and the only source for ids the live catalog has
|
|
15
|
+
* dropped (legacy models still named by old tasks and pricing rows).
|
|
16
|
+
*
|
|
17
|
+
* NO database imports (worker-safe). Every lookup is synchronous; the HTTP
|
|
18
|
+
* refresh is TTL-cached and never throws, so hot paths stay cheap.
|
|
19
|
+
*/
|
|
20
|
+
import modelsDevSnapshot from "../be/modelsdev-cache.json";
|
|
21
|
+
|
|
22
|
+
export interface RuntimeCatalogModel {
|
|
23
|
+
id?: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
cost?: { input?: number; output?: number; cache_read?: number; cache_write?: number };
|
|
26
|
+
limit?: { context?: number };
|
|
27
|
+
release_date?: string;
|
|
28
|
+
status?: string;
|
|
29
|
+
reasoning?: boolean;
|
|
30
|
+
reasoning_options?: { type?: string; values?: string[] }[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RuntimeCatalogProvider {
|
|
34
|
+
id?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
models?: Record<string, RuntimeCatalogModel>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type RuntimeCatalog = Record<string, RuntimeCatalogProvider | undefined>;
|
|
40
|
+
|
|
41
|
+
const SNAPSHOT = modelsDevSnapshot as unknown as RuntimeCatalog;
|
|
42
|
+
|
|
43
|
+
/** Default freshness window for the worker-side HTTP pull. */
|
|
44
|
+
export const RUNTIME_CATALOG_TTL_MS = 10 * 60_000;
|
|
45
|
+
const FETCH_TIMEOUT_MS = 5_000;
|
|
46
|
+
|
|
47
|
+
let live: RuntimeCatalog | null = null;
|
|
48
|
+
let liveAt = 0;
|
|
49
|
+
let liveSource: "live" | "snapshot" | null = null;
|
|
50
|
+
const mergedSections = new Map<string, Record<string, RuntimeCatalogModel>>();
|
|
51
|
+
let inFlight: Promise<boolean> | null = null;
|
|
52
|
+
|
|
53
|
+
/** Replace the live layer (API process after a reload; worker after a pull). */
|
|
54
|
+
export function setRuntimeModelCatalog(
|
|
55
|
+
providers: RuntimeCatalog,
|
|
56
|
+
at: number = Date.now(),
|
|
57
|
+
source: "live" | "snapshot" = "live",
|
|
58
|
+
): void {
|
|
59
|
+
live = providers;
|
|
60
|
+
liveAt = at;
|
|
61
|
+
liveSource = source;
|
|
62
|
+
mergedSections.clear();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resetRuntimeModelCatalogForTests(): void {
|
|
66
|
+
live = null;
|
|
67
|
+
liveAt = 0;
|
|
68
|
+
liveSource = null;
|
|
69
|
+
mergedSections.clear();
|
|
70
|
+
inFlight = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Where the current live layer came from; null = snapshot only. */
|
|
74
|
+
export function runtimeModelCatalogSource(): "live" | "snapshot" | null {
|
|
75
|
+
return liveSource;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One provider section: snapshot models overlaid by the live catalog (live wins per id). */
|
|
79
|
+
export function runtimeCatalogSection(provider: string): Record<string, RuntimeCatalogModel> {
|
|
80
|
+
const cached = mergedSections.get(provider);
|
|
81
|
+
if (cached) return cached;
|
|
82
|
+
const merged: Record<string, RuntimeCatalogModel> = {
|
|
83
|
+
...(SNAPSHOT[provider]?.models ?? {}),
|
|
84
|
+
...(live?.[provider]?.models ?? {}),
|
|
85
|
+
};
|
|
86
|
+
mergedSections.set(provider, merged);
|
|
87
|
+
return merged;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function runtimeCatalogModel(
|
|
91
|
+
provider: string,
|
|
92
|
+
modelId: string,
|
|
93
|
+
): RuntimeCatalogModel | undefined {
|
|
94
|
+
return runtimeCatalogSection(provider)[modelId];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Pull the live catalog from the API server. TTL-cached (`maxAgeMs`),
|
|
99
|
+
* single-flight, never throws: returns false and keeps the previous layer
|
|
100
|
+
* when the server is unreachable or answers non-2xx.
|
|
101
|
+
*/
|
|
102
|
+
export async function refreshRuntimeModelCatalog(opts: {
|
|
103
|
+
apiUrl: string;
|
|
104
|
+
apiKey?: string;
|
|
105
|
+
agentId?: string;
|
|
106
|
+
maxAgeMs?: number;
|
|
107
|
+
fetchImpl?: typeof fetch;
|
|
108
|
+
}): Promise<boolean> {
|
|
109
|
+
const maxAge = opts.maxAgeMs ?? RUNTIME_CATALOG_TTL_MS;
|
|
110
|
+
if (live && Date.now() - liveAt < maxAge) return true;
|
|
111
|
+
if (inFlight) return inFlight;
|
|
112
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
113
|
+
inFlight = (async () => {
|
|
114
|
+
try {
|
|
115
|
+
const headers: Record<string, string> = {};
|
|
116
|
+
if (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;
|
|
117
|
+
if (opts.agentId) headers["X-Agent-ID"] = opts.agentId;
|
|
118
|
+
const res = await doFetch(`${opts.apiUrl}/api/models-catalog`, {
|
|
119
|
+
headers,
|
|
120
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
121
|
+
});
|
|
122
|
+
if (!res.ok) return false;
|
|
123
|
+
const body = (await res.json()) as {
|
|
124
|
+
source?: "live" | "snapshot";
|
|
125
|
+
providers?: RuntimeCatalog;
|
|
126
|
+
};
|
|
127
|
+
if (!body || typeof body.providers !== "object" || body.providers === null) return false;
|
|
128
|
+
setRuntimeModelCatalog(body.providers, Date.now(), body.source ?? "live");
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
} finally {
|
|
133
|
+
inFlight = null;
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
return inFlight;
|
|
137
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WorkflowDefinition } from "../types";
|
|
2
|
+
import type { ApprovalOutcome, ResolvedApproval } from "./executors/base";
|
|
3
|
+
import type { ExecutorRegistry } from "./executors/registry";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Step output and output port for a step whose approval request resolved.
|
|
7
|
+
*
|
|
8
|
+
* Both resume paths (the live `approval.resolved` event and the recovery sweep)
|
|
9
|
+
* go through here so they cannot disagree. A `human-in-the-loop` step gets its
|
|
10
|
+
* own output on the port named after the status. An executor that parked on the
|
|
11
|
+
* same approval machinery (`system-one-decision` with `humanReview`) shapes its
|
|
12
|
+
* own output from what it stored before it parked.
|
|
13
|
+
*/
|
|
14
|
+
export function shapeApprovalResolution(
|
|
15
|
+
registry: ExecutorRegistry,
|
|
16
|
+
definition: Pick<WorkflowDefinition, "nodes">,
|
|
17
|
+
nodeId: string,
|
|
18
|
+
parkedOutput: unknown,
|
|
19
|
+
approval: ResolvedApproval,
|
|
20
|
+
): ApprovalOutcome {
|
|
21
|
+
const node = definition.nodes.find((candidate) => candidate.id === nodeId);
|
|
22
|
+
if (node && registry.has(node.type)) {
|
|
23
|
+
const shaped = registry.get(node.type).resolveApproval(parkedOutput, approval);
|
|
24
|
+
if (shaped) return shaped;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
output: {
|
|
28
|
+
requestId: approval.requestId,
|
|
29
|
+
status: approval.status,
|
|
30
|
+
responses: approval.responses,
|
|
31
|
+
},
|
|
32
|
+
nextPort:
|
|
33
|
+
approval.status === "timeout"
|
|
34
|
+
? "timeout"
|
|
35
|
+
: approval.status === "rejected"
|
|
36
|
+
? "rejected"
|
|
37
|
+
: "approved",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -6,6 +6,10 @@ import type {
|
|
|
6
6
|
WorkflowPatch,
|
|
7
7
|
} from "../types";
|
|
8
8
|
import type { ExecutorRegistry } from "./executors/registry";
|
|
9
|
+
import {
|
|
10
|
+
systemOneRetryViolations,
|
|
11
|
+
systemOneStaticShapeViolations,
|
|
12
|
+
} from "./executors/system-one-decision";
|
|
9
13
|
|
|
10
14
|
/** Extract all target node IDs from a node's `next` field */
|
|
11
15
|
export function getNextTargets(next: string | string[] | Record<string, string>): string[] {
|
|
@@ -206,6 +210,9 @@ export function validateDefinition(
|
|
|
206
210
|
`Node "${node.id}" human-in-the-loop config.questions must be an array or one exact {{interpolation}} token`,
|
|
207
211
|
);
|
|
208
212
|
}
|
|
213
|
+
// A system-one-decision node's question ids and return types are static, and it never carries
|
|
214
|
+
// an engine retry policy (it retries transient transport errors itself).
|
|
215
|
+
errors.push(...systemOneStaticShapeViolations(node), ...systemOneRetryViolations(node));
|
|
209
216
|
if (node.type === "foreach") {
|
|
210
217
|
validateForeachNode(node, errors);
|
|
211
218
|
// A legacy `#` id may stay editable as a NORMAL node, but never as a foreach
|
package/src/workflows/engine.ts
CHANGED
|
@@ -28,10 +28,16 @@ import { shouldSkipCooldown } from "./cooldown";
|
|
|
28
28
|
import { findEntryNodes, getNextTargets, getSuccessors, resolveValidationPort } from "./definition";
|
|
29
29
|
import type { AsyncExecutorResult } from "./executors/base";
|
|
30
30
|
import type { ExecutorRegistry } from "./executors/registry";
|
|
31
|
+
import {
|
|
32
|
+
SYSTEM_ONE_DECISION_NODE_TYPE,
|
|
33
|
+
systemOneRetryViolations,
|
|
34
|
+
systemOneUnresolvedError,
|
|
35
|
+
} from "./executors/system-one-decision";
|
|
31
36
|
import { FOREACH_TERMINAL_STEP_STATUSES, resolveForeachParent } from "./foreach-join";
|
|
32
37
|
import { getSecretInputKeys, redactSecretsForStorage, resolveInputs } from "./input";
|
|
33
38
|
import { validateJsonSchema } from "./json-schema-validator";
|
|
34
39
|
import { getMaxWorkflowStepsPerRun } from "./limits";
|
|
40
|
+
import { findWorkflowReadinessProblems, formatReadinessRunError } from "./readiness";
|
|
35
41
|
import { deepInterpolate } from "./template";
|
|
36
42
|
import { runStepValidation, type ValidationRunResult } from "./validation";
|
|
37
43
|
|
|
@@ -149,6 +155,18 @@ export async function startWorkflowExecution(
|
|
|
149
155
|
triggerType: options.triggerType ?? "manual",
|
|
150
156
|
});
|
|
151
157
|
|
|
158
|
+
// An executor that cannot run (for example a system-one-decision node with no API key) fails
|
|
159
|
+
// the run here, before any node has side effects, instead of partway through.
|
|
160
|
+
const notReady = await findWorkflowReadinessProblems(workflow.definition, registry);
|
|
161
|
+
if (notReady.length > 0) {
|
|
162
|
+
await updateWorkflowRun(runId, {
|
|
163
|
+
status: "failed",
|
|
164
|
+
error: formatReadinessRunError(notReady),
|
|
165
|
+
finishedAt: new Date().toISOString(),
|
|
166
|
+
});
|
|
167
|
+
return runId;
|
|
168
|
+
}
|
|
169
|
+
|
|
152
170
|
// Resolve inputs and merge into initial context
|
|
153
171
|
const ctx: Record<string, unknown> = { trigger: triggerData };
|
|
154
172
|
if (options.requestedByUserId) {
|
|
@@ -744,6 +762,15 @@ async function runClaimedStep(
|
|
|
744
762
|
// 3. Get executor
|
|
745
763
|
const executor = registry.get(node.type);
|
|
746
764
|
|
|
765
|
+
// 3a. A system-one-decision node retries transient transport errors itself (config.maxRetries).
|
|
766
|
+
// Engine retries are not status-aware, so a policy here would re-send rejected
|
|
767
|
+
// requests and multiply paid attempts. Fail before any request is built.
|
|
768
|
+
const systemOneRetryProblems = systemOneRetryViolations(node);
|
|
769
|
+
if (systemOneRetryProblems.length > 0) {
|
|
770
|
+
await checkpointStepFailure(runId, stepId, systemOneRetryProblems.join("; "), 0);
|
|
771
|
+
return { outcome: "failed", successors: [] };
|
|
772
|
+
}
|
|
773
|
+
|
|
747
774
|
// 3b. Build local interpolation context from explicit inputs mapping
|
|
748
775
|
const interpolationCtx = buildNodeInterpolationCtx(node, ctx);
|
|
749
776
|
|
|
@@ -765,6 +792,7 @@ async function runClaimedStep(
|
|
|
765
792
|
value: interpolatedValue,
|
|
766
793
|
unresolved,
|
|
767
794
|
scriptBodyUnresolved,
|
|
795
|
+
strictUnresolved,
|
|
768
796
|
} = interpolateNodeConfig(node, interpolationCtx);
|
|
769
797
|
const interpolatedConfig = interpolatedValue as Record<string, unknown>;
|
|
770
798
|
const executionCtx: Record<string, unknown> = { ...ctx, ...interpolationCtx };
|
|
@@ -775,6 +803,17 @@ async function runClaimedStep(
|
|
|
775
803
|
return { outcome: "failed", successors: [] };
|
|
776
804
|
}
|
|
777
805
|
|
|
806
|
+
// A paid, non-idempotent call must not go out with a blanked field.
|
|
807
|
+
if (strictUnresolved && strictUnresolved.length > 0) {
|
|
808
|
+
await checkpointStepFailure(
|
|
809
|
+
runId,
|
|
810
|
+
stepId,
|
|
811
|
+
systemOneUnresolvedError(node.id, strictUnresolved),
|
|
812
|
+
0,
|
|
813
|
+
);
|
|
814
|
+
return { outcome: "failed", successors: [] };
|
|
815
|
+
}
|
|
816
|
+
|
|
778
817
|
if (unresolved.length > 0) {
|
|
779
818
|
console.warn(
|
|
780
819
|
`[workflow] Step ${node.id}: unresolved interpolation tokens: ${unresolved.join(", ")}`,
|
|
@@ -1063,7 +1102,36 @@ function buildScriptBodyCtx(
|
|
|
1063
1102
|
export function interpolateNodeConfig(
|
|
1064
1103
|
node: Pick<WorkflowNode, "type" | "config" | "inputs">,
|
|
1065
1104
|
interpolationCtx: Record<string, unknown>,
|
|
1066
|
-
): {
|
|
1105
|
+
): {
|
|
1106
|
+
value: unknown;
|
|
1107
|
+
unresolved: string[];
|
|
1108
|
+
scriptBodyUnresolved?: string[];
|
|
1109
|
+
/** Tokens that must resolve before dispatch; the caller fails the step when non-empty. */
|
|
1110
|
+
strictUnresolved?: string[];
|
|
1111
|
+
} {
|
|
1112
|
+
if (node.type === SYSTEM_ONE_DECISION_NODE_TYPE) {
|
|
1113
|
+
// `state` and `questions` keep a whole-token reference's JSON type (an object
|
|
1114
|
+
// stays an object; string interpolation would flatten it to JSON text). The
|
|
1115
|
+
// resolved value is data, never re-interpolated, so a literal `{{` inside a
|
|
1116
|
+
// lead's text is not read as another template. Every other field, and every
|
|
1117
|
+
// unresolved token anywhere in the config, is strict: no blank reaches the API.
|
|
1118
|
+
const { state, questions, ...rest } = node.config;
|
|
1119
|
+
const restResult = deepInterpolate(rest, interpolationCtx);
|
|
1120
|
+
const dynamic: Record<string, unknown> = {};
|
|
1121
|
+
if (Object.hasOwn(node.config, "state")) dynamic.state = state;
|
|
1122
|
+
if (Object.hasOwn(node.config, "questions")) dynamic.questions = questions;
|
|
1123
|
+
const dynamicResult = deepInterpolate(dynamic, interpolationCtx, { preserveRawTokens: true });
|
|
1124
|
+
const unresolved = [...restResult.unresolved, ...dynamicResult.unresolved];
|
|
1125
|
+
return {
|
|
1126
|
+
value: {
|
|
1127
|
+
...(restResult.value as Record<string, unknown>),
|
|
1128
|
+
...(dynamicResult.value as Record<string, unknown>),
|
|
1129
|
+
},
|
|
1130
|
+
unresolved,
|
|
1131
|
+
strictUnresolved: unresolved,
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1067
1135
|
if (node.type === "foreach" && Object.hasOwn(node.config, "over")) {
|
|
1068
1136
|
const { over, body, ...configWithoutOverAndBody } = node.config;
|
|
1069
1137
|
const configResult = deepInterpolate(configWithoutOverAndBody, interpolationCtx);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { explicitModelErrorForAgent } from "../../be/model-validation";
|
|
2
3
|
import { applyPreTaskCreate } from "../../extensions/apply-task-create";
|
|
3
4
|
import { workflowContextKey } from "../../tasks/context-key";
|
|
4
5
|
import { TaskCreationBlockedError } from "../../tasks/errors";
|
|
@@ -30,6 +31,8 @@ export const AgentTaskConfigSchema = z.object({
|
|
|
30
31
|
vcsRepo: z.string().min(1).optional(),
|
|
31
32
|
model: z.string().min(1).optional(),
|
|
32
33
|
modelTier: ModelTierSchema.optional(),
|
|
34
|
+
/** Accept a `model` the catalog does not list (a custom id). Without it an unknown id fails the node. */
|
|
35
|
+
allowCustomModel: z.boolean().optional(),
|
|
33
36
|
effort: ReasoningEffortSchema.optional(),
|
|
34
37
|
parentTaskId: z.string().uuid().optional(),
|
|
35
38
|
requestedByUserId: z.string().optional(),
|
|
@@ -98,6 +101,12 @@ export class AgentTaskExecutor extends BaseExecutor<
|
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
// 3. Create the task (config is already deep-interpolated by the engine)
|
|
104
|
+
const modelError = await explicitModelErrorForAgent({
|
|
105
|
+
model: splitLegacyModelAlias({ model: config.model, modelTier: config.modelTier }).model,
|
|
106
|
+
allowCustomModel: config.allowCustomModel,
|
|
107
|
+
agentId: config.agentId,
|
|
108
|
+
});
|
|
109
|
+
if (modelError) return { status: "failed", error: modelError };
|
|
101
110
|
const preCreate = await applyPreTaskCreate({
|
|
102
111
|
description: config.template,
|
|
103
112
|
options: {
|