@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,447 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import crypto from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { preflightCodexLifecycleEvidence, resolveCodexTerminal, resolveCodexThread } from './app-server.js';
6
+ import { createCodexLifecycleStore } from './store.js';
7
+ import { verifyCodexSandboxControllerContextWithWarnings } from './sandbox-controller.js';
8
+ import { resolveTaskRef } from '../../../task/resolve-ref.js';
9
+ import { resolveTaskContext } from '../../../task/resolve-ref.js';
10
+ import { hasSealableOrchestrationDelegation, readRun } from '../../../task/orchestration.js';
11
+ import { parseCodexLifecycleBinding, verifyCodexLifecycleTaskBinding } from './binding.js';
12
+ import { activateCodexOrchestrationDelegation, activateCodexSpawnDelegation, reconcileCodexOrchestrationDelegation, sealCodexOrchestrationDelegation, sealCodexParentDelegation } from '../codex-orchestration.js';
13
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from '../../../internal/cli-route-inventory.js';
14
+ const USAGE = 'Usage: agent-infra-internal codex-lifecycle <hook-event|resolve-start|resolve-stop|preflight|consume> [options]\n';
15
+ const MANAGED_AGENT = /^agent-infra-lifecycle-(executor|reviewer)$/;
16
+ function output(value) {
17
+ process.stdout.write(`${JSON.stringify(value)}\n`);
18
+ }
19
+ function failure(code, message) {
20
+ output({ status: 'failed', changed: false, evidence: null, diagnostics: [], error: { code, message } });
21
+ process.exitCode = 1;
22
+ }
23
+ function outputBridgeResult(result) {
24
+ if (result.error?.code === 'ORCHESTRATION_DELEGATION_MISSING') {
25
+ output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
26
+ return;
27
+ }
28
+ output(result);
29
+ if (result.status !== 'running')
30
+ process.exitCode = 1;
31
+ }
32
+ function parse(args) {
33
+ if (args[0] === '--help' || args[0] === '-h') {
34
+ process.stdout.write(USAGE);
35
+ return null;
36
+ }
37
+ const operation = args[0];
38
+ if (!operation) {
39
+ failure('CODEX_LIFECYCLE_PAYLOAD_INVALID', 'operation is required');
40
+ return null;
41
+ }
42
+ const allowed = {
43
+ 'hook-event': ['--event', '--bridge'],
44
+ 'resolve-start': ['--child-id'],
45
+ 'resolve-stop': ['--child-id'],
46
+ preflight: ['--format', '--session-id', '--turn-id', '--tool-use-id'],
47
+ consume: ['--child-id', '--consumer']
48
+ }[operation];
49
+ if (!allowed) {
50
+ failure('CODEX_LIFECYCLE_PAYLOAD_INVALID', 'unknown operation');
51
+ return null;
52
+ }
53
+ const values = {};
54
+ for (let index = 1; index < args.length; index += 1) {
55
+ const flag = args[index];
56
+ if (!allowed.includes(flag) || Object.hasOwn(values, flag)) {
57
+ failure('CODEX_LIFECYCLE_PAYLOAD_INVALID', `invalid or duplicate option '${flag}'`);
58
+ return null;
59
+ }
60
+ const value = args[++index];
61
+ if (!value || value.startsWith('--')) {
62
+ failure('CODEX_LIFECYCLE_PAYLOAD_INVALID', `option '${flag}' requires a value`);
63
+ return null;
64
+ }
65
+ values[flag] = value;
66
+ }
67
+ return Object.freeze({ operation, values: Object.freeze(values) });
68
+ }
69
+ function cliVersion() {
70
+ const result = spawnSync('codex', ['--version'], { encoding: 'utf8' });
71
+ const version = /codex-cli\s+(\d+\.\d+\.\d+)/.exec(result.stdout ?? '')?.[1];
72
+ if (result.status !== 0 || !version)
73
+ throw new Error('Codex CLI version is unavailable');
74
+ return version;
75
+ }
76
+ function hookDefinitionHash() {
77
+ const file = path.join(process.cwd(), '.codex', 'hooks.json');
78
+ return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
79
+ }
80
+ function resolveLifecycleTaskContext() {
81
+ const controllerPath = process.env.AGENT_INFRA_CODEX_CONTROLLER_CONTEXT;
82
+ const taskId = controllerPath
83
+ ? verifyCodexSandboxControllerContextWithWarnings(controllerPath, { repoRoot: process.cwd() }).context.taskId
84
+ : process.env.AGENT_INFRA_TASK_ID;
85
+ const context = taskId
86
+ ? resolveTaskRef(taskId, { repoRoot: process.cwd() })
87
+ : resolveTaskContext(undefined, { repoRoot: process.cwd() });
88
+ if (!context.ok)
89
+ throw new Error(`${context.code}: ${context.message}`);
90
+ return context;
91
+ }
92
+ function verifyPendingBinding(binding, nativeAgent, expected = {}) {
93
+ const resolved = resolveLifecycleTaskContext();
94
+ const run = readRun(resolved.taskDir);
95
+ if (!run)
96
+ throw new Error('Codex lifecycle task binding has no current orchestration run');
97
+ verifyCodexLifecycleTaskBinding(binding, run, nativeAgent, expected);
98
+ return { resolved, run };
99
+ }
100
+ function verifyStoredBinding(taskId, binding, nativeAgent) {
101
+ const resolved = resolveTaskRef(taskId, { repoRoot: process.cwd() });
102
+ if (!resolved.ok)
103
+ throw new Error(`${resolved.code}: ${resolved.message}`);
104
+ const run = readRun(resolved.taskDir);
105
+ const receipt = run?.pendingDelegation;
106
+ if (!receipt || run?.status !== 'running'
107
+ || receipt.id !== binding.receiptId
108
+ || receipt.runId !== binding.runId
109
+ || receipt.client !== 'codex'
110
+ || receipt.role !== nativeRole(nativeAgent)) {
111
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: stored task receipt is not the current pending lifecycle event');
112
+ }
113
+ }
114
+ function sameTaskBinding(left, right) {
115
+ return left?.runId === right.runId && left.receiptId === right.receiptId;
116
+ }
117
+ function nativeRole(nativeAgent) {
118
+ const match = /^agent-infra-lifecycle-(executor|reviewer)$/u.exec(nativeAgent);
119
+ return match?.[1] ?? null;
120
+ }
121
+ function taskScopedStore(taskId) {
122
+ return createCodexLifecycleStore({
123
+ repoRoot: process.cwd(),
124
+ taskId,
125
+ cliVersion: cliVersion()
126
+ });
127
+ }
128
+ async function readStdin() {
129
+ let input = '';
130
+ process.stdin.setEncoding('utf8');
131
+ for await (const chunk of process.stdin) {
132
+ input += chunk;
133
+ if (Buffer.byteLength(input) > 64 * 1024)
134
+ throw new Error('Codex lifecycle input exceeds 64 KiB');
135
+ }
136
+ return JSON.parse(input || '{}');
137
+ }
138
+ function recordFromPayload(phase, payload, taskBinding) {
139
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
140
+ throw new Error('Codex lifecycle hook payload must be an object');
141
+ }
142
+ const value = payload;
143
+ const text = (key) => typeof value[key] === 'string' ? value[key] : '';
144
+ if (!MANAGED_AGENT.test(text('nativeAgent')))
145
+ return null;
146
+ if (phase === 'pre-tool')
147
+ return {
148
+ type: 'hook-spawn',
149
+ sessionId: text('sessionId'),
150
+ turnId: text('turnId'),
151
+ toolUseId: text('toolUseId'),
152
+ nativeAgent: text('nativeAgent'),
153
+ ...(text('requestedModel') ? { requestedModel: text('requestedModel') } : {}),
154
+ ...(text('requestedReasoningEffort') ? { requestedReasoningEffort: text('requestedReasoningEffort') } : {}),
155
+ hookDefinitionHash: text('hookDefinitionHash'),
156
+ ...(taskBinding ? { taskBinding } : {})
157
+ };
158
+ if (phase === 'subagent-start')
159
+ return {
160
+ type: 'hook-child',
161
+ sessionId: text('sessionId'),
162
+ turnId: text('turnId'),
163
+ childThreadId: text('childThreadId'),
164
+ nativeAgent: text('nativeAgent'),
165
+ source: 'hook'
166
+ };
167
+ if (phase === 'subagent-stop')
168
+ return {
169
+ type: 'hook-stop',
170
+ sessionId: text('sessionId'),
171
+ turnId: text('turnId'),
172
+ childThreadId: text('childThreadId'),
173
+ nativeAgent: text('nativeAgent')
174
+ };
175
+ if (phase === 'post-tool')
176
+ return null;
177
+ throw new Error(`unknown Codex lifecycle hook phase '${phase}'`);
178
+ }
179
+ function payloadText(payload, key) {
180
+ return payload && typeof payload === 'object' && !Array.isArray(payload)
181
+ && typeof payload[key] === 'string'
182
+ ? payload[key]
183
+ : '';
184
+ }
185
+ function resolveHookTaskContext(phase, toolName, payload) {
186
+ const spawnName = payloadText(payload, 'taskName');
187
+ const needsSpawnBinding = phase === 'pre-tool' || (phase === 'post-tool' && toolName === 'collaborationspawn_agent');
188
+ const spawnBinding = needsSpawnBinding ? parseCodexLifecycleBinding(spawnName) : null;
189
+ if (needsSpawnBinding && !spawnBinding) {
190
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISSING: task_name must carry the prepared task binding');
191
+ }
192
+ const nativeAgent = payloadText(payload, 'nativeAgent');
193
+ const taskContext = spawnBinding
194
+ ? verifyPendingBinding(spawnBinding.binding, nativeAgent, {
195
+ requestedModel: payloadText(payload, 'requestedModel') || undefined,
196
+ requestedReasoningEffort: payloadText(payload, 'requestedReasoningEffort') || undefined
197
+ }).resolved
198
+ : resolveLifecycleTaskContext();
199
+ const store = taskScopedStore(taskContext.taskId);
200
+ if (spawnBinding)
201
+ verifyStoredBinding(taskContext.taskId, spawnBinding.binding, nativeAgent);
202
+ return { spawnBinding, nativeAgent, taskContext, store };
203
+ }
204
+ function currentHookTaskBinding(taskDir, taskId, spawnBinding, nativeAgent) {
205
+ if (!spawnBinding) {
206
+ const run = readRun(taskDir);
207
+ const receipt = run?.pendingDelegation;
208
+ if (!receipt || receipt.client !== 'codex' || receipt.role !== nativeRole(nativeAgent)) {
209
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: no matching pending task receipt');
210
+ }
211
+ verifyStoredBinding(taskId, { taskId: receipt.taskId, runId: receipt.runId, receiptId: receipt.id }, nativeAgent);
212
+ }
213
+ const receipt = readRun(taskDir)?.pendingDelegation;
214
+ return receipt && { taskId: receipt.taskId, runId: receipt.runId, receiptId: receipt.id };
215
+ }
216
+ function validateHookStopIdentity(store, event, currentBinding) {
217
+ const record = store.read(event.childThreadId);
218
+ const child = record.state.child;
219
+ if (!currentBinding || !sameTaskBinding(record.taskBinding, currentBinding)
220
+ || !child
221
+ || child.sessionId !== event.sessionId
222
+ || child.turnId !== event.turnId
223
+ || child.childThreadId !== event.childThreadId
224
+ || child.nativeAgent !== event.nativeAgent) {
225
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: stop event does not match the current task receipt and child');
226
+ }
227
+ }
228
+ function matchingHookChildSpawns(store, binding, event) {
229
+ return binding ? store.findByTaskBinding(binding).filter((record) => record.state.spawn?.sessionId === event.sessionId
230
+ && record.state.spawn?.nativeAgent === event.nativeAgent) : [];
231
+ }
232
+ async function applyHookChildEvent(event, currentBinding, taskContext, store, bridge, resolved) {
233
+ const candidates = matchingHookChildSpawns(store, currentBinding, event);
234
+ if (candidates.length !== 1) {
235
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: child event has no unique spawn for the current task receipt');
236
+ }
237
+ const childResolution = resolved ?? await resolveCodexThread(event.childThreadId);
238
+ verifyStoredBinding(taskContext.taskId, currentBinding, event.nativeAgent);
239
+ const latestCandidates = matchingHookChildSpawns(store, currentBinding, event);
240
+ if (latestCandidates.length !== 1 || latestCandidates[0].revision !== candidates[0].revision) {
241
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: task receipt spawn became ambiguous during child resolution');
242
+ }
243
+ if (childResolution.resolution.thread.childThreadId !== event.childThreadId
244
+ || childResolution.resolution.thread.parentThreadId !== event.sessionId
245
+ || childResolution.resolution.thread.nativeAgent !== event.nativeAgent) {
246
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: resolved child does not match the current spawn');
247
+ }
248
+ verifyStoredBinding(taskContext.taskId, currentBinding, event.nativeAgent);
249
+ const spawn = latestCandidates[0].state.spawn;
250
+ if (!spawn)
251
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: current spawn record is missing');
252
+ const result = store.applyToSpawn({
253
+ sessionId: spawn.sessionId, turnId: spawn.turnId, toolUseId: spawn.toolUseId, taskBinding: currentBinding
254
+ }, { ...event, parentThreadId: childResolution.resolution.thread.parentThreadId }, latestCandidates[0].revision);
255
+ if (bridge) {
256
+ outputBridgeResult(await activateCodexOrchestrationDelegation(event.childThreadId, {
257
+ store, orchestrationOptions: { repoRoot: process.cwd(), taskId: taskContext.taskId },
258
+ resolveThread: async () => childResolution
259
+ }));
260
+ return;
261
+ }
262
+ output({ status: result.state.status, changed: true, evidence: result.state, diagnostics: childResolution.diagnostics, error: result.state.error });
263
+ if (result.state.status === 'invalid')
264
+ process.exitCode = 1;
265
+ }
266
+ async function handleBridgePostTool(toolName, payload, nativeAgent, taskId, store) {
267
+ const orchestrationOptions = { repoRoot: process.cwd(), taskId };
268
+ if (toolName === 'collaborationspawn_agent') {
269
+ if (!MANAGED_AGENT.test(nativeAgent)) {
270
+ output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
271
+ return;
272
+ }
273
+ const requestedModel = payloadText(payload, 'requestedModel');
274
+ const requestedReasoningEffort = payloadText(payload, 'requestedReasoningEffort');
275
+ const bridged = await activateCodexSpawnDelegation({
276
+ sessionId: payloadText(payload, 'sessionId'), turnId: payloadText(payload, 'turnId'),
277
+ toolUseId: payloadText(payload, 'toolUseId'), transcriptPath: payloadText(payload, 'transcriptPath'),
278
+ nativeAgent, taskName: payloadText(payload, 'taskName'),
279
+ ...(requestedModel ? { requestedModel } : {}),
280
+ ...(requestedReasoningEffort ? { requestedReasoningEffort } : {})
281
+ }, { store, orchestrationOptions });
282
+ outputBridgeResult(bridged);
283
+ return;
284
+ }
285
+ if (toolName === 'collaborationwait_agent') {
286
+ outputBridgeResult(await sealCodexParentDelegation(payloadText(payload, 'sessionId'), { store, orchestrationOptions }));
287
+ return;
288
+ }
289
+ output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
290
+ }
291
+ async function sealConsumedHookStop(event, store, taskId) {
292
+ const result = await sealCodexOrchestrationDelegation(event.childThreadId, {
293
+ store, orchestrationOptions: { repoRoot: process.cwd(), taskId }
294
+ });
295
+ outputBridgeResult(result);
296
+ }
297
+ function handleMissingHookEvent(phase, bridge, payload, taskId) {
298
+ if (phase === 'post-tool' && bridge) {
299
+ const value = payload;
300
+ outputBridgeResult(reconcileCodexOrchestrationDelegation(String(value.childThreadId ?? ''), {
301
+ orchestrationOptions: { repoRoot: process.cwd(), taskId }
302
+ }));
303
+ return;
304
+ }
305
+ output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
306
+ }
307
+ async function handlePreflight(parsed) {
308
+ const format = parsed.values['--format'] ?? 'json';
309
+ if (!['text', 'json'].includes(format))
310
+ throw new Error("format must be 'text' or 'json'");
311
+ const runtimeFields = [parsed.values['--session-id'], parsed.values['--turn-id'], parsed.values['--tool-use-id']];
312
+ if (runtimeFields.some(Boolean) && !runtimeFields.every(Boolean)) {
313
+ throw new Error('preflight runtime identity requires --session-id, --turn-id, and --tool-use-id together');
314
+ }
315
+ const runtimeIdentity = runtimeFields.every(Boolean)
316
+ ? { sessionId: runtimeFields[0], turnId: runtimeFields[1], toolUseId: runtimeFields[2] }
317
+ : undefined;
318
+ const result = await preflightCodexLifecycleEvidence(process.cwd(), runtimeIdentity);
319
+ if (format === 'text') {
320
+ process.stdout.write(`Codex lifecycle static preflight: ready\nCLI: ${result.cliVersion}\nRuntime hook liveness: ${result.runtimeLiveness ? 'observed' : 'not-yet-observed'}\n`);
321
+ }
322
+ else
323
+ output({ status: 'ready', changed: false, evidence: result, diagnostics: result.diagnostics, error: null });
324
+ }
325
+ async function codexLifecycle(args = []) {
326
+ if (!ensureInternalHandlerRoute('codex-lifecycle', args))
327
+ return;
328
+ const parsed = parse(args);
329
+ if (!parsed || process.exitCode)
330
+ return;
331
+ try {
332
+ if (internalHandlerRoute('codex-lifecycle', 'preflight', parsed.operation)) {
333
+ await handlePreflight(parsed);
334
+ return;
335
+ }
336
+ if (internalHandlerRoute('codex-lifecycle', 'hook-event', parsed.operation)) {
337
+ const phase = parsed.values['--event'];
338
+ if (!phase || !['pre-tool', 'subagent-start', 'post-tool', 'subagent-stop'].includes(phase)) {
339
+ throw new Error('hook-event requires a known --event');
340
+ }
341
+ if (parsed.values['--bridge'] !== undefined && parsed.values['--bridge'] !== 'true') {
342
+ throw new Error("hook-event --bridge must be 'true'");
343
+ }
344
+ const payload = await readStdin();
345
+ const toolName = payloadText(payload, 'toolName');
346
+ const hookContext = resolveHookTaskContext(phase, toolName, payload);
347
+ const { spawnBinding, nativeAgent, taskContext, store } = hookContext;
348
+ if (phase === 'post-tool' && parsed.values['--bridge'] === 'true') {
349
+ await handleBridgePostTool(toolName, payload, nativeAgent, taskContext.taskId, store);
350
+ return;
351
+ }
352
+ const event = recordFromPayload(phase, payload, spawnBinding?.binding);
353
+ if (!event) {
354
+ handleMissingHookEvent(phase, parsed.values['--bridge'] === 'true', payload, taskContext.taskId);
355
+ return;
356
+ }
357
+ const currentBinding = currentHookTaskBinding(taskContext.taskDir, taskContext.taskId, spawnBinding, nativeAgent);
358
+ if (event.type === 'hook-stop') {
359
+ validateHookStopIdentity(store, event, currentBinding);
360
+ }
361
+ if (parsed.values['--bridge'] === 'true' && event.type === 'hook-stop'
362
+ && store.read(event.childThreadId).consumer) {
363
+ await sealConsumedHookStop(event, store, taskContext.taskId);
364
+ return;
365
+ }
366
+ if (event.type === 'hook-child') {
367
+ await applyHookChildEvent(event, currentBinding, taskContext, store, parsed.values['--bridge'] === 'true');
368
+ return;
369
+ }
370
+ if (!currentBinding)
371
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: current task receipt is missing');
372
+ verifyStoredBinding(taskContext.taskId, currentBinding, nativeAgent);
373
+ const result = store.apply(event);
374
+ if (parsed.values['--bridge'] === 'true' && event.type === 'hook-stop') {
375
+ if (!hasSealableOrchestrationDelegation('codex', event.childThreadId, { repoRoot: process.cwd(), taskId: taskContext.taskId })) {
376
+ output({ status: 'ignored', changed: false, evidence: null, diagnostics: [], error: null });
377
+ return;
378
+ }
379
+ output({
380
+ status: result.state.status,
381
+ changed: true,
382
+ evidence: result.state,
383
+ diagnostics: [],
384
+ error: result.state.error
385
+ });
386
+ return;
387
+ }
388
+ output({ status: result.state.status, changed: true, evidence: result.state, diagnostics: [], error: result.state.error });
389
+ if (result.state.status === 'invalid')
390
+ process.exitCode = 1;
391
+ return;
392
+ }
393
+ const childThreadId = parsed.values['--child-id'];
394
+ if (!childThreadId)
395
+ throw new Error(`operation '${parsed.operation}' requires --child-id`);
396
+ const taskContext = resolveLifecycleTaskContext();
397
+ const store = taskScopedStore(taskContext.taskId);
398
+ const existing = store.read(childThreadId);
399
+ if (!existing.taskBinding)
400
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISSING: stored lifecycle identity has no task binding');
401
+ verifyStoredBinding(taskContext.taskId, existing.taskBinding, existing.state.startEvidence?.nativeAgent ?? existing.state.spawn?.nativeAgent ?? '');
402
+ if (internalHandlerRoute('codex-lifecycle', 'resolve-start', parsed.operation)) {
403
+ if (store.read(childThreadId).state.spawn?.hookDefinitionHash !== hookDefinitionHash()) {
404
+ throw new Error('Codex lifecycle hook definition hash is stale');
405
+ }
406
+ const resolved = await resolveCodexThread(childThreadId);
407
+ verifyStoredBinding(taskContext.taskId, existing.taskBinding, existing.state.startEvidence?.nativeAgent ?? existing.state.spawn?.nativeAgent ?? '');
408
+ let latest = store.apply(resolved.resolution.thread);
409
+ for (const reroute of resolved.reroutes)
410
+ latest = store.apply(reroute);
411
+ latest = store.apply(resolved.resolution.settings);
412
+ output({ status: latest.state.status, changed: true, evidence: latest.state.startEvidence, diagnostics: resolved.diagnostics, error: latest.state.error });
413
+ if (latest.state.status !== 'start-ready')
414
+ process.exitCode = 1;
415
+ return;
416
+ }
417
+ if (internalHandlerRoute('codex-lifecycle', 'resolve-stop', parsed.operation)) {
418
+ const stopTurnId = store.read(childThreadId).state.stop?.turnId;
419
+ if (!stopTurnId)
420
+ throw new Error('Codex lifecycle stop hook is not available');
421
+ const terminal = await resolveCodexTerminal(childThreadId, stopTurnId);
422
+ verifyStoredBinding(taskContext.taskId, existing.taskBinding, existing.state.startEvidence?.nativeAgent ?? existing.state.spawn?.nativeAgent ?? '');
423
+ const latest = store.apply(terminal);
424
+ if (latest.state.status !== 'stop-ready') {
425
+ throw new Error(`Codex lifecycle stop evidence is not ready (status=${latest.state.status})`);
426
+ }
427
+ output({ status: latest.state.status, changed: true, evidence: latest.state.stopEvidence, diagnostics: [], error: latest.state.error });
428
+ return;
429
+ }
430
+ if (internalHandlerRoute('codex-lifecycle', 'consume', parsed.operation)) {
431
+ const consumer = parsed.values['--consumer'];
432
+ if (!consumer)
433
+ throw new Error('consume requires --consumer');
434
+ verifyStoredBinding(taskContext.taskId, existing.taskBinding, existing.state.startEvidence?.nativeAgent ?? existing.state.spawn?.nativeAgent ?? '');
435
+ const consumed = store.consume(childThreadId, consumer, hookDefinitionHash());
436
+ output({ status: 'consumed', changed: true, evidence: {
437
+ start: consumed.state.startEvidence,
438
+ stop: consumed.state.stopEvidence
439
+ }, diagnostics: [], error: null });
440
+ }
441
+ }
442
+ catch (error) {
443
+ failure('CODEX_LIFECYCLE_FAILED', error instanceof Error ? error.message : String(error));
444
+ }
445
+ }
446
+ export { codexLifecycle };
447
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,130 @@
1
+ import { SandboxControlClientError, requestSandboxAgentOperation } from '../../../sandbox/control/client.js';
2
+ function fail(code, message, accepted = true) {
3
+ throw new SandboxControlClientError({ code, message: `${code}: ${message}`, retryable: false }, accepted);
4
+ }
5
+ function exactKeys(value, keys) {
6
+ return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
7
+ }
8
+ function validProcess(value) {
9
+ if (!value || typeof value !== 'object' || Array.isArray(value))
10
+ return false;
11
+ const process = value;
12
+ return exactKeys(process, ['pid', 'startTime'])
13
+ && Number.isSafeInteger(process.pid) && process.pid > 0
14
+ && Number.isSafeInteger(process.startTime) && process.startTime >= 0;
15
+ }
16
+ function validBuild(value) {
17
+ if (!value || typeof value !== 'object' || Array.isArray(value))
18
+ return false;
19
+ const build = value;
20
+ return exactKeys(build, ['internalExecutableBuildHash', 'lifecycleContractHash', 'packageVersion', 'protocolVersion'])
21
+ && build.protocolVersion === 3
22
+ && typeof build.packageVersion === 'string' && build.packageVersion.length > 0
23
+ && typeof build.internalExecutableBuildHash === 'string' && /^[a-f0-9]{64}$/u.test(build.internalExecutableBuildHash)
24
+ && typeof build.lifecycleContractHash === 'string' && /^[a-f0-9]{64}$/u.test(build.lifecycleContractHash);
25
+ }
26
+ function parseCodexControllerResult(response) {
27
+ if (response.phase !== 'completed' || response.error !== null || response.stderr !== '') {
28
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result outer response is invalid');
29
+ }
30
+ let value;
31
+ try {
32
+ if (!response.stdout.endsWith('\n') || response.stdout.slice(0, -1).includes('\n'))
33
+ throw new Error('not canonical');
34
+ value = JSON.parse(response.stdout);
35
+ }
36
+ catch {
37
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid');
38
+ }
39
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
40
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result payload is invalid');
41
+ }
42
+ const result = value;
43
+ const keys = result.status === 'verified'
44
+ ? ['binding', 'changed', 'error', 'lease', 'status', 'version']
45
+ : ['changed', 'error', 'lease', 'status', 'version'];
46
+ if (!exactKeys(result, keys) || result.version !== 1
47
+ || !['opened', 'closed', 'failed', 'verified'].includes(result.status)) {
48
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result schema is invalid');
49
+ }
50
+ if (result.status === 'failed') {
51
+ const error = result.error;
52
+ if (response.exitCode !== 1 || result.changed !== false || result.lease !== null || !error
53
+ || !exactKeys(error, ['code', 'message', 'retryable'])
54
+ || typeof error.code !== 'string' || !/^[A-Z][A-Z0-9_]+$/u.test(error.code)
55
+ || typeof error.message !== 'string' || error.retryable !== false) {
56
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller failure result is invalid');
57
+ }
58
+ fail(error.code, error.message);
59
+ }
60
+ if (response.exitCode !== 0 || result.error !== null) {
61
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller success result is invalid');
62
+ }
63
+ if (result.status === 'closed') {
64
+ if (typeof result.changed !== 'boolean' || result.lease !== null)
65
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller close result is invalid');
66
+ return result;
67
+ }
68
+ if (result.status === 'verified') {
69
+ const binding = result.binding;
70
+ if (result.changed !== false || result.lease !== null || !binding
71
+ || !exactKeys(binding, ['controllerInstanceDigest', 'controlGeneration', 'taskId'])
72
+ || typeof binding.taskId !== 'string' || binding.taskId.length === 0
73
+ || typeof binding.controlGeneration !== 'string' || binding.controlGeneration.length === 0
74
+ || typeof binding.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(binding.controllerInstanceDigest)) {
75
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify result is invalid');
76
+ }
77
+ return result;
78
+ }
79
+ const lease = result.lease;
80
+ if (result.changed !== true || !lease
81
+ || !exactKeys(lease, ['buildIdentity', 'controlGeneration', 'controllerInstanceDigest', 'controllerProcess', 'expiresAt', 'issuedAt', 'leaseId', 'leaseSecret', 'taskId', 'version'])
82
+ || lease.version !== 1 || typeof lease.leaseId !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseId)
83
+ || typeof lease.leaseSecret !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.leaseSecret)
84
+ || typeof lease.taskId !== 'string' || lease.taskId.length === 0
85
+ || typeof lease.controlGeneration !== 'string' || lease.controlGeneration.length === 0
86
+ || typeof lease.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(lease.controllerInstanceDigest)
87
+ || !validProcess(lease.controllerProcess) || !validBuild(lease.buildIdentity)
88
+ || !Number.isSafeInteger(lease.issuedAt) || !Number.isSafeInteger(lease.expiresAt)
89
+ || lease.expiresAt <= lease.issuedAt) {
90
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller open result is invalid');
91
+ }
92
+ return result;
93
+ }
94
+ function requestController(params) {
95
+ const result = parseCodexControllerResult(requestSandboxAgentOperation({
96
+ agentClient: 'codex',
97
+ operation: `controller.${params.command}`,
98
+ payload: params.command === 'open'
99
+ ? { controllerProcess: params.controllerProcess }
100
+ : { proof: params.controllerProof },
101
+ ...params
102
+ }));
103
+ if (result.status === 'opened'
104
+ && (result.lease.controlGeneration !== (params.generation ?? process.env.AGENT_INFRA_CONTROL_GENERATION)
105
+ || result.lease.controllerProcess.pid !== params.controllerProcess.pid
106
+ || result.lease.controllerProcess.startTime !== params.controllerProcess.startTime)) {
107
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller result does not match the request');
108
+ }
109
+ return result;
110
+ }
111
+ function requestCodexControllerOpen(params) {
112
+ const result = requestController({ ...params, command: 'open', controllerProof: null });
113
+ if (result.status !== 'opened')
114
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller open returned the wrong result');
115
+ return result;
116
+ }
117
+ function requestCodexControllerClose(params) {
118
+ const result = requestController({ ...params, command: 'close' });
119
+ if (result.status !== 'closed')
120
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller close returned the wrong result');
121
+ return result;
122
+ }
123
+ function requestCodexControllerVerify(params) {
124
+ const result = requestController({ ...params, controllerProcess: params.controllerProof.controllerProcess, command: 'verify' });
125
+ if (result.status !== 'verified')
126
+ fail('SANDBOX_CONTROL_RESULT_INVALID', 'controller verify returned the wrong result');
127
+ return result;
128
+ }
129
+ export { parseCodexControllerResult, requestCodexControllerClose, requestCodexControllerOpen, requestCodexControllerVerify };
130
+ //# sourceMappingURL=controller-client.js.map