@fitlab-ai/agent-infra 0.11.1 → 0.11.2

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 (88) hide show
  1. package/dist/lib/internal/cli-route-inventory.js +1 -1
  2. package/dist/lib/internal/platform-comment.js +59 -0
  3. package/dist/lib/internal/sandbox-control.js +4 -131
  4. package/dist/lib/internal/task-artifact.js +1 -1
  5. package/dist/lib/internal/task-operation-registry.js +1 -0
  6. package/dist/lib/internal/task-review.js +5 -5
  7. package/dist/lib/internal/task-validate.js +0 -6
  8. package/dist/lib/platform/comment-safety.js +7 -1
  9. package/dist/lib/platform/github-provider.js +13 -3
  10. package/dist/lib/platform/issue-comments.js +120 -13
  11. package/dist/lib/platform/provider-contract.d.ts +2 -0
  12. package/dist/lib/platform/provider-validation.js +7 -2
  13. package/dist/lib/platform/verification-sync.js +19 -2
  14. package/dist/lib/sandbox/control/executor.js +0 -38
  15. package/dist/lib/sandbox/control/finalization-response.js +10 -2
  16. package/dist/lib/sandbox/control/task-view.js +3 -1
  17. package/dist/lib/sandbox/control/workflow-executor.js +28 -43
  18. package/dist/lib/task/artifact-command.js +6 -6
  19. package/dist/lib/task/codex-orchestration.js +10 -208
  20. package/dist/lib/task/commit-operation.js +3 -18
  21. package/dist/lib/task/control-authority.js +1 -8
  22. package/dist/lib/task/delegation-receipts.js +14 -76
  23. package/dist/lib/task/events.js +89 -237
  24. package/dist/lib/task/finalization-state.js +2 -1
  25. package/dist/lib/task/finalization.js +273 -49
  26. package/dist/lib/task/lifecycle-execution.js +2 -11
  27. package/dist/lib/task/local-artifact-finalization.js +25 -242
  28. package/dist/lib/task/orchestration.js +0 -18
  29. package/dist/lib/task/review-command.js +0 -1
  30. package/dist/lib/task/review-finalization.js +46 -211
  31. package/dist/lib/task/workflow-command.js +4 -1
  32. package/dist/lib/task/workflow-dispatch.js +55 -3
  33. package/dist/lifecycle-build-manifest.json +3 -3
  34. package/lib/internal/cli-route-inventory.ts +1 -1
  35. package/lib/internal/platform-comment.ts +38 -0
  36. package/lib/internal/sandbox-control.ts +3 -145
  37. package/lib/internal/task-artifact.ts +1 -1
  38. package/lib/internal/task-operation-registry.ts +1 -0
  39. package/lib/internal/task-review.ts +5 -5
  40. package/lib/internal/task-validate.ts +0 -5
  41. package/lib/platform/comment-safety.ts +8 -0
  42. package/lib/platform/github-provider.ts +11 -2
  43. package/lib/platform/issue-comments.ts +132 -13
  44. package/lib/platform/provider-contract.ts +2 -0
  45. package/lib/platform/provider-validation.ts +6 -2
  46. package/lib/platform/verification-sync.ts +19 -2
  47. package/lib/sandbox/control/executor.ts +0 -44
  48. package/lib/sandbox/control/finalization-response.ts +10 -2
  49. package/lib/sandbox/control/task-view.ts +3 -1
  50. package/lib/sandbox/control/workflow-executor.ts +23 -50
  51. package/lib/task/artifact-command.ts +6 -7
  52. package/lib/task/codex-orchestration.ts +8 -241
  53. package/lib/task/commit-operation.ts +3 -23
  54. package/lib/task/control-authority.ts +1 -8
  55. package/lib/task/delegation-receipts.ts +12 -79
  56. package/lib/task/events.ts +106 -255
  57. package/lib/task/finalization-state.ts +2 -1
  58. package/lib/task/finalization.ts +277 -57
  59. package/lib/task/lifecycle-execution.ts +10 -21
  60. package/lib/task/local-artifact-finalization.ts +23 -281
  61. package/lib/task/orchestration.ts +0 -34
  62. package/lib/task/review-command.ts +0 -1
  63. package/lib/task/review-finalization.ts +45 -193
  64. package/lib/task/workflow-command.ts +4 -1
  65. package/lib/task/workflow-dispatch.ts +46 -3
  66. package/package.json +2 -2
  67. package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
  68. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
  69. package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
  70. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
  71. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  72. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  73. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  74. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  75. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  76. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  77. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  78. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  79. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  80. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  81. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  82. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  83. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  84. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  85. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  86. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  87. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  88. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
@@ -4,9 +4,15 @@ import type { SandboxControlResponse } from './protocol.ts';
4
4
  export function finalizationTerminalResponse(taskId: string, requestId: string, receipt: TaskFinalizationReceipt): SandboxControlResponse {
5
5
  const pendingSteps = [
6
6
  receipt.taskComment === 'pending' ? 'task-comment' : null,
7
- receipt.verification === 'pending' ? 'verification' : null
7
+ receipt.verification === 'pending' ? 'verification' : null,
8
+ receipt.summary === 'pending' ? 'summary' : null,
9
+ receipt.postSummaryVerification === 'pending' ? 'post-summary-verification' : null
8
10
  ].filter((step): step is string => step !== null);
9
- const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
11
+ const completedSteps = [
12
+ 'lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment',
13
+ receipt.verification === 'pending' ? null : 'verification', receipt.summary === 'pending' ? null : 'summary',
14
+ receipt.postSummaryVerification === 'pending' ? null : 'post-summary-verification'
15
+ ]
10
16
  .filter((step): step is string => step !== null);
11
17
  const warnings = receipt.warnings
12
18
  .filter((warning) => warning.status === 'open')
@@ -16,6 +22,8 @@ export function finalizationTerminalResponse(taskId: string, requestId: string,
16
22
  lifecycle: { status: 'no-op', changed: false, error: null },
17
23
  taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
18
24
  verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
25
+ summary: receipt.summary === 'pending' ? null : { status: 'no-op', changed: false, error: null },
26
+ postSummaryVerification: receipt.postSummaryVerification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
19
27
  completedSteps, pendingSteps,
20
28
  result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
21
29
  ? 'completed_with_warnings' : 'completed',
@@ -56,9 +56,11 @@ function receiptIdentity(value: unknown, generation: string, requestId?: string)
56
56
  const binding = receipt.controlBinding;
57
57
  if (!binding || typeof binding !== 'object' || Array.isArray(binding)) return null;
58
58
  const controlBinding = binding as Record<string, unknown>;
59
- if (receipt.version !== 2 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
59
+ if (receipt.version !== 3 || typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
60
60
  || !Number.isSafeInteger(receipt.revision) || (receipt.revision as number) < 0
61
61
  || receipt.lifecycle !== 'done'
62
+ || !['pending', 'done', 'skipped'].includes(String(receipt.summary))
63
+ || !['pending', 'done', 'skipped'].includes(String(receipt.postSummaryVerification))
62
64
  || controlBinding.generation !== generation
63
65
  || typeof controlBinding.requestId !== 'string'
64
66
  || !/^[a-f0-9-]{16,64}$/u.test(controlBinding.requestId)
@@ -1,3 +1,7 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+
1
5
  import { TASK_WORKFLOW_COMMANDS } from '../../task/workflow-command.ts';
2
6
 
3
7
  import { parseArtifactCommand, executeArtifactCommand } from '../../task/artifact-command.ts';
@@ -5,10 +9,8 @@ import { parseReviewCommand } from '../../task/review-command.ts';
5
9
  import { prepareLocalArtifact, preflightLocalArtifact, commitLocalArtifactProvenance } from '../../task/local-artifact-finalization.ts';
6
10
  import {
7
11
  prepareReviewSummaryCandidate,
8
- commitReviewSummaryProvenance,
9
12
  preflightReviewSummary
10
13
  } from '../../task/review-finalization.ts';
11
- import { readArtifactRecoveryIntent } from '../../task/artifact-repair-intent.ts';
12
14
  import { withTaskExecutionLock } from '../../task/task-execution-lock.ts';
13
15
  import { dispatchWorkflowCommand } from '../../task/workflow-dispatch.ts';
14
16
  import { assertSandboxTaskSource } from '../workspace-view.ts';
@@ -37,6 +39,18 @@ function executionResult(result: Record<string, unknown>): SandboxControlExecuti
37
39
  return { exitCode: result.status === 'failed' || result.status === 'refused' ? 1 : 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
38
40
  }
39
41
 
42
+ function replaceCurrentArtifactAtomically(file: string, content: string): void {
43
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
44
+ try {
45
+ fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
46
+ fs.renameSync(temporary, file);
47
+ } finally {
48
+ try { fs.unlinkSync(temporary); } catch (error) {
49
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
50
+ }
51
+ }
52
+ }
53
+
40
54
  /** The broker already authorized this host executor; domain operations own validation. */
41
55
  export async function executeTaskWorkflow(
42
56
  manifest: SandboxControlManifest,
@@ -45,25 +59,6 @@ export async function executeTaskWorkflow(
45
59
  options: TaskWorkflowExecutionOptions = {}
46
60
  ): Promise<SandboxControlExecutionResult> {
47
61
  let publicationCommitted = false;
48
- let recoveryObservation: Readonly<{
49
- family: 'analysis' | 'plan' | 'code' | 'review-analysis' | 'review-plan' | 'review-code';
50
- artifact: string;
51
- wasPassed: boolean;
52
- }> | null = null;
53
- const observeRecoveryPublication = (): void => {
54
- if (!recoveryObservation || recoveryObservation.wasPassed) return;
55
- const intent = readArtifactRecoveryIntent(
56
- manifest.repoRoot,
57
- request.taskId,
58
- recoveryObservation.family,
59
- recoveryObservation.artifact
60
- );
61
- publicationCommitted ||= intent?.state === 'preflight-ready'
62
- || intent?.state === 'preflight-commit-started'
63
- || intent?.state === 'preflight-passed'
64
- || intent?.state === 'passed'
65
- || intent?.state === 'commit-started';
66
- };
67
62
  let faultTriggered = false;
68
63
  const fault = (window: TaskWorkflowFaultWindow): void => {
69
64
  if (faultTriggered || options.faultWindow !== window) return;
@@ -125,47 +120,26 @@ export async function executeTaskWorkflow(
125
120
  if ('operation' in input) {
126
121
  const { family } = input;
127
122
  if (family !== 'analysis' && family !== 'plan' && family !== 'code') throw new Error('ARTIFACT_IDENTITY_INVALID');
128
- const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, recoveryId: input.recoveryId, lockAlreadyHeld: true } as const;
123
+ const local = { taskRef: request.taskId, family, artifact: input.artifact, repoRoot: manifest.repoRoot, lockAlreadyHeld: true } as const;
129
124
  if (input.operation === 'preflight') {
130
- // Preflight only seals a verified generation. It must not publish the
131
- // formal artifact, set publicationStarted, or append finalizer audit.
132
125
  const result = preflightLocalArtifact(local);
133
- recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
134
- observeRecoveryPublication();
135
126
  return response(result);
136
127
  }
137
- recoveryObservation = {
138
- family,
139
- artifact: input.artifact,
140
- wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
141
- };
142
- const prepared = prepareLocalArtifact(local, content, lifecycleRecoveryAttestation ?? undefined);
143
- observeRecoveryPublication();
144
- const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
145
- if (prepared.result.status === 'failed') return executionResult(commitLocalArtifactProvenance(prepared, commitOptions));
146
- result = commitLocalArtifactProvenance(prepared, commitOptions);
128
+ const prepared = prepareLocalArtifact(local, content);
129
+ result = commitLocalArtifactProvenance(prepared);
147
130
  } else {
148
131
  if (input.overrideTicket) throw new Error('TASK_WORKFLOW_OVERRIDE_UNSUPPORTED');
149
132
  const family = `review-${input.stage}` as 'review-analysis' | 'review-plan' | 'review-code';
150
133
  if (request.operation === 'review-preflight') {
151
134
  const result = preflightReviewSummary(input, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
152
- recoveryObservation = { family, artifact: input.artifact, wasPassed: false };
153
- observeRecoveryPublication();
154
135
  return response(result);
155
136
  }
156
- recoveryObservation = {
157
- family,
158
- artifact: input.artifact,
159
- wasPassed: readArtifactRecoveryIntent(manifest.repoRoot, request.taskId, family, input.artifact)?.state === 'passed'
160
- };
161
- const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true, startRecovery: true });
162
- observeRecoveryPublication();
137
+ const prepared = prepareReviewSummaryCandidate(input, content, { repoRoot: manifest.repoRoot, lockAlreadyHeld: true });
163
138
  if (input.dryRun || prepared.result.status === 'planned') return executionResult(prepared.result);
164
- const commitOptions = { afterPublish: () => fault('after-atomic-rename') };
165
- if (prepared.result.status === 'failed') return executionResult(commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions));
166
- result = commitReviewSummaryProvenance(prepared, manifest.repoRoot, commitOptions);
139
+ if (prepared.result.status === 'failed') return executionResult(prepared.result);
140
+ if (prepared.result.changed) replaceCurrentArtifactAtomically(path.join(taskDir, input.artifact), prepared.content);
141
+ result = prepared.result;
167
142
  }
168
- observeRecoveryPublication();
169
143
  publicationCommitted ||= result.changed === true;
170
144
  if (result.status !== 'failed') appendDiagnosticAudit(manifest, 'task-workflow-artifact-finalized', {
171
145
  requestId: request.id, sandboxTaskId: request.taskId, workflowOperation: request.operation,
@@ -175,7 +149,6 @@ export async function executeTaskWorkflow(
175
149
  return response(result);
176
150
  });
177
151
  } catch (error) {
178
- observeRecoveryPublication();
179
152
  const message = error instanceof Error ? error.message : String(error);
180
153
  const code = (error as { code?: string })?.code ?? /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_WORKFLOW_REQUEST_INVALID';
181
154
  return executionResult({ status: 'failed', changed: publicationCommitted ? null : false, error: { code, message } });
@@ -13,7 +13,6 @@ export type ArtifactCommand = Readonly<{
13
13
  family: string;
14
14
  artifact: string;
15
15
  locale?: 'zh-CN' | 'en';
16
- recoveryId?: string;
17
16
  }>;
18
17
 
19
18
  /** One option contract for the CLI and trusted projection executor. */
@@ -26,8 +25,8 @@ export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
26
25
  const allowed = {
27
26
  inspect: ['--family'],
28
27
  init: ['--family', '--artifact', '--locale'],
29
- 'preflight': ['--family', '--artifact', '--recovery-id'],
30
- 'finalize-local': ['--family', '--artifact', '--recovery-id']
28
+ 'preflight': ['--family', '--artifact'],
29
+ 'finalize-local': ['--family', '--artifact']
31
30
  }[operation as ArtifactCommand['operation']];
32
31
  for (let index = 2; index < args.length; index += 1) {
33
32
  const flag = args[index]!;
@@ -43,7 +42,7 @@ export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
43
42
  if (locale !== undefined && locale !== 'zh-CN' && locale !== 'en') throw new Error("option '--locale' must be 'zh-CN' or 'en'");
44
43
  return {
45
44
  taskRef, operation: operation as ArtifactCommand['operation'], family: fields['--family'],
46
- artifact: fields['--artifact'] ?? '', locale, recoveryId: fields['--recovery-id']
45
+ artifact: fields['--artifact'] ?? '', locale
47
46
  };
48
47
  }
49
48
  /** Resolve authoritative task metadata separately from a trusted candidate directory. */
@@ -51,7 +50,7 @@ export function executeArtifactCommand(
51
50
  command: ArtifactCommand,
52
51
  options: Readonly<{ repoRoot?: string; artifactDir?: string }> = {}
53
52
  ): Record<string, unknown> {
54
- const { taskRef, operation, family, artifact, locale, recoveryId } = command;
53
+ const { taskRef, operation, family, artifact, locale } = command;
55
54
  const fail = (code: string, message: string) => ({ status: 'failed', changed: false, error: { code, message } });
56
55
  if (operation === 'inspect') {
57
56
  const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
@@ -82,6 +81,6 @@ export function executeArtifactCommand(
82
81
  if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
83
82
  return fail('ARTIFACT_PAYLOAD_INVALID', "preflight and finalize-local only support 'analysis', 'plan', and 'code'");
84
83
  }
85
- if (operation === 'preflight') return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
86
- return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
84
+ if (operation === 'preflight') return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
85
+ return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
87
86
  }
@@ -5,21 +5,10 @@ import {
5
5
  resolveCodexThread
6
6
  } from '../agent-clients/adapters/codex-lifecycle/app-server.ts';
7
7
  import { createCodexLifecycleStore } from '../agent-clients/adapters/codex-lifecycle/store.ts';
8
- import {
9
- createCodexCapabilityStore,
10
- isCodexCapabilityProvenanceDetail
11
- } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
12
- import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
13
8
  import {
14
9
  computeLifecycleBuildIdentity,
15
- verifyLifecycleBuildIdentity,
16
10
  type LifecycleBuildIdentity,
17
- type LifecycleIdentityWarning
18
11
  } from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
19
- import { verifyCodexSandboxControllerContextWithWarnings as verifySandboxControllerWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
20
- import {
21
- verifyCodexSandboxControllerContextWithWarnings as verifyControllerContextFileWithWarnings
22
- } from '../agent-clients/adapters/codex-lifecycle/controller-context.ts';
23
12
  import type { AgentClientId } from '../agent-clients/types.ts';
24
13
  import { resolveTaskRef } from './resolve-ref.ts';
25
14
  import {
@@ -29,32 +18,20 @@ import {
29
18
  OrchestrationStateError,
30
19
  pauseMatchingOrchestrationDelegation,
31
20
  prepareOrchestrationDelegation,
32
- routeOrchestration,
33
21
  reconcileMatchingOrchestrationDelegation,
34
22
  sealMatchingOrchestrationDelegationWithHostEvidence
35
23
  } from './orchestration.ts';
36
- import { randomUUID, createHash } from 'node:crypto';
37
24
  import type { OrchestrationOptions, OrchestrationResult } from './orchestration.ts';
38
- import {
39
- consumeLifecycleRecoveryAttestation,
40
- issueLifecycleRecoveryAttestation,
41
- validateLifecycleRecoveryAttestation,
42
- type LifecycleRecoveryAttestationV1
43
- } from './control-authority.ts';
44
25
 
45
26
  type LifecycleStore = ReturnType<typeof createCodexLifecycleStore>;
46
- type CapabilityStore = ReturnType<typeof createCodexCapabilityStore>;
47
27
  type CodexBridgeOptions = Readonly<{
48
28
  repoRoot?: string;
49
29
  store?: LifecycleStore;
50
30
  preflight?: typeof preflightCodexLifecycleEvidence;
51
31
  resolveThread?: typeof resolveCodexThread;
52
32
  resolveTerminal?: typeof resolveCodexTerminal;
53
- capabilityStore?: CapabilityStore;
54
33
  buildIdentity?: LifecycleBuildIdentity;
55
34
  orchestrationOptions?: OrchestrationOptions;
56
- controllerBinding?: Readonly<{ instanceDigest: string; controlGeneration: string }>;
57
- deferLifecycleRecoveryConsumption?: boolean;
58
35
  }>;
59
36
 
60
37
  type CodexSpawnIdentity = Readonly<{
@@ -68,35 +45,14 @@ type CodexSpawnIdentity = Readonly<{
68
45
  requestedReasoningEffort?: string;
69
46
  }>;
70
47
 
71
- function buildIdentityDigest(value: LifecycleBuildIdentity): string {
72
- return createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
73
- }
74
-
75
48
  function coreOptions(options: CodexBridgeOptions): OrchestrationOptions {
76
49
  return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
77
50
  }
78
51
 
79
- function bridgeFailure(code: string, message: string, detail?: CodexCapabilityProvenanceDetail): OrchestrationResult {
52
+ function bridgeFailure(code: string, message: string): OrchestrationResult {
80
53
  return {
81
54
  status: 'failed', changed: false, taskId: null, run: null, next: null,
82
- error: { code, message, ...(detail ? { detail } : {}) }
83
- };
84
- }
85
-
86
- function capabilityFailure(error: unknown): NonNullable<OrchestrationResult['error']> {
87
- const detailValue = error instanceof Error
88
- ? (error as Error & { detail?: unknown }).detail
89
- : undefined;
90
- return {
91
- code: error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
92
- ? error.name
93
- : 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
94
- message: error instanceof Error ? error.message : String(error),
95
- ...(error instanceof Error
96
- && error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
97
- && isCodexCapabilityProvenanceDetail(detailValue)
98
- ? { detail: detailValue }
99
- : {})
55
+ error: { code, message }
100
56
  };
101
57
  }
102
58
 
@@ -112,35 +68,12 @@ function requiredStore(options: CodexBridgeOptions): LifecycleStore {
112
68
  return options.store;
113
69
  }
114
70
 
115
- function brokerControllerBinding(): Readonly<{ instanceDigest: string; controlGeneration: string }> | null {
116
- const raw = process.env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING;
117
- if (!raw) return null;
118
- let value: unknown;
119
- try {
120
- value = JSON.parse(raw);
121
- } catch {
122
- throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
123
- }
124
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
125
- throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
126
- }
127
- const binding = value as Record<string, unknown>;
128
- if (Object.keys(binding).sort().join(',') !== 'controlGeneration,instanceDigest'
129
- || typeof binding.instanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.instanceDigest)
130
- || typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0) {
131
- throw new Error('CODEX_SANDBOX_CONTROLLER_BINDING_INVALID');
132
- }
133
- return binding as { instanceDigest: string; controlGeneration: string };
134
- }
135
-
136
71
  async function prepareCodexOrchestrationDelegation(
137
72
  taskRef: string,
138
73
  input: Readonly<{
139
74
  client: AgentClientId;
140
75
  requestedModel?: string;
141
76
  requestedReasoningEffort?: string;
142
- capabilityRef?: string;
143
- lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
144
77
  }>,
145
78
  options: CodexBridgeOptions = {}
146
79
  ): Promise<OrchestrationResult> {
@@ -149,164 +82,11 @@ async function prepareCodexOrchestrationDelegation(
149
82
  const repoRoot = options.repoRoot ?? process.cwd();
150
83
  const resolved = resolveTaskRef(taskRef, { repoRoot });
151
84
  if (!resolved.ok) return bridgeFailure(resolved.code, resolved.message);
152
- const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
153
- const capabilityRef = input.capabilityRef;
154
- if (!capabilityRef) {
155
- return bridgeFailure(
156
- 'ORCHESTRATION_CODEX_CAPABILITY_REQUIRED',
157
- 'Codex prepare requires a current-session capability reference'
158
- );
159
- }
160
- const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
161
- const brokerController = options.controllerBinding ?? brokerControllerBinding();
162
- const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
163
- const contextVerification = contextPath
164
- ? (brokerController
165
- ? verifyControllerContextFileWithWarnings(contextPath, {
166
- repoRoot,
167
- generation: process.env.AGENT_INFRA_CONTROL_GENERATION ?? brokerController.controlGeneration
168
- })
169
- : verifySandboxControllerWithWarnings(contextPath, { repoRoot }))
170
- : null;
171
- const identityWarnings: LifecycleIdentityWarning[] = [...(contextVerification?.warnings ?? [])];
172
- const localController = contextVerification?.context ?? null;
173
- if (localController && brokerController
174
- && (localController.controllerInstanceDigest !== brokerController.instanceDigest
175
- || localController.controlGeneration !== brokerController.controlGeneration)) {
176
- return bridgeFailure(
177
- 'CODEX_SANDBOX_CONTROLLER_BINDING_MISMATCH',
178
- 'broker and local controller bindings do not match'
179
- );
180
- }
181
- const controller = brokerController ?? (localController ? {
182
- instanceDigest: localController.controllerInstanceDigest,
183
- controlGeneration: localController.controlGeneration
184
- } : null);
185
- const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
186
- const attested = capabilityStore.inspectReference(capabilityRef);
187
- const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
188
- if (!capabilityIdentity.ok) {
189
- return bridgeFailure(capabilityIdentity.code!, capabilityIdentity.message!);
190
- }
191
- identityWarnings.push(...capabilityIdentity.warnings);
192
- const lifecycleProvenance = {
193
- ...attested.buildIdentity,
194
- hookDefinitionHash: preflight.hookDefinitionHash,
195
- ...preflight.hookProvenance,
196
- capabilitySessionId: attested.sessionId!,
197
- capabilityTurnId: attested.turnId!,
198
- capabilityToolUseId: attested.toolUseId!,
199
- controllerInstanceDigest: controller?.instanceDigest ?? null,
200
- controlGeneration: controller?.controlGeneration ?? null
201
- };
202
- const capabilityExpected = {
203
- taskId: resolved.taskId,
204
- hookDefinitionHash: preflight.hookDefinitionHash,
205
- buildIdentity,
206
- ...(controller ? { controller: {
207
- instanceDigest: controller.instanceDigest,
208
- controlGeneration: controller.controlGeneration
209
- } } : {})
210
- };
211
- if (input.lifecycleRecoveryAttestation) {
212
- try { validateLifecycleRecoveryAttestation(input.lifecycleRecoveryAttestation); }
213
- catch (error) {
214
- return bridgeFailure(
215
- 'LIFECYCLE_AUTHORITY_ATTESTATION_INVALID',
216
- error instanceof Error ? error.message : String(error)
217
- );
218
- }
219
- }
220
- let lifecycleRecoveryAttestation: LifecycleRecoveryAttestationV1 | null = input.lifecycleRecoveryAttestation ?? null;
221
- const routed = input.capabilityRef && !lifecycleRecoveryAttestation
222
- ? routeOrchestration(taskRef, coreOptions(options))
223
- : null;
224
- if (routed && !routed.next) return routed;
225
- if (routed?.next && ['analysis', 'plan', 'code'].includes(routed.next.stage)) {
226
- const recoveryRequest = {
227
- version: 1 as const,
228
- requestId: randomUUID(),
229
- operationId: randomUUID(),
230
- phase: 'orchestration.prepare' as const,
231
- taskId: resolved.taskId,
232
- family: routed.next.stage === 'analysis' ? 'analysis' as const
233
- : routed.next.stage === 'plan' ? 'plan' as const : 'code' as const,
234
- artifact: routed.next.artifact,
235
- round: routed.next.round,
236
- lifecycleRequestId: randomUUID(),
237
- authorityRef: capabilityRef,
238
- expectedControlGeneration: controller?.controlGeneration ?? '',
239
- expectedControllerInstanceDigest: controller?.instanceDigest ?? '0'.repeat(64),
240
- expectedBuildIdentityDigest: buildIdentityDigest(buildIdentity),
241
- expectedHookDefinitionHash: preflight.hookDefinitionHash
242
- };
243
- const authority = issueLifecycleRecoveryAttestation(recoveryRequest, {
244
- capabilityStore,
245
- ...(controller ? { controllerBinding: controller } : {}),
246
- buildIdentity
247
- });
248
- if (authority.status === 'rejected' || !authority.attestation) {
249
- return bridgeFailure(
250
- authority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED',
251
- authority.error?.message ?? 'lifecycle authority was rejected'
252
- );
253
- }
254
- lifecycleRecoveryAttestation = authority.attestation;
255
- }
256
- const prepared = prepareOrchestrationDelegation(taskRef, {
257
- ...input,
258
- lifecycleProvenance,
259
- lifecycleRecoveryAttestation
260
- }, {
261
- ...coreOptions(options),
262
- validateLifecycleCapability: () => {
263
- try {
264
- const validated = capabilityStore.validateReference(capabilityRef, capabilityExpected);
265
- if (validated.sessionId !== attested.sessionId
266
- || validated.turnId !== attested.turnId
267
- || validated.toolUseId !== attested.toolUseId) {
268
- return {
269
- code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
270
- message: 'capability identity changed during validation'
271
- };
272
- }
273
- return null;
274
- } catch (error) {
275
- return capabilityFailure(error);
276
- }
277
- },
278
- consumeLifecycleCapability: () => null
279
- });
280
- if (lifecycleRecoveryAttestation && !options.deferLifecycleRecoveryConsumption && prepared.status !== 'failed') {
281
- try {
282
- consumeLifecycleRecoveryAttestation(lifecycleRecoveryAttestation);
283
- } catch (error) {
284
- return bridgeFailure(
285
- error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED',
286
- error instanceof Error ? error.message : String(error)
287
- );
288
- }
289
- }
290
- return identityWarnings.length > 0 && prepared.status !== 'failed'
291
- ? { ...prepared, warnings: Object.freeze(identityWarnings) }
292
- : prepared;
85
+ await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
86
+ return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
293
87
  } catch (error) {
294
88
  if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
295
- const detailValue = error instanceof Error
296
- ? (error as Error & { detail?: unknown }).detail
297
- : undefined;
298
- const detail = error instanceof Error
299
- && error.name === 'CODEX_CAPABILITY_PROVENANCE_MISMATCH'
300
- && isCodexCapabilityProvenanceDetail(detailValue)
301
- ? detailValue
302
- : undefined;
303
- return bridgeFailure(
304
- error instanceof Error && error.name.startsWith('CODEX_CAPABILITY_')
305
- ? error.name
306
- : 'ORCHESTRATION_CLIENT_PREFLIGHT_FAILED',
307
- error instanceof Error ? error.message : String(error),
308
- detail
309
- );
89
+ return bridgeFailure('ORCHESTRATION_CLIENT_PREFLIGHT_FAILED', error instanceof Error ? error.message : String(error));
310
90
  }
311
91
  }
312
92
 
@@ -335,16 +115,6 @@ async function activateCodexOrchestrationDelegation(
335
115
  toolUseId: evidence.spawnToolUseId
336
116
  });
337
117
  const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
338
- const contextPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
339
- const contextVerification = contextPath
340
- ? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
341
- : null;
342
- const controller = options.controllerBinding
343
- ?? brokerControllerBinding()
344
- ?? (contextVerification?.context ? {
345
- instanceDigest: contextVerification.context.controllerInstanceDigest,
346
- controlGeneration: contextVerification.context.controlGeneration
347
- } : null);
348
118
  const activated = activateMatchingOrchestrationDelegation('codex', {
349
119
  nativeAgent: evidence.nativeAgent,
350
120
  childId: evidence.childThreadId,
@@ -366,14 +136,11 @@ async function activateCodexOrchestrationDelegation(
366
136
  capabilityTurnId: evidence.parentTurnId,
367
137
  spawnToolUseId: evidence.spawnToolUseId,
368
138
  spawnObservedAt: record.spawnObservedAt ?? undefined,
369
- controllerInstanceDigest: controller?.instanceDigest ?? null,
370
- controlGeneration: controller?.controlGeneration ?? null
139
+ controllerInstanceDigest: null,
140
+ controlGeneration: null
371
141
  }
372
142
  }, coreOptions(options));
373
- const warnings = contextVerification?.warnings ?? [];
374
- return warnings.length > 0 && activated.status !== 'failed'
375
- ? { ...activated, warnings: Object.freeze(warnings) }
376
- : activated;
143
+ return activated;
377
144
  } catch (error) {
378
145
  if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
379
146
  return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', error instanceof Error ? error.message : String(error), options);
@@ -32,8 +32,6 @@ type CommitOperationInput = Readonly<{
32
32
  agent?: string;
33
33
  mode?: CommitExecutionMode;
34
34
  round?: number;
35
- /** Permit one retry to rebase a prepared intent's expected tree while HEAD and intent identity remain unchanged. */
36
- recoverPreparedIntent?: boolean;
37
35
  delivery?: Readonly<{
38
36
  mode: 'local' | 'push';
39
37
  remote: string;
@@ -282,20 +280,6 @@ function checkpointTimestamp(): string {
282
280
  return new Date().toISOString();
283
281
  }
284
282
 
285
- function samePreparedCheckpointIdentity(
286
- pending: NonNullable<ReturnType<typeof readCheckpointIntent>>,
287
- identity: Parameters<typeof checkpointIntentDigest>[0]
288
- ): boolean {
289
- return pending.state === 'prepared'
290
- && pending.taskId === identity.taskId
291
- && pending.branch === identity.branch
292
- && pending.mode === identity.mode
293
- && pending.expectedHead === identity.expectedHead
294
- && pending.message === identity.message
295
- && pending.round === identity.round
296
- && JSON.stringify(pending.paths) === JSON.stringify(identity.paths);
297
- }
298
-
299
283
  function executeUnlocked(input: CommitOperationInput, task: BoundTask | null, mode: CommitExecutionMode): CommitOperationResult {
300
284
  const inspected = inspectGitWorkflow(input.cwd);
301
285
  if (!inspected.snapshot) return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
@@ -330,15 +314,11 @@ function executeUnlocked(input: CommitOperationInput, task: BoundTask | null, mo
330
314
  };
331
315
  }
332
316
  if (pendingIntent && !sameCheckpointIntent(pendingIntent, identity)) {
333
- if (!input.recoverPreparedIntent || !samePreparedCheckpointIdentity(pendingIntent, identity)) return {
334
- status: 'failed' as const, changed: false, snapshot: inspected.snapshot, operations: [],
335
- error: { code: 'COMMIT_INTENT_CONFLICT', message: 'A different checkpoint intent is still pending for this task' }
336
- };
317
+ const timestamp = checkpointTimestamp();
337
318
  pendingIntent = {
338
- ...pendingIntent,
339
- expectedTree: identity.expectedTree,
319
+ version: 1, ...identity,
340
320
  digest: checkpointIntentDigest(identity),
341
- updatedAt: checkpointTimestamp()
321
+ state: 'prepared', committedHead: null, createdAt: timestamp, updatedAt: timestamp
342
322
  };
343
323
  try { writeCheckpointIntent(task!.repoRoot, pendingIntent); }
344
324
  catch (error) {
@@ -789,17 +789,11 @@ function orchestration(
789
789
  client: input.client as AgentClientId,
790
790
  requestedModel: input.requestedModel as string | undefined,
791
791
  requestedReasoningEffort: input.requestedReasoningEffort as string | undefined,
792
- ...(input.capabilityRef === undefined ? {} : { capabilityRef: input.capabilityRef as string }),
793
- ...(context.lifecycleRecoveryAttestation === undefined
794
- ? {}
795
- : { lifecycleRecoveryAttestation: context.lifecycleRecoveryAttestation })
796
792
  };
797
793
  if (prepareInput.client === 'codex') {
798
794
  return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
799
795
  repoRoot: context.repoRoot,
800
796
  orchestrationOptions: options,
801
- deferLifecycleRecoveryConsumption: context.source === 'sandbox-executor',
802
- ...(context.controllerBinding ? { controllerBinding: context.controllerBinding } : {})
803
797
  });
804
798
  }
805
799
  return prepareOrchestrationDelegation(operation.taskRef, prepareInput, options);
@@ -912,7 +906,7 @@ const FINALIZATION_FLAGS = new Set(['--agent']);
912
906
  const ORCHESTRATION_FLAGS = new Set([
913
907
  '--agent', '--max-steps', '--executor-model', '--executor-reasoning-effort', '--reviewer-model',
914
908
  '--reviewer-reasoning-effort', '--client', '--requested-model', '--requested-reasoning-effort',
915
- '--capability-ref', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
909
+ '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
916
910
  '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
917
911
  '--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
918
912
  '--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
@@ -1049,7 +1043,6 @@ export function parseTaskControlOperation(
1049
1043
  if (parsedIntent === 'prepare') {
1050
1044
  input.requestedModel = value(values, '--requested-model');
1051
1045
  input.requestedReasoningEffort = value(values, '--requested-reasoning-effort');
1052
- input.capabilityRef = value(values, '--capability-ref');
1053
1046
  }
1054
1047
  if (parsedIntent === 'await-activation') {
1055
1048
  required(values, ['--stage', '--round', '--artifact', '--role']);