@fitlab-ai/agent-infra 0.9.6 → 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 +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
- 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/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/task-orchestration.js +37 -5
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- 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/sandbox/commands/create.js +103 -51
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +13 -7
- package/dist/lib/sandbox/control/lifecycle.js +238 -29
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +25 -15
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/mounts.js +5 -13
- package/dist/lib/sandbox/recovery.js +59 -42
- package/dist/lib/sandbox/workspace-view.js +57 -40
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +84 -2
- package/dist/lib/task/events.js +139 -2
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +235 -14
- 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 +40 -8
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
- 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/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 +33 -4
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/task-orchestration.ts +31 -4
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- 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/sandbox/commands/create.ts +56 -7
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +14 -10
- package/lib/sandbox/control/lifecycle.ts +264 -33
- package/lib/sandbox/control/protocol.ts +31 -5
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +34 -17
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/mounts.ts +5 -15
- package/lib/sandbox/recovery.ts +71 -42
- package/lib/sandbox/workspace-view.ts +61 -40
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +160 -8
- package/lib/task/events.ts +122 -1
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +356 -17
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +51 -16
- 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 +25 -1
- 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/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/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- 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 +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -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 +2 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- 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,
|
|
@@ -104,7 +112,25 @@ type ClientCapabilityRecovery = Readonly<{
|
|
|
104
112
|
}>;
|
|
105
113
|
resultingStatus: 'running';
|
|
106
114
|
}>;
|
|
107
|
-
type
|
|
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;
|
|
108
134
|
type CleanCompletionEvidence = Readonly<{
|
|
109
135
|
kind: 'reviewed-head-clean';
|
|
110
136
|
observedAt: string;
|
|
@@ -116,7 +142,7 @@ type CleanCompletionEvidence = Readonly<{
|
|
|
116
142
|
prHead: string;
|
|
117
143
|
}>;
|
|
118
144
|
type OrchestrationRun = Readonly<{
|
|
119
|
-
schemaVersion: 1 | 2;
|
|
145
|
+
schemaVersion: 1 | 2 | 3;
|
|
120
146
|
taskId: string;
|
|
121
147
|
runId: string;
|
|
122
148
|
status: OrchestrationStatus;
|
|
@@ -193,6 +219,9 @@ type OrchestrationOptions = {
|
|
|
193
219
|
diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
|
|
194
220
|
supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
|
|
195
221
|
token?: () => string;
|
|
222
|
+
monotonicNow?: () => number;
|
|
223
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
224
|
+
hasActiveLifecycleEvidence?: (receipt: DelegationReceipt) => boolean;
|
|
196
225
|
};
|
|
197
226
|
|
|
198
227
|
type CommitIntentResult = Readonly<{
|
|
@@ -238,7 +267,7 @@ function readRun(taskDir: string): OrchestrationRun | null {
|
|
|
238
267
|
const file = orchestrationPath(taskDir);
|
|
239
268
|
if (!fs.existsSync(file)) return null;
|
|
240
269
|
const run = JSON.parse(fs.readFileSync(file, 'utf8')) as OrchestrationRun;
|
|
241
|
-
if (run.schemaVersion
|
|
270
|
+
if (![2, 3].includes(run.schemaVersion) || !isV2Policy(run.modelPolicy)) return run;
|
|
242
271
|
return {
|
|
243
272
|
...run,
|
|
244
273
|
modelPolicy: {
|
|
@@ -329,7 +358,7 @@ function commitFinalizationView(inspection: CommitFinalizationInspection): NonNu
|
|
|
329
358
|
|
|
330
359
|
function validCommitRun(run: OrchestrationRun | null, taskId: string): run is OrchestrationRun {
|
|
331
360
|
return run !== null
|
|
332
|
-
&& [1, 2].includes(run.schemaVersion)
|
|
361
|
+
&& [1, 2, 3].includes(run.schemaVersion)
|
|
333
362
|
&& run.taskId === taskId
|
|
334
363
|
&& ['running', 'paused', 'completed'].includes(run.status)
|
|
335
364
|
&& Array.isArray(run.receipts)
|
|
@@ -406,6 +435,21 @@ function canRecoverCodexUnsupportedPause(run: OrchestrationRun, taskDir: string)
|
|
|
406
435
|
&& (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
|
|
407
436
|
}
|
|
408
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
|
+
|
|
409
453
|
function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
410
454
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
411
455
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
@@ -420,7 +464,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
420
464
|
}
|
|
421
465
|
if (existing) {
|
|
422
466
|
if (
|
|
423
|
-
![1, 2].includes(existing.schemaVersion)
|
|
467
|
+
![1, 2, 3].includes(existing.schemaVersion)
|
|
424
468
|
|| !Array.isArray(existing.receipts)
|
|
425
469
|
|| !Object.prototype.hasOwnProperty.call(existing, 'pendingDelegation')
|
|
426
470
|
) {
|
|
@@ -429,7 +473,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
429
473
|
if (existing.status === 'completed' && existing.schemaVersion === 1) {
|
|
430
474
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
431
475
|
}
|
|
432
|
-
if (existing.schemaVersion === 2) {
|
|
476
|
+
if (existing.schemaVersion === 2 || existing.schemaVersion === 3) {
|
|
433
477
|
const policyError = validateModelPolicy(isV2Policy(existing.modelPolicy) ? existing.modelPolicy : undefined);
|
|
434
478
|
if (
|
|
435
479
|
policyError
|
|
@@ -437,11 +481,18 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
437
481
|
|| !isAgentClientId(existing.modelPolicySource.client)
|
|
438
482
|
|| !Array.isArray(existing.recoveryHistory)
|
|
439
483
|
) {
|
|
440
|
-
return failed('ORCHESTRATION_STATE_INVALID',
|
|
484
|
+
return failed('ORCHESTRATION_STATE_INVALID', `schemaVersion ${existing.schemaVersion} orchestration state is incomplete`, resolved.taskId);
|
|
441
485
|
}
|
|
442
486
|
if (existing.status === 'completed') {
|
|
443
487
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
444
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
|
+
}
|
|
445
496
|
if (existing.modelPolicySource.client !== options.client) {
|
|
446
497
|
return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
|
|
447
498
|
}
|
|
@@ -478,6 +529,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
478
529
|
resultingStatus: 'running'
|
|
479
530
|
};
|
|
480
531
|
const resumed = withUpdatedRun(existing, {
|
|
532
|
+
schemaVersion: 3,
|
|
481
533
|
status: 'running',
|
|
482
534
|
pause: null,
|
|
483
535
|
recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
|
|
@@ -486,7 +538,42 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
486
538
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
487
539
|
}
|
|
488
540
|
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
489
|
-
|
|
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);
|
|
490
577
|
saveRun(resolved.taskDir, resumed);
|
|
491
578
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
492
579
|
}
|
|
@@ -562,7 +649,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
562
649
|
};
|
|
563
650
|
const recovered: OrchestrationRun = Object.freeze({
|
|
564
651
|
...existing,
|
|
565
|
-
schemaVersion:
|
|
652
|
+
schemaVersion: 3,
|
|
566
653
|
status: resultingStatus,
|
|
567
654
|
modelPolicy: policy!,
|
|
568
655
|
modelPolicySource: source,
|
|
@@ -593,7 +680,7 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
593
680
|
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
594
681
|
const source: ModelPolicySource = { kind: sourceKind, client: options.client, resolvedAt: now };
|
|
595
682
|
const run: OrchestrationRun = {
|
|
596
|
-
schemaVersion:
|
|
683
|
+
schemaVersion: 3,
|
|
597
684
|
taskId: resolved.taskId,
|
|
598
685
|
runId: (options.id ?? randomUUID)(),
|
|
599
686
|
status: 'running',
|
|
@@ -1332,7 +1419,12 @@ function statusOrchestration(taskRef: string, options: OrchestrationOptions = {}
|
|
|
1332
1419
|
|
|
1333
1420
|
function prepareOrchestrationDelegationUnlocked(
|
|
1334
1421
|
taskRef: string,
|
|
1335
|
-
input: Readonly<{
|
|
1422
|
+
input: Readonly<{
|
|
1423
|
+
client: AgentClientId;
|
|
1424
|
+
requestedModel?: string;
|
|
1425
|
+
requestedReasoningEffort?: string;
|
|
1426
|
+
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
1427
|
+
}>,
|
|
1336
1428
|
options: OrchestrationOptions = {}
|
|
1337
1429
|
): OrchestrationResult {
|
|
1338
1430
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
@@ -1354,6 +1446,9 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1354
1446
|
}
|
|
1355
1447
|
const run = readRun(resolved.taskDir);
|
|
1356
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
|
+
}
|
|
1357
1452
|
if (!isV2Policy(run.modelPolicy)) return failed('ORCHESTRATION_MODEL_EVIDENCE_MISSING', 'running orchestration has no persisted model policy', resolved.taskId);
|
|
1358
1453
|
if (run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
|
|
1359
1454
|
const repositoryPending = matchingDelegations(() => true, options);
|
|
@@ -1370,6 +1465,9 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1370
1465
|
if (!validModel(input.requestedReasoningEffort)) {
|
|
1371
1466
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
1372
1467
|
}
|
|
1468
|
+
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
1469
|
+
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
1470
|
+
}
|
|
1373
1471
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
1374
1472
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
1375
1473
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -1398,8 +1496,9 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1398
1496
|
requestedModel: input.requestedModel,
|
|
1399
1497
|
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
1400
1498
|
workspaceSnapshotScope: 'task',
|
|
1499
|
+
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
1401
1500
|
beforeFingerprint
|
|
1402
|
-
}, { id: options.id, now: options.now });
|
|
1501
|
+
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
1403
1502
|
const updated = withUpdatedRun(run, {
|
|
1404
1503
|
nextStage: next.stage,
|
|
1405
1504
|
pendingDelegation: receipt,
|
|
@@ -1413,7 +1512,12 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1413
1512
|
|
|
1414
1513
|
function prepareOrchestrationDelegation(
|
|
1415
1514
|
taskRef: string,
|
|
1416
|
-
input: Readonly<{
|
|
1515
|
+
input: Readonly<{
|
|
1516
|
+
client: AgentClientId;
|
|
1517
|
+
requestedModel?: string;
|
|
1518
|
+
requestedReasoningEffort?: string;
|
|
1519
|
+
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
1520
|
+
}>,
|
|
1417
1521
|
options: OrchestrationOptions = {}
|
|
1418
1522
|
): OrchestrationResult {
|
|
1419
1523
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
@@ -1436,6 +1540,51 @@ function prepareOrchestrationDelegation(
|
|
|
1436
1540
|
}
|
|
1437
1541
|
}
|
|
1438
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
|
+
|
|
1439
1588
|
function matchingDelegations(
|
|
1440
1589
|
predicate: (receipt: DelegationReceipt) => boolean,
|
|
1441
1590
|
options: OrchestrationOptions
|
|
@@ -1447,7 +1596,7 @@ function matchingDelegations(
|
|
|
1447
1596
|
.filter((entry) => entry.isDirectory())
|
|
1448
1597
|
.flatMap((entry) => {
|
|
1449
1598
|
const run = readRun(path.join(activeRoot, entry.name));
|
|
1450
|
-
if (run?.status !== 'running' || !run.pendingDelegation) return [];
|
|
1599
|
+
if (run?.schemaVersion !== 3 || run.status !== 'running' || !run.pendingDelegation) return [];
|
|
1451
1600
|
const receipt = run.pendingDelegation;
|
|
1452
1601
|
return predicate(receipt)
|
|
1453
1602
|
? [{ taskId: entry.name, run }]
|
|
@@ -1469,6 +1618,33 @@ function uniqueMatchingDelegation(
|
|
|
1469
1618
|
return matches[0]!;
|
|
1470
1619
|
}
|
|
1471
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
|
+
|
|
1472
1648
|
function activateMatchingOrchestrationDelegation(
|
|
1473
1649
|
client: AgentClientId,
|
|
1474
1650
|
event: Parameters<typeof activateDelegation>[1],
|
|
@@ -1602,7 +1778,7 @@ function sealMatchingOrchestrationDelegationWithHostEvidence(
|
|
|
1602
1778
|
});
|
|
1603
1779
|
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
1604
1780
|
const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
|
|
1605
|
-
const validated = sealDelegation(receipt, baseEvent, { now: options.now });
|
|
1781
|
+
const validated = sealDelegation(receipt, baseEvent, { now: options.now, requireHostEvidence: false });
|
|
1606
1782
|
if (!validated.ok) {
|
|
1607
1783
|
return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
|
|
1608
1784
|
}
|
|
@@ -1662,7 +1838,14 @@ function activateOrchestrationDelegation(
|
|
|
1662
1838
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1663
1839
|
const run = readRun(resolved.taskDir);
|
|
1664
1840
|
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1665
|
-
|
|
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
|
+
});
|
|
1666
1849
|
if (!result.ok) {
|
|
1667
1850
|
if (result.code === 'DELEGATION_IGNORED') return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1668
1851
|
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
@@ -1672,6 +1855,153 @@ function activateOrchestrationDelegation(
|
|
|
1672
1855
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1673
1856
|
}
|
|
1674
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
|
+
|
|
1675
2005
|
function completeOrchestrationStage(
|
|
1676
2006
|
taskRef: string,
|
|
1677
2007
|
event: Parameters<typeof completeDelegationStage>[1],
|
|
@@ -1681,6 +2011,7 @@ function completeOrchestrationStage(
|
|
|
1681
2011
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1682
2012
|
const run = readRun(resolved.taskDir);
|
|
1683
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);
|
|
1684
2015
|
if (!run.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
|
|
1685
2016
|
const result = completeDelegationStage(run.pendingDelegation, event);
|
|
1686
2017
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
@@ -1703,6 +2034,7 @@ function inspectOrchestrationStage(
|
|
|
1703
2034
|
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1704
2035
|
}
|
|
1705
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);
|
|
1706
2038
|
const receipt = run.pendingDelegation;
|
|
1707
2039
|
if (
|
|
1708
2040
|
!receipt
|
|
@@ -1761,6 +2093,7 @@ function sealOrchestrationDelegation(
|
|
|
1761
2093
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1762
2094
|
const run = readRun(resolved.taskDir);
|
|
1763
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);
|
|
1764
2097
|
const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
|
|
1765
2098
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1766
2099
|
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
@@ -1773,6 +2106,7 @@ function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {
|
|
|
1773
2106
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1774
2107
|
const run = readRun(resolved.taskDir);
|
|
1775
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);
|
|
1776
2110
|
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
1777
2111
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1778
2112
|
const completed = result.receipt.stage === 'commit';
|
|
@@ -1809,6 +2143,7 @@ function pauseOrchestration(
|
|
|
1809
2143
|
export {
|
|
1810
2144
|
activateMatchingOrchestrationDelegation,
|
|
1811
2145
|
activateOrchestrationDelegation,
|
|
2146
|
+
awaitOrchestrationDelegationActivation,
|
|
1812
2147
|
advanceOrchestration,
|
|
1813
2148
|
abortCommitIntent,
|
|
1814
2149
|
beginCommitIntent,
|
|
@@ -1817,7 +2152,10 @@ export {
|
|
|
1817
2152
|
commitOrchestrationStageCompletion,
|
|
1818
2153
|
completeCommitIntent,
|
|
1819
2154
|
completeOrchestrationStage,
|
|
2155
|
+
dispatchOrchestrationDelegation,
|
|
1820
2156
|
inspectOrchestrationStage,
|
|
2157
|
+
hasActivatableOrchestrationDelegation,
|
|
2158
|
+
hasSealableOrchestrationDelegation,
|
|
1821
2159
|
orchestrationPath,
|
|
1822
2160
|
pauseMatchingOrchestrationDelegation,
|
|
1823
2161
|
pauseOrchestration,
|
|
@@ -1826,6 +2164,7 @@ export {
|
|
|
1826
2164
|
readRun,
|
|
1827
2165
|
reconcileMatchingOrchestrationDelegation,
|
|
1828
2166
|
recoverCommitIntent,
|
|
2167
|
+
recoverPreparedOrchestrationDelegation,
|
|
1829
2168
|
routeOrchestration,
|
|
1830
2169
|
sealMatchingOrchestrationDelegation,
|
|
1831
2170
|
sealMatchingOrchestrationDelegationWithHostEvidence,
|
package/lib/task/sections.ts
CHANGED
|
@@ -73,7 +73,7 @@ class DocumentMutationError extends Error {
|
|
|
73
73
|
|
|
74
74
|
function parseTable(
|
|
75
75
|
content: string,
|
|
76
|
-
input: { sectionAliases: readonly string[]; columns: readonly string[] }
|
|
76
|
+
input: { sectionAliases: readonly string[]; columns: readonly string[]; keyColumn?: string }
|
|
77
77
|
): ParsedTable | null {
|
|
78
78
|
validateAliases(input.sectionAliases, 'table section aliases');
|
|
79
79
|
if (
|
|
@@ -83,6 +83,10 @@ function parseTable(
|
|
|
83
83
|
) {
|
|
84
84
|
throw new DocumentMutationError('MUTATION_INVALID', 'table columns are invalid');
|
|
85
85
|
}
|
|
86
|
+
const keyColumn = input.keyColumn ?? input.columns[0]!;
|
|
87
|
+
if (!input.columns.includes(keyColumn)) {
|
|
88
|
+
throw new DocumentMutationError('MUTATION_INVALID', 'table key column is invalid');
|
|
89
|
+
}
|
|
86
90
|
const sections = matchingSections(content, input.sectionAliases);
|
|
87
91
|
if (sections.length === 0) return null;
|
|
88
92
|
if (sections.length > 1) {
|
|
@@ -125,7 +129,7 @@ function parseTable(
|
|
|
125
129
|
column,
|
|
126
130
|
decodeCell(parsed.cells[columnIndex]!).trim()
|
|
127
131
|
]));
|
|
128
|
-
const key = values[
|
|
132
|
+
const key = values[keyColumn]!;
|
|
129
133
|
if (seen.has(key)) {
|
|
130
134
|
throw new DocumentMutationError('TABLE_DUPLICATE_KEY', `duplicate table key '${key}'`);
|
|
131
135
|
}
|