@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.
Files changed (127) hide show
  1. package/dist/bin/internal-cli.js +5 -0
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
  7. package/dist/lib/defaults.json +10 -2
  8. package/dist/lib/git/workflow.js +30 -3
  9. package/dist/lib/internal/codex-lifecycle.js +69 -1
  10. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  11. package/dist/lib/internal/task-orchestration.js +37 -5
  12. package/dist/lib/platform/context.js +6 -4
  13. package/dist/lib/platform/github-client.js +55 -2
  14. package/dist/lib/process-data/checkpoint.js +319 -0
  15. package/dist/lib/process-data/index.js +128 -60
  16. package/dist/lib/process-data/materialize.js +84 -0
  17. package/dist/lib/process-data/normalize.js +34 -1
  18. package/dist/lib/process-data/reconcile.js +55 -1
  19. package/dist/lib/process-data/sources.js +422 -1
  20. package/dist/lib/process-data/store.js +341 -11
  21. package/dist/lib/sandbox/commands/create.js +103 -51
  22. package/dist/lib/sandbox/commands/rm.js +64 -15
  23. package/dist/lib/sandbox/commands/start.js +4 -3
  24. package/dist/lib/sandbox/control/container-identity.js +64 -0
  25. package/dist/lib/sandbox/control/executor.js +13 -7
  26. package/dist/lib/sandbox/control/lifecycle.js +238 -29
  27. package/dist/lib/sandbox/control/protocol.js +7 -0
  28. package/dist/lib/sandbox/control/server.js +202 -26
  29. package/dist/lib/sandbox/control/state.js +25 -15
  30. package/dist/lib/sandbox/control/timing.js +8 -0
  31. package/dist/lib/sandbox/mounts.js +5 -13
  32. package/dist/lib/sandbox/recovery.js +59 -42
  33. package/dist/lib/sandbox/workspace-view.js +57 -40
  34. package/dist/lib/server/daemon.js +1 -1
  35. package/dist/lib/server/process-control.js +11 -1
  36. package/dist/lib/server/process-state.js +57 -20
  37. package/dist/lib/task/artifact-lifecycle.js +82 -11
  38. package/dist/lib/task/artifact-receipts.js +128 -0
  39. package/dist/lib/task/codex-orchestration.js +81 -6
  40. package/dist/lib/task/commands/validate.js +1 -1
  41. package/dist/lib/task/create.js +10 -2
  42. package/dist/lib/task/delegation-receipts.js +84 -2
  43. package/dist/lib/task/events.js +139 -2
  44. package/dist/lib/task/ledger.js +13 -1
  45. package/dist/lib/task/orchestration.js +235 -14
  46. package/dist/lib/task/sections.js +5 -1
  47. package/dist/lib/task/task-execution-lock.js +5 -4
  48. package/dist/lib/task/verification-engine.js +40 -8
  49. package/dist/lifecycle-build-manifest.json +6 -0
  50. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
  51. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  52. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
  53. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  54. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
  55. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +33 -4
  56. package/lib/defaults.json +10 -2
  57. package/lib/git/workflow.ts +25 -2
  58. package/lib/internal/codex-lifecycle.ts +68 -1
  59. package/lib/internal/codex-sandbox-controller.ts +109 -0
  60. package/lib/internal/task-orchestration.ts +31 -4
  61. package/lib/platform/context.ts +8 -5
  62. package/lib/platform/github-client.ts +61 -3
  63. package/lib/process-data/checkpoint.ts +335 -0
  64. package/lib/process-data/index.ts +112 -60
  65. package/lib/process-data/materialize.ts +83 -0
  66. package/lib/process-data/normalize.ts +29 -1
  67. package/lib/process-data/reconcile.ts +64 -2
  68. package/lib/process-data/sources.ts +449 -2
  69. package/lib/process-data/store.ts +304 -11
  70. package/lib/process-data/types.ts +124 -2
  71. package/lib/sandbox/commands/create.ts +56 -7
  72. package/lib/sandbox/commands/rm.ts +64 -14
  73. package/lib/sandbox/commands/start.ts +4 -3
  74. package/lib/sandbox/control/container-identity.ts +93 -0
  75. package/lib/sandbox/control/executor.ts +14 -10
  76. package/lib/sandbox/control/lifecycle.ts +264 -33
  77. package/lib/sandbox/control/protocol.ts +31 -5
  78. package/lib/sandbox/control/server.ts +184 -24
  79. package/lib/sandbox/control/state.ts +34 -17
  80. package/lib/sandbox/control/timing.ts +12 -0
  81. package/lib/sandbox/mounts.ts +5 -15
  82. package/lib/sandbox/recovery.ts +71 -42
  83. package/lib/sandbox/workspace-view.ts +61 -40
  84. package/lib/server/daemon.ts +1 -1
  85. package/lib/server/process-control.ts +13 -3
  86. package/lib/server/process-state.ts +82 -27
  87. package/lib/task/artifact-lifecycle.ts +73 -10
  88. package/lib/task/artifact-receipts.ts +161 -0
  89. package/lib/task/codex-orchestration.ts +91 -5
  90. package/lib/task/commands/validate.ts +1 -1
  91. package/lib/task/create.ts +11 -2
  92. package/lib/task/delegation-receipts.ts +160 -8
  93. package/lib/task/events.ts +122 -1
  94. package/lib/task/ledger.ts +16 -1
  95. package/lib/task/orchestration.ts +356 -17
  96. package/lib/task/sections.ts +6 -2
  97. package/lib/task/task-execution-lock.ts +11 -9
  98. package/lib/task/verification-engine.ts +51 -16
  99. package/package.json +1 -1
  100. package/templates/.agents/README.en.md +7 -0
  101. package/templates/.agents/README.zh-CN.md +5 -0
  102. package/templates/.agents/hooks/lifecycle-delegation.js +25 -1
  103. package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
  104. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
  105. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  106. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  107. package/templates/.agents/rules/review-handshake.en.md +3 -1
  108. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  109. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  110. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  111. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  112. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  113. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  114. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  115. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  116. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  117. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  118. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  119. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  120. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  121. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  122. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  123. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -0
  124. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
  125. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  126. package/templates/.agents/workspace/README.en.md +0 -26
  127. 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 { DelegationReceipt, DelegationRole, DelegationStage } from './delegation-receipts.ts';
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 OrchestrationRecovery = ModelPolicyRecovery | ClientCapabilityRecovery;
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 !== 2 || !isV2Policy(run.modelPolicy)) return run;
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', 'schemaVersion 2 orchestration state is incomplete', resolved.taskId);
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
- const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
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: 2,
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: 2,
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<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
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<{ client: AgentClientId; requestedModel?: string; requestedReasoningEffort?: string }>,
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
- const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
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,
@@ -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[input.columns[0]!]!;
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
  }