@fitlab-ai/agent-infra 0.9.5 → 0.9.6
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 +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +126 -16
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +48 -4
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/git/workflow.js +0 -1
- package/dist/lib/internal/codex-lifecycle.js +100 -2
- 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 +5 -4
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +15 -16
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +318 -89
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/executor.js +136 -0
- package/dist/lib/sandbox/control/lifecycle.js +348 -0
- package/dist/lib/sandbox/control/protocol.js +62 -26
- package/dist/lib/sandbox/control/server.js +222 -102
- package/dist/lib/sandbox/control/state.js +181 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +35 -24
- package/dist/lib/sandbox/recovery.js +262 -45
- package/dist/lib/sandbox/workspace-view.js +73 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/process-control.js +6 -0
- package/dist/lib/task/artifact-lifecycle.js +5 -4
- package/dist/lib/task/codex-orchestration.js +161 -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/delegation-receipts.js +25 -0
- package/dist/lib/task/events.js +10 -4
- 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/orchestration.js +133 -4
- package/dist/lib/task/verification-engine.js +38 -6
- package/dist/lib/task/verification.js +1 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +142 -15
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +51 -3
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/git/workflow.ts +0 -1
- package/lib/internal/codex-lifecycle.ts +107 -3
- 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 +4 -4
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +17 -21
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +368 -103
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/executor.ts +163 -0
- package/lib/sandbox/control/lifecycle.ts +342 -0
- package/lib/sandbox/control/protocol.ts +96 -69
- package/lib/sandbox/control/server.ts +239 -110
- package/lib/sandbox/control/state.ts +196 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +31 -13
- package/lib/sandbox/recovery.ts +301 -60
- package/lib/sandbox/workspace-view.ts +81 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/process-control.ts +11 -0
- package/lib/task/artifact-lifecycle.ts +5 -4
- package/lib/task/codex-orchestration.ts +233 -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/delegation-receipts.ts +52 -3
- package/lib/task/events.ts +9 -4
- 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/orchestration.ts +199 -4
- package/lib/task/verification-engine.ts +42 -8
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +48 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- 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/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 +14 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +44 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +44 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -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
|
@@ -74,7 +74,7 @@ type ModelPolicySource = Readonly<{
|
|
|
74
74
|
client: AgentClientId;
|
|
75
75
|
resolvedAt: string;
|
|
76
76
|
}>;
|
|
77
|
-
type
|
|
77
|
+
type ModelPolicyRecovery = Readonly<{
|
|
78
78
|
code: 'MODEL_POLICY_SUPPLEMENTED';
|
|
79
79
|
recoveredAt: string;
|
|
80
80
|
previousSchemaVersion: 1;
|
|
@@ -85,6 +85,26 @@ type OrchestrationRecovery = Readonly<{
|
|
|
85
85
|
pendingDelegation: false;
|
|
86
86
|
resultingStatus: OrchestrationStatus;
|
|
87
87
|
}>;
|
|
88
|
+
type ClientCapabilityRecovery = Readonly<{
|
|
89
|
+
code: 'CLIENT_CAPABILITY_ENABLED';
|
|
90
|
+
recoveredAt: string;
|
|
91
|
+
previousSchemaVersion: 2;
|
|
92
|
+
previousStatus: 'paused';
|
|
93
|
+
previousPause: Readonly<{ code: 'ORCHESTRATION_CLIENT_UNSUPPORTED'; message: string; recoverable: boolean }>;
|
|
94
|
+
client: 'codex';
|
|
95
|
+
guards: Readonly<{
|
|
96
|
+
stepCount: 0;
|
|
97
|
+
nextStage: null;
|
|
98
|
+
baselineEmpty: true;
|
|
99
|
+
receiptCount: 0;
|
|
100
|
+
pendingDelegation: false;
|
|
101
|
+
commitAuthorizationUnused: true;
|
|
102
|
+
completionEvidenceAbsent: true;
|
|
103
|
+
commitIntentAbsent: true;
|
|
104
|
+
}>;
|
|
105
|
+
resultingStatus: 'running';
|
|
106
|
+
}>;
|
|
107
|
+
type OrchestrationRecovery = ModelPolicyRecovery | ClientCapabilityRecovery;
|
|
88
108
|
type CleanCompletionEvidence = Readonly<{
|
|
89
109
|
kind: 'reviewed-head-clean';
|
|
90
110
|
observedAt: string;
|
|
@@ -369,6 +389,23 @@ function resolveProjectPolicy(repoRoot: string, client: AgentClientId): Orchestr
|
|
|
369
389
|
return normalizeAgentClients(raw).state[client].orchestration;
|
|
370
390
|
}
|
|
371
391
|
|
|
392
|
+
function canRecoverCodexUnsupportedPause(run: OrchestrationRun, taskDir: string): boolean {
|
|
393
|
+
return run.schemaVersion === 2
|
|
394
|
+
&& run.status === 'paused'
|
|
395
|
+
&& run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
396
|
+
&& run.modelPolicySource?.client === 'codex'
|
|
397
|
+
&& run.stepCount === 0
|
|
398
|
+
&& run.nextStage === null
|
|
399
|
+
&& run.baseline === ''
|
|
400
|
+
&& run.pendingDelegation === null
|
|
401
|
+
&& run.receipts.length === 0
|
|
402
|
+
&& run.commitAuthorization?.issuedAt === null
|
|
403
|
+
&& run.commitAuthorization?.consumedAt === null
|
|
404
|
+
&& run.completionEvidence == null
|
|
405
|
+
&& !fs.existsSync(commitIntentPath(taskDir))
|
|
406
|
+
&& (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
|
|
407
|
+
}
|
|
408
|
+
|
|
372
409
|
function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
373
410
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
374
411
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
@@ -415,6 +452,39 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
415
452
|
return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
|
|
416
453
|
}
|
|
417
454
|
}
|
|
455
|
+
if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
|
|
456
|
+
if (!canRecoverCodexUnsupportedPause(existing, resolved.taskDir)) {
|
|
457
|
+
return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
458
|
+
}
|
|
459
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
460
|
+
const previousPause = existing.pause as ClientCapabilityRecovery['previousPause'];
|
|
461
|
+
const recovery: ClientCapabilityRecovery = {
|
|
462
|
+
code: 'CLIENT_CAPABILITY_ENABLED',
|
|
463
|
+
recoveredAt: now,
|
|
464
|
+
previousSchemaVersion: 2,
|
|
465
|
+
previousStatus: 'paused',
|
|
466
|
+
previousPause,
|
|
467
|
+
client: 'codex',
|
|
468
|
+
guards: {
|
|
469
|
+
stepCount: 0,
|
|
470
|
+
nextStage: null,
|
|
471
|
+
baselineEmpty: true,
|
|
472
|
+
receiptCount: 0,
|
|
473
|
+
pendingDelegation: false,
|
|
474
|
+
commitAuthorizationUnused: true,
|
|
475
|
+
completionEvidenceAbsent: true,
|
|
476
|
+
commitIntentAbsent: true
|
|
477
|
+
},
|
|
478
|
+
resultingStatus: 'running'
|
|
479
|
+
};
|
|
480
|
+
const resumed = withUpdatedRun(existing, {
|
|
481
|
+
status: 'running',
|
|
482
|
+
pause: null,
|
|
483
|
+
recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
|
|
484
|
+
}, () => now);
|
|
485
|
+
saveRun(resolved.taskDir, resumed);
|
|
486
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
487
|
+
}
|
|
418
488
|
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
419
489
|
const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
|
|
420
490
|
saveRun(resolved.taskDir, resumed);
|
|
@@ -1377,7 +1447,7 @@ function matchingDelegations(
|
|
|
1377
1447
|
.filter((entry) => entry.isDirectory())
|
|
1378
1448
|
.flatMap((entry) => {
|
|
1379
1449
|
const run = readRun(path.join(activeRoot, entry.name));
|
|
1380
|
-
if (!run
|
|
1450
|
+
if (run?.status !== 'running' || !run.pendingDelegation) return [];
|
|
1381
1451
|
const receipt = run.pendingDelegation;
|
|
1382
1452
|
return predicate(receipt)
|
|
1383
1453
|
? [{ taskId: entry.name, run }]
|
|
@@ -1411,13 +1481,44 @@ function activateMatchingOrchestrationDelegation(
|
|
|
1411
1481
|
(receipt) => receipt.status === 'prepared',
|
|
1412
1482
|
options
|
|
1413
1483
|
);
|
|
1414
|
-
if ('status' in matched)
|
|
1484
|
+
if ('status' in matched) {
|
|
1485
|
+
if (matched.error?.code !== 'ORCHESTRATION_DELEGATION_MISSING') return matched;
|
|
1486
|
+
const replay = uniqueMatchingDelegation(
|
|
1487
|
+
client,
|
|
1488
|
+
(receipt) => receipt.childId === event.childId && ['activated', 'stage-completed'].includes(receipt.status),
|
|
1489
|
+
options
|
|
1490
|
+
);
|
|
1491
|
+
if ('status' in replay) return matched;
|
|
1492
|
+
const receipt = replay.run.pendingDelegation!;
|
|
1493
|
+
const sameEvidence = receipt.parentId === event.parentId
|
|
1494
|
+
&& receipt.spawnMode === event.spawnMode
|
|
1495
|
+
&& receipt.actualModel === event.actualModel
|
|
1496
|
+
&& receipt.actualReasoningEffort === event.actualReasoningEffort
|
|
1497
|
+
&& receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
|
|
1498
|
+
&& receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
|
|
1499
|
+
&& receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
|
|
1500
|
+
if (!sameEvidence) {
|
|
1501
|
+
return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
|
|
1502
|
+
}
|
|
1503
|
+
return { status: replay.run.status, changed: false, taskId: replay.taskId, run: replay.run, next: null, error: null };
|
|
1504
|
+
}
|
|
1415
1505
|
if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
|
|
1416
1506
|
return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
|
|
1417
1507
|
}
|
|
1418
1508
|
return activateOrchestrationDelegation(matched.taskId, event, options);
|
|
1419
1509
|
}
|
|
1420
1510
|
|
|
1511
|
+
function pauseMatchingOrchestrationDelegation(
|
|
1512
|
+
client: AgentClientId,
|
|
1513
|
+
code: string,
|
|
1514
|
+
message: string,
|
|
1515
|
+
options: OrchestrationOptions = {}
|
|
1516
|
+
): OrchestrationResult {
|
|
1517
|
+
const matched = uniqueMatchingDelegation(client, () => true, options);
|
|
1518
|
+
if ('status' in matched) return matched;
|
|
1519
|
+
return pauseOrchestration(matched.taskId, code, message, true, options);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1421
1522
|
function sealMatchingOrchestrationDelegation(
|
|
1422
1523
|
client: AgentClientId,
|
|
1423
1524
|
event: Readonly<{ nativeAgent: string; childId: string }>,
|
|
@@ -1462,6 +1563,96 @@ function sealMatchingOrchestrationDelegation(
|
|
|
1462
1563
|
}
|
|
1463
1564
|
}
|
|
1464
1565
|
|
|
1566
|
+
function sealMatchingOrchestrationDelegationWithHostEvidence(
|
|
1567
|
+
client: AgentClientId,
|
|
1568
|
+
event: Readonly<{ nativeAgent: string; childId: string }>,
|
|
1569
|
+
consumeEvidence: (receipt: DelegationReceipt) => Readonly<{
|
|
1570
|
+
stopRevision: number;
|
|
1571
|
+
consumer: string;
|
|
1572
|
+
consumedAt: string;
|
|
1573
|
+
}>,
|
|
1574
|
+
options: OrchestrationOptions = {}
|
|
1575
|
+
): OrchestrationResult {
|
|
1576
|
+
const role = managedDelegationRole(event.nativeAgent);
|
|
1577
|
+
if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
1578
|
+
const matched = uniqueMatchingDelegation(
|
|
1579
|
+
client,
|
|
1580
|
+
(receipt) => (
|
|
1581
|
+
(receipt.status === 'stage-completed' || receipt.status === 'sealed')
|
|
1582
|
+
&& receipt.childId === event.childId
|
|
1583
|
+
),
|
|
1584
|
+
options
|
|
1585
|
+
);
|
|
1586
|
+
if ('status' in matched) return matched;
|
|
1587
|
+
const receipt = matched.run.pendingDelegation!;
|
|
1588
|
+
if (receipt.status === 'sealed') {
|
|
1589
|
+
return reconcileMatchingOrchestrationDelegation(client, event.childId, options);
|
|
1590
|
+
}
|
|
1591
|
+
if (receipt.role !== role) {
|
|
1592
|
+
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
1593
|
+
}
|
|
1594
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1595
|
+
try {
|
|
1596
|
+
const gitRoot = gitRootFor(repoRoot, options);
|
|
1597
|
+
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
1598
|
+
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1599
|
+
gitRoot,
|
|
1600
|
+
stateRoot: repoRoot,
|
|
1601
|
+
taskId: snapshotTaskId
|
|
1602
|
+
});
|
|
1603
|
+
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
1604
|
+
const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
|
|
1605
|
+
const validated = sealDelegation(receipt, baseEvent, { now: options.now });
|
|
1606
|
+
if (!validated.ok) {
|
|
1607
|
+
return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
|
|
1608
|
+
}
|
|
1609
|
+
const hostEvidence = consumeEvidence(receipt);
|
|
1610
|
+
const sealed = sealDelegation(receipt, { ...baseEvent, hostEvidence }, { now: options.now });
|
|
1611
|
+
if (!sealed.ok) return pauseOrchestration(matched.taskId, sealed.code, sealed.message, true, options);
|
|
1612
|
+
const resolved = resolveTaskRef(matched.taskId, { repoRoot });
|
|
1613
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1614
|
+
const updated = withUpdatedRun(matched.run, { pendingDelegation: sealed.receipt }, options.now);
|
|
1615
|
+
saveRun(resolved.taskDir, updated);
|
|
1616
|
+
return { status: 'running', changed: true, taskId: matched.taskId, run: updated, next: null, error: null };
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
return pauseOrchestration(
|
|
1619
|
+
matched.taskId,
|
|
1620
|
+
'ORCHESTRATION_CODEX_EVIDENCE_FAILED',
|
|
1621
|
+
error instanceof Error ? error.message : String(error),
|
|
1622
|
+
true,
|
|
1623
|
+
options
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
function reconcileMatchingOrchestrationDelegation(
|
|
1629
|
+
client: AgentClientId,
|
|
1630
|
+
childId: string,
|
|
1631
|
+
options: OrchestrationOptions = {}
|
|
1632
|
+
): OrchestrationResult {
|
|
1633
|
+
const matched = uniqueMatchingDelegation(
|
|
1634
|
+
client,
|
|
1635
|
+
(receipt) => receipt.childId === childId,
|
|
1636
|
+
options
|
|
1637
|
+
);
|
|
1638
|
+
if ('status' in matched) return matched;
|
|
1639
|
+
const receipt = matched.run.pendingDelegation!;
|
|
1640
|
+
if (
|
|
1641
|
+
receipt.status !== 'sealed'
|
|
1642
|
+
|| receipt.hostEvidence?.consumer !== receipt.id
|
|
1643
|
+
|| receipt.hostEvidence.consumedAt === null
|
|
1644
|
+
) {
|
|
1645
|
+
return pauseOrchestration(
|
|
1646
|
+
matched.taskId,
|
|
1647
|
+
'ORCHESTRATION_CODEX_RECONCILIATION_FAILED',
|
|
1648
|
+
'Codex native spawn did not produce one sealed receipt with consumed host evidence',
|
|
1649
|
+
true,
|
|
1650
|
+
options
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
return { status: matched.run.status, changed: false, taskId: matched.taskId, run: matched.run, next: null, error: null };
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1465
1656
|
function activateOrchestrationDelegation(
|
|
1466
1657
|
taskRef: string,
|
|
1467
1658
|
event: Parameters<typeof activateDelegation>[1],
|
|
@@ -1628,13 +1819,16 @@ export {
|
|
|
1628
1819
|
completeOrchestrationStage,
|
|
1629
1820
|
inspectOrchestrationStage,
|
|
1630
1821
|
orchestrationPath,
|
|
1822
|
+
pauseMatchingOrchestrationDelegation,
|
|
1631
1823
|
pauseOrchestration,
|
|
1632
1824
|
planOrchestrationStageCompletion,
|
|
1633
1825
|
prepareOrchestrationDelegation,
|
|
1634
1826
|
readRun,
|
|
1827
|
+
reconcileMatchingOrchestrationDelegation,
|
|
1635
1828
|
recoverCommitIntent,
|
|
1636
1829
|
routeOrchestration,
|
|
1637
1830
|
sealMatchingOrchestrationDelegation,
|
|
1831
|
+
sealMatchingOrchestrationDelegationWithHostEvidence,
|
|
1638
1832
|
sealOrchestrationDelegation,
|
|
1639
1833
|
startCommitAttempt,
|
|
1640
1834
|
statusCommitIntent,
|
|
@@ -1650,5 +1844,6 @@ export type {
|
|
|
1650
1844
|
OrchestrationRun,
|
|
1651
1845
|
OrchestrationStageCompletion,
|
|
1652
1846
|
OrchestrationStageIdentity,
|
|
1653
|
-
OrchestrationStatus
|
|
1847
|
+
OrchestrationStatus,
|
|
1848
|
+
OrchestrationOptions
|
|
1654
1849
|
};
|
|
@@ -268,14 +268,29 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
268
268
|
return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
|
|
269
269
|
}
|
|
270
270
|
for (const recovery of run.recoveryHistory) {
|
|
271
|
-
|
|
272
|
-
recovery.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
const validModelRecovery = recovery.code === 'MODEL_POLICY_SUPPLEMENTED'
|
|
272
|
+
&& recovery.previousSchemaVersion === 1
|
|
273
|
+
&& recovery.receiptCount === 0
|
|
274
|
+
&& recovery.pendingDelegation === false
|
|
275
|
+
&& exactText(recovery.recoveredAt)
|
|
276
|
+
&& recovery.policySource;
|
|
277
|
+
const guards = recovery.guards;
|
|
278
|
+
const validCapabilityRecovery = recovery.code === 'CLIENT_CAPABILITY_ENABLED'
|
|
279
|
+
&& recovery.previousSchemaVersion === 2
|
|
280
|
+
&& recovery.previousStatus === 'paused'
|
|
281
|
+
&& recovery.previousPause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
282
|
+
&& recovery.client === 'codex'
|
|
283
|
+
&& recovery.resultingStatus === 'running'
|
|
284
|
+
&& exactText(recovery.recoveredAt)
|
|
285
|
+
&& guards?.stepCount === 0
|
|
286
|
+
&& guards?.nextStage === null
|
|
287
|
+
&& guards?.baselineEmpty === true
|
|
288
|
+
&& guards?.receiptCount === 0
|
|
289
|
+
&& guards?.pendingDelegation === false
|
|
290
|
+
&& guards?.commitAuthorizationUnused === true
|
|
291
|
+
&& guards?.completionEvidenceAbsent === true
|
|
292
|
+
&& guards?.commitIntentAbsent === true;
|
|
293
|
+
if (!validModelRecovery && !validCapabilityRecovery) {
|
|
279
294
|
return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
|
|
280
295
|
}
|
|
281
296
|
}
|
|
@@ -333,6 +348,25 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
|
|
|
333
348
|
) {
|
|
334
349
|
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
|
|
335
350
|
}
|
|
351
|
+
if (receipt.client === 'codex') {
|
|
352
|
+
const host = receipt.hostEvidence;
|
|
353
|
+
if (
|
|
354
|
+
host?.kind !== 'codex-lifecycle-v1'
|
|
355
|
+
|| !exactText(host.hookDefinitionHash)
|
|
356
|
+
|| !Number.isSafeInteger(host.startRevision)
|
|
357
|
+
|| host.startRevision < 1
|
|
358
|
+
) {
|
|
359
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex start evidence`);
|
|
360
|
+
}
|
|
361
|
+
if (['sealed', 'consumed'].includes(receipt.status) && (
|
|
362
|
+
!Number.isSafeInteger(host.stopRevision)
|
|
363
|
+
|| host.stopRevision <= host.startRevision
|
|
364
|
+
|| host.consumer !== receipt.id
|
|
365
|
+
|| !exactText(host.consumedAt)
|
|
366
|
+
)) {
|
|
367
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid Codex consumed stop evidence`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
336
370
|
}
|
|
337
371
|
}
|
|
338
372
|
if (run.receipts.some((receipt: any) => receipt.status !== 'consumed')) {
|
package/lib/task/verification.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { TaskWorkspaceState } from './resolve-ref.ts';
|
|
|
11
11
|
type VerificationEvent =
|
|
12
12
|
| 'analyze.awaiting-input' | 'analyze.completed'
|
|
13
13
|
| 'review-analysis.completed' | 'plan.completed' | 'review-plan.completed'
|
|
14
|
-
| 'code.completed' | 'review-code.completed' | 'manual-validation.completed'
|
|
14
|
+
| 'code.completed' | 'review-code.completed' | 'manual-validation.completed' | 'validation-run.completed'
|
|
15
15
|
| 'block-task.completed' | 'cancel-task.completed' | 'commit.completed'
|
|
16
16
|
| 'complete-task.preflight' | 'complete-task.completed'
|
|
17
17
|
| 'create-pr.completed' | 'create-task.completed'
|
|
@@ -62,6 +62,7 @@ const VERIFICATION_CATALOG: Readonly<Record<VerificationEvent, VerificationSpec>
|
|
|
62
62
|
'code.completed': gate('code-task', 'active', 'code'),
|
|
63
63
|
'review-code.completed': gate('review-code', 'active', 'review-code'),
|
|
64
64
|
'manual-validation.completed': gate('complete-manual-validation', 'active', 'manual-validation'),
|
|
65
|
+
'validation-run.completed': gate('run-manual-validation', 'active', 'validation-run'),
|
|
65
66
|
'block-task.completed': gate('block-task', 'blocked'),
|
|
66
67
|
'cancel-task.completed': gate('cancel-task', 'completed'),
|
|
67
68
|
'commit.completed': gate('commit', 'active'),
|
package/package.json
CHANGED
|
@@ -5,6 +5,8 @@ import { readFileSync } from 'node:fs';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
7
|
const MAX_INPUT_BYTES = 64 * 1024;
|
|
8
|
+
const repoPackageJson = fileURLToPath(new URL('../../package.json', import.meta.url));
|
|
9
|
+
const sourceInternalCli = fileURLToPath(new URL('../../bin/internal-cli.ts', import.meta.url));
|
|
8
10
|
const localInternalCli = fileURLToPath(new URL('../../dist/bin/internal-cli.js', import.meta.url));
|
|
9
11
|
const codexHooks = fileURLToPath(new URL('../../.codex/hooks.json', import.meta.url));
|
|
10
12
|
const clientIndex = process.argv.indexOf('--client');
|
|
@@ -13,6 +15,27 @@ if (client !== 'claude-code' && client !== 'codex') {
|
|
|
13
15
|
process.stderr.write('Lifecycle delegation hook requires a supported --client value\n');
|
|
14
16
|
process.exit(1);
|
|
15
17
|
}
|
|
18
|
+
|
|
19
|
+
function internalCliInvocation(args) {
|
|
20
|
+
let isAgentInfraSourceCheckout = false;
|
|
21
|
+
try {
|
|
22
|
+
isAgentInfraSourceCheckout = JSON.parse(readFileSync(repoPackageJson, 'utf8')).name === '@fitlab-ai/agent-infra';
|
|
23
|
+
} catch {
|
|
24
|
+
// Installed project hooks do not require a repository package.json.
|
|
25
|
+
}
|
|
26
|
+
if (isAgentInfraSourceCheckout && existsSync(sourceInternalCli)) {
|
|
27
|
+
return {
|
|
28
|
+
command: process.execPath,
|
|
29
|
+
commandArgs: ['--experimental-strip-types', sourceInternalCli, ...args],
|
|
30
|
+
shell: false
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (existsSync(localInternalCli)) {
|
|
34
|
+
return { command: process.execPath, commandArgs: [localInternalCli, ...args], shell: false };
|
|
35
|
+
}
|
|
36
|
+
return { command: 'agent-infra-internal', commandArgs: args, shell: process.platform === 'win32' };
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
let input = '';
|
|
17
40
|
process.stdin.setEncoding('utf8');
|
|
18
41
|
process.stdin.on('data', (chunk) => {
|
|
@@ -34,15 +57,28 @@ process.stdin.on('end', () => {
|
|
|
34
57
|
const explicitPhase = phaseIndex >= 0 ? process.argv[phaseIndex + 1] : '';
|
|
35
58
|
const hook = String(explicitPhase || event.hook_event_name || event.event || '').toLowerCase();
|
|
36
59
|
const toolInput = event.tool_input && typeof event.tool_input === 'object' ? event.tool_input : {};
|
|
60
|
+
const toolName = String(event.tool_name || event.tool || '').toLowerCase();
|
|
61
|
+
let toolResponse = {};
|
|
62
|
+
try {
|
|
63
|
+
toolResponse = typeof event.tool_response === 'string'
|
|
64
|
+
? JSON.parse(event.tool_response)
|
|
65
|
+
: (event.tool_response || {});
|
|
66
|
+
} catch {
|
|
67
|
+
// Core ignores opaque tool responses; only the host timeout bit is forwarded.
|
|
68
|
+
}
|
|
37
69
|
const nativeAgent = event.agent_name || event.agent_type || event.agent?.name
|
|
38
70
|
|| toolInput.agent_type || toolInput.agent || toolInput.name;
|
|
39
|
-
if (!String(nativeAgent || '').startsWith('agent-infra-lifecycle-')) process.exit(0);
|
|
40
71
|
if (client === 'codex') {
|
|
41
72
|
const phases = new Set(['pre-tool', 'subagent-start', 'post-tool', 'subagent-stop']);
|
|
42
73
|
if (!phases.has(hook)) {
|
|
43
74
|
process.stderr.write('Managed Codex lifecycle hook event has an unknown type\n');
|
|
44
75
|
process.exit(1);
|
|
45
76
|
}
|
|
77
|
+
const managedAgent = /^agent-infra-lifecycle-(executor|reviewer)$/.test(String(nativeAgent || ''));
|
|
78
|
+
const parentReconcile = hook === 'post-tool'
|
|
79
|
+
&& toolName === 'collaborationwait_agent'
|
|
80
|
+
&& toolResponse.timed_out !== true;
|
|
81
|
+
if (!managedAgent && !parentReconcile) process.exit(0);
|
|
46
82
|
const hookDefinitionHash = createHash('sha256').update(readFileSync(codexHooks)).digest('hex');
|
|
47
83
|
const normalized = {
|
|
48
84
|
sessionId: String(event.session_id || ''),
|
|
@@ -52,19 +88,20 @@ process.stdin.on('end', () => {
|
|
|
52
88
|
nativeAgent: String(nativeAgent),
|
|
53
89
|
requestedModel: String(toolInput.model || ''),
|
|
54
90
|
requestedReasoningEffort: String(toolInput.reasoning_effort || toolInput.reasoningEffort || ''),
|
|
55
|
-
hookDefinitionHash
|
|
91
|
+
hookDefinitionHash,
|
|
92
|
+
toolName,
|
|
93
|
+
taskName: String(toolInput.task_name || ''),
|
|
94
|
+
transcriptPath: String(event.transcript_path || '')
|
|
56
95
|
};
|
|
57
|
-
const args = ['codex-lifecycle', 'hook-event', '--event', hook];
|
|
96
|
+
const args = ['codex-lifecycle', 'hook-event', '--event', hook, '--bridge', 'true'];
|
|
58
97
|
try {
|
|
59
|
-
const
|
|
60
|
-
const command = useLocal ? process.execPath : 'agent-infra-internal';
|
|
61
|
-
const commandArgs = useLocal ? [localInternalCli, ...args] : args;
|
|
98
|
+
const { command, commandArgs, shell } = internalCliInvocation(args);
|
|
62
99
|
const output = execFileSync(command, commandArgs, {
|
|
63
100
|
encoding: 'utf8',
|
|
64
101
|
input: JSON.stringify(normalized),
|
|
65
|
-
shell
|
|
102
|
+
shell,
|
|
66
103
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
-
timeout:
|
|
104
|
+
timeout: 15_000
|
|
68
105
|
});
|
|
69
106
|
process.stdout.write(output);
|
|
70
107
|
} catch (error) {
|
|
@@ -74,6 +111,7 @@ process.stdin.on('end', () => {
|
|
|
74
111
|
}
|
|
75
112
|
return;
|
|
76
113
|
}
|
|
114
|
+
if (!/^agent-infra-lifecycle-(executor|reviewer)$/.test(String(nativeAgent || ''))) process.exit(0);
|
|
77
115
|
const start = hook.includes('start');
|
|
78
116
|
const stop = hook.includes('stop');
|
|
79
117
|
if (!start && !stop) {
|
|
@@ -102,12 +140,10 @@ process.stdin.on('end', () => {
|
|
|
102
140
|
);
|
|
103
141
|
}
|
|
104
142
|
try {
|
|
105
|
-
const
|
|
106
|
-
const command = useLocal ? process.execPath : 'agent-infra-internal';
|
|
107
|
-
const commandArgs = useLocal ? [localInternalCli, ...args] : args;
|
|
143
|
+
const { command, commandArgs, shell } = internalCliInvocation(args);
|
|
108
144
|
const output = execFileSync(command, commandArgs, {
|
|
109
145
|
encoding: 'utf8',
|
|
110
|
-
shell
|
|
146
|
+
shell,
|
|
111
147
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
112
148
|
});
|
|
113
149
|
process.stdout.write(output);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
## Recovery
|
|
12
12
|
|
|
13
|
-
`orchestration.json` is the detailed state source. A v2 run persists complete policy, source, and append-only recovery history. Only a v1 run with no pending delegation and zero receipts may be upgraded in place after supplying a complete policy; any historical receipt stays paused because effort is unverifiable. Migration is forward-only: old binaries must not advance active v2 runs.
|
|
13
|
+
`orchestration.json` is the detailed state source. A v2 run persists complete policy, source, and append-only recovery history. Only a v1 run with no pending delegation and zero receipts may be upgraded in place after supplying a complete policy; any historical receipt stays paused because effort is unverifiable. A historical Codex `ORCHESTRATION_CLIENT_UNSUPPORTED` v2 pause appends `CLIENT_CAPABILITY_ENABLED` and resumes only when step count, next stage, baseline, receipts, pending delegation, commit authorization, completion evidence, and commit intent all prove that execution never advanced; unknown or non-empty evidence preserves the pause. Migration is forward-only: old binaries must not advance active v2 runs.
|
|
14
14
|
|
|
15
15
|
## Model Policy
|
|
16
16
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
## 恢复语义
|
|
12
12
|
|
|
13
|
-
`orchestration.json` 是详细状态源。v2 run 保存完整策略、来源与 append-only 恢复历史。只有无 pending delegation 且零历史 receipt 的 v1 run 可在补齐完整策略后原地升级;任一历史 receipt 因缺 effort
|
|
13
|
+
`orchestration.json` 是详细状态源。v2 run 保存完整策略、来源与 append-only 恢复历史。只有无 pending delegation 且零历史 receipt 的 v1 run 可在补齐完整策略后原地升级;任一历史 receipt 因缺 effort 证据保持暂停。Codex 的历史 `ORCHESTRATION_CLIENT_UNSUPPORTED` v2 pause 仅在 stepCount、nextStage、baseline、receipts、pending delegation、commit authorization、completion evidence 与 commit intent 全部证明从未推进时追加 `CLIENT_CAPABILITY_ENABLED` 并恢复;任一未知或非空证据保持原暂停。迁移是 forward-only,旧二进制不得推进 v2 active run。
|
|
14
14
|
|
|
15
15
|
## 模型策略
|
|
16
16
|
|
|
@@ -47,6 +47,7 @@ SKILLs currently covered by this exemption:
|
|
|
47
47
|
|
|
48
48
|
- `release`: may confirm publish after presenting the latest release snapshot
|
|
49
49
|
- `create-release-note`: may confirm publish after presenting exact staged notes and their digest
|
|
50
|
+
- `post-release`: may confirm a normal push after preparing the local post commit and presenting the complete `postConfirmation` and digest
|
|
50
51
|
|
|
51
52
|
### Exemption 5: Complete model policy missing at lifecycle start
|
|
52
53
|
|
|
@@ -15,16 +15,42 @@ Require one explicit canonical SemVer `{version}`. Do not fall back to the lates
|
|
|
15
15
|
agent-infra-internal release-workflow inspect {version}
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Pending or unknown is blocked; a definite failure is failed.
|
|
18
|
+
Pending or unknown is blocked; a definite failure is failed. Report `complete` directly from external facts.
|
|
19
19
|
|
|
20
|
-
## 2.
|
|
20
|
+
## 2. Prepare Local Post-release Work
|
|
21
|
+
|
|
22
|
+
When incomplete, run:
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
|
-
agent-infra-internal release-workflow post {version}
|
|
25
|
+
agent-infra-internal release-workflow post-prepare {version}
|
|
26
|
+
agent-infra-internal release-workflow inspect {version}
|
|
24
27
|
```
|
|
25
28
|
|
|
26
|
-
The core owns build, next development version, inline artifacts, explicit-path commit
|
|
29
|
+
The core owns build, the optional demo, next development version, inline artifacts, and the explicit-path post commit. This action must not push. Replays recover from Git and channel facts without repeating completed work.
|
|
30
|
+
|
|
31
|
+
## 3. Check the Confirmation Snapshot
|
|
32
|
+
|
|
33
|
+
- `complete`: report completion without asking or publishing.
|
|
34
|
+
- `postConfirmation` present: display every field and its `sha256`, then continue to step 4.
|
|
35
|
+
- `postConfirmation` absent: report diagnostics such as `isHead=false` or a dirty worktree/index and stop without asking or publishing.
|
|
27
36
|
|
|
28
|
-
##
|
|
37
|
+
## 4. Authorize the Current Snapshot
|
|
38
|
+
|
|
39
|
+
Only after displaying the complete `postConfirmation` in the current session, ask once whether to publish. Continue only for an unambiguous affirmative answer. Denial, adjustment, a question, ambiguity, or interruption stops. Authorization never carries across sessions or snapshots.
|
|
40
|
+
|
|
41
|
+
## 5. Publish the Confirmed Snapshot
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
agent-infra-internal release-workflow post-publish {version} \
|
|
45
|
+
--expected-sha256 "{post-confirmation-sha256}"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The core re-inspects and verifies the digest before one normal branch push; force pushes are forbidden. On snapshot drift, stop and return to step 1 for a new display and authorization.
|
|
49
|
+
|
|
50
|
+
## 6. Re-inspect and Report
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agent-infra-internal release-workflow inspect {version}
|
|
54
|
+
```
|
|
29
55
|
|
|
30
|
-
Report channel, smoke, commit, and
|
|
56
|
+
Report all channel, released/new version, smoke, post commit, and remote branch facts. Describe completion only for phase `complete`; never present failed, degraded, or blocked as success.
|
|
@@ -15,16 +15,42 @@ description: >
|
|
|
15
15
|
agent-infra-internal release-workflow inspect {version}
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
pending/unknown 必须 blocked,确定失败必须 failed
|
|
18
|
+
pending/unknown 必须 blocked,确定失败必须 failed。`complete` 直接按外部事实报告完成。
|
|
19
19
|
|
|
20
|
-
## 2.
|
|
20
|
+
## 2. 准备本地后处理
|
|
21
|
+
|
|
22
|
+
尚未完成时执行:
|
|
21
23
|
|
|
22
24
|
```bash
|
|
23
|
-
agent-infra-internal release-workflow post {version}
|
|
25
|
+
agent-infra-internal release-workflow post-prepare {version}
|
|
26
|
+
agent-infra-internal release-workflow inspect {version}
|
|
24
27
|
```
|
|
25
28
|
|
|
26
|
-
core 负责 build
|
|
29
|
+
core 负责 build、可选 demo、下一开发版本、内联产物和显式路径 post commit;该动作不得 push。重跑从 Git 与渠道事实恢复,不重复已完成操作。
|
|
30
|
+
|
|
31
|
+
## 3. 检查确认快照
|
|
32
|
+
|
|
33
|
+
- `complete`:报告完成,不询问、不 publish。
|
|
34
|
+
- 存在 `postConfirmation`:展示完整字段和 `sha256`,进入步骤 4。
|
|
35
|
+
- 不存在 `postConfirmation`:报告 `isHead=false`、工作树或暂存区非空等诊断并停止,不询问、不 publish。
|
|
27
36
|
|
|
28
|
-
##
|
|
37
|
+
## 4. 获取当前快照授权
|
|
38
|
+
|
|
39
|
+
仅在当前会话已展示完整 `postConfirmation` 后询问一次是否发布。只有无歧义肯定答复才继续;否定、调整、疑问、歧义或中断均停止。授权不跨会话或快照复用。
|
|
40
|
+
|
|
41
|
+
## 5. 发布已确认快照
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
agent-infra-internal release-workflow post-publish {version} \
|
|
45
|
+
--expected-sha256 "{post-confirmation-sha256}"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
core 重新 inspect 并校验摘要后仅执行普通 branch push,禁止 force push。摘要漂移时停止并回到步骤 1 重新展示、重新确认。
|
|
49
|
+
|
|
50
|
+
## 6. 写后复核并报告
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agent-infra-internal release-workflow inspect {version}
|
|
54
|
+
```
|
|
29
55
|
|
|
30
|
-
报告全部渠道、smoke、commit
|
|
56
|
+
报告全部渠道、released/new version、smoke、post commit 与远端 branch 事实;只有阶段为 `complete` 才表述为完成,不把 failed/degraded/blocked 表述为成功。
|