@moretea-labs/forge 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -2
- package/CHANGELOG.md +20 -0
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
- package/adapters/chatgpt/provider-delivery.ts +1 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
- package/adapters/computer/chatgpt-extension-target.ts +295 -50
- package/adapters/computer/chrome-extension/background.js +227 -71
- package/adapters/computer/chrome-extension/content.js +181 -157
- package/adapters/computer/chrome-extension/manifest.json +3 -1
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +39 -9
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -6
- package/adapters/mcp/instructions.ts +2 -2
- package/adapters/mcp/multi-repository.ts +7 -28
- package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
- package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
- package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
- package/adapters/mcp/runtime-gateway/router.ts +3 -5
- package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
- package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
- package/adapters/mcp/server.ts +24 -3
- package/adapters/mcp/setup.ts +4 -10
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
- package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
- package/adapters/mcp/toolset-names.ts +11 -42
- package/adapters/mcp/toolset.ts +16 -90
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +18 -6
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +54 -14
- package/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +1 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +114 -30
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +5 -5
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +14 -2
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -54
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +24 -12
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +1 -12
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
- package/packages/kernel/work/application/work-service.ts +1 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +9 -2
- package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
- package/scripts/assemble-template.ts +11 -72
- package/scripts/check-mcp-compatibility.ts +55 -74
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +305 -28
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +3 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +0 -1
- package/scripts/run-governed-gate.ts +2 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +7 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
- package/scripts/sync-generated-authority.mjs +0 -1
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/agent-jobs/integration.ts +2 -2
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +2 -14
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/controller/completion-orchestrator.ts +1 -1
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +2 -2
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +97 -76
- package/src/cli/controller/operational-plan.ts +1 -1
- package/src/cli/controller/systemd-user.ts +4 -10
- package/src/cli/editing/edit-session.ts +17 -5
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +1 -1
- package/src/cli/local-bridge/console-view-models.ts +1 -1
- package/src/cli/local-bridge/facade-api.ts +0 -2
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-scope.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +2 -1
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +39 -3
- package/src/cli/repository/inspector.ts +185 -343
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +7 -0
- package/src/runtime/context/semantic-navigation.ts +1 -1
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
- package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
- package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
- package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
- package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
- package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
- package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
- package/src/runtime/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
- package/src/runtime/execution/process-runtime/runtime.ts +20 -16
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +1 -2
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/plugins/action-application.ts +12 -1
- package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
- package/src/runtime/plugins/browser-adapter.ts +381 -313
- package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
- package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
- package/src/runtime/plugins/browser-registration.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +26 -35
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
- package/src/runtime/plugins/computer-registration.ts +76 -9
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +166 -1
- package/src/runtime/plugins/google-shared.ts +10 -5
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +45 -40
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/local-system-adapter.ts +1 -99
- package/src/runtime/plugins/store.ts +80 -7
- package/src/runtime/plugins/types.ts +4 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/recovery/actions.ts +1 -1
- package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/release/release-coordinator.ts +20 -4
- package/src/runtime/root/controller-progression-composition.ts +67 -5
- package/src/runtime/root/package-connector-service.ts +123 -11
- package/src/runtime/root/package-runtime-release.ts +6 -18
- package/src/runtime/root/package-runtime-service.ts +7 -13
- package/src/runtime/root/release-execution-canary.ts +18 -5
- package/src/runtime/root/release-manifest.ts +0 -34
- package/src/runtime/root/release-materialize.ts +0 -78
- package/src/runtime/root/runtime.ts +1 -2
- package/src/runtime/root/scheduled-controller-composition.ts +2 -1
- package/src/runtime/root/service.ts +90 -11
- package/src/runtime/root/types.ts +0 -6
- package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
- package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
- package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/standalone-recovery/core.ts +189 -174
- package/src/runtime/standalone-recovery/entry.ts +62 -30
- package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
- package/src/runtime/workflow/schedules/engine.ts +36 -14
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
- package/supervisor/client.ts +31 -14
- package/supervisor/control-plane.ts +210 -115
- package/supervisor/forge-validators.ts +88 -21
- package/supervisor/native-browser-adapter.ts +393 -28
- package/supervisor/native-messaging/host.ts +21 -2
- package/supervisor/protocol.ts +74 -26
- package/supervisor/server.ts +16 -8
- package/supervisor/store.ts +476 -57
- package/supervisor/types.ts +19 -13
- package/SKILL.md +0 -427
- package/adapters/computer/chatgpt-target-router.ts +0 -103
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
|
@@ -746,45 +746,70 @@ export function bindControllerSessionToCurrentRuntime(
|
|
|
746
746
|
},
|
|
747
747
|
): ControllerSession {
|
|
748
748
|
assertIdentity(input);
|
|
749
|
-
const current = getControllerSession(options, input.workId);
|
|
750
|
-
if (!current) {
|
|
751
|
-
if (input.allowClaimIfMissing !== true) throw new Error(`WORK_CONTROLLER_OWNER_REQUIRED: ${input.workId}`);
|
|
752
|
-
return claimControllerSession(options, { ...input, expectedClaimGeneration: 0 });
|
|
753
|
-
}
|
|
754
|
-
const currentPrincipal = controllerSessionPrincipalId(current);
|
|
755
|
-
if (current.controllerId !== input.controllerId) {
|
|
756
|
-
throw new Error(`WORK_CONTROLLER_OWNER_MISMATCH: ${input.workId} is owned by ${current.controllerId}`);
|
|
757
|
-
}
|
|
758
|
-
if (current.controllerType !== input.controllerType) {
|
|
759
|
-
throw new Error(`WORK_CONTROLLER_TYPE_MISMATCH: ${input.workId} is owned by ${current.controllerType}`);
|
|
760
|
-
}
|
|
761
|
-
if (currentPrincipal !== input.principalId.trim()) {
|
|
762
|
-
throw new Error(`WORK_CONTROLLER_PRINCIPAL_MISMATCH: ${input.workId}`);
|
|
763
|
-
}
|
|
764
|
-
const ownerInstanceId = current.controllerInstanceId?.trim() || '';
|
|
765
749
|
const requestedInstanceId = input.controllerInstanceId.trim();
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
750
|
+
const currentRuntimeInstanceId = input.currentRuntimeInstanceId?.trim() || '';
|
|
751
|
+
return withControllerSessionMutationLock(
|
|
752
|
+
options,
|
|
753
|
+
input.workId,
|
|
754
|
+
`controller-bind-current-runtime:${input.controllerId}:${input.sessionId}`,
|
|
755
|
+
() => {
|
|
756
|
+
assertControllerWorkClaimable(options, input.workId);
|
|
757
|
+
const store = read(options);
|
|
758
|
+
const current = activeSession(store, input.workId, optionsNowMs(options));
|
|
759
|
+
const retained = current ? undefined : getRetainedControllerSession(options, input.workId);
|
|
760
|
+
const retainedMatchesIdentity = Boolean(
|
|
761
|
+
retained
|
|
762
|
+
&& retained.controllerId === input.controllerId
|
|
763
|
+
&& retained.controllerType === input.controllerType
|
|
764
|
+
&& controllerSessionPrincipalId(retained) === input.principalId.trim(),
|
|
765
|
+
);
|
|
766
|
+
const previous = current ?? (input.allowClaimIfMissing === true && retainedMatchesIdentity ? retained : undefined);
|
|
767
|
+
|
|
768
|
+
if (!current && !previous && input.allowClaimIfMissing !== true) {
|
|
769
|
+
throw new Error(`WORK_CONTROLLER_OWNER_REQUIRED: ${input.workId}`);
|
|
770
|
+
}
|
|
771
|
+
if (!current && retainedMatchesIdentity) {
|
|
772
|
+
if (!currentRuntimeInstanceId || currentRuntimeInstanceId !== requestedInstanceId) {
|
|
773
|
+
throw new Error(`WORK_CONTROLLER_INSTANCE_MISMATCH: ${input.workId}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (current) {
|
|
778
|
+
const currentPrincipal = controllerSessionPrincipalId(current);
|
|
779
|
+
if (current.controllerId !== input.controllerId) {
|
|
780
|
+
throw new Error(`WORK_CONTROLLER_OWNER_MISMATCH: ${input.workId} is owned by ${current.controllerId}`);
|
|
781
|
+
}
|
|
782
|
+
if (current.controllerType !== input.controllerType) {
|
|
783
|
+
throw new Error(`WORK_CONTROLLER_TYPE_MISMATCH: ${input.workId} is owned by ${current.controllerType}`);
|
|
784
|
+
}
|
|
785
|
+
if (currentPrincipal !== input.principalId.trim()) {
|
|
786
|
+
throw new Error(`WORK_CONTROLLER_PRINCIPAL_MISMATCH: ${input.workId}`);
|
|
787
|
+
}
|
|
788
|
+
const ownerInstanceId = current.controllerInstanceId?.trim() || '';
|
|
789
|
+
if (ownerInstanceId !== requestedInstanceId) {
|
|
790
|
+
if (!currentRuntimeInstanceId || currentRuntimeInstanceId !== requestedInstanceId) {
|
|
791
|
+
throw new Error(`WORK_CONTROLLER_INSTANCE_MISMATCH: ${input.workId}`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const requestedAuthorityDigest = input.authorityDigest?.trim() || '';
|
|
795
|
+
const currentAuthorityDigest = current.authorityDigest?.trim() || '';
|
|
796
|
+
if (
|
|
797
|
+
ownerInstanceId === requestedInstanceId
|
|
798
|
+
&& current.sessionId === input.sessionId
|
|
799
|
+
&& (!requestedAuthorityDigest || requestedAuthorityDigest === currentAuthorityDigest)
|
|
800
|
+
) return current;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// The opaque Work capability belongs to durable ownership, not to one
|
|
804
|
+
// transport or Runtime process. A positively-current Runtime migration keeps
|
|
805
|
+
// the same digest while claim generation fences the new controller epoch.
|
|
806
|
+
const preservedAuthorityDigest = input.authorityDigest?.trim() || previous?.authorityDigest?.trim() || undefined;
|
|
807
|
+
return persistClaim(options, store, {
|
|
808
|
+
...input,
|
|
809
|
+
...(preservedAuthorityDigest ? { authorityDigest: preservedAuthorityDigest } : {}),
|
|
810
|
+
}, previous);
|
|
811
|
+
},
|
|
812
|
+
);
|
|
788
813
|
}
|
|
789
814
|
|
|
790
815
|
export function releaseControllerSession(options: ControllerSessionStoreOptions, workId: string, controllerId: string): void {
|
|
@@ -36,6 +36,16 @@ export function recordDirectActivity(
|
|
|
36
36
|
return item;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Read one existing activity by its stable identity, independent of list-window truncation. */
|
|
40
|
+
export function getDirectActivity(controllerHome: string, activityId: string): CapabilityActivityItem | undefined {
|
|
41
|
+
try {
|
|
42
|
+
const item = JSON.parse(readFileSync(activityFilePath(controllerHome, activityId), 'utf8')) as CapabilityActivityItem;
|
|
43
|
+
return item.activityId === activityId ? item : undefined;
|
|
44
|
+
} catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
export function listDirectActivities(controllerHome: string, limit = 50): CapabilityActivityItem[] {
|
|
40
50
|
const dir = activityDir(controllerHome);
|
|
41
51
|
const files = readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
@@ -122,7 +122,10 @@ function cronFieldMatches(value: number, field: string, min: number, max: number
|
|
|
122
122
|
const trimmed = part.trim();
|
|
123
123
|
if (trimmed === '*') return true;
|
|
124
124
|
const stepMatch = trimmed.match(/^\*\/(\d+)$/);
|
|
125
|
-
if (stepMatch)
|
|
125
|
+
if (stepMatch) {
|
|
126
|
+
const step = Math.max(1, Number(stepMatch[1]));
|
|
127
|
+
return value >= min && value <= max && (value - min) % step === 0;
|
|
128
|
+
}
|
|
126
129
|
const rangeMatch = trimmed.match(/^(\d+)-(\d+)(?:\/(\d+))?$/);
|
|
127
130
|
if (rangeMatch) {
|
|
128
131
|
const start = Math.max(min, Number(rangeMatch[1]));
|
|
@@ -101,9 +101,9 @@ function requestPath(controllerHome: string, targetKey: string, requestId: strin
|
|
|
101
101
|
export const SCHEDULE_OCCURRENCE_RECENT_LIMIT = 5_000;
|
|
102
102
|
export const SCHEDULE_CREATED_OCCURRENCE_STALE_MS = 5 * 60 * 1_000;
|
|
103
103
|
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
104
|
+
const WORK_CONTINUATION_MAX_FAILURES = 3;
|
|
105
|
+
const WORK_CONTINUATION_DAILY_BUDGET_MINUTES = 60;
|
|
106
|
+
const WORK_CONTINUATION_MIN_COOLDOWN_MINUTES = 10;
|
|
107
107
|
|
|
108
108
|
function boundedSchedulePolicy(schedule: RepositorySchedule): RepositorySchedule['policy'] {
|
|
109
109
|
const policy = {
|
|
@@ -111,17 +111,18 @@ function boundedSchedulePolicy(schedule: RepositorySchedule): RepositorySchedule
|
|
|
111
111
|
backoffBaseMinutes: Math.max(1, schedule.policy.backoffBaseMinutes ?? schedule.policy.cooldownMinutes ?? 1),
|
|
112
112
|
backoffMaxMinutes: Math.max(1, schedule.policy.backoffMaxMinutes ?? 24 * 60),
|
|
113
113
|
};
|
|
114
|
-
if (schedule.action.operation !== 'external_controller_wake') return policy;
|
|
115
|
-
// A Work-bound
|
|
114
|
+
if (schedule.action.operation !== 'external_controller_wake' && schedule.action.operation !== 'controller_progression') return policy;
|
|
115
|
+
// A Work-bound continuation is expensive and can affect the user's
|
|
116
116
|
// provider. No schedule payload may convert a transient tool outage into an
|
|
117
117
|
// all-day retry loop. Larger work needs an explicit future user action after
|
|
118
118
|
// the bounded lane records useful failure evidence.
|
|
119
119
|
return {
|
|
120
120
|
...policy,
|
|
121
|
-
maxFailures: Math.max(1, Math.min(policy.maxFailures,
|
|
122
|
-
dailyBudgetMinutes: Math.max(1, Math.min(policy.dailyBudgetMinutes,
|
|
123
|
-
cooldownMinutes: Math.max(policy.cooldownMinutes,
|
|
124
|
-
backoffBaseMinutes: Math.max(policy.backoffBaseMinutes,
|
|
121
|
+
maxFailures: Math.max(1, Math.min(policy.maxFailures, WORK_CONTINUATION_MAX_FAILURES)),
|
|
122
|
+
dailyBudgetMinutes: Math.max(1, Math.min(policy.dailyBudgetMinutes, WORK_CONTINUATION_DAILY_BUDGET_MINUTES)),
|
|
123
|
+
cooldownMinutes: Math.max(policy.cooldownMinutes, WORK_CONTINUATION_MIN_COOLDOWN_MINUTES),
|
|
124
|
+
backoffBaseMinutes: Math.max(policy.backoffBaseMinutes, WORK_CONTINUATION_MIN_COOLDOWN_MINUTES),
|
|
125
|
+
backoffMaxMinutes: Math.max(policy.backoffMaxMinutes, WORK_CONTINUATION_MIN_COOLDOWN_MINUTES, policy.backoffBaseMinutes),
|
|
125
126
|
};
|
|
126
127
|
}
|
|
127
128
|
function canonical(value: unknown): unknown {
|
|
@@ -175,7 +176,7 @@ export function createSchedule(controllerHome: string, input: CreateScheduleInpu
|
|
|
175
176
|
return getSchedule(controllerHome, input.repoId, record.scheduleId);
|
|
176
177
|
}
|
|
177
178
|
const timestamp = new Date().toISOString();
|
|
178
|
-
const
|
|
179
|
+
const candidate: RepositorySchedule = {
|
|
179
180
|
...input,
|
|
180
181
|
requestId,
|
|
181
182
|
schemaVersion: 1,
|
|
@@ -185,6 +186,7 @@ export function createSchedule(controllerHome: string, input: CreateScheduleInpu
|
|
|
185
186
|
updatedAt: timestamp,
|
|
186
187
|
consecutiveFailures: 0,
|
|
187
188
|
};
|
|
189
|
+
const schedule: RepositorySchedule = { ...candidate, policy: boundedSchedulePolicy(candidate) };
|
|
188
190
|
writeJsonAtomic(schedulePath(controllerHome, schedule.repoId, schedule.scheduleId), schedule);
|
|
189
191
|
writeJsonAtomic(requestRecordPath, { schemaVersion: 1, requestId, semanticKey, scheduleId: schedule.scheduleId, repoId: schedule.repoId, createdAt: timestamp } satisfies ScheduleRequestRecord);
|
|
190
192
|
appendRuntimeEvent(controllerHome, { repoId: schedule.repoId, entityType: 'schedule', entityId: schedule.scheduleId, eventType: 'schedule_created', requestId, revision: schedule.revision, data: { name: schedule.name, shadowMode: schedule.policy.shadowMode } });
|
|
@@ -228,6 +230,7 @@ function persistScheduleRevision(
|
|
|
228
230
|
requestId: current.requestId,
|
|
229
231
|
createdAt: current.createdAt,
|
|
230
232
|
updatedAt: new Date().toISOString(),
|
|
233
|
+
policy: boundedSchedulePolicy(candidate),
|
|
231
234
|
};
|
|
232
235
|
writeJsonAtomic(schedulePath(controllerHome, next.repoId, next.scheduleId), next);
|
|
233
236
|
appendRuntimeEvent(controllerHome, { repoId: next.repoId, entityType: 'schedule', entityId: next.scheduleId, eventType: 'schedule_updated', requestId: next.requestId, revision: next.revision, data: { enabled: next.enabled, pausedReason: next.pausedReason } });
|
|
@@ -55,7 +55,7 @@ export interface RepositoryCompletionReceipt {
|
|
|
55
55
|
delivery: {
|
|
56
56
|
kind: CompletionReceiptDeliveryKind;
|
|
57
57
|
status: CompletionReceiptDeliveryStatus;
|
|
58
|
-
strategy: 'edit_session_commit' | 'work_fast_forward' | 'already_integrated' | 'no_change' | 'remote';
|
|
58
|
+
strategy: 'edit_session_commit' | 'work_fast_forward' | 'work_merge_commit' | 'already_integrated' | 'no_change' | 'remote';
|
|
59
59
|
reachable: boolean;
|
|
60
60
|
recordedAt: string;
|
|
61
61
|
};
|
|
@@ -135,8 +135,15 @@ export function validateWorkSemantics(contract: WorkContract): WorkContract {
|
|
|
135
135
|
if (outcome === 'completed_changed' && contract.workKind !== 'repository_change') {
|
|
136
136
|
throw new Error('WORK_SEMANTICS_INVALID: completed_changed requires repository_change WorkKind');
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
// An empty repository delivery is evidence, not authority to rewrite WorkKind.
|
|
139
|
+
const emptyRepositoryDelivery = contract.workKind === 'repository_change'
|
|
140
|
+
&& isRepositoryCompletionReceipt(contract.completionReceipt)
|
|
141
|
+
&& contract.completionReceipt.delivery.kind === 'no_change'
|
|
142
|
+
&& contract.completionReceipt.changedPaths.length === 0;
|
|
143
|
+
if (outcome === 'completed_no_change'
|
|
144
|
+
&& !['completed_no_change', 'read_only_review'].includes(contract.workKind)
|
|
145
|
+
&& !emptyRepositoryDelivery) {
|
|
146
|
+
throw new Error('WORK_SEMANTICS_INVALID: completed_no_change requires a no-change WorkKind or proven empty repository delivery');
|
|
140
147
|
}
|
|
141
148
|
if (outcome === 'completed_no_change' && contract.workKind === 'read_only_review' && contract.completionReceipt?.source !== 'read_only_review') {
|
|
142
149
|
throw new Error('WORK_SEMANTICS_INVALID: read_only_review completed_no_change requires a read_only_review completion receipt');
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
readControlPlaneRecordWithinTransaction,
|
|
15
15
|
withControlPlaneTransaction,
|
|
16
16
|
writeControlPlaneRecordWithinTransaction,
|
|
17
|
+
type SqliteDatabase,
|
|
17
18
|
} from '../../../../src/runtime/control-plane/persistence/sqlite-store';
|
|
18
19
|
import {
|
|
19
20
|
MAX_IMPLEMENTATION_REVIEW_HISTORY,
|
|
@@ -97,6 +98,8 @@ export type CreateWorkContractInput = Omit<
|
|
|
97
98
|
export interface ListWorkContractOptions extends WorkContractStoreOptions {
|
|
98
99
|
state?: SemanticWorkState | 'active' | 'all';
|
|
99
100
|
limit?: number;
|
|
101
|
+
/** Explicit pagination for complete internal historical inventory. */
|
|
102
|
+
offset?: number;
|
|
100
103
|
detailLevel?: 'summary' | 'detail' | 'raw';
|
|
101
104
|
}
|
|
102
105
|
|
|
@@ -121,6 +124,9 @@ export interface WorkContractLineageSnapshot {
|
|
|
121
124
|
contracts: WorkContract[];
|
|
122
125
|
invalid: Array<{
|
|
123
126
|
workId: string;
|
|
127
|
+
requirementId?: string;
|
|
128
|
+
semanticParentWorkId?: string;
|
|
129
|
+
dependsOnWorkIds?: string[];
|
|
124
130
|
parentWorkId?: string;
|
|
125
131
|
predecessorWorkId?: string;
|
|
126
132
|
supersedes: string[];
|
|
@@ -640,6 +646,9 @@ function lineageInvalidWork(
|
|
|
640
646
|
const clean = (id: unknown): string | undefined => typeof id === 'string' && id.trim() ? id.trim() : undefined;
|
|
641
647
|
return {
|
|
642
648
|
workId: clean(raw.workId) ?? recordKey ?? '',
|
|
649
|
+
...(clean(raw.requirementId) ? { requirementId: clean(raw.requirementId) } : {}),
|
|
650
|
+
...(clean(raw.semanticParentWorkId) ? { semanticParentWorkId: clean(raw.semanticParentWorkId) } : {}),
|
|
651
|
+
dependsOnWorkIds: Array.isArray(raw.dependsOnWorkIds) ? raw.dependsOnWorkIds.map(clean).filter((id): id is string => Boolean(id)) : [],
|
|
643
652
|
...(clean(raw.parentWorkId) ? { parentWorkId: clean(raw.parentWorkId) } : {}),
|
|
644
653
|
...(clean(raw.predecessorWorkId) ? { predecessorWorkId: clean(raw.predecessorWorkId) } : {}),
|
|
645
654
|
supersedes: Array.isArray(raw.supersedes) ? raw.supersedes.map(clean).filter((id): id is string => Boolean(id)) : [],
|
|
@@ -990,6 +999,8 @@ export function isCurrentWorkContract(contract: WorkContract): boolean {
|
|
|
990
999
|
export function listWorkContracts(options: ListWorkContractOptions): WorkContract[] {
|
|
991
1000
|
const state = options.state ?? 'active';
|
|
992
1001
|
const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 50), 100));
|
|
1002
|
+
const offset = Math.max(0, Math.trunc(options.offset ?? 0));
|
|
1003
|
+
if (state === 'active' && offset > 0) throw new Error('WORK_ACTIVE_PAGINATION_UNSUPPORTED');
|
|
993
1004
|
if (state === 'active' && sqliteBacked(options)) {
|
|
994
1005
|
const active = readActiveWorkCandidates({ ...options, limit });
|
|
995
1006
|
if (active.invalid.length > 0) {
|
|
@@ -1008,7 +1019,7 @@ export function listWorkContracts(options: ListWorkContractOptions): WorkContrac
|
|
|
1008
1019
|
return semanticWorkState(contract) === state;
|
|
1009
1020
|
})
|
|
1010
1021
|
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
1011
|
-
.slice(
|
|
1022
|
+
.slice(offset, offset + limit);
|
|
1012
1023
|
}
|
|
1013
1024
|
|
|
1014
1025
|
function workExecutionSemanticScopeKeys(contract: WorkContract): string[] {
|
|
@@ -1414,6 +1425,7 @@ function updateWorkContractInternal(
|
|
|
1414
1425
|
allowLifecycleWrite = false,
|
|
1415
1426
|
allowImplementationReviewWrite = false,
|
|
1416
1427
|
allowPhaseRegression = false,
|
|
1428
|
+
atomicPlacementEffect?: (database: SqliteDatabase, contract: WorkContract) => void,
|
|
1417
1429
|
): WorkContract {
|
|
1418
1430
|
return withExactWorkContractWrite(options, workId, () => {
|
|
1419
1431
|
const authoritativeOptions = authoritativeExistingWorkStoreOptions(options, workId);
|
|
@@ -1517,9 +1529,11 @@ function updateWorkContractInternal(
|
|
|
1517
1529
|
action: 'work_contract_updated',
|
|
1518
1530
|
expectedRevision: exact.revision,
|
|
1519
1531
|
});
|
|
1532
|
+
atomicPlacementEffect?.(database, next);
|
|
1520
1533
|
});
|
|
1521
1534
|
return next;
|
|
1522
1535
|
}
|
|
1536
|
+
if (atomicPlacementEffect) throw new Error('WORK_PLACEMENT_ATOMIC_SQLITE_REQUIRED');
|
|
1523
1537
|
const contracts = [...store!.contracts];
|
|
1524
1538
|
contracts[index] = next;
|
|
1525
1539
|
writeWorkContractStore(authoritativeOptions, { schemaVersion: 4, updatedAt: at, contracts });
|
|
@@ -1604,6 +1618,19 @@ export function updateWorkContract(
|
|
|
1604
1618
|
): WorkContract {
|
|
1605
1619
|
return updateWorkContractInternal(options, workId, patch, false);
|
|
1606
1620
|
}
|
|
1621
|
+
export function updateWorkContractPlacementAtomically(
|
|
1622
|
+
options: WorkContractStoreOptions,
|
|
1623
|
+
workId: string,
|
|
1624
|
+
patch: Pick<WorkContractMetadataPatch, 'checkoutId' | 'worktreeRef' | 'executionPlacement' | 'worktreePolicy'>,
|
|
1625
|
+
effect: (database: SqliteDatabase, contract: WorkContract) => void,
|
|
1626
|
+
): WorkContract {
|
|
1627
|
+
// The WorkContract writer remains the sole semantic metadata authority.
|
|
1628
|
+
// An execution WorkHandle is committed inside the same SQLite transaction,
|
|
1629
|
+
// never as a second step that could leave conflicting checkout ownership.
|
|
1630
|
+
return updateWorkContractInternal(options, workId, patch, false, false, false, false, effect);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
|
|
1607
1634
|
|
|
1608
1635
|
/** Explicit execution-classification transition when an open Work begins governed repository mutation. */
|
|
1609
1636
|
export function promoteWorkToRepositoryChange(
|
|
@@ -6,8 +6,6 @@ import {
|
|
|
6
6
|
} from './runtime-contract';
|
|
7
7
|
|
|
8
8
|
export interface BrowserRuntimeProvider<TInput, TResult> extends BrowserProviderCapabilities {
|
|
9
|
-
/** Lower values are preferred when multiple providers satisfy the same capability contract. */
|
|
10
|
-
priority: number;
|
|
11
9
|
/** Availability is decided before execution; ordinary provider failures are never fallback signals. */
|
|
12
10
|
supportsInput(input: TInput): boolean;
|
|
13
11
|
execute(input: TInput): Promise<TResult>;
|
|
@@ -37,22 +35,6 @@ export class BrowserProviderSelectionError extends Error {
|
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
/**
|
|
41
|
-
* A typed pre-action rejection. Runtime may select another provider because this proves the action
|
|
42
|
-
* was not attempted. Arbitrary provider errors are intentionally not mapped to this signal.
|
|
43
|
-
*/
|
|
44
|
-
export class BrowserProviderUnavailableBeforeActionError extends Error {
|
|
45
|
-
readonly providerId: string;
|
|
46
|
-
readonly reason: string;
|
|
47
|
-
|
|
48
|
-
constructor(providerId: string, reason: string) {
|
|
49
|
-
super(`${providerId} unavailable before action: ${reason}`);
|
|
50
|
-
this.name = 'BrowserProviderUnavailableBeforeActionError';
|
|
51
|
-
this.providerId = providerId;
|
|
52
|
-
this.reason = reason;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
38
|
function foregroundCompatible(
|
|
57
39
|
provider: BrowserProviderCapabilities,
|
|
58
40
|
requested: BrowserForegroundRequirement,
|
|
@@ -61,24 +43,23 @@ function foregroundCompatible(
|
|
|
61
43
|
}
|
|
62
44
|
|
|
63
45
|
export class BrowserProviderRegistry<TInput, TResult> {
|
|
64
|
-
private
|
|
46
|
+
private state?: BrowserProviderState<TInput, TResult>;
|
|
65
47
|
|
|
66
48
|
register(provider: BrowserRuntimeProvider<TInput, TResult>): void {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
current.provider = provider;
|
|
49
|
+
if (this.state?.provider.providerId === provider.providerId) {
|
|
50
|
+
this.state.provider = provider;
|
|
70
51
|
return;
|
|
71
52
|
}
|
|
72
|
-
|
|
53
|
+
// A configuration change replaces the binding; it never accumulates candidates.
|
|
54
|
+
this.state = { provider, generation: 1 };
|
|
73
55
|
}
|
|
74
56
|
|
|
75
57
|
invalidate(providerId: string, reason: string): void {
|
|
76
|
-
|
|
77
|
-
if (state) state.invalidatedReason = reason;
|
|
58
|
+
if (this.state?.provider.providerId === providerId) this.state.invalidatedReason = reason;
|
|
78
59
|
}
|
|
79
60
|
|
|
80
61
|
invalidateAll(reason: string): void {
|
|
81
|
-
|
|
62
|
+
if (this.state) this.state.invalidatedReason = reason;
|
|
82
63
|
}
|
|
83
64
|
|
|
84
65
|
private async prepare(state: BrowserProviderState<TInput, TResult>): Promise<void> {
|
|
@@ -92,17 +73,12 @@ export class BrowserProviderRegistry<TInput, TResult> {
|
|
|
92
73
|
input: TInput;
|
|
93
74
|
requiredCapabilities: readonly BrowserProviderCapability[];
|
|
94
75
|
foreground: BrowserForegroundRequirement;
|
|
95
|
-
excludedProviderIds?: ReadonlySet<string>;
|
|
96
76
|
}): Promise<BrowserProviderSelection<TInput, TResult>> {
|
|
97
|
-
const
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
|
|
101
|
-
.
|
|
102
|
-
.sort((left, right) => left.provider.priority - right.provider.priority
|
|
103
|
-
|| left.provider.providerId.localeCompare(right.provider.providerId));
|
|
104
|
-
|
|
105
|
-
const selected = candidates[0];
|
|
77
|
+
const selected = this.state
|
|
78
|
+
&& this.state.provider.supportsInput(options.input)
|
|
79
|
+
&& providerSupports(this.state.provider, options.requiredCapabilities)
|
|
80
|
+
&& foregroundCompatible(this.state.provider, options.foreground)
|
|
81
|
+
? this.state : undefined;
|
|
106
82
|
if (!selected) throw new BrowserProviderSelectionError(options.requiredCapabilities);
|
|
107
83
|
await this.prepare(selected);
|
|
108
84
|
return { provider: selected.provider, generation: selected.generation };
|
|
@@ -22,6 +22,10 @@ export interface ComputerChatgptConversationObservation {
|
|
|
22
22
|
userMessages?: string[];
|
|
23
23
|
assistantMessages?: string[];
|
|
24
24
|
composerText?: string;
|
|
25
|
+
/** Read-only content-script selector attestation; undefined on older loaded scripts. */
|
|
26
|
+
composerSelectorVersion?: 'visible-single-v2';
|
|
27
|
+
/** Exact loaded content-script contract; file projection/version on disk is not a live browser attestation. */
|
|
28
|
+
composerReplacementContract?: 'exact-backed-up-project-draft-v1';
|
|
25
29
|
providerActivityText: string;
|
|
26
30
|
providerFailureText: string;
|
|
27
31
|
pageText?: string;
|
|
@@ -44,13 +48,13 @@ export interface ComputerChatgptConversationTarget {
|
|
|
44
48
|
identity: ComputerChatgptTargetIdentity;
|
|
45
49
|
observe(options?: ComputerChatgptConversationObservationOptions): Promise<ComputerChatgptConversationObservation>;
|
|
46
50
|
/** UI-only preparation. Must never click Send or create a provider turn. */
|
|
47
|
-
prepare(prompt: string, options?: { mode?: 'send' | 'resume' | 'recover'
|
|
51
|
+
prepare(prompt: string, options?: { mode?: 'send' | 'resume' | 'recover' }): Promise<{ ready: true } | { ready: false; reasonCode: string }>;
|
|
48
52
|
dispatch(
|
|
49
53
|
prompt: string,
|
|
50
|
-
options?: { mode?: 'send' | 'resume' | 'recover'
|
|
54
|
+
options?: { mode?: 'send' | 'resume' | 'recover' },
|
|
51
55
|
): Promise<
|
|
52
56
|
| { mutation: 'not_attempted'; reasonCode: string }
|
|
53
|
-
| { mutation: 'attempted'; confirmed?: boolean
|
|
57
|
+
| { mutation: 'attempted'; confirmed?: boolean }
|
|
54
58
|
>;
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -79,7 +83,10 @@ export interface ComputerObservedChatgptConversation {
|
|
|
79
83
|
|
|
80
84
|
export interface ComputerChatgptConversationInventory {
|
|
81
85
|
conversations: ComputerObservedChatgptConversation[];
|
|
86
|
+
/** Full coverage of all supported browser families, not only active providers. */
|
|
82
87
|
complete: boolean;
|
|
88
|
+
/** At least one provider completed its own inventory; not exact-target/send proof. */
|
|
89
|
+
providerInventoryReady?: boolean;
|
|
83
90
|
unavailableProviders: string[];
|
|
84
91
|
}
|
|
85
92
|
|
|
@@ -91,7 +98,7 @@ export interface ComputerChatgptConversationInventory {
|
|
|
91
98
|
export type ComputerChatgptExtensionCommand =
|
|
92
99
|
| { commandId: string; kind: 'ensure'; identity: ComputerChatgptTargetIdentity; providerBinding?: ComputerSurfaceProviderBinding }
|
|
93
100
|
| { commandId: string; kind: 'observe'; identity: ComputerChatgptTargetIdentity; providerBinding?: ComputerSurfaceProviderBinding; options?: ComputerChatgptConversationObservationOptions }
|
|
94
|
-
| { commandId: string; kind: 'dispatch' | 'prepare'; identity: ComputerChatgptTargetIdentity; providerBinding?: ComputerSurfaceProviderBinding; prompt: string; mode?: 'send' | 'resume' | 'recover'
|
|
101
|
+
| { commandId: string; kind: 'dispatch' | 'prepare'; identity: ComputerChatgptTargetIdentity; providerBinding?: ComputerSurfaceProviderBinding; prompt: string; mode?: 'send' | 'resume' | 'recover' }
|
|
95
102
|
| { commandId: string; kind: 'find_marker'; marker: string; bootstrapKey?: string }
|
|
96
103
|
| { commandId: string; kind: 'close'; identity: ComputerChatgptTargetIdentity; providerBinding?: ComputerSurfaceProviderBinding };
|
|
97
104
|
export type ComputerChatgptExtensionCommandInput = ComputerChatgptExtensionCommand extends infer Command
|
|
@@ -107,10 +114,12 @@ export type ComputerChatgptExtensionCommandResult =
|
|
|
107
114
|
| { kind: 'prepared'; ready: true }
|
|
108
115
|
| { kind: 'prepared'; ready: false; reasonCode: string }
|
|
109
116
|
| { kind: 'dispatch'; mutation: 'not_attempted'; reasonCode: string }
|
|
110
|
-
| { kind: 'dispatch'; mutation: 'attempted'; confirmed?: boolean;
|
|
117
|
+
| { kind: 'dispatch'; mutation: 'attempted'; confirmed?: boolean; observation?: ComputerChatgptConversationObservation }
|
|
111
118
|
| { kind: 'marker_matches'; matches: Array<{ identity: ComputerChatgptConversationIdentity; providerBinding?: ComputerSurfaceProviderBinding; observation: ComputerChatgptConversationObservation }> }
|
|
112
119
|
| { kind: 'closed' }
|
|
113
|
-
| { kind: 'failed'; code: string; retryable?: boolean; failoverSafe?: boolean; humanAction?: 'login' | 'grant_permission'
|
|
120
|
+
| { kind: 'failed'; code: string; retryable?: boolean; failoverSafe?: boolean; humanAction?: 'login' | 'grant_permission';
|
|
121
|
+
/** Exact attachment retained after ensure hydration failed; never Send/readiness authority. */
|
|
122
|
+
providerBinding?: ComputerSurfaceProviderBinding };
|
|
114
123
|
|
|
115
124
|
export interface ComputerChatgptExtensionHeartbeat {
|
|
116
125
|
providerId: string;
|
|
@@ -128,7 +137,8 @@ export interface ComputerChatgptExtensionBrokerRpc {
|
|
|
128
137
|
|
|
129
138
|
export interface ComputerChatgptConversationTargetPort {
|
|
130
139
|
inventory(): Promise<ComputerChatgptConversationInventory>;
|
|
131
|
-
|
|
140
|
+
/** Read-only reconciliation cannot create a missing tab. Only a separately authorized new send may opt in to exact conversation creation. */
|
|
141
|
+
ensureExact(identity: ComputerChatgptConversationIdentity, options?: { createIfMissing?: boolean }): Promise<ComputerChatgptTargetResult>;
|
|
132
142
|
openBootstrap(projectUrl: string, bootstrapKey: string): Promise<ComputerChatgptTargetResult>;
|
|
133
143
|
findBySubmittedMarker(
|
|
134
144
|
marker: string,
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
10
10
|
import { join, dirname } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
|
-
import { runHooks } from "./run-skill-hook";
|
|
13
12
|
|
|
14
13
|
// ============================================================================
|
|
15
14
|
// Types
|
|
@@ -64,23 +63,6 @@ export interface PlanMap {
|
|
|
64
63
|
plans: Record<string, PlanConfig>;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
export interface SkillVersionManifest {
|
|
68
|
-
version: string;
|
|
69
|
-
templateVersion: string;
|
|
70
|
-
compatibility: {
|
|
71
|
-
minClaudeCodeVersion: string;
|
|
72
|
-
minBunVersion: string;
|
|
73
|
-
};
|
|
74
|
-
breakingChanges: Array<{
|
|
75
|
-
version: string;
|
|
76
|
-
description: string;
|
|
77
|
-
}>;
|
|
78
|
-
generatedProjectStamp: {
|
|
79
|
-
format: string;
|
|
80
|
-
location: string;
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
66
|
interface RuntimeProfileConfig {
|
|
85
67
|
label: string;
|
|
86
68
|
claudePolicy: string;
|
|
@@ -134,7 +116,6 @@ const ASSETS_DIR = join(REPO_ROOT, "assets");
|
|
|
134
116
|
const PARTIALS_DIR = join(ASSETS_DIR, "partials");
|
|
135
117
|
const PARTIALS_AGENTS_DIR = join(ASSETS_DIR, "partials-agents");
|
|
136
118
|
const VERSIONS_FILE = join(ASSETS_DIR, "versions.json");
|
|
137
|
-
const SKILL_VERSION_FILE = join(ASSETS_DIR, "skill-version.json");
|
|
138
119
|
const PLAN_MAP_FILE = join(ASSETS_DIR, "plan-map.json");
|
|
139
120
|
const QUESTION_PACK_FILE = join(ASSETS_DIR, "initializer-question-pack.v4.json");
|
|
140
121
|
|
|
@@ -276,31 +257,14 @@ export function loadVersions(versionsFilePath: string = VERSIONS_FILE): Record<s
|
|
|
276
257
|
return versions;
|
|
277
258
|
}
|
|
278
259
|
|
|
279
|
-
/**
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (!existsSync(skillVersionFilePath)) {
|
|
286
|
-
throw new Error(`skill-version.json not found at ${skillVersionFilePath}`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
let parsed: unknown;
|
|
290
|
-
try {
|
|
291
|
-
parsed = JSON.parse(readFileSync(skillVersionFilePath, "utf-8"));
|
|
292
|
-
} catch (error) {
|
|
293
|
-
throw new Error(
|
|
294
|
-
`Failed to parse skill-version.json: ${error instanceof Error ? error.message : String(error)}`
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const manifest = parsed as SkillVersionManifest;
|
|
299
|
-
if (!manifest.version || !manifest.templateVersion) {
|
|
300
|
-
throw new Error("skill-version.json missing required version or templateVersion field");
|
|
260
|
+
/** Read the Forge release version from its single canonical package manifest. */
|
|
261
|
+
export function loadForgeVersion(): string {
|
|
262
|
+
const pkgPath = join(ASSETS_DIR, "..", "package.json");
|
|
263
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { version?: string };
|
|
264
|
+
if (typeof pkg.version !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(pkg.version)) {
|
|
265
|
+
throw new Error("Invalid Forge package version in " + pkgPath);
|
|
301
266
|
}
|
|
302
|
-
|
|
303
|
-
return manifest;
|
|
267
|
+
return pkg.version;
|
|
304
268
|
}
|
|
305
269
|
|
|
306
270
|
/**
|
|
@@ -935,7 +899,7 @@ export function assembleTemplate(options: AssemblyOptions): string {
|
|
|
935
899
|
|
|
936
900
|
// Load version variables
|
|
937
901
|
const versions = loadVersions();
|
|
938
|
-
const
|
|
902
|
+
const forgeVersion = loadForgeVersion();
|
|
939
903
|
|
|
940
904
|
// Merge all variables (user variables take precedence)
|
|
941
905
|
const mergedVariables: Record<string, string> = {
|
|
@@ -943,8 +907,8 @@ export function assembleTemplate(options: AssemblyOptions): string {
|
|
|
943
907
|
...getDefaultTemplateVariables(resolvedPlanType, planMap, quickMode),
|
|
944
908
|
...variables,
|
|
945
909
|
PLAN_TYPE: resolvedPlanType,
|
|
946
|
-
|
|
947
|
-
TEMPLATE_VERSION:
|
|
910
|
+
FORGE_VERSION: forgeVersion,
|
|
911
|
+
TEMPLATE_VERSION: forgeVersion,
|
|
948
912
|
};
|
|
949
913
|
|
|
950
914
|
const webappRenderingModel = getWebappRenderingTemplateVariables(
|
|
@@ -1017,31 +981,6 @@ export function assembleTemplate(options: AssemblyOptions): string {
|
|
|
1017
981
|
return assembled;
|
|
1018
982
|
}
|
|
1019
983
|
|
|
1020
|
-
/**
|
|
1021
|
-
* Async wrapper that runs lifecycle hooks around assembleTemplate().
|
|
1022
|
-
* Use this from CLI or scripts that support async. Tests can use sync assembleTemplate().
|
|
1023
|
-
*/
|
|
1024
|
-
export async function assembleTemplateWithHooks(options: AssemblyOptions): Promise<string> {
|
|
1025
|
-
const hookContext = {
|
|
1026
|
-
planType: options.planType,
|
|
1027
|
-
target: options.target ?? "claude",
|
|
1028
|
-
quickMode: options.quickMode ?? false,
|
|
1029
|
-
};
|
|
1030
|
-
|
|
1031
|
-
// Pre-assemble hook (sync — failure aborts)
|
|
1032
|
-
const preResult = await runHooks("pre-assemble", hookContext);
|
|
1033
|
-
if (!preResult.success) {
|
|
1034
|
-
throw new Error("pre-assemble hook failed, aborting assembly");
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
const output = assembleTemplate(options);
|
|
1038
|
-
|
|
1039
|
-
// Post-assemble hook (advisory — failure is warning only)
|
|
1040
|
-
await runHooks("post-assemble", { ...hookContext, outputLength: output.length });
|
|
1041
|
-
|
|
1042
|
-
return output;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
984
|
// ============================================================================
|
|
1046
985
|
// CLI
|
|
1047
986
|
// ============================================================================
|
|
@@ -1163,7 +1102,7 @@ if (import.meta.main) {
|
|
|
1163
1102
|
cloudflareNative: parsed.cloudflare,
|
|
1164
1103
|
};
|
|
1165
1104
|
|
|
1166
|
-
const output =
|
|
1105
|
+
const output = assembleTemplate(options);
|
|
1167
1106
|
console.log(output);
|
|
1168
1107
|
} catch (error) {
|
|
1169
1108
|
console.error("Error:", error instanceof Error ? error.message : error);
|