@openclaw/codex 2026.7.1 → 2026.7.2-beta.2
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/README.md +27 -3
- package/dist/app-server-policy-Scc-Wevo.js +49 -0
- package/dist/{attempt-notifications-BGsEIIDI.js → attempt-notifications-BFmNhqFl.js} +3 -6
- package/dist/auth-binding-C1czuHPA.js +62 -0
- package/dist/cli-metadata.js +20 -0
- package/dist/{app-server-policy-C968Kgin.js → command-formatters-Dq9InZUK.js} +39 -51
- package/dist/{command-handlers-Dv-pzAmg.js → command-handlers-IgqmL_kv.js} +161 -61
- package/dist/{compact-4mVBriKT.js → compact-DCTmg3id.js} +39 -10
- package/dist/computer-use-CPcU8TjG.js +685 -0
- package/dist/conversation-control-CGkBlfc5.js +286 -0
- package/dist/doctor-contract-api.js +120 -81
- package/dist/{provider-capabilities-CDnHbmUZ.js → dynamic-tools-C_1tEs34.js} +6109 -5725
- package/dist/harness.js +59 -15
- package/dist/index.js +1460 -269
- package/dist/{media-understanding-provider-BHOu9-Gg.js → media-understanding-provider-D31dOJwb.js} +32 -15
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{models-DDmO1zwd.js → models-uh26C8QU.js} +5 -9
- package/dist/notification-correlation-KmfV4EkP.js +67 -0
- package/dist/plugin-app-cache-key-Ceb-lm2c.js +315 -0
- package/dist/{provider-zjPfx5Fs.js → rate-limits-DyXaYAxU.js} +10 -266
- package/dist/request-B_oQsCXy.js +157 -0
- package/dist/{run-attempt-FUyOjGCV.js → run-attempt-MLzoMe_m.js} +7395 -6430
- package/dist/runtime-artifact-C2ITXdhM.js +488 -0
- package/dist/{sandbox-guard-DA2TQfZW.js → sandbox-guard-D1Wky__S.js} +10 -1
- package/dist/{config-CYEDnLJ2.js → session-binding-h1mmCGnl.js} +985 -31
- package/dist/session-catalog-CEvoXWHA.js +2412 -0
- package/dist/session-cli-XsEuWb_C.js +175 -0
- package/dist/{shared-client-4ICy3U6d.js → shared-client-JiAnW6pc.js} +2193 -1319
- package/dist/{side-question-N4OaDer2.js → side-question-DqDvIwSU.js} +140 -70
- package/dist/{thread-lifecycle-qWE88Dn2.js → thread-lifecycle-CN_pPtPh.js} +1826 -1188
- package/dist/{protocol-validators-dZQ-UTOa.js → transcript-mirror-D9rTxx2P.js} +485 -2
- package/dist/transport-stdio-DXgGZ7Ib.js +88 -0
- package/dist/usage-D5Rohxoc.js +25 -0
- package/dist/{web-search-provider.runtime-BSlriav6.js → web-search-provider.runtime-CKVBn3fP.js} +3 -3
- package/npm-shrinkwrap.json +56 -30
- package/openclaw.plugin.json +177 -14
- package/package.json +8 -6
- package/dist/command-rpc-eLM_mtC7.js +0 -784
- package/dist/computer-use-Bmaz333N.js +0 -377
- package/dist/notification-correlation-Bo7KB3ks.js +0 -35
- package/dist/plugin-app-cache-key-BrhVdeEf.js +0 -98
- package/dist/prompt-overlay.js +0 -15
- package/dist/protocol-2POPqAY4.js +0 -13
- package/dist/provider-catalog.js +0 -79
- package/dist/provider-discovery.js +0 -34
- package/dist/provider.js +0 -2
- package/dist/request-BcJyl8KL.js +0 -36
- package/dist/session-binding-C1ZXdP-x.js +0 -627
|
@@ -1,239 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { a as normalizeCodexAppServerBindingModelProvider, i as isCodexAppServerNativeAuthProfile, o as reclaimCurrentCodexSessionGeneration, r as hashCodexAppServerBindingFingerprint, s as sessionBindingIdentity } from "./session-binding-C1ZXdP-x.js";
|
|
8
|
-
import "node:fs/promises";
|
|
9
|
-
import "node:path";
|
|
10
|
-
import { isFutureDateTimestampMs, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
11
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
1
|
+
import { G as isCodexAppServerPrewriteRequestCancellationError, H as isCodexAppServerConnectionClosedError, R as CodexAppServerRpcError, X as flattenCodexDynamicToolFunctions, Z as isJsonObject, d as retireSharedCodexAppServerClientIfCurrent, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, o as isCodexAppServerStartSelectionChangedError, t as clearSharedCodexAppServerClientIfCurrent, z as getCodexAppServerClientInstanceId } from "./shared-client-JiAnW6pc.js";
|
|
2
|
+
import { T as resolveCodexPluginsPolicy, a as isCodexAppServerNativeAuthProfile, c as sessionBindingIdentity, d as CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, h as codexSandboxPolicyForTurn, i as hashCodexAppServerBindingFingerprint, n as assertCodexBindingMayBeReplaced, o as normalizeCodexAppServerBindingModelProvider, s as reclaimCurrentCodexSessionGeneration, u as CODEX_PLUGINS_MARKETPLACE_NAME } from "./session-binding-h1mmCGnl.js";
|
|
3
|
+
import { a as projectBoundedCodexThreadHistory, d as assertCodexThreadStartResponse, l as assertCodexThreadForkResponse, u as assertCodexThreadResumeResponse } from "./transcript-mirror-D9rTxx2P.js";
|
|
4
|
+
import { a as serializeCodexAppInventoryError, i as defaultCodexAppInventoryCache, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-Ceb-lm2c.js";
|
|
5
|
+
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
6
|
+
import * as crypto$1 from "node:crypto";
|
|
12
7
|
import crypto from "node:crypto";
|
|
13
8
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
|
-
import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
15
|
-
import { buildCodexUserMcpServersThreadConfigPatch } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
16
|
-
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
9
|
+
import { SKILL_WORKSHOP_TOOL_NAME, buildSkillWorkshopPromptSection, embeddedAgentLog, formatErrorMessage, isActiveHarnessContextEngine, isHostScopedAgentToolActive } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
17
10
|
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
18
|
-
import {
|
|
11
|
+
import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
19
12
|
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
var CodexAppInventoryCache = class {
|
|
24
|
-
constructor(options = {}) {
|
|
25
|
-
this.entries = /* @__PURE__ */ new Map();
|
|
26
|
-
this.inFlight = /* @__PURE__ */ new Map();
|
|
27
|
-
this.refreshTokens = /* @__PURE__ */ new Map();
|
|
28
|
-
this.diagnostics = /* @__PURE__ */ new Map();
|
|
29
|
-
this.revision = 0;
|
|
30
|
-
this.ttlMs = options.ttlMs ?? 36e5;
|
|
31
|
-
}
|
|
32
|
-
/** Reads a snapshot and schedules refresh when missing, stale, or forced. */
|
|
33
|
-
read(params) {
|
|
34
|
-
const nowMs = resolveDateTimestampMs(params.nowMs);
|
|
35
|
-
const entry = this.entries.get(params.key);
|
|
36
|
-
if (!entry) {
|
|
37
|
-
const refreshScheduled = params.suppressRefresh ? false : this.scheduleRefresh(params);
|
|
38
|
-
return {
|
|
39
|
-
state: "missing",
|
|
40
|
-
key: params.key,
|
|
41
|
-
revision: this.revision,
|
|
42
|
-
refreshScheduled,
|
|
43
|
-
...this.diagnostics.get(params.key) ? { diagnostic: this.diagnostics.get(params.key) } : {}
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const state = entry.invalidated || !isFutureDateTimestampMs(entry.expiresAtMs, { nowMs }) ? "stale" : "fresh";
|
|
47
|
-
const refreshScheduled = state === "fresh" && !params.forceRefetch ? false : this.scheduleRefresh(params);
|
|
48
|
-
return {
|
|
49
|
-
state,
|
|
50
|
-
key: params.key,
|
|
51
|
-
revision: entry.revision,
|
|
52
|
-
snapshot: stripEntryState(entry),
|
|
53
|
-
refreshScheduled,
|
|
54
|
-
...entry.lastError ? { diagnostic: entry.lastError } : {}
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
/** Forces or joins an immediate refresh for a cache key. */
|
|
58
|
-
refreshNow(params) {
|
|
59
|
-
return this.refresh(params);
|
|
60
|
-
}
|
|
61
|
-
/** Marks a key stale and records the reason as a diagnostic. */
|
|
62
|
-
invalidate(key, reason, nowMs = Date.now()) {
|
|
63
|
-
this.revision += 1;
|
|
64
|
-
const diagnostic = {
|
|
65
|
-
message: reason,
|
|
66
|
-
atMs: nowMs
|
|
67
|
-
};
|
|
68
|
-
const entry = this.entries.get(key);
|
|
69
|
-
if (entry) {
|
|
70
|
-
entry.invalidated = true;
|
|
71
|
-
entry.lastError = diagnostic;
|
|
72
|
-
entry.revision = this.revision;
|
|
73
|
-
} else this.diagnostics.set(key, diagnostic);
|
|
74
|
-
return this.revision;
|
|
75
|
-
}
|
|
76
|
-
/** Clears all cached snapshots, diagnostics, in-flight requests, and revision state. */
|
|
77
|
-
clear() {
|
|
78
|
-
this.entries.clear();
|
|
79
|
-
this.inFlight.clear();
|
|
80
|
-
this.refreshTokens.clear();
|
|
81
|
-
this.diagnostics.clear();
|
|
82
|
-
this.revision = 0;
|
|
83
|
-
}
|
|
84
|
-
/** Returns the monotonically increasing cache revision. */
|
|
85
|
-
getRevision() {
|
|
86
|
-
return this.revision;
|
|
87
|
-
}
|
|
88
|
-
scheduleRefresh(params) {
|
|
89
|
-
if (this.inFlight.has(params.key) && !params.forceRefetch) return true;
|
|
90
|
-
const promise = this.refresh(params);
|
|
91
|
-
this.inFlight.set(params.key, promise);
|
|
92
|
-
promise.catch(() => void 0);
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
async refresh(params) {
|
|
96
|
-
const existing = this.inFlight.get(params.key);
|
|
97
|
-
if (existing && !params.forceRefetch) return existing;
|
|
98
|
-
const refreshToken = (this.refreshTokens.get(params.key) ?? 0) + 1;
|
|
99
|
-
this.refreshTokens.set(params.key, refreshToken);
|
|
100
|
-
const promise = this.refreshUncoalesced(params, refreshToken);
|
|
101
|
-
this.inFlight.set(params.key, promise);
|
|
102
|
-
try {
|
|
103
|
-
return await promise;
|
|
104
|
-
} finally {
|
|
105
|
-
if (this.inFlight.get(params.key) === promise) this.inFlight.delete(params.key);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
async refreshUncoalesced(params, refreshToken) {
|
|
109
|
-
const nowMs = resolveDateTimestampMs(params.nowMs);
|
|
110
|
-
try {
|
|
111
|
-
const apps = await listAllApps(params.request, params.forceRefetch ?? false, params.targetAppIds);
|
|
112
|
-
this.revision += 1;
|
|
113
|
-
const expiresAtMs = resolveExpiresAtMsFromDurationMs(this.ttlMs, { nowMs }) ?? 0;
|
|
114
|
-
const snapshot = {
|
|
115
|
-
key: params.key,
|
|
116
|
-
apps,
|
|
117
|
-
fetchedAtMs: nowMs,
|
|
118
|
-
expiresAtMs,
|
|
119
|
-
revision: this.revision
|
|
120
|
-
};
|
|
121
|
-
if (this.refreshTokens.get(params.key) === refreshToken) {
|
|
122
|
-
this.entries.set(params.key, {
|
|
123
|
-
...snapshot,
|
|
124
|
-
invalidated: false
|
|
125
|
-
});
|
|
126
|
-
this.diagnostics.delete(params.key);
|
|
127
|
-
}
|
|
128
|
-
return snapshot;
|
|
129
|
-
} catch (error) {
|
|
130
|
-
const diagnostic = {
|
|
131
|
-
message: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
|
|
132
|
-
atMs: nowMs
|
|
133
|
-
};
|
|
134
|
-
this.diagnostics.set(params.key, diagnostic);
|
|
135
|
-
const entry = this.entries.get(params.key);
|
|
136
|
-
if (entry) entry.lastError = diagnostic;
|
|
137
|
-
embeddedAgentLog.warn("codex app inventory refresh failed", {
|
|
138
|
-
forceRefetch: params.forceRefetch === true,
|
|
139
|
-
keyFingerprint: fingerprintInventoryCacheKey(params.key),
|
|
140
|
-
error: serializeCodexAppInventoryError(error)
|
|
141
|
-
});
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
/** Serializes a refresh failure without leaking large or sensitive error data. */
|
|
147
|
-
function serializeCodexAppInventoryError(error) {
|
|
148
|
-
const record = isRecord(error) ? error : void 0;
|
|
149
|
-
const data = record && "data" in record ? redactErrorData(record.data) : void 0;
|
|
150
|
-
return {
|
|
151
|
-
name: error instanceof Error ? error.name : typeof record?.name === "string" ? record.name : void 0,
|
|
152
|
-
message: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
|
|
153
|
-
...typeof record?.code === "number" ? { code: record.code } : {},
|
|
154
|
-
...data !== void 0 ? { data } : {}
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
/** Shared app inventory cache used by Codex app-server runtime paths. */
|
|
158
|
-
const defaultCodexAppInventoryCache = new CodexAppInventoryCache();
|
|
159
|
-
/** Builds a stable cache key from build versions and runtime identity fields. */
|
|
160
|
-
function buildCodexAppInventoryCacheKey(input, openClawVersion, codexPluginVersion) {
|
|
161
|
-
return JSON.stringify({
|
|
162
|
-
openClawVersion,
|
|
163
|
-
codexPluginVersion,
|
|
164
|
-
codexHome: input.codexHome ?? null,
|
|
165
|
-
endpoint: input.endpoint ?? null,
|
|
166
|
-
runtimeIdentity: normalizeRuntimeIdentityForCacheKey(input.runtimeIdentity),
|
|
167
|
-
authProfileId: input.authProfileId ?? null,
|
|
168
|
-
accountId: input.accountId ?? null,
|
|
169
|
-
envApiKeyFingerprint: input.envApiKeyFingerprint ?? null,
|
|
170
|
-
appServerVersion: input.appServerVersion ?? null
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
function normalizeRuntimeIdentityForCacheKey(value) {
|
|
174
|
-
if (!value) return null;
|
|
175
|
-
const entries = Object.entries(value).flatMap(([key, rawValue]) => {
|
|
176
|
-
const normalized = rawValue?.trim();
|
|
177
|
-
return normalized ? [[key, normalized]] : [];
|
|
178
|
-
}).toSorted(([left], [right]) => left.localeCompare(right));
|
|
179
|
-
return entries.length > 0 ? Object.fromEntries(entries) : null;
|
|
180
|
-
}
|
|
181
|
-
async function listAllApps(request, forceRefetch, targetAppIds = []) {
|
|
182
|
-
const apps = [];
|
|
183
|
-
const targetIds = new Set(targetAppIds.filter(Boolean));
|
|
184
|
-
const remainingTargetIds = new Set(targetIds);
|
|
185
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
186
|
-
let cursor;
|
|
187
|
-
do {
|
|
188
|
-
const response = await request("app/list", {
|
|
189
|
-
cursor,
|
|
190
|
-
limit: targetIds.size > 0 ? CODEX_TARGETED_APP_INVENTORY_LIMIT : 100,
|
|
191
|
-
forceRefetch
|
|
192
|
-
});
|
|
193
|
-
apps.push(...response.data);
|
|
194
|
-
for (const app of response.data) remainingTargetIds.delete(app.id);
|
|
195
|
-
if (targetIds.size > 0 && remainingTargetIds.size === 0) break;
|
|
196
|
-
cursor = response.nextCursor;
|
|
197
|
-
if (cursor && seenCursors.has(cursor)) throw new Error(`app/list returned repeated cursor ${cursor}`);
|
|
198
|
-
if (cursor) seenCursors.add(cursor);
|
|
199
|
-
} while (cursor);
|
|
200
|
-
return apps;
|
|
201
|
-
}
|
|
202
|
-
function stripEntryState(entry) {
|
|
203
|
-
const { invalidated: _invalidated, ...snapshot } = entry;
|
|
204
|
-
return snapshot;
|
|
205
|
-
}
|
|
206
|
-
function fingerprintInventoryCacheKey(key) {
|
|
207
|
-
let hash = 0;
|
|
208
|
-
for (let index = 0; index < key.length; index += 1) hash = hash * 31 + key.charCodeAt(index) >>> 0;
|
|
209
|
-
return hash.toString(16).padStart(8, "0");
|
|
210
|
-
}
|
|
211
|
-
function redactErrorData(value, depth = 0) {
|
|
212
|
-
if (value === void 0) return;
|
|
213
|
-
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
214
|
-
if (depth > 6) return "[truncated]";
|
|
215
|
-
if (Array.isArray(value)) return value.map((entry) => redactErrorData(entry, depth + 1) ?? null);
|
|
216
|
-
if (isRecord(value)) {
|
|
217
|
-
const redacted = {};
|
|
218
|
-
for (const [key, entry] of Object.entries(value)) redacted[key] = isSensitiveErrorDataKey(key) ? "<redacted>" : redactErrorData(entry, depth + 1) ?? null;
|
|
219
|
-
return redacted;
|
|
220
|
-
}
|
|
221
|
-
if (typeof value === "string" && value.length > 500) return `${truncateUtf16Safe(value, 500)}...`;
|
|
222
|
-
if (typeof value === "string") return value;
|
|
223
|
-
if (typeof value === "bigint") return value.toString();
|
|
224
|
-
if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol()";
|
|
225
|
-
if (typeof value === "function") return value.name ? `[function ${value.name}]` : "[function]";
|
|
226
|
-
return "[unserializable]";
|
|
227
|
-
}
|
|
228
|
-
function sanitizeErrorMessage(message) {
|
|
229
|
-
const htmlStart = message.search(/<html[\s>]/i);
|
|
230
|
-
const redacted = (htmlStart >= 0 ? `${message.slice(0, htmlStart).trimEnd()} [HTML response body omitted]` : message).replace(/([?&][^=\s"'<>]*(?:api[_-]?key|authorization|cookie|credential|password|secret|token|tk)[^=\s"'<>]*=)[^&\s"'<>]+/gi, "$1<redacted>");
|
|
231
|
-
return redacted.length > MAX_SERIALIZED_ERROR_MESSAGE_LENGTH ? `${redacted.slice(0, MAX_SERIALIZED_ERROR_MESSAGE_LENGTH)}...` : redacted;
|
|
232
|
-
}
|
|
233
|
-
function isSensitiveErrorDataKey(key) {
|
|
234
|
-
return /api[_-]?key|authorization|cookie|credential|password|secret|token/i.test(key);
|
|
235
|
-
}
|
|
236
|
-
//#endregion
|
|
13
|
+
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
14
|
+
import { GPT5_HEARTBEAT_PROMPT_OVERLAY } from "openclaw/plugin-sdk/provider-model-shared";
|
|
15
|
+
import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
|
|
237
16
|
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
238
17
|
/**
|
|
239
18
|
* Reads Codex plugin marketplace state and app inventory to decide which
|
|
@@ -252,19 +31,21 @@ async function readCodexPluginInventory(params) {
|
|
|
252
31
|
}]
|
|
253
32
|
};
|
|
254
33
|
const appInventory = readCachedAppInventory(params);
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
34
|
+
const curatedListed = await listCodexPluginMetadata(params, "curated-global", {});
|
|
35
|
+
const shouldListWorkspacePlugins = policy.pluginPolicies.some((pluginPolicy) => pluginPolicy.enabled && pluginPolicy.marketplaceName === "workspace-directory");
|
|
36
|
+
let workspaceListResult;
|
|
37
|
+
if (shouldListWorkspacePlugins) try {
|
|
38
|
+
workspaceListResult = {
|
|
39
|
+
kind: "listed",
|
|
40
|
+
response: await listCodexPluginMetadata(params, "workspace-directory", {
|
|
41
|
+
cwds: [],
|
|
42
|
+
marketplaceKinds: [CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME]
|
|
43
|
+
})
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (!(error instanceof CodexAppServerRpcError)) throw error;
|
|
47
|
+
workspaceListResult = { kind: "rejected" };
|
|
48
|
+
}
|
|
268
49
|
const diagnostics = [];
|
|
269
50
|
const records = [];
|
|
270
51
|
if (appInventory?.state === "missing") diagnostics.push({
|
|
@@ -277,19 +58,27 @@ async function readCodexPluginInventory(params) {
|
|
|
277
58
|
});
|
|
278
59
|
for (const pluginPolicy of policy.pluginPolicies) {
|
|
279
60
|
if (!pluginPolicy.enabled) continue;
|
|
280
|
-
const
|
|
61
|
+
const listed = pluginPolicy.marketplaceName === "workspace-directory" ? workspaceListResult?.kind === "listed" ? workspaceListResult.response : void 0 : curatedListed;
|
|
62
|
+
const hasMarketplace = pluginPolicy.marketplaceName === "workspace-directory" ? listed?.marketplaces.some((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME) === true : listed?.marketplaces.some(isOpenAiCuratedMarketplace) === true;
|
|
63
|
+
if (!listed || !hasMarketplace) {
|
|
64
|
+
diagnostics.push({
|
|
65
|
+
code: "marketplace_missing",
|
|
66
|
+
plugin: pluginPolicy,
|
|
67
|
+
message: `Codex marketplace ${pluginPolicy.marketplaceName} was not found.`
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const resolvedPlugin = pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(listed, pluginPolicy.pluginName) : findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
|
|
281
72
|
if (!resolvedPlugin) {
|
|
282
73
|
diagnostics.push({
|
|
283
74
|
code: "plugin_missing",
|
|
284
75
|
plugin: pluginPolicy,
|
|
285
|
-
message: `${pluginPolicy.pluginName} was not found in ${
|
|
76
|
+
message: `${pluginPolicy.pluginName} was not found in ${pluginPolicy.marketplaceName}.`
|
|
286
77
|
});
|
|
287
78
|
continue;
|
|
288
79
|
}
|
|
289
80
|
const { summary } = resolvedPlugin;
|
|
290
|
-
const
|
|
291
|
-
if (records.length === 0) marketplace = pluginMarketplace;
|
|
292
|
-
const detail = await readPluginDetail(params, pluginMarketplace, pluginPolicy, summary, diagnostics);
|
|
81
|
+
const detail = await readPluginDetail(params, marketplaceRef(resolvedPlugin.marketplace, pluginPolicy.marketplaceName), pluginPolicy, summary, diagnostics);
|
|
293
82
|
const ownedAppIds = detail?.apps.map((app) => app.id).filter(Boolean).toSorted() ?? [];
|
|
294
83
|
const appOwnership = resolveAppOwnership({
|
|
295
84
|
detail,
|
|
@@ -324,7 +113,6 @@ async function readCodexPluginInventory(params) {
|
|
|
324
113
|
}
|
|
325
114
|
return {
|
|
326
115
|
policy,
|
|
327
|
-
marketplace,
|
|
328
116
|
records,
|
|
329
117
|
diagnostics,
|
|
330
118
|
...appInventory ? { appInventory } : {}
|
|
@@ -334,7 +122,7 @@ async function readCodexPluginInventory(params) {
|
|
|
334
122
|
function findOpenAiCuratedPluginSummary(listed, pluginName) {
|
|
335
123
|
const resolved = findOpenAiCuratedMarketplacePlugin(listed, pluginName);
|
|
336
124
|
return resolved ? {
|
|
337
|
-
marketplace: marketplaceRef(resolved.marketplace),
|
|
125
|
+
marketplace: marketplaceRef(resolved.marketplace, CODEX_PLUGINS_MARKETPLACE_NAME),
|
|
338
126
|
summary: resolved.summary
|
|
339
127
|
} : void 0;
|
|
340
128
|
}
|
|
@@ -346,6 +134,31 @@ function pluginReadParams(marketplace, pluginName) {
|
|
|
346
134
|
pluginName
|
|
347
135
|
};
|
|
348
136
|
}
|
|
137
|
+
/** Returns configured plugin keys whose current metadata may still recover. */
|
|
138
|
+
function resolveRecoverableCodexPluginConfigKeys(params) {
|
|
139
|
+
return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
|
|
140
|
+
pluginPolicy,
|
|
141
|
+
metadataCache: params.metadataCache,
|
|
142
|
+
appCacheKey: params.appCacheKey
|
|
143
|
+
})).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
|
|
144
|
+
}
|
|
145
|
+
async function listCodexPluginMetadata(params, queryKind, requestParams) {
|
|
146
|
+
if (!params.metadataCache || !params.appCacheKey || queryKind === "workspace-directory") return await params.request("plugin/list", requestParams);
|
|
147
|
+
return (await params.metadataCache.load({
|
|
148
|
+
appCacheKey: params.appCacheKey,
|
|
149
|
+
queryKind,
|
|
150
|
+
requestParams,
|
|
151
|
+
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
152
|
+
cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
153
|
+
})).response;
|
|
154
|
+
}
|
|
155
|
+
function isSettledMissingPluginPolicy(params) {
|
|
156
|
+
const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "workspace-directory" : "curated-global";
|
|
157
|
+
const listed = params.metadataCache.read(params.appCacheKey, queryKind)?.response;
|
|
158
|
+
if (!listed) return false;
|
|
159
|
+
if (queryKind === "workspace-directory") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
160
|
+
return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
161
|
+
}
|
|
349
162
|
function readCachedAppInventory(params) {
|
|
350
163
|
if (!params.appCache || !params.appCacheKey) return;
|
|
351
164
|
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
@@ -358,6 +171,14 @@ function readCachedAppInventory(params) {
|
|
|
358
171
|
}
|
|
359
172
|
async function readPluginDetail(params, marketplace, pluginPolicy, summary, diagnostics) {
|
|
360
173
|
if (params.readPluginDetails === false) return;
|
|
174
|
+
if (marketplace.name === "workspace-directory" && marketplace.remoteMarketplaceName && !summary.remotePluginId) {
|
|
175
|
+
diagnostics.push({
|
|
176
|
+
code: "plugin_detail_unavailable",
|
|
177
|
+
plugin: pluginPolicy,
|
|
178
|
+
message: `${pluginPolicy.pluginName} detail unavailable: Codex did not return a remote plugin id.`
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
361
182
|
try {
|
|
362
183
|
return (await params.request("plugin/read", pluginReadParams(marketplace, marketplace.remoteMarketplaceName && summary.remotePluginId ? summary.remotePluginId : pluginPolicy.pluginName))).plugin;
|
|
363
184
|
} catch (error) {
|
|
@@ -416,31 +237,40 @@ function findOpenAiCuratedMarketplacePlugin(listed, pluginName) {
|
|
|
416
237
|
};
|
|
417
238
|
}
|
|
418
239
|
}
|
|
240
|
+
function findWorkspaceMarketplacePlugin(listed, pluginName) {
|
|
241
|
+
const marketplace = listed.marketplaces.find((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME);
|
|
242
|
+
const summary = marketplace?.plugins.find((plugin) => plugin.id === pluginName);
|
|
243
|
+
return marketplace && summary ? {
|
|
244
|
+
marketplace,
|
|
245
|
+
summary
|
|
246
|
+
} : void 0;
|
|
247
|
+
}
|
|
419
248
|
function pluginNameFromPluginId(pluginId, marketplaceName) {
|
|
420
249
|
const trimmed = pluginId.trim();
|
|
421
250
|
if (!trimmed) return;
|
|
422
251
|
const marketplaceSuffix = `@${marketplaceName}`;
|
|
423
252
|
return (trimmed.endsWith(marketplaceSuffix) ? trimmed.slice(0, -marketplaceSuffix.length) : trimmed).split("/").at(-1)?.trim() || void 0;
|
|
424
253
|
}
|
|
425
|
-
function marketplaceRef(marketplace) {
|
|
254
|
+
function marketplaceRef(marketplace, name) {
|
|
426
255
|
return {
|
|
427
|
-
name
|
|
256
|
+
name,
|
|
428
257
|
...marketplace.path ? { path: marketplace.path } : {},
|
|
429
258
|
...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
|
|
430
259
|
};
|
|
431
260
|
}
|
|
261
|
+
/** True for either supported OpenAI curated marketplace wire name. */
|
|
432
262
|
function isOpenAiCuratedMarketplace(marketplace) {
|
|
433
263
|
return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME;
|
|
434
264
|
}
|
|
435
265
|
//#endregion
|
|
436
266
|
//#region extensions/codex/src/app-server/plugin-activation.ts
|
|
437
|
-
/**
|
|
267
|
+
/** Activates a curated plugin or rejects a workspace plugin that is not already active. */
|
|
438
268
|
async function ensureCodexPluginActivation(params) {
|
|
439
|
-
if (params.identity.marketplaceName
|
|
440
|
-
const listed = await params
|
|
269
|
+
if (params.identity.marketplaceName === "workspace-directory") return activationFailure(params.identity, "disabled", { message: "workspace-directory plugins must be installed and enabled outside OpenClaw before use." });
|
|
270
|
+
const listed = await listCuratedCodexPluginMetadata(params);
|
|
441
271
|
const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName);
|
|
442
272
|
if (!resolved) {
|
|
443
|
-
if (!listed.marketplaces.some(
|
|
273
|
+
if (!listed.marketplaces.some(isOpenAiCuratedMarketplace)) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
|
|
444
274
|
return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
|
|
445
275
|
}
|
|
446
276
|
if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
|
|
@@ -452,6 +282,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
452
282
|
diagnostics: []
|
|
453
283
|
};
|
|
454
284
|
const installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, resolved.marketplace.remoteMarketplaceName && resolved.summary.remotePluginId ? resolved.summary.remotePluginId : params.identity.pluginName));
|
|
285
|
+
if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
|
|
455
286
|
const refreshDiagnostics = [];
|
|
456
287
|
let refreshFailed = false;
|
|
457
288
|
try {
|
|
@@ -459,6 +290,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
459
290
|
request: params.request,
|
|
460
291
|
appCache: params.appCache,
|
|
461
292
|
appCacheKey: params.appCacheKey,
|
|
293
|
+
metadataCache: params.metadataCache,
|
|
462
294
|
targetAppIds: params.targetAppIds
|
|
463
295
|
});
|
|
464
296
|
refreshDiagnostics.push(...refreshResult.diagnostics);
|
|
@@ -480,7 +312,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
480
312
|
/** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
|
|
481
313
|
async function refreshCodexPluginRuntimeState(params) {
|
|
482
314
|
const diagnostics = [];
|
|
483
|
-
await params
|
|
315
|
+
await listCuratedCodexPluginMetadata(params);
|
|
484
316
|
await params.request("skills/list", {
|
|
485
317
|
cwds: [],
|
|
486
318
|
forceReload: true
|
|
@@ -507,6 +339,17 @@ async function refreshCodexPluginRuntimeState(params) {
|
|
|
507
339
|
}
|
|
508
340
|
return { diagnostics };
|
|
509
341
|
}
|
|
342
|
+
async function listCuratedCodexPluginMetadata(params) {
|
|
343
|
+
const requestParams = {};
|
|
344
|
+
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
|
|
345
|
+
return (await params.metadataCache.load({
|
|
346
|
+
appCacheKey: params.appCacheKey,
|
|
347
|
+
queryKind: "curated-global",
|
|
348
|
+
requestParams,
|
|
349
|
+
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
350
|
+
cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
351
|
+
})).response;
|
|
352
|
+
}
|
|
510
353
|
function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
|
|
511
354
|
return {
|
|
512
355
|
identity,
|
|
@@ -537,6 +380,20 @@ function buildCodexPluginThreadConfigInputFingerprint(params) {
|
|
|
537
380
|
appCacheKey: params.appCacheKey ?? null
|
|
538
381
|
});
|
|
539
382
|
}
|
|
383
|
+
/** Builds the deny-all app patch used when plugin discovery exceeds its turn budget. */
|
|
384
|
+
function buildCodexPluginThreadConfigTimeoutFallback(params) {
|
|
385
|
+
return {
|
|
386
|
+
...emptyPluginThreadConfig({
|
|
387
|
+
enabled: true,
|
|
388
|
+
inputFingerprint: buildCodexPluginThreadConfigInputFingerprint(params),
|
|
389
|
+
configPatch: buildDisabledAppsConfigPatch()
|
|
390
|
+
}),
|
|
391
|
+
diagnostics: [{
|
|
392
|
+
code: "plugin_config_timeout",
|
|
393
|
+
message: params.message
|
|
394
|
+
}]
|
|
395
|
+
};
|
|
396
|
+
}
|
|
540
397
|
/** Builds the Codex apps config patch and policy context for a native thread. */
|
|
541
398
|
async function buildCodexPluginThreadConfig(params) {
|
|
542
399
|
const appCache = params.appCache ?? defaultCodexAppInventoryCache;
|
|
@@ -556,6 +413,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
556
413
|
request: params.request,
|
|
557
414
|
appCache,
|
|
558
415
|
appCacheKey: params.appCacheKey,
|
|
416
|
+
metadataCache: params.metadataCache,
|
|
559
417
|
nowMs: params.nowMs,
|
|
560
418
|
suppressAppInventoryRefresh: true
|
|
561
419
|
}) : emptyCodexPluginInventory(policy);
|
|
@@ -572,6 +430,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
572
430
|
request: params.request,
|
|
573
431
|
appCache,
|
|
574
432
|
appCacheKey: params.appCacheKey,
|
|
433
|
+
metadataCache: params.metadataCache,
|
|
575
434
|
nowMs: params.nowMs
|
|
576
435
|
});
|
|
577
436
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -588,6 +447,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
588
447
|
request: params.request,
|
|
589
448
|
appCache,
|
|
590
449
|
appCacheKey: params.appCacheKey,
|
|
450
|
+
metadataCache: params.metadataCache,
|
|
591
451
|
targetAppIds: record.ownedAppIds
|
|
592
452
|
});
|
|
593
453
|
activationResults.push(activation);
|
|
@@ -611,6 +471,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
611
471
|
request: params.request,
|
|
612
472
|
appCache,
|
|
613
473
|
appCacheKey: params.appCacheKey,
|
|
474
|
+
metadataCache: params.metadataCache,
|
|
614
475
|
nowMs: params.nowMs
|
|
615
476
|
});
|
|
616
477
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -630,6 +491,7 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
630
491
|
request: params.request,
|
|
631
492
|
appCache,
|
|
632
493
|
appCacheKey: params.appCacheKey,
|
|
494
|
+
metadataCache: params.metadataCache,
|
|
633
495
|
nowMs: params.nowMs
|
|
634
496
|
});
|
|
635
497
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -947,127 +809,6 @@ function stableStringify(value) {
|
|
|
947
809
|
return JSON.stringify(value);
|
|
948
810
|
}
|
|
949
811
|
//#endregion
|
|
950
|
-
//#region extensions/codex/src/app-server/attempt-client-cleanup.ts
|
|
951
|
-
/**
|
|
952
|
-
* Best-effort cleanup helpers for Codex app-server startup attempts and turns.
|
|
953
|
-
*/
|
|
954
|
-
/** Timeout for best-effort app-server turn interruption during cleanup. */
|
|
955
|
-
const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
956
|
-
/** Timeout for best-effort thread unsubscribe during cleanup. */
|
|
957
|
-
const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
|
|
958
|
-
/** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
|
|
959
|
-
var CodexAppServerUnsafeSubscriptionError = class extends Error {
|
|
960
|
-
constructor(message, options) {
|
|
961
|
-
super(message, options);
|
|
962
|
-
this.name = "CodexAppServerUnsafeSubscriptionError";
|
|
963
|
-
}
|
|
964
|
-
};
|
|
965
|
-
function isCodexAppServerUnsafeSubscriptionError(error) {
|
|
966
|
-
return error instanceof CodexAppServerUnsafeSubscriptionError;
|
|
967
|
-
}
|
|
968
|
-
/** Asserts Codex resumed the exact thread this attempt subscribed to. */
|
|
969
|
-
function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
|
|
970
|
-
if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
|
|
971
|
-
}
|
|
972
|
-
async function closeClientAndWaitIfAvailable(client) {
|
|
973
|
-
const closeable = client;
|
|
974
|
-
if (typeof closeable.closeAndWait === "function") {
|
|
975
|
-
await closeable.closeAndWait();
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
closeable.close?.();
|
|
979
|
-
}
|
|
980
|
-
async function closeCodexStartupClientBestEffort(client) {
|
|
981
|
-
if (!client) return;
|
|
982
|
-
const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
|
|
983
|
-
if (unclaimedSharedClient.closed) {
|
|
984
|
-
await closeClientAndWaitIfAvailable(client);
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
if (unclaimedSharedClient.found) {
|
|
988
|
-
if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
|
|
992
|
-
if (retiredSharedClient) {
|
|
993
|
-
if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
if (clearSharedCodexAppServerClientIfCurrent(client)) {
|
|
997
|
-
await closeClientAndWaitIfAvailable(client);
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
await closeClientAndWaitIfAvailable(client);
|
|
1001
|
-
}
|
|
1002
|
-
/** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
|
|
1003
|
-
function interruptCodexTurnBestEffort(client, params) {
|
|
1004
|
-
const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
|
|
1005
|
-
const requestParams = {
|
|
1006
|
-
threadId: params.threadId,
|
|
1007
|
-
turnId: params.turnId
|
|
1008
|
-
};
|
|
1009
|
-
try {
|
|
1010
|
-
const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
|
|
1011
|
-
Promise.resolve(interrupt).catch((error) => {
|
|
1012
|
-
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1013
|
-
});
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
/** Unsubscribes from a thread while swallowing cleanup-only failures. */
|
|
1019
|
-
async function unsubscribeCodexThreadBestEffort(client, params) {
|
|
1020
|
-
try {
|
|
1021
|
-
await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
|
|
1022
|
-
return true;
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
|
|
1025
|
-
threadId: params.threadId,
|
|
1026
|
-
error
|
|
1027
|
-
});
|
|
1028
|
-
return false;
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
/**
|
|
1032
|
-
* Retires the shared client after a timed-out turn so later runs do not reuse a
|
|
1033
|
-
* potentially wedged app-server connection.
|
|
1034
|
-
*/
|
|
1035
|
-
async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
|
|
1036
|
-
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
|
|
1037
|
-
const detachedSharedClient = Boolean(retiredSharedClient);
|
|
1038
|
-
interruptCodexTurnBestEffort(client, {
|
|
1039
|
-
threadId: params.threadId,
|
|
1040
|
-
turnId: params.turnId,
|
|
1041
|
-
timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
|
|
1042
|
-
});
|
|
1043
|
-
await unsubscribeCodexThreadBestEffort(client, {
|
|
1044
|
-
threadId: params.threadId,
|
|
1045
|
-
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
1046
|
-
});
|
|
1047
|
-
let closedClient = retiredSharedClient?.closed ?? false;
|
|
1048
|
-
if (!detachedSharedClient) {
|
|
1049
|
-
const close = client.close;
|
|
1050
|
-
if (typeof close === "function") try {
|
|
1051
|
-
close.call(client);
|
|
1052
|
-
closedClient = true;
|
|
1053
|
-
} catch (error) {
|
|
1054
|
-
embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
|
|
1055
|
-
threadId: params.threadId,
|
|
1056
|
-
turnId: params.turnId,
|
|
1057
|
-
error
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
|
|
1062
|
-
threadId: params.threadId,
|
|
1063
|
-
turnId: params.turnId,
|
|
1064
|
-
reason: params.reason,
|
|
1065
|
-
detachedSharedClient,
|
|
1066
|
-
closedClient,
|
|
1067
|
-
activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
//#endregion
|
|
1071
812
|
//#region extensions/codex/src/app-server/context-engine-projection.ts
|
|
1072
813
|
const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
|
|
1073
814
|
const CONTEXT_OPEN = "<conversation_context>";
|
|
@@ -1128,7 +869,7 @@ function resolveCodexContextEngineProjectionReserveTokens(params) {
|
|
|
1128
869
|
const compaction = asRecord(asRecord(asRecord(params.config)?.agents)?.defaults)?.compaction;
|
|
1129
870
|
const configuredReserveTokens = toNonNegativeInt(asRecord(compaction)?.reserveTokens);
|
|
1130
871
|
const configuredReserveTokensFloor = toNonNegativeInt(asRecord(compaction)?.reserveTokensFloor);
|
|
1131
|
-
if (configuredReserveTokens !== void 0) return Math.max(configuredReserveTokens, configuredReserveTokensFloor ??
|
|
872
|
+
if (configuredReserveTokens !== void 0) return Math.max(configuredReserveTokens, configuredReserveTokensFloor ?? DEFAULT_CODEX_PROJECTION_RESERVE_TOKENS);
|
|
1132
873
|
if (configuredReserveTokensFloor !== void 0) return configuredReserveTokensFloor;
|
|
1133
874
|
}
|
|
1134
875
|
/** Fits projected context prompts under Codex app-server turn/start text limits. */
|
|
@@ -1311,7 +1052,9 @@ function resolveTextPartMaxChars(maxRenderedContextChars) {
|
|
|
1311
1052
|
return Math.min(MAX_TEXT_PART_CHARS, Math.max(DEFAULT_TEXT_PART_CHARS, Math.floor(maxRenderedContextChars / 4)));
|
|
1312
1053
|
}
|
|
1313
1054
|
function truncateText(text, maxChars) {
|
|
1314
|
-
|
|
1055
|
+
if (text.length <= maxChars) return text;
|
|
1056
|
+
const truncated = truncateUtf16Safe(text, maxChars);
|
|
1057
|
+
return `${truncated}\n[truncated ${text.length - truncated.length} chars]`;
|
|
1315
1058
|
}
|
|
1316
1059
|
function truncateOlderContext(text, maxChars) {
|
|
1317
1060
|
if (text.length <= maxChars) return text;
|
|
@@ -1322,53 +1065,312 @@ function truncateOlderContext(text, maxChars) {
|
|
|
1322
1065
|
marker = buildMarker(text.length - tailChars);
|
|
1323
1066
|
if (marker.length >= maxChars) return marker.slice(0, maxChars);
|
|
1324
1067
|
tailChars = maxChars - marker.length;
|
|
1325
|
-
return `${marker}${
|
|
1326
|
-
}
|
|
1327
|
-
function sliceTailFromCodePointBoundary(text, tailChars) {
|
|
1328
|
-
let start = text.length - tailChars;
|
|
1329
|
-
if (start > 0 && start < text.length) {
|
|
1330
|
-
const code = text.charCodeAt(start);
|
|
1331
|
-
if (code >= 56320 && code <= 57343) start += 1;
|
|
1332
|
-
}
|
|
1333
|
-
return text.slice(start);
|
|
1068
|
+
return `${marker}${sliceUtf16Safe(text, -tailChars).trimStart()}`;
|
|
1334
1069
|
}
|
|
1335
1070
|
//#endregion
|
|
1336
|
-
//#region extensions/codex/src/app-server/
|
|
1337
|
-
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
}
|
|
1360
|
-
/** Returns true for private QA runs that force the Codex runtime profile. */
|
|
1361
|
-
function isForcedPrivateQaCodexRuntime(env = process.env) {
|
|
1362
|
-
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
1071
|
+
//#region extensions/codex/src/app-server/thread-context-engine.ts
|
|
1072
|
+
function buildContextEngineBinding(params, projection) {
|
|
1073
|
+
const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
|
|
1074
|
+
const engineId = contextEngine?.info?.id?.trim();
|
|
1075
|
+
if (!contextEngine || !engineId) return;
|
|
1076
|
+
return {
|
|
1077
|
+
schemaVersion: 1,
|
|
1078
|
+
engineId,
|
|
1079
|
+
policyFingerprint: JSON.stringify({
|
|
1080
|
+
schemaVersion: 1,
|
|
1081
|
+
engineId,
|
|
1082
|
+
engineVersion: contextEngine.info.version,
|
|
1083
|
+
ownsCompaction: contextEngine.info.ownsCompaction === true,
|
|
1084
|
+
turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
|
|
1085
|
+
citationsMode: resolveContextEngineCitationsMode(params.config),
|
|
1086
|
+
contextTokenBudget: params.contextTokenBudget,
|
|
1087
|
+
projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
|
|
1088
|
+
contextTokenBudget: params.contextTokenBudget,
|
|
1089
|
+
reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
|
|
1090
|
+
})
|
|
1091
|
+
}),
|
|
1092
|
+
projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
|
|
1093
|
+
};
|
|
1363
1094
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1095
|
+
function buildContextEngineProjectionBinding(projection) {
|
|
1096
|
+
return {
|
|
1097
|
+
schemaVersion: 1,
|
|
1098
|
+
mode: "thread_bootstrap",
|
|
1099
|
+
epoch: projection.epoch,
|
|
1100
|
+
fingerprint: projection.fingerprint
|
|
1101
|
+
};
|
|
1367
1102
|
}
|
|
1368
|
-
function
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1103
|
+
function isContextEngineBindingCompatible(previous, next) {
|
|
1104
|
+
return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
|
|
1105
|
+
}
|
|
1106
|
+
function areContextEngineProjectionBindingsCompatible(previous, next) {
|
|
1107
|
+
if (!next) return previous === void 0;
|
|
1108
|
+
return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
|
|
1109
|
+
}
|
|
1110
|
+
function resolveContextEngineCitationsMode(config) {
|
|
1111
|
+
const rootConfig = isUnknownRecord(config) ? config : void 0;
|
|
1112
|
+
const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
|
|
1113
|
+
return isJsonConfigValue(citations) ? citations : void 0;
|
|
1114
|
+
}
|
|
1115
|
+
function isUnknownRecord(value) {
|
|
1116
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1117
|
+
}
|
|
1118
|
+
function isJsonConfigValue(value) {
|
|
1119
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1120
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1121
|
+
if (Array.isArray(value)) return value.every(isJsonConfigValue);
|
|
1122
|
+
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
|
|
1123
|
+
}
|
|
1124
|
+
//#endregion
|
|
1125
|
+
//#region extensions/codex/src/app-server/thread-fingerprints.ts
|
|
1126
|
+
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
1127
|
+
return fingerprintDynamicTools(dynamicTools);
|
|
1128
|
+
}
|
|
1129
|
+
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
1130
|
+
return legacyFingerprintDynamicTools(dynamicTools);
|
|
1131
|
+
}
|
|
1132
|
+
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
1133
|
+
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
1134
|
+
}
|
|
1135
|
+
function fingerprintDynamicTools(dynamicTools) {
|
|
1136
|
+
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
1137
|
+
}
|
|
1138
|
+
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
1139
|
+
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
1140
|
+
}
|
|
1141
|
+
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1142
|
+
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
1143
|
+
}
|
|
1144
|
+
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1145
|
+
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
|
|
1146
|
+
}
|
|
1147
|
+
function redactUserMcpServersFingerprintSecrets(value) {
|
|
1148
|
+
if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
|
|
1149
|
+
if (!value || typeof value !== "object") return value;
|
|
1150
|
+
const next = {};
|
|
1151
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1152
|
+
if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1153
|
+
next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
next[key] = redactUserMcpServersFingerprintSecrets(entry);
|
|
1157
|
+
}
|
|
1158
|
+
return next;
|
|
1159
|
+
}
|
|
1160
|
+
function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
1161
|
+
return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
|
|
1162
|
+
}
|
|
1163
|
+
function fingerprintJsonObject(value) {
|
|
1164
|
+
return JSON.stringify(stabilizeJsonValue(value));
|
|
1165
|
+
}
|
|
1166
|
+
function fingerprintEnvironmentSelection(environments) {
|
|
1167
|
+
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
1168
|
+
}
|
|
1169
|
+
function fingerprintDynamicToolSpec(tool) {
|
|
1170
|
+
return stabilizeDynamicToolFingerprintValue(tool);
|
|
1171
|
+
}
|
|
1172
|
+
function stabilizeDynamicToolFingerprintValue(value) {
|
|
1173
|
+
if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
|
|
1174
|
+
if (!isJsonObject(value)) return value;
|
|
1175
|
+
const stable = {};
|
|
1176
|
+
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
1177
|
+
if (key === "description") continue;
|
|
1178
|
+
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
|
1179
|
+
}
|
|
1180
|
+
return stable;
|
|
1181
|
+
}
|
|
1182
|
+
function stabilizeJsonValue(value) {
|
|
1183
|
+
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
1184
|
+
if (!isJsonObject(value)) return value;
|
|
1185
|
+
const stable = {};
|
|
1186
|
+
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
1187
|
+
return stable;
|
|
1188
|
+
}
|
|
1189
|
+
function readActiveCodexTurnIds(thread) {
|
|
1190
|
+
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
1191
|
+
}
|
|
1192
|
+
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
1193
|
+
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
1194
|
+
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
1195
|
+
return !previous || previous === next || previous === nextLegacy;
|
|
1196
|
+
}
|
|
1197
|
+
function areUserMcpServersFingerprintsCompatible(params) {
|
|
1198
|
+
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
1199
|
+
}
|
|
1200
|
+
function shouldStartTransientNoToolThread(params) {
|
|
1201
|
+
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
1202
|
+
}
|
|
1203
|
+
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
1204
|
+
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
1205
|
+
}
|
|
1206
|
+
function compareJsonFingerprint(left, right) {
|
|
1207
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1208
|
+
}
|
|
1209
|
+
//#endregion
|
|
1210
|
+
//#region extensions/codex/src/app-server/attempt-client-cleanup.ts
|
|
1211
|
+
/**
|
|
1212
|
+
* Best-effort cleanup helpers for Codex app-server startup attempts and turns.
|
|
1213
|
+
*/
|
|
1214
|
+
/** Timeout for best-effort app-server turn interruption during cleanup. */
|
|
1215
|
+
const CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS = 5e3;
|
|
1216
|
+
/** Timeout for best-effort thread unsubscribe during cleanup. */
|
|
1217
|
+
const CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS = 5e3;
|
|
1218
|
+
/** Raised when a thread subscription may be live on a client OpenClaw no longer controls. */
|
|
1219
|
+
var CodexAppServerUnsafeSubscriptionError = class extends Error {
|
|
1220
|
+
constructor(message, options) {
|
|
1221
|
+
super(message, options);
|
|
1222
|
+
this.name = "CodexAppServerUnsafeSubscriptionError";
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
function isCodexAppServerUnsafeSubscriptionError(error) {
|
|
1226
|
+
return error instanceof CodexAppServerUnsafeSubscriptionError;
|
|
1227
|
+
}
|
|
1228
|
+
/** Asserts Codex resumed the exact thread this attempt subscribed to. */
|
|
1229
|
+
function assertCodexThreadResumeSubscription(requestedThreadId, returnedThreadId) {
|
|
1230
|
+
if (returnedThreadId !== requestedThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume returned ${returnedThreadId} for ${requestedThreadId}`);
|
|
1231
|
+
}
|
|
1232
|
+
async function closeClientAndWaitIfAvailable(client) {
|
|
1233
|
+
const closeable = client;
|
|
1234
|
+
if (typeof closeable.closeAndWait === "function") {
|
|
1235
|
+
await closeable.closeAndWait();
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
closeable.close?.();
|
|
1239
|
+
}
|
|
1240
|
+
async function closeCodexStartupClientBestEffort(client) {
|
|
1241
|
+
if (!client) return;
|
|
1242
|
+
const unclaimedSharedClient = clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client);
|
|
1243
|
+
if (unclaimedSharedClient.closed) {
|
|
1244
|
+
await closeClientAndWaitIfAvailable(client);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (unclaimedSharedClient.found) {
|
|
1248
|
+
if (retireSharedCodexAppServerClientIfCurrent(client)?.closed) await closeClientAndWaitIfAvailable(client);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
|
|
1252
|
+
if (retiredSharedClient) {
|
|
1253
|
+
if (retiredSharedClient.closed) await closeClientAndWaitIfAvailable(client);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (clearSharedCodexAppServerClientIfCurrent(client)) {
|
|
1257
|
+
await closeClientAndWaitIfAvailable(client);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
await closeClientAndWaitIfAvailable(client);
|
|
1261
|
+
}
|
|
1262
|
+
/** Sends a turn interrupt without blocking abort cleanup on app-server errors. */
|
|
1263
|
+
function interruptCodexTurnBestEffort(client, params) {
|
|
1264
|
+
const requestOptions = params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0 ? { timeoutMs: params.timeoutMs } : void 0;
|
|
1265
|
+
const requestParams = {
|
|
1266
|
+
threadId: params.threadId,
|
|
1267
|
+
turnId: params.turnId
|
|
1268
|
+
};
|
|
1269
|
+
try {
|
|
1270
|
+
const interrupt = requestOptions ? client.request("turn/interrupt", requestParams, requestOptions) : client.request("turn/interrupt", requestParams);
|
|
1271
|
+
Promise.resolve(interrupt).catch((error) => {
|
|
1272
|
+
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1273
|
+
});
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
/** Unsubscribes from a thread while swallowing cleanup-only failures. */
|
|
1279
|
+
async function unsubscribeCodexThreadBestEffort(client, params) {
|
|
1280
|
+
try {
|
|
1281
|
+
await client.request("thread/unsubscribe", { threadId: params.threadId }, { timeoutMs: params.timeoutMs });
|
|
1282
|
+
return true;
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
embeddedAgentLog.debug("codex app-server thread unsubscribe cleanup failed", {
|
|
1285
|
+
threadId: params.threadId,
|
|
1286
|
+
error
|
|
1287
|
+
});
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Retires the shared client after a timed-out turn so later runs do not reuse a
|
|
1293
|
+
* potentially wedged app-server connection.
|
|
1294
|
+
*/
|
|
1295
|
+
async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
|
|
1296
|
+
const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client, { failActiveLeases: params.suspectPhysicalClient });
|
|
1297
|
+
const detachedSharedClient = Boolean(retiredSharedClient);
|
|
1298
|
+
if (!(params.suspectPhysicalClient && (retiredSharedClient?.closed ?? false))) {
|
|
1299
|
+
interruptCodexTurnBestEffort(client, {
|
|
1300
|
+
threadId: params.threadId,
|
|
1301
|
+
turnId: params.turnId,
|
|
1302
|
+
timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
|
|
1303
|
+
});
|
|
1304
|
+
await unsubscribeCodexThreadBestEffort(client, {
|
|
1305
|
+
threadId: params.threadId,
|
|
1306
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
let closedClient = retiredSharedClient?.closed ?? false;
|
|
1310
|
+
if (!detachedSharedClient) {
|
|
1311
|
+
const close = client.close;
|
|
1312
|
+
if (typeof close === "function") try {
|
|
1313
|
+
close.call(client);
|
|
1314
|
+
closedClient = true;
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
|
|
1317
|
+
threadId: params.threadId,
|
|
1318
|
+
turnId: params.turnId,
|
|
1319
|
+
error
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
|
|
1324
|
+
threadId: params.threadId,
|
|
1325
|
+
turnId: params.turnId,
|
|
1326
|
+
reason: params.reason,
|
|
1327
|
+
detachedSharedClient,
|
|
1328
|
+
closedClient,
|
|
1329
|
+
activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region extensions/codex/src/app-server/dynamic-tool-profile.ts
|
|
1334
|
+
/** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
|
|
1335
|
+
const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
|
|
1336
|
+
"read",
|
|
1337
|
+
"write",
|
|
1338
|
+
"edit",
|
|
1339
|
+
"apply_patch",
|
|
1340
|
+
"exec",
|
|
1341
|
+
"process",
|
|
1342
|
+
"update_plan",
|
|
1343
|
+
"tool_call",
|
|
1344
|
+
"tool_describe",
|
|
1345
|
+
"tool_search",
|
|
1346
|
+
"tool_search_code"
|
|
1347
|
+
];
|
|
1348
|
+
const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
|
|
1349
|
+
const DYNAMIC_TOOL_NAME_ALIASES = {
|
|
1350
|
+
bash: "exec",
|
|
1351
|
+
"apply-patch": "apply_patch"
|
|
1352
|
+
};
|
|
1353
|
+
/** Normalizes OpenClaw/Codex tool names before filtering and allowlist checks. */
|
|
1354
|
+
function normalizeCodexDynamicToolName(name) {
|
|
1355
|
+
const normalized = name.trim().toLowerCase();
|
|
1356
|
+
return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
|
|
1357
|
+
}
|
|
1358
|
+
/** True only for the host-scoped OpenClaw run's exact tool contract. */
|
|
1359
|
+
function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
|
|
1360
|
+
return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
|
|
1361
|
+
}
|
|
1362
|
+
/** Returns true for private QA runs that force the Codex runtime profile. */
|
|
1363
|
+
function isForcedPrivateQaCodexRuntime(env = process.env) {
|
|
1364
|
+
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
1365
|
+
}
|
|
1366
|
+
/** Resolves whether dynamic tools load directly or through Codex tool search. */
|
|
1367
|
+
function resolveCodexDynamicToolsLoading(config, env = process.env) {
|
|
1368
|
+
return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
|
|
1369
|
+
}
|
|
1370
|
+
function normalizeCodexModelId(modelId) {
|
|
1371
|
+
const normalized = modelId?.trim().toLowerCase();
|
|
1372
|
+
if (!normalized) return "";
|
|
1373
|
+
return normalized.includes("/") ? normalized.split("/").at(-1) : normalized;
|
|
1372
1374
|
}
|
|
1373
1375
|
/** Returns true when model behavior requires direct dynamic-tool registration. */
|
|
1374
1376
|
function shouldUseDirectCodexDynamicToolsForModel(modelId) {
|
|
@@ -1390,8 +1392,18 @@ function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}
|
|
|
1390
1392
|
}
|
|
1391
1393
|
/** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
|
|
1392
1394
|
function filterCodexDynamicTools(tools, config, env = process.env) {
|
|
1395
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: false });
|
|
1396
|
+
}
|
|
1397
|
+
/** Keeps exec/process only when Codex cannot advertise an environment-backed native shell. */
|
|
1398
|
+
function filterCodexDynamicToolsWithOpenClawShell(tools, config, env = process.env) {
|
|
1399
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: true });
|
|
1400
|
+
}
|
|
1401
|
+
function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
1393
1402
|
const excludes = /* @__PURE__ */ new Set();
|
|
1394
|
-
if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES)
|
|
1403
|
+
if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
|
|
1404
|
+
if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
|
|
1405
|
+
excludes.add(name);
|
|
1406
|
+
}
|
|
1395
1407
|
for (const name of config.codexDynamicToolsExclude ?? []) {
|
|
1396
1408
|
const trimmed = normalizeCodexDynamicToolName(name);
|
|
1397
1409
|
if (trimmed) excludes.add(trimmed);
|
|
@@ -1399,98 +1411,268 @@ function filterCodexDynamicTools(tools, config, env = process.env) {
|
|
|
1399
1411
|
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
1400
1412
|
}
|
|
1401
1413
|
//#endregion
|
|
1402
|
-
//#region extensions/codex/src/app-server/
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
1408
|
-
/** Validates and normalizes an inline image data URL for Codex history payloads. */
|
|
1409
|
-
function sanitizeInlineImageDataUrl$1(imageUrl) {
|
|
1410
|
-
return sanitizeInlineImageDataUrl(imageUrl);
|
|
1414
|
+
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
1415
|
+
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
1416
|
+
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
1417
|
+
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
1418
|
+
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
1411
1419
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1420
|
+
//#endregion
|
|
1421
|
+
//#region extensions/codex/src/app-server/thread-binding-policy.ts
|
|
1422
|
+
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
1423
|
+
if (!params.current) return false;
|
|
1424
|
+
if (params.binding === params.current) return false;
|
|
1425
|
+
return params.connectionClass === "remote" || Boolean(params.binding);
|
|
1415
1426
|
}
|
|
1416
|
-
function
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
const commaIndex = imageUrl.indexOf(",");
|
|
1425
|
-
const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
|
|
1426
|
-
return {
|
|
1427
|
-
...record,
|
|
1428
|
-
mimeType: mime,
|
|
1429
|
-
data: imageUrl.slice(commaIndex + 1)
|
|
1430
|
-
};
|
|
1427
|
+
function resolveCodexGpt56MultiAgentVersion(modelRef) {
|
|
1428
|
+
let modelId = modelRef?.trim().toLowerCase();
|
|
1429
|
+
if (!modelId) return;
|
|
1430
|
+
const slashIndex = modelId.indexOf("/");
|
|
1431
|
+
if (slashIndex > 0) {
|
|
1432
|
+
const provider = modelId.slice(0, slashIndex);
|
|
1433
|
+
if (provider !== "openai" && provider !== "codex") return;
|
|
1434
|
+
modelId = modelId.slice(slashIndex + 1);
|
|
1431
1435
|
}
|
|
1432
|
-
if (
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1436
|
+
if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
|
|
1437
|
+
return modelId === "gpt-5.6-luna" ? "v1" : void 0;
|
|
1438
|
+
}
|
|
1439
|
+
function shouldRotateCodexGpt56MultiAgentBinding(params) {
|
|
1440
|
+
const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
|
|
1441
|
+
const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
|
|
1442
|
+
return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
|
|
1443
|
+
}
|
|
1444
|
+
function isTransientWebSearchRestriction(params) {
|
|
1445
|
+
if (params.nativeProviderWebSearchSupport === "unknown") return true;
|
|
1446
|
+
if (params.params.config?.tools?.web?.search?.enabled === false) return false;
|
|
1447
|
+
if (params.params.disableTools === true) return true;
|
|
1448
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
1449
|
+
if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
|
|
1450
|
+
if (params.webSearchAllowed !== false) return false;
|
|
1451
|
+
if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
|
|
1452
|
+
if (params.params.toolsAllow === void 0) return false;
|
|
1453
|
+
return !params.params.toolsAllow.some((name) => {
|
|
1454
|
+
const normalized = normalizeCodexDynamicToolName(name);
|
|
1455
|
+
return normalized === "*" || normalized === "web_search";
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
function shouldRecheckRecoverablePluginBinding(params) {
|
|
1459
|
+
if (!params.pluginThreadConfig?.enabled) return false;
|
|
1460
|
+
if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
|
|
1461
|
+
const policyContext = params.binding.pluginAppPolicyContext;
|
|
1462
|
+
if (!policyContext) return false;
|
|
1463
|
+
const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
|
|
1464
|
+
const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
|
|
1465
|
+
const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
|
|
1466
|
+
const bindingContainsSettledPlugin = enabledPluginConfigKeys.filter((configKey) => !recoverablePluginConfigKeySet.has(configKey)).some((configKey) => (policyContext.pluginAppIds[configKey]?.length ?? 0) > 0 || Object.values(policyContext.apps).some((app) => app.source !== "account" && app.configKey === configKey));
|
|
1467
|
+
const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
|
|
1468
|
+
return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
|
|
1469
|
+
}
|
|
1470
|
+
//#endregion
|
|
1471
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
|
|
1472
|
+
var CodexThreadStartRequestError = class extends Error {
|
|
1473
|
+
constructor(cause) {
|
|
1474
|
+
super(formatErrorMessage(cause), { cause });
|
|
1475
|
+
this.name = "CodexThreadStartRequestError";
|
|
1441
1476
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
} : {
|
|
1448
|
-
type: "input_text",
|
|
1449
|
-
text: invalidInlineImageText(label)
|
|
1450
|
-
};
|
|
1477
|
+
};
|
|
1478
|
+
var CodexThreadBindingConflictError = class extends Error {
|
|
1479
|
+
constructor(threadId, operation) {
|
|
1480
|
+
super(`Codex thread binding changed while ${operation}: ${threadId}`);
|
|
1481
|
+
this.name = "CodexThreadBindingConflictError";
|
|
1451
1482
|
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1483
|
+
};
|
|
1484
|
+
var CodexRingZeroAttestationError = class extends Error {
|
|
1485
|
+
constructor(cause) {
|
|
1486
|
+
super("Codex ring-zero MCP attestation failed", { cause });
|
|
1487
|
+
this.name = "CodexRingZeroAttestationError";
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
var CodexThreadBindingConflictAfterCleanupError = class extends CodexThreadBindingConflictError {};
|
|
1491
|
+
var CodexAdoptedThreadActiveError = class extends Error {
|
|
1492
|
+
constructor() {
|
|
1493
|
+
super("Codex session became active in another runner; wait for it to finish before continuing");
|
|
1494
|
+
this.name = "CodexAdoptedThreadActiveError";
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
//#endregion
|
|
1498
|
+
//#region extensions/codex/src/app-server/reasoning-effort.ts
|
|
1499
|
+
const CODEX_REASONING_EFFORTS = [
|
|
1500
|
+
"minimal",
|
|
1501
|
+
"low",
|
|
1502
|
+
"medium",
|
|
1503
|
+
"high",
|
|
1504
|
+
"xhigh",
|
|
1505
|
+
"max",
|
|
1506
|
+
"ultra"
|
|
1507
|
+
];
|
|
1508
|
+
const GPT_56_MAX_REASONING_EFFORTS = [
|
|
1509
|
+
"low",
|
|
1510
|
+
"medium",
|
|
1511
|
+
"high",
|
|
1512
|
+
"xhigh",
|
|
1513
|
+
"max"
|
|
1514
|
+
];
|
|
1515
|
+
const GPT_56_ULTRA_REASONING_EFFORTS = [...GPT_56_MAX_REASONING_EFFORTS, "ultra"];
|
|
1516
|
+
const GPT_5_PRO_REASONING_EFFORTS = [
|
|
1517
|
+
"medium",
|
|
1518
|
+
"high",
|
|
1519
|
+
"xhigh"
|
|
1520
|
+
];
|
|
1521
|
+
const GPT_56_ULTRA_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.6-sol", "gpt-5.6-terra"]);
|
|
1522
|
+
const GPT_56_MAX_MODEL_IDS = /* @__PURE__ */ new Set([...GPT_56_ULTRA_MODEL_IDS, "gpt-5.6-luna"]);
|
|
1523
|
+
const MODERN_CODEX_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
1524
|
+
...GPT_56_MAX_MODEL_IDS,
|
|
1525
|
+
"gpt-5.5",
|
|
1526
|
+
"gpt-5.5-pro",
|
|
1527
|
+
"gpt-5.4",
|
|
1528
|
+
"gpt-5.4-pro",
|
|
1529
|
+
"gpt-5.4-mini",
|
|
1530
|
+
"gpt-5.3-codex-spark"
|
|
1531
|
+
]);
|
|
1532
|
+
function normalizeCodexReasoningEfforts(efforts) {
|
|
1533
|
+
if (!efforts) return [];
|
|
1534
|
+
const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
|
|
1535
|
+
return CODEX_REASONING_EFFORTS.filter((effort) => supported.has(effort));
|
|
1536
|
+
}
|
|
1537
|
+
/** Read reasoning metadata after the Codex app-server route has been selected. */
|
|
1538
|
+
function readCodexSupportedReasoningEfforts(compat) {
|
|
1539
|
+
if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
|
|
1540
|
+
const efforts = compat.supportedReasoningEfforts;
|
|
1541
|
+
if (!Array.isArray(efforts)) return;
|
|
1542
|
+
return efforts.filter((effort) => typeof effort === "string");
|
|
1543
|
+
}
|
|
1544
|
+
function resolveSupportedReasoningEffort(params) {
|
|
1545
|
+
const supported = normalizeCodexReasoningEfforts(params.supportedReasoningEfforts);
|
|
1546
|
+
if (supported.includes(params.requested)) return params.requested;
|
|
1547
|
+
const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
|
|
1548
|
+
const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
|
|
1549
|
+
return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
|
|
1550
|
+
}
|
|
1551
|
+
function resolveFallbackReasoningEfforts(modelId) {
|
|
1552
|
+
const normalized = modelId.trim().toLowerCase();
|
|
1553
|
+
if (GPT_56_ULTRA_MODEL_IDS.has(normalized)) return GPT_56_ULTRA_REASONING_EFFORTS;
|
|
1554
|
+
if (normalized === "gpt-5.6-luna") return GPT_56_MAX_REASONING_EFFORTS;
|
|
1555
|
+
if (normalized === "gpt-5.5-pro" || normalized === "gpt-5.4-pro") return GPT_5_PRO_REASONING_EFFORTS;
|
|
1556
|
+
}
|
|
1557
|
+
/** Resolve a turn effort from app-server metadata, with exact-name offline fallbacks. */
|
|
1558
|
+
function resolveCodexAppServerReasoningEffort(params) {
|
|
1559
|
+
if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
|
|
1560
|
+
const supportedReasoningEfforts = params.supportedReasoningEfforts ?? resolveFallbackReasoningEfforts(params.modelId);
|
|
1561
|
+
if (supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
1562
|
+
requested: params.thinkLevel,
|
|
1563
|
+
supportedReasoningEfforts
|
|
1564
|
+
}) ?? null;
|
|
1565
|
+
const normalizedModelId = params.modelId.trim().toLowerCase();
|
|
1566
|
+
if (params.thinkLevel === "minimal") return MODERN_CODEX_MODEL_IDS.has(normalizedModelId) ? "low" : "minimal";
|
|
1567
|
+
if (params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
|
|
1568
|
+
return params.thinkLevel === "max" && GPT_56_MAX_MODEL_IDS.has(normalizedModelId) ? "max" : null;
|
|
1462
1569
|
}
|
|
1463
1570
|
//#endregion
|
|
1464
|
-
//#region extensions/codex/src/app-server/
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1571
|
+
//#region extensions/codex/src/app-server/thread-model-selection.ts
|
|
1572
|
+
const CODEX_NATIVE_PERSONALITY_NONE = "none";
|
|
1573
|
+
function resolveCodexBindingModelProviderFallback(params) {
|
|
1574
|
+
const provider = params.provider?.trim().toLowerCase();
|
|
1575
|
+
if (provider && provider !== "codex") return;
|
|
1576
|
+
const currentModel = params.currentModel?.trim();
|
|
1577
|
+
const bindingModel = params.bindingModel?.trim();
|
|
1578
|
+
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
1579
|
+
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
1580
|
+
}
|
|
1581
|
+
function resolveCodexAppServerThreadModelSelection(params) {
|
|
1582
|
+
const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
|
|
1583
|
+
const explicitModelProvider = resolveCodexAppServerModelProvider({
|
|
1584
|
+
provider: params.provider,
|
|
1585
|
+
authProfileId,
|
|
1586
|
+
authProfileStore: params.authProfileStore,
|
|
1587
|
+
agentDir: params.agentDir,
|
|
1588
|
+
config: params.config
|
|
1589
|
+
});
|
|
1590
|
+
const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
|
|
1591
|
+
provider: params.provider,
|
|
1592
|
+
currentModel: params.model,
|
|
1593
|
+
bindingModel: params.binding.model,
|
|
1594
|
+
bindingModelProvider: params.binding.modelProvider
|
|
1595
|
+
}) : void 0;
|
|
1596
|
+
return resolveCodexAppServerRequestModelSelection({
|
|
1597
|
+
model: params.model,
|
|
1598
|
+
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
1599
|
+
authProfileId,
|
|
1600
|
+
authProfileStore: params.authProfileStore,
|
|
1601
|
+
agentDir: params.agentDir,
|
|
1602
|
+
config: params.config
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
function resolveCodexAppServerRequestModelSelection(params) {
|
|
1606
|
+
const model = params.model.trim();
|
|
1607
|
+
const modelProvider = params.modelProvider?.trim();
|
|
1608
|
+
if (modelProvider) return {
|
|
1609
|
+
model,
|
|
1610
|
+
modelProvider
|
|
1611
|
+
};
|
|
1612
|
+
const slashIndex = model.indexOf("/");
|
|
1613
|
+
if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
|
|
1614
|
+
const inferredModelProvider = resolveCodexAppServerModelProvider({
|
|
1615
|
+
provider: model.slice(0, slashIndex),
|
|
1616
|
+
authProfileId: params.authProfileId,
|
|
1617
|
+
authProfileStore: params.authProfileStore,
|
|
1618
|
+
agentDir: params.agentDir,
|
|
1619
|
+
config: params.config
|
|
1620
|
+
});
|
|
1621
|
+
return {
|
|
1622
|
+
model: model.slice(slashIndex + 1).trim(),
|
|
1623
|
+
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
function hasProviderQualifiedModelRef(model) {
|
|
1627
|
+
const trimmed = model?.trim();
|
|
1628
|
+
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
|
1629
|
+
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
1630
|
+
}
|
|
1631
|
+
function resolveCodexAppServerModelProvider(params) {
|
|
1632
|
+
const normalized = params.provider.trim();
|
|
1633
|
+
const normalizedLower = normalized.toLowerCase();
|
|
1634
|
+
if (!normalized || normalizedLower === "codex") return;
|
|
1635
|
+
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
1636
|
+
return normalizedLower === "openai" ? "openai" : normalized;
|
|
1637
|
+
}
|
|
1638
|
+
function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
|
|
1639
|
+
return resolveCodexAppServerReasoningEffort({
|
|
1640
|
+
thinkLevel,
|
|
1641
|
+
modelId,
|
|
1642
|
+
supportedReasoningEfforts
|
|
1643
|
+
});
|
|
1469
1644
|
}
|
|
1470
1645
|
//#endregion
|
|
1471
|
-
//#region extensions/codex/src/app-server/thread-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
return
|
|
1646
|
+
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
1647
|
+
function buildDeveloperInstructions(params, options = {}) {
|
|
1648
|
+
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
1649
|
+
surface: "codex_app_server",
|
|
1650
|
+
includeLegacyGlobalGuidance: false
|
|
1651
|
+
}).join("\n");
|
|
1652
|
+
return [
|
|
1653
|
+
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
1654
|
+
buildDeferredDynamicToolManifest(options.dynamicTools),
|
|
1655
|
+
buildSkillWorkshopInstruction(options.dynamicTools),
|
|
1656
|
+
"Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.",
|
|
1657
|
+
buildVisibleReplyInstruction(params, options.dynamicTools),
|
|
1658
|
+
nativeCommandGuidance,
|
|
1659
|
+
params.extraSystemPrompt
|
|
1660
|
+
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
1661
|
+
}
|
|
1662
|
+
function buildDeferredDynamicToolManifest(dynamicTools) {
|
|
1663
|
+
const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
|
|
1664
|
+
if (deferredToolNames.length === 0) return;
|
|
1665
|
+
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
1666
|
+
}
|
|
1667
|
+
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
1668
|
+
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
1669
|
+
return buildSkillWorkshopPromptSection().join("\n");
|
|
1670
|
+
}
|
|
1671
|
+
function buildVisibleReplyInstruction(params, dynamicTools) {
|
|
1672
|
+
const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
|
|
1673
|
+
if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. For progress, set `final=false`. When the message is the completed reply to the current source conversation, set `final=true`; OpenClaw stops after confirming delivery. If `final` is omitted, OpenClaw continues and resolves the latest omitted source reply only when the turn ends successfully. Do not repeat visible message content in your final answer.";
|
|
1674
|
+
if (messageToolAvailable) return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation. Use `message` only for explicit out-of-band sends, media/file sends, or sends to a different target.";
|
|
1675
|
+
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
|
|
1494
1676
|
}
|
|
1495
1677
|
//#endregion
|
|
1496
1678
|
//#region extensions/codex/src/app-server/web-search.ts
|
|
@@ -1559,25 +1741,10 @@ function resolveCodexWebSearchPlan(params) {
|
|
|
1559
1741
|
threadConfig: buildCodexNativeWebSearchThreadConfig(params.config)
|
|
1560
1742
|
};
|
|
1561
1743
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
super(formatErrorMessage(cause), { cause });
|
|
1567
|
-
this.name = "CodexThreadStartRequestError";
|
|
1568
|
-
}
|
|
1569
|
-
};
|
|
1570
|
-
var CodexThreadBindingConflictError = class extends Error {
|
|
1571
|
-
constructor(threadId, operation) {
|
|
1572
|
-
super(`Codex thread binding changed while ${operation}: ${threadId}`);
|
|
1573
|
-
this.name = "CodexThreadBindingConflictError";
|
|
1574
|
-
}
|
|
1575
|
-
};
|
|
1576
|
-
const CODEX_NATIVE_PERSONALITY_NONE = "none";
|
|
1577
|
-
const CODEX_CODE_MODE_THREAD_CONFIG = {
|
|
1578
|
-
"features.code_mode": true,
|
|
1579
|
-
"features.code_mode_only": false,
|
|
1580
|
-
"features.apply_patch_streaming_events": true
|
|
1744
|
+
const CODEX_CODE_MODE_THREAD_CONFIG = {
|
|
1745
|
+
"features.code_mode": true,
|
|
1746
|
+
"features.code_mode_only": false,
|
|
1747
|
+
"features.apply_patch_streaming_events": true
|
|
1581
1748
|
};
|
|
1582
1749
|
const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
|
|
1583
1750
|
"features.code_mode": false,
|
|
@@ -1585,6 +1752,559 @@ const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
|
|
|
1585
1752
|
};
|
|
1586
1753
|
const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
|
|
1587
1754
|
const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
|
|
1755
|
+
const CODEX_RING_ZERO_THREAD_CONFIG = {
|
|
1756
|
+
"features.apps": false,
|
|
1757
|
+
"features.current_time_reminder": false,
|
|
1758
|
+
"features.deferred_executor": false,
|
|
1759
|
+
"features.enable_fanout": false,
|
|
1760
|
+
"features.goals": false,
|
|
1761
|
+
"features.hooks": false,
|
|
1762
|
+
"features.image_generation": false,
|
|
1763
|
+
"features.memories": false,
|
|
1764
|
+
"features.multi_agent": false,
|
|
1765
|
+
"features.multi_agent_v2": false,
|
|
1766
|
+
"features.plugins": false,
|
|
1767
|
+
"features.standalone_web_search": false,
|
|
1768
|
+
"features.token_budget": false,
|
|
1769
|
+
"orchestrator.mcp.enabled": false,
|
|
1770
|
+
"orchestrator.skills.enabled": false,
|
|
1771
|
+
"tools.experimental_request_user_input.enabled": false,
|
|
1772
|
+
hooks: {
|
|
1773
|
+
PreToolUse: [],
|
|
1774
|
+
PermissionRequest: [],
|
|
1775
|
+
PostToolUse: [],
|
|
1776
|
+
PreCompact: [],
|
|
1777
|
+
PostCompact: [],
|
|
1778
|
+
SessionStart: [],
|
|
1779
|
+
UserPromptSubmit: [],
|
|
1780
|
+
SubagentStart: [],
|
|
1781
|
+
SubagentStop: [],
|
|
1782
|
+
Stop: []
|
|
1783
|
+
},
|
|
1784
|
+
project_doc_max_bytes: 0,
|
|
1785
|
+
notify: [],
|
|
1786
|
+
web_search: "disabled"
|
|
1787
|
+
};
|
|
1788
|
+
const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
|
|
1789
|
+
"apps",
|
|
1790
|
+
"code_mode",
|
|
1791
|
+
"code_mode_only",
|
|
1792
|
+
"current_time_reminder",
|
|
1793
|
+
"deferred_executor",
|
|
1794
|
+
"enable_fanout",
|
|
1795
|
+
"goals",
|
|
1796
|
+
"hooks",
|
|
1797
|
+
"image_generation",
|
|
1798
|
+
"memories",
|
|
1799
|
+
"multi_agent",
|
|
1800
|
+
"multi_agent_v2",
|
|
1801
|
+
"plugins",
|
|
1802
|
+
"standalone_web_search",
|
|
1803
|
+
"token_budget"
|
|
1804
|
+
]);
|
|
1805
|
+
const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
1806
|
+
"mdm",
|
|
1807
|
+
"system",
|
|
1808
|
+
"enterpriseManaged",
|
|
1809
|
+
"user",
|
|
1810
|
+
"project",
|
|
1811
|
+
"sessionFlags"
|
|
1812
|
+
]);
|
|
1813
|
+
function buildThreadStartParams(params, options) {
|
|
1814
|
+
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
1815
|
+
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
1816
|
+
provider: params.provider,
|
|
1817
|
+
authProfileId: params.authProfileId,
|
|
1818
|
+
authProfileStore: params.authProfileStore,
|
|
1819
|
+
agentDir: params.agentDir,
|
|
1820
|
+
config: params.config
|
|
1821
|
+
});
|
|
1822
|
+
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
1823
|
+
model: options.model ?? params.modelId,
|
|
1824
|
+
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
1825
|
+
authProfileId: params.authProfileId,
|
|
1826
|
+
authProfileStore: params.authProfileStore,
|
|
1827
|
+
agentDir: params.agentDir,
|
|
1828
|
+
config: params.config
|
|
1829
|
+
});
|
|
1830
|
+
return {
|
|
1831
|
+
model: modelSelection.model,
|
|
1832
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
1833
|
+
cwd: options.cwd,
|
|
1834
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
1835
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
1836
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
1837
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1838
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
1839
|
+
serviceName: "OpenClaw",
|
|
1840
|
+
...ringZeroActive ? { baseInstructions: "" } : {},
|
|
1841
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
1842
|
+
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
1843
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1844
|
+
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
1845
|
+
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
1846
|
+
webSearchAllowed: options.webSearchAllowed,
|
|
1847
|
+
appServer: options.appServer,
|
|
1848
|
+
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1849
|
+
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
1850
|
+
}),
|
|
1851
|
+
...resolveCodexThreadEnvironmentSelection(options),
|
|
1852
|
+
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
1853
|
+
dynamicTools: [...options.dynamicTools],
|
|
1854
|
+
experimentalRawEvents: true
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function buildThreadResumeParams(params, options) {
|
|
1858
|
+
const modelSelection = options.preserveNativeModel ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
1859
|
+
model: options.model ?? params.modelId,
|
|
1860
|
+
modelProvider: options.modelProvider ?? resolveCodexAppServerModelProvider({
|
|
1861
|
+
provider: params.provider,
|
|
1862
|
+
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
1863
|
+
authProfileStore: params.authProfileStore,
|
|
1864
|
+
agentDir: params.agentDir,
|
|
1865
|
+
config: params.config
|
|
1866
|
+
}),
|
|
1867
|
+
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
1868
|
+
authProfileStore: params.authProfileStore,
|
|
1869
|
+
agentDir: params.agentDir,
|
|
1870
|
+
config: params.config
|
|
1871
|
+
});
|
|
1872
|
+
return {
|
|
1873
|
+
threadId: options.threadId,
|
|
1874
|
+
...modelSelection ? {
|
|
1875
|
+
model: modelSelection.model,
|
|
1876
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
|
|
1877
|
+
} : {},
|
|
1878
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
1879
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
1880
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
1881
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1882
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
1883
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
1884
|
+
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
1885
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1886
|
+
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
1887
|
+
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
1888
|
+
webSearchAllowed: options.webSearchAllowed,
|
|
1889
|
+
appServer: options.appServer,
|
|
1890
|
+
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1891
|
+
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
1892
|
+
}),
|
|
1893
|
+
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
1897
|
+
const codeModeConfig = {
|
|
1898
|
+
...CODEX_CODE_MODE_THREAD_CONFIG,
|
|
1899
|
+
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
1900
|
+
};
|
|
1901
|
+
if (options.nativeCodeModeEnabled === false) {
|
|
1902
|
+
const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
|
|
1903
|
+
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
1904
|
+
return disabledConfig;
|
|
1905
|
+
}
|
|
1906
|
+
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, { "features.code_mode_only": true }) ?? {
|
|
1907
|
+
...codeModeConfig,
|
|
1908
|
+
"features.code_mode_only": true
|
|
1909
|
+
}, options.directOnlyToolNamespaces);
|
|
1910
|
+
return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config) ?? { ...codeModeConfig }, options.directOnlyToolNamespaces);
|
|
1911
|
+
}
|
|
1912
|
+
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
1913
|
+
if (!requiredNamespaces?.length) return config;
|
|
1914
|
+
const configured = config["code_mode.direct_only_tool_namespaces"];
|
|
1915
|
+
const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
1916
|
+
return {
|
|
1917
|
+
...config,
|
|
1918
|
+
"code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
1922
|
+
return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
|
|
1923
|
+
}
|
|
1924
|
+
function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
1925
|
+
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
1926
|
+
const configMcpServers = config?.mcp_servers;
|
|
1927
|
+
if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
|
|
1928
|
+
const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
1929
|
+
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
1930
|
+
config: params.config,
|
|
1931
|
+
disableTools: params.disableTools,
|
|
1932
|
+
nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
|
|
1933
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1934
|
+
webSearchAllowed: options.webSearchAllowed
|
|
1935
|
+
}).threadConfig;
|
|
1936
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
1937
|
+
const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
|
|
1938
|
+
if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
|
|
1939
|
+
return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
|
|
1940
|
+
...runtimeConfig,
|
|
1941
|
+
...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
|
|
1945
|
+
if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
|
|
1946
|
+
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
1947
|
+
return {
|
|
1948
|
+
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
1949
|
+
...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
async function readCodexInheritedMcpServerNames(client, cwd, signal) {
|
|
1953
|
+
const response = await client.request("config/read", {
|
|
1954
|
+
cwd,
|
|
1955
|
+
includeLayers: true
|
|
1956
|
+
}, { signal });
|
|
1957
|
+
if (!isJsonObject(response) || !isJsonObject(response.config)) throw new Error("Codex config/read returned an invalid effective config");
|
|
1958
|
+
if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
|
|
1959
|
+
for (const layer of response.layers) {
|
|
1960
|
+
if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
|
|
1961
|
+
if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex ring-zero cannot override config layer ${layer.name.type}`);
|
|
1962
|
+
if (!CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex ring-zero does not recognize config layer ${layer.name.type}`);
|
|
1963
|
+
}
|
|
1964
|
+
const configuredServers = response.config.mcp_servers;
|
|
1965
|
+
if (configuredServers === void 0) return [];
|
|
1966
|
+
if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
|
|
1967
|
+
return Object.keys(configuredServers).toSorted();
|
|
1968
|
+
}
|
|
1969
|
+
async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
|
|
1970
|
+
const response = await client.request("configRequirements/read", void 0, { signal });
|
|
1971
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
|
|
1972
|
+
if (response.requirements === null) return;
|
|
1973
|
+
if (!isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
|
|
1974
|
+
for (const key of [
|
|
1975
|
+
"hooks",
|
|
1976
|
+
"managedHooks",
|
|
1977
|
+
"managed_hooks"
|
|
1978
|
+
]) {
|
|
1979
|
+
const hooks = response.requirements[key];
|
|
1980
|
+
if (hooks === void 0 || hooks === null) continue;
|
|
1981
|
+
if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
|
|
1982
|
+
if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex ring-zero cannot override managed hooks");
|
|
1983
|
+
}
|
|
1984
|
+
for (const key of ["featureRequirements", "feature_requirements"]) {
|
|
1985
|
+
const requirements = response.requirements[key];
|
|
1986
|
+
if (requirements === void 0 || requirements === null) continue;
|
|
1987
|
+
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
1988
|
+
for (const [feature, enabled] of Object.entries(requirements)) {
|
|
1989
|
+
if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
1990
|
+
if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(feature)) throw new Error(`Codex ring-zero cannot override required feature ${feature}`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
async function attestCodexRingZeroThreadHasNoMcpServers(client, threadId, signal) {
|
|
1995
|
+
const response = await client.request("mcpServerStatus/list", {
|
|
1996
|
+
threadId,
|
|
1997
|
+
limit: 1,
|
|
1998
|
+
detail: "toolsAndAuthOnly"
|
|
1999
|
+
}, { signal });
|
|
2000
|
+
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid ring-zero attestation");
|
|
2001
|
+
if (response.data.length > 0) {
|
|
2002
|
+
const first = response.data[0];
|
|
2003
|
+
const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
|
|
2004
|
+
throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
|
|
2005
|
+
}
|
|
2006
|
+
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
2007
|
+
}
|
|
2008
|
+
function hasNonEmptyJsonValue(value) {
|
|
2009
|
+
if (value === null || value === false || value === "") return false;
|
|
2010
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
2011
|
+
if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
|
|
2012
|
+
return true;
|
|
2013
|
+
}
|
|
2014
|
+
function resolveCodexThreadApprovalsReviewer(appServer, config) {
|
|
2015
|
+
return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
|
|
2016
|
+
}
|
|
2017
|
+
function codexThreadSandboxOrPermissions(appServer) {
|
|
2018
|
+
if (appServer.networkProxy) return {};
|
|
2019
|
+
return { sandbox: appServer.sandbox };
|
|
2020
|
+
}
|
|
2021
|
+
function resolveCodexThreadEnvironmentSelection(options) {
|
|
2022
|
+
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
2023
|
+
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
2024
|
+
return {};
|
|
2025
|
+
}
|
|
2026
|
+
//#endregion
|
|
2027
|
+
//#region extensions/codex/src/app-server/thread-resume.ts
|
|
2028
|
+
/** Owns Codex thread/resume subscription safety. */
|
|
2029
|
+
/** Resumes one thread and retires the physical client when acceptance is indeterminate. */
|
|
2030
|
+
async function resumeCodexAppServerThread(params) {
|
|
2031
|
+
const threadId = params.request.threadId;
|
|
2032
|
+
let response;
|
|
2033
|
+
try {
|
|
2034
|
+
response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
|
|
2035
|
+
...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
|
|
2036
|
+
...params.signal ? { signal: params.signal } : {}
|
|
2037
|
+
}));
|
|
2038
|
+
assertCodexThreadResumeSubscription(threadId, response.thread.id);
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
|
|
2041
|
+
if (error instanceof CodexAppServerRpcError) throw error;
|
|
2042
|
+
try {
|
|
2043
|
+
await params.abandonClient();
|
|
2044
|
+
} catch (abandonError) {
|
|
2045
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
|
|
2046
|
+
}
|
|
2047
|
+
if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
|
|
2048
|
+
throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
|
|
2049
|
+
}
|
|
2050
|
+
return response;
|
|
2051
|
+
}
|
|
2052
|
+
//#endregion
|
|
2053
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-io.ts
|
|
2054
|
+
async function resumeExistingCodexThread(params, context) {
|
|
2055
|
+
const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
|
|
2056
|
+
let resumeReservation;
|
|
2057
|
+
try {
|
|
2058
|
+
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
2059
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({
|
|
2060
|
+
action: "resume",
|
|
2061
|
+
binding: resumeBinding
|
|
2062
|
+
}) ?? {
|
|
2063
|
+
configPatch: params.finalConfigPatch,
|
|
2064
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2065
|
+
};
|
|
2066
|
+
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
|
|
2067
|
+
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
|
|
2068
|
+
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2069
|
+
threadId: resumeBinding.threadId,
|
|
2070
|
+
authProfileId,
|
|
2071
|
+
model: startModelSelection.model,
|
|
2072
|
+
modelProvider: startModelProvider,
|
|
2073
|
+
preserveNativeModel: resumeBinding.preserveNativeModel === true,
|
|
2074
|
+
appServer: params.appServer,
|
|
2075
|
+
dynamicTools: params.dynamicTools,
|
|
2076
|
+
developerInstructions: params.developerInstructions,
|
|
2077
|
+
config: resumeConfig,
|
|
2078
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2079
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2080
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2081
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2082
|
+
hostSystemAgentActive,
|
|
2083
|
+
ringZeroInheritedMcpServerNames
|
|
2084
|
+
}));
|
|
2085
|
+
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2086
|
+
throwIfAborted();
|
|
2087
|
+
if (resumeBinding.preserveNativeModel === true) {
|
|
2088
|
+
const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
2089
|
+
threadId: resumeBinding.threadId,
|
|
2090
|
+
includeTurns: false
|
|
2091
|
+
}, { signal: params.signal }));
|
|
2092
|
+
throwIfAborted();
|
|
2093
|
+
if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
2094
|
+
}
|
|
2095
|
+
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
2096
|
+
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
2097
|
+
client: params.client,
|
|
2098
|
+
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
2099
|
+
request: resumeParams,
|
|
2100
|
+
signal: params.signal
|
|
2101
|
+
}));
|
|
2102
|
+
if (ringZeroActive) try {
|
|
2103
|
+
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2106
|
+
throw new CodexRingZeroAttestationError(error);
|
|
2107
|
+
}
|
|
2108
|
+
throwIfAborted();
|
|
2109
|
+
const boundAuthProfileId = authProfileId;
|
|
2110
|
+
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2111
|
+
const resumePatch = {
|
|
2112
|
+
cwd: params.cwd,
|
|
2113
|
+
authProfileId: boundAuthProfileId,
|
|
2114
|
+
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
2115
|
+
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
2116
|
+
modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
2117
|
+
dynamicToolsFingerprint,
|
|
2118
|
+
dynamicToolsContainDeferred,
|
|
2119
|
+
webSearchThreadConfigFingerprint,
|
|
2120
|
+
userMcpServersFingerprint,
|
|
2121
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2122
|
+
ringZeroConfigFingerprint,
|
|
2123
|
+
ringZeroClientInstanceId,
|
|
2124
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2125
|
+
networkProxyConfigFingerprint,
|
|
2126
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
2127
|
+
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2128
|
+
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
|
2129
|
+
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
|
2130
|
+
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
|
2131
|
+
contextEngine: contextEngineBinding,
|
|
2132
|
+
environmentSelectionFingerprint
|
|
2133
|
+
};
|
|
2134
|
+
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2135
|
+
kind: "patch",
|
|
2136
|
+
threadId: resumeBinding.threadId,
|
|
2137
|
+
patch: resumePatch
|
|
2138
|
+
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2139
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2140
|
+
sessionId: params.params.sessionId,
|
|
2141
|
+
sessionKey: params.params.sessionKey,
|
|
2142
|
+
threadId: response.thread.id,
|
|
2143
|
+
engineId: contextEngineBinding.engineId,
|
|
2144
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
2145
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2146
|
+
action: "resumed"
|
|
2147
|
+
});
|
|
2148
|
+
lifecycleTiming.mark("thread-ready");
|
|
2149
|
+
lifecycleTiming.logSummary({
|
|
2150
|
+
runId: params.params.runId,
|
|
2151
|
+
sessionId: params.params.sessionId,
|
|
2152
|
+
sessionKey: params.params.sessionKey,
|
|
2153
|
+
threadId: response.thread.id,
|
|
2154
|
+
action: "resumed"
|
|
2155
|
+
});
|
|
2156
|
+
const activeTurnIds = readActiveCodexTurnIds(response.thread);
|
|
2157
|
+
return {
|
|
2158
|
+
...resumeBinding,
|
|
2159
|
+
threadId: response.thread.id,
|
|
2160
|
+
...resumePatch,
|
|
2161
|
+
lifecycle: {
|
|
2162
|
+
action: "resumed",
|
|
2163
|
+
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
resumeReservation?.release();
|
|
2168
|
+
if (isCodexAppServerStartSelectionChangedError(error)) throw error;
|
|
2169
|
+
if (error instanceof CodexRingZeroAttestationError) {
|
|
2170
|
+
await clearCurrentBinding("retiring a failed ring-zero thread attestation");
|
|
2171
|
+
throw error;
|
|
2172
|
+
}
|
|
2173
|
+
if (error instanceof CodexAdoptedThreadActiveError) throw error;
|
|
2174
|
+
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
2175
|
+
const resumeRejected = error instanceof CodexAppServerRpcError;
|
|
2176
|
+
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
2177
|
+
threadId: resumeBinding.threadId,
|
|
2178
|
+
timeoutMs: 5e3
|
|
2179
|
+
}) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
2180
|
+
if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
|
|
2181
|
+
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
2182
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
async function startFreshCodexThread(params, context) {
|
|
2186
|
+
const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding } = context;
|
|
2187
|
+
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2188
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2189
|
+
configPatch: params.finalConfigPatch,
|
|
2190
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2191
|
+
};
|
|
2192
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
2193
|
+
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
2194
|
+
cwd: params.cwd,
|
|
2195
|
+
dynamicTools: params.dynamicTools,
|
|
2196
|
+
appServer: params.appServer,
|
|
2197
|
+
developerInstructions: params.developerInstructions,
|
|
2198
|
+
config,
|
|
2199
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2200
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2201
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2202
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2203
|
+
environmentSelection: params.environmentSelection,
|
|
2204
|
+
model: startModelSelection.model,
|
|
2205
|
+
modelProvider: startModelProvider,
|
|
2206
|
+
hostSystemAgentActive,
|
|
2207
|
+
ringZeroInheritedMcpServerNames
|
|
2208
|
+
}));
|
|
2209
|
+
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2210
|
+
const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2211
|
+
try {
|
|
2212
|
+
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2215
|
+
throw error;
|
|
2216
|
+
}
|
|
2217
|
+
}));
|
|
2218
|
+
if (ringZeroActive) try {
|
|
2219
|
+
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
2220
|
+
} catch (error) {
|
|
2221
|
+
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2222
|
+
throw error;
|
|
2223
|
+
}
|
|
2224
|
+
throwIfAborted();
|
|
2225
|
+
const modelProvider = resolveCodexAppServerModelProvider({
|
|
2226
|
+
provider: params.params.provider,
|
|
2227
|
+
authProfileId: params.params.authProfileId,
|
|
2228
|
+
authProfileStore: params.params.authProfileStore,
|
|
2229
|
+
agentDir: params.params.agentDir,
|
|
2230
|
+
config: params.params.config
|
|
2231
|
+
});
|
|
2232
|
+
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2233
|
+
if (!preserveExistingBinding) {
|
|
2234
|
+
if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2235
|
+
kind: "set",
|
|
2236
|
+
if: { kind: "absent" },
|
|
2237
|
+
binding: {
|
|
2238
|
+
threadId: response.thread.id,
|
|
2239
|
+
cwd: params.cwd,
|
|
2240
|
+
authProfileId: params.params.authProfileId,
|
|
2241
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2242
|
+
modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
|
|
2243
|
+
dynamicToolsFingerprint,
|
|
2244
|
+
dynamicToolsContainDeferred,
|
|
2245
|
+
webSearchThreadConfigFingerprint,
|
|
2246
|
+
userMcpServersFingerprint,
|
|
2247
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2248
|
+
ringZeroConfigFingerprint,
|
|
2249
|
+
ringZeroClientInstanceId,
|
|
2250
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2251
|
+
networkProxyConfigFingerprint,
|
|
2252
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2253
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2254
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2255
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2256
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2257
|
+
contextEngine: contextEngineBinding,
|
|
2258
|
+
environmentSelectionFingerprint
|
|
2259
|
+
}
|
|
2260
|
+
}))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
|
|
2261
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2262
|
+
sessionId: params.params.sessionId,
|
|
2263
|
+
sessionKey: params.params.sessionKey,
|
|
2264
|
+
threadId: response.thread.id,
|
|
2265
|
+
engineId: contextEngineBinding.engineId,
|
|
2266
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
2267
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2268
|
+
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
lifecycleTiming.mark("thread-ready");
|
|
2272
|
+
lifecycleTiming.logSummary({
|
|
2273
|
+
runId: params.params.runId,
|
|
2274
|
+
sessionId: params.params.sessionId,
|
|
2275
|
+
sessionKey: params.params.sessionKey,
|
|
2276
|
+
threadId: response.thread.id,
|
|
2277
|
+
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2278
|
+
});
|
|
2279
|
+
return {
|
|
2280
|
+
threadId: response.thread.id,
|
|
2281
|
+
cwd: params.cwd,
|
|
2282
|
+
authProfileId: params.params.authProfileId,
|
|
2283
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2284
|
+
modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
|
|
2285
|
+
dynamicToolsFingerprint,
|
|
2286
|
+
dynamicToolsContainDeferred,
|
|
2287
|
+
userMcpServersFingerprint,
|
|
2288
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2289
|
+
ringZeroConfigFingerprint,
|
|
2290
|
+
ringZeroClientInstanceId,
|
|
2291
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2292
|
+
networkProxyConfigFingerprint,
|
|
2293
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2294
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2295
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2296
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2297
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2298
|
+
contextEngine: contextEngineBinding,
|
|
2299
|
+
environmentSelectionFingerprint,
|
|
2300
|
+
lifecycle: {
|
|
2301
|
+
action: "started",
|
|
2302
|
+
...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
|
|
2303
|
+
}
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
//#endregion
|
|
2307
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
|
|
1588
2308
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
|
|
1589
2309
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
|
|
1590
2310
|
function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
|
|
@@ -1672,34 +2392,376 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
|
1672
2392
|
}
|
|
1673
2393
|
};
|
|
1674
2394
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2395
|
+
//#endregion
|
|
2396
|
+
//#region extensions/codex/src/app-server/thread-supervision.ts
|
|
2397
|
+
async function materializePendingSupervisionBranch(params) {
|
|
2398
|
+
let pending = params.binding.pendingSupervisionBranch;
|
|
2399
|
+
const connectionFingerprint = buildCodexAppServerConnectionFingerprint(params.appServer, params.attempt.agentDir);
|
|
2400
|
+
if (!pending.connectionFingerprint || pending.connectionFingerprint !== connectionFingerprint) throw new Error("Codex supervision source connection changed before branch materialization");
|
|
2401
|
+
pending = await recoverPendingSupervisionArtifacts(params, pending);
|
|
2402
|
+
params.throwIfAborted();
|
|
2403
|
+
const sourceResponse = await params.lifecycleTiming.measure("supervision-source-read", () => params.client.request("thread/read", {
|
|
2404
|
+
threadId: pending.sourceThreadId,
|
|
2405
|
+
includeTurns: true
|
|
2406
|
+
}, { signal: params.signal }));
|
|
2407
|
+
params.throwIfAborted();
|
|
2408
|
+
const sourceThread = sourceResponse.thread;
|
|
2409
|
+
if (sourceThread.id !== pending.sourceThreadId) throw new Error(`Codex supervision source read returned ${sourceThread.id} for ${pending.sourceThreadId}`);
|
|
2410
|
+
assertPendingSupervisionSnapshotUnchanged(sourceThread, pending);
|
|
2411
|
+
const history = projectBoundedCodexThreadHistory({
|
|
2412
|
+
thread: sourceThread,
|
|
2413
|
+
throughTurnId: pending.lastTurnId ?? null,
|
|
2414
|
+
importedAt: Date.now(),
|
|
2415
|
+
modelProvider: sourceThread.modelProvider
|
|
1681
2416
|
});
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
2417
|
+
let bindingCommitted = false;
|
|
2418
|
+
let provisionalCleanupSafe = true;
|
|
2419
|
+
try {
|
|
2420
|
+
const probeParams = buildPendingSupervisionProbeForkParams(params, pending);
|
|
2421
|
+
const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
|
|
2422
|
+
try {
|
|
2423
|
+
return await params.client.request("thread/fork", probeParams, { signal: params.signal });
|
|
2424
|
+
} catch (error) {
|
|
2425
|
+
if (!(error instanceof CodexAppServerRpcError)) throw new CodexAppServerUnsafeSubscriptionError("Codex model probe fork may have materialized without a response", { cause: error });
|
|
2426
|
+
throw error;
|
|
2427
|
+
}
|
|
1686
2428
|
});
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
2429
|
+
const probeThreadId = requireDistinctSupervisionThreadId({
|
|
2430
|
+
threadId: readSupervisionResponseThreadId(rawProbeResponse),
|
|
2431
|
+
sourceThreadId: pending.sourceThreadId,
|
|
2432
|
+
role: "model probe"
|
|
2433
|
+
});
|
|
2434
|
+
pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId]);
|
|
2435
|
+
params.throwIfAborted();
|
|
2436
|
+
const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
2437
|
+
const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
|
|
2438
|
+
const nativeModelProvider = requireNativeSupervisionModelProvider({
|
|
2439
|
+
responseModelProvider: probeResponse.modelProvider,
|
|
2440
|
+
responseThreadModelProvider: probeResponse.thread.modelProvider
|
|
2441
|
+
});
|
|
2442
|
+
const startParams = buildThreadStartParams({
|
|
2443
|
+
...params.attempt,
|
|
2444
|
+
modelId: nativeModel
|
|
2445
|
+
}, {
|
|
2446
|
+
cwd: params.cwd,
|
|
2447
|
+
dynamicTools: params.dynamicTools,
|
|
2448
|
+
appServer: params.appServer,
|
|
2449
|
+
developerInstructions: params.developerInstructions,
|
|
2450
|
+
config: params.config,
|
|
2451
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2452
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2453
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2454
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2455
|
+
environmentSelection: params.environmentSelection,
|
|
2456
|
+
model: nativeModel,
|
|
2457
|
+
modelProvider: nativeModelProvider
|
|
2458
|
+
});
|
|
2459
|
+
assertExactSupervisionModelSelection(startParams, {
|
|
2460
|
+
model: nativeModel,
|
|
2461
|
+
modelProvider: nativeModelProvider,
|
|
2462
|
+
operation: "thread/start request"
|
|
2463
|
+
});
|
|
2464
|
+
const rawStartResponse = await params.lifecycleTiming.measure("supervision-thread-start", async () => {
|
|
2465
|
+
try {
|
|
2466
|
+
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2467
|
+
} catch (error) {
|
|
2468
|
+
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2469
|
+
throw new CodexAppServerUnsafeSubscriptionError("Canonical Codex branch may have started without a response", { cause: error });
|
|
2470
|
+
}
|
|
2471
|
+
});
|
|
2472
|
+
const finalThreadId = requireDistinctSupervisionThreadId({
|
|
2473
|
+
threadId: readSupervisionResponseThreadId(rawStartResponse),
|
|
2474
|
+
sourceThreadId: pending.sourceThreadId,
|
|
2475
|
+
otherThreadId: probeThreadId,
|
|
2476
|
+
role: "canonical branch"
|
|
2477
|
+
});
|
|
2478
|
+
pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId, finalThreadId]);
|
|
2479
|
+
params.throwIfAborted();
|
|
2480
|
+
assertExactSupervisionModelSelection(assertCodexThreadStartResponse(rawStartResponse), {
|
|
2481
|
+
model: nativeModel,
|
|
2482
|
+
modelProvider: nativeModelProvider,
|
|
2483
|
+
operation: "thread/start response"
|
|
2484
|
+
});
|
|
2485
|
+
if (history.responseItems.length > 0) {
|
|
2486
|
+
await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
|
|
2487
|
+
threadId: finalThreadId,
|
|
2488
|
+
items: history.responseItems
|
|
2489
|
+
}, { signal: params.signal }));
|
|
2490
|
+
params.throwIfAborted();
|
|
2491
|
+
}
|
|
2492
|
+
if (!await archiveSupervisionArtifact(params.client, probeThreadId)) throw new Error(`Failed to archive temporary Codex model probe: ${probeThreadId}`);
|
|
2493
|
+
pending = await trackPendingSupervisionArtifacts(params, pending, [finalThreadId]);
|
|
2494
|
+
const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
|
|
2495
|
+
const bindingModelProvider = params.normalizeBindingModelProvider(params.attempt.authProfileId, nativeModelProvider);
|
|
2496
|
+
let committed = false;
|
|
2497
|
+
try {
|
|
2498
|
+
committed = await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2499
|
+
kind: "commit-pending-supervision-branch",
|
|
2500
|
+
expected: pending,
|
|
2501
|
+
threadId: finalThreadId,
|
|
2502
|
+
patch: {
|
|
2503
|
+
...params.bindingPatch,
|
|
2504
|
+
model: nativeModel,
|
|
2505
|
+
modelProvider: bindingModelProvider,
|
|
2506
|
+
historyCoveredThrough
|
|
2507
|
+
}
|
|
2508
|
+
});
|
|
2509
|
+
} catch (error) {
|
|
2510
|
+
let current;
|
|
2511
|
+
try {
|
|
2512
|
+
current = await params.bindingStore.read(params.bindingIdentity);
|
|
2513
|
+
} catch (readError) {
|
|
2514
|
+
provisionalCleanupSafe = false;
|
|
2515
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding could not be verified: ${finalThreadId}`, { cause: new AggregateError([error, readError]) });
|
|
2516
|
+
}
|
|
2517
|
+
if (matchesMaterializedSupervisionBranch(current, {
|
|
2518
|
+
sourceThreadId: pending.sourceThreadId,
|
|
2519
|
+
connectionFingerprint,
|
|
2520
|
+
threadId: finalThreadId,
|
|
2521
|
+
model: nativeModel,
|
|
2522
|
+
modelProvider: bindingModelProvider,
|
|
2523
|
+
historyCoveredThrough
|
|
2524
|
+
})) committed = true;
|
|
2525
|
+
else {
|
|
2526
|
+
if (!matchesPendingSupervisionState(current, pending)) {
|
|
2527
|
+
provisionalCleanupSafe = false;
|
|
2528
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding changed while commit was uncertain: ${finalThreadId}`, { cause: error });
|
|
2529
|
+
}
|
|
2530
|
+
throw error;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
if (!committed) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "committing a supervised Codex branch");
|
|
2534
|
+
bindingCommitted = true;
|
|
2535
|
+
params.lifecycleTiming.mark("thread-ready");
|
|
2536
|
+
params.lifecycleTiming.logSummary({
|
|
2537
|
+
runId: params.attempt.runId,
|
|
2538
|
+
sessionId: params.attempt.sessionId,
|
|
2539
|
+
sessionKey: params.attempt.sessionKey,
|
|
2540
|
+
threadId: finalThreadId,
|
|
2541
|
+
action: "forked"
|
|
2542
|
+
});
|
|
2543
|
+
return {
|
|
2544
|
+
...params.binding,
|
|
2545
|
+
...params.bindingPatch,
|
|
2546
|
+
threadId: finalThreadId,
|
|
2547
|
+
pendingSupervisionBranch: void 0,
|
|
2548
|
+
model: nativeModel,
|
|
2549
|
+
modelProvider: bindingModelProvider,
|
|
2550
|
+
historyCoveredThrough,
|
|
2551
|
+
lifecycle: { action: "forked" }
|
|
2552
|
+
};
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
if (bindingCommitted) throw error;
|
|
2555
|
+
if (error instanceof CodexThreadBindingConflictAfterCleanupError) throw error;
|
|
2556
|
+
if (!provisionalCleanupSafe) {
|
|
2557
|
+
await params.abandonClient();
|
|
2558
|
+
throw error;
|
|
2559
|
+
}
|
|
2560
|
+
const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
|
|
2561
|
+
let cleanupStateError;
|
|
2562
|
+
if (cleanup.remaining.length !== (pending.cleanupThreadIds?.length ?? 0)) {
|
|
2563
|
+
const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
|
|
2564
|
+
try {
|
|
2565
|
+
if (await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2566
|
+
kind: "patch-pending-supervision-branch",
|
|
2567
|
+
expected: pending,
|
|
2568
|
+
pending: nextPending
|
|
2569
|
+
})) pending = nextPending;
|
|
2570
|
+
} catch (stateError) {
|
|
2571
|
+
cleanupStateError = stateError;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
const unsafeCleanup = cleanup.remaining.length > 0 || isCodexAppServerUnsafeSubscriptionError(error);
|
|
2575
|
+
if (unsafeCleanup) await params.abandonClient();
|
|
2576
|
+
if (cleanupStateError) {
|
|
2577
|
+
const cause = new AggregateError([error, cleanupStateError]);
|
|
2578
|
+
if (unsafeCleanup) throw new CodexAppServerUnsafeSubscriptionError("Codex supervised branch cleanup state could not be recorded", { cause });
|
|
2579
|
+
throw new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
|
|
2580
|
+
}
|
|
2581
|
+
if (cleanup.remaining.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup remains pending: ${cleanup.remaining.join(", ")}`, { cause: error });
|
|
2582
|
+
throw error;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
function buildPendingSupervisionProbeForkParams(params, pending) {
|
|
2586
|
+
const runtimeConfig = buildCodexRuntimeThreadConfigForRun(params.attempt, params.config, {
|
|
2587
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2588
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2589
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2590
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2591
|
+
appServer: params.appServer
|
|
2592
|
+
});
|
|
2593
|
+
return {
|
|
2594
|
+
threadId: pending.sourceThreadId,
|
|
2595
|
+
...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
|
|
2596
|
+
cwd: params.cwd,
|
|
2597
|
+
approvalPolicy: params.appServer.approvalPolicy,
|
|
2598
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(params.appServer, runtimeConfig),
|
|
2599
|
+
...codexThreadSandboxOrPermissions(params.appServer),
|
|
2600
|
+
...params.appServer.serviceTier !== void 0 ? { serviceTier: params.appServer.serviceTier } : {},
|
|
2601
|
+
config: runtimeConfig,
|
|
2602
|
+
developerInstructions: params.developerInstructions ?? buildDeveloperInstructions(params.attempt, { dynamicTools: params.dynamicTools }),
|
|
2603
|
+
ephemeral: false,
|
|
2604
|
+
threadSource: "appServer",
|
|
2605
|
+
excludeTurns: true
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
function assertPendingSupervisionSnapshotUnchanged(thread, pending) {
|
|
2609
|
+
if (pending.lastTurnId) return;
|
|
2610
|
+
if (thread.status?.type === "active" || (thread.turns?.length ?? 0) > 0) throw new Error("Codex source changed after Continue; reopen the source session before sending a message");
|
|
2611
|
+
}
|
|
2612
|
+
function requireNonBlankSupervisionValue(value, label) {
|
|
2613
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`Codex supervision ${label} is missing`);
|
|
2614
|
+
return value.trim();
|
|
2615
|
+
}
|
|
2616
|
+
function requireNativeSupervisionModelProvider(params) {
|
|
2617
|
+
const responseProvider = requireNonBlankSupervisionValue(params.responseModelProvider, "native model provider");
|
|
2618
|
+
const threadProvider = params.responseThreadModelProvider?.trim();
|
|
2619
|
+
if (threadProvider && threadProvider !== responseProvider) throw new Error(`Codex supervision model provider mismatch: ${responseProvider} != ${threadProvider}`);
|
|
2620
|
+
return responseProvider;
|
|
2621
|
+
}
|
|
2622
|
+
function assertExactSupervisionModelSelection(value, expected) {
|
|
2623
|
+
if (value.model !== expected.model || value.modelProvider !== expected.modelProvider) throw new Error(`Codex supervision ${expected.operation} changed native model selection: ${value.modelProvider ?? "unknown"}/${value.model ?? "unknown"}`);
|
|
2624
|
+
}
|
|
2625
|
+
function matchesPendingSupervisionState(binding, expected) {
|
|
2626
|
+
const pending = binding?.pendingSupervisionBranch;
|
|
2627
|
+
const cleanupThreadIds = pending?.cleanupThreadIds ?? [];
|
|
2628
|
+
const expectedCleanupThreadIds = expected.cleanupThreadIds ?? [];
|
|
2629
|
+
return binding?.threadId === expected.sourceThreadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && pending?.sourceThreadId === expected.sourceThreadId && pending.connectionFingerprint === expected.connectionFingerprint && pending.lastTurnId === expected.lastTurnId && cleanupThreadIds.length === expectedCleanupThreadIds.length && cleanupThreadIds.every((threadId, index) => threadId === expectedCleanupThreadIds[index]);
|
|
2630
|
+
}
|
|
2631
|
+
function matchesMaterializedSupervisionBranch(binding, expected) {
|
|
2632
|
+
return binding?.threadId === expected.threadId && binding.connectionScope === "supervision" && binding.supervisionSourceThreadId === expected.sourceThreadId && binding.appServerRuntimeFingerprint === expected.connectionFingerprint && binding.pendingSupervisionBranch === void 0 && binding.model === expected.model && binding.modelProvider === expected.modelProvider && binding.historyCoveredThrough === expected.historyCoveredThrough;
|
|
2633
|
+
}
|
|
2634
|
+
function requireDistinctSupervisionThreadId(params) {
|
|
2635
|
+
let threadId;
|
|
2636
|
+
try {
|
|
2637
|
+
threadId = requireNonBlankSupervisionValue(params.threadId, `${params.role} thread id`);
|
|
2638
|
+
} catch (error) {
|
|
2639
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex supervision ${params.role} may have materialized without a safe thread id`, { cause: error });
|
|
2640
|
+
}
|
|
2641
|
+
if (threadId === params.sourceThreadId || threadId === params.otherThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervision ${params.role} reused an existing thread: ${threadId}`);
|
|
2642
|
+
return threadId;
|
|
2643
|
+
}
|
|
2644
|
+
function readSupervisionResponseThreadId(value) {
|
|
2645
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
2646
|
+
const thread = value.thread;
|
|
2647
|
+
if (!thread || typeof thread !== "object" || Array.isArray(thread)) return;
|
|
2648
|
+
return thread.id;
|
|
2649
|
+
}
|
|
2650
|
+
async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
2651
|
+
if (!pending.cleanupThreadIds?.length) return pending;
|
|
2652
|
+
const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
|
|
2653
|
+
const next = withPendingSupervisionCleanup(pending, cleanup.remaining);
|
|
2654
|
+
if (cleanup.remaining.length > 0) {
|
|
2655
|
+
if (cleanup.remaining.length !== pending.cleanupThreadIds.length) {
|
|
2656
|
+
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2657
|
+
kind: "patch-pending-supervision-branch",
|
|
2658
|
+
expected: pending,
|
|
2659
|
+
pending: next
|
|
2660
|
+
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recording supervised Codex cleanup recovery");
|
|
2661
|
+
}
|
|
2662
|
+
throw new Error(`Codex supervised branch cleanup must finish before retry: ${cleanup.remaining.join(", ")}`);
|
|
2663
|
+
}
|
|
2664
|
+
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2665
|
+
kind: "patch-pending-supervision-branch",
|
|
2666
|
+
expected: pending,
|
|
2667
|
+
pending: next
|
|
2668
|
+
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
|
|
2669
|
+
return next;
|
|
2670
|
+
}
|
|
2671
|
+
async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
|
|
2672
|
+
const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
2673
|
+
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2674
|
+
kind: "patch-pending-supervision-branch",
|
|
2675
|
+
expected: pending,
|
|
2676
|
+
pending: next
|
|
2677
|
+
})) {
|
|
2678
|
+
const cleanupFailed = [];
|
|
2679
|
+
for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
|
|
2680
|
+
if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
|
|
2681
|
+
throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
2682
|
+
}
|
|
2683
|
+
return next;
|
|
2684
|
+
}
|
|
2685
|
+
function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
|
|
2686
|
+
return {
|
|
2687
|
+
sourceThreadId: pending.sourceThreadId,
|
|
2688
|
+
...pending.connectionFingerprint ? { connectionFingerprint: pending.connectionFingerprint } : {},
|
|
2689
|
+
...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
|
|
2690
|
+
...cleanupThreadIds.length > 0 ? { cleanupThreadIds } : {}
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
async function cleanPendingSupervisionArtifacts(client, pending) {
|
|
2694
|
+
const remaining = [];
|
|
2695
|
+
for (const threadId of pending.cleanupThreadIds ?? []) if (!await archiveSupervisionArtifact(client, threadId)) remaining.push(threadId);
|
|
2696
|
+
return { remaining };
|
|
2697
|
+
}
|
|
2698
|
+
async function archiveSupervisionArtifact(client, threadId) {
|
|
2699
|
+
try {
|
|
2700
|
+
await client.request("thread/archive", { threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
|
|
2701
|
+
return true;
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
const message = formatErrorMessage(error).toLowerCase();
|
|
2704
|
+
if (message.includes("no rollout found for thread id") || message.includes("thread not found") || message.includes("already archived")) return true;
|
|
2705
|
+
await unsubscribeCodexThreadBestEffort(client, {
|
|
2706
|
+
threadId,
|
|
2707
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
2708
|
+
});
|
|
2709
|
+
embeddedAgentLog.warn("failed to archive temporary Codex supervision thread", {
|
|
2710
|
+
threadId,
|
|
2711
|
+
error
|
|
2712
|
+
});
|
|
2713
|
+
return false;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
//#endregion
|
|
2717
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-run.ts
|
|
2718
|
+
async function startOrResumeThread(params) {
|
|
2719
|
+
const bindingIdentity = sessionBindingIdentity({
|
|
2720
|
+
sessionId: params.params.sessionId,
|
|
2721
|
+
sessionKey: params.params.sessionKey,
|
|
2722
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
2723
|
+
config: params.params.config
|
|
2724
|
+
});
|
|
2725
|
+
return await params.bindingStore.withLease(bindingIdentity, async () => {
|
|
2726
|
+
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
2727
|
+
...params.timing,
|
|
2728
|
+
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
2729
|
+
});
|
|
2730
|
+
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
2731
|
+
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
2732
|
+
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
2733
|
+
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
2734
|
+
config: params.params.config,
|
|
2735
|
+
disableTools: params.params.disableTools,
|
|
2736
|
+
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
2737
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
1695
2738
|
webSearchAllowed: params.webSearchAllowed
|
|
1696
2739
|
})).threadConfig);
|
|
1697
2740
|
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
1698
2741
|
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
1699
|
-
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 :
|
|
2742
|
+
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : await buildCodexUserMcpServersThreadConfigPatchForRuntime(params.params.config, {
|
|
2743
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
2744
|
+
agentDir: params.params.agentDir,
|
|
2745
|
+
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
2746
|
+
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
2747
|
+
serverName,
|
|
2748
|
+
error: formatErrorMessage(error)
|
|
2749
|
+
})
|
|
2750
|
+
});
|
|
1700
2751
|
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
1701
2752
|
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
1702
2753
|
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
2754
|
+
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
2755
|
+
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
2756
|
+
if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
|
|
2757
|
+
const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
2758
|
+
if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
|
|
2759
|
+
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
2760
|
+
version: 1,
|
|
2761
|
+
baseInstructions: "",
|
|
2762
|
+
config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
|
|
2763
|
+
}) : void 0;
|
|
2764
|
+
const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
|
|
1703
2765
|
let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
|
|
1704
2766
|
const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
|
|
1705
2767
|
authProfileId,
|
|
@@ -1708,6 +2770,14 @@ async function startOrResumeThread(params) {
|
|
|
1708
2770
|
agentDir: params.params.agentDir,
|
|
1709
2771
|
config: params.params.config
|
|
1710
2772
|
});
|
|
2773
|
+
const throwIfAborted = () => {
|
|
2774
|
+
if (!params.signal?.aborted) return;
|
|
2775
|
+
const reason = params.signal.reason;
|
|
2776
|
+
if (reason instanceof Error) throw reason;
|
|
2777
|
+
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
2778
|
+
error.name = "AbortError";
|
|
2779
|
+
throw error;
|
|
2780
|
+
};
|
|
1711
2781
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
1712
2782
|
if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
|
|
1713
2783
|
bindingStore: params.bindingStore,
|
|
@@ -1715,18 +2785,74 @@ async function startOrResumeThread(params) {
|
|
|
1715
2785
|
config: params.params.config
|
|
1716
2786
|
}))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
|
|
1717
2787
|
}
|
|
2788
|
+
if (binding?.pendingSupervisionBranch) {
|
|
2789
|
+
const pendingBinding = binding;
|
|
2790
|
+
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2791
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2792
|
+
configPatch: params.finalConfigPatch,
|
|
2793
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2794
|
+
};
|
|
2795
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
2796
|
+
return await materializePendingSupervisionBranch({
|
|
2797
|
+
client: params.client,
|
|
2798
|
+
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
2799
|
+
bindingStore: params.bindingStore,
|
|
2800
|
+
bindingIdentity,
|
|
2801
|
+
binding: pendingBinding,
|
|
2802
|
+
attempt: params.params,
|
|
2803
|
+
cwd: params.cwd,
|
|
2804
|
+
dynamicTools: params.dynamicTools,
|
|
2805
|
+
appServer: params.appServer,
|
|
2806
|
+
developerInstructions: params.developerInstructions,
|
|
2807
|
+
config,
|
|
2808
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2809
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2810
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2811
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2812
|
+
environmentSelection: params.environmentSelection,
|
|
2813
|
+
signal: params.signal,
|
|
2814
|
+
throwIfAborted,
|
|
2815
|
+
lifecycleTiming,
|
|
2816
|
+
normalizeBindingModelProvider,
|
|
2817
|
+
bindingPatch: {
|
|
2818
|
+
cwd: params.cwd,
|
|
2819
|
+
authProfileId: void 0,
|
|
2820
|
+
preserveNativeModel: true,
|
|
2821
|
+
dynamicToolsFingerprint,
|
|
2822
|
+
dynamicToolsContainDeferred,
|
|
2823
|
+
webSearchThreadConfigFingerprint,
|
|
2824
|
+
userMcpServersFingerprint,
|
|
2825
|
+
mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
|
|
2826
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2827
|
+
networkProxyConfigFingerprint,
|
|
2828
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2829
|
+
appServerRuntimeFingerprint: buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir),
|
|
2830
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2831
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2832
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2833
|
+
contextEngine: contextEngineBinding,
|
|
2834
|
+
environmentSelectionFingerprint,
|
|
2835
|
+
conversationSourceTransferComplete: true
|
|
2836
|
+
}
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
1718
2839
|
const clearCurrentBinding = async (operation) => {
|
|
1719
2840
|
const current = binding;
|
|
1720
2841
|
if (!current?.threadId) return;
|
|
2842
|
+
assertCodexBindingMayBeReplaced(current, operation);
|
|
1721
2843
|
if (!await params.bindingStore.mutate(bindingIdentity, {
|
|
1722
2844
|
kind: "clear",
|
|
1723
2845
|
threadId: current.threadId
|
|
1724
2846
|
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
1725
2847
|
binding = void 0;
|
|
1726
2848
|
};
|
|
2849
|
+
if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
|
|
2850
|
+
embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
|
|
2851
|
+
await clearCurrentBinding("rotating a ring-zero thread binding");
|
|
2852
|
+
}
|
|
1727
2853
|
if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
|
|
1728
2854
|
connectionClass: params.appServer.connectionClass,
|
|
1729
|
-
current: params.appServerRuntimeFingerprint,
|
|
2855
|
+
current: binding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
1730
2856
|
binding: binding.appServerRuntimeFingerprint
|
|
1731
2857
|
})) {
|
|
1732
2858
|
embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
|
|
@@ -1758,17 +2884,9 @@ async function startOrResumeThread(params) {
|
|
|
1758
2884
|
config: params.params.config
|
|
1759
2885
|
});
|
|
1760
2886
|
const startModelProvider = startModelSelection.modelProvider;
|
|
1761
|
-
let preserveExistingBinding = params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
2887
|
+
let preserveExistingBinding = !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
1762
2888
|
let rotatedContextEngineBinding = false;
|
|
1763
2889
|
let prebuiltPluginThreadConfig;
|
|
1764
|
-
const throwIfAborted = () => {
|
|
1765
|
-
if (!params.signal?.aborted) return;
|
|
1766
|
-
const reason = params.signal.reason;
|
|
1767
|
-
if (reason instanceof Error) throw reason;
|
|
1768
|
-
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
1769
|
-
error.name = "AbortError";
|
|
1770
|
-
throw error;
|
|
1771
|
-
};
|
|
1772
2890
|
const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
|
|
1773
2891
|
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
1774
2892
|
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
@@ -1776,7 +2894,8 @@ async function startOrResumeThread(params) {
|
|
|
1776
2894
|
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
1777
2895
|
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
1778
2896
|
if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
|
|
1779
|
-
|
|
2897
|
+
assertCodexBindingMayBeReplaced(binding, "changing MCP configuration");
|
|
2898
|
+
if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
|
|
1780
2899
|
embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
|
|
1781
2900
|
preserveExistingBinding = true;
|
|
1782
2901
|
} else {
|
|
@@ -1787,7 +2906,8 @@ async function startOrResumeThread(params) {
|
|
|
1787
2906
|
}
|
|
1788
2907
|
const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
|
|
1789
2908
|
if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
|
|
1790
|
-
|
|
2909
|
+
assertCodexBindingMayBeReplaced(binding, "changing web-search configuration");
|
|
2910
|
+
if (!ringZeroActive && transientWebSearchRestriction) {
|
|
1791
2911
|
embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
|
|
1792
2912
|
preserveExistingBinding = true;
|
|
1793
2913
|
} else {
|
|
@@ -1796,7 +2916,8 @@ async function startOrResumeThread(params) {
|
|
|
1796
2916
|
}
|
|
1797
2917
|
binding = void 0;
|
|
1798
2918
|
}
|
|
1799
|
-
if (binding?.threadId && transientNativeToolRestriction) {
|
|
2919
|
+
if (binding?.threadId && transientNativeToolRestriction && !ringZeroActive) {
|
|
2920
|
+
assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
|
|
1800
2921
|
embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
|
|
1801
2922
|
preserveExistingBinding = true;
|
|
1802
2923
|
binding = void 0;
|
|
@@ -1872,6 +2993,7 @@ async function startOrResumeThread(params) {
|
|
|
1872
2993
|
}
|
|
1873
2994
|
}
|
|
1874
2995
|
if (binding?.threadId) if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
|
|
2996
|
+
assertCodexBindingMayBeReplaced(binding, "changing the dynamic tool catalog");
|
|
1875
2997
|
preserveExistingBinding = shouldStartTransientNoToolThread({
|
|
1876
2998
|
previous: binding.dynamicToolsFingerprint,
|
|
1877
2999
|
nextHasDynamicTools: params.dynamicTools.length > 0
|
|
@@ -1879,483 +3001,147 @@ async function startOrResumeThread(params) {
|
|
|
1879
3001
|
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
1880
3002
|
else {
|
|
1881
3003
|
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
1882
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1883
|
-
}
|
|
1884
|
-
} else {
|
|
1885
|
-
const
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
1935
|
-
networkProxyConfigFingerprint,
|
|
1936
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
1937
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
1938
|
-
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
|
1939
|
-
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
|
1940
|
-
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
|
1941
|
-
contextEngine: contextEngineBinding,
|
|
1942
|
-
environmentSelectionFingerprint
|
|
1943
|
-
};
|
|
1944
|
-
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
1945
|
-
kind: "patch",
|
|
1946
|
-
threadId: resumeBinding.threadId,
|
|
1947
|
-
patch: resumePatch
|
|
1948
|
-
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
1949
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
1950
|
-
sessionId: params.params.sessionId,
|
|
1951
|
-
sessionKey: params.params.sessionKey,
|
|
1952
|
-
threadId: response.thread.id,
|
|
1953
|
-
engineId: contextEngineBinding.engineId,
|
|
1954
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
1955
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
1956
|
-
action: "resumed"
|
|
1957
|
-
});
|
|
1958
|
-
lifecycleTiming.mark("thread-ready");
|
|
1959
|
-
lifecycleTiming.logSummary({
|
|
1960
|
-
runId: params.params.runId,
|
|
1961
|
-
sessionId: params.params.sessionId,
|
|
1962
|
-
sessionKey: params.params.sessionKey,
|
|
1963
|
-
threadId: response.thread.id,
|
|
1964
|
-
action: "resumed"
|
|
1965
|
-
});
|
|
1966
|
-
const activeTurnIds = readActiveCodexTurnIds(response.thread);
|
|
1967
|
-
return {
|
|
1968
|
-
...resumeBinding,
|
|
1969
|
-
threadId: response.thread.id,
|
|
1970
|
-
...resumePatch,
|
|
1971
|
-
lifecycle: {
|
|
1972
|
-
action: "resumed",
|
|
1973
|
-
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
1974
|
-
}
|
|
1975
|
-
};
|
|
1976
|
-
} catch (error) {
|
|
1977
|
-
resumeReservation?.release();
|
|
1978
|
-
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
1979
|
-
const resumeRejected = error instanceof CodexAppServerRpcError;
|
|
1980
|
-
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
1981
|
-
threadId: resumeBinding.threadId,
|
|
1982
|
-
timeoutMs: 5e3
|
|
1983
|
-
}) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
1984
|
-
if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
|
|
1985
|
-
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
1986
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
1990
|
-
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
1991
|
-
configPatch: params.finalConfigPatch,
|
|
1992
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
1993
|
-
};
|
|
1994
|
-
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
1995
|
-
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
1996
|
-
cwd: params.cwd,
|
|
1997
|
-
dynamicTools: params.dynamicTools,
|
|
1998
|
-
appServer: params.appServer,
|
|
1999
|
-
developerInstructions: params.developerInstructions,
|
|
2000
|
-
config,
|
|
2001
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2002
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2003
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2004
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2005
|
-
environmentSelection: params.environmentSelection,
|
|
2006
|
-
model: startModelSelection.model,
|
|
2007
|
-
modelProvider: startModelProvider
|
|
2008
|
-
}));
|
|
2009
|
-
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2010
|
-
const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2011
|
-
try {
|
|
2012
|
-
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2013
|
-
} catch (error) {
|
|
2014
|
-
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2015
|
-
throw error;
|
|
2016
|
-
}
|
|
2017
|
-
}));
|
|
2018
|
-
throwIfAborted();
|
|
2019
|
-
const modelProvider = resolveCodexAppServerModelProvider({
|
|
2020
|
-
provider: params.params.provider,
|
|
2021
|
-
authProfileId: params.params.authProfileId,
|
|
2022
|
-
authProfileStore: params.params.authProfileStore,
|
|
2023
|
-
agentDir: params.params.agentDir,
|
|
2024
|
-
config: params.params.config
|
|
2025
|
-
});
|
|
2026
|
-
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2027
|
-
if (!preserveExistingBinding) {
|
|
2028
|
-
if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2029
|
-
kind: "set",
|
|
2030
|
-
if: { kind: "absent" },
|
|
2031
|
-
binding: {
|
|
2032
|
-
threadId: response.thread.id,
|
|
2033
|
-
cwd: params.cwd,
|
|
2034
|
-
authProfileId: params.params.authProfileId,
|
|
2035
|
-
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2036
|
-
modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
|
|
2037
|
-
dynamicToolsFingerprint,
|
|
2038
|
-
dynamicToolsContainDeferred,
|
|
2039
|
-
webSearchThreadConfigFingerprint,
|
|
2040
|
-
userMcpServersFingerprint,
|
|
2041
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2042
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2043
|
-
networkProxyConfigFingerprint,
|
|
2044
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2045
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2046
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2047
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2048
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2049
|
-
contextEngine: contextEngineBinding,
|
|
2050
|
-
environmentSelectionFingerprint
|
|
2051
|
-
}
|
|
2052
|
-
}))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
|
|
2053
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2054
|
-
sessionId: params.params.sessionId,
|
|
2055
|
-
sessionKey: params.params.sessionKey,
|
|
2056
|
-
threadId: response.thread.id,
|
|
2057
|
-
engineId: contextEngineBinding.engineId,
|
|
2058
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2059
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2060
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2061
|
-
});
|
|
2062
|
-
}
|
|
2063
|
-
lifecycleTiming.mark("thread-ready");
|
|
2064
|
-
lifecycleTiming.logSummary({
|
|
2065
|
-
runId: params.params.runId,
|
|
2066
|
-
sessionId: params.params.sessionId,
|
|
2067
|
-
sessionKey: params.params.sessionKey,
|
|
2068
|
-
threadId: response.thread.id,
|
|
2069
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2070
|
-
});
|
|
2071
|
-
return {
|
|
2072
|
-
threadId: response.thread.id,
|
|
2073
|
-
cwd: params.cwd,
|
|
2074
|
-
authProfileId: params.params.authProfileId,
|
|
2075
|
-
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2076
|
-
modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
|
|
2077
|
-
dynamicToolsFingerprint,
|
|
2078
|
-
dynamicToolsContainDeferred,
|
|
2079
|
-
userMcpServersFingerprint,
|
|
2080
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2081
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2082
|
-
networkProxyConfigFingerprint,
|
|
2083
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2084
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2085
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2086
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2087
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2088
|
-
contextEngine: contextEngineBinding,
|
|
2089
|
-
environmentSelectionFingerprint,
|
|
2090
|
-
lifecycle: {
|
|
2091
|
-
action: "started",
|
|
2092
|
-
...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
|
|
2093
|
-
}
|
|
2094
|
-
};
|
|
2095
|
-
});
|
|
2096
|
-
}
|
|
2097
|
-
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
2098
|
-
if (!params.current) return false;
|
|
2099
|
-
if (params.binding === params.current) return false;
|
|
2100
|
-
return params.connectionClass === "remote" || Boolean(params.binding);
|
|
2101
|
-
}
|
|
2102
|
-
function resolveCodexGpt56MultiAgentVersion(modelRef) {
|
|
2103
|
-
let modelId = modelRef?.trim().toLowerCase();
|
|
2104
|
-
if (!modelId) return;
|
|
2105
|
-
const slashIndex = modelId.indexOf("/");
|
|
2106
|
-
if (slashIndex > 0) {
|
|
2107
|
-
const provider = modelId.slice(0, slashIndex);
|
|
2108
|
-
if (provider !== "openai" && provider !== "codex") return;
|
|
2109
|
-
modelId = modelId.slice(slashIndex + 1);
|
|
2110
|
-
}
|
|
2111
|
-
if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
|
|
2112
|
-
return modelId === "gpt-5.6-luna" ? "v1" : void 0;
|
|
2113
|
-
}
|
|
2114
|
-
function shouldRotateCodexGpt56MultiAgentBinding(params) {
|
|
2115
|
-
const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
|
|
2116
|
-
const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
|
|
2117
|
-
return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
|
|
2118
|
-
}
|
|
2119
|
-
function isTransientWebSearchRestriction(params) {
|
|
2120
|
-
if (params.nativeProviderWebSearchSupport === "unknown") return true;
|
|
2121
|
-
if (params.params.config?.tools?.web?.search?.enabled === false) return false;
|
|
2122
|
-
if (params.params.disableTools === true) return true;
|
|
2123
|
-
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
2124
|
-
if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
|
|
2125
|
-
if (params.webSearchAllowed !== false) return false;
|
|
2126
|
-
if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
|
|
2127
|
-
if (params.params.toolsAllow === void 0) return false;
|
|
2128
|
-
return !params.params.toolsAllow.some((name) => {
|
|
2129
|
-
const normalized = normalizeCodexDynamicToolName(name);
|
|
2130
|
-
return normalized === "*" || normalized === "web_search";
|
|
2131
|
-
});
|
|
2132
|
-
}
|
|
2133
|
-
function buildContextEngineBinding(params, projection) {
|
|
2134
|
-
const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
|
|
2135
|
-
const engineId = contextEngine?.info?.id?.trim();
|
|
2136
|
-
if (!contextEngine || !engineId) return;
|
|
2137
|
-
return {
|
|
2138
|
-
schemaVersion: 1,
|
|
2139
|
-
engineId,
|
|
2140
|
-
policyFingerprint: JSON.stringify({
|
|
2141
|
-
schemaVersion: 1,
|
|
2142
|
-
engineId,
|
|
2143
|
-
engineVersion: contextEngine.info.version,
|
|
2144
|
-
ownsCompaction: contextEngine.info.ownsCompaction === true,
|
|
2145
|
-
turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
|
|
2146
|
-
citationsMode: resolveContextEngineCitationsMode(params.config),
|
|
2147
|
-
contextTokenBudget: params.contextTokenBudget,
|
|
2148
|
-
projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
|
|
2149
|
-
contextTokenBudget: params.contextTokenBudget,
|
|
2150
|
-
reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
|
|
2151
|
-
})
|
|
2152
|
-
}),
|
|
2153
|
-
projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
|
|
2154
|
-
};
|
|
2155
|
-
}
|
|
2156
|
-
function buildContextEngineProjectionBinding(projection) {
|
|
2157
|
-
return {
|
|
2158
|
-
schemaVersion: 1,
|
|
2159
|
-
mode: "thread_bootstrap",
|
|
2160
|
-
epoch: projection.epoch,
|
|
2161
|
-
fingerprint: projection.fingerprint
|
|
2162
|
-
};
|
|
2163
|
-
}
|
|
2164
|
-
function isContextEngineBindingCompatible(previous, next) {
|
|
2165
|
-
return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
|
|
2166
|
-
}
|
|
2167
|
-
function areContextEngineProjectionBindingsCompatible(previous, next) {
|
|
2168
|
-
if (!next) return previous === void 0;
|
|
2169
|
-
return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
|
|
2170
|
-
}
|
|
2171
|
-
function resolveContextEngineCitationsMode(config) {
|
|
2172
|
-
const rootConfig = isUnknownRecord(config) ? config : void 0;
|
|
2173
|
-
const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
|
|
2174
|
-
return isJsonConfigValue(citations) ? citations : void 0;
|
|
2175
|
-
}
|
|
2176
|
-
function isUnknownRecord(value) {
|
|
2177
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2178
|
-
}
|
|
2179
|
-
function isJsonConfigValue(value) {
|
|
2180
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
2181
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
2182
|
-
if (Array.isArray(value)) return value.every(isJsonConfigValue);
|
|
2183
|
-
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
|
|
2184
|
-
}
|
|
2185
|
-
function shouldRecheckRecoverablePluginBinding(params) {
|
|
2186
|
-
if (!params.pluginThreadConfig?.enabled) return false;
|
|
2187
|
-
if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
|
|
2188
|
-
const policyContext = params.binding.pluginAppPolicyContext;
|
|
2189
|
-
if (!policyContext) return false;
|
|
2190
|
-
const expectedPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
|
|
2191
|
-
return Object.keys(policyContext.apps).length === 0 || expectedPluginConfigKeys.length > 0;
|
|
2192
|
-
}
|
|
2193
|
-
function buildThreadStartParams(params, options) {
|
|
2194
|
-
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
2195
|
-
provider: params.provider,
|
|
2196
|
-
authProfileId: params.authProfileId,
|
|
2197
|
-
authProfileStore: params.authProfileStore,
|
|
2198
|
-
agentDir: params.agentDir,
|
|
2199
|
-
config: params.config
|
|
2200
|
-
});
|
|
2201
|
-
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
2202
|
-
model: options.model ?? params.modelId,
|
|
2203
|
-
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
2204
|
-
authProfileId: params.authProfileId,
|
|
2205
|
-
authProfileStore: params.authProfileStore,
|
|
2206
|
-
agentDir: params.agentDir,
|
|
2207
|
-
config: params.config
|
|
2208
|
-
});
|
|
2209
|
-
return {
|
|
2210
|
-
model: modelSelection.model,
|
|
2211
|
-
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
2212
|
-
cwd: options.cwd,
|
|
2213
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
2214
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2215
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2216
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2217
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2218
|
-
serviceName: "OpenClaw",
|
|
2219
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2220
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2221
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2222
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2223
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2224
|
-
appServer: options.appServer
|
|
2225
|
-
}),
|
|
2226
|
-
...resolveCodexThreadEnvironmentSelection(options),
|
|
2227
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
2228
|
-
dynamicTools: [...options.dynamicTools],
|
|
2229
|
-
experimentalRawEvents: true
|
|
2230
|
-
};
|
|
2231
|
-
}
|
|
2232
|
-
function buildThreadResumeParams(params, options) {
|
|
2233
|
-
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
2234
|
-
provider: params.provider,
|
|
2235
|
-
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
2236
|
-
authProfileStore: params.authProfileStore,
|
|
2237
|
-
agentDir: params.agentDir,
|
|
2238
|
-
config: params.config
|
|
2239
|
-
});
|
|
2240
|
-
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
2241
|
-
model: options.model ?? params.modelId,
|
|
2242
|
-
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
2243
|
-
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
2244
|
-
authProfileStore: params.authProfileStore,
|
|
2245
|
-
agentDir: params.agentDir,
|
|
2246
|
-
config: params.config
|
|
2247
|
-
});
|
|
2248
|
-
return {
|
|
2249
|
-
threadId: options.threadId,
|
|
2250
|
-
model: modelSelection.model,
|
|
2251
|
-
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
2252
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
2253
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2254
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2255
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2256
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2257
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2258
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2259
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2260
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2261
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
2262
|
-
appServer: options.appServer
|
|
2263
|
-
}),
|
|
2264
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
function resolveCodexBindingModelProviderFallback(params) {
|
|
2268
|
-
const provider = params.provider?.trim().toLowerCase();
|
|
2269
|
-
if (provider && provider !== "codex") return;
|
|
2270
|
-
const currentModel = params.currentModel?.trim();
|
|
2271
|
-
const bindingModel = params.bindingModel?.trim();
|
|
2272
|
-
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
2273
|
-
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
2274
|
-
}
|
|
2275
|
-
function resolveCodexAppServerThreadModelSelection(params) {
|
|
2276
|
-
const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
|
|
2277
|
-
const explicitModelProvider = resolveCodexAppServerModelProvider({
|
|
2278
|
-
provider: params.provider,
|
|
2279
|
-
authProfileId,
|
|
2280
|
-
authProfileStore: params.authProfileStore,
|
|
2281
|
-
agentDir: params.agentDir,
|
|
2282
|
-
config: params.config
|
|
2283
|
-
});
|
|
2284
|
-
const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
|
|
2285
|
-
provider: params.provider,
|
|
2286
|
-
currentModel: params.model,
|
|
2287
|
-
bindingModel: params.binding.model,
|
|
2288
|
-
bindingModelProvider: params.binding.modelProvider
|
|
2289
|
-
}) : void 0;
|
|
2290
|
-
return resolveCodexAppServerRequestModelSelection({
|
|
2291
|
-
model: params.model,
|
|
2292
|
-
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
2293
|
-
authProfileId,
|
|
2294
|
-
authProfileStore: params.authProfileStore,
|
|
2295
|
-
agentDir: params.agentDir,
|
|
2296
|
-
config: params.config
|
|
2297
|
-
});
|
|
2298
|
-
}
|
|
2299
|
-
function resolveCodexAppServerRequestModelSelection(params) {
|
|
2300
|
-
const model = params.model.trim();
|
|
2301
|
-
const modelProvider = params.modelProvider?.trim();
|
|
2302
|
-
if (modelProvider) return {
|
|
2303
|
-
model,
|
|
2304
|
-
modelProvider
|
|
2305
|
-
};
|
|
2306
|
-
const slashIndex = model.indexOf("/");
|
|
2307
|
-
if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
|
|
2308
|
-
const inferredModelProvider = resolveCodexAppServerModelProvider({
|
|
2309
|
-
provider: model.slice(0, slashIndex),
|
|
2310
|
-
authProfileId: params.authProfileId,
|
|
2311
|
-
authProfileStore: params.authProfileStore,
|
|
2312
|
-
agentDir: params.agentDir,
|
|
2313
|
-
config: params.config
|
|
3004
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
3005
|
+
}
|
|
3006
|
+
} else {
|
|
3007
|
+
const resumed = await resumeExistingCodexThread(params, {
|
|
3008
|
+
binding,
|
|
3009
|
+
bindingIdentity,
|
|
3010
|
+
startModelSelection,
|
|
3011
|
+
startModelProvider,
|
|
3012
|
+
userMcpServersConfigPatch,
|
|
3013
|
+
dynamicToolsFingerprint,
|
|
3014
|
+
dynamicToolsContainDeferred,
|
|
3015
|
+
webSearchThreadConfigFingerprint,
|
|
3016
|
+
userMcpServersFingerprint,
|
|
3017
|
+
ringZeroConfigFingerprint,
|
|
3018
|
+
ringZeroClientInstanceId,
|
|
3019
|
+
networkProxyConfigFingerprint,
|
|
3020
|
+
contextEngineBinding,
|
|
3021
|
+
environmentSelectionFingerprint,
|
|
3022
|
+
hostSystemAgentActive,
|
|
3023
|
+
ringZeroActive,
|
|
3024
|
+
ringZeroInheritedMcpServerNames,
|
|
3025
|
+
lifecycleTiming,
|
|
3026
|
+
normalizeBindingModelProvider,
|
|
3027
|
+
throwIfAborted,
|
|
3028
|
+
clearCurrentBinding
|
|
3029
|
+
});
|
|
3030
|
+
if (resumed) return resumed;
|
|
3031
|
+
}
|
|
3032
|
+
return await startFreshCodexThread(params, {
|
|
3033
|
+
bindingIdentity,
|
|
3034
|
+
startModelSelection,
|
|
3035
|
+
startModelProvider,
|
|
3036
|
+
userMcpServersConfigPatch,
|
|
3037
|
+
dynamicToolsFingerprint,
|
|
3038
|
+
dynamicToolsContainDeferred,
|
|
3039
|
+
webSearchThreadConfigFingerprint,
|
|
3040
|
+
userMcpServersFingerprint,
|
|
3041
|
+
ringZeroConfigFingerprint,
|
|
3042
|
+
ringZeroClientInstanceId,
|
|
3043
|
+
networkProxyConfigFingerprint,
|
|
3044
|
+
contextEngineBinding,
|
|
3045
|
+
environmentSelectionFingerprint,
|
|
3046
|
+
hostSystemAgentActive,
|
|
3047
|
+
ringZeroActive,
|
|
3048
|
+
ringZeroInheritedMcpServerNames,
|
|
3049
|
+
lifecycleTiming,
|
|
3050
|
+
normalizeBindingModelProvider,
|
|
3051
|
+
throwIfAborted,
|
|
3052
|
+
prebuiltPluginThreadConfig,
|
|
3053
|
+
preserveExistingBinding,
|
|
3054
|
+
rotatedContextEngineBinding
|
|
3055
|
+
});
|
|
2314
3056
|
});
|
|
2315
|
-
return {
|
|
2316
|
-
model: model.slice(slashIndex + 1).trim(),
|
|
2317
|
-
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
2318
|
-
};
|
|
2319
3057
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
3058
|
+
//#endregion
|
|
3059
|
+
//#region extensions/codex/src/app-server/image-payload-sanitizer.ts
|
|
3060
|
+
/**
|
|
3061
|
+
* Sanitizes inline image payloads mirrored through Codex history so invalid
|
|
3062
|
+
* base64 data becomes readable text instead of poisoning replayed transcripts.
|
|
3063
|
+
*/
|
|
3064
|
+
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
3065
|
+
/** Validates and normalizes an inline image data URL for Codex history payloads. */
|
|
3066
|
+
function sanitizeInlineImageDataUrl$1(imageUrl) {
|
|
3067
|
+
return sanitizeInlineImageDataUrl(imageUrl);
|
|
2324
3068
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
if (
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
return
|
|
3069
|
+
/** Builds the replacement text inserted when an inline image payload is invalid. */
|
|
3070
|
+
function invalidInlineImageText(label) {
|
|
3071
|
+
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
3072
|
+
}
|
|
3073
|
+
function sanitizeImageContentRecord(record, label) {
|
|
3074
|
+
if (record.type === "image" && typeof record.data === "string") {
|
|
3075
|
+
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
|
|
3076
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
|
|
3077
|
+
if (!imageUrl) return {
|
|
3078
|
+
type: "text",
|
|
3079
|
+
text: invalidInlineImageText(label)
|
|
3080
|
+
};
|
|
3081
|
+
const commaIndex = imageUrl.indexOf(",");
|
|
3082
|
+
const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
|
|
3083
|
+
return {
|
|
3084
|
+
...record,
|
|
3085
|
+
mimeType: mime,
|
|
3086
|
+
data: imageUrl.slice(commaIndex + 1)
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
3090
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
|
|
3091
|
+
return imageUrl ? {
|
|
3092
|
+
...record,
|
|
3093
|
+
imageUrl
|
|
3094
|
+
} : {
|
|
3095
|
+
type: "inputText",
|
|
3096
|
+
text: invalidInlineImageText(label)
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
3100
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
|
|
3101
|
+
return imageUrl ? {
|
|
3102
|
+
...record,
|
|
3103
|
+
image_url: imageUrl
|
|
3104
|
+
} : {
|
|
3105
|
+
type: "input_text",
|
|
3106
|
+
text: invalidInlineImageText(label)
|
|
3107
|
+
};
|
|
2334
3108
|
}
|
|
2335
|
-
if (options.nativeCodeModeOnlyEnabled === true) return mergeCodexThreadConfigs(codeModeConfig, config, { "features.code_mode_only": true }) ?? {
|
|
2336
|
-
...codeModeConfig,
|
|
2337
|
-
"features.code_mode_only": true
|
|
2338
|
-
};
|
|
2339
|
-
return mergeCodexThreadConfigs(codeModeConfig, config) ?? { ...codeModeConfig };
|
|
2340
3109
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
3110
|
+
/** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
|
|
3111
|
+
function sanitizeCodexHistoryImagePayloads(value, label) {
|
|
3112
|
+
if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
|
|
3113
|
+
if (!isRecord(value)) return value;
|
|
3114
|
+
const imageRecord = sanitizeImageContentRecord(value, label);
|
|
3115
|
+
if (imageRecord) return imageRecord;
|
|
3116
|
+
const next = {};
|
|
3117
|
+
for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
|
|
3118
|
+
return next;
|
|
3119
|
+
}
|
|
3120
|
+
//#endregion
|
|
3121
|
+
//#region extensions/codex/src/app-server/user-input.ts
|
|
3122
|
+
/** Builds ordered Codex user input for both new turns and same-turn steering. */
|
|
3123
|
+
function buildCodexUserInput(text, images) {
|
|
3124
|
+
const imageInputs = (images ?? []).map((image) => {
|
|
3125
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
|
|
3126
|
+
return imageUrl ? {
|
|
3127
|
+
type: "image",
|
|
3128
|
+
url: imageUrl
|
|
3129
|
+
} : {
|
|
3130
|
+
type: "text",
|
|
3131
|
+
text: invalidInlineImageText("codex user input"),
|
|
3132
|
+
text_elements: []
|
|
3133
|
+
};
|
|
3134
|
+
});
|
|
3135
|
+
return [...text === void 0 ? [] : [{
|
|
3136
|
+
type: "text",
|
|
3137
|
+
text,
|
|
3138
|
+
text_elements: []
|
|
3139
|
+
}], ...imageInputs];
|
|
2356
3140
|
}
|
|
3141
|
+
//#endregion
|
|
3142
|
+
//#region extensions/codex/src/app-server/turn-params.ts
|
|
2357
3143
|
function buildTurnStartParams(params, options) {
|
|
2358
|
-
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
3144
|
+
const modelSelection = options.preserveNativeTurnSettings ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
2359
3145
|
model: options.model ?? params.modelId,
|
|
2360
3146
|
modelProvider: options.modelProvider,
|
|
2361
3147
|
authProfileId: params.authProfileId,
|
|
@@ -2366,37 +3152,27 @@ function buildTurnStartParams(params, options) {
|
|
|
2366
3152
|
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
2367
3153
|
return {
|
|
2368
3154
|
threadId: options.threadId,
|
|
2369
|
-
input:
|
|
3155
|
+
input: buildCodexUserInput(options.promptText ?? params.prompt, params.images),
|
|
2370
3156
|
cwd: options.cwd,
|
|
2371
3157
|
approvalPolicy: options.appServer.approvalPolicy,
|
|
2372
3158
|
approvalsReviewer: options.appServer.approvalsReviewer,
|
|
2373
3159
|
...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.cwd) },
|
|
2374
|
-
|
|
2375
|
-
|
|
3160
|
+
...modelSelection ? {
|
|
3161
|
+
model: modelSelection.model,
|
|
3162
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
3163
|
+
} : {},
|
|
2376
3164
|
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2377
|
-
effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)),
|
|
3165
|
+
...modelSelection ? { effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)) } : {},
|
|
2378
3166
|
...options.environmentSelection ? { environments: options.environmentSelection } : {},
|
|
2379
|
-
collaborationMode: buildTurnCollaborationMode(params, {
|
|
3167
|
+
...modelSelection ? { collaborationMode: buildTurnCollaborationMode(params, {
|
|
2380
3168
|
model: modelSelection.model,
|
|
2381
3169
|
turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
|
|
2382
3170
|
skillsCollaborationInstructions: options.skillsCollaborationInstructions,
|
|
2383
3171
|
memoryCollaborationInstructions: options.memoryCollaborationInstructions,
|
|
2384
3172
|
heartbeatCollaborationInstructions: options.heartbeatCollaborationInstructions
|
|
2385
|
-
})
|
|
3173
|
+
}) } : {}
|
|
2386
3174
|
};
|
|
2387
3175
|
}
|
|
2388
|
-
function resolveCodexThreadApprovalsReviewer(appServer, config) {
|
|
2389
|
-
return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
|
|
2390
|
-
}
|
|
2391
|
-
function codexThreadSandboxOrPermissions(appServer) {
|
|
2392
|
-
if (appServer.networkProxy) return {};
|
|
2393
|
-
return { sandbox: appServer.sandbox };
|
|
2394
|
-
}
|
|
2395
|
-
function resolveCodexThreadEnvironmentSelection(options) {
|
|
2396
|
-
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
2397
|
-
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
2398
|
-
return {};
|
|
2399
|
-
}
|
|
2400
3176
|
function buildTurnCollaborationMode(params, options = {}) {
|
|
2401
3177
|
const model = options.model ?? params.modelId;
|
|
2402
3178
|
return {
|
|
@@ -2442,149 +3218,11 @@ function buildHeartbeatCollaborationInstructions() {
|
|
|
2442
3218
|
return [
|
|
2443
3219
|
"This is an OpenClaw heartbeat turn. Apply these instructions only to this heartbeat wake; ordinary chat turns should stay in Codex Default mode.",
|
|
2444
3220
|
"When you are ready to end the heartbeat, prefer the structured `heartbeat_respond` tool so OpenClaw can record the wake outcome and notification decision. If `heartbeat_respond` is not already available and `tool_search` is available, search for `heartbeat_respond`, load it, then call it. Use `notify=false` when nothing should visibly interrupt the user.",
|
|
2445
|
-
|
|
3221
|
+
GPT5_HEARTBEAT_PROMPT_OVERLAY
|
|
2446
3222
|
].join("\n\n");
|
|
2447
3223
|
}
|
|
2448
3224
|
function joinPresentSections(...sections) {
|
|
2449
3225
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
2450
3226
|
}
|
|
2451
|
-
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
2452
|
-
return fingerprintDynamicTools(dynamicTools);
|
|
2453
|
-
}
|
|
2454
|
-
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
2455
|
-
return legacyFingerprintDynamicTools(dynamicTools);
|
|
2456
|
-
}
|
|
2457
|
-
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
2458
|
-
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
2459
|
-
}
|
|
2460
|
-
function fingerprintDynamicTools(dynamicTools) {
|
|
2461
|
-
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
2462
|
-
}
|
|
2463
|
-
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
2464
|
-
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
2465
|
-
}
|
|
2466
|
-
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
2467
|
-
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
2468
|
-
}
|
|
2469
|
-
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
2470
|
-
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(configPatch))) : void 0;
|
|
2471
|
-
}
|
|
2472
|
-
function fingerprintJsonObject(value) {
|
|
2473
|
-
return JSON.stringify(stabilizeJsonValue(value));
|
|
2474
|
-
}
|
|
2475
|
-
function fingerprintEnvironmentSelection(environments) {
|
|
2476
|
-
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
2477
|
-
}
|
|
2478
|
-
function fingerprintDynamicToolSpec(tool) {
|
|
2479
|
-
return stabilizeDynamicToolFingerprintValue(tool);
|
|
2480
|
-
}
|
|
2481
|
-
function stabilizeDynamicToolFingerprintValue(value) {
|
|
2482
|
-
if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
|
|
2483
|
-
if (!isJsonObject(value)) return value;
|
|
2484
|
-
const stable = {};
|
|
2485
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
2486
|
-
if (key === "description") continue;
|
|
2487
|
-
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
|
2488
|
-
}
|
|
2489
|
-
return stable;
|
|
2490
|
-
}
|
|
2491
|
-
function stabilizeJsonValue(value) {
|
|
2492
|
-
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
2493
|
-
if (!isJsonObject(value)) return value;
|
|
2494
|
-
const stable = {};
|
|
2495
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
2496
|
-
return stable;
|
|
2497
|
-
}
|
|
2498
|
-
function readActiveCodexTurnIds(thread) {
|
|
2499
|
-
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
2500
|
-
}
|
|
2501
|
-
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
2502
|
-
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
2503
|
-
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
2504
|
-
return !previous || previous === next || previous === nextLegacy;
|
|
2505
|
-
}
|
|
2506
|
-
function areUserMcpServersFingerprintsCompatible(params) {
|
|
2507
|
-
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
2508
|
-
}
|
|
2509
|
-
function shouldStartTransientNoToolThread(params) {
|
|
2510
|
-
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
2511
|
-
}
|
|
2512
|
-
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
2513
|
-
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
2514
|
-
}
|
|
2515
|
-
function compareJsonFingerprint(left, right) {
|
|
2516
|
-
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
2517
|
-
}
|
|
2518
|
-
function buildDeveloperInstructions(params, options = {}) {
|
|
2519
|
-
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
2520
|
-
surface: "codex_app_server",
|
|
2521
|
-
includeLegacyGlobalGuidance: false
|
|
2522
|
-
}).join("\n");
|
|
2523
|
-
return [
|
|
2524
|
-
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
2525
|
-
buildDeferredDynamicToolManifest(options.dynamicTools),
|
|
2526
|
-
buildSkillWorkshopInstruction(options.dynamicTools),
|
|
2527
|
-
"Use Codex native `spawn_agent` for Codex subagents. `spawn_agent` and the other native collaboration tools may be deferred: when `spawn_agent` is not directly listed, load it with `tool_search` before spawning. Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent`.",
|
|
2528
|
-
buildVisibleReplyInstruction(params, options.dynamicTools),
|
|
2529
|
-
nativeCommandGuidance,
|
|
2530
|
-
params.extraSystemPrompt
|
|
2531
|
-
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
2532
|
-
}
|
|
2533
|
-
function buildDeferredDynamicToolManifest(dynamicTools) {
|
|
2534
|
-
const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
|
|
2535
|
-
if (deferredToolNames.length === 0) return;
|
|
2536
|
-
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
2537
|
-
}
|
|
2538
|
-
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
2539
|
-
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
2540
|
-
return buildSkillWorkshopPromptSection().join("\n");
|
|
2541
|
-
}
|
|
2542
|
-
function buildVisibleReplyInstruction(params, dynamicTools) {
|
|
2543
|
-
const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
|
|
2544
|
-
if (params.sourceReplyDeliveryMode === "message_tool_only" && messageToolAvailable) return "Visible source replies are not automatically delivered for this run. Use `message(action=send)` for user-visible source-channel output. Do not repeat that visible content in your final answer.";
|
|
2545
|
-
if (messageToolAvailable) return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation. Use `message` only for explicit out-of-band sends, media/file sends, or sends to a different target.";
|
|
2546
|
-
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
|
|
2547
|
-
}
|
|
2548
|
-
function buildUserInput(params, promptText = params.prompt) {
|
|
2549
|
-
const imageInputs = (params.images ?? []).map((image) => {
|
|
2550
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
|
|
2551
|
-
return imageUrl ? {
|
|
2552
|
-
type: "image",
|
|
2553
|
-
url: imageUrl
|
|
2554
|
-
} : {
|
|
2555
|
-
type: "text",
|
|
2556
|
-
text: invalidInlineImageText("codex user input"),
|
|
2557
|
-
text_elements: []
|
|
2558
|
-
};
|
|
2559
|
-
});
|
|
2560
|
-
return [{
|
|
2561
|
-
type: "text",
|
|
2562
|
-
text: promptText,
|
|
2563
|
-
text_elements: []
|
|
2564
|
-
}, ...imageInputs];
|
|
2565
|
-
}
|
|
2566
|
-
function resolveCodexAppServerModelProvider(params) {
|
|
2567
|
-
const normalized = params.provider.trim();
|
|
2568
|
-
const normalizedLower = normalized.toLowerCase();
|
|
2569
|
-
if (!normalized || normalizedLower === "codex") return;
|
|
2570
|
-
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
2571
|
-
return normalizedLower === "openai" ? "openai" : normalized;
|
|
2572
|
-
}
|
|
2573
|
-
function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
|
|
2574
|
-
if (thinkLevel === "off" || thinkLevel === "adaptive") return null;
|
|
2575
|
-
if (supportedReasoningEfforts) return resolveCodexSupportedReasoningEffort({
|
|
2576
|
-
requested: thinkLevel,
|
|
2577
|
-
supportedReasoningEfforts
|
|
2578
|
-
}) ?? null;
|
|
2579
|
-
const fallbackReasoningEfforts = resolveCodexFallbackReasoningEfforts(modelId);
|
|
2580
|
-
if (fallbackReasoningEfforts) return resolveCodexSupportedReasoningEffort({
|
|
2581
|
-
requested: thinkLevel,
|
|
2582
|
-
supportedReasoningEfforts: fallbackReasoningEfforts
|
|
2583
|
-
}) ?? null;
|
|
2584
|
-
if (thinkLevel === "minimal") return isModernCodexModel(modelId) ? "low" : "minimal";
|
|
2585
|
-
if (thinkLevel === "low" || thinkLevel === "medium" || thinkLevel === "high" || thinkLevel === "xhigh") return thinkLevel;
|
|
2586
|
-
if (thinkLevel === "max" && isMaxReasoningCodexModel(modelId)) return "max";
|
|
2587
|
-
return null;
|
|
2588
|
-
}
|
|
2589
3227
|
//#endregion
|
|
2590
|
-
export {
|
|
3228
|
+
export { CodexAppServerUnsafeSubscriptionError as A, fitCodexProjectedContextForTurnStart as B, isForcedPrivateQaCodexRuntime as C, resolveCodexDynamicToolsLoadingForRuntime as D, resolveCodexDynamicToolsLoading as E, areCodexDynamicToolFingerprintsCompatible as F, buildCodexPluginThreadConfig as G, resolveCodexContextEngineProjectionMaxChars as H, codexDynamicToolsFingerprint as I, mergeCodexThreadConfigs as J, buildCodexPluginThreadConfigInputFingerprint as K, codexLegacyDynamicToolsFingerprint as L, interruptCodexTurnBestEffort as M, retireCodexAppServerClientAfterTimedOutTurn as N, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as O, unsubscribeCodexThreadBestEffort as P, resolveRecoverableCodexPluginConfigKeys as Q, buildContextEngineBinding as R, filterCodexDynamicToolsWithOpenClawShell as S, normalizeCodexDynamicToolName as T, resolveCodexContextEngineProjectionReserveTokens as U, projectContextEngineAssemblyForCodex as V, buildCodexPluginAppsConfigPatchFromPolicyContext as W, ensureCodexPluginActivation as X, shouldBuildCodexPluginThreadConfig as Y, pluginReadParams as Z, resolveCodexBindingModelProviderFallback as _, sanitizeCodexHistoryImagePayloads as a, isCodexAppServerProfilerEnabled as b, resumeCodexAppServerThread as c, resolveCodexWebSearchPlan as d, buildDeveloperInstructions as f, resolveCodexAppServerThreadModelSelection as g, resolveCodexAppServerRequestModelSelection as h, invalidInlineImageText as i, closeCodexStartupClientBestEffort as j, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as k, buildCodexRuntimeThreadConfig as l, resolveCodexAppServerModelProvider as m, buildTurnStartParams as n, sanitizeInlineImageDataUrl$1 as o, CODEX_NATIVE_PERSONALITY_NONE as p, buildCodexPluginThreadConfigTimeoutFallback as q, buildCodexUserInput as r, startOrResumeThread as s, buildTurnCollaborationMode as t, buildCodexNativeWebSearchThreadConfig as u, resolveReasoningEffort as v, isSystemAgentOnlyCodexDynamicToolAllowlist as w, filterCodexDynamicTools as x, readCodexSupportedReasoningEfforts as y, isContextEngineBindingCompatible as z };
|