@fitlab-ai/agent-infra 0.9.5 → 0.9.7
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/dist/bin/internal-cli.js +8 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -4
- package/dist/lib/internal/codex-lifecycle.js +169 -3
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +40 -7
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +111 -60
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +372 -94
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +142 -0
- package/dist/lib/sandbox/control/lifecycle.js +557 -0
- package/dist/lib/sandbox/control/protocol.js +69 -26
- package/dist/lib/sandbox/control/server.js +395 -99
- package/dist/lib/sandbox/control/state.js +191 -0
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +22 -19
- package/dist/lib/sandbox/recovery.js +284 -50
- package/dist/lib/sandbox/workspace-view.js +90 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +17 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +87 -15
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +236 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +109 -2
- package/dist/lib/task/events.js +149 -6
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +365 -15
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +78 -14
- package/dist/lib/task/verification.js +1 -0
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -3
- package/lib/internal/codex-lifecycle.ts +175 -4
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +34 -7
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +70 -25
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +423 -108
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +167 -0
- package/lib/sandbox/control/lifecycle.ts +573 -0
- package/lib/sandbox/control/protocol.ts +121 -68
- package/lib/sandbox/control/server.ts +398 -109
- package/lib/sandbox/control/state.ts +213 -0
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +16 -8
- package/lib/sandbox/recovery.ts +335 -65
- package/lib/sandbox/workspace-view.ts +102 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +24 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +78 -14
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +319 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +208 -7
- package/lib/task/events.ts +131 -5
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +552 -18
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -24
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -10,13 +10,20 @@ import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.
|
|
|
10
10
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
11
11
|
import {
|
|
12
12
|
activateDelegation,
|
|
13
|
+
abortPreparedDelegation,
|
|
13
14
|
completeDelegationStage,
|
|
14
15
|
consumeDelegation,
|
|
16
|
+
dispatchDelegation,
|
|
15
17
|
managedDelegationRole,
|
|
16
18
|
prepareDelegation,
|
|
17
19
|
sealDelegation
|
|
18
20
|
} from './delegation-receipts.ts';
|
|
19
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
DelegationLifecycleProvenance,
|
|
23
|
+
DelegationReceipt,
|
|
24
|
+
DelegationRole,
|
|
25
|
+
DelegationStage
|
|
26
|
+
} from './delegation-receipts.ts';
|
|
20
27
|
import { normalizeAgentClients } from '../agent-clients/config.ts';
|
|
21
28
|
import { inspectPlatformPullRequest } from '../platform/pull-requests.ts';
|
|
22
29
|
import {
|
|
@@ -39,6 +46,7 @@ import type { RepositorySnapshot } from './workspace-snapshot.ts';
|
|
|
39
46
|
import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
|
|
40
47
|
import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
|
|
41
48
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
49
|
+
import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
42
50
|
import {
|
|
43
51
|
CommitIntentError,
|
|
44
52
|
commitIntentPath,
|
|
@@ -74,7 +82,7 @@ type ModelPolicySource = Readonly<{
|
|
|
74
82
|
client: AgentClientId;
|
|
75
83
|
resolvedAt: string;
|
|
76
84
|
}>;
|
|
77
|
-
type
|
|
85
|
+
type ModelPolicyRecovery = Readonly<{
|
|
78
86
|
code: 'MODEL_POLICY_SUPPLEMENTED';
|
|
79
87
|
recoveredAt: string;
|
|
80
88
|
previousSchemaVersion: 1;
|
|
@@ -85,6 +93,44 @@ type OrchestrationRecovery = Readonly<{
|
|
|
85
93
|
pendingDelegation: false;
|
|
86
94
|
resultingStatus: OrchestrationStatus;
|
|
87
95
|
}>;
|
|
96
|
+
type ClientCapabilityRecovery = Readonly<{
|
|
97
|
+
code: 'CLIENT_CAPABILITY_ENABLED';
|
|
98
|
+
recoveredAt: string;
|
|
99
|
+
previousSchemaVersion: 2;
|
|
100
|
+
previousStatus: 'paused';
|
|
101
|
+
previousPause: Readonly<{ code: 'ORCHESTRATION_CLIENT_UNSUPPORTED'; message: string; recoverable: boolean }>;
|
|
102
|
+
client: 'codex';
|
|
103
|
+
guards: Readonly<{
|
|
104
|
+
stepCount: 0;
|
|
105
|
+
nextStage: null;
|
|
106
|
+
baselineEmpty: true;
|
|
107
|
+
receiptCount: 0;
|
|
108
|
+
pendingDelegation: false;
|
|
109
|
+
commitAuthorizationUnused: true;
|
|
110
|
+
completionEvidenceAbsent: true;
|
|
111
|
+
commitIntentAbsent: true;
|
|
112
|
+
}>;
|
|
113
|
+
resultingStatus: 'running';
|
|
114
|
+
}>;
|
|
115
|
+
type SchemaMigrationRecovery = Readonly<{
|
|
116
|
+
code: 'SCHEMA_V3_MIGRATED';
|
|
117
|
+
recoveredAt: string;
|
|
118
|
+
previousSchemaVersion: 2;
|
|
119
|
+
previousStatus: 'paused';
|
|
120
|
+
previousPause: Readonly<{ code: string; message: string; recoverable: boolean }>;
|
|
121
|
+
guards: Readonly<{
|
|
122
|
+
stepCount: 0;
|
|
123
|
+
nextStage: null;
|
|
124
|
+
baselineEmpty: true;
|
|
125
|
+
receiptCount: 0;
|
|
126
|
+
pendingDelegation: false;
|
|
127
|
+
commitAuthorizationUnused: true;
|
|
128
|
+
completionEvidenceAbsent: true;
|
|
129
|
+
commitIntentAbsent: true;
|
|
130
|
+
}>;
|
|
131
|
+
resultingStatus: 'running';
|
|
132
|
+
}>;
|
|
133
|
+
type OrchestrationRecovery = ModelPolicyRecovery | ClientCapabilityRecovery | SchemaMigrationRecovery;
|
|
88
134
|
type CleanCompletionEvidence = Readonly<{
|
|
89
135
|
kind: 'reviewed-head-clean';
|
|
90
136
|
observedAt: string;
|
|
@@ -96,7 +142,7 @@ type CleanCompletionEvidence = Readonly<{
|
|
|
96
142
|
prHead: string;
|
|
97
143
|
}>;
|
|
98
144
|
type OrchestrationRun = Readonly<{
|
|
99
|
-
schemaVersion: 1 | 2;
|
|
145
|
+
schemaVersion: 1 | 2 | 3;
|
|
100
146
|
taskId: string;
|
|
101
147
|
runId: string;
|
|
102
148
|
status: OrchestrationStatus;
|
|
@@ -173,6 +219,9 @@ type OrchestrationOptions = {
|
|
|
173
219
|
diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
|
|
174
220
|
supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
|
|
175
221
|
token?: () => string;
|
|
222
|
+
monotonicNow?: () => number;
|
|
223
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
224
|
+
hasActiveLifecycleEvidence?: (receipt: DelegationReceipt) => boolean;
|
|
176
225
|
};
|
|
177
226
|
|
|
178
227
|
type CommitIntentResult = Readonly<{
|
|
@@ -218,7 +267,7 @@ function readRun(taskDir: string): OrchestrationRun | null {
|
|
|
218
267
|
const file = orchestrationPath(taskDir);
|
|
219
268
|
if (!fs.existsSync(file)) return null;
|
|
220
269
|
const run = JSON.parse(fs.readFileSync(file, 'utf8')) as OrchestrationRun;
|
|
221
|
-
if (run.schemaVersion
|
|
270
|
+
if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy)) return run;
|
|
222
271
|
return {
|
|
223
272
|
...run,
|
|
224
273
|
modelPolicy: {
|
|
@@ -309,7 +358,7 @@ function commitFinalizationView(inspection: CommitFinalizationInspection): NonNu
|
|
|
309
358
|
|
|
310
359
|
function validCommitRun(run: OrchestrationRun | null, taskId: string): run is OrchestrationRun {
|
|
311
360
|
return run !== null
|
|
312
|
-
&& [1, 2].includes(run.schemaVersion)
|
|
361
|
+
&& [1, 2, 3].includes(run.schemaVersion)
|
|
313
362
|
&& run.taskId === taskId
|
|
314
363
|
&& ['running', 'paused', 'completed'].includes(run.status)
|
|
315
364
|
&& Array.isArray(run.receipts)
|
|
@@ -369,6 +418,38 @@ function resolveProjectPolicy(repoRoot: string, client: AgentClientId): Orchestr
|
|
|
369
418
|
return normalizeAgentClients(raw).state[client].orchestration;
|
|
370
419
|
}
|
|
371
420
|
|
|
421
|
+
function canRecoverCodexUnsupportedPause(run: OrchestrationRun, taskDir: string): boolean {
|
|
422
|
+
return run.schemaVersion === 2
|
|
423
|
+
&& run.status === 'paused'
|
|
424
|
+
&& run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
425
|
+
&& run.modelPolicySource?.client === 'codex'
|
|
426
|
+
&& run.stepCount === 0
|
|
427
|
+
&& run.nextStage === null
|
|
428
|
+
&& run.baseline === ''
|
|
429
|
+
&& run.pendingDelegation === null
|
|
430
|
+
&& run.receipts.length === 0
|
|
431
|
+
&& run.commitAuthorization?.issuedAt === null
|
|
432
|
+
&& run.commitAuthorization?.consumedAt === null
|
|
433
|
+
&& run.completionEvidence == null
|
|
434
|
+
&& !fs.existsSync(commitIntentPath(taskDir))
|
|
435
|
+
&& (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function canMigrateRecoverableV2Pause(run: OrchestrationRun, taskDir: string): boolean {
|
|
439
|
+
return run.schemaVersion === 2
|
|
440
|
+
&& run.status === 'paused'
|
|
441
|
+
&& run.pause?.recoverable === true
|
|
442
|
+
&& run.stepCount === 0
|
|
443
|
+
&& run.nextStage === null
|
|
444
|
+
&& run.baseline === ''
|
|
445
|
+
&& run.pendingDelegation === null
|
|
446
|
+
&& run.receipts.length === 0
|
|
447
|
+
&& run.commitAuthorization?.issuedAt === null
|
|
448
|
+
&& run.commitAuthorization?.consumedAt === null
|
|
449
|
+
&& run.completionEvidence == null
|
|
450
|
+
&& !fs.existsSync(commitIntentPath(taskDir));
|
|
451
|
+
}
|
|
452
|
+
|
|
372
453
|
function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
373
454
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
374
455
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
@@ -383,7 +464,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
383
464
|
}
|
|
384
465
|
if (existing) {
|
|
385
466
|
if (
|
|
386
|
-
![1, 2].includes(existing.schemaVersion)
|
|
467
|
+
![1, 2, 3].includes(existing.schemaVersion)
|
|
387
468
|
|| !Array.isArray(existing.receipts)
|
|
388
469
|
|| !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')
|
|
389
470
|
) {
|
|
@@ -392,7 +473,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
392
473
|
if (existing.status === 'completed' && existing.schemaVersion === 1) {
|
|
393
474
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
394
475
|
}
|
|
395
|
-
if (existing.schemaVersion === 2) {
|
|
476
|
+
if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
|
|
396
477
|
const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
|
|
397
478
|
if (
|
|
398
479
|
policyError
|
|
@@ -400,11 +481,18 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
400
481
|
|| !isAgentClientId(existing.modelPolicySource.client)
|
|
401
482
|
|| !Array.isArray(existing.recoveryHistory)
|
|
402
483
|
) {
|
|
403
|
-
return failed('ORCHESTRATION_STATE_INVALID',
|
|
484
|
+
return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
|
|
404
485
|
}
|
|
405
486
|
if (existing.status === 'completed') {
|
|
406
487
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
407
488
|
}
|
|
489
|
+
if (existing.schemaVersion === 2 && existing.status === 'running') {
|
|
490
|
+
return failed(
|
|
491
|
+
'ORCHESTRATION_STATE_INVALID',
|
|
492
|
+
'active schemaVersion 2 runs cannot be advanced by lifecycle protocol v3',
|
|
493
|
+
resolved.taskId
|
|
494
|
+
);
|
|
495
|
+
}
|
|
408
496
|
if (existing.modelPolicySource.client !== options.client) {
|
|
409
497
|
return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
|
|
410
498
|
}
|
|
@@ -415,8 +503,77 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
415
503
|
return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
|
|
416
504
|
}
|
|
417
505
|
}
|
|
506
|
+
if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
|
|
507
|
+
if (!canRecoverCodexUnsupportedPause(existing, resolved.taskDir)) {
|
|
508
|
+
return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
509
|
+
}
|
|
510
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
511
|
+
const previousPause = existing.pause as ClientCapabilityRecovery['previousPause'];
|
|
512
|
+
const recovery: ClientCapabilityRecovery = {
|
|
513
|
+
code: 'CLIENT_CAPABILITY_ENABLED',
|
|
514
|
+
recoveredAt: now,
|
|
515
|
+
previousSchemaVersion: 2,
|
|
516
|
+
previousStatus: 'paused',
|
|
517
|
+
previousPause,
|
|
518
|
+
client: 'codex',
|
|
519
|
+
guards: {
|
|
520
|
+
stepCount: 0,
|
|
521
|
+
nextStage: null,
|
|
522
|
+
baselineEmpty: true,
|
|
523
|
+
receiptCount: 0,
|
|
524
|
+
pendingDelegation: false,
|
|
525
|
+
commitAuthorizationUnused: true,
|
|
526
|
+
completionEvidenceAbsent: true,
|
|
527
|
+
commitIntentAbsent: true
|
|
528
|
+
},
|
|
529
|
+
resultingStatus: 'running'
|
|
530
|
+
};
|
|
531
|
+
const resumed = withUpdatedRun(existing, {
|
|
532
|
+
schemaVersion: 3,
|
|
533
|
+
status: 'running',
|
|
534
|
+
pause: null,
|
|
535
|
+
recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
|
|
536
|
+
}, () => now);
|
|
537
|
+
saveRun(resolved.taskDir, resumed);
|
|
538
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
539
|
+
}
|
|
418
540
|
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
419
|
-
|
|
541
|
+
if (existing.schemaVersion === 2 && !canMigrateRecoverableV2Pause(existing, resolved.taskDir)) {
|
|
542
|
+
const paused = withUpdatedRun(existing, {
|
|
543
|
+
pause: {
|
|
544
|
+
code: 'ORCHESTRATION_SCHEMA_MIGRATION_REQUIRED',
|
|
545
|
+
message: 'schemaVersion 2 recoverable pause contains execution evidence and cannot migrate automatically',
|
|
546
|
+
recoverable: false
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
saveRun(resolved.taskDir, paused);
|
|
550
|
+
return { status: 'paused', changed: true, taskId: resolved.taskId, run: paused, next: null, error: null };
|
|
551
|
+
}
|
|
552
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
553
|
+
const recovery: SchemaMigrationRecovery | null = existing.schemaVersion === 2 ? {
|
|
554
|
+
code: 'SCHEMA_V3_MIGRATED',
|
|
555
|
+
recoveredAt: now,
|
|
556
|
+
previousSchemaVersion: 2,
|
|
557
|
+
previousStatus: 'paused',
|
|
558
|
+
previousPause: existing.pause,
|
|
559
|
+
guards: {
|
|
560
|
+
stepCount: 0,
|
|
561
|
+
nextStage: null,
|
|
562
|
+
baselineEmpty: true,
|
|
563
|
+
receiptCount: 0,
|
|
564
|
+
pendingDelegation: false,
|
|
565
|
+
commitAuthorizationUnused: true,
|
|
566
|
+
completionEvidenceAbsent: true,
|
|
567
|
+
commitIntentAbsent: true
|
|
568
|
+
},
|
|
569
|
+
resultingStatus: 'running'
|
|
570
|
+
} : null;
|
|
571
|
+
const resumed = withUpdatedRun(existing, {
|
|
572
|
+
schemaVersion: 3,
|
|
573
|
+
status: 'running',
|
|
574
|
+
pause: null,
|
|
575
|
+
...(recovery ? { recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery]) } : {})
|
|
576
|
+
}, () => now);
|
|
420
577
|
saveRun(resolved.taskDir, resumed);
|
|
421
578
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
422
579
|
}
|
|
@@ -492,7 +649,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
492
649
|
};
|
|
493
650
|
const recovered: OrchestrationRun = Object.freeze({
|
|
494
651
|
...existing,
|
|
495
|
-
schemaVersion:
|
|
652
|
+
schemaVersion: 3,
|
|
496
653
|
status: resultingStatus,
|
|
497
654
|
modelPolicy: policy!,
|
|
498
655
|
modelPolicySource: source,
|
|
@@ -523,7 +680,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
523
680
|
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
524
681
|
const source: ModelPolicySource = { kind: sourceKind, client: options.client, resolvedAt: now };
|
|
525
682
|
const run: OrchestrationRun = {
|
|
526
|
-
schemaVersion:
|
|
683
|
+
schemaVersion: 3,
|
|
527
684
|
taskId: resolved.taskId,
|
|
528
685
|
runId: (options.id ?? randomUUID)(),
|
|
529
686
|
status: 'running',
|
|
@@ -1262,7 +1419,12 @@ function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}
|
|
|
1262
1419
|
|
|
1263
1420
|
function prepareOrchestrationDelegationUnlocked(
|
|
1264
1421
|
taskRef: string,
|
|
1265
|
-
input: Readonly<{
|
|
1422
|
+
input: Readonly<{
|
|
1423
|
+
client: AgentClientId;
|
|
1424
|
+
requestedModel?: string;
|
|
1425
|
+
requestedReasoningEffort?: string;
|
|
1426
|
+
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
1427
|
+
}>,
|
|
1266
1428
|
options: OrchestrationOptions = {}
|
|
1267
1429
|
): OrchestrationResult {
|
|
1268
1430
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
@@ -1284,6 +1446,9 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1284
1446
|
}
|
|
1285
1447
|
const run = readRun(resolved.taskDir);
|
|
1286
1448
|
if (!run || run.status !== 'running') return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
|
|
1449
|
+
if (run.schemaVersion !== 3) {
|
|
1450
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'delegation preparation requires orchestration schemaVersion 3', resolved.taskId);
|
|
1451
|
+
}
|
|
1287
1452
|
if (!isV2Policy(run.modelPolicy)) return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
|
|
1288
1453
|
if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
|
|
1289
1454
|
const repositoryPending = matchingDelegations(() => true, options);
|
|
@@ -1300,6 +1465,9 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1300
1465
|
if (!validModel(input.requestedReasoningEffort)) {
|
|
1301
1466
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
1302
1467
|
}
|
|
1468
|
+
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
1469
|
+
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
1470
|
+
}
|
|
1303
1471
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
1304
1472
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
1305
1473
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -1328,8 +1496,9 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1328
1496
|
requestedModel: input.requestedModel,
|
|
1329
1497
|
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
1330
1498
|
workspaceSnapshotScope: 'task',
|
|
1499
|
+
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
1331
1500
|
beforeFingerprint
|
|
1332
|
-
}, { id: options.id, now: options.now });
|
|
1501
|
+
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
1333
1502
|
const updated = withUpdatedRun(run, {
|
|
1334
1503
|
nextStage: next.stage,
|
|
1335
1504
|
pendingDelegation: receipt,
|
|
@@ -1343,7 +1512,12 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1343
1512
|
|
|
1344
1513
|
function prepareOrchestrationDelegation(
|
|
1345
1514
|
taskRef: string,
|
|
1346
|
-
input: Readonly<{
|
|
1515
|
+
input: Readonly<{
|
|
1516
|
+
client: AgentClientId;
|
|
1517
|
+
requestedModel?: string;
|
|
1518
|
+
requestedReasoningEffort?: string;
|
|
1519
|
+
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
1520
|
+
}>,
|
|
1347
1521
|
options: OrchestrationOptions = {}
|
|
1348
1522
|
): OrchestrationResult {
|
|
1349
1523
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
@@ -1366,6 +1540,51 @@ function prepareOrchestrationDelegation(
|
|
|
1366
1540
|
}
|
|
1367
1541
|
}
|
|
1368
1542
|
|
|
1543
|
+
function dispatchOrchestrationDelegationUnlocked(
|
|
1544
|
+
taskRef: string,
|
|
1545
|
+
options: OrchestrationOptions = {}
|
|
1546
|
+
): OrchestrationResult {
|
|
1547
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1548
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1549
|
+
const run = readRun(resolved.taskDir);
|
|
1550
|
+
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1551
|
+
if (run.schemaVersion !== 3 || run.status !== 'running') {
|
|
1552
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running schemaVersion 3 orchestration', resolved.taskId);
|
|
1553
|
+
}
|
|
1554
|
+
const result = dispatchDelegation(run.pendingDelegation, {
|
|
1555
|
+
now: options.now,
|
|
1556
|
+
monotonicNow: options.monotonicNow
|
|
1557
|
+
});
|
|
1558
|
+
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1559
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
1560
|
+
saveRun(resolved.taskDir, updated);
|
|
1561
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function dispatchOrchestrationDelegation(
|
|
1565
|
+
taskRef: string,
|
|
1566
|
+
options: OrchestrationOptions = {}
|
|
1567
|
+
): OrchestrationResult {
|
|
1568
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1569
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1570
|
+
try {
|
|
1571
|
+
return withTaskExecutionLock(
|
|
1572
|
+
resolved.repoRoot,
|
|
1573
|
+
'__repository__',
|
|
1574
|
+
'task-orchestration.dispatch.repository',
|
|
1575
|
+
() => withTaskExecutionLock(
|
|
1576
|
+
resolved.repoRoot,
|
|
1577
|
+
resolved.taskId,
|
|
1578
|
+
'task-orchestration.dispatch.task',
|
|
1579
|
+
() => dispatchOrchestrationDelegationUnlocked(taskRef, options)
|
|
1580
|
+
)
|
|
1581
|
+
);
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
1584
|
+
throw error;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1369
1588
|
function matchingDelegations(
|
|
1370
1589
|
predicate: (receipt: DelegationReceipt) => boolean,
|
|
1371
1590
|
options: OrchestrationOptions
|
|
@@ -1377,7 +1596,7 @@ function matchingDelegations(
|
|
|
1377
1596
|
.filter((entry) => entry.isDirectory())
|
|
1378
1597
|
.flatMap((entry) => {
|
|
1379
1598
|
const run = readRun(path.join(activeRoot, entry.name));
|
|
1380
|
-
if (!run
|
|
1599
|
+
if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation) return [];
|
|
1381
1600
|
const receipt = run.pendingDelegation;
|
|
1382
1601
|
return predicate(receipt)
|
|
1383
1602
|
? [{ taskId: entry.name, run }]
|
|
@@ -1399,6 +1618,33 @@ function uniqueMatchingDelegation(
|
|
|
1399
1618
|
return matches[0]!;
|
|
1400
1619
|
}
|
|
1401
1620
|
|
|
1621
|
+
function hasActivatableOrchestrationDelegation(
|
|
1622
|
+
client: AgentClientId,
|
|
1623
|
+
childId: string,
|
|
1624
|
+
options: OrchestrationOptions = {}
|
|
1625
|
+
): boolean {
|
|
1626
|
+
return matchingDelegations(
|
|
1627
|
+
(receipt) => receipt.client === client && (
|
|
1628
|
+
receipt.status === 'prepared'
|
|
1629
|
+
|| (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))
|
|
1630
|
+
),
|
|
1631
|
+
options
|
|
1632
|
+
).length > 0;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
function hasSealableOrchestrationDelegation(
|
|
1636
|
+
client: AgentClientId,
|
|
1637
|
+
childId: string,
|
|
1638
|
+
options: OrchestrationOptions = {}
|
|
1639
|
+
): boolean {
|
|
1640
|
+
return matchingDelegations(
|
|
1641
|
+
(receipt) => receipt.client === client
|
|
1642
|
+
&& receipt.childId === childId
|
|
1643
|
+
&& ['stage-completed', 'sealed'].includes(receipt.status),
|
|
1644
|
+
options
|
|
1645
|
+
).length > 0;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1402
1648
|
function activateMatchingOrchestrationDelegation(
|
|
1403
1649
|
client: AgentClientId,
|
|
1404
1650
|
event: Parameters<typeof activateDelegation>[1],
|
|
@@ -1411,13 +1657,44 @@ function activateMatchingOrchestrationDelegation(
|
|
|
1411
1657
|
(receipt) => receipt.status === 'prepared',
|
|
1412
1658
|
options
|
|
1413
1659
|
);
|
|
1414
|
-
if ('status' in matched)
|
|
1660
|
+
if ('status' in matched) {
|
|
1661
|
+
if (matched.error?.code !== 'ORCHESTRATION_DELEGATION_MISSING') return matched;
|
|
1662
|
+
const replay = uniqueMatchingDelegation(
|
|
1663
|
+
client,
|
|
1664
|
+
(receipt) => receipt.childId === event.childId && ['activated', 'stage-completed'].includes(receipt.status),
|
|
1665
|
+
options
|
|
1666
|
+
);
|
|
1667
|
+
if ('status' in replay) return matched;
|
|
1668
|
+
const receipt = replay.run.pendingDelegation!;
|
|
1669
|
+
const sameEvidence = receipt.parentId === event.parentId
|
|
1670
|
+
&& receipt.spawnMode === event.spawnMode
|
|
1671
|
+
&& receipt.actualModel === event.actualModel
|
|
1672
|
+
&& receipt.actualReasoningEffort === event.actualReasoningEffort
|
|
1673
|
+
&& receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
|
|
1674
|
+
&& receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
|
|
1675
|
+
&& receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
|
|
1676
|
+
if (!sameEvidence) {
|
|
1677
|
+
return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
|
|
1678
|
+
}
|
|
1679
|
+
return { status: replay.run.status, changed: false, taskId: replay.taskId, run: replay.run, next: null, error: null };
|
|
1680
|
+
}
|
|
1415
1681
|
if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
|
|
1416
1682
|
return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
|
|
1417
1683
|
}
|
|
1418
1684
|
return activateOrchestrationDelegation(matched.taskId, event, options);
|
|
1419
1685
|
}
|
|
1420
1686
|
|
|
1687
|
+
function pauseMatchingOrchestrationDelegation(
|
|
1688
|
+
client: AgentClientId,
|
|
1689
|
+
code: string,
|
|
1690
|
+
message: string,
|
|
1691
|
+
options: OrchestrationOptions = {}
|
|
1692
|
+
): OrchestrationResult {
|
|
1693
|
+
const matched = uniqueMatchingDelegation(client, () => true, options);
|
|
1694
|
+
if ('status' in matched) return matched;
|
|
1695
|
+
return pauseOrchestration(matched.taskId, code, message, true, options);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1421
1698
|
function sealMatchingOrchestrationDelegation(
|
|
1422
1699
|
client: AgentClientId,
|
|
1423
1700
|
event: Readonly<{ nativeAgent: string; childId: string }>,
|
|
@@ -1462,6 +1739,96 @@ function sealMatchingOrchestrationDelegation(
|
|
|
1462
1739
|
}
|
|
1463
1740
|
}
|
|
1464
1741
|
|
|
1742
|
+
function sealMatchingOrchestrationDelegationWithHostEvidence(
|
|
1743
|
+
client: AgentClientId,
|
|
1744
|
+
event: Readonly<{ nativeAgent: string; childId: string }>,
|
|
1745
|
+
consumeEvidence: (receipt: DelegationReceipt) => Readonly<{
|
|
1746
|
+
stopRevision: number;
|
|
1747
|
+
consumer: string;
|
|
1748
|
+
consumedAt: string;
|
|
1749
|
+
}>,
|
|
1750
|
+
options: OrchestrationOptions = {}
|
|
1751
|
+
): OrchestrationResult {
|
|
1752
|
+
const role = managedDelegationRole(event.nativeAgent);
|
|
1753
|
+
if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
1754
|
+
const matched = uniqueMatchingDelegation(
|
|
1755
|
+
client,
|
|
1756
|
+
(receipt) => (
|
|
1757
|
+
(receipt.status === 'stage-completed' || receipt.status === 'sealed')
|
|
1758
|
+
&& receipt.childId === event.childId
|
|
1759
|
+
),
|
|
1760
|
+
options
|
|
1761
|
+
);
|
|
1762
|
+
if ('status' in matched) return matched;
|
|
1763
|
+
const receipt = matched.run.pendingDelegation!;
|
|
1764
|
+
if (receipt.status === 'sealed') {
|
|
1765
|
+
return reconcileMatchingOrchestrationDelegation(client, event.childId, options);
|
|
1766
|
+
}
|
|
1767
|
+
if (receipt.role !== role) {
|
|
1768
|
+
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
1769
|
+
}
|
|
1770
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1771
|
+
try {
|
|
1772
|
+
const gitRoot = gitRootFor(repoRoot, options);
|
|
1773
|
+
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
1774
|
+
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1775
|
+
gitRoot,
|
|
1776
|
+
stateRoot: repoRoot,
|
|
1777
|
+
taskId: snapshotTaskId
|
|
1778
|
+
});
|
|
1779
|
+
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
1780
|
+
const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
|
|
1781
|
+
const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
|
|
1782
|
+
if (!validated.ok) {
|
|
1783
|
+
return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
|
|
1784
|
+
}
|
|
1785
|
+
const hostEvidence = consumeEvidence(receipt);
|
|
1786
|
+
const sealed = sealDelegation(receipt, { ...baseEvent, hostEvidence }, { now: options.now });
|
|
1787
|
+
if (!sealed.ok) return pauseOrchestration(matched.taskId, sealed.code, sealed.message, true, options);
|
|
1788
|
+
const resolved = resolveTaskRef(matched.taskId, { repoRoot });
|
|
1789
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1790
|
+
const updated = withUpdatedRun(matched.run, { pendingDelegation: sealed.receipt }, options.now);
|
|
1791
|
+
saveRun(resolved.taskDir, updated);
|
|
1792
|
+
return { status: 'running', changed: true, taskId: matched.taskId, run: updated, next: null, error: null };
|
|
1793
|
+
} catch (error) {
|
|
1794
|
+
return pauseOrchestration(
|
|
1795
|
+
matched.taskId,
|
|
1796
|
+
'ORCHESTRATION_CODEX_EVIDENCE_FAILED',
|
|
1797
|
+
error instanceof Error ? error.message : String(error),
|
|
1798
|
+
true,
|
|
1799
|
+
options
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
function reconcileMatchingOrchestrationDelegation(
|
|
1805
|
+
client: AgentClientId,
|
|
1806
|
+
childId: string,
|
|
1807
|
+
options: OrchestrationOptions = {}
|
|
1808
|
+
): OrchestrationResult {
|
|
1809
|
+
const matched = uniqueMatchingDelegation(
|
|
1810
|
+
client,
|
|
1811
|
+
(receipt) => receipt.childId === childId,
|
|
1812
|
+
options
|
|
1813
|
+
);
|
|
1814
|
+
if ('status' in matched) return matched;
|
|
1815
|
+
const receipt = matched.run.pendingDelegation!;
|
|
1816
|
+
if (
|
|
1817
|
+
receipt.status !== 'sealed'
|
|
1818
|
+
|| receipt.hostEvidence?.consumer !== receipt.id
|
|
1819
|
+
|| receipt.hostEvidence.consumedAt === null
|
|
1820
|
+
) {
|
|
1821
|
+
return pauseOrchestration(
|
|
1822
|
+
matched.taskId,
|
|
1823
|
+
'ORCHESTRATION_CODEX_RECONCILIATION_FAILED',
|
|
1824
|
+
'Codex native spawn did not produce one sealed receipt with consumed host evidence',
|
|
1825
|
+
true,
|
|
1826
|
+
options
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
return { status: matched.run.status, changed: false, taskId: matched.taskId, run: matched.run, next: null, error: null };
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1465
1832
|
function activateOrchestrationDelegation(
|
|
1466
1833
|
taskRef: string,
|
|
1467
1834
|
event: Parameters<typeof activateDelegation>[1],
|
|
@@ -1471,7 +1838,14 @@ function activateOrchestrationDelegation(
|
|
|
1471
1838
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1472
1839
|
const run = readRun(resolved.taskDir);
|
|
1473
1840
|
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1474
|
-
|
|
1841
|
+
if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'active delegation requires schemaVersion 3', resolved.taskId);
|
|
1842
|
+
if (run.status !== 'running') {
|
|
1843
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1844
|
+
}
|
|
1845
|
+
const result = activateDelegation(run.pendingDelegation, event, {
|
|
1846
|
+
now: options.now,
|
|
1847
|
+
monotonicNow: options.monotonicNow
|
|
1848
|
+
});
|
|
1475
1849
|
if (!result.ok) {
|
|
1476
1850
|
if (result.code === 'DELEGATION_IGNORED') return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1477
1851
|
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
@@ -1481,6 +1855,153 @@ function activateOrchestrationDelegation(
|
|
|
1481
1855
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1482
1856
|
}
|
|
1483
1857
|
|
|
1858
|
+
async function awaitOrchestrationDelegationActivation(
|
|
1859
|
+
taskRef: string,
|
|
1860
|
+
event: OrchestrationStageIdentity,
|
|
1861
|
+
options: OrchestrationOptions = {}
|
|
1862
|
+
): Promise<OrchestrationResult> {
|
|
1863
|
+
const sleep = options.sleep
|
|
1864
|
+
?? ((milliseconds: number) => new Promise<void>((resolve) => setTimeout(resolve, milliseconds)));
|
|
1865
|
+
while (true) {
|
|
1866
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1867
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1868
|
+
const run = readRun(resolved.taskDir);
|
|
1869
|
+
if (!run?.pendingDelegation) {
|
|
1870
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1871
|
+
}
|
|
1872
|
+
if (run.schemaVersion !== 3) {
|
|
1873
|
+
return failed('ORCHESTRATION_STATE_INVALID', 'activation barrier requires schemaVersion 3', resolved.taskId);
|
|
1874
|
+
}
|
|
1875
|
+
const receipt = run.pendingDelegation;
|
|
1876
|
+
const matches = receipt.stage === event.stage
|
|
1877
|
+
&& receipt.round === event.round
|
|
1878
|
+
&& receipt.artifact === event.artifact
|
|
1879
|
+
&& receipt.role === event.role;
|
|
1880
|
+
if (!matches) {
|
|
1881
|
+
return pauseOrchestration(
|
|
1882
|
+
taskRef,
|
|
1883
|
+
'ORCHESTRATION_PROVENANCE_MISMATCH',
|
|
1884
|
+
'activation barrier identity does not match the pending delegation',
|
|
1885
|
+
true,
|
|
1886
|
+
options
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
if (receipt.status === 'activated' || receipt.status === 'stage-completed') {
|
|
1890
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1891
|
+
}
|
|
1892
|
+
if (run.status !== 'running') {
|
|
1893
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1894
|
+
}
|
|
1895
|
+
if (receipt.status !== 'prepared') {
|
|
1896
|
+
return pauseOrchestration(
|
|
1897
|
+
taskRef,
|
|
1898
|
+
'ORCHESTRATION_ACTIVATION_STATE_INVALID',
|
|
1899
|
+
`activation barrier found receipt status '${receipt.status}'`,
|
|
1900
|
+
true,
|
|
1901
|
+
options
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
if (
|
|
1905
|
+
receipt.activationDeadlineAt == null
|
|
1906
|
+
|| !Number.isFinite(Date.parse(receipt.activationDeadlineAt))
|
|
1907
|
+
) {
|
|
1908
|
+
return pauseOrchestration(
|
|
1909
|
+
taskRef,
|
|
1910
|
+
'ORCHESTRATION_DELEGATION_NOT_DISPATCHED',
|
|
1911
|
+
'activation barrier was entered before the native spawn dispatch boundary',
|
|
1912
|
+
true,
|
|
1913
|
+
options
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
if (Date.parse((options.now ?? (() => new Date().toISOString()))()) >= Date.parse(receipt.activationDeadlineAt)) {
|
|
1917
|
+
return pauseOrchestration(
|
|
1918
|
+
taskRef,
|
|
1919
|
+
'ORCHESTRATION_ACTIVATION_TIMEOUT',
|
|
1920
|
+
'fresh child did not receive trusted activation evidence before the deadline',
|
|
1921
|
+
true,
|
|
1922
|
+
options
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
await sleep(100);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function recoverPreparedOrchestrationDelegation(
|
|
1930
|
+
taskRef: string,
|
|
1931
|
+
options: OrchestrationOptions = {}
|
|
1932
|
+
): OrchestrationResult {
|
|
1933
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1934
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1935
|
+
try {
|
|
1936
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-orchestration.recover-prepared', () => {
|
|
1937
|
+
const run = readRun(resolved.taskDir);
|
|
1938
|
+
const receipt = run?.pendingDelegation;
|
|
1939
|
+
if (!run || !receipt) {
|
|
1940
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1941
|
+
}
|
|
1942
|
+
if (receipt.status !== 'prepared') {
|
|
1943
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'only a never-activated prepared receipt can recover', resolved.taskId);
|
|
1944
|
+
}
|
|
1945
|
+
const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
|
|
1946
|
+
if (
|
|
1947
|
+
receipt.activationDeadlineAt != null
|
|
1948
|
+
&& Number.isFinite(Date.parse(receipt.activationDeadlineAt))
|
|
1949
|
+
&& wallNow <= Date.parse(receipt.activationDeadlineAt)
|
|
1950
|
+
) {
|
|
1951
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
|
|
1952
|
+
}
|
|
1953
|
+
const activeLifecycleEvidence = options.hasActiveLifecycleEvidence?.(receipt)
|
|
1954
|
+
?? (
|
|
1955
|
+
receipt.client === 'codex'
|
|
1956
|
+
&& receipt.lifecycleProvenance
|
|
1957
|
+
&& hasActiveCodexLifecycleEvidence(
|
|
1958
|
+
path.join(resolved.repoRoot, '.agents', 'workspace', '.runtime', 'codex-lifecycle'),
|
|
1959
|
+
{
|
|
1960
|
+
nativeAgent: `agent-infra-lifecycle-${receipt.role}`,
|
|
1961
|
+
hookDefinitionHash: receipt.lifecycleProvenance.hookDefinitionHash
|
|
1962
|
+
}
|
|
1963
|
+
)
|
|
1964
|
+
);
|
|
1965
|
+
if (activeLifecycleEvidence) {
|
|
1966
|
+
return failed(
|
|
1967
|
+
'ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE',
|
|
1968
|
+
'active Codex lifecycle evidence blocks prepared receipt recovery',
|
|
1969
|
+
resolved.taskId
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
1973
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'active commit intent blocks prepared recovery', resolved.taskId);
|
|
1974
|
+
}
|
|
1975
|
+
const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1976
|
+
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
1977
|
+
stateRoot: resolved.repoRoot,
|
|
1978
|
+
taskId: receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null
|
|
1979
|
+
});
|
|
1980
|
+
if (current !== receipt.beforeFingerprint) {
|
|
1981
|
+
return failed('ORCHESTRATION_PREPARED_RECOVERY_DRIFT', 'workspace changed after delegation preparation', resolved.taskId);
|
|
1982
|
+
}
|
|
1983
|
+
const aborted = abortPreparedDelegation(receipt);
|
|
1984
|
+
if (!aborted.ok) return failed(aborted.code, aborted.message, resolved.taskId);
|
|
1985
|
+
const updated = withUpdatedRun(run, {
|
|
1986
|
+
status: 'running',
|
|
1987
|
+
pause: null,
|
|
1988
|
+
nextStage: null,
|
|
1989
|
+
pendingDelegation: null,
|
|
1990
|
+
receipts: Object.freeze([...run.receipts, aborted.receipt])
|
|
1991
|
+
}, options.now);
|
|
1992
|
+
saveRun(resolved.taskDir, updated);
|
|
1993
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1994
|
+
});
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
if (error instanceof TaskExecutionLockError) return failed(error.code, error.message, resolved.taskId);
|
|
1997
|
+
return failed(
|
|
1998
|
+
'ORCHESTRATION_PREPARED_RECOVERY_FAILED',
|
|
1999
|
+
error instanceof Error ? error.message : String(error),
|
|
2000
|
+
resolved.taskId
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
|
|
1484
2005
|
function completeOrchestrationStage(
|
|
1485
2006
|
taskRef: string,
|
|
1486
2007
|
event: Parameters<typeof completeDelegationStage>[1],
|
|
@@ -1490,6 +2011,7 @@ function completeOrchestrationStage(
|
|
|
1490
2011
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1491
2012
|
const run = readRun(resolved.taskDir);
|
|
1492
2013
|
if (!run) return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
2014
|
+
if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'stage completion requires schemaVersion 3', resolved.taskId);
|
|
1493
2015
|
if (!run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
|
|
1494
2016
|
const result = completeDelegationStage(run.pendingDelegation, event);
|
|
1495
2017
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
@@ -1512,6 +2034,7 @@ function inspectOrchestrationStage(
|
|
|
1512
2034
|
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1513
2035
|
}
|
|
1514
2036
|
if (!run) return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
2037
|
+
if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'stage inspection requires schemaVersion 3', resolved.taskId);
|
|
1515
2038
|
const receipt = run.pendingDelegation;
|
|
1516
2039
|
if (
|
|
1517
2040
|
!receipt
|
|
@@ -1570,6 +2093,7 @@ function sealOrchestrationDelegation(
|
|
|
1570
2093
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1571
2094
|
const run = readRun(resolved.taskDir);
|
|
1572
2095
|
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
2096
|
+
if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'delegation sealing requires schemaVersion 3', resolved.taskId);
|
|
1573
2097
|
const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
|
|
1574
2098
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1575
2099
|
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
@@ -1582,6 +2106,7 @@ function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {
|
|
|
1582
2106
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1583
2107
|
const run = readRun(resolved.taskDir);
|
|
1584
2108
|
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
2109
|
+
if (run.schemaVersion !== 3) return failed('ORCHESTRATION_STATE_INVALID', 'orchestration advance requires schemaVersion 3', resolved.taskId);
|
|
1585
2110
|
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
1586
2111
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1587
2112
|
const completed = result.receipt.stage === 'commit';
|
|
@@ -1618,6 +2143,7 @@ function pauseOrchestration(
|
|
|
1618
2143
|
export {
|
|
1619
2144
|
activateMatchingOrchestrationDelegation,
|
|
1620
2145
|
activateOrchestrationDelegation,
|
|
2146
|
+
awaitOrchestrationDelegationActivation,
|
|
1621
2147
|
advanceOrchestration,
|
|
1622
2148
|
abortCommitIntent,
|
|
1623
2149
|
beginCommitIntent,
|
|
@@ -1626,15 +2152,22 @@ export {
|
|
|
1626
2152
|
commitOrchestrationStageCompletion,
|
|
1627
2153
|
completeCommitIntent,
|
|
1628
2154
|
completeOrchestrationStage,
|
|
2155
|
+
dispatchOrchestrationDelegation,
|
|
1629
2156
|
inspectOrchestrationStage,
|
|
2157
|
+
hasActivatableOrchestrationDelegation,
|
|
2158
|
+
hasSealableOrchestrationDelegation,
|
|
1630
2159
|
orchestrationPath,
|
|
2160
|
+
pauseMatchingOrchestrationDelegation,
|
|
1631
2161
|
pauseOrchestration,
|
|
1632
2162
|
planOrchestrationStageCompletion,
|
|
1633
2163
|
prepareOrchestrationDelegation,
|
|
1634
2164
|
readRun,
|
|
2165
|
+
reconcileMatchingOrchestrationDelegation,
|
|
1635
2166
|
recoverCommitIntent,
|
|
2167
|
+
recoverPreparedOrchestrationDelegation,
|
|
1636
2168
|
routeOrchestration,
|
|
1637
2169
|
sealMatchingOrchestrationDelegation,
|
|
2170
|
+
sealMatchingOrchestrationDelegationWithHostEvidence,
|
|
1638
2171
|
sealOrchestrationDelegation,
|
|
1639
2172
|
startCommitAttempt,
|
|
1640
2173
|
statusCommitIntent,
|
|
@@ -1650,5 +2183,6 @@ export type {
|
|
|
1650
2183
|
OrchestrationRun,
|
|
1651
2184
|
OrchestrationStageCompletion,
|
|
1652
2185
|
OrchestrationStageIdentity,
|
|
1653
|
-
OrchestrationStatus
|
|
2186
|
+
OrchestrationStatus,
|
|
2187
|
+
OrchestrationOptions
|
|
1654
2188
|
};
|