@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
package/src/be/db/tasks/write.ts
CHANGED
|
@@ -15,7 +15,17 @@ import { scrubSecrets } from "../../../utils/secret-scrubber";
|
|
|
15
15
|
import { emitTaskStarted } from "../../task-lifecycle-events";
|
|
16
16
|
import { getAgentById, isAgentEligibleForTask } from "../agents";
|
|
17
17
|
import { getDbClient } from "../runtime";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
classifyTaskHumanFree,
|
|
20
|
+
reclassifyTaskHumanFree,
|
|
21
|
+
tagWriteChangesHumanFree,
|
|
22
|
+
} from "./human-free";
|
|
23
|
+
import {
|
|
24
|
+
type AgentTaskRow,
|
|
25
|
+
getTaskById,
|
|
26
|
+
NEVER_STARTED_TASK_STATUSES,
|
|
27
|
+
rowToAgentTask,
|
|
28
|
+
} from "./read";
|
|
19
29
|
|
|
20
30
|
type TaskWriteDependencies = {
|
|
21
31
|
createLogEntry: (entry: {
|
|
@@ -68,9 +78,10 @@ export async function createTask(
|
|
|
68
78
|
): Promise<AgentTask> {
|
|
69
79
|
const id = crypto.randomUUID();
|
|
70
80
|
const source = options?.source ?? "mcp";
|
|
81
|
+
const isHumanFree = await classifyTaskHumanFree({ source });
|
|
71
82
|
const row = await getDbClient().get<AgentTaskRow>(
|
|
72
|
-
`INSERT INTO agent_tasks (id, "key", agentId, task, status, source, slackChannelId, slackThreadTs, slackUserId, swarmVersion, createdAt, lastUpdatedAt)
|
|
73
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
|
|
83
|
+
`INSERT INTO agent_tasks (id, "key", agentId, task, status, source, slackChannelId, slackThreadTs, slackUserId, swarmVersion, isHumanFree, createdAt, lastUpdatedAt)
|
|
84
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
|
|
74
85
|
[
|
|
75
86
|
id,
|
|
76
87
|
defaultAssetKey("task", id),
|
|
@@ -82,6 +93,7 @@ export async function createTask(
|
|
|
82
93
|
options?.slackThreadTs ?? null,
|
|
83
94
|
options?.slackUserId ?? null,
|
|
84
95
|
pkg.version,
|
|
96
|
+
isHumanFree ? 1 : 0,
|
|
85
97
|
],
|
|
86
98
|
);
|
|
87
99
|
if (!row) throw new Error("Failed to create task");
|
|
@@ -359,10 +371,17 @@ export async function completeTask(
|
|
|
359
371
|
if (completed && options?.addTags?.length) {
|
|
360
372
|
const existingTags: string[] = completed.tags ? JSON.parse(completed.tags) : [];
|
|
361
373
|
const nextTags = Array.from(new Set([...existingTags, ...options.addTags]));
|
|
374
|
+
const previousTagsJson = completed.tags;
|
|
375
|
+
const nextTagsJson = JSON.stringify(nextTags);
|
|
362
376
|
completed = await getDbClient().get<AgentTaskRow>(
|
|
363
377
|
"UPDATE agent_tasks SET tags = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ? RETURNING *",
|
|
364
|
-
[
|
|
378
|
+
[nextTagsJson, id],
|
|
365
379
|
);
|
|
380
|
+
// Only a tag the classifier reads (e.g. "heartbeat") can flip the flag;
|
|
381
|
+
// a "deferred" write must not walk the task's whole subtree.
|
|
382
|
+
if (tagWriteChangesHumanFree(previousTagsJson, nextTagsJson)) {
|
|
383
|
+
await reclassifyTaskHumanFree([id]);
|
|
384
|
+
}
|
|
366
385
|
}
|
|
367
386
|
if (completed && options?.deferredAt) {
|
|
368
387
|
completed = await getDbClient().get<AgentTaskRow>(
|
|
@@ -752,20 +771,88 @@ export async function supersedeTask(
|
|
|
752
771
|
);
|
|
753
772
|
});
|
|
754
773
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
774
|
+
// Without a resume id the dependents wait: every caller supersedes first,
|
|
775
|
+
// creates the resume, then calls `backfillSupersedeTaskResumeTaskId`, which
|
|
776
|
+
// settles them. Cascading here would fail work the resume is about to carry.
|
|
777
|
+
// A caller whose resume is not created settles with `null`; heartbeat
|
|
778
|
+
// repair settles whatever a crash left in between.
|
|
779
|
+
if (args.resumeTaskId) await settleSupersededTaskDependents(id, args.resumeTaskId);
|
|
760
780
|
}
|
|
761
781
|
|
|
762
782
|
return row ? rowToAgentTask(row) : null;
|
|
763
783
|
}
|
|
764
784
|
|
|
785
|
+
/**
|
|
786
|
+
* Settle the dependents of a superseded task. With a resume id, every
|
|
787
|
+
* never-started dependent has the superseded id in `dependsOn` replaced by the
|
|
788
|
+
* resume id, so it waits on the resume instead of dying with
|
|
789
|
+
* `Blocked dependency … was superseded`. Anything still depending on the
|
|
790
|
+
* superseded task afterwards (a dependent that already started, or every
|
|
791
|
+
* dependent when there is no resume) cascade-fails. Idempotent.
|
|
792
|
+
*/
|
|
793
|
+
export async function settleSupersededTaskDependents(
|
|
794
|
+
supersededId: string,
|
|
795
|
+
resumeTaskId: string | null,
|
|
796
|
+
): Promise<void> {
|
|
797
|
+
try {
|
|
798
|
+
if (resumeTaskId) {
|
|
799
|
+
const placeholders = NEVER_STARTED_TASK_STATUSES.map(() => "?").join(", ");
|
|
800
|
+
// One statement: the new array is built from the row's current
|
|
801
|
+
// `dependsOn`, so settlements of a shared dependent compose, and the
|
|
802
|
+
// status predicate is evaluated at write time, so a claim into
|
|
803
|
+
// `in_progress` is skipped while offered -> reviewing/pending is not.
|
|
804
|
+
const repointed = await getDbClient().query<{ id: string; agentId: string | null }>(
|
|
805
|
+
`UPDATE agent_tasks
|
|
806
|
+
SET dependsOn = (
|
|
807
|
+
SELECT json_group_array(value ORDER BY k) FROM (
|
|
808
|
+
SELECT CASE WHEN j.value = ? THEN ? ELSE j.value END AS value,
|
|
809
|
+
MIN(j.key) AS k
|
|
810
|
+
FROM json_each(agent_tasks.dependsOn) AS j
|
|
811
|
+
GROUP BY 1)),
|
|
812
|
+
lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
813
|
+
WHERE status IN (${placeholders})
|
|
814
|
+
AND EXISTS (SELECT 1 FROM json_each(agent_tasks.dependsOn) WHERE value = ?)
|
|
815
|
+
RETURNING id, agentId`,
|
|
816
|
+
[supersededId, resumeTaskId, ...NEVER_STARTED_TASK_STATUSES, supersededId],
|
|
817
|
+
);
|
|
818
|
+
for (const dep of repointed) {
|
|
819
|
+
try {
|
|
820
|
+
await dependencies.createLogEntry({
|
|
821
|
+
eventType: "task_dependency_repointed",
|
|
822
|
+
taskId: dep.id,
|
|
823
|
+
agentId: dep.agentId ?? undefined,
|
|
824
|
+
oldValue: supersededId,
|
|
825
|
+
newValue: resumeTaskId,
|
|
826
|
+
metadata: { reason: "supersede_resume" },
|
|
827
|
+
});
|
|
828
|
+
} catch {}
|
|
829
|
+
}
|
|
830
|
+
// Repair can settle onto a resume that already failed; its own cascade
|
|
831
|
+
// ran before these dependents pointed at it.
|
|
832
|
+
const resume = await getTaskById(resumeTaskId);
|
|
833
|
+
if (repointed.length > 0 && (resume?.status === "failed" || resume?.status === "cancelled")) {
|
|
834
|
+
await dependencies.cascadeFailDependents(resumeTaskId, resume.status);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
await dependencies.cascadeFailDependents(supersededId, "superseded");
|
|
838
|
+
} catch (err) {
|
|
839
|
+
console.error("[supersedeTask] settling dependents error:", err);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Attach the resume id to the task's `task_superseded` log entry, then settle
|
|
845
|
+
* the dependents `supersedeTask` left waiting (re-point to the resume, cascade
|
|
846
|
+
* the rest).
|
|
847
|
+
*/
|
|
765
848
|
export async function backfillSupersedeTaskResumeTaskId(
|
|
766
849
|
taskId: string,
|
|
767
850
|
resumeTaskId: string,
|
|
768
851
|
): Promise<boolean> {
|
|
852
|
+
if ((await getTaskById(taskId))?.status === "superseded") {
|
|
853
|
+
await settleSupersededTaskDependents(taskId, resumeTaskId);
|
|
854
|
+
}
|
|
855
|
+
|
|
769
856
|
const row = await getDbClient().get<{ id: string; metadata: string | null }>(
|
|
770
857
|
`SELECT id, metadata
|
|
771
858
|
FROM agent_log
|
|
@@ -960,8 +1047,17 @@ export async function getRecentlyCancelledTasksForAgent(agentId: string): Promis
|
|
|
960
1047
|
}
|
|
961
1048
|
|
|
962
1049
|
export async function deleteTask(id: string): Promise<boolean> {
|
|
963
|
-
|
|
964
|
-
|
|
1050
|
+
return await getDbClient().transaction(async (tx) => {
|
|
1051
|
+
// `parentTaskId` has no foreign key, so children survive with a dangling
|
|
1052
|
+
// parent. The human-free rule reads the parent row, so they are reclassified
|
|
1053
|
+
// once it is gone.
|
|
1054
|
+
const childIds = (
|
|
1055
|
+
await tx.query<{ id: string }>("SELECT id FROM agent_tasks WHERE parentTaskId = ?", [id])
|
|
1056
|
+
).map((row) => row.id);
|
|
1057
|
+
const result = await tx.run("DELETE FROM agent_tasks WHERE id = ?", [id]);
|
|
1058
|
+
await reclassifyTaskHumanFree(childIds);
|
|
1059
|
+
return result.changes > 0;
|
|
1060
|
+
});
|
|
965
1061
|
}
|
|
966
1062
|
|
|
967
1063
|
export async function updateTaskProgress(id: string, progress: string): Promise<AgentTask | null> {
|
package/src/be/db.ts
CHANGED
|
@@ -161,6 +161,7 @@ import {
|
|
|
161
161
|
getLatestContextVersion,
|
|
162
162
|
} from "./db/context-versions";
|
|
163
163
|
import { getDb, getDbClient } from "./db/runtime";
|
|
164
|
+
import { classifyTaskHumanFree, reclassifyTaskHumanFree } from "./db/tasks/human-free";
|
|
164
165
|
import {
|
|
165
166
|
type AgentTaskRow,
|
|
166
167
|
configureTaskReadDependencies,
|
|
@@ -187,6 +188,7 @@ export {
|
|
|
187
188
|
getAgentById,
|
|
188
189
|
getAgentDailyTaskCounts,
|
|
189
190
|
getAgentHarnessProviders,
|
|
191
|
+
getAgentStatusCounts,
|
|
190
192
|
getAllAgents,
|
|
191
193
|
getLeadAgent,
|
|
192
194
|
getRemainingCapacity,
|
|
@@ -260,6 +262,7 @@ export {
|
|
|
260
262
|
getRecentlyFinishedWorkerTasks,
|
|
261
263
|
getSlackTasksMissingTree,
|
|
262
264
|
getSupersededTasksWithoutResume,
|
|
265
|
+
getSupersededTasksWithUnsettledDependents,
|
|
263
266
|
getTaskById,
|
|
264
267
|
getTaskStats,
|
|
265
268
|
getTasksByAgentId,
|
|
@@ -291,6 +294,7 @@ export {
|
|
|
291
294
|
pauseTask,
|
|
292
295
|
resetOrphanedInProgressTasksForAgent,
|
|
293
296
|
resumeTask,
|
|
297
|
+
settleSupersededTaskDependents,
|
|
294
298
|
startTask,
|
|
295
299
|
supersedeTask,
|
|
296
300
|
updateTaskClaudeSessionId,
|
|
@@ -2703,6 +2707,16 @@ export async function createTaskExtended(
|
|
|
2703
2707
|
logRoutingDecisionFailure(id, "capture", error);
|
|
2704
2708
|
}
|
|
2705
2709
|
|
|
2710
|
+
const isHumanFree = await classifyTaskHumanFree({
|
|
2711
|
+
taskType: options?.taskType,
|
|
2712
|
+
tags: JSON.stringify(options?.tags ?? []),
|
|
2713
|
+
source: options?.source ?? "mcp",
|
|
2714
|
+
requestedByUserId: options?.requestedByUserId,
|
|
2715
|
+
requestedByUserIdInherited,
|
|
2716
|
+
parentTaskId: options?.parentTaskId,
|
|
2717
|
+
workflowRunId: options?.workflowRunId,
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2706
2720
|
const inserted = await getDbClient().get<AgentTaskRow>(
|
|
2707
2721
|
`INSERT INTO agent_tasks (
|
|
2708
2722
|
id, "key", agentId, creatorAgentId, task, status, source, routing_reason, routing_source, routing_note,
|
|
@@ -2712,8 +2726,8 @@ export async function createTaskExtended(
|
|
|
2712
2726
|
vcsInstallationId, vcsNodeId,
|
|
2713
2727
|
agentmailInboxId, agentmailMessageId, agentmailThreadId,
|
|
2714
2728
|
mentionMessageId, mentionChannelId, dir, parentTaskId, model, modelTier, effort, scheduleId,
|
|
2715
|
-
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, requestedByUserIdInherited, contextKey, routingAffinity, swarmVersion, createdAt, lastUpdatedAt, created_by, updated_by
|
|
2716
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2729
|
+
workflowRunId, workflowRunStepId, outputSchema, followUpConfig, requestedByUserId, requestedByUserIdInherited, contextKey, routingAffinity, swarmVersion, createdAt, lastUpdatedAt, created_by, updated_by, isHumanFree
|
|
2730
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2717
2731
|
[
|
|
2718
2732
|
id,
|
|
2719
2733
|
assetKey,
|
|
@@ -2768,6 +2782,7 @@ export async function createTaskExtended(
|
|
|
2768
2782
|
now,
|
|
2769
2783
|
auditUserId,
|
|
2770
2784
|
auditUserId,
|
|
2785
|
+
isHumanFree ? 1 : 0,
|
|
2771
2786
|
],
|
|
2772
2787
|
);
|
|
2773
2788
|
if (!inserted) throw new Error("Failed to create task");
|
|
@@ -4893,60 +4908,12 @@ export interface SessionCostByUserRow {
|
|
|
4893
4908
|
/** `opts.userId` sentinel selecting spend with no human requester. */
|
|
4894
4909
|
export const UNATTRIBUTED_USER_ID = "unattributed";
|
|
4895
4910
|
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
LEFT JOIN agent_tasks parent ON parent.id = task.parentTaskId
|
|
4903
|
-
WHERE COALESCE(task.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
|
|
4904
|
-
OR COALESCE(task.tags, '[]') LIKE '%"heartbeat"%'
|
|
4905
|
-
OR (COALESCE(task.source, '') = 'schedule' AND task.requestedByUserId IS NULL)
|
|
4906
|
-
OR (
|
|
4907
|
-
task.parentTaskId IS NULL
|
|
4908
|
-
AND COALESCE(task.source, '') = 'workflow'
|
|
4909
|
-
AND task.requestedByUserId IS NULL
|
|
4910
|
-
AND EXISTS (
|
|
4911
|
-
SELECT 1
|
|
4912
|
-
FROM workflow_runs run
|
|
4913
|
-
WHERE run.id = task.workflowRunId
|
|
4914
|
-
AND run.triggerType = 'schedule'
|
|
4915
|
-
AND run.created_by IS NULL
|
|
4916
|
-
)
|
|
4917
|
-
)
|
|
4918
|
-
OR (
|
|
4919
|
-
COALESCE(task.source, '') = 'system'
|
|
4920
|
-
AND parent.id IS NOT NULL
|
|
4921
|
-
AND parent.requestedByUserId IS NULL
|
|
4922
|
-
)
|
|
4923
|
-
|
|
4924
|
-
UNION
|
|
4925
|
-
|
|
4926
|
-
SELECT child.id
|
|
4927
|
-
FROM agent_tasks child
|
|
4928
|
-
JOIN human_free_tasks parent ON child.parentTaskId = parent.id
|
|
4929
|
-
WHERE child.requestedByUserId IS NULL
|
|
4930
|
-
OR child.requestedByUserIdInherited = 1
|
|
4931
|
-
)`;
|
|
4932
|
-
/** True on rows joined through `human_free_tasks hf` (see `getSessionCostSummary`). */
|
|
4933
|
-
const HUMAN_FREE_SQL = "hf.id IS NOT NULL";
|
|
4934
|
-
const ROOT_HUMAN_FREE_SQL = `(
|
|
4935
|
-
COALESCE(t.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
|
|
4936
|
-
OR COALESCE(t.tags, '[]') LIKE '%"heartbeat"%'
|
|
4937
|
-
OR (COALESCE(t.source, '') = 'schedule' AND t.requestedByUserId IS NULL)
|
|
4938
|
-
OR (
|
|
4939
|
-
COALESCE(t.source, '') = 'workflow'
|
|
4940
|
-
AND t.requestedByUserId IS NULL
|
|
4941
|
-
AND EXISTS (
|
|
4942
|
-
SELECT 1
|
|
4943
|
-
FROM workflow_runs run
|
|
4944
|
-
WHERE run.id = t.workflowRunId
|
|
4945
|
-
AND run.triggerType = 'schedule'
|
|
4946
|
-
AND run.created_by IS NULL
|
|
4947
|
-
)
|
|
4948
|
-
)
|
|
4949
|
-
)`;
|
|
4911
|
+
/**
|
|
4912
|
+
* True for a session whose task is structurally human-free (`isHumanFree`, set
|
|
4913
|
+
* when the task is created; rule in `src/be/db/tasks/human-free.ts`). A session
|
|
4914
|
+
* with no task row is not human-free.
|
|
4915
|
+
*/
|
|
4916
|
+
const HUMAN_FREE_SQL = "(COALESCE(t.isHumanFree, 0) = 1)";
|
|
4950
4917
|
|
|
4951
4918
|
export async function getSessionCostSummary(opts: {
|
|
4952
4919
|
startDate?: string;
|
|
@@ -4966,19 +4933,15 @@ export async function getSessionCostSummary(opts: {
|
|
|
4966
4933
|
// re-attributed after the fact, so the human requester is resolved by joining
|
|
4967
4934
|
// through the task (same shape as `getDailySpendForUser`). Every column is
|
|
4968
4935
|
// `sc.`-qualified because `createdAt`/`agentId` exist on both sides.
|
|
4969
|
-
|
|
4970
|
-
//
|
|
4971
|
-
//
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
//
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
// human requester. That classification propagates through descendants while
|
|
4979
|
-
// they remain unattributed, so autonomous fan-out cannot leak back into the
|
|
4980
|
-
// denominator. An explicitly attributed child is an independent handoff and
|
|
4981
|
-
// stops propagation down that branch.
|
|
4936
|
+
// INDEXED BY: without ANALYZE statistics (production has none) the planner
|
|
4937
|
+
// probes tasks through the primary key and reads each full row, which follows
|
|
4938
|
+
// the task's overflow pages. The covering index answers the same probe from
|
|
4939
|
+
// the index alone (migration 182).
|
|
4940
|
+
const from =
|
|
4941
|
+
"FROM session_costs sc LEFT JOIN agent_tasks t INDEXED BY idx_agent_tasks_usage_cover ON t.id = sc.taskId";
|
|
4942
|
+
// Only a user filter reads the task, and the join adds no rows (task id is unique).
|
|
4943
|
+
const agentFrom = opts.userId === undefined ? "FROM session_costs sc" : from;
|
|
4944
|
+
|
|
4982
4945
|
const conditions: string[] = [];
|
|
4983
4946
|
const params: string[] = [];
|
|
4984
4947
|
|
|
@@ -5002,11 +4965,6 @@ export async function getSessionCostSummary(opts: {
|
|
|
5002
4965
|
}
|
|
5003
4966
|
|
|
5004
4967
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5005
|
-
// Only a user filter makes the daily and per-agent breakdowns classify rows.
|
|
5006
|
-
// Without one they skip the CTE, which scans every task.
|
|
5007
|
-
const classify = opts.userId !== undefined;
|
|
5008
|
-
const withHf = classify ? `WITH RECURSIVE ${HUMAN_FREE_TASKS_CTE}` : "";
|
|
5009
|
-
const scopedFrom = classify ? fromHf : from;
|
|
5010
4968
|
const subscriptionSql = `t.credentialKeyType IN (${SUBSCRIPTION_KEY_TYPES.map((k) => `'${k}'`).join(", ")})`;
|
|
5011
4969
|
|
|
5012
4970
|
// Totals
|
|
@@ -5026,8 +4984,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5026
4984
|
};
|
|
5027
4985
|
|
|
5028
4986
|
const totalsRow = await getDbClient().get<TotalsRow>(
|
|
5029
|
-
`
|
|
5030
|
-
SELECT
|
|
4987
|
+
`SELECT
|
|
5031
4988
|
COALESCE(SUM(sc.totalCostUsd), 0) as totalCostUsd,
|
|
5032
4989
|
COALESCE(SUM(sc.inputTokens), 0) as totalInputTokens,
|
|
5033
4990
|
COALESCE(SUM(sc.outputTokens), 0) as totalOutputTokens,
|
|
@@ -5044,7 +5001,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5044
5001
|
COUNT(DISTINCT CASE WHEN ${HUMAN_FREE_SQL} THEN t.id END) as excludedTaskCount,
|
|
5045
5002
|
COALESCE(SUM(CASE WHEN ${subscriptionSql}
|
|
5046
5003
|
THEN sc.totalCostUsd ELSE 0 END), 0) as subscriptionCostUsd
|
|
5047
|
-
${
|
|
5004
|
+
${from} ${where}`,
|
|
5048
5005
|
params,
|
|
5049
5006
|
);
|
|
5050
5007
|
|
|
@@ -5082,8 +5039,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5082
5039
|
sessions: number;
|
|
5083
5040
|
subscriptionCostUsd: number;
|
|
5084
5041
|
}>(
|
|
5085
|
-
|
|
5086
|
-
SELECT
|
|
5042
|
+
`SELECT
|
|
5087
5043
|
DATE(sc.createdAt) as date,
|
|
5088
5044
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
5089
5045
|
COALESCE(SUM(sc.inputTokens), 0) as inputTokens,
|
|
@@ -5091,7 +5047,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5091
5047
|
COUNT(*) as sessions,
|
|
5092
5048
|
COALESCE(SUM(CASE WHEN ${subscriptionSql} THEN sc.totalCostUsd ELSE 0 END), 0)
|
|
5093
5049
|
as subscriptionCostUsd
|
|
5094
|
-
${
|
|
5050
|
+
${from} ${where}
|
|
5095
5051
|
GROUP BY DATE(sc.createdAt)
|
|
5096
5052
|
ORDER BY date ASC`,
|
|
5097
5053
|
params,
|
|
@@ -5109,15 +5065,14 @@ export async function getSessionCostSummary(opts: {
|
|
|
5109
5065
|
sessions: number;
|
|
5110
5066
|
durationMs: number;
|
|
5111
5067
|
}>(
|
|
5112
|
-
|
|
5113
|
-
SELECT
|
|
5068
|
+
`SELECT
|
|
5114
5069
|
sc.agentId as agentId,
|
|
5115
5070
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
5116
5071
|
COALESCE(SUM(sc.inputTokens), 0) as inputTokens,
|
|
5117
5072
|
COALESCE(SUM(sc.outputTokens), 0) as outputTokens,
|
|
5118
5073
|
COUNT(*) as sessions,
|
|
5119
5074
|
COALESCE(SUM(sc.durationMs), 0) as durationMs
|
|
5120
|
-
${
|
|
5075
|
+
${agentFrom} ${where}
|
|
5121
5076
|
GROUP BY sc.agentId
|
|
5122
5077
|
ORDER BY costUsd DESC`,
|
|
5123
5078
|
params,
|
|
@@ -5129,15 +5084,14 @@ export async function getSessionCostSummary(opts: {
|
|
|
5129
5084
|
let byUser: SessionCostByUserRow[] = [];
|
|
5130
5085
|
if (groupBy === "user" || groupBy === "both") {
|
|
5131
5086
|
byUser = await getDbClient().query<SessionCostByUserRow>(
|
|
5132
|
-
`
|
|
5133
|
-
SELECT
|
|
5087
|
+
`SELECT
|
|
5134
5088
|
CASE WHEN ${HUMAN_FREE_SQL} THEN NULL ELSE t.requestedByUserId END as userId,
|
|
5135
5089
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
5136
5090
|
COALESCE(SUM(sc.inputTokens), 0) as inputTokens,
|
|
5137
5091
|
COALESCE(SUM(sc.outputTokens), 0) as outputTokens,
|
|
5138
5092
|
COUNT(DISTINCT sc.taskId) as tasks,
|
|
5139
5093
|
COALESCE(SUM(sc.durationMs), 0) as durationMs
|
|
5140
|
-
${
|
|
5094
|
+
${from} ${where}
|
|
5141
5095
|
GROUP BY CASE WHEN ${HUMAN_FREE_SQL} THEN NULL ELSE t.requestedByUserId END
|
|
5142
5096
|
ORDER BY costUsd DESC`,
|
|
5143
5097
|
params,
|
|
@@ -5151,8 +5105,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5151
5105
|
"name" | "subscription" | "plan" | "planSource"
|
|
5152
5106
|
>;
|
|
5153
5107
|
const rows = await getDbClient().query<CredentialRow>(
|
|
5154
|
-
|
|
5155
|
-
SELECT
|
|
5108
|
+
`SELECT
|
|
5156
5109
|
t.credentialKeyType as keyType,
|
|
5157
5110
|
t.credentialKeySuffix as keySuffix,
|
|
5158
5111
|
COALESCE(SUM(sc.totalCostUsd), 0) as costUsd,
|
|
@@ -5161,7 +5114,7 @@ export async function getSessionCostSummary(opts: {
|
|
|
5161
5114
|
COUNT(*) as sessions,
|
|
5162
5115
|
MIN(sc.createdAt) as firstSessionAt,
|
|
5163
5116
|
MAX(sc.createdAt) as lastSessionAt
|
|
5164
|
-
${
|
|
5117
|
+
${from} ${where}
|
|
5165
5118
|
GROUP BY t.credentialKeyType, t.credentialKeySuffix
|
|
5166
5119
|
ORDER BY costUsd DESC`,
|
|
5167
5120
|
params,
|
|
@@ -5223,18 +5176,6 @@ async function getCredentialLabels(): Promise<
|
|
|
5223
5176
|
return labels;
|
|
5224
5177
|
}
|
|
5225
5178
|
|
|
5226
|
-
/**
|
|
5227
|
-
* Changes when a session cost or a task is inserted. Part of the usage report
|
|
5228
|
-
* cache key (`src/http/usage-cache.ts`). Reads two rowid maxima, so it is cheap.
|
|
5229
|
-
*/
|
|
5230
|
-
export async function getUsageDataVersion(): Promise<string> {
|
|
5231
|
-
const row = await getDbClient().get<{ version: string }>(
|
|
5232
|
-
`SELECT COALESCE((SELECT MAX(rowid) FROM session_costs), 0) || ':' ||
|
|
5233
|
-
COALESCE((SELECT MAX(rowid) FROM agent_tasks), 0) as version`,
|
|
5234
|
-
);
|
|
5235
|
-
return row?.version ?? "0:0";
|
|
5236
|
-
}
|
|
5237
|
-
|
|
5238
5179
|
// --- Per-person attribution (four-metric view) ---
|
|
5239
5180
|
|
|
5240
5181
|
export interface AttributionByPersonRow {
|
|
@@ -5265,7 +5206,7 @@ export interface AttributionByPersonRow {
|
|
|
5265
5206
|
* a composite score. Scope is root tasks (`problemsInitiated`/`problemsShipped`)
|
|
5266
5207
|
* or the person's entire task tree (`*Reached`); `requestedByUserId IS NULL`
|
|
5267
5208
|
* (autonomous work) and structurally-human-free rows with a stale/inherited
|
|
5268
|
-
* requester (
|
|
5209
|
+
* requester (`agent_tasks.isHumanFree`, see `src/be/db/tasks/human-free.ts`) are excluded —
|
|
5269
5210
|
* neither belongs to a person.
|
|
5270
5211
|
*
|
|
5271
5212
|
* "Problems shipped" detection walks each root's full task tree, preferring a
|
|
@@ -5309,18 +5250,20 @@ export async function getAttributionByPerson(opts: {
|
|
|
5309
5250
|
|
|
5310
5251
|
type RootRow = { userId: string; initiated: number; shipped: number };
|
|
5311
5252
|
const rootRows = await getDbClient().query<RootRow>(
|
|
5312
|
-
`WITH RECURSIVE selected_roots(id, requestedByUserId, status
|
|
5313
|
-
SELECT t.id, t.requestedByUserId, t.status
|
|
5253
|
+
`WITH RECURSIVE selected_roots(id, requestedByUserId, status) AS (
|
|
5254
|
+
SELECT t.id, t.requestedByUserId, t.status
|
|
5314
5255
|
FROM agent_tasks t
|
|
5315
|
-
${where} AND t.parentTaskId IS NULL AND
|
|
5256
|
+
${where} AND t.parentTaskId IS NULL AND t.isHumanFree = 0
|
|
5316
5257
|
),
|
|
5317
|
-
|
|
5318
|
-
|
|
5258
|
+
-- Ids only: carrying each descendant's \`output\` through the recursion copied
|
|
5259
|
+
-- every task's text into the queue. The output fallback below reads it by id.
|
|
5260
|
+
task_tree(rootId, taskId) AS (
|
|
5261
|
+
SELECT id, id
|
|
5319
5262
|
FROM selected_roots
|
|
5320
5263
|
|
|
5321
5264
|
UNION ALL
|
|
5322
5265
|
|
|
5323
|
-
SELECT tree.rootId, child.id
|
|
5266
|
+
SELECT tree.rootId, child.id
|
|
5324
5267
|
FROM agent_tasks child
|
|
5325
5268
|
JOIN task_tree tree ON child.parentTaskId = tree.taskId
|
|
5326
5269
|
),
|
|
@@ -5353,8 +5296,11 @@ export async function getAttributionByPerson(opts: {
|
|
|
5353
5296
|
|
|
5354
5297
|
SELECT tree.rootId
|
|
5355
5298
|
FROM task_tree tree
|
|
5356
|
-
|
|
5357
|
-
|
|
5299
|
+
-- CROSS JOIN pins the order (tree outer, task by id). Left to itself the
|
|
5300
|
+
-- planner scans every task's output and probes the tree instead.
|
|
5301
|
+
CROSS JOIN agent_tasks described ON described.id = tree.taskId
|
|
5302
|
+
WHERE described.output LIKE '%github.com/%/pull/%'
|
|
5303
|
+
OR described.output LIKE '%/-/merge_requests/%'
|
|
5358
5304
|
)
|
|
5359
5305
|
SELECT
|
|
5360
5306
|
t.requestedByUserId as userId,
|
|
@@ -5373,65 +5319,13 @@ export async function getAttributionByPerson(opts: {
|
|
|
5373
5319
|
surfacesReached: number;
|
|
5374
5320
|
};
|
|
5375
5321
|
const reachRows = await getDbClient().query<ReachRow>(
|
|
5376
|
-
`
|
|
5377
|
-
-- Only the columns read below: a task row carries its full prompt and output.
|
|
5378
|
-
SELECT t.id, t.agentId, t.vcsRepo, t.source, t.parentTaskId, t.requestedByUserId,
|
|
5379
|
-
t.requestedByUserIdInherited, t.taskType, t.tags, t.workflowRunId
|
|
5380
|
-
FROM agent_tasks t
|
|
5381
|
-
${where}
|
|
5382
|
-
),
|
|
5383
|
-
task_ancestry(
|
|
5384
|
-
taskId, id, parentTaskId, requestedByUserId, requestedByUserIdInherited,
|
|
5385
|
-
taskType, tags, source, workflowRunId
|
|
5386
|
-
) AS (
|
|
5387
|
-
SELECT
|
|
5388
|
-
id, id, parentTaskId, requestedByUserId, requestedByUserIdInherited,
|
|
5389
|
-
taskType, tags, source, workflowRunId
|
|
5390
|
-
FROM report_tasks
|
|
5391
|
-
|
|
5392
|
-
UNION ALL
|
|
5393
|
-
|
|
5394
|
-
SELECT
|
|
5395
|
-
child.taskId, parent.id, parent.parentTaskId, parent.requestedByUserId,
|
|
5396
|
-
parent.requestedByUserIdInherited, parent.taskType, parent.tags,
|
|
5397
|
-
parent.source, parent.workflowRunId
|
|
5398
|
-
FROM agent_tasks parent
|
|
5399
|
-
JOIN task_ancestry child ON parent.id = child.parentTaskId
|
|
5400
|
-
WHERE child.requestedByUserId IS NULL
|
|
5401
|
-
OR child.requestedByUserIdInherited = 1
|
|
5402
|
-
),
|
|
5403
|
-
human_free_report_tasks(id) AS (
|
|
5404
|
-
SELECT DISTINCT ancestor.taskId
|
|
5405
|
-
FROM task_ancestry ancestor
|
|
5406
|
-
LEFT JOIN agent_tasks parent ON parent.id = ancestor.parentTaskId
|
|
5407
|
-
WHERE COALESCE(ancestor.taskType, '') IN ('heartbeat', 'heartbeat-checklist', 'boot-triage')
|
|
5408
|
-
OR COALESCE(ancestor.tags, '[]') LIKE '%"heartbeat"%'
|
|
5409
|
-
OR (COALESCE(ancestor.source, '') = 'schedule' AND ancestor.requestedByUserId IS NULL)
|
|
5410
|
-
OR (
|
|
5411
|
-
ancestor.parentTaskId IS NULL
|
|
5412
|
-
AND COALESCE(ancestor.source, '') = 'workflow'
|
|
5413
|
-
AND ancestor.requestedByUserId IS NULL
|
|
5414
|
-
AND EXISTS (
|
|
5415
|
-
SELECT 1
|
|
5416
|
-
FROM workflow_runs run
|
|
5417
|
-
WHERE run.id = ancestor.workflowRunId
|
|
5418
|
-
AND run.triggerType = 'schedule'
|
|
5419
|
-
AND run.created_by IS NULL
|
|
5420
|
-
)
|
|
5421
|
-
)
|
|
5422
|
-
OR (
|
|
5423
|
-
COALESCE(ancestor.source, '') = 'system'
|
|
5424
|
-
AND parent.id IS NOT NULL
|
|
5425
|
-
AND parent.requestedByUserId IS NULL
|
|
5426
|
-
)
|
|
5427
|
-
)
|
|
5428
|
-
SELECT
|
|
5322
|
+
`SELECT
|
|
5429
5323
|
t.requestedByUserId as userId,
|
|
5430
5324
|
COUNT(DISTINCT t.agentId) as agentsReached,
|
|
5431
5325
|
COUNT(DISTINCT t.vcsRepo) as reposReached,
|
|
5432
5326
|
COUNT(DISTINCT t.source) as surfacesReached
|
|
5433
|
-
FROM
|
|
5434
|
-
|
|
5327
|
+
FROM agent_tasks t
|
|
5328
|
+
${where} AND t.isHumanFree = 0
|
|
5435
5329
|
GROUP BY t.requestedByUserId`,
|
|
5436
5330
|
params,
|
|
5437
5331
|
);
|
|
@@ -7596,7 +7490,19 @@ export async function updateWorkflow(
|
|
|
7596
7490
|
}
|
|
7597
7491
|
|
|
7598
7492
|
export async function deleteWorkflow(id: string, source?: "api" | "mcp"): Promise<boolean> {
|
|
7493
|
+
// One transaction: the stored human-free flags must never lag the deleted runs
|
|
7494
|
+
// (a scheduled workflow root is human-free only while its run row exists).
|
|
7495
|
+
return await getDbClient().transaction(() => deleteWorkflowRows(id, source));
|
|
7496
|
+
}
|
|
7497
|
+
|
|
7498
|
+
async function deleteWorkflowRows(id: string, source?: "api" | "mcp"): Promise<boolean> {
|
|
7599
7499
|
const client = getDbClient();
|
|
7500
|
+
const linkedTaskIds = (
|
|
7501
|
+
await client.query<{ id: string }>(
|
|
7502
|
+
`SELECT id FROM agent_tasks WHERE workflowRunId IN (SELECT id FROM workflow_runs WHERE workflowId = ?)`,
|
|
7503
|
+
[id],
|
|
7504
|
+
)
|
|
7505
|
+
).map((row) => row.id);
|
|
7600
7506
|
// Cascade delete in FK-safe order:
|
|
7601
7507
|
// 1. Unlink agent_tasks (they reference steps and runs)
|
|
7602
7508
|
await client.run(
|
|
@@ -7610,6 +7516,7 @@ export async function deleteWorkflow(id: string, source?: "api" | "mcp"): Promis
|
|
|
7610
7516
|
);
|
|
7611
7517
|
// 3. Delete runs (they reference workflow)
|
|
7612
7518
|
await client.run("DELETE FROM workflow_runs WHERE workflowId = ?", [id]);
|
|
7519
|
+
await reclassifyTaskHumanFree(linkedTaskIds);
|
|
7613
7520
|
// 4. Delete workflow
|
|
7614
7521
|
const result = await client.run("DELETE FROM workflows WHERE id = ?", [id]);
|
|
7615
7522
|
const deleted = result.changes > 0;
|
|
@@ -12112,6 +12019,21 @@ export async function deleteUser(id: string, replacementUserId?: string): Promis
|
|
|
12112
12019
|
)`,
|
|
12113
12020
|
);
|
|
12114
12021
|
const replacement = replacementUserId ?? null;
|
|
12022
|
+
// The human-free rule only asks whether a task's requester or its workflow
|
|
12023
|
+
// run's creator is NULL, so only clearing them (no replacement) can change a
|
|
12024
|
+
// stored flag. Gather the tasks now: after the rewrite nothing identifies
|
|
12025
|
+
// them. The workflow branch covers roots whose requester was never set.
|
|
12026
|
+
const reclassifySeedIds: string[] = replacementUserId
|
|
12027
|
+
? []
|
|
12028
|
+
: (
|
|
12029
|
+
await tx.query<{ id: string }>(
|
|
12030
|
+
`SELECT id FROM agent_tasks WHERE requestedByUserId = ?
|
|
12031
|
+
UNION
|
|
12032
|
+
SELECT id FROM agent_tasks
|
|
12033
|
+
WHERE workflowRunId IN (SELECT id FROM workflow_runs WHERE created_by = ?)`,
|
|
12034
|
+
[id, id],
|
|
12035
|
+
)
|
|
12036
|
+
).map((row) => row.id);
|
|
12115
12037
|
for (const reference of references) {
|
|
12116
12038
|
const table = quoteSqlIdentifier(reference.tableName);
|
|
12117
12039
|
const column = quoteSqlIdentifier(reference.columnName);
|
|
@@ -12139,6 +12061,8 @@ export async function deleteUser(id: string, replacementUserId?: string): Promis
|
|
|
12139
12061
|
);
|
|
12140
12062
|
}
|
|
12141
12063
|
|
|
12064
|
+
await reclassifyTaskHumanFree(reclassifySeedIds);
|
|
12065
|
+
|
|
12142
12066
|
const result = await tx.run("DELETE FROM users WHERE id = ?", [id]);
|
|
12143
12067
|
return result.changes > 0;
|
|
12144
12068
|
});
|