@fitlab-ai/agent-infra 0.11.11 → 0.11.13

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 (154) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +7 -1
  3. package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
  9. package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
  12. package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
  14. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
  15. package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
  16. package/dist/lib/agent-clients/adapters/codex.js +40 -1
  17. package/dist/lib/agent-clients/adapters/index.js +2 -10
  18. package/dist/lib/agent-clients/tokens.js +3 -2
  19. package/dist/lib/internal/agent-client.js +132 -72
  20. package/dist/lib/internal/cli-route-inventory.js +2 -2
  21. package/dist/lib/internal/sandbox-control.js +1 -81
  22. package/dist/lib/internal/task-finalization.js +26 -19
  23. package/dist/lib/internal/task-lifecycle.js +1 -1
  24. package/dist/lib/internal/task-operation-registry.js +1 -1
  25. package/dist/lib/internal/task-validate.js +1 -1
  26. package/dist/lib/platform/github-client.js +33 -3
  27. package/dist/lib/platform/issues.js +18 -6
  28. package/dist/lib/runtime/agent-runtime.js +6 -29
  29. package/dist/lib/sandbox/commands/control-status.js +1 -0
  30. package/dist/lib/sandbox/commands/create.js +7 -2
  31. package/dist/lib/sandbox/commands/ls.js +4 -5
  32. package/dist/lib/sandbox/commands/rm.js +1 -1
  33. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  34. package/dist/lib/sandbox/control/client.js +14 -201
  35. package/dist/lib/sandbox/control/executor.js +12 -125
  36. package/dist/lib/sandbox/control/lifecycle.js +66 -4
  37. package/dist/lib/sandbox/control/protocol.js +15 -54
  38. package/dist/lib/sandbox/control/server.js +216 -281
  39. package/dist/lib/sandbox/control/task-view.js +0 -13
  40. package/dist/lib/sandbox/mounts.js +9 -1
  41. package/dist/lib/sandbox/recovery.js +31 -10
  42. package/dist/lib/sandbox/removal.js +28 -5
  43. package/dist/lib/sandbox/workspace-identity.js +18 -6
  44. package/dist/lib/sandbox/workspace-view.js +56 -19
  45. package/dist/lib/task/commands/ls.js +3 -3
  46. package/dist/lib/task/control-authority.js +56 -264
  47. package/dist/lib/task/control-recovery.js +2 -3
  48. package/dist/lib/task/create.js +2 -2
  49. package/dist/lib/task/delegation-receipts.js +74 -161
  50. package/dist/lib/task/events.js +88 -44
  51. package/dist/lib/task/finalization.js +35 -6
  52. package/dist/lib/task/lifecycle.js +274 -115
  53. package/dist/lib/task/orchestration.js +201 -194
  54. package/dist/lib/task/runtime-paths.js +10 -0
  55. package/dist/lib/task/short-id.js +37 -27
  56. package/dist/lib/task/verification-engine.js +32 -26
  57. package/dist/lib/task/workspace-snapshot.js +1 -1
  58. package/dist/lifecycle-build-manifest.json +3 -3
  59. package/lib/agent-clients/adapter.ts +39 -0
  60. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  61. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  62. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  63. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  64. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  65. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  66. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  67. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  68. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  69. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  70. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  71. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  72. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  73. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
  74. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  75. package/lib/agent-clients/adapters/codex.ts +44 -1
  76. package/lib/agent-clients/adapters/index.ts +7 -20
  77. package/lib/agent-clients/tokens.ts +4 -3
  78. package/lib/internal/agent-client.ts +142 -73
  79. package/lib/internal/cli-route-inventory.ts +2 -2
  80. package/lib/internal/sandbox-control.ts +0 -97
  81. package/lib/internal/task-finalization.ts +30 -18
  82. package/lib/internal/task-lifecycle.ts +1 -1
  83. package/lib/internal/task-operation-registry.ts +1 -1
  84. package/lib/internal/task-validate.ts +1 -1
  85. package/lib/platform/github-client.ts +34 -3
  86. package/lib/platform/issues.ts +18 -6
  87. package/lib/runtime/agent-runtime.ts +6 -41
  88. package/lib/sandbox/commands/control-status.ts +1 -0
  89. package/lib/sandbox/commands/create.ts +7 -1
  90. package/lib/sandbox/commands/ls.ts +4 -5
  91. package/lib/sandbox/commands/rm.ts +1 -1
  92. package/lib/sandbox/control/agent-operation.ts +49 -0
  93. package/lib/sandbox/control/client.ts +20 -254
  94. package/lib/sandbox/control/executor.ts +13 -136
  95. package/lib/sandbox/control/lifecycle.ts +67 -4
  96. package/lib/sandbox/control/protocol.ts +22 -66
  97. package/lib/sandbox/control/server.ts +21 -95
  98. package/lib/sandbox/control/task-view.ts +0 -19
  99. package/lib/sandbox/mounts.ts +10 -1
  100. package/lib/sandbox/recovery.ts +38 -17
  101. package/lib/sandbox/removal.ts +33 -7
  102. package/lib/sandbox/workspace-identity.ts +16 -6
  103. package/lib/sandbox/workspace-view.ts +50 -18
  104. package/lib/task/commands/ls.ts +3 -3
  105. package/lib/task/control-authority.ts +66 -378
  106. package/lib/task/control-recovery.ts +3 -4
  107. package/lib/task/create.ts +3 -3
  108. package/lib/task/delegation-receipts.ts +87 -231
  109. package/lib/task/events.ts +100 -42
  110. package/lib/task/finalization.ts +37 -7
  111. package/lib/task/lifecycle.ts +257 -109
  112. package/lib/task/orchestration.ts +260 -237
  113. package/lib/task/runtime-paths.ts +12 -0
  114. package/lib/task/short-id.ts +33 -40
  115. package/lib/task/verification-engine.ts +30 -25
  116. package/lib/task/workspace-snapshot.ts +1 -1
  117. package/package.json +1 -1
  118. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  119. package/templates/.agents/rules/README.en.md +2 -1
  120. package/templates/.agents/rules/README.zh-CN.md +2 -1
  121. package/templates/.agents/rules/next-step-output.en.md +6 -19
  122. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  123. package/templates/.agents/rules/strategy-pattern.en.md +25 -0
  124. package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
  125. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  126. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  127. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  130. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  131. package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
  132. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
  133. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
  134. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
  135. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
  136. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
  137. package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
  138. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
  139. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  142. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  143. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  144. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  145. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  149. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  150. package/dist/lib/internal/codex-lifecycle.js +0 -385
  151. package/dist/lib/task/codex-orchestration.js +0 -206
  152. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  153. package/lib/internal/codex-lifecycle.ts +0 -390
  154. package/lib/task/codex-orchestration.ts +0 -276
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
 
3
- import { createCodexLifecycleStore } from './store.ts';
4
- import type { StoredCodexLifecycle } from './store.ts';
3
+ import { createCodexLifecycleStore, readCodexLifecycleActivationEvidence } from './store.ts';
4
+ import type { CodexLifecycleActivationEvidence, StoredCodexLifecycle } from './store.ts';
5
5
  import { managedDelegationRole } from '../../../task/delegation-receipts.ts';
6
6
  import type { DelegationReceipt } from '../../../task/delegation-receipts.ts';
7
7
  import { normalizeAgentToken } from '../../tokens.ts';
@@ -10,7 +10,6 @@ import type { ActivityLogSection, StepRow } from '../../../task/activity-log.ts'
10
10
  import { captureTaskWriteMetadata, writeTask } from '../../../task/write.ts';
11
11
  import { parseTypedTaskFrontmatter } from '../../../task/frontmatter.ts';
12
12
  import { resolveTaskRef } from '../../../task/resolve-ref.ts';
13
- import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.ts';
14
13
  import {
15
14
  recoverActivatedOrchestrationDelegationUnderLock,
16
15
  readRun
@@ -93,10 +92,12 @@ function rowForReceipt(rows: readonly StepRow[], receipt: DelegationReceipt): St
93
92
 
94
93
  function readLifecycleStore(
95
94
  options: LifecycleRecoveryOptions,
96
- repoRoot: string
95
+ repoRoot: string,
96
+ taskId: string
97
97
  ): ReturnType<typeof createCodexLifecycleStore> {
98
98
  return options.lifecycleStore ?? createCodexLifecycleStore({
99
- root: resolveAgentRuntimeStoreRoot({ repoRoot, store: 'lifecycle' }),
99
+ repoRoot,
100
+ taskId,
100
101
  cliVersion: 'recovery',
101
102
  now: options.now
102
103
  });
@@ -119,39 +120,10 @@ function validateStopRecord(
119
120
  record: StoredCodexLifecycle,
120
121
  receipt: DelegationReceipt
121
122
  ): { ok: true } | { ok: false; code: string; message: string } {
122
- const start = record.state.startEvidence;
123
- const stop = record.state.stopEvidence;
124
- const provenance = receipt.lifecycleProvenance;
125
- const host = receipt.hostEvidence;
126
- if (
127
- receipt.client !== 'codex'
128
- || !provenance
129
- || host?.kind !== 'codex-lifecycle-v2'
130
- || !start
131
- || !stop
132
- || record.state.status !== 'stop-ready'
133
- || stop.terminalStatus !== 'completed'
134
- || stop.hookStopObserved !== true
135
- || start.childThreadId !== receipt.childId
136
- || start.parentThreadId !== receipt.parentId
137
- || managedDelegationRole(start.nativeAgent) !== receipt.role
138
- || start.hookDefinitionHash !== provenance.hookDefinitionHash
139
- || host.hookDefinitionHash !== provenance.hookDefinitionHash
140
- || host.capabilitySessionId !== provenance.capabilitySessionId
141
- || host.capabilityTurnId !== provenance.capabilityTurnId
142
- || host.capabilityToolUseId !== provenance.capabilityToolUseId
143
- || host.spawnToolUseId !== start.spawnToolUseId
144
- || !host.spawnObservedAt
145
- || host.spawnObservedAt !== record.spawnObservedAt
146
- || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
147
- || host.controlGeneration !== provenance.controlGeneration
148
- || typeof host.controllerInstanceDigest !== 'string'
149
- || typeof host.controlGeneration !== 'string'
150
- || host.startRevision < 1
151
- || record.revision <= host.startRevision
152
- || record.state.child?.childThreadId !== receipt.childId
153
- || record.state.stop?.childThreadId !== receipt.childId
154
- ) {
123
+ if (!stopRecordMatchesReceipt(record, receipt)) {
124
+ return { ok: false, code: 'RECOVERY_TASK_BINDING_MISMATCH', message: 'lifecycle evidence belongs to a different task receipt' };
125
+ }
126
+ if (!stopEvidenceMatchesReceipt(record, receipt)) {
155
127
  return { ok: false, code: 'RECOVERY_STOP_EVIDENCE_INVALID', message: 'Codex stop evidence does not match the activated receipt' };
156
128
  }
157
129
  if (record.consumer !== null && record.consumer !== receipt.id) {
@@ -160,6 +132,104 @@ function validateStopRecord(
160
132
  return { ok: true };
161
133
  }
162
134
 
135
+ function stopRecordMatchesReceipt(record: StoredCodexLifecycle, receipt: DelegationReceipt): boolean {
136
+ return record.taskBinding?.runId === receipt.runId
137
+ && record.taskBinding.receiptId === receipt.id;
138
+ }
139
+
140
+ function stopEvidenceMatchesReceipt(record: StoredCodexLifecycle, receipt: DelegationReceipt): boolean {
141
+ const start = record.state.startEvidence;
142
+ const stop = record.state.stopEvidence;
143
+ const host = readCodexLifecycleActivationEvidence(receipt);
144
+ if (!stopRecordHasRequiredEvidence(record, receipt, start, stop, host)) return false;
145
+ return spawnMatchesReceipt(record, receipt, host!, start!)
146
+ && childMatchesReceipt(record, receipt, start!, stop!)
147
+ && hostEvidenceMatches(record, receipt, host!, start!);
148
+ }
149
+
150
+ function stopRecordHasRequiredEvidence(
151
+ record: StoredCodexLifecycle,
152
+ receipt: DelegationReceipt,
153
+ start: StoredCodexLifecycle['state']['startEvidence'],
154
+ stop: StoredCodexLifecycle['state']['stopEvidence'],
155
+ host: CodexLifecycleActivationEvidence | null
156
+ ): boolean {
157
+ return receipt.client === 'codex'
158
+ && host?.kind === 'codex-lifecycle-v2'
159
+ && Boolean(start && stop && record.state.spawn && record.state.child && record.state.stop)
160
+ && record.state.status === 'stop-ready'
161
+ && stop?.terminalStatus === 'completed'
162
+ && stop.hookStopObserved === true;
163
+ }
164
+
165
+ function spawnMatchesReceipt(
166
+ record: StoredCodexLifecycle,
167
+ receipt: DelegationReceipt,
168
+ host: CodexLifecycleActivationEvidence,
169
+ start: NonNullable<StoredCodexLifecycle['state']['startEvidence']>
170
+ ): boolean {
171
+ const spawn = record.state.spawn;
172
+ return Boolean(spawn)
173
+ && spawn!.sessionId === start.parentThreadId
174
+ && spawn!.turnId === host.capabilityTurnId
175
+ && spawn!.toolUseId === host.spawnToolUseId
176
+ && spawn!.nativeAgent === start.nativeAgent
177
+ && spawn!.hookDefinitionHash === host.hookDefinitionHash
178
+ && spawn!.requestedModel === receipt.requestedModel
179
+ && spawn!.requestedReasoningEffort === receipt.requestedReasoningEffort
180
+ && spawn!.taskBinding?.runId === receipt.runId
181
+ && spawn!.taskBinding.receiptId === receipt.id;
182
+ }
183
+
184
+ function childMatchesReceipt(
185
+ record: StoredCodexLifecycle,
186
+ receipt: DelegationReceipt,
187
+ start: NonNullable<StoredCodexLifecycle['state']['startEvidence']>,
188
+ stop: NonNullable<StoredCodexLifecycle['state']['stopEvidence']>
189
+ ): boolean {
190
+ const child = record.state.child;
191
+ const stopState = record.state.stop;
192
+ return Boolean(child && stopState)
193
+ && child!.sessionId === start.parentThreadId
194
+ && stopState!.turnId === child!.turnId
195
+ && (stopState!.source === 'parent-rollout'
196
+ ? stop.turnId === record.state.terminal?.turnId
197
+ : child!.turnId === stop.turnId)
198
+ && child!.childThreadId === receipt.childId
199
+ && child!.nativeAgent === start.nativeAgent
200
+ && stopState!.childThreadId === receipt.childId
201
+ && stopState!.nativeAgent === start.nativeAgent
202
+ && start.childThreadId === receipt.childId
203
+ && start.parentThreadId === receipt.parentId
204
+ && managedDelegationRole(start.nativeAgent) === receipt.role;
205
+ }
206
+
207
+ function hostEvidenceMatches(
208
+ record: StoredCodexLifecycle,
209
+ receipt: DelegationReceipt,
210
+ host: CodexLifecycleActivationEvidence,
211
+ start: NonNullable<StoredCodexLifecycle['state']['startEvidence']>
212
+ ): boolean {
213
+ return start.hookDefinitionHash === host.hookDefinitionHash
214
+ && host.capabilitySessionId === start.parentThreadId
215
+ && host.capabilityTurnId === record.state.spawn?.turnId
216
+ && host.spawnToolUseId === start.spawnToolUseId
217
+ && Boolean(host.spawnObservedAt)
218
+ && host.spawnObservedAt === record.spawnObservedAt
219
+ && typeof host.controllerInstanceDigest === 'string'
220
+ && typeof host.controlGeneration === 'string'
221
+ && hostRevisionMatches(record, receipt, host);
222
+ }
223
+
224
+ function hostRevisionMatches(record: StoredCodexLifecycle, receipt: DelegationReceipt, host: CodexLifecycleActivationEvidence): boolean {
225
+ return typeof host.controllerInstanceDigest === 'string'
226
+ && typeof host.controlGeneration === 'string'
227
+ && host.startRevision >= 1
228
+ && record.revision > host.startRevision
229
+ && record.state.child?.childThreadId === receipt.childId
230
+ && record.state.stop?.childThreadId === receipt.childId;
231
+ }
232
+
163
233
  function abortedCandidates(run: OrchestrationRun, rows: readonly StepRow[]): RecoveryCandidate[] {
164
234
  const candidates = run.receipts.flatMap((receipt) => {
165
235
  if (receipt.status !== 'aborted' || receipt.activatedAt === null || receipt.client !== 'codex') return [];
@@ -228,62 +298,117 @@ function recoverStartedLifecycleUnderLock(
228
298
 
229
299
  const rows = openRows(section);
230
300
  const pending = run.pendingDelegation;
231
- if (!pending) {
232
- const candidates = abortedCandidates(run, rows);
233
- if (candidates.length === 0) return result(request, 'no-op', { taskId });
234
- if (candidates.length > 1) {
235
- return failure(request, 'conflict', 'RECOVERY_CANDIDATE_AMBIGUOUS', 'more than one aborted delegation has an open lifecycle row', { taskId });
236
- }
237
- return appendRecoveryLog(request, taskId, section, candidates[0]!, resolved.repoRoot, options.writeTask ?? writeTask);
301
+ if (!pending) return recoverAbortedLifecycle(request, taskId, section, resolved.repoRoot, run, rows, options);
302
+ return recoverActiveLifecycle(request, taskId, section, resolved.repoRoot, run, rows, pending, options);
303
+ }
304
+
305
+ function recoverAbortedLifecycle(
306
+ request: LifecycleRecoveryRequest,
307
+ taskId: string,
308
+ section: ActivityLogSection,
309
+ repoRoot: string,
310
+ run: OrchestrationRun,
311
+ rows: readonly StepRow[],
312
+ options: LifecycleRecoveryOptions
313
+ ): LifecycleRecoveryResult {
314
+ const candidates = abortedCandidates(run, rows);
315
+ if (candidates.length === 0) return result(request, 'no-op', { taskId });
316
+ if (candidates.length > 1) {
317
+ return failure(request, 'conflict', 'RECOVERY_CANDIDATE_AMBIGUOUS', 'more than one aborted delegation has an open lifecycle row', { taskId });
238
318
  }
319
+ return appendRecoveryLog(request, taskId, section, candidates[0]!, repoRoot, options.writeTask ?? writeTask);
320
+ }
321
+
322
+ function recoverActiveLifecycle(
323
+ request: LifecycleRecoveryRequest,
324
+ taskId: string,
325
+ section: ActivityLogSection,
326
+ repoRoot: string,
327
+ run: OrchestrationRun,
328
+ rows: readonly StepRow[],
329
+ pending: DelegationReceipt,
330
+ options: LifecycleRecoveryOptions
331
+ ): LifecycleRecoveryResult {
239
332
  if (pending.status !== 'activated') return result(request, 'no-op', { taskId });
240
333
  if (pending.client !== 'codex' || run.status !== 'running' || run.pause !== null) {
241
334
  return failure(request, 'conflict', 'RECOVERY_ORCHESTRATION_INVALID', 'activated recovery requires one running Codex delegation', {
242
335
  taskId, receiptId: pending.id, childId: pending.childId
243
336
  });
244
337
  }
338
+ const rowResult = matchingActiveRecoveryRow(request, taskId, rows, pending);
339
+ if ('result' in rowResult) return rowResult.result;
340
+ const evidenceResult = consumeRecoveryEvidence(request, taskId, repoRoot, pending, options);
341
+ if ('result' in evidenceResult) return evidenceResult.result;
342
+ return finishActiveRecovery(request, taskId, section, repoRoot, pending, rowResult.row, options);
343
+ }
344
+
345
+ function matchingActiveRecoveryRow(
346
+ request: LifecycleRecoveryRequest,
347
+ taskId: string,
348
+ rows: readonly StepRow[],
349
+ pending: DelegationReceipt
350
+ ): { row: StepRow } | { result: LifecycleRecoveryResult } {
245
351
  const matchingRows = rowForReceipt(rows, pending);
246
352
  if (matchingRows.length !== 1 || normalizeAgentToken(matchingRows[0]!.agent) !== 'codex' || matchingRows[0]!.note !== 'started') {
247
- return failure(request, 'conflict', 'RECOVERY_LOG_CONFLICT', 'activated delegation does not have one matching open lifecycle row', {
353
+ return { result: failure(request, 'conflict', 'RECOVERY_LOG_CONFLICT', 'activated delegation does not have one matching open lifecycle row', {
248
354
  taskId, receiptId: pending.id, childId: pending.childId
249
- });
355
+ }) };
250
356
  }
251
357
  if (!pending.childId) {
252
- return failure(request, 'conflict', 'RECOVERY_DELEGATION_INVALID', 'activated delegation has no child identity', { taskId, receiptId: pending.id });
358
+ return { result: failure(request, 'conflict', 'RECOVERY_DELEGATION_INVALID', 'activated delegation has no child identity', { taskId, receiptId: pending.id }) };
253
359
  }
254
- const store = readLifecycleStore(options, resolved.repoRoot);
255
- const stored = readStoredEvidence(store, pending.childId);
256
- if ('error' in stored) return failure(request, 'owner-unknown', 'RECOVERY_STORE_UNKNOWN', stored.error.message, { taskId, receiptId: pending.id, childId: pending.childId });
257
- if ('missing' in stored) return failure(request, 'owner-unknown', 'RECOVERY_STOP_EVIDENCE_MISSING', 'matching Codex lifecycle stop evidence is missing', { taskId, receiptId: pending.id, childId: pending.childId });
258
- const evidence = validateStopRecord(stored, pending);
259
- if (!evidence.ok) return failure(request, evidence.code === 'RECOVERY_CONSUMER_CONFLICT' ? 'conflict' : 'owner-unknown', evidence.code, evidence.message, { taskId, receiptId: pending.id, childId: pending.childId });
360
+ return { row: matchingRows[0]! };
361
+ }
260
362
 
363
+ function consumeRecoveryEvidence(
364
+ request: LifecycleRecoveryRequest,
365
+ taskId: string,
366
+ repoRoot: string,
367
+ pending: DelegationReceipt,
368
+ options: LifecycleRecoveryOptions
369
+ ): { consumed: StoredCodexLifecycle } | { result: LifecycleRecoveryResult } {
370
+ const store = readLifecycleStore(options, repoRoot, taskId);
371
+ const stored = readStoredEvidence(store, pending.childId!);
372
+ if ('error' in stored) return { result: failure(request, 'owner-unknown', 'RECOVERY_STORE_UNKNOWN', stored.error.message, { taskId, receiptId: pending.id, childId: pending.childId }) };
373
+ if ('missing' in stored) return { result: failure(request, 'owner-unknown', 'RECOVERY_STOP_EVIDENCE_MISSING', 'matching Codex lifecycle stop evidence is missing', { taskId, receiptId: pending.id, childId: pending.childId }) };
374
+ const evidence = validateStopRecord(stored, pending);
375
+ if (!evidence.ok) return { result: failure(request, evidence.code === 'RECOVERY_CONSUMER_CONFLICT' ? 'conflict' : 'owner-unknown', evidence.code, evidence.message, { taskId, receiptId: pending.id, childId: pending.childId }) };
261
376
  let consumed = stored;
262
377
  if (stored.consumer === null) {
263
378
  try {
264
- consumed = store.consume(pending.childId, pending.id, pending.hostEvidence?.hookDefinitionHash);
379
+ consumed = store.consumeWithinTaskLock(pending.childId!, pending.id, stored.state.startEvidence?.hookDefinitionHash, {
380
+ taskId: pending.taskId, runId: pending.runId, receiptId: pending.id
381
+ });
265
382
  } catch (error) {
266
- return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_CONSUME_FAILED', error instanceof Error ? error.message : String(error), { taskId, receiptId: pending.id, childId: pending.childId });
383
+ return { result: failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_CONSUME_FAILED', error instanceof Error ? error.message : String(error), { taskId, receiptId: pending.id, childId: pending.childId }) };
267
384
  }
268
385
  }
269
- if (!consumed.consumedAt) {
270
- return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_INVALID', 'consumed stop evidence has no timestamp', { taskId, receiptId: pending.id, childId: pending.childId });
271
- }
386
+ return consumed.consumedAt
387
+ ? { consumed }
388
+ : { result: failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_INVALID', 'consumed stop evidence has no timestamp', { taskId, receiptId: pending.id, childId: pending.childId }) };
389
+ }
390
+
391
+ function finishActiveRecovery(
392
+ request: LifecycleRecoveryRequest,
393
+ taskId: string,
394
+ section: ActivityLogSection,
395
+ repoRoot: string,
396
+ pending: DelegationReceipt,
397
+ row: StepRow,
398
+ options: LifecycleRecoveryOptions
399
+ ): LifecycleRecoveryResult {
272
400
  const recovered = recoverActivatedOrchestrationDelegationUnderLock(taskId, {
273
401
  receiptId: pending.id,
274
402
  stage: pending.stage,
275
403
  round: pending.round,
276
404
  artifact: pending.artifact,
277
405
  startedAgent: request.agent,
278
- childId: pending.childId,
279
- stopRevision: consumed.revision,
280
- consumer: pending.id,
281
- consumedAt: consumed.consumedAt
282
- }, { ...options.orchestration, repoRoot: resolved.repoRoot, now: options.now });
406
+ childId: pending.childId!
407
+ }, { ...options.orchestration, repoRoot, now: options.now });
283
408
  if (recovered.status === 'failed' || !recovered.run) {
284
409
  return failure(request, 'owner-unknown', recovered.error?.code ?? 'RECOVERY_ORCHESTRATION_FAILED', recovered.error?.message ?? 'orchestration recovery failed', { taskId, receiptId: pending.id, childId: pending.childId });
285
410
  }
286
- return appendRecoveryLog(request, taskId, section, { receipt: recovered.run.receipts.at(-1)!, row: matchingRows[0]! }, resolved.repoRoot, options.writeTask ?? writeTask);
411
+ return appendRecoveryLog(request, taskId, section, { receipt: recovered.run.receipts.at(-1)!, row }, repoRoot, options.writeTask ?? writeTask);
287
412
  }
288
413
 
289
414
  function recoverStartedLifecycleFromAdapter(
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  runCodexSandboxController,
3
3
  verifyCodexSandboxControllerContextWithWarnings
4
- } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.ts';
5
- import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
4
+ } from './sandbox-controller.ts';
5
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from '../../../internal/cli-route-inventory.ts';
6
6
 
7
7
  const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
8
8
 
@@ -10,7 +10,7 @@ import {
10
10
  requestCodexControllerClose,
11
11
  requestCodexControllerOpen,
12
12
  requestCodexControllerVerify
13
- } from '../../../sandbox/control/client.ts';
13
+ } from './controller-client.ts';
14
14
  import { getProcessStartTime, type ProcessIdentity } from '../../../server/process-state.ts';
15
15
  import { LIFECYCLE_PROTOCOL_VERSION, type LifecycleIdentityWarning } from './build-identity.ts';
16
16
  import {
@@ -126,8 +126,7 @@ function verifyControllerBinding(
126
126
  ...control,
127
127
  timeoutMs: 30_000
128
128
  });
129
- if (verified.binding.taskId !== context.taskId
130
- || verified.binding.controlGeneration !== context.controlGeneration
129
+ if (verified.binding.controlGeneration !== context.controlGeneration
131
130
  || verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
132
131
  throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
133
132
  }
@@ -150,8 +149,7 @@ function verifyCodexSandboxControllerContextWithWarnings(
150
149
  ...control,
151
150
  timeoutMs: 30_000
152
151
  });
153
- if (verified.binding.taskId !== context.taskId
154
- || verified.binding.controlGeneration !== context.controlGeneration
152
+ if (verified.binding.controlGeneration !== context.controlGeneration
155
153
  || verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
156
154
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
157
155
  }