@moretea-labs/forge 1.6.0 → 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 +26 -0
- package/CLAUDE.md +2 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- 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/prompt-guard.sh +5 -81
- 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 +1 -1
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.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 +11 -36
- 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/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 +26 -6
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- 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/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- 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-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-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/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/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +1 -1
- 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 +785 -469
- 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/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +0 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +0 -513
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- 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/repository-tools.ts +247 -107
- package/src/cli/mcp/server.ts +537 -111
- 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 +101 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +101 -19
- 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/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +61 -3
- package/src/cli/repository/inspector.ts +553 -63
- 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 +47 -8
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- 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 +143 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
- 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 +132 -1
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
- package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
- 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 +22 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +175 -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 +355 -116
- 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/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 +13 -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 +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +53 -0
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +43 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -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 +1192 -114
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +184 -82
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2901 -241
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff.ts +92 -2
- package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
- 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 +8 -2
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- 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 +123 -6
- 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 +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +269 -29
- package/src/runtime/plugins/types.ts +35 -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/maintenance-executor.ts +393 -28
- 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 +19 -0
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- 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 +700 -92
- 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 +81 -3
- package/src/runtime/workflow/schedules/engine.ts +287 -151
- 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 +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- 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/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 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- 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 -373
- package/docs/architecture/current/entity-model.md +0 -612
- 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 -169
- 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 -321
- 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 -93
- 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/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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
|
|
3
|
-
export const ROUTE_POLICY_VERSION = 'route-policy-
|
|
3
|
+
export const ROUTE_POLICY_VERSION = 'route-policy-v4' as const;
|
|
4
4
|
|
|
5
5
|
export type RouteExecutionMode = 'direct_control' | 'goal_workloop' | 'handoff_only';
|
|
6
6
|
export type RouteWorkMode = 'direct_edit' | 'bounded_work' | 'quick_agent' | 'issue_task';
|
|
@@ -238,8 +238,6 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
|
|
|
238
238
|
? false
|
|
239
239
|
: input.recovery.isolationRequired === true || explicitParallelMode;
|
|
240
240
|
const requiresRecovery = input.recovery.required === true
|
|
241
|
-
|| input.intent.requiresLongRunningChecks === true
|
|
242
|
-
|| explicitMode === 'debug'
|
|
243
241
|
|| explicitMode === 'release'
|
|
244
242
|
|| explicitMode === 'scale';
|
|
245
243
|
const coordinationRequired = explicitParallelMode || input.intent.requiresIndependentDeliverables === true
|
|
@@ -289,26 +287,21 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
|
|
|
289
287
|
}
|
|
290
288
|
|
|
291
289
|
if (input.workspace.dirty && mutation) {
|
|
292
|
-
reasons.push({
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
requiresIsolation: true, requiresRecovery: true, createHandoff: true, waitForUser: false,
|
|
297
|
-
approvalState: 'approval_not_required', alternatives: [], ...decisionBase(input, reasons),
|
|
298
|
-
};
|
|
290
|
+
reasons.push({
|
|
291
|
+
code: 'dirty_workspace_preserve_existing_changes',
|
|
292
|
+
message: 'The checkout is already dirty; preserve unrelated changes and treat inspected/actual scope as evidence instead of creating Work solely for adoption.',
|
|
293
|
+
});
|
|
299
294
|
}
|
|
300
295
|
if (explicitMode) reasons.push({ code: `explicit_${explicitMode}`, message: `Explicit -${explicitMode} mode overrides automatic work topology while policy gates remain authoritative.` });
|
|
301
296
|
if (protectedPath) reasons.push({ code: 'protected_path', message: 'The predicted scope includes a protected or release-sensitive path.' });
|
|
302
297
|
if (requiresRecovery) reasons.push({ code: 'recovery_required', message: 'The operation needs resumable Work and bounded recovery.' });
|
|
303
298
|
if (requiresIsolation) reasons.push({ code: 'isolation_required', message: 'The operation requires an isolated checkout or serialized lane.' });
|
|
304
|
-
if (input.intent.requiresLongRunningChecks) reasons.push({ code: 'long_checks', message: 'Long-running checks require durable
|
|
299
|
+
if (input.intent.requiresLongRunningChecks) reasons.push({ code: 'long_checks', message: 'Long-running checks may use a lightweight handle; duration alone does not require durable Work.' });
|
|
305
300
|
if (input.intent.requiresInvestigation) reasons.push({ code: 'investigation', message: 'Investigation is required before or during implementation.' });
|
|
306
301
|
if (input.intent.needsDependencies) reasons.push({ code: 'dependencies', message: 'Dependency ordering requires durable Work.' });
|
|
307
302
|
if (coordinationRequired) reasons.push({ code: 'independent_deliverables', message: 'Multiple independent deliverables require durable PlanContract/Work coordination.' });
|
|
308
303
|
|
|
309
304
|
const explicitBoundedMode = explicitMode === 'plan'
|
|
310
|
-
|| explicitMode === 'debug'
|
|
311
|
-
|| explicitMode === 'review'
|
|
312
305
|
|| explicitMode === 'release';
|
|
313
306
|
const complex = explicitMode === 'direct'
|
|
314
307
|
? false
|
|
@@ -320,11 +313,9 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
|
|
|
320
313
|
|| input.capabilities.requiresWorker === true
|
|
321
314
|
|| input.capabilities.requiresExternalEffect === true
|
|
322
315
|
|| input.intent.needsDependencies === true
|
|
323
|
-
|| protectedPath
|
|
324
316
|
|| destructive
|
|
325
317
|
|| remoteWrite
|
|
326
|
-
|| secretAccess
|
|
327
|
-
|| (mutation && (expectedFiles > 4 || expectedChangedLines > 200));
|
|
318
|
+
|| secretAccess;
|
|
328
319
|
const executionMode: RouteExecutionMode = complex ? 'goal_workloop' : 'direct_control';
|
|
329
320
|
// Work topology is independent from executor/provider choice. Independent or
|
|
330
321
|
// parallel deliverables stay in the durable Goal Workloop and are decomposed by
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { resolveControllerHome } from '../../cli/repositories/controller-home';
|
|
2
2
|
import { readSchedulerHealthSnapshot } from './global-scheduler/scheduler';
|
|
3
|
-
import type { ControllerStartupRecoveryResult } from './startup-recovery';
|
|
4
3
|
import type { RuntimeSourceIdentity } from './runtime-generation';
|
|
5
4
|
import { readRuntimeOwner } from '../root/ownership';
|
|
6
5
|
import { observeRuntimeStatus } from '../root/status';
|
|
@@ -26,7 +25,6 @@ export interface ForgeRuntimeStatus {
|
|
|
26
25
|
gatewaySeparated?: boolean;
|
|
27
26
|
workerIsolation?: boolean;
|
|
28
27
|
degraded?: boolean;
|
|
29
|
-
recovery?: ControllerStartupRecoveryResult;
|
|
30
28
|
generation?: string;
|
|
31
29
|
source?: RuntimeSourceIdentity;
|
|
32
30
|
restartRequired?: boolean;
|
|
@@ -21,6 +21,19 @@ export interface McpTimingTrace {
|
|
|
21
21
|
commandExecutionMs?: number;
|
|
22
22
|
resultSerializationMs?: number;
|
|
23
23
|
resultPersistenceMs?: number;
|
|
24
|
+
/** Public Gateway only: resolve current Canonical Runtime identity/token/endpoint. */
|
|
25
|
+
gatewayProxyResolveMs?: number;
|
|
26
|
+
/** Public Gateway only: establish or await a Canonical Runtime MCP client connection. */
|
|
27
|
+
gatewayProxyConnectMs?: number;
|
|
28
|
+
/** Public Gateway only: MCP call duration after a client is acquired, including Canonical Runtime work. */
|
|
29
|
+
gatewayProxyCallMs?: number;
|
|
30
|
+
/** Public Gateway only: time from forwarding the tool call until the Canonical Runtime handler starts. */
|
|
31
|
+
gatewayProxyCanonicalDispatchLagMs?: number;
|
|
32
|
+
/** Canonical Runtime server duration projected back through the proxy response. */
|
|
33
|
+
gatewayProxyCanonicalDurationMs?: number;
|
|
34
|
+
/** Public Gateway only: response/transport tail after canonical handler completion. */
|
|
35
|
+
gatewayProxyReturnMs?: number;
|
|
36
|
+
gatewayProxyConnectionState?: 'reused' | 'cold_connect' | 'coalesced_connect' | 'identity_reconnect';
|
|
24
37
|
totalToolDurationMs: number;
|
|
25
38
|
sessionId?: string;
|
|
26
39
|
repoId?: string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export type McpTransportTimingScope = 'tool_call' | 'connect_and_tool_call';
|
|
2
|
+
|
|
3
|
+
export interface McpTransportLatencySample {
|
|
4
|
+
label: string;
|
|
5
|
+
tool: string;
|
|
6
|
+
timingScope?: McpTransportTimingScope;
|
|
7
|
+
clientTotalMs: number;
|
|
8
|
+
serverDurationMs: number;
|
|
9
|
+
traceId?: string;
|
|
10
|
+
requestId?: string;
|
|
11
|
+
observedAt?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface McpTransportLatencyStats {
|
|
15
|
+
p50: number;
|
|
16
|
+
p95: number;
|
|
17
|
+
max: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface McpTransportLatencyGroup {
|
|
21
|
+
label: string;
|
|
22
|
+
tool: string;
|
|
23
|
+
timingScope: McpTransportTimingScope;
|
|
24
|
+
sampleCount: number;
|
|
25
|
+
clientTotalMs: McpTransportLatencyStats;
|
|
26
|
+
serverDurationMs: McpTransportLatencyStats;
|
|
27
|
+
externalResidualMs: McpTransportLatencyStats;
|
|
28
|
+
externalSharePct: McpTransportLatencyStats;
|
|
29
|
+
serverExceedsClientCount: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface McpTransportLatencyReport {
|
|
33
|
+
schemaVersion: 1;
|
|
34
|
+
sampleCount: number;
|
|
35
|
+
groups: McpTransportLatencyGroup[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function round(value: number): number {
|
|
39
|
+
return Math.round(value * 100) / 100;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function percentile(values: number[], fraction: number): number {
|
|
43
|
+
if (values.length === 0) return 0;
|
|
44
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
45
|
+
const index = Math.max(0, Math.min(sorted.length - 1, Math.ceil(sorted.length * fraction) - 1));
|
|
46
|
+
return round(sorted[index]!);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function stats(values: number[]): McpTransportLatencyStats {
|
|
50
|
+
return {
|
|
51
|
+
p50: percentile(values, 0.5),
|
|
52
|
+
p95: percentile(values, 0.95),
|
|
53
|
+
max: round(Math.max(...values, 0)),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validDuration(value: unknown): value is number {
|
|
58
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function normalizeMcpTransportLatencySample(value: unknown): McpTransportLatencySample {
|
|
62
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
63
|
+
throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: sample must be an object');
|
|
64
|
+
}
|
|
65
|
+
const record = value as Record<string, unknown>;
|
|
66
|
+
const label = typeof record.label === 'string' ? record.label.trim() : '';
|
|
67
|
+
const tool = typeof record.tool === 'string' ? record.tool.trim() : '';
|
|
68
|
+
const timingScope = record.timingScope === undefined || record.timingScope === 'tool_call'
|
|
69
|
+
? 'tool_call'
|
|
70
|
+
: record.timingScope === 'connect_and_tool_call'
|
|
71
|
+
? 'connect_and_tool_call'
|
|
72
|
+
: undefined;
|
|
73
|
+
if (!label) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: label is required');
|
|
74
|
+
if (!tool) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: tool is required');
|
|
75
|
+
if (!timingScope) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: timingScope must be tool_call or connect_and_tool_call');
|
|
76
|
+
if (!validDuration(record.clientTotalMs)) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: clientTotalMs must be a non-negative finite number');
|
|
77
|
+
if (!validDuration(record.serverDurationMs)) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: serverDurationMs must be a non-negative finite number');
|
|
78
|
+
return {
|
|
79
|
+
label,
|
|
80
|
+
tool,
|
|
81
|
+
timingScope,
|
|
82
|
+
clientTotalMs: record.clientTotalMs,
|
|
83
|
+
serverDurationMs: record.serverDurationMs,
|
|
84
|
+
...(typeof record.traceId === 'string' && record.traceId.trim() ? { traceId: record.traceId.trim() } : {}),
|
|
85
|
+
...(typeof record.requestId === 'string' && record.requestId.trim() ? { requestId: record.requestId.trim() } : {}),
|
|
86
|
+
...(typeof record.observedAt === 'string' && record.observedAt.trim() ? { observedAt: record.observedAt.trim() } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function extractForgeServerDuration(result: unknown): number | undefined {
|
|
91
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) return undefined;
|
|
92
|
+
const structured = (result as Record<string, unknown>).structuredContent;
|
|
93
|
+
if (!structured || typeof structured !== 'object' || Array.isArray(structured)) return undefined;
|
|
94
|
+
const responseMeta = (structured as Record<string, unknown>).responseMeta;
|
|
95
|
+
if (!responseMeta || typeof responseMeta !== 'object' || Array.isArray(responseMeta)) return undefined;
|
|
96
|
+
const duration = (responseMeta as Record<string, unknown>).serverDurationMs;
|
|
97
|
+
return validDuration(duration) ? duration : undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function aggregateMcpTransportLatencySamples(values: unknown[]): McpTransportLatencyReport {
|
|
101
|
+
const samples = values.map(normalizeMcpTransportLatencySample);
|
|
102
|
+
const grouped = new Map<string, McpTransportLatencySample[]>();
|
|
103
|
+
for (const sample of samples) {
|
|
104
|
+
const key = `${sample.label}\u0000${sample.tool}\u0000${sample.timingScope ?? 'tool_call'}`;
|
|
105
|
+
const entries = grouped.get(key) ?? [];
|
|
106
|
+
entries.push(sample);
|
|
107
|
+
grouped.set(key, entries);
|
|
108
|
+
}
|
|
109
|
+
const groups = [...grouped.values()].map((entries): McpTransportLatencyGroup => {
|
|
110
|
+
const client = entries.map((sample) => sample.clientTotalMs);
|
|
111
|
+
const server = entries.map((sample) => sample.serverDurationMs);
|
|
112
|
+
const residual = entries.map((sample) => Math.max(0, sample.clientTotalMs - sample.serverDurationMs));
|
|
113
|
+
const share = entries.map((sample, index) => client[index]! > 0 ? residual[index]! / client[index]! * 100 : 0);
|
|
114
|
+
return {
|
|
115
|
+
label: entries[0]!.label,
|
|
116
|
+
tool: entries[0]!.tool,
|
|
117
|
+
timingScope: entries[0]!.timingScope ?? 'tool_call',
|
|
118
|
+
sampleCount: entries.length,
|
|
119
|
+
clientTotalMs: stats(client),
|
|
120
|
+
serverDurationMs: stats(server),
|
|
121
|
+
externalResidualMs: stats(residual),
|
|
122
|
+
externalSharePct: stats(share),
|
|
123
|
+
serverExceedsClientCount: entries.filter((sample) => sample.serverDurationMs > sample.clientTotalMs).length,
|
|
124
|
+
};
|
|
125
|
+
}).sort((left, right) => left.label.localeCompare(right.label)
|
|
126
|
+
|| left.tool.localeCompare(right.tool)
|
|
127
|
+
|| left.timingScope.localeCompare(right.timingScope));
|
|
128
|
+
return { schemaVersion: 1, sampleCount: samples.length, groups };
|
|
129
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
|
-
import { existsSync, lstatSync, readdirSync, rmSync } from 'fs';
|
|
2
|
+
import { existsSync, lstatSync, readdirSync, rmSync, statfsSync } from 'fs';
|
|
3
3
|
import { tmpdir } from 'os';
|
|
4
4
|
import { basename, dirname, join, resolve } from 'path';
|
|
5
5
|
|
|
@@ -46,6 +46,7 @@ export interface RuntimePerformanceDiagnostics {
|
|
|
46
46
|
};
|
|
47
47
|
processes: RuntimeProcessSample[];
|
|
48
48
|
temp: { scannedRoots: string[]; totalEntries: number; cleanupCandidates: number; entries: RuntimeTempEntry[] };
|
|
49
|
+
storage: { availableBytes?: number; availableGiB?: number; pressure: 'normal' | 'warning' | 'critical' | 'unknown'; managedCacheRoots: string[] };
|
|
49
50
|
cleanupPreview?: { safeToTerminate: RuntimeProcessSample[]; safeToRemoveTempEntries: RuntimeTempEntry[]; requiresConfirmation: true };
|
|
50
51
|
truncated: {
|
|
51
52
|
summary: boolean;
|
|
@@ -322,7 +323,27 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
|
|
|
322
323
|
const temp = input.includeTempDirs === false
|
|
323
324
|
? { roots: [] as string[], entries: [] as RuntimeTempEntry[] }
|
|
324
325
|
: scanRuntimeTempEntries(processes);
|
|
326
|
+
let availableBytes: number | undefined;
|
|
327
|
+
try {
|
|
328
|
+
const fs = statfsSync(input.repoRoot);
|
|
329
|
+
availableBytes = Number(fs.bavail) * Number(fs.bsize);
|
|
330
|
+
} catch {}
|
|
331
|
+
const availableGiB = availableBytes === undefined ? undefined : Math.round((availableBytes / (1024 ** 3)) * 10) / 10;
|
|
332
|
+
const storagePressure: RuntimePerformanceDiagnostics['storage']['pressure'] = availableGiB === undefined
|
|
333
|
+
? 'unknown'
|
|
334
|
+
: availableGiB < 30
|
|
335
|
+
? 'critical'
|
|
336
|
+
: availableGiB < 50
|
|
337
|
+
? 'warning'
|
|
338
|
+
: 'normal';
|
|
339
|
+
const managedCacheRoots = [
|
|
340
|
+
join(input.repoRoot, '.forge', 'ios', 'DerivedData'),
|
|
341
|
+
join(input.repoRoot, '.ai', 'harness'),
|
|
342
|
+
join(input.repoRoot, '.repo-harness'),
|
|
343
|
+
].filter((path) => existsSync(path));
|
|
325
344
|
const findings: RuntimePerformanceDiagnostics['findings'] = [];
|
|
345
|
+
if (storagePressure === 'critical') findings.push({ severity: 'critical', code: 'LOW_DISK_SPACE', message: `Only ${availableGiB} GiB of repository volume space is available.` });
|
|
346
|
+
else if (storagePressure === 'warning') findings.push({ severity: 'warning', code: 'LOW_DISK_SPACE', message: `Repository volume free space is ${availableGiB} GiB.` });
|
|
326
347
|
if (orphanWorkers.length > 0) findings.push({ severity: orphanWorkers.length >= 3 ? 'critical' : 'warning', code: 'ORPHAN_WORKERS', message: 'Detected ' + orphanWorkers.length + ' orphan worker process(es).' });
|
|
327
348
|
const totalCpu = Math.round(repoProcesses.reduce((sum, process) => sum + process.cpu, 0) * 10) / 10;
|
|
328
349
|
if (totalCpu >= 100 || highCpu.length >= 3) findings.push({ severity: 'warning', code: 'HIGH_FORGE_CPU', message: 'Forge-related CPU is ' + totalCpu + '%.' });
|
|
@@ -341,6 +362,7 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
|
|
|
341
362
|
...(orphanWorkers.length ? ['Terminate orphan workers only after reviewing cleanupPreview.safeToTerminate.'] : []),
|
|
342
363
|
...(tempCleanupCandidates.length ? ['Remove stale temp entries through runtime_maintenance_apply after reviewing cleanupPreview.safeToRemoveTempEntries.'] : []),
|
|
343
364
|
...(highCpu.length ? ['Inspect highCpuProcesses before restarting the controller stack.'] : []),
|
|
365
|
+
...(storagePressure === 'critical' ? ['Allow Forge generated-cache retention to reclaim stale DerivedData before starting another heavy iOS verification.'] : []),
|
|
344
366
|
];
|
|
345
367
|
return {
|
|
346
368
|
schemaVersion: 1,
|
|
@@ -358,6 +380,7 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
|
|
|
358
380
|
cleanupCandidates: tempCleanupCandidates.length,
|
|
359
381
|
entries: temp.entries.slice(0, MAX_DIAGNOSTIC_TEMP_ENTRIES),
|
|
360
382
|
},
|
|
383
|
+
storage: { availableBytes, availableGiB, pressure: storagePressure, managedCacheRoots },
|
|
361
384
|
cleanupPreview: input.cleanupPreview === true
|
|
362
385
|
? {
|
|
363
386
|
safeToTerminate: rawCleanupProcesses,
|
|
@@ -18,7 +18,7 @@ export interface RuntimeEntityEvent {
|
|
|
18
18
|
eventId: string;
|
|
19
19
|
eventType: string;
|
|
20
20
|
repoId: string;
|
|
21
|
-
entityType: 'job' | 'plugin' | 'schedule' | 'occurrence' | 'portfolio' | '
|
|
21
|
+
entityType: 'job' | 'plugin' | 'schedule' | 'occurrence' | 'portfolio' | 'release' | 'lease' | 'schedule-decision';
|
|
22
22
|
entityId: string;
|
|
23
23
|
correlationId?: string;
|
|
24
24
|
causationId?: string;
|
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'crypto';
|
|
|
2
2
|
import { existsSync, readdirSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { ensureControllerHome, ensureRepositoryControllerLayout, repositoryControllerRoot } from '../../../cli/repositories/controller-home';
|
|
5
|
-
import { withControllerLock } from '../../../cli/repositories/locks';
|
|
5
|
+
import { releaseControllerLock, tryAcquireControllerLock, withControllerLock } from '../../../cli/repositories/locks';
|
|
6
6
|
import { appendJobEvent } from '../../evidence/event-ledger';
|
|
7
7
|
import { writeExecutionArtifact } from '../../evidence/artifact-store';
|
|
8
8
|
import { markRepositoryProjectionDirty } from '../../projections/invalidation';
|
|
@@ -206,7 +206,9 @@ function writeActiveIndex(controllerHome: string, index: ActiveJobIndex): void {
|
|
|
206
206
|
writeJsonAtomic(activeIndexPath(controllerHome), {
|
|
207
207
|
schemaVersion: 1,
|
|
208
208
|
updatedAt: now(),
|
|
209
|
-
|
|
209
|
+
// Active work is correctness state, not a presentation window. Dropping an
|
|
210
|
+
// entry here makes Scheduler/recovery reads forget a durable non-terminal Job.
|
|
211
|
+
jobs: [...deduped.values()].sort((a, b) => a.queuedAt.localeCompare(b.queuedAt)),
|
|
210
212
|
} satisfies ActiveJobIndex);
|
|
211
213
|
}
|
|
212
214
|
|
|
@@ -258,7 +260,9 @@ export function rebuildExecutionJobIndexes(controllerHome: string, repoIds: stri
|
|
|
258
260
|
for (const repoId of targetRepos) {
|
|
259
261
|
const records = join(executionJobRoot(controllerHome, repoId), 'records');
|
|
260
262
|
let names: string[] = [];
|
|
261
|
-
|
|
263
|
+
// Startup repair must rebuild every active and request authority. Only the
|
|
264
|
+
// recent-history projection below is intentionally bounded.
|
|
265
|
+
try { names = readdirSync(records).filter((name) => name.endsWith('.json')); } catch { continue; }
|
|
262
266
|
for (const name of names) {
|
|
263
267
|
try {
|
|
264
268
|
const job = readJsonFile<ExecutionJob>(join(records, name));
|
|
@@ -648,11 +652,61 @@ export function listActiveExecutionJobs(controllerHome: string, repoId?: string)
|
|
|
648
652
|
try {
|
|
649
653
|
const job = getExecutionJob(controllerHome, entry.repoId, entry.jobId);
|
|
650
654
|
if (ACTIVE_JOB_STATUSES.has(job.status)) output.push(job);
|
|
651
|
-
} catch { /* stale
|
|
655
|
+
} catch { /* maintenance reconciliation owns stale-index repair */ }
|
|
652
656
|
}
|
|
653
657
|
return output;
|
|
654
658
|
}
|
|
655
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Maintenance-only active-index convergence. The active index itself is the
|
|
662
|
+
* bounded candidate set; this never scans historical Job directories. Ordinary
|
|
663
|
+
* read APIs remain side-effect free, while the Scheduler can remove terminal or
|
|
664
|
+
* missing entries once and make subsequent idle reads genuinely cheap.
|
|
665
|
+
*/
|
|
666
|
+
export function reconcileActiveExecutionJobIndex(
|
|
667
|
+
controllerHome: string,
|
|
668
|
+
repoId?: string,
|
|
669
|
+
): ExecutionJob[] {
|
|
670
|
+
const key = { scope: 'global' as const, resource: 'execution-index' };
|
|
671
|
+
const owner = `execution-index-active-reconcile:${repoId ?? 'all'}`;
|
|
672
|
+
const acquired = tryAcquireControllerLock(controllerHome, key, owner);
|
|
673
|
+
if (!acquired.acquired) return listActiveExecutionJobs(controllerHome, repoId);
|
|
674
|
+
try {
|
|
675
|
+
const index = readActiveIndex(controllerHome);
|
|
676
|
+
const jobs: ExecutionJob[] = [];
|
|
677
|
+
const retained: ActiveJobIndex['jobs'] = [];
|
|
678
|
+
let removed = 0;
|
|
679
|
+
for (const entry of index.jobs) {
|
|
680
|
+
if (repoId && entry.repoId !== repoId) {
|
|
681
|
+
retained.push(entry);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
const job = getExecutionJob(controllerHome, entry.repoId, entry.jobId);
|
|
686
|
+
if (!ACTIVE_JOB_STATUSES.has(job.status)) {
|
|
687
|
+
removed += 1;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
jobs.push(job);
|
|
691
|
+
retained.push({
|
|
692
|
+
jobId: job.jobId,
|
|
693
|
+
repoId: job.repoId,
|
|
694
|
+
status: job.status,
|
|
695
|
+
priority: job.priority,
|
|
696
|
+
queuedAt: job.queuedAt,
|
|
697
|
+
updatedAt: job.updatedAt,
|
|
698
|
+
});
|
|
699
|
+
} catch {
|
|
700
|
+
removed += 1;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (removed > 0) writeActiveIndex(controllerHome, { ...index, jobs: retained });
|
|
704
|
+
return jobs;
|
|
705
|
+
} finally {
|
|
706
|
+
releaseControllerLock(controllerHome, key, acquired.lock.lockId);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
656
710
|
function executionJobApprovalRequestId(job: ExecutionJob): string | undefined {
|
|
657
711
|
if (typeof job.result?.approvalRequestId === 'string') return job.result.approvalRequestId;
|
|
658
712
|
const authorization = job.result?.authorization;
|
|
@@ -45,7 +45,7 @@ export interface ResourceClaimSpec {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export interface ExecutionJobOrigin {
|
|
48
|
-
surface: 'mcp' | 'local-ui' | 'mobile-intent' | 'chatgpt-action' | '
|
|
48
|
+
surface: 'mcp' | 'local-ui' | 'mobile-intent' | 'chatgpt-action' | 'cli' | 'schedule' | 'reconciliation' | 'system';
|
|
49
49
|
actor?: string;
|
|
50
50
|
correlationId?: string;
|
|
51
51
|
causationId?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import { existsSync, mkdirSync, realpathSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, symlinkSync } from 'fs';
|
|
3
3
|
import { dirname, join, resolve } from 'path';
|
|
4
4
|
import { repositoryControllerRoot } from '../../cli/repositories/controller-home';
|
|
5
5
|
import type { RepositoryRecord } from '../../cli/repositories/types';
|
|
@@ -14,6 +14,7 @@ import { branchSlugSegment, validateBranchName } from '../../cli/repositories/br
|
|
|
14
14
|
import { withControllerLock } from '../../cli/repositories/locks';
|
|
15
15
|
import { resolveGitExecutable } from '../../effects/git-executable';
|
|
16
16
|
import { runProcess } from '../../effects/process-runner';
|
|
17
|
+
import { repositoryChildProcessEnvironment, resolveBunExecutable } from '../shared/process-environment';
|
|
17
18
|
import { readJsonFile, writeJsonAtomic } from '../shared/json-files';
|
|
18
19
|
|
|
19
20
|
export interface EnsureManagedWorkspaceInput {
|
|
@@ -21,6 +22,24 @@ export interface EnsureManagedWorkspaceInput {
|
|
|
21
22
|
title: string;
|
|
22
23
|
baseRef?: string;
|
|
23
24
|
branchName?: string;
|
|
25
|
+
/** Explicitly materialize lockfile-backed Node dependencies in this isolated worktree. */
|
|
26
|
+
prepareDependencies?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ManagedWorkspaceDependencyBootstrap {
|
|
30
|
+
packageManager: 'bun' | 'pnpm' | 'npm' | 'yarn';
|
|
31
|
+
lockfile: string;
|
|
32
|
+
command: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ManagedWorkspaceDependencyPreparation {
|
|
36
|
+
mode: 'already_ready' | 'canonical_reuse' | 'installed';
|
|
37
|
+
nodeModulesRoot: string;
|
|
38
|
+
canonicalRoot?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ManagedWorkspaceDependencies {
|
|
42
|
+
materializeDependencies?: (workspaceRoot: string) => void;
|
|
24
43
|
}
|
|
25
44
|
|
|
26
45
|
export interface ManagedWorkspace {
|
|
@@ -73,6 +92,180 @@ function assertBranch(root: string, branch: string): string {
|
|
|
73
92
|
return normalized;
|
|
74
93
|
}
|
|
75
94
|
|
|
95
|
+
export function managedWorkspaceDependencyBootstrap(repoRoot: string): ManagedWorkspaceDependencyBootstrap | undefined {
|
|
96
|
+
if (!existsSync(join(repoRoot, 'package.json')) || existsSync(join(repoRoot, 'node_modules'))) return undefined;
|
|
97
|
+
const lockCandidates = [
|
|
98
|
+
['bun', 'bun.lock', ['bun', 'install', '--frozen-lockfile']],
|
|
99
|
+
['bun', 'bun.lockb', ['bun', 'install', '--frozen-lockfile']],
|
|
100
|
+
['pnpm', 'pnpm-lock.yaml', ['pnpm', 'install', '--frozen-lockfile']],
|
|
101
|
+
['npm', 'package-lock.json', ['npm', 'ci']],
|
|
102
|
+
['yarn', 'yarn.lock', ['yarn', 'install', '--frozen-lockfile']],
|
|
103
|
+
] as const;
|
|
104
|
+
const detected = lockCandidates.find(([, lockfile]) => existsSync(join(repoRoot, lockfile)));
|
|
105
|
+
if (!detected) return undefined;
|
|
106
|
+
return { packageManager: detected[0], lockfile: detected[1], command: [...detected[2]] };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const DEPENDENCY_REUSE_CONFIG_PATHS = [
|
|
110
|
+
'.npmrc',
|
|
111
|
+
'bunfig.toml',
|
|
112
|
+
'pnpm-workspace.yaml',
|
|
113
|
+
'.yarnrc.yml',
|
|
114
|
+
] as const;
|
|
115
|
+
|
|
116
|
+
function dependencyInputMatches(leftRoot: string, rightRoot: string, path: string): boolean {
|
|
117
|
+
const left = join(leftRoot, path);
|
|
118
|
+
const right = join(rightRoot, path);
|
|
119
|
+
const leftExists = existsSync(left);
|
|
120
|
+
const rightExists = existsSync(right);
|
|
121
|
+
if (leftExists !== rightExists) return false;
|
|
122
|
+
return !leftExists || readFileSync(left).equals(readFileSync(right));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function linkedWorkspaceCanonicalRoot(repoRoot: string): string | undefined {
|
|
126
|
+
const common = runProcess(resolveGitExecutable(), ['-C', repoRoot, 'rev-parse', '--path-format=absolute', '--git-common-dir'], {
|
|
127
|
+
timeoutMs: 15_000,
|
|
128
|
+
maxOutputBytes: 8 * 1024,
|
|
129
|
+
});
|
|
130
|
+
if (!common.ok || !common.stdout.trim()) return undefined;
|
|
131
|
+
const canonicalRoot = dirname(resolve(common.stdout.trim()));
|
|
132
|
+
if (!existsSync(canonicalRoot)) return undefined;
|
|
133
|
+
try {
|
|
134
|
+
if (realpathSync(canonicalRoot) === realpathSync(repoRoot)) return undefined;
|
|
135
|
+
} catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const topLevel = runProcess(resolveGitExecutable(), ['-C', canonicalRoot, 'rev-parse', '--show-toplevel'], {
|
|
139
|
+
timeoutMs: 15_000,
|
|
140
|
+
maxOutputBytes: 8 * 1024,
|
|
141
|
+
});
|
|
142
|
+
if (!topLevel.ok || !topLevel.stdout.trim()) return undefined;
|
|
143
|
+
try {
|
|
144
|
+
return realpathSync(topLevel.stdout.trim()) === realpathSync(canonicalRoot) ? canonicalRoot : undefined;
|
|
145
|
+
} catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function resolveManagedWorkspaceCanonicalDependencies(repoRoot: string): {
|
|
151
|
+
canonicalRoot: string;
|
|
152
|
+
nodeModulesRoot: string;
|
|
153
|
+
} | undefined {
|
|
154
|
+
const bootstrap = managedWorkspaceDependencyBootstrap(repoRoot);
|
|
155
|
+
if (!bootstrap) return undefined;
|
|
156
|
+
const canonicalRoot = linkedWorkspaceCanonicalRoot(repoRoot);
|
|
157
|
+
if (!canonicalRoot) return undefined;
|
|
158
|
+
const nodeModulesRoot = join(canonicalRoot, 'node_modules');
|
|
159
|
+
if (!existsSync(nodeModulesRoot)) return undefined;
|
|
160
|
+
const dependencyInputs = ['package.json', bootstrap.lockfile, ...DEPENDENCY_REUSE_CONFIG_PATHS];
|
|
161
|
+
if (!dependencyInputs.every((path) => dependencyInputMatches(repoRoot, canonicalRoot, path))) return undefined;
|
|
162
|
+
return { canonicalRoot, nodeModulesRoot: realpathSync(nodeModulesRoot) };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function packageManifestRequiresDependencyMaterialization(repoRoot: string): boolean {
|
|
166
|
+
const packagePath = join(repoRoot, 'package.json');
|
|
167
|
+
if (!existsSync(packagePath)) return false;
|
|
168
|
+
let manifest: Record<string, unknown>;
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(readFileSync(packagePath, 'utf8')) as unknown;
|
|
171
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return true;
|
|
172
|
+
manifest = parsed as Record<string, unknown>;
|
|
173
|
+
} catch {
|
|
174
|
+
// Preserve fail-closed dependency preparation for malformed manifests. The
|
|
175
|
+
// package manager remains the authority for the concrete parse error once a
|
|
176
|
+
// supported frozen lockfile is present.
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const dependencyFields = [
|
|
181
|
+
'dependencies',
|
|
182
|
+
'devDependencies',
|
|
183
|
+
'optionalDependencies',
|
|
184
|
+
'peerDependencies',
|
|
185
|
+
'bundleDependencies',
|
|
186
|
+
'bundledDependencies',
|
|
187
|
+
] as const;
|
|
188
|
+
if (dependencyFields.some((field) => {
|
|
189
|
+
const value = manifest[field];
|
|
190
|
+
return Array.isArray(value)
|
|
191
|
+
? value.length > 0
|
|
192
|
+
: Boolean(value && typeof value === 'object' && Object.keys(value as Record<string, unknown>).length > 0);
|
|
193
|
+
})) return true;
|
|
194
|
+
|
|
195
|
+
const workspaces = manifest.workspaces;
|
|
196
|
+
if (Array.isArray(workspaces)) return workspaces.length > 0;
|
|
197
|
+
if (workspaces && typeof workspaces === 'object') {
|
|
198
|
+
const packages = (workspaces as Record<string, unknown>).packages;
|
|
199
|
+
if (Array.isArray(packages)) return packages.length > 0;
|
|
200
|
+
return Object.keys(workspaces as Record<string, unknown>).length > 0;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function reuseManagedWorkspaceCanonicalDependencies(repoRoot: string): ManagedWorkspaceDependencyPreparation | undefined {
|
|
206
|
+
const nodeModulesRoot = join(repoRoot, 'node_modules');
|
|
207
|
+
const reusable = resolveManagedWorkspaceCanonicalDependencies(repoRoot);
|
|
208
|
+
if (!reusable) return undefined;
|
|
209
|
+
try {
|
|
210
|
+
symlinkSync(reusable.nodeModulesRoot, nodeModulesRoot, process.platform === 'win32' ? 'junction' : 'dir');
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (!existsSync(nodeModulesRoot)) {
|
|
213
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
214
|
+
throw new Error(`MANAGED_WORKSPACE_DEPENDENCY_REUSE_FAILED: ${detail}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
mode: 'canonical_reuse',
|
|
219
|
+
nodeModulesRoot: realpathSync(nodeModulesRoot),
|
|
220
|
+
canonicalRoot: reusable.canonicalRoot,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function materializeManagedWorkspaceDependencies(repoRoot: string): ManagedWorkspaceDependencyPreparation | undefined {
|
|
225
|
+
if (!existsSync(join(repoRoot, 'package.json'))) return undefined;
|
|
226
|
+
const nodeModulesRoot = join(repoRoot, 'node_modules');
|
|
227
|
+
if (existsSync(nodeModulesRoot)) {
|
|
228
|
+
return { mode: 'already_ready', nodeModulesRoot: realpathSync(nodeModulesRoot) };
|
|
229
|
+
}
|
|
230
|
+
// A linked worktree may safely reuse canonical dependencies even when the
|
|
231
|
+
// package manifest itself declares no dependencies. The supported lockfile
|
|
232
|
+
// and dependency-reuse inputs remain the authority for proving equivalence.
|
|
233
|
+
const reused = reuseManagedWorkspaceCanonicalDependencies(repoRoot);
|
|
234
|
+
if (reused) return reused;
|
|
235
|
+
// A package manifest can define dependency-free scripts (for example a
|
|
236
|
+
// built-in `node -e` check). Such checks do not need node_modules and must not
|
|
237
|
+
// be rejected merely because the repository intentionally has no lockfile.
|
|
238
|
+
if (!packageManifestRequiresDependencyMaterialization(repoRoot)) return undefined;
|
|
239
|
+
const bootstrap = managedWorkspaceDependencyBootstrap(repoRoot);
|
|
240
|
+
if (!bootstrap) {
|
|
241
|
+
throw new Error('MANAGED_WORKSPACE_DEPENDENCY_LOCK_REQUIRED: package.json declares dependencies but no supported lockfile was found');
|
|
242
|
+
}
|
|
243
|
+
const childEnv = repositoryChildProcessEnvironment();
|
|
244
|
+
const executable = bootstrap.packageManager === 'bun'
|
|
245
|
+
? resolveBunExecutable(process.execPath, childEnv)
|
|
246
|
+
: bootstrap.command[0]!;
|
|
247
|
+
const result = runProcess(executable, bootstrap.command.slice(1), {
|
|
248
|
+
cwd: repoRoot,
|
|
249
|
+
env: childEnv,
|
|
250
|
+
timeoutMs: 10 * 60_000,
|
|
251
|
+
maxOutputBytes: 4 * 1024 * 1024,
|
|
252
|
+
});
|
|
253
|
+
if (!result.ok) {
|
|
254
|
+
const detail = result.stderr || result.error || `exit ${result.status}`;
|
|
255
|
+
throw new Error(`MANAGED_WORKSPACE_DEPENDENCY_INSTALL_FAILED: ${bootstrap.command.join(' ')}: ${detail}`);
|
|
256
|
+
}
|
|
257
|
+
if (!existsSync(nodeModulesRoot)) {
|
|
258
|
+
throw new Error(`MANAGED_WORKSPACE_DEPENDENCY_INSTALL_INCOMPLETE: ${bootstrap.command.join(' ')} completed without node_modules`);
|
|
259
|
+
}
|
|
260
|
+
return { mode: 'installed', nodeModulesRoot: realpathSync(nodeModulesRoot) };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function materializeManagedWorkspaceCheckDependencies(repoRoot: string): ManagedWorkspaceDependencyPreparation | undefined {
|
|
264
|
+
if (!existsSync(join(repoRoot, 'package.json')) || existsSync(join(repoRoot, 'node_modules'))) return undefined;
|
|
265
|
+
if (!linkedWorkspaceCanonicalRoot(repoRoot)) return undefined;
|
|
266
|
+
return materializeManagedWorkspaceDependencies(repoRoot);
|
|
267
|
+
}
|
|
268
|
+
|
|
76
269
|
function existingWorkspace(path: string, branch: string): boolean {
|
|
77
270
|
if (!existsSync(path)) return false;
|
|
78
271
|
const root = runProcess(resolveGitExecutable(), ['-C', path, 'rev-parse', '--show-toplevel'], {
|
|
@@ -109,6 +302,7 @@ export function ensureManagedWorkspace(
|
|
|
109
302
|
controllerHome: string,
|
|
110
303
|
repository: RepositoryRecord,
|
|
111
304
|
input: EnsureManagedWorkspaceInput,
|
|
305
|
+
dependencies: ManagedWorkspaceDependencies = {},
|
|
112
306
|
): ManagedWorkspace {
|
|
113
307
|
const requestId = input.requestId.trim();
|
|
114
308
|
if (!requestId) throw new Error('MANAGED_WORKSPACE_REQUEST_ID_REQUIRED');
|
|
@@ -159,6 +353,14 @@ export function ensureManagedWorkspace(
|
|
|
159
353
|
if (!repositoryCheckoutRootMatches(repository, path)) {
|
|
160
354
|
throw new Error(`MANAGED_WORKSPACE_REPOSITORY_MISMATCH: ${path}`);
|
|
161
355
|
}
|
|
356
|
+
if (input.prepareDependencies === true) {
|
|
357
|
+
(dependencies.materializeDependencies ?? materializeManagedWorkspaceDependencies)(path);
|
|
358
|
+
} else {
|
|
359
|
+
// Reusing a byte-equivalent canonical dependency tree is cheap and
|
|
360
|
+
// deterministic, so isolated Work checkouts should get it even when a
|
|
361
|
+
// full package-manager install was not explicitly requested.
|
|
362
|
+
reuseManagedWorkspaceCanonicalDependencies(path);
|
|
363
|
+
}
|
|
162
364
|
|
|
163
365
|
const record = addRepositoryCheckout({
|
|
164
366
|
repoId: repository.repoId,
|