@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
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// An explicit model id (task, schedule, workflow node, agent runtime) is checked against the
|
|
2
|
+
// model catalog; `allowCustomModel` is the escape hatch. See src/be/model-validation.ts.
|
|
3
|
+
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
5
|
+
import { unlink } from "node:fs/promises";
|
|
6
|
+
import { createServer, type Server } from "node:http";
|
|
7
|
+
import { closeDb, createAgent, getDbClient, getSwarmConfigs, initDb } from "../be/db";
|
|
8
|
+
import { explicitModelError, isKnownCatalogModel } from "../be/model-validation";
|
|
9
|
+
import { handleAgentsRest } from "../http/agents";
|
|
10
|
+
import { handleSchedules } from "../http/schedules";
|
|
11
|
+
import { handleTasks } from "../http/tasks";
|
|
12
|
+
import { sendTaskHandler } from "../tools/send-task";
|
|
13
|
+
import { ownerCtx } from "../tools/task-tool-ctx";
|
|
14
|
+
import { setRequestAuth } from "../utils/request-auth-context";
|
|
15
|
+
import { AgentTaskExecutor } from "../workflows/executors/agent-task";
|
|
16
|
+
import { listenOnFreePort } from "./test-net";
|
|
17
|
+
|
|
18
|
+
const TEST_DB_PATH = "./test-explicit-model-validation.sqlite";
|
|
19
|
+
|
|
20
|
+
async function removeDbFiles(path: string): Promise<void> {
|
|
21
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
22
|
+
await unlink(path + suffix).catch(() => undefined);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let server: Server;
|
|
27
|
+
let baseUrl = "";
|
|
28
|
+
let leadId = "";
|
|
29
|
+
let workerId = "";
|
|
30
|
+
let acpId = "";
|
|
31
|
+
|
|
32
|
+
beforeAll(async () => {
|
|
33
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
34
|
+
initDb(TEST_DB_PATH);
|
|
35
|
+
leadId = (await createAgent({ name: "validation-lead", isLead: true, status: "idle" })).id;
|
|
36
|
+
workerId = (
|
|
37
|
+
await createAgent({
|
|
38
|
+
name: "validation-worker",
|
|
39
|
+
isLead: false,
|
|
40
|
+
status: "idle",
|
|
41
|
+
harnessProvider: "claude",
|
|
42
|
+
})
|
|
43
|
+
).id;
|
|
44
|
+
acpId = (
|
|
45
|
+
await createAgent({
|
|
46
|
+
name: "validation-acp",
|
|
47
|
+
isLead: false,
|
|
48
|
+
status: "idle",
|
|
49
|
+
harnessProvider: "acp",
|
|
50
|
+
})
|
|
51
|
+
).id;
|
|
52
|
+
|
|
53
|
+
server = createServer((req, res) => {
|
|
54
|
+
setRequestAuth(req, { kind: "operator", fingerprint: "explicit-model-validation-test" });
|
|
55
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
56
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
57
|
+
const agentId = (req.headers["x-agent-id"] as string | undefined) ?? undefined;
|
|
58
|
+
void (async () => {
|
|
59
|
+
res.setHeader("Content-Type", "application/json");
|
|
60
|
+
if (await handleTasks(req, res, segments, url.searchParams, agentId)) return;
|
|
61
|
+
if (await handleSchedules(req, res, segments, url.searchParams, agentId)) return;
|
|
62
|
+
if (await handleAgentsRest(req, res, segments, url.searchParams, agentId)) return;
|
|
63
|
+
res.writeHead(404);
|
|
64
|
+
res.end("{}");
|
|
65
|
+
})();
|
|
66
|
+
});
|
|
67
|
+
baseUrl = `http://localhost:${await listenOnFreePort(server)}`;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterAll(async () => {
|
|
71
|
+
server.close();
|
|
72
|
+
closeDb();
|
|
73
|
+
await removeDbFiles(TEST_DB_PATH);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
async function api(method: string, path: string, body: unknown) {
|
|
77
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
78
|
+
method,
|
|
79
|
+
headers: { "content-type": "application/json" },
|
|
80
|
+
body: JSON.stringify(body),
|
|
81
|
+
});
|
|
82
|
+
const text = await res.text();
|
|
83
|
+
return { status: res.status, body: text ? (JSON.parse(text) as Record<string, any>) : {} };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe("isKnownCatalogModel", () => {
|
|
87
|
+
const catalog = {
|
|
88
|
+
anthropic: {
|
|
89
|
+
models: {
|
|
90
|
+
"claude-opus-5-5": { release_date: "2026-09-22" },
|
|
91
|
+
"claude-sonnet-5-5": { release_date: "2026-09-28" },
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
openai: { models: { "gpt-5.6-sol": {} } },
|
|
95
|
+
openrouter: { models: { "deepseek/deepseek-v4.1-flash": {} } },
|
|
96
|
+
"amazon-bedrock": { models: { "anthropic.claude-sonnet-5": {} } },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
test("catalog ids, bare or provider-qualified, and Claude CLI shortnames are known", () => {
|
|
100
|
+
for (const id of [
|
|
101
|
+
"claude-opus-5-5",
|
|
102
|
+
"gpt-5.6-sol",
|
|
103
|
+
"anthropic/claude-opus-5-5",
|
|
104
|
+
"openrouter/deepseek/deepseek-v4.1-flash",
|
|
105
|
+
"deepseek/deepseek-v4.1-flash",
|
|
106
|
+
"amazon-bedrock/anthropic.claude-sonnet-5",
|
|
107
|
+
"amazon-bedrock/us.anthropic.claude-sonnet-5",
|
|
108
|
+
"opus",
|
|
109
|
+
"sonnet",
|
|
110
|
+
"claude-opus-5-5[1m]",
|
|
111
|
+
]) {
|
|
112
|
+
expect(isKnownCatalogModel(id, catalog)).toBe(true);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("a typo, a wrong provider, and object-prototype names are unknown", () => {
|
|
117
|
+
for (const id of [
|
|
118
|
+
"claude-nonexistent-9",
|
|
119
|
+
"claude-opus-5-6",
|
|
120
|
+
"openrouter/deepseek/nope",
|
|
121
|
+
"gpt-9",
|
|
122
|
+
"haikuu",
|
|
123
|
+
"constructor",
|
|
124
|
+
"__proto__",
|
|
125
|
+
"toString",
|
|
126
|
+
"openai/constructor",
|
|
127
|
+
"",
|
|
128
|
+
]) {
|
|
129
|
+
expect(isKnownCatalogModel(id, catalog)).toBe(false);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("explicitModelError", () => {
|
|
135
|
+
test("rejects an unknown id with a message that names the escape hatch", async () => {
|
|
136
|
+
const error = await explicitModelError({ model: "claude-nonexistent-9" });
|
|
137
|
+
expect(error).toContain('Unknown model "claude-nonexistent-9"');
|
|
138
|
+
expect(error).toContain("allowCustomModel");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("passes a catalog id, a shortname, nothing at all, and a custom id with the flag", async () => {
|
|
142
|
+
expect(await explicitModelError({ model: "claude-opus-5-5" })).toBeNull();
|
|
143
|
+
expect(await explicitModelError({ model: "opus" })).toBeNull();
|
|
144
|
+
expect(await explicitModelError({ model: undefined })).toBeNull();
|
|
145
|
+
expect(await explicitModelError({ model: " " })).toBeNull();
|
|
146
|
+
expect(
|
|
147
|
+
await explicitModelError({ model: "claude-nonexistent-9", allowCustomModel: true }),
|
|
148
|
+
).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("aliases: valid grammar that resolves passes, bad grammar and empty matches fail", async () => {
|
|
152
|
+
expect(await explicitModelError({ model: "latest:anthropic/opus" })).toBeNull();
|
|
153
|
+
expect(await explicitModelError({ model: "latest:nonsense" })).toContain("Invalid model alias");
|
|
154
|
+
expect(await explicitModelError({ model: "latest:anthropic/nosuchfamily" })).toContain(
|
|
155
|
+
"matches no model",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("harnesses the catalog does not describe skip the check", async () => {
|
|
160
|
+
for (const harnessProvider of ["acp", "devin", "dsh"]) {
|
|
161
|
+
expect(await explicitModelError({ model: "vendor-private-1", harnessProvider })).toBeNull();
|
|
162
|
+
}
|
|
163
|
+
expect(
|
|
164
|
+
await explicitModelError({ model: "vendor-private-1", harnessProvider: "claude" }),
|
|
165
|
+
).toContain("Unknown model");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("POST /api/tasks", () => {
|
|
170
|
+
const base = { task: "validate the model", agentId: leadId, routingReason: "skill" };
|
|
171
|
+
|
|
172
|
+
test("an unknown model is a 400 and creates no task", async () => {
|
|
173
|
+
const before = await getDbClient().get<{ n: number }>("SELECT COUNT(*) AS n FROM agent_tasks");
|
|
174
|
+
const res = await api("POST", "/api/tasks", { ...base, model: "claude-nonexistent-9" });
|
|
175
|
+
expect(res.status).toBe(400);
|
|
176
|
+
expect(String(res.body.error)).toContain("Unknown model");
|
|
177
|
+
const after = await getDbClient().get<{ n: number }>("SELECT COUNT(*) AS n FROM agent_tasks");
|
|
178
|
+
expect(after?.n).toBe(before?.n ?? 0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("a catalog model, an alias, a tier and a custom id with the flag are accepted", async () => {
|
|
182
|
+
for (const extra of [
|
|
183
|
+
{ model: "claude-opus-5-5" },
|
|
184
|
+
{ model: "latest:anthropic/opus" },
|
|
185
|
+
{ modelTier: "smart" },
|
|
186
|
+
{ model: "claude-nonexistent-9", allowCustomModel: true },
|
|
187
|
+
]) {
|
|
188
|
+
const res = await api("POST", "/api/tasks", { ...base, ...extra });
|
|
189
|
+
expect(res.status).toBe(201);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("an ACP assignee takes any model id", async () => {
|
|
194
|
+
const res = await api("POST", "/api/tasks", {
|
|
195
|
+
...base,
|
|
196
|
+
agentId: acpId,
|
|
197
|
+
model: "acp-private-model",
|
|
198
|
+
});
|
|
199
|
+
expect(res.status).toBe(201);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("send-task", () => {
|
|
204
|
+
const args = { task: "validate the model", offerMode: false, allowDuplicate: false };
|
|
205
|
+
|
|
206
|
+
test("an unknown model is refused, the flag lets it through", async () => {
|
|
207
|
+
const sender = ownerCtx({ agentId: workerId });
|
|
208
|
+
const refused = await sendTaskHandler(sender, { ...args, model: "claude-nonexistent-9" });
|
|
209
|
+
expect(refused.ok).toBe(false);
|
|
210
|
+
expect(refused.message).toContain("Unknown model");
|
|
211
|
+
const accepted = await sendTaskHandler(sender, {
|
|
212
|
+
...args,
|
|
213
|
+
model: "claude-nonexistent-9",
|
|
214
|
+
allowCustomModel: true,
|
|
215
|
+
});
|
|
216
|
+
expect(accepted.ok).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("schedules", () => {
|
|
221
|
+
const body = { name: "s-unknown-model", taskTemplate: "tick", intervalMs: 3_600_000 };
|
|
222
|
+
|
|
223
|
+
test("create rejects an unknown model; the flag stores it", async () => {
|
|
224
|
+
const refused = await api("POST", "/api/schedules", { ...body, model: "claude-nonexistent-9" });
|
|
225
|
+
expect(refused.status).toBe(400);
|
|
226
|
+
const accepted = await api("POST", "/api/schedules", {
|
|
227
|
+
...body,
|
|
228
|
+
name: "s-custom-model",
|
|
229
|
+
model: "claude-nonexistent-9",
|
|
230
|
+
allowCustomModel: true,
|
|
231
|
+
});
|
|
232
|
+
expect(accepted.status).toBe(201);
|
|
233
|
+
expect(accepted.body.model).toBe("claude-nonexistent-9");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("update rejects a new unknown model but re-saves the stored one", async () => {
|
|
237
|
+
const created = await api("POST", "/api/schedules", {
|
|
238
|
+
...body,
|
|
239
|
+
name: "s-update-model",
|
|
240
|
+
model: "claude-nonexistent-9",
|
|
241
|
+
allowCustomModel: true,
|
|
242
|
+
});
|
|
243
|
+
const id = created.body.id as string;
|
|
244
|
+
const changed = await api("PUT", `/api/schedules/${id}`, { model: "claude-nonexistent-10" });
|
|
245
|
+
expect(changed.status).toBe(400);
|
|
246
|
+
const resaved = await api("PUT", `/api/schedules/${id}`, {
|
|
247
|
+
model: "claude-nonexistent-9",
|
|
248
|
+
description: "an unrelated edit",
|
|
249
|
+
});
|
|
250
|
+
expect(resaved.status).toBe(200);
|
|
251
|
+
expect(resaved.body.description).toBe("an unrelated edit");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("update to a new unknown model with the flag succeeds on PUT and PATCH", async () => {
|
|
255
|
+
const created = await api("POST", "/api/schedules", {
|
|
256
|
+
...body,
|
|
257
|
+
name: "s-update-custom-model",
|
|
258
|
+
model: "claude-sonnet-5-5",
|
|
259
|
+
});
|
|
260
|
+
const id = created.body.id as string;
|
|
261
|
+
const put = await api("PUT", `/api/schedules/${id}`, {
|
|
262
|
+
model: "claude-nonexistent-10",
|
|
263
|
+
allowCustomModel: true,
|
|
264
|
+
});
|
|
265
|
+
expect(put.status).toBe(200);
|
|
266
|
+
expect(put.body.model).toBe("claude-nonexistent-10");
|
|
267
|
+
expect(put.body.allowCustomModel).toBeUndefined();
|
|
268
|
+
const patch = await api("PATCH", `/api/schedules/${id}`, {
|
|
269
|
+
model: "claude-nonexistent-11",
|
|
270
|
+
allowCustomModel: true,
|
|
271
|
+
});
|
|
272
|
+
expect(patch.status).toBe(200);
|
|
273
|
+
expect(patch.body.model).toBe("claude-nonexistent-11");
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe("PATCH /api/agents/:id/runtime", () => {
|
|
278
|
+
test("an unknown model is a 400 and writes nothing; allow_custom_model stores it", async () => {
|
|
279
|
+
const path = `/api/agents/${workerId}/runtime`;
|
|
280
|
+
const refused = await api("PATCH", path, {
|
|
281
|
+
harness_provider: "claude",
|
|
282
|
+
model: "claude-nonexistent-9",
|
|
283
|
+
});
|
|
284
|
+
expect(refused.status).toBe(400);
|
|
285
|
+
const rows = await getSwarmConfigs({ scope: "agent", scopeId: workerId });
|
|
286
|
+
expect(rows.find((r) => r.key === "MODEL_OVERRIDE")).toBeUndefined();
|
|
287
|
+
|
|
288
|
+
const accepted = await api("PATCH", path, {
|
|
289
|
+
harness_provider: "claude",
|
|
290
|
+
model: "claude-nonexistent-9",
|
|
291
|
+
allow_custom_model: true,
|
|
292
|
+
});
|
|
293
|
+
expect(accepted.status).toBe(200);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("a latest: alias is not an agent default model", async () => {
|
|
297
|
+
const res = await api("PATCH", `/api/agents/${workerId}/runtime`, {
|
|
298
|
+
harness_provider: "claude",
|
|
299
|
+
model: "latest:anthropic/opus",
|
|
300
|
+
allow_custom_model: true,
|
|
301
|
+
});
|
|
302
|
+
expect(res.status).toBe(400);
|
|
303
|
+
expect(String(res.body.error)).toContain("MODEL_TIER");
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe("workflow agent-task node", () => {
|
|
308
|
+
test("fails the node on an unknown model, accepts it with allowCustomModel", async () => {
|
|
309
|
+
const deps = {
|
|
310
|
+
db: {
|
|
311
|
+
getTaskByWorkflowRunStepId: async () => null,
|
|
312
|
+
getWorkflow: async () => null,
|
|
313
|
+
createTaskExtended: async () => ({ id: crypto.randomUUID() }),
|
|
314
|
+
},
|
|
315
|
+
} as unknown as ConstructorParameters<typeof AgentTaskExecutor>[0];
|
|
316
|
+
const executor = new AgentTaskExecutor(deps);
|
|
317
|
+
const meta = { runId: crypto.randomUUID(), stepId: crypto.randomUUID(), nodeId: "n" } as never;
|
|
318
|
+
const refused = await executor.run({
|
|
319
|
+
config: { template: "do it", model: "claude-nonexistent-9" },
|
|
320
|
+
context: {},
|
|
321
|
+
meta,
|
|
322
|
+
});
|
|
323
|
+
expect(refused.status).toBe("failed");
|
|
324
|
+
expect(refused.error).toContain("Unknown model");
|
|
325
|
+
const accepted = await executor.run({
|
|
326
|
+
config: { template: "do it", model: "claude-nonexistent-9", allowCustomModel: true },
|
|
327
|
+
context: {},
|
|
328
|
+
meta,
|
|
329
|
+
});
|
|
330
|
+
expect(accepted.status).toBe("success");
|
|
331
|
+
});
|
|
332
|
+
});
|