@openclaw/codex 2026.5.16-beta.4 → 2026.5.16-beta.5

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.
@@ -5,15 +5,19 @@ import { r as isCodexAppServerApprovalRequest } from "./client-6FkrXfaz.js";
5
5
  import { u as formatCodexUsageLimitErrorMessage } from "./command-formatters-BRW7_Nu7.js";
6
6
  import { i as getSharedCodexAppServerClient, s as refreshCodexAppServerAuthTokens } from "./shared-client-DlvmoLBJ.js";
7
7
  import { i as readCodexAppServerBinding } from "./session-binding-DqApZIgD.js";
8
- import { b as createCodexDynamicToolBridge, d as resolveReasoningEffort, n as buildCodexRuntimeThreadConfig, u as resolveCodexAppServerModelProvider, x as filterCodexDynamicTools } from "./thread-lifecycle-wcyYqwrl.js";
9
- import { n as handleCodexAppServerElicitationRequest, r as handleCodexAppServerApprovalRequest, t as filterToolsForVisionInputs } from "./vision-tools-Cm_YicZb.js";
8
+ import { S as filterCodexDynamicTools, T as resolveCodexDynamicToolsLoading, b as createCodexDynamicToolBridge, d as resolveReasoningEffort, h as mergeCodexThreadConfigs, n as buildCodexRuntimeThreadConfig, u as resolveCodexAppServerModelProvider } from "./thread-lifecycle-BbriKhTq.js";
9
+ import { a as handleCodexAppServerElicitationRequest, i as buildCodexNativeHookRelayDisabledConfig, n as CODEX_NATIVE_HOOK_RELAY_EVENTS, o as handleCodexAppServerApprovalRequest, r as buildCodexNativeHookRelayConfig, t as filterToolsForVisionInputs } from "./vision-tools-BX9YuTEK.js";
10
10
  import { n as rememberCodexRateLimits, t as readRecentCodexRateLimits } from "./rate-limit-cache-dvhq-4pi.js";
11
- import { embeddedAgentLog, formatErrorMessage, resolveAgentDir, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
11
+ import { buildAgentHookContextChannelFields, embeddedAgentLog, formatErrorMessage, registerNativeHookRelay, resolveAgentDir, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
12
12
  //#region extensions/codex/src/app-server/side-question.ts
13
13
  const CODEX_SIDE_DYNAMIC_TOOL_TIMEOUT_MS = 3e4;
14
14
  const CODEX_SIDE_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
15
15
  const CODEX_SIDE_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
16
16
  const SIDE_QUESTION_COMPLETION_TIMEOUT_MS = 6e5;
17
+ const CODEX_SIDE_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
18
+ const CODEX_SIDE_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 6e4;
19
+ const CODEX_SIDE_NATIVE_HOOK_RELAY_STARTUP_REQUEST_COUNT = 3;
20
+ const CODEX_SIDE_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
17
21
  const SIDE_BOUNDARY_PROMPT = `Side conversation boundary.
18
22
 
19
23
  Everything before this boundary is inherited history from the parent thread. It is reference context only. It is not your current task.
@@ -63,6 +67,7 @@ async function runCodexAppServerSideQuestion(params, options = {}) {
63
67
  let childThreadId;
64
68
  let turnId;
65
69
  let removeRequestHandler;
70
+ let nativeHookRelay;
66
71
  try {
67
72
  const cwd = binding.cwd || params.workspaceDir || process.cwd();
68
73
  const sideRunParams = buildSideRunAttemptParams(params, {
@@ -103,6 +108,7 @@ async function runCodexAppServerSideQuestion(params, options = {}) {
103
108
  paramsForRun: sideRunParams,
104
109
  threadId: childThreadId,
105
110
  turnId,
111
+ nativeHookRelay,
106
112
  signal: runAbortController.signal
107
113
  });
108
114
  if (request.method !== "item/tool/call") return;
@@ -122,6 +128,36 @@ async function runCodexAppServerSideQuestion(params, options = {}) {
122
128
  const approvalPolicy = binding.approvalPolicy ?? appServer.approvalPolicy;
123
129
  const sandbox = binding.sandbox ?? appServer.sandbox;
124
130
  const serviceTier = binding.serviceTier ?? appServer.serviceTier;
131
+ const nativeHookRelayEvents = resolveCodexSideNativeHookRelayEvents({
132
+ configuredEvents: options.nativeHookRelay?.events,
133
+ approvalPolicy
134
+ });
135
+ nativeHookRelay = options.nativeHookRelay ? registerCodexSideNativeHookRelay({
136
+ options: options.nativeHookRelay,
137
+ events: nativeHookRelayEvents,
138
+ agentId: sessionAgentId,
139
+ sessionId: params.sessionId,
140
+ sessionKey: params.sessionKey,
141
+ config: params.cfg,
142
+ runId: sideRunParams.runId,
143
+ channelId: buildAgentHookContextChannelFields({
144
+ sessionKey: params.sessionKey,
145
+ messageChannel: params.messageChannel,
146
+ messageProvider: params.messageProvider,
147
+ currentChannelId: params.currentChannelId
148
+ }).channelId,
149
+ requestTimeoutMs: appServer.requestTimeoutMs,
150
+ completionTimeoutMs: Math.max(appServer.turnCompletionIdleTimeoutMs, SIDE_QUESTION_COMPLETION_TIMEOUT_MS),
151
+ signal: runAbortController.signal
152
+ }) : void 0;
153
+ const nativeHookRelayConfig = nativeHookRelay ? buildCodexNativeHookRelayConfig({
154
+ relay: nativeHookRelay,
155
+ events: nativeHookRelayEvents,
156
+ hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec,
157
+ clearOmittedEvents: true
158
+ }) : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : void 0;
159
+ const runtimeThreadConfig = buildCodexRuntimeThreadConfig(void 0);
160
+ const threadConfig = mergeCodexThreadConfigs(nativeHookRelayConfig, runtimeThreadConfig) ?? runtimeThreadConfig;
125
161
  const modelProvider = resolveCodexAppServerModelProvider({
126
162
  provider: params.provider,
127
163
  authProfileId,
@@ -137,7 +173,7 @@ async function runCodexAppServerSideQuestion(params, options = {}) {
137
173
  approvalsReviewer: appServer.approvalsReviewer,
138
174
  sandbox,
139
175
  ...serviceTier ? { serviceTier } : {},
140
- config: buildCodexRuntimeThreadConfig(void 0),
176
+ config: threadConfig,
141
177
  developerInstructions: SIDE_DEVELOPER_INSTRUCTIONS,
142
178
  ephemeral: true,
143
179
  threadSource: "user"
@@ -184,18 +220,51 @@ async function runCodexAppServerSideQuestion(params, options = {}) {
184
220
  if (!trimmed) throw new Error("Codex /btw completed without an answer.");
185
221
  return { text: trimmed };
186
222
  } finally {
187
- params.opts?.abortSignal?.removeEventListener("abort", abortFromUpstream);
188
- if (!runAbortController.signal.aborted) runAbortController.abort("codex_side_question_finished");
189
- removeNotificationHandler();
190
- removeRequestHandler?.();
191
- await cleanupCodexSideThread(client, {
192
- threadId: childThreadId,
193
- turnId,
194
- interrupt: !collector.completed,
195
- timeoutMs: appServer.requestTimeoutMs
196
- });
223
+ try {
224
+ params.opts?.abortSignal?.removeEventListener("abort", abortFromUpstream);
225
+ if (!runAbortController.signal.aborted) runAbortController.abort("codex_side_question_finished");
226
+ removeNotificationHandler();
227
+ removeRequestHandler?.();
228
+ await cleanupCodexSideThread(client, {
229
+ threadId: childThreadId,
230
+ turnId,
231
+ interrupt: !collector.completed,
232
+ timeoutMs: appServer.requestTimeoutMs
233
+ });
234
+ } finally {
235
+ nativeHookRelay?.unregister();
236
+ }
197
237
  }
198
238
  }
239
+ function resolveCodexSideNativeHookRelayEvents(params) {
240
+ if (params.configuredEvents?.length) return params.configuredEvents;
241
+ return params.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_SIDE_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
242
+ }
243
+ function registerCodexSideNativeHookRelay(params) {
244
+ if (params.options.enabled === false) return;
245
+ return registerNativeHookRelay({
246
+ provider: "codex",
247
+ ...params.agentId ? { agentId: params.agentId } : {},
248
+ sessionId: params.sessionId,
249
+ ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
250
+ ...params.config ? { config: params.config } : {},
251
+ runId: params.runId,
252
+ ...params.channelId ? { channelId: params.channelId } : {},
253
+ allowedEvents: params.events,
254
+ ttlMs: resolveCodexSideNativeHookRelayTtlMs({
255
+ explicitTtlMs: params.options.ttlMs,
256
+ requestTimeoutMs: params.requestTimeoutMs,
257
+ completionTimeoutMs: params.completionTimeoutMs
258
+ }),
259
+ signal: params.signal,
260
+ command: { timeoutMs: params.options.gatewayTimeoutMs }
261
+ });
262
+ }
263
+ function resolveCodexSideNativeHookRelayTtlMs(params) {
264
+ if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
265
+ const relayBudgetMs = params.requestTimeoutMs * CODEX_SIDE_NATIVE_HOOK_RELAY_STARTUP_REQUEST_COUNT + params.completionTimeoutMs + CODEX_SIDE_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
266
+ return Math.max(CODEX_SIDE_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
267
+ }
199
268
  function buildSideRunAttemptParams(params, options) {
200
269
  return {
201
270
  params,
@@ -211,6 +280,9 @@ function buildSideRunAttemptParams(params, options) {
211
280
  sessionFile: params.sessionFile,
212
281
  sessionKey: params.sessionKey,
213
282
  agentId: params.agentId,
283
+ ...params.messageChannel ? { messageChannel: params.messageChannel } : {},
284
+ ...params.messageProvider ? { messageProvider: params.messageProvider } : {},
285
+ ...params.currentChannelId ? { currentChannelId: params.currentChannelId } : {},
214
286
  workspaceDir: options.cwd,
215
287
  authProfileId: options.authProfileId,
216
288
  authProfileIdSource: params.authProfileIdSource,
@@ -262,6 +334,14 @@ async function createCodexSideToolBridge(input) {
262
334
  modelApi: runtimeModel.api,
263
335
  modelContextWindowTokens: runtimeModel.contextWindow,
264
336
  modelAuthMode: resolveModelAuthMode(runtimeModel.provider, input.params.cfg, void 0, { workspaceDir: input.cwd }),
337
+ ...input.params.messageProvider || input.params.messageChannel ? { messageProvider: input.params.messageProvider ?? input.params.messageChannel } : {},
338
+ ...input.params.currentChannelId ? { currentChannelId: input.params.currentChannelId } : {},
339
+ hookChannelId: buildAgentHookContextChannelFields({
340
+ sessionKey: input.params.sessionKey,
341
+ messageChannel: input.params.messageChannel,
342
+ messageProvider: input.params.messageProvider,
343
+ currentChannelId: input.params.currentChannelId
344
+ }).channelId,
265
345
  sandbox,
266
346
  modelHasVision: runtimeModel.input?.includes("image") ?? false,
267
347
  requireExplicitMessageTarget: true
@@ -270,16 +350,23 @@ async function createCodexSideToolBridge(input) {
270
350
  hasInboundImages: false
271
351
  });
272
352
  }
353
+ const hookChannelFields = buildAgentHookContextChannelFields({
354
+ sessionKey: input.params.sessionKey,
355
+ messageChannel: input.params.messageChannel,
356
+ messageProvider: input.params.messageProvider,
357
+ currentChannelId: input.params.currentChannelId
358
+ });
273
359
  return createCodexDynamicToolBridge({
274
360
  tools,
275
361
  signal: input.signal,
276
- loading: input.pluginConfig.codexDynamicToolsLoading ?? "searchable",
362
+ loading: resolveCodexDynamicToolsLoading(input.pluginConfig),
277
363
  hookContext: {
278
364
  agentId: input.sessionAgentId,
279
365
  config: input.params.cfg,
280
366
  sessionId: input.params.sessionId,
281
367
  sessionKey: input.params.sessionKey,
282
- runId: input.params.opts?.runId ?? `codex-btw:${input.params.sessionId}`
368
+ runId: input.params.opts?.runId ?? `codex-btw:${input.params.sessionId}`,
369
+ ...hookChannelFields
283
370
  }
284
371
  });
285
372
  }
package/dist/test-api.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { c as resolveCodexAppServerRuntimeOptions } from "./config-B5pq6hEz.js";
2
- import { a as buildThreadResumeParams, b as createCodexDynamicToolBridge, i as buildDeveloperInstructions, o as buildThreadStartParams, s as buildTurnStartParams, x as filterCodexDynamicTools } from "./thread-lifecycle-wcyYqwrl.js";
2
+ import { S as filterCodexDynamicTools, a as buildThreadResumeParams, b as createCodexDynamicToolBridge, i as buildDeveloperInstructions, o as buildThreadStartParams, s as buildTurnStartParams } from "./thread-lifecycle-BbriKhTq.js";
3
3
  //#region extensions/codex/test-api.ts
4
4
  function resolveCodexPromptSnapshotAppServerOptions(pluginConfig) {
5
5
  return resolveCodexAppServerRuntimeOptions({
@@ -8,6 +8,7 @@ import { i as readCodexAppServerBinding, n as isCodexAppServerNativeAuthProfile,
8
8
  import { a as defaultCodexAppInventoryCache, r as readCodexPluginInventory, t as ensureCodexPluginActivation } from "./plugin-activation-B49xb7pI.js";
9
9
  import crypto from "node:crypto";
10
10
  import { HEARTBEAT_RESPONSE_TOOL_NAME, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractToolResultMediaArtifact, filterToolResultMediaUrls, isActiveHarnessContextEngine, isMessagingTool, isMessagingToolSendAction, isToolWrappedWithBeforeToolCallHook, normalizeHeartbeatToolResponse, runAgentHarnessAfterToolCallHook, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
11
+ import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
11
12
  import { buildCodexUserMcpServersThreadConfigPatch } from "openclaw/plugin-sdk/codex-mcp-projection";
12
13
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
13
14
  //#region extensions/codex/src/app-server/dynamic-tool-profile.ts
@@ -32,9 +33,15 @@ function normalizeCodexDynamicToolName(name) {
32
33
  const normalized = name.trim().toLowerCase();
33
34
  return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
34
35
  }
35
- function filterCodexDynamicTools(tools, config) {
36
+ function isForcedPrivateQaCodexRuntime(env = process.env) {
37
+ return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
38
+ }
39
+ function resolveCodexDynamicToolsLoading(config, env = process.env) {
40
+ return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
41
+ }
42
+ function filterCodexDynamicTools(tools, config, env = process.env) {
36
43
  const excludes = /* @__PURE__ */ new Set();
37
- for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) excludes.add(name);
44
+ if (!isForcedPrivateQaCodexRuntime(env)) for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) excludes.add(name);
38
45
  for (const name of config.codexDynamicToolsExclude ?? []) {
39
46
  const trimmed = normalizeCodexDynamicToolName(name);
40
47
  if (trimmed) excludes.add(trimmed);
@@ -42,11 +49,113 @@ function filterCodexDynamicTools(tools, config) {
42
49
  return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
43
50
  }
44
51
  //#endregion
52
+ //#region extensions/codex/src/app-server/image-payload-sanitizer.ts
53
+ const DATA_URL_PREFIX = "data:";
54
+ const IMAGE_OMITTED_TEXT = "omitted image payload: invalid inline image data";
55
+ function startsWithDataUrl(value) {
56
+ return value.slice(0, 5).toLowerCase() === DATA_URL_PREFIX;
57
+ }
58
+ function canonicalizeBase64(base64) {
59
+ let cleaned = "";
60
+ let padding = 0;
61
+ let sawPadding = false;
62
+ for (let i = 0; i < base64.length; i += 1) {
63
+ const code = base64.charCodeAt(i);
64
+ if (code <= 32) continue;
65
+ if (code === 61) {
66
+ padding += 1;
67
+ if (padding > 2) return;
68
+ sawPadding = true;
69
+ cleaned += "=";
70
+ continue;
71
+ }
72
+ if (sawPadding || !(code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47)) return;
73
+ cleaned += base64[i];
74
+ }
75
+ if (!cleaned || cleaned.length % 4 !== 0) return;
76
+ return cleaned;
77
+ }
78
+ function sniffImageMime(buffer) {
79
+ if (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) return "image/png";
80
+ if (buffer.length >= 3 && buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) return "image/jpeg";
81
+ if (buffer.length >= 12 && buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP") return "image/webp";
82
+ if (buffer.length >= 6 && (buffer.subarray(0, 6).toString("ascii") === "GIF87a" || buffer.subarray(0, 6).toString("ascii") === "GIF89a")) return "image/gif";
83
+ }
84
+ function sanitizeInlineImageDataUrl(imageUrl) {
85
+ if (!startsWithDataUrl(imageUrl)) return imageUrl;
86
+ const commaIndex = imageUrl.indexOf(",");
87
+ if (commaIndex < 0) return;
88
+ const metadata = imageUrl.slice(5, commaIndex);
89
+ const payload = imageUrl.slice(commaIndex + 1);
90
+ const metadataParts = metadata.split(";").map((part) => part.trim());
91
+ if (!(metadataParts[0]?.toLowerCase())?.startsWith("image/")) return;
92
+ if (!metadataParts.slice(1).some((part) => part.toLowerCase() === "base64")) return;
93
+ const canonicalPayload = canonicalizeBase64(payload);
94
+ if (!canonicalPayload) return;
95
+ const sniffedMimeType = sniffImageMime(Buffer.from(canonicalPayload, "base64"));
96
+ if (!sniffedMimeType) return;
97
+ return `data:${sniffedMimeType};base64,${canonicalPayload}`;
98
+ }
99
+ function invalidInlineImageText(label) {
100
+ return `[${label}] ${IMAGE_OMITTED_TEXT}`;
101
+ }
102
+ function isRecord$1(value) {
103
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
104
+ }
105
+ function sanitizeImageContentRecord(record, label) {
106
+ if (record.type === "image" && typeof record.data === "string") {
107
+ const mimeType = typeof record.mimeType === "string" ? record.mimeType : "image/png";
108
+ const imageUrl = sanitizeInlineImageDataUrl(`data:${mimeType};base64,${record.data}`);
109
+ if (!imageUrl) return {
110
+ type: "text",
111
+ text: invalidInlineImageText(label)
112
+ };
113
+ const commaIndex = imageUrl.indexOf(",");
114
+ const mime = imageUrl.slice(5, commaIndex).split(";")[0] ?? mimeType;
115
+ return {
116
+ ...record,
117
+ mimeType: mime,
118
+ data: imageUrl.slice(commaIndex + 1)
119
+ };
120
+ }
121
+ if (record.type === "inputImage" && typeof record.imageUrl === "string") {
122
+ const imageUrl = sanitizeInlineImageDataUrl(record.imageUrl);
123
+ return imageUrl ? {
124
+ ...record,
125
+ imageUrl
126
+ } : {
127
+ type: "inputText",
128
+ text: invalidInlineImageText(label)
129
+ };
130
+ }
131
+ if (record.type === "input_image" && typeof record.image_url === "string") {
132
+ const imageUrl = sanitizeInlineImageDataUrl(record.image_url);
133
+ return imageUrl ? {
134
+ ...record,
135
+ image_url: imageUrl
136
+ } : {
137
+ type: "input_text",
138
+ text: invalidInlineImageText(label)
139
+ };
140
+ }
141
+ }
142
+ function sanitizeCodexHistoryImagePayloads(value, label) {
143
+ if (Array.isArray(value)) return value.map((entry) => sanitizeCodexHistoryImagePayloads(entry, label));
144
+ if (!isRecord$1(value)) return value;
145
+ const imageRecord = sanitizeImageContentRecord(value, label);
146
+ if (imageRecord) return imageRecord;
147
+ const next = {};
148
+ for (const [key, child] of Object.entries(value)) next[key] = sanitizeCodexHistoryImagePayloads(child, label);
149
+ return next;
150
+ }
151
+ //#endregion
45
152
  //#region extensions/codex/src/app-server/dynamic-tools.ts
46
153
  const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
47
154
  const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = new Set(["sessions_yield"]);
155
+ const DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS = 16e3;
48
156
  function createCodexDynamicToolBridge(params) {
49
157
  const toolResultHookContext = toToolResultHookContext(params.hookContext);
158
+ const toolResultMaxChars = resolveCodexDynamicToolResultMaxChars(params.hookContext);
50
159
  const tools = params.tools.map((tool) => isToolWrappedWithBeforeToolCallHook(tool) ? tool : wrapToolWithBeforeToolCallHook(tool, params.hookContext));
51
160
  const toolMap = new Map(tools.map((tool) => [tool.name, tool]));
52
161
  const telemetry = {
@@ -120,12 +229,13 @@ function createCodexDynamicToolBridge(params) {
120
229
  agentId: toolResultHookContext.agentId,
121
230
  sessionId: toolResultHookContext.sessionId,
122
231
  sessionKey: toolResultHookContext.sessionKey,
232
+ channelId: toolResultHookContext.channelId,
123
233
  startArgs: args,
124
234
  result,
125
235
  startedAt
126
236
  });
127
237
  return {
128
- contentItems: result.content.flatMap(convertToolContent),
238
+ contentItems: convertToolContents(result.content, toolResultMaxChars),
129
239
  success: !resultIsError
130
240
  };
131
241
  } catch (error) {
@@ -143,6 +253,7 @@ function createCodexDynamicToolBridge(params) {
143
253
  agentId: toolResultHookContext.agentId,
144
254
  sessionId: toolResultHookContext.sessionId,
145
255
  sessionKey: toolResultHookContext.sessionKey,
256
+ channelId: toolResultHookContext.channelId,
146
257
  startArgs: args,
147
258
  error: error instanceof Error ? error.message : String(error),
148
259
  startedAt
@@ -172,14 +283,34 @@ function createCodexDynamicToolSpec(params) {
172
283
  };
173
284
  }
174
285
  function toToolResultHookContext(ctx) {
175
- const { agentId, sessionId, sessionKey, runId } = ctx ?? {};
286
+ const { agentId, sessionId, sessionKey, runId, channelId } = ctx ?? {};
176
287
  return {
177
288
  ...agentId && { agentId },
178
289
  ...sessionId && { sessionId },
179
290
  ...sessionKey && { sessionKey },
180
- ...runId && { runId }
291
+ ...runId && { runId },
292
+ ...channelId && { channelId }
181
293
  };
182
294
  }
295
+ function resolveCodexDynamicToolResultMaxChars(ctx) {
296
+ return resolveAgentContextLimitValue({
297
+ config: ctx?.config,
298
+ agentId: ctx?.agentId,
299
+ key: "toolResultMaxChars"
300
+ }) ?? DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS;
301
+ }
302
+ function resolveAgentContextLimitValue(params) {
303
+ const agents = readRecord(params.config?.agents);
304
+ const defaultValue = readPositiveInteger(readRecord(readRecord(agents?.defaults)?.contextLimits)?.[params.key]);
305
+ if (!params.agentId) return defaultValue;
306
+ const list = agents?.list;
307
+ if (!Array.isArray(list)) return defaultValue;
308
+ const normalizedAgentId = normalizeAgentId(params.agentId);
309
+ return readPositiveInteger(readRecord(readRecord(list.find((entry) => {
310
+ const entryId = readRecord(entry)?.id;
311
+ return typeof entryId === "string" && normalizeAgentId(entryId) === normalizedAgentId;
312
+ }))?.contextLimits)?.[params.key]) ?? defaultValue;
313
+ }
183
314
  function composeAbortSignals(...signals) {
184
315
  const activeSignals = signals.filter((signal) => Boolean(signal));
185
316
  if (activeSignals.length === 0) return new AbortController().signal;
@@ -261,6 +392,13 @@ function extractInternalSourceReplyPayload(details) {
261
392
  function isRecord(value) {
262
393
  return value !== null && typeof value === "object" && !Array.isArray(value);
263
394
  }
395
+ function readRecord(value) {
396
+ return isRecord(value) ? value : void 0;
397
+ }
398
+ function readPositiveInteger(value) {
399
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return;
400
+ return Math.floor(value);
401
+ }
264
402
  function isToolResultError(result) {
265
403
  const details = result.details;
266
404
  if (!isRecord(details)) return false;
@@ -270,14 +408,66 @@ function isToolResultError(result) {
270
408
  const status = details.status.trim().toLowerCase();
271
409
  return status !== "" && status !== "0" && status !== "ok" && status !== "success" && status !== "completed" && status !== "recorded" && status !== "running";
272
410
  }
411
+ function normalizeToolResultMaxChars(maxChars) {
412
+ return typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS;
413
+ }
414
+ function convertToolContents(content, toolResultMaxChars = DEFAULT_CODEX_DYNAMIC_TOOL_RESULT_MAX_CHARS) {
415
+ const maxChars = normalizeToolResultMaxChars(toolResultMaxChars);
416
+ const totalTextChars = content.reduce((total, item) => total + (item.type === "text" ? item.text.length : 0), 0);
417
+ if (totalTextChars <= maxChars) return content.flatMap(convertToolContent);
418
+ const noticeText = `...(OpenClaw truncated dynamic tool result: original ${totalTextChars} chars, showing ${maxChars}; rerun with narrower args.)`;
419
+ const notice = `\n${noticeText}`;
420
+ let remainingTextBudget = Math.max(0, maxChars - notice.length);
421
+ let appendedNotice = false;
422
+ const output = [];
423
+ for (const item of content) {
424
+ if (item.type !== "text") {
425
+ output.push(...convertToolContent(item));
426
+ continue;
427
+ }
428
+ if (appendedNotice) continue;
429
+ if (notice.length >= maxChars) {
430
+ output.push({
431
+ type: "inputText",
432
+ text: noticeText.slice(0, maxChars)
433
+ });
434
+ appendedNotice = true;
435
+ continue;
436
+ }
437
+ const sliceLength = Math.min(item.text.length, remainingTextBudget);
438
+ remainingTextBudget -= sliceLength;
439
+ const shouldAppendNotice = remainingTextBudget <= 0;
440
+ const text = item.text.slice(0, sliceLength);
441
+ if (shouldAppendNotice) {
442
+ output.push({
443
+ type: "inputText",
444
+ text: `${text.trimEnd()}${notice}`.slice(0, maxChars)
445
+ });
446
+ appendedNotice = true;
447
+ } else if (text.length > 0) output.push({
448
+ type: "inputText",
449
+ text
450
+ });
451
+ }
452
+ if (!appendedNotice) output.push({
453
+ type: "inputText",
454
+ text: noticeText.slice(0, maxChars)
455
+ });
456
+ return output;
457
+ }
273
458
  function convertToolContent(content) {
274
459
  if (content.type === "text") return [{
275
460
  type: "inputText",
276
461
  text: content.text
277
462
  }];
463
+ const imageUrl = sanitizeInlineImageDataUrl(`data:${content.mimeType};base64,${content.data}`);
464
+ if (!imageUrl) return [{
465
+ type: "inputText",
466
+ text: invalidInlineImageText("codex dynamic tool")
467
+ }];
278
468
  return [{
279
469
  type: "inputImage",
280
- imageUrl: `data:${content.mimeType};base64,${content.data}`
470
+ imageUrl
281
471
  }];
282
472
  }
283
473
  function toJsonValue(value) {
@@ -1238,14 +1428,22 @@ function renderCodexRuntimePromptOverlay(params) {
1238
1428
  ].filter((section) => typeof section === "string" && section.trim().length > 0).join("\n\n");
1239
1429
  }
1240
1430
  function buildUserInput(params, promptText = params.prompt) {
1431
+ const imageInputs = (params.images ?? []).map((image) => {
1432
+ const imageUrl = sanitizeInlineImageDataUrl(`data:${image.mimeType};base64,${image.data}`);
1433
+ return imageUrl ? {
1434
+ type: "image",
1435
+ url: imageUrl
1436
+ } : {
1437
+ type: "text",
1438
+ text: invalidInlineImageText("codex user input"),
1439
+ text_elements: []
1440
+ };
1441
+ });
1241
1442
  return [{
1242
1443
  type: "text",
1243
1444
  text: promptText,
1244
1445
  text_elements: []
1245
- }, ...(params.images ?? []).map((image) => ({
1246
- type: "image",
1247
- url: `data:${image.mimeType};base64,${image.data}`
1248
- }))];
1446
+ }, ...imageInputs];
1249
1447
  }
1250
1448
  function resolveCodexAppServerModelProvider(params) {
1251
1449
  const normalized = params.provider.trim();
@@ -1260,4 +1458,4 @@ function resolveReasoningEffort(thinkLevel, modelId) {
1260
1458
  return null;
1261
1459
  }
1262
1460
  //#endregion
1263
- export { normalizeCodexDynamicToolName as S, projectContextEngineAssemblyForCodex as _, buildThreadResumeParams as a, createCodexDynamicToolBridge as b, codexDynamicToolsFingerprint as c, resolveReasoningEffort as d, startOrResumeThread as f, shouldBuildCodexPluginThreadConfig as g, mergeCodexThreadConfigs as h, buildDeveloperInstructions as i, isContextEngineBindingCompatible as l, buildCodexPluginThreadConfigInputFingerprint as m, buildCodexRuntimeThreadConfig as n, buildThreadStartParams as o, buildCodexPluginThreadConfig as p, buildContextEngineBinding as r, buildTurnStartParams as s, areCodexDynamicToolFingerprintsCompatible as t, resolveCodexAppServerModelProvider as u, resolveCodexContextEngineProjectionMaxChars as v, filterCodexDynamicTools as x, resolveCodexContextEngineProjectionReserveTokens as y };
1461
+ export { isForcedPrivateQaCodexRuntime as C, filterCodexDynamicTools as S, resolveCodexDynamicToolsLoading as T, projectContextEngineAssemblyForCodex as _, buildThreadResumeParams as a, createCodexDynamicToolBridge as b, codexDynamicToolsFingerprint as c, resolveReasoningEffort as d, startOrResumeThread as f, shouldBuildCodexPluginThreadConfig as g, mergeCodexThreadConfigs as h, buildDeveloperInstructions as i, isContextEngineBindingCompatible as l, buildCodexPluginThreadConfigInputFingerprint as m, buildCodexRuntimeThreadConfig as n, buildThreadStartParams as o, buildCodexPluginThreadConfig as p, buildContextEngineBinding as r, buildTurnStartParams as s, areCodexDynamicToolFingerprintsCompatible as t, resolveCodexAppServerModelProvider as u, resolveCodexContextEngineProjectionMaxChars as v, normalizeCodexDynamicToolName as w, sanitizeCodexHistoryImagePayloads as x, resolveCodexContextEngineProjectionReserveTokens as y };