@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,632 @@
1
+ import {
2
+ preflightCodexLifecycleEvidence,
3
+ resolveCodexSpawnedChild,
4
+ resolveCodexTerminal,
5
+ resolveCodexThread
6
+ } from './codex-lifecycle/app-server.ts';
7
+ import { createCodexLifecycleStore } from './codex-lifecycle/store.ts';
8
+ import {
9
+ computeLifecycleBuildIdentity,
10
+ type LifecycleBuildIdentity,
11
+ } from './codex-lifecycle/build-identity.ts';
12
+ import type { AgentClientId } from '../types.ts';
13
+ import { resolveTaskRef } from '../../task/resolve-ref.ts';
14
+ import {
15
+ activateMatchingOrchestrationDelegation,
16
+ hasActivatableOrchestrationDelegation,
17
+ hasSealableOrchestrationDelegation,
18
+ OrchestrationStateError,
19
+ pauseMatchingOrchestrationDelegation,
20
+ prepareOrchestrationDelegation,
21
+ reconcileMatchingOrchestrationDelegation,
22
+ readDelegationAdapterEvidence,
23
+ readRun,
24
+ sealMatchingOrchestrationDelegation
25
+ } from '../../task/orchestration.ts';
26
+ import type { OrchestrationOptions, OrchestrationResult } from '../../task/orchestration.ts';
27
+ import { managedDelegationRole } from '../../task/delegation-receipts.ts';
28
+ import {
29
+ encodeCodexLifecycleBinding,
30
+ parseCodexLifecycleBinding,
31
+ verifyCodexLifecycleTaskBinding,
32
+ type CodexLifecycleTaskBinding
33
+ } from './codex-lifecycle/binding.ts';
34
+ import { reduceCodexLifecycleEvent } from './codex-lifecycle/evidence.ts';
35
+ import type { CodexLifecycleEvent } from './codex-lifecycle/evidence.ts';
36
+
37
+ type LifecycleStore = ReturnType<typeof createCodexLifecycleStore>;
38
+ type TaskRunWithPendingDelegation = NonNullable<ReturnType<typeof readRun>> & Readonly<{
39
+ pendingDelegation: NonNullable<NonNullable<ReturnType<typeof readRun>>['pendingDelegation']>;
40
+ }>;
41
+ type CodexBridgeOptions = Readonly<{
42
+ repoRoot?: string;
43
+ store?: LifecycleStore;
44
+ preflight?: typeof preflightCodexLifecycleEvidence;
45
+ resolveThread?: typeof resolveCodexThread;
46
+ resolveTerminal?: typeof resolveCodexTerminal;
47
+ buildIdentity?: LifecycleBuildIdentity;
48
+ orchestrationOptions?: OrchestrationOptions;
49
+ }>;
50
+
51
+ type CodexSpawnIdentity = Readonly<{
52
+ sessionId: string;
53
+ turnId: string;
54
+ toolUseId: string;
55
+ transcriptPath: string;
56
+ nativeAgent: string;
57
+ taskName: string;
58
+ requestedModel?: string;
59
+ requestedReasoningEffort?: string;
60
+ }>;
61
+
62
+ function coreOptions(options: CodexBridgeOptions): OrchestrationOptions {
63
+ return {
64
+ ...options.orchestrationOptions,
65
+ repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot,
66
+ taskId: options.orchestrationOptions?.taskId ?? options.store?.taskId ?? undefined
67
+ };
68
+ }
69
+
70
+ function taskRunMatchesReceipt(
71
+ run: ReturnType<typeof readRun>,
72
+ binding: CodexLifecycleTaskBinding,
73
+ nativeAgent: string
74
+ ): run is TaskRunWithPendingDelegation {
75
+ const receipt = run?.pendingDelegation;
76
+ const role = nativeAgent.endsWith('reviewer') ? 'reviewer' : 'executor';
77
+ return run?.status === 'running'
78
+ && receipt?.runId === binding.runId
79
+ && receipt.id === binding.receiptId
80
+ && receipt.client === 'codex'
81
+ && receipt.role === role;
82
+ }
83
+
84
+ function recordMatchesTaskReceipt(childThreadId: string, options: CodexBridgeOptions) {
85
+ const store = requiredStore(options);
86
+ let record: ReturnType<LifecycleStore['read']>;
87
+ try {
88
+ record = store.read(childThreadId);
89
+ } catch (error) {
90
+ throw new Error(`CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: ${error instanceof Error ? error.message : String(error)}`);
91
+ }
92
+ const binding = record.taskBinding;
93
+ if (!binding) throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: lifecycle record has no receipt binding');
94
+ const repoRoot = options.repoRoot ?? options.orchestrationOptions?.repoRoot ?? process.cwd();
95
+ const resolved = resolveTaskRef(binding.taskId, { repoRoot });
96
+ if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
97
+ const run = readRun(resolved.taskDir);
98
+ const nativeAgent = record.state.startEvidence?.nativeAgent ?? record.state.spawn?.nativeAgent ?? '';
99
+ if (!taskRunMatchesReceipt(run, binding, nativeAgent)) {
100
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: lifecycle record does not match the current pending task receipt');
101
+ }
102
+ const receipt = run.pendingDelegation;
103
+ return { store, record, binding, resolved, run, receipt };
104
+ }
105
+
106
+ function sameTaskReceipt(
107
+ left: Readonly<{ taskId: string; runId: string; id: string }>,
108
+ right: Readonly<{ taskId: string; runId: string; id: string }>
109
+ ): boolean {
110
+ return left.runId === right.runId && left.id === right.id;
111
+ }
112
+
113
+ function sameLifecycleRevision(
114
+ left: Readonly<{ revision: number; taskBinding: CodexLifecycleTaskBinding | null }>,
115
+ right: Readonly<{ revision: number; taskBinding: CodexLifecycleTaskBinding | null }>
116
+ ): boolean {
117
+ return left.revision === right.revision
118
+ && sameTaskBinding(left.taskBinding, right.taskBinding);
119
+ }
120
+
121
+ function sameTaskBinding(
122
+ left: CodexLifecycleTaskBinding | null,
123
+ right: CodexLifecycleTaskBinding | null
124
+ ): boolean {
125
+ return left?.runId === right?.runId
126
+ && left?.receiptId === right?.receiptId;
127
+ }
128
+
129
+ function isBindingFailure(message: string): boolean {
130
+ return /CODEX_LIFECYCLE_TASK_BINDING_MISMATCH|CODEX_EVIDENCE_(?:IDENTITY_MISMATCH|PARENT_MISMATCH|REPLAY_CONFLICT)|task binding|identity mismatch|ambiguous active children/iu.test(message);
131
+ }
132
+
133
+ function codexSealFailure(
134
+ error: unknown,
135
+ options: CodexBridgeOptions,
136
+ source: 'child' | 'parent'
137
+ ): OrchestrationResult {
138
+ if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ if (message === 'CODEX_TURN_NOT_TERMINAL') {
141
+ return source === 'parent'
142
+ ? bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'The matching Codex child has not completed')
143
+ : pauseBridge('ORCHESTRATION_CODEX_STOP_FAILED', message, options);
144
+ }
145
+ if (isBindingFailure(message)) return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', message);
146
+ return pauseBridge('ORCHESTRATION_CODEX_STOP_FAILED', message, options);
147
+ }
148
+
149
+ function sameBoundLifecycleRecord(
150
+ bound: ReturnType<typeof recordMatchesTaskReceipt>,
151
+ current: ReturnType<typeof recordMatchesTaskReceipt>
152
+ ): boolean {
153
+ return sameTaskReceipt(bound.receipt, current.receipt)
154
+ && sameLifecycleRevision(bound.record, current.record);
155
+ }
156
+
157
+ function candidateActivationState(
158
+ state: ReturnType<typeof reduceCodexLifecycleEvent>,
159
+ events: readonly CodexLifecycleEvent[]
160
+ ) {
161
+ return events.reduce((current, event) => reduceCodexLifecycleEvent(current, event), state);
162
+ }
163
+
164
+ function candidateActivationFailure(
165
+ state: ReturnType<typeof reduceCodexLifecycleEvent>,
166
+ options: CodexBridgeOptions
167
+ ): OrchestrationResult | null {
168
+ if (state.status === 'invalid') {
169
+ return bridgeFailure(state.error?.code ?? 'CODEX_EVIDENCE_IDENTITY_MISMATCH', state.error?.message ?? 'Codex lifecycle identity does not match');
170
+ }
171
+ if (state.status !== 'start-ready' || !state.startEvidence) {
172
+ return pauseBridge('ORCHESTRATION_CODEX_START_EVIDENCE_INVALID', 'Codex lifecycle start evidence is not ready', options);
173
+ }
174
+ return null;
175
+ }
176
+
177
+ async function collectActivationEvidence(
178
+ childThreadId: string,
179
+ options: CodexBridgeOptions,
180
+ bound: ReturnType<typeof recordMatchesTaskReceipt>
181
+ ): Promise<Readonly<{
182
+ preflight: Awaited<ReturnType<typeof preflightCodexLifecycleEvidence>>;
183
+ events: readonly CodexLifecycleEvent[];
184
+ evidence: NonNullable<ReturnType<typeof reduceCodexLifecycleEvent>['startEvidence']>;
185
+ }> | OrchestrationResult> {
186
+ const resolved = await (options.resolveThread ?? resolveCodexThread)(childThreadId);
187
+ const current = recordMatchesTaskReceipt(childThreadId, options);
188
+ if (!sameBoundLifecycleRecord(bound, current)) {
189
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'task receipt or lifecycle revision changed while resolving Codex child evidence');
190
+ }
191
+ const candidateEvents = [resolved.resolution.thread, ...resolved.reroutes, resolved.resolution.settings];
192
+ const candidateState = candidateActivationState(bound.record.state, candidateEvents);
193
+ const candidateFailure = candidateActivationFailure(candidateState, options);
194
+ if (candidateFailure) return candidateFailure;
195
+ const evidence = candidateState.startEvidence!;
196
+ const repoRoot = options.repoRoot ?? process.cwd();
197
+ const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot, {
198
+ sessionId: evidence.parentThreadId,
199
+ turnId: evidence.parentTurnId,
200
+ toolUseId: evidence.spawnToolUseId
201
+ });
202
+ const afterPreflight = recordMatchesTaskReceipt(childThreadId, options);
203
+ if (!sameBoundLifecycleRecord(bound, afterPreflight)) {
204
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'task receipt or lifecycle revision changed while validating Codex host evidence');
205
+ }
206
+ return { preflight, events: candidateEvents, evidence };
207
+ }
208
+
209
+ function applyCollectedActivationEvidence(
210
+ childThreadId: string,
211
+ options: CodexBridgeOptions,
212
+ store: LifecycleStore,
213
+ bound: ReturnType<typeof recordMatchesTaskReceipt>,
214
+ events: readonly CodexLifecycleEvent[]
215
+ ): ReturnType<LifecycleStore['read']> | OrchestrationResult {
216
+ const current = recordMatchesTaskReceipt(childThreadId, options);
217
+ if (!sameBoundLifecycleRecord(bound, current)) {
218
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'task receipt or lifecycle revision changed before Codex activation');
219
+ }
220
+ for (const event of events) store.apply(event);
221
+ const record = store.read(childThreadId);
222
+ return record.state.status === 'start-ready' && record.state.startEvidence
223
+ ? record
224
+ : bridgeFailure('CODEX_EVIDENCE_IDENTITY_MISMATCH', 'Codex lifecycle identity changed before activation');
225
+ }
226
+
227
+ function bridgeFailure(code: string, message: string): OrchestrationResult {
228
+ return {
229
+ status: 'failed', changed: false, taskId: null, run: null, next: null,
230
+ error: { code, message }
231
+ };
232
+ }
233
+
234
+ function pauseBridge(code: string, message: string, options: CodexBridgeOptions): OrchestrationResult {
235
+ const paused = pauseMatchingOrchestrationDelegation('codex', code, message, coreOptions(options));
236
+ return paused.error?.code === 'ORCHESTRATION_DELEGATION_MISSING'
237
+ ? bridgeFailure(code, message)
238
+ : paused;
239
+ }
240
+
241
+ function requiredStore(options: CodexBridgeOptions): LifecycleStore {
242
+ if (!options.store) throw new Error('Codex lifecycle store is required for orchestration bridge events');
243
+ return options.store;
244
+ }
245
+
246
+ async function prepareCodexOrchestrationDelegation(
247
+ taskRef: string,
248
+ input: Readonly<{
249
+ client: AgentClientId;
250
+ requestedModel?: string;
251
+ requestedReasoningEffort?: string;
252
+ }>,
253
+ options: CodexBridgeOptions = {}
254
+ ): Promise<OrchestrationResult> {
255
+ if (input.client !== 'codex') return prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
256
+ try {
257
+ const repoRoot = options.repoRoot ?? process.cwd();
258
+ const resolved = resolveTaskRef(taskRef, { repoRoot });
259
+ if (!resolved.ok) return bridgeFailure(resolved.code, resolved.message);
260
+ await (options.preflight ?? preflightCodexLifecycleEvidence)(
261
+ repoRoot,
262
+ undefined,
263
+ resolved.taskId
264
+ );
265
+ const prepared = prepareOrchestrationDelegation(taskRef, input, coreOptions(options));
266
+ const receipt = prepared.run?.pendingDelegation;
267
+ if (prepared.status !== 'running' || !receipt) return prepared;
268
+ return Object.freeze({
269
+ ...prepared,
270
+ adapterContext: encodeCodexLifecycleBinding({
271
+ taskId: receipt.taskId,
272
+ runId: receipt.runId,
273
+ receiptId: receipt.id
274
+ })
275
+ });
276
+ } catch (error) {
277
+ if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
278
+ return bridgeFailure('ORCHESTRATION_CLIENT_PREFLIGHT_FAILED', error instanceof Error ? error.message : String(error));
279
+ }
280
+ }
281
+
282
+ async function activateCodexOrchestrationDelegation(
283
+ childThreadId: string,
284
+ options: CodexBridgeOptions = {}
285
+ ): Promise<OrchestrationResult> {
286
+ try {
287
+ const store = requiredStore(options);
288
+ const bound = recordMatchesTaskReceipt(childThreadId, options);
289
+ if (!['prepared', 'activated', 'stage-completed'].includes(bound.receipt.status)) {
290
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'lifecycle task receipt is not in an activatable state');
291
+ }
292
+ if (!hasActivatableOrchestrationDelegation('codex', childThreadId, coreOptions(options))) {
293
+ return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
294
+ }
295
+ const activationEvidence = await collectActivationEvidence(childThreadId, options, bound);
296
+ if ('status' in activationEvidence) return activationEvidence;
297
+ const { evidence, events, preflight } = activationEvidence;
298
+ const repoRoot = options.repoRoot ?? process.cwd();
299
+ const record = applyCollectedActivationEvidence(childThreadId, options, store, bound, events);
300
+ if ('status' in record) return record;
301
+ const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
302
+ const storedAdapterEvidence = readDelegationAdapterEvidence(
303
+ bound.receipt.taskId, bound.receipt.id, 'codex', coreOptions(options)
304
+ );
305
+ const adapterState = typeof storedAdapterEvidence === 'object' && storedAdapterEvidence !== null && !Array.isArray(storedAdapterEvidence)
306
+ ? storedAdapterEvidence as Record<string, unknown>
307
+ : {};
308
+ const activated = activateMatchingOrchestrationDelegation('codex', {
309
+ nativeAgent: evidence.nativeAgent,
310
+ childId: evidence.childThreadId,
311
+ parentId: evidence.parentThreadId,
312
+ spawnMode: evidence.spawnMode,
313
+ actualModel: evidence.actualModel.value,
314
+ actualReasoningEffort: evidence.actualReasoningEffort.value,
315
+ ...(evidence.modelFallbackReason ? { modelFallbackReason: evidence.modelFallbackReason } : {}),
316
+ ...(evidence.reasoningEffortFallbackReason
317
+ ? { reasoningEffortFallbackReason: evidence.reasoningEffortFallbackReason }
318
+ : {}),
319
+ clientEvidence: {
320
+ ...adapterState,
321
+ activationEvidence: {
322
+ kind: 'codex-lifecycle-v2',
323
+ hookDefinitionHash: evidence.hookDefinitionHash,
324
+ startRevision: record.revision,
325
+ ...buildIdentity,
326
+ ...preflight.hookProvenance,
327
+ capabilitySessionId: evidence.parentThreadId,
328
+ capabilityTurnId: evidence.parentTurnId,
329
+ spawnToolUseId: evidence.spawnToolUseId,
330
+ spawnObservedAt: record.spawnObservedAt ?? undefined,
331
+ controllerInstanceDigest: null,
332
+ controlGeneration: null
333
+ }
334
+ }
335
+ }, coreOptions(options));
336
+ return activated;
337
+ } catch (error) {
338
+ if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
339
+ const message = error instanceof Error ? error.message : String(error);
340
+ if (isBindingFailure(message)) {
341
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', message);
342
+ }
343
+ return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', message, options);
344
+ }
345
+ }
346
+
347
+ function matchingSpawnRecords(
348
+ store: LifecycleStore,
349
+ binding: CodexLifecycleTaskBinding,
350
+ spawn: CodexSpawnIdentity
351
+ ) {
352
+ return store.findByTaskBinding(binding).filter((record) =>
353
+ record.state.spawn?.sessionId === spawn.sessionId
354
+ && record.state.spawn?.turnId === spawn.turnId
355
+ && record.state.spawn?.toolUseId === spawn.toolUseId
356
+ && record.state.spawn?.nativeAgent === spawn.nativeAgent
357
+ );
358
+ }
359
+
360
+ function verifySpawnTask(
361
+ binding: CodexLifecycleTaskBinding,
362
+ spawn: CodexSpawnIdentity,
363
+ store: LifecycleStore,
364
+ options: CodexBridgeOptions
365
+ ) {
366
+ const resolved = resolveTaskRef(binding.taskId, { repoRoot: options.repoRoot ?? process.cwd() });
367
+ if (!resolved.ok) return bridgeFailure(resolved.code, resolved.message);
368
+ const run = readRun(resolved.taskDir);
369
+ if (!run) return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'Codex task has no current orchestration run');
370
+ verifyCodexLifecycleTaskBinding(binding, run, spawn.nativeAgent, {
371
+ requestedModel: spawn.requestedModel,
372
+ requestedReasoningEffort: spawn.requestedReasoningEffort
373
+ });
374
+ return { taskDir: resolved.taskDir };
375
+ }
376
+
377
+ async function resolveCurrentSpawnChild(
378
+ childThreadId: string,
379
+ spawn: CodexSpawnIdentity,
380
+ options: CodexBridgeOptions
381
+ ) {
382
+ const resolved = await (options.resolveThread ?? resolveCodexThread)(childThreadId);
383
+ if (resolved.resolution.thread.childThreadId !== childThreadId
384
+ || resolved.resolution.thread.parentThreadId !== spawn.sessionId
385
+ || resolved.resolution.thread.nativeAgent !== spawn.nativeAgent) {
386
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'resolved Codex child does not match the native spawn identity');
387
+ }
388
+ return { resolved };
389
+ }
390
+
391
+ async function activateCodexSpawnDelegation(
392
+ spawn: CodexSpawnIdentity,
393
+ options: CodexBridgeOptions = {}
394
+ ): Promise<OrchestrationResult> {
395
+ try {
396
+ const store = requiredStore(options);
397
+ const parsed = parseCodexLifecycleBinding(spawn.taskName);
398
+ if (!parsed) return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISSING', 'Codex spawn task_name has no valid task binding');
399
+ const task = verifySpawnTask(parsed.binding, spawn, store, options);
400
+ if ('status' in task) return task;
401
+ const candidates = matchingSpawnRecords(store, parsed.binding, spawn);
402
+ if (candidates.length !== 1) {
403
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'Codex spawn does not identify one current task lifecycle record');
404
+ }
405
+ const childThreadId = resolveCodexSpawnedChild(spawn.transcriptPath, spawn);
406
+ const current = await resolveCurrentSpawnChild(
407
+ childThreadId, spawn, options
408
+ );
409
+ if ('status' in current) return current;
410
+ const latestTask = verifySpawnTask(parsed.binding, spawn, store, options);
411
+ if ('status' in latestTask) return latestTask;
412
+ const latestCandidates = matchingSpawnRecords(store, parsed.binding, spawn);
413
+ if (latestCandidates.length !== 1 || latestCandidates[0]!.revision !== candidates[0]!.revision) {
414
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'Codex spawn record changed while resolving its child');
415
+ }
416
+ store.applyToSpawn({ ...spawn, taskBinding: parsed.binding }, {
417
+ type: 'hook-child',
418
+ sessionId: spawn.sessionId,
419
+ turnId: spawn.turnId,
420
+ childThreadId,
421
+ parentThreadId: current.resolved.resolution.thread.parentThreadId,
422
+ nativeAgent: spawn.nativeAgent,
423
+ source: 'parent-rollout'
424
+ }, latestCandidates[0]!.revision);
425
+ return activateCodexOrchestrationDelegation(childThreadId, {
426
+ ...options,
427
+ store,
428
+ resolveThread: async () => current.resolved
429
+ });
430
+ } catch (error) {
431
+ if (error instanceof OrchestrationStateError) return bridgeFailure(error.code, error.message);
432
+ const message = error instanceof Error ? error.message : String(error);
433
+ if (/CODEX_LIFECYCLE_TASK_BINDING_MISMATCH|task binding|task_name|identity does not match|does not match the current pending receipt|ambiguous/iu.test(message)) {
434
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', message);
435
+ }
436
+ return pauseBridge('ORCHESTRATION_CODEX_START_FAILED', message, options);
437
+ }
438
+ }
439
+
440
+ async function sealCodexOrchestrationDelegation(
441
+ childThreadId: string,
442
+ options: CodexBridgeOptions = {}
443
+ ): Promise<OrchestrationResult> {
444
+ try {
445
+ const bound = recordMatchesTaskReceipt(childThreadId, options);
446
+ if (!hasSealableOrchestrationDelegation('codex', childThreadId, coreOptions(options))) {
447
+ return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
448
+ }
449
+ const store = requiredStore(options);
450
+ const existing = store.read(childThreadId);
451
+ if (!existing.consumer) {
452
+ const stopTurnId = existing.state.stop?.turnId;
453
+ if (!stopTurnId) {
454
+ return pauseBridge('ORCHESTRATION_CODEX_STOP_EVIDENCE_INVALID', 'Codex lifecycle stop hook is not available', options);
455
+ }
456
+ const terminal = await (options.resolveTerminal ?? resolveCodexTerminal)(childThreadId, stopTurnId);
457
+ const current = recordMatchesTaskReceipt(childThreadId, options);
458
+ if (!sameTaskReceipt(bound.receipt, current.receipt) || !sameLifecycleRevision(existing, current.record)) {
459
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'task receipt or lifecycle revision changed while resolving Codex terminal evidence');
460
+ }
461
+ store.apply(terminal);
462
+ }
463
+ const record = store.read(childThreadId);
464
+ const evidence = record.state.stopEvidence;
465
+ if (record.state.status !== 'stop-ready' || !evidence || !record.state.startEvidence) {
466
+ return pauseBridge('ORCHESTRATION_CODEX_STOP_EVIDENCE_INVALID', 'Codex lifecycle stop evidence is not ready', options);
467
+ }
468
+ const current = recordMatchesTaskReceipt(childThreadId, options);
469
+ const latest = store.read(childThreadId);
470
+ if (!sameTaskReceipt(bound.receipt, current.receipt) || !sameLifecycleRevision(record, latest)
471
+ || !sameTaskBinding(latest.taskBinding, current.binding)) {
472
+ throw new Error('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH: task receipt changed before lifecycle evidence consumption');
473
+ }
474
+ if (current.receipt.status === 'sealed') {
475
+ if (latest.consumer !== current.receipt.id || !latest.consumedAt) {
476
+ return pauseBridge('ORCHESTRATION_CODEX_RECONCILIATION_FAILED', 'Codex lifecycle evidence was not consumed by the sealed receipt', options);
477
+ }
478
+ return reconcileMatchingOrchestrationDelegation('codex', childThreadId, coreOptions(options));
479
+ }
480
+ return sealMatchingOrchestrationDelegation(
481
+ 'codex',
482
+ { nativeAgent: record.state.startEvidence.nativeAgent, childId: childThreadId },
483
+ coreOptions(options),
484
+ () => {
485
+ try {
486
+ const consumed = store.consumeWithinTaskLock(
487
+ childThreadId,
488
+ current.receipt.id,
489
+ existing.state.spawn?.hookDefinitionHash,
490
+ existing.taskBinding ?? undefined
491
+ );
492
+ return consumed.consumer === current.receipt.id && consumed.consumedAt
493
+ ? null
494
+ : bridgeFailure('ORCHESTRATION_CODEX_RECONCILIATION_FAILED', 'Codex lifecycle evidence was not consumed by the active receipt');
495
+ } catch (error) {
496
+ return codexSealFailure(error, options, 'child');
497
+ }
498
+ }
499
+ );
500
+ } catch (error) {
501
+ return codexSealFailure(error, options, 'child');
502
+ }
503
+ }
504
+
505
+ function findCurrentParentReceipt(store: LifecycleStore, repoRoot: string) {
506
+ if (!store.taskId) return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISSING', 'parent seal requires a task-scoped lifecycle store');
507
+ const resolved = resolveTaskRef(store.taskId, { repoRoot });
508
+ if (!resolved.ok) return bridgeFailure(resolved.code, resolved.message);
509
+ const pending = readRun(resolved.taskDir)?.pendingDelegation;
510
+ if (!pending || pending.client !== 'codex') {
511
+ return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
512
+ }
513
+ return { pending };
514
+ }
515
+
516
+ function findParentLifecycleRecord(
517
+ parentThreadId: string,
518
+ store: LifecycleStore,
519
+ pending: NonNullable<NonNullable<ReturnType<typeof readRun>>['pendingDelegation']>
520
+ ) {
521
+ const binding = { taskId: pending.taskId, runId: pending.runId, receiptId: pending.id };
522
+ const candidates = store.findByTaskBinding(binding).filter((record) =>
523
+ record.state.startEvidence?.parentThreadId === parentThreadId
524
+ );
525
+ if (!candidates.length) return bridgeFailure('ORCHESTRATION_DELEGATION_MISSING', 'No matching Codex delegation is active');
526
+ if (candidates.length !== 1) {
527
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'parent seal has multiple lifecycle records for the current task receipt');
528
+ }
529
+ const active = candidates[0]!;
530
+ const start = active.state.startEvidence;
531
+ const child = active.state.child;
532
+ if (!start || !child || start.childThreadId !== child.childThreadId
533
+ || start.parentThreadId !== parentThreadId
534
+ || active.state.spawn?.sessionId !== parentThreadId
535
+ || active.state.spawn.nativeAgent !== start.nativeAgent
536
+ || managedDelegationRole(start.nativeAgent) !== pending.role) {
537
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'parent lifecycle record does not match the current native delegation identity');
538
+ }
539
+ return { active, start, child };
540
+ }
541
+
542
+ async function collectParentTerminalIfNeeded(
543
+ active: ReturnType<LifecycleStore['read']>,
544
+ start: NonNullable<ReturnType<typeof reduceCodexLifecycleEvent>['startEvidence']>,
545
+ options: CodexBridgeOptions
546
+ ): Promise<Awaited<ReturnType<typeof resolveCodexTerminal>> | null> {
547
+ if (active.state.terminal) return null;
548
+ const resolveTerminal = options.resolveTerminal ?? resolveCodexTerminal;
549
+ return active.state.stop
550
+ ? resolveTerminal(start.childThreadId, active.state.stop.turnId)
551
+ : resolveTerminal(start.childThreadId);
552
+ }
553
+
554
+ function applyParentTerminalCandidate(
555
+ parentThreadId: string,
556
+ repoRoot: string,
557
+ store: LifecycleStore,
558
+ pending: NonNullable<NonNullable<ReturnType<typeof readRun>>['pendingDelegation']>,
559
+ active: ReturnType<LifecycleStore['read']>,
560
+ start: NonNullable<ReturnType<typeof reduceCodexLifecycleEvent>['startEvidence']>,
561
+ child: NonNullable<ReturnType<typeof reduceCodexLifecycleEvent>['child']>,
562
+ terminal: Awaited<ReturnType<typeof resolveCodexTerminal>> | null
563
+ ): OrchestrationResult | null {
564
+ const latestReceipt = findCurrentParentReceipt(store, repoRoot);
565
+ if ('status' in latestReceipt) return latestReceipt;
566
+ if (!sameTaskReceipt(pending, latestReceipt.pending)) {
567
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'task receipt changed while resolving parent terminal evidence');
568
+ }
569
+ const latestMatch = findParentLifecycleRecord(parentThreadId, store, latestReceipt.pending);
570
+ if ('status' in latestMatch) return latestMatch;
571
+ if (!sameLifecycleRevision(active, latestMatch.active)
572
+ || !sameTaskBinding(active.taskBinding, latestMatch.active.taskBinding)) {
573
+ return bridgeFailure('CODEX_LIFECYCLE_TASK_BINDING_MISMATCH', 'task receipt or lifecycle revision changed while resolving parent terminal evidence');
574
+ }
575
+ if (terminal) store.apply(terminal);
576
+ if (!store.read(start.childThreadId).state.stop) {
577
+ store.apply({
578
+ type: 'hook-stop', sessionId: parentThreadId, turnId: child.turnId,
579
+ childThreadId: start.childThreadId, nativeAgent: start.nativeAgent, source: 'parent-rollout'
580
+ });
581
+ }
582
+ return null;
583
+ }
584
+
585
+ async function sealCodexParentDelegation(
586
+ parentThreadId: string,
587
+ options: CodexBridgeOptions = {}
588
+ ): Promise<OrchestrationResult> {
589
+ try {
590
+ const store = requiredStore(options);
591
+ const repoRoot = options.repoRoot ?? options.orchestrationOptions?.repoRoot ?? process.cwd();
592
+ const current = findCurrentParentReceipt(store, repoRoot);
593
+ if ('status' in current) return current;
594
+ const pending = current.pending;
595
+ const match = findParentLifecycleRecord(parentThreadId, store, pending);
596
+ if ('status' in match) return match;
597
+ const { active, start, child } = match;
598
+ if (active.consumer) return sealCodexOrchestrationDelegation(start.childThreadId, options);
599
+ const terminal = await collectParentTerminalIfNeeded(active, start, options);
600
+ const failure = applyParentTerminalCandidate(parentThreadId, repoRoot, store, pending, active, start, child, terminal);
601
+ if (failure) return failure;
602
+ return sealCodexOrchestrationDelegation(start.childThreadId, options);
603
+ } catch (error) {
604
+ return codexSealFailure(error, options, 'parent');
605
+ }
606
+ }
607
+
608
+ function reconcileCodexOrchestrationDelegation(
609
+ childThreadId: string,
610
+ options: CodexBridgeOptions = {}
611
+ ): OrchestrationResult {
612
+ try {
613
+ const bound = recordMatchesTaskReceipt(childThreadId, options);
614
+ const record = requiredStore(options).read(childThreadId);
615
+ if (bound.receipt.status !== 'sealed' || record.consumer !== bound.receipt.id || !record.consumedAt) {
616
+ return pauseBridge('ORCHESTRATION_CODEX_RECONCILIATION_FAILED', 'Codex lifecycle evidence was not consumed by the sealed receipt', options);
617
+ }
618
+ } catch (error) {
619
+ return codexSealFailure(error, options, 'child');
620
+ }
621
+ return reconcileMatchingOrchestrationDelegation('codex', childThreadId, coreOptions(options));
622
+ }
623
+
624
+ export {
625
+ activateCodexOrchestrationDelegation,
626
+ activateCodexSpawnDelegation,
627
+ prepareCodexOrchestrationDelegation,
628
+ reconcileCodexOrchestrationDelegation,
629
+ sealCodexParentDelegation,
630
+ sealCodexOrchestrationDelegation
631
+ };
632
+ export type { CodexBridgeOptions };