@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,279 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
closeDb,
|
|
5
|
+
createAgent,
|
|
6
|
+
createScheduledTask,
|
|
7
|
+
createSessionCost,
|
|
8
|
+
createTaskExtended,
|
|
9
|
+
createUser,
|
|
10
|
+
createWorkflow,
|
|
11
|
+
createWorkflowRun,
|
|
12
|
+
getDbClient,
|
|
13
|
+
getSessionCostSummary,
|
|
14
|
+
initDb,
|
|
15
|
+
} from "../be/db";
|
|
16
|
+
import { createTask } from "../be/db/tasks/write";
|
|
17
|
+
import { expectFlagsMatchLegacy, legacyHumanFreeIds, storedFlags } from "./human-free-oracle";
|
|
18
|
+
|
|
19
|
+
const TEST_DB_PATH = "./test-human-free-flag.sqlite";
|
|
20
|
+
|
|
21
|
+
describe("agent_tasks.isHumanFree", () => {
|
|
22
|
+
const named: Record<string, string> = {};
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
26
|
+
try {
|
|
27
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
initDb(TEST_DB_PATH);
|
|
31
|
+
|
|
32
|
+
const user = await createUser({ name: "Flag Requester" });
|
|
33
|
+
const otherUser = await createUser({ name: "Flag Other Requester" });
|
|
34
|
+
const remember = async (name: string, task: Promise<{ id: string }>) => {
|
|
35
|
+
named[name] = (await task).id;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Root rules.
|
|
39
|
+
await remember("human", createTaskExtended("Human work", { requestedByUserId: user.id }));
|
|
40
|
+
await remember("heartbeat", createTaskExtended("hb", { taskType: "heartbeat" }));
|
|
41
|
+
await remember("checklist", createTaskExtended("hb", { taskType: "heartbeat-checklist" }));
|
|
42
|
+
await remember("bootTriage", createTaskExtended("bt", { taskType: "boot-triage" }));
|
|
43
|
+
await remember(
|
|
44
|
+
"staleRequesterHeartbeat",
|
|
45
|
+
createTaskExtended("hb", { taskType: "heartbeat", requestedByUserId: user.id }),
|
|
46
|
+
);
|
|
47
|
+
await remember("tagHeartbeat", createTaskExtended("tag", { tags: ["heartbeat"] }));
|
|
48
|
+
// LIKE is case-insensitive, so the stored flag must be too.
|
|
49
|
+
await remember("tagHeartbeatCase", createTaskExtended("tag", { tags: ["Heartbeat"] }));
|
|
50
|
+
await remember("tagLookalike", createTaskExtended("tag", { tags: ["heartbeat-ish"] }));
|
|
51
|
+
await remember("schedule", createTaskExtended("sched", { source: "schedule" }));
|
|
52
|
+
await remember(
|
|
53
|
+
"humanSchedule",
|
|
54
|
+
createTaskExtended("sched", { source: "schedule", requestedByUserId: user.id }),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Propagation below a human-free root.
|
|
58
|
+
await remember(
|
|
59
|
+
"scheduleChild",
|
|
60
|
+
createTaskExtended("child", { parentTaskId: named.schedule as string }),
|
|
61
|
+
);
|
|
62
|
+
await remember(
|
|
63
|
+
"scheduleGrandchild",
|
|
64
|
+
createTaskExtended("grandchild", { parentTaskId: named.scheduleChild as string }),
|
|
65
|
+
);
|
|
66
|
+
await remember(
|
|
67
|
+
"handoff",
|
|
68
|
+
createTaskExtended("handoff", {
|
|
69
|
+
parentTaskId: named.schedule as string,
|
|
70
|
+
requestedByUserId: user.id,
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
await remember(
|
|
74
|
+
"handoffChild",
|
|
75
|
+
createTaskExtended("below a handoff", { parentTaskId: named.handoff as string }),
|
|
76
|
+
);
|
|
77
|
+
// A requester copied from a stale-attributed heartbeat does not stop propagation.
|
|
78
|
+
await remember(
|
|
79
|
+
"inheritedChild",
|
|
80
|
+
createTaskExtended("inherited", { parentTaskId: named.staleRequesterHeartbeat as string }),
|
|
81
|
+
);
|
|
82
|
+
await remember(
|
|
83
|
+
"explicitBelowStale",
|
|
84
|
+
createTaskExtended("explicit", {
|
|
85
|
+
parentTaskId: named.staleRequesterHeartbeat as string,
|
|
86
|
+
requestedByUserId: otherUser.id,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// `source = system`: free only when the parent has no requester.
|
|
91
|
+
await remember(
|
|
92
|
+
"systemUnderHuman",
|
|
93
|
+
createTaskExtended("sys", { source: "system", parentTaskId: named.human as string }),
|
|
94
|
+
);
|
|
95
|
+
await remember(
|
|
96
|
+
"systemUnderFree",
|
|
97
|
+
createTaskExtended("sys", { source: "system", parentTaskId: named.schedule as string }),
|
|
98
|
+
);
|
|
99
|
+
const requesterless = await createTaskExtended("requesterless root");
|
|
100
|
+
named.requesterless = requesterless.id;
|
|
101
|
+
await remember(
|
|
102
|
+
"systemUnderRequesterless",
|
|
103
|
+
createTaskExtended("sys", { source: "system", parentTaskId: requesterless.id }),
|
|
104
|
+
);
|
|
105
|
+
await remember("systemRoot", createTaskExtended("sys", { source: "system" }));
|
|
106
|
+
|
|
107
|
+
// Workflow roots follow the run's trigger and creator, not the task's own fields.
|
|
108
|
+
const workflow = await createWorkflow({
|
|
109
|
+
name: `human-free-flag-${crypto.randomUUID()}`,
|
|
110
|
+
definition: { nodes: [] },
|
|
111
|
+
});
|
|
112
|
+
const freeSchedule = await createScheduledTask({
|
|
113
|
+
name: `flag-free-${crypto.randomUUID()}`,
|
|
114
|
+
intervalMs: 60_000,
|
|
115
|
+
targetType: "workflow",
|
|
116
|
+
workflowId: workflow.id,
|
|
117
|
+
});
|
|
118
|
+
const humanSchedule = await createScheduledTask({
|
|
119
|
+
name: `flag-human-${crypto.randomUUID()}`,
|
|
120
|
+
intervalMs: 60_000,
|
|
121
|
+
targetType: "workflow",
|
|
122
|
+
workflowId: workflow.id,
|
|
123
|
+
createdBy: user.id,
|
|
124
|
+
});
|
|
125
|
+
const freeRun = await createWorkflowRun({
|
|
126
|
+
id: crypto.randomUUID(),
|
|
127
|
+
workflowId: workflow.id,
|
|
128
|
+
triggerType: "schedule",
|
|
129
|
+
triggerData: { scheduleId: freeSchedule.id },
|
|
130
|
+
});
|
|
131
|
+
const humanRun = await createWorkflowRun({
|
|
132
|
+
id: crypto.randomUUID(),
|
|
133
|
+
workflowId: workflow.id,
|
|
134
|
+
triggerType: "schedule",
|
|
135
|
+
triggerData: { scheduleId: humanSchedule.id },
|
|
136
|
+
createdBy: user.id,
|
|
137
|
+
});
|
|
138
|
+
const manualRun = await createWorkflowRun({
|
|
139
|
+
id: crypto.randomUUID(),
|
|
140
|
+
workflowId: workflow.id,
|
|
141
|
+
triggerData: { triggerType: "schedule", scheduleId: freeSchedule.id },
|
|
142
|
+
});
|
|
143
|
+
await remember(
|
|
144
|
+
"workflowFreeRoot",
|
|
145
|
+
createTaskExtended("wf", { source: "workflow", workflowRunId: freeRun.id }),
|
|
146
|
+
);
|
|
147
|
+
await remember(
|
|
148
|
+
"workflowFreeChild",
|
|
149
|
+
createTaskExtended("wf child", {
|
|
150
|
+
source: "workflow",
|
|
151
|
+
workflowRunId: freeRun.id,
|
|
152
|
+
parentTaskId: named.workflowFreeRoot as string,
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
await remember(
|
|
156
|
+
"workflowHumanRoot",
|
|
157
|
+
createTaskExtended("wf", {
|
|
158
|
+
source: "workflow",
|
|
159
|
+
workflowRunId: humanRun.id,
|
|
160
|
+
requestedByUserId: user.id,
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
await remember(
|
|
164
|
+
"workflowManualRoot",
|
|
165
|
+
createTaskExtended("wf", { source: "workflow", workflowRunId: manualRun.id }),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// The minimal insert path.
|
|
169
|
+
named.minimalDefault = (await createTask("agent-flag", "minimal")).id;
|
|
170
|
+
named.minimalSchedule = (await createTask("agent-flag", "minimal", { source: "schedule" })).id;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
afterAll(async () => {
|
|
174
|
+
closeDb();
|
|
175
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
176
|
+
try {
|
|
177
|
+
await unlink(TEST_DB_PATH + suffix);
|
|
178
|
+
} catch {}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("is set at creation to exactly what the recursive CTE selected", async () => {
|
|
183
|
+
const flags = await storedFlags();
|
|
184
|
+
const legacy = await legacyHumanFreeIds();
|
|
185
|
+
|
|
186
|
+
// The matrix must exercise both outcomes, or the comparison proves nothing.
|
|
187
|
+
expect(legacy.size).toBeGreaterThan(8);
|
|
188
|
+
expect(flags.size - legacy.size).toBeGreaterThan(5);
|
|
189
|
+
expectFlagsMatchLegacy(flags, legacy);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("classifies each rule and propagation case", async () => {
|
|
193
|
+
const flags = await storedFlags();
|
|
194
|
+
const expected: Record<string, boolean> = {
|
|
195
|
+
human: false,
|
|
196
|
+
heartbeat: true,
|
|
197
|
+
checklist: true,
|
|
198
|
+
bootTriage: true,
|
|
199
|
+
staleRequesterHeartbeat: true,
|
|
200
|
+
tagHeartbeat: true,
|
|
201
|
+
tagHeartbeatCase: true,
|
|
202
|
+
tagLookalike: false,
|
|
203
|
+
schedule: true,
|
|
204
|
+
humanSchedule: false,
|
|
205
|
+
scheduleChild: true,
|
|
206
|
+
scheduleGrandchild: true,
|
|
207
|
+
handoff: false,
|
|
208
|
+
handoffChild: false,
|
|
209
|
+
inheritedChild: true,
|
|
210
|
+
explicitBelowStale: false,
|
|
211
|
+
systemUnderHuman: false,
|
|
212
|
+
systemUnderFree: true,
|
|
213
|
+
requesterless: false,
|
|
214
|
+
systemUnderRequesterless: true,
|
|
215
|
+
systemRoot: false,
|
|
216
|
+
workflowFreeRoot: true,
|
|
217
|
+
workflowFreeChild: true,
|
|
218
|
+
workflowHumanRoot: false,
|
|
219
|
+
workflowManualRoot: false,
|
|
220
|
+
minimalDefault: false,
|
|
221
|
+
minimalSchedule: true,
|
|
222
|
+
};
|
|
223
|
+
const actual = Object.fromEntries(
|
|
224
|
+
Object.keys(expected).map((name) => [name, flags.get(named[name] as string)]),
|
|
225
|
+
);
|
|
226
|
+
expect(actual).toEqual(expected);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("migration 182 backfill selects the same tasks as creation-time classification", async () => {
|
|
230
|
+
const before = await storedFlags();
|
|
231
|
+
const sql = await Bun.file("src/be/migrations/182_task_human_free_flag.sql").text();
|
|
232
|
+
const start = sql.indexOf("WITH RECURSIVE");
|
|
233
|
+
const endMarker = "WHERE id IN (SELECT id FROM human_free_tasks);";
|
|
234
|
+
const end = sql.indexOf(endMarker) + endMarker.length;
|
|
235
|
+
expect(start).toBeGreaterThan(-1);
|
|
236
|
+
expect(end).toBeGreaterThan(endMarker.length);
|
|
237
|
+
|
|
238
|
+
await getDbClient().run("UPDATE agent_tasks SET isHumanFree = 0");
|
|
239
|
+
await getDbClient().run(sql.slice(start, end));
|
|
240
|
+
|
|
241
|
+
expect(await storedFlags()).toEqual(before);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("summary splits human-free and attributed spend from the stored flag", async () => {
|
|
245
|
+
const agent = await createAgent({ name: "Flag Agent", isLead: false, status: "idle" });
|
|
246
|
+
for (const [name, cost] of [
|
|
247
|
+
["human", 1],
|
|
248
|
+
["heartbeat", 2],
|
|
249
|
+
["scheduleChild", 4],
|
|
250
|
+
["handoff", 8],
|
|
251
|
+
] as const) {
|
|
252
|
+
await createSessionCost({
|
|
253
|
+
sessionId: `flag-${name}`,
|
|
254
|
+
taskId: named[name] as string,
|
|
255
|
+
agentId: agent.id,
|
|
256
|
+
totalCostUsd: cost,
|
|
257
|
+
durationMs: 1000,
|
|
258
|
+
numTurns: 1,
|
|
259
|
+
model: "opus",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// A session whose task row is gone is not human-free.
|
|
263
|
+
await createSessionCost({
|
|
264
|
+
sessionId: "flag-no-task",
|
|
265
|
+
agentId: agent.id,
|
|
266
|
+
totalCostUsd: 16,
|
|
267
|
+
durationMs: 1000,
|
|
268
|
+
numTurns: 1,
|
|
269
|
+
model: "opus",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const { totals } = await getSessionCostSummary({ agentId: agent.id, groupBy: "day" });
|
|
273
|
+
expect(totals.totalCostUsd).toBe(31);
|
|
274
|
+
expect(totals.excludedCostUsd).toBe(6);
|
|
275
|
+
expect(totals.excludedTaskCount).toBe(2);
|
|
276
|
+
expect(totals.attributableCostUsd).toBe(25);
|
|
277
|
+
expect(totals.attributedCostUsd).toBe(9);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { expect } from "bun:test";
|
|
2
|
+
import { getDbClient } from "../be/db";
|
|
3
|
+
|
|
4
|
+
// The recursive CTE the usage reports rebuilt on every call before
|
|
5
|
+
// `agent_tasks.isHumanFree` existed. Kept as an independent oracle: the stored
|
|
6
|
+
// flag must select exactly the tasks this selects.
|
|
7
|
+
const LEGACY_HUMAN_FREE_CTE = `WITH RECURSIVE human_free_tasks(id) AS (
|
|
8
|
+
SELECT task.id
|
|
9
|
+
FROM agent_tasks task
|
|
10
|
+
LEFT JOIN agent_tasks parent ON parent.id = task.parentTaskId
|
|
11
|
+
WHERE COALESCE(task.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
|
|
12
|
+
OR COALESCE(task.tags, '[]') LIKE '%"heartbeat"%'
|
|
13
|
+
OR (COALESCE(task.source, '') = 'schedule' AND task.requestedByUserId IS NULL)
|
|
14
|
+
OR (
|
|
15
|
+
task.parentTaskId IS NULL
|
|
16
|
+
AND COALESCE(task.source, '') = 'workflow'
|
|
17
|
+
AND task.requestedByUserId IS NULL
|
|
18
|
+
AND EXISTS (
|
|
19
|
+
SELECT 1 FROM workflow_runs run
|
|
20
|
+
WHERE run.id = task.workflowRunId
|
|
21
|
+
AND run.triggerType = 'schedule'
|
|
22
|
+
AND run.created_by IS NULL
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
OR (
|
|
26
|
+
COALESCE(task.source, '') = 'system'
|
|
27
|
+
AND parent.id IS NOT NULL
|
|
28
|
+
AND parent.requestedByUserId IS NULL
|
|
29
|
+
)
|
|
30
|
+
UNION
|
|
31
|
+
SELECT child.id
|
|
32
|
+
FROM agent_tasks child
|
|
33
|
+
JOIN human_free_tasks parent ON child.parentTaskId = parent.id
|
|
34
|
+
WHERE child.requestedByUserId IS NULL
|
|
35
|
+
OR child.requestedByUserIdInherited = 1
|
|
36
|
+
)`;
|
|
37
|
+
|
|
38
|
+
export async function storedFlags(): Promise<Map<string, boolean>> {
|
|
39
|
+
const rows = await getDbClient().query<{ id: string; isHumanFree: number }>(
|
|
40
|
+
"SELECT id, isHumanFree FROM agent_tasks",
|
|
41
|
+
);
|
|
42
|
+
return new Map(rows.map((row) => [row.id, row.isHumanFree === 1]));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function legacyHumanFreeIds(): Promise<Set<string>> {
|
|
46
|
+
const rows = await getDbClient().query<{ id: string }>(
|
|
47
|
+
`${LEGACY_HUMAN_FREE_CTE} SELECT id FROM human_free_tasks`,
|
|
48
|
+
);
|
|
49
|
+
return new Set(rows.map((row) => row.id));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function expectFlagsMatchLegacy(flags: Map<string, boolean>, legacy: Set<string>) {
|
|
53
|
+
const mismatched = [...flags]
|
|
54
|
+
.filter(([id, flagged]) => flagged !== legacy.has(id))
|
|
55
|
+
.map(([id, flagged]) => `${id} stored=${flagged} legacy=${legacy.has(id)}`);
|
|
56
|
+
expect(mismatched).toEqual([]);
|
|
57
|
+
}
|