@openclaw/codex 2026.5.26-beta.1 → 2026.5.26-beta.2

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,16 +1,16 @@
1
- import { t as defaultCodexAppServerClientFactory } from "./client-factory-CaOpX89B.js";
1
+ import { n as defaultLeasedCodexAppServerClientFactory } from "./client-factory-fW4Hh9q_.js";
2
2
  import { c as readCodexPluginConfig, d as resolveCodexPluginsPolicy, f as shouldAutoApproveCodexAppServerApprovals, i as isCodexAppServerApprovalPolicyAllowedByRequirements, l as resolveCodexAppServerRuntimeOptions, o as isCodexSandboxExecServerEnabled, p as withMcpElicitationsApprovalPolicy, u as resolveCodexComputerUseConfig } from "./config-0-UN67Qg.js";
3
- import { a as readCodexDynamicToolCallParams, c as readCodexTurn, i as assertCodexTurnStartResponse } from "./protocol-validators-CSuFMLvY.js";
3
+ import { a as readCodexDynamicToolCallParams, c as readCodexTurn, i as assertCodexTurnStartResponse } from "./protocol-validators-DtjYmUw_.js";
4
4
  import { a as isCodexAppServerConnectionClosedError, i as isCodexAppServerApprovalRequest, l as isJsonObject, r as compareCodexAppServerVersions, s as MIN_CODEX_SANDBOX_EXEC_SERVER_APP_SERVER_VERSION } from "./client-BVK_jmHW.js";
5
- import { C as defaultCodexAppInventoryCache, D as projectContextEngineAssemblyForCodex, O as resolveCodexContextEngineProjectionMaxChars, T as sanitizeCodexHistoryImagePayloads, _ as buildCodexPluginThreadConfigInputFingerprint, a as buildDeveloperInstructions, c as buildTurnCollaborationMode, d as isContextEngineBindingCompatible, g as buildCodexPluginThreadConfig, h as isCodexAppServerProfilerEnabled, i as buildContextEngineBinding, k as resolveCodexContextEngineProjectionReserveTokens, l as buildTurnStartParams, m as startOrResumeThread, n as areCodexDynamicToolFingerprintsCompatible, u as codexDynamicToolsFingerprint, v as mergeCodexThreadConfigs, y as shouldBuildCodexPluginThreadConfig } from "./thread-lifecycle-BapIT9Qs.js";
5
+ import { C as defaultCodexAppInventoryCache, D as projectContextEngineAssemblyForCodex, O as resolveCodexContextEngineProjectionMaxChars, T as sanitizeCodexHistoryImagePayloads, _ as buildCodexPluginThreadConfigInputFingerprint, a as buildDeveloperInstructions, c as buildTurnCollaborationMode, d as isContextEngineBindingCompatible, g as buildCodexPluginThreadConfig, h as isCodexAppServerProfilerEnabled, i as buildContextEngineBinding, k as resolveCodexContextEngineProjectionReserveTokens, l as buildTurnStartParams, m as startOrResumeThread, n as areCodexDynamicToolFingerprintsCompatible, u as codexDynamicToolsFingerprint, v as mergeCodexThreadConfigs, y as shouldBuildCodexPluginThreadConfig } from "./thread-lifecycle-CUXQezJL.js";
6
6
  import { i as readCodexAppServerBinding, t as clearCodexAppServerBinding } from "./session-binding-UFjjws4k.js";
7
- import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./request-DmBkYLPy.js";
7
+ import { n as buildCodexPluginAppCacheKey, r as CODEX_CONTROL_METHODS } from "./request-OaxhR46w.js";
8
8
  import { g as shouldRefreshCodexRateLimitsForUsageLimitMessage, h as resolveCodexUsageLimitResetAtMs, i as readCodexNotificationTurnId, m as formatCodexUsageLimitErrorMessage, n as isCodexNotificationForTurn, r as readCodexNotificationThreadId, s as formatCodexDisplayText, t as describeCodexNotificationCorrelation } from "./notification-correlation-YINts3PA.js";
9
- import { c as resolveCodexAppServerAuthAccountCacheKey, d as resolveCodexAppServerFallbackApiKeyCacheKey, f as resolveCodexAppServerHomeDir, l as resolveCodexAppServerAuthProfileId, s as refreshCodexAppServerAuthTokens, t as clearSharedCodexAppServerClientIfCurrent, u as resolveCodexAppServerAuthProfileIdForAgent } from "./shared-client-Fou5nAyt.js";
9
+ import { a as releaseLeasedSharedCodexAppServerClient, d as resolveCodexAppServerAuthProfileId, f as resolveCodexAppServerAuthProfileIdForAgent, l as refreshCodexAppServerAuthTokens, m as resolveCodexAppServerHomeDir, o as retireSharedCodexAppServerClientIfCurrent, p as resolveCodexAppServerFallbackApiKeyCacheKey, t as clearSharedCodexAppServerClientIfCurrent, u as resolveCodexAppServerAuthAccountCacheKey } from "./shared-client-8kIrP817.js";
10
10
  import { i as resolveCodexNativeExecutionPolicy } from "./sandbox-guard-CTnEWuor.js";
11
- import { a as resolveCodexDynamicToolsLoading, i as normalizeCodexDynamicToolName, n as filterCodexDynamicTools, r as isForcedPrivateQaCodexRuntime, t as createCodexDynamicToolBridge } from "./dynamic-tools-BG9ICyU4.js";
12
- import { a as handleCodexAppServerElicitationRequest, c as emitDynamicToolTerminalDiagnostic, i as buildCodexNativeHookRelayDisabledConfig, l as handleCodexAppServerApprovalRequest, n as CODEX_NATIVE_HOOK_RELAY_EVENTS, o as emitDynamicToolErrorDiagnostic, r as buildCodexNativeHookRelayConfig, s as emitDynamicToolStartedDiagnostic, t as filterToolsForVisionInputs } from "./vision-tools-B30YCKRK.js";
13
- import { t as ensureCodexComputerUse } from "./computer-use-CDDlIBhY.js";
11
+ import { a as resolveCodexDynamicToolsLoading, i as normalizeCodexDynamicToolName, n as filterCodexDynamicTools, r as isForcedPrivateQaCodexRuntime, t as createCodexDynamicToolBridge } from "./dynamic-tools-DlEefKNQ.js";
12
+ import { a as handleCodexAppServerElicitationRequest, c as emitDynamicToolTerminalDiagnostic, i as buildCodexNativeHookRelayDisabledConfig, l as handleCodexAppServerApprovalRequest, n as CODEX_NATIVE_HOOK_RELAY_EVENTS, o as emitDynamicToolErrorDiagnostic, r as buildCodexNativeHookRelayConfig, s as emitDynamicToolStartedDiagnostic, t as filterToolsForVisionInputs } from "./vision-tools-DqpLmF5H.js";
13
+ import { t as ensureCodexComputerUse } from "./computer-use-DSdj1hJY.js";
14
14
  import { n as rememberCodexRateLimits, t as readRecentCodexRateLimits } from "./rate-limit-cache-N66I-Rd7.js";
15
15
  import { createHash, randomUUID } from "node:crypto";
16
16
  import fsSync from "node:fs";
@@ -108,7 +108,7 @@ function buildSenderLabel(params) {
108
108
  if (!params.senderId || label.includes(params.senderId)) return label;
109
109
  return `${label} (${params.senderId})`;
110
110
  }
111
- function buildCodexUserPromptMessage(params) {
111
+ function buildCodexUserPromptMessageFromPrepared(params, preparedUserMessage) {
112
112
  const senderId = normalizeOptionalString(params.senderId);
113
113
  const senderName = normalizeOptionalString(params.senderName);
114
114
  const senderUsername = normalizeOptionalString(params.senderUsername);
@@ -120,6 +120,18 @@ function buildCodexUserPromptMessage(params) {
120
120
  senderE164
121
121
  });
122
122
  const sourceChannel = normalizeOptionalString(params.inputProvenance?.sourceChannel ?? params.messageChannel ?? params.messageProvider);
123
+ if (preparedUserMessage) return {
124
+ role: "user",
125
+ timestamp: Date.now(),
126
+ ...params.inputProvenance ? { provenance: params.inputProvenance } : {},
127
+ ...sourceChannel ? { sourceChannel } : {},
128
+ ...senderId ? { senderId } : {},
129
+ ...senderName ? { senderName } : {},
130
+ ...senderUsername ? { senderUsername } : {},
131
+ ...senderE164 ? { senderE164 } : {},
132
+ ...senderLabel ? { senderLabel } : {},
133
+ ...preparedUserMessage
134
+ };
123
135
  return {
124
136
  role: "user",
125
137
  content: params.prompt,
@@ -133,6 +145,12 @@ function buildCodexUserPromptMessage(params) {
133
145
  ...senderLabel ? { senderLabel } : {}
134
146
  };
135
147
  }
148
+ function buildCodexUserPromptMessage(params) {
149
+ return buildCodexUserPromptMessageFromPrepared(params, params.userTurnTranscriptRecorder?.message);
150
+ }
151
+ async function buildResolvedCodexUserPromptMessage(params) {
152
+ return buildCodexUserPromptMessageFromPrepared(params, await params.userTurnTranscriptRecorder?.resolveMessage() ?? params.userTurnTranscriptRecorder?.message);
153
+ }
136
154
  /**
137
155
  * Tag a message with a stable logical identity for mirror dedupe. Callers
138
156
  * should use a value that is invariant for the same logical message across
@@ -174,24 +192,30 @@ function buildMirrorDedupeIdentity(message) {
174
192
  }
175
193
  async function mirrorCodexAppServerTranscript(params) {
176
194
  const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
177
- if (messages.length === 0) return;
195
+ if (messages.length === 0) return { userMessagesPresent: [] };
178
196
  const lock = await acquireSessionWriteLock({
179
197
  sessionFile: params.sessionFile,
180
198
  ...resolveSessionWriteLockOptions(params.config)
181
199
  });
182
200
  const appendedUpdates = [];
201
+ const userMessagesPresent = [];
183
202
  try {
184
203
  const mirrorState = await readTranscriptMirrorState(params.sessionFile);
185
204
  let nextMessageSeq = mirrorState.messageCount;
186
205
  for (const message of messages) {
187
206
  const dedupeIdentity = buildMirrorDedupeIdentity(message);
188
207
  const idempotencyKey = params.idempotencyScope ? `${params.idempotencyScope}:${dedupeIdentity}` : void 0;
189
- if (idempotencyKey && mirrorState.idempotencyKeys.has(idempotencyKey)) continue;
208
+ const transcriptMessage = {
209
+ ...message,
210
+ ...idempotencyKey ? { idempotencyKey } : {}
211
+ };
212
+ if (idempotencyKey && mirrorState.idempotencyKeys.has(idempotencyKey)) {
213
+ const persistedUserMessage = mirrorState.userMessagesByIdempotencyKey.get(idempotencyKey);
214
+ if (persistedUserMessage) userMessagesPresent.push(persistedUserMessage);
215
+ continue;
216
+ }
190
217
  const nextMessage = runAgentHarnessBeforeMessageWriteHook({
191
- message: {
192
- ...message,
193
- ...idempotencyKey ? { idempotencyKey } : {}
194
- },
218
+ message: transcriptMessage,
195
219
  agentId: params.agentId,
196
220
  sessionKey: params.sessionKey
197
221
  });
@@ -203,8 +227,13 @@ async function mirrorCodexAppServerTranscript(params) {
203
227
  const { messageId, message: appendedMessage } = await appendSessionTranscriptMessage({
204
228
  transcriptPath: params.sessionFile,
205
229
  message: messageToAppend,
230
+ idempotencyLookup: idempotencyKey ? "caller-checked" : "scan",
206
231
  config: params.config
207
232
  });
233
+ if (appendedMessage.role === "user") {
234
+ userMessagesPresent.push(appendedMessage);
235
+ if (idempotencyKey) mirrorState.userMessagesByIdempotencyKey.set(idempotencyKey, appendedMessage);
236
+ }
208
237
  nextMessageSeq += 1;
209
238
  appendedUpdates.push({
210
239
  messageId,
@@ -223,9 +252,11 @@ async function mirrorCodexAppServerTranscript(params) {
223
252
  messageId: update.messageId,
224
253
  messageSeq: update.messageSeq
225
254
  });
255
+ return { userMessagesPresent };
226
256
  }
227
257
  async function readTranscriptMirrorState(sessionFile) {
228
258
  const idempotencyKeys = /* @__PURE__ */ new Set();
259
+ const userMessagesByIdempotencyKey = /* @__PURE__ */ new Map();
229
260
  let messageCount = 0;
230
261
  let raw;
231
262
  try {
@@ -234,7 +265,8 @@ async function readTranscriptMirrorState(sessionFile) {
234
265
  if (error.code !== "ENOENT") throw error;
235
266
  return {
236
267
  idempotencyKeys,
237
- messageCount
268
+ messageCount,
269
+ userMessagesByIdempotencyKey
238
270
  };
239
271
  }
240
272
  for (const line of raw.split(/\r?\n/)) {
@@ -242,14 +274,18 @@ async function readTranscriptMirrorState(sessionFile) {
242
274
  try {
243
275
  const parsed = JSON.parse(line);
244
276
  if (parsed.type === "message") messageCount += 1;
245
- if (typeof parsed.message?.idempotencyKey === "string") idempotencyKeys.add(parsed.message.idempotencyKey);
277
+ if (typeof parsed.message?.idempotencyKey === "string") {
278
+ idempotencyKeys.add(parsed.message.idempotencyKey);
279
+ if (parsed.message.role === "user") userMessagesByIdempotencyKey.set(parsed.message.idempotencyKey, parsed.message);
280
+ }
246
281
  } catch {
247
282
  continue;
248
283
  }
249
284
  }
250
285
  return {
251
286
  idempotencyKeys,
252
- messageCount
287
+ messageCount,
288
+ userMessagesByIdempotencyKey
253
289
  };
254
290
  }
255
291
  //#endregion
@@ -4312,7 +4348,7 @@ function readRequestId(record) {
4312
4348
  }
4313
4349
  //#endregion
4314
4350
  //#region extensions/codex/src/app-server/run-attempt.ts
4315
- const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 3e4;
4351
+ const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 9e4;
4316
4352
  const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
4317
4353
  const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
4318
4354
  const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
@@ -4799,7 +4835,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
4799
4835
  let codexModelCallTerminalEmitted = false;
4800
4836
  let codexModelCallRequestPayloadBytes;
4801
4837
  const preDynamicStartupStages = createCodexDynamicToolBuildStageTracker({ enabled: profilerEnabled });
4802
- const attemptClientFactory = options.clientFactory ?? defaultCodexAppServerClientFactory;
4838
+ const attemptClientFactory = options.clientFactory ?? defaultLeasedCodexAppServerClientFactory;
4803
4839
  const pluginConfig = readCodexPluginConfig(options.pluginConfig);
4804
4840
  const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
4805
4841
  const configuredAppServer = resolveCodexAppServerRuntimeOptions({ pluginConfig });
@@ -5150,6 +5186,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
5150
5186
  let thread;
5151
5187
  let trajectoryEndRecorded = false;
5152
5188
  let nativeHookRelay;
5189
+ let releaseSharedClientLease;
5153
5190
  let startupClientForCleanup;
5154
5191
  let sandboxExecEnvironmentAcquired = false;
5155
5192
  const releaseSandboxExecEnvironment = async () => {
@@ -5233,106 +5270,120 @@ async function runCodexAppServerAttempt(params, options = {}) {
5233
5270
  operation: async () => {
5234
5271
  let attemptedClient;
5235
5272
  const startupAttempt = async () => {
5236
- const startupClient = await attemptClientFactory(appServer.start, startupAuthProfileId, agentDir, params.config);
5237
- attemptedClient = startupClient;
5238
- startupClientForCleanup = startupClient;
5239
- await ensureCodexComputerUse({
5240
- client: startupClient,
5241
- pluginConfig,
5242
- timeoutMs: appServer.requestTimeoutMs,
5243
- signal: runAbortController.signal
5244
- });
5245
- let startupSandboxEnvironment;
5246
- let startupSandboxEnvironmentAcquired = false;
5247
- const releaseStartupSandboxEnvironment = async () => {
5248
- if (startupSandboxEnvironmentAcquired) {
5249
- startupSandboxEnvironmentAcquired = false;
5250
- await releaseCodexSandboxExecServerEnvironment(sandbox);
5251
- }
5252
- };
5253
- releaseStartupResourcesOnTimeout = releaseStartupSandboxEnvironment;
5273
+ let startupClientLease;
5274
+ let startupAttemptSucceeded = false;
5254
5275
  try {
5255
- startupSandboxEnvironment = shouldRequireCodexSandboxExecServerEnvironment({
5256
- sandbox,
5257
- nativeToolSurfaceEnabled,
5258
- sandboxExecServerEnabled
5259
- }) ? await ensureCodexSandboxExecServerEnvironment({
5276
+ const startupClient = await attemptClientFactory(appServer.start, startupAuthProfileId, agentDir, params.config);
5277
+ startupClientLease = () => {
5278
+ releaseLeasedSharedCodexAppServerClient(startupClient);
5279
+ };
5280
+ releaseSharedClientLease = startupClientLease;
5281
+ attemptedClient = startupClient;
5282
+ startupClientForCleanup = startupClient;
5283
+ await ensureCodexComputerUse({
5260
5284
  client: startupClient,
5261
- sandbox: sandbox ?? null,
5262
- appServerStartOptions: appServer.start,
5285
+ pluginConfig,
5263
5286
  timeoutMs: appServer.requestTimeoutMs,
5264
5287
  signal: runAbortController.signal
5265
- }) : void 0;
5266
- startupSandboxEnvironmentAcquired = Boolean(startupSandboxEnvironment);
5267
- if (runAbortController.signal.aborted) {
5268
- await releaseStartupSandboxEnvironment();
5269
- throw new Error("codex app-server startup aborted");
5270
- }
5271
- if (sandbox?.enabled && nativeToolSurfaceEnabled && sandboxExecServerEnabled && !startupSandboxEnvironment) throw new Error("Codex app-server did not register an OpenClaw sandbox exec-server environment.");
5272
- } catch (error) {
5273
- await releaseStartupSandboxEnvironment();
5274
- throw error;
5275
- }
5276
- const startupEnvironmentSelection = resolveCodexSandboxEnvironmentSelection(startupSandboxEnvironment, nativeToolSurfaceEnabled);
5277
- const startupExecutionCwd = resolveCodexAppServerExecutionCwd({
5278
- effectiveWorkspace,
5279
- environment: startupSandboxEnvironment,
5280
- nativeToolSurfaceEnabled
5281
- });
5282
- const startupSandboxPolicy = startupSandboxEnvironment ? resolveCodexExternalSandboxPolicyForOpenClawSandbox(sandbox) : void 0;
5283
- const buildThreadLifecycleParams = () => ({
5284
- client: startupClient,
5285
- params: buildActiveRunAttemptParams(),
5286
- agentId: sessionAgentId,
5287
- cwd: startupExecutionCwd,
5288
- dynamicTools: toolBridge.specs,
5289
- appServer: pluginAppServer,
5290
- developerInstructions: promptBuild.developerInstructions,
5291
- config: threadConfig,
5292
- finalConfigPatch: nativeHookRelayConfig,
5293
- nativeCodeModeEnabled: nativeToolSurfaceEnabled,
5294
- nativeCodeModeOnlyEnabled: appServer.codeModeOnly,
5295
- userMcpServersEnabled: nativeToolSurfaceEnabled,
5296
- mcpServersFingerprint: bundleMcpThreadConfig.fingerprint,
5297
- mcpServersFingerprintEvaluated: bundleMcpThreadConfig.evaluated,
5298
- environmentSelection: startupEnvironmentSelection,
5299
- contextEngineProjection,
5300
- pluginThreadConfig: pluginThreadConfigRequired ? {
5301
- enabled: true,
5302
- inputFingerprint: pluginThreadConfigInputFingerprint,
5303
- enabledPluginConfigKeys,
5304
- build: () => buildCodexPluginThreadConfig({
5305
- pluginConfig: pluginThreadConfigPluginConfig,
5306
- request: (method, requestParams) => startupClient.request(method, requestParams, {
5307
- timeoutMs: appServer.requestTimeoutMs,
5308
- signal: runAbortController.signal
5309
- }),
5310
- appCache: defaultCodexAppInventoryCache,
5311
- appCacheKey: pluginAppCacheKey
5312
- })
5313
- } : void 0
5314
- });
5315
- try {
5316
- restartContextEngineCodexThread = () => startOrResumeThread(buildThreadLifecycleParams());
5317
- const startupThread = await startOrResumeThread(buildThreadLifecycleParams());
5318
- if (runAbortController.signal.aborted) {
5288
+ });
5289
+ let startupSandboxEnvironment;
5290
+ let startupSandboxEnvironmentAcquired = false;
5291
+ const releaseStartupSandboxEnvironment = async () => {
5292
+ if (startupSandboxEnvironmentAcquired) {
5293
+ startupSandboxEnvironmentAcquired = false;
5294
+ await releaseCodexSandboxExecServerEnvironment(sandbox);
5295
+ }
5296
+ };
5297
+ releaseStartupResourcesOnTimeout = releaseStartupSandboxEnvironment;
5298
+ try {
5299
+ startupSandboxEnvironment = shouldRequireCodexSandboxExecServerEnvironment({
5300
+ sandbox,
5301
+ nativeToolSurfaceEnabled,
5302
+ sandboxExecServerEnabled
5303
+ }) ? await ensureCodexSandboxExecServerEnvironment({
5304
+ client: startupClient,
5305
+ sandbox: sandbox ?? null,
5306
+ appServerStartOptions: appServer.start,
5307
+ timeoutMs: appServer.requestTimeoutMs,
5308
+ signal: runAbortController.signal
5309
+ }) : void 0;
5310
+ startupSandboxEnvironmentAcquired = Boolean(startupSandboxEnvironment);
5311
+ if (runAbortController.signal.aborted) {
5312
+ await releaseStartupSandboxEnvironment();
5313
+ throw new Error("codex app-server startup aborted");
5314
+ }
5315
+ if (sandbox?.enabled && nativeToolSurfaceEnabled && sandboxExecServerEnabled && !startupSandboxEnvironment) throw new Error("Codex app-server did not register an OpenClaw sandbox exec-server environment.");
5316
+ } catch (error) {
5319
5317
  await releaseStartupSandboxEnvironment();
5320
- throw new Error("codex app-server startup aborted");
5318
+ throw error;
5321
5319
  }
5322
- startupSandboxEnvironmentAcquired = false;
5323
- return {
5320
+ const startupEnvironmentSelection = resolveCodexSandboxEnvironmentSelection(startupSandboxEnvironment, nativeToolSurfaceEnabled);
5321
+ const startupExecutionCwd = resolveCodexAppServerExecutionCwd({
5322
+ effectiveWorkspace,
5323
+ environment: startupSandboxEnvironment,
5324
+ nativeToolSurfaceEnabled
5325
+ });
5326
+ const startupSandboxPolicy = startupSandboxEnvironment ? resolveCodexExternalSandboxPolicyForOpenClawSandbox(sandbox) : void 0;
5327
+ const buildThreadLifecycleParams = () => ({
5324
5328
  client: startupClient,
5325
- thread: startupThread,
5326
- sandboxEnvironment: startupSandboxEnvironment,
5329
+ params: buildActiveRunAttemptParams(),
5330
+ agentId: sessionAgentId,
5331
+ cwd: startupExecutionCwd,
5332
+ dynamicTools: toolBridge.specs,
5333
+ appServer: pluginAppServer,
5334
+ developerInstructions: promptBuild.developerInstructions,
5335
+ config: threadConfig,
5336
+ finalConfigPatch: nativeHookRelayConfig,
5337
+ nativeCodeModeEnabled: nativeToolSurfaceEnabled,
5338
+ nativeCodeModeOnlyEnabled: appServer.codeModeOnly,
5339
+ userMcpServersEnabled: nativeToolSurfaceEnabled,
5340
+ mcpServersFingerprint: bundleMcpThreadConfig.fingerprint,
5341
+ mcpServersFingerprintEvaluated: bundleMcpThreadConfig.evaluated,
5327
5342
  environmentSelection: startupEnvironmentSelection,
5328
- executionCwd: startupExecutionCwd,
5329
- sandboxPolicy: startupSandboxPolicy
5330
- };
5331
- } catch (error) {
5332
- await releaseStartupSandboxEnvironment();
5333
- throw error;
5343
+ contextEngineProjection,
5344
+ pluginThreadConfig: pluginThreadConfigRequired ? {
5345
+ enabled: true,
5346
+ inputFingerprint: pluginThreadConfigInputFingerprint,
5347
+ enabledPluginConfigKeys,
5348
+ build: () => buildCodexPluginThreadConfig({
5349
+ pluginConfig: pluginThreadConfigPluginConfig,
5350
+ request: (method, requestParams) => startupClient.request(method, requestParams, {
5351
+ timeoutMs: appServer.requestTimeoutMs,
5352
+ signal: runAbortController.signal
5353
+ }),
5354
+ appCache: defaultCodexAppInventoryCache,
5355
+ appCacheKey: pluginAppCacheKey
5356
+ })
5357
+ } : void 0
5358
+ });
5359
+ try {
5360
+ restartContextEngineCodexThread = () => startOrResumeThread(buildThreadLifecycleParams());
5361
+ const startupThread = await startOrResumeThread(buildThreadLifecycleParams());
5362
+ if (runAbortController.signal.aborted) {
5363
+ await releaseStartupSandboxEnvironment();
5364
+ throw new Error("codex app-server startup aborted");
5365
+ }
5366
+ startupSandboxEnvironmentAcquired = false;
5367
+ startupAttemptSucceeded = true;
5368
+ return {
5369
+ client: startupClient,
5370
+ thread: startupThread,
5371
+ sandboxEnvironment: startupSandboxEnvironment,
5372
+ environmentSelection: startupEnvironmentSelection,
5373
+ executionCwd: startupExecutionCwd,
5374
+ sandboxPolicy: startupSandboxPolicy
5375
+ };
5376
+ } catch (error) {
5377
+ await releaseStartupSandboxEnvironment();
5378
+ throw error;
5379
+ } finally {
5380
+ if (releaseStartupResourcesOnTimeout === releaseStartupSandboxEnvironment) releaseStartupResourcesOnTimeout = void 0;
5381
+ }
5334
5382
  } finally {
5335
- if (releaseStartupResourcesOnTimeout === releaseStartupSandboxEnvironment) releaseStartupResourcesOnTimeout = void 0;
5383
+ if (!startupAttemptSucceeded) {
5384
+ if (releaseSharedClientLease === startupClientLease) releaseSharedClientLease = void 0;
5385
+ startupClientLease?.();
5386
+ }
5336
5387
  }
5337
5388
  };
5338
5389
  for (let attempt = 1; attempt <= CODEX_APP_SERVER_STARTUP_CONNECTION_CLOSE_MAX_ATTEMPTS; attempt += 1) try {
@@ -5539,7 +5590,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
5539
5590
  runAbortController.abort("turn_progress_idle_timeout");
5540
5591
  };
5541
5592
  const fireTurnCompletionIdleTimeout = () => {
5542
- if (completed || runAbortController.signal.aborted || !turnCompletionIdleWatchArmed || activeAppServerTurnRequests > 0) return;
5593
+ if (completed || terminalTurnNotificationQueued || runAbortController.signal.aborted || !turnCompletionIdleWatchArmed || activeAppServerTurnRequests > 0) return;
5543
5594
  const timeoutMs = turnCompletionIdleTimeoutOverrideMs ?? turnCompletionIdleTimeoutMs;
5544
5595
  const idleMs = Math.max(0, Date.now() - turnCompletionLastActivityAt);
5545
5596
  if (idleMs < timeoutMs) {
@@ -5569,7 +5620,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
5569
5620
  runAbortController.abort("turn_completion_idle_timeout");
5570
5621
  };
5571
5622
  const fireTurnTerminalIdleTimeout = () => {
5572
- if (completed || runAbortController.signal.aborted || !turnTerminalIdleWatchArmed || activeAppServerTurnRequests > 0) return;
5623
+ if (completed || terminalTurnNotificationQueued || runAbortController.signal.aborted || !turnTerminalIdleWatchArmed || activeAppServerTurnRequests > 0) return;
5573
5624
  const idleMs = Math.max(0, Date.now() - turnCompletionLastActivityAt);
5574
5625
  if (idleMs < turnTerminalIdleTimeoutMs) {
5575
5626
  scheduleTurnTerminalIdleWatch();
@@ -5891,7 +5942,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
5891
5942
  threadId: thread.threadId,
5892
5943
  ...turnId ? { turnId } : {}
5893
5944
  });
5894
- embeddedAgentLog.debug("codex app-server raw notification received", correlation);
5945
+ embeddedAgentLog.trace("codex app-server raw notification received", correlation);
5895
5946
  if (notification.method === "turn/completed" && correlation.matchesActiveTurn === false) if (correlation.matchesActiveThread) embeddedAgentLog.warn("codex app-server turn/completed did not match active turn", correlation);
5896
5947
  else embeddedAgentLog.debug("codex app-server turn/completed ignored for other subscribed thread", correlation);
5897
5948
  if (isCodexNotificationOutsideActiveRun(correlation)) return Promise.resolve();
@@ -5901,6 +5952,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
5901
5952
  return Promise.resolve();
5902
5953
  }
5903
5954
  if (isTerminalTurnNotificationForTurn(notification, turnId)) terminalTurnNotificationQueued = true;
5955
+ if (correlation.matchesActiveTurn !== false) {
5956
+ const now = Date.now();
5957
+ turnCompletionLastActivityAt = now;
5958
+ turnCompletionLastActivityReason = `notification:${notification.method}`;
5959
+ turnAttemptLastProgressAt = now;
5960
+ turnAttemptLastProgressReason = `notification:${notification.method}`;
5961
+ }
5904
5962
  notificationQueue = notificationQueue.then(() => handleNotification(notification), () => handleNotification(notification));
5905
5963
  return notificationQueue;
5906
5964
  };
@@ -6352,6 +6410,8 @@ async function runCodexAppServerAttempt(params, options = {}) {
6352
6410
  }
6353
6411
  });
6354
6412
  params.abortSignal?.removeEventListener("abort", abortFromUpstream);
6413
+ releaseSharedClientLease?.();
6414
+ releaseSharedClientLease = void 0;
6355
6415
  if (usageLimitError) {
6356
6416
  await markCodexAuthProfileBlockedFromRateLimits({
6357
6417
  params,
@@ -6368,7 +6428,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
6368
6428
  throw turnStartError;
6369
6429
  }
6370
6430
  }
6371
- if (!turn) throw new Error("codex app-server turn/start failed without an error");
6431
+ if (!turn) {
6432
+ releaseSharedClientLease?.();
6433
+ releaseSharedClientLease = void 0;
6434
+ throw new Error("codex app-server turn/start failed without an error");
6435
+ }
6372
6436
  turnId = turn.turn.id;
6373
6437
  const activeTurnId = turn.turn.id;
6374
6438
  emitExecutionPhaseOnce("turn_accepted", { phase: "turn_accepted" });
@@ -6440,9 +6504,11 @@ async function runCodexAppServerAttempt(params, options = {}) {
6440
6504
  abort: () => runAbortController.abort("aborted")
6441
6505
  };
6442
6506
  setActiveEmbeddedRun(params.sessionId, handle, params.sessionKey);
6507
+ const notifyUserMessagePersisted = createCodexAppServerUserMessagePersistenceNotifier(params);
6443
6508
  mirrorPromptAtTurnStartBestEffort({
6444
6509
  params,
6445
6510
  agentId: sessionAgentId,
6511
+ notifyUserMessagePersisted,
6446
6512
  sessionKey: sandboxSessionKey,
6447
6513
  threadId: thread.threadId,
6448
6514
  turnId: activeTurnId
@@ -6451,10 +6517,19 @@ async function runCodexAppServerAttempt(params, options = {}) {
6451
6517
  turnTerminalIdleWatchArmed = true;
6452
6518
  touchTurnCompletionActivity("turn:start", { attemptProgress: true });
6453
6519
  const abortListener = () => {
6520
+ if (timedOut) {
6521
+ retireCodexAppServerClientAfterTimedOutTurn(client, {
6522
+ threadId: thread.threadId,
6523
+ turnId: activeTurnId,
6524
+ reason: String(runAbortController.signal.reason ?? "timeout")
6525
+ }).finally(() => {
6526
+ resolveCompletion?.();
6527
+ });
6528
+ return;
6529
+ }
6454
6530
  interruptCodexTurnBestEffort(client, {
6455
6531
  threadId: thread.threadId,
6456
- turnId: activeTurnId,
6457
- timeoutMs: timedOut ? CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS : void 0
6532
+ turnId: activeTurnId
6458
6533
  });
6459
6534
  resolveCompletion?.();
6460
6535
  };
@@ -6513,6 +6588,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
6513
6588
  await mirrorTranscriptBestEffort({
6514
6589
  params,
6515
6590
  agentId: sessionAgentId,
6591
+ notifyUserMessagePersisted,
6516
6592
  result,
6517
6593
  sessionKey: contextSessionKey,
6518
6594
  threadId: thread.threadId,
@@ -6637,6 +6713,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
6637
6713
  notificationCleanup();
6638
6714
  requestCleanup();
6639
6715
  closeCleanup?.();
6716
+ releaseSharedClientLease?.();
6640
6717
  if (nativeHookRelay) if (shouldDelayNativeHookRelayUnregister) scheduleCodexNativeHookRelayUnregister({
6641
6718
  relay: nativeHookRelay,
6642
6719
  hookTimeoutSec: options.nativeHookRelay?.hookTimeoutSec
@@ -6940,6 +7017,41 @@ async function unsubscribeCodexThreadBestEffort(client, params) {
6940
7017
  });
6941
7018
  }
6942
7019
  }
7020
+ async function retireCodexAppServerClientAfterTimedOutTurn(client, params) {
7021
+ const retiredSharedClient = retireSharedCodexAppServerClientIfCurrent(client);
7022
+ const detachedSharedClient = Boolean(retiredSharedClient);
7023
+ interruptCodexTurnBestEffort(client, {
7024
+ threadId: params.threadId,
7025
+ turnId: params.turnId,
7026
+ timeoutMs: CODEX_APP_SERVER_INTERRUPT_TIMEOUT_MS
7027
+ });
7028
+ await unsubscribeCodexThreadBestEffort(client, {
7029
+ threadId: params.threadId,
7030
+ timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
7031
+ });
7032
+ let closedClient = retiredSharedClient?.closed ?? false;
7033
+ if (!detachedSharedClient) {
7034
+ const close = client.close;
7035
+ if (typeof close === "function") try {
7036
+ close.call(client);
7037
+ closedClient = true;
7038
+ } catch (error) {
7039
+ embeddedAgentLog.debug("codex app-server client close failed during timeout cleanup", {
7040
+ threadId: params.threadId,
7041
+ turnId: params.turnId,
7042
+ error
7043
+ });
7044
+ }
7045
+ }
7046
+ embeddedAgentLog.warn("codex app-server client retired after timed-out turn", {
7047
+ threadId: params.threadId,
7048
+ turnId: params.turnId,
7049
+ reason: params.reason,
7050
+ detachedSharedClient,
7051
+ closedClient,
7052
+ activeSharedClientLeases: retiredSharedClient?.activeLeases ?? 0
7053
+ });
7054
+ }
6943
7055
  function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
6944
7056
  return {
6945
7057
  sessionKey: sandboxSessionKey,
@@ -7043,7 +7155,7 @@ async function buildDynamicTools(input) {
7043
7155
  modelCompat: params.model.compat && typeof params.model.compat === "object" ? params.model.compat : void 0,
7044
7156
  modelApi: params.model.api,
7045
7157
  modelContextWindowTokens: params.model.contextWindow,
7046
- modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, void 0, { workspaceDir: input.effectiveWorkspace }),
7158
+ modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, params.toolAuthProfileStore ?? params.authProfileStore, { workspaceDir: input.effectiveWorkspace }),
7047
7159
  suppressManagedWebSearch: false,
7048
7160
  currentChannelId: params.currentChannelId,
7049
7161
  hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
@@ -7901,6 +8013,16 @@ function readPositiveNumber(value) {
7901
8013
  function readNonEmptyString(value) {
7902
8014
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
7903
8015
  }
8016
+ const CODEX_DELIVERY_HINT_LINES = ["Delivery: to send a message, use the `message` tool.", "Delivery: Final assistant text is not automatically delivered in this run. Use the `message` tool to send user-visible output."];
8017
+ function splitLeadingCodexDeliveryHint(prompt) {
8018
+ const trimmedStart = prompt.trimStart();
8019
+ const matchedHint = CODEX_DELIVERY_HINT_LINES.find((hint) => trimmedStart.startsWith(hint));
8020
+ if (!matchedHint) return { prompt };
8021
+ return {
8022
+ deliveryHint: matchedHint,
8023
+ prompt: trimmedStart.slice(matchedHint.length).replace(/^\s*\n/, "").trimStart()
8024
+ };
8025
+ }
7904
8026
  function buildCodexOpenClawPromptContext(params) {
7905
8027
  if (!shouldInjectCodexOpenClawPromptContext(params.params)) return;
7906
8028
  const sections = [params.skillsPrompt?.trim() ? [
@@ -7925,12 +8047,18 @@ function shouldInjectCodexOpenClawPromptContext(params) {
7925
8047
  }
7926
8048
  function prependCodexOpenClawPromptContext(prompt, context) {
7927
8049
  if (!context?.trim()) return prompt;
7928
- const promptSection = prompt.startsWith("OpenClaw assembled context for this turn:") ? prompt : ["Current user request:", prompt].join("\n");
8050
+ const { deliveryHint, prompt: promptWithoutDeliveryHint } = splitLeadingCodexDeliveryHint(prompt);
8051
+ const promptSection = promptWithoutDeliveryHint.startsWith("OpenClaw assembled context for this turn:") ? promptWithoutDeliveryHint : ["Current user request:", promptWithoutDeliveryHint].join("\n");
8052
+ const deliverySection = deliveryHint ? [
8053
+ "OpenClaw delivery metadata:",
8054
+ "This delivery metadata is runtime routing guidance, not the user's request.",
8055
+ deliveryHint
8056
+ ].join("\n") : void 0;
7929
8057
  return [
7930
8058
  context.trim(),
7931
- "",
8059
+ deliverySection,
7932
8060
  promptSection
7933
- ].join("\n");
8061
+ ].filter(Boolean).join("\n\n");
7934
8062
  }
7935
8063
  function renderCodexWorkspaceBootstrapPromptContext(contextFiles) {
7936
8064
  const files = selectCodexWorkspacePromptContextFiles(contextFiles);
@@ -8041,29 +8169,63 @@ function getCodexContextFileBasename(filePath) {
8041
8169
  }
8042
8170
  async function mirrorTranscriptBestEffort(params) {
8043
8171
  try {
8044
- await mirrorCodexAppServerTranscript({
8172
+ const messages = await resolveFinalCodexMirrorMessages({
8173
+ params: params.params,
8174
+ messagesSnapshot: params.result.messagesSnapshot,
8175
+ turnId: params.turnId
8176
+ });
8177
+ const mirrorResult = await mirrorCodexAppServerTranscript({
8045
8178
  sessionFile: params.params.sessionFile,
8046
8179
  agentId: params.agentId,
8047
8180
  sessionKey: params.sessionKey,
8048
- messages: params.result.messagesSnapshot,
8181
+ messages,
8049
8182
  idempotencyScope: `codex-app-server:${params.threadId}`,
8050
8183
  config: params.params.config
8051
8184
  });
8185
+ for (const message of mirrorResult.userMessagesPresent) params.notifyUserMessagePersisted(message);
8052
8186
  } catch (error) {
8053
8187
  embeddedAgentLog.warn("failed to mirror codex app-server transcript", { error });
8054
8188
  }
8055
8189
  }
8190
+ async function resolveFinalCodexMirrorMessages(params) {
8191
+ if (params.params.suppressNextUserMessagePersistence || !params.params.userTurnTranscriptRecorder) return params.messagesSnapshot;
8192
+ const resolvedPrompt = attachCodexMirrorIdentity(await buildResolvedCodexUserPromptMessage(params.params), `${params.turnId}:prompt`);
8193
+ const firstUserIndex = params.messagesSnapshot.findIndex((message) => message.role === "user");
8194
+ if (firstUserIndex === -1) return [resolvedPrompt, ...params.messagesSnapshot];
8195
+ const messages = params.messagesSnapshot.slice();
8196
+ messages[firstUserIndex] = resolvedPrompt;
8197
+ return messages;
8198
+ }
8199
+ function createCodexAppServerUserMessagePersistenceNotifier(runParams) {
8200
+ let notified = false;
8201
+ return (message) => {
8202
+ if (notified) return;
8203
+ notified = true;
8204
+ runParams.userTurnTranscriptRecorder?.markRuntimePersisted(message);
8205
+ try {
8206
+ runParams.onUserMessagePersisted?.(message);
8207
+ } catch (error) {
8208
+ embeddedAgentLog.warn("codex app-server user persistence notification failed", { error: formatErrorMessage(error) });
8209
+ }
8210
+ };
8211
+ }
8056
8212
  async function mirrorPromptAtTurnStartBestEffort(params) {
8057
8213
  if (params.params.suppressNextUserMessagePersistence) return;
8058
8214
  try {
8059
- await mirrorCodexAppServerTranscript({
8060
- sessionFile: params.params.sessionFile,
8061
- agentId: params.agentId,
8062
- sessionKey: params.sessionKey,
8063
- messages: [attachCodexMirrorIdentity(buildCodexUserPromptMessage(params.params), `${params.turnId}:prompt`)],
8064
- idempotencyScope: `codex-app-server:${params.threadId}`,
8065
- config: params.params.config
8066
- });
8215
+ const mirrorPromise = (async () => {
8216
+ const userPromptMessage = attachCodexMirrorIdentity(await buildResolvedCodexUserPromptMessage(params.params), `${params.turnId}:prompt`);
8217
+ const mirrorResult = await mirrorCodexAppServerTranscript({
8218
+ sessionFile: params.params.sessionFile,
8219
+ agentId: params.agentId,
8220
+ sessionKey: params.sessionKey,
8221
+ messages: [userPromptMessage],
8222
+ idempotencyScope: `codex-app-server:${params.threadId}`,
8223
+ config: params.params.config
8224
+ });
8225
+ for (const message of mirrorResult.userMessagesPresent) params.notifyUserMessagePersisted(message);
8226
+ })();
8227
+ params.params.userTurnTranscriptRecorder?.markRuntimePersistencePending(mirrorPromise);
8228
+ await mirrorPromise;
8067
8229
  } catch (error) {
8068
8230
  embeddedAgentLog.warn("failed to mirror codex app-server prompt at turn start", { error });
8069
8231
  }