@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.
- package/dist/bin/internal-cli.js +2 -2
- package/dist/lib/agent-clients/adapter.js +7 -1
- package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
- package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
- package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
- package/dist/lib/agent-clients/adapters/codex.js +40 -1
- package/dist/lib/agent-clients/adapters/index.js +2 -10
- package/dist/lib/agent-clients/tokens.js +3 -2
- package/dist/lib/internal/agent-client.js +132 -72
- package/dist/lib/internal/cli-route-inventory.js +2 -2
- package/dist/lib/internal/sandbox-control.js +1 -81
- package/dist/lib/internal/task-finalization.js +26 -19
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -1
- package/dist/lib/internal/task-validate.js +1 -1
- package/dist/lib/platform/github-client.js +33 -3
- package/dist/lib/platform/issues.js +18 -6
- package/dist/lib/runtime/agent-runtime.js +6 -29
- package/dist/lib/sandbox/commands/control-status.js +1 -0
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/ls.js +4 -5
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/control/agent-operation.js +34 -0
- package/dist/lib/sandbox/control/client.js +14 -201
- package/dist/lib/sandbox/control/executor.js +12 -125
- package/dist/lib/sandbox/control/lifecycle.js +66 -4
- package/dist/lib/sandbox/control/protocol.js +15 -54
- package/dist/lib/sandbox/control/server.js +216 -281
- package/dist/lib/sandbox/control/task-view.js +0 -13
- package/dist/lib/sandbox/mounts.js +9 -1
- package/dist/lib/sandbox/recovery.js +31 -10
- package/dist/lib/sandbox/removal.js +28 -5
- package/dist/lib/sandbox/workspace-identity.js +18 -6
- package/dist/lib/sandbox/workspace-view.js +56 -19
- package/dist/lib/task/commands/ls.js +3 -3
- package/dist/lib/task/control-authority.js +56 -264
- package/dist/lib/task/control-recovery.js +2 -3
- package/dist/lib/task/create.js +2 -2
- package/dist/lib/task/delegation-receipts.js +74 -161
- package/dist/lib/task/events.js +88 -44
- package/dist/lib/task/finalization.js +35 -6
- package/dist/lib/task/lifecycle.js +274 -115
- package/dist/lib/task/orchestration.js +201 -194
- package/dist/lib/task/runtime-paths.js +10 -0
- package/dist/lib/task/short-id.js +37 -27
- package/dist/lib/task/verification-engine.js +32 -26
- package/dist/lib/task/workspace-snapshot.js +1 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +39 -0
- package/lib/agent-clients/adapters/claude-code.ts +5 -1
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
- package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
- package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
- package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
- package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
- package/lib/agent-clients/adapters/codex.ts +44 -1
- package/lib/agent-clients/adapters/index.ts +7 -20
- package/lib/agent-clients/tokens.ts +4 -3
- package/lib/internal/agent-client.ts +142 -73
- package/lib/internal/cli-route-inventory.ts +2 -2
- package/lib/internal/sandbox-control.ts +0 -97
- package/lib/internal/task-finalization.ts +30 -18
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -1
- package/lib/internal/task-validate.ts +1 -1
- package/lib/platform/github-client.ts +34 -3
- package/lib/platform/issues.ts +18 -6
- package/lib/runtime/agent-runtime.ts +6 -41
- package/lib/sandbox/commands/control-status.ts +1 -0
- package/lib/sandbox/commands/create.ts +7 -1
- package/lib/sandbox/commands/ls.ts +4 -5
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/control/agent-operation.ts +49 -0
- package/lib/sandbox/control/client.ts +20 -254
- package/lib/sandbox/control/executor.ts +13 -136
- package/lib/sandbox/control/lifecycle.ts +67 -4
- package/lib/sandbox/control/protocol.ts +22 -66
- package/lib/sandbox/control/server.ts +21 -95
- package/lib/sandbox/control/task-view.ts +0 -19
- package/lib/sandbox/mounts.ts +10 -1
- package/lib/sandbox/recovery.ts +38 -17
- package/lib/sandbox/removal.ts +33 -7
- package/lib/sandbox/workspace-identity.ts +16 -6
- package/lib/sandbox/workspace-view.ts +50 -18
- package/lib/task/commands/ls.ts +3 -3
- package/lib/task/control-authority.ts +66 -378
- package/lib/task/control-recovery.ts +3 -4
- package/lib/task/create.ts +3 -3
- package/lib/task/delegation-receipts.ts +87 -231
- package/lib/task/events.ts +100 -42
- package/lib/task/finalization.ts +37 -7
- package/lib/task/lifecycle.ts +257 -109
- package/lib/task/orchestration.ts +260 -237
- package/lib/task/runtime-paths.ts +12 -0
- package/lib/task/short-id.ts +33 -40
- package/lib/task/verification-engine.ts +30 -25
- package/lib/task/workspace-snapshot.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/next-step-output.en.md +6 -19
- package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
- package/templates/.agents/rules/strategy-pattern.en.md +25 -0
- package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
- package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
- package/dist/lib/internal/codex-lifecycle.js +0 -385
- package/dist/lib/task/codex-orchestration.js +0 -206
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
- package/lib/internal/codex-lifecycle.ts +0 -390
- 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 };
|