@humwick/server 0.130.1 → 0.130.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/docs/guides/tools-administration.md +4 -0
- package/docs/guides/tools-coordination.md +1 -0
- package/docs/guides/tools-tasks.md +8 -0
- package/docs/guides/workflows.md +58 -0
- package/docs/openapi.json +414 -1
- package/package.json +8 -17
- package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-BWLqMGKG.js} +2 -2
- package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-3TlIQpjk.js} +2 -2
- package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-Wimm0Czv.js} +2 -2
- package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-Bau_vNax.js} +2 -2
- package/public/assets/{activity-yLztXEBt.js → activity-CrSurn-r.js} +2 -2
- package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-CknAJKcb.js} +2 -2
- package/public/assets/agent-liveness-CaNwK0Zf.js +2 -0
- package/public/assets/{agent-name-CTZBrSD9.js → agent-name-DKbiIhBo.js} +2 -2
- package/public/assets/agent-profiles-DwF6EQ4R.js +2 -0
- package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-Diz4Sjjb.js} +2 -2
- package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-CRhpRQza.js} +2 -2
- package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-LrUEU3lB.js} +2 -2
- package/public/assets/{agents-BYDslkJc.js → agents-B-v66C-4.js} +2 -2
- package/public/assets/{analytics-B-XSbEMC.js → analytics-BqCb-LKi.js} +2 -2
- package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-kf4pGuAR.js} +2 -2
- package/public/assets/{artifacts-oBOqks-2.js → artifacts-D32zyw4Y.js} +2 -2
- package/public/assets/{artifacts-DPN1q4pA.js → artifacts-DJevXcvg.js} +2 -2
- package/public/assets/{automation-4KcRFXEu.js → automation-BpRajx_5.js} +2 -2
- package/public/assets/{backlog-Cw9xPTCF.js → backlog-D_UjTdzX.js} +2 -2
- package/public/assets/{busy-health-CTu32MZt.js → busy-health-N3VEh4ao.js} +2 -2
- package/public/assets/{channels-BiXzg1G9.js → channels-9jopungq.js} +2 -2
- package/public/assets/{chat-Ctk2qwvA.js → chat-CSXrTB3C.js} +2 -2
- package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-p1NnmqK0.js} +2 -2
- package/public/assets/{connectors-C36ax4Vw.js → connectors-CZdqlDNL.js} +2 -2
- package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BgdsE4dG.js} +2 -2
- package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-30aXTKva.js} +2 -2
- package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-B_jvw2GE.js} +2 -2
- package/public/assets/{deploys-Bwfq146q.js → deploys-DoN6bAb5.js} +2 -2
- package/public/assets/{display-CfO7rTuF.js → display-BrKWFGJz.js} +2 -2
- package/public/assets/{files-DhhZI2Ih.js → files-D6XEu8Ng.js} +2 -2
- package/public/assets/{fleet-B_rFEbnL.js → fleet-C7jQWbw2.js} +2 -2
- package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BiDs3MLx.js} +2 -2
- package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-CQBwc1fb.js} +3 -3
- package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-DyG0WsQS.js} +2 -2
- package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-DKaW31dN.js} +2 -2
- package/public/assets/index-CRHep3M9.js +15 -0
- package/public/assets/index-MYAIwYTX.css +2 -0
- package/public/assets/{insights-fH1EL-qZ.js → insights-CzrtkpOE.js} +2 -2
- package/public/assets/{integrations-C5Oix6ng.js → integrations-aVsUmigt.js} +2 -2
- package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-BnSlM_95.js} +2 -2
- package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-qe7Ee47Z.js} +2 -2
- package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-NfjqFZAk.js} +2 -2
- package/public/assets/{managed-agents-egllbpij.js → managed-agents-BiH0HgaE.js} +2 -2
- package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-BixaT3Xr.js} +3 -3
- package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-DNGlWIS3.js} +2 -2
- package/public/assets/{memory-C8WmXsyr.js → memory-DNsCl6UX.js} +2 -2
- package/public/assets/{messages-BzQdqlO6.js → messages-DopU1L1X.js} +2 -2
- package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Cq7ckLQT.js} +2 -2
- package/public/assets/{overview-DCUnI4Z7.js → overview-C1DCTzzV.js} +2 -2
- package/public/assets/{pairs-ftCd2XcD.js → pairs-DE_m6rrE.js} +2 -2
- package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-B3rfh77F.js} +2 -2
- package/public/assets/project-profile-instructions-DQWZE0kA.js +2 -0
- package/public/assets/projects-Br2Bd2h1.js +2 -0
- package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-BKvFsvGD.js} +2 -2
- package/public/assets/provider-display-DsuWMvVL.js +2 -0
- package/public/assets/{registry-DWCU5c8q.js → registry-Cw38_6gW.js} +2 -2
- package/public/assets/run-card-CpKqRbhg.js +2 -0
- package/public/assets/scroll-text-BKXS1ahr.js +2 -0
- package/public/assets/{secrets-C7GIUATz.js → secrets-DxI-vzSq.js} +2 -2
- package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-D9s_VJbd.js} +2 -2
- package/public/assets/{settings-C6sZ9eBa.js → settings-CKZTVGIx.js} +2 -2
- package/public/assets/{shipping-Bu8p1gEw.js → shipping-B9YtnwtG.js} +2 -2
- package/public/assets/{stats-V9Z-GxUd.js → stats-DyUBKyWI.js} +2 -2
- package/public/assets/{status-dot-CVv0iGHL.js → status-dot-DYqlEUhJ.js} +2 -2
- package/public/assets/{store-BvtFdlGX.js → store-DcClXLH0.js} +3 -3
- package/public/assets/{tasks-CXU75HRF.js → tasks-Bh7Chr4-.js} +2 -2
- package/public/assets/{teams-BPf976zb.js → teams-bowI7qVY.js} +2 -2
- package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-BgVLHdsU.js} +3 -3
- package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-Dz22UPy6.js} +2 -2
- package/public/assets/token-estimate-Co-Jdphh.js +2 -0
- package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-DSQQ-iY7.js} +2 -2
- package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-samMQhQf.js} +2 -2
- package/public/assets/use-project-list-59LheVP7.js +2 -0
- package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BZa25xEB.js} +2 -2
- package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-DHxlvUvV.js} +2 -2
- package/public/assets/versioned-prompt-editor-BmED5DnJ.js +8 -0
- package/public/assets/{work-queue-TCjJxeT-.js → work-queue-CSvpuNLz.js} +2 -2
- package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-DAZcj5qt.js} +2 -2
- package/public/assets/{workflows-CXQX20Qj.js → workflows-DdmxRSyg.js} +2 -2
- package/public/assets/{workspaces-qSYzKYMA.js → workspaces-BxeKfVuF.js} +2 -2
- package/public/cosmos.html +4 -4
- package/public/index.html +31 -29
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
- package/runner/src/adapters/claude-effort.ts +2 -4
- package/runner/src/adapters/codex-launch-config.ts +4 -5
- package/runner/src/adapters/codex.ts +1 -1
- package/runner/src/adapters/opencode-provider.ts +7 -22
- package/runner/src/adapters/opencode.ts +4 -4
- package/runner/src/agent-runner/credentials.ts +42 -12
- package/runner/src/agent-runner/inbox.ts +27 -0
- package/runner/src/agent-runner/lifecycle-milestones.ts +0 -2
- package/runner/src/bin/claude.ts +4 -0
- package/runner/src/bin/codex.ts +4 -0
- package/runner/src/bin/opencode.ts +4 -0
- package/runner/src/index.ts +5 -2
- package/runner/src/provider-setup.ts +34 -0
- package/runner/src/providers.ts +2 -1
- package/runner/src/runner-core.ts +18 -23
- package/runner/src/spawn-input.ts +2 -1
- package/runner/src/test-run-dedup.ts +59 -56
- package/scripts/cd-config.ts +8 -0
- package/scripts/gate-run-lease.ts +10 -7
- package/scripts/gate.sh +5 -1
- package/src/branch-land-notice.ts +93 -0
- package/src/branch-landed.ts +7 -76
- package/src/bus.ts +19 -20
- package/src/cd-deploy-notify.ts +30 -2
- package/src/cd-host-lag-notify.ts +7 -6
- package/src/ci/incidents.ts +2 -2
- package/src/ci/monitor.ts +5 -8
- package/src/ci/root-missing.ts +2 -7
- package/src/ci/transition-notify.ts +7 -39
- package/src/cli/cd-pauses.ts +19 -0
- package/src/cli/orchestrator.ts +1 -1
- package/src/cli/state.ts +2 -19
- package/src/cli/task-edit.ts +22 -0
- package/src/cli/task.ts +3 -1
- package/src/cli/workflow.ts +8 -2
- package/src/commands-db.ts +3 -3
- package/src/config-store-write.ts +13 -1
- package/src/config-store.ts +25 -14
- package/src/db/activity-async.ts +6 -1
- package/src/db/activity-write-ops.ts +12 -1
- package/src/db/activity.ts +1 -14
- package/src/db/agent-search.ts +1 -15
- package/src/db/agents-async.ts +52 -5
- package/src/db/agents-write-ops.ts +12 -3
- package/src/db/agents.ts +81 -54
- package/src/db/ci-incidents-async.ts +5 -5
- package/src/db/ci-incidents-write-ops.ts +2 -2
- package/src/db/command-row.ts +10 -2
- package/src/db/commands-list-query.ts +2 -0
- package/src/db/commands-spawn-read.ts +1 -1
- package/src/db/config-store-write-ops.ts +7 -2
- package/src/db/continuation-archives-async.ts +5 -0
- package/src/db/continuation-archives-write-ops.ts +7 -0
- package/src/db/continuations.ts +2 -2
- package/src/db/db-writer-worker.ts +8 -1
- package/src/db/deploy-events-async.ts +1 -0
- package/src/db/deploy-events-read-ops.ts +2 -1
- package/src/db/deploy-events.ts +35 -4
- package/src/db/hosts-registration.ts +10 -2
- package/src/db/hosts.ts +9 -6
- package/src/db/inbox.ts +0 -14
- package/src/db/index.ts +2 -0
- package/src/db/integrations-async.ts +8 -0
- package/src/db/integrations-ops.ts +15 -0
- package/src/db/integrations.ts +15 -21
- package/src/db/issue-lifecycle.ts +3 -4
- package/src/db/issue-refs-async.ts +20 -0
- package/src/db/issue-refs-write-ops.ts +29 -0
- package/src/db/issue-refs.ts +7 -8
- package/src/db/long-running-work.ts +2 -2
- package/src/db/main-connection-writes.ts +23 -2
- package/src/db/maintenance-job-reconcile.ts +61 -0
- package/src/db/maintenance-job-runs-async.ts +14 -0
- package/src/db/maintenance-job-write-ops.ts +9 -0
- package/src/db/mappers.ts +2 -14
- package/src/db/merge-lease.ts +1 -15
- package/src/db/migrations.ts +7 -15
- package/src/db/orchestrators.ts +4 -72
- package/src/db/pairs.ts +1 -13
- package/src/db/project-profile-instructions.ts +50 -0
- package/src/db/prompt-versions.ts +9 -2
- package/src/db/provider-config-history.ts +8 -3
- package/src/db/provider-model-seed.ts +34 -0
- package/src/db/read-op-marshaling-fixtures.ts +5 -0
- package/src/db/read-ops.ts +4 -0
- package/src/db/routing-policy.ts +2 -3
- package/src/db/schema.ts +9 -3
- package/src/db/spawn-delegation-state.ts +11 -4
- package/src/db/stats-async.ts +4 -3
- package/src/db/task-entity.ts +23 -2
- package/src/db/task-list.ts +3 -1
- package/src/db/tasks.ts +1 -17
- package/src/db/timer-ingress.ts +7 -1
- package/src/db/token-db-write-ops.ts +17 -0
- package/src/db/token-standing.ts +15 -11
- package/src/db/workflow-contact.ts +1 -1
- package/src/db/workflow-task-read-ops.ts +30 -0
- package/src/db/write-ops.ts +6 -0
- package/src/db/writer-gateway.ts +17 -11
- package/src/dispatch-availability.ts +10 -6
- package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
- package/src/host/disk-alert.ts +121 -0
- package/src/lifecycle-manager.ts +3 -2
- package/src/maintenance/cd-watcher.ts +3 -3
- package/src/maintenance/critical-job-watchdog.ts +16 -17
- package/src/maintenance/index.ts +1 -0
- package/src/maintenance/jobs.ts +3 -2
- package/src/maintenance/scheduler.ts +84 -128
- package/src/maintenance/stalled-delivery-pager.ts +6 -5
- package/src/maintenance/workspaces.ts +1 -1
- package/src/managed-policy.ts +31 -27
- package/src/mcp/index.ts +5 -2
- package/src/mcp/output-descriptions.ts +8 -8
- package/src/mcp/presentation.ts +4 -4
- package/src/mcp/project-profile-instructions.ts +32 -0
- package/src/mcp/tool-groups.ts +1 -1
- package/src/mcp/tools-spawn.ts +4 -3
- package/src/mcp-agent-action.ts +1 -2
- package/src/mcp-issue-ref-tools.ts +12 -11
- package/src/mcp-maintenance-tools.ts +2 -2
- package/src/mcp-profile-trust-tools.ts +4 -4
- package/src/mcp-provider-catalog.ts +2 -2
- package/src/mcp-task-tools.ts +11 -5
- package/src/mcp-team-template-tools.ts +3 -3
- package/src/mcp-workflow-tools.ts +10 -3
- package/src/notification-types.ts +2 -1
- package/src/operator-alarm.ts +26 -11
- package/src/project-instructions.ts +4 -0
- package/src/project-profile-instruction-text.ts +6 -0
- package/src/project-profile-instructions.ts +20 -0
- package/src/provider-catalog-store.ts +3 -49
- package/src/provider-config-store.ts +12 -11
- package/src/provider-model-seed.ts +1 -18
- package/src/provider-quota-config-store.ts +5 -4
- package/src/provider-secrets.test-fixture.ts +3 -3
- package/src/recipe-runner.ts +2 -2
- package/src/repo-config-source.ts +10 -4
- package/src/routes/agent-profiles.ts +6 -6
- package/src/routes/agents-spawn.ts +1 -1
- package/src/routes/agents.ts +6 -6
- package/src/routes/commands-listing.ts +4 -0
- package/src/routes/commands.ts +2 -2
- package/src/routes/continuation-archives.ts +2 -2
- package/src/routes/index.ts +10 -1
- package/src/routes/integrations.ts +24 -50
- package/src/routes/issue-refs.ts +7 -7
- package/src/routes/orchestrator-bootstrap.ts +1 -1
- package/src/routes/orchestrator-host-registration.ts +2 -2
- package/src/routes/orchestrator-host-utilization.ts +3 -1
- package/src/routes/orchestrator-proxy.ts +2 -2
- package/src/routes/orchestrator.ts +2 -2
- package/src/routes/project-profile-instructions.ts +27 -0
- package/src/routes/project-trust.ts +2 -2
- package/src/routes/provider-config.ts +6 -6
- package/src/routes/provider-quota-config.ts +2 -2
- package/src/routes/settings.ts +5 -5
- package/src/routes/stats.ts +6 -6
- package/src/routes/task-http.ts +15 -1
- package/src/routes/task-issues.ts +11 -26
- package/src/routes/tasks.ts +22 -53
- package/src/routes/team-templates.ts +5 -5
- package/src/routes/teams-config.ts +2 -2
- package/src/routes/tokens.ts +12 -5
- package/src/routes/workflows.ts +20 -5
- package/src/routing-policy-normalize.ts +1 -2
- package/src/routing-step-defaults.ts +0 -2
- package/src/runner-token-mint-limit.ts +63 -0
- package/src/runtime-grant.ts +4 -2
- package/src/runtime-tokens.ts +39 -7
- package/src/security.ts +13 -20
- package/src/server-background.ts +1 -1
- package/src/server.ts +5 -2
- package/src/services/host-utilization.ts +23 -0
- package/src/services/idle-epics.ts +5 -6
- package/src/services/issue-lifecycle.ts +57 -61
- package/src/services/issue-refs.ts +9 -13
- package/src/services/kernel-timers-cutover-chaos-worker.ts +10 -7
- package/src/services/kernel-timers-ingress.ts +1 -1
- package/src/services/landed-ancestry.ts +19 -3
- package/src/services/memory-areas.ts +15 -51
- package/src/services/plan-ci-notice.ts +57 -0
- package/src/services/plan-ci-recovery.ts +2 -55
- package/src/services/plan-wake-digest.ts +1 -0
- package/src/services/profile-trust.ts +4 -3
- package/src/services/project-config-read.ts +70 -0
- package/src/services/project-profile-instructions.ts +54 -0
- package/src/services/provisioning-harness/budget-baseline.json +4 -4
- package/src/services/restart-spawn.ts +61 -49
- package/src/services/self-care-state.ts +2 -1
- package/src/services/self-effort.ts +5 -5
- package/src/services/send-message.ts +5 -1
- package/src/services/spawn-agent.ts +13 -2
- package/src/services/spawn-chain-inspector.ts +6 -13
- package/src/services/spawn-policy.ts +68 -0
- package/src/services/spawn-tree-cleanup.ts +2 -2
- package/src/services/task-dispatch.ts +3 -0
- package/src/services/task-state-reconcile.ts +6 -14
- package/src/services/team-templates.ts +9 -8
- package/src/services/transient-agent-reaper.ts +3 -1
- package/src/services/workflow-auto-start.ts +25 -13
- package/src/services/workflow-ci-main.ts +1 -1
- package/src/services/workflow-deferred-cleanup.ts +4 -2
- package/src/services/workflow-effect-driver.ts +2 -0
- package/src/services/workflow-effects.ts +18 -11
- package/src/services/workflow-land-notice.ts +10 -0
- package/src/services/workflow-pause-text.ts +43 -0
- package/src/services/workflow-reconcile.ts +13 -11
- package/src/services/workflow-run-view.ts +2 -0
- package/src/services/workflow-runs.ts +29 -13
- package/src/services/workflow-supervisor-state.ts +5 -0
- package/src/services/workflow-supervisor.ts +7 -3
- package/src/settings/registry.ts +1 -2
- package/src/spawn-command.ts +4 -4
- package/src/spawn-models.ts +10 -12
- package/src/spawn-placement.ts +1 -1
- package/src/spawn-targets.ts +1 -1
- package/src/startup-context-injection.ts +1 -0
- package/src/state-adoption/cd-cutover.ts +53 -7
- package/src/state-adoption/fleet-cutover.ts +35 -0
- package/src/state-adoption/fleet-plan.ts +36 -0
- package/src/state-adoption/index.ts +2 -2
- package/src/state-adoption/linux-plan.ts +2 -2
- package/src/state-adoption/linux.ts +2 -2
- package/src/state-adoption/macos-plan.ts +2 -2
- package/src/state-adoption/macos.ts +1 -1
- package/src/steward.ts +7 -6
- package/src/task-dispatch-live.ts +1 -1
- package/src/task-dispatcher.ts +1 -1
- package/src/teams-config-store.ts +4 -3
- package/src/token-db-write.ts +10 -0
- package/src/workspace-command-settle.ts +2 -1
- package/src/workspace-steward-wake.ts +2 -2
- package/node_modules/@humwick/kernel/LICENSE +0 -167
- package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
- package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
- package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
- package/node_modules/@humwick/kernel/config.ts +0 -111
- package/node_modules/@humwick/kernel/contract.ts +0 -572
- package/node_modules/@humwick/kernel/control-authz.ts +0 -281
- package/node_modules/@humwick/kernel/control.ts +0 -645
- package/node_modules/@humwick/kernel/effects.ts +0 -493
- package/node_modules/@humwick/kernel/event-log.ts +0 -319
- package/node_modules/@humwick/kernel/http-transport.ts +0 -384
- package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
- package/node_modules/@humwick/kernel/identity.ts +0 -248
- package/node_modules/@humwick/kernel/index.ts +0 -28
- package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
- package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
- package/node_modules/@humwick/kernel/landing.ts +0 -953
- package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
- package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
- package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
- package/node_modules/@humwick/kernel/message-authority.ts +0 -281
- package/node_modules/@humwick/kernel/module-host.ts +0 -722
- package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
- package/node_modules/@humwick/kernel/package.json +0 -39
- package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
- package/node_modules/@humwick/kernel/persistence.ts +0 -510
- package/node_modules/@humwick/kernel/registry.ts +0 -60
- package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
- package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
- package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
- package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
- package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
- package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
- package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
- package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
- package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
- package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
- package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
- package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
- package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
- package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
- package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
- package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
- package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
- package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
- package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
- package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
- package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
- package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
- package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
- package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
- package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
- package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
- package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
- package/node_modules/@humwick/offload/LICENSE +0 -167
- package/node_modules/@humwick/offload/README.md +0 -57
- package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
- package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
- package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
- package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
- package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/envelope.js +0 -80
- package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
- package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
- package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/index.js +0 -5
- package/node_modules/@humwick/offload/dist/index.js.map +0 -1
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
- package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
- package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
- package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/types.js +0 -176
- package/node_modules/@humwick/offload/dist/types.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
- package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
- package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
- package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker.js +0 -1
- package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
- package/node_modules/@humwick/offload/package.json +0 -42
- package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
- package/node_modules/@humwick/offload/src/envelope.ts +0 -92
- package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
- package/node_modules/@humwick/offload/src/index.ts +0 -4
- package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
- package/node_modules/@humwick/offload/src/types.ts +0 -235
- package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
- package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
- package/node_modules/@humwick/offload/src/worker.ts +0 -5
- package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
- package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
- package/public/assets/index-B7ZerHLY.css +0 -2
- package/public/assets/index-BU3uWo2t.js +0 -15
- package/public/assets/projects-B_lmcqfO.js +0 -2
- package/public/assets/provider-display-DbLWpP3j.js +0 -2
- package/public/assets/run-card-CO_bguQf.js +0 -2
- package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
- package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
- package/runner/src/gate-run-lease-probe.ts +0 -38
- package/src/db/claude-catalog-reconcile.ts +0 -27
- package/src/routing-model-aliases.ts +0 -21
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isHumanAgentId } from "@humwick/sdk";
|
|
2
|
+
import type { BranchLandedInput, resolveLandedTask } from "./branch-landed";
|
|
3
|
+
import { getAgent, getTaskDetail } from "./db";
|
|
4
|
+
import { renderTemplate } from "./prompt-resolver";
|
|
5
|
+
import { routeNotification, INFORMATIONAL_TTL_MS } from "./notification-router";
|
|
6
|
+
import { stampLifecycleSignal, taskSignalKey } from "./lifecycle-signal-registry";
|
|
7
|
+
import { resolveCoordinatorAgentId } from "./steward-identity";
|
|
8
|
+
import { resolveTaskOwnerAgentId } from "./task-owner";
|
|
9
|
+
import { audienceWithTaskOwnerRole } from "./task-owner-notices";
|
|
10
|
+
import { notifyLandUnconfirmed, verifyPublishedLand } from "./services/landed-ancestry";
|
|
11
|
+
|
|
12
|
+
export async function announceVerifiedLand(input: BranchLandedInput, landedTask: ReturnType<typeof resolveLandedTask>, idempotencyKey?: string): Promise<void> {
|
|
13
|
+
const { workspace } = input;
|
|
14
|
+
const base = workspace.baseRef ?? "main";
|
|
15
|
+
const verification = input.mergedSha && input.landVerifiedSha === input.mergedSha
|
|
16
|
+
? "confirmed" : await verifyPublishedLand(workspace.repoRoot, input.mergedSha, workspace.baseRef);
|
|
17
|
+
if (verification !== "confirmed") {
|
|
18
|
+
notifyLandUnconfirmed({ workspace, sha: input.mergedSha, targetRef: base.startsWith("origin/") ? base : `origin/${base}`, kind: verification });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const landedBranch = workspace.branch;
|
|
22
|
+
const shortSha = input.mergedSha?.slice(0, 12);
|
|
23
|
+
const landedTaskPayload = landedTask ? { taskId: landedTask.taskId, taskLabel: landedTask.taskLabel } : {};
|
|
24
|
+
const author = workspace.ownerAgentId;
|
|
25
|
+
const shaLabel = shortSha ? ` as \`${shortSha}\`` : "";
|
|
26
|
+
const subjectLabel = input.subject ? ` — "${input.subject}"` : "";
|
|
27
|
+
const payload = {
|
|
28
|
+
verified: true,
|
|
29
|
+
kind: "branch.landed",
|
|
30
|
+
workspaceId: workspace.id,
|
|
31
|
+
repoRoot: workspace.repoRoot,
|
|
32
|
+
branch: landedBranch,
|
|
33
|
+
base,
|
|
34
|
+
sha: input.mergedSha,
|
|
35
|
+
author,
|
|
36
|
+
newBranch: input.newBranch,
|
|
37
|
+
pushed: input.pushed,
|
|
38
|
+
...landedTaskPayload,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// The branch author cares most — push regardless of online (store-ahead delivers it on
|
|
42
|
+
// next poll if they've moved on, #234). A live owner continues in its worktree.
|
|
43
|
+
if (author) {
|
|
44
|
+
const branchLabel = landedBranch ? `\`${landedBranch}\`` : "Your branch";
|
|
45
|
+
const continueLabel = input.newBranch
|
|
46
|
+
? ` You're now on \`${input.newBranch}\` — keep working there.`
|
|
47
|
+
: " Worktree reclaimed.";
|
|
48
|
+
// Durable (ttl null): the author's own branch landing must reach them even after they go
|
|
49
|
+
// offline (store-ahead #234) — not a bystander notice that goes stale.
|
|
50
|
+
const notices = routeNotification({
|
|
51
|
+
type: "branch.landed",
|
|
52
|
+
scope: { author, repoRoot: workspace.repoRoot, workspaceId: workspace.id },
|
|
53
|
+
recipients: [author],
|
|
54
|
+
ttlMs: null,
|
|
55
|
+
message: {
|
|
56
|
+
subject: "Your branch landed",
|
|
57
|
+
body: renderTemplate("notify.land.success", {
|
|
58
|
+
branch: branchLabel,
|
|
59
|
+
base,
|
|
60
|
+
sha: `${shaLabel}${subjectLabel}`,
|
|
61
|
+
publishLabel: "",
|
|
62
|
+
continueLabel,
|
|
63
|
+
}) + " verified: true.",
|
|
64
|
+
payload: { ...payload, notification: "landed-success" },
|
|
65
|
+
idempotencyKey,
|
|
66
|
+
replyExpected: false,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if (landedTask && input.mergedSha) {
|
|
70
|
+
for (const notice of notices) stampLifecycleSignal(notice.to, taskSignalKey(landedTask.taskId, "landed-on-main", input.mergedSha));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Coordinator copy (#2837), informational. No explicit recipients: the router records the rule decision (#446).
|
|
75
|
+
const mainScope = { author, repoRoot: workspace.repoRoot, workspaceId: workspace.id, teamId: landedTask?.teamId ?? (author ? getAgent(author)?.teamId : undefined), coordinatorAgentIds: [...new Set([resolveCoordinatorAgentId(author), landedTask && resolveTaskOwnerAgentId(getTaskDetail(landedTask.taskId))].filter((id): id is string => !!id && !isHumanAgentId(id)))] };
|
|
76
|
+
const branchLabel = landedBranch ? `\`${landedBranch}\`` : "A branch";
|
|
77
|
+
const authorLabel = author ? ` by \`${author}\`` : "";
|
|
78
|
+
const notices = routeNotification({
|
|
79
|
+
type: "branch.landed",
|
|
80
|
+
scope: mainScope, ...audienceWithTaskOwnerRole("branch.landed", mainScope, landedTask?.taskId), // #2862
|
|
81
|
+
ttlMs: INFORMATIONAL_TTL_MS,
|
|
82
|
+
message: {
|
|
83
|
+
subject: `Merged to ${base}`,
|
|
84
|
+
body: `🔀 ${branchLabel}${authorLabel} merged to \`${base}\`${shaLabel}${subjectLabel}. verified: true.`,
|
|
85
|
+
payload: { ...payload, notification: "landed-success" },
|
|
86
|
+
idempotencyKey,
|
|
87
|
+
replyExpected: false,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
if (landedTask && input.mergedSha) {
|
|
91
|
+
for (const notice of notices) stampLifecycleSignal(notice.to, taskSignalKey(landedTask.taskId, "landed-on-main", input.mergedSha));
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/branch-landed.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { announceVerifiedLand } from "./branch-land-notice";
|
|
1
2
|
import { errMessage, isHumanAgentId, type BaseWorktreeSyncResult } from "@humwick/sdk";
|
|
2
3
|
import { emitRelayEvent } from "./events";
|
|
3
4
|
import { routeNotification, INFORMATIONAL_TTL_MS } from "./notification-router";
|
|
@@ -32,6 +33,8 @@ export interface BranchLandedInput extends LandedTaskAttributionInput {
|
|
|
32
33
|
workspace: Pick<WorkspaceRecord, "id" | "repoRoot" | "branch" | "baseRef" | "baseSha" | "ownerAgentId" | "orchestratorId">;
|
|
33
34
|
/** SHA the base now points at after the land. */
|
|
34
35
|
mergedSha?: string;
|
|
36
|
+
/** Landing host proof from its fresh origin ancestry check. Must match mergedSha. */
|
|
37
|
+
landVerifiedSha?: string;
|
|
35
38
|
/** Original command settlement clock when recovering an older land. */
|
|
36
39
|
landedAt?: number;
|
|
37
40
|
/** Subject line of the landed commit, when the orchestrator reported it. */
|
|
@@ -168,7 +171,7 @@ async function landedCommitSubjects(input: BranchLandedInput, log: BranchLandedL
|
|
|
168
171
|
}, log);
|
|
169
172
|
}
|
|
170
173
|
|
|
171
|
-
function resolveLandedTask(input: LandedTaskAttributionInput): {
|
|
174
|
+
export function resolveLandedTask(input: LandedTaskAttributionInput): {
|
|
172
175
|
taskId: number;
|
|
173
176
|
taskLabel: string;
|
|
174
177
|
projectId?: string;
|
|
@@ -348,79 +351,7 @@ export function announceBranchLanded(input: BranchLandedInput): void {
|
|
|
348
351
|
}
|
|
349
352
|
})());
|
|
350
353
|
|
|
351
|
-
|
|
352
|
-
const shaLabel = shortSha ? ` as \`${shortSha}\`` : "";
|
|
353
|
-
const subjectLabel = input.subject ? ` — "${input.subject}"` : "";
|
|
354
|
-
const payload = {
|
|
355
|
-
kind: "branch.landed",
|
|
356
|
-
workspaceId: workspace.id,
|
|
357
|
-
repoRoot: workspace.repoRoot,
|
|
358
|
-
branch: landedBranch,
|
|
359
|
-
base,
|
|
360
|
-
sha: input.mergedSha,
|
|
361
|
-
author,
|
|
362
|
-
newBranch: input.newBranch,
|
|
363
|
-
pushed: input.pushed,
|
|
364
|
-
...landedTaskPayload,
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
// A local-only land (push disabled, or base has no upstream) has NOT reached the
|
|
368
|
-
// remote — say so rather than implying a published merge an agent would report as
|
|
369
|
-
// shipped (#285). `undefined` (older orchestrators) keeps the message generic.
|
|
370
|
-
const publishLabel = input.pushed === false ? ` Not yet pushed to \`origin/${base}\` — local only.` : "";
|
|
371
|
-
|
|
372
|
-
// The branch author cares most — push regardless of online (store-ahead delivers it on
|
|
373
|
-
// next poll if they've moved on, #234). A live owner continues in its worktree.
|
|
374
|
-
if (author) {
|
|
375
|
-
const branchLabel = landedBranch ? `\`${landedBranch}\`` : "Your branch";
|
|
376
|
-
const continueLabel = input.newBranch
|
|
377
|
-
? ` You're now on \`${input.newBranch}\` — keep working there.`
|
|
378
|
-
: " Worktree reclaimed.";
|
|
379
|
-
// Durable (ttl null): the author's own branch landing must reach them even after they go
|
|
380
|
-
// offline (store-ahead #234) — not a bystander notice that goes stale.
|
|
381
|
-
const notices = routeNotification({
|
|
382
|
-
type: "branch.landed",
|
|
383
|
-
scope: { author, repoRoot: workspace.repoRoot, workspaceId: workspace.id },
|
|
384
|
-
recipients: [author],
|
|
385
|
-
ttlMs: null,
|
|
386
|
-
message: {
|
|
387
|
-
subject: "Your branch landed",
|
|
388
|
-
body: renderTemplate("notify.land.success", {
|
|
389
|
-
branch: branchLabel,
|
|
390
|
-
base,
|
|
391
|
-
sha: `${shaLabel}${subjectLabel}`,
|
|
392
|
-
publishLabel,
|
|
393
|
-
continueLabel,
|
|
394
|
-
}),
|
|
395
|
-
payload: { ...payload, notification: "landed-success" },
|
|
396
|
-
idempotencyKey,
|
|
397
|
-
replyExpected: false,
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
if (landedTask && input.mergedSha) {
|
|
401
|
-
for (const notice of notices) stampLifecycleSignal(notice.to, taskSignalKey(landedTask.taskId, "landed-on-main", input.mergedSha));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Coordinator copy (#2837), informational. No explicit recipients: the router records the rule decision (#446).
|
|
406
|
-
const mainScope = { author, repoRoot: workspace.repoRoot, workspaceId: workspace.id, teamId: landedTask?.teamId ?? (author ? getAgent(author)?.teamId : undefined), coordinatorAgentIds: [...new Set([resolveCoordinatorAgentId(author), landedTask && resolveTaskOwnerAgentId(getTaskDetail(landedTask.taskId))].filter((id): id is string => !!id && !isHumanAgentId(id)))] };
|
|
407
|
-
const branchLabel = landedBranch ? `\`${landedBranch}\`` : "A branch";
|
|
408
|
-
const authorLabel = author ? ` by \`${author}\`` : "";
|
|
409
|
-
const notices = routeNotification({
|
|
410
|
-
type: "branch.landed",
|
|
411
|
-
scope: mainScope, ...audienceWithTaskOwnerRole("branch.landed", mainScope, landedTask?.taskId), // #2862
|
|
412
|
-
ttlMs: INFORMATIONAL_TTL_MS,
|
|
413
|
-
message: {
|
|
414
|
-
subject: `Merged to ${base}`,
|
|
415
|
-
body: `🔀 ${branchLabel}${authorLabel} merged to \`${base}\`${shaLabel}${subjectLabel}.${publishLabel}`,
|
|
416
|
-
payload: { ...payload, notification: "landed-success" },
|
|
417
|
-
idempotencyKey,
|
|
418
|
-
replyExpected: false,
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
if (landedTask && input.mergedSha) {
|
|
422
|
-
for (const notice of notices) stampLifecycleSignal(notice.to, taskSignalKey(landedTask.taskId, "landed-on-main", input.mergedSha));
|
|
423
|
-
}
|
|
354
|
+
trackLandLifecycleDrive(announceVerifiedLand(input, landedTask, idempotencyKey));
|
|
424
355
|
}
|
|
425
356
|
|
|
426
357
|
/**
|
|
@@ -682,7 +613,7 @@ const DRAINED_LAND_STRAND_BOOKKEEPING = {
|
|
|
682
613
|
* (record the receipt, announce a real land, drain the steward/strand bookkeeping so no
|
|
683
614
|
* watchdog re-wakes or re-pages a branch with nothing left to land).
|
|
684
615
|
*/
|
|
685
|
-
export async function reconcileLandedWorkspace(ws: WorkspaceRecord, preview: WorkspaceMergePreview): Promise<LandedWorkspaceReconciliation> {
|
|
616
|
+
export async function reconcileLandedWorkspace(ws: WorkspaceRecord, preview: WorkspaceMergePreview, landVerifiedSha?: string): Promise<LandedWorkspaceReconciliation> {
|
|
686
617
|
const observedSha = typeof preview.prMergeSha === "string"
|
|
687
618
|
? preview.prMergeSha
|
|
688
619
|
: typeof preview.landedSha === "string"
|
|
@@ -753,7 +684,7 @@ export async function reconcileLandedWorkspace(ws: WorkspaceRecord, preview: Wor
|
|
|
753
684
|
metadata: { source: "server", maintenanceJobId: "workspace-conflict-scan", workspaceId: ws.id, fromStatus: ws.status, toStatus: nextStatus, ...(ownerAlive ? { ownerAlive: true, keptLive: true } : {}), ...(sha ? { landedSha: sha } : {}), ...(landedCommitCount === 0 ? { landedCommitCount: 0 } : {}) },
|
|
754
685
|
});
|
|
755
686
|
try {
|
|
756
|
-
notifyBranchLanded({ workspace: ws, mergedSha: sha, landedTipSha: sha, commitShas: sha ? [sha] : [], pushed: true, prLand: preview.prMerged === true, ...(landedCommitCount === 0 ? { landedCommitCount } : {}) });
|
|
687
|
+
notifyBranchLanded({ workspace: ws, mergedSha: sha, landVerifiedSha, landedTipSha: sha, commitShas: sha ? [sha] : [], pushed: true, prLand: preview.prMerged === true, ...(landedCommitCount === 0 ? { landedCommitCount } : {}) });
|
|
757
688
|
} catch {
|
|
758
689
|
// Notification is best-effort; the status change + activity event still stand.
|
|
759
690
|
}
|
package/src/bus.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Server, ServerWebSocket } from "bun";
|
|
2
|
-
import { ValidationError,
|
|
2
|
+
import { ValidationError, applyAgentLifecycleUpdateAsync, getAgent, getDb, orphanTasksForAgentAsync, recordHeartbeatAsync, validateAgentSession } from "./db";
|
|
3
3
|
import { getOutboxCursor, hasReplayGap, replayEventsSlice, type BusEvent } from "./bus-outbox";
|
|
4
4
|
import { emitAgentStatus } from "./agent-status-events";
|
|
5
5
|
import { AgentStatusSubscribers, matchesSubscription, wantsAgentStatus } from "./transport-subscriptions";
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
type BusFrame,
|
|
35
35
|
type RegisterFrame,
|
|
36
36
|
} from "@humwick/sdk/protocol";
|
|
37
|
-
import { errMessage, isRecord } from "@humwick/sdk";
|
|
37
|
+
import { errMessage, fireAndForget, isRecord } from "@humwick/sdk";
|
|
38
38
|
import { getComponentAuth, isComponentAuthorizedFor, isAuthorized, isOriginAllowed, unauthorized } from "./security";
|
|
39
39
|
import type { AgentCard, ComponentToken, ContextState, Message, ProviderCapabilities, QuotaState, RegisterAgentInput, Task } from "./types";
|
|
40
40
|
|
|
@@ -138,8 +138,11 @@ export function busHandleClose(ws: BusWebSocket): void {
|
|
|
138
138
|
// on the establishing socket id, so a foreign socket's close cannot evict the owner's binding and a stale
|
|
139
139
|
// close cannot evict a newer register's binding).
|
|
140
140
|
if (!conn.transportReconnect) {
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
const agentId = conn.agentId;
|
|
142
|
+
void fireAndForget(`[bus] mark ${agentId} stale on close`, async () => {
|
|
143
|
+
await applyAgentLifecycleUpdateAsync(agentId, { status: "stale" }, { instanceId: conn.instanceId, epoch: conn.epoch });
|
|
144
|
+
queueAgentStatusEvent(agentId);
|
|
145
|
+
});
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
}
|
|
@@ -162,7 +165,7 @@ export async function expireStaleBusAgents(graceMs = busStaleGraceMs(), lagGrace
|
|
|
162
165
|
// but do NOT revoke the token — a reconnect accepts the EXISTING token and re-marks
|
|
163
166
|
// live. The #484 restart-strand revoke now fires on instance takeover in registerAgent
|
|
164
167
|
// (a fresh instanceId → epoch bump), never on a heartbeat lapse.
|
|
165
|
-
|
|
168
|
+
await applyAgentLifecycleUpdateAsync(row.id, { status: "offline" });
|
|
166
169
|
await emitAgentStatus(row.id);
|
|
167
170
|
const tasks = await orphanTasksForAgentAsync(row.id);
|
|
168
171
|
for (const task of tasks) {
|
|
@@ -187,7 +190,7 @@ async function handleFrame(ws: BusWebSocket, frame: ReturnType<typeof validateCl
|
|
|
187
190
|
await handleRegister(ws, frame);
|
|
188
191
|
return;
|
|
189
192
|
case "heartbeat":
|
|
190
|
-
await withRegistered(ws, frame, (conn) => {
|
|
193
|
+
await withRegistered(ws, frame, async (conn) => {
|
|
191
194
|
if (conn.agentId) {
|
|
192
195
|
// #633 — a runner whose provider terminally exited stays alive and keeps sending idle
|
|
193
196
|
// keepalives (semanticStatus is never "offline"). Those must NOT resurrect an agent the
|
|
@@ -199,11 +202,10 @@ async function handleFrame(ws: BusWebSocket, frame: ReturnType<typeof validateCl
|
|
|
199
202
|
if (!heldOffline) {
|
|
200
203
|
const wasUnavailable = current ? isAgentUnavailable(current) : false;
|
|
201
204
|
const runtime = runtimeFromMeta(frame.payload.meta);
|
|
202
|
-
// #1468 — coalesce the steady-state liveness write;
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
recordHeartbeat(conn.agentId, { guard: { instanceId: conn.instanceId, epoch: conn.epoch }, status: frame.payload.status, runtime });
|
|
205
|
+
// #1468 — coalesce the steady-state liveness write; only a status change / resurrection
|
|
206
|
+
// writes (last_seen + runtime), on the writer thread (#1870 S7). This replaces the old
|
|
207
|
+
// heartbeat()+setStatus() pair that rewrote the row twice per beat even when nothing changed.
|
|
208
|
+
await recordHeartbeatAsync(conn.agentId, { guard: { instanceId: conn.instanceId, epoch: conn.epoch }, status: frame.payload.status, runtime });
|
|
207
209
|
queueAgentStatusEvent(conn.agentId);
|
|
208
210
|
// #416 — flush queued messages if the agent transitioned from unavailable to available.
|
|
209
211
|
if (wasUnavailable) {
|
|
@@ -225,19 +227,16 @@ async function handleFrame(ws: BusWebSocket, frame: ReturnType<typeof validateCl
|
|
|
225
227
|
await withRegistered(ws, frame, async (conn) => {
|
|
226
228
|
if (!conn.agentId) return sendError(ws, frame.id, "AGENT_REQUIRED", "status frames require an agent registration");
|
|
227
229
|
const guard = { instanceId: conn.instanceId, epoch: conn.epoch };
|
|
228
|
-
// #
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
// thread hop mid-frame, so routing these writes through their async facades would not move
|
|
232
|
-
// them off-loop — it would only add a round-trip.
|
|
233
|
-
// #2111 — but the three writes ARE now one. This used to be getAgent(before) + setStatus
|
|
230
|
+
// #1870 S7 — the per-status-frame write runs on the writer thread; this socket's FIFO frame
|
|
231
|
+
// queue keeps its frames ordered across the hop, and the lifecycle ordinal fences it against
|
|
232
|
+
// every other lifecycle writer. #2111 — the three writes are one. This used to be getAgent(before) + setStatus
|
|
234
233
|
// (guard read + before read + UPDATE in its own BEGIN IMMEDIATE) + markReady (guard read +
|
|
235
234
|
// UPDATE) + mergeAgentMeta (two reads + UPDATE) + getAgent(after): ~7 row reads and 3 UPDATEs
|
|
236
235
|
// of one row per frame. `applyAgentLifecycleUpdate` does one guard read, one UPDATE and hands
|
|
237
236
|
// back the before/after cards the audit calls below need. The flap ledger is different: it is
|
|
238
237
|
// a cold durable history and must stay ordered with its status edge, so await it within this
|
|
239
238
|
// socket's existing FIFO frame queue.
|
|
240
|
-
const applied =
|
|
239
|
+
const applied = await applyAgentLifecycleUpdateAsync(
|
|
241
240
|
conn.agentId,
|
|
242
241
|
{ status: frame.payload.agentStatus, ready: frame.payload.ready, meta: frame.payload.meta },
|
|
243
242
|
guard,
|
|
@@ -378,7 +377,7 @@ async function handleRegister(ws: BusWebSocket, frame: RegisterFrame): Promise<v
|
|
|
378
377
|
machine: payload.machine,
|
|
379
378
|
capabilities: payload.capabilities,
|
|
380
379
|
status: runnerManaged ? "idle" : "online",
|
|
381
|
-
ready:
|
|
380
|
+
ready: !runnerManaged,
|
|
382
381
|
instanceId: payload.instanceId,
|
|
383
382
|
...(providerCapabilities ? { providerCapabilities } : {}),
|
|
384
383
|
...(context ? { context } : {}),
|
|
@@ -400,7 +399,7 @@ async function handleRegister(ws: BusWebSocket, frame: RegisterFrame): Promise<v
|
|
|
400
399
|
// newly registered agent stale. Fence the install on liveness and clean up the provisional
|
|
401
400
|
// registration with the same instance/epoch guard used by an established connection's close.
|
|
402
401
|
if (!liveBusSocketIds.has(ws.data.id)) {
|
|
403
|
-
if (payload.agentId &&
|
|
402
|
+
if (payload.agentId && (await applyAgentLifecycleUpdateAsync(payload.agentId, { status: "stale" }, { instanceId: payload.instanceId, epoch })).changed) {
|
|
404
403
|
queueAgentStatusEvent(payload.agentId);
|
|
405
404
|
}
|
|
406
405
|
return;
|
package/src/cd-deploy-notify.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { getDeployEventForCommitAsync, deployChangesForCommitsAsync } from "./db/deploy-events-async";
|
|
3
|
+
import type { DeployChanges } from "./db/deploy-events";
|
|
4
|
+
import { runGit } from "./services/git-process";
|
|
1
5
|
import { VERSION } from "./config";
|
|
2
6
|
import { reportedGitSha } from "./git-sha";
|
|
3
7
|
import { routeNotification } from "./notification-router";
|
|
@@ -17,6 +21,7 @@ registerDefaultAudienceRules("cd.deploy_live", [audiencePredicates.persistentCoo
|
|
|
17
21
|
export interface CdDeployLiveInput {
|
|
18
22
|
/** Defaults to this process's package version. */
|
|
19
23
|
version?: string;
|
|
24
|
+
repoRoot?: string;
|
|
20
25
|
/** Defaults to the running process's attested commit sha (undefined on an npm install). */
|
|
21
26
|
gitSha?: string;
|
|
22
27
|
}
|
|
@@ -32,7 +37,7 @@ export interface CdDeployLiveInput {
|
|
|
32
37
|
* there is no stable identity to key the one-per-deploy dedup on, and this notice is specifically
|
|
33
38
|
* about the CD-deploy restart glitch that only that deployment model produces.
|
|
34
39
|
*/
|
|
35
|
-
export function notifyCdDeployLive(input: CdDeployLiveInput = {}): void {
|
|
40
|
+
export async function notifyCdDeployLive(input: CdDeployLiveInput = {}): Promise<void> {
|
|
36
41
|
const version = input.version ?? VERSION;
|
|
37
42
|
const gitSha = input.gitSha ?? reportedGitSha();
|
|
38
43
|
if (!gitSha) {
|
|
@@ -43,6 +48,7 @@ export function notifyCdDeployLive(input: CdDeployLiveInput = {}): void {
|
|
|
43
48
|
return;
|
|
44
49
|
}
|
|
45
50
|
const shortSha = gitSha.slice(0, 12);
|
|
51
|
+
const contents = await deployNoticeContents(gitSha, input.repoRoot ?? resolve(import.meta.dir, ".."));
|
|
46
52
|
|
|
47
53
|
routeNotification({
|
|
48
54
|
type: "cd.deploy_live",
|
|
@@ -50,10 +56,32 @@ export function notifyCdDeployLive(input: CdDeployLiveInput = {}): void {
|
|
|
50
56
|
ttlMs: null,
|
|
51
57
|
message: {
|
|
52
58
|
subject: `Humwick ${version} is now running`,
|
|
53
|
-
body: `Humwick v${version} (\`${shortSha}\`) is now running after a restart.
|
|
59
|
+
body: `Humwick v${version} (\`${shortSha}\`) is now running after a restart. Interrupted tool calls were the deploy, not an incident; retry them.${contents}`,
|
|
54
60
|
payload: { kind: "cd.deploy-live", version, gitSha },
|
|
55
61
|
idempotencyKey: `cd-deploy-live:${gitSha}`,
|
|
56
62
|
replyExpected: false,
|
|
57
63
|
},
|
|
58
64
|
});
|
|
59
65
|
}
|
|
66
|
+
|
|
67
|
+
async function deployNoticeContents(gitSha: string, repoRoot: string): Promise<string> {
|
|
68
|
+
const event = await getDeployEventForCommitAsync(gitSha);
|
|
69
|
+
if (!event) return "";
|
|
70
|
+
let changes: DeployChanges | undefined = event.changes;
|
|
71
|
+
if (changes?.state !== "ready") {
|
|
72
|
+
if (!event.previousSha) return "\nContents unavailable: no previous deployed SHA.";
|
|
73
|
+
const result = await runGit(["-C", repoRoot, "log", "--format=%H%x00%s", `${event.previousSha}..${gitSha}`, "--"], 5000, 1024 * 1024);
|
|
74
|
+
if (result.unavailable || result.exitCode !== 0) return "\nContents unavailable: deploy range could not be read.";
|
|
75
|
+
const commits = result.stdout.trim().split("\n").filter(Boolean).map(line => {
|
|
76
|
+
const [sha, ...subject] = line.split("\x00");
|
|
77
|
+
return { sha: sha!, subject: subject.join("\x00") };
|
|
78
|
+
});
|
|
79
|
+
changes = { state: "ready", ...await deployChangesForCommitsAsync(commits) };
|
|
80
|
+
}
|
|
81
|
+
const lines = changes.tasks.flatMap(task => [
|
|
82
|
+
`T${task.id} ${task.label.replace(/\s+/g, " ")}${task.issues.length ? ` — ${task.issues.map(issue => `${issue.repo}#${issue.externalKey} (${issue.relation ?? "references"})`).join(", ")}` : ""}`,
|
|
83
|
+
...(task.onDeploy ? [` onDeploy: ${task.onDeploy.replace(/\s+/g, " ")}`] : []),
|
|
84
|
+
]);
|
|
85
|
+
lines.push(`${changes.unmatchedCommits.length} non-Task commit(s).`);
|
|
86
|
+
return `\n${lines.join("\n")}`;
|
|
87
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { resolveCdFleetAllowlist } from "../scripts/cd-config.ts";
|
|
2
2
|
import { isCdFleetHostAllowlisted } from "../scripts/cd-fleet-membership.ts";
|
|
3
|
-
import { getConfig
|
|
4
|
-
import {
|
|
3
|
+
import { getConfig } from "./config-store.ts";
|
|
4
|
+
import { setConfigAsync } from "./config-store-write.ts";
|
|
5
|
+
import { createActivityEventAsync } from "./db/activity-async.ts";
|
|
5
6
|
import { computeFleetConvergenceAsync, type FleetConvergence } from "./fleet-convergence.ts";
|
|
6
|
-
import {
|
|
7
|
+
import { raiseOperatorAlarmAsync } from "./operator-alarm.ts";
|
|
7
8
|
import { listOrchestratorsAsync } from "./db/orchestrators-async.ts";
|
|
8
9
|
|
|
9
10
|
/** Server-side: a durable alarm per lag episode, and a recovery record when it clears. */
|
|
@@ -17,7 +18,7 @@ export async function notifyCdHostLag(convergence?: FleetConvergence, allowlist
|
|
|
17
18
|
// Keep the episode's identity while targets advance, until this Host catches up.
|
|
18
19
|
const since = previous[host.id] ?? host.lag.since;
|
|
19
20
|
current[host.id] = since;
|
|
20
|
-
|
|
21
|
+
await raiseOperatorAlarmAsync({ clientId: `cd-host-lag:${host.id}:${since}`, source: "cd-host-lag", view: "deploys",
|
|
21
22
|
subject: `CD deploy lag on ${host.id}`, dedupeEvent: true,
|
|
22
23
|
detail: `${host.id} is ${Math.floor(host.lag.ageMs / 60_000)} minutes behind ${status.targetSha.slice(0, 12)}: ${host.deferred?.reason ?? host.reason}`,
|
|
23
24
|
metadata: { orchestratorId: host.id, since, targetSha: status.targetSha, deferred: host.deferred } });
|
|
@@ -29,8 +30,8 @@ export async function notifyCdHostLag(convergence?: FleetConvergence, allowlist
|
|
|
29
30
|
if (!host || !isCdFleetHostAllowlisted(host, allowlist)) { delete current[id]; continue; }
|
|
30
31
|
if (!status.targetSha || host.gitSha !== status.targetSha) { current[id] = since; continue; }
|
|
31
32
|
delete current[id];
|
|
32
|
-
|
|
33
|
+
await createActivityEventAsync({ clientId: `cd-host-lag-resolved:${id}:${since}`, kind: "state", view: "deploys",
|
|
33
34
|
title: `CD deploy lag cleared on ${id}`, metadata: { source: "cd-host-lag", orchestratorId: id, since, resolved: true } });
|
|
34
35
|
}
|
|
35
|
-
if (JSON.stringify(current) !== JSON.stringify(previous))
|
|
36
|
+
if (JSON.stringify(current) !== JSON.stringify(previous)) await setConfigAsync("cd-host-lag", "alarms", current);
|
|
36
37
|
}
|
package/src/ci/incidents.ts
CHANGED
|
@@ -169,7 +169,7 @@ export function ingestCiObservation(observation: CiObservation, now: number = Da
|
|
|
169
169
|
|
|
170
170
|
/** Gateway-routed form for async-capable polling jobs. The complete observation transition is one
|
|
171
171
|
* writer operation, so no gateway await exposes a stale watch state to a later incident decision. */
|
|
172
|
-
export async function ingestCiObservationAsync(observation: CiObservation): Promise<CiIncidentIngestResult> {
|
|
172
|
+
export async function ingestCiObservationAsync(observation: CiObservation, now?: number): Promise<CiIncidentIngestResult> {
|
|
173
173
|
const transition = transitionInput(observation);
|
|
174
|
-
return formatTransitionResult(await ingestCiObservationTransitionAsync(transition.input), transition.watchKey, transition.fingerprint);
|
|
174
|
+
return formatTransitionResult(await ingestCiObservationTransitionAsync(transition.input, now), transition.watchKey, transition.fingerprint);
|
|
175
175
|
}
|
package/src/ci/monitor.ts
CHANGED
|
@@ -12,8 +12,8 @@ import { isPathWithinBase } from "../utils";
|
|
|
12
12
|
import { computeCiWatchKey, isFailingJob } from "./incidents.ts";
|
|
13
13
|
import { ghApi, type GitHubApi } from "../issue-tracker/github.ts";
|
|
14
14
|
import { resolveCoordinatorAgentId } from "../steward-identity.ts";
|
|
15
|
-
import { notifyCiRootMissing,
|
|
16
|
-
import { notifyCiTransition,
|
|
15
|
+
import { notifyCiRootMissing, type CiRootMissing } from "./root-missing.ts";
|
|
16
|
+
import { notifyCiTransition, type CiTransitionNotifyResult } from "./transition-notify.ts";
|
|
17
17
|
import { workflowNotTriggered } from "./not-triggered.ts";
|
|
18
18
|
import type { CiJobRef, CiObservation, CiObservationStatus, CiRunRef } from "./provider.ts";
|
|
19
19
|
|
|
@@ -343,7 +343,7 @@ export interface CiMonitorDeps {
|
|
|
343
343
|
rootsForProject: (projectId: string) => ProjectRoot[];
|
|
344
344
|
loadConfig: (root: ProjectRoot) => CiMonitorConfig | null | Promise<CiMonitorConfig | null>;
|
|
345
345
|
api: GitHubApi;
|
|
346
|
-
notify: typeof notifyCiTransition
|
|
346
|
+
notify: (...args: Parameters<typeof notifyCiTransition>) => CiTransitionNotifyResult | Promise<CiTransitionNotifyResult>;
|
|
347
347
|
resolveCoordinator: (rootPath: string) => string | undefined;
|
|
348
348
|
/** Commits workflows wait on: each watch of their root reports its verdict on them too. */
|
|
349
349
|
shaWaits: () => Promise<CiShaWait[]>;
|
|
@@ -419,7 +419,6 @@ export async function runCiMonitor(input: Partial<CiMonitorDeps> = {}): Promise<
|
|
|
419
419
|
readinessMs: 0, historyMs: 0, historyApiCalls: 0,
|
|
420
420
|
transitions: { went_red: 0, back_green: 0, never_matched: 0, config_mismatch: 0, none: 0 },
|
|
421
421
|
};
|
|
422
|
-
const useAsync = deps.notify === notifyCiTransition && !deps.now;
|
|
423
422
|
// A repository counts as missing only if no root of it has the config, so a duplicate root cannot flap the notice.
|
|
424
423
|
const missingRoots = new Map<string, CiRootMissing>();
|
|
425
424
|
const presentRepositories = new Set<string>();
|
|
@@ -464,9 +463,7 @@ export async function runCiMonitor(input: Partial<CiMonitorDeps> = {}): Promise<
|
|
|
464
463
|
const observation = await observeGitHubCiMonitorWatch(repository, watch, deps.api, runsPayload);
|
|
465
464
|
if (deps.signal?.aborted) break;
|
|
466
465
|
const notificationDeps = { resolveCoordinator: () => deps.resolveCoordinator(root.rootPath), ...(deps.now ? { now: deps.now() } : {}) };
|
|
467
|
-
const transition =
|
|
468
|
-
? await notifyCiTransitionAsync(observation, notificationDeps)
|
|
469
|
-
: deps.notify(observation, notificationDeps);
|
|
466
|
+
const transition = await deps.notify(observation, notificationDeps);
|
|
470
467
|
result.transitions[transition.transition] += 1;
|
|
471
468
|
// The branch-tip row keys the watch; the same key carries its verdict on each waited commit.
|
|
472
469
|
const watchKey = computeCiWatchKey(observation);
|
|
@@ -495,7 +492,7 @@ export async function runCiMonitor(input: Partial<CiMonitorDeps> = {}): Promise<
|
|
|
495
492
|
if (presentRepositories.has(key) || deps.signal?.aborted) continue;
|
|
496
493
|
const notifyDeps = { resolveCoordinator: () => deps.resolveCoordinator(missing.rootPath), ...(deps.now ? { now: deps.now() } : {}) };
|
|
497
494
|
try {
|
|
498
|
-
if (
|
|
495
|
+
if (await notifyCiRootMissing(missing, notifyDeps)) result.missingRoots += 1;
|
|
499
496
|
} catch (error) {
|
|
500
497
|
result.errors += 1;
|
|
501
498
|
console.warn(`ci monitor ${missing.repository} root-missing notice skipped: ${errMessage(error)}`);
|
package/src/ci/root-missing.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { markCiRootMissingNotified } from "../db/ci-incidents.ts";
|
|
2
1
|
import { markCiRootMissingNotifiedAsync } from "../db/ci-incidents-async.ts";
|
|
3
2
|
import { routeCiIncidentNotification } from "./notifications.ts";
|
|
4
3
|
import { resolveCoordinator, type CiTransitionNotifyDeps } from "./transition-notify.ts";
|
|
@@ -26,10 +25,6 @@ function notify(input: CiRootMissing, deps: Deps, fired: boolean): boolean {
|
|
|
26
25
|
return true;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
export function notifyCiRootMissing(input: CiRootMissing, deps: Deps = {}): boolean {
|
|
30
|
-
return notify(input, deps,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function notifyCiRootMissingAsync(input: CiRootMissing, deps: Deps = {}): Promise<boolean> {
|
|
34
|
-
return notify(input, deps, await markCiRootMissingNotifiedAsync(input.repository));
|
|
28
|
+
export async function notifyCiRootMissing(input: CiRootMissing, deps: Deps = {}): Promise<boolean> {
|
|
29
|
+
return notify(input, deps, await markCiRootMissingNotifiedAsync(input.repository, deps.now));
|
|
35
30
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { stewardFallbackTarget } from "../config.ts";
|
|
2
2
|
import { emitRelayEvent } from "../events.ts";
|
|
3
3
|
import type { WorkspaceHumanEscalationResult } from "../workspace-human-escalation.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { ingestCiObservationAsync, type CiIncidentIngestResult } from "./incidents.ts";
|
|
5
5
|
import {
|
|
6
6
|
routeCiIncidentNotification,
|
|
7
7
|
type CiFailedNotificationPayload,
|
|
@@ -41,7 +41,7 @@ import type { CiObservation, CiRunRef } from "./provider.ts";
|
|
|
41
41
|
export const CI_MAIN_WATCH_COORDINATOR = "cd-watcher";
|
|
42
42
|
|
|
43
43
|
export interface CiTransitionNotifyDeps {
|
|
44
|
-
ingest?: (observation: CiObservation, now?: number) => CiIncidentIngestResult
|
|
44
|
+
ingest?: (observation: CiObservation, now?: number) => CiIncidentIngestResult | Promise<CiIncidentIngestResult>;
|
|
45
45
|
route?: (input: Parameters<typeof routeCiIncidentNotification>[0]) => RouteCiIncidentNotificationResult;
|
|
46
46
|
/** Resolve the coordinator/owner agent the relay message addresses. Defaults to the configured
|
|
47
47
|
* steward fallback, then the system watch actor. */
|
|
@@ -65,16 +65,17 @@ export interface CiTransitionNotifyResult {
|
|
|
65
65
|
* Detect and (if a flip occurred) alert on a CI red/green transition for a single observed watch.
|
|
66
66
|
* Idempotent per transition — safe to call every poll tick.
|
|
67
67
|
*/
|
|
68
|
-
export function notifyCiTransition(
|
|
68
|
+
export async function notifyCiTransition(
|
|
69
69
|
observation: CiObservation | null | undefined,
|
|
70
70
|
deps: CiTransitionNotifyDeps = {},
|
|
71
|
-
): CiTransitionNotifyResult {
|
|
71
|
+
): Promise<CiTransitionNotifyResult> {
|
|
72
72
|
if (!observation) return { transition: "none", notified: false, messages: 0 };
|
|
73
|
-
const ingest = deps.ingest ??
|
|
73
|
+
const ingest = deps.ingest ?? ingestCiObservationAsync;
|
|
74
74
|
const route = deps.route ?? routeCiIncidentNotification;
|
|
75
75
|
const now = deps.now ?? Date.now();
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
// Notification emission follows the committed transition (one writer op, #1870), never a pre-write observation.
|
|
78
|
+
const result = await ingest(observation, deps.now);
|
|
78
79
|
emitCiStatus(result);
|
|
79
80
|
|
|
80
81
|
if (result.kind === "opened") {
|
|
@@ -155,39 +156,6 @@ export function notifyCiTransition(
|
|
|
155
156
|
return { transition: "none", ...(result.kind === "deduped" ? { incidentId: result.incident.id } : {}), notified: false, messages: 0 };
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
/** Gateway-routed companion for async maintenance polling. Notification emission follows the
|
|
159
|
-
* committed transition, never a pre-write observation. */
|
|
160
|
-
export async function notifyCiTransitionAsync(
|
|
161
|
-
observation: CiObservation | null | undefined,
|
|
162
|
-
deps: Omit<CiTransitionNotifyDeps, "ingest"> = {},
|
|
163
|
-
): Promise<CiTransitionNotifyResult> {
|
|
164
|
-
if (!observation) return { transition: "none", notified: false, messages: 0 };
|
|
165
|
-
const route = deps.route ?? routeCiIncidentNotification;
|
|
166
|
-
const now = deps.now ?? Date.now();
|
|
167
|
-
const result = await ingestCiObservationAsync(observation);
|
|
168
|
-
emitCiStatus(result);
|
|
169
|
-
if (result.kind === "opened") {
|
|
170
|
-
const coordinatorAgentId = resolveCoordinator(deps);
|
|
171
|
-
const routed = route({ type: "ci.failed", payload: { incidentId: result.incident.id, provider: observation.provider, repository: observation.repository, branch: observation.branch, workflow: observation.workflow, failingHeadSha: observation.headSha, coordinatorAgentId, observedAt: observation.observedAt, ...(observation.conclusion ? { conclusion: observation.conclusion } : {}), failingRunIds: observation.failingRuns.map((run) => run.id), failingJobNames: failingJobNames(observation) }, alertHuman: true, subject: redSubject(observation), body: redBody(observation, result.incident.id) });
|
|
172
|
-
return { transition: "went_red", incidentId: result.incident.id, notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
|
|
173
|
-
}
|
|
174
|
-
if (result.kind === "resolved") {
|
|
175
|
-
const coordinatorAgentId = resolveCoordinator(deps);
|
|
176
|
-
const routed = route({ type: "ci.recovered", payload: { incidentId: result.incident.id, provider: observation.provider, repository: observation.repository, branch: observation.branch, workflow: observation.workflow, failingHeadSha: result.incident.openedHeadSha, coordinatorAgentId, recoveredAt: now, greenHeadSha: observation.headSha, ...(result.incident.resolution ? { resolution: result.incident.resolution } : {}) }, alertHuman: true, subject: greenSubject(observation), body: greenBody(observation, result.incident.id, result.incident.openedHeadSha) });
|
|
177
|
-
return { transition: "back_green", incidentId: result.incident.id, notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
|
|
178
|
-
}
|
|
179
|
-
if (result.kind === "never_matched") {
|
|
180
|
-
const coordinatorAgentId = resolveCoordinator(deps);
|
|
181
|
-
const routed = route({ type: "ci.never_matched", payload: { provider: observation.provider, repository: observation.repository, branch: observation.branch, workflow: observation.workflow, coordinatorAgentId, firstSeenAt: result.firstSeenAt, observedAt: observation.observedAt }, alertHuman: true, subject: neverMatchedSubject(observation), body: neverMatchedBody(observation) });
|
|
182
|
-
return { transition: "never_matched", notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
|
|
183
|
-
}
|
|
184
|
-
if (result.kind === "config_mismatch") {
|
|
185
|
-
const routed = route(configMismatchRoute(observation, resolveCoordinator(deps), result.unmatchedJobs));
|
|
186
|
-
return { transition: "config_mismatch", notified: true, messages: routed.messages.length, ...(routed.humanFallback ? { humanFallback: routed.humanFallback } : {}) };
|
|
187
|
-
}
|
|
188
|
-
return { transition: "none", ...(result.kind === "deduped" ? { incidentId: result.incident.id } : {}), notified: false, messages: 0 };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
159
|
/** Live CI view (#3011): one `ci.status` event per watch status transition, never per poll. */
|
|
192
160
|
function emitCiStatus(result: CiIncidentIngestResult): void {
|
|
193
161
|
if (result.statusChange) emitRelayEvent({ type: "ci.status", source: "server", subject: result.watchKey, data: { ...result.statusChange } });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CdPause, CdPauseControl } from "../state-adoption/cd-cutover";
|
|
2
|
+
import { apiRequest, ApiRequestError } from "./_shared";
|
|
3
|
+
|
|
4
|
+
/** The audited maintenance pause API (#1645) shared by local and fleet cutovers (#3470). */
|
|
5
|
+
export const cdWatcherPauses: CdPauseControl = {
|
|
6
|
+
async pause(holder, reason) {
|
|
7
|
+
try {
|
|
8
|
+
return ((await apiRequest("POST", "/api/maintenance/jobs/cd-watcher/pause", { holder, reason, indefinite: true })) as { pause: CdPause }).pause;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (error instanceof ApiRequestError && error.status === 409) return (error.responseBody as { pause: CdPause }).pause;
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
async current() {
|
|
15
|
+
const { pauses } = (await apiRequest("GET", "/api/maintenance/pauses")) as { pauses: Array<CdPause & { jobId: string }> };
|
|
16
|
+
return pauses.find((pause) => pause.jobId === "cd-watcher") ?? null;
|
|
17
|
+
},
|
|
18
|
+
async resume() { await apiRequest("POST", "/api/maintenance/jobs/cd-watcher/resume"); },
|
|
19
|
+
};
|
package/src/cli/orchestrator.ts
CHANGED
|
@@ -118,7 +118,7 @@ export async function handleOrchestratorCommand(args: string[]): Promise<void> {
|
|
|
118
118
|
// Install the server package too so a real `humwick` bin lands in the
|
|
119
119
|
// runtime .bin dir (#1421): under bunx there is no global install, so without
|
|
120
120
|
// this the darwin shim below would point at a bin that does not exist.
|
|
121
|
-
const packageCommand = skipInstall ? [] : ["npm", "install", "--prefix", runtimePrefix,
|
|
121
|
+
const packageCommand = skipInstall ? [] : ["npm", "install", "--prefix", runtimePrefix, `@humwick/runner@${version}`, `@humwick/orchestrator@${version}`, `@humwick/server@${version}`, OPENCODE_HARNESS_PACKAGE];
|
|
122
122
|
// W1: on macOS, put `humwick` on the interactive PATH (shim + profile
|
|
123
123
|
// block). Gated to darwin — Linux global-npm installs already get the shim via
|
|
124
124
|
// the postinstall, and we avoid editing Linux user profiles unnecessarily.
|