@desplega.ai/agent-swarm 1.157.0 → 1.158.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-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
- package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
- package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
- package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
- package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
- package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
- package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
- package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
- package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
- package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
- package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
- package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
- package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
- package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
- package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
- package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
- package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
- package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
- package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
- package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
- package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
- package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
- package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
- package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
- package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
- package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
- package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
- package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
- package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
- package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
- package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
- package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
- package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
- package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
- package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
- package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
- package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
- package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
- package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
- package/dist/cli-t1ze64h3.js +811 -0
- package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
- package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
- package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
- package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
- package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
- package/dist/cli.js +29 -15
- package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
- package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
- package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
- package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
- package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
- package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
- package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
- package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
- package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
- package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
- package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
- package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
- package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
- package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
- package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
- package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
- package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
- package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
- package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
- package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
- package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
- package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
- package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
- package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
- package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
- package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
- package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
- package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
- package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
- package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
- package/openapi.json +1626 -629
- package/package.json +2 -1
- package/src/apps/catalog.generated.json +229 -0
- package/src/apps/page-validator.ts +71 -16
- package/src/apps/row-store.ts +66 -11
- package/src/apps/sync.ts +59 -17
- package/src/artifact-sdk/browser-sdk.ts +436 -2
- package/src/be/approval-sweeps.ts +109 -0
- package/src/be/boot-seeds.ts +40 -0
- package/src/be/chunking.ts +1 -1
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/approvals.ts +78 -0
- package/src/be/db/tasks/read.ts +197 -39
- package/src/be/db/tasks/write.ts +41 -9
- package/src/be/db-client.ts +107 -48
- package/src/be/db.ts +219 -76
- package/src/be/events.ts +33 -1
- package/src/be/memory/graph-expansion.ts +10 -3
- package/src/be/memory/providers/sqlite-store.ts +55 -13
- package/src/be/memory/raters/retrieval.ts +5 -2
- package/src/be/memory/reranker.ts +26 -2
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
- package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
- package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
- package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +1857 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-normalize.ts +2 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/steering.ts +53 -3
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/task-citations.ts +29 -0
- package/src/cli.tsx +14 -1
- package/src/commands/onboard/compose-generator.ts +8 -3
- package/src/commands/onboard/container-engine.ts +220 -0
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/health-check.tsx +4 -1
- package/src/commands/onboard/steps/prereq-check.tsx +50 -54
- package/src/commands/onboard/steps/start.tsx +31 -16
- package/src/commands/onboard/types.ts +7 -2
- package/src/commands/onboard.tsx +39 -4
- package/src/commands/profile-sync.ts +79 -26
- package/src/commands/runner.ts +8 -1
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +263 -103
- package/src/hooks/hook.ts +21 -12
- package/src/http/agents.ts +35 -0
- package/src/http/approval-requests.ts +136 -64
- package/src/http/apps.ts +14 -3
- package/src/http/config.ts +15 -1
- package/src/http/events.ts +62 -2
- package/src/http/index.ts +6 -4
- package/src/http/memory.ts +21 -7
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +9 -4
- package/src/http/sessions.ts +25 -18
- package/src/http/stats.ts +19 -7
- package/src/http/status.ts +11 -1
- package/src/http/tasks.ts +40 -8
- package/src/http/webhooks.ts +67 -46
- package/src/http/workflows.ts +15 -20
- package/src/linear/outbound.ts +37 -15
- package/src/otel-impl.ts +151 -1
- package/src/otel.ts +14 -3
- package/src/prompts/memories.ts +19 -2
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/modelsdev-reasoning.json +551 -0
- package/src/rbac/legacy-policy.ts +16 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/schedule-task.ts +3 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +12 -7
- package/src/slack/app.ts +35 -4
- package/src/slack/connection-state.ts +17 -0
- package/src/tasks/context-key.ts +1 -0
- package/src/tasks/worker-follow-up.ts +6 -2
- package/src/tests/agent-task-activity.test.ts +74 -0
- package/src/tests/agents-list-model-display.test.ts +6 -1
- package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
- package/src/tests/approval-requests.test.ts +395 -0
- package/src/tests/approval-sweeps-config.test.ts +47 -0
- package/src/tests/approval-sweeps.test.ts +508 -0
- package/src/tests/apps-kibo-blocks.test.ts +77 -0
- package/src/tests/apps-spike.test.ts +56 -1
- package/src/tests/apps-sync-engine.test.ts +20 -9
- package/src/tests/boot-seeds.test.ts +170 -0
- package/src/tests/cancel-approval-request-tool.test.ts +115 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/config-key-filter.test.ts +80 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-client-busy-retry.test.ts +96 -0
- package/src/tests/db-client.test.ts +60 -1
- package/src/tests/db-task-read-contract.test.ts +10 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/events-http.test.ts +103 -0
- package/src/tests/extensions-catalog.test.ts +22 -17
- package/src/tests/extensions-pre-task.test.ts +107 -0
- package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
- package/src/tests/follow-up-config-inheritance.test.ts +260 -0
- package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
- package/src/tests/heartbeat-reboot-live.test.ts +95 -0
- package/src/tests/heartbeat-stall-cas.test.ts +80 -0
- package/src/tests/heartbeat.test.ts +99 -3
- package/src/tests/hook-mcp-config.test.ts +29 -0
- package/src/tests/http-api-integration.test.ts +16 -2
- package/src/tests/kv-storage.test.ts +62 -0
- package/src/tests/linear-outbound-sync.test.ts +120 -5
- package/src/tests/list-endpoint-slimming.test.ts +91 -0
- package/src/tests/memory-graph-organic-route.test.ts +155 -0
- package/src/tests/memory-http-recall-gating.test.ts +14 -4
- package/src/tests/memory-hybrid.test.ts +161 -1
- package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
- package/src/tests/memory.test.ts +22 -0
- package/src/tests/onboard-container-engine.test.ts +240 -0
- package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
- package/src/tests/pricing-routes.test.ts +14 -0
- package/src/tests/profile-sync-warning.test.ts +8 -4
- package/src/tests/promote-draft-task-route.test.ts +19 -0
- package/src/tests/provider-registration.test.ts +296 -42
- package/src/tests/rbac-engine.test.ts +30 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/sessions-detail-batch.test.ts +250 -0
- package/src/tests/sessions.test.ts +118 -0
- package/src/tests/slack-config.test.ts +22 -0
- package/src/tests/sonnet-5-5-registry.test.ts +44 -0
- package/src/tests/status.test.ts +36 -1
- package/src/tests/swarm-diff.test.ts +316 -10
- package/src/tests/task-completion-idempotency.test.ts +2 -1
- package/src/tests/tasks-requested-by-filter.test.ts +3 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/ui-list-slim-projections.test.ts +158 -0
- package/src/tests/webhooks-task-blocked.test.ts +127 -0
- package/src/tests/workflow-approval-recovery.test.ts +203 -0
- package/src/tests/workflow-hitl-routing.test.ts +22 -1
- package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
- package/src/tests/workflow-http-v2.test.ts +99 -8
- package/src/tests/workflow-retry-validation.test.ts +56 -0
- package/src/tests/workflow-tla-races.test.ts +389 -0
- package/src/tests/workflow-validation-port-routing.test.ts +113 -1
- package/src/tools/cancel-approval-request.ts +57 -0
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/request-human-input.ts +4 -2
- package/src/tools/send-task.ts +4 -0
- package/src/tools/templates.ts +19 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/workflows/list-workflow-runs.ts +1 -0
- package/src/types.ts +38 -0
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/approval-cancel.ts +152 -0
- package/src/workflows/approval-notifications.ts +84 -0
- package/src/workflows/checkpoint.ts +10 -5
- package/src/workflows/definition.ts +21 -0
- package/src/workflows/engine.ts +152 -21
- package/src/workflows/executors/human-in-the-loop.ts +1 -1
- package/src/workflows/recovery.ts +100 -45
- package/src/workflows/resume.ts +49 -18
- package/src/workflows/retry-poller.ts +71 -21
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/github-sender-allowlist/README.md +18 -0
- package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
- package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
- package/templates/skills/apps/content.md +20 -1
- package/templates/skills/code-quality/content.md +26 -1
- package/templates/skills/pages/content.md +16 -4
- package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
- package/dist/cli-2daksqps.js +0 -378
- package/dist/rbac-roles-3gxmnb20.js +0 -37
|
@@ -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 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 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';
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
autoCancelStaleApprovalRequests,
|
|
3
|
+
timeoutExpiredApprovalRequests,
|
|
4
|
+
} from "../be/approval-sweeps";
|
|
1
5
|
import {
|
|
2
6
|
assignUnassignedTaskPending,
|
|
3
7
|
backfillSupersedeTaskResumeTaskId,
|
|
4
8
|
buildRoutingAffinityFromAgent,
|
|
9
|
+
cascadeFailDependents,
|
|
5
10
|
cleanupStaleSessions,
|
|
6
11
|
createLogEntry,
|
|
7
12
|
createTaskExtended,
|
|
@@ -12,6 +17,7 @@ import {
|
|
|
12
17
|
getActiveTaskCount,
|
|
13
18
|
getAllAgents,
|
|
14
19
|
getDbClient,
|
|
20
|
+
getDependentTasks,
|
|
15
21
|
getIdleWorkersWithCapacity,
|
|
16
22
|
getLeadAgent,
|
|
17
23
|
getPendingSteeringForTask,
|
|
@@ -21,6 +27,7 @@ import {
|
|
|
21
27
|
getStalePinnedResumes,
|
|
22
28
|
getStaleUnassignedAffinityTasks,
|
|
23
29
|
getStalledInProgressTasks,
|
|
30
|
+
getSupersededTasksWithoutResume,
|
|
24
31
|
getTaskById,
|
|
25
32
|
getTaskStats,
|
|
26
33
|
getTasksByStatus,
|
|
@@ -257,6 +264,8 @@ export interface HeartbeatFindings {
|
|
|
257
264
|
staleRuntimes: number;
|
|
258
265
|
staleOfferedTasks: number;
|
|
259
266
|
abandonedDraftTasks: number;
|
|
267
|
+
approvalAutoCancelled: number;
|
|
268
|
+
approvalTimedOut: number;
|
|
260
269
|
};
|
|
261
270
|
}
|
|
262
271
|
|
|
@@ -334,6 +343,8 @@ export async function codeLevelTriage(): Promise<HeartbeatFindings> {
|
|
|
334
343
|
staleRuntimes: 0,
|
|
335
344
|
staleOfferedTasks: 0,
|
|
336
345
|
abandonedDraftTasks: 0,
|
|
346
|
+
approvalAutoCancelled: 0,
|
|
347
|
+
approvalTimedOut: 0,
|
|
337
348
|
},
|
|
338
349
|
};
|
|
339
350
|
|
|
@@ -352,6 +363,10 @@ export async function codeLevelTriage(): Promise<HeartbeatFindings> {
|
|
|
352
363
|
// 1. Detect and remediate stalled tasks (tiered: auto-fail dead workers)
|
|
353
364
|
await detectAndRemediateStalledTasks(findings);
|
|
354
365
|
|
|
366
|
+
// 1.5. Give a resume to any task superseded without one (crash between
|
|
367
|
+
// the supersede write and the resume write).
|
|
368
|
+
await repairSupersededWithoutResume(findings);
|
|
369
|
+
|
|
355
370
|
// 2. Check and fix worker health
|
|
356
371
|
await checkWorkerHealth(findings);
|
|
357
372
|
|
|
@@ -498,6 +513,40 @@ async function detectAndRemediateStalledTasks(findings: HeartbeatFindings): Prom
|
|
|
498
513
|
}
|
|
499
514
|
}
|
|
500
515
|
|
|
516
|
+
/** Repair window: skip in-flight supersedes, stop at a day of history. */
|
|
517
|
+
const ORPHAN_SUPERSEDE_MIN_AGE_MS = 60 * 1000;
|
|
518
|
+
const ORPHAN_SUPERSEDE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Supersede and resume creation are separate writes. If the API process dies
|
|
522
|
+
* between them, the task stays `superseded` with no resume and nothing else
|
|
523
|
+
* picks it up (the stall classifier only reads `in_progress`). This creates
|
|
524
|
+
* the missing resume. Found by the TLA+ model (specs/tla/heartbeat/).
|
|
525
|
+
*/
|
|
526
|
+
async function repairSupersededWithoutResume(findings: HeartbeatFindings): Promise<void> {
|
|
527
|
+
const now = Date.now();
|
|
528
|
+
const orphans = await getSupersededTasksWithoutResume(
|
|
529
|
+
new Date(now - ORPHAN_SUPERSEDE_MIN_AGE_MS).toISOString(),
|
|
530
|
+
new Date(now - ORPHAN_SUPERSEDE_MAX_AGE_MS).toISOString(),
|
|
531
|
+
);
|
|
532
|
+
for (const task of orphans) {
|
|
533
|
+
if (!task.agentId) continue;
|
|
534
|
+
if (getNextResumeGeneration(task) > maxResumeGenerations()) continue;
|
|
535
|
+
const resume = await createResumeFollowUp({ parentId: task.id, reason: "crash_recovery" });
|
|
536
|
+
if (resume.kind !== "created") continue;
|
|
537
|
+
await backfillSupersedeTaskResumeTaskId(task.id, resume.task.id);
|
|
538
|
+
findings.autoResumedTasks.push({
|
|
539
|
+
taskId: task.id,
|
|
540
|
+
resumeTaskId: resume.task.id,
|
|
541
|
+
agentId: task.agentId,
|
|
542
|
+
reason: "repaired superseded task without resume",
|
|
543
|
+
});
|
|
544
|
+
console.log(
|
|
545
|
+
`[Heartbeat] Created missing resume ${resume.task.id.slice(0, 8)} for superseded task ${task.id.slice(0, 8)}`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
501
550
|
function isRemediationAction(value: unknown): value is RemediationAction {
|
|
502
551
|
return typeof value === "string" && REMEDIATION_ACTIONS.has(value as RemediationAction);
|
|
503
552
|
}
|
|
@@ -556,7 +605,10 @@ async function remediateCrashedWorkerTask(
|
|
|
556
605
|
if (!task.agentId) return; // Type guard — caller already checked.
|
|
557
606
|
|
|
558
607
|
if (decision.action === "fail") {
|
|
559
|
-
|
|
608
|
+
// CAS on the lastUpdatedAt this sweep read: progress since then wins.
|
|
609
|
+
const failed = await failTask(task.id, decision.reason, {
|
|
610
|
+
expectedLastUpdatedAt: task.lastUpdatedAt,
|
|
611
|
+
});
|
|
560
612
|
if (failed) {
|
|
561
613
|
findings.autoFailedTasks.push({
|
|
562
614
|
taskId: task.id,
|
|
@@ -578,6 +630,8 @@ async function remediateCrashedWorkerTask(
|
|
|
578
630
|
const superseded = await supersedeTask(task.id, {
|
|
579
631
|
reason: decision.reason,
|
|
580
632
|
resumeTaskId: null,
|
|
633
|
+
// CAS on the lastUpdatedAt this sweep read: progress since then wins.
|
|
634
|
+
expectedLastUpdatedAt: task.lastUpdatedAt,
|
|
581
635
|
});
|
|
582
636
|
if (!superseded) {
|
|
583
637
|
return;
|
|
@@ -737,128 +791,145 @@ export async function runRebootSweep(): Promise<void> {
|
|
|
737
791
|
// Heartbeated after (or within skew of) this boot → genuinely live, skip
|
|
738
792
|
continue;
|
|
739
793
|
}
|
|
740
|
-
//
|
|
794
|
+
// Workers run in their own containers and outlive an API restart, and
|
|
795
|
+
// sessions heartbeat on tool calls only. A pre-boot heartbeat is
|
|
796
|
+
// therefore not evidence of a dead worker: a live one inside a long
|
|
797
|
+
// model call has none in the first seconds after boot. Only a session
|
|
798
|
+
// stale by the classifier's own threshold counts as dead; anything
|
|
799
|
+
// fresher is left to the regular stalled-task sweep.
|
|
800
|
+
if (Date.now() - sessionLastSeen < stallThresholdStaleHeartbeatMin() * 60 * 1000) {
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
// Stale session → fall through to auto-fail + reboot-retry child
|
|
741
804
|
}
|
|
742
805
|
|
|
743
806
|
// Clean up pre-boot stale session before failing (if it existed)
|
|
744
807
|
if (session) await deleteActiveSession(task.id);
|
|
745
808
|
|
|
746
|
-
// Auto-fail the task
|
|
747
|
-
|
|
809
|
+
// Auto-fail the task. Its dependents are settled in the `finally` below
|
|
810
|
+
// (re-pointed to the retry child, or cascade-failed when there is none)
|
|
811
|
+
// instead of by failTask's unconditional cascade.
|
|
812
|
+
const failed = await failTask(task.id, reason, { cascadeDependents: false });
|
|
748
813
|
if (!failed) continue;
|
|
749
814
|
|
|
750
|
-
// Fix agent status
|
|
751
|
-
if ((await getActiveTaskCount(task.agentId)) === 0) {
|
|
752
|
-
await restoreAgentIdleAfterRemediation(task.agentId);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// Don't retry system-generated heartbeat tasks
|
|
756
|
-
if (SKIP_AUTO_RESUME_TYPES.has(task.taskType ?? "")) {
|
|
757
|
-
rebootAffectedTasks.push({ original: failed, retryTaskId: null });
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// Auto-retry: create a replacement task with parentTaskId
|
|
762
815
|
let retryTaskId: string | null = null;
|
|
816
|
+
try {
|
|
817
|
+
// Fix agent status
|
|
818
|
+
if ((await getActiveTaskCount(task.agentId)) === 0) {
|
|
819
|
+
await restoreAgentIdleAfterRemediation(task.agentId);
|
|
820
|
+
}
|
|
763
821
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
);
|
|
770
|
-
rebootAffectedTasks.push({ original: failed, retryTaskId: null });
|
|
771
|
-
continue;
|
|
772
|
-
}
|
|
822
|
+
// Don't retry system-generated heartbeat tasks
|
|
823
|
+
if (SKIP_AUTO_RESUME_TYPES.has(task.taskType ?? "")) {
|
|
824
|
+
rebootAffectedTasks.push({ original: failed, retryTaskId: null });
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
773
827
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
828
|
+
// Auto-retry: create a replacement task with parentTaskId
|
|
829
|
+
// A corrupt persisted affinity is never converted into an untagged
|
|
830
|
+
// retry by this independent recovery path.
|
|
831
|
+
if (task.routingAffinityInvalid) {
|
|
832
|
+
console.error(
|
|
833
|
+
`[Heartbeat] Reboot retry suppressed for ${task.id.slice(0, 8)}: invalid routing affinity`,
|
|
834
|
+
);
|
|
835
|
+
rebootAffectedTasks.push({ original: failed, retryTaskId: null });
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Guard: only retry if parent doesn't already have a retry child
|
|
840
|
+
const existingRetry = await getDbClient().get<{ id: string }>(
|
|
841
|
+
`SELECT id FROM agent_tasks
|
|
777
842
|
WHERE parentTaskId = ?
|
|
778
843
|
AND status NOT IN ('completed', 'failed', 'cancelled')
|
|
779
844
|
LIMIT 1`,
|
|
780
|
-
|
|
781
|
-
|
|
845
|
+
[task.id],
|
|
846
|
+
);
|
|
782
847
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
848
|
+
if (!existingRetry) {
|
|
849
|
+
try {
|
|
850
|
+
// Routing affinity (Phase 3): pin the retry child to the original
|
|
851
|
+
// agent when it still looks recoverable (row exists, not offline,
|
|
852
|
+
// has capacity) — the same gate `createResumeFollowUp` uses for its
|
|
853
|
+
// same-agent pin. This keeps the retry off the role-blind pool
|
|
854
|
+
// whenever the original worker is merely restarting. Always stamp
|
|
855
|
+
// a `routingAffinity` snapshot from the original agent — even on
|
|
856
|
+
// the pool-fallback leg (agent gone/offline/at-capacity) — so that
|
|
857
|
+
// leg is still role/capability-gated instead of role-blind.
|
|
858
|
+
let preferredAgentId: string | undefined;
|
|
859
|
+
let sourceCandidate = await getPinCandidateAgent(task.agentId);
|
|
860
|
+
if (sourceCandidate) {
|
|
861
|
+
const activeCount = await getActiveTaskCount(sourceCandidate.id);
|
|
862
|
+
const maxTasks = sourceCandidate.maxTasks ?? 1;
|
|
863
|
+
const hasCap = activeCount < maxTasks;
|
|
864
|
+
if (hasCap) {
|
|
865
|
+
preferredAgentId = sourceCandidate.id;
|
|
866
|
+
} else {
|
|
867
|
+
sourceCandidate = null;
|
|
868
|
+
console.warn(
|
|
869
|
+
`[Heartbeat] Reboot retry for task ${task.id.slice(0, 8)} NOT pinned: agent ${task.agentId.slice(0, 8)} at capacity (${activeCount}/${maxTasks}); falling back to affinity-gated pool`,
|
|
870
|
+
);
|
|
871
|
+
}
|
|
806
872
|
}
|
|
807
|
-
}
|
|
808
873
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
agentId: preferredAgentId,
|
|
828
|
-
routingReason:
|
|
829
|
-
preferredAgentId === undefined
|
|
830
|
-
? undefined
|
|
831
|
-
: preferredAgentId === task.agentId
|
|
832
|
-
? "continuity"
|
|
833
|
-
: "reroute_fault",
|
|
834
|
-
routingSource: preferredAgentId === undefined ? undefined : "engine_default",
|
|
835
|
-
tags,
|
|
836
|
-
priority: task.priority,
|
|
837
|
-
source: task.source,
|
|
838
|
-
taskType: task.taskType ?? undefined,
|
|
839
|
-
routingAffinity,
|
|
840
|
-
});
|
|
841
|
-
if (authorization.decision) {
|
|
842
|
-
await createLogEntry({
|
|
843
|
-
eventType: "task_recovery_authorization",
|
|
844
|
-
taskId: retryTask.id,
|
|
874
|
+
const leadOnly = task.routingAffinity?.leadOnly === true;
|
|
875
|
+
const authorization = await resolveLeadOnlyRecoveryAssignment(task, sourceCandidate);
|
|
876
|
+
if (leadOnly) preferredAgentId = authorization.agentId;
|
|
877
|
+
|
|
878
|
+
const tags = ["reboot-retry", "auto-generated"];
|
|
879
|
+
if (preferredAgentId !== undefined) tags.push(REBOOT_RETRY_PIN_TAG);
|
|
880
|
+
const sourceAffinity = (await buildRoutingAffinityFromAgent(task.agentId)) ?? undefined;
|
|
881
|
+
const routingAffinity = leadOnly
|
|
882
|
+
? {
|
|
883
|
+
...(sourceAffinity ?? task.routingAffinity),
|
|
884
|
+
capabilities:
|
|
885
|
+
task.routingAffinity?.capabilities ?? sourceAffinity?.capabilities ?? [],
|
|
886
|
+
leadOnly: true,
|
|
887
|
+
}
|
|
888
|
+
: sourceAffinity;
|
|
889
|
+
|
|
890
|
+
const retryTask = await createTaskExtended(task.task, {
|
|
891
|
+
parentTaskId: task.id,
|
|
845
892
|
agentId: preferredAgentId,
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
893
|
+
routingReason:
|
|
894
|
+
preferredAgentId === undefined
|
|
895
|
+
? undefined
|
|
896
|
+
: preferredAgentId === task.agentId
|
|
897
|
+
? "continuity"
|
|
898
|
+
: "reroute_fault",
|
|
899
|
+
routingSource: preferredAgentId === undefined ? undefined : "engine_default",
|
|
900
|
+
tags,
|
|
901
|
+
priority: task.priority,
|
|
902
|
+
source: task.source,
|
|
903
|
+
taskType: task.taskType ?? undefined,
|
|
904
|
+
// The retry re-runs the same work, so it keeps the creator's
|
|
905
|
+
// onCompleted/onFailed.
|
|
906
|
+
inheritParentFollowUpConfig: true,
|
|
907
|
+
routingAffinity,
|
|
852
908
|
});
|
|
909
|
+
if (authorization.decision) {
|
|
910
|
+
await createLogEntry({
|
|
911
|
+
eventType: "task_recovery_authorization",
|
|
912
|
+
taskId: retryTask.id,
|
|
913
|
+
agentId: preferredAgentId,
|
|
914
|
+
metadata: {
|
|
915
|
+
leadOnly: true,
|
|
916
|
+
parentTaskId: task.id,
|
|
917
|
+
sourceAgentId: task.agentId,
|
|
918
|
+
decision: authorization.decision,
|
|
919
|
+
},
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
retryTaskId = retryTask.id;
|
|
923
|
+
console.log(`[Heartbeat] Reboot retry created: ${retryTaskId} (parent: ${task.id})`);
|
|
924
|
+
} catch (err) {
|
|
925
|
+
console.error(`[Heartbeat] Failed to create retry task for ${task.id}:`, err);
|
|
853
926
|
}
|
|
854
|
-
retryTaskId = retryTask.id;
|
|
855
|
-
console.log(`[Heartbeat] Reboot retry created: ${retryTaskId} (parent: ${task.id})`);
|
|
856
|
-
} catch (err) {
|
|
857
|
-
console.error(`[Heartbeat] Failed to create retry task for ${task.id}:`, err);
|
|
858
927
|
}
|
|
859
|
-
}
|
|
860
928
|
|
|
861
|
-
|
|
929
|
+
rebootAffectedTasks.push({ original: failed, retryTaskId });
|
|
930
|
+
} finally {
|
|
931
|
+
await settleRebootSweptDependents(task.id, retryTaskId);
|
|
932
|
+
}
|
|
862
933
|
}
|
|
863
934
|
|
|
864
935
|
console.log(
|
|
@@ -869,6 +940,67 @@ export async function runRebootSweep(): Promise<void> {
|
|
|
869
940
|
}
|
|
870
941
|
}
|
|
871
942
|
|
|
943
|
+
/** Statuses a dependent can hold before it ever started running. */
|
|
944
|
+
const NEVER_STARTED_STATUSES = new Set([
|
|
945
|
+
"draft",
|
|
946
|
+
"backlog",
|
|
947
|
+
"unassigned",
|
|
948
|
+
"offered",
|
|
949
|
+
"reviewing",
|
|
950
|
+
"pending",
|
|
951
|
+
]);
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Settle the dependents of a reboot-swept task. With a retry child, every
|
|
955
|
+
* never-started dependent has the swept id in `dependsOn` replaced by the
|
|
956
|
+
* retry id, so it waits on the retry instead of dying with `Blocked dependency
|
|
957
|
+
* … was failed`. The row keeps its id, agent, Slack fields, follow-up config,
|
|
958
|
+
* priority and parent. Anything still depending on the swept task afterwards
|
|
959
|
+
* (no retry, or a dependent that already started) cascade-fails as before.
|
|
960
|
+
*/
|
|
961
|
+
async function settleRebootSweptDependents(
|
|
962
|
+
sweptId: string,
|
|
963
|
+
retryTaskId: string | null,
|
|
964
|
+
): Promise<void> {
|
|
965
|
+
try {
|
|
966
|
+
if (retryTaskId) {
|
|
967
|
+
for (const dep of await getDependentTasks(sweptId)) {
|
|
968
|
+
if (!NEVER_STARTED_STATUSES.has(dep.status)) continue;
|
|
969
|
+
const dependsOn = [
|
|
970
|
+
...new Set(dep.dependsOn.map((id) => (id === sweptId ? retryTaskId : id))),
|
|
971
|
+
];
|
|
972
|
+
// Status + membership predicates make the re-point a no-op if the
|
|
973
|
+
// dependent was claimed or re-pointed since it was read.
|
|
974
|
+
const row = await getDbClient().get<{ id: string }>(
|
|
975
|
+
`UPDATE agent_tasks
|
|
976
|
+
SET dependsOn = ?, lastUpdatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
977
|
+
WHERE id = ? AND status = ?
|
|
978
|
+
AND EXISTS (SELECT 1 FROM json_each(agent_tasks.dependsOn) WHERE value = ?)
|
|
979
|
+
RETURNING id`,
|
|
980
|
+
[JSON.stringify(dependsOn), dep.id, dep.status, sweptId],
|
|
981
|
+
);
|
|
982
|
+
if (!row) continue;
|
|
983
|
+
try {
|
|
984
|
+
await createLogEntry({
|
|
985
|
+
eventType: "task_dependency_repointed",
|
|
986
|
+
taskId: dep.id,
|
|
987
|
+
agentId: dep.agentId ?? undefined,
|
|
988
|
+
oldValue: sweptId,
|
|
989
|
+
newValue: retryTaskId,
|
|
990
|
+
metadata: { reason: "reboot_retry" },
|
|
991
|
+
});
|
|
992
|
+
} catch {}
|
|
993
|
+
console.log(
|
|
994
|
+
`[Heartbeat] Reboot sweep: dependent ${dep.id.slice(0, 8)} re-pointed from ${sweptId.slice(0, 8)} to retry ${retryTaskId.slice(0, 8)}`,
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
await cascadeFailDependents(sweptId, "failed");
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
console.error(`[Heartbeat] Reboot sweep: settling dependents of ${sweptId} failed:`, err);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
872
1004
|
/** Get tasks affected by the most recent reboot sweep */
|
|
873
1005
|
export function getRebootAffectedTasks() {
|
|
874
1006
|
return rebootAffectedTasks;
|
|
@@ -1187,6 +1319,24 @@ async function cleanupStaleResources(findings: HeartbeatFindings): Promise<void>
|
|
|
1187
1319
|
// Workflow engine may not be initialized yet — skip recovery
|
|
1188
1320
|
findings.staleCleanup.workflowRuns = 0;
|
|
1189
1321
|
}
|
|
1322
|
+
// Approval sweeps run after the recovery pass, so a waiting run past its
|
|
1323
|
+
// expiresAt is routed on its timeout port before the timeout sweep reads it.
|
|
1324
|
+
try {
|
|
1325
|
+
findings.staleCleanup.approvalTimedOut = (
|
|
1326
|
+
await timeoutExpiredApprovalRequests()
|
|
1327
|
+
).timedOut.length;
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
console.error("[heartbeat] approval timeout sweep failed:", err);
|
|
1330
|
+
findings.staleCleanup.approvalTimedOut = 0;
|
|
1331
|
+
}
|
|
1332
|
+
try {
|
|
1333
|
+
findings.staleCleanup.approvalAutoCancelled = (
|
|
1334
|
+
await autoCancelStaleApprovalRequests()
|
|
1335
|
+
).cancelled.length;
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
console.error("[heartbeat] approval auto-cancel sweep failed:", err);
|
|
1338
|
+
findings.staleCleanup.approvalAutoCancelled = 0;
|
|
1339
|
+
}
|
|
1190
1340
|
}
|
|
1191
1341
|
|
|
1192
1342
|
// ============================================================================
|
|
@@ -1484,6 +1634,8 @@ export async function runHeartbeatSweep(): Promise<void> {
|
|
|
1484
1634
|
staleRuntimes: 0,
|
|
1485
1635
|
staleOfferedTasks: 0,
|
|
1486
1636
|
abandonedDraftTasks: 0,
|
|
1637
|
+
approvalAutoCancelled: 0,
|
|
1638
|
+
approvalTimedOut: 0,
|
|
1487
1639
|
},
|
|
1488
1640
|
};
|
|
1489
1641
|
// Expiry runs even on a cleanup-only tick: an idle agent whose runtime
|
|
@@ -1553,9 +1705,17 @@ function logFindings(findings: HeartbeatFindings): void {
|
|
|
1553
1705
|
workflowRuns,
|
|
1554
1706
|
staleOfferedTasks,
|
|
1555
1707
|
abandonedDraftTasks,
|
|
1708
|
+
approvalAutoCancelled,
|
|
1709
|
+
approvalTimedOut,
|
|
1556
1710
|
} = findings.staleCleanup;
|
|
1557
1711
|
const totalCleanup =
|
|
1558
|
-
sessions +
|
|
1712
|
+
sessions +
|
|
1713
|
+
reviewingTasks +
|
|
1714
|
+
mentionProcessing +
|
|
1715
|
+
inboxProcessing +
|
|
1716
|
+
workflowRuns +
|
|
1717
|
+
approvalAutoCancelled +
|
|
1718
|
+
approvalTimedOut;
|
|
1559
1719
|
if (totalCleanup > 0) {
|
|
1560
1720
|
parts.push(`stale_cleanup=${totalCleanup}`);
|
|
1561
1721
|
}
|
package/src/hooks/hook.ts
CHANGED
|
@@ -42,6 +42,26 @@ type McpServerConfig = {
|
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
export async function loadHookMcpConfig(
|
|
46
|
+
projectDir: string,
|
|
47
|
+
workspaceDir = "/workspace",
|
|
48
|
+
): Promise<McpServerConfig | undefined> {
|
|
49
|
+
for (const configDir of new Set([projectDir, workspaceDir])) {
|
|
50
|
+
try {
|
|
51
|
+
const mcpFile = Bun.file(`${configDir}/.mcp.json`);
|
|
52
|
+
if (!(await mcpFile.exists())) continue;
|
|
53
|
+
|
|
54
|
+
const config = await mcpFile.json();
|
|
55
|
+
const serverConfig = config?.mcpServers?.[SERVER_NAME] as McpServerConfig | undefined;
|
|
56
|
+
if (serverConfig) return serverConfig;
|
|
57
|
+
} catch {
|
|
58
|
+
// Try the next config location.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
interface HookMessage {
|
|
46
66
|
hook_event_name: string;
|
|
47
67
|
session_id?: string;
|
|
@@ -493,18 +513,7 @@ export async function runSessionSummaryFromStdin(): Promise<void> {
|
|
|
493
513
|
*/
|
|
494
514
|
export async function handleHook(): Promise<void> {
|
|
495
515
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
496
|
-
|
|
497
|
-
let mcpConfig: McpServerConfig | undefined;
|
|
498
|
-
|
|
499
|
-
try {
|
|
500
|
-
const mcpFile = Bun.file(`${projectDir}/.mcp.json`);
|
|
501
|
-
if (await mcpFile.exists()) {
|
|
502
|
-
const config = await mcpFile.json();
|
|
503
|
-
mcpConfig = config?.mcpServers?.[SERVER_NAME] as McpServerConfig;
|
|
504
|
-
}
|
|
505
|
-
} catch {
|
|
506
|
-
// No config found, proceed without MCP
|
|
507
|
-
}
|
|
516
|
+
const mcpConfig = await loadHookMcpConfig(projectDir);
|
|
508
517
|
|
|
509
518
|
let msg: HookMessage;
|
|
510
519
|
try {
|