@openclaw/codex 2026.5.16-beta.3 → 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.
@@ -1,20 +1,20 @@
1
- import { c as resolveCodexAppServerRuntimeOptions, d as withMcpElicitationsApprovalPolicy, i as isCodexAppServerApprovalPolicyAllowedByRequirements, s as readCodexPluginConfig, u as resolveCodexPluginsPolicy } from "./config-3ATInASk.js";
1
+ import { c as resolveCodexAppServerRuntimeOptions, d as withMcpElicitationsApprovalPolicy, i as isCodexAppServerApprovalPolicyAllowedByRequirements, s as readCodexPluginConfig, u as resolveCodexPluginsPolicy } from "./config-B5pq6hEz.js";
2
2
  import { a as readCodexDynamicToolCallParams, c as readCodexTurn, i as assertCodexTurnStartResponse } from "./protocol-validators-BGBspNmF.js";
3
3
  import { t as isJsonObject } from "./protocol-C9UWI98H.js";
4
- import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-CA7amCZ8.js";
5
- import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./request-Czoa60Nt.js";
4
+ import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-6FkrXfaz.js";
5
+ import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./request-XPjLayGw.js";
6
6
  import { d as resolveCodexUsageLimitResetAtMs, f as shouldRefreshCodexRateLimitsForUsageLimitMessage, r as formatCodexDisplayText, u as formatCodexUsageLimitErrorMessage } from "./command-formatters-BRW7_Nu7.js";
7
- import { c as resolveCodexAppServerAuthAccountCacheKey, d as resolveCodexAppServerEnvApiKeyCacheKey, l as resolveCodexAppServerAuthProfileId, n as clearSharedCodexAppServerClientIfCurrent, s as refreshCodexAppServerAuthTokens, u as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-CnbrvEfV.js";
8
- import { i as readCodexAppServerBinding, t as clearCodexAppServerBinding } from "./session-binding-DbdVqMzW.js";
9
- import { a as defaultCodexAppInventoryCache } from "./plugin-activation-BDU4e-NA.js";
10
- import { S as normalizeCodexDynamicToolName, _ as projectContextEngineAssemblyForCodex, b as createCodexDynamicToolBridge, c as codexDynamicToolsFingerprint, f as startOrResumeThread, g as shouldBuildCodexPluginThreadConfig, h as mergeCodexThreadConfigs, i as buildDeveloperInstructions, l as isContextEngineBindingCompatible, m as buildCodexPluginThreadConfigInputFingerprint, p as buildCodexPluginThreadConfig, r as buildContextEngineBinding, s as buildTurnStartParams, t as areCodexDynamicToolFingerprintsCompatible, v as resolveCodexContextEngineProjectionMaxChars, x as filterCodexDynamicTools, y as resolveCodexContextEngineProjectionReserveTokens } from "./thread-lifecycle-D2sxRPdz.js";
11
- import { t as defaultCodexAppServerClientFactory } from "./client-factory-DwzPofwS.js";
12
- import { n as handleCodexAppServerElicitationRequest, r as handleCodexAppServerApprovalRequest, t as filterToolsForVisionInputs } from "./vision-tools-Cm_YicZb.js";
13
- import { t as ensureCodexComputerUse } from "./computer-use-BX02ojP4.js";
7
+ import { c as resolveCodexAppServerAuthAccountCacheKey, d as resolveCodexAppServerEnvApiKeyCacheKey, f as resolveCodexAppServerHomeDir, l as resolveCodexAppServerAuthProfileId, s as refreshCodexAppServerAuthTokens, t as clearSharedCodexAppServerClientIfCurrent, u as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-DlvmoLBJ.js";
8
+ import { i as readCodexAppServerBinding, t as clearCodexAppServerBinding } from "./session-binding-DqApZIgD.js";
9
+ import { a as defaultCodexAppInventoryCache } from "./plugin-activation-B49xb7pI.js";
10
+ import { C as isForcedPrivateQaCodexRuntime, S as filterCodexDynamicTools, T as resolveCodexDynamicToolsLoading, _ as projectContextEngineAssemblyForCodex, b as createCodexDynamicToolBridge, c as codexDynamicToolsFingerprint, f as startOrResumeThread, g as shouldBuildCodexPluginThreadConfig, h as mergeCodexThreadConfigs, i as buildDeveloperInstructions, l as isContextEngineBindingCompatible, m as buildCodexPluginThreadConfigInputFingerprint, p as buildCodexPluginThreadConfig, r as buildContextEngineBinding, s as buildTurnStartParams, t as areCodexDynamicToolFingerprintsCompatible, v as resolveCodexContextEngineProjectionMaxChars, w as normalizeCodexDynamicToolName, x as sanitizeCodexHistoryImagePayloads, y as resolveCodexContextEngineProjectionReserveTokens } from "./thread-lifecycle-BbriKhTq.js";
11
+ import { t as defaultCodexAppServerClientFactory } from "./client-factory-9L6Ie1dC.js";
12
+ 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";
13
+ import { t as ensureCodexComputerUse } from "./computer-use-UJ3dxrXF.js";
14
14
  import { n as rememberCodexRateLimits, t as readRecentCodexRateLimits } from "./rate-limit-cache-dvhq-4pi.js";
15
15
  import { createHash } from "node:crypto";
16
16
  import nodeFs from "node:fs";
17
- import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, embeddedAgentLog, emitAgentEvent, emitSessionTranscriptUpdate, finalizeHarnessContextEngineTurn, formatErrorMessage, formatToolAggregate, formatToolProgressOutput, hasBeforeToolCallPolicy, inferToolMetaFromArgs, isActiveHarnessContextEngine, isSubagentSessionKey, loadCodexBundleMcpThreadConfig, normalizeAgentRuntimeTools, normalizeUsage, registerNativeHookRelay, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveContextEngineOwnerPluginId, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentCleanupStep, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessAgentEndHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
17
+ import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, embeddedAgentLog, emitAgentEvent, emitSessionTranscriptUpdate, finalizeHarnessContextEngineTurn, formatErrorMessage, formatToolAggregate, formatToolProgressOutput, hasBeforeToolCallPolicy, inferToolMetaFromArgs, isActiveHarnessContextEngine, isSubagentSessionKey, loadCodexBundleMcpThreadConfig, normalizeAgentRuntimeTools, normalizeUsage, registerNativeHookRelay, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveContextEngineOwnerPluginId, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockOptions, resolveUserPath, runAgentCleanupStep, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessAgentEndHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
18
18
  import fs from "node:fs/promises";
19
19
  import path from "node:path";
20
20
  import { markAuthProfileBlockedUntil, resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
@@ -324,7 +324,7 @@ async function readCodexMirroredSessionHistoryMessages(sessionFile) {
324
324
  const firstEntry = entries[0];
325
325
  if (firstEntry?.type !== "session" || typeof firstEntry.id !== "string") return;
326
326
  migrateSessionEntries(entries);
327
- return buildSessionContext(entries.filter((entry) => entry.type !== "session")).messages;
327
+ return sanitizeCodexHistoryImagePayloads(buildSessionContext(entries.filter((entry) => entry.type !== "session")).messages, "codex mirrored history");
328
328
  } catch (error) {
329
329
  if (isMissingFileError(error)) return [];
330
330
  return;
@@ -446,7 +446,7 @@ async function mirrorCodexAppServerTranscript(params) {
446
446
  if (messages.length === 0) return;
447
447
  const lock = await acquireSessionWriteLock({
448
448
  sessionFile: params.sessionFile,
449
- timeoutMs: resolveSessionWriteLockAcquireTimeoutMs(params.config)
449
+ ...resolveSessionWriteLockOptions(params.config)
450
450
  });
451
451
  try {
452
452
  const existingIdempotencyKeys = await readTranscriptIdempotencyKeys(params.sessionFile);
@@ -990,6 +990,7 @@ var CodexAppServerEventProjector = class {
990
990
  });
991
991
  }
992
992
  this.recordToolMeta(item);
993
+ this.emitSnapshotOnlyNativeToolProgress(item);
993
994
  this.recordNativeToolTranscriptCall(item);
994
995
  this.recordNativeToolTranscriptResult(item);
995
996
  this.emitAfterToolCallObservation(item);
@@ -999,6 +1000,33 @@ var CodexAppServerEventProjector = class {
999
1000
  this.activeCompactionItemIds.clear();
1000
1001
  await this.maybeEndReasoning();
1001
1002
  }
1003
+ emitSnapshotOnlyNativeToolProgress(item) {
1004
+ if (!shouldSynthesizeToolProgressForItem(item) || !this.isCurrentTurnSnapshotItem(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
1005
+ if (!this.activeItemIds.has(item.id)) {
1006
+ this.emitStandardItemEvent({
1007
+ phase: "start",
1008
+ item
1009
+ });
1010
+ this.emitNormalizedToolItemEvent({
1011
+ phase: "start",
1012
+ item
1013
+ });
1014
+ }
1015
+ this.activeItemIds.delete(item.id);
1016
+ this.emitStandardItemEvent({
1017
+ phase: "end",
1018
+ item
1019
+ });
1020
+ this.emitNormalizedToolItemEvent({
1021
+ phase: "result",
1022
+ item
1023
+ });
1024
+ this.completedItemIds.add(item.id);
1025
+ }
1026
+ isCurrentTurnSnapshotItem(item) {
1027
+ const itemTurnId = readItemString(item, "turnId") ?? readItemString(item, "turn_id");
1028
+ return itemTurnId === void 0 || itemTurnId === this.turnId;
1029
+ }
1002
1030
  handleOutputDelta(params, toolName) {
1003
1031
  const itemId = readString$2(params, "itemId");
1004
1032
  const delta = readString$2(params, "delta");
@@ -1128,6 +1156,13 @@ var CodexAppServerEventProjector = class {
1128
1156
  const meta = itemMeta(item, this.toolProgressDetailMode());
1129
1157
  const args = params.phase === "start" ? itemToolArgs(item) : void 0;
1130
1158
  const status = params.phase === "result" ? itemStatus(item) : "running";
1159
+ this.recordToolTrajectoryEvent({
1160
+ phase: params.phase,
1161
+ item,
1162
+ name,
1163
+ args,
1164
+ status
1165
+ });
1131
1166
  this.emitDiagnosticToolExecutionEvent({
1132
1167
  phase: params.phase,
1133
1168
  item,
@@ -1152,6 +1187,32 @@ var CodexAppServerEventProjector = class {
1152
1187
  });
1153
1188
  if (params.phase === "result") this.emitAfterToolCallObservation(item);
1154
1189
  }
1190
+ recordToolTrajectoryEvent(params) {
1191
+ if (params.phase === "start") {
1192
+ this.options.trajectoryRecorder?.recordEvent("tool.call", {
1193
+ threadId: this.threadId,
1194
+ turnId: this.turnId,
1195
+ itemId: params.item.id,
1196
+ toolCallId: params.item.id,
1197
+ name: params.name,
1198
+ arguments: params.args
1199
+ });
1200
+ return;
1201
+ }
1202
+ const toolResult = itemToolResult(params.item).result;
1203
+ const output = itemOutputText(params.item);
1204
+ this.options.trajectoryRecorder?.recordEvent("tool.result", {
1205
+ threadId: this.threadId,
1206
+ turnId: this.turnId,
1207
+ itemId: params.item.id,
1208
+ toolCallId: params.item.id,
1209
+ name: params.name,
1210
+ status: params.status,
1211
+ isError: isNonSuccessItemStatus(params.status),
1212
+ ...toolResult ? { result: toolResult } : {},
1213
+ ...output ? { output } : {}
1214
+ });
1215
+ }
1155
1216
  emitDiagnosticToolExecutionEvent(params) {
1156
1217
  const base = {
1157
1218
  runId: this.params.runId,
@@ -1749,88 +1810,6 @@ function readTurn(value) {
1749
1810
  return readCodexTurn(value);
1750
1811
  }
1751
1812
  //#endregion
1752
- //#region extensions/codex/src/app-server/native-hook-relay.ts
1753
- const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
1754
- "pre_tool_use",
1755
- "post_tool_use",
1756
- "permission_request",
1757
- "before_agent_finalize"
1758
- ];
1759
- const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
1760
- pre_tool_use: "PreToolUse",
1761
- post_tool_use: "PostToolUse",
1762
- permission_request: "PermissionRequest",
1763
- before_agent_finalize: "Stop"
1764
- };
1765
- const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
1766
- pre_tool_use: "pre_tool_use",
1767
- post_tool_use: "post_tool_use",
1768
- permission_request: "permission_request",
1769
- before_agent_finalize: "stop"
1770
- };
1771
- const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
1772
- function buildCodexNativeHookRelayConfig(params) {
1773
- const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
1774
- const config = { "features.hooks": true };
1775
- const hookState = {};
1776
- for (const event of events) {
1777
- const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
1778
- const command = params.relay.commandForEvent(event);
1779
- const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
1780
- config[`hooks.${codexEvent}`] = [{ hooks: [{
1781
- type: "command",
1782
- command,
1783
- timeout,
1784
- async: false,
1785
- statusMessage: "OpenClaw native hook relay"
1786
- }] }];
1787
- const state = {
1788
- enabled: true,
1789
- trusted_hash: codexCommandHookTrustedHash({
1790
- event,
1791
- command,
1792
- timeout,
1793
- statusMessage: "OpenClaw native hook relay"
1794
- })
1795
- };
1796
- for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
1797
- }
1798
- config["hooks.state"] = hookState;
1799
- return config;
1800
- }
1801
- function buildCodexNativeHookRelayDisabledConfig() {
1802
- return {
1803
- "features.hooks": false,
1804
- "hooks.PreToolUse": [],
1805
- "hooks.PostToolUse": [],
1806
- "hooks.PermissionRequest": [],
1807
- "hooks.Stop": []
1808
- };
1809
- }
1810
- function normalizeHookTimeoutSec(value) {
1811
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : 5;
1812
- }
1813
- function codexCommandHookTrustedHash(params) {
1814
- const identity = {
1815
- event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[params.event],
1816
- hooks: [{
1817
- async: false,
1818
- command: params.command,
1819
- statusMessage: params.statusMessage,
1820
- timeout: params.timeout,
1821
- type: "command"
1822
- }]
1823
- };
1824
- return `sha256:${createHash("sha256").update(JSON.stringify(sortJsonValue(identity))).digest("hex")}`;
1825
- }
1826
- function sortJsonValue(value) {
1827
- if (!value || typeof value !== "object") return value;
1828
- if (Array.isArray(value)) return value.map(sortJsonValue);
1829
- const sorted = {};
1830
- for (const key of Object.keys(value).toSorted()) sorted[key] = sortJsonValue(value[key]);
1831
- return sorted;
1832
- }
1833
- //#endregion
1834
1813
  //#region extensions/codex/src/app-server/trajectory.ts
1835
1814
  const SENSITIVE_FIELD_RE = /(?:authorization|cookie|credential|key|password|passwd|secret|token)/iu;
1836
1815
  const PRIVATE_PAYLOAD_FIELD_RE = /(?:image|screenshot|attachment|fileData|dataUri)/iu;
@@ -2401,6 +2380,169 @@ function isCodexAppServerPolicyMode(value) {
2401
2380
  function isCodexAppServerApprovalPolicy(value) {
2402
2381
  return value === "never" || value === "on-request" || value === "on-failure" || value === "untrusted";
2403
2382
  }
2383
+ const CODEX_APP_SERVER_NATIVE_THREAD_MAX_TOKENS = 7e4;
2384
+ const CODEX_APP_SERVER_BYTE_UNITS = {
2385
+ b: 1,
2386
+ k: 1024,
2387
+ kb: 1024,
2388
+ kib: 1024,
2389
+ m: 1024 * 1024,
2390
+ mb: 1024 * 1024,
2391
+ mib: 1024 * 1024,
2392
+ g: 1024 * 1024 * 1024,
2393
+ gb: 1024 * 1024 * 1024,
2394
+ gib: 1024 * 1024 * 1024,
2395
+ t: 1024 * 1024 * 1024 * 1024,
2396
+ tb: 1024 * 1024 * 1024 * 1024,
2397
+ tib: 1024 * 1024 * 1024 * 1024
2398
+ };
2399
+ function parseCodexAppServerByteLimit(value) {
2400
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.floor(value);
2401
+ if (typeof value !== "string") return;
2402
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)?$/i);
2403
+ if (!match) return;
2404
+ const amount = Number(match[1]);
2405
+ if (!Number.isFinite(amount) || amount <= 0) return;
2406
+ const multiplier = CODEX_APP_SERVER_BYTE_UNITS[(match[2] ?? "b").toLowerCase()];
2407
+ if (multiplier === void 0) return;
2408
+ return Math.max(1, Math.round(amount * multiplier));
2409
+ }
2410
+ async function listCodexAppServerRolloutFilesForThread(agentDir, threadId, codexHome) {
2411
+ const resolvedAgentDir = path.resolve(agentDir);
2412
+ const resolvedCodexHome = codexHome?.trim() ? path.resolve(codexHome) : resolveCodexAppServerHomeDir(resolvedAgentDir);
2413
+ const roots = [
2414
+ path.join(resolvedCodexHome, "sessions"),
2415
+ path.join(resolveCodexAppServerHomeDir(resolvedAgentDir), "sessions"),
2416
+ path.join(resolvedAgentDir, "agent", "codex-home", "sessions"),
2417
+ path.join(path.dirname(resolvedAgentDir), "codex-home", "sessions")
2418
+ ];
2419
+ const files = [];
2420
+ const visited = /* @__PURE__ */ new Set();
2421
+ for (const root of roots) {
2422
+ if (visited.has(root)) continue;
2423
+ visited.add(root);
2424
+ const stack = [root];
2425
+ while (stack.length > 0) {
2426
+ const dir = stack.pop();
2427
+ if (!dir) continue;
2428
+ let entries;
2429
+ try {
2430
+ entries = await fs.readdir(dir, { withFileTypes: true });
2431
+ } catch {
2432
+ continue;
2433
+ }
2434
+ for (const entry of entries) {
2435
+ const file = path.join(dir, entry.name);
2436
+ if (entry.isDirectory()) {
2437
+ stack.push(file);
2438
+ continue;
2439
+ }
2440
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl") || !entry.name.includes(threadId)) continue;
2441
+ try {
2442
+ files.push({
2443
+ path: file,
2444
+ bytes: (await fs.stat(file)).size
2445
+ });
2446
+ } catch {}
2447
+ }
2448
+ }
2449
+ }
2450
+ return files;
2451
+ }
2452
+ async function readCodexSessionRecordForSessionFile(sessionFile) {
2453
+ const sessionsFile = path.join(path.dirname(sessionFile), "sessions.json");
2454
+ let store;
2455
+ try {
2456
+ store = JSON.parse(await fs.readFile(sessionsFile, "utf8"));
2457
+ } catch {
2458
+ return;
2459
+ }
2460
+ if (!isJsonObject(store)) return;
2461
+ const resolvedSessionFile = path.resolve(sessionFile);
2462
+ for (const [sessionKey, record] of Object.entries(store)) {
2463
+ if (!isJsonObject(record) || typeof record.sessionFile !== "string") continue;
2464
+ if (path.resolve(record.sessionFile) !== resolvedSessionFile) continue;
2465
+ return {
2466
+ sessionKey,
2467
+ ...record
2468
+ };
2469
+ }
2470
+ }
2471
+ async function readCodexAppServerRolloutTokenUsage(file) {
2472
+ let handle;
2473
+ try {
2474
+ handle = await fs.open(file, "r");
2475
+ } catch {
2476
+ return;
2477
+ }
2478
+ let totalTokens;
2479
+ try {
2480
+ for await (const line of handle.readLines()) {
2481
+ const lineTokens = readCodexAppServerRolloutTokenUsageLine(line);
2482
+ if (lineTokens !== void 0) totalTokens = lineTokens;
2483
+ }
2484
+ } finally {
2485
+ await handle.close();
2486
+ }
2487
+ return totalTokens;
2488
+ }
2489
+ function readCodexAppServerRolloutTokenUsageLine(line) {
2490
+ if (!line.trim()) return;
2491
+ try {
2492
+ const parsed = JSON.parse(line);
2493
+ const payload = isJsonObject(parsed) ? parsed.payload : void 0;
2494
+ const info = isJsonObject(payload) && payload.type === "token_count" && isJsonObject(payload.info) ? payload.info : void 0;
2495
+ if (!info) return;
2496
+ const usage = isJsonObject(info.last_token_usage) ? info.last_token_usage : isJsonObject(info.total_token_usage) ? info.total_token_usage : void 0;
2497
+ const value = usage?.total_tokens ?? usage?.totalTokens;
2498
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2499
+ } catch {
2500
+ return;
2501
+ }
2502
+ }
2503
+ function maxFiniteNumber(values) {
2504
+ const nums = values.filter((value) => typeof value === "number" && Number.isFinite(value));
2505
+ if (nums.length === 0) return;
2506
+ return Math.max(...nums);
2507
+ }
2508
+ async function rotateOversizedCodexAppServerStartupBinding(params) {
2509
+ const binding = params.binding;
2510
+ if (!binding?.threadId) return binding;
2511
+ if (params.config?.agents?.defaults?.compaction?.truncateAfterCompaction !== true) return binding;
2512
+ const sessionRecord = await readCodexSessionRecordForSessionFile(params.sessionFile);
2513
+ const maxBytes = parseCodexAppServerByteLimit(params.config?.agents?.defaults?.compaction?.maxActiveTranscriptBytes);
2514
+ const rolloutFiles = await listCodexAppServerRolloutFilesForThread(params.agentDir, binding.threadId, params.codexHome);
2515
+ if (maxBytes !== void 0) {
2516
+ const oversizedFiles = rolloutFiles.filter((file) => file.bytes >= maxBytes);
2517
+ if (oversizedFiles.length > 0) {
2518
+ embeddedAgentLog.warn("codex app-server native transcript exceeded active byte limit; starting a fresh thread", {
2519
+ threadId: binding.threadId,
2520
+ maxBytes,
2521
+ files: oversizedFiles.map((file) => ({
2522
+ path: file.path,
2523
+ bytes: file.bytes
2524
+ }))
2525
+ });
2526
+ await clearCodexAppServerBinding(params.sessionFile);
2527
+ return;
2528
+ }
2529
+ }
2530
+ const nativeTokens = maxFiniteNumber(await Promise.all(rolloutFiles.map(async (file) => readCodexAppServerRolloutTokenUsage(file.path))));
2531
+ const sessionTokens = sessionRecord?.totalTokensFresh !== false && typeof sessionRecord?.totalTokens === "number" && Number.isFinite(sessionRecord.totalTokens) ? sessionRecord.totalTokens : void 0;
2532
+ const tokenCount = maxFiniteNumber([sessionTokens, nativeTokens]);
2533
+ if (tokenCount !== void 0 && tokenCount >= CODEX_APP_SERVER_NATIVE_THREAD_MAX_TOKENS) {
2534
+ embeddedAgentLog.warn("codex app-server native transcript exceeded active token limit; starting a fresh thread", {
2535
+ threadId: binding.threadId,
2536
+ maxTokens: CODEX_APP_SERVER_NATIVE_THREAD_MAX_TOKENS,
2537
+ sessionKey: sessionRecord?.sessionKey,
2538
+ sessionTokens,
2539
+ nativeTokens
2540
+ });
2541
+ await clearCodexAppServerBinding(params.sessionFile);
2542
+ return;
2543
+ }
2544
+ return binding;
2545
+ }
2404
2546
  async function runCodexAppServerAttempt(params, options = {}) {
2405
2547
  const attemptStartedAt = Date.now();
2406
2548
  const attemptClientFactory = options.clientFactory ?? defaultCodexAppServerClientFactory;
@@ -2440,8 +2582,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
2440
2582
  agentId: params.agentId
2441
2583
  });
2442
2584
  const agentDir = params.agentDir ?? resolveAgentDir$1(params.config ?? {}, sessionAgentId);
2443
- const startupBinding = await readCodexAppServerBinding(params.sessionFile);
2444
- const startupAuthProfileCandidate = params.runtimePlan?.auth.forwardedAuthProfileId ?? params.authProfileId ?? startupBinding?.authProfileId;
2585
+ let startupBinding = await readCodexAppServerBinding(params.sessionFile);
2586
+ const startupBindingAuthProfileId = startupBinding?.authProfileId;
2587
+ startupBinding = await rotateOversizedCodexAppServerStartupBinding({
2588
+ binding: startupBinding,
2589
+ sessionFile: params.sessionFile,
2590
+ agentDir,
2591
+ codexHome: appServer.start.env?.CODEX_HOME,
2592
+ config: params.config
2593
+ });
2594
+ const startupAuthProfileCandidate = params.runtimePlan?.auth.forwardedAuthProfileId ?? params.authProfileId ?? startupBinding?.authProfileId ?? startupBindingAuthProfileId;
2445
2595
  const startupAuthProfileId = params.authProfileStore ? resolveCodexAppServerAuthProfileId({
2446
2596
  authProfileId: startupAuthProfileCandidate,
2447
2597
  store: params.authProfileStore,
@@ -2483,6 +2633,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2483
2633
  });
2484
2634
  for (const diagnostic of bundleMcpThreadConfig.diagnostics) embeddedAgentLog.warn(`bundle-mcp: ${diagnostic.pluginId}: ${diagnostic.message}`);
2485
2635
  const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
2636
+ const hookChannelId = resolveCodexAppServerHookChannelId(params, sandboxSessionKey);
2486
2637
  let yieldDetected = false;
2487
2638
  const toolBridge = createCodexDynamicToolBridge({
2488
2639
  tools: await buildDynamicTools({
@@ -2499,14 +2650,15 @@ async function runCodexAppServerAttempt(params, options = {}) {
2499
2650
  }
2500
2651
  }),
2501
2652
  signal: runAbortController.signal,
2502
- loading: pluginConfig.codexDynamicToolsLoading ?? "searchable",
2653
+ loading: resolveCodexDynamicToolsLoading(pluginConfig),
2503
2654
  directToolNames: shouldForceMessageTool(params) ? ["message"] : [],
2504
2655
  hookContext: {
2505
2656
  agentId: sessionAgentId,
2506
2657
  config: params.config,
2507
2658
  sessionId: params.sessionId,
2508
2659
  sessionKey: sandboxSessionKey,
2509
- runId: params.runId
2660
+ runId: params.runId,
2661
+ channelId: hookChannelId
2510
2662
  }
2511
2663
  });
2512
2664
  const hadSessionFile = await pathExists(activeSessionFile);
@@ -2524,7 +2676,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2524
2676
  workspaceDir: params.workspaceDir,
2525
2677
  messageProvider: params.messageProvider ?? void 0,
2526
2678
  trigger: params.trigger,
2527
- channelId: params.messageChannel ?? params.messageProvider ?? void 0,
2679
+ channelId: hookChannelId,
2528
2680
  ...hookContextWindowFields
2529
2681
  };
2530
2682
  const activeContextEnginePluginId = activeContextEngine ? resolveContextEngineOwnerPluginId(activeContextEngine) : void 0;
@@ -2644,7 +2796,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2644
2796
  prePromptMessageCount = projection.prePromptMessageCount;
2645
2797
  }
2646
2798
  const buildPromptFromCurrentInputs = () => resolveAgentHarnessBeforePromptBuildResult({
2647
- prompt: prependCurrentTurnContext(promptText, params.currentTurnContext),
2799
+ prompt: prependCurrentInboundContext(promptText, params.currentInboundContext),
2648
2800
  developerInstructions,
2649
2801
  messages: historyMessages,
2650
2802
  ctx: hookContext
@@ -2688,16 +2840,18 @@ async function runCodexAppServerAttempt(params, options = {}) {
2688
2840
  sessionKey: sandboxSessionKey,
2689
2841
  config: params.config,
2690
2842
  runId: params.runId,
2843
+ channelId: hookChannelId,
2691
2844
  attemptTimeoutMs: params.timeoutMs,
2692
2845
  startupTimeoutMs,
2693
2846
  turnStartTimeoutMs: params.timeoutMs,
2694
2847
  signal: runAbortController.signal
2695
2848
  });
2696
- const threadConfig = mergeCodexThreadConfigs(nativeHookRelay ? buildCodexNativeHookRelayConfig({
2849
+ const nativeHookRelayConfig = nativeHookRelay ? buildCodexNativeHookRelayConfig({
2697
2850
  relay: nativeHookRelay,
2698
2851
  events: nativeHookRelayEvents,
2699
2852
  hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec
2700
- }) : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : void 0, bundleMcpThreadConfig?.configPatch);
2853
+ }) : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : void 0;
2854
+ const threadConfig = mergeCodexThreadConfigs(bundleMcpThreadConfig?.configPatch);
2701
2855
  const pluginThreadConfigEnabled = shouldBuildCodexPluginThreadConfig(pluginConfig);
2702
2856
  const pluginAppCacheKey = buildCodexPluginAppCacheKey({
2703
2857
  appServer,
@@ -2740,6 +2894,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2740
2894
  appServer: pluginAppServer,
2741
2895
  developerInstructions: promptBuild.developerInstructions,
2742
2896
  config: threadConfig,
2897
+ finalConfigPatch: nativeHookRelayConfig,
2743
2898
  mcpServersFingerprint: bundleMcpThreadConfig.fingerprint,
2744
2899
  mcpServersFingerprintEvaluated: bundleMcpThreadConfig.evaluated,
2745
2900
  contextEngineProjection,
@@ -2826,9 +2981,12 @@ async function runCodexAppServerAttempt(params, options = {}) {
2826
2981
  let userInputBridge;
2827
2982
  let steeringQueue;
2828
2983
  let completed = false;
2984
+ let terminalTurnNotificationQueued = false;
2829
2985
  let timedOut = false;
2830
2986
  let turnCompletionIdleTimedOut = false;
2831
2987
  let turnCompletionIdleTimeoutMessage;
2988
+ let clientClosedPromptError;
2989
+ let clientClosedAbort = false;
2832
2990
  let lifecycleStarted = false;
2833
2991
  let lifecycleTerminalEmitted = false;
2834
2992
  let resolveCompletion;
@@ -2842,6 +3000,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2842
3000
  let turnCompletionIdleTimer;
2843
3001
  let turnCompletionIdleWatchArmed = false;
2844
3002
  let turnCompletionIdleWatchPinnedByTerminalError = false;
3003
+ let turnCompletionIdleTimeoutOverrideMs;
2845
3004
  let turnAssistantCompletionIdleTimer;
2846
3005
  let turnAssistantCompletionIdleWatchArmed = false;
2847
3006
  let turnAssistantCompletionLastActivityAt = Date.now();
@@ -2953,8 +3112,9 @@ async function runCodexAppServerAttempt(params, options = {}) {
2953
3112
  };
2954
3113
  const fireTurnCompletionIdleTimeout = () => {
2955
3114
  if (completed || runAbortController.signal.aborted || !turnCompletionIdleWatchArmed || activeAppServerTurnRequests > 0) return;
3115
+ const timeoutMs = turnCompletionIdleTimeoutOverrideMs ?? turnCompletionIdleTimeoutMs;
2956
3116
  const idleMs = Math.max(0, Date.now() - turnCompletionLastActivityAt);
2957
- if (idleMs < turnCompletionIdleTimeoutMs) {
3117
+ if (idleMs < timeoutMs) {
2958
3118
  scheduleTurnCompletionIdleWatch();
2959
3119
  return;
2960
3120
  }
@@ -2966,7 +3126,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2966
3126
  threadId: thread.threadId,
2967
3127
  turnId,
2968
3128
  idleMs,
2969
- timeoutMs: turnCompletionIdleTimeoutMs,
3129
+ timeoutMs,
2970
3130
  lastActivityReason: turnCompletionLastActivityReason,
2971
3131
  ...turnCompletionLastActivityDetails
2972
3132
  });
@@ -2974,7 +3134,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2974
3134
  threadId: thread.threadId,
2975
3135
  turnId,
2976
3136
  idleMs,
2977
- timeoutMs: turnCompletionIdleTimeoutMs,
3137
+ timeoutMs,
2978
3138
  lastActivityReason: turnCompletionLastActivityReason,
2979
3139
  ...turnCompletionLastActivityDetails
2980
3140
  });
@@ -3013,7 +3173,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3013
3173
  clearTurnCompletionIdleTimer();
3014
3174
  if (completed || runAbortController.signal.aborted || !turnCompletionIdleWatchArmed || activeAppServerTurnRequests > 0) return;
3015
3175
  const elapsedMs = Math.max(0, Date.now() - turnCompletionLastActivityAt);
3016
- const delayMs = Math.max(1, turnCompletionIdleTimeoutMs - elapsedMs);
3176
+ const delayMs = Math.max(1, (turnCompletionIdleTimeoutOverrideMs ?? turnCompletionIdleTimeoutMs) - elapsedMs);
3017
3177
  turnCompletionIdleTimer = setTimeout(fireTurnCompletionIdleTimeout, delayMs);
3018
3178
  turnCompletionIdleTimer.unref?.();
3019
3179
  }
@@ -3064,6 +3224,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3064
3224
  turnCompletionLastActivityAt = Date.now();
3065
3225
  turnCompletionLastActivityReason = reason;
3066
3226
  turnCompletionLastActivityDetails = options?.details;
3227
+ turnCompletionIdleTimeoutOverrideMs = void 0;
3067
3228
  if (options?.attemptProgress) {
3068
3229
  turnAttemptLastProgressAt = turnCompletionLastActivityAt;
3069
3230
  turnAttemptLastProgressReason = reason;
@@ -3092,6 +3253,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3092
3253
  const disarmTurnCompletionIdleWatch = () => {
3093
3254
  turnCompletionIdleWatchArmed = false;
3094
3255
  turnCompletionIdleWatchPinnedByTerminalError = false;
3256
+ turnCompletionIdleTimeoutOverrideMs = void 0;
3095
3257
  clearTurnCompletionIdleTimer();
3096
3258
  };
3097
3259
  const disarmTurnAssistantCompletionIdleWatch = () => {
@@ -3108,6 +3270,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3108
3270
  const armTurnCompletionIdleWatch = (options) => {
3109
3271
  turnCompletionIdleWatchArmed = true;
3110
3272
  turnCompletionIdleWatchPinnedByTerminalError = options?.pinnedByTerminalError === true;
3273
+ turnCompletionIdleTimeoutOverrideMs = options?.timeoutMs !== void 0 ? Math.max(1, Math.floor(options.timeoutMs)) : void 0;
3111
3274
  scheduleTurnCompletionIdleWatch();
3112
3275
  };
3113
3276
  const emitLifecycleStart = () => {
@@ -3162,6 +3325,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
3162
3325
  itemId: item.id
3163
3326
  });
3164
3327
  };
3328
+ const isTerminalTurnNotificationForTurn = (notification, notificationTurnId) => {
3329
+ if (!isTurnNotification(notification.params, thread.threadId, notificationTurnId)) return false;
3330
+ return notification.method === "turn/completed" || isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt });
3331
+ };
3165
3332
  const handleNotification = async (notification) => {
3166
3333
  userInputBridge?.handleNotification(notification);
3167
3334
  if (!projector || !turnId) {
@@ -3183,6 +3350,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3183
3350
  const rawToolOutputCompletion = isRawToolOutputCompletionNotification(notification);
3184
3351
  if (isCurrentTurnNotification && (rawToolOutputCompletion || isNativeToolProgressNotification(notification))) turnCrossedToolHandoff = true;
3185
3352
  const assistantCompletionCanRelease = isAssistantCompletionReleaseNotification(notification, turnCrossedToolHandoff);
3353
+ const postToolRawAssistantCompletionNeedsTerminalGuard = isCurrentTurnNotification && turnCrossedToolHandoff && isRawAssistantCompletionNotification(notification) && activeTurnItemIds.size === 0;
3186
3354
  const shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem = isCurrentTurnNotification && notification.method === "item/completed" && activeTurnItemIds.size === 0 && !trackedDynamicToolCompletion && !assistantCompletionCanRelease;
3187
3355
  if (isCurrentTurnNotification && notification.method === "error") {
3188
3356
  if (isRetryableErrorNotification(notification.params)) disarmTurnCompletionIdleWatch();
@@ -3190,13 +3358,15 @@ async function runCodexAppServerAttempt(params, options = {}) {
3190
3358
  disarmTurnAssistantCompletionIdleWatch();
3191
3359
  } else if (isTurnCompletion) disarmTurnAssistantCompletionIdleWatch();
3192
3360
  else if (isCurrentTurnNotification && assistantCompletionCanRelease) armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
3361
+ else if (postToolRawAssistantCompletionNeedsTerminalGuard) armTurnCompletionIdleWatch({ timeoutMs: turnAssistantCompletionIdleTimeoutMs });
3193
3362
  else if (unblockedAssistantCompletionRelease) armTurnAssistantCompletionIdleWatch(describeNotificationActivity(notification));
3194
3363
  else if (shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) armTurnCompletionIdleWatch();
3195
3364
  else if (isCurrentTurnNotification && rawToolOutputCompletion) armTurnCompletionIdleWatch();
3196
3365
  else if (isCurrentTurnNotification && shouldDisarmAssistantCompletionIdleWatch(notification)) disarmTurnAssistantCompletionIdleWatch();
3197
- if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification && !trackedDynamicToolCompletion && !rawToolOutputCompletion && !shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) disarmTurnCompletionIdleWatch();
3366
+ if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification && !trackedDynamicToolCompletion && !rawToolOutputCompletion && !postToolRawAssistantCompletionNeedsTerminalGuard && !shouldRearmCompletionIdleWatchAfterLastCurrentTurnItem) disarmTurnCompletionIdleWatch();
3198
3367
  const isTurnAbortMarker = isCurrentTurnNotification && isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt });
3199
- const isTurnTerminal = isTurnCompletion || isTurnAbortMarker;
3368
+ const isTurnTerminal = isTerminalTurnNotificationForTurn(notification, turnId);
3369
+ if (isTurnTerminal) terminalTurnNotificationQueued = true;
3200
3370
  try {
3201
3371
  await waitForCodexNotificationDispatchTurn();
3202
3372
  await projector.handleNotification(notification);
@@ -3223,6 +3393,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3223
3393
  pendingNotifications.push(notification);
3224
3394
  return Promise.resolve();
3225
3395
  }
3396
+ if (isTerminalTurnNotificationForTurn(notification, turnId)) terminalTurnNotificationQueued = true;
3226
3397
  notificationQueue = notificationQueue.then(() => handleNotification(notification), () => handleNotification(notification));
3227
3398
  return notificationQueue;
3228
3399
  };
@@ -3280,6 +3451,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3280
3451
  paramsForRun: params,
3281
3452
  threadId: thread.threadId,
3282
3453
  turnId,
3454
+ nativeHookRelay,
3283
3455
  signal: runAbortController.signal
3284
3456
  });
3285
3457
  }
@@ -3375,6 +3547,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3375
3547
  } else scheduleTurnProgressWatches();
3376
3548
  }
3377
3549
  });
3550
+ let closeCleanup;
3378
3551
  const forceContextEngineCompactionForCodexOverflow = async (error) => {
3379
3552
  if (!activeContextEngine?.info.ownsCompaction) return false;
3380
3553
  embeddedAgentLog.warn("codex app-server context-engine turn overflowed; forcing context-engine compaction", {
@@ -3520,6 +3693,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
3520
3693
  signal: runAbortController.signal
3521
3694
  });
3522
3695
  const turnStartErrorMessage = usageLimitError?.message ?? formatErrorMessage(turnStartError);
3696
+ if (isInvalidCodexImagePayloadError(turnStartErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(activeSessionFile, {
3697
+ phase: "turn_start",
3698
+ threadId: thread.threadId,
3699
+ error: turnStartErrorMessage
3700
+ });
3523
3701
  emitCodexAppServerEvent(params, {
3524
3702
  stream: "codex_app_server.lifecycle",
3525
3703
  data: {
@@ -3602,7 +3780,31 @@ async function runCodexAppServerAttempt(params, options = {}) {
3602
3780
  prompt: promptBuild.prompt,
3603
3781
  imagesCount: params.images?.length ?? 0
3604
3782
  });
3605
- projector = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId, { nativePostToolUseRelayEnabled: nativeHookRelay?.allowedEvents.includes("post_tool_use") === true });
3783
+ projector = new CodexAppServerEventProjector(params, thread.threadId, activeTurnId, {
3784
+ nativePostToolUseRelayEnabled: nativeHookRelay?.allowedEvents.includes("post_tool_use") === true && nativeHookRelay.shouldRelayEvent("post_tool_use"),
3785
+ trajectoryRecorder
3786
+ });
3787
+ if (isTerminalTurnStatus(turn.turn.status) || pendingNotifications.some((notification) => isTerminalTurnNotificationForTurn(notification, activeTurnId))) terminalTurnNotificationQueued = true;
3788
+ closeCleanup = client.addCloseHandler?.(() => {
3789
+ if (completed || terminalTurnNotificationQueued || runAbortController.signal.aborted) return;
3790
+ clientClosedPromptError = "codex app-server client closed before turn completed";
3791
+ trajectoryRecorder?.recordEvent("turn.client_closed", {
3792
+ threadId: thread.threadId,
3793
+ turnId: activeTurnId
3794
+ });
3795
+ embeddedAgentLog.warn("codex app-server client closed before turn completed", {
3796
+ threadId: thread.threadId,
3797
+ turnId: activeTurnId
3798
+ });
3799
+ clientClosedAbort = true;
3800
+ runAbortController.abort("client_closed");
3801
+ completed = true;
3802
+ clearTurnAttemptIdleTimer();
3803
+ clearTurnCompletionIdleTimer();
3804
+ clearTurnAssistantCompletionIdleTimer();
3805
+ clearTurnTerminalIdleTimer();
3806
+ resolveCompletion?.();
3807
+ });
3606
3808
  emitLifecycleStart();
3607
3809
  const activeProjector = projector;
3608
3810
  turnTerminalIdleWatchArmed = true;
@@ -3655,9 +3857,15 @@ async function runCodexAppServerAttempt(params, options = {}) {
3655
3857
  try {
3656
3858
  await completion;
3657
3859
  const result = activeProjector.buildResult(toolBridge.telemetry, { yieldDetected });
3658
- const finalAborted = result.aborted || runAbortController.signal.aborted;
3659
- let finalPromptError = turnCompletionIdleTimedOut ? turnCompletionIdleTimeoutMessage : timedOut ? "codex app-server attempt timed out" : result.promptError;
3860
+ const finalAborted = result.aborted || runAbortController.signal.aborted && !clientClosedAbort;
3861
+ let finalPromptError = clientClosedPromptError ?? (turnCompletionIdleTimedOut ? turnCompletionIdleTimeoutMessage : timedOut ? "codex app-server attempt timed out" : result.promptError);
3660
3862
  const finalPromptErrorMessage = typeof finalPromptError === "string" ? finalPromptError : finalPromptError ? formatErrorMessage(finalPromptError) : void 0;
3863
+ if (isInvalidCodexImagePayloadError(finalPromptErrorMessage)) await clearCodexBindingAfterInvalidImagePayload(activeSessionFile, {
3864
+ phase: "turn_completed",
3865
+ threadId: thread.threadId,
3866
+ turnId: activeTurnId,
3867
+ error: finalPromptErrorMessage
3868
+ });
3661
3869
  if (shouldRefreshCodexRateLimitsForUsageLimitMessage(finalPromptErrorMessage)) finalPromptError = await refreshCodexUsageLimitErrorMessage({
3662
3870
  client,
3663
3871
  source: {
@@ -3668,7 +3876,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3668
3876
  timeoutMs: appServer.requestTimeoutMs,
3669
3877
  signal: runAbortController.signal
3670
3878
  });
3671
- const finalPromptErrorSource = timedOut ? "prompt" : result.promptErrorSource;
3879
+ const finalPromptErrorSource = timedOut || clientClosedPromptError ? "prompt" : result.promptErrorSource;
3672
3880
  recordCodexTrajectoryCompletion(trajectoryRecorder, {
3673
3881
  attempt: params,
3674
3882
  result,
@@ -3774,11 +3982,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
3774
3982
  error: "codex app-server run completed without lifecycle terminal event"
3775
3983
  });
3776
3984
  if (trajectoryRecorder && !trajectoryEndRecorded) trajectoryRecorder.recordEvent("session.ended", {
3777
- status: timedOut || runAbortController.signal.aborted ? "interrupted" : "cleanup",
3985
+ status: timedOut || runAbortController.signal.aborted && !clientClosedAbort ? "interrupted" : "cleanup",
3778
3986
  threadId: thread.threadId,
3779
3987
  turnId: activeTurnId,
3780
3988
  timedOut,
3781
- aborted: runAbortController.signal.aborted
3989
+ aborted: runAbortController.signal.aborted && !clientClosedAbort
3782
3990
  });
3783
3991
  await runAgentCleanupStep({
3784
3992
  runId: params.runId,
@@ -3797,6 +4005,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3797
4005
  clearTurnTerminalIdleTimer();
3798
4006
  notificationCleanup();
3799
4007
  requestCleanup();
4008
+ closeCleanup?.();
3800
4009
  nativeHookRelay?.unregister();
3801
4010
  runAbortController.signal.removeEventListener("abort", abortListener);
3802
4011
  params.abortSignal?.removeEventListener("abort", abortFromUpstream);
@@ -3956,6 +4165,7 @@ function createCodexNativeHookRelay(params) {
3956
4165
  ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3957
4166
  ...params.config ? { config: params.config } : {},
3958
4167
  runId: params.runId,
4168
+ ...params.channelId ? { channelId: params.channelId } : {},
3959
4169
  allowedEvents: params.events,
3960
4170
  ttlMs: resolveCodexNativeHookRelayTtlMs({
3961
4171
  explicitTtlMs: params.options?.ttlMs,
@@ -4019,6 +4229,15 @@ function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
4019
4229
  runSessionKey: params.sessionKey && params.sessionKey !== sandboxSessionKey ? params.sessionKey : void 0
4020
4230
  };
4021
4231
  }
4232
+ function resolveCodexAppServerHookChannelId(params, sandboxSessionKey) {
4233
+ return buildAgentHookContextChannelFields({
4234
+ sessionKey: sandboxSessionKey,
4235
+ messageChannel: params.messageChannel,
4236
+ messageProvider: params.messageProvider,
4237
+ currentChannelId: params.currentChannelId,
4238
+ messageTo: params.messageTo
4239
+ }).channelId;
4240
+ }
4022
4241
  function buildOpenClawCodingToolsOptions(input) {
4023
4242
  const { params } = input;
4024
4243
  const modelHasVision = params.model.input?.includes("image") ?? false;
@@ -4045,7 +4264,7 @@ function buildOpenClawCodingToolsOptions(input) {
4045
4264
  senderUsername: params.senderUsername,
4046
4265
  senderE164: params.senderE164,
4047
4266
  senderIsOwner: params.senderIsOwner,
4048
- allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,
4267
+ allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(),
4049
4268
  ...sessionKeys,
4050
4269
  sessionId: params.sessionId,
4051
4270
  runId: params.runId,
@@ -4066,6 +4285,7 @@ function buildOpenClawCodingToolsOptions(input) {
4066
4285
  modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, void 0, { workspaceDir: input.effectiveWorkspace }),
4067
4286
  suppressManagedWebSearch: false,
4068
4287
  currentChannelId: params.currentChannelId,
4288
+ hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
4069
4289
  currentThreadTs: params.currentThreadTs,
4070
4290
  currentMessageId: params.currentMessageId,
4071
4291
  replyToMode: params.replyToMode,
@@ -4324,6 +4544,23 @@ function readCodexErrorPayload(error) {
4324
4544
  rateLimits
4325
4545
  };
4326
4546
  }
4547
+ function isInvalidCodexImagePayloadError(message) {
4548
+ if (typeof message !== "string" || !message.trim()) return false;
4549
+ const normalizedMessage = message.replace(/[_-]+/gu, " ");
4550
+ return /\b(?:invalid|malformed)\b[\s\S]{0,120}\b(?:image|image url|base64)\b/iu.test(normalizedMessage) || /\b(?:image|image url|base64)\b[\s\S]{0,120}\b(?:invalid|malformed)\b/iu.test(normalizedMessage);
4551
+ }
4552
+ async function clearCodexBindingAfterInvalidImagePayload(sessionFile, fields) {
4553
+ const currentBinding = await readCodexAppServerBinding(sessionFile);
4554
+ if (fields.threadId && currentBinding && currentBinding.threadId !== fields.threadId) {
4555
+ embeddedAgentLog.warn("codex app-server image payload error detected for unbound thread; preserving thread binding", {
4556
+ ...fields,
4557
+ boundThreadId: currentBinding.threadId
4558
+ });
4559
+ return;
4560
+ }
4561
+ embeddedAgentLog.warn("codex app-server image payload error detected; clearing thread binding", fields);
4562
+ await clearCodexAppServerBinding(sessionFile);
4563
+ }
4327
4564
  function describeNotificationActivity(notification) {
4328
4565
  if (!isJsonObject(notification.params)) return { lastNotificationMethod: notification.method };
4329
4566
  if (notification.method !== "rawResponseItem/completed") return { lastNotificationMethod: notification.method };
@@ -4692,7 +4929,7 @@ function codexExecutionToolName(item) {
4692
4929
  function joinPresentSections(...sections) {
4693
4930
  return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
4694
4931
  }
4695
- function prependCurrentTurnContext(prompt, context) {
4932
+ function prependCurrentInboundContext(prompt, context) {
4696
4933
  const text = context?.text.trim();
4697
4934
  return text ? [text, prompt].filter(Boolean).join("\n\n") : prompt;
4698
4935
  }
@@ -4708,6 +4945,7 @@ function handleApprovalRequest(params) {
4708
4945
  paramsForRun: params.paramsForRun,
4709
4946
  threadId: params.threadId,
4710
4947
  turnId: params.turnId,
4948
+ nativeHookRelay: params.nativeHookRelay,
4711
4949
  signal: params.signal
4712
4950
  });
4713
4951
  }