@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/commands/runner.ts
CHANGED
|
@@ -36,7 +36,9 @@ import {
|
|
|
36
36
|
import type { SteerDeliveryResult } from "../providers/types.ts";
|
|
37
37
|
import { initTelemetry, telemetry } from "../telemetry.ts";
|
|
38
38
|
import {
|
|
39
|
+
type ModelTierOverrides,
|
|
39
40
|
type ProviderName,
|
|
41
|
+
parseWorkerModelTierOverrides,
|
|
40
42
|
type ReasoningEffort,
|
|
41
43
|
type RepoGuidelines,
|
|
42
44
|
resolveTaskModelSelection,
|
|
@@ -55,9 +57,11 @@ import {
|
|
|
55
57
|
type RateLimitWindowTelemetry,
|
|
56
58
|
resolveCodexCreditsExhaustedCooldownMs,
|
|
57
59
|
} from "../utils/error-tracker.ts";
|
|
60
|
+
import { probeHarnessCliVersion, reportHarnessModelOutcome } from "../utils/harness-cli-version.ts";
|
|
58
61
|
import { resolveHarnessProvider } from "../utils/harness-provider.ts";
|
|
59
62
|
import { prettyPrintLine, prettyPrintStderr } from "../utils/pretty-print.ts";
|
|
60
63
|
import { terminateRegisteredProcessGroups } from "../utils/process-group.ts";
|
|
64
|
+
import { refreshRuntimeModelCatalog } from "../utils/runtime-model-catalog.ts";
|
|
61
65
|
import { resolveScriptsOnlyMode } from "../utils/scripts-only-mode.ts";
|
|
62
66
|
import { scrubSecrets } from "../utils/secret-scrubber.ts";
|
|
63
67
|
import { refreshSkillsIfChanged } from "../utils/skills-refresh.ts";
|
|
@@ -1220,6 +1224,8 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
|
|
|
1220
1224
|
"set-config": "⚙️ Setting config",
|
|
1221
1225
|
"list-config": "⚙️ Listing config",
|
|
1222
1226
|
"delete-config": "⚙️ Deleting config",
|
|
1227
|
+
"model-catalog-refresh": "🧠 Refreshing model catalog",
|
|
1228
|
+
"model-catalog-overlay-upsert": "🧠 Updating model catalog overlay",
|
|
1223
1229
|
// Schedules
|
|
1224
1230
|
"create-schedule": "📅 Creating schedule",
|
|
1225
1231
|
"list-schedules": "📅 Listing schedules",
|
|
@@ -2909,6 +2915,16 @@ interface PollOptions {
|
|
|
2909
2915
|
pollInterval: number;
|
|
2910
2916
|
pollTimeout: number;
|
|
2911
2917
|
since?: string; // Optional: for filtering finished tasks
|
|
2918
|
+
/** Live harness provider; keys the MODEL_TIER_* overrides sent with the poll. */
|
|
2919
|
+
harnessProvider?: ProviderName;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
/**
|
|
2923
|
+
* The worker's own MODEL_TIER_* overrides, parsed from its process env (not
|
|
2924
|
+
* the swarm_config-merged env: global config is already visible server-side).
|
|
2925
|
+
*/
|
|
2926
|
+
function workerModelTierOverrides(provider: ProviderName): ModelTierOverrides {
|
|
2927
|
+
return parseWorkerModelTierOverrides(process.env, provider);
|
|
2912
2928
|
}
|
|
2913
2929
|
|
|
2914
2930
|
type RequesterProfile = NonNullable<Trigger["requestedBy"]>;
|
|
@@ -2990,6 +3006,8 @@ export async function registerAgent(opts: {
|
|
|
2990
3006
|
provider,
|
|
2991
3007
|
harness_provider: harnessProvider,
|
|
2992
3008
|
runtimeInstanceId: opts.runtimeInstanceId,
|
|
3009
|
+
modelTierOverrides: workerModelTierOverrides(harnessProvider),
|
|
3010
|
+
harnessCliVersion: (await probeHarnessCliVersion(harnessProvider)) ?? undefined,
|
|
2993
3011
|
}),
|
|
2994
3012
|
});
|
|
2995
3013
|
|
|
@@ -3035,6 +3053,14 @@ async function pollForTrigger(opts: PollOptions): Promise<Trigger | null> {
|
|
|
3035
3053
|
|
|
3036
3054
|
async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
|
|
3037
3055
|
const startTime = Date.now();
|
|
3056
|
+
// Keep the process-wide model catalog fresh (TTL-cached, never throws) so
|
|
3057
|
+
// context windows, pricing and reasoning levels cover models added to the
|
|
3058
|
+
// API's catalog after this worker booted.
|
|
3059
|
+
void refreshRuntimeModelCatalog({
|
|
3060
|
+
apiUrl: opts.apiUrl,
|
|
3061
|
+
apiKey: opts.apiKey,
|
|
3062
|
+
agentId: opts.agentId,
|
|
3063
|
+
});
|
|
3038
3064
|
const headers: Record<string, string> = {
|
|
3039
3065
|
"X-Agent-ID": opts.agentId,
|
|
3040
3066
|
};
|
|
@@ -3045,6 +3071,11 @@ async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
|
|
|
3045
3071
|
headers.Authorization = `Bearer ${opts.apiKey}`;
|
|
3046
3072
|
}
|
|
3047
3073
|
injectTraceContext(headers);
|
|
3074
|
+
if (opts.harnessProvider) {
|
|
3075
|
+
headers["X-Model-Tier-Overrides"] = encodeURIComponent(
|
|
3076
|
+
JSON.stringify(workerModelTierOverrides(opts.harnessProvider)),
|
|
3077
|
+
);
|
|
3078
|
+
}
|
|
3048
3079
|
|
|
3049
3080
|
while (Date.now() - startTime < opts.pollTimeout) {
|
|
3050
3081
|
try {
|
|
@@ -3527,6 +3558,9 @@ async function spawnProviderProcess(
|
|
|
3527
3558
|
taskId?: string;
|
|
3528
3559
|
model?: string;
|
|
3529
3560
|
modelTier?: string;
|
|
3561
|
+
/** Server claim-time resolution (task.resolvedModel); wins over the local one. */
|
|
3562
|
+
resolvedModel?: string;
|
|
3563
|
+
modelSource?: string;
|
|
3530
3564
|
effort?: ReasoningEffort;
|
|
3531
3565
|
resumeSessionId?: string;
|
|
3532
3566
|
harnessProvider: ProviderName;
|
|
@@ -3562,7 +3596,7 @@ async function spawnProviderProcess(
|
|
|
3562
3596
|
opts.apiKey,
|
|
3563
3597
|
opts.agentId,
|
|
3564
3598
|
process.env,
|
|
3565
|
-
opts.model,
|
|
3599
|
+
opts.resolvedModel || opts.model,
|
|
3566
3600
|
{
|
|
3567
3601
|
repoId: sessionRepo?.id,
|
|
3568
3602
|
provider: adapter.name as ProviderName,
|
|
@@ -3619,7 +3653,20 @@ async function spawnProviderProcess(
|
|
|
3619
3653
|
harnessProvider: opts.harnessProvider,
|
|
3620
3654
|
env: freshEnv,
|
|
3621
3655
|
});
|
|
3622
|
-
|
|
3656
|
+
// The server resolves the model at claim time (worker-env > tier-config >
|
|
3657
|
+
// tier-default, `latest:` aliases, guardrails) and records it on the task.
|
|
3658
|
+
// The local resolution stays as a check: a difference usually means the
|
|
3659
|
+
// server saw a stale MODEL_TIER_* override or a swarm_config tier value.
|
|
3660
|
+
if (
|
|
3661
|
+
opts.resolvedModel &&
|
|
3662
|
+
taskModelSelection.model &&
|
|
3663
|
+
taskModelSelection.model !== opts.resolvedModel
|
|
3664
|
+
) {
|
|
3665
|
+
console.log(
|
|
3666
|
+
`[${opts.role}] model resolution mismatch for task ${opts.taskId ?? "?"}: server=${opts.resolvedModel} (${opts.modelSource ?? "?"}) local=${taskModelSelection.model}; using server value`,
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3669
|
+
const taskModel = opts.resolvedModel || taskModelSelection.model || "";
|
|
3623
3670
|
const model = taskModel || configModel || "";
|
|
3624
3671
|
|
|
3625
3672
|
// Resolve Codex OAuth pool slot BEFORE building ProviderSessionConfig so we
|
|
@@ -4517,6 +4564,17 @@ async function checkCompletedProcesses(
|
|
|
4517
4564
|
console.log(`[${role}] Detected error for task ${taskId.slice(0, 8)}: ${failureReason}`);
|
|
4518
4565
|
}
|
|
4519
4566
|
|
|
4567
|
+
// Record whether this worker's CLI accepts the model (harness_model_support).
|
|
4568
|
+
void reportHarnessModelOutcome({
|
|
4569
|
+
apiUrl: apiConfig.apiUrl,
|
|
4570
|
+
apiKey: apiConfig.apiKey,
|
|
4571
|
+
agentId: apiConfig.agentId,
|
|
4572
|
+
harness: harnessProvider,
|
|
4573
|
+
model,
|
|
4574
|
+
exitCode: result.exitCode,
|
|
4575
|
+
failureReason,
|
|
4576
|
+
});
|
|
4577
|
+
|
|
4520
4578
|
// If rate-limited and we know which key was used, report it.
|
|
4521
4579
|
// Codex adapter prefixes failure reasons with `[rate-limit]` /
|
|
4522
4580
|
// `[usage-limit]` (see codex-adapter.formatTerminalError); Claude
|
|
@@ -5911,6 +5969,8 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
5911
5969
|
taskId: task.id,
|
|
5912
5970
|
model: (task as { model?: string }).model,
|
|
5913
5971
|
modelTier: (task as { modelTier?: string }).modelTier,
|
|
5972
|
+
resolvedModel: (task as { resolvedModel?: string }).resolvedModel,
|
|
5973
|
+
modelSource: (task as { modelSource?: string }).modelSource,
|
|
5914
5974
|
effort: (task as { effort?: ReasoningEffort }).effort,
|
|
5915
5975
|
harnessProvider: state.harnessProvider,
|
|
5916
5976
|
cwd: resumeCwd,
|
|
@@ -6127,6 +6187,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6127
6187
|
pollInterval: PollIntervalMs,
|
|
6128
6188
|
runtimeInstanceId,
|
|
6129
6189
|
pollTimeout: effectiveTimeout,
|
|
6190
|
+
harnessProvider: state.harnessProvider,
|
|
6130
6191
|
});
|
|
6131
6192
|
|
|
6132
6193
|
if (trigger) {
|
|
@@ -6407,6 +6468,16 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6407
6468
|
.join("\n\n");
|
|
6408
6469
|
const taskSystemPrompt = taskPromptParts + cwdWarning;
|
|
6409
6470
|
|
|
6471
|
+
// The server refused the task's explicit model for this worker's CLI
|
|
6472
|
+
// version (harness_model_support = unsupported): fail fast, no spawn.
|
|
6473
|
+
const modelUnsupported = (trigger.task as { modelUnsupported?: string } | undefined)
|
|
6474
|
+
?.modelUnsupported;
|
|
6475
|
+
if (trigger.taskId && modelUnsupported) {
|
|
6476
|
+
console.log(`[${role}] ${modelUnsupported}`);
|
|
6477
|
+
await ensureTaskFinished(apiConfig, role, trigger.taskId, 1, modelUnsupported);
|
|
6478
|
+
continue;
|
|
6479
|
+
}
|
|
6480
|
+
|
|
6410
6481
|
iteration++;
|
|
6411
6482
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
6412
6483
|
const taskIdSlice = trigger.taskId?.slice(0, 8) || "notask";
|
|
@@ -6472,6 +6543,9 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6472
6543
|
taskId: trigger.taskId,
|
|
6473
6544
|
model: taskModel,
|
|
6474
6545
|
modelTier: taskModelTier,
|
|
6546
|
+
resolvedModel: (trigger.task as { resolvedModel?: string } | undefined)
|
|
6547
|
+
?.resolvedModel,
|
|
6548
|
+
modelSource: (trigger.task as { modelSource?: string } | undefined)?.modelSource,
|
|
6475
6549
|
effort: taskEffort,
|
|
6476
6550
|
harnessProvider: state.harnessProvider,
|
|
6477
6551
|
cwd: effectiveCwd,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Generated by scripts/bundle-extension-types.ts. Do not edit.
|
|
2
2
|
// biome-ignore-all lint/suspicious/noTemplateCurlyInString: Type declarations contain literal template placeholders.
|
|
3
3
|
export const EXTENSION_TYPE_DEFINITIONS =
|
|
4
|
-
'// biome-ignore-all lint/suspicious/noConfusingVoidType: Extension handlers may explicitly return no result.\ndeclare module "swarm-extension" {\n export type CreateTaskOptions = {\n key?: string | undefined;\n agentId?: string | null | undefined;\n creatorAgentId?: string | undefined;\n source?:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "system"\n | "linear"\n | "jira"\n | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n taskType?: string | undefined;\n tags?: string[] | undefined;\n priority?: number | undefined;\n dependsOn?: string[] | undefined;\n offeredTo?: string | undefined;\n status?: "draft" | "backlog" | "unassigned" | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n overrideSlackContext?: boolean | undefined;\n vcsProvider?: "github" | "gitlab" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | undefined;\n workflowRunStepId?: string | undefined;\n sourceTaskId?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n inheritParentOutputSchema?: boolean | undefined;\n inheritParentRoutingAffinity?: boolean | undefined;\n inheritParentFollowUpConfig?: boolean | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n requestedByUserId?: string | undefined;\n contextKey?: string | undefined;\n bypassTrackerContextDedup?: boolean | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n };\n\n export type AgentTask = {\n id: string;\n key: string;\n agentId: string | null;\n task: string;\n status:\n | "draft"\n | "backlog"\n | "unassigned"\n | "offered"\n | "reviewing"\n | "pending"\n | "in_progress"\n | "paused"\n | "completed"\n | "failed"\n | "cancelled"\n | "superseded";\n source:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "system"\n | "linear"\n | "jira";\n tags: string[];\n priority: number;\n dependsOn: string[];\n createdAt: string;\n lastUpdatedAt: string;\n slackReplySent: boolean;\n wasPaused: boolean;\n creatorAgentId?: string | undefined;\n title?: string | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n taskType?: string | undefined;\n offeredTo?: string | undefined;\n offeredAt?: string | undefined;\n acceptedAt?: string | undefined;\n rejectionReason?: string | undefined;\n finishedAt?: string | undefined;\n notifiedAt?: string | undefined;\n failureReason?: string | undefined;\n output?: string | undefined;\n progress?: string | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n slackProgressMessageTs?: string | undefined;\n slackTreeRootMessageTs?: string | undefined;\n vcsProvider?: "github" | "gitlab" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n claudeSessionId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | null | undefined;\n workflowRunStepId?: string | null | undefined;\n contextKey?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n deferredAt?: string | undefined;\n compactionCount?: number | undefined;\n peakContextPercent?: number | undefined;\n peakContextTokens?: number | undefined;\n contextWindowSize?: number | undefined;\n credentialKeySuffix?: string | undefined;\n credentialKeyType?: string | undefined;\n requestedByUserId?: string | undefined;\n swarmVersion?: string | undefined;\n provider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | undefined;\n providerMeta?: Record<string, unknown> | undefined;\n harnessVariant?: string | undefined;\n harnessVariantMeta?: Record<string, unknown> | undefined;\n totalCostUsd?: number | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n routingAffinityInvalid?: boolean | undefined;\n };\n\n export type ActiveSession = {\n id: string;\n agentId: string;\n taskId: string | null;\n triggerType: string;\n inboxMessageId: string | null;\n taskDescription: string | null;\n runnerSessionId: string | null;\n providerSessionId: string | null;\n startedAt: string;\n lastHeartbeatAt: string;\n runtimeInstanceId?: string | null | undefined;\n };\n\n export type RequestInfo = {\n sessionId: string | undefined;\n agentId: string | undefined;\n runtimeInstanceId: string | undefined;\n sourceTaskId: string | undefined;\n contextKey: string | undefined;\n callOrigin: "mcp" | "extension" | "script-sdk";\n };\n\n import type { SwarmSdk } from "swarm-sdk";\n import type { z } from "zod";\n export type Runtime = "api" | "worker";\n export interface ExtensionScriptAsset {\n name: string;\n file: string;\n description: string;\n intent?: string;\n }\n export interface ExtensionScheduleAsset {\n name: string;\n description?: string;\n script: string;\n cronExpression?: string;\n intervalMs?: number;\n timezone?: string;\n args?: Record<string, unknown>;\n }\n export interface ExtensionWorkflowAsset {\n file: string;\n }\n export interface ExtensionSkillAsset {\n dir: string;\n }\n export interface ExtensionManifest {\n $schema?: string;\n name: string;\n description: string;\n version: string;\n runtime: Runtime;\n assets: {\n hooks: string;\n scripts?: readonly ExtensionScriptAsset[];\n schedules?: readonly ExtensionScheduleAsset[];\n workflows?: readonly ExtensionWorkflowAsset[];\n skills?: readonly ExtensionSkillAsset[];\n };\n homepage?: string;\n author?: string;\n }\n export interface HooksModule {\n default: SwarmExtension;\n config?: z.ZodTypeAny;\n }\n export interface ExtensionState {\n get<T = unknown>(key: string): Promise<T | null>;\n set<T>(key: string, value: T): Promise<void>;\n incr(key: string, by?: number): Promise<number>;\n del(key: string): Promise<void>;\n }\n export interface ExtensionLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n }\n type ConfigFor<M extends ExtensionManifest> = M extends {\n config: infer C extends z.ZodTypeAny;\n }\n ? z.infer<C>\n : Record<string, unknown>;\n export interface ApiCtx<Config = Record<string, unknown>> {\n swarm: SwarmSdk;\n state: ExtensionState;\n config: Config;\n log: ExtensionLogger;\n signal: AbortSignal;\n event: {\n name: keyof SwarmEventMap;\n at: string;\n extension: {\n id: string;\n version: number;\n };\n };\n }\n export interface WorkerCtx<Config = Record<string, unknown>>\n extends Omit<ApiCtx<Config>, "swarm"> {\n swarm: SwarmSdk;\n worker: {\n agentId: string;\n taskId: string;\n harness: string;\n };\n }\n export type CtxFor<M extends ExtensionManifest> = M["runtime"] extends "api"\n ? ApiCtx<ConfigFor<M>>\n : WorkerCtx<ConfigFor<M>>;\n export type PreResult<Modify> =\n | void\n | {\n action: "continue";\n }\n | {\n action: "modify";\n data: Modify;\n }\n | {\n action: "block";\n reason: string;\n };\n export type TaskCreateOrigin =\n | "rest"\n | "app"\n | "mcp"\n | "slack"\n | "schedule"\n | "workflow"\n | "webhook"\n | "followUp"\n | `extension:${string}`;\n export interface TaskCreateEvent {\n options: CreateTaskOptions;\n description: string;\n origin: TaskCreateOrigin;\n requestInfo?: RequestInfo;\n }\n export type TaskCreateModify = Partial<\n Pick<\n CreateTaskOptions,\n | "agentId"\n | "creatorAgentId"\n | "source"\n | "taskType"\n | "tags"\n | "priority"\n | "dependsOn"\n | "offeredTo"\n | "vcsProvider"\n | "vcsRepo"\n | "vcsEventType"\n | "vcsNumber"\n | "vcsCommentId"\n | "vcsAuthor"\n | "vcsUrl"\n | "vcsInstallationId"\n | "vcsNodeId"\n | "agentmailInboxId"\n | "agentmailMessageId"\n | "agentmailThreadId"\n | "mentionMessageId"\n | "mentionChannelId"\n | "dir"\n | "model"\n | "modelTier"\n | "effort"\n | "outputSchema"\n | "followUpConfig"\n | "bypassTrackerContextDedup"\n >\n > & {\n description?: string;\n };\n export interface TaskFollowUpEvent {\n completedTask: AgentTask;\n status: "completed" | "failed";\n output?: string;\n failureReason?: string;\n workerAgentId: string;\n leadAgentId: string;\n summary: string;\n }\n export type TaskFollowUpModify = Partial<\n Pick<CreateTaskOptions, "agentId" | "priority" | "followUpConfig">\n > & {\n description?: string;\n };\n export interface SlackRouteEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n botMentioned: boolean;\n threadContext?: {\n channelId: string;\n threadTs: string;\n };\n }\n export interface SlackRouteModify {\n target:\n | {\n kind: "agent";\n agentId: string;\n }\n | {\n kind: "lead";\n }\n | {\n kind: "broadcast";\n };\n }\n export type HeartbeatClassification = "no-session" | "stale-session" | "fresh-stalled";\n export type HeartbeatAction = "supersede-resume" | "fail" | "record";\n export interface HeartbeatRemediateEvent {\n task: AgentTask;\n session?: ActiveSession;\n classification: HeartbeatClassification;\n proposedAction: HeartbeatAction;\n reason: string;\n taskAgeMs: number;\n sessionHeartbeatAgeMs?: number;\n }\n export interface HeartbeatRemediateModify {\n proposedAction: HeartbeatAction;\n }\n export interface ToolCallEvent {\n tool: string;\n args: unknown;\n requestInfo: RequestInfo;\n }\n export interface ToolCallModify {\n args: unknown;\n }\n export interface TaskEvent {\n task: AgentTask;\n }\n export interface TaskCompletedEvent extends TaskEvent {\n output: string;\n }\n export interface TaskFailedEvent extends TaskEvent {\n failureReason: string;\n }\n export interface TaskSupersededEvent extends TaskEvent {\n supersededBy: string;\n }\n export interface TaskProgressEvent extends TaskEvent {\n progress: string;\n }\n export interface SlackMessageEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n taskId?: string;\n }\n export interface ToolCallCompletedEvent extends ToolCallEvent {\n result: unknown;\n durationMs: number;\n }\n export interface SwarmEventMap {\n "pre.task.create": {\n event: TaskCreateEvent;\n modify: TaskCreateModify;\n result: PreResult<TaskCreateModify>;\n };\n "pre.task.followUp": {\n event: TaskFollowUpEvent;\n modify: TaskFollowUpModify;\n result: PreResult<TaskFollowUpModify>;\n };\n "pre.slack.route": {\n event: SlackRouteEvent;\n modify: SlackRouteModify;\n result: PreResult<SlackRouteModify>;\n };\n "pre.heartbeat.remediate": {\n event: HeartbeatRemediateEvent;\n modify: HeartbeatRemediateModify;\n result: PreResult<HeartbeatRemediateModify>;\n };\n "pre.tool.call": {\n event: ToolCallEvent;\n modify: ToolCallModify;\n result: PreResult<ToolCallModify>;\n };\n "post.task.created": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.completed": {\n event: TaskCompletedEvent;\n modify: never;\n result: void;\n };\n "post.task.failed": {\n event: TaskFailedEvent;\n modify: never;\n result: void;\n };\n "post.task.cancelled": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.superseded": {\n event: TaskSupersededEvent;\n modify: never;\n result: void;\n };\n "post.task.progress": {\n event: TaskProgressEvent;\n modify: never;\n result: void;\n };\n "post.slack.message": {\n event: SlackMessageEvent;\n modify: never;\n result: void;\n };\n "post.tool.call": {\n event: ToolCallCompletedEvent;\n modify: never;\n result: void;\n };\n }\n export interface ExtensionApi<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > {\n on<E extends keyof SwarmEventMap>(\n event: E,\n handler: (\n event: SwarmEventMap[E]["event"],\n ctx: CtxFor<M>,\n ) => Promise<SwarmEventMap[E]["result"]> | SwarmEventMap[E]["result"],\n opts?: {\n priority?: number;\n },\n ): void;\n }\n export type SwarmExtension<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > = (api: ExtensionApi<M>) => void;\n export { block, modify } from "./contract-runtime";\n}\n';
|
|
4
|
+
'// biome-ignore-all lint/suspicious/noConfusingVoidType: Extension handlers may explicitly return no result.\ndeclare module "swarm-extension" {\n export type CreateTaskOptions = {\n key?: string | undefined;\n agentId?: string | null | undefined;\n creatorAgentId?: string | undefined;\n source?:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "system"\n | "linear"\n | "jira"\n | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n taskType?: string | undefined;\n tags?: string[] | undefined;\n priority?: number | undefined;\n dependsOn?: string[] | undefined;\n offeredTo?: string | undefined;\n status?: "draft" | "backlog" | "unassigned" | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n overrideSlackContext?: boolean | undefined;\n vcsProvider?: "github" | "gitlab" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | undefined;\n workflowRunStepId?: string | undefined;\n sourceTaskId?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n inheritParentOutputSchema?: boolean | undefined;\n inheritParentRoutingAffinity?: boolean | undefined;\n inheritParentFollowUpConfig?: boolean | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n requestedByUserId?: string | undefined;\n contextKey?: string | undefined;\n bypassTrackerContextDedup?: boolean | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n };\n\n export type AgentTask = {\n id: string;\n key: string;\n agentId: string | null;\n task: string;\n status:\n | "draft"\n | "backlog"\n | "unassigned"\n | "offered"\n | "reviewing"\n | "pending"\n | "in_progress"\n | "paused"\n | "completed"\n | "failed"\n | "cancelled"\n | "superseded";\n source:\n | "workflow"\n | "schedule"\n | "slack"\n | "agentmail"\n | "mcp"\n | "api"\n | "ui"\n | "github"\n | "gitlab"\n | "system"\n | "linear"\n | "jira";\n tags: string[];\n priority: number;\n dependsOn: string[];\n createdAt: string;\n lastUpdatedAt: string;\n slackReplySent: boolean;\n wasPaused: boolean;\n creatorAgentId?: string | undefined;\n title?: string | undefined;\n routingReason?:\n | "skill"\n | "continuity"\n | "overflow"\n | "human_pinned"\n | "reroute_fault"\n | undefined;\n routingSource?: "declared" | "engine_default" | undefined;\n routingNote?: string | undefined;\n taskType?: string | undefined;\n offeredTo?: string | undefined;\n offeredAt?: string | undefined;\n acceptedAt?: string | undefined;\n rejectionReason?: string | undefined;\n finishedAt?: string | undefined;\n notifiedAt?: string | undefined;\n failureReason?: string | undefined;\n output?: string | undefined;\n progress?: string | undefined;\n slackChannelId?: string | undefined;\n slackThreadTs?: string | undefined;\n slackTriggerMessageTs?: string | undefined;\n slackUserId?: string | undefined;\n slackProgressMessageTs?: string | undefined;\n slackTreeRootMessageTs?: string | undefined;\n vcsProvider?: "github" | "gitlab" | undefined;\n vcsRepo?: string | undefined;\n vcsEventType?: string | undefined;\n vcsNumber?: number | undefined;\n vcsCommentId?: number | undefined;\n vcsAuthor?: string | undefined;\n vcsUrl?: string | undefined;\n vcsInstallationId?: number | undefined;\n vcsNodeId?: string | undefined;\n agentmailInboxId?: string | undefined;\n agentmailMessageId?: string | undefined;\n agentmailThreadId?: string | undefined;\n mentionMessageId?: string | undefined;\n mentionChannelId?: string | undefined;\n dir?: string | undefined;\n parentTaskId?: string | undefined;\n claudeSessionId?: string | undefined;\n model?: string | undefined;\n modelTier?: "smol" | "regular" | "smart" | "ultra" | undefined;\n effort?: "off" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;\n resolvedModel?: string | undefined;\n modelSource?: string | undefined;\n modelAlias?: string | undefined;\n scheduleId?: string | undefined;\n workflowRunId?: string | null | undefined;\n workflowRunStepId?: string | null | undefined;\n contextKey?: string | undefined;\n outputSchema?: Record<string, unknown> | undefined;\n followUpConfig?:\n | {\n disabled?: boolean | undefined;\n onCompleted?: string | undefined;\n onFailed?: string | undefined;\n }\n | undefined;\n deferredAt?: string | undefined;\n compactionCount?: number | undefined;\n peakContextPercent?: number | undefined;\n peakContextTokens?: number | undefined;\n contextWindowSize?: number | undefined;\n credentialKeySuffix?: string | undefined;\n credentialKeyType?: string | undefined;\n requestedByUserId?: string | undefined;\n swarmVersion?: string | undefined;\n provider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | undefined;\n providerMeta?: Record<string, unknown> | undefined;\n harnessVariant?: string | undefined;\n harnessVariantMeta?: Record<string, unknown> | undefined;\n totalCostUsd?: number | undefined;\n routingAffinity?:\n | {\n capabilities: string[];\n sourceAgentId?: string | undefined;\n role?: string | undefined;\n harnessProvider?:\n | "claude"\n | "codex"\n | "pi"\n | "devin"\n | "claude-managed"\n | "opencode"\n | "acp"\n | "dsh"\n | undefined;\n leadOnly?: boolean | undefined;\n }\n | undefined;\n routingAffinityInvalid?: boolean | undefined;\n };\n\n export type ActiveSession = {\n id: string;\n agentId: string;\n taskId: string | null;\n triggerType: string;\n inboxMessageId: string | null;\n taskDescription: string | null;\n runnerSessionId: string | null;\n providerSessionId: string | null;\n startedAt: string;\n lastHeartbeatAt: string;\n runtimeInstanceId?: string | null | undefined;\n };\n\n export type RequestInfo = {\n sessionId: string | undefined;\n agentId: string | undefined;\n runtimeInstanceId: string | undefined;\n sourceTaskId: string | undefined;\n contextKey: string | undefined;\n callOrigin: "mcp" | "extension" | "script-sdk";\n };\n\n import type { SwarmSdk } from "swarm-sdk";\n import type { z } from "zod";\n export type Runtime = "api" | "worker";\n export interface ExtensionScriptAsset {\n name: string;\n file: string;\n description: string;\n intent?: string;\n }\n export interface ExtensionScheduleAsset {\n name: string;\n description?: string;\n script: string;\n cronExpression?: string;\n intervalMs?: number;\n timezone?: string;\n args?: Record<string, unknown>;\n }\n export interface ExtensionWorkflowAsset {\n file: string;\n }\n export interface ExtensionSkillAsset {\n dir: string;\n }\n export interface ExtensionManifest {\n $schema?: string;\n name: string;\n description: string;\n version: string;\n runtime: Runtime;\n assets: {\n hooks: string;\n scripts?: readonly ExtensionScriptAsset[];\n schedules?: readonly ExtensionScheduleAsset[];\n workflows?: readonly ExtensionWorkflowAsset[];\n skills?: readonly ExtensionSkillAsset[];\n };\n homepage?: string;\n author?: string;\n }\n export interface HooksModule {\n default: SwarmExtension;\n config?: z.ZodTypeAny;\n }\n export interface ExtensionState {\n get<T = unknown>(key: string): Promise<T | null>;\n set<T>(key: string, value: T): Promise<void>;\n incr(key: string, by?: number): Promise<number>;\n del(key: string): Promise<void>;\n }\n export interface ExtensionLogger {\n debug(message: string, data?: unknown): void;\n info(message: string, data?: unknown): void;\n warn(message: string, data?: unknown): void;\n error(message: string, data?: unknown): void;\n }\n type ConfigFor<M extends ExtensionManifest> = M extends {\n config: infer C extends z.ZodTypeAny;\n }\n ? z.infer<C>\n : Record<string, unknown>;\n export interface ApiCtx<Config = Record<string, unknown>> {\n swarm: SwarmSdk;\n state: ExtensionState;\n config: Config;\n log: ExtensionLogger;\n signal: AbortSignal;\n event: {\n name: keyof SwarmEventMap;\n at: string;\n extension: {\n id: string;\n version: number;\n };\n };\n }\n export interface WorkerCtx<Config = Record<string, unknown>>\n extends Omit<ApiCtx<Config>, "swarm"> {\n swarm: SwarmSdk;\n worker: {\n agentId: string;\n taskId: string;\n harness: string;\n };\n }\n export type CtxFor<M extends ExtensionManifest> = M["runtime"] extends "api"\n ? ApiCtx<ConfigFor<M>>\n : WorkerCtx<ConfigFor<M>>;\n export type PreResult<Modify> =\n | void\n | {\n action: "continue";\n }\n | {\n action: "modify";\n data: Modify;\n }\n | {\n action: "block";\n reason: string;\n };\n export type TaskCreateOrigin =\n | "rest"\n | "app"\n | "mcp"\n | "slack"\n | "schedule"\n | "workflow"\n | "webhook"\n | "followUp"\n | `extension:${string}`;\n export interface TaskCreateEvent {\n options: CreateTaskOptions;\n description: string;\n origin: TaskCreateOrigin;\n requestInfo?: RequestInfo;\n }\n export type TaskCreateModify = Partial<\n Pick<\n CreateTaskOptions,\n | "agentId"\n | "creatorAgentId"\n | "source"\n | "taskType"\n | "tags"\n | "priority"\n | "dependsOn"\n | "offeredTo"\n | "vcsProvider"\n | "vcsRepo"\n | "vcsEventType"\n | "vcsNumber"\n | "vcsCommentId"\n | "vcsAuthor"\n | "vcsUrl"\n | "vcsInstallationId"\n | "vcsNodeId"\n | "agentmailInboxId"\n | "agentmailMessageId"\n | "agentmailThreadId"\n | "mentionMessageId"\n | "mentionChannelId"\n | "dir"\n | "model"\n | "modelTier"\n | "effort"\n | "outputSchema"\n | "followUpConfig"\n | "bypassTrackerContextDedup"\n >\n > & {\n description?: string;\n };\n export interface TaskFollowUpEvent {\n completedTask: AgentTask;\n status: "completed" | "failed";\n output?: string;\n failureReason?: string;\n workerAgentId: string;\n leadAgentId: string;\n summary: string;\n }\n export type TaskFollowUpModify = Partial<\n Pick<CreateTaskOptions, "agentId" | "priority" | "followUpConfig">\n > & {\n description?: string;\n };\n export interface SlackRouteEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n botMentioned: boolean;\n threadContext?: {\n channelId: string;\n threadTs: string;\n };\n }\n export interface SlackRouteModify {\n target:\n | {\n kind: "agent";\n agentId: string;\n }\n | {\n kind: "lead";\n }\n | {\n kind: "broadcast";\n };\n }\n export type HeartbeatClassification = "no-session" | "stale-session" | "fresh-stalled";\n export type HeartbeatAction = "supersede-resume" | "fail" | "record";\n export interface HeartbeatRemediateEvent {\n task: AgentTask;\n session?: ActiveSession;\n classification: HeartbeatClassification;\n proposedAction: HeartbeatAction;\n reason: string;\n taskAgeMs: number;\n sessionHeartbeatAgeMs?: number;\n }\n export interface HeartbeatRemediateModify {\n proposedAction: HeartbeatAction;\n }\n export interface ToolCallEvent {\n tool: string;\n args: unknown;\n requestInfo: RequestInfo;\n }\n export interface ToolCallModify {\n args: unknown;\n }\n export interface TaskEvent {\n task: AgentTask;\n }\n export interface TaskCompletedEvent extends TaskEvent {\n output: string;\n }\n export interface TaskFailedEvent extends TaskEvent {\n failureReason: string;\n }\n export interface TaskSupersededEvent extends TaskEvent {\n supersededBy: string;\n }\n export interface TaskProgressEvent extends TaskEvent {\n progress: string;\n }\n export interface SlackMessageEvent {\n channelId: string;\n userId: string;\n text: string;\n threadTs?: string;\n taskId?: string;\n }\n export interface ToolCallCompletedEvent extends ToolCallEvent {\n result: unknown;\n durationMs: number;\n }\n export interface SwarmEventMap {\n "pre.task.create": {\n event: TaskCreateEvent;\n modify: TaskCreateModify;\n result: PreResult<TaskCreateModify>;\n };\n "pre.task.followUp": {\n event: TaskFollowUpEvent;\n modify: TaskFollowUpModify;\n result: PreResult<TaskFollowUpModify>;\n };\n "pre.slack.route": {\n event: SlackRouteEvent;\n modify: SlackRouteModify;\n result: PreResult<SlackRouteModify>;\n };\n "pre.heartbeat.remediate": {\n event: HeartbeatRemediateEvent;\n modify: HeartbeatRemediateModify;\n result: PreResult<HeartbeatRemediateModify>;\n };\n "pre.tool.call": {\n event: ToolCallEvent;\n modify: ToolCallModify;\n result: PreResult<ToolCallModify>;\n };\n "post.task.created": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.completed": {\n event: TaskCompletedEvent;\n modify: never;\n result: void;\n };\n "post.task.failed": {\n event: TaskFailedEvent;\n modify: never;\n result: void;\n };\n "post.task.cancelled": {\n event: TaskEvent;\n modify: never;\n result: void;\n };\n "post.task.superseded": {\n event: TaskSupersededEvent;\n modify: never;\n result: void;\n };\n "post.task.progress": {\n event: TaskProgressEvent;\n modify: never;\n result: void;\n };\n "post.slack.message": {\n event: SlackMessageEvent;\n modify: never;\n result: void;\n };\n "post.tool.call": {\n event: ToolCallCompletedEvent;\n modify: never;\n result: void;\n };\n }\n export interface ExtensionApi<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > {\n on<E extends keyof SwarmEventMap>(\n event: E,\n handler: (\n event: SwarmEventMap[E]["event"],\n ctx: CtxFor<M>,\n ) => Promise<SwarmEventMap[E]["result"]> | SwarmEventMap[E]["result"],\n opts?: {\n priority?: number;\n },\n ): void;\n }\n export type SwarmExtension<\n M extends ExtensionManifest = ExtensionManifest & {\n runtime: "api";\n },\n > = (api: ExtensionApi<M>) => void;\n export { block, modify } from "./contract-runtime";\n}\n';
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
getStaleUnassignedAffinityTasks,
|
|
29
29
|
getStalledInProgressTasks,
|
|
30
30
|
getSupersededTasksWithoutResume,
|
|
31
|
+
getSupersededTasksWithUnsettledDependents,
|
|
31
32
|
getTaskById,
|
|
32
33
|
getTaskStats,
|
|
33
34
|
getTasksByStatus,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
releaseStaleOfferedTasksForOfflineAgents,
|
|
43
44
|
releaseStaleProcessingInbox,
|
|
44
45
|
releaseStaleReviewingTasks,
|
|
46
|
+
settleSupersededTaskDependents,
|
|
45
47
|
supersedeTask,
|
|
46
48
|
updateAgentStatus,
|
|
47
49
|
} from "../be/db";
|
|
@@ -66,6 +68,7 @@ import {
|
|
|
66
68
|
resolveLeadOnlyRecoveryAssignment,
|
|
67
69
|
} from "../tasks/worker-follow-up";
|
|
68
70
|
import type { AgentTask } from "../types";
|
|
71
|
+
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
69
72
|
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
70
73
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
71
74
|
import { getExecutorRegistry } from "../workflows";
|
|
@@ -217,12 +220,30 @@ export const HEARTBEAT_RESUME_PIN_GRACE_MIN = (() => {
|
|
|
217
220
|
*/
|
|
218
221
|
const POOL_AFFINITY_ESCALATION_MIN = Number(process.env.POOL_AFFINITY_ESCALATION_MIN) || 15;
|
|
219
222
|
|
|
220
|
-
|
|
221
|
-
const HEARTBEAT_CHECKLIST_INTERVAL_MS =
|
|
222
|
-
Number(process.env.HEARTBEAT_CHECKLIST_INTERVAL_MS) || 30 * 60 * 1000;
|
|
223
|
+
const DEFAULT_HEARTBEAT_CHECKLIST_INTERVAL_MS = 30 * 60 * 1000;
|
|
223
224
|
|
|
224
|
-
/**
|
|
225
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Heartbeat checklist interval: how often to check HEARTBEAT.md (default: 30 min).
|
|
227
|
+
*
|
|
228
|
+
* An explicit `0` (or negative) turns the RECURRING checklist tick off; the
|
|
229
|
+
* one-shot boot triage still runs. Absent, empty, or non-numeric values fall
|
|
230
|
+
* back to the default (not `Number(x) || default`, which would coerce `0` to 30 min).
|
|
231
|
+
*/
|
|
232
|
+
function heartbeatChecklistIntervalMs(): number {
|
|
233
|
+
const raw = process.env.HEARTBEAT_CHECKLIST_INTERVAL_MS?.trim();
|
|
234
|
+
if (!raw) return DEFAULT_HEARTBEAT_CHECKLIST_INTERVAL_MS;
|
|
235
|
+
const parsed = Number(raw);
|
|
236
|
+
if (!Number.isFinite(parsed)) return DEFAULT_HEARTBEAT_CHECKLIST_INTERVAL_MS;
|
|
237
|
+
return Math.max(0, parsed);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Whether to disable the heartbeat checklist entirely (recurring tick AND boot
|
|
242
|
+
* triage). Parsed with the shared env-flag helper, so `"false"`/`"0"` keep it on.
|
|
243
|
+
*/
|
|
244
|
+
function isHeartbeatChecklistDisabled(): boolean {
|
|
245
|
+
return isEnvFlagEnabled("HEARTBEAT_CHECKLIST_DISABLE", false);
|
|
246
|
+
}
|
|
226
247
|
|
|
227
248
|
// ============================================================================
|
|
228
249
|
// Types
|
|
@@ -275,6 +296,7 @@ export interface HeartbeatFindings {
|
|
|
275
296
|
|
|
276
297
|
let heartbeatInterval: ReturnType<typeof setInterval> | null = null;
|
|
277
298
|
let checklistInterval: ReturnType<typeof setInterval> | null = null;
|
|
299
|
+
let bootTriageTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
278
300
|
let isSweeping = false;
|
|
279
301
|
let beforeHeartbeatSupersedeForTests: ((task: AgentTask) => void | Promise<void>) | null = null;
|
|
280
302
|
|
|
@@ -530,10 +552,15 @@ async function repairSupersededWithoutResume(findings: HeartbeatFindings): Promi
|
|
|
530
552
|
new Date(now - ORPHAN_SUPERSEDE_MAX_AGE_MS).toISOString(),
|
|
531
553
|
);
|
|
532
554
|
for (const task of orphans) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
555
|
+
const resume =
|
|
556
|
+
task.agentId && getNextResumeGeneration(task) <= maxResumeGenerations()
|
|
557
|
+
? await createResumeFollowUp({ parentId: task.id, reason: "crash_recovery" })
|
|
558
|
+
: null;
|
|
559
|
+
if (resume?.kind !== "created" || !task.agentId) {
|
|
560
|
+
// No resume will carry the work: its dependents fail as they would have.
|
|
561
|
+
await settleSupersededTaskDependents(task.id, null);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
537
564
|
await backfillSupersedeTaskResumeTaskId(task.id, resume.task.id);
|
|
538
565
|
findings.autoResumedTasks.push({
|
|
539
566
|
taskId: task.id,
|
|
@@ -545,6 +572,16 @@ async function repairSupersededWithoutResume(findings: HeartbeatFindings): Promi
|
|
|
545
572
|
`[Heartbeat] Created missing resume ${resume.task.id.slice(0, 8)} for superseded task ${task.id.slice(0, 8)}`,
|
|
546
573
|
);
|
|
547
574
|
}
|
|
575
|
+
|
|
576
|
+
// A crash after the resume was created but before the backfill leaves
|
|
577
|
+
// dependents waiting on the superseded task; settle them onto that resume.
|
|
578
|
+
const unsettled = await getSupersededTasksWithUnsettledDependents(
|
|
579
|
+
new Date(now - ORPHAN_SUPERSEDE_MIN_AGE_MS).toISOString(),
|
|
580
|
+
new Date(now - ORPHAN_SUPERSEDE_MAX_AGE_MS).toISOString(),
|
|
581
|
+
);
|
|
582
|
+
for (const { taskId, resumeTaskId } of unsettled) {
|
|
583
|
+
await settleSupersededTaskDependents(taskId, resumeTaskId);
|
|
584
|
+
}
|
|
548
585
|
}
|
|
549
586
|
|
|
550
587
|
function isRemediationAction(value: unknown): value is RemediationAction {
|
|
@@ -679,6 +716,7 @@ async function remediateCrashedWorkerTask(
|
|
|
679
716
|
resume.kind === "skipped"
|
|
680
717
|
? `resume_creation_skipped_${resume.reason}`
|
|
681
718
|
: "resume_creation_skipped_workflow";
|
|
719
|
+
await settleSupersededTaskDependents(task.id, null);
|
|
682
720
|
const failed = await failTask(task.id, reason);
|
|
683
721
|
if (failed) {
|
|
684
722
|
findings.autoFailedTasks.push({
|
|
@@ -1835,19 +1873,26 @@ export async function createBootTriageTask(): Promise<void> {
|
|
|
1835
1873
|
/**
|
|
1836
1874
|
* Start the heartbeat checklist polling loop (separate from the infrastructure sweep).
|
|
1837
1875
|
*/
|
|
1838
|
-
export function startHeartbeatChecklist(intervalMs =
|
|
1839
|
-
if (
|
|
1876
|
+
export function startHeartbeatChecklist(intervalMs = heartbeatChecklistIntervalMs()): void {
|
|
1877
|
+
if (isHeartbeatChecklistDisabled()) {
|
|
1840
1878
|
console.log("[Heartbeat] Checklist disabled via HEARTBEAT_CHECKLIST_DISABLE");
|
|
1841
1879
|
return;
|
|
1842
1880
|
}
|
|
1843
|
-
if (checklistInterval) {
|
|
1881
|
+
if (checklistInterval || bootTriageTimeout) {
|
|
1844
1882
|
return; // Already running
|
|
1845
1883
|
}
|
|
1846
1884
|
|
|
1847
|
-
console.log(`[Heartbeat] Checklist starting with ${intervalMs}ms interval`);
|
|
1848
|
-
|
|
1849
1885
|
// Boot triage at T+90s — after reboot sweep (T+5s) has completed and results are available
|
|
1850
|
-
setTimeout(() => createBootTriageTask(), 90_000);
|
|
1886
|
+
bootTriageTimeout = setTimeout(() => createBootTriageTask(), 90_000);
|
|
1887
|
+
|
|
1888
|
+
if (intervalMs <= 0) {
|
|
1889
|
+
console.log(
|
|
1890
|
+
"[Heartbeat] Recurring checklist off (HEARTBEAT_CHECKLIST_INTERVAL_MS=0); boot triage still scheduled",
|
|
1891
|
+
);
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
console.log(`[Heartbeat] Checklist starting with ${intervalMs}ms interval`);
|
|
1851
1896
|
|
|
1852
1897
|
// Recurring checklist starts from the second interval onward
|
|
1853
1898
|
checklistInterval = setInterval(() => {
|
|
@@ -1864,6 +1909,10 @@ export function startHeartbeatChecklist(intervalMs = HEARTBEAT_CHECKLIST_INTERVA
|
|
|
1864
1909
|
* Stop the heartbeat checklist polling loop.
|
|
1865
1910
|
*/
|
|
1866
1911
|
export function stopHeartbeatChecklist(): void {
|
|
1912
|
+
if (bootTriageTimeout) {
|
|
1913
|
+
clearTimeout(bootTriageTimeout);
|
|
1914
|
+
bootTriageTimeout = null;
|
|
1915
|
+
}
|
|
1867
1916
|
if (checklistInterval) {
|
|
1868
1917
|
clearInterval(checklistInterval);
|
|
1869
1918
|
checklistInterval = null;
|
|
@@ -28,8 +28,8 @@ Goal: Review system status and your standing orders, take action if needed.
|
|
|
28
28
|
## Instructions
|
|
29
29
|
1. **Read your HEARTBEAT.md** — run \`read /workspace/HEARTBEAT.md\` to get the latest standing orders (the snapshot above may be slightly stale).
|
|
30
30
|
2. **Prune tracked HEARTBEAT items FIRST.** Active Blockers + Watch Items + Open Discussion combined must stay at ≤10 items; 20 is the absolute max only when genuinely super busy. This cap does not apply to evergreen Standing Orders / Governance / Playbook-index reference sections. Before adding anything, re-check every tracked item against its lift trigger and remove anything resolved, stale, or past its trigger date. Lift incident detail to memory; never keep it inline in HEARTBEAT.md. Every new tracked item must include an explicit lift trigger + date; if it has no removal condition, do not add it. If the tracked list is already at/over cap, prune before (or instead of) adding — the cap is binding.
|
|
31
|
-
3. **Run seeded heartbeat data-gathering.** Use \`script-run\` with global
|
|
32
|
-
4. Review the system status above plus the
|
|
31
|
+
3. **Run seeded heartbeat data-gathering.** Use \`script-run\` with the global scripts \`schedule-health\` and \`task-failure-audit\`, both with args \`{ "days": 1, "publishPage": false }\`. They report failing schedules and failure clusters. Check any other standing order in HEARTBEAT.md yourself. Scripts can read Slack too (\`ctx.swarm.slack_read\` or the seeded \`slack-thread-flatten\` script), so a recurring Slack check can be scripted.
|
|
32
|
+
4. Review the system status above plus the script results for anything that needs attention (stalled tasks, idle workers with available work, anomalies).
|
|
33
33
|
5. **CRITICAL — Reboot failure triage:** Failures with reason "worker session not found" or "worker session heartbeat is stale" indicate tasks that were INTERRUPTED by a server restart. These are NOT "expected auto-cleanup" — they represent work that was lost mid-execution. For each such failure:
|
|
34
34
|
- Check what the task was (via \`get-task-details\` with the task ID from the failure)
|
|
35
35
|
- If a retry task was auto-created (tagged \`reboot-retry\`), verify it is progressing
|
package/src/http/agents.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { isAlias } from "@desplega/model-catalog";
|
|
2
3
|
import { ensure } from "@desplega.ai/business-use";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import {
|
|
@@ -29,6 +30,12 @@ import {
|
|
|
29
30
|
upsertSwarmConfig,
|
|
30
31
|
} from "../be/db";
|
|
31
32
|
import { createEvent } from "../be/events";
|
|
33
|
+
import { setAgentHarnessCliVersion } from "../be/harness-model-support";
|
|
34
|
+
import {
|
|
35
|
+
sanitizeModelTierOverrides,
|
|
36
|
+
setAgentModelTierOverrides,
|
|
37
|
+
} from "../be/model-tier-resolution";
|
|
38
|
+
import { explicitModelError } from "../be/model-validation";
|
|
32
39
|
import {
|
|
33
40
|
getRuntimeInstanceById,
|
|
34
41
|
listRuntimeInstancesForAgent,
|
|
@@ -150,6 +157,14 @@ const registerAgent = route({
|
|
|
150
157
|
* enforcing the mode-dependent requirement.
|
|
151
158
|
*/
|
|
152
159
|
runtimeInstanceId: z.string().min(1).optional(),
|
|
160
|
+
/**
|
|
161
|
+
* The worker's parsed MODEL_TIER_* env overrides, {provider: {tier: model}}.
|
|
162
|
+
* Values only. Stored on the agent row for claim-time resolution; `{}`
|
|
163
|
+
* clears, omitted leaves the stored value unchanged.
|
|
164
|
+
*/
|
|
165
|
+
modelTierOverrides: z.record(z.string(), z.record(z.string(), z.string())).optional(),
|
|
166
|
+
/** The worker's harness CLI version (`claude --version` / `codex --version`). */
|
|
167
|
+
harnessCliVersion: z.string().max(64).optional(),
|
|
153
168
|
}),
|
|
154
169
|
responses: {
|
|
155
170
|
200: {
|
|
@@ -246,7 +261,7 @@ const updateAgentRuntimeRoute = route({
|
|
|
246
261
|
pattern: ["api", "agents", null, "runtime"],
|
|
247
262
|
summary: "Update an agent's runtime harness and default model",
|
|
248
263
|
description:
|
|
249
|
-
"Updates `agents.harness_provider` and agent-scoped runtime config. The settings apply to future provider sessions. For `model`, `reasoning_effort`, and `claude.transport`: omit the field to leave it unchanged, send `null` to clear the corresponding override, or send a value to set it.",
|
|
264
|
+
"Updates `agents.harness_provider` and agent-scoped runtime config. The settings apply to future provider sessions. For `model`, `reasoning_effort`, and `claude.transport`: omit the field to leave it unchanged, send `null` to clear the corresponding override, or send a value to set it. A `model` the model catalog does not list is a 400 unless `allow_custom_model` is true. A `latest:` alias is not accepted here (aliases resolve for a task's model and for `MODEL_TIER_*` values); `reasoning_effort` must be one the harness and model support.",
|
|
250
265
|
tags: ["Agents"],
|
|
251
266
|
params: z.object({ id: z.string() }),
|
|
252
267
|
query: z.object({ repoId: z.string().optional() }),
|
|
@@ -708,6 +723,17 @@ export async function handleAgentRegister(
|
|
|
708
723
|
return { agent, created: true };
|
|
709
724
|
});
|
|
710
725
|
|
|
726
|
+
if (parsed.body.harnessCliVersion !== undefined) {
|
|
727
|
+
await setAgentHarnessCliVersion(agentId, parsed.body.harnessCliVersion.trim() || null);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (parsed.body.modelTierOverrides) {
|
|
731
|
+
await setAgentModelTierOverrides(
|
|
732
|
+
agentId,
|
|
733
|
+
sanitizeModelTierOverrides(parsed.body.modelTierOverrides),
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
711
737
|
telemetry.agent("registered", {
|
|
712
738
|
role: parsed.body.role,
|
|
713
739
|
capabilities: parsed.body.capabilities ?? [],
|
|
@@ -1108,6 +1134,28 @@ export async function handleAgentsRest(
|
|
|
1108
1134
|
}
|
|
1109
1135
|
}
|
|
1110
1136
|
|
|
1137
|
+
// An explicit model must be in the catalog unless the caller marks it custom
|
|
1138
|
+
// (`allow_custom_model`). `null` clears the override and `undefined` leaves it.
|
|
1139
|
+
if (typeof model === "string" && isAlias(model)) {
|
|
1140
|
+
jsonError(
|
|
1141
|
+
res,
|
|
1142
|
+
`A latest: alias cannot be an agent's default model ("${model}"). Set the alias on a task, or as a MODEL_TIER_<PROVIDER>_<TIER> value.`,
|
|
1143
|
+
400,
|
|
1144
|
+
);
|
|
1145
|
+
return true;
|
|
1146
|
+
}
|
|
1147
|
+
if (typeof model === "string") {
|
|
1148
|
+
const modelError = await explicitModelError({
|
|
1149
|
+
model,
|
|
1150
|
+
allowCustomModel: allow_custom_model,
|
|
1151
|
+
harnessProvider: harness_provider,
|
|
1152
|
+
});
|
|
1153
|
+
if (modelError) {
|
|
1154
|
+
jsonError(res, modelError, 400);
|
|
1155
|
+
return true;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1111
1159
|
let agent: Agent | null;
|
|
1112
1160
|
try {
|
|
1113
1161
|
agent = await getDbClient().transaction(async () => {
|
package/src/http/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
upsertSwarmConfig,
|
|
16
16
|
} from "../be/db";
|
|
17
17
|
import { startDbRetention, stopDbRetention } from "../be/db-retention";
|
|
18
|
+
import { startHumanFreeDrain, stopHumanFreeDrain } from "../be/human-free-drain";
|
|
18
19
|
import {
|
|
19
20
|
enqueueAuditRow,
|
|
20
21
|
flushAuditBuffer,
|
|
@@ -488,6 +489,9 @@ async function shutdown() {
|
|
|
488
489
|
// Stop opt-in session, agent-log, and event retention before closing SQLite.
|
|
489
490
|
await stopDbRetention();
|
|
490
491
|
|
|
492
|
+
// Stop the human-free reclassification drain before closing SQLite.
|
|
493
|
+
stopHumanFreeDrain();
|
|
494
|
+
|
|
491
495
|
// Stop scratch-script retention garbage collector
|
|
492
496
|
stopScratchScriptGc();
|
|
493
497
|
|
|
@@ -771,6 +775,10 @@ httpServer
|
|
|
771
775
|
// read on each tick, so config reloads take effect without a restart.
|
|
772
776
|
await startDbRetention();
|
|
773
777
|
|
|
778
|
+
// Finish the human-free reclassification a request left queued (a very large
|
|
779
|
+
// task tree, or a restart mid-drain). Wakes on demand; see human-free-drain.ts.
|
|
780
|
+
startHumanFreeDrain();
|
|
781
|
+
|
|
774
782
|
// (RBAC audit sink is wired pre-listen — see above httpServer.listen.)
|
|
775
783
|
|
|
776
784
|
// Background backfill: re-embed any agent_memory rows with wrong-dimension
|