@openclaw/codex 2026.7.2-beta.7 → 2026.8.1-beta.3

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.
Files changed (68) hide show
  1. package/README.md +2 -0
  2. package/dist/api.js +147 -0
  3. package/dist/app-server-policy-bg5QPSVP.js +25 -0
  4. package/dist/attempt-client-cleanup-URjUlCHg.js +735 -0
  5. package/dist/binding-connection-DAvZwp96.js +45 -0
  6. package/dist/{bounded-turn-D2ZAgZVz.js → bounded-turn-4BjrZEwW.js} +63 -56
  7. package/dist/{capabilities-BFDTVYr4.js → capabilities-CH3K57v4.js} +8 -2
  8. package/dist/cli-metadata.js +1 -1
  9. package/dist/{command-formatters-C336eRVS.js → command-formatters-D0nZ3qtd.js} +18 -18
  10. package/dist/{command-handlers-DAHUSry9.js → command-handlers-DC86lgT4.js} +507 -101
  11. package/dist/command-rpc-xUIIonsO.js +90 -0
  12. package/dist/{compact-D_uvHurS.js → compact-COqgBRny.js} +234 -214
  13. package/dist/{session-binding-BXpo9ymA.js → config-CpNeo5AE.js} +284 -1163
  14. package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
  15. package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
  16. package/dist/doctor-contract-api.js +48 -50
  17. package/dist/{dynamic-tools-D2gYkSBO.js → dynamic-tools-fJZhl4CE.js} +3442 -1433
  18. package/dist/{effective-mcp-catalog-Bbd98Inw.js → effective-mcp-catalog-4YKn51aG.js} +20 -15
  19. package/dist/elicitation-input-DMkeki8d.js +71 -0
  20. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  21. package/dist/{attempt-notifications-AP-a-Q5k.js → event-projector-usage-CLwNEJqW.js} +113 -29
  22. package/dist/harness-DoKQcF8n.js +258 -0
  23. package/dist/harness.js +2 -160
  24. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  25. package/dist/index.js +730 -256
  26. package/dist/isolated-completion-DPMtlH9r.js +75 -0
  27. package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
  28. package/dist/managed-binary-By4FZC6i.js +212 -0
  29. package/dist/media-understanding-provider.js +5 -5
  30. package/dist/model-catalog-CmGoqVKB.js +48 -0
  31. package/dist/{models-BBMKU4jA.js → models-CZw1wClT.js} +12 -17
  32. package/dist/native-subagent-monitor-GGq3RnOo.js +3225 -0
  33. package/dist/node-exec-server.runtime-Bsu1LdFZ.js +192 -0
  34. package/dist/{plugin-app-cache-key-DztPafOA.js → plugin-app-cache-key-DMNQ8Zi8.js} +18 -6
  35. package/dist/plugin-approval-roundtrip-C75Yiiwc.js +95 -0
  36. package/dist/plugin-marketplace-discovery-CSWDkMrP.js +399 -0
  37. package/dist/{computer-use-CYEc8U3w.js → plugin-metadata-cache-BXH5NF-4.js} +109 -6
  38. package/dist/protocol-validators-VAVyEnDp.js +2399 -0
  39. package/dist/{rate-limits-CU-gVCCS.js → rate-limits-BI1dMHDG.js} +17 -35
  40. package/dist/{request-qKh331yu.js → request-DSK0W6YI.js} +3 -3
  41. package/dist/{run-attempt-CY9WlDLc.js → run-attempt-BV3HzuCf.js} +3416 -7194
  42. package/dist/{runtime-artifact-CmtG_t1L.js → runtime-artifact-BvyT1uyd.js} +11 -29
  43. package/dist/{sandbox-guard-NuhpXmPB.js → sandbox-guard-C1qEeZ5C.js} +29 -30
  44. package/dist/session-binding-BTrLf9Qm.js +874 -0
  45. package/dist/{session-catalog-DyOgR4WH.js → session-catalog-ezP_UcRM.js} +1584 -1289
  46. package/dist/session-catalog-home-id-BNwAJ9D7.js +18 -0
  47. package/dist/{session-cli-2PZaFLRh.js → session-cli-B1hKvfkI.js} +20 -7
  48. package/dist/session-retirement-CE4iMiLh.js +36 -0
  49. package/dist/{session-upstream-marker-B6U7tLw5.js → session-upstream-marker-D_2npHlA.js} +1 -1
  50. package/dist/{settled-turn-finalizer-CXY2Z9dT.js → settled-turn-finalizer-Dx1onIqg.js} +40 -28
  51. package/dist/{shared-client-DTbvH-XV.js → shared-client-Dw-rzii0.js} +1315 -469
  52. package/dist/{side-question-_ulBk1AL.js → side-question-DbXkJZzg.js} +140 -69
  53. package/dist/{thread-lifecycle-DF53ogiA.js → thread-lifecycle-DKdrnTMX.js} +927 -768
  54. package/dist/thread-ownership-i2NBwDUL.js +92 -0
  55. package/dist/thread-resume-CdJk2Ehf.js +30 -0
  56. package/dist/transcript-mirror-BU0_LKAL.js +588 -0
  57. package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DhoGy33Z.js} +13 -2
  58. package/dist/upstream-prompt-provenance-CFmsKwr9.js +39 -0
  59. package/dist/{upstream-session-fork-Dy4Xvuoj.js → upstream-session-fork-BDVJCLAD.js} +14 -8
  60. package/dist/{usage-CQ6GhD5-.js → usage-9dKJCgcy.js} +3 -3
  61. package/dist/{web-search-provider.runtime-CCFhg2z5.js → web-search-provider.runtime-DCdx4DGu.js} +8 -9
  62. package/openclaw.plugin.json +49 -4
  63. package/package.json +6 -5
  64. package/dist/app-server-policy-BSaO2JIT.js +0 -49
  65. package/dist/conversation-control-xsIeXbo4.js +0 -287
  66. package/dist/event-projector-usage-Xp4AlR2k.js +0 -86
  67. package/dist/notification-correlation-CU6_AnOb.js +0 -67
  68. package/dist/transcript-mirror-DzsdNCf5.js +0 -6924
@@ -0,0 +1,18 @@
1
+ import path from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import fsSync from "node:fs";
4
+ //#region extensions/codex/src/session-catalog-home-id.ts
5
+ function canonicalCodexCatalogHome(value) {
6
+ const resolved = path.resolve(value);
7
+ try {
8
+ return fsSync.realpathSync.native(resolved);
9
+ } catch {
10
+ return resolved;
11
+ }
12
+ }
13
+ /** One canonical identity for catalog discovery and durable ownership rows. */
14
+ function codexCatalogHomeId(codexHome) {
15
+ return createHash("sha256").update("openclaw:codex-session-catalog-home:v1\0").update(canonicalCodexCatalogHome(codexHome)).digest("hex");
16
+ }
17
+ //#endregion
18
+ export { codexCatalogHomeId as n, canonicalCodexCatalogHome as t };
@@ -1,10 +1,15 @@
1
- import { a as CODEX_LOCAL_SESSION_HOST_ID } from "./session-catalog-DyOgR4WH.js";
1
+ import "./session-catalog-ezP_UcRM.js";
2
2
  import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
4
  import { sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
5
+ import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
5
6
  import { addGatewayClientOptions, callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
6
7
  //#region extensions/codex/src/session-cli.ts
7
8
  const CODEX_SESSION_CATALOG_CLI_TIMEOUT_MS = 75e3;
9
+ function requestedAgentId(options) {
10
+ const agentId = options.agent?.trim();
11
+ return agentId ? normalizeAgentId(agentId) : void 0;
12
+ }
8
13
  function writeLine(value = "") {
9
14
  process.stdout.write(`${value}\n`);
10
15
  }
@@ -80,12 +85,14 @@ function filterHosts(result, selector) {
80
85
  } : result;
81
86
  }
82
87
  async function listCodexSessions(options) {
88
+ const agentId = requestedAgentId(options);
83
89
  const host = options.host?.trim() || void 0;
84
90
  const cursor = options.cursor?.trim() || void 0;
85
91
  if (cursor && !host) throw new Error("--cursor requires --host so the cursor is routed to one Codex host");
86
92
  const search = options.search?.trim() || void 0;
87
93
  const limitPerHost = parsePageLimit(options.limit);
88
94
  const params = {
95
+ ...agentId ? { agentId } : {},
89
96
  ...search ? { search } : {},
90
97
  ...limitPerHost !== void 0 ? { limitPerHost } : {},
91
98
  ...host ? { hostIds: [host] } : {},
@@ -122,10 +129,13 @@ function readThreadId(value) {
122
129
  }
123
130
  async function continueCodexSession(threadIdValue, options) {
124
131
  const threadId = readThreadId(threadIdValue);
132
+ const agentId = requestedAgentId(options);
133
+ const hostId = options.host?.trim() || "gateway:local";
125
134
  const raw = await callGatewayFromCli("sessions.catalog.continue", gatewayOptions(options), {
126
135
  catalogId: "codex",
127
- hostId: CODEX_LOCAL_SESSION_HOST_ID,
128
- threadId
136
+ hostId,
137
+ threadId,
138
+ ...agentId ? { agentId } : {}
129
139
  }, {
130
140
  mode: "cli",
131
141
  scopes: ["operator.write"]
@@ -140,11 +150,14 @@ async function continueCodexSession(threadIdValue, options) {
140
150
  }
141
151
  async function archiveCodexSession(threadIdValue, options) {
142
152
  const threadId = readThreadId(threadIdValue);
153
+ const agentId = requestedAgentId(options);
154
+ const hostId = options.host?.trim() || "gateway:local";
143
155
  if (options.confirmNoOtherRunner !== true) throw new Error("--confirm-no-other-runner is required because Codex client and runner activity is process-local");
144
156
  const raw = await callGatewayFromCli("sessions.catalog.archive", gatewayOptions(options), {
145
157
  catalogId: "codex",
146
- hostId: CODEX_LOCAL_SESSION_HOST_ID,
158
+ hostId,
147
159
  threadId,
160
+ ...agentId ? { agentId } : {},
148
161
  confirmNoOtherRunner: true
149
162
  }, {
150
163
  mode: "cli",
@@ -161,13 +174,13 @@ async function archiveCodexSession(threadIdValue, options) {
161
174
  /** Registers the plugin-owned Codex session supervision CLI. */
162
175
  function registerCodexSessionCli(program) {
163
176
  const codex = program.command("codex").description("Inspect and branch from Codex sessions through the Gateway");
164
- addGatewayClientOptions(codex.command("sessions").description("List non-archived Codex app-server sessions across connected hosts").option("--search <text>", "Search session titles (case-insensitive)").option("--host <id>", "Filter by stable host id").option("--limit <count>", "Maximum sessions returned per host").option("--cursor <cursor>", "Continue one host page (requires --host)").option("--json", "Print the structured catalog response", false), { timeoutMs: CODEX_SESSION_CATALOG_CLI_TIMEOUT_MS }).action(async (options) => {
177
+ addGatewayClientOptions(codex.command("sessions").description("List non-archived Codex app-server sessions across connected hosts").option("--agent <id>", "Agent id that owns the Codex sessions").option("--search <text>", "Search session titles (case-insensitive)").option("--host <id>", "Filter by stable host id").option("--limit <count>", "Maximum sessions returned per host").option("--cursor <cursor>", "Continue one host page (requires --host)").option("--json", "Print the structured catalog response", false), { timeoutMs: CODEX_SESSION_CATALOG_CLI_TIMEOUT_MS }).action(async (options) => {
165
178
  await listCodexSessions(options);
166
179
  });
167
- addGatewayClientOptions(codex.command("continue <thread-id>").description("Continue a Gateway-local Codex thread as an OpenClaw branch").option("--json", "Print the structured response", false)).action(async (threadId, options) => {
180
+ addGatewayClientOptions(codex.command("continue <thread-id>").description("Continue a Gateway-local Codex thread as an OpenClaw branch").option("--agent <id>", "Agent id that owns the Codex session").option("--host <id>", "Stable local host id from codex sessions").option("--json", "Print the structured response", false)).action(async (threadId, options) => {
168
181
  await continueCodexSession(threadId, options);
169
182
  });
170
- addGatewayClientOptions(codex.command("archive <thread-id>").description("Archive a stored or idle Gateway-local Codex thread").option("--confirm-no-other-runner", "Confirm no other Codex client or OpenClaw runner is using this thread", false).option("--json", "Print the structured response", false)).action(async (threadId, options) => {
183
+ addGatewayClientOptions(codex.command("archive <thread-id>").description("Archive a stored or idle Gateway-local Codex thread").option("--agent <id>", "Agent id that owns the Codex session").option("--host <id>", "Stable local host id from codex sessions").option("--confirm-no-other-runner", "Confirm no other Codex client or OpenClaw runner is using this thread", false).option("--json", "Print the structured response", false)).action(async (threadId, options) => {
171
184
  await archiveCodexSession(threadId, options);
172
185
  });
173
186
  }
@@ -0,0 +1,36 @@
1
+ import { J as releaseCodexAppServerLiveThread, d as retainSharedCodexAppServerClientByInstanceId } from "./shared-client-Dw-rzii0.js";
2
+ import { a as closeCodexStartupClientBestEffort, d as unsubscribeCodexThreadBestEffort, r as CodexAppServerUnsafeSubscriptionError } from "./attempt-client-cleanup-URjUlCHg.js";
3
+ import { t as isIncognitoSessionKey } from "./incognito-session-ET_ufDH-.js";
4
+ import { t as codexNativeSubagentMonitorRuntime } from "./native-subagent-monitor-GGq3RnOo.js";
5
+ //#region extensions/codex/src/app-server/session-retirement.ts
6
+ /** Retire binding and native subscription under the same generation/physical-client ownership fence. */
7
+ async function retireCodexAppServerSessionGeneration(params) {
8
+ const retireGeneration = () => params.mode === "reset" ? params.bindingStore.resetSessionGeneration(params.identity) : params.bindingStore.retireSessionGeneration(params.identity);
9
+ const expectedBinding = await params.bindingStore.read(params.identity);
10
+ if (!expectedBinding) return await retireGeneration();
11
+ return await params.bindingStore.withLease(params.identity, async () => {
12
+ const binding = await params.bindingStore.read(params.identity);
13
+ if (binding?.threadId !== expectedBinding.threadId) return "conflict";
14
+ const result = await retireGeneration();
15
+ if (result !== "applied" || !binding?.clientId) return result;
16
+ const clientLease = retainSharedCodexAppServerClientByInstanceId(binding.clientId);
17
+ if (!clientLease) return result;
18
+ try {
19
+ codexNativeSubagentMonitorRuntime.retireParent(clientLease.client, binding.threadId);
20
+ if (!await releaseCodexAppServerLiveThread(clientLease.client, binding.threadId) && isIncognitoSessionKey(params.identity.sessionKey)) {
21
+ if (!await unsubscribeCodexThreadBestEffort(clientLease.client, {
22
+ threadId: binding.threadId,
23
+ timeoutMs: 5e3
24
+ })) {
25
+ await closeCodexStartupClientBestEffort(clientLease.client);
26
+ throw new CodexAppServerUnsafeSubscriptionError(`Codex retired session subscription could not be released: ${binding.threadId}`);
27
+ }
28
+ }
29
+ } finally {
30
+ clientLease.release();
31
+ }
32
+ return result;
33
+ });
34
+ }
35
+ //#endregion
36
+ export { retireCodexAppServerSessionGeneration };
@@ -1,4 +1,4 @@
1
- import { r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-DzsdNCf5.js";
1
+ import { r as importCodexThreadHistoryToTranscript } from "./transcript-mirror-BU0_LKAL.js";
2
2
  import { resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
3
3
  //#region extensions/codex/src/app-server/session-history-import.ts
4
4
  /** Creates a session whose transcript is derived from one verified Codex thread snapshot. */
@@ -1,8 +1,9 @@
1
- import { W as isJsonObject } from "./shared-client-DTbvH-XV.js";
2
- import { c as fingerprintCodexMirrorSourceMessage, d as attachCodexMirrorIdentity, f as readMirrorIdentity, l as readCodexMirrorSourceFingerprint, p as readUpstreamUserText, t as codexTranscriptMirrorRuntime } from "./transcript-mirror-DzsdNCf5.js";
3
- import { t as runBoundedCodexAppServerTurn } from "./bounded-turn-D2ZAgZVz.js";
4
- import { n as createAssistantMessage } from "./event-projector-assistant-message-DTgnPPVF.js";
5
- import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
1
+ import { ut as isJsonObject } from "./shared-client-Dw-rzii0.js";
2
+ import { i as readUpstreamUserText, r as readMirrorIdentity, t as attachCodexMirrorIdentity } from "./upstream-prompt-provenance-CFmsKwr9.js";
3
+ import { c as readCodexMirrorSourceFingerprint, s as fingerprintCodexMirrorSourceMessage, t as codexTranscriptMirrorRuntime } from "./transcript-mirror-BU0_LKAL.js";
4
+ import { t as runBoundedCodexAppServerTurn } from "./bounded-turn-4BjrZEwW.js";
5
+ import { n as createAssistantMessage } from "./event-projector-assistant-message-BwxV4d6N.js";
6
+ import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
7
  import { Buffer } from "node:buffer";
7
8
  import { isSilentReplyText } from "openclaw/plugin-sdk/reply-runtime";
8
9
  //#region extensions/codex/src/app-server/settled-turn-projection.ts
@@ -11,9 +12,6 @@ const MAX_PROJECTION_BYTES = 512 * 1024;
11
12
  const MAX_TEXT_BYTES = 64 * 1024;
12
13
  const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/u;
13
14
  const TOOL_ERROR_STATUS_PREFIX = "[Tool result status: error]\n";
14
- function readNonEmptyString(value) {
15
- return typeof value === "string" ? value.trim() || void 0 : void 0;
16
- }
17
15
  function readBoundedText(value, label, maxBytes = MAX_TEXT_BYTES) {
18
16
  if (typeof value !== "string" || !value.trim()) return;
19
17
  if (Buffer.byteLength(value, "utf8") > maxBytes) throw new Error(`Codex settled-turn projection found oversized ${label}`);
@@ -28,12 +26,12 @@ function responseItemBytes(item) {
28
26
  return Buffer.byteLength(JSON.stringify(item), "utf8");
29
27
  }
30
28
  function requireCallId(value) {
31
- const callId = readNonEmptyString(value);
29
+ const callId = normalizeOptionalString(value);
32
30
  if (!callId || callId.length > 256) throw new Error("Codex settled-turn projection found an invalid tool call id");
33
31
  return callId;
34
32
  }
35
33
  function requireToolName(value) {
36
- const name = readNonEmptyString(value);
34
+ const name = normalizeOptionalString(value);
37
35
  if (!name || !TOOL_NAME_PATTERN.test(name)) throw new Error("Codex settled-turn projection found an invalid tool name");
38
36
  return name;
39
37
  }
@@ -58,27 +56,26 @@ function serializeToolArguments(value) {
58
56
  return requireBoundedText(serialized, "tool arguments");
59
57
  }
60
58
  function projectUserMessage(message) {
61
- const record = message;
62
59
  const upstreamUserText = readUpstreamUserText(message);
63
- if (upstreamUserText && typeof record.content === "string") return [{
60
+ if (upstreamUserText && typeof message.content === "string") return [{
64
61
  type: "message",
65
62
  role: "user",
66
63
  content: [{
67
64
  type: "input_text",
68
- text: requireBoundedText(upstreamUserText, "upstream user text")
65
+ text: requireBoundedText(upstreamUserText, "upstream user text", MAX_PROJECTION_BYTES)
69
66
  }]
70
67
  }];
71
- if (typeof record.content === "string") return [{
68
+ if (typeof message.content === "string") return [{
72
69
  type: "message",
73
70
  role: "user",
74
71
  content: [{
75
72
  type: "input_text",
76
- text: requireBoundedText(record.content, "user message")
73
+ text: requireBoundedText(message.content, "user message")
77
74
  }]
78
75
  }];
79
- if (!Array.isArray(record.content)) throw new Error("Codex settled-turn projection found unsupported user content");
76
+ if (!Array.isArray(message.content)) throw new Error("Codex settled-turn projection found unsupported user content");
80
77
  const content = [];
81
- for (const value of record.content) {
78
+ for (const value of message.content) {
82
79
  if (!isRecord(value)) throw new Error("Codex settled-turn projection found malformed user content");
83
80
  if (value.type === "text") {
84
81
  const text = readBoundedText(value.text, "user text");
@@ -88,7 +85,7 @@ function projectUserMessage(message) {
88
85
  });
89
86
  continue;
90
87
  }
91
- throw new Error(`Codex settled-turn projection does not support user content ${String(value.type)}`);
88
+ throw new Error(`Codex settled-turn projection does not support user content ${value.type}`);
92
89
  }
93
90
  if (content.length === 0) throw new Error("Codex settled-turn projection found an empty user message");
94
91
  return [{
@@ -146,13 +143,14 @@ function projectToolResult(message) {
146
143
  const id = requireCallId(message.toolCallId);
147
144
  const name = requireToolName(message.toolName);
148
145
  if (!Array.isArray(message.content)) throw new Error("Codex settled-turn projection found unsupported tool result content");
149
- if (message.isError !== void 0 && typeof message.isError !== "boolean") throw new Error("Codex settled-turn projection found invalid tool result status");
150
- const isError = message.isError === true;
146
+ const isErrorValue = message.isError;
147
+ if (isErrorValue !== void 0 && typeof isErrorValue !== "boolean") throw new Error("Codex settled-turn projection found invalid tool result status");
148
+ const isError = isErrorValue === true;
151
149
  const parts = [];
152
150
  for (const value of message.content) {
153
151
  if (!isRecord(value)) throw new Error("Codex settled-turn projection found malformed tool result content");
154
152
  if (value.type === "image") {
155
- const mimeType = readNonEmptyString(value.mimeType) ?? "unknown type";
153
+ const mimeType = normalizeOptionalString(value.mimeType) ?? "unknown type";
156
154
  parts.push(`[Image tool result: ${mimeType}]`);
157
155
  continue;
158
156
  }
@@ -175,17 +173,16 @@ function projectToolResult(message) {
175
173
  };
176
174
  }
177
175
  function projectMessage(message) {
178
- const record = message;
179
176
  let items;
180
177
  let calls = [];
181
178
  let results = [];
182
179
  if (message.role === "user") items = projectUserMessage(message);
183
180
  else if (message.role === "assistant") {
184
- const projected = projectAssistantMessage(record);
181
+ const projected = projectAssistantMessage(message);
185
182
  items = projected.items;
186
183
  calls = projected.calls;
187
184
  } else if (message.role === "toolResult") {
188
- const projected = projectToolResult(record);
185
+ const projected = projectToolResult(message);
189
186
  items = [projected.item];
190
187
  results = [projected.result];
191
188
  } else throw new Error(`Codex settled-turn projection does not support role ${message.role}`);
@@ -246,6 +243,7 @@ async function runCodexSettledTurnFinalization(operation, options) {
246
243
  mode: "required",
247
244
  id: attempt.modelId
248
245
  },
246
+ modelProvider: "openai",
249
247
  profile: attempt.authProfileId,
250
248
  timeoutMs: attempt.runTimeoutOverrideMs ?? attempt.timeoutMs,
251
249
  signal: attempt.abortSignal,
@@ -262,7 +260,8 @@ async function runCodexSettledTurnFinalization(operation, options) {
262
260
  requiredModalities: ["text"],
263
261
  isolation: "private-stdio",
264
262
  historyItems,
265
- requireNoExternalCapabilities: true
263
+ requireNoExternalCapabilities: true,
264
+ allowEmptyText: true
266
265
  });
267
266
  let promptEchoSeen = false;
268
267
  let unexpectedItem;
@@ -281,7 +280,15 @@ async function runCodexSettledTurnFinalization(operation, options) {
281
280
  }
282
281
  if (unexpectedItem) throw new Error(`Codex settled-turn finalization returned unexpected native item: ${unexpectedItem.type}`);
283
282
  const text = bounded.text.trim();
284
- if (!text || isSilentReplyText(text)) throw new Error("Codex settled-turn finalization completed without a visible answer");
283
+ if (!text) return {
284
+ assistant: createAssistantMessage(attempt, "", {
285
+ tokenUsage: bounded.usage,
286
+ aborted: false,
287
+ promptError: null
288
+ }),
289
+ ...bounded.usage ? { usage: bounded.usage } : {}
290
+ };
291
+ if (isSilentReplyText(text)) throw new Error("Codex settled-turn finalization completed without a visible answer");
285
292
  const mirrorIdentity = `settled-finalizer:${attempt.runId}`;
286
293
  const assistant = attachCodexMirrorIdentity(createAssistantMessage(attempt, text, {
287
294
  tokenUsage: bounded.usage,
@@ -296,6 +303,11 @@ async function runCodexSettledTurnFinalization(operation, options) {
296
303
  cwd: attempt.workspaceDir,
297
304
  messages: [assistant],
298
305
  idempotencyScope: `codex-settled-finalizer:${attempt.runId}`,
306
+ runId: attempt.runId,
307
+ terminalAssistantOwner: {
308
+ mirrorIdentity,
309
+ runId: attempt.runId
310
+ },
299
311
  config: attempt.config,
300
312
  skipBeforeMessageWriteHooks: true
301
313
  });
@@ -303,8 +315,8 @@ async function runCodexSettledTurnFinalization(operation, options) {
303
315
  const expectedFingerprint = fingerprintCodexMirrorSourceMessage(assistant);
304
316
  if (!mirrorResult.assistantMirrorIdentitiesOwned.includes(mirrorIdentity) || !persistedMessage || persistedMessage.role !== "assistant" || readCodexMirrorSourceFingerprint(persistedMessage) !== expectedFingerprint) throw new Error("Codex settled-turn final answer transcript attestation mismatch");
305
317
  const persistedAssistant = persistedMessage;
306
- const persistedAssistantRecord = persistedAssistant;
307
- const assistantTranscriptIdempotencyKey = typeof persistedAssistantRecord.idempotencyKey === "string" ? persistedAssistantRecord.idempotencyKey.trim() : "";
318
+ const persistedIdempotencyKey = "idempotencyKey" in persistedAssistant ? persistedAssistant.idempotencyKey : void 0;
319
+ const assistantTranscriptIdempotencyKey = typeof persistedIdempotencyKey === "string" ? persistedIdempotencyKey.trim() : "";
308
320
  return {
309
321
  assistant: persistedAssistant,
310
322
  assistantTranscriptOwned: true,