@humwick/server 0.130.0 → 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 +6 -1
- 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-C7jQWbw2.js +2 -0
- 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 +24 -3
- 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 +10 -6
- package/src/commands-write.ts +8 -0
- 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 +13 -1
- package/src/db/activity.ts +1 -14
- package/src/db/agent-lifecycle-write-ops.ts +1 -0
- package/src/db/agent-meta.ts +2 -2
- package/src/db/agent-ownership-write-ops.ts +4 -0
- package/src/db/agent-search.ts +1 -15
- package/src/db/agents-async.ts +58 -7
- package/src/db/agents-write-ops.ts +28 -5
- package/src/db/agents.ts +81 -54
- package/src/db/artifacts-write-ops.ts +8 -3
- package/src/db/bus-outbox-write-ops.ts +1 -0
- package/src/db/channels-write-ops.ts +4 -0
- package/src/db/ci-incidents-async.ts +5 -5
- package/src/db/ci-incidents-write-ops.ts +2 -2
- package/src/db/claude-api-budget-ops.ts +5 -5
- package/src/db/claude-app-oauth.ts +4 -4
- package/src/db/codex-reset-consumes-write-ops.ts +3 -0
- 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/commands-write-ops.ts +21 -0
- package/src/db/config-store-write-ops.ts +9 -2
- package/src/db/continuation-archives-async.ts +5 -0
- package/src/db/continuation-archives-write-ops.ts +9 -0
- package/src/db/continuations.ts +2 -2
- package/src/db/db-writer-worker.ts +8 -1
- package/src/db/delivery-write-ops.ts +5 -0
- 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-write-ops.ts +14 -0
- package/src/db/hosts.ts +9 -6
- package/src/db/inbox-write-ops.ts +4 -0
- package/src/db/inbox.ts +0 -14
- package/src/db/index.ts +2 -0
- package/src/db/insights-write-ops.ts +1 -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/macos-privacy.ts +1 -1
- 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/message-reads-write-ops.ts +3 -0
- package/src/db/message-write-ops.ts +4 -0
- package/src/db/migrations.ts +7 -15
- package/src/db/orchestrators-async.ts +7 -3
- package/src/db/orchestrators-write-ops.ts +7 -1
- package/src/db/orchestrators.ts +21 -77
- package/src/db/pairs.ts +1 -13
- package/src/db/project-profile-instructions.ts +50 -0
- package/src/db/prompt-versions-ops.ts +1 -0
- package/src/db/prompt-versions.ts +9 -2
- package/src/db/provider-catalog-write-ops.ts +4 -4
- package/src/db/provider-config-history.ts +8 -3
- package/src/db/provider-flap-messages-write-ops.ts +1 -0
- package/src/db/provider-model-seed.ts +34 -0
- package/src/db/provider-quota-lease-write-ops.ts +1 -0
- package/src/db/provider-quota-write-ops.ts +2 -0
- package/src/db/provisioning-write-ops.ts +9 -8
- package/src/db/read-op-marshaling-fixtures.ts +5 -0
- package/src/db/read-ops.ts +4 -0
- package/src/db/routing-policy.ts +3 -4
- package/src/db/schema.ts +9 -3
- package/src/db/secrets-broker-write-ops.ts +5 -5
- package/src/db/secrets-vault-write.ts +7 -7
- package/src/db/services-write-ops.ts +3 -0
- package/src/db/spawn-delegation-state.ts +11 -4
- package/src/db/spawn-tree-cleanup.ts +3 -0
- 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/team-spawn-delegation.ts +1 -1
- package/src/db/teams-write-ops.ts +4 -4
- package/src/db/timer-cutover-write-ops.ts +7 -0
- package/src/db/timer-ingress-write-ops.ts +9 -0
- package/src/db/timer-ingress.ts +7 -1
- package/src/db/timer-operator-write-ops.ts +14 -0
- package/src/db/timer-transfer-write-ops.ts +4 -0
- package/src/db/timer-write-ops.ts +14 -0
- package/src/db/token-db-write-ops.ts +21 -0
- package/src/db/token-standing.ts +15 -11
- package/src/db/user-settings-write-ops.ts +1 -0
- package/src/db/vault-backup.ts +2 -2
- package/src/db/vault-expiry.ts +2 -2
- package/src/db/vault-health.ts +3 -3
- package/src/db/vault-token-ops.ts +8 -8
- package/src/db/web-tool-call-ops.ts +3 -3
- package/src/db/workflow-contact.ts +1 -1
- package/src/db/workflow-task-read-ops.ts +30 -0
- package/src/db/write-ops.ts +15 -1
- package/src/db/writer-fault-seam.ts +5 -2
- package/src/db/writer-gateway.ts +21 -12
- 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-host-tools.ts +1 -1
- 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/package-pack.ts +42 -39
- 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/host-resource-control.ts +4 -1
- 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 +3 -3
- 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 +4 -4
- package/src/routes/project-profile-instructions.ts +27 -0
- package/src/routes/project-trust.ts +2 -2
- package/src/routes/provider-config.ts +9 -8
- 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 +50 -9
- 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 +12 -7
- package/src/services/kernel-timers-ingress.ts +1 -1
- package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +4 -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 -47
- 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 -46
- 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/fleet-BL4UXS_k.js +0 -2
- package/public/assets/index-B7ZerHLY.css +0 -2
- package/public/assets/index-BK0WV6hF.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
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
// #3091 — workflows by default: a builder spawn for a Task that lands code starts the project's build-review
|
|
2
2
|
// workflow instead, and its run spawns the builder. One core behind relay_spawn_agent, relay_task_dispatch and
|
|
3
3
|
// POST /api/agents/spawn; `workflow: false` keeps the manual path. review, research and ops Tasks never start one.
|
|
4
|
+
// #3736 — a project spawn-policy rule matching the Task names the workflow instead, whatever the Task's type.
|
|
4
5
|
import { WORKFLOW_AUTO_START_NAME, workflowAutoStarts, type WorkflowRunAgent, type WorkflowRunView } from "@humwick/sdk";
|
|
5
|
-
import {
|
|
6
|
+
import { SPAWN_POLICY_FILE } from "@humwick/sdk/spawn-policy";
|
|
7
|
+
import { getAgent, getTaskDetailAsync, resolveProjectForCwd, ValidationError } from "../db";
|
|
6
8
|
import { getAgentProfile } from "../config-store";
|
|
7
9
|
import { McpAuthError, McpNotFoundError } from "../mcp-errors";
|
|
8
10
|
import { ServiceAuthError } from "./errors";
|
|
9
11
|
import { getWorkflowRun, launchWorkflowRun, prepareWorkflowRun, type PreparedWorkflowRun } from "./workflow-runs";
|
|
10
12
|
import type { WorkflowPins } from "./workflow-pins";
|
|
11
13
|
import type { AuthContext } from "./auth-context";
|
|
14
|
+
import { spawnerKind, spawnPolicyTask, spawnPolicyWorkflowFor } from "./spawn-policy";
|
|
12
15
|
|
|
13
16
|
/** A builder spawn as its transport received it: the Task, the opt-out, and every other field the caller set (wire names). */
|
|
14
17
|
export interface BuilderSpawn {
|
|
@@ -58,18 +61,27 @@ function refused(fields: Record<string, unknown>): string[] {
|
|
|
58
61
|
export async function workflowForBuilderSpawn(spawn: BuilderSpawn, ctx: AuthContext): Promise<BuilderSpawnOutcome> {
|
|
59
62
|
if (spawn.workflow === false || spawn.taskId === undefined) return { started: false };
|
|
60
63
|
const task = await getTaskDetailAsync(spawn.taskId);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
64
|
+
if (!task || task.kind !== "deliverable") return { started: false };
|
|
65
|
+
const type = task.dispatch?.type;
|
|
66
|
+
const { cwd, orchestratorId } = spawn.fields;
|
|
67
|
+
const caller = ctx.callerAgentId ? getAgent(ctx.callerAgentId) ?? undefined : undefined;
|
|
68
|
+
const checkout = typeof cwd === "string" && cwd ? { cwd, ...(typeof orchestratorId === "string" && orchestratorId ? { orchestratorId } : {}) } : undefined;
|
|
69
|
+
const policy = await spawnPolicyWorkflowFor(
|
|
70
|
+
{ projectId: task.projectId ?? (checkout ? resolveProjectForCwd(checkout.cwd)?.id : undefined), ...(checkout ? { checkout } : {}) },
|
|
71
|
+
{ spawner: spawnerKind(ctx, {}, caller), task: spawnPolicyTask(task)! },
|
|
72
|
+
);
|
|
73
|
+
if (!policy && !workflowAutoStarts(type)) return { started: false };
|
|
74
|
+
const workflow = policy?.workflow ?? WORKFLOW_AUTO_START_NAME;
|
|
75
|
+
const why = policy ? `T${task.id} matches ${SPAWN_POLICY_FILE} rule "${policy.rule}"` : `T${task.id} is a ${type} Task`;
|
|
76
|
+
const subject = `${why}, so this spawn starts workflow ${workflow}, whose run spawns the builder`;
|
|
77
|
+
const optOut = policy ? "the project's spawn policy runs this Task only through that workflow" : "pass workflow:false to spawn by hand";
|
|
65
78
|
if (!taskRepos(task).length) return { started: false, reason: `T${task.id} names no repo, so no workflow can run on it` };
|
|
66
79
|
const conflicts = refused(spawn.fields);
|
|
67
|
-
if (conflicts.length) throw new ValidationError(`${subject}; the run cannot honor ${conflicts.map((key) => `${key}=${JSON.stringify(spawn.fields[key])}`).join(", ")}. Drop ${conflicts.length > 1 ? "them" : "it"}
|
|
80
|
+
if (conflicts.length) throw new ValidationError(`${subject}; the run cannot honor ${conflicts.map((key) => `${key}=${JSON.stringify(spawn.fields[key])}`).join(", ")}. Drop ${conflicts.length > 1 ? "them" : "it"}${policy ? "" : `, or ${optOut}`}`);
|
|
68
81
|
try {
|
|
69
82
|
// Located as the spawn itself would be: its cwd and host, else the Task's repo root.
|
|
70
|
-
const { cwd, orchestratorId } = spawn.fields;
|
|
71
83
|
const prepared = await prepareWorkflowRun({
|
|
72
|
-
workflow
|
|
84
|
+
workflow, taskId: task.id, ...(typeof cwd === "string" && cwd ? { cwd } : {}), ...(typeof orchestratorId === "string" && orchestratorId ? { orchestratorId } : {}),
|
|
73
85
|
}, ctx);
|
|
74
86
|
const inputSteps = Object.entries(prepared.read.workflow.definition.steps).filter(([, step]) => step.task === "input");
|
|
75
87
|
const role = spawn.fields.role;
|
|
@@ -80,7 +92,7 @@ export async function workflowForBuilderSpawn(spawn: BuilderSpawn, ctx: AuthCont
|
|
|
80
92
|
const steer = BRIEF_FIELDS.map((key) => spawn.fields[key]).filter((value): value is string => typeof value === "string" && Boolean(value.trim())).join("\n\n");
|
|
81
93
|
// The call answers for the builder's spawn: one the run cannot dispatch fails it, as a spawn by hand would (#3160).
|
|
82
94
|
const started = await launchWorkflowRun(prepared, { ...(pins ? { pins } : {}), ...(steer ? { steer } : {}), refuseUndispatched: true }, ctx) as WorkflowRunView & { existing?: true; warnings?: string[] };
|
|
83
|
-
return { started: true, receipt: await receiptFor(prepared, started, set(spawn.fields).filter((key) => UNUSED_FIELDS.includes(key)), ctx) };
|
|
95
|
+
return { started: true, receipt: await receiptFor(prepared, started, set(spawn.fields).filter((key) => UNUSED_FIELDS.includes(key)), { why, policyRule: policy?.rule }, ctx) };
|
|
84
96
|
} catch (error) {
|
|
85
97
|
if (error instanceof ServiceAuthError) throw new McpAuthError(`${subject}: ${error.message}; ${optOut}`);
|
|
86
98
|
if (error instanceof ValidationError || error instanceof McpAuthError || error instanceof McpNotFoundError) error.message = `${subject}: ${error.message}; ${optOut}`;
|
|
@@ -99,7 +111,7 @@ function builderPin(fields: Record<string, unknown>): Record<string, string> {
|
|
|
99
111
|
}
|
|
100
112
|
|
|
101
113
|
/** The spawn's receipt: the run, and its builder as a spawn receipt names one (the newest agent of the build step). */
|
|
102
|
-
async function receiptFor(prepared: PreparedWorkflowRun, started: WorkflowRunView & { existing?: true; warnings?: string[] }, ignored: string[], ctx: AuthContext): Promise<Record<string, unknown>> {
|
|
114
|
+
async function receiptFor(prepared: PreparedWorkflowRun, started: WorkflowRunView & { existing?: true; warnings?: string[] }, ignored: string[], cause: { why: string; policyRule?: string }, ctx: AuthContext): Promise<Record<string, unknown>> {
|
|
103
115
|
const { task, read } = prepared;
|
|
104
116
|
const run = await getWorkflowRun(started.runId, ctx, { verbose: true }) as WorkflowRunView & { agents: WorkflowRunAgent[] };
|
|
105
117
|
const step = run.steps.find((entry) => read.workflow.definition.steps[entry.name]?.task === "input")!;
|
|
@@ -107,8 +119,8 @@ async function receiptFor(prepared: PreparedWorkflowRun, started: WorkflowRunVie
|
|
|
107
119
|
const profile = step.pin?.profile ?? read.workflow.roles.get(step.role)?.extends;
|
|
108
120
|
const label = `workflow ${run.workflow} (${run.source ?? "project"}) run ${run.runId}`;
|
|
109
121
|
const note = started.existing
|
|
110
|
-
? `T${task.id} already runs ${label}, which owns its builder: no builder was spawned. relay_workflow_get shows its agents; pass workflow:false to spawn beside it.`
|
|
111
|
-
:
|
|
122
|
+
? `T${task.id} already runs ${label}, which owns its builder: no builder was spawned. relay_workflow_get shows its agents${cause.policyRule ? "" : "; pass workflow:false to spawn beside it"}.`
|
|
123
|
+
: `${cause.why}, so Relay started ${label}; its run ${agent ? "spawned" : "is spawning"} the builder (role ${step.role} on profile ${profile}). You hear back on a step spawn failure, an escalation or the result.${cause.policyRule ? "" : " Pass workflow:false to spawn by hand."}`;
|
|
112
124
|
const warnings = [...(started.warnings ?? []), ...(ignored.length ? [`the run sets these for its own agents, so ${ignored.join(", ")} went unused`] : [])];
|
|
113
125
|
return {
|
|
114
126
|
ok: true,
|
|
@@ -116,7 +128,7 @@ async function receiptFor(prepared: PreparedWorkflowRun, started: WorkflowRunVie
|
|
|
116
128
|
taskId: task.id,
|
|
117
129
|
workflowRun: {
|
|
118
130
|
runId: run.runId, workflow: run.workflow, ...(run.source ? { source: run.source } : {}), sha: run.sha, status: run.status, step: run.step,
|
|
119
|
-
taskType: task.dispatch?.type, ...(started.existing ? { existing: true } : {}),
|
|
131
|
+
taskType: task.dispatch?.type, ...(cause.policyRule ? { spawnPolicyRule: cause.policyRule } : {}), ...(started.existing ? { existing: true } : {}),
|
|
120
132
|
builder: { step: step.name, role: step.role, ...(profile ? { profile } : {}), ...(step.pin ? { pin: step.pin } : {}) },
|
|
121
133
|
},
|
|
122
134
|
...(agent ? { spawnRequestId: agent.spawnRequestId, agentId: agent.agentId ?? null, registered: Boolean(agent.agentId), ...(agent.provider ? { provider: agent.provider } : {}) } : {}),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { correlatedCiIncidents } from "./plan-ci-
|
|
1
|
+
import { correlatedCiIncidents } from "./plan-ci-notice";
|
|
2
2
|
// #3045 H5 — the `ci-main` step after the land: CI on main for the exact landed commit. The CI monitor's
|
|
3
3
|
// existing tick observes every commit a run waits on (ci_sha_observations); this module only reads that
|
|
4
4
|
// evidence. Red alerts the coordinator once while the run keeps waiting; green on every watch ends the run
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { workflowLandVerification } from "./workflow-land-notice";
|
|
1
2
|
import type { WorkflowEffect, WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
|
|
2
3
|
import { notifySystemMessage } from "../notify";
|
|
3
4
|
import { workflowCoordinatorTarget } from "./workflow-coordinator";
|
|
@@ -29,10 +30,11 @@ async function resumeHosts(sup: Supervisor, orchestratorId?: string): Promise<vo
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export async function notifyDeferredWorkflowCleanup(run: WorkflowRunRecord, effect: WorkflowEffect): Promise<void> {
|
|
33
|
+
const proof = run.landed?.sha ? await workflowLandVerification(run, run.landed.sha) : undefined;
|
|
32
34
|
notifySystemMessage(await workflowCoordinatorTarget(run), {
|
|
33
35
|
subject: `Workflow ${run.id}: cleanup deferred`,
|
|
34
|
-
body: `Workflow ${run.id} landed as ${run.landed?.sha}. Shutdowns on offline hosts are recorded durably and will run when those hosts re-register. The run can finish with its CI result.`,
|
|
35
|
-
payload: { kind: "workflow.cleanup-deferred", runId: run.id, effectId: effect.id, deferred: effect.result?.deferred },
|
|
36
|
+
body: `Workflow ${run.id}: ${proof?.verified ? `landed as ${run.landed?.sha}. verified: true.` : proof?.line ?? "No land receipt."} Shutdowns on offline hosts are recorded durably and will run when those hosts re-register. The run can finish with its CI result.`,
|
|
37
|
+
payload: { ...(proof ? { verified: proof.verified } : {}), kind: "workflow.cleanup-deferred", runId: run.id, effectId: effect.id, deferred: effect.result?.deferred },
|
|
36
38
|
idempotencyKey: `workflow:${run.id}:cleanup-deferred`,
|
|
37
39
|
replyExpected: false,
|
|
38
40
|
maxAgeSeconds: null,
|
|
@@ -156,6 +156,8 @@ export async function drainEffects(sup: Supervisor): Promise<number> {
|
|
|
156
156
|
continue;
|
|
157
157
|
}
|
|
158
158
|
const run = await invoke<WorkflowRunRecord>(sup, "get", { runId: effect.runId });
|
|
159
|
+
// #3757 — a paused run spawns, messages, arms and reaps nothing until its unpause; only its pause notices go out.
|
|
160
|
+
if (run.pause && effect.kind !== "pause-notice") continue;
|
|
159
161
|
if (await settleEffect(sup, run, effect)) settled += 1;
|
|
160
162
|
} catch (error) {
|
|
161
163
|
console.warn(`workflow effect ${effect.id} could not be settled: ${errMessage(error)}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { planCiNotice } from "./plan-ci-
|
|
1
|
+
import { planCiNotice } from "./plan-ci-notice";
|
|
2
2
|
import { performWorkflowDecision, performWithdrawWorkflowDecision } from "./workflow-decisions";
|
|
3
3
|
import { getCommandAsync } from "../commands-write";
|
|
4
4
|
// #3045 — the src half of a workflow run: the kernel writes effect intents and waits; this module
|
|
@@ -23,9 +23,10 @@ import { spawnTaskDispatch } from "./task-dispatch";
|
|
|
23
23
|
import type { DispatchPin } from "../task-dispatcher";
|
|
24
24
|
import { createTaskEntityAsync } from "./task-entity";
|
|
25
25
|
import { workflowNotice } from "./workflow-notice";
|
|
26
|
+
import { workflowPauseNotice } from "./workflow-pause-text";
|
|
26
27
|
import { planQuietingRunNotice } from "./plan-wake-policy";
|
|
27
28
|
import { workflowOverdueLead } from "./workflow-overdue-notice";
|
|
28
|
-
import { workflowLandNotice } from "./workflow-land-notice";
|
|
29
|
+
import { workflowLandNotice, workflowLandVerification } from "./workflow-land-notice";
|
|
29
30
|
import { ciRedLead, ciResultLine } from "./workflow-ci-main";
|
|
30
31
|
import { stepTaskDispatch } from "./workflow-pins";
|
|
31
32
|
import { workflowDispatchResult } from "./workflow-dispatch-result";
|
|
@@ -112,7 +113,7 @@ async function dispatchRole(run: WorkflowRunRecord, effect: WorkflowEffect, agen
|
|
|
112
113
|
const spawnRequestId = spawnRequestIdFor(effect);
|
|
113
114
|
await call("prepareDispatch", { id: effect.id, taskId, spawnRequestId });
|
|
114
115
|
const spawn = await spawnTaskDispatch({
|
|
115
|
-
taskId, role, roleSha: run.sha, ...(run.callerAgentId ? { workflowCaller: { agentId: run.callerAgentId, maxLiveAgents: run.maxLiveAgents ?? 2, runId: run.rootRunId ?? run.id } } : {}), ...(run.snapshot?.[run.workflow]?.roles[role] ? { roleSnapshot: run.snapshot[run.workflow]!.roles[role] as unknown as import("../project-roles").ProjectRole } : {}), workflowRunId: run.id, pin: stepDispatchPin(pin, step), pinSources: pin?.from, createReview: false, ...(pin?.orchestratorId ? { orchestratorId: pin.orchestratorId } : {}),
|
|
116
|
+
taskId, role, roleSha: run.sha, ...(run.callerAgentId ? { workflowCaller: { agentId: run.callerAgentId, maxLiveAgents: run.maxLiveAgents ?? 2, runId: run.rootRunId ?? run.id } } : {}), ...(run.snapshot?.[run.workflow]?.roles[role] ? { roleSnapshot: run.snapshot[run.workflow]!.roles[role] as unknown as import("../project-roles").ProjectRole } : {}), workflowRunId: run.id, workflowNames: [...new Set([run.workflow, ...Object.keys(run.snapshot ?? {})])], pin: stepDispatchPin(pin, step), pinSources: pin?.from, createReview: false, ...(pin?.orchestratorId ? { orchestratorId: pin.orchestratorId } : {}),
|
|
116
117
|
taskRepoCwd: run.cwd, roleHost: run.orchestratorId, ...(pin?.profile ? { roleProfile: pin.profile } : {}),
|
|
117
118
|
spawnRequestId, waitForRegistrationMs: 0, verbose: false, ...(steer ? { steer } : {}), ...avoid,
|
|
118
119
|
}, WORKFLOW_CTX);
|
|
@@ -377,6 +378,10 @@ export async function performWorkflowEffect(run: WorkflowRunRecord, effect: Work
|
|
|
377
378
|
return { messageId: message.id };
|
|
378
379
|
}
|
|
379
380
|
case "landed": return { superseded: "the CI verdict owns the coordinator notice" };
|
|
381
|
+
case "pause-notice": {
|
|
382
|
+
const message = notifySystemMessage(await workflowCoordinatorTarget(run), { ...workflowPauseNotice(runLabel(run), run, effect), idempotencyKey: key, replyExpected: false });
|
|
383
|
+
return { messageId: message.id };
|
|
384
|
+
}
|
|
380
385
|
case "reap":
|
|
381
386
|
return reapAgents(run, effect, reapTargets(run, effect, agents));
|
|
382
387
|
case "dispatch": {
|
|
@@ -451,12 +456,13 @@ export async function performWorkflowEffect(run: WorkflowRunRecord, effect: Work
|
|
|
451
456
|
}
|
|
452
457
|
if (effect.payload.reason === "ci-red" || effect.payload.reason === "ci-late") {
|
|
453
458
|
const sha = str(effect.payload.landedSha) ?? run.landed?.sha ?? "";
|
|
459
|
+
const proof = await workflowLandVerification(run, sha);
|
|
454
460
|
const message = notifySystemMessage(await workflowCoordinatorTarget(run), {
|
|
455
|
-
subject: `${runLabel(run)} landed, CI on main ${effect.payload.reason === "ci-red" ? "red" : "late"}`,
|
|
456
|
-
body: workflowNotice(run, [`T${run.taskId} landed as ${sha}
|
|
461
|
+
subject: `${runLabel(run)} ${proof.verified ? "landed" : "land verification FAILED"}, CI on main ${effect.payload.reason === "ci-red" ? "red" : "late"}`,
|
|
462
|
+
body: workflowNotice(run, [proof.verified ? `T${run.taskId} landed as ${sha}. ${proof.line}` : proof.line, ...(effect.payload.reason === "ci-red" ? ciRedLead(runLabel(run), effect.payload) : [ciResultLine(effect.payload.ci as WorkflowCiVerdict)!])]),
|
|
457
463
|
idempotencyKey: `workflow:landed:${run.id}:${sha}:${effect.waitKey ?? effect.id}`,
|
|
458
464
|
replyExpected: false,
|
|
459
|
-
payload: { kind: "workflow.landed", runId: run.id, taskId: run.taskId, landedSha: sha, reason: effect.payload.reason, ci: effect.payload.ci },
|
|
465
|
+
payload: { verified: proof.verified, kind: "workflow.landed", runId: run.id, taskId: run.taskId, landedSha: sha, reason: effect.payload.reason, ci: effect.payload.ci },
|
|
460
466
|
});
|
|
461
467
|
return { messageId: message.id };
|
|
462
468
|
}
|
|
@@ -494,18 +500,19 @@ export async function performWorkflowEffect(run: WorkflowRunRecord, effect: Work
|
|
|
494
500
|
const receipt = await planCiNotice(run, effect.payload, true);
|
|
495
501
|
if (receipt) return receipt;
|
|
496
502
|
}
|
|
503
|
+
const proof = sha ? await workflowLandVerification(run, sha) : undefined;
|
|
497
504
|
const ciLine = ciResultLine(ci);
|
|
498
505
|
const quietFor = await planQuietingRunNotice(run.taskId, "result", ci);
|
|
499
|
-
if (quietFor) return { quiet: "plan-dispatched", planId: quietFor };
|
|
506
|
+
if (quietFor && proof?.verified !== false) return { quiet: "plan-dispatched", planId: quietFor };
|
|
500
507
|
const nothingToLand = effect.payload.nothingToLand === true || child?.output?.nothingToLand === true;
|
|
501
508
|
const rounds = typeof effect.payload.rounds === "number" ? effect.payload.rounds : child?.output?.rounds ?? child?.round;
|
|
502
|
-
const outcome = !sha ? "finished its script steps" : nothingToLand ? `had nothing to land: its reviewed head is already on main as ${sha}` : `landed as ${sha}`;
|
|
509
|
+
const outcome = !sha ? "finished its script steps" : !proof?.verified ? `has an unverified land receipt for ${sha}` : nothingToLand ? `had nothing to land: its reviewed head is already on main as ${sha}` : `landed as ${sha}`;
|
|
503
510
|
const message = notifySystemMessage(await workflowCoordinatorTarget(run), {
|
|
504
|
-
subject: `${runLabel(run)} done${nothingToLand ? ", nothing to land" : ""}${ci?.status === "fail" ? ", CI on main red" : ""}`,
|
|
505
|
-
body: workflowNotice(run, [`${runLabel(run)} is done: T${run.taskId} ${outcome}${rounds === undefined ? "" : ` after ${rounds} round(s)`}
|
|
511
|
+
subject: `${runLabel(run)} ${proof?.verified === false ? "land verification FAILED" : "done"}${nothingToLand ? ", nothing to land" : ""}${ci?.status === "fail" ? ", CI on main red" : ""}`,
|
|
512
|
+
body: workflowNotice(run, [`${runLabel(run)} is done: T${run.taskId} ${outcome}${rounds === undefined ? "" : ` after ${rounds} round(s)`}.${proof ? ` ${proof.line}` : ""}`, ciLine ?? (child || !run.rootRunId ? "CI on main: not configured." : "CI on main: unknown.")], children.items, !!children.cursor),
|
|
506
513
|
idempotencyKey: key,
|
|
507
514
|
replyExpected: false,
|
|
508
|
-
payload: { kind: "workflow.result", runId: run.id, taskId: run.taskId, landedSha: sha, ...(rounds === undefined ? {} : { rounds }), ...(nothingToLand ? { nothingToLand } : {}), ...(ci ? { ci } : {}) },
|
|
515
|
+
payload: { ...(proof ? { verified: proof.verified } : {}), kind: "workflow.result", runId: run.id, taskId: run.taskId, landedSha: sha, ...(rounds === undefined ? {} : { rounds }), ...(nothingToLand ? { nothingToLand } : {}), ...(ci ? { ci } : {}) },
|
|
509
516
|
});
|
|
510
517
|
return { messageId: message.id };
|
|
511
518
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isRecord } from "@humwick/sdk";
|
|
2
|
+
import { verifyPublishedLand } from "./landed-ancestry";
|
|
1
3
|
import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
|
|
2
4
|
import { landLaneSnapshot } from "../db/commands-land-wait";
|
|
3
5
|
import { formatRelativeTime } from "../mcp/presentation";
|
|
@@ -16,3 +18,11 @@ export async function workflowLandNotice(run: WorkflowRunRecord): Promise<string
|
|
|
16
18
|
const progress = workspace.landProgressAt == null ? "unknown" : formatRelativeTime(workspace.landProgressAt).relative;
|
|
17
19
|
return `Land workspace ${workspace.id}: landState ${state} (${age}); last reported progress: ${progress}; lane position ${position} among waiting lands; merging now: ${active.length ? active.join(", ") : lane ? "none" : "unknown"}; gate host: ${gateHost}.`;
|
|
18
20
|
}
|
|
21
|
+
|
|
22
|
+
export async function workflowLandVerification(run: WorkflowRunRecord, sha: string): Promise<{ verified: boolean; line: string }> {
|
|
23
|
+
const workspace = await deliveredWorkspace(run);
|
|
24
|
+
const hostProof = workspace?.metadata.mergeResult;
|
|
25
|
+
const verdict = isRecord(hostProof) && hostProof.landVerifiedSha === sha ? "confirmed" : await verifyPublishedLand(workspace?.repoRoot ?? run.cwd, sha, workspace?.baseRef);
|
|
26
|
+
return verdict === "confirmed" ? { verified: true, line: "verified: true" }
|
|
27
|
+
: { verified: false, line: `Land NOT verified: ${sha} is ${verdict === "not-ancestor" ? "absent from" : "unresolvable on"} fetched origin. Coordinator action required.` };
|
|
28
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// #3757 — what a paused run tells its coordinator: a notice on pause and unpause, and one reminder line per paused run
|
|
2
|
+
// of its team in each new context generation (the self-care section of the self-resume continuation).
|
|
3
|
+
import { isRecord, parseJson } from "@humwick/sdk";
|
|
4
|
+
import type { WorkflowEffect, WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
|
|
5
|
+
import { coordinatedTeamIds } from "../db/coordinator-seat";
|
|
6
|
+
import { taskOwnerRoleTeamId } from "../db/task-owner";
|
|
7
|
+
import { pausedWorkflowRuns } from "../db/workflow-task-read-ops";
|
|
8
|
+
import { workflowText } from "./workflow-notice";
|
|
9
|
+
|
|
10
|
+
const UNPAUSE = "relay_workflow_resume {runId, pause: false}";
|
|
11
|
+
type Pause = NonNullable<WorkflowRunRecord["pause"]>;
|
|
12
|
+
|
|
13
|
+
function pauseOf(value: unknown): Pause | undefined {
|
|
14
|
+
return isRecord(value) && typeof value.reason === "string" && typeof value.by === "string" && typeof value.at === "number"
|
|
15
|
+
? { reason: value.reason, by: value.by, at: value.at } : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function pausedBy(pause: Pause): string {
|
|
19
|
+
return `paused since ${new Date(pause.at).toISOString()} by ${pause.by}: ${workflowText(pause.reason, 240)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The coordinator notice a pause or an unpause sends. */
|
|
23
|
+
export function workflowPauseNotice(label: string, run: WorkflowRunRecord, effect: WorkflowEffect): { subject: string; body: string; payload: Record<string, unknown> } {
|
|
24
|
+
const pause = pauseOf(effect.payload) ?? { reason: "", by: "unknown", at: effect.createdAt };
|
|
25
|
+
const kind = effect.payload.action === "unpaused" ? "workflow.unpaused" : "workflow.paused";
|
|
26
|
+
const body = kind === "workflow.paused"
|
|
27
|
+
? `${label} is ${pausedBy(pause)}. A working step agent finishes; the run does not go past its next step, delivery, review or land, and its timers stand still. Continue it: ${UNPAUSE}.`
|
|
28
|
+
: `${label} was unpaused by ${String(effect.payload.unpausedBy)} after ${Math.round(Number(effect.payload.pausedMs) / 60_000)}m. It was ${pausedBy(pause)}. It goes on from step ${run.step}; its deadlines moved by the paused time.`;
|
|
29
|
+
return { subject: `${label} ${kind === "workflow.paused" ? "paused" : "unpaused"}`, body, payload: { kind, runId: run.id, taskId: run.taskId, ...pause, ...(kind === "workflow.unpaused" ? { unpausedBy: effect.payload.unpausedBy } : {}) } };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The paused runs of the teams this agent coordinates, and the ones it started; "" when none are paused. */
|
|
33
|
+
export function renderPausedRunsSection(agentId: string): string {
|
|
34
|
+
const teams = new Set(coordinatedTeamIds(agentId));
|
|
35
|
+
const lines = pausedWorkflowRuns().flatMap((row) => {
|
|
36
|
+
const teamId = taskOwnerRoleTeamId(row.owner) ?? row.teamId;
|
|
37
|
+
if (row.coordinator !== agentId && row.callerAgentId !== agentId && !(teamId && teams.has(teamId))) return [];
|
|
38
|
+
const pause = pauseOf(parseJson<unknown>(row.pause, undefined));
|
|
39
|
+
if (!pause) return [];
|
|
40
|
+
return [`- ${row.runId} on T${row.taskId}${row.taskTitle ? ` (${workflowText(row.taskTitle, 80)})` : ""}: ${pausedBy(pause)}`];
|
|
41
|
+
});
|
|
42
|
+
return lines.length ? [`Paused workflow runs of your team: they keep their agents and wait. Continue one with ${UNPAUSE}.`, ...lines].join("\n") : "";
|
|
43
|
+
}
|
|
@@ -8,7 +8,7 @@ import { workflowDecisionSignal } from "./workflow-decisions";
|
|
|
8
8
|
import { errMessage, isRecord, stringValue } from "@humwick/sdk";
|
|
9
9
|
|
|
10
10
|
import { waitTimeoutMs } from "@humwick/kernel/workflow-definition";
|
|
11
|
-
import { workflowEffectId, type WorkflowAgent, type WorkflowEffect, type WorkflowRunRecord, type WorkflowSignal } from "@humwick/kernel/workflow.pilot";
|
|
11
|
+
import { workflowEffectId, workflowRunClock, type WorkflowAgent, type WorkflowEffect, type WorkflowRunRecord, type WorkflowSignal } from "@humwick/kernel/workflow.pilot";
|
|
12
12
|
import { listActivityEventsAsync } from "../db";
|
|
13
13
|
|
|
14
14
|
import { getGateway } from "../db/gateway";
|
|
@@ -21,7 +21,7 @@ import { workflowCleanupDeferred, notifyDeferredWorkflowCleanup } from "./workfl
|
|
|
21
21
|
|
|
22
22
|
import { deliveredWorkspace, landedSignal, landStopSignal } from "./workflow-land";
|
|
23
23
|
|
|
24
|
-
import { invoke, SUPERVISOR, testOptions, type Supervisor } from "./workflow-supervisor-state";
|
|
24
|
+
import { invoke, SUPERVISOR, supervisorNow, testOptions, type Supervisor } from "./workflow-supervisor-state";
|
|
25
25
|
import { workflowWaitAgent } from "./workflow-agent-obligation";
|
|
26
26
|
import { landingProgress } from "../workspace-phase";
|
|
27
27
|
type Json = Record<string, unknown>;
|
|
@@ -49,7 +49,7 @@ async function alertStalled(sup: Supervisor, run: WorkflowRunRecord, wait: Await
|
|
|
49
49
|
/** A wait past its deadline alerts the coordinator once and keeps waiting, like a stopped land (#3045 H1). */
|
|
50
50
|
async function alertTimeout(sup: Supervisor, run: WorkflowRunRecord, wait: Awaiting, taskId: number | undefined): Promise<boolean> {
|
|
51
51
|
const timeoutMs = wait.deadline !== undefined ? wait.deadline - wait.since : waitTimeoutMs(run.definition, run.step);
|
|
52
|
-
const now = (
|
|
52
|
+
const now = workflowRunClock(run, supervisorNow());
|
|
53
53
|
if (wait.timedOutAt !== undefined || now < wait.since + timeoutMs) return false;
|
|
54
54
|
if (wait.kind === "landed") {
|
|
55
55
|
const workspace = await deliveredWorkspace(run);
|
|
@@ -65,13 +65,15 @@ export async function reconcileRun(sup: Supervisor, run: WorkflowRunRecord, land
|
|
|
65
65
|
const wait = run.awaiting;
|
|
66
66
|
if (!wait || (await invoke<{ present: boolean }>(sup, "hasSignal", { key: wait.key })).present) return false;
|
|
67
67
|
if (wait.kind === "for-each") return false;
|
|
68
|
+
// #3757 — a paused run's deadlines stand still: they are read at its pause.
|
|
69
|
+
const now = workflowRunClock(run, supervisorNow());
|
|
68
70
|
let payload: Json | undefined;
|
|
69
71
|
let kind: WorkflowSignal["kind"] = wait.kind;
|
|
70
72
|
const taskId = wait.taskId ?? await effectTaskId(sup, wait.effectId);
|
|
71
73
|
if (wait.kind === "decision") {
|
|
72
74
|
payload = workflowDecisionSignal(wait.effectId!);
|
|
73
75
|
if (!payload) {
|
|
74
|
-
if (wait.deadline === undefined ||
|
|
76
|
+
if (wait.deadline === undefined || now < wait.deadline) return false;
|
|
75
77
|
kind = "effect-failed";
|
|
76
78
|
payload = { effect: "decision", error: "DECISION_DEADLINE: operator decision reached its explicit deadline" };
|
|
77
79
|
}
|
|
@@ -80,7 +82,7 @@ export async function reconcileRun(sup: Supervisor, run: WorkflowRunRecord, land
|
|
|
80
82
|
if (!effect?.result?.childRunId) return false;
|
|
81
83
|
const child = await invoke<WorkflowRunRecord>(sup, "get", { runId: effect.result.childRunId });
|
|
82
84
|
if (child.status === "running") {
|
|
83
|
-
if (wait.deadline !== undefined &&
|
|
85
|
+
if (wait.deadline !== undefined && now >= wait.deadline) return (await invoke<{ created: boolean }>(sup, "parkChild", { runId: run.id, childRunId: child.id, reason: `CHILD_TIMEOUT: ${child.id} exceeded ${wait.deadline}` })).created;
|
|
84
86
|
return false;
|
|
85
87
|
}
|
|
86
88
|
if (child.status === "escalated") {
|
|
@@ -112,19 +114,19 @@ export async function reconcileRun(sup: Supervisor, run: WorkflowRunRecord, land
|
|
|
112
114
|
payload = taskId === undefined ? undefined : await deliverySignal(run, taskId, wait.afterEventId, wait.since);
|
|
113
115
|
if (!payload) {
|
|
114
116
|
const { effect } = await invoke<{ effect?: WorkflowEffect }>(sup, "getEffect", { id: wait.effectId ?? workflowEffectId(run.id, run.waitSeq, "dispatch") });
|
|
115
|
-
payload = await spawnFailureSignal(effect,
|
|
117
|
+
payload = await spawnFailureSignal(effect, now);
|
|
116
118
|
if (payload) kind = "effect-failed";
|
|
117
119
|
}
|
|
118
120
|
if (!payload) {
|
|
119
|
-
payload = await exitSignal(sup, run);
|
|
121
|
+
payload = await exitSignal(sup, run, now);
|
|
120
122
|
if (payload) kind = payload.effect === "dispatch" ? "effect-failed" : "agent-exited";
|
|
121
123
|
}
|
|
122
124
|
} else if (wait.kind === "ci") {
|
|
123
125
|
// A CI wait ends on its verdict, never on a timeout alert: past the deadline it reports CI red or "no CI".
|
|
124
|
-
const outcome = await ciMainOutcome(run, wait,
|
|
126
|
+
const outcome = await ciMainOutcome(run, wait, now, (await deliveredWorkspace(run))?.baseRef);
|
|
125
127
|
if (outcome.red && (await invoke<{ created: boolean }>(sup, "alert", { runId: run.id, waitKey: wait.key, ...outcome.red })).created) return true;
|
|
126
128
|
if (!outcome.verdict) return false;
|
|
127
|
-
if (outcome.verdict.status === "none" && wait.deadline !== undefined &&
|
|
129
|
+
if (outcome.verdict.status === "none" && wait.deadline !== undefined && now >= wait.deadline
|
|
128
130
|
&& (await invoke<{ created: boolean }>(sup, "alert", { runId: run.id, waitKey: wait.key, key: "ci-late", reason: "ci-late", detail: { ci: outcome.verdict, landedSha: wait.sha } })).created) return true;
|
|
129
131
|
payload = outcome.verdict;
|
|
130
132
|
} else {
|
|
@@ -153,10 +155,10 @@ export async function reconcileRun(sup: Supervisor, run: WorkflowRunRecord, land
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
/** #3166 — the wait's step agent exited without a delivery, has stayed gone past the grace, and is not live again. */
|
|
156
|
-
async function exitSignal(sup: Supervisor, run: WorkflowRunRecord): Promise<Json | undefined> {
|
|
158
|
+
async function exitSignal(sup: Supervisor, run: WorkflowRunRecord, now: number): Promise<Json | undefined> {
|
|
157
159
|
const { agent } = await invoke<{ agent?: WorkflowAgent }>(sup, "exitedWaitAgent", { runId: run.id });
|
|
158
160
|
if (agent?.spawnFailedAt && !agent.readyAt) return { effect: "dispatch", error: `spawn ${agent.spawnRequestId}: ${agent.spawnError ?? "spawn failed"}` };
|
|
159
|
-
if (!agent?.exitedAt ||
|
|
161
|
+
if (!agent?.exitedAt || now - agent.exitedAt < AGENT_EXIT_GRACE_MS) return undefined;
|
|
160
162
|
return agentExitSignal(agent);
|
|
161
163
|
}
|
|
162
164
|
|
|
@@ -40,6 +40,7 @@ export function runView(run: WorkflowRunRecord): WorkflowRunView {
|
|
|
40
40
|
...(run.awaiting ? { awaiting: awaitingView(run.awaiting) } : {}),
|
|
41
41
|
...(run.overdueAt !== undefined ? { overdueAt: run.overdueAt } : {}),
|
|
42
42
|
...(run.overdueNotifiedAt !== undefined ? { overdueNotifiedAt: run.overdueNotifiedAt } : {}),
|
|
43
|
+
...(run.pause ? { paused: run.pause } : {}),
|
|
43
44
|
...(run.reason ? { reason: run.reason } : {}),
|
|
44
45
|
...(run.resumes?.length ? { resumes: run.resumes } : {}),
|
|
45
46
|
...(run.output ? { output: run.output } : {}),
|
|
@@ -57,6 +58,7 @@ export function leanRunView(run: WorkflowRunRecord, agents: WorkflowRunAgent[]):
|
|
|
57
58
|
...(run.overdueAt !== undefined ? { overdueAt: run.overdueAt } : {}),
|
|
58
59
|
...(run.overdueNotifiedAt !== undefined ? { overdueNotifiedAt: run.overdueNotifiedAt } : {}),
|
|
59
60
|
...(run.awaiting ? { awaiting: awaitingView(run.awaiting) } : {}),
|
|
61
|
+
...(run.pause ? { paused: run.pause } : {}),
|
|
60
62
|
...(run.reason ? { reason: workflowText(run.reason) } : {}),
|
|
61
63
|
...(run.output ? { output: { ...run.output, ...(run.output.ci ? { ci: { status: run.output.ci.status, ...(run.output.ci.sha ? { sha: run.output.ci.sha } : {}) } } : {}) } } : {}),
|
|
62
64
|
agents: agents.map((agent) => ({
|
|
@@ -43,6 +43,7 @@ import { leanRunView, runView } from "./workflow-run-view";
|
|
|
43
43
|
import type { AuthContext } from "./auth-context";
|
|
44
44
|
|
|
45
45
|
import { invoke, live, runAgents, requestPump, serialized, ensureSupervisor, type Supervisor } from "./workflow-supervisor";
|
|
46
|
+
import { supervisorNow } from "./workflow-supervisor-state";
|
|
46
47
|
export { workflowAgentObligation } from "./workflow-agent-obligation";
|
|
47
48
|
import { reconcileRun } from "./workflow-reconcile";
|
|
48
49
|
import { holdWorkflowVerification } from "./workflow-verification";
|
|
@@ -198,12 +199,7 @@ export async function readWorkflowRunBudget(runId: string, ctx: AuthContext): Pr
|
|
|
198
199
|
|
|
199
200
|
/** #3352 — a coordinator amends a live run's cap; new spawns see it at their next admission. */
|
|
200
201
|
export async function amendWorkflowRunBudget(input: { runId: string; tokenCap: number }, ctx: AuthContext): Promise<Json> {
|
|
201
|
-
const sup = await
|
|
202
|
-
const run = await readRun(sup, input.runId);
|
|
203
|
-
const task = await getTaskDetailAsync(run.taskId);
|
|
204
|
-
if (!task || !(ctx.callerAgentId === run.callerAgentId || canControlTaskReviewHold(ctx, task))) {
|
|
205
|
-
throw new ServiceAuthError(`amending the budget of workflow run ${run.id} is coordinator-only — ${COORDINATOR_AUTHORITY_HINT}`);
|
|
206
|
-
}
|
|
202
|
+
const { sup, run } = await controlledRun(input.runId, ctx, "amending the budget of");
|
|
207
203
|
const amended = await serialized(sup, () => invoke<WorkflowRunRecord>(sup, "amendTokenBudget", { runId: run.id, tokenCap: input.tokenCap })).catch((error: unknown) => {
|
|
208
204
|
if (error instanceof OpError && error.code === "conflict") throw new ValidationError(error.message);
|
|
209
205
|
throw error;
|
|
@@ -270,13 +266,7 @@ export async function resumeWorkflowRun(input: { runId: string; activationId?: s
|
|
|
270
266
|
/** relay_workflow_cancel (#3166): end a running or escalated run cancelled. A running run ends at once, its step
|
|
271
267
|
* agents reaped per their `reap`; the Task, its review hold, any delivery and an armed land stay as they are. */
|
|
272
268
|
export async function cancelWorkflowRun(input: { runId: string; reason?: string }, ctx: AuthContext): Promise<Json> {
|
|
273
|
-
const sup = await
|
|
274
|
-
const run = await readRun(sup, input.runId);
|
|
275
|
-
const task = await getTaskDetailAsync(run.taskId);
|
|
276
|
-
if (!task || !(ctx.callerAgentId === run.callerAgentId || canControlTaskReviewHold(ctx, task))) {
|
|
277
|
-
throw new ServiceAuthError(`cancelling workflow run ${run.id} is coordinator-only — ${COORDINATOR_AUTHORITY_HINT}`);
|
|
278
|
-
}
|
|
279
|
-
const by = ctx.callerAgentId ?? ctx.actor.id;
|
|
269
|
+
const { sup, run, by } = await controlledRun(input.runId, ctx, "cancelling");
|
|
280
270
|
const reason = `cancelled by ${by}${input.reason ? `: ${input.reason}` : ""}`;
|
|
281
271
|
// Between passes, so the cancel lands before the next routing.
|
|
282
272
|
const { cancelled } = await serialized(sup, () => invoke<{ cancelled: boolean }>(sup, "cancel", { runId: run.id, reason, by })).catch((error: unknown) => {
|
|
@@ -287,6 +277,32 @@ export async function cancelWorkflowRun(input: { runId: string; reason?: string
|
|
|
287
277
|
return { ...runView(await readRun(sup, run.id)), cancelled };
|
|
288
278
|
}
|
|
289
279
|
|
|
280
|
+
/** The run a coordinator-only control acts on: its caller, the input Task's coordinators and admins may. */
|
|
281
|
+
async function controlledRun(runId: string, ctx: AuthContext, verb: string): Promise<{ sup: Supervisor; run: WorkflowRunRecord; by: string }> {
|
|
282
|
+
const sup = await ensureSupervisor();
|
|
283
|
+
const run = await readRun(sup, runId);
|
|
284
|
+
const task = await getTaskDetailAsync(run.taskId);
|
|
285
|
+
if (!task || !(ctx.callerAgentId === run.callerAgentId || canControlTaskReviewHold(ctx, task))) {
|
|
286
|
+
throw new ServiceAuthError(`${verb} workflow run ${run.id} is coordinator-only — ${COORDINATOR_AUTHORITY_HINT}`);
|
|
287
|
+
}
|
|
288
|
+
return { sup, run, by: ctx.callerAgentId ?? ctx.actor.id };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** #3757 — pause a running run at its next seam (reason required) or unpause it; its agents keep working meanwhile. */
|
|
292
|
+
export async function pauseWorkflowRun(input: { runId: string; pause: boolean; reason?: string }, ctx: AuthContext): Promise<Json> {
|
|
293
|
+
const reason = input.reason?.trim();
|
|
294
|
+
if (input.pause && !reason) throw new ValidationError("pausing a workflow run needs a reason (MCP: note): say why it waits and for what");
|
|
295
|
+
const { sup, run, by } = await controlledRun(input.runId, ctx, input.pause ? "pausing" : "unpausing");
|
|
296
|
+
// Between passes, so no pass routes or performs with a stale view of the pause.
|
|
297
|
+
await serialized(sup, () => invoke(sup, input.pause ? "pause" : "unpause", { runId: run.id, by, now: supervisorNow(), ...(input.pause ? { reason } : {}) }))
|
|
298
|
+
.catch((error: unknown) => {
|
|
299
|
+
if (error instanceof OpError && (error.code === "conflict" || error.code === "invalid_input")) throw new ValidationError(error.message);
|
|
300
|
+
throw error;
|
|
301
|
+
});
|
|
302
|
+
await requestPump(sup);
|
|
303
|
+
return runView(await readRun(sup, run.id));
|
|
304
|
+
}
|
|
305
|
+
|
|
290
306
|
/** relay_workflow_get: the run's visible record, after every pass already scheduled. */
|
|
291
307
|
export async function getWorkflowRun(runId: string, ctx: AuthContext, options: WorkflowRunReadOptions = {}): Promise<Json> {
|
|
292
308
|
const sup = await ensureSupervisor();
|
|
@@ -45,6 +45,11 @@ let stopped = Promise.resolve();
|
|
|
45
45
|
let changeTimer: Timer | undefined;
|
|
46
46
|
export let testOptions: SupervisorTestOptions = {};
|
|
47
47
|
|
|
48
|
+
/** The supervisor's clock: deadlines are read and a pause is stamped against it. */
|
|
49
|
+
export function supervisorNow(): number {
|
|
50
|
+
return (testOptions.now ?? Date.now)();
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
/** The supervisor bound to the current database handle (a test that swaps the handle rebuilds it). */
|
|
49
54
|
export function live(): Supervisor | undefined {
|
|
50
55
|
return current && current.generation === getDbGeneration() ? current : undefined;
|
|
@@ -25,9 +25,13 @@ function startSupervisor(): Supervisor {
|
|
|
25
25
|
persistence: createGatewayKernelPersistence(),
|
|
26
26
|
leaseTtlMs: testOptions.leaseTtlMs ?? LEASE_TTL_MS,
|
|
27
27
|
opCommitFaults: testOptions.opCommitFaults,
|
|
28
|
-
// A wait is satisfied by its signal row: one keyed read instead of the default event-log scan.
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// A wait is satisfied by its signal row: one keyed read instead of the default event-log scan. A paused run's
|
|
29
|
+
// signal waits for its unpause (#3757).
|
|
30
|
+
workflowWaitEvaluator: async (waitFor) => {
|
|
31
|
+
if (waitFor.kind !== "event" || waitFor.correlationId === undefined) return false;
|
|
32
|
+
const wait = await host.invoke(workflow, "hasSignal", SUPERVISOR, { key: waitFor.correlationId }) as { present: boolean; paused?: boolean };
|
|
33
|
+
return wait.present && !wait.paused;
|
|
34
|
+
},
|
|
31
35
|
});
|
|
32
36
|
host.register(workflow);
|
|
33
37
|
const sup: Supervisor = {
|
package/src/settings/registry.ts
CHANGED
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
SPAWN_PROVIDERS,
|
|
33
33
|
TEAM_IDLE_THRESHOLD_MINUTES_MAX,
|
|
34
34
|
TEAM_IDLE_THRESHOLD_MINUTES_MIN,
|
|
35
|
-
VALID_EFFORTS,
|
|
36
35
|
type JSONSchema,
|
|
37
36
|
type SettingDescriptor,
|
|
38
37
|
type SettingScope,
|
|
@@ -166,7 +165,7 @@ const STEWARD_SCHEMA: JSONSchema = {
|
|
|
166
165
|
enabled: { type: "boolean" },
|
|
167
166
|
provider: { type: "string", enum: [...SPAWN_PROVIDERS] },
|
|
168
167
|
model: { type: "string", maxLength: 120 },
|
|
169
|
-
effort: { type: "string",
|
|
168
|
+
effort: { type: "string", maxLength: 120 },
|
|
170
169
|
permissionMode: { type: "string", enum: [...APPROVAL_MODES] },
|
|
171
170
|
keepaliveSeconds: { type: "integer", minimum: 0, maximum: 2_592_000 },
|
|
172
171
|
},
|
package/src/spawn-command.ts
CHANGED
|
@@ -112,11 +112,12 @@ export function resolveSpawnModelParams(
|
|
|
112
112
|
// #2599 — the full record is server-computed once from the effective-catalog selection:
|
|
113
113
|
// qualified runtime name + both windows + the explicit threshold. Same positive-int gates
|
|
114
114
|
// as the legacy window (never 0/NaN/negative), via the shared builder.
|
|
115
|
-
const record = runnerSupportsModelRecord(opts.runnerProtocol)
|
|
115
|
+
const record = runnerSupportsModelRecord(opts.runnerProtocol ?? MODEL_RECORD_PROTOCOL_VERSION)
|
|
116
116
|
? resolvedModelRecordFromSelection({
|
|
117
117
|
alias: selection.modelAlias,
|
|
118
118
|
providerModel: selection.providerModel,
|
|
119
119
|
effort: selection.effort,
|
|
120
|
+
efforts: selection.model?.efforts,
|
|
120
121
|
qualifiedModel: qualifyServerModelName(provider, selection.providerModel, effectiveProviderCatalogList()),
|
|
121
122
|
limits: selection.model?.limits,
|
|
122
123
|
...(selection.model?.contextThreshold ? { contextThreshold: selection.model.contextThreshold } : {}),
|
|
@@ -135,7 +136,6 @@ export function resolveSpawnModelParams(
|
|
|
135
136
|
...(record ? { modelRecord: record } : {}),
|
|
136
137
|
};
|
|
137
138
|
} catch (error) {
|
|
138
|
-
if (opts.modelSource === "runtime") return { ...(model ? { model } : {}), ...(effort ? { effort } : {}) };
|
|
139
139
|
throw new ValidationError(errMessage(error));
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -157,11 +157,12 @@ export async function resolveSpawnModelParamsAsync(
|
|
|
157
157
|
);
|
|
158
158
|
const windowTokens = selection.model?.limits?.contextWindowTokens?.value;
|
|
159
159
|
// #2599 — same full-record twin as the sync path (see its comment).
|
|
160
|
-
const record = runnerSupportsModelRecord(opts.runnerProtocol)
|
|
160
|
+
const record = runnerSupportsModelRecord(opts.runnerProtocol ?? MODEL_RECORD_PROTOCOL_VERSION)
|
|
161
161
|
? resolvedModelRecordFromSelection({
|
|
162
162
|
alias: selection.modelAlias,
|
|
163
163
|
providerModel: selection.providerModel,
|
|
164
164
|
effort: selection.effort,
|
|
165
|
+
efforts: selection.model?.efforts,
|
|
165
166
|
qualifiedModel: qualifyServerModelName(provider, selection.providerModel, liveCatalog),
|
|
166
167
|
limits: selection.model?.limits,
|
|
167
168
|
...(selection.model?.contextThreshold ? { contextThreshold: selection.model.contextThreshold } : {}),
|
|
@@ -176,7 +177,6 @@ export async function resolveSpawnModelParamsAsync(
|
|
|
176
177
|
...(record ? { modelRecord: record } : {}),
|
|
177
178
|
};
|
|
178
179
|
} catch (error) {
|
|
179
|
-
if (opts.modelSource === "runtime") return { ...(model ? { model } : {}), ...(effort ? { effort } : {}) };
|
|
180
180
|
throw new ValidationError(errMessage(error));
|
|
181
181
|
}
|
|
182
182
|
}
|
package/src/spawn-models.ts
CHANGED
|
@@ -2,21 +2,19 @@ import { isOpenAiApiModel } from "@humwick/sdk";
|
|
|
2
2
|
import type { ProviderCatalogEntry } from "@humwick/sdk/provider-catalog";
|
|
3
3
|
import type { Orchestrator, SpawnProvider } from "./types";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
export function hostSupportsModel(host: Orchestrator, provider: SpawnProvider, model?: string
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
/** Hosts report local capabilities; model definitions and efforts belong to the hub catalog. */
|
|
6
|
+
export function hostSupportsModel(host: Orchestrator, provider: SpawnProvider, model?: string): boolean {
|
|
7
|
+
if (!isOpenAiApiModel(provider, model)) return true;
|
|
8
|
+
const ready = host.providerStatus?.find((entry) => entry.name === provider)?.features?.openaiApi;
|
|
9
|
+
if (ready !== undefined) return ready;
|
|
10
|
+
// Older hosts encoded credential readiness by including the paid model in their catalog.
|
|
11
|
+
return host.providerCatalog?.find((entry) => entry.provider === provider)?.models
|
|
12
|
+
.some((entry) => isOpenAiApiModel(provider, entry.alias) && !entry.disabled) ?? false;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
+
/** Project the hub catalog onto the host's genuinely local capabilities. */
|
|
15
16
|
export function hostRunnableCatalog(entry: ProviderCatalogEntry, host: Orchestrator): ProviderCatalogEntry {
|
|
16
17
|
const models = entry.models.filter((model) => !model.disabled && !model.unavailable
|
|
17
|
-
&& hostSupportsModel(host, entry.provider, model.alias))
|
|
18
|
-
const efforts = model.efforts.filter((effort) => hostSupportsModel(host, entry.provider, model.alias, effort));
|
|
19
|
-
return { ...model, efforts, defaultEffort: model.defaultEffort && efforts.includes(model.defaultEffort) ? model.defaultEffort : undefined };
|
|
20
|
-
});
|
|
18
|
+
&& hostSupportsModel(host, entry.provider, model.alias));
|
|
21
19
|
return { ...entry, models };
|
|
22
20
|
}
|
package/src/spawn-placement.ts
CHANGED
|
@@ -321,7 +321,7 @@ export function hostSpawnAdmission(
|
|
|
321
321
|
if (view?.policy.spawnEnabled === false) add("host", "draining", `host ${item.hostname} (${item.id}) is draining: new spawns disabled`);
|
|
322
322
|
if (!pinned && view && (!view.enabled || view.state === "unavailable" || view.state === "retired" || view.band === "blocked")) add("host", "unavailable", "host unavailable or blocked");
|
|
323
323
|
if (!pinned && view && view.aggregates.agentSlotsMax !== 0 && view.aggregates.agentSlotsUsed >= view.aggregates.agentSlotsMax) add("capacity", "slots", "host spawn slots full");
|
|
324
|
-
if (!pinned && !hostSupportsModel(item, provider, model
|
|
324
|
+
if (!pinned && !hostSupportsModel(item, provider, model)) add("provider", "model", `provider '${provider}' does not support requested model or effort`);
|
|
325
325
|
if (capacity) add("capacity", "agent_cap", `host ${item.hostname} (${item.id}) is ${capacity}`);
|
|
326
326
|
return assessSpawnAdmission(item, provider, checks, pinnedAuth);
|
|
327
327
|
}
|
package/src/spawn-targets.ts
CHANGED
|
@@ -290,7 +290,7 @@ function providerMatrixFor(
|
|
|
290
290
|
const providers = [...new Set([...o.providers, ...(o.providerStatus ?? []).filter((s) => s.degraded || s.cli?.transient || s.runner?.transient || s.auth?.transient).map((s) => s.name)])];
|
|
291
291
|
return providers.filter((provider) => !disabledProviders.has(provider) && !hostDisabledFor?.has(provider)).map((provider) => {
|
|
292
292
|
const globalEntry = global.find((e) => e.provider === provider);
|
|
293
|
-
//
|
|
293
|
+
// The hub owns models and efforts; hosts contribute only local capabilities.
|
|
294
294
|
const cat = globalEntry ? hostRunnableCatalog(globalEntry, o) : undefined;
|
|
295
295
|
const quotaAdvisory = quotaAdvisories.get(provider) ?? defaultQuotaAdvisory(provider);
|
|
296
296
|
// #932 — advertise the SAME default a bare spawn would actually resolve to. resolveDefaultModel
|