@moretea-labs/forge 1.5.1 → 1.7.0
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 +2 -0
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md +2 -0
- package/DESIGN.md +7 -2
- package/README.md +4 -4
- package/README.zh-CN.md +2 -2
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/pre-edit-guard.sh +8 -39
- package/assets/hooks/prompt-guard.sh +7 -83
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +54 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/sprint-backlog.sh +6 -6
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +170 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +38 -22
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +3 -3
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Installation.md +1 -1
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +29 -10
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capture-plan.sh +3 -3
- package/scripts/check-agent-tooling.sh +8 -35
- package/scripts/check-architecture-sync.sh +17 -86
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-public-docs.sh +14 -1
- package/scripts/check-runtime-architecture.mjs +107 -64
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-workflow.sh +4 -4
- package/scripts/contract-worktree.sh +1 -21
- package/scripts/ensure-task-workflow.sh +22 -55
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +15 -44
- package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
- package/scripts/migrate-project-template.sh +23 -1179
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +11 -36
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/ship-worktrees.sh +2 -29
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/sprint-backlog.sh +1 -1
- package/scripts/verify-contract.sh +1 -111
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +3 -180
- package/scripts/workflow-contract.ts +1 -5
- package/src/cli/commands/chatgpt.ts +31 -2
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +37 -10
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +106 -3
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +898 -430
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/github.ts +133 -0
- package/src/cli/hook/prompt-guard-decision.ts +17 -18
- package/src/cli/hook/prompt-intents.ts +1 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +225 -67
- package/src/cli/local-bridge/console-view-models.ts +3 -35
- package/src/cli/local-bridge/facade-api.ts +2 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/styles.css +27 -11
- package/src/cli/local-bridge/frontend/src/types.ts +8 -7
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +29 -532
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/paths.ts +8 -2
- package/src/cli/mcp/repository-tools.ts +356 -101
- package/src/cli/mcp/server.ts +625 -54
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +42 -5
- package/src/cli/mcp/transports/http.ts +125 -15
- package/src/cli/mcp/transports/session-registry.ts +67 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +209 -14
- package/src/cli/repositories/command-executor.ts +55 -521
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +121 -0
- package/src/cli/repositories/safe-patch.ts +6 -4
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +110 -10
- package/src/cli/repository/inspector.ts +565 -62
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation.ts +275 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/validation.ts +15 -2
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
- package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
- package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
- package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +55 -11
- package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/routing/route-policy.ts +7 -16
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +19 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
- package/src/runtime/execution/process-runtime/check-result.ts +63 -2
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
- package/src/runtime/execution/process-runtime/gc.ts +98 -12
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
- package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
- package/src/runtime/execution/process-runtime/runtime.ts +59 -4
- package/src/runtime/execution/process-runtime/store.ts +7 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -0
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/workers/executor.ts +0 -48
- package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +185 -83
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
- package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2925 -318
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +6 -31
- package/src/runtime/plugins/browser-handoff.ts +92 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
- package/src/runtime/plugins/browser-runtime.ts +183 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-registration.ts +84 -5
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +4 -2
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/gmail-adapter.ts +28 -2
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +125 -8
- package/src/runtime/plugins/macos-capability-broker.ts +55 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +366 -24
- package/src/runtime/plugins/types.ts +39 -2
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/actions.ts +0 -10
- package/src/runtime/recovery/classifier.ts +0 -9
- package/src/runtime/recovery/maintenance-executor.ts +415 -18
- package/src/runtime/recovery/probe.ts +0 -1
- package/src/runtime/recovery/types.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +186 -8
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +430 -22
- package/src/runtime/root/release-manifest.ts +7 -2
- package/src/runtime/root/release-materialize.ts +162 -19
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -27
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/index.ts +0 -1
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/safe-tooling/types.ts +0 -30
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +702 -94
- package/src/runtime/standalone-recovery/entry.ts +99 -23
- package/src/runtime/standalone-recovery/installer.ts +19 -3
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
- package/src/runtime/workflow/schedules/engine.ts +509 -80
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +48 -4
- package/src/runtime/workflow/schedules/store.ts +131 -10
- package/src/runtime/workflow/schedules/types.ts +6 -3
- package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/bin/forge-desktop-helper.mjs +0 -204
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
- package/docs/architecture/current/codegraph-context-provider.md +0 -226
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
- package/docs/architecture/current/entity-model.md +0 -592
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -144
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -311
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
- package/docs/operations/controller-desktop-plugin.md +0 -112
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/plugins/desktop-adapter.ts +0 -374
- package/src/runtime/safe-tooling/browser-safe.ts +0 -133
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -14,6 +14,15 @@ import { CONTROL_PLANE_SCHEMA_VERSION } from '../control-plane/persistence/sqlit
|
|
|
14
14
|
* `forge runtime service install` operation; staging alone never starts or
|
|
15
15
|
* publishes anything.
|
|
16
16
|
*/
|
|
17
|
+
export const FORGE_MACOS_RUNTIME_SIGNING_IDENTIFIER = 'com.moretea.forge.runtime';
|
|
18
|
+
|
|
19
|
+
export interface MacOSRuntimeCodeSigning {
|
|
20
|
+
identifier: string;
|
|
21
|
+
teamIdentifier: string;
|
|
22
|
+
designatedRequirement: string;
|
|
23
|
+
authority: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
export interface StagedRuntimeRelease {
|
|
18
27
|
releasePath: string;
|
|
19
28
|
manifestPath: string;
|
|
@@ -22,20 +31,23 @@ export interface StagedRuntimeRelease {
|
|
|
22
31
|
diagnosticArtifactIdentity?: string;
|
|
23
32
|
browserNodeBridgeArtifactIdentity?: string;
|
|
24
33
|
browserHandoffArtifactIdentity?: string;
|
|
25
|
-
desktopHelperArtifactIdentity?: string;
|
|
26
34
|
processRunnerArtifactIdentity?: string;
|
|
27
35
|
checkRunnerArtifactIdentity?: string;
|
|
36
|
+
pluginActionSidecarArtifactIdentity?: string;
|
|
28
37
|
externalPluginProbeArtifactIdentity?: string;
|
|
29
38
|
codeGraphNodeArtifactIdentity?: string;
|
|
30
39
|
codeGraphSidecarArtifactIdentity?: string;
|
|
31
40
|
codeGraphLibraryArtifactIdentity?: string;
|
|
32
41
|
controllerUiArtifactIdentity?: string;
|
|
42
|
+
macosCodeSigning?: MacOSRuntimeCodeSigning;
|
|
33
43
|
manifestSha256: string;
|
|
34
44
|
sourceCommit: string;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
export interface RuntimeReleaseMaterializerDependencies {
|
|
38
48
|
now?: () => number;
|
|
49
|
+
platform?: NodeJS.Platform;
|
|
50
|
+
signMacOSRuntime?: (input: { executable: string; controllerHome: string }) => MacOSRuntimeCodeSigning;
|
|
39
51
|
uuid?: () => string;
|
|
40
52
|
compileBinary?: (input: { sourceRoot: string; outputPath: string; entryPath?: string }) => { ok: boolean; stderr?: string; stdout?: string; error?: string };
|
|
41
53
|
bundleNodeHost?: (input: { sourceRoot: string; outputPath: string; entryPath: string }) => { ok: boolean; stderr?: string; stdout?: string; error?: string };
|
|
@@ -59,6 +71,8 @@ export interface CandidateRuntimeStageReceiptV1 {
|
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
export interface CandidateRuntimeReleaseStagerDependencies {
|
|
74
|
+
platform?: NodeJS.Platform;
|
|
75
|
+
inspectMacOSRuntime?: (executable: string) => MacOSRuntimeCodeSigning;
|
|
62
76
|
runCandidateStager?: (input: {
|
|
63
77
|
bunExecutable: string;
|
|
64
78
|
scriptPath: string;
|
|
@@ -78,6 +92,95 @@ function sha256(path: string): string {
|
|
|
78
92
|
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
79
93
|
}
|
|
80
94
|
|
|
95
|
+
function parseMacOSRuntimeCodeSigning(value: unknown, context: string): MacOSRuntimeCodeSigning | undefined {
|
|
96
|
+
if (value === undefined) return undefined;
|
|
97
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${context}: macosCodeSigning must be an object`);
|
|
98
|
+
const record = value as Record<string, unknown>;
|
|
99
|
+
const field = (name: keyof MacOSRuntimeCodeSigning): string => {
|
|
100
|
+
const entry = record[name];
|
|
101
|
+
if (typeof entry !== 'string' || !entry.trim()) throw new Error(`${context}: macosCodeSigning.${name} is required`);
|
|
102
|
+
return entry.trim();
|
|
103
|
+
};
|
|
104
|
+
const signing = {
|
|
105
|
+
identifier: field('identifier'),
|
|
106
|
+
teamIdentifier: field('teamIdentifier'),
|
|
107
|
+
designatedRequirement: field('designatedRequirement'),
|
|
108
|
+
authority: field('authority'),
|
|
109
|
+
};
|
|
110
|
+
if (signing.identifier !== FORGE_MACOS_RUNTIME_SIGNING_IDENTIFIER) {
|
|
111
|
+
throw new Error(`${context}: macosCodeSigning.identifier must be ${FORGE_MACOS_RUNTIME_SIGNING_IDENTIFIER}`);
|
|
112
|
+
}
|
|
113
|
+
if (!/^[A-Z0-9]{10}$/.test(signing.teamIdentifier)) throw new Error(`${context}: macosCodeSigning.teamIdentifier is invalid`);
|
|
114
|
+
if (!signing.authority.startsWith('Developer ID Application: ')) throw new Error(`${context}: macosCodeSigning.authority must be Developer ID Application`);
|
|
115
|
+
if (!signing.designatedRequirement.includes(`identifier \"${FORGE_MACOS_RUNTIME_SIGNING_IDENTIFIER}\"`)
|
|
116
|
+
|| !signing.designatedRequirement.includes('anchor apple generic')
|
|
117
|
+
|| !signing.designatedRequirement.includes(`certificate leaf[subject.OU] = ${signing.teamIdentifier}`)) {
|
|
118
|
+
throw new Error(`${context}: macosCodeSigning.designatedRequirement is not the stable Developer ID contract`);
|
|
119
|
+
}
|
|
120
|
+
return signing;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readActiveMacOSRuntimeTeam(controllerHome: string): string | undefined {
|
|
124
|
+
const authorityPath = join(resolve(controllerHome), 'runtime', 'releases', 'authority.json');
|
|
125
|
+
if (!existsSync(authorityPath)) return undefined;
|
|
126
|
+
try {
|
|
127
|
+
const authority = JSON.parse(readFileSync(authorityPath, 'utf8')) as { active?: { manifestPath?: string } };
|
|
128
|
+
if (!authority.active?.manifestPath || !existsSync(authority.active.manifestPath)) return undefined;
|
|
129
|
+
const manifest = JSON.parse(readFileSync(authority.active.manifestPath, 'utf8')) as Record<string, unknown>;
|
|
130
|
+
return parseMacOSRuntimeCodeSigning(manifest.macosCodeSigning, 'RUNTIME_RELEASE_ACTIVE_SIGNING_INVALID')?.teamIdentifier;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error instanceof Error && error.message.startsWith('RUNTIME_RELEASE_ACTIVE_SIGNING_INVALID')) throw error;
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function inspectMacOSRuntimeCodeSigning(executable: string): MacOSRuntimeCodeSigning {
|
|
138
|
+
const verify = runProcess('codesign', ['--verify', '--strict', executable], { timeoutMs: 30_000, maxOutputBytes: 64 * 1024 });
|
|
139
|
+
if (!verify.ok) throw new Error(`RUNTIME_RELEASE_MACOS_SIGNATURE_INVALID: ${verify.stderr || verify.stdout || verify.error}`.slice(0, 2_000));
|
|
140
|
+
const detail = runProcess('codesign', ['-dv', '--verbose=4', executable], { timeoutMs: 30_000, maxOutputBytes: 64 * 1024 });
|
|
141
|
+
if (!detail.ok) throw new Error(`RUNTIME_RELEASE_MACOS_SIGNATURE_INSPECTION_FAILED: ${detail.stderr || detail.stdout || detail.error}`.slice(0, 2_000));
|
|
142
|
+
const detailText = `${detail.stdout}\n${detail.stderr}`;
|
|
143
|
+
const identifier = detailText.match(/^Identifier=(.+)$/m)?.[1]?.trim() ?? '';
|
|
144
|
+
const teamIdentifier = detailText.match(/^TeamIdentifier=(.+)$/m)?.[1]?.trim() ?? '';
|
|
145
|
+
const authority = detailText.match(/^Authority=(Developer ID Application:.+)$/m)?.[1]?.trim() ?? '';
|
|
146
|
+
const requirementResult = runProcess('codesign', ['-d', '-r-', executable], { timeoutMs: 30_000, maxOutputBytes: 64 * 1024 });
|
|
147
|
+
if (!requirementResult.ok) throw new Error(`RUNTIME_RELEASE_MACOS_REQUIREMENT_INSPECTION_FAILED: ${requirementResult.stderr || requirementResult.stdout || requirementResult.error}`.slice(0, 2_000));
|
|
148
|
+
const requirementText = `${requirementResult.stdout}\n${requirementResult.stderr}`;
|
|
149
|
+
const designatedRequirement = requirementText.match(/^designated => (.+)$/m)?.[1]?.trim() ?? '';
|
|
150
|
+
return parseMacOSRuntimeCodeSigning({ identifier, teamIdentifier, authority, designatedRequirement }, 'RUNTIME_RELEASE_MACOS_SIGNATURE_INVALID')!;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function defaultSignMacOSRuntime(input: { executable: string; controllerHome: string }): MacOSRuntimeCodeSigning {
|
|
154
|
+
const identities = runProcess('security', ['find-identity', '-v', '-p', 'codesigning'], { timeoutMs: 30_000, maxOutputBytes: 128 * 1024 });
|
|
155
|
+
if (!identities.ok) throw new Error(`RUNTIME_RELEASE_MACOS_SIGNING_IDENTITY_LOOKUP_FAILED: ${identities.stderr || identities.stdout || identities.error}`.slice(0, 2_000));
|
|
156
|
+
const candidates = identities.stdout.split(/\r?\n/).flatMap((line) => {
|
|
157
|
+
const match = line.match(/^\s*\d+\)\s+([A-Fa-f0-9]{40})\s+\"(Developer ID Application: .+ \(([A-Z0-9]{10})\))\"/);
|
|
158
|
+
return match ? [{ hash: match[1]!, label: match[2]!, teamIdentifier: match[3]! }] : [];
|
|
159
|
+
});
|
|
160
|
+
const configured = process.env.FORGE_MACOS_RUNTIME_SIGNING_IDENTITY?.trim();
|
|
161
|
+
const expectedTeam = readActiveMacOSRuntimeTeam(input.controllerHome);
|
|
162
|
+
let selected = configured
|
|
163
|
+
? candidates.find((candidate) => candidate.hash.toLowerCase() === configured.toLowerCase() || candidate.label === configured)
|
|
164
|
+
: undefined;
|
|
165
|
+
if (configured && !selected) throw new Error('RUNTIME_RELEASE_MACOS_SIGNING_IDENTITY_NOT_FOUND');
|
|
166
|
+
if (!selected) {
|
|
167
|
+
const eligible = expectedTeam ? candidates.filter((candidate) => candidate.teamIdentifier === expectedTeam) : candidates;
|
|
168
|
+
const teams = Array.from(new Set(eligible.map((candidate) => candidate.teamIdentifier)));
|
|
169
|
+
if (teams.length !== 1 || eligible.length === 0) {
|
|
170
|
+
throw new Error(expectedTeam ? 'RUNTIME_RELEASE_MACOS_SIGNING_IDENTITY_FOR_TEAM_REQUIRED' : 'RUNTIME_RELEASE_MACOS_SIGNING_IDENTITY_REQUIRED');
|
|
171
|
+
}
|
|
172
|
+
selected = eligible[0];
|
|
173
|
+
}
|
|
174
|
+
if (expectedTeam && selected.teamIdentifier !== expectedTeam) throw new Error('RUNTIME_RELEASE_MACOS_SIGNING_TEAM_CHANGED');
|
|
175
|
+
const signed = runProcess('codesign', [
|
|
176
|
+
'--force', '--sign', selected.hash, '--identifier', FORGE_MACOS_RUNTIME_SIGNING_IDENTIFIER, '--options', 'runtime', input.executable,
|
|
177
|
+
], { timeoutMs: 120_000, maxOutputBytes: 128 * 1024 });
|
|
178
|
+
if (!signed.ok) throw new Error(`RUNTIME_RELEASE_MACOS_SIGNING_FAILED: ${signed.stderr || signed.stdout || signed.error}`.slice(0, 2_000));
|
|
179
|
+
const inspected = inspectMacOSRuntimeCodeSigning(input.executable);
|
|
180
|
+
if (inspected.teamIdentifier !== selected.teamIdentifier) throw new Error('RUNTIME_RELEASE_MACOS_SIGNING_TEAM_MISMATCH');
|
|
181
|
+
return inspected;
|
|
182
|
+
}
|
|
183
|
+
|
|
81
184
|
function sha256Directory(root: string): string {
|
|
82
185
|
const hash = createHash('sha256');
|
|
83
186
|
const visit = (directory: string): void => {
|
|
@@ -203,12 +306,28 @@ export function stageRuntimeReleaseFromCandidateSource(input: {
|
|
|
203
306
|
if (sha256(manifestPath) !== receipt.manifestSha256) {
|
|
204
307
|
throw new Error('RUNTIME_RELEASE_CANDIDATE_MANIFEST_IDENTITY_MISMATCH');
|
|
205
308
|
}
|
|
309
|
+
let rawManifest: Record<string, unknown>;
|
|
310
|
+
try { rawManifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as Record<string, unknown>; }
|
|
311
|
+
catch (error) { throw new Error(`RUNTIME_RELEASE_CANDIDATE_MANIFEST_INVALID: ${error instanceof Error ? error.message : String(error)}`); }
|
|
312
|
+
const platform = dependencies.platform ?? process.platform;
|
|
313
|
+
const macosCodeSigning = parseMacOSRuntimeCodeSigning(rawManifest.macosCodeSigning, 'RUNTIME_RELEASE_CANDIDATE_SIGNING_INVALID');
|
|
314
|
+
if (platform === 'darwin') {
|
|
315
|
+
if (!macosCodeSigning) throw new Error('RUNTIME_RELEASE_CANDIDATE_MACOS_SIGNING_REQUIRED');
|
|
316
|
+
const actual = (dependencies.inspectMacOSRuntime ?? inspectMacOSRuntimeCodeSigning)(runtimePath);
|
|
317
|
+
if (actual.identifier !== macosCodeSigning.identifier
|
|
318
|
+
|| actual.teamIdentifier !== macosCodeSigning.teamIdentifier
|
|
319
|
+
|| actual.authority !== macosCodeSigning.authority
|
|
320
|
+
|| actual.designatedRequirement !== macosCodeSigning.designatedRequirement) {
|
|
321
|
+
throw new Error('RUNTIME_RELEASE_CANDIDATE_MACOS_SIGNING_MISMATCH');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
206
324
|
|
|
207
325
|
return {
|
|
208
326
|
releasePath,
|
|
209
327
|
manifestPath,
|
|
210
328
|
releaseId: receipt.releaseId,
|
|
211
329
|
artifactIdentity: receipt.artifactIdentity,
|
|
330
|
+
...(macosCodeSigning ? { macosCodeSigning } : {}),
|
|
212
331
|
manifestSha256: receipt.manifestSha256,
|
|
213
332
|
sourceCommit: receipt.sourceCommit,
|
|
214
333
|
};
|
|
@@ -297,6 +416,14 @@ export function stageRuntimeRelease(input: {
|
|
|
297
416
|
throw new Error(`RUNTIME_RELEASE_BUILD_FAILED: ${compile.stderr || compile.stdout || compile.error}`.slice(0, 2_000));
|
|
298
417
|
}
|
|
299
418
|
chmodSync(executable, 0o700);
|
|
419
|
+
const platform = dependencies.platform ?? process.platform;
|
|
420
|
+
const macosCodeSigning = platform === 'darwin'
|
|
421
|
+
? (dependencies.signMacOSRuntime ?? defaultSignMacOSRuntime)({ executable, controllerHome: resolve(input.controllerHome) })
|
|
422
|
+
: undefined;
|
|
423
|
+
if (platform === 'darwin' && !macosCodeSigning) throw new Error('RUNTIME_RELEASE_MACOS_SIGNING_REQUIRED');
|
|
424
|
+
const normalizedMacOSCodeSigning = macosCodeSigning
|
|
425
|
+
? parseMacOSRuntimeCodeSigning(macosCodeSigning, 'RUNTIME_RELEASE_MACOS_SIGNING_INVALID')
|
|
426
|
+
: undefined;
|
|
300
427
|
const artifactIdentity = `sha256:${sha256(executable)}`;
|
|
301
428
|
|
|
302
429
|
const diagnosticExecutable = join(staging, 'forge-cli');
|
|
@@ -365,6 +492,19 @@ export function stageRuntimeRelease(input: {
|
|
|
365
492
|
chmodSync(checkRunnerPath, 0o700);
|
|
366
493
|
const checkRunnerArtifactIdentity = `sha256:${sha256(checkRunnerPath)}`;
|
|
367
494
|
|
|
495
|
+
const pluginActionSidecarEntrypoint = 'forge-plugin-action-sidecar' as const;
|
|
496
|
+
const pluginActionSidecarPath = join(staging, pluginActionSidecarEntrypoint);
|
|
497
|
+
const pluginActionSidecarCompile = compileBinary({
|
|
498
|
+
sourceRoot,
|
|
499
|
+
outputPath: pluginActionSidecarPath,
|
|
500
|
+
entryPath: join(sourceRoot, 'src/runtime/plugins/plugin-action-sidecar.ts'),
|
|
501
|
+
});
|
|
502
|
+
if (!pluginActionSidecarCompile.ok) {
|
|
503
|
+
throw new Error(`RUNTIME_RELEASE_PLUGIN_ACTION_SIDECAR_BUILD_FAILED: ${pluginActionSidecarCompile.stderr || pluginActionSidecarCompile.stdout || pluginActionSidecarCompile.error}`.slice(0, 2_000));
|
|
504
|
+
}
|
|
505
|
+
chmodSync(pluginActionSidecarPath, 0o700);
|
|
506
|
+
const pluginActionSidecarArtifactIdentity = `sha256:${sha256(pluginActionSidecarPath)}`;
|
|
507
|
+
|
|
368
508
|
const externalPluginProbeEntrypoint = 'external-unix-socket-probe.cjs' as const;
|
|
369
509
|
const sourceExternalPluginProbePath = join(sourceRoot, 'src', 'runtime', 'plugins', externalPluginProbeEntrypoint);
|
|
370
510
|
if (!existsSync(sourceExternalPluginProbePath)) {
|
|
@@ -407,32 +547,24 @@ export function stageRuntimeRelease(input: {
|
|
|
407
547
|
cpSync(sourceControllerUiPath, controllerUiPath, { recursive: true, force: false });
|
|
408
548
|
const controllerUiArtifactIdentity = `sha256:${sha256Directory(controllerUiPath)}`;
|
|
409
549
|
|
|
410
|
-
const desktopHelperEntrypoint = 'forge-desktop-helper.mjs' as const;
|
|
411
|
-
const sourceDesktopHelperPath = join(sourceRoot, 'bin', desktopHelperEntrypoint);
|
|
412
|
-
if (!existsSync(sourceDesktopHelperPath)) {
|
|
413
|
-
throw new Error(`RUNTIME_RELEASE_DESKTOP_HELPER_SOURCE_MISSING: ${sourceDesktopHelperPath}`);
|
|
414
|
-
}
|
|
415
|
-
const desktopHelperPath = join(staging, desktopHelperEntrypoint);
|
|
416
|
-
copyFileSync(sourceDesktopHelperPath, desktopHelperPath);
|
|
417
|
-
chmodSync(desktopHelperPath, 0o700);
|
|
418
|
-
const desktopHelperArtifactIdentity = `sha256:${sha256(desktopHelperPath)}`;
|
|
419
550
|
const manifest = {
|
|
420
551
|
schemaVersion: 1,
|
|
421
552
|
releaseId,
|
|
422
553
|
artifactIdentity,
|
|
423
554
|
entrypoint: 'forge-runtime',
|
|
555
|
+
...(normalizedMacOSCodeSigning ? { macosCodeSigning: normalizedMacOSCodeSigning } : {}),
|
|
424
556
|
diagnosticEntrypoint: 'forge-cli',
|
|
425
557
|
diagnosticArtifactIdentity,
|
|
426
558
|
browserNodeBridgeEntrypoint,
|
|
427
559
|
browserNodeBridgeArtifactIdentity,
|
|
428
560
|
browserHandoffEntrypoint,
|
|
429
561
|
browserHandoffArtifactIdentity,
|
|
430
|
-
desktopHelperEntrypoint,
|
|
431
|
-
desktopHelperArtifactIdentity,
|
|
432
562
|
processRunnerEntrypoint,
|
|
433
563
|
processRunnerArtifactIdentity,
|
|
434
564
|
checkRunnerEntrypoint,
|
|
435
565
|
checkRunnerArtifactIdentity,
|
|
566
|
+
pluginActionSidecarEntrypoint,
|
|
567
|
+
pluginActionSidecarArtifactIdentity,
|
|
436
568
|
externalPluginProbeEntrypoint,
|
|
437
569
|
externalPluginProbeArtifactIdentity,
|
|
438
570
|
codeGraphNodeEntrypoint,
|
|
@@ -464,10 +596,10 @@ export function stageRuntimeRelease(input: {
|
|
|
464
596
|
manifestPath: join(releasePath, 'manifest.json'),
|
|
465
597
|
releaseId,
|
|
466
598
|
artifactIdentity,
|
|
599
|
+
...(normalizedMacOSCodeSigning ? { macosCodeSigning: normalizedMacOSCodeSigning } : {}),
|
|
467
600
|
diagnosticArtifactIdentity,
|
|
468
601
|
browserNodeBridgeArtifactIdentity,
|
|
469
602
|
browserHandoffArtifactIdentity,
|
|
470
|
-
desktopHelperArtifactIdentity,
|
|
471
603
|
processRunnerArtifactIdentity,
|
|
472
604
|
checkRunnerArtifactIdentity,
|
|
473
605
|
externalPluginProbeArtifactIdentity,
|
|
@@ -484,12 +616,23 @@ export function stageRuntimeRelease(input: {
|
|
|
484
616
|
}
|
|
485
617
|
}
|
|
486
618
|
|
|
487
|
-
export function assertRuntimeReleaseFiles(release: StagedRuntimeRelease): void {
|
|
619
|
+
export function assertRuntimeReleaseFiles(release: StagedRuntimeRelease, dependencies: { platform?: NodeJS.Platform; inspectMacOSRuntime?: (executable: string) => MacOSRuntimeCodeSigning } = {}): void {
|
|
488
620
|
if (!existsSync(release.releasePath) || !existsSync(release.manifestPath)) {
|
|
489
621
|
throw new Error(`RUNTIME_RELEASE_FILES_MISSING: ${release.releasePath}`);
|
|
490
622
|
}
|
|
491
|
-
|
|
492
|
-
|
|
623
|
+
const runtimePath = join(release.releasePath, 'forge-runtime');
|
|
624
|
+
if (!existsSync(runtimePath)) {
|
|
625
|
+
throw new Error(`RUNTIME_RELEASE_ENTRYPOINT_MISSING: ${runtimePath}`);
|
|
626
|
+
}
|
|
627
|
+
const platform = dependencies.platform ?? process.platform;
|
|
628
|
+
if (platform === 'darwin' && release.macosCodeSigning) {
|
|
629
|
+
const actual = (dependencies.inspectMacOSRuntime ?? inspectMacOSRuntimeCodeSigning)(runtimePath);
|
|
630
|
+
if (actual.identifier !== release.macosCodeSigning.identifier
|
|
631
|
+
|| actual.teamIdentifier !== release.macosCodeSigning.teamIdentifier
|
|
632
|
+
|| actual.authority !== release.macosCodeSigning.authority
|
|
633
|
+
|| actual.designatedRequirement !== release.macosCodeSigning.designatedRequirement) {
|
|
634
|
+
throw new Error('RUNTIME_RELEASE_MACOS_SIGNING_MISMATCH');
|
|
635
|
+
}
|
|
493
636
|
}
|
|
494
637
|
if (release.diagnosticArtifactIdentity && !existsSync(join(release.releasePath, 'forge-cli'))) {
|
|
495
638
|
throw new Error(`RUNTIME_RELEASE_DIAGNOSTIC_ENTRYPOINT_MISSING: ${join(release.releasePath, 'forge-cli')}`);
|
|
@@ -500,15 +643,15 @@ export function assertRuntimeReleaseFiles(release: StagedRuntimeRelease): void {
|
|
|
500
643
|
if (release.browserHandoffArtifactIdentity && !existsSync(join(release.releasePath, 'browser-handoff-host.js'))) {
|
|
501
644
|
throw new Error(`RUNTIME_RELEASE_BROWSER_HANDOFF_HOST_MISSING: ${join(release.releasePath, 'browser-handoff-host.js')}`);
|
|
502
645
|
}
|
|
503
|
-
if (release.desktopHelperArtifactIdentity && !existsSync(join(release.releasePath, 'forge-desktop-helper.mjs'))) {
|
|
504
|
-
throw new Error(`RUNTIME_RELEASE_DESKTOP_HELPER_MISSING: ${join(release.releasePath, 'forge-desktop-helper.mjs')}`);
|
|
505
|
-
}
|
|
506
646
|
if (release.processRunnerArtifactIdentity && !existsSync(join(release.releasePath, 'process-runner.js'))) {
|
|
507
647
|
throw new Error(`RUNTIME_RELEASE_PROCESS_RUNNER_MISSING: ${join(release.releasePath, 'process-runner.js')}`);
|
|
508
648
|
}
|
|
509
649
|
if (release.checkRunnerArtifactIdentity && !existsSync(join(release.releasePath, 'forge-check-runner'))) {
|
|
510
650
|
throw new Error(`RUNTIME_RELEASE_CHECK_RUNNER_MISSING: ${join(release.releasePath, 'forge-check-runner')}`);
|
|
511
651
|
}
|
|
652
|
+
if (release.pluginActionSidecarArtifactIdentity && !existsSync(join(release.releasePath, 'forge-plugin-action-sidecar'))) {
|
|
653
|
+
throw new Error(`RUNTIME_RELEASE_PLUGIN_ACTION_SIDECAR_MISSING: ${join(release.releasePath, 'forge-plugin-action-sidecar')}`);
|
|
654
|
+
}
|
|
512
655
|
if (release.externalPluginProbeArtifactIdentity && !existsSync(join(release.releasePath, 'external-unix-socket-probe.cjs'))) {
|
|
513
656
|
throw new Error(`RUNTIME_RELEASE_EXTERNAL_PLUGIN_PROBE_MISSING: ${join(release.releasePath, 'external-unix-socket-probe.cjs')}`);
|
|
514
657
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'fs';
|
|
3
3
|
import { dirname, join, resolve } from 'path';
|
|
4
4
|
import { ensureControllerHome } from '../../cli/repositories/controller-home';
|
|
5
5
|
import {
|
|
@@ -102,9 +102,14 @@ function validRelease(controllerHome: string, release: RuntimePublishedRelease |
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
type RuntimeReleaseAuthorityRead =
|
|
106
|
+
| { state: 'missing' }
|
|
107
|
+
| { state: 'valid'; authority: RuntimeReleaseAuthority }
|
|
108
|
+
| { state: 'invalid'; reason: string };
|
|
109
|
+
|
|
110
|
+
function inspectRuntimeReleaseAuthority(controllerHome: string): RuntimeReleaseAuthorityRead {
|
|
106
111
|
const path = runtimeReleaseAuthorityPath(controllerHome);
|
|
107
|
-
if (!existsSync(path)) return
|
|
112
|
+
if (!existsSync(path)) return { state: 'missing' };
|
|
108
113
|
try {
|
|
109
114
|
const value = JSON.parse(readFileSync(path, 'utf8')) as RuntimeReleaseAuthority;
|
|
110
115
|
if (
|
|
@@ -117,11 +122,24 @@ export function readRuntimeReleaseAuthority(controllerHome: string): RuntimeRele
|
|
|
117
122
|
|| !Number.isFinite(Date.parse(value.committedAt))
|
|
118
123
|
|| !validRelease(controllerHome, value.active)
|
|
119
124
|
|| (value.previous !== undefined && !validRelease(controllerHome, value.previous))
|
|
120
|
-
) return
|
|
121
|
-
return value;
|
|
122
|
-
} catch {
|
|
123
|
-
return
|
|
125
|
+
) return { state: 'invalid', reason: 'authority fields or referenced release evidence are invalid' };
|
|
126
|
+
return { state: 'valid', authority: value };
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return { state: 'invalid', reason: error instanceof Error ? error.message : String(error) };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function mutableRuntimeReleaseAuthority(controllerHome: string): RuntimeReleaseAuthority | undefined {
|
|
133
|
+
const observed = inspectRuntimeReleaseAuthority(controllerHome);
|
|
134
|
+
if (observed.state === 'invalid') {
|
|
135
|
+
throw new Error(`RUNTIME_RELEASE_AUTHORITY_INVALID_EXISTING: ${observed.reason}`);
|
|
124
136
|
}
|
|
137
|
+
return observed.state === 'valid' ? observed.authority : undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function readRuntimeReleaseAuthority(controllerHome: string): RuntimeReleaseAuthority | undefined {
|
|
141
|
+
const observed = inspectRuntimeReleaseAuthority(controllerHome);
|
|
142
|
+
return observed.state === 'valid' ? observed.authority : undefined;
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
function writeRuntimeReleaseAuthority(controllerHome: string, authority: RuntimeReleaseAuthority): RuntimeReleaseAuthority {
|
|
@@ -145,7 +163,7 @@ export function ensureActiveRuntimeRelease(
|
|
|
145
163
|
operationId = 'runtime-start',
|
|
146
164
|
): RuntimeReleaseAuthority {
|
|
147
165
|
const active = manifestRecord(controllerHome, manifestPath);
|
|
148
|
-
const current =
|
|
166
|
+
const current = mutableRuntimeReleaseAuthority(controllerHome);
|
|
149
167
|
if (current) {
|
|
150
168
|
if (!sameRelease(current.active, active)) throw new Error('RUNTIME_RELEASE_AUTHORITY_MISMATCH');
|
|
151
169
|
return current;
|
|
@@ -169,8 +187,18 @@ export function publishRuntimeRelease(
|
|
|
169
187
|
): RuntimeReleaseAuthority {
|
|
170
188
|
if (!operationId.trim()) throw new Error('RUNTIME_RELEASE_OPERATION_ID_REQUIRED');
|
|
171
189
|
const candidate = manifestRecord(controllerHome, manifestPath);
|
|
172
|
-
const current =
|
|
173
|
-
if (!current)
|
|
190
|
+
const current = mutableRuntimeReleaseAuthority(controllerHome);
|
|
191
|
+
if (!current) {
|
|
192
|
+
return writeRuntimeReleaseAuthority(controllerHome, {
|
|
193
|
+
schemaVersion: 1,
|
|
194
|
+
status: 'committed',
|
|
195
|
+
revision: 1,
|
|
196
|
+
fencingToken: randomUUID(),
|
|
197
|
+
active: candidate,
|
|
198
|
+
operationId,
|
|
199
|
+
committedAt: new Date().toISOString(),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
174
202
|
if (sameRelease(current.active, candidate)) return current;
|
|
175
203
|
const backup = backupPath(controllerHome, current.active.releaseId, operationId);
|
|
176
204
|
const inspection = dependencies.backupDatabase(controllerHome, backup);
|
|
@@ -190,13 +218,22 @@ export function publishRuntimeRelease(
|
|
|
190
218
|
});
|
|
191
219
|
}
|
|
192
220
|
|
|
221
|
+
export function revertInitialRuntimeReleasePublication(controllerHome: string, operationId: string): void {
|
|
222
|
+
const current = mutableRuntimeReleaseAuthority(controllerHome);
|
|
223
|
+
if (!current) return;
|
|
224
|
+
if (current.revision !== 1 || current.previous !== undefined || current.operationId !== operationId) {
|
|
225
|
+
throw new Error('RUNTIME_RELEASE_INITIAL_PUBLICATION_REVERT_MISMATCH');
|
|
226
|
+
}
|
|
227
|
+
rmSync(runtimeReleaseAuthorityPath(controllerHome), { force: true });
|
|
228
|
+
}
|
|
229
|
+
|
|
193
230
|
export function rollbackRuntimeRelease(
|
|
194
231
|
controllerHome: string,
|
|
195
232
|
operationId: string,
|
|
196
233
|
dependencies: RuntimeReleaseStoreDependencies = DEFAULT_DEPENDENCIES,
|
|
197
234
|
): RuntimeReleaseAuthority {
|
|
198
235
|
if (!operationId.trim()) throw new Error('RUNTIME_RELEASE_OPERATION_ID_REQUIRED');
|
|
199
|
-
const current =
|
|
236
|
+
const current = mutableRuntimeReleaseAuthority(controllerHome);
|
|
200
237
|
const target = current?.previous;
|
|
201
238
|
if (!current || !target?.databaseBackup) throw new Error('RUNTIME_PREVIOUS_RELEASE_UNAVAILABLE');
|
|
202
239
|
const currentBackup = backupPath(controllerHome, current.active.releaseId, operationId);
|
|
@@ -6,6 +6,8 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
|
|
|
6
6
|
import type { ControlPlaneDatabaseInspection } from '../control-plane/persistence/sqlite-store';
|
|
7
7
|
import { inspectControlPlaneDatabase } from '../control-plane/persistence/sqlite-store';
|
|
8
8
|
import { activateExclusiveWorkAdmission } from '../control-plane/facade/work-admission-policy';
|
|
9
|
+
import { closeCodeGraphReadProviderSessions } from '../context/codegraph-read-provider';
|
|
10
|
+
import { cancelAllLightweightProcesses } from '../execution/process-runtime/lightweight-managed';
|
|
9
11
|
import {
|
|
10
12
|
collectRuntimeSourceIdentity,
|
|
11
13
|
rotateRuntimeGeneration,
|
|
@@ -40,6 +42,9 @@ export interface CanonicalRuntimeDependencies {
|
|
|
40
42
|
runMcpProbe(endpoint: string, authToken: string): Promise<void>;
|
|
41
43
|
collectRuntimeSourceIdentity: typeof collectRuntimeSourceIdentity;
|
|
42
44
|
rotateRuntimeGeneration: typeof rotateRuntimeGeneration;
|
|
45
|
+
stopLightweightProcesses(controllerHome: string): Promise<number>;
|
|
46
|
+
stopContextReadHelpers(): Promise<void>;
|
|
47
|
+
computeToolSurfaceFingerprint: typeof runtimeGatewayToolSurfaceFingerprint;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
50
|
async function defaultMcpProbe(endpoint: string, authToken: string): Promise<void> {
|
|
@@ -77,6 +82,9 @@ const DEFAULT_DEPENDENCIES: CanonicalRuntimeDependencies = {
|
|
|
77
82
|
runMcpProbe: defaultMcpProbe,
|
|
78
83
|
collectRuntimeSourceIdentity,
|
|
79
84
|
rotateRuntimeGeneration,
|
|
85
|
+
stopLightweightProcesses: cancelAllLightweightProcesses,
|
|
86
|
+
stopContextReadHelpers: closeCodeGraphReadProviderSessions,
|
|
87
|
+
computeToolSurfaceFingerprint: runtimeGatewayToolSurfaceFingerprint,
|
|
80
88
|
};
|
|
81
89
|
|
|
82
90
|
export class CanonicalForgeRuntime {
|
|
@@ -143,6 +151,17 @@ export class CanonicalForgeRuntime {
|
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
private refreshToolSurfaceFingerprint(runtimeSourceRoot: string): void {
|
|
155
|
+
const next = this.dependencies.computeToolSurfaceFingerprint({
|
|
156
|
+
controllerHome: this.config.controllerHome,
|
|
157
|
+
runtimeInstanceId: this.runtimeInstanceId,
|
|
158
|
+
runtimeSourceRoot,
|
|
159
|
+
});
|
|
160
|
+
if (next === this.toolSurfaceFingerprint) return;
|
|
161
|
+
this.toolSurfaceFingerprint = next;
|
|
162
|
+
this.publishStatus();
|
|
163
|
+
}
|
|
164
|
+
|
|
146
165
|
endpoint(): string | undefined {
|
|
147
166
|
return this.transport?.endpoint;
|
|
148
167
|
}
|
|
@@ -218,11 +237,7 @@ export class CanonicalForgeRuntime {
|
|
|
218
237
|
});
|
|
219
238
|
|
|
220
239
|
stage = 'transport';
|
|
221
|
-
this.
|
|
222
|
-
controllerHome: this.config.controllerHome,
|
|
223
|
-
runtimeInstanceId: this.runtimeInstanceId,
|
|
224
|
-
runtimeSourceRoot,
|
|
225
|
-
});
|
|
240
|
+
this.refreshToolSurfaceFingerprint(runtimeSourceRoot);
|
|
226
241
|
this.transport = await this.dependencies.startTransport({
|
|
227
242
|
host: this.config.host,
|
|
228
243
|
port: this.config.port,
|
|
@@ -235,6 +250,7 @@ export class CanonicalForgeRuntime {
|
|
|
235
250
|
sessionId,
|
|
236
251
|
controllerType,
|
|
237
252
|
}),
|
|
253
|
+
onToolSurfaceObservation: () => this.refreshToolSurfaceFingerprint(runtimeSourceRoot),
|
|
238
254
|
onFatal: (error) => this.failCore('MCP_TRANSPORT_FAILED', error.message),
|
|
239
255
|
});
|
|
240
256
|
this.publishStatus();
|
|
@@ -298,6 +314,8 @@ export class CanonicalForgeRuntime {
|
|
|
298
314
|
// Stop accepting new MCP work before quiescing Scheduler activity, then
|
|
299
315
|
// release the Controller Home claim only after all in-process services stop.
|
|
300
316
|
await this.transport?.close().catch(() => undefined);
|
|
317
|
+
await this.dependencies.stopLightweightProcesses(this.config.controllerHome).catch(() => undefined);
|
|
318
|
+
await this.dependencies.stopContextReadHelpers().catch(() => undefined);
|
|
301
319
|
await this.localBridge?.close().catch(() => undefined);
|
|
302
320
|
await this.scheduler?.stop().catch(() => undefined);
|
|
303
321
|
const ownerPid = this.ownership?.record.pid;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync,
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
|
5
5
|
import { resolveControllerHome } from '../../cli/repositories/controller-home';
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
bootstrapLaunchAgentWithRetryV2,
|
|
8
8
|
bootoutLaunchAgentWithRetryV2,
|
|
9
9
|
installLaunchAgent,
|
|
10
|
+
retireConflictingForgeLaunchAgents,
|
|
10
11
|
} from '../../cli/controller/launch-agents';
|
|
11
12
|
|
|
12
13
|
export interface ForgeRuntimeServiceConfig {
|
|
@@ -209,52 +210,118 @@ export function activeRuntimeLaunchSpec(controllerHome: string): ActiveRuntimeLa
|
|
|
209
210
|
};
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
function
|
|
213
|
+
function pathEntryExists(path: string): boolean {
|
|
214
|
+
try {
|
|
215
|
+
lstatSync(path);
|
|
216
|
+
return true;
|
|
217
|
+
} catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function stableRuntimeEntrypointMatches(path: string, target: string): boolean {
|
|
223
|
+
try {
|
|
224
|
+
const current = lstatSync(path);
|
|
225
|
+
const desired = statSync(target);
|
|
226
|
+
if (!current.isFile() || current.isSymbolicLink() || !desired.isFile()) return false;
|
|
227
|
+
if (current.size !== desired.size || (current.mode & 0o777) !== (desired.mode & 0o777)) return false;
|
|
228
|
+
return readFileSync(path).equals(readFileSync(target));
|
|
229
|
+
} catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Keep the Runtime process at one physical path on macOS. TCC resolves symlinks
|
|
236
|
+
* before assigning the responsible process, so a stable symlink to an immutable
|
|
237
|
+
* release still creates a new permission principal for every release directory.
|
|
238
|
+
* The executable is Developer-ID signed with a stable designated requirement;
|
|
239
|
+
* atomically mirroring those exact signed bytes to this fixed service path keeps
|
|
240
|
+
* TCC identity stable while release assets/rollback remain bound by manifest/env.
|
|
241
|
+
*/
|
|
242
|
+
function atomicMirrorRuntimeEntrypoint(path: string, target: string): void {
|
|
213
243
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
214
244
|
const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
215
|
-
rmSync(temporary, { force: true });
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
rmSync(temporary, { force: true, recursive: true });
|
|
246
|
+
try {
|
|
247
|
+
copyFileSync(target, temporary);
|
|
248
|
+
chmodSync(temporary, statSync(target).mode & 0o777);
|
|
249
|
+
try {
|
|
250
|
+
if (lstatSync(path).isDirectory()) rmSync(path, { force: true, recursive: true });
|
|
251
|
+
} catch {}
|
|
252
|
+
renameSync(temporary, path);
|
|
253
|
+
} finally {
|
|
254
|
+
rmSync(temporary, { force: true, recursive: true });
|
|
255
|
+
}
|
|
218
256
|
}
|
|
219
257
|
|
|
220
258
|
export function syncForgeRuntimeActiveEntrypoint(controllerHome: string): { path: string; target?: string; changed: boolean } {
|
|
221
259
|
const paths = forgeRuntimeServicePaths(controllerHome);
|
|
222
260
|
const target = activeRuntimeEntrypoint(controllerHome);
|
|
223
|
-
if (!target)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
261
|
+
if (!target) {
|
|
262
|
+
const existed = pathEntryExists(paths.activeEntrypointPath);
|
|
263
|
+
if (existed) rmSync(paths.activeEntrypointPath, { force: true, recursive: true });
|
|
264
|
+
return { path: paths.activeEntrypointPath, changed: existed };
|
|
265
|
+
}
|
|
266
|
+
if (stableRuntimeEntrypointMatches(paths.activeEntrypointPath, target)) {
|
|
267
|
+
return { path: paths.activeEntrypointPath, target, changed: false };
|
|
268
|
+
}
|
|
269
|
+
atomicMirrorRuntimeEntrypoint(paths.activeEntrypointPath, target);
|
|
270
|
+
if (!stableRuntimeEntrypointMatches(paths.activeEntrypointPath, target)) {
|
|
271
|
+
throw new Error('FORGE_RUNTIME_STABLE_ENTRYPOINT_MIRROR_FAILED');
|
|
272
|
+
}
|
|
231
273
|
return { path: paths.activeEntrypointPath, target, changed: true };
|
|
232
274
|
}
|
|
233
275
|
|
|
234
|
-
|
|
276
|
+
function desiredForgeRuntimeLaunchAgentContract(
|
|
235
277
|
input: { controllerHome: string; bootstrapNodeExecutable?: string; bootstrapRunnerPath?: string },
|
|
236
|
-
): { paths: ForgeRuntimeServicePaths; mode: 'release' | 'bootstrap';
|
|
278
|
+
): { paths: ForgeRuntimeServicePaths; mode: 'release' | 'bootstrap'; plist: string } {
|
|
237
279
|
const paths = forgeRuntimeServicePaths(input.controllerHome);
|
|
238
280
|
const active = activeRuntimeEntrypoint(input.controllerHome);
|
|
239
281
|
const useRelease = Boolean(active);
|
|
240
282
|
const releaseLaunch = useRelease ? activeRuntimeLaunchSpec(input.controllerHome) : undefined;
|
|
241
|
-
if (useRelease) syncForgeRuntimeActiveEntrypoint(input.controllerHome);
|
|
242
283
|
if (!useRelease && (!input.bootstrapNodeExecutable || !input.bootstrapRunnerPath)) {
|
|
243
284
|
throw new Error('FORGE_RUNTIME_BOOTSTRAP_RUNNER_REQUIRED');
|
|
244
285
|
}
|
|
245
286
|
if (useRelease && !releaseLaunch) throw new Error('FORGE_RUNTIME_RELEASE_LAUNCH_SPEC_UNAVAILABLE');
|
|
246
|
-
|
|
287
|
+
return {
|
|
247
288
|
paths,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
289
|
+
mode: useRelease ? 'release' : 'bootstrap',
|
|
290
|
+
plist: renderForgeRuntimeLaunchAgent({
|
|
291
|
+
paths,
|
|
292
|
+
...(useRelease
|
|
293
|
+
? { activeEntrypointPath: paths.activeEntrypointPath, runtimeArgs: releaseLaunch!.args, environment: releaseLaunch!.environment }
|
|
294
|
+
: { nodeExecutable: input.bootstrapNodeExecutable!, runnerPath: input.bootstrapRunnerPath! }),
|
|
295
|
+
}),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function inspectForgeRuntimeLaunchAgentContract(
|
|
300
|
+
input: { controllerHome: string; bootstrapNodeExecutable?: string; bootstrapRunnerPath?: string; inspectUserLaunchAgent?: boolean },
|
|
301
|
+
): { paths: ForgeRuntimeServicePaths; mode: 'release' | 'bootstrap'; sourceMatches: boolean; installedMatches: boolean; matches: boolean } {
|
|
302
|
+
const desired = desiredForgeRuntimeLaunchAgentContract(input);
|
|
303
|
+
const existingSource = existsSync(desired.paths.sourcePlistPath) ? readFileSync(desired.paths.sourcePlistPath, 'utf8') : undefined;
|
|
304
|
+
const existingInstalled = input.inspectUserLaunchAgent && existsSync(desired.paths.installedPlistPath)
|
|
305
|
+
? readFileSync(desired.paths.installedPlistPath, 'utf8')
|
|
306
|
+
: undefined;
|
|
307
|
+
const sourceMatches = existingSource === desired.plist;
|
|
308
|
+
const installedMatches = input.inspectUserLaunchAgent !== true || existingInstalled === desired.plist;
|
|
309
|
+
return { paths: desired.paths, mode: desired.mode, sourceMatches, installedMatches, matches: sourceMatches && installedMatches };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function ensureForgeRuntimeLaunchAgentContract(
|
|
313
|
+
input: { controllerHome: string; bootstrapNodeExecutable?: string; bootstrapRunnerPath?: string; installUserLaunchAgent?: boolean },
|
|
314
|
+
): { paths: ForgeRuntimeServicePaths; mode: 'release' | 'bootstrap'; changed: boolean } {
|
|
315
|
+
const desired = desiredForgeRuntimeLaunchAgentContract(input);
|
|
316
|
+
if (desired.mode === 'release') syncForgeRuntimeActiveEntrypoint(input.controllerHome);
|
|
317
|
+
const existingSource = existsSync(desired.paths.sourcePlistPath) ? readFileSync(desired.paths.sourcePlistPath, 'utf8') : undefined;
|
|
318
|
+
const existingInstalled = input.installUserLaunchAgent && existsSync(desired.paths.installedPlistPath)
|
|
319
|
+
? readFileSync(desired.paths.installedPlistPath, 'utf8')
|
|
320
|
+
: undefined;
|
|
321
|
+
const changed = existingSource !== desired.plist || (input.installUserLaunchAgent === true && existingInstalled !== desired.plist);
|
|
322
|
+
if (existingSource !== desired.plist) atomicWrite(desired.paths.sourcePlistPath, desired.plist);
|
|
323
|
+
if (input.installUserLaunchAgent === true && existingInstalled !== desired.plist) atomicWrite(desired.paths.installedPlistPath, desired.plist, 0o644);
|
|
324
|
+
return { paths: desired.paths, mode: desired.mode, changed };
|
|
258
325
|
}
|
|
259
326
|
|
|
260
327
|
export function renderForgeRuntimeLaunchAgent(input: { paths: ForgeRuntimeServicePaths; activeEntrypointPath?: string; runtimeArgs?: string[]; environment?: Record<string, string>; nodeExecutable?: string; runnerPath?: string }): string {
|
|
@@ -286,6 +353,12 @@ export async function installForgeRuntimeService(input: { config: ForgeRuntimeSe
|
|
|
286
353
|
bootstrapRunnerPath: input.runnerPath,
|
|
287
354
|
});
|
|
288
355
|
await retireLegacyBrowserAutomationLaunchAgent(config.controllerHome);
|
|
356
|
+
await retireConflictingForgeLaunchAgents({
|
|
357
|
+
desiredLabel: paths.label,
|
|
358
|
+
labelPrefix: 'com.moretea.forge.runtime.',
|
|
359
|
+
port: config.port,
|
|
360
|
+
requiredArguments: ['--controller-home'],
|
|
361
|
+
});
|
|
289
362
|
installLaunchAgent(paths.sourcePlistPath, paths.label);
|
|
290
363
|
const result = await bootstrapLaunchAgentWithRetryV2({ label: paths.label, plistPath: paths.installedPlistPath });
|
|
291
364
|
if (!result.ok) throw new Error(`FORGE_RUNTIME_SERVICE_BOOTSTRAP_FAILED: ${result.diagnostics.join('; ')}`);
|