@openclaw/codex 2026.9.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 (67) hide show
  1. package/dist/api.js +6 -6
  2. package/dist/{attempt-client-cleanup-DYHcRQAa.js → attempt-client-cleanup-Bp1ZOnk2.js} +76 -32
  3. package/dist/{attempt-context-DmQUNWwT.js → attempt-context-CSazkWns.js} +6 -6
  4. package/dist/{bounded-turn-wzJrwKKe.js → bounded-turn-DxGJRYbY.js} +46 -45
  5. package/dist/{capabilities-dIjNVCYH.js → capabilities-B1mgvpP8.js} +2 -1
  6. package/dist/cli-metadata.js +1 -1
  7. package/dist/{command-formatters-CLHhEB1Q.js → command-formatters-DSbGMXA0.js} +2 -2
  8. package/dist/{command-handlers-CTGb2csu.js → command-handlers-DOJvGv6-.js} +146 -74
  9. package/dist/{compact-_t0HQrUS.js → compact-Y_xBqrHN.js} +83 -48
  10. package/dist/{config-BhmfC1s6.js → config-CyapYSji.js} +47 -105
  11. package/dist/config-utils-C57JiJrU.js +69 -0
  12. package/dist/doctor-contract-api.js +4 -5
  13. package/dist/{dynamic-tools-BU9tmlYm.js → dynamic-tools-BwpvTxaX.js} +678 -638
  14. package/dist/{effective-mcp-catalog-DzYBW4h1.js → effective-mcp-catalog-CY9Jn4bA.js} +4 -3
  15. package/dist/{event-projector-assistant-message-DHRFXKbS.js → event-projector-assistant-message-B9kp6iGC.js} +11 -2
  16. package/dist/{event-projector-usage-C5mcrGjp.js → event-projector-usage-ZqEVbAEY.js} +30 -13
  17. package/dist/{harness-DXVZ5II5.js → harness-Czh2Sdoc.js} +36 -13
  18. package/dist/harness.js +1 -1
  19. package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
  20. package/dist/index.js +68 -84
  21. package/dist/{isolated-completion-kg9kv4iH.js → isolated-completion-CRh-51AK.js} +9 -21
  22. package/dist/{managed-binary-4-Vjz5GH.js → managed-binary-DcpnJF9x.js} +105 -73
  23. package/dist/media-understanding-provider.js +1 -1
  24. package/dist/{model-catalog-CUM68Hkl.js → model-catalog-DT5vo6qo.js} +5 -5
  25. package/dist/{models-sYnEc0gd.js → models-CcrgSMQR.js} +4 -4
  26. package/dist/{native-subagent-monitor-CnH147jH.js → native-subagent-monitor-B3bvQGcg.js} +12 -11
  27. package/dist/{node-exec-server.runtime-CstNHCmC.js → node-exec-server.runtime-nfbUOtUB.js} +3 -3
  28. package/dist/{plugin-approval-roundtrip-CNe-Xrjx.js → plugin-approval-roundtrip-aQVdyL5g.js} +1 -1
  29. package/dist/{plugin-marketplace-discovery-CfFlKqDl.js → plugin-marketplace-discovery-B7gHXJ3d.js} +35 -22
  30. package/dist/{plugin-metadata-cache-BFrPhb2c.js → plugin-metadata-cache-BwCLTY_U.js} +5 -5
  31. package/dist/{provider-capabilities-DW5p4k9w.js → provider-capabilities-BpG9Ltc5.js} +2 -2
  32. package/dist/{rate-limits-CawQJfUS.js → rate-limits-Fx_l4eNa.js} +1 -1
  33. package/dist/{request-DYAI5FQE.js → request-C8H22rPi.js} +2 -2
  34. package/dist/{run-attempt-D1hJO4fX.js → run-attempt-BzthFQSM.js} +977 -723
  35. package/dist/{runtime-artifact-C3jVimFn.js → runtime-artifact-Bh_gf6-t.js} +3 -3
  36. package/dist/{session-binding-BHuTCRZQ.js → session-binding-4Ki04sW-.js} +94 -282
  37. package/dist/session-binding-record-CyaXZvTe.js +270 -0
  38. package/dist/{session-catalog-yyvTWFOw.js → session-catalog-B47uwekb.js} +266 -114
  39. package/dist/{session-cli-CmFEXMAM.js → session-cli-BjLhpmS1.js} +1 -1
  40. package/dist/session-history-DWmMqiqW.js +93 -0
  41. package/dist/session-history-read-LHldwvyh.js +36 -0
  42. package/dist/session-history-worker-runtime.js +48 -0
  43. package/dist/{settled-turn-context-D3SNWtUn.js → session-history.worker-zZVcx0ME.js} +30 -31
  44. package/dist/session-history.worker.js +2 -0
  45. package/dist/{session-initialization-BeWzjGiR.js → session-initialization-BCFVnLL4.js} +2 -1
  46. package/dist/{session-permission-policy-DeWVTCMj.js → session-permission-policy-BaueHmSj.js} +1 -1
  47. package/dist/{session-retirement-BujDjXVF.js → session-retirement-DNdmYdFc.js} +7 -7
  48. package/dist/{session-upstream-marker-C351-5Qw.js → session-upstream-marker-DReTpGz6.js} +4 -3
  49. package/dist/settled-turn-context-BOM3Y1pl.js +45 -0
  50. package/dist/{settled-turn-finalizer-BVJx4yoc.js → settled-turn-finalizer-BI-b1sry.js} +53 -42
  51. package/dist/{shared-client-DxcfF9KX.js → shared-client-CKhX2kFt.js} +279 -268
  52. package/dist/{side-question-BTZAII4l.js → side-question-B6DWAw1A.js} +85 -36
  53. package/dist/{thread-lifecycle-DAcEJ_Om.js → thread-lifecycle-DPK30jad.js} +382 -388
  54. package/dist/{thread-ownership-zGokd8i1.js → thread-ownership-CbucAmmp.js} +5 -5
  55. package/dist/thread-policy-CTalhWFy.js +105 -0
  56. package/dist/{thread-resume-C7pyVKeW.js → thread-resume-BLgqkUc1.js} +7 -7
  57. package/dist/{transcript-mirror-DTUxFWa_.js → transcript-mirror-_njEB84z.js} +67 -125
  58. package/dist/transcript-mirror-attestation-Byl34PC-.js +76 -0
  59. package/dist/{transport-stdio-CyPldczm.js → transport-stdio-CzTngV0j.js} +10 -8
  60. package/dist/{upstream-session-fork-CYlBIGVt.js → upstream-session-fork-1fwbNKzQ.js} +85 -165
  61. package/dist/{usage-Bj-rrzkR.js → usage-i9fv5-4m.js} +3 -3
  62. package/dist/{web-search-provider.runtime-D2FWSK4w.js → web-search-provider.runtime-DKQn67A7.js} +3 -3
  63. package/package.json +6 -6
  64. package/dist/binding-connection-E6uDsPlx.js +0 -45
  65. package/dist/launch-args-RXQwn8zV.js +0 -90
  66. package/dist/session-history-C5J1AKzG.js +0 -87
  67. package/dist/thread-policy-g4KbJPzp.js +0 -54
@@ -1,23 +1,23 @@
1
- import { $t as isJsonObject, Qt as flattenCodexDynamicToolFunctions, Zt as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, _t as resolveCodexToolAbortTerminalReason, bt as withDynamicToolTranscriptDetails, it as readCodexTurn, ot as createCodexElicitationResponse, vt as createFailedDynamicToolResponse, yt as withDynamicToolExecutionState } from "./shared-client-DxcfF9KX.js";
2
- import { E as readCodexPluginConfig, w as isCodexRemoteExecPlacementSandbox } from "./config-BhmfC1s6.js";
3
- import { E as itemStatus, I as readItem, b as auditNativeToolUnfinishedStatus, h as isCodexNotificationForTurn, v as auditNativeToolName, y as auditNativeToolTerminalStatus } from "./attempt-client-cleanup-DYHcRQAa.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-DAcEJ_Om.js";
5
- import { l as resolveCodexNativeExecutionPolicy, u as resolveCodexNodeExecToolOverrides } from "./request-DYAI5FQE.js";
6
- import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-CnfoUPCE.js";
7
- import { a as formatCodexDisplayText } from "./command-formatters-CLHhEB1Q.js";
8
- 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-CNe-Xrjx.js";
9
- 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-DW5p4k9w.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";
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";
16
18
  import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
17
- import { createHash, randomUUID } from "node:crypto";
18
19
  import { resolveAgentDir as resolveAgentDir$1 } from "openclaw/plugin-sdk/agent-runtime";
19
20
  import { formatErrorMessage as formatErrorMessage$1, redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
20
- import { killProcessTree } from "openclaw/plugin-sdk/process-runtime";
21
21
  import { SsrFBlockedError, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
22
22
  import { spawn } from "node:child_process";
23
23
  import { once } from "node:events";
@@ -26,238 +26,18 @@ import { DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, estimateToolResultTextChars, resolv
26
26
  import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
27
27
  import { isIP } from "node:net";
28
28
  import { WebSocketServer } from "ws";
29
- import { fileURLToPath, pathToFileURL } from "node:url";
30
29
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
31
30
  import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
32
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 { consumeTrustedToolNoStartError, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
36
+ import { consumeTrustedToolNoStartError, copyInternalToolResultState, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
37
37
  import { normalizeOpenAIStrictCompatSchema } from "openclaw/plugin-sdk/provider-tools";
38
- //#region extensions/codex/src/app-server/event-projector-native-tool-lifecycle.ts
39
- function isMcpToolCallItemNotification(method, params) {
40
- return (method === "item/started" || method === "item/completed") && isJsonObject(params.item) && params.item.type === "mcpToolCall";
41
- }
42
- /** Owns native item lifetimes for diagnostics and MCP approval correlation. */
43
- var CodexNativeToolLifecycleProjector = class {
44
- constructor(context, threadId, turnId, options = {}) {
45
- this.context = context;
46
- this.threadId = threadId;
47
- this.turnId = turnId;
48
- this.options = options;
49
- this.startedAtByItem = /* @__PURE__ */ new Map();
50
- this.activeItems = /* @__PURE__ */ new Map();
51
- this.webSearchCompletionByItem = /* @__PURE__ */ new Map();
52
- this.completedItemIds = /* @__PURE__ */ new Set();
53
- this.approvalFailureDispositionByItem = /* @__PURE__ */ new Map();
54
- this.preToolUseFailureByItem = /* @__PURE__ */ new Map();
55
- this.finalized = false;
56
- this.turnCompleted = false;
57
- this.pendingMcpNotifications = 0;
58
- }
59
- getActiveMcpToolCall(serverName) {
60
- if (this.finalized || this.turnCompleted || this.pendingMcpNotifications > 0 || this.options.runAbortSignal?.aborted) return;
61
- let candidate;
62
- for (const { mcpToolCall } of this.activeItems.values()) {
63
- if (mcpToolCall?.server !== serverName) continue;
64
- if (candidate) return;
65
- candidate = mcpToolCall;
66
- }
67
- 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;
68
- return {
69
- id: candidate.id,
70
- server: candidate.server,
71
- tool: candidate.tool,
72
- arguments: candidate.arguments
73
- };
74
- }
75
- recordMcpToolCallReceipt(notification) {
76
- const params = isJsonObject(notification.params) ? notification.params : void 0;
77
- if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
78
- if (isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications += 1;
79
- else if (notification.method === "turn/completed" && readCodexTurn(params.turn)?.id === this.turnId) this.turnCompleted = true;
80
- }
81
- handleNotification(notification) {
82
- const params = isJsonObject(notification.params) ? notification.params : void 0;
83
- if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
84
- if (this.pendingMcpNotifications > 0 && isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications -= 1;
85
- if (notification.method === "turn/completed") {
86
- const turn = readCodexTurn(params.turn);
87
- if (!turn || turn.id !== this.turnId) return;
88
- this.turnCompleted = true;
89
- for (const item of turn.items ?? []) this.recordSnapshotItem(item);
90
- return;
91
- }
92
- if (notification.method === "rawResponseItem/completed") {
93
- const item = isJsonObject(params.item) ? params.item : void 0;
94
- if (item) this.recordRawWebSearchResult(item);
95
- return;
96
- }
97
- if (notification.method !== "item/started" && notification.method !== "item/completed") return;
98
- const item = readItem(params.item);
99
- if (!item) return;
100
- this.recordItem({
101
- phase: notification.method === "item/started" ? "start" : "result",
102
- item,
103
- sourceTimestampMs: asDateTimestampMs(notification.method === "item/started" ? params.startedAtMs : params.completedAtMs)
104
- });
105
- }
106
- recordItem(params) {
107
- const toolName = auditNativeToolName(params.item) ?? (params.item.type === "mcpToolCall" ? params.item.type : void 0);
108
- if (!toolName || this.completedItemIds.has(params.item.id)) return;
109
- if (params.phase === "start") {
110
- this.recordStarted(params.item.id, toolName, auditNativeToolUnfinishedStatus(params.item), params.sourceTimestampMs, params.item.type === "mcpToolCall" ? params.item : void 0);
111
- return;
112
- }
113
- if (params.item.type === "webSearch") {
114
- this.webSearchCompletionByItem.set(params.item.id, {
115
- runWasAborted: this.options.runAbortSignal?.aborted === true,
116
- sourceTimestampMs: params.sourceTimestampMs
117
- });
118
- return;
119
- }
120
- const itemDurationMs = typeof params.item.durationMs === "number" ? params.item.durationMs : void 0;
121
- this.recordTerminal(params.item.id, toolName, auditNativeToolTerminalStatus(params.item), {
122
- itemDurationMs,
123
- sourceTimestampMs: params.sourceTimestampMs
124
- });
125
- }
126
- recordApprovalFailureDisposition(toolCallId, disposition) {
127
- if (!this.completedItemIds.has(toolCallId)) this.approvalFailureDispositionByItem.set(toolCallId, disposition);
128
- }
129
- recordPreToolUseFailure(failure, runWasAborted = this.options.runAbortSignal?.aborted === true) {
130
- if (this.completedItemIds.has(failure.toolCallId)) return;
131
- const record = {
132
- failure,
133
- terminalReason: runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : failure.disposition
134
- };
135
- if (this.finalized) {
136
- this.completedItemIds.add(failure.toolCallId);
137
- this.emitPreToolUseFailure(record, failure.toolName, failure.durationMs);
138
- return;
139
- }
140
- this.preToolUseFailureByItem.set(failure.toolCallId, record);
141
- }
142
- recordRawWebSearchResult(item) {
143
- if (readStringField(item, "type") !== "web_search_call") return;
144
- const toolCallId = readStringField(item, "id");
145
- if (!toolCallId || this.completedItemIds.has(toolCallId)) return;
146
- const toolName = "web_search";
147
- this.recordStarted(toolCallId, toolName, "unknown");
148
- const rawStatus = readStringField(item, "status");
149
- if (rawStatus === "in_progress" || rawStatus === "running") return;
150
- const status = rawStatus === "completed" ? "completed" : rawStatus === "cancelled" ? "cancelled" : rawStatus === "failed" || rawStatus === "error" || rawStatus === "incomplete" ? "failed" : "unknown";
151
- this.recordTerminal(toolCallId, toolName, status, { sourceTimestampMs: this.webSearchCompletionByItem.get(toolCallId)?.sourceTimestampMs });
152
- }
153
- recordTerminal(toolCallId, toolName, status, options = {}) {
154
- const runWasAborted = options.runWasAborted ?? this.options.runAbortSignal?.aborted === true;
155
- const preToolUseFailure = this.preToolUseFailureByItem.get(toolCallId);
156
- this.preToolUseFailureByItem.delete(toolCallId);
157
- const approvalFailureDisposition = this.approvalFailureDispositionByItem.get(toolCallId);
158
- this.approvalFailureDispositionByItem.delete(toolCallId);
159
- this.completedItemIds.add(toolCallId);
160
- this.activeItems.delete(toolCallId);
161
- this.webSearchCompletionByItem.delete(toolCallId);
162
- const startedAt = this.startedAtByItem.get(toolCallId);
163
- this.startedAtByItem.delete(toolCallId);
164
- const endedAt = options.sourceTimestampMs ?? Date.now();
165
- const durationMs = options.itemDurationMs ?? (startedAt === void 0 ? 0 : Math.max(0, endedAt - startedAt));
166
- if (preToolUseFailure) {
167
- this.emitPreToolUseFailure(preToolUseFailure, toolName, durationMs, options.sourceTimestampMs);
168
- return;
169
- }
170
- const terminalEvent = approvalFailureDisposition ? {
171
- type: "tool.execution.error",
172
- durationMs,
173
- errorCategory: "codex_native_tool_approval",
174
- terminalReason: approvalFailureDisposition
175
- } : status === "blocked" ? {
176
- type: "tool.execution.blocked",
177
- reason: "codex_native_tool_blocked",
178
- deniedReason: "codex_native_tool_blocked"
179
- } : status === "failed" || status === "cancelled" || status === "unknown" ? {
180
- type: "tool.execution.error",
181
- durationMs,
182
- errorCategory: status === "unknown" ? "codex_native_tool_outcome_unknown" : status === "cancelled" ? "aborted" : "codex_native_tool_error",
183
- ...status === "unknown" ? { errorCode: "tool_outcome_unknown" } : {},
184
- terminalReason: status === "unknown" ? "failed" : runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : status === "cancelled" ? "cancelled" : "failed"
185
- } : {
186
- type: "tool.execution.completed",
187
- durationMs
188
- };
189
- emitTrustedDiagnosticEvent({
190
- ...this.buildBase(toolCallId, toolName),
191
- ...terminalEvent,
192
- ...options.sourceTimestampMs !== void 0 ? { sourceTimestampMs: options.sourceTimestampMs } : {}
193
- });
194
- }
195
- finalizeActive(runWasAborted = this.options.runAbortSignal?.aborted === true) {
196
- this.finalized = true;
197
- for (const [toolCallId, { toolName, unfinishedStatus }] of this.activeItems) {
198
- const webSearchCompletion = this.webSearchCompletionByItem.get(toolCallId);
199
- const itemRunWasAborted = webSearchCompletion ? webSearchCompletion.runWasAborted : runWasAborted;
200
- this.recordTerminal(toolCallId, toolName, unfinishedStatus, {
201
- runWasAborted: itemRunWasAborted,
202
- sourceTimestampMs: webSearchCompletion?.sourceTimestampMs
203
- });
204
- }
205
- for (const [toolCallId, record] of this.preToolUseFailureByItem) if (!this.completedItemIds.has(toolCallId)) this.recordTerminal(toolCallId, record.failure.toolName, "failed", { itemDurationMs: record.failure.durationMs });
206
- this.activeItems.clear();
207
- this.webSearchCompletionByItem.clear();
208
- this.approvalFailureDispositionByItem.clear();
209
- this.preToolUseFailureByItem.clear();
210
- }
211
- emitPreToolUseFailure(record, toolName, durationMs, sourceTimestampMs) {
212
- emitCodexNativePreToolUseFailureDiagnostic({
213
- agentId: this.context.agentId,
214
- sessionId: this.context.sessionId,
215
- sessionKey: this.context.sessionKey,
216
- runId: this.context.runId,
217
- failure: {
218
- ...record.failure,
219
- toolName,
220
- durationMs
221
- },
222
- terminalReason: record.terminalReason,
223
- sourceTimestampMs
224
- });
225
- }
226
- recordSnapshotItem(item) {
227
- if (!auditNativeToolName(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
228
- const toolName = auditNativeToolName(item);
229
- if (!toolName) return;
230
- this.recordStarted(item.id, toolName, auditNativeToolUnfinishedStatus(item));
231
- this.recordItem({
232
- phase: "result",
233
- item
234
- });
235
- }
236
- recordStarted(toolCallId, toolName, unfinishedStatus, sourceTimestampMs, mcpToolCall) {
237
- if (this.activeItems.has(toolCallId)) return;
238
- this.startedAtByItem.set(toolCallId, sourceTimestampMs ?? Date.now());
239
- this.activeItems.set(toolCallId, {
240
- toolName,
241
- unfinishedStatus,
242
- mcpToolCall
243
- });
244
- emitTrustedDiagnosticEvent({
245
- type: "tool.execution.started",
246
- ...this.buildBase(toolCallId, toolName),
247
- ...sourceTimestampMs !== void 0 ? { sourceTimestampMs } : {}
248
- });
249
- }
250
- buildBase(toolCallId, toolName) {
251
- return {
252
- agentId: this.context.agentId,
253
- runId: this.context.runId,
254
- sessionId: this.context.sessionId,
255
- sessionKey: this.context.sessionKey,
256
- toolName,
257
- toolCallId
258
- };
259
- }
260
- };
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 = {};
261
41
  //#endregion
262
42
  //#region extensions/codex/src/app-server/remote-workspace-path.ts
263
43
  /** Projects a gateway workspace path into the remote Codex execution workspace. */
@@ -307,273 +87,6 @@ function joinRemoteWorkspacePath(remoteRoot, suffix) {
307
87
  return remoteRoot.endsWith("/") ? `${remoteRoot}${suffix}` : `${remoteRoot}/${suffix}`;
308
88
  }
309
89
  //#endregion
310
- //#region extensions/codex/src/app-server/remote-workspace-media.ts
311
- const REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS = 6e4;
312
- const REMOTE_WORKSPACE_MEDIA_MAX_BYTES = 67108864;
313
- const REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS = 16;
314
- const CODEX_REMOTE_MEDIA_CHUNK_BYTES = 524288;
315
- const CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES = 1048576;
316
- const CODEX_BOUNDED_REMOTE_FILE_READER = [
317
- "try{",
318
- "const fs=require(\"node:fs\");",
319
- "const path=require(\"node:path\");",
320
- "const file=process.argv[1];",
321
- "const max=Number(process.argv[2]);",
322
- "const offset=Number(process.argv[3]);",
323
- "const chunk=Number(process.argv[4]);",
324
- "const workspace=process.argv[5];",
325
- "if(!Number.isSafeInteger(max)||max<0)throw Error(\"invalid media byte limit\");",
326
- "if(!Number.isSafeInteger(offset)||offset<0||!Number.isSafeInteger(chunk)||chunk<=0)throw Error(\"invalid media chunk\");",
327
- "if(fs.lstatSync(file).isSymbolicLink())throw Error(\"symbolic links are not allowed\");",
328
- "const noFollow=fs.constants.O_NOFOLLOW??0;",
329
- "const fd=fs.openSync(file,fs.constants.O_RDONLY|noFollow);",
330
- "try{",
331
- "const before=fs.fstatSync(fd);",
332
- "if(!before.isFile())throw Error(\"not a regular file\");",
333
- "if(workspace){",
334
- "const descriptor=process.platform===\"linux\"?fs.realpathSync(`/proc/self/fd/${fd}`):fs.realpathSync(file);",
335
- "const relative=path.relative(fs.realpathSync(workspace),descriptor);",
336
- "if(!relative||relative===\"..\"||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative))throw Error(\"file escapes remote workspace\");",
337
- "const verified=fs.statSync(descriptor);",
338
- "if(verified.dev!==before.dev||verified.ino!==before.ino)throw Error(\"file changed while being opened\");",
339
- "}",
340
- "if(before.size>max)throw Error(`file exceeds limit of ${max} bytes`);",
341
- "if(offset>before.size)throw Error(\"file changed while being read\");",
342
- "const expected=Math.min(chunk,before.size-offset);",
343
- "const buffer=Buffer.allocUnsafe(expected);",
344
- "let total=0;",
345
- "while(total<buffer.length){",
346
- "const count=fs.readSync(fd,buffer,total,buffer.length-total,offset+total);",
347
- "if(count===0)break;total+=count;",
348
- "}",
349
- "if(total!==expected)throw Error(\"file changed while being read\");",
350
- "const after=fs.fstatSync(fd);",
351
- "const revision=stat=>[stat.dev,stat.ino,stat.size,stat.mtimeMs,stat.ctimeMs].join(\":\");",
352
- "if(!after.isFile()||revision(after)!==revision(before))throw Error(\"file changed while being read\");",
353
- "process.stdout.write(JSON.stringify({dataBase64:buffer.toString(\"base64\"),size:before.size,revision:revision(before)}));",
354
- "}finally{fs.closeSync(fd)}",
355
- "}catch(error){process.stderr.write(error instanceof Error?error.message:String(error));process.exitCode=1}"
356
- ].join("");
357
- const MESSAGE_MEDIA_KEYS = [
358
- "media",
359
- "mediaUrl",
360
- "media_url",
361
- "path",
362
- "filePath",
363
- "fileUrl",
364
- "imageUrl",
365
- "image_url"
366
- ];
367
- const MESSAGE_MEDIA_ARRAY_KEYS = [
368
- "mediaUrls",
369
- "media_urls",
370
- "imageUrls",
371
- "image_urls"
372
- ];
373
- const ATTACHMENT_MEDIA_KEYS = [
374
- "media",
375
- "mediaUrl",
376
- "path",
377
- "filePath",
378
- "fileUrl",
379
- "url"
380
- ];
381
- /** Reads actual remote bytes with a cap enforced by Codex before transport. */
382
- async function readBoundedCodexRemoteWorkspaceFile(params) {
383
- if (!Number.isSafeInteger(params.maxBytes) || params.maxBytes < 0) throw new Error("Codex remote workspace upload requires a valid media byte limit.");
384
- params.signal?.throwIfAborted();
385
- const chunks = [];
386
- let offset = 0;
387
- let expectedSize;
388
- let expectedRevision;
389
- const startedAt = Date.now();
390
- do {
391
- params.signal?.throwIfAborted();
392
- const timeoutMs = params.timeoutMs === void 0 ? void 0 : params.timeoutMs - (Date.now() - startedAt);
393
- if (timeoutMs !== void 0 && timeoutMs <= 0) throw new Error("Codex remote workspace file transfer timed out.");
394
- let response;
395
- try {
396
- response = await params.client.request("command/exec", {
397
- command: [
398
- "node",
399
- "-e",
400
- CODEX_BOUNDED_REMOTE_FILE_READER,
401
- "--",
402
- params.path,
403
- String(params.maxBytes),
404
- String(offset),
405
- String(CODEX_REMOTE_MEDIA_CHUNK_BYTES),
406
- ...params.workspaceRoot ? [params.workspaceRoot] : []
407
- ],
408
- env: {
409
- NODE_OPTIONS: null,
410
- NODE_PATH: null
411
- },
412
- ...timeoutMs === void 0 ? {} : { timeoutMs }
413
- }, {
414
- signal: params.signal,
415
- timeoutMs
416
- });
417
- } catch (error) {
418
- 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 });
419
- throw error;
420
- }
421
- if (!response || response.exitCode !== 0) {
422
- const detail = typeof response?.stderr === "string" ? response.stderr.trim() : "";
423
- throw new Error(`Codex remote workspace artifact could not be read: ${params.path}${detail ? `: ${detail}` : ""}`);
424
- }
425
- 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.");
426
- let payload;
427
- try {
428
- payload = JSON.parse(response.stdout);
429
- } catch {
430
- throw new Error("Codex remote workspace artifact returned invalid chunk data.");
431
- }
432
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
433
- const chunk = payload;
434
- 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.");
435
- if (expectedSize === void 0) {
436
- expectedSize = chunk.size;
437
- expectedRevision = chunk.revision;
438
- }
439
- if (chunk.size !== expectedSize || chunk.revision !== expectedRevision) throw new Error("Codex remote workspace artifact changed during chunked transfer.");
440
- const remainingBytes = expectedSize - offset;
441
- const expectedChunkBytes = Math.min(CODEX_REMOTE_MEDIA_CHUNK_BYTES, remainingBytes);
442
- if (chunk.dataBase64.length > Math.ceil(expectedChunkBytes / 3) * 4) throw new Error("Codex remote workspace artifact returned oversized chunk data.");
443
- const buffer = Buffer.from(chunk.dataBase64, "base64");
444
- if (buffer.byteLength !== expectedChunkBytes || buffer.toString("base64") !== chunk.dataBase64) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
445
- chunks.push(buffer);
446
- offset += buffer.byteLength;
447
- } while (offset < (expectedSize ?? 0));
448
- return { dataBase64: Buffer.concat(chunks, offset).toString("base64") };
449
- }
450
- /** Stages authoritative bounded remote bytes into immutable Gateway-owned media. */
451
- async function prepareCodexRemoteWorkspaceMessageMedia(params) {
452
- const { localWorkspaceRoot, remoteWorkspaceRoot } = params;
453
- if (!localWorkspaceRoot || !remoteWorkspaceRoot) return params.args;
454
- const remotePathsByLocalPath = /* @__PURE__ */ new Map();
455
- const gatewayManagedPaths = /* @__PURE__ */ new Set();
456
- const gatewayMediaRoot = getMediaDir();
457
- let attachmentEntries = 0;
458
- const mapMediaPath = (value) => {
459
- if (typeof value !== "string") return value;
460
- if (path.isAbsolute(value) && isPathStrictlyInside(gatewayMediaRoot, value)) {
461
- attachmentEntries += 1;
462
- gatewayManagedPaths.add(value);
463
- return value;
464
- }
465
- const mapped = mapCodexAppServerLocalWorkspacePath({
466
- value,
467
- localWorkspaceRoot,
468
- remoteWorkspaceRoot
469
- });
470
- if (value.trim() && !isCodexPassThroughMediaSource(value)) {
471
- attachmentEntries += 1;
472
- remotePathsByLocalPath.set(mapped, mapCodexAppServerRemoteWorkspacePath({
473
- value: mapped,
474
- localWorkspaceRoot,
475
- remoteWorkspaceRoot
476
- }));
477
- }
478
- return mapped;
479
- };
480
- let mappedArgs = params.args;
481
- const setMappedValue = (key, value) => {
482
- if (value === params.args[key]) return;
483
- if (mappedArgs === params.args) mappedArgs = { ...params.args };
484
- mappedArgs[key] = value;
485
- };
486
- for (const key of MESSAGE_MEDIA_KEYS) setMappedValue(key, mapMediaPath(params.args[key]));
487
- for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
488
- const value = params.args[key];
489
- if (Array.isArray(value)) {
490
- const mapped = value.map(mapMediaPath);
491
- if (mapped.some((entry, index) => entry !== value[index])) setMappedValue(key, mapped);
492
- }
493
- }
494
- if (Array.isArray(params.args.attachments)) {
495
- const attachments = params.args.attachments;
496
- const mapped = attachments.map((attachment) => {
497
- if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
498
- const record = attachment;
499
- let mappedAttachment = record;
500
- for (const key of ATTACHMENT_MEDIA_KEYS) {
501
- const value = mapMediaPath(record[key]);
502
- if (value !== record[key]) {
503
- if (mappedAttachment === record) mappedAttachment = { ...record };
504
- mappedAttachment[key] = value;
505
- }
506
- }
507
- return mappedAttachment;
508
- });
509
- if (mapped.some((attachment, index) => attachment !== attachments[index])) setMappedValue("attachments", mapped);
510
- }
511
- if (attachmentEntries > REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS) throw new Error(`Codex remote workspace upload exceeds the ${REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS}-attachment limit.`);
512
- for (const managedPath of gatewayManagedPaths) await assertGatewayManagedMediaPath(managedPath, gatewayMediaRoot);
513
- if (remotePathsByLocalPath.size === 0) return mappedArgs;
514
- const readRemoteFile = params.readRemoteFile;
515
- if (!readRemoteFile) throw new Error("Codex remote workspace file transfer requires an active app-server client.");
516
- const maxBytes = params.maxBytes ?? REMOTE_WORKSPACE_MEDIA_MAX_BYTES;
517
- const timeoutMs = params.timeoutMs ?? REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS;
518
- const deadline = Date.now() + timeoutMs;
519
- const stagedPaths = /* @__PURE__ */ new Map();
520
- let totalBytes = 0;
521
- for (const [localPath, remotePath] of remotePathsByLocalPath) {
522
- params.signal?.throwIfAborted();
523
- const remainingBytes = maxBytes - totalBytes;
524
- const remainingMs = deadline - Date.now();
525
- if (remainingMs <= 0) throw new Error("Codex remote workspace attachment batch timed out.");
526
- const response = await readRemoteFile({
527
- path: remotePath,
528
- maxBytes: remainingBytes,
529
- workspaceRoot: remoteWorkspaceRoot,
530
- signal: params.signal,
531
- timeoutMs: remainingMs
532
- });
533
- if (!response || typeof response.dataBase64 !== "string") throw new Error(`Codex remote workspace artifact returned no file data: ${remotePath}`);
534
- if (response.dataBase64.length > Math.ceil(remainingBytes / 3) * 4) throw new Error(`Codex remote workspace artifact exceeds the limit of ${remainingBytes} bytes.`);
535
- const remoteBuffer = Buffer.from(response.dataBase64, "base64");
536
- if (remoteBuffer.byteLength > remainingBytes || remoteBuffer.toString("base64") !== response.dataBase64) throw new Error(`Codex remote workspace artifact returned invalid or oversized file data: ${remotePath}`);
537
- totalBytes += remoteBuffer.byteLength;
538
- const saved = await saveMediaBuffer(remoteBuffer, void 0, "outbound", maxBytes, path.basename(remotePath));
539
- stagedPaths.set(localPath, saved.path);
540
- }
541
- return mapMessageMediaValues(mappedArgs, (value) => stagedPaths.get(value) ?? value);
542
- }
543
- async function assertGatewayManagedMediaPath(value, mediaRoot) {
544
- const opened = await (await root(mediaRoot, { symlinks: "reject" })).open(path.relative(mediaRoot, value), { symlinks: "reject" });
545
- try {
546
- if (!(await opened.handle.stat()).isFile()) throw new Error(`Codex Gateway-managed media is not a regular file: ${value}`);
547
- } finally {
548
- await opened[Symbol.asyncDispose]();
549
- }
550
- }
551
- function mapMessageMediaValues(args, mapValue) {
552
- const mapped = { ...args };
553
- for (const key of MESSAGE_MEDIA_KEYS) {
554
- const value = mapped[key];
555
- if (typeof value === "string") mapped[key] = mapValue(value);
556
- }
557
- for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
558
- const value = mapped[key];
559
- if (Array.isArray(value)) mapped[key] = value.map((entry) => typeof entry === "string" ? mapValue(entry) : entry);
560
- }
561
- if (Array.isArray(mapped.attachments)) mapped.attachments = mapped.attachments.map((attachment) => {
562
- if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
563
- const record = { ...attachment };
564
- for (const key of ATTACHMENT_MEDIA_KEYS) {
565
- const value = record[key];
566
- if (typeof value === "string") record[key] = mapValue(value);
567
- }
568
- return record;
569
- });
570
- return mapped;
571
- }
572
- //#endregion
573
- //#region extensions/codex/src/app-server/dynamic-tool-build-state.ts
574
- /** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
575
- const dynamicToolBuildState = {};
576
- //#endregion
577
90
  //#region extensions/codex/src/app-server/vision-tools.ts
578
91
  /**
579
92
  * Codex's enabled native surface includes its stable view_image loader. Keep
@@ -641,17 +154,8 @@ function resolveCodexAppServerHookChannelId(params, sandboxSessionKey) {
641
154
  }
642
155
  const CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS = 1e3;
643
156
  const CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS = 500;
644
- /** Creates cheap optional timing instrumentation for the dynamic-tool hot path. */
645
- function createCodexDynamicToolBuildStageTracker(options = {}) {
646
- if (!options.enabled) return {
647
- mark() {},
648
- snapshot() {
649
- return {
650
- totalMs: 0,
651
- stages: []
652
- };
653
- }
654
- };
157
+ /** Captures bounded preparation stages before a slow turn needs diagnosis. */
158
+ function createCodexDynamicToolBuildStageTracker() {
655
159
  const startedAt = Date.now();
656
160
  let previousAt = startedAt;
657
161
  const stages = [];
@@ -675,8 +179,10 @@ function createCodexDynamicToolBuildStageTracker(options = {}) {
675
179
  };
676
180
  }
677
181
  /** Returns true when dynamic-tool construction is slow enough to warrant a warning log. */
678
- function shouldWarnCodexDynamicToolBuildStageSummary(summary) {
679
- 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);
680
186
  }
681
187
  /** Formats per-stage timings into the compact form used by Codex app-server logs. */
682
188
  function formatCodexDynamicToolBuildStageSummary(summary) {
@@ -702,7 +208,7 @@ async function buildDynamicTools(input) {
702
208
  input.onWebSearchPolicyResolved?.(false);
703
209
  return [];
704
210
  }
705
- const toolBuildStages = createCodexDynamicToolBuildStageTracker({ enabled: input.profilerEnabled });
211
+ const toolBuildStages = createCodexDynamicToolBuildStageTracker();
706
212
  const modelHasVision = params.model.input?.includes("image") ?? false;
707
213
  const agentDir = params.agentDir ?? resolveAgentDir$1(params.config ?? {}, input.sessionAgentId);
708
214
  const injectedOpenClawCodingToolsFactory = dynamicToolBuildState.openClawCodingToolsFactory;
@@ -738,6 +244,10 @@ async function buildDynamicTools(input) {
738
244
  ...toolConstructionPlan ? { toolConstructionPlan } : {},
739
245
  messageProvider: resolveCodexMessageToolProvider(params),
740
246
  toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
247
+ ...params.onToolResult ? { questionPrompt: {
248
+ send: params.onToolResult,
249
+ ...params.messageChannel ? { messageChannel: params.messageChannel } : {}
250
+ } } : {},
741
251
  clientCaps: params.clientCaps,
742
252
  chatType: params.chatType,
743
253
  agentAccountId: params.agentAccountId,
@@ -911,7 +421,7 @@ async function buildDynamicTools(input) {
911
421
  }))
912
422
  });
913
423
  const summary = toolBuildStages.snapshot();
914
- if (shouldWarnCodexDynamicToolBuildStageSummary(summary)) {
424
+ if (shouldWarnCodexDynamicToolBuildStageSummary(summary, input.profilerEnabled)) {
915
425
  const phase = input.forceHeartbeatTool ? "registered-tools" : "runtime-tools";
916
426
  embeddedAgentLog.warn(`codex app-server dynamic tool build timings runId=${params.runId} sessionId=${params.sessionId} phase=${phase} totalMs=${summary.totalMs} stages=${formatCodexDynamicToolBuildStageSummary(summary)}`, {
917
427
  runId: params.runId,
@@ -1033,82 +543,589 @@ function disableCodexPluginThreadConfig(pluginConfig) {
1033
543
  ...config.codexPlugins,
1034
544
  enabled: false
1035
545
  }
1036
- };
546
+ };
547
+ }
548
+ /** Adds sandbox_exec/process aliases when native Code Mode cannot directly honor the sandbox. */
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;
551
+ const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
552
+ const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
553
+ if (!execTool || !processTool) return filteredTools;
554
+ const sandboxExecTool = {
555
+ ...execTool,
556
+ name: "sandbox_exec",
557
+ description: "Run a shell command through OpenClaw's configured sandbox backend for this session. Use when OpenClaw sandboxing is active or when a command must execute in the sandbox backend, such as an SSH-backed sandbox or Docker container-path bind layout. Use Codex's native shell only when no OpenClaw sandbox is active and native Code Mode is available.",
558
+ execute: async (toolCallId, args, signal, onUpdate) => {
559
+ const result = await execTool.execute(toolCallId, args, signal, onUpdate);
560
+ return {
561
+ ...result,
562
+ content: result.content.map((item) => item.type === "text" ? Object.assign({}, item, { text: item.text.replace("Use process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.", "Use sandbox_process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.") }) : item)
563
+ };
564
+ }
565
+ };
566
+ const sandboxProcessTool = {
567
+ ...processTool,
568
+ name: "sandbox_process",
569
+ description: "Manage background shell sessions through OpenClaw's configured sandbox backend for this session: list, poll, log, write, send-keys, submit, paste, kill, clear, or remove. Use only for sandbox follow-up; use Codex's native shell session handling only when no OpenClaw sandbox is active and native Code Mode is available."
570
+ };
571
+ return [
572
+ ...filteredTools,
573
+ sandboxExecTool,
574
+ sandboxProcessTool
575
+ ];
576
+ }
577
+ function isSandboxShellDynamicToolExcluded(config) {
578
+ return isCodexDynamicToolExcluded(config, [
579
+ "exec",
580
+ "sandbox_exec",
581
+ "process",
582
+ "sandbox_process"
583
+ ]);
584
+ }
585
+ async function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
586
+ if (isCodexMemoryFlushRun(input.params)) return filteredTools;
587
+ const nodeExecIsDefault = nodePolicy.effectiveExecHost === "node";
588
+ const nodeExecAvailableFromAuto = nodePolicy.requestedExecHost === "auto" && nodePolicy.effectiveExecHost === "gateway";
589
+ if (!nodeExecIsDefault && !nodeExecAvailableFromAuto) return filteredTools;
590
+ const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
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;
594
+ }
595
+ function shouldKeepOpenClawShellDynamicTools(input, nodePolicy) {
596
+ return !isCodexMemoryFlushRun(input.params) && input.nativeToolSurfaceEnabled === false && input.sandbox?.enabled !== true && nodePolicy.effectiveExecHost !== "node";
597
+ }
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;
603
+ }
604
+ /** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
605
+ function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
606
+ if (!toolsAllow) return tools;
607
+ if (toolsAllow.length === 0) return [];
608
+ if (hasWildcardCodexToolsAllow(toolsAllow)) return tools;
609
+ const allowSet = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)).filter(Boolean));
610
+ return tools.filter((tool) => {
611
+ const normalized = normalizeCodexDynamicToolName(tool.name);
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");
613
+ });
614
+ }
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") };
1037
1007
  }
1038
- /** Adds sandbox_exec/process aliases when native Code Mode cannot directly honor the sandbox. */
1039
- function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input, executionPolicy) {
1040
- if (isCodexMemoryFlushRun(input.params) || !executionPolicy.nativeToolSurfaceAllowed || !input.sandbox?.enabled || !input.sandbox.backendId.trim() || input.nativeToolSurfaceEnabled !== false || isSandboxShellDynamicToolExcluded(input.pluginConfig)) return filteredTools;
1041
- const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
1042
- const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
1043
- if (!execTool || !processTool) return filteredTools;
1044
- const sandboxExecTool = {
1045
- ...execTool,
1046
- name: "sandbox_exec",
1047
- description: "Run a shell command through OpenClaw's configured sandbox backend for this session. Use when OpenClaw sandboxing is active or when a command must execute in the sandbox backend, such as an SSH-backed sandbox or Docker container-path bind layout. Use Codex's native shell only when no OpenClaw sandbox is active and native Code Mode is available.",
1048
- execute: async (toolCallId, args, signal, onUpdate) => {
1049
- const result = await execTool.execute(toolCallId, args, signal, onUpdate);
1050
- return {
1051
- ...result,
1052
- content: result.content.map((item) => item.type === "text" ? Object.assign({}, item, { text: item.text.replace("Use process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.", "Use sandbox_process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.") }) : item)
1053
- };
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
+ }));
1054
1035
  }
1036
+ return mapped;
1055
1037
  };
1056
- const sandboxProcessTool = {
1057
- ...processTool,
1058
- name: "sandbox_process",
1059
- description: "Manage background shell sessions through OpenClaw's configured sandbox backend for this session: list, poll, log, write, send-keys, submit, paste, kill, clear, or remove. Use only for sandbox follow-up; use Codex's native shell session handling only when no OpenClaw sandbox is active and native Code Mode is available."
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;
1060
1043
  };
1061
- return [
1062
- ...filteredTools,
1063
- sandboxExecTool,
1064
- sandboxProcessTool
1065
- ];
1066
- }
1067
- function isSandboxShellDynamicToolExcluded(config) {
1068
- return isCodexDynamicToolExcluded(config, [
1069
- "exec",
1070
- "sandbox_exec",
1071
- "process",
1072
- "sandbox_process"
1073
- ]);
1074
- }
1075
- async function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
1076
- if (isCodexMemoryFlushRun(input.params)) return filteredTools;
1077
- const nodeExecIsDefault = nodePolicy.effectiveExecHost === "node";
1078
- const nodeExecAvailableFromAuto = nodePolicy.requestedExecHost === "auto" && nodePolicy.effectiveExecHost === "gateway";
1079
- if (!nodeExecIsDefault && !nodeExecAvailableFromAuto) return filteredTools;
1080
- const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
1081
- if (!execTool || isCodexDynamicToolExcluded(input.pluginConfig, ["exec", "node_exec"]) || filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === "node_exec")) return filteredTools;
1082
- const nodeExec = await createNodeExecAliasDynamicTool(execTool, nodePolicy.node, input.runAbortController.signal, input.nodeExecAvailability);
1083
- return nodeExec ? [...filteredTools, nodeExec] : filteredTools;
1084
- }
1085
- function shouldKeepOpenClawShellDynamicTools(input, nodePolicy) {
1086
- return !isCodexMemoryFlushRun(input.params) && input.nativeToolSurfaceEnabled === false && input.sandbox?.enabled !== true && nodePolicy.effectiveExecHost !== "node";
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);
1087
1100
  }
1088
- /** Keeps replacement shell tools direct even when model metadata mandates Codex Code Mode. */
1089
- function placeDisabledNativeShellToolsInDirectNamespace(tools, nativeToolSurfaceEnabled) {
1090
- if (nativeToolSurfaceEnabled !== false) return tools;
1091
- for (const tool of tools) if (CODEX_DISABLED_NATIVE_SHELL_DYNAMIC_TOOLS.has(normalizeCodexDynamicToolName(tool.name))) tool.catalogMode = "direct-only";
1092
- return tools;
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
+ }
1093
1108
  }
1094
- /** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
1095
- function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
1096
- if (!toolsAllow) return tools;
1097
- if (toolsAllow.length === 0) return [];
1098
- if (hasWildcardCodexToolsAllow(toolsAllow)) return tools;
1099
- const allowSet = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)).filter(Boolean));
1100
- return tools.filter((tool) => {
1101
- const normalized = normalizeCodexDynamicToolName(tool.name);
1102
- 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");
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;
1103
1127
  });
1104
- }
1105
- /** Detects the wildcard allowlist marker after Codex tool-name normalization. */
1106
- function hasWildcardCodexToolsAllow(toolsAllow) {
1107
- return toolsAllow.some((name) => normalizeCodexDynamicToolName(name) === "*");
1108
- }
1109
- /** Forces message delivery through the message tool when the source channel requires it. */
1110
- function shouldForceMessageTool(params) {
1111
- return params.disableMessageTool !== true && params.sourceReplyDeliveryMode === "message_tool_only";
1128
+ return mapped;
1112
1129
  }
1113
1130
  //#endregion
1114
1131
  //#region extensions/codex/src/app-server/sandbox-exec-server-node-relay.ts
@@ -4686,6 +4703,7 @@ const CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS = 128;
4686
4703
  const CODEX_DYNAMIC_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/u;
4687
4704
  const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = /* @__PURE__ */ new Set([
4688
4705
  "agents_list",
4706
+ "agents_wait",
4689
4707
  "progress_card",
4690
4708
  "sessions_spawn",
4691
4709
  "sessions_yield"
@@ -4864,6 +4882,34 @@ function readCodexDynamicToolDescriptor(tool, toolIndex) {
4864
4882
  };
4865
4883
  }
4866
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);
4905
+ }
4906
+ return {
4907
+ tools,
4908
+ quarantinedTools,
4909
+ preparedNames
4910
+ };
4911
+ }
4912
+ //#endregion
4867
4913
  //#region extensions/codex/src/app-server/dynamic-tools.ts
4868
4914
  /**
4869
4915
  * Bridges OpenClaw runtime tools into Codex app-server dynamic tool specs and
@@ -5068,18 +5114,21 @@ function createCodexDynamicToolBridge(params) {
5068
5114
  const registeredProjection = params.registeredTools ? projectCodexDynamicTools(params.registeredTools) : availableProjection;
5069
5115
  const inheritedSpecs = params.registeredSpecs ? structuredClone([...params.registeredSpecs]) : void 0;
5070
5116
  const inheritedNames = inheritedSpecs ? new Set(flattenCodexDynamicToolFunctions(inheritedSpecs).map((tool) => tool.name)) : void 0;
5071
- const availableTools = availableProjection.tools.filter((entry) => !inheritedNames || inheritedNames.has(entry.name));
5072
- const quarantinedAvailableToolNames = new Set(availableProjection.quarantinedTools.map((tool) => tool.tool));
5073
- const registeredSpecTools = (params.registeredTools ? registeredProjection.tools : availableTools).filter((entry) => !quarantinedAvailableToolNames.has(entry.name));
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);
5074
5121
  const toolMap = new Map(availableTools.map((entry) => [entry.name, entry]));
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);
5075
5124
  const registeredToolNames = inheritedNames ?? new Set(registeredSpecTools.map((entry) => entry.name));
5076
5125
  const quarantinedTools = dedupeQuarantinedDynamicTools([...availableProjection.quarantinedTools, ...registeredProjection.quarantinedTools]);
5077
- warnQuarantinedDynamicTools({
5126
+ reportQuarantinedDynamicTools({
5078
5127
  tools: quarantinedTools,
5079
5128
  availableToolCount: availableTools.length,
5080
- registeredToolCount: registeredSpecTools.length
5129
+ registeredToolCount: registeredSpecTools.length,
5130
+ hookContext: params.hookContext
5081
5131
  });
5082
- emitQuarantinedDynamicToolDiagnostics(quarantinedTools, params.hookContext);
5083
5132
  const telemetry = {
5084
5133
  didSendViaMessagingTool: false,
5085
5134
  didDeliverSourceReplyViaMessageTool: false,
@@ -5222,6 +5271,7 @@ function createCodexDynamicToolBridge(params) {
5222
5271
  }));
5223
5272
  const rawResult = await Reflect.apply(tool.execute, tool, executionArgs);
5224
5273
  captureExecutionBoundary();
5274
+ if (toolName === "message" && asOptionalRecord(asOptionalRecord(rawResult.details)?.messageDelivery)?.sourceReplyDelivered === true) telemetry.sourceReplyDelivered = true;
5225
5275
  const telemetryRawResult = sanitizeToolResult(rawResult);
5226
5276
  const rawIsError = isToolResultError(rawResult);
5227
5277
  const rawResultFailureKind = resolveToolResultFailureKind(rawResult);
@@ -5330,6 +5380,7 @@ function createCodexDynamicToolBridge(params) {
5330
5380
  const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
5331
5381
  withDynamicToolAsyncStarted(response, asyncStarted);
5332
5382
  const replaySafe = executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
5383
+ copyInternalToolResultState(rawResult, response);
5333
5384
  return withDynamicToolExecutionState(response, {
5334
5385
  executedArguments: executedArgs,
5335
5386
  executionStarted: didStartExecution && !executionPrevented,
@@ -5386,19 +5437,36 @@ function createCodexDynamicToolBridge(params) {
5386
5437
  };
5387
5438
  }
5388
5439
  function projectCodexExecutableDynamicToolSurface(tools, hookContext) {
5389
- const projected = projectCodexDynamicTools(tools);
5390
- 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
+ }
5391
5458
  return {
5392
- tools: wrapped.tools,
5393
- quarantinedTools: dedupeQuarantinedDynamicTools([...projected.quarantinedTools, ...wrapped.quarantinedTools])
5459
+ tools: wrappedTools,
5460
+ quarantinedTools: dedupeQuarantinedDynamicTools(quarantinedTools)
5394
5461
  };
5395
5462
  }
5396
5463
  /** Applies the exact schema and hook-wrapper projection used by the executable Codex bridge. */
5397
5464
  function projectCodexExecutableDynamicTools(params) {
5398
5465
  const projected = projectCodexExecutableDynamicToolSurface(params.tools, params.hookContext);
5466
+ const finalized = finalizeCodexToolAvailability(projected.tools);
5399
5467
  return {
5400
- availableTools: projected.tools.map((entry) => entry.tool),
5401
- quarantinedTools: projected.quarantinedTools
5468
+ availableTools: finalized.tools.map((entry) => entry.tool),
5469
+ quarantinedTools: [...projected.quarantinedTools, ...finalized.quarantinedTools]
5402
5470
  };
5403
5471
  }
5404
5472
  function notifyAgentToolResult(observer, toolName, result, isError) {
@@ -5424,50 +5492,22 @@ function failedToolResult(message, status = "failed") {
5424
5492
  }
5425
5493
  };
5426
5494
  }
5427
- function wrapProjectedCodexDynamicTools(tools, hookContext) {
5428
- const wrappedTools = [];
5429
- const quarantinedTools = [];
5430
- for (const entry of tools) try {
5431
- if (isToolWrappedWithBeforeToolCallHook(entry.tool)) {
5432
- setBeforeToolCallDiagnosticsEnabled(entry.tool, false);
5433
- wrappedTools.push(entry);
5434
- continue;
5435
- }
5436
- wrappedTools.push({
5437
- ...entry,
5438
- tool: wrapToolWithBeforeToolCallHook(entry.tool, hookContext, { emitDiagnostics: false })
5439
- });
5440
- } catch {
5441
- quarantinedTools.push({
5442
- tool: entry.name,
5443
- violations: [`${entry.name} could not be wrapped for before-tool-call hooks`]
5444
- });
5445
- }
5446
- return {
5447
- tools: wrappedTools,
5448
- quarantinedTools
5449
- };
5450
- }
5451
- function warnQuarantinedDynamicTools(params) {
5495
+ function reportQuarantinedDynamicTools(params) {
5452
5496
  if (params.tools.length === 0) return;
5453
- const unique = /* @__PURE__ */ new Map();
5454
- for (const tool of params.tools) unique.set(tool.tool, tool.violations);
5455
- 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`, {
5456
- 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 }) => ({
5457
5499
  tool,
5458
5500
  violations
5459
5501
  })),
5460
5502
  availableToolCount: params.availableToolCount,
5461
5503
  registeredToolCount: params.registeredToolCount
5462
5504
  });
5463
- }
5464
- function emitQuarantinedDynamicToolDiagnostics(tools, ctx) {
5465
- for (const tool of tools) emitTrustedDiagnosticEvent({
5505
+ for (const tool of params.tools) emitTrustedDiagnosticEvent({
5466
5506
  type: "tool.execution.blocked",
5467
- agentId: ctx?.agentId,
5468
- runId: ctx?.runId,
5469
- sessionId: ctx?.sessionId,
5470
- sessionKey: ctx?.sessionKey,
5507
+ agentId: params.hookContext?.agentId,
5508
+ runId: params.hookContext?.runId,
5509
+ sessionId: params.hookContext?.sessionId,
5510
+ sessionKey: params.hookContext?.sessionKey,
5471
5511
  toolName: tool.tool,
5472
5512
  deniedReason: "unsupported_tool_schema",
5473
5513
  reason: tool.violations.join(", ")
@@ -5758,4 +5798,4 @@ function isCronAddAction(args) {
5758
5798
  return typeof action === "string" && action.trim().toLowerCase() === "add";
5759
5799
  }
5760
5800
  //#endregion
5761
- export { filterCodexVisionTools as C, shouldWarnCodexDynamicToolBuildStageSummary as S, CodexNativeToolLifecycleProjector as T, resolveCodexMessageToolProvider as _, emitDynamicToolStartedDiagnostic as a, shouldEnableCodexAppServerNativeToolSurface as b, ensureCodexSandboxExecServerEnvironment as c, createCodexDynamicToolBuildStageTracker as d, disableCodexPluginThreadConfig as f, resolveCodexExternalSandboxPolicyForOpenClawSandbox as g, resolveCodexAppServerHookChannelId as h, emitDynamicToolErrorDiagnostic as i, releaseCodexSandboxExecServerEnvironment as l, resolveCodexAppServerExecutionCwd as m, projectCodexExecutableDynamicTools as n, emitDynamicToolTerminalDiagnostic as o, formatCodexDynamicToolBuildStageSummary as p, routeCodexAppServerElicitationRequest as r, handleCodexAppServerApprovalRequest as s, createCodexDynamicToolBridge as t, buildDynamicTools as u, resolveCodexNodePlacementToolConstructionPlan as v, readBoundedCodexRemoteWorkspaceFile as w, shouldRequireCodexSandboxExecServerEnvironment as x, resolveCodexSandboxEnvironmentSelection 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 };