@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
@@ -0,0 +1,131 @@
1
+ import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, readCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
2
+ import { computeLifecycleBuildIdentity } from './build-identity.js';
3
+ function validProcess(value) {
4
+ if (!value || typeof value !== 'object' || Array.isArray(value))
5
+ return false;
6
+ const process = value;
7
+ return Object.keys(process).sort().join(',') === 'pid,startTime'
8
+ && Number.isSafeInteger(process.pid) && process.pid > 0
9
+ && Number.isSafeInteger(process.startTime) && process.startTime >= 0;
10
+ }
11
+ function validProof(value) {
12
+ if (!value || typeof value !== 'object' || Array.isArray(value))
13
+ return false;
14
+ const proof = value;
15
+ return Object.keys(proof).sort().join(',') === 'controllerProcess,leaseId,leaseSecret,version'
16
+ && proof.version === 1
17
+ && typeof proof.leaseId === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseId)
18
+ && typeof proof.leaseSecret === 'string' && /^[a-f0-9]{64}$/u.test(proof.leaseSecret)
19
+ && validProcess(proof.controllerProcess);
20
+ }
21
+ function validate(params) {
22
+ const { operation, payload, manifest } = params;
23
+ const expectedKeys = operation === 'controller.open' ? ['controllerProcess'] : ['proof'];
24
+ if (!['controller.open', 'controller.close', 'controller.verify'].includes(operation)
25
+ || Object.keys(payload).sort().join(',') !== expectedKeys.sort().join(',')
26
+ || (operation === 'controller.open'
27
+ ? !validProcess(payload.controllerProcess)
28
+ : !validProof(payload.proof))) {
29
+ throw new Error('SANDBOX_CONTROL_REQUEST_INVALID: controller operation payload is invalid');
30
+ }
31
+ if (manifest.mode !== 'task-bound' || !manifest.taskId) {
32
+ throw new Error('SANDBOX_CONTROL_BRANCH_ONLY: controller operations require a task-bound sandbox');
33
+ }
34
+ }
35
+ async function execute(params) {
36
+ const { operation, payload, manifest, manifestPath } = params;
37
+ try {
38
+ const result = operation === 'controller.open'
39
+ ? openCodexControllerRegistration({
40
+ manifest,
41
+ manifestPath,
42
+ controllerProcess: payload.controllerProcess,
43
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
44
+ })
45
+ : operation === 'controller.close'
46
+ ? closeCodexControllerRegistration({
47
+ manifest,
48
+ manifestPath,
49
+ proof: payload.proof
50
+ })
51
+ : (() => {
52
+ const binding = resolveCodexControllerBinding({
53
+ manifest,
54
+ manifestPath,
55
+ proof: payload.proof,
56
+ buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
57
+ });
58
+ return {
59
+ version: 1,
60
+ status: 'verified',
61
+ changed: false,
62
+ lease: null,
63
+ binding: {
64
+ taskId: manifest.taskId,
65
+ controlGeneration: binding.controlGeneration,
66
+ controllerInstanceDigest: binding.instanceDigest
67
+ },
68
+ error: null
69
+ };
70
+ })();
71
+ return { exitCode: 0, stdout: `${JSON.stringify(result)}\n`, stderr: '' };
72
+ }
73
+ catch (error) {
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ const code = error instanceof CodexControllerRegistrationError
76
+ ? error.code
77
+ : /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'CODEX_SANDBOX_CONTROLLER_FAILED';
78
+ return {
79
+ exitCode: 1,
80
+ stdout: `${JSON.stringify({
81
+ version: 1,
82
+ status: 'failed',
83
+ changed: false,
84
+ lease: null,
85
+ error: { code, message, retryable: false }
86
+ })}\n`,
87
+ stderr: ''
88
+ };
89
+ }
90
+ }
91
+ function recover(params) {
92
+ if (params.stdout === null)
93
+ return { consistent: false };
94
+ let output;
95
+ try {
96
+ const parsed = JSON.parse(params.stdout);
97
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
98
+ return { consistent: false };
99
+ output = parsed;
100
+ }
101
+ catch {
102
+ return { consistent: false };
103
+ }
104
+ if (output.error !== null || typeof output.status !== 'string')
105
+ return { consistent: false };
106
+ try {
107
+ const registration = readCodexControllerRegistration(params.manifestPath);
108
+ if (params.operation === 'controller.open') {
109
+ const lease = output.lease;
110
+ return { consistent: output.status === 'opened' && output.changed === true
111
+ && lease?.controlGeneration === registration.controlGeneration
112
+ && lease?.controllerInstanceDigest === registration.controllerInstanceDigest
113
+ && registration.controlGeneration === params.manifest.generation };
114
+ }
115
+ if (params.operation === 'controller.verify') {
116
+ const binding = output.binding;
117
+ return { consistent: output.status === 'verified' && output.changed === false
118
+ && binding?.controlGeneration === registration.controlGeneration
119
+ && binding?.controllerInstanceDigest === registration.controllerInstanceDigest };
120
+ }
121
+ return { consistent: false };
122
+ }
123
+ catch (error) {
124
+ return { consistent: params.operation === 'controller.close'
125
+ && error.code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
126
+ && output.status === 'closed' && typeof output.changed === 'boolean' };
127
+ }
128
+ }
129
+ const codexControllerOperation = Object.freeze({ validate, execute, recover });
130
+ export { codexControllerOperation };
131
+ //# sourceMappingURL=controller-operation.js.map
@@ -1,8 +1,8 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import { parseLinuxProcessStat } from '../../server/process-state.js';
5
- import { commandForEngine, runProbe } from '../shell.js';
4
+ import { parseLinuxProcessStat } from '../../../server/process-state.js';
5
+ import { commandForEngine, runProbe } from '../../../sandbox/shell.js';
6
6
  const CONTROLLER_TTL_MS = 4 * 60 * 60 * 1_000;
7
7
  const HEX_256 = /^[a-f0-9]{64}$/u;
8
8
  const REGISTRATION_KEYS = [
@@ -1,3 +1,50 @@
1
+ function isCodexLifecycleActivationEvidence(value, parentId) {
2
+ if (!value || typeof value !== 'object' || Array.isArray(value))
3
+ return false;
4
+ const state = value;
5
+ const evidence = state.activationEvidence;
6
+ if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence))
7
+ return false;
8
+ const activation = evidence;
9
+ return activation.kind === 'codex-lifecycle-v2'
10
+ && typeof activation.hookDefinitionHash === 'string'
11
+ && activation.hookDefinitionHash.trim() !== ''
12
+ && Number.isSafeInteger(activation.startRevision)
13
+ && activation.startRevision >= 1
14
+ && activation.capabilitySessionId === parentId;
15
+ }
16
+ function isCodexDelegationReceiptEvidence(value) {
17
+ if (!value || typeof value !== 'object' || Array.isArray(value))
18
+ return false;
19
+ const receipt = value;
20
+ if (receipt.client !== 'codex' || typeof receipt.id !== 'string')
21
+ return false;
22
+ const status = receipt.status;
23
+ if (!['activated', 'stage-completed', 'sealed', 'consumed', 'aborted'].includes(status))
24
+ return true;
25
+ const adapterEvidence = receipt.adapterEvidence;
26
+ if (!adapterEvidence || typeof adapterEvidence !== 'object' || Array.isArray(adapterEvidence))
27
+ return false;
28
+ const codex = adapterEvidence.codex;
29
+ if (!codex || typeof codex !== 'object' || Array.isArray(codex))
30
+ return false;
31
+ const state = codex;
32
+ if (!isCodexLifecycleActivationEvidence(state, String(receipt.parentId ?? '')))
33
+ return false;
34
+ if (!['sealed', 'consumed', 'aborted'].includes(status))
35
+ return true;
36
+ const records = state.records;
37
+ if (!records || typeof records !== 'object' || Array.isArray(records))
38
+ return false;
39
+ return Object.values(records).some((record) => {
40
+ if (!record || typeof record !== 'object' || Array.isArray(record))
41
+ return false;
42
+ const evidence = record;
43
+ return evidence.consumer === receipt.id
44
+ && typeof evidence.consumedAt === 'string'
45
+ && evidence.consumedAt.trim() !== '';
46
+ });
47
+ }
1
48
  function createCodexLifecycleState(cliVersion) {
2
49
  if (!cliVersion.trim())
3
50
  throw new Error('Codex CLI version is required');
@@ -185,5 +232,5 @@ function expireCodexLifecycleState(state) {
185
232
  })
186
233
  });
187
234
  }
188
- export { createCodexLifecycleState, expireCodexLifecycleState, reduceCodexLifecycleEvent };
235
+ export { createCodexLifecycleState, expireCodexLifecycleState, isCodexLifecycleActivationEvidence, isCodexDelegationReceiptEvidence, reduceCodexLifecycleEvent };
189
236
  //# sourceMappingURL=evidence.js.map
@@ -1,12 +1,11 @@
1
1
  import fs from 'node:fs';
2
- import { createCodexLifecycleStore } from './store.js';
2
+ import { createCodexLifecycleStore, readCodexLifecycleActivationEvidence } from './store.js';
3
3
  import { managedDelegationRole } from '../../../task/delegation-receipts.js';
4
4
  import { normalizeAgentToken } from '../../tokens.js';
5
5
  import { locateActivityLog, pairEntries, appendActivityEntry } from '../../../task/activity-log.js';
6
6
  import { captureTaskWriteMetadata, writeTask } from '../../../task/write.js';
7
7
  import { parseTypedTaskFrontmatter } from '../../../task/frontmatter.js';
8
8
  import { resolveTaskRef } from '../../../task/resolve-ref.js';
9
- import { resolveAgentRuntimeStoreRoot } from '../../../runtime/agent-runtime.js';
10
9
  import { recoverActivatedOrchestrationDelegationUnderLock, readRun } from '../../../task/orchestration.js';
11
10
  const ACTIONS = {
12
11
  analysis: 'Analyze Task',
@@ -47,9 +46,10 @@ function rowForReceipt(rows, receipt) {
47
46
  const pattern = actionPattern(receipt);
48
47
  return pattern ? rows.filter((row) => pattern.test(row.step)) : [];
49
48
  }
50
- function readLifecycleStore(options, repoRoot) {
49
+ function readLifecycleStore(options, repoRoot, taskId) {
51
50
  return options.lifecycleStore ?? createCodexLifecycleStore({
52
- root: resolveAgentRuntimeStoreRoot({ repoRoot, store: 'lifecycle' }),
51
+ repoRoot,
52
+ taskId,
53
53
  cliVersion: 'recovery',
54
54
  now: options.now
55
55
  });
@@ -66,37 +66,10 @@ function readStoredEvidence(store, childId) {
66
66
  }
67
67
  }
68
68
  function validateStopRecord(record, receipt) {
69
- const start = record.state.startEvidence;
70
- const stop = record.state.stopEvidence;
71
- const provenance = receipt.lifecycleProvenance;
72
- const host = receipt.hostEvidence;
73
- if (receipt.client !== 'codex'
74
- || !provenance
75
- || host?.kind !== 'codex-lifecycle-v2'
76
- || !start
77
- || !stop
78
- || record.state.status !== 'stop-ready'
79
- || stop.terminalStatus !== 'completed'
80
- || stop.hookStopObserved !== true
81
- || start.childThreadId !== receipt.childId
82
- || start.parentThreadId !== receipt.parentId
83
- || managedDelegationRole(start.nativeAgent) !== receipt.role
84
- || start.hookDefinitionHash !== provenance.hookDefinitionHash
85
- || host.hookDefinitionHash !== provenance.hookDefinitionHash
86
- || host.capabilitySessionId !== provenance.capabilitySessionId
87
- || host.capabilityTurnId !== provenance.capabilityTurnId
88
- || host.capabilityToolUseId !== provenance.capabilityToolUseId
89
- || host.spawnToolUseId !== start.spawnToolUseId
90
- || !host.spawnObservedAt
91
- || host.spawnObservedAt !== record.spawnObservedAt
92
- || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
93
- || host.controlGeneration !== provenance.controlGeneration
94
- || typeof host.controllerInstanceDigest !== 'string'
95
- || typeof host.controlGeneration !== 'string'
96
- || host.startRevision < 1
97
- || record.revision <= host.startRevision
98
- || record.state.child?.childThreadId !== receipt.childId
99
- || record.state.stop?.childThreadId !== receipt.childId) {
69
+ if (!stopRecordMatchesReceipt(record, receipt)) {
70
+ return { ok: false, code: 'RECOVERY_TASK_BINDING_MISMATCH', message: 'lifecycle evidence belongs to a different task receipt' };
71
+ }
72
+ if (!stopEvidenceMatchesReceipt(record, receipt)) {
100
73
  return { ok: false, code: 'RECOVERY_STOP_EVIDENCE_INVALID', message: 'Codex stop evidence does not match the activated receipt' };
101
74
  }
102
75
  if (record.consumer !== null && record.consumer !== receipt.id) {
@@ -104,6 +77,77 @@ function validateStopRecord(record, receipt) {
104
77
  }
105
78
  return { ok: true };
106
79
  }
80
+ function stopRecordMatchesReceipt(record, receipt) {
81
+ return record.taskBinding?.runId === receipt.runId
82
+ && record.taskBinding.receiptId === receipt.id;
83
+ }
84
+ function stopEvidenceMatchesReceipt(record, receipt) {
85
+ const start = record.state.startEvidence;
86
+ const stop = record.state.stopEvidence;
87
+ const host = readCodexLifecycleActivationEvidence(receipt);
88
+ if (!stopRecordHasRequiredEvidence(record, receipt, start, stop, host))
89
+ return false;
90
+ return spawnMatchesReceipt(record, receipt, host, start)
91
+ && childMatchesReceipt(record, receipt, start, stop)
92
+ && hostEvidenceMatches(record, receipt, host, start);
93
+ }
94
+ function stopRecordHasRequiredEvidence(record, receipt, start, stop, host) {
95
+ return receipt.client === 'codex'
96
+ && host?.kind === 'codex-lifecycle-v2'
97
+ && Boolean(start && stop && record.state.spawn && record.state.child && record.state.stop)
98
+ && record.state.status === 'stop-ready'
99
+ && stop?.terminalStatus === 'completed'
100
+ && stop.hookStopObserved === true;
101
+ }
102
+ function spawnMatchesReceipt(record, receipt, host, start) {
103
+ const spawn = record.state.spawn;
104
+ return Boolean(spawn)
105
+ && spawn.sessionId === start.parentThreadId
106
+ && spawn.turnId === host.capabilityTurnId
107
+ && spawn.toolUseId === host.spawnToolUseId
108
+ && spawn.nativeAgent === start.nativeAgent
109
+ && spawn.hookDefinitionHash === host.hookDefinitionHash
110
+ && spawn.requestedModel === receipt.requestedModel
111
+ && spawn.requestedReasoningEffort === receipt.requestedReasoningEffort
112
+ && spawn.taskBinding?.runId === receipt.runId
113
+ && spawn.taskBinding.receiptId === receipt.id;
114
+ }
115
+ function childMatchesReceipt(record, receipt, start, stop) {
116
+ const child = record.state.child;
117
+ const stopState = record.state.stop;
118
+ return Boolean(child && stopState)
119
+ && child.sessionId === start.parentThreadId
120
+ && stopState.turnId === child.turnId
121
+ && (stopState.source === 'parent-rollout'
122
+ ? stop.turnId === record.state.terminal?.turnId
123
+ : child.turnId === stop.turnId)
124
+ && child.childThreadId === receipt.childId
125
+ && child.nativeAgent === start.nativeAgent
126
+ && stopState.childThreadId === receipt.childId
127
+ && stopState.nativeAgent === start.nativeAgent
128
+ && start.childThreadId === receipt.childId
129
+ && start.parentThreadId === receipt.parentId
130
+ && managedDelegationRole(start.nativeAgent) === receipt.role;
131
+ }
132
+ function hostEvidenceMatches(record, receipt, host, start) {
133
+ return start.hookDefinitionHash === host.hookDefinitionHash
134
+ && host.capabilitySessionId === start.parentThreadId
135
+ && host.capabilityTurnId === record.state.spawn?.turnId
136
+ && host.spawnToolUseId === start.spawnToolUseId
137
+ && Boolean(host.spawnObservedAt)
138
+ && host.spawnObservedAt === record.spawnObservedAt
139
+ && typeof host.controllerInstanceDigest === 'string'
140
+ && typeof host.controlGeneration === 'string'
141
+ && hostRevisionMatches(record, receipt, host);
142
+ }
143
+ function hostRevisionMatches(record, receipt, host) {
144
+ return typeof host.controllerInstanceDigest === 'string'
145
+ && typeof host.controlGeneration === 'string'
146
+ && host.startRevision >= 1
147
+ && record.revision > host.startRevision
148
+ && record.state.child?.childThreadId === receipt.childId
149
+ && record.state.stop?.childThreadId === receipt.childId;
150
+ }
107
151
  function abortedCandidates(run, rows) {
108
152
  const candidates = run.receipts.flatMap((receipt) => {
109
153
  if (receipt.status !== 'aborted' || receipt.activatedAt === null || receipt.client !== 'codex')
@@ -165,15 +209,20 @@ function recoverStartedLifecycleUnderLock(request, options = {}) {
165
209
  return result(request, 'no-op', { taskId });
166
210
  const rows = openRows(section);
167
211
  const pending = run.pendingDelegation;
168
- if (!pending) {
169
- const candidates = abortedCandidates(run, rows);
170
- if (candidates.length === 0)
171
- return result(request, 'no-op', { taskId });
172
- if (candidates.length > 1) {
173
- return failure(request, 'conflict', 'RECOVERY_CANDIDATE_AMBIGUOUS', 'more than one aborted delegation has an open lifecycle row', { taskId });
174
- }
175
- return appendRecoveryLog(request, taskId, section, candidates[0], resolved.repoRoot, options.writeTask ?? writeTask);
212
+ if (!pending)
213
+ return recoverAbortedLifecycle(request, taskId, section, resolved.repoRoot, run, rows, options);
214
+ return recoverActiveLifecycle(request, taskId, section, resolved.repoRoot, run, rows, pending, options);
215
+ }
216
+ function recoverAbortedLifecycle(request, taskId, section, repoRoot, run, rows, options) {
217
+ const candidates = abortedCandidates(run, rows);
218
+ if (candidates.length === 0)
219
+ return result(request, 'no-op', { taskId });
220
+ if (candidates.length > 1) {
221
+ return failure(request, 'conflict', 'RECOVERY_CANDIDATE_AMBIGUOUS', 'more than one aborted delegation has an open lifecycle row', { taskId });
176
222
  }
223
+ return appendRecoveryLog(request, taskId, section, candidates[0], repoRoot, options.writeTask ?? writeTask);
224
+ }
225
+ function recoverActiveLifecycle(request, taskId, section, repoRoot, run, rows, pending, options) {
177
226
  if (pending.status !== 'activated')
178
227
  return result(request, 'no-op', { taskId });
179
228
  if (pending.client !== 'codex' || run.status !== 'running' || run.pause !== null) {
@@ -181,51 +230,64 @@ function recoverStartedLifecycleUnderLock(request, options = {}) {
181
230
  taskId, receiptId: pending.id, childId: pending.childId
182
231
  });
183
232
  }
233
+ const rowResult = matchingActiveRecoveryRow(request, taskId, rows, pending);
234
+ if ('result' in rowResult)
235
+ return rowResult.result;
236
+ const evidenceResult = consumeRecoveryEvidence(request, taskId, repoRoot, pending, options);
237
+ if ('result' in evidenceResult)
238
+ return evidenceResult.result;
239
+ return finishActiveRecovery(request, taskId, section, repoRoot, pending, rowResult.row, options);
240
+ }
241
+ function matchingActiveRecoveryRow(request, taskId, rows, pending) {
184
242
  const matchingRows = rowForReceipt(rows, pending);
185
243
  if (matchingRows.length !== 1 || normalizeAgentToken(matchingRows[0].agent) !== 'codex' || matchingRows[0].note !== 'started') {
186
- return failure(request, 'conflict', 'RECOVERY_LOG_CONFLICT', 'activated delegation does not have one matching open lifecycle row', {
187
- taskId, receiptId: pending.id, childId: pending.childId
188
- });
244
+ return { result: failure(request, 'conflict', 'RECOVERY_LOG_CONFLICT', 'activated delegation does not have one matching open lifecycle row', {
245
+ taskId, receiptId: pending.id, childId: pending.childId
246
+ }) };
189
247
  }
190
248
  if (!pending.childId) {
191
- return failure(request, 'conflict', 'RECOVERY_DELEGATION_INVALID', 'activated delegation has no child identity', { taskId, receiptId: pending.id });
249
+ return { result: failure(request, 'conflict', 'RECOVERY_DELEGATION_INVALID', 'activated delegation has no child identity', { taskId, receiptId: pending.id }) };
192
250
  }
193
- const store = readLifecycleStore(options, resolved.repoRoot);
251
+ return { row: matchingRows[0] };
252
+ }
253
+ function consumeRecoveryEvidence(request, taskId, repoRoot, pending, options) {
254
+ const store = readLifecycleStore(options, repoRoot, taskId);
194
255
  const stored = readStoredEvidence(store, pending.childId);
195
256
  if ('error' in stored)
196
- return failure(request, 'owner-unknown', 'RECOVERY_STORE_UNKNOWN', stored.error.message, { taskId, receiptId: pending.id, childId: pending.childId });
257
+ return { result: failure(request, 'owner-unknown', 'RECOVERY_STORE_UNKNOWN', stored.error.message, { taskId, receiptId: pending.id, childId: pending.childId }) };
197
258
  if ('missing' in stored)
198
- return failure(request, 'owner-unknown', 'RECOVERY_STOP_EVIDENCE_MISSING', 'matching Codex lifecycle stop evidence is missing', { taskId, receiptId: pending.id, childId: pending.childId });
259
+ return { result: failure(request, 'owner-unknown', 'RECOVERY_STOP_EVIDENCE_MISSING', 'matching Codex lifecycle stop evidence is missing', { taskId, receiptId: pending.id, childId: pending.childId }) };
199
260
  const evidence = validateStopRecord(stored, pending);
200
261
  if (!evidence.ok)
201
- return failure(request, evidence.code === 'RECOVERY_CONSUMER_CONFLICT' ? 'conflict' : 'owner-unknown', evidence.code, evidence.message, { taskId, receiptId: pending.id, childId: pending.childId });
262
+ return { result: failure(request, evidence.code === 'RECOVERY_CONSUMER_CONFLICT' ? 'conflict' : 'owner-unknown', evidence.code, evidence.message, { taskId, receiptId: pending.id, childId: pending.childId }) };
202
263
  let consumed = stored;
203
264
  if (stored.consumer === null) {
204
265
  try {
205
- consumed = store.consume(pending.childId, pending.id, pending.hostEvidence?.hookDefinitionHash);
266
+ consumed = store.consumeWithinTaskLock(pending.childId, pending.id, stored.state.startEvidence?.hookDefinitionHash, {
267
+ taskId: pending.taskId, runId: pending.runId, receiptId: pending.id
268
+ });
206
269
  }
207
270
  catch (error) {
208
- return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_CONSUME_FAILED', error instanceof Error ? error.message : String(error), { taskId, receiptId: pending.id, childId: pending.childId });
271
+ return { result: failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_CONSUME_FAILED', error instanceof Error ? error.message : String(error), { taskId, receiptId: pending.id, childId: pending.childId }) };
209
272
  }
210
273
  }
211
- if (!consumed.consumedAt) {
212
- return failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_INVALID', 'consumed stop evidence has no timestamp', { taskId, receiptId: pending.id, childId: pending.childId });
213
- }
274
+ return consumed.consumedAt
275
+ ? { consumed }
276
+ : { result: failure(request, 'owner-unknown', 'RECOVERY_EVIDENCE_INVALID', 'consumed stop evidence has no timestamp', { taskId, receiptId: pending.id, childId: pending.childId }) };
277
+ }
278
+ function finishActiveRecovery(request, taskId, section, repoRoot, pending, row, options) {
214
279
  const recovered = recoverActivatedOrchestrationDelegationUnderLock(taskId, {
215
280
  receiptId: pending.id,
216
281
  stage: pending.stage,
217
282
  round: pending.round,
218
283
  artifact: pending.artifact,
219
284
  startedAgent: request.agent,
220
- childId: pending.childId,
221
- stopRevision: consumed.revision,
222
- consumer: pending.id,
223
- consumedAt: consumed.consumedAt
224
- }, { ...options.orchestration, repoRoot: resolved.repoRoot, now: options.now });
285
+ childId: pending.childId
286
+ }, { ...options.orchestration, repoRoot, now: options.now });
225
287
  if (recovered.status === 'failed' || !recovered.run) {
226
288
  return failure(request, 'owner-unknown', recovered.error?.code ?? 'RECOVERY_ORCHESTRATION_FAILED', recovered.error?.message ?? 'orchestration recovery failed', { taskId, receiptId: pending.id, childId: pending.childId });
227
289
  }
228
- return appendRecoveryLog(request, taskId, section, { receipt: recovered.run.receipts.at(-1), row: matchingRows[0] }, resolved.repoRoot, options.writeTask ?? writeTask);
290
+ return appendRecoveryLog(request, taskId, section, { receipt: recovered.run.receipts.at(-1), row }, repoRoot, options.writeTask ?? writeTask);
229
291
  }
230
292
  function recoverStartedLifecycleFromAdapter(request, options = {}) {
231
293
  return recoverStartedLifecycleUnderLock(request, options);
@@ -1,5 +1,5 @@
1
- import { runCodexSandboxController, verifyCodexSandboxControllerContextWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
2
- import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
1
+ import { runCodexSandboxController, verifyCodexSandboxControllerContextWithWarnings } from './sandbox-controller.js';
2
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from '../../../internal/cli-route-inventory.js';
3
3
  const USAGE = 'Usage: agent-infra-internal codex-sandbox-controller <run|verify-context> [options]\n';
4
4
  function fail(code, message, exitCode = 1) {
5
5
  process.stdout.write(`${JSON.stringify({
@@ -103,4 +103,4 @@ async function codexSandboxController(args = []) {
103
103
  }
104
104
  }
105
105
  export { codexSandboxController };
106
- //# sourceMappingURL=codex-sandbox-controller.js.map
106
+ //# sourceMappingURL=sandbox-controller-cli.js.map
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import semver from 'semver';
7
7
  import * as toml from 'smol-toml';
8
- import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify } from '../../../sandbox/control/client.js';
8
+ import { requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify } from './controller-client.js';
9
9
  import { getProcessStartTime } from '../../../server/process-state.js';
10
10
  import { LIFECYCLE_PROTOCOL_VERSION } from './build-identity.js';
11
11
  import { contextFromControllerLease, controllerProofFromContext, verifyCodexSandboxControllerContextWithWarnings as verifyContextFileWithWarnings, writeCodexSandboxControllerContext } from './controller-context.js';
@@ -62,8 +62,7 @@ function verifyControllerBinding(context, control, requestControllerVerify) {
62
62
  ...control,
63
63
  timeoutMs: 30_000
64
64
  });
65
- if (verified.binding.taskId !== context.taskId
66
- || verified.binding.controlGeneration !== context.controlGeneration
65
+ if (verified.binding.controlGeneration !== context.controlGeneration
67
66
  || verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
68
67
  throw new Error('CODEX_SANDBOX_CONTROLLER_TASK_BINDING_INVALID');
69
68
  }
@@ -82,8 +81,7 @@ function verifyCodexSandboxControllerContextWithWarnings(contextPath, options =
82
81
  ...control,
83
82
  timeoutMs: 30_000
84
83
  });
85
- if (verified.binding.taskId !== context.taskId
86
- || verified.binding.controlGeneration !== context.controlGeneration
84
+ if (verified.binding.controlGeneration !== context.controlGeneration
87
85
  || verified.binding.controllerInstanceDigest !== context.controllerInstanceDigest) {
88
86
  throw new Error('CODEX_SANDBOX_CONTROLLER_CONTEXT_INVALID');
89
87
  }