@humwick/server 0.130.2 → 0.130.3
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 +73 -4
- package/package.json +8 -7
- package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-Bq9kr21N.js} +2 -2
- package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-Dqiroy06.js} +2 -2
- package/public/assets/{TraceView-Wimm0Czv.js → TraceView-DYU962-H.js} +2 -2
- package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DhPfzioE.js} +2 -2
- package/public/assets/{activity-CrSurn-r.js → activity-DzCcQTee.js} +2 -2
- package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-SgDt9i3T.js} +2 -2
- package/public/assets/agent-liveness-Bq-s73jW.js +2 -0
- package/public/assets/{agent-name-DKbiIhBo.js → agent-name-CcY3Aijy.js} +2 -2
- package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-cxtRBn6O.js} +2 -2
- package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-DGYbIhaI.js} +2 -2
- package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-DHbftV0N.js} +2 -2
- package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CY113sp-.js} +2 -2
- package/public/assets/{agents-B-v66C-4.js → agents-BPzY7IlQ.js} +2 -2
- package/public/assets/{analytics-BqCb-LKi.js → analytics-DENFPv_a.js} +2 -2
- package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-KRvSc7sl.js} +2 -2
- package/public/assets/{artifacts-DJevXcvg.js → artifacts-C9gSmL3s.js} +2 -2
- package/public/assets/{artifacts-D32zyw4Y.js → artifacts-DT-HkI5O.js} +2 -2
- package/public/assets/{automation-BpRajx_5.js → automation-BsmjQhKD.js} +2 -2
- package/public/assets/{backlog-D_UjTdzX.js → backlog-8NYH_62t.js} +2 -2
- package/public/assets/{busy-health-N3VEh4ao.js → busy-health-D3hwvYb4.js} +2 -2
- package/public/assets/{channels-9jopungq.js → channels-DCJ5WL7O.js} +2 -2
- package/public/assets/{chat-CSXrTB3C.js → chat-DAIl357U.js} +3 -3
- package/public/assets/{code-preview-p1NnmqK0.js → code-preview-CzrZfr9U.js} +2 -2
- package/public/assets/{connectors-CZdqlDNL.js → connectors-xTxtlyou.js} +2 -2
- package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BpebFLK2.js} +2 -2
- package/public/assets/{crate-agent-30aXTKva.js → crate-agent-BHvI5wXC.js} +2 -2
- package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-DGT0o3wJ.js} +2 -2
- package/public/assets/{deploys-DoN6bAb5.js → deploys-hqWOHSHE.js} +2 -2
- package/public/assets/{display-BrKWFGJz.js → display-CDeH4I8T.js} +2 -2
- package/public/assets/{files-D6XEu8Ng.js → files-BTeGVmTg.js} +2 -2
- package/public/assets/{fleet-C7jQWbw2.js → fleet-DgXtNgCa.js} +2 -2
- package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BHEt5yc_.js} +2 -2
- package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-MLtodyPp.js} +3 -3
- package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-CAe664Z8.js} +2 -2
- package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-mapF4Fj9.js} +2 -2
- package/public/assets/{index-CRHep3M9.js → index-CqqgOI5R.js} +5 -5
- package/public/assets/{insights-CzrtkpOE.js → insights-DNyf7IzF.js} +2 -2
- package/public/assets/{integrations-aVsUmigt.js → integrations-DlYOHq7T.js} +2 -2
- package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-CeWBNWn7.js} +2 -2
- package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-Cib8nqdE.js} +2 -2
- package/public/assets/{maintenance-NfjqFZAk.js → maintenance-DSn1cPm7.js} +2 -2
- package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-C3CkM81m.js} +2 -2
- package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-W4VQQPAv.js} +3 -3
- package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CMsTdhjq.js} +2 -2
- package/public/assets/{memory-DNsCl6UX.js → memory-BV17qtO4.js} +2 -2
- package/public/assets/{messages-DopU1L1X.js → messages-Bm_1AqSA.js} +2 -2
- package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-Dg-vfP09.js} +2 -2
- package/public/assets/{overview-C1DCTzzV.js → overview-DcYCrvlr.js} +2 -2
- package/public/assets/{pairs-DE_m6rrE.js → pairs-8o9nuG3V.js} +2 -2
- package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-BsRyBMea.js} +2 -2
- package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-DETKFn_E.js} +2 -2
- package/public/assets/{projects-Br2Bd2h1.js → projects-M_m6AHws.js} +2 -2
- package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-C_mNCgeL.js} +2 -2
- package/public/assets/provider-display-BEeLXC_D.js +2 -0
- package/public/assets/{registry-Cw38_6gW.js → registry-DDHHHE5N.js} +2 -2
- package/public/assets/run-card-Dt4I4jaP.js +2 -0
- package/public/assets/{secrets-DxI-vzSq.js → secrets-BkVwxI5O.js} +2 -2
- package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-Crq-6a-B.js} +2 -2
- package/public/assets/{settings-CKZTVGIx.js → settings-CYcavMFu.js} +2 -2
- package/public/assets/shipping-H6gzEeZB.js +2 -0
- package/public/assets/{stats-DyUBKyWI.js → stats-BJVeWkpV.js} +2 -2
- package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-BcHyw1Vw.js} +2 -2
- package/public/assets/store-DKPDIZBe.js +9 -0
- package/public/assets/{tasks-Bh7Chr4-.js → tasks-qJjlQPpx.js} +2 -2
- package/public/assets/{teams-bowI7qVY.js → teams-Cd5Sk1uX.js} +2 -2
- package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-CpY5_m7W.js} +3 -3
- package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-DI_2llFR.js} +2 -2
- package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-0yJA_buM.js} +2 -2
- package/public/assets/{use-decisions-samMQhQf.js → use-decisions-uJXHf34W.js} +2 -2
- package/public/assets/{use-project-list-59LheVP7.js → use-project-list-CpWl6eHQ.js} +2 -2
- package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-BdxBW8mL.js} +2 -2
- package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-3IndfpOc.js} +2 -2
- package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-CIVg7XB1.js} +2 -2
- package/public/assets/{work-queue-CSvpuNLz.js → work-queue-B9rnLwHZ.js} +2 -2
- package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-X8wxyhzC.js} +2 -2
- package/public/assets/{workflows-DdmxRSyg.js → workflows-o5YyMFW7.js} +2 -2
- package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CzEGoAng.js} +2 -2
- package/public/cosmos.html +4 -4
- package/public/index.html +28 -28
- package/runner/plugins/claude/.claude-plugin/plugin.json +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 +2 -1
- 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/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/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 +1 -11
- 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/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 +8 -3
- 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/injection-sweep-pacing.ts +6 -8
- 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 +14 -15
- 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-secret-files.ts +1 -1
- package/src/routes/hosts.ts +11 -11
- package/src/routes/inbox.ts +3 -2
- package/src/routes/index.ts +2 -1
- 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 +1 -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 +2 -2
- package/src/routes/secrets-import.ts +1 -1
- package/src/routes/secrets-vault.ts +7 -7
- 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-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 +21 -22
- 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/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-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/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/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/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/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,
|
package/src/runtime-tokens.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stringValue } 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";
|
|
@@ -13,11 +13,9 @@ import { getLaunchFact, recordLaunchFact, type LaunchFact } from "./db/launch-fa
|
|
|
13
13
|
import type { ComponentToken, SpawnApprovalMode, TokenConstraints, TokenRecord } from "./types";
|
|
14
14
|
import { listManagedOrchestratorsForAgent } from "./orchestrator-lookup";
|
|
15
15
|
import { sameHostIdentity } from "./utils";
|
|
16
|
-
import {
|
|
17
|
-
import { canonicalAgentProfileName, hasScope } from "@humwick/sdk/types";
|
|
16
|
+
import { canonicalAgentProfileName } from "@humwick/sdk/types";
|
|
18
17
|
import { COORDINATOR_PROFILE_NAMES } from "./agent-system-tags";
|
|
19
18
|
import { SECRETS_DEFAULT_PROFILES, SECRETS_TOOL_GROUP, SECRETS_WRITE_SCOPE } from "./secrets-broker/config";
|
|
20
|
-
import { INCLUDED_TOOL_SCOPE_NEEDS, RELAY_TOOL_GROUPS, resolveRelayTools, SELF_CARE_TOOL_GROUP } from "./mcp/tool-groups";
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* Who vouches for a runner launch's authority (#3110 review). A launch without a positively identified
|
|
@@ -194,8 +192,8 @@ export function issueOrchestratorRuntimeToken(input: {
|
|
|
194
192
|
}
|
|
195
193
|
|
|
196
194
|
/** #1870 S6c — the mint on the writer thread, for the async routes. */
|
|
197
|
-
export function issueOrchestratorRuntimeTokenAsync(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): Promise<RuntimeTokenResult> {
|
|
198
|
-
return createTokenAsync(orchestratorTokenSpec(input));
|
|
195
|
+
export async function issueOrchestratorRuntimeTokenAsync(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): Promise<RuntimeTokenResult> {
|
|
196
|
+
return (await createTokenAsync(orchestratorTokenSpec(input)));
|
|
199
197
|
}
|
|
200
198
|
|
|
201
199
|
function orchestratorTokenSpec(input: Parameters<typeof issueOrchestratorRuntimeToken>[0]): CreateTokenInput {
|
|
@@ -269,21 +267,15 @@ function runnerTokenSpec(input: RunnerRuntimeTokenInput): { token: CreateTokenIn
|
|
|
269
267
|
const grant = authority.kind === "successor" ? successorGrant(input.agentId, profile)
|
|
270
268
|
: { ...deriveRunnerGrant(profile, authority.kind === "delegated" ? { agentInitiated, callerScopes: authority.callerScopes, parentAgentId: input.spawnedBy, projectId: input.projectId, maxSpawnedAgents: input.maxSpawnedAgents } : { agentInitiated }), agentInitiated };
|
|
271
269
|
const { canSpawn, maxSpawnedAgents, toolGroups, toolInclude, toolExclude } = grant;
|
|
272
|
-
|
|
273
|
-
for (const authoredName of toolInclude ?? []) {
|
|
274
|
-
const name = registryToolName(authoredName);
|
|
275
|
-
if (!selected.has(name)) continue;
|
|
276
|
-
const needs = INCLUDED_TOOL_SCOPE_NEEDS[name];
|
|
277
|
-
if (RELAY_TOOL_GROUPS.kernel!.includes(name) || (needs && !needs.some((need) => hasScope(grant.scope, need)))) {
|
|
278
|
-
throw new Error(`relay.tools.include: ${name} requires ${needs?.join(" or ") ?? "kernel control scopes"}; profile ${profile} cannot grant that scope under this launch authority`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
270
|
+
// Includes choose the surface, not launch authority. MCP scope filtering hides unavailable tools (#3817).
|
|
281
271
|
// #1570 — provider-split: a Codex worker eager-loads every listed tool schema every turn (no
|
|
282
272
|
// client-native deferral), so trim its always-loaded MCP surface to the lean worker set. Classified
|
|
283
273
|
// by explicit worker profile (not tool-group shape — reviewers share the groups but aren't workers).
|
|
284
274
|
const toolAllowlist = toolInclude !== undefined || toolExclude !== undefined
|
|
285
275
|
? undefined : toolAllowlistForRunner(input.provider, profile);
|
|
286
|
-
|
|
276
|
+
// #3804 — an agent-spawned child stays on its launch host, except a delegated sub-coordinator: its home host is
|
|
277
|
+
// often full, so it places spawns wherever its parent can unless the launch pinned it.
|
|
278
|
+
const hostPinned = grant.agentInitiated && !grant.delegated ? true : (input.hostPinned ?? hostPinForProfile(input.profile));
|
|
287
279
|
const subject = input.policyName
|
|
288
280
|
? `runner:policy:${input.policyName}`
|
|
289
281
|
: input.spawnRequestId
|
|
@@ -339,7 +331,7 @@ export function issueRunnerRuntimeTokenAsync(input: RunnerRuntimeTokenInput): Pr
|
|
|
339
331
|
return createRunnerTokenAsync(spec.token, spec.launchFact ? { agentId: input.agentId, fact: spec.launchFact } : undefined);
|
|
340
332
|
}
|
|
341
333
|
|
|
342
|
-
export function issueInteractiveRunnerRuntimeToken(input: {
|
|
334
|
+
export async function issueInteractiveRunnerRuntimeToken(input: {
|
|
343
335
|
provider: string;
|
|
344
336
|
cwd: string;
|
|
345
337
|
runnerId: string;
|
|
@@ -351,9 +343,9 @@ export function issueInteractiveRunnerRuntimeToken(input: {
|
|
|
351
343
|
* authorization, so it must ride the signature rather than the caller-writable agent card. */
|
|
352
344
|
approvalMode?: SpawnApprovalMode;
|
|
353
345
|
createdBy?: string;
|
|
354
|
-
}): RuntimeTokenResult {
|
|
346
|
+
}): Promise<RuntimeTokenResult> {
|
|
355
347
|
const agentId = input.agentId ?? input.runnerId;
|
|
356
|
-
return
|
|
348
|
+
return (await createTokenAsync({
|
|
357
349
|
profileId: "provider-interactive",
|
|
358
350
|
sub: `runner:interactive:${input.host ?? "local"}:${input.runnerId}`,
|
|
359
351
|
role: "provider",
|
|
@@ -368,22 +360,22 @@ export function issueInteractiveRunnerRuntimeToken(input: {
|
|
|
368
360
|
canDelegate: false,
|
|
369
361
|
},
|
|
370
362
|
createdBy: input.createdBy ?? "interactive-runner",
|
|
371
|
-
});
|
|
363
|
+
}));
|
|
372
364
|
}
|
|
373
365
|
|
|
374
|
-
export function issueMcpRuntimeToken(input: {
|
|
366
|
+
export async function issueMcpRuntimeToken(input: {
|
|
375
367
|
sessionId: string;
|
|
376
368
|
agentId?: string;
|
|
377
369
|
targets?: string[];
|
|
378
370
|
channels?: string[];
|
|
379
371
|
createdBy?: string;
|
|
380
|
-
}): RuntimeTokenResult {
|
|
372
|
+
}): Promise<RuntimeTokenResult> {
|
|
381
373
|
const targets = input.targets?.length
|
|
382
374
|
? input.targets
|
|
383
375
|
: input.agentId
|
|
384
376
|
? ["user", input.agentId]
|
|
385
377
|
: undefined;
|
|
386
|
-
return
|
|
378
|
+
return (await createTokenAsync({
|
|
387
379
|
profileId: "mcp-client",
|
|
388
380
|
sub: input.agentId ? `mcp:${input.agentId}:${input.sessionId}` : `mcp:session:${input.sessionId}`,
|
|
389
381
|
role: "mcp",
|
|
@@ -394,16 +386,16 @@ export function issueMcpRuntimeToken(input: {
|
|
|
394
386
|
canDelegate: false,
|
|
395
387
|
},
|
|
396
388
|
createdBy: input.createdBy ?? "runtime",
|
|
397
|
-
});
|
|
389
|
+
}));
|
|
398
390
|
}
|
|
399
391
|
|
|
400
|
-
export function issueIntegrationRuntimeToken(input: {
|
|
392
|
+
export async function issueIntegrationRuntimeToken(input: {
|
|
401
393
|
name: string;
|
|
402
394
|
targets?: string[];
|
|
403
395
|
channels?: string[];
|
|
404
396
|
createdBy?: string;
|
|
405
|
-
}): RuntimeTokenResult {
|
|
406
|
-
return
|
|
397
|
+
}): Promise<RuntimeTokenResult> {
|
|
398
|
+
return (await createTokenAsync({
|
|
407
399
|
profileId: "integration-client",
|
|
408
400
|
sub: input.name,
|
|
409
401
|
role: "integration",
|
|
@@ -414,7 +406,7 @@ export function issueIntegrationRuntimeToken(input: {
|
|
|
414
406
|
canDelegate: false,
|
|
415
407
|
},
|
|
416
408
|
createdBy: input.createdBy ?? "runtime",
|
|
417
|
-
});
|
|
409
|
+
}));
|
|
418
410
|
}
|
|
419
411
|
|
|
420
412
|
export function runnerRuntimeTokenEnv(input: RunnerRuntimeTokenInput): Record<string, string> {
|