@openclaw/codex 2026.5.7-beta.1 → 2026.5.9-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-BZiM7rhH.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-DYkCXAze.js";
5
+ import { d as formatCodexUsageLimitErrorMessage, i as formatCodexDisplayText } from "./request-CaW8DCAa.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-pLCOzMaZ.js";
7
+ import { i as readCodexAppServerBinding } from "./session-binding-C_HDlZx8.js";
8
+ import { i as defaultCodexAppInventoryCache, r as buildCodexAppInventoryCacheKey } from "./plugin-activation-D0uXssSN.js";
9
+ import { a as buildTurnStartParams, c as buildCodexPluginThreadConfig, d as createCodexDynamicToolBridge, f as applyCodexDynamicToolProfile, l as buildCodexPluginThreadConfigInputFingerprint, n as buildDeveloperInstructions, o as codexDynamicToolsFingerprint, p as normalizeCodexDynamicToolName, s as startOrResumeThread, t as areCodexDynamicToolFingerprintsCompatible, u as shouldBuildCodexPluginThreadConfig } from "./thread-lifecycle-unEEsXe-.js";
10
+ import { n as defaultCodexAppServerClientFactory } from "./client-factory-D3-EhEwN.js";
11
+ import { n as rememberCodexRateLimits, r as ensureCodexComputerUse, t as readRecentCodexRateLimits } from "./rate-limit-cache-BZWGqXPP.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) {
@@ -1813,13 +1963,13 @@ function readNumber(record, key) {
1813
1963
  const value = record[key];
1814
1964
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1815
1965
  }
1816
- function readBoolean(record, key) {
1966
+ function readBoolean$1(record, key) {
1817
1967
  const value = record[key];
1818
1968
  return typeof value === "boolean" ? value : void 0;
1819
1969
  }
1820
1970
  function readBooleanAlias(record, keys) {
1821
1971
  for (const key of keys) {
1822
- const value = readBoolean(record, key);
1972
+ const value = readBoolean$1(record, key);
1823
1973
  if (value !== void 0) return value;
1824
1974
  }
1825
1975
  }
@@ -2111,62 +2261,17 @@ const JWT_VALUE_RE = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]
2111
2261
  const COOKIE_PAIR_RE = /\b([A-Za-z][A-Za-z0-9_.-]{1,64})=([A-Za-z0-9+/._~%=-]{16,})(?=;|\s|$)/gu;
2112
2262
  const TRAJECTORY_RUNTIME_FILE_MAX_BYTES = 50 * 1024 * 1024;
2113
2263
  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
2264
  function resolveCodexTrajectoryPointerFlags(constants = nodeFs.constants) {
2119
2265
  const noFollow = constants.O_NOFOLLOW;
2120
2266
  return constants.O_CREAT | constants.O_TRUNC | constants.O_WRONLY | (typeof noFollow === "number" ? noFollow : 0);
2121
2267
  }
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
2268
  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
- }
2269
+ await appendRegularFile({
2270
+ filePath,
2271
+ content: line,
2272
+ maxFileBytes: TRAJECTORY_RUNTIME_FILE_MAX_BYTES,
2273
+ rejectSymlinkParents: true
2274
+ });
2170
2275
  }
2171
2276
  function boundedTrajectoryLine(event) {
2172
2277
  const line = JSON.stringify(event);
@@ -2289,7 +2394,7 @@ function parseTrajectoryEnabled(env) {
2289
2394
  }
2290
2395
  function resolveTrajectoryFilePath(params) {
2291
2396
  const dirOverride = params.env.OPENCLAW_TRAJECTORY_DIR?.trim();
2292
- if (dirOverride) return resolveContainedPath(resolveUserPath$1(dirOverride), `${safeTrajectorySessionFileName(params.sessionId)}.jsonl`);
2397
+ if (dirOverride) return resolveContainedPath(resolveUserPath(dirOverride), `${safeTrajectorySessionFileName(params.sessionId)}.jsonl`);
2293
2398
  return params.sessionFile.endsWith(".jsonl") ? `${params.sessionFile.slice(0, -6)}.trajectory.jsonl` : `${params.sessionFile}.trajectory.jsonl`;
2294
2399
  }
2295
2400
  function safeTrajectorySessionFileName(sessionId) {
@@ -2529,8 +2634,11 @@ function filterToolsForVisionInputs(tools, params) {
2529
2634
  //#region extensions/codex/src/app-server/run-attempt.ts
2530
2635
  const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 3e4;
2531
2636
  const CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS = 3;
2637
+ const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
2532
2638
  const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
2533
2639
  const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
2640
+ const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 30 * 6e4;
2641
+ const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 5 * 6e4;
2534
2642
  const CODEX_STEER_ALL_DEBOUNCE_MS = 500;
2535
2643
  const LOG_FIELD_MAX_LENGTH = 160;
2536
2644
  const CODEX_NATIVE_PROJECT_DOC_BASENAMES = new Set(["agents.md"]);
@@ -2544,7 +2652,12 @@ const CODEX_BOOTSTRAP_CONTEXT_ORDER = new Map([
2544
2652
  ["memory.md", 60],
2545
2653
  ["heartbeat.md", 70]
2546
2654
  ]);
2547
- let clientFactory = defaultCodexAppServerClientFactory;
2655
+ const testClientFactoryStorage = new AsyncLocalStorage();
2656
+ const clientFactory = defaultCodexAppServerClientFactory;
2657
+ let openClawCodingToolsFactoryForTests;
2658
+ function resolveCodexAppServerClientFactory() {
2659
+ return testClientFactoryStorage.getStore() ?? clientFactory;
2660
+ }
2548
2661
  function emitCodexAppServerEvent(params, event) {
2549
2662
  try {
2550
2663
  emitAgentEvent({
@@ -2680,11 +2793,42 @@ function toCodexTextInput(text) {
2680
2793
  text_elements: []
2681
2794
  };
2682
2795
  }
2796
+ function resolveCodexPluginAppCacheEndpoint(appServer) {
2797
+ return JSON.stringify({
2798
+ transport: appServer.start.transport,
2799
+ command: appServer.start.command,
2800
+ args: appServer.start.args,
2801
+ url: appServer.start.url ?? null,
2802
+ credentialFingerprint: fingerprintCodexPluginAppCacheCredentials(appServer.start)
2803
+ });
2804
+ }
2805
+ function fingerprintCodexPluginAppCacheCredentials(startOptions) {
2806
+ const authToken = startOptions.authToken ?? "";
2807
+ const headers = Object.entries(startOptions.headers).map(([key, value]) => [key.toLowerCase(), value]).toSorted(([left], [right]) => left.localeCompare(right));
2808
+ if (!authToken && headers.length === 0) return null;
2809
+ const hash = createHash("sha256");
2810
+ hash.update("openclaw:codex:plugin-app-cache-credentials:v1");
2811
+ hash.update("\0");
2812
+ hash.update(authToken);
2813
+ for (const [key, value] of headers) {
2814
+ hash.update("\0");
2815
+ hash.update(key);
2816
+ hash.update("\0");
2817
+ hash.update(value);
2818
+ }
2819
+ return `sha256:${hash.digest("hex")}`;
2820
+ }
2821
+ function resolveCodexPluginAppCacheCodexHome(appServer, agentDir) {
2822
+ const configuredCodexHome = appServer.start.env?.CODEX_HOME?.trim();
2823
+ if (configuredCodexHome) return configuredCodexHome;
2824
+ return appServer.start.transport === "stdio" ? resolveCodexAppServerHomeDir(agentDir) : void 0;
2825
+ }
2683
2826
  async function runCodexAppServerAttempt(params, options = {}) {
2684
2827
  const attemptStartedAt = Date.now();
2685
- const attemptClientFactory = clientFactory;
2828
+ const attemptClientFactory = resolveCodexAppServerClientFactory();
2686
2829
  const pluginConfig = readCodexPluginConfig(options.pluginConfig);
2687
2830
  const appServer = resolveCodexAppServerRuntimeOptions({ pluginConfig });
2831
+ let pluginAppServer = appServer;
2688
2832
  const nativeHookRelayEvents = resolveCodexNativeHookRelayEvents({
2689
2833
  configuredEvents: options.nativeHookRelay?.events,
2690
2834
  appServer
@@ -2710,7 +2854,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2710
2854
  config: params.config,
2711
2855
  agentId: params.agentId
2712
2856
  });
2713
- const agentDir = params.agentDir ?? resolveOpenClawAgentDir();
2857
+ const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, sessionAgentId);
2714
2858
  const startupBinding = await readCodexAppServerBinding(params.sessionFile);
2715
2859
  const startupAuthProfileCandidate = params.runtimePlan?.auth.forwardedAuthProfileId ?? params.authProfileId ?? startupBinding?.authProfileId;
2716
2860
  const startupAuthProfileId = params.authProfileStore ? resolveCodexAppServerAuthProfileId({
@@ -2727,6 +2871,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
2727
2871
  sessionKey: sandboxSessionKey,
2728
2872
  ...startupAuthProfileId ? { authProfileId: startupAuthProfileId } : {}
2729
2873
  };
2874
+ const startupAuthAccountCacheKey = await resolveCodexAppServerAuthAccountCacheKey({
2875
+ authProfileId: startupAuthProfileId,
2876
+ authProfileStore: params.authProfileStore,
2877
+ agentDir,
2878
+ config: params.config
2879
+ });
2880
+ const startupEnvApiKeyCacheKey = startupAuthProfileId ? void 0 : resolveCodexAppServerEnvApiKeyCacheKey({ startOptions: appServer.start });
2730
2881
  const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine) ? params.contextEngine : void 0;
2731
2882
  let yieldDetected = false;
2732
2883
  const toolBridge = createCodexDynamicToolBridge({
@@ -2744,6 +2895,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
2744
2895
  }
2745
2896
  }),
2746
2897
  signal: runAbortController.signal,
2898
+ loading: pluginConfig.codexDynamicToolsLoading ?? "searchable",
2899
+ directToolNames: shouldForceMessageTool(params) ? ["message"] : [],
2747
2900
  hookContext: {
2748
2901
  agentId: sessionAgentId,
2749
2902
  config: params.config,
@@ -2752,7 +2905,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2752
2905
  runId: params.runId
2753
2906
  }
2754
2907
  });
2755
- const hadSessionFile = await fileExists(params.sessionFile);
2908
+ const hadSessionFile = await pathExists(params.sessionFile);
2756
2909
  let historyMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? [];
2757
2910
  const hookContext = {
2758
2911
  runId: params.runId,
@@ -2784,8 +2937,15 @@ async function runCodexAppServerAttempt(params, options = {}) {
2784
2937
  historyMessages = await readMirroredSessionHistoryMessages(params.sessionFile) ?? historyMessages;
2785
2938
  }
2786
2939
  const baseDeveloperInstructions = buildDeveloperInstructions(params);
2940
+ const workspaceBootstrapInstructions = await buildCodexWorkspaceBootstrapInstructions({
2941
+ params,
2942
+ resolvedWorkspace,
2943
+ effectiveWorkspace,
2944
+ sessionKey: sandboxSessionKey,
2945
+ sessionAgentId
2946
+ });
2787
2947
  let promptText = params.prompt;
2788
- let developerInstructions = baseDeveloperInstructions;
2948
+ let developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions);
2789
2949
  let prePromptMessageCount = historyMessages.length;
2790
2950
  if (activeContextEngine) try {
2791
2951
  const assembled = await assembleHarnessContextEngine({
@@ -2807,7 +2967,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2807
2967
  systemPromptAddition: assembled.systemPromptAddition
2808
2968
  });
2809
2969
  promptText = projection.promptText;
2810
- developerInstructions = joinPresentSections(baseDeveloperInstructions, projection.developerInstructionAddition);
2970
+ developerInstructions = joinPresentSections(baseDeveloperInstructions, workspaceBootstrapInstructions, projection.developerInstructionAddition);
2811
2971
  prePromptMessageCount = projection.prePromptMessageCount;
2812
2972
  } catch (assembleErr) {
2813
2973
  embeddedAgentLog.warn("context engine assemble failed; using Codex baseline prompt", { error: formatErrorMessage(assembleErr) });
@@ -2831,13 +2991,6 @@ async function runCodexAppServerAttempt(params, options = {}) {
2831
2991
  messages: historyMessages,
2832
2992
  ctx: hookContext
2833
2993
  });
2834
- const workspaceBootstrapInstructions = await buildCodexWorkspaceBootstrapInstructions({
2835
- params,
2836
- resolvedWorkspace,
2837
- effectiveWorkspace,
2838
- sessionKey: sandboxSessionKey,
2839
- sessionAgentId
2840
- });
2841
2994
  const trajectoryRecorder = createCodexTrajectoryRecorder({
2842
2995
  attempt: params,
2843
2996
  cwd: effectiveWorkspace,
@@ -2850,6 +3003,10 @@ async function runCodexAppServerAttempt(params, options = {}) {
2850
3003
  let trajectoryEndRecorded = false;
2851
3004
  let nativeHookRelay;
2852
3005
  let startupClientForCleanup;
3006
+ const startupTimeoutMs = resolveCodexStartupTimeoutMs({
3007
+ timeoutMs: params.timeoutMs,
3008
+ timeoutFloorMs: options.startupTimeoutFloorMs
3009
+ });
2853
3010
  try {
2854
3011
  emitCodexAppServerEvent(params, {
2855
3012
  stream: "codex_app_server.lifecycle",
@@ -2863,16 +3020,36 @@ async function runCodexAppServerAttempt(params, options = {}) {
2863
3020
  sessionKey: sandboxSessionKey,
2864
3021
  config: params.config,
2865
3022
  runId: params.runId,
3023
+ attemptTimeoutMs: params.timeoutMs,
3024
+ startupTimeoutMs,
3025
+ turnStartTimeoutMs: params.timeoutMs,
2866
3026
  signal: runAbortController.signal
2867
3027
  });
2868
- const threadConfig = mergeCodexConfigInstructions(nativeHookRelay ? buildCodexNativeHookRelayConfig({
3028
+ const threadConfig = nativeHookRelay ? buildCodexNativeHookRelayConfig({
2869
3029
  relay: nativeHookRelay,
2870
3030
  events: nativeHookRelayEvents,
2871
3031
  hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec
2872
- }) : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : void 0, workspaceBootstrapInstructions);
3032
+ }) : options.nativeHookRelay?.enabled === false ? buildCodexNativeHookRelayDisabledConfig() : void 0;
3033
+ const pluginThreadConfigEnabled = shouldBuildCodexPluginThreadConfig(pluginConfig);
3034
+ const pluginAppCacheKey = buildCodexAppInventoryCacheKey({
3035
+ codexHome: resolveCodexPluginAppCacheCodexHome(appServer, agentDir),
3036
+ endpoint: resolveCodexPluginAppCacheEndpoint(appServer),
3037
+ authProfileId: startupAuthProfileId,
3038
+ accountId: startupAuthAccountCacheKey,
3039
+ envApiKeyFingerprint: startupEnvApiKeyCacheKey
3040
+ });
3041
+ const pluginThreadConfigInputFingerprint = pluginThreadConfigEnabled ? buildCodexPluginThreadConfigInputFingerprint({
3042
+ pluginConfig,
3043
+ appCacheKey: pluginAppCacheKey
3044
+ }) : void 0;
3045
+ const resolvedPluginPolicy = pluginThreadConfigEnabled ? resolveCodexPluginsPolicy(pluginConfig) : void 0;
3046
+ const enabledPluginConfigKeys = resolvedPluginPolicy ? resolvedPluginPolicy.pluginPolicies.filter((plugin) => plugin.enabled).map((plugin) => plugin.configKey).toSorted() : void 0;
3047
+ pluginAppServer = resolvedPluginPolicy?.enabled === true ? {
3048
+ ...appServer,
3049
+ approvalPolicy: withMcpElicitationsApprovalPolicy(appServer.approvalPolicy)
3050
+ } : appServer;
2873
3051
  ({client, thread} = await withCodexStartupTimeout({
2874
- timeoutMs: params.timeoutMs,
2875
- timeoutFloorMs: options.startupTimeoutFloorMs,
3052
+ timeoutMs: startupTimeoutMs,
2876
3053
  signal: runAbortController.signal,
2877
3054
  operation: async () => {
2878
3055
  let attemptedClient;
@@ -2893,9 +3070,23 @@ async function runCodexAppServerAttempt(params, options = {}) {
2893
3070
  params: runtimeParams,
2894
3071
  cwd: effectiveWorkspace,
2895
3072
  dynamicTools: toolBridge.specs,
2896
- appServer,
3073
+ appServer: pluginAppServer,
2897
3074
  developerInstructions: promptBuild.developerInstructions,
2898
- config: threadConfig
3075
+ config: threadConfig,
3076
+ pluginThreadConfig: pluginThreadConfigEnabled ? {
3077
+ enabled: true,
3078
+ inputFingerprint: pluginThreadConfigInputFingerprint,
3079
+ enabledPluginConfigKeys,
3080
+ build: () => buildCodexPluginThreadConfig({
3081
+ pluginConfig,
3082
+ request: (method, requestParams) => startupClient.request(method, requestParams, {
3083
+ timeoutMs: appServer.requestTimeoutMs,
3084
+ signal: runAbortController.signal
3085
+ }),
3086
+ appCache: defaultCodexAppInventoryCache,
3087
+ appCacheKey: pluginAppCacheKey
3088
+ })
3089
+ } : void 0
2899
3090
  })
2900
3091
  };
2901
3092
  };
@@ -2971,14 +3162,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
2971
3162
  resolveCompletion = resolve;
2972
3163
  });
2973
3164
  let notificationQueue = Promise.resolve();
2974
- const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs(options.turnCompletionIdleTimeoutMs);
3165
+ const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs(options.turnCompletionIdleTimeoutMs ?? appServer.turnCompletionIdleTimeoutMs);
2975
3166
  const turnTerminalIdleTimeoutMs = resolveCodexTurnTerminalIdleTimeoutMs(options.turnTerminalIdleTimeoutMs);
2976
3167
  let turnCompletionIdleTimer;
2977
3168
  let turnCompletionIdleWatchArmed = false;
3169
+ let turnCompletionIdleWatchPinnedByTerminalError = false;
2978
3170
  let turnTerminalIdleTimer;
2979
3171
  let turnTerminalIdleWatchArmed = false;
2980
3172
  let turnCompletionLastActivityAt = Date.now();
2981
3173
  let turnCompletionLastActivityReason = "startup";
3174
+ let turnCompletionLastActivityDetails;
2982
3175
  let activeAppServerTurnRequests = 0;
2983
3176
  const clearTurnCompletionIdleTimer = () => {
2984
3177
  if (turnCompletionIdleTimer) {
@@ -3008,14 +3201,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
3008
3201
  turnId,
3009
3202
  idleMs,
3010
3203
  timeoutMs: turnCompletionIdleTimeoutMs,
3011
- lastActivityReason: turnCompletionLastActivityReason
3204
+ lastActivityReason: turnCompletionLastActivityReason,
3205
+ ...turnCompletionLastActivityDetails
3012
3206
  });
3013
3207
  embeddedAgentLog.warn("codex app-server turn idle timed out waiting for completion", {
3014
3208
  threadId: thread.threadId,
3015
3209
  turnId,
3016
3210
  idleMs,
3017
3211
  timeoutMs: turnCompletionIdleTimeoutMs,
3018
- lastActivityReason: turnCompletionLastActivityReason
3212
+ lastActivityReason: turnCompletionLastActivityReason,
3213
+ ...turnCompletionLastActivityDetails
3019
3214
  });
3020
3215
  runAbortController.abort("turn_completion_idle_timeout");
3021
3216
  };
@@ -3035,14 +3230,16 @@ async function runCodexAppServerAttempt(params, options = {}) {
3035
3230
  turnId,
3036
3231
  idleMs,
3037
3232
  timeoutMs: turnTerminalIdleTimeoutMs,
3038
- lastActivityReason: turnCompletionLastActivityReason
3233
+ lastActivityReason: turnCompletionLastActivityReason,
3234
+ ...turnCompletionLastActivityDetails
3039
3235
  });
3040
3236
  embeddedAgentLog.warn("codex app-server turn idle timed out waiting for terminal event", {
3041
3237
  threadId: thread.threadId,
3042
3238
  turnId,
3043
3239
  idleMs,
3044
3240
  timeoutMs: turnTerminalIdleTimeoutMs,
3045
- lastActivityReason: turnCompletionLastActivityReason
3241
+ lastActivityReason: turnCompletionLastActivityReason,
3242
+ ...turnCompletionLastActivityDetails
3046
3243
  });
3047
3244
  runAbortController.abort("turn_terminal_idle_timeout");
3048
3245
  };
@@ -3065,6 +3262,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3065
3262
  const touchTurnCompletionActivity = (reason, options) => {
3066
3263
  turnCompletionLastActivityAt = Date.now();
3067
3264
  turnCompletionLastActivityReason = reason;
3265
+ turnCompletionLastActivityDetails = options?.details;
3068
3266
  emitTrustedDiagnosticEvent({
3069
3267
  type: "run.progress",
3070
3268
  runId: params.runId,
@@ -3072,10 +3270,23 @@ async function runCodexAppServerAttempt(params, options = {}) {
3072
3270
  sessionKey: params.sessionKey,
3073
3271
  reason: `codex_app_server:${reason}`
3074
3272
  });
3075
- if (options?.arm) turnCompletionIdleWatchArmed = true;
3273
+ if (options?.arm) {
3274
+ turnCompletionIdleWatchArmed = true;
3275
+ turnCompletionIdleWatchPinnedByTerminalError = false;
3276
+ }
3076
3277
  scheduleTurnCompletionIdleWatch();
3077
3278
  scheduleTurnTerminalIdleWatch();
3078
3279
  };
3280
+ const disarmTurnCompletionIdleWatch = () => {
3281
+ turnCompletionIdleWatchArmed = false;
3282
+ turnCompletionIdleWatchPinnedByTerminalError = false;
3283
+ clearTurnCompletionIdleTimer();
3284
+ };
3285
+ const armTurnCompletionIdleWatch = (options) => {
3286
+ turnCompletionIdleWatchArmed = true;
3287
+ turnCompletionIdleWatchPinnedByTerminalError = options?.pinnedByTerminalError === true;
3288
+ scheduleTurnCompletionIdleWatch();
3289
+ };
3079
3290
  const emitLifecycleStart = () => {
3080
3291
  emitCodexAppServerEvent(params, {
3081
3292
  stream: "lifecycle",
@@ -3099,13 +3310,17 @@ async function runCodexAppServerAttempt(params, options = {}) {
3099
3310
  lifecycleTerminalEmitted = true;
3100
3311
  };
3101
3312
  const handleNotification = async (notification) => {
3102
- touchTurnCompletionActivity(`notification:${notification.method}`);
3103
3313
  userInputBridge?.handleNotification(notification);
3104
3314
  if (!projector || !turnId) {
3105
3315
  pendingNotifications.push(notification);
3106
3316
  return;
3107
3317
  }
3108
- const isTurnCompletion = notification.method === "turn/completed" && isTurnNotification(notification.params, thread.threadId, turnId);
3318
+ const isCurrentTurnNotification = isTurnNotification(notification.params, thread.threadId, turnId);
3319
+ if (isCurrentTurnNotification) touchTurnCompletionActivity(`notification:${notification.method}`, { details: describeNotificationActivity(notification) });
3320
+ if (isCurrentTurnNotification && notification.method === "error") if (isRetryableErrorNotification(notification.params)) disarmTurnCompletionIdleWatch();
3321
+ else armTurnCompletionIdleWatch({ pinnedByTerminalError: true });
3322
+ else if (turnCompletionIdleWatchArmed && !turnCompletionIdleWatchPinnedByTerminalError && notification.method !== "turn/completed" && isCurrentTurnNotification) disarmTurnCompletionIdleWatch();
3323
+ const isTurnCompletion = notification.method === "turn/completed" && isCurrentTurnNotification;
3109
3324
  try {
3110
3325
  await projector.handleNotification(notification);
3111
3326
  } catch (error) {
@@ -3136,7 +3351,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
3136
3351
  try {
3137
3352
  if (request.method === "account/chatgptAuthTokens/refresh") return refreshCodexAppServerAuthTokens({
3138
3353
  agentDir,
3139
- authProfileId: startupAuthProfileId
3354
+ authProfileId: startupAuthProfileId,
3355
+ config: params.config
3140
3356
  });
3141
3357
  if (!turnId) return;
3142
3358
  if (request.method === "mcpServer/elicitation/request") {
@@ -3146,6 +3362,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3146
3362
  paramsForRun: params,
3147
3363
  threadId: thread.threadId,
3148
3364
  turnId,
3365
+ pluginAppPolicyContext: thread.pluginAppPolicyContext,
3149
3366
  signal: runAbortController.signal
3150
3367
  });
3151
3368
  }
@@ -3265,7 +3482,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3265
3482
  turn = assertCodexTurnStartResponse(await client.request("turn/start", buildTurnStartParams(params, {
3266
3483
  threadId: thread.threadId,
3267
3484
  cwd: effectiveWorkspace,
3268
- appServer,
3485
+ appServer: pluginAppServer,
3269
3486
  promptText: promptBuild.prompt
3270
3487
  }), {
3271
3488
  timeoutMs: params.timeoutMs,
@@ -3593,7 +3810,12 @@ function createCodexNativeHookRelay(params) {
3593
3810
  ...params.config ? { config: params.config } : {},
3594
3811
  runId: params.runId,
3595
3812
  allowedEvents: params.events,
3596
- ttlMs: params.options?.ttlMs,
3813
+ ttlMs: resolveCodexNativeHookRelayTtlMs({
3814
+ explicitTtlMs: params.options?.ttlMs,
3815
+ attemptTimeoutMs: params.attemptTimeoutMs,
3816
+ startupTimeoutMs: params.startupTimeoutMs,
3817
+ turnStartTimeoutMs: params.turnStartTimeoutMs
3818
+ }),
3597
3819
  signal: params.signal,
3598
3820
  command: { timeoutMs: params.options?.gatewayTimeoutMs }
3599
3821
  });
@@ -3602,6 +3824,11 @@ function resolveCodexNativeHookRelayEvents(params) {
3602
3824
  if (params.configuredEvents?.length) return params.configuredEvents;
3603
3825
  return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
3604
3826
  }
3827
+ function resolveCodexNativeHookRelayTtlMs(params) {
3828
+ if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
3829
+ const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
3830
+ return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
3831
+ }
3605
3832
  function buildCodexNativeHookRelayId(params) {
3606
3833
  const hash = createHash("sha256");
3607
3834
  hash.update("openclaw:codex:native-hook-relay:v1");
@@ -3616,13 +3843,20 @@ function interruptCodexTurnBestEffort(client, params) {
3616
3843
  embeddedAgentLog.debug("codex app-server turn interrupt failed during abort", { error });
3617
3844
  });
3618
3845
  }
3846
+ function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
3847
+ return {
3848
+ sessionKey: sandboxSessionKey,
3849
+ runSessionKey: params.sessionKey && params.sessionKey !== sandboxSessionKey ? params.sessionKey : void 0
3850
+ };
3851
+ }
3619
3852
  async function buildDynamicTools(input) {
3620
3853
  const { params } = input;
3621
3854
  if (params.disableTools || !supportsModelTools(params.model)) return [];
3622
3855
  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({
3856
+ const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
3857
+ const createOpenClawCodingTools = openClawCodingToolsFactoryForTests ?? (await import("openclaw/plugin-sdk/agent-harness")).createOpenClawCodingTools;
3858
+ const sessionKeys = resolveOpenClawCodingToolsSessionKeys(params, input.sandboxSessionKey);
3859
+ const filteredTools = filterCodexDynamicToolsForAllowlist(filterToolsForVisionInputs(applyCodexDynamicToolProfile(createOpenClawCodingTools({
3626
3860
  agentId: input.sessionAgentId,
3627
3861
  ...buildEmbeddedAttemptToolRunContext(params),
3628
3862
  exec: {
@@ -3644,8 +3878,7 @@ async function buildDynamicTools(input) {
3644
3878
  senderE164: params.senderE164,
3645
3879
  senderIsOwner: params.senderIsOwner,
3646
3880
  allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,
3647
- sessionKey: input.sandboxSessionKey,
3648
- runSessionKey: params.sessionKey && params.sessionKey !== input.sandboxSessionKey ? params.sessionKey : void 0,
3881
+ ...sessionKeys,
3649
3882
  sessionId: params.sessionId,
3650
3883
  runId: params.runId,
3651
3884
  agentDir,
@@ -3670,8 +3903,9 @@ async function buildDynamicTools(input) {
3670
3903
  hasRepliedRef: params.hasRepliedRef,
3671
3904
  modelHasVision,
3672
3905
  requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
3906
+ sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
3673
3907
  disableMessageTool: params.disableMessageTool,
3674
- forceMessageTool: params.sourceReplyDeliveryMode === "message_tool_only",
3908
+ forceMessageTool: shouldForceMessageTool(params),
3675
3909
  enableHeartbeatTool: params.trigger === "heartbeat",
3676
3910
  forceHeartbeatTool: params.trigger === "heartbeat",
3677
3911
  onYield: (message) => {
@@ -3688,8 +3922,7 @@ async function buildDynamicTools(input) {
3688
3922
  }), input.pluginConfig), {
3689
3923
  modelHasVision,
3690
3924
  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;
3925
+ }), params.toolsAllow);
3693
3926
  return normalizeAgentRuntimeTools({
3694
3927
  runtimePlan: params.runtimePlan,
3695
3928
  tools: filteredTools,
@@ -3702,6 +3935,14 @@ async function buildDynamicTools(input) {
3702
3935
  model: params.model
3703
3936
  });
3704
3937
  }
3938
+ function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
3939
+ if (!toolsAllow || toolsAllow.length === 0) return tools;
3940
+ const allowSet = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)).filter(Boolean));
3941
+ return tools.filter((tool) => allowSet.has(normalizeCodexDynamicToolName(tool.name)));
3942
+ }
3943
+ function shouldForceMessageTool(params) {
3944
+ return params.sourceReplyDeliveryMode === "message_tool_only";
3945
+ }
3705
3946
  function shouldProjectMirroredHistoryForCodexStart(params) {
3706
3947
  if (!params.historyMessages.some((message) => message.role === "user")) return false;
3707
3948
  if (!params.startupBinding?.threadId) return true;
@@ -3723,10 +3964,9 @@ async function withCodexStartupTimeout(params) {
3723
3964
  }
3724
3965
  reject(error);
3725
3966
  };
3726
- const timeoutMs = Math.max(params.timeoutFloorMs ?? 100, params.timeoutMs);
3727
3967
  timeout = setTimeout(() => {
3728
3968
  rejectOnce(/* @__PURE__ */ new Error("codex app-server startup timed out"));
3729
- }, timeoutMs);
3969
+ }, params.timeoutMs);
3730
3970
  const abortListener = () => rejectOnce(/* @__PURE__ */ new Error("codex app-server startup aborted"));
3731
3971
  params.signal.addEventListener("abort", abortListener, { once: true });
3732
3972
  abortCleanup = () => params.signal.removeEventListener("abort", abortListener);
@@ -3736,6 +3976,9 @@ async function withCodexStartupTimeout(params) {
3736
3976
  abortCleanup?.();
3737
3977
  }
3738
3978
  }
3979
+ function resolveCodexStartupTimeoutMs(params) {
3980
+ return Math.max(params.timeoutFloorMs ?? CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS, params.timeoutMs);
3981
+ }
3739
3982
  function resolveCodexTurnCompletionIdleTimeoutMs(value) {
3740
3983
  if (value === void 0) return CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS;
3741
3984
  if (!Number.isFinite(value)) return CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS;
@@ -3791,10 +4034,37 @@ function readCodexErrorPayload(error) {
3791
4034
  rateLimits: nestedError.rateLimits ?? data.rateLimits
3792
4035
  };
3793
4036
  }
4037
+ function describeNotificationActivity(notification) {
4038
+ if (!isJsonObject(notification.params)) return { lastNotificationMethod: notification.method };
4039
+ if (notification.method !== "rawResponseItem/completed") return { lastNotificationMethod: notification.method };
4040
+ const item = isJsonObject(notification.params.item) ? notification.params.item : void 0;
4041
+ if (!item) return { lastNotificationMethod: notification.method };
4042
+ return {
4043
+ lastNotificationMethod: notification.method,
4044
+ lastNotificationItemId: readString(item, "id"),
4045
+ lastNotificationItemType: readString(item, "type"),
4046
+ lastNotificationItemRole: readString(item, "role"),
4047
+ lastAssistantTextPreview: readRawAssistantTextPreview(item)
4048
+ };
4049
+ }
4050
+ function readRawAssistantTextPreview(item) {
4051
+ if (readString(item, "role") !== "assistant" || !Array.isArray(item.content)) return;
4052
+ const text = item.content.flatMap((content) => {
4053
+ if (!isJsonObject(content)) return [];
4054
+ const contentText = readString(content, "text");
4055
+ return contentText ? [contentText] : [];
4056
+ }).join("\n").trim();
4057
+ if (!text) return;
4058
+ return text.length > 240 ? `${text.slice(0, 237)}...` : text;
4059
+ }
3794
4060
  function isTurnNotification(value, threadId, turnId) {
3795
4061
  if (!isJsonObject(value)) return false;
3796
4062
  return readString(value, "threadId") === threadId && readNotificationTurnId(value) === turnId;
3797
4063
  }
4064
+ function isRetryableErrorNotification(value) {
4065
+ if (!isJsonObject(value)) return false;
4066
+ return readBoolean(value, "willRetry") === true || readBoolean(value, "will_retry") === true;
4067
+ }
3798
4068
  function isTerminalTurnStatus(status) {
3799
4069
  return status === "completed" || status === "interrupted" || status === "failed";
3800
4070
  }
@@ -3809,6 +4079,10 @@ function readString(record, key) {
3809
4079
  const value = record[key];
3810
4080
  return typeof value === "string" ? value : void 0;
3811
4081
  }
4082
+ function readBoolean(record, key) {
4083
+ const value = record[key];
4084
+ return typeof value === "boolean" ? value : void 0;
4085
+ }
3812
4086
  async function readMirroredSessionHistoryMessages(sessionFile) {
3813
4087
  const messages = await readCodexMirroredSessionHistoryMessages(sessionFile);
3814
4088
  if (!messages) embeddedAgentLog.warn("failed to read mirrored session history for codex harness hooks", { sessionFile });
@@ -3855,12 +4129,6 @@ function renderCodexWorkspaceBootstrapInstructions(contextFiles) {
3855
4129
  for (const file of files) lines.push(`## ${file.path}`, "", file.content, "");
3856
4130
  return lines.join("\n").trim();
3857
4131
  }
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
4132
  function remapCodexContextFilePath(params) {
3865
4133
  const relativePath = path.relative(params.sourceWorkspaceDir, params.file.path);
3866
4134
  if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath) || params.sourceWorkspaceDir === params.targetWorkspaceDir) return params.file;
@@ -3900,15 +4168,6 @@ async function mirrorTranscriptBestEffort(params) {
3900
4168
  embeddedAgentLog.warn("failed to mirror codex app-server transcript", { error });
3901
4169
  }
3902
4170
  }
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
4171
  function isNonEmptyString(value) {
3913
4172
  return typeof value === "string" && value.length > 0;
3914
4173
  }
@@ -3925,8 +4184,5 @@ function handleApprovalRequest(params) {
3925
4184
  signal: params.signal
3926
4185
  });
3927
4186
  }
3928
- ({ ...createCodexAppServerClientFactoryTestHooks((factory) => {
3929
- clientFactory = factory;
3930
- }) });
3931
4187
  //#endregion
3932
4188
  export { runCodexAppServerAttempt };