@openclaw/codex 2026.7.2-beta.5 → 2026.7.33
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 +3 -27
- package/dist/{command-formatters-ico4ZFnk.js → .setup/app-server-policy-C_qTMEMy.mjs} +51 -9
- package/dist/{attempt-notifications-CMR2_fmR.js → .setup/attempt-notifications-F8krwWL4.mjs} +6 -3
- package/dist/{command-handlers-CRmnwI2N.js → .setup/command-handlers-DSRFfor4.mjs} +1156 -1319
- package/dist/.setup/command-rpc-BvYsfQ7G.mjs +786 -0
- package/dist/{compact-dEN49UH6.js → .setup/compact-BNgh7I4t.mjs} +10 -40
- package/dist/.setup/computer-use-C3QkmSO3.mjs +377 -0
- package/dist/.setup/config-CYEDnLJ2.mjs +1138 -0
- package/dist/{conversation-binding-data-BHRpYbX3.js → .setup/conversation-binding-data-CrRxtDBu.mjs} +1 -1
- package/dist/{bounded-turn-FEbUXoYT.js → .setup/media-understanding-provider-EbJCyDqe.mjs} +193 -92
- package/dist/{models-BA01NEez.js → .setup/models-C_aZk03A.mjs} +11 -6
- package/dist/.setup/notification-correlation-iYs4WieS.mjs +35 -0
- package/dist/.setup/plugin-app-cache-key-DR8jV9oS.mjs +99 -0
- package/dist/.setup/protocol-2POPqAY4.mjs +13 -0
- package/dist/{protocol-validators-Dm4eXZmt.js → .setup/protocol-validators-dZQ-UTOa.mjs} +111 -334
- package/dist/{rate-limits-S_PXs0ul.js → .setup/provider-DGxDs6g8.mjs} +269 -13
- package/dist/{dynamic-tools-LIt9ojep.js → .setup/provider-capabilities-DS2N_zc1.mjs} +5465 -3399
- package/dist/.setup/request-DFR0JyoV.mjs +36 -0
- package/dist/.setup/run-attempt-C7xEKFVB.mjs +8180 -0
- package/dist/{sandbox-guard-D1Wky__S.js → .setup/sandbox-guard-DA2TQfZW.mjs} +1 -10
- package/dist/.setup/session-binding-DuihlMJc.mjs +628 -0
- package/dist/.setup/shared-client-B2KmLPRw.mjs +1854 -0
- package/dist/{side-question-CO12sHxo.js → .setup/side-question-Cv6JXWCS.mjs} +84 -159
- package/dist/{thread-lifecycle-DRg_FdzC.js → .setup/thread-lifecycle-u6CZdOOH.mjs} +1159 -1799
- package/dist/{web-search-provider.runtime-CaQMYDbp.js → .setup/web-search-provider.runtime-Ce2zOHxW.mjs} +3 -3
- package/dist/doctor-contract-api.js +122 -88
- package/dist/harness.js +20 -85
- package/dist/index.js +381 -1537
- package/dist/media-understanding-provider.js +1 -173
- package/dist/prompt-overlay.js +15 -0
- package/dist/provider-catalog.js +79 -0
- package/dist/provider-discovery.js +34 -0
- package/dist/provider.js +2 -0
- package/dist/web-search-contract-api.js +1 -1
- package/openclaw.plugin.json +178 -221
- package/package.json +8 -10
- package/dist/app-server-policy-BmDgseo_.js +0 -49
- package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
- package/dist/auth-binding-B4FfbcCg.js +0 -62
- package/dist/capabilities-BHRSI6Cn.js +0 -36
- package/dist/cli-metadata.js +0 -20
- package/dist/computer-use-BjGylO-E.js +0 -695
- package/dist/conversation-control-DE7d9DOj.js +0 -287
- package/dist/event-projector-assistant-message-DTgnPPVF.js +0 -101
- package/dist/event-projector-usage-tvgb1NCX.js +0 -69
- package/dist/incognito-session-ET_ufDH-.js +0 -2
- package/dist/notification-correlation-Bj_Ak_jI.js +0 -67
- package/dist/plugin-app-cache-key-DSXX-rXO.js +0 -315
- package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
- package/dist/realtime-voice-api.js +0 -2
- package/dist/request-CXpPennK.js +0 -157
- package/dist/run-attempt-C5GTPJOE.js +0 -11935
- package/dist/runtime-artifact-C2ITXdhM.js +0 -488
- package/dist/session-binding-DlVQ-Pxs.js +0 -2161
- package/dist/session-catalog-CP2oCcEb.js +0 -2444
- package/dist/session-cli-CqZ0Fp2a.js +0 -175
- package/dist/session-upstream-marker-QE-m5QFG.js +0 -82
- package/dist/settled-turn-finalizer-CA_2QXIE.js +0 -303
- package/dist/shared-client-BVKE40c-.js +0 -2952
- package/dist/transcript-mirror-uKA-A4tB.js +0 -550
- package/dist/transport-stdio-DXgGZ7Ib.js +0 -88
- package/dist/upstream-session-fork-_Vrz4WJr.js +0 -320
- package/dist/usage-UpVbdX5G.js +0 -25
- /package/dist/{rolldown-runtime-8H4AJuhK.js → .setup/rolldown-runtime-8H4AJuhK.mjs} +0 -0
- /package/dist/{session-binding-meta-B7aEMU7g.js → .setup/session-binding-meta-B7aEMU7g.mjs} +0 -0
- /package/dist/{web-search-provider.shared-BrZmlqyR.js → .setup/web-search-provider.shared-BrZmlqyR.mjs} +0 -0
|
@@ -1,95 +1,237 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { a as
|
|
6
|
-
import { t as
|
|
7
|
-
import { a as
|
|
8
|
-
import
|
|
9
|
-
import
|
|
1
|
+
import { n as flattenCodexDynamicToolFunctions, r as isJsonObject } from "./protocol-2POPqAY4.mjs";
|
|
2
|
+
import { h as resolveCodexPluginsPolicy, i as codexSandboxPolicyForTurn, t as CODEX_PLUGINS_MARKETPLACE_NAME } from "./config-CYEDnLJ2.mjs";
|
|
3
|
+
import { n as assertCodexThreadResumeResponse, r as assertCodexThreadStartResponse } from "./protocol-validators-dZQ-UTOa.mjs";
|
|
4
|
+
import { CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "../prompt-overlay.js";
|
|
5
|
+
import { a as readCodexSupportedReasoningEfforts, i as isModernCodexModel, o as resolveCodexFallbackReasoningEfforts, r as isMaxReasoningCodexModel, s as resolveCodexSupportedReasoningEffort } from "./provider-DGxDs6g8.mjs";
|
|
6
|
+
import { f as isCodexAppServerConnectionClosedError, n as clearSharedCodexAppServerClientIfCurrentAndUnclaimed, s as retireSharedCodexAppServerClientIfCurrent, t as clearSharedCodexAppServerClientIfCurrent, u as CodexAppServerRpcError } from "./shared-client-B2KmLPRw.mjs";
|
|
7
|
+
import { a as normalizeCodexAppServerBindingModelProvider, i as isCodexAppServerNativeAuthProfile, o as reclaimCurrentCodexSessionGeneration, r as hashCodexAppServerBindingFingerprint, s as sessionBindingIdentity } from "./session-binding-DuihlMJc.mjs";
|
|
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";
|
|
10
12
|
import crypto from "node:crypto";
|
|
11
13
|
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { buildCodexUserMcpServersThreadConfigPatchForRuntime } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
15
|
-
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-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
16
|
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
17
|
-
import {
|
|
17
|
+
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
18
18
|
import { INLINE_IMAGE_DATA_URL_PREFIX, sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
19
|
+
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
20
|
+
const CODEX_TARGETED_APP_INVENTORY_LIMIT = 1e3;
|
|
21
|
+
const MAX_SERIALIZED_ERROR_MESSAGE_LENGTH = 500;
|
|
22
|
+
/** In-memory app inventory cache with coalesced refreshes per key. */
|
|
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;
|
|
38
94
|
}
|
|
39
|
-
|
|
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
|
+
};
|
|
40
156
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const error = record.error;
|
|
56
|
-
return isJsonObject(error) ? readString(error, "message") : void 0;
|
|
57
|
-
}
|
|
58
|
-
function readHookOutputEntries(value) {
|
|
59
|
-
if (!Array.isArray(value)) return [];
|
|
60
|
-
return value.flatMap((entry) => {
|
|
61
|
-
if (!isJsonObject(entry)) return [];
|
|
62
|
-
const text = readString(entry, "text");
|
|
63
|
-
if (!text) return [];
|
|
64
|
-
const kind = readString(entry, "kind");
|
|
65
|
-
return [{
|
|
66
|
-
...kind ? { kind } : {},
|
|
67
|
-
text
|
|
68
|
-
}];
|
|
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
|
|
69
171
|
});
|
|
70
172
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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);
|
|
93
235
|
}
|
|
94
236
|
//#endregion
|
|
95
237
|
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
@@ -110,21 +252,19 @@ async function readCodexPluginInventory(params) {
|
|
|
110
252
|
}]
|
|
111
253
|
};
|
|
112
254
|
const appInventory = readCachedAppInventory(params);
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
workspaceListResult = { kind: "rejected" };
|
|
127
|
-
}
|
|
255
|
+
const listed = await params.request("plugin/list", { cwds: [] });
|
|
256
|
+
const marketplaceEntry = listed.marketplaces.find(isOpenAiCuratedMarketplace);
|
|
257
|
+
if (!marketplaceEntry) return {
|
|
258
|
+
policy,
|
|
259
|
+
records: [],
|
|
260
|
+
diagnostics: policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled).map((pluginPolicy) => ({
|
|
261
|
+
code: "marketplace_missing",
|
|
262
|
+
plugin: pluginPolicy,
|
|
263
|
+
message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.`
|
|
264
|
+
})),
|
|
265
|
+
...appInventory ? { appInventory } : {}
|
|
266
|
+
};
|
|
267
|
+
let marketplace = marketplaceRef(marketplaceEntry);
|
|
128
268
|
const diagnostics = [];
|
|
129
269
|
const records = [];
|
|
130
270
|
if (appInventory?.state === "missing") diagnostics.push({
|
|
@@ -137,27 +277,19 @@ async function readCodexPluginInventory(params) {
|
|
|
137
277
|
});
|
|
138
278
|
for (const pluginPolicy of policy.pluginPolicies) {
|
|
139
279
|
if (!pluginPolicy.enabled) continue;
|
|
140
|
-
const
|
|
141
|
-
const hasMarketplace = pluginPolicy.marketplaceName === "workspace-directory" ? listed?.marketplaces.some((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME) === true : listed?.marketplaces.some(isOpenAiCuratedMarketplace) === true;
|
|
142
|
-
if (!listed || !hasMarketplace) {
|
|
143
|
-
diagnostics.push({
|
|
144
|
-
code: "marketplace_missing",
|
|
145
|
-
plugin: pluginPolicy,
|
|
146
|
-
message: `Codex marketplace ${pluginPolicy.marketplaceName} was not found.`
|
|
147
|
-
});
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const resolvedPlugin = pluginPolicy.marketplaceName === "workspace-directory" ? findWorkspaceMarketplacePlugin(listed, pluginPolicy.pluginName) : findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
|
|
280
|
+
const resolvedPlugin = findOpenAiCuratedMarketplacePlugin(listed, pluginPolicy.pluginName);
|
|
151
281
|
if (!resolvedPlugin) {
|
|
152
282
|
diagnostics.push({
|
|
153
283
|
code: "plugin_missing",
|
|
154
284
|
plugin: pluginPolicy,
|
|
155
|
-
message: `${pluginPolicy.pluginName} was not found in ${
|
|
285
|
+
message: `${pluginPolicy.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.`
|
|
156
286
|
});
|
|
157
287
|
continue;
|
|
158
288
|
}
|
|
159
289
|
const { summary } = resolvedPlugin;
|
|
160
|
-
const
|
|
290
|
+
const pluginMarketplace = marketplaceRef(resolvedPlugin.marketplace);
|
|
291
|
+
if (records.length === 0) marketplace = pluginMarketplace;
|
|
292
|
+
const detail = await readPluginDetail(params, pluginMarketplace, pluginPolicy, summary, diagnostics);
|
|
161
293
|
const ownedAppIds = detail?.apps.map((app) => app.id).filter(Boolean).toSorted() ?? [];
|
|
162
294
|
const appOwnership = resolveAppOwnership({
|
|
163
295
|
detail,
|
|
@@ -192,6 +324,7 @@ async function readCodexPluginInventory(params) {
|
|
|
192
324
|
}
|
|
193
325
|
return {
|
|
194
326
|
policy,
|
|
327
|
+
marketplace,
|
|
195
328
|
records,
|
|
196
329
|
diagnostics,
|
|
197
330
|
...appInventory ? { appInventory } : {}
|
|
@@ -201,7 +334,7 @@ async function readCodexPluginInventory(params) {
|
|
|
201
334
|
function findOpenAiCuratedPluginSummary(listed, pluginName) {
|
|
202
335
|
const resolved = findOpenAiCuratedMarketplacePlugin(listed, pluginName);
|
|
203
336
|
return resolved ? {
|
|
204
|
-
marketplace: marketplaceRef(resolved.marketplace
|
|
337
|
+
marketplace: marketplaceRef(resolved.marketplace),
|
|
205
338
|
summary: resolved.summary
|
|
206
339
|
} : void 0;
|
|
207
340
|
}
|
|
@@ -213,31 +346,6 @@ function pluginReadParams(marketplace, pluginName) {
|
|
|
213
346
|
pluginName
|
|
214
347
|
};
|
|
215
348
|
}
|
|
216
|
-
/** Returns configured plugin keys whose current metadata may still recover. */
|
|
217
|
-
function resolveRecoverableCodexPluginConfigKeys(params) {
|
|
218
|
-
return params.policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled && !isSettledMissingPluginPolicy({
|
|
219
|
-
pluginPolicy,
|
|
220
|
-
metadataCache: params.metadataCache,
|
|
221
|
-
appCacheKey: params.appCacheKey
|
|
222
|
-
})).map((pluginPolicy) => pluginPolicy.configKey).toSorted();
|
|
223
|
-
}
|
|
224
|
-
async function listCodexPluginMetadata(params, queryKind, requestParams) {
|
|
225
|
-
if (!params.metadataCache || !params.appCacheKey || queryKind === "workspace-directory") return await params.request("plugin/list", requestParams);
|
|
226
|
-
return (await params.metadataCache.load({
|
|
227
|
-
appCacheKey: params.appCacheKey,
|
|
228
|
-
queryKind,
|
|
229
|
-
requestParams,
|
|
230
|
-
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
231
|
-
cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
232
|
-
})).response;
|
|
233
|
-
}
|
|
234
|
-
function isSettledMissingPluginPolicy(params) {
|
|
235
|
-
const queryKind = params.pluginPolicy.marketplaceName === "workspace-directory" ? "workspace-directory" : "curated-global";
|
|
236
|
-
const listed = params.metadataCache.read(params.appCacheKey, queryKind)?.response;
|
|
237
|
-
if (!listed) return false;
|
|
238
|
-
if (queryKind === "workspace-directory") return !findWorkspaceMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
239
|
-
return !findOpenAiCuratedMarketplacePlugin(listed, params.pluginPolicy.pluginName);
|
|
240
|
-
}
|
|
241
349
|
function readCachedAppInventory(params) {
|
|
242
350
|
if (!params.appCache || !params.appCacheKey) return;
|
|
243
351
|
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
@@ -250,14 +358,6 @@ function readCachedAppInventory(params) {
|
|
|
250
358
|
}
|
|
251
359
|
async function readPluginDetail(params, marketplace, pluginPolicy, summary, diagnostics) {
|
|
252
360
|
if (params.readPluginDetails === false) return;
|
|
253
|
-
if (marketplace.name === "workspace-directory" && marketplace.remoteMarketplaceName && !summary.remotePluginId) {
|
|
254
|
-
diagnostics.push({
|
|
255
|
-
code: "plugin_detail_unavailable",
|
|
256
|
-
plugin: pluginPolicy,
|
|
257
|
-
message: `${pluginPolicy.pluginName} detail unavailable: Codex did not return a remote plugin id.`
|
|
258
|
-
});
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
361
|
try {
|
|
262
362
|
return (await params.request("plugin/read", pluginReadParams(marketplace, marketplace.remoteMarketplaceName && summary.remotePluginId ? summary.remotePluginId : pluginPolicy.pluginName))).plugin;
|
|
263
363
|
} catch (error) {
|
|
@@ -316,40 +416,31 @@ function findOpenAiCuratedMarketplacePlugin(listed, pluginName) {
|
|
|
316
416
|
};
|
|
317
417
|
}
|
|
318
418
|
}
|
|
319
|
-
function findWorkspaceMarketplacePlugin(listed, pluginName) {
|
|
320
|
-
const marketplace = listed.marketplaces.find((entry) => entry.name === CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME);
|
|
321
|
-
const summary = marketplace?.plugins.find((plugin) => plugin.id === pluginName);
|
|
322
|
-
return marketplace && summary ? {
|
|
323
|
-
marketplace,
|
|
324
|
-
summary
|
|
325
|
-
} : void 0;
|
|
326
|
-
}
|
|
327
419
|
function pluginNameFromPluginId(pluginId, marketplaceName) {
|
|
328
420
|
const trimmed = pluginId.trim();
|
|
329
421
|
if (!trimmed) return;
|
|
330
422
|
const marketplaceSuffix = `@${marketplaceName}`;
|
|
331
423
|
return (trimmed.endsWith(marketplaceSuffix) ? trimmed.slice(0, -marketplaceSuffix.length) : trimmed).split("/").at(-1)?.trim() || void 0;
|
|
332
424
|
}
|
|
333
|
-
function marketplaceRef(marketplace
|
|
425
|
+
function marketplaceRef(marketplace) {
|
|
334
426
|
return {
|
|
335
|
-
name,
|
|
427
|
+
name: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
336
428
|
...marketplace.path ? { path: marketplace.path } : {},
|
|
337
429
|
...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
|
|
338
430
|
};
|
|
339
431
|
}
|
|
340
|
-
/** True for either supported OpenAI curated marketplace wire name. */
|
|
341
432
|
function isOpenAiCuratedMarketplace(marketplace) {
|
|
342
433
|
return marketplace.name === "openai-curated" || marketplace.name === CODEX_PLUGINS_REMOTE_MARKETPLACE_NAME;
|
|
343
434
|
}
|
|
344
435
|
//#endregion
|
|
345
436
|
//#region extensions/codex/src/app-server/plugin-activation.ts
|
|
346
|
-
/**
|
|
437
|
+
/** Installs/enables a configured Codex plugin and refreshes plugin/app state. */
|
|
347
438
|
async function ensureCodexPluginActivation(params) {
|
|
348
|
-
if (params.identity.marketplaceName
|
|
349
|
-
const listed = await
|
|
439
|
+
if (params.identity.marketplaceName !== "openai-curated") return activationFailure(params.identity, "marketplace_missing", { message: "Only openai-curated plugins can be activated." });
|
|
440
|
+
const listed = await params.request("plugin/list", { cwds: [] });
|
|
350
441
|
const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName);
|
|
351
442
|
if (!resolved) {
|
|
352
|
-
if (!listed.marketplaces.some(
|
|
443
|
+
if (!listed.marketplaces.some((marketplace) => marketplace.name === "openai-curated")) return activationFailure(params.identity, "marketplace_missing", { message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.` });
|
|
353
444
|
return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
|
|
354
445
|
}
|
|
355
446
|
if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
|
|
@@ -361,7 +452,6 @@ async function ensureCodexPluginActivation(params) {
|
|
|
361
452
|
diagnostics: []
|
|
362
453
|
};
|
|
363
454
|
const installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, resolved.marketplace.remoteMarketplaceName && resolved.summary.remotePluginId ? resolved.summary.remotePluginId : params.identity.pluginName));
|
|
364
|
-
if (params.metadataCache && params.appCacheKey) params.metadataCache.invalidate(params.appCacheKey);
|
|
365
455
|
const refreshDiagnostics = [];
|
|
366
456
|
let refreshFailed = false;
|
|
367
457
|
try {
|
|
@@ -369,7 +459,6 @@ async function ensureCodexPluginActivation(params) {
|
|
|
369
459
|
request: params.request,
|
|
370
460
|
appCache: params.appCache,
|
|
371
461
|
appCacheKey: params.appCacheKey,
|
|
372
|
-
metadataCache: params.metadataCache,
|
|
373
462
|
targetAppIds: params.targetAppIds
|
|
374
463
|
});
|
|
375
464
|
refreshDiagnostics.push(...refreshResult.diagnostics);
|
|
@@ -391,7 +480,7 @@ async function ensureCodexPluginActivation(params) {
|
|
|
391
480
|
/** Forces Codex plugin, skill, hook, MCP, and app inventory refreshes after activation. */
|
|
392
481
|
async function refreshCodexPluginRuntimeState(params) {
|
|
393
482
|
const diagnostics = [];
|
|
394
|
-
await
|
|
483
|
+
await params.request("plugin/list", { cwds: [] });
|
|
395
484
|
await params.request("skills/list", {
|
|
396
485
|
cwds: [],
|
|
397
486
|
forceReload: true
|
|
@@ -418,17 +507,6 @@ async function refreshCodexPluginRuntimeState(params) {
|
|
|
418
507
|
}
|
|
419
508
|
return { diagnostics };
|
|
420
509
|
}
|
|
421
|
-
async function listCuratedCodexPluginMetadata(params) {
|
|
422
|
-
const requestParams = {};
|
|
423
|
-
if (!params.metadataCache || !params.appCacheKey) return await params.request("plugin/list", requestParams);
|
|
424
|
-
return (await params.metadataCache.load({
|
|
425
|
-
appCacheKey: params.appCacheKey,
|
|
426
|
-
queryKind: "curated-global",
|
|
427
|
-
requestParams,
|
|
428
|
-
request: async (method, listedParams) => await params.request(method, listedParams),
|
|
429
|
-
cacheable: (response) => (response.marketplaces ?? []).some((marketplace) => isOpenAiCuratedMarketplace(marketplace))
|
|
430
|
-
})).response;
|
|
431
|
-
}
|
|
432
510
|
function activationFailure(identity, reason, diagnostic, extraDiagnostics = []) {
|
|
433
511
|
return {
|
|
434
512
|
identity,
|
|
@@ -459,20 +537,6 @@ function buildCodexPluginThreadConfigInputFingerprint(params) {
|
|
|
459
537
|
appCacheKey: params.appCacheKey ?? null
|
|
460
538
|
});
|
|
461
539
|
}
|
|
462
|
-
/** Builds the deny-all app patch used when plugin discovery exceeds its turn budget. */
|
|
463
|
-
function buildCodexPluginThreadConfigTimeoutFallback(params) {
|
|
464
|
-
return {
|
|
465
|
-
...emptyPluginThreadConfig({
|
|
466
|
-
enabled: true,
|
|
467
|
-
inputFingerprint: buildCodexPluginThreadConfigInputFingerprint(params),
|
|
468
|
-
configPatch: buildDisabledAppsConfigPatch()
|
|
469
|
-
}),
|
|
470
|
-
diagnostics: [{
|
|
471
|
-
code: "plugin_config_timeout",
|
|
472
|
-
message: params.message
|
|
473
|
-
}]
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
540
|
/** Builds the Codex apps config patch and policy context for a native thread. */
|
|
477
541
|
async function buildCodexPluginThreadConfig(params) {
|
|
478
542
|
const appCache = params.appCache ?? defaultCodexAppInventoryCache;
|
|
@@ -492,7 +556,6 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
492
556
|
request: params.request,
|
|
493
557
|
appCache,
|
|
494
558
|
appCacheKey: params.appCacheKey,
|
|
495
|
-
metadataCache: params.metadataCache,
|
|
496
559
|
nowMs: params.nowMs,
|
|
497
560
|
suppressAppInventoryRefresh: true
|
|
498
561
|
}) : emptyCodexPluginInventory(policy);
|
|
@@ -509,7 +572,6 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
509
572
|
request: params.request,
|
|
510
573
|
appCache,
|
|
511
574
|
appCacheKey: params.appCacheKey,
|
|
512
|
-
metadataCache: params.metadataCache,
|
|
513
575
|
nowMs: params.nowMs
|
|
514
576
|
});
|
|
515
577
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -526,7 +588,6 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
526
588
|
request: params.request,
|
|
527
589
|
appCache,
|
|
528
590
|
appCacheKey: params.appCacheKey,
|
|
529
|
-
metadataCache: params.metadataCache,
|
|
530
591
|
targetAppIds: record.ownedAppIds
|
|
531
592
|
});
|
|
532
593
|
activationResults.push(activation);
|
|
@@ -550,7 +611,6 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
550
611
|
request: params.request,
|
|
551
612
|
appCache,
|
|
552
613
|
appCacheKey: params.appCacheKey,
|
|
553
|
-
metadataCache: params.metadataCache,
|
|
554
614
|
nowMs: params.nowMs
|
|
555
615
|
});
|
|
556
616
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -570,7 +630,6 @@ async function buildCodexPluginThreadConfig(params) {
|
|
|
570
630
|
request: params.request,
|
|
571
631
|
appCache,
|
|
572
632
|
appCacheKey: params.appCacheKey,
|
|
573
|
-
metadataCache: params.metadataCache,
|
|
574
633
|
nowMs: params.nowMs
|
|
575
634
|
});
|
|
576
635
|
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
@@ -888,6 +947,127 @@ function stableStringify(value) {
|
|
|
888
947
|
return JSON.stringify(value);
|
|
889
948
|
}
|
|
890
949
|
//#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
|
|
891
1071
|
//#region extensions/codex/src/app-server/context-engine-projection.ts
|
|
892
1072
|
const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
|
|
893
1073
|
const CONTEXT_OPEN = "<conversation_context>";
|
|
@@ -943,9 +1123,13 @@ function resolveCodexContextEngineProjectionMaxChars(params) {
|
|
|
943
1123
|
reserveTokens: params.reserveTokens
|
|
944
1124
|
}) * APPROX_RENDERED_CHARS_PER_TOKEN);
|
|
945
1125
|
}
|
|
946
|
-
/**
|
|
947
|
-
function resolveCodexContextEngineProjectionReserveTokens() {
|
|
948
|
-
|
|
1126
|
+
/** Reads Codex projection reserve tokens from compaction config. */
|
|
1127
|
+
function resolveCodexContextEngineProjectionReserveTokens(params) {
|
|
1128
|
+
const compaction = asRecord(asRecord(asRecord(params.config)?.agents)?.defaults)?.compaction;
|
|
1129
|
+
const configuredReserveTokens = toNonNegativeInt(asRecord(compaction)?.reserveTokens);
|
|
1130
|
+
const configuredReserveTokensFloor = toNonNegativeInt(asRecord(compaction)?.reserveTokensFloor);
|
|
1131
|
+
if (configuredReserveTokens !== void 0) return Math.max(configuredReserveTokens, configuredReserveTokensFloor ?? 2e4);
|
|
1132
|
+
if (configuredReserveTokensFloor !== void 0) return configuredReserveTokensFloor;
|
|
949
1133
|
}
|
|
950
1134
|
/** Fits projected context prompts under Codex app-server turn/start text limits. */
|
|
951
1135
|
function fitCodexProjectedContextForTurnStart(params) {
|
|
@@ -993,6 +1177,13 @@ function resolveProjectionPromptBudgetTokens(params) {
|
|
|
993
1177
|
const effectiveReserveTokens = Math.min(requestedReserveTokens, Math.max(0, params.contextTokenBudget - minPromptBudget));
|
|
994
1178
|
return Math.max(1, params.contextTokenBudget - effectiveReserveTokens);
|
|
995
1179
|
}
|
|
1180
|
+
function asRecord(value) {
|
|
1181
|
+
return value && typeof value === "object" ? value : void 0;
|
|
1182
|
+
}
|
|
1183
|
+
function toNonNegativeInt(value) {
|
|
1184
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return;
|
|
1185
|
+
return Math.floor(value);
|
|
1186
|
+
}
|
|
996
1187
|
function dropDuplicateTrailingPrompt(messages, prompt) {
|
|
997
1188
|
if (!prompt) return messages;
|
|
998
1189
|
const trailing = messages.at(-1);
|
|
@@ -1120,9 +1311,7 @@ function resolveTextPartMaxChars(maxRenderedContextChars) {
|
|
|
1120
1311
|
return Math.min(MAX_TEXT_PART_CHARS, Math.max(DEFAULT_TEXT_PART_CHARS, Math.floor(maxRenderedContextChars / 4)));
|
|
1121
1312
|
}
|
|
1122
1313
|
function truncateText(text, maxChars) {
|
|
1123
|
-
|
|
1124
|
-
const truncated = truncateUtf16Safe(text, maxChars);
|
|
1125
|
-
return `${truncated}\n[truncated ${text.length - truncated.length} chars]`;
|
|
1314
|
+
return text.length > maxChars ? `${text.slice(0, maxChars)}\n[truncated ${text.length - maxChars} chars]` : text;
|
|
1126
1315
|
}
|
|
1127
1316
|
function truncateOlderContext(text, maxChars) {
|
|
1128
1317
|
if (text.length <= maxChars) return text;
|
|
@@ -1133,150 +1322,15 @@ function truncateOlderContext(text, maxChars) {
|
|
|
1133
1322
|
marker = buildMarker(text.length - tailChars);
|
|
1134
1323
|
if (marker.length >= maxChars) return marker.slice(0, maxChars);
|
|
1135
1324
|
tailChars = maxChars - marker.length;
|
|
1136
|
-
return `${marker}${
|
|
1137
|
-
}
|
|
1138
|
-
//#endregion
|
|
1139
|
-
//#region extensions/codex/src/app-server/thread-context-engine.ts
|
|
1140
|
-
function buildContextEngineBinding(params, projection) {
|
|
1141
|
-
const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
|
|
1142
|
-
const engineId = contextEngine?.info?.id?.trim();
|
|
1143
|
-
if (!contextEngine || !engineId) return;
|
|
1144
|
-
return {
|
|
1145
|
-
schemaVersion: 1,
|
|
1146
|
-
engineId,
|
|
1147
|
-
policyFingerprint: JSON.stringify({
|
|
1148
|
-
schemaVersion: 1,
|
|
1149
|
-
engineId,
|
|
1150
|
-
engineVersion: contextEngine.info.version,
|
|
1151
|
-
ownsCompaction: contextEngine.info.ownsCompaction === true,
|
|
1152
|
-
turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
|
|
1153
|
-
citationsMode: resolveContextEngineCitationsMode(params.config),
|
|
1154
|
-
contextTokenBudget: params.contextTokenBudget,
|
|
1155
|
-
projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
|
|
1156
|
-
contextTokenBudget: params.contextTokenBudget,
|
|
1157
|
-
reserveTokens: resolveCodexContextEngineProjectionReserveTokens()
|
|
1158
|
-
})
|
|
1159
|
-
}),
|
|
1160
|
-
projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
function buildContextEngineProjectionBinding(projection) {
|
|
1164
|
-
return {
|
|
1165
|
-
schemaVersion: 1,
|
|
1166
|
-
mode: "thread_bootstrap",
|
|
1167
|
-
epoch: projection.epoch,
|
|
1168
|
-
fingerprint: projection.fingerprint
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
function isContextEngineBindingCompatible(previous, next) {
|
|
1172
|
-
return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
|
|
1173
|
-
}
|
|
1174
|
-
function areContextEngineProjectionBindingsCompatible(previous, next) {
|
|
1175
|
-
if (!next) return previous === void 0;
|
|
1176
|
-
return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
|
|
1177
|
-
}
|
|
1178
|
-
function resolveContextEngineCitationsMode(config) {
|
|
1179
|
-
const rootConfig = isUnknownRecord(config) ? config : void 0;
|
|
1180
|
-
const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
|
|
1181
|
-
return isJsonConfigValue(citations) ? citations : void 0;
|
|
1182
|
-
}
|
|
1183
|
-
function isUnknownRecord(value) {
|
|
1184
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1185
|
-
}
|
|
1186
|
-
function isJsonConfigValue(value) {
|
|
1187
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1188
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
1189
|
-
if (Array.isArray(value)) return value.every(isJsonConfigValue);
|
|
1190
|
-
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
|
|
1191
|
-
}
|
|
1192
|
-
//#endregion
|
|
1193
|
-
//#region extensions/codex/src/app-server/thread-fingerprints.ts
|
|
1194
|
-
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
1195
|
-
return fingerprintDynamicTools(dynamicTools);
|
|
1196
|
-
}
|
|
1197
|
-
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
1198
|
-
return legacyFingerprintDynamicTools(dynamicTools);
|
|
1199
|
-
}
|
|
1200
|
-
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
1201
|
-
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
1202
|
-
}
|
|
1203
|
-
function fingerprintDynamicTools(dynamicTools) {
|
|
1204
|
-
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
1205
|
-
}
|
|
1206
|
-
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
1207
|
-
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
1208
|
-
}
|
|
1209
|
-
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1210
|
-
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
1211
|
-
}
|
|
1212
|
-
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1213
|
-
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
|
|
1325
|
+
return `${marker}${sliceTailFromCodePointBoundary(text, tailChars).trimStart()}`;
|
|
1214
1326
|
}
|
|
1215
|
-
function
|
|
1216
|
-
|
|
1217
|
-
if (
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1221
|
-
next[key] = Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]));
|
|
1222
|
-
continue;
|
|
1223
|
-
}
|
|
1224
|
-
next[key] = redactUserMcpServersFingerprintSecrets(entry);
|
|
1225
|
-
}
|
|
1226
|
-
return next;
|
|
1227
|
-
}
|
|
1228
|
-
function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
1229
|
-
return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
|
|
1230
|
-
}
|
|
1231
|
-
function fingerprintJsonObject(value) {
|
|
1232
|
-
return JSON.stringify(stabilizeJsonValue(value));
|
|
1233
|
-
}
|
|
1234
|
-
function fingerprintEnvironmentSelection(environments) {
|
|
1235
|
-
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
1236
|
-
}
|
|
1237
|
-
function fingerprintDynamicToolSpec(tool) {
|
|
1238
|
-
return stabilizeDynamicToolFingerprintValue(tool);
|
|
1239
|
-
}
|
|
1240
|
-
function stabilizeDynamicToolFingerprintValue(value) {
|
|
1241
|
-
if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
|
|
1242
|
-
if (!isJsonObject(value)) return value;
|
|
1243
|
-
const stable = {};
|
|
1244
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
1245
|
-
if (key === "description") continue;
|
|
1246
|
-
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
|
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;
|
|
1247
1332
|
}
|
|
1248
|
-
return
|
|
1249
|
-
}
|
|
1250
|
-
function stabilizeJsonValue(value) {
|
|
1251
|
-
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
1252
|
-
if (!isJsonObject(value)) return value;
|
|
1253
|
-
const stable = {};
|
|
1254
|
-
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
1255
|
-
return stable;
|
|
1256
|
-
}
|
|
1257
|
-
function readActiveCodexTurnIds(thread) {
|
|
1258
|
-
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
1259
|
-
}
|
|
1260
|
-
function readActiveCodexTurnIdsFromResume(response) {
|
|
1261
|
-
const pagedTurns = response.initialTurnsPage?.data;
|
|
1262
|
-
return readActiveCodexTurnIds(Array.isArray(pagedTurns) ? { turns: pagedTurns } : response.thread);
|
|
1263
|
-
}
|
|
1264
|
-
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
1265
|
-
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
1266
|
-
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
1267
|
-
return !previous || previous === next || previous === nextLegacy;
|
|
1268
|
-
}
|
|
1269
|
-
function areUserMcpServersFingerprintsCompatible(params) {
|
|
1270
|
-
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
1271
|
-
}
|
|
1272
|
-
function shouldStartTransientNoToolThread(params) {
|
|
1273
|
-
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
1274
|
-
}
|
|
1275
|
-
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
1276
|
-
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
1277
|
-
}
|
|
1278
|
-
function compareJsonFingerprint(left, right) {
|
|
1279
|
-
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
1333
|
+
return text.slice(start);
|
|
1280
1334
|
}
|
|
1281
1335
|
//#endregion
|
|
1282
1336
|
//#region extensions/codex/src/app-server/dynamic-tool-profile.ts
|
|
@@ -1294,12 +1348,6 @@ const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
|
|
|
1294
1348
|
"tool_search",
|
|
1295
1349
|
"tool_search_code"
|
|
1296
1350
|
];
|
|
1297
|
-
const CODEX_NATIVE_GOAL_TOOL_EXCLUDES = [
|
|
1298
|
-
"get_goal",
|
|
1299
|
-
"create_goal",
|
|
1300
|
-
"update_goal"
|
|
1301
|
-
];
|
|
1302
|
-
const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
|
|
1303
1351
|
const DYNAMIC_TOOL_NAME_ALIASES = {
|
|
1304
1352
|
bash: "exec",
|
|
1305
1353
|
"apply-patch": "apply_patch"
|
|
@@ -1309,10 +1357,6 @@ function normalizeCodexDynamicToolName(name) {
|
|
|
1309
1357
|
const normalized = name.trim().toLowerCase();
|
|
1310
1358
|
return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
|
|
1311
1359
|
}
|
|
1312
|
-
/** True only for the host-scoped OpenClaw run's exact tool contract. */
|
|
1313
|
-
function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
|
|
1314
|
-
return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
|
|
1315
|
-
}
|
|
1316
1360
|
/** Returns true for private QA runs that force the Codex runtime profile. */
|
|
1317
1361
|
function isForcedPrivateQaCodexRuntime(env = process.env) {
|
|
1318
1362
|
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
@@ -1346,20 +1390,8 @@ function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}
|
|
|
1346
1390
|
}
|
|
1347
1391
|
/** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
|
|
1348
1392
|
function filterCodexDynamicTools(tools, config, env = process.env) {
|
|
1349
|
-
return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: false });
|
|
1350
|
-
}
|
|
1351
|
-
/** Keeps exec/process only when Codex cannot advertise an environment-backed native shell. */
|
|
1352
|
-
function filterCodexDynamicToolsWithOpenClawShell(tools, config, env = process.env) {
|
|
1353
|
-
return filterCodexDynamicToolsWithOptions(tools, config, env, { preserveOpenClawShell: true });
|
|
1354
|
-
}
|
|
1355
|
-
function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
1356
1393
|
const excludes = /* @__PURE__ */ new Set();
|
|
1357
|
-
for (const name of
|
|
1358
|
-
if (isForcedPrivateQaCodexRuntime(env)) excludes.add("apply_patch");
|
|
1359
|
-
else for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
|
|
1360
|
-
if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
|
|
1361
|
-
excludes.add(name);
|
|
1362
|
-
}
|
|
1394
|
+
if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) excludes.add(name);
|
|
1363
1395
|
for (const name of config.codexDynamicToolsExclude ?? []) {
|
|
1364
1396
|
const trimmed = normalizeCodexDynamicToolName(name);
|
|
1365
1397
|
if (trimmed) excludes.add(trimmed);
|
|
@@ -1367,268 +1399,98 @@ function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
|
1367
1399
|
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
1368
1400
|
}
|
|
1369
1401
|
//#endregion
|
|
1370
|
-
//#region extensions/codex/src/app-server/
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
if (!params.current) return false;
|
|
1380
|
-
if (params.binding === params.current) return false;
|
|
1381
|
-
return params.connectionClass === "remote" || Boolean(params.binding);
|
|
1382
|
-
}
|
|
1383
|
-
function resolveCodexGpt56MultiAgentVersion(modelRef) {
|
|
1384
|
-
let modelId = modelRef?.trim().toLowerCase();
|
|
1385
|
-
if (!modelId) return;
|
|
1386
|
-
const slashIndex = modelId.indexOf("/");
|
|
1387
|
-
if (slashIndex > 0) {
|
|
1388
|
-
const provider = modelId.slice(0, slashIndex);
|
|
1389
|
-
if (provider !== "openai" && provider !== "codex") return;
|
|
1390
|
-
modelId = modelId.slice(slashIndex + 1);
|
|
1391
|
-
}
|
|
1392
|
-
if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
|
|
1393
|
-
return modelId === "gpt-5.6-luna" ? "v1" : void 0;
|
|
1394
|
-
}
|
|
1395
|
-
function shouldRotateCodexGpt56MultiAgentBinding(params) {
|
|
1396
|
-
const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
|
|
1397
|
-
const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
|
|
1398
|
-
return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
|
|
1399
|
-
}
|
|
1400
|
-
function isTransientWebSearchRestriction(params) {
|
|
1401
|
-
if (params.nativeProviderWebSearchSupport === "unknown") return true;
|
|
1402
|
-
if (params.params.config?.tools?.web?.search?.enabled === false) return false;
|
|
1403
|
-
if (params.params.disableTools === true) return true;
|
|
1404
|
-
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
1405
|
-
if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
|
|
1406
|
-
if (params.webSearchAllowed !== false) return false;
|
|
1407
|
-
if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
|
|
1408
|
-
if (params.params.toolsAllow === void 0) return false;
|
|
1409
|
-
return !params.params.toolsAllow.some((name) => {
|
|
1410
|
-
const normalized = normalizeCodexDynamicToolName(name);
|
|
1411
|
-
return normalized === "*" || normalized === "web_search";
|
|
1412
|
-
});
|
|
1402
|
+
//#region extensions/codex/src/app-server/image-payload-sanitizer.ts
|
|
1403
|
+
/**
|
|
1404
|
+
* Sanitizes inline image payloads mirrored through Codex history so invalid
|
|
1405
|
+
* base64 data becomes readable text instead of poisoning replayed transcripts.
|
|
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);
|
|
1413
1411
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
const policyContext = params.binding.pluginAppPolicyContext;
|
|
1418
|
-
if (!policyContext) return false;
|
|
1419
|
-
const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
|
|
1420
|
-
const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
|
|
1421
|
-
const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
|
|
1422
|
-
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));
|
|
1423
|
-
const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
|
|
1424
|
-
return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
|
|
1412
|
+
/** Builds the replacement text inserted when an inline image payload is invalid. */
|
|
1413
|
+
function invalidInlineImageText(label) {
|
|
1414
|
+
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
1425
1415
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
};
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1416
|
+
function sanitizeImageContentRecord(record, label) {
|
|
1417
|
+
if (record.type === "image" && typeof record.data === "string") {
|
|
1418
|
+
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
|
|
1419
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${mimeType};base64,${record.data}`);
|
|
1420
|
+
if (!imageUrl) return {
|
|
1421
|
+
type: "text",
|
|
1422
|
+
text: invalidInlineImageText(label)
|
|
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
|
+
};
|
|
1438
1431
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1432
|
+
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
1433
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
|
|
1434
|
+
return imageUrl ? {
|
|
1435
|
+
...record,
|
|
1436
|
+
imageUrl
|
|
1437
|
+
} : {
|
|
1438
|
+
type: "inputText",
|
|
1439
|
+
text: invalidInlineImageText(label)
|
|
1440
|
+
};
|
|
1444
1441
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1442
|
+
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
1443
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
|
|
1444
|
+
return imageUrl ? {
|
|
1445
|
+
...record,
|
|
1446
|
+
image_url: imageUrl
|
|
1447
|
+
} : {
|
|
1448
|
+
type: "input_text",
|
|
1449
|
+
text: invalidInlineImageText(label)
|
|
1450
|
+
};
|
|
1451
1451
|
}
|
|
1452
|
-
};
|
|
1453
|
-
//#endregion
|
|
1454
|
-
//#region extensions/codex/src/app-server/reasoning-effort.ts
|
|
1455
|
-
const CODEX_REASONING_EFFORTS = [
|
|
1456
|
-
"minimal",
|
|
1457
|
-
"low",
|
|
1458
|
-
"medium",
|
|
1459
|
-
"high",
|
|
1460
|
-
"xhigh",
|
|
1461
|
-
"max",
|
|
1462
|
-
"ultra"
|
|
1463
|
-
];
|
|
1464
|
-
const GPT_56_MAX_REASONING_EFFORTS = [
|
|
1465
|
-
"low",
|
|
1466
|
-
"medium",
|
|
1467
|
-
"high",
|
|
1468
|
-
"xhigh",
|
|
1469
|
-
"max"
|
|
1470
|
-
];
|
|
1471
|
-
const GPT_56_ULTRA_REASONING_EFFORTS = [...GPT_56_MAX_REASONING_EFFORTS, "ultra"];
|
|
1472
|
-
const GPT_5_PRO_REASONING_EFFORTS = [
|
|
1473
|
-
"medium",
|
|
1474
|
-
"high",
|
|
1475
|
-
"xhigh"
|
|
1476
|
-
];
|
|
1477
|
-
const GPT_56_ULTRA_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.6-sol", "gpt-5.6-terra"]);
|
|
1478
|
-
const GPT_56_MAX_MODEL_IDS = /* @__PURE__ */ new Set([...GPT_56_ULTRA_MODEL_IDS, "gpt-5.6-luna"]);
|
|
1479
|
-
const MODERN_CODEX_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
1480
|
-
...GPT_56_MAX_MODEL_IDS,
|
|
1481
|
-
"gpt-5.5",
|
|
1482
|
-
"gpt-5.5-pro",
|
|
1483
|
-
"gpt-5.4",
|
|
1484
|
-
"gpt-5.4-pro",
|
|
1485
|
-
"gpt-5.4-mini",
|
|
1486
|
-
"gpt-5.3-codex-spark"
|
|
1487
|
-
]);
|
|
1488
|
-
function normalizeCodexReasoningEfforts(efforts) {
|
|
1489
|
-
if (!efforts) return [];
|
|
1490
|
-
const supported = new Set(efforts.map((effort) => effort.trim().toLowerCase()));
|
|
1491
|
-
return CODEX_REASONING_EFFORTS.filter((effort) => supported.has(effort));
|
|
1492
|
-
}
|
|
1493
|
-
/** Read reasoning metadata after the Codex app-server route has been selected. */
|
|
1494
|
-
function readCodexSupportedReasoningEfforts(compat) {
|
|
1495
|
-
if (!compat || typeof compat !== "object" || Array.isArray(compat)) return;
|
|
1496
|
-
const efforts = compat.supportedReasoningEfforts;
|
|
1497
|
-
if (!Array.isArray(efforts)) return;
|
|
1498
|
-
return efforts.filter((effort) => typeof effort === "string");
|
|
1499
|
-
}
|
|
1500
|
-
function resolveSupportedReasoningEffort(params) {
|
|
1501
|
-
const supported = normalizeCodexReasoningEfforts(params.supportedReasoningEfforts);
|
|
1502
|
-
if (supported.includes(params.requested)) return params.requested;
|
|
1503
|
-
const fallbackEfforts = params.requested === "ultra" ? supported : supported.filter((effort) => effort !== "ultra");
|
|
1504
|
-
const requestedRank = CODEX_REASONING_EFFORTS.indexOf(params.requested);
|
|
1505
|
-
return fallbackEfforts.find((effort) => CODEX_REASONING_EFFORTS.indexOf(effort) >= requestedRank) ?? fallbackEfforts.at(-1);
|
|
1506
|
-
}
|
|
1507
|
-
function resolveFallbackReasoningEfforts(modelId) {
|
|
1508
|
-
const normalized = modelId.trim().toLowerCase();
|
|
1509
|
-
if (GPT_56_ULTRA_MODEL_IDS.has(normalized)) return GPT_56_ULTRA_REASONING_EFFORTS;
|
|
1510
|
-
if (normalized === "gpt-5.6-luna") return GPT_56_MAX_REASONING_EFFORTS;
|
|
1511
|
-
if (normalized === "gpt-5.5-pro" || normalized === "gpt-5.4-pro") return GPT_5_PRO_REASONING_EFFORTS;
|
|
1512
|
-
}
|
|
1513
|
-
/** Resolve a turn effort from app-server metadata, with exact-name offline fallbacks. */
|
|
1514
|
-
function resolveCodexAppServerReasoningEffort(params) {
|
|
1515
|
-
if (params.thinkLevel === "off" || params.thinkLevel === "adaptive") return null;
|
|
1516
|
-
const supportedReasoningEfforts = params.supportedReasoningEfforts ?? resolveFallbackReasoningEfforts(params.modelId);
|
|
1517
|
-
if (supportedReasoningEfforts) return resolveSupportedReasoningEffort({
|
|
1518
|
-
requested: params.thinkLevel,
|
|
1519
|
-
supportedReasoningEfforts
|
|
1520
|
-
}) ?? null;
|
|
1521
|
-
const normalizedModelId = params.modelId.trim().toLowerCase();
|
|
1522
|
-
if (params.thinkLevel === "minimal") return MODERN_CODEX_MODEL_IDS.has(normalizedModelId) ? "low" : "minimal";
|
|
1523
|
-
if (params.thinkLevel === "low" || params.thinkLevel === "medium" || params.thinkLevel === "high" || params.thinkLevel === "xhigh") return params.thinkLevel;
|
|
1524
|
-
return params.thinkLevel === "max" && GPT_56_MAX_MODEL_IDS.has(normalizedModelId) ? "max" : null;
|
|
1525
|
-
}
|
|
1526
|
-
//#endregion
|
|
1527
|
-
//#region extensions/codex/src/app-server/thread-model-selection.ts
|
|
1528
|
-
const CODEX_NATIVE_PERSONALITY_NONE = "none";
|
|
1529
|
-
function resolveCodexBindingModelProviderFallback(params) {
|
|
1530
|
-
const provider = params.provider?.trim().toLowerCase();
|
|
1531
|
-
if (provider && provider !== "codex") return;
|
|
1532
|
-
const currentModel = params.currentModel?.trim();
|
|
1533
|
-
const bindingModel = params.bindingModel?.trim();
|
|
1534
|
-
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
1535
|
-
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
1536
|
-
}
|
|
1537
|
-
function resolveCodexAppServerThreadModelSelection(params) {
|
|
1538
|
-
const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
|
|
1539
|
-
const explicitModelProvider = resolveCodexAppServerModelProvider({
|
|
1540
|
-
provider: params.provider,
|
|
1541
|
-
authProfileId,
|
|
1542
|
-
authProfileStore: params.authProfileStore,
|
|
1543
|
-
agentDir: params.agentDir,
|
|
1544
|
-
config: params.config
|
|
1545
|
-
});
|
|
1546
|
-
const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
|
|
1547
|
-
provider: params.provider,
|
|
1548
|
-
currentModel: params.model,
|
|
1549
|
-
bindingModel: params.binding.model,
|
|
1550
|
-
bindingModelProvider: params.binding.modelProvider
|
|
1551
|
-
}) : void 0;
|
|
1552
|
-
return resolveCodexAppServerRequestModelSelection({
|
|
1553
|
-
model: params.model,
|
|
1554
|
-
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
1555
|
-
authProfileId,
|
|
1556
|
-
authProfileStore: params.authProfileStore,
|
|
1557
|
-
agentDir: params.agentDir,
|
|
1558
|
-
config: params.config
|
|
1559
|
-
});
|
|
1560
|
-
}
|
|
1561
|
-
function resolveCodexAppServerRequestModelSelection(params) {
|
|
1562
|
-
const model = params.model.trim();
|
|
1563
|
-
const modelProvider = params.modelProvider?.trim();
|
|
1564
|
-
if (modelProvider) return {
|
|
1565
|
-
model,
|
|
1566
|
-
modelProvider
|
|
1567
|
-
};
|
|
1568
|
-
const slashIndex = model.indexOf("/");
|
|
1569
|
-
if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
|
|
1570
|
-
const inferredModelProvider = resolveCodexAppServerModelProvider({
|
|
1571
|
-
provider: model.slice(0, slashIndex),
|
|
1572
|
-
authProfileId: params.authProfileId,
|
|
1573
|
-
authProfileStore: params.authProfileStore,
|
|
1574
|
-
agentDir: params.agentDir,
|
|
1575
|
-
config: params.config
|
|
1576
|
-
});
|
|
1577
|
-
return {
|
|
1578
|
-
model: model.slice(slashIndex + 1).trim(),
|
|
1579
|
-
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
function hasProviderQualifiedModelRef(model) {
|
|
1583
|
-
const trimmed = model?.trim();
|
|
1584
|
-
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
|
1585
|
-
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
1586
|
-
}
|
|
1587
|
-
function resolveCodexAppServerModelProvider(params) {
|
|
1588
|
-
const normalized = params.provider.trim();
|
|
1589
|
-
const normalizedLower = normalized.toLowerCase();
|
|
1590
|
-
if (!normalized || normalizedLower === "codex") return;
|
|
1591
|
-
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
1592
|
-
return normalizedLower === "openai" ? "openai" : normalized;
|
|
1593
1452
|
}
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1453
|
+
/** Recursively sanitizes all Codex history image shapes while preserving unknown structure. */
|
|
1454
|
+
function sanitizeCodexHistoryImagePayloads(value, label) {
|
|
1455
|
+
if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
|
|
1456
|
+
if (!isRecord(value)) return value;
|
|
1457
|
+
const imageRecord = sanitizeImageContentRecord(value, label);
|
|
1458
|
+
if (imageRecord) return imageRecord;
|
|
1459
|
+
const next = {};
|
|
1460
|
+
for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
|
|
1461
|
+
return next;
|
|
1600
1462
|
}
|
|
1601
1463
|
//#endregion
|
|
1602
|
-
//#region extensions/codex/src/app-server/
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
}).join("\n");
|
|
1608
|
-
return [
|
|
1609
|
-
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
1610
|
-
buildDeferredDynamicToolManifest(options.dynamicTools),
|
|
1611
|
-
buildSkillWorkshopInstruction(options.dynamicTools),
|
|
1612
|
-
"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`.",
|
|
1613
|
-
buildVisibleReplyInstruction(params, options.dynamicTools),
|
|
1614
|
-
nativeCommandGuidance,
|
|
1615
|
-
params.extraSystemPrompt
|
|
1616
|
-
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
1617
|
-
}
|
|
1618
|
-
function buildDeferredDynamicToolManifest(dynamicTools) {
|
|
1619
|
-
const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
|
|
1620
|
-
if (deferredToolNames.length === 0) return;
|
|
1621
|
-
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
1622
|
-
}
|
|
1623
|
-
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
1624
|
-
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
1625
|
-
return buildSkillWorkshopPromptSection().join("\n");
|
|
1464
|
+
//#region extensions/codex/src/app-server/profiler-flag.ts
|
|
1465
|
+
const PROFILER_FLAGS = ["profiler", "codex.profiler"];
|
|
1466
|
+
/** Checks the generic and Codex-specific profiler diagnostic flags. */
|
|
1467
|
+
function isCodexAppServerProfilerEnabled(config, env = process.env) {
|
|
1468
|
+
return PROFILER_FLAGS.some((flag) => isDiagnosticFlagEnabled(flag, config, env));
|
|
1626
1469
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1470
|
+
//#endregion
|
|
1471
|
+
//#region extensions/codex/src/app-server/thread-resume.ts
|
|
1472
|
+
/** Owns Codex thread/resume subscription safety. */
|
|
1473
|
+
/** Resumes one thread and retires the physical client when acceptance is indeterminate. */
|
|
1474
|
+
async function resumeCodexAppServerThread(params) {
|
|
1475
|
+
const threadId = params.request.threadId;
|
|
1476
|
+
let response;
|
|
1477
|
+
try {
|
|
1478
|
+
response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
|
|
1479
|
+
...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
|
|
1480
|
+
...params.signal ? { signal: params.signal } : {}
|
|
1481
|
+
}));
|
|
1482
|
+
assertCodexThreadResumeSubscription(threadId, response.thread.id);
|
|
1483
|
+
} catch (error) {
|
|
1484
|
+
if (error instanceof CodexAppServerRpcError) throw error;
|
|
1485
|
+
try {
|
|
1486
|
+
await params.abandonClient();
|
|
1487
|
+
} catch (abandonError) {
|
|
1488
|
+
throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
|
|
1489
|
+
}
|
|
1490
|
+
if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
|
|
1491
|
+
throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
|
|
1492
|
+
}
|
|
1493
|
+
return response;
|
|
1632
1494
|
}
|
|
1633
1495
|
//#endregion
|
|
1634
1496
|
//#region extensions/codex/src/app-server/web-search.ts
|
|
@@ -1697,589 +1559,32 @@ function resolveCodexWebSearchPlan(params) {
|
|
|
1697
1559
|
threadConfig: buildCodexNativeWebSearchThreadConfig(params.config)
|
|
1698
1560
|
};
|
|
1699
1561
|
}
|
|
1562
|
+
//#endregion
|
|
1563
|
+
//#region extensions/codex/src/app-server/thread-lifecycle.ts
|
|
1564
|
+
var CodexThreadStartRequestError = class extends Error {
|
|
1565
|
+
constructor(cause) {
|
|
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";
|
|
1700
1577
|
const CODEX_CODE_MODE_THREAD_CONFIG = {
|
|
1701
1578
|
"features.code_mode": true,
|
|
1702
1579
|
"features.code_mode_only": false,
|
|
1703
1580
|
"features.apply_patch_streaming_events": true
|
|
1704
1581
|
};
|
|
1705
|
-
const CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG = { "features.goals": false };
|
|
1706
1582
|
const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
|
|
1707
1583
|
"features.code_mode": false,
|
|
1708
1584
|
"features.code_mode_only": false
|
|
1709
1585
|
};
|
|
1710
1586
|
const CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG = { project_doc_max_bytes: 0 };
|
|
1711
1587
|
const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
|
|
1712
|
-
const CODEX_DELEGATION_DISABLED_THREAD_CONFIG = {
|
|
1713
|
-
"features.multi_agent": false,
|
|
1714
|
-
"features.multi_agent_v2": false
|
|
1715
|
-
};
|
|
1716
|
-
const CODEX_RING_ZERO_THREAD_CONFIG = {
|
|
1717
|
-
"features.apps": false,
|
|
1718
|
-
"features.current_time_reminder": false,
|
|
1719
|
-
"features.deferred_executor": false,
|
|
1720
|
-
"features.enable_fanout": false,
|
|
1721
|
-
"features.goals": false,
|
|
1722
|
-
"features.hooks": false,
|
|
1723
|
-
"features.image_generation": false,
|
|
1724
|
-
"features.memories": false,
|
|
1725
|
-
"features.multi_agent": false,
|
|
1726
|
-
"features.multi_agent_v2": false,
|
|
1727
|
-
"features.plugins": false,
|
|
1728
|
-
"features.standalone_web_search": false,
|
|
1729
|
-
"features.token_budget": false,
|
|
1730
|
-
"orchestrator.mcp.enabled": false,
|
|
1731
|
-
"orchestrator.skills.enabled": false,
|
|
1732
|
-
"tools.experimental_request_user_input.enabled": false,
|
|
1733
|
-
hooks: {
|
|
1734
|
-
PreToolUse: [],
|
|
1735
|
-
PermissionRequest: [],
|
|
1736
|
-
PostToolUse: [],
|
|
1737
|
-
PreCompact: [],
|
|
1738
|
-
PostCompact: [],
|
|
1739
|
-
SessionStart: [],
|
|
1740
|
-
UserPromptSubmit: [],
|
|
1741
|
-
SubagentStart: [],
|
|
1742
|
-
SubagentStop: [],
|
|
1743
|
-
Stop: []
|
|
1744
|
-
},
|
|
1745
|
-
project_doc_max_bytes: 0,
|
|
1746
|
-
notify: [],
|
|
1747
|
-
web_search: "disabled"
|
|
1748
|
-
};
|
|
1749
|
-
const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
|
|
1750
|
-
"apps",
|
|
1751
|
-
"code_mode",
|
|
1752
|
-
"code_mode_only",
|
|
1753
|
-
"current_time_reminder",
|
|
1754
|
-
"deferred_executor",
|
|
1755
|
-
"enable_fanout",
|
|
1756
|
-
"goals",
|
|
1757
|
-
"hooks",
|
|
1758
|
-
"image_generation",
|
|
1759
|
-
"memories",
|
|
1760
|
-
"multi_agent",
|
|
1761
|
-
"multi_agent_v2",
|
|
1762
|
-
"plugins",
|
|
1763
|
-
"standalone_web_search",
|
|
1764
|
-
"token_budget"
|
|
1765
|
-
]);
|
|
1766
|
-
const CODEX_RING_ZERO_OVERRIDABLE_LAYER_TYPES = /* @__PURE__ */ new Set([
|
|
1767
|
-
"mdm",
|
|
1768
|
-
"system",
|
|
1769
|
-
"enterpriseManaged",
|
|
1770
|
-
"user",
|
|
1771
|
-
"project",
|
|
1772
|
-
"sessionFlags"
|
|
1773
|
-
]);
|
|
1774
|
-
function buildThreadStartParams(params, options) {
|
|
1775
|
-
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
1776
|
-
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
1777
|
-
provider: params.provider,
|
|
1778
|
-
authProfileId: params.authProfileId,
|
|
1779
|
-
authProfileStore: params.authProfileStore,
|
|
1780
|
-
agentDir: params.agentDir,
|
|
1781
|
-
config: params.config
|
|
1782
|
-
});
|
|
1783
|
-
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
1784
|
-
model: options.model ?? params.modelId,
|
|
1785
|
-
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
1786
|
-
authProfileId: params.authProfileId,
|
|
1787
|
-
authProfileStore: params.authProfileStore,
|
|
1788
|
-
agentDir: params.agentDir,
|
|
1789
|
-
config: params.config
|
|
1790
|
-
});
|
|
1791
|
-
return {
|
|
1792
|
-
model: modelSelection.model,
|
|
1793
|
-
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
1794
|
-
cwd: options.cwd,
|
|
1795
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
1796
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
1797
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
1798
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1799
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
1800
|
-
serviceName: "OpenClaw",
|
|
1801
|
-
...ringZeroActive ? { baseInstructions: "" } : {},
|
|
1802
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
1803
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
1804
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1805
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
1806
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
1807
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
1808
|
-
appServer: options.appServer,
|
|
1809
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1810
|
-
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
1811
|
-
}),
|
|
1812
|
-
...resolveCodexThreadEnvironmentSelection(options),
|
|
1813
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
1814
|
-
dynamicTools: [...options.dynamicTools],
|
|
1815
|
-
experimentalRawEvents: true,
|
|
1816
|
-
...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
|
|
1817
|
-
};
|
|
1818
|
-
}
|
|
1819
|
-
function buildThreadResumeParams(params, options) {
|
|
1820
|
-
const modelSelection = options.preserveNativeModel ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
1821
|
-
model: options.model ?? params.modelId,
|
|
1822
|
-
modelProvider: options.modelProvider ?? resolveCodexAppServerModelProvider({
|
|
1823
|
-
provider: params.provider,
|
|
1824
|
-
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
1825
|
-
authProfileStore: params.authProfileStore,
|
|
1826
|
-
agentDir: params.agentDir,
|
|
1827
|
-
config: params.config
|
|
1828
|
-
}),
|
|
1829
|
-
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
1830
|
-
authProfileStore: params.authProfileStore,
|
|
1831
|
-
agentDir: params.agentDir,
|
|
1832
|
-
config: params.config
|
|
1833
|
-
});
|
|
1834
|
-
return {
|
|
1835
|
-
threadId: options.threadId,
|
|
1836
|
-
excludeTurns: true,
|
|
1837
|
-
initialTurnsPage: {
|
|
1838
|
-
limit: 1,
|
|
1839
|
-
sortDirection: "desc",
|
|
1840
|
-
itemsView: "notLoaded"
|
|
1841
|
-
},
|
|
1842
|
-
...modelSelection ? {
|
|
1843
|
-
model: modelSelection.model,
|
|
1844
|
-
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
|
|
1845
|
-
} : {},
|
|
1846
|
-
approvalPolicy: options.appServer.approvalPolicy,
|
|
1847
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
1848
|
-
...codexThreadSandboxOrPermissions(options.appServer),
|
|
1849
|
-
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1850
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
1851
|
-
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
1852
|
-
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
1853
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1854
|
-
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
1855
|
-
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools),
|
|
1856
|
-
webSearchAllowed: options.webSearchAllowed,
|
|
1857
|
-
appServer: options.appServer,
|
|
1858
|
-
hostSystemAgentActive: options.hostSystemAgentActive,
|
|
1859
|
-
ringZeroInheritedMcpServerNames: options.ringZeroInheritedMcpServerNames
|
|
1860
|
-
}),
|
|
1861
|
-
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
1865
|
-
const codeModeConfig = {
|
|
1866
|
-
...CODEX_CODE_MODE_THREAD_CONFIG,
|
|
1867
|
-
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
1868
|
-
};
|
|
1869
|
-
if (options.nativeCodeModeEnabled === false) {
|
|
1870
|
-
const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
|
|
1871
|
-
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
1872
|
-
return disabledConfig;
|
|
1873
|
-
}
|
|
1874
|
-
if (options.nativeCodeModeOnlyEnabled === true) return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, { "features.code_mode_only": true }) ?? {
|
|
1875
|
-
...codeModeConfig,
|
|
1876
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG,
|
|
1877
|
-
"features.code_mode_only": true
|
|
1878
|
-
}, options.directOnlyToolNamespaces);
|
|
1879
|
-
return ensureDirectOnlyToolNamespaces(mergeCodexThreadConfigs(codeModeConfig, config, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG) ?? {
|
|
1880
|
-
...codeModeConfig,
|
|
1881
|
-
...CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG
|
|
1882
|
-
}, options.directOnlyToolNamespaces);
|
|
1883
|
-
}
|
|
1884
|
-
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
1885
|
-
if (!requiredNamespaces?.length) return config;
|
|
1886
|
-
const configured = config["code_mode.direct_only_tool_namespaces"];
|
|
1887
|
-
const namespaces = Array.isArray(configured) ? configured.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
1888
|
-
return {
|
|
1889
|
-
...config,
|
|
1890
|
-
"code_mode.direct_only_tool_namespaces": [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
1891
|
-
};
|
|
1892
|
-
}
|
|
1893
|
-
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
1894
|
-
return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
|
|
1895
|
-
}
|
|
1896
|
-
function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
1897
|
-
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
1898
|
-
const configMcpServers = config?.mcp_servers;
|
|
1899
|
-
if (ringZeroActive && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex ring-zero received invalid thread mcp_servers config");
|
|
1900
|
-
const ringZeroMcpServerNames = [...options.ringZeroInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
1901
|
-
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
1902
|
-
config: params.config,
|
|
1903
|
-
disableTools: params.disableTools,
|
|
1904
|
-
nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
|
|
1905
|
-
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1906
|
-
webSearchAllowed: options.webSearchAllowed
|
|
1907
|
-
}).threadConfig;
|
|
1908
|
-
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
1909
|
-
const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, ringZeroMcpServerNames)) ?? baseConfig;
|
|
1910
|
-
if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
|
|
1911
|
-
return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
|
|
1912
|
-
...runtimeConfig,
|
|
1913
|
-
...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
|
|
1914
|
-
};
|
|
1915
|
-
}
|
|
1916
|
-
function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
|
|
1917
|
-
if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
|
|
1918
|
-
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
1919
|
-
return {
|
|
1920
|
-
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
1921
|
-
...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
|
-
async function readCodexInheritedMcpServerNames(client, cwd, signal) {
|
|
1925
|
-
const response = await client.request("config/read", {
|
|
1926
|
-
cwd,
|
|
1927
|
-
includeLayers: true
|
|
1928
|
-
}, { signal });
|
|
1929
|
-
if (!isJsonObject(response) || !isJsonObject(response.config)) throw new Error("Codex config/read returned an invalid effective config");
|
|
1930
|
-
if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
|
|
1931
|
-
for (const layer of response.layers) {
|
|
1932
|
-
if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
|
|
1933
|
-
if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") throw new Error(`Codex ring-zero cannot override config layer ${layer.name.type}`);
|
|
1934
|
-
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}`);
|
|
1935
|
-
}
|
|
1936
|
-
const configuredServers = response.config.mcp_servers;
|
|
1937
|
-
if (configuredServers === void 0) return [];
|
|
1938
|
-
if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
|
|
1939
|
-
return Object.keys(configuredServers).toSorted();
|
|
1940
|
-
}
|
|
1941
|
-
async function assertCodexRingZeroHasNoManagedHooks(client, signal) {
|
|
1942
|
-
const response = await client.request("configRequirements/read", void 0, { signal });
|
|
1943
|
-
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
|
|
1944
|
-
if (response.requirements === null) return;
|
|
1945
|
-
if (!isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
|
|
1946
|
-
for (const key of [
|
|
1947
|
-
"hooks",
|
|
1948
|
-
"managedHooks",
|
|
1949
|
-
"managed_hooks"
|
|
1950
|
-
]) {
|
|
1951
|
-
const hooks = response.requirements[key];
|
|
1952
|
-
if (hooks === void 0 || hooks === null) continue;
|
|
1953
|
-
if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
|
|
1954
|
-
if (hasNonEmptyJsonValue(hooks)) throw new Error("Codex ring-zero cannot override managed hooks");
|
|
1955
|
-
}
|
|
1956
|
-
for (const key of ["featureRequirements", "feature_requirements"]) {
|
|
1957
|
-
const requirements = response.requirements[key];
|
|
1958
|
-
if (requirements === void 0 || requirements === null) continue;
|
|
1959
|
-
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
1960
|
-
for (const [feature, enabled] of Object.entries(requirements)) {
|
|
1961
|
-
if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
1962
|
-
if (enabled && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(feature)) throw new Error(`Codex ring-zero cannot override required feature ${feature}`);
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
async function attestCodexRingZeroThreadHasNoMcpServers(client, threadId, signal) {
|
|
1967
|
-
const response = await client.request("mcpServerStatus/list", {
|
|
1968
|
-
threadId,
|
|
1969
|
-
limit: 1,
|
|
1970
|
-
detail: "toolsAndAuthOnly"
|
|
1971
|
-
}, { signal });
|
|
1972
|
-
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid ring-zero attestation");
|
|
1973
|
-
if (response.data.length > 0) {
|
|
1974
|
-
const first = response.data[0];
|
|
1975
|
-
const serverName = isJsonObject(first) && typeof first.name === "string" ? first.name : "unknown";
|
|
1976
|
-
throw new Error(`Codex ring-zero MCP attestation found server ${serverName}`);
|
|
1977
|
-
}
|
|
1978
|
-
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
1979
|
-
}
|
|
1980
|
-
function hasNonEmptyJsonValue(value) {
|
|
1981
|
-
if (value === null || value === false || value === "") return false;
|
|
1982
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
1983
|
-
if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
|
|
1984
|
-
return true;
|
|
1985
|
-
}
|
|
1986
|
-
function resolveCodexThreadApprovalsReviewer(appServer, config) {
|
|
1987
|
-
return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
|
|
1988
|
-
}
|
|
1989
|
-
function codexThreadSandboxOrPermissions(appServer) {
|
|
1990
|
-
if (appServer.networkProxy) return {};
|
|
1991
|
-
return { sandbox: appServer.sandbox };
|
|
1992
|
-
}
|
|
1993
|
-
function resolveCodexThreadEnvironmentSelection(options) {
|
|
1994
|
-
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
1995
|
-
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
1996
|
-
return {};
|
|
1997
|
-
}
|
|
1998
|
-
//#endregion
|
|
1999
|
-
//#region extensions/codex/src/app-server/thread-resume.ts
|
|
2000
|
-
/** Owns Codex thread/resume subscription safety. */
|
|
2001
|
-
/** Resumes one thread and retires the physical client when acceptance is indeterminate. */
|
|
2002
|
-
async function resumeCodexAppServerThread(params) {
|
|
2003
|
-
const threadId = params.request.threadId;
|
|
2004
|
-
let response;
|
|
2005
|
-
try {
|
|
2006
|
-
response = assertCodexThreadResumeResponse(await params.client.request("thread/resume", params.request, {
|
|
2007
|
-
...params.timeoutMs !== void 0 ? { timeoutMs: params.timeoutMs } : {},
|
|
2008
|
-
...params.signal ? { signal: params.signal } : {}
|
|
2009
|
-
}));
|
|
2010
|
-
assertCodexThreadResumeSubscription(threadId, response.thread.id);
|
|
2011
|
-
} catch (error) {
|
|
2012
|
-
if (isCodexAppServerStartSelectionChangedError(error) || isCodexAppServerPrewriteRequestCancellationError(error)) throw error;
|
|
2013
|
-
if (error instanceof CodexAppServerRpcError) throw error;
|
|
2014
|
-
try {
|
|
2015
|
-
await params.abandonClient();
|
|
2016
|
-
} catch (abandonError) {
|
|
2017
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Codex thread/resume client could not be retired for ${threadId}`, { cause: abandonError });
|
|
2018
|
-
}
|
|
2019
|
-
if (error instanceof CodexAppServerUnsafeSubscriptionError) throw error;
|
|
2020
|
-
throw new CodexAppServerUnsafeSubscriptionError(error instanceof Error ? error.message : `Codex thread/resume outcome is indeterminate for ${threadId}`, { cause: error });
|
|
2021
|
-
}
|
|
2022
|
-
return response;
|
|
2023
|
-
}
|
|
2024
|
-
//#endregion
|
|
2025
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-io.ts
|
|
2026
|
-
async function resumeExistingCodexThread(params, context) {
|
|
2027
|
-
const { binding: resumeBinding, bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, clearCurrentBinding } = context;
|
|
2028
|
-
let resumeReservation;
|
|
2029
|
-
try {
|
|
2030
|
-
const authProfileId = resumeBinding.connectionScope === "supervision" ? void 0 : params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
2031
|
-
const finalConfigPatch = params.buildFinalConfigPatch?.({
|
|
2032
|
-
action: "resume",
|
|
2033
|
-
binding: resumeBinding
|
|
2034
|
-
}) ?? {
|
|
2035
|
-
configPatch: params.finalConfigPatch,
|
|
2036
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2037
|
-
};
|
|
2038
|
-
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
|
|
2039
|
-
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
|
|
2040
|
-
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
2041
|
-
threadId: resumeBinding.threadId,
|
|
2042
|
-
authProfileId,
|
|
2043
|
-
model: startModelSelection.model,
|
|
2044
|
-
modelProvider: startModelProvider,
|
|
2045
|
-
preserveNativeModel: resumeBinding.preserveNativeModel === true,
|
|
2046
|
-
appServer: params.appServer,
|
|
2047
|
-
dynamicTools: params.dynamicTools,
|
|
2048
|
-
developerInstructions: params.developerInstructions,
|
|
2049
|
-
config: resumeConfig,
|
|
2050
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2051
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2052
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2053
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2054
|
-
hostSystemAgentActive,
|
|
2055
|
-
ringZeroInheritedMcpServerNames
|
|
2056
|
-
}));
|
|
2057
|
-
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
2058
|
-
throwIfAborted();
|
|
2059
|
-
if (resumeBinding.preserveNativeModel === true) {
|
|
2060
|
-
const current = await lifecycleTiming.measure("thread-read-adoption-status", () => params.client.request("thread/read", {
|
|
2061
|
-
threadId: resumeBinding.threadId,
|
|
2062
|
-
includeTurns: false
|
|
2063
|
-
}, { signal: params.signal }));
|
|
2064
|
-
throwIfAborted();
|
|
2065
|
-
if (current.thread.status?.type === "active") throw new CodexAdoptedThreadActiveError();
|
|
2066
|
-
}
|
|
2067
|
-
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
2068
|
-
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
2069
|
-
client: params.client,
|
|
2070
|
-
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
2071
|
-
request: resumeParams,
|
|
2072
|
-
signal: params.signal
|
|
2073
|
-
}));
|
|
2074
|
-
if (ringZeroActive) try {
|
|
2075
|
-
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
2076
|
-
} catch (error) {
|
|
2077
|
-
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2078
|
-
throw new CodexRingZeroAttestationError(error);
|
|
2079
|
-
}
|
|
2080
|
-
throwIfAborted();
|
|
2081
|
-
const boundAuthProfileId = authProfileId;
|
|
2082
|
-
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
2083
|
-
const resumePatch = {
|
|
2084
|
-
cwd: params.cwd,
|
|
2085
|
-
authProfileId: boundAuthProfileId,
|
|
2086
|
-
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
2087
|
-
preserveNativeModel: resumeBinding.preserveNativeModel === true ? true : void 0,
|
|
2088
|
-
modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
2089
|
-
dynamicToolsFingerprint,
|
|
2090
|
-
dynamicToolsContainDeferred,
|
|
2091
|
-
webSearchThreadConfigFingerprint,
|
|
2092
|
-
userMcpServersFingerprint,
|
|
2093
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2094
|
-
ringZeroConfigFingerprint,
|
|
2095
|
-
ringZeroClientInstanceId,
|
|
2096
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2097
|
-
networkProxyConfigFingerprint,
|
|
2098
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
2099
|
-
appServerRuntimeFingerprint: resumeBinding.connectionScope === "supervision" ? buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir) : params.appServerRuntimeFingerprint,
|
|
2100
|
-
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
|
2101
|
-
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
|
2102
|
-
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
|
2103
|
-
contextEngine: contextEngineBinding,
|
|
2104
|
-
environmentSelectionFingerprint
|
|
2105
|
-
};
|
|
2106
|
-
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2107
|
-
kind: "patch",
|
|
2108
|
-
threadId: resumeBinding.threadId,
|
|
2109
|
-
patch: resumePatch
|
|
2110
|
-
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
2111
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2112
|
-
sessionId: params.params.sessionId,
|
|
2113
|
-
sessionKey: params.params.sessionKey,
|
|
2114
|
-
threadId: response.thread.id,
|
|
2115
|
-
engineId: contextEngineBinding.engineId,
|
|
2116
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2117
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2118
|
-
action: "resumed"
|
|
2119
|
-
});
|
|
2120
|
-
lifecycleTiming.mark("thread-ready");
|
|
2121
|
-
lifecycleTiming.logSummary({
|
|
2122
|
-
runId: params.params.runId,
|
|
2123
|
-
sessionId: params.params.sessionId,
|
|
2124
|
-
sessionKey: params.params.sessionKey,
|
|
2125
|
-
threadId: response.thread.id,
|
|
2126
|
-
action: "resumed"
|
|
2127
|
-
});
|
|
2128
|
-
const activeTurnIds = readActiveCodexTurnIdsFromResume(response);
|
|
2129
|
-
return {
|
|
2130
|
-
...resumeBinding,
|
|
2131
|
-
threadId: response.thread.id,
|
|
2132
|
-
...resumePatch,
|
|
2133
|
-
lifecycle: {
|
|
2134
|
-
action: "resumed",
|
|
2135
|
-
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
2136
|
-
}
|
|
2137
|
-
};
|
|
2138
|
-
} catch (error) {
|
|
2139
|
-
resumeReservation?.release();
|
|
2140
|
-
if (isCodexAppServerStartSelectionChangedError(error)) throw error;
|
|
2141
|
-
if (error instanceof CodexRingZeroAttestationError) {
|
|
2142
|
-
await clearCurrentBinding("retiring a failed ring-zero thread attestation");
|
|
2143
|
-
throw error;
|
|
2144
|
-
}
|
|
2145
|
-
if (error instanceof CodexAdoptedThreadActiveError) throw error;
|
|
2146
|
-
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
2147
|
-
const resumeRejected = error instanceof CodexAppServerRpcError;
|
|
2148
|
-
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
2149
|
-
threadId: resumeBinding.threadId,
|
|
2150
|
-
timeoutMs: 5e3
|
|
2151
|
-
}) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
2152
|
-
if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
|
|
2153
|
-
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
2154
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
async function startFreshCodexThread(params, context) {
|
|
2158
|
-
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
2159
|
-
const { bindingIdentity, startModelSelection, startModelProvider, userMcpServersConfigPatch, dynamicToolsFingerprint, dynamicToolsContainDeferred, webSearchThreadConfigFingerprint, userMcpServersFingerprint, ringZeroConfigFingerprint, ringZeroClientInstanceId, networkProxyConfigFingerprint, contextEngineBinding, environmentSelectionFingerprint, hostSystemAgentActive, ringZeroActive, ringZeroInheritedMcpServerNames, lifecycleTiming, normalizeBindingModelProvider, throwIfAborted, prebuiltPluginThreadConfig, preserveExistingBinding, rotatedContextEngineBinding } = context;
|
|
2160
|
-
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2161
|
-
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2162
|
-
configPatch: params.finalConfigPatch,
|
|
2163
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2164
|
-
};
|
|
2165
|
-
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
2166
|
-
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
2167
|
-
cwd: params.cwd,
|
|
2168
|
-
dynamicTools: params.dynamicTools,
|
|
2169
|
-
appServer: params.appServer,
|
|
2170
|
-
developerInstructions: params.developerInstructions,
|
|
2171
|
-
config,
|
|
2172
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2173
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2174
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2175
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2176
|
-
environmentSelection: params.environmentSelection,
|
|
2177
|
-
model: startModelSelection.model,
|
|
2178
|
-
modelProvider: startModelProvider,
|
|
2179
|
-
hostSystemAgentActive,
|
|
2180
|
-
ringZeroInheritedMcpServerNames
|
|
2181
|
-
}));
|
|
2182
|
-
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2183
|
-
const response = assertCodexThreadStartResponse(await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2184
|
-
try {
|
|
2185
|
-
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2186
|
-
} catch (error) {
|
|
2187
|
-
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2188
|
-
throw error;
|
|
2189
|
-
}
|
|
2190
|
-
}));
|
|
2191
|
-
if (ringZeroActive) try {
|
|
2192
|
-
await lifecycleTiming.measure("ring-zero-mcp-attestation", () => attestCodexRingZeroThreadHasNoMcpServers(params.client, response.thread.id, params.signal));
|
|
2193
|
-
} catch (error) {
|
|
2194
|
-
await (params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)))();
|
|
2195
|
-
throw error;
|
|
2196
|
-
}
|
|
2197
|
-
throwIfAborted();
|
|
2198
|
-
const modelProvider = resolveCodexAppServerModelProvider({
|
|
2199
|
-
provider: params.params.provider,
|
|
2200
|
-
authProfileId: params.params.authProfileId,
|
|
2201
|
-
authProfileStore: params.params.authProfileStore,
|
|
2202
|
-
agentDir: params.params.agentDir,
|
|
2203
|
-
config: params.params.config
|
|
2204
|
-
});
|
|
2205
|
-
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2206
|
-
if (!preserveExistingBinding) {
|
|
2207
|
-
if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2208
|
-
kind: "set",
|
|
2209
|
-
if: { kind: "absent" },
|
|
2210
|
-
binding: {
|
|
2211
|
-
threadId: response.thread.id,
|
|
2212
|
-
...clientId ? { clientId } : {},
|
|
2213
|
-
cwd: params.cwd,
|
|
2214
|
-
authProfileId: params.params.authProfileId,
|
|
2215
|
-
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2216
|
-
modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
|
|
2217
|
-
dynamicToolsFingerprint,
|
|
2218
|
-
dynamicToolsContainDeferred,
|
|
2219
|
-
webSearchThreadConfigFingerprint,
|
|
2220
|
-
userMcpServersFingerprint,
|
|
2221
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2222
|
-
ringZeroConfigFingerprint,
|
|
2223
|
-
ringZeroClientInstanceId,
|
|
2224
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2225
|
-
networkProxyConfigFingerprint,
|
|
2226
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2227
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2228
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2229
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2230
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2231
|
-
contextEngine: contextEngineBinding,
|
|
2232
|
-
environmentSelectionFingerprint
|
|
2233
|
-
}
|
|
2234
|
-
}))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
|
|
2235
|
-
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2236
|
-
sessionId: params.params.sessionId,
|
|
2237
|
-
sessionKey: params.params.sessionKey,
|
|
2238
|
-
threadId: response.thread.id,
|
|
2239
|
-
engineId: contextEngineBinding.engineId,
|
|
2240
|
-
epoch: contextEngineBinding.projection?.epoch,
|
|
2241
|
-
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2242
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2243
|
-
});
|
|
2244
|
-
}
|
|
2245
|
-
lifecycleTiming.mark("thread-ready");
|
|
2246
|
-
lifecycleTiming.logSummary({
|
|
2247
|
-
runId: params.params.runId,
|
|
2248
|
-
sessionId: params.params.sessionId,
|
|
2249
|
-
sessionKey: params.params.sessionKey,
|
|
2250
|
-
threadId: response.thread.id,
|
|
2251
|
-
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2252
|
-
});
|
|
2253
|
-
return {
|
|
2254
|
-
threadId: response.thread.id,
|
|
2255
|
-
...clientId ? { clientId } : {},
|
|
2256
|
-
cwd: params.cwd,
|
|
2257
|
-
authProfileId: params.params.authProfileId,
|
|
2258
|
-
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2259
|
-
modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
|
|
2260
|
-
dynamicToolsFingerprint,
|
|
2261
|
-
dynamicToolsContainDeferred,
|
|
2262
|
-
userMcpServersFingerprint,
|
|
2263
|
-
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2264
|
-
ringZeroConfigFingerprint,
|
|
2265
|
-
ringZeroClientInstanceId,
|
|
2266
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2267
|
-
networkProxyConfigFingerprint,
|
|
2268
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2269
|
-
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2270
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2271
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2272
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2273
|
-
contextEngine: contextEngineBinding,
|
|
2274
|
-
environmentSelectionFingerprint,
|
|
2275
|
-
lifecycle: {
|
|
2276
|
-
action: "started",
|
|
2277
|
-
...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
|
|
2278
|
-
}
|
|
2279
|
-
};
|
|
2280
|
-
}
|
|
2281
|
-
//#endregion
|
|
2282
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-timing.ts
|
|
2283
1588
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_TOTAL_MS = 1e3;
|
|
2284
1589
|
const CODEX_THREAD_LIFECYCLE_TIMING_WARN_STAGE_MS = 500;
|
|
2285
1590
|
function shouldWarnCodexThreadLifecycleTimingSummary(summary, options = {}) {
|
|
@@ -2367,378 +1672,34 @@ function createCodexThreadLifecycleTimingTracker(options = {}) {
|
|
|
2367
1672
|
}
|
|
2368
1673
|
};
|
|
2369
1674
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
pending = await recoverPendingSupervisionArtifacts(params, pending);
|
|
2377
|
-
params.throwIfAborted();
|
|
2378
|
-
const sourceResponse = await params.lifecycleTiming.measure("supervision-source-read", () => params.client.request("thread/read", {
|
|
2379
|
-
threadId: pending.sourceThreadId,
|
|
2380
|
-
includeTurns: true
|
|
2381
|
-
}, { signal: params.signal }));
|
|
2382
|
-
params.throwIfAborted();
|
|
2383
|
-
const sourceThread = sourceResponse.thread;
|
|
2384
|
-
if (sourceThread.id !== pending.sourceThreadId) throw new Error(`Codex supervision source read returned ${sourceThread.id} for ${pending.sourceThreadId}`);
|
|
2385
|
-
assertPendingSupervisionSnapshotUnchanged(sourceThread, pending);
|
|
2386
|
-
const history = projectBoundedCodexThreadHistory({
|
|
2387
|
-
thread: sourceThread,
|
|
2388
|
-
throughTurnId: pending.lastTurnId ?? null,
|
|
2389
|
-
importedAt: Date.now(),
|
|
2390
|
-
modelProvider: sourceThread.modelProvider
|
|
1675
|
+
async function startOrResumeThread(params) {
|
|
1676
|
+
const bindingIdentity = sessionBindingIdentity({
|
|
1677
|
+
sessionId: params.params.sessionId,
|
|
1678
|
+
sessionKey: params.params.sessionKey,
|
|
1679
|
+
agentId: params.agentId ?? params.params.agentId,
|
|
1680
|
+
config: params.params.config
|
|
2391
1681
|
});
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
const rawProbeResponse = await params.lifecycleTiming.measure("supervision-model-probe-fork", async () => {
|
|
2397
|
-
try {
|
|
2398
|
-
return await params.client.request("thread/fork", probeParams, { signal: params.signal });
|
|
2399
|
-
} catch (error) {
|
|
2400
|
-
if (!(error instanceof CodexAppServerRpcError)) throw new CodexAppServerUnsafeSubscriptionError("Codex model probe fork may have materialized without a response", { cause: error });
|
|
2401
|
-
throw error;
|
|
2402
|
-
}
|
|
2403
|
-
});
|
|
2404
|
-
const probeThreadId = requireDistinctSupervisionThreadId({
|
|
2405
|
-
threadId: readSupervisionResponseThreadId(rawProbeResponse),
|
|
2406
|
-
sourceThreadId: pending.sourceThreadId,
|
|
2407
|
-
role: "model probe"
|
|
2408
|
-
});
|
|
2409
|
-
pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId]);
|
|
2410
|
-
params.throwIfAborted();
|
|
2411
|
-
const probeResponse = assertCodexThreadForkResponse(rawProbeResponse);
|
|
2412
|
-
const nativeModel = requireNonBlankSupervisionValue(probeResponse.model, "native model");
|
|
2413
|
-
const nativeModelProvider = requireNativeSupervisionModelProvider({
|
|
2414
|
-
responseModelProvider: probeResponse.modelProvider,
|
|
2415
|
-
responseThreadModelProvider: probeResponse.thread.modelProvider
|
|
1682
|
+
return await params.bindingStore.withLease(bindingIdentity, async () => {
|
|
1683
|
+
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
1684
|
+
...params.timing,
|
|
1685
|
+
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
2416
1686
|
});
|
|
2417
|
-
const
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
developerInstructions: params.developerInstructions,
|
|
2425
|
-
config: params.config,
|
|
2426
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2427
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2428
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2429
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2430
|
-
environmentSelection: params.environmentSelection,
|
|
2431
|
-
model: nativeModel,
|
|
2432
|
-
modelProvider: nativeModelProvider
|
|
2433
|
-
});
|
|
2434
|
-
assertExactSupervisionModelSelection(startParams, {
|
|
2435
|
-
model: nativeModel,
|
|
2436
|
-
modelProvider: nativeModelProvider,
|
|
2437
|
-
operation: "thread/start request"
|
|
2438
|
-
});
|
|
2439
|
-
const rawStartResponse = await params.lifecycleTiming.measure("supervision-thread-start", async () => {
|
|
2440
|
-
try {
|
|
2441
|
-
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2442
|
-
} catch (error) {
|
|
2443
|
-
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2444
|
-
throw new CodexAppServerUnsafeSubscriptionError("Canonical Codex branch may have started without a response", { cause: error });
|
|
2445
|
-
}
|
|
2446
|
-
});
|
|
2447
|
-
const finalThreadId = requireDistinctSupervisionThreadId({
|
|
2448
|
-
threadId: readSupervisionResponseThreadId(rawStartResponse),
|
|
2449
|
-
sourceThreadId: pending.sourceThreadId,
|
|
2450
|
-
otherThreadId: probeThreadId,
|
|
2451
|
-
role: "canonical branch"
|
|
2452
|
-
});
|
|
2453
|
-
pending = await trackPendingSupervisionArtifacts(params, pending, [probeThreadId, finalThreadId]);
|
|
2454
|
-
params.throwIfAborted();
|
|
2455
|
-
assertExactSupervisionModelSelection(assertCodexThreadStartResponse(rawStartResponse), {
|
|
2456
|
-
model: nativeModel,
|
|
2457
|
-
modelProvider: nativeModelProvider,
|
|
2458
|
-
operation: "thread/start response"
|
|
2459
|
-
});
|
|
2460
|
-
if (history.responseItems.length > 0) {
|
|
2461
|
-
await params.lifecycleTiming.measure("supervision-history-inject", () => params.client.request("thread/inject_items", {
|
|
2462
|
-
threadId: finalThreadId,
|
|
2463
|
-
items: history.responseItems
|
|
2464
|
-
}, { signal: params.signal }));
|
|
2465
|
-
params.throwIfAborted();
|
|
2466
|
-
}
|
|
2467
|
-
if (!await archiveSupervisionArtifact(params.client, probeThreadId)) throw new Error(`Failed to archive temporary Codex model probe: ${probeThreadId}`);
|
|
2468
|
-
pending = await trackPendingSupervisionArtifacts(params, pending, [finalThreadId]);
|
|
2469
|
-
const historyCoveredThrough = (/* @__PURE__ */ new Date()).toISOString();
|
|
2470
|
-
const bindingModelProvider = params.normalizeBindingModelProvider(params.attempt.authProfileId, nativeModelProvider);
|
|
2471
|
-
let committed = false;
|
|
2472
|
-
try {
|
|
2473
|
-
committed = await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2474
|
-
kind: "commit-pending-supervision-branch",
|
|
2475
|
-
expected: pending,
|
|
2476
|
-
threadId: finalThreadId,
|
|
2477
|
-
patch: {
|
|
2478
|
-
...params.bindingPatch,
|
|
2479
|
-
model: nativeModel,
|
|
2480
|
-
modelProvider: bindingModelProvider,
|
|
2481
|
-
historyCoveredThrough
|
|
2482
|
-
}
|
|
2483
|
-
});
|
|
2484
|
-
} catch (error) {
|
|
2485
|
-
let current;
|
|
2486
|
-
try {
|
|
2487
|
-
current = await params.bindingStore.read(params.bindingIdentity);
|
|
2488
|
-
} catch (readError) {
|
|
2489
|
-
provisionalCleanupSafe = false;
|
|
2490
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding could not be verified: ${finalThreadId}`, { cause: new AggregateError([error, readError]) });
|
|
2491
|
-
}
|
|
2492
|
-
if (matchesMaterializedSupervisionBranch(current, {
|
|
2493
|
-
sourceThreadId: pending.sourceThreadId,
|
|
2494
|
-
connectionFingerprint,
|
|
2495
|
-
threadId: finalThreadId,
|
|
2496
|
-
model: nativeModel,
|
|
2497
|
-
modelProvider: bindingModelProvider,
|
|
2498
|
-
historyCoveredThrough
|
|
2499
|
-
})) committed = true;
|
|
2500
|
-
else {
|
|
2501
|
-
if (!matchesPendingSupervisionState(current, pending)) {
|
|
2502
|
-
provisionalCleanupSafe = false;
|
|
2503
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Canonical Codex branch binding changed while commit was uncertain: ${finalThreadId}`, { cause: error });
|
|
2504
|
-
}
|
|
2505
|
-
throw error;
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
if (!committed) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "committing a supervised Codex branch");
|
|
2509
|
-
bindingCommitted = true;
|
|
2510
|
-
params.lifecycleTiming.mark("thread-ready");
|
|
2511
|
-
params.lifecycleTiming.logSummary({
|
|
2512
|
-
runId: params.attempt.runId,
|
|
2513
|
-
sessionId: params.attempt.sessionId,
|
|
2514
|
-
sessionKey: params.attempt.sessionKey,
|
|
2515
|
-
threadId: finalThreadId,
|
|
2516
|
-
action: "forked"
|
|
2517
|
-
});
|
|
2518
|
-
return {
|
|
2519
|
-
...params.binding,
|
|
2520
|
-
...params.bindingPatch,
|
|
2521
|
-
threadId: finalThreadId,
|
|
2522
|
-
pendingSupervisionBranch: void 0,
|
|
2523
|
-
model: nativeModel,
|
|
2524
|
-
modelProvider: bindingModelProvider,
|
|
2525
|
-
historyCoveredThrough,
|
|
2526
|
-
lifecycle: { action: "forked" }
|
|
2527
|
-
};
|
|
2528
|
-
} catch (error) {
|
|
2529
|
-
if (bindingCommitted) throw error;
|
|
2530
|
-
if (error instanceof CodexThreadBindingConflictAfterCleanupError) throw error;
|
|
2531
|
-
if (!provisionalCleanupSafe) {
|
|
2532
|
-
await params.abandonClient();
|
|
2533
|
-
throw error;
|
|
2534
|
-
}
|
|
2535
|
-
const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
|
|
2536
|
-
let cleanupStateError;
|
|
2537
|
-
if (cleanup.remaining.length !== (pending.cleanupThreadIds?.length ?? 0)) {
|
|
2538
|
-
const nextPending = withPendingSupervisionCleanup(pending, cleanup.remaining);
|
|
2539
|
-
try {
|
|
2540
|
-
if (await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2541
|
-
kind: "patch-pending-supervision-branch",
|
|
2542
|
-
expected: pending,
|
|
2543
|
-
pending: nextPending
|
|
2544
|
-
})) pending = nextPending;
|
|
2545
|
-
} catch (stateError) {
|
|
2546
|
-
cleanupStateError = stateError;
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
const unsafeCleanup = cleanup.remaining.length > 0 || isCodexAppServerUnsafeSubscriptionError(error);
|
|
2550
|
-
if (unsafeCleanup) await params.abandonClient();
|
|
2551
|
-
if (cleanupStateError) {
|
|
2552
|
-
const cause = new AggregateError([error, cleanupStateError]);
|
|
2553
|
-
if (unsafeCleanup) throw new CodexAppServerUnsafeSubscriptionError("Codex supervised branch cleanup state could not be recorded", { cause });
|
|
2554
|
-
throw new AggregateError([error, cleanupStateError], "Codex supervised branch cleanup state could not be recorded", { cause: error });
|
|
2555
|
-
}
|
|
2556
|
-
if (cleanup.remaining.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch cleanup remains pending: ${cleanup.remaining.join(", ")}`, { cause: error });
|
|
2557
|
-
throw error;
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
function buildPendingSupervisionProbeForkParams(params, pending) {
|
|
2561
|
-
const runtimeConfig = buildCodexRuntimeThreadConfigForRun(params.attempt, params.config, {
|
|
2562
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2563
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2564
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2565
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2566
|
-
appServer: params.appServer
|
|
2567
|
-
});
|
|
2568
|
-
return {
|
|
2569
|
-
threadId: pending.sourceThreadId,
|
|
2570
|
-
...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
|
|
2571
|
-
cwd: params.cwd,
|
|
2572
|
-
approvalPolicy: params.appServer.approvalPolicy,
|
|
2573
|
-
approvalsReviewer: resolveCodexThreadApprovalsReviewer(params.appServer, runtimeConfig),
|
|
2574
|
-
...codexThreadSandboxOrPermissions(params.appServer),
|
|
2575
|
-
...params.appServer.serviceTier !== void 0 ? { serviceTier: params.appServer.serviceTier } : {},
|
|
2576
|
-
config: runtimeConfig,
|
|
2577
|
-
developerInstructions: params.developerInstructions ?? buildDeveloperInstructions(params.attempt, { dynamicTools: params.dynamicTools }),
|
|
2578
|
-
ephemeral: isIncognitoSessionKey(params.attempt.sessionKey),
|
|
2579
|
-
threadSource: "appServer",
|
|
2580
|
-
excludeTurns: true
|
|
2581
|
-
};
|
|
2582
|
-
}
|
|
2583
|
-
function assertPendingSupervisionSnapshotUnchanged(thread, pending) {
|
|
2584
|
-
if (pending.lastTurnId) return;
|
|
2585
|
-
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");
|
|
2586
|
-
}
|
|
2587
|
-
function requireNonBlankSupervisionValue(value, label) {
|
|
2588
|
-
if (typeof value !== "string" || !value.trim()) throw new Error(`Codex supervision ${label} is missing`);
|
|
2589
|
-
return value.trim();
|
|
2590
|
-
}
|
|
2591
|
-
function requireNativeSupervisionModelProvider(params) {
|
|
2592
|
-
const responseProvider = requireNonBlankSupervisionValue(params.responseModelProvider, "native model provider");
|
|
2593
|
-
const threadProvider = params.responseThreadModelProvider?.trim();
|
|
2594
|
-
if (threadProvider && threadProvider !== responseProvider) throw new Error(`Codex supervision model provider mismatch: ${responseProvider} != ${threadProvider}`);
|
|
2595
|
-
return responseProvider;
|
|
2596
|
-
}
|
|
2597
|
-
function assertExactSupervisionModelSelection(value, expected) {
|
|
2598
|
-
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"}`);
|
|
2599
|
-
}
|
|
2600
|
-
function matchesPendingSupervisionState(binding, expected) {
|
|
2601
|
-
const pending = binding?.pendingSupervisionBranch;
|
|
2602
|
-
const cleanupThreadIds = pending?.cleanupThreadIds ?? [];
|
|
2603
|
-
const expectedCleanupThreadIds = expected.cleanupThreadIds ?? [];
|
|
2604
|
-
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]);
|
|
2605
|
-
}
|
|
2606
|
-
function matchesMaterializedSupervisionBranch(binding, expected) {
|
|
2607
|
-
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;
|
|
2608
|
-
}
|
|
2609
|
-
function requireDistinctSupervisionThreadId(params) {
|
|
2610
|
-
let threadId;
|
|
2611
|
-
try {
|
|
2612
|
-
threadId = requireNonBlankSupervisionValue(params.threadId, `${params.role} thread id`);
|
|
2613
|
-
} catch (error) {
|
|
2614
|
-
throw new CodexAppServerUnsafeSubscriptionError(`Codex supervision ${params.role} may have materialized without a safe thread id`, { cause: error });
|
|
2615
|
-
}
|
|
2616
|
-
if (threadId === params.sourceThreadId || threadId === params.otherThreadId) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervision ${params.role} reused an existing thread: ${threadId}`);
|
|
2617
|
-
return threadId;
|
|
2618
|
-
}
|
|
2619
|
-
function readSupervisionResponseThreadId(value) {
|
|
2620
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
2621
|
-
const thread = value.thread;
|
|
2622
|
-
if (!thread || typeof thread !== "object" || Array.isArray(thread)) return;
|
|
2623
|
-
return thread.id;
|
|
2624
|
-
}
|
|
2625
|
-
async function recoverPendingSupervisionArtifacts(params, pending) {
|
|
2626
|
-
if (!pending.cleanupThreadIds?.length) return pending;
|
|
2627
|
-
const cleanup = await cleanPendingSupervisionArtifacts(params.client, pending);
|
|
2628
|
-
const next = withPendingSupervisionCleanup(pending, cleanup.remaining);
|
|
2629
|
-
if (cleanup.remaining.length > 0) {
|
|
2630
|
-
if (cleanup.remaining.length !== pending.cleanupThreadIds.length) {
|
|
2631
|
-
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2632
|
-
kind: "patch-pending-supervision-branch",
|
|
2633
|
-
expected: pending,
|
|
2634
|
-
pending: next
|
|
2635
|
-
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recording supervised Codex cleanup recovery");
|
|
2636
|
-
}
|
|
2637
|
-
throw new Error(`Codex supervised branch cleanup must finish before retry: ${cleanup.remaining.join(", ")}`);
|
|
2638
|
-
}
|
|
2639
|
-
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2640
|
-
kind: "patch-pending-supervision-branch",
|
|
2641
|
-
expected: pending,
|
|
2642
|
-
pending: next
|
|
2643
|
-
})) throw new CodexThreadBindingConflictError(pending.sourceThreadId, "recovering a supervised Codex branch");
|
|
2644
|
-
return next;
|
|
2645
|
-
}
|
|
2646
|
-
async function trackPendingSupervisionArtifacts(params, pending, cleanupThreadIds) {
|
|
2647
|
-
const next = withPendingSupervisionCleanup(pending, cleanupThreadIds);
|
|
2648
|
-
if (!await params.bindingStore.mutate(params.bindingIdentity, {
|
|
2649
|
-
kind: "patch-pending-supervision-branch",
|
|
2650
|
-
expected: pending,
|
|
2651
|
-
pending: next
|
|
2652
|
-
})) {
|
|
2653
|
-
const cleanupFailed = [];
|
|
2654
|
-
for (const threadId of cleanupThreadIds) if (!await archiveSupervisionArtifact(params.client, threadId)) cleanupFailed.push(threadId);
|
|
2655
|
-
if (cleanupFailed.length > 0) throw new CodexAppServerUnsafeSubscriptionError(`Codex supervised branch CAS cleanup failed: ${cleanupFailed.join(", ")}`);
|
|
2656
|
-
throw new CodexThreadBindingConflictAfterCleanupError(pending.sourceThreadId, "tracking supervised Codex branch cleanup");
|
|
2657
|
-
}
|
|
2658
|
-
return next;
|
|
2659
|
-
}
|
|
2660
|
-
function withPendingSupervisionCleanup(pending, cleanupThreadIds) {
|
|
2661
|
-
return {
|
|
2662
|
-
sourceThreadId: pending.sourceThreadId,
|
|
2663
|
-
...pending.connectionFingerprint ? { connectionFingerprint: pending.connectionFingerprint } : {},
|
|
2664
|
-
...pending.lastTurnId ? { lastTurnId: pending.lastTurnId } : {},
|
|
2665
|
-
...cleanupThreadIds.length > 0 ? { cleanupThreadIds } : {}
|
|
2666
|
-
};
|
|
2667
|
-
}
|
|
2668
|
-
async function cleanPendingSupervisionArtifacts(client, pending) {
|
|
2669
|
-
const remaining = [];
|
|
2670
|
-
for (const threadId of pending.cleanupThreadIds ?? []) if (!await archiveSupervisionArtifact(client, threadId)) remaining.push(threadId);
|
|
2671
|
-
return { remaining };
|
|
2672
|
-
}
|
|
2673
|
-
async function archiveSupervisionArtifact(client, threadId) {
|
|
2674
|
-
try {
|
|
2675
|
-
await client.request("thread/archive", { threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
|
|
2676
|
-
return true;
|
|
2677
|
-
} catch (error) {
|
|
2678
|
-
const message = formatErrorMessage(error).toLowerCase();
|
|
2679
|
-
if (message.includes("no rollout found for thread id") || message.includes("thread not found") || message.includes("already archived")) return true;
|
|
2680
|
-
await unsubscribeCodexThreadBestEffort(client, {
|
|
2681
|
-
threadId,
|
|
2682
|
-
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
2683
|
-
});
|
|
2684
|
-
embeddedAgentLog.warn("failed to archive temporary Codex supervision thread", {
|
|
2685
|
-
threadId,
|
|
2686
|
-
error
|
|
2687
|
-
});
|
|
2688
|
-
return false;
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
//#endregion
|
|
2692
|
-
//#region extensions/codex/src/app-server/thread-lifecycle-run.ts
|
|
2693
|
-
async function startOrResumeThread(params) {
|
|
2694
|
-
const incognito = isIncognitoSessionKey(params.params.sessionKey);
|
|
2695
|
-
const clientId = resolveCodexAppServerClientInstanceId(params.client);
|
|
2696
|
-
const bindingIdentity = sessionBindingIdentity({
|
|
2697
|
-
sessionId: params.params.sessionId,
|
|
2698
|
-
sessionKey: params.params.sessionKey,
|
|
2699
|
-
agentId: params.agentId ?? params.params.agentId,
|
|
2700
|
-
config: params.params.config
|
|
2701
|
-
});
|
|
2702
|
-
return await params.bindingStore.withLease(bindingIdentity, async () => {
|
|
2703
|
-
const lifecycleTiming = createCodexThreadLifecycleTimingTracker({
|
|
2704
|
-
...params.timing,
|
|
2705
|
-
enabled: params.timing?.enabled ?? isCodexAppServerProfilerEnabled(params.params.config)
|
|
2706
|
-
});
|
|
2707
|
-
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => codexLegacyDynamicToolsFingerprint(params.dynamicTools));
|
|
2708
|
-
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
2709
|
-
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
2710
|
-
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
2711
|
-
config: params.params.config,
|
|
2712
|
-
disableTools: params.params.disableTools,
|
|
2713
|
-
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
1687
|
+
const legacyDynamicToolsFingerprint = lifecycleTiming.measureSync("legacy-dynamic-tools-fingerprint", () => legacyFingerprintDynamicTools(params.dynamicTools));
|
|
1688
|
+
const dynamicToolsFingerprint = lifecycleTiming.measureSync("dynamic-tools-fingerprint", () => hashCodexAppServerBindingFingerprint(legacyDynamicToolsFingerprint));
|
|
1689
|
+
const dynamicToolsContainDeferred = flattenCodexDynamicToolFunctions(params.dynamicTools).some((tool) => tool.deferLoading === true);
|
|
1690
|
+
const webSearchThreadConfigFingerprint = fingerprintJsonObject(lifecycleTiming.measureSync("web-search-plan", () => resolveCodexWebSearchPlan({
|
|
1691
|
+
config: params.params.config,
|
|
1692
|
+
disableTools: params.params.disableTools,
|
|
1693
|
+
nativeToolSurfaceEnabled: params.nativeCodeModeEnabled,
|
|
2714
1694
|
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2715
1695
|
webSearchAllowed: params.webSearchAllowed
|
|
2716
1696
|
})).threadConfig);
|
|
2717
1697
|
const networkProxyConfigFingerprint = params.appServer.networkProxy?.configFingerprint;
|
|
2718
1698
|
const contextEngineBinding = lifecycleTiming.measureSync("context-engine-binding", () => buildContextEngineBinding(params.params, params.contextEngineProjection));
|
|
2719
|
-
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 :
|
|
2720
|
-
agentId: params.agentId ?? params.params.agentId,
|
|
2721
|
-
agentDir: params.params.agentDir,
|
|
2722
|
-
allowLiteralOAuthProjection: params.appServer.connectionClass !== "remote",
|
|
2723
|
-
onServerUnavailable: (serverName, error) => embeddedAgentLog.warn("skipping unavailable MCP OAuth server", {
|
|
2724
|
-
serverName,
|
|
2725
|
-
error: formatErrorMessage(error)
|
|
2726
|
-
})
|
|
2727
|
-
});
|
|
1699
|
+
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : buildCodexUserMcpServersThreadConfigPatch(params.params.config, { agentId: params.agentId ?? params.params.agentId });
|
|
2728
1700
|
const legacyUserMcpServersFingerprint = legacyFingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2729
1701
|
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
2730
1702
|
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
2731
|
-
const hostSystemAgentActive = params.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw");
|
|
2732
|
-
const ringZeroActive = hostSystemAgentActive && isSystemAgentOnlyCodexDynamicToolAllowlist(params.params.toolsAllow);
|
|
2733
|
-
if (ringZeroActive && params.nativeCodeModeEnabled !== false) throw new Error("Codex ring-zero requires native code mode to be disabled");
|
|
2734
|
-
const ringZeroInheritedMcpServerNames = ringZeroActive ? await lifecycleTiming.measure("ring-zero-mcp-config-read", () => readCodexInheritedMcpServerNames(params.client, params.cwd, params.signal)) : [];
|
|
2735
|
-
if (ringZeroActive) await lifecycleTiming.measure("ring-zero-config-requirements-read", () => assertCodexRingZeroHasNoManagedHooks(params.client, params.signal));
|
|
2736
|
-
const ringZeroConfigFingerprint = ringZeroActive ? fingerprintJsonObject({
|
|
2737
|
-
version: 1,
|
|
2738
|
-
baseInstructions: "",
|
|
2739
|
-
config: buildCodexRingZeroThreadConfigPatch(params.params, true, ringZeroInheritedMcpServerNames)
|
|
2740
|
-
}) : void 0;
|
|
2741
|
-
const ringZeroClientInstanceId = ringZeroActive ? getCodexAppServerClientInstanceId(params.client) : void 0;
|
|
2742
1703
|
let binding = await lifecycleTiming.measure("read-binding", () => params.bindingStore.read(bindingIdentity));
|
|
2743
1704
|
const normalizeBindingModelProvider = (authProfileId, modelProvider) => normalizeCodexAppServerBindingModelProvider({
|
|
2744
1705
|
authProfileId,
|
|
@@ -2747,90 +1708,25 @@ async function startOrResumeThread(params) {
|
|
|
2747
1708
|
agentDir: params.params.agentDir,
|
|
2748
1709
|
config: params.params.config
|
|
2749
1710
|
});
|
|
2750
|
-
const throwIfAborted = () => {
|
|
2751
|
-
if (!params.signal?.aborted) return;
|
|
2752
|
-
const reason = params.signal.reason;
|
|
2753
|
-
if (reason instanceof Error) throw reason;
|
|
2754
|
-
const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "codex app-server thread lifecycle aborted");
|
|
2755
|
-
error.name = "AbortError";
|
|
2756
|
-
throw error;
|
|
2757
|
-
};
|
|
2758
1711
|
if (!binding && bindingIdentity.kind === "session" && bindingIdentity.sessionKey) {
|
|
2759
1712
|
if (!await lifecycleTiming.measure("reclaim-binding-generation", () => reclaimCurrentCodexSessionGeneration({
|
|
2760
1713
|
bindingStore: params.bindingStore,
|
|
2761
1714
|
identity: bindingIdentity,
|
|
2762
1715
|
config: params.params.config
|
|
2763
|
-
}))) throw
|
|
2764
|
-
}
|
|
2765
|
-
if (binding?.pendingSupervisionBranch) {
|
|
2766
|
-
const pendingBinding = binding;
|
|
2767
|
-
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
2768
|
-
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
2769
|
-
configPatch: params.finalConfigPatch,
|
|
2770
|
-
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
2771
|
-
};
|
|
2772
|
-
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
2773
|
-
return await materializePendingSupervisionBranch({
|
|
2774
|
-
client: params.client,
|
|
2775
|
-
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
2776
|
-
bindingStore: params.bindingStore,
|
|
2777
|
-
bindingIdentity,
|
|
2778
|
-
binding: pendingBinding,
|
|
2779
|
-
attempt: params.params,
|
|
2780
|
-
cwd: params.cwd,
|
|
2781
|
-
dynamicTools: params.dynamicTools,
|
|
2782
|
-
appServer: params.appServer,
|
|
2783
|
-
developerInstructions: params.developerInstructions,
|
|
2784
|
-
config,
|
|
2785
|
-
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2786
|
-
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2787
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2788
|
-
webSearchAllowed: params.webSearchAllowed,
|
|
2789
|
-
environmentSelection: params.environmentSelection,
|
|
2790
|
-
signal: params.signal,
|
|
2791
|
-
throwIfAborted,
|
|
2792
|
-
lifecycleTiming,
|
|
2793
|
-
normalizeBindingModelProvider,
|
|
2794
|
-
bindingPatch: {
|
|
2795
|
-
cwd: params.cwd,
|
|
2796
|
-
...clientId ? { clientId } : {},
|
|
2797
|
-
authProfileId: void 0,
|
|
2798
|
-
preserveNativeModel: true,
|
|
2799
|
-
dynamicToolsFingerprint,
|
|
2800
|
-
dynamicToolsContainDeferred,
|
|
2801
|
-
webSearchThreadConfigFingerprint,
|
|
2802
|
-
userMcpServersFingerprint,
|
|
2803
|
-
mcpServersFingerprint: params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : pendingBinding.mcpServersFingerprint,
|
|
2804
|
-
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2805
|
-
networkProxyConfigFingerprint,
|
|
2806
|
-
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2807
|
-
appServerRuntimeFingerprint: buildCodexAppServerConnectionFingerprint(params.appServer, params.params.agentDir),
|
|
2808
|
-
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2809
|
-
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2810
|
-
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2811
|
-
contextEngine: contextEngineBinding,
|
|
2812
|
-
environmentSelectionFingerprint,
|
|
2813
|
-
conversationSourceTransferComplete: true
|
|
2814
|
-
}
|
|
2815
|
-
});
|
|
1716
|
+
}))) throw new Error(`Codex session generation is no longer current: ${bindingIdentity.sessionId}`);
|
|
2816
1717
|
}
|
|
2817
1718
|
const clearCurrentBinding = async (operation) => {
|
|
2818
1719
|
const current = binding;
|
|
2819
1720
|
if (!current?.threadId) return;
|
|
2820
|
-
assertCodexBindingMayBeReplaced(current, operation);
|
|
2821
1721
|
if (!await params.bindingStore.mutate(bindingIdentity, {
|
|
2822
1722
|
kind: "clear",
|
|
2823
1723
|
threadId: current.threadId
|
|
2824
1724
|
})) throw new CodexThreadBindingConflictError(current.threadId, operation);
|
|
2825
1725
|
binding = void 0;
|
|
2826
1726
|
};
|
|
2827
|
-
if (binding?.threadId && (binding.ringZeroConfigFingerprint !== ringZeroConfigFingerprint || binding.ringZeroClientInstanceId !== ringZeroClientInstanceId) && (ringZeroActive || binding.ringZeroConfigFingerprint !== void 0)) {
|
|
2828
|
-
embeddedAgentLog.debug("codex app-server ring-zero restriction changed; rotating thread", { threadId: binding.threadId });
|
|
2829
|
-
await clearCurrentBinding("rotating a ring-zero thread binding");
|
|
2830
|
-
}
|
|
2831
1727
|
if (binding?.threadId && shouldRotateCodexAppServerBindingForRuntime({
|
|
2832
1728
|
connectionClass: params.appServer.connectionClass,
|
|
2833
|
-
current:
|
|
1729
|
+
current: params.appServerRuntimeFingerprint,
|
|
2834
1730
|
binding: binding.appServerRuntimeFingerprint
|
|
2835
1731
|
})) {
|
|
2836
1732
|
embeddedAgentLog.debug("codex app-server runtime identity changed; starting a new thread", {
|
|
@@ -2862,10 +1758,17 @@ async function startOrResumeThread(params) {
|
|
|
2862
1758
|
config: params.params.config
|
|
2863
1759
|
});
|
|
2864
1760
|
const startModelProvider = startModelSelection.modelProvider;
|
|
2865
|
-
|
|
2866
|
-
let preserveExistingBinding = transientDelegationRestriction || !ringZeroActive && params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
1761
|
+
let preserveExistingBinding = params.nativeProviderWebSearchSupport === "unknown" && !binding?.threadId;
|
|
2867
1762
|
let rotatedContextEngineBinding = false;
|
|
2868
1763
|
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
|
+
};
|
|
2869
1772
|
const webSearchBindingChanged = binding?.threadId && binding.webSearchThreadConfigFingerprint !== webSearchThreadConfigFingerprint;
|
|
2870
1773
|
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
2871
1774
|
const transientNativeToolRestriction = params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction;
|
|
@@ -2873,8 +1776,7 @@ async function startOrResumeThread(params) {
|
|
|
2873
1776
|
const explicitTransientWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed !== false && transientWebSearchRestriction;
|
|
2874
1777
|
const unknownProviderWebSearchSupport = params.nativeProviderWebSearchSupport === "unknown";
|
|
2875
1778
|
if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
|
|
2876
|
-
|
|
2877
|
-
if (!ringZeroActive && (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport))) {
|
|
1779
|
+
if (transientNativeToolRestriction || webSearchBindingChanged && (explicitTransientWebSearchRestriction || unknownProviderWebSearchSupport)) {
|
|
2878
1780
|
embeddedAgentLog.debug("codex app-server MCP config changed during transient restricted turn; starting transient thread", { threadId: binding.threadId });
|
|
2879
1781
|
preserveExistingBinding = true;
|
|
2880
1782
|
} else {
|
|
@@ -2885,8 +1787,7 @@ async function startOrResumeThread(params) {
|
|
|
2885
1787
|
}
|
|
2886
1788
|
const deferLegacyWebSearchRotationToTransientNativeSurface = params.nativeCodeModeEnabled === false && binding?.webSearchThreadConfigFingerprint === void 0 && !persistentWebSearchRestriction;
|
|
2887
1789
|
if (binding?.threadId && webSearchBindingChanged && !deferLegacyWebSearchRotationToTransientNativeSurface) {
|
|
2888
|
-
|
|
2889
|
-
if (!ringZeroActive && transientWebSearchRestriction) {
|
|
1790
|
+
if (transientWebSearchRestriction) {
|
|
2890
1791
|
embeddedAgentLog.debug("codex app-server web search restricted for turn; starting transient thread", { threadId: binding.threadId });
|
|
2891
1792
|
preserveExistingBinding = true;
|
|
2892
1793
|
} else {
|
|
@@ -2895,17 +1796,11 @@ async function startOrResumeThread(params) {
|
|
|
2895
1796
|
}
|
|
2896
1797
|
binding = void 0;
|
|
2897
1798
|
}
|
|
2898
|
-
if (binding?.threadId && transientNativeToolRestriction
|
|
2899
|
-
assertCodexBindingMayBeReplaced(binding, "starting a native-tool-restricted turn");
|
|
1799
|
+
if (binding?.threadId && transientNativeToolRestriction) {
|
|
2900
1800
|
embeddedAgentLog.debug("codex app-server native tool surface disabled for turn; starting transient thread", { threadId: binding.threadId });
|
|
2901
1801
|
preserveExistingBinding = true;
|
|
2902
1802
|
binding = void 0;
|
|
2903
1803
|
}
|
|
2904
|
-
if (binding?.threadId && transientDelegationRestriction) {
|
|
2905
|
-
assertCodexBindingMayBeReplaced(binding, "starting a delegation-restricted turn");
|
|
2906
|
-
embeddedAgentLog.debug("codex app-server delegation restricted for turn; starting transient thread", { threadId: binding.threadId });
|
|
2907
|
-
binding = void 0;
|
|
2908
|
-
}
|
|
2909
1804
|
if (binding?.threadId && (binding.contextEngine || contextEngineBinding)) {
|
|
2910
1805
|
if (!contextEngineBinding || !isContextEngineBindingCompatible(binding.contextEngine, contextEngineBinding)) {
|
|
2911
1806
|
embeddedAgentLog.debug("codex app-server context-engine binding changed; starting a new thread", {
|
|
@@ -2976,178 +1871,494 @@ async function startOrResumeThread(params) {
|
|
|
2976
1871
|
binding = void 0;
|
|
2977
1872
|
}
|
|
2978
1873
|
}
|
|
2979
|
-
if (binding?.threadId)
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
});
|
|
2985
|
-
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
2986
|
-
else {
|
|
2987
|
-
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
2988
|
-
await clearCurrentBinding("rotating a stale thread binding");
|
|
2989
|
-
}
|
|
2990
|
-
} else if (incognito) {
|
|
2991
|
-
if (binding.clientId && binding.clientId === clientId) {
|
|
2992
|
-
params.buildFinalConfigPatch?.({
|
|
2993
|
-
action: "resume",
|
|
2994
|
-
binding
|
|
2995
|
-
});
|
|
2996
|
-
throwIfAborted();
|
|
2997
|
-
lifecycleTiming.mark("thread-ready");
|
|
2998
|
-
lifecycleTiming.logSummary({
|
|
2999
|
-
runId: params.params.runId,
|
|
3000
|
-
sessionId: params.params.sessionId,
|
|
3001
|
-
sessionKey: params.params.sessionKey,
|
|
3002
|
-
threadId: binding.threadId,
|
|
3003
|
-
action: "resumed"
|
|
1874
|
+
if (binding?.threadId) {
|
|
1875
|
+
if (binding.dynamicToolsFingerprint && !areDynamicToolFingerprintsCompatible(binding.dynamicToolsFingerprint, dynamicToolsFingerprint, legacyDynamicToolsFingerprint)) {
|
|
1876
|
+
preserveExistingBinding = shouldStartTransientNoToolThread({
|
|
1877
|
+
previous: binding.dynamicToolsFingerprint,
|
|
1878
|
+
nextHasDynamicTools: params.dynamicTools.length > 0
|
|
3004
1879
|
});
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
1880
|
+
if (preserveExistingBinding) embeddedAgentLog.debug("codex app-server dynamic tools unavailable for turn; starting transient thread", { threadId: binding.threadId });
|
|
1881
|
+
else {
|
|
1882
|
+
embeddedAgentLog.debug("codex app-server dynamic tool catalog changed; starting a new thread", { threadId: binding.threadId });
|
|
1883
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
1884
|
+
}
|
|
1885
|
+
} else {
|
|
1886
|
+
const resumeBinding = binding;
|
|
1887
|
+
let resumeReservation;
|
|
1888
|
+
try {
|
|
1889
|
+
const authProfileId = params.params.authProfileId ?? resumeBinding.authProfileId;
|
|
1890
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({
|
|
1891
|
+
action: "resume",
|
|
1892
|
+
binding: resumeBinding
|
|
1893
|
+
}) ?? {
|
|
1894
|
+
configPatch: params.finalConfigPatch,
|
|
1895
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
1896
|
+
};
|
|
1897
|
+
const pluginAppsConfigPatch = params.pluginThreadConfig?.enabled && resumeBinding.pluginAppPolicyContext ? buildCodexPluginAppsConfigPatchFromPolicyContext(resumeBinding.pluginAppPolicyContext) : void 0;
|
|
1898
|
+
const resumeConfig = mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginAppsConfigPatch, finalConfigPatch.configPatch);
|
|
1899
|
+
const resumeParams = lifecycleTiming.measureSync("thread-resume-params", () => buildThreadResumeParams(params.params, {
|
|
1900
|
+
threadId: resumeBinding.threadId,
|
|
1901
|
+
authProfileId,
|
|
1902
|
+
model: startModelSelection.model,
|
|
1903
|
+
modelProvider: startModelProvider,
|
|
1904
|
+
appServer: params.appServer,
|
|
1905
|
+
dynamicTools: params.dynamicTools,
|
|
1906
|
+
developerInstructions: params.developerInstructions,
|
|
1907
|
+
config: resumeConfig,
|
|
1908
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
1909
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
1910
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
1911
|
+
webSearchAllowed: params.webSearchAllowed
|
|
1912
|
+
}));
|
|
1913
|
+
const requestModelProvider = typeof resumeParams.modelProvider === "string" && resumeParams.modelProvider.trim() ? resumeParams.modelProvider : void 0;
|
|
1914
|
+
throwIfAborted();
|
|
1915
|
+
resumeReservation = params.reserveResumeThread?.(resumeBinding.threadId);
|
|
1916
|
+
const response = await lifecycleTiming.measure("thread-resume-request", () => resumeCodexAppServerThread({
|
|
1917
|
+
client: params.client,
|
|
1918
|
+
abandonClient: params.abandonClient ?? (() => closeCodexStartupClientBestEffort(params.client)),
|
|
1919
|
+
request: resumeParams,
|
|
1920
|
+
signal: params.signal
|
|
1921
|
+
}));
|
|
1922
|
+
throwIfAborted();
|
|
1923
|
+
const boundAuthProfileId = authProfileId;
|
|
1924
|
+
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : resumeBinding.mcpServersFingerprint;
|
|
1925
|
+
const resumePatch = {
|
|
1926
|
+
cwd: params.cwd,
|
|
1927
|
+
authProfileId: boundAuthProfileId,
|
|
1928
|
+
model: response.model ?? resumeParams.model ?? params.params.modelId,
|
|
1929
|
+
modelProvider: normalizeBindingModelProvider(boundAuthProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider),
|
|
1930
|
+
dynamicToolsFingerprint,
|
|
1931
|
+
dynamicToolsContainDeferred,
|
|
1932
|
+
webSearchThreadConfigFingerprint,
|
|
1933
|
+
userMcpServersFingerprint,
|
|
1934
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
1935
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
1936
|
+
networkProxyConfigFingerprint,
|
|
1937
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration ?? resumeBinding.nativeHookRelayGeneration,
|
|
1938
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
1939
|
+
pluginAppsFingerprint: resumeBinding.pluginAppsFingerprint,
|
|
1940
|
+
pluginAppsInputFingerprint: resumeBinding.pluginAppsInputFingerprint,
|
|
1941
|
+
pluginAppPolicyContext: resumeBinding.pluginAppPolicyContext,
|
|
1942
|
+
contextEngine: contextEngineBinding,
|
|
1943
|
+
environmentSelectionFingerprint
|
|
1944
|
+
};
|
|
1945
|
+
if (!await lifecycleTiming.measure("thread-resume-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
1946
|
+
kind: "patch",
|
|
1947
|
+
threadId: resumeBinding.threadId,
|
|
1948
|
+
patch: resumePatch
|
|
1949
|
+
}))) throw new CodexThreadBindingConflictError(resumeBinding.threadId, "committing a resumed thread");
|
|
1950
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
1951
|
+
sessionId: params.params.sessionId,
|
|
1952
|
+
sessionKey: params.params.sessionKey,
|
|
1953
|
+
threadId: response.thread.id,
|
|
1954
|
+
engineId: contextEngineBinding.engineId,
|
|
1955
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
1956
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
1957
|
+
action: "resumed"
|
|
1958
|
+
});
|
|
1959
|
+
lifecycleTiming.mark("thread-ready");
|
|
1960
|
+
lifecycleTiming.logSummary({
|
|
1961
|
+
runId: params.params.runId,
|
|
1962
|
+
sessionId: params.params.sessionId,
|
|
1963
|
+
sessionKey: params.params.sessionKey,
|
|
1964
|
+
threadId: response.thread.id,
|
|
1965
|
+
action: "resumed"
|
|
1966
|
+
});
|
|
1967
|
+
const activeTurnIds = readActiveCodexTurnIds(response.thread);
|
|
1968
|
+
return {
|
|
1969
|
+
...resumeBinding,
|
|
1970
|
+
threadId: response.thread.id,
|
|
1971
|
+
...resumePatch,
|
|
1972
|
+
lifecycle: {
|
|
1973
|
+
action: "resumed",
|
|
1974
|
+
...activeTurnIds.length ? { activeTurnIds } : {}
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
resumeReservation?.release();
|
|
1979
|
+
if (isCodexAppServerUnsafeSubscriptionError(error)) throw error;
|
|
1980
|
+
const resumeRejected = error instanceof CodexAppServerRpcError;
|
|
1981
|
+
if (!await unsubscribeCodexThreadBestEffort(params.client, {
|
|
1982
|
+
threadId: resumeBinding.threadId,
|
|
1983
|
+
timeoutMs: 5e3
|
|
1984
|
+
}) && !resumeRejected && !isCodexAppServerConnectionClosedError(error) && !params.signal?.aborted) throw new CodexAppServerUnsafeSubscriptionError("Codex thread/resume subscription cleanup failed", { cause: error });
|
|
1985
|
+
if (isCodexAppServerConnectionClosedError(error) || params.signal?.aborted) throw error;
|
|
1986
|
+
embeddedAgentLog.warn("codex app-server thread resume failed; starting a new thread", { error });
|
|
1987
|
+
await clearCurrentBinding("rotating a stale thread binding");
|
|
1988
|
+
}
|
|
3009
1989
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
1990
|
+
}
|
|
1991
|
+
const pluginThreadConfig = params.pluginThreadConfig?.enabled ? prebuiltPluginThreadConfig ?? await lifecycleTiming.measure("plugin-config-build", () => params.pluginThreadConfig?.build()) : void 0;
|
|
1992
|
+
const finalConfigPatch = params.buildFinalConfigPatch?.({ action: "start" }) ?? {
|
|
1993
|
+
configPatch: params.finalConfigPatch,
|
|
1994
|
+
nativeHookRelayGeneration: params.nativeHookRelayGeneration
|
|
1995
|
+
};
|
|
1996
|
+
const config = lifecycleTiming.measureSync("merge-thread-config", () => mergeCodexThreadConfigs(params.config, userMcpServersConfigPatch, pluginThreadConfig?.configPatch, finalConfigPatch.configPatch));
|
|
1997
|
+
const startParams = lifecycleTiming.measureSync("thread-start-params", () => buildThreadStartParams(params.params, {
|
|
1998
|
+
cwd: params.cwd,
|
|
1999
|
+
dynamicTools: params.dynamicTools,
|
|
2000
|
+
appServer: params.appServer,
|
|
2001
|
+
developerInstructions: params.developerInstructions,
|
|
2002
|
+
config,
|
|
2003
|
+
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
2004
|
+
nativeProviderWebSearchSupport: params.nativeProviderWebSearchSupport,
|
|
2005
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
2006
|
+
webSearchAllowed: params.webSearchAllowed,
|
|
2007
|
+
environmentSelection: params.environmentSelection,
|
|
2008
|
+
model: startModelSelection.model,
|
|
2009
|
+
modelProvider: startModelProvider
|
|
2010
|
+
}));
|
|
2011
|
+
const requestModelProvider = typeof startParams.modelProvider === "string" && startParams.modelProvider.trim() ? startParams.modelProvider : void 0;
|
|
2012
|
+
const threadStartResponse = await lifecycleTiming.measure("thread-start-request", async () => {
|
|
2013
|
+
try {
|
|
2014
|
+
return await params.client.request("thread/start", startParams, { signal: params.signal });
|
|
2015
|
+
} catch (error) {
|
|
2016
|
+
if (error instanceof CodexAppServerRpcError) throw new CodexThreadStartRequestError(error);
|
|
2017
|
+
throw error;
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
const response = assertCodexThreadStartResponse(threadStartResponse);
|
|
2021
|
+
throwIfAborted();
|
|
2022
|
+
const modelProvider = resolveCodexAppServerModelProvider({
|
|
2023
|
+
provider: params.params.provider,
|
|
2024
|
+
authProfileId: params.params.authProfileId,
|
|
2025
|
+
authProfileStore: params.params.authProfileStore,
|
|
2026
|
+
agentDir: params.params.agentDir,
|
|
2027
|
+
config: params.params.config
|
|
2028
|
+
});
|
|
2029
|
+
const nextMcpServersFingerprint = params.mcpServersFingerprintEvaluated === true ? params.mcpServersFingerprint : void 0;
|
|
2030
|
+
if (!preserveExistingBinding) {
|
|
2031
|
+
if (!await lifecycleTiming.measure("thread-start-write-binding", () => params.bindingStore.mutate(bindingIdentity, {
|
|
2032
|
+
kind: "set",
|
|
2033
|
+
if: { kind: "absent" },
|
|
2034
|
+
binding: {
|
|
2035
|
+
threadId: response.thread.id,
|
|
2036
|
+
cwd: params.cwd,
|
|
2037
|
+
authProfileId: params.params.authProfileId,
|
|
2038
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2039
|
+
modelProvider: normalizeBindingModelProvider(params.params.authProfileId, response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider),
|
|
2040
|
+
dynamicToolsFingerprint,
|
|
2041
|
+
dynamicToolsContainDeferred,
|
|
2042
|
+
webSearchThreadConfigFingerprint,
|
|
2043
|
+
userMcpServersFingerprint,
|
|
2044
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2045
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
2046
|
+
networkProxyConfigFingerprint,
|
|
2047
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2048
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2049
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2050
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2051
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2052
|
+
contextEngine: contextEngineBinding,
|
|
2053
|
+
environmentSelectionFingerprint
|
|
2054
|
+
}
|
|
2055
|
+
}))) throw new CodexThreadBindingConflictError(response.thread.id, "committing a fresh thread");
|
|
2056
|
+
if (contextEngineBinding) embeddedAgentLog.info("codex app-server wrote context-engine thread binding", {
|
|
2057
|
+
sessionId: params.params.sessionId,
|
|
2058
|
+
sessionKey: params.params.sessionKey,
|
|
2059
|
+
threadId: response.thread.id,
|
|
2060
|
+
engineId: contextEngineBinding.engineId,
|
|
2061
|
+
epoch: contextEngineBinding.projection?.epoch,
|
|
2062
|
+
fingerprint: contextEngineBinding.projection?.fingerprint,
|
|
2063
|
+
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
3035
2064
|
});
|
|
3036
|
-
if (resumed) return resumed;
|
|
3037
2065
|
}
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
2066
|
+
lifecycleTiming.mark("thread-ready");
|
|
2067
|
+
lifecycleTiming.logSummary({
|
|
2068
|
+
runId: params.params.runId,
|
|
2069
|
+
sessionId: params.params.sessionId,
|
|
2070
|
+
sessionKey: params.params.sessionKey,
|
|
2071
|
+
threadId: response.thread.id,
|
|
2072
|
+
action: rotatedContextEngineBinding ? "rotated" : "started"
|
|
2073
|
+
});
|
|
2074
|
+
return {
|
|
2075
|
+
threadId: response.thread.id,
|
|
2076
|
+
cwd: params.cwd,
|
|
2077
|
+
authProfileId: params.params.authProfileId,
|
|
2078
|
+
model: response.model ?? startParams.model ?? params.params.modelId,
|
|
2079
|
+
modelProvider: response.modelProvider ?? requestModelProvider ?? startModelProvider ?? modelProvider,
|
|
3043
2080
|
dynamicToolsFingerprint,
|
|
3044
2081
|
dynamicToolsContainDeferred,
|
|
3045
|
-
webSearchThreadConfigFingerprint,
|
|
3046
2082
|
userMcpServersFingerprint,
|
|
3047
|
-
|
|
3048
|
-
|
|
2083
|
+
mcpServersFingerprint: nextMcpServersFingerprint,
|
|
2084
|
+
networkProxyProfileName: params.appServer.networkProxy?.profileName,
|
|
3049
2085
|
networkProxyConfigFingerprint,
|
|
3050
|
-
|
|
2086
|
+
nativeHookRelayGeneration: finalConfigPatch.nativeHookRelayGeneration,
|
|
2087
|
+
appServerRuntimeFingerprint: params.appServerRuntimeFingerprint,
|
|
2088
|
+
pluginAppsFingerprint: pluginThreadConfig?.fingerprint,
|
|
2089
|
+
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
2090
|
+
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
2091
|
+
contextEngine: contextEngineBinding,
|
|
3051
2092
|
environmentSelectionFingerprint,
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
throwIfAborted,
|
|
3058
|
-
prebuiltPluginThreadConfig,
|
|
3059
|
-
preserveExistingBinding,
|
|
3060
|
-
rotatedContextEngineBinding
|
|
3061
|
-
});
|
|
2093
|
+
lifecycle: {
|
|
2094
|
+
action: "started",
|
|
2095
|
+
...rotatedContextEngineBinding ? { rotatedContextEngineBinding } : {}
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
3062
2098
|
});
|
|
3063
2099
|
}
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
* base64 data becomes readable text instead of poisoning replayed transcripts.
|
|
3069
|
-
*/
|
|
3070
|
-
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
3071
|
-
/** Validates and normalizes an inline image data URL for Codex history payloads. */
|
|
3072
|
-
function sanitizeInlineImageDataUrl$1(imageUrl) {
|
|
3073
|
-
return sanitizeInlineImageDataUrl(imageUrl);
|
|
3074
|
-
}
|
|
3075
|
-
/** Builds the replacement text inserted when an inline image payload is invalid. */
|
|
3076
|
-
function invalidInlineImageText(label) {
|
|
3077
|
-
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
2100
|
+
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
2101
|
+
if (!params.current) return false;
|
|
2102
|
+
if (params.binding === params.current) return false;
|
|
2103
|
+
return params.connectionClass === "remote" || Boolean(params.binding);
|
|
3078
2104
|
}
|
|
3079
|
-
function
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
const commaIndex = imageUrl.indexOf(",");
|
|
3088
|
-
const mime = imageUrl.slice(INLINE_IMAGE_DATA_URL_PREFIX.length, commaIndex).split(";")[0] ?? mimeType;
|
|
3089
|
-
return {
|
|
3090
|
-
...record,
|
|
3091
|
-
mimeType: mime,
|
|
3092
|
-
data: imageUrl.slice(commaIndex + 1)
|
|
3093
|
-
};
|
|
3094
|
-
}
|
|
3095
|
-
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
3096
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(record.imageUrl);
|
|
3097
|
-
return imageUrl ? {
|
|
3098
|
-
...record,
|
|
3099
|
-
imageUrl
|
|
3100
|
-
} : {
|
|
3101
|
-
type: "inputText",
|
|
3102
|
-
text: invalidInlineImageText(label)
|
|
3103
|
-
};
|
|
3104
|
-
}
|
|
3105
|
-
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
3106
|
-
const imageUrl = sanitizeInlineImageDataUrl$1(record.image_url);
|
|
3107
|
-
return imageUrl ? {
|
|
3108
|
-
...record,
|
|
3109
|
-
image_url: imageUrl
|
|
3110
|
-
} : {
|
|
3111
|
-
type: "input_text",
|
|
3112
|
-
text: invalidInlineImageText(label)
|
|
3113
|
-
};
|
|
2105
|
+
function resolveCodexGpt56MultiAgentVersion(modelRef) {
|
|
2106
|
+
let modelId = modelRef?.trim().toLowerCase();
|
|
2107
|
+
if (!modelId) return;
|
|
2108
|
+
const slashIndex = modelId.indexOf("/");
|
|
2109
|
+
if (slashIndex > 0) {
|
|
2110
|
+
const provider = modelId.slice(0, slashIndex);
|
|
2111
|
+
if (provider !== "openai" && provider !== "codex") return;
|
|
2112
|
+
modelId = modelId.slice(slashIndex + 1);
|
|
3114
2113
|
}
|
|
2114
|
+
if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
|
|
2115
|
+
return modelId === "gpt-5.6-luna" ? "v1" : void 0;
|
|
3115
2116
|
}
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
const imageRecord = sanitizeImageContentRecord(value, label);
|
|
3121
|
-
if (imageRecord) return imageRecord;
|
|
3122
|
-
const next = {};
|
|
3123
|
-
for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
|
|
3124
|
-
return next;
|
|
2117
|
+
function shouldRotateCodexGpt56MultiAgentBinding(params) {
|
|
2118
|
+
const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
|
|
2119
|
+
const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
|
|
2120
|
+
return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
|
|
3125
2121
|
}
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
text_elements: []
|
|
3139
|
-
};
|
|
2122
|
+
function isTransientWebSearchRestriction(params) {
|
|
2123
|
+
if (params.nativeProviderWebSearchSupport === "unknown") return true;
|
|
2124
|
+
if (params.params.config?.tools?.web?.search?.enabled === false) return false;
|
|
2125
|
+
if (params.params.disableTools === true) return true;
|
|
2126
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
2127
|
+
if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
|
|
2128
|
+
if (params.webSearchAllowed !== false) return false;
|
|
2129
|
+
if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
|
|
2130
|
+
if (params.params.toolsAllow === void 0) return false;
|
|
2131
|
+
return !params.params.toolsAllow.some((name) => {
|
|
2132
|
+
const normalized = normalizeCodexDynamicToolName(name);
|
|
2133
|
+
return normalized === "*" || normalized === "web_search";
|
|
3140
2134
|
});
|
|
3141
|
-
return [...text === void 0 ? [] : [{
|
|
3142
|
-
type: "text",
|
|
3143
|
-
text,
|
|
3144
|
-
text_elements: []
|
|
3145
|
-
}], ...imageInputs];
|
|
3146
2135
|
}
|
|
3147
|
-
|
|
3148
|
-
|
|
2136
|
+
function buildContextEngineBinding(params, projection) {
|
|
2137
|
+
const contextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
|
|
2138
|
+
const engineId = contextEngine?.info?.id?.trim();
|
|
2139
|
+
if (!contextEngine || !engineId) return;
|
|
2140
|
+
return {
|
|
2141
|
+
schemaVersion: 1,
|
|
2142
|
+
engineId,
|
|
2143
|
+
policyFingerprint: JSON.stringify({
|
|
2144
|
+
schemaVersion: 1,
|
|
2145
|
+
engineId,
|
|
2146
|
+
engineVersion: contextEngine.info.version,
|
|
2147
|
+
ownsCompaction: contextEngine.info.ownsCompaction === true,
|
|
2148
|
+
turnMaintenanceMode: contextEngine.info.turnMaintenanceMode,
|
|
2149
|
+
citationsMode: resolveContextEngineCitationsMode(params.config),
|
|
2150
|
+
contextTokenBudget: params.contextTokenBudget,
|
|
2151
|
+
projectionMaxChars: resolveCodexContextEngineProjectionMaxChars({
|
|
2152
|
+
contextTokenBudget: params.contextTokenBudget,
|
|
2153
|
+
reserveTokens: resolveCodexContextEngineProjectionReserveTokens({ config: params.config })
|
|
2154
|
+
})
|
|
2155
|
+
}),
|
|
2156
|
+
projection: projection ? buildContextEngineProjectionBinding(projection) : void 0
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
function buildContextEngineProjectionBinding(projection) {
|
|
2160
|
+
return {
|
|
2161
|
+
schemaVersion: 1,
|
|
2162
|
+
mode: "thread_bootstrap",
|
|
2163
|
+
epoch: projection.epoch,
|
|
2164
|
+
fingerprint: projection.fingerprint
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
function isContextEngineBindingCompatible(previous, next) {
|
|
2168
|
+
return previous?.schemaVersion === next.schemaVersion && previous.engineId === next.engineId && previous.policyFingerprint === next.policyFingerprint && areContextEngineProjectionBindingsCompatible(previous.projection, next.projection);
|
|
2169
|
+
}
|
|
2170
|
+
function areContextEngineProjectionBindingsCompatible(previous, next) {
|
|
2171
|
+
if (!next) return previous === void 0;
|
|
2172
|
+
return previous?.schemaVersion === next.schemaVersion && previous.mode === next.mode && previous.epoch === next.epoch && previous.fingerprint === next.fingerprint;
|
|
2173
|
+
}
|
|
2174
|
+
function resolveContextEngineCitationsMode(config) {
|
|
2175
|
+
const rootConfig = isUnknownRecord(config) ? config : void 0;
|
|
2176
|
+
const citations = (isUnknownRecord(rootConfig?.memory) ? rootConfig.memory : void 0)?.citations;
|
|
2177
|
+
return isJsonConfigValue(citations) ? citations : void 0;
|
|
2178
|
+
}
|
|
2179
|
+
function isUnknownRecord(value) {
|
|
2180
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2181
|
+
}
|
|
2182
|
+
function isJsonConfigValue(value) {
|
|
2183
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
2184
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
2185
|
+
if (Array.isArray(value)) return value.every(isJsonConfigValue);
|
|
2186
|
+
return isUnknownRecord(value) && Object.values(value).every(isJsonConfigValue);
|
|
2187
|
+
}
|
|
2188
|
+
function shouldRecheckRecoverablePluginBinding(params) {
|
|
2189
|
+
if (!params.pluginThreadConfig?.enabled) return false;
|
|
2190
|
+
if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
|
|
2191
|
+
const policyContext = params.binding.pluginAppPolicyContext;
|
|
2192
|
+
if (!policyContext) return false;
|
|
2193
|
+
const expectedPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
|
|
2194
|
+
return Object.keys(policyContext.apps).length === 0 || expectedPluginConfigKeys.length > 0;
|
|
2195
|
+
}
|
|
2196
|
+
function buildThreadStartParams(params, options) {
|
|
2197
|
+
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
2198
|
+
provider: params.provider,
|
|
2199
|
+
authProfileId: params.authProfileId,
|
|
2200
|
+
authProfileStore: params.authProfileStore,
|
|
2201
|
+
agentDir: params.agentDir,
|
|
2202
|
+
config: params.config
|
|
2203
|
+
});
|
|
2204
|
+
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
2205
|
+
model: options.model ?? params.modelId,
|
|
2206
|
+
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
2207
|
+
authProfileId: params.authProfileId,
|
|
2208
|
+
authProfileStore: params.authProfileStore,
|
|
2209
|
+
agentDir: params.agentDir,
|
|
2210
|
+
config: params.config
|
|
2211
|
+
});
|
|
2212
|
+
return {
|
|
2213
|
+
model: modelSelection.model,
|
|
2214
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
2215
|
+
cwd: options.cwd,
|
|
2216
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
2217
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2218
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2219
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2220
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2221
|
+
serviceName: "OpenClaw",
|
|
2222
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2223
|
+
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2224
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2225
|
+
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2226
|
+
webSearchAllowed: options.webSearchAllowed,
|
|
2227
|
+
appServer: options.appServer
|
|
2228
|
+
}),
|
|
2229
|
+
...resolveCodexThreadEnvironmentSelection(options),
|
|
2230
|
+
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
2231
|
+
dynamicTools: [...options.dynamicTools],
|
|
2232
|
+
experimentalRawEvents: true
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
function buildThreadResumeParams(params, options) {
|
|
2236
|
+
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
2237
|
+
provider: params.provider,
|
|
2238
|
+
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
2239
|
+
authProfileStore: params.authProfileStore,
|
|
2240
|
+
agentDir: params.agentDir,
|
|
2241
|
+
config: params.config
|
|
2242
|
+
});
|
|
2243
|
+
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
2244
|
+
model: options.model ?? params.modelId,
|
|
2245
|
+
modelProvider: options.modelProvider ?? resolvedModelProvider,
|
|
2246
|
+
authProfileId: options.authProfileId ?? params.authProfileId,
|
|
2247
|
+
authProfileStore: params.authProfileStore,
|
|
2248
|
+
agentDir: params.agentDir,
|
|
2249
|
+
config: params.config
|
|
2250
|
+
});
|
|
2251
|
+
return {
|
|
2252
|
+
threadId: options.threadId,
|
|
2253
|
+
model: modelSelection.model,
|
|
2254
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
2255
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
2256
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
2257
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
2258
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
2259
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
2260
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
2261
|
+
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
2262
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2263
|
+
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled,
|
|
2264
|
+
webSearchAllowed: options.webSearchAllowed,
|
|
2265
|
+
appServer: options.appServer
|
|
2266
|
+
}),
|
|
2267
|
+
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools })
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
function resolveCodexBindingModelProviderFallback(params) {
|
|
2271
|
+
const provider = params.provider?.trim().toLowerCase();
|
|
2272
|
+
if (provider && provider !== "codex") return;
|
|
2273
|
+
const currentModel = params.currentModel?.trim();
|
|
2274
|
+
const bindingModel = params.bindingModel?.trim();
|
|
2275
|
+
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
2276
|
+
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
2277
|
+
}
|
|
2278
|
+
function resolveCodexAppServerThreadModelSelection(params) {
|
|
2279
|
+
const authProfileId = params.authProfileId ?? params.binding?.authProfileId;
|
|
2280
|
+
const explicitModelProvider = resolveCodexAppServerModelProvider({
|
|
2281
|
+
provider: params.provider,
|
|
2282
|
+
authProfileId,
|
|
2283
|
+
authProfileStore: params.authProfileStore,
|
|
2284
|
+
agentDir: params.agentDir,
|
|
2285
|
+
config: params.config
|
|
2286
|
+
});
|
|
2287
|
+
const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
|
|
2288
|
+
provider: params.provider,
|
|
2289
|
+
currentModel: params.model,
|
|
2290
|
+
bindingModel: params.binding.model,
|
|
2291
|
+
bindingModelProvider: params.binding.modelProvider
|
|
2292
|
+
}) : void 0;
|
|
2293
|
+
return resolveCodexAppServerRequestModelSelection({
|
|
2294
|
+
model: params.model,
|
|
2295
|
+
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
2296
|
+
authProfileId,
|
|
2297
|
+
authProfileStore: params.authProfileStore,
|
|
2298
|
+
agentDir: params.agentDir,
|
|
2299
|
+
config: params.config
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
function resolveCodexAppServerRequestModelSelection(params) {
|
|
2303
|
+
const model = params.model.trim();
|
|
2304
|
+
const modelProvider = params.modelProvider?.trim();
|
|
2305
|
+
if (modelProvider) return {
|
|
2306
|
+
model,
|
|
2307
|
+
modelProvider
|
|
2308
|
+
};
|
|
2309
|
+
const slashIndex = model.indexOf("/");
|
|
2310
|
+
if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
|
|
2311
|
+
const inferredModelProvider = resolveCodexAppServerModelProvider({
|
|
2312
|
+
provider: model.slice(0, slashIndex),
|
|
2313
|
+
authProfileId: params.authProfileId,
|
|
2314
|
+
authProfileStore: params.authProfileStore,
|
|
2315
|
+
agentDir: params.agentDir,
|
|
2316
|
+
config: params.config
|
|
2317
|
+
});
|
|
2318
|
+
return {
|
|
2319
|
+
model: model.slice(slashIndex + 1).trim(),
|
|
2320
|
+
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
function hasProviderQualifiedModelRef(model) {
|
|
2324
|
+
const trimmed = model?.trim();
|
|
2325
|
+
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
|
2326
|
+
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
2327
|
+
}
|
|
2328
|
+
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
2329
|
+
const codeModeConfig = {
|
|
2330
|
+
...CODEX_CODE_MODE_THREAD_CONFIG,
|
|
2331
|
+
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
2332
|
+
};
|
|
2333
|
+
if (options.nativeCodeModeEnabled === false) {
|
|
2334
|
+
const disabledConfig = mergeCodexThreadConfigs(config, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG) ?? { ...CODEX_CODE_MODE_DISABLED_THREAD_CONFIG };
|
|
2335
|
+
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
2336
|
+
return disabledConfig;
|
|
2337
|
+
}
|
|
2338
|
+
if (options.nativeCodeModeOnlyEnabled === true) return mergeCodexThreadConfigs(codeModeConfig, config, { "features.code_mode_only": true }) ?? {
|
|
2339
|
+
...codeModeConfig,
|
|
2340
|
+
"features.code_mode_only": true
|
|
2341
|
+
};
|
|
2342
|
+
return mergeCodexThreadConfigs(codeModeConfig, config) ?? { ...codeModeConfig };
|
|
2343
|
+
}
|
|
2344
|
+
function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
2345
|
+
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
2346
|
+
config: params.config,
|
|
2347
|
+
disableTools: params.disableTools,
|
|
2348
|
+
nativeToolSurfaceEnabled: options.nativeCodeModeEnabled,
|
|
2349
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
2350
|
+
webSearchAllowed: options.webSearchAllowed
|
|
2351
|
+
}).threadConfig;
|
|
2352
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
2353
|
+
const runtimeConfig = mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0) ?? baseConfig;
|
|
2354
|
+
if (params.bootstrapContextMode !== "lightweight") return runtimeConfig;
|
|
2355
|
+
return mergeCodexThreadConfigs(runtimeConfig, CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG) ?? {
|
|
2356
|
+
...runtimeConfig,
|
|
2357
|
+
...CODEX_LIGHTWEIGHT_CONTEXT_THREAD_CONFIG
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
3149
2360
|
function buildTurnStartParams(params, options) {
|
|
3150
|
-
const modelSelection =
|
|
2361
|
+
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
3151
2362
|
model: options.model ?? params.modelId,
|
|
3152
2363
|
modelProvider: options.modelProvider,
|
|
3153
2364
|
authProfileId: params.authProfileId,
|
|
@@ -3158,26 +2369,37 @@ function buildTurnStartParams(params, options) {
|
|
|
3158
2369
|
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
3159
2370
|
return {
|
|
3160
2371
|
threadId: options.threadId,
|
|
3161
|
-
input:
|
|
2372
|
+
input: buildUserInput(params, options.promptText),
|
|
3162
2373
|
cwd: options.cwd,
|
|
3163
2374
|
approvalPolicy: options.appServer.approvalPolicy,
|
|
3164
2375
|
approvalsReviewer: options.appServer.approvalsReviewer,
|
|
3165
|
-
...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.cwd
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
3169
|
-
} : {},
|
|
2376
|
+
...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.cwd) },
|
|
2377
|
+
model: modelSelection.model,
|
|
2378
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
3170
2379
|
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
3171
|
-
|
|
2380
|
+
effort: resolveReasoningEffort(params.thinkLevel, modelSelection.model, readCodexSupportedReasoningEfforts(params.model?.compat)),
|
|
3172
2381
|
...options.environmentSelection ? { environments: options.environmentSelection } : {},
|
|
3173
|
-
|
|
2382
|
+
collaborationMode: buildTurnCollaborationMode(params, {
|
|
3174
2383
|
model: modelSelection.model,
|
|
3175
2384
|
turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
|
|
3176
2385
|
skillsCollaborationInstructions: options.skillsCollaborationInstructions,
|
|
3177
|
-
memoryCollaborationInstructions: options.memoryCollaborationInstructions
|
|
3178
|
-
|
|
2386
|
+
memoryCollaborationInstructions: options.memoryCollaborationInstructions,
|
|
2387
|
+
heartbeatCollaborationInstructions: options.heartbeatCollaborationInstructions
|
|
2388
|
+
})
|
|
3179
2389
|
};
|
|
3180
2390
|
}
|
|
2391
|
+
function resolveCodexThreadApprovalsReviewer(appServer, config) {
|
|
2392
|
+
return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
|
|
2393
|
+
}
|
|
2394
|
+
function codexThreadSandboxOrPermissions(appServer) {
|
|
2395
|
+
if (appServer.networkProxy) return {};
|
|
2396
|
+
return { sandbox: appServer.sandbox };
|
|
2397
|
+
}
|
|
2398
|
+
function resolveCodexThreadEnvironmentSelection(options) {
|
|
2399
|
+
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
2400
|
+
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
2401
|
+
return {};
|
|
2402
|
+
}
|
|
3181
2403
|
function buildTurnCollaborationMode(params, options = {}) {
|
|
3182
2404
|
const model = options.model ?? params.modelId;
|
|
3183
2405
|
return {
|
|
@@ -3192,7 +2414,7 @@ function buildTurnCollaborationMode(params, options = {}) {
|
|
|
3192
2414
|
function buildTurnScopedCollaborationInstructions(params, options = {}) {
|
|
3193
2415
|
const contextInstructions = joinPresentSections(options.turnScopedDeveloperInstructions, options.memoryCollaborationInstructions, options.skillsCollaborationInstructions);
|
|
3194
2416
|
if (params.trigger === "cron") return joinPresentSections(buildCronCollaborationInstructions(), contextInstructions);
|
|
3195
|
-
if (params.trigger === "heartbeat" && params.bootstrapContextRunKind !== "commitment-only") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions);
|
|
2417
|
+
if (params.trigger === "heartbeat" && params.bootstrapContextRunKind !== "commitment-only") return joinPresentSections(buildHeartbeatCollaborationInstructions(), contextInstructions, options.heartbeatCollaborationInstructions);
|
|
3196
2418
|
if (contextInstructions?.trim()) return joinPresentSections(buildDefaultCollaborationInstructions(), contextInstructions);
|
|
3197
2419
|
return null;
|
|
3198
2420
|
}
|
|
@@ -3223,11 +2445,149 @@ function buildHeartbeatCollaborationInstructions() {
|
|
|
3223
2445
|
return [
|
|
3224
2446
|
"This is an OpenClaw heartbeat turn. Apply these instructions only to this heartbeat wake; ordinary chat turns should stay in Codex Default mode.",
|
|
3225
2447
|
"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.",
|
|
3226
|
-
|
|
2448
|
+
CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY
|
|
3227
2449
|
].join("\n\n");
|
|
3228
2450
|
}
|
|
3229
2451
|
function joinPresentSections(...sections) {
|
|
3230
2452
|
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
3231
2453
|
}
|
|
2454
|
+
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
2455
|
+
return fingerprintDynamicTools(dynamicTools);
|
|
2456
|
+
}
|
|
2457
|
+
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
2458
|
+
return legacyFingerprintDynamicTools(dynamicTools);
|
|
2459
|
+
}
|
|
2460
|
+
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
2461
|
+
return areDynamicToolFingerprintsCompatible(params.previous, params.next, params.nextLegacy);
|
|
2462
|
+
}
|
|
2463
|
+
function fingerprintDynamicTools(dynamicTools) {
|
|
2464
|
+
return hashCodexAppServerBindingFingerprint(legacyFingerprintDynamicTools(dynamicTools));
|
|
2465
|
+
}
|
|
2466
|
+
function legacyFingerprintDynamicTools(dynamicTools) {
|
|
2467
|
+
return JSON.stringify(dynamicTools.map(fingerprintDynamicToolSpec).toSorted(compareJsonFingerprint));
|
|
2468
|
+
}
|
|
2469
|
+
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
2470
|
+
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
2471
|
+
}
|
|
2472
|
+
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
2473
|
+
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(configPatch))) : void 0;
|
|
2474
|
+
}
|
|
2475
|
+
function fingerprintJsonObject(value) {
|
|
2476
|
+
return JSON.stringify(stabilizeJsonValue(value));
|
|
2477
|
+
}
|
|
2478
|
+
function fingerprintEnvironmentSelection(environments) {
|
|
2479
|
+
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
2480
|
+
}
|
|
2481
|
+
function fingerprintDynamicToolSpec(tool) {
|
|
2482
|
+
return stabilizeDynamicToolFingerprintValue(tool);
|
|
2483
|
+
}
|
|
2484
|
+
function stabilizeDynamicToolFingerprintValue(value) {
|
|
2485
|
+
if (Array.isArray(value)) return value.map(stabilizeDynamicToolFingerprintValue);
|
|
2486
|
+
if (!isJsonObject(value)) return value;
|
|
2487
|
+
const stable = {};
|
|
2488
|
+
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
2489
|
+
if (key === "description") continue;
|
|
2490
|
+
stable[key] = stabilizeDynamicToolFingerprintValue(child);
|
|
2491
|
+
}
|
|
2492
|
+
return stable;
|
|
2493
|
+
}
|
|
2494
|
+
function stabilizeJsonValue(value) {
|
|
2495
|
+
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
2496
|
+
if (!isJsonObject(value)) return value;
|
|
2497
|
+
const stable = {};
|
|
2498
|
+
for (const [key, child] of Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right))) stable[key] = stabilizeJsonValue(child);
|
|
2499
|
+
return stable;
|
|
2500
|
+
}
|
|
2501
|
+
function readActiveCodexTurnIds(thread) {
|
|
2502
|
+
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
2503
|
+
}
|
|
2504
|
+
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = legacyFingerprintDynamicTools([]);
|
|
2505
|
+
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
2506
|
+
function areDynamicToolFingerprintsCompatible(previous, next, nextLegacy) {
|
|
2507
|
+
return !previous || previous === next || previous === nextLegacy;
|
|
2508
|
+
}
|
|
2509
|
+
function areUserMcpServersFingerprintsCompatible(params) {
|
|
2510
|
+
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
2511
|
+
}
|
|
2512
|
+
function shouldStartTransientNoToolThread(params) {
|
|
2513
|
+
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
2514
|
+
}
|
|
2515
|
+
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
2516
|
+
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
2517
|
+
}
|
|
2518
|
+
function compareJsonFingerprint(left, right) {
|
|
2519
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
2520
|
+
}
|
|
2521
|
+
function buildDeveloperInstructions(params, options = {}) {
|
|
2522
|
+
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
2523
|
+
surface: "codex_app_server",
|
|
2524
|
+
includeLegacyGlobalGuidance: false
|
|
2525
|
+
}).join("\n");
|
|
2526
|
+
return [
|
|
2527
|
+
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
2528
|
+
buildDeferredDynamicToolManifest(options.dynamicTools),
|
|
2529
|
+
buildSkillWorkshopInstruction(options.dynamicTools),
|
|
2530
|
+
"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`.",
|
|
2531
|
+
buildVisibleReplyInstruction(params, options.dynamicTools),
|
|
2532
|
+
nativeCommandGuidance,
|
|
2533
|
+
params.extraSystemPrompt
|
|
2534
|
+
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
2535
|
+
}
|
|
2536
|
+
function buildDeferredDynamicToolManifest(dynamicTools) {
|
|
2537
|
+
const deferredToolNames = [...new Set(flattenCodexDynamicToolFunctions(dynamicTools).filter((tool) => tool.deferLoading === true).map((tool) => tool.name.trim()).filter(Boolean))].toSorted((left, right) => left.localeCompare(right));
|
|
2538
|
+
if (deferredToolNames.length === 0) return;
|
|
2539
|
+
return `Deferred searchable OpenClaw dynamic tools available: ${deferredToolNames.join(", ")}. Use \`tool_search\` to load exact callable specs before use.`;
|
|
2540
|
+
}
|
|
2541
|
+
function buildSkillWorkshopInstruction(dynamicTools) {
|
|
2542
|
+
if (!flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === SKILL_WORKSHOP_TOOL_NAME)) return;
|
|
2543
|
+
return buildSkillWorkshopPromptSection().join("\n");
|
|
2544
|
+
}
|
|
2545
|
+
function buildVisibleReplyInstruction(params, dynamicTools) {
|
|
2546
|
+
const messageToolAvailable = dynamicTools ? flattenCodexDynamicToolFunctions(dynamicTools).some((tool) => tool.name.trim() === "message") : params.disableMessageTool !== true;
|
|
2547
|
+
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.";
|
|
2548
|
+
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.";
|
|
2549
|
+
return "For the current source conversation, reply normally in your final assistant message; OpenClaw will deliver it through the active source conversation.";
|
|
2550
|
+
}
|
|
2551
|
+
function buildUserInput(params, promptText = params.prompt) {
|
|
2552
|
+
const imageInputs = (params.images ?? []).map((image) => {
|
|
2553
|
+
const imageUrl = sanitizeInlineImageDataUrl$1(`data:${image.mimeType};base64,${image.data}`);
|
|
2554
|
+
return imageUrl ? {
|
|
2555
|
+
type: "image",
|
|
2556
|
+
url: imageUrl
|
|
2557
|
+
} : {
|
|
2558
|
+
type: "text",
|
|
2559
|
+
text: invalidInlineImageText("codex user input"),
|
|
2560
|
+
text_elements: []
|
|
2561
|
+
};
|
|
2562
|
+
});
|
|
2563
|
+
return [{
|
|
2564
|
+
type: "text",
|
|
2565
|
+
text: promptText,
|
|
2566
|
+
text_elements: []
|
|
2567
|
+
}, ...imageInputs];
|
|
2568
|
+
}
|
|
2569
|
+
function resolveCodexAppServerModelProvider(params) {
|
|
2570
|
+
const normalized = params.provider.trim();
|
|
2571
|
+
const normalizedLower = normalized.toLowerCase();
|
|
2572
|
+
if (!normalized || normalizedLower === "codex") return;
|
|
2573
|
+
if (isCodexAppServerNativeAuthProfile(params) && normalizedLower === "openai") return;
|
|
2574
|
+
return normalizedLower === "openai" ? "openai" : normalized;
|
|
2575
|
+
}
|
|
2576
|
+
function resolveReasoningEffort(thinkLevel, modelId, supportedReasoningEfforts) {
|
|
2577
|
+
if (thinkLevel === "off" || thinkLevel === "adaptive") return null;
|
|
2578
|
+
if (supportedReasoningEfforts) return resolveCodexSupportedReasoningEffort({
|
|
2579
|
+
requested: thinkLevel,
|
|
2580
|
+
supportedReasoningEfforts
|
|
2581
|
+
}) ?? null;
|
|
2582
|
+
const fallbackReasoningEfforts = resolveCodexFallbackReasoningEfforts(modelId);
|
|
2583
|
+
if (fallbackReasoningEfforts) return resolveCodexSupportedReasoningEffort({
|
|
2584
|
+
requested: thinkLevel,
|
|
2585
|
+
supportedReasoningEfforts: fallbackReasoningEfforts
|
|
2586
|
+
}) ?? null;
|
|
2587
|
+
if (thinkLevel === "minimal") return isModernCodexModel(modelId) ? "low" : "minimal";
|
|
2588
|
+
if (thinkLevel === "low" || thinkLevel === "medium" || thinkLevel === "high" || thinkLevel === "xhigh") return thinkLevel;
|
|
2589
|
+
if (thinkLevel === "max" && isMaxReasoningCodexModel(modelId)) return "max";
|
|
2590
|
+
return null;
|
|
2591
|
+
}
|
|
3232
2592
|
//#endregion
|
|
3233
|
-
export {
|
|
2593
|
+
export { resolveCodexContextEngineProjectionMaxChars as A, buildCodexPluginThreadConfig as B, filterCodexDynamicTools as C, resolveCodexDynamicToolsLoadingForRuntime as D, resolveCodexDynamicToolsLoading as E, closeCodexStartupClientBestEffort as F, pluginReadParams as G, mergeCodexThreadConfigs as H, interruptCodexTurnBestEffort as I, buildCodexAppInventoryCacheKey as K, retireCodexAppServerClientAfterTimedOutTurn as L, CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS as M, CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS as N, fitCodexProjectedContextForTurnStart as O, CodexAppServerUnsafeSubscriptionError as P, unsubscribeCodexThreadBestEffort as R, sanitizeInlineImageDataUrl$1 as S, normalizeCodexDynamicToolName as T, shouldBuildCodexPluginThreadConfig as U, buildCodexPluginThreadConfigInputFingerprint as V, ensureCodexPluginActivation as W, buildCodexNativeWebSearchThreadConfig as _, buildDeveloperInstructions as a, invalidInlineImageText as b, codexDynamicToolsFingerprint as c, resolveCodexAppServerModelProvider as d, resolveCodexAppServerRequestModelSelection as f, startOrResumeThread as g, resolveReasoningEffort as h, buildContextEngineBinding as i, resolveCodexContextEngineProjectionReserveTokens as j, projectContextEngineAssemblyForCodex as k, codexLegacyDynamicToolsFingerprint as l, resolveCodexBindingModelProviderFallback as m, areCodexDynamicToolFingerprintsCompatible as n, buildTurnCollaborationMode as o, resolveCodexAppServerThreadModelSelection as p, defaultCodexAppInventoryCache as q, buildCodexRuntimeThreadConfig as r, buildTurnStartParams as s, CODEX_NATIVE_PERSONALITY_NONE as t, isContextEngineBindingCompatible as u, resolveCodexWebSearchPlan as v, isForcedPrivateQaCodexRuntime as w, sanitizeCodexHistoryImagePayloads as x, isCodexAppServerProfilerEnabled as y, buildCodexPluginAppsConfigPatchFromPolicyContext as z };
|