@humwick/server 0.130.2 → 0.130.3
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/bundled/prompts/manifest.json +1 -0
- package/bundled/prompts/task.hydration.md +1 -1
- package/bundled/roles/builder.md +1 -1
- package/docs/guides/sub-coordinators.md +12 -5
- package/docs/guides/tools-coordination.md +1 -0
- package/docs/guides/workflows.md +16 -0
- package/docs/openapi.json +73 -4
- package/package.json +8 -7
- package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-Bq9kr21N.js} +2 -2
- package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-Dqiroy06.js} +2 -2
- package/public/assets/{TraceView-Wimm0Czv.js → TraceView-DYU962-H.js} +2 -2
- package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DhPfzioE.js} +2 -2
- package/public/assets/{activity-CrSurn-r.js → activity-DzCcQTee.js} +2 -2
- package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-SgDt9i3T.js} +2 -2
- package/public/assets/agent-liveness-Bq-s73jW.js +2 -0
- package/public/assets/{agent-name-DKbiIhBo.js → agent-name-CcY3Aijy.js} +2 -2
- package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-cxtRBn6O.js} +2 -2
- package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-DGYbIhaI.js} +2 -2
- package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-DHbftV0N.js} +2 -2
- package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CY113sp-.js} +2 -2
- package/public/assets/{agents-B-v66C-4.js → agents-BPzY7IlQ.js} +2 -2
- package/public/assets/{analytics-BqCb-LKi.js → analytics-DENFPv_a.js} +2 -2
- package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-KRvSc7sl.js} +2 -2
- package/public/assets/{artifacts-DJevXcvg.js → artifacts-C9gSmL3s.js} +2 -2
- package/public/assets/{artifacts-D32zyw4Y.js → artifacts-DT-HkI5O.js} +2 -2
- package/public/assets/{automation-BpRajx_5.js → automation-BsmjQhKD.js} +2 -2
- package/public/assets/{backlog-D_UjTdzX.js → backlog-8NYH_62t.js} +2 -2
- package/public/assets/{busy-health-N3VEh4ao.js → busy-health-D3hwvYb4.js} +2 -2
- package/public/assets/{channels-9jopungq.js → channels-DCJ5WL7O.js} +2 -2
- package/public/assets/{chat-CSXrTB3C.js → chat-DAIl357U.js} +3 -3
- package/public/assets/{code-preview-p1NnmqK0.js → code-preview-CzrZfr9U.js} +2 -2
- package/public/assets/{connectors-CZdqlDNL.js → connectors-xTxtlyou.js} +2 -2
- package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BpebFLK2.js} +2 -2
- package/public/assets/{crate-agent-30aXTKva.js → crate-agent-BHvI5wXC.js} +2 -2
- package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-DGT0o3wJ.js} +2 -2
- package/public/assets/{deploys-DoN6bAb5.js → deploys-hqWOHSHE.js} +2 -2
- package/public/assets/{display-BrKWFGJz.js → display-CDeH4I8T.js} +2 -2
- package/public/assets/{files-D6XEu8Ng.js → files-BTeGVmTg.js} +2 -2
- package/public/assets/{fleet-C7jQWbw2.js → fleet-DgXtNgCa.js} +2 -2
- package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BHEt5yc_.js} +2 -2
- package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-MLtodyPp.js} +3 -3
- package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-CAe664Z8.js} +2 -2
- package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-mapF4Fj9.js} +2 -2
- package/public/assets/{index-CRHep3M9.js → index-CqqgOI5R.js} +5 -5
- package/public/assets/{insights-CzrtkpOE.js → insights-DNyf7IzF.js} +2 -2
- package/public/assets/{integrations-aVsUmigt.js → integrations-DlYOHq7T.js} +2 -2
- package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-CeWBNWn7.js} +2 -2
- package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-Cib8nqdE.js} +2 -2
- package/public/assets/{maintenance-NfjqFZAk.js → maintenance-DSn1cPm7.js} +2 -2
- package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-C3CkM81m.js} +2 -2
- package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-W4VQQPAv.js} +3 -3
- package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CMsTdhjq.js} +2 -2
- package/public/assets/{memory-DNsCl6UX.js → memory-BV17qtO4.js} +2 -2
- package/public/assets/{messages-DopU1L1X.js → messages-Bm_1AqSA.js} +2 -2
- package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-Dg-vfP09.js} +2 -2
- package/public/assets/{overview-C1DCTzzV.js → overview-DcYCrvlr.js} +2 -2
- package/public/assets/{pairs-DE_m6rrE.js → pairs-8o9nuG3V.js} +2 -2
- package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-BsRyBMea.js} +2 -2
- package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-DETKFn_E.js} +2 -2
- package/public/assets/{projects-Br2Bd2h1.js → projects-M_m6AHws.js} +2 -2
- package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-C_mNCgeL.js} +2 -2
- package/public/assets/provider-display-BEeLXC_D.js +2 -0
- package/public/assets/{registry-Cw38_6gW.js → registry-DDHHHE5N.js} +2 -2
- package/public/assets/run-card-Dt4I4jaP.js +2 -0
- package/public/assets/{secrets-DxI-vzSq.js → secrets-BkVwxI5O.js} +2 -2
- package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-Crq-6a-B.js} +2 -2
- package/public/assets/{settings-CKZTVGIx.js → settings-CYcavMFu.js} +2 -2
- package/public/assets/shipping-H6gzEeZB.js +2 -0
- package/public/assets/{stats-DyUBKyWI.js → stats-BJVeWkpV.js} +2 -2
- package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-BcHyw1Vw.js} +2 -2
- package/public/assets/store-DKPDIZBe.js +9 -0
- package/public/assets/{tasks-Bh7Chr4-.js → tasks-qJjlQPpx.js} +2 -2
- package/public/assets/{teams-bowI7qVY.js → teams-Cd5Sk1uX.js} +2 -2
- package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-CpY5_m7W.js} +3 -3
- package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-DI_2llFR.js} +2 -2
- package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-0yJA_buM.js} +2 -2
- package/public/assets/{use-decisions-samMQhQf.js → use-decisions-uJXHf34W.js} +2 -2
- package/public/assets/{use-project-list-59LheVP7.js → use-project-list-CpWl6eHQ.js} +2 -2
- package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-BdxBW8mL.js} +2 -2
- package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-3IndfpOc.js} +2 -2
- package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-CIVg7XB1.js} +2 -2
- package/public/assets/{work-queue-CSvpuNLz.js → work-queue-B9rnLwHZ.js} +2 -2
- package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-X8wxyhzC.js} +2 -2
- package/public/assets/{workflows-DdmxRSyg.js → workflows-o5YyMFW7.js} +2 -2
- package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CzEGoAng.js} +2 -2
- package/public/cosmos.html +4 -4
- package/public/index.html +28 -28
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapters/claude-headless.ts +1 -1
- package/runner/src/adapters/claude-launch-exit.ts +26 -0
- package/runner/src/adapters/claude-session-probe.ts +1 -1
- package/runner/src/adapters/claude-tmux.ts +38 -10
- package/runner/src/adapters/claude.ts +28 -75
- package/runner/src/adapters/provider-quota-harvest.ts +20 -71
- package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
- package/runner/src/agent-runner/liveness-exchange.ts +104 -0
- package/runner/src/claude-system-prompt.ts +49 -0
- package/runner/src/launch-assembly.ts +14 -10
- package/runner/src/profile-home.ts +1 -1
- package/runner/src/project-instructions.ts +2 -2
- package/runner/src/provider-command.ts +18 -0
- package/runner/src/provider-setup.ts +2 -1
- package/runner/src/runner-core.ts +14 -15
- package/runner/src/runner-helpers.ts +0 -8
- package/runner/src/session-scratch.ts +6 -2
- package/runner/test-support/provisioning-context.ts +4 -2
- package/runner/test-support/runner-harness.ts +2 -1
- package/scripts/steward-policy-cleanup.ts +2 -2
- package/src/agent-authz.ts +2 -2
- package/src/agent-drive-arbiter.ts +3 -8
- package/src/agent-exit-cleanup-async.ts +2 -2
- package/src/agent-exit-cleanup.ts +23 -19
- package/src/agent-lifecycle-events.ts +112 -85
- package/src/agent-ref.ts +3 -2
- package/src/agent-status-events.ts +43 -14
- package/src/auth/password.ts +4 -3
- package/src/auth/session-refresh.ts +1 -1
- package/src/auth/session.test-fixture.ts +2 -2
- package/src/auth/session.ts +7 -7
- package/src/automations/crud.ts +8 -81
- package/src/automations/dispatch.ts +33 -58
- package/src/automations/index.ts +1 -1
- package/src/automations/persistence.ts +65 -0
- package/src/automations/precheck.ts +18 -16
- package/src/automations/read-async.ts +11 -0
- package/src/automations/reconcile.ts +30 -39
- package/src/automations/storage.ts +33 -10
- package/src/automations/write-async.ts +33 -0
- package/src/branch-land-notice.ts +3 -3
- package/src/branch-landed.ts +25 -23
- package/src/bus.ts +21 -19
- package/src/cd-deploy-notify.ts +2 -2
- package/src/ci/notifications.ts +4 -4
- package/src/ci/root-missing.ts +3 -3
- package/src/ci/transition-notify.ts +5 -5
- package/src/claude-app/oauth-http.ts +3 -3
- package/src/cli/workflow.ts +12 -3
- package/src/commands-db.ts +4 -4
- package/src/commands-write.ts +8 -0
- package/src/compaction-watch.ts +10 -8
- package/src/config-store-write.ts +2 -2
- package/src/config.ts +4 -11
- package/src/connectors.ts +10 -8
- package/src/context-advisory.ts +22 -21
- package/src/db/agent-action-write.ts +36 -0
- package/src/db/agent-control-async.ts +9 -0
- package/src/db/agent-control-write.ts +49 -0
- package/src/db/agent-effort-observe.ts +22 -0
- package/src/db/agent-meta.ts +14 -3
- package/src/db/agents-async.ts +8 -5
- package/src/db/agents.ts +15 -12
- package/src/db/auth-identities.ts +2 -2
- package/src/db/automation-read-ops.ts +11 -0
- package/src/db/automation-write-ops.ts +46 -0
- package/src/db/blackboard.ts +2 -2
- package/src/db/browser-session-write.ts +32 -0
- package/src/db/bus-outbox-write-ops.ts +3 -1
- package/src/db/checkout-refresh-schema.ts +47 -0
- package/src/db/checkout-refresh.ts +5 -5
- package/src/db/command-agent-profile-backfill.ts +39 -0
- package/src/db/command-agent-profiles.ts +5 -1
- package/src/db/command-retention.ts +126 -0
- package/src/db/commands-planner-stats.test-fixture.ts +8 -0
- package/src/db/commands-read-ops.ts +16 -0
- package/src/db/connection.ts +1 -182
- package/src/db/context-carry-write.ts +74 -0
- package/src/db/continuations.ts +4 -4
- package/src/db/db-error-registry.ts +12 -0
- package/src/db/gateway.ts +26 -133
- package/src/db/history-retention-ops.ts +13 -0
- package/src/db/inbox-async.ts +2 -2
- package/src/db/inbox-read-ops.ts +1 -1
- package/src/db/inbox.ts +9 -3
- package/src/db/issue-associations.ts +2 -2
- package/src/db/issue-refs-async.ts +4 -1
- package/src/db/issue-refs-read-ops.ts +6 -0
- package/src/db/issue-refs.ts +8 -8
- package/src/db/kernel-event-log-schema.ts +2 -0
- package/src/db/land-boundaries.ts +51 -5
- package/src/db/land-gate-attestations.ts +2 -2
- package/src/db/landing-receipts.ts +2 -2
- package/src/db/lifecycle-context-write.ts +5 -0
- package/src/db/lifecycle-tail-async.ts +76 -0
- package/src/db/lifecycle-tail-write-ops.ts +62 -0
- package/src/db/lifecycle-write-window.ts +2 -1
- package/src/db/lineage-pair-dedupe-sql.ts +3 -3
- package/src/db/main-connection-mode.ts +11 -0
- package/src/db/main-connection-writes.ts +20 -2
- package/src/db/maintenance-job-runs-async.ts +2 -2
- package/src/db/maintenance-job-write-ops.ts +1 -1
- package/src/db/maintenance-pauses.ts +2 -2
- package/src/db/message-send.ts +4 -0
- package/src/db/messages.ts +31 -25
- package/src/db/migrations.ts +18 -1
- package/src/db/notification-write-ops.ts +19 -0
- package/src/db/operator-answer-write.ts +19 -0
- package/src/db/orchestrators.ts +10 -4
- package/src/db/pairs-async.ts +22 -1
- package/src/db/pairs-write-ops.ts +8 -3
- package/src/db/pairs.ts +41 -48
- package/src/db/plan-dispatch-schema.ts +5 -1
- package/src/db/plan-ready-notices.ts +43 -0
- package/src/db/plan-tools-include-migration.ts +36 -0
- package/src/db/plans-write-ops.ts +2 -0
- package/src/db/presence-buffer.ts +5 -0
- package/src/db/presence-loop.ts +18 -23
- package/src/db/presence-write.ts +0 -6
- package/src/db/project-entries-async.ts +14 -0
- package/src/db/project-entries-read-ops.ts +5 -1
- package/src/db/project-entries-write-ops.ts +8 -0
- package/src/db/project-entries.ts +15 -12
- package/src/db/project-journal.ts +2 -2
- package/src/db/project-trust-approvals.ts +2 -2
- package/src/db/prompt-templates.ts +4 -4
- package/src/db/read-op-marshaling-fixtures.ts +18 -0
- package/src/db/read-ops.ts +4 -0
- package/src/db/retention.ts +13 -2
- package/src/db/review-publish-write.ts +8 -0
- package/src/db/runtime-admin-async.ts +19 -0
- package/src/db/runtime-admin-write-ops.ts +36 -0
- package/src/db/runtime-tail-async.ts +288 -0
- package/src/db/runtime-tail-write-ops.ts +97 -0
- package/src/db/schema.ts +4 -1
- package/src/db/spawn-budget.ts +5 -0
- package/src/db/spawn-delegation.ts +1 -11
- package/src/db/sql-audit-indexes.ts +3 -0
- package/src/db/stats-async.ts +5 -3
- package/src/db/task-entity.ts +5 -4
- package/src/db/team-claim-policy.ts +60 -0
- package/src/db/teams-async.ts +4 -0
- package/src/db/teams-write-ops.ts +4 -0
- package/src/db/terminal-events.ts +15 -0
- package/src/db/timer-cutover-bodies.ts +292 -0
- package/src/db/timer-cutover-core.ts +37 -278
- package/src/db/timer-cutover-state.ts +40 -0
- package/src/db/timer-cutover-write-ops.ts +1 -40
- package/src/db/token-db-write-ops.ts +2 -0
- package/src/db/token-standing-async.ts +12 -0
- package/src/db/token-standing.ts +6 -9
- package/src/db/token-usage-retention.ts +33 -0
- package/src/db/token-usage-write-ops.ts +1 -1
- package/src/db/token-usage.ts +4 -1
- package/src/db/user-invites-async.ts +13 -0
- package/src/db/user-invites.ts +3 -3
- package/src/db/users.ts +14 -6
- package/src/db/workspaces.ts +8 -5
- package/src/db/write-ops.ts +31 -12
- package/src/db/writer-fault-seam.ts +2 -9
- package/src/db/writer-fault-test-ops.ts +18 -0
- package/src/db/writer-gateway.ts +42 -46
- package/src/dev.ts +8 -3
- package/src/event-loop-lag.ts +4 -2
- package/src/event-loop-yield.ts +2 -2
- package/src/events.ts +10 -8
- package/src/host-provider-store.ts +5 -4
- package/src/hub-metrics-report.ts +5 -1
- package/src/hub-metrics.ts +38 -18
- package/src/injection-sweep-pacing.ts +6 -8
- package/src/land-gate-notifications.ts +11 -10
- package/src/land-gate-queue-progress.ts +3 -3
- package/src/land-settle-dispatch.ts +52 -92
- package/src/lifecycle-context-pressure.ts +11 -10
- package/src/lifecycle-manager.ts +104 -98
- package/src/maintenance/breaker.ts +18 -16
- package/src/maintenance/checkout-refresh.ts +18 -1
- package/src/maintenance/critical-job-watchdog.ts +3 -3
- package/src/maintenance/events.ts +3 -3
- package/src/maintenance/history-retention-job.ts +30 -0
- package/src/maintenance/jobs.ts +29 -67
- package/src/maintenance/long-running-work.ts +3 -1
- package/src/maintenance/offline-agent-prune-job.ts +28 -0
- package/src/maintenance/orphaned-sessions.ts +5 -4
- package/src/maintenance/pauses.ts +4 -10
- package/src/maintenance/plan-ready-notice-job.ts +71 -0
- package/src/maintenance/scheduler.ts +18 -13
- package/src/maintenance/stalled-delivery-pager.ts +4 -4
- package/src/maintenance/teams.ts +19 -30
- package/src/maintenance/workspaces.ts +17 -27
- package/src/managed-policy-escalation.ts +7 -7
- package/src/managed-policy.ts +3 -3
- package/src/managed-provider-gate.ts +5 -4
- package/src/managed-spawn-reservation.ts +5 -4
- package/src/mcp/index.ts +8 -8
- package/src/mcp/output-descriptions.ts +1 -1
- package/src/mcp/presentation.ts +2 -1
- package/src/mcp/runtime-output.ts +6 -1
- package/src/mcp/self-care-visibility.ts +2 -2
- package/src/mcp/spawn-targets-matrix.ts +10 -1
- package/src/mcp/tool-groups.ts +3 -2
- package/src/mcp/tool-roles.ts +81 -0
- package/src/mcp/tools-workspace.ts +1 -1
- package/src/mcp-blackboard-tools.ts +6 -5
- package/src/mcp-decision-tools.ts +7 -7
- package/src/mcp-egress-tools.ts +10 -9
- package/src/mcp-issue-activity-tools.ts +2 -2
- package/src/mcp-issue-association-tools.ts +4 -4
- package/src/mcp-issue-ref-tools.ts +3 -3
- package/src/mcp-merge-tools.ts +5 -5
- package/src/mcp-notification-subscription-tools.ts +14 -15
- package/src/mcp-output-levels.test-fixture.ts +1 -1
- package/src/mcp-profile-trust-tools.ts +1 -1
- package/src/mcp-project-journal-tools.ts +4 -12
- package/src/mcp-project-tools.ts +17 -18
- package/src/mcp-routing-policy-tools.ts +3 -2
- package/src/mcp-team-tools.ts +6 -6
- package/src/mcp-workflow-tools.ts +5 -3
- package/src/memory-area-injection.ts +13 -12
- package/src/memory-boundary-capture.ts +6 -6
- package/src/memory-injection.ts +45 -43
- package/src/merge-pause-maintenance.ts +5 -4
- package/src/notification-router.ts +44 -17
- package/src/notify.ts +5 -5
- package/src/observed-reset-refresh.ts +4 -4
- package/src/operator-alarm.ts +2 -5
- package/src/project-ingest.ts +6 -6
- package/src/project-instructions.ts +8 -1
- package/src/project-trust.ts +12 -11
- package/src/prompt-resolver.ts +3 -3
- package/src/provider-quota-async.ts +5 -7
- package/src/provider-timeline-event.ts +4 -7
- package/src/quota-band-delivery.ts +2 -2
- package/src/recipe-runner.ts +4 -3
- package/src/retention-config-store.ts +4 -3
- package/src/reviewer-pipeline.ts +11 -10
- package/src/routes/_route.ts +4 -4
- package/src/routes/_shared.ts +13 -12
- package/src/routes/agent-effort.ts +5 -5
- package/src/routes/agent-heartbeat-quota.ts +23 -0
- package/src/routes/agent-keep.ts +1 -1
- package/src/routes/agent-reporting.ts +1 -1
- package/src/routes/agent-self-effort.ts +5 -5
- package/src/routes/agent-sessions.ts +24 -24
- package/src/routes/agents-spawn.ts +1 -1
- package/src/routes/agents.ts +17 -119
- package/src/routes/auth.ts +14 -15
- package/src/routes/authority-audit.ts +2 -2
- package/src/routes/automations.ts +6 -5
- package/src/routes/channel-binding-delete.ts +1 -1
- package/src/routes/channel-output.ts +3 -3
- package/src/routes/claude-api-budget.ts +5 -5
- package/src/routes/commands-listing.ts +1 -1
- package/src/routes/commands.ts +29 -27
- package/src/routes/context-resets.ts +2 -2
- package/src/routes/coordinator-teams.ts +1 -1
- package/src/routes/decisions.ts +2 -2
- package/src/routes/host-secret-files.ts +1 -1
- package/src/routes/hosts.ts +11 -11
- package/src/routes/inbox.ts +3 -2
- package/src/routes/index.ts +2 -1
- package/src/routes/integrations.ts +9 -7
- package/src/routes/issue-activity.ts +2 -2
- package/src/routes/issue-associations.ts +4 -4
- package/src/routes/issue-refs.ts +3 -3
- package/src/routes/kernel-timers-operator.ts +1 -1
- package/src/routes/memory.ts +1 -1
- package/src/routes/merges.ts +11 -11
- package/src/routes/messages.ts +23 -17
- package/src/routes/orchestrator-bootstrap.ts +1 -1
- package/src/routes/orchestrator-host-registration.ts +2 -0
- package/src/routes/orchestrator-host-utilization.ts +2 -2
- package/src/routes/orchestrator-proxy.ts +2 -2
- package/src/routes/orchestrator.ts +27 -18
- package/src/routes/pairs.ts +8 -8
- package/src/routes/project-profile-instructions.ts +1 -1
- package/src/routes/project-trust.ts +1 -1
- package/src/routes/projects.ts +16 -16
- package/src/routes/prompt-templates.ts +12 -12
- package/src/routes/provider-quotas.ts +10 -10
- package/src/routes/run-command.ts +1 -1
- package/src/routes/secrets-backup.ts +2 -2
- package/src/routes/secrets-import.ts +1 -1
- package/src/routes/secrets-vault.ts +7 -7
- package/src/routes/settings.ts +1 -1
- package/src/routes/shipping.ts +9 -1
- package/src/routes/spawn-policy.ts +8 -7
- package/src/routes/sse.ts +2 -2
- package/src/routes/team-templates.ts +7 -7
- package/src/routes/teams.ts +10 -10
- package/src/routes/tokens.ts +18 -17
- package/src/routes/users.ts +24 -24
- package/src/routes/viewer-read.ts +3 -3
- package/src/routes/web-tool-calls.ts +4 -4
- package/src/routes/workflows.ts +2 -2
- package/src/routes/workspaces.ts +17 -16
- package/src/routing-policy-push.ts +6 -6
- package/src/runtime-grant.ts +17 -11
- package/src/runtime-tokens.ts +20 -28
- package/src/secrets-broker/config.ts +1 -1
- package/src/secrets-broker/service.ts +2 -2
- package/src/security.ts +3 -1
- package/src/self-resume-carry.ts +21 -110
- package/src/self-resume-supersede.ts +4 -3
- package/src/self-resume.ts +54 -66
- package/src/server-background.ts +1 -1
- package/src/server.ts +21 -22
- package/src/services/agent-action.ts +12 -31
- package/src/services/agent-effort.ts +7 -32
- package/src/services/agent-hang-watchdog-job.ts +2 -2
- package/src/services/agent-heartbeat.ts +166 -0
- package/src/services/agent-reporting.ts +1 -1
- package/src/services/browser-inventory.ts +3 -3
- package/src/services/browser-session-state.ts +3 -2
- package/src/services/browser-session.ts +21 -27
- package/src/services/bus-command-update.ts +9 -9
- package/src/services/checkout-refresh-state.ts +1 -0
- package/src/services/checkout-refresh.test-fixture.ts +3 -2
- package/src/services/command-host.ts +7 -2
- package/src/services/coordinator-digest.ts +3 -2
- package/src/services/dispatch-command.ts +12 -13
- package/src/services/effective-launch-driver.ts +14 -9
- package/src/services/host-changed-paths.test-fixture.ts +5 -6
- package/src/services/host-project-readiness.ts +2 -2
- package/src/services/host-providers.ts +1 -1
- package/src/services/host-utilization.ts +9 -9
- package/src/services/idle-agent-digest.ts +17 -12
- package/src/services/idle-epics.ts +4 -4
- package/src/services/interrupt-agent.ts +12 -9
- package/src/services/issue-activity.ts +3 -3
- package/src/services/issue-associations.ts +15 -18
- package/src/services/issue-lifecycle-comments.ts +3 -2
- package/src/services/issue-lifecycle.ts +14 -13
- package/src/services/issue-refs.ts +4 -5
- package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
- package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
- package/src/services/kernel-timers-ingress.ts +3 -3
- package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
- package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
- package/src/services/kernel-timers-outbox.ts +1 -1
- package/src/services/kernel-timers-stage10-operator.ts +1 -1
- package/src/services/kernel-timers.ts +4 -3
- package/src/services/land-gate-placement.ts +11 -2
- package/src/services/land-issue-relations.ts +5 -4
- package/src/services/land-restart-retry.ts +7 -6
- package/src/services/landed-ancestry.ts +6 -5
- package/src/services/legacy-landing-host.ts +4 -1
- package/src/services/legacy-landing-recovery.ts +8 -6
- package/src/services/managed-running.ts +17 -17
- package/src/services/memory-areas.ts +3 -3
- package/src/services/merge-pause.ts +9 -15
- package/src/services/operator-decisions.ts +37 -41
- package/src/services/orchestrator-registration-recovery.ts +6 -2
- package/src/services/orchestrator-upgrade.ts +14 -12
- package/src/services/permission-decision.ts +4 -3
- package/src/services/plan-ci-notice.ts +1 -1
- package/src/services/plan-conformance.ts +4 -4
- package/src/services/plan-dispatch-policy.ts +10 -3
- package/src/services/plan-dispatch.ts +35 -14
- package/src/services/plan-graph.ts +39 -261
- package/src/services/plan-import.ts +1 -1
- package/src/services/plan-live-bindings.ts +8 -37
- package/src/services/plan-task-create.ts +1 -1
- package/src/services/plan-terminal-refs.ts +2 -12
- package/src/services/plan-wake-digest.ts +8 -3
- package/src/services/profile-trust.ts +2 -2
- package/src/services/project.ts +11 -10
- package/src/services/prompt-templates.ts +6 -5
- package/src/services/provider-hold-nudge.ts +9 -9
- package/src/services/provider-hold-record.ts +13 -12
- package/src/services/provider-state-audit.ts +19 -18
- package/src/services/provisioning-harness/driver.ts +1 -1
- package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
- package/src/services/provisioning-harness/lifecycle.ts +24 -22
- package/src/services/provisioning-harness/prepare.ts +16 -17
- package/src/services/rate-limit-resume.ts +10 -9
- package/src/services/red-main-auto-revert.ts +7 -5
- package/src/services/red-main-controller-deps.ts +20 -3
- package/src/services/red-main-incident.ts +3 -1
- package/src/services/red-main-quarantine.ts +2 -3
- package/src/services/register-agent.ts +22 -22
- package/src/services/restart-project-role.ts +1 -1
- package/src/services/restart-spawn.ts +17 -16
- package/src/services/review-publish.ts +7 -24
- package/src/services/review-ref-reconcile.ts +29 -38
- package/src/services/runtime-dependency-alert.ts +27 -0
- package/src/services/scheduler-command.ts +11 -9
- package/src/services/scheduler.ts +5 -5
- package/src/services/self-effort-grant.ts +5 -25
- package/src/services/send-message.ts +16 -33
- package/src/services/shutdown-agent.ts +17 -14
- package/src/services/spawn-agent.ts +12 -41
- package/src/services/spawn-chain-inspector.ts +1 -1
- package/src/services/spawn-cwd-placement.ts +21 -5
- package/src/services/spawn-issue-activity.ts +8 -6
- package/src/services/spawn-launch.ts +17 -1
- package/src/services/spawn-loss-escalation.ts +1 -1
- package/src/services/spawn-registration-watchdog.ts +2 -2
- package/src/services/spawn-repo-probe.ts +7 -5
- package/src/services/spawn-tree-cleanup.ts +7 -8
- package/src/services/steward-reaper.ts +4 -4
- package/src/services/task-context-hydration.ts +25 -1
- package/src/services/task-delivery.ts +20 -34
- package/src/services/task-entity.ts +40 -203
- package/src/services/task-issue-link.ts +13 -20
- package/src/services/task-issue-weave.ts +12 -11
- package/src/services/task-lifecycle.ts +27 -31
- package/src/services/task-repo-cwd.ts +37 -0
- package/src/services/task-review-hold.ts +4 -17
- package/src/services/task-semantic-events.ts +13 -11
- package/src/services/task-state-reconcile.ts +3 -4
- package/src/services/task-validation.ts +2 -2
- package/src/services/team-templates.ts +5 -5
- package/src/services/team.ts +31 -98
- package/src/services/terminal-provider-exit.ts +6 -5
- package/src/services/timer-message-fire-chaos-worker.ts +7 -3
- package/src/services/timer-message-fire-outbox.ts +1 -1
- package/src/services/transient-agent-reaper.ts +22 -19
- package/src/services/turn-reconcile-audit.ts +9 -9
- package/src/services/user-onboarding.ts +8 -6
- package/src/services/user-profile.ts +6 -9
- package/src/services/work-finality.ts +16 -4
- package/src/services/workflow-auto-start.ts +2 -1
- package/src/services/workflow-caller.ts +61 -4
- package/src/services/workflow-child-proof-worker.ts +1 -1
- package/src/services/workflow-control-proof-worker.ts +1 -1
- package/src/services/workflow-coordinator.ts +8 -2
- package/src/services/workflow-decisions.ts +5 -5
- package/src/services/workflow-deferred-cleanup.ts +1 -1
- package/src/services/workflow-effect-driver.ts +42 -19
- package/src/services/workflow-effects.ts +41 -27
- package/src/services/workflow-pins.ts +7 -4
- package/src/services/workflow-reconcile.ts +1 -0
- package/src/services/workflow-resume-pins.ts +3 -3
- package/src/services/workflow-routing-defaults.ts +18 -0
- package/src/services/workflow-run-view.ts +4 -3
- package/src/services/workflow-run.test-fixture.ts +1 -1
- package/src/services/workflow-runs.ts +34 -22
- package/src/services/workflow-supervisor-state.ts +21 -1
- package/src/services/workflow-supervisor.ts +4 -3
- package/src/services/workspace-recovery.ts +4 -3
- package/src/spawn-admission.ts +10 -2
- package/src/spawn-placement.ts +27 -9
- package/src/spawn-targets.ts +8 -8
- package/src/sse-agent-status.ts +15 -6
- package/src/sse.ts +49 -24
- package/src/startup-context-injection.ts +61 -57
- package/src/steward-handoff.ts +4 -3
- package/src/steward-policy-cleanup.ts +9 -8
- package/src/steward.ts +2 -2
- package/src/task-dispatch-live.ts +5 -2
- package/src/task-dispatcher.ts +2 -2
- package/src/token-db.ts +2 -2
- package/src/token-usage-async.ts +2 -4
- package/src/user-awaiting-reply.ts +5 -5
- package/src/workspace-actions.ts +24 -29
- package/src/workspace-auto-merge.ts +14 -12
- package/src/workspace-changed-paths-client.ts +16 -5
- package/src/workspace-cleanup-stale.ts +30 -7
- package/src/workspace-command-settle.ts +24 -16
- package/src/workspace-config.ts +1 -1
- package/src/workspace-conflict-resolver.ts +9 -7
- package/src/workspace-dead-owner-recovery.ts +5 -4
- package/src/workspace-escalation.ts +12 -11
- package/src/workspace-human-escalation.ts +3 -3
- package/src/workspace-land-gate-watchdog.ts +3 -3
- package/src/workspace-land-refusal.ts +20 -0
- package/src/workspace-land-stopped.ts +1 -0
- package/src/workspace-merge.ts +10 -26
- package/src/workspace-nothing-to-land-notice.ts +2 -2
- package/src/workspace-orphans.ts +18 -37
- package/src/workspace-pr-completion.ts +10 -8
- package/src/workspace-rekey.ts +3 -3
- package/src/workspace-steward-failure.ts +2 -2
- package/src/workspace-steward-wake.ts +2 -2
- package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
- package/public/assets/provider-display-DsuWMvVL.js +0 -2
- package/public/assets/run-card-CpKqRbhg.js +0 -2
- package/public/assets/shipping-B9YtnwtG.js +0 -2
- package/public/assets/store-DcClXLH0.js +0 -9
- package/runner/src/agent-runner/http-liveness.ts +0 -85
- package/src/db/on-loop-write-transaction.ts +0 -34
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { relayEnv } from "@humwick/sdk/env";
|
|
3
|
+
import { getGateway } from "../db/gateway";
|
|
4
|
+
import { getPlan, listPlansByTeam } from "../db/plans";
|
|
5
|
+
import type { DueReadyNodes, ReadyPlanScan } from "../db/plan-ready-notices";
|
|
6
|
+
import { listActiveTeams } from "../db/teams";
|
|
7
|
+
import { routeNotification } from "../notification-router";
|
|
8
|
+
import { compactText, liveFrontier } from "../services/plan-wake-digest";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_READY_NOTICE_MINUTES = 30;
|
|
11
|
+
|
|
12
|
+
/** #3660 — minutes a ready node may sit unstarted before the coordinator hears about it. */
|
|
13
|
+
export function planReadyNoticeMs(): number {
|
|
14
|
+
const minutes = Number(relayEnv(process.env, "AGENT_RELAY_PLAN_READY_NOTICE_MINUTES"));
|
|
15
|
+
return (Number.isFinite(minutes) && minutes > 0 ? minutes : DEFAULT_READY_NOTICE_MINUTES) * 60_000;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Ready (all prerequisites landed), still pending after the Task projection, and nobody owns it. */
|
|
19
|
+
function scanTeamPlans(teamId: string, signal: AbortSignal): Array<ReadyPlanScan & { title: string }> {
|
|
20
|
+
const scans: Array<ReadyPlanScan & { title: string }> = [];
|
|
21
|
+
let cursor: string | undefined;
|
|
22
|
+
do {
|
|
23
|
+
const page = listPlansByTeam(teamId, { limit: 100, cursor });
|
|
24
|
+
for (const summary of page.plans) {
|
|
25
|
+
if (signal.aborted) return scans;
|
|
26
|
+
if (!summary.nodeCount || summary.completion === "done") continue;
|
|
27
|
+
const stored = getPlan(summary.id);
|
|
28
|
+
if (!stored) continue;
|
|
29
|
+
const { frontier } = liveFrontier(stored);
|
|
30
|
+
scans.push({ planId: stored.id, title: stored.title ?? "Untitled", nodeIds: frontier.ready.filter(node => !node.ownerRef).map(node => node.id) });
|
|
31
|
+
}
|
|
32
|
+
cursor = page.nextCursor;
|
|
33
|
+
} while (cursor);
|
|
34
|
+
return scans;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function noticeUnstartedReadyNodes(signal: AbortSignal, now = Date.now()): Promise<Record<string, unknown>> {
|
|
38
|
+
const scans: Array<ReadyPlanScan & { title: string; teamId: string }> = [];
|
|
39
|
+
for (const team of listActiveTeams()) {
|
|
40
|
+
if (signal.aborted) return { notices: 0 };
|
|
41
|
+
if (team.coordinatorId) scans.push(...scanTeamPlans(team.id, signal).map(scan => ({ ...scan, teamId: team.id })));
|
|
42
|
+
}
|
|
43
|
+
if (signal.aborted) return { notices: 0 };
|
|
44
|
+
const due = await getGateway().write("plans.readyNotices.sync", { scans: scans.map(({ planId, nodeIds }) => ({ planId, nodeIds })), now, dueMs: planReadyNoticeMs() }) as unknown as DueReadyNodes[];
|
|
45
|
+
const byPlan = new Map(scans.map(scan => [scan.planId, scan]));
|
|
46
|
+
let notices = 0;
|
|
47
|
+
for (const item of due) {
|
|
48
|
+
const scan = byPlan.get(item.planId)!;
|
|
49
|
+
if (signal.aborted) break;
|
|
50
|
+
const nodeIds = item.nodes.map(node => node.nodeId);
|
|
51
|
+
const minutes = Math.round(planReadyNoticeMs() / 60_000);
|
|
52
|
+
const key = createHash("sha1").update(item.nodes.map(node => `${node.nodeId}@${node.readySince}`).join(",")).digest("hex").slice(0, 12);
|
|
53
|
+
await routeNotification({
|
|
54
|
+
type: "team.idle", scope: { teamId: scan.teamId }, recipients: [`coordinator:${scan.teamId}`],
|
|
55
|
+
message: { kind: "system", from: "system", subject: "Plan nodes ready but not started", replyExpected: false,
|
|
56
|
+
body: `${compactText(scan.title, 56)} (${scan.planId}): ready for ${minutes}+ min, not started: ${nodeIds.slice(0, 8).map(id => compactText(id, 32)).join(",")}${nodeIds.length > 8 ? `,+${nodeIds.length - 8}` : ""}. Start them or mark them blocked (relay_plan_get mode=resume).`,
|
|
57
|
+
payload: { kind: "plan.ready-unstarted", teamId: scan.teamId, planId: scan.planId, nodeIds },
|
|
58
|
+
idempotencyKey: `plan-ready:${scan.planId}:${key}` },
|
|
59
|
+
});
|
|
60
|
+
await getGateway().write("plans.readyNotices.mark", { planId: item.planId, nodeIds, now });
|
|
61
|
+
notices++;
|
|
62
|
+
}
|
|
63
|
+
return { notices };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const PLAN_READY_NOTICE_JOB = {
|
|
67
|
+
id: "plan-ready-notice", title: "Ready Plan nodes not started", critical: false,
|
|
68
|
+
description: "Tell each coordinator once when a Plan node has been ready with nothing started for the notice window.",
|
|
69
|
+
intervalMs: 5 * 60_000, deadlineMs: 60_000, runOnStart: false,
|
|
70
|
+
handler: (context: { signal: AbortSignal }) => noticeUnstartedReadyNodes(context.signal),
|
|
71
|
+
} satisfies import("./types").MaintenanceJobDefinition;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
+
import { rescheduleRunOnStartJobsAsync, claimMaintenanceRunAsync, finishMaintenanceRunAsync, heartbeatMaintenanceRunAsync, ensureMaintenanceJobsAsync, reclaimDeadLeasesAsync } from "../db/maintenance-job-runs-async";
|
|
1
2
|
import { timePostBindPhase } from "../post-bind-startup";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { errMessage, isRecord } from "@humwick/sdk";
|
|
4
5
|
import { classifyPidLiveness } from "@humwick/sdk/process-utils";
|
|
5
6
|
import { CRITICAL_MAINTENANCE_PROGRESS_STALE_MS, createActivityEventAsync, getDb, MAINTENANCE_RUN_AUTORESET_GUARD_SQL, recordDrainConvoy, writeTransaction } from "../db";
|
|
6
|
-
import {
|
|
7
|
-
import { ensureMaintenanceJobsAsync, reclaimDeadLeasesAsync, rescheduleRunOnStartJobsAsync } from "../db/maintenance-job-runs-async";
|
|
8
|
-
import { reclaimDeadLeases, rescheduleRunOnStartJobs, upsertMaintenanceJobs } from "../db/maintenance-job-reconcile";
|
|
7
|
+
import { reclaimDeadLeases, upsertMaintenanceJobs } from "../db/maintenance-job-reconcile";
|
|
9
8
|
import type { DeadLease, MaintenanceJobSpec } from "../db/maintenance-job-reconcile";
|
|
10
9
|
import type { MaintenanceRunOutcome } from "../db/maintenance-job-runs";
|
|
11
10
|
import { withAbortDeadline } from "../async-deadline";
|
|
12
11
|
import { MAINTENANCE_BREAKER_LAG_MS } from "../config";
|
|
13
12
|
import { findPauseByJobId, getMaintenanceJobPause, notPausedSql } from "../db/maintenance-pauses";
|
|
14
13
|
import { setLagBreakerObserver } from "../event-loop-lag";
|
|
14
|
+
import { framed, runInFrame } from "../hub-metrics";
|
|
15
15
|
import { resetRateLimitResumeTrackerForTests } from "../services/rate-limit-resume";
|
|
16
16
|
import { resetTransientTrackerForTests } from "../services/transient-agent-reaper";
|
|
17
17
|
import { isEphemeralMode } from "../execution-mode";
|
|
@@ -79,7 +79,7 @@ const MAINTENANCE_PROGRESS_HEARTBEAT_MS = 60_000;
|
|
|
79
79
|
// 3. HARD BLAST-RADIUS CAP: at most ONE new auto-pause per lag sample (operator Q4). If several
|
|
80
80
|
// qualify, pause the single largest in-gap block and warn about the rest. One freeze may never
|
|
81
81
|
// pause a fleet.
|
|
82
|
-
setLagBreakerObserver((_gapStartAt, gapEndAt, lagMs) => {
|
|
82
|
+
setLagBreakerObserver(async (_gapStartAt, gapEndAt, lagMs) => {
|
|
83
83
|
const reason = `blocked the event loop ~${Math.round(lagMs)}ms (measured block)`;
|
|
84
84
|
// #1725 P1 F-A — the ACTUATOR uses the TIGHTENED window `[gapEndAt - lagMs, gapEndAt]`, the actual
|
|
85
85
|
// freeze interval, NOT the sampler's diagnostic window `[_gapStartAt, gapEndAt]`. The sampler builds
|
|
@@ -143,7 +143,7 @@ setLagBreakerObserver((_gapStartAt, gapEndAt, lagMs) => {
|
|
|
143
143
|
const blamed = strikable.reduce((worst, entry) => (entry.overlapMs > worst.overlapMs ? entry : worst));
|
|
144
144
|
if (isRunActive(blamed.run.runId)) abortRun(blamed.run.runId, new MaintenanceBreakerError(`aborted by circuit breaker: ${reason}`));
|
|
145
145
|
const critical = definitions.find((job) => job.id === blamed.run.jobId)?.critical ?? false;
|
|
146
|
-
recordBreakerStrike({ runId: blamed.run.runId, jobId: blamed.run.jobId, critical, kind: "lag", reason });
|
|
146
|
+
(await recordBreakerStrike({ runId: blamed.run.runId, jobId: blamed.run.jobId, critical, kind: "lag", reason }));
|
|
147
147
|
const others = strikable.filter((entry) => entry !== blamed);
|
|
148
148
|
if (others.length > 0) {
|
|
149
149
|
console.warn(`[relay] maintenance circuit breaker: ${strikable.length} runs each blocked >=${Math.round(shareThresholdMs)}ms of this ${Math.round(lagMs)}ms freeze — struck ${blamed.run.jobId} (in-freeze block ${Math.round(blamed.overlapMs)}ms), one-per-sample cap; not struck: ${others.map((entry) => `${entry.run.jobId} (${Math.round(entry.overlapMs)}ms)`).join(", ")}`);
|
|
@@ -166,8 +166,8 @@ const runBlockTotalMs = (run: MaintenanceRunRecord): number => Math.round((run.b
|
|
|
166
166
|
/** Exposes the internal `runJob` directly, bypassing the `definitions` lookup `runMaintenanceJobNow`
|
|
167
167
|
* does, so tests can drive the run-registry/timeout/crash paths with a synthetic handler.
|
|
168
168
|
* Production code never calls this name. */
|
|
169
|
-
export const runMaintenanceJobForTests = (definition: MaintenanceJobDefinition, options: { force?: boolean; overridePause?: boolean } = {}) =>
|
|
170
|
-
runJob(definition, options);
|
|
169
|
+
export const runMaintenanceJobForTests = async (definition: MaintenanceJobDefinition, options: { force?: boolean; overridePause?: boolean } = {}) =>
|
|
170
|
+
(await runJob(definition, options));
|
|
171
171
|
|
|
172
172
|
export function resetOrphanTrackerForTests(): void {
|
|
173
173
|
resetOrphanSessionTrackerForTests();
|
|
@@ -185,7 +185,7 @@ export function startMaintenanceScheduler(): void {
|
|
|
185
185
|
// never bubble to an unhandledRejection that exits the relay process.
|
|
186
186
|
if (timer) return;
|
|
187
187
|
timer = setInterval(
|
|
188
|
-
() =>
|
|
188
|
+
framed("timer maintenance", () => runDueMaintenanceJobs().catch((error) => console.warn(`maintenance tick failed: ${errMessage(error)}`))),
|
|
189
189
|
SCHEDULER_TICK_MS,
|
|
190
190
|
);
|
|
191
191
|
}
|
|
@@ -221,8 +221,8 @@ async function bootMaintenanceJobs(): Promise<void> {
|
|
|
221
221
|
* doing it from the periodic reconcile would make run-on-start jobs run every
|
|
222
222
|
* scheduler tick.
|
|
223
223
|
*/
|
|
224
|
-
export function scheduleRunOnStartJobs(now: number = Date.now()): void {
|
|
225
|
-
|
|
224
|
+
export async function scheduleRunOnStartJobs(now: number = Date.now()): Promise<void> {
|
|
225
|
+
(await rescheduleRunOnStartJobsAsync(now));
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
function readMaintenanceJobs(): MaintenanceJob[] {
|
|
@@ -255,7 +255,7 @@ export async function runMaintenanceJobNow(id: string, options: { overridePause?
|
|
|
255
255
|
await ensureMaintenanceJobsAsync(jobSpecs());
|
|
256
256
|
const definition = definitions.find((job) => job.id === id);
|
|
257
257
|
if (!definition) throw new Error("maintenance job not found");
|
|
258
|
-
return runJob(definition, { force: true, overridePause: options.overridePause });
|
|
258
|
+
return (await runJob(definition, { force: true, overridePause: options.overridePause }));
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
export async function runLegacyMaintenanceReaper(): Promise<Record<string, unknown>> {
|
|
@@ -450,7 +450,12 @@ async function persistRunOutcome(outcome: MaintenanceRunOutcome): Promise<boolea
|
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
-
|
|
453
|
+
/** #3798 — each run is its own hub-metrics frame, so a job's main-thread CPU is named per job. */
|
|
454
|
+
function runJob(definition: MaintenanceJobDefinition, options: { force?: boolean; overridePause?: boolean } = {}): Promise<MaintenanceJobRun> {
|
|
455
|
+
return runInFrame({ callsite: `job ${definition.id}` }, () => executeJob(definition, options));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async function executeJob(definition: MaintenanceJobDefinition, options: { force?: boolean; overridePause?: boolean }): Promise<MaintenanceJobRun> {
|
|
454
459
|
const startedAt = Date.now();
|
|
455
460
|
// One execution boundary covers ticks, forced runs and already-queued definitions.
|
|
456
461
|
if (isEphemeralMode() && definition.instanceSafe !== true) {
|
|
@@ -584,7 +589,7 @@ async function runJob(definition: MaintenanceJobDefinition, options: { force?: b
|
|
|
584
589
|
if (persisted && (isTimeout || breakerAborted)) {
|
|
585
590
|
// A breaker abort is a lag event that the lag observer has usually already struck (deduped by
|
|
586
591
|
// runId here); a timeout is its own consecutive-overrun signal.
|
|
587
|
-
recordBreakerStrike({ runId: run.runId, jobId: definition.id, critical: definition.critical, kind: isTimeout ? "timeout" : "lag", reason: isTimeout ? `timed out after ${deadlineMs}ms` : message });
|
|
592
|
+
(await recordBreakerStrike({ runId: run.runId, jobId: definition.id, critical: definition.critical, kind: isTimeout ? "timeout" : "lag", reason: isTimeout ? `timed out after ${deadlineMs}ms` : message }));
|
|
588
593
|
}
|
|
589
594
|
if (definition.logRuns) console.warn(`[relay] maintenance job ${definition.id} ${status} in ${durationMs}ms: ${message}`);
|
|
590
595
|
return { id: definition.id, status, startedAt, finishedAt, durationMs, error: message, ...(result ? { result } : {}) };
|
|
@@ -69,7 +69,7 @@ export async function runStalledDeliveryPager(now: number = Date.now()): Promise
|
|
|
69
69
|
state = { ...state, failStreak: 0, okStreak: state.stalled ? state.okStreak + 1 : 0 };
|
|
70
70
|
if (state.stalled && state.okStreak >= STALL_PAGE_CONFIRM_CHECKS) {
|
|
71
71
|
if (state.alerted) {
|
|
72
|
-
const result = notifyWorkspaceHumanEscalation({
|
|
72
|
+
const result = (await notifyWorkspaceHumanEscalation({
|
|
73
73
|
subject: "Message delivery recovered",
|
|
74
74
|
body: `Messages are being delivered to live agents again. The stall began ${new Date(state.since).toISOString()}.`,
|
|
75
75
|
priority: "default",
|
|
@@ -77,7 +77,7 @@ export async function runStalledDeliveryPager(now: number = Date.now()): Promise
|
|
|
77
77
|
source: "stalled-delivery-pager",
|
|
78
78
|
payload: { kind: "message-delivery.recovered", since: state.since, recoveredAt: now },
|
|
79
79
|
idempotencyKey: `stalled-delivery:${state.since}:recovered`,
|
|
80
|
-
});
|
|
80
|
+
}));
|
|
81
81
|
recovered = result.accepted;
|
|
82
82
|
if (!result.accepted) console.warn(`[relay] #3014 message delivery recovered — operator NOT paged: ${result.reason}`);
|
|
83
83
|
}
|
|
@@ -89,7 +89,7 @@ export async function runStalledDeliveryPager(now: number = Date.now()): Promise
|
|
|
89
89
|
if (state.stalled && !state.alerted) {
|
|
90
90
|
// Persist the episode first so a crash after the send re-sends under the same key.
|
|
91
91
|
await persist();
|
|
92
|
-
const result = notifyWorkspaceHumanEscalation({
|
|
92
|
+
const result = (await notifyWorkspaceHumanEscalation({
|
|
93
93
|
subject: "Message delivery stalled",
|
|
94
94
|
body: [
|
|
95
95
|
`${check.subjects?.length ?? 0} live, idle agents have had mail unattempted for over 2 minutes and no turn was delivered to any agent in 5 minutes.`,
|
|
@@ -102,7 +102,7 @@ export async function runStalledDeliveryPager(now: number = Date.now()): Promise
|
|
|
102
102
|
source: "stalled-delivery-pager",
|
|
103
103
|
payload: { kind: "message-delivery.stalled", since: state.since, count: check.count, subjects: check.subjects },
|
|
104
104
|
idempotencyKey: `stalled-delivery:${state.since}:alert`,
|
|
105
|
-
});
|
|
105
|
+
}));
|
|
106
106
|
alerted = result.accepted;
|
|
107
107
|
if (!result.accepted) console.warn(`[relay] #3014 message delivery stalled — operator NOT paged: ${result.reason}`);
|
|
108
108
|
state = { ...state, alerted: true };
|
package/src/maintenance/teams.ts
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
agentsWithPendingOwnRelayRun,
|
|
6
|
-
getAgent,
|
|
7
|
-
getCrashReport,
|
|
8
|
-
hasTerminalEvent,
|
|
9
|
-
getTeamBusyState,
|
|
10
|
-
getTeamRoster,
|
|
11
|
-
listActiveTeams,
|
|
12
|
-
setTeamZeroMembersSince,
|
|
13
|
-
TEAM_AUTO_REAP_SUMMARY_PREFIX,
|
|
14
|
-
} from "../db";
|
|
15
|
-
import { clearTeamIdleEntry, coordinatorsWithPendingTimers, getTeamIdleEntries, setTeamIdleEntry } from "../db/team-idle";
|
|
1
|
+
import { setTeamIdleEntryAsync, clearTeamIdleEntryAsync } from "../db/runtime-tail-async";
|
|
2
|
+
import { setTeamZeroMembersSinceAsync, clearTeamZeroMembersSinceAsync } from "../db/teams-async";
|
|
3
|
+
import { countLiveTeamMembers, createActivityEventAsync, agentsWithPendingOwnRelayRun, getAgent, getCrashReport, hasTerminalEvent, getTeamBusyState, getTeamRoster, listActiveTeams, TEAM_AUTO_REAP_SUMMARY_PREFIX } from "../db";
|
|
4
|
+
import { coordinatorsWithPendingTimers, getTeamIdleEntries } from "../db/team-idle";
|
|
16
5
|
import { openWorkflowRunHolders } from "../db/workflow-task-read-ops";
|
|
17
6
|
import { STALE_TTL_MS, stewardFallbackTarget } from "../config";
|
|
18
7
|
import { agentProviderState } from "../provider-state";
|
|
@@ -48,13 +37,13 @@ function memberIsAlive(member: AgentCard, freshCutoff: number): boolean {
|
|
|
48
37
|
* after its land, a graceful shutdown). This watchdog must surface ONLY a genuine silent death:
|
|
49
38
|
* an agent that went quiet with NO terminal event of any kind.
|
|
50
39
|
*/
|
|
51
|
-
function detectSilentMembers(
|
|
40
|
+
async function detectSilentMembers(
|
|
52
41
|
teamId: string,
|
|
53
42
|
coordinatorId: string | undefined,
|
|
54
43
|
now: number,
|
|
55
44
|
thresholdMs: number,
|
|
56
45
|
out: { pingedMembers: string[]; watchedMembers: number },
|
|
57
|
-
): void {
|
|
46
|
+
): Promise<void> {
|
|
58
47
|
const freshCutoff = now - thresholdMs;
|
|
59
48
|
const watch = teamMemberWatchTracker.get(teamId) ?? new Map<string, TeamMemberWatch>();
|
|
60
49
|
teamMemberWatchTracker.set(teamId, watch);
|
|
@@ -85,7 +74,7 @@ function detectSilentMembers(
|
|
|
85
74
|
if (getCrashReport(memberId) || hasTerminalEvent(memberId, entry.epoch)) continue;
|
|
86
75
|
if (!coordinatorId) continue;
|
|
87
76
|
const minutes = Math.floor(gapMs / 60_000);
|
|
88
|
-
notifyTarget(
|
|
77
|
+
await notifyTarget(
|
|
89
78
|
"team.member_silent",
|
|
90
79
|
{ teamId, agentId: memberId },
|
|
91
80
|
coordinatorId,
|
|
@@ -179,7 +168,7 @@ async function detectGoneCoordinator(
|
|
|
179
168
|
watch.escalated = true; // the committed no-target audit is the complete no-silent-dissolve action
|
|
180
169
|
return;
|
|
181
170
|
}
|
|
182
|
-
const sent = notifyTarget(
|
|
171
|
+
const sent = await notifyTarget(
|
|
183
172
|
"team.coordinator_gone",
|
|
184
173
|
{ teamId, agentId: coordinatorId, parent: resolveCoordinatorAgentId(coordinatorId) ?? undefined },
|
|
185
174
|
escalationTarget,
|
|
@@ -222,10 +211,10 @@ function teamIdlePayload(teamId: string, coordinatorId: string, thresholdMinutes
|
|
|
222
211
|
};
|
|
223
212
|
}
|
|
224
213
|
|
|
225
|
-
function notifyTeamIdle(teamId: string, coordinatorId: string, thresholdMinutes: number, idleForMs: number, firstNoBusyAt: number): string | null {
|
|
214
|
+
async function notifyTeamIdle(teamId: string, coordinatorId: string, thresholdMinutes: number, idleForMs: number, firstNoBusyAt: number): Promise<string | null> {
|
|
226
215
|
const roundedIdleMinutes = Math.max(thresholdMinutes, Math.floor(idleForMs / 60_000));
|
|
227
216
|
const payload = teamIdlePayload(teamId, coordinatorId, thresholdMinutes, idleForMs, firstNoBusyAt);
|
|
228
|
-
return notifyTarget(
|
|
217
|
+
return await notifyTarget(
|
|
229
218
|
"team.idle",
|
|
230
219
|
{ teamId, agentId: coordinatorId },
|
|
231
220
|
coordinatorId,
|
|
@@ -270,7 +259,7 @@ export async function runTeamIdleWatchdog(input: { now?: number; signal?: AbortS
|
|
|
270
259
|
seen.add(team.id);
|
|
271
260
|
// #634 — runs every cycle, independent of the team-level idle gate below: a single silent
|
|
272
261
|
// member must surface even while OTHER members keep the team "busy".
|
|
273
|
-
detectSilentMembers(team.id, team.coordinatorId, now, thresholdMs, memberSilence);
|
|
262
|
+
await detectSilentMembers(team.id, team.coordinatorId, now, thresholdMs, memberSilence);
|
|
274
263
|
// #412/#468 — coordinator-gone escalation, also every cycle and independent of the idle gate: a
|
|
275
264
|
// dead coordinator must surface even if the team isn't (yet) idle, and the idle-ping path can't
|
|
276
265
|
// reach it (it would just ping the void).
|
|
@@ -299,7 +288,7 @@ export async function runTeamIdleWatchdog(input: { now?: number; signal?: AbortS
|
|
|
299
288
|
? busy.busyMemberIds.filter((id) => id !== team.coordinatorId)
|
|
300
289
|
: busy.busyMemberIds;
|
|
301
290
|
if (busyMemberIds.length > 0) {
|
|
302
|
-
if (existingEntry)
|
|
291
|
+
if (existingEntry) (await clearTeamIdleEntryAsync(team.id));
|
|
303
292
|
teamIdleTracker.delete(team.id);
|
|
304
293
|
busyTeamIds.push(team.id);
|
|
305
294
|
continue;
|
|
@@ -311,7 +300,7 @@ export async function runTeamIdleWatchdog(input: { now?: number; signal?: AbortS
|
|
|
311
300
|
}
|
|
312
301
|
|
|
313
302
|
const entry = existingEntry ?? { firstNoBusyAt: now, pinged: false };
|
|
314
|
-
if (!existingEntry)
|
|
303
|
+
if (!existingEntry) (await setTeamIdleEntryAsync(team.id, entry));
|
|
315
304
|
teamIdleTracker.set(team.id, entry);
|
|
316
305
|
const idleForMs = now - entry.firstNoBusyAt;
|
|
317
306
|
if (idleForMs < thresholdMs) {
|
|
@@ -328,10 +317,10 @@ export async function runTeamIdleWatchdog(input: { now?: number; signal?: AbortS
|
|
|
328
317
|
continue;
|
|
329
318
|
}
|
|
330
319
|
|
|
331
|
-
const sent = notifyTeamIdle(team.id, team.coordinatorId, config.thresholdMinutes, idleForMs, entry.firstNoBusyAt);
|
|
320
|
+
const sent = await notifyTeamIdle(team.id, team.coordinatorId, config.thresholdMinutes, idleForMs, entry.firstNoBusyAt);
|
|
332
321
|
if (!sent) continue;
|
|
333
322
|
entry.pinged = true;
|
|
334
|
-
|
|
323
|
+
(await setTeamIdleEntryAsync(team.id, entry));
|
|
335
324
|
pingedTeamIds.push(team.id);
|
|
336
325
|
}
|
|
337
326
|
|
|
@@ -341,7 +330,7 @@ export async function runTeamIdleWatchdog(input: { now?: number; signal?: AbortS
|
|
|
341
330
|
// coordinator that was already told. Skip the sweep instead; the next full pass reclaims them.
|
|
342
331
|
if (!signal?.aborted) {
|
|
343
332
|
for (const key of teamIdleTracker.keys()) if (!seen.has(key)) {
|
|
344
|
-
|
|
333
|
+
(await clearTeamIdleEntryAsync(key));
|
|
345
334
|
teamIdleTracker.delete(key);
|
|
346
335
|
}
|
|
347
336
|
for (const key of teamMemberWatchTracker.keys()) if (!seen.has(key)) teamMemberWatchTracker.delete(key);
|
|
@@ -385,7 +374,7 @@ export async function reapZeroMemberTeams(context: { signal?: AbortSignal } = {}
|
|
|
385
374
|
const live = countLiveTeamMembers(team.id, cutoff);
|
|
386
375
|
if (live > 0) {
|
|
387
376
|
if (team.zeroMembersSince !== undefined) {
|
|
388
|
-
|
|
377
|
+
(await clearTeamZeroMembersSinceAsync(team.id));
|
|
389
378
|
clearedTeamIds.push(team.id);
|
|
390
379
|
}
|
|
391
380
|
continue;
|
|
@@ -393,7 +382,7 @@ export async function reapZeroMemberTeams(context: { signal?: AbortSignal } = {}
|
|
|
393
382
|
|
|
394
383
|
zeroMemberTeams++;
|
|
395
384
|
if (team.zeroMembersSince === undefined) {
|
|
396
|
-
|
|
385
|
+
(await setTeamZeroMembersSinceAsync(team.id, now));
|
|
397
386
|
trackedTeamIds.push(team.id);
|
|
398
387
|
continue;
|
|
399
388
|
}
|
|
@@ -401,7 +390,7 @@ export async function reapZeroMemberTeams(context: { signal?: AbortSignal } = {}
|
|
|
401
390
|
|
|
402
391
|
const outcomeSummary = `${TEAM_AUTO_REAP_SUMMARY_PREFIX} zero live members for ${grace}ms`;
|
|
403
392
|
// Leave outcomeStatus null (unknown): an abandoned team is NOT a failed one.
|
|
404
|
-
const dissolved = dissolveTeamById(team.id, { outcomeSummary });
|
|
393
|
+
const dissolved = await dissolveTeamById(team.id, { outcomeSummary });
|
|
405
394
|
await createActivityEventAsync({
|
|
406
395
|
clientId: `team-reap-${team.id}-${now}`,
|
|
407
396
|
kind: "state",
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
+
import { releaseExpiredMergeLeasesAsync, reelectRepoStewardAsync } from "../db/runtime-tail-async";
|
|
2
|
+
import { deleteWorkspaceAsync } from "../db/lifecycle-tail-async";
|
|
3
|
+
import { createCommandAsync } from "../commands-write";
|
|
1
4
|
import { releaseExpiredStewardHandbacks } from "./steward-handback";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
agentsWithRunningCommandTimers,
|
|
5
|
-
createActivityEventAsync,
|
|
6
|
-
deleteWorkspace,
|
|
7
|
-
getAgent,
|
|
8
|
-
getRepoSteward,
|
|
9
|
-
getWorkspace,
|
|
10
|
-
listOrchestrators,
|
|
11
|
-
listWorkspaces,
|
|
12
|
-
patchWorkspaceMetadata,
|
|
13
|
-
reelectRepoSteward,
|
|
14
|
-
releaseExpiredMergeLeases,
|
|
15
|
-
updateWorkspaceStatusAsync,
|
|
16
|
-
setWorkspaceBranchAsync, patchWorkspaceMetadataAsync} from "../db";
|
|
5
|
+
|
|
6
|
+
import { agentsWithRunningCommandTimers, createActivityEventAsync, getAgent, getRepoSteward, getWorkspace, listOrchestrators, listWorkspaces, patchWorkspaceMetadata, updateWorkspaceStatusAsync, setWorkspaceBranchAsync, patchWorkspaceMetadataAsync } from "../db";
|
|
17
7
|
import type { WorkspaceMergePreview, WorkspaceRecord, WorkspaceStatus } from "../types";
|
|
18
8
|
import { reconcileLandedWorkspace } from "../branch-landed";
|
|
19
9
|
import { workspaceStewardEligible } from "../steward-queue";
|
|
@@ -187,7 +177,7 @@ export async function scanWorkspaceConflicts(context: { signal?: AbortSignal } =
|
|
|
187
177
|
// a landing steward would spawn, find nothing to resolve, and re-strand the row. Route it to
|
|
188
178
|
// the branch agent's COORDINATOR (owner/fallback) via the custody path instead — they own the
|
|
189
179
|
// decision to rebase or abandon. (The event-driven auto-merge hook escalates the same way.)
|
|
190
|
-
const targets = routeWorkspaceCustodyEscalation({
|
|
180
|
+
const targets = await routeWorkspaceCustodyEscalation({
|
|
191
181
|
workspace: ws,
|
|
192
182
|
subject: "Workspace land keeps reverting (moved base)",
|
|
193
183
|
body: `Workspace \`${ws.branch ?? ws.id}\` in ${ws.repoRoot} keeps getting its tip reverted as ${p.baseRef ?? "base"} moves under it (#1242); it can't auto-land. Please coordinate a rebase or clean-up.`,
|
|
@@ -360,7 +350,7 @@ export async function workspaceGC(context: { signal?: AbortSignal } = {}): Promi
|
|
|
360
350
|
const now = Date.now();
|
|
361
351
|
const cutoff = now - WORKSPACE_RETENTION_MS;
|
|
362
352
|
const reviewCutoff = now - WORKSPACE_REVIEW_TTL_MS;
|
|
363
|
-
const releasedLeaseRepos =
|
|
353
|
+
const releasedLeaseRepos = (await releaseExpiredMergeLeasesAsync(now));
|
|
364
354
|
|
|
365
355
|
const all = listWorkspaces();
|
|
366
356
|
// #1726 — reclaim expired-terminal rows in a BOUNDED batch. GC can be paused for long stretches
|
|
@@ -376,7 +366,7 @@ export async function workspaceGC(context: { signal?: AbortSignal } = {}): Promi
|
|
|
376
366
|
const terminalIds: string[] = [];
|
|
377
367
|
for (const ws of terminalBatch) {
|
|
378
368
|
if (signal?.aborted) break;
|
|
379
|
-
|
|
369
|
+
(await deleteWorkspaceAsync(ws.id));
|
|
380
370
|
terminalIds.push(ws.id);
|
|
381
371
|
}
|
|
382
372
|
// Rows the abort left undeleted are still expired and terminal, so they stay in the backlog the
|
|
@@ -394,7 +384,7 @@ export async function workspaceGC(context: { signal?: AbortSignal } = {}): Promi
|
|
|
394
384
|
for (const ws of all) {
|
|
395
385
|
if (signal?.aborted) break;
|
|
396
386
|
if (!STRANDABLE_STATUSES.has(ws.status) || ws.mode !== "isolated" || !ws.worktreePath) continue;
|
|
397
|
-
|
|
387
|
+
(await reelectRepoStewardAsync(ws.repoRoot));
|
|
398
388
|
const fresh = getWorkspace(ws.id);
|
|
399
389
|
if (!fresh || !STRANDABLE_STATUSES.has(fresh.status)) continue;
|
|
400
390
|
const ownerAlive = isOwnerAlive(fresh.ownerAgentId);
|
|
@@ -518,7 +508,7 @@ export async function workspaceGC(context: { signal?: AbortSignal } = {}): Promi
|
|
|
518
508
|
// resolve. (A stranded row that is genuinely in `conflict` status is already handed to a steward
|
|
519
509
|
// by the conflict-scan path; the GC backstop's job here is custody, not conflict resolution.)
|
|
520
510
|
const strandedHours = Math.round((now - strandedAt) / (60 * 60 * 1000));
|
|
521
|
-
const targets = routeWorkspaceCustodyEscalation({
|
|
511
|
+
const targets = await routeWorkspaceCustodyEscalation({
|
|
522
512
|
workspace: fresh,
|
|
523
513
|
subject: "Stranded workspace needs an owner",
|
|
524
514
|
body: `Workspace \`${fresh.branch ?? fresh.id}\` in ${fresh.repoRoot} is ${fresh.status} and has been stranded for ${strandedHours}h. Please coordinate ${fresh.status === "conflict" ? "conflict resolution" : "review/merge"} or clean up the worktree.`,
|
|
@@ -530,13 +520,13 @@ export async function workspaceGC(context: { signal?: AbortSignal } = {}): Promi
|
|
|
530
520
|
// fallback configured). Marking this "escalated" would silently swallow the strand and
|
|
531
521
|
// repeat every sweep forever. Instead leave it UNescalated (don't stamp escalatedAt, so
|
|
532
522
|
// the next sweep retries once recipients exist) and raise a louder operator-visible alarm.
|
|
533
|
-
raiseOperatorAlarm({
|
|
523
|
+
(await raiseOperatorAlarm({
|
|
534
524
|
clientId: `workspace-gc-unescalatable-${fresh.id}:${strandedAt}:${Math.floor(now / escalationMs)}`,
|
|
535
525
|
source: "workspace-gc",
|
|
536
526
|
subject: "Stranded workspace has no one to escalate to",
|
|
537
527
|
detail: `${fresh.branch ?? fresh.id} in ${fresh.repoRoot} has been ${fresh.status} for ${strandedHours}h but has no owner, coordinator, or fallback configured to escalate to. Configure AGENT_RELAY_WORKSPACE_STEWARD_FALLBACK or reclaim the worktree manually.`,
|
|
538
528
|
metadata: { maintenanceJobId: "workspace-gc", workspaceId: fresh.id, repoRoot: fresh.repoRoot, branch: fresh.branch, status: fresh.status, strandedAt },
|
|
539
|
-
});
|
|
529
|
+
}));
|
|
540
530
|
continue;
|
|
541
531
|
}
|
|
542
532
|
escalationTargets.push(...targets);
|
|
@@ -589,7 +579,7 @@ export async function workspaceGC(context: { signal?: AbortSignal } = {}): Promi
|
|
|
589
579
|
// operator alarm when it resolves to nobody) instead of `stewardAgentId ?? fallback`.
|
|
590
580
|
// Most strandable workspaces have no steward, so the old notice silently dropped while
|
|
591
581
|
// the state change still happened.
|
|
592
|
-
const targets = routeWorkspaceCustodyEscalationOrAlarm({
|
|
582
|
+
const targets = await routeWorkspaceCustodyEscalationOrAlarm({
|
|
593
583
|
source: "workspace-gc",
|
|
594
584
|
workspace: fresh,
|
|
595
585
|
type: "workspace.auto_abandoned",
|
|
@@ -620,12 +610,12 @@ export async function workspaceGC(context: { signal?: AbortSignal } = {}): Promi
|
|
|
620
610
|
if (signal?.aborted) break;
|
|
621
611
|
const orch = orchestrators.find((candidate) => workspacePathWithinBase(repoRoot, candidate.baseDir));
|
|
622
612
|
if (!orch?.agentId) continue;
|
|
623
|
-
const command =
|
|
613
|
+
const command = (await createCommandAsync({
|
|
624
614
|
type: "workspace.prune",
|
|
625
615
|
source: "system",
|
|
626
616
|
target: orch.agentId,
|
|
627
617
|
params: { repoRoot, requestedBy: "workspace-gc", requestedAt: now },
|
|
628
|
-
});
|
|
618
|
+
}));
|
|
629
619
|
emitCommand(command);
|
|
630
620
|
pruneCommands.push(command.id);
|
|
631
621
|
}
|
|
@@ -696,7 +686,7 @@ export async function refreshIdleWorkspaces(context: { signal?: AbortSignal } =
|
|
|
696
686
|
if (gitAhead !== undefined && gitAhead > 0) { skipped.push(`${ws.id}:ahead`); continue; }
|
|
697
687
|
if (gitDirtyCount !== undefined && gitDirtyCount > 0) { skipped.push(`${ws.id}:dirty`); continue; }
|
|
698
688
|
|
|
699
|
-
const command =
|
|
689
|
+
const command = (await createCommandAsync({
|
|
700
690
|
type: "workspace.idle-refresh",
|
|
701
691
|
source: "system",
|
|
702
692
|
target: orch.agentId,
|
|
@@ -711,7 +701,7 @@ export async function refreshIdleWorkspaces(context: { signal?: AbortSignal } =
|
|
|
711
701
|
requestedBy: "workspace-idle-refresh",
|
|
712
702
|
requestedAt: now,
|
|
713
703
|
},
|
|
714
|
-
});
|
|
704
|
+
}));
|
|
715
705
|
emitCommand(command);
|
|
716
706
|
dispatched.push(ws.id);
|
|
717
707
|
}
|
|
@@ -28,11 +28,11 @@ function managedPolicyEscalationRecipients(policy: SpawnPolicy, state: ManagedAg
|
|
|
28
28
|
// agent — often the repo steward that lands branches — is down. Route those to the same
|
|
29
29
|
// recipients as the offline branch, and when there is no reachable recipient raise an
|
|
30
30
|
// operator-visible alarm instead of silently dropping the signal.
|
|
31
|
-
export function escalateManagedSpawnFailure(
|
|
31
|
+
export async function escalateManagedSpawnFailure(
|
|
32
32
|
policy: SpawnPolicy,
|
|
33
33
|
state: ManagedAgentState,
|
|
34
34
|
opts: { phase: "failed" | "register-timeout" | "spawn-backoff"; error: string },
|
|
35
|
-
): void {
|
|
35
|
+
): Promise<void> {
|
|
36
36
|
const recipients = managedPolicyEscalationRecipients(policy, state);
|
|
37
37
|
const queuedMessages = queuedPolicyMessageCount(policy.name);
|
|
38
38
|
const subject = opts.phase === "failed"
|
|
@@ -55,17 +55,17 @@ export function escalateManagedSpawnFailure(
|
|
|
55
55
|
error: opts.error,
|
|
56
56
|
};
|
|
57
57
|
if (!recipients.length) {
|
|
58
|
-
raiseOperatorAlarm({
|
|
58
|
+
(await raiseOperatorAlarm({
|
|
59
59
|
clientId: `managed-policy-alarm-${opts.phase}-${policy.name}-${policy.orchestratorId}`,
|
|
60
60
|
source: "lifecycle-manager",
|
|
61
61
|
subject,
|
|
62
62
|
detail: `${body}\nNo owner, coordinator, or fallback is reachable to page.`,
|
|
63
63
|
view: "managed-agents",
|
|
64
64
|
metadata: payload,
|
|
65
|
-
});
|
|
65
|
+
}));
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
routeNotification({
|
|
68
|
+
await routeNotification({
|
|
69
69
|
type: "agent.spawn_failed",
|
|
70
70
|
scope: { target: `policy:${policy.name}` },
|
|
71
71
|
recipients,
|
|
@@ -85,14 +85,14 @@ export function escalateManagedSpawnFailure(
|
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export function escalateOfflineOrchestrator(policy: SpawnPolicy, state: ManagedAgentState, orch: Orchestrator | null | undefined): void {
|
|
88
|
+
export async function escalateOfflineOrchestrator(policy: SpawnPolicy, state: ManagedAgentState, orch: Orchestrator | null | undefined): Promise<void> {
|
|
89
89
|
if (state.consecutiveFailures < ORCHESTRATOR_OFFLINE_ESCALATION_FAILURES) return;
|
|
90
90
|
const recipients = managedPolicyEscalationRecipients(policy, state);
|
|
91
91
|
if (!recipients.length) return;
|
|
92
92
|
const host = orch?.hostname ? `${orch.hostname} (${policy.orchestratorId})` : policy.orchestratorId;
|
|
93
93
|
const queuedMessages = queuedPolicyMessageCount(policy.name);
|
|
94
94
|
const nextRetry = state.backoffUntil ? new Date(state.backoffUntil).toISOString() : "not scheduled";
|
|
95
|
-
routeNotification({
|
|
95
|
+
await routeNotification({
|
|
96
96
|
type: "agent.spawn_failed",
|
|
97
97
|
scope: { target: `policy:${policy.name}` },
|
|
98
98
|
recipients,
|
package/src/managed-policy.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runnerRuntimeTokenEnvAsync, type RunnerLaunchAuthority, type RunnerRuntimeTokenInput } from "./runtime-tokens";
|
|
1
2
|
import { getManifest } from "@humwick/providers";
|
|
2
3
|
import { getAgentProfile, resolveAgentProfileKey } from "./config-store";
|
|
3
4
|
import { effectiveProviderCatalogListAsync, ValidationError, withResolvedProvisioning, withResolvedProvisioningAsync } from "./db";
|
|
@@ -7,7 +8,6 @@ import { getAgent } from "./db/agents";
|
|
|
7
8
|
import { getOrchestratorAsync } from "./db/orchestrators-async";
|
|
8
9
|
import { composeProfileSpawnInstructions, composeProfileSpawnInstructionsAsync } from "./project-instructions";
|
|
9
10
|
import { getSelfEffortCeiling } from "./db/self-effort-permissions";
|
|
10
|
-
import { runnerRuntimeTokenEnv, runnerRuntimeTokenEnvAsync, type RunnerLaunchAuthority, type RunnerRuntimeTokenInput } from "./runtime-tokens";
|
|
11
11
|
import { recordedLaunchMode, resolveHostLaunchMode } from "./launch-mode";
|
|
12
12
|
import { buildSpawnCommand, generateSpawnAgentId, resolveSpawnModelParams, resolveSpawnModelParamsAsync } from "./spawn-command";
|
|
13
13
|
import { applyDefaultSharedMcp } from "./shared-mcp";
|
|
@@ -65,7 +65,7 @@ function systemTagsForManagedPolicy(policy: SpawnPolicy): string[] {
|
|
|
65
65
|
return tags;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export function buildManagedSpawnParams(policy: SpawnPolicy, requestId: string, ctx: ManagedSpawnContext): Record<string, unknown
|
|
68
|
+
export async function buildManagedSpawnParams(policy: SpawnPolicy, requestId: string, ctx: ManagedSpawnContext): Promise<Record<string, unknown>> {
|
|
69
69
|
if (policy.profile) policy = { ...policy, profile: resolveAgentProfileKey(policy.profile) ?? policy.profile };
|
|
70
70
|
assertManagedProviderEnabled(managedPolicyProviderDisabledMessage(policy));
|
|
71
71
|
const agentId = ctx.identityAgentId ?? generateSpawnAgentId();
|
|
@@ -97,7 +97,7 @@ export function buildManagedSpawnParams(policy: SpawnPolicy, requestId: string,
|
|
|
97
97
|
|
|
98
98
|
return buildManagedCommand({ policy, requestId, ctx, agentId, prompt, agentProfile, orchestrator, extraCwdPrefixes, composed,
|
|
99
99
|
modelParams: resolveSpawnModelParams(policy.provider, policy.model, policy.effort),
|
|
100
|
-
env:
|
|
100
|
+
env: (await runnerRuntimeTokenEnvAsync(managedTokenInput(policy, requestId, ctx, agentId, extraCwdPrefixes))),
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { upsertManagedAgentStateAsync, updateManagedAgentStateAsync } from "./db/lifecycle-tail-async";
|
|
1
2
|
// #2656 — "off means nothing new starts on it": a managed policy whose provider is disabled (operator
|
|
2
3
|
// switch or all models disabled) must not launch. The lifecycle manager parks the policy with a
|
|
3
4
|
// visible reason instead of spawning — no backoff churn, no crash loop — and its normal reconcile
|
|
4
5
|
// tick resumes the policy on its own once the provider is re-enabled.
|
|
5
6
|
import { findProviderCatalog, providerDisabledMessage, type ProviderCatalogEntry } from "@humwick/sdk/provider-catalog";
|
|
6
|
-
import { getManagedAgentState
|
|
7
|
+
import { getManagedAgentState } from "./config-store";
|
|
7
8
|
import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
8
9
|
import type { ManagedAgentState, SpawnPolicy } from "./types";
|
|
9
10
|
|
|
@@ -17,11 +18,11 @@ export function managedPolicyProviderDisabledMessage(policy: Pick<SpawnPolicy, "
|
|
|
17
18
|
* Record the skipped launch on the policy's state (`stopped` + lastError) so the dashboard shows why.
|
|
18
19
|
* Returns the new state only when it changed, so a caller emits once rather than every tick.
|
|
19
20
|
*/
|
|
20
|
-
export function parkManagedPolicyForDisabledProvider(policy: SpawnPolicy, message: string): ManagedAgentState | undefined {
|
|
21
|
+
export async function parkManagedPolicyForDisabledProvider(policy: SpawnPolicy, message: string): Promise<ManagedAgentState | undefined> {
|
|
21
22
|
const state = getManagedAgentState(policy.name);
|
|
22
23
|
if (state?.status === "stopped" && state.lastError === message) return undefined;
|
|
23
24
|
const next = state
|
|
24
|
-
?
|
|
25
|
-
:
|
|
25
|
+
? (await updateManagedAgentStateAsync(policy.name, { status: "stopped", spawnRequestId: undefined, lastError: message }))
|
|
26
|
+
: (await upsertManagedAgentStateAsync({ policyName: policy.name, status: "stopped", orchestratorId: policy.orchestratorId, provider: policy.provider, restartCount: 0, consecutiveFailures: 0, lastError: message }));
|
|
26
27
|
return next ?? undefined;
|
|
27
28
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { upsertManagedAgentStateAsync } from "./db/lifecycle-tail-async";
|
|
2
|
+
import { getManagedAgentState, getSpawnPolicy } from "./config-store";
|
|
2
3
|
import type { ManagedAgentState, SpawnPolicy } from "./types";
|
|
3
4
|
|
|
4
5
|
/** True only while an async preparation still owns the policy's starting reservation. */
|
|
@@ -15,9 +16,9 @@ export function managedSpawnReservationCurrent(policy: SpawnPolicy, policySnapsh
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
/** Return a preparation's own reservation to its pre-spawn state when the host drains. */
|
|
18
|
-
export function restoreDrainedSpawnReservation(policy: SpawnPolicy, requestId: string, prior: ManagedAgentState | null): void {
|
|
19
|
+
export async function restoreDrainedSpawnReservation(policy: SpawnPolicy, requestId: string, prior: ManagedAgentState | null): Promise<void> {
|
|
19
20
|
const current = getManagedAgentState(policy.name);
|
|
20
21
|
if (current?.status !== "starting" || current.spawnRequestId !== requestId) return;
|
|
21
|
-
|
|
22
|
-
provider: policy.provider, spawnRequestId: undefined, restartCount: 0, consecutiveFailures: 0 });
|
|
22
|
+
(await upsertManagedAgentStateAsync(prior ?? { policyName: policy.name, status: "stopped", orchestratorId: policy.orchestratorId,
|
|
23
|
+
provider: policy.provider, spawnRequestId: undefined, restartCount: 0, consecutiveFailures: 0 }));
|
|
23
24
|
}
|