@openclaw/codex 2026.9.1-beta.1 → 2026.9.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.
Files changed (81) hide show
  1. package/dist/api.js +8 -9
  2. package/dist/{attempt-client-cleanup-bfsmcQ6m.js → attempt-client-cleanup-Bp1ZOnk2.js} +105 -75
  3. package/dist/attempt-context-CSazkWns.js +634 -0
  4. package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
  5. package/dist/{bounded-turn-B7iBUcuP.js → bounded-turn-DxGJRYbY.js} +53 -57
  6. package/dist/{capabilities-BRMh0aIq.js → capabilities-B1mgvpP8.js} +2 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-DEsIufkt.js → command-formatters-DSbGMXA0.js} +5 -4
  9. package/dist/{command-handlers-D_-1_qRp.js → command-handlers-DOJvGv6-.js} +243 -148
  10. package/dist/{compact-BglNFZrQ.js → compact-Y_xBqrHN.js} +109 -65
  11. package/dist/{config-giW8X0QA.js → config-CyapYSji.js} +328 -374
  12. package/dist/config-utils-C57JiJrU.js +69 -0
  13. package/dist/{context-compaction-activity-BOeJf7yo.js → context-compaction-activity-CMla5FPJ.js} +4 -1
  14. package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
  15. package/dist/doctor-contract-api.js +144 -14
  16. package/dist/{dynamic-tools-Dnlfa--w.js → dynamic-tools-BwpvTxaX.js} +1119 -1135
  17. package/dist/{effective-mcp-catalog-CTeGrnfO.js → effective-mcp-catalog-CY9Jn4bA.js} +4 -3
  18. package/dist/{event-projector-assistant-message-C5-Ds8mA.js → event-projector-assistant-message-B9kp6iGC.js} +15 -6
  19. package/dist/event-projector-usage-ZqEVbAEY.js +212 -0
  20. package/dist/{harness-Csz0F-DK.js → harness-Czh2Sdoc.js} +65 -52
  21. package/dist/harness.js +1 -1
  22. package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
  23. package/dist/index.js +495 -457
  24. package/dist/{isolated-completion-Cv6FqygM.js → isolated-completion-CRh-51AK.js} +9 -21
  25. package/dist/{managed-binary-DFYK4DWq.js → managed-binary-DcpnJF9x.js} +117 -80
  26. package/dist/media-understanding-provider.js +1 -1
  27. package/dist/model-catalog-DT5vo6qo.js +101 -0
  28. package/dist/{models-TS3HBU-q.js → models-CcrgSMQR.js} +26 -29
  29. package/dist/{native-subagent-monitor-4Mfq2HDs.js → native-subagent-monitor-B3bvQGcg.js} +63 -122
  30. package/dist/{node-exec-server.runtime-BpWqJO1V.js → node-exec-server.runtime-nfbUOtUB.js} +10 -7
  31. package/dist/{plugin-approval-roundtrip-bUm777tR.js → plugin-approval-roundtrip-aQVdyL5g.js} +30 -3
  32. package/dist/{plugin-marketplace-discovery-B9DvaAG-.js → plugin-marketplace-discovery-B7gHXJ3d.js} +44 -30
  33. package/dist/{plugin-metadata-cache-CTCT3w5L.js → plugin-metadata-cache-BwCLTY_U.js} +6 -6
  34. package/dist/provider-capabilities-BpG9Ltc5.js +101 -0
  35. package/dist/{rate-limits-C5_D8Gmb.js → rate-limits-Fx_l4eNa.js} +3 -2
  36. package/dist/{sandbox-guard-kuWTn9C5.js → request-C8H22rPi.js} +195 -2
  37. package/dist/{run-attempt-DYc0QCBF.js → run-attempt-BzthFQSM.js} +1616 -2634
  38. package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-Bh_gf6-t.js} +22 -15
  39. package/dist/{session-binding-Bcynfd-_.js → session-binding-4Ki04sW-.js} +282 -399
  40. package/dist/session-binding-record-CyaXZvTe.js +270 -0
  41. package/dist/{session-catalog-DaLKVtpH.js → session-catalog-B47uwekb.js} +812 -504
  42. package/dist/{session-cli-DMc4oS2w.js → session-cli-BjLhpmS1.js} +1 -1
  43. package/dist/session-history-DWmMqiqW.js +93 -0
  44. package/dist/session-history-read-LHldwvyh.js +36 -0
  45. package/dist/session-history-worker-runtime.js +48 -0
  46. package/dist/session-history.worker-zZVcx0ME.js +267 -0
  47. package/dist/session-history.worker.js +2 -0
  48. package/dist/session-initialization-BCFVnLL4.js +75 -0
  49. package/dist/{session-permission-policy-BrNH1bjy.js → session-permission-policy-BaueHmSj.js} +2 -2
  50. package/dist/session-retirement-DNdmYdFc.js +97 -0
  51. package/dist/{session-upstream-marker-BIkusb95.js → session-upstream-marker-DReTpGz6.js} +20 -4
  52. package/dist/settled-turn-context-BOM3Y1pl.js +45 -0
  53. package/dist/settled-turn-finalizer-BI-b1sry.js +115 -0
  54. package/dist/{shared-client-DKz23hHY.js → shared-client-CKhX2kFt.js} +3659 -847
  55. package/dist/{side-question-DjA_jsNe.js → side-question-B6DWAw1A.js} +168 -81
  56. package/dist/{thread-lifecycle-L7oEkTlq.js → thread-lifecycle-DPK30jad.js} +2412 -2145
  57. package/dist/{thread-ownership-DZeuKH1j.js → thread-ownership-CbucAmmp.js} +27 -16
  58. package/dist/thread-policy-CTalhWFy.js +105 -0
  59. package/dist/{plugin-app-cache-key-C1CAdfcP.js → thread-resume-BLgqkUc1.js} +61 -9
  60. package/dist/{transcript-mirror-DsCMWBca.js → transcript-mirror-_njEB84z.js} +114 -110
  61. package/dist/transcript-mirror-attestation-Byl34PC-.js +76 -0
  62. package/dist/transport-stdio-CzTngV0j.js +728 -0
  63. package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
  64. package/dist/upstream-session-fork-1fwbNKzQ.js +886 -0
  65. package/dist/{usage-ROIaW3wo.js → usage-i9fv5-4m.js} +3 -3
  66. package/dist/{web-search-provider.runtime-ClaNPV7N.js → web-search-provider.runtime-DKQn67A7.js} +3 -3
  67. package/openclaw.plugin.json +1 -30
  68. package/package.json +6 -6
  69. package/dist/binding-connection-BMvPX73o.js +0 -45
  70. package/dist/command-rpc-CCeCkf_v.js +0 -90
  71. package/dist/event-projector-usage-Bk1I90Bg.js +0 -328
  72. package/dist/incognito-session-ET_ufDH-.js +0 -2
  73. package/dist/model-catalog-i9rNBthX.js +0 -48
  74. package/dist/protocol-validators-VeJxxJ0U.js +0 -2421
  75. package/dist/request-DXXGHko0.js +0 -157
  76. package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
  77. package/dist/session-retirement-CcNC3YSY.js +0 -36
  78. package/dist/settled-turn-finalizer-Dc3ApjU4.js +0 -328
  79. package/dist/thread-resume-BE9qNEW0.js +0 -30
  80. package/dist/transport-stdio-DhoGy33Z.js +0 -99
  81. package/dist/upstream-session-fork-DlaHxSLC.js +0 -326
@@ -1,232 +1,43 @@
1
- import { h as readCodexPluginConfig, p as isCodexRemoteExecPlacementSandbox } from "./config-giW8X0QA.js";
2
- import { K as createCodexElicitationResponse, gt as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, p as releaseLeasedSharedCodexAppServerClient, vt as isJsonObject } from "./shared-client-DKz23hHY.js";
3
- import { E as itemStatus, F as readItem, b as auditNativeToolUnfinishedStatus, h as isCodexNotificationForTurn, v as auditNativeToolName, y as auditNativeToolTerminalStatus } from "./attempt-client-cleanup-bfsmcQ6m.js";
4
- import { l as readCodexTurn } from "./protocol-validators-VeJxxJ0U.js";
5
- import { $ as isForcedPrivateQaCodexRuntime, D as withDynamicToolExecutionState, E as createFailedDynamicToolResponse, F as resolveCodexWebSearchPlan, O as withDynamicToolTranscriptDetails, Q as filterCodexDynamicToolsForDisabledNativeSurface, T as resolveCodexToolAbortTerminalReason, Z as filterCodexDynamicTools, d as emitCodexNativePreToolUseFailureDiagnostic, nt as normalizeCodexDynamicToolName, tt as isSystemAgentOnlyCodexDynamicToolAllowlist } from "./thread-lifecycle-L7oEkTlq.js";
6
- import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
7
- import { r as formatCodexDisplayText } from "./command-formatters-DEsIufkt.js";
8
- import { a as resolveCodexNodeExecToolOverrides, i as resolveCodexNativeExecutionPolicy } from "./sandbox-guard-kuWTn9C5.js";
9
- import { a as sanitizeCodexApprovalVisibleText, c as waitForPluginApprovalDecision, i as requestPluginApproval, n as codexApprovalTimeoutText, o as stripDanglingCodexApprovalTerminalSequence, r as mapExecDecisionToOutcome, s as truncateCodexApprovalDisplayText, t as approvalRequestExplicitlyUnavailable } from "./plugin-approval-roundtrip-bUm777tR.js";
1
+ import { B as isJsonObject, E as isCodexRemoteExecPlacementSandbox, O as readCodexPluginConfig, R as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, z as flattenCodexDynamicToolFunctions } from "./config-CyapYSji.js";
2
+ import { St as withDynamicToolTranscriptDetails, bt as createFailedDynamicToolResponse, ct as createCodexElicitationResponse, ot as readCodexTurn, xt as withDynamicToolExecutionState, yt as resolveCodexToolAbortTerminalReason } from "./shared-client-CKhX2kFt.js";
3
+ import { E as itemStatus, L as readItem, T as itemName, b as auditNativeToolUnfinishedStatus, h as isCodexNotificationForTurn, k as shouldClearTerminalPresentationForNativeItem, v as auditNativeToolName, y as auditNativeToolTerminalStatus } from "./attempt-client-cleanup-Bp1ZOnk2.js";
4
+ import { B as emitCodexNativePreToolUseFailureDiagnostic, S as resolveCodexWebSearchPlan, at as filterCodexDynamicTools, lt as isSystemAgentOnlyCodexDynamicToolAllowlist, ot as filterCodexDynamicToolsForDisabledNativeSurface, st as isForcedPrivateQaCodexRuntime, ut as normalizeCodexDynamicToolName } from "./thread-lifecycle-DPK30jad.js";
5
+ import { l as resolveCodexNativeExecutionPolicy, u as resolveCodexNodeExecToolOverrides } from "./request-C8H22rPi.js";
6
+ import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-tupAr-2o.js";
7
+ import { a as formatCodexDisplayText } from "./command-formatters-DSbGMXA0.js";
8
+ import { a as CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME, c as createNodeExecAliasDynamicTool, i as CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME, l as isCodexDynamicToolExcluded, o as createGatewayExecProjection, r as CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME, s as createGatewayProcessProjection } from "./provider-capabilities-BpG9Ltc5.js";
9
+ import { a as requestPluginApprovalOutcome, c as truncateCodexApprovalDisplayText, i as requestPluginApproval, l as waitForPluginApprovalDecision, n as codexApprovalTimeoutText, o as sanitizeCodexApprovalVisibleText, r as mapExecDecisionToOutcome, s as stripDanglingCodexApprovalTerminalSequence, t as approvalRequestExplicitlyUnavailable } from "./plugin-approval-roundtrip-aQVdyL5g.js";
10
10
  import { asNonArrayRecord, asOptionalRecord, isRecord, normalizeOptionalString, normalizeTrimmedStringList, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
11
+ import { createHash, randomUUID } from "node:crypto";
12
+ import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
13
+ import { killProcessTree } from "openclaw/plugin-sdk/process-runtime";
11
14
  import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
12
15
  import path, { posix } from "node:path";
13
16
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
14
- import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
15
- import { HEARTBEAT_RESPONSE_TOOL_NAME, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, consumeAdjustedParamsForToolCall, consumePreExecutionBlockedToolCall, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, extractToolResultMediaArtifact, filterProviderNormalizableTools, filterToolResultMediaUrls, finalizeToolTerminalPresentation, formatApprovalDisplayPath, formatToolExecutionErrorMessage, getBeforeToolCallFailureDisposition, getChannelAgentToolMeta, getPluginToolMeta, getPluginToolSideEffectOwnerKey, hasNativeHookRelayInvocation, invokeNativeHookRelay, isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, isHostScopedAgentToolActive, isMessagingTool, isMessagingToolSendAction, isReplaySafeToolCall, isSubagentSessionKey, isToolResultError, isToolWrappedWithBeforeToolCallHook, normalizeAgentRuntimeTools, normalizeHeartbeatToolResponse, projectRuntimeToolInputSchema, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveNativeHookRelayDeferredToolApproval, resolveToolExecutionErrorKind, resolveToolResultFailureKind, runAgentHarnessAfterToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, supportsModelTools, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
16
- import { createHash, randomUUID } from "node:crypto";
17
+ import { HEARTBEAT_RESPONSE_TOOL_NAME, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, consumeAdjustedParamsForToolCall, consumePreExecutionBlockedToolCall, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, extractToolResultMediaArtifact, filterProviderNormalizableTools, filterToolResultMediaUrls, finalizeAgentToolAvailability, finalizeToolTerminalPresentation, formatApprovalDisplayPath, formatToolExecutionErrorMessage, getBeforeToolCallFailureDisposition, getChannelAgentToolMeta, getPluginToolMeta, getPluginToolSideEffectOwnerKey, hasNativeHookRelayInvocation, invokeNativeHookRelay, isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, isHostScopedAgentToolActive, isMessagingTool, isMessagingToolSendAction, isReplaySafeToolCall, isSubagentSessionKey, isToolResultError, isToolWrappedWithBeforeToolCallHook, normalizeAgentRuntimeTools, normalizeHeartbeatToolResponse, projectRuntimeToolInputSchema, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveNativeHookRelayDeferredToolApproval, resolveToolExecutionErrorKind, resolveToolResultFailureKind, runAgentHarnessAfterToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, supportsModelTools, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
18
+ import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
17
19
  import { resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
18
20
  import { formatErrorMessage as formatErrorMessage$1, redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
19
- import { killProcessTree } from "openclaw/plugin-sdk/process-runtime";
20
21
  import { SsrFBlockedError, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
21
22
  import { spawn } from "node:child_process";
23
+ import { once } from "node:events";
22
24
  import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
23
25
  import { DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, estimateToolResultTextChars, resolveLiveToolResultMaxChars, sliceToolResultTextToBudget, sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
24
- import { once } from "node:events";
26
+ import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
25
27
  import { isIP } from "node:net";
26
28
  import { WebSocketServer } from "ws";
27
- import { fileURLToPath, pathToFileURL } from "node:url";
28
29
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
29
30
  import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
30
- import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
31
- import { runWithCronCreatorAuthorityCapabilityResolver } from "openclaw/plugin-sdk/codex-mcp-projection";
32
- import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
31
+ import { formatMcpCodexApprovalRemedy, requiresMcpCodexToolApproval, resolveCodexScheduledToolProjectionFactory, resolveProjectedMcpCodexToolApprovalMode, runWithCronCreatorAuthorityCapabilityResolver } from "openclaw/plugin-sdk/codex-mcp-projection";
33
32
  import { buildRemoteCommand, isToolAllowed, sanitizeEnvVars } from "openclaw/plugin-sdk/sandbox";
33
+ import { fileURLToPath, pathToFileURL } from "node:url";
34
34
  import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
35
35
  import { getMediaDir } from "openclaw/plugin-sdk/media-runtime";
36
- import { normalizeOpenAIToolSchemas } from "openclaw/plugin-sdk/provider-tools";
37
- //#region extensions/codex/src/app-server/event-projector-native-tool-lifecycle.ts
38
- /** Projects metadata-only lifecycle diagnostics for native tool items. */
39
- var CodexNativeToolLifecycleProjector = class {
40
- constructor(context, threadId, turnId, options = {}) {
41
- this.context = context;
42
- this.threadId = threadId;
43
- this.turnId = turnId;
44
- this.options = options;
45
- this.startedAtByItem = /* @__PURE__ */ new Map();
46
- this.activeItems = /* @__PURE__ */ new Map();
47
- this.webSearchCompletionByItem = /* @__PURE__ */ new Map();
48
- this.completedItemIds = /* @__PURE__ */ new Set();
49
- this.approvalFailureDispositionByItem = /* @__PURE__ */ new Map();
50
- this.preToolUseFailureByItem = /* @__PURE__ */ new Map();
51
- this.finalized = false;
52
- }
53
- handleNotification(notification) {
54
- const params = isJsonObject(notification.params) ? notification.params : void 0;
55
- if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
56
- if (notification.method === "turn/completed") {
57
- const turn = readCodexTurn(params.turn);
58
- if (!turn || turn.id !== this.turnId) return;
59
- for (const item of turn.items ?? []) this.recordSnapshotItem(item);
60
- return;
61
- }
62
- if (notification.method === "rawResponseItem/completed") {
63
- const item = isJsonObject(params.item) ? params.item : void 0;
64
- if (item) this.recordRawWebSearchResult(item);
65
- return;
66
- }
67
- if (notification.method !== "item/started" && notification.method !== "item/completed") return;
68
- const item = readItem(params.item);
69
- if (!item) return;
70
- this.recordItem({
71
- phase: notification.method === "item/started" ? "start" : "result",
72
- item,
73
- sourceTimestampMs: asDateTimestampMs(notification.method === "item/started" ? params.startedAtMs : params.completedAtMs)
74
- });
75
- }
76
- recordItem(params) {
77
- const toolName = auditNativeToolName(params.item);
78
- if (!toolName || this.completedItemIds.has(params.item.id)) return;
79
- if (params.phase === "start") {
80
- this.recordStarted(params.item.id, toolName, auditNativeToolUnfinishedStatus(params.item), params.sourceTimestampMs);
81
- return;
82
- }
83
- if (params.item.type === "webSearch") {
84
- this.webSearchCompletionByItem.set(params.item.id, {
85
- runWasAborted: this.options.runAbortSignal?.aborted === true,
86
- sourceTimestampMs: params.sourceTimestampMs
87
- });
88
- return;
89
- }
90
- const itemDurationMs = typeof params.item.durationMs === "number" ? params.item.durationMs : void 0;
91
- this.recordTerminal(params.item.id, toolName, auditNativeToolTerminalStatus(params.item), {
92
- itemDurationMs,
93
- sourceTimestampMs: params.sourceTimestampMs
94
- });
95
- }
96
- recordApprovalFailureDisposition(toolCallId, disposition) {
97
- if (!this.completedItemIds.has(toolCallId)) this.approvalFailureDispositionByItem.set(toolCallId, disposition);
98
- }
99
- recordPreToolUseFailure(failure, runWasAborted = this.options.runAbortSignal?.aborted === true) {
100
- if (this.completedItemIds.has(failure.toolCallId)) return;
101
- const record = {
102
- failure,
103
- terminalReason: runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : failure.disposition
104
- };
105
- if (this.finalized) {
106
- this.completedItemIds.add(failure.toolCallId);
107
- this.emitPreToolUseFailure(record, failure.toolName, failure.durationMs);
108
- return;
109
- }
110
- this.preToolUseFailureByItem.set(failure.toolCallId, record);
111
- }
112
- recordRawWebSearchResult(item) {
113
- if (readStringField(item, "type") !== "web_search_call") return;
114
- const toolCallId = readStringField(item, "id");
115
- if (!toolCallId || this.completedItemIds.has(toolCallId)) return;
116
- const toolName = "web_search";
117
- this.recordStarted(toolCallId, toolName, "unknown");
118
- const rawStatus = readStringField(item, "status");
119
- if (rawStatus === "in_progress" || rawStatus === "running") return;
120
- const status = rawStatus === "completed" ? "completed" : rawStatus === "cancelled" ? "cancelled" : rawStatus === "failed" || rawStatus === "error" || rawStatus === "incomplete" ? "failed" : "unknown";
121
- this.recordTerminal(toolCallId, toolName, status, { sourceTimestampMs: this.webSearchCompletionByItem.get(toolCallId)?.sourceTimestampMs });
122
- }
123
- recordTerminal(toolCallId, toolName, status, options = {}) {
124
- const runWasAborted = options.runWasAborted ?? this.options.runAbortSignal?.aborted === true;
125
- const preToolUseFailure = this.preToolUseFailureByItem.get(toolCallId);
126
- this.preToolUseFailureByItem.delete(toolCallId);
127
- const approvalFailureDisposition = this.approvalFailureDispositionByItem.get(toolCallId);
128
- this.approvalFailureDispositionByItem.delete(toolCallId);
129
- this.completedItemIds.add(toolCallId);
130
- this.activeItems.delete(toolCallId);
131
- this.webSearchCompletionByItem.delete(toolCallId);
132
- const startedAt = this.startedAtByItem.get(toolCallId);
133
- this.startedAtByItem.delete(toolCallId);
134
- const endedAt = options.sourceTimestampMs ?? Date.now();
135
- const durationMs = options.itemDurationMs ?? (startedAt === void 0 ? 0 : Math.max(0, endedAt - startedAt));
136
- if (preToolUseFailure) {
137
- this.emitPreToolUseFailure(preToolUseFailure, toolName, durationMs, options.sourceTimestampMs);
138
- return;
139
- }
140
- const terminalEvent = approvalFailureDisposition ? {
141
- type: "tool.execution.error",
142
- durationMs,
143
- errorCategory: "codex_native_tool_approval",
144
- terminalReason: approvalFailureDisposition
145
- } : status === "blocked" ? {
146
- type: "tool.execution.blocked",
147
- reason: "codex_native_tool_blocked",
148
- deniedReason: "codex_native_tool_blocked"
149
- } : status === "failed" || status === "cancelled" || status === "unknown" ? {
150
- type: "tool.execution.error",
151
- durationMs,
152
- errorCategory: status === "unknown" ? "codex_native_tool_outcome_unknown" : status === "cancelled" ? "aborted" : "codex_native_tool_error",
153
- ...status === "unknown" ? { errorCode: "tool_outcome_unknown" } : {},
154
- terminalReason: status === "unknown" ? "failed" : runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : status === "cancelled" ? "cancelled" : "failed"
155
- } : {
156
- type: "tool.execution.completed",
157
- durationMs
158
- };
159
- emitTrustedDiagnosticEvent({
160
- ...this.buildBase(toolCallId, toolName),
161
- ...terminalEvent,
162
- ...options.sourceTimestampMs !== void 0 ? { sourceTimestampMs: options.sourceTimestampMs } : {}
163
- });
164
- }
165
- finalizeActive(runWasAborted = this.options.runAbortSignal?.aborted === true) {
166
- this.finalized = true;
167
- for (const [toolCallId, { toolName, unfinishedStatus }] of this.activeItems) {
168
- const webSearchCompletion = this.webSearchCompletionByItem.get(toolCallId);
169
- const itemRunWasAborted = webSearchCompletion ? webSearchCompletion.runWasAborted : runWasAborted;
170
- this.recordTerminal(toolCallId, toolName, unfinishedStatus, {
171
- runWasAborted: itemRunWasAborted,
172
- sourceTimestampMs: webSearchCompletion?.sourceTimestampMs
173
- });
174
- }
175
- for (const [toolCallId, record] of this.preToolUseFailureByItem) if (!this.completedItemIds.has(toolCallId)) this.recordTerminal(toolCallId, record.failure.toolName, "failed", { itemDurationMs: record.failure.durationMs });
176
- this.activeItems.clear();
177
- this.webSearchCompletionByItem.clear();
178
- this.approvalFailureDispositionByItem.clear();
179
- this.preToolUseFailureByItem.clear();
180
- }
181
- emitPreToolUseFailure(record, toolName, durationMs, sourceTimestampMs) {
182
- emitCodexNativePreToolUseFailureDiagnostic({
183
- agentId: this.context.agentId,
184
- sessionId: this.context.sessionId,
185
- sessionKey: this.context.sessionKey,
186
- runId: this.context.runId,
187
- failure: {
188
- ...record.failure,
189
- toolName,
190
- durationMs
191
- },
192
- terminalReason: record.terminalReason,
193
- sourceTimestampMs
194
- });
195
- }
196
- recordSnapshotItem(item) {
197
- if (!auditNativeToolName(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
198
- const toolName = auditNativeToolName(item);
199
- if (!toolName) return;
200
- this.recordStarted(item.id, toolName, auditNativeToolUnfinishedStatus(item));
201
- this.recordItem({
202
- phase: "result",
203
- item
204
- });
205
- }
206
- recordStarted(toolCallId, toolName, unfinishedStatus, sourceTimestampMs) {
207
- if (this.activeItems.has(toolCallId)) return;
208
- this.startedAtByItem.set(toolCallId, sourceTimestampMs ?? Date.now());
209
- this.activeItems.set(toolCallId, {
210
- toolName,
211
- unfinishedStatus
212
- });
213
- emitTrustedDiagnosticEvent({
214
- type: "tool.execution.started",
215
- ...this.buildBase(toolCallId, toolName),
216
- ...sourceTimestampMs !== void 0 ? { sourceTimestampMs } : {}
217
- });
218
- }
219
- buildBase(toolCallId, toolName) {
220
- return {
221
- agentId: this.context.agentId,
222
- runId: this.context.runId,
223
- sessionId: this.context.sessionId,
224
- sessionKey: this.context.sessionKey,
225
- toolName,
226
- toolCallId
227
- };
228
- }
229
- };
36
+ import { consumeTrustedToolNoStartError, copyInternalToolResultState, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
37
+ import { normalizeOpenAIStrictCompatSchema } from "openclaw/plugin-sdk/provider-tools";
38
+ //#region extensions/codex/src/app-server/dynamic-tool-build-state.ts
39
+ /** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
40
+ const dynamicToolBuildState = {};
230
41
  //#endregion
231
42
  //#region extensions/codex/src/app-server/remote-workspace-path.ts
232
43
  /** Projects a gateway workspace path into the remote Codex execution workspace. */
@@ -276,357 +87,6 @@ function joinRemoteWorkspacePath(remoteRoot, suffix) {
276
87
  return remoteRoot.endsWith("/") ? `${remoteRoot}${suffix}` : `${remoteRoot}/${suffix}`;
277
88
  }
278
89
  //#endregion
279
- //#region extensions/codex/src/app-server/remote-workspace-media.ts
280
- const REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS = 6e4;
281
- const REMOTE_WORKSPACE_MEDIA_MAX_BYTES = 64 * 1024 * 1024;
282
- const REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS = 16;
283
- const CODEX_REMOTE_MEDIA_CHUNK_BYTES = 512 * 1024;
284
- const CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES = 1024 * 1024;
285
- const CODEX_BOUNDED_REMOTE_FILE_READER = [
286
- "try{",
287
- "const fs=require(\"node:fs\");",
288
- "const path=require(\"node:path\");",
289
- "const file=process.argv[1];",
290
- "const max=Number(process.argv[2]);",
291
- "const offset=Number(process.argv[3]);",
292
- "const chunk=Number(process.argv[4]);",
293
- "const workspace=process.argv[5];",
294
- "if(!Number.isSafeInteger(max)||max<0)throw Error(\"invalid media byte limit\");",
295
- "if(!Number.isSafeInteger(offset)||offset<0||!Number.isSafeInteger(chunk)||chunk<=0)throw Error(\"invalid media chunk\");",
296
- "if(fs.lstatSync(file).isSymbolicLink())throw Error(\"symbolic links are not allowed\");",
297
- "const noFollow=fs.constants.O_NOFOLLOW??0;",
298
- "const fd=fs.openSync(file,fs.constants.O_RDONLY|noFollow);",
299
- "try{",
300
- "const before=fs.fstatSync(fd);",
301
- "if(!before.isFile())throw Error(\"not a regular file\");",
302
- "if(workspace){",
303
- "const descriptor=process.platform===\"linux\"?fs.realpathSync(`/proc/self/fd/${fd}`):fs.realpathSync(file);",
304
- "const relative=path.relative(fs.realpathSync(workspace),descriptor);",
305
- "if(!relative||relative===\"..\"||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative))throw Error(\"file escapes remote workspace\");",
306
- "const verified=fs.statSync(descriptor);",
307
- "if(verified.dev!==before.dev||verified.ino!==before.ino)throw Error(\"file changed while being opened\");",
308
- "}",
309
- "if(before.size>max)throw Error(`file exceeds limit of ${max} bytes`);",
310
- "if(offset>before.size)throw Error(\"file changed while being read\");",
311
- "const expected=Math.min(chunk,before.size-offset);",
312
- "const buffer=Buffer.allocUnsafe(expected);",
313
- "let total=0;",
314
- "while(total<buffer.length){",
315
- "const count=fs.readSync(fd,buffer,total,buffer.length-total,offset+total);",
316
- "if(count===0)break;total+=count;",
317
- "}",
318
- "if(total!==expected)throw Error(\"file changed while being read\");",
319
- "const after=fs.fstatSync(fd);",
320
- "const revision=stat=>[stat.dev,stat.ino,stat.size,stat.mtimeMs,stat.ctimeMs].join(\":\");",
321
- "if(!after.isFile()||revision(after)!==revision(before))throw Error(\"file changed while being read\");",
322
- "process.stdout.write(JSON.stringify({dataBase64:buffer.toString(\"base64\"),size:before.size,revision:revision(before)}));",
323
- "}finally{fs.closeSync(fd)}",
324
- "}catch(error){process.stderr.write(error instanceof Error?error.message:String(error));process.exitCode=1}"
325
- ].join("");
326
- const MESSAGE_MEDIA_KEYS = [
327
- "media",
328
- "mediaUrl",
329
- "media_url",
330
- "path",
331
- "filePath",
332
- "fileUrl",
333
- "imageUrl",
334
- "image_url"
335
- ];
336
- const MESSAGE_MEDIA_ARRAY_KEYS = [
337
- "mediaUrls",
338
- "media_urls",
339
- "imageUrls",
340
- "image_urls"
341
- ];
342
- const ATTACHMENT_MEDIA_KEYS = [
343
- "media",
344
- "mediaUrl",
345
- "path",
346
- "filePath",
347
- "fileUrl",
348
- "url"
349
- ];
350
- /** Reads actual remote bytes with a cap enforced by Codex before transport. */
351
- async function readBoundedCodexRemoteWorkspaceFile(params) {
352
- if (!Number.isSafeInteger(params.maxBytes) || params.maxBytes < 0) throw new Error("Codex remote workspace upload requires a valid media byte limit.");
353
- params.signal?.throwIfAborted();
354
- const chunks = [];
355
- let offset = 0;
356
- let expectedSize;
357
- let expectedRevision;
358
- const startedAt = Date.now();
359
- do {
360
- params.signal?.throwIfAborted();
361
- const timeoutMs = params.timeoutMs === void 0 ? void 0 : params.timeoutMs - (Date.now() - startedAt);
362
- if (timeoutMs !== void 0 && timeoutMs <= 0) throw new Error("Codex remote workspace file transfer timed out.");
363
- let response;
364
- try {
365
- response = await params.client.request("command/exec", {
366
- command: [
367
- "node",
368
- "-e",
369
- CODEX_BOUNDED_REMOTE_FILE_READER,
370
- "--",
371
- params.path,
372
- String(params.maxBytes),
373
- String(offset),
374
- String(CODEX_REMOTE_MEDIA_CHUNK_BYTES),
375
- ...params.workspaceRoot ? [params.workspaceRoot] : []
376
- ],
377
- env: {
378
- NODE_OPTIONS: null,
379
- NODE_PATH: null
380
- },
381
- ...timeoutMs === void 0 ? {} : { timeoutMs }
382
- }, {
383
- signal: params.signal,
384
- timeoutMs
385
- });
386
- } catch (error) {
387
- if (error instanceof Error && /failed to spawn|executable.*not found|\bENOENT\b/iu.test(error.message)) throw new Error("Codex remote workspace file transfer requires Node.js on the remote app-server host.", { cause: error });
388
- throw error;
389
- }
390
- if (!response || response.exitCode !== 0) {
391
- const detail = typeof response?.stderr === "string" ? response.stderr.trim() : "";
392
- throw new Error(`Codex remote workspace artifact could not be read: ${params.path}${detail ? `: ${detail}` : ""}`);
393
- }
394
- if (typeof response.stdout !== "string" || response.stdout.length > CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES) throw new Error("Codex remote workspace artifact exceeded the native command output cap.");
395
- let payload;
396
- try {
397
- payload = JSON.parse(response.stdout);
398
- } catch {
399
- throw new Error("Codex remote workspace artifact returned invalid chunk data.");
400
- }
401
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
402
- const chunk = payload;
403
- if (typeof chunk.dataBase64 !== "string" || !Number.isSafeInteger(chunk.size) || chunk.size < 0 || chunk.size > params.maxBytes || typeof chunk.revision !== "string" || !chunk.revision) throw new Error("Codex remote workspace artifact returned invalid or oversized chunk data.");
404
- if (expectedSize === void 0) {
405
- expectedSize = chunk.size;
406
- expectedRevision = chunk.revision;
407
- }
408
- if (chunk.size !== expectedSize || chunk.revision !== expectedRevision) throw new Error("Codex remote workspace artifact changed during chunked transfer.");
409
- const remainingBytes = expectedSize - offset;
410
- const expectedChunkBytes = Math.min(CODEX_REMOTE_MEDIA_CHUNK_BYTES, remainingBytes);
411
- if (chunk.dataBase64.length > Math.ceil(expectedChunkBytes / 3) * 4) throw new Error("Codex remote workspace artifact returned oversized chunk data.");
412
- const buffer = Buffer.from(chunk.dataBase64, "base64");
413
- if (buffer.byteLength !== expectedChunkBytes || buffer.toString("base64") !== chunk.dataBase64) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
414
- chunks.push(buffer);
415
- offset += buffer.byteLength;
416
- } while (offset < (expectedSize ?? 0));
417
- return { dataBase64: Buffer.concat(chunks, offset).toString("base64") };
418
- }
419
- /** Stages authoritative bounded remote bytes into immutable Gateway-owned media. */
420
- async function prepareCodexRemoteWorkspaceMessageMedia(params) {
421
- const { localWorkspaceRoot, remoteWorkspaceRoot } = params;
422
- if (!localWorkspaceRoot || !remoteWorkspaceRoot) return params.args;
423
- const remotePathsByLocalPath = /* @__PURE__ */ new Map();
424
- const gatewayManagedPaths = /* @__PURE__ */ new Set();
425
- const gatewayMediaRoot = getMediaDir();
426
- let attachmentEntries = 0;
427
- const mapMediaPath = (value) => {
428
- if (typeof value !== "string") return value;
429
- if (path.isAbsolute(value) && isPathStrictlyInside(gatewayMediaRoot, value)) {
430
- attachmentEntries += 1;
431
- gatewayManagedPaths.add(value);
432
- return value;
433
- }
434
- const mapped = mapCodexAppServerLocalWorkspacePath({
435
- value,
436
- localWorkspaceRoot,
437
- remoteWorkspaceRoot
438
- });
439
- if (value.trim() && !isCodexPassThroughMediaSource(value)) {
440
- attachmentEntries += 1;
441
- remotePathsByLocalPath.set(mapped, mapCodexAppServerRemoteWorkspacePath({
442
- value: mapped,
443
- localWorkspaceRoot,
444
- remoteWorkspaceRoot
445
- }));
446
- }
447
- return mapped;
448
- };
449
- let mappedArgs = params.args;
450
- const setMappedValue = (key, value) => {
451
- if (value === params.args[key]) return;
452
- if (mappedArgs === params.args) mappedArgs = { ...params.args };
453
- mappedArgs[key] = value;
454
- };
455
- for (const key of MESSAGE_MEDIA_KEYS) setMappedValue(key, mapMediaPath(params.args[key]));
456
- for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
457
- const value = params.args[key];
458
- if (Array.isArray(value)) {
459
- const mapped = value.map(mapMediaPath);
460
- if (mapped.some((entry, index) => entry !== value[index])) setMappedValue(key, mapped);
461
- }
462
- }
463
- if (Array.isArray(params.args.attachments)) {
464
- const attachments = params.args.attachments;
465
- const mapped = attachments.map((attachment) => {
466
- if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
467
- const record = attachment;
468
- let mappedAttachment = record;
469
- for (const key of ATTACHMENT_MEDIA_KEYS) {
470
- const value = mapMediaPath(record[key]);
471
- if (value !== record[key]) {
472
- if (mappedAttachment === record) mappedAttachment = { ...record };
473
- mappedAttachment[key] = value;
474
- }
475
- }
476
- return mappedAttachment;
477
- });
478
- if (mapped.some((attachment, index) => attachment !== attachments[index])) setMappedValue("attachments", mapped);
479
- }
480
- if (attachmentEntries > REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS) throw new Error(`Codex remote workspace upload exceeds the ${REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS}-attachment limit.`);
481
- for (const managedPath of gatewayManagedPaths) await assertGatewayManagedMediaPath(managedPath, gatewayMediaRoot);
482
- if (remotePathsByLocalPath.size === 0) return mappedArgs;
483
- const readRemoteFile = params.readRemoteFile;
484
- if (!readRemoteFile) throw new Error("Codex remote workspace file transfer requires an active app-server client.");
485
- const maxBytes = params.maxBytes ?? REMOTE_WORKSPACE_MEDIA_MAX_BYTES;
486
- const timeoutMs = params.timeoutMs ?? REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS;
487
- const deadline = Date.now() + timeoutMs;
488
- const stagedPaths = /* @__PURE__ */ new Map();
489
- let totalBytes = 0;
490
- for (const [localPath, remotePath] of remotePathsByLocalPath) {
491
- params.signal?.throwIfAborted();
492
- const remainingBytes = maxBytes - totalBytes;
493
- const remainingMs = deadline - Date.now();
494
- if (remainingMs <= 0) throw new Error("Codex remote workspace attachment batch timed out.");
495
- const response = await readRemoteFile({
496
- path: remotePath,
497
- maxBytes: remainingBytes,
498
- workspaceRoot: remoteWorkspaceRoot,
499
- signal: params.signal,
500
- timeoutMs: remainingMs
501
- });
502
- if (!response || typeof response.dataBase64 !== "string") throw new Error(`Codex remote workspace artifact returned no file data: ${remotePath}`);
503
- if (response.dataBase64.length > Math.ceil(remainingBytes / 3) * 4) throw new Error(`Codex remote workspace artifact exceeds the limit of ${remainingBytes} bytes.`);
504
- const remoteBuffer = Buffer.from(response.dataBase64, "base64");
505
- if (remoteBuffer.byteLength > remainingBytes || remoteBuffer.toString("base64") !== response.dataBase64) throw new Error(`Codex remote workspace artifact returned invalid or oversized file data: ${remotePath}`);
506
- totalBytes += remoteBuffer.byteLength;
507
- const saved = await saveMediaBuffer(remoteBuffer, void 0, "outbound", maxBytes, path.basename(remotePath));
508
- stagedPaths.set(localPath, saved.path);
509
- }
510
- return mapMessageMediaValues(mappedArgs, (value) => stagedPaths.get(value) ?? value);
511
- }
512
- async function assertGatewayManagedMediaPath(value, mediaRoot) {
513
- const opened = await (await root(mediaRoot, { symlinks: "reject" })).open(path.relative(mediaRoot, value), { symlinks: "reject" });
514
- try {
515
- if (!(await opened.handle.stat()).isFile()) throw new Error(`Codex Gateway-managed media is not a regular file: ${value}`);
516
- } finally {
517
- await opened[Symbol.asyncDispose]();
518
- }
519
- }
520
- function mapMessageMediaValues(args, mapValue) {
521
- const mapped = { ...args };
522
- for (const key of MESSAGE_MEDIA_KEYS) {
523
- const value = mapped[key];
524
- if (typeof value === "string") mapped[key] = mapValue(value);
525
- }
526
- for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
527
- const value = mapped[key];
528
- if (Array.isArray(value)) mapped[key] = value.map((entry) => typeof entry === "string" ? mapValue(entry) : entry);
529
- }
530
- if (Array.isArray(mapped.attachments)) mapped.attachments = mapped.attachments.map((attachment) => {
531
- if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
532
- const record = { ...attachment };
533
- for (const key of ATTACHMENT_MEDIA_KEYS) {
534
- const value = record[key];
535
- if (typeof value === "string") record[key] = mapValue(value);
536
- }
537
- return record;
538
- });
539
- return mapped;
540
- }
541
- //#endregion
542
- //#region extensions/codex/src/app-server/dynamic-tool-build-state.ts
543
- /** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
544
- const dynamicToolBuildState = {};
545
- //#endregion
546
- //#region extensions/codex/src/app-server/shell-dynamic-tools.ts
547
- const CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME = "node_exec";
548
- const CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME = "gateway_exec";
549
- const CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME = "gateway_process";
550
- const CODEX_EXEC_POLICY_PARAMETER_NAMES = /* @__PURE__ */ new Set([
551
- "host",
552
- "security",
553
- "ask"
554
- ]);
555
- const CODEX_NODE_EXEC_PARAMETER_NAMES = /* @__PURE__ */ new Set([
556
- "command",
557
- "workdir",
558
- "env",
559
- "timeoutSeconds",
560
- "node"
561
- ]);
562
- const PROCESS_FOLLOWUP_TEXT = "Use process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.";
563
- /** Returns true when plugin config explicitly removes any named dynamic tool. */
564
- function isCodexDynamicToolExcluded(config, names) {
565
- const normalizedNames = new Set(names.map((name) => normalizeCodexDynamicToolName(name)));
566
- return (config.codexDynamicToolsExclude ?? []).some((name) => normalizedNames.has(normalizeCodexDynamicToolName(name)));
567
- }
568
- function createExecAliasDynamicTool(execTool, params) {
569
- const pinnedNode = params.host === "node" ? params.node?.trim() : void 0;
570
- const nodeAlias = params.host === "node";
571
- const gatewayProcessAliasAvailable = params.host === "gateway" && params.processAliasAvailable;
572
- const name = nodeAlias ? CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME : CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME;
573
- const description = nodeAlias ? pinnedNode ? "Run a shell command to completion on the OpenClaw configured remote node for this session. This tool always uses OpenClaw host=node internally and follows the existing node exec approval and allowlist policy. Remote-node background follow-up is unavailable. Use Codex's native shell for local app-server work." : "Run a shell command to completion on an OpenClaw remote node. Select the node by name or id when multiple nodes are available. This tool always uses OpenClaw host=node internally and follows the existing node exec approval and allowlist policy. Remote-node background follow-up is unavailable. Use Codex's native shell for local app-server work." : "Run a shell command through OpenClaw on the Gateway host for OpenClaw-managed Gateway environment access, including Secret Store agent-readable environment values and protected egress sentinels. Native Codex shell remains preferred for ordinary local work. This tool always uses OpenClaw host=gateway internally and follows Gateway exec approval and allowlist policy.";
574
- const followupText = nodeAlias ? "Remote-node background follow-up is unavailable. Wait for the command to complete." : gatewayProcessAliasAvailable ? "Use gateway_process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up." : "Background session follow-up is unavailable because gateway_process is not exposed. Rerun without background=true and set yieldMs high enough to wait for completion.";
575
- return {
576
- ...execTool,
577
- name,
578
- description,
579
- parameters: hideExecDynamicToolParameters(execTool.parameters, !nodeAlias || Boolean(pinnedNode), nodeAlias),
580
- execute: async (toolCallId, args, signal, onUpdate) => {
581
- const result = await execTool.execute(toolCallId, pinExecDynamicToolArgs(args, params, pinnedNode), signal, onUpdate);
582
- return {
583
- ...result,
584
- content: result.content.map((item) => item.type === "text" ? Object.assign({}, item, { text: item.text.replace(PROCESS_FOLLOWUP_TEXT, followupText) }) : item)
585
- };
586
- }
587
- };
588
- }
589
- function createGatewayProcessAliasDynamicTool(processTool) {
590
- return {
591
- ...processTool,
592
- name: CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME,
593
- description: "Manage background shell sessions in the existing per-session OpenClaw process scope: list, poll, log, write, send-keys, submit, paste, kill, clear, or remove. Use for gateway_exec follow-up; use native Codex shell session handling for ordinary local work."
594
- };
595
- }
596
- function pinExecDynamicToolArgs(args, params, configuredNode) {
597
- const { host: _host, security: _security, ask: _ask, node: requestedNode, ...rest } = normalizeExecDynamicToolArgs(args);
598
- if (params.host === "gateway") return {
599
- ...rest,
600
- host: params.host,
601
- ...params.ask ? { ask: params.ask } : {}
602
- };
603
- const nodeArgs = Object.fromEntries(Object.entries(rest).filter(([name]) => CODEX_NODE_EXEC_PARAMETER_NAMES.has(name)));
604
- const node = configuredNode ?? (typeof requestedNode === "string" ? requestedNode.trim() : "");
605
- return {
606
- ...nodeArgs,
607
- host: params.host,
608
- ...node ? { node } : {}
609
- };
610
- }
611
- function normalizeExecDynamicToolArgs(args) {
612
- return args && typeof args === "object" && !Array.isArray(args) ? args : {};
613
- }
614
- function hideExecDynamicToolParameters(parameters, hideNode, nodeOnly) {
615
- if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) return parameters;
616
- const schema = parameters;
617
- const rawProperties = schema.properties;
618
- if (!rawProperties || typeof rawProperties !== "object" || Array.isArray(rawProperties)) return parameters;
619
- const includeParameter = (name) => nodeOnly ? CODEX_NODE_EXEC_PARAMETER_NAMES.has(name) && !(hideNode && name === "node") : !CODEX_EXEC_POLICY_PARAMETER_NAMES.has(normalizeCodexDynamicToolName(name)) && !(hideNode && normalizeCodexDynamicToolName(name) === "node");
620
- const nextProperties = Object.fromEntries(Object.entries(rawProperties).filter(([name]) => includeParameter(name)));
621
- const rawRequired = schema.required;
622
- const nextRequired = Array.isArray(rawRequired) ? rawRequired.filter((name) => typeof name !== "string" || includeParameter(name)) : rawRequired;
623
- return {
624
- ...schema,
625
- properties: nextProperties,
626
- ...Array.isArray(rawRequired) ? { required: nextRequired } : {}
627
- };
628
- }
629
- //#endregion
630
90
  //#region extensions/codex/src/app-server/vision-tools.ts
631
91
  /**
632
92
  * Codex's enabled native surface includes its stable view_image loader. Keep
@@ -652,6 +112,15 @@ const CODEX_NATIVE_SANDBOX_TOOL_REQUIREMENTS = [
652
112
  "apply_patch"
653
113
  ];
654
114
  const CODEX_MEMORY_FLUSH_DYNAMIC_TOOL_ALLOW = /* @__PURE__ */ new Set(["read", "write"]);
115
+ const CODEX_DISABLED_NATIVE_SHELL_DYNAMIC_TOOLS = /* @__PURE__ */ new Set([
116
+ "exec",
117
+ "process",
118
+ "sandbox_exec",
119
+ "sandbox_process",
120
+ CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME,
121
+ CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME,
122
+ CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME
123
+ ]);
655
124
  /** Keeps node filesystem and process ownership on its native exec-server. */
656
125
  function resolveCodexNodePlacementToolConstructionPlan(sandbox, nativeToolSurfaceEnabled) {
657
126
  if (!isCodexRemoteExecPlacementSandbox(sandbox) || sandbox?.backendId !== "node" || !("placementNodeId" in sandbox) || typeof sandbox.placementNodeId !== "string" || !sandbox.placementNodeId) return;
@@ -669,13 +138,6 @@ function preserveRingZeroSystemAgentTool(allTools, filteredTools) {
669
138
  if (!openclaw) return filteredTools;
670
139
  return [openclaw, ...filteredTools.filter((tool) => tool.name !== "openclaw")];
671
140
  }
672
- /** Splits sandbox and run session keys so tool calls can bind to both scopes when needed. */
673
- function resolveOpenClawCodingToolsSessionKeys(params, sandboxSessionKey) {
674
- return {
675
- sessionKey: sandboxSessionKey,
676
- runSessionKey: params.sessionKey && params.sessionKey !== sandboxSessionKey ? params.sessionKey : void 0
677
- };
678
- }
679
141
  /** Returns the canonical channel used for Codex message routing and receipts. */
680
142
  function resolveCodexMessageToolProvider(params) {
681
143
  return params.messageChannel ?? params.messageProvider;
@@ -692,17 +154,8 @@ function resolveCodexAppServerHookChannelId(params, sandboxSessionKey) {
692
154
  }
693
155
  const CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS = 1e3;
694
156
  const CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS = 500;
695
- /** Creates cheap optional timing instrumentation for the dynamic-tool hot path. */
696
- function createCodexDynamicToolBuildStageTracker(options = {}) {
697
- if (!options.enabled) return {
698
- mark() {},
699
- snapshot() {
700
- return {
701
- totalMs: 0,
702
- stages: []
703
- };
704
- }
705
- };
157
+ /** Captures bounded preparation stages before a slow turn needs diagnosis. */
158
+ function createCodexDynamicToolBuildStageTracker() {
706
159
  const startedAt = Date.now();
707
160
  let previousAt = startedAt;
708
161
  const stages = [];
@@ -726,8 +179,10 @@ function createCodexDynamicToolBuildStageTracker(options = {}) {
726
179
  };
727
180
  }
728
181
  /** Returns true when dynamic-tool construction is slow enough to warrant a warning log. */
729
- function shouldWarnCodexDynamicToolBuildStageSummary(summary) {
730
- return summary.totalMs >= CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS || summary.stages.some((stage) => stage.durationMs >= CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS);
182
+ function shouldWarnCodexDynamicToolBuildStageSummary(summary, profilerEnabled = false) {
183
+ const totalWarnMs = profilerEnabled ? CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS : 1e4;
184
+ const stageWarnMs = profilerEnabled ? CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS : 5e3;
185
+ return summary.totalMs >= totalWarnMs || summary.stages.some((stage) => stage.durationMs >= stageWarnMs);
731
186
  }
732
187
  /** Formats per-stage timings into the compact form used by Codex app-server logs. */
733
188
  function formatCodexDynamicToolBuildStageSummary(summary) {
@@ -740,6 +195,10 @@ async function buildDynamicTools(input) {
740
195
  ...params,
741
196
  disableMessageTool: false
742
197
  } : params;
198
+ const toolRunContext = buildEmbeddedAttemptToolRunContext({
199
+ ...params,
200
+ forceMessageTool: shouldForceMessageTool(messagePolicyParams)
201
+ });
743
202
  if (params.disableTools) {
744
203
  input.onWebSearchPolicyResolved?.(false);
745
204
  return [];
@@ -749,15 +208,15 @@ async function buildDynamicTools(input) {
749
208
  input.onWebSearchPolicyResolved?.(false);
750
209
  return [];
751
210
  }
752
- const toolBuildStages = createCodexDynamicToolBuildStageTracker({ enabled: input.profilerEnabled });
211
+ const toolBuildStages = createCodexDynamicToolBuildStageTracker();
753
212
  const modelHasVision = params.model.input?.includes("image") ?? false;
754
213
  const agentDir = params.agentDir ?? resolveAgentDir$1(params.config ?? {}, input.sessionAgentId);
755
214
  const injectedOpenClawCodingToolsFactory = dynamicToolBuildState.openClawCodingToolsFactory;
756
- let agentHarnessModule;
757
- const loadAgentHarnessModule = async () => agentHarnessModule ??= await import("openclaw/plugin-sdk/agent-harness");
758
- toolBuildStages.mark("load-agent-harness-tools");
759
- const sessionKeys = resolveOpenClawCodingToolsSessionKeys(params, input.sandboxSessionKey);
760
- const nativeExecutionPolicy = resolveCodexNativeExecutionPolicyForDynamicTools(input);
215
+ const nativeExecutionPolicy = resolveCodexNativeExecutionPolicyForRun(params, {
216
+ agentId: input.policyAgentId,
217
+ runtimeSessionKey: input.sandboxSessionKey,
218
+ sandbox: input.sandbox
219
+ });
761
220
  const webSearchPlan = resolveCodexWebSearchPlan({
762
221
  config: params.config,
763
222
  disableTools: params.disableTools,
@@ -765,109 +224,118 @@ async function buildDynamicTools(input) {
765
224
  nativeProviderWebSearchSupport: input.nativeProviderWebSearchSupport
766
225
  });
767
226
  const webFetchHostnameAllowlistRef = {};
768
- const buildOpenClawCodingTools = () => {
769
- const toolConstructionPlan = resolveCodexNodePlacementToolConstructionPlan(input.sandbox, input.nativeToolSurfaceEnabled);
770
- const options = {
771
- agentId: input.sessionAgentId,
772
- ...buildEmbeddedAttemptToolRunContext(params),
773
- exec: {
774
- ...params.execOverrides,
775
- ...input.sessionPermissionPolicy ? { mode: input.sessionPermissionPolicy.execMode } : {},
776
- ...resolveCodexNodeExecToolOverrides(nativeExecutionPolicy),
777
- config: params.config,
778
- elevated: params.bashElevated
779
- },
780
- sessionPermissionPolicy: input.sessionPermissionPolicy ? {
781
- mode: input.sessionPermissionPolicy.mode,
782
- root: input.sessionPermissionPolicy.root
783
- } : void 0,
784
- sandbox: input.sandbox,
785
- ...toolConstructionPlan ? { toolConstructionPlan } : {},
786
- messageProvider: resolveCodexMessageToolProvider(params),
787
- toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
788
- clientCaps: params.clientCaps,
789
- chatType: params.chatType,
790
- agentAccountId: params.agentAccountId,
791
- messageTo: params.messageTo,
792
- messageThreadId: params.messageThreadId,
793
- nativeChannelId: params.chatId,
794
- messageActionTurnCapability: params.messageActionTurnCapability,
795
- groupId: params.groupId,
796
- groupChannel: params.groupChannel,
797
- groupSpace: params.groupSpace,
798
- spawnedBy: params.spawnedBy,
799
- senderId: params.senderId,
800
- senderName: params.senderName,
801
- senderUsername: params.senderUsername,
802
- senderE164: params.senderE164,
803
- senderIsOwner: params.senderIsOwner,
804
- inputProvenance: params.inputProvenance,
805
- trustedInternalHandoff: params.trustedInternalHandoff,
806
- scheduledToolPolicy: params.scheduledToolPolicy,
807
- allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(),
808
- ...sessionKeys,
809
- sessionId: params.sessionId,
810
- runId: params.runId,
811
- approvalReviewerDeviceId: params.approvalReviewerDeviceId,
812
- agentDir,
813
- preparedModelRuntime: params.preparedModelRuntime,
814
- cwd: input.effectiveCwd ?? input.effectiveWorkspace,
815
- workspaceDir: input.effectiveWorkspace,
816
- spawnWorkspaceDir: input.effectiveCwd && input.effectiveCwd !== input.effectiveWorkspace ? input.resolvedWorkspace : resolveAttemptSpawnWorkspaceDir({
817
- sandbox: input.sandbox,
818
- resolvedWorkspace: input.resolvedWorkspace
819
- }),
227
+ const toolConstructionPlan = resolveCodexNodePlacementToolConstructionPlan(input.sandbox, input.nativeToolSurfaceEnabled);
228
+ const options = {
229
+ agentId: input.sessionAgentId,
230
+ policyAgentId: input.policyAgentId,
231
+ ...toolRunContext,
232
+ exec: {
233
+ ...params.execOverrides,
234
+ ...input.sessionPermissionPolicy ? { mode: input.sessionPermissionPolicy.execMode } : {},
235
+ ...resolveCodexNodeExecToolOverrides(nativeExecutionPolicy),
820
236
  config: params.config,
821
- githubPublicationAvailable: params.githubPublicationAvailable,
822
- authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
823
- abortSignal: input.runAbortController.signal,
824
- emitBeforeToolCallDiagnostics: false,
825
- modelProvider: params.model.provider,
826
- modelId: params.modelId,
827
- modelCompat: params.model.compat && typeof params.model.compat === "object" ? params.model.compat : void 0,
828
- modelApi: params.model.api,
829
- modelContextWindowTokens: params.model.contextWindow,
830
- delegationCapability: params.delegationCapability,
831
- modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, params.toolAuthProfileStore ?? params.authProfileStore, { workspaceDir: input.effectiveWorkspace }),
832
- suppressManagedWebSearch: false,
833
- webFetchHostnameAllowlistRef,
834
- currentChannelId: params.currentChannelId,
835
- currentMessagingTarget: params.currentMessagingTarget,
836
- hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
837
- currentThreadTs: params.currentThreadTs,
838
- currentMessageId: params.currentMessageId,
839
- replyToMode: params.replyToMode,
840
- hasRepliedRef: params.hasRepliedRef,
841
- modelHasVision,
842
- computerContextEpoch: input.computerContextEpoch,
843
- requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
844
- sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
845
- taskSuggestionDeliveryMode: params.taskSuggestionDeliveryMode,
846
- disableMessageTool: input.ignoreDisableMessageTool ? false : params.disableMessageTool,
847
- forceMessageTool: shouldForceMessageTool(messagePolicyParams),
848
- enableHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
849
- forceHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
850
- onYield: (message, acknowledgment) => {
851
- input.onYieldDetected(acknowledgment);
852
- input.onCodexAppServerEvent?.({
853
- stream: "codex_app_server.tool",
854
- data: {
855
- name: "sessions_yield",
856
- message
857
- }
858
- });
859
- },
860
- claimYieldCompletion: input.claimYieldCompletion,
861
- recordToolPrepStage: (name) => {
862
- toolBuildStages.mark(name);
863
- },
864
- onToolOutcome: params.onToolOutcome,
865
- isTurnTainted: params.isTurnTainted,
866
- allocateToolOutcomeOrdinal: params.allocateToolOutcomeOrdinal,
867
- cronCreatorToolAllowlistRef: input.cronCreatorToolAllowlistRef,
868
- cronCreatorToolAllowlistCaptureRef: input.cronCreatorToolAllowlistCaptureRef,
869
- cronCreatorAuthorityUnavailableReason: input.cronCreatorAuthorityUnavailableReason
870
- };
237
+ elevated: params.bashElevated
238
+ },
239
+ sessionPermissionPolicy: input.sessionPermissionPolicy ? {
240
+ mode: input.sessionPermissionPolicy.mode,
241
+ root: input.sessionPermissionPolicy.root
242
+ } : void 0,
243
+ sandbox: input.sandbox,
244
+ ...toolConstructionPlan ? { toolConstructionPlan } : {},
245
+ messageProvider: resolveCodexMessageToolProvider(params),
246
+ toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
247
+ ...params.onToolResult ? { questionPrompt: {
248
+ send: params.onToolResult,
249
+ ...params.messageChannel ? { messageChannel: params.messageChannel } : {}
250
+ } } : {},
251
+ clientCaps: params.clientCaps,
252
+ chatType: params.chatType,
253
+ agentAccountId: params.agentAccountId,
254
+ messageTo: params.messageTo,
255
+ messageThreadId: params.messageThreadId,
256
+ nativeChannelId: params.chatId,
257
+ messageActionTurnCapability: params.messageActionTurnCapability,
258
+ groupId: params.groupId,
259
+ groupChannel: params.groupChannel,
260
+ groupSpace: params.groupSpace,
261
+ spawnedBy: params.spawnedBy,
262
+ senderId: params.senderId,
263
+ senderName: params.senderName,
264
+ senderUsername: params.senderUsername,
265
+ senderE164: params.senderE164,
266
+ senderIsOwner: params.senderIsOwner,
267
+ inputProvenance: params.inputProvenance,
268
+ trustedInternalHandoff: params.trustedInternalHandoff,
269
+ scheduledToolPolicy: params.scheduledToolPolicy,
270
+ allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(),
271
+ sessionKey: input.sandboxSessionKey,
272
+ runSessionKey: params.sessionKey && params.sessionKey !== input.sandboxSessionKey ? params.sessionKey : void 0,
273
+ sessionId: params.sessionId,
274
+ runId: params.runId,
275
+ approvalReviewerDeviceId: params.approvalReviewerDeviceId,
276
+ agentDir,
277
+ preparedModelRuntime: params.preparedModelRuntime,
278
+ cwd: input.effectiveCwd ?? input.effectiveWorkspace,
279
+ workspaceDir: input.effectiveWorkspace,
280
+ spawnWorkspaceDir: input.effectiveCwd && input.effectiveCwd !== input.effectiveWorkspace ? input.resolvedWorkspace : resolveAttemptSpawnWorkspaceDir({
281
+ sandbox: input.sandbox,
282
+ resolvedWorkspace: input.resolvedWorkspace
283
+ }),
284
+ config: params.config,
285
+ skillsSnapshot: params.skillsSnapshot,
286
+ ...params.skillLibraryAuthoring ? { skillWorkshop: { libraryAuthoring: params.skillLibraryAuthoring } } : {},
287
+ githubPublicationAvailable: params.githubPublicationAvailable,
288
+ authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
289
+ abortSignal: input.runAbortController.signal,
290
+ emitBeforeToolCallDiagnostics: false,
291
+ modelProvider: params.model.provider,
292
+ modelId: params.modelId,
293
+ modelCompat: params.model.compat && typeof params.model.compat === "object" ? params.model.compat : void 0,
294
+ modelApi: params.model.api,
295
+ modelContextWindowTokens: params.model.contextWindow,
296
+ delegationCapability: params.delegationCapability,
297
+ modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, params.toolAuthProfileStore ?? params.authProfileStore, { workspaceDir: input.effectiveWorkspace }),
298
+ suppressManagedWebSearch: false,
299
+ webFetchHostnameAllowlistRef,
300
+ currentChannelId: params.currentChannelId,
301
+ currentMessagingTarget: params.currentMessagingTarget,
302
+ hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
303
+ currentThreadTs: params.currentThreadTs,
304
+ currentMessageId: params.currentMessageId,
305
+ replyToMode: params.replyToMode,
306
+ hasRepliedRef: params.hasRepliedRef,
307
+ modelHasVision,
308
+ computerContextEpoch: input.computerContextEpoch,
309
+ registerRunCleanup: input.registerRunCleanup,
310
+ requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
311
+ sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
312
+ taskSuggestionDeliveryMode: params.taskSuggestionDeliveryMode,
313
+ disableMessageTool: input.ignoreDisableMessageTool ? false : params.disableMessageTool,
314
+ forceMessageTool: shouldForceMessageTool(messagePolicyParams),
315
+ enableHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
316
+ forceHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
317
+ onYield: (message, acknowledgment) => {
318
+ input.onYieldDetected(acknowledgment);
319
+ input.onCodexAppServerEvent?.({
320
+ stream: "codex_app_server.tool",
321
+ data: {
322
+ name: "sessions_yield",
323
+ message
324
+ }
325
+ });
326
+ },
327
+ claimYieldCompletion: input.claimYieldCompletion,
328
+ recordToolPrepStage: (name) => {
329
+ toolBuildStages.mark(name);
330
+ },
331
+ onToolOutcome: params.onToolOutcome,
332
+ isTurnTainted: params.isTurnTainted,
333
+ allocateToolOutcomeOrdinal: params.allocateToolOutcomeOrdinal,
334
+ cronCreatorToolAllowlistRef: input.cronCreatorToolAllowlistRef,
335
+ cronCreatorToolAllowlistCaptureRef: input.cronCreatorToolAllowlistCaptureRef,
336
+ cronCreatorAuthorityUnavailableReason: input.cronCreatorAuthorityUnavailableReason
337
+ };
338
+ const buildOpenClawCodingTools = () => {
871
339
  const bindingOptions = { cwd: input.effectiveCwd ?? input.effectiveWorkspace };
872
340
  if (injectedOpenClawCodingToolsFactory) return params.hostCapabilities.bindToolSurface(injectedOpenClawCodingToolsFactory(options), bindingOptions);
873
341
  const createToolSurface = params.hostCapabilities.createToolSurface;
@@ -887,7 +355,7 @@ async function buildDynamicTools(input) {
887
355
  const readableAllTools = [...readableAllToolProjection.tools];
888
356
  const normallyProfiledTools = input.nativeToolSurfaceEnabled === false ? filterCodexDynamicToolsForDisabledNativeSurface(readableAllTools, input.pluginConfig, { preserveShell: shouldKeepOpenClawShellDynamicTools(input, nativeExecutionPolicy) }) : filterCodexDynamicTools(readableAllTools, input.pluginConfig);
889
357
  const profileFilteredTools = (input.isHostScopedToolActive?.("openclaw") ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) ? preserveRingZeroSystemAgentTool(readableAllTools, normallyProfiledTools) : normallyProfiledTools;
890
- const codexFilteredTools = addNodeShellDynamicToolsIfNeeded(addGatewayShellDynamicToolsIfAvailable(addSandboxShellDynamicToolsIfAvailable(isCodexMemoryFlushRun(params) ? filterCodexMemoryFlushDynamicTools(readableAllTools) : profileFilteredTools, readableAllTools, input), readableAllTools, input, nativeExecutionPolicy), readableAllTools, input, nativeExecutionPolicy);
358
+ const codexFilteredTools = await addNodeShellDynamicToolsIfNeeded(addGatewayShellDynamicToolsIfAvailable(addSandboxShellDynamicToolsIfAvailable(isCodexMemoryFlushRun(params) ? filterCodexMemoryFlushDynamicTools(readableAllTools) : profileFilteredTools, readableAllTools, input, nativeExecutionPolicy), readableAllTools, input, nativeExecutionPolicy), readableAllTools, input, nativeExecutionPolicy);
891
359
  toolBuildStages.mark("codex-filtering");
892
360
  const visionFilteredTools = filterCodexVisionTools(codexFilteredTools, {
893
361
  modelHasVision,
@@ -896,33 +364,33 @@ async function buildDynamicTools(input) {
896
364
  toolBuildStages.mark("vision-filtering");
897
365
  const webSearchPresent = visionFilteredTools.some((tool) => tool.name === "web_search");
898
366
  const persistentCodexWebSearchSurface = params.config?.tools?.web?.search?.enabled !== false && !(input.pluginConfig.codexDynamicToolsExclude ?? []).some((name) => normalizeCodexDynamicToolName(name) === "web_search");
899
- const webSearchPolicy = webSearchPresent || persistentCodexWebSearchSurface ? (await loadAgentHarnessModule()).resolveWebSearchToolPolicy({
900
- config: params.config,
901
- modelProvider: params.model.provider,
902
- modelId: params.modelId,
903
- agentId: input.sessionAgentId,
904
- sessionKey: input.sandboxSessionKey,
905
- sandboxToolPolicy: input.sandbox?.tools,
906
- messageProvider: resolveCodexMessageToolProvider(params),
907
- agentAccountId: params.agentAccountId,
908
- groupId: params.groupId,
909
- groupChannel: params.groupChannel,
910
- groupSpace: params.groupSpace,
911
- spawnedBy: params.spawnedBy,
912
- senderId: params.senderId,
913
- senderName: params.senderName,
914
- senderUsername: params.senderUsername,
915
- senderE164: params.senderE164,
916
- inputProvenance: params.inputProvenance,
917
- trustedInternalHandoff: params.trustedInternalHandoff,
918
- scheduledToolPolicy: params.scheduledToolPolicy
919
- }) : {
920
- allowed: false,
921
- persistentAllowed: false
922
- };
923
- const transientWebSearchRestriction = !webSearchPolicy.allowed && webSearchPolicy.persistentAllowed || isCodexMemoryFlushRun(params);
924
- input.onPersistentWebSearchPolicyResolved?.(webSearchPresent || persistentCodexWebSearchSurface && transientWebSearchRestriction && webSearchPolicy.persistentAllowed);
925
- const filteredTools = filterCodexDynamicToolsForAllowlist(visionFilteredTools, includeForcedCodexDynamicToolAllow(params.toolsAllow, messagePolicyParams));
367
+ let persistentWebSearchAllowed = webSearchPresent;
368
+ if (input.onPersistentWebSearchPolicyResolved && !webSearchPresent && persistentCodexWebSearchSurface) {
369
+ const webSearchPolicy = (await import("openclaw/plugin-sdk/agent-harness")).resolveWebSearchToolPolicy({
370
+ config: params.config,
371
+ modelProvider: params.model.provider,
372
+ modelId: params.modelId,
373
+ agentId: input.policyAgentId,
374
+ sessionKey: input.sandboxSessionKey,
375
+ sandboxToolPolicy: input.sandbox?.tools,
376
+ messageProvider: resolveCodexMessageToolProvider(params),
377
+ agentAccountId: params.agentAccountId,
378
+ groupId: params.groupId,
379
+ groupChannel: params.groupChannel,
380
+ groupSpace: params.groupSpace,
381
+ spawnedBy: params.spawnedBy,
382
+ senderId: params.senderId,
383
+ senderName: params.senderName,
384
+ senderUsername: params.senderUsername,
385
+ senderE164: params.senderE164,
386
+ inputProvenance: params.inputProvenance,
387
+ trustedInternalHandoff: params.trustedInternalHandoff,
388
+ scheduledToolPolicy: params.scheduledToolPolicy
389
+ });
390
+ persistentWebSearchAllowed = webSearchPolicy.persistentAllowed && (!webSearchPolicy.allowed || isCodexMemoryFlushRun(params));
391
+ }
392
+ input.onPersistentWebSearchPolicyResolved?.(persistentWebSearchAllowed);
393
+ const filteredTools = filterCodexDynamicToolsForAllowlist(visionFilteredTools, toolRunContext.runtimeToolAllowlist);
926
394
  toolBuildStages.mark("allowlist-filter");
927
395
  const normalizedTools = normalizeAgentRuntimeTools({
928
396
  runtimePlan: input.ignoreRuntimePlan ? void 0 : params.runtimePlan,
@@ -941,7 +409,7 @@ async function buildDynamicTools(input) {
941
409
  const webSearchAllowed = normalizedTools.some((tool) => tool.name === "web_search");
942
410
  webFetchHostnameAllowlistRef.value = webSearchAllowed ? webSearchPlan.webFetchHostnameAllowlist : void 0;
943
411
  input.onWebSearchPolicyResolved?.(webSearchAllowed);
944
- const exposedTools = webSearchPlan.suppressManagedWebSearch ? normalizedTools.filter((tool) => tool.name !== "web_search") : normalizedTools;
412
+ const exposedTools = placeDisabledNativeShellToolsInDirectNamespace(webSearchPlan.suppressManagedWebSearch ? normalizedTools.filter((tool) => tool.name !== "web_search") : normalizedTools, input.nativeToolSurfaceEnabled);
945
413
  if (preNormalizationDiagnostics.length > 0) embeddedAgentLog.warn(`codex app-server quarantined ${preNormalizationDiagnostics.length} unsupported runtime tool schema${preNormalizationDiagnostics.length === 1 ? "" : "s"} before dynamic tool registration`, {
946
414
  runId: params.runId,
947
415
  sessionId: params.sessionId,
@@ -953,7 +421,7 @@ async function buildDynamicTools(input) {
953
421
  }))
954
422
  });
955
423
  const summary = toolBuildStages.snapshot();
956
- if (shouldWarnCodexDynamicToolBuildStageSummary(summary)) {
424
+ if (shouldWarnCodexDynamicToolBuildStageSummary(summary, input.profilerEnabled)) {
957
425
  const phase = input.forceHeartbeatTool ? "registered-tools" : "runtime-tools";
958
426
  embeddedAgentLog.warn(`codex app-server dynamic tool build timings runId=${params.runId} sessionId=${params.sessionId} phase=${phase} totalMs=${summary.totalMs} stages=${formatCodexDynamicToolBuildStageSummary(summary)}`, {
959
427
  runId: params.runId,
@@ -983,49 +451,39 @@ function addGatewayShellDynamicToolsIfAvailable(filteredTools, allTools, input,
983
451
  if (!execTool || execExcluded || existingNames.has("gateway_exec")) return filteredTools;
984
452
  const processExcluded = isCodexDynamicToolExcluded(input.pluginConfig, ["process", CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME]);
985
453
  const processAliasAvailable = Boolean(processTool && !processExcluded && !existingNames.has("gateway_process"));
986
- const toolsToAppend = [createExecAliasDynamicTool(execTool, {
987
- host: "gateway",
454
+ const createProjection = resolveCodexScheduledToolProjectionFactory(input.params.hostCapabilities);
455
+ if (!createProjection) return filteredTools;
456
+ const toolsToAppend = [createGatewayExecProjection(createProjection, execTool, {
988
457
  processAliasAvailable,
989
458
  ...input.sessionPermissionPolicy?.mode === "guarded" ? { ask: "always" } : {}
990
459
  })];
991
- if (processAliasAvailable && processTool) toolsToAppend.push(createGatewayProcessAliasDynamicTool(processTool));
460
+ if (processAliasAvailable && processTool) toolsToAppend.push(createGatewayProcessProjection(createProjection, processTool));
992
461
  return [...filteredTools, ...toolsToAppend];
993
462
  }
994
- /** Preserves delivery-critical tools when a narrow allowlist would otherwise hide them. */
995
- function includeForcedCodexDynamicToolAllow(toolsAllow, params) {
996
- if (toolsAllow === void 0 || hasWildcardCodexToolsAllow(toolsAllow)) return toolsAllow;
997
- const forcedToolNames = shouldForceMessageTool(params) ? ["message"] : [];
998
- if (forcedToolNames.length === 0) return toolsAllow;
999
- if (toolsAllow.length === 0) return forcedToolNames;
1000
- const normalized = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)));
1001
- const missingToolNames = forcedToolNames.filter((toolName) => !normalized.has(normalizeCodexDynamicToolName(toolName)));
1002
- return missingToolNames.length === 0 ? toolsAllow : [...toolsAllow, ...missingToolNames];
1003
- }
1004
463
  /** Decides whether Codex native code mode can own shell/file tools for this turn. */
1005
464
  function shouldEnableCodexAppServerNativeToolSurface(params, sandbox, options = {}) {
1006
465
  if (params.pluginHarnessToolPolicyRestricted === true) return false;
1007
466
  if (isCodexMemoryFlushRun(params)) return false;
1008
467
  if (params.disableTools) return false;
1009
- if (isCodexNativeExecutionBlockedByNodeExecHost(params, {
468
+ if (!resolveCodexNativeExecutionPolicyForRun(params, {
1010
469
  agentId: options.agentId,
1011
470
  runtimeSessionKey: options.runtimeSessionKey,
1012
471
  sandbox
1013
- })) return false;
1014
- const toolsAllow = includeForcedCodexDynamicToolAllow(params.toolsAllow, params);
472
+ }).nativeToolSurfaceAllowed) return false;
473
+ const toolsAllow = params.toolsAllow;
1015
474
  if (toolsAllow === void 0) return canCodexAppServerNativeToolSurfaceHonorSandbox(sandbox, options);
1016
475
  return hasWildcardCodexToolsAllow(toolsAllow) && canCodexAppServerNativeToolSurfaceHonorSandbox(sandbox, options);
1017
476
  }
1018
- /** Returns true when OpenClaw policy requires the Node-owned exec/process tools instead. */
1019
- function isCodexNativeExecutionBlockedByNodeExecHost(params, options = {}) {
1020
- return !resolveCodexNativeExecutionPolicy({
477
+ function resolveCodexNativeExecutionPolicyForRun(params, options = {}) {
478
+ return resolveCodexNativeExecutionPolicy({
1021
479
  config: params.config,
1022
480
  sessionKey: resolveCodexRuntimePolicySessionKey(params, options.runtimeSessionKey),
1023
481
  sessionId: params.sessionId,
1024
482
  agentId: options.agentId,
1025
483
  execOverrides: params.execOverrides,
1026
- sandboxAvailable: options.sandbox?.enabled,
484
+ sandboxAvailable: options.sandbox === null ? false : options.sandbox?.enabled,
1027
485
  readRuntimeSessionEntry: true
1028
- }).nativeToolSurfaceAllowed;
486
+ });
1029
487
  }
1030
488
  function resolveCodexRuntimePolicySessionKey(params, runtimeSessionKey) {
1031
489
  return runtimeSessionKey?.trim() || params.sandboxSessionKey?.trim() || params.sessionKey?.trim() || params.sessionId;
@@ -1088,8 +546,8 @@ function disableCodexPluginThreadConfig(pluginConfig) {
1088
546
  };
1089
547
  }
1090
548
  /** Adds sandbox_exec/process aliases when native Code Mode cannot directly honor the sandbox. */
1091
- function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input) {
1092
- if (!shouldExposeSandboxExecDynamicTool(input) || isSandboxShellDynamicToolExcluded(input.pluginConfig)) return filteredTools;
549
+ function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input, executionPolicy) {
550
+ if (isCodexMemoryFlushRun(input.params) || !executionPolicy.nativeToolSurfaceAllowed || !input.sandbox?.enabled || !input.sandbox.backendId.trim() || input.nativeToolSurfaceEnabled !== false || isSandboxShellDynamicToolExcluded(input.pluginConfig)) return filteredTools;
1093
551
  const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
1094
552
  const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
1095
553
  if (!execTool || !processTool) return filteredTools;
@@ -1116,16 +574,6 @@ function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input)
1116
574
  sandboxProcessTool
1117
575
  ];
1118
576
  }
1119
- function shouldExposeSandboxExecDynamicTool(input) {
1120
- if (isCodexMemoryFlushRun(input.params)) return false;
1121
- if (isCodexNativeExecutionBlockedByNodeExecHost(input.params, {
1122
- agentId: input.sessionAgentId,
1123
- runtimeSessionKey: input.sandboxSessionKey,
1124
- sandbox: input.sandbox
1125
- })) return false;
1126
- const backendId = input.sandbox?.enabled ? input.sandbox.backendId.trim().toLowerCase() : "";
1127
- return Boolean(backendId && input.nativeToolSurfaceEnabled === false);
1128
- }
1129
577
  function isSandboxShellDynamicToolExcluded(config) {
1130
578
  return isCodexDynamicToolExcluded(config, [
1131
579
  "exec",
@@ -1134,32 +582,24 @@ function isSandboxShellDynamicToolExcluded(config) {
1134
582
  "sandbox_process"
1135
583
  ]);
1136
584
  }
1137
- function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
585
+ async function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
1138
586
  if (isCodexMemoryFlushRun(input.params)) return filteredTools;
1139
587
  const nodeExecIsDefault = nodePolicy.effectiveExecHost === "node";
1140
588
  const nodeExecAvailableFromAuto = nodePolicy.requestedExecHost === "auto" && nodePolicy.effectiveExecHost === "gateway";
1141
589
  if (!nodeExecIsDefault && !nodeExecAvailableFromAuto) return filteredTools;
1142
590
  const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
1143
- if (!execTool) return filteredTools;
1144
- if (!isCodexDynamicToolExcluded(input.pluginConfig, ["exec", "node_exec"]) && !filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === "node_exec")) return [...filteredTools, createExecAliasDynamicTool(execTool, {
1145
- host: "node",
1146
- node: nodePolicy.node
1147
- })];
1148
- return filteredTools;
591
+ if (!execTool || isCodexDynamicToolExcluded(input.pluginConfig, ["exec", "node_exec"]) || filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === "node_exec")) return filteredTools;
592
+ const nodeExec = await createNodeExecAliasDynamicTool(execTool, nodePolicy.node, input.runAbortController.signal, input.nodeExecAvailability);
593
+ return nodeExec ? [...filteredTools, nodeExec] : filteredTools;
1149
594
  }
1150
595
  function shouldKeepOpenClawShellDynamicTools(input, nodePolicy) {
1151
596
  return !isCodexMemoryFlushRun(input.params) && input.nativeToolSurfaceEnabled === false && input.sandbox?.enabled !== true && nodePolicy.effectiveExecHost !== "node";
1152
597
  }
1153
- function resolveCodexNativeExecutionPolicyForDynamicTools(input) {
1154
- return resolveCodexNativeExecutionPolicy({
1155
- config: input.params.config,
1156
- sessionKey: resolveCodexRuntimePolicySessionKey(input.params, input.sandboxSessionKey),
1157
- sessionId: input.params.sessionId,
1158
- agentId: input.sessionAgentId,
1159
- execOverrides: input.params.execOverrides,
1160
- sandboxAvailable: input.sandbox?.enabled,
1161
- readRuntimeSessionEntry: true
1162
- });
598
+ /** Keeps replacement shell tools direct even when model metadata mandates Codex Code Mode. */
599
+ function placeDisabledNativeShellToolsInDirectNamespace(tools, nativeToolSurfaceEnabled) {
600
+ if (nativeToolSurfaceEnabled !== false) return tools;
601
+ for (const tool of tools) if (CODEX_DISABLED_NATIVE_SHELL_DYNAMIC_TOOLS.has(normalizeCodexDynamicToolName(tool.name))) tool.catalogMode = "direct-only";
602
+ return tools;
1163
603
  }
1164
604
  /** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
1165
605
  function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
@@ -1172,19 +612,526 @@ function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
1172
612
  return allowSet.has(normalized) || normalized === "sandbox_exec" && allowSet.has("exec") || normalized === "sandbox_process" && (allowSet.has("exec") || allowSet.has("process")) || normalized === "gateway_exec" && allowSet.has("exec") || normalized === "gateway_process" && (allowSet.has("exec") || allowSet.has("process")) || normalized === "node_exec" && allowSet.has("exec");
1173
613
  });
1174
614
  }
1175
- /** Detects the wildcard allowlist marker after Codex tool-name normalization. */
1176
- function hasWildcardCodexToolsAllow(toolsAllow) {
1177
- return toolsAllow.some((name) => normalizeCodexDynamicToolName(name) === "*");
615
+ /** Detects the wildcard allowlist marker after Codex tool-name normalization. */
616
+ function hasWildcardCodexToolsAllow(toolsAllow) {
617
+ return toolsAllow.some((name) => normalizeCodexDynamicToolName(name) === "*");
618
+ }
619
+ /** Forces message delivery through the message tool when the source channel requires it. */
620
+ function shouldForceMessageTool(params) {
621
+ return params.disableMessageTool !== true && params.sourceReplyDeliveryMode === "message_tool_only";
622
+ }
623
+ //#endregion
624
+ //#region extensions/codex/src/app-server/event-projector-native-tool-lifecycle.ts
625
+ function isMcpToolCallItemNotification(method, params) {
626
+ return (method === "item/started" || method === "item/completed") && isJsonObject(params.item) && params.item.type === "mcpToolCall";
627
+ }
628
+ /** Owns native item lifetimes, outcome order, presentation, and approval correlation. */
629
+ var CodexNativeToolLifecycleProjector = class {
630
+ constructor(context, threadId, turnId, options = {}) {
631
+ this.context = context;
632
+ this.threadId = threadId;
633
+ this.turnId = turnId;
634
+ this.options = options;
635
+ this.terminalPresentationClearedItemIds = /* @__PURE__ */ new Set();
636
+ this.nativeToolOutcomeOrdinals = /* @__PURE__ */ new Map();
637
+ this.startedAtByItem = /* @__PURE__ */ new Map();
638
+ this.activeItems = /* @__PURE__ */ new Map();
639
+ this.webSearchCompletionByItem = /* @__PURE__ */ new Map();
640
+ this.completedItemIds = /* @__PURE__ */ new Set();
641
+ this.approvalFailureDispositionByItem = /* @__PURE__ */ new Map();
642
+ this.preToolUseFailureByItem = /* @__PURE__ */ new Map();
643
+ this.finalized = false;
644
+ this.turnCompleted = false;
645
+ this.pendingMcpNotifications = 0;
646
+ }
647
+ recordNativeToolOutcome(item) {
648
+ if (!item || this.nativeToolOutcomeOrdinals.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
649
+ const ordinal = this.context.allocateToolOutcomeOrdinal?.(item.id);
650
+ if (ordinal !== void 0) this.nativeToolOutcomeOrdinals.set(item.id, ordinal);
651
+ }
652
+ clearTerminalPresentationForNativeItem(item) {
653
+ if (!item || this.terminalPresentationClearedItemIds.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
654
+ const toolCallOrdinal = this.nativeToolOutcomeOrdinals.get(item.id);
655
+ this.terminalPresentationClearedItemIds.add(item.id);
656
+ this.context.onToolOutcome?.({
657
+ toolName: itemName(item) ?? item.type,
658
+ argsHash: "",
659
+ resultHash: "",
660
+ ...toolCallOrdinal !== void 0 ? { toolCallOrdinal } : {},
661
+ terminalPresentation: void 0,
662
+ presentationOnly: true
663
+ });
664
+ }
665
+ getActiveMcpToolCall(serverName) {
666
+ if (this.finalized || this.turnCompleted || this.pendingMcpNotifications > 0 || this.options.runAbortSignal?.aborted) return;
667
+ let candidate;
668
+ for (const { mcpToolCall } of this.activeItems.values()) {
669
+ if (mcpToolCall?.server !== serverName) continue;
670
+ if (candidate) return;
671
+ candidate = mcpToolCall;
672
+ }
673
+ if (!candidate || candidate.status !== "inProgress" || typeof candidate.server !== "string" || !candidate.server.trim() || typeof candidate.tool !== "string" || !candidate.tool.trim() || candidate.arguments === void 0 || candidate.appContext != null || candidate.pluginId != null) return;
674
+ return {
675
+ id: candidate.id,
676
+ server: candidate.server,
677
+ tool: candidate.tool,
678
+ arguments: candidate.arguments
679
+ };
680
+ }
681
+ recordMcpToolCallReceipt(notification) {
682
+ const params = isJsonObject(notification.params) ? notification.params : void 0;
683
+ if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
684
+ if (isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications += 1;
685
+ else if (notification.method === "turn/completed" && readCodexTurn(params.turn)?.id === this.turnId) this.turnCompleted = true;
686
+ }
687
+ handleNotification(notification) {
688
+ const params = isJsonObject(notification.params) ? notification.params : void 0;
689
+ if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
690
+ if (this.pendingMcpNotifications > 0 && isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications -= 1;
691
+ if (notification.method === "turn/completed") {
692
+ const turn = readCodexTurn(params.turn);
693
+ if (!turn || turn.id !== this.turnId) return;
694
+ this.turnCompleted = true;
695
+ for (const item of turn.items ?? []) this.recordSnapshotItem(item);
696
+ return;
697
+ }
698
+ if (notification.method === "rawResponseItem/completed") {
699
+ const item = isJsonObject(params.item) ? params.item : void 0;
700
+ if (item) this.recordRawWebSearchResult(item);
701
+ return;
702
+ }
703
+ if (notification.method !== "item/started" && notification.method !== "item/completed") return;
704
+ const item = readItem(params.item);
705
+ if (!item) return;
706
+ this.recordItem({
707
+ phase: notification.method === "item/started" ? "start" : "result",
708
+ item,
709
+ sourceTimestampMs: asDateTimestampMs(notification.method === "item/started" ? params.startedAtMs : params.completedAtMs)
710
+ });
711
+ }
712
+ recordItem(params) {
713
+ const toolName = auditNativeToolName(params.item) ?? (params.item.type === "mcpToolCall" ? params.item.type : void 0);
714
+ if (!toolName || this.completedItemIds.has(params.item.id)) return;
715
+ if (params.phase === "start") {
716
+ this.recordStarted(params.item.id, toolName, auditNativeToolUnfinishedStatus(params.item), params.sourceTimestampMs, params.item.type === "mcpToolCall" ? params.item : void 0);
717
+ return;
718
+ }
719
+ if (params.item.type === "webSearch") {
720
+ this.webSearchCompletionByItem.set(params.item.id, {
721
+ runWasAborted: this.options.runAbortSignal?.aborted === true,
722
+ sourceTimestampMs: params.sourceTimestampMs
723
+ });
724
+ return;
725
+ }
726
+ const itemDurationMs = typeof params.item.durationMs === "number" ? params.item.durationMs : void 0;
727
+ this.recordTerminal(params.item.id, toolName, auditNativeToolTerminalStatus(params.item), {
728
+ itemDurationMs,
729
+ sourceTimestampMs: params.sourceTimestampMs
730
+ });
731
+ }
732
+ recordApprovalFailureDisposition(toolCallId, disposition) {
733
+ if (!this.completedItemIds.has(toolCallId)) this.approvalFailureDispositionByItem.set(toolCallId, disposition);
734
+ }
735
+ recordPreToolUseFailure(failure, runWasAborted = this.options.runAbortSignal?.aborted === true) {
736
+ if (this.completedItemIds.has(failure.toolCallId)) return;
737
+ const record = {
738
+ failure,
739
+ terminalReason: runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : failure.disposition
740
+ };
741
+ if (this.finalized) {
742
+ this.completedItemIds.add(failure.toolCallId);
743
+ this.emitPreToolUseFailure(record, failure.toolName, failure.durationMs);
744
+ return;
745
+ }
746
+ this.preToolUseFailureByItem.set(failure.toolCallId, record);
747
+ }
748
+ recordRawWebSearchResult(item) {
749
+ if (readStringField(item, "type") !== "web_search_call") return;
750
+ const toolCallId = readStringField(item, "id");
751
+ if (!toolCallId || this.completedItemIds.has(toolCallId)) return;
752
+ const toolName = "web_search";
753
+ this.recordStarted(toolCallId, toolName, "unknown");
754
+ const rawStatus = readStringField(item, "status");
755
+ if (rawStatus === "in_progress" || rawStatus === "running") return;
756
+ const status = rawStatus === "completed" ? "completed" : rawStatus === "cancelled" ? "cancelled" : rawStatus === "failed" || rawStatus === "error" || rawStatus === "incomplete" ? "failed" : "unknown";
757
+ this.recordTerminal(toolCallId, toolName, status, { sourceTimestampMs: this.webSearchCompletionByItem.get(toolCallId)?.sourceTimestampMs });
758
+ }
759
+ recordTerminal(toolCallId, toolName, status, options = {}) {
760
+ const runWasAborted = options.runWasAborted ?? this.options.runAbortSignal?.aborted === true;
761
+ const preToolUseFailure = this.preToolUseFailureByItem.get(toolCallId);
762
+ this.preToolUseFailureByItem.delete(toolCallId);
763
+ const approvalFailureDisposition = this.approvalFailureDispositionByItem.get(toolCallId);
764
+ this.approvalFailureDispositionByItem.delete(toolCallId);
765
+ this.completedItemIds.add(toolCallId);
766
+ this.activeItems.delete(toolCallId);
767
+ this.webSearchCompletionByItem.delete(toolCallId);
768
+ const startedAt = this.startedAtByItem.get(toolCallId);
769
+ this.startedAtByItem.delete(toolCallId);
770
+ const endedAt = options.sourceTimestampMs ?? Date.now();
771
+ const durationMs = options.itemDurationMs ?? (startedAt === void 0 ? 0 : Math.max(0, endedAt - startedAt));
772
+ if (preToolUseFailure) {
773
+ this.emitPreToolUseFailure(preToolUseFailure, toolName, durationMs, options.sourceTimestampMs);
774
+ return;
775
+ }
776
+ const terminalEvent = approvalFailureDisposition ? {
777
+ type: "tool.execution.error",
778
+ durationMs,
779
+ errorCategory: "codex_native_tool_approval",
780
+ terminalReason: approvalFailureDisposition
781
+ } : status === "blocked" ? {
782
+ type: "tool.execution.blocked",
783
+ reason: "codex_native_tool_blocked",
784
+ deniedReason: "codex_native_tool_blocked"
785
+ } : status === "failed" || status === "cancelled" || status === "unknown" ? {
786
+ type: "tool.execution.error",
787
+ durationMs,
788
+ errorCategory: status === "unknown" ? "codex_native_tool_outcome_unknown" : status === "cancelled" ? "aborted" : "codex_native_tool_error",
789
+ ...status === "unknown" ? { errorCode: "tool_outcome_unknown" } : {},
790
+ terminalReason: status === "unknown" ? "failed" : runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : status === "cancelled" ? "cancelled" : "failed"
791
+ } : {
792
+ type: "tool.execution.completed",
793
+ durationMs
794
+ };
795
+ emitTrustedDiagnosticEvent({
796
+ ...this.buildBase(toolCallId, toolName),
797
+ ...terminalEvent,
798
+ ...options.sourceTimestampMs !== void 0 ? { sourceTimestampMs: options.sourceTimestampMs } : {}
799
+ });
800
+ }
801
+ finalizeActive(runWasAborted = this.options.runAbortSignal?.aborted === true) {
802
+ this.finalized = true;
803
+ for (const [toolCallId, { toolName, unfinishedStatus }] of this.activeItems) {
804
+ const webSearchCompletion = this.webSearchCompletionByItem.get(toolCallId);
805
+ const itemRunWasAborted = webSearchCompletion ? webSearchCompletion.runWasAborted : runWasAborted;
806
+ this.recordTerminal(toolCallId, toolName, unfinishedStatus, {
807
+ runWasAborted: itemRunWasAborted,
808
+ sourceTimestampMs: webSearchCompletion?.sourceTimestampMs
809
+ });
810
+ }
811
+ for (const [toolCallId, record] of this.preToolUseFailureByItem) if (!this.completedItemIds.has(toolCallId)) this.recordTerminal(toolCallId, record.failure.toolName, "failed", { itemDurationMs: record.failure.durationMs });
812
+ this.activeItems.clear();
813
+ this.webSearchCompletionByItem.clear();
814
+ this.approvalFailureDispositionByItem.clear();
815
+ this.preToolUseFailureByItem.clear();
816
+ }
817
+ emitPreToolUseFailure(record, toolName, durationMs, sourceTimestampMs) {
818
+ emitCodexNativePreToolUseFailureDiagnostic({
819
+ agentId: this.context.agentId,
820
+ sessionId: this.context.sessionId,
821
+ sessionKey: this.context.sessionKey,
822
+ runId: this.context.runId,
823
+ failure: {
824
+ ...record.failure,
825
+ toolName,
826
+ durationMs
827
+ },
828
+ terminalReason: record.terminalReason,
829
+ sourceTimestampMs
830
+ });
831
+ }
832
+ recordSnapshotItem(item) {
833
+ if (!auditNativeToolName(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
834
+ const toolName = auditNativeToolName(item);
835
+ if (!toolName) return;
836
+ this.recordStarted(item.id, toolName, auditNativeToolUnfinishedStatus(item));
837
+ this.recordItem({
838
+ phase: "result",
839
+ item
840
+ });
841
+ }
842
+ recordStarted(toolCallId, toolName, unfinishedStatus, sourceTimestampMs, mcpToolCall) {
843
+ if (this.activeItems.has(toolCallId)) return;
844
+ this.startedAtByItem.set(toolCallId, sourceTimestampMs ?? Date.now());
845
+ this.activeItems.set(toolCallId, {
846
+ toolName,
847
+ unfinishedStatus,
848
+ mcpToolCall
849
+ });
850
+ emitTrustedDiagnosticEvent({
851
+ type: "tool.execution.started",
852
+ ...this.buildBase(toolCallId, toolName),
853
+ ...sourceTimestampMs !== void 0 ? { sourceTimestampMs } : {}
854
+ });
855
+ }
856
+ buildBase(toolCallId, toolName) {
857
+ return {
858
+ agentId: this.context.agentId,
859
+ runId: this.context.runId,
860
+ sessionId: this.context.sessionId,
861
+ sessionKey: this.context.sessionKey,
862
+ toolName,
863
+ toolCallId
864
+ };
865
+ }
866
+ };
867
+ //#endregion
868
+ //#region extensions/codex/src/app-server/remote-workspace-media.ts
869
+ const REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS = 6e4;
870
+ const REMOTE_WORKSPACE_MEDIA_MAX_BYTES = 67108864;
871
+ const REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS = 16;
872
+ const CODEX_REMOTE_MEDIA_CHUNK_BYTES = 524288;
873
+ const CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES = 1048576;
874
+ const CODEX_BOUNDED_REMOTE_FILE_READER = [
875
+ "try{",
876
+ "const fs=require(\"node:fs\");",
877
+ "const path=require(\"node:path\");",
878
+ "const file=process.argv[1];",
879
+ "const max=Number(process.argv[2]);",
880
+ "const offset=Number(process.argv[3]);",
881
+ "const chunk=Number(process.argv[4]);",
882
+ "const workspace=process.argv[5];",
883
+ "if(!Number.isSafeInteger(max)||max<0)throw Error(\"invalid media byte limit\");",
884
+ "if(!Number.isSafeInteger(offset)||offset<0||!Number.isSafeInteger(chunk)||chunk<=0)throw Error(\"invalid media chunk\");",
885
+ "if(fs.lstatSync(file).isSymbolicLink())throw Error(\"symbolic links are not allowed\");",
886
+ "const noFollow=fs.constants.O_NOFOLLOW??0;",
887
+ "const fd=fs.openSync(file,fs.constants.O_RDONLY|noFollow);",
888
+ "try{",
889
+ "const before=fs.fstatSync(fd);",
890
+ "if(!before.isFile())throw Error(\"not a regular file\");",
891
+ "if(workspace){",
892
+ "const descriptor=process.platform===\"linux\"?fs.realpathSync(`/proc/self/fd/${fd}`):fs.realpathSync(file);",
893
+ "const relative=path.relative(fs.realpathSync(workspace),descriptor);",
894
+ "if(!relative||relative===\"..\"||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative))throw Error(\"file escapes remote workspace\");",
895
+ "const verified=fs.statSync(descriptor);",
896
+ "if(verified.dev!==before.dev||verified.ino!==before.ino)throw Error(\"file changed while being opened\");",
897
+ "}",
898
+ "if(before.size>max)throw Error(`file exceeds limit of ${max} bytes`);",
899
+ "if(offset>before.size)throw Error(\"file changed while being read\");",
900
+ "const expected=Math.min(chunk,before.size-offset);",
901
+ "const buffer=Buffer.allocUnsafe(expected);",
902
+ "let total=0;",
903
+ "while(total<buffer.length){",
904
+ "const count=fs.readSync(fd,buffer,total,buffer.length-total,offset+total);",
905
+ "if(count===0)break;total+=count;",
906
+ "}",
907
+ "if(total!==expected)throw Error(\"file changed while being read\");",
908
+ "const after=fs.fstatSync(fd);",
909
+ "const revision=stat=>[stat.dev,stat.ino,stat.size,stat.mtimeMs,stat.ctimeMs].join(\":\");",
910
+ "if(!after.isFile()||revision(after)!==revision(before))throw Error(\"file changed while being read\");",
911
+ "process.stdout.write(JSON.stringify({dataBase64:buffer.toString(\"base64\"),size:before.size,revision:revision(before)}));",
912
+ "}finally{fs.closeSync(fd)}",
913
+ "}catch(error){process.stderr.write(error instanceof Error?error.message:String(error));process.exitCode=1}"
914
+ ].join("");
915
+ const MESSAGE_MEDIA_KEYS = [
916
+ "media",
917
+ "mediaUrl",
918
+ "media_url",
919
+ "path",
920
+ "filePath",
921
+ "fileUrl",
922
+ "imageUrl",
923
+ "image_url"
924
+ ];
925
+ const MESSAGE_MEDIA_ARRAY_KEYS = [
926
+ "mediaUrls",
927
+ "media_urls",
928
+ "imageUrls",
929
+ "image_urls"
930
+ ];
931
+ const ATTACHMENT_MEDIA_KEYS = [
932
+ "media",
933
+ "mediaUrl",
934
+ "path",
935
+ "filePath",
936
+ "fileUrl",
937
+ "url"
938
+ ];
939
+ /** Reads actual remote bytes with a cap enforced by Codex before transport. */
940
+ async function readBoundedCodexRemoteWorkspaceFile(params) {
941
+ if (!Number.isSafeInteger(params.maxBytes) || params.maxBytes < 0) throw new Error("Codex remote workspace upload requires a valid media byte limit.");
942
+ params.signal?.throwIfAborted();
943
+ const chunks = [];
944
+ let offset = 0;
945
+ let expectedSize;
946
+ let expectedRevision;
947
+ const startedAt = Date.now();
948
+ do {
949
+ params.signal?.throwIfAborted();
950
+ const timeoutMs = params.timeoutMs === void 0 ? void 0 : params.timeoutMs - (Date.now() - startedAt);
951
+ if (timeoutMs !== void 0 && timeoutMs <= 0) throw new Error("Codex remote workspace file transfer timed out.");
952
+ let response;
953
+ try {
954
+ response = await params.client.request("command/exec", {
955
+ command: [
956
+ "node",
957
+ "-e",
958
+ CODEX_BOUNDED_REMOTE_FILE_READER,
959
+ "--",
960
+ params.path,
961
+ String(params.maxBytes),
962
+ String(offset),
963
+ String(CODEX_REMOTE_MEDIA_CHUNK_BYTES),
964
+ ...params.workspaceRoot ? [params.workspaceRoot] : []
965
+ ],
966
+ env: {
967
+ NODE_OPTIONS: null,
968
+ NODE_PATH: null
969
+ },
970
+ ...timeoutMs === void 0 ? {} : { timeoutMs }
971
+ }, {
972
+ signal: params.signal,
973
+ timeoutMs
974
+ });
975
+ } catch (error) {
976
+ if (error instanceof Error && /failed to spawn|executable.*not found|\bENOENT\b/iu.test(error.message)) throw new Error("Codex remote workspace file transfer requires Node.js on the remote app-server host.", { cause: error });
977
+ throw error;
978
+ }
979
+ if (!response || response.exitCode !== 0) {
980
+ const detail = typeof response?.stderr === "string" ? response.stderr.trim() : "";
981
+ throw new Error(`Codex remote workspace artifact could not be read: ${params.path}${detail ? `: ${detail}` : ""}`);
982
+ }
983
+ if (typeof response.stdout !== "string" || response.stdout.length > CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES) throw new Error("Codex remote workspace artifact exceeded the native command output cap.");
984
+ let payload;
985
+ try {
986
+ payload = JSON.parse(response.stdout);
987
+ } catch {
988
+ throw new Error("Codex remote workspace artifact returned invalid chunk data.");
989
+ }
990
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
991
+ const chunk = payload;
992
+ if (typeof chunk.dataBase64 !== "string" || !Number.isSafeInteger(chunk.size) || chunk.size < 0 || chunk.size > params.maxBytes || typeof chunk.revision !== "string" || !chunk.revision) throw new Error("Codex remote workspace artifact returned invalid or oversized chunk data.");
993
+ if (expectedSize === void 0) {
994
+ expectedSize = chunk.size;
995
+ expectedRevision = chunk.revision;
996
+ }
997
+ if (chunk.size !== expectedSize || chunk.revision !== expectedRevision) throw new Error("Codex remote workspace artifact changed during chunked transfer.");
998
+ const remainingBytes = expectedSize - offset;
999
+ const expectedChunkBytes = Math.min(CODEX_REMOTE_MEDIA_CHUNK_BYTES, remainingBytes);
1000
+ if (chunk.dataBase64.length > Math.ceil(expectedChunkBytes / 3) * 4) throw new Error("Codex remote workspace artifact returned oversized chunk data.");
1001
+ const buffer = Buffer.from(chunk.dataBase64, "base64");
1002
+ if (buffer.byteLength !== expectedChunkBytes || buffer.toString("base64") !== chunk.dataBase64) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
1003
+ chunks.push(buffer);
1004
+ offset += buffer.byteLength;
1005
+ } while (offset < (expectedSize ?? 0));
1006
+ return { dataBase64: Buffer.concat(chunks, offset).toString("base64") };
1007
+ }
1008
+ /** Stages authoritative bounded remote bytes into immutable Gateway-owned media. */
1009
+ async function prepareCodexRemoteWorkspaceMessageMedia(params) {
1010
+ const { localWorkspaceRoot, remoteWorkspaceRoot } = params;
1011
+ if (!localWorkspaceRoot || !remoteWorkspaceRoot) return params.args;
1012
+ const remotePathsByLocalPath = /* @__PURE__ */ new Map();
1013
+ const gatewayManagedPaths = /* @__PURE__ */ new Set();
1014
+ const gatewayMediaRoot = getMediaDir();
1015
+ let attachmentEntries = 0;
1016
+ const mapMediaPath = (value) => {
1017
+ if (typeof value !== "string") return value;
1018
+ if (path.isAbsolute(value) && isPathStrictlyInside(gatewayMediaRoot, value)) {
1019
+ attachmentEntries += 1;
1020
+ gatewayManagedPaths.add(value);
1021
+ return value;
1022
+ }
1023
+ const mapped = mapCodexAppServerLocalWorkspacePath({
1024
+ value,
1025
+ localWorkspaceRoot,
1026
+ remoteWorkspaceRoot
1027
+ });
1028
+ if (value.trim() && !isCodexPassThroughMediaSource(value)) {
1029
+ attachmentEntries += 1;
1030
+ remotePathsByLocalPath.set(mapped, mapCodexAppServerRemoteWorkspacePath({
1031
+ value: mapped,
1032
+ localWorkspaceRoot,
1033
+ remoteWorkspaceRoot
1034
+ }));
1035
+ }
1036
+ return mapped;
1037
+ };
1038
+ let mappedArgs = params.args;
1039
+ const setMappedValue = (key, value) => {
1040
+ if (value === params.args[key]) return;
1041
+ if (mappedArgs === params.args) mappedArgs = { ...params.args };
1042
+ mappedArgs[key] = value;
1043
+ };
1044
+ for (const key of MESSAGE_MEDIA_KEYS) setMappedValue(key, mapMediaPath(params.args[key]));
1045
+ for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
1046
+ const value = params.args[key];
1047
+ if (Array.isArray(value)) {
1048
+ const mapped = value.map(mapMediaPath);
1049
+ if (mapped.some((entry, index) => entry !== value[index])) setMappedValue(key, mapped);
1050
+ }
1051
+ }
1052
+ if (Array.isArray(params.args.attachments)) {
1053
+ const attachments = params.args.attachments;
1054
+ const mapped = attachments.map((attachment) => {
1055
+ if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
1056
+ const record = attachment;
1057
+ let mappedAttachment = record;
1058
+ for (const key of ATTACHMENT_MEDIA_KEYS) {
1059
+ const value = mapMediaPath(record[key]);
1060
+ if (value !== record[key]) {
1061
+ if (mappedAttachment === record) mappedAttachment = { ...record };
1062
+ mappedAttachment[key] = value;
1063
+ }
1064
+ }
1065
+ return mappedAttachment;
1066
+ });
1067
+ if (mapped.some((attachment, index) => attachment !== attachments[index])) setMappedValue("attachments", mapped);
1068
+ }
1069
+ if (attachmentEntries > REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS) throw new Error(`Codex remote workspace upload exceeds the ${REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS}-attachment limit.`);
1070
+ for (const managedPath of gatewayManagedPaths) await assertGatewayManagedMediaPath(managedPath, gatewayMediaRoot);
1071
+ if (remotePathsByLocalPath.size === 0) return mappedArgs;
1072
+ const readRemoteFile = params.readRemoteFile;
1073
+ if (!readRemoteFile) throw new Error("Codex remote workspace file transfer requires an active app-server client.");
1074
+ const maxBytes = params.maxBytes ?? REMOTE_WORKSPACE_MEDIA_MAX_BYTES;
1075
+ const timeoutMs = params.timeoutMs ?? REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS;
1076
+ const deadline = Date.now() + timeoutMs;
1077
+ const stagedPaths = /* @__PURE__ */ new Map();
1078
+ let totalBytes = 0;
1079
+ for (const [localPath, remotePath] of remotePathsByLocalPath) {
1080
+ params.signal?.throwIfAborted();
1081
+ const remainingBytes = maxBytes - totalBytes;
1082
+ const remainingMs = deadline - Date.now();
1083
+ if (remainingMs <= 0) throw new Error("Codex remote workspace attachment batch timed out.");
1084
+ const response = await readRemoteFile({
1085
+ path: remotePath,
1086
+ maxBytes: remainingBytes,
1087
+ workspaceRoot: remoteWorkspaceRoot,
1088
+ signal: params.signal,
1089
+ timeoutMs: remainingMs
1090
+ });
1091
+ if (!response || typeof response.dataBase64 !== "string") throw new Error(`Codex remote workspace artifact returned no file data: ${remotePath}`);
1092
+ if (response.dataBase64.length > Math.ceil(remainingBytes / 3) * 4) throw new Error(`Codex remote workspace artifact exceeds the limit of ${remainingBytes} bytes.`);
1093
+ const remoteBuffer = Buffer.from(response.dataBase64, "base64");
1094
+ if (remoteBuffer.byteLength > remainingBytes || remoteBuffer.toString("base64") !== response.dataBase64) throw new Error(`Codex remote workspace artifact returned invalid or oversized file data: ${remotePath}`);
1095
+ totalBytes += remoteBuffer.byteLength;
1096
+ const saved = await saveMediaBuffer(remoteBuffer, void 0, "outbound", maxBytes, path.basename(remotePath));
1097
+ stagedPaths.set(localPath, saved.path);
1098
+ }
1099
+ return mapMessageMediaValues(mappedArgs, (value) => stagedPaths.get(value) ?? value);
1100
+ }
1101
+ async function assertGatewayManagedMediaPath(value, mediaRoot) {
1102
+ const opened = await (await root(mediaRoot, { symlinks: "reject" })).open(path.relative(mediaRoot, value), { symlinks: "reject" });
1103
+ try {
1104
+ if (!(await opened.handle.stat()).isFile()) throw new Error(`Codex Gateway-managed media is not a regular file: ${value}`);
1105
+ } finally {
1106
+ await opened[Symbol.asyncDispose]();
1107
+ }
1178
1108
  }
1179
- /** Forces message delivery through the message tool when the source channel requires it. */
1180
- function shouldForceMessageTool(params) {
1181
- return params.disableMessageTool !== true && params.sourceReplyDeliveryMode === "message_tool_only";
1109
+ function mapMessageMediaValues(args, mapValue) {
1110
+ const mapped = { ...args };
1111
+ for (const key of MESSAGE_MEDIA_KEYS) {
1112
+ const value = mapped[key];
1113
+ if (typeof value === "string") mapped[key] = mapValue(value);
1114
+ }
1115
+ for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
1116
+ const value = mapped[key];
1117
+ if (Array.isArray(value)) mapped[key] = value.map((entry) => typeof entry === "string" ? mapValue(entry) : entry);
1118
+ }
1119
+ if (Array.isArray(mapped.attachments)) mapped.attachments = mapped.attachments.map((attachment) => {
1120
+ if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
1121
+ const record = { ...attachment };
1122
+ for (const key of ATTACHMENT_MEDIA_KEYS) {
1123
+ const value = record[key];
1124
+ if (typeof value === "string") record[key] = mapValue(value);
1125
+ }
1126
+ return record;
1127
+ });
1128
+ return mapped;
1182
1129
  }
1183
1130
  //#endregion
1184
1131
  //#region extensions/codex/src/app-server/sandbox-exec-server-node-relay.ts
1185
- const CODEX_NODE_EXEC_SERVER_MAX_MESSAGE_BYTES$1 = 64 * 1024 * 1024;
1132
+ const CODEX_NODE_EXEC_SERVER_MAX_MESSAGE_BYTES$1 = 67108864;
1186
1133
  const CODEX_NODE_EXEC_SERVER_MAX_FAILURE_DETAIL_CHARS = 240;
1187
- const CODEX_NODE_HTTP_CREDENTIAL_BODY_MAX_BYTES = 1024 * 1024;
1134
+ const CODEX_NODE_HTTP_CREDENTIAL_BODY_MAX_BYTES = 1048576;
1188
1135
  const CODEX_NODE_HTTP_CREDENTIAL_SCAN_MAX_FIELDS = 256;
1189
1136
  const CODEX_NODE_HTTP_CREDENTIAL_SCAN_MAX_DEPTH = 8;
1190
1137
  const CODEX_NODE_HTTP_CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -1824,10 +1771,10 @@ function joinSandboxChildPath(parent, child) {
1824
1771
  * Implements filesystem JSON-RPC handlers for the Codex sandbox exec-server
1825
1772
  * with OpenClaw sandbox policy checks before every bridge operation.
1826
1773
  */
1827
- const CODEX_SANDBOX_EXEC_SERVER_MAX_READ_FILE_BYTES = 512 * 1024 * 1024;
1774
+ const CODEX_SANDBOX_EXEC_SERVER_MAX_READ_FILE_BYTES = 536870912;
1828
1775
  const CODEX_SANDBOX_EXEC_SERVER_MAX_OPEN_FILE_READS = 128;
1829
- const CODEX_SANDBOX_EXEC_SERVER_MAX_BUFFERED_FILE_READ_BYTES = 64 * 1024 * 1024;
1830
- const CODEX_SANDBOX_EXEC_SERVER_MAX_READ_BLOCK_BYTES = 1024 * 1024;
1776
+ const CODEX_SANDBOX_EXEC_SERVER_MAX_BUFFERED_FILE_READ_BYTES = 67108864;
1777
+ const CODEX_SANDBOX_EXEC_SERVER_MAX_READ_BLOCK_BYTES = 1048576;
1831
1778
  const CODEX_SANDBOX_EXEC_SERVER_MAX_FILE_READ_HANDLE_ID_BYTES = 32;
1832
1779
  /** Opens a policy-checked sandbox file under a bounded, connection-owned handle. */
1833
1780
  async function openFile(execServer, handles, params) {
@@ -2231,7 +2178,7 @@ function delay(ms) {
2231
2178
  * access through the active OpenClaw sandbox backend.
2232
2179
  */
2233
2180
  /** Maximum JSON-line size accepted from the streaming HTTP helper process. */
2234
- const SANDBOX_HTTP_STREAM_LINE_MAX_CHARS = 256 * 1024;
2181
+ const SANDBOX_HTTP_STREAM_LINE_MAX_CHARS = 262144;
2235
2182
  /** Handles one sandbox HTTP JSON-RPC request, optionally streaming response body deltas. */
2236
2183
  async function httpRequest(execServer, notifications, params) {
2237
2184
  const record = requireObject(params, "http/request params");
@@ -2543,6 +2490,9 @@ def assert_url_allowed(url):
2543
2490
  PINNED_ADDRESSES[hostname] = sorted(addresses)
2544
2491
 
2545
2492
  class GuardedRedirectHandler(urllib.request.HTTPRedirectHandler):
2493
+ # Python 3.9 lacks the 308 dispatch alias; use the same guarded redirect path.
2494
+ http_error_308 = urllib.request.HTTPRedirectHandler.http_error_302
2495
+
2546
2496
  def __init__(self, redirect_policy):
2547
2497
  self.redirect_policy = redirect_policy
2548
2498
 
@@ -2656,7 +2606,7 @@ python3 "$tmp"
2656
2606
  * termination for Codex sandbox exec-server process RPCs.
2657
2607
  */
2658
2608
  const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
2659
- const RETAINED_PROCESS_OUTPUT_BYTES = 1024 * 1024;
2609
+ const RETAINED_PROCESS_OUTPUT_BYTES = 1048576;
2660
2610
  const CLOSED_PROCESS_EVICTION_MS = 6e4;
2661
2611
  /** Starts a sandbox-backed process and registers it in the connection-local process table. */
2662
2612
  async function startProcess(execServer, processes, notify, params) {
@@ -2913,10 +2863,11 @@ function readEnv(value) {
2913
2863
  return env;
2914
2864
  }
2915
2865
  function readProcessEnv(record) {
2916
- return sanitizeEnvVars({
2866
+ const requestedEnv = {
2917
2867
  ...buildEnvFromPolicy(record.envPolicy),
2918
2868
  ...readEnv(record.env)
2919
- }).allowed;
2869
+ };
2870
+ return sanitizeEnvVars(requestedEnv).allowed;
2920
2871
  }
2921
2872
  function buildEnvFromPolicy(value) {
2922
2873
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
@@ -3031,8 +2982,8 @@ var CodexSandboxExecSession = class {
3031
2982
  * Hosts the local OpenClaw sandbox exec-server that Codex app-server native
3032
2983
  * execution can register as an external environment.
3033
2984
  */
3034
- const CODEX_SANDBOX_EXEC_SERVER_MAX_INBOUND_MESSAGE_BYTES = 100 * 1024 * 1024;
3035
- const CODEX_NODE_EXEC_SERVER_MAX_MESSAGE_BYTES = 64 * 1024 * 1024;
2985
+ const CODEX_SANDBOX_EXEC_SERVER_MAX_INBOUND_MESSAGE_BYTES = 104857600;
2986
+ const CODEX_NODE_EXEC_SERVER_MAX_MESSAGE_BYTES = 67108864;
3036
2987
  const codexNodeExecServerLeases = /* @__PURE__ */ new WeakMap();
3037
2988
  /** Starts or reuses a sandbox exec-server and registers it with Codex app-server. */
3038
2989
  async function ensureCodexSandboxExecServerEnvironment(params) {
@@ -3323,52 +3274,6 @@ async function handleMessage(session, data) {
3323
3274
  await session.handleRequest(parseRequest(buffer.toString("utf8")));
3324
3275
  }
3325
3276
  //#endregion
3326
- //#region extensions/codex/src/app-server/provider-capabilities.ts
3327
- function resolveOverriddenProviderWebSearchSupport(modelProviderOverride) {
3328
- const provider = modelProviderOverride?.trim().toLowerCase();
3329
- if (!provider) return;
3330
- return provider === "openai" ? "supported" : "unsupported";
3331
- }
3332
- async function readConfiguredProviderWebSearchSupport(params) {
3333
- return (await params.client.request("modelProvider/capabilities/read", {}, {
3334
- timeoutMs: params.timeoutMs,
3335
- signal: params.signal
3336
- })).webSearch ? "supported" : "unsupported";
3337
- }
3338
- async function resolveCodexProviderWebSearchSupportForClient(params) {
3339
- const overrideSupport = resolveOverriddenProviderWebSearchSupport(params.modelProviderOverride);
3340
- if (overrideSupport) return overrideSupport;
3341
- try {
3342
- return await readConfiguredProviderWebSearchSupport(params);
3343
- } catch {
3344
- return "unknown";
3345
- }
3346
- }
3347
- async function resolveCodexProviderWebSearchSupport(params) {
3348
- const overrideSupport = resolveOverriddenProviderWebSearchSupport(params.modelProviderOverride);
3349
- if (overrideSupport) return overrideSupport;
3350
- let client;
3351
- try {
3352
- client = await params.clientFactory({
3353
- startOptions: params.appServer.start,
3354
- ...params.preparedAuth ? { preparedAuth: params.preparedAuth } : { authProfileId: params.authProfileId },
3355
- agentDir: params.agentDir,
3356
- config: params.config,
3357
- timeoutMs: params.appServer.requestTimeoutMs
3358
- });
3359
- return await resolveCodexProviderWebSearchSupportForClient({
3360
- client,
3361
- timeoutMs: params.appServer.requestTimeoutMs,
3362
- modelProviderOverride: params.modelProviderOverride,
3363
- signal: params.signal
3364
- });
3365
- } catch {
3366
- return "unknown";
3367
- } finally {
3368
- if (client) releaseLeasedSharedCodexAppServerClient(client);
3369
- }
3370
- }
3371
- //#endregion
3372
3277
  //#region extensions/codex/src/app-server/approval-bridge.ts
3373
3278
  /**
3374
3279
  * Bridges Codex app-server approval requests into OpenClaw policy hooks and
@@ -3413,6 +3318,8 @@ async function handleCodexAppServerApprovalRequest(params) {
3413
3318
  resolvedOutcome = "approved-once";
3414
3319
  resolvedMessage = "Codex app-server approval granted for this byte-bound command only.";
3415
3320
  }
3321
+ params.signal?.throwIfAborted();
3322
+ if (resolvedOutcome !== "denied") params.paramsForRun.hostCapabilities.assertActive();
3416
3323
  emitApprovalEvent(params.paramsForRun, {
3417
3324
  phase: "resolved",
3418
3325
  kind: context.kind,
@@ -3451,6 +3358,7 @@ async function handleCodexAppServerApprovalRequest(params) {
3451
3358
  autoApprove: params.autoApprove,
3452
3359
  signal: params.signal
3453
3360
  });
3361
+ params.signal?.throwIfAborted();
3454
3362
  if (policyOutcome?.outcome === "denied") {
3455
3363
  recordNativeToolFailureDisposition(params, context, policyOutcome.failureDisposition);
3456
3364
  return await resolvePolicyApproval("denied", policyOutcome.reason);
@@ -3459,6 +3367,7 @@ async function handleCodexAppServerApprovalRequest(params) {
3459
3367
  if (CONCRETE_TOOL_AUTO_APPROVAL_METHODS.has(params.method) && !readNetworkApprovalContext(requestParams) && params.autoApprove === true) return await resolvePolicyApproval("approved-session", "Codex app-server approval auto-approved by runtime policy.");
3460
3368
  const requestResult = await requestPluginApproval({
3461
3369
  hostCapabilities: params.paramsForRun.hostCapabilities,
3370
+ signal: params.signal,
3462
3371
  title: context.title,
3463
3372
  description: context.description,
3464
3373
  severity: context.severity,
@@ -3972,7 +3881,8 @@ function summarizeNetworkPolicyAmendments(value) {
3972
3881
  function sanitizePermissionHostValue(value) {
3973
3882
  const withoutScheme = sanitizePermissionScalar(value).toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//, "");
3974
3883
  const authority = withoutScheme.split(/[/?#]/, 1)[0] ?? withoutScheme;
3975
- return truncateCodexApprovalDisplayText(authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority, PERMISSION_VALUE_MAX_LENGTH);
3884
+ const withoutUserInfo = authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority;
3885
+ return truncateCodexApprovalDisplayText(withoutUserInfo, PERMISSION_VALUE_MAX_LENGTH);
3976
3886
  }
3977
3887
  function sanitizePermissionPathValue(value) {
3978
3888
  return truncateCodexApprovalDisplayText(formatApprovalDisplayPath(sanitizePermissionScalar(value)), PERMISSION_VALUE_MAX_LENGTH);
@@ -4101,7 +4011,8 @@ function appendPreviewPart(source, part, separator) {
4101
4011
  }
4102
4012
  function sanitizeApprovalPreview(source, maxLength) {
4103
4013
  if (!source || !source.value) return { omitted: false };
4104
- const sanitized = sanitizeCodexApprovalVisibleText(stripDanglingCodexApprovalTerminalSequence(source.value));
4014
+ const rawPreview = stripDanglingCodexApprovalTerminalSequence(source.value);
4015
+ const sanitized = sanitizeCodexApprovalVisibleText(rawPreview);
4105
4016
  if (!sanitized) return { omitted: true };
4106
4017
  return {
4107
4018
  text: formatCodexDisplayText(truncateCodexApprovalDisplayText(sanitized, maxLength)),
@@ -4246,6 +4157,7 @@ async function routeCodexAppServerElicitationRequest(params) {
4246
4157
  const requestTurnId = requestParams.turnId;
4247
4158
  if (requestTurnId !== null && requestTurnId !== void 0 && requestTurnId !== params.turnId) return { kind: "not-mine" };
4248
4159
  if (!((isJsonObject(requestParams["_meta"]) ? requestParams["_meta"] : void 0)?.[MCP_TOOL_APPROVAL_KIND_KEY] === MCP_TOOL_APPROVAL_KIND || params.computerUseMcpServerName !== void 0 && readNonBlankStringField(requestParams, "serverName") === params.computerUseMcpServerName)) return { kind: "not-mine" };
4160
+ if (params.signal?.aborted) return handled(createCodexElicitationResponse("cancel"));
4249
4161
  const pluginResolution = resolvePluginElicitation({
4250
4162
  requestParams,
4251
4163
  pluginAppPolicyContext: params.pluginAppPolicyContext
@@ -4270,16 +4182,63 @@ async function routeCodexAppServerElicitationRequest(params) {
4270
4182
  signal: params.signal
4271
4183
  }));
4272
4184
  }
4273
- const approvalPrompt = readComputerUseApprovalElicitation(requestParams, params.computerUseMcpServerName) ?? readBridgeableApprovalElicitation(requestParams);
4185
+ const computerUsePrompt = readComputerUseApprovalElicitation(requestParams, params.computerUseMcpServerName);
4186
+ const approvalPrompt = computerUsePrompt ?? readBridgeableApprovalElicitation(requestParams);
4274
4187
  if (!approvalPrompt) return handled(createCodexElicitationResponse("decline"));
4188
+ let persistence;
4189
+ if (!computerUsePrompt) {
4190
+ const serverName = readNonBlankStringField(requestParams, "serverName");
4191
+ const server = serverName ? params.paramsForRun.config?.mcp?.servers?.[serverName] : void 0;
4192
+ const mode = serverName ? resolveProjectedMcpCodexToolApprovalMode(serverName, server ?? {}, params.projectedMcpServers?.[serverName]) : void 0;
4193
+ if (!requiresMcpCodexToolApproval({
4194
+ mode,
4195
+ fullPermission: params.autoApproveMcpTools
4196
+ })) {
4197
+ params.paramsForRun.hostCapabilities.assertActive();
4198
+ return handled(buildElicitationResponse(approvalPrompt, "approved-once"));
4199
+ }
4200
+ if (mode === "prompt") approvalPrompt.allowedDecisions = ["allow-once", "deny"];
4201
+ else if (serverName && serverName !== CODEX_APPS_SERVER_NAME && Object.hasOwn(params.paramsForRun.config?.mcp?.servers ?? {}, serverName) && requestTurnId === params.turnId && readPersistHints(approvalPrompt.meta, "explicit").includes("always")) {
4202
+ const resolveItem = () => {
4203
+ const item = params.getActiveMcpToolCall?.(serverName);
4204
+ return item?.server === serverName && matchesMcpApprovalDisplay(item, approvalPrompt.meta) ? item : void 0;
4205
+ };
4206
+ const item = resolveItem();
4207
+ if (item) persistence = {
4208
+ mcpTool: {
4209
+ server: serverName,
4210
+ tool: item.tool
4211
+ },
4212
+ toolCallId: item.id,
4213
+ isMcpToolApprovalActive: () => {
4214
+ const current = resolveItem();
4215
+ return current?.id === item.id && current.tool === item.tool;
4216
+ }
4217
+ };
4218
+ }
4219
+ }
4275
4220
  return handled(buildElicitationResponse(approvalPrompt, await requestPluginApprovalOutcome({
4276
- paramsForRun: params.paramsForRun,
4221
+ hostCapabilities: params.paramsForRun.hostCapabilities,
4277
4222
  title: approvalPrompt.title,
4278
4223
  description: approvalPrompt.description,
4279
4224
  allowedDecisions: approvalPrompt.allowedDecisions,
4225
+ toolName: "codex_mcp_tool_approval",
4226
+ ...persistence,
4280
4227
  signal: params.signal
4281
4228
  })));
4282
4229
  }
4230
+ function matchesMcpApprovalDisplay(item, meta) {
4231
+ if (!Object.hasOwn(meta, MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY)) return true;
4232
+ const display = meta[MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY];
4233
+ if (!Array.isArray(display)) return false;
4234
+ const args = item.arguments;
4235
+ return display.every((param) => {
4236
+ if (!isJsonObject(param) || typeof param.name !== "string" || !isJsonObject(args)) return false;
4237
+ if (!Object.hasOwn(args, param.name)) return false;
4238
+ const value = args[param.name];
4239
+ return typeof param.value !== "string" || param.value === (typeof value === "string" ? value : JSON.stringify(value));
4240
+ });
4241
+ }
4283
4242
  function handled(response) {
4284
4243
  return {
4285
4244
  kind: "handled",
@@ -4395,14 +4354,14 @@ async function buildPluginPolicyElicitationResponse(params) {
4395
4354
  const response = buildElicitationResponse(approvalPrompt, "approved-once");
4396
4355
  if (response.action === "accept") {
4397
4356
  if (mode === "allow") return response;
4398
- const outcome = await requestPluginApprovalOutcome({
4399
- paramsForRun: params.paramsForRun,
4357
+ return buildElicitationResponse(approvalPrompt, await requestPluginApprovalOutcome({
4358
+ hostCapabilities: params.paramsForRun.hostCapabilities,
4400
4359
  title: approvalPrompt.title,
4401
4360
  description: approvalPrompt.description,
4402
4361
  allowedDecisions: allowedPluginPolicyApprovalDecisions(mode, approvalPrompt),
4362
+ toolName: "codex_mcp_tool_approval",
4403
4363
  signal: params.signal
4404
- });
4405
- return buildElicitationResponse(approvalPrompt, mode === "ask" && outcome === "approved-session" ? "approved-once" : outcome);
4364
+ }));
4406
4365
  }
4407
4366
  logPluginElicitationDecline("unmappable_schema", params.requestParams);
4408
4367
  return createCodexElicitationResponse("decline");
@@ -4437,15 +4396,16 @@ function readPluginApprovalElicitation(entry, requestParams) {
4437
4396
  allowedDecisions: buildApprovalAllowedDecisions(requestedSchema, meta)
4438
4397
  };
4439
4398
  }
4440
- function buildApprovalAllowedDecisions(requestedSchema, meta) {
4441
- return canMapPersistentApproval(requestedSchema, meta) ? [
4399
+ function buildApprovalAllowedDecisions(requestedSchema, meta, allowSession = false) {
4400
+ return canMapPersistentApproval(requestedSchema, meta, allowSession) ? [
4442
4401
  "allow-once",
4443
4402
  "allow-always",
4444
4403
  "deny"
4445
4404
  ] : ["allow-once", "deny"];
4446
4405
  }
4447
- function canMapPersistentApproval(requestedSchema, meta) {
4406
+ function canMapPersistentApproval(requestedSchema, meta, allowSession) {
4448
4407
  const persistHints = readPersistHints(meta, "explicit");
4408
+ if (allowSession) return choosePersistHint(persistHints) !== void 0;
4449
4409
  if (persistHints.length > 0) return persistHints.includes("always");
4450
4410
  const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
4451
4411
  return Object.entries(properties).some(([name, value]) => {
@@ -4470,16 +4430,20 @@ function readBridgeableApprovalElicitation(requestParams) {
4470
4430
  const requestedSchema = requestParams.requestedSchema;
4471
4431
  if (readNonBlankStringField(requestedSchema, "type") !== "object" || !isJsonObject(requestedSchema.properties)) return;
4472
4432
  const title = sanitizeDisplayText(readNonBlankStringField(requestParams, "message") ?? "") || "Codex MCP tool approval";
4433
+ const serverName = readNonBlankStringField(requestParams, "serverName");
4473
4434
  return {
4474
4435
  title,
4475
4436
  description: buildApprovalDescription({
4476
4437
  title,
4477
4438
  meta: requestParams["_meta"],
4478
4439
  requestedSchema,
4479
- serverName: sanitizeOptionalDisplayText(readNonBlankStringField(requestParams, "serverName"))
4440
+ serverName: sanitizeOptionalDisplayText(serverName),
4441
+ remedy: serverName ? formatMcpCodexApprovalRemedy(serverName) : void 0
4480
4442
  }),
4481
4443
  requestedSchema,
4482
- meta: requestParams["_meta"]
4444
+ meta: requestParams["_meta"],
4445
+ persistHintsMode: "explicit",
4446
+ allowedDecisions: buildApprovalAllowedDecisions(requestedSchema, requestParams["_meta"], true)
4483
4447
  };
4484
4448
  }
4485
4449
  function readComputerUseApprovalElicitation(requestParams, expectedServerName) {
@@ -4509,6 +4473,7 @@ function buildApprovalDescription(params) {
4509
4473
  connectorName && `App: ${connectorName}`,
4510
4474
  toolTitle && `Tool: ${toolTitle}`,
4511
4475
  params.serverName && `MCP server: ${params.serverName}`,
4476
+ params.remedy,
4512
4477
  toolDescription
4513
4478
  ].filter((line) => Boolean(line));
4514
4479
  const paramLines = readDisplayParamLines(params.meta);
@@ -4544,7 +4509,8 @@ function readDisplayParamLines(meta) {
4544
4509
  return remaining > 0 ? [...lines, `- Additional parameters: ${remaining} more`] : lines;
4545
4510
  }
4546
4511
  function formatDisplayParamValue(value) {
4547
- return truncateCodexApprovalDisplayText(sanitizeDisplayText(typeof value === "string" ? value : formatDisplayJsonValue(value ?? null)), MAX_DISPLAY_PARAM_VALUE_LENGTH);
4512
+ const formatted = typeof value === "string" ? value : formatDisplayJsonValue(value ?? null);
4513
+ return truncateCodexApprovalDisplayText(sanitizeDisplayText(formatted), MAX_DISPLAY_PARAM_VALUE_LENGTH);
4548
4514
  }
4549
4515
  function formatDisplayJsonValue(value, depth = MAX_DISPLAY_VALUE_DEPTH) {
4550
4516
  if (value === null) return "null";
@@ -4588,35 +4554,10 @@ function sanitizeDisplayText(value) {
4588
4554
  const escaped = sanitized ? formatCodexDisplayText(sanitized) : "";
4589
4555
  return clipped && escaped ? `${escaped}...` : escaped;
4590
4556
  }
4591
- async function requestPluginApprovalOutcome(params) {
4592
- try {
4593
- const requestResult = await requestPluginApproval({
4594
- hostCapabilities: params.paramsForRun.hostCapabilities,
4595
- title: params.title,
4596
- description: params.description,
4597
- severity: "warning",
4598
- toolName: "codex_mcp_tool_approval",
4599
- allowedDecisions: params.allowedDecisions
4600
- });
4601
- const approvalId = requestResult?.id;
4602
- if (!approvalId) return "unavailable";
4603
- const approvalResult = approvalRequestExplicitlyUnavailable(requestResult) ? void 0 : await waitForPluginApprovalDecision({
4604
- hostCapabilities: params.paramsForRun.hostCapabilities,
4605
- approvalId,
4606
- signal: params.signal
4607
- });
4608
- if (params.signal?.aborted) return "cancelled";
4609
- if (approvalResult?.terminalReason === "timeout") return "timed-out";
4610
- return mapExecDecisionToOutcome(approvalResult?.decision);
4611
- } catch {
4612
- return params.signal?.aborted ? "cancelled" : "denied";
4613
- }
4614
- }
4615
4557
  function buildElicitationResponse(approvalPrompt, outcome) {
4616
4558
  const { requestedSchema, meta } = approvalPrompt;
4617
4559
  if (outcome === "cancelled") return createCodexElicitationResponse("cancel");
4618
- if (outcome === "timed-out") return createCodexElicitationResponse("decline", null, { message: codexApprovalTimeoutText("other") });
4619
- if (outcome === "denied" || outcome === "unavailable") return createCodexElicitationResponse("decline");
4560
+ if (outcome === "timed-out" || outcome === "denied" || outcome === "unavailable") return createCodexElicitationResponse("decline");
4620
4561
  const content = buildAcceptedContent(approvalPrompt, outcome);
4621
4562
  if (!content && !hasNoSchemaProperties(requestedSchema)) {
4622
4563
  embeddedAgentLog.warn("codex MCP approval elicitation approved without a mappable response", {
@@ -4643,7 +4584,7 @@ function buildAcceptedContent(approvalPrompt, outcome) {
4643
4584
  schema,
4644
4585
  required: required.has(name)
4645
4586
  };
4646
- const next = readApprovalFieldValue(property, outcome) ?? readPersistFieldValue(property, meta, outcome, approvalPrompt.persistHintsMode ?? "legacy") ?? readFallbackFieldValue(property, outcome);
4587
+ const next = readApprovalFieldValue(property, outcome, choosePersistHint(readPersistHints(meta, approvalPrompt.persistHintsMode))) ?? readPersistFieldValue(property, meta, outcome, approvalPrompt.persistHintsMode ?? "legacy") ?? readFallbackFieldValue(property, outcome);
4647
4588
  if (next === void 0) {
4648
4589
  if (isApprovalField(property)) sawApprovalField = true;
4649
4590
  if (property.required) return;
@@ -4654,15 +4595,14 @@ function buildAcceptedContent(approvalPrompt, outcome) {
4654
4595
  }
4655
4596
  return sawApprovalField ? content : void 0;
4656
4597
  }
4657
- function readApprovalFieldValue(property, outcome) {
4598
+ function readApprovalFieldValue(property, outcome, persist) {
4658
4599
  if (!isApprovalField(property)) return;
4659
4600
  if (readNonBlankStringField(property.schema, "type") === "boolean") return true;
4660
4601
  const options = readEnumOptions(property.schema);
4661
4602
  if (options.length === 0) return;
4662
- const sessionChoice = options.find((option) => isSessionApprovalOption(option));
4663
4603
  const acceptChoice = options.find((option) => isPositiveApprovalOption(option));
4664
- if (outcome === "approved-session") return sessionChoice?.value ?? acceptChoice?.value;
4665
- return acceptChoice?.value ?? sessionChoice?.value;
4604
+ if (outcome === "approved-session") return options.find((option) => isPersistentApprovalOption(option, persist))?.value ?? acceptChoice?.value;
4605
+ return acceptChoice?.value;
4666
4606
  }
4667
4607
  function readPersistFieldValue(property, meta, outcome, persistHintsMode) {
4668
4608
  if (!isPersistField(property) || outcome !== "approved-session") return;
@@ -4741,19 +4681,233 @@ function isPositiveApprovalOption(option) {
4741
4681
  const haystack = `${option.value} ${option.label}`.toLowerCase();
4742
4682
  return /\b(allow|approve|accept|yes|continue|proceed|true)\b/.test(haystack);
4743
4683
  }
4744
- function isSessionApprovalOption(option) {
4684
+ function isPersistentApprovalOption(option, persist) {
4745
4685
  const haystack = `${option.value} ${option.label}`.toLowerCase();
4746
- return /\b(session|always|persistent)\b/.test(haystack) && /\b(allow|approve|accept)\b/.test(haystack);
4686
+ return (persist === "always" ? /\b(always|persistent)\b|\bdon't ask me again\b/.test(haystack) : persist === "session" && /\bsession\b/.test(haystack)) && /\b(allow|approve|accept)\b/.test(haystack);
4747
4687
  }
4748
4688
  function readNonBlankStringField(record, key) {
4749
4689
  const value = record?.[key];
4750
4690
  return typeof value === "string" && value.trim() ? value : void 0;
4751
4691
  }
4752
- function readFirstString$1(record, keys) {
4753
- for (const key of keys) {
4754
- const value = readNonBlankStringField(record, key);
4755
- if (value) return value;
4692
+ function readFirstString$1(record, keys) {
4693
+ for (const key of keys) {
4694
+ const value = readNonBlankStringField(record, key);
4695
+ if (value) return value;
4696
+ }
4697
+ }
4698
+ //#endregion
4699
+ //#region extensions/codex/src/app-server/dynamic-tool-catalog.ts
4700
+ /** Namespace attached to OpenClaw-owned dynamic tools exposed to Codex. */
4701
+ const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
4702
+ const CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS = 128;
4703
+ const CODEX_DYNAMIC_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/u;
4704
+ const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = /* @__PURE__ */ new Set([
4705
+ "agents_list",
4706
+ "agents_wait",
4707
+ "progress_card",
4708
+ "sessions_spawn",
4709
+ "sessions_yield"
4710
+ ]);
4711
+ function createCodexDynamicToolSpecs(params) {
4712
+ const directToolNames = /* @__PURE__ */ new Set([...ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES, ...params.directToolNames ?? []]);
4713
+ const specs = [];
4714
+ const namespaceTools = [];
4715
+ const directOnlyNamespaceTools = [];
4716
+ const entries = params.loading === "direct" ? params.entries : params.entries.toSorted((left, right) => left.name.localeCompare(right.name));
4717
+ for (const entry of entries) {
4718
+ const functionSpec = createCodexDynamicToolFunctionSpec({ entry });
4719
+ if (entry.name === "openclaw" && directToolNames.has(entry.name)) {
4720
+ specs.push(functionSpec);
4721
+ continue;
4722
+ }
4723
+ if (entry.tool.catalogMode === "direct-only") {
4724
+ directOnlyNamespaceTools.push(functionSpec);
4725
+ continue;
4726
+ }
4727
+ if (params.loading === "direct" || directToolNames.has(entry.name)) {
4728
+ specs.push(functionSpec);
4729
+ continue;
4730
+ }
4731
+ namespaceTools.push({
4732
+ ...functionSpec,
4733
+ deferLoading: true
4734
+ });
4735
+ }
4736
+ if (namespaceTools.length > 0) specs.push({
4737
+ type: "namespace",
4738
+ name: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
4739
+ description: "",
4740
+ tools: namespaceTools
4741
+ });
4742
+ if (directOnlyNamespaceTools.length > 0) specs.push({
4743
+ type: "namespace",
4744
+ name: CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE,
4745
+ description: "",
4746
+ tools: directOnlyNamespaceTools
4747
+ });
4748
+ return specs;
4749
+ }
4750
+ function createCodexDynamicToolFunctionSpec(params) {
4751
+ return {
4752
+ type: "function",
4753
+ name: params.entry.name,
4754
+ description: params.entry.description,
4755
+ inputSchema: params.entry.inputSchema
4756
+ };
4757
+ }
4758
+ function projectCodexDynamicTools(tools) {
4759
+ const projectedTools = [];
4760
+ const quarantinedTools = [];
4761
+ let length;
4762
+ try {
4763
+ length = tools.length;
4764
+ } catch {
4765
+ return {
4766
+ tools: [],
4767
+ quarantinedTools: [{
4768
+ tool: "tool[0]",
4769
+ violations: ["tool[0] is unreadable"]
4770
+ }]
4771
+ };
4772
+ }
4773
+ for (let toolIndex = 0; toolIndex < length; toolIndex += 1) {
4774
+ let tool;
4775
+ try {
4776
+ tool = tools[toolIndex];
4777
+ } catch {
4778
+ quarantinedTools.push({
4779
+ tool: `tool[${toolIndex}]`,
4780
+ violations: [`tool[${toolIndex}] is unreadable`]
4781
+ });
4782
+ continue;
4783
+ }
4784
+ const descriptor = readCodexDynamicToolDescriptor(tool, toolIndex);
4785
+ if (!descriptor.ok) {
4786
+ quarantinedTools.push(descriptor.diagnostic);
4787
+ continue;
4788
+ }
4789
+ const normalizedParameters = normalizeOpenAIStrictCompatSchema(descriptor.parameters);
4790
+ const projection = projectRuntimeToolInputSchema(normalizedParameters ?? descriptor.parameters, `${descriptor.name}.inputSchema`);
4791
+ if (projection.violations.length > 0) {
4792
+ quarantinedTools.push({
4793
+ tool: descriptor.name,
4794
+ violations: projection.violations
4795
+ });
4796
+ continue;
4797
+ }
4798
+ if (!isRecord(projection.schema)) {
4799
+ quarantinedTools.push({
4800
+ tool: descriptor.name,
4801
+ violations: [`${descriptor.name}.inputSchema must be a JSON object schema`]
4802
+ });
4803
+ continue;
4804
+ }
4805
+ projectedTools.push({
4806
+ tool,
4807
+ name: descriptor.name,
4808
+ description: descriptor.description,
4809
+ inputSchema: projection.schema
4810
+ });
4811
+ }
4812
+ return {
4813
+ tools: projectedTools,
4814
+ quarantinedTools
4815
+ };
4816
+ }
4817
+ function readCodexDynamicToolDescriptor(tool, toolIndex) {
4818
+ const fallbackName = `tool[${toolIndex}]`;
4819
+ let name;
4820
+ try {
4821
+ const rawName = tool.name;
4822
+ if (typeof rawName !== "string" || !rawName) return {
4823
+ ok: false,
4824
+ diagnostic: {
4825
+ tool: fallbackName,
4826
+ violations: [`${fallbackName}.name must be a non-empty string`]
4827
+ }
4828
+ };
4829
+ const trimmedName = rawName.trim();
4830
+ let nameViolation;
4831
+ if (!trimmedName) nameViolation = `${rawName}.name must not be empty`;
4832
+ else if (trimmedName !== rawName) nameViolation = `${rawName}.name must not have leading or trailing whitespace`;
4833
+ else if (!CODEX_DYNAMIC_TOOL_NAME_PATTERN.test(rawName)) nameViolation = `${rawName}.name must match ^[a-zA-Z0-9_-]+$`;
4834
+ else if (rawName.length > CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS) nameViolation = `${rawName}.name must be at most ${CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS} characters`;
4835
+ else if (rawName === "mcp" || rawName.startsWith("mcp__")) nameViolation = `${rawName}.name is reserved by Codex app-server`;
4836
+ if (nameViolation) return {
4837
+ ok: false,
4838
+ diagnostic: {
4839
+ tool: rawName,
4840
+ violations: [nameViolation]
4841
+ }
4842
+ };
4843
+ name = rawName;
4844
+ } catch {
4845
+ return {
4846
+ ok: false,
4847
+ diagnostic: {
4848
+ tool: fallbackName,
4849
+ violations: [`${fallbackName}.name is unreadable`]
4850
+ }
4851
+ };
4852
+ }
4853
+ let description;
4854
+ try {
4855
+ description = typeof tool.description === "string" ? tool.description : "";
4856
+ } catch {
4857
+ return {
4858
+ ok: false,
4859
+ diagnostic: {
4860
+ tool: name,
4861
+ violations: [`${name}.description is unreadable`]
4862
+ }
4863
+ };
4864
+ }
4865
+ let parameters;
4866
+ try {
4867
+ parameters = tool.parameters;
4868
+ } catch {
4869
+ return {
4870
+ ok: false,
4871
+ diagnostic: {
4872
+ tool: name,
4873
+ violations: [`${name}.inputSchema is unreadable`]
4874
+ }
4875
+ };
4876
+ }
4877
+ return {
4878
+ ok: true,
4879
+ name,
4880
+ description,
4881
+ parameters
4882
+ };
4883
+ }
4884
+ //#endregion
4885
+ //#region extensions/codex/src/app-server/dynamic-tool-availability.ts
4886
+ /** Prepare the actual executable set, then reproject only changed owner definitions. */
4887
+ function finalizeCodexToolAvailability(entries) {
4888
+ const snapshots = entries.map((entry) => ({
4889
+ entry,
4890
+ parameters: entry.tool.parameters,
4891
+ description: entry.tool.description
4892
+ }));
4893
+ const preparedNames = /* @__PURE__ */ new Set();
4894
+ finalizeAgentToolAvailability(entries.map((entry) => entry.tool), { onPrepared: (tool) => preparedNames.add(tool.name) });
4895
+ const tools = [];
4896
+ const quarantinedTools = [];
4897
+ for (const { entry, parameters, description } of snapshots) {
4898
+ if (entry.tool.parameters === parameters && entry.tool.description === description) {
4899
+ tools.push(entry);
4900
+ continue;
4901
+ }
4902
+ const projected = projectCodexDynamicTools([entry.tool]);
4903
+ tools.push(...projected.tools);
4904
+ quarantinedTools.push(...projected.quarantinedTools);
4756
4905
  }
4906
+ return {
4907
+ tools,
4908
+ quarantinedTools,
4909
+ preparedNames
4910
+ };
4757
4911
  }
4758
4912
  //#endregion
4759
4913
  //#region extensions/codex/src/app-server/dynamic-tools.ts
@@ -4778,7 +4932,7 @@ function assertCodexDynamicToolInputMatchesSchema(params) {
4778
4932
  const visibleErrors = validation.errors.slice(0, MAX_CODEX_DYNAMIC_TOOL_VALIDATION_ERRORS);
4779
4933
  const details = visibleErrors.map((error) => {
4780
4934
  if (error.text.length <= MAX_CODEX_DYNAMIC_TOOL_VALIDATION_ERROR_CHARS) return error.text;
4781
- return `${error.text.slice(0, MAX_CODEX_DYNAMIC_TOOL_VALIDATION_ERROR_CHARS - 19)}${CODEX_DYNAMIC_TOOL_VALIDATION_TRUNCATED_SUFFIX}`;
4935
+ return `${error.text.slice(0, 141)}${CODEX_DYNAMIC_TOOL_VALIDATION_TRUNCATED_SUFFIX}`;
4782
4936
  }).join("; ");
4783
4937
  const omitted = validation.errors.length - visibleErrors.length;
4784
4938
  const omittedSuffix = omitted > 0 ? `; ${omitted} more violation(s) omitted` : "";
@@ -4919,15 +5073,6 @@ function normalizeAcceptedSessionSpawn(result) {
4919
5073
  childSessionKey
4920
5074
  } : null;
4921
5075
  }
4922
- /** Namespace attached to OpenClaw-owned dynamic tools exposed to Codex. */
4923
- const CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE = "openclaw";
4924
- const CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS = 128;
4925
- const CODEX_DYNAMIC_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/u;
4926
- const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = /* @__PURE__ */ new Set([
4927
- "agents_list",
4928
- "sessions_spawn",
4929
- "sessions_yield"
4930
- ]);
4931
5076
  const EXPLICIT_MESSAGE_PROVIDER_KEYS = ["channel", "provider"];
4932
5077
  const EXPLICIT_MESSAGE_TARGET_KEYS = [
4933
5078
  "target",
@@ -4967,18 +5112,23 @@ function createCodexDynamicToolBridge(params) {
4967
5112
  const toolResultMaxChars = typeof contextWindowTokens === "number" && Number.isFinite(contextWindowTokens) && contextWindowTokens > 0 ? Math.max(1, resolveLiveToolResultMaxChars({ contextWindowTokens })) : DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS;
4968
5113
  const availableProjection = projectCodexExecutableDynamicToolSurface(params.tools, params.hookContext);
4969
5114
  const registeredProjection = params.registeredTools ? projectCodexDynamicTools(params.registeredTools) : availableProjection;
4970
- const availableTools = availableProjection.tools;
4971
- const quarantinedAvailableToolNames = new Set(availableProjection.quarantinedTools.map((tool) => tool.tool));
4972
- const registeredSpecTools = (params.registeredTools ? registeredProjection.tools : availableTools).filter((entry) => !quarantinedAvailableToolNames.has(entry.name));
5115
+ const inheritedSpecs = params.registeredSpecs ? structuredClone([...params.registeredSpecs]) : void 0;
5116
+ const inheritedNames = inheritedSpecs ? new Set(flattenCodexDynamicToolFunctions(inheritedSpecs).map((tool) => tool.name)) : void 0;
5117
+ const registrationNames = inheritedNames ?? new Set(registeredProjection.tools.map((entry) => entry.name));
5118
+ const finalized = finalizeCodexToolAvailability(availableProjection.tools.filter((entry) => registrationNames.has(entry.name)));
5119
+ const availableTools = finalized.tools;
5120
+ availableProjection.quarantinedTools.push(...finalized.quarantinedTools);
4973
5121
  const toolMap = new Map(availableTools.map((entry) => [entry.name, entry]));
4974
- const registeredToolNames = new Set(registeredSpecTools.map((entry) => entry.name));
5122
+ const quarantinedAvailableToolNames = new Set(availableProjection.quarantinedTools.map((tool) => tool.tool));
5123
+ const registeredSpecTools = (params.registeredTools ? registeredProjection.tools : availableTools).filter((entry) => !quarantinedAvailableToolNames.has(entry.name)).map((entry) => finalized.preparedNames.has(entry.name) ? toolMap.get(entry.name) ?? entry : entry);
5124
+ const registeredToolNames = inheritedNames ?? new Set(registeredSpecTools.map((entry) => entry.name));
4975
5125
  const quarantinedTools = dedupeQuarantinedDynamicTools([...availableProjection.quarantinedTools, ...registeredProjection.quarantinedTools]);
4976
- warnQuarantinedDynamicTools({
5126
+ reportQuarantinedDynamicTools({
4977
5127
  tools: quarantinedTools,
4978
5128
  availableToolCount: availableTools.length,
4979
- registeredToolCount: registeredSpecTools.length
5129
+ registeredToolCount: registeredSpecTools.length,
5130
+ hookContext: params.hookContext
4980
5131
  });
4981
- emitQuarantinedDynamicToolDiagnostics(quarantinedTools, params.hookContext);
4982
5132
  const telemetry = {
4983
5133
  didSendViaMessagingTool: false,
4984
5134
  didDeliverSourceReplyViaMessageTool: false,
@@ -4987,6 +5137,8 @@ function createCodexDynamicToolBridge(params) {
4987
5137
  messagingToolSentTargets: [],
4988
5138
  messagingToolSourceReplyPayloads: [],
4989
5139
  toolMediaUrls: [],
5140
+ toolAutoDeliveryMediaUrls: [],
5141
+ coreTtsToolResults: [],
4990
5142
  toolAudioAsVoice: false,
4991
5143
  acceptedSessionSpawns: [],
4992
5144
  quarantinedTools
@@ -5002,16 +5154,23 @@ function createCodexDynamicToolBridge(params) {
5002
5154
  };
5003
5155
  const legacyExtensionRunner = createCodexAppServerToolResultExtensionRunner(toolResultHookContext);
5004
5156
  const executionSnapshotStates = /* @__PURE__ */ new Map();
5005
- const directToolNames = /* @__PURE__ */ new Set([...ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES, ...params.directToolNames ?? []]);
5157
+ const directToolNames = params.directToolNames;
5006
5158
  let readRemoteWorkspaceFile;
5007
5159
  return {
5008
5160
  availableTools: availableTools.map((entry) => entry.tool),
5009
- availableSpecs: createCodexDynamicToolSpecs({
5161
+ availableSpecs: inheritedSpecs ? inheritedSpecs.flatMap((spec) => {
5162
+ if (spec.type === "function") return toolMap.has(spec.name) ? [spec] : [];
5163
+ const tools = spec.tools.filter((tool) => toolMap.has(tool.name));
5164
+ return tools.length ? [{
5165
+ ...spec,
5166
+ tools
5167
+ }] : [];
5168
+ }) : createCodexDynamicToolSpecs({
5010
5169
  entries: availableTools,
5011
5170
  loading: params.loading ?? "searchable",
5012
5171
  directToolNames
5013
5172
  }),
5014
- specs: createCodexDynamicToolSpecs({
5173
+ specs: inheritedSpecs ?? createCodexDynamicToolSpecs({
5015
5174
  entries: registeredSpecTools,
5016
5175
  loading: params.loading ?? "searchable",
5017
5176
  directToolNames
@@ -5112,6 +5271,7 @@ function createCodexDynamicToolBridge(params) {
5112
5271
  }));
5113
5272
  const rawResult = await Reflect.apply(tool.execute, tool, executionArgs);
5114
5273
  captureExecutionBoundary();
5274
+ if (toolName === "message" && asOptionalRecord(asOptionalRecord(rawResult.details)?.messageDelivery)?.sourceReplyDelivered === true) telemetry.sourceReplyDelivered = true;
5115
5275
  const telemetryRawResult = sanitizeToolResult(rawResult);
5116
5276
  const rawIsError = isToolResultError(rawResult);
5117
5277
  const rawResultFailureKind = resolveToolResultFailureKind(rawResult);
@@ -5200,13 +5360,17 @@ function createCodexDynamicToolBridge(params) {
5200
5360
  const toolConfirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && !resultIsError && (rawResult.terminate === true || result.terminate === true);
5201
5361
  const confirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && (toolConfirmedSourceReply || deliveredSourceReply || receiptConfirmedSourceReply);
5202
5362
  const sourceReplyFinal = confirmedSourceReply ? executedArgs.final !== false : void 0;
5363
+ const autoDeliveryTtsMediaUrls = getCoreTtsToolResultMediaUrls(rawResult);
5203
5364
  collectToolTelemetry({
5204
5365
  toolName,
5205
5366
  args: executedArgs,
5206
5367
  result,
5207
5368
  mediaTrustResult: telemetryRawResult,
5208
5369
  telemetry,
5370
+ signal,
5209
5371
  isError: resultIsError,
5372
+ autoDeliveryTtsMediaUrls,
5373
+ coreTtsToolResult: autoDeliveryTtsMediaUrls?.length ? rawResult : void 0,
5210
5374
  messagingTarget: confirmedMessagingTarget,
5211
5375
  sourceReplyFinal
5212
5376
  });
@@ -5216,12 +5380,15 @@ function createCodexDynamicToolBridge(params) {
5216
5380
  const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
5217
5381
  withDynamicToolAsyncStarted(response, asyncStarted);
5218
5382
  const replaySafe = executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
5383
+ copyInternalToolResultState(rawResult, response);
5219
5384
  return withDynamicToolExecutionState(response, {
5220
5385
  executedArguments: executedArgs,
5221
5386
  executionStarted: didStartExecution && !executionPrevented,
5222
5387
  sideEffectEvidence: !replaySafe
5223
5388
  });
5224
5389
  } catch (error) {
5390
+ const trustedNoStart = consumeTrustedToolNoStartError(error);
5391
+ executionPrevented ||= trustedNoStart;
5225
5392
  captureExecutionBoundary();
5226
5393
  if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId) invalidateComputerFrame(params.computerContextEpoch);
5227
5394
  const executionDisposition = getBeforeToolCallFailureDisposition(error) ?? (signal.aborted ? resolveCodexToolAbortTerminalReason(signal) : resolveToolExecutionErrorKind(error));
@@ -5238,13 +5405,6 @@ function createCodexDynamicToolBridge(params) {
5238
5405
  toolCallOrdinal: options?.toolCallOrdinal
5239
5406
  });
5240
5407
  notifyAgentToolResult(options?.onAgentToolResult, toolName, failedResult, true);
5241
- collectToolTelemetry({
5242
- toolName,
5243
- args: executedArgs,
5244
- result: void 0,
5245
- telemetry,
5246
- isError: true
5247
- });
5248
5408
  runAgentHarnessAfterToolCallHook({
5249
5409
  toolName,
5250
5410
  toolCallId: call.callId,
@@ -5277,19 +5437,36 @@ function createCodexDynamicToolBridge(params) {
5277
5437
  };
5278
5438
  }
5279
5439
  function projectCodexExecutableDynamicToolSurface(tools, hookContext) {
5280
- const projected = projectCodexDynamicTools(tools);
5281
- const wrapped = wrapProjectedCodexDynamicTools(projected.tools, hookContext);
5440
+ const { tools: projectedTools, quarantinedTools } = projectCodexDynamicTools(tools);
5441
+ const wrappedTools = [];
5442
+ for (const entry of projectedTools) try {
5443
+ if (isToolWrappedWithBeforeToolCallHook(entry.tool)) {
5444
+ setBeforeToolCallDiagnosticsEnabled(entry.tool, false);
5445
+ wrappedTools.push(entry);
5446
+ continue;
5447
+ }
5448
+ wrappedTools.push({
5449
+ ...entry,
5450
+ tool: wrapToolWithBeforeToolCallHook(entry.tool, hookContext, { emitDiagnostics: false })
5451
+ });
5452
+ } catch {
5453
+ quarantinedTools.push({
5454
+ tool: entry.name,
5455
+ violations: [`${entry.name} could not be wrapped for before-tool-call hooks`]
5456
+ });
5457
+ }
5282
5458
  return {
5283
- tools: wrapped.tools,
5284
- quarantinedTools: dedupeQuarantinedDynamicTools([...projected.quarantinedTools, ...wrapped.quarantinedTools])
5459
+ tools: wrappedTools,
5460
+ quarantinedTools: dedupeQuarantinedDynamicTools(quarantinedTools)
5285
5461
  };
5286
5462
  }
5287
5463
  /** Applies the exact schema and hook-wrapper projection used by the executable Codex bridge. */
5288
5464
  function projectCodexExecutableDynamicTools(params) {
5289
5465
  const projected = projectCodexExecutableDynamicToolSurface(params.tools, params.hookContext);
5466
+ const finalized = finalizeCodexToolAvailability(projected.tools);
5290
5467
  return {
5291
- availableTools: projected.tools.map((entry) => entry.tool),
5292
- quarantinedTools: projected.quarantinedTools
5468
+ availableTools: finalized.tools.map((entry) => entry.tool),
5469
+ quarantinedTools: [...projected.quarantinedTools, ...finalized.quarantinedTools]
5293
5470
  };
5294
5471
  }
5295
5472
  function notifyAgentToolResult(observer, toolName, result, isError) {
@@ -5315,226 +5492,22 @@ function failedToolResult(message, status = "failed") {
5315
5492
  }
5316
5493
  };
5317
5494
  }
5318
- function wrapProjectedCodexDynamicTools(tools, hookContext) {
5319
- const wrappedTools = [];
5320
- const quarantinedTools = [];
5321
- for (const entry of tools) try {
5322
- if (isToolWrappedWithBeforeToolCallHook(entry.tool)) {
5323
- setBeforeToolCallDiagnosticsEnabled(entry.tool, false);
5324
- wrappedTools.push(entry);
5325
- continue;
5326
- }
5327
- wrappedTools.push({
5328
- ...entry,
5329
- tool: wrapToolWithBeforeToolCallHook(entry.tool, hookContext, { emitDiagnostics: false })
5330
- });
5331
- } catch {
5332
- quarantinedTools.push({
5333
- tool: entry.name,
5334
- violations: [`${entry.name} could not be wrapped for before-tool-call hooks`]
5335
- });
5336
- }
5337
- return {
5338
- tools: wrappedTools,
5339
- quarantinedTools
5340
- };
5341
- }
5342
- function createCodexDynamicToolSpecs(params) {
5343
- const specs = [];
5344
- const namespaceTools = [];
5345
- const directOnlyNamespaceTools = [];
5346
- const entries = params.loading === "direct" ? params.entries : params.entries.toSorted((left, right) => left.name.localeCompare(right.name));
5347
- for (const entry of entries) {
5348
- const functionSpec = createCodexDynamicToolFunctionSpec({ entry });
5349
- if (entry.name === "openclaw" && params.directToolNames.has(entry.name)) {
5350
- specs.push(functionSpec);
5351
- continue;
5352
- }
5353
- if (entry.tool.catalogMode === "direct-only") {
5354
- directOnlyNamespaceTools.push(functionSpec);
5355
- continue;
5356
- }
5357
- if (params.loading === "direct" || params.directToolNames.has(entry.name)) {
5358
- specs.push(functionSpec);
5359
- continue;
5360
- }
5361
- namespaceTools.push({
5362
- ...functionSpec,
5363
- deferLoading: true
5364
- });
5365
- }
5366
- if (namespaceTools.length > 0) specs.push({
5367
- type: "namespace",
5368
- name: CODEX_OPENCLAW_DYNAMIC_TOOL_NAMESPACE,
5369
- description: "",
5370
- tools: namespaceTools
5371
- });
5372
- if (directOnlyNamespaceTools.length > 0) specs.push({
5373
- type: "namespace",
5374
- name: CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE,
5375
- description: "",
5376
- tools: directOnlyNamespaceTools
5377
- });
5378
- return specs;
5379
- }
5380
- function createCodexDynamicToolFunctionSpec(params) {
5381
- return {
5382
- type: "function",
5383
- name: params.entry.name,
5384
- description: params.entry.description,
5385
- inputSchema: params.entry.inputSchema
5386
- };
5387
- }
5388
- function projectCodexDynamicTools(tools) {
5389
- const projectedTools = [];
5390
- const quarantinedTools = [];
5391
- let length;
5392
- try {
5393
- length = tools.length;
5394
- } catch {
5395
- return {
5396
- tools: [],
5397
- quarantinedTools: [{
5398
- tool: "tool[0]",
5399
- violations: ["tool[0] is unreadable"]
5400
- }]
5401
- };
5402
- }
5403
- for (let toolIndex = 0; toolIndex < length; toolIndex += 1) {
5404
- let tool;
5405
- try {
5406
- tool = tools[toolIndex];
5407
- } catch {
5408
- quarantinedTools.push({
5409
- tool: `tool[${toolIndex}]`,
5410
- violations: [`tool[${toolIndex}] is unreadable`]
5411
- });
5412
- continue;
5413
- }
5414
- const descriptor = readCodexDynamicToolDescriptor(tool, toolIndex);
5415
- if (!descriptor.ok) {
5416
- quarantinedTools.push(descriptor.diagnostic);
5417
- continue;
5418
- }
5419
- const normalizedParameters = normalizeOpenAIToolSchemas({
5420
- provider: "openai",
5421
- modelApi: "openai-chatgpt-responses",
5422
- tools: [{ parameters: descriptor.parameters }]
5423
- })[0]?.parameters;
5424
- const projection = projectRuntimeToolInputSchema(normalizedParameters ?? descriptor.parameters, `${descriptor.name}.inputSchema`);
5425
- if (projection.violations.length > 0) {
5426
- quarantinedTools.push({
5427
- tool: descriptor.name,
5428
- violations: projection.violations
5429
- });
5430
- continue;
5431
- }
5432
- if (!isRecord(projection.schema)) {
5433
- quarantinedTools.push({
5434
- tool: descriptor.name,
5435
- violations: [`${descriptor.name}.inputSchema must be a JSON object schema`]
5436
- });
5437
- continue;
5438
- }
5439
- projectedTools.push({
5440
- tool,
5441
- name: descriptor.name,
5442
- description: descriptor.description,
5443
- inputSchema: projection.schema
5444
- });
5445
- }
5446
- return {
5447
- tools: projectedTools,
5448
- quarantinedTools
5449
- };
5450
- }
5451
- function readCodexDynamicToolDescriptor(tool, toolIndex) {
5452
- const fallbackName = `tool[${toolIndex}]`;
5453
- let name;
5454
- try {
5455
- const rawName = tool.name;
5456
- if (typeof rawName !== "string" || !rawName) return {
5457
- ok: false,
5458
- diagnostic: {
5459
- tool: fallbackName,
5460
- violations: [`${fallbackName}.name must be a non-empty string`]
5461
- }
5462
- };
5463
- const trimmedName = rawName.trim();
5464
- let nameViolation;
5465
- if (!trimmedName) nameViolation = `${rawName}.name must not be empty`;
5466
- else if (trimmedName !== rawName) nameViolation = `${rawName}.name must not have leading or trailing whitespace`;
5467
- else if (!CODEX_DYNAMIC_TOOL_NAME_PATTERN.test(rawName)) nameViolation = `${rawName}.name must match ^[a-zA-Z0-9_-]+$`;
5468
- else if (rawName.length > CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS) nameViolation = `${rawName}.name must be at most ${CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS} characters`;
5469
- else if (rawName === "mcp" || rawName.startsWith("mcp__")) nameViolation = `${rawName}.name is reserved by Codex app-server`;
5470
- if (nameViolation) return {
5471
- ok: false,
5472
- diagnostic: {
5473
- tool: rawName,
5474
- violations: [nameViolation]
5475
- }
5476
- };
5477
- name = rawName;
5478
- } catch {
5479
- return {
5480
- ok: false,
5481
- diagnostic: {
5482
- tool: fallbackName,
5483
- violations: [`${fallbackName}.name is unreadable`]
5484
- }
5485
- };
5486
- }
5487
- let description;
5488
- try {
5489
- description = typeof tool.description === "string" ? tool.description : "";
5490
- } catch {
5491
- return {
5492
- ok: false,
5493
- diagnostic: {
5494
- tool: name,
5495
- violations: [`${name}.description is unreadable`]
5496
- }
5497
- };
5498
- }
5499
- let parameters;
5500
- try {
5501
- parameters = tool.parameters;
5502
- } catch {
5503
- return {
5504
- ok: false,
5505
- diagnostic: {
5506
- tool: name,
5507
- violations: [`${name}.inputSchema is unreadable`]
5508
- }
5509
- };
5510
- }
5511
- return {
5512
- ok: true,
5513
- name,
5514
- description,
5515
- parameters
5516
- };
5517
- }
5518
- function warnQuarantinedDynamicTools(params) {
5495
+ function reportQuarantinedDynamicTools(params) {
5519
5496
  if (params.tools.length === 0) return;
5520
- const unique = /* @__PURE__ */ new Map();
5521
- for (const tool of params.tools) unique.set(tool.tool, tool.violations);
5522
- embeddedAgentLog.warn(`codex app-server quarantined ${unique.size} unsupported dynamic tool ${unique.size === 1 ? "definition" : "definitions"}: ${[...unique.keys()].join(", ")}; retained ${params.availableToolCount} available and ${params.registeredToolCount} registered tools`, {
5523
- tools: [...unique.entries()].map(([tool, violations]) => ({
5497
+ embeddedAgentLog.warn(`codex app-server quarantined ${params.tools.length} unsupported dynamic tool ${params.tools.length === 1 ? "definition" : "definitions"}: ${params.tools.map((tool) => tool.tool).join(", ")}; retained ${params.availableToolCount} available and ${params.registeredToolCount} registered tools`, {
5498
+ tools: params.tools.map(({ tool, violations }) => ({
5524
5499
  tool,
5525
5500
  violations
5526
5501
  })),
5527
5502
  availableToolCount: params.availableToolCount,
5528
5503
  registeredToolCount: params.registeredToolCount
5529
5504
  });
5530
- }
5531
- function emitQuarantinedDynamicToolDiagnostics(tools, ctx) {
5532
- for (const tool of tools) emitTrustedDiagnosticEvent({
5505
+ for (const tool of params.tools) emitTrustedDiagnosticEvent({
5533
5506
  type: "tool.execution.blocked",
5534
- agentId: ctx?.agentId,
5535
- runId: ctx?.runId,
5536
- sessionId: ctx?.sessionId,
5537
- sessionKey: ctx?.sessionKey,
5507
+ agentId: params.hookContext?.agentId,
5508
+ runId: params.hookContext?.runId,
5509
+ sessionId: params.hookContext?.sessionId,
5510
+ sessionKey: params.hookContext?.sessionKey,
5538
5511
  toolName: tool.tool,
5539
5512
  deniedReason: "unsupported_tool_schema",
5540
5513
  reason: tool.violations.join(", ")
@@ -5569,15 +5542,26 @@ function collectToolTelemetry(params) {
5569
5542
  const response = normalizeHeartbeatToolResponse(params.result?.details);
5570
5543
  if (response) params.telemetry.heartbeatToolResponse = response;
5571
5544
  }
5572
- if (!params.isError && params.result) {
5545
+ if (params.result && !params.signal.aborted) {
5573
5546
  const media = extractToolResultMediaArtifact(params.result);
5574
5547
  if (media) {
5575
5548
  const mediaUrls = filterToolResultMediaUrls(params.toolName, media.mediaUrls, params.mediaTrustResult ?? params.result);
5576
5549
  const seen = new Set(params.telemetry.toolMediaUrls);
5577
- for (const mediaUrl of mediaUrls) if (!seen.has(mediaUrl)) {
5578
- seen.add(mediaUrl);
5579
- params.telemetry.toolMediaUrls.push(mediaUrl);
5550
+ const autoDeliveryMediaUrls = new Set(params.telemetry.toolAutoDeliveryMediaUrls);
5551
+ const rawAutoDeliveryMediaUrls = new Set(params.autoDeliveryTtsMediaUrls);
5552
+ let retainsCoreTtsMedia = false;
5553
+ for (const mediaUrl of mediaUrls) {
5554
+ if (!seen.has(mediaUrl)) {
5555
+ seen.add(mediaUrl);
5556
+ params.telemetry.toolMediaUrls.push(mediaUrl);
5557
+ }
5558
+ if (rawAutoDeliveryMediaUrls.has(mediaUrl)) {
5559
+ autoDeliveryMediaUrls.add(mediaUrl);
5560
+ retainsCoreTtsMedia = true;
5561
+ } else autoDeliveryMediaUrls.delete(mediaUrl);
5580
5562
  }
5563
+ params.telemetry.toolAutoDeliveryMediaUrls = [...autoDeliveryMediaUrls];
5564
+ if (retainsCoreTtsMedia && params.coreTtsToolResult && !params.telemetry.coreTtsToolResults.includes(params.coreTtsToolResult)) params.telemetry.coreTtsToolResults.push(params.coreTtsToolResult);
5581
5565
  if (media.audioAsVoice) params.telemetry.toolAudioAsVoice = true;
5582
5566
  }
5583
5567
  }
@@ -5814,4 +5798,4 @@ function isCronAddAction(args) {
5814
5798
  return typeof action === "string" && action.trim().toLowerCase() === "add";
5815
5799
  }
5816
5800
  //#endregion
5817
- export { shouldRequireCodexSandboxExecServerEnvironment as C, CodexNativeToolLifecycleProjector as D, readBoundedCodexRemoteWorkspaceFile as E, shouldEnableCodexAppServerNativeToolSurface as S, filterCodexVisionTools as T, resolveCodexAppServerHookChannelId as _, emitDynamicToolStartedDiagnostic as a, resolveCodexNodePlacementToolConstructionPlan as b, resolveCodexProviderWebSearchSupport as c, releaseCodexSandboxExecServerEnvironment as d, buildDynamicTools as f, resolveCodexAppServerExecutionCwd as g, formatCodexDynamicToolBuildStageSummary as h, emitDynamicToolErrorDiagnostic as i, resolveCodexProviderWebSearchSupportForClient as l, disableCodexPluginThreadConfig as m, projectCodexExecutableDynamicTools as n, emitDynamicToolTerminalDiagnostic as o, createCodexDynamicToolBuildStageTracker as p, routeCodexAppServerElicitationRequest as r, handleCodexAppServerApprovalRequest as s, createCodexDynamicToolBridge as t, ensureCodexSandboxExecServerEnvironment as u, resolveCodexExternalSandboxPolicyForOpenClawSandbox as v, shouldWarnCodexDynamicToolBuildStageSummary as w, resolveCodexSandboxEnvironmentSelection as x, resolveCodexMessageToolProvider as y };
5801
+ export { shouldRequireCodexSandboxExecServerEnvironment as C, shouldEnableCodexAppServerNativeToolSurface as S, filterCodexVisionTools as T, resolveCodexAppServerHookChannelId as _, emitDynamicToolStartedDiagnostic as a, resolveCodexNodePlacementToolConstructionPlan as b, ensureCodexSandboxExecServerEnvironment as c, CodexNativeToolLifecycleProjector as d, buildDynamicTools as f, resolveCodexAppServerExecutionCwd as g, formatCodexDynamicToolBuildStageSummary as h, emitDynamicToolErrorDiagnostic as i, releaseCodexSandboxExecServerEnvironment as l, disableCodexPluginThreadConfig as m, projectCodexExecutableDynamicTools as n, emitDynamicToolTerminalDiagnostic as o, createCodexDynamicToolBuildStageTracker as p, routeCodexAppServerElicitationRequest as r, handleCodexAppServerApprovalRequest as s, createCodexDynamicToolBridge as t, readBoundedCodexRemoteWorkspaceFile as u, resolveCodexExternalSandboxPolicyForOpenClawSandbox as v, shouldWarnCodexDynamicToolBuildStageSummary as w, resolveCodexSandboxEnvironmentSelection as x, resolveCodexMessageToolProvider as y };