@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
package/src/routes/workflows.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseWorkflowDecisionResponse } from "../services/workflow-decisions";
|
|
2
2
|
// #3076 — the dashboard's workflow run reads: the run list (by status and project), one run, the stats
|
|
3
3
|
// window and the workflows that ran, all from the workflow run records relay_workflow_get reads, with its visibility rule.
|
|
4
|
-
// #3120 — the catalog. #3166 — cancel a run, as relay_workflow_cancel does.
|
|
4
|
+
// #3120 — the catalog. #3166 — cancel a run, as relay_workflow_cancel does. #3757 — pause and unpause one.
|
|
5
5
|
import { OpError } from "@humwick/kernel";
|
|
6
6
|
import { isRecord, WORKFLOW_STATS_WINDOWS, type WorkflowStatsWindow } from "@humwick/sdk";
|
|
7
7
|
import { resolveProjectAsync, ValidationError } from "../db";
|
|
@@ -10,7 +10,7 @@ import { authContextFromRequest } from "../services/auth-context";
|
|
|
10
10
|
import { ServiceAuthError } from "../services/errors";
|
|
11
11
|
import { readWorkflowCatalog } from "../services/workflow-catalog";
|
|
12
12
|
import { requireSpawnBudget } from "../spawn-budget";
|
|
13
|
-
import { amendWorkflowRunBudget, cancelWorkflowRun, getWorkflowRun, readWorkflowRunBudget, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun } from "../services/workflow-runs";
|
|
13
|
+
import { amendWorkflowRunBudget, cancelWorkflowRun, getWorkflowRun, pauseWorkflowRun, readWorkflowRunBudget, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun } from "../services/workflow-runs";
|
|
14
14
|
import { workflowDefinitions, workflowStats } from "../services/workflow-stats";
|
|
15
15
|
import { cleanString, taskIdField } from "../validation";
|
|
16
16
|
import { error, json, parseBody, type Handler } from "./_shared";
|
|
@@ -83,7 +83,22 @@ export const postWorkflowRunCancelRoute: Handler = async (req, params) => {
|
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
/** #3757 — pause (a `reason` is required) or unpause a run, as relay_workflow_resume's `pause` does; the dashboard's buttons use these. */
|
|
87
|
+
function workflowRunPauseRoute(pause: boolean): Handler {
|
|
88
|
+
return async (req, params) => {
|
|
89
|
+
const parsed = await parseBody<unknown>(req);
|
|
90
|
+
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
91
|
+
if (parsed.body !== null && !isRecord(parsed.body)) return error("body must be a JSON object", 400);
|
|
92
|
+
try {
|
|
93
|
+
const reason = cleanString(parsed.body?.reason, "reason", { max: 2000 });
|
|
94
|
+
return json(await pauseWorkflowRun({ runId: params.runId!, pause, ...(reason ? { reason } : {}) }, authContextFromRequest(req)));
|
|
95
|
+
} catch (e) { return runRouteError(e); }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export const postWorkflowRunPauseRoute = workflowRunPauseRoute(true);
|
|
99
|
+
export const postWorkflowRunUnpauseRoute = workflowRunPauseRoute(false);
|
|
100
|
+
|
|
101
|
+
function runRouteError(e: unknown): Response {
|
|
87
102
|
if (e instanceof McpNotFoundError) return error(e.message, 404);
|
|
88
103
|
if (e instanceof ServiceAuthError) return error(e.message, 403);
|
|
89
104
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
@@ -91,7 +106,7 @@ function budgetRouteError(e: unknown): Response {
|
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
export const getWorkflowRunBudgetRoute: Handler = async (req, params) => {
|
|
94
|
-
try { return json(await readWorkflowRunBudget(params.runId!, authContextFromRequest(req))); } catch (e) { return
|
|
109
|
+
try { return json(await readWorkflowRunBudget(params.runId!, authContextFromRequest(req))); } catch (e) { return runRouteError(e); }
|
|
95
110
|
};
|
|
96
111
|
|
|
97
112
|
export const patchWorkflowRunBudgetRoute: Handler = async (req, params) => {
|
|
@@ -100,7 +115,7 @@ export const patchWorkflowRunBudgetRoute: Handler = async (req, params) => {
|
|
|
100
115
|
try {
|
|
101
116
|
const { tokenCap } = requireSpawnBudget(parsed.body);
|
|
102
117
|
return json(await amendWorkflowRunBudget({ runId: params.runId!, tokenCap }, authContextFromRequest(req)));
|
|
103
|
-
} catch (e) { return
|
|
118
|
+
} catch (e) { return runRouteError(e); }
|
|
104
119
|
};
|
|
105
120
|
|
|
106
121
|
export const postWorkflowRunResumeRoute: Handler = async (req, params) => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ValidationError } from "@humwick/sdk";
|
|
2
|
-
import { currentDispatchModels } from "./routing-model-aliases";
|
|
3
2
|
import { normalizeStepRoutingDefaults } from "./routing-step-defaults";
|
|
4
3
|
import {
|
|
5
4
|
isRecord,
|
|
@@ -199,7 +198,7 @@ export function normalizeDispatchPolicy(raw: unknown): DispatchPolicy {
|
|
|
199
198
|
*/
|
|
200
199
|
/** Pure projection used by reader-gateway callers that already captured the policy snapshot. */
|
|
201
200
|
export function dispatchPolicyFor(policy: RoutingPolicy): DispatchPolicy {
|
|
202
|
-
return policy.dispatch
|
|
201
|
+
return policy.dispatch ?? DEFAULT_DISPATCH_POLICY;
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
const MAX_RULES = 100;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { cleanString, isRecord, isSpawnProvider, ValidationError, type StepRoutingDefaults, type StepRoutingTarget } from "@humwick/sdk";
|
|
2
|
-
import { VALID_EFFORTS } from "@humwick/sdk/provider-catalog";
|
|
3
2
|
import { WORKFLOW_PIN_FIELDS } from "@humwick/kernel/workflow-definition";
|
|
4
3
|
|
|
5
4
|
export function normalizeStepRoutingDefaults(raw: unknown): StepRoutingDefaults {
|
|
@@ -25,7 +24,6 @@ function normalizeRoles(raw: unknown, where: string): Record<string, StepRouting
|
|
|
25
24
|
for (const key of Object.keys(target)) if (!(WORKFLOW_PIN_FIELDS as readonly string[]).includes(key)) throw new ValidationError(`${where}.${role}.${key} is not a routing field`);
|
|
26
25
|
const fields = Object.fromEntries(Object.entries(target).map(([key, value]) => [key, cleanString(value, `${where}.${role}.${key}`, { required: true, max: 200 })]));
|
|
27
26
|
if (fields.provider !== undefined && !isSpawnProvider(fields.provider)) throw new ValidationError(`${where}.${role}.provider is not a provider`);
|
|
28
|
-
if (fields.effort !== undefined && !(VALID_EFFORTS as readonly string[]).includes(fields.effort)) throw new ValidationError(`${where}.${role}.effort must be ${VALID_EFFORTS.join(", ")}`);
|
|
29
27
|
return [role, fields as StepRoutingTarget];
|
|
30
28
|
}));
|
|
31
29
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// #3775 — per-subject cap on runner token successor mints (renew, orchestrator re-mint, boundary
|
|
2
|
+
// re-derive). A runner stuck re-minting after 401 minted 700-1,400 tokens/min and overloaded the
|
|
3
|
+
// hub writer; this refuses the excess before any token row is written. A spawn subject
|
|
4
|
+
// (`runner:spawn:<id>`) keeps one subject across its whole token lineage, so it is the key. Only a
|
|
5
|
+
// real mint is charged: a replayed handoff (retry or concurrent caller) writes nothing.
|
|
6
|
+
|
|
7
|
+
/** At most this many new tokens per subject in any rolling window. */
|
|
8
|
+
export const RUNNER_TOKEN_MINTS_PER_WINDOW = 3;
|
|
9
|
+
export const RUNNER_TOKEN_MINT_WINDOW_MS = 60_000;
|
|
10
|
+
const MAX_TRACKED_SUBJECTS = 10_000;
|
|
11
|
+
|
|
12
|
+
export class RunnerTokenMintLimit {
|
|
13
|
+
/** Recent mint times per subject, oldest first, at most RUNNER_TOKEN_MINTS_PER_WINDOW long. */
|
|
14
|
+
private readonly mints = new Map<string, number[]>();
|
|
15
|
+
/** Subjects currently refused, so a trip logs once rather than once per refused mint. */
|
|
16
|
+
private readonly tripped = new Set<string>();
|
|
17
|
+
private readonly now: () => number;
|
|
18
|
+
private readonly log: (message: string) => void;
|
|
19
|
+
|
|
20
|
+
constructor(options: { now?: () => number; log?: (message: string) => void } = {}) {
|
|
21
|
+
this.now = options.now ?? Date.now;
|
|
22
|
+
this.log = options.log ?? console.warn;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Seconds `sub` must wait before its next mint, or undefined when it may mint now. Charges nothing. */
|
|
26
|
+
refusal(sub: string): number | undefined {
|
|
27
|
+
const recent = this.recent(sub);
|
|
28
|
+
if (recent.length < RUNNER_TOKEN_MINTS_PER_WINDOW) {
|
|
29
|
+
this.tripped.delete(sub);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (!this.tripped.has(sub)) {
|
|
33
|
+
if (this.tripped.size >= MAX_TRACKED_SUBJECTS) this.tripped.clear();
|
|
34
|
+
this.tripped.add(sub);
|
|
35
|
+
this.log(`[token-mint] rate limit tripped for ${sub}: ${RUNNER_TOKEN_MINTS_PER_WINDOW} tokens minted in the last ${RUNNER_TOKEN_MINT_WINDOW_MS / 1000}s; refusing until the window frees`);
|
|
36
|
+
}
|
|
37
|
+
return Math.max(1, Math.ceil((recent[0]! + RUNNER_TOKEN_MINT_WINDOW_MS - this.now()) / 1000));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Record one new token minted for `sub`. */
|
|
41
|
+
charge(sub: string): void {
|
|
42
|
+
const recent = this.recent(sub);
|
|
43
|
+
if (!this.mints.has(sub) && this.mints.size >= MAX_TRACKED_SUBJECTS) this.mints.delete(this.mints.keys().next().value!);
|
|
44
|
+
recent.push(this.now());
|
|
45
|
+
if (recent.length > RUNNER_TOKEN_MINTS_PER_WINDOW) recent.shift();
|
|
46
|
+
this.mints.set(sub, recent);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
reset(): void {
|
|
50
|
+
this.mints.clear();
|
|
51
|
+
this.tripped.clear();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private recent(sub: string): number[] {
|
|
55
|
+
const since = this.now() - RUNNER_TOKEN_MINT_WINDOW_MS;
|
|
56
|
+
const recent = (this.mints.get(sub) ?? []).filter((at) => at > since);
|
|
57
|
+
if (recent.length) this.mints.set(sub, recent);
|
|
58
|
+
else this.mints.delete(sub);
|
|
59
|
+
return recent;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const runnerTokenMintLimit = new RunnerTokenMintLimit();
|
package/src/runtime-grant.ts
CHANGED
|
@@ -24,7 +24,9 @@ const SPAWN_SCOPES = ["command:spawn", "command:shutdown"] as const;
|
|
|
24
24
|
const FLEET_OPS_PROFILES = new Set(["coordinator", "ops-assistant"]);
|
|
25
25
|
// #1925 H10 — fleet visibility is coordinator-facing. Keep this a fixed trusted-profile allowlist,
|
|
26
26
|
// like command:fleet, rather than a user-editable profile setting that could widen worker tokens.
|
|
27
|
-
const
|
|
27
|
+
const HOST_WRITE_PROFILES = new Set(["coordinator"]);
|
|
28
|
+
// #3772 — the reboot runbook reads resource_control held/waiter counts from /api/hosts; ops profiles read, never write.
|
|
29
|
+
const HOST_READ_PROFILES = new Set([...HOST_WRITE_PROFILES, "ops-assistant", "sec-ops"]);
|
|
28
30
|
// #3073 — coordinator-only groups (not in WORKER_TOOL_GROUPS) a coordinator always gets, even when its stored override predates them.
|
|
29
31
|
const COORDINATOR_ONLY_TOOL_GROUPS = ["decisions", "workflows", "project", "routing"] as const;
|
|
30
32
|
// #3327 — the coordinator edits routing preferences, like Edin in the dashboard.
|
|
@@ -109,7 +111,7 @@ export function deriveRunnerGrant(profile: string | undefined, authority: Deriva
|
|
|
109
111
|
...nonTrustedBaseline(agentInitiated),
|
|
110
112
|
...(fleetOps ? [FLEET_SCOPE] : []),
|
|
111
113
|
...(hostsRead ? ["hosts:read"] : []),
|
|
112
|
-
...(!agentInitiated && Boolean(profile &&
|
|
114
|
+
...(!agentInitiated && Boolean(profile && HOST_WRITE_PROFILES.has(profile)) && callerMayMintHostsScope(callerScopes, "hosts:write") ? ["hosts:write"] : []),
|
|
113
115
|
...(grantsSecretsScope(profile, toolGroups, callerScopes) ? [SECRETS_WRITE_SCOPE] : []),
|
|
114
116
|
...(!agentInitiated && (COORDINATOR_PROFILE_NAMES as readonly string[]).includes(profile ?? "") && (callerScopes === undefined || hasScope(callerScopes, ROUTING_WRITE_SCOPE)) ? [ROUTING_WRITE_SCOPE] : []),
|
|
115
117
|
...(grant.canSpawn ? SPAWN_SCOPES.filter(scope => !agentInitiated || callerScopes === undefined || hasScope(callerScopes, scope)) : []),
|
package/src/runtime-tokens.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { registryToolName } from "@humwick/sdk";
|
|
|
2
2
|
import { writeChildEnv } from "@humwick/sdk/env";
|
|
3
3
|
import { deriveRunnerGrant, successorGrant } from "./runtime-grant";
|
|
4
4
|
export { runnerSpawnGrantForAgent } from "./runtime-grant";
|
|
5
|
-
import { TOKEN_RECOVERY_WINDOW_SECONDS, createToken, getToken, getTokenProfile, type CreateTokenInput } from "./token-db";
|
|
6
|
-
import {
|
|
5
|
+
import { TOKEN_RECOVERY_WINDOW_SECONDS, createToken, getToken, getTokenProfile, type CreateTokenInput, type HandoffTokenInput, type TokenHandoff } from "./token-db";
|
|
6
|
+
import { runnerTokenMintLimit } from "./runner-token-mint-limit";
|
|
7
|
+
import { createRunnerTokenAsync, createTokenAsync, handoffTokenAsync } from "./token-db-write";
|
|
7
8
|
import { FLEET_SCOPE, verifyComponentTokenAllowExpired } from "./security";
|
|
8
9
|
import { getAgentProfile, hostPinForProfile, spawnGrantForProfile, toolGroupsForProfile } from "./config-store";
|
|
9
10
|
import { toolAllowlistForRunner } from "./mcp-tool-allowlist";
|
|
@@ -38,6 +39,22 @@ interface RuntimeTokenResult {
|
|
|
38
39
|
record: TokenRecord;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/** A refused successor mint; `retryAfterSeconds` marks a per-subject rate limit (HTTP 429). */
|
|
43
|
+
export type RunnerTokenMintRefusal = { error: string; retryAfterSeconds?: number };
|
|
44
|
+
|
|
45
|
+
/** #3775 — the one way a runner token's successor is minted: within the subject's mint limit, charged per new token. */
|
|
46
|
+
export async function handoffRunnerTokenAsync(sub: string, input: HandoffTokenInput): Promise<TokenHandoff | null | { retryAfterSeconds: number }> {
|
|
47
|
+
const retryAfterSeconds = runnerTokenMintLimit.refusal(sub);
|
|
48
|
+
if (retryAfterSeconds !== undefined) return { retryAfterSeconds };
|
|
49
|
+
const handoff = await handoffTokenAsync(input);
|
|
50
|
+
if (handoff && !handoff.replayed) runnerTokenMintLimit.charge(sub);
|
|
51
|
+
return handoff;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function mintRateLimited(handoff: object | null): handoff is { retryAfterSeconds: number } {
|
|
55
|
+
return handoff !== null && "retryAfterSeconds" in handoff;
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
function runnerTokenOwnedByOrchestratorHost(
|
|
42
59
|
constraints: NonNullable<ReturnType<typeof verifyComponentTokenAllowExpired>>["constraints"],
|
|
43
60
|
orchestratorId: string,
|
|
@@ -68,7 +85,7 @@ export async function reissueRunnerRuntimeToken(input: {
|
|
|
68
85
|
expiredToken: string;
|
|
69
86
|
orchestratorId: string;
|
|
70
87
|
createdBy?: string;
|
|
71
|
-
}): Promise<RuntimeTokenResult |
|
|
88
|
+
}): Promise<RuntimeTokenResult | RunnerTokenMintRefusal> {
|
|
72
89
|
const payload = verifyComponentTokenAllowExpired(input.expiredToken, { allowStaleRevoked: true });
|
|
73
90
|
// #3133 — recovery hands off from the token's record, which holds its standing; a token without one recovers nothing.
|
|
74
91
|
if (!payload?.jti) return { error: "invalid or revoked runner token" };
|
|
@@ -95,7 +112,8 @@ export async function reissueRunnerRuntimeToken(input: {
|
|
|
95
112
|
constraints: payload.constraints,
|
|
96
113
|
createdBy: input.createdBy ?? `remint:${payload.jti}`,
|
|
97
114
|
};
|
|
98
|
-
const handoff = await
|
|
115
|
+
const handoff = await handoffRunnerTokenAsync(payload.sub, { jti: payload.jti, successor, recoverExpiredWithinSeconds: TOKEN_RECOVERY_WINDOW_SECONDS, createdBy: successor.createdBy });
|
|
116
|
+
if (mintRateLimited(handoff)) return { error: "runner token mint rate limited", retryAfterSeconds: handoff.retryAfterSeconds };
|
|
99
117
|
return handoff ?? { error: "invalid or revoked runner token" };
|
|
100
118
|
}
|
|
101
119
|
|
|
@@ -122,7 +140,7 @@ function runnerGrantKey(scope: readonly string[] | undefined, constraints: Token
|
|
|
122
140
|
* (current profile, lineage ceiling) and the presented token's launch identity. An identical grant is left
|
|
123
141
|
* alone; otherwise the token is handed off like a renewal, so a lost response is recovered by a retry.
|
|
124
142
|
*/
|
|
125
|
-
export async function remintRunnerRuntimeToken(auth: ComponentToken, createdBy: string): Promise<RunnerTokenRemint | { unchanged: true; jti: string } |
|
|
143
|
+
export async function remintRunnerRuntimeToken(auth: ComponentToken, createdBy: string): Promise<RunnerTokenRemint | { unchanged: true; jti: string } | RunnerTokenMintRefusal> {
|
|
126
144
|
const previous = auth.jti ? getToken(auth.jti) : null;
|
|
127
145
|
const c = auth.constraints ?? {};
|
|
128
146
|
const agentId = c.agents?.length === 1 ? c.agents[0] : undefined;
|
|
@@ -149,7 +167,8 @@ export async function remintRunnerRuntimeToken(auth: ComponentToken, createdBy:
|
|
|
149
167
|
});
|
|
150
168
|
if (token.constraints?.orchestrators?.includes("")) return { error: "the agent's host is unknown, so its host pin cannot be kept" };
|
|
151
169
|
if (runnerGrantKey(previous.scope, previous.constraints) === runnerGrantKey(token.scope, token.constraints)) return { unchanged: true, jti: previous.jti };
|
|
152
|
-
const handoff = await
|
|
170
|
+
const handoff = await handoffRunnerTokenAsync(auth.sub, { jti: previous.jti, allowedProfileIds: ["provider-agent"], successor: token, createdBy });
|
|
171
|
+
if (mintRateLimited(handoff)) return { error: "runner token mint rate limited", retryAfterSeconds: handoff.retryAfterSeconds };
|
|
153
172
|
if (!handoff) return { error: "the presented token was already rotated" };
|
|
154
173
|
const { record } = handoff;
|
|
155
174
|
return {
|
|
@@ -306,6 +325,7 @@ function runnerTokenSpec(input: RunnerRuntimeTokenInput): { token: CreateTokenIn
|
|
|
306
325
|
: { kind: "root", ...(authority.kind === "delegated" ? { launcherScopes: authority.callerScopes } : {}), ...(profile ? { profile } : {}) } };
|
|
307
326
|
}
|
|
308
327
|
|
|
328
|
+
/** Sync twin for in-process tests; every runtime launch path mints through the writer op below. */
|
|
309
329
|
export function issueRunnerRuntimeToken(input: RunnerRuntimeTokenInput): RuntimeTokenResult {
|
|
310
330
|
const spec = runnerTokenSpec(input);
|
|
311
331
|
const issued = createToken(spec.token);
|
|
@@ -314,6 +334,11 @@ export function issueRunnerRuntimeToken(input: RunnerRuntimeTokenInput): Runtime
|
|
|
314
334
|
return issued;
|
|
315
335
|
}
|
|
316
336
|
|
|
337
|
+
export function issueRunnerRuntimeTokenAsync(input: RunnerRuntimeTokenInput): Promise<RuntimeTokenResult> {
|
|
338
|
+
const spec = runnerTokenSpec(input);
|
|
339
|
+
return createRunnerTokenAsync(spec.token, spec.launchFact ? { agentId: input.agentId, fact: spec.launchFact } : undefined);
|
|
340
|
+
}
|
|
341
|
+
|
|
317
342
|
export function issueInteractiveRunnerRuntimeToken(input: {
|
|
318
343
|
provider: string;
|
|
319
344
|
cwd: string;
|
|
@@ -393,7 +418,14 @@ export function issueIntegrationRuntimeToken(input: {
|
|
|
393
418
|
}
|
|
394
419
|
|
|
395
420
|
export function runnerRuntimeTokenEnv(input: RunnerRuntimeTokenInput): Record<string, string> {
|
|
396
|
-
|
|
421
|
+
return runnerTokenEnv(input, issueRunnerRuntimeToken(input));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export async function runnerRuntimeTokenEnvAsync(input: RunnerRuntimeTokenInput): Promise<Record<string, string>> {
|
|
425
|
+
return runnerTokenEnv(input, await issueRunnerRuntimeTokenAsync(input));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function runnerTokenEnv(input: RunnerRuntimeTokenInput, issued: RuntimeTokenResult): Record<string, string> {
|
|
397
429
|
return writeChildEnv({}, {
|
|
398
430
|
AGENT_RELAY_TOKEN: issued.token,
|
|
399
431
|
AGENT_RELAY_TOKEN_JTI: issued.record.jti,
|
package/src/security.ts
CHANGED
|
@@ -233,6 +233,8 @@ export function isOrchestratorRegistrationAuthorized(req: Request): boolean {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
const SHARED_UI_ROUTE_SCOPES: Record<string, string> = {
|
|
236
|
+
"/api/orchestrators/bootstrap": "token:write",
|
|
237
|
+
"/api/orchestrators/bootstrap/exchange": "token:write",
|
|
236
238
|
"/api/claude-api-budget/summary": "agent:read",
|
|
237
239
|
"/api/claude-api-budget": "system:admin",
|
|
238
240
|
"/api/stats": "stats:read",
|
|
@@ -252,6 +254,15 @@ function sharedUiRouteScope(pathname: string, method: string): string | undefine
|
|
|
252
254
|
if (/^\/api\/api-budgets\/[^/]+\/summary$/.test(pathname)) return "agent:read";
|
|
253
255
|
if (/^\/api\/api-budgets\/[^/]+$/.test(pathname)) return "system:admin";
|
|
254
256
|
if (/^\/api\/shipping\/incidents\/[^/]+\/abandon$/.test(pathname)) return "command:write";
|
|
257
|
+
if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
|
|
258
|
+
if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
|
|
259
|
+
if (pathname === "/api/decisions" && method === "POST") return "teams:write"; // #2914: filing = the relay_decision scope
|
|
260
|
+
if (pathname.startsWith("/api/decisions")) return method === "GET" ? "teams:read" : "message:send"; // #2885: answering = messaging the coordinator
|
|
261
|
+
if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
|
|
262
|
+
if (pathname.startsWith("/api/issue-associations")) return method === "GET" ? "issue:read" : "issue:write";
|
|
263
|
+
if (pathname.startsWith("/api/issue-activity")) return method === "GET" ? "issue:read" : "issue:write";
|
|
264
|
+
if (pathname.startsWith("/api/issue-lifecycle")) return method === "GET" ? "issue:read" : "issue:write";
|
|
265
|
+
if (pathname.startsWith("/api/issue-refs")) return method === "GET" ? "issue:read" : "issue:write";
|
|
255
266
|
return Object.hasOwn(SHARED_UI_ROUTE_SCOPES, pathname) ? SHARED_UI_ROUTE_SCOPES[pathname] : undefined;
|
|
256
267
|
}
|
|
257
268
|
|
|
@@ -266,8 +277,6 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
|
|
|
266
277
|
if (pathname === "/api/route") return "agent:read";
|
|
267
278
|
if (pathname === "/api/notifications/preview") return "notifications:read";
|
|
268
279
|
if (pathname === "/api/mcp") return "mcp:use";
|
|
269
|
-
if (pathname === "/api/orchestrators/bootstrap/exchange") return "token:write";
|
|
270
|
-
if (pathname === "/api/orchestrators/bootstrap") return "token:write";
|
|
271
280
|
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/logs\//)) return "logs:read";
|
|
272
281
|
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/terminal\/[^/]+\/input$/)) return "terminal:write";
|
|
273
282
|
if (pathname.match(/^\/api\/orchestrators\/[^/]+\/terminal\//)) return "terminal:read";
|
|
@@ -323,16 +332,8 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
|
|
|
323
332
|
if (pathname.match(/^\/api\/provider-quotas\/[^/]+\/reset-fired$/)) return "quota:write";
|
|
324
333
|
if (pathname === "/api/provider-quotas") return method === "GET" ? "agent:read" : "quota:write";
|
|
325
334
|
if (pathname === "/api/routing-policy") return method === "GET" ? "agent:read" : "settings:write:routing";
|
|
326
|
-
if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
|
|
327
335
|
if (pathname === "/api/settings/prompts" || pathname.startsWith("/api/settings/prompts/")) return method === "GET" ? "agent:read" : "system:admin";
|
|
328
|
-
if (
|
|
329
|
-
if (pathname === "/api/decisions" && method === "POST") return "teams:write"; // #2914: filing = the relay_decision scope
|
|
330
|
-
if (pathname.startsWith("/api/decisions")) return method === "GET" ? "teams:read" : "message:send"; // #2885: answering = messaging the coordinator
|
|
331
|
-
if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
|
|
332
|
-
if (pathname.startsWith("/api/issue-associations")) return method === "GET" ? "issue:read" : "issue:write";
|
|
333
|
-
if (pathname.startsWith("/api/issue-activity")) return method === "GET" ? "issue:read" : "issue:write";
|
|
334
|
-
if (pathname.startsWith("/api/issue-lifecycle")) return method === "GET" ? "issue:read" : "issue:write";
|
|
335
|
-
if (pathname.startsWith("/api/issue-refs")) return method === "GET" ? "issue:read" : "issue:write";
|
|
336
|
+
if (/^\/api\/projects\/[^/]+\/profile-instructions(?:\/|$)/.test(pathname)) return method === "GET" ? "agent:read" : "agent:write";
|
|
336
337
|
if (pathname.startsWith("/api/projects")) return method === "GET" ? "project:read" : "project:write";
|
|
337
338
|
// Insights config (the feature toggle) stays admin-only via the default; only the
|
|
338
339
|
// mechanical observation feed is writable by lower-privilege callers.
|
|
@@ -466,7 +467,6 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
|
|
|
466
467
|
if (pathname === "/api/routing-policy") {
|
|
467
468
|
return method === "GET" ? "agent:read" : "settings:write:routing";
|
|
468
469
|
}
|
|
469
|
-
if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
|
|
470
470
|
if (pathname === "/api/settings/prompts" || pathname.startsWith("/api/settings/prompts/")) {
|
|
471
471
|
return method === "GET" ? "agent:read" : versionedPromptsEnabled() ? "agent:write" : "system:admin";
|
|
472
472
|
}
|
|
@@ -477,16 +477,9 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
|
|
|
477
477
|
if (pathname.startsWith("/api/workflows")) return method === "GET" ? "task:read" : "task:write";
|
|
478
478
|
if (pathname.startsWith("/api/orchestrators")) return method === "GET" ? "agent:read" : "command:write";
|
|
479
479
|
if (pathname.startsWith("/api/plans")) return method === "GET" ? "plans:read" : "plans:write";
|
|
480
|
-
if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
|
|
481
|
-
if (pathname === "/api/decisions" && method === "POST") return "teams:write"; // #2914: filing = the relay_decision scope
|
|
482
|
-
if (pathname.startsWith("/api/decisions")) return method === "GET" ? "teams:read" : "message:send"; // #2885: answering = messaging the coordinator
|
|
483
|
-
if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
|
|
484
|
-
if (pathname.startsWith("/api/issue-associations")) return method === "GET" ? "issue:read" : "issue:write";
|
|
485
|
-
if (pathname.startsWith("/api/issue-activity")) return method === "GET" ? "issue:read" : "issue:write";
|
|
486
|
-
if (pathname.startsWith("/api/issue-lifecycle")) return method === "GET" ? "issue:read" : "issue:write";
|
|
487
|
-
if (pathname.startsWith("/api/issue-refs")) return method === "GET" ? "issue:read" : "issue:write";
|
|
488
480
|
// Fail-closed default returns system:admin, so this case is load-bearing: without it
|
|
489
481
|
// every /api/projects call from a scoped component token would 403-blackhole (#405).
|
|
482
|
+
if (/^\/api\/projects\/[^/]+\/profile-instructions(?:\/|$)/.test(pathname)) return method === "GET" ? "agent:read" : "agent:write";
|
|
490
483
|
if (pathname.startsWith("/api/projects")) return method === "GET" ? "project:read" : "project:write";
|
|
491
484
|
// The Runner posts the #184 context-gathering signal here (source:"server") via its
|
|
492
485
|
// provider token. Without this case the path fell through to the system:admin default
|
package/src/server-background.ts
CHANGED
|
@@ -19,6 +19,6 @@ export function startServerBackgroundJobs(): void {
|
|
|
19
19
|
{ name: "connectors", start: startConnectorStatusPoller },
|
|
20
20
|
{ name: "maintenance", start: startMaintenanceScheduler },
|
|
21
21
|
{ name: "steward-rekey-notices", start: async () => (await import("./workspace-rekey")).drainStewardRekeyNotices() },
|
|
22
|
-
{ name: "deploy-notice", start: () => { if (!isEphemeralMode()) notifyCdDeployLive(); } },
|
|
22
|
+
{ name: "deploy-notice", start: async () => { if (!isEphemeralMode()) await notifyCdDeployLive(); } },
|
|
23
23
|
]);
|
|
24
24
|
}
|
package/src/server.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { inviteLinkRedirect } from "./invite-link";
|
|
|
7
7
|
import { claudeAppBoundary, CLAUDE_APP_PATH, isClaudeAppPath } from "./claude-app-auth";
|
|
8
8
|
import { handleClaudeApp } from "./claude-app-gateway";
|
|
9
9
|
import { handleClaudeAppOAuth } from "./claude-app/oauth-http";
|
|
10
|
+
import { installTimerIngressSchema } from "./db/timer-ingress";
|
|
11
|
+
import { beginMainWriteBootPhase, endMainWriteBootPhase } from "./db/main-connection-writes";
|
|
10
12
|
import { markServerBound, timePostBindPhase } from "./post-bind-startup";
|
|
11
13
|
import type { ServerWebSocket } from "bun";
|
|
12
14
|
import { beginStartupPhase, enableStartupTiming, timeStartupPhase } from "./startup-timing";
|
|
@@ -24,7 +26,6 @@ import { reconcileAutomationRuns, runDueAutomationsAsync, type AutomationDispatc
|
|
|
24
26
|
import { getLifecycleManager } from "./lifecycle-manager";
|
|
25
27
|
import { reconcileStewardPolicyModels } from "./steward-policy-model-reconcile";
|
|
26
28
|
import { getConfig, setConfig } from "./config-store";
|
|
27
|
-
import { reconcileProviderModelSeed } from "./provider-model-seed";
|
|
28
29
|
export { reconcileProviderModelSeed } from "./provider-model-seed";
|
|
29
30
|
import { ensureEscalationChannelRegistered } from "./connectors";
|
|
30
31
|
import { installNotificationSubscriptionRule } from "./notification-subscription-rules";
|
|
@@ -111,6 +112,7 @@ export async function startServer(): Promise<void> {
|
|
|
111
112
|
startArtifactCheckoutLease();
|
|
112
113
|
|
|
113
114
|
assertSafeNetworkConfig(HOST);
|
|
115
|
+
beginMainWriteBootPhase();
|
|
114
116
|
timeStartupPhase("server.database", () => initDb(DB_PATH));
|
|
115
117
|
// #1960 T2 — stand the gateway up EXPLICITLY here rather than leaving it to the lazy self-init
|
|
116
118
|
// (`getGateway()` on first call, gateway.ts), which would race the rest of this boot sequence.
|
|
@@ -136,7 +138,6 @@ export async function startServer(): Promise<void> {
|
|
|
136
138
|
installNotificationSubscriptionRule();
|
|
137
139
|
startPlanLiveBindings();
|
|
138
140
|
reconcileRelayVersion();
|
|
139
|
-
if (!isEphemeralMode()) reconcileProviderModelSeed();
|
|
140
141
|
// Register the ntfy escalation channel row from persisted connector config BEFORE the
|
|
141
142
|
// warning check, so a correctly-configured relay doesn't spuriously warn while the async
|
|
142
143
|
// channels-host connect is still in flight (#1510).
|
|
@@ -174,7 +175,9 @@ export async function startServer(): Promise<void> {
|
|
|
174
175
|
}, AUTOMATION_INTERVAL_MS);
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
installTimerIngressSchema();
|
|
177
179
|
finishServices();
|
|
180
|
+
endMainWriteBootPhase();
|
|
178
181
|
timeStartupPhase("server.bind", () => Bun.serve<RelaySocketData>({
|
|
179
182
|
port: PORT,
|
|
180
183
|
hostname: HOST,
|
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
// 2. the §4.4 in-memory last-hour series, ONLY once the durable write landed, so the Monitor
|
|
12
12
|
// chart can never show a point the Fleet card cannot corroborate;
|
|
13
13
|
// 3. #2822 — the one operator page a gate-capacity transition owes, decided by that same write.
|
|
14
|
+
// 4. #3762 — the one low-disk page (ntfy + coordinator) a disk level change owes, same write.
|
|
14
15
|
import type { HostDaemonSample, HostMachineSample } from "@humwick/sdk";
|
|
15
16
|
import { recordHostUtilizationAsync } from "../db";
|
|
16
17
|
import { recordHostUtilizationSample } from "../host/utilization-buffer";
|
|
17
18
|
import { composeGateCapacityNotice, type GateCapacityAlert } from "../host/gate-capacity-alert";
|
|
19
|
+
import { composeDiskNotice, type DiskAlert } from "../host/disk-alert";
|
|
20
|
+
import { stewardFallbackTarget } from "../config";
|
|
21
|
+
import { notifyTarget } from "../maintenance/events";
|
|
18
22
|
import { notifyWorkspaceHumanEscalation } from "../workspace-human-escalation";
|
|
19
23
|
|
|
20
24
|
export interface RecordHostUtilizationInput {
|
|
@@ -52,9 +56,28 @@ export async function recordHostUtilization(input: RecordHostUtilizationInput):
|
|
|
52
56
|
}
|
|
53
57
|
recordHostUtilizationSample(input.hostId, input.machine, input.now ?? Date.now());
|
|
54
58
|
if (recorded?.gateAlert) notifyGateCapacity(input.hostId, recorded.hostLabel ?? input.hostId, recorded.gateAlert, input.now ?? Date.now());
|
|
59
|
+
if (recorded?.diskAlert) notifyDisk(input.hostId, recorded.hostLabel ?? input.hostId, recorded.diskAlert);
|
|
55
60
|
return true;
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
/** Operator ntfy plus the steward/coordinator target — the same two recipients as the critical-job watchdog. */
|
|
64
|
+
function notifyDisk(hostId: string, label: string, alert: DiskAlert): void {
|
|
65
|
+
const notice = composeDiskNotice(hostId, label, alert);
|
|
66
|
+
const payload = { ...alert, kind: `host-disk.${alert.kind}`, hostId };
|
|
67
|
+
const human = notifyWorkspaceHumanEscalation({
|
|
68
|
+
subject: notice.subject,
|
|
69
|
+
body: notice.body,
|
|
70
|
+
priority: notice.priority,
|
|
71
|
+
tags: ["agent-relay", "host-disk"],
|
|
72
|
+
source: "host-disk",
|
|
73
|
+
payload,
|
|
74
|
+
idempotencyKey: notice.idempotencyKey,
|
|
75
|
+
});
|
|
76
|
+
const target = stewardFallbackTarget();
|
|
77
|
+
const routed = target ? notifyTarget("maintenance.degraded", {}, target, notice.subject, notice.body, payload, notice.idempotencyKey) : null;
|
|
78
|
+
console.warn(`[relay] ${notice.subject}${human.accepted || routed ? "" : " — nobody was notified (no ntfy channel or steward target)"}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
function notifyGateCapacity(hostId: string, label: string, alert: GateCapacityAlert, now: number): void {
|
|
59
82
|
const notice = composeGateCapacityNotice(hostId, label, alert, now);
|
|
60
83
|
const result = notifyWorkspaceHumanEscalation({
|
|
@@ -3,8 +3,8 @@ import { isTerminalTaskStatus } from "./task-entity";
|
|
|
3
3
|
import { errMessage } from "@humwick/sdk";
|
|
4
4
|
import { coordinatedTeamIds } from "../db/coordinator-seat";
|
|
5
5
|
import { teamEpicWork } from "../db/team-epics";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { issueTrackerNotFoundCount, getIssueHierarchy } from "../db/long-running-work";
|
|
7
|
+
import { putIssueHierarchyAsync, recordIssueRefLiveStatusAsync, recordIssueTrackerNotFoundAsync } from "../db/issue-refs-async";
|
|
8
8
|
import { resolveIssueTrackerAdapter } from "../issue-tracker";
|
|
9
9
|
import { GitHubApiError } from "../issue-tracker/github";
|
|
10
10
|
import { routeNotification } from "../notification-router";
|
|
@@ -41,14 +41,13 @@ export async function refreshTeamEpicHierarchy(teamId: string, signal?: AbortSig
|
|
|
41
41
|
const live = await adapter.getStatus(issue);
|
|
42
42
|
if (signal?.aborted) break;
|
|
43
43
|
statusLoaded = true;
|
|
44
|
-
|
|
45
|
-
updateIssueRef(issue.id, { status: live.status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() });
|
|
44
|
+
await recordIssueRefLiveStatusAsync(issue.id, { status: live.status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() });
|
|
46
45
|
if (live.status !== "open") continue;
|
|
47
46
|
const children = adapter.listSubIssues ? await adapter.listSubIssues(issue) : await adapter.listOpenSubIssues?.(issue);
|
|
48
|
-
if (children)
|
|
47
|
+
if (children) await putIssueHierarchyAsync(issue.id, children);
|
|
49
48
|
} catch (error) {
|
|
50
49
|
if (!statusLoaded && error instanceof GitHubApiError && error.statusCode === 404
|
|
51
|
-
&&
|
|
50
|
+
&& await recordIssueTrackerNotFoundAsync(issue.id) >= 2) {
|
|
52
51
|
reportUnresolvable(teamId, issue);
|
|
53
52
|
continue;
|
|
54
53
|
}
|