@openclaw/codex 2026.5.20 → 2026.5.24-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{client-Ab4Fw77C.js → client-1sSy4p3z.js} +25 -16
- package/dist/{client-factory-BbesECdh.js → client-factory-Bk6i4FnW.js} +1 -1
- package/dist/{command-handlers-Gceg_rBZ.js → command-handlers-CcTABuem.js} +64 -7
- package/dist/{compact-BaSDurdH.js → compact-DnTgL6UT.js} +186 -40
- package/dist/{computer-use-B4iTog6z.js → computer-use-Hdq1WgTA.js} +2 -2
- package/dist/{config-DqIp6oTk.js → config-DDMrwfJl.js} +21 -2
- package/dist/dynamic-tools-Bq717oJR.js +497 -0
- package/dist/harness.js +15 -5
- package/dist/index.js +10 -9
- package/dist/media-understanding-provider.js +6 -3
- package/dist/{models-C8MdOXGD.js → models-DtGLkqMP.js} +1 -1
- package/dist/{node-cli-sessions-T1olB1SH.js → node-cli-sessions-9CAqnIaA.js} +23 -9
- package/dist/{command-formatters-BVBnEgyA.js → notification-correlation-qKY_sgga.js} +66 -15
- package/dist/provider-catalog.js +26 -41
- package/dist/provider.js +4 -4
- package/dist/{request-BePR77QD.js → request-D93E78SA.js} +12 -3
- package/dist/{run-attempt-CRHVB240.js → run-attempt-CT1N__qp.js} +3828 -1391
- package/dist/sandbox-guard-CTnEWuor.js +233 -0
- package/dist/{session-binding-DEiYHgJ_.js → session-binding-Bw_mfIW2.js} +4 -2
- package/dist/{shared-client-DCxJx5QU.js → shared-client-CFCUGEVs.js} +2 -2
- package/dist/{side-question-DlJAUYst.js → side-question-CYFMTA1O.js} +20 -15
- package/dist/test-api.js +4 -2
- package/dist/{thread-lifecycle-Cgi62SSl.js → thread-lifecycle-DGoaguJh.js} +628 -592
- package/dist/{vision-tools-B2wt6ecs.js → vision-tools-DOnxzH2y.js} +7 -3
- package/npm-shrinkwrap.json +1934 -0
- package/openclaw.plugin.json +29 -0
- package/package.json +8 -6
- package/dist/plugin-activation-BkQkPLOY.js +0 -452
|
@@ -1,593 +1,17 @@
|
|
|
1
|
-
import { r as codexSandboxPolicyForTurn,
|
|
1
|
+
import { d as resolveCodexPluginsPolicy, r as codexSandboxPolicyForTurn, t as CODEX_PLUGINS_MARKETPLACE_NAME } from "./config-DDMrwfJl.js";
|
|
2
2
|
import { n as assertCodexThreadResumeResponse, r as assertCodexThreadStartResponse } from "./protocol-validators-_gKDcd0x.js";
|
|
3
3
|
import { t as isJsonObject } from "./protocol-oeJQu4rs.js";
|
|
4
4
|
import { CODEX_GPT5_HEARTBEAT_PROMPT_OVERLAY } from "./prompt-overlay.js";
|
|
5
5
|
import { isModernCodexModel } from "./provider.js";
|
|
6
|
-
import {
|
|
7
|
-
import { i as readCodexAppServerBinding, n as isCodexAppServerNativeAuthProfile, o as writeCodexAppServerBinding, t as clearCodexAppServerBinding } from "./session-binding-
|
|
8
|
-
import { a as defaultCodexAppInventoryCache, o as serializeCodexAppInventoryError, r as readCodexPluginInventory, t as ensureCodexPluginActivation } from "./plugin-activation-BkQkPLOY.js";
|
|
6
|
+
import { a as isCodexAppServerConnectionClosedError } from "./client-1sSy4p3z.js";
|
|
7
|
+
import { i as readCodexAppServerBinding, n as isCodexAppServerNativeAuthProfile, o as writeCodexAppServerBinding, t as clearCodexAppServerBinding } from "./session-binding-Bw_mfIW2.js";
|
|
9
8
|
import crypto from "node:crypto";
|
|
10
|
-
import {
|
|
11
|
-
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
9
|
+
import { embeddedAgentLog, isActiveHarnessContextEngine } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
12
10
|
import { buildCodexUserMcpServersThreadConfigPatch } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
13
11
|
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
14
12
|
import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"read",
|
|
18
|
-
"write",
|
|
19
|
-
"edit",
|
|
20
|
-
"apply_patch",
|
|
21
|
-
"exec",
|
|
22
|
-
"process",
|
|
23
|
-
"update_plan",
|
|
24
|
-
"tool_call",
|
|
25
|
-
"tool_describe",
|
|
26
|
-
"tool_search",
|
|
27
|
-
"tool_search_code"
|
|
28
|
-
];
|
|
29
|
-
const DYNAMIC_TOOL_NAME_ALIASES = {
|
|
30
|
-
bash: "exec",
|
|
31
|
-
"apply-patch": "apply_patch"
|
|
32
|
-
};
|
|
33
|
-
function normalizeCodexDynamicToolName(name) {
|
|
34
|
-
const normalized = name.trim().toLowerCase();
|
|
35
|
-
return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
|
|
36
|
-
}
|
|
37
|
-
function isForcedPrivateQaCodexRuntime(env = process.env) {
|
|
38
|
-
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
39
|
-
}
|
|
40
|
-
function resolveCodexDynamicToolsLoading(config, env = process.env) {
|
|
41
|
-
return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
|
|
42
|
-
}
|
|
43
|
-
function filterCodexDynamicTools(tools, config, env = process.env) {
|
|
44
|
-
const excludes = /* @__PURE__ */ new Set();
|
|
45
|
-
if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) excludes.add(name);
|
|
46
|
-
for (const name of config.codexDynamicToolsExclude ?? []) {
|
|
47
|
-
const trimmed = normalizeCodexDynamicToolName(name);
|
|
48
|
-
if (trimmed) excludes.add(trimmed);
|
|
49
|
-
}
|
|
50
|
-
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region extensions/codex/src/app-server/image-payload-sanitizer.ts
|
|
54
|
-
const DATA_URL_PREFIX = "data:";
|
|
55
|
-
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
56
|
-
const IMAGE_SIGNATURES = [
|
|
57
|
-
{
|
|
58
|
-
mime: "image/png",
|
|
59
|
-
matches: (buffer) => buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
mime: "image/jpeg",
|
|
63
|
-
matches: (buffer) => buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
mime: "image/webp",
|
|
67
|
-
matches: (buffer) => buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
mime: "image/gif",
|
|
71
|
-
matches: (buffer) => buffer.length >= 6 && (buffer.subarray(0, 6).toString("ascii") === "GIF87a" || buffer.subarray(0, 6).toString("ascii") === "GIF89a")
|
|
72
|
-
}
|
|
73
|
-
];
|
|
74
|
-
function startsWithDataUrl(value) {
|
|
75
|
-
return value.slice(0, 5).toLowerCase() === DATA_URL_PREFIX;
|
|
76
|
-
}
|
|
77
|
-
function canonicalizeBase64(base64) {
|
|
78
|
-
let cleaned = "";
|
|
79
|
-
let padding = 0;
|
|
80
|
-
let sawPadding = false;
|
|
81
|
-
for (let i = 0; i < base64.length; i += 1) {
|
|
82
|
-
const code = base64.charCodeAt(i);
|
|
83
|
-
if (code <= 32) continue;
|
|
84
|
-
if (code === 61) {
|
|
85
|
-
padding += 1;
|
|
86
|
-
if (padding > 2) return;
|
|
87
|
-
sawPadding = true;
|
|
88
|
-
cleaned += "=";
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (sawPadding || !(code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47)) return;
|
|
92
|
-
cleaned += base64[i];
|
|
93
|
-
}
|
|
94
|
-
if (!cleaned || cleaned.length % 4 !== 0) return;
|
|
95
|
-
return cleaned;
|
|
96
|
-
}
|
|
97
|
-
function sniffImageMime(buffer) {
|
|
98
|
-
return IMAGE_SIGNATURES.find((signature) => signature.matches(buffer))?.mime;
|
|
99
|
-
}
|
|
100
|
-
function parseImageDataUrl(value) {
|
|
101
|
-
if (!startsWithDataUrl(value)) return {
|
|
102
|
-
metadata: [],
|
|
103
|
-
payload: value
|
|
104
|
-
};
|
|
105
|
-
const commaIndex = value.indexOf(",");
|
|
106
|
-
if (commaIndex < 0) return;
|
|
107
|
-
return {
|
|
108
|
-
metadata: value.slice(5, commaIndex).split(";").map((part) => part.trim()),
|
|
109
|
-
payload: value.slice(commaIndex + 1)
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function metadataAllowsImageBase64(metadata) {
|
|
113
|
-
const [mimeType, ...options] = metadata;
|
|
114
|
-
return mimeType !== void 0 && mimeType.toLowerCase().startsWith("image/") && options.some((part) => part.toLowerCase() === "base64");
|
|
115
|
-
}
|
|
116
|
-
function sanitizeInlineImageDataUrl(imageUrl) {
|
|
117
|
-
const parsed = parseImageDataUrl(imageUrl);
|
|
118
|
-
if (!parsed) return;
|
|
119
|
-
if (parsed.metadata.length === 0) return imageUrl;
|
|
120
|
-
if (!metadataAllowsImageBase64(parsed.metadata)) return;
|
|
121
|
-
const canonicalPayload = canonicalizeBase64(parsed.payload);
|
|
122
|
-
if (!canonicalPayload) return;
|
|
123
|
-
const sniffedMimeType = sniffImageMime(Buffer.from(canonicalPayload, "base64"));
|
|
124
|
-
if (!sniffedMimeType) return;
|
|
125
|
-
return `data:${sniffedMimeType};base64,${canonicalPayload}`;
|
|
126
|
-
}
|
|
127
|
-
function invalidInlineImageText(label) {
|
|
128
|
-
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
129
|
-
}
|
|
130
|
-
function isRecord$1(value) {
|
|
131
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
132
|
-
}
|
|
133
|
-
function sanitizeImageContentRecord(record, label) {
|
|
134
|
-
if (record.type === "image" && typeof record.data === "string") {
|
|
135
|
-
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
|
|
136
|
-
const imageUrl = sanitizeInlineImageDataUrl(`data:${mimeType};base64,${record.data}`);
|
|
137
|
-
if (!imageUrl) return {
|
|
138
|
-
type: "text",
|
|
139
|
-
text: invalidInlineImageText(label)
|
|
140
|
-
};
|
|
141
|
-
const commaIndex = imageUrl.indexOf(",");
|
|
142
|
-
const mime = imageUrl.slice(5, commaIndex).split(";")[0] ?? mimeType;
|
|
143
|
-
return {
|
|
144
|
-
...record,
|
|
145
|
-
mimeType: mime,
|
|
146
|
-
data: imageUrl.slice(commaIndex + 1)
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
150
|
-
const imageUrl = sanitizeInlineImageDataUrl(record.imageUrl);
|
|
151
|
-
return imageUrl ? {
|
|
152
|
-
...record,
|
|
153
|
-
imageUrl
|
|
154
|
-
} : {
|
|
155
|
-
type: "inputText",
|
|
156
|
-
text: invalidInlineImageText(label)
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
160
|
-
const imageUrl = sanitizeInlineImageDataUrl(record.image_url);
|
|
161
|
-
return imageUrl ? {
|
|
162
|
-
...record,
|
|
163
|
-
image_url: imageUrl
|
|
164
|
-
} : {
|
|
165
|
-
type: "input_text",
|
|
166
|
-
text: invalidInlineImageText(label)
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
function sanitizeCodexHistoryImagePayloads(value, label) {
|
|
171
|
-
if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
|
|
172
|
-
if (!isRecord$1(value)) return value;
|
|
173
|
-
const imageRecord = sanitizeImageContentRecord(value, label);
|
|
174
|
-
if (imageRecord) return imageRecord;
|
|
175
|
-
const next = {};
|
|
176
|
-
for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
|
|
177
|
-
return next;
|
|
178
|
-
}
|
|
179
|
-
//#endregion
|
|
180
|
-
//#region extensions/codex/src/app-server/dynamic-tools.ts
|
|
181
|
-
const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
|
|
182
|
-
const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = new Set(["sessions_yield"]);
|
|
183
|
-
const DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS = 16e3;
|
|
184
|
-
function createCodexDynamicToolBridge(params) {
|
|
185
|
-
const toolResultHookContext = toToolResultHookContext(params.hookContext);
|
|
186
|
-
const toolResultMaxChars = resolveCodexDynamicToolResultMaxChars(params.hookContext);
|
|
187
|
-
const tools = params.tools.map((tool) => {
|
|
188
|
-
if (isToolWrappedWithBeforeToolCallHook(tool)) {
|
|
189
|
-
setBeforeToolCallDiagnosticsEnabled(tool, false);
|
|
190
|
-
return tool;
|
|
191
|
-
}
|
|
192
|
-
return wrapToolWithBeforeToolCallHook(tool, params.hookContext, { emitDiagnostics: false });
|
|
193
|
-
});
|
|
194
|
-
const toolMap = new Map(tools.map((tool) => [tool.name, tool]));
|
|
195
|
-
const telemetry = {
|
|
196
|
-
didSendViaMessagingTool: false,
|
|
197
|
-
messagingToolSentTexts: [],
|
|
198
|
-
messagingToolSentMediaUrls: [],
|
|
199
|
-
messagingToolSentTargets: [],
|
|
200
|
-
messagingToolSourceReplyPayloads: [],
|
|
201
|
-
toolMediaUrls: [],
|
|
202
|
-
toolAudioAsVoice: false
|
|
203
|
-
};
|
|
204
|
-
const middlewareRunner = createAgentToolResultMiddlewareRunner({
|
|
205
|
-
runtime: "codex",
|
|
206
|
-
...toolResultHookContext
|
|
207
|
-
});
|
|
208
|
-
const legacyExtensionRunner = createCodexAppServerToolResultExtensionRunner(toolResultHookContext);
|
|
209
|
-
const directToolNames = new Set([...ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES, ...params.directToolNames ?? []]);
|
|
210
|
-
return {
|
|
211
|
-
specs: tools.map((tool) => createCodexDynamicToolSpec({
|
|
212
|
-
tool,
|
|
213
|
-
loading: params.loading ?? "searchable",
|
|
214
|
-
directToolNames
|
|
215
|
-
})),
|
|
216
|
-
telemetry,
|
|
217
|
-
handleToolCall: async (call, options) => {
|
|
218
|
-
const tool = toolMap.get(call.tool);
|
|
219
|
-
if (!tool) return {
|
|
220
|
-
contentItems: [{
|
|
221
|
-
type: "inputText",
|
|
222
|
-
text: `Unknown OpenClaw tool: ${call.tool}`
|
|
223
|
-
}],
|
|
224
|
-
success: false
|
|
225
|
-
};
|
|
226
|
-
const args = jsonObjectToRecord(call.arguments);
|
|
227
|
-
const startedAt = Date.now();
|
|
228
|
-
const signal = composeAbortSignals(params.signal, options?.signal);
|
|
229
|
-
try {
|
|
230
|
-
const preparedArgs = tool.prepareArguments ? tool.prepareArguments(args) : args;
|
|
231
|
-
const rawResult = await tool.execute(call.callId, preparedArgs, signal);
|
|
232
|
-
const rawIsError = isToolResultError(rawResult);
|
|
233
|
-
const middlewareResult = await middlewareRunner.applyToolResultMiddleware({
|
|
234
|
-
threadId: call.threadId,
|
|
235
|
-
turnId: call.turnId,
|
|
236
|
-
toolCallId: call.callId,
|
|
237
|
-
toolName: tool.name,
|
|
238
|
-
args,
|
|
239
|
-
isError: rawIsError,
|
|
240
|
-
result: rawResult
|
|
241
|
-
});
|
|
242
|
-
const result = await legacyExtensionRunner.applyToolResultExtensions({
|
|
243
|
-
threadId: call.threadId,
|
|
244
|
-
turnId: call.turnId,
|
|
245
|
-
toolCallId: call.callId,
|
|
246
|
-
toolName: tool.name,
|
|
247
|
-
args,
|
|
248
|
-
result: middlewareResult
|
|
249
|
-
});
|
|
250
|
-
const resultIsError = rawIsError || isToolResultError(result);
|
|
251
|
-
collectToolTelemetry({
|
|
252
|
-
toolName: tool.name,
|
|
253
|
-
args,
|
|
254
|
-
result,
|
|
255
|
-
mediaTrustResult: rawResult,
|
|
256
|
-
telemetry,
|
|
257
|
-
isError: resultIsError
|
|
258
|
-
});
|
|
259
|
-
runAgentHarnessAfterToolCallHook({
|
|
260
|
-
toolName: tool.name,
|
|
261
|
-
toolCallId: call.callId,
|
|
262
|
-
runId: toolResultHookContext.runId,
|
|
263
|
-
agentId: toolResultHookContext.agentId,
|
|
264
|
-
sessionId: toolResultHookContext.sessionId,
|
|
265
|
-
sessionKey: toolResultHookContext.sessionKey,
|
|
266
|
-
channelId: toolResultHookContext.channelId,
|
|
267
|
-
startArgs: args,
|
|
268
|
-
result,
|
|
269
|
-
startedAt
|
|
270
|
-
});
|
|
271
|
-
return withDiagnosticTerminalType({
|
|
272
|
-
contentItems: convertToolContents(result.content, toolResultMaxChars),
|
|
273
|
-
success: !resultIsError
|
|
274
|
-
}, inferToolResultDiagnosticTerminalType(result, resultIsError));
|
|
275
|
-
} catch (error) {
|
|
276
|
-
collectToolTelemetry({
|
|
277
|
-
toolName: tool.name,
|
|
278
|
-
args,
|
|
279
|
-
result: void 0,
|
|
280
|
-
telemetry,
|
|
281
|
-
isError: true
|
|
282
|
-
});
|
|
283
|
-
runAgentHarnessAfterToolCallHook({
|
|
284
|
-
toolName: tool.name,
|
|
285
|
-
toolCallId: call.callId,
|
|
286
|
-
runId: toolResultHookContext.runId,
|
|
287
|
-
agentId: toolResultHookContext.agentId,
|
|
288
|
-
sessionId: toolResultHookContext.sessionId,
|
|
289
|
-
sessionKey: toolResultHookContext.sessionKey,
|
|
290
|
-
channelId: toolResultHookContext.channelId,
|
|
291
|
-
startArgs: args,
|
|
292
|
-
error: error instanceof Error ? error.message : String(error),
|
|
293
|
-
startedAt
|
|
294
|
-
});
|
|
295
|
-
return withDiagnosticTerminalType({
|
|
296
|
-
contentItems: [{
|
|
297
|
-
type: "inputText",
|
|
298
|
-
text: error instanceof Error ? error.message : String(error)
|
|
299
|
-
}],
|
|
300
|
-
success: false
|
|
301
|
-
}, "error");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
function createCodexDynamicToolSpec(params) {
|
|
307
|
-
const base = {
|
|
308
|
-
name: params.tool.name,
|
|
309
|
-
description: params.tool.description,
|
|
310
|
-
inputSchema: toJsonValue(params.tool.parameters)
|
|
311
|
-
};
|
|
312
|
-
if (params.loading === "direct" || params.directToolNames.has(params.tool.name)) return base;
|
|
313
|
-
return {
|
|
314
|
-
...base,
|
|
315
|
-
namespace: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
|
|
316
|
-
deferLoading: true
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
function toToolResultHookContext(ctx) {
|
|
320
|
-
const { agentId, sessionId, sessionKey, runId, channelId } = ctx ?? {};
|
|
321
|
-
return {
|
|
322
|
-
...agentId && { agentId },
|
|
323
|
-
...sessionId && { sessionId },
|
|
324
|
-
...sessionKey && { sessionKey },
|
|
325
|
-
...runId && { runId },
|
|
326
|
-
...channelId && { channelId }
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
function resolveCodexDynamicToolResultMaxChars(ctx) {
|
|
330
|
-
return resolveAgentContextLimitValue({
|
|
331
|
-
config: ctx?.config,
|
|
332
|
-
agentId: ctx?.agentId,
|
|
333
|
-
key: "toolResultMaxChars"
|
|
334
|
-
}) ?? DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS;
|
|
335
|
-
}
|
|
336
|
-
function resolveAgentContextLimitValue(params) {
|
|
337
|
-
const agents = readRecord(params.config?.agents);
|
|
338
|
-
const defaultValue = readPositiveInteger(readRecord(readRecord(agents?.defaults)?.contextLimits)?.[params.key]);
|
|
339
|
-
if (!params.agentId) return defaultValue;
|
|
340
|
-
const list = agents?.list;
|
|
341
|
-
if (!Array.isArray(list)) return defaultValue;
|
|
342
|
-
const normalizedAgentId = normalizeAgentId(params.agentId);
|
|
343
|
-
return readPositiveInteger(readRecord(readRecord(list.find((entry) => {
|
|
344
|
-
const entryId = readRecord(entry)?.id;
|
|
345
|
-
return typeof entryId === "string" && normalizeAgentId(entryId) === normalizedAgentId;
|
|
346
|
-
}))?.contextLimits)?.[params.key]) ?? defaultValue;
|
|
347
|
-
}
|
|
348
|
-
function composeAbortSignals(...signals) {
|
|
349
|
-
const activeSignals = signals.filter((signal) => Boolean(signal));
|
|
350
|
-
if (activeSignals.length === 0) return new AbortController().signal;
|
|
351
|
-
if (activeSignals.length === 1) return activeSignals[0];
|
|
352
|
-
return AbortSignal.any(activeSignals);
|
|
353
|
-
}
|
|
354
|
-
function collectToolTelemetry(params) {
|
|
355
|
-
if (params.isError) return;
|
|
356
|
-
if (!params.isError && params.toolName === "cron" && isCronAddAction(params.args)) params.telemetry.successfulCronAdds = (params.telemetry.successfulCronAdds ?? 0) + 1;
|
|
357
|
-
if (!params.isError && params.toolName === HEARTBEAT_RESPONSE_TOOL_NAME) {
|
|
358
|
-
const response = normalizeHeartbeatToolResponse(params.result?.details);
|
|
359
|
-
if (response) params.telemetry.heartbeatToolResponse = response;
|
|
360
|
-
}
|
|
361
|
-
if (!params.isError && params.result) {
|
|
362
|
-
const media = extractToolResultMediaArtifact(params.result);
|
|
363
|
-
if (media) {
|
|
364
|
-
const mediaUrls = filterToolResultMediaUrls(params.toolName, media.mediaUrls, params.mediaTrustResult ?? params.result);
|
|
365
|
-
const seen = new Set(params.telemetry.toolMediaUrls);
|
|
366
|
-
for (const mediaUrl of mediaUrls) if (!seen.has(mediaUrl)) {
|
|
367
|
-
seen.add(mediaUrl);
|
|
368
|
-
params.telemetry.toolMediaUrls.push(mediaUrl);
|
|
369
|
-
}
|
|
370
|
-
if (media.audioAsVoice) params.telemetry.toolAudioAsVoice = true;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
if (!isMessagingTool(params.toolName) || !isMessagingToolSendAction(params.toolName, params.args)) return;
|
|
374
|
-
params.telemetry.didSendViaMessagingTool = true;
|
|
375
|
-
const sourceReplyPayload = extractInternalSourceReplyPayload(params.result?.details);
|
|
376
|
-
if (sourceReplyPayload) {
|
|
377
|
-
params.telemetry.messagingToolSourceReplyPayloads.push(sourceReplyPayload);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
const text = readFirstString(params.args, [
|
|
381
|
-
"text",
|
|
382
|
-
"message",
|
|
383
|
-
"body",
|
|
384
|
-
"content"
|
|
385
|
-
]);
|
|
386
|
-
if (text) params.telemetry.messagingToolSentTexts.push(text);
|
|
387
|
-
const mediaUrls = collectMediaUrls(params.args);
|
|
388
|
-
params.telemetry.messagingToolSentMediaUrls.push(...mediaUrls);
|
|
389
|
-
params.telemetry.messagingToolSentTargets.push({
|
|
390
|
-
tool: params.toolName,
|
|
391
|
-
provider: readFirstString(params.args, ["provider", "channel"]) ?? params.toolName,
|
|
392
|
-
accountId: readFirstString(params.args, ["accountId", "account_id"]),
|
|
393
|
-
to: readFirstString(params.args, [
|
|
394
|
-
"to",
|
|
395
|
-
"target",
|
|
396
|
-
"recipient"
|
|
397
|
-
]),
|
|
398
|
-
threadId: readFirstString(params.args, [
|
|
399
|
-
"threadId",
|
|
400
|
-
"thread_id",
|
|
401
|
-
"messageThreadId"
|
|
402
|
-
]),
|
|
403
|
-
...text ? { text } : {},
|
|
404
|
-
...mediaUrls.length > 0 ? { mediaUrls } : {}
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
function extractInternalSourceReplyPayload(details) {
|
|
408
|
-
if (!isRecord(details) || details.sourceReplySink !== "internal-ui") return;
|
|
409
|
-
const rawPayload = details.sourceReply;
|
|
410
|
-
if (!isRecord(rawPayload)) return;
|
|
411
|
-
const text = readFirstString(rawPayload, ["text", "message"]);
|
|
412
|
-
const mediaUrls = collectMediaUrls(rawPayload);
|
|
413
|
-
const mediaUrl = typeof rawPayload.mediaUrl === "string" && rawPayload.mediaUrl.trim() ? rawPayload.mediaUrl.trim() : mediaUrls[0];
|
|
414
|
-
const payload = {
|
|
415
|
-
...text ? { text } : {},
|
|
416
|
-
...mediaUrl ? { mediaUrl } : {},
|
|
417
|
-
...mediaUrls.length > 0 ? { mediaUrls } : {},
|
|
418
|
-
...rawPayload.audioAsVoice === true ? { audioAsVoice: true } : {},
|
|
419
|
-
...isRecord(rawPayload.presentation) ? { presentation: rawPayload.presentation } : {},
|
|
420
|
-
...isRecord(rawPayload.interactive) ? { interactive: rawPayload.interactive } : {},
|
|
421
|
-
...isRecord(rawPayload.channelData) ? { channelData: rawPayload.channelData } : {},
|
|
422
|
-
...typeof details.idempotencyKey === "string" && details.idempotencyKey.trim() ? { idempotencyKey: details.idempotencyKey.trim() } : {}
|
|
423
|
-
};
|
|
424
|
-
return text || mediaUrls.length > 0 || payload.presentation || payload.interactive ? payload : void 0;
|
|
425
|
-
}
|
|
426
|
-
function isRecord(value) {
|
|
427
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
428
|
-
}
|
|
429
|
-
function readRecord(value) {
|
|
430
|
-
return isRecord(value) ? value : void 0;
|
|
431
|
-
}
|
|
432
|
-
function readPositiveInteger(value) {
|
|
433
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return;
|
|
434
|
-
return Math.floor(value);
|
|
435
|
-
}
|
|
436
|
-
function isToolResultError(result) {
|
|
437
|
-
const details = result.details;
|
|
438
|
-
if (!isRecord(details)) return false;
|
|
439
|
-
if (details.timedOut === true) return true;
|
|
440
|
-
if (typeof details.exitCode === "number" && details.exitCode !== 0) return true;
|
|
441
|
-
if (typeof details.status !== "string") return false;
|
|
442
|
-
const status = details.status.trim().toLowerCase();
|
|
443
|
-
return status !== "" && status !== "0" && status !== "ok" && status !== "success" && status !== "completed" && status !== "recorded" && status !== "running";
|
|
444
|
-
}
|
|
445
|
-
function inferToolResultDiagnosticTerminalType(result, isError) {
|
|
446
|
-
const details = result.details;
|
|
447
|
-
if (isRecord(details) && typeof details.status === "string") {
|
|
448
|
-
if (details.status.trim().toLowerCase() === "blocked") return "blocked";
|
|
449
|
-
}
|
|
450
|
-
return isError ? "error" : "completed";
|
|
451
|
-
}
|
|
452
|
-
function withDiagnosticTerminalType(response, terminalType) {
|
|
453
|
-
Object.defineProperty(response, "diagnosticTerminalType", {
|
|
454
|
-
configurable: true,
|
|
455
|
-
enumerable: false,
|
|
456
|
-
value: terminalType
|
|
457
|
-
});
|
|
458
|
-
return response;
|
|
459
|
-
}
|
|
460
|
-
function normalizeToolResultMaxChars(maxChars) {
|
|
461
|
-
return typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS;
|
|
462
|
-
}
|
|
463
|
-
function convertToolContents(content, toolResultMaxChars = DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS) {
|
|
464
|
-
const maxChars = normalizeToolResultMaxChars(toolResultMaxChars);
|
|
465
|
-
const totalTextChars = content.reduce((total, item) => total + (item.type === "text" ? item.text.length : 0), 0);
|
|
466
|
-
if (totalTextChars <= maxChars) return content.flatMap(convertToolContent);
|
|
467
|
-
const noticeText = `...(OpenClaw truncated dynamic tool result: original ${totalTextChars} chars, showing ${maxChars}; rerun with narrower args.)`;
|
|
468
|
-
const notice = `\n${noticeText}`;
|
|
469
|
-
let remainingTextBudget = Math.max(0, maxChars - notice.length);
|
|
470
|
-
let appendedNotice = false;
|
|
471
|
-
const output = [];
|
|
472
|
-
for (const item of content) {
|
|
473
|
-
if (item.type !== "text") {
|
|
474
|
-
output.push(...convertToolContent(item));
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
if (appendedNotice) continue;
|
|
478
|
-
if (notice.length >= maxChars) {
|
|
479
|
-
output.push({
|
|
480
|
-
type: "inputText",
|
|
481
|
-
text: noticeText.slice(0, maxChars)
|
|
482
|
-
});
|
|
483
|
-
appendedNotice = true;
|
|
484
|
-
continue;
|
|
485
|
-
}
|
|
486
|
-
const sliceLength = Math.min(item.text.length, remainingTextBudget);
|
|
487
|
-
remainingTextBudget -= sliceLength;
|
|
488
|
-
const shouldAppendNotice = remainingTextBudget <= 0;
|
|
489
|
-
const text = item.text.slice(0, sliceLength);
|
|
490
|
-
if (shouldAppendNotice) {
|
|
491
|
-
output.push({
|
|
492
|
-
type: "inputText",
|
|
493
|
-
text: `${text.trimEnd()}${notice}`.slice(0, maxChars)
|
|
494
|
-
});
|
|
495
|
-
appendedNotice = true;
|
|
496
|
-
} else if (text.length > 0) output.push({
|
|
497
|
-
type: "inputText",
|
|
498
|
-
text
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
if (!appendedNotice) output.push({
|
|
502
|
-
type: "inputText",
|
|
503
|
-
text: noticeText.slice(0, maxChars)
|
|
504
|
-
});
|
|
505
|
-
return output;
|
|
506
|
-
}
|
|
507
|
-
function convertToolContent(content) {
|
|
508
|
-
if (content.type === "text") return [{
|
|
509
|
-
type: "inputText",
|
|
510
|
-
text: content.text
|
|
511
|
-
}];
|
|
512
|
-
const imageUrl = sanitizeInlineImageDataUrl(`data:${content.mimeType};base64,${content.data}`);
|
|
513
|
-
if (!imageUrl) return [{
|
|
514
|
-
type: "inputText",
|
|
515
|
-
text: invalidInlineImageText("codex dynamic tool")
|
|
516
|
-
}];
|
|
517
|
-
return [{
|
|
518
|
-
type: "inputImage",
|
|
519
|
-
imageUrl
|
|
520
|
-
}];
|
|
521
|
-
}
|
|
522
|
-
function toJsonValue(value) {
|
|
523
|
-
try {
|
|
524
|
-
const text = JSON.stringify(value);
|
|
525
|
-
if (!text) return {};
|
|
526
|
-
return JSON.parse(text);
|
|
527
|
-
} catch {
|
|
528
|
-
return {};
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
function jsonObjectToRecord(value) {
|
|
532
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
533
|
-
return value;
|
|
534
|
-
}
|
|
535
|
-
function readFirstString(record, keys) {
|
|
536
|
-
for (const key of keys) {
|
|
537
|
-
const value = record[key];
|
|
538
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
539
|
-
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
function collectMediaUrls(record) {
|
|
543
|
-
const urls = [];
|
|
544
|
-
const pushMediaUrl = (value) => {
|
|
545
|
-
if (typeof value === "string" && value.trim()) urls.push(value.trim());
|
|
546
|
-
};
|
|
547
|
-
const pushAttachment = (value) => {
|
|
548
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
549
|
-
const attachment = value;
|
|
550
|
-
for (const key of [
|
|
551
|
-
"media",
|
|
552
|
-
"mediaUrl",
|
|
553
|
-
"path",
|
|
554
|
-
"filePath",
|
|
555
|
-
"fileUrl",
|
|
556
|
-
"url"
|
|
557
|
-
]) pushMediaUrl(attachment[key]);
|
|
558
|
-
};
|
|
559
|
-
for (const key of [
|
|
560
|
-
"media",
|
|
561
|
-
"mediaUrl",
|
|
562
|
-
"media_url",
|
|
563
|
-
"path",
|
|
564
|
-
"filePath",
|
|
565
|
-
"fileUrl",
|
|
566
|
-
"imageUrl",
|
|
567
|
-
"image_url"
|
|
568
|
-
]) {
|
|
569
|
-
const value = record[key];
|
|
570
|
-
pushMediaUrl(value);
|
|
571
|
-
}
|
|
572
|
-
for (const key of [
|
|
573
|
-
"mediaUrls",
|
|
574
|
-
"media_urls",
|
|
575
|
-
"imageUrls",
|
|
576
|
-
"image_urls"
|
|
577
|
-
]) {
|
|
578
|
-
const value = record[key];
|
|
579
|
-
if (!Array.isArray(value)) continue;
|
|
580
|
-
for (const entry of value) pushMediaUrl(entry);
|
|
581
|
-
}
|
|
582
|
-
const attachments = record.attachments;
|
|
583
|
-
if (Array.isArray(attachments)) for (const attachment of attachments) pushAttachment(attachment);
|
|
584
|
-
return urls;
|
|
585
|
-
}
|
|
586
|
-
function isCronAddAction(args) {
|
|
587
|
-
const action = args.action;
|
|
588
|
-
return typeof action === "string" && action.trim().toLowerCase() === "add";
|
|
589
|
-
}
|
|
590
|
-
//#endregion
|
|
13
|
+
import "node:fs/promises";
|
|
14
|
+
import "node:path";
|
|
591
15
|
//#region extensions/codex/src/app-server/context-engine-projection.ts
|
|
592
16
|
const CONTEXT_HEADER = "OpenClaw assembled context for this turn:";
|
|
593
17
|
const CONTEXT_OPEN = "<conversation_context>";
|
|
@@ -599,7 +23,7 @@ const MAX_RENDERED_CONTEXT_CHARS = 1e6;
|
|
|
599
23
|
const DEFAULT_TEXT_PART_CHARS = 6e3;
|
|
600
24
|
const MAX_TEXT_PART_CHARS = 128e3;
|
|
601
25
|
const APPROX_RENDERED_CHARS_PER_TOKEN = 4;
|
|
602
|
-
const
|
|
26
|
+
const DEFAULT_CODEX_PROJECTION_RESERVE_TOKENS = 2e4;
|
|
603
27
|
const MIN_PROMPT_BUDGET_RATIO = .5;
|
|
604
28
|
const MIN_PROMPT_BUDGET_TOKENS = 8e3;
|
|
605
29
|
/**
|
|
@@ -643,11 +67,11 @@ function resolveCodexContextEngineProjectionReserveTokens(params) {
|
|
|
643
67
|
const compaction = asRecord(asRecord(asRecord(params.config)?.agents)?.defaults)?.compaction;
|
|
644
68
|
const configuredReserveTokens = toNonNegativeInt(asRecord(compaction)?.reserveTokens);
|
|
645
69
|
const configuredReserveTokensFloor = toNonNegativeInt(asRecord(compaction)?.reserveTokensFloor);
|
|
646
|
-
if (configuredReserveTokens !== void 0) return Math.max(configuredReserveTokens, configuredReserveTokensFloor ??
|
|
70
|
+
if (configuredReserveTokens !== void 0) return Math.max(configuredReserveTokens, configuredReserveTokensFloor ?? 2e4);
|
|
647
71
|
if (configuredReserveTokensFloor !== void 0) return configuredReserveTokensFloor;
|
|
648
72
|
}
|
|
649
73
|
function resolveProjectionPromptBudgetTokens(params) {
|
|
650
|
-
const requestedReserveTokens = typeof params.reserveTokens === "number" && Number.isFinite(params.reserveTokens) && params.reserveTokens >= 0 ? Math.floor(params.reserveTokens) :
|
|
74
|
+
const requestedReserveTokens = typeof params.reserveTokens === "number" && Number.isFinite(params.reserveTokens) && params.reserveTokens >= 0 ? Math.floor(params.reserveTokens) : DEFAULT_CODEX_PROJECTION_RESERVE_TOKENS;
|
|
651
75
|
const minPromptBudget = Math.min(MIN_PROMPT_BUDGET_TOKENS, Math.max(1, Math.floor(params.contextTokenBudget * MIN_PROMPT_BUDGET_RATIO)));
|
|
652
76
|
const effectiveReserveTokens = Math.min(requestedReserveTokens, Math.max(0, params.contextTokenBudget - minPromptBudget));
|
|
653
77
|
return Math.max(1, params.contextTokenBudget - effectiveReserveTokens);
|
|
@@ -800,6 +224,579 @@ function truncateOlderContext(text, maxChars) {
|
|
|
800
224
|
return `${marker}${text.slice(text.length - tailChars).trimStart()}`;
|
|
801
225
|
}
|
|
802
226
|
//#endregion
|
|
227
|
+
//#region extensions/codex/src/app-server/image-payload-sanitizer.ts
|
|
228
|
+
const DATA_URL_PREFIX = "data:";
|
|
229
|
+
const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
|
|
230
|
+
const IMAGE_SIGNATURES = [
|
|
231
|
+
{
|
|
232
|
+
mime: "image/png",
|
|
233
|
+
matches: (buffer) => buffer.length >= 8 && buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71 && buffer[4] === 13 && buffer[5] === 10 && buffer[6] === 26 && buffer[7] === 10
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
mime: "image/jpeg",
|
|
237
|
+
matches: (buffer) => buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
mime: "image/webp",
|
|
241
|
+
matches: (buffer) => buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
mime: "image/gif",
|
|
245
|
+
matches: (buffer) => buffer.length >= 6 && (buffer.subarray(0, 6).toString("ascii") === "GIF87a" || buffer.subarray(0, 6).toString("ascii") === "GIF89a")
|
|
246
|
+
}
|
|
247
|
+
];
|
|
248
|
+
function startsWithDataUrl(value) {
|
|
249
|
+
return value.slice(0, 5).toLowerCase() === DATA_URL_PREFIX;
|
|
250
|
+
}
|
|
251
|
+
function canonicalizeBase64(base64) {
|
|
252
|
+
let cleaned = "";
|
|
253
|
+
let padding = 0;
|
|
254
|
+
let sawPadding = false;
|
|
255
|
+
for (let i = 0; i < base64.length; i += 1) {
|
|
256
|
+
const code = base64.charCodeAt(i);
|
|
257
|
+
if (code <= 32) continue;
|
|
258
|
+
if (code === 61) {
|
|
259
|
+
padding += 1;
|
|
260
|
+
if (padding > 2) return;
|
|
261
|
+
sawPadding = true;
|
|
262
|
+
cleaned += "=";
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (sawPadding || !(code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47)) return;
|
|
266
|
+
cleaned += base64[i];
|
|
267
|
+
}
|
|
268
|
+
if (!cleaned || cleaned.length % 4 !== 0) return;
|
|
269
|
+
return cleaned;
|
|
270
|
+
}
|
|
271
|
+
function sniffImageMime(buffer) {
|
|
272
|
+
return IMAGE_SIGNATURES.find((signature) => signature.matches(buffer))?.mime;
|
|
273
|
+
}
|
|
274
|
+
function parseImageDataUrl(value) {
|
|
275
|
+
if (!startsWithDataUrl(value)) return {
|
|
276
|
+
metadata: [],
|
|
277
|
+
payload: value
|
|
278
|
+
};
|
|
279
|
+
const commaIndex = value.indexOf(",");
|
|
280
|
+
if (commaIndex < 0) return;
|
|
281
|
+
return {
|
|
282
|
+
metadata: value.slice(5, commaIndex).split(";").map((part) => part.trim()),
|
|
283
|
+
payload: value.slice(commaIndex + 1)
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function metadataAllowsImageBase64(metadata) {
|
|
287
|
+
const [mimeType, ...options] = metadata;
|
|
288
|
+
return mimeType !== void 0 && mimeType.toLowerCase().startsWith("image/") && options.some((part) => part.toLowerCase() === "base64");
|
|
289
|
+
}
|
|
290
|
+
function sanitizeInlineImageDataUrl(imageUrl) {
|
|
291
|
+
const parsed = parseImageDataUrl(imageUrl);
|
|
292
|
+
if (!parsed) return;
|
|
293
|
+
if (parsed.metadata.length === 0) return imageUrl;
|
|
294
|
+
if (!metadataAllowsImageBase64(parsed.metadata)) return;
|
|
295
|
+
const canonicalPayload = canonicalizeBase64(parsed.payload);
|
|
296
|
+
if (!canonicalPayload) return;
|
|
297
|
+
const sniffedMimeType = sniffImageMime(Buffer.from(canonicalPayload, "base64"));
|
|
298
|
+
if (!sniffedMimeType) return;
|
|
299
|
+
return `data:${sniffedMimeType};base64,${canonicalPayload}`;
|
|
300
|
+
}
|
|
301
|
+
function invalidInlineImageText(label) {
|
|
302
|
+
return `[${label}] ${IMAGE_OMITTED_TEXT}`;
|
|
303
|
+
}
|
|
304
|
+
function isRecord$1(value) {
|
|
305
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
306
|
+
}
|
|
307
|
+
function sanitizeImageContentRecord(record, label) {
|
|
308
|
+
if (record.type === "image" && typeof record.data === "string") {
|
|
309
|
+
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
|
|
310
|
+
const imageUrl = sanitizeInlineImageDataUrl(`data:${mimeType};base64,${record.data}`);
|
|
311
|
+
if (!imageUrl) return {
|
|
312
|
+
type: "text",
|
|
313
|
+
text: invalidInlineImageText(label)
|
|
314
|
+
};
|
|
315
|
+
const commaIndex = imageUrl.indexOf(",");
|
|
316
|
+
const mime = imageUrl.slice(5, commaIndex).split(";")[0] ?? mimeType;
|
|
317
|
+
return {
|
|
318
|
+
...record,
|
|
319
|
+
mimeType: mime,
|
|
320
|
+
data: imageUrl.slice(commaIndex + 1)
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (record.type === "inputImage" && typeof record.imageUrl === "string") {
|
|
324
|
+
const imageUrl = sanitizeInlineImageDataUrl(record.imageUrl);
|
|
325
|
+
return imageUrl ? {
|
|
326
|
+
...record,
|
|
327
|
+
imageUrl
|
|
328
|
+
} : {
|
|
329
|
+
type: "inputText",
|
|
330
|
+
text: invalidInlineImageText(label)
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (record.type === "input_image" && typeof record.image_url === "string") {
|
|
334
|
+
const imageUrl = sanitizeInlineImageDataUrl(record.image_url);
|
|
335
|
+
return imageUrl ? {
|
|
336
|
+
...record,
|
|
337
|
+
image_url: imageUrl
|
|
338
|
+
} : {
|
|
339
|
+
type: "input_text",
|
|
340
|
+
text: invalidInlineImageText(label)
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function sanitizeCodexHistoryImagePayloads(value, label) {
|
|
345
|
+
if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
|
|
346
|
+
if (!isRecord$1(value)) return value;
|
|
347
|
+
const imageRecord = sanitizeImageContentRecord(value, label);
|
|
348
|
+
if (imageRecord) return imageRecord;
|
|
349
|
+
const next = {};
|
|
350
|
+
for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
|
|
351
|
+
return next;
|
|
352
|
+
}
|
|
353
|
+
const MAX_SERIALIZED_ERROR_MESSAGE_LENGTH = 500;
|
|
354
|
+
var CodexAppInventoryCache = class {
|
|
355
|
+
constructor(options = {}) {
|
|
356
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
357
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
358
|
+
this.refreshTokens = /* @__PURE__ */ new Map();
|
|
359
|
+
this.diagnostics = /* @__PURE__ */ new Map();
|
|
360
|
+
this.revision = 0;
|
|
361
|
+
this.ttlMs = options.ttlMs ?? 36e5;
|
|
362
|
+
}
|
|
363
|
+
read(params) {
|
|
364
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
365
|
+
const entry = this.entries.get(params.key);
|
|
366
|
+
if (!entry) {
|
|
367
|
+
const refreshScheduled = params.suppressRefresh ? false : this.scheduleRefresh(params);
|
|
368
|
+
return {
|
|
369
|
+
state: "missing",
|
|
370
|
+
key: params.key,
|
|
371
|
+
revision: this.revision,
|
|
372
|
+
refreshScheduled,
|
|
373
|
+
...this.diagnostics.get(params.key) ? { diagnostic: this.diagnostics.get(params.key) } : {}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const state = entry.invalidated || entry.expiresAtMs <= nowMs ? "stale" : "fresh";
|
|
377
|
+
const refreshScheduled = state === "fresh" && !params.forceRefetch ? false : this.scheduleRefresh(params);
|
|
378
|
+
return {
|
|
379
|
+
state,
|
|
380
|
+
key: params.key,
|
|
381
|
+
revision: entry.revision,
|
|
382
|
+
snapshot: stripEntryState(entry),
|
|
383
|
+
refreshScheduled,
|
|
384
|
+
...entry.lastError ? { diagnostic: entry.lastError } : {}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
refreshNow(params) {
|
|
388
|
+
return this.refresh(params);
|
|
389
|
+
}
|
|
390
|
+
invalidate(key, reason, nowMs = Date.now()) {
|
|
391
|
+
this.revision += 1;
|
|
392
|
+
const diagnostic = {
|
|
393
|
+
message: reason,
|
|
394
|
+
atMs: nowMs
|
|
395
|
+
};
|
|
396
|
+
const entry = this.entries.get(key);
|
|
397
|
+
if (entry) {
|
|
398
|
+
entry.invalidated = true;
|
|
399
|
+
entry.lastError = diagnostic;
|
|
400
|
+
entry.revision = this.revision;
|
|
401
|
+
} else this.diagnostics.set(key, diagnostic);
|
|
402
|
+
return this.revision;
|
|
403
|
+
}
|
|
404
|
+
clear() {
|
|
405
|
+
this.entries.clear();
|
|
406
|
+
this.inFlight.clear();
|
|
407
|
+
this.refreshTokens.clear();
|
|
408
|
+
this.diagnostics.clear();
|
|
409
|
+
this.revision = 0;
|
|
410
|
+
}
|
|
411
|
+
getRevision() {
|
|
412
|
+
return this.revision;
|
|
413
|
+
}
|
|
414
|
+
scheduleRefresh(params) {
|
|
415
|
+
if (this.inFlight.has(params.key) && !params.forceRefetch) return true;
|
|
416
|
+
const promise = this.refresh(params);
|
|
417
|
+
this.inFlight.set(params.key, promise);
|
|
418
|
+
promise.catch(() => void 0);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
async refresh(params) {
|
|
422
|
+
const existing = this.inFlight.get(params.key);
|
|
423
|
+
if (existing && !params.forceRefetch) return existing;
|
|
424
|
+
const refreshToken = (this.refreshTokens.get(params.key) ?? 0) + 1;
|
|
425
|
+
this.refreshTokens.set(params.key, refreshToken);
|
|
426
|
+
const promise = this.refreshUncoalesced(params, refreshToken);
|
|
427
|
+
this.inFlight.set(params.key, promise);
|
|
428
|
+
try {
|
|
429
|
+
return await promise;
|
|
430
|
+
} finally {
|
|
431
|
+
if (this.inFlight.get(params.key) === promise) this.inFlight.delete(params.key);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async refreshUncoalesced(params, refreshToken) {
|
|
435
|
+
const nowMs = params.nowMs ?? Date.now();
|
|
436
|
+
try {
|
|
437
|
+
const apps = await listAllApps(params.request, params.forceRefetch ?? false);
|
|
438
|
+
this.revision += 1;
|
|
439
|
+
const snapshot = {
|
|
440
|
+
key: params.key,
|
|
441
|
+
apps,
|
|
442
|
+
fetchedAtMs: nowMs,
|
|
443
|
+
expiresAtMs: nowMs + this.ttlMs,
|
|
444
|
+
revision: this.revision
|
|
445
|
+
};
|
|
446
|
+
if (this.refreshTokens.get(params.key) === refreshToken) {
|
|
447
|
+
this.entries.set(params.key, {
|
|
448
|
+
...snapshot,
|
|
449
|
+
invalidated: false
|
|
450
|
+
});
|
|
451
|
+
this.diagnostics.delete(params.key);
|
|
452
|
+
}
|
|
453
|
+
return snapshot;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
const diagnostic = {
|
|
456
|
+
message: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
|
|
457
|
+
atMs: nowMs
|
|
458
|
+
};
|
|
459
|
+
this.diagnostics.set(params.key, diagnostic);
|
|
460
|
+
const entry = this.entries.get(params.key);
|
|
461
|
+
if (entry) entry.lastError = diagnostic;
|
|
462
|
+
embeddedAgentLog.warn("codex app inventory refresh failed", {
|
|
463
|
+
forceRefetch: params.forceRefetch === true,
|
|
464
|
+
keyFingerprint: fingerprintInventoryCacheKey(params.key),
|
|
465
|
+
error: serializeCodexAppInventoryError(error)
|
|
466
|
+
});
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
function serializeCodexAppInventoryError(error) {
|
|
472
|
+
const record = isRecord(error) ? error : void 0;
|
|
473
|
+
const data = record && "data" in record ? redactErrorData(record.data) : void 0;
|
|
474
|
+
return {
|
|
475
|
+
name: error instanceof Error ? error.name : typeof record?.name === "string" ? record.name : void 0,
|
|
476
|
+
message: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
|
|
477
|
+
...typeof record?.code === "number" ? { code: record.code } : {},
|
|
478
|
+
...data !== void 0 ? { data } : {}
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const defaultCodexAppInventoryCache = new CodexAppInventoryCache();
|
|
482
|
+
function buildCodexAppInventoryCacheKey(input) {
|
|
483
|
+
return JSON.stringify({
|
|
484
|
+
codexHome: input.codexHome ?? null,
|
|
485
|
+
endpoint: input.endpoint ?? null,
|
|
486
|
+
authProfileId: input.authProfileId ?? null,
|
|
487
|
+
accountId: input.accountId ?? null,
|
|
488
|
+
envApiKeyFingerprint: input.envApiKeyFingerprint ?? null,
|
|
489
|
+
appServerVersion: input.appServerVersion ?? null
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
async function listAllApps(request, forceRefetch) {
|
|
493
|
+
const apps = [];
|
|
494
|
+
let cursor;
|
|
495
|
+
do {
|
|
496
|
+
const response = await request("app/list", {
|
|
497
|
+
cursor,
|
|
498
|
+
limit: 100,
|
|
499
|
+
forceRefetch
|
|
500
|
+
});
|
|
501
|
+
apps.push(...response.data);
|
|
502
|
+
cursor = response.nextCursor;
|
|
503
|
+
} while (cursor);
|
|
504
|
+
return apps;
|
|
505
|
+
}
|
|
506
|
+
function stripEntryState(entry) {
|
|
507
|
+
const { invalidated: _invalidated, ...snapshot } = entry;
|
|
508
|
+
return snapshot;
|
|
509
|
+
}
|
|
510
|
+
function fingerprintInventoryCacheKey(key) {
|
|
511
|
+
let hash = 0;
|
|
512
|
+
for (let index = 0; index < key.length; index += 1) hash = hash * 31 + key.charCodeAt(index) >>> 0;
|
|
513
|
+
return hash.toString(16).padStart(8, "0");
|
|
514
|
+
}
|
|
515
|
+
function isRecord(value) {
|
|
516
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
517
|
+
}
|
|
518
|
+
function redactErrorData(value, depth = 0) {
|
|
519
|
+
if (value === void 0) return;
|
|
520
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") return value;
|
|
521
|
+
if (depth > 6) return "[truncated]";
|
|
522
|
+
if (Array.isArray(value)) return value.map((entry) => redactErrorData(entry, depth + 1) ?? null);
|
|
523
|
+
if (isRecord(value)) {
|
|
524
|
+
const redacted = {};
|
|
525
|
+
for (const [key, entry] of Object.entries(value)) redacted[key] = isSensitiveErrorDataKey(key) ? "<redacted>" : redactErrorData(entry, depth + 1) ?? null;
|
|
526
|
+
return redacted;
|
|
527
|
+
}
|
|
528
|
+
if (typeof value === "string" && value.length > 500) return `${value.slice(0, 500)}...`;
|
|
529
|
+
if (typeof value === "string") return value;
|
|
530
|
+
if (typeof value === "bigint") return value.toString();
|
|
531
|
+
if (typeof value === "symbol") return value.description ? `Symbol(${value.description})` : "Symbol()";
|
|
532
|
+
if (typeof value === "function") return value.name ? `[function ${value.name}]` : "[function]";
|
|
533
|
+
return "[unserializable]";
|
|
534
|
+
}
|
|
535
|
+
function sanitizeErrorMessage(message) {
|
|
536
|
+
const htmlStart = message.search(/<html[\s>]/i);
|
|
537
|
+
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>");
|
|
538
|
+
return redacted.length > MAX_SERIALIZED_ERROR_MESSAGE_LENGTH ? `${redacted.slice(0, MAX_SERIALIZED_ERROR_MESSAGE_LENGTH)}...` : redacted;
|
|
539
|
+
}
|
|
540
|
+
function isSensitiveErrorDataKey(key) {
|
|
541
|
+
return /api[_-]?key|authorization|cookie|credential|password|secret|token/i.test(key);
|
|
542
|
+
}
|
|
543
|
+
//#endregion
|
|
544
|
+
//#region extensions/codex/src/app-server/plugin-inventory.ts
|
|
545
|
+
async function readCodexPluginInventory(params) {
|
|
546
|
+
const policy = params.policy ?? resolveCodexPluginsPolicy(params.pluginConfig);
|
|
547
|
+
if (!policy.enabled) return {
|
|
548
|
+
policy,
|
|
549
|
+
records: [],
|
|
550
|
+
diagnostics: [{
|
|
551
|
+
code: "disabled",
|
|
552
|
+
message: "Native Codex plugin support is disabled."
|
|
553
|
+
}]
|
|
554
|
+
};
|
|
555
|
+
const appInventory = readCachedAppInventory(params);
|
|
556
|
+
const marketplaceEntry = (await params.request("plugin/list", { cwds: [] })).marketplaces.find((marketplace) => marketplace.name === CODEX_PLUGINS_MARKETPLACE_NAME);
|
|
557
|
+
if (!marketplaceEntry) return {
|
|
558
|
+
policy,
|
|
559
|
+
records: [],
|
|
560
|
+
diagnostics: policy.pluginPolicies.filter((pluginPolicy) => pluginPolicy.enabled).map((pluginPolicy) => ({
|
|
561
|
+
code: "marketplace_missing",
|
|
562
|
+
plugin: pluginPolicy,
|
|
563
|
+
message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.`
|
|
564
|
+
})),
|
|
565
|
+
...appInventory ? { appInventory } : {}
|
|
566
|
+
};
|
|
567
|
+
const marketplace = marketplaceRef(marketplaceEntry);
|
|
568
|
+
const diagnostics = [];
|
|
569
|
+
const records = [];
|
|
570
|
+
if (appInventory?.state === "missing") diagnostics.push({
|
|
571
|
+
code: "app_inventory_missing",
|
|
572
|
+
message: "Cached Codex app inventory is missing; plugin apps are excluded for this setup."
|
|
573
|
+
});
|
|
574
|
+
else if (appInventory?.state === "stale") diagnostics.push({
|
|
575
|
+
code: "app_inventory_stale",
|
|
576
|
+
message: "Cached Codex app inventory is stale; using stale app readiness and refreshing."
|
|
577
|
+
});
|
|
578
|
+
for (const pluginPolicy of policy.pluginPolicies) {
|
|
579
|
+
if (!pluginPolicy.enabled) continue;
|
|
580
|
+
const summary = findPluginSummary(marketplaceEntry, pluginPolicy.pluginName);
|
|
581
|
+
if (!summary) {
|
|
582
|
+
diagnostics.push({
|
|
583
|
+
code: "plugin_missing",
|
|
584
|
+
plugin: pluginPolicy,
|
|
585
|
+
message: `${pluginPolicy.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.`
|
|
586
|
+
});
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const detail = await readPluginDetail(params, marketplace, pluginPolicy, diagnostics);
|
|
590
|
+
const ownedAppIds = detail?.apps.map((app) => app.id).filter(Boolean).toSorted() ?? [];
|
|
591
|
+
const appOwnership = resolveAppOwnership({
|
|
592
|
+
detail,
|
|
593
|
+
appInventory,
|
|
594
|
+
summary
|
|
595
|
+
});
|
|
596
|
+
if (appOwnership === "ambiguous") diagnostics.push({
|
|
597
|
+
code: "app_ownership_ambiguous",
|
|
598
|
+
plugin: pluginPolicy,
|
|
599
|
+
message: `${pluginPolicy.pluginName} has only display-name app matches; apps are not exposed until ownership is stable.`
|
|
600
|
+
});
|
|
601
|
+
if (summary.installed && !summary.enabled) diagnostics.push({
|
|
602
|
+
code: "plugin_disabled",
|
|
603
|
+
plugin: pluginPolicy,
|
|
604
|
+
message: `${pluginPolicy.pluginName} is installed in Codex but disabled.`
|
|
605
|
+
});
|
|
606
|
+
const apps = resolveOwnedApps({
|
|
607
|
+
pluginPolicy,
|
|
608
|
+
detail,
|
|
609
|
+
appInventory
|
|
610
|
+
});
|
|
611
|
+
records.push({
|
|
612
|
+
policy: pluginPolicy,
|
|
613
|
+
summary,
|
|
614
|
+
...detail ? { detail } : {},
|
|
615
|
+
activationRequired: !summary.installed || !summary.enabled,
|
|
616
|
+
authRequired: apps.some((app) => app.needsAuth || !app.accessible),
|
|
617
|
+
appOwnership,
|
|
618
|
+
ownedAppIds,
|
|
619
|
+
apps
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
policy,
|
|
624
|
+
marketplace,
|
|
625
|
+
records,
|
|
626
|
+
diagnostics,
|
|
627
|
+
...appInventory ? { appInventory } : {}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function findOpenAiCuratedPluginSummary(listed, pluginName) {
|
|
631
|
+
const marketplaceEntry = listed.marketplaces.find((marketplace) => marketplace.name === CODEX_PLUGINS_MARKETPLACE_NAME);
|
|
632
|
+
if (!marketplaceEntry) return;
|
|
633
|
+
const summary = findPluginSummary(marketplaceEntry, pluginName);
|
|
634
|
+
return summary ? {
|
|
635
|
+
marketplace: marketplaceRef(marketplaceEntry),
|
|
636
|
+
summary
|
|
637
|
+
} : void 0;
|
|
638
|
+
}
|
|
639
|
+
function pluginReadParams(marketplace, pluginName) {
|
|
640
|
+
return {
|
|
641
|
+
...marketplace.path ? { marketplacePath: marketplace.path } : {},
|
|
642
|
+
...marketplace.remoteMarketplaceName ? { remoteMarketplaceName: marketplace.remoteMarketplaceName } : {},
|
|
643
|
+
pluginName
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function readCachedAppInventory(params) {
|
|
647
|
+
if (!params.appCache || !params.appCacheKey) return;
|
|
648
|
+
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
649
|
+
return params.appCache.read({
|
|
650
|
+
key: params.appCacheKey,
|
|
651
|
+
request,
|
|
652
|
+
nowMs: params.nowMs,
|
|
653
|
+
suppressRefresh: params.suppressAppInventoryRefresh
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
async function readPluginDetail(params, marketplace, pluginPolicy, diagnostics) {
|
|
657
|
+
if (params.readPluginDetails === false) return;
|
|
658
|
+
try {
|
|
659
|
+
return (await params.request("plugin/read", pluginReadParams(marketplace, pluginPolicy.pluginName))).plugin;
|
|
660
|
+
} catch (error) {
|
|
661
|
+
diagnostics.push({
|
|
662
|
+
code: "plugin_detail_unavailable",
|
|
663
|
+
plugin: pluginPolicy,
|
|
664
|
+
message: `${pluginPolicy.pluginName} detail unavailable: ${error instanceof Error ? error.message : String(error)}`
|
|
665
|
+
});
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function resolveAppOwnership(params) {
|
|
670
|
+
if (params.detail && params.detail.apps.length > 0) return "proven";
|
|
671
|
+
return (params.appInventory?.snapshot?.apps ?? []).filter((app) => app.pluginDisplayNames.some((displayName) => displayName === params.summary.name)).length > 0 ? "ambiguous" : "none";
|
|
672
|
+
}
|
|
673
|
+
function resolveOwnedApps(params) {
|
|
674
|
+
const detailApps = params.detail?.apps ?? [];
|
|
675
|
+
if (detailApps.length === 0) return [];
|
|
676
|
+
if (params.appInventory?.state === "missing") {
|
|
677
|
+
embeddedAgentLog.warn("codex plugin inventory missing app inventory for detail apps", {
|
|
678
|
+
configKey: params.pluginPolicy.configKey,
|
|
679
|
+
pluginName: params.pluginPolicy.pluginName,
|
|
680
|
+
appIds: detailApps.map((app) => app.id).toSorted()
|
|
681
|
+
});
|
|
682
|
+
return [];
|
|
683
|
+
}
|
|
684
|
+
const appInfoById = new Map((params.appInventory?.snapshot?.apps ?? []).map((app) => [app.id, app]));
|
|
685
|
+
return detailApps.map((app) => {
|
|
686
|
+
const info = appInfoById.get(app.id);
|
|
687
|
+
if (!info) return {
|
|
688
|
+
id: app.id,
|
|
689
|
+
name: app.name,
|
|
690
|
+
accessible: false,
|
|
691
|
+
enabled: false,
|
|
692
|
+
needsAuth: true
|
|
693
|
+
};
|
|
694
|
+
return {
|
|
695
|
+
id: app.id,
|
|
696
|
+
name: app.name,
|
|
697
|
+
accessible: info.isAccessible,
|
|
698
|
+
enabled: info.isEnabled,
|
|
699
|
+
needsAuth: app.needsAuth || !info.isAccessible
|
|
700
|
+
};
|
|
701
|
+
}).toSorted((left, right) => left.id.localeCompare(right.id));
|
|
702
|
+
}
|
|
703
|
+
function findPluginSummary(marketplace, pluginName) {
|
|
704
|
+
return marketplace.plugins.find((plugin) => plugin.name === pluginName || plugin.id === pluginName || plugin.id === `${pluginName}@${marketplace.name}` || pluginNameFromPluginId(plugin.id, marketplace.name) === pluginName);
|
|
705
|
+
}
|
|
706
|
+
function pluginNameFromPluginId(pluginId, marketplaceName) {
|
|
707
|
+
const trimmed = pluginId.trim();
|
|
708
|
+
if (!trimmed) return;
|
|
709
|
+
const marketplaceSuffix = `@${marketplaceName}`;
|
|
710
|
+
return (trimmed.endsWith(marketplaceSuffix) ? trimmed.slice(0, -marketplaceSuffix.length) : trimmed).split("/").at(-1)?.trim() || void 0;
|
|
711
|
+
}
|
|
712
|
+
function marketplaceRef(marketplace) {
|
|
713
|
+
return {
|
|
714
|
+
name: CODEX_PLUGINS_MARKETPLACE_NAME,
|
|
715
|
+
...marketplace.path ? { path: marketplace.path } : {},
|
|
716
|
+
...!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
//#endregion
|
|
720
|
+
//#region extensions/codex/src/app-server/plugin-activation.ts
|
|
721
|
+
async function ensureCodexPluginActivation(params) {
|
|
722
|
+
if (params.identity.marketplaceName !== "openai-curated") return activationFailure(params.identity, "marketplace_missing", { message: "Only " + CODEX_PLUGINS_MARKETPLACE_NAME + " plugins can be activated." });
|
|
723
|
+
const listed = await params.request("plugin/list", { cwds: [] });
|
|
724
|
+
const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName);
|
|
725
|
+
if (!resolved) {
|
|
726
|
+
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.` });
|
|
727
|
+
return activationFailure(params.identity, "plugin_missing", { message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.` });
|
|
728
|
+
}
|
|
729
|
+
if (resolved.summary.installed && resolved.summary.enabled && !params.installEvenIfActive) return {
|
|
730
|
+
identity: params.identity,
|
|
731
|
+
ok: true,
|
|
732
|
+
reason: "already_active",
|
|
733
|
+
installAttempted: false,
|
|
734
|
+
marketplace: resolved.marketplace,
|
|
735
|
+
diagnostics: []
|
|
736
|
+
};
|
|
737
|
+
const installResponse = await params.request("plugin/install", pluginReadParams(resolved.marketplace, params.identity.pluginName));
|
|
738
|
+
const refreshDiagnostics = [];
|
|
739
|
+
let refreshFailed = false;
|
|
740
|
+
try {
|
|
741
|
+
const refreshResult = await refreshCodexPluginRuntimeState({
|
|
742
|
+
request: params.request,
|
|
743
|
+
appCache: params.appCache,
|
|
744
|
+
appCacheKey: params.appCacheKey
|
|
745
|
+
});
|
|
746
|
+
refreshDiagnostics.push(...refreshResult.diagnostics);
|
|
747
|
+
} catch (error) {
|
|
748
|
+
refreshFailed = true;
|
|
749
|
+
refreshDiagnostics.push({ message: `Codex plugin runtime refresh failed after install: ${error instanceof Error ? error.message : String(error)}` });
|
|
750
|
+
}
|
|
751
|
+
const authRequired = installResponse.appsNeedingAuth.length > 0;
|
|
752
|
+
return {
|
|
753
|
+
identity: params.identity,
|
|
754
|
+
ok: !authRequired && !refreshFailed,
|
|
755
|
+
reason: refreshFailed ? "refresh_failed" : authRequired ? "auth_required" : resolved.summary.installed && resolved.summary.enabled ? "already_active" : "installed",
|
|
756
|
+
installAttempted: true,
|
|
757
|
+
marketplace: resolved.marketplace,
|
|
758
|
+
installResponse,
|
|
759
|
+
diagnostics: [...refreshDiagnostics, ...installResponse.appsNeedingAuth.map((app) => ({ message: `${app.name} requires app authentication before plugin tools are exposed.` }))]
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
async function refreshCodexPluginRuntimeState(params) {
|
|
763
|
+
const diagnostics = [];
|
|
764
|
+
await params.request("plugin/list", { cwds: [] });
|
|
765
|
+
await params.request("skills/list", {
|
|
766
|
+
cwds: [],
|
|
767
|
+
forceReload: true
|
|
768
|
+
});
|
|
769
|
+
try {
|
|
770
|
+
await params.request("hooks/list", { cwds: [] });
|
|
771
|
+
} catch (error) {
|
|
772
|
+
diagnostics.push({ message: `Codex hooks refresh skipped: ${error instanceof Error ? error.message : String(error)}` });
|
|
773
|
+
}
|
|
774
|
+
await params.request("config/mcpServer/reload", void 0);
|
|
775
|
+
if (params.appCache && params.appCacheKey) {
|
|
776
|
+
params.appCache.invalidate(params.appCacheKey, "Codex plugin activation changed app inventory");
|
|
777
|
+
const request = async (method, requestParams) => await params.request(method, requestParams);
|
|
778
|
+
try {
|
|
779
|
+
await params.appCache.refreshNow({
|
|
780
|
+
key: params.appCacheKey,
|
|
781
|
+
request,
|
|
782
|
+
forceRefetch: true
|
|
783
|
+
});
|
|
784
|
+
} catch (error) {
|
|
785
|
+
diagnostics.push({ message: `Codex app inventory refresh skipped: ${error instanceof Error ? error.message : String(error)}` });
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return { diagnostics };
|
|
789
|
+
}
|
|
790
|
+
function activationFailure(identity, reason, diagnostic) {
|
|
791
|
+
return {
|
|
792
|
+
identity,
|
|
793
|
+
ok: false,
|
|
794
|
+
reason,
|
|
795
|
+
installAttempted: false,
|
|
796
|
+
diagnostics: [diagnostic]
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
//#endregion
|
|
803
800
|
//#region extensions/codex/src/app-server/plugin-thread-config.ts
|
|
804
801
|
const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 1;
|
|
805
802
|
const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 1;
|
|
@@ -1091,6 +1088,7 @@ async function startOrResumeThread(params) {
|
|
|
1091
1088
|
const contextEngineBinding = buildContextEngineBinding(params.params, params.contextEngineProjection);
|
|
1092
1089
|
const userMcpServersConfigPatch = params.userMcpServersEnabled === false ? void 0 : buildCodexUserMcpServersThreadConfigPatch(params.params.config, { agentId: params.agentId ?? params.params.agentId });
|
|
1093
1090
|
const userMcpServersFingerprint = fingerprintUserMcpServersConfigPatch(userMcpServersConfigPatch);
|
|
1091
|
+
const environmentSelectionFingerprint = fingerprintEnvironmentSelection(params.environmentSelection);
|
|
1094
1092
|
let binding = await readCodexAppServerBinding(params.params.sessionFile, {
|
|
1095
1093
|
authProfileStore: params.params.authProfileStore,
|
|
1096
1094
|
agentDir: params.params.agentDir,
|
|
@@ -1127,6 +1125,11 @@ async function startOrResumeThread(params) {
|
|
|
1127
1125
|
await clearCodexAppServerBinding(params.params.sessionFile);
|
|
1128
1126
|
binding = void 0;
|
|
1129
1127
|
}
|
|
1128
|
+
if (binding?.threadId && binding.environmentSelectionFingerprint !== environmentSelectionFingerprint) {
|
|
1129
|
+
embeddedAgentLog.debug("codex app-server environment selection changed; starting a new thread", { threadId: binding.threadId });
|
|
1130
|
+
await clearCodexAppServerBinding(params.params.sessionFile);
|
|
1131
|
+
binding = void 0;
|
|
1132
|
+
}
|
|
1130
1133
|
if (binding?.threadId && params.mcpServersFingerprintEvaluated === true && binding.mcpServersFingerprint !== params.mcpServersFingerprint) {
|
|
1131
1134
|
embeddedAgentLog.debug("codex app-server MCP config changed; starting a new thread", { threadId: binding.threadId });
|
|
1132
1135
|
await clearCodexAppServerBinding(params.params.sessionFile);
|
|
@@ -1208,6 +1211,7 @@ async function startOrResumeThread(params) {
|
|
|
1208
1211
|
pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint,
|
|
1209
1212
|
pluginAppPolicyContext: binding.pluginAppPolicyContext,
|
|
1210
1213
|
contextEngine: contextEngineBinding,
|
|
1214
|
+
environmentSelectionFingerprint,
|
|
1211
1215
|
createdAt: binding.createdAt
|
|
1212
1216
|
}, {
|
|
1213
1217
|
authProfileStore: params.params.authProfileStore,
|
|
@@ -1237,6 +1241,7 @@ async function startOrResumeThread(params) {
|
|
|
1237
1241
|
pluginAppsInputFingerprint: binding.pluginAppsInputFingerprint,
|
|
1238
1242
|
pluginAppPolicyContext: binding.pluginAppPolicyContext,
|
|
1239
1243
|
contextEngine: contextEngineBinding,
|
|
1244
|
+
environmentSelectionFingerprint,
|
|
1240
1245
|
lifecycle: { action: "resumed" }
|
|
1241
1246
|
};
|
|
1242
1247
|
} catch (error) {
|
|
@@ -1253,7 +1258,8 @@ async function startOrResumeThread(params) {
|
|
|
1253
1258
|
developerInstructions: params.developerInstructions,
|
|
1254
1259
|
config,
|
|
1255
1260
|
nativeCodeModeEnabled: params.nativeCodeModeEnabled,
|
|
1256
|
-
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled
|
|
1261
|
+
nativeCodeModeOnlyEnabled: params.nativeCodeModeOnlyEnabled,
|
|
1262
|
+
environmentSelection: params.environmentSelection
|
|
1257
1263
|
})));
|
|
1258
1264
|
const modelProvider = resolveCodexAppServerModelProvider({
|
|
1259
1265
|
provider: params.params.provider,
|
|
@@ -1278,6 +1284,7 @@ async function startOrResumeThread(params) {
|
|
|
1278
1284
|
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
1279
1285
|
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
1280
1286
|
contextEngine: contextEngineBinding,
|
|
1287
|
+
environmentSelectionFingerprint,
|
|
1281
1288
|
createdAt
|
|
1282
1289
|
}, {
|
|
1283
1290
|
authProfileStore: params.params.authProfileStore,
|
|
@@ -1309,6 +1316,7 @@ async function startOrResumeThread(params) {
|
|
|
1309
1316
|
pluginAppsInputFingerprint: pluginThreadConfig?.inputFingerprint,
|
|
1310
1317
|
pluginAppPolicyContext: pluginThreadConfig?.policyContext,
|
|
1311
1318
|
contextEngine: contextEngineBinding,
|
|
1319
|
+
environmentSelectionFingerprint,
|
|
1312
1320
|
createdAt,
|
|
1313
1321
|
updatedAt: createdAt,
|
|
1314
1322
|
lifecycle: {
|
|
@@ -1398,7 +1406,7 @@ function buildThreadStartParams(params, options) {
|
|
|
1398
1406
|
nativeCodeModeEnabled: options.nativeCodeModeEnabled,
|
|
1399
1407
|
nativeCodeModeOnlyEnabled: options.nativeCodeModeOnlyEnabled
|
|
1400
1408
|
}),
|
|
1401
|
-
...options
|
|
1409
|
+
...resolveCodexThreadEnvironmentSelection(options),
|
|
1402
1410
|
developerInstructions: options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }),
|
|
1403
1411
|
dynamicTools: options.dynamicTools,
|
|
1404
1412
|
experimentalRawEvents: true,
|
|
@@ -1460,9 +1468,18 @@ function buildTurnStartParams(params, options) {
|
|
|
1460
1468
|
model: params.modelId,
|
|
1461
1469
|
...options.appServer.serviceTier ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1462
1470
|
effort: resolveReasoningEffort(params.thinkLevel, params.modelId),
|
|
1463
|
-
|
|
1471
|
+
...options.environmentSelection ? { environments: options.environmentSelection } : {},
|
|
1472
|
+
collaborationMode: buildTurnCollaborationMode(params, {
|
|
1473
|
+
turnScopedDeveloperInstructions: options.turnScopedDeveloperInstructions,
|
|
1474
|
+
heartbeatCollaborationInstructions: options.heartbeatCollaborationInstructions
|
|
1475
|
+
})
|
|
1464
1476
|
};
|
|
1465
1477
|
}
|
|
1478
|
+
function resolveCodexThreadEnvironmentSelection(options) {
|
|
1479
|
+
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
1480
|
+
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
1481
|
+
return {};
|
|
1482
|
+
}
|
|
1466
1483
|
function buildTurnCollaborationMode(params, options = {}) {
|
|
1467
1484
|
return {
|
|
1468
1485
|
mode: "default",
|
|
@@ -1474,10 +1491,26 @@ function buildTurnCollaborationMode(params, options = {}) {
|
|
|
1474
1491
|
};
|
|
1475
1492
|
}
|
|
1476
1493
|
function buildTurnScopedCollaborationInstructions(params, options = {}) {
|
|
1477
|
-
if (params.trigger === "cron") return buildCronCollaborationInstructions();
|
|
1478
|
-
if (params.trigger === "heartbeat") return joinPresentSections(buildHeartbeatCollaborationInstructions(), options.heartbeatCollaborationInstructions);
|
|
1494
|
+
if (params.trigger === "cron") return joinPresentSections(buildCronCollaborationInstructions(), options.turnScopedDeveloperInstructions);
|
|
1495
|
+
if (params.trigger === "heartbeat") return joinPresentSections(buildHeartbeatCollaborationInstructions(), options.turnScopedDeveloperInstructions, options.heartbeatCollaborationInstructions);
|
|
1496
|
+
if (options.turnScopedDeveloperInstructions?.trim()) return joinPresentSections(buildDefaultCollaborationInstructions(), options.turnScopedDeveloperInstructions);
|
|
1479
1497
|
return null;
|
|
1480
1498
|
}
|
|
1499
|
+
function buildDefaultCollaborationInstructions() {
|
|
1500
|
+
return [
|
|
1501
|
+
"# Collaboration Mode: Default",
|
|
1502
|
+
"",
|
|
1503
|
+
"You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.",
|
|
1504
|
+
"",
|
|
1505
|
+
"Your active mode changes only when new developer instructions with a different `<collaboration_mode>...</collaboration_mode>` change it; user requests or tool descriptions do not change mode by themselves. Known mode names are Default and Plan.",
|
|
1506
|
+
"",
|
|
1507
|
+
"## request_user_input availability",
|
|
1508
|
+
"",
|
|
1509
|
+
"Use the `request_user_input` tool only when it is listed in the available tools for this turn.",
|
|
1510
|
+
"",
|
|
1511
|
+
"In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message."
|
|
1512
|
+
].join("\n");
|
|
1513
|
+
}
|
|
1481
1514
|
function buildCronCollaborationInstructions() {
|
|
1482
1515
|
return [
|
|
1483
1516
|
"This is an OpenClaw cron automation turn. Apply these instructions only to this scheduled job; ordinary chat turns should stay in Codex Default mode.",
|
|
@@ -1508,11 +1541,14 @@ function fingerprintDynamicTools(dynamicTools) {
|
|
|
1508
1541
|
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
1509
1542
|
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
1510
1543
|
}
|
|
1544
|
+
function fingerprintEnvironmentSelection(environments) {
|
|
1545
|
+
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
1546
|
+
}
|
|
1511
1547
|
function fingerprintDynamicToolSpec(tool) {
|
|
1512
1548
|
if (!isJsonObject(tool)) return stabilizeJsonValue(tool);
|
|
1513
1549
|
const stable = {};
|
|
1514
1550
|
for (const [key, child] of Object.entries(tool).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
1515
|
-
if (key === "description") continue;
|
|
1551
|
+
if (key === "description" || key === "deferLoading" || key === "namespace") continue;
|
|
1516
1552
|
stable[key] = stabilizeJsonValue(child);
|
|
1517
1553
|
}
|
|
1518
1554
|
return stable;
|
|
@@ -1589,4 +1625,4 @@ function resolveReasoningEffort(thinkLevel, modelId) {
|
|
|
1589
1625
|
return null;
|
|
1590
1626
|
}
|
|
1591
1627
|
//#endregion
|
|
1592
|
-
export {
|
|
1628
|
+
export { sanitizeCodexHistoryImagePayloads as C, resolveCodexContextEngineProjectionMaxChars as D, projectContextEngineAssemblyForCodex as E, resolveCodexContextEngineProjectionReserveTokens as O, invalidInlineImageText as S, DEFAULT_CODEX_PROJECTION_RESERVE_TOKENS as T, shouldBuildCodexPluginThreadConfig as _, buildThreadResumeParams as a, buildCodexAppInventoryCacheKey as b, buildTurnStartParams as c, resolveCodexAppServerModelProvider as d, resolveReasoningEffort as f, mergeCodexThreadConfigs as g, buildCodexPluginThreadConfigInputFingerprint as h, buildDeveloperInstructions as i, codexDynamicToolsFingerprint as l, buildCodexPluginThreadConfig as m, buildCodexRuntimeThreadConfig as n, buildThreadStartParams as o, startOrResumeThread as p, buildContextEngineBinding as r, buildTurnCollaborationMode as s, areCodexDynamicToolFingerprintsCompatible as t, isContextEngineBindingCompatible as u, ensureCodexPluginActivation as v, sanitizeInlineImageDataUrl as w, defaultCodexAppInventoryCache as x, pluginReadParams as y };
|