@openclaw/codex 2026.9.1 → 2026.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/api.js +18 -23
- package/dist/apply-B6el6hdO.js +357 -0
- package/dist/{attempt-client-cleanup-DYHcRQAa.js → attempt-client-cleanup-D6cR54TV.js} +113 -49
- package/dist/{attempt-context-DmQUNWwT.js → attempt-context-AO4DSjE0.js} +11 -9
- package/dist/{event-projector-usage-C5mcrGjp.js → attempt-notifications--H8zXR5-.js} +3 -89
- package/dist/auth-cache-key-Kq6oerr6.js +498 -0
- package/dist/auth-profile-CwzO4_RG.js +43 -0
- package/dist/auth-profile-selection-DFu9e167.js +45 -0
- package/dist/{binding-connection-E6uDsPlx.js → binding-connection-QqgcPCgk.js} +18 -3
- package/dist/{bounded-turn-wzJrwKKe.js → bounded-turn-lRpmZHFv.js} +54 -150
- package/dist/build-state-6RL6qohz.js +19 -0
- package/dist/{capabilities-dIjNVCYH.js → capabilities-BeqLPbtM.js} +2 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/client-BEKu4yDx.js +3983 -0
- package/dist/command-dispatch-CisT9Jqq.js +21 -0
- package/dist/{command-formatters-CLHhEB1Q.js → command-formatters-DeTfrapr.js} +2 -2
- package/dist/{command-handlers-CTGb2csu.js → command-handlers-BfIsJEuA.js} +158 -76
- package/dist/command-rpc-BMqRQOFX.js +226 -0
- package/dist/{compact-_t0HQrUS.js → compact-CGJZ2rlb.js} +89 -49
- package/dist/config-JDfTI4VI.js +106 -0
- package/dist/{config-BhmfC1s6.js → config-options-jnIFvwxD.js} +208 -654
- package/dist/config-runtime-BWf6JLWG.js +11 -0
- package/dist/config-utils-C57JiJrU.js +69 -0
- package/dist/conversation-binding-CxOlHiix.js +889 -0
- package/dist/{plugin-marketplace-discovery-CfFlKqDl.js → conversation-control-CUCrSIQI.js} +36 -155
- package/dist/doctor-contract-api.js +8 -9
- package/dist/{dynamic-tools-BU9tmlYm.js → dynamic-tools-D-JBoaLt.js} +734 -698
- package/dist/{effective-mcp-catalog-DzYBW4h1.js → effective-mcp-catalog-J_5Ep5Um.js} +4 -3
- package/dist/{elicitation-input-DMkeki8d.js → elicitation-input-DPoK8F7w.js} +8 -22
- package/dist/ephemeral-turn-DSwzP8YY.js +101 -0
- package/dist/{event-projector-assistant-message-DHRFXKbS.js → event-projector-assistant-message-BmBM4qBV.js} +23 -27
- package/dist/event-projector-usage-CIyrYYid.js +102 -0
- package/dist/{harness-DXVZ5II5.js → harness-FR6eQgyO.js} +47 -17
- package/dist/harness.js +1 -1
- package/dist/history-rejection-B4nOXNXF.js +13 -0
- package/dist/{image-payload-sanitizer-CnfoUPCE.js → image-payload-sanitizer-tupAr-2o.js} +29 -15
- package/dist/incognito-session-uhrBF6wJ.js +15 -0
- package/dist/index.js +413 -2893
- package/dist/{isolated-completion-kg9kv4iH.js → isolated-completion-DHBefrzg.js} +12 -21
- package/dist/{managed-binary-4-Vjz5GH.js → managed-binary-DrjjRRUD.js} +17 -84
- package/dist/managed-thread-store-D1t5jh3G.js +66 -0
- package/dist/media-understanding-provider.js +4 -1
- package/dist/{model-catalog-CUM68Hkl.js → model-catalog-B_6M04Wo.js} +6 -5
- package/dist/{models-sYnEc0gd.js → models-P9k2qAof.js} +4 -4
- package/dist/native-execution-policy-umd0tgh4.js +128 -0
- package/dist/{native-subagent-monitor-CnH147jH.js → native-subagent-monitor-BbCUKRwC.js} +76 -48
- package/dist/node-cli-sessions-rj0ysJwM.js +527 -0
- package/dist/{node-exec-server.runtime-CstNHCmC.js → node-exec-server.runtime-BY_ctYnT.js} +54 -23
- package/dist/plan-BMJdMOEF.js +711 -0
- package/dist/plugin-activation-BQK-f5sV.js +133 -0
- package/dist/{thread-resume-C7pyVKeW.js → plugin-app-cache-key-CdSQzWyX.js} +5 -53
- package/dist/{plugin-approval-roundtrip-CNe-Xrjx.js → plugin-approval-roundtrip-DeW95wB5.js} +1 -1
- package/dist/plugin-inventory-CoRCzWW4.js +325 -0
- package/dist/plugin-marketplace-discovery-3akBizzF.js +134 -0
- package/dist/{plugin-metadata-cache-BFrPhb2c.js → plugin-metadata-cache-DgNfRAGA.js} +13 -9
- package/dist/protocol-CAAcVIKF.js +275 -0
- package/dist/{provider-capabilities-DW5p4k9w.js → provider-capabilities-W4tfK5Y5.js} +2 -2
- package/dist/{rate-limits-CawQJfUS.js → rate-limits-Bqnx1N4z.js} +1 -1
- package/dist/request-CGvGOpeR.js +198 -0
- package/dist/{run-attempt-D1hJO4fX.js → run-attempt-DwKds3DN.js} +1498 -1028
- package/dist/{runtime-artifact-C3jVimFn.js → runtime-artifact-T-NwqeSH.js} +28 -28
- package/dist/sandbox-guard-BcT66fWy.js +165 -0
- package/dist/{session-binding-BHuTCRZQ.js → session-binding-CKSepwIC.js} +118 -324
- package/dist/session-binding-record-CaZjYwNS.js +256 -0
- package/dist/session-catalog-HaDcp7Sv.js +1533 -0
- package/dist/session-catalog-adoption-BKLtSrwi.js +251 -0
- package/dist/session-catalog-archive-D0i2pGj2.js +56 -0
- package/dist/session-catalog-home-id-D7-aLiNt.js +190 -0
- package/dist/session-catalog-node-adoption-BUuDYY8R.js +182 -0
- package/dist/session-catalog-node-continue-CBtc4wkI.js +238 -0
- package/dist/session-catalog-parsing-CdD3JhdM.js +280 -0
- package/dist/session-catalog-provenance-DkJHo735.js +124 -0
- package/dist/{session-cli-CmFEXMAM.js → session-cli-CbgaHh0f.js} +3 -2
- package/dist/session-history-Cz-tOOYj.js +96 -0
- package/dist/{session-upstream-marker-C351-5Qw.js → session-history-import-DFN7sWRU.js} +5 -49
- package/dist/session-history-read-cxPUxyVV.js +50 -0
- package/dist/session-history-worker-runtime.js +60 -0
- package/dist/{settled-turn-context-D3SNWtUn.js → session-history.worker-CNEvGaU5.js} +75 -69
- package/dist/session-history.worker.js +2 -0
- package/dist/{session-initialization-BeWzjGiR.js → session-initialization-Jo86C60I.js} +2 -1
- package/dist/{session-permission-policy-DeWVTCMj.js → session-permission-policy-B22InTp_.js} +4 -7
- package/dist/{session-retirement-BujDjXVF.js → session-retirement-BSNG_mhf.js} +8 -7
- package/dist/session-upstream-marker-D15C9NHp.js +46 -0
- package/dist/settled-turn-context-Du6gdUDd.js +45 -0
- package/dist/{settled-turn-finalizer-BVJx4yoc.js → settled-turn-finalizer-BuLUrZl2.js} +54 -42
- package/dist/shared-client-CVhez2Uo.js +2911 -0
- package/dist/{side-question-BTZAII4l.js → side-question-DLbr2vUU.js} +135 -191
- package/dist/source-Da-h6f7e.js +518 -0
- package/dist/thread-archive-guard-D1bP04Tf.js +53 -0
- package/dist/{thread-lifecycle-DAcEJ_Om.js → thread-lifecycle-D1pzRwHB.js} +467 -977
- package/dist/{thread-ownership-zGokd8i1.js → thread-ownership-D6oASVRu.js} +15 -17
- package/dist/{thread-policy-g4KbJPzp.js → thread-policy-D8kI7yAO.js} +2 -2
- package/dist/thread-resume-T5cveyxA.js +52 -0
- package/dist/timeout-BVw8aaM1.js +33 -0
- package/dist/transcript-mirror-attestation-DibE0krH.js +89 -0
- package/dist/{transcript-mirror-DTUxFWa_.js → transcript-mirror-t5TOccct.js} +111 -379
- package/dist/{transport-stdio-CyPldczm.js → transport-stdio-BGjHoYLr.js} +80 -48
- package/dist/{upstream-session-fork-CYlBIGVt.js → upstream-session-fork-De-sLJ2r.js} +94 -166
- package/dist/{usage-Bj-rrzkR.js → usage-CPPTmBvZ.js} +4 -3
- package/dist/version-omm6IPI3.js +12 -0
- package/dist/{web-search-provider.runtime-D2FWSK4w.js → web-search-provider.runtime-CSVgQL0w.js} +3 -3
- package/openclaw.plugin.json +22 -1
- package/package.json +6 -6
- package/dist/request-DYAI5FQE.js +0 -475
- package/dist/session-catalog-yyvTWFOw.js +0 -3120
- package/dist/session-history-C5J1AKzG.js +0 -87
- package/dist/shared-client-DxcfF9KX.js +0 -7494
- package/dist/{auth-binding-Ds_QVm-X.js → auth-binding-Co2Vfcf_.js} +1 -1
|
@@ -1,263 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { E as
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { a as CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME, c as createNodeExecAliasDynamicTool, i as CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME, l as isCodexDynamicToolExcluded, o as createGatewayExecProjection, r as CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME, s as createGatewayProcessProjection } from "./provider-capabilities-
|
|
1
|
+
import { a as flattenCodexDynamicToolFunctions, o as isJsonObject, r as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, x as readCodexPluginConfig, y as isCodexRemoteExecPlacementSandbox } from "./protocol-CAAcVIKF.js";
|
|
2
|
+
import { B as withDynamicToolTranscriptDetails, E as createCodexElicitationResponse, L as resolveCodexToolAbortTerminalReason, R as createFailedDynamicToolResponse, w as readCodexTurn, z as withDynamicToolExecutionState } from "./client-BEKu4yDx.js";
|
|
3
|
+
import { a as formatCodexDisplayText } from "./command-formatters-DeTfrapr.js";
|
|
4
|
+
import "./config-JDfTI4VI.js";
|
|
5
|
+
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-tupAr-2o.js";
|
|
6
|
+
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-D6cR54TV.js";
|
|
7
|
+
import { B as emitCodexNativePreToolUseFailureDiagnostic, S as resolveCodexWebSearchPlan, it as isForcedPrivateQaCodexRuntime, nt as filterCodexDynamicTools, ot as isSystemAgentOnlyCodexDynamicToolAllowlist, rt as filterCodexDynamicToolsForDisabledNativeSurface, st as normalizeCodexDynamicToolName } from "./thread-lifecycle-D1pzRwHB.js";
|
|
8
|
+
import { n as resolveCodexNativeExecutionPolicy, r as resolveCodexNodeExecToolOverrides } from "./native-execution-policy-umd0tgh4.js";
|
|
9
|
+
import { a as CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME, c as createNodeExecAliasDynamicTool, i as CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME, l as isCodexDynamicToolExcluded, o as createGatewayExecProjection, r as CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME, s as createGatewayProcessProjection } from "./provider-capabilities-W4tfK5Y5.js";
|
|
10
|
+
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-DeW95wB5.js";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
10
12
|
import { asNonArrayRecord, asOptionalRecord, isRecord, normalizeOptionalString, normalizeTrimmedStringList, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
11
|
-
import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
|
|
12
|
-
import path, { posix } from "node:path";
|
|
13
|
-
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
14
|
-
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
15
|
-
import { HEARTBEAT_RESPONSE_TOOL_NAME, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, consumeAdjustedParamsForToolCall, consumePreExecutionBlockedToolCall, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, extractToolResultMediaArtifact, filterProviderNormalizableTools, filterToolResultMediaUrls, finalizeToolTerminalPresentation, formatApprovalDisplayPath, formatToolExecutionErrorMessage, getBeforeToolCallFailureDisposition, getChannelAgentToolMeta, getPluginToolMeta, getPluginToolSideEffectOwnerKey, hasNativeHookRelayInvocation, invokeNativeHookRelay, isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, isHostScopedAgentToolActive, isMessagingTool, isMessagingToolSendAction, isReplaySafeToolCall, isSubagentSessionKey, isToolResultError, isToolWrappedWithBeforeToolCallHook, normalizeAgentRuntimeTools, normalizeHeartbeatToolResponse, projectRuntimeToolInputSchema, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveNativeHookRelayDeferredToolApproval, resolveToolExecutionErrorKind, resolveToolResultFailureKind, runAgentHarnessAfterToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, supportsModelTools, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
16
|
-
import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
17
13
|
import { createHash, randomUUID } from "node:crypto";
|
|
18
|
-
import {
|
|
19
|
-
import { formatErrorMessage as formatErrorMessage$1, redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
|
|
14
|
+
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
20
15
|
import { killProcessTree } from "openclaw/plugin-sdk/process-runtime";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
16
|
+
import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
|
|
17
|
+
import path, { posix } from "node:path";
|
|
18
|
+
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
23
19
|
import { once } from "node:events";
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
24
21
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
25
22
|
import { DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, estimateToolResultTextChars, resolveLiveToolResultMaxChars, sliceToolResultTextToBudget, sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
23
|
+
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
24
|
+
import { createStageTimingTracker, formatStageTimings } from "openclaw/plugin-sdk/time-runtime";
|
|
25
|
+
import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
26
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
27
|
+
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
|
28
|
+
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";
|
|
29
|
+
import { formatErrorMessage as formatErrorMessage$2, redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
|
|
26
30
|
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
27
31
|
import { isIP } from "node:net";
|
|
28
|
-
import { WebSocketServer } from "ws";
|
|
29
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
30
|
-
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
31
32
|
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
32
33
|
import { formatMcpCodexApprovalRemedy, requiresMcpCodexToolApproval, resolveCodexScheduledToolProjectionFactory, resolveProjectedMcpCodexToolApprovalMode, runWithCronCreatorAuthorityCapabilityResolver } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
33
34
|
import { buildRemoteCommand, isToolAllowed, sanitizeEnvVars } from "openclaw/plugin-sdk/sandbox";
|
|
34
35
|
import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
|
35
36
|
import { getMediaDir } from "openclaw/plugin-sdk/media-runtime";
|
|
36
|
-
import { consumeTrustedToolNoStartError, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
|
37
|
+
import { consumeTrustedToolNoStartError, copyInternalToolResultState, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
|
38
|
+
import { SsrFBlockedError, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
37
39
|
import { normalizeOpenAIStrictCompatSchema } from "openclaw/plugin-sdk/provider-tools";
|
|
38
|
-
//#region extensions/codex/src/app-server/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
/** Owns native item lifetimes for diagnostics and MCP approval correlation. */
|
|
43
|
-
var CodexNativeToolLifecycleProjector = class {
|
|
44
|
-
constructor(context, threadId, turnId, options = {}) {
|
|
45
|
-
this.context = context;
|
|
46
|
-
this.threadId = threadId;
|
|
47
|
-
this.turnId = turnId;
|
|
48
|
-
this.options = options;
|
|
49
|
-
this.startedAtByItem = /* @__PURE__ */ new Map();
|
|
50
|
-
this.activeItems = /* @__PURE__ */ new Map();
|
|
51
|
-
this.webSearchCompletionByItem = /* @__PURE__ */ new Map();
|
|
52
|
-
this.completedItemIds = /* @__PURE__ */ new Set();
|
|
53
|
-
this.approvalFailureDispositionByItem = /* @__PURE__ */ new Map();
|
|
54
|
-
this.preToolUseFailureByItem = /* @__PURE__ */ new Map();
|
|
55
|
-
this.finalized = false;
|
|
56
|
-
this.turnCompleted = false;
|
|
57
|
-
this.pendingMcpNotifications = 0;
|
|
58
|
-
}
|
|
59
|
-
getActiveMcpToolCall(serverName) {
|
|
60
|
-
if (this.finalized || this.turnCompleted || this.pendingMcpNotifications > 0 || this.options.runAbortSignal?.aborted) return;
|
|
61
|
-
let candidate;
|
|
62
|
-
for (const { mcpToolCall } of this.activeItems.values()) {
|
|
63
|
-
if (mcpToolCall?.server !== serverName) continue;
|
|
64
|
-
if (candidate) return;
|
|
65
|
-
candidate = mcpToolCall;
|
|
66
|
-
}
|
|
67
|
-
if (!candidate || candidate.status !== "inProgress" || typeof candidate.server !== "string" || !candidate.server.trim() || typeof candidate.tool !== "string" || !candidate.tool.trim() || candidate.arguments === void 0 || candidate.appContext != null || candidate.pluginId != null) return;
|
|
68
|
-
return {
|
|
69
|
-
id: candidate.id,
|
|
70
|
-
server: candidate.server,
|
|
71
|
-
tool: candidate.tool,
|
|
72
|
-
arguments: candidate.arguments
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
recordMcpToolCallReceipt(notification) {
|
|
76
|
-
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
77
|
-
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
78
|
-
if (isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications += 1;
|
|
79
|
-
else if (notification.method === "turn/completed" && readCodexTurn(params.turn)?.id === this.turnId) this.turnCompleted = true;
|
|
80
|
-
}
|
|
81
|
-
handleNotification(notification) {
|
|
82
|
-
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
83
|
-
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
84
|
-
if (this.pendingMcpNotifications > 0 && isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications -= 1;
|
|
85
|
-
if (notification.method === "turn/completed") {
|
|
86
|
-
const turn = readCodexTurn(params.turn);
|
|
87
|
-
if (!turn || turn.id !== this.turnId) return;
|
|
88
|
-
this.turnCompleted = true;
|
|
89
|
-
for (const item of turn.items ?? []) this.recordSnapshotItem(item);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (notification.method === "rawResponseItem/completed") {
|
|
93
|
-
const item = isJsonObject(params.item) ? params.item : void 0;
|
|
94
|
-
if (item) this.recordRawWebSearchResult(item);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (notification.method !== "item/started" && notification.method !== "item/completed") return;
|
|
98
|
-
const item = readItem(params.item);
|
|
99
|
-
if (!item) return;
|
|
100
|
-
this.recordItem({
|
|
101
|
-
phase: notification.method === "item/started" ? "start" : "result",
|
|
102
|
-
item,
|
|
103
|
-
sourceTimestampMs: asDateTimestampMs(notification.method === "item/started" ? params.startedAtMs : params.completedAtMs)
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
recordItem(params) {
|
|
107
|
-
const toolName = auditNativeToolName(params.item) ?? (params.item.type === "mcpToolCall" ? params.item.type : void 0);
|
|
108
|
-
if (!toolName || this.completedItemIds.has(params.item.id)) return;
|
|
109
|
-
if (params.phase === "start") {
|
|
110
|
-
this.recordStarted(params.item.id, toolName, auditNativeToolUnfinishedStatus(params.item), params.sourceTimestampMs, params.item.type === "mcpToolCall" ? params.item : void 0);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (params.item.type === "webSearch") {
|
|
114
|
-
this.webSearchCompletionByItem.set(params.item.id, {
|
|
115
|
-
runWasAborted: this.options.runAbortSignal?.aborted === true,
|
|
116
|
-
sourceTimestampMs: params.sourceTimestampMs
|
|
117
|
-
});
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const itemDurationMs = typeof params.item.durationMs === "number" ? params.item.durationMs : void 0;
|
|
121
|
-
this.recordTerminal(params.item.id, toolName, auditNativeToolTerminalStatus(params.item), {
|
|
122
|
-
itemDurationMs,
|
|
123
|
-
sourceTimestampMs: params.sourceTimestampMs
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
recordApprovalFailureDisposition(toolCallId, disposition) {
|
|
127
|
-
if (!this.completedItemIds.has(toolCallId)) this.approvalFailureDispositionByItem.set(toolCallId, disposition);
|
|
128
|
-
}
|
|
129
|
-
recordPreToolUseFailure(failure, runWasAborted = this.options.runAbortSignal?.aborted === true) {
|
|
130
|
-
if (this.completedItemIds.has(failure.toolCallId)) return;
|
|
131
|
-
const record = {
|
|
132
|
-
failure,
|
|
133
|
-
terminalReason: runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : failure.disposition
|
|
134
|
-
};
|
|
135
|
-
if (this.finalized) {
|
|
136
|
-
this.completedItemIds.add(failure.toolCallId);
|
|
137
|
-
this.emitPreToolUseFailure(record, failure.toolName, failure.durationMs);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
this.preToolUseFailureByItem.set(failure.toolCallId, record);
|
|
141
|
-
}
|
|
142
|
-
recordRawWebSearchResult(item) {
|
|
143
|
-
if (readStringField(item, "type") !== "web_search_call") return;
|
|
144
|
-
const toolCallId = readStringField(item, "id");
|
|
145
|
-
if (!toolCallId || this.completedItemIds.has(toolCallId)) return;
|
|
146
|
-
const toolName = "web_search";
|
|
147
|
-
this.recordStarted(toolCallId, toolName, "unknown");
|
|
148
|
-
const rawStatus = readStringField(item, "status");
|
|
149
|
-
if (rawStatus === "in_progress" || rawStatus === "running") return;
|
|
150
|
-
const status = rawStatus === "completed" ? "completed" : rawStatus === "cancelled" ? "cancelled" : rawStatus === "failed" || rawStatus === "error" || rawStatus === "incomplete" ? "failed" : "unknown";
|
|
151
|
-
this.recordTerminal(toolCallId, toolName, status, { sourceTimestampMs: this.webSearchCompletionByItem.get(toolCallId)?.sourceTimestampMs });
|
|
152
|
-
}
|
|
153
|
-
recordTerminal(toolCallId, toolName, status, options = {}) {
|
|
154
|
-
const runWasAborted = options.runWasAborted ?? this.options.runAbortSignal?.aborted === true;
|
|
155
|
-
const preToolUseFailure = this.preToolUseFailureByItem.get(toolCallId);
|
|
156
|
-
this.preToolUseFailureByItem.delete(toolCallId);
|
|
157
|
-
const approvalFailureDisposition = this.approvalFailureDispositionByItem.get(toolCallId);
|
|
158
|
-
this.approvalFailureDispositionByItem.delete(toolCallId);
|
|
159
|
-
this.completedItemIds.add(toolCallId);
|
|
160
|
-
this.activeItems.delete(toolCallId);
|
|
161
|
-
this.webSearchCompletionByItem.delete(toolCallId);
|
|
162
|
-
const startedAt = this.startedAtByItem.get(toolCallId);
|
|
163
|
-
this.startedAtByItem.delete(toolCallId);
|
|
164
|
-
const endedAt = options.sourceTimestampMs ?? Date.now();
|
|
165
|
-
const durationMs = options.itemDurationMs ?? (startedAt === void 0 ? 0 : Math.max(0, endedAt - startedAt));
|
|
166
|
-
if (preToolUseFailure) {
|
|
167
|
-
this.emitPreToolUseFailure(preToolUseFailure, toolName, durationMs, options.sourceTimestampMs);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const terminalEvent = approvalFailureDisposition ? {
|
|
171
|
-
type: "tool.execution.error",
|
|
172
|
-
durationMs,
|
|
173
|
-
errorCategory: "codex_native_tool_approval",
|
|
174
|
-
terminalReason: approvalFailureDisposition
|
|
175
|
-
} : status === "blocked" ? {
|
|
176
|
-
type: "tool.execution.blocked",
|
|
177
|
-
reason: "codex_native_tool_blocked",
|
|
178
|
-
deniedReason: "codex_native_tool_blocked"
|
|
179
|
-
} : status === "failed" || status === "cancelled" || status === "unknown" ? {
|
|
180
|
-
type: "tool.execution.error",
|
|
181
|
-
durationMs,
|
|
182
|
-
errorCategory: status === "unknown" ? "codex_native_tool_outcome_unknown" : status === "cancelled" ? "aborted" : "codex_native_tool_error",
|
|
183
|
-
...status === "unknown" ? { errorCode: "tool_outcome_unknown" } : {},
|
|
184
|
-
terminalReason: status === "unknown" ? "failed" : runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : status === "cancelled" ? "cancelled" : "failed"
|
|
185
|
-
} : {
|
|
186
|
-
type: "tool.execution.completed",
|
|
187
|
-
durationMs
|
|
188
|
-
};
|
|
189
|
-
emitTrustedDiagnosticEvent({
|
|
190
|
-
...this.buildBase(toolCallId, toolName),
|
|
191
|
-
...terminalEvent,
|
|
192
|
-
...options.sourceTimestampMs !== void 0 ? { sourceTimestampMs: options.sourceTimestampMs } : {}
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
finalizeActive(runWasAborted = this.options.runAbortSignal?.aborted === true) {
|
|
196
|
-
this.finalized = true;
|
|
197
|
-
for (const [toolCallId, { toolName, unfinishedStatus }] of this.activeItems) {
|
|
198
|
-
const webSearchCompletion = this.webSearchCompletionByItem.get(toolCallId);
|
|
199
|
-
const itemRunWasAborted = webSearchCompletion ? webSearchCompletion.runWasAborted : runWasAborted;
|
|
200
|
-
this.recordTerminal(toolCallId, toolName, unfinishedStatus, {
|
|
201
|
-
runWasAborted: itemRunWasAborted,
|
|
202
|
-
sourceTimestampMs: webSearchCompletion?.sourceTimestampMs
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
for (const [toolCallId, record] of this.preToolUseFailureByItem) if (!this.completedItemIds.has(toolCallId)) this.recordTerminal(toolCallId, record.failure.toolName, "failed", { itemDurationMs: record.failure.durationMs });
|
|
206
|
-
this.activeItems.clear();
|
|
207
|
-
this.webSearchCompletionByItem.clear();
|
|
208
|
-
this.approvalFailureDispositionByItem.clear();
|
|
209
|
-
this.preToolUseFailureByItem.clear();
|
|
210
|
-
}
|
|
211
|
-
emitPreToolUseFailure(record, toolName, durationMs, sourceTimestampMs) {
|
|
212
|
-
emitCodexNativePreToolUseFailureDiagnostic({
|
|
213
|
-
agentId: this.context.agentId,
|
|
214
|
-
sessionId: this.context.sessionId,
|
|
215
|
-
sessionKey: this.context.sessionKey,
|
|
216
|
-
runId: this.context.runId,
|
|
217
|
-
failure: {
|
|
218
|
-
...record.failure,
|
|
219
|
-
toolName,
|
|
220
|
-
durationMs
|
|
221
|
-
},
|
|
222
|
-
terminalReason: record.terminalReason,
|
|
223
|
-
sourceTimestampMs
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
recordSnapshotItem(item) {
|
|
227
|
-
if (!auditNativeToolName(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
|
|
228
|
-
const toolName = auditNativeToolName(item);
|
|
229
|
-
if (!toolName) return;
|
|
230
|
-
this.recordStarted(item.id, toolName, auditNativeToolUnfinishedStatus(item));
|
|
231
|
-
this.recordItem({
|
|
232
|
-
phase: "result",
|
|
233
|
-
item
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
recordStarted(toolCallId, toolName, unfinishedStatus, sourceTimestampMs, mcpToolCall) {
|
|
237
|
-
if (this.activeItems.has(toolCallId)) return;
|
|
238
|
-
this.startedAtByItem.set(toolCallId, sourceTimestampMs ?? Date.now());
|
|
239
|
-
this.activeItems.set(toolCallId, {
|
|
240
|
-
toolName,
|
|
241
|
-
unfinishedStatus,
|
|
242
|
-
mcpToolCall
|
|
243
|
-
});
|
|
244
|
-
emitTrustedDiagnosticEvent({
|
|
245
|
-
type: "tool.execution.started",
|
|
246
|
-
...this.buildBase(toolCallId, toolName),
|
|
247
|
-
...sourceTimestampMs !== void 0 ? { sourceTimestampMs } : {}
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
buildBase(toolCallId, toolName) {
|
|
251
|
-
return {
|
|
252
|
-
agentId: this.context.agentId,
|
|
253
|
-
runId: this.context.runId,
|
|
254
|
-
sessionId: this.context.sessionId,
|
|
255
|
-
sessionKey: this.context.sessionKey,
|
|
256
|
-
toolName,
|
|
257
|
-
toolCallId
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
};
|
|
40
|
+
//#region extensions/codex/src/app-server/dynamic-tool-build-state.ts
|
|
41
|
+
/** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
|
|
42
|
+
const dynamicToolBuildState = {};
|
|
261
43
|
//#endregion
|
|
262
44
|
//#region extensions/codex/src/app-server/remote-workspace-path.ts
|
|
263
45
|
/** Projects a gateway workspace path into the remote Codex execution workspace. */
|
|
@@ -307,273 +89,6 @@ function joinRemoteWorkspacePath(remoteRoot, suffix) {
|
|
|
307
89
|
return remoteRoot.endsWith("/") ? `${remoteRoot}${suffix}` : `${remoteRoot}/${suffix}`;
|
|
308
90
|
}
|
|
309
91
|
//#endregion
|
|
310
|
-
//#region extensions/codex/src/app-server/remote-workspace-media.ts
|
|
311
|
-
const REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS = 6e4;
|
|
312
|
-
const REMOTE_WORKSPACE_MEDIA_MAX_BYTES = 67108864;
|
|
313
|
-
const REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS = 16;
|
|
314
|
-
const CODEX_REMOTE_MEDIA_CHUNK_BYTES = 524288;
|
|
315
|
-
const CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES = 1048576;
|
|
316
|
-
const CODEX_BOUNDED_REMOTE_FILE_READER = [
|
|
317
|
-
"try{",
|
|
318
|
-
"const fs=require(\"node:fs\");",
|
|
319
|
-
"const path=require(\"node:path\");",
|
|
320
|
-
"const file=process.argv[1];",
|
|
321
|
-
"const max=Number(process.argv[2]);",
|
|
322
|
-
"const offset=Number(process.argv[3]);",
|
|
323
|
-
"const chunk=Number(process.argv[4]);",
|
|
324
|
-
"const workspace=process.argv[5];",
|
|
325
|
-
"if(!Number.isSafeInteger(max)||max<0)throw Error(\"invalid media byte limit\");",
|
|
326
|
-
"if(!Number.isSafeInteger(offset)||offset<0||!Number.isSafeInteger(chunk)||chunk<=0)throw Error(\"invalid media chunk\");",
|
|
327
|
-
"if(fs.lstatSync(file).isSymbolicLink())throw Error(\"symbolic links are not allowed\");",
|
|
328
|
-
"const noFollow=fs.constants.O_NOFOLLOW??0;",
|
|
329
|
-
"const fd=fs.openSync(file,fs.constants.O_RDONLY|noFollow);",
|
|
330
|
-
"try{",
|
|
331
|
-
"const before=fs.fstatSync(fd);",
|
|
332
|
-
"if(!before.isFile())throw Error(\"not a regular file\");",
|
|
333
|
-
"if(workspace){",
|
|
334
|
-
"const descriptor=process.platform===\"linux\"?fs.realpathSync(`/proc/self/fd/${fd}`):fs.realpathSync(file);",
|
|
335
|
-
"const relative=path.relative(fs.realpathSync(workspace),descriptor);",
|
|
336
|
-
"if(!relative||relative===\"..\"||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative))throw Error(\"file escapes remote workspace\");",
|
|
337
|
-
"const verified=fs.statSync(descriptor);",
|
|
338
|
-
"if(verified.dev!==before.dev||verified.ino!==before.ino)throw Error(\"file changed while being opened\");",
|
|
339
|
-
"}",
|
|
340
|
-
"if(before.size>max)throw Error(`file exceeds limit of ${max} bytes`);",
|
|
341
|
-
"if(offset>before.size)throw Error(\"file changed while being read\");",
|
|
342
|
-
"const expected=Math.min(chunk,before.size-offset);",
|
|
343
|
-
"const buffer=Buffer.allocUnsafe(expected);",
|
|
344
|
-
"let total=0;",
|
|
345
|
-
"while(total<buffer.length){",
|
|
346
|
-
"const count=fs.readSync(fd,buffer,total,buffer.length-total,offset+total);",
|
|
347
|
-
"if(count===0)break;total+=count;",
|
|
348
|
-
"}",
|
|
349
|
-
"if(total!==expected)throw Error(\"file changed while being read\");",
|
|
350
|
-
"const after=fs.fstatSync(fd);",
|
|
351
|
-
"const revision=stat=>[stat.dev,stat.ino,stat.size,stat.mtimeMs,stat.ctimeMs].join(\":\");",
|
|
352
|
-
"if(!after.isFile()||revision(after)!==revision(before))throw Error(\"file changed while being read\");",
|
|
353
|
-
"process.stdout.write(JSON.stringify({dataBase64:buffer.toString(\"base64\"),size:before.size,revision:revision(before)}));",
|
|
354
|
-
"}finally{fs.closeSync(fd)}",
|
|
355
|
-
"}catch(error){process.stderr.write(error instanceof Error?error.message:String(error));process.exitCode=1}"
|
|
356
|
-
].join("");
|
|
357
|
-
const MESSAGE_MEDIA_KEYS = [
|
|
358
|
-
"media",
|
|
359
|
-
"mediaUrl",
|
|
360
|
-
"media_url",
|
|
361
|
-
"path",
|
|
362
|
-
"filePath",
|
|
363
|
-
"fileUrl",
|
|
364
|
-
"imageUrl",
|
|
365
|
-
"image_url"
|
|
366
|
-
];
|
|
367
|
-
const MESSAGE_MEDIA_ARRAY_KEYS = [
|
|
368
|
-
"mediaUrls",
|
|
369
|
-
"media_urls",
|
|
370
|
-
"imageUrls",
|
|
371
|
-
"image_urls"
|
|
372
|
-
];
|
|
373
|
-
const ATTACHMENT_MEDIA_KEYS = [
|
|
374
|
-
"media",
|
|
375
|
-
"mediaUrl",
|
|
376
|
-
"path",
|
|
377
|
-
"filePath",
|
|
378
|
-
"fileUrl",
|
|
379
|
-
"url"
|
|
380
|
-
];
|
|
381
|
-
/** Reads actual remote bytes with a cap enforced by Codex before transport. */
|
|
382
|
-
async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
383
|
-
if (!Number.isSafeInteger(params.maxBytes) || params.maxBytes < 0) throw new Error("Codex remote workspace upload requires a valid media byte limit.");
|
|
384
|
-
params.signal?.throwIfAborted();
|
|
385
|
-
const chunks = [];
|
|
386
|
-
let offset = 0;
|
|
387
|
-
let expectedSize;
|
|
388
|
-
let expectedRevision;
|
|
389
|
-
const startedAt = Date.now();
|
|
390
|
-
do {
|
|
391
|
-
params.signal?.throwIfAborted();
|
|
392
|
-
const timeoutMs = params.timeoutMs === void 0 ? void 0 : params.timeoutMs - (Date.now() - startedAt);
|
|
393
|
-
if (timeoutMs !== void 0 && timeoutMs <= 0) throw new Error("Codex remote workspace file transfer timed out.");
|
|
394
|
-
let response;
|
|
395
|
-
try {
|
|
396
|
-
response = await params.client.request("command/exec", {
|
|
397
|
-
command: [
|
|
398
|
-
"node",
|
|
399
|
-
"-e",
|
|
400
|
-
CODEX_BOUNDED_REMOTE_FILE_READER,
|
|
401
|
-
"--",
|
|
402
|
-
params.path,
|
|
403
|
-
String(params.maxBytes),
|
|
404
|
-
String(offset),
|
|
405
|
-
String(CODEX_REMOTE_MEDIA_CHUNK_BYTES),
|
|
406
|
-
...params.workspaceRoot ? [params.workspaceRoot] : []
|
|
407
|
-
],
|
|
408
|
-
env: {
|
|
409
|
-
NODE_OPTIONS: null,
|
|
410
|
-
NODE_PATH: null
|
|
411
|
-
},
|
|
412
|
-
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
413
|
-
}, {
|
|
414
|
-
signal: params.signal,
|
|
415
|
-
timeoutMs
|
|
416
|
-
});
|
|
417
|
-
} catch (error) {
|
|
418
|
-
if (error instanceof Error && /failed to spawn|executable.*not found|\bENOENT\b/iu.test(error.message)) throw new Error("Codex remote workspace file transfer requires Node.js on the remote app-server host.", { cause: error });
|
|
419
|
-
throw error;
|
|
420
|
-
}
|
|
421
|
-
if (!response || response.exitCode !== 0) {
|
|
422
|
-
const detail = typeof response?.stderr === "string" ? response.stderr.trim() : "";
|
|
423
|
-
throw new Error(`Codex remote workspace artifact could not be read: ${params.path}${detail ? `: ${detail}` : ""}`);
|
|
424
|
-
}
|
|
425
|
-
if (typeof response.stdout !== "string" || response.stdout.length > CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES) throw new Error("Codex remote workspace artifact exceeded the native command output cap.");
|
|
426
|
-
let payload;
|
|
427
|
-
try {
|
|
428
|
-
payload = JSON.parse(response.stdout);
|
|
429
|
-
} catch {
|
|
430
|
-
throw new Error("Codex remote workspace artifact returned invalid chunk data.");
|
|
431
|
-
}
|
|
432
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
|
|
433
|
-
const chunk = payload;
|
|
434
|
-
if (typeof chunk.dataBase64 !== "string" || !Number.isSafeInteger(chunk.size) || chunk.size < 0 || chunk.size > params.maxBytes || typeof chunk.revision !== "string" || !chunk.revision) throw new Error("Codex remote workspace artifact returned invalid or oversized chunk data.");
|
|
435
|
-
if (expectedSize === void 0) {
|
|
436
|
-
expectedSize = chunk.size;
|
|
437
|
-
expectedRevision = chunk.revision;
|
|
438
|
-
}
|
|
439
|
-
if (chunk.size !== expectedSize || chunk.revision !== expectedRevision) throw new Error("Codex remote workspace artifact changed during chunked transfer.");
|
|
440
|
-
const remainingBytes = expectedSize - offset;
|
|
441
|
-
const expectedChunkBytes = Math.min(CODEX_REMOTE_MEDIA_CHUNK_BYTES, remainingBytes);
|
|
442
|
-
if (chunk.dataBase64.length > Math.ceil(expectedChunkBytes / 3) * 4) throw new Error("Codex remote workspace artifact returned oversized chunk data.");
|
|
443
|
-
const buffer = Buffer.from(chunk.dataBase64, "base64");
|
|
444
|
-
if (buffer.byteLength !== expectedChunkBytes || buffer.toString("base64") !== chunk.dataBase64) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
|
|
445
|
-
chunks.push(buffer);
|
|
446
|
-
offset += buffer.byteLength;
|
|
447
|
-
} while (offset < (expectedSize ?? 0));
|
|
448
|
-
return { dataBase64: Buffer.concat(chunks, offset).toString("base64") };
|
|
449
|
-
}
|
|
450
|
-
/** Stages authoritative bounded remote bytes into immutable Gateway-owned media. */
|
|
451
|
-
async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
452
|
-
const { localWorkspaceRoot, remoteWorkspaceRoot } = params;
|
|
453
|
-
if (!localWorkspaceRoot || !remoteWorkspaceRoot) return params.args;
|
|
454
|
-
const remotePathsByLocalPath = /* @__PURE__ */ new Map();
|
|
455
|
-
const gatewayManagedPaths = /* @__PURE__ */ new Set();
|
|
456
|
-
const gatewayMediaRoot = getMediaDir();
|
|
457
|
-
let attachmentEntries = 0;
|
|
458
|
-
const mapMediaPath = (value) => {
|
|
459
|
-
if (typeof value !== "string") return value;
|
|
460
|
-
if (path.isAbsolute(value) && isPathStrictlyInside(gatewayMediaRoot, value)) {
|
|
461
|
-
attachmentEntries += 1;
|
|
462
|
-
gatewayManagedPaths.add(value);
|
|
463
|
-
return value;
|
|
464
|
-
}
|
|
465
|
-
const mapped = mapCodexAppServerLocalWorkspacePath({
|
|
466
|
-
value,
|
|
467
|
-
localWorkspaceRoot,
|
|
468
|
-
remoteWorkspaceRoot
|
|
469
|
-
});
|
|
470
|
-
if (value.trim() && !isCodexPassThroughMediaSource(value)) {
|
|
471
|
-
attachmentEntries += 1;
|
|
472
|
-
remotePathsByLocalPath.set(mapped, mapCodexAppServerRemoteWorkspacePath({
|
|
473
|
-
value: mapped,
|
|
474
|
-
localWorkspaceRoot,
|
|
475
|
-
remoteWorkspaceRoot
|
|
476
|
-
}));
|
|
477
|
-
}
|
|
478
|
-
return mapped;
|
|
479
|
-
};
|
|
480
|
-
let mappedArgs = params.args;
|
|
481
|
-
const setMappedValue = (key, value) => {
|
|
482
|
-
if (value === params.args[key]) return;
|
|
483
|
-
if (mappedArgs === params.args) mappedArgs = { ...params.args };
|
|
484
|
-
mappedArgs[key] = value;
|
|
485
|
-
};
|
|
486
|
-
for (const key of MESSAGE_MEDIA_KEYS) setMappedValue(key, mapMediaPath(params.args[key]));
|
|
487
|
-
for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
|
|
488
|
-
const value = params.args[key];
|
|
489
|
-
if (Array.isArray(value)) {
|
|
490
|
-
const mapped = value.map(mapMediaPath);
|
|
491
|
-
if (mapped.some((entry, index) => entry !== value[index])) setMappedValue(key, mapped);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if (Array.isArray(params.args.attachments)) {
|
|
495
|
-
const attachments = params.args.attachments;
|
|
496
|
-
const mapped = attachments.map((attachment) => {
|
|
497
|
-
if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
|
|
498
|
-
const record = attachment;
|
|
499
|
-
let mappedAttachment = record;
|
|
500
|
-
for (const key of ATTACHMENT_MEDIA_KEYS) {
|
|
501
|
-
const value = mapMediaPath(record[key]);
|
|
502
|
-
if (value !== record[key]) {
|
|
503
|
-
if (mappedAttachment === record) mappedAttachment = { ...record };
|
|
504
|
-
mappedAttachment[key] = value;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return mappedAttachment;
|
|
508
|
-
});
|
|
509
|
-
if (mapped.some((attachment, index) => attachment !== attachments[index])) setMappedValue("attachments", mapped);
|
|
510
|
-
}
|
|
511
|
-
if (attachmentEntries > REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS) throw new Error(`Codex remote workspace upload exceeds the ${REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS}-attachment limit.`);
|
|
512
|
-
for (const managedPath of gatewayManagedPaths) await assertGatewayManagedMediaPath(managedPath, gatewayMediaRoot);
|
|
513
|
-
if (remotePathsByLocalPath.size === 0) return mappedArgs;
|
|
514
|
-
const readRemoteFile = params.readRemoteFile;
|
|
515
|
-
if (!readRemoteFile) throw new Error("Codex remote workspace file transfer requires an active app-server client.");
|
|
516
|
-
const maxBytes = params.maxBytes ?? REMOTE_WORKSPACE_MEDIA_MAX_BYTES;
|
|
517
|
-
const timeoutMs = params.timeoutMs ?? REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS;
|
|
518
|
-
const deadline = Date.now() + timeoutMs;
|
|
519
|
-
const stagedPaths = /* @__PURE__ */ new Map();
|
|
520
|
-
let totalBytes = 0;
|
|
521
|
-
for (const [localPath, remotePath] of remotePathsByLocalPath) {
|
|
522
|
-
params.signal?.throwIfAborted();
|
|
523
|
-
const remainingBytes = maxBytes - totalBytes;
|
|
524
|
-
const remainingMs = deadline - Date.now();
|
|
525
|
-
if (remainingMs <= 0) throw new Error("Codex remote workspace attachment batch timed out.");
|
|
526
|
-
const response = await readRemoteFile({
|
|
527
|
-
path: remotePath,
|
|
528
|
-
maxBytes: remainingBytes,
|
|
529
|
-
workspaceRoot: remoteWorkspaceRoot,
|
|
530
|
-
signal: params.signal,
|
|
531
|
-
timeoutMs: remainingMs
|
|
532
|
-
});
|
|
533
|
-
if (!response || typeof response.dataBase64 !== "string") throw new Error(`Codex remote workspace artifact returned no file data: ${remotePath}`);
|
|
534
|
-
if (response.dataBase64.length > Math.ceil(remainingBytes / 3) * 4) throw new Error(`Codex remote workspace artifact exceeds the limit of ${remainingBytes} bytes.`);
|
|
535
|
-
const remoteBuffer = Buffer.from(response.dataBase64, "base64");
|
|
536
|
-
if (remoteBuffer.byteLength > remainingBytes || remoteBuffer.toString("base64") !== response.dataBase64) throw new Error(`Codex remote workspace artifact returned invalid or oversized file data: ${remotePath}`);
|
|
537
|
-
totalBytes += remoteBuffer.byteLength;
|
|
538
|
-
const saved = await saveMediaBuffer(remoteBuffer, void 0, "outbound", maxBytes, path.basename(remotePath));
|
|
539
|
-
stagedPaths.set(localPath, saved.path);
|
|
540
|
-
}
|
|
541
|
-
return mapMessageMediaValues(mappedArgs, (value) => stagedPaths.get(value) ?? value);
|
|
542
|
-
}
|
|
543
|
-
async function assertGatewayManagedMediaPath(value, mediaRoot) {
|
|
544
|
-
const opened = await (await root(mediaRoot, { symlinks: "reject" })).open(path.relative(mediaRoot, value), { symlinks: "reject" });
|
|
545
|
-
try {
|
|
546
|
-
if (!(await opened.handle.stat()).isFile()) throw new Error(`Codex Gateway-managed media is not a regular file: ${value}`);
|
|
547
|
-
} finally {
|
|
548
|
-
await opened[Symbol.asyncDispose]();
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
function mapMessageMediaValues(args, mapValue) {
|
|
552
|
-
const mapped = { ...args };
|
|
553
|
-
for (const key of MESSAGE_MEDIA_KEYS) {
|
|
554
|
-
const value = mapped[key];
|
|
555
|
-
if (typeof value === "string") mapped[key] = mapValue(value);
|
|
556
|
-
}
|
|
557
|
-
for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
|
|
558
|
-
const value = mapped[key];
|
|
559
|
-
if (Array.isArray(value)) mapped[key] = value.map((entry) => typeof entry === "string" ? mapValue(entry) : entry);
|
|
560
|
-
}
|
|
561
|
-
if (Array.isArray(mapped.attachments)) mapped.attachments = mapped.attachments.map((attachment) => {
|
|
562
|
-
if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
|
|
563
|
-
const record = { ...attachment };
|
|
564
|
-
for (const key of ATTACHMENT_MEDIA_KEYS) {
|
|
565
|
-
const value = record[key];
|
|
566
|
-
if (typeof value === "string") record[key] = mapValue(value);
|
|
567
|
-
}
|
|
568
|
-
return record;
|
|
569
|
-
});
|
|
570
|
-
return mapped;
|
|
571
|
-
}
|
|
572
|
-
//#endregion
|
|
573
|
-
//#region extensions/codex/src/app-server/dynamic-tool-build-state.ts
|
|
574
|
-
/** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
|
|
575
|
-
const dynamicToolBuildState = {};
|
|
576
|
-
//#endregion
|
|
577
92
|
//#region extensions/codex/src/app-server/vision-tools.ts
|
|
578
93
|
/**
|
|
579
94
|
* Codex's enabled native surface includes its stable view_image loader. Keep
|
|
@@ -641,46 +156,23 @@ function resolveCodexAppServerHookChannelId(params, sandboxSessionKey) {
|
|
|
641
156
|
}
|
|
642
157
|
const CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS = 1e3;
|
|
643
158
|
const CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS = 500;
|
|
644
|
-
/**
|
|
645
|
-
function createCodexDynamicToolBuildStageTracker(
|
|
646
|
-
|
|
647
|
-
mark() {},
|
|
648
|
-
snapshot() {
|
|
649
|
-
return {
|
|
650
|
-
totalMs: 0,
|
|
651
|
-
stages: []
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
const startedAt = Date.now();
|
|
656
|
-
let previousAt = startedAt;
|
|
657
|
-
const stages = [];
|
|
658
|
-
const toMs = (value) => Math.max(0, Math.round(value));
|
|
159
|
+
/** Captures bounded preparation stages before a slow turn needs diagnosis. */
|
|
160
|
+
function createCodexDynamicToolBuildStageTracker() {
|
|
161
|
+
const { mark, snapshot } = createStageTimingTracker();
|
|
659
162
|
return {
|
|
660
|
-
mark
|
|
661
|
-
|
|
662
|
-
stages.push({
|
|
663
|
-
name,
|
|
664
|
-
durationMs: toMs(currentAt - previousAt),
|
|
665
|
-
elapsedMs: toMs(currentAt - startedAt)
|
|
666
|
-
});
|
|
667
|
-
previousAt = currentAt;
|
|
668
|
-
},
|
|
669
|
-
snapshot() {
|
|
670
|
-
return {
|
|
671
|
-
totalMs: toMs(Date.now() - startedAt),
|
|
672
|
-
stages: stages.slice()
|
|
673
|
-
};
|
|
674
|
-
}
|
|
163
|
+
mark,
|
|
164
|
+
snapshot
|
|
675
165
|
};
|
|
676
166
|
}
|
|
677
167
|
/** Returns true when dynamic-tool construction is slow enough to warrant a warning log. */
|
|
678
|
-
function shouldWarnCodexDynamicToolBuildStageSummary(summary) {
|
|
679
|
-
|
|
168
|
+
function shouldWarnCodexDynamicToolBuildStageSummary(summary, profilerEnabled = false) {
|
|
169
|
+
const totalWarnMs = profilerEnabled ? CODEX_DYNAMIC_TOOL_BUILD_WARN_TOTAL_MS : 1e4;
|
|
170
|
+
const stageWarnMs = profilerEnabled ? CODEX_DYNAMIC_TOOL_BUILD_WARN_STAGE_MS : 5e3;
|
|
171
|
+
return summary.totalMs >= totalWarnMs || summary.stages.some((stage) => stage.durationMs >= stageWarnMs);
|
|
680
172
|
}
|
|
681
173
|
/** Formats per-stage timings into the compact form used by Codex app-server logs. */
|
|
682
174
|
function formatCodexDynamicToolBuildStageSummary(summary) {
|
|
683
|
-
return summary.stages
|
|
175
|
+
return formatStageTimings(summary.stages);
|
|
684
176
|
}
|
|
685
177
|
/** Builds, filters, and normalizes Codex-compatible runtime tools for a single turn. */
|
|
686
178
|
async function buildDynamicTools(input) {
|
|
@@ -702,9 +194,9 @@ async function buildDynamicTools(input) {
|
|
|
702
194
|
input.onWebSearchPolicyResolved?.(false);
|
|
703
195
|
return [];
|
|
704
196
|
}
|
|
705
|
-
const toolBuildStages = createCodexDynamicToolBuildStageTracker(
|
|
197
|
+
const toolBuildStages = createCodexDynamicToolBuildStageTracker();
|
|
706
198
|
const modelHasVision = params.model.input?.includes("image") ?? false;
|
|
707
|
-
const agentDir = params.agentDir ?? resolveAgentDir
|
|
199
|
+
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
|
|
708
200
|
const injectedOpenClawCodingToolsFactory = dynamicToolBuildState.openClawCodingToolsFactory;
|
|
709
201
|
const nativeExecutionPolicy = resolveCodexNativeExecutionPolicyForRun(params, {
|
|
710
202
|
agentId: input.policyAgentId,
|
|
@@ -738,31 +230,17 @@ async function buildDynamicTools(input) {
|
|
|
738
230
|
...toolConstructionPlan ? { toolConstructionPlan } : {},
|
|
739
231
|
messageProvider: resolveCodexMessageToolProvider(params),
|
|
740
232
|
toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
messageThreadId: params.messageThreadId,
|
|
746
|
-
nativeChannelId: params.chatId,
|
|
747
|
-
messageActionTurnCapability: params.messageActionTurnCapability,
|
|
748
|
-
groupId: params.groupId,
|
|
749
|
-
groupChannel: params.groupChannel,
|
|
750
|
-
groupSpace: params.groupSpace,
|
|
751
|
-
spawnedBy: params.spawnedBy,
|
|
752
|
-
senderId: params.senderId,
|
|
753
|
-
senderName: params.senderName,
|
|
754
|
-
senderUsername: params.senderUsername,
|
|
755
|
-
senderE164: params.senderE164,
|
|
756
|
-
senderIsOwner: params.senderIsOwner,
|
|
233
|
+
...params.onToolResult ? { questionPrompt: {
|
|
234
|
+
send: params.onToolResult,
|
|
235
|
+
...params.messageChannel ? { messageChannel: params.messageChannel } : {}
|
|
236
|
+
} } : {},
|
|
757
237
|
inputProvenance: params.inputProvenance,
|
|
758
238
|
trustedInternalHandoff: params.trustedInternalHandoff,
|
|
759
|
-
scheduledToolPolicy: params.scheduledToolPolicy,
|
|
760
239
|
allowGatewaySubagentBinding: params.allowGatewaySubagentBinding || isForcedPrivateQaCodexRuntime(),
|
|
761
240
|
sessionKey: input.sandboxSessionKey,
|
|
762
241
|
runSessionKey: params.sessionKey && params.sessionKey !== input.sandboxSessionKey ? params.sessionKey : void 0,
|
|
763
242
|
sessionId: params.sessionId,
|
|
764
243
|
runId: params.runId,
|
|
765
|
-
approvalReviewerDeviceId: params.approvalReviewerDeviceId,
|
|
766
244
|
agentDir,
|
|
767
245
|
preparedModelRuntime: params.preparedModelRuntime,
|
|
768
246
|
cwd: input.effectiveCwd ?? input.effectiveWorkspace,
|
|
@@ -787,25 +265,18 @@ async function buildDynamicTools(input) {
|
|
|
787
265
|
modelAuthMode: resolveModelAuthMode(params.model.provider, params.config, params.toolAuthProfileStore ?? params.authProfileStore, { workspaceDir: input.effectiveWorkspace }),
|
|
788
266
|
suppressManagedWebSearch: false,
|
|
789
267
|
webFetchHostnameAllowlistRef,
|
|
790
|
-
currentChannelId: params.currentChannelId,
|
|
791
|
-
currentMessagingTarget: params.currentMessagingTarget,
|
|
792
268
|
hookChannelId: resolveCodexAppServerHookChannelId(params, input.sandboxSessionKey),
|
|
793
|
-
currentThreadTs: params.currentThreadTs,
|
|
794
|
-
currentMessageId: params.currentMessageId,
|
|
795
|
-
replyToMode: params.replyToMode,
|
|
796
|
-
hasRepliedRef: params.hasRepliedRef,
|
|
797
269
|
modelHasVision,
|
|
798
270
|
computerContextEpoch: input.computerContextEpoch,
|
|
271
|
+
oneShotCliRun: params.oneShotCliRun,
|
|
799
272
|
registerRunCleanup: input.registerRunCleanup,
|
|
800
273
|
requireExplicitMessageTarget: params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
|
|
801
|
-
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
|
802
|
-
taskSuggestionDeliveryMode: params.taskSuggestionDeliveryMode,
|
|
803
274
|
disableMessageTool: input.ignoreDisableMessageTool ? false : params.disableMessageTool,
|
|
804
275
|
forceMessageTool: shouldForceMessageTool(messagePolicyParams),
|
|
805
276
|
enableHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
|
|
806
277
|
forceHeartbeatTool: params.trigger === "heartbeat" || input.forceHeartbeatTool === true,
|
|
807
278
|
onYield: (message, acknowledgment) => {
|
|
808
|
-
input.onYieldDetected(acknowledgment);
|
|
279
|
+
input.onYieldDetected(message, acknowledgment);
|
|
809
280
|
input.onCodexAppServerEvent?.({
|
|
810
281
|
stream: "codex_app_server.tool",
|
|
811
282
|
data: {
|
|
@@ -825,6 +296,7 @@ async function buildDynamicTools(input) {
|
|
|
825
296
|
cronCreatorToolAllowlistCaptureRef: input.cronCreatorToolAllowlistCaptureRef,
|
|
826
297
|
cronCreatorAuthorityUnavailableReason: input.cronCreatorAuthorityUnavailableReason
|
|
827
298
|
};
|
|
299
|
+
input.onMessageToolTargetResolved?.(options.requireExplicitMessageTarget === true);
|
|
828
300
|
const buildOpenClawCodingTools = () => {
|
|
829
301
|
const bindingOptions = { cwd: input.effectiveCwd ?? input.effectiveWorkspace };
|
|
830
302
|
if (injectedOpenClawCodingToolsFactory) return params.hostCapabilities.bindToolSurface(injectedOpenClawCodingToolsFactory(options), bindingOptions);
|
|
@@ -911,7 +383,7 @@ async function buildDynamicTools(input) {
|
|
|
911
383
|
}))
|
|
912
384
|
});
|
|
913
385
|
const summary = toolBuildStages.snapshot();
|
|
914
|
-
if (shouldWarnCodexDynamicToolBuildStageSummary(summary)) {
|
|
386
|
+
if (shouldWarnCodexDynamicToolBuildStageSummary(summary, input.profilerEnabled)) {
|
|
915
387
|
const phase = input.forceHeartbeatTool ? "registered-tools" : "runtime-tools";
|
|
916
388
|
embeddedAgentLog.warn(`codex app-server dynamic tool build timings runId=${params.runId} sessionId=${params.sessionId} phase=${phase} totalMs=${summary.totalMs} stages=${formatCodexDynamicToolBuildStageSummary(summary)}`, {
|
|
917
389
|
runId: params.runId,
|
|
@@ -1033,82 +505,589 @@ function disableCodexPluginThreadConfig(pluginConfig) {
|
|
|
1033
505
|
...config.codexPlugins,
|
|
1034
506
|
enabled: false
|
|
1035
507
|
}
|
|
1036
|
-
};
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/** Adds sandbox_exec/process aliases when native Code Mode cannot directly honor the sandbox. */
|
|
511
|
+
function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input, executionPolicy) {
|
|
512
|
+
if (isCodexMemoryFlushRun(input.params) || !executionPolicy.nativeToolSurfaceAllowed || !input.sandbox?.enabled || !input.sandbox.backendId.trim() || input.nativeToolSurfaceEnabled !== false || isSandboxShellDynamicToolExcluded(input.pluginConfig)) return filteredTools;
|
|
513
|
+
const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
|
|
514
|
+
const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
|
|
515
|
+
if (!execTool || !processTool) return filteredTools;
|
|
516
|
+
const sandboxExecTool = {
|
|
517
|
+
...execTool,
|
|
518
|
+
name: "sandbox_exec",
|
|
519
|
+
description: "Run a shell command through OpenClaw's configured sandbox backend for this session. Use when OpenClaw sandboxing is active or when a command must execute in the sandbox backend, such as an SSH-backed sandbox or Docker container-path bind layout. Use Codex's native shell only when no OpenClaw sandbox is active and native Code Mode is available.",
|
|
520
|
+
execute: async (toolCallId, args, signal, onUpdate) => {
|
|
521
|
+
const result = await execTool.execute(toolCallId, args, signal, onUpdate);
|
|
522
|
+
return {
|
|
523
|
+
...result,
|
|
524
|
+
content: result.content.map((item) => item.type === "text" ? Object.assign({}, item, { text: item.text.replace("Use process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.", "Use sandbox_process (list/poll/log/write/send-keys/submit/paste/kill/clear/remove) for follow-up.") }) : item)
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
const sandboxProcessTool = {
|
|
529
|
+
...processTool,
|
|
530
|
+
name: "sandbox_process",
|
|
531
|
+
description: "Manage background shell sessions through OpenClaw's configured sandbox backend for this session: list, poll, log, write, send-keys, submit, paste, kill, clear, or remove. Use only for sandbox follow-up; use Codex's native shell session handling only when no OpenClaw sandbox is active and native Code Mode is available."
|
|
532
|
+
};
|
|
533
|
+
return [
|
|
534
|
+
...filteredTools,
|
|
535
|
+
sandboxExecTool,
|
|
536
|
+
sandboxProcessTool
|
|
537
|
+
];
|
|
538
|
+
}
|
|
539
|
+
function isSandboxShellDynamicToolExcluded(config) {
|
|
540
|
+
return isCodexDynamicToolExcluded(config, [
|
|
541
|
+
"exec",
|
|
542
|
+
"sandbox_exec",
|
|
543
|
+
"process",
|
|
544
|
+
"sandbox_process"
|
|
545
|
+
]);
|
|
546
|
+
}
|
|
547
|
+
async function addNodeShellDynamicToolsIfNeeded(filteredTools, allTools, input, nodePolicy) {
|
|
548
|
+
if (isCodexMemoryFlushRun(input.params)) return filteredTools;
|
|
549
|
+
const nodeExecIsDefault = nodePolicy.effectiveExecHost === "node";
|
|
550
|
+
const nodeExecAvailableFromAuto = nodePolicy.requestedExecHost === "auto" && nodePolicy.effectiveExecHost === "gateway";
|
|
551
|
+
if (!nodeExecIsDefault && !nodeExecAvailableFromAuto) return filteredTools;
|
|
552
|
+
const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
|
|
553
|
+
if (!execTool || isCodexDynamicToolExcluded(input.pluginConfig, ["exec", "node_exec"]) || filteredTools.some((tool) => normalizeCodexDynamicToolName(tool.name) === "node_exec")) return filteredTools;
|
|
554
|
+
const nodeExec = await createNodeExecAliasDynamicTool(execTool, nodePolicy.node, input.runAbortController.signal, input.nodeExecAvailability);
|
|
555
|
+
return nodeExec ? [...filteredTools, nodeExec] : filteredTools;
|
|
556
|
+
}
|
|
557
|
+
function shouldKeepOpenClawShellDynamicTools(input, nodePolicy) {
|
|
558
|
+
return !isCodexMemoryFlushRun(input.params) && input.nativeToolSurfaceEnabled === false && input.sandbox?.enabled !== true && nodePolicy.effectiveExecHost !== "node";
|
|
559
|
+
}
|
|
560
|
+
/** Keeps replacement shell tools direct even when model metadata mandates Codex Code Mode. */
|
|
561
|
+
function placeDisabledNativeShellToolsInDirectNamespace(tools, nativeToolSurfaceEnabled) {
|
|
562
|
+
if (nativeToolSurfaceEnabled !== false) return tools;
|
|
563
|
+
for (const tool of tools) if (CODEX_DISABLED_NATIVE_SHELL_DYNAMIC_TOOLS.has(normalizeCodexDynamicToolName(tool.name))) tool.catalogMode = "direct-only";
|
|
564
|
+
return tools;
|
|
565
|
+
}
|
|
566
|
+
/** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
|
|
567
|
+
function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
|
|
568
|
+
if (!toolsAllow) return tools;
|
|
569
|
+
if (toolsAllow.length === 0) return [];
|
|
570
|
+
if (hasWildcardCodexToolsAllow(toolsAllow)) return tools;
|
|
571
|
+
const allowSet = new Set(toolsAllow.map((name) => normalizeCodexDynamicToolName(name)).filter(Boolean));
|
|
572
|
+
return tools.filter((tool) => {
|
|
573
|
+
const normalized = normalizeCodexDynamicToolName(tool.name);
|
|
574
|
+
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");
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
/** Detects the wildcard allowlist marker after Codex tool-name normalization. */
|
|
578
|
+
function hasWildcardCodexToolsAllow(toolsAllow) {
|
|
579
|
+
return toolsAllow.some((name) => normalizeCodexDynamicToolName(name) === "*");
|
|
580
|
+
}
|
|
581
|
+
/** Forces message delivery through the message tool when the source channel requires it. */
|
|
582
|
+
function shouldForceMessageTool(params) {
|
|
583
|
+
return params.disableMessageTool !== true && params.sourceReplyDeliveryMode === "message_tool_only";
|
|
584
|
+
}
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region extensions/codex/src/app-server/event-projector-native-tool-lifecycle.ts
|
|
587
|
+
function isMcpToolCallItemNotification(method, params) {
|
|
588
|
+
return (method === "item/started" || method === "item/completed") && isJsonObject(params.item) && params.item.type === "mcpToolCall";
|
|
589
|
+
}
|
|
590
|
+
/** Owns native item lifetimes, outcome order, presentation, and approval correlation. */
|
|
591
|
+
var CodexNativeToolLifecycleProjector = class {
|
|
592
|
+
constructor(context, threadId, turnId, options = {}) {
|
|
593
|
+
this.context = context;
|
|
594
|
+
this.threadId = threadId;
|
|
595
|
+
this.turnId = turnId;
|
|
596
|
+
this.options = options;
|
|
597
|
+
this.terminalPresentationClearedItemIds = /* @__PURE__ */ new Set();
|
|
598
|
+
this.nativeToolOutcomeOrdinals = /* @__PURE__ */ new Map();
|
|
599
|
+
this.startedAtByItem = /* @__PURE__ */ new Map();
|
|
600
|
+
this.activeItems = /* @__PURE__ */ new Map();
|
|
601
|
+
this.webSearchCompletionByItem = /* @__PURE__ */ new Map();
|
|
602
|
+
this.completedItemIds = /* @__PURE__ */ new Set();
|
|
603
|
+
this.approvalFailureDispositionByItem = /* @__PURE__ */ new Map();
|
|
604
|
+
this.preToolUseFailureByItem = /* @__PURE__ */ new Map();
|
|
605
|
+
this.finalized = false;
|
|
606
|
+
this.turnCompleted = false;
|
|
607
|
+
this.pendingMcpNotifications = 0;
|
|
608
|
+
}
|
|
609
|
+
recordNativeToolOutcome(item) {
|
|
610
|
+
if (!item || this.nativeToolOutcomeOrdinals.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
|
|
611
|
+
const ordinal = this.context.allocateToolOutcomeOrdinal?.(item.id);
|
|
612
|
+
if (ordinal !== void 0) this.nativeToolOutcomeOrdinals.set(item.id, ordinal);
|
|
613
|
+
}
|
|
614
|
+
clearTerminalPresentationForNativeItem(item) {
|
|
615
|
+
if (!item || this.terminalPresentationClearedItemIds.has(item.id) || !shouldClearTerminalPresentationForNativeItem(item)) return;
|
|
616
|
+
const toolCallOrdinal = this.nativeToolOutcomeOrdinals.get(item.id);
|
|
617
|
+
this.terminalPresentationClearedItemIds.add(item.id);
|
|
618
|
+
this.context.onToolOutcome?.({
|
|
619
|
+
toolName: itemName(item) ?? item.type,
|
|
620
|
+
argsHash: "",
|
|
621
|
+
resultHash: "",
|
|
622
|
+
...toolCallOrdinal !== void 0 ? { toolCallOrdinal } : {},
|
|
623
|
+
terminalPresentation: void 0,
|
|
624
|
+
presentationOnly: true
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
getActiveMcpToolCall(serverName) {
|
|
628
|
+
if (this.finalized || this.turnCompleted || this.pendingMcpNotifications > 0 || this.options.runAbortSignal?.aborted) return;
|
|
629
|
+
let candidate;
|
|
630
|
+
for (const { mcpToolCall } of this.activeItems.values()) {
|
|
631
|
+
if (mcpToolCall?.server !== serverName) continue;
|
|
632
|
+
if (candidate) return;
|
|
633
|
+
candidate = mcpToolCall;
|
|
634
|
+
}
|
|
635
|
+
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;
|
|
636
|
+
return {
|
|
637
|
+
id: candidate.id,
|
|
638
|
+
server: candidate.server,
|
|
639
|
+
tool: candidate.tool,
|
|
640
|
+
arguments: candidate.arguments
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
recordMcpToolCallReceipt(notification) {
|
|
644
|
+
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
645
|
+
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
646
|
+
if (isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications += 1;
|
|
647
|
+
else if (notification.method === "turn/completed" && readCodexTurn(params.turn)?.id === this.turnId) this.turnCompleted = true;
|
|
648
|
+
}
|
|
649
|
+
handleNotification(notification) {
|
|
650
|
+
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
651
|
+
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
652
|
+
if (this.pendingMcpNotifications > 0 && isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications -= 1;
|
|
653
|
+
if (notification.method === "turn/completed") {
|
|
654
|
+
const turn = readCodexTurn(params.turn);
|
|
655
|
+
if (!turn || turn.id !== this.turnId) return;
|
|
656
|
+
this.turnCompleted = true;
|
|
657
|
+
for (const item of turn.items ?? []) this.recordSnapshotItem(item);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (notification.method === "rawResponseItem/completed") {
|
|
661
|
+
const item = isJsonObject(params.item) ? params.item : void 0;
|
|
662
|
+
if (item) this.recordRawWebSearchResult(item);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (notification.method !== "item/started" && notification.method !== "item/completed") return;
|
|
666
|
+
const item = readItem(params.item);
|
|
667
|
+
if (!item) return;
|
|
668
|
+
this.recordItem({
|
|
669
|
+
phase: notification.method === "item/started" ? "start" : "result",
|
|
670
|
+
item,
|
|
671
|
+
sourceTimestampMs: asDateTimestampMs(notification.method === "item/started" ? params.startedAtMs : params.completedAtMs)
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
recordItem(params) {
|
|
675
|
+
const toolName = auditNativeToolName(params.item) ?? (params.item.type === "mcpToolCall" ? params.item.type : void 0);
|
|
676
|
+
if (!toolName || this.completedItemIds.has(params.item.id)) return;
|
|
677
|
+
if (params.phase === "start") {
|
|
678
|
+
this.recordStarted(params.item.id, toolName, auditNativeToolUnfinishedStatus(params.item), params.sourceTimestampMs, params.item.type === "mcpToolCall" ? params.item : void 0);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (params.item.type === "webSearch") {
|
|
682
|
+
this.webSearchCompletionByItem.set(params.item.id, {
|
|
683
|
+
runWasAborted: this.options.runAbortSignal?.aborted === true,
|
|
684
|
+
sourceTimestampMs: params.sourceTimestampMs
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const itemDurationMs = typeof params.item.durationMs === "number" ? params.item.durationMs : void 0;
|
|
689
|
+
this.recordTerminal(params.item.id, toolName, auditNativeToolTerminalStatus(params.item), {
|
|
690
|
+
itemDurationMs,
|
|
691
|
+
sourceTimestampMs: params.sourceTimestampMs
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
recordApprovalFailureDisposition(toolCallId, disposition) {
|
|
695
|
+
if (!this.completedItemIds.has(toolCallId)) this.approvalFailureDispositionByItem.set(toolCallId, disposition);
|
|
696
|
+
}
|
|
697
|
+
recordPreToolUseFailure(failure, runWasAborted = this.options.runAbortSignal?.aborted === true) {
|
|
698
|
+
if (this.completedItemIds.has(failure.toolCallId)) return;
|
|
699
|
+
const record = {
|
|
700
|
+
failure,
|
|
701
|
+
terminalReason: runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : failure.disposition
|
|
702
|
+
};
|
|
703
|
+
if (this.finalized) {
|
|
704
|
+
this.completedItemIds.add(failure.toolCallId);
|
|
705
|
+
this.emitPreToolUseFailure(record, failure.toolName, failure.durationMs);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
this.preToolUseFailureByItem.set(failure.toolCallId, record);
|
|
709
|
+
}
|
|
710
|
+
recordRawWebSearchResult(item) {
|
|
711
|
+
if (readStringField(item, "type") !== "web_search_call") return;
|
|
712
|
+
const toolCallId = readStringField(item, "id");
|
|
713
|
+
if (!toolCallId || this.completedItemIds.has(toolCallId)) return;
|
|
714
|
+
const toolName = "web_search";
|
|
715
|
+
this.recordStarted(toolCallId, toolName, "unknown");
|
|
716
|
+
const rawStatus = readStringField(item, "status");
|
|
717
|
+
if (rawStatus === "in_progress" || rawStatus === "running") return;
|
|
718
|
+
const status = rawStatus === "completed" ? "completed" : rawStatus === "cancelled" ? "cancelled" : rawStatus === "failed" || rawStatus === "error" || rawStatus === "incomplete" ? "failed" : "unknown";
|
|
719
|
+
this.recordTerminal(toolCallId, toolName, status, { sourceTimestampMs: this.webSearchCompletionByItem.get(toolCallId)?.sourceTimestampMs });
|
|
720
|
+
}
|
|
721
|
+
recordTerminal(toolCallId, toolName, status, options = {}) {
|
|
722
|
+
const runWasAborted = options.runWasAborted ?? this.options.runAbortSignal?.aborted === true;
|
|
723
|
+
const preToolUseFailure = this.preToolUseFailureByItem.get(toolCallId);
|
|
724
|
+
this.preToolUseFailureByItem.delete(toolCallId);
|
|
725
|
+
const approvalFailureDisposition = this.approvalFailureDispositionByItem.get(toolCallId);
|
|
726
|
+
this.approvalFailureDispositionByItem.delete(toolCallId);
|
|
727
|
+
this.completedItemIds.add(toolCallId);
|
|
728
|
+
this.activeItems.delete(toolCallId);
|
|
729
|
+
this.webSearchCompletionByItem.delete(toolCallId);
|
|
730
|
+
const startedAt = this.startedAtByItem.get(toolCallId);
|
|
731
|
+
this.startedAtByItem.delete(toolCallId);
|
|
732
|
+
const endedAt = options.sourceTimestampMs ?? Date.now();
|
|
733
|
+
const durationMs = options.itemDurationMs ?? (startedAt === void 0 ? 0 : Math.max(0, endedAt - startedAt));
|
|
734
|
+
if (preToolUseFailure) {
|
|
735
|
+
this.emitPreToolUseFailure(preToolUseFailure, toolName, durationMs, options.sourceTimestampMs);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const terminalEvent = approvalFailureDisposition ? {
|
|
739
|
+
type: "tool.execution.error",
|
|
740
|
+
durationMs,
|
|
741
|
+
errorCategory: "codex_native_tool_approval",
|
|
742
|
+
terminalReason: approvalFailureDisposition
|
|
743
|
+
} : status === "blocked" ? {
|
|
744
|
+
type: "tool.execution.blocked",
|
|
745
|
+
reason: "codex_native_tool_blocked",
|
|
746
|
+
deniedReason: "codex_native_tool_blocked"
|
|
747
|
+
} : status === "failed" || status === "cancelled" || status === "unknown" ? {
|
|
748
|
+
type: "tool.execution.error",
|
|
749
|
+
durationMs,
|
|
750
|
+
errorCategory: status === "unknown" ? "codex_native_tool_outcome_unknown" : status === "cancelled" ? "aborted" : "codex_native_tool_error",
|
|
751
|
+
...status === "unknown" ? { errorCode: "tool_outcome_unknown" } : {},
|
|
752
|
+
terminalReason: status === "unknown" ? "failed" : runWasAborted && this.options.runAbortSignal ? resolveCodexToolAbortTerminalReason(this.options.runAbortSignal) : status === "cancelled" ? "cancelled" : "failed"
|
|
753
|
+
} : {
|
|
754
|
+
type: "tool.execution.completed",
|
|
755
|
+
durationMs
|
|
756
|
+
};
|
|
757
|
+
emitTrustedDiagnosticEvent({
|
|
758
|
+
...this.buildBase(toolCallId, toolName),
|
|
759
|
+
...terminalEvent,
|
|
760
|
+
...options.sourceTimestampMs !== void 0 ? { sourceTimestampMs: options.sourceTimestampMs } : {}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
finalizeActive(runWasAborted = this.options.runAbortSignal?.aborted === true) {
|
|
764
|
+
this.finalized = true;
|
|
765
|
+
for (const [toolCallId, { toolName, unfinishedStatus }] of this.activeItems) {
|
|
766
|
+
const webSearchCompletion = this.webSearchCompletionByItem.get(toolCallId);
|
|
767
|
+
const itemRunWasAborted = webSearchCompletion ? webSearchCompletion.runWasAborted : runWasAborted;
|
|
768
|
+
this.recordTerminal(toolCallId, toolName, unfinishedStatus, {
|
|
769
|
+
runWasAborted: itemRunWasAborted,
|
|
770
|
+
sourceTimestampMs: webSearchCompletion?.sourceTimestampMs
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
for (const [toolCallId, record] of this.preToolUseFailureByItem) if (!this.completedItemIds.has(toolCallId)) this.recordTerminal(toolCallId, record.failure.toolName, "failed", { itemDurationMs: record.failure.durationMs });
|
|
774
|
+
this.activeItems.clear();
|
|
775
|
+
this.webSearchCompletionByItem.clear();
|
|
776
|
+
this.approvalFailureDispositionByItem.clear();
|
|
777
|
+
this.preToolUseFailureByItem.clear();
|
|
778
|
+
}
|
|
779
|
+
emitPreToolUseFailure(record, toolName, durationMs, sourceTimestampMs) {
|
|
780
|
+
emitCodexNativePreToolUseFailureDiagnostic({
|
|
781
|
+
agentId: this.context.agentId,
|
|
782
|
+
sessionId: this.context.sessionId,
|
|
783
|
+
sessionKey: this.context.sessionKey,
|
|
784
|
+
runId: this.context.runId,
|
|
785
|
+
failure: {
|
|
786
|
+
...record.failure,
|
|
787
|
+
toolName,
|
|
788
|
+
durationMs
|
|
789
|
+
},
|
|
790
|
+
terminalReason: record.terminalReason,
|
|
791
|
+
sourceTimestampMs
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
recordSnapshotItem(item) {
|
|
795
|
+
if (!auditNativeToolName(item) || this.completedItemIds.has(item.id) || itemStatus(item) === "running") return;
|
|
796
|
+
const toolName = auditNativeToolName(item);
|
|
797
|
+
if (!toolName) return;
|
|
798
|
+
this.recordStarted(item.id, toolName, auditNativeToolUnfinishedStatus(item));
|
|
799
|
+
this.recordItem({
|
|
800
|
+
phase: "result",
|
|
801
|
+
item
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
recordStarted(toolCallId, toolName, unfinishedStatus, sourceTimestampMs, mcpToolCall) {
|
|
805
|
+
if (this.activeItems.has(toolCallId)) return;
|
|
806
|
+
this.startedAtByItem.set(toolCallId, sourceTimestampMs ?? Date.now());
|
|
807
|
+
this.activeItems.set(toolCallId, {
|
|
808
|
+
toolName,
|
|
809
|
+
unfinishedStatus,
|
|
810
|
+
mcpToolCall
|
|
811
|
+
});
|
|
812
|
+
emitTrustedDiagnosticEvent({
|
|
813
|
+
type: "tool.execution.started",
|
|
814
|
+
...this.buildBase(toolCallId, toolName),
|
|
815
|
+
...sourceTimestampMs !== void 0 ? { sourceTimestampMs } : {}
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
buildBase(toolCallId, toolName) {
|
|
819
|
+
return {
|
|
820
|
+
agentId: this.context.agentId,
|
|
821
|
+
runId: this.context.runId,
|
|
822
|
+
sessionId: this.context.sessionId,
|
|
823
|
+
sessionKey: this.context.sessionKey,
|
|
824
|
+
toolName,
|
|
825
|
+
toolCallId
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
//#endregion
|
|
830
|
+
//#region extensions/codex/src/app-server/remote-workspace-media.ts
|
|
831
|
+
const REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS = 6e4;
|
|
832
|
+
const REMOTE_WORKSPACE_MEDIA_MAX_BYTES = 67108864;
|
|
833
|
+
const REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS = 16;
|
|
834
|
+
const CODEX_REMOTE_MEDIA_CHUNK_BYTES = 524288;
|
|
835
|
+
const CODEX_REMOTE_COMMAND_DEFAULT_OUTPUT_BYTES = 1048576;
|
|
836
|
+
const CODEX_BOUNDED_REMOTE_FILE_READER = [
|
|
837
|
+
"try{",
|
|
838
|
+
"const fs=require(\"node:fs\");",
|
|
839
|
+
"const path=require(\"node:path\");",
|
|
840
|
+
"const file=process.argv[1];",
|
|
841
|
+
"const max=Number(process.argv[2]);",
|
|
842
|
+
"const offset=Number(process.argv[3]);",
|
|
843
|
+
"const chunk=Number(process.argv[4]);",
|
|
844
|
+
"const workspace=process.argv[5];",
|
|
845
|
+
"if(!Number.isSafeInteger(max)||max<0)throw Error(\"invalid media byte limit\");",
|
|
846
|
+
"if(!Number.isSafeInteger(offset)||offset<0||!Number.isSafeInteger(chunk)||chunk<=0)throw Error(\"invalid media chunk\");",
|
|
847
|
+
"if(fs.lstatSync(file).isSymbolicLink())throw Error(\"symbolic links are not allowed\");",
|
|
848
|
+
"const noFollow=fs.constants.O_NOFOLLOW??0;",
|
|
849
|
+
"const fd=fs.openSync(file,fs.constants.O_RDONLY|noFollow);",
|
|
850
|
+
"try{",
|
|
851
|
+
"const before=fs.fstatSync(fd);",
|
|
852
|
+
"if(!before.isFile())throw Error(\"not a regular file\");",
|
|
853
|
+
"if(workspace){",
|
|
854
|
+
"const descriptor=process.platform===\"linux\"?fs.realpathSync(`/proc/self/fd/${fd}`):fs.realpathSync(file);",
|
|
855
|
+
"const relative=path.relative(fs.realpathSync(workspace),descriptor);",
|
|
856
|
+
"if(!relative||relative===\"..\"||relative.startsWith(`..${path.sep}`)||path.isAbsolute(relative))throw Error(\"file escapes remote workspace\");",
|
|
857
|
+
"const verified=fs.statSync(descriptor);",
|
|
858
|
+
"if(verified.dev!==before.dev||verified.ino!==before.ino)throw Error(\"file changed while being opened\");",
|
|
859
|
+
"}",
|
|
860
|
+
"if(before.size>max)throw Error(`file exceeds limit of ${max} bytes`);",
|
|
861
|
+
"if(offset>before.size)throw Error(\"file changed while being read\");",
|
|
862
|
+
"const expected=Math.min(chunk,before.size-offset);",
|
|
863
|
+
"const buffer=Buffer.allocUnsafe(expected);",
|
|
864
|
+
"let total=0;",
|
|
865
|
+
"while(total<buffer.length){",
|
|
866
|
+
"const count=fs.readSync(fd,buffer,total,buffer.length-total,offset+total);",
|
|
867
|
+
"if(count===0)break;total+=count;",
|
|
868
|
+
"}",
|
|
869
|
+
"if(total!==expected)throw Error(\"file changed while being read\");",
|
|
870
|
+
"const after=fs.fstatSync(fd);",
|
|
871
|
+
"const revision=stat=>[stat.dev,stat.ino,stat.size,stat.mtimeMs,stat.ctimeMs].join(\":\");",
|
|
872
|
+
"if(!after.isFile()||revision(after)!==revision(before))throw Error(\"file changed while being read\");",
|
|
873
|
+
"process.stdout.write(JSON.stringify({dataBase64:buffer.toString(\"base64\"),size:before.size,revision:revision(before)}));",
|
|
874
|
+
"}finally{fs.closeSync(fd)}",
|
|
875
|
+
"}catch(error){process.stderr.write(error instanceof Error?error.message:String(error));process.exitCode=1}"
|
|
876
|
+
].join("");
|
|
877
|
+
const MESSAGE_MEDIA_KEYS = [
|
|
878
|
+
"media",
|
|
879
|
+
"mediaUrl",
|
|
880
|
+
"media_url",
|
|
881
|
+
"path",
|
|
882
|
+
"filePath",
|
|
883
|
+
"fileUrl",
|
|
884
|
+
"imageUrl",
|
|
885
|
+
"image_url"
|
|
886
|
+
];
|
|
887
|
+
const MESSAGE_MEDIA_ARRAY_KEYS = [
|
|
888
|
+
"mediaUrls",
|
|
889
|
+
"media_urls",
|
|
890
|
+
"imageUrls",
|
|
891
|
+
"image_urls"
|
|
892
|
+
];
|
|
893
|
+
const ATTACHMENT_MEDIA_KEYS = [
|
|
894
|
+
"media",
|
|
895
|
+
"mediaUrl",
|
|
896
|
+
"path",
|
|
897
|
+
"filePath",
|
|
898
|
+
"fileUrl",
|
|
899
|
+
"url"
|
|
900
|
+
];
|
|
901
|
+
/** Reads actual remote bytes with a cap enforced by Codex before transport. */
|
|
902
|
+
async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
903
|
+
if (!Number.isSafeInteger(params.maxBytes) || params.maxBytes < 0) throw new Error("Codex remote workspace upload requires a valid media byte limit.");
|
|
904
|
+
params.signal?.throwIfAborted();
|
|
905
|
+
const chunks = [];
|
|
906
|
+
let offset = 0;
|
|
907
|
+
let expectedSize;
|
|
908
|
+
let expectedRevision;
|
|
909
|
+
const startedAt = Date.now();
|
|
910
|
+
do {
|
|
911
|
+
params.signal?.throwIfAborted();
|
|
912
|
+
const timeoutMs = params.timeoutMs === void 0 ? void 0 : params.timeoutMs - (Date.now() - startedAt);
|
|
913
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) throw new Error("Codex remote workspace file transfer timed out.");
|
|
914
|
+
let response;
|
|
915
|
+
try {
|
|
916
|
+
response = await params.client.request("command/exec", {
|
|
917
|
+
command: [
|
|
918
|
+
"node",
|
|
919
|
+
"-e",
|
|
920
|
+
CODEX_BOUNDED_REMOTE_FILE_READER,
|
|
921
|
+
"--",
|
|
922
|
+
params.path,
|
|
923
|
+
String(params.maxBytes),
|
|
924
|
+
String(offset),
|
|
925
|
+
String(CODEX_REMOTE_MEDIA_CHUNK_BYTES),
|
|
926
|
+
...params.workspaceRoot ? [params.workspaceRoot] : []
|
|
927
|
+
],
|
|
928
|
+
env: {
|
|
929
|
+
NODE_OPTIONS: null,
|
|
930
|
+
NODE_PATH: null
|
|
931
|
+
},
|
|
932
|
+
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
933
|
+
}, {
|
|
934
|
+
signal: params.signal,
|
|
935
|
+
timeoutMs
|
|
936
|
+
});
|
|
937
|
+
} catch (error) {
|
|
938
|
+
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 });
|
|
939
|
+
throw error;
|
|
940
|
+
}
|
|
941
|
+
if (!response || response.exitCode !== 0) {
|
|
942
|
+
const detail = typeof response?.stderr === "string" ? response.stderr.trim() : "";
|
|
943
|
+
throw new Error(`Codex remote workspace artifact could not be read: ${params.path}${detail ? `: ${detail}` : ""}`);
|
|
944
|
+
}
|
|
945
|
+
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.");
|
|
946
|
+
let payload;
|
|
947
|
+
try {
|
|
948
|
+
payload = JSON.parse(response.stdout);
|
|
949
|
+
} catch {
|
|
950
|
+
throw new Error("Codex remote workspace artifact returned invalid chunk data.");
|
|
951
|
+
}
|
|
952
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
|
|
953
|
+
const chunk = payload;
|
|
954
|
+
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.");
|
|
955
|
+
if (expectedSize === void 0) {
|
|
956
|
+
expectedSize = chunk.size;
|
|
957
|
+
expectedRevision = chunk.revision;
|
|
958
|
+
}
|
|
959
|
+
if (chunk.size !== expectedSize || chunk.revision !== expectedRevision) throw new Error("Codex remote workspace artifact changed during chunked transfer.");
|
|
960
|
+
const remainingBytes = expectedSize - offset;
|
|
961
|
+
const expectedChunkBytes = Math.min(CODEX_REMOTE_MEDIA_CHUNK_BYTES, remainingBytes);
|
|
962
|
+
if (chunk.dataBase64.length > Math.ceil(expectedChunkBytes / 3) * 4) throw new Error("Codex remote workspace artifact returned oversized chunk data.");
|
|
963
|
+
const buffer = Buffer.from(chunk.dataBase64, "base64");
|
|
964
|
+
if (buffer.byteLength !== expectedChunkBytes || buffer.toString("base64") !== chunk.dataBase64) throw new Error("Codex remote workspace artifact returned invalid chunk data.");
|
|
965
|
+
chunks.push(buffer);
|
|
966
|
+
offset += buffer.byteLength;
|
|
967
|
+
} while (offset < (expectedSize ?? 0));
|
|
968
|
+
return { dataBase64: Buffer.concat(chunks, offset).toString("base64") };
|
|
1037
969
|
}
|
|
1038
|
-
/**
|
|
1039
|
-
function
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
970
|
+
/** Stages authoritative bounded remote bytes into immutable Gateway-owned media. */
|
|
971
|
+
async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
972
|
+
const { localWorkspaceRoot, remoteWorkspaceRoot } = params;
|
|
973
|
+
if (!localWorkspaceRoot || !remoteWorkspaceRoot) return params.args;
|
|
974
|
+
const remotePathsByLocalPath = /* @__PURE__ */ new Map();
|
|
975
|
+
const gatewayManagedPaths = /* @__PURE__ */ new Set();
|
|
976
|
+
const gatewayMediaRoot = getMediaDir();
|
|
977
|
+
let attachmentEntries = 0;
|
|
978
|
+
const mapMediaPath = (value) => {
|
|
979
|
+
if (typeof value !== "string") return value;
|
|
980
|
+
if (path.isAbsolute(value) && isPathStrictlyInside(gatewayMediaRoot, value)) {
|
|
981
|
+
attachmentEntries += 1;
|
|
982
|
+
gatewayManagedPaths.add(value);
|
|
983
|
+
return value;
|
|
984
|
+
}
|
|
985
|
+
const mapped = mapCodexAppServerLocalWorkspacePath({
|
|
986
|
+
value,
|
|
987
|
+
localWorkspaceRoot,
|
|
988
|
+
remoteWorkspaceRoot
|
|
989
|
+
});
|
|
990
|
+
if (value.trim() && !isCodexPassThroughMediaSource(value)) {
|
|
991
|
+
attachmentEntries += 1;
|
|
992
|
+
remotePathsByLocalPath.set(mapped, mapCodexAppServerRemoteWorkspacePath({
|
|
993
|
+
value: mapped,
|
|
994
|
+
localWorkspaceRoot,
|
|
995
|
+
remoteWorkspaceRoot
|
|
996
|
+
}));
|
|
1054
997
|
}
|
|
998
|
+
return mapped;
|
|
1055
999
|
};
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1000
|
+
let mappedArgs = params.args;
|
|
1001
|
+
const setMappedValue = (key, value) => {
|
|
1002
|
+
if (value === params.args[key]) return;
|
|
1003
|
+
if (mappedArgs === params.args) mappedArgs = { ...params.args };
|
|
1004
|
+
mappedArgs[key] = value;
|
|
1060
1005
|
};
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1006
|
+
for (const key of MESSAGE_MEDIA_KEYS) setMappedValue(key, mapMediaPath(params.args[key]));
|
|
1007
|
+
for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
|
|
1008
|
+
const value = params.args[key];
|
|
1009
|
+
if (Array.isArray(value)) {
|
|
1010
|
+
const mapped = value.map(mapMediaPath);
|
|
1011
|
+
if (mapped.some((entry, index) => entry !== value[index])) setMappedValue(key, mapped);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (Array.isArray(params.args.attachments)) {
|
|
1015
|
+
const attachments = params.args.attachments;
|
|
1016
|
+
const mapped = attachments.map((attachment) => {
|
|
1017
|
+
if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
|
|
1018
|
+
const record = attachment;
|
|
1019
|
+
let mappedAttachment = record;
|
|
1020
|
+
for (const key of ATTACHMENT_MEDIA_KEYS) {
|
|
1021
|
+
const value = mapMediaPath(record[key]);
|
|
1022
|
+
if (value !== record[key]) {
|
|
1023
|
+
if (mappedAttachment === record) mappedAttachment = { ...record };
|
|
1024
|
+
mappedAttachment[key] = value;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return mappedAttachment;
|
|
1028
|
+
});
|
|
1029
|
+
if (mapped.some((attachment, index) => attachment !== attachments[index])) setMappedValue("attachments", mapped);
|
|
1030
|
+
}
|
|
1031
|
+
if (attachmentEntries > REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS) throw new Error(`Codex remote workspace upload exceeds the ${REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS}-attachment limit.`);
|
|
1032
|
+
for (const managedPath of gatewayManagedPaths) await assertGatewayManagedMediaPath(managedPath, gatewayMediaRoot);
|
|
1033
|
+
if (remotePathsByLocalPath.size === 0) return mappedArgs;
|
|
1034
|
+
const readRemoteFile = params.readRemoteFile;
|
|
1035
|
+
if (!readRemoteFile) throw new Error("Codex remote workspace file transfer requires an active app-server client.");
|
|
1036
|
+
const maxBytes = params.maxBytes ?? REMOTE_WORKSPACE_MEDIA_MAX_BYTES;
|
|
1037
|
+
const timeoutMs = params.timeoutMs ?? REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS;
|
|
1038
|
+
const deadline = Date.now() + timeoutMs;
|
|
1039
|
+
const stagedPaths = /* @__PURE__ */ new Map();
|
|
1040
|
+
let totalBytes = 0;
|
|
1041
|
+
for (const [localPath, remotePath] of remotePathsByLocalPath) {
|
|
1042
|
+
params.signal?.throwIfAborted();
|
|
1043
|
+
const remainingBytes = maxBytes - totalBytes;
|
|
1044
|
+
const remainingMs = deadline - Date.now();
|
|
1045
|
+
if (remainingMs <= 0) throw new Error("Codex remote workspace attachment batch timed out.");
|
|
1046
|
+
const response = await readRemoteFile({
|
|
1047
|
+
path: remotePath,
|
|
1048
|
+
maxBytes: remainingBytes,
|
|
1049
|
+
workspaceRoot: remoteWorkspaceRoot,
|
|
1050
|
+
signal: params.signal,
|
|
1051
|
+
timeoutMs: remainingMs
|
|
1052
|
+
});
|
|
1053
|
+
if (!response || typeof response.dataBase64 !== "string") throw new Error(`Codex remote workspace artifact returned no file data: ${remotePath}`);
|
|
1054
|
+
if (response.dataBase64.length > Math.ceil(remainingBytes / 3) * 4) throw new Error(`Codex remote workspace artifact exceeds the limit of ${remainingBytes} bytes.`);
|
|
1055
|
+
const remoteBuffer = Buffer.from(response.dataBase64, "base64");
|
|
1056
|
+
if (remoteBuffer.byteLength > remainingBytes || remoteBuffer.toString("base64") !== response.dataBase64) throw new Error(`Codex remote workspace artifact returned invalid or oversized file data: ${remotePath}`);
|
|
1057
|
+
totalBytes += remoteBuffer.byteLength;
|
|
1058
|
+
const saved = await saveMediaBuffer(remoteBuffer, void 0, "outbound", maxBytes, path.basename(remotePath));
|
|
1059
|
+
stagedPaths.set(localPath, saved.path);
|
|
1060
|
+
}
|
|
1061
|
+
return mapMessageMediaValues(mappedArgs, (value) => stagedPaths.get(value) ?? value);
|
|
1087
1062
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1063
|
+
async function assertGatewayManagedMediaPath(value, mediaRoot) {
|
|
1064
|
+
const opened = await (await root(mediaRoot, { symlinks: "reject" })).open(path.relative(mediaRoot, value), { symlinks: "reject" });
|
|
1065
|
+
try {
|
|
1066
|
+
if (!(await opened.handle.stat()).isFile()) throw new Error(`Codex Gateway-managed media is not a regular file: ${value}`);
|
|
1067
|
+
} finally {
|
|
1068
|
+
await opened[Symbol.asyncDispose]();
|
|
1069
|
+
}
|
|
1093
1070
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1071
|
+
function mapMessageMediaValues(args, mapValue) {
|
|
1072
|
+
const mapped = { ...args };
|
|
1073
|
+
for (const key of MESSAGE_MEDIA_KEYS) {
|
|
1074
|
+
const value = mapped[key];
|
|
1075
|
+
if (typeof value === "string") mapped[key] = mapValue(value);
|
|
1076
|
+
}
|
|
1077
|
+
for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
|
|
1078
|
+
const value = mapped[key];
|
|
1079
|
+
if (Array.isArray(value)) mapped[key] = value.map((entry) => typeof entry === "string" ? mapValue(entry) : entry);
|
|
1080
|
+
}
|
|
1081
|
+
if (Array.isArray(mapped.attachments)) mapped.attachments = mapped.attachments.map((attachment) => {
|
|
1082
|
+
if (!attachment || typeof attachment !== "object" || Array.isArray(attachment)) return attachment;
|
|
1083
|
+
const record = { ...attachment };
|
|
1084
|
+
for (const key of ATTACHMENT_MEDIA_KEYS) {
|
|
1085
|
+
const value = record[key];
|
|
1086
|
+
if (typeof value === "string") record[key] = mapValue(value);
|
|
1087
|
+
}
|
|
1088
|
+
return record;
|
|
1103
1089
|
});
|
|
1104
|
-
|
|
1105
|
-
/** Detects the wildcard allowlist marker after Codex tool-name normalization. */
|
|
1106
|
-
function hasWildcardCodexToolsAllow(toolsAllow) {
|
|
1107
|
-
return toolsAllow.some((name) => normalizeCodexDynamicToolName(name) === "*");
|
|
1108
|
-
}
|
|
1109
|
-
/** Forces message delivery through the message tool when the source channel requires it. */
|
|
1110
|
-
function shouldForceMessageTool(params) {
|
|
1111
|
-
return params.disableMessageTool !== true && params.sourceReplyDeliveryMode === "message_tool_only";
|
|
1090
|
+
return mapped;
|
|
1112
1091
|
}
|
|
1113
1092
|
//#endregion
|
|
1114
1093
|
//#region extensions/codex/src/app-server/sandbox-exec-server-node-relay.ts
|
|
@@ -1140,7 +1119,7 @@ const CODEX_NODE_HTTP_CREDENTIAL_FIELD_NAME_PATTERN = /^(?:(?:[a-z\d]+_)*(?:toke
|
|
|
1140
1119
|
const nodeExecServerTextDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
1141
1120
|
/** Produces the bounded, redacted terminal failure shared by pending and claimed node leases. */
|
|
1142
1121
|
function createCodexNodeExecServerDisconnectError(reason, cause) {
|
|
1143
|
-
const detail = cause === void 0 ? "" : `: ${truncateUtf16Safe(redactSensitiveText(formatErrorMessage$
|
|
1122
|
+
const detail = cause === void 0 ? "" : `: ${truncateUtf16Safe(redactSensitiveText(formatErrorMessage$2(cause), { mode: "tools" }), CODEX_NODE_EXEC_SERVER_MAX_FAILURE_DETAIL_CHARS)}`;
|
|
1144
1123
|
return /* @__PURE__ */ new Error(`Codex execution node disconnected; start a fresh attempt. (${reason}${detail})`);
|
|
1145
1124
|
}
|
|
1146
1125
|
/** Relays one authorized, single-use Codex exec-server channel without interpreting its protocol. */
|
|
@@ -1434,6 +1413,10 @@ const sandboxExecServerRegistry = {
|
|
|
1434
1413
|
}
|
|
1435
1414
|
};
|
|
1436
1415
|
//#endregion
|
|
1416
|
+
//#region extensions/codex/src/app-server/sandbox-exec-server.websocket.ts
|
|
1417
|
+
const require = createRequire(import.meta.url);
|
|
1418
|
+
const websocket = require(path.join(path.dirname(require.resolve("ws/package.json")), "index.js"));
|
|
1419
|
+
//#endregion
|
|
1437
1420
|
//#region extensions/codex/src/app-server/sandbox-exec-server/json-rpc.ts
|
|
1438
1421
|
/** JSON-RPC error code used when a sandbox filesystem resource does not exist. */
|
|
1439
1422
|
const JSON_RPC_NOT_FOUND = -32004;
|
|
@@ -3112,7 +3095,7 @@ async function startOpenClawExecServer(sandbox) {
|
|
|
3112
3095
|
fsBridge
|
|
3113
3096
|
};
|
|
3114
3097
|
}
|
|
3115
|
-
const server = new WebSocketServer({
|
|
3098
|
+
const server = new websocket.WebSocketServer({
|
|
3116
3099
|
host: "127.0.0.1",
|
|
3117
3100
|
port: 0,
|
|
3118
3101
|
maxPayload: connection.kind === "node" ? CODEX_NODE_EXEC_SERVER_MAX_MESSAGE_BYTES : CODEX_SANDBOX_EXEC_SERVER_MAX_INBOUND_MESSAGE_BYTES
|
|
@@ -4686,6 +4669,7 @@ const CODEX_DYNAMIC_TOOL_NAME_MAX_CHARS = 128;
|
|
|
4686
4669
|
const CODEX_DYNAMIC_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/u;
|
|
4687
4670
|
const ALWAYS_DIRECT_DYNAMIC_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
4688
4671
|
"agents_list",
|
|
4672
|
+
"agents_wait",
|
|
4689
4673
|
"progress_card",
|
|
4690
4674
|
"sessions_spawn",
|
|
4691
4675
|
"sessions_yield"
|
|
@@ -4864,6 +4848,34 @@ function readCodexDynamicToolDescriptor(tool, toolIndex) {
|
|
|
4864
4848
|
};
|
|
4865
4849
|
}
|
|
4866
4850
|
//#endregion
|
|
4851
|
+
//#region extensions/codex/src/app-server/dynamic-tool-availability.ts
|
|
4852
|
+
/** Prepare the actual executable set, then reproject only changed owner definitions. */
|
|
4853
|
+
function finalizeCodexToolAvailability(entries) {
|
|
4854
|
+
const snapshots = entries.map((entry) => ({
|
|
4855
|
+
entry,
|
|
4856
|
+
parameters: entry.tool.parameters,
|
|
4857
|
+
description: entry.tool.description
|
|
4858
|
+
}));
|
|
4859
|
+
const preparedNames = /* @__PURE__ */ new Set();
|
|
4860
|
+
finalizeAgentToolAvailability(entries.map((entry) => entry.tool), { onPrepared: (tool) => preparedNames.add(tool.name) });
|
|
4861
|
+
const tools = [];
|
|
4862
|
+
const quarantinedTools = [];
|
|
4863
|
+
for (const { entry, parameters, description } of snapshots) {
|
|
4864
|
+
if (entry.tool.parameters === parameters && entry.tool.description === description) {
|
|
4865
|
+
tools.push(entry);
|
|
4866
|
+
continue;
|
|
4867
|
+
}
|
|
4868
|
+
const projected = projectCodexDynamicTools([entry.tool]);
|
|
4869
|
+
tools.push(...projected.tools);
|
|
4870
|
+
quarantinedTools.push(...projected.quarantinedTools);
|
|
4871
|
+
}
|
|
4872
|
+
return {
|
|
4873
|
+
tools,
|
|
4874
|
+
quarantinedTools,
|
|
4875
|
+
preparedNames
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
//#endregion
|
|
4867
4879
|
//#region extensions/codex/src/app-server/dynamic-tools.ts
|
|
4868
4880
|
/**
|
|
4869
4881
|
* Bridges OpenClaw runtime tools into Codex app-server dynamic tool specs and
|
|
@@ -5068,18 +5080,21 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5068
5080
|
const registeredProjection = params.registeredTools ? projectCodexDynamicTools(params.registeredTools) : availableProjection;
|
|
5069
5081
|
const inheritedSpecs = params.registeredSpecs ? structuredClone([...params.registeredSpecs]) : void 0;
|
|
5070
5082
|
const inheritedNames = inheritedSpecs ? new Set(flattenCodexDynamicToolFunctions(inheritedSpecs).map((tool) => tool.name)) : void 0;
|
|
5071
|
-
const
|
|
5072
|
-
const
|
|
5073
|
-
const
|
|
5083
|
+
const registrationNames = inheritedNames ?? new Set(registeredProjection.tools.map((entry) => entry.name));
|
|
5084
|
+
const finalized = finalizeCodexToolAvailability(availableProjection.tools.filter((entry) => registrationNames.has(entry.name)));
|
|
5085
|
+
const availableTools = finalized.tools;
|
|
5086
|
+
availableProjection.quarantinedTools.push(...finalized.quarantinedTools);
|
|
5074
5087
|
const toolMap = new Map(availableTools.map((entry) => [entry.name, entry]));
|
|
5088
|
+
const quarantinedAvailableToolNames = new Set(availableProjection.quarantinedTools.map((tool) => tool.tool));
|
|
5089
|
+
const registeredSpecTools = (params.registeredTools ? registeredProjection.tools : availableTools).filter((entry) => !quarantinedAvailableToolNames.has(entry.name)).map((entry) => finalized.preparedNames.has(entry.name) ? toolMap.get(entry.name) ?? entry : entry);
|
|
5075
5090
|
const registeredToolNames = inheritedNames ?? new Set(registeredSpecTools.map((entry) => entry.name));
|
|
5076
5091
|
const quarantinedTools = dedupeQuarantinedDynamicTools([...availableProjection.quarantinedTools, ...registeredProjection.quarantinedTools]);
|
|
5077
|
-
|
|
5092
|
+
reportQuarantinedDynamicTools({
|
|
5078
5093
|
tools: quarantinedTools,
|
|
5079
5094
|
availableToolCount: availableTools.length,
|
|
5080
|
-
registeredToolCount: registeredSpecTools.length
|
|
5095
|
+
registeredToolCount: registeredSpecTools.length,
|
|
5096
|
+
hookContext: params.hookContext
|
|
5081
5097
|
});
|
|
5082
|
-
emitQuarantinedDynamicToolDiagnostics(quarantinedTools, params.hookContext);
|
|
5083
5098
|
const telemetry = {
|
|
5084
5099
|
didSendViaMessagingTool: false,
|
|
5085
5100
|
didDeliverSourceReplyViaMessageTool: false,
|
|
@@ -5222,6 +5237,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5222
5237
|
}));
|
|
5223
5238
|
const rawResult = await Reflect.apply(tool.execute, tool, executionArgs);
|
|
5224
5239
|
captureExecutionBoundary();
|
|
5240
|
+
if (toolName === "message" && asOptionalRecord(asOptionalRecord(rawResult.details)?.messageDelivery)?.sourceReplyDelivered === true) telemetry.sourceReplyDelivered = true;
|
|
5225
5241
|
const telemetryRawResult = sanitizeToolResult(rawResult);
|
|
5226
5242
|
const rawIsError = isToolResultError(rawResult);
|
|
5227
5243
|
const rawResultFailureKind = resolveToolResultFailureKind(rawResult);
|
|
@@ -5330,6 +5346,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5330
5346
|
const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
|
|
5331
5347
|
withDynamicToolAsyncStarted(response, asyncStarted);
|
|
5332
5348
|
const replaySafe = executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
5349
|
+
copyInternalToolResultState(rawResult, response);
|
|
5333
5350
|
return withDynamicToolExecutionState(response, {
|
|
5334
5351
|
executedArguments: executedArgs,
|
|
5335
5352
|
executionStarted: didStartExecution && !executionPrevented,
|
|
@@ -5386,19 +5403,36 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5386
5403
|
};
|
|
5387
5404
|
}
|
|
5388
5405
|
function projectCodexExecutableDynamicToolSurface(tools, hookContext) {
|
|
5389
|
-
const
|
|
5390
|
-
const
|
|
5406
|
+
const { tools: projectedTools, quarantinedTools } = projectCodexDynamicTools(tools);
|
|
5407
|
+
const wrappedTools = [];
|
|
5408
|
+
for (const entry of projectedTools) try {
|
|
5409
|
+
if (isToolWrappedWithBeforeToolCallHook(entry.tool)) {
|
|
5410
|
+
setBeforeToolCallDiagnosticsEnabled(entry.tool, false);
|
|
5411
|
+
wrappedTools.push(entry);
|
|
5412
|
+
continue;
|
|
5413
|
+
}
|
|
5414
|
+
wrappedTools.push({
|
|
5415
|
+
...entry,
|
|
5416
|
+
tool: wrapToolWithBeforeToolCallHook(entry.tool, hookContext, { emitDiagnostics: false })
|
|
5417
|
+
});
|
|
5418
|
+
} catch {
|
|
5419
|
+
quarantinedTools.push({
|
|
5420
|
+
tool: entry.name,
|
|
5421
|
+
violations: [`${entry.name} could not be wrapped for before-tool-call hooks`]
|
|
5422
|
+
});
|
|
5423
|
+
}
|
|
5391
5424
|
return {
|
|
5392
|
-
tools:
|
|
5393
|
-
quarantinedTools: dedupeQuarantinedDynamicTools(
|
|
5425
|
+
tools: wrappedTools,
|
|
5426
|
+
quarantinedTools: dedupeQuarantinedDynamicTools(quarantinedTools)
|
|
5394
5427
|
};
|
|
5395
5428
|
}
|
|
5396
5429
|
/** Applies the exact schema and hook-wrapper projection used by the executable Codex bridge. */
|
|
5397
5430
|
function projectCodexExecutableDynamicTools(params) {
|
|
5398
5431
|
const projected = projectCodexExecutableDynamicToolSurface(params.tools, params.hookContext);
|
|
5432
|
+
const finalized = finalizeCodexToolAvailability(projected.tools);
|
|
5399
5433
|
return {
|
|
5400
|
-
availableTools:
|
|
5401
|
-
quarantinedTools: projected.quarantinedTools
|
|
5434
|
+
availableTools: finalized.tools.map((entry) => entry.tool),
|
|
5435
|
+
quarantinedTools: [...projected.quarantinedTools, ...finalized.quarantinedTools]
|
|
5402
5436
|
};
|
|
5403
5437
|
}
|
|
5404
5438
|
function notifyAgentToolResult(observer, toolName, result, isError) {
|
|
@@ -5409,7 +5443,8 @@ function notifyAgentToolResult(observer, toolName, result, isError) {
|
|
|
5409
5443
|
isError
|
|
5410
5444
|
});
|
|
5411
5445
|
} catch (error) {
|
|
5412
|
-
|
|
5446
|
+
const message = formatToolExecutionErrorMessage(error, "Unknown error");
|
|
5447
|
+
embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${toolName} error=${message}`);
|
|
5413
5448
|
}
|
|
5414
5449
|
}
|
|
5415
5450
|
function failedToolResult(message, status = "failed") {
|
|
@@ -5424,50 +5459,22 @@ function failedToolResult(message, status = "failed") {
|
|
|
5424
5459
|
}
|
|
5425
5460
|
};
|
|
5426
5461
|
}
|
|
5427
|
-
function
|
|
5428
|
-
const wrappedTools = [];
|
|
5429
|
-
const quarantinedTools = [];
|
|
5430
|
-
for (const entry of tools) try {
|
|
5431
|
-
if (isToolWrappedWithBeforeToolCallHook(entry.tool)) {
|
|
5432
|
-
setBeforeToolCallDiagnosticsEnabled(entry.tool, false);
|
|
5433
|
-
wrappedTools.push(entry);
|
|
5434
|
-
continue;
|
|
5435
|
-
}
|
|
5436
|
-
wrappedTools.push({
|
|
5437
|
-
...entry,
|
|
5438
|
-
tool: wrapToolWithBeforeToolCallHook(entry.tool, hookContext, { emitDiagnostics: false })
|
|
5439
|
-
});
|
|
5440
|
-
} catch {
|
|
5441
|
-
quarantinedTools.push({
|
|
5442
|
-
tool: entry.name,
|
|
5443
|
-
violations: [`${entry.name} could not be wrapped for before-tool-call hooks`]
|
|
5444
|
-
});
|
|
5445
|
-
}
|
|
5446
|
-
return {
|
|
5447
|
-
tools: wrappedTools,
|
|
5448
|
-
quarantinedTools
|
|
5449
|
-
};
|
|
5450
|
-
}
|
|
5451
|
-
function warnQuarantinedDynamicTools(params) {
|
|
5462
|
+
function reportQuarantinedDynamicTools(params) {
|
|
5452
5463
|
if (params.tools.length === 0) return;
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
embeddedAgentLog.warn(`codex app-server quarantined ${unique.size} unsupported dynamic tool ${unique.size === 1 ? "definition" : "definitions"}: ${[...unique.keys()].join(", ")}; retained ${params.availableToolCount} available and ${params.registeredToolCount} registered tools`, {
|
|
5456
|
-
tools: [...unique.entries()].map(([tool, violations]) => ({
|
|
5464
|
+
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`, {
|
|
5465
|
+
tools: params.tools.map(({ tool, violations }) => ({
|
|
5457
5466
|
tool,
|
|
5458
5467
|
violations
|
|
5459
5468
|
})),
|
|
5460
5469
|
availableToolCount: params.availableToolCount,
|
|
5461
5470
|
registeredToolCount: params.registeredToolCount
|
|
5462
5471
|
});
|
|
5463
|
-
|
|
5464
|
-
function emitQuarantinedDynamicToolDiagnostics(tools, ctx) {
|
|
5465
|
-
for (const tool of tools) emitTrustedDiagnosticEvent({
|
|
5472
|
+
for (const tool of params.tools) emitTrustedDiagnosticEvent({
|
|
5466
5473
|
type: "tool.execution.blocked",
|
|
5467
|
-
agentId:
|
|
5468
|
-
runId:
|
|
5469
|
-
sessionId:
|
|
5470
|
-
sessionKey:
|
|
5474
|
+
agentId: params.hookContext?.agentId,
|
|
5475
|
+
runId: params.hookContext?.runId,
|
|
5476
|
+
sessionId: params.hookContext?.sessionId,
|
|
5477
|
+
sessionKey: params.hookContext?.sessionKey,
|
|
5471
5478
|
toolName: tool.tool,
|
|
5472
5479
|
deniedReason: "unsupported_tool_schema",
|
|
5473
5480
|
reason: tool.violations.join(", ")
|
|
@@ -5644,7 +5651,36 @@ function withDynamicToolAsyncStarted(response, asyncStarted) {
|
|
|
5644
5651
|
function normalizeToolResultMaxChars(maxChars) {
|
|
5645
5652
|
return typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS;
|
|
5646
5653
|
}
|
|
5647
|
-
function
|
|
5654
|
+
function sanitizeToolTextRuns(rawContent) {
|
|
5655
|
+
const content = [];
|
|
5656
|
+
for (let index = 0; index < rawContent.length;) {
|
|
5657
|
+
const item = rawContent[index];
|
|
5658
|
+
if (item.type !== "text") {
|
|
5659
|
+
content.push(item);
|
|
5660
|
+
index += 1;
|
|
5661
|
+
continue;
|
|
5662
|
+
}
|
|
5663
|
+
const textRun = [];
|
|
5664
|
+
while (index < rawContent.length) {
|
|
5665
|
+
const next = rawContent[index];
|
|
5666
|
+
if (next.type !== "text") break;
|
|
5667
|
+
textRun.push(next);
|
|
5668
|
+
index += 1;
|
|
5669
|
+
}
|
|
5670
|
+
const sanitizedText = sanitizeToolResult(textRun.map((entry) => entry.text).join(""));
|
|
5671
|
+
let offset = 0;
|
|
5672
|
+
content.push(...textRun.map((entry, runIndex) => {
|
|
5673
|
+
const targetEnd = runIndex === textRun.length - 1 ? sanitizedText.length : Math.min(sanitizedText.length, offset + entry.text.length);
|
|
5674
|
+
const text = sliceUtf16Safe(sanitizedText, offset, targetEnd);
|
|
5675
|
+
const sanitized = Object.assign({}, entry, { text });
|
|
5676
|
+
offset += text.length;
|
|
5677
|
+
return sanitized;
|
|
5678
|
+
}));
|
|
5679
|
+
}
|
|
5680
|
+
return content;
|
|
5681
|
+
}
|
|
5682
|
+
function convertToolContents(rawContent, toolResultMaxChars = DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS) {
|
|
5683
|
+
const content = sanitizeToolTextRuns(rawContent);
|
|
5648
5684
|
const maxChars = normalizeToolResultMaxChars(toolResultMaxChars);
|
|
5649
5685
|
const totalTextChars = content.reduce((total, item) => total + (item.type === "text" ? item.text.length : 0), 0);
|
|
5650
5686
|
if (content.reduce((total, item) => total + (item.type === "text" ? estimateToolResultTextChars(item.text) : 0), 0) <= maxChars) return content.flatMap(convertToolContent);
|
|
@@ -5758,4 +5794,4 @@ function isCronAddAction(args) {
|
|
|
5758
5794
|
return typeof action === "string" && action.trim().toLowerCase() === "add";
|
|
5759
5795
|
}
|
|
5760
5796
|
//#endregion
|
|
5761
|
-
export {
|
|
5797
|
+
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 };
|