@openclaw/codex 2026.9.1-beta.1 → 2026.9.1

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 (73) hide show
  1. package/dist/api.js +5 -6
  2. package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-DYHcRQAa.js} +31 -45
  3. package/dist/attempt-context-DmQUNWwT.js +634 -0
  4. package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
  5. package/dist/{binding-connection-BMvPX73o.js → binding-connection-E6uDsPlx.js} +2 -2
  6. package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-wzJrwKKe.js} +15 -20
  7. package/dist/{capabilities-BRMh0aIq.js → capabilities-dIjNVCYH.js} +1 -1
  8. package/dist/cli-metadata.js +1 -1
  9. package/dist/{command-formatters-DEsIufkt.js → command-formatters-CLHhEB1Q.js} +5 -4
  10. package/dist/{command-handlers-D_-1_qRp.js → command-handlers-CTGb2csu.js} +124 -101
  11. package/dist/{compact-BglNFZrQ.js → compact-_t0HQrUS.js} +38 -29
  12. package/dist/{config-giW8X0QA.js → config-BhmfC1s6.js} +294 -282
  13. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  14. package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
  15. package/dist/doctor-contract-api.js +143 -12
  16. package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BU9tmlYm.js} +599 -655
  17. package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-DzYBW4h1.js} +2 -2
  18. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-DHRFXKbS.js} +4 -4
  19. package/dist/{event-projector-usage-Bk1I90Bg.js → event-projector-usage-C5mcrGjp.js} +15 -148
  20. package/dist/{harness-Csz0F-DK.js → harness-DXVZ5II5.js} +42 -52
  21. package/dist/harness.js +1 -1
  22. package/dist/index.js +458 -404
  23. package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-kg9kv4iH.js} +4 -4
  24. package/dist/launch-args-RXQwn8zV.js +90 -0
  25. package/dist/{managed-binary-DFYK4DWq.js → managed-binary-4-Vjz5GH.js} +14 -9
  26. package/dist/media-understanding-provider.js +1 -1
  27. package/dist/model-catalog-CUM68Hkl.js +101 -0
  28. package/dist/{models-TS3HBU-q.js → models-sYnEc0gd.js} +25 -28
  29. package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-CnH147jH.js} +55 -115
  30. package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-CstNHCmC.js} +9 -6
  31. package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-CNe-Xrjx.js} +30 -3
  32. package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-CfFlKqDl.js} +15 -14
  33. package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BFrPhb2c.js} +6 -6
  34. package/dist/provider-capabilities-DW5p4k9w.js +101 -0
  35. package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-CawQJfUS.js} +3 -2
  36. package/dist/{sandbox-guard-kuWTn9C5.js → request-DYAI5FQE.js} +195 -2
  37. package/dist/{run-attempt-DYc0QCBF.js → run-attempt-D1hJO4fX.js} +828 -2100
  38. package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-C3jVimFn.js} +21 -14
  39. package/dist/{session-binding-Bcynfd-_.js → session-binding-BHuTCRZQ.js} +198 -127
  40. package/dist/{session-catalog-DaLKVtpH.js → session-catalog-yyvTWFOw.js} +564 -408
  41. package/dist/{session-cli-DMc4oS2w.js → session-cli-CmFEXMAM.js} +1 -1
  42. package/dist/session-history-C5J1AKzG.js +87 -0
  43. package/dist/session-initialization-BeWzjGiR.js +74 -0
  44. package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-DeWVTCMj.js} +2 -2
  45. package/dist/session-retirement-BujDjXVF.js +97 -0
  46. package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-C351-5Qw.js} +19 -4
  47. package/dist/settled-turn-context-D3SNWtUn.js +268 -0
  48. package/dist/settled-turn-finalizer-BVJx4yoc.js +104 -0
  49. package/dist/{shared-client-DKz23hHY.js → shared-client-DxcfF9KX.js} +3480 -679
  50. package/dist/{side-question-DjA_jsNe.js → side-question-BTZAII4l.js} +108 -70
  51. package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DAcEJ_Om.js} +2188 -1915
  52. package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-zGokd8i1.js} +25 -14
  53. package/dist/thread-policy-g4KbJPzp.js +54 -0
  54. package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-C7pyVKeW.js} +59 -7
  55. package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-DTUxFWa_.js} +79 -17
  56. package/dist/transport-stdio-CyPldczm.js +726 -0
  57. package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
  58. package/dist/upstream-session-fork-CYlBIGVt.js +966 -0
  59. package/dist/{usage-ROIaW3wo.js → usage-Bj-rrzkR.js} +3 -3
  60. package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-D2FWSK4w.js} +3 -3
  61. package/openclaw.plugin.json +1 -30
  62. package/package.json +6 -6
  63. package/dist/command-rpc-CCeCkf_v.js +0 -90
  64. package/dist/incognito-session-ET_ufDH-.js +0 -2
  65. package/dist/model-catalog-i9rNBthX.js +0 -48
  66. package/dist/protocol-validators-VeJxxJ0U.js +0 -2421
  67. package/dist/request-DXXGHko0.js +0 -157
  68. package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
  69. package/dist/session-retirement-CcNC3YSY.js +0 -36
  70. package/dist/settled-turn-finalizer-Dc3ApjU4.js +0 -328
  71. package/dist/thread-resume-BE9qNEW0.js +0 -30
  72. package/dist/transport-stdio-DhoGy33Z.js +0 -99
  73. package/dist/upstream-session-fork-DlaHxSLC.js +0 -326
@@ -0,0 +1,966 @@
1
+ import { $t as isJsonObject, Jt as readCodexSessionMeta, Q as assertCodexThreadForkResponse, Qt as flattenCodexDynamicToolFunctions, X as assertCodexThreadAcceptsDirectInput, f as readCodexAppServerClientDesktopGenerationFingerprint, wt as hasCodexAppServerLiveThread, xt as claimCodexAppServerLiveThread } from "./shared-client-DxcfF9KX.js";
2
+ import { E as readCodexPluginConfig, O as resolveOpenClawExecPolicyForCodexAppServer, p as assertCodexModelBackedReviewerEffectiveConfig, s as resolveCodexSupervisionAppServerRuntimeOptions } from "./config-BhmfC1s6.js";
3
+ import { d as sessionBindingIdentity } from "./session-binding-BHuTCRZQ.js";
4
+ import { i as buildCodexPluginAppCacheKey } from "./thread-resume-C7pyVKeW.js";
5
+ import { $ as resolveCodexNativeSkillIsolation, C as buildDeveloperInstructions, Ct as buildCodexPluginThreadConfig, Dt as mergeCodexThreadConfigs, F as assertCodexNativeHookRelayAllowed, I as buildCodexNativeHookRelayConfig, Ot as shouldBuildCodexPluginThreadConfig, Q as applyCodexNativeSkillIsolation, R as buildCodexNativeHookRelayId, S as resolveCodexWebSearchPlan, V as resolveCodexNativeHookRelayEvents, W as attestCodexPluginThreadApps, Z as parseCodexNativeToolCatalog, it as fingerprintUserMcpServersConfigPatch, rt as fingerprintJsonObject, tt as codexDynamicToolsFingerprint, y as buildCodexThreadConfiguration } from "./thread-lifecycle-DAcEJ_Om.js";
6
+ import { o as withCodexAppServerThreadMutation } from "./thread-ownership-zGokd8i1.js";
7
+ import { c as fingerprintCodexMirrorSourceMessage, m as projectCodexUserItemText, u as readCodexMirrorSourceFingerprint } from "./transcript-mirror-DTUxFWa_.js";
8
+ import { l as resolveCodexNativeExecutionPolicy, t as CodexAppServerScopedRequestRejectedError } from "./request-DYAI5FQE.js";
9
+ import { i as refreshCodexThreadPolicy, n as CodexThreadPolicyHandoffError } from "./thread-policy-g4KbJPzp.js";
10
+ import { i as readUpstreamUserText, r as readMirrorIdentity } from "./upstream-prompt-provenance-LphB8slG.js";
11
+ import { n as prepareCodexSessionInitialization } from "./session-initialization-BeWzjGiR.js";
12
+ import { i as createImportedCodexSession, n as codexUpstreamBaseline, t as codexLastTerminalTurnId } from "./session-upstream-marker-C351-5Qw.js";
13
+ import { l as isCodexDynamicToolExcluded, n as resolveCodexProviderWebSearchSupportForClient } from "./provider-capabilities-DW5p4k9w.js";
14
+ import { o as prepareCodexWorkspaceDeveloperInstructions } from "./attempt-context-DmQUNWwT.js";
15
+ import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
16
+ import { lstat } from "node:fs/promises";
17
+ import path from "node:path";
18
+ import { loadCodexBundleMcpThreadConfig } from "openclaw/plugin-sdk/agent-harness-runtime";
19
+ import { constants, createZstdDecompress } from "node:zlib";
20
+ import { root } from "openclaw/plugin-sdk/file-access-runtime";
21
+ import { randomUUID } from "node:crypto";
22
+ import { isDeepStrictEqual } from "node:util";
23
+ import { resolveAgentWorkspaceDir } from "openclaw/plugin-sdk/agent-runtime";
24
+ import { loadExecApprovals } from "openclaw/plugin-sdk/exec-approvals-runtime";
25
+ import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
26
+ import { buildCodexUserMcpServersThreadConfigPatchForRuntime, resolveCodexMcpToolOverridesForAgent } from "openclaw/plugin-sdk/codex-mcp-projection";
27
+ import { buildNativeHookRelayCommandPlan } from "openclaw/plugin-sdk/native-hook-relay-runtime";
28
+ import { appendSessionTranscriptMessagesByIdentity, readVisibleSessionTranscriptMessageEntries } from "openclaw/plugin-sdk/session-transcript-runtime";
29
+ //#region extensions/codex/src/session-rollout-selection.ts
30
+ const CHUNK_BYTES = 65536;
31
+ const RECORD_BYTES = 1048576;
32
+ const SCAN_BYTES = 8388608;
33
+ const DECOMPRESSED_BYTES = 33554432;
34
+ const DEADLINE_MS = 5e3;
35
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
36
+ function scalar(value) {
37
+ if (typeof value !== "string" || !value.trim() || Buffer.byteLength(value) > 256) throw new Error("Codex rollout has an invalid model or provider selection");
38
+ return value;
39
+ }
40
+ function record(line) {
41
+ if (!line.length || line.length > RECORD_BYTES) throw new Error("Codex rollout record exceeds the observation limit");
42
+ const parsed = JSON.parse(utf8Decoder.decode(line));
43
+ if (!isRecord(parsed) || typeof parsed.type !== "string" || !isRecord(parsed.payload)) throw new Error("Codex rollout contains an incomplete record");
44
+ return {
45
+ type: parsed.type,
46
+ payload: parsed.payload
47
+ };
48
+ }
49
+ /** Observe settings only; rollback changes history, never the native scalar selection. */
50
+ function observation(parsed, threadId) {
51
+ const payload = parsed.payload;
52
+ if (parsed.type === "session_meta") {
53
+ if (typeof payload.id !== "string") throw new Error("Codex rollout metadata is missing its thread identity");
54
+ return payload.id === threadId ? { provider: scalar(payload.model_provider) } : {};
55
+ }
56
+ if (parsed.type === "turn_context") return { model: scalar(payload.model) };
57
+ if (parsed.type === "event_msg" && payload.type === "thread_settings_applied") {
58
+ if (!isRecord(payload.thread_settings)) throw new Error("Codex rollout settings are incomplete");
59
+ return {
60
+ model: scalar(payload.thread_settings.model),
61
+ provider: scalar(payload.thread_settings.model_provider_id)
62
+ };
63
+ }
64
+ return {};
65
+ }
66
+ async function readExtent(handle, start, length) {
67
+ const buffer = Buffer.alloc(length);
68
+ for (let offset = 0; offset < length;) {
69
+ const { bytesRead } = await handle.read(buffer, offset, length - offset, start + offset);
70
+ if (!bytesRead) throw new Error("Codex rollout changed during model observation");
71
+ offset += bytesRead;
72
+ }
73
+ return buffer;
74
+ }
75
+ /** Latest durable selection at one verified local rollout snapshot, not live-memory state. */
76
+ async function readCodexRolloutSelection(params) {
77
+ const deadline = Date.now() + DEADLINE_MS;
78
+ const check = () => {
79
+ params.assertCurrent();
80
+ if (Date.now() >= deadline) throw new Error("Codex rollout model observation timed out");
81
+ };
82
+ check();
83
+ const rootStat = await lstat(params.sessionsRoot);
84
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error("Codex rollout root is not a verified local directory");
85
+ const safeRoot = await root(params.sessionsRoot, {
86
+ hardlinks: "reject",
87
+ symlinks: "reject",
88
+ maxBytes: Number.MAX_SAFE_INTEGER
89
+ });
90
+ const plain = params.rolloutPath.endsWith(".zst") ? params.rolloutPath.slice(0, -4) : params.rolloutPath;
91
+ let selected = plain;
92
+ let opened;
93
+ try {
94
+ opened = await safeRoot.open(path.relative(params.sessionsRoot, selected));
95
+ } catch (error) {
96
+ if (!isRecord(error) || error.code !== "not-found" && error.code !== "ENOENT") throw error;
97
+ selected = `${plain}.zst`;
98
+ opened = await safeRoot.open(path.relative(params.sessionsRoot, selected));
99
+ }
100
+ try {
101
+ check();
102
+ const snapshot = opened.stat;
103
+ let metadata;
104
+ let model;
105
+ let provider;
106
+ const header = (parsed) => {
107
+ if (parsed.type !== "session_meta" || parsed.payload.id !== params.threadId) throw new Error("Codex rollout does not belong to the bound thread");
108
+ scalar(parsed.payload.model_provider);
109
+ metadata = parsed.payload;
110
+ };
111
+ if (selected.endsWith(".zst")) {
112
+ if (snapshot.size === 0 || snapshot.size > SCAN_BYTES) throw new Error("Compressed Codex rollout exceeds the observation limit");
113
+ const input = opened.handle.createReadStream({
114
+ autoClose: false,
115
+ highWaterMark: CHUNK_BYTES,
116
+ end: snapshot.size - 1
117
+ });
118
+ const decoder = createZstdDecompress({
119
+ chunkSize: CHUNK_BYTES,
120
+ params: { [constants.ZSTD_d_windowLogMax]: 25 }
121
+ });
122
+ const timer = setTimeout(() => decoder.destroy(/* @__PURE__ */ new Error("Codex rollout observation timed out")), Math.max(1, deadline - Date.now()));
123
+ input.on("error", (error) => decoder.destroy(error));
124
+ const forward = input.pipe(decoder);
125
+ let tail = Buffer.alloc(0);
126
+ let total = 0;
127
+ try {
128
+ for await (const chunk of forward) {
129
+ check();
130
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
131
+ total += bytes.length;
132
+ if (total > DECOMPRESSED_BYTES) throw new Error("Decompressed Codex rollout exceeds the observation limit");
133
+ tail = Buffer.concat([tail, bytes]);
134
+ let newline;
135
+ while ((newline = tail.indexOf(10)) >= 0) {
136
+ const parsed = record(tail.subarray(0, newline));
137
+ if (!metadata) header(parsed);
138
+ const value = observation(parsed, params.threadId);
139
+ model = value.model ?? model;
140
+ provider = value.provider ?? provider;
141
+ tail = tail.subarray(newline + 1);
142
+ }
143
+ if (tail.length > RECORD_BYTES) throw new Error("Codex rollout record exceeds the observation limit");
144
+ }
145
+ if (tail.length) throw new Error("Codex rollout has an incomplete trailing record");
146
+ } finally {
147
+ clearTimeout(timer);
148
+ if (!input.readableEnded) input.destroy();
149
+ decoder.destroy();
150
+ }
151
+ } else {
152
+ let first = Buffer.alloc(0);
153
+ for (let offset = 0; offset < snapshot.size && first.length <= RECORD_BYTES;) {
154
+ check();
155
+ const bytes = await readExtent(opened.handle, offset, Math.min(CHUNK_BYTES, snapshot.size - offset));
156
+ offset += bytes.length;
157
+ first = Buffer.concat([first, bytes]);
158
+ const newline = first.indexOf(10);
159
+ if (newline >= 0) {
160
+ header(record(first.subarray(0, newline)));
161
+ break;
162
+ }
163
+ }
164
+ if (!metadata) throw new Error("Codex rollout metadata is incomplete or oversized");
165
+ let end = snapshot.size;
166
+ let scanned = 0;
167
+ let tail = Buffer.alloc(0);
168
+ while (end > 0 && (!model || !provider)) {
169
+ check();
170
+ const length = Math.min(CHUNK_BYTES, end, SCAN_BYTES - scanned);
171
+ if (!length) throw new Error("Codex rollout model observation exhausted its scan budget");
172
+ end -= length;
173
+ const bytes = await readExtent(opened.handle, end, length);
174
+ if (!scanned) {
175
+ if (bytes.at(-1) !== 10) throw new Error("Codex rollout has an incomplete trailing record");
176
+ tail = bytes.subarray(0, -1);
177
+ } else tail = Buffer.concat([bytes, tail]);
178
+ scanned += length;
179
+ for (;;) {
180
+ const newline = tail.lastIndexOf(10);
181
+ if (newline < 0 && (end !== 0 || tail.length === 0)) break;
182
+ const value = observation(record(tail.subarray(newline + 1)), params.threadId);
183
+ model ??= value.model;
184
+ provider ??= value.provider;
185
+ tail = newline < 0 ? Buffer.alloc(0) : tail.subarray(0, newline);
186
+ if (model && provider) break;
187
+ }
188
+ if (tail.length > RECORD_BYTES) throw new Error("Codex rollout record exceeds the observation limit");
189
+ }
190
+ }
191
+ const assertUnchanged = async () => {
192
+ params.assertCurrent();
193
+ if (selected !== plain) {
194
+ try {
195
+ await lstat(plain);
196
+ } catch (error) {
197
+ if (!isRecord(error) || error.code !== "ENOENT") throw error;
198
+ return await verifySelected();
199
+ }
200
+ throw new Error("Codex rollout selection changed during model observation");
201
+ }
202
+ await verifySelected();
203
+ };
204
+ const verifySelected = async () => {
205
+ params.assertCurrent();
206
+ const currentRoot = await lstat(params.sessionsRoot);
207
+ const fresh = await safeRoot.open(path.relative(params.sessionsRoot, selected));
208
+ try {
209
+ const stat = fresh.stat;
210
+ if (stat.dev !== snapshot.dev || stat.ino !== snapshot.ino || stat.size !== snapshot.size || stat.mtimeMs !== snapshot.mtimeMs || stat.ctimeMs !== snapshot.ctimeMs || stat.nlink !== 1 || currentRoot.isSymbolicLink() || currentRoot.ino !== rootStat.ino || currentRoot.dev !== rootStat.dev) throw new Error("Codex rollout changed during model observation");
211
+ } finally {
212
+ await fresh.handle.close();
213
+ }
214
+ params.assertCurrent();
215
+ };
216
+ const descriptor = await opened.handle.stat();
217
+ if (descriptor.dev !== snapshot.dev || descriptor.ino !== snapshot.ino || descriptor.size !== snapshot.size || descriptor.mtimeMs !== snapshot.mtimeMs || descriptor.ctimeMs !== snapshot.ctimeMs || descriptor.nlink !== 1) throw new Error("Codex rollout changed during model observation");
218
+ await assertUnchanged();
219
+ check();
220
+ if (!model || !provider || !metadata) throw new Error("Codex rollout has no complete durable model selection");
221
+ return {
222
+ model,
223
+ modelProvider: provider,
224
+ metadata,
225
+ assertUnchanged
226
+ };
227
+ } finally {
228
+ await opened.handle.close();
229
+ }
230
+ }
231
+ //#endregion
232
+ //#region extensions/codex/src/app-server/canonical-fork-preparation.ts
233
+ /** Creation preserves native declarations; admitted turns own all live executors. */
234
+ async function prepareCanonicalCodexFork(params) {
235
+ const { created, initialization, config, context } = params;
236
+ const assertCurrent = initialization.assertCurrent;
237
+ assertCurrent();
238
+ if (!initialization.prepareNativeToolPolicy) throw new Error("Canonical Codex forks require host native tool policy preparation. Update the Gateway before retrying.");
239
+ const workspaceDir = resolveAgentWorkspaceDir(config, created.agentId);
240
+ const cwd = created.entry.spawnedCwd ?? workspaceDir;
241
+ const execution = resolveCodexNativeExecutionPolicy({
242
+ config,
243
+ agentId: created.agentId,
244
+ sessionKey: created.key,
245
+ sessionEntry: created.entry
246
+ });
247
+ if (params.sandbox === "required" || !execution.nativeToolSurfaceAllowed || execution.effectiveExecHost !== "gateway" || context.appServer.remoteWorkspaceRoot) throw new Error("This child requires an execution environment that cannot be prepared during a native fork. Fork an original imported message instead.");
248
+ const pluginConfig = readCodexPluginConfig(context.pluginConfig);
249
+ const appServer = {
250
+ ...resolveCodexSupervisionAppServerRuntimeOptions({
251
+ pluginConfig,
252
+ config,
253
+ agentDir: context.agentDir,
254
+ model: params.model,
255
+ modelProvider: params.modelProvider,
256
+ execPolicy: resolveOpenClawExecPolicyForCodexAppServer({
257
+ config,
258
+ agentId: created.agentId,
259
+ approvals: loadExecApprovals()
260
+ })
261
+ }),
262
+ start: context.appServer.start
263
+ };
264
+ const { webSearchAllowed: hostWebSearchAllowed } = await initialization.prepareNativeToolPolicy({
265
+ provider: params.modelProvider,
266
+ runtimeProvider: "codex",
267
+ id: params.model
268
+ });
269
+ assertCurrent();
270
+ const webSearchAllowed = hostWebSearchAllowed && !isCodexDynamicToolExcluded(pluginConfig, ["web_search"]);
271
+ const dynamicTools = params.dynamicTools;
272
+ const nativeProviderWebSearchSupport = await resolveCodexProviderWebSearchSupportForClient({
273
+ client: context.client,
274
+ timeoutMs: appServer.requestTimeoutMs,
275
+ modelProviderOverride: params.modelProvider,
276
+ signal: AbortSignal.timeout(appServer.requestTimeoutMs)
277
+ });
278
+ assertCurrent();
279
+ const webSearch = resolveCodexWebSearchPlan({
280
+ config,
281
+ nativeToolSurfaceEnabled: true,
282
+ nativeProviderWebSearchSupport,
283
+ webSearchAllowed
284
+ });
285
+ const toolOverrides = resolveCodexMcpToolOverridesForAgent(config, {
286
+ agentId: created.agentId,
287
+ toolOverrides: created.entry.toolOverrides
288
+ });
289
+ const bundleMcp = await loadCodexBundleMcpThreadConfig({
290
+ workspaceDir,
291
+ agentId: created.agentId,
292
+ cfg: config,
293
+ toolOverrides,
294
+ preparationOnly: true
295
+ });
296
+ assertCurrent();
297
+ if (bundleMcp.diagnostics.length) throw new Error("The child's MCP configuration could not be prepared completely.");
298
+ const userMcp = await buildCodexUserMcpServersThreadConfigPatchForRuntime(config, {
299
+ agentId: created.agentId,
300
+ agentDir: context.agentDir,
301
+ toolOverrides,
302
+ preparationOnly: true
303
+ });
304
+ assertCurrent();
305
+ const apps = shouldBuildCodexPluginThreadConfig(pluginConfig) ? await buildCodexPluginThreadConfig({
306
+ pluginConfig,
307
+ configCwd: cwd,
308
+ appCacheKey: buildCodexPluginAppCacheKey({
309
+ appServer,
310
+ agentDir: context.agentDir,
311
+ runtimeIdentity: context.client.getRuntimeIdentity(),
312
+ desktopGenerationFingerprint: readCodexAppServerClientDesktopGenerationFingerprint(context.client)
313
+ }),
314
+ request: async (method, requestParams) => {
315
+ assertCurrent();
316
+ if (![
317
+ "plugin/list",
318
+ "plugin/read",
319
+ "plugin/installed",
320
+ "app/list",
321
+ "app/read",
322
+ "app/installed",
323
+ "config/read",
324
+ "configRequirements/read"
325
+ ].includes(method)) throw new Error("Codex plugin setup is required before native fork preparation.");
326
+ const response = await context.client.request(method, requestParams);
327
+ assertCurrent();
328
+ return response;
329
+ }
330
+ }) : void 0;
331
+ assertCurrent();
332
+ if (apps?.diagnostics.length) throw new Error("Codex app policy is not ready for a native fork. Complete plugin setup and retry.");
333
+ const nativeSkillIsolation = await resolveCodexNativeSkillIsolation({
334
+ client: context.client,
335
+ cwd,
336
+ codexHome: appServer.start.env?.CODEX_HOME,
337
+ home: appServer.start.env?.HOME,
338
+ userProfile: appServer.start.env?.USERPROFILE
339
+ });
340
+ assertCurrent();
341
+ const generation = randomUUID();
342
+ const relay = buildNativeHookRelayCommandPlan({
343
+ provider: "codex",
344
+ agentId: created.agentId,
345
+ sessionKey: created.key,
346
+ config,
347
+ relayId: buildCodexNativeHookRelayId({
348
+ agentId: created.agentId,
349
+ sessionKey: created.key,
350
+ sessionId: created.sessionId
351
+ }),
352
+ generation,
353
+ preToolUseLoopDetection: appServer.loopDetectionPreToolUseRelay
354
+ });
355
+ const events = resolveCodexNativeHookRelayEvents({ appServer });
356
+ if (events.includes("pre_tool_use") && relay.shouldRelayEvent("pre_tool_use")) {
357
+ await assertCodexNativeHookRelayAllowed(context.client);
358
+ assertCurrent();
359
+ }
360
+ await assertCodexModelBackedReviewerEffectiveConfig({
361
+ client: context.client,
362
+ approvalsReviewer: appServer.approvalsReviewer,
363
+ cwd
364
+ });
365
+ assertCurrent();
366
+ const workspaceInstructions = await prepareCodexWorkspaceDeveloperInstructions({
367
+ config,
368
+ agentId: created.agentId,
369
+ sessionKey: created.key,
370
+ sessionId: created.sessionId,
371
+ workspaceDir,
372
+ cwd
373
+ });
374
+ assertCurrent();
375
+ const promptContext = {
376
+ config,
377
+ agentId: created.agentId,
378
+ sessionKey: created.key,
379
+ modelId: params.model
380
+ };
381
+ const developerInstructions = [buildDeveloperInstructions(promptContext, { dynamicTools }), workspaceInstructions].filter(Boolean).join("\n\n");
382
+ const bundleConfig = bundleMcp.configPatch;
383
+ if (bundleConfig && !isJsonObject(bundleConfig)) throw new Error("Invalid child MCP thread configuration");
384
+ const threadConfig = applyCodexNativeSkillIsolation(mergeCodexThreadConfigs(bundleConfig, userMcp, apps?.configPatch, appServer.networkProxy?.configPatch, buildCodexNativeHookRelayConfig({
385
+ relay,
386
+ events,
387
+ clearOmittedEvents: true
388
+ })), nativeSkillIsolation);
389
+ return {
390
+ request: buildCodexThreadConfiguration(promptContext, {
391
+ cwd,
392
+ appServer,
393
+ dynamicTools,
394
+ developerInstructions,
395
+ config: threadConfig,
396
+ nativeCodeModeEnabled: true,
397
+ nativeCodeModeOnlyEnabled: appServer.codeModeOnly,
398
+ nativeProviderWebSearchSupport,
399
+ webSearchAllowed,
400
+ hostSystemAgentActive: false
401
+ }),
402
+ provisionalAppIds: apps?.provisionalAppIds ?? [],
403
+ bindingPolicy: {
404
+ nativeHookRelayGeneration: generation,
405
+ agentWorkspaceDeveloperInstructions: workspaceInstructions,
406
+ networkProxyProfileName: appServer.networkProxy?.profileName,
407
+ networkProxyConfigFingerprint: appServer.networkProxy?.configFingerprint,
408
+ nativeSkillIsolationFingerprint: nativeSkillIsolation ? fingerprintJsonObject({
409
+ version: 1,
410
+ disabledUserSkillPaths: nativeSkillIsolation.disabledUserSkillPaths
411
+ }) : void 0,
412
+ userMcpServersFingerprint: fingerprintUserMcpServersConfigPatch(userMcp),
413
+ mcpServersFingerprint: bundleMcp.fingerprint,
414
+ webSearchThreadConfigFingerprint: fingerprintJsonObject(webSearch.threadConfig),
415
+ pluginAppsFingerprint: apps?.fingerprint,
416
+ pluginAppsInputFingerprint: apps?.inputFingerprint,
417
+ pluginAppPolicyContext: apps?.policyContext
418
+ }
419
+ };
420
+ }
421
+ //#endregion
422
+ //#region extensions/codex/src/app-server/upstream-fork-boundary.ts
423
+ const TURN_PAGE_LIMIT = 100;
424
+ function failure(code, message) {
425
+ return {
426
+ ok: false,
427
+ code,
428
+ message
429
+ };
430
+ }
431
+ function textOnlyMessage(content) {
432
+ if (typeof content === "string") return content;
433
+ if (!Array.isArray(content)) return;
434
+ const texts = [];
435
+ for (const block of content) {
436
+ if (!block || typeof block !== "object" || Array.isArray(block)) return;
437
+ const typed = block;
438
+ if (typed.type !== "text" || typeof typed.text !== "string") return;
439
+ texts.push(typed.text);
440
+ }
441
+ return texts.join("\n");
442
+ }
443
+ function resolveCodexUpstreamForkBoundaryFromTurns(params) {
444
+ let localIndex = 0;
445
+ let matchedPrefix = false;
446
+ for (const [turnIndex, turn] of params.turns.entries()) {
447
+ let userMessagesInTurn = 0;
448
+ for (const item of turn.items) {
449
+ if (item.type !== "userMessage") continue;
450
+ const isSteer = userMessagesInTurn > 0;
451
+ userMessagesInTurn += 1;
452
+ const nativeText = textOnlyMessage(item.content);
453
+ if (nativeText === void 0) return failure("drift-mismatch", "A message before the fork point contains images or attachments that cannot be verified across OpenClaw and Codex. Fork from a text-only span instead.");
454
+ const local = params.localPrefix[localIndex];
455
+ const upstreamText = local && readUpstreamUserText(local.message);
456
+ const text = upstreamText ? nativeText : projectCodexUserItemText(item);
457
+ if (!text) continue;
458
+ const identity = local && readMirrorIdentity(local.message);
459
+ const matchesIdentity = identity === `${turn.id}:${item.id}` || !isSteer && identity === `${turn.id}:prompt`;
460
+ if (!matchedPrefix && !matchesIdentity) continue;
461
+ matchedPrefix = true;
462
+ const localText = textOnlyMessage(local && "content" in local.message ? local.message.content : void 0);
463
+ const upstreamPromptVerified = !upstreamText || local?.message.role === "user" && readCodexMirrorSourceFingerprint(local.message) === fingerprintCodexMirrorSourceMessage(local.message);
464
+ if (!matchesIdentity || !upstreamPromptVerified || localText === void 0 || text !== (upstreamText ?? localText)) return failure("drift-mismatch", "The local conversation no longer matches the Codex thread. Refresh the session and try again.");
465
+ if (localIndex < params.localPrefix.length - 1) {
466
+ localIndex += 1;
467
+ continue;
468
+ }
469
+ if (isSteer) return failure("steer-message", "This message steered an existing Codex turn and cannot be forked independently. Fork from the turn's first message instead.");
470
+ if (turn.status === "inProgress") return failure("in-progress-turn", "This Codex turn is still in progress. Wait for it to finish, then try forking again.");
471
+ const retained = turnIndex > 0 ? params.turns[turnIndex - 1] : void 0;
472
+ return {
473
+ ok: true,
474
+ boundary: {
475
+ beforeTurnId: turn.id,
476
+ lastRetainedTurnId: retained?.id ?? null
477
+ }
478
+ };
479
+ }
480
+ }
481
+ return failure("drift-mismatch", "The local history has no verified boundary in this Codex thread. Use native Codex to fork this conversation.");
482
+ }
483
+ async function listCodexUpstreamTurns(control, threadId) {
484
+ const turns = [];
485
+ const seenCursors = /* @__PURE__ */ new Set();
486
+ let cursor;
487
+ for (;;) {
488
+ const page = await control.listTurnPage({
489
+ threadId,
490
+ limit: TURN_PAGE_LIMIT,
491
+ sortDirection: "asc",
492
+ itemsView: "full",
493
+ ...cursor ? { cursor } : {}
494
+ });
495
+ turns.push(...page.data);
496
+ const nextCursor = page.nextCursor?.trim() || void 0;
497
+ if (!nextCursor) return turns;
498
+ if (seenCursors.has(nextCursor)) throw new Error("Codex returned a repeated thread/turns/list cursor");
499
+ seenCursors.add(nextCursor);
500
+ cursor = nextCursor;
501
+ }
502
+ }
503
+ async function resolveCodexUpstreamForkBoundary(params) {
504
+ try {
505
+ const entries = await readVisibleSessionTranscriptMessageEntries({
506
+ agentId: params.agentId,
507
+ sessionId: params.sessionId,
508
+ sessionKey: params.sessionKey,
509
+ storePath: params.storePath
510
+ });
511
+ const visibleUserEntries = entries.filter((entry) => entry.role === "user");
512
+ const targetIndex = visibleUserEntries.findIndex((entry) => entry.entryId === params.entryId);
513
+ if (targetIndex < 0) return failure("drift-mismatch", "The local message could not be mapped to the Codex thread. Refresh the session and try again.");
514
+ const target = visibleUserEntries[targetIndex];
515
+ const isOriginal = (entry) => {
516
+ const identity = readMirrorIdentity(entry.message);
517
+ return Boolean(identity && "idempotencyKey" in entry.message && entry.message.idempotencyKey === `codex-app-server:${params.threadId}:history:${identity}` && readCodexMirrorSourceFingerprint(entry.message));
518
+ };
519
+ const canonical = Boolean(params.canonicalThreadId && !isOriginal(target));
520
+ const threadId = canonical ? params.canonicalThreadId : params.threadId;
521
+ const thread = await params.control.readThread(threadId, false);
522
+ if (thread.id !== threadId) return failure("upstream-unavailable", "This Codex thread is unavailable or its identity changed.");
523
+ assertCodexThreadAcceptsDirectInput(thread);
524
+ if (thread.status?.type === "active") return failure("in-progress-turn", "This Codex thread is active. Wait for it to finish before forking.");
525
+ const localPrefix = visibleUserEntries.slice(0, targetIndex + 1).filter((entry) => {
526
+ if (!canonical) return true;
527
+ if (isOriginal(entry)) return false;
528
+ const meta = "__openclaw" in entry.message ? entry.message["__openclaw"] : void 0;
529
+ const blocked = isRecord(meta) ? meta.beforeAgentRunBlocked : void 0;
530
+ return !(entry !== target && isRecord(blocked) && typeof blocked.blockedBy === "string" && typeof blocked.blockedAt === "number");
531
+ });
532
+ const turns = await listCodexUpstreamTurns(params.control, threadId);
533
+ const resolved = resolveCodexUpstreamForkBoundaryFromTurns({
534
+ turns,
535
+ localPrefix
536
+ });
537
+ const selected = canonical ? entries.slice(0, entries.indexOf(target) + 1) : [];
538
+ const displayPrefix = selected.slice(0, -1);
539
+ if (canonical && (displayPrefix.length > 200 || Buffer.byteLength(JSON.stringify(displayPrefix)) > 524288)) return failure("upstream-unavailable", "The local display prefix exceeds the safe fork-copy limit. Use native Codex to fork this conversation.");
540
+ const frozen = structuredClone(selected);
541
+ const prefix = frozen.slice(0, -1);
542
+ return resolved.ok ? {
543
+ ...resolved,
544
+ editorText: textOnlyMessage("content" in target.message ? target.message.content : void 0),
545
+ ...canonical ? { canonical: {
546
+ thread,
547
+ turns,
548
+ prefix,
549
+ assertUnchanged: async () => {
550
+ const current = await readVisibleSessionTranscriptMessageEntries(params);
551
+ const index = current.findIndex((entry) => entry.entryId === params.entryId);
552
+ if (index < 0 || !isDeepStrictEqual(current.slice(0, index + 1), frozen)) throw new Error("The local Codex fork prefix changed during initialization");
553
+ const currentThread = await params.control.readThread(threadId, false);
554
+ assertCodexThreadAcceptsDirectInput(currentThread);
555
+ if (currentThread.id !== thread.id || currentThread.path !== thread.path || currentThread.cwd !== thread.cwd || currentThread.historyMode !== thread.historyMode || currentThread.status?.type === "active") throw new Error("The canonical Codex source changed during initialization");
556
+ const currentTurns = await listCodexUpstreamTurns(params.control, threadId);
557
+ const cut = turns.findIndex((turn) => turn.id === resolved.boundary.beforeTurnId);
558
+ if (!isDeepStrictEqual(currentTurns.slice(0, cut + 1), turns.slice(0, cut + 1))) throw new Error("The canonical Codex fork boundary changed during initialization");
559
+ }
560
+ } } : {}
561
+ } : resolved;
562
+ } catch {
563
+ return failure("upstream-unavailable", "The Codex thread could not be read. Check that Codex is available, then try again.");
564
+ }
565
+ }
566
+ function precheckCodexUpstreamForkBoundary(params) {
567
+ const target = params.turns.find((turn) => turn.id === params.boundary.beforeTurnId);
568
+ if (!target) return failure("upstream-unavailable", "The Codex thread changed before it could be forked. Refresh the session and try again.");
569
+ if (target.status === "inProgress") return failure("in-progress-turn", "This Codex turn is still in progress. Wait for it to finish, then try forking again.");
570
+ return {
571
+ ok: true,
572
+ boundary: params.boundary
573
+ };
574
+ }
575
+ //#endregion
576
+ //#region extensions/codex/src/app-server/canonical-session-fork.ts
577
+ /** Native history stays native; only the verified local display prefix is copied. */
578
+ async function forkCanonicalCodexSession(params) {
579
+ const { fork, resolved, control, bindingStore, sourceBinding, config } = params;
580
+ const context = control.forkContext;
581
+ if (!context?.localSessionsRoot || !resolved.canonical.thread.path) throw new Error("Canonical Codex forks require the verified local rollout on its selected connection. Fork an original imported message instead.");
582
+ const sourceIdentity = sessionBindingIdentity({
583
+ ...fork.source,
584
+ config
585
+ });
586
+ return bindingStore.withLease(sourceIdentity, async () => {
587
+ if (!isDeepStrictEqual(await bindingStore.read(sourceIdentity), sourceBinding)) throw new Error("Codex canonical source binding changed before initialization");
588
+ let freshThreadId;
589
+ let ownership;
590
+ let subscriptionReleased = false;
591
+ let policyWriteUncertain = false;
592
+ return {
593
+ status: "created",
594
+ key: (await params.runtime.agent.session.createSessionEntry({
595
+ cfg: config,
596
+ key: fork.targetKey,
597
+ agentId: fork.source.agentId,
598
+ spawnedCwd: resolved.canonical.thread.cwd ?? sourceBinding.cwd,
599
+ initialEntry: {
600
+ agentHarnessId: params.harnessRuntimeId,
601
+ modelSelectionLocked: true
602
+ },
603
+ afterCreate: async (created) => {
604
+ if (!created.initialization) throw new Error("Canonical Codex forks require host creation authority");
605
+ const host = created.initialization;
606
+ const initialization = prepareCodexSessionInitialization({
607
+ initialization: host,
608
+ bindingStore,
609
+ identity: sessionBindingIdentity({
610
+ agentId: created.agentId,
611
+ sessionId: created.sessionId,
612
+ sessionKey: created.key,
613
+ config
614
+ }),
615
+ assertCleanupAllowed: () => {
616
+ if (policyWriteUncertain) throw new Error("Fresh Codex policy delivery is uncertain; cleanup could not be verified. Inspect the retained thread before retrying.");
617
+ },
618
+ prepareCleanup: () => async (assertCurrent) => {
619
+ if (!freshThreadId) return;
620
+ const threadId = freshThreadId;
621
+ await withCodexAppServerThreadMutation(threadId, async () => {
622
+ assertCurrent();
623
+ if (!subscriptionReleased) {
624
+ if (!ownership) throw new Error("The fresh Codex fork has no verified subscription owner; inspect it before retrying.");
625
+ ownership.assertCurrent();
626
+ }
627
+ if (await bindingStore.hasOtherThreadOwner(threadId)) throw new Error("Codex fork cleanup refused: a successor owns the native thread");
628
+ assertCurrent();
629
+ try {
630
+ await control.archiveThread(threadId, assertCurrent);
631
+ } catch (cause) {
632
+ control.retireConnection?.();
633
+ throw new Error("Fresh Codex fork cleanup could not be verified; inspect the retained thread before retrying.", { cause });
634
+ }
635
+ subscriptionReleased = true;
636
+ assertCurrent();
637
+ });
638
+ }
639
+ });
640
+ const assertCurrent = () => {
641
+ initialization.assertCurrent();
642
+ if (ownership && !subscriptionReleased) ownership.assertCurrent();
643
+ };
644
+ const selection = await readCodexRolloutSelection({
645
+ sessionsRoot: context.localSessionsRoot,
646
+ rolloutPath: resolved.canonical.thread.path,
647
+ threadId: sourceBinding.threadId,
648
+ assertCurrent
649
+ });
650
+ assertCurrent();
651
+ if (!sourceBinding.dynamicToolsFingerprint) throw new Error("The canonical source has no verified native catalog binding");
652
+ const sourceCatalog = parseCodexNativeToolCatalog(selection.metadata, sourceBinding.threadId, sourceBinding.dynamicToolsFingerprint);
653
+ const prepared = await prepareCanonicalCodexFork({
654
+ created,
655
+ initialization: host,
656
+ config,
657
+ context,
658
+ model: selection.model,
659
+ modelProvider: selection.modelProvider,
660
+ sandbox: fork.sandbox,
661
+ dynamicTools: sourceCatalog
662
+ });
663
+ assertCurrent();
664
+ await selection.assertUnchanged();
665
+ assertCurrent();
666
+ await resolved.canonical.assertUnchanged();
667
+ assertCurrent();
668
+ if (!isDeepStrictEqual(await bindingStore.read(sourceIdentity), sourceBinding)) throw new Error("Codex canonical source binding changed during preparation");
669
+ assertCurrent();
670
+ let raw;
671
+ try {
672
+ raw = await control.forkThread({
673
+ ...prepared.request,
674
+ threadId: sourceBinding.threadId,
675
+ beforeTurnId: resolved.boundary.beforeTurnId,
676
+ model: selection.model,
677
+ modelProvider: selection.modelProvider,
678
+ threadSource: "appServer",
679
+ excludeTurns: true
680
+ }, assertCurrent);
681
+ } catch (error) {
682
+ if (!(error instanceof CodexAppServerScopedRequestRejectedError)) control.retireConnection?.();
683
+ throw error;
684
+ }
685
+ let response;
686
+ try {
687
+ response = assertCodexThreadForkResponse(raw);
688
+ } catch (error) {
689
+ control.retireConnection?.();
690
+ throw error;
691
+ }
692
+ if (!response.thread.id.trim() || response.thread.id === sourceBinding.threadId || response.thread.id === fork.upstream.threadId || response.thread.forkedFromId !== sourceBinding.threadId || await bindingStore.hasOtherThreadOwner(response.thread.id)) {
693
+ control.retireConnection?.();
694
+ throw new Error("Codex fork returned an unsafe native thread identity");
695
+ }
696
+ freshThreadId = response.thread.id;
697
+ ownership = await claimCodexAppServerLiveThread(context.client, freshThreadId);
698
+ if (!ownership) {
699
+ control.retireConnection?.();
700
+ throw new Error("Codex fork subscription ownership could not be acquired");
701
+ }
702
+ assertCurrent();
703
+ if (response.model !== selection.model || response.modelProvider !== selection.modelProvider || response.thread.modelProvider !== selection.modelProvider) throw new Error("Codex fork did not preserve the exact canonical source and selected native model");
704
+ const turns = await listCodexUpstreamTurns(control, freshThreadId);
705
+ assertCurrent();
706
+ const cut = resolved.canonical.turns.findIndex((turn) => turn.id === resolved.boundary.beforeTurnId);
707
+ if (cut < 0 || !isDeepStrictEqual(turns, resolved.canonical.turns.slice(0, cut))) throw new Error("Codex did not apply the exact beforeTurnId cut. Reconnect to a compatible server and retry.");
708
+ const metadata = response.thread.path ? await readCodexSessionMeta(context.localSessionsRoot, response.thread.path, freshThreadId) : void 0;
709
+ assertCurrent();
710
+ if (!metadata || metadata.model_provider !== selection.modelProvider) throw new Error("The fresh Codex fork metadata could not be verified");
711
+ const childCatalog = parseCodexNativeToolCatalog(metadata, freshThreadId);
712
+ if (!isDeepStrictEqual(sourceCatalog, childCatalog)) throw new Error("Codex fork did not preserve the actual native tool catalog");
713
+ await selection.assertUnchanged();
714
+ assertCurrent();
715
+ await attestCodexPluginThreadApps({
716
+ client: context.client,
717
+ threadId: freshThreadId,
718
+ appIds: prepared.provisionalAppIds
719
+ });
720
+ assertCurrent();
721
+ await resolved.canonical.assertUnchanged();
722
+ assertCurrent();
723
+ try {
724
+ await refreshCodexThreadPolicy({
725
+ client: context.client,
726
+ threadId: freshThreadId,
727
+ developerInstructions: prepared.request.developerInstructions,
728
+ timeoutMs: context.appServer.requestTimeoutMs,
729
+ assertCurrent
730
+ });
731
+ } catch (error) {
732
+ if (error instanceof CodexThreadPolicyHandoffError && error.outcome === "unknown") {
733
+ policyWriteUncertain = true;
734
+ control.retireConnection?.();
735
+ }
736
+ throw error;
737
+ }
738
+ const appended = await appendSessionTranscriptMessagesByIdentity({
739
+ config,
740
+ storePath: resolveStorePath(config.session?.store, { agentId: created.agentId }),
741
+ agentId: created.agentId,
742
+ sessionKey: created.key,
743
+ sessionId: created.sessionId,
744
+ messages: resolved.canonical.prefix.map((entry) => ({
745
+ message: structuredClone(entry.message),
746
+ idempotencyLookup: "scan"
747
+ }))
748
+ });
749
+ assertCurrent();
750
+ if (appended.length !== resolved.canonical.prefix.length || appended.some((item, index) => !item.appended || !isDeepStrictEqual(item.message, resolved.canonical.prefix[index]?.message) || index > 0 && item.effectiveParentId !== appended[index - 1]?.messageId)) throw new Error("The canonical Codex display prefix could not be copied completely");
751
+ initialization.link({
752
+ sessionKey: created.key,
753
+ agentId: created.agentId,
754
+ catalogId: fork.upstream.catalogId,
755
+ hostId: fork.upstream.hostId,
756
+ threadId: fork.upstream.threadId,
757
+ upstreamKind: fork.upstream.kind,
758
+ upstreamRef: fork.upstream.ref,
759
+ marker: codexUpstreamBaseline({
760
+ ...response.thread,
761
+ turns
762
+ }, normalizeOptionalString)
763
+ });
764
+ await initialization.bind({
765
+ threadId: freshThreadId,
766
+ connectionScope: "supervision",
767
+ supervisionSourceThreadId: fork.upstream.threadId,
768
+ preserveNativeModel: true,
769
+ conversationSourceTransferComplete: true,
770
+ cwd: prepared.request.cwd ?? "",
771
+ rolloutPath: response.thread.path ?? void 0,
772
+ model: selection.model,
773
+ modelProvider: selection.modelProvider,
774
+ appServerRuntimeFingerprint: control.connectionFingerprint,
775
+ dynamicToolsFingerprint: codexDynamicToolsFingerprint(childCatalog),
776
+ dynamicToolsContainDeferred: flattenCodexDynamicToolFunctions(childCatalog).some((tool) => tool.deferLoading === true),
777
+ ...prepared.bindingPolicy,
778
+ historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
779
+ });
780
+ assertCurrent();
781
+ await ownership.release(freshThreadId, assertCurrent);
782
+ subscriptionReleased = true;
783
+ initialization.assertCurrent();
784
+ await resolved.canonical.assertUnchanged();
785
+ initialization.assertCurrent();
786
+ if (!isDeepStrictEqual(await bindingStore.read(sourceIdentity), sourceBinding)) throw new Error("Codex source binding changed before fork readiness");
787
+ initialization.assertCurrent();
788
+ return { pluginExtensions: created.entry.pluginExtensions };
789
+ }
790
+ }).finally(() => {
791
+ if (freshThreadId && !subscriptionReleased && hasCodexAppServerLiveThread(context.client, freshThreadId)) control.retireConnection?.();
792
+ })).key,
793
+ ...resolved.editorText !== void 0 ? { editorText: resolved.editorText } : {}
794
+ };
795
+ });
796
+ }
797
+ //#endregion
798
+ //#region extensions/codex/src/app-server/upstream-session-fork.ts
799
+ function readConnectionFingerprint(ref) {
800
+ if (!isRecord(ref)) return;
801
+ return typeof ref.connectionFingerprint === "string" && ref.connectionFingerprint.trim() ? ref.connectionFingerprint : void 0;
802
+ }
803
+ async function forkCodexUpstreamSession(params, options) {
804
+ try {
805
+ const sourceFingerprint = params.upstream.kind === "codex-app-server" ? readConnectionFingerprint(params.upstream.ref) : void 0;
806
+ const requestControl = sourceFingerprint ? options.controlFactory.forUpstream(params.source.agentId, sourceFingerprint) : void 0;
807
+ if (!sourceFingerprint || !requestControl) return {
808
+ status: "failed",
809
+ code: "upstream-unavailable",
810
+ message: "This Codex thread is not available on the current connection. Reconnect to its host and try again."
811
+ };
812
+ return await requestControl.withPinnedConnection(async (control) => {
813
+ const sourceBinding = await options.bindingStore.read(sessionBindingIdentity({
814
+ ...params.source,
815
+ config: options.resolveConfig?.()
816
+ }));
817
+ const supervised = sourceBinding?.connectionScope === "supervision";
818
+ const sourceThreadId = params.upstream.threadId;
819
+ let initializerOwnsFork = false;
820
+ const archiveFreshFork = async (forkedThreadId, assertCurrent) => withCodexAppServerThreadMutation(forkedThreadId, async () => {
821
+ assertCurrent?.();
822
+ if (await options.bindingStore.hasOtherThreadOwner(forkedThreadId)) throw new Error("Codex fork cleanup refused: the native thread has another owner");
823
+ assertCurrent?.();
824
+ try {
825
+ await control.archiveThread(forkedThreadId, assertCurrent);
826
+ assertCurrent?.();
827
+ } catch (cause) {
828
+ control.retireConnection?.();
829
+ throw new Error("Codex fork cleanup could not be verified; inspect the retained native thread before retrying.", { cause });
830
+ }
831
+ });
832
+ if (sourceFingerprint !== control.connectionFingerprint || supervised && (sourceBinding.supervisionSourceThreadId !== params.upstream.threadId || (sourceBinding.pendingSupervisionBranch?.connectionFingerprint ?? sourceBinding.appServerRuntimeFingerprint) !== sourceFingerprint)) return {
833
+ status: "failed",
834
+ code: "upstream-unavailable",
835
+ message: "This Codex thread is not available on the current connection. Reconnect to its host and try again."
836
+ };
837
+ const resolved = await resolveCodexUpstreamForkBoundary({
838
+ ...params.source,
839
+ threadId: sourceThreadId,
840
+ canonicalThreadId: supervised && !sourceBinding.pendingSupervisionBranch ? sourceBinding.threadId : void 0,
841
+ control
842
+ });
843
+ if (!resolved.ok) return {
844
+ status: "failed",
845
+ code: resolved.code,
846
+ message: resolved.message
847
+ };
848
+ if (resolved.canonical && sourceBinding) return await forkCanonicalCodexSession({
849
+ fork: params,
850
+ resolved: {
851
+ ...resolved,
852
+ canonical: resolved.canonical
853
+ },
854
+ sourceBinding,
855
+ control,
856
+ bindingStore: options.bindingStore,
857
+ runtime: options.runtime,
858
+ harnessRuntimeId: options.harnessRuntimeId,
859
+ config: options.resolveConfig?.() ?? {}
860
+ });
861
+ const liveTurns = await listCodexUpstreamTurns(control, sourceThreadId);
862
+ const precheck = precheckCodexUpstreamForkBoundary({
863
+ boundary: resolved.boundary,
864
+ turns: liveTurns
865
+ });
866
+ if (!precheck.ok) return {
867
+ status: "failed",
868
+ code: precheck.code,
869
+ message: precheck.message
870
+ };
871
+ const rawResponse = await control.forkThread({
872
+ threadId: sourceThreadId,
873
+ beforeTurnId: resolved.boundary.beforeTurnId,
874
+ ...params.sandbox === "required" ? { sandbox: "workspace-write" } : {},
875
+ excludeTurns: true
876
+ });
877
+ const response = assertCodexThreadForkResponse(rawResponse);
878
+ const threadId = response.thread.id.trim();
879
+ if (!threadId) throw new Error("Codex thread/fork response did not include a thread id");
880
+ if (threadId === sourceThreadId || threadId === sourceBinding?.threadId) throw new Error("Codex thread/fork response reused the source thread id");
881
+ const forkedThreadId = threadId;
882
+ try {
883
+ const connectionFingerprint = normalizeOptionalString(control.connectionFingerprint);
884
+ if (!connectionFingerprint) throw new Error("Codex fork connection did not include a fingerprint");
885
+ const forkedTurns = await listCodexUpstreamTurns(control, threadId);
886
+ const expectedLastTurnId = resolved.boundary.lastRetainedTurnId;
887
+ if ((forkedTurns.at(-1)?.id ?? null) !== expectedLastTurnId) throw new Error("This Codex version does not support message-level forks. Update Codex, reconnect, and try again.");
888
+ const forkedThread = {
889
+ ...response.thread,
890
+ turns: forkedTurns
891
+ };
892
+ const throughTurnId = codexLastTerminalTurnId(forkedThread, normalizeOptionalString) ?? null;
893
+ const marker = codexUpstreamBaseline(forkedThread, normalizeOptionalString);
894
+ const config = options.resolveConfig?.() ?? {};
895
+ return {
896
+ status: "created",
897
+ key: (await createImportedCodexSession({
898
+ runtime: options.runtime,
899
+ bindingStore: options.bindingStore,
900
+ prepareCleanup: () => {
901
+ initializerOwnsFork = true;
902
+ return (assertCurrent) => archiveFreshFork(forkedThreadId, assertCurrent);
903
+ },
904
+ config,
905
+ key: params.targetKey,
906
+ agentId: params.source.agentId,
907
+ thread: forkedThread,
908
+ throughTurnId,
909
+ initialEntry: {
910
+ agentHarnessId: options.harnessRuntimeId,
911
+ modelSelectionLocked: true
912
+ },
913
+ afterImport: async (entry, initialization) => {
914
+ initialization.link({
915
+ sessionKey: entry.key,
916
+ agentId: entry.agentId,
917
+ catalogId: params.upstream.catalogId,
918
+ hostId: params.upstream.hostId,
919
+ threadId,
920
+ upstreamKind: params.upstream.kind,
921
+ upstreamRef: {
922
+ connectionFingerprint,
923
+ threadId
924
+ },
925
+ marker
926
+ });
927
+ await initialization.bind({
928
+ threadId,
929
+ connectionScope: "supervision",
930
+ supervisionSourceThreadId: threadId,
931
+ preserveNativeModel: true,
932
+ conversationSourceTransferComplete: true,
933
+ pendingSupervisionBranch: {
934
+ sourceThreadId: threadId,
935
+ connectionFingerprint,
936
+ ...throughTurnId ? { lastTurnId: throughTurnId } : {}
937
+ },
938
+ cwd: forkedThread.cwd ?? "",
939
+ model: response.model,
940
+ modelProvider: response.modelProvider ?? void 0,
941
+ historyCoveredThrough: (/* @__PURE__ */ new Date()).toISOString()
942
+ });
943
+ return { pluginExtensions: entry.entry.pluginExtensions };
944
+ }
945
+ })).key,
946
+ ...resolved.editorText !== void 0 ? { editorText: resolved.editorText } : {}
947
+ };
948
+ } catch (error) {
949
+ if (!initializerOwnsFork) await options.bindingStore.withThreadArchiveFence(() => archiveFreshFork(forkedThreadId));
950
+ return {
951
+ status: "failed",
952
+ code: "upstream-unavailable",
953
+ message: error instanceof Error ? error.message : "The Codex fork could not be imported. Refresh sessions and try again."
954
+ };
955
+ }
956
+ });
957
+ } catch (error) {
958
+ return {
959
+ status: "failed",
960
+ code: "upstream-unavailable",
961
+ message: error instanceof Error ? error.message : "The Codex thread could not be forked. Check that Codex is available, then try again."
962
+ };
963
+ }
964
+ }
965
+ //#endregion
966
+ export { forkCodexUpstreamSession };