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