@humwick/server 0.130.2 → 0.130.4
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/bundled/prompts/manifest.json +1 -0
- package/bundled/prompts/task.hydration.md +1 -1
- package/bundled/roles/builder.md +1 -1
- package/docs/guides/sub-coordinators.md +12 -5
- package/docs/guides/tools-coordination.md +1 -0
- package/docs/guides/workflows.md +16 -0
- package/docs/openapi.json +175 -4
- package/package.json +8 -7
- package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-BOkmEvgZ.js} +2 -2
- package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-BkaDJp0r.js} +2 -2
- package/public/assets/{TraceView-Wimm0Czv.js → TraceView-B99Uu2yT.js} +2 -2
- package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DUIfTTFF.js} +2 -2
- package/public/assets/{activity-CrSurn-r.js → activity-ZIX0N1El.js} +2 -2
- package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-kq9TPYFw.js} +2 -2
- package/public/assets/agent-liveness-DSQY1Aa_.js +2 -0
- package/public/assets/{agent-name-DKbiIhBo.js → agent-name-C_7F4DFR.js} +2 -2
- package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-BNOcrGcY.js} +2 -2
- package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-B6yAh70a.js} +2 -2
- package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-CcrhmOzF.js} +2 -2
- package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CXSLrKiq.js} +2 -2
- package/public/assets/{agents-B-v66C-4.js → agents-DIZG97_X.js} +2 -2
- package/public/assets/{analytics-BqCb-LKi.js → analytics-rasmoPjR.js} +2 -2
- package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-tBpTrEPb.js} +2 -2
- package/public/assets/{artifacts-DJevXcvg.js → artifacts-BNkrynP1.js} +2 -2
- package/public/assets/{artifacts-D32zyw4Y.js → artifacts-Bjq-ZyBA.js} +2 -2
- package/public/assets/{automation-BpRajx_5.js → automation-Cx422L3L.js} +2 -2
- package/public/assets/{backlog-D_UjTdzX.js → backlog-RuFQ8PUu.js} +2 -2
- package/public/assets/{busy-health-N3VEh4ao.js → busy-health-BvNQ9g5x.js} +2 -2
- package/public/assets/{channels-9jopungq.js → channels-DNbKAS0I.js} +2 -2
- package/public/assets/{chat-CSXrTB3C.js → chat-Bp3Fx73h.js} +3 -3
- package/public/assets/{code-preview-p1NnmqK0.js → code-preview-KIRZA6cz.js} +2 -2
- package/public/assets/{connectors-CZdqlDNL.js → connectors-k7vO4vvc.js} +2 -2
- package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BuVoq1aw.js} +2 -2
- package/public/assets/{crate-agent-30aXTKva.js → crate-agent-irqlt4NH.js} +2 -2
- package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-D_rurNBz.js} +2 -2
- package/public/assets/{deploys-DoN6bAb5.js → deploys-yzZqIpxE.js} +2 -2
- package/public/assets/{display-BrKWFGJz.js → display-BemJG1f_.js} +2 -2
- package/public/assets/{files-D6XEu8Ng.js → files-DXWcrxOz.js} +2 -2
- package/public/assets/{fleet-C7jQWbw2.js → fleet-TEuOCZXE.js} +2 -2
- package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BGoDAFI-.js} +2 -2
- package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-BB9eq9nN.js} +2 -2
- package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-qy2nTD_1.js} +3 -3
- package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-BGPHxuwT.js} +2 -2
- package/public/assets/{index-CRHep3M9.js → index-BC5XZc0z.js} +5 -5
- package/public/assets/{index-MYAIwYTX.css → index-D8Rp9pz2.css} +1 -1
- package/public/assets/{insights-CzrtkpOE.js → insights-C-KQG0h-.js} +2 -2
- package/public/assets/{integrations-aVsUmigt.js → integrations-ClPRPyKU.js} +2 -2
- package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-DbH8J9Ak.js} +2 -2
- package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-PH4zVn24.js} +2 -2
- package/public/assets/{maintenance-NfjqFZAk.js → maintenance-5lV43uTL.js} +2 -2
- package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-BdgU35aX.js} +2 -2
- package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-DNT4OUwQ.js} +3 -3
- package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CnCcPy0A.js} +2 -2
- package/public/assets/{memory-DNsCl6UX.js → memory--GgWH1gH.js} +2 -2
- package/public/assets/{messages-DopU1L1X.js → messages-CzFl_97O.js} +2 -2
- package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-CVvJVkzv.js} +2 -2
- package/public/assets/overview-DsrZvE86.js +2 -0
- package/public/assets/{pairs-DE_m6rrE.js → pairs-D2d0d2py.js} +2 -2
- package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-_SMuSbYF.js} +2 -2
- package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-Apn15y43.js} +2 -2
- package/public/assets/{projects-Br2Bd2h1.js → projects-D3B6kto5.js} +2 -2
- package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-DnPB6Ez6.js} +2 -2
- package/public/assets/provider-display-bWwiuedV.js +2 -0
- package/public/assets/{registry-Cw38_6gW.js → registry-D790cIhN.js} +2 -2
- package/public/assets/run-card-BCro-Odj.js +2 -0
- package/public/assets/{secrets-DxI-vzSq.js → secrets-CVXvfvbU.js} +2 -2
- package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-D3Xxqb9w.js} +2 -2
- package/public/assets/{settings-CKZTVGIx.js → settings-CoUC7Jci.js} +2 -2
- package/public/assets/shipping-D_Lp9n_J.js +2 -0
- package/public/assets/{stats-DyUBKyWI.js → stats-CXiNjB9S.js} +2 -2
- package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-C703YIy1.js} +2 -2
- package/public/assets/store-BgDALGIX.js +9 -0
- package/public/assets/{tasks-Bh7Chr4-.js → tasks-DO0WpJCx.js} +2 -2
- package/public/assets/{teams-bowI7qVY.js → teams-FGkdi_tL.js} +2 -2
- package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-Dh8dSC7e.js} +3 -3
- package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-B5Zriip3.js} +2 -2
- package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-CRUVDvLC.js} +2 -2
- package/public/assets/{use-decisions-samMQhQf.js → use-decisions-CoaqRh4l.js} +2 -2
- package/public/assets/{use-project-list-59LheVP7.js → use-project-list-X-BgT53X.js} +2 -2
- package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-0X_UZkk-.js} +2 -2
- package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-CkLhVBGu.js} +2 -2
- package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-BkTi7eim.js} +2 -2
- package/public/assets/{work-queue-CSvpuNLz.js → work-queue-DDOLEyKo.js} +2 -2
- package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-CzOsB8BM.js} +2 -2
- package/public/assets/{workflows-DdmxRSyg.js → workflows-DJilvmRM.js} +2 -2
- package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CHcfbLR3.js} +2 -2
- package/public/cosmos.html +4 -4
- package/public/index.html +29 -29
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapters/claude-api.ts +1 -1
- package/runner/src/adapters/claude-headless.ts +1 -1
- package/runner/src/adapters/claude-launch-exit.ts +26 -0
- package/runner/src/adapters/claude-session-probe.ts +1 -1
- package/runner/src/adapters/claude-tmux.ts +38 -10
- package/runner/src/adapters/claude.ts +28 -75
- package/runner/src/adapters/provider-quota-harvest.ts +20 -71
- package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
- package/runner/src/agent-runner/liveness-exchange.ts +104 -0
- package/runner/src/claude-system-prompt.ts +49 -0
- package/runner/src/launch-assembly.ts +14 -10
- package/runner/src/profile-home.ts +1 -1
- package/runner/src/project-instructions.ts +2 -2
- package/runner/src/provider-command.ts +18 -0
- package/runner/src/provider-setup.ts +3 -2
- package/runner/src/runner-core.ts +14 -15
- package/runner/src/runner-helpers.ts +0 -8
- package/runner/src/session-scratch.ts +6 -2
- package/runner/test-support/provisioning-context.ts +4 -2
- package/runner/test-support/runner-harness.ts +2 -1
- package/scripts/steward-policy-cleanup.ts +2 -2
- package/src/agent-authz.ts +2 -2
- package/src/agent-drive-arbiter.ts +3 -8
- package/src/agent-exit-cleanup-async.ts +2 -2
- package/src/agent-exit-cleanup.ts +23 -19
- package/src/agent-lifecycle-events.ts +112 -85
- package/src/agent-ref.ts +3 -2
- package/src/agent-status-events.ts +43 -14
- package/src/auth/login-link.ts +35 -0
- package/src/auth/password.ts +4 -3
- package/src/auth/session-refresh.ts +1 -1
- package/src/auth/session.test-fixture.ts +2 -2
- package/src/auth/session.ts +7 -7
- package/src/automations/crud.ts +8 -81
- package/src/automations/dispatch.ts +33 -58
- package/src/automations/index.ts +1 -1
- package/src/automations/persistence.ts +65 -0
- package/src/automations/precheck.ts +18 -16
- package/src/automations/read-async.ts +11 -0
- package/src/automations/reconcile.ts +30 -39
- package/src/automations/storage.ts +33 -10
- package/src/automations/write-async.ts +33 -0
- package/src/branch-land-notice.ts +3 -3
- package/src/branch-landed.ts +25 -23
- package/src/bus.ts +21 -19
- package/src/cd-deploy-notify.ts +2 -2
- package/src/ci/notifications.ts +4 -4
- package/src/ci/root-missing.ts +3 -3
- package/src/ci/transition-notify.ts +5 -5
- package/src/claude-app/oauth-http.ts +3 -3
- package/src/cli/index.ts +7 -1
- package/src/cli/init.ts +53 -0
- package/src/cli/orchestrator.ts +1 -7
- package/src/cli/setup.ts +6 -4
- package/src/cli/workflow.ts +12 -3
- package/src/commands-db.ts +4 -4
- package/src/commands-write.ts +8 -0
- package/src/compaction-watch.ts +10 -8
- package/src/config-store-write.ts +2 -2
- package/src/config.ts +4 -11
- package/src/connectors.ts +10 -8
- package/src/context-advisory.ts +22 -21
- package/src/db/agent-action-write.ts +36 -0
- package/src/db/agent-control-async.ts +9 -0
- package/src/db/agent-control-write.ts +49 -0
- package/src/db/agent-effort-observe.ts +22 -0
- package/src/db/agent-meta.ts +14 -3
- package/src/db/agents-async.ts +8 -5
- package/src/db/agents.ts +15 -12
- package/src/db/auth-identities.ts +2 -2
- package/src/db/automation-read-ops.ts +11 -0
- package/src/db/automation-write-ops.ts +46 -0
- package/src/db/blackboard.ts +2 -2
- package/src/db/browser-session-write.ts +32 -0
- package/src/db/bus-outbox-write-ops.ts +3 -1
- package/src/db/checkout-refresh-schema.ts +47 -0
- package/src/db/checkout-refresh.ts +5 -5
- package/src/db/command-agent-profile-backfill.ts +39 -0
- package/src/db/command-agent-profiles.ts +5 -1
- package/src/db/command-retention.ts +126 -0
- package/src/db/commands-planner-stats.test-fixture.ts +8 -0
- package/src/db/commands-read-ops.ts +16 -0
- package/src/db/connection.ts +1 -182
- package/src/db/context-carry-write.ts +74 -0
- package/src/db/continuations.ts +4 -4
- package/src/db/db-error-registry.ts +12 -0
- package/src/db/gateway.ts +26 -133
- package/src/db/history-retention-ops.ts +13 -0
- package/src/db/inbox-async.ts +2 -2
- package/src/db/inbox-read-ops.ts +1 -1
- package/src/db/inbox.ts +9 -3
- package/src/db/issue-associations.ts +2 -2
- package/src/db/issue-refs-async.ts +4 -1
- package/src/db/issue-refs-read-ops.ts +6 -0
- package/src/db/issue-refs.ts +8 -8
- package/src/db/kernel-event-log-schema.ts +2 -0
- package/src/db/land-boundaries.ts +51 -5
- package/src/db/land-gate-attestations.ts +2 -2
- package/src/db/landing-receipts.ts +2 -2
- package/src/db/lifecycle-context-write.ts +5 -0
- package/src/db/lifecycle-tail-async.ts +76 -0
- package/src/db/lifecycle-tail-write-ops.ts +62 -0
- package/src/db/lifecycle-write-window.ts +2 -1
- package/src/db/lineage-pair-dedupe-sql.ts +3 -3
- package/src/db/main-connection-mode.ts +11 -0
- package/src/db/main-connection-writes.ts +20 -2
- package/src/db/maintenance-job-runs-async.ts +2 -2
- package/src/db/maintenance-job-write-ops.ts +1 -1
- package/src/db/maintenance-pauses.ts +2 -2
- package/src/db/message-send.ts +4 -0
- package/src/db/messages.ts +31 -25
- package/src/db/migrations.ts +18 -1
- package/src/db/notification-write-ops.ts +19 -0
- package/src/db/operator-answer-write.ts +19 -0
- package/src/db/orchestrators.ts +10 -4
- package/src/db/pairs-async.ts +22 -1
- package/src/db/pairs-write-ops.ts +8 -3
- package/src/db/pairs.ts +41 -48
- package/src/db/plan-dispatch-schema.ts +5 -1
- package/src/db/plan-ready-notices.ts +43 -0
- package/src/db/plan-tools-include-migration.ts +36 -0
- package/src/db/plans-write-ops.ts +2 -0
- package/src/db/presence-buffer.ts +5 -0
- package/src/db/presence-loop.ts +18 -23
- package/src/db/presence-write.ts +0 -6
- package/src/db/project-entries-async.ts +14 -0
- package/src/db/project-entries-read-ops.ts +5 -1
- package/src/db/project-entries-write-ops.ts +8 -0
- package/src/db/project-entries.ts +15 -12
- package/src/db/project-journal.ts +2 -2
- package/src/db/project-trust-approvals.ts +2 -2
- package/src/db/prompt-templates.ts +4 -4
- package/src/db/read-op-marshaling-fixtures.ts +18 -0
- package/src/db/read-ops.ts +4 -0
- package/src/db/retention.ts +13 -2
- package/src/db/review-publish-write.ts +8 -0
- package/src/db/runtime-admin-async.ts +19 -0
- package/src/db/runtime-admin-write-ops.ts +36 -0
- package/src/db/runtime-tail-async.ts +288 -0
- package/src/db/runtime-tail-write-ops.ts +97 -0
- package/src/db/schema.ts +4 -1
- package/src/db/spawn-budget.ts +5 -0
- package/src/db/spawn-delegation.ts +3 -12
- package/src/db/sql-audit-indexes.ts +3 -0
- package/src/db/stats-async.ts +5 -3
- package/src/db/task-entity.ts +5 -4
- package/src/db/team-claim-policy.ts +60 -0
- package/src/db/teams-async.ts +4 -0
- package/src/db/teams-write-ops.ts +4 -0
- package/src/db/terminal-events.ts +15 -0
- package/src/db/timer-cutover-bodies.ts +292 -0
- package/src/db/timer-cutover-core.ts +37 -278
- package/src/db/timer-cutover-state.ts +40 -0
- package/src/db/timer-cutover-write-ops.ts +1 -40
- package/src/db/token-db-write-ops.ts +2 -0
- package/src/db/token-standing-async.ts +12 -0
- package/src/db/token-standing.ts +6 -9
- package/src/db/token-usage-retention.ts +33 -0
- package/src/db/token-usage-write-ops.ts +1 -1
- package/src/db/token-usage.ts +4 -1
- package/src/db/user-invites-async.ts +13 -0
- package/src/db/user-invites.ts +3 -3
- package/src/db/users.ts +14 -6
- package/src/db/workflow-execution.ts +3 -3
- package/src/db/workspaces.ts +8 -5
- package/src/db/write-ops.ts +31 -12
- package/src/db/writer-fault-seam.ts +2 -9
- package/src/db/writer-fault-test-ops.ts +18 -0
- package/src/db/writer-gateway.ts +42 -46
- package/src/dev.ts +12 -16
- package/src/event-loop-lag.ts +4 -2
- package/src/event-loop-yield.ts +2 -2
- package/src/events.ts +10 -8
- package/src/host-provider-store.ts +5 -4
- package/src/hub-metrics-report.ts +5 -1
- package/src/hub-metrics.ts +38 -18
- package/src/init.ts +299 -0
- package/src/injection-sweep-pacing.ts +6 -8
- package/src/invite-link.ts +16 -9
- package/src/land-gate-notifications.ts +11 -10
- package/src/land-gate-queue-progress.ts +3 -3
- package/src/land-settle-dispatch.ts +52 -92
- package/src/lifecycle-context-pressure.ts +11 -10
- package/src/lifecycle-manager.ts +104 -98
- package/src/maintenance/breaker.ts +18 -16
- package/src/maintenance/checkout-refresh.ts +18 -1
- package/src/maintenance/critical-job-watchdog.ts +3 -3
- package/src/maintenance/events.ts +3 -3
- package/src/maintenance/history-retention-job.ts +30 -0
- package/src/maintenance/jobs.ts +29 -67
- package/src/maintenance/long-running-work.ts +3 -1
- package/src/maintenance/offline-agent-prune-job.ts +28 -0
- package/src/maintenance/orphaned-sessions.ts +5 -4
- package/src/maintenance/pauses.ts +4 -10
- package/src/maintenance/plan-ready-notice-job.ts +71 -0
- package/src/maintenance/scheduler.ts +18 -13
- package/src/maintenance/stalled-delivery-pager.ts +4 -4
- package/src/maintenance/teams.ts +19 -30
- package/src/maintenance/workspaces.ts +17 -27
- package/src/managed-policy-escalation.ts +7 -7
- package/src/managed-policy.ts +3 -3
- package/src/managed-provider-gate.ts +5 -4
- package/src/managed-spawn-reservation.ts +5 -4
- package/src/mcp/index.ts +8 -8
- package/src/mcp/output-descriptions.ts +1 -1
- package/src/mcp/presentation.ts +2 -1
- package/src/mcp/runtime-output.ts +6 -1
- package/src/mcp/self-care-visibility.ts +2 -2
- package/src/mcp/spawn-targets-matrix.ts +10 -1
- package/src/mcp/tool-groups.ts +3 -2
- package/src/mcp/tool-roles.ts +81 -0
- package/src/mcp/tools-workspace.ts +1 -1
- package/src/mcp-blackboard-tools.ts +6 -5
- package/src/mcp-decision-tools.ts +7 -7
- package/src/mcp-egress-tools.ts +10 -9
- package/src/mcp-issue-activity-tools.ts +2 -2
- package/src/mcp-issue-association-tools.ts +4 -4
- package/src/mcp-issue-ref-tools.ts +3 -3
- package/src/mcp-merge-tools.ts +5 -5
- package/src/mcp-notification-subscription-tools.ts +14 -15
- package/src/mcp-output-levels.test-fixture.ts +1 -1
- package/src/mcp-profile-trust-tools.ts +1 -1
- package/src/mcp-project-journal-tools.ts +4 -12
- package/src/mcp-project-tools.ts +17 -18
- package/src/mcp-routing-policy-tools.ts +3 -2
- package/src/mcp-team-tools.ts +6 -6
- package/src/mcp-workflow-tools.ts +5 -3
- package/src/memory-area-injection.ts +13 -12
- package/src/memory-boundary-capture.ts +6 -6
- package/src/memory-injection.ts +45 -43
- package/src/merge-pause-maintenance.ts +5 -4
- package/src/notification-router.ts +44 -17
- package/src/notify.ts +5 -5
- package/src/observed-reset-refresh.ts +4 -4
- package/src/operator-alarm.ts +2 -5
- package/src/project-ingest.ts +6 -6
- package/src/project-instructions.ts +8 -1
- package/src/project-trust.ts +12 -11
- package/src/prompt-resolver.ts +3 -3
- package/src/provider-quota-async.ts +5 -7
- package/src/provider-timeline-event.ts +4 -7
- package/src/quota-band-delivery.ts +2 -2
- package/src/recipe-runner.ts +4 -3
- package/src/retention-config-store.ts +4 -3
- package/src/reviewer-pipeline.ts +11 -10
- package/src/routes/_route.ts +4 -4
- package/src/routes/_shared.ts +13 -12
- package/src/routes/agent-effort.ts +5 -5
- package/src/routes/agent-heartbeat-quota.ts +23 -0
- package/src/routes/agent-keep.ts +1 -1
- package/src/routes/agent-reporting.ts +1 -1
- package/src/routes/agent-self-effort.ts +5 -5
- package/src/routes/agent-sessions.ts +24 -24
- package/src/routes/agents-spawn.ts +1 -1
- package/src/routes/agents.ts +17 -119
- package/src/routes/auth.ts +44 -17
- package/src/routes/authority-audit.ts +2 -2
- package/src/routes/automations.ts +6 -5
- package/src/routes/channel-binding-delete.ts +1 -1
- package/src/routes/channel-output.ts +3 -3
- package/src/routes/claude-api-budget.ts +5 -5
- package/src/routes/commands-listing.ts +1 -1
- package/src/routes/commands.ts +29 -27
- package/src/routes/context-resets.ts +2 -2
- package/src/routes/coordinator-teams.ts +1 -1
- package/src/routes/decisions.ts +2 -2
- package/src/routes/host-providers.ts +18 -0
- package/src/routes/host-secret-files.ts +1 -1
- package/src/routes/hosts.ts +11 -11
- package/src/routes/inbox.ts +3 -2
- package/src/routes/index.ts +7 -3
- package/src/routes/integrations.ts +9 -7
- package/src/routes/issue-activity.ts +2 -2
- package/src/routes/issue-associations.ts +4 -4
- package/src/routes/issue-refs.ts +3 -3
- package/src/routes/kernel-timers-operator.ts +1 -1
- package/src/routes/memory.ts +1 -1
- package/src/routes/merges.ts +11 -11
- package/src/routes/messages.ts +23 -17
- package/src/routes/orchestrator-bootstrap.ts +3 -1
- package/src/routes/orchestrator-host-registration.ts +2 -0
- package/src/routes/orchestrator-host-utilization.ts +2 -2
- package/src/routes/orchestrator-proxy.ts +2 -2
- package/src/routes/orchestrator.ts +27 -18
- package/src/routes/pairs.ts +8 -8
- package/src/routes/project-profile-instructions.ts +1 -1
- package/src/routes/project-trust.ts +1 -1
- package/src/routes/projects.ts +16 -16
- package/src/routes/prompt-templates.ts +12 -12
- package/src/routes/provider-quotas.ts +10 -10
- package/src/routes/run-command.ts +1 -1
- package/src/routes/secrets-backup.ts +4 -5
- package/src/routes/secrets-import.ts +3 -4
- package/src/routes/secrets-vault.ts +12 -14
- package/src/routes/settings.ts +1 -1
- package/src/routes/shipping.ts +9 -1
- package/src/routes/spawn-policy.ts +8 -7
- package/src/routes/sse.ts +2 -2
- package/src/routes/team-templates.ts +7 -7
- package/src/routes/teams.ts +10 -10
- package/src/routes/tokens.ts +18 -17
- package/src/routes/users.ts +24 -24
- package/src/routes/viewer-read.ts +3 -3
- package/src/routes/web-tool-calls.ts +4 -4
- package/src/routes/workflows.ts +2 -2
- package/src/routes/workspaces.ts +17 -16
- package/src/routing-policy-push.ts +6 -6
- package/src/runtime-grant.ts +17 -11
- package/src/runtime-install.ts +30 -0
- package/src/runtime-tokens.ts +20 -28
- package/src/secrets-broker/config.ts +1 -1
- package/src/secrets-broker/service.ts +2 -2
- package/src/security.ts +3 -1
- package/src/self-resume-carry.ts +21 -110
- package/src/self-resume-supersede.ts +4 -3
- package/src/self-resume.ts +54 -66
- package/src/server-background.ts +1 -1
- package/src/server.ts +25 -26
- package/src/services/agent-action.ts +12 -31
- package/src/services/agent-effort.ts +7 -32
- package/src/services/agent-hang-watchdog-job.ts +2 -2
- package/src/services/agent-heartbeat.ts +166 -0
- package/src/services/agent-reporting.ts +1 -1
- package/src/services/auth-context.ts +6 -0
- package/src/services/browser-inventory.ts +3 -3
- package/src/services/browser-session-state.ts +3 -2
- package/src/services/browser-session.ts +21 -27
- package/src/services/bus-command-update.ts +9 -9
- package/src/services/checkout-refresh-state.ts +1 -0
- package/src/services/checkout-refresh.test-fixture.ts +3 -2
- package/src/services/command-host.ts +7 -2
- package/src/services/coordinator-digest.ts +3 -2
- package/src/services/dispatch-command.ts +12 -13
- package/src/services/effective-launch-driver.ts +14 -9
- package/src/services/host-changed-paths.test-fixture.ts +5 -6
- package/src/services/host-project-readiness.ts +2 -2
- package/src/services/host-providers.ts +1 -1
- package/src/services/host-utilization.ts +9 -9
- package/src/services/idle-agent-digest.ts +17 -12
- package/src/services/idle-epics.ts +4 -4
- package/src/services/interrupt-agent.ts +12 -9
- package/src/services/issue-activity.ts +3 -3
- package/src/services/issue-associations.ts +15 -18
- package/src/services/issue-lifecycle-comments.ts +3 -2
- package/src/services/issue-lifecycle.ts +14 -13
- package/src/services/issue-refs.ts +4 -5
- package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
- package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
- package/src/services/kernel-timers-ingress.ts +3 -3
- package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
- package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
- package/src/services/kernel-timers-outbox.ts +1 -1
- package/src/services/kernel-timers-stage10-operator.ts +1 -1
- package/src/services/kernel-timers.ts +4 -3
- package/src/services/land-gate-placement.ts +11 -2
- package/src/services/land-issue-relations.ts +5 -4
- package/src/services/land-restart-retry.ts +7 -6
- package/src/services/landed-ancestry.ts +6 -5
- package/src/services/legacy-landing-host.ts +4 -1
- package/src/services/legacy-landing-recovery.ts +8 -6
- package/src/services/managed-running.ts +17 -17
- package/src/services/memory-areas.ts +3 -3
- package/src/services/merge-pause.ts +9 -15
- package/src/services/operator-decisions.ts +37 -41
- package/src/services/orchestrator-registration-recovery.ts +6 -2
- package/src/services/orchestrator-upgrade.ts +14 -12
- package/src/services/permission-decision.ts +4 -3
- package/src/services/plan-ci-notice.ts +1 -1
- package/src/services/plan-conformance.ts +4 -4
- package/src/services/plan-dispatch-policy.ts +10 -3
- package/src/services/plan-dispatch.ts +35 -14
- package/src/services/plan-graph.ts +39 -261
- package/src/services/plan-import.ts +1 -1
- package/src/services/plan-live-bindings.ts +8 -37
- package/src/services/plan-task-create.ts +1 -1
- package/src/services/plan-terminal-refs.ts +2 -12
- package/src/services/plan-wake-digest.ts +8 -3
- package/src/services/profile-trust.ts +2 -2
- package/src/services/project.ts +11 -10
- package/src/services/prompt-templates.ts +6 -5
- package/src/services/provider-api-key.ts +73 -0
- package/src/services/provider-hold-nudge.ts +9 -9
- package/src/services/provider-hold-record.ts +13 -12
- package/src/services/provider-state-audit.ts +19 -18
- package/src/services/provisioning-harness/driver.ts +1 -1
- package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
- package/src/services/provisioning-harness/lifecycle.ts +24 -22
- package/src/services/provisioning-harness/prepare.ts +16 -17
- package/src/services/rate-limit-resume.ts +10 -9
- package/src/services/red-main-auto-revert.ts +7 -5
- package/src/services/red-main-controller-deps.ts +20 -3
- package/src/services/red-main-incident.ts +3 -1
- package/src/services/red-main-quarantine.ts +2 -3
- package/src/services/register-agent.ts +22 -22
- package/src/services/restart-project-role.ts +1 -1
- package/src/services/restart-spawn.ts +17 -16
- package/src/services/review-publish.ts +7 -24
- package/src/services/review-ref-reconcile.ts +29 -38
- package/src/services/runtime-dependency-alert.ts +27 -0
- package/src/services/scheduler-command.ts +11 -9
- package/src/services/scheduler.ts +5 -5
- package/src/services/self-effort-grant.ts +5 -25
- package/src/services/send-message.ts +16 -33
- package/src/services/shutdown-agent.ts +17 -14
- package/src/services/spawn-agent.ts +12 -41
- package/src/services/spawn-chain-inspector.ts +1 -1
- package/src/services/spawn-cwd-placement.ts +21 -5
- package/src/services/spawn-issue-activity.ts +8 -6
- package/src/services/spawn-launch.ts +17 -1
- package/src/services/spawn-loss-escalation.ts +1 -1
- package/src/services/spawn-registration-watchdog.ts +2 -2
- package/src/services/spawn-repo-probe.ts +7 -5
- package/src/services/spawn-tree-cleanup.ts +7 -8
- package/src/services/steward-reaper.ts +4 -4
- package/src/services/task-context-hydration.ts +25 -1
- package/src/services/task-delivery.ts +20 -34
- package/src/services/task-entity.ts +40 -203
- package/src/services/task-issue-link.ts +13 -20
- package/src/services/task-issue-weave.ts +12 -11
- package/src/services/task-lifecycle.ts +27 -31
- package/src/services/task-repo-cwd.ts +37 -0
- package/src/services/task-review-hold.ts +4 -17
- package/src/services/task-semantic-events.ts +13 -11
- package/src/services/task-state-reconcile.ts +3 -4
- package/src/services/task-validation.ts +2 -2
- package/src/services/team-templates.ts +5 -5
- package/src/services/team.ts +31 -98
- package/src/services/terminal-provider-exit.ts +6 -5
- package/src/services/timer-message-fire-chaos-worker.ts +7 -3
- package/src/services/timer-message-fire-outbox.ts +1 -1
- package/src/services/transient-agent-reaper.ts +22 -19
- package/src/services/turn-reconcile-audit.ts +9 -9
- package/src/services/user-onboarding.ts +8 -6
- package/src/services/user-profile.ts +6 -9
- package/src/services/work-finality.ts +16 -4
- package/src/services/workflow-auto-start.ts +2 -1
- package/src/services/workflow-caller.ts +61 -4
- package/src/services/workflow-child-proof-worker.ts +1 -1
- package/src/services/workflow-control-proof-worker.ts +1 -1
- package/src/services/workflow-coordinator.ts +8 -2
- package/src/services/workflow-decisions.ts +5 -5
- package/src/services/workflow-deferred-cleanup.ts +1 -1
- package/src/services/workflow-effect-driver.ts +42 -19
- package/src/services/workflow-effects.ts +41 -27
- package/src/services/workflow-pins.ts +7 -4
- package/src/services/workflow-reconcile.ts +1 -0
- package/src/services/workflow-resume-pins.ts +3 -3
- package/src/services/workflow-routing-defaults.ts +18 -0
- package/src/services/workflow-run-view.ts +4 -3
- package/src/services/workflow-run.test-fixture.ts +1 -1
- package/src/services/workflow-runs.ts +34 -22
- package/src/services/workflow-supervisor-state.ts +21 -1
- package/src/services/workflow-supervisor.ts +4 -3
- package/src/services/workspace-recovery.ts +4 -3
- package/src/setup.ts +47 -8
- package/src/spawn-admission.ts +10 -2
- package/src/spawn-placement.ts +27 -9
- package/src/spawn-targets.ts +8 -8
- package/src/sse-agent-status.ts +15 -6
- package/src/sse.ts +49 -24
- package/src/startup-context-injection.ts +61 -57
- package/src/steward-handoff.ts +4 -3
- package/src/steward-policy-cleanup.ts +9 -8
- package/src/steward.ts +2 -2
- package/src/task-dispatch-live.ts +5 -2
- package/src/task-dispatcher.ts +2 -2
- package/src/token-db.ts +2 -2
- package/src/token-usage-async.ts +2 -4
- package/src/user-awaiting-reply.ts +5 -5
- package/src/utils.ts +7 -0
- package/src/workspace-actions.ts +24 -29
- package/src/workspace-auto-merge.ts +14 -12
- package/src/workspace-changed-paths-client.ts +16 -5
- package/src/workspace-cleanup-stale.ts +30 -7
- package/src/workspace-command-settle.ts +24 -16
- package/src/workspace-config.ts +1 -1
- package/src/workspace-conflict-resolver.ts +9 -7
- package/src/workspace-dead-owner-recovery.ts +5 -4
- package/src/workspace-escalation.ts +12 -11
- package/src/workspace-human-escalation.ts +3 -3
- package/src/workspace-land-gate-watchdog.ts +3 -3
- package/src/workspace-land-refusal.ts +20 -0
- package/src/workspace-land-stopped.ts +1 -0
- package/src/workspace-merge.ts +10 -26
- package/src/workspace-nothing-to-land-notice.ts +2 -2
- package/src/workspace-orphans.ts +18 -37
- package/src/workspace-pr-completion.ts +10 -8
- package/src/workspace-rekey.ts +3 -3
- package/src/workspace-steward-failure.ts +2 -2
- package/src/workspace-steward-wake.ts +2 -2
- package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
- package/public/assets/overview-C1DCTzzV.js +0 -2
- package/public/assets/provider-display-DsuWMvVL.js +0 -2
- package/public/assets/run-card-CpKqRbhg.js +0 -2
- package/public/assets/shipping-B9YtnwtG.js +0 -2
- package/public/assets/store-DcClXLH0.js +0 -9
- package/runner/src/agent-runner/http-liveness.ts +0 -85
- package/src/db/on-loop-write-transaction.ts +0 -34
package/src/routes/tokens.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { upsertIntegrationRegistryAsync, upsertTokenProfileAsync, updateTokenProfileAsync, deleteTokenProfileAsync } from "../db/runtime-tail-async";
|
|
1
2
|
// Auto-split from routes.ts (#299). Domain: tokens.
|
|
2
3
|
import { revokeUserSession, sessionUserIdOf } from "../auth/session";
|
|
3
4
|
import { withSessionCookie } from "../auth/session-cookie";
|
|
4
5
|
import { APPROVAL_MODES, SPAWN_PROVIDERS, isRecord, userSinkId } from "@humwick/sdk";
|
|
5
|
-
import { ValidationError, getOrchestrator
|
|
6
|
+
import { ValidationError, getOrchestrator } from "../db";
|
|
6
7
|
import { auditEvent, authAuditMetadata, authorizeRoute, error, isRootCredentialRequest, json, parseBody, resolveActingUser, type Handler } from "./_shared";
|
|
7
8
|
import { cleanString, cleanStringArray, cleanTokenConstraints, optionalEnum } from "../validation";
|
|
8
|
-
import {
|
|
9
|
+
import { getToken, getTokenProfile, listTokenProfiles, listTokens } from "../token-db";
|
|
9
10
|
import { createTokenAsync, revokeTokenAsync } from "../token-db-write";
|
|
10
11
|
import { getComponentAuth, getIntegrationAuth, isAuthorized } from "../security";
|
|
11
12
|
import { handoffRunnerTokenAsync, issueIntegrationRuntimeToken, issueInteractiveRunnerRuntimeToken, issueMcpRuntimeToken, reissueRunnerRuntimeToken, remintRunnerRuntimeToken, type RunnerTokenMintRefusal } from "../runtime-tokens";
|
|
@@ -176,7 +177,7 @@ export const postRuntimeTokenRemint: Handler = async (req) => {
|
|
|
176
177
|
export const postOrchestratorRunnerToken: Handler = async (req, params) => {
|
|
177
178
|
const orch = getOrchestrator(params.id!);
|
|
178
179
|
if (!orch) return error("orchestrator not found", 404);
|
|
179
|
-
const denied = authorizeRoute(req, { scope: "command:write", resource: { orchestratorId: orch.id } });
|
|
180
|
+
const denied = (await authorizeRoute(req, { scope: "command:write", resource: { orchestratorId: orch.id } }));
|
|
180
181
|
if (denied) return denied;
|
|
181
182
|
const parsed = await parseBody<unknown>(req);
|
|
182
183
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
@@ -219,7 +220,7 @@ export const postInteractiveRunnerRuntimeToken: Handler = async (req) => {
|
|
|
219
220
|
// (`isRootCredentialRequest` above), which can mint any token it likes anyway — the value is
|
|
220
221
|
// pinned into the signature so every LATER, scoped read of it is unforgeable.
|
|
221
222
|
const approvalMode = optionalEnum(parsed.body.approvalMode, "approvalMode", APPROVAL_MODES);
|
|
222
|
-
const runtimeToken = issueInteractiveRunnerRuntimeToken({
|
|
223
|
+
const runtimeToken = (await issueInteractiveRunnerRuntimeToken({
|
|
223
224
|
provider,
|
|
224
225
|
cwd,
|
|
225
226
|
runnerId,
|
|
@@ -228,7 +229,7 @@ export const postInteractiveRunnerRuntimeToken: Handler = async (req) => {
|
|
|
228
229
|
host,
|
|
229
230
|
...(approvalMode ? { approvalMode } : {}),
|
|
230
231
|
createdBy: `interactive:${runnerId}`,
|
|
231
|
-
});
|
|
232
|
+
}));
|
|
232
233
|
await auditEvent({
|
|
233
234
|
clientId: "server-runtime-token-interactive-" + runnerId + "-" + Date.now(),
|
|
234
235
|
kind: "state",
|
|
@@ -263,13 +264,13 @@ export const postMcpRuntimeToken: Handler = async (req) => {
|
|
|
263
264
|
const agentId = cleanString(parsed.body.agentId, "agentId", { max: 240 });
|
|
264
265
|
const targets = cleanStringArray(parsed.body.targets, "targets", { itemMax: 80, maxItems: 50 });
|
|
265
266
|
const channels = cleanStringArray(parsed.body.channels, "channels", { itemMax: 80, maxItems: 50 });
|
|
266
|
-
const runtimeToken = issueMcpRuntimeToken({
|
|
267
|
+
const runtimeToken = (await issueMcpRuntimeToken({
|
|
267
268
|
sessionId,
|
|
268
269
|
agentId,
|
|
269
270
|
targets,
|
|
270
271
|
channels,
|
|
271
272
|
createdBy: `mcp:${sessionId}`,
|
|
272
|
-
});
|
|
273
|
+
}));
|
|
273
274
|
await auditEvent({
|
|
274
275
|
clientId: "server-runtime-token-mcp-" + sessionId + "-" + Date.now(),
|
|
275
276
|
kind: "state",
|
|
@@ -302,19 +303,19 @@ export const postIntegrationRuntimeToken: Handler = async (req) => {
|
|
|
302
303
|
const name = cleanString(parsed.body.name, "name", { required: true, max: 120 })!;
|
|
303
304
|
const targets = cleanStringArray(parsed.body.targets, "targets", { itemMax: 80, maxItems: 50 });
|
|
304
305
|
const channels = cleanStringArray(parsed.body.channels, "channels", { itemMax: 80, maxItems: 50 });
|
|
305
|
-
const runtimeToken = issueIntegrationRuntimeToken({
|
|
306
|
+
const runtimeToken = (await issueIntegrationRuntimeToken({
|
|
306
307
|
name,
|
|
307
308
|
targets,
|
|
308
309
|
channels,
|
|
309
310
|
createdBy: `integration:${name}`,
|
|
310
|
-
});
|
|
311
|
-
|
|
311
|
+
}));
|
|
312
|
+
(await upsertIntegrationRegistryAsync({
|
|
312
313
|
name,
|
|
313
314
|
scopes: runtimeToken.record.scope,
|
|
314
315
|
targets,
|
|
315
316
|
channels,
|
|
316
317
|
source: "api",
|
|
317
|
-
});
|
|
318
|
+
}));
|
|
318
319
|
await auditEvent({
|
|
319
320
|
clientId: "server-runtime-token-integration-" + name + "-" + Date.now(),
|
|
320
321
|
kind: "state",
|
|
@@ -344,7 +345,7 @@ export const postTokenProfile: Handler = async (req) => {
|
|
|
344
345
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
345
346
|
try {
|
|
346
347
|
const input = cleanTokenProfileInput(parsed.body);
|
|
347
|
-
return json(
|
|
348
|
+
return json((await upsertTokenProfileAsync({
|
|
348
349
|
id: input.id,
|
|
349
350
|
name: input.name!,
|
|
350
351
|
description: input.description,
|
|
@@ -353,7 +354,7 @@ export const postTokenProfile: Handler = async (req) => {
|
|
|
353
354
|
constraints: input.constraints,
|
|
354
355
|
ttlSeconds: input.ttlSeconds,
|
|
355
356
|
createdBy: input.createdBy,
|
|
356
|
-
}), 201);
|
|
357
|
+
})), 201);
|
|
357
358
|
} catch (e) {
|
|
358
359
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
359
360
|
if (e instanceof Error) return error(e.message, 400);
|
|
@@ -366,7 +367,7 @@ export const patchTokenProfile: Handler = async (req, params) => {
|
|
|
366
367
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
367
368
|
try {
|
|
368
369
|
const patch = cleanTokenProfileInput(parsed.body, true);
|
|
369
|
-
const updated =
|
|
370
|
+
const updated = (await updateTokenProfileAsync(params.id!, {
|
|
370
371
|
name: patch.name,
|
|
371
372
|
description: patch.description,
|
|
372
373
|
role: patch.role,
|
|
@@ -374,7 +375,7 @@ export const patchTokenProfile: Handler = async (req, params) => {
|
|
|
374
375
|
constraints: patch.constraints,
|
|
375
376
|
ttlSeconds: patch.ttlSeconds,
|
|
376
377
|
createdBy: patch.createdBy,
|
|
377
|
-
});
|
|
378
|
+
}));
|
|
378
379
|
return updated ? json(updated) : error("token profile not found", 404);
|
|
379
380
|
} catch (e) {
|
|
380
381
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
@@ -383,8 +384,8 @@ export const patchTokenProfile: Handler = async (req, params) => {
|
|
|
383
384
|
}
|
|
384
385
|
};
|
|
385
386
|
|
|
386
|
-
export const deleteTokenProfileRoute: Handler = (_req, params) => {
|
|
387
|
-
return
|
|
387
|
+
export const deleteTokenProfileRoute: Handler = async (_req, params) => {
|
|
388
|
+
return (await deleteTokenProfileAsync(params.id!)) ? json({ ok: true }) : error("token profile not found or built-in", 404);
|
|
388
389
|
};
|
|
389
390
|
|
|
390
391
|
export const postTokenRevoke: Handler = async (req, params) => {
|
package/src/routes/users.ts
CHANGED
|
@@ -60,14 +60,14 @@ export const getMe: Handler = (req) => {
|
|
|
60
60
|
return user ? json({ ...user, hasPassword: hasPasswordLogin(user.id) } satisfies CurrentUser) : error("user not found", 404);
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
export const patchMe: Handler = (req) => patchUserProfile(req, { id: requestUserId(req) });
|
|
63
|
+
export const patchMe: Handler = async (req) => (await patchUserProfile(req, { id: requestUserId(req) }));
|
|
64
64
|
|
|
65
|
-
export const patchUserById: Handler = (req, params) => patchUserProfile(req, params);
|
|
65
|
+
export const patchUserById: Handler = async (req, params) => (await patchUserProfile(req, params));
|
|
66
66
|
|
|
67
67
|
async function patchUserProfile(req: Request, params: Record<string, string>): Promise<Response> {
|
|
68
68
|
try {
|
|
69
69
|
const userId = userIdParam(req, params);
|
|
70
|
-
if (!canManageUser(req, userId)) return authorityDenied(req, "forbidden");
|
|
70
|
+
if (!canManageUser(req, userId)) return (await authorityDenied(req, "forbidden"));
|
|
71
71
|
const parsed = await parseBody<unknown>(req);
|
|
72
72
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
73
73
|
if (!isRecord(parsed.body)) return error("body must be an object");
|
|
@@ -76,14 +76,14 @@ async function patchUserProfile(req: Request, params: Record<string, string>): P
|
|
|
76
76
|
const role = optionalEnum(body.role, "role", INVITE_ROLES);
|
|
77
77
|
const status = optionalEnum(body.status, "status", ACCOUNT_STATUSES);
|
|
78
78
|
if (role || status) {
|
|
79
|
-
if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can change a user's role or status");
|
|
79
|
+
if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can change a user's role or status"));
|
|
80
80
|
const before = getUser(userId);
|
|
81
81
|
const after = await changeUserAccess({ actorUserId: requestUserId(req), userId, role, status });
|
|
82
|
-
if (before && after.role !== before.role) recordUserManagement(req, userId, "change role", { from: before.role, to: after.role });
|
|
83
|
-
if (before && after.status !== before.status) recordUserManagement(req, userId, after.status === "suspended" ? "suspend" : "reactivate");
|
|
82
|
+
if (before && after.role !== before.role) (await recordUserManagement(req, userId, "change role", { from: before.role, to: after.role }));
|
|
83
|
+
if (before && after.status !== before.status) (await recordUserManagement(req, userId, after.status === "suspended" ? "suspend" : "reactivate"));
|
|
84
84
|
}
|
|
85
85
|
const displayName = cleanString(body.displayName, "displayName", { required: !role && !status, max: 120 });
|
|
86
|
-
if (displayName) return json(updateUserDisplayName({ userId, displayName }));
|
|
86
|
+
if (displayName) return json((await updateUserDisplayName({ userId, displayName })));
|
|
87
87
|
const user = userOr404(userId);
|
|
88
88
|
return user instanceof Response ? user : json(user);
|
|
89
89
|
} catch (e) {
|
|
@@ -91,9 +91,9 @@ async function patchUserProfile(req: Request, params: Record<string, string>): P
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export const postMyAvatar: Handler = (req) => postUserAvatar(req, { id: requestUserId(req) });
|
|
94
|
+
export const postMyAvatar: Handler = async (req) => (await postUserAvatar(req, { id: requestUserId(req) }));
|
|
95
95
|
|
|
96
|
-
export const postUserAvatarById: Handler = (req, params) => postUserAvatar(req, params);
|
|
96
|
+
export const postUserAvatarById: Handler = async (req, params) => (await postUserAvatar(req, params));
|
|
97
97
|
|
|
98
98
|
async function avatarUploadInput(req: Request): Promise<{ bytes: Uint8Array; filename?: string; mediaType: string; digest?: string }> {
|
|
99
99
|
const contentType = req.headers.get("content-type") ?? "";
|
|
@@ -123,7 +123,7 @@ async function postUserAvatar(req: Request, params: Record<string, string>): Pro
|
|
|
123
123
|
const userId = userIdParam(req, params);
|
|
124
124
|
const user = userOr404(userId);
|
|
125
125
|
if (user instanceof Response) return user;
|
|
126
|
-
if (!canManageUser(req, userId)) return authorityDenied(req, "forbidden");
|
|
126
|
+
if (!canManageUser(req, userId)) return (await authorityDenied(req, "forbidden"));
|
|
127
127
|
const input = await avatarUploadInput(req);
|
|
128
128
|
const result = await storeUserAvatar({ userId, actor: requestUserAddress(req), ...input });
|
|
129
129
|
return json(result, 201);
|
|
@@ -132,25 +132,25 @@ async function postUserAvatar(req: Request, params: Record<string, string>): Pro
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
export const deleteMyAvatar: Handler = (req) => deleteUserAvatar(req, { id: requestUserId(req) });
|
|
135
|
+
export const deleteMyAvatar: Handler = async (req) => (await deleteUserAvatar(req, { id: requestUserId(req) }));
|
|
136
136
|
|
|
137
|
-
export const deleteUserAvatarById: Handler = (req, params) => deleteUserAvatar(req, params);
|
|
137
|
+
export const deleteUserAvatarById: Handler = async (req, params) => (await deleteUserAvatar(req, params));
|
|
138
138
|
|
|
139
139
|
async function deleteUserAvatar(req: Request, params: Record<string, string>): Promise<Response> {
|
|
140
140
|
try {
|
|
141
141
|
const userId = userIdParam(req, params);
|
|
142
142
|
const user = userOr404(userId);
|
|
143
143
|
if (user instanceof Response) return user;
|
|
144
|
-
if (!canManageUser(req, userId)) return authorityDenied(req, "forbidden");
|
|
144
|
+
if (!canManageUser(req, userId)) return (await authorityDenied(req, "forbidden"));
|
|
145
145
|
return json(await removeUserAvatar({ userId }));
|
|
146
146
|
} catch (e) {
|
|
147
147
|
return profileError(e);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
export const getUserAvatar: Handler = (req, params) => serveUserAvatar(req, params, false);
|
|
151
|
+
export const getUserAvatar: Handler = async (req, params) => (await serveUserAvatar(req, params, false));
|
|
152
152
|
|
|
153
|
-
export const headUserAvatar: Handler = (req, params) => serveUserAvatar(req, params, true);
|
|
153
|
+
export const headUserAvatar: Handler = async (req, params) => (await serveUserAvatar(req, params, true));
|
|
154
154
|
|
|
155
155
|
async function serveUserAvatar(req: Request, params: Record<string, string>, head: boolean): Promise<Response> {
|
|
156
156
|
const userId = userIdParam(req, params);
|
|
@@ -158,7 +158,7 @@ async function serveUserAvatar(req: Request, params: Record<string, string>, hea
|
|
|
158
158
|
if (!user?.avatarArtifactId) return error("avatar not found", 404);
|
|
159
159
|
const artifact = await getArtifactAsync(user.avatarArtifactId);
|
|
160
160
|
if (!artifact) return error("avatar artifact not found", 404);
|
|
161
|
-
if (!canReadAvatar(req, userId, artifact)) return authorityDenied(req, "forbidden");
|
|
161
|
+
if (!canReadAvatar(req, userId, artifact)) return (await authorityDenied(req, "forbidden"));
|
|
162
162
|
const blob = await getArtifactBlobAsync(artifact.blobDigest);
|
|
163
163
|
if (!blob) return error("avatar blob not found", 404);
|
|
164
164
|
const headers = {
|
|
@@ -183,15 +183,15 @@ export const invitesDisabled = () => error("user invites are disabled", 404);
|
|
|
183
183
|
/** POST /api/users/invites — admin only. The code is shown once; share `/invite/<code>` by hand. */
|
|
184
184
|
export const postUserInvite: Handler = async (req) => {
|
|
185
185
|
if (!userInvitesEnabled()) return invitesDisabled();
|
|
186
|
-
if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can invite users");
|
|
186
|
+
if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can invite users"));
|
|
187
187
|
const parsed = await parseBody<unknown>(req);
|
|
188
188
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
189
189
|
const body = isRecord(parsed.body) ? parsed.body : {};
|
|
190
190
|
try {
|
|
191
191
|
const role = optionalEnum(body.role, "role", INVITE_ROLES) ?? "operator";
|
|
192
192
|
const displayName = cleanString(body.displayName, "displayName", { max: 120 });
|
|
193
|
-
const { invite, code } = createInvite({ role, displayName, createdBy: requestUserId(req) });
|
|
194
|
-
recordUserManagement(req, `invite:${invite.id}`, "create invite", { role, ...(displayName ? { displayName } : {}) });
|
|
193
|
+
const { invite, code } = (await createInvite({ role, displayName, createdBy: requestUserId(req) }));
|
|
194
|
+
(await recordUserManagement(req, `invite:${invite.id}`, "create invite", { role, ...(displayName ? { displayName } : {}) }));
|
|
195
195
|
return json({ invite, code, path: `/invite/${code}` }, 201);
|
|
196
196
|
} catch (e) {
|
|
197
197
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
@@ -202,7 +202,7 @@ export const postUserInvite: Handler = async (req) => {
|
|
|
202
202
|
/** PUT /api/users/me/password — a signed-in user, or the admin token for the default admin. */
|
|
203
203
|
export const putMyPassword: Handler = async (req) => {
|
|
204
204
|
const acting = resolveActingUser(req);
|
|
205
|
-
if (!acting) return authorityDenied(req, "sign in to set a password");
|
|
205
|
+
if (!acting) return (await authorityDenied(req, "sign in to set a password"));
|
|
206
206
|
const parsed = await parseBody<unknown>(req);
|
|
207
207
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
208
208
|
if (!isRecord(parsed.body)) return error("password required");
|
|
@@ -219,11 +219,11 @@ export const putMyPassword: Handler = async (req) => {
|
|
|
219
219
|
|
|
220
220
|
/** DELETE /api/users/:id/sessions — sign the user out everywhere; their next call gets 401. */
|
|
221
221
|
export const deleteUserSessions: Handler = async (req, params) => {
|
|
222
|
-
if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can sign a user out");
|
|
222
|
+
if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can sign a user out"));
|
|
223
223
|
try {
|
|
224
224
|
const userId = decodeURIComponent(params.id!);
|
|
225
225
|
const revoked = await revokeUserSessions(userId);
|
|
226
|
-
recordUserManagement(req, userId, "revoke sessions", { revoked });
|
|
226
|
+
(await recordUserManagement(req, userId, "revoke sessions", { revoked }));
|
|
227
227
|
return json({ revoked });
|
|
228
228
|
} catch (e) {
|
|
229
229
|
return profileError(e);
|
|
@@ -232,7 +232,7 @@ export const deleteUserSessions: Handler = async (req, params) => {
|
|
|
232
232
|
|
|
233
233
|
/** POST /api/users/:id/handover — every agent of the user gets `toUserId` as its principal. */
|
|
234
234
|
export const postUserHandover: Handler = async (req, params) => {
|
|
235
|
-
if (!isAdminRequest(req)) return authorityDenied(req, "only an admin can hand over agents");
|
|
235
|
+
if (!isAdminRequest(req)) return (await authorityDenied(req, "only an admin can hand over agents"));
|
|
236
236
|
try {
|
|
237
237
|
const parsed = await parseBody<unknown>(req);
|
|
238
238
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
@@ -240,7 +240,7 @@ export const postUserHandover: Handler = async (req, params) => {
|
|
|
240
240
|
const toUserId = cleanString(body.toUserId, "toUserId", { required: true, max: 64 })!;
|
|
241
241
|
const fromUserId = decodeURIComponent(params.id!);
|
|
242
242
|
const agentIds = await handOverAgents({ fromUserId, toUserId });
|
|
243
|
-
recordUserManagement(req, fromUserId, "hand over agents", { toUserId, agentIds });
|
|
243
|
+
(await recordUserManagement(req, fromUserId, "hand over agents", { toUserId, agentIds }));
|
|
244
244
|
return json({ toUserId, agentIds });
|
|
245
245
|
} catch (e) {
|
|
246
246
|
return profileError(e);
|
|
@@ -12,15 +12,15 @@ export function scopeResourceReads(method: string, path: string, handler: Handle
|
|
|
12
12
|
const viewer = viewerScopeForRequest(req);
|
|
13
13
|
if (!viewer || viewer.seesAll) return handler(req, params);
|
|
14
14
|
const id = params.id!;
|
|
15
|
-
if (resource === "agents" && !agentVisibleTo(viewer, id)) return authorityDenied(req, "agent not found", 404, id, "read agent");
|
|
15
|
+
if (resource === "agents" && !agentVisibleTo(viewer, id)) return (await authorityDenied(req, "agent not found", 404, id, "read agent"));
|
|
16
16
|
if (resource === "tasks") {
|
|
17
17
|
const taskId = parseId(id);
|
|
18
|
-
if (taskId !== null && !taskVisibleTo(viewer, { id: taskId })) return authorityDenied(req, "task not found", 404, id, "read task");
|
|
18
|
+
if (taskId !== null && !taskVisibleTo(viewer, { id: taskId })) return (await authorityDenied(req, "task not found", 404, id, "read task"));
|
|
19
19
|
}
|
|
20
20
|
if (resource === "messages") {
|
|
21
21
|
const messageId = parseId(id);
|
|
22
22
|
const message = messageId === null ? null : await getMessageAsync(messageId);
|
|
23
|
-
if (message && !messageVisibleTo(viewer, message)) return authorityDenied(req, "you can only read your own messages", 403, id, path.endsWith("/thread") ? "read thread" : "read message");
|
|
23
|
+
if (message && !messageVisibleTo(viewer, message)) return (await authorityDenied(req, "you can only read your own messages", 403, id, path.endsWith("/thread") ? "read thread" : "read message"));
|
|
24
24
|
}
|
|
25
25
|
return handler(req, params);
|
|
26
26
|
};
|
|
@@ -5,27 +5,27 @@ import { ingestWebToolCall, queryWebSearchBudget, queryWebToolCalls, queryWebToo
|
|
|
5
5
|
import { authorizeRoute, error, json, parseBody, type Handler } from "./_shared";
|
|
6
6
|
|
|
7
7
|
export const getWebToolCalls: Handler = async req => {
|
|
8
|
-
const denied = authorizeRoute(req, { scope: "agent:read" });
|
|
8
|
+
const denied = (await authorizeRoute(req, { scope: "agent:read" }));
|
|
9
9
|
if (denied) return denied;
|
|
10
10
|
try { return json(await queryWebToolCalls(Object.fromEntries(new URL(req.url).searchParams))); }
|
|
11
11
|
catch (e) { if (e instanceof ValidationError) return error(e.message, 400); throw e; }
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export const getWebToolCallSummary: Handler = async req => {
|
|
15
|
-
const denied = authorizeRoute(req, { scope: "agent:read" });
|
|
15
|
+
const denied = (await authorizeRoute(req, { scope: "agent:read" }));
|
|
16
16
|
if (denied) return denied;
|
|
17
17
|
try { return json(await queryWebToolCallSummary(Object.fromEntries(new URL(req.url).searchParams))); }
|
|
18
18
|
catch (e) { if (e instanceof ValidationError) return error(e.message, 400); throw e; }
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const getWebSearchBudget: Handler = async req => {
|
|
22
|
-
const denied = authorizeRoute(req, { scope: "agent:read" });
|
|
22
|
+
const denied = (await authorizeRoute(req, { scope: "agent:read" }));
|
|
23
23
|
if (denied) return denied;
|
|
24
24
|
return json(await queryWebSearchBudget());
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
export const postWebToolCall: Handler = async req => {
|
|
28
|
-
const denied = authorizeRoute(req, { scope: "agent:write" });
|
|
28
|
+
const denied = (await authorizeRoute(req, { scope: "agent:write" }));
|
|
29
29
|
if (denied) return denied;
|
|
30
30
|
const body = await parseBody<unknown>(req);
|
|
31
31
|
if (!body.ok) return error(body.error, body.status);
|
package/src/routes/workflows.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { McpNotFoundError } from "../mcp-errors";
|
|
|
9
9
|
import { authContextFromRequest } from "../services/auth-context";
|
|
10
10
|
import { ServiceAuthError } from "../services/errors";
|
|
11
11
|
import { readWorkflowCatalog } from "../services/workflow-catalog";
|
|
12
|
+
import { parseWorkflowPins } from "../services/workflow-pins";
|
|
12
13
|
import { requireSpawnBudget } from "../spawn-budget";
|
|
13
14
|
import { amendWorkflowRunBudget, cancelWorkflowRun, getWorkflowRun, pauseWorkflowRun, readWorkflowRunBudget, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun } from "../services/workflow-runs";
|
|
14
15
|
import { workflowDefinitions, workflowStats } from "../services/workflow-stats";
|
|
@@ -123,7 +124,7 @@ export const postWorkflowRunResumeRoute: Handler = async (req, params) => {
|
|
|
123
124
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
124
125
|
if (!isRecord(parsed.body)) return error("body must be a JSON object", 400);
|
|
125
126
|
try {
|
|
126
|
-
return json(await resumeWorkflowRun({ runId: params.runId!, decision: parseWorkflowDecisionResponse(parsed.body.decision), activationId: cleanString(parsed.body.activationId, "activationId", { max: 2000 }), note: cleanString(parsed.body.note, "note", { max: 8000 }) }, authContextFromRequest(req)));
|
|
127
|
+
return json(await resumeWorkflowRun({ runId: params.runId!, pins: parseWorkflowPins(parsed.body.pins), step: cleanString(parsed.body.step, "step", { max: 64 }), decision: parseWorkflowDecisionResponse(parsed.body.decision), activationId: cleanString(parsed.body.activationId, "activationId", { max: 2000 }), note: cleanString(parsed.body.note, "note", { max: 8000 }) }, authContextFromRequest(req)));
|
|
127
128
|
} catch (e) {
|
|
128
129
|
if (e instanceof McpNotFoundError) return error(e.message, 404);
|
|
129
130
|
if (e instanceof ServiceAuthError) return error(e.message, 403);
|
|
@@ -146,7 +147,6 @@ export const postWorkflowRunStartRoute: Handler = async (req) => {
|
|
|
146
147
|
try {
|
|
147
148
|
const workflow = cleanString(parsed.body.workflow, "workflow", { max: 64 });
|
|
148
149
|
if (!workflow) throw new ValidationError("workflow is required");
|
|
149
|
-
const { parseWorkflowPins } = await import("../services/workflow-pins");
|
|
150
150
|
return json(await startWorkflowRun({ workflow, taskId: taskIdField(parsed.body.taskId), pins: parseWorkflowPins(parsed.body.pins), inputs: parsed.body.inputs as Record<string, unknown> | undefined, requestKey: cleanString(parsed.body.requestKey, "requestKey", { max: 200 }), budget: parsed.body.budget }, authContextFromRequest(req)), 202);
|
|
151
151
|
} catch (e) {
|
|
152
152
|
if (e instanceof ServiceAuthError) return error(e.message, 403);
|
package/src/routes/workspaces.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
import { createCommandAsync } from "../commands-write";
|
|
2
|
+
import { deleteWorkspaceAsync } from "../db/lifecycle-tail-async";
|
|
1
3
|
// Auto-split from routes.ts (#299). Domain: workspaces.
|
|
2
4
|
import { RELAY_TOKEN_HEADER, isRecord } from "@humwick/sdk";
|
|
3
5
|
import { TERMINAL_WORKSPACE_STATUSES, describeWorkspacePhase, landReceipt } from "../workspace-phase";
|
|
4
6
|
import { VALID_WORKSPACE_STATUSES, cleanMeta, cleanString, optionalEnum } from "../validation";
|
|
5
|
-
import { ValidationError,
|
|
7
|
+
import { ValidationError, getAgent, getWorkspace, listMergeLeases, listOrchestrators, listRepoStewards, listWorkspaces, updateWorkspaceStatus, withResolvedWorkspaceProjectsAsync } from "../db";
|
|
6
8
|
import { recoverIsolatedWorkspace } from "../services/workspace-recovery";
|
|
7
9
|
import { WORKSPACE_ACTIONS, applyWorkspaceAction, waitForWorkspaceStatus } from "../workspace-actions";
|
|
8
10
|
import { buildWorkspaceCleanupCommand, cleanupStaleWorkspaces } from "../workspace-cleanup-stale";
|
|
9
11
|
import { auditEvent, authAuditMetadata, authorizeRoute, emitCommand, error, isRootCredentialRequest, json, parseBody, type Handler } from "./_shared";
|
|
10
12
|
import { collectWorkspaceOrphans } from "../workspace-orphans";
|
|
11
|
-
import {
|
|
12
|
-
import { LAND_STRATEGIES, isOwnerAlive, isOwnerProcessConfirmedGone, withOwnerOnline } from "../workspace-merge";
|
|
13
|
+
import { getCommand } from "../commands-db";
|
|
14
|
+
import { LAND_STRATEGIES, isOwnerAlive, isOwnerProcessConfirmedGone, withOwnerOnline, AUTO_MERGE_POLICIES } from "../workspace-merge";
|
|
13
15
|
import { isPathWithinBase } from "../utils";
|
|
14
16
|
import { resolve } from "node:path";
|
|
15
17
|
import { type WorkspaceAutoMergePolicy, type WorkspaceDiagnostics, type WorkspaceGitState, type WorkspaceLandGateLevel, type WorkspaceLandingPolicy, type WorkspaceMergeStrategy, type WorkspaceRecoveryBranch, type WorkspaceRecord, type WorkspaceStatus } from "../types";
|
|
16
|
-
import { AUTO_MERGE_POLICIES } from "../workspace-merge";
|
|
17
18
|
import { workspaceActiveClaim } from "../workspace-claim";
|
|
18
19
|
import { resolveCoordinatorAgentId } from "../steward-identity";
|
|
19
20
|
import { relayToken } from "../config";
|
|
@@ -231,7 +232,7 @@ export const getWorkspaceRecoveryBranches: Handler = async (req) => {
|
|
|
231
232
|
};
|
|
232
233
|
|
|
233
234
|
export const postWorkspaceRecoveryBranchDiscard: Handler = async (req) => {
|
|
234
|
-
const denied = authorizeRoute(req, { scope: "command:write" });
|
|
235
|
+
const denied = (await authorizeRoute(req, { scope: "command:write" }));
|
|
235
236
|
if (denied) return denied;
|
|
236
237
|
const parsed = await parseBody<unknown>(req);
|
|
237
238
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
@@ -256,13 +257,13 @@ export const postWorkspaceRecoveryBranchDiscard: Handler = async (req) => {
|
|
|
256
257
|
if (target.safeToDelete !== true && !force) {
|
|
257
258
|
return error(`branch holds work or cannot be proven safe (${target.preserveReason ?? `${target.unmergedAhead ?? target.ahead ?? "?"} unlanded commit(s)`}); land it first or pass force:true with reason to discard`, 409);
|
|
258
259
|
}
|
|
259
|
-
const command =
|
|
260
|
+
const command = (await createCommandAsync({
|
|
260
261
|
type: "workspace.recovery-branch-discard",
|
|
261
262
|
source: "system",
|
|
262
263
|
target: orch.agentId,
|
|
263
264
|
correlationId: branch,
|
|
264
265
|
params: { action: "recovery-branch-discard", repoRoot, branch, baseRef: baseRef ?? target.baseRef, baseSha: baseSha ?? target.baseSha, force, reason, requestedBy: "steward", requestedAt: Date.now() },
|
|
265
|
-
});
|
|
266
|
+
}));
|
|
266
267
|
emitCommand(command);
|
|
267
268
|
await auditEvent({
|
|
268
269
|
clientId: `workspace-recovery-branch-discard-${Date.now()}`,
|
|
@@ -282,7 +283,7 @@ export const postWorkspaceRecoveryBranchDiscard: Handler = async (req) => {
|
|
|
282
283
|
};
|
|
283
284
|
|
|
284
285
|
export const postWorkspaceOrphanReclaim: Handler = async (req) => {
|
|
285
|
-
const denied = authorizeRoute(req, { scope: "command:write" });
|
|
286
|
+
const denied = (await authorizeRoute(req, { scope: "command:write" }));
|
|
286
287
|
if (denied) return denied;
|
|
287
288
|
const parsed = await parseBody<unknown>(req);
|
|
288
289
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
@@ -306,12 +307,12 @@ export const postWorkspaceOrphanReclaim: Handler = async (req) => {
|
|
|
306
307
|
? listOrchestrators().find((candidate) => candidate.id === target.orchestratorId && candidate.status === "online")
|
|
307
308
|
: listOrchestrators().find((candidate) => candidate.status === "online" && isPathWithinBase(repoRoot, candidate.baseDir));
|
|
308
309
|
if (!orch) return error("no online orchestrator owns this path", 409);
|
|
309
|
-
const command =
|
|
310
|
+
const command = (await createCommandAsync({
|
|
310
311
|
type: "workspace.cleanup",
|
|
311
312
|
source: "system",
|
|
312
313
|
target: orch.agentId,
|
|
313
314
|
params: { action: "cleanup", worktreePath, repoRoot, branch, baseRef, baseSha, deleteBranch: true, reclaim: true, requestedBy: "dashboard", requestedAt: Date.now() },
|
|
314
|
-
});
|
|
315
|
+
}));
|
|
315
316
|
emitCommand(command);
|
|
316
317
|
await auditEvent({
|
|
317
318
|
clientId: `workspace-orphan-reclaim-${Date.now()}`,
|
|
@@ -443,7 +444,7 @@ export const getWorkspaceDiagnostics: Handler = async (_req, params) => {
|
|
|
443
444
|
};
|
|
444
445
|
|
|
445
446
|
export const postWorkspaceCleanupStale: Handler = async (req) => {
|
|
446
|
-
const denied = authorizeRoute(req, { scope: "command:write" });
|
|
447
|
+
const denied = (await authorizeRoute(req, { scope: "command:write" }));
|
|
447
448
|
if (denied) return denied;
|
|
448
449
|
const parsed = await parseBody<unknown>(req);
|
|
449
450
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
@@ -475,7 +476,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
475
476
|
// owner-merge cwd-authz short-circuit below, so a recovered row still runs the standard cwd check.
|
|
476
477
|
let freshlyRecovered = false;
|
|
477
478
|
if (!workspace) {
|
|
478
|
-
const recovery = recoverIsolatedWorkspace(authContextFromRequest(req), isRecord(parsed.body.workspace) ? parsed.body.workspace : undefined, params.id!);
|
|
479
|
+
const recovery = (await recoverIsolatedWorkspace(authContextFromRequest(req), isRecord(parsed.body.workspace) ? parsed.body.workspace : undefined, params.id!));
|
|
479
480
|
if (!recovery.ok) return error("workspace not found", 404);
|
|
480
481
|
workspace = recovery.workspace;
|
|
481
482
|
freshlyRecovered = recovery.created;
|
|
@@ -509,7 +510,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
509
510
|
// fuzzy label match (the 545ca290 impersonation hole); steward authz resolves the actual
|
|
510
511
|
// elected steward, not the null-prone `workspace.stewardAgentId` mirror.
|
|
511
512
|
const authorized = isWorkspaceRecoveryAuthorized(authCtx, workspace);
|
|
512
|
-
const denied = authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write" });
|
|
513
|
+
const denied = (await authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write" }));
|
|
513
514
|
if (denied) return denied;
|
|
514
515
|
if (!authorized) return error("forbidden", 403);
|
|
515
516
|
// A raw root credential or a VERIFIED component recovery principal (one of the four #1747
|
|
@@ -534,7 +535,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
534
535
|
hasComponentScope(component, "command:write");
|
|
535
536
|
if (!ownsMergeRequest && !isRecoveryAction) {
|
|
536
537
|
const authzCwd = freshlyRecovered ? workspace.sourceCwd : workspace.worktreePath;
|
|
537
|
-
const denied = authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write", resource: { agentId, cwd: authzCwd } });
|
|
538
|
+
const denied = (await authorizeRoute(req, { scope: requiresCommand ? "command:write" : "agent:write", resource: { agentId, cwd: authzCwd } }));
|
|
538
539
|
if (denied) return denied;
|
|
539
540
|
}
|
|
540
541
|
|
|
@@ -617,7 +618,7 @@ export const postWorkspaceAction: Handler = async (req, params) => {
|
|
|
617
618
|
};
|
|
618
619
|
|
|
619
620
|
export const deleteWorkspaceById: Handler = async (req, params) => {
|
|
620
|
-
const denied = authorizeRoute(req, { scope: "command:write" });
|
|
621
|
+
const denied = (await authorizeRoute(req, { scope: "command:write" }));
|
|
621
622
|
if (denied) return denied;
|
|
622
623
|
const workspace = getWorkspace(params.id!);
|
|
623
624
|
if (!workspace) return error("workspace not found", 404);
|
|
@@ -626,7 +627,7 @@ export const deleteWorkspaceById: Handler = async (req, params) => {
|
|
|
626
627
|
// outlives this row: once the workspace is gone the reconciler sees the ledger entry, treats the
|
|
627
628
|
// round as closed, and deletes each leaked ref by its OWN stored value with backoff until origin
|
|
628
629
|
// confirms. So a purge just drops the workspace row; the ledger owns cleanup.
|
|
629
|
-
const removed =
|
|
630
|
+
const removed = (await deleteWorkspaceAsync(workspace.id));
|
|
630
631
|
if (!removed) return error("workspace not found", 404);
|
|
631
632
|
await auditEvent({
|
|
632
633
|
clientId: `workspace-delete-${workspace.id}-${Date.now()}`,
|
|
@@ -5,7 +5,7 @@ import { emitRelayEvent } from "./events";
|
|
|
5
5
|
import { routeNotification } from "./notification-router";
|
|
6
6
|
import { renderTemplate } from "./prompt-resolver";
|
|
7
7
|
import { parseJson } from "./utils";
|
|
8
|
-
import { isRecord } from "@humwick/sdk";
|
|
8
|
+
import { fireAndForget, isRecord } from "@humwick/sdk";
|
|
9
9
|
import type { RoutingPolicy } from "@humwick/sdk";
|
|
10
10
|
|
|
11
11
|
const PUSH_TTL_MS = 15 * 60 * 1000;
|
|
@@ -33,16 +33,16 @@ export function notifyRoutingPolicyChanged(policy: RoutingPolicy, previous: Rout
|
|
|
33
33
|
if (pending) clearTimeout(pending.timer);
|
|
34
34
|
pending = {
|
|
35
35
|
db, previous: baseline, policy: structuredClone(policy), updatedBy: opts.updatedBy,
|
|
36
|
-
timer: setTimeout(() => {
|
|
36
|
+
timer: setTimeout(() => void fireAndForget("[routing-policy] FYI", async () => {
|
|
37
37
|
const settled = pending;
|
|
38
38
|
pending = undefined;
|
|
39
39
|
try {
|
|
40
40
|
if (!settled || settled.db !== getDb()) return;
|
|
41
|
-
pushSettledPolicy(settled.previous, settled.policy, settled.updatedBy);
|
|
41
|
+
await pushSettledPolicy(settled.previous, settled.policy, settled.updatedBy);
|
|
42
42
|
} catch (error) {
|
|
43
43
|
console.warn("Routing policy FYI failed:", error);
|
|
44
44
|
}
|
|
45
|
-
}, QUIET_WINDOW_MS),
|
|
45
|
+
}), QUIET_WINDOW_MS),
|
|
46
46
|
};
|
|
47
47
|
pending.timer.unref();
|
|
48
48
|
}
|
|
@@ -98,13 +98,13 @@ function ruleDelta(previous: unknown[], current: unknown[], path: string): strin
|
|
|
98
98
|
return delta;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function pushSettledPolicy(previous: RoutingPolicy, policy: RoutingPolicy, updatedBy?: string): void {
|
|
101
|
+
async function pushSettledPolicy(previous: RoutingPolicy, policy: RoutingPolicy, updatedBy?: string): Promise<void> {
|
|
102
102
|
const delta = policyDelta(previous, policy);
|
|
103
103
|
if (delta.length === 0) return;
|
|
104
104
|
const recipients = getSpawnCapableAgentIds();
|
|
105
105
|
if (recipients.length === 0) return;
|
|
106
106
|
|
|
107
|
-
routeNotification({
|
|
107
|
+
await routeNotification({
|
|
108
108
|
type: "routing.policy.changed",
|
|
109
109
|
scope: {},
|
|
110
110
|
recipients,
|
package/src/runtime-grant.ts
CHANGED
|
@@ -5,11 +5,11 @@ import { getLaunchFact } from "./db/launch-facts";
|
|
|
5
5
|
import { getTokenProfile } from "./token-db";
|
|
6
6
|
import { getAgentProfile, spawnGrantForProfile, toolGroupsForProfile } from "./config-store";
|
|
7
7
|
import { FLEET_SCOPE } from "./security";
|
|
8
|
-
import { stringValue } from "@humwick/sdk";
|
|
8
|
+
import { registryToolName, stringValue } from "@humwick/sdk";
|
|
9
9
|
import { canonicalAgentProfileName, hasScope } from "@humwick/sdk/types";
|
|
10
10
|
import { COORDINATOR_PROFILE_NAMES } from "./agent-system-tags";
|
|
11
11
|
import { SECRETS_DEFAULT_PROFILES, SECRETS_TOOL_GROUP, SECRETS_WRITE_SCOPE } from "./secrets-broker/config";
|
|
12
|
-
import { resolveRelayTools, SELF_CARE_TOOL_GROUP } from "./mcp/tool-groups";
|
|
12
|
+
import { RELAY_TOOL_GROUPS, resolveRelayTools, SELF_CARE_TOOL_GROUP } from "./mcp/tool-groups";
|
|
13
13
|
|
|
14
14
|
// Scopes that turn an agent's runtime token into a spawn-capable one (#221). Appended to the
|
|
15
15
|
// provider-agent base scope only when the resolved profile permits (maxSpawnedAgents > 0).
|
|
@@ -27,6 +27,8 @@ const FLEET_OPS_PROFILES = new Set(["coordinator", "ops-assistant"]);
|
|
|
27
27
|
const HOST_WRITE_PROFILES = new Set(["coordinator"]);
|
|
28
28
|
// #3772 — the reboot runbook reads resource_control held/waiter counts from /api/hosts; ops profiles read, never write.
|
|
29
29
|
const HOST_READ_PROFILES = new Set([...HOST_WRITE_PROFILES, "ops-assistant", "sec-ops"]);
|
|
30
|
+
// #3780 — operations and QA need hub diagnostics; coordinators can delegate the read scope.
|
|
31
|
+
const STATS_READ_PROFILES = new Set(["coordinator", "ops-assistant", "qa"]);
|
|
30
32
|
// #3073 — coordinator-only groups (not in WORKER_TOOL_GROUPS) a coordinator always gets, even when its stored override predates them.
|
|
31
33
|
const COORDINATOR_ONLY_TOOL_GROUPS = ["decisions", "workflows", "project", "routing"] as const;
|
|
32
34
|
// #3327 — the coordinator edits routing preferences, like Edin in the dashboard.
|
|
@@ -45,11 +47,10 @@ function callerMayMintHostsScope(callerScopes: string[] | undefined, scope: "hos
|
|
|
45
47
|
return callerScopes === undefined || hasScope(callerScopes, scope);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
// #3110 —
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const inGroup = toolGroups ? toolGroups.includes(SECRETS_TOOL_GROUP) : Boolean(profile && SECRETS_DEFAULT_PROFILES.has(profile));
|
|
50
|
+
// #3110/#3817 — broker groups and exact includes request the same scope, capped by the caller.
|
|
51
|
+
function grantsSecretsScope(profile: string | undefined, toolGroups: string[] | undefined, explicitlySelected: boolean, callerScopes: string[] | undefined): boolean {
|
|
52
|
+
const inGroup = (toolGroups ? toolGroups.includes(SECRETS_TOOL_GROUP) : Boolean(profile && SECRETS_DEFAULT_PROFILES.has(profile)))
|
|
53
|
+
|| explicitlySelected;
|
|
53
54
|
return inGroup && (callerScopes === undefined || hasScope(callerScopes, SECRETS_WRITE_SCOPE));
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -74,6 +75,8 @@ function nonTrustedBaseline(agentInitiated: boolean): string[] {
|
|
|
74
75
|
interface RunnerGrant {
|
|
75
76
|
scope: string[];
|
|
76
77
|
canSpawn: boolean;
|
|
78
|
+
/** A sub-coordinator under its root coordinator's project delegation: it places spawns on any host its parent can (#3804). */
|
|
79
|
+
delegated: boolean;
|
|
77
80
|
maxSpawnedAgents: number;
|
|
78
81
|
toolGroups: string[] | undefined;
|
|
79
82
|
toolInclude?: string[];
|
|
@@ -95,7 +98,7 @@ export function deriveRunnerGrant(profile: string | undefined, authority: Deriva
|
|
|
95
98
|
const selection = profile ? getAgentProfile(profile)?.value.relay.tools : undefined;
|
|
96
99
|
let toolGroups = toolGroupsForProfile(profile);
|
|
97
100
|
// #2897/#3073/#3110 — stored overrides predate newer trusted-profile groups: coordinators always get the coordinator-only
|
|
98
|
-
// groups, and the
|
|
101
|
+
// groups, and the coordinator gets `secrets`. Keep explicit NONE and full-surface semantics.
|
|
99
102
|
if (toolGroups?.length) {
|
|
100
103
|
const extra: string[] = [];
|
|
101
104
|
if ((COORDINATOR_PROFILE_NAMES as readonly string[]).includes(profile ?? "")) extra.push(...COORDINATOR_ONLY_TOOL_GROUPS);
|
|
@@ -106,19 +109,21 @@ export function deriveRunnerGrant(profile: string | undefined, authority: Deriva
|
|
|
106
109
|
}
|
|
107
110
|
const fleetOps = !agentInitiated && Boolean(profile && FLEET_OPS_PROFILES.has(profile)) && callerMayMintFleetScope(callerScopes);
|
|
108
111
|
const hostsRead = !agentInitiated && Boolean(profile && HOST_READ_PROFILES.has(profile)) && callerMayMintHostsScope(callerScopes, "hosts:read");
|
|
109
|
-
resolveRelayTools({ ...selection, groups: toolGroups });
|
|
112
|
+
const selected = resolveRelayTools({ ...selection, groups: toolGroups });
|
|
113
|
+
const explicitSecrets = selection?.include?.map(registryToolName).some(name => selected.has(name) && RELAY_TOOL_GROUPS[SECRETS_TOOL_GROUP]!.includes(name)) ?? false;
|
|
110
114
|
let scope = [
|
|
111
115
|
...nonTrustedBaseline(agentInitiated),
|
|
112
116
|
...(fleetOps ? [FLEET_SCOPE] : []),
|
|
113
117
|
...(hostsRead ? ["hosts:read"] : []),
|
|
118
|
+
...(profile && STATS_READ_PROFILES.has(profile.toLowerCase()) && (callerScopes === undefined || hasScope(callerScopes, "stats:read")) ? ["stats:read"] : []),
|
|
114
119
|
...(!agentInitiated && Boolean(profile && HOST_WRITE_PROFILES.has(profile)) && callerMayMintHostsScope(callerScopes, "hosts:write") ? ["hosts:write"] : []),
|
|
115
|
-
...(grantsSecretsScope(profile, toolGroups, callerScopes) ? [SECRETS_WRITE_SCOPE] : []),
|
|
120
|
+
...(grantsSecretsScope(profile, toolGroups, explicitSecrets, callerScopes) ? [SECRETS_WRITE_SCOPE] : []),
|
|
116
121
|
...(!agentInitiated && (COORDINATOR_PROFILE_NAMES as readonly string[]).includes(profile ?? "") && (callerScopes === undefined || hasScope(callerScopes, ROUTING_WRITE_SCOPE)) ? [ROUTING_WRITE_SCOPE] : []),
|
|
117
122
|
...(grant.canSpawn ? SPAWN_SCOPES.filter(scope => !agentInitiated || callerScopes === undefined || hasScope(callerScopes, scope)) : []),
|
|
118
123
|
...(delegated && callerScopes && hasScope(callerScopes, "teams:write") ? ["teams:write"] : []),
|
|
119
124
|
];
|
|
120
125
|
if (lineage?.participating && callerScopes) scope = scope.filter(scope => hasScope(callerScopes, scope));
|
|
121
|
-
return { scope, canSpawn: grant.canSpawn, maxSpawnedAgents: grant.maxSpawnedAgents, toolGroups,
|
|
126
|
+
return { scope, canSpawn: grant.canSpawn, delegated, maxSpawnedAgents: grant.maxSpawnedAgents, toolGroups,
|
|
122
127
|
...(selection?.include === undefined ? {} : { toolInclude: selection.include }),
|
|
123
128
|
...(selection?.exclude === undefined ? {} : { toolExclude: selection.exclude }),
|
|
124
129
|
};
|
|
@@ -151,6 +156,7 @@ export function successorGrant(agentId: string, profile: string | undefined, dep
|
|
|
151
156
|
return {
|
|
152
157
|
scope: derived.scope.filter((scope) => ceiling.scope.includes(scope)),
|
|
153
158
|
canSpawn: derived.canSpawn && ceiling.canSpawn,
|
|
159
|
+
delegated: derived.delegated && ceiling.delegated,
|
|
154
160
|
maxSpawnedAgents: Math.min(derived.maxSpawnedAgents, ceiling.maxSpawnedAgents),
|
|
155
161
|
toolGroups: derived.toolGroups,
|
|
156
162
|
toolInclude: derived.toolInclude,
|