@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
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import PQueue from "p-queue";
|
|
2
|
-
import { beginRun, finishLog, recordResult, startLog, type RunContext } from "./envelope.js";
|
|
3
|
-
import { OffloadError, StrikeCounter, normalizeError, validateTimeout, type OffloadDefaults, type OffloadEnvelope } from "./types.js";
|
|
4
|
-
|
|
5
|
-
export interface AsyncRunnerOptions extends OffloadDefaults {
|
|
6
|
-
concurrency: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface AsyncRunner {
|
|
10
|
-
run<T>(taskName: string, fn: (signal: AbortSignal) => Promise<T> | T, env?: OffloadEnvelope): Promise<T>;
|
|
11
|
-
readonly stats: { active: number; queued: number };
|
|
12
|
-
/** Resolves when everything accepted so far has finished. Does not cancel anything. */
|
|
13
|
-
drain(): Promise<void>;
|
|
14
|
-
/** Cancels in-flight work, settles every outstanding caller, and refuses new work. */
|
|
15
|
-
destroy(): Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type Outcome<T> = { ok: true; value: T } | { ok: false; error: OffloadError };
|
|
19
|
-
|
|
20
|
-
type LiveRun = {
|
|
21
|
-
taskName: string;
|
|
22
|
-
teardown: AbortController;
|
|
23
|
-
settled: Promise<void>;
|
|
24
|
-
/** Settles a caller whose work has not started yet; a no-op once it has. */
|
|
25
|
-
cancelIfQueued(error: OffloadError): void;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/** Bounded I/O runner. Work receives the composed signal and must cooperate with aborts. */
|
|
29
|
-
export function createAsyncRunner(options: AsyncRunnerOptions): AsyncRunner {
|
|
30
|
-
if (!Number.isSafeInteger(options.concurrency) || options.concurrency < 1) throw new RangeError("concurrency must be a positive integer");
|
|
31
|
-
const queue = new PQueue({ concurrency: options.concurrency });
|
|
32
|
-
const counter = new StrikeCounter(options.escalationThreshold, options.escalationWindowAttempts);
|
|
33
|
-
const live = new Set<LiveRun>();
|
|
34
|
-
let destroyed = false;
|
|
35
|
-
|
|
36
|
-
const abortedError = (taskName: string, cause?: unknown): OffloadError =>
|
|
37
|
-
new OffloadError({ kind: "aborted", taskName, message: `${taskName} was aborted`, cause });
|
|
38
|
-
const destroyedError = (taskName: string): OffloadError =>
|
|
39
|
-
new OffloadError({ kind: "aborted", taskName, message: `${taskName} was cancelled: async runner destroyed` });
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
get stats() { return { active: queue.pending, queued: queue.size }; },
|
|
43
|
-
async run<T>(taskName: string, fn: (signal: AbortSignal) => Promise<T> | T, env?: OffloadEnvelope): Promise<T> {
|
|
44
|
-
const labels = env?.labels ?? {};
|
|
45
|
-
const logger = env?.logger ?? options.logger;
|
|
46
|
-
const sink = env?.onEscalate ?? options.onEscalate;
|
|
47
|
-
const reportRejection = async (error: OffloadError): Promise<never> => {
|
|
48
|
-
// Rejecting before the run reaches startLog is still a terminal outcome; without this
|
|
49
|
-
// the structured stream shows nothing at all for it.
|
|
50
|
-
finishLog(logger, "async", taskName, labels, performance.now(), error);
|
|
51
|
-
await recordResult({ counter, taskName, backend: "async", labels, error, sink, logger });
|
|
52
|
-
throw error;
|
|
53
|
-
};
|
|
54
|
-
if (destroyed) return reportRejection(destroyedError(taskName));
|
|
55
|
-
if (env?.signal?.aborted) return reportRejection(abortedError(taskName, env.signal.reason));
|
|
56
|
-
// Eagerly validated so an invalid deadline rejects with an OffloadError rather than
|
|
57
|
-
// escaping the queue callback as a raw RangeError (#1809 P0-1, same class).
|
|
58
|
-
try {
|
|
59
|
-
validateTimeout(env?.timeoutMs ?? options.defaultTimeoutMs);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
return reportRejection(normalizeError(taskName, error, "rejected"));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let publish!: (outcome: Outcome<T>) => void;
|
|
65
|
-
let published = false;
|
|
66
|
-
const outcome = new Promise<Outcome<T>>((resolve) => {
|
|
67
|
-
publish = (value) => { if (!published) { published = true; resolve(value); } };
|
|
68
|
-
});
|
|
69
|
-
let started = false;
|
|
70
|
-
let cancelled = false;
|
|
71
|
-
const entry: LiveRun = {
|
|
72
|
-
taskName,
|
|
73
|
-
teardown: new AbortController(),
|
|
74
|
-
settled: outcome.then(() => undefined),
|
|
75
|
-
cancelIfQueued: (error) => {
|
|
76
|
-
if (started || cancelled) return;
|
|
77
|
-
cancelled = true;
|
|
78
|
-
// The single funnel for both abort-while-queued and destroy()-cancels-queued, so
|
|
79
|
-
// the one line each terminal path owes the structured stream is emitted here. The
|
|
80
|
-
// callback bails on `cancelled` without logging, which keeps it to exactly one.
|
|
81
|
-
finishLog(logger, "async", taskName, labels, performance.now(), error);
|
|
82
|
-
publish({ ok: false, error });
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
live.add(entry);
|
|
86
|
-
|
|
87
|
-
// p-queue's own `signal` option used to provide this, but it also released the slot
|
|
88
|
-
// while the work kept running (#1809 P1-7). Cancelling here keeps abort prompt for
|
|
89
|
-
// queued work without lying about occupancy.
|
|
90
|
-
const signal = env?.signal;
|
|
91
|
-
const onQueuedAbort = signal ? () => entry.cancelIfQueued(abortedError(taskName, signal.reason)) : undefined;
|
|
92
|
-
if (signal && onQueuedAbort) signal.addEventListener("abort", onQueuedAbort, { once: true });
|
|
93
|
-
|
|
94
|
-
const task = queue.add(async () => {
|
|
95
|
-
if (signal && onQueuedAbort) signal.removeEventListener("abort", onQueuedAbort);
|
|
96
|
-
let work: Promise<T> | undefined;
|
|
97
|
-
let context: RunContext | undefined;
|
|
98
|
-
let onAbort: (() => void) | undefined;
|
|
99
|
-
try {
|
|
100
|
-
if (cancelled) return;
|
|
101
|
-
if (destroyed || entry.teardown.signal.aborted) {
|
|
102
|
-
const error = destroyedError(taskName);
|
|
103
|
-
finishLog(logger, "async", taskName, labels, performance.now(), error);
|
|
104
|
-
publish({ ok: false, error });
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
started = true;
|
|
108
|
-
// Match WorkerPool semantics: a task's deadline starts when it receives a slot,
|
|
109
|
-
// not while it is waiting behind unrelated bounded work.
|
|
110
|
-
context = beginRun(taskName, env, options, entry.teardown.signal);
|
|
111
|
-
const run = context;
|
|
112
|
-
const startedAt = startLog(logger, "async", taskName, labels);
|
|
113
|
-
try {
|
|
114
|
-
if (run.signal.aborted) throw run.abortError();
|
|
115
|
-
const abort = new Promise<never>((_, reject) => {
|
|
116
|
-
onAbort = () => reject(run.abortError());
|
|
117
|
-
run.signal.addEventListener("abort", onAbort, { once: true });
|
|
118
|
-
});
|
|
119
|
-
work = Promise.resolve().then(() => fn(run.signal));
|
|
120
|
-
const value = await Promise.race([work, abort]);
|
|
121
|
-
finishLog(logger, "async", taskName, labels, startedAt);
|
|
122
|
-
await recordResult({ counter, taskName, backend: "async", labels, sink, logger });
|
|
123
|
-
publish({ ok: true, value });
|
|
124
|
-
} catch (error) {
|
|
125
|
-
const normalized = run.signal.aborted ? run.abortError() : normalizeError(taskName, error);
|
|
126
|
-
finishLog(logger, "async", taskName, labels, startedAt, normalized);
|
|
127
|
-
await recordResult({ counter, taskName, backend: "async", labels, error: normalized, sink, logger });
|
|
128
|
-
publish({ ok: false, error: normalized });
|
|
129
|
-
} finally {
|
|
130
|
-
// The composite signal stays reachable from the caller's long-lived signal, so an
|
|
131
|
-
// un-removed listener retains this closure for the process lifetime (#1809 P0-4).
|
|
132
|
-
if (onAbort) run.signal.removeEventListener("abort", onAbort);
|
|
133
|
-
run.cleanup();
|
|
134
|
-
}
|
|
135
|
-
} catch (error) {
|
|
136
|
-
const normalized = normalizeError(taskName, error, "rejected");
|
|
137
|
-
finishLog(logger, "async", taskName, labels, performance.now(), normalized);
|
|
138
|
-
await recordResult({ counter, taskName, backend: "async", labels, error: normalized, sink, logger });
|
|
139
|
-
publish({ ok: false, error: normalized });
|
|
140
|
-
} finally {
|
|
141
|
-
context?.cleanup();
|
|
142
|
-
// The caller may already have been settled by the abort race; the slot stays held
|
|
143
|
-
// until the work itself finishes, otherwise the cap admits a replacement while the
|
|
144
|
-
// aborted task is still running and `stats` under-reports it (#1809 P1-7).
|
|
145
|
-
if (work) await work.catch(() => undefined);
|
|
146
|
-
live.delete(entry);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
// The callback settles callers through `publish` and never throws, but a stray
|
|
150
|
-
// rejection here would surface as an unhandled rejection rather than a failed run.
|
|
151
|
-
void task.catch(() => undefined);
|
|
152
|
-
|
|
153
|
-
const result = await outcome;
|
|
154
|
-
if (!result.ok) throw result.error;
|
|
155
|
-
return result.value;
|
|
156
|
-
},
|
|
157
|
-
drain: () => queue.onIdle(),
|
|
158
|
-
async destroy(): Promise<void> {
|
|
159
|
-
destroyed = true;
|
|
160
|
-
const outstanding = [...live];
|
|
161
|
-
for (const entry of outstanding) {
|
|
162
|
-
entry.cancelIfQueued(destroyedError(entry.taskName));
|
|
163
|
-
entry.teardown.abort(new DOMException("Async runner destroyed", "AbortError"));
|
|
164
|
-
}
|
|
165
|
-
// Waits on caller settlement rather than `queue.onIdle()`: work that ignores its signal
|
|
166
|
-
// must not be able to make teardown hang. Its slot is still held until it finishes.
|
|
167
|
-
await Promise.all(outstanding.map((entry) => entry.settled));
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { OffloadError, type OffloadDefaults, type OffloadEnvelope, type OffloadLogger, type EscalationEvent, type EscalationSink, type StrikeCounter, log, validateTimeout } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export interface RunContext {
|
|
4
|
-
signal: AbortSignal;
|
|
5
|
-
timeoutMs?: number;
|
|
6
|
-
cleanup(): void;
|
|
7
|
-
abortError(): OffloadError;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* `teardown` is the backend's own cancellation channel (runner `destroy()`); it is composed
|
|
12
|
-
* with the caller's signal and the deadline so every backend has exactly one signal to observe.
|
|
13
|
-
*/
|
|
14
|
-
export function beginRun(taskName: string, env: OffloadEnvelope | undefined, defaults: OffloadDefaults, teardown?: AbortSignal, timeoutMsOverride?: number | null): RunContext {
|
|
15
|
-
// `timeoutMsOverride` lets the WorkerPool pass an ALREADY-RESOLVED timeout: a number (e.g. the
|
|
16
|
-
// remaining budget of an absolute deadline on a retry-safe re-dispatch, #1822 MED-2) or `null`
|
|
17
|
-
// meaning "no deadline, definitively." Either way it is authoritative and already validated, so
|
|
18
|
-
// the envelope AND the mutable pool default are NOT re-read — that is what keeps a post-submission
|
|
19
|
-
// mutation of either from adding or erasing this job's deadline (#1822 MED). `null` is used for
|
|
20
|
-
// "no deadline" rather than `undefined` on purpose: `undefined` is indistinguishable from "no
|
|
21
|
-
// argument passed", which is how the async/subprocess runners ask to resolve from env/default.
|
|
22
|
-
const timeoutMs = timeoutMsOverride === undefined
|
|
23
|
-
? validateTimeout(env?.timeoutMs ?? defaults.defaultTimeoutMs)
|
|
24
|
-
: (timeoutMsOverride ?? undefined);
|
|
25
|
-
const timeout = new AbortController();
|
|
26
|
-
const sources = [env?.signal, teardown].filter((source): source is AbortSignal => source !== undefined);
|
|
27
|
-
const signal = sources.length === 0 ? timeout.signal : AbortSignal.any([...sources, timeout.signal]);
|
|
28
|
-
let timedOut = false;
|
|
29
|
-
const timer = timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
30
|
-
timedOut = true;
|
|
31
|
-
timeout.abort(new DOMException(`Timed out after ${timeoutMs}ms`, "TimeoutError"));
|
|
32
|
-
}, timeoutMs);
|
|
33
|
-
return {
|
|
34
|
-
signal,
|
|
35
|
-
timeoutMs,
|
|
36
|
-
cleanup: () => { if (timer) clearTimeout(timer); },
|
|
37
|
-
abortError: () => new OffloadError({
|
|
38
|
-
kind: timedOut ? "timeout" : "aborted",
|
|
39
|
-
taskName,
|
|
40
|
-
message: timedOut ? `${taskName} timed out after ${timeoutMs}ms` : `${taskName} was aborted`,
|
|
41
|
-
cause: signal.reason,
|
|
42
|
-
}),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Labels are spread FIRST throughout: adopters pass generic context bags, and a label that
|
|
47
|
-
// happens to be named `taskName` or `backend` must not silently replace the envelope's own
|
|
48
|
-
// fields — that would destroy exactly the uniformity this logging contract exists to provide.
|
|
49
|
-
export function startLog(logger: OffloadLogger | undefined, backend: EscalationEvent["backend"], taskName: string, labels: Record<string, string>): number {
|
|
50
|
-
const startedAt = performance.now();
|
|
51
|
-
log(logger, "debug", "offload started", { ...labels, backend, taskName });
|
|
52
|
-
return startedAt;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function finishLog(logger: OffloadLogger | undefined, backend: EscalationEvent["backend"], taskName: string, labels: Record<string, string>, startedAt: number, error?: OffloadError): void {
|
|
56
|
-
const fields = { ...labels, backend, taskName, tookMs: Math.round(performance.now() - startedAt) };
|
|
57
|
-
if (error) log(logger, "warn", "offload failed", { ...fields, kind: error.kind, code: error.code, error: error.message });
|
|
58
|
-
else log(logger, "debug", "offload completed", fields);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export async function recordResult(options: {
|
|
62
|
-
counter: StrikeCounter;
|
|
63
|
-
taskName: string;
|
|
64
|
-
backend: EscalationEvent["backend"];
|
|
65
|
-
labels: Record<string, string>;
|
|
66
|
-
error?: OffloadError;
|
|
67
|
-
sink?: EscalationSink;
|
|
68
|
-
logger?: OffloadLogger;
|
|
69
|
-
}): Promise<void> {
|
|
70
|
-
if (!options.error) {
|
|
71
|
-
options.counter.succeed(options.taskName);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// Caller-directed cancellation is neither a backend failure nor a timeout. It must not
|
|
75
|
-
// page a host merely because a legitimate shutdown cancelled several queued operations.
|
|
76
|
-
if (options.error.kind === "aborted") return;
|
|
77
|
-
const strikes = options.counter.fail(options.taskName);
|
|
78
|
-
if (strikes < options.counter.threshold) return;
|
|
79
|
-
// Like the maintenance breaker, escalation is edge-triggered: one notification at
|
|
80
|
-
// the threshold, then a latch until a successful run re-arms the task.
|
|
81
|
-
options.counter.trip(options.taskName);
|
|
82
|
-
if (!options.sink) return;
|
|
83
|
-
try {
|
|
84
|
-
await options.sink({ taskName: options.taskName, backend: options.backend, error: options.error, strikes, labels: options.labels });
|
|
85
|
-
} catch (error) {
|
|
86
|
-
log(options.logger, "error", "offload escalation sink failed", { taskName: options.taskName, backend: options.backend, error: String(error) });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function errorForExit(taskName: string, code?: string): OffloadError {
|
|
91
|
-
return new OffloadError({ kind: "worker-exit", taskName, code, message: `${taskName} worker exited${code ? ` (${code})` : ""}` });
|
|
92
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { appendFileSync, readFileSync } from "node:fs";
|
|
2
|
-
import type { OffloadWorkerTask } from "../worker.js";
|
|
3
|
-
|
|
4
|
-
// `file`-based counters let a test count executions/deaths deterministically across worker
|
|
5
|
-
// threads (a shared in-process counter is not visible from a worker), so the #1822 F2/F3 tests
|
|
6
|
-
// can PROVE the mechanism (exactly-once, bounded retries, real deaths) instead of hoping on
|
|
7
|
-
// timing. Each execution/death appends one byte; the test reads the file size.
|
|
8
|
-
const task: OffloadWorkerTask<{ kind: string; ms?: number; value?: unknown; file?: string; code?: number }, unknown> = async (input) => {
|
|
9
|
-
if (input.kind === "burn") {
|
|
10
|
-
const until = Date.now() + (input.ms ?? 0);
|
|
11
|
-
while (Date.now() < until) { /* intentionally synchronous: timeout must terminate this worker */ }
|
|
12
|
-
return input.value ?? "burned";
|
|
13
|
-
}
|
|
14
|
-
if (input.kind === "delay") {
|
|
15
|
-
await Bun.sleep(input.ms ?? 0);
|
|
16
|
-
return input.value;
|
|
17
|
-
}
|
|
18
|
-
if (input.kind === "throw") throw Object.assign(new RangeError("bad worker input"), { code: "BAD_INPUT" });
|
|
19
|
-
if (input.kind === "exit") process.exit(1);
|
|
20
|
-
// Returns a value now, then exits AFTER the slot has gone idle: simulates an OOM/SIGKILL
|
|
21
|
-
// that lands on a worker between jobs, which is the #1822 item 2 scenario. Records the death
|
|
22
|
-
// to `file` (if given) so a test can assert the deaths actually happened.
|
|
23
|
-
if (input.kind === "exit-when-idle") {
|
|
24
|
-
setTimeout(() => { if (input.file) appendFileSync(input.file, "d"); process.exit(0); }, input.ms ?? 20);
|
|
25
|
-
return input.value ?? "ok";
|
|
26
|
-
}
|
|
27
|
-
// Optionally runs for `ms` (so a deadline can bite mid-execution), records one execution to
|
|
28
|
-
// `file`, then exits the worker every time. Exits with `code` (default 1 = abnormal); pass
|
|
29
|
-
// code 0 to model a CLEAN worker exit while a job is in flight. Used to prove at-most-once
|
|
30
|
-
// (default → one record) vs bounded retry (retrySafe → MAX+1 records), the absolute deadline,
|
|
31
|
-
// and exit-manner attribution (abnormal charges, clean does not — #1822 MED-4).
|
|
32
|
-
if (input.kind === "crash-record") {
|
|
33
|
-
if (input.ms) await Bun.sleep(input.ms);
|
|
34
|
-
if (input.file) appendFileSync(input.file, "x");
|
|
35
|
-
process.exit(input.code ?? 1);
|
|
36
|
-
}
|
|
37
|
-
// Records one execution, then crashes ONLY on the first execution (file was empty) and succeeds
|
|
38
|
-
// on any retry — proves a retry-safe re-dispatch recovers and is bounded (file has 2 records).
|
|
39
|
-
if (input.kind === "flaky-record") {
|
|
40
|
-
let prior = 0;
|
|
41
|
-
try { prior = input.file ? readFileSync(input.file, "utf8").length : 0; } catch { prior = 0; }
|
|
42
|
-
if (input.file) appendFileSync(input.file, "x");
|
|
43
|
-
if (prior === 0) process.exit(1);
|
|
44
|
-
return input.value ?? "recovered";
|
|
45
|
-
}
|
|
46
|
-
return input.value;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export default task;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { createAsyncRunner, type AsyncRunner, type AsyncRunnerOptions } from "./async-runner.js";
|
|
2
|
-
export { createSubprocessRunner, type SubprocessResult, type SubprocessRunner, type SubprocessRunnerOptions, type SubprocessSpec } from "./subprocess-runner.js";
|
|
3
|
-
export { createWorkerPool, type WorkerPool, type WorkerPoolOptions, type WorkerPoolStats } from "./worker-pool.js";
|
|
4
|
-
export { OffloadError, StrikeCounter, type EscalationEvent, type EscalationSink, type OffloadDefaults, type OffloadEnvelope, type OffloadErrorKind, type OffloadLogger } from "./types.js";
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import PQueue from "p-queue";
|
|
2
|
-
import { beginRun, finishLog, recordResult, startLog, type RunContext } from "./envelope.js";
|
|
3
|
-
import { OffloadError, StrikeCounter, normalizeError, validateTimeout, type OffloadDefaults, type OffloadEnvelope } from "./types.js";
|
|
4
|
-
|
|
5
|
-
export interface SubprocessSpec {
|
|
6
|
-
command: string[];
|
|
7
|
-
cwd?: string;
|
|
8
|
-
env?: Record<string, string | undefined>;
|
|
9
|
-
/**
|
|
10
|
-
* Exit codes that are a normal ANSWER rather than a failure. Defaults to `[0]`; pass
|
|
11
|
-
* `"any"` when every code is data (a land gate whose failing codes are not enumerable).
|
|
12
|
-
*
|
|
13
|
-
* Probe-shaped callers use a non-zero exit as data — `tmux has-session` exits 1 when the
|
|
14
|
-
* session is absent, `git rev-parse` exits 128 outside a repo, `which` exits 1. Without
|
|
15
|
-
* this they reject AND take an escalation strike for a healthy result, which inverts the
|
|
16
|
-
* signal: the sink pages on a healthy fleet, then latches (it only re-arms on a success,
|
|
17
|
-
* which never comes in that steady state) so the real outage is silent.
|
|
18
|
-
*
|
|
19
|
-
* `"any"` includes SIGNAL DEATHS — a child killed by SIGKILL (137) or SIGSEGV (139)
|
|
20
|
-
* returns as a success and RE-ARMS the strike counter. That is intended for a land gate,
|
|
21
|
-
* where an OOM-killed gate is simply a failed gate, but it means an infrastructure death
|
|
22
|
-
* reads as a healthy answer and will never escalate. Prefer an explicit code list unless
|
|
23
|
-
* the failing codes genuinely cannot be enumerated. Timeout and abort still throw.
|
|
24
|
-
*/
|
|
25
|
-
okExitCodes?: readonly number[] | "any";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface SubprocessResult {
|
|
29
|
-
exitCode: number;
|
|
30
|
-
stdout: string;
|
|
31
|
-
stderr: string;
|
|
32
|
-
/** True when either stream hit `maxOutputBytes` and the captured text was cut short. */
|
|
33
|
-
truncated: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface SubprocessRunnerOptions extends OffloadDefaults {
|
|
37
|
-
concurrency?: number;
|
|
38
|
-
/**
|
|
39
|
-
* How long a child may keep running after SIGTERM before it is SIGKILLed. This is the
|
|
40
|
-
* slack on the deadline: a run cannot outlive `timeoutMs + killGraceMs` (plus a short
|
|
41
|
-
* reap window) even if it traps or ignores SIGTERM. Defaults to 2000ms.
|
|
42
|
-
*/
|
|
43
|
-
killGraceMs?: number;
|
|
44
|
-
/** Captured bytes per stream before output is truncated. Defaults to 8 MiB. */
|
|
45
|
-
maxOutputBytes?: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface SubprocessRunner {
|
|
49
|
-
run(taskName: string, process: SubprocessSpec, env?: OffloadEnvelope): Promise<SubprocessResult>;
|
|
50
|
-
readonly stats: { active: number; queued: number };
|
|
51
|
-
/** A subprocess owns the heavy work, so maintenance callers may mark their await window off-thread. */
|
|
52
|
-
readonly offThread: true;
|
|
53
|
-
/** Resolves when everything accepted so far has finished. Does not cancel anything. */
|
|
54
|
-
drain(): Promise<void>;
|
|
55
|
-
/** SIGKILLs and awaits every live child, settles every outstanding caller, and refuses new work. */
|
|
56
|
-
destroy(): Promise<void>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
type Child = ReturnType<typeof Bun.spawn>;
|
|
60
|
-
|
|
61
|
-
const DEFAULT_KILL_GRACE_MS = 2_000;
|
|
62
|
-
const DEFAULT_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
63
|
-
/** How long to wait for a SIGKILLed child to be reaped before giving its slot back anyway. */
|
|
64
|
-
const REAP_GRACE_MS = 1_000;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* `detached` children are reparented rather than reaped when the host dies (#1809 P1-5), so
|
|
68
|
-
* every live group is killed from a single process-level exit hook. One module-scoped hook
|
|
69
|
-
* keeps runner instances from piling up `exit` listeners.
|
|
70
|
-
*/
|
|
71
|
-
const liveChildren = new Set<Child>();
|
|
72
|
-
let exitHookInstalled = false;
|
|
73
|
-
|
|
74
|
-
function killGroup(child: Child, signal: NodeJS.Signals): void {
|
|
75
|
-
try {
|
|
76
|
-
if (process.platform !== "win32") process.kill(-child.pid, signal);
|
|
77
|
-
else child.kill();
|
|
78
|
-
} catch {
|
|
79
|
-
try { child.kill(signal); } catch { /* the child is already gone */ }
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function trackChild(child: Child): void {
|
|
84
|
-
liveChildren.add(child);
|
|
85
|
-
if (exitHookInstalled) return;
|
|
86
|
-
exitHookInstalled = true;
|
|
87
|
-
process.on("exit", () => {
|
|
88
|
-
for (const live of liveChildren) killGroup(live, "SIGKILL");
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Reads a stream up to `limit` bytes, then keeps draining so the child never blocks on a full pipe. */
|
|
93
|
-
async function readCapped(stream: ReadableStream<Uint8Array> | null, limit: number): Promise<{ text: string; truncated: boolean }> {
|
|
94
|
-
if (!stream) return { text: "", truncated: false };
|
|
95
|
-
const reader = stream.getReader();
|
|
96
|
-
const chunks: Uint8Array[] = [];
|
|
97
|
-
let size = 0;
|
|
98
|
-
let truncated = false;
|
|
99
|
-
try {
|
|
100
|
-
for (;;) {
|
|
101
|
-
const { done, value } = await reader.read();
|
|
102
|
-
if (done) break;
|
|
103
|
-
if (!value) continue;
|
|
104
|
-
const remaining = limit - size;
|
|
105
|
-
if (remaining <= 0) {
|
|
106
|
-
truncated = true;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (value.byteLength > remaining) {
|
|
110
|
-
chunks.push(value.subarray(0, remaining));
|
|
111
|
-
size = limit;
|
|
112
|
-
truncated = true;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
chunks.push(value);
|
|
116
|
-
size += value.byteLength;
|
|
117
|
-
}
|
|
118
|
-
} finally {
|
|
119
|
-
reader.releaseLock();
|
|
120
|
-
}
|
|
121
|
-
const buffer = new Uint8Array(size);
|
|
122
|
-
let offset = 0;
|
|
123
|
-
for (const chunk of chunks) {
|
|
124
|
-
buffer.set(chunk, offset);
|
|
125
|
-
offset += chunk.byteLength;
|
|
126
|
-
}
|
|
127
|
-
return { text: new TextDecoder().decode(buffer), truncated };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
type Outcome = { ok: true; value: SubprocessResult } | { ok: false; error: OffloadError };
|
|
131
|
-
|
|
132
|
-
type LiveRun = {
|
|
133
|
-
taskName: string;
|
|
134
|
-
teardown: AbortController;
|
|
135
|
-
settled: Promise<void>;
|
|
136
|
-
/** Settles a caller whose child has not been spawned yet; a no-op once it has. */
|
|
137
|
-
cancelIfQueued(error: OffloadError): void;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Bounded wrapper around the repo's standard `Bun.spawn(... { stdout: "pipe", stderr: "pipe" })`
|
|
142
|
-
* pattern. On POSIX, each run gets its own process group so timeout/abort also kills
|
|
143
|
-
* descendants (for example shell, git, or install subprocess trees); Windows falls back
|
|
144
|
-
* to Bun's direct-child kill because POSIX process groups do not exist there.
|
|
145
|
-
*/
|
|
146
|
-
export function createSubprocessRunner(options: SubprocessRunnerOptions = {}): SubprocessRunner {
|
|
147
|
-
const queue = new PQueue({ concurrency: options.concurrency ?? 1 });
|
|
148
|
-
const counter = new StrikeCounter(options.escalationThreshold, options.escalationWindowAttempts);
|
|
149
|
-
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
150
|
-
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
151
|
-
if (!Number.isFinite(killGraceMs) || killGraceMs < 0) throw new RangeError("killGraceMs must be a non-negative finite number");
|
|
152
|
-
if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1) throw new RangeError("maxOutputBytes must be a positive integer");
|
|
153
|
-
const live = new Set<LiveRun>();
|
|
154
|
-
const children = new Set<Child>();
|
|
155
|
-
let destroyed = false;
|
|
156
|
-
|
|
157
|
-
const abortedError = (taskName: string, cause?: unknown): OffloadError =>
|
|
158
|
-
new OffloadError({ kind: "aborted", taskName, message: `${taskName} was aborted`, cause });
|
|
159
|
-
const destroyedError = (taskName: string): OffloadError =>
|
|
160
|
-
new OffloadError({ kind: "aborted", taskName, message: `${taskName} was cancelled: subprocess runner destroyed` });
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
offThread: true,
|
|
164
|
-
get stats() { return { active: queue.pending, queued: queue.size }; },
|
|
165
|
-
async run(taskName: string, spec: SubprocessSpec, env?: OffloadEnvelope): Promise<SubprocessResult> {
|
|
166
|
-
const labels = env?.labels ?? {};
|
|
167
|
-
const logger = env?.logger ?? options.logger;
|
|
168
|
-
const sink = env?.onEscalate ?? options.onEscalate;
|
|
169
|
-
const reportRejection = async (error: OffloadError): Promise<never> => {
|
|
170
|
-
// Rejecting before the run reaches startLog is still a terminal outcome; without this
|
|
171
|
-
// the structured stream shows nothing at all for it.
|
|
172
|
-
finishLog(logger, "subprocess", taskName, labels, performance.now(), error);
|
|
173
|
-
await recordResult({ counter, taskName, backend: "subprocess", labels, error, sink, logger });
|
|
174
|
-
throw error;
|
|
175
|
-
};
|
|
176
|
-
if (!spec.command.length) return reportRejection(new OffloadError({ kind: "rejected", taskName, message: "Subprocess command must not be empty" }));
|
|
177
|
-
if (destroyed) return reportRejection(destroyedError(taskName));
|
|
178
|
-
if (env?.signal?.aborted) return reportRejection(abortedError(taskName, env.signal.reason));
|
|
179
|
-
try {
|
|
180
|
-
validateTimeout(env?.timeoutMs ?? options.defaultTimeoutMs);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
return reportRejection(normalizeError(taskName, error, "rejected"));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let publish!: (outcome: Outcome) => void;
|
|
186
|
-
let published = false;
|
|
187
|
-
const outcome = new Promise<Outcome>((resolve) => {
|
|
188
|
-
publish = (value) => { if (!published) { published = true; resolve(value); } };
|
|
189
|
-
});
|
|
190
|
-
let started = false;
|
|
191
|
-
let cancelled = false;
|
|
192
|
-
const entry: LiveRun = {
|
|
193
|
-
taskName,
|
|
194
|
-
teardown: new AbortController(),
|
|
195
|
-
settled: outcome.then(() => undefined),
|
|
196
|
-
cancelIfQueued: (error) => {
|
|
197
|
-
if (started || cancelled) return;
|
|
198
|
-
cancelled = true;
|
|
199
|
-
// The single funnel for both abort-while-queued and destroy()-cancels-queued, so
|
|
200
|
-
// the one line each terminal path owes the structured stream is emitted here. The
|
|
201
|
-
// callback bails on `cancelled` without logging, which keeps it to exactly one.
|
|
202
|
-
finishLog(logger, "subprocess", taskName, labels, performance.now(), error);
|
|
203
|
-
publish({ ok: false, error });
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
live.add(entry);
|
|
207
|
-
|
|
208
|
-
// p-queue's own `signal` option used to provide prompt abort for queued runs, but it
|
|
209
|
-
// also released the slot while the child kept running (#1809 P1-7).
|
|
210
|
-
const signal = env?.signal;
|
|
211
|
-
const onQueuedAbort = signal ? () => entry.cancelIfQueued(abortedError(taskName, signal.reason)) : undefined;
|
|
212
|
-
if (signal && onQueuedAbort) signal.addEventListener("abort", onQueuedAbort, { once: true });
|
|
213
|
-
|
|
214
|
-
const task = queue.add(async () => {
|
|
215
|
-
if (signal && onQueuedAbort) signal.removeEventListener("abort", onQueuedAbort);
|
|
216
|
-
let context: RunContext | undefined;
|
|
217
|
-
let child: Child | undefined;
|
|
218
|
-
let onAbort: (() => void) | undefined;
|
|
219
|
-
let killTimer: ReturnType<typeof setTimeout> | undefined;
|
|
220
|
-
let abandonTimer: ReturnType<typeof setTimeout> | undefined;
|
|
221
|
-
let reaped = false;
|
|
222
|
-
let abandon!: () => void;
|
|
223
|
-
const abandoned = new Promise<"abandoned">((resolve) => { abandon = () => resolve("abandoned"); });
|
|
224
|
-
try {
|
|
225
|
-
if (cancelled) return;
|
|
226
|
-
if (destroyed || entry.teardown.signal.aborted) {
|
|
227
|
-
const error = destroyedError(taskName);
|
|
228
|
-
finishLog(logger, "subprocess", taskName, labels, performance.now(), error);
|
|
229
|
-
publish({ ok: false, error });
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
started = true;
|
|
233
|
-
context = beginRun(taskName, env, options, entry.teardown.signal);
|
|
234
|
-
const run = context;
|
|
235
|
-
const startedAt = startLog(logger, "subprocess", taskName, labels);
|
|
236
|
-
// SIGTERM alone bounds nothing: a child that traps or ignores it runs to completion
|
|
237
|
-
// while holding the slot (#1809 P0-3). Escalate to a group SIGKILL, then stop
|
|
238
|
-
// waiting altogether so the deadline is real even for an unkillable child.
|
|
239
|
-
onAbort = () => {
|
|
240
|
-
if (!child || reaped) return;
|
|
241
|
-
killGroup(child, "SIGTERM");
|
|
242
|
-
killTimer = setTimeout(() => {
|
|
243
|
-
// `reaped` keeps a late timer from signalling a process group whose pid the
|
|
244
|
-
// kernel may already have handed to somebody else.
|
|
245
|
-
if (!child || reaped) return;
|
|
246
|
-
killGroup(child, "SIGKILL");
|
|
247
|
-
abandonTimer = setTimeout(abandon, REAP_GRACE_MS);
|
|
248
|
-
}, killGraceMs);
|
|
249
|
-
};
|
|
250
|
-
run.signal.addEventListener("abort", onAbort, { once: true });
|
|
251
|
-
try {
|
|
252
|
-
if (run.signal.aborted) throw run.abortError();
|
|
253
|
-
child = Bun.spawn(spec.command, {
|
|
254
|
-
cwd: spec.cwd,
|
|
255
|
-
env: spec.env ? { ...process.env, ...spec.env } : process.env,
|
|
256
|
-
stdin: "ignore", stdout: "pipe", stderr: "pipe",
|
|
257
|
-
detached: process.platform !== "win32",
|
|
258
|
-
});
|
|
259
|
-
trackChild(child);
|
|
260
|
-
children.add(child);
|
|
261
|
-
const exiting = child;
|
|
262
|
-
void exiting.exited.then(() => {
|
|
263
|
-
reaped = true;
|
|
264
|
-
liveChildren.delete(exiting);
|
|
265
|
-
children.delete(exiting);
|
|
266
|
-
});
|
|
267
|
-
const completed = Promise.all([
|
|
268
|
-
readCapped(child.stdout as ReadableStream<Uint8Array> | null, maxOutputBytes),
|
|
269
|
-
readCapped(child.stderr as ReadableStream<Uint8Array> | null, maxOutputBytes),
|
|
270
|
-
child.exited,
|
|
271
|
-
]);
|
|
272
|
-
const settledRun = await Promise.race([completed, abandoned]);
|
|
273
|
-
if (settledRun === "abandoned") {
|
|
274
|
-
throw run.signal.aborted
|
|
275
|
-
? run.abortError()
|
|
276
|
-
: new OffloadError({ kind: "timeout", taskName, message: `${taskName} did not exit after SIGKILL` });
|
|
277
|
-
}
|
|
278
|
-
const [stdout, stderr, exitCode] = settledRun;
|
|
279
|
-
if (run.signal.aborted) throw run.abortError();
|
|
280
|
-
const okExitCodes = spec.okExitCodes ?? [0];
|
|
281
|
-
if (okExitCodes !== "any" && !okExitCodes.includes(exitCode)) throw new OffloadError({
|
|
282
|
-
kind: "rejected", taskName, code: String(exitCode), message: `${taskName} exited ${exitCode}: ${stderr.text.trim() || stdout.text.trim()}`,
|
|
283
|
-
});
|
|
284
|
-
const result: SubprocessResult = { exitCode, stdout: stdout.text, stderr: stderr.text, truncated: stdout.truncated || stderr.truncated };
|
|
285
|
-
finishLog(logger, "subprocess", taskName, labels, startedAt);
|
|
286
|
-
await recordResult({ counter, taskName, backend: "subprocess", labels, sink, logger });
|
|
287
|
-
publish({ ok: true, value: result });
|
|
288
|
-
} catch (error) {
|
|
289
|
-
const normalized = run.signal.aborted ? run.abortError() : normalizeError(taskName, error);
|
|
290
|
-
finishLog(logger, "subprocess", taskName, labels, startedAt, normalized);
|
|
291
|
-
await recordResult({ counter, taskName, backend: "subprocess", labels, error: normalized, sink, logger });
|
|
292
|
-
publish({ ok: false, error: normalized });
|
|
293
|
-
} finally {
|
|
294
|
-
if (onAbort) run.signal.removeEventListener("abort", onAbort);
|
|
295
|
-
run.cleanup();
|
|
296
|
-
}
|
|
297
|
-
} catch (error) {
|
|
298
|
-
const normalized = normalizeError(taskName, error, "rejected");
|
|
299
|
-
finishLog(logger, "subprocess", taskName, labels, performance.now(), normalized);
|
|
300
|
-
await recordResult({ counter, taskName, backend: "subprocess", labels, error: normalized, sink, logger });
|
|
301
|
-
publish({ ok: false, error: normalized });
|
|
302
|
-
} finally {
|
|
303
|
-
context?.cleanup();
|
|
304
|
-
// Keep the slot until the child is actually reaped, so the concurrency cap and
|
|
305
|
-
// `stats` stay true while a kill is in flight (#1809 P1-7). `abandoned` bounds
|
|
306
|
-
// this: the SIGKILL escalation above always ends the wait.
|
|
307
|
-
if (child) await Promise.race([child.exited, abandoned]);
|
|
308
|
-
if (killTimer) clearTimeout(killTimer);
|
|
309
|
-
if (abandonTimer) clearTimeout(abandonTimer);
|
|
310
|
-
live.delete(entry);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
void task.catch(() => undefined);
|
|
314
|
-
|
|
315
|
-
const result = await outcome;
|
|
316
|
-
if (!result.ok) throw result.error;
|
|
317
|
-
return result.value;
|
|
318
|
-
},
|
|
319
|
-
drain: () => queue.onIdle(),
|
|
320
|
-
async destroy(): Promise<void> {
|
|
321
|
-
destroyed = true;
|
|
322
|
-
const outstanding = [...live];
|
|
323
|
-
for (const entry of outstanding) {
|
|
324
|
-
entry.cancelIfQueued(destroyedError(entry.taskName));
|
|
325
|
-
entry.teardown.abort(new DOMException("Subprocess runner destroyed", "AbortError"));
|
|
326
|
-
}
|
|
327
|
-
for (const child of children) killGroup(child, "SIGKILL");
|
|
328
|
-
await Promise.all([
|
|
329
|
-
...outstanding.map((entry) => entry.settled),
|
|
330
|
-
...[...children].map((child) => child.exited),
|
|
331
|
-
]);
|
|
332
|
-
},
|
|
333
|
-
};
|
|
334
|
-
}
|