@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,5 +1,6 @@
|
|
|
1
|
+
import { writeAtomicTextFile } from '../shared/atomic-text-file';
|
|
1
2
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
-
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync
|
|
3
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from 'fs';
|
|
3
4
|
import { homedir } from 'os';
|
|
4
5
|
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
|
5
6
|
import { resolveControllerHome } from '../../cli/repositories/controller-home';
|
|
@@ -37,13 +38,6 @@ function xml(value: string): string {
|
|
|
37
38
|
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
function atomicWrite(path: string, content: string, mode = 0o600): void {
|
|
41
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
42
|
-
const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
|
|
43
|
-
writeFileSync(temporary, content, { encoding: 'utf8', mode });
|
|
44
|
-
renameSync(temporary, path);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
41
|
export function forgeRuntimeServicePaths(controllerHome: string): ForgeRuntimeServicePaths {
|
|
48
42
|
const home = resolveControllerHome(controllerHome);
|
|
49
43
|
const serviceRoot = join(home, 'runtime', 'service');
|
|
@@ -194,6 +188,91 @@ async function retireLegacyBrowserAutomationLaunchAgent(controllerHome: string):
|
|
|
194
188
|
rmSync(legacy.installedPlistPath, { force: true });
|
|
195
189
|
}
|
|
196
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Read the previously installed exact launchd contract only when config.json
|
|
193
|
+
* cannot be validated. Both canonical source and installed contracts must be
|
|
194
|
+
* identical and must still identify the active immutable Runtime release.
|
|
195
|
+
* A stale/mismatched plist never supplies a guessed listener port.
|
|
196
|
+
*/
|
|
197
|
+
function verifiedRuntimeLaunchdContract(controllerHome: string): { port: number; args: string[] } | undefined {
|
|
198
|
+
try {
|
|
199
|
+
const paths = forgeRuntimeServicePaths(controllerHome);
|
|
200
|
+
const active = readActiveRuntimeRelease(paths.controllerHome);
|
|
201
|
+
if (!active || !activeRuntimeEntrypoint(paths.controllerHome)) return undefined;
|
|
202
|
+
if (!existsSync(paths.sourcePlistPath) || !existsSync(paths.installedPlistPath)) return undefined;
|
|
203
|
+
if (statSync(paths.sourcePlistPath).size > 128 * 1024 || statSync(paths.installedPlistPath).size > 128 * 1024) return undefined;
|
|
204
|
+
const source = readFileSync(paths.sourcePlistPath, 'utf8');
|
|
205
|
+
if (source !== readFileSync(paths.installedPlistPath, 'utf8')) return undefined;
|
|
206
|
+
const parseXmlString = (raw: string): string | undefined => {
|
|
207
|
+
if (/&(?!(?:amp|lt|gt|quot|apos);)/.test(raw) || /[<>]/.test(raw)) return undefined;
|
|
208
|
+
return raw.replace(/&(amp|lt|gt|quot|apos);/g, (_, entity: string) =>
|
|
209
|
+
({ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" })[entity] ?? '');
|
|
210
|
+
};
|
|
211
|
+
const label = /<key>Label<\/key>\s*<string>([^<]*)<\/string>/.exec(source);
|
|
212
|
+
if (!label || parseXmlString(label[1]!) !== paths.label) return undefined;
|
|
213
|
+
const program = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(source);
|
|
214
|
+
if (!program) return undefined;
|
|
215
|
+
const args: string[] = [];
|
|
216
|
+
const text = program[1]!;
|
|
217
|
+
const itemPattern = /\s*<string>([^<]*)<\/string>\s*/g;
|
|
218
|
+
let cursor = 0;
|
|
219
|
+
for (let match = itemPattern.exec(text); match; match = itemPattern.exec(text)) {
|
|
220
|
+
if (match.index !== cursor) return undefined;
|
|
221
|
+
const value = parseXmlString(match[1]!);
|
|
222
|
+
if (value === undefined) return undefined;
|
|
223
|
+
args.push(value);
|
|
224
|
+
cursor = itemPattern.lastIndex;
|
|
225
|
+
}
|
|
226
|
+
if (cursor !== text.length || args[0] !== resolve(paths.activeEntrypointPath)) return undefined;
|
|
227
|
+
const exactValue = (flag: string): string | undefined => {
|
|
228
|
+
const indices = args.flatMap((arg, index) => arg === flag ? [index] : []);
|
|
229
|
+
return indices.length === 1 ? args[indices[0]! + 1] : undefined;
|
|
230
|
+
};
|
|
231
|
+
if (exactValue('--controller-home') !== paths.controllerHome
|
|
232
|
+
|| exactValue('--release-manifest') !== resolve(active.manifestPath)) return undefined;
|
|
233
|
+
const portString = exactValue('--port');
|
|
234
|
+
if (!portString || !/^[1-9]\d{0,4}$/.test(portString)) return undefined;
|
|
235
|
+
const port = Number(portString);
|
|
236
|
+
return port <= 65_535 ? { port, args } : undefined;
|
|
237
|
+
} catch {
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Read-only projection of the original installed launchd contract. */
|
|
243
|
+
export function verifiedRuntimePortFromLaunchdContract(controllerHome: string): number | undefined {
|
|
244
|
+
return verifiedRuntimeLaunchdContract(controllerHome)?.port;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Use the one existing config writer only after service and listener quiescence. */
|
|
248
|
+
export function restoreRuntimeConfigFromVerifiedLaunchdContract(controllerHome: string): boolean {
|
|
249
|
+
const proven = verifiedRuntimeLaunchdContract(controllerHome);
|
|
250
|
+
if (!proven) return false;
|
|
251
|
+
const arg = (flag: string): string | undefined => {
|
|
252
|
+
const indices = proven.args.flatMap((value, index) => value === flag ? [index] : []);
|
|
253
|
+
return indices.length === 1 ? proven.args[indices[0]! + 1] : undefined;
|
|
254
|
+
};
|
|
255
|
+
const host = arg('--host');
|
|
256
|
+
const authTokenFile = arg('--auth-token-file');
|
|
257
|
+
if (!host || !authTokenFile) return false;
|
|
258
|
+
try {
|
|
259
|
+
const topologyString = arg('--deployment-topology');
|
|
260
|
+
const topology = topologyString ? JSON.parse(topologyString) as RuntimeDeploymentTopology : undefined;
|
|
261
|
+
writeForgeRuntimeServiceConfig({
|
|
262
|
+
schemaVersion: 1,
|
|
263
|
+
controllerHome: forgeRuntimeServicePaths(controllerHome).controllerHome,
|
|
264
|
+
host,
|
|
265
|
+
port: proven.port,
|
|
266
|
+
authTokenFile,
|
|
267
|
+
...(arg('--repo') ? { repositoryRoot: arg('--repo') } : {}),
|
|
268
|
+
...(topology ? { topology } : {}),
|
|
269
|
+
});
|
|
270
|
+
return true;
|
|
271
|
+
} catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
197
276
|
export function activeRuntimeLaunchSpec(controllerHome: string): ActiveRuntimeLaunchSpec | undefined {
|
|
198
277
|
const home = resolveControllerHome(controllerHome);
|
|
199
278
|
const active = readActiveRuntimeRelease(home);
|
|
@@ -346,8 +425,8 @@ export function ensureForgeRuntimeLaunchAgentContract(
|
|
|
346
425
|
? readFileSync(desired.paths.installedPlistPath, 'utf8')
|
|
347
426
|
: undefined;
|
|
348
427
|
const changed = existingSource !== desired.plist || (input.installUserLaunchAgent === true && existingInstalled !== desired.plist);
|
|
349
|
-
if (existingSource !== desired.plist)
|
|
350
|
-
if (input.installUserLaunchAgent === true && existingInstalled !== desired.plist)
|
|
428
|
+
if (existingSource !== desired.plist) writeAtomicTextFile(desired.paths.sourcePlistPath, desired.plist);
|
|
429
|
+
if (input.installUserLaunchAgent === true && existingInstalled !== desired.plist) writeAtomicTextFile(desired.paths.installedPlistPath, desired.plist, 0o644);
|
|
351
430
|
return { paths: desired.paths, mode: desired.mode, changed };
|
|
352
431
|
}
|
|
353
432
|
|
|
@@ -367,7 +446,7 @@ export function writeForgeRuntimeServiceConfig(input: ForgeRuntimeServiceConfig)
|
|
|
367
446
|
const config = validateForgeRuntimeServiceConfig(input);
|
|
368
447
|
const paths = forgeRuntimeServicePaths(config.controllerHome);
|
|
369
448
|
mkdirSync(join(paths.serviceRoot, 'logs'), { recursive: true, mode: 0o700 });
|
|
370
|
-
|
|
449
|
+
writeAtomicTextFile(paths.configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
371
450
|
return { config, paths };
|
|
372
451
|
}
|
|
373
452
|
|
|
@@ -84,12 +84,6 @@ export interface RuntimeReleaseManifest {
|
|
|
84
84
|
browserAutomationHelperEntrypoint?: 'browser-automation-helper';
|
|
85
85
|
browserAutomationHelperArtifactIdentity?: string;
|
|
86
86
|
browserAutomationHelperContractIdentity?: string;
|
|
87
|
-
codeGraphNodeEntrypoint?: 'codegraph-node';
|
|
88
|
-
codeGraphNodeArtifactIdentity?: string;
|
|
89
|
-
codeGraphSidecarEntrypoint?: 'codegraph-sidecar.cjs';
|
|
90
|
-
codeGraphSidecarArtifactIdentity?: string;
|
|
91
|
-
codeGraphLibraryRoot?: 'codegraph-lib';
|
|
92
|
-
codeGraphLibraryArtifactIdentity?: string;
|
|
93
87
|
packageRoot?: 'package';
|
|
94
88
|
packageArtifactIdentity?: string;
|
|
95
89
|
controllerUiRoot?: 'ui-dist';
|
|
@@ -12,11 +12,12 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
bindChatgptWorkConversation,
|
|
14
14
|
getChatgptWorkConversationBinding,
|
|
15
|
+
listChatgptWorkBindingsForConversation,
|
|
15
16
|
rebindChatgptWorkConversation,
|
|
16
17
|
type ChatgptWorkConversationBinding,
|
|
17
18
|
} from '../../../adapters/chatgpt/work-conversation-binding-store';
|
|
18
19
|
import { readRequirement } from '../control-plane/persistence/requirement-store';
|
|
19
|
-
import { getWorkflowSupervisorCurrentConversation, getWorkflowSupervisorTaskByConversationId, registerWorkflowSupervisorTask, reserveWorkflowSupervisorEnrollment } from '../../../supervisor/client';
|
|
20
|
+
import { bindWorkflowSupervisorScheduledWake, getWorkflowSupervisorCurrentConversation, getWorkflowSupervisorTask, getWorkflowSupervisorTaskByConversationId, getWorkflowSupervisorTaskStall, registerWorkflowSupervisorTask, reserveWorkflowSupervisorEnrollment, wakeWorkflowSupervisorScheduledTask } from '../../../supervisor/client';
|
|
20
21
|
import { resolveWorkflowSupervisorForgeHome, workflowSupervisorSocketPath } from '../../../supervisor/paths';
|
|
21
22
|
import type { WorkflowSupervisorLifecycleHooks, WorkflowSupervisorTask } from '../../../supervisor/types';
|
|
22
23
|
import { getRuntimeWriteClaim } from './write-fence';
|
|
@@ -130,15 +131,37 @@ export function inheritWorkflowSupervisorConversationBinding(
|
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
function workflowSupervisorContractText(task: WorkflowSupervisorTask, key: string): string | undefined {
|
|
134
|
+
export function workflowSupervisorContractText(task: WorkflowSupervisorTask, key: string): string | undefined {
|
|
134
135
|
const value = task.completionContract[key] ?? task.userBlockerPolicy[key];
|
|
135
136
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
function workflowSupervisorOriginWorkId(
|
|
139
|
+
function workflowSupervisorOriginWorkId(
|
|
140
|
+
task: WorkflowSupervisorTask,
|
|
141
|
+
repoId: string,
|
|
142
|
+
controllerHome: string,
|
|
143
|
+
conversationId = task.conversationId,
|
|
144
|
+
): string | undefined {
|
|
139
145
|
const legacyWorkPrefix = `forge:${repoId}:work:`;
|
|
140
|
-
|
|
146
|
+
const explicit = workflowSupervisorContractText(task, 'work_id')
|
|
141
147
|
?? (task.taskId.startsWith(legacyWorkPrefix) ? task.taskId.slice(legacyWorkPrefix.length) || undefined : undefined);
|
|
148
|
+
const original = explicit ? getWorkContract({ controllerHome, repoId }, explicit) : undefined;
|
|
149
|
+
if (explicit && (!original || semanticWorkState(original) === 'open')) return explicit;
|
|
150
|
+
const active = listChatgptWorkBindingsForConversation({ controllerHome, repoId }, conversationId)
|
|
151
|
+
.map(({ workId }) => getWorkContract({ controllerHome, repoId }, workId))
|
|
152
|
+
.filter((work): work is NonNullable<typeof work> => Boolean(work && semanticWorkState(work) === 'open'));
|
|
153
|
+
// A terminal predecessor retains its audit and conversation binding. Only
|
|
154
|
+
// explicit successor lineage within the same Requirement can transfer the
|
|
155
|
+
// active Work projection; siblings cannot inherit the predecessor's task.
|
|
156
|
+
if (explicit) {
|
|
157
|
+
const successors = active.filter((work) => work.predecessorWorkId === explicit
|
|
158
|
+
&& original?.requirementId && work.requirementId === original.requirementId);
|
|
159
|
+
return successors.length === 1 && active.length === 1 ? successors[0]!.workId : explicit;
|
|
160
|
+
}
|
|
161
|
+
// Standalone enrollment predates Work binding. It may project into one
|
|
162
|
+
// unambiguous open Work, without creating a second conversation authority.
|
|
163
|
+
if (task.continuationPolicy.kind !== 'standalone_supervisor') return undefined;
|
|
164
|
+
return active.length === 1 ? active[0]!.workId : undefined;
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
/**
|
|
@@ -152,7 +175,7 @@ function workflowSupervisorWorkProgressFingerprint(controllerHome: string, task:
|
|
|
152
175
|
const repoId = workflowSupervisorContractText(task, 'repo_id');
|
|
153
176
|
const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
|
|
154
177
|
if (!repoId || (taskControllerHome && taskControllerHome !== controllerHome)) return undefined;
|
|
155
|
-
const workId = workflowSupervisorOriginWorkId(task, repoId);
|
|
178
|
+
const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
|
|
156
179
|
if (!workId) return undefined;
|
|
157
180
|
const work = getWorkContract({ controllerHome, repoId }, workId);
|
|
158
181
|
if (!work) return undefined;
|
|
@@ -195,20 +218,44 @@ function createForgeWorkflowSupervisorBrowserTaskActive(controllerHome: string):
|
|
|
195
218
|
const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
|
|
196
219
|
if (!repoId || taskControllerHome !== controllerHome) return false;
|
|
197
220
|
if (requirementId && readRequirement({ controllerHome }, requirementId)?.value.state === 'cancelled') return false;
|
|
198
|
-
const workId = workflowSupervisorOriginWorkId(task, repoId);
|
|
221
|
+
const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
|
|
199
222
|
if (!requirementId && !workId) return false;
|
|
200
|
-
// An
|
|
201
|
-
//
|
|
202
|
-
|
|
223
|
+
// An independent Requirement Goal can outlive an execution Work; an exact
|
|
224
|
+
// Work-owned Supervisor cannot initiate another Send after that Work closes.
|
|
225
|
+
if (terminalWorkSupervisorStopReason(controllerHome, task)) return false;
|
|
203
226
|
const binding = workId ? getChatgptWorkConversationBinding({ controllerHome, repoId }, workId) : undefined;
|
|
204
227
|
return !binding || binding.conversationId === task.conversationId;
|
|
205
228
|
};
|
|
206
229
|
}
|
|
207
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Only an exact Work-owned Supervisor task follows that Work's terminal state.
|
|
233
|
+
* Requirement-owned autonomous Goals and standalone tasks keep their own
|
|
234
|
+
* explicit lifecycle; a completed lower-layer carrier is not goal completion.
|
|
235
|
+
*/
|
|
236
|
+
function terminalWorkSupervisorStopReason(controllerHome: string, task: WorkflowSupervisorTask): string | undefined {
|
|
237
|
+
const repoId = workflowSupervisorContractText(task, 'repo_id');
|
|
238
|
+
const workId = workflowSupervisorContractText(task, 'work_id');
|
|
239
|
+
const taskHome = workflowSupervisorContractText(task, 'controller_home');
|
|
240
|
+
if (!repoId || !workId || (taskHome && taskHome !== controllerHome)) return undefined;
|
|
241
|
+
if (task.taskId !== `forge:${repoId}:work:${workId}`
|
|
242
|
+
|| task.continuationPolicy.kind === 'standalone_supervisor') return undefined;
|
|
243
|
+
const work = getWorkContract({ controllerHome, repoId }, workId);
|
|
244
|
+
if (!work) return undefined;
|
|
245
|
+
const state = semanticWorkState(work);
|
|
246
|
+
return state !== 'open'
|
|
247
|
+
? `Exact Work ${workId} is ${state}; stop its Work-owned Supervisor continuation without changing the original Work or any successor Schedule.`
|
|
248
|
+
: undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
208
251
|
export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): WorkflowSupervisorLifecycleHooks {
|
|
209
252
|
const browserTaskActive = createForgeWorkflowSupervisorBrowserTaskActive(controllerHome);
|
|
210
253
|
return {
|
|
254
|
+
terminalWorkStopReasonForTask: (task) => terminalWorkSupervisorStopReason(controllerHome, task),
|
|
211
255
|
canonicalObjectiveForTask: (task) => {
|
|
256
|
+
// Standalone Supervisor owns its full objective; Work execution context
|
|
257
|
+
// must never silently replace the original autonomous business goal.
|
|
258
|
+
if (task.continuationPolicy.kind === 'standalone_supervisor') return undefined;
|
|
212
259
|
const repoId = workflowSupervisorContractText(task, 'repo_id');
|
|
213
260
|
const requirementId = workflowSupervisorContractText(task, 'requirement_id');
|
|
214
261
|
const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
|
|
@@ -220,7 +267,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
|
|
|
220
267
|
const outcome = readRequirement({ controllerHome }, requirementId)?.value.outcomeStatement?.trim();
|
|
221
268
|
if (outcome) return outcome;
|
|
222
269
|
}
|
|
223
|
-
const workId = workflowSupervisorOriginWorkId(task, repoId);
|
|
270
|
+
const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
|
|
224
271
|
if (!workId) return undefined;
|
|
225
272
|
const work = getWorkContract({ controllerHome, repoId }, workId);
|
|
226
273
|
// Legacy / independent Work without Requirement authority keeps the
|
|
@@ -231,9 +278,21 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
|
|
|
231
278
|
const repoId = workflowSupervisorContractText(task, 'repo_id');
|
|
232
279
|
const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
|
|
233
280
|
if (!repoId || (taskControllerHome && taskControllerHome !== controllerHome)) return undefined;
|
|
234
|
-
const workId = workflowSupervisorOriginWorkId(task, repoId);
|
|
281
|
+
const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
|
|
235
282
|
if (!workId) return undefined;
|
|
236
|
-
|
|
283
|
+
const cognitive = renderCognitiveAttentionForWork({ controllerHome, repoId, workId, query: task.objective, maxItems: 6, maxBytes: 5 * 1024 });
|
|
284
|
+
if (task.continuationPolicy.kind !== 'standalone_supervisor') return cognitive;
|
|
285
|
+
const work = getWorkContract({ controllerHome, repoId }, workId);
|
|
286
|
+
if (!work) return cognitive;
|
|
287
|
+
// Thin evidence, not a second task/goal authority. Recent semantic refs
|
|
288
|
+
// carry the concrete next step even when Cognitive recall is empty.
|
|
289
|
+
const refs = (work.semanticResultRefs ?? []).slice(-5).map((ref) => ref.slice(0, 500));
|
|
290
|
+
return [
|
|
291
|
+
`Canonical Work: ${workId} (read-only execution context; Supervisor objective remains authoritative).`,
|
|
292
|
+
`Work objective: ${work.objective.slice(0, 600)}`,
|
|
293
|
+
...refs.map((ref) => `Work evidence: ${ref}`),
|
|
294
|
+
...(cognitive ? [cognitive] : []),
|
|
295
|
+
].join('\n').slice(0, 8_000);
|
|
237
296
|
},
|
|
238
297
|
effectDispatchEvidence: ({ task }) => {
|
|
239
298
|
const claim = getRuntimeWriteClaim();
|
|
@@ -255,7 +314,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
|
|
|
255
314
|
if (effect.kind !== 'enrollment') return undefined;
|
|
256
315
|
const repoId = workflowSupervisorContractText(task, 'repo_id');
|
|
257
316
|
const requirementId = workflowSupervisorContractText(task, 'requirement_id');
|
|
258
|
-
const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId) : undefined;
|
|
317
|
+
const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId, controllerHome) : undefined;
|
|
259
318
|
const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
|
|
260
319
|
if (!repoId || (!requirementId && !workId) || taskControllerHome !== controllerHome) return undefined;
|
|
261
320
|
const store = { controllerHome, repoId };
|
|
@@ -308,16 +367,26 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
|
|
|
308
367
|
browserTaskActive,
|
|
309
368
|
bootstrapConversationBound: (task, context) => {
|
|
310
369
|
const repoId = workflowSupervisorContractText(task, 'repo_id');
|
|
311
|
-
const
|
|
370
|
+
const declaredWorkId = workflowSupervisorContractText(task, 'work_id');
|
|
312
371
|
const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
|
|
313
|
-
if (!repoId ||
|
|
314
|
-
|
|
372
|
+
if (!repoId || taskControllerHome !== controllerHome) return;
|
|
373
|
+
// On migration, retain the sole active Work projection even for a
|
|
374
|
+
// standalone task without work_id or a completed predecessor. Prefer
|
|
375
|
+
// a binding already moved to the new conversation on duplicate callbacks.
|
|
376
|
+
const currentWorkId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
|
|
377
|
+
const bindingWorkId = context?.migratedFromConversationId
|
|
378
|
+
? (currentWorkId && currentWorkId !== declaredWorkId
|
|
379
|
+
? currentWorkId
|
|
380
|
+
: workflowSupervisorOriginWorkId(task, repoId, controllerHome, context.migratedFromConversationId) ?? declaredWorkId)
|
|
381
|
+
: declaredWorkId;
|
|
382
|
+
if (!bindingWorkId) return;
|
|
383
|
+
const existing = getChatgptWorkConversationBinding({ controllerHome, repoId }, bindingWorkId);
|
|
315
384
|
if (existing && existing.conversationId !== task.conversationId) {
|
|
316
385
|
if (!context?.migratedFromConversationId || context.migratedFromConversationId !== existing.conversationId) {
|
|
317
386
|
throw new Error('WORKFLOW_SUPERVISOR_BOOTSTRAP_WORK_CONVERSATION_CONFLICT');
|
|
318
387
|
}
|
|
319
388
|
rebindChatgptWorkConversation({ controllerHome, repoId }, {
|
|
320
|
-
workId,
|
|
389
|
+
workId: bindingWorkId,
|
|
321
390
|
previousConversationId: existing.conversationId,
|
|
322
391
|
conversationUrl: task.conversationUrl,
|
|
323
392
|
localAlias: existing.localAlias,
|
|
@@ -326,7 +395,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
|
|
|
326
395
|
}
|
|
327
396
|
if (!existing) {
|
|
328
397
|
bindChatgptWorkConversation({ controllerHome, repoId }, {
|
|
329
|
-
workId,
|
|
398
|
+
workId: bindingWorkId,
|
|
330
399
|
conversationUrl: task.conversationUrl,
|
|
331
400
|
localAlias: 'Forge autonomous execution',
|
|
332
401
|
});
|
|
@@ -336,7 +405,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
|
|
|
336
405
|
if (effect.kind !== 'enrollment') return;
|
|
337
406
|
const repoId = workflowSupervisorContractText(task, 'repo_id');
|
|
338
407
|
const requirementId = workflowSupervisorContractText(task, 'requirement_id');
|
|
339
|
-
const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId) : undefined;
|
|
408
|
+
const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId, controllerHome) : undefined;
|
|
340
409
|
const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
|
|
341
410
|
if (!repoId || (!requirementId && !workId) || taskControllerHome !== controllerHome) return;
|
|
342
411
|
const store = { controllerHome, repoId };
|
|
@@ -408,9 +477,62 @@ export async function bindCurrentWorkflowSupervisorConversationForWork(
|
|
|
408
477
|
return { status: 'bound', binding };
|
|
409
478
|
}
|
|
410
479
|
|
|
480
|
+
export async function bindWorkflowSupervisorScheduledWakeForWork(
|
|
481
|
+
options: { controllerHome: string; repoId: string },
|
|
482
|
+
workId: string,
|
|
483
|
+
scheduleId: string,
|
|
484
|
+
): Promise<{ status: 'bound'; taskId: string } | { status: 'not_enrolled' | 'binding_deferred' }> {
|
|
485
|
+
const forgeHome = resolveWorkflowSupervisorForgeHome(options.controllerHome);
|
|
486
|
+
if (!existsSync(workflowSupervisorSocketPath(forgeHome))) return { status: 'binding_deferred' };
|
|
487
|
+
const exactTaskId = taskIdForWork(options.repoId, workId);
|
|
488
|
+
let task = await getWorkflowSupervisorTask(forgeHome, exactTaskId);
|
|
489
|
+
if (!task) {
|
|
490
|
+
const binding = getChatgptWorkConversationBinding(options, workId);
|
|
491
|
+
if (binding) {
|
|
492
|
+
const authority = await getWorkflowSupervisorTaskByConversationId(forgeHome, binding.conversationId);
|
|
493
|
+
if (authority) {
|
|
494
|
+
const ownerRepoId = typeof authority.task.completionContract.repo_id === 'string'
|
|
495
|
+
? authority.task.completionContract.repo_id : authority.task.continuationPolicy.repo_id;
|
|
496
|
+
if (ownerRepoId !== options.repoId || authority.task.completionContract.work_id !== workId) {
|
|
497
|
+
throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_WORK_IDENTITY_MISMATCH');
|
|
498
|
+
}
|
|
499
|
+
task = authority.task;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (!task) return { status: 'not_enrolled' };
|
|
504
|
+
const bound = await bindWorkflowSupervisorScheduledWake(forgeHome, {
|
|
505
|
+
taskId: task.taskId,
|
|
506
|
+
repoId: options.repoId,
|
|
507
|
+
workId,
|
|
508
|
+
scheduleId,
|
|
509
|
+
});
|
|
510
|
+
return { status: 'bound', taskId: bound.task.taskId };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export async function wakeWorkflowSupervisorScheduledTaskForWork(
|
|
514
|
+
options: { controllerHome: string; repoId: string },
|
|
515
|
+
workId: string,
|
|
516
|
+
scheduleId: string,
|
|
517
|
+
occurrenceId: string,
|
|
518
|
+
): Promise<{ status: 'woken' | 'not_waiting' | 'not_enrolled' | 'binding_deferred'; taskId?: string; effectId?: string }> {
|
|
519
|
+
const binding = await bindWorkflowSupervisorScheduledWakeForWork(options, workId, scheduleId);
|
|
520
|
+
if (binding.status !== 'bound') return binding;
|
|
521
|
+
if (!binding.taskId) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_BINDING_TASK_ID_MISSING');
|
|
522
|
+
const forgeHome = resolveWorkflowSupervisorForgeHome(options.controllerHome);
|
|
523
|
+
const stall = await getWorkflowSupervisorTaskStall(forgeHome, binding.taskId);
|
|
524
|
+
if (stall.state !== 'waiting') return { status: 'not_waiting', taskId: binding.taskId };
|
|
525
|
+
const wake = await wakeWorkflowSupervisorScheduledTask(forgeHome, {
|
|
526
|
+
taskId: binding.taskId, scheduleId, occurrenceId,
|
|
527
|
+
});
|
|
528
|
+
return { status: 'woken', taskId: binding.taskId, effectId: wake.effect.effectId };
|
|
529
|
+
}
|
|
530
|
+
|
|
411
531
|
export async function ensureWorkflowSupervisorEnrollmentForWork(
|
|
412
532
|
options: { controllerHome: string; repoId: string },
|
|
413
533
|
workId: string,
|
|
534
|
+
scheduledWakeScheduleId?: string,
|
|
535
|
+
scheduleOccurrenceId?: string,
|
|
414
536
|
): Promise<{ status: WorkflowSupervisorEnrollmentStatus; taskId?: string; effectId?: string; reason?: string }> {
|
|
415
537
|
const boundary = workflowSupervisorBoundaryForWork(options, workId);
|
|
416
538
|
if (boundary.status === 'not_eligible') return { status: boundary.status };
|
|
@@ -421,6 +543,55 @@ export async function ensureWorkflowSupervisorEnrollmentForWork(
|
|
|
421
543
|
const taskId = taskIdForWork(options.repoId, workId);
|
|
422
544
|
if (!existsSync(workflowSupervisorSocketPath(forgeHome))) return { status: 'daemon_unavailable', taskId };
|
|
423
545
|
|
|
546
|
+
// A Work-derived Supervisor id is a durable task reservation, not a
|
|
547
|
+
// registration template to rewrite on every Scheduler wake. In particular,
|
|
548
|
+
// an explicitly stopped historical task remains terminal even if its Work is
|
|
549
|
+
// still open, and a live bootstrap retains its original immutable objective
|
|
550
|
+
// and effect when the lower Work wording changes. Do not mint a replacement
|
|
551
|
+
// send, mutate Task identity, or use lower ControllerRound state to revive it.
|
|
552
|
+
const existingWorkTask = await getWorkflowSupervisorTask(forgeHome, taskId);
|
|
553
|
+
if (existingWorkTask) {
|
|
554
|
+
const existingRepoId = typeof existingWorkTask.completionContract.repo_id === 'string'
|
|
555
|
+
? existingWorkTask.completionContract.repo_id
|
|
556
|
+
: existingWorkTask.continuationPolicy.repo_id;
|
|
557
|
+
const existingWorkId = existingWorkTask.completionContract.work_id;
|
|
558
|
+
if (existingRepoId !== options.repoId || existingWorkId !== workId) {
|
|
559
|
+
throw new Error('WORKFLOW_SUPERVISOR_TASK_ID_CONFLICT');
|
|
560
|
+
}
|
|
561
|
+
let stall = await getWorkflowSupervisorTaskStall(forgeHome, taskId);
|
|
562
|
+
if (stall.state === 'terminal') {
|
|
563
|
+
return { status: 'lower_layer_not_ready', reason: 'WORKFLOW_SUPERVISOR_TASK_TERMINAL' };
|
|
564
|
+
}
|
|
565
|
+
if (scheduledWakeScheduleId) {
|
|
566
|
+
await bindWorkflowSupervisorScheduledWake(forgeHome, {
|
|
567
|
+
taskId,
|
|
568
|
+
repoId: options.repoId,
|
|
569
|
+
workId,
|
|
570
|
+
scheduleId: scheduledWakeScheduleId,
|
|
571
|
+
});
|
|
572
|
+
stall = await getWorkflowSupervisorTaskStall(forgeHome, taskId);
|
|
573
|
+
if (stall.state === 'waiting') {
|
|
574
|
+
if (!scheduleOccurrenceId) return { status: 'lower_layer_not_ready', taskId, reason: 'SCHEDULE_OCCURRENCE_ID_REQUIRED' };
|
|
575
|
+
const wake = await wakeWorkflowSupervisorScheduledTask(forgeHome, {
|
|
576
|
+
taskId, scheduleId: scheduledWakeScheduleId, occurrenceId: scheduleOccurrenceId,
|
|
577
|
+
});
|
|
578
|
+
return { status: 'enrolled', taskId, effectId: wake.effect.effectId };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const ownedRequirementId = typeof existingWorkTask.completionContract.requirement_id === 'string'
|
|
582
|
+
? existingWorkTask.completionContract.requirement_id : undefined;
|
|
583
|
+
if (ownedRequirementId !== getWorkContract(options, workId)?.requirementId) {
|
|
584
|
+
throw new Error('WORKFLOW_SUPERVISOR_TASK_REQUIREMENT_CONFLICT');
|
|
585
|
+
}
|
|
586
|
+
if (boundary.status === 'conversation_pending') {
|
|
587
|
+
if (existingWorkTask.continuationPolicy.kind !== 'forge_goal_outer_turn') {
|
|
588
|
+
throw new Error('WORKFLOW_SUPERVISOR_TASK_ID_CONFLICT');
|
|
589
|
+
}
|
|
590
|
+
const effect = await reserveWorkflowSupervisorEnrollment(forgeHome, taskId);
|
|
591
|
+
return { status: 'enrolled', taskId, effectId: effect.effectId };
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
424
595
|
// Once an exact conversation is already owned by a Supervisor task, that
|
|
425
596
|
// task is the outer-turn authority. ControllerRound is only first-admission
|
|
426
597
|
// execution context and must not regain veto power over later continuation.
|
|
@@ -445,6 +616,22 @@ export async function ensureWorkflowSupervisorEnrollmentForWork(
|
|
|
445
616
|
reason: `WORKFLOW_SUPERVISOR_TASK_TERMINAL:${existingAuthority.terminal}`,
|
|
446
617
|
};
|
|
447
618
|
}
|
|
619
|
+
if (scheduledWakeScheduleId) {
|
|
620
|
+
await bindWorkflowSupervisorScheduledWake(forgeHome, {
|
|
621
|
+
taskId: existingAuthority.task.taskId,
|
|
622
|
+
repoId: options.repoId,
|
|
623
|
+
workId,
|
|
624
|
+
scheduleId: scheduledWakeScheduleId,
|
|
625
|
+
});
|
|
626
|
+
const stall = await getWorkflowSupervisorTaskStall(forgeHome, existingAuthority.task.taskId);
|
|
627
|
+
if (stall.state === 'waiting') {
|
|
628
|
+
if (!scheduleOccurrenceId) return { status: 'lower_layer_not_ready', taskId: existingAuthority.task.taskId, reason: 'SCHEDULE_OCCURRENCE_ID_REQUIRED' };
|
|
629
|
+
const wake = await wakeWorkflowSupervisorScheduledTask(forgeHome, {
|
|
630
|
+
taskId: existingAuthority.task.taskId, scheduleId: scheduledWakeScheduleId, occurrenceId: scheduleOccurrenceId,
|
|
631
|
+
});
|
|
632
|
+
return { status: 'enrolled', taskId: existingAuthority.task.taskId, effectId: wake.effect.effectId };
|
|
633
|
+
}
|
|
634
|
+
}
|
|
448
635
|
const effect = await reserveWorkflowSupervisorEnrollment(forgeHome, existingAuthority.task.taskId);
|
|
449
636
|
return { status: 'enrolled', taskId: existingAuthority.task.taskId, effectId: effect.effectId };
|
|
450
637
|
}
|
|
@@ -476,6 +663,7 @@ export async function ensureWorkflowSupervisorEnrollmentForWork(
|
|
|
476
663
|
...(boundary.status === 'outer_turn'
|
|
477
664
|
? { exact_conversation_id: boundary.conversationId, exact_conversation_url: boundary.conversationUrl }
|
|
478
665
|
: { bootstrap: true }),
|
|
666
|
+
...(scheduledWakeScheduleId ? { scheduled_wake_schedule_id: scheduledWakeScheduleId } : {}),
|
|
479
667
|
outer_turn_owner: 'workflow_supervisor',
|
|
480
668
|
},
|
|
481
669
|
userBlockerPolicy: {
|