@moretea-labs/forge 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +2 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +2 -0
- package/assets/plugin-registry.v1.json +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +17 -0
- package/assets/templates/helpers/inspect-project-state.ts +8 -6
- package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
- package/assets/templates/helpers/workflow-contract.ts +5 -1
- package/docs/architecture/CURRENT.md +12 -4
- package/docs/forge-plugin-management.md +1 -1
- package/docs/operations/controller-performance-and-502.md +19 -0
- package/docs/operations/homebrew.md +2 -2
- package/package.json +5 -2
- package/scripts/architecture-event.ts +2 -820
- package/scripts/architecture-queue.sh +2 -645
- package/scripts/archive-architecture-request.sh +2 -242
- package/scripts/archive-workflow.sh +1 -291
- package/scripts/benchmark-work-efficiency.ts +0 -0
- package/scripts/capability-config.ts +2 -392
- package/scripts/capability-resolver.ts +2 -503
- package/scripts/capture-plan.sh +1 -332
- package/scripts/check-agent-tooling.sh +2 -1531
- package/scripts/check-architecture-sync.sh +2 -302
- package/scripts/check-brain-manifest.sh +2 -253
- package/scripts/check-context-files.sh +2 -47
- package/scripts/check-deploy-sql-order.sh +2 -115
- package/scripts/check-runtime-architecture.mjs +243 -16
- package/scripts/check-skill-version.ts +3 -191
- package/scripts/check-source-duplication.mjs +61 -0
- package/scripts/check-stable-baseline.ts +0 -0
- package/scripts/check-task-sync.sh +2 -63
- package/scripts/check-task-workflow.sh +2 -1000
- package/scripts/codex-handoff-resume.sh +2 -267
- package/scripts/context-contract-sync.sh +2 -514
- package/scripts/contract-run.ts +2 -463
- package/scripts/contract-worktree.sh +1 -702
- package/scripts/ensure-task-workflow.sh +1 -1192
- package/scripts/inspect-project-state.ts +4 -279
- package/scripts/lib/project-init-lib.sh +7 -2
- package/scripts/lib/run-forge-helper.sh +19 -0
- package/scripts/lib/run-forge-helper.ts +26 -0
- package/scripts/migrate-project-template.sh +1 -33
- package/scripts/migrate-workflow-docs.ts +4 -412
- package/scripts/new-plan.sh +1 -180
- package/scripts/new-spec.sh +1 -47
- package/scripts/new-sprint.sh +1 -46
- package/scripts/plan-to-todo.sh +1 -771
- package/scripts/prepare-codex-handoff.sh +2 -201
- package/scripts/prepare-handoff.sh +1 -78
- package/scripts/refresh-current-status.sh +2 -467
- package/scripts/run-governed-gate.ts +3 -0
- package/scripts/select-agent-context-blocks.sh +2 -64
- package/scripts/ship-worktrees.sh +1 -531
- package/scripts/sprint-backlog.sh +1 -834
- package/scripts/stage-runtime-release.ts +2 -1
- package/scripts/summarize-failures.sh +2 -123
- package/scripts/switch-plan.sh +1 -150
- package/scripts/sync-brain-docs.sh +2 -379
- package/scripts/sync-generated-authority.mjs +175 -0
- package/scripts/verify-contract.sh +2 -593
- package/scripts/verify-sprint.sh +1 -500
- package/scripts/workflow-contract.ts +4 -201
- package/scripts/workstream-sync.sh +2 -261
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/commands/plugin.ts +12 -3
- package/src/cli/controller/check-runner.ts +7 -2
- package/src/cli/controller/context-pack.ts +46 -21
- package/src/cli/github/contracts.ts +16 -0
- package/src/cli/github/github.ts +2 -16
- package/src/cli/github/session.ts +2 -1
- package/src/cli/local-bridge/facade-api.ts +37 -47
- package/src/cli/local-bridge/server.ts +2 -2
- package/src/cli/mcp/legacy-tool-service.ts +10 -33
- package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
- package/src/cli/mcp/repository-tools.ts +85 -89
- package/src/cli/mcp/server.ts +57 -13
- package/src/cli/mcp/tool-contract.ts +14 -0
- package/src/cli/mcp/toolset.ts +2 -2
- package/src/cli/mcp/transports/http.ts +23 -0
- package/src/cli/repositories/command-classifier.ts +2 -2
- package/src/cli/repositories/command-executor.ts +24 -6
- package/src/cli/repositories/repository-snapshot.ts +5 -2
- package/src/cli/repositories/structured-git.ts +57 -3
- package/src/cli/repository/inspector.ts +11 -3
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +61 -0
- package/src/runtime/context/semantic-navigation.ts +9 -61
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +18 -25
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
- package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
- package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
- package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
- package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
- package/src/runtime/control-plane/execution/work-handle-store.ts +1 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
- package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
- package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
- package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +4 -43
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +13 -3
- package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
- package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/controller-round-relay.ts +164 -37
- package/src/runtime/control-plane/facade/controller-session-store.ts +183 -24
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +15 -1
- package/src/runtime/control-plane/facade/goal-workloop.ts +329 -148
- package/src/runtime/control-plane/facade/index.ts +2 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +90 -22
- package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
- package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
- package/src/runtime/control-plane/facade/types.ts +68 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +3 -209
- package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +14 -7
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +5 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +13 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +15 -6
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +54 -5
- package/src/runtime/control-plane/launcher/thin-launcher.ts +56 -7
- package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
- package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
- package/src/runtime/control-plane/routing/route-policy.ts +18 -6
- package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
- package/src/runtime/evidence/process-check-execution.ts +18 -0
- package/src/runtime/execution/jobs/child-reference.ts +6 -2
- package/src/runtime/execution/process-runtime/check-result.ts +53 -18
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
- package/src/runtime/execution/process-runtime/command-facade.ts +39 -17
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +20 -3
- package/src/runtime/execution/process-runtime/persisted-check.ts +4 -1
- package/src/runtime/execution/process-runtime/runtime.ts +10 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -12
- package/src/runtime/execution/work-activity.ts +21 -0
- package/src/runtime/execution/workers/executor.ts +18 -13
- package/src/runtime/gateway/mcp/execution-tools.ts +35 -2930
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
- package/src/runtime/gateway/mcp/router.ts +2 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +21 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +805 -1009
- package/src/runtime/plugins/app-store-connect-adapter.ts +93 -28
- package/src/runtime/plugins/browser-adapter.ts +256 -1083
- package/src/runtime/plugins/browser-handoff-host.ts +2 -25
- package/src/runtime/plugins/browser-handoff.ts +18 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +121 -16
- package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
- package/src/runtime/plugins/browser-native-session-service.ts +222 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +10 -0
- package/src/runtime/plugins/browser-session-store.ts +121 -0
- package/src/runtime/plugins/browser-session-types.ts +98 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +4 -4
- package/src/runtime/plugins/external-adapter.ts +92 -58
- package/src/runtime/plugins/macos-capability-broker.ts +0 -8
- package/src/runtime/plugins/store.ts +66 -9
- package/src/runtime/plugins/types.ts +3 -1
- package/src/runtime/recovery/maintenance-executor.ts +61 -25
- package/src/runtime/root/package-connector-service.ts +46 -4
- package/src/runtime/root/package-runtime-release.ts +1 -1
- package/src/runtime/root/package-runtime-service.ts +33 -2
- package/src/runtime/root/release-manifest.ts +137 -16
- package/src/runtime/root/release-materialize.ts +109 -35
- package/src/runtime/root/types.ts +10 -0
- package/src/runtime/standalone-recovery/core.ts +425 -194
- package/src/runtime/standalone-recovery/entry.ts +1 -1
- package/src/runtime/standalone-recovery/installer.ts +1 -1
- package/src/runtime/workflow/schedules/engine.ts +113 -87
- package/src/runtime/workflow/schedules/settlement.ts +26 -23
- package/src/runtime/workflow/schedules/store.ts +79 -10
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
writeControlPlaneRecord,
|
|
6
6
|
} from '../persistence/sqlite-store';
|
|
7
7
|
import type { ControllerType } from '../facade/types';
|
|
8
|
+
import { redactProcessOutput } from '../../../effects/process-runner';
|
|
8
9
|
|
|
9
10
|
const NAMESPACE = 'external_controller_launch_reservation';
|
|
10
11
|
const DEFAULT_TTL_MS = 2 * 60_000;
|
|
@@ -24,6 +25,9 @@ export interface ExternalControllerLaunchReservation {
|
|
|
24
25
|
exitSignal?: string;
|
|
25
26
|
releasedAt?: string;
|
|
26
27
|
releaseReason?: string;
|
|
28
|
+
/** Bounded, redacted provider output captured only during launcher startup. */
|
|
29
|
+
stdoutTail?: string;
|
|
30
|
+
stderrTail?: string;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
export interface LaunchReservationStoreOptions {
|
|
@@ -57,6 +61,14 @@ export function getExternalControllerLaunchReservation(
|
|
|
57
61
|
return active(record(options, workId)?.value);
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
/** Read the latest reservation record including released/failed startup evidence. */
|
|
65
|
+
export function readExternalControllerLaunchReservationRecord(
|
|
66
|
+
options: LaunchReservationStoreOptions,
|
|
67
|
+
workId: string,
|
|
68
|
+
): ExternalControllerLaunchReservation | undefined {
|
|
69
|
+
return record(options, workId)?.value;
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
export function reserveExternalControllerLaunch(
|
|
61
73
|
options: LaunchReservationStoreOptions,
|
|
62
74
|
input: {
|
|
@@ -131,11 +143,43 @@ export function attachExternalControllerLaunchPid(
|
|
|
131
143
|
);
|
|
132
144
|
}
|
|
133
145
|
|
|
146
|
+
export function recordExternalControllerLaunchDiagnostics(
|
|
147
|
+
options: LaunchReservationStoreOptions,
|
|
148
|
+
workId: string,
|
|
149
|
+
reservationId: string,
|
|
150
|
+
diagnostics: { stdoutTail?: string; stderrTail?: string },
|
|
151
|
+
): ExternalControllerLaunchReservation | undefined {
|
|
152
|
+
return withControllerLock(
|
|
153
|
+
options.controllerHome,
|
|
154
|
+
{ scope: 'task', repoId: options.repoId, taskId: `controller-launch-${workId}` },
|
|
155
|
+
`controller-launch-diagnostics:${reservationId}`,
|
|
156
|
+
() => {
|
|
157
|
+
const existing = record(options, workId);
|
|
158
|
+
if (!existing || existing.value.reservationId !== reservationId) return existing?.value;
|
|
159
|
+
const next: ExternalControllerLaunchReservation = {
|
|
160
|
+
...existing.value,
|
|
161
|
+
...(diagnostics.stdoutTail ? { stdoutTail: redactProcessOutput(diagnostics.stdoutTail).slice(-8 * 1024) } : {}),
|
|
162
|
+
...(diagnostics.stderrTail ? { stderrTail: redactProcessOutput(diagnostics.stderrTail).slice(-8 * 1024) } : {}),
|
|
163
|
+
};
|
|
164
|
+
writeControlPlaneRecord(options.controllerHome, {
|
|
165
|
+
namespace: NAMESPACE,
|
|
166
|
+
scope: options.repoId,
|
|
167
|
+
key: workId,
|
|
168
|
+
schemaVersion: 1,
|
|
169
|
+
value: next,
|
|
170
|
+
action: 'external_controller_launch_diagnostics',
|
|
171
|
+
expectedRevision: existing.revision,
|
|
172
|
+
});
|
|
173
|
+
return next;
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
134
178
|
export function recordExternalControllerLaunchExit(
|
|
135
179
|
options: LaunchReservationStoreOptions,
|
|
136
180
|
workId: string,
|
|
137
181
|
reservationId: string,
|
|
138
|
-
exit: { exitCode: number | null; signal: string | null },
|
|
182
|
+
exit: { exitCode: number | null; signal: string | null; stdoutTail?: string; stderrTail?: string },
|
|
139
183
|
): ExternalControllerLaunchReservation | undefined {
|
|
140
184
|
return withControllerLock(
|
|
141
185
|
options.controllerHome,
|
|
@@ -143,16 +187,21 @@ export function recordExternalControllerLaunchExit(
|
|
|
143
187
|
`controller-launch-exit:${reservationId}`,
|
|
144
188
|
() => {
|
|
145
189
|
const existing = record(options, workId);
|
|
146
|
-
if (!existing || existing.value.reservationId !== reservationId
|
|
190
|
+
if (!existing || existing.value.reservationId !== reservationId) return existing?.value;
|
|
147
191
|
const exitedAt = nowIso(options);
|
|
192
|
+
const alreadyReleased = Boolean(existing.value.releasedAt);
|
|
148
193
|
const next: ExternalControllerLaunchReservation = {
|
|
149
194
|
...existing.value,
|
|
150
|
-
expiresAt: exitedAt,
|
|
195
|
+
expiresAt: alreadyReleased ? existing.value.expiresAt : exitedAt,
|
|
151
196
|
exitedAt,
|
|
152
197
|
...(exit.exitCode === null ? {} : { exitCode: exit.exitCode }),
|
|
153
198
|
...(exit.signal ? { exitSignal: exit.signal } : {}),
|
|
154
|
-
|
|
155
|
-
|
|
199
|
+
...(exit.stdoutTail ? { stdoutTail: redactProcessOutput(exit.stdoutTail).slice(-8 * 1024) } : {}),
|
|
200
|
+
...(exit.stderrTail ? { stderrTail: redactProcessOutput(exit.stderrTail).slice(-8 * 1024) } : {}),
|
|
201
|
+
...(alreadyReleased ? {} : {
|
|
202
|
+
releasedAt: exitedAt,
|
|
203
|
+
releaseReason: `process_exit:code=${String(exit.exitCode ?? 'null')}:signal=${exit.signal ?? 'none'}`.slice(0, 240),
|
|
204
|
+
}),
|
|
156
205
|
};
|
|
157
206
|
writeControlPlaneRecord(options.controllerHome, {
|
|
158
207
|
namespace: NAMESPACE,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '../facade';
|
|
8
8
|
import {
|
|
9
9
|
attachExternalControllerLaunchPid,
|
|
10
|
+
recordExternalControllerLaunchDiagnostics,
|
|
10
11
|
recordExternalControllerLaunchExit,
|
|
11
12
|
releaseExternalControllerLaunchReservation,
|
|
12
13
|
reserveExternalControllerLaunch,
|
|
@@ -16,6 +17,7 @@ import { getControllerSession } from '../facade/controller-session-store';
|
|
|
16
17
|
import { codexMcpConfigArgs, resolveProviderMcpBootstrap, type ProviderMcpBootstrap } from './provider-mcp-bootstrap';
|
|
17
18
|
import { getChatgptWorkConversationBinding } from './chatgpt-work-binding-store';
|
|
18
19
|
import { repositoryChildProcessEnvironment } from '../../shared/process-environment';
|
|
20
|
+
import { redactProcessOutput } from '../../../effects/process-runner';
|
|
19
21
|
|
|
20
22
|
export interface ThinLauncherRequest {
|
|
21
23
|
controllerType: Exclude<ControllerType, 'human'>;
|
|
@@ -47,7 +49,22 @@ export interface ThinLauncherResult {
|
|
|
47
49
|
|
|
48
50
|
const LAUNCHER_STARTUP_GRACE_MS = 250;
|
|
49
51
|
const CODEX_WORK_CLAIM_TIMEOUT_MS = 30_000;
|
|
52
|
+
const CODEX_WORK_CLAIM_SETTLEMENT_GRACE_MS = 1_500;
|
|
50
53
|
const CODEX_WORK_CLAIM_POLL_INTERVAL_MS = 100;
|
|
54
|
+
const STARTUP_DIAGNOSTIC_BYTES = 8 * 1024;
|
|
55
|
+
|
|
56
|
+
function appendStartupDiagnosticTail(current: string, chunk: unknown): string {
|
|
57
|
+
const text = redactProcessOutput(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk ?? ''));
|
|
58
|
+
const buffer = Buffer.from(`${current}${text}`, 'utf8');
|
|
59
|
+
return buffer.length <= STARTUP_DIAGNOSTIC_BYTES
|
|
60
|
+
? buffer.toString('utf8')
|
|
61
|
+
: buffer.subarray(buffer.length - STARTUP_DIAGNOSTIC_BYTES).toString('utf8');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function startupDiagnosticSummary(stdoutTail: string, stderrTail: string): string {
|
|
65
|
+
const value = redactProcessOutput(stderrTail.trim() || stdoutTail.trim()).replace(/\s+/g, ' ').slice(0, 1200);
|
|
66
|
+
return value ? `; startup_output=${value}` : '';
|
|
67
|
+
}
|
|
51
68
|
|
|
52
69
|
interface ExternalControllerClaimExpectation {
|
|
53
70
|
controllerType: 'codex';
|
|
@@ -59,6 +76,7 @@ interface ExternalControllerClaimExpectation {
|
|
|
59
76
|
export interface ThinLauncherDependencies {
|
|
60
77
|
resolveProviderMcpBootstrap?: typeof resolveProviderMcpBootstrap;
|
|
61
78
|
claimTimeoutMs?: number;
|
|
79
|
+
claimSettlementGraceMs?: number;
|
|
62
80
|
claimPollIntervalMs?: number;
|
|
63
81
|
}
|
|
64
82
|
|
|
@@ -102,11 +120,31 @@ async function awaitExternalControllerStartup(
|
|
|
102
120
|
workId: string,
|
|
103
121
|
reservationId: string,
|
|
104
122
|
claimExpectation?: ExternalControllerClaimExpectation,
|
|
105
|
-
options: { claimTimeoutMs?: number; claimPollIntervalMs?: number } = {},
|
|
123
|
+
options: { claimTimeoutMs?: number; claimSettlementGraceMs?: number; claimPollIntervalMs?: number } = {},
|
|
106
124
|
): Promise<void> {
|
|
107
125
|
await new Promise<void>((resolve, reject) => {
|
|
108
126
|
let startupSettled = false;
|
|
109
127
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
128
|
+
let stdoutTail = '';
|
|
129
|
+
let stderrTail = '';
|
|
130
|
+
const captureStdout = (chunk: unknown) => { stdoutTail = appendStartupDiagnosticTail(stdoutTail, chunk); };
|
|
131
|
+
const captureStderr = (chunk: unknown) => { stderrTail = appendStartupDiagnosticTail(stderrTail, chunk); };
|
|
132
|
+
child.stdout?.on('data', captureStdout);
|
|
133
|
+
child.stderr?.on('data', captureStderr);
|
|
134
|
+
const closeStartupPipes = () => {
|
|
135
|
+
child.stdout?.off('data', captureStdout);
|
|
136
|
+
child.stderr?.off('data', captureStderr);
|
|
137
|
+
child.stdout?.destroy();
|
|
138
|
+
child.stderr?.destroy();
|
|
139
|
+
};
|
|
140
|
+
const persistDiagnostics = () => {
|
|
141
|
+
if (!stdoutTail && !stderrTail) return;
|
|
142
|
+
try {
|
|
143
|
+
recordExternalControllerLaunchDiagnostics(stores.work, workId, reservationId, { stdoutTail, stderrTail });
|
|
144
|
+
} catch {
|
|
145
|
+
// Startup diagnostics are evidence only; preserve the primary ownership/claim outcome.
|
|
146
|
+
}
|
|
147
|
+
};
|
|
110
148
|
const clearTimer = () => {
|
|
111
149
|
if (timer) clearTimeout(timer);
|
|
112
150
|
timer = undefined;
|
|
@@ -126,9 +164,12 @@ async function awaitExternalControllerStartup(
|
|
|
126
164
|
if (startupSettled) return;
|
|
127
165
|
startupSettled = true;
|
|
128
166
|
clearTimer();
|
|
167
|
+
persistDiagnostics();
|
|
168
|
+
closeStartupPipes();
|
|
129
169
|
if (terminate) terminateUnclaimedChild();
|
|
130
170
|
releaseFailure(reason);
|
|
131
|
-
|
|
171
|
+
const diagnostics = startupDiagnosticSummary(stdoutTail, stderrTail);
|
|
172
|
+
reject(diagnostics ? new Error(`${error.message}${diagnostics}`) : error);
|
|
132
173
|
};
|
|
133
174
|
const expectedClaimMatches = (): { matches: boolean; mismatch?: string } => {
|
|
134
175
|
if (!claimExpectation) return { matches: true };
|
|
@@ -156,6 +197,8 @@ async function awaitExternalControllerStartup(
|
|
|
156
197
|
recordExternalControllerLaunchExit(stores.work, workId, reservationId, {
|
|
157
198
|
exitCode,
|
|
158
199
|
signal: signal ? String(signal) : null,
|
|
200
|
+
stdoutTail,
|
|
201
|
+
stderrTail,
|
|
159
202
|
});
|
|
160
203
|
} catch {
|
|
161
204
|
// Exit evidence is best-effort after another authority has already released the reservation.
|
|
@@ -163,22 +206,26 @@ async function awaitExternalControllerStartup(
|
|
|
163
206
|
if (startupSettled) return;
|
|
164
207
|
startupSettled = true;
|
|
165
208
|
clearTimer();
|
|
209
|
+
closeStartupPipes();
|
|
166
210
|
const phase = claimExpectation ? 'before exact Work claim became live' : 'during startup grace';
|
|
167
|
-
reject(new Error(`LAUNCHER_STARTUP_FAILED: external Controller exited ${phase} (code=${String(exitCode ?? 'null')}, signal=${signal ?? 'none'})`));
|
|
211
|
+
reject(new Error(`LAUNCHER_STARTUP_FAILED: external Controller exited ${phase} (code=${String(exitCode ?? 'null')}, signal=${signal ?? 'none'})${startupDiagnosticSummary(stdoutTail, stderrTail)}`));
|
|
168
212
|
});
|
|
169
213
|
|
|
170
214
|
if (!claimExpectation) {
|
|
171
215
|
timer = setTimeout(() => {
|
|
172
216
|
if (startupSettled) return;
|
|
173
217
|
startupSettled = true;
|
|
218
|
+
closeStartupPipes();
|
|
174
219
|
resolve();
|
|
175
220
|
}, LAUNCHER_STARTUP_GRACE_MS);
|
|
176
221
|
return;
|
|
177
222
|
}
|
|
178
223
|
|
|
179
224
|
const claimTimeoutMs = Math.max(100, Math.min(options.claimTimeoutMs ?? CODEX_WORK_CLAIM_TIMEOUT_MS, 60_000));
|
|
225
|
+
const claimSettlementGraceMs = Math.max(0, Math.min(options.claimSettlementGraceMs ?? CODEX_WORK_CLAIM_SETTLEMENT_GRACE_MS, 5_000));
|
|
180
226
|
const claimPollIntervalMs = Math.max(10, Math.min(options.claimPollIntervalMs ?? CODEX_WORK_CLAIM_POLL_INTERVAL_MS, 1_000));
|
|
181
227
|
const claimDeadline = Date.now() + claimTimeoutMs;
|
|
228
|
+
const claimSettlementDeadline = claimDeadline + claimSettlementGraceMs;
|
|
182
229
|
const pollClaim = () => {
|
|
183
230
|
if (startupSettled) return;
|
|
184
231
|
let observation: ReturnType<typeof expectedClaimMatches>;
|
|
@@ -213,14 +260,15 @@ async function awaitExternalControllerStartup(
|
|
|
213
260
|
}
|
|
214
261
|
startupSettled = true;
|
|
215
262
|
timer = undefined;
|
|
263
|
+
closeStartupPipes();
|
|
216
264
|
resolve();
|
|
217
265
|
}, LAUNCHER_STARTUP_GRACE_MS);
|
|
218
266
|
return;
|
|
219
267
|
}
|
|
220
|
-
if (Date.now() >=
|
|
268
|
+
if (Date.now() >= claimSettlementDeadline) {
|
|
221
269
|
fail(
|
|
222
|
-
new Error(`LAUNCHER_CLAIM_TIMEOUT: Codex pid=${String(child.pid ?? 'unknown')} did not claim exact Work ${workId} through Forge MCP within ${claimTimeoutMs}ms`),
|
|
223
|
-
`claim_timeout:${claimTimeoutMs}ms`,
|
|
270
|
+
new Error(`LAUNCHER_CLAIM_TIMEOUT: Codex pid=${String(child.pid ?? 'unknown')} did not claim exact Work ${workId} through Forge MCP within ${claimTimeoutMs}ms plus ${claimSettlementGraceMs}ms settlement grace`),
|
|
271
|
+
`claim_timeout:${claimTimeoutMs}ms+${claimSettlementGraceMs}ms_settlement`,
|
|
224
272
|
true,
|
|
225
273
|
);
|
|
226
274
|
return;
|
|
@@ -345,7 +393,7 @@ export async function launchSuperController(
|
|
|
345
393
|
const child = spawn(invocation.executable, invocation.args, {
|
|
346
394
|
cwd: request.cwd,
|
|
347
395
|
detached: true,
|
|
348
|
-
stdio: 'ignore',
|
|
396
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
349
397
|
env: launcherProcessEnvironment(mcpBootstrap?.env ?? process.env),
|
|
350
398
|
});
|
|
351
399
|
attachExternalControllerLaunchPid(stores.work, work.workId, reservation.reservationId, child.pid);
|
|
@@ -362,6 +410,7 @@ export async function launchSuperController(
|
|
|
362
410
|
} : undefined,
|
|
363
411
|
{
|
|
364
412
|
claimTimeoutMs: dependencies.claimTimeoutMs,
|
|
413
|
+
claimSettlementGraceMs: dependencies.claimSettlementGraceMs,
|
|
365
414
|
claimPollIntervalMs: dependencies.claimPollIntervalMs,
|
|
366
415
|
},
|
|
367
416
|
);
|
|
@@ -165,18 +165,6 @@ export function updateRequirement(
|
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
/** @deprecated Migration/legacy compatibility only. New Plan creation must bind Plan.requirementId and derive active Plan slices by query. */
|
|
169
|
-
export function setRequirementPlan(
|
|
170
|
-
options: RequirementStoreOptions,
|
|
171
|
-
input: { requirementId: string; planId: string; action?: string },
|
|
172
|
-
): Requirement {
|
|
173
|
-
return updateRequirement(options, {
|
|
174
|
-
requirementId: input.requirementId,
|
|
175
|
-
action: input.action ?? 'requirement_active_plan_bound',
|
|
176
|
-
mutate: (current) => ({ ...current, activePlanId: id(input.planId) }),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
168
|
export interface RequirementCompletionInput {
|
|
181
169
|
requirementId: string;
|
|
182
170
|
work: Pick<WorkContract,
|
|
@@ -2,16 +2,18 @@ import { RepoActor } from './actor';
|
|
|
2
2
|
|
|
3
3
|
export class RepoActorRegistry {
|
|
4
4
|
private readonly controllerHome: string;
|
|
5
|
+
private readonly defaultMaxConcurrentWorkers: number;
|
|
5
6
|
private readonly actors = new Map<string, RepoActor>();
|
|
6
|
-
constructor(controllerHome: string) {
|
|
7
|
+
constructor(controllerHome: string, options: { maxConcurrentWorkers?: number } = {}) {
|
|
7
8
|
this.controllerHome = controllerHome;
|
|
9
|
+
this.defaultMaxConcurrentWorkers = Math.max(1, options.maxConcurrentWorkers ?? 4);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
get(repoId: string): RepoActor {
|
|
11
13
|
let actor = this.actors.get(repoId);
|
|
12
14
|
if (!actor) {
|
|
13
15
|
actor = new RepoActor(this.controllerHome, repoId, {
|
|
14
|
-
maxConcurrentWorkers: Number(process.env.FORGE_PER_REPO_WORKERS ??
|
|
16
|
+
maxConcurrentWorkers: Number(process.env.FORGE_PER_REPO_WORKERS ?? this.defaultMaxConcurrentWorkers),
|
|
15
17
|
});
|
|
16
18
|
this.actors.set(repoId, actor);
|
|
17
19
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
|
|
3
|
-
export const ROUTE_POLICY_VERSION = 'route-policy-
|
|
3
|
+
export const ROUTE_POLICY_VERSION = 'route-policy-v5' as const;
|
|
4
4
|
|
|
5
5
|
export type RouteExecutionMode = 'direct_control' | 'goal_workloop' | 'handoff_only';
|
|
6
6
|
export type RouteWorkMode = 'direct_edit' | 'bounded_work' | 'quick_agent' | 'issue_task';
|
|
@@ -50,6 +50,10 @@ export interface RoutePolicyInput {
|
|
|
50
50
|
dirty?: boolean;
|
|
51
51
|
checkoutId?: string;
|
|
52
52
|
fingerprint?: string;
|
|
53
|
+
/** Canonical typed placement constraint resolved before routing. */
|
|
54
|
+
placement?: 'current' | 'isolated' | 'auto';
|
|
55
|
+
/** Admission fence: Direct Control/current-main mutation is not permitted. */
|
|
56
|
+
directMainProhibited?: boolean;
|
|
53
57
|
};
|
|
54
58
|
policy: {
|
|
55
59
|
risk?: string;
|
|
@@ -234,9 +238,11 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
|
|
|
234
238
|
const protectedPath = paths.some((path) => PROTECTED_PATH.test(path));
|
|
235
239
|
const explicitMode = input.intent.explicitMode;
|
|
236
240
|
const explicitParallelMode = explicitMode === 'scale';
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
241
|
+
const placementIsolationRequired = input.workspace.placement === 'isolated'
|
|
242
|
+
|| input.workspace.directMainProhibited === true;
|
|
243
|
+
const requiresIsolation = placementIsolationRequired
|
|
244
|
+
|| input.recovery.isolationRequired === true
|
|
245
|
+
|| explicitParallelMode;
|
|
240
246
|
const requiresRecovery = input.recovery.required === true
|
|
241
247
|
|| explicitMode === 'release'
|
|
242
248
|
|| explicitMode === 'scale';
|
|
@@ -295,6 +301,8 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
|
|
|
295
301
|
if (explicitMode) reasons.push({ code: `explicit_${explicitMode}`, message: `Explicit -${explicitMode} mode overrides automatic work topology while policy gates remain authoritative.` });
|
|
296
302
|
if (protectedPath) reasons.push({ code: 'protected_path', message: 'The predicted scope includes a protected or release-sensitive path.' });
|
|
297
303
|
if (requiresRecovery) reasons.push({ code: 'recovery_required', message: 'The operation needs resumable Work and bounded recovery.' });
|
|
304
|
+
if (input.workspace.placement === 'isolated') reasons.push({ code: 'placement_isolated', message: 'Typed Work admission requires an isolated workspace.' });
|
|
305
|
+
if (input.workspace.directMainProhibited === true) reasons.push({ code: 'direct_main_prohibited', message: 'Typed Work admission forbids the Direct Control/current-main mutation lane.' });
|
|
298
306
|
if (requiresIsolation) reasons.push({ code: 'isolation_required', message: 'The operation requires an isolated checkout or serialized lane.' });
|
|
299
307
|
if (input.intent.requiresLongRunningChecks) reasons.push({ code: 'long_checks', message: 'Long-running checks may use a lightweight handle; duration alone does not require durable Work.' });
|
|
300
308
|
if (input.intent.requiresInvestigation) reasons.push({ code: 'investigation', message: 'Investigation is required before or during implementation.' });
|
|
@@ -303,7 +311,11 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
|
|
|
303
311
|
|
|
304
312
|
const explicitBoundedMode = explicitMode === 'plan'
|
|
305
313
|
|| explicitMode === 'release';
|
|
306
|
-
const
|
|
314
|
+
const directModeCanRemainFast = explicitMode === 'direct'
|
|
315
|
+
&& !requiresIsolation
|
|
316
|
+
&& !requiresRecovery
|
|
317
|
+
&& !coordinationRequired;
|
|
318
|
+
const complex = directModeCanRemainFast
|
|
307
319
|
? false
|
|
308
320
|
: coordinationRequired
|
|
309
321
|
|| explicitBoundedMode
|
|
@@ -320,7 +332,7 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
|
|
|
320
332
|
// Work topology is independent from executor/provider choice. Independent or
|
|
321
333
|
// parallel deliverables stay in the durable Goal Workloop and are decomposed by
|
|
322
334
|
// PlanContract/Work rather than introducing another project-level lifecycle.
|
|
323
|
-
const workMode: RouteWorkMode =
|
|
335
|
+
const workMode: RouteWorkMode = directModeCanRemainFast
|
|
324
336
|
? 'direct_edit'
|
|
325
337
|
: coordinationRequired
|
|
326
338
|
? 'bounded_work'
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type WorkspacePlacementMode = 'current' | 'isolated' | 'auto';
|
|
2
|
+
|
|
3
|
+
export interface WorkspaceAdmissionConstraintInput {
|
|
4
|
+
workspaceMode?: WorkspacePlacementMode;
|
|
5
|
+
requireWorktree?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Forbid repository mutation through the current/direct-main lane. This is
|
|
8
|
+
* an admission constraint, not a routing hint: callers must never silently
|
|
9
|
+
* downgrade it to Direct Control.
|
|
10
|
+
*/
|
|
11
|
+
directMainProhibited?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface WorkspaceAdmissionConstraint {
|
|
15
|
+
workspaceMode: WorkspacePlacementMode;
|
|
16
|
+
requireWorktree: boolean;
|
|
17
|
+
directMainProhibited: boolean;
|
|
18
|
+
requiresIsolation: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type WorkspaceAdmissionResolution =
|
|
22
|
+
| { ok: true; constraint: WorkspaceAdmissionConstraint }
|
|
23
|
+
| { ok: false; code: 'WORKSPACE_PLACEMENT_CONSTRAINT_CONFLICT'; message: string };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Canonicalize Work placement before Route Policy runs. Placement is a policy
|
|
27
|
+
* fence: explicit isolation / direct-main prohibition has precedence over an
|
|
28
|
+
* operator request for the fast Direct Control topology.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveWorkspaceAdmissionConstraint(
|
|
31
|
+
input: WorkspaceAdmissionConstraintInput = {},
|
|
32
|
+
): WorkspaceAdmissionResolution {
|
|
33
|
+
const workspaceMode = input.workspaceMode ?? 'auto';
|
|
34
|
+
const directMainProhibited = input.directMainProhibited === true;
|
|
35
|
+
|
|
36
|
+
if (workspaceMode === 'current' && input.requireWorktree === true) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
code: 'WORKSPACE_PLACEMENT_CONSTRAINT_CONFLICT',
|
|
40
|
+
message: 'workspaceMode=current conflicts with requireWorktree=true.',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (workspaceMode === 'isolated' && input.requireWorktree === false) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
code: 'WORKSPACE_PLACEMENT_CONSTRAINT_CONFLICT',
|
|
47
|
+
message: 'workspaceMode=isolated conflicts with requireWorktree=false.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (workspaceMode === 'current' && directMainProhibited) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
code: 'WORKSPACE_PLACEMENT_CONSTRAINT_CONFLICT',
|
|
54
|
+
message: 'workspaceMode=current conflicts with directMainProhibited=true because the current lane cannot prove a non-main placement at admission time.',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const requiresIsolation = workspaceMode === 'isolated'
|
|
59
|
+
|| input.requireWorktree === true
|
|
60
|
+
|| directMainProhibited;
|
|
61
|
+
return {
|
|
62
|
+
ok: true,
|
|
63
|
+
constraint: {
|
|
64
|
+
workspaceMode: requiresIsolation ? 'isolated' : workspaceMode,
|
|
65
|
+
requireWorktree: requiresIsolation,
|
|
66
|
+
directMainProhibited,
|
|
67
|
+
requiresIsolation,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-bound Check identity shared by Process Runtime and Work validation.
|
|
3
|
+
*
|
|
4
|
+
* This is durable evidence metadata, not Process lifecycle state. Keeping the
|
|
5
|
+
* contract outside Process Runtime prevents WorkHandle persistence from
|
|
6
|
+
* depending on the Process implementation layer.
|
|
7
|
+
*/
|
|
8
|
+
export interface ProcessCheckExecutionIdentity {
|
|
9
|
+
schemaVersion: 1;
|
|
10
|
+
checkId: string;
|
|
11
|
+
cacheKey: string;
|
|
12
|
+
revision: string;
|
|
13
|
+
definitionDigest: string;
|
|
14
|
+
environmentFingerprint: string;
|
|
15
|
+
timeoutMs: number;
|
|
16
|
+
reuseScope: 'repository' | 'checkout';
|
|
17
|
+
scopeKey: string;
|
|
18
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { ExecutionJob } from './types';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
export interface ChildReferenceReceiptLike {
|
|
4
|
+
childReference?: unknown;
|
|
5
|
+
result?: Record<string, unknown>;
|
|
6
|
+
}
|
|
3
7
|
|
|
4
8
|
/** Durable pointer from a short-lived parent Execution Job to its child Agent Run. */
|
|
5
9
|
export interface ExecutionChildReference {
|
|
@@ -72,7 +76,7 @@ export function childReferenceFromUnknown(value: unknown): ExecutionChildReferen
|
|
|
72
76
|
return pickChildReference(value as Record<string, unknown>);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
|
-
export function childReferenceFromReceipt(receipt:
|
|
79
|
+
export function childReferenceFromReceipt(receipt: ChildReferenceReceiptLike | undefined): ExecutionChildReference | undefined {
|
|
76
80
|
if (!receipt) return undefined;
|
|
77
81
|
return childReferenceFromUnknown(receipt.childReference)
|
|
78
82
|
?? childReferenceFromUnknown(receipt.result);
|
|
@@ -66,6 +66,37 @@ export interface PersistedCheckTerminalEvidence {
|
|
|
66
66
|
infrastructureReason?: string;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
export interface TerminalCheckEvidenceClassificationInput {
|
|
70
|
+
processError?: { code: string; message: string };
|
|
71
|
+
structuredPresent: boolean;
|
|
72
|
+
structuredMatches: boolean;
|
|
73
|
+
legacyPresent: boolean;
|
|
74
|
+
legacyMatches: boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Compatibility-level classifier for terminal Check evidence. Keep this pure so
|
|
79
|
+
* legacy/public callers and the Process Runtime record adapter share one semantic
|
|
80
|
+
* authority instead of carrying duplicate decision trees.
|
|
81
|
+
*/
|
|
82
|
+
export function classifyTerminalCheckEvidence(
|
|
83
|
+
input: TerminalCheckEvidenceClassificationInput,
|
|
84
|
+
): PersistedCheckTerminalEvidence {
|
|
85
|
+
if (input.structuredMatches || input.legacyMatches) return { state: 'matched' };
|
|
86
|
+
if (!input.structuredPresent && !input.legacyPresent && input.processError?.message?.trim()) {
|
|
87
|
+
const reason = input.processError.message.trim().slice(0, 512);
|
|
88
|
+
return {
|
|
89
|
+
state: 'process_runtime_failed_before_result',
|
|
90
|
+
warning: `check process failed before structured result receipt: ${reason}`,
|
|
91
|
+
infrastructureReason: reason,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (input.structuredPresent || input.legacyPresent) {
|
|
95
|
+
return { state: 'mismatch', warning: 'check result receipt did not match the terminal Process semantic identity' };
|
|
96
|
+
}
|
|
97
|
+
return { state: 'missing', warning: 'check result receipt is missing for the terminal Check Process' };
|
|
98
|
+
}
|
|
99
|
+
|
|
69
100
|
/**
|
|
70
101
|
* Classify one terminal Check Process using the structured result emitted by
|
|
71
102
|
* the check-runner sidecar. A Process Runtime/admission failure can terminate
|
|
@@ -74,9 +105,15 @@ export interface PersistedCheckTerminalEvidence {
|
|
|
74
105
|
* rejected the candidate. Missing/mismatched result identity also fails closed
|
|
75
106
|
* as infrastructure rather than manufacturing acceptance evidence.
|
|
76
107
|
*/
|
|
108
|
+
export interface LegacyCheckEvidenceLike {
|
|
109
|
+
cacheKey?: string;
|
|
110
|
+
failureClass?: PersistedCheckResultReceipt['failureClass'];
|
|
111
|
+
}
|
|
112
|
+
|
|
77
113
|
export function classifyPersistedCheckTerminalEvidence(
|
|
78
114
|
record: ManagedProcessRecord,
|
|
79
115
|
expectedCheckId: string,
|
|
116
|
+
options: { legacyEvidence?: LegacyCheckEvidenceLike } = {},
|
|
80
117
|
): PersistedCheckTerminalEvidence {
|
|
81
118
|
const structured = readPersistedCheckResultReceipt(record.origin?.checkResultReceiptPath);
|
|
82
119
|
const structuredMatches = Boolean(
|
|
@@ -85,22 +122,20 @@ export function classifyPersistedCheckTerminalEvidence(
|
|
|
85
122
|
&& structured.checkId === expectedCheckId
|
|
86
123
|
&& structured.cacheKey === record.checkExecution.cacheKey,
|
|
87
124
|
);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
return { state: 'missing', warning: 'check result receipt is missing for the terminal Check Process' };
|
|
125
|
+
const legacy = options.legacyEvidence;
|
|
126
|
+
const legacyMatches = Boolean(
|
|
127
|
+
legacy?.cacheKey
|
|
128
|
+
&& record.checkExecution?.cacheKey
|
|
129
|
+
&& legacy.cacheKey === record.checkExecution.cacheKey,
|
|
130
|
+
);
|
|
131
|
+
const classified = classifyTerminalCheckEvidence({
|
|
132
|
+
processError: record.error,
|
|
133
|
+
structuredPresent: Boolean(structured),
|
|
134
|
+
structuredMatches,
|
|
135
|
+
legacyPresent: Boolean(legacy),
|
|
136
|
+
legacyMatches,
|
|
137
|
+
});
|
|
138
|
+
return classified.state === 'matched'
|
|
139
|
+
? { ...classified, failureClass: structuredMatches ? structured?.failureClass : legacy?.failureClass }
|
|
140
|
+
: classified;
|
|
106
141
|
}
|
|
@@ -16,6 +16,7 @@ interface ParsedArgs {
|
|
|
16
16
|
expectedCheckFingerprint: string;
|
|
17
17
|
resultReceiptPath?: string;
|
|
18
18
|
cleanupRoot?: string;
|
|
19
|
+
isolatedControllerHome?: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
function requiredValue(argv: string[], flag: string): string {
|
|
@@ -38,6 +39,7 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
38
39
|
expectedCheckFingerprint: requiredValue(argv, '--expected-check-fingerprint'),
|
|
39
40
|
resultReceiptPath: argv.includes('--result-receipt') ? requiredValue(argv, '--result-receipt') : undefined,
|
|
40
41
|
cleanupRoot: argv.includes('--cleanup-root') ? requiredValue(argv, '--cleanup-root') : undefined,
|
|
42
|
+
isolatedControllerHome: argv.includes('--isolated-controller-home') ? requiredValue(argv, '--isolated-controller-home') : undefined,
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -51,7 +53,11 @@ export async function runPersistedCheckSidecar(argv = process.argv.slice(2)): Pr
|
|
|
51
53
|
throw new Error('CHECK_SNAPSHOT_CHANGED: registered check changed before Process Runtime execution');
|
|
52
54
|
}
|
|
53
55
|
const identity = controllerCheckExecutionIdentity(root, args.checkId, args.timeoutMs, snapshot);
|
|
54
|
-
const result = await runControllerCheckAsync(root, args.checkId, {
|
|
56
|
+
const result = await runControllerCheckAsync(root, args.checkId, {
|
|
57
|
+
requestedTimeoutMs: args.timeoutMs,
|
|
58
|
+
snapshot,
|
|
59
|
+
isolatedControllerHome: args.isolatedControllerHome,
|
|
60
|
+
});
|
|
55
61
|
if (args.resultReceiptPath) {
|
|
56
62
|
writePersistedCheckResultReceipt(args.resultReceiptPath, {
|
|
57
63
|
checkId: args.checkId,
|