@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
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { parseChatgptConversationIdentity } from './chatgpt-conversation';
|
|
3
|
-
import { parseSupervisorCompletion, renderEffectMarker, renderSupervisorPrompt, sha256, validateEffectId } from './protocol';
|
|
3
|
+
import { parseSupervisorCompletion, renderEffectMarker, renderSupervisorPrompt, sha256, validateEffectId, validateSupervisorObjective } from './protocol';
|
|
4
4
|
import { WorkflowSupervisorStore } from './store';
|
|
5
|
-
import
|
|
5
|
+
import { getWorkContract } from '../packages/kernel/work/api/index';
|
|
6
|
+
import { readRequirement } from '../src/runtime/control-plane/persistence/requirement-store';
|
|
7
|
+
import { resolveWorkflowSupervisorForgeHome } from './paths';
|
|
8
|
+
import type { WorkflowAssistantObservation, WorkflowAssistantObservationResult, WorkflowContractValidation, WorkflowEffectKind, WorkflowSupervisorBrowserPollResult, WorkflowSupervisorBrowserTask, WorkflowSupervisorCompletion, WorkflowSupervisorDiscoveredConversation, WorkflowSupervisorEffect, WorkflowSupervisorLifecycleHooks, WorkflowSupervisorProjectScope, WorkflowSupervisorTask, WorkflowSupervisorTaskInput, WorkflowSupervisorTerminalState, WorkflowSupervisorValidators, WorkflowSupervisorPreflightComposerObservation } from './types';
|
|
6
9
|
|
|
7
10
|
function compactProjectIdentity(value: string): string {
|
|
8
11
|
return value.normalize('NFKC').toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, '');
|
|
@@ -65,13 +68,47 @@ export class WorkflowSupervisorControlPlane {
|
|
|
65
68
|
expectedObjectiveSha256: string;
|
|
66
69
|
requestId: string;
|
|
67
70
|
authorizedBy?: string;
|
|
68
|
-
|
|
71
|
+
workId?: string;
|
|
72
|
+
requirementId?: string;
|
|
73
|
+
expectedCompletionContractSha256?: string;
|
|
74
|
+
}): { task: WorkflowSupervisorTask; changed: boolean; deduplicated: boolean; objectiveSha256: string; completionContractSha256: string; semanticBindingChanged: boolean; refreshedEffect?: WorkflowSupervisorEffect } {
|
|
69
75
|
const task = this.requireTask(input.taskId);
|
|
70
76
|
requireNonTerminalTask(this.store, task.taskId);
|
|
71
|
-
const objective = input.objective
|
|
72
|
-
if (!objective || objective.length > 8_000) throw new Error('WORKFLOW_SUPERVISOR_OBJECTIVE_INVALID');
|
|
77
|
+
const objective = validateSupervisorObjective(input.objective);
|
|
73
78
|
if (!/^[0-9a-f]{64}$/i.test(input.expectedObjectiveSha256)) throw new Error('WORKFLOW_SUPERVISOR_OBJECTIVE_REVISION_EXPECTED_SHA_INVALID');
|
|
74
79
|
if (!input.requestId.trim()) throw new Error('WORKFLOW_SUPERVISOR_OBJECTIVE_REVISION_REQUEST_REQUIRED');
|
|
80
|
+
const workId = input.workId?.trim() || undefined;
|
|
81
|
+
const requirementId = input.requirementId?.trim() || undefined;
|
|
82
|
+
if (workId && requirementId) throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_TARGET_INVALID');
|
|
83
|
+
if ((workId || requirementId) && !input.expectedCompletionContractSha256) {
|
|
84
|
+
throw new Error('WORKFLOW_SUPERVISOR_COMPLETION_CONTRACT_REVISION_EXPECTED_SHA_REQUIRED');
|
|
85
|
+
}
|
|
86
|
+
if (!workId && !requirementId && input.expectedCompletionContractSha256) {
|
|
87
|
+
throw new Error('WORKFLOW_SUPERVISOR_COMPLETION_CONTRACT_REVISION_TARGET_REQUIRED');
|
|
88
|
+
}
|
|
89
|
+
let semanticBinding: { workId?: string; requirementId?: string; expectedCompletionContractSha256: string } | undefined;
|
|
90
|
+
if (workId || requirementId) {
|
|
91
|
+
if (task.completionContract.kind !== 'model_semantic_completion') {
|
|
92
|
+
throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_REVISION_KIND_UNSUPPORTED');
|
|
93
|
+
}
|
|
94
|
+
const controllerHome = typeof task.completionContract.controller_home === 'string' ? task.completionContract.controller_home.trim() : '';
|
|
95
|
+
const repoId = typeof task.completionContract.repo_id === 'string' ? task.completionContract.repo_id.trim() : '';
|
|
96
|
+
if (!controllerHome || !repoId
|
|
97
|
+
|| resolveWorkflowSupervisorForgeHome(controllerHome) !== resolveWorkflowSupervisorForgeHome(this.store.forgeHome)) {
|
|
98
|
+
throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_CONTEXT_REQUIRED');
|
|
99
|
+
}
|
|
100
|
+
if (workId && !getWorkContract({ controllerHome, scopeKey: repoId }, workId)) {
|
|
101
|
+
throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_WORK_NOT_FOUND');
|
|
102
|
+
}
|
|
103
|
+
if (requirementId && !readRequirement({ controllerHome }, requirementId)?.value) {
|
|
104
|
+
throw new Error('WORKFLOW_SUPERVISOR_SEMANTIC_BINDING_REQUIREMENT_NOT_FOUND');
|
|
105
|
+
}
|
|
106
|
+
semanticBinding = {
|
|
107
|
+
...(workId ? { workId } : {}),
|
|
108
|
+
...(requirementId ? { requirementId } : {}),
|
|
109
|
+
expectedCompletionContractSha256: input.expectedCompletionContractSha256!,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
75
112
|
const pending = this.store.currentUnappliedEffect(task.taskId);
|
|
76
113
|
let refreshEffect: { effectId: string; expectedPrompt: string; prompt: string } | undefined;
|
|
77
114
|
if (pending && !this.store.latestEffectDispatch(pending.effectId)) {
|
|
@@ -89,6 +126,7 @@ export class WorkflowSupervisorControlPlane {
|
|
|
89
126
|
expectedObjectiveSha256: input.expectedObjectiveSha256.toLowerCase(),
|
|
90
127
|
requestId: input.requestId.trim(),
|
|
91
128
|
authorizedBy: input.authorizedBy?.trim() || 'operator',
|
|
129
|
+
...(semanticBinding ? { semanticBinding } : {}),
|
|
92
130
|
...(refreshEffect ? { refreshEffect } : {}),
|
|
93
131
|
});
|
|
94
132
|
}
|
|
@@ -134,6 +172,14 @@ export class WorkflowSupervisorControlPlane {
|
|
|
134
172
|
throw new Error('WORKFLOW_SUPERVISOR_RECOVERY_SOURCE_CHANGED');
|
|
135
173
|
}
|
|
136
174
|
if (pending) {
|
|
175
|
+
if (this.store.effectDispatchBudget(pending.effectId).generations === 0) {
|
|
176
|
+
// Browser provider was repaired independently of Forge's source release.
|
|
177
|
+
// The old enrollment text and effect identity remain authoritative;
|
|
178
|
+
// rearm a single preparation without manufacturing a provider send.
|
|
179
|
+
this.store.authorizeOperatorPreflightRetry({ effectId: pending.effectId, requestId: input.requestId,
|
|
180
|
+
reason: input.reason, authorizedBy });
|
|
181
|
+
return { recoveryEffect: pending, action: 'retry_authorized' };
|
|
182
|
+
}
|
|
137
183
|
// Read-only reclassification first: a dispatch-owner pre-send return is a
|
|
138
184
|
// stronger fact than any operator decision and makes the effect retryable
|
|
139
185
|
// through the normal mechanical path.
|
|
@@ -236,6 +282,31 @@ export class WorkflowSupervisorControlPlane {
|
|
|
236
282
|
const tasks = this.store.listTasks();
|
|
237
283
|
return activeOnly ? tasks.filter((task) => !this.store.terminalAction(task.taskId)) : tasks;
|
|
238
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Completed/blocked/stopped history is a read-only projection of canonical
|
|
287
|
+
* terminal events. It never archives the ChatGPT conversation or rewrites Work.
|
|
288
|
+
*/
|
|
289
|
+
listTerminalTasks(): Array<{ task: WorkflowSupervisorTask; terminal: WorkflowSupervisorTerminalState }> {
|
|
290
|
+
return this.store.listTasks().flatMap((task) => {
|
|
291
|
+
const terminal = this.store.terminalAction(task.taskId);
|
|
292
|
+
return terminal ? [{ task, terminal }] : [];
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/** Reconcile Work-owned task closure after a crash or a direct Work writer.
|
|
296
|
+
* Independent Requirement and standalone Goals are intentionally excluded.
|
|
297
|
+
*/
|
|
298
|
+
reconcileTerminalWorkBoundTasks(): string[] {
|
|
299
|
+
if (!this.hooks.terminalWorkStopReasonForTask) return [];
|
|
300
|
+
const stopped: string[] = [];
|
|
301
|
+
for (const task of this.store.listTasks()) {
|
|
302
|
+
if (this.store.terminalAction(task.taskId)) continue;
|
|
303
|
+
const reason = this.hooks.terminalWorkStopReasonForTask(task);
|
|
304
|
+
if (!reason) continue;
|
|
305
|
+
const result = this.store.stopTask(task.taskId, reason);
|
|
306
|
+
if (!result.deduplicated) stopped.push(task.taskId);
|
|
307
|
+
}
|
|
308
|
+
return stopped;
|
|
309
|
+
}
|
|
239
310
|
stopTask(taskId: string, reason = 'Stopped by operator request.'): { taskId: string; terminal: 'STOPPED'; deduplicated: boolean } {
|
|
240
311
|
return this.store.stopTask(taskId, reason);
|
|
241
312
|
}
|
|
@@ -251,6 +322,51 @@ export class WorkflowSupervisorControlPlane {
|
|
|
251
322
|
return task;
|
|
252
323
|
}
|
|
253
324
|
getEffect(id: string): WorkflowSupervisorEffect | undefined { return this.store.getEffect(validateEffectId(id)); }
|
|
325
|
+
preSendComposerObservation(effectId: string): WorkflowSupervisorPreflightComposerObservation | undefined {
|
|
326
|
+
return this.store.preSendComposerObservation(validateEffectId(effectId));
|
|
327
|
+
}
|
|
328
|
+
/** Bind the one canonical Scheduler occurrence lane to an exact Work-owned Supervisor task. */
|
|
329
|
+
bindScheduledWake(input: { taskId: string; repoId: string; workId: string; scheduleId: string }) {
|
|
330
|
+
const task = this.requireTask(input.taskId);
|
|
331
|
+
requireNonTerminalTask(this.store, task.taskId);
|
|
332
|
+
const bound = this.store.bindScheduledWake(input);
|
|
333
|
+
const pending = this.store.currentUnappliedEffect(task.taskId);
|
|
334
|
+
if (pending && !this.store.latestEffectDispatch(pending.effectId) && !this.store.effectApplied(pending.effectId)) {
|
|
335
|
+
const source = pending.sourceCompletionFingerprint ? this.store.getCompletion(pending.sourceCompletionFingerprint) : undefined;
|
|
336
|
+
const checkpoint = source?.proposal.reason === 'compact_receipt' ? undefined : source?.proposal.checkpoint;
|
|
337
|
+
this.store.refreshUndispatchedEffectPrompt({
|
|
338
|
+
taskId: task.taskId,
|
|
339
|
+
effectId: pending.effectId,
|
|
340
|
+
expectedPrompt: pending.prompt,
|
|
341
|
+
prompt: this.renderPrompt(bound.task, pending.effectId, pending.kind, checkpoint),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return bound;
|
|
345
|
+
}
|
|
346
|
+
/** Consume a completed WAIT receipt exactly once for one exact Scheduler occurrence. */
|
|
347
|
+
wakeScheduledTask(input: { taskId: string; scheduleId: string; occurrenceId: string }): { effect: WorkflowSupervisorEffect; deduplicated: boolean } {
|
|
348
|
+
const task = this.requireTask(input.taskId);
|
|
349
|
+
if (task.continuationPolicy.scheduled_wake_schedule_id !== input.scheduleId) {
|
|
350
|
+
throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_BINDING_MISMATCH');
|
|
351
|
+
}
|
|
352
|
+
const originKey = `scheduled-wake:${input.scheduleId}:${input.occurrenceId}`;
|
|
353
|
+
const id = stableEffectId(originKey);
|
|
354
|
+
const prior = this.store.getEffectByOriginKey(originKey);
|
|
355
|
+
if (prior) {
|
|
356
|
+
if (prior.taskId !== task.taskId || prior.effectId !== id || prior.kind !== 'continuation' || !prior.sourceCompletionFingerprint) {
|
|
357
|
+
throw new Error('WORKFLOW_SUPERVISOR_SCHEDULED_WAKE_IDENTITY_CONFLICT');
|
|
358
|
+
}
|
|
359
|
+
return { effect: prior, deduplicated: true };
|
|
360
|
+
}
|
|
361
|
+
requireNonTerminalTask(this.store, task.taskId);
|
|
362
|
+
return this.store.reserveScheduledWake({
|
|
363
|
+
taskId: task.taskId,
|
|
364
|
+
scheduleId: input.scheduleId,
|
|
365
|
+
occurrenceId: input.occurrenceId,
|
|
366
|
+
effectId: id,
|
|
367
|
+
prompt: this.renderPrompt(task, id, 'continuation', `scheduled wake ${input.scheduleId} occurrence ${input.occurrenceId}`),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
254
370
|
/** Read-only stall classification for one task; no lifecycle authority. */
|
|
255
371
|
taskStall(taskId: string): ReturnType<WorkflowSupervisorStore['taskStall']> { return this.store.taskStall(this.requireTask(taskId).taskId); }
|
|
256
372
|
/**
|
|
@@ -407,39 +523,41 @@ export class WorkflowSupervisorControlPlane {
|
|
|
407
523
|
if (!task.conversationId.startsWith('bootstrap:')) throw new Error('WORKFLOW_SUPERVISOR_BOOTSTRAP_NOT_PENDING');
|
|
408
524
|
const terminal = this.store.terminalAction(task.taskId);
|
|
409
525
|
if (terminal === 'STOPPED') return { authorized: true, task: browserTask(task), terminal };
|
|
410
|
-
|
|
411
|
-
|
|
526
|
+
// While identity is still bootstrap, reconcile the latest applied leaf
|
|
527
|
+
// before dispatching any successor. An unstarted recovery child must not
|
|
528
|
+
// send from a Project/new-composer surface while the source conversation
|
|
529
|
+
// is still unbound. The existing effect ledger owns observation cooldown.
|
|
530
|
+
const applied = this.store.latestAppliedLeafEffectWithoutCompletion(task.taskId);
|
|
531
|
+
if (applied) {
|
|
532
|
+
if (!this.store.bootstrapAppliedReconcileDue(applied.effectId)) {
|
|
533
|
+
return { authorized: true, task: browserTask(task) };
|
|
534
|
+
}
|
|
535
|
+
const dispatch = this.store.latestEffectDispatch(applied.effectId);
|
|
412
536
|
return {
|
|
413
537
|
authorized: true,
|
|
414
538
|
task: browserTask(task),
|
|
415
539
|
command: {
|
|
416
|
-
mode:
|
|
417
|
-
effectId:
|
|
418
|
-
kind:
|
|
419
|
-
prompt:
|
|
420
|
-
dispatchGeneration:
|
|
540
|
+
mode: 'reconcile',
|
|
541
|
+
effectId: applied.effectId,
|
|
542
|
+
kind: applied.kind,
|
|
543
|
+
prompt: applied.prompt,
|
|
544
|
+
dispatchGeneration: dispatch?.generation ?? 1,
|
|
421
545
|
conversationId: task.conversationId,
|
|
422
546
|
conversationUrl: task.conversationUrl,
|
|
423
547
|
},
|
|
424
548
|
};
|
|
425
549
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// canonicalized the new /c/<conversation> route. Keep one read-only
|
|
429
|
-
// reconciliation command for that applied effect so the exact conversation
|
|
430
|
-
// can be bound without manufacturing another provider generation.
|
|
431
|
-
const applied = this.store.latestAppliedEffectWithoutCompletion(task.taskId);
|
|
432
|
-
if (!applied) return { authorized: true, task: browserTask(task) };
|
|
433
|
-
const dispatch = this.store.latestEffectDispatch(applied.effectId);
|
|
550
|
+
const pending = this.store.nextBrowserEffect(task.taskId);
|
|
551
|
+
if (!pending) return { authorized: true, task: browserTask(task) };
|
|
434
552
|
return {
|
|
435
553
|
authorized: true,
|
|
436
554
|
task: browserTask(task),
|
|
437
555
|
command: {
|
|
438
|
-
mode:
|
|
439
|
-
effectId:
|
|
440
|
-
kind:
|
|
441
|
-
prompt:
|
|
442
|
-
dispatchGeneration:
|
|
556
|
+
mode: pending.mode,
|
|
557
|
+
effectId: pending.effect.effectId,
|
|
558
|
+
kind: pending.effect.kind,
|
|
559
|
+
prompt: pending.effect.prompt,
|
|
560
|
+
dispatchGeneration: pending.generation,
|
|
443
561
|
conversationId: task.conversationId,
|
|
444
562
|
conversationUrl: task.conversationUrl,
|
|
445
563
|
},
|
|
@@ -468,13 +586,17 @@ export class WorkflowSupervisorControlPlane {
|
|
|
468
586
|
const pending = this.store.nextBrowserEffect(task.taskId);
|
|
469
587
|
const dispatch = this.store.latestEffectDispatch(effect.effectId);
|
|
470
588
|
if (!pending || pending.effect.effectId !== effect.effectId || pending.mode !== 'reconcile'
|
|
471
|
-
|| !dispatch || pending.generation !== dispatch.generation
|
|
589
|
+
|| !dispatch || pending.generation !== dispatch.generation
|
|
590
|
+
|| dispatch.evidence.surface !== 'computer-bootstrap'
|
|
591
|
+
|| input.evidence?.dispatch_mutation !== 'not_attempted'
|
|
592
|
+
|| input.evidence?.send_clicked !== false) {
|
|
472
593
|
throw new Error('WORKFLOW_SUPERVISOR_BOOTSTRAP_NOT_APPLIED_PROOF_INVALID');
|
|
473
594
|
}
|
|
474
595
|
this.store.recordEffectNotAppliedProof(effect.effectId, input.observationId, {
|
|
475
|
-
surface: 'computer-bootstrap',
|
|
596
|
+
surface: 'computer-bootstrap', send_clicked: false,
|
|
476
597
|
dispatch_generation: dispatch.generation,
|
|
477
598
|
pre_send_rejection: true,
|
|
599
|
+
attestation: 'local_computer_dispatch_returned_without_send',
|
|
478
600
|
});
|
|
479
601
|
return;
|
|
480
602
|
}
|
|
@@ -482,12 +604,12 @@ export class WorkflowSupervisorControlPlane {
|
|
|
482
604
|
this.observeEffect({ effectId: effect.effectId, observationId: input.observationId, outcome: input.outcome, evidence });
|
|
483
605
|
if (input.outcome === 'applied') this.hooks.effectApplied?.(task, effect, { observationId: input.observationId, evidence });
|
|
484
606
|
}
|
|
485
|
-
/**
|
|
486
|
-
recordProviderPreparationFailure(input: { taskId: string; effectId: string; reasonCode: string }): void {
|
|
607
|
+
/** Persist a bounded preflight rejection; started generations require an exact no-click proof first. */
|
|
608
|
+
recordProviderPreparationFailure(input: { taskId: string; effectId: string; reasonCode: string; composerObservation?: WorkflowSupervisorPreflightComposerObservation }): void {
|
|
487
609
|
const task = this.requireTask(input.taskId);
|
|
488
610
|
const effect = this.store.getEffect(input.effectId);
|
|
489
611
|
if (effect?.taskId !== task.taskId) throw new Error('WORKFLOW_SUPERVISOR_PRE_SEND_EFFECT_TASK_MISMATCH');
|
|
490
|
-
this.store.recordEffectPreparationFailure(input.effectId, input.reasonCode);
|
|
612
|
+
this.store.recordEffectPreparationFailure(input.effectId, input.reasonCode, input.composerObservation);
|
|
491
613
|
}
|
|
492
614
|
|
|
493
615
|
browserBeginEffect(input: { conversationId: string; conversationUrl: string; effectId: string; dispatchId: string; dispatchGeneration: number; evidence?: Record<string, unknown> }): { started: boolean; mode: 'send' | 'reconcile'; generation: number } {
|
|
@@ -543,17 +665,25 @@ export class WorkflowSupervisorControlPlane {
|
|
|
543
665
|
if (dispatch?.generation !== input.dispatchGeneration) throw new Error('WORKFLOW_SUPERVISOR_DISPATCH_GENERATION_CHANGED');
|
|
544
666
|
this.store.recordEffectNotAppliedProof(input.effectId, input.observationId, {
|
|
545
667
|
surface: input.surface?.trim().slice(0, 128) || 'macos-native', reason: input.reason, send_clicked: false, dispatch_generation: input.dispatchGeneration,
|
|
546
|
-
// This call is the dispatch owner's
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
// it a purely local obstacle permanently exhausted the effect.
|
|
668
|
+
// This call is the dispatch owner's exact mechanical return before any
|
|
669
|
+
// Send click. The store refunds this generation once; the existing preflight
|
|
670
|
+
// gate bounds ordinary repeats without spending actual send budget.
|
|
550
671
|
pre_send_rejection: true,
|
|
551
672
|
});
|
|
673
|
+
const recoveryStopPending = effect.kind === 'recovery' && input.reason.trim() === 'provider_recovery_stop_pending';
|
|
674
|
+
if (!recoveryStopPending) {
|
|
675
|
+
this.recordProviderPreparationFailure({ taskId: task.taskId, effectId: effect.effectId, reasonCode: input.reason });
|
|
676
|
+
}
|
|
552
677
|
}
|
|
553
678
|
browserObserveProviderTurn(input: { conversationId: string; conversationUrl: string; generating: boolean; latestAssistantResponse: string; providerActivityText?: string; providerFailureCode?: string; receiptMissing?: boolean; observedAtMs: number; graceMs: number }): { state: 'inactive' | 'none' | 'generating' | 'idle_pending' | 'recovery_reserved' | 'exhausted'; recoveryEffect?: WorkflowSupervisorEffect } {
|
|
554
679
|
const task = this.requireBrowserTask(input.conversationId, input.conversationUrl);
|
|
555
680
|
if (!this.browserTaskActiveForExternalEffect(task)) return { state: 'inactive' };
|
|
556
|
-
|
|
681
|
+
// Exhaustion stops new recovery mutations, not read-only observation of the exhausted applied leaf.
|
|
682
|
+
let sourceEffect = this.store.latestAppliedEffectWithoutCompletion(task.taskId);
|
|
683
|
+
if (!sourceEffect) {
|
|
684
|
+
const exhaustedLeaf = this.store.latestAppliedLeafEffectWithoutCompletion(task.taskId);
|
|
685
|
+
if (exhaustedLeaf && this.store.providerResumeExhausted(exhaustedLeaf.effectId)) sourceEffect = exhaustedLeaf;
|
|
686
|
+
}
|
|
557
687
|
if (!sourceEffect) return { state: 'none' };
|
|
558
688
|
const recoveryId = stableEffectId(`provider-recovery:${sourceEffect.effectId}`);
|
|
559
689
|
const providerFailureCode = input.providerFailureCode?.trim();
|
|
@@ -613,8 +743,8 @@ export class WorkflowSupervisorControlPlane {
|
|
|
613
743
|
if (committed.successorEffect) reconciled += 1;
|
|
614
744
|
continue;
|
|
615
745
|
}
|
|
616
|
-
|
|
617
|
-
const validation = await
|
|
746
|
+
if (latest.action === 'WAIT') continue;
|
|
747
|
+
const validation = await this.validateTerminalProposal(task, latest.proposal);
|
|
618
748
|
const correctionId = validation.valid ? undefined : stableEffectId(`completion:${latest.completionFingerprint}`);
|
|
619
749
|
this.store.resolveTerminal({
|
|
620
750
|
completionFingerprint: latest.completionFingerprint,
|
|
@@ -646,9 +776,10 @@ export class WorkflowSupervisorControlPlane {
|
|
|
646
776
|
// original effect context for idempotent duplicate observation.
|
|
647
777
|
const priorCompletion = expectedEffect ? undefined : this.store.getCompletionByResponseSha256(task.taskId, responseSha256);
|
|
648
778
|
const expectedEffectId = expectedEffect?.effectId ?? priorCompletion?.sourceEffectId;
|
|
779
|
+
const receiptEffect = expectedEffect ?? (expectedEffectId ? this.store.getEffect(expectedEffectId) : undefined);
|
|
649
780
|
const parsed = parseSupervisorCompletion(
|
|
650
781
|
input.responseText,
|
|
651
|
-
expectedEffectId ? { task, effectId: expectedEffectId } : undefined,
|
|
782
|
+
expectedEffectId ? { task, effectId: expectedEffectId, prompt: receiptEffect?.prompt } : undefined,
|
|
652
783
|
);
|
|
653
784
|
const sourceEffect = this.store.getEffect(parsed.proposal.sourceEffectId);
|
|
654
785
|
if (!sourceEffect || sourceEffect.taskId !== task.taskId || !this.store.effectApplied(sourceEffect.effectId)) throw new Error('WORKFLOW_SUPERVISOR_CAUSAL_EFFECT_NOT_APPLIED');
|
|
@@ -675,81 +806,19 @@ export class WorkflowSupervisorControlPlane {
|
|
|
675
806
|
const committed = this.reserveContinuation(task, completion);
|
|
676
807
|
return { action: 'CONTINUE', completionFingerprint, terminal: false, successorEffect: committed.successorEffect!, deduplicated: committed.deduplicated };
|
|
677
808
|
}
|
|
809
|
+
if (parsed.proposal.action === 'WAIT') {
|
|
810
|
+
const committed = this.store.commitCompletion(completion);
|
|
811
|
+
return { action: 'WAIT', completionFingerprint, terminal: false, deduplicated: committed.deduplicated };
|
|
812
|
+
}
|
|
678
813
|
const committed = this.store.commitCompletion(completion);
|
|
679
814
|
|
|
680
|
-
const
|
|
681
|
-
const validation = await validator(task, parsed.proposal);
|
|
815
|
+
const validation = await this.validateTerminalProposal(task, parsed.proposal);
|
|
682
816
|
const correctionId = validation.valid ? undefined : stableEffectId(`completion:${completionFingerprint}`);
|
|
683
817
|
const resolved = this.store.resolveTerminal({ completionFingerprint, taskId: task.taskId, action: parsed.proposal.action, accepted: validation.valid, reason: validation.reason,
|
|
684
818
|
...(correctionId ? { correction: { effectId: correctionId, prompt: this.renderPrompt(task, correctionId, 'correction', parsed.proposal.reason === 'compact_receipt' ? undefined : parsed.proposal.checkpoint, validation.reason) } } : {}) });
|
|
685
819
|
return { action: parsed.proposal.action, completionFingerprint, terminal: validation.valid, ...(resolved.successorEffect ? { successorEffect: resolved.successorEffect } : {}), validation, deduplicated: committed.deduplicated || resolved.deduplicated };
|
|
686
820
|
}
|
|
687
821
|
|
|
688
|
-
/**
|
|
689
|
-
* The autonomous transport records its final state through the Forge tool
|
|
690
|
-
* call, not through text rendered in the ChatGPT page. The exact task and
|
|
691
|
-
* applied effect are derived locally; callers cannot select a different
|
|
692
|
-
* conversation or effect by supplying model-authored identifiers.
|
|
693
|
-
*/
|
|
694
|
-
async observeAutomationReceipt(input: {
|
|
695
|
-
taskId: string;
|
|
696
|
-
conversationId: string;
|
|
697
|
-
status: WorkflowSupervisorAutomationStatus;
|
|
698
|
-
receiptId: string;
|
|
699
|
-
}): Promise<WorkflowAssistantObservationResult | { recorded: true }> {
|
|
700
|
-
if (input.status === 'working') return { recorded: true };
|
|
701
|
-
const task = this.requireTask(input.taskId);
|
|
702
|
-
if (task.conversationId !== input.conversationId) throw new Error('WORKFLOW_SUPERVISOR_CONVERSATION_MISMATCH');
|
|
703
|
-
const terminal = this.store.terminalAction(task.taskId);
|
|
704
|
-
if (terminal) throw new Error(`WORKFLOW_SUPERVISOR_TASK_TERMINAL:${terminal}`);
|
|
705
|
-
const expectedEffect = this.store.latestAppliedEffectWithoutCompletion(task.taskId)
|
|
706
|
-
?? this.store.latestAppliedLeafEffectWithoutCompletion(task.taskId);
|
|
707
|
-
// A provider can reuse one tool name/status in successive outer turns.
|
|
708
|
-
// Include the locally selected effect in the durable receipt identity so a
|
|
709
|
-
// later `continue` cannot replay the first effect's completion.
|
|
710
|
-
const replayCompletion = expectedEffect ? undefined : this.store.getLatestCompletion(task.taskId);
|
|
711
|
-
if (!expectedEffect && (!replayCompletion
|
|
712
|
-
|| replayCompletion.proposal.checkpoint !== `automation:${input.receiptId}:${replayCompletion.sourceEffectId}`)) {
|
|
713
|
-
throw new Error('WORKFLOW_SUPERVISOR_CAUSAL_EFFECT_NOT_APPLIED');
|
|
714
|
-
}
|
|
715
|
-
const receiptForEffect = `${input.receiptId}:${expectedEffect?.effectId ?? replayCompletion!.sourceEffectId}`;
|
|
716
|
-
const responseText = `automation:${input.status}:${receiptForEffect}`;
|
|
717
|
-
const responseSha256 = sha256(responseText);
|
|
718
|
-
const priorCompletion = expectedEffect ? undefined : this.store.getCompletionByResponseSha256(task.taskId, responseSha256);
|
|
719
|
-
const sourceEffect = expectedEffect ?? (priorCompletion ? this.store.getEffect(priorCompletion.sourceEffectId) : undefined);
|
|
720
|
-
if (!sourceEffect || sourceEffect.taskId !== task.taskId || !this.store.effectApplied(sourceEffect.effectId)) {
|
|
721
|
-
throw new Error('WORKFLOW_SUPERVISOR_CAUSAL_EFFECT_NOT_APPLIED');
|
|
722
|
-
}
|
|
723
|
-
const action = input.status === 'continue' ? 'CONTINUE' : input.status === 'done' ? 'DONE' : 'NEEDS_USER';
|
|
724
|
-
const proposal = {
|
|
725
|
-
action: action as 'CONTINUE' | 'DONE' | 'NEEDS_USER',
|
|
726
|
-
sourceEffectId: sourceEffect.effectId,
|
|
727
|
-
checkpoint: `automation:${receiptForEffect}`,
|
|
728
|
-
reason: 'automation_tool_receipt',
|
|
729
|
-
evidence: [],
|
|
730
|
-
conversationId: task.conversationId,
|
|
731
|
-
taskId: task.taskId,
|
|
732
|
-
supervisorState: action === 'CONTINUE' ? 'running' as const : action === 'DONE' ? 'done' as const : 'needs_user' as const,
|
|
733
|
-
};
|
|
734
|
-
const controlBlockSha256 = sha256(responseText);
|
|
735
|
-
const completionFingerprint = sha256(jsonIdentity(task.taskId, task.conversationId, sourceEffect.effectId, responseSha256, controlBlockSha256));
|
|
736
|
-
const completion: WorkflowSupervisorCompletion = { completionFingerprint, taskId: task.taskId, sourceEffectId: sourceEffect.effectId, action: proposal.action, responseSha256, controlBlockSha256, proposal, committedAt: new Date().toISOString() };
|
|
737
|
-
if (proposal.action === 'CONTINUE') {
|
|
738
|
-
const committed = this.reserveContinuation(task, completion);
|
|
739
|
-
return { action: 'CONTINUE', completionFingerprint, terminal: false, successorEffect: committed.successorEffect!, deduplicated: committed.deduplicated };
|
|
740
|
-
}
|
|
741
|
-
const committed = this.store.commitCompletion(completion);
|
|
742
|
-
const validation = proposal.action === 'DONE'
|
|
743
|
-
? await this.validators.completionContract(task, proposal)
|
|
744
|
-
: await this.validators.userBlockerPolicy(task, proposal);
|
|
745
|
-
const correctionId = validation.valid ? undefined : stableEffectId(`completion:${completionFingerprint}`);
|
|
746
|
-
const resolved = this.store.resolveTerminal({
|
|
747
|
-
completionFingerprint, taskId: task.taskId, action: proposal.action, accepted: validation.valid, reason: validation.reason,
|
|
748
|
-
...(correctionId ? { correction: { effectId: correctionId, prompt: this.renderPrompt(task, correctionId, 'correction', proposal.checkpoint, validation.reason) } } : {}),
|
|
749
|
-
});
|
|
750
|
-
return { action: proposal.action, completionFingerprint, terminal: validation.valid, ...(resolved.successorEffect ? { successorEffect: resolved.successorEffect } : {}), validation, deduplicated: committed.deduplicated || resolved.deduplicated };
|
|
751
|
-
}
|
|
752
|
-
|
|
753
822
|
/** Model CONTINUE and its causal successor commit in the same authority transaction. */
|
|
754
823
|
private reserveContinuation(task: WorkflowSupervisorTask, completion: WorkflowSupervisorCompletion) {
|
|
755
824
|
const nextId = stableEffectId(`completion:${completion.completionFingerprint}`);
|
|
@@ -765,6 +834,32 @@ export class WorkflowSupervisorControlPlane {
|
|
|
765
834
|
});
|
|
766
835
|
}
|
|
767
836
|
|
|
837
|
+
private async validateTerminalProposal(
|
|
838
|
+
task: WorkflowSupervisorTask,
|
|
839
|
+
proposal: WorkflowSupervisorCompletion['proposal'],
|
|
840
|
+
): Promise<WorkflowContractValidation> {
|
|
841
|
+
const validator = proposal.action === 'DONE' ? this.validators.completionContract : this.validators.userBlockerPolicy;
|
|
842
|
+
const validation = await validator(task, proposal);
|
|
843
|
+
if (typeof task.continuationPolicy.scheduled_wake_schedule_id !== 'string') return validation;
|
|
844
|
+
if (proposal.action === 'DONE') {
|
|
845
|
+
return {
|
|
846
|
+
...validation,
|
|
847
|
+
valid: false,
|
|
848
|
+
reason: validation.valid ? 'scheduled_work_must_wait' : validation.reason,
|
|
849
|
+
evidence: [...(validation.evidence ?? []), 'A Work-bound recurring Schedule owns the next run; use WAIT instead of terminal DONE.'],
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
if (proposal.action === 'NEEDS_USER') {
|
|
853
|
+
return {
|
|
854
|
+
...validation,
|
|
855
|
+
valid: false,
|
|
856
|
+
reason: validation.valid ? 'scheduled_work_must_remain_open' : validation.reason,
|
|
857
|
+
evidence: [...(validation.evidence ?? []), 'Persist the exact user blocker and use WAIT; a recurring scheduled Work must remain open.'],
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
return validation;
|
|
861
|
+
}
|
|
862
|
+
|
|
768
863
|
private renderPrompt(
|
|
769
864
|
task: WorkflowSupervisorTask,
|
|
770
865
|
effectId: string,
|
|
@@ -801,18 +896,18 @@ export class WorkflowSupervisorControlPlane {
|
|
|
801
896
|
if (!effect.sourceCompletionFingerprint) return true;
|
|
802
897
|
const completion = this.store.getCompletion(effect.sourceCompletionFingerprint);
|
|
803
898
|
if (!completion || completion.taskId !== task.taskId) return false;
|
|
804
|
-
//
|
|
805
|
-
//
|
|
806
|
-
//
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
//
|
|
811
|
-
|
|
812
|
-
|
|
899
|
+
// A compact completion is committed only after observing the provider's
|
|
900
|
+
// finished assistant reply and checking its causal challenge. ChatGPT may
|
|
901
|
+
// subsequently collapse that historical reply or its original user marker;
|
|
902
|
+
// requiring the bytes to remain in the DOM would strand a valid successor.
|
|
903
|
+
// Persisted legacy tool receipts are read-only migration evidence; the MCP
|
|
904
|
+
// receipt writer has been retired. In both cases the committed completion,
|
|
905
|
+
// not a new heuristic, is the one authority for the successor.
|
|
906
|
+
const durableReceipt = completion.proposal.reason === 'compact_receipt'
|
|
907
|
+
|| completion.proposal.reason === 'automation_tool_receipt';
|
|
813
908
|
const sourceEffect = this.store.getEffect(completion.sourceEffectId);
|
|
814
909
|
if (!sourceEffect || sourceEffect.taskId !== task.taskId) return false;
|
|
815
|
-
if (
|
|
910
|
+
if (durableReceipt) return true;
|
|
816
911
|
if (sha256(snapshot.latestAssistantResponse) !== completion.responseSha256) return false;
|
|
817
912
|
return normalizeBrowserText(snapshot.latestUserText) === normalizeBrowserText(sourceEffect.prompt)
|
|
818
913
|
|| browserTextHasEffect(snapshot.latestUserText, sourceEffect.effectId);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isLegacyMachineRequirementWait, readRequirement } from '../src/runtime/control-plane/persistence/requirement-store';
|
|
2
|
-
import { getWorkContract, isTerminalSemanticWorkState } from '../packages/kernel/work/api/index';
|
|
2
|
+
import { getWorkContract, isTerminalSemanticWorkState, readWorkContractLineageSnapshot, type SemanticWorkState } from '../packages/kernel/work/api/index';
|
|
3
|
+
import { SEMANTIC_SCOPE_KEY } from '../src/cli/repositories/controller-home';
|
|
3
4
|
import type { WorkflowContractValidation, WorkflowSupervisorProposal, WorkflowSupervisorTask, WorkflowSupervisorValidators } from './types';
|
|
4
5
|
|
|
5
6
|
function contractText(task: WorkflowSupervisorTask, key: string): string | undefined {
|
|
@@ -11,8 +12,8 @@ function requirementFor(task: WorkflowSupervisorTask, proposal?: WorkflowSupervi
|
|
|
11
12
|
const controllerHome = contractText(task, 'controller_home');
|
|
12
13
|
const repoId = contractText(task, 'repo_id');
|
|
13
14
|
const workId = contractText(task, 'work_id');
|
|
14
|
-
const workRequirementId = controllerHome &&
|
|
15
|
-
? getWorkContract({ controllerHome, repoId }, workId)?.requirementId
|
|
15
|
+
const workRequirementId = controllerHome && workId
|
|
16
|
+
? getWorkContract({ controllerHome, scopeKey: repoId ?? SEMANTIC_SCOPE_KEY }, workId)?.requirementId
|
|
16
17
|
: undefined;
|
|
17
18
|
// activeScope is legacy read compatibility only. New receipts never make model
|
|
18
19
|
// output authoritative for Requirement identity.
|
|
@@ -28,37 +29,103 @@ function workFor(task: WorkflowSupervisorTask) {
|
|
|
28
29
|
const controllerHome = contractText(task, 'controller_home');
|
|
29
30
|
const repoId = contractText(task, 'repo_id');
|
|
30
31
|
const workId = contractText(task, 'work_id');
|
|
31
|
-
return controllerHome &&
|
|
32
|
+
return controllerHome && workId
|
|
33
|
+
? getWorkContract({ controllerHome, scopeKey: repoId ?? SEMANTIC_SCOPE_KEY }, workId) : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
function boundWorkLineage(task: WorkflowSupervisorTask, requirementId?: string): WorkflowContractValidation | undefined {
|
|
38
|
+
const controllerHome = contractText(task, 'controller_home');
|
|
39
|
+
const repoId = contractText(task, 'repo_id');
|
|
40
|
+
const workId = requirementId ? undefined : contractText(task, 'work_id');
|
|
41
|
+
if (!controllerHome || (!workId && !requirementId)) return undefined;
|
|
42
|
+
// Repository rows are compatibility evidence; portable semantic rows win by identity.
|
|
43
|
+
const scopes = repoId ? [repoId, SEMANTIC_SCOPE_KEY] : [SEMANTIC_SCOPE_KEY];
|
|
44
|
+
const byId = new Map<string, { workId: string; requirementId?: string; parentWorkId?: string;
|
|
45
|
+
semanticParentWorkId?: string; dependsOnWorkIds?: string[]; predecessorWorkId?: string;
|
|
46
|
+
supersedes?: string[]; supersededBy?: string; semanticState?: SemanticWorkState; error?: string }>();
|
|
47
|
+
for (const scopeKey of new Set(scopes)) {
|
|
48
|
+
const snapshot = readWorkContractLineageSnapshot({ controllerHome, scopeKey });
|
|
49
|
+
if (snapshot.contracts.length + snapshot.invalid.length >= 5_000) return unsupported('work_lineage_read_limit');
|
|
50
|
+
for (const work of [...snapshot.contracts, ...snapshot.invalid]) byId.set(work.workId, work);
|
|
51
|
+
}
|
|
52
|
+
const relevant = new Set<string>(workId ? [workId] : [...byId.values()]
|
|
53
|
+
.filter((work) => work.requirementId === requirementId).map((work) => work.workId));
|
|
54
|
+
let expanded = true;
|
|
55
|
+
while (expanded) {
|
|
56
|
+
expanded = false;
|
|
57
|
+
for (const work of byId.values()) {
|
|
58
|
+
if (relevant.has(work.parentWorkId ?? '') || relevant.has(work.semanticParentWorkId ?? '')
|
|
59
|
+
|| relevant.has(work.predecessorWorkId ?? '') || (work.supersedes ?? []).some((id) => relevant.has(id))) {
|
|
60
|
+
if (!relevant.has(work.workId)) { relevant.add(work.workId); expanded = true; }
|
|
61
|
+
}
|
|
62
|
+
if (relevant.has(work.workId)) {
|
|
63
|
+
for (const dependency of [...(work.dependsOnWorkIds ?? []), ...(work.supersededBy ? [work.supersededBy] : [])]) {
|
|
64
|
+
if (!relevant.has(dependency)) { relevant.add(dependency); expanded = true; }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const blockers = [...relevant].filter((id) => {
|
|
70
|
+
const work = byId.get(id);
|
|
71
|
+
return !work || Boolean(work.error) || !work.semanticState || !isTerminalSemanticWorkState(work.semanticState);
|
|
72
|
+
}).sort();
|
|
73
|
+
return blockers.length > 0
|
|
74
|
+
? { valid: false, reason: 'bound_work_lineage_not_terminal', evidence: blockers.slice(0, 8) }
|
|
75
|
+
: undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateWorkCompletion(task: WorkflowSupervisorTask): WorkflowContractValidation {
|
|
79
|
+
const work = workFor(task);
|
|
80
|
+
return work && work.semanticState === 'completed'
|
|
81
|
+
? boundWorkLineage(task) ?? { valid: true, reason: 'work_terminal_committed' }
|
|
82
|
+
: unsupported('work_not_completed');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validateRequirementCompletion(task: WorkflowSupervisorTask, proposal: WorkflowSupervisorProposal): WorkflowContractValidation {
|
|
86
|
+
const requirement = requirementFor(task, proposal);
|
|
87
|
+
if (!requirement) return unsupported('requirement_not_found');
|
|
88
|
+
if (requirement.state !== 'done' || !requirement.semanticAcceptance) {
|
|
89
|
+
return { valid: false, reason: 'requirement_not_semantically_done:' + requirement.state, evidence: requirement.auditRefs.slice(-8) };
|
|
90
|
+
}
|
|
91
|
+
return boundWorkLineage(task, requirement.requirementId)
|
|
92
|
+
?? { valid: true, reason: 'requirement_semantic_acceptance_committed', evidence: requirement.auditRefs.slice(-8) };
|
|
32
93
|
}
|
|
33
94
|
|
|
34
95
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
96
|
+
* A standalone task may intentionally have no semantic target. When it names a
|
|
97
|
+
* Work or Requirement, DONE must use the same committed semantic proof as the
|
|
98
|
+
* corresponding typed completion contract. Repository provenance alone is not
|
|
99
|
+
* a semantic binding, and proposal text never creates one.
|
|
38
100
|
*/
|
|
101
|
+
function validateSemanticCompletionBinding(task: WorkflowSupervisorTask, proposal: WorkflowSupervisorProposal): WorkflowContractValidation {
|
|
102
|
+
const workId = contractText(task, 'work_id');
|
|
103
|
+
const requirementId = contractText(task, 'requirement_id');
|
|
104
|
+
if (workId && requirementId) return unsupported('semantic_completion_binding_ambiguous');
|
|
105
|
+
if (workId) return validateWorkCompletion(task);
|
|
106
|
+
if (requirementId) return validateRequirementCompletion(task, proposal);
|
|
107
|
+
return { valid: true, reason: 'model_semantic_completion' };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Read committed semantic Goal and explicit Work lineage without changing either authority. */
|
|
39
111
|
export function forgeWorkflowSupervisorValidators(): WorkflowSupervisorValidators {
|
|
40
112
|
return {
|
|
41
113
|
completionContract: async (task, proposal) => {
|
|
42
114
|
if (task.completionContract.kind === 'model_semantic_completion') {
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
if (task.completionContract.kind === 'forge_work_done') {
|
|
46
|
-
const work = workFor(task);
|
|
47
|
-
return work && isTerminalSemanticWorkState(work.semanticState)
|
|
48
|
-
? { valid: true, reason: 'work_terminal_committed' }
|
|
49
|
-
: unsupported('work_not_terminal');
|
|
115
|
+
return validateSemanticCompletionBinding(task, proposal);
|
|
50
116
|
}
|
|
117
|
+
if (task.completionContract.kind === 'forge_work_done') return validateWorkCompletion(task);
|
|
51
118
|
if (!['forge_requirement_done', 'forge_dynamic_requirement_done'].includes(String(task.completionContract.kind ?? ''))) return unsupported('completion_contract_kind_unsupported');
|
|
52
|
-
|
|
53
|
-
if (!requirement) return unsupported('requirement_not_found');
|
|
54
|
-
if (requirement.state !== 'done' || !requirement.semanticAcceptance) {
|
|
55
|
-
return { valid: false, reason: `requirement_not_semantically_done:${requirement.state}`, evidence: requirement.auditRefs.slice(-8) };
|
|
56
|
-
}
|
|
57
|
-
return { valid: true, reason: 'requirement_semantic_acceptance_committed', evidence: requirement.auditRefs.slice(-8) };
|
|
119
|
+
return validateRequirementCompletion(task, proposal);
|
|
58
120
|
},
|
|
59
121
|
userBlockerPolicy: async (task, proposal) => {
|
|
60
122
|
if (task.userBlockerPolicy.kind === 'model_semantic_user_blocker') {
|
|
61
|
-
|
|
123
|
+
// Tool visibility is a provider failure, not a decision or permission
|
|
124
|
+
// only the user can supply. Use the immutable completion evidence;
|
|
125
|
+
// do not add another task state or rely on transient browser text.
|
|
126
|
+
return proposal.evidence.includes('NO_TOOL_RECEIPT')
|
|
127
|
+
? { valid: false, reason: 'tool_unavailable_not_user_blocker' }
|
|
128
|
+
: { valid: true, reason: 'model_semantic_user_blocker' };
|
|
62
129
|
}
|
|
63
130
|
if (task.userBlockerPolicy.kind === 'forge_work_waiting_for_user') {
|
|
64
131
|
const work = workFor(task);
|