@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.
Files changed (272) hide show
  1. package/dist/{acp-adapter-xaq4n6pc.js → acp-adapter-qhch0n2v.js} +1 -1
  2. package/dist/{actions-p9hz5yat.js → actions-dtsh1mfb.js} +33 -29
  3. package/dist/{anthropic-messages-51pz26qe.js → anthropic-messages-f2h06k64.js} +8 -3
  4. package/dist/{app-g26wdcz6.js → app-y1b9srva.js} +10 -9
  5. package/dist/{assistant-wydfqkb3.js → assistant-31hwy5xj.js} +34 -30
  6. package/dist/{azure-openai-responses-8gksqtmz.js → azure-openai-responses-0k1mfax5.js} +12 -13
  7. package/dist/{boot-reembed-se8zmdcp.js → boot-reembed-48d2qpkt.js} +11 -11
  8. package/dist/{boot-reembed-g5ckg9yb.js → boot-reembed-w0bryhkr.js} +12 -12
  9. package/dist/{boot-scrub-logs-wfxqan61.js → boot-scrub-logs-t65wbrn7.js} +9 -8
  10. package/dist/{boot-seeds-s3q01xj3.js → boot-seeds-h7c32hvm.js} +12 -11
  11. package/dist/{claude-adapter-tv2x4f36.js → claude-adapter-1t6611ne.js} +26 -21
  12. package/dist/{claude-managed-adapter-41b2s7fd.js → claude-managed-adapter-werp659j.js} +4 -2
  13. package/dist/{claude-managed-setup-60g8aazx.js → claude-managed-setup-y6rdgbc1.js} +11 -2
  14. package/dist/claude-sdk-session-zhkyk8yw.js +29319 -0
  15. package/dist/{cli-vj6h9kc2.js → cli-0m7wct0d.js} +59 -1
  16. package/dist/{cli-wscdvcqz.js → cli-0n2e4x49.js} +1 -1
  17. package/dist/cli-0xqde53b.js +176 -0
  18. package/dist/{cli-xberxa1r.js → cli-10ym38qc.js} +2 -2
  19. package/dist/{cli-5b6b62tz.js → cli-13hw4z91.js} +1 -1
  20. package/dist/{cli-rqz8qbw7.js → cli-20035trh.js} +50 -18
  21. package/dist/{cli-k0g1sjzm.js → cli-31nkw3g3.js} +3 -3
  22. package/dist/cli-3gvrr8vj.js +1422 -0
  23. package/dist/{cli-c07r9a2m.js → cli-48527fpw.js} +9 -8
  24. package/dist/{cli-24k806sc.js → cli-4aj25szq.js} +2 -2
  25. package/dist/{cli-7gtvvpbh.js → cli-4tvgz99m.js} +7 -7
  26. package/dist/cli-503ya656.js +455 -0
  27. package/dist/{cli-cw9zck8j.js → cli-5f2hq128.js} +8 -4
  28. package/dist/{cli-s8eet254.js → cli-68bmme5p.js} +11078 -27833
  29. package/dist/{cli-ftfvggzx.js → cli-79aq9cgb.js} +794 -563
  30. package/dist/cli-7cxw8b60.js +14910 -0
  31. package/dist/cli-7kyk9jr3.js +22 -0
  32. package/dist/cli-7zttzetj.js +324 -0
  33. package/dist/{cli-y95wp3tg.js → cli-96qd5psa.js} +1 -1
  34. package/dist/{cli-g8djp6qe.js → cli-9yg65c1j.js} +22 -0
  35. package/dist/{cli-3gmcwfyx.js → cli-ardpzhjm.js} +5 -5
  36. package/dist/{cli-7s3syk22.js → cli-b6thtzac.js} +5 -5
  37. package/dist/{cli-00w2c66p.js → cli-bgv70kym.js} +13 -2
  38. package/dist/{cli-j55n64b6.js → cli-bv0a38v9.js} +163 -44
  39. package/dist/cli-ca2w7zv7.js +115 -0
  40. package/dist/cli-cpvsaqsw.js +333 -0
  41. package/dist/cli-cygyqj3b.js +754 -0
  42. package/dist/{cli-x24pshtq.js → cli-dvjpkkjd.js} +6 -6
  43. package/dist/{cli-5d10vd2m.js → cli-e0ndxhtk.js} +4 -4
  44. package/dist/{cli-4198qc9v.js → cli-e4xnc0qp.js} +15 -3
  45. package/dist/{cli-rjehvrts.js → cli-eg7cdg68.js} +8 -8
  46. package/dist/cli-ejpygnqz.js +3815 -0
  47. package/dist/{cli-fgqxwrpv.js → cli-fjtp3463.js} +1631 -127
  48. package/dist/{cli-zt1227k1.js → cli-fkqhbg8g.js} +5 -3
  49. package/dist/{cli-47m6dkd3.js → cli-g9df2m37.js} +409 -168
  50. package/dist/cli-gzyh0j0g.js +8455 -0
  51. package/dist/cli-hgqfttfc.js +183 -0
  52. package/dist/cli-hj65wsts.js +17755 -0
  53. package/dist/cli-j9z8mfx3.js +476 -0
  54. package/dist/{cli-aehm1rbq.js → cli-kc3dp84d.js} +1135 -61
  55. package/dist/{cli-h38x0qhg.js → cli-kdnehe0t.js} +4 -3
  56. package/dist/{cli-20cj37vt.js → cli-m4excttz.js} +136 -2216
  57. package/dist/{cli-8qmme6vd.js → cli-mg24hkt4.js} +2 -2
  58. package/dist/{cli-xhaeg2nc.js → cli-mzkqpxee.js} +1 -1
  59. package/dist/{cli-q67y4wzm.js → cli-phsaj4xq.js} +24 -2
  60. package/dist/{cli-pw9rj5t9.js → cli-pqetb83f.js} +1 -176
  61. package/dist/{cli-rj19hc34.js → cli-qjxy52w7.js} +1 -1
  62. package/dist/{cli-53sz8gkz.js → cli-qv4jv8bx.js} +1 -1
  63. package/dist/cli-qvnnx4ct.js +1051 -0
  64. package/dist/cli-rb9m6rpe.js +4980 -0
  65. package/dist/{cli-tt9k8r35.js → cli-rfezr056.js} +476 -909
  66. package/dist/{cli-ggm92wcn.js → cli-rgjprct3.js} +1 -0
  67. package/dist/{cli-9httdpyq.js → cli-rrqmqr1s.js} +1 -1
  68. package/dist/cli-rykfmgwg.js +102 -0
  69. package/dist/{cli-eetrvv91.js → cli-t3p77qk1.js} +1 -1
  70. package/dist/{cli-68w09sjw.js → cli-vn0qv9cj.js} +25 -88
  71. package/dist/{cli-987hg0aa.js → cli-vwbbaecp.js} +2 -2
  72. package/dist/cli-wd9hz40d.js +69 -0
  73. package/dist/{cli-j57qkrpq.js → cli-wgjx0q33.js} +13 -13
  74. package/dist/cli-xgpf00sk.js +2149 -0
  75. package/dist/{cli-mcnhgnn1.js → cli-ykk5jaax.js} +184 -26
  76. package/dist/{cli-aky8tctm.js → cli-z9x0sh1t.js} +3 -3
  77. package/dist/{cli-1hkrch3k.js → cli-zw65mab6.js} +5 -5
  78. package/dist/cli.js +38 -29
  79. package/dist/cloudflare-workers-ai-system-one-1sg586qv.js +46 -0
  80. package/dist/{codex-adapter-cnm8wk11.js → codex-adapter-jqfqqa21.js} +21 -14
  81. package/dist/{codex-hook-9hptmdhj.js → codex-hook-5eys3dya.js} +2 -2
  82. package/dist/{codex-session-runner-gfg547zj.js → codex-session-runner-zd341dsm.js} +21 -14
  83. package/dist/{commands-e3bn0p75.js → commands-vfsysz4s.js} +9 -8
  84. package/dist/{db-03v9kzr2.js → db-s3znhvbn.js} +15 -10
  85. package/dist/{dispatcher-nrmg8yht.js → dispatcher-5xn0c7rf.js} +32 -28
  86. package/dist/{dsh-adapter-k03nfqa6.js → dsh-adapter-894mgfvs.js} +3 -3
  87. package/dist/{e2b-93x1k0j5.js → e2b-ez07znam.js} +4 -3
  88. package/dist/execute-71ysgv35.js +965 -0
  89. package/dist/{google-generative-ai-bxmv50nc.js → google-generative-ai-1nfcdphp.js} +5 -4
  90. package/dist/{google-vertex-809af514.js → google-vertex-1tqcd7b1.js} +5 -4
  91. package/dist/{handlers-aqcedbyb.js → handlers-5w4jwycr.js} +35 -31
  92. package/dist/{hook-7vkskkre.js → hook-853bxrr6.js} +16 -13
  93. package/dist/{hook-ybvmrmhq.js → hook-z0hbxj85.js} +16 -13
  94. package/dist/{http-2eqa8t24.js → http-akc3wy3m.js} +600 -123
  95. package/dist/{index-f6cryxwe.js → index-4a5njnm1.js} +2 -59
  96. package/dist/{index-a3vn96n8.js → index-5sz16ksh.js} +187 -47
  97. package/dist/{index-c6zsv1bd.js → index-bjmjh9h4.js} +11 -11
  98. package/dist/{index-chgdkq36.js → index-cd151qkq.js} +32 -28
  99. package/dist/{index-x71bvq0p.js → index-k661dzzr.js} +33 -29
  100. package/dist/{index-1yks3qcr.js → index-ttpbhnjg.js} +32 -28
  101. package/dist/{keepalive-vtkf5n99.js → keepalive-8p9rpvz5.js} +11 -10
  102. package/dist/{lead-h3sp357x.js → lead-jp69z5h3.js} +49 -45
  103. package/dist/llama-cpp-classify-t2tccams.js +335 -0
  104. package/dist/{maintenance-jzeqtk3q.js → maintenance-fwx4ev0k.js} +12 -12
  105. package/dist/{mistral-conversations-zx0kn554.js → mistral-conversations-bhvwqcnk.js} +14 -17
  106. package/dist/{oauth-refresh-sweep-qp3dzmdp.js → oauth-refresh-sweep-cwcq1ctx.js} +10 -9
  107. package/dist/{onboard-tmsp0qbz.js → onboard-2b4q0t84.js} +6 -5
  108. package/dist/{openai-codex-responses-4vjqzvjp.js → openai-codex-responses-nekthgks.js} +21 -8
  109. package/dist/{openai-completions-9hjrqbw8.js → openai-completions-hxtzwj0s.js} +8 -11
  110. package/dist/{openai-responses-7b1pn0xt.js → openai-responses-w4r118w7.js} +22 -17
  111. package/dist/{opencode-adapter-x6x2da1z.js → opencode-adapter-zz7xvjj6.js} +6 -4
  112. package/dist/{openrouter-images-9f9ek4rw.js → openrouter-images-b6sqqjgn.js} +4 -6
  113. package/dist/{otel-impl-9mz73q65.js → otel-impl-g8rs4vwr.js} +1 -1
  114. package/dist/{pi-messages-txpjj39q.js → pi-messages-vxdt8v7j.js} +2 -1
  115. package/dist/{pi-mono-adapter-m6dvv0dz.js → pi-mono-adapter-n0me28wt.js} +29 -17
  116. package/dist/pricing-refresh-0v77385v.js +39 -0
  117. package/dist/{rbac-roles-c9wqz83x.js → rbac-roles-krjyazbj.js} +10 -9
  118. package/dist/{render-v2-gryrh9h8.js → render-v2-3wjsp9aw.js} +10 -9
  119. package/dist/runtime-ktjysacx.js +24 -0
  120. package/dist/{setup-q9720bt6.js → setup-yeqryke6.js} +6 -5
  121. package/dist/typesafe-system-one-96960jxp.js +27 -0
  122. package/dist/{virtual-modules-3htpgn8x.js → virtual-modules-dzdeq1sf.js} +21 -13
  123. package/dist/{work-objects-b76x7deb.js → work-objects-3pakpkw0.js} +35 -31
  124. package/dist/{worker-6vrxbv41.js → worker-85testgj.js} +49 -45
  125. package/openapi.json +863 -14
  126. package/package.json +9 -8
  127. package/src/be/db/agents.ts +20 -0
  128. package/src/be/db/tasks/human-free.ts +442 -0
  129. package/src/be/db/tasks/read.ts +56 -1
  130. package/src/be/db/tasks/write.ts +107 -11
  131. package/src/be/db.ts +89 -165
  132. package/src/be/harness-model-support.ts +182 -0
  133. package/src/be/human-free-drain.ts +67 -0
  134. package/src/be/migrations/182_task_human_free_flag.sql +63 -0
  135. package/src/be/migrations/183_human_free_reclassify_queue.sql +25 -0
  136. package/src/be/migrations/184_model_catalog.sql +120 -0
  137. package/src/be/model-catalog-store.ts +644 -0
  138. package/src/be/model-tier-keys.ts +31 -0
  139. package/src/be/model-tier-resolution.ts +489 -0
  140. package/src/be/model-validation.ts +112 -0
  141. package/src/be/models-catalog.ts +61 -7
  142. package/src/be/modelsdev-cache.json +82 -0
  143. package/src/be/modelsdev-cache.ts +2 -0
  144. package/src/be/pricing-refresh.ts +154 -12
  145. package/src/be/scripts/typecheck.ts +15 -0
  146. package/src/be/seed-scripts/catalog/task-failure-audit.ts +61 -9
  147. package/src/be/swarm-config-guard.ts +6 -0
  148. package/src/cli.tsx +10 -0
  149. package/src/commands/claude-managed-setup.ts +3 -1
  150. package/src/commands/runner.ts +76 -2
  151. package/src/extensions/contract-types.generated.ts +1 -1
  152. package/src/heartbeat/heartbeat.ts +64 -15
  153. package/src/heartbeat/templates.ts +2 -2
  154. package/src/http/agents.ts +49 -1
  155. package/src/http/index.ts +8 -0
  156. package/src/http/models-catalog.ts +357 -8
  157. package/src/http/poll.ts +45 -2
  158. package/src/http/schedules.ts +36 -6
  159. package/src/http/session-data.ts +2 -5
  160. package/src/http/stats.ts +6 -8
  161. package/src/http/tasks.ts +26 -1
  162. package/src/http/usage-cache.ts +56 -15
  163. package/src/http/workflows.ts +35 -8
  164. package/src/prompts/session-templates.ts +1 -0
  165. package/src/providers/claude-managed-models.ts +47 -161
  166. package/src/providers/codex-adapter.ts +4 -4
  167. package/src/providers/codex-models.ts +65 -198
  168. package/src/providers/reasoning-effort.ts +44 -140
  169. package/src/rbac/legacy-policy.ts +25 -0
  170. package/src/rbac/permissions.ts +10 -0
  171. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  172. package/src/scripts-runtime/types/stdlib.d.ts +15 -0
  173. package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
  174. package/src/scripts-runtime/types/swarm-sdk.d.ts +15 -0
  175. package/src/server.ts +6 -0
  176. package/src/slack/message-text.ts +56 -2
  177. package/src/slack/thread-buffer.ts +10 -2
  178. package/src/tests/agents-list-model-display.test.ts +4 -6
  179. package/src/tests/base-prompt.test.ts +3 -0
  180. package/src/tests/claude-adapter.test.ts +10 -0
  181. package/src/tests/claude-managed-adapter.test.ts +12 -18
  182. package/src/tests/codex-adapter.test.ts +2 -1
  183. package/src/tests/codex-gpt6-release.test.ts +66 -5
  184. package/src/tests/context-window.test.ts +7 -5
  185. package/src/tests/db-extraction-contract.test.ts +1 -0
  186. package/src/tests/db-task-read-contract.test.ts +5 -1
  187. package/src/tests/db-task-write-contract.test.ts +4 -1
  188. package/src/tests/docs-index-footprint.test.js +3 -3
  189. package/src/tests/explicit-model-validation.test.ts +332 -0
  190. package/src/tests/harness-model-support.test.ts +374 -0
  191. package/src/tests/heartbeat-checklist-config.test.ts +71 -0
  192. package/src/tests/heartbeat-checklist.test.ts +4 -2
  193. package/src/tests/heartbeat-orphan-resume.test.ts +48 -0
  194. package/src/tests/human-free-flag.test.ts +279 -0
  195. package/src/tests/human-free-oracle.ts +57 -0
  196. package/src/tests/human-free-reclassify.test.ts +669 -0
  197. package/src/tests/migration-046-budgets.test.ts +3 -3
  198. package/src/tests/model-catalog-migration.test.ts +134 -0
  199. package/src/tests/model-catalog-table.test.ts +249 -0
  200. package/src/tests/model-catalog-write-access.test.ts +387 -0
  201. package/src/tests/model-tier-resolution.test.ts +519 -0
  202. package/src/tests/models-catalog.test.ts +1 -1
  203. package/src/tests/multi-runtime-enabled-stats.test.ts +65 -1
  204. package/src/tests/package-publish.test.ts +137 -36
  205. package/src/tests/pricing-routes.test.ts +2 -2
  206. package/src/tests/rbac-engine.test.ts +42 -2
  207. package/src/tests/reasoning-effort.test.ts +109 -0
  208. package/src/tests/refresh-modelsdev-carryforward.test.ts +5 -5
  209. package/src/tests/runtime-model-catalog.test.ts +86 -0
  210. package/src/tests/script-connections.test.ts +5 -0
  211. package/src/tests/seed-scripts.test.ts +89 -0
  212. package/src/tests/session-costs.test.ts +14 -11
  213. package/src/tests/slack-message-text.test.ts +101 -0
  214. package/src/tests/slack-thread-buffer-context.test.ts +93 -0
  215. package/src/tests/task-cascade-fail.test.ts +148 -2
  216. package/src/tests/tool-annotations.test.ts +1 -1
  217. package/src/tests/usage-cache.test.ts +166 -0
  218. package/src/tests/workflow-executors.test.ts +5 -2
  219. package/src/tests/workflow-system-one-decision-review.test.ts +1021 -0
  220. package/src/tests/workflow-system-one-decision-save-warnings.test.ts +365 -0
  221. package/src/tests/workflow-system-one-decision.test.ts +2063 -0
  222. package/src/tools/model-catalog/index.ts +137 -0
  223. package/src/tools/schedules/create-schedule.ts +16 -1
  224. package/src/tools/schedules/patch-schedule.ts +17 -0
  225. package/src/tools/schedules/update-schedule.ts +17 -0
  226. package/src/tools/send-task.ts +14 -0
  227. package/src/tools/task-action.ts +12 -0
  228. package/src/tools/tool-config.ts +3 -1
  229. package/src/tools/workflows/create-workflow.ts +9 -2
  230. package/src/tools/workflows/patch-workflow-node.ts +17 -7
  231. package/src/tools/workflows/patch-workflow.ts +9 -2
  232. package/src/tools/workflows/update-workflow.ts +9 -2
  233. package/src/types.ts +30 -1
  234. package/src/utils/context-window.ts +47 -53
  235. package/src/utils/harness-cli-version.ts +115 -0
  236. package/src/utils/harness-model-error.ts +33 -0
  237. package/src/utils/runtime-model-catalog.ts +137 -0
  238. package/src/workflows/approval-resolution.ts +39 -0
  239. package/src/workflows/definition.ts +7 -0
  240. package/src/workflows/engine.ts +69 -1
  241. package/src/workflows/executors/agent-task.ts +9 -0
  242. package/src/workflows/executors/base.ts +53 -0
  243. package/src/workflows/executors/human-in-the-loop.ts +8 -8
  244. package/src/workflows/executors/index.ts +1 -0
  245. package/src/workflows/executors/registry.ts +2 -0
  246. package/src/workflows/executors/system-one-decision.ts +1232 -0
  247. package/src/workflows/executors/system-one-providers.ts +301 -0
  248. package/src/workflows/executors/workflow-llm.ts +13 -1
  249. package/src/workflows/readiness.ts +86 -0
  250. package/src/workflows/recovery.ts +12 -12
  251. package/src/workflows/resume.ts +23 -9
  252. package/templates/schedules/daily-blocker-digest/content.md +4 -5
  253. package/templates/skills/agent-browser/SKILL.md +6 -5
  254. package/templates/skills/agent-browser/content.md +6 -5
  255. package/templates/skills/heartbeat-runbook/config.json +1 -1
  256. package/templates/skills/heartbeat-runbook/content.md +2 -2
  257. package/templates/skills/swarm-scripts/SKILL.md +1 -1
  258. package/templates/skills/swarm-scripts/content.md +1 -1
  259. package/templates/skills/workflow-iterate/content.md +81 -1
  260. package/templates/workflows/claude-code-changelog-watch/content.md +2 -0
  261. package/templates/workflows/pr-review-status-sweep/content.md +2 -0
  262. package/dist/claude-sdk-session-t442qz6f.js +0 -39108
  263. package/dist/cli-0xjkj1j7.js +0 -61
  264. package/dist/cli-65hjmev5.js +0 -1141
  265. package/dist/cli-aw1jwp1r.js +0 -14075
  266. package/dist/cli-h967nb6r.js +0 -6603
  267. package/dist/cli-vapyk8sw.js +0 -20
  268. package/dist/cli-ws6rqycm.js +0 -247
  269. package/dist/pricing-refresh-v1cbr9te.js +0 -27
  270. package/src/providers/modelsdev-reasoning.json +0 -6453
  271. package/dist/{codex-login-x3jk4etn.js → codex-login-he9vrn90.js} +3 -3
  272. 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
+ }