@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.
- package/dist/api.js +6 -6
- package/dist/{attempt-client-cleanup-DYHcRQAa.js → attempt-client-cleanup-Bp1ZOnk2.js} +76 -32
- package/dist/{attempt-context-DmQUNWwT.js → attempt-context-CSazkWns.js} +6 -6
- package/dist/{bounded-turn-wzJrwKKe.js → bounded-turn-DxGJRYbY.js} +46 -45
- package/dist/{capabilities-dIjNVCYH.js → capabilities-B1mgvpP8.js} +2 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-CLHhEB1Q.js → command-formatters-DSbGMXA0.js} +2 -2
- package/dist/{command-handlers-CTGb2csu.js → command-handlers-DOJvGv6-.js} +146 -74
- package/dist/{compact-_t0HQrUS.js → compact-Y_xBqrHN.js} +83 -48
- package/dist/{config-BhmfC1s6.js → config-CyapYSji.js} +47 -105
- package/dist/config-utils-C57JiJrU.js +69 -0
- package/dist/doctor-contract-api.js +4 -5
- package/dist/{dynamic-tools-BU9tmlYm.js → dynamic-tools-BwpvTxaX.js} +678 -638
- package/dist/{effective-mcp-catalog-DzYBW4h1.js → effective-mcp-catalog-CY9Jn4bA.js} +4 -3
- package/dist/{event-projector-assistant-message-DHRFXKbS.js → event-projector-assistant-message-B9kp6iGC.js} +11 -2
- package/dist/{event-projector-usage-C5mcrGjp.js → event-projector-usage-ZqEVbAEY.js} +30 -13
- package/dist/{harness-DXVZ5II5.js → harness-Czh2Sdoc.js} +36 -13
- package/dist/harness.js +1 -1
- package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
- package/dist/index.js +68 -84
- package/dist/{isolated-completion-kg9kv4iH.js → isolated-completion-CRh-51AK.js} +9 -21
- package/dist/{managed-binary-4-Vjz5GH.js → managed-binary-DcpnJF9x.js} +105 -73
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{model-catalog-CUM68Hkl.js → model-catalog-DT5vo6qo.js} +5 -5
- package/dist/{models-sYnEc0gd.js → models-CcrgSMQR.js} +4 -4
- package/dist/{native-subagent-monitor-CnH147jH.js → native-subagent-monitor-B3bvQGcg.js} +12 -11
- package/dist/{node-exec-server.runtime-CstNHCmC.js → node-exec-server.runtime-nfbUOtUB.js} +3 -3
- package/dist/{plugin-approval-roundtrip-CNe-Xrjx.js → plugin-approval-roundtrip-aQVdyL5g.js} +1 -1
- package/dist/{plugin-marketplace-discovery-CfFlKqDl.js → plugin-marketplace-discovery-B7gHXJ3d.js} +35 -22
- package/dist/{plugin-metadata-cache-BFrPhb2c.js → plugin-metadata-cache-BwCLTY_U.js} +5 -5
- package/dist/{provider-capabilities-DW5p4k9w.js → provider-capabilities-BpG9Ltc5.js} +2 -2
- package/dist/{rate-limits-CawQJfUS.js → rate-limits-Fx_l4eNa.js} +1 -1
- package/dist/{request-DYAI5FQE.js → request-C8H22rPi.js} +2 -2
- package/dist/{run-attempt-D1hJO4fX.js → run-attempt-BzthFQSM.js} +977 -723
- package/dist/{runtime-artifact-C3jVimFn.js → runtime-artifact-Bh_gf6-t.js} +3 -3
- package/dist/{session-binding-BHuTCRZQ.js → session-binding-4Ki04sW-.js} +94 -282
- package/dist/session-binding-record-CyaXZvTe.js +270 -0
- package/dist/{session-catalog-yyvTWFOw.js → session-catalog-B47uwekb.js} +266 -114
- package/dist/{session-cli-CmFEXMAM.js → session-cli-BjLhpmS1.js} +1 -1
- package/dist/session-history-DWmMqiqW.js +93 -0
- package/dist/session-history-read-LHldwvyh.js +36 -0
- package/dist/session-history-worker-runtime.js +48 -0
- package/dist/{settled-turn-context-D3SNWtUn.js → session-history.worker-zZVcx0ME.js} +30 -31
- package/dist/session-history.worker.js +2 -0
- package/dist/{session-initialization-BeWzjGiR.js → session-initialization-BCFVnLL4.js} +2 -1
- package/dist/{session-permission-policy-DeWVTCMj.js → session-permission-policy-BaueHmSj.js} +1 -1
- package/dist/{session-retirement-BujDjXVF.js → session-retirement-DNdmYdFc.js} +7 -7
- package/dist/{session-upstream-marker-C351-5Qw.js → session-upstream-marker-DReTpGz6.js} +4 -3
- package/dist/settled-turn-context-BOM3Y1pl.js +45 -0
- package/dist/{settled-turn-finalizer-BVJx4yoc.js → settled-turn-finalizer-BI-b1sry.js} +53 -42
- package/dist/{shared-client-DxcfF9KX.js → shared-client-CKhX2kFt.js} +279 -268
- package/dist/{side-question-BTZAII4l.js → side-question-B6DWAw1A.js} +85 -36
- package/dist/{thread-lifecycle-DAcEJ_Om.js → thread-lifecycle-DPK30jad.js} +382 -388
- package/dist/{thread-ownership-zGokd8i1.js → thread-ownership-CbucAmmp.js} +5 -5
- package/dist/thread-policy-CTalhWFy.js +105 -0
- package/dist/{thread-resume-C7pyVKeW.js → thread-resume-BLgqkUc1.js} +7 -7
- package/dist/{transcript-mirror-DTUxFWa_.js → transcript-mirror-_njEB84z.js} +67 -125
- package/dist/transcript-mirror-attestation-Byl34PC-.js +76 -0
- package/dist/{transport-stdio-CyPldczm.js → transport-stdio-CzTngV0j.js} +10 -8
- package/dist/{upstream-session-fork-CYlBIGVt.js → upstream-session-fork-1fwbNKzQ.js} +85 -165
- package/dist/{usage-Bj-rrzkR.js → usage-i9fv5-4m.js} +3 -3
- package/dist/{web-search-provider.runtime-D2FWSK4w.js → web-search-provider.runtime-DKQn67A7.js} +3 -3
- package/package.json +6 -6
- package/dist/binding-connection-E6uDsPlx.js +0 -45
- package/dist/launch-args-RXQwn8zV.js +0 -90
- package/dist/session-history-C5J1AKzG.js +0 -87
- package/dist/thread-policy-g4KbJPzp.js +0 -54
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { E as itemStatus,
|
|
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-
|
|
5
|
-
import { l as resolveCodexNativeExecutionPolicy, u as resolveCodexNodeExecToolOverrides } from "./request-
|
|
6
|
-
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-
|
|
7
|
-
import { a as formatCodexDisplayText } from "./command-formatters-
|
|
8
|
-
import { a as
|
|
9
|
-
import { a as
|
|
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 {
|
|
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/
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
/**
|
|
645
|
-
function createCodexDynamicToolBuildStageTracker(
|
|
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
|
-
|
|
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(
|
|
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
|
-
/**
|
|
1039
|
-
function
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
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
|
|
5072
|
-
const
|
|
5073
|
-
const
|
|
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
|
-
|
|
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
|
|
5390
|
-
const
|
|
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:
|
|
5393
|
-
quarantinedTools: dedupeQuarantinedDynamicTools(
|
|
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:
|
|
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
|
|
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
|
-
|
|
5454
|
-
|
|
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:
|
|
5468
|
-
runId:
|
|
5469
|
-
sessionId:
|
|
5470
|
-
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 {
|
|
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 };
|