@openclaw/codex 2026.5.4-beta.1 → 2026.5.4-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,12 +1,13 @@
1
- import { i as resolveCodexAppServerRuntimeOptions, r as readCodexPluginConfig } from "./config-CkkoMeqF.js";
2
- import { i as readCodexDynamicToolCallParams, r as assertCodexTurnStartResponse, s as readCodexTurn } from "./protocol-validators-Cpopom3_.js";
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";
3
3
  import { t as isJsonObject } from "./protocol-C9UWI98H.js";
4
- import { i as isCodexAppServerConnectionClosedError, r as isCodexAppServerApprovalRequest } from "./client-chGfNrq5.js";
5
- import { a as refreshCodexAppServerAuthTokens, o as resolveCodexAppServerAuthProfileId, s as resolveCodexAppServerAuthProfileIdForAgent, t as clearSharedCodexAppServerClientIfCurrent } from "./shared-client-B7LbV3PF.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";
6
7
  import { i as readCodexAppServerBinding } from "./session-binding-DuJYTJQy.js";
7
- import { a as startOrResumeThread, i as buildTurnStartParams, o as createCodexDynamicToolBridge, s as applyCodexDynamicToolProfile, t as buildDeveloperInstructions } from "./thread-lifecycle-C2MSeIjn.js";
8
- import { n as defaultCodexAppServerClientFactory, t as createCodexAppServerClientFactoryTestHooks } from "./client-factory-ClSvDfKC.js";
9
- import { t as ensureCodexComputerUse } from "./computer-use-DtCrnbLb.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";
10
11
  import { createHash } from "node:crypto";
11
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";
12
13
  import fs from "node:fs/promises";
@@ -80,6 +81,7 @@ function truncateForGateway(value, maxLength) {
80
81
  const PERMISSION_DESCRIPTION_MAX_LENGTH = 700;
81
82
  const PERMISSION_SAMPLE_LIMIT = 2;
82
83
  const PERMISSION_VALUE_MAX_LENGTH = 48;
84
+ const COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH = 80;
83
85
  const APPROVAL_PREVIEW_SCAN_MAX_LENGTH = 4096;
84
86
  const APPROVAL_PREVIEW_OMITTED = "[preview truncated or unsafe content omitted]";
85
87
  const ANSI_OSC_SEQUENCE_RE$1 = new RegExp(String.raw`(?:\u001b]|\u009d)[^\u001b\u009c\u0007]*(?:\u0007|\u001b\\|\u009c)`, "g");
@@ -153,7 +155,7 @@ async function handleCodexAppServerApprovalRequest(params) {
153
155
  title: context.title,
154
156
  ...context.eventDetails,
155
157
  ...approvalEventScope(params.method, cancelled ? "cancelled" : "denied"),
156
- message: cancelled ? "Codex app-server approval cancelled because the run stopped." : `Codex app-server approval route failed: ${formatErrorMessage$1(error)}`
158
+ message: cancelled ? "Codex app-server approval cancelled because the run stopped." : `Codex app-server approval route failed: ${formatCodexDisplayText(formatErrorMessage$1(error))}`
157
159
  });
158
160
  return buildApprovalResponse(params.method, context.requestParams, cancelled ? "cancelled" : "denied");
159
161
  }
@@ -181,7 +183,8 @@ function matchesCurrentTurn$1(requestParams, threadId, turnId) {
181
183
  }
182
184
  function buildApprovalContext(params) {
183
185
  const itemId = readString$4(params.requestParams, "itemId") ?? readString$4(params.requestParams, "callId") ?? readString$4(params.requestParams, "approvalId");
184
- const commandPreview = sanitizeApprovalPreview(readDisplayCommandPreview(params.requestParams), 180);
186
+ const commandDetailLines = params.method === "item/commandExecution/requestApproval" ? describeCommandApprovalDetails(params.requestParams) : [];
187
+ const commandPreview = sanitizeApprovalPreview(readDisplayCommandPreview(params.requestParams), commandDetailLines.length > 0 ? COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH : 180);
185
188
  const reasonPreview = sanitizeApprovalPreview(readStringPreview(params.requestParams, "reason"), 180);
186
189
  const command = commandPreview.text;
187
190
  const reason = reasonPreview.text;
@@ -192,7 +195,11 @@ function buildApprovalContext(params) {
192
195
  return {
193
196
  kind,
194
197
  title,
195
- description: permissionLines.length > 0 ? joinDescriptionLinesWithinLimit(permissionLines, PERMISSION_DESCRIPTION_MAX_LENGTH) : [subject, params.paramsForRun.sessionKey && `Session: ${params.paramsForRun.sessionKey}`].filter(Boolean).join("\n"),
198
+ description: permissionLines.length > 0 ? joinDescriptionLinesWithinLimit(permissionLines, PERMISSION_DESCRIPTION_MAX_LENGTH) : [
199
+ subject,
200
+ ...commandDetailLines,
201
+ params.paramsForRun.sessionKey && `Session: ${params.paramsForRun.sessionKey}`
202
+ ].filter(Boolean).join("\n"),
196
203
  severity: kind === "exec" ? "warning" : "info",
197
204
  toolName: kind === "exec" ? "codex_command_approval" : params.method === "item/permissions/requestApproval" ? "codex_permission_approval" : "codex_file_approval",
198
205
  itemId,
@@ -235,46 +242,101 @@ function unsupportedApprovalResponse() {
235
242
  };
236
243
  }
237
244
  function describeRequestedPermissions(requestParams) {
238
- const permissions = requestedPermissions(requestParams);
245
+ return describePermissionProfile(requestedPermissions(requestParams), "Permissions");
246
+ }
247
+ function describeCommandApprovalDetails(requestParams) {
248
+ const lines = [];
249
+ const additionalPermissions = isJsonObject(requestParams?.additionalPermissions) ? requestParams.additionalPermissions : void 0;
250
+ if (additionalPermissions) lines.push(...describePermissionProfile(additionalPermissions, "Additional permissions"));
251
+ const execpolicySummary = summarizeStringArray(requestParams?.proposedExecpolicyAmendment, "Proposed exec policy", sanitizePermissionScalar);
252
+ if (execpolicySummary) lines.push(execpolicySummary);
253
+ const networkAmendmentSummary = summarizeNetworkPolicyAmendments(requestParams?.proposedNetworkPolicyAmendments);
254
+ if (networkAmendmentSummary) lines.push(networkAmendmentSummary);
255
+ return lines;
256
+ }
257
+ function describePermissionProfile(permissions, label) {
239
258
  const lines = [];
240
259
  const kinds = [];
241
260
  const risks = /* @__PURE__ */ new Set();
242
261
  if (isJsonObject(permissions.network)) kinds.push("network");
243
262
  if (isJsonObject(permissions.fileSystem)) kinds.push("fileSystem");
244
- if (kinds.length > 0) lines.push(`Permissions: ${kinds.join(", ")}`);
263
+ if (kinds.length > 0) lines.push(`${label}: ${kinds.join(", ")}`);
245
264
  let networkSummary;
246
- if (isJsonObject(permissions.network)) networkSummary = summarizePermissionRecord(permissions.network, risks, [{
247
- key: "allowHosts",
248
- label: "allowHosts",
249
- sanitize: sanitizePermissionHostValue,
250
- risksFor: permissionHostRisks
251
- }]);
265
+ if (isJsonObject(permissions.network)) {
266
+ const summaries = [summarizeNetworkEnabledPermission(permissions.network, risks), summarizePermissionRecord(permissions.network, risks, [{
267
+ key: "allowHosts",
268
+ label: "allowHosts",
269
+ sanitize: sanitizePermissionHostValue,
270
+ risksFor: permissionHostRisks
271
+ }])].filter((summary) => Boolean(summary));
272
+ networkSummary = summaries.length > 0 ? summaries.join("; ") : void 0;
273
+ }
252
274
  let fileSystemSummary;
253
- if (isJsonObject(permissions.fileSystem)) fileSystemSummary = summarizePermissionRecord(permissions.fileSystem, risks, [
254
- {
255
- key: "roots",
256
- label: "roots",
257
- sanitize: sanitizePermissionPathValue,
258
- risksFor: permissionPathRisks
259
- },
260
- {
261
- key: "readPaths",
262
- label: "readPaths",
263
- sanitize: sanitizePermissionPathValue,
264
- risksFor: permissionPathRisks
265
- },
266
- {
267
- key: "writePaths",
268
- label: "writePaths",
269
- sanitize: sanitizePermissionPathValue,
270
- risksFor: permissionPathRisks
271
- }
272
- ]);
275
+ if (isJsonObject(permissions.fileSystem)) {
276
+ const summaries = [summarizePermissionRecord(permissions.fileSystem, risks, [
277
+ {
278
+ key: "read",
279
+ label: "read",
280
+ sanitize: sanitizePermissionPathValue,
281
+ risksFor: permissionPathRisks
282
+ },
283
+ {
284
+ key: "write",
285
+ label: "write",
286
+ sanitize: sanitizePermissionPathValue,
287
+ risksFor: permissionPathRisks
288
+ },
289
+ {
290
+ key: "roots",
291
+ label: "roots",
292
+ sanitize: sanitizePermissionPathValue,
293
+ risksFor: permissionPathRisks
294
+ },
295
+ {
296
+ key: "readPaths",
297
+ label: "readPaths",
298
+ sanitize: sanitizePermissionPathValue,
299
+ risksFor: permissionPathRisks
300
+ },
301
+ {
302
+ key: "writePaths",
303
+ label: "writePaths",
304
+ sanitize: sanitizePermissionPathValue,
305
+ risksFor: permissionPathRisks
306
+ }
307
+ ]), summarizeFileSystemEntries(permissions.fileSystem, risks)].filter((summary) => Boolean(summary));
308
+ fileSystemSummary = summaries.length > 0 ? summaries.join("; ") : void 0;
309
+ }
273
310
  if (risks.size > 0) lines.push(`High-risk targets: ${[...risks].join(", ")}`);
274
311
  if (networkSummary) lines.push(`Network ${networkSummary}`);
275
312
  if (fileSystemSummary) lines.push(`File system ${fileSystemSummary}`);
276
313
  return lines;
277
314
  }
315
+ function summarizeNetworkEnabledPermission(permission, risks) {
316
+ const enabled = permission.enabled;
317
+ if (typeof enabled !== "boolean") return;
318
+ if (enabled) risks.add("network access");
319
+ return `enabled: ${enabled}`;
320
+ }
321
+ function summarizeFileSystemEntries(permission, risks) {
322
+ const entries = permission.entries;
323
+ if (!Array.isArray(entries)) return;
324
+ const samples = [];
325
+ let count = 0;
326
+ for (const entry of entries) {
327
+ const item = isJsonObject(entry) ? entry : void 0;
328
+ const path = typeof item?.path === "string" ? item.path.trim() : "";
329
+ const access = typeof item?.access === "string" ? item.access.trim() : "";
330
+ if (!path || !access) continue;
331
+ count += 1;
332
+ if (access !== "none") for (const risk of permissionPathRisks(path)) risks.add(risk);
333
+ if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizePermissionScalar(access)} ${sanitizePermissionPathValue(path)}`);
334
+ }
335
+ if (count === 0) return;
336
+ const remaining = count - samples.length;
337
+ const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
338
+ return `entries: ${samples.join(", ")}${remainderSuffix}`;
339
+ }
278
340
  function summarizePermissionRecord(permission, risks, descriptors) {
279
341
  const details = [];
280
342
  for (const descriptor of descriptors) {
@@ -293,6 +355,32 @@ function summarizePermissionArray(record, descriptor, risks) {
293
355
  const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
294
356
  return `${descriptor.label}: ${sampleValues.join(", ")}${remainderSuffix}`;
295
357
  }
358
+ function summarizeStringArray(value, label, sanitize) {
359
+ if (!Array.isArray(value)) return;
360
+ const values = value.filter((entry) => typeof entry === "string").map((entry) => sanitize(entry)).filter(Boolean);
361
+ if (values.length === 0) return;
362
+ const samples = values.slice(0, PERMISSION_SAMPLE_LIMIT);
363
+ const remaining = values.length - samples.length;
364
+ const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
365
+ return `${label}: ${samples.join(", ")}${remainderSuffix}`;
366
+ }
367
+ function summarizeNetworkPolicyAmendments(value) {
368
+ if (!Array.isArray(value)) return;
369
+ const samples = [];
370
+ let count = 0;
371
+ for (const entry of value) {
372
+ const amendment = isJsonObject(entry) ? entry : void 0;
373
+ const host = typeof amendment?.host === "string" ? amendment.host : "";
374
+ const action = typeof amendment?.action === "string" ? amendment.action : "";
375
+ if (!host || !action) continue;
376
+ count += 1;
377
+ if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizePermissionScalar(action)} ${sanitizePermissionHostValue(host)}`);
378
+ }
379
+ if (count === 0) return;
380
+ const remaining = count - samples.length;
381
+ const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
382
+ return `Proposed network policy: ${samples.join(", ")}${remainderSuffix}`;
383
+ }
296
384
  function readStringArray(record, key) {
297
385
  const value = record[key];
298
386
  return Array.isArray(value) ? value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean) : [];
@@ -433,7 +521,7 @@ function sanitizeApprovalPreview(source, maxLength) {
433
521
  const sanitized = sanitizeVisibleScalar(source.value.replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE$1, ""));
434
522
  if (!sanitized) return { omitted: true };
435
523
  return {
436
- text: truncate(sanitized, maxLength),
524
+ text: formatCodexDisplayText(truncate(sanitized, maxLength)),
437
525
  omitted: source.clipped
438
526
  };
439
527
  }
@@ -679,7 +767,8 @@ function sanitizeDisplayText(value) {
679
767
  const scanned = value.slice(0, DISPLAY_TEXT_SCAN_MAX_LENGTH);
680
768
  const clipped = value.length > DISPLAY_TEXT_SCAN_MAX_LENGTH;
681
769
  const sanitized = scanned.replace(ANSI_OSC_SEQUENCE_RE, "").replace(ANSI_CONTROL_SEQUENCE_RE, "").replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE, "").replace(INVISIBLE_FORMATTING_CONTROL_RE, " ").replace(CONTROL_CHARACTER_RE, " ").replace(/\s+/g, " ").trim();
682
- return clipped ? `${sanitized}...` : sanitized;
770
+ const escaped = sanitized ? formatCodexDisplayText(sanitized) : "";
771
+ return clipped && escaped ? `${escaped}...` : escaped;
683
772
  }
684
773
  function truncateDisplayText(value, maxLength) {
685
774
  return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
@@ -872,6 +961,110 @@ async function readCodexMirroredSessionHistoryMessages(sessionFile) {
872
961
  }
873
962
  }
874
963
  //#endregion
964
+ //#region extensions/codex/src/app-server/transcript-mirror.ts
965
+ const MIRROR_IDENTITY_META_KEY = "mirrorIdentity";
966
+ /**
967
+ * Tag a message with a stable logical identity for mirror dedupe. Callers
968
+ * should use a value that is invariant for the same logical message across
969
+ * re-emits (e.g. `${turnId}:prompt`, `${turnId}:assistant`) but distinct
970
+ * for genuinely-distinct messages (different turns, different kinds). When
971
+ * present this identity replaces the role/content fingerprint in the
972
+ * idempotency key, so the dedupe survives caller-scope rotation without
973
+ * collapsing distinct same-content turns.
974
+ */
975
+ function attachCodexMirrorIdentity(message, identity) {
976
+ const record = message;
977
+ const existing = record.__openclaw;
978
+ const baseMeta = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
979
+ return {
980
+ ...record,
981
+ __openclaw: {
982
+ ...baseMeta,
983
+ [MIRROR_IDENTITY_META_KEY]: identity
984
+ }
985
+ };
986
+ }
987
+ function readMirrorIdentity(message) {
988
+ const meta = message.__openclaw;
989
+ if (!meta || typeof meta !== "object" || Array.isArray(meta)) return;
990
+ const id = meta[MIRROR_IDENTITY_META_KEY];
991
+ return typeof id === "string" && id.length > 0 ? id : void 0;
992
+ }
993
+ function fingerprintMirrorMessageContent(message) {
994
+ const payload = JSON.stringify({
995
+ role: message.role,
996
+ content: message.content
997
+ });
998
+ return createHash("sha256").update(payload).digest("hex").slice(0, 16);
999
+ }
1000
+ function buildMirrorDedupeIdentity(message) {
1001
+ const explicit = readMirrorIdentity(message);
1002
+ if (explicit) return explicit;
1003
+ return `${message.role}:${fingerprintMirrorMessageContent(message)}`;
1004
+ }
1005
+ async function mirrorCodexAppServerTranscript(params) {
1006
+ const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant");
1007
+ if (messages.length === 0) return;
1008
+ const lock = await acquireSessionWriteLock({
1009
+ sessionFile: params.sessionFile,
1010
+ timeoutMs: resolveSessionWriteLockAcquireTimeoutMs(params.config)
1011
+ });
1012
+ try {
1013
+ const existingIdempotencyKeys = await readTranscriptIdempotencyKeys(params.sessionFile);
1014
+ for (const message of messages) {
1015
+ const dedupeIdentity = buildMirrorDedupeIdentity(message);
1016
+ const idempotencyKey = params.idempotencyScope ? `${params.idempotencyScope}:${dedupeIdentity}` : void 0;
1017
+ if (idempotencyKey && existingIdempotencyKeys.has(idempotencyKey)) continue;
1018
+ const nextMessage = runAgentHarnessBeforeMessageWriteHook({
1019
+ message: {
1020
+ ...message,
1021
+ ...idempotencyKey ? { idempotencyKey } : {}
1022
+ },
1023
+ agentId: params.agentId,
1024
+ sessionKey: params.sessionKey
1025
+ });
1026
+ if (!nextMessage) continue;
1027
+ const messageToAppend = idempotencyKey ? {
1028
+ ...nextMessage,
1029
+ idempotencyKey
1030
+ } : nextMessage;
1031
+ await appendSessionTranscriptMessage({
1032
+ transcriptPath: params.sessionFile,
1033
+ message: messageToAppend,
1034
+ config: params.config
1035
+ });
1036
+ if (idempotencyKey) existingIdempotencyKeys.add(idempotencyKey);
1037
+ }
1038
+ } finally {
1039
+ await lock.release();
1040
+ }
1041
+ if (params.sessionKey) emitSessionTranscriptUpdate({
1042
+ sessionFile: params.sessionFile,
1043
+ sessionKey: params.sessionKey
1044
+ });
1045
+ else emitSessionTranscriptUpdate(params.sessionFile);
1046
+ }
1047
+ async function readTranscriptIdempotencyKeys(sessionFile) {
1048
+ const keys = /* @__PURE__ */ new Set();
1049
+ let raw;
1050
+ try {
1051
+ raw = await fs.readFile(sessionFile, "utf8");
1052
+ } catch (error) {
1053
+ if (error.code !== "ENOENT") throw error;
1054
+ return keys;
1055
+ }
1056
+ for (const line of raw.split(/\r?\n/)) {
1057
+ if (!line.trim()) continue;
1058
+ try {
1059
+ const parsed = JSON.parse(line);
1060
+ if (typeof parsed.message?.idempotencyKey === "string") keys.add(parsed.message.idempotencyKey);
1061
+ } catch {
1062
+ continue;
1063
+ }
1064
+ }
1065
+ return keys;
1066
+ }
1067
+ //#endregion
875
1068
  //#region extensions/codex/src/app-server/event-projector.ts
876
1069
  const ZERO_USAGE = {
877
1070
  input: 0,
@@ -930,6 +1123,11 @@ var CodexAppServerEventProjector = class {
930
1123
  async handleNotification(notification) {
931
1124
  const params = isJsonObject(notification.params) ? notification.params : void 0;
932
1125
  if (!params) return;
1126
+ if (notification.method === "account/rateLimits/updated") {
1127
+ this.latestRateLimits = params;
1128
+ rememberCodexRateLimits(params);
1129
+ return;
1130
+ }
933
1131
  if (isHookNotificationMethod(notification.method)) {
934
1132
  if (!this.isHookNotificationForCurrentThread(params)) return;
935
1133
  } else if (!this.isNotificationForTurn(params)) return;
@@ -978,7 +1176,7 @@ var CodexAppServerEventProjector = class {
978
1176
  break;
979
1177
  case "error":
980
1178
  if (readBooleanAlias(params, ["willRetry", "will_retry"]) === true) break;
981
- this.promptError = readCodexErrorNotificationMessage(params) ?? "codex app-server error";
1179
+ this.promptError = this.formatCodexErrorMessage(params) ?? "codex app-server error";
982
1180
  this.promptErrorSource = "prompt";
983
1181
  break;
984
1182
  default: break;
@@ -989,14 +1187,15 @@ var CodexAppServerEventProjector = class {
989
1187
  const reasoningText = collectTextValues(this.reasoningTextByItem).join("\n\n");
990
1188
  const planText = collectTextValues(this.planTextByItem).join("\n\n");
991
1189
  const lastAssistant = assistantTexts.length > 0 ? this.createAssistantMessage(assistantTexts.join("\n\n")) : void 0;
992
- const messagesSnapshot = [{
1190
+ const turnId = this.turnId;
1191
+ const messagesSnapshot = [attachCodexMirrorIdentity({
993
1192
  role: "user",
994
1193
  content: this.params.prompt,
995
1194
  timestamp: Date.now()
996
- }];
997
- if (reasoningText) messagesSnapshot.push(this.createAssistantMirrorMessage("Codex reasoning", reasoningText));
998
- if (planText) messagesSnapshot.push(this.createAssistantMirrorMessage("Codex plan", planText));
999
- if (lastAssistant) messagesSnapshot.push(lastAssistant);
1195
+ }, `${turnId}:prompt`)];
1196
+ if (reasoningText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex reasoning", reasoningText), `${turnId}:reasoning`));
1197
+ if (planText) messagesSnapshot.push(attachCodexMirrorIdentity(this.createAssistantMirrorMessage("Codex plan", planText), `${turnId}:plan`));
1198
+ if (lastAssistant) messagesSnapshot.push(attachCodexMirrorIdentity(lastAssistant, `${turnId}:assistant`));
1000
1199
  const turnFailed = this.completedTurn?.status === "failed";
1001
1200
  const turnInterrupted = this.completedTurn?.status === "interrupted";
1002
1201
  const promptError = this.promptError ?? (turnFailed ? this.completedTurn?.error?.message ?? "codex app-server turn failed" : null);
@@ -1267,7 +1466,11 @@ var CodexAppServerEventProjector = class {
1267
1466
  this.completedTurn = turn;
1268
1467
  if (turn.status === "interrupted") this.aborted = true;
1269
1468
  if (turn.status === "failed") {
1270
- this.promptError = turn.error?.message ?? "codex app-server turn failed";
1469
+ this.promptError = formatCodexUsageLimitErrorMessage({
1470
+ message: turn.error?.message,
1471
+ codexErrorInfo: turn.error?.codexErrorInfo,
1472
+ rateLimits: this.latestRateLimits ?? readRecentCodexRateLimits()
1473
+ }) ?? turn.error?.message ?? "codex app-server turn failed";
1271
1474
  this.promptErrorSource = "prompt";
1272
1475
  }
1273
1476
  for (const item of turn.items ?? []) {
@@ -1420,6 +1623,14 @@ var CodexAppServerEventProjector = class {
1420
1623
  ...meta ? { meta } : {}
1421
1624
  });
1422
1625
  }
1626
+ formatCodexErrorMessage(params) {
1627
+ const error = isJsonObject(params.error) ? params.error : void 0;
1628
+ return formatCodexUsageLimitErrorMessage({
1629
+ message: error ? readString$2(error, "message") : void 0,
1630
+ codexErrorInfo: error?.codexErrorInfo,
1631
+ rateLimits: this.latestRateLimits ?? readRecentCodexRateLimits()
1632
+ }) ?? readCodexErrorNotificationMessage(params);
1633
+ }
1423
1634
  emitAgentEvent(event) {
1424
1635
  try {
1425
1636
  emitAgentEvent({
@@ -2019,69 +2230,6 @@ function normalizeCodexTrajectoryError(value) {
2019
2230
  }
2020
2231
  }
2021
2232
  //#endregion
2022
- //#region extensions/codex/src/app-server/transcript-mirror.ts
2023
- async function mirrorCodexAppServerTranscript(params) {
2024
- const messages = params.messages.filter((message) => message.role === "user" || message.role === "assistant");
2025
- if (messages.length === 0) return;
2026
- const lock = await acquireSessionWriteLock({
2027
- sessionFile: params.sessionFile,
2028
- timeoutMs: resolveSessionWriteLockAcquireTimeoutMs(params.config)
2029
- });
2030
- try {
2031
- const existingIdempotencyKeys = await readTranscriptIdempotencyKeys(params.sessionFile);
2032
- for (const [index, message] of messages.entries()) {
2033
- const idempotencyKey = params.idempotencyScope ? `${params.idempotencyScope}:${message.role}:${index}` : void 0;
2034
- if (idempotencyKey && existingIdempotencyKeys.has(idempotencyKey)) continue;
2035
- const nextMessage = runAgentHarnessBeforeMessageWriteHook({
2036
- message: {
2037
- ...message,
2038
- ...idempotencyKey ? { idempotencyKey } : {}
2039
- },
2040
- agentId: params.agentId,
2041
- sessionKey: params.sessionKey
2042
- });
2043
- if (!nextMessage) continue;
2044
- const messageToAppend = idempotencyKey ? {
2045
- ...nextMessage,
2046
- idempotencyKey
2047
- } : nextMessage;
2048
- await appendSessionTranscriptMessage({
2049
- transcriptPath: params.sessionFile,
2050
- message: messageToAppend,
2051
- config: params.config
2052
- });
2053
- if (idempotencyKey) existingIdempotencyKeys.add(idempotencyKey);
2054
- }
2055
- } finally {
2056
- await lock.release();
2057
- }
2058
- if (params.sessionKey) emitSessionTranscriptUpdate({
2059
- sessionFile: params.sessionFile,
2060
- sessionKey: params.sessionKey
2061
- });
2062
- else emitSessionTranscriptUpdate(params.sessionFile);
2063
- }
2064
- async function readTranscriptIdempotencyKeys(sessionFile) {
2065
- const keys = /* @__PURE__ */ new Set();
2066
- let raw;
2067
- try {
2068
- raw = await fs.readFile(sessionFile, "utf8");
2069
- } catch (error) {
2070
- if (error.code !== "ENOENT") throw error;
2071
- return keys;
2072
- }
2073
- for (const line of raw.split(/\r?\n/)) {
2074
- if (!line.trim()) continue;
2075
- try {
2076
- const parsed = JSON.parse(line);
2077
- if (typeof parsed.message?.idempotencyKey === "string") keys.add(parsed.message.idempotencyKey);
2078
- } catch {
2079
- continue;
2080
- }
2081
- }
2082
- return keys;
2083
- }
2084
- //#endregion
2085
2233
  //#region extensions/codex/src/app-server/user-input-bridge.ts
2086
2234
  function createCodexUserInputBridge(params) {
2087
2235
  let pending;
@@ -2097,6 +2245,7 @@ function createCodexUserInputBridge(params) {
2097
2245
  const requestParams = readUserInputParams(request.params);
2098
2246
  if (!requestParams) return;
2099
2247
  if (requestParams.threadId !== params.threadId || requestParams.turnId !== params.turnId) return;
2248
+ if (requestParams.questions.length === 0) return emptyUserInputResponse();
2100
2249
  resolvePending(emptyUserInputResponse());
2101
2250
  return new Promise((resolve) => {
2102
2251
  const abortListener = () => resolvePending(emptyUserInputResponse());
@@ -2191,11 +2340,11 @@ async function deliverUserInputPrompt(params, questions) {
2191
2340
  function formatUserInputPrompt(questions) {
2192
2341
  const lines = ["Codex needs input:"];
2193
2342
  questions.forEach((question, index) => {
2194
- if (questions.length > 1) lines.push("", `${index + 1}. ${question.header}`, question.question);
2195
- else lines.push("", question.header, question.question);
2343
+ if (questions.length > 1) lines.push("", `${index + 1}. ${formatCodexDisplayText(question.header)}`, formatCodexDisplayText(question.question));
2344
+ else lines.push("", formatCodexDisplayText(question.header), formatCodexDisplayText(question.question));
2196
2345
  if (question.isSecret) lines.push("This channel may show your reply to other participants.");
2197
2346
  question.options?.forEach((option, optionIndex) => {
2198
- lines.push(`${optionIndex + 1}. ${option.label}${option.description ? ` - ${option.description}` : ""}`);
2347
+ lines.push(`${optionIndex + 1}. ${formatCodexDisplayText(option.label)}${option.description ? ` - ${formatCodexDisplayText(option.description)}` : ""}`);
2199
2348
  });
2200
2349
  if (question.isOther) lines.push("Other: reply with your own answer.");
2201
2350
  });
@@ -2205,14 +2354,18 @@ function buildUserInputResponse(questions, inputText) {
2205
2354
  const answers = {};
2206
2355
  if (questions.length === 1) {
2207
2356
  const question = questions[0];
2208
- if (question) answers[question.id] = { answers: [normalizeAnswer(inputText, question)] };
2357
+ if (question) {
2358
+ const answer = normalizeAnswer(inputText, question);
2359
+ answers[question.id] = { answers: answer ? [answer] : [] };
2360
+ }
2209
2361
  return { answers };
2210
2362
  }
2211
2363
  const keyed = parseKeyedAnswers(inputText);
2212
2364
  const fallbackLines = inputText.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2213
2365
  questions.forEach((question, index) => {
2214
2366
  const answer = keyed.get(question.id.toLowerCase()) ?? keyed.get(question.header.toLowerCase()) ?? keyed.get(question.question.toLowerCase()) ?? keyed.get(String(index + 1)) ?? fallbackLines[index] ?? "";
2215
- answers[question.id] = { answers: answer ? [normalizeAnswer(answer, question)] : [] };
2367
+ const normalized = answer ? normalizeAnswer(answer, question) : void 0;
2368
+ answers[question.id] = { answers: normalized ? [normalized] : [] };
2216
2369
  });
2217
2370
  return { answers };
2218
2371
  }
@@ -2222,7 +2375,10 @@ function normalizeAnswer(answer, question) {
2222
2375
  const optionIndex = /^\d+$/.test(trimmed) ? Number(trimmed) - 1 : -1;
2223
2376
  const indexed = optionIndex >= 0 ? options[optionIndex] : void 0;
2224
2377
  if (indexed) return indexed.label;
2225
- return options.find((option) => option.label.toLowerCase() === trimmed.toLowerCase())?.label ?? trimmed;
2378
+ const exact = options.find((option) => option.label.toLowerCase() === trimmed.toLowerCase());
2379
+ if (exact) return exact.label;
2380
+ if (options.length > 0 && !question.isOther) return;
2381
+ return trimmed || void 0;
2226
2382
  }
2227
2383
  function parseKeyedAnswers(inputText) {
2228
2384
  const answers = /* @__PURE__ */ new Map();
@@ -2468,6 +2624,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2468
2624
  signal: runAbortController.signal,
2469
2625
  hookContext: {
2470
2626
  agentId: sessionAgentId,
2627
+ config: params.config,
2471
2628
  sessionId: params.sessionId,
2472
2629
  sessionKey: sandboxSessionKey,
2473
2630
  runId: params.runId
@@ -2533,6 +2690,19 @@ async function runCodexAppServerAttempt(params, options = {}) {
2533
2690
  } catch (assembleErr) {
2534
2691
  embeddedAgentLog.warn("context engine assemble failed; using Codex baseline prompt", { error: formatErrorMessage(assembleErr) });
2535
2692
  }
2693
+ else if (shouldProjectMirroredHistoryForCodexStart({
2694
+ startupBinding,
2695
+ dynamicToolsFingerprint: codexDynamicToolsFingerprint(toolBridge.specs),
2696
+ historyMessages
2697
+ })) {
2698
+ const projection = projectContextEngineAssemblyForCodex({
2699
+ assembledMessages: historyMessages,
2700
+ originalHistoryMessages: historyMessages,
2701
+ prompt: params.prompt
2702
+ });
2703
+ promptText = projection.promptText;
2704
+ prePromptMessageCount = projection.prePromptMessageCount;
2705
+ }
2536
2706
  const promptBuild = await resolveAgentHarnessBeforePromptBuildResult({
2537
2707
  prompt: promptText,
2538
2708
  developerInstructions,
@@ -2568,6 +2738,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2568
2738
  agentId: sessionAgentId,
2569
2739
  sessionId: params.sessionId,
2570
2740
  sessionKey: sandboxSessionKey,
2741
+ config: params.config,
2571
2742
  runId: params.runId,
2572
2743
  signal: runAbortController.signal
2573
2744
  });
@@ -2955,11 +3126,13 @@ async function runCodexAppServerAttempt(params, options = {}) {
2955
3126
  signal: runAbortController.signal
2956
3127
  }));
2957
3128
  } catch (error) {
3129
+ const usageLimitError = formatCodexTurnStartUsageLimitError(error, pendingNotifications);
3130
+ const turnStartErrorMessage = usageLimitError ?? formatErrorMessage(error);
2958
3131
  emitCodexAppServerEvent(params, {
2959
3132
  stream: "codex_app_server.lifecycle",
2960
3133
  data: {
2961
3134
  phase: "turn_start_failed",
2962
- error: formatErrorMessage(error)
3135
+ error: turnStartErrorMessage
2963
3136
  }
2964
3137
  });
2965
3138
  trajectoryRecorder?.recordEvent("session.ended", {
@@ -2967,7 +3140,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2967
3140
  threadId: thread.threadId,
2968
3141
  timedOut,
2969
3142
  aborted: runAbortController.signal.aborted,
2970
- promptError: normalizeCodexTrajectoryError(error)
3143
+ promptError: turnStartErrorMessage
2971
3144
  });
2972
3145
  trajectoryEndRecorded = true;
2973
3146
  runAgentHarnessLlmOutputHook({
@@ -2986,7 +3159,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
2986
3159
  event: {
2987
3160
  messages: turnStartFailureMessages,
2988
3161
  success: false,
2989
- error: formatErrorMessage(error),
3162
+ error: turnStartErrorMessage,
2990
3163
  durationMs: Date.now() - attemptStartedAt
2991
3164
  },
2992
3165
  ctx: hookContext
@@ -3004,6 +3177,7 @@ async function runCodexAppServerAttempt(params, options = {}) {
3004
3177
  }
3005
3178
  });
3006
3179
  params.abortSignal?.removeEventListener("abort", abortFromUpstream);
3180
+ if (usageLimitError) throw new Error(usageLimitError, { cause: error });
3007
3181
  throw error;
3008
3182
  }
3009
3183
  turnId = turn.turn.id;
@@ -3270,6 +3444,7 @@ function createCodexNativeHookRelay(params) {
3270
3444
  ...params.agentId ? { agentId: params.agentId } : {},
3271
3445
  sessionId: params.sessionId,
3272
3446
  ...params.sessionKey ? { sessionKey: params.sessionKey } : {},
3447
+ ...params.config ? { config: params.config } : {},
3273
3448
  runId: params.runId,
3274
3449
  allowedEvents: params.options?.events ?? CODEX_NATIVE_HOOK_RELAY_EVENTS,
3275
3450
  ttlMs: params.options?.ttlMs,
@@ -3377,6 +3552,14 @@ async function buildDynamicTools(input) {
3377
3552
  model: params.model
3378
3553
  });
3379
3554
  }
3555
+ function shouldProjectMirroredHistoryForCodexStart(params) {
3556
+ if (!params.historyMessages.some((message) => message.role === "user")) return false;
3557
+ if (!params.startupBinding?.threadId) return true;
3558
+ return !areCodexDynamicToolFingerprintsCompatible({
3559
+ previous: params.startupBinding.dynamicToolsFingerprint,
3560
+ next: params.dynamicToolsFingerprint
3561
+ });
3562
+ }
3380
3563
  async function withCodexStartupTimeout(params) {
3381
3564
  if (params.signal.aborted) throw new Error("codex app-server startup aborted");
3382
3565
  let timeout;
@@ -3416,6 +3599,48 @@ function resolveCodexTurnTerminalIdleTimeoutMs(value) {
3416
3599
  function readDynamicToolCallParams(value) {
3417
3600
  return readCodexDynamicToolCallParams(value);
3418
3601
  }
3602
+ function formatCodexTurnStartUsageLimitError(error, pendingNotifications) {
3603
+ const notificationError = readLatestCodexErrorNotification(pendingNotifications);
3604
+ const errorPayload = readCodexErrorPayload(error);
3605
+ return formatCodexUsageLimitErrorMessage({
3606
+ message: notificationError?.message ?? errorPayload.message ?? formatErrorMessage(error),
3607
+ codexErrorInfo: notificationError?.codexErrorInfo ?? errorPayload.codexErrorInfo,
3608
+ rateLimits: readLatestRateLimitNotificationPayload(pendingNotifications) ?? errorPayload.rateLimits ?? readRecentCodexRateLimits()
3609
+ });
3610
+ }
3611
+ function readLatestRateLimitNotificationPayload(notifications) {
3612
+ for (let index = notifications.length - 1; index >= 0; index -= 1) {
3613
+ const notification = notifications[index];
3614
+ if (notification?.method === "account/rateLimits/updated") {
3615
+ rememberCodexRateLimits(notification.params);
3616
+ return notification.params;
3617
+ }
3618
+ }
3619
+ }
3620
+ function readLatestCodexErrorNotification(notifications) {
3621
+ for (let index = notifications.length - 1; index >= 0; index -= 1) {
3622
+ const notification = notifications[index];
3623
+ if (notification?.method !== "error" || !isJsonObject(notification.params)) continue;
3624
+ const error = notification.params.error;
3625
+ if (!isJsonObject(error)) continue;
3626
+ return {
3627
+ message: readString(error, "message"),
3628
+ codexErrorInfo: error.codexErrorInfo
3629
+ };
3630
+ }
3631
+ }
3632
+ function readCodexErrorPayload(error) {
3633
+ const message = error instanceof Error ? error.message : void 0;
3634
+ if (!error || typeof error !== "object" || !("data" in error)) return { message };
3635
+ const data = error.data;
3636
+ if (!isJsonObject(data)) return { message };
3637
+ const nestedError = isJsonObject(data.error) ? data.error : data;
3638
+ return {
3639
+ message: readString(nestedError, "message") ?? message,
3640
+ codexErrorInfo: nestedError.codexErrorInfo,
3641
+ rateLimits: nestedError.rateLimits ?? data.rateLimits
3642
+ };
3643
+ }
3419
3644
  function isTurnNotification(value, threadId, turnId) {
3420
3645
  if (!isJsonObject(value)) return false;
3421
3646
  return readString(value, "threadId") === threadId && readNotificationTurnId(value) === turnId;
@@ -3518,7 +3743,7 @@ async function mirrorTranscriptBestEffort(params) {
3518
3743
  agentId: params.agentId,
3519
3744
  sessionKey: params.sessionKey,
3520
3745
  messages: params.result.messagesSnapshot,
3521
- idempotencyScope: `codex-app-server:${params.threadId}:${params.turnId}`,
3746
+ idempotencyScope: `codex-app-server:${params.threadId}`,
3522
3747
  config: params.params.config
3523
3748
  });
3524
3749
  } catch (error) {