@desplega.ai/agent-swarm 1.131.0 → 1.133.1
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/README.md +2 -2
- package/dist/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
- package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
- package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
- package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
- package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
- package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
- package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
- package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
- package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
- package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
- package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
- package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
- package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
- package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
- package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
- package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
- package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
- package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
- package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
- package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
- package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
- package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
- package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
- package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
- package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
- package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
- package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
- package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
- package/dist/cli-et1bbwqf.js +13 -0
- package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
- package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
- package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
- package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
- package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
- package/dist/cli-hkwfe3qj.js +530 -0
- package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
- package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
- package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
- package/dist/cli-pzq5rjym.js +15 -0
- package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
- package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
- package/dist/cli-qys3rhhz.js +27 -0
- package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
- package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
- package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
- package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
- package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
- package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
- package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
- package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
- package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
- package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
- package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
- package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
- package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
- package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
- package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
- package/dist/cli.js +22 -22
- package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
- package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
- package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
- package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
- package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
- package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
- package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
- package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
- package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
- package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
- package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
- package/dist/index-0gr8vd6k.js +39 -0
- package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
- package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
- package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
- package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
- package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
- package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
- package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
- package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
- package/dist/mistral-conversations-00zrp7g4.js +717 -0
- package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
- package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
- package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
- package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
- package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
- package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
- package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
- package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
- package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
- package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
- package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
- package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
- package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
- package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
- package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
- package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
- package/openapi.json +542 -16
- package/package.json +6 -6
- package/src/be/budget-refusal-notify.ts +14 -7
- package/src/be/db-queries/task-artifacts.ts +182 -0
- package/src/be/db.ts +783 -169
- package/src/be/events.ts +5 -0
- package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
- package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
- package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
- package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
- package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
- package/src/be/migrations/136_task_requester_provenance.sql +37 -0
- package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
- package/src/be/multi-runtime.ts +430 -0
- package/src/be/scripts/db.ts +43 -1
- package/src/be/scripts/extract-schema.ts +3 -1
- package/src/be/scripts/retention.ts +164 -0
- package/src/be/scripts/run-saved.ts +16 -1
- package/src/be/scripts/typecheck.ts +3 -2
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/steering.ts +1 -1
- package/src/be/swarm-config-guard.ts +18 -0
- package/src/commands/credential-wait.ts +54 -0
- package/src/commands/onboard/steps/core-credentials.tsx +1 -1
- package/src/commands/onboard/steps/custom-templates.tsx +1 -1
- package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
- package/src/commands/onboard/steps/health-check.tsx +1 -1
- package/src/commands/onboard/steps/start.tsx +1 -1
- package/src/commands/profile-sync-audit.ts +56 -0
- package/src/commands/profile-sync.ts +287 -15
- package/src/commands/provider-credentials.ts +46 -19
- package/src/commands/runner.ts +196 -33
- package/src/commands/templates.ts +22 -0
- package/src/github/handlers.ts +55 -9
- package/src/heartbeat/heartbeat.ts +71 -12
- package/src/hooks/hook.ts +49 -33
- package/src/http/active-sessions.ts +11 -1
- package/src/http/agents.ts +261 -30
- package/src/http/all-routes.ts +1 -0
- package/src/http/config.ts +13 -2
- package/src/http/core.ts +102 -4
- package/src/http/db-query-bounded.ts +313 -0
- package/src/http/db-query-shared.ts +249 -0
- package/src/http/db-query.ts +54 -42
- package/src/http/events.ts +2 -0
- package/src/http/index.ts +27 -2
- package/src/http/mcp-bridge.ts +37 -3
- package/src/http/mcp.ts +12 -0
- package/src/http/memory.ts +13 -2
- package/src/http/openapi.ts +1 -0
- package/src/http/poll.ts +70 -27
- package/src/http/route-def.ts +43 -9
- package/src/http/schedules.ts +2 -2
- package/src/http/scripts.ts +31 -4
- package/src/http/session-data.ts +44 -0
- package/src/http/tasks.ts +2 -2
- package/src/http/users.ts +74 -52
- package/src/http/webhooks.ts +5 -2
- package/src/http/workflows.ts +1 -4
- package/src/http/x.ts +14 -0
- package/src/integrations/kapso/inbound.ts +1 -1
- package/src/jira/outbound.ts +39 -8
- package/src/jira/webhook-lifecycle.ts +13 -2
- package/src/linear/outbound.ts +43 -10
- package/src/oauth/keepalive.ts +13 -2
- package/src/prompts/base-prompt.ts +1 -1
- package/src/prompts/session-templates.ts +45 -7
- package/src/providers/claude-adapter.ts +6 -0
- package/src/providers/claude-managed-models.ts +34 -10
- package/src/providers/codex-adapter.ts +6 -0
- package/src/providers/opencode-adapter.ts +6 -0
- package/src/providers/pi-mono-adapter.ts +8 -0
- package/src/providers/pricing-sources.md +24 -0
- package/src/queue-stall-alarm.ts +216 -0
- package/src/scheduler/scheduler.ts +7 -0
- package/src/script-workflows/executor.ts +2 -1
- package/src/scripts-runtime/executors/native.ts +2 -1
- package/src/scripts-runtime/executors/types.ts +5 -0
- package/src/scripts-runtime/loader.ts +5 -0
- package/src/scripts-runtime/swarm-config.ts +8 -0
- package/src/scripts-runtime/swarm-sdk.ts +7 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/tests/budget-claim-gate.test.ts +20 -0
- package/src/tests/claude-adapter.test.ts +32 -0
- package/src/tests/claude-managed-adapter.test.ts +38 -7
- package/src/tests/codex-adapter.test.ts +30 -0
- package/src/tests/create-task-validation.test.ts +128 -0
- package/src/tests/credential-wait.test.ts +67 -1
- package/src/tests/db-query.test.ts +711 -2
- package/src/tests/http-api-integration.test.ts +81 -0
- package/src/tests/http-users.test.ts +166 -1
- package/src/tests/identity-field-budget.test.ts +176 -0
- package/src/tests/mcp-tools-user.test.ts +34 -0
- package/src/tests/mcp-user-route.test.ts +17 -0
- package/src/tests/metrics-http.test.ts +44 -0
- package/src/tests/multi-runtime-registration.test.ts +2703 -0
- package/src/tests/openapi-response-contract.test.ts +24 -0
- package/src/tests/openapi-route-headers.test.ts +83 -0
- package/src/tests/opencode-adapter.test.ts +40 -0
- package/src/tests/outbound-listener-rejection.test.ts +138 -0
- package/src/tests/pi-mono-adapter.test.ts +98 -0
- package/src/tests/profile-sync-warning.test.ts +277 -0
- package/src/tests/profile-sync.test.ts +232 -6
- package/src/tests/prompt-template-session.test.ts +32 -3
- package/src/tests/queue-stall-alarm.test.ts +226 -0
- package/src/tests/requester-comms.test.ts +32 -0
- package/src/tests/runner-requester-profile.test.ts +38 -0
- package/src/tests/sandboxed-process.test.ts +45 -0
- package/src/tests/schedule-target-type.test.ts +38 -0
- package/src/tests/script-connections.test.ts +16 -3
- package/src/tests/scripts-external-api.test.ts +8 -3
- package/src/tests/scripts-http.test.ts +12 -0
- package/src/tests/scripts-retention.test.ts +384 -0
- package/src/tests/scripts-runtime-identity.test.ts +375 -0
- package/src/tests/scripts-runtime.test.ts +4 -3
- package/src/tests/sdk-allowlist.test.ts +47 -0
- package/src/tests/send-task-requested-by.test.ts +9 -0
- package/src/tests/session-costs.test.ts +506 -1
- package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
- package/src/tests/steering-core.test.ts +28 -0
- package/src/tests/task-artifacts.test.ts +386 -0
- package/src/tests/task-attachments-schema.test.ts +17 -1
- package/src/tests/task-completion-idempotency.test.ts +57 -0
- package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
- package/src/tests/update-profile-api.test.ts +67 -39
- package/src/tests/update-profile-auth.test.ts +48 -4
- package/src/tests/user-attribution-migration.test.ts +54 -0
- package/src/tests/user-identity.test.ts +72 -0
- package/src/tests/workflow-async-v2.test.ts +39 -1
- package/src/tests/workflow-http-v2.test.ts +3 -2
- package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
- package/src/tests/workflow-retry-v2.test.ts +45 -1
- package/src/tests/workflow-swarm-script.test.ts +14 -5
- package/src/tools/db-query.ts +18 -8
- package/src/tools/get-task-details.ts +24 -3
- package/src/tools/manage-user.ts +36 -3
- package/src/tools/poll-task.ts +93 -14
- package/src/tools/script-common.ts +5 -0
- package/src/tools/send-task.ts +1 -4
- package/src/tools/store-progress.ts +13 -2
- package/src/tools/swarm-config/delete-config.ts +9 -0
- package/src/tools/swarm-config/set-config.ts +3 -2
- package/src/tools/task-action.ts +30 -0
- package/src/tools/task-tool-ctx.ts +8 -1
- package/src/tools/update-profile.ts +16 -21
- package/src/tools/utils.ts +5 -0
- package/src/tools/workflows/trigger-workflow.ts +7 -1
- package/src/types.ts +165 -1
- package/src/utils/github-pull-request.ts +76 -0
- package/src/utils/identity-field-budget.ts +73 -0
- package/src/utils/multi-runtime.ts +20 -0
- package/src/utils/requester-comms.ts +24 -0
- package/src/utils/sandboxed-process.ts +40 -14
- package/src/workflows/engine.ts +22 -2
- package/src/workflows/executors/swarm-script.ts +24 -1
- package/src/workflows/resume.ts +19 -11
- package/src/workflows/retry-poller.ts +1 -0
- package/src/workflows/triggers.ts +4 -0
- package/templates/schedules/daily-compounding-reflection/content.md +12 -3
- package/templates/skills/db-query-guidance/config.json +14 -0
- package/templates/skills/db-query-guidance/content.md +57 -0
- package/dist/cli-3te4jdkm.js +0 -225
- package/dist/cli-bmze986d.js +0 -7
- package/dist/cli-kh3ba2zf.js +0 -607
- package/dist/index-hp1kdbjj.js +0 -36
- package/dist/mistral-conversations-81fwav0j.js +0 -30578
- package/dist/otel-nqa12wf3.js +0 -3149
- package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
|
@@ -36,6 +36,11 @@ import {
|
|
|
36
36
|
updateAgentStatus,
|
|
37
37
|
} from "../be/db";
|
|
38
38
|
import { repointTrackerSyncBySwarmId } from "../be/db-queries/tracker";
|
|
39
|
+
import {
|
|
40
|
+
agentsWithLiveRuntime,
|
|
41
|
+
countActiveRuntimeInstancesForAgent,
|
|
42
|
+
expireStaleRuntimeInstances,
|
|
43
|
+
} from "../be/multi-runtime";
|
|
39
44
|
import { promotePendingSteeringForTask } from "../be/steering";
|
|
40
45
|
import { resolveTemplate } from "../prompts/resolver";
|
|
41
46
|
import {
|
|
@@ -48,6 +53,7 @@ import {
|
|
|
48
53
|
REBOOT_RETRY_PIN_TAG,
|
|
49
54
|
} from "../tasks/worker-follow-up";
|
|
50
55
|
import type { AgentTask } from "../types";
|
|
56
|
+
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
51
57
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
52
58
|
import { getExecutorRegistry } from "../workflows";
|
|
53
59
|
import { recoverIncompleteRuns } from "../workflows/recovery";
|
|
@@ -212,6 +218,7 @@ export interface HeartbeatFindings {
|
|
|
212
218
|
mentionProcessing: number;
|
|
213
219
|
inboxProcessing: number;
|
|
214
220
|
workflowRuns: number;
|
|
221
|
+
staleRuntimes: number;
|
|
215
222
|
};
|
|
216
223
|
}
|
|
217
224
|
|
|
@@ -285,9 +292,15 @@ export async function codeLevelTriage(): Promise<HeartbeatFindings> {
|
|
|
285
292
|
mentionProcessing: 0,
|
|
286
293
|
inboxProcessing: 0,
|
|
287
294
|
workflowRuns: 0,
|
|
295
|
+
staleRuntimes: 0,
|
|
288
296
|
},
|
|
289
297
|
};
|
|
290
298
|
|
|
299
|
+
// 0. Retire runtimes that stopped pinging, before anything reasons about
|
|
300
|
+
// which workers are alive — otherwise auto-assignment below can hand a task
|
|
301
|
+
// to an agent this same sweep is about to mark offline, stranding it.
|
|
302
|
+
findings.staleCleanup.staleRuntimes = expireStaleRuntimeInstances().expired;
|
|
303
|
+
|
|
291
304
|
// 1. Detect and remediate stalled tasks (tiered: auto-fail dead workers)
|
|
292
305
|
detectAndRemediateStalledTasks(findings);
|
|
293
306
|
|
|
@@ -423,7 +436,7 @@ function remediateCrashedWorkerTask(
|
|
|
423
436
|
`[Heartbeat] Workflow-step task ${task.id.slice(0, 8)} failed — engine will handle retry (${opts.shortLabel})`,
|
|
424
437
|
);
|
|
425
438
|
const remaining = getActiveTaskCount(task.agentId);
|
|
426
|
-
if (remaining === 0)
|
|
439
|
+
if (remaining === 0) restoreAgentIdleAfterRemediation(task.agentId);
|
|
427
440
|
}
|
|
428
441
|
return;
|
|
429
442
|
}
|
|
@@ -443,7 +456,7 @@ function remediateCrashedWorkerTask(
|
|
|
443
456
|
})`,
|
|
444
457
|
);
|
|
445
458
|
const remaining = getActiveTaskCount(task.agentId);
|
|
446
|
-
if (remaining === 0)
|
|
459
|
+
if (remaining === 0) restoreAgentIdleAfterRemediation(task.agentId);
|
|
447
460
|
}
|
|
448
461
|
return;
|
|
449
462
|
}
|
|
@@ -462,7 +475,7 @@ function remediateCrashedWorkerTask(
|
|
|
462
475
|
`[Heartbeat] Auto-failed task ${task.id.slice(0, 8)} — ${RESUME_BUDGET_EXHAUSTED_REASON} (${opts.shortLabel})`,
|
|
463
476
|
);
|
|
464
477
|
const remaining = getActiveTaskCount(task.agentId);
|
|
465
|
-
if (remaining === 0)
|
|
478
|
+
if (remaining === 0) restoreAgentIdleAfterRemediation(task.agentId);
|
|
466
479
|
}
|
|
467
480
|
return;
|
|
468
481
|
}
|
|
@@ -537,7 +550,7 @@ function remediateCrashedWorkerTask(
|
|
|
537
550
|
if (opts.cleanupActiveSession) deleteActiveSession(task.id);
|
|
538
551
|
|
|
539
552
|
const remaining = getActiveTaskCount(task.agentId);
|
|
540
|
-
if (remaining === 0)
|
|
553
|
+
if (remaining === 0) restoreAgentIdleAfterRemediation(task.agentId);
|
|
541
554
|
}
|
|
542
555
|
|
|
543
556
|
/**
|
|
@@ -622,7 +635,7 @@ export async function runRebootSweep(): Promise<void> {
|
|
|
622
635
|
|
|
623
636
|
// Fix agent status
|
|
624
637
|
if (getActiveTaskCount(task.agentId) === 0) {
|
|
625
|
-
|
|
638
|
+
restoreAgentIdleAfterRemediation(task.agentId);
|
|
626
639
|
}
|
|
627
640
|
|
|
628
641
|
// Don't retry system-generated heartbeat tasks
|
|
@@ -760,8 +773,20 @@ function autoAssignPoolTasks(findings: HeartbeatFindings): void {
|
|
|
760
773
|
// assigning to them would just have them exit on their next poll. Filter on
|
|
761
774
|
// the rows already returned (emptyPollCount is populated) rather than
|
|
762
775
|
// re-querying per worker via shouldBlockPolling().
|
|
776
|
+
// A multi-runtime agent whose runtimes have all died still reads as idle
|
|
777
|
+
// until its rows expire; assigning to it would strand the task, since
|
|
778
|
+
// nothing is left to poll for it.
|
|
779
|
+
// While the mode is on, only an agent with a live runtime can poll, so
|
|
780
|
+
// anything else — dead runtimes, or a worker that has not re-registered
|
|
781
|
+
// since the flag was enabled — would have the task stranded on it. With
|
|
782
|
+
// the flag off this is inert: legacy workers stop refreshing their
|
|
783
|
+
// retained rows, and filtering on them would park tasks on healthy agents.
|
|
784
|
+
const multiRuntime = isMultiRuntimeEnabled();
|
|
785
|
+
const withLiveRuntime = multiRuntime ? agentsWithLiveRuntime() : null;
|
|
763
786
|
const idleWorkers = getIdleWorkersWithCapacity().filter(
|
|
764
|
-
(w) =>
|
|
787
|
+
(w) =>
|
|
788
|
+
(w.emptyPollCount ?? 0) < MAX_EMPTY_POLLS &&
|
|
789
|
+
(withLiveRuntime === null || withLiveRuntime.has(w.id)),
|
|
765
790
|
);
|
|
766
791
|
if (idleWorkers.length === 0) return;
|
|
767
792
|
|
|
@@ -959,6 +984,16 @@ function escalateStarvedPoolTasks(findings: HeartbeatFindings): void {
|
|
|
959
984
|
}
|
|
960
985
|
}
|
|
961
986
|
|
|
987
|
+
/**
|
|
988
|
+
* Return a remediated agent to idle — unless multi-runtime liveness says no
|
|
989
|
+
* process is serving it. Recovery still runs for the task; the agent just
|
|
990
|
+
* must not be advertised as available when nothing can poll for it.
|
|
991
|
+
*/
|
|
992
|
+
function restoreAgentIdleAfterRemediation(agentId: string): void {
|
|
993
|
+
if (isMultiRuntimeEnabled() && countActiveRuntimeInstancesForAgent(agentId) === 0) return;
|
|
994
|
+
updateAgentStatus(agentId, "idle");
|
|
995
|
+
}
|
|
996
|
+
|
|
962
997
|
/**
|
|
963
998
|
* Call existing stale resource cleanup functions.
|
|
964
999
|
*/
|
|
@@ -1271,8 +1306,13 @@ export async function runHeartbeatSweep(): Promise<void> {
|
|
|
1271
1306
|
mentionProcessing: 0,
|
|
1272
1307
|
inboxProcessing: 0,
|
|
1273
1308
|
workflowRuns: 0,
|
|
1309
|
+
staleRuntimes: 0,
|
|
1274
1310
|
},
|
|
1275
1311
|
};
|
|
1312
|
+
// Expiry runs even on a cleanup-only tick: an idle agent whose runtime
|
|
1313
|
+
// stopped pinging is exactly the case the preflight gate sees as
|
|
1314
|
+
// "nothing actionable", and it would otherwise stay available forever.
|
|
1315
|
+
cleanupOnlyFindings.staleCleanup.staleRuntimes = expireStaleRuntimeInstances().expired;
|
|
1276
1316
|
await cleanupStaleResources(cleanupOnlyFindings);
|
|
1277
1317
|
logFindings(cleanupOnlyFindings);
|
|
1278
1318
|
return; // Nothing actionable — bail early
|
|
@@ -1345,14 +1385,28 @@ export function startHeartbeat(intervalMs = defaultIntervalMs()): void {
|
|
|
1345
1385
|
|
|
1346
1386
|
console.log(`[Heartbeat] Starting with ${intervalMs}ms interval`);
|
|
1347
1387
|
|
|
1348
|
-
// Run aggressive reboot sweep first (no thresholds), then normal sweep cycle
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1388
|
+
// Run aggressive reboot sweep first (no thresholds), then normal sweep cycle.
|
|
1389
|
+
// Both sweeps are best-effort: `runHeartbeatSweep` resets `isSweeping` in a
|
|
1390
|
+
// `finally` but has no `catch`, so a throw anywhere inside it escapes as an
|
|
1391
|
+
// unhandled rejection unless it is caught here.
|
|
1392
|
+
setTimeout(() => {
|
|
1393
|
+
runRebootSweep()
|
|
1394
|
+
.then(() => runHeartbeatSweep())
|
|
1395
|
+
.catch((err) =>
|
|
1396
|
+
console.error(
|
|
1397
|
+
"[Heartbeat] boot sweep failed:",
|
|
1398
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
1399
|
+
),
|
|
1400
|
+
);
|
|
1352
1401
|
}, 5000);
|
|
1353
1402
|
|
|
1354
1403
|
heartbeatInterval = setInterval(() => {
|
|
1355
|
-
runHeartbeatSweep()
|
|
1404
|
+
runHeartbeatSweep().catch((err) =>
|
|
1405
|
+
console.error(
|
|
1406
|
+
"[Heartbeat] sweep failed:",
|
|
1407
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
1408
|
+
),
|
|
1409
|
+
);
|
|
1356
1410
|
}, intervalMs);
|
|
1357
1411
|
|
|
1358
1412
|
// Also start the checklist interval
|
|
@@ -1434,7 +1488,12 @@ export function startHeartbeatChecklist(intervalMs = HEARTBEAT_CHECKLIST_INTERVA
|
|
|
1434
1488
|
|
|
1435
1489
|
// Recurring checklist starts from the second interval onward
|
|
1436
1490
|
checklistInterval = setInterval(() => {
|
|
1437
|
-
checkHeartbeatChecklist()
|
|
1491
|
+
checkHeartbeatChecklist().catch((err) =>
|
|
1492
|
+
console.error(
|
|
1493
|
+
"[Heartbeat] checklist check failed:",
|
|
1494
|
+
scrubSecrets(err instanceof Error ? err.message : String(err)),
|
|
1495
|
+
),
|
|
1496
|
+
);
|
|
1438
1497
|
}, intervalMs);
|
|
1439
1498
|
}
|
|
1440
1499
|
|
package/src/hooks/hook.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type RetrievalRow,
|
|
11
11
|
} from "../be/memory/raters/llm";
|
|
12
12
|
import {
|
|
13
|
+
buildIndependentIdentityPayloads,
|
|
13
14
|
contentSha256,
|
|
14
15
|
readIdentityBaselines,
|
|
15
16
|
warnProfileFileTooLarge,
|
|
@@ -110,6 +111,42 @@ export function shouldShowRegistrationNudge(opts: {
|
|
|
110
111
|
return true;
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
export async function postHookProfileUpdate({
|
|
115
|
+
url,
|
|
116
|
+
headers,
|
|
117
|
+
body,
|
|
118
|
+
label,
|
|
119
|
+
fetchImpl = fetch,
|
|
120
|
+
}: {
|
|
121
|
+
url: string;
|
|
122
|
+
headers: Record<string, string>;
|
|
123
|
+
body: Record<string, string>;
|
|
124
|
+
label: string;
|
|
125
|
+
fetchImpl?: typeof fetch;
|
|
126
|
+
}): Promise<void> {
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetchImpl(url, {
|
|
129
|
+
method: "PUT",
|
|
130
|
+
headers: {
|
|
131
|
+
...headers,
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(body),
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
const responseBody = await response.text();
|
|
138
|
+
console.error(
|
|
139
|
+
scrubSecrets(
|
|
140
|
+
`[hook] ${label} profile sync failed: HTTP ${response.status}${responseBody ? ` — ${responseBody}` : ""}`,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
146
|
+
console.error(scrubSecrets(`[hook] ${label} profile sync errored: ${message}`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
/**
|
|
114
151
|
* Hook response for blocking actions
|
|
115
152
|
* See: https://code.claude.com/docs/en/hooks
|
|
@@ -637,23 +674,12 @@ export async function handleHook(): Promise<void> {
|
|
|
637
674
|
const content = await file.text();
|
|
638
675
|
|
|
639
676
|
if (!content.trim()) return;
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
await fetch(`${getBaseUrl()}/api/agents/${agentId}/profile`, {
|
|
647
|
-
method: "PUT",
|
|
648
|
-
headers: {
|
|
649
|
-
...mcpConfig.headers,
|
|
650
|
-
"Content-Type": "application/json",
|
|
651
|
-
},
|
|
652
|
-
body: JSON.stringify({ claudeMd: content, changeSource: "session_sync" }),
|
|
653
|
-
});
|
|
654
|
-
} catch {
|
|
655
|
-
// Silently fail - don't block shutdown
|
|
656
|
-
}
|
|
677
|
+
await postHookProfileUpdate({
|
|
678
|
+
url: `${getBaseUrl()}/api/agents/${agentId}/profile`,
|
|
679
|
+
headers: mcpConfig.headers,
|
|
680
|
+
body: { claudeMd: content, changeSource: "session_sync" },
|
|
681
|
+
label: "claudeMd",
|
|
682
|
+
});
|
|
657
683
|
};
|
|
658
684
|
|
|
659
685
|
// Minimum length for SOUL.md and IDENTITY.md to prevent accidental corruption.
|
|
@@ -684,8 +710,6 @@ export async function handleHook(): Promise<void> {
|
|
|
684
710
|
const content = await soulFile.text();
|
|
685
711
|
if (baselines?.soulMd && contentSha256(content) === baselines.soulMd) {
|
|
686
712
|
// Unchanged during session — skip to preserve Lead's DB edits
|
|
687
|
-
} else if (content.length > MAX_PROFILE_FILE_LENGTH) {
|
|
688
|
-
warnProfileFileTooLarge(agentId, "soulMd", content.length, "hook");
|
|
689
713
|
} else if (content.trim()) {
|
|
690
714
|
if (content.length < IDENTITY_FILE_MIN_LENGTH) {
|
|
691
715
|
console.error(
|
|
@@ -702,8 +726,6 @@ export async function handleHook(): Promise<void> {
|
|
|
702
726
|
const content = await identityFile.text();
|
|
703
727
|
if (baselines?.identityMd && contentSha256(content) === baselines.identityMd) {
|
|
704
728
|
// Unchanged during session — skip
|
|
705
|
-
} else if (content.length > MAX_PROFILE_FILE_LENGTH) {
|
|
706
|
-
warnProfileFileTooLarge(agentId, "identityMd", content.length, "hook");
|
|
707
729
|
} else if (content.trim()) {
|
|
708
730
|
if (content.length < IDENTITY_FILE_MIN_LENGTH) {
|
|
709
731
|
console.error(
|
|
@@ -720,8 +742,6 @@ export async function handleHook(): Promise<void> {
|
|
|
720
742
|
const content = await toolsMdFile.text();
|
|
721
743
|
if (baselines?.toolsMd && contentSha256(content) === baselines.toolsMd) {
|
|
722
744
|
// Unchanged during session — skip
|
|
723
|
-
} else if (content.length > MAX_PROFILE_FILE_LENGTH) {
|
|
724
|
-
warnProfileFileTooLarge(agentId, "toolsMd", content.length, "hook");
|
|
725
745
|
} else if (content.trim()) {
|
|
726
746
|
updates.toolsMd = content;
|
|
727
747
|
}
|
|
@@ -741,17 +761,13 @@ export async function handleHook(): Promise<void> {
|
|
|
741
761
|
|
|
742
762
|
if (Object.keys(updates).length === 0) return;
|
|
743
763
|
|
|
744
|
-
|
|
745
|
-
await
|
|
746
|
-
|
|
747
|
-
headers:
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
},
|
|
751
|
-
body: JSON.stringify({ ...updates, changeSource }),
|
|
764
|
+
for (const payload of buildIndependentIdentityPayloads(updates, changeSource)) {
|
|
765
|
+
await postHookProfileUpdate({
|
|
766
|
+
url: `${getBaseUrl()}/api/agents/${agentId}/profile`,
|
|
767
|
+
headers: mcpConfig.headers,
|
|
768
|
+
body: payload.body,
|
|
769
|
+
label: payload.label,
|
|
752
770
|
});
|
|
753
|
-
} catch {
|
|
754
|
-
// Silently fail
|
|
755
771
|
}
|
|
756
772
|
};
|
|
757
773
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
updateActiveSessionProviderSessionId,
|
|
13
13
|
} from "../be/db";
|
|
14
14
|
import { ActiveSessionSchema, AgentTaskSchema } from "../types";
|
|
15
|
+
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
15
16
|
import { route } from "./route-def";
|
|
16
17
|
import { jsonError } from "./utils";
|
|
17
18
|
|
|
@@ -47,6 +48,7 @@ const createActiveSession = route({
|
|
|
47
48
|
inboxMessageId: z.string().optional(),
|
|
48
49
|
taskDescription: z.string().optional(),
|
|
49
50
|
runnerSessionId: z.string().optional(),
|
|
51
|
+
runtimeInstanceId: z.string().optional(),
|
|
50
52
|
}),
|
|
51
53
|
responses: {
|
|
52
54
|
201: { description: "Session created", schema: z.object({ session: ActiveSessionSchema }) },
|
|
@@ -166,6 +168,7 @@ export async function handleActiveSessions(
|
|
|
166
168
|
inboxMessageId: parsed.body.inboxMessageId,
|
|
167
169
|
taskDescription: parsed.body.taskDescription,
|
|
168
170
|
runnerSessionId: parsed.body.runnerSessionId,
|
|
171
|
+
runtimeInstanceId: parsed.body.runtimeInstanceId,
|
|
169
172
|
});
|
|
170
173
|
createActiveSession.respond(res, 201, { session });
|
|
171
174
|
return true;
|
|
@@ -211,7 +214,14 @@ export async function handleActiveSessions(
|
|
|
211
214
|
if (!parsed) return true;
|
|
212
215
|
let cleaned = 0;
|
|
213
216
|
if (parsed.body?.agentId) {
|
|
214
|
-
|
|
217
|
+
// Multi-runtime: several processes share this agent id, and a booting
|
|
218
|
+
// worker has no evidence distinguishing a crashed predecessor's session
|
|
219
|
+
// from a live-but-quiet sibling's — sessions heartbeat on tool activity
|
|
220
|
+
// only, and during the activation window a live worker's runtime may
|
|
221
|
+
// have no row at all. Reclamation stays with the heartbeat's
|
|
222
|
+
// stalled-task classifier (stale session AND stale task), backstopped
|
|
223
|
+
// by the sweep's stale-session cleanup; boot cleanup deletes nothing.
|
|
224
|
+
cleaned = isMultiRuntimeEnabled() ? 0 : cleanupAgentSessions(parsed.body.agentId);
|
|
215
225
|
} else {
|
|
216
226
|
cleaned = cleanupStaleSessions(parsed.body?.maxAgeMinutes ?? 30);
|
|
217
227
|
}
|