@moretea-labs/forge 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +70 -7
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +228 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
- package/adapters/computer/chatgpt-extension-target.ts +463 -0
- package/adapters/computer/chatgpt-target-router.ts +103 -0
- package/adapters/computer/chrome-extension/background.js +385 -0
- package/adapters/computer/chrome-extension/content.js +378 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +30 -0
- package/adapters/computer/desktop-operator-discovery.ts +11 -31
- package/adapters/computer/interaction-target-authority.ts +142 -18
- package/adapters/mcp/auth.ts +0 -1
- package/adapters/mcp/instructions.ts +5 -4
- package/adapters/mcp/multi-repository.ts +3 -19
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
- package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +18 -504
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +67 -23
- package/adapters/mcp/setup.ts +1 -3
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
- package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
- package/adapters/mcp/toolset-names.ts +5 -0
- package/adapters/mcp/toolset.ts +6 -5
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +154 -677
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/skill-version.json +2 -2
- package/docs/architecture/CURRENT.md +111 -41
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/domains/verification.md +1 -0
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/forge-plugin-management.md +2 -2
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +1 -1
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +2 -0
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +14 -6
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +25 -14
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +17 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +25 -63
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +48 -136
- package/scripts/check-runtime-architecture.mjs +622 -217
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/lib/project-init-lib.sh +3 -0
- package/scripts/open-source-audit-allowlist.txt +1 -2
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +0 -1
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-mcp-http-runtime.ts +5 -0
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +11 -34
- package/src/cli/agent-jobs/integration.ts +18 -49
- package/src/cli/agent-jobs/job-manager.ts +7 -3
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- package/src/cli/commands/recovery.ts +135 -121
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +4 -43
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/context/source-materializer.ts +81 -100
- package/src/cli/controller/context-pack.ts +10 -8
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +24 -51
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +39 -1
- package/src/cli/editing/edit-session.ts +1 -7
- package/src/cli/local-bridge/console-automations.ts +18 -13
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +64 -191
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +35 -16
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/registry.ts +77 -11
- package/src/cli/repositories/structured-git.ts +62 -2
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/semantic-navigation-contract.ts +9 -0
- package/src/runtime/context/semantic-navigation.ts +6 -34
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
- package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
- package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/execution/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +35 -2
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/workers/executor.ts +2 -6
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +85 -28
- package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
- package/src/runtime/plugins/browser-adapter.ts +757 -34
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
- package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +16 -0
- package/src/runtime/plugins/browser-runtime.ts +14 -1
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +222 -11
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/gmail-adapter.ts +23 -2
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +14 -7
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios-adapter.ts +1 -1
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -2
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +602 -428
- package/src/runtime/plugins/types.ts +20 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/release-coordinator.ts +296 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +105 -19
- package/src/runtime/root/package-runtime-release.ts +117 -18
- package/src/runtime/root/package-runtime-service.ts +17 -11
- package/src/runtime/root/release-execution-canary.ts +44 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -6
- package/src/runtime/root/release-materialize.ts +372 -28
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +159 -27
- package/src/runtime/root/scheduled-controller-composition.ts +28 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +36 -33
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -3
- package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3702 -342
- package/src/runtime/standalone-recovery/entry.ts +469 -39
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
- package/src/runtime/workflow/schedules/engine.ts +138 -172
- package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +201 -0
- package/supervisor/control-plane.ts +868 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +80 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +786 -0
- package/supervisor/native-messaging/host.ts +39 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +202 -0
- package/supervisor/server.ts +404 -0
- package/supervisor/store.ts +1451 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +255 -0
- package/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- package/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -150
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/stage-runtime-release.ts +0 -44
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { once } from 'node:events';
|
|
2
|
+
import { WorkflowSupervisorControlPlane } from '../../../supervisor/control-plane';
|
|
3
|
+
import { forgeWorkflowSupervisorValidators } from '../../../supervisor/forge-validators';
|
|
4
|
+
import { forgeWorkflowSupervisorLifecycleHooks } from './workflow-supervisor-composition';
|
|
5
|
+
import { resolveWorkflowSupervisorForgeHome, workflowSupervisorSocketPath } from '../../../supervisor/paths';
|
|
6
|
+
import {
|
|
7
|
+
createWorkflowSupervisorServer,
|
|
8
|
+
reconcileWorkflowSupervisorSocket,
|
|
9
|
+
WorkflowSupervisorEphemeralDiscovery,
|
|
10
|
+
} from '../../../supervisor/server';
|
|
11
|
+
import { startWorkflowSupervisorNativeBrowserAdapter, type WorkflowSupervisorNativeBrowserHandle, type WorkflowSupervisorStallProjection, type WorkflowSupervisorTransportProjection } from '../../../supervisor/native-browser-adapter';
|
|
12
|
+
import type { WorkflowSupervisorConsumerStatus } from '../../../supervisor/types';
|
|
13
|
+
import { WorkflowSupervisorStore } from '../../../supervisor/store';
|
|
14
|
+
import { createComputerInteractionTargetAuthority } from '../../../adapters/computer/interaction-target-authority';
|
|
15
|
+
import { MacOsChatgptConversationTargetPort } from '../plugins/computer-chatgpt-macos-target';
|
|
16
|
+
import { ChromeExtensionChatgptConversationTargetPort } from '../../../adapters/computer/chatgpt-extension-target';
|
|
17
|
+
import { PreferredChatgptConversationTargetPort } from '../../../adapters/computer/chatgpt-target-router';
|
|
18
|
+
import { createRuntimeComputerTargetPersistence } from './computer-target-persistence';
|
|
19
|
+
import { listDirectActivities, listUserRequests, recordDirectActivity, recordUserRequest, resolveUserRequest } from '../../../packages/kernel/identity/api/index';
|
|
20
|
+
import { getRuntimeWriteClaim } from './write-fence';
|
|
21
|
+
|
|
22
|
+
export interface RuntimeWorkflowSupervisorHandle {
|
|
23
|
+
readonly done: Promise<void>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Workflow Supervisor is a distinct lifecycle authority, not a distinct OS
|
|
29
|
+
* service. Canonical Forge Runtime already owns the durable process lifecycle;
|
|
30
|
+
* this composition gives Supervisor one in-process server/writer while keeping
|
|
31
|
+
* its SQLite and Unix socket authority isolated under the exact Controller Home.
|
|
32
|
+
*/
|
|
33
|
+
export function startWorkflowSupervisorRuntime(controllerHome: string): Promise<RuntimeWorkflowSupervisorHandle>;
|
|
34
|
+
export function startWorkflowSupervisorRuntime(
|
|
35
|
+
controllerHome: string,
|
|
36
|
+
options: { nativeBrowserAdapter?: boolean },
|
|
37
|
+
): Promise<RuntimeWorkflowSupervisorHandle>;
|
|
38
|
+
export async function startWorkflowSupervisorRuntime(
|
|
39
|
+
controllerHome: string,
|
|
40
|
+
options: { nativeBrowserAdapter?: boolean } = {},
|
|
41
|
+
): Promise<RuntimeWorkflowSupervisorHandle> {
|
|
42
|
+
const forgeHome = resolveWorkflowSupervisorForgeHome(controllerHome);
|
|
43
|
+
const socketPath = workflowSupervisorSocketPath(forgeHome);
|
|
44
|
+
const claim = getRuntimeWriteClaim();
|
|
45
|
+
const writer = claim && !claim.unmanaged
|
|
46
|
+
? { runtimeInstanceId: claim.runtimeInstanceId, fencingGeneration: claim.fencingGeneration, pid: claim.ownerPid }
|
|
47
|
+
: undefined;
|
|
48
|
+
if (writer) await reconcileWorkflowSupervisorSocket({ socketPath, incoming: writer });
|
|
49
|
+
const store = new WorkflowSupervisorStore(forgeHome, {
|
|
50
|
+
activeReleaseId: () => getRuntimeWriteClaim()?.releaseId,
|
|
51
|
+
});
|
|
52
|
+
const controlPlane = new WorkflowSupervisorControlPlane(
|
|
53
|
+
store,
|
|
54
|
+
forgeWorkflowSupervisorValidators(),
|
|
55
|
+
forgeWorkflowSupervisorLifecycleHooks(controllerHome),
|
|
56
|
+
);
|
|
57
|
+
const discovery = new WorkflowSupervisorEphemeralDiscovery();
|
|
58
|
+
const browserAdapterEnabled = true;
|
|
59
|
+
const nativeBrowserAdapterEnabled = options.nativeBrowserAdapter !== false;
|
|
60
|
+
let nativeBrowser: WorkflowSupervisorNativeBrowserHandle | undefined;
|
|
61
|
+
const transportActivityId = (taskId?: string): string => `workflow-supervisor-transport-${(taskId ?? 'instance').replace(/[^a-zA-Z0-9._-]/g, '_')}`;
|
|
62
|
+
const reportTransportState = (projection: WorkflowSupervisorTransportProjection): void => {
|
|
63
|
+
const activityId = transportActivityId(projection.taskId);
|
|
64
|
+
const existing = listDirectActivities(controllerHome, 200).find((item) => item.activityId === activityId);
|
|
65
|
+
const recovered = projection.state === 'recovered';
|
|
66
|
+
recordDirectActivity(controllerHome, {
|
|
67
|
+
activityId,
|
|
68
|
+
capabilityId: 'workflow-supervisor.transport',
|
|
69
|
+
kind: 'direct_execution',
|
|
70
|
+
targetScope: projection.taskId ? `workflow-supervisor-task:${projection.taskId}` : 'workflow-supervisor:instance',
|
|
71
|
+
principalId: 'forge-runtime',
|
|
72
|
+
status: recovered ? 'completed' : 'running',
|
|
73
|
+
startedAt: existing?.startedAt ?? projection.firstFailureAt ?? projection.observedAt,
|
|
74
|
+
...(recovered ? { completedAt: projection.observedAt } : {}),
|
|
75
|
+
summary: recovered
|
|
76
|
+
? 'Workflow Supervisor transport recovered; autonomous continuation resumed without user action.'
|
|
77
|
+
: `Workflow Supervisor transport is temporarily degraded and auto-recovering${projection.code ? ` (${projection.code})` : ''}. Durable task/effect progress is preserved.`,
|
|
78
|
+
});
|
|
79
|
+
if (recovered && projection.taskId) {
|
|
80
|
+
const prefix = `workflow-supervisor.transport-human:${projection.taskId}:`;
|
|
81
|
+
for (const request of listUserRequests(controllerHome, 'pending').filter((item) => item.rootCauseKey.startsWith(prefix))) {
|
|
82
|
+
resolveUserRequest(controllerHome, { requestId: request.requestId, decision: 'transport_recovered', resolvedBy: 'forge-runtime' });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const stallActivityId = (taskId: string): string => `workflow-supervisor-stall-${taskId.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
|
|
87
|
+
const reportTaskStall = (projection: WorkflowSupervisorStallProjection): void => {
|
|
88
|
+
const activityId = stallActivityId(projection.taskId);
|
|
89
|
+
const existing = listDirectActivities(controllerHome, 200).find((item) => item.activityId === activityId);
|
|
90
|
+
const exhausted = projection.state === 'exhausted';
|
|
91
|
+
if (!exhausted && !existing) return;
|
|
92
|
+
const budget = projection.generations !== undefined && projection.maxGenerations !== undefined
|
|
93
|
+
? ` (${projection.generations}/${projection.maxGenerations})`
|
|
94
|
+
: '';
|
|
95
|
+
recordDirectActivity(controllerHome, {
|
|
96
|
+
activityId,
|
|
97
|
+
capabilityId: 'workflow-supervisor.continuation',
|
|
98
|
+
kind: 'direct_execution',
|
|
99
|
+
targetScope: `workflow-supervisor-task:${projection.taskId}`,
|
|
100
|
+
principalId: 'forge-runtime',
|
|
101
|
+
status: exhausted ? 'running' : 'completed',
|
|
102
|
+
startedAt: existing?.startedAt ?? projection.observedAt,
|
|
103
|
+
...(!exhausted ? { completedAt: projection.observedAt } : {}),
|
|
104
|
+
summary: exhausted
|
|
105
|
+
? projection.stallKind === 'provider_resume_exhausted'
|
|
106
|
+
? 'Workflow Supervisor exhausted bounded provider-resume attempts. The committed effect is preserved; only read-only reconciliation for late receipt evidence continues, and no duplicate provider turn will be sent.'
|
|
107
|
+
: `Workflow Supervisor exhausted bounded automatic dispatch generations${budget}. The task/effect remains durable, but automatic sending is paused until explicit recovery or new authoritative evidence; no duplicate provider turn will be sent.`
|
|
108
|
+
: 'Workflow Supervisor continuation stall cleared; the durable task/effect can progress again.',
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const requestHumanAction = (input: { taskId: string; effectId?: string; action: 'login' | 'grant_permission'; code: string }): void => {
|
|
112
|
+
const actionLabel = input.action === 'login' ? 'sign in to ChatGPT' : 'restore browser automation permission';
|
|
113
|
+
recordUserRequest(controllerHome, {
|
|
114
|
+
kind: 'user_action_request',
|
|
115
|
+
rootCauseKey: `workflow-supervisor.transport-human:${input.taskId}:${input.action}`,
|
|
116
|
+
title: input.action === 'login' ? 'ChatGPT sign-in required' : 'Browser permission required',
|
|
117
|
+
summary: `Workflow Supervisor cannot continue this task until you ${actionLabel}. The task and effect ledger are preserved and will resume automatically afterward.`,
|
|
118
|
+
actionRequired: input.action,
|
|
119
|
+
targetScope: { scopeKind: 'workflow_supervisor_task', scopeId: input.taskId },
|
|
120
|
+
presentation: {
|
|
121
|
+
severity: 'blocked',
|
|
122
|
+
creationReason: 'workflow_supervisor_transport_human_boundary',
|
|
123
|
+
reason: input.code,
|
|
124
|
+
currentState: { taskId: input.taskId, ...(input.effectId ? { effectId: input.effectId } : {}), transportCode: input.code },
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const targetAuthority = createComputerInteractionTargetAuthority(createRuntimeComputerTargetPersistence());
|
|
129
|
+
const extensionTargetPort = new ChromeExtensionChatgptConversationTargetPort(controllerHome, targetAuthority);
|
|
130
|
+
const appleEventsTargetPort = new MacOsChatgptConversationTargetPort(controllerHome, targetAuthority);
|
|
131
|
+
const targetPort = new PreferredChatgptConversationTargetPort(extensionTargetPort, appleEventsTargetPort);
|
|
132
|
+
const browserConsumerStatus = (): WorkflowSupervisorConsumerStatus => nativeBrowser?.status() ?? {
|
|
133
|
+
enabled: browserAdapterEnabled,
|
|
134
|
+
running: false,
|
|
135
|
+
observedAt: new Date().toISOString(),
|
|
136
|
+
transportFailureStreak: 0,
|
|
137
|
+
providerBackpressureMs: 0,
|
|
138
|
+
stalled: false,
|
|
139
|
+
};
|
|
140
|
+
const server = createWorkflowSupervisorServer({
|
|
141
|
+
controlPlane,
|
|
142
|
+
socketPath,
|
|
143
|
+
discovery,
|
|
144
|
+
browserAdapterEnabled,
|
|
145
|
+
browserConsumerStatus,
|
|
146
|
+
computerExtensionBroker: extensionTargetPort,
|
|
147
|
+
...(writer ? { writer } : {}),
|
|
148
|
+
});
|
|
149
|
+
const done = once(server, 'close').then(() => undefined);
|
|
150
|
+
await once(server, 'listening');
|
|
151
|
+
let closing = false;
|
|
152
|
+
let reconciliationInFlight: Promise<void> | undefined;
|
|
153
|
+
const reconcileCommittedContinuations = (): void => {
|
|
154
|
+
if (closing || reconciliationInFlight) return;
|
|
155
|
+
reconciliationInFlight = controlPlane.reconcileCommittedContinuations()
|
|
156
|
+
.then(() => undefined)
|
|
157
|
+
.catch((error) => { process.stderr.write(`[workflow-supervisor-reconcile] ${error instanceof Error ? error.message : String(error)}\\n`); })
|
|
158
|
+
.finally(() => { reconciliationInFlight = undefined; });
|
|
159
|
+
};
|
|
160
|
+
// Reconcile before browser polling starts so a Runtime restart can finish a
|
|
161
|
+
// previously committed CONTINUE without requiring another user/provider turn.
|
|
162
|
+
reconcileCommittedContinuations();
|
|
163
|
+
// Reconciliation is a core Runtime responsibility. Keep this timer referenced
|
|
164
|
+
// for the whole Runtime lifetime instead of treating continuation delivery as
|
|
165
|
+
// best-effort background work.
|
|
166
|
+
const reconciliationTimer = setInterval(reconcileCommittedContinuations, 2_000);
|
|
167
|
+
if (nativeBrowserAdapterEnabled) {
|
|
168
|
+
nativeBrowser = startWorkflowSupervisorNativeBrowserAdapter(controlPlane, discovery, {
|
|
169
|
+
targetPort,
|
|
170
|
+
nowMs: () => Date.now(),
|
|
171
|
+
// Liveness authority for one applied provider turn. Live evidence: these
|
|
172
|
+
// Supervisor turns run for 10-50 minutes, so any quiet window shorter than
|
|
173
|
+
// that reserved duplicate recovery prompts on turns that were still
|
|
174
|
+
// working (60s and then 3 minutes both fired in production). This is the
|
|
175
|
+
// existing bounded maximum, and the digest itself now tracks live turn
|
|
176
|
+
// activity, so the window only elapses on a genuinely silent surface. A
|
|
177
|
+
// real provider failure is classified from provider failure text
|
|
178
|
+
// independently of this threshold.
|
|
179
|
+
providerIdleGraceMs: 10 * 60_000,
|
|
180
|
+
providerScopeKey: controllerHome,
|
|
181
|
+
sleep: async (ms) => { await new Promise((resolve) => setTimeout(resolve, ms)); },
|
|
182
|
+
setInterval: (handler, ms) => setInterval(handler, ms),
|
|
183
|
+
clearInterval: (timer) => clearInterval(timer),
|
|
184
|
+
onError: (error) => { process.stderr.write(`[workflow-supervisor-computer-target] ${error instanceof Error ? error.message : String(error)}\\n`); },
|
|
185
|
+
reportTransportState,
|
|
186
|
+
reportTaskStall,
|
|
187
|
+
requestHumanAction,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
done,
|
|
192
|
+
async close(): Promise<void> {
|
|
193
|
+
if (closing) return;
|
|
194
|
+
closing = true;
|
|
195
|
+
clearInterval(reconciliationTimer);
|
|
196
|
+
try {
|
|
197
|
+
await reconciliationInFlight?.catch(() => undefined);
|
|
198
|
+
await nativeBrowser?.close().catch(() => undefined);
|
|
199
|
+
if (server.listening) {
|
|
200
|
+
await new Promise<void>((resolve, reject) => {
|
|
201
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} finally {
|
|
205
|
+
store.close();
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import { resolveControllerHome } from '../../cli/repositories/controller-home';
|
|
3
3
|
import { isProcessAlive } from '../shared/process-tree';
|
|
4
|
-
import { readRuntimeOwner, type RuntimeOwnerRecord } from './ownership';
|
|
4
|
+
import { readRuntimeIncarnation, readRuntimeOwner, type RuntimeOwnerRecord } from './ownership';
|
|
5
5
|
import {
|
|
6
6
|
readRuntimeReleaseAuthority,
|
|
7
7
|
type RuntimeReleaseAuthority,
|
|
@@ -29,6 +29,7 @@ export type RuntimeWriteAction = (typeof RUNTIME_WRITE_ACTIONS)[number];
|
|
|
29
29
|
export interface RuntimeWriteClaim {
|
|
30
30
|
controllerHome: string;
|
|
31
31
|
runtimeInstanceId: string;
|
|
32
|
+
fencingGeneration: number;
|
|
32
33
|
ownerPid: number;
|
|
33
34
|
releaseAuthorityRevision: number;
|
|
34
35
|
releaseFencingToken: string;
|
|
@@ -50,6 +51,7 @@ export interface RuntimeWriteFenceCheck {
|
|
|
50
51
|
export const RUNTIME_WRITE_CLAIM_ENV = {
|
|
51
52
|
controllerHome: 'FORGE_CONTROLLER_HOME',
|
|
52
53
|
runtimeInstanceId: 'FORGE_RUNTIME_INSTANCE_ID',
|
|
54
|
+
fencingGeneration: 'FORGE_RUNTIME_INCARNATION_GENERATION',
|
|
53
55
|
ownerPid: 'FORGE_RUNTIME_OWNER_PID',
|
|
54
56
|
releaseAuthorityRevision: 'FORGE_RELEASE_AUTHORITY_REVISION',
|
|
55
57
|
releaseFencingToken: 'FORGE_RELEASE_FENCING_TOKEN',
|
|
@@ -75,6 +77,7 @@ function claimFromAuthority(
|
|
|
75
77
|
return {
|
|
76
78
|
controllerHome: resolveControllerHome(controllerHome),
|
|
77
79
|
runtimeInstanceId: owner.runtimeInstanceId,
|
|
80
|
+
fencingGeneration: owner.fencingGeneration ?? 0,
|
|
78
81
|
ownerPid: owner.pid,
|
|
79
82
|
releaseAuthorityRevision: authority.revision,
|
|
80
83
|
releaseFencingToken: authority.fencingToken,
|
|
@@ -85,11 +88,22 @@ function claimFromAuthority(
|
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
export function currentRuntimeWriteClaimEnvironment(controllerHome: string): Record<string, string> {
|
|
92
|
+
const home = resolveControllerHome(controllerHome);
|
|
93
|
+
const owner = readRuntimeOwner(home);
|
|
94
|
+
const authority = readRuntimeReleaseAuthority(home);
|
|
95
|
+
if (!owner || !authority) {
|
|
96
|
+
throw new Error('RUNTIME_WRITE_CLAIM_BIND_FAILED: Runtime owner or release authority is missing');
|
|
97
|
+
}
|
|
98
|
+
return runtimeWriteClaimEnvironment(claimFromAuthority(home, owner, authority));
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
export function runtimeWriteClaimEnvironment(claim: RuntimeWriteClaim): Record<string, string> {
|
|
89
102
|
if (claim.unmanaged) return {};
|
|
90
103
|
return {
|
|
91
104
|
[RUNTIME_WRITE_CLAIM_ENV.controllerHome]: claim.controllerHome,
|
|
92
105
|
[RUNTIME_WRITE_CLAIM_ENV.runtimeInstanceId]: claim.runtimeInstanceId,
|
|
106
|
+
[RUNTIME_WRITE_CLAIM_ENV.fencingGeneration]: String(claim.fencingGeneration),
|
|
93
107
|
[RUNTIME_WRITE_CLAIM_ENV.ownerPid]: String(claim.ownerPid),
|
|
94
108
|
[RUNTIME_WRITE_CLAIM_ENV.releaseAuthorityRevision]: String(claim.releaseAuthorityRevision),
|
|
95
109
|
[RUNTIME_WRITE_CLAIM_ENV.releaseFencingToken]: claim.releaseFencingToken,
|
|
@@ -130,6 +144,7 @@ export const clearRuntimeWriteClaimForTests = clearRuntimeWriteClaim;
|
|
|
130
144
|
export function bindRuntimeWriteClaim(input: {
|
|
131
145
|
controllerHome: string;
|
|
132
146
|
runtimeInstanceId?: string;
|
|
147
|
+
fencingGeneration?: number;
|
|
133
148
|
ownerPid?: number;
|
|
134
149
|
releaseAuthorityRevision?: number;
|
|
135
150
|
releaseFencingToken?: string;
|
|
@@ -146,6 +161,7 @@ export function bindRuntimeWriteClaim(input: {
|
|
|
146
161
|
|
|
147
162
|
const explicit = {
|
|
148
163
|
runtimeInstanceId: input.runtimeInstanceId?.trim(),
|
|
164
|
+
fencingGeneration: input.fencingGeneration,
|
|
149
165
|
ownerPid: input.ownerPid,
|
|
150
166
|
releaseAuthorityRevision: input.releaseAuthorityRevision,
|
|
151
167
|
releaseFencingToken: input.releaseFencingToken?.trim(),
|
|
@@ -157,6 +173,7 @@ export function bindRuntimeWriteClaim(input: {
|
|
|
157
173
|
const hasAnyExplicit = explicitValues.some((value) => value !== undefined && value !== '');
|
|
158
174
|
const hasFullExplicit = Boolean(
|
|
159
175
|
explicit.runtimeInstanceId
|
|
176
|
+
&& Number.isInteger(explicit.fencingGeneration) && explicit.fencingGeneration! > 0
|
|
160
177
|
&& Number.isInteger(explicit.ownerPid) && explicit.ownerPid! > 0
|
|
161
178
|
&& Number.isInteger(explicit.releaseAuthorityRevision) && explicit.releaseAuthorityRevision! > 0
|
|
162
179
|
&& explicit.releaseFencingToken
|
|
@@ -169,6 +186,7 @@ export function bindRuntimeWriteClaim(input: {
|
|
|
169
186
|
return installClaim({
|
|
170
187
|
controllerHome: home,
|
|
171
188
|
runtimeInstanceId: explicit.runtimeInstanceId!,
|
|
189
|
+
fencingGeneration: explicit.fencingGeneration!,
|
|
172
190
|
ownerPid: explicit.ownerPid!,
|
|
173
191
|
releaseAuthorityRevision: explicit.releaseAuthorityRevision!,
|
|
174
192
|
releaseFencingToken: explicit.releaseFencingToken!,
|
|
@@ -190,6 +208,7 @@ export function bindRuntimeWriteClaim(input: {
|
|
|
190
208
|
return installClaim({
|
|
191
209
|
controllerHome: home,
|
|
192
210
|
runtimeInstanceId: `unmanaged-${process.pid}`,
|
|
211
|
+
fencingGeneration: 0,
|
|
193
212
|
ownerPid: process.pid,
|
|
194
213
|
releaseAuthorityRevision: 0,
|
|
195
214
|
releaseFencingToken: 'unmanaged',
|
|
@@ -220,6 +239,7 @@ export function bindInheritedRuntimeWriteClaimFromEnvironment(
|
|
|
220
239
|
return bindRuntimeWriteClaim({
|
|
221
240
|
controllerHome,
|
|
222
241
|
runtimeInstanceId,
|
|
242
|
+
fencingGeneration: positiveInteger(env[RUNTIME_WRITE_CLAIM_ENV.fencingGeneration]),
|
|
223
243
|
ownerPid: positiveInteger(env[RUNTIME_WRITE_CLAIM_ENV.ownerPid]),
|
|
224
244
|
releaseAuthorityRevision: positiveInteger(env[RUNTIME_WRITE_CLAIM_ENV.releaseAuthorityRevision]),
|
|
225
245
|
releaseFencingToken: env[RUNTIME_WRITE_CLAIM_ENV.releaseFencingToken]?.trim(),
|
|
@@ -256,6 +276,7 @@ export function assertRuntimeMayWrite(
|
|
|
256
276
|
const home = explicitHome ?? claim?.controllerHome;
|
|
257
277
|
if (!home) return { allowed: true, reason: 'unbound_no_controller_home' };
|
|
258
278
|
const owner = readRuntimeOwner(home);
|
|
279
|
+
const incarnation = readRuntimeIncarnation(home);
|
|
259
280
|
const authority = readRuntimeReleaseAuthority(home);
|
|
260
281
|
if (!claim) {
|
|
261
282
|
if (!owner && !authority) return { allowed: true, reason: 'unbound_no_runtime_authority' };
|
|
@@ -276,9 +297,18 @@ export function assertRuntimeMayWrite(
|
|
|
276
297
|
}
|
|
277
298
|
if (!owner) return { allowed: false, reason: 'runtime_owner_missing', authority };
|
|
278
299
|
if (!isProcessAlive(owner.pid)) return { allowed: false, reason: 'runtime_owner_dead', owner, authority };
|
|
300
|
+
if (!incarnation) return { allowed: false, reason: 'runtime_incarnation_missing', owner, authority };
|
|
301
|
+
if (
|
|
302
|
+
incarnation.runtimeInstanceId !== owner.runtimeInstanceId
|
|
303
|
+
|| incarnation.pid !== owner.pid
|
|
304
|
+
|| incarnation.fencingGeneration !== (owner.fencingGeneration ?? 0)
|
|
305
|
+
) return { allowed: false, reason: 'runtime_incarnation_authority_mismatch', owner, authority };
|
|
279
306
|
if (owner.runtimeInstanceId !== claim.runtimeInstanceId) {
|
|
280
307
|
return { allowed: false, reason: 'runtime_instance_fenced', owner, authority };
|
|
281
308
|
}
|
|
309
|
+
if (incarnation.fencingGeneration !== claim.fencingGeneration) {
|
|
310
|
+
return { allowed: false, reason: 'runtime_incarnation_generation_fenced', owner, authority };
|
|
311
|
+
}
|
|
282
312
|
if (owner.pid !== claim.ownerPid) return { allowed: false, reason: 'runtime_owner_pid_fenced', owner, authority };
|
|
283
313
|
if (!authority) return { allowed: false, reason: 'runtime_release_authority_missing', owner };
|
|
284
314
|
if (authority.revision !== claim.releaseAuthorityRevision) {
|
|
@@ -299,11 +329,29 @@ export function assertRuntimeMayWrite(
|
|
|
299
329
|
return { allowed: true, owner, authority };
|
|
300
330
|
}
|
|
301
331
|
|
|
332
|
+
export class RuntimeWriteFenceError extends Error {
|
|
333
|
+
readonly code = 'WRITER_FENCED' as const;
|
|
334
|
+
|
|
335
|
+
constructor(
|
|
336
|
+
readonly action: RuntimeWriteAction,
|
|
337
|
+
readonly reason: string,
|
|
338
|
+
) {
|
|
339
|
+
super(`WRITER_FENCED:${action}:${reason}`);
|
|
340
|
+
this.name = 'RuntimeWriteFenceError';
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function isRuntimeWriteFenceError(error: unknown): error is RuntimeWriteFenceError {
|
|
345
|
+
return Boolean(error
|
|
346
|
+
&& typeof error === 'object'
|
|
347
|
+
&& (error as { code?: unknown }).code === 'WRITER_FENCED');
|
|
348
|
+
}
|
|
349
|
+
|
|
302
350
|
export function assertRuntimeMayWriteOrThrow(
|
|
303
351
|
action: RuntimeWriteAction,
|
|
304
352
|
controllerHomeOverride?: string,
|
|
305
353
|
): RuntimeReleaseAuthority | undefined {
|
|
306
354
|
const check = assertRuntimeMayWrite(action, controllerHomeOverride);
|
|
307
|
-
if (!check.allowed) throw new
|
|
355
|
+
if (!check.allowed) throw new RuntimeWriteFenceError(action, check.reason ?? 'denied');
|
|
308
356
|
return check.authority;
|
|
309
357
|
}
|
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
2
|
|
|
3
3
|
export type StoredGoogleService = 'gmail' | 'calendar' | 'tasks' | 'google-workspace';
|
|
4
|
+
export type StoredGoogleCredentialKind = 'refresh-token' | 'client-secret';
|
|
4
5
|
|
|
5
6
|
export interface GoogleCredentialStoreAdapter {
|
|
6
7
|
available(): boolean;
|
|
7
|
-
read(service: StoredGoogleService): string | undefined;
|
|
8
|
-
write(service: StoredGoogleService,
|
|
8
|
+
read(service: StoredGoogleService, kind?: StoredGoogleCredentialKind): string | undefined;
|
|
9
|
+
write(service: StoredGoogleService, value: string, kind?: StoredGoogleCredentialKind): void;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const KEYCHAIN_SERVICE_PREFIX = 'forge.google-oauth';
|
|
12
|
-
const KEYCHAIN_ACCOUNT =
|
|
13
|
-
|
|
13
|
+
const KEYCHAIN_ACCOUNT: Record<StoredGoogleCredentialKind, string> = {
|
|
14
|
+
'refresh-token': 'refresh-token',
|
|
15
|
+
'client-secret': 'client-secret',
|
|
16
|
+
};
|
|
17
|
+
const memoryCache = new Map<string, string>();
|
|
14
18
|
|
|
15
19
|
function keychainService(service: StoredGoogleService): string {
|
|
16
20
|
return `${KEYCHAIN_SERVICE_PREFIX}.${service}`;
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
function cacheKey(service: StoredGoogleService, kind: StoredGoogleCredentialKind): string {
|
|
24
|
+
return `${service}:${kind}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
const macKeychainAdapter: GoogleCredentialStoreAdapter = {
|
|
20
28
|
available: () => process.platform === 'darwin',
|
|
21
|
-
read(service) {
|
|
29
|
+
read(service, kind = 'refresh-token') {
|
|
22
30
|
if (process.platform !== 'darwin') return undefined;
|
|
23
31
|
try {
|
|
24
32
|
const value = execFileSync('/usr/bin/security', [
|
|
25
33
|
'find-generic-password',
|
|
26
34
|
'-s', keychainService(service),
|
|
27
|
-
'-a', KEYCHAIN_ACCOUNT,
|
|
35
|
+
'-a', KEYCHAIN_ACCOUNT[kind],
|
|
28
36
|
'-w',
|
|
29
37
|
], {
|
|
30
38
|
encoding: 'utf-8',
|
|
@@ -36,7 +44,7 @@ const macKeychainAdapter: GoogleCredentialStoreAdapter = {
|
|
|
36
44
|
return undefined;
|
|
37
45
|
}
|
|
38
46
|
},
|
|
39
|
-
write(service,
|
|
47
|
+
write(service, value, kind = 'refresh-token') {
|
|
40
48
|
if (process.platform !== 'darwin') {
|
|
41
49
|
throw new Error('GOOGLE_CREDENTIAL_STORE_UNAVAILABLE: macOS Keychain is required for local OAuth credential persistence');
|
|
42
50
|
}
|
|
@@ -44,8 +52,8 @@ const macKeychainAdapter: GoogleCredentialStoreAdapter = {
|
|
|
44
52
|
'add-generic-password',
|
|
45
53
|
'-U',
|
|
46
54
|
'-s', keychainService(service),
|
|
47
|
-
'-a', KEYCHAIN_ACCOUNT,
|
|
48
|
-
'-w',
|
|
55
|
+
'-a', KEYCHAIN_ACCOUNT[kind],
|
|
56
|
+
'-w', value,
|
|
49
57
|
], {
|
|
50
58
|
encoding: 'utf-8',
|
|
51
59
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
@@ -67,29 +75,76 @@ export function googleCredentialStoreStatus(): Record<string, unknown> {
|
|
|
67
75
|
available: adapter.available(),
|
|
68
76
|
repositoryPersistence: false,
|
|
69
77
|
controllerStatePersistence: false,
|
|
78
|
+
credentialKinds: ['refresh-token', 'client-secret'],
|
|
70
79
|
};
|
|
71
80
|
}
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
function readStoredGoogleCredential(
|
|
83
|
+
service: StoredGoogleService,
|
|
84
|
+
kind: StoredGoogleCredentialKind,
|
|
85
|
+
): { value: string; source: string } | undefined {
|
|
74
86
|
const candidates: StoredGoogleService[] = service === 'google-workspace'
|
|
75
87
|
? ['google-workspace']
|
|
76
88
|
: [service, 'google-workspace'];
|
|
77
89
|
for (const candidate of candidates) {
|
|
78
|
-
const
|
|
79
|
-
|
|
90
|
+
const key = cacheKey(candidate, kind);
|
|
91
|
+
const cached = memoryCache.get(key);
|
|
92
|
+
if (cached) return { value: cached, source: `keychain:${candidate}:${kind}` };
|
|
80
93
|
if (!adapter.available()) continue;
|
|
81
|
-
const
|
|
82
|
-
if (!
|
|
83
|
-
memoryCache.set(
|
|
84
|
-
return {
|
|
94
|
+
const value = adapter.read(candidate, kind)?.trim();
|
|
95
|
+
if (!value) continue;
|
|
96
|
+
memoryCache.set(key, value);
|
|
97
|
+
return { value, source: `keychain:${candidate}:${kind}` };
|
|
85
98
|
}
|
|
86
99
|
return undefined;
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
102
|
+
function writeStoredGoogleCredential(service: StoredGoogleService, kind: StoredGoogleCredentialKind, value: string): void {
|
|
103
|
+
const normalized = value.trim();
|
|
104
|
+
if (!normalized) throw new Error(kind === 'refresh-token' ? 'GOOGLE_REFRESH_TOKEN_REQUIRED' : 'GOOGLE_CLIENT_SECRET_REQUIRED');
|
|
92
105
|
if (!adapter.available()) throw new Error('GOOGLE_CREDENTIAL_STORE_UNAVAILABLE');
|
|
93
|
-
adapter.write(service,
|
|
94
|
-
memoryCache.set(service,
|
|
106
|
+
adapter.write(service, normalized, kind);
|
|
107
|
+
memoryCache.set(cacheKey(service, kind), normalized);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function readStoredGoogleRefreshToken(service: StoredGoogleService): { token: string; source: string } | undefined {
|
|
111
|
+
const stored = readStoredGoogleCredential(service, 'refresh-token');
|
|
112
|
+
return stored ? { token: stored.value, source: stored.source } : undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function writeStoredGoogleRefreshToken(service: StoredGoogleService, refreshToken: string): void {
|
|
116
|
+
writeStoredGoogleCredential(service, 'refresh-token', refreshToken);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function readStoredGoogleClientSecret(service: StoredGoogleService): { secret: string; source: string } | undefined {
|
|
120
|
+
const stored = readStoredGoogleCredential(service, 'client-secret');
|
|
121
|
+
return stored ? { secret: stored.value, source: stored.source } : undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function writeStoredGoogleClientSecret(service: StoredGoogleService, clientSecret: string): void {
|
|
125
|
+
writeStoredGoogleCredential(service, 'client-secret', clientSecret);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function importStoredGoogleClientSecretFromClipboard(service: StoredGoogleService): Record<string, unknown> {
|
|
129
|
+
if (process.platform !== 'darwin' || !adapter.available()) throw new Error('GOOGLE_CREDENTIAL_STORE_UNAVAILABLE');
|
|
130
|
+
const secret = execFileSync('/usr/bin/pbpaste', [], {
|
|
131
|
+
encoding: 'utf-8',
|
|
132
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
133
|
+
timeout: 2_000,
|
|
134
|
+
}).trim();
|
|
135
|
+
if (!secret || secret.length > 1024 || /\s/.test(secret)) throw new Error('GOOGLE_CLIENT_SECRET_CLIPBOARD_INVALID');
|
|
136
|
+
writeStoredGoogleClientSecret(service, secret);
|
|
137
|
+
execFileSync('/usr/bin/pbcopy', [], {
|
|
138
|
+
encoding: 'utf-8',
|
|
139
|
+
input: '',
|
|
140
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
141
|
+
timeout: 2_000,
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
stored: true,
|
|
145
|
+
service,
|
|
146
|
+
backend: 'macos-keychain',
|
|
147
|
+
clipboardCleared: true,
|
|
148
|
+
credentialMaterialReturned: false,
|
|
149
|
+
};
|
|
95
150
|
}
|
|
@@ -5,6 +5,7 @@ import { URLSearchParams } from 'url';
|
|
|
5
5
|
import { installGoogleAccessToken } from '../plugins/google-shared';
|
|
6
6
|
import {
|
|
7
7
|
googleCredentialStoreStatus,
|
|
8
|
+
readStoredGoogleClientSecret,
|
|
8
9
|
type StoredGoogleService,
|
|
9
10
|
writeStoredGoogleRefreshToken,
|
|
10
11
|
} from './google-credential-store';
|
|
@@ -138,16 +139,33 @@ function base64Url(value: Buffer): string {
|
|
|
138
139
|
return value.toString('base64url');
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
function clientId(): string | undefined {
|
|
142
|
-
|
|
142
|
+
function clientId(service: GoogleOAuthService): string | undefined {
|
|
143
|
+
const serviceSpecific = service === 'gmail'
|
|
144
|
+
? process.env.FORGE_GMAIL_CLIENT_ID?.trim()
|
|
145
|
+
: service === 'calendar'
|
|
146
|
+
? process.env.FORGE_GOOGLE_CALENDAR_CLIENT_ID?.trim()
|
|
147
|
+
: service === 'tasks'
|
|
148
|
+
? process.env.FORGE_GOOGLE_TASKS_CLIENT_ID?.trim()
|
|
149
|
+
: undefined;
|
|
150
|
+
return serviceSpecific
|
|
151
|
+
|| process.env.FORGE_GOOGLE_WORKSPACE_CLIENT_ID?.trim()
|
|
143
152
|
|| process.env.FORGE_GOOGLE_CLIENT_ID?.trim()
|
|
144
153
|
|| process.env.GOOGLE_CLIENT_ID?.trim();
|
|
145
154
|
}
|
|
146
155
|
|
|
147
|
-
function clientSecret(): string | undefined {
|
|
148
|
-
|
|
156
|
+
function clientSecret(service: GoogleOAuthService): string | undefined {
|
|
157
|
+
const serviceSpecific = service === 'gmail'
|
|
158
|
+
? process.env.FORGE_GMAIL_CLIENT_SECRET?.trim()
|
|
159
|
+
: service === 'calendar'
|
|
160
|
+
? process.env.FORGE_GOOGLE_CALENDAR_CLIENT_SECRET?.trim()
|
|
161
|
+
: service === 'tasks'
|
|
162
|
+
? process.env.FORGE_GOOGLE_TASKS_CLIENT_SECRET?.trim()
|
|
163
|
+
: undefined;
|
|
164
|
+
return serviceSpecific
|
|
165
|
+
|| process.env.FORGE_GOOGLE_WORKSPACE_CLIENT_SECRET?.trim()
|
|
149
166
|
|| process.env.FORGE_GOOGLE_CLIENT_SECRET?.trim()
|
|
150
|
-
|| process.env.GOOGLE_CLIENT_SECRET?.trim()
|
|
167
|
+
|| process.env.GOOGLE_CLIENT_SECRET?.trim()
|
|
168
|
+
|| readStoredGoogleClientSecret(service)?.secret;
|
|
151
169
|
}
|
|
152
170
|
|
|
153
171
|
export function prepareGoogleOAuthLogin(
|
|
@@ -157,7 +175,7 @@ export function prepareGoogleOAuthLogin(
|
|
|
157
175
|
pruneOAuthRequests(controllerHome);
|
|
158
176
|
const redirectUri = validateRedirectUri(input.redirectUri);
|
|
159
177
|
const scopes = validateScopes(input.service, input.scopes);
|
|
160
|
-
const configuredClientId = clientId();
|
|
178
|
+
const configuredClientId = clientId(input.service);
|
|
161
179
|
if (!configuredClientId) {
|
|
162
180
|
return {
|
|
163
181
|
schemaVersion: 1,
|
|
@@ -236,7 +254,7 @@ export async function completeGoogleOAuthLogin(
|
|
|
236
254
|
}
|
|
237
255
|
const code = input.code?.trim();
|
|
238
256
|
if (!code) throw new Error('GOOGLE_OAUTH_CODE_REQUIRED');
|
|
239
|
-
const configuredSecret = clientSecret();
|
|
257
|
+
const configuredSecret = clientSecret(selected.record.service);
|
|
240
258
|
if (!configuredSecret) throw new Error('GOOGLE_OAUTH_CLIENT_SECRET_REQUIRED');
|
|
241
259
|
const body = new URLSearchParams({
|
|
242
260
|
grant_type: 'authorization_code',
|
|
@@ -88,10 +88,10 @@ export function prepareWorkspaceAuthLogin(
|
|
|
88
88
|
controllerHomeOrInput: string | WorkspaceAuthLoginInput = {},
|
|
89
89
|
maybeInput: WorkspaceAuthLoginInput = {},
|
|
90
90
|
): Record<string, unknown> {
|
|
91
|
-
bootstrapManagedRuntimeEnv();
|
|
92
91
|
const controllerHome = typeof controllerHomeOrInput === 'string'
|
|
93
92
|
? controllerHomeOrInput
|
|
94
93
|
: process.env.FORGE_CONTROLLER_HOME?.trim() || process.cwd();
|
|
94
|
+
bootstrapManagedRuntimeEnv({ controllerHome });
|
|
95
95
|
const input = typeof controllerHomeOrInput === 'string' ? maybeInput : controllerHomeOrInput;
|
|
96
96
|
const service = normalizeService(input.service);
|
|
97
97
|
const redirectUri = input.redirectUri || process.env.FORGE_GOOGLE_REDIRECT_URI || 'http://127.0.0.1:8766/oauth/google/callback';
|
|
@@ -39,6 +39,8 @@ export interface BoundedChildRunOptions {
|
|
|
39
39
|
env?: NodeJS.ProcessEnv;
|
|
40
40
|
timeoutMs: number;
|
|
41
41
|
maxOutputBytes?: number;
|
|
42
|
+
/** Optional bounded stdin payload for request/response sidecars. */
|
|
43
|
+
input?: string | Buffer;
|
|
42
44
|
stdio?: 'capture' | 'inherit';
|
|
43
45
|
forwardSignals?: boolean;
|
|
44
46
|
onSpawn?: (pid: number) => void;
|
|
@@ -120,7 +122,7 @@ export async function runBoundedChild(
|
|
|
120
122
|
const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
121
123
|
const stdio: StdioOptions = options.stdio === 'inherit'
|
|
122
124
|
? ['ignore', 'inherit', 'inherit']
|
|
123
|
-
: ['ignore', 'pipe', 'pipe'];
|
|
125
|
+
: [options.input === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'];
|
|
124
126
|
let child: ChildProcess;
|
|
125
127
|
try {
|
|
126
128
|
child = spawn(command, [...args], {
|
|
@@ -147,6 +149,12 @@ export async function runBoundedChild(
|
|
|
147
149
|
|
|
148
150
|
const pid = child.pid;
|
|
149
151
|
if (pid) options.onSpawn?.(pid);
|
|
152
|
+
if (options.input !== undefined && child.stdin) {
|
|
153
|
+
// A canary or validation failure may close stdin before the parent writes.
|
|
154
|
+
// The child outcome is authoritative; a late EPIPE must not crash the host.
|
|
155
|
+
child.stdin.on('error', () => undefined);
|
|
156
|
+
child.stdin.end(options.input);
|
|
157
|
+
}
|
|
150
158
|
let stdout = '';
|
|
151
159
|
let stderr = '';
|
|
152
160
|
child.stdout?.on('data', (chunk) => { stdout = appendBounded(stdout, chunk, maxOutputBytes); });
|