@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
|
@@ -4,11 +4,11 @@ import { getGateway } from "../db/gateway";
|
|
|
4
4
|
import { getAgentAsync } from "../db";
|
|
5
5
|
import { shutdownAgent } from "./shutdown-agent";
|
|
6
6
|
import { currentStop } from "./workflow-effects";
|
|
7
|
-
import { invoke,
|
|
7
|
+
import { invoke, supervisorNow, type Supervisor } from "./workflow-supervisor-state";
|
|
8
8
|
|
|
9
9
|
/** Every retry resumes the same outbox effect; only host witnesses release reservations. */
|
|
10
10
|
export async function performSpawnTreeCleanup(sup: Supervisor, effect: WorkflowEffect): Promise<boolean> {
|
|
11
|
-
const now = (
|
|
11
|
+
const now = supervisorNow();
|
|
12
12
|
if (typeof effect.result?.nextAttemptAt === "number" && effect.result.nextAttemptAt > now) return false;
|
|
13
13
|
const agentId = String(effect.payload.agentId);
|
|
14
14
|
await getGateway().write("spawnTree.prepareCleanup", { agentId });
|
|
@@ -105,6 +105,7 @@ export interface TaskDispatchSpawnInput {
|
|
|
105
105
|
/** #3045 — the workflow run dispatching this step; #3091 the base profile its step pins, the run's checkout standing in
|
|
106
106
|
* for the Task's repo root, and the host its role is read on first. Service-only. */
|
|
107
107
|
workflowRunId?: string;
|
|
108
|
+
workflowNames?: string[];
|
|
108
109
|
workflowCaller?: SpawnAgentInput["workflowCaller"];
|
|
109
110
|
roleProfile?: string;
|
|
110
111
|
taskRepoCwd?: string;
|
|
@@ -139,6 +140,7 @@ export async function spawnTaskDispatch(input: TaskDispatchSpawnInput, ctx: Auth
|
|
|
139
140
|
role,
|
|
140
141
|
roleSha, roleSnapshot,
|
|
141
142
|
workflowRunId,
|
|
143
|
+
workflowNames: input.workflowNames,
|
|
142
144
|
workflowCaller: input.workflowCaller,
|
|
143
145
|
roleProfile,
|
|
144
146
|
taskRepoCwd,
|
|
@@ -173,6 +175,7 @@ export async function spawnTaskDispatch(input: TaskDispatchSpawnInput, ctx: Auth
|
|
|
173
175
|
...(decision ? { decision } : {}),
|
|
174
176
|
...(reviewTaskId !== undefined ? { reviewTaskId } : {}),
|
|
175
177
|
...(spawn.warnings ? { warnings: spawn.warnings } : {}),
|
|
178
|
+
...(spawn.spawnPolicy ? { spawnPolicy: spawn.spawnPolicy } : {}),
|
|
176
179
|
...(verbose ? { command: summarizeSpawnCommandForCaller(spawn.command) } : {}),
|
|
177
180
|
};
|
|
178
181
|
}
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
// live in src/db/task-state-reconcile.ts; every change is applied by its conditional writer, which
|
|
5
5
|
// re-checks the rule in the transaction, then audited here with one `task.reconciled` relay event.
|
|
6
6
|
import { errMessage } from "@humwick/sdk";
|
|
7
|
-
import {
|
|
8
|
-
import { getIssueRef
|
|
7
|
+
import { writerLockAvailable } from "../db/connection";
|
|
8
|
+
import { getIssueRef } from "../db/issue-refs";
|
|
9
|
+
import { updateIssueRefAsync } from "../db/issue-refs-async";
|
|
9
10
|
import { applyTaskStateReconcileAsync, listTaskStateReconcileCandidatesAsync } from "../db/task-state-reconcile-async";
|
|
10
11
|
import { TASK_RECONCILE_ACTOR, type TaskReconcileRule, type TaskStateReconcileApplied } from "../db/task-state-reconcile";
|
|
11
12
|
import { emitRelayEvent } from "../events";
|
|
@@ -104,19 +105,10 @@ async function refreshIssueRefs(refIds: string[], opts: TaskStateReconcileOption
|
|
|
104
105
|
try {
|
|
105
106
|
const live = await (opts.adapterResolver ?? resolveIssueTrackerAdapter)(ref).getStatus(ref);
|
|
106
107
|
const status = live.status === "open" || live.status === "closed" ? live.status : ref.status;
|
|
107
|
-
// The tracker read can take long: re-check the lock right before the write
|
|
108
|
+
// The tracker read can take long: re-check the lock right before the write. The write itself waits on
|
|
109
|
+
// the writer thread, never on the loop (#1870 S7).
|
|
108
110
|
if (steps.contended()) break;
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
withFastFailConnection(STEP_BUSY_TIMEOUT_MS, () => updateIssueRef(ref.id, { status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() }));
|
|
112
|
-
return true;
|
|
113
|
-
} catch (error) {
|
|
114
|
-
if (!isSqliteBusyError(error)) throw error;
|
|
115
|
-
result.moreWork = true;
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
if (!written) break;
|
|
111
|
+
await updateIssueRefAsync(ref.id, { status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() });
|
|
120
112
|
closedAny ||= status === "closed";
|
|
121
113
|
} catch (error) {
|
|
122
114
|
refreshFailedAt.set(id, now);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isRecord, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES, APPROVAL_MODES, VALID_AGENT_LIFECYCLES } from "@humwick/sdk";
|
|
2
2
|
import { getTeam, ValidationError } from "../db";
|
|
3
|
-
import {
|
|
3
|
+
import { getConfig, listConfig } from "../config-store";
|
|
4
|
+
import { deleteConfigAsync, setConfigAsync } from "../config-store-write";
|
|
4
5
|
import { TEAM_TEMPLATE_NAMESPACE } from "../team-template-config";
|
|
5
6
|
import { cleanString, optionalEnum } from "../validation";
|
|
6
7
|
import type { AuthContext } from "./auth-context";
|
|
@@ -47,11 +48,11 @@ function withTimestamps(template: TeamTemplate, existing?: TeamTemplate): TeamTe
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
export function
|
|
51
|
+
export async function saveTeamTemplateAsync(input: Record<string, unknown>, updatedBy?: string): Promise<TeamTemplate> {
|
|
51
52
|
if (input.teamId !== undefined && input.composition !== undefined) {
|
|
52
53
|
throw new ValidationError("teamId capture and manual composition are mutually exclusive");
|
|
53
54
|
}
|
|
54
|
-
if (input.teamId !== undefined) return
|
|
55
|
+
if (input.teamId !== undefined) return captureTeamTemplateFromTeamAsync(cleanString(input.teamId, "teamId", { required: true, max: 120 })!, input, updatedBy);
|
|
55
56
|
const slug = templateSlug(input);
|
|
56
57
|
const existing = getConfig<TeamTemplate>(TEAM_TEMPLATE_NAMESPACE, slug)?.value;
|
|
57
58
|
const template = withTimestamps({
|
|
@@ -64,7 +65,7 @@ export function saveTeamTemplate(input: Record<string, unknown>, updatedBy?: str
|
|
|
64
65
|
...(cleanString(input.definitionOfDone, "definitionOfDone", { max: 4000 }) ? { definitionOfDone: cleanString(input.definitionOfDone, "definitionOfDone", { max: 4000 }) } : {}),
|
|
65
66
|
...(input.planSkeleton !== undefined ? { planSkeleton: input.planSkeleton as TeamTemplate["planSkeleton"] } : {}),
|
|
66
67
|
}, existing);
|
|
67
|
-
return
|
|
68
|
+
return (await setConfigAsync(TEAM_TEMPLATE_NAMESPACE, template.slug, template, updatedBy)).value as TeamTemplate;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
export function listTeamTemplates(): TeamTemplate[] {
|
|
@@ -75,8 +76,8 @@ export function getTeamTemplate(slug: string): TeamTemplate | null {
|
|
|
75
76
|
return getConfig<TeamTemplate>(TEAM_TEMPLATE_NAMESPACE, slug)?.value ?? null;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
export function
|
|
79
|
-
return
|
|
79
|
+
export function deleteTeamTemplateAsync(slug: string, updatedBy?: string): Promise<boolean> {
|
|
80
|
+
return deleteConfigAsync(TEAM_TEMPLATE_NAMESPACE, slug, updatedBy);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
function parseFrozenComposition(raw: string): Array<Record<string, unknown>> {
|
|
@@ -107,7 +108,7 @@ function compositionFromFrozenSnapshot(raw: string): TeamTemplateCompositionEntr
|
|
|
107
108
|
return composition;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
export function
|
|
111
|
+
export async function captureTeamTemplateFromTeamAsync(teamId: string, input: Record<string, unknown> = {}, updatedBy?: string): Promise<TeamTemplate> {
|
|
111
112
|
const team = getTeam(teamId);
|
|
112
113
|
if (!team) throw new ValidationError(`team ${teamId} not found`);
|
|
113
114
|
if (!team.composition) throw new ValidationError("team has no frozen composition snapshot; dissolve it before capture");
|
|
@@ -123,7 +124,7 @@ export function captureTeamTemplateFromTeam(teamId: string, input: Record<string
|
|
|
123
124
|
...(team.brief ? { brief: team.brief } : {}),
|
|
124
125
|
...(team.definitionOfDone ? { definitionOfDone: team.definitionOfDone } : {}),
|
|
125
126
|
}, existing);
|
|
126
|
-
return
|
|
127
|
+
return (await setConfigAsync(TEAM_TEMPLATE_NAMESPACE, template.slug, template, updatedBy)).value as TeamTemplate;
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
function cleanWait(value: unknown): number {
|
|
@@ -15,6 +15,7 @@ import { activeAgentKeep } from "./agent-keep";
|
|
|
15
15
|
import { automaticDisposalBlocker } from "./automatic-disposal";
|
|
16
16
|
import { agentGenerationKey } from "../db/agent-shutdown-in-flight";
|
|
17
17
|
import { workflowAgentObligation } from "./workflow-agent-obligation";
|
|
18
|
+
import { workflowRunPaused } from "../db/workflow-task-read-ops";
|
|
18
19
|
import type { AgentCard, Command, WorkspaceRecord } from "../types";
|
|
19
20
|
import { emitCommandEvent } from "../command-events";
|
|
20
21
|
|
|
@@ -356,7 +357,8 @@ export async function reapTransientAgents(context: { signal?: AbortSignal } = {}
|
|
|
356
357
|
if (completedProviderTurns(agent) === 0) { transientTracker.delete(agent.id); waiting.push(agent.id); continue; }
|
|
357
358
|
// #3234 — an empty branch is not completion while the workflow still awaits this agent's delivery.
|
|
358
359
|
const runId = workflowRunIdOf(agent);
|
|
359
|
-
|
|
360
|
+
// #3757 — a paused run keeps all its step agents.
|
|
361
|
+
if (runId && (workflowRunPaused(runId) || await workflowAgentObligation(runId, agent.id))) { transientTracker.delete(agent.id); waiting.push(agent.id); continue; }
|
|
360
362
|
const entry = transientTracker.get(agent.id) ?? { firstIdleAt: now };
|
|
361
363
|
transientTracker.set(agent.id, entry);
|
|
362
364
|
const idleForMs = now - entry.firstIdleAt;
|
|
@@ -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();
|