@moretea-labs/forge 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +73 -7
- package/CHANGELOG.md +34 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +531 -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 +229 -2
- package/adapters/chatgpt/work-conversation-binding-store.ts +99 -54
- package/adapters/computer/chatgpt-extension-target.ts +708 -0
- package/adapters/computer/chrome-extension/background.js +541 -0
- package/adapters/computer/chrome-extension/content.js +402 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +32 -0
- package/adapters/computer/desktop-operator-discovery.ts +11 -31
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +178 -24
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -7
- package/adapters/mcp/instructions.ts +6 -5
- package/adapters/mcp/multi-repository.ts +10 -47
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- 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 +490 -113
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +83 -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 +14 -54
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +132 -47
- package/adapters/mcp/runtime-gateway/process-tools.ts +173 -23
- 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 +19 -507
- package/adapters/mcp/runtime-gateway/routing-policy.ts +506 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +15 -66
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +123 -178
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +11 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +37 -1
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +416 -270
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +140 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +262 -3123
- 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 +229 -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 +320 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +180 -0
- package/adapters/mcp/server.ts +91 -26
- package/adapters/mcp/setup.ts +5 -13
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +703 -3092
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +5 -10
- package/adapters/mcp/tool-mapping/repository-tools.ts +170 -213
- package/adapters/mcp/toolset-names.ts +11 -37
- package/adapters/mcp/toolset.ts +19 -92
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +377 -0
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +200 -683
- 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/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +26 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +205 -51
- 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/20260904-ios-device-migration-baseline.md +42 -0
- 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 +234 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +74 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +342 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +6 -5
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +3 -3
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +16 -4
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -52
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +15 -7
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +25 -13
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +25 -25
- 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 +348 -97
- 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 +87 -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/eligibility.ts +4 -1
- 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 +166 -143
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +18 -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 +34 -65
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +847 -729
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- 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 +153 -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/assemble-template.ts +11 -72
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +94 -201
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +921 -239
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +6 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +1 -3
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +12 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- 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 +95 -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/sync-generated-authority.mjs +0 -1
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +12 -35
- package/src/cli/agent-jobs/integration.ts +20 -51
- package/src/cli/agent-jobs/job-manager.ts +7 -3
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- 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/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +137 -135
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-execution.ts +41 -0
- 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 +5 -44
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +83 -102
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +107 -84
- 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 +25 -52
- 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 +42 -10
- package/src/cli/editing/edit-session.ts +18 -12
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +19 -14
- package/src/cli/local-bridge/console-view-models.ts +4 -13
- package/src/cli/local-bridge/facade-api.ts +64 -193
- 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-classifier.ts +0 -9
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +37 -18
- 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/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +79 -12
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +101 -5
- package/src/cli/repository/inspector.ts +185 -343
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/effects/process-runner.ts +17 -1
- 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/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +16 -0
- package/src/runtime/context/semantic-navigation.ts +7 -35
- 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 +215 -12
- 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 +616 -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 +197 -110
- package/src/runtime/control-plane/execution/work-handle-store.ts +72 -44
- 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 +60 -34
- package/src/runtime/control-plane/execution/work-preparation-service.ts +231 -267
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- 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 +201 -199
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +33 -85
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +6 -2
- package/src/runtime/control-plane/facade/capability-registry.ts +364 -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 +9 -9
- package/src/runtime/control-plane/generated-cache-retention.ts +206 -59
- 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 +256 -117
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -12
- 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/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +29 -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 +295 -107
- 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/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- 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 +82 -30
- 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 +55 -18
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +3 -8
- package/src/runtime/maintenance/cleanup.ts +41 -5
- 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 +97 -29
- package/src/runtime/plugins/app-store-connect-adapter.ts +290 -18
- package/src/runtime/plugins/browser-adapter.ts +1124 -333
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +431 -137
- package/src/runtime/plugins/browser-manifest-surface.ts +47 -12
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +18 -0
- package/src/runtime/plugins/browser-runtime.ts +40 -36
- 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 +228 -0
- package/src/runtime/plugins/computer-registration.ts +298 -20
- 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/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +189 -3
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +24 -12
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +46 -41
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -100
- 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 +682 -435
- package/src/runtime/plugins/types.ts +24 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +4 -4
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +30 -40
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/release-coordinator.ts +312 -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 +254 -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 +227 -29
- package/src/runtime/root/package-runtime-release.ts +117 -30
- package/src/runtime/root/package-runtime-service.ts +24 -24
- package/src/runtime/root/release-execution-canary.ts +57 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -40
- package/src/runtime/root/release-materialize.ts +372 -106
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +160 -29
- package/src/runtime/root/scheduled-controller-composition.ts +29 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +126 -44
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -9
- package/src/runtime/root/workflow-supervisor-composition.ts +681 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +564 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +67 -22
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +5 -2
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/json-files.ts +8 -3
- 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 +3792 -417
- package/src/runtime/standalone-recovery/entry.ts +507 -45
- 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 +12 -2
- 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 +11 -3
- package/src/runtime/workflow/schedules/engine.ts +166 -178
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +95 -11
- 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 +218 -0
- package/supervisor/control-plane.ts +963 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +147 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +1151 -0
- package/supervisor/native-messaging/host.ts +58 -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 +250 -0
- package/supervisor/server.ts +412 -0
- package/supervisor/store.ts +1870 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +261 -0
- package/SKILL.md +0 -427
- 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/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- 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-skill-version.ts +0 -5
- package/scripts/check-tarball-install-smoke.sh +0 -150
- package/scripts/ensure-codegraph.sh +0 -16
- 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/run-skill-hook.ts +0 -244
- package/scripts/stage-runtime-release.ts +0 -44
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- 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/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- 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
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { randomUUID
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
2
|
import { existsSync, watch } from 'fs';
|
|
3
3
|
import { dirname } from 'path';
|
|
4
4
|
import express, { type Request, type Response, type NextFunction } from 'express';
|
|
5
5
|
import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest, type NodeMcpRequestHandler } from "@modelcontextprotocol/node";
|
|
6
|
-
import { createMcpHandler, isLegacyRequest, type McpRequestContext
|
|
7
|
-
import {
|
|
6
|
+
import { createMcpHandler, isLegacyRequest, type McpRequestContext } from "@modelcontextprotocol/server";
|
|
7
|
+
import { InvalidTokenError } from "@modelcontextprotocol/server-legacy/auth";
|
|
8
8
|
import {
|
|
9
9
|
buildMultiRepositoryToolDefinitions,
|
|
10
10
|
createCanonicalRuntimeProxy,
|
|
@@ -17,39 +17,31 @@ import {
|
|
|
17
17
|
} from '../server';
|
|
18
18
|
import {
|
|
19
19
|
loadMcpServiceLocalConfig,
|
|
20
|
-
loadMcpServiceRuntimeState,
|
|
21
20
|
mcpServiceOAuthTokenStoreFallbackPaths,
|
|
22
21
|
mcpServiceOAuthTokenStorePath,
|
|
23
22
|
parseMcpHttpAuthMode,
|
|
24
23
|
readMcpServiceBearerToken,
|
|
25
24
|
readMcpServiceOAuthPassphrase,
|
|
26
|
-
type McpLocalConfig,
|
|
27
25
|
type McpHttpAuthMode,
|
|
28
26
|
} from '../auth';
|
|
29
27
|
import { createMcpOAuthProvider, McpOAuthTokenStore } from '../oauth';
|
|
30
28
|
import { resolveMcpRepoRoot } from '../repo';
|
|
31
|
-
import { buildMcpToolDefinitions } from '../tool-mapping/tools';
|
|
32
29
|
import { resolveControllerHome } from '../../../src/cli/repositories/controller-home';
|
|
33
|
-
import {
|
|
34
|
-
controllerExposureSnapshot,
|
|
35
|
-
} from '../toolset';
|
|
36
|
-
import { readForgeRuntimeStatus } from '../../../src/runtime/control-plane/runtime-status-client';
|
|
37
|
-
import { invalidateExecutionSession } from '../../../src/runtime/control-plane/execution/session-store';
|
|
38
|
-
import { runtimeIdentitySnapshot } from '../runtime-gateway/runtime-tools';
|
|
39
|
-
import { projectionBlocksReadiness, readRepositoryProjectionSnapshot } from '../../../src/runtime/projections/materialized-view';
|
|
40
30
|
import { readRuntimeGeneration } from '../../../src/runtime/control-plane/runtime-generation';
|
|
31
|
+
import { recordMcpTransportEvent } from '../../../src/runtime/diagnostics/mcp-timing';
|
|
41
32
|
import { readRuntimeStatusSnapshot, runtimeStatusPath } from '../../../src/runtime/root/status';
|
|
42
|
-
import { getRepository, listRepositories } from '../../../src/cli/repositories/registry';
|
|
43
|
-
import { buildControllerTaskLedgerProjection } from '../../../src/cli/controller/task-ledger';
|
|
44
|
-
import { legacyIssueAuthorityRetired } from '../../../src/cli/controller/legacy-issue-cutover';
|
|
45
|
-
import { reconcileReadinessProjectionSource } from '../readiness-projection';
|
|
46
33
|
import {
|
|
47
34
|
FORGE_MCP_SCHEMA_VERSION,
|
|
48
35
|
FORGE_TOOL_SURFACE,
|
|
49
36
|
FORGE_VERSION,
|
|
50
|
-
repositoryIdentity,
|
|
51
37
|
} from '../../../src/cli/controller/runtime-config';
|
|
52
|
-
import { McpSessionRegistry, type McpSessionRoute } from './session-registry';
|
|
38
|
+
import { McpSessionRegistry, type ClosableMcpTransport, type McpSessionRegistryOptions, type McpSessionRoute } from './session-registry';
|
|
39
|
+
import { getConfiguredPublicOrigin, getPublicOrigin, registerMcpOAuthHttpRoutes } from './oauth-http';
|
|
40
|
+
import { registerMcpHttpObservationRoutes } from './http-observation';
|
|
41
|
+
import { reconcileFinishedMcpPosts } from './http-post-lifecycle';
|
|
42
|
+
import { RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN } from '../../../src/runtime/root/mcp-transport';
|
|
43
|
+
import { packageConnectorCutoverDrainIsActive } from '../../../src/runtime/root/package-connector-service';
|
|
44
|
+
export { isAllowedMcpOAuthRedirectUri, isIncompleteOAuthAuthorizeRequest } from './oauth-http';
|
|
53
45
|
import {
|
|
54
46
|
connectionIdentity,
|
|
55
47
|
ensureForgeInstanceIdentity,
|
|
@@ -65,17 +57,6 @@ export interface McpHttpOptions extends McpServerOptions {
|
|
|
65
57
|
auth?: string;
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
function localControllerDiagnosticMatchesRuntime(
|
|
69
|
-
payload: Record<string, unknown> | null,
|
|
70
|
-
generation?: string,
|
|
71
|
-
): boolean {
|
|
72
|
-
return payload?.status === 'ok'
|
|
73
|
-
&& payload.toolSurface === FORGE_TOOL_SURFACE
|
|
74
|
-
&& payload.schemaVersion === FORGE_MCP_SCHEMA_VERSION
|
|
75
|
-
&& payload.version === FORGE_VERSION
|
|
76
|
-
&& (generation === undefined || payload.generation === generation);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
60
|
function bearerFromRequest(req: Request): string | null {
|
|
80
61
|
const header = req.headers.authorization;
|
|
81
62
|
if (typeof header !== 'string') return null;
|
|
@@ -228,8 +209,42 @@ function sendMcpToolSurfaceReset(
|
|
|
228
209
|
});
|
|
229
210
|
}
|
|
230
211
|
|
|
212
|
+
/**
|
|
213
|
+
* A schema-fenced transport cannot be reused after the reset response. Delay
|
|
214
|
+
* retirement until Express has flushed the reconnect instruction, then release
|
|
215
|
+
* its session-local state. Work identity is Controller-owned, not transport-owned.
|
|
216
|
+
*/
|
|
217
|
+
function retireAfterToolSurfaceReset(
|
|
218
|
+
res: Response,
|
|
219
|
+
registry: HttpSessionRegistry,
|
|
220
|
+
sessionId: string,
|
|
221
|
+
): void {
|
|
222
|
+
let retired = false;
|
|
223
|
+
const retire = (): void => {
|
|
224
|
+
if (retired) return;
|
|
225
|
+
retired = true;
|
|
226
|
+
void registry.close(sessionId, 'tool_surface_changed');
|
|
227
|
+
};
|
|
228
|
+
res.once('finish', retire);
|
|
229
|
+
res.once('close', retire);
|
|
230
|
+
}
|
|
231
|
+
|
|
231
232
|
export function mcpRequestError(error: unknown) {
|
|
232
233
|
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
234
|
+
if (rawMessage.includes(RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN)) {
|
|
235
|
+
return {
|
|
236
|
+
status: 503 as const,
|
|
237
|
+
body: {
|
|
238
|
+
error: 'runtime_cutover_outcome_unknown' as const,
|
|
239
|
+
code: RUNTIME_MCP_CUTOVER_OUTCOME_UNKNOWN,
|
|
240
|
+
message: 'The Canonical Runtime changed after this request was admitted. Reconcile the original request/effect identity before retrying.',
|
|
241
|
+
recoverable: true as const,
|
|
242
|
+
retryable: false as const,
|
|
243
|
+
sessionPreserved: true as const,
|
|
244
|
+
action: 'reconcile' as const,
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
233
248
|
const retryable = /(?:\b502\b|\b503\b|\b429\b|ECONNRESET|ETIMEDOUT|EAI_AGAIN|CANONICAL_RUNTIME_TIMEOUT|server_busy|session_capacity|gateway)/i.test(rawMessage);
|
|
234
249
|
return {
|
|
235
250
|
status: (retryable ? 503 : 500) as 500 | 503,
|
|
@@ -254,343 +269,6 @@ export function sendMcpRequestError(res: Response, error: unknown): void {
|
|
|
254
269
|
res.status(response.status).json(response.body);
|
|
255
270
|
}
|
|
256
271
|
|
|
257
|
-
function getConfiguredPublicOrigin(config: McpLocalConfig | null): string | undefined {
|
|
258
|
-
const configured = process.env.FORGE_MCP_PUBLIC_ORIGIN?.trim();
|
|
259
|
-
if (configured) {
|
|
260
|
-
try {
|
|
261
|
-
return new URL(configured).origin;
|
|
262
|
-
} catch (_error) {
|
|
263
|
-
// Fall through to service or legacy config.
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const endpoint = config?.chatgpt?.endpoint?.trim();
|
|
267
|
-
if (!endpoint) return undefined;
|
|
268
|
-
try {
|
|
269
|
-
return new URL(endpoint).origin;
|
|
270
|
-
} catch (_error) {
|
|
271
|
-
return undefined;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function getPublicOrigin(req: Request, configuredOrigin: string | undefined): string {
|
|
276
|
-
if (configuredOrigin) return configuredOrigin;
|
|
277
|
-
const proto = (req.headers['x-forwarded-proto'] as string | undefined) ?? 'https';
|
|
278
|
-
const host = (req.headers['x-forwarded-host'] as string | undefined) ?? req.headers.host ?? '127.0.0.1:8765';
|
|
279
|
-
return `${proto}://${host}`;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function localControllerHealthUrl(host: string, port: number): string {
|
|
283
|
-
return `http://${host === '::1' ? '[::1]' : host}:${port}/health`;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async function jsonHealth(url: string): Promise<Record<string, unknown> | null> {
|
|
287
|
-
const controller = new AbortController();
|
|
288
|
-
const timeout = setTimeout(() => controller.abort(), 2_000);
|
|
289
|
-
try {
|
|
290
|
-
const response = await fetch(url, {
|
|
291
|
-
signal: controller.signal,
|
|
292
|
-
headers: { accept: 'application/json' },
|
|
293
|
-
});
|
|
294
|
-
if (!response.ok) return null;
|
|
295
|
-
return await response.json() as Record<string, unknown>;
|
|
296
|
-
} catch (_error) {
|
|
297
|
-
return null;
|
|
298
|
-
} finally {
|
|
299
|
-
clearTimeout(timeout);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function isAllowedMcpOAuthRedirectUri(redirectUri: string): boolean {
|
|
304
|
-
try {
|
|
305
|
-
const url = new URL(redirectUri);
|
|
306
|
-
if (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) {
|
|
307
|
-
return true;
|
|
308
|
-
}
|
|
309
|
-
if (
|
|
310
|
-
url.protocol === 'https:' &&
|
|
311
|
-
(url.origin === 'https://chatgpt.com' || url.origin === 'https://chat.openai.com') &&
|
|
312
|
-
url.pathname.startsWith('/connector/oauth/')
|
|
313
|
-
) {
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
return false;
|
|
317
|
-
} catch (_error) {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function isRegisteredRedirectUri(redirectUri: string, client: { redirect_uris?: string[] }): boolean {
|
|
323
|
-
return (client.redirect_uris ?? []).some((registered) => redirectUriMatches(redirectUri, registered));
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function isRegisteredExternalHttpsRedirectUri(redirectUri: string, client: { redirect_uris?: string[] }): boolean {
|
|
327
|
-
try {
|
|
328
|
-
const url = new URL(redirectUri);
|
|
329
|
-
return url.protocol === 'https:' && !url.username && !url.password && isRegisteredRedirectUri(redirectUri, client);
|
|
330
|
-
} catch (_error) {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function isSafeOAuthFallbackRedirectUri(redirectUri: string): boolean {
|
|
336
|
-
try {
|
|
337
|
-
const url = new URL(redirectUri);
|
|
338
|
-
if (url.username || url.password) return false;
|
|
339
|
-
if (url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) return true;
|
|
340
|
-
return url.protocol === 'https:';
|
|
341
|
-
} catch (_error) {
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async function getOrRegisterPublicOAuthClient(
|
|
347
|
-
provider: ReturnType<typeof createMcpOAuthProvider>,
|
|
348
|
-
clientId: string,
|
|
349
|
-
redirectUri: string | undefined,
|
|
350
|
-
req: Request,
|
|
351
|
-
): Promise<OAuthClientInformationFull | undefined> {
|
|
352
|
-
const existing = await provider.clientsStore.getClient(clientId);
|
|
353
|
-
if (existing) return existing as OAuthClientInformationFull;
|
|
354
|
-
if (!redirectUri || !isSafeOAuthFallbackRedirectUri(redirectUri) || !provider.clientsStore.registerClient) return undefined;
|
|
355
|
-
oauthTrace(req, 'authorize:auto_register_public_client', {
|
|
356
|
-
redirectScheme: new URL(redirectUri).protocol,
|
|
357
|
-
redirectHost: new URL(redirectUri).hostname,
|
|
358
|
-
});
|
|
359
|
-
return provider.clientsStore.registerClient({
|
|
360
|
-
client_id: clientId,
|
|
361
|
-
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
362
|
-
client_name: 'forge OAuth fallback client',
|
|
363
|
-
redirect_uris: [redirectUri],
|
|
364
|
-
token_endpoint_auth_method: 'none',
|
|
365
|
-
grant_types: ['authorization_code', 'refresh_token'],
|
|
366
|
-
response_types: ['code'],
|
|
367
|
-
} as unknown as Omit<OAuthClientInformationFull, 'client_id' | 'client_id_issued_at'>) as OAuthClientInformationFull;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function escapeHtmlAttribute(value: string): string {
|
|
371
|
-
return value
|
|
372
|
-
.replace(/&/g, '&')
|
|
373
|
-
.replace(/"/g, '"')
|
|
374
|
-
.replace(/'/g, ''')
|
|
375
|
-
.replace(/</g, '<')
|
|
376
|
-
.replace(/>/g, '>');
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function renderPassphrasePage(params: URLSearchParams): string {
|
|
380
|
-
const hiddenFields = Array.from(params.entries())
|
|
381
|
-
.filter(([key]) => key !== 'passphrase')
|
|
382
|
-
.map(([key, value]) => `<input type="hidden" name="${escapeHtmlAttribute(key)}" value="${escapeHtmlAttribute(value)}">`)
|
|
383
|
-
.join('\n');
|
|
384
|
-
|
|
385
|
-
return `<!doctype html>
|
|
386
|
-
<html lang="en">
|
|
387
|
-
<head><meta charset="utf-8"><title>Authorize forge</title>
|
|
388
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
389
|
-
<style>
|
|
390
|
-
body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f6f6f3;color:#1f2328}
|
|
391
|
-
.card{width:min(420px,92vw);background:#fff;border:1px solid #d8d8d0;border-radius:12px;padding:32px;box-shadow:0 12px 40px rgba(0,0,0,.08)}
|
|
392
|
-
h1{font-size:20px;margin:0 0 8px}p{margin:0 0 20px;color:#60666d;line-height:1.45}
|
|
393
|
-
input{width:100%;box-sizing:border-box;border:1px solid #bfc4c9;border-radius:8px;padding:12px;font-size:16px}
|
|
394
|
-
button{width:100%;margin-top:14px;border:0;border-radius:8px;padding:12px;background:#1f2328;color:#fff;font-size:16px;font-weight:600}
|
|
395
|
-
</style></head>
|
|
396
|
-
<body><main class="card">
|
|
397
|
-
<h1>Authorize forge</h1>
|
|
398
|
-
<p>Enter the local MCP passphrase to let this MCP client use this workflow-scoped connector.</p>
|
|
399
|
-
<form method="POST" action="/authorize">
|
|
400
|
-
${hiddenFields}
|
|
401
|
-
<input type="password" name="passphrase" placeholder="Passphrase" autofocus>
|
|
402
|
-
<button type="submit">Authorize</button>
|
|
403
|
-
</form>
|
|
404
|
-
</main></body></html>`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/** Collect OAuth authorize params from query (GET) or body (POST form). */
|
|
408
|
-
function oauthAuthorizeParamSource(req: Request): Record<string, unknown> {
|
|
409
|
-
if (req.method === 'POST' && req.body && typeof req.body === 'object' && !Buffer.isBuffer(req.body)) {
|
|
410
|
-
return req.body as Record<string, unknown>;
|
|
411
|
-
}
|
|
412
|
-
return req.query as Record<string, unknown>;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function readOAuthAuthorizeString(source: Record<string, unknown>, key: string): string {
|
|
416
|
-
const value = source[key];
|
|
417
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* True when the request lacks the OAuth parameters needed for a real authorization.
|
|
422
|
-
* Incomplete requests must not render the passphrase form (incompatible clients loop there).
|
|
423
|
-
*/
|
|
424
|
-
export function isIncompleteOAuthAuthorizeRequest(source: Record<string, unknown>): boolean {
|
|
425
|
-
const clientId = readOAuthAuthorizeString(source, 'client_id');
|
|
426
|
-
const responseType = readOAuthAuthorizeString(source, 'response_type');
|
|
427
|
-
const codeChallenge = readOAuthAuthorizeString(source, 'code_challenge');
|
|
428
|
-
const redirectUri = readOAuthAuthorizeString(source, 'redirect_uri');
|
|
429
|
-
// redirect_uri may be omitted when the client has a single registered URI; client_id is the usable redirect context.
|
|
430
|
-
const hasRedirectContext = Boolean(redirectUri) || Boolean(clientId);
|
|
431
|
-
return !clientId || !responseType || !codeChallenge || !hasRedirectContext;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
function incompleteOAuthAuthorizeResponseBody(): {
|
|
435
|
-
error: 'invalid_request';
|
|
436
|
-
error_description: string;
|
|
437
|
-
message: string;
|
|
438
|
-
hint: string;
|
|
439
|
-
} {
|
|
440
|
-
return {
|
|
441
|
-
error: 'invalid_request',
|
|
442
|
-
error_description:
|
|
443
|
-
'OAuth authorization request is incomplete. Required: client_id, response_type, code_challenge, and a usable redirect context (redirect_uri or a registered client).',
|
|
444
|
-
message:
|
|
445
|
-
'This endpoint expects a complete OAuth authorization request (PKCE). Non-OAuth MCP clients should use /mcp-bearer with Authorization: Bearer <token> instead of /authorize.',
|
|
446
|
-
hint: 'Use POST/GET /mcp-bearer with a forge bearer token for clients that cannot complete OAuth dynamic registration and PKCE.',
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function isOAuthDebugTraceEnabled(): boolean {
|
|
451
|
-
return process.env.FORGE_MCP_OAUTH_TRACE === '1' || process.env.FORGE_MCP_OAUTH_TRACE === 'true';
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const SENSITIVE_OAUTH_FIELDS = new Set([
|
|
455
|
-
'passphrase',
|
|
456
|
-
'code',
|
|
457
|
-
'code_verifier',
|
|
458
|
-
'client_secret',
|
|
459
|
-
'access_token',
|
|
460
|
-
'refresh_token',
|
|
461
|
-
'token',
|
|
462
|
-
'authorization',
|
|
463
|
-
]);
|
|
464
|
-
|
|
465
|
-
function safeOAuthFieldNames(source: Record<string, unknown>): string[] {
|
|
466
|
-
return Object.keys(source)
|
|
467
|
-
.filter((key) => !SENSITIVE_OAUTH_FIELDS.has(key.toLowerCase()))
|
|
468
|
-
.sort();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function oauthTrace(req: Request, event: string, extra: Record<string, unknown> = {}): void {
|
|
472
|
-
if (!isOAuthDebugTraceEnabled()) return;
|
|
473
|
-
const source = oauthAuthorizeParamSource(req);
|
|
474
|
-
const userAgent = typeof req.headers['user-agent'] === 'string'
|
|
475
|
-
? req.headers['user-agent'].split(/[\s/]/)[0]
|
|
476
|
-
: undefined;
|
|
477
|
-
const safe = {
|
|
478
|
-
event,
|
|
479
|
-
method: req.method,
|
|
480
|
-
path: req.path,
|
|
481
|
-
fieldNames: safeOAuthFieldNames(source),
|
|
482
|
-
hasClientId: Boolean(readOAuthAuthorizeString(source, 'client_id')),
|
|
483
|
-
hasRedirectUri: Boolean(readOAuthAuthorizeString(source, 'redirect_uri')),
|
|
484
|
-
hasCodeChallenge: Boolean(readOAuthAuthorizeString(source, 'code_challenge')),
|
|
485
|
-
responseType: readOAuthAuthorizeString(source, 'response_type') || undefined,
|
|
486
|
-
codeChallengeMethod: readOAuthAuthorizeString(source, 'code_challenge_method') || undefined,
|
|
487
|
-
grantType: readOAuthAuthorizeString(source, 'grant_type') || undefined,
|
|
488
|
-
hasResource: Boolean(readOAuthAuthorizeString(source, 'resource')),
|
|
489
|
-
userAgent,
|
|
490
|
-
...extra,
|
|
491
|
-
};
|
|
492
|
-
console.error(`[forge:mcp-oauth] ${JSON.stringify(safe)}`);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function oauthTraceMiddleware(event: string): (req: Request, res: Response, next: NextFunction) => void {
|
|
496
|
-
return (req, res, next) => {
|
|
497
|
-
oauthTrace(req, `${event}:request`);
|
|
498
|
-
res.once('finish', () => oauthTrace(req, `${event}:response`, { statusCode: res.statusCode }));
|
|
499
|
-
next();
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function rejectIncompleteOAuthAuthorize(req: Request, res: Response, next: NextFunction): void {
|
|
504
|
-
const source = oauthAuthorizeParamSource(req);
|
|
505
|
-
if (isIncompleteOAuthAuthorizeRequest(source)) {
|
|
506
|
-
oauthTrace(req, 'authorize:incomplete');
|
|
507
|
-
res.status(400).json(incompleteOAuthAuthorizeResponseBody());
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
oauthTrace(req, 'authorize:complete');
|
|
511
|
-
next();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function requirePassphrase(passphrase: string): (req: Request, res: Response, next: NextFunction) => void {
|
|
515
|
-
return (req, res, next) => {
|
|
516
|
-
const provided = typeof req.body?.passphrase === 'string' ? req.body.passphrase : undefined;
|
|
517
|
-
if (provided) {
|
|
518
|
-
const a = Buffer.from(provided);
|
|
519
|
-
const b = Buffer.from(passphrase);
|
|
520
|
-
if (a.length === b.length && timingSafeEqual(a, b)) {
|
|
521
|
-
next();
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
// Prefer body hidden fields on POST (failed passphrase re-render), else query string on GET.
|
|
526
|
-
const source = oauthAuthorizeParamSource(req);
|
|
527
|
-
const params = new URLSearchParams();
|
|
528
|
-
for (const [key, value] of Object.entries(source)) {
|
|
529
|
-
if (key === 'passphrase') continue;
|
|
530
|
-
if (typeof value === 'string') params.set(key, value);
|
|
531
|
-
}
|
|
532
|
-
if ([...params.keys()].length === 0 && req.url.includes('?')) {
|
|
533
|
-
const fromUrl = new URLSearchParams(req.url.slice(req.url.indexOf('?')));
|
|
534
|
-
for (const [key, value] of fromUrl.entries()) {
|
|
535
|
-
if (key !== 'passphrase') params.set(key, value);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
res.type('html').send(renderPassphrasePage(params));
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function oauthAuthorizationHandler(provider: ReturnType<typeof createMcpOAuthProvider>) {
|
|
543
|
-
return async (req: Request, res: Response) => {
|
|
544
|
-
const query = req.method === 'POST' ? req.body : req.query;
|
|
545
|
-
const clientId = typeof query.client_id === 'string' ? query.client_id : '';
|
|
546
|
-
const responseType = typeof query.response_type === 'string' ? query.response_type : '';
|
|
547
|
-
const codeChallenge = typeof query.code_challenge === 'string' ? query.code_challenge : '';
|
|
548
|
-
const codeChallengeMethod = typeof query.code_challenge_method === 'string' ? query.code_challenge_method : '';
|
|
549
|
-
const state = typeof query.state === 'string' ? query.state : undefined;
|
|
550
|
-
const scope = typeof query.scope === 'string' ? query.scope : undefined;
|
|
551
|
-
let redirectUri = typeof query.redirect_uri === 'string' ? query.redirect_uri : undefined;
|
|
552
|
-
|
|
553
|
-
if (responseType !== 'code') {
|
|
554
|
-
res.status(400).json({ error: 'unsupported_response_type', error_description: 'Only code response type is supported' });
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
if (!codeChallenge || codeChallengeMethod !== 'S256') {
|
|
558
|
-
res.status(400).json({ error: 'invalid_request', error_description: 'PKCE S256 is required' });
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const client = await getOrRegisterPublicOAuthClient(provider, clientId, redirectUri, req);
|
|
563
|
-
if (!client) {
|
|
564
|
-
res.status(400).json({ error: 'invalid_client', error_description: 'Unknown client_id' });
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
if (!redirectUri && client.redirect_uris.length === 1) {
|
|
568
|
-
redirectUri = client.redirect_uris[0];
|
|
569
|
-
}
|
|
570
|
-
if (!redirectUri || (!isAllowedMcpOAuthRedirectUri(redirectUri) && !isRegisteredExternalHttpsRedirectUri(redirectUri, client))) {
|
|
571
|
-
res.status(400).json({
|
|
572
|
-
error: 'invalid_request',
|
|
573
|
-
error_description: 'redirect_uri must be localhost, a ChatGPT connector callback URL, or a registered HTTPS client redirect_uri',
|
|
574
|
-
});
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
if (!isRegisteredRedirectUri(redirectUri, client)) {
|
|
578
|
-
res.status(400).json({
|
|
579
|
-
error: 'invalid_request',
|
|
580
|
-
error_description: 'redirect_uri must match a registered client redirect_uri',
|
|
581
|
-
});
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
await provider.authorize(client as OAuthClientInformationFull, {
|
|
586
|
-
state,
|
|
587
|
-
scopes: scope ? scope.split(' ') : [],
|
|
588
|
-
redirectUri,
|
|
589
|
-
codeChallenge,
|
|
590
|
-
}, res);
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
|
|
594
272
|
function sendBearerUnauthorized(res: Response, description: string, hasConfiguredToken: boolean): void {
|
|
595
273
|
res.setHeader('www-authenticate', 'Bearer realm="forge-mcp"');
|
|
596
274
|
res.status(hasConfiguredToken ? 401 : 503).json({
|
|
@@ -663,7 +341,8 @@ function requireMcpHttpAuth(
|
|
|
663
341
|
|
|
664
342
|
interface McpRuntimeStats {
|
|
665
343
|
initializing: number;
|
|
666
|
-
|
|
344
|
+
/** Canonical HTTP POST admission owner; one entry per unfinished response. */
|
|
345
|
+
activePostStartedAt: Map<Response, number>;
|
|
667
346
|
rejectedOverload: number;
|
|
668
347
|
}
|
|
669
348
|
|
|
@@ -682,9 +361,88 @@ const MCP_STREAM_LEASE_MS = positiveIntegerEnv('FORGE_MCP_STREAM_LEASE_MS', 30 *
|
|
|
682
361
|
const MCP_SESSION_ABSOLUTE_LIFETIME_MS = positiveIntegerEnv('FORGE_MCP_SESSION_ABSOLUTE_LIFETIME_MS', 2 * 60 * 60_000);
|
|
683
362
|
const MCP_ACTIVE_POST_STALL_MS = positiveIntegerEnv('FORGE_MCP_ACTIVE_POST_STALL_MS', 10 * 60_000);
|
|
684
363
|
|
|
364
|
+
function sendReleaseCutoverDraining(res: Response): void {
|
|
365
|
+
res.setHeader('retry-after', '1');
|
|
366
|
+
res.status(503).json({
|
|
367
|
+
error: 'release_cutover_draining',
|
|
368
|
+
code: 'MCP_RELEASE_CUTOVER_DRAINING',
|
|
369
|
+
message: 'This Connector is draining admitted MCP requests for a Runtime release cutover; retry shortly.',
|
|
370
|
+
recoverable: true,
|
|
371
|
+
retryable: true,
|
|
372
|
+
sessionPreserved: true,
|
|
373
|
+
action: 'retry',
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function connectorCutoverDrainActive(req: Request, controllerHome?: string): boolean {
|
|
378
|
+
if (!controllerHome || !req.socket.localPort) return false;
|
|
379
|
+
return packageConnectorCutoverDrainIsActive(
|
|
380
|
+
controllerHome,
|
|
381
|
+
`http://127.0.0.1:${req.socket.localPort}/mcp`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function admitMcpPost(
|
|
386
|
+
stats: McpRuntimeStats,
|
|
387
|
+
res: Response,
|
|
388
|
+
cutoverDrain?: { controllerHome: string; connectorEndpoint: string },
|
|
389
|
+
): boolean {
|
|
390
|
+
if (cutoverDrain && packageConnectorCutoverDrainIsActive(cutoverDrain.controllerHome, cutoverDrain.connectorEndpoint)) {
|
|
391
|
+
sendReleaseCutoverDraining(res);
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
reconcileFinishedMcpPosts(stats.activePostStartedAt);
|
|
395
|
+
if (stats.activePostStartedAt.size >= MAX_ACTIVE_POSTS) {
|
|
396
|
+
stats.rejectedOverload += 1;
|
|
397
|
+
res.setHeader('retry-after', '1');
|
|
398
|
+
res.status(503).json({
|
|
399
|
+
error: 'server_busy',
|
|
400
|
+
code: 'MCP_SERVER_BUSY',
|
|
401
|
+
message: 'Too many MCP requests are active; retry shortly',
|
|
402
|
+
recoverable: true,
|
|
403
|
+
retryable: true,
|
|
404
|
+
sessionPreserved: true,
|
|
405
|
+
action: 'retry',
|
|
406
|
+
});
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
stats.activePostStartedAt.set(res, Date.now());
|
|
411
|
+
const release = (): void => {
|
|
412
|
+
if (!stats.activePostStartedAt.has(res)) return;
|
|
413
|
+
stats.activePostStartedAt.delete(res);
|
|
414
|
+
res.off('finish', release);
|
|
415
|
+
res.off('close', release);
|
|
416
|
+
};
|
|
417
|
+
res.once('finish', release);
|
|
418
|
+
res.once('close', release);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
685
422
|
type McpToolContext = ReturnType<typeof createMcpToolContext>;
|
|
686
423
|
type HttpSessionRegistry = McpSessionRegistry<NodeStreamableHTTPServerTransport, McpToolContext>;
|
|
687
424
|
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Public HTTP transport registry. It owns transport admission/leases and
|
|
428
|
+
* transport-local observability only; closing a transport session must never
|
|
429
|
+
* invalidate durable execution, Work, or ControllerRound authority.
|
|
430
|
+
*/
|
|
431
|
+
export function createMcpHttpSessionRegistry<
|
|
432
|
+
TTransport extends ClosableMcpTransport = NodeStreamableHTTPServerTransport,
|
|
433
|
+
TContext = McpToolContext,
|
|
434
|
+
>(options: McpSessionRegistryOptions<TTransport, TContext> = {}): McpSessionRegistry<TTransport, TContext> {
|
|
435
|
+
return new McpSessionRegistry<TTransport, TContext>({
|
|
436
|
+
maximumSessions: MAX_MCP_SESSIONS,
|
|
437
|
+
maximumSessionsPerPrincipal: MAX_MCP_SESSIONS_PER_PRINCIPAL,
|
|
438
|
+
idleTtlMs: MCP_SESSION_IDLE_TTL_MS,
|
|
439
|
+
streamLeaseMs: MCP_STREAM_LEASE_MS,
|
|
440
|
+
absoluteLifetimeMs: MCP_SESSION_ABSOLUTE_LIFETIME_MS,
|
|
441
|
+
activePostStallMs: MCP_ACTIVE_POST_STALL_MS,
|
|
442
|
+
...options,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
688
446
|
function principalIdFromModernRequestContext(context: McpRequestContext): string {
|
|
689
447
|
const clientId = context.authInfo?.clientId?.trim();
|
|
690
448
|
if (clientId) return `oauth-client:${clientId}`;
|
|
@@ -731,6 +489,12 @@ async function handleMcpPost(
|
|
|
731
489
|
res.status(400).json({ error: 'invalid JSON request body' });
|
|
732
490
|
return;
|
|
733
491
|
}
|
|
492
|
+
const connectorEndpoint = req.socket.localPort
|
|
493
|
+
? `http://127.0.0.1:${req.socket.localPort}/mcp`
|
|
494
|
+
: undefined;
|
|
495
|
+
if (!admitMcpPost(stats, res, baseOptions.controllerHome && connectorEndpoint
|
|
496
|
+
? { controllerHome: baseOptions.controllerHome, connectorEndpoint }
|
|
497
|
+
: undefined)) return;
|
|
734
498
|
if (modernHandler) {
|
|
735
499
|
const webRequest = await toWebRequest(req, body);
|
|
736
500
|
if (!(await isLegacyRequest(webRequest, body))) {
|
|
@@ -744,7 +508,7 @@ async function handleMcpPost(
|
|
|
744
508
|
res.setHeader('Mcp-Session-Reset', 'reinitialized');
|
|
745
509
|
res.setHeader('x-forge-session-reset', 'reinitialized');
|
|
746
510
|
}
|
|
747
|
-
if (stats.initializing >= MAX_INITIALIZING_SESSIONS
|
|
511
|
+
if (stats.initializing >= MAX_INITIALIZING_SESSIONS) {
|
|
748
512
|
stats.rejectedOverload += 1;
|
|
749
513
|
res.setHeader('retry-after', '1');
|
|
750
514
|
res.status(503).json({
|
|
@@ -759,7 +523,6 @@ async function handleMcpPost(
|
|
|
759
523
|
return;
|
|
760
524
|
}
|
|
761
525
|
stats.initializing += 1;
|
|
762
|
-
stats.activePosts += 1;
|
|
763
526
|
let transport: NodeStreamableHTTPServerTransport | undefined;
|
|
764
527
|
let reservationId: string | undefined;
|
|
765
528
|
let initializedSessionId: string | undefined;
|
|
@@ -839,11 +602,23 @@ async function handleMcpPost(
|
|
|
839
602
|
server = createForgeMcpServerFromContext(sessionContext, runtimeSchema, sharedRuntimeProxy);
|
|
840
603
|
await server.connect(transport);
|
|
841
604
|
await transport.handleRequest(req, res, body);
|
|
605
|
+
if (
|
|
606
|
+
initializedSessionId
|
|
607
|
+
&& 'controllerHome' in sessionContext
|
|
608
|
+
&& typeof sessionContext.controllerHome === 'string'
|
|
609
|
+
) {
|
|
610
|
+
recordMcpTransportEvent(sessionContext.controllerHome, {
|
|
611
|
+
kind: 'session_initialized',
|
|
612
|
+
sessionId: initializedSessionId,
|
|
613
|
+
connectionId: connection.connectionId,
|
|
614
|
+
route,
|
|
615
|
+
principalId,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
842
618
|
} finally {
|
|
843
619
|
if (initializedSessionId) registry.endPost(initializedSessionId);
|
|
844
620
|
if (reservationId) registry.releaseInitialize(reservationId);
|
|
845
|
-
stats.initializing
|
|
846
|
-
stats.activePosts -= 1;
|
|
621
|
+
stats.initializing = Math.max(0, stats.initializing - 1);
|
|
847
622
|
if (!transport?.sessionId) await transport?.close().catch(() => undefined);
|
|
848
623
|
}
|
|
849
624
|
return;
|
|
@@ -851,14 +626,13 @@ async function handleMcpPost(
|
|
|
851
626
|
if (sessionId) {
|
|
852
627
|
const managed = registry.get(sessionId);
|
|
853
628
|
if (managed && managed.route === route && managed.principalId === principalFromRequest(req)) {
|
|
854
|
-
if (managed.inFlightPosts >= MAX_POSTS_PER_SESSION
|
|
629
|
+
if (managed.inFlightPosts >= MAX_POSTS_PER_SESSION) {
|
|
855
630
|
stats.rejectedOverload += 1;
|
|
856
631
|
res.setHeader('retry-after', '1');
|
|
857
632
|
res.status(429).json({ error: 'session_busy', message: 'Too many MCP requests are active; retry shortly' });
|
|
858
633
|
return;
|
|
859
634
|
}
|
|
860
635
|
registry.beginPost(sessionId);
|
|
861
|
-
stats.activePosts += 1;
|
|
862
636
|
try {
|
|
863
637
|
const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
|
|
864
638
|
currentToolSurfaceFingerprint(),
|
|
@@ -878,23 +652,21 @@ async function handleMcpPost(
|
|
|
878
652
|
return;
|
|
879
653
|
}
|
|
880
654
|
}
|
|
881
|
-
// Keep the transport/session alive long enough for the host to observe
|
|
882
|
-
// the recoverable reset and issue a replacement initialize request.
|
|
883
|
-
// The initialize path explicitly supersedes this session afterward.
|
|
884
655
|
sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
|
|
656
|
+
retireAfterToolSurfaceReset(res, registry, sessionId);
|
|
885
657
|
return;
|
|
886
658
|
}
|
|
887
659
|
if (toolCallOutsideSessionSchema(body, managed.toolNames)) {
|
|
888
660
|
// A call against a newer discovery surface is the same recoverable
|
|
889
|
-
// schema-fence condition
|
|
890
|
-
//
|
|
661
|
+
// schema-fence condition: this transport must reinitialize before it
|
|
662
|
+
// can execute anything else.
|
|
891
663
|
sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
|
|
664
|
+
retireAfterToolSurfaceReset(res, registry, sessionId);
|
|
892
665
|
return;
|
|
893
666
|
}
|
|
894
667
|
await managed.transport.handleRequest(req, res, body);
|
|
895
668
|
} finally {
|
|
896
669
|
registry.endPost(sessionId);
|
|
897
|
-
stats.activePosts -= 1;
|
|
898
670
|
}
|
|
899
671
|
return;
|
|
900
672
|
}
|
|
@@ -909,7 +681,12 @@ async function handleMcpGet(
|
|
|
909
681
|
route: McpSessionRoute,
|
|
910
682
|
currentToolSurfaceFingerprint: () => string | undefined,
|
|
911
683
|
resolveRuntimeSchema?: (context: McpToolContext) => Promise<CanonicalRuntimeToolSchema | undefined>,
|
|
684
|
+
controllerHome?: string,
|
|
912
685
|
): Promise<void> {
|
|
686
|
+
if (connectorCutoverDrainActive(req, controllerHome)) {
|
|
687
|
+
sendReleaseCutoverDraining(res);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
913
690
|
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
914
691
|
const managed = sessionId ? registry.get(sessionId) : undefined;
|
|
915
692
|
if (!managed || managed.route !== route || managed.principalId !== principalFromRequest(req)) {
|
|
@@ -933,9 +710,8 @@ async function handleMcpGet(
|
|
|
933
710
|
: undefined,
|
|
934
711
|
);
|
|
935
712
|
if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
|
|
936
|
-
// Preserve the existing SSE transport while asking the host to
|
|
937
|
-
// reinitialize. The replacement initialize owns supersession/cleanup.
|
|
938
713
|
sendMcpToolSurfaceReset(res, managed.toolSurfaceFingerprint, currentFingerprint);
|
|
714
|
+
retireAfterToolSurfaceReset(res, registry, sessionId!);
|
|
939
715
|
return;
|
|
940
716
|
}
|
|
941
717
|
await managed.transport.handleRequest(req, res);
|
|
@@ -992,32 +768,25 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
|
|
|
992
768
|
tokenStore?.load();
|
|
993
769
|
const oauthProvider = tokenStore ? createMcpOAuthProvider(tokenStore) : null;
|
|
994
770
|
const configuredPublicOrigin = getConfiguredPublicOrigin(serviceConfig);
|
|
995
|
-
const sessionRegistry =
|
|
996
|
-
maximumSessions: MAX_MCP_SESSIONS,
|
|
997
|
-
maximumSessionsPerPrincipal: MAX_MCP_SESSIONS_PER_PRINCIPAL,
|
|
998
|
-
idleTtlMs: MCP_SESSION_IDLE_TTL_MS,
|
|
999
|
-
streamLeaseMs: MCP_STREAM_LEASE_MS,
|
|
1000
|
-
absoluteLifetimeMs: MCP_SESSION_ABSOLUTE_LIFETIME_MS,
|
|
1001
|
-
activePostStallMs: MCP_ACTIVE_POST_STALL_MS,
|
|
771
|
+
const sessionRegistry = createMcpHttpSessionRegistry<NodeStreamableHTTPServerTransport, McpToolContext>({
|
|
1002
772
|
onSessionClosed: (session, reason) => {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
);
|
|
773
|
+
if (reason !== 'transport_close') return;
|
|
774
|
+
recordMcpTransportEvent(controllerHome, {
|
|
775
|
+
kind: 'interruption',
|
|
776
|
+
sessionId: session.sessionId,
|
|
777
|
+
connectionId: session.connectionId,
|
|
778
|
+
route: session.route,
|
|
779
|
+
principalId: session.principalId,
|
|
780
|
+
reason,
|
|
781
|
+
});
|
|
1012
782
|
},
|
|
1013
783
|
});
|
|
1014
|
-
const runtimeStats: McpRuntimeStats = { initializing: 0,
|
|
784
|
+
const runtimeStats: McpRuntimeStats = { initializing: 0, activePostStartedAt: new Map(), rejectedOverload: 0 };
|
|
1015
785
|
const toolContext = createMcpToolContext({ ...opts, repo: repoRoot, controllerHome, profile });
|
|
1016
786
|
const baseOptions: McpServerOptions = {
|
|
1017
787
|
repo: repoRoot,
|
|
1018
788
|
controllerHome,
|
|
1019
789
|
profile,
|
|
1020
|
-
toolset: opts.toolset,
|
|
1021
790
|
enableChatgptBrowser: opts.enableChatgptBrowser,
|
|
1022
791
|
enableDevRunner: opts.enableDevRunner,
|
|
1023
792
|
devRunnerAgents: opts.devRunnerAgents,
|
|
@@ -1055,295 +824,43 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
|
|
|
1055
824
|
return await readCanonicalRuntimeToolSchema(context, sharedRuntimeProxy);
|
|
1056
825
|
}
|
|
1057
826
|
: undefined;
|
|
1058
|
-
const localControllerConfig = {
|
|
1059
|
-
enabled: serviceConfig?.localController?.enabled ?? profile === 'controller',
|
|
1060
|
-
host: serviceConfig?.localController?.host ?? '127.0.0.1',
|
|
1061
|
-
port: serviceConfig?.localController?.port ?? 8766,
|
|
1062
|
-
};
|
|
1063
|
-
const compatibilityToolDefinitions = buildMcpToolDefinitions(toolContext.policy, { enableChatgptBrowser: opts.enableChatgptBrowser === true });
|
|
1064
827
|
const toolSurface = toolContext.policy.profile === 'controller' ? FORGE_TOOL_SURFACE : `${toolContext.policy.profile}-legacy-v1`;
|
|
1065
828
|
const toolSurfaceSchemaVersion = toolContext.policy.profile === 'controller' ? FORGE_MCP_SCHEMA_VERSION : 1;
|
|
1066
829
|
const forgeVersion = FORGE_VERSION;
|
|
1067
|
-
const repoId = toolContext.policy.profile === 'controller' || !repoRoot ? undefined : repositoryIdentity(repoRoot);
|
|
1068
|
-
const startedAt = new Date().toISOString();
|
|
1069
|
-
const localOrigin = `http://${host === '::' || host === '0.0.0.0' ? '127.0.0.1' : host}:${port}`;
|
|
1070
|
-
const advertisedOrigin = configuredPublicOrigin ?? localOrigin;
|
|
1071
830
|
const app = express();
|
|
1072
831
|
app.set('trust proxy', 1);
|
|
1073
832
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
res.setHeader('x-forge-tool-surface', toolSurface);
|
|
1102
|
-
res.setHeader('x-forge-version', String(forgeVersion));
|
|
1103
|
-
res.setHeader('x-forge-schema-version', String(toolSurfaceSchemaVersion));
|
|
1104
|
-
if (health?.toolset) res.setHeader('x-forge-toolset', health.toolset);
|
|
1105
|
-
if (health?.runtimeToolSurfaceFingerprint) res.setHeader('x-forge-runtime-tool-surface-fingerprint', health.runtimeToolSurfaceFingerprint);
|
|
1106
|
-
if (health?.toolSurfaceFingerprint) res.setHeader('x-forge-tool-surface-fingerprint', health.toolSurfaceFingerprint);
|
|
1107
|
-
const sessionSnapshot = sessionRegistry.snapshot();
|
|
1108
|
-
res.json({
|
|
1109
|
-
status: 'ok',
|
|
1110
|
-
server: 'forge-mcp',
|
|
1111
|
-
forgeInstanceId: forgeInstance.instanceId,
|
|
1112
|
-
...(process.env.FORGE_MCP_INSTANCE_ID
|
|
1113
|
-
? { controllerInstanceId: process.env.FORGE_MCP_INSTANCE_ID }
|
|
1114
|
-
: {}),
|
|
1115
|
-
version: forgeVersion,
|
|
1116
|
-
profile: toolContext.policy.profile,
|
|
1117
|
-
toolSurface,
|
|
1118
|
-
schemaVersion: toolSurfaceSchemaVersion,
|
|
1119
|
-
toolSurfaceFingerprint: health?.toolSurfaceFingerprint,
|
|
1120
|
-
runtimeToolSurfaceFingerprint: health?.runtimeToolSurfaceFingerprint,
|
|
1121
|
-
generation: health?.generation,
|
|
1122
|
-
source: health?.source,
|
|
1123
|
-
// Deprecated diagnostic alias; not a Runtime schema claim.
|
|
1124
|
-
toolset: health?.toolset ?? 'full',
|
|
1125
|
-
gatewayToolset: health?.toolset ?? 'full',
|
|
1126
|
-
toolCount: health?.toolCount,
|
|
1127
|
-
compatibilityToolCount: compatibilityToolDefinitions.length,
|
|
1128
|
-
runtimeIdentity: health?.runtimeIdentity,
|
|
1129
|
-
configuredAccessMode: health?.configuredAccessMode,
|
|
1130
|
-
effectiveAccessMode: health?.effectiveAccessMode,
|
|
1131
|
-
effectiveToolset: health?.effectiveToolset,
|
|
1132
|
-
accessModeSource: health?.accessModeSource,
|
|
1133
|
-
accessModeLastAppliedAt: health?.accessModeLastAppliedAt,
|
|
1134
|
-
exposureRevision: health?.exposureRevision,
|
|
1135
|
-
...(repoId ? { repoId } : {}),
|
|
1136
|
-
startedAt,
|
|
1137
|
-
runner: {
|
|
1138
|
-
enabled: toolContext.policy.execution.agentRunner,
|
|
1139
|
-
defaultTimeoutMs: toolContext.policy.execution.runnerTimeoutMs,
|
|
1140
|
-
maxTimeoutMs: toolContext.policy.execution.runnerMaxTimeoutMs,
|
|
1141
|
-
},
|
|
1142
|
-
auth: authMode === 'oauth'
|
|
1143
|
-
? (oauthPassphrase ? 'oauth' : 'missing')
|
|
1144
|
-
: authMode === 'bearer'
|
|
1145
|
-
? (authToken ? 'required' : 'missing')
|
|
1146
|
-
: 'none',
|
|
1147
|
-
...(oauthProvider ? { oauthAuthorizationCodes: oauthProvider.authorizationCodeDiagnostics() } : {}),
|
|
1148
|
-
mcpEndpoint: `${advertisedOrigin}/mcp`,
|
|
1149
|
-
// Grok now completes standard OAuth dynamic registration + PKCE on the
|
|
1150
|
-
// canonical MCP resource. Keep /mcp-grok below only as a legacy alias.
|
|
1151
|
-
grokEndpoint: `${advertisedOrigin}/mcp`,
|
|
1152
|
-
bearerEndpoint: `${advertisedOrigin}/mcp-bearer`,
|
|
1153
|
-
sessions: {
|
|
1154
|
-
...sessionSnapshot,
|
|
1155
|
-
initializing: runtimeStats.initializing,
|
|
1156
|
-
activePosts: runtimeStats.activePosts,
|
|
1157
|
-
maximumActivePosts: MAX_ACTIVE_POSTS,
|
|
1158
|
-
rejectedOverload: runtimeStats.rejectedOverload,
|
|
1159
|
-
},
|
|
1160
|
-
});
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
// Recovery probes this endpoint every few seconds. Keep it strictly in-memory
|
|
1164
|
-
// and transport-scoped: whole-control-plane readiness below may traverse every
|
|
1165
|
-
// repository projection and probe the local bridge, which must never become a
|
|
1166
|
-
// periodic event-loop load generator for the public MCP Connector itself.
|
|
1167
|
-
app.get('/transport-ready', (_req, res) => {
|
|
1168
|
-
const sessionSnapshot = sessionRegistry.snapshot();
|
|
1169
|
-
const sessionCapacityReady = sessionSnapshot.acceptingNewSessions
|
|
1170
|
-
&& runtimeStats.initializing < MAX_INITIALIZING_SESSIONS
|
|
1171
|
-
&& runtimeStats.activePosts < MAX_ACTIVE_POSTS;
|
|
1172
|
-
res.status(sessionCapacityReady ? 200 : 503).json({
|
|
1173
|
-
ready: sessionCapacityReady,
|
|
1174
|
-
profile: toolContext.policy.profile,
|
|
1175
|
-
gateway: sessionCapacityReady ? 'ready' : 'saturated',
|
|
1176
|
-
sessionCapacity: sessionSnapshot,
|
|
1177
|
-
runtimeCapacity: {
|
|
1178
|
-
initializing: runtimeStats.initializing,
|
|
1179
|
-
maximumInitializing: MAX_INITIALIZING_SESSIONS,
|
|
1180
|
-
activePosts: runtimeStats.activePosts,
|
|
1181
|
-
maximumActivePosts: MAX_ACTIVE_POSTS,
|
|
1182
|
-
},
|
|
1183
|
-
});
|
|
1184
|
-
});
|
|
1185
|
-
|
|
1186
|
-
app.get('/ready', async (_req, res) => {
|
|
1187
|
-
const runtimeGeneration = currentRuntimeGeneration();
|
|
1188
|
-
const sessionSnapshot = sessionRegistry.snapshot();
|
|
1189
|
-
const sessionCapacityReady = sessionSnapshot.acceptingNewSessions
|
|
1190
|
-
&& runtimeStats.initializing < MAX_INITIALIZING_SESSIONS
|
|
1191
|
-
&& runtimeStats.activePosts < MAX_ACTIVE_POSTS;
|
|
1192
|
-
if (!runtimeControllerHome) {
|
|
1193
|
-
res.status(sessionCapacityReady ? 200 : 503).json({
|
|
1194
|
-
ready: sessionCapacityReady,
|
|
1195
|
-
profile: toolContext.policy.profile,
|
|
1196
|
-
gateway: sessionCapacityReady ? 'ready' : 'saturated',
|
|
1197
|
-
controllerDaemon: 'not-required',
|
|
1198
|
-
sessionCapacity: sessionSnapshot,
|
|
1199
|
-
});
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
const daemon = readForgeRuntimeStatus(runtimeControllerHome);
|
|
1203
|
-
const runtimeState = loadMcpServiceRuntimeState(runtimeControllerHome, repoRoot);
|
|
1204
|
-
const repositories = listRepositories(runtimeControllerHome).filter((repository) => repository.enabled && !repository.removedAt);
|
|
1205
|
-
const projectionSnapshots = repositories.map((repository) => {
|
|
1206
|
-
const snapshot = readRepositoryProjectionSnapshot(runtimeControllerHome, repository.repoId);
|
|
1207
|
-
const reconciliation = reconcileReadinessProjectionSource(
|
|
1208
|
-
snapshot,
|
|
1209
|
-
legacyIssueAuthorityRetired(repository.canonicalRoot)
|
|
1210
|
-
? undefined
|
|
1211
|
-
: buildControllerTaskLedgerProjection(repository.canonicalRoot),
|
|
1212
|
-
);
|
|
1213
|
-
return { repoId: repository.repoId, snapshot, reconciliation };
|
|
1214
|
-
});
|
|
1215
|
-
const staleRepositories = projectionSnapshots
|
|
1216
|
-
.filter(({ snapshot }) => snapshot.stale)
|
|
1217
|
-
.map(({ repoId }) => repoId);
|
|
1218
|
-
const blockingStaleRepositories = projectionSnapshots
|
|
1219
|
-
.filter(({ snapshot }) => projectionBlocksReadiness(snapshot))
|
|
1220
|
-
.map(({ repoId }) => repoId);
|
|
1221
|
-
const sourceMismatches = projectionSnapshots
|
|
1222
|
-
.filter(({ reconciliation }) => reconciliation.status === 'mismatch')
|
|
1223
|
-
.map(({ repoId, reconciliation }) => ({ repoId, ...reconciliation }));
|
|
1224
|
-
const localBridgeHealth = localControllerConfig.enabled
|
|
1225
|
-
? await jsonHealth(localControllerHealthUrl(localControllerConfig.host, localControllerConfig.port))
|
|
1226
|
-
: null;
|
|
1227
|
-
const localBridgeReady = !localControllerConfig.enabled
|
|
1228
|
-
|| localControllerDiagnosticMatchesRuntime(localBridgeHealth, runtimeGeneration?.generation);
|
|
1229
|
-
const daemonReady = daemon.status === 'ready' && daemon.degraded !== true;
|
|
1230
|
-
const projectionReady = blockingStaleRepositories.length === 0;
|
|
1231
|
-
const publicConfigured = Boolean(runtimeState?.tunnel?.publicEndpoint);
|
|
1232
|
-
const publicReady = !publicConfigured || runtimeState?.tunnel?.healthy === true;
|
|
1233
|
-
const connectorReady = !publicConfigured || (
|
|
1234
|
-
publicReady
|
|
1235
|
-
&& runtimeState?.tunnel?.connectorNeedsReconnect !== true
|
|
1236
|
-
);
|
|
1237
|
-
const ready = daemonReady && projectionReady && localBridgeReady && sessionCapacityReady;
|
|
1238
|
-
res.status(ready ? 200 : 503).json({
|
|
1239
|
-
ready,
|
|
1240
|
-
generation: runtimeGeneration?.generation,
|
|
1241
|
-
source: runtimeGeneration?.source,
|
|
1242
|
-
gateway: { status: ready ? 'ready' : 'degraded', thin: true, eventLoopIsolatedFromWorkers: true },
|
|
1243
|
-
controllerDaemon: daemon,
|
|
1244
|
-
localBridge: {
|
|
1245
|
-
enabled: localControllerConfig.enabled,
|
|
1246
|
-
ready: localBridgeReady,
|
|
1247
|
-
endpoint: `http://${localControllerConfig.host === '::1' ? '[::1]' : localControllerConfig.host}:${localControllerConfig.port}/`,
|
|
1248
|
-
},
|
|
1249
|
-
projections: {
|
|
1250
|
-
ready: projectionReady,
|
|
1251
|
-
repositoryCount: repositories.length,
|
|
1252
|
-
staleRepositories,
|
|
1253
|
-
blockingStaleRepositories,
|
|
1254
|
-
sourceMismatches,
|
|
1255
|
-
},
|
|
1256
|
-
publicReadiness: {
|
|
1257
|
-
configured: publicConfigured,
|
|
1258
|
-
ready: publicReady,
|
|
1259
|
-
endpoint: runtimeState?.tunnel?.publicEndpoint,
|
|
1260
|
-
},
|
|
1261
|
-
connectorReadiness: {
|
|
1262
|
-
configured: publicConfigured,
|
|
1263
|
-
ready: connectorReady,
|
|
1264
|
-
connectorNeedsReconnect: runtimeState?.tunnel?.connectorNeedsReconnect === true,
|
|
1265
|
-
},
|
|
1266
|
-
sessionCapacity: sessionSnapshot,
|
|
1267
|
-
});
|
|
1268
|
-
});
|
|
1269
|
-
|
|
1270
|
-
app.get('/repos/:repoId/health', (req, res) => {
|
|
1271
|
-
if (!runtimeControllerHome) {
|
|
1272
|
-
res.status(404).json({ error: 'controller profile required' });
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
try {
|
|
1276
|
-
const repository = getRepository(req.params.repoId, runtimeControllerHome, { includeRemoved: true });
|
|
1277
|
-
const projection = readRepositoryProjectionSnapshot(runtimeControllerHome, repository.repoId);
|
|
1278
|
-
res.json({
|
|
1279
|
-
status: repository.enabled && !repository.removedAt ? 'ok' : 'disabled',
|
|
1280
|
-
repository: {
|
|
1281
|
-
repoId: repository.repoId,
|
|
1282
|
-
checkoutId: repository.activeCheckoutId,
|
|
1283
|
-
enabled: repository.enabled,
|
|
1284
|
-
removedAt: repository.removedAt,
|
|
1285
|
-
},
|
|
1286
|
-
projection,
|
|
1287
|
-
});
|
|
1288
|
-
} catch (error) {
|
|
1289
|
-
res.status(404).json({ error: error instanceof Error ? error.message : String(error) });
|
|
1290
|
-
}
|
|
833
|
+
registerMcpHttpObservationRoutes({
|
|
834
|
+
app,
|
|
835
|
+
toolContext,
|
|
836
|
+
sessionRegistry,
|
|
837
|
+
runtimeStats,
|
|
838
|
+
runtimeControllerHome,
|
|
839
|
+
repoRoot,
|
|
840
|
+
forgeInstanceId: forgeInstance.instanceId,
|
|
841
|
+
currentRuntimeToolSurfaceFingerprint,
|
|
842
|
+
toolSurface,
|
|
843
|
+
toolSurfaceSchemaVersion,
|
|
844
|
+
forgeVersion,
|
|
845
|
+
authMode,
|
|
846
|
+
authTokenConfigured: Boolean(authToken),
|
|
847
|
+
oauthPassphraseConfigured: Boolean(oauthPassphrase),
|
|
848
|
+
oauthAuthorizationCodeDiagnostics: oauthProvider ? () => oauthProvider.authorizationCodeDiagnostics() : undefined,
|
|
849
|
+
configuredPublicOrigin,
|
|
850
|
+
host,
|
|
851
|
+
port,
|
|
852
|
+
enableChatgptBrowser: opts.enableChatgptBrowser === true,
|
|
853
|
+
localController: {
|
|
854
|
+
enabled: serviceConfig?.localController?.enabled ?? profile === 'controller',
|
|
855
|
+
host: serviceConfig?.localController?.host ?? '127.0.0.1',
|
|
856
|
+
port: serviceConfig?.localController?.port ?? 8766,
|
|
857
|
+
},
|
|
858
|
+
maxInitializingSessions: MAX_INITIALIZING_SESSIONS,
|
|
859
|
+
maxActivePosts: MAX_ACTIVE_POSTS,
|
|
1291
860
|
});
|
|
1292
861
|
|
|
1293
862
|
if (authMode === 'oauth' && oauthProvider) {
|
|
1294
|
-
app
|
|
1295
|
-
app.use('/authorize', oauthTraceMiddleware('authorize'));
|
|
1296
|
-
// Reject incomplete OAuth requests before rendering the passphrase form.
|
|
1297
|
-
app.use('/authorize', rejectIncompleteOAuthAuthorize);
|
|
1298
|
-
app.use('/authorize', requirePassphrase(oauthPassphrase ?? ''));
|
|
1299
|
-
app.use('/authorize', oauthAuthorizationHandler(oauthProvider));
|
|
1300
|
-
app.use('/token', oauthTraceMiddleware('token'));
|
|
1301
|
-
app.use('/token', tokenHandler({ provider: oauthProvider, rateLimit: false }));
|
|
1302
|
-
app.use('/revoke', oauthTraceMiddleware('revoke'));
|
|
1303
|
-
app.use('/revoke', revocationHandler({ provider: oauthProvider, rateLimit: false }));
|
|
1304
|
-
app.use('/register', oauthTraceMiddleware('register'));
|
|
1305
|
-
app.use('/register', clientRegistrationHandler({ clientsStore: oauthProvider.clientsStore, rateLimit: false }));
|
|
1306
|
-
app.get('/.well-known/oauth-authorization-server', (req, res) => {
|
|
1307
|
-
const origin = getPublicOrigin(req, configuredPublicOrigin);
|
|
1308
|
-
res.json({
|
|
1309
|
-
issuer: origin,
|
|
1310
|
-
authorization_endpoint: `${origin}/authorize`,
|
|
1311
|
-
token_endpoint: `${origin}/token`,
|
|
1312
|
-
revocation_endpoint: `${origin}/revoke`,
|
|
1313
|
-
registration_endpoint: `${origin}/register`,
|
|
1314
|
-
response_types_supported: ['code'],
|
|
1315
|
-
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
1316
|
-
code_challenge_methods_supported: ['S256'],
|
|
1317
|
-
token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
|
|
1318
|
-
scopes_supported: ['forge'],
|
|
1319
|
-
});
|
|
1320
|
-
});
|
|
1321
|
-
app.get('/.well-known/openid-configuration', (req, res) => {
|
|
1322
|
-
const origin = getPublicOrigin(req, configuredPublicOrigin);
|
|
1323
|
-
res.json({
|
|
1324
|
-
issuer: origin,
|
|
1325
|
-
authorization_endpoint: `${origin}/authorize`,
|
|
1326
|
-
token_endpoint: `${origin}/token`,
|
|
1327
|
-
registration_endpoint: `${origin}/register`,
|
|
1328
|
-
response_types_supported: ['code'],
|
|
1329
|
-
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
1330
|
-
code_challenge_methods_supported: ['S256'],
|
|
1331
|
-
token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
|
|
1332
|
-
scopes_supported: ['forge'],
|
|
1333
|
-
});
|
|
1334
|
-
});
|
|
1335
|
-
const protectedResourceMetadata = (resourcePath: '/mcp' | '/mcp-grok' | '/mcp-bearer') => (req: Request, res: Response): void => {
|
|
1336
|
-
const origin = getPublicOrigin(req, configuredPublicOrigin);
|
|
1337
|
-
res.json({
|
|
1338
|
-
resource: `${origin}${resourcePath}`,
|
|
1339
|
-
authorization_servers: [origin],
|
|
1340
|
-
scopes_supported: ['forge'],
|
|
1341
|
-
bearer_methods_supported: ['header'],
|
|
1342
|
-
});
|
|
1343
|
-
};
|
|
1344
|
-
app.get('/.well-known/oauth-protected-resource/mcp', protectedResourceMetadata('/mcp'));
|
|
1345
|
-
app.get('/.well-known/oauth-protected-resource/mcp-grok', protectedResourceMetadata('/mcp-grok'));
|
|
1346
|
-
app.get('/.well-known/oauth-protected-resource/mcp-bearer', protectedResourceMetadata('/mcp-bearer'));
|
|
863
|
+
registerMcpOAuthHttpRoutes(app, oauthProvider, oauthPassphrase ?? '', configuredPublicOrigin);
|
|
1347
864
|
}
|
|
1348
865
|
|
|
1349
866
|
const setMcpResponseHeaders = (_req: Request, res: Response, next: NextFunction): void => {
|
|
@@ -1367,7 +884,7 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
|
|
|
1367
884
|
});
|
|
1368
885
|
});
|
|
1369
886
|
app.get('/mcp', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin), (req, res) => {
|
|
1370
|
-
handleMcpGet(req, res, sessionRegistry, '/mcp', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
|
|
887
|
+
handleMcpGet(req, res, sessionRegistry, '/mcp', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, controllerHome).catch((error: unknown) => {
|
|
1371
888
|
if (!res.headersSent) sendMcpRequestError(res, error);
|
|
1372
889
|
});
|
|
1373
890
|
});
|
|
@@ -1385,7 +902,7 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
|
|
|
1385
902
|
});
|
|
1386
903
|
});
|
|
1387
904
|
app.get('/mcp-grok', requireMcpHttpAuth(authMode, authToken, oauthProvider, configuredPublicOrigin, '/mcp-grok'), (req, res) => {
|
|
1388
|
-
handleMcpGet(req, res, sessionRegistry, '/mcp-grok', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
|
|
905
|
+
handleMcpGet(req, res, sessionRegistry, '/mcp-grok', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, controllerHome).catch((error: unknown) => {
|
|
1389
906
|
if (!res.headersSent) sendMcpRequestError(res, error);
|
|
1390
907
|
});
|
|
1391
908
|
});
|
|
@@ -1403,7 +920,7 @@ export async function startMcpHttp(opts: McpHttpOptions): Promise<void> {
|
|
|
1403
920
|
});
|
|
1404
921
|
});
|
|
1405
922
|
app.get('/mcp-bearer', requireMcpHttpAuth('bearer', authToken, null, configuredPublicOrigin), (req, res) => {
|
|
1406
|
-
handleMcpGet(req, res, sessionRegistry, '/mcp-bearer', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema).catch((error: unknown) => {
|
|
923
|
+
handleMcpGet(req, res, sessionRegistry, '/mcp-bearer', currentRuntimeToolSurfaceFingerprint, resolveRuntimeSchema, controllerHome).catch((error: unknown) => {
|
|
1407
924
|
if (!res.headersSent) sendMcpRequestError(res, error);
|
|
1408
925
|
});
|
|
1409
926
|
});
|