@openclaw/codex 2026.5.7 → 2026.5.10-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.
@@ -1,22 +1,25 @@
1
- import { i as resolveCodexAppServerRuntimeOptions, r as readCodexPluginConfig } from "./config-ByrA30No.js";
2
- import { i as readCodexDynamicToolCallParams, r as assertCodexTurnStartResponse, s as readCodexTurn } from "./protocol-validators-Dky2yV4W.js";
1
+ import { l as resolveCodexPluginsPolicy, o as readCodexPluginConfig, s as resolveCodexAppServerRuntimeOptions, u as withMcpElicitationsApprovalPolicy } from "./config-CT01BBDc.js";
2
+ import { i as readCodexDynamicToolCallParams, r as assertCodexTurnStartResponse, s as readCodexTurn } from "./protocol-validators-CbqWfY5M.js";
3
3
  import { t as isJsonObject } from "./protocol-C9UWI98H.js";
4
- import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-BGbqC7jk.js";
5
- import { r as formatCodexDisplayText, u as formatCodexUsageLimitErrorMessage } from "./command-formatters-PiJcdUbu.js";
6
- import { a as refreshCodexAppServerAuthTokens, o as resolveCodexAppServerAuthProfileId, s as resolveCodexAppServerAuthProfileIdForAgent, t as clearSharedCodexAppServerClientIfCurrent } from "./shared-client-Dfk3Enm-.js";
7
- import { i as readCodexAppServerBinding } from "./session-binding-DuJYTJQy.js";
8
- import { a as buildTurnStartParams, c as createCodexDynamicToolBridge, l as applyCodexDynamicToolProfile, n as buildDeveloperInstructions, o as codexDynamicToolsFingerprint, s as startOrResumeThread, t as areCodexDynamicToolFingerprintsCompatible } from "./thread-lifecycle-CzllX4PU.js";
9
- import { n as defaultCodexAppServerClientFactory, t as createCodexAppServerClientFactoryTestHooks } from "./client-factory--cll1Fba.js";
10
- import { n as rememberCodexRateLimits, r as ensureCodexComputerUse, t as readRecentCodexRateLimits } from "./rate-limit-cache-t6ebYmfS.js";
4
+ import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-CTzy3Y6M.js";
5
+ import { d as formatCodexUsageLimitErrorMessage, i as formatCodexDisplayText } from "./request-DC1Dz3iZ.js";
6
+ import { a as refreshCodexAppServerAuthTokens, c as resolveCodexAppServerAuthProfileIdForAgent, l as resolveCodexAppServerEnvApiKeyCacheKey, o as resolveCodexAppServerAuthAccountCacheKey, s as resolveCodexAppServerAuthProfileId, t as clearSharedCodexAppServerClientIfCurrent, u as resolveCodexAppServerHomeDir } from "./shared-client-p-TvEiNL.js";
7
+ import { i as readCodexAppServerBinding } from "./session-binding-B44KIZM2.js";
8
+ import { i as defaultCodexAppInventoryCache, r as buildCodexAppInventoryCacheKey } from "./plugin-activation-CweAZa7r.js";
9
+ import { a as buildTurnStartParams, c as buildCodexPluginThreadConfig, d as createCodexDynamicToolBridge, f as filterCodexDynamicTools, l as buildCodexPluginThreadConfigInputFingerprint, n as buildDeveloperInstructions, o as codexDynamicToolsFingerprint, p as normalizeCodexDynamicToolName, s as startOrResumeThread, t as areCodexDynamicToolFingerprintsCompatible, u as shouldBuildCodexPluginThreadConfig } from "./thread-lifecycle-DtD_qoMW.js";
10
+ import { n as defaultCodexAppServerClientFactory } from "./client-factory-BJL_efz4.js";
11
+ import { n as rememberCodexRateLimits, r as ensureCodexComputerUse, t as readRecentCodexRateLimits } from "./rate-limit-cache-DbZvmrAD.js";
11
12
  import { createHash } from "node:crypto";
12
- import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, callGatewayTool, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, embeddedAgentLog, emitAgentEvent, emitSessionTranscriptUpdate, finalizeHarnessContextEngineTurn, formatApprovalDisplayPath, formatErrorMessage, formatToolAggregate, formatToolProgressOutput, inferToolMetaFromArgs, isActiveHarnessContextEngine, isSubagentSessionKey, normalizeAgentRuntimeTools, normalizeUsage, registerNativeHookRelay, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveModelAuthMode, resolveOpenClawAgentDir, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentCleanupStep, runAgentHarnessAfterCompactionHook, runAgentHarnessAgentEndHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
13
+ import nodeFs from "node:fs";
14
+ import { TOOL_PROGRESS_OUTPUT_MAX_CHARS, acquireSessionWriteLock, appendSessionTranscriptMessage, assembleHarnessContextEngine, bootstrapHarnessContextEngine, buildEmbeddedAttemptToolRunContext, buildHarnessContextEngineRuntimeContext, buildHarnessContextEngineRuntimeContextFromUsage, callGatewayTool, classifyAgentHarnessTerminalOutcome, clearActiveEmbeddedRun, embeddedAgentLog, emitAgentEvent, emitSessionTranscriptUpdate, finalizeHarnessContextEngineTurn, formatApprovalDisplayPath, formatErrorMessage, formatToolAggregate, formatToolProgressOutput, inferToolMetaFromArgs, isActiveHarnessContextEngine, isSubagentSessionKey, normalizeAgentRuntimeTools, normalizeUsage, registerNativeHookRelay, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentCleanupStep, runAgentHarnessAfterCompactionHook, runAgentHarnessAgentEndHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, runHarnessContextEngineMaintenance, setActiveEmbeddedRun, supportsModelTools } from "openclaw/plugin-sdk/agent-harness-runtime";
13
15
  import fs from "node:fs/promises";
14
16
  import path from "node:path";
15
- import nodeFs from "node:fs";
17
+ import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
18
+ import { appendRegularFile, pathExists } from "openclaw/plugin-sdk/security-runtime";
19
+ import { AsyncLocalStorage } from "node:async_hooks";
16
20
  import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
17
21
  import { buildSessionContext, migrateSessionEntries, parseSessionEntries } from "@mariozechner/pi-coding-agent";
18
22
  import { redactSensitiveFieldValue, redactToolPayloadText } from "openclaw/plugin-sdk/text-runtime";
19
- import { resolveUserPath as resolveUserPath$1 } from "openclaw/plugin-sdk/agent-harness";
20
23
  //#region extensions/codex/src/app-server/plugin-approval-roundtrip.ts
21
24
  const DEFAULT_CODEX_APPROVAL_TIMEOUT_MS = 12e4;
22
25
  const MAX_PLUGIN_APPROVAL_TITLE_LENGTH = 80;
@@ -92,7 +95,7 @@ const INVISIBLE_FORMATTING_CONTROL_RE$1 = new RegExp(String.raw`[\u00ad\u034f\u0
92
95
  const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE$1 = new RegExp(String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`);
93
96
  async function handleCodexAppServerApprovalRequest(params) {
94
97
  const requestParams = isJsonObject(params.requestParams) ? params.requestParams : void 0;
95
- if (!matchesCurrentTurn$1(requestParams, params.threadId, params.turnId)) return;
98
+ if (!matchesCurrentTurn(requestParams, params.threadId, params.turnId)) return;
96
99
  if (!isSupportedAppServerApprovalMethod(params.method)) return unsupportedApprovalResponse();
97
100
  const context = buildApprovalContext({
98
101
  method: params.method,
@@ -176,7 +179,7 @@ function buildApprovalResponse(method, requestParams, outcome) {
176
179
  }
177
180
  return unsupportedApprovalResponse();
178
181
  }
179
- function matchesCurrentTurn$1(requestParams, threadId, turnId) {
182
+ function matchesCurrentTurn(requestParams, threadId, turnId) {
180
183
  if (!requestParams) return false;
181
184
  const requestThreadId = readString$4(requestParams, "threadId") ?? readString$4(requestParams, "conversationId");
182
185
  const requestTurnId = readString$4(requestParams, "turnId");
@@ -636,6 +639,29 @@ const MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY = "connector_name";
636
639
  const MCP_TOOL_APPROVAL_TOOL_TITLE_KEY = "tool_title";
637
640
  const MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY = "tool_description";
638
641
  const MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY = "tool_params_display";
642
+ const PLUGIN_APP_ID_META_KEYS = [
643
+ "app_id",
644
+ "appId",
645
+ "codex_app_id",
646
+ "codexAppId"
647
+ ];
648
+ const PLUGIN_NAME_META_KEYS = [
649
+ "plugin_name",
650
+ "pluginName",
651
+ "codex_plugin_name",
652
+ "codexPluginName"
653
+ ];
654
+ const PLUGIN_CONFIG_KEY_META_KEYS = [
655
+ "config_key",
656
+ "configKey",
657
+ "codex_config_key"
658
+ ];
659
+ const PLUGIN_MARKETPLACE_NAME_META_KEYS = [
660
+ "marketplace_name",
661
+ "marketplaceName",
662
+ "codex_marketplace_name",
663
+ "codexMarketplaceName"
664
+ ];
639
665
  const MAX_DISPLAY_PARAM_ENTRIES = 8;
640
666
  const MAX_DISPLAY_PARAM_VALUE_LENGTH = 120;
641
667
  const MAX_DISPLAY_VALUE_ARRAY_ITEMS = 8;
@@ -649,7 +675,24 @@ const INVISIBLE_FORMATTING_CONTROL_RE = new RegExp(String.raw`[\u00ad\u034f\u061
649
675
  const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE = new RegExp(String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`);
650
676
  async function handleCodexAppServerElicitationRequest(params) {
651
677
  const requestParams = isJsonObject(params.requestParams) ? params.requestParams : void 0;
652
- if (!matchesCurrentTurn(requestParams, params.threadId, params.turnId)) return;
678
+ if (!requestParams) return;
679
+ if (!matchesCurrentThread(requestParams, params.threadId)) return;
680
+ if (turnIdMismatches(requestParams, params.turnId)) return;
681
+ const pluginResolution = resolvePluginElicitation({
682
+ requestParams,
683
+ pluginAppPolicyContext: params.pluginAppPolicyContext
684
+ });
685
+ if (pluginResolution.kind !== "not_plugin") {
686
+ if (pluginResolution.kind === "decline") {
687
+ logPluginElicitationDecline(pluginResolution.reason, requestParams);
688
+ return declineElicitationResponse();
689
+ }
690
+ if (!hasExactTurnId(requestParams, params.turnId)) {
691
+ logPluginElicitationDecline("missing_active_turn", requestParams);
692
+ return declineElicitationResponse();
693
+ }
694
+ return buildPluginPolicyElicitationResponse(pluginResolution.entry, requestParams);
695
+ }
653
696
  const approvalPrompt = readBridgeableApprovalElicitation(requestParams);
654
697
  if (!approvalPrompt) return;
655
698
  const outcome = await requestPluginApprovalOutcome({
@@ -660,12 +703,113 @@ async function handleCodexAppServerElicitationRequest(params) {
660
703
  });
661
704
  return buildElicitationResponse(approvalPrompt.requestedSchema, approvalPrompt.meta, outcome);
662
705
  }
663
- function matchesCurrentTurn(requestParams, threadId, turnId) {
706
+ function matchesCurrentThread(requestParams, threadId) {
664
707
  if (!requestParams) return false;
665
- if (readString$3(requestParams, "threadId") !== threadId) return false;
666
- const rawTurnId = requestParams.turnId;
667
- if (rawTurnId !== null && rawTurnId !== void 0 && rawTurnId !== turnId) return false;
668
- return true;
708
+ return readString$3(requestParams, "threadId") === threadId;
709
+ }
710
+ function turnIdMismatches(requestParams, turnId) {
711
+ const rawTurnId = requestParams?.turnId;
712
+ return rawTurnId !== null && rawTurnId !== void 0 && rawTurnId !== turnId;
713
+ }
714
+ function hasExactTurnId(requestParams, turnId) {
715
+ return requestParams?.turnId === turnId;
716
+ }
717
+ function resolvePluginElicitation(params) {
718
+ const requestParams = params.requestParams;
719
+ if (!requestParams) return { kind: "not_plugin" };
720
+ const meta = isJsonObject(requestParams._meta) ? requestParams._meta : {};
721
+ const context = params.pluginAppPolicyContext;
722
+ const entries = context ? Object.values(context.apps) : [];
723
+ const appId = readFirstString(meta, PLUGIN_APP_ID_META_KEYS) ?? readFirstString(requestParams, PLUGIN_APP_ID_META_KEYS);
724
+ if (appId) {
725
+ if (!context) return {
726
+ kind: "decline",
727
+ reason: "missing_policy_context"
728
+ };
729
+ const entry = context.apps[appId];
730
+ return uniquePluginMatch(entry ? [entry] : [], "app_id");
731
+ }
732
+ const serverName = readString$3(requestParams, "serverName");
733
+ if (serverName && context) {
734
+ const matches = entries.filter((entry) => entry.mcpServerNames.includes(serverName));
735
+ if (matches.length > 0) return uniquePluginMatch(matches, "server_name");
736
+ }
737
+ const metadataResolution = resolvePluginStableMetadataMatch({
738
+ meta,
739
+ requestParams,
740
+ entries,
741
+ context
742
+ });
743
+ if (metadataResolution.kind !== "not_plugin") return metadataResolution;
744
+ if (context && hasDisplayNameOnlyPluginMatch(meta, entries)) return {
745
+ kind: "decline",
746
+ reason: "display_name_only"
747
+ };
748
+ return { kind: "not_plugin" };
749
+ }
750
+ function resolvePluginStableMetadataMatch(params) {
751
+ const pluginName = readFirstString(params.meta, PLUGIN_NAME_META_KEYS) ?? readFirstString(params.requestParams, PLUGIN_NAME_META_KEYS);
752
+ const configKey = readFirstString(params.meta, PLUGIN_CONFIG_KEY_META_KEYS) ?? readFirstString(params.requestParams, PLUGIN_CONFIG_KEY_META_KEYS);
753
+ const marketplaceName = readFirstString(params.meta, PLUGIN_MARKETPLACE_NAME_META_KEYS) ?? readFirstString(params.requestParams, PLUGIN_MARKETPLACE_NAME_META_KEYS);
754
+ if (!pluginName && !configKey) return { kind: "not_plugin" };
755
+ if (!params.context) return {
756
+ kind: "decline",
757
+ reason: "missing_policy_context"
758
+ };
759
+ return uniquePluginMatch(params.entries.filter((entry) => {
760
+ if (marketplaceName && entry.marketplaceName !== marketplaceName) return false;
761
+ if (pluginName && entry.pluginName !== pluginName) return false;
762
+ if (configKey && entry.configKey !== configKey) return false;
763
+ return true;
764
+ }), "metadata");
765
+ }
766
+ function uniquePluginMatch(matches, source) {
767
+ if (matches.length === 1 && matches[0]) return {
768
+ kind: "matched",
769
+ entry: matches[0]
770
+ };
771
+ return {
772
+ kind: "decline",
773
+ reason: matches.length === 0 ? `${source}_not_enabled` : `${source}_ambiguous`
774
+ };
775
+ }
776
+ function hasDisplayNameOnlyPluginMatch(meta, entries) {
777
+ const connectorName = readString$3(meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY);
778
+ if (!connectorName) return false;
779
+ const normalized = normalizePluginIdentityText(connectorName);
780
+ return entries.some((entry) => normalizePluginIdentityText(entry.pluginName) === normalized || normalizePluginIdentityText(entry.configKey) === normalized);
781
+ }
782
+ function normalizePluginIdentityText(value) {
783
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
784
+ }
785
+ function buildPluginPolicyElicitationResponse(entry, requestParams) {
786
+ if (!entry.allowDestructiveActions) {
787
+ logPluginElicitationDecline("destructive_actions_disabled", requestParams);
788
+ return declineElicitationResponse();
789
+ }
790
+ if (readString$3(requestParams, "mode") !== "form" || !isJsonObject(requestParams.requestedSchema)) {
791
+ logPluginElicitationDecline("unsupported_schema", requestParams);
792
+ return declineElicitationResponse();
793
+ }
794
+ const meta = isJsonObject(requestParams._meta) ? requestParams._meta : {};
795
+ const response = buildElicitationResponse(requestParams.requestedSchema, meta, "approved-once");
796
+ if (isJsonObject(response) && response.action === "accept") return response;
797
+ logPluginElicitationDecline("unmappable_schema", requestParams);
798
+ return declineElicitationResponse();
799
+ }
800
+ function declineElicitationResponse() {
801
+ return {
802
+ action: "decline",
803
+ content: null,
804
+ _meta: null
805
+ };
806
+ }
807
+ function logPluginElicitationDecline(reason, requestParams) {
808
+ embeddedAgentLog.debug("codex plugin elicitation declined", {
809
+ reason,
810
+ serverName: readString$3(requestParams, "serverName"),
811
+ mode: readString$3(requestParams, "mode")
812
+ });
669
813
  }
670
814
  function readBridgeableApprovalElicitation(requestParams) {
671
815
  if (!requestParams || readString$3(requestParams, "mode") !== "form" || !isJsonObject(requestParams._meta) || requestParams._meta[MCP_TOOL_APPROVAL_KIND_KEY] !== MCP_TOOL_APPROVAL_KIND || !isJsonObject(requestParams.requestedSchema)) return;
@@ -944,6 +1088,12 @@ function readString$3(record, key) {
944
1088
  const value = record?.[key];
945
1089
  return typeof value === "string" && value.trim() ? value : void 0;
946
1090
  }
1091
+ function readFirstString(record, keys) {
1092
+ for (const key of keys) {
1093
+ const value = readString$3(record, key);
1094
+ if (value) return value;
1095
+ }
1096
+ }
947
1097
  //#endregion
948
1098
  //#region extensions/codex/src/app-server/session-history.ts
949
1099
  function isMissingFileError(error) {
@@ -1038,7 +1188,7 @@ function buildMirrorDedupeIdentity(message) {
1038
1188
  return `${message.role}:${fingerprintMirrorMessageContent(message)}`;
1039
1189
  }
1040
1190
  async function mirrorCodexAppServerTranscript(params) {
1041
- const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant");
1191
+ const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
1042
1192
  if (messages.length === 0) return;
1043
1193
  const lock = await acquireSessionWriteLock({
1044
1194
  sessionFile: params.sessionFile,
@@ -1130,6 +1280,7 @@ const CODEX_PROMPT_TOTAL_INPUT_KEYS = [
1130
1280
  "prompt_tokens"
1131
1281
  ];
1132
1282
  const MAX_TOOL_OUTPUT_DELTA_MESSAGES_PER_ITEM = 20;
1283
+ const TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS = 12e3;
1133
1284
  var CodexAppServerEventProjector = class {
1134
1285
  constructor(params, threadId, turnId) {
1135
1286
  this.params = params;
@@ -1147,6 +1298,9 @@ var CodexAppServerEventProjector = class {
1147
1298
  this.toolResultOutputStreamedItemIds = /* @__PURE__ */ new Set();
1148
1299
  this.toolResultOutputDeltaState = /* @__PURE__ */ new Map();
1149
1300
  this.toolMetas = /* @__PURE__ */ new Map();
1301
+ this.toolTranscriptMessages = [];
1302
+ this.toolTranscriptCallIds = /* @__PURE__ */ new Set();
1303
+ this.toolTranscriptResultIds = /* @__PURE__ */ new Set();
1150
1304
  this.assistantStarted = false;
1151
1305
  this.reasoningStarted = false;
1152
1306
  this.reasoningEnded = false;
@@ -1230,6 +1384,7 @@ var CodexAppServerEventProjector = class {
1230
1384
  }, `${turnId}:prompt`)];
1231
1385
  if (reasoningText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex reasoning", reasoningText), `${turnId}:reasoning`));
1232
1386
  if (planText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex plan", planText), `${turnId}:plan`));
1387
+ messagesSnapshot.push(...this.toolTranscriptMessages);
1233
1388
  if (lastAssistant) messagesSnapshot.push(attachCodexMirrorIdentity(lastAssistant, `${turnId}:assistant`));
1234
1389
  const turnFailed = this.completedTurn?.status === "failed";
1235
1390
  const turnInterrupted = this.completedTurn?.status === "interrupted";
@@ -1282,6 +1437,21 @@ var CodexAppServerEventProjector = class {
1282
1437
  didSendDeterministicApprovalPrompt: this.guardianReviewCount > 0 ? false : void 0
1283
1438
  };
1284
1439
  }
1440
+ recordDynamicToolCall(params) {
1441
+ this.recordToolTranscriptCall({
1442
+ id: params.callId,
1443
+ name: params.tool,
1444
+ arguments: sanitizeCodexToolArguments(params.arguments)
1445
+ });
1446
+ }
1447
+ recordDynamicToolResult(params) {
1448
+ this.recordToolTranscriptResult({
1449
+ id: params.callId,
1450
+ name: params.tool,
1451
+ text: collectDynamicToolContentText(params.contentItems),
1452
+ isError: !params.success
1453
+ });
1454
+ }
1285
1455
  markTimedOut() {
1286
1456
  this.aborted = true;
1287
1457
  this.promptError = "codex app-server attempt timed out";
@@ -1373,6 +1543,7 @@ var CodexAppServerEventProjector = class {
1373
1543
  phase: "start",
1374
1544
  item
1375
1545
  });
1546
+ this.recordNativeToolTranscriptCall(item);
1376
1547
  this.emitToolResultSummary(item);
1377
1548
  this.emitAgentEvent({
1378
1549
  stream: "codex_app_server.item",
@@ -1439,6 +1610,8 @@ var CodexAppServerEventProjector = class {
1439
1610
  phase: "result",
1440
1611
  item
1441
1612
  });
1613
+ this.recordNativeToolTranscriptCall(item);
1614
+ this.recordNativeToolTranscriptResult(item);
1442
1615
  this.emitToolResultSummary(item);
1443
1616
  this.emitToolResultOutput(item);
1444
1617
  this.emitAgentEvent({
@@ -1529,6 +1702,8 @@ var CodexAppServerEventProjector = class {
1529
1702
  });
1530
1703
  }
1531
1704
  this.recordToolMeta(item);
1705
+ this.recordNativeToolTranscriptCall(item);
1706
+ this.recordNativeToolTranscriptResult(item);
1532
1707
  this.emitToolResultSummary(item);
1533
1708
  this.emitToolResultOutput(item);
1534
1709
  }
@@ -1693,6 +1868,37 @@ var CodexAppServerEventProjector = class {
1693
1868
  ...meta ? { meta } : {}
1694
1869
  });
1695
1870
  }
1871
+ recordNativeToolTranscriptCall(item) {
1872
+ if (!item || !shouldSynthesizeToolProgressForItem(item)) return;
1873
+ const name = itemName(item);
1874
+ if (!name) return;
1875
+ this.recordToolTranscriptCall({
1876
+ id: item.id,
1877
+ name,
1878
+ arguments: itemToolArgs(item)
1879
+ });
1880
+ }
1881
+ recordNativeToolTranscriptResult(item) {
1882
+ if (!item || !shouldSynthesizeToolProgressForItem(item)) return;
1883
+ const name = itemName(item);
1884
+ if (!name) return;
1885
+ this.recordToolTranscriptResult({
1886
+ id: item.id,
1887
+ name,
1888
+ text: itemTranscriptResultText(item),
1889
+ isError: isNonSuccessItemStatus(itemStatus(item))
1890
+ });
1891
+ }
1892
+ recordToolTranscriptCall(params) {
1893
+ if (!params.id || !params.name || this.toolTranscriptCallIds.has(params.id)) return;
1894
+ this.toolTranscriptCallIds.add(params.id);
1895
+ this.toolTranscriptMessages.push(attachCodexMirrorIdentity(this.createToolCallMessage(params), `${this.turnId}:tool:${params.id}:call`));
1896
+ }
1897
+ recordToolTranscriptResult(params) {
1898
+ if (!params.id || !params.name || this.toolTranscriptResultIds.has(params.id)) return;
1899
+ this.toolTranscriptResultIds.add(params.id);
1900
+ this.toolTranscriptMessages.push(attachCodexMirrorIdentity(this.createToolResultMessage(params), `${this.turnId}:tool:${params.id}:result`));
1901
+ }
1696
1902
  formatCodexErrorMessage(params) {
1697
1903
  const error = isJsonObject(params.error) ? params.error : void 0;
1698
1904
  return formatCodexUsageLimitErrorMessage({
@@ -1779,6 +1985,41 @@ var CodexAppServerEventProjector = class {
1779
1985
  timestamp: Date.now()
1780
1986
  };
1781
1987
  }
1988
+ createToolCallMessage(params) {
1989
+ return {
1990
+ role: "assistant",
1991
+ content: [{
1992
+ type: "toolCall",
1993
+ id: params.id,
1994
+ name: params.name,
1995
+ arguments: normalizeToolTranscriptArguments(params.arguments)
1996
+ }],
1997
+ api: this.params.model.api ?? "openai-codex-responses",
1998
+ provider: this.params.provider,
1999
+ model: this.params.modelId,
2000
+ usage: ZERO_USAGE,
2001
+ stopReason: "toolUse",
2002
+ timestamp: Date.now()
2003
+ };
2004
+ }
2005
+ createToolResultMessage(params) {
2006
+ const text = truncateToolTranscriptText(params.text?.trim() || toolResultStatusText(params));
2007
+ return {
2008
+ role: "toolResult",
2009
+ toolCallId: params.id,
2010
+ toolName: params.name,
2011
+ isError: params.isError,
2012
+ content: [{
2013
+ type: "toolResult",
2014
+ toolCallId: params.id,
2015
+ toolUseId: params.id,
2016
+ tool_use_id: params.id,
2017
+ content: text,
2018
+ text
2019
+ }],
2020
+ timestamp: Date.now()
2021
+ };
2022
+ }
1782
2023
  isNotificationForTurn(params) {
1783
2024
  const threadId = readString$2(params, "threadId");
1784
2025
  const turnId = readNotificationTurnId$1(params);
@@ -1813,13 +2054,13 @@ function readNumber(record, key) {
1813
2054
  const value = record[key];
1814
2055
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1815
2056
  }
1816
- function readBoolean(record, key) {
2057
+ function readBoolean$1(record, key) {
1817
2058
  const value = record[key];
1818
2059
  return typeof value === "boolean" ? value : void 0;
1819
2060
  }
1820
2061
  function readBooleanAlias(record, keys) {
1821
2062
  for (const key of keys) {
1822
- const value = readBoolean(record, key);
2063
+ const value = readBoolean$1(record, key);
1823
2064
  if (value !== void 0) return value;
1824
2065
  }
1825
2066
  }
@@ -2003,6 +2244,16 @@ function itemOutputText(item) {
2003
2244
  return item.result ? stringifyJsonValue(item.result) : void 0;
2004
2245
  }
2005
2246
  }
2247
+ function itemTranscriptResultText(item) {
2248
+ const output = itemOutputText(item);
2249
+ if (output) return output;
2250
+ const result = itemToolResult(item).result;
2251
+ return result ? stringifyJsonValue(result) : itemStatus(item);
2252
+ }
2253
+ function normalizeToolTranscriptArguments(value) {
2254
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2255
+ return value;
2256
+ }
2006
2257
  function collectDynamicToolContentText(contentItems) {
2007
2258
  if (!Array.isArray(contentItems)) return "";
2008
2259
  return contentItems.flatMap((entry) => {
@@ -2011,6 +2262,13 @@ function collectDynamicToolContentText(contentItems) {
2011
2262
  return text ? [text] : [];
2012
2263
  }).join("\n");
2013
2264
  }
2265
+ function truncateToolTranscriptText(text) {
2266
+ if (text.length <= TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS) return text;
2267
+ return `${text.slice(0, TOOL_TRANSCRIPT_OUTPUT_MAX_CHARS)}\n...(truncated)...`;
2268
+ }
2269
+ function toolResultStatusText(params) {
2270
+ return params.isError ? `${params.name} failed` : `${params.name} completed`;
2271
+ }
2014
2272
  function stringifyJsonValue(value) {
2015
2273
  try {
2016
2274
  return JSON.stringify(value, null, 2);
@@ -2111,62 +2369,17 @@ const JWT_VALUE_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]
2111
2369
  const COOKIE_PAIR_RE = /\b([A-Za-z][A-Za-z0-9_.-]{1,64})=([A-Za-z0-9+/._~%=-]{16,})(?=;|\s|$)/gu;
2112
2370
  const TRAJECTORY_RUNTIME_FILE_MAX_BYTES = 50 * 1024 * 1024;
2113
2371
  const TRAJECTORY_RUNTIME_EVENT_MAX_BYTES = 256 * 1024;
2114
- function resolveCodexTrajectoryAppendFlags(constants = nodeFs.constants) {
2115
- const noFollow = constants.O_NOFOLLOW;
2116
- return constants.O_CREAT | constants.O_APPEND | constants.O_WRONLY | (typeof noFollow === "number" ? noFollow : 0);
2117
- }
2118
2372
  function resolveCodexTrajectoryPointerFlags(constants = nodeFs.constants) {
2119
2373
  const noFollow = constants.O_NOFOLLOW;
2120
2374
  return constants.O_CREAT | constants.O_TRUNC | constants.O_WRONLY | (typeof noFollow === "number" ? noFollow : 0);
2121
2375
  }
2122
- async function assertNoSymlinkParents(filePath) {
2123
- const resolvedDir = path.resolve(path.dirname(filePath));
2124
- const parsed = path.parse(resolvedDir);
2125
- const relativeParts = path.relative(parsed.root, resolvedDir).split(path.sep).filter(Boolean);
2126
- let current = parsed.root;
2127
- for (const part of relativeParts) {
2128
- current = path.join(current, part);
2129
- const stat = await fs.lstat(current);
2130
- if (stat.isSymbolicLink()) {
2131
- if (path.dirname(current) === parsed.root) continue;
2132
- throw new Error(`Refusing to write trajectory under symlinked directory: ${current}`);
2133
- }
2134
- if (!stat.isDirectory()) throw new Error(`Refusing to write trajectory under non-directory: ${current}`);
2135
- }
2136
- }
2137
- function verifyStableOpenedTrajectoryFile(params) {
2138
- if (!params.postOpenStat.isFile()) throw new Error(`Refusing to write trajectory to non-file: ${params.filePath}`);
2139
- if (params.postOpenStat.nlink > 1) throw new Error(`Refusing to write trajectory to hardlinked file: ${params.filePath}`);
2140
- const pre = params.preOpenStat;
2141
- if (pre && (pre.dev !== params.postOpenStat.dev || pre.ino !== params.postOpenStat.ino)) throw new Error(`Refusing to write trajectory after file changed: ${params.filePath}`);
2142
- }
2143
2376
  async function safeAppendTrajectoryFile(filePath, line) {
2144
- await assertNoSymlinkParents(filePath);
2145
- let preOpenStat;
2146
- try {
2147
- const stat = await fs.lstat(filePath);
2148
- if (stat.isSymbolicLink()) throw new Error(`Refusing to write trajectory through symlink: ${filePath}`);
2149
- if (!stat.isFile()) throw new Error(`Refusing to write trajectory to non-file: ${filePath}`);
2150
- preOpenStat = stat;
2151
- } catch (err) {
2152
- if (err.code !== "ENOENT") throw err;
2153
- }
2154
- const lineBytes = Buffer.byteLength(line, "utf8");
2155
- if ((preOpenStat?.size ?? 0) + lineBytes > TRAJECTORY_RUNTIME_FILE_MAX_BYTES) return;
2156
- const handle = await fs.open(filePath, resolveCodexTrajectoryAppendFlags(), 384);
2157
- try {
2158
- const stat = await handle.stat();
2159
- verifyStableOpenedTrajectoryFile({
2160
- preOpenStat,
2161
- postOpenStat: stat,
2162
- filePath
2163
- });
2164
- if (stat.size + lineBytes > TRAJECTORY_RUNTIME_FILE_MAX_BYTES) return;
2165
- await handle.chmod(384);
2166
- await handle.appendFile(line, "utf8");
2167
- } finally {
2168
- await handle.close();
2169
- }
2377
+ await appendRegularFile({
2378
+ filePath,
2379
+ content: line,
2380
+ maxFileBytes: TRAJECTORY_RUNTIME_FILE_MAX_BYTES,
2381
+ rejectSymlinkParents: true
2382
+ });
2170
2383
  }
2171
2384
  function boundedTrajectoryLine(event) {
2172
2385
  const line = JSON.stringify(event);
@@ -2289,7 +2502,7 @@ function parseTrajectoryEnabled(env) {
2289
2502
  }
2290
2503
  function resolveTrajectoryFilePath(params) {
2291
2504
  const dirOverride = params.env.OPENCLAW_TRAJECTORY_DIR?.trim();
2292
- if (dirOverride) return resolveContainedPath(resolveUserPath$1(dirOverride), `${safeTrajectorySessionFileName(params.sessionId)}.jsonl`);
2505
+ if (dirOverride) return resolveContainedPath(resolveUserPath(dirOverride), `${safeTrajectorySessionFileName(params.sessionId)}.jsonl`);
2293
2506
  return params.sessionFile.endsWith(".jsonl") ? `${params.sessionFile.slice(0, -6)}.trajectory.jsonl` : `${params.sessionFile}.trajectory.jsonl`;
2294
2507
  }
2295
2508
  function safeTrajectorySessionFileName(sessionId) {
@@ -2528,9 +2741,14 @@ function filterToolsForVisionInputs(tools, params) {
2528
2741
  //#endregion
2529
2742
  //#region extensions/codex/src/app-server/run-attempt.ts
2530
2743
  const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 3e4;
2744
+ const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
2745
+ const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
2531
2746
  const CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS = 3;
2747
+ const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
2532
2748
  const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
2533
2749
  const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
2750
+ const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
2751
+ const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 6e4;
2534
2752
  const CODEX_STEER_ALL_DEBOUNCE_MS = 500;
2535
2753
  const LOG_FIELD_MAX_LENGTH = 160;
2536
2754
  const CODEX_NATIVE_PROJECT_DOC_BASENAMES = new Set(["agents.md"]);
@@ -2544,7 +2762,12 @@ const CODEX_BOOTSTRAP_CONTEXT_ORDER = new Map([
2544
2762
  ["memory.md", 60],
2545
2763
  ["heartbeat.md", 70]
2546
2764
  ]);
2547
- let clientFactory = defaultCodexAppServerClientFactory;
2765
+ const testClientFactoryStorage = new AsyncLocalStorage();
2766
+ const clientFactory = defaultCodexAppServerClientFactory;
2767
+ let openClawCodingToolsFactoryForTests;
2768
+ function resolveCodexAppServerClientFactory() {
2769
+ return testClientFactoryStorage.getStore() ?? clientFactory;
2770
+ }
2548
2771
  function emitCodexAppServerEvent(params, event) {
2549
2772
  try {
2550
2773
  emitAgentEvent({
@@ -2680,11 +2903,42 @@ function toCodexTextInput(text) {
2680
2903
  text_elements: []
2681
2904
  };
2682
2905
  }
2906
+ function resolveCodexPluginAppCacheEndpoint(appServer) {
2907
+ return JSON.stringify({
2908
+ transport: appServer.start.transport,
2909
+ command: appServer.start.command,
2910
+ args: appServer.start.args,
2911
+ url: appServer.start.url ?? null,
2912
+ credentialFingerprint: fingerprintCodexPluginAppCacheCredentials(appServer.start)
2913
+ });
2914
+ }
2915
+ function fingerprintCodexPluginAppCacheCredentials(startOptions) {
2916
+ const authToken = startOptions.authToken ?? "";
2917
+ const headers = Object.entries(startOptions.headers).map(([key, value]) => [key.toLowerCase(), value]).toSorted(([left], [right]) => left.localeCompare(right));
2918
+ if (!authToken && headers.length === 0) return null;
2919
+ const hash = createHash("sha256");
2920
+ hash.update("openclaw:codex:plugin-app-cache-credentials:v1");
2921
+ hash.update("\0");
2922
+ hash.update(authToken);
2923
+ for (const [key, value] of headers) {
2924
+ hash.update("\0");
2925
+ hash.update(key);
2926
+ hash.update("\0");
2927
+ hash.update(value);
2928
+ }
2929
+ return `sha256:${hash.digest("hex")}`;
2930
+ }
2931
+ function resolveCodexPluginAppCacheCodexHome(appServer, agentDir) {
2932
+ const configuredCodexHome = appServer.start.env?.CODEX_HOME?.trim();
2933
+ if (configuredCodexHome) return configuredCodexHome;
2934
+ return appServer.start.transport === "stdio" ? resolveCodexAppServerHomeDir(agentDir) : void 0;
2935
+ }
2683
2936
  async function runCodexAppServerAttempt(params, options = {}) {
2684
2937
  const attemptStartedAt = Date.now();
2685
- const attemptClientFactory = clientFactory;
2938
+ const attemptClientFactory = resolveCodexAppServerClientFactory();
2686
2939
  const pluginConfig = readCodexPluginConfig(options.pluginConfig);
2687
2940
  const appServer = resolveCodexAppServerRuntimeOptions({ pluginConfig });
2941
+ let pluginAppServer = appServer;
2688
2942
  const nativeHookRelayEvents = resolveCodexNativeHookRelayEvents({
2689
2943
  configuredEvents: options.nativeHookRelay?.events,
2690
2944
  appServer
@@ -2710,7 +2964,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2710
2964
  config: params.config,
2711
2965
  agentId: params.agentId
2712
2966
  });
2713
- const agentDir = params.agentDir ?? resolveOpenClawAgentDir();
2967
+ const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId);
2714
2968
  const startupBinding = await readCodexAppServerBinding(params.sessionFile);
2715
2969
  const startupAuthProfileCandidate = params.runtimePlan?.auth.forwardedAuthProfileId ?? params.authProfileId ?? startupBinding?.authProfileId;
2716
2970
  const startupAuthProfileId = params.authProfileStore ? resolveCodexAppServerAuthProfileId({
@@ -2727,6 +2981,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
2727
2981
  sessionKey: sandboxSessionKey,
2728
2982
  ...startupAuthProfileId ? { authProfileId: startupAuthProfileId } : {}
2729
2983
  };
2984
+ const startupAuthAccountCacheKey = await resolveCodexAppServerAuthAccountCacheKey({
2985
+ authProfileId: startupAuthProfileId,
2986
+ authProfileStore: params.authProfileStore,
2987
+ agentDir,
2988
+ config: params.config
2989
+ });
2990
+ const startupEnvApiKeyCacheKey = startupAuthProfileId ? void 0 : resolveCodexAppServerEnvApiKeyCacheKey({ startOptions: appServer.start });
2730
2991
  const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
2731
2992
  let yieldDetected = false;
2732
2993
  const toolBridge = createCodexDynamicToolBridge({
@@ -2744,6 +3005,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
2744
3005
  }
2745
3006
  }),
2746
3007
  signal: runAbortController.signal,
3008
+ loading: pluginConfig.codexDynamicToolsLoading ?? "searchable",
3009
+ directToolNames: shouldForceMessageTool(params) ? ["message"] : [],
2747
3010
  hookContext: {
2748
3011
  agentId: sessionAgentId,
2749
3012
  config: params.config,
@@ -2752,7 +3015,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2752
3015
  runId: params.runId
2753
3016
  }
2754
3017
  });
2755
- const hadSessionFile = await fileExists(params.sessionFile);
3018
+ const hadSessionFile = await pathExists(params.sessionFile);
2756
3019
  let historyMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? [];
2757
3020
  const hookContext = {
2758
3021
  runId: params.runId,
@@ -2784,8 +3047,15 @@ async function runCodexAppServerAttempt(params, options = {}) {
2784
3047
  historyMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? historyMessages;
2785
3048
  }
2786
3049
  const baseDeveloperInstructions = buildDeveloperInstructions(params);
3050
+ const workspaceBootstrapInstructions = await buildCodexWorkspaceBootstrapInstructions({
3051
+ params,
3052
+ resolvedWorkspace,
3053
+ effectiveWorkspace,
3054
+ sessionKey: sandboxSessionKey,
3055
+ sessionAgentId
3056
+ });
2787
3057
  let promptText = params.prompt;
2788
- let developerInstructions = baseDeveloperInstructions;
3058
+ let developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions);
2789
3059
  let prePromptMessageCount = historyMessages.length;
2790
3060
  if (activeContextEngine) try {
2791
3061
  const assembled = await assembleHarnessContextEngine({
@@ -2807,7 +3077,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2807
3077
  systemPromptAddition: assembled.systemPromptAddition
2808
3078
  });
2809
3079
  promptText = projection.promptText;
2810
- developerInstructions = joinPresentSections(baseDeveloperInstructions, projection.developerInstructionAddition);
3080
+ developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions, projection.developerInstructionAddition);
2811
3081
  prePromptMessageCount = projection.prePromptMessageCount;
2812
3082
  } catch (assembleErr) {
2813
3083
  embeddedAgentLog.warn("context engine assemble failed; using Codex baseline prompt", { error: formatErrorMessage(assembleErr) });
@@ -2831,13 +3101,6 @@ async function runCodexAppServerAttempt(params, options = {}) {
2831
3101
  messages: historyMessages,
2832
3102
  ctx: hookContext
2833
3103
  });
2834
- const workspaceBootstrapInstructions = await buildCodexWorkspaceBootstrapInstructions({
2835
- params,
2836
- resolvedWorkspace,
2837
- effectiveWorkspace,
2838
- sessionKey: sandboxSessionKey,
2839
- sessionAgentId
2840
- });
2841
3104
  const trajectoryRecorder = createCodexTrajectoryRecorder({
2842
3105
  attempt: params,
2843
3106
  cwd: effectiveWorkspace,
@@ -2850,6 +3113,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
2850
3113
  let trajectoryEndRecorded = false;
2851
3114
  let nativeHookRelay;
2852
3115
  let startupClientForCleanup;
3116
+ const startupTimeoutMs = resolveCodexStartupTimeoutMs({
3117
+ timeoutMs: params.timeoutMs,
3118
+ timeoutFloorMs: options.startupTimeoutFloorMs
3119
+ });
2853
3120
  try {
2854
3121
  emitCodexAppServerEvent(params, {
2855
3122
  stream: "codex_app_server.lifecycle",
@@ -2863,16 +3130,36 @@ async function runCodexAppServerAttempt(params, options = {}) {
2863
3130
  sessionKey: sandboxSessionKey,
2864
3131
  config: params.config,
2865
3132
  runId: params.runId,
3133
+ attemptTimeoutMs: params.timeoutMs,
3134
+ startupTimeoutMs,
3135
+ turnStartTimeoutMs: params.timeoutMs,
2866
3136
  signal: runAbortController.signal
2867
3137
  });
2868
- const threadConfig = mergeCodexConfigInstructions(nativeHookRelay ? buildCodexNativeHookRelayConfig({
3138
+ const threadConfig = nativeHookRelay ? buildCodexNativeHookRelayConfig({
2869
3139
  relay: nativeHookRelay,
2870
3140
  events: nativeHookRelayEvents,
2871
3141
  hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec
2872
- }) : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : void 0, workspaceBootstrapInstructions);
3142
+ }) : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : void 0;
3143
+ const pluginThreadConfigEnabled = shouldBuildCodexPluginThreadConfig(pluginConfig);
3144
+ const pluginAppCacheKey = buildCodexAppInventoryCacheKey({
3145
+ codexHome: resolveCodexPluginAppCacheCodexHome(appServer, agentDir),
3146
+ endpoint: resolveCodexPluginAppCacheEndpoint(appServer),
3147
+ authProfileId: startupAuthProfileId,
3148
+ accountId: startupAuthAccountCacheKey,
3149
+ envApiKeyFingerprint: startupEnvApiKeyCacheKey
3150
+ });
3151
+ const pluginThreadConfigInputFingerprint = pluginThreadConfigEnabled ? buildCodexPluginThreadConfigInputFingerprint({
3152
+ pluginConfig,
3153
+ appCacheKey: pluginAppCacheKey
3154
+ }) : void 0;
3155
+ const resolvedPluginPolicy = pluginThreadConfigEnabled ? resolveCodexPluginsPolicy(pluginConfig) : void 0;
3156
+ const enabledPluginConfigKeys = resolvedPluginPolicy ? resolvedPluginPolicy.pluginPolicies.filter((plugin) => plugin.enabled).map((plugin) => plugin.configKey).toSorted() : void 0;
3157
+ pluginAppServer = resolvedPluginPolicy?.enabled === true ? {
3158
+ ...appServer,
3159
+ approvalPolicy: withMcpElicitationsApprovalPolicy(appServer.approvalPolicy)
3160
+ } : appServer;
2873
3161
  ({client, thread} = await withCodexStartupTimeout({
2874
- timeoutMs: params.timeoutMs,
2875
- timeoutFloorMs: options.startupTimeoutFloorMs,
3162
+ timeoutMs: startupTimeoutMs,
2876
3163
  signal: runAbortController.signal,
2877
3164
  operation: async () => {
2878
3165
  let attemptedClient;
@@ -2893,9 +3180,23 @@ async function runCodexAppServerAttempt(params, options = {}) {
2893
3180
  params: runtimeParams,
2894
3181
  cwd: effectiveWorkspace,
2895
3182
  dynamicTools: toolBridge.specs,
2896
- appServer,
3183
+ appServer: pluginAppServer,
2897
3184
  developerInstructions: promptBuild.developerInstructions,
2898
- config: threadConfig
3185
+ config: threadConfig,
3186
+ pluginThreadConfig: pluginThreadConfigEnabled ? {
3187
+ enabled: true,
3188
+ inputFingerprint: pluginThreadConfigInputFingerprint,
3189
+ enabledPluginConfigKeys,
3190
+ build: () => buildCodexPluginThreadConfig({
3191
+ pluginConfig,
3192
+ request: (method, requestParams) => startupClient.request(method, requestParams, {
3193
+ timeoutMs: appServer.requestTimeoutMs,
3194
+ signal: runAbortController.signal
3195
+ }),
3196
+ appCache: defaultCodexAppInventoryCache,
3197
+ appCacheKey: pluginAppCacheKey
3198
+ })
3199
+ } : void 0
2899
3200
  })
2900
3201
  };
2901
3202
  };
@@ -2971,14 +3272,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
2971
3272
  resolveCompletion = resolve;
2972
3273
  });
2973
3274
  let notificationQueue = Promise.resolve();
2974
- const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs(options.turnCompletionIdleTimeoutMs);
3275
+ const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs(options.turnCompletionIdleTimeoutMs ?? appServer.turnCompletionIdleTimeoutMs);
2975
3276
  const turnTerminalIdleTimeoutMs = resolveCodexTurnTerminalIdleTimeoutMs(options.turnTerminalIdleTimeoutMs);
2976
3277
  let turnCompletionIdleTimer;
2977
3278
  let turnCompletionIdleWatchArmed = false;
3279
+ let turnCompletionIdleWatchPinnedByTerminalError = false;
2978
3280
  let turnTerminalIdleTimer;
2979
3281
  let turnTerminalIdleWatchArmed = false;
2980
3282
  let turnCompletionLastActivityAt = Date.now();
2981
3283
  let turnCompletionLastActivityReason = "startup";
3284
+ let turnCompletionLastActivityDetails;
2982
3285
  let activeAppServerTurnRequests = 0;
2983
3286
  const clearTurnCompletionIdleTimer = () => {
2984
3287
  if (turnCompletionIdleTimer) {
@@ -3008,14 +3311,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
3008
3311
  turnId,
3009
3312
  idleMs,
3010
3313
  timeoutMs: turnCompletionIdleTimeoutMs,
3011
- lastActivityReason: turnCompletionLastActivityReason
3314
+ lastActivityReason: turnCompletionLastActivityReason,
3315
+ ...turnCompletionLastActivityDetails
3012
3316
  });
3013
3317
  embeddedAgentLog.warn("codex app-server turn idle timed out waiting for completion", {
3014
3318
  threadId: thread.threadId,
3015
3319
  turnId,
3016
3320
  idleMs,
3017
3321
  timeoutMs: turnCompletionIdleTimeoutMs,
3018
- lastActivityReason: turnCompletionLastActivityReason
3322
+ lastActivityReason: turnCompletionLastActivityReason,
3323
+ ...turnCompletionLastActivityDetails
3019
3324
  });
3020
3325
  runAbortController.abort("turn_completion_idle_timeout");
3021
3326
  };
@@ -3035,14 +3340,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
3035
3340
  turnId,
3036
3341
  idleMs,
3037
3342
  timeoutMs: turnTerminalIdleTimeoutMs,
3038
- lastActivityReason: turnCompletionLastActivityReason
3343
+ lastActivityReason: turnCompletionLastActivityReason,
3344
+ ...turnCompletionLastActivityDetails
3039
3345
  });
3040
3346
  embeddedAgentLog.warn("codex app-server turn idle timed out waiting for terminal event", {
3041
3347
  threadId: thread.threadId,
3042
3348
  turnId,
3043
3349
  idleMs,
3044
3350
  timeoutMs: turnTerminalIdleTimeoutMs,
3045
- lastActivityReason: turnCompletionLastActivityReason
3351
+ lastActivityReason: turnCompletionLastActivityReason,
3352
+ ...turnCompletionLastActivityDetails
3046
3353
  });
3047
3354
  runAbortController.abort("turn_terminal_idle_timeout");
3048
3355
  };
@@ -3065,6 +3372,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3065
3372
  const touchTurnCompletionActivity = (reason, options) => {
3066
3373
  turnCompletionLastActivityAt = Date.now();
3067
3374
  turnCompletionLastActivityReason = reason;
3375
+ turnCompletionLastActivityDetails = options?.details;
3068
3376
  emitTrustedDiagnosticEvent({
3069
3377
  type: "run.progress",
3070
3378
  runId: params.runId,
@@ -3072,10 +3380,23 @@ async function runCodexAppServerAttempt(params, options = {}) {
3072
3380
  sessionKey: params.sessionKey,
3073
3381
  reason: `codex_app_server:${reason}`
3074
3382
  });
3075
- if (options?.arm) turnCompletionIdleWatchArmed = true;
3383
+ if (options?.arm) {
3384
+ turnCompletionIdleWatchArmed = true;
3385
+ turnCompletionIdleWatchPinnedByTerminalError = false;
3386
+ }
3076
3387
  scheduleTurnCompletionIdleWatch();
3077
3388
  scheduleTurnTerminalIdleWatch();
3078
3389
  };
3390
+ const disarmTurnCompletionIdleWatch = () => {
3391
+ turnCompletionIdleWatchArmed = false;
3392
+ turnCompletionIdleWatchPinnedByTerminalError = false;
3393
+ clearTurnCompletionIdleTimer();
3394
+ };
3395
+ const armTurnCompletionIdleWatch = (options) => {
3396
+ turnCompletionIdleWatchArmed = true;
3397
+ turnCompletionIdleWatchPinnedByTerminalError = options?.pinnedByTerminalError === true;
3398
+ scheduleTurnCompletionIdleWatch();
3399
+ };
3079
3400
  const emitLifecycleStart = () => {
3080
3401
  emitCodexAppServerEvent(params, {
3081
3402
  stream: "lifecycle",
@@ -3099,13 +3420,17 @@ async function runCodexAppServerAttempt(params, options = {}) {
3099
3420
  lifecycleTerminalEmitted = true;
3100
3421
  };
3101
3422
  const handleNotification = async (notification) => {
3102
- touchTurnCompletionActivity(`notification:${notification.method}`);
3103
3423
  userInputBridge?.handleNotification(notification);
3104
3424
  if (!projector || !turnId) {
3105
3425
  pendingNotifications.push(notification);
3106
3426
  return;
3107
3427
  }
3108
- const isTurnCompletion = notification.method === "turn/completed" && isTurnNotification(notification.params, thread.threadId, turnId);
3428
+ const isCurrentTurnNotification = isTurnNotification(notification.params, thread.threadId, turnId);
3429
+ if (isCurrentTurnNotification) touchTurnCompletionActivity(`notification:${notification.method}`, { details: describeNotificationActivity(notification) });
3430
+ if (isCurrentTurnNotification && notification.method === "error") if (isRetryableErrorNotification(notification.params)) disarmTurnCompletionIdleWatch();
3431
+ else armTurnCompletionIdleWatch({ pinnedByTerminalError: true });
3432
+ else if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification) disarmTurnCompletionIdleWatch();
3433
+ const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
3109
3434
  try {
3110
3435
  await projector.handleNotification(notification);
3111
3436
  } catch (error) {
@@ -3136,7 +3461,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
3136
3461
  try {
3137
3462
  if (request.method === "account/chatgptAuthTokens/refresh") return refreshCodexAppServerAuthTokens({
3138
3463
  agentDir,
3139
- authProfileId: startupAuthProfileId
3464
+ authProfileId: startupAuthProfileId,
3465
+ config: params.config
3140
3466
  });
3141
3467
  if (!turnId) return;
3142
3468
  if (request.method === "mcpServer/elicitation/request") {
@@ -3146,6 +3472,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3146
3472
  paramsForRun: params,
3147
3473
  threadId: thread.threadId,
3148
3474
  turnId,
3475
+ pluginAppPolicyContext: thread.pluginAppPolicyContext,
3149
3476
  signal: runAbortController.signal
3150
3477
  });
3151
3478
  }
@@ -3180,6 +3507,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
3180
3507
  name: call.tool,
3181
3508
  arguments: call.arguments
3182
3509
  });
3510
+ projector?.recordDynamicToolCall({
3511
+ callId: call.callId,
3512
+ tool: call.tool,
3513
+ arguments: call.arguments
3514
+ });
3183
3515
  const toolMeta = inferCodexDynamicToolMeta(call, resolveCodexToolProgressDetailMode(params.toolProgressDetail));
3184
3516
  const toolArgs = sanitizeCodexToolArguments(call.arguments);
3185
3517
  emitCodexAppServerEvent(params, {
@@ -3192,18 +3524,22 @@ async function runCodexAppServerAttempt(params, options = {}) {
3192
3524
  ...toolArgs ? { args: toolArgs } : {}
3193
3525
  }
3194
3526
  });
3527
+ const dynamicToolTimeoutMs = resolveDynamicToolCallTimeoutMs({
3528
+ call,
3529
+ config: params.config
3530
+ });
3195
3531
  const response = await handleDynamicToolCallWithTimeout({
3196
3532
  call,
3197
3533
  toolBridge,
3198
3534
  signal: runAbortController.signal,
3199
- timeoutMs: CODEX_DYNAMIC_TOOL_TIMEOUT_MS,
3535
+ timeoutMs: dynamicToolTimeoutMs,
3200
3536
  onTimeout: () => {
3201
3537
  trajectoryRecorder?.recordEvent("tool.timeout", {
3202
3538
  threadId: call.threadId,
3203
3539
  turnId: call.turnId,
3204
3540
  toolCallId: call.callId,
3205
3541
  name: call.tool,
3206
- timeoutMs: CODEX_DYNAMIC_TOOL_TIMEOUT_MS
3542
+ timeoutMs: dynamicToolTimeoutMs
3207
3543
  });
3208
3544
  }
3209
3545
  });
@@ -3215,6 +3551,12 @@ async function runCodexAppServerAttempt(params, options = {}) {
3215
3551
  success: response.success,
3216
3552
  contentItems: response.contentItems
3217
3553
  });
3554
+ projector?.recordDynamicToolResult({
3555
+ callId: call.callId,
3556
+ tool: call.tool,
3557
+ success: response.success,
3558
+ contentItems: response.contentItems
3559
+ });
3218
3560
  emitCodexAppServerEvent(params, {
3219
3561
  stream: "tool",
3220
3562
  data: {
@@ -3265,7 +3607,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3265
3607
  turn = assertCodexTurnStartResponse(await client.request("turn/start", buildTurnStartParams(params, {
3266
3608
  threadId: thread.threadId,
3267
3609
  cwd: effectiveWorkspace,
3268
- appServer,
3610
+ appServer: pluginAppServer,
3269
3611
  promptText: promptBuild.prompt
3270
3612
  }), {
3271
3613
  timeoutMs: params.timeoutMs,
@@ -3535,7 +3877,7 @@ async function handleDynamicToolCallWithTimeout(params) {
3535
3877
  resolveAbort = resolve;
3536
3878
  });
3537
3879
  const timeoutPromise = new Promise((resolve) => {
3538
- const timeoutMs = Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_TIMEOUT_MS, params.timeoutMs));
3880
+ const timeoutMs = clampDynamicToolTimeoutMs(params.timeoutMs);
3539
3881
  timeout = setTimeout(() => {
3540
3882
  timedOut = true;
3541
3883
  const timeoutDetails = formatDynamicToolTimeoutDetails({
@@ -3578,6 +3920,31 @@ function failedDynamicToolResponse(message) {
3578
3920
  }]
3579
3921
  };
3580
3922
  }
3923
+ function resolveDynamicToolCallTimeoutMs(params) {
3924
+ return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_TOOL_TIMEOUT_MS);
3925
+ }
3926
+ function readDynamicToolCallTimeoutMs(value) {
3927
+ if (!isJsonObject(value)) return;
3928
+ return readPositiveFiniteTimeoutMs(value.timeoutMs);
3929
+ }
3930
+ function readConfiguredDynamicToolTimeoutMs(toolName, config) {
3931
+ if (toolName === "image_generate") {
3932
+ const imageGenerationModel = config?.agents?.defaults?.imageGenerationModel;
3933
+ if (!imageGenerationModel || typeof imageGenerationModel !== "object") return;
3934
+ return readPositiveFiniteTimeoutMs(imageGenerationModel.timeoutMs);
3935
+ }
3936
+ if (toolName === "image") return readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds) ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS;
3937
+ }
3938
+ function readTimeoutSecondsAsMs(value) {
3939
+ const seconds = readPositiveFiniteTimeoutMs(value);
3940
+ return seconds === void 0 ? void 0 : seconds * 1e3;
3941
+ }
3942
+ function readPositiveFiniteTimeoutMs(value) {
3943
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
3944
+ }
3945
+ function clampDynamicToolTimeoutMs(timeoutMs) {
3946
+ return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
3947
+ }
3581
3948
  function createCodexNativeHookRelay(params) {
3582
3949
  if (params.options?.enabled === false) return;
3583
3950
  return registerNativeHookRelay({
@@ -3593,7 +3960,12 @@ function createCodexNativeHookRelay(params) {
3593
3960
  ...params.config ? { config: params.config } : {},
3594
3961
  runId: params.runId,
3595
3962
  allowedEvents: params.events,
3596
- ttlMs: params.options?.ttlMs,
3963
+ ttlMs: resolveCodexNativeHookRelayTtlMs({
3964
+ explicitTtlMs: params.options?.ttlMs,
3965
+ attemptTimeoutMs: params.attemptTimeoutMs,
3966
+ startupTimeoutMs: params.startupTimeoutMs,
3967
+ turnStartTimeoutMs: params.turnStartTimeoutMs
3968
+ }),
3597
3969
  signal: params.signal,
3598
3970
  command: { timeoutMs: params.options?.gatewayTimeoutMs }
3599
3971
  });
@@ -3602,6 +3974,11 @@ function resolveCodexNativeHookRelayEvents(params) {
3602
3974
  if (params.configuredEvents?.length) return params.configuredEvents;
3603
3975
  return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
3604
3976
  }
3977
+ function resolveCodexNativeHookRelayTtlMs(params) {
3978
+ if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
3979
+ const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
3980
+ return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
3981
+ }
3605
3982
  function buildCodexNativeHookRelayId(params) {
3606
3983
  const hash = createHash("sha256");
3607
3984
  hash.update("openclaw:codex:native-hook-relay:v1");
@@ -3616,13 +3993,20 @@ function interruptCodexTurnBestEffort(client, params) {
3616
3993
  embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
3617
3994
  });
3618
3995
  }
3996
+ function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
3997
+ return {
3998
+ sessionKey: sandboxSessionKey,
3999
+ runSessionKey: params.sessionKey && params.sessionKey !== sandboxSessionKey ? params.sessionKey : void 0
4000
+ };
4001
+ }
3619
4002
  async function buildDynamicTools(input) {
3620
4003
  const { params } = input;
3621
4004
  if (params.disableTools || !supportsModelTools(params.model)) return [];
3622
4005
  const modelHasVision = params.model.input?.includes("image") ?? false;
3623
- const agentDir = params.agentDir ?? resolveOpenClawAgentDir();
3624
- const { createOpenClawCodingTools } = await import("openclaw/plugin-sdk/agent-harness");
3625
- const visionFilteredTools = filterToolsForVisionInputs(applyCodexDynamicToolProfile(createOpenClawCodingTools({
4006
+ const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
4007
+ const createOpenClawCodingTools = openClawCodingToolsFactoryForTests ?? (await import("openclaw/plugin-sdk/agent-harness")).createOpenClawCodingTools;
4008
+ const sessionKeys = resolveOpenClawCodingToolsSessionKeys(params, input.sandboxSessionKey);
4009
+ const filteredTools = filterCodexDynamicToolsForAllowlist(filterToolsForVisionInputs(filterCodexDynamicTools(createOpenClawCodingTools({
3626
4010
  agentId: input.sessionAgentId,
3627
4011
  ...buildEmbeddedAttemptToolRunContext(params),
3628
4012
  exec: {
@@ -3644,8 +4028,7 @@ async function buildDynamicTools(input) {
3644
4028
  senderE164: params.senderE164,
3645
4029
  senderIsOwner: params.senderIsOwner,
3646
4030
  allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,
3647
- sessionKey: input.sandboxSessionKey,
3648
- runSessionKey: params.sessionKey && params.sessionKey !== input.sandboxSessionKey ? params.sessionKey : void 0,
4031
+ ...sessionKeys,
3649
4032
  sessionId: params.sessionId,
3650
4033
  runId: params.runId,
3651
4034
  agentDir,
@@ -3670,8 +4053,9 @@ async function buildDynamicTools(input) {
3670
4053
  hasRepliedRef: params.hasRepliedRef,
3671
4054
  modelHasVision,
3672
4055
  requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
4056
+ sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
3673
4057
  disableMessageTool: params.disableMessageTool,
3674
- forceMessageTool: params.sourceReplyDeliveryMode === "message_tool_only",
4058
+ forceMessageTool: shouldForceMessageTool(params),
3675
4059
  enableHeartbeatTool: params.trigger === "heartbeat",
3676
4060
  forceHeartbeatTool: params.trigger === "heartbeat",
3677
4061
  onYield: (message) => {
@@ -3688,8 +4072,7 @@ async function buildDynamicTools(input) {
3688
4072
  }), input.pluginConfig), {
3689
4073
  modelHasVision,
3690
4074
  hasInboundImages: (params.images?.length ?? 0) > 0
3691
- });
3692
- const filteredTools = params.toolsAllow && params.toolsAllow.length > 0 ? visionFilteredTools.filter((tool) => params.toolsAllow?.includes(tool.name)) : visionFilteredTools;
4075
+ }), params.toolsAllow);
3693
4076
  return normalizeAgentRuntimeTools({
3694
4077
  runtimePlan: params.runtimePlan,
3695
4078
  tools: filteredTools,
@@ -3702,6 +4085,14 @@ async function buildDynamicTools(input) {
3702
4085
  model: params.model
3703
4086
  });
3704
4087
  }
4088
+ function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
4089
+ if (!toolsAllow || toolsAllow.length === 0) return tools;
4090
+ const allowSet = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)).filter(Boolean));
4091
+ return tools.filter((tool) => allowSet.has(normalizeCodexDynamicToolName(tool.name)));
4092
+ }
4093
+ function shouldForceMessageTool(params) {
4094
+ return params.sourceReplyDeliveryMode === "message_tool_only";
4095
+ }
3705
4096
  function shouldProjectMirroredHistoryForCodexStart(params) {
3706
4097
  if (!params.historyMessages.some((message) => message.role === "user")) return false;
3707
4098
  if (!params.startupBinding?.threadId) return true;
@@ -3723,10 +4114,9 @@ async function withCodexStartupTimeout(params) {
3723
4114
  }
3724
4115
  reject(error);
3725
4116
  };
3726
- const timeoutMs = Math.max(params.timeoutFloorMs ?? 100, params.timeoutMs);
3727
4117
  timeout = setTimeout(() => {
3728
4118
  rejectOnce(/* @__PURE__ */ new Error("codex app-server startup timed out"));
3729
- }, timeoutMs);
4119
+ }, params.timeoutMs);
3730
4120
  const abortListener = () => rejectOnce(/* @__PURE__ */ new Error("codex app-server startup aborted"));
3731
4121
  params.signal.addEventListener("abort", abortListener, { once: true });
3732
4122
  abortCleanup = () => params.signal.removeEventListener("abort", abortListener);
@@ -3736,6 +4126,9 @@ async function withCodexStartupTimeout(params) {
3736
4126
  abortCleanup?.();
3737
4127
  }
3738
4128
  }
4129
+ function resolveCodexStartupTimeoutMs(params) {
4130
+ return Math.max(params.timeoutFloorMs ?? CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS, params.timeoutMs);
4131
+ }
3739
4132
  function resolveCodexTurnCompletionIdleTimeoutMs(value) {
3740
4133
  if (value === void 0) return CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS;
3741
4134
  if (!Number.isFinite(value)) return CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS;
@@ -3791,10 +4184,37 @@ function readCodexErrorPayload(error) {
3791
4184
  rateLimits: nestedError.rateLimits ?? data.rateLimits
3792
4185
  };
3793
4186
  }
4187
+ function describeNotificationActivity(notification) {
4188
+ if (!isJsonObject(notification.params)) return { lastNotificationMethod: notification.method };
4189
+ if (notification.method !== "rawResponseItem/completed") return { lastNotificationMethod: notification.method };
4190
+ const item = isJsonObject(notification.params.item) ? notification.params.item : void 0;
4191
+ if (!item) return { lastNotificationMethod: notification.method };
4192
+ return {
4193
+ lastNotificationMethod: notification.method,
4194
+ lastNotificationItemId: readString(item, "id"),
4195
+ lastNotificationItemType: readString(item, "type"),
4196
+ lastNotificationItemRole: readString(item, "role"),
4197
+ lastAssistantTextPreview: readRawAssistantTextPreview(item)
4198
+ };
4199
+ }
4200
+ function readRawAssistantTextPreview(item) {
4201
+ if (readString(item, "role") !== "assistant" || !Array.isArray(item.content)) return;
4202
+ const text = item.content.flatMap((content) => {
4203
+ if (!isJsonObject(content)) return [];
4204
+ const contentText = readString(content, "text");
4205
+ return contentText ? [contentText] : [];
4206
+ }).join("\n").trim();
4207
+ if (!text) return;
4208
+ return text.length > 240 ? `${text.slice(0, 237)}...` : text;
4209
+ }
3794
4210
  function isTurnNotification(value, threadId, turnId) {
3795
4211
  if (!isJsonObject(value)) return false;
3796
4212
  return readString(value, "threadId") === threadId && readNotificationTurnId(value) === turnId;
3797
4213
  }
4214
+ function isRetryableErrorNotification(value) {
4215
+ if (!isJsonObject(value)) return false;
4216
+ return readBoolean(value, "willRetry") === true || readBoolean(value, "will_retry") === true;
4217
+ }
3798
4218
  function isTerminalTurnStatus(status) {
3799
4219
  return status === "completed" || status === "interrupted" || status === "failed";
3800
4220
  }
@@ -3809,6 +4229,10 @@ function readString(record, key) {
3809
4229
  const value = record[key];
3810
4230
  return typeof value === "string" ? value : void 0;
3811
4231
  }
4232
+ function readBoolean(record, key) {
4233
+ const value = record[key];
4234
+ return typeof value === "boolean" ? value : void 0;
4235
+ }
3812
4236
  async function readMirroredSessionHistoryMessages(sessionFile) {
3813
4237
  const messages = await readCodexMirroredSessionHistoryMessages(sessionFile);
3814
4238
  if (!messages) embeddedAgentLog.warn("failed to read mirrored session history for codex harness hooks", { sessionFile });
@@ -3855,12 +4279,6 @@ function renderCodexWorkspaceBootstrapInstructions(contextFiles) {
3855
4279
  for (const file of files) lines.push(`## ${file.path}`, "", file.content, "");
3856
4280
  return lines.join("\n").trim();
3857
4281
  }
3858
- function mergeCodexConfigInstructions(config, instructions) {
3859
- if (!instructions?.trim()) return config;
3860
- const merged = { ...config };
3861
- merged.instructions = joinPresentSections(typeof merged.instructions === "string" ? merged.instructions.trim() : void 0, instructions);
3862
- return merged;
3863
- }
3864
4282
  function remapCodexContextFilePath(params) {
3865
4283
  const relativePath = path.relative(params.sourceWorkspaceDir, params.file.path);
3866
4284
  if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath) || params.sourceWorkspaceDir === params.targetWorkspaceDir) return params.file;
@@ -3900,15 +4318,6 @@ async function mirrorTranscriptBestEffort(params) {
3900
4318
  embeddedAgentLog.warn("failed to mirror codex app-server transcript", { error });
3901
4319
  }
3902
4320
  }
3903
- async function fileExists(filePath) {
3904
- try {
3905
- await fs.stat(filePath);
3906
- return true;
3907
- } catch (error) {
3908
- if (error.code === "ENOENT") return false;
3909
- throw error;
3910
- }
3911
- }
3912
4321
  function isNonEmptyString(value) {
3913
4322
  return typeof value === "string" && value.length > 0;
3914
4323
  }
@@ -3925,8 +4334,5 @@ function handleApprovalRequest(params) {
3925
4334
  signal: params.signal
3926
4335
  });
3927
4336
  }
3928
- ({ ...createCodexAppServerClientFactoryTestHooks((factory) => {
3929
- clientFactory = factory;
3930
- }) });
3931
4337
  //#endregion
3932
4338
  export { runCodexAppServerAttempt };