@moretea-labs/forge 1.8.0 → 1.8.1
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/AGENTS.md +70 -7
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +228 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
- package/adapters/computer/chatgpt-extension-target.ts +463 -0
- package/adapters/computer/chatgpt-target-router.ts +103 -0
- package/adapters/computer/chrome-extension/background.js +385 -0
- package/adapters/computer/chrome-extension/content.js +378 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +30 -0
- package/adapters/computer/desktop-operator-discovery.ts +11 -31
- package/adapters/computer/interaction-target-authority.ts +142 -18
- package/adapters/mcp/auth.ts +0 -1
- package/adapters/mcp/instructions.ts +5 -4
- package/adapters/mcp/multi-repository.ts +3 -19
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
- package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +18 -504
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +67 -23
- package/adapters/mcp/setup.ts +1 -3
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
- package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
- package/adapters/mcp/toolset-names.ts +5 -0
- package/adapters/mcp/toolset.ts +6 -5
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +154 -677
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/skill-version.json +2 -2
- package/docs/architecture/CURRENT.md +111 -41
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/domains/verification.md +1 -0
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/forge-plugin-management.md +2 -2
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +1 -1
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +2 -0
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +14 -6
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +25 -14
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +17 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +25 -63
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +48 -136
- package/scripts/check-runtime-architecture.mjs +622 -217
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/lib/project-init-lib.sh +3 -0
- package/scripts/open-source-audit-allowlist.txt +1 -2
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +0 -1
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-mcp-http-runtime.ts +5 -0
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +11 -34
- package/src/cli/agent-jobs/integration.ts +18 -49
- package/src/cli/agent-jobs/job-manager.ts +7 -3
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- package/src/cli/commands/recovery.ts +135 -121
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +4 -43
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/context/source-materializer.ts +81 -100
- package/src/cli/controller/context-pack.ts +10 -8
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +24 -51
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +39 -1
- package/src/cli/editing/edit-session.ts +1 -7
- package/src/cli/local-bridge/console-automations.ts +18 -13
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +64 -191
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +35 -16
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/registry.ts +77 -11
- package/src/cli/repositories/structured-git.ts +62 -2
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/semantic-navigation-contract.ts +9 -0
- package/src/runtime/context/semantic-navigation.ts +6 -34
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
- package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
- package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/execution/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +35 -2
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/workers/executor.ts +2 -6
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +85 -28
- package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
- package/src/runtime/plugins/browser-adapter.ts +757 -34
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
- package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +16 -0
- package/src/runtime/plugins/browser-runtime.ts +14 -1
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +222 -11
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/gmail-adapter.ts +23 -2
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +14 -7
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios-adapter.ts +1 -1
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -2
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +602 -428
- package/src/runtime/plugins/types.ts +20 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/release-coordinator.ts +296 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +105 -19
- package/src/runtime/root/package-runtime-release.ts +117 -18
- package/src/runtime/root/package-runtime-service.ts +17 -11
- package/src/runtime/root/release-execution-canary.ts +44 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -6
- package/src/runtime/root/release-materialize.ts +372 -28
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +159 -27
- package/src/runtime/root/scheduled-controller-composition.ts +28 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +36 -33
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -3
- package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3702 -342
- package/src/runtime/standalone-recovery/entry.ts +469 -39
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
- package/src/runtime/workflow/schedules/engine.ts +138 -172
- package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +201 -0
- package/supervisor/control-plane.ts +868 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +80 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +786 -0
- package/supervisor/native-messaging/host.ts +39 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +202 -0
- package/supervisor/server.ts +404 -0
- package/supervisor/store.ts +1451 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +255 -0
- package/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- package/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -150
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/stage-runtime-release.ts +0 -44
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -3,6 +3,11 @@ import type { AssistantContextSnapshot, ControllerRoundRelayRecord } from '../..
|
|
|
3
3
|
import { prepareAssistantWorkContext } from '../context/assistant-work-context';
|
|
4
4
|
import { renderAssistantContext, type AssistantContextResolution } from '../context/assistant-context';
|
|
5
5
|
import { getChatgptWorkConversationBinding } from '../../../adapters/chatgpt/work-conversation-binding-store';
|
|
6
|
+
import {
|
|
7
|
+
chatgptControllerProviderBindingId,
|
|
8
|
+
upsertChatgptControllerBinding,
|
|
9
|
+
type ChatgptControllerBindingPayload,
|
|
10
|
+
} from '../../../adapters/chatgpt/controller-binding-store';
|
|
6
11
|
import {
|
|
7
12
|
buildChatgptControllerRoundPrompt,
|
|
8
13
|
chatgptControllerRoundBindingAuthorizesRecovery,
|
|
@@ -22,6 +27,18 @@ export interface ChatgptControllerRoundBindingSnapshot {
|
|
|
22
27
|
bindingId?: string;
|
|
23
28
|
browserSessionId?: string;
|
|
24
29
|
conversationUrl?: string;
|
|
30
|
+
authorizationGrantRefs?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function chatgptControllerRoundBindingId(workId: string): string {
|
|
34
|
+
return chatgptControllerProviderBindingId(workId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function upsertChatgptControllerRoundTransportBinding(
|
|
38
|
+
store: ControllerRoundCompositionStore,
|
|
39
|
+
input: Omit<ChatgptControllerBindingPayload, 'schemaVersion' | 'bindingId' | 'repoId' | 'createdAt' | 'updatedAt'>,
|
|
40
|
+
) {
|
|
41
|
+
return upsertChatgptControllerBinding(store, input);
|
|
25
42
|
}
|
|
26
43
|
|
|
27
44
|
export function chatgptControllerRoundBinding(
|
|
@@ -34,6 +51,7 @@ export function chatgptControllerRoundBinding(
|
|
|
34
51
|
bindingId: binding.bindingId,
|
|
35
52
|
browserSessionId: binding.latestBrowserSessionId,
|
|
36
53
|
conversationUrl: binding.conversationUrl,
|
|
54
|
+
authorizationGrantRefs: [...(binding.authorizationGrantRefs ?? [])],
|
|
37
55
|
};
|
|
38
56
|
}
|
|
39
57
|
|
|
@@ -65,6 +83,7 @@ export function recordChatgptControllerRoundTabSettlement(
|
|
|
65
83
|
relayScopeId: string;
|
|
66
84
|
status: ChatgptControllerRoundSettlementStatus;
|
|
67
85
|
error?: string;
|
|
86
|
+
attemptIdentity?: string;
|
|
68
87
|
},
|
|
69
88
|
): void {
|
|
70
89
|
recordChatgptControllerRoundSettlement(store, input);
|
|
@@ -79,7 +98,7 @@ function controllerAssistantContextSnapshot(resolution: AssistantContextResoluti
|
|
|
79
98
|
sourceRevision: item.provenance.sourceRevision,
|
|
80
99
|
}));
|
|
81
100
|
const identity = {
|
|
82
|
-
projectId: resolution.projectId,
|
|
101
|
+
...(resolution.projectId ? { projectId: resolution.projectId } : {}),
|
|
83
102
|
items,
|
|
84
103
|
gaps: resolution.gaps,
|
|
85
104
|
missingRequiredSources: resolution.missingRequiredSources,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export type RuntimeRemoteControllerKind = 'chatgpt' | 'mcp';
|
|
2
|
+
|
|
3
|
+
export interface RuntimeDeploymentTopology {
|
|
4
|
+
schemaVersion: 1;
|
|
5
|
+
remoteControllers: RuntimeRemoteControllerKind[];
|
|
6
|
+
capabilityIntents: string[];
|
|
7
|
+
persistentRuntimeRequired: boolean;
|
|
8
|
+
components: {
|
|
9
|
+
workflowSupervisor: boolean;
|
|
10
|
+
workflowSupervisorNativeBrowser: boolean;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const REMOTE_CONTROLLERS = new Set<RuntimeRemoteControllerKind>(['chatgpt', 'mcp']);
|
|
15
|
+
|
|
16
|
+
function normalizeCapabilityIntent(value: string): string {
|
|
17
|
+
const normalized = value.trim().toLowerCase();
|
|
18
|
+
if (!/^[-a-z0-9_.]{3,128}$/.test(normalized)) throw new Error(`RUNTIME_TOPOLOGY_CAPABILITY_INTENT_INVALID: ${value}`);
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizedRemoteControllers(values: readonly string[] | undefined): RuntimeRemoteControllerKind[] {
|
|
23
|
+
return Array.from(new Set(
|
|
24
|
+
(values ?? [])
|
|
25
|
+
.map((value) => value.trim().toLowerCase())
|
|
26
|
+
.filter((value): value is RuntimeRemoteControllerKind => REMOTE_CONTROLLERS.has(value as RuntimeRemoteControllerKind)),
|
|
27
|
+
)).sort();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function deriveRuntimeDeploymentTopology(input: {
|
|
31
|
+
controllers?: readonly string[];
|
|
32
|
+
capabilityIntents?: readonly string[];
|
|
33
|
+
}): RuntimeDeploymentTopology {
|
|
34
|
+
const remoteControllers = normalizedRemoteControllers(input.controllers);
|
|
35
|
+
const capabilityIntents = Array.from(new Set(
|
|
36
|
+
(input.capabilityIntents ?? []).map(normalizeCapabilityIntent),
|
|
37
|
+
)).sort();
|
|
38
|
+
const chatgpt = remoteControllers.includes('chatgpt');
|
|
39
|
+
return {
|
|
40
|
+
schemaVersion: 1,
|
|
41
|
+
remoteControllers,
|
|
42
|
+
capabilityIntents,
|
|
43
|
+
persistentRuntimeRequired: remoteControllers.length > 0,
|
|
44
|
+
components: {
|
|
45
|
+
// Requirement-level ChatGPT continuation uses Supervisor Core as its
|
|
46
|
+
// outer-turn authority. Generic remote MCP transport does not.
|
|
47
|
+
workflowSupervisor: chatgpt,
|
|
48
|
+
// ChatGPT Work has one controlled outer-turn executor: the Supervisor's
|
|
49
|
+
// Computer adapter. It owns exact-conversation delivery and its durable
|
|
50
|
+
// effect ledger; Scheduler must never fall back to direct Browser delivery.
|
|
51
|
+
workflowSupervisorNativeBrowser: chatgpt,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Missing topology means an installation predates this contract. Preserve the
|
|
58
|
+
* complete ChatGPT Supervisor composition during upgrade.
|
|
59
|
+
*/
|
|
60
|
+
export function legacyRuntimeDeploymentTopology(): RuntimeDeploymentTopology {
|
|
61
|
+
return deriveRuntimeDeploymentTopology({ controllers: ['chatgpt'] });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function normalizeRuntimeDeploymentTopology(value: unknown): RuntimeDeploymentTopology {
|
|
65
|
+
if (value === undefined || value === null) return legacyRuntimeDeploymentTopology();
|
|
66
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('RUNTIME_TOPOLOGY_INVALID');
|
|
67
|
+
const record = value as Record<string, unknown>;
|
|
68
|
+
if (record.schemaVersion !== 1) throw new Error('RUNTIME_TOPOLOGY_VERSION_UNSUPPORTED');
|
|
69
|
+
if (!Array.isArray(record.remoteControllers)
|
|
70
|
+
|| record.remoteControllers.some((entry) => typeof entry !== 'string' || !REMOTE_CONTROLLERS.has(entry as RuntimeRemoteControllerKind))) {
|
|
71
|
+
throw new Error('RUNTIME_TOPOLOGY_REMOTE_CONTROLLERS_INVALID');
|
|
72
|
+
}
|
|
73
|
+
if (!Array.isArray(record.capabilityIntents)
|
|
74
|
+
|| record.capabilityIntents.some((entry) => typeof entry !== 'string')) {
|
|
75
|
+
throw new Error('RUNTIME_TOPOLOGY_CAPABILITY_INTENTS_INVALID');
|
|
76
|
+
}
|
|
77
|
+
const remoteControllers = normalizedRemoteControllers(record.remoteControllers as string[]);
|
|
78
|
+
const capabilityIntents = Array.from(new Set(
|
|
79
|
+
(record.capabilityIntents as string[]).map(normalizeCapabilityIntent),
|
|
80
|
+
)).sort();
|
|
81
|
+
const canonical = deriveRuntimeDeploymentTopology({ controllers: remoteControllers, capabilityIntents });
|
|
82
|
+
const components = record.components;
|
|
83
|
+
if (!components || typeof components !== 'object' || Array.isArray(components)) throw new Error('RUNTIME_TOPOLOGY_COMPONENTS_REQUIRED');
|
|
84
|
+
const componentRecord = components as Record<string, unknown>;
|
|
85
|
+
if (typeof componentRecord.workflowSupervisor !== 'boolean'
|
|
86
|
+
|| typeof componentRecord.workflowSupervisorNativeBrowser !== 'boolean') {
|
|
87
|
+
throw new Error('RUNTIME_TOPOLOGY_COMPONENTS_INVALID');
|
|
88
|
+
}
|
|
89
|
+
if (componentRecord.workflowSupervisor !== canonical.components.workflowSupervisor
|
|
90
|
+
|| record.persistentRuntimeRequired !== canonical.persistentRuntimeRequired) {
|
|
91
|
+
throw new Error('RUNTIME_TOPOLOGY_DERIVATION_MISMATCH');
|
|
92
|
+
}
|
|
93
|
+
// The flag is derived, so stale persisted topology cannot disable the sole
|
|
94
|
+
// autonomous delivery owner or reintroduce an alternate Browser path.
|
|
95
|
+
return canonical;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function parseRuntimeDeploymentTopologyArgument(value: string | undefined): RuntimeDeploymentTopology {
|
|
99
|
+
if (!value?.trim()) return legacyRuntimeDeploymentTopology();
|
|
100
|
+
try {
|
|
101
|
+
return normalizeRuntimeDeploymentTopology(JSON.parse(value));
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
104
|
+
throw new Error(`RUNTIME_TOPOLOGY_ARGUMENT_INVALID: ${detail}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -3,6 +3,7 @@ import { resolve } from 'path';
|
|
|
3
3
|
import { pathToFileURL } from 'url';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { CanonicalForgeRuntime } from './runtime';
|
|
6
|
+
import { parseRuntimeDeploymentTopologyArgument } from './deployment-topology';
|
|
6
7
|
|
|
7
8
|
interface CliOptions {
|
|
8
9
|
controllerHome: string;
|
|
@@ -11,7 +12,7 @@ interface CliOptions {
|
|
|
11
12
|
host: string;
|
|
12
13
|
port: string;
|
|
13
14
|
authTokenFile: string;
|
|
14
|
-
|
|
15
|
+
deploymentTopology?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function readAuthToken(path: string): string {
|
|
@@ -29,7 +30,7 @@ export async function runCanonicalRuntimeCli(argv = process.argv): Promise<void>
|
|
|
29
30
|
.requiredOption('--host <host>', 'MCP listener host')
|
|
30
31
|
.requiredOption('--port <port>', 'MCP listener port')
|
|
31
32
|
.requiredOption('--auth-token-file <path>', 'Bearer token file')
|
|
32
|
-
.option('--
|
|
33
|
+
.option('--deployment-topology <json>', 'Normalized product-level Runtime deployment topology');
|
|
33
34
|
command.parse(argv);
|
|
34
35
|
const options = command.opts<CliOptions>();
|
|
35
36
|
const port = Number(options.port);
|
|
@@ -40,8 +41,8 @@ export async function runCanonicalRuntimeCli(argv = process.argv): Promise<void>
|
|
|
40
41
|
releaseManifestPath: resolve(options.releaseManifest),
|
|
41
42
|
host: options.host,
|
|
42
43
|
port,
|
|
44
|
+
topology: parseRuntimeDeploymentTopologyArgument(options.deploymentTopology),
|
|
43
45
|
authToken: readAuthToken(options.authTokenFile),
|
|
44
|
-
exclusiveWorkId: options.exclusiveWorkId,
|
|
45
46
|
});
|
|
46
47
|
await runtime.start();
|
|
47
48
|
process.stderr.write(`${JSON.stringify({
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, realpathSync } from 'fs';
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from 'path';
|
|
4
|
+
import { inspectControlPlaneDatabaseFile, type ControlPlaneDatabaseInspection } from '../control-plane/persistence/sqlite-store';
|
|
5
|
+
import { loadRuntimeReleaseManifest } from './release-manifest';
|
|
6
|
+
import { forgeRuntimeServicePaths, validateForgeRuntimeServiceConfig, type ForgeRuntimeServiceConfig } from './service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A Recovery-owned, bounded restore point. The release is immutable Runtime
|
|
10
|
+
* authority; the SQLite copy and service contract are the state and bootstrap
|
|
11
|
+
* material needed to make that release useful after the Kernel is dead.
|
|
12
|
+
*/
|
|
13
|
+
export interface KnownGoodRecoveryBundle {
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
attestationId: string;
|
|
16
|
+
root: string;
|
|
17
|
+
database: {
|
|
18
|
+
path: string;
|
|
19
|
+
sha256: string;
|
|
20
|
+
schemaVersion: number;
|
|
21
|
+
recordCount: number;
|
|
22
|
+
auditEventCount: number;
|
|
23
|
+
};
|
|
24
|
+
serviceContract: {
|
|
25
|
+
path: string;
|
|
26
|
+
sha256: string;
|
|
27
|
+
};
|
|
28
|
+
createdAt: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface KnownGoodReleaseIdentity {
|
|
32
|
+
path: string;
|
|
33
|
+
revision: string;
|
|
34
|
+
artifactIdentity: string;
|
|
35
|
+
manifestSha256: string;
|
|
36
|
+
workerProtocolVersion: number;
|
|
37
|
+
controllerHome?: string;
|
|
38
|
+
recoveryBundle?: KnownGoodRecoveryBundle;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface InspectedKnownGoodRecoveryBundle {
|
|
42
|
+
releaseRoot: string;
|
|
43
|
+
database: ControlPlaneDatabaseInspection;
|
|
44
|
+
serviceConfig: ForgeRuntimeServiceConfig;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SHA256_FILE_CHUNK_BYTES = 1024 * 1024;
|
|
48
|
+
|
|
49
|
+
/** Hash arbitrarily large Recovery artifacts without allocating the whole file. */
|
|
50
|
+
export function sha256FileBounded(path: string): string {
|
|
51
|
+
const descriptor = openSync(path, 'r');
|
|
52
|
+
const hash = createHash('sha256');
|
|
53
|
+
const buffer = Buffer.allocUnsafe(SHA256_FILE_CHUNK_BYTES);
|
|
54
|
+
try {
|
|
55
|
+
for (;;) {
|
|
56
|
+
const bytesRead = readSync(descriptor, buffer, 0, buffer.length, null);
|
|
57
|
+
if (bytesRead === 0) break;
|
|
58
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
59
|
+
}
|
|
60
|
+
return hash.digest('hex');
|
|
61
|
+
} finally {
|
|
62
|
+
closeSync(descriptor);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function canonical(path: string): string {
|
|
67
|
+
try { return realpathSync(path); }
|
|
68
|
+
catch { return resolve(path); }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function directChild(root: string, path: string): boolean {
|
|
72
|
+
return canonical(dirname(path)) === canonical(root);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function inside(root: string, path: string): boolean {
|
|
76
|
+
const value = relative(canonical(root), canonical(path));
|
|
77
|
+
return value === '' || (!value.startsWith('..') && value !== '..');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function requiredText(value: unknown, code: string): string {
|
|
81
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error(code);
|
|
82
|
+
return value.trim();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function requiredPositiveInteger(value: unknown, code: string): number {
|
|
86
|
+
if (!Number.isInteger(value) || Number(value) < 1) throw new Error(code);
|
|
87
|
+
return Number(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function validateRecoveryBundleShape(entry: KnownGoodReleaseIdentity, controllerHome: string): KnownGoodRecoveryBundle {
|
|
91
|
+
const bundle = entry.recoveryBundle;
|
|
92
|
+
if (!bundle || bundle.schemaVersion !== 1) throw new Error('KNOWN_GOOD_RECOVERY_BUNDLE_MISSING');
|
|
93
|
+
const home = resolve(controllerHome);
|
|
94
|
+
const root = resolve(bundle.root);
|
|
95
|
+
const expectedBundlesRoot = resolve(home, 'recovery', 'bundles', 'known-good');
|
|
96
|
+
const attestationId = requiredText(bundle.attestationId, 'KNOWN_GOOD_RECOVERY_BUNDLE_ID_INVALID');
|
|
97
|
+
if (basename(root) !== attestationId || !directChild(expectedBundlesRoot, root)) {
|
|
98
|
+
throw new Error('KNOWN_GOOD_RECOVERY_BUNDLE_OUTSIDE_AUTHORITY');
|
|
99
|
+
}
|
|
100
|
+
if (!/^[-a-zA-Z0-9_]{8,160}$/.test(attestationId)) throw new Error('KNOWN_GOOD_RECOVERY_BUNDLE_ID_INVALID');
|
|
101
|
+
if (!inside(root, bundle.database.path) || basename(resolve(bundle.database.path)) !== 'controller.sqlite') {
|
|
102
|
+
throw new Error('KNOWN_GOOD_RECOVERY_DATABASE_PATH_INVALID');
|
|
103
|
+
}
|
|
104
|
+
if (!inside(root, bundle.serviceContract.path) || basename(resolve(bundle.serviceContract.path)) !== 'service-contract.json') {
|
|
105
|
+
throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_CONTRACT_PATH_INVALID');
|
|
106
|
+
}
|
|
107
|
+
requiredText(bundle.database.sha256, 'KNOWN_GOOD_RECOVERY_DATABASE_HASH_INVALID');
|
|
108
|
+
requiredText(bundle.serviceContract.sha256, 'KNOWN_GOOD_RECOVERY_SERVICE_CONTRACT_HASH_INVALID');
|
|
109
|
+
requiredPositiveInteger(bundle.database.schemaVersion, 'KNOWN_GOOD_RECOVERY_DATABASE_SCHEMA_INVALID');
|
|
110
|
+
return {
|
|
111
|
+
...bundle,
|
|
112
|
+
attestationId,
|
|
113
|
+
root,
|
|
114
|
+
database: { ...bundle.database, path: resolve(bundle.database.path) },
|
|
115
|
+
serviceContract: { ...bundle.serviceContract, path: resolve(bundle.serviceContract.path) },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validate the exact three-way restore invariant used by both Recovery and
|
|
121
|
+
* release retention. Legacy evidence without a Recovery bundle is historical
|
|
122
|
+
* audit data only: it must never be promoted to recoverable authority.
|
|
123
|
+
*/
|
|
124
|
+
export function inspectKnownGoodRecoveryBundle(
|
|
125
|
+
controllerHome: string,
|
|
126
|
+
entry: KnownGoodReleaseIdentity,
|
|
127
|
+
): InspectedKnownGoodRecoveryBundle {
|
|
128
|
+
const home = resolve(controllerHome);
|
|
129
|
+
if (entry.controllerHome && canonical(entry.controllerHome) !== canonical(home)) {
|
|
130
|
+
throw new Error('KNOWN_GOOD_RECOVERY_CONTROLLER_HOME_MISMATCH');
|
|
131
|
+
}
|
|
132
|
+
const manifestPath = resolve(requiredText(entry.path, 'KNOWN_GOOD_RELEASE_MANIFEST_PATH_INVALID'));
|
|
133
|
+
const releasesRoot = resolve(home, 'runtime', 'releases');
|
|
134
|
+
const releaseRoot = dirname(manifestPath);
|
|
135
|
+
if (
|
|
136
|
+
basename(manifestPath) !== 'manifest.json'
|
|
137
|
+
|| basename(releaseRoot) !== entry.revision
|
|
138
|
+
|| !directChild(releasesRoot, releaseRoot)
|
|
139
|
+
|| !existsSync(manifestPath)
|
|
140
|
+
) throw new Error('KNOWN_GOOD_RELEASE_OUTSIDE_AUTHORITY');
|
|
141
|
+
const manifest = loadRuntimeReleaseManifest(manifestPath, home);
|
|
142
|
+
if (
|
|
143
|
+
manifest.releaseId !== entry.revision
|
|
144
|
+
|| manifest.artifactIdentity !== entry.artifactIdentity
|
|
145
|
+
|| manifest.workerProtocolVersion !== entry.workerProtocolVersion
|
|
146
|
+
|| sha256FileBounded(manifestPath) !== entry.manifestSha256
|
|
147
|
+
) throw new Error('KNOWN_GOOD_RELEASE_IDENTITY_MISMATCH');
|
|
148
|
+
|
|
149
|
+
const bundle = validateRecoveryBundleShape(entry, home);
|
|
150
|
+
if (!existsSync(bundle.database.path)) throw new Error('KNOWN_GOOD_RECOVERY_DATABASE_MISSING');
|
|
151
|
+
if (sha256FileBounded(bundle.database.path) !== bundle.database.sha256) throw new Error('KNOWN_GOOD_RECOVERY_DATABASE_HASH_MISMATCH');
|
|
152
|
+
const database = inspectControlPlaneDatabaseFile(bundle.database.path);
|
|
153
|
+
if (
|
|
154
|
+
database.schemaVersion !== bundle.database.schemaVersion
|
|
155
|
+
|| database.recordCount !== bundle.database.recordCount
|
|
156
|
+
|| database.auditEventCount !== bundle.database.auditEventCount
|
|
157
|
+
) throw new Error('KNOWN_GOOD_RECOVERY_DATABASE_INSPECTION_MISMATCH');
|
|
158
|
+
|
|
159
|
+
if (!existsSync(bundle.serviceContract.path)) throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_CONTRACT_MISSING');
|
|
160
|
+
if (sha256FileBounded(bundle.serviceContract.path) !== bundle.serviceContract.sha256) {
|
|
161
|
+
throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_CONTRACT_HASH_MISMATCH');
|
|
162
|
+
}
|
|
163
|
+
let rawContract: unknown;
|
|
164
|
+
try { rawContract = JSON.parse(readFileSync(bundle.serviceContract.path, 'utf8')); }
|
|
165
|
+
catch { throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_CONTRACT_INVALID'); }
|
|
166
|
+
if (!rawContract || typeof rawContract !== 'object' || Array.isArray(rawContract)) {
|
|
167
|
+
throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_CONTRACT_INVALID');
|
|
168
|
+
}
|
|
169
|
+
const contract = rawContract as Record<string, unknown>;
|
|
170
|
+
if (contract.schemaVersion !== 1 || !contract.serviceConfig || typeof contract.serviceConfig !== 'object' || Array.isArray(contract.serviceConfig)) {
|
|
171
|
+
throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_CONTRACT_INVALID');
|
|
172
|
+
}
|
|
173
|
+
const serviceConfig = validateForgeRuntimeServiceConfig(contract.serviceConfig as ForgeRuntimeServiceConfig);
|
|
174
|
+
if (canonical(serviceConfig.controllerHome) !== canonical(home)) throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_HOME_MISMATCH');
|
|
175
|
+
const expectedConfigPath = forgeRuntimeServicePaths(home).configPath;
|
|
176
|
+
if (canonical(requiredText(contract.configPath, 'KNOWN_GOOD_RECOVERY_SERVICE_PATH_INVALID')) !== canonical(expectedConfigPath)) {
|
|
177
|
+
throw new Error('KNOWN_GOOD_RECOVERY_SERVICE_PATH_INVALID');
|
|
178
|
+
}
|
|
179
|
+
return { releaseRoot, database, serviceConfig };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function knownGoodRecoveryBundleRoot(controllerHome: string): string {
|
|
183
|
+
return join(resolve(controllerHome), 'recovery', 'bundles', 'known-good');
|
|
184
|
+
}
|
|
@@ -35,7 +35,6 @@ export async function startConfiguredRuntimeLocalBridge(input: {
|
|
|
35
35
|
controllerHome: input.controllerHome,
|
|
36
36
|
host: local.host ?? '127.0.0.1',
|
|
37
37
|
port: local.port ?? 8766,
|
|
38
|
-
openBrowser: local.autoOpen === true,
|
|
39
38
|
mode: 'embedded',
|
|
40
39
|
});
|
|
41
40
|
const generation = readRuntimeGeneration(input.controllerHome)?.generation;
|
|
@@ -13,11 +13,19 @@ interface ManagedSession {
|
|
|
13
13
|
activeStreams: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
type RuntimeMcpRequestId = string | number;
|
|
17
|
+
interface ActiveRuntimeMcpPost {
|
|
18
|
+
response: Response;
|
|
19
|
+
requestIds: RuntimeMcpRequestId[];
|
|
20
|
+
transport?: NodeStreamableHTTPServerTransport;
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
export interface RuntimeMcpSessionSnapshot {
|
|
17
24
|
active: number;
|
|
18
25
|
maximum: number;
|
|
19
26
|
initializing: number;
|
|
20
27
|
protected: number;
|
|
28
|
+
activeRequests: number;
|
|
21
29
|
capacityAvailable: number;
|
|
22
30
|
capacityEvictions: number;
|
|
23
31
|
}
|
|
@@ -47,6 +55,8 @@ export interface StartRuntimeMcpTransportOptions {
|
|
|
47
55
|
onFatal?: (error: Error) => void;
|
|
48
56
|
/** Internal Runtime sessions are bounded even if a Gateway dies without DELETE. */
|
|
49
57
|
maximumSessions?: number;
|
|
58
|
+
/** Test/embedded override; production defaults to the bounded cutover grace. */
|
|
59
|
+
requestDrainTimeoutMs?: number;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
function authorized(request: Request, configuredToken: string): boolean {
|
|
@@ -76,6 +86,15 @@ function observesToolSurface(body: unknown): boolean {
|
|
|
76
86
|
});
|
|
77
87
|
}
|
|
78
88
|
|
|
89
|
+
function runtimeMcpRequestIds(body: unknown): RuntimeMcpRequestId[] {
|
|
90
|
+
const values = Array.isArray(body) ? body : [body];
|
|
91
|
+
return values.flatMap((value) => {
|
|
92
|
+
if (!value || typeof value !== 'object') return [];
|
|
93
|
+
const id = (value as Record<string, unknown>).id;
|
|
94
|
+
return typeof id === 'string' || typeof id === 'number' ? [id] : [];
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
79
98
|
function parseBody(body: unknown): unknown {
|
|
80
99
|
if (!Buffer.isBuffer(body)) return body;
|
|
81
100
|
try { return JSON.parse(body.toString('utf8')); } catch {
|
|
@@ -95,7 +114,16 @@ function authMiddleware(token: string) {
|
|
|
95
114
|
}
|
|
96
115
|
|
|
97
116
|
const SESSION_CLOSE_TIMEOUT_MS = 1_000;
|
|
98
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Cutover first withdraws admission, then gives already-admitted requests a
|
|
119
|
+
* bounded grace period that still fits inside the primary service stop budget.
|
|
120
|
+
* Requests that outlive it are explicitly settled as outcome-unknown before
|
|
121
|
+
* session/socket teardown, so an outer controller turn is never left waiting on
|
|
122
|
+
* a connection the Runtime is intentionally destroying.
|
|
123
|
+
*/
|
|
124
|
+
export const RUNTIME_MCP_REQUEST_DRAIN_TIMEOUT_MS = 5_000;
|
|
125
|
+
const CUTOVER_SETTLEMENT_DRAIN_TIMEOUT_MS = 250;
|
|
126
|
+
export const RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN = 'MCP_RUNTIME_CUTOVER_OUTCOME_UNKNOWN';
|
|
99
127
|
export const DEFAULT_RUNTIME_MCP_MAX_SESSIONS = 64;
|
|
100
128
|
|
|
101
129
|
function boundedRuntimeMcpMaximumSessions(value: number | undefined): number {
|
|
@@ -112,31 +140,41 @@ function closeServer(server: NodeHttpServer): Promise<void> {
|
|
|
112
140
|
});
|
|
113
141
|
}
|
|
114
142
|
|
|
115
|
-
function boundedDrain(wait: Promise<void>, timeoutMs: number): Promise<
|
|
116
|
-
if (timeoutMs <= 0) return
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
async function boundedDrain(wait: Promise<void>, timeoutMs: number): Promise<boolean> {
|
|
144
|
+
if (timeoutMs <= 0) return false;
|
|
145
|
+
let drained = false;
|
|
146
|
+
await Promise.race([
|
|
147
|
+
wait.then(() => { drained = true; }).catch(() => undefined),
|
|
119
148
|
new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),
|
|
120
149
|
]);
|
|
150
|
+
return drained;
|
|
121
151
|
}
|
|
122
152
|
|
|
123
153
|
export async function closeRuntimeMcpTransportResources(input: {
|
|
124
154
|
closeListener: () => Promise<void>;
|
|
125
155
|
closeSessions: Array<() => Promise<void>>;
|
|
126
156
|
waitForRequestDrain?: () => Promise<void>;
|
|
157
|
+
settleActiveRequests?: () => void | Promise<void>;
|
|
127
158
|
forceCloseConnections?: () => void;
|
|
128
159
|
requestDrainTimeoutMs?: number;
|
|
129
160
|
sessionCloseTimeoutMs?: number;
|
|
130
161
|
}): Promise<void> {
|
|
131
|
-
// Withdraw the
|
|
132
|
-
//
|
|
133
|
-
//
|
|
162
|
+
// Withdraw the listener first so no new request can enter. Existing calls get
|
|
163
|
+
// one bounded grace window. If that expires, settle still-open POST responses
|
|
164
|
+
// as explicit cutover/outcome-unknown before closing session transports. This
|
|
165
|
+
// preserves the no-blind-replay contract while preventing a controller turn
|
|
166
|
+
// from being stranded on a socket the Runtime is about to destroy.
|
|
134
167
|
const listenerClose = input.closeListener();
|
|
135
168
|
void listenerClose.catch(() => undefined);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
169
|
+
const requestDrain = input.waitForRequestDrain?.() ?? Promise.resolve();
|
|
170
|
+
const drained = await boundedDrain(
|
|
171
|
+
requestDrain,
|
|
172
|
+
Math.max(0, input.requestDrainTimeoutMs ?? RUNTIME_MCP_REQUEST_DRAIN_TIMEOUT_MS),
|
|
139
173
|
);
|
|
174
|
+
if (!drained) {
|
|
175
|
+
await input.settleActiveRequests?.();
|
|
176
|
+
await boundedDrain(requestDrain, CUTOVER_SETTLEMENT_DRAIN_TIMEOUT_MS);
|
|
177
|
+
}
|
|
140
178
|
const sessionDrain = Promise.allSettled(input.closeSessions.map(async (close) => await close()));
|
|
141
179
|
await boundedDrain(sessionDrain.then(() => undefined), Math.max(0, input.sessionCloseTimeoutMs ?? SESSION_CLOSE_TIMEOUT_MS));
|
|
142
180
|
input.forceCloseConnections?.();
|
|
@@ -152,12 +190,14 @@ export async function startRuntimeMcpTransport(
|
|
|
152
190
|
let initializingSessions = 0;
|
|
153
191
|
let capacityEvictions = 0;
|
|
154
192
|
let activeRequests = 0;
|
|
193
|
+
const activePosts = new Map<Response, ActiveRuntimeMcpPost>();
|
|
155
194
|
|
|
156
195
|
const sessionSnapshot = (): RuntimeMcpSessionSnapshot => ({
|
|
157
196
|
active: sessions.size,
|
|
158
197
|
maximum: maximumSessions,
|
|
159
198
|
initializing: initializingSessions,
|
|
160
199
|
protected: [...sessions.values()].filter((session) => session.activePosts > 0).length,
|
|
200
|
+
activeRequests,
|
|
161
201
|
capacityAvailable: Math.max(0, maximumSessions - sessions.size - initializingSessions),
|
|
162
202
|
capacityEvictions,
|
|
163
203
|
});
|
|
@@ -192,7 +232,7 @@ export async function startRuntimeMcpTransport(
|
|
|
192
232
|
managed.lastActivityAt = Date.now();
|
|
193
233
|
}
|
|
194
234
|
};
|
|
195
|
-
const
|
|
235
|
+
const postDrainWaiters = new Set<() => void>();
|
|
196
236
|
const app = express();
|
|
197
237
|
app.disable('x-powered-by');
|
|
198
238
|
app.use((_req, res, next) => {
|
|
@@ -202,18 +242,17 @@ export async function startRuntimeMcpTransport(
|
|
|
202
242
|
if (settled) return;
|
|
203
243
|
settled = true;
|
|
204
244
|
activeRequests = Math.max(0, activeRequests - 1);
|
|
205
|
-
if (activeRequests === 0) {
|
|
206
|
-
for (const resolve of requestDrainWaiters) resolve();
|
|
207
|
-
requestDrainWaiters.clear();
|
|
208
|
-
}
|
|
209
245
|
};
|
|
210
246
|
res.once('finish', settle);
|
|
211
247
|
res.once('close', settle);
|
|
212
248
|
next();
|
|
213
249
|
});
|
|
214
|
-
|
|
250
|
+
// Only POST requests can represent an in-flight tool mutation/read result that
|
|
251
|
+
// must settle before cutover. Long-lived GET/SSE streams are transport-only and
|
|
252
|
+
// must not consume the cutover grace window.
|
|
253
|
+
const waitForRequestDrain = (): Promise<void> => activePosts.size === 0
|
|
215
254
|
? Promise.resolve()
|
|
216
|
-
: new Promise<void>((resolve) =>
|
|
255
|
+
: new Promise<void>((resolve) => postDrainWaiters.add(resolve));
|
|
217
256
|
app.get('/ready', (_req, res) => {
|
|
218
257
|
const readiness = options.readiness();
|
|
219
258
|
res.status(readiness.ready ? 200 : 503).json(readiness);
|
|
@@ -221,12 +260,24 @@ export async function startRuntimeMcpTransport(
|
|
|
221
260
|
|
|
222
261
|
const requireAuth = authMiddleware(options.authToken);
|
|
223
262
|
app.post('/mcp', requireAuth, express.raw({ type: '*/*', limit: '1mb' }), (req, res) => {
|
|
263
|
+
const activePost: ActiveRuntimeMcpPost = { response: res, requestIds: [] };
|
|
264
|
+
activePosts.set(res, activePost);
|
|
265
|
+
const releasePostResponse = (): void => {
|
|
266
|
+
activePosts.delete(res);
|
|
267
|
+
if (activePosts.size === 0) {
|
|
268
|
+
for (const resolve of postDrainWaiters) resolve();
|
|
269
|
+
postDrainWaiters.clear();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
res.once('finish', releasePostResponse);
|
|
273
|
+
res.once('close', releasePostResponse);
|
|
224
274
|
void (async () => {
|
|
225
275
|
let body: unknown;
|
|
226
276
|
try { body = parseBody(req.body); } catch (error) {
|
|
227
277
|
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
|
|
228
278
|
return;
|
|
229
279
|
}
|
|
280
|
+
activePost.requestIds = runtimeMcpRequestIds(body);
|
|
230
281
|
if (observesToolSurface(body)) options.onToolSurfaceObservation?.();
|
|
231
282
|
const requestedSessionId = req.headers['mcp-session-id'];
|
|
232
283
|
const sessionId = typeof requestedSessionId === 'string' ? requestedSessionId : undefined;
|
|
@@ -265,6 +316,7 @@ export async function startRuntimeMcpTransport(
|
|
|
265
316
|
});
|
|
266
317
|
},
|
|
267
318
|
});
|
|
319
|
+
activePost.transport = transport;
|
|
268
320
|
transport.onclose = () => {
|
|
269
321
|
if (transport.sessionId) sessions.delete(transport.sessionId);
|
|
270
322
|
};
|
|
@@ -293,10 +345,15 @@ export async function startRuntimeMcpTransport(
|
|
|
293
345
|
res.status(404).json({ error: 'mcp_session_not_found' });
|
|
294
346
|
return;
|
|
295
347
|
}
|
|
348
|
+
activePost.transport = managed.transport;
|
|
296
349
|
await withManagedSessionRequest(managed, 'post', async () => await managed.transport.handleRequest(req, res, body));
|
|
297
350
|
})().catch((error: unknown) => {
|
|
298
351
|
if (!res.headersSent) res.status(500).json({ error: 'mcp_request_failed' });
|
|
299
352
|
console.error('[forge-runtime mcp] request failed:', error);
|
|
353
|
+
}).finally(() => {
|
|
354
|
+
releasePostResponse();
|
|
355
|
+
res.off('finish', releasePostResponse);
|
|
356
|
+
res.off('close', releasePostResponse);
|
|
300
357
|
});
|
|
301
358
|
});
|
|
302
359
|
app.get('/mcp', requireAuth, (req, res) => {
|
|
@@ -362,6 +419,7 @@ export async function startRuntimeMcpTransport(
|
|
|
362
419
|
await closeRuntimeMcpTransportResources({
|
|
363
420
|
closeListener: async () => await closeServer(httpServer),
|
|
364
421
|
waitForRequestDrain,
|
|
422
|
+
requestDrainTimeoutMs: options.requestDrainTimeoutMs,
|
|
365
423
|
// Snapshot sessions only after the request drain: an initialize request
|
|
366
424
|
// already in flight may register its session while shutdown is starting.
|
|
367
425
|
closeSessions: [async () => {
|
|
@@ -369,6 +427,41 @@ export async function startRuntimeMcpTransport(
|
|
|
369
427
|
await Promise.allSettled(activeSessions.map(async ({ transport }) => await transport.close().catch(() => undefined)));
|
|
370
428
|
sessions.clear();
|
|
371
429
|
}],
|
|
430
|
+
settleActiveRequests: async () => {
|
|
431
|
+
for (const active of activePosts.values()) {
|
|
432
|
+
let protocolSettled = false;
|
|
433
|
+
if (active.transport && active.requestIds.length > 0) {
|
|
434
|
+
const outcomes = await Promise.allSettled(active.requestIds.map(async (id) => {
|
|
435
|
+
await active.transport!.send({
|
|
436
|
+
jsonrpc: '2.0',
|
|
437
|
+
id,
|
|
438
|
+
error: {
|
|
439
|
+
code: -32000,
|
|
440
|
+
message: RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN,
|
|
441
|
+
data: {
|
|
442
|
+
outcome: 'unknown',
|
|
443
|
+
recoverable: true,
|
|
444
|
+
retryable: false,
|
|
445
|
+
action: 'reconcile',
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}));
|
|
450
|
+
protocolSettled = outcomes.some((outcome) => outcome.status === 'fulfilled');
|
|
451
|
+
}
|
|
452
|
+
if (protocolSettled) continue;
|
|
453
|
+
const response = active.response;
|
|
454
|
+
if (response.destroyed || response.writableEnded || response.headersSent) continue;
|
|
455
|
+
response.status(409).json({
|
|
456
|
+
error: 'runtime_cutover_outcome_unknown',
|
|
457
|
+
code: RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN,
|
|
458
|
+
message: 'Canonical Runtime entered cutover after this request was admitted. The request outcome is unknown; reconcile by request/effect identity before any retry.',
|
|
459
|
+
recoverable: true,
|
|
460
|
+
retryable: false,
|
|
461
|
+
action: 'reconcile',
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
},
|
|
372
465
|
forceCloseConnections: () => httpServer.closeAllConnections?.(),
|
|
373
466
|
});
|
|
374
467
|
},
|