@openclaw/codex 2026.9.5 → 2026.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.setup/{account-usage-runtime-C2MOIvfq.mjs → account-usage-runtime-Eem_WGqY.mjs} +5 -5
- package/dist/.setup/{apply-CJ0ZfmBG.mjs → apply-DDOi8uNx.mjs} +22 -28
- package/dist/.setup/{attempt-client-cleanup-BpYKdTA0.mjs → attempt-client-cleanup-CEv1cKwF.mjs} +58 -43
- package/dist/.setup/attempt-workspace-context-DUVEn_CS.mjs +331 -0
- package/dist/.setup/{auth-cache-key-QirmgiCo.mjs → auth-cache-key-B2mpaI9D.mjs} +9 -81
- package/dist/.setup/auth-profile-health-oHizqgye.mjs +53 -0
- package/dist/.setup/auth-profile-recovery-BREezV0L.mjs +14 -0
- package/dist/.setup/{binding-connection-Bm38niE8.mjs → binding-connection-ThoatDcb.mjs} +1 -1
- package/dist/.setup/{binding-connection.runtime-CnRMC0ta.mjs → binding-connection.runtime-yoHrABt-.mjs} +4 -4
- package/dist/.setup/{bounded-turn-BtlZcivC.mjs → bounded-turn-B2HScEgT.mjs} +138 -32
- package/dist/.setup/{build-state-Xx9ybZqa.mjs → build-state-C7EnDVgr.mjs} +1 -1
- package/dist/.setup/{capabilities-_vXcZGSx.mjs → capabilities-CDXOOFdZ.mjs} +1 -1
- package/dist/.setup/{client-vXs2929Q.mjs → client-Cs08OXVQ.mjs} +583 -559
- package/dist/.setup/client-catalog-response-BMgt_4q4.mjs +285 -0
- package/dist/.setup/{command-dispatch-naHIZwwF.mjs → command-dispatch-B4JuOsOk.mjs} +8 -4
- package/dist/.setup/{command-formatters-CGEkdawF.mjs → command-formatters-Bmqvr9tO.mjs} +8 -4
- package/dist/.setup/{command-handlers-BtAJEW1X.mjs → command-handlers-DW7Lx9oH.mjs} +106 -172
- package/dist/.setup/{command-rpc-DHFpn4le.mjs → command-rpc-DixIj3HW.mjs} +36 -21
- package/dist/.setup/{compact-C3i3qlzj.mjs → compact-Du5UvtxA.mjs} +14 -21
- package/dist/.setup/{config-CedDWjM-.mjs → config-BoTP_mrL.mjs} +3 -3
- package/dist/.setup/{config-options-BsanJ8QG.mjs → config-options-BvaRs51b.mjs} +46 -30
- package/dist/.setup/{config-runtime-CeAhl_h4.mjs → config-runtime-C8Rw1m1m.mjs} +1 -1
- package/dist/.setup/{config-security-BRQ2mgW9.mjs → config-security-BEReZ6go.mjs} +35 -120
- package/dist/.setup/{conversation-binding-DhzrpTM6.mjs → conversation-binding-DP-gMrBP.mjs} +21 -32
- package/dist/.setup/{conversation-control-DN53ns41.mjs → conversation-control-uK9Jomlc.mjs} +28 -67
- package/dist/.setup/{dynamic-tools-D16lOp67.mjs → dynamic-tools-BDXl-8Cw.mjs} +848 -578
- package/dist/.setup/{effective-mcp-catalog-K6kLCN8X.mjs → effective-mcp-catalog-CvnvnGIe.mjs} +3 -3
- package/dist/.setup/{ephemeral-turn-R1gA8Sn6.mjs → ephemeral-turn-poidXgek.mjs} +9 -6
- package/dist/.setup/{event-projector-assistant-message-DNUR1s0A.mjs → event-projector-assistant-message-BsruvNrH.mjs} +2 -4
- package/dist/.setup/{event-projector-items-DyTQ_XqL.mjs → event-projector-items-C6L_5ZiT.mjs} +42 -8
- package/dist/.setup/{event-projector-usage-C7Ntzs-f.mjs → event-projector-usage-CnBS8Wa3.mjs} +1 -1
- package/dist/.setup/{harness-rxaaoN-o.mjs → harness-a_MsE4-_.mjs} +23 -17
- package/dist/.setup/{inference-context-fulhZuz8.mjs → inference-context-D96l9-ew.mjs} +1 -1
- package/dist/.setup/inference-proxy-b-aJMSxm.mjs +601 -0
- package/dist/.setup/{isolated-completion-B6srt48J.mjs → isolated-completion-BIZFhTNG.mjs} +17 -10
- package/dist/.setup/launch-args-DbFCehO7.mjs +196 -0
- package/dist/.setup/{managed-binary-DP6Rib3P.mjs → managed-binary-BnshlFag.mjs} +13 -2
- package/dist/.setup/{model-catalog-DkvUuBey.mjs → model-catalog-CfXJf9Ay.mjs} +9 -10
- package/dist/.setup/{models-BDFgm_G-.mjs → models-KnqBv0hE.mjs} +3 -3
- package/dist/.setup/{native-auth-BLZGqFjw.mjs → native-auth-rWbGbMn3.mjs} +4 -4
- package/dist/.setup/{native-subagent-history-YXaL6dMd.mjs → native-subagent-history-0nYYgo0k.mjs} +26 -13
- package/dist/.setup/{native-subagent-monitor-B1hxzIOy.mjs → native-subagent-monitor-D3filLY2.mjs} +145 -317
- package/dist/.setup/{native-subagent-task-ids-DsuOonKv.mjs → native-subagent-task-ids-wb8CKZGN.mjs} +69 -39
- package/dist/.setup/{node-cli-sessions-Cl8b2ZF3.mjs → node-cli-sessions-r3avj02l.mjs} +86 -68
- package/dist/.setup/node-exec-server.runtime-BDwWXVpK.mjs +247 -0
- package/dist/.setup/{plan-xD7WKYTM.mjs → plan-CN3VwfNU.mjs} +74 -76
- package/dist/.setup/{plugin-activation-r8nq5Jzp.mjs → plugin-activation-DT3MNtqj.mjs} +7 -16
- package/dist/.setup/{plugin-app-cache-key-BOxKP7db.mjs → plugin-app-cache-key-B2CsSdnV.mjs} +4 -20
- package/dist/.setup/{plugin-approval-roundtrip-CjfzafAZ.mjs → plugin-approval-roundtrip-C0MZzCuP.mjs} +1 -1
- package/dist/.setup/{plugin-inventory-DUle8DLz.mjs → plugin-inventory-BoRei8Z4.mjs} +16 -7
- package/dist/.setup/{plugin-marketplace-discovery-P-Vndfqq.mjs → plugin-marketplace-discovery-CDOi8IFs.mjs} +4 -1
- package/dist/.setup/{plugin-metadata-cache-DsS5buNa.mjs → plugin-metadata-cache-Ctmo83k_.mjs} +224 -154
- package/dist/.setup/protocol-CANUwXJ3.mjs +17 -0
- package/dist/.setup/{provider-fFh_-VR3.mjs → provider-BOsxQHOM.mjs} +9 -5
- package/dist/.setup/{provider-capabilities-B8J4Dy-O.mjs → provider-capabilities-BfE88FZ5.mjs} +31 -3
- package/dist/.setup/{rate-limits-DOEGYRNs.mjs → rate-limits-CMU3DUD0.mjs} +116 -137
- package/dist/.setup/{request-BioXJLYa.mjs → request-D2L0zMrq.mjs} +23 -15
- package/dist/.setup/{run-attempt-rlA07pUv.mjs → run-attempt-BHu11r7h.mjs} +1197 -617
- package/dist/.setup/{runtime-artifact-Dy38Z5iU.mjs → runtime-artifact-CLZ8udh-.mjs} +29 -45
- package/dist/.setup/{sandbox-guard-BBP-RbTf.mjs → sandbox-guard-C2sRMOEY.mjs} +1 -1
- package/dist/.setup/{session-binding-_0W_fHRr.mjs → session-binding-Cm0apEbd.mjs} +3 -32
- package/dist/.setup/{session-binding-record-TH3xA0zj.mjs → session-binding-record-BGoz8wOK.mjs} +22 -3
- package/dist/.setup/{session-binding-sidecars-CKYEcfNn.mjs → session-binding-sidecars-BlB5EvIS.mjs} +48 -45
- package/dist/.setup/{session-catalog-fUdWDx-U.mjs → session-catalog-Pno1xGcl.mjs} +482 -182
- package/dist/.setup/{session-catalog-adoption-CR_whkEW.mjs → session-catalog-adoption-BI6UTDZT.mjs} +57 -25
- package/dist/.setup/{session-catalog-archive-CwZaPacf.mjs → session-catalog-archive-lHBqzhaO.mjs} +6 -6
- package/dist/.setup/{session-catalog-events-jMcYfQyd.mjs → session-catalog-events-Bj6j94E_.mjs} +122 -25
- package/dist/.setup/{session-catalog-index-B141u15s.mjs → session-catalog-index-B9_VQIUz.mjs} +178 -78
- package/dist/.setup/{session-catalog-index-order-Cc5BzlQr.mjs → session-catalog-index-order-hcdz07yN.mjs} +2 -2
- package/dist/.setup/{session-catalog-index-state-BAuDztHN.mjs → session-catalog-index-state-Dgh8ORJm.mjs} +5 -2
- package/dist/.setup/{session-catalog-native-projection-ChV3cCtm.mjs → session-catalog-native-projection-DowriLid.mjs} +30 -13
- package/dist/.setup/{session-catalog-node-adoption-BVRsYOSh.mjs → session-catalog-node-adoption-ITddWx_X.mjs} +24 -12
- package/dist/.setup/{session-catalog-node-continue-Dowv8osw.mjs → session-catalog-node-continue-6g6qi4A9.mjs} +22 -13
- package/dist/.setup/{session-catalog-projection-C1Bj9Jl-.mjs → session-catalog-projection-zK_PUsiE.mjs} +13 -5
- package/dist/.setup/{session-cli-DIj-IE-E.mjs → session-cli-irrD-uo7.mjs} +4 -4
- package/dist/.setup/{session-history-C--SrLI8.mjs → session-history-BrqhSCf9.mjs} +17 -22
- package/dist/.setup/{session-history-import-JHXT5Xfi.mjs → session-history-import-D-3wAgOP.mjs} +4 -4
- package/dist/.setup/{session-history.worker-CpNj7H_P.mjs → session-history.worker-CaCh0xF_.mjs} +5 -18
- package/dist/.setup/{session-initialization-DH1z6fXn.mjs → session-initialization-rcgbZiid.mjs} +2 -2
- package/dist/.setup/{session-permission-policy-DhurXY0l.mjs → session-permission-policy-BI1QK_98.mjs} +2 -2
- package/dist/.setup/{session-retirement-CU87AOg9.mjs → session-retirement-BH-S6qkJ.mjs} +25 -6
- package/dist/.setup/{settled-turn-finalizer-CiY2bLFK.mjs → settled-turn-finalizer-DiAi6Kx6.mjs} +8 -8
- package/dist/.setup/{shared-client-BpTtuLQ5.mjs → shared-client-DA4VR4Eb.mjs} +59 -89
- package/dist/.setup/{side-question-DSCZyoBD.mjs → side-question-BJ1wSDcB.mjs} +147 -211
- package/dist/.setup/{source-Bjp4UQij.mjs → source-CUzJq5WN.mjs} +9 -9
- package/dist/.setup/spawn-error-CL3n6MAa.mjs +57 -0
- package/dist/.setup/{thread-archive-guard-jmecf6OJ.mjs → thread-archive-guard-C9frICzX.mjs} +10 -3
- package/dist/.setup/{thread-history-page-BTx-4xsg.mjs → thread-history-page-DoVpnHim.mjs} +1 -1
- package/dist/.setup/{thread-lifecycle-Cik28qWn.mjs → thread-lifecycle-B3mfz1TG.mjs} +172 -2271
- package/dist/.setup/{thread-ownership-skomxnIt.mjs → thread-ownership-DcTtAcXj.mjs} +4 -2
- package/dist/.setup/{thread-policy-Cjs4B8xf.mjs → thread-policy-DGuQsuFC.mjs} +19 -3
- package/dist/.setup/thread-requests-BLvGkP2R.mjs +2205 -0
- package/dist/.setup/{thread-resume-BkNpj9Pz.mjs → thread-resume-DIBXwOpE.mjs} +6 -6
- package/dist/.setup/{timeout-DZF05XrR.mjs → timeout-C910MdAB.mjs} +19 -2
- package/dist/.setup/{transcript-history-projection-DHNBDhCU.mjs → transcript-history-projection-ZZpkFkJ_.mjs} +94 -64
- package/dist/.setup/{transcript-mirror-D4qp_leP.mjs → transcript-mirror-BtHDBp1G.mjs} +218 -198
- package/dist/.setup/{transport-stdio-Cmx94Oiy.mjs → transport-stdio-h2VHOskC.mjs} +135 -55
- package/dist/.setup/{upstream-session-fork-D3UKxgVD.mjs → upstream-session-fork-BUr4OUbC.mjs} +44 -36
- package/dist/.setup/{usage-BOgBEKso.mjs → usage-BML0r-ua.mjs} +5 -4
- package/dist/.setup/{usage-limit-error-C0ow7DOc.mjs → usage-limit-error-DUMbefVY.mjs} +4 -4
- package/dist/.setup/{web-search-provider.runtime-CTGuJt6i.mjs → web-search-provider.runtime-CDKlN-Rv.mjs} +4 -3
- package/dist/api.js +23 -12
- package/dist/catalog-page-worker-entrypoint.js +12 -0
- package/dist/catalog-page.worker.js +9 -0
- package/dist/cli-metadata.js +1 -1
- package/dist/doctor-contract-api.js +2 -2
- package/dist/harness.js +1 -1
- package/dist/index.js +221 -170
- package/dist/media-understanding-provider.js +2 -2
- package/dist/migration-provider-api.js +1 -1
- package/dist/provider-discovery.js +1 -1
- package/dist/session-history-worker-runtime.js +15 -32
- package/dist/session-history.worker.js +1 -1
- package/package.json +9 -6
- package/dist/.setup/attempt-context-VXVwTCfS.mjs +0 -636
- package/dist/.setup/inference-proxy-CC15HuSi.mjs +0 -311
- package/dist/.setup/launch-args-DnMak49W.mjs +0 -111
- package/dist/.setup/node-exec-server.runtime-BaViBKNz.mjs +0 -226
- package/dist/.setup/version-CoqzXPgK.mjs +0 -12
- package/dist/.setup/{native-execution-policy-YBMHSzC4.mjs → native-execution-policy-umd0tgh4.mjs} +1 -1
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { CODEX_NATIVE_TOOL_REQUIREMENTS } from "../native-tool-policy.js";
|
|
2
|
-
import { d as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE, m as isJsonObject, p as flattenCodexDynamicToolFunctions } from "./config-security-BRQ2mgW9.mjs";
|
|
3
2
|
import { d as readCodexPluginConfig, l as isCodexRemoteExecPlacementSandbox } from "./config-parsing-CcB9iPoq.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { o as formatCodexDisplayText } from "./command-formatters-
|
|
6
|
-
import { c as itemName, f as shouldClearTerminalPresentationForNativeItem, l as itemStatus, n as auditNativeToolTerminalStatus, r as auditNativeToolUnfinishedStatus, t as auditNativeToolName
|
|
7
|
-
import { s as codexAppIdentityKey } from "./plugin-app-cache-key-
|
|
8
|
-
import "./config-
|
|
3
|
+
import { i as isJsonObject, r as flattenCodexDynamicToolFunctions, t as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE } from "./protocol-CANUwXJ3.mjs";
|
|
4
|
+
import { o as formatCodexDisplayText } from "./command-formatters-Bmqvr9tO.mjs";
|
|
5
|
+
import { S as readItem, c as itemName, f as shouldClearTerminalPresentationForNativeItem, l as itemStatus, n as auditNativeToolTerminalStatus, r as auditNativeToolUnfinishedStatus, t as auditNativeToolName } from "./event-projector-items-C6L_5ZiT.mjs";
|
|
6
|
+
import { s as codexAppIdentityKey } from "./plugin-app-cache-key-B2CsSdnV.mjs";
|
|
7
|
+
import "./config-BoTP_mrL.mjs";
|
|
9
8
|
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-tupAr-2o.mjs";
|
|
10
|
-
import { v as
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
9
|
+
import { V as protectCodexAppServerLiveThread, v as retainSharedCodexAppServerClientIfCurrent } from "./shared-client-DA4VR4Eb.mjs";
|
|
10
|
+
import { E as createCodexElicitationResponse, L as resolveCodexToolAbortTerminalReason, R as createFailedDynamicToolResponse, T as readCodexTurnCompletedNotification, m as CodexServerRequestResolvedError, z as failedToolResult } from "./client-Cs08OXVQ.mjs";
|
|
11
|
+
import { b as readCodexNotificationTurnId, v as isCodexNotificationForTurn, y as readCodexNotificationThreadId } from "./attempt-client-cleanup-CEv1cKwF.mjs";
|
|
12
|
+
import { A as emitCodexNativePreToolUseFailureDiagnostic, at as filterCodexDynamicTools, lt as isSystemAgentOnlyCodexDynamicToolAllowlist, ot as filterCodexDynamicToolsForDisabledNativeSurface, p as resolveCodexWebSearchPlan, st as isForcedPrivateQaCodexRuntime, ut as normalizeCodexDynamicToolName } from "./thread-requests-BLvGkP2R.mjs";
|
|
13
|
+
import { n as resolveCodexNativeExecutionPolicy, r as resolveCodexNodeExecToolOverrides } from "./native-execution-policy-umd0tgh4.mjs";
|
|
14
|
+
import { a as CODEX_NODE_EXEC_DYNAMIC_TOOL_NAME, c as createNodeExecAliasDynamicTool, d as isCodexDynamicToolExcluded, i as CODEX_GATEWAY_PROCESS_DYNAMIC_TOOL_NAME, l as createSandboxExecProjection, o as createGatewayExecProjection, r as CODEX_GATEWAY_EXEC_DYNAMIC_TOOL_NAME, s as createGatewayProcessProjection, u as createSandboxProcessProjection } from "./provider-capabilities-BfE88FZ5.mjs";
|
|
15
|
+
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-C0MZzCuP.mjs";
|
|
15
16
|
import { createRequire } from "node:module";
|
|
16
|
-
import { asNonArrayRecord, asOptionalRecord, isRecord,
|
|
17
|
+
import { asNonArrayRecord, asOptionalRecord, isRecord, normalizeTrimmedStringList, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
17
18
|
import { createHash, randomUUID } from "node:crypto";
|
|
18
19
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
19
|
-
import {
|
|
20
|
+
import { signalProcessTree, spawnTerminalPty } from "openclaw/plugin-sdk/process-runtime";
|
|
20
21
|
import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
|
|
21
22
|
import path, { posix } from "node:path";
|
|
23
|
+
import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
22
24
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
23
25
|
import { once } from "node:events";
|
|
24
26
|
import { spawn } from "node:child_process";
|
|
@@ -27,24 +29,19 @@ import { DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, estimateToolResultTextChars, resolv
|
|
|
27
29
|
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
28
30
|
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
29
31
|
import { createStageTimingTracker, formatStageTimings } from "openclaw/plugin-sdk/time-runtime";
|
|
30
|
-
import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
31
32
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
32
33
|
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
|
33
|
-
import { HEARTBEAT_RESPONSE_TOOL_NAME, applyEmbeddedAttemptToolsAllow, 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,
|
|
34
|
+
import { HEARTBEAT_RESPONSE_TOOL_NAME, applyEmbeddedAttemptToolsAllow, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, consumeAdjustedParamsForToolCall, consumePreExecutionBlockedToolCall, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, extractMessagingToolSourceReplyPayload, extractToolResultMediaArtifact, filterProviderNormalizableTools, filterToolResultMediaUrls, finalizeAgentToolAvailability, finalizeToolTerminalPresentation, formatApprovalDisplayPath, formatToolExecutionErrorMessage, getBeforeToolCallFailureDisposition, getChannelAgentToolMeta, getPluginToolMeta, getPluginToolSideEffectOwnerKey, hasNativeHookRelayInvocation, invokeNativeHookRelay, isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, isDeliveredMessagingToolSendToCurrentSource, isHostScopedAgentToolActive, isMessagingTool, isReplaySafeToolCall, isSubagentSessionKey, isToolResultError, isToolWrappedWithBeforeToolCallHook, normalizeAgentRuntimeTools, normalizeHeartbeatToolResponse, projectPluginMessageDeliveryFact, projectRuntimeToolInputSchema, readEmbeddedMessageDeliveryFact, readToolOperatorHint, resolveAttemptSpawnWorkspaceDir, resolveModelAuthMode, resolveNativeHookRelayDeferredToolApproval, resolveToolExecutionErrorKind, resolveToolResultFailureKind, runAgentHarnessAfterToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, supportsModelTools, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
34
35
|
import { formatErrorMessage as formatErrorMessage$2, redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
|
|
35
|
-
import { consumeTrustedToolNoStartError, copyInternalToolResultState, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
|
36
|
+
import { consumeTrustedToolNoStartError, copyInternalToolResultState, getCoreTtsToolResultMediaUrls, normalizeAcceptedSessionSpawnResult } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
|
36
37
|
import { isIP } from "node:net";
|
|
37
38
|
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
38
39
|
import { formatMcpCodexApprovalRemedy, requiresMcpCodexToolApproval, resolveCodexScheduledToolProjectionFactory, resolveProjectedMcpCodexToolApprovalMode, runWithCronCreatorAuthorityCapabilityResolver } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
39
|
-
import { buildRemoteCommand, isToolAllowed, sanitizeEnvVars } from "openclaw/plugin-sdk/sandbox";
|
|
40
|
+
import { SANDBOX_COMMAND_MAX_BUFFER_BYTES, buildRemoteCommand, isToolAllowed, prepareSandboxProcessCleanup, sanitizeEnvVars } from "openclaw/plugin-sdk/sandbox";
|
|
40
41
|
import { SsrFBlockedError, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
41
|
-
import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
|
42
|
+
import { normalizeMediaReferenceForComparison, saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
|
42
43
|
import { getMediaDir } from "openclaw/plugin-sdk/media-runtime";
|
|
43
44
|
import { normalizeOpenAIStrictCompatSchema } from "openclaw/plugin-sdk/provider-tools";
|
|
44
|
-
//#region extensions/codex/src/app-server/dynamic-tool-build-state.ts
|
|
45
|
-
/** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
|
|
46
|
-
const dynamicToolBuildState = {};
|
|
47
|
-
//#endregion
|
|
48
45
|
//#region extensions/codex/src/app-server/remote-workspace-path.ts
|
|
49
46
|
/** Projects a gateway workspace path into the remote Codex execution workspace. */
|
|
50
47
|
function mapCodexAppServerRemoteWorkspacePath(params) {
|
|
@@ -193,7 +190,6 @@ async function buildDynamicTools(input) {
|
|
|
193
190
|
const toolBuildStages = createCodexDynamicToolBuildStageTracker();
|
|
194
191
|
const modelHasVision = params.model.input?.includes("image") ?? false;
|
|
195
192
|
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
|
|
196
|
-
const injectedOpenClawCodingToolsFactory = dynamicToolBuildState.openClawCodingToolsFactory;
|
|
197
193
|
const nativeExecutionPolicy = resolveCodexNativeExecutionPolicyForRun(params, {
|
|
198
194
|
agentId: input.policyAgentId,
|
|
199
195
|
runtimeSessionKey: input.sandboxSessionKey,
|
|
@@ -249,7 +245,6 @@ async function buildDynamicTools(input) {
|
|
|
249
245
|
config: params.config,
|
|
250
246
|
skillsSnapshot: params.skillsSnapshot,
|
|
251
247
|
...params.skillLibraryAuthoring ? { skillWorkshop: { libraryAuthoring: params.skillLibraryAuthoring } } : {},
|
|
252
|
-
githubPublicationAvailable: params.githubPublicationAvailable,
|
|
253
248
|
authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
|
|
254
249
|
abortSignal: input.runAbortController.signal,
|
|
255
250
|
emitBeforeToolCallDiagnostics: false,
|
|
@@ -296,7 +291,6 @@ async function buildDynamicTools(input) {
|
|
|
296
291
|
input.onMessageToolTargetResolved?.(options.requireExplicitMessageTarget === true);
|
|
297
292
|
const buildOpenClawCodingTools = () => {
|
|
298
293
|
const bindingOptions = { cwd: input.effectiveCwd ?? input.effectiveWorkspace };
|
|
299
|
-
if (injectedOpenClawCodingToolsFactory) return params.hostCapabilities.bindToolSurface(injectedOpenClawCodingToolsFactory(options), bindingOptions);
|
|
300
294
|
const createToolSurface = params.hostCapabilities.createToolSurface;
|
|
301
295
|
if (!createToolSurface) throw new Error("Codex tool construction requires a current host capability");
|
|
302
296
|
return createToolSurface(options, bindingOptions);
|
|
@@ -509,29 +503,9 @@ function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input,
|
|
|
509
503
|
if (isCodexMemoryFlushRun(input.params) || !executionPolicy.nativeToolSurfaceAllowed || !input.sandbox?.enabled || !input.sandbox.backendId.trim() || input.nativeToolSurfaceEnabled !== false || isSandboxShellDynamicToolExcluded(input.pluginConfig)) return filteredTools;
|
|
510
504
|
const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
|
|
511
505
|
const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
|
|
512
|
-
if (!execTool
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
name: "sandbox_exec",
|
|
516
|
-
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.",
|
|
517
|
-
execute: async (toolCallId, args, signal, onUpdate) => {
|
|
518
|
-
const result = await execTool.execute(toolCallId, args, signal, onUpdate);
|
|
519
|
-
return {
|
|
520
|
-
...result,
|
|
521
|
-
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)
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
const sandboxProcessTool = {
|
|
526
|
-
...processTool,
|
|
527
|
-
name: "sandbox_process",
|
|
528
|
-
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."
|
|
529
|
-
};
|
|
530
|
-
return [
|
|
531
|
-
...filteredTools,
|
|
532
|
-
sandboxExecTool,
|
|
533
|
-
sandboxProcessTool
|
|
534
|
-
];
|
|
506
|
+
if (!execTool) return filteredTools;
|
|
507
|
+
const sandboxTools = [...filteredTools, createSandboxExecProjection(execTool)];
|
|
508
|
+
return processTool ? [...sandboxTools, createSandboxProcessProjection(processTool)] : sandboxTools;
|
|
535
509
|
}
|
|
536
510
|
function isSandboxShellDynamicToolExcluded(config) {
|
|
537
511
|
return isCodexDynamicToolExcluded(config, [
|
|
@@ -640,17 +614,17 @@ var CodexNativeToolLifecycleProjector = class {
|
|
|
640
614
|
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
641
615
|
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
642
616
|
if (isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications += 1;
|
|
643
|
-
else if (notification.method === "turn/completed" &&
|
|
617
|
+
else if (notification.method === "turn/completed" && readCodexTurnCompletedNotification(params)?.turn.id === this.turnId) this.turnCompleted = true;
|
|
644
618
|
}
|
|
645
619
|
handleNotification(notification) {
|
|
646
620
|
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
647
621
|
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
648
622
|
if (this.pendingMcpNotifications > 0 && isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications -= 1;
|
|
649
623
|
if (notification.method === "turn/completed") {
|
|
650
|
-
const turn =
|
|
624
|
+
const turn = readCodexTurnCompletedNotification(params)?.turn;
|
|
651
625
|
if (!turn || turn.id !== this.turnId) return;
|
|
652
626
|
this.turnCompleted = true;
|
|
653
|
-
for (const item of turn.items
|
|
627
|
+
for (const item of turn.items) this.recordSnapshotItem(item);
|
|
654
628
|
return;
|
|
655
629
|
}
|
|
656
630
|
if (notification.method === "rawResponseItem/completed") {
|
|
@@ -894,6 +868,22 @@ const ATTACHMENT_MEDIA_KEYS = [
|
|
|
894
868
|
"fileUrl",
|
|
895
869
|
"url"
|
|
896
870
|
];
|
|
871
|
+
function collectCodexMessageMediaUrls(record) {
|
|
872
|
+
const urls = [];
|
|
873
|
+
const push = (value) => {
|
|
874
|
+
if (typeof value === "string" && value.trim()) urls.push(value.trim());
|
|
875
|
+
};
|
|
876
|
+
for (const key of MESSAGE_MEDIA_KEYS) push(record[key]);
|
|
877
|
+
for (const key of MESSAGE_MEDIA_ARRAY_KEYS) {
|
|
878
|
+
const value = record[key];
|
|
879
|
+
if (Array.isArray(value)) for (const entry of value) push(entry);
|
|
880
|
+
}
|
|
881
|
+
if (Array.isArray(record.attachments)) for (const attachment of record.attachments) {
|
|
882
|
+
if (!isRecord(attachment)) continue;
|
|
883
|
+
for (const key of ATTACHMENT_MEDIA_KEYS) push(attachment[key]);
|
|
884
|
+
}
|
|
885
|
+
return urls;
|
|
886
|
+
}
|
|
897
887
|
/** Reads actual remote bytes with a cap enforced by Codex before transport. */
|
|
898
888
|
async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
899
889
|
if (!Number.isSafeInteger(params.maxBytes) || params.maxBytes < 0) throw new Error("Codex remote workspace upload requires a valid media byte limit.");
|
|
@@ -902,10 +892,10 @@ async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
|
902
892
|
let offset = 0;
|
|
903
893
|
let expectedSize;
|
|
904
894
|
let expectedRevision;
|
|
905
|
-
const startedAt =
|
|
895
|
+
const startedAt = performance.now();
|
|
906
896
|
do {
|
|
907
897
|
params.signal?.throwIfAborted();
|
|
908
|
-
const timeoutMs = params.timeoutMs === void 0 ? void 0 : params.timeoutMs - (
|
|
898
|
+
const timeoutMs = params.timeoutMs === void 0 ? void 0 : Math.floor(params.timeoutMs - (performance.now() - startedAt));
|
|
909
899
|
if (timeoutMs !== void 0 && timeoutMs <= 0) throw new Error("Codex remote workspace file transfer timed out.");
|
|
910
900
|
let response;
|
|
911
901
|
try {
|
|
@@ -966,7 +956,11 @@ async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
|
966
956
|
/** Stages authoritative bounded remote bytes into immutable Gateway-owned media. */
|
|
967
957
|
async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
968
958
|
const { localWorkspaceRoot, remoteWorkspaceRoot } = params;
|
|
969
|
-
|
|
959
|
+
const sourcePathsByStagedPath = /* @__PURE__ */ new Map();
|
|
960
|
+
if (!localWorkspaceRoot || !remoteWorkspaceRoot) return {
|
|
961
|
+
args: params.args,
|
|
962
|
+
sourcePathsByStagedPath
|
|
963
|
+
};
|
|
970
964
|
const remotePathsByLocalPath = /* @__PURE__ */ new Map();
|
|
971
965
|
const gatewayManagedPaths = /* @__PURE__ */ new Set();
|
|
972
966
|
const gatewayMediaRoot = getMediaDir();
|
|
@@ -985,11 +979,18 @@ async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
|
985
979
|
});
|
|
986
980
|
if (value.trim() && !isCodexPassThroughMediaSource(value)) {
|
|
987
981
|
attachmentEntries += 1;
|
|
988
|
-
|
|
982
|
+
const remotePath = mapCodexAppServerRemoteWorkspacePath({
|
|
989
983
|
value: mapped,
|
|
990
984
|
localWorkspaceRoot,
|
|
991
985
|
remoteWorkspaceRoot
|
|
992
|
-
})
|
|
986
|
+
});
|
|
987
|
+
const sourcePaths = remotePathsByLocalPath.get(mapped)?.sourcePaths ?? /* @__PURE__ */ new Set();
|
|
988
|
+
sourcePaths.add(value);
|
|
989
|
+
sourcePaths.add(remotePath);
|
|
990
|
+
remotePathsByLocalPath.set(mapped, {
|
|
991
|
+
remotePath,
|
|
992
|
+
sourcePaths
|
|
993
|
+
});
|
|
993
994
|
}
|
|
994
995
|
return mapped;
|
|
995
996
|
};
|
|
@@ -1026,18 +1027,21 @@ async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
|
1026
1027
|
}
|
|
1027
1028
|
if (attachmentEntries > REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS) throw new Error(`Codex remote workspace upload exceeds the ${REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS}-attachment limit.`);
|
|
1028
1029
|
for (const managedPath of gatewayManagedPaths) await assertGatewayManagedMediaPath(managedPath, gatewayMediaRoot);
|
|
1029
|
-
if (remotePathsByLocalPath.size === 0) return
|
|
1030
|
+
if (remotePathsByLocalPath.size === 0) return {
|
|
1031
|
+
args: mappedArgs,
|
|
1032
|
+
sourcePathsByStagedPath
|
|
1033
|
+
};
|
|
1030
1034
|
const readRemoteFile = params.readRemoteFile;
|
|
1031
1035
|
if (!readRemoteFile) throw new Error("Codex remote workspace file transfer requires an active app-server client.");
|
|
1032
1036
|
const maxBytes = params.maxBytes ?? REMOTE_WORKSPACE_MEDIA_MAX_BYTES;
|
|
1033
1037
|
const timeoutMs = params.timeoutMs ?? REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS;
|
|
1034
|
-
const deadline =
|
|
1038
|
+
const deadline = performance.now() + timeoutMs;
|
|
1035
1039
|
const stagedPaths = /* @__PURE__ */ new Map();
|
|
1036
1040
|
let totalBytes = 0;
|
|
1037
|
-
for (const [localPath, remotePath] of remotePathsByLocalPath) {
|
|
1041
|
+
for (const [localPath, { remotePath, sourcePaths }] of remotePathsByLocalPath) {
|
|
1038
1042
|
params.signal?.throwIfAborted();
|
|
1039
1043
|
const remainingBytes = maxBytes - totalBytes;
|
|
1040
|
-
const remainingMs = deadline -
|
|
1044
|
+
const remainingMs = Math.floor(deadline - performance.now());
|
|
1041
1045
|
if (remainingMs <= 0) throw new Error("Codex remote workspace attachment batch timed out.");
|
|
1042
1046
|
const response = await readRemoteFile({
|
|
1043
1047
|
path: remotePath,
|
|
@@ -1053,8 +1057,15 @@ async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
|
1053
1057
|
totalBytes += remoteBuffer.byteLength;
|
|
1054
1058
|
const saved = await saveMediaBuffer(remoteBuffer, void 0, "outbound", maxBytes, path.basename(remotePath));
|
|
1055
1059
|
stagedPaths.set(localPath, saved.path);
|
|
1060
|
+
sourcePathsByStagedPath.set(normalizeMediaReferenceForComparison(saved.path), [...sourcePaths]);
|
|
1056
1061
|
}
|
|
1057
|
-
return
|
|
1062
|
+
return {
|
|
1063
|
+
args: mapMessageMediaValues(mappedArgs, (value) => stagedPaths.get(value) ?? value),
|
|
1064
|
+
sourcePathsByStagedPath
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function resolveCodexMediaSourceUrls(mediaUrls, sourcePathsByStagedPath) {
|
|
1068
|
+
return [...new Set(mediaUrls.flatMap((url) => [url, ...sourcePathsByStagedPath?.get(normalizeMediaReferenceForComparison(url)) ?? []]))];
|
|
1058
1069
|
}
|
|
1059
1070
|
async function assertGatewayManagedMediaPath(value, mediaRoot) {
|
|
1060
1071
|
const opened = await (await root(mediaRoot, { symlinks: "reject" })).open(path.relative(mediaRoot, value), { symlinks: "reject" });
|
|
@@ -1086,6 +1097,209 @@ function mapMessageMediaValues(args, mapValue) {
|
|
|
1086
1097
|
return mapped;
|
|
1087
1098
|
}
|
|
1088
1099
|
//#endregion
|
|
1100
|
+
//#region extensions/codex/src/app-server/native-process-authority.ts
|
|
1101
|
+
const clients = /* @__PURE__ */ new WeakMap();
|
|
1102
|
+
/** A physical client joins native call receipts to its concrete sandbox processes. */
|
|
1103
|
+
function getCodexNativeProcessClient(client) {
|
|
1104
|
+
let owner = clients.get(client);
|
|
1105
|
+
if (!owner) {
|
|
1106
|
+
owner = new CodexNativeProcessClient(client);
|
|
1107
|
+
clients.set(client, owner);
|
|
1108
|
+
}
|
|
1109
|
+
return owner;
|
|
1110
|
+
}
|
|
1111
|
+
function hasCodexNativeBackgroundProcesses(client, threadId) {
|
|
1112
|
+
return clients.get(client)?.hasProcesses(threadId) ?? false;
|
|
1113
|
+
}
|
|
1114
|
+
var CodexNativeProcessClient = class {
|
|
1115
|
+
constructor(client) {
|
|
1116
|
+
this.client = client;
|
|
1117
|
+
this.id = randomUUID();
|
|
1118
|
+
this.authPath = `/openclaw-${randomUUID()}`;
|
|
1119
|
+
this.threads = /* @__PURE__ */ new Map();
|
|
1120
|
+
this.closed = false;
|
|
1121
|
+
client.addNotificationHandler((notification) => {
|
|
1122
|
+
if (!isJsonObject(notification.params)) return;
|
|
1123
|
+
const threadId = readCodexNotificationThreadId(notification.params);
|
|
1124
|
+
const turnId = readCodexNotificationTurnId(notification.params);
|
|
1125
|
+
const commands = threadId ? this.threads.get(threadId) : void 0;
|
|
1126
|
+
if (!commands || !turnId) return;
|
|
1127
|
+
if (notification.method === "turn/completed") {
|
|
1128
|
+
for (const command of commands.values()) if (command.turnId === turnId) this.closeAdmission(command);
|
|
1129
|
+
} else if (notification.method === "item/completed") {
|
|
1130
|
+
const item = notification.params.item;
|
|
1131
|
+
const command = isJsonObject(item) && typeof item.id === "string" ? commands.get(item.id) : void 0;
|
|
1132
|
+
if (command?.turnId === turnId) this.closeAdmission(command);
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
client.addCloseHandler(() => {
|
|
1136
|
+
this.closed = true;
|
|
1137
|
+
const owners = /* @__PURE__ */ new Set();
|
|
1138
|
+
for (const commands of this.threads.values()) for (const command of commands.values()) owners.add(command.owner);
|
|
1139
|
+
for (const owner of owners) owner.cancelClient(this);
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
admit(owner, receipt, parentTurn, assertActive) {
|
|
1143
|
+
if (this.closed) throw new Error("Codex process source client is closed");
|
|
1144
|
+
let commands = this.threads.get(receipt.threadId);
|
|
1145
|
+
const existing = commands?.get(receipt.itemId);
|
|
1146
|
+
if (existing) {
|
|
1147
|
+
if (existing.owner === owner && existing.turnId === receipt.turnId && existing.accepting) return;
|
|
1148
|
+
throw new Error("Codex reused an unsettled native command identity");
|
|
1149
|
+
}
|
|
1150
|
+
if (!commands) {
|
|
1151
|
+
commands = /* @__PURE__ */ new Map();
|
|
1152
|
+
this.threads.set(receipt.threadId, commands);
|
|
1153
|
+
}
|
|
1154
|
+
const command = {
|
|
1155
|
+
...receipt,
|
|
1156
|
+
owner,
|
|
1157
|
+
client: this,
|
|
1158
|
+
parentTurn,
|
|
1159
|
+
accepting: true,
|
|
1160
|
+
processes: /* @__PURE__ */ new Set(),
|
|
1161
|
+
assertActive,
|
|
1162
|
+
releaseClient: retainSharedCodexAppServerClientIfCurrent(this.client),
|
|
1163
|
+
releaseThread: protectCodexAppServerLiveThread(this.client, receipt.threadId)
|
|
1164
|
+
};
|
|
1165
|
+
commands.set(receipt.itemId, command);
|
|
1166
|
+
owner.commands.add(command);
|
|
1167
|
+
}
|
|
1168
|
+
hasProcesses(threadId) {
|
|
1169
|
+
return [...this.threads.get(threadId)?.values() ?? []].some((command) => command.processes.size > 0);
|
|
1170
|
+
}
|
|
1171
|
+
claim(metadata, terminate) {
|
|
1172
|
+
if (this.closed || !isJsonObject(metadata) || typeof metadata.threadId !== "string" || typeof metadata.toolCallId !== "string") throw new Error("Codex process start requires its admitted native command");
|
|
1173
|
+
const command = this.threads.get(metadata.threadId)?.get(metadata.toolCallId);
|
|
1174
|
+
if (!command) throw new Error("Codex process start has no admitted native command");
|
|
1175
|
+
const assertAdmission = () => {
|
|
1176
|
+
command.owner.assertCurrent();
|
|
1177
|
+
command.assertActive();
|
|
1178
|
+
if (this.closed || !command.accepting || this.threads.get(command.threadId)?.get(command.itemId) !== command) throw new Error("Codex native command admission has ended");
|
|
1179
|
+
};
|
|
1180
|
+
assertAdmission();
|
|
1181
|
+
const process = { terminate };
|
|
1182
|
+
command.processes.add(process);
|
|
1183
|
+
let settled = false;
|
|
1184
|
+
return {
|
|
1185
|
+
assertAdmission,
|
|
1186
|
+
assertCurrent: () => {
|
|
1187
|
+
command.owner.assertCurrent();
|
|
1188
|
+
if (this.closed || settled) throw new Error("Codex native process authority has ended");
|
|
1189
|
+
},
|
|
1190
|
+
settle: () => {
|
|
1191
|
+
if (settled) return;
|
|
1192
|
+
settled = true;
|
|
1193
|
+
command.processes.delete(process);
|
|
1194
|
+
this.forgetSettled(command);
|
|
1195
|
+
},
|
|
1196
|
+
fail: (error) => command.owner.reportSettlementFailure(error)
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
closeAdmission(command) {
|
|
1200
|
+
command.accepting = false;
|
|
1201
|
+
this.forgetSettled(command);
|
|
1202
|
+
}
|
|
1203
|
+
forgetSettled(command) {
|
|
1204
|
+
if (command.accepting || command.processes.size > 0) return;
|
|
1205
|
+
const commands = this.threads.get(command.threadId);
|
|
1206
|
+
if (commands?.get(command.itemId) === command) {
|
|
1207
|
+
commands.delete(command.itemId);
|
|
1208
|
+
if (commands.size === 0) this.threads.delete(command.threadId);
|
|
1209
|
+
}
|
|
1210
|
+
command.owner.commands.delete(command);
|
|
1211
|
+
command.releaseThread();
|
|
1212
|
+
command.releaseClient?.();
|
|
1213
|
+
command.owner.releaseIfSettled();
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
/** Original-source custody outlives foreground authority, never permitting new admission after release. */
|
|
1217
|
+
var CodexNativeProcessAuthority = class {
|
|
1218
|
+
constructor(host, onCleanupFailure) {
|
|
1219
|
+
this.onCleanupFailure = onCleanupFailure;
|
|
1220
|
+
this.commands = /* @__PURE__ */ new Set();
|
|
1221
|
+
this.holds = 1;
|
|
1222
|
+
this.cancelled = false;
|
|
1223
|
+
this.released = false;
|
|
1224
|
+
this.onAbort = () => {
|
|
1225
|
+
if (!this.cancelled) this.cancel().catch(this.onCleanupFailure);
|
|
1226
|
+
};
|
|
1227
|
+
this.source = host.retainSourceAuthority?.();
|
|
1228
|
+
this.source?.signal?.addEventListener("abort", this.onAbort, { once: true });
|
|
1229
|
+
if (this.source?.signal?.aborted) this.onAbort();
|
|
1230
|
+
}
|
|
1231
|
+
assertCurrent() {
|
|
1232
|
+
if (this.cancelled || this.released) throw new Error("Codex native process source has ended");
|
|
1233
|
+
try {
|
|
1234
|
+
this.source?.assertCurrent();
|
|
1235
|
+
if (this.cancelled || this.released) throw new Error("Codex native process source has ended");
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
this.onAbort();
|
|
1238
|
+
throw error;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
bindTurn(client, threadId, turnId) {
|
|
1242
|
+
this.parentTurn = {
|
|
1243
|
+
client,
|
|
1244
|
+
threadId,
|
|
1245
|
+
turnId
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
admit(client, receipt, assertAdmissionCurrent, childParentThreadId) {
|
|
1249
|
+
this.assertCurrent();
|
|
1250
|
+
if (this.holds === 0) throw new Error("Codex native process admission is closed");
|
|
1251
|
+
const parent = this.parentTurn;
|
|
1252
|
+
if (parent?.client !== client || (childParentThreadId ? childParentThreadId !== parent.threadId : receipt.threadId !== parent.threadId || receipt.turnId !== parent.turnId)) throw new Error("Codex native command does not belong to its admitted turn");
|
|
1253
|
+
assertAdmissionCurrent();
|
|
1254
|
+
getCodexNativeProcessClient(client).admit(this, receipt, parent, assertAdmissionCurrent);
|
|
1255
|
+
}
|
|
1256
|
+
retainAdmission() {
|
|
1257
|
+
this.assertCurrent();
|
|
1258
|
+
this.holds += 1;
|
|
1259
|
+
let held = true;
|
|
1260
|
+
return () => {
|
|
1261
|
+
if (held) {
|
|
1262
|
+
held = false;
|
|
1263
|
+
this.release();
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
release() {
|
|
1268
|
+
if (this.holds === 0) return;
|
|
1269
|
+
this.holds -= 1;
|
|
1270
|
+
if (this.holds === 0) for (const command of this.commands) command.client.closeAdmission(command);
|
|
1271
|
+
this.releaseIfSettled();
|
|
1272
|
+
}
|
|
1273
|
+
releaseIfSettled() {
|
|
1274
|
+
if (this.released || this.holds > 0 || this.commands.size > 0) return;
|
|
1275
|
+
this.released = true;
|
|
1276
|
+
this.source?.signal?.removeEventListener("abort", this.onAbort);
|
|
1277
|
+
this.source?.release();
|
|
1278
|
+
}
|
|
1279
|
+
cancelTurn(client, threadId, turnId) {
|
|
1280
|
+
const owner = getCodexNativeProcessClient(client);
|
|
1281
|
+
return this.terminate([...this.commands].filter((command) => command.client === owner && command.parentTurn.threadId === threadId && command.parentTurn.turnId === turnId));
|
|
1282
|
+
}
|
|
1283
|
+
cancelClient(client) {
|
|
1284
|
+
this.terminate([...this.commands].filter((command) => command.client === client)).catch(this.onCleanupFailure);
|
|
1285
|
+
}
|
|
1286
|
+
reportSettlementFailure(error) {
|
|
1287
|
+
this.onCleanupFailure(new AggregateError([error], "Codex native process cleanup failed; background work remains unsettled"));
|
|
1288
|
+
}
|
|
1289
|
+
cancel() {
|
|
1290
|
+
this.cancelled = true;
|
|
1291
|
+
return this.cancellation ??= this.terminate([...this.commands]);
|
|
1292
|
+
}
|
|
1293
|
+
async terminate(commands) {
|
|
1294
|
+
const processes = commands.flatMap((command) => {
|
|
1295
|
+
command.client.closeAdmission(command);
|
|
1296
|
+
return [...command.processes];
|
|
1297
|
+
});
|
|
1298
|
+
const failures = (await Promise.allSettled(processes.map((process) => process.terminate()))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
1299
|
+
if (failures.length > 0) throw new AggregateError(failures, "Codex native process cleanup failed; background work remains unsettled");
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
//#endregion
|
|
1089
1303
|
//#region extensions/codex/src/app-server/sandbox-exec-server-node-relay.ts
|
|
1090
1304
|
const CODEX_NODE_EXEC_SERVER_MAX_MESSAGE_BYTES$1 = 67108864;
|
|
1091
1305
|
const CODEX_NODE_EXEC_SERVER_MAX_FAILURE_DETAIL_CHARS = 240;
|
|
@@ -2064,7 +2278,7 @@ function metadataResponse(stat) {
|
|
|
2064
2278
|
/** Owns one sandbox subprocess tree through close, reaping, and backend finalization. */
|
|
2065
2279
|
const SANDBOX_CHILD_TERM_GRACE_MS = 1e3;
|
|
2066
2280
|
const SANDBOX_CHILD_REAP_TIMEOUT_MS = 4500;
|
|
2067
|
-
const
|
|
2281
|
+
const SANDBOX_CHILD_INTERRUPT_POLL_MS = 50;
|
|
2068
2282
|
async function spawnSandboxChild(params) {
|
|
2069
2283
|
const [command, ...args] = params.argv;
|
|
2070
2284
|
const finalize = async (status, exitCode) => await params.finalizeExec?.({
|
|
@@ -2078,54 +2292,88 @@ async function spawnSandboxChild(params) {
|
|
|
2078
2292
|
throw new Error("OpenClaw sandbox exec spec did not provide a command.");
|
|
2079
2293
|
}
|
|
2080
2294
|
let child;
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
detached: process.platform !== "win32",
|
|
2084
|
-
env: params.env,
|
|
2085
|
-
stdio: [
|
|
2086
|
-
"pipe",
|
|
2087
|
-
"pipe",
|
|
2088
|
-
"pipe"
|
|
2089
|
-
]
|
|
2090
|
-
});
|
|
2091
|
-
} catch (error) {
|
|
2092
|
-
await finalize("failed", null).catch(params.onFinalizeError);
|
|
2093
|
-
throw error;
|
|
2094
|
-
}
|
|
2295
|
+
let pty;
|
|
2296
|
+
let exitOutcome;
|
|
2095
2297
|
let outcome;
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2298
|
+
let escalation;
|
|
2299
|
+
const ready = createDeferred();
|
|
2300
|
+
const exited = createDeferred();
|
|
2301
|
+
const closed = createDeferred();
|
|
2302
|
+
const recordExit = (exitCode, signal) => {
|
|
2303
|
+
if (!exitOutcome) {
|
|
2304
|
+
clearTimeout(escalation);
|
|
2305
|
+
exited.resolve(exitOutcome = {
|
|
2306
|
+
exitCode,
|
|
2307
|
+
signal
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
const recordClose = (exitCode, signal) => {
|
|
2312
|
+
recordExit(exitCode, signal);
|
|
2313
|
+
closed.resolve(outcome = {
|
|
2314
|
+
exitCode,
|
|
2099
2315
|
signal
|
|
2100
|
-
})
|
|
2101
|
-
}
|
|
2102
|
-
let
|
|
2316
|
+
});
|
|
2317
|
+
};
|
|
2318
|
+
let startFailed = false;
|
|
2319
|
+
let startupPending = true;
|
|
2320
|
+
let terminationRequested = false;
|
|
2103
2321
|
let terminationCleanup;
|
|
2104
2322
|
let terminationError;
|
|
2105
|
-
|
|
2323
|
+
let settlementStarted = false;
|
|
2324
|
+
const interruptions = /* @__PURE__ */ new Set();
|
|
2325
|
+
const localSignals = [];
|
|
2326
|
+
const settled = closed.promise.then(async (result) => {
|
|
2327
|
+
settlementStarted = true;
|
|
2106
2328
|
await terminationCleanup;
|
|
2107
|
-
|
|
2108
|
-
await
|
|
2329
|
+
if (interruptions.size > 0) await Promise.allSettled(interruptions);
|
|
2330
|
+
await Promise.all(localSignals);
|
|
2331
|
+
child?.stdin?.destroy();
|
|
2332
|
+
await finalize(startFailed ? "failed" : params.finalizeStatus(result), startFailed ? null : result.exitCode);
|
|
2109
2333
|
return result;
|
|
2110
2334
|
});
|
|
2111
2335
|
settled.catch(params.onFinalizeError);
|
|
2336
|
+
const assertCurrent = () => {
|
|
2337
|
+
params.assertCurrent?.();
|
|
2338
|
+
if (terminationRequested) throw new Error("Sandbox child process start cancelled");
|
|
2339
|
+
};
|
|
2112
2340
|
let terminationPromise;
|
|
2113
2341
|
const owner = {
|
|
2114
|
-
|
|
2342
|
+
assertCurrent,
|
|
2343
|
+
exited: exited.promise,
|
|
2344
|
+
closed: closed.promise,
|
|
2115
2345
|
settled,
|
|
2116
2346
|
terminate: () => terminationPromise ??= (async () => {
|
|
2117
|
-
|
|
2347
|
+
terminationRequested = true;
|
|
2348
|
+
if (startupPending) await ready.promise;
|
|
2349
|
+
if (startFailed || settlementStarted) return await settled;
|
|
2350
|
+
child?.stdin?.destroy();
|
|
2118
2351
|
terminationCleanup = params.terminateRemote?.().catch((error) => {
|
|
2119
2352
|
terminationError = error instanceof Error ? error : new Error(String(error));
|
|
2120
2353
|
});
|
|
2121
2354
|
await terminationCleanup;
|
|
2122
2355
|
if (!outcome) {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2356
|
+
const signalLocal = (signal) => {
|
|
2357
|
+
if (exitOutcome) return;
|
|
2358
|
+
if (pty) pty.kill(signal);
|
|
2359
|
+
else if (child?.pid) {
|
|
2360
|
+
const pid = child.pid;
|
|
2361
|
+
localSignals.push(new Promise((resolve) => {
|
|
2362
|
+
signalProcessTree(pid, signal, {
|
|
2363
|
+
detached: process.platform !== "win32",
|
|
2364
|
+
onComplete: resolve
|
|
2365
|
+
});
|
|
2366
|
+
}));
|
|
2367
|
+
} else child?.kill(signal);
|
|
2368
|
+
};
|
|
2369
|
+
signalLocal("SIGTERM");
|
|
2370
|
+
if (!exitOutcome) {
|
|
2371
|
+
escalation = setTimeout(() => signalLocal("SIGKILL"), SANDBOX_CHILD_TERM_GRACE_MS);
|
|
2372
|
+
escalation.unref?.();
|
|
2373
|
+
}
|
|
2374
|
+
const reaped = await Promise.race([closed.promise.then(() => true), delay(SANDBOX_CHILD_REAP_TIMEOUT_MS).then(() => false)]).finally(() => clearTimeout(escalation));
|
|
2375
|
+
await Promise.all(localSignals);
|
|
2376
|
+
if (!reaped) throw new Error(`Sandbox child process tree ${pty?.pid ?? child?.pid ?? "unknown"} survived SIGKILL; tear down the sandbox environment and inspect the surviving process tree before retrying.`);
|
|
2129
2377
|
}
|
|
2130
2378
|
const result = await settled;
|
|
2131
2379
|
if (terminationError) throw terminationError;
|
|
@@ -2134,46 +2382,85 @@ async function spawnSandboxChild(params) {
|
|
|
2134
2382
|
};
|
|
2135
2383
|
params.owners.add(owner);
|
|
2136
2384
|
settled.then(() => params.owners.delete(owner), () => params.owners.delete(owner));
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
allowFailure: true,
|
|
2151
|
-
signal: AbortSignal.timeout(SANDBOX_CHILD_REAP_TIMEOUT_MS)
|
|
2152
|
-
});
|
|
2153
|
-
if (result.code !== 0) {
|
|
2154
|
-
const detail = result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim();
|
|
2155
|
-
throw new Error(detail || `Sandbox process tree cleanup failed with code ${result.code}; tear down the sandbox environment and inspect surviving processes before retrying.`);
|
|
2385
|
+
const interrupt = async () => {
|
|
2386
|
+
await ready.promise;
|
|
2387
|
+
const interruptRemote = params.interruptRemote;
|
|
2388
|
+
if (exitOutcome || terminationRequested || !interruptRemote) return;
|
|
2389
|
+
const interruption = (async () => {
|
|
2390
|
+
const deadline = performance.now() + SANDBOX_CHILD_REAP_TIMEOUT_MS;
|
|
2391
|
+
while (true) {
|
|
2392
|
+
if (exitOutcome || terminationRequested) return;
|
|
2393
|
+
const remainingMs = Math.ceil(deadline - performance.now());
|
|
2394
|
+
if (remainingMs <= 0) throw new Error("Sandbox process interrupt timed out waiting for remote process admission");
|
|
2395
|
+
assertCurrent();
|
|
2396
|
+
if (await interruptRemote(remainingMs)) return;
|
|
2397
|
+
await Promise.race([delay(Math.min(SANDBOX_CHILD_INTERRUPT_POLL_MS, Math.max(0, deadline - performance.now()))), exited.promise]);
|
|
2156
2398
|
}
|
|
2399
|
+
})();
|
|
2400
|
+
interruptions.add(interruption);
|
|
2401
|
+
try {
|
|
2402
|
+
await interruption;
|
|
2403
|
+
} finally {
|
|
2404
|
+
interruptions.delete(interruption);
|
|
2157
2405
|
}
|
|
2158
2406
|
};
|
|
2407
|
+
try {
|
|
2408
|
+
if (params.usePty) {
|
|
2409
|
+
const env = Object.fromEntries(Object.entries(params.env).filter((entry) => entry[1] !== void 0));
|
|
2410
|
+
pty = await spawnTerminalPty({
|
|
2411
|
+
file: command,
|
|
2412
|
+
args,
|
|
2413
|
+
cwd: params.cwd,
|
|
2414
|
+
env,
|
|
2415
|
+
cols: 80,
|
|
2416
|
+
rows: 24
|
|
2417
|
+
}, { assertCurrent });
|
|
2418
|
+
pty.onExit(({ exitCode, signal }) => {
|
|
2419
|
+
recordClose(signal ? 1 : exitCode, signal || null);
|
|
2420
|
+
});
|
|
2421
|
+
return {
|
|
2422
|
+
...owner,
|
|
2423
|
+
pty,
|
|
2424
|
+
interrupt
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
assertCurrent();
|
|
2428
|
+
child = spawn(command, args, {
|
|
2429
|
+
detached: process.platform !== "win32",
|
|
2430
|
+
env: params.env,
|
|
2431
|
+
cwd: params.cwd,
|
|
2432
|
+
stdio: [
|
|
2433
|
+
"pipe",
|
|
2434
|
+
"pipe",
|
|
2435
|
+
"pipe"
|
|
2436
|
+
]
|
|
2437
|
+
});
|
|
2438
|
+
child.once("exit", (code, signal) => recordExit(code ?? 1, signal));
|
|
2439
|
+
child.once("close", (code, signal) => recordClose(code ?? 1, signal));
|
|
2440
|
+
const markStartFailed = () => {
|
|
2441
|
+
startFailed = true;
|
|
2442
|
+
};
|
|
2443
|
+
child.once("error", markStartFailed);
|
|
2444
|
+
try {
|
|
2445
|
+
await once(child, "spawn");
|
|
2446
|
+
} finally {
|
|
2447
|
+
child.off("error", markStartFailed);
|
|
2448
|
+
}
|
|
2449
|
+
return {
|
|
2450
|
+
...owner,
|
|
2451
|
+
process: child,
|
|
2452
|
+
interrupt
|
|
2453
|
+
};
|
|
2454
|
+
} catch (error) {
|
|
2455
|
+
startFailed = true;
|
|
2456
|
+
if (!child) recordClose(1, null);
|
|
2457
|
+
await settled.catch(() => void 0);
|
|
2458
|
+
throw error;
|
|
2459
|
+
} finally {
|
|
2460
|
+
startupPending = false;
|
|
2461
|
+
ready.resolve();
|
|
2462
|
+
}
|
|
2159
2463
|
}
|
|
2160
|
-
const SANDBOX_REMOTE_TERMINATE_SCRIPT = String.raw`
|
|
2161
|
-
find_owned_pids() {
|
|
2162
|
-
for env_file in /proc/[0-9]*/environ; do
|
|
2163
|
-
if [ -r "$env_file" ] && tr '\0' '\n' < "$env_file" 2>/dev/null | grep -Fqx "$1"; then
|
|
2164
|
-
basename "$(dirname "$env_file")"
|
|
2165
|
-
fi
|
|
2166
|
-
done
|
|
2167
|
-
}
|
|
2168
|
-
owned="$(find_owned_pids "$1")"
|
|
2169
|
-
[ -z "$owned" ] || kill -TERM $owned 2>/dev/null || true
|
|
2170
|
-
sleep 1
|
|
2171
|
-
owned="$(find_owned_pids "$1")"
|
|
2172
|
-
[ -z "$owned" ] || kill -KILL $owned 2>/dev/null || true
|
|
2173
|
-
sleep 1
|
|
2174
|
-
owned="$(find_owned_pids "$1")"
|
|
2175
|
-
[ -z "$owned" ] || { echo "Sandbox process IDs survived SIGKILL: $owned" >&2; exit 1; }
|
|
2176
|
-
`.trim();
|
|
2177
2464
|
function delay(ms) {
|
|
2178
2465
|
return new Promise((resolve) => {
|
|
2179
2466
|
setTimeout(resolve, ms).unref?.();
|
|
@@ -2188,7 +2475,7 @@ function delay(ms) {
|
|
|
2188
2475
|
/** Maximum JSON-line size accepted from the streaming HTTP helper process. */
|
|
2189
2476
|
const SANDBOX_HTTP_STREAM_LINE_MAX_CHARS = 262144;
|
|
2190
2477
|
/** Handles one sandbox HTTP JSON-RPC request, optionally streaming response body deltas. */
|
|
2191
|
-
async function httpRequest(execServer, notifications, params) {
|
|
2478
|
+
async function httpRequest(execServer, notifications, params, operations) {
|
|
2192
2479
|
const record = requireObject(params, "http/request params");
|
|
2193
2480
|
const requestId = requireString(record.requestId, "requestId");
|
|
2194
2481
|
const url = requireString(record.url, "url");
|
|
@@ -2204,11 +2491,14 @@ async function httpRequest(execServer, notifications, params) {
|
|
|
2204
2491
|
redirectPolicy,
|
|
2205
2492
|
streamResponse: record.streamResponse === true
|
|
2206
2493
|
};
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2494
|
+
const response = createDeferred();
|
|
2495
|
+
const operation = runSandboxHttpRequest(execServer, notifications, requestId, request, response);
|
|
2496
|
+
operations.add(operation);
|
|
2497
|
+
operation.then(() => operations.delete(operation), (error) => {
|
|
2498
|
+
operations.delete(operation);
|
|
2499
|
+
response.reject(error);
|
|
2211
2500
|
});
|
|
2501
|
+
return await response.promise;
|
|
2212
2502
|
}
|
|
2213
2503
|
function assertSandboxHttpRequestTargetAllowed(url) {
|
|
2214
2504
|
let parsed;
|
|
@@ -2220,50 +2510,54 @@ function assertSandboxHttpRequestTargetAllowed(url) {
|
|
|
2220
2510
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new SsrFBlockedError(`Blocked non-HTTP(S) protocol in sandbox http/request: ${parsed.protocol}`);
|
|
2221
2511
|
if (isBlockedHostnameOrIp(parsed.hostname)) throw new SsrFBlockedError(`Blocked hostname or private/internal IP in sandbox http/request: ${parsed.hostname}`);
|
|
2222
2512
|
}
|
|
2223
|
-
async function runSandboxHttpRequest(execServer, params) {
|
|
2224
|
-
const result = await execServer.backend.runShellCommand({
|
|
2225
|
-
script: SANDBOX_HTTP_REQUEST_SCRIPT,
|
|
2226
|
-
stdin: JSON.stringify(params),
|
|
2227
|
-
allowFailure: true
|
|
2228
|
-
});
|
|
2229
|
-
if (result.code !== 0) {
|
|
2230
|
-
const stderr = result.stderr.toString("utf8").trim();
|
|
2231
|
-
throw new Error(stderr || `sandbox http/request failed with code ${result.code}`);
|
|
2232
|
-
}
|
|
2233
|
-
const parsed = JSON.parse(result.stdout.toString("utf8"));
|
|
2234
|
-
if (typeof parsed.status !== "number" || !Array.isArray(parsed.headers)) throw new Error("sandbox http/request returned an invalid response envelope");
|
|
2235
|
-
return {
|
|
2236
|
-
status: parsed.status,
|
|
2237
|
-
headers: readHttpHeaders(parsed.headers),
|
|
2238
|
-
bodyBase64: typeof parsed.bodyBase64 === "string" ? parsed.bodyBase64 : ""
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
async function runStreamingSandboxHttpRequest(execServer, notifications, requestId, params) {
|
|
2242
|
-
const backend = execServer.backend;
|
|
2243
|
-
const remoteExec = prepareSandboxChildExec(backend, {});
|
|
2244
|
-
const execSpec = await backend.buildExecSpec({
|
|
2245
|
-
command: SANDBOX_HTTP_REQUEST_SCRIPT,
|
|
2246
|
-
workdir: execServer.sandbox.containerWorkdir,
|
|
2247
|
-
env: remoteExec.env,
|
|
2248
|
-
usePty: false
|
|
2249
|
-
});
|
|
2513
|
+
async function runSandboxHttpRequest(execServer, notifications, requestId, params, response) {
|
|
2250
2514
|
const lifecycle = { failed: false };
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2515
|
+
let owner;
|
|
2516
|
+
try {
|
|
2517
|
+
notifications.signal.throwIfAborted();
|
|
2518
|
+
const backend = execServer.backend;
|
|
2519
|
+
const remoteExec = prepareSandboxProcessCleanup(backend, {});
|
|
2520
|
+
const execSpec = await backend.buildExecSpec({
|
|
2521
|
+
command: SANDBOX_HTTP_REQUEST_SCRIPT,
|
|
2522
|
+
workdir: execServer.sandbox.containerWorkdir,
|
|
2523
|
+
env: remoteExec.env,
|
|
2524
|
+
usePty: false
|
|
2525
|
+
});
|
|
2526
|
+
owner = await spawnSandboxChild({
|
|
2527
|
+
argv: execSpec.argv,
|
|
2528
|
+
env: execSpec.env,
|
|
2529
|
+
cwd: execSpec.cwd,
|
|
2530
|
+
assertCurrent: () => {
|
|
2531
|
+
notifications.signal.throwIfAborted();
|
|
2532
|
+
execSpec.assertCurrent?.();
|
|
2533
|
+
},
|
|
2534
|
+
finalizeExec: backend.finalizeExec,
|
|
2535
|
+
finalizeToken: execSpec.finalizeToken,
|
|
2536
|
+
finalizeStatus: (outcome) => lifecycle.failed || outcome.exitCode !== 0 ? "failed" : "completed",
|
|
2537
|
+
onFinalizeError: (error) => {
|
|
2538
|
+
embeddedAgentLog.warn("codex sandbox http/request finalize failed", { error });
|
|
2539
|
+
},
|
|
2540
|
+
owners: execServer.children,
|
|
2541
|
+
terminateRemote: remoteExec.terminate
|
|
2542
|
+
});
|
|
2543
|
+
} catch (error) {
|
|
2544
|
+
response.reject(error);
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2263
2547
|
const child = owner.process;
|
|
2548
|
+
const completion = createDeferred();
|
|
2549
|
+
owner.settled.then(() => completion.resolve(), completion.reject);
|
|
2550
|
+
let termination;
|
|
2551
|
+
const terminate = () => {
|
|
2552
|
+
if (!termination) {
|
|
2553
|
+
termination = owner.terminate().then(() => void 0);
|
|
2554
|
+
termination.then(completion.resolve, completion.reject);
|
|
2555
|
+
}
|
|
2556
|
+
return termination;
|
|
2557
|
+
};
|
|
2264
2558
|
const abortOnSessionClose = () => {
|
|
2265
2559
|
lifecycle.failed = true;
|
|
2266
|
-
|
|
2560
|
+
terminate().catch((error) => {
|
|
2267
2561
|
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2268
2562
|
});
|
|
2269
2563
|
};
|
|
@@ -2271,21 +2565,38 @@ async function runStreamingSandboxHttpRequest(execServer, notifications, request
|
|
|
2271
2565
|
child.once("close", () => {
|
|
2272
2566
|
notifications.signal.removeEventListener("abort", abortOnSessionClose);
|
|
2273
2567
|
});
|
|
2274
|
-
if (notifications.signal.aborted) abortOnSessionClose();
|
|
2275
2568
|
child.stdin.on("error", (error) => {
|
|
2276
2569
|
if (error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED") return;
|
|
2277
2570
|
embeddedAgentLog.warn("codex sandbox http/request stdin write failed", { error });
|
|
2278
2571
|
});
|
|
2279
|
-
|
|
2280
|
-
return await readStreamingSandboxHttpResponse({
|
|
2572
|
+
readSandboxHttpResponse({
|
|
2281
2573
|
child,
|
|
2282
2574
|
lifecycle,
|
|
2283
|
-
|
|
2575
|
+
terminate,
|
|
2284
2576
|
requestId,
|
|
2285
|
-
notifications
|
|
2286
|
-
|
|
2577
|
+
notifications,
|
|
2578
|
+
streamResponse: params.streamResponse
|
|
2579
|
+
}).then(response.resolve, response.reject);
|
|
2580
|
+
try {
|
|
2581
|
+
if (notifications.signal.aborted) abortOnSessionClose();
|
|
2582
|
+
else {
|
|
2583
|
+
owner.assertCurrent();
|
|
2584
|
+
child.stdin.end(JSON.stringify(params));
|
|
2585
|
+
}
|
|
2586
|
+
await completion.promise;
|
|
2587
|
+
await termination;
|
|
2588
|
+
} catch (error) {
|
|
2589
|
+
lifecycle.failed = true;
|
|
2590
|
+
response.reject(error);
|
|
2591
|
+
await terminate().catch((cleanupError) => {
|
|
2592
|
+
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error: cleanupError });
|
|
2593
|
+
});
|
|
2594
|
+
throw error;
|
|
2595
|
+
} finally {
|
|
2596
|
+
notifications.signal.removeEventListener("abort", abortOnSessionClose);
|
|
2597
|
+
}
|
|
2287
2598
|
}
|
|
2288
|
-
function
|
|
2599
|
+
function readSandboxHttpResponse(params) {
|
|
2289
2600
|
return new Promise((resolve, reject) => {
|
|
2290
2601
|
let headerResolved = false;
|
|
2291
2602
|
let failed = false;
|
|
@@ -2293,11 +2604,21 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2293
2604
|
let lastBodySeq = 0;
|
|
2294
2605
|
let stdoutBuffer = "";
|
|
2295
2606
|
let stderr = "";
|
|
2296
|
-
const
|
|
2607
|
+
const buffered = {
|
|
2608
|
+
stdout: {
|
|
2609
|
+
chunks: [],
|
|
2610
|
+
bytes: 0
|
|
2611
|
+
},
|
|
2612
|
+
stderr: {
|
|
2613
|
+
chunks: [],
|
|
2614
|
+
bytes: 0
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2617
|
+
const fail = (message) => {
|
|
2297
2618
|
if (failed) return;
|
|
2298
2619
|
failed = true;
|
|
2299
2620
|
params.lifecycle.failed = true;
|
|
2300
|
-
params.
|
|
2621
|
+
params.terminate().catch((error) => {
|
|
2301
2622
|
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2302
2623
|
});
|
|
2303
2624
|
if (headerResolved) {
|
|
@@ -2312,9 +2633,28 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2312
2633
|
}
|
|
2313
2634
|
reject(new Error(message));
|
|
2314
2635
|
};
|
|
2315
|
-
|
|
2636
|
+
const bufferOutput = (stream, chunk) => {
|
|
2637
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2638
|
+
const output = buffered[stream];
|
|
2639
|
+
output.bytes += buffer.byteLength;
|
|
2640
|
+
if (output.bytes > SANDBOX_COMMAND_MAX_BUFFER_BYTES) {
|
|
2641
|
+
fail(`sandbox http/request ${stream} exceeded ${SANDBOX_COMMAND_MAX_BUFFER_BYTES} bytes`);
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
output.chunks.push(buffer);
|
|
2645
|
+
};
|
|
2646
|
+
if (params.streamResponse) {
|
|
2647
|
+
params.child.stdout.setEncoding("utf8");
|
|
2648
|
+
params.child.stderr.setEncoding("utf8");
|
|
2649
|
+
}
|
|
2316
2650
|
params.child.stdout.on("data", (chunk) => {
|
|
2317
|
-
|
|
2651
|
+
if (failed) return;
|
|
2652
|
+
if (!params.streamResponse) {
|
|
2653
|
+
bufferOutput("stdout", chunk);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2657
|
+
stdoutBuffer += text;
|
|
2318
2658
|
let newline = stdoutBuffer.indexOf("\n");
|
|
2319
2659
|
while (newline >= 0) {
|
|
2320
2660
|
const line = stdoutBuffer.slice(0, newline).trim();
|
|
@@ -2341,15 +2681,20 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2341
2681
|
});
|
|
2342
2682
|
}
|
|
2343
2683
|
} catch (error) {
|
|
2344
|
-
fail(error instanceof Error ? error.message : String(error)
|
|
2684
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
2345
2685
|
}
|
|
2346
2686
|
newline = stdoutBuffer.indexOf("\n");
|
|
2347
2687
|
}
|
|
2348
|
-
if (stdoutBuffer.length > SANDBOX_HTTP_STREAM_LINE_MAX_CHARS) fail(`sandbox http/request produced an unterminated stdout line longer than ${SANDBOX_HTTP_STREAM_LINE_MAX_CHARS} characters
|
|
2688
|
+
if (stdoutBuffer.length > SANDBOX_HTTP_STREAM_LINE_MAX_CHARS) fail(`sandbox http/request produced an unterminated stdout line longer than ${SANDBOX_HTTP_STREAM_LINE_MAX_CHARS} characters`);
|
|
2349
2689
|
});
|
|
2350
|
-
params.child.stderr.setEncoding("utf8");
|
|
2351
2690
|
params.child.stderr.on("data", (chunk) => {
|
|
2352
|
-
|
|
2691
|
+
if (failed) return;
|
|
2692
|
+
if (!params.streamResponse) {
|
|
2693
|
+
bufferOutput("stderr", chunk);
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2697
|
+
stderr = sliceUtf16Safe(`${stderr}${text}`, -4096);
|
|
2353
2698
|
});
|
|
2354
2699
|
params.child.once("error", (error) => {
|
|
2355
2700
|
childFailure ??= error.message;
|
|
@@ -2359,17 +2704,32 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2359
2704
|
const exitCode = code ?? 1;
|
|
2360
2705
|
if (failed) return;
|
|
2361
2706
|
if (childFailure) {
|
|
2362
|
-
fail(childFailure
|
|
2707
|
+
fail(childFailure);
|
|
2363
2708
|
return;
|
|
2364
2709
|
}
|
|
2365
2710
|
if (exitCode === 0) {
|
|
2711
|
+
if (!params.streamResponse) {
|
|
2712
|
+
try {
|
|
2713
|
+
const parsed = JSON.parse(Buffer.concat(buffered.stdout.chunks).toString("utf8"));
|
|
2714
|
+
if (typeof parsed.status !== "number" || !Array.isArray(parsed.headers)) throw new Error("sandbox http/request returned an invalid response envelope");
|
|
2715
|
+
resolve({
|
|
2716
|
+
status: parsed.status,
|
|
2717
|
+
headers: readHttpHeaders(parsed.headers),
|
|
2718
|
+
bodyBase64: typeof parsed.bodyBase64 === "string" ? parsed.bodyBase64 : ""
|
|
2719
|
+
});
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
2722
|
+
}
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2366
2725
|
if (!headerResolved) {
|
|
2367
2726
|
params.lifecycle.failed = true;
|
|
2368
2727
|
reject(/* @__PURE__ */ new Error("sandbox http/request exited before returning headers"));
|
|
2369
2728
|
}
|
|
2370
2729
|
return;
|
|
2371
2730
|
}
|
|
2372
|
-
|
|
2731
|
+
if (!params.streamResponse) stderr = Buffer.concat(buffered.stderr.chunks).toString("utf8");
|
|
2732
|
+
fail(stderr.trim() || `sandbox http/request failed with code ${exitCode}`);
|
|
2373
2733
|
});
|
|
2374
2734
|
});
|
|
2375
2735
|
}
|
|
@@ -2617,7 +2977,7 @@ const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
2617
2977
|
const RETAINED_PROCESS_OUTPUT_BYTES = 1048576;
|
|
2618
2978
|
const CLOSED_PROCESS_EVICTION_MS = 6e4;
|
|
2619
2979
|
/** Starts a sandbox-backed process and registers it in the connection-local process table. */
|
|
2620
|
-
async function startProcess(execServer, processes, notify, params) {
|
|
2980
|
+
async function startProcess(execServer, processes, notify, params, processAuthority) {
|
|
2621
2981
|
const record = requireObject(params, "process/start params");
|
|
2622
2982
|
const processId = requireString(record.processId, "processId");
|
|
2623
2983
|
if (processes.has(processId)) throw new Error(`process already exists: ${processId}`);
|
|
@@ -2649,18 +3009,39 @@ async function startProcess(execServer, processes, notify, params) {
|
|
|
2649
3009
|
managed.evictionTimer.unref?.();
|
|
2650
3010
|
}
|
|
2651
3011
|
};
|
|
3012
|
+
const source = processAuthority?.claim(record.metadata, async () => {
|
|
3013
|
+
await terminateManagedProcess(managed);
|
|
3014
|
+
});
|
|
2652
3015
|
processes.set(processId, managed);
|
|
2653
3016
|
const startPromise = runProcess(execServer, managed, {
|
|
2654
3017
|
argv,
|
|
2655
3018
|
cwd,
|
|
2656
|
-
env
|
|
3019
|
+
env,
|
|
3020
|
+
source
|
|
2657
3021
|
});
|
|
2658
3022
|
managed.startPromise = startPromise;
|
|
3023
|
+
startPromise.catch(() => void 0).then(async () => {
|
|
3024
|
+
await managed.child?.settled;
|
|
3025
|
+
if (managed.terminationRequested) await managed.child?.terminate();
|
|
3026
|
+
source?.settle();
|
|
3027
|
+
}).catch((error) => {
|
|
3028
|
+
source?.fail(error);
|
|
3029
|
+
embeddedAgentLog.warn("codex sandbox process settlement failed", {
|
|
3030
|
+
processId,
|
|
3031
|
+
error: coerceErrorMessage(error)
|
|
3032
|
+
});
|
|
3033
|
+
});
|
|
2659
3034
|
try {
|
|
2660
3035
|
await startPromise;
|
|
2661
3036
|
} catch (error) {
|
|
2662
|
-
processes.delete(processId);
|
|
2663
3037
|
managed.failure = coerceErrorMessage(error);
|
|
3038
|
+
await managed.child?.terminate().catch((cleanupError) => {
|
|
3039
|
+
embeddedAgentLog.warn("codex sandbox failed-start cleanup failed", {
|
|
3040
|
+
processId,
|
|
3041
|
+
error: coerceErrorMessage(cleanupError)
|
|
3042
|
+
});
|
|
3043
|
+
});
|
|
3044
|
+
processes.delete(processId);
|
|
2664
3045
|
managed.exitCode = null;
|
|
2665
3046
|
managed.exited = true;
|
|
2666
3047
|
managed.closed = true;
|
|
@@ -2684,13 +3065,14 @@ function assertSupportedProcessSandbox(execServer, record) {
|
|
|
2684
3065
|
}
|
|
2685
3066
|
async function runProcess(execServer, managed, params) {
|
|
2686
3067
|
const backend = execServer.backend;
|
|
3068
|
+
params.source?.assertAdmission();
|
|
2687
3069
|
throwIfProcessStartCancelled(managed);
|
|
2688
|
-
const remoteExec =
|
|
3070
|
+
const remoteExec = prepareSandboxProcessCleanup(backend, params.env);
|
|
2689
3071
|
const execSpec = await backend.buildExecSpec({
|
|
2690
|
-
command: buildRemoteCommand(params.argv)
|
|
3072
|
+
command: `exec ${buildRemoteCommand(params.argv)}`,
|
|
2691
3073
|
workdir: params.cwd,
|
|
2692
3074
|
env: remoteExec.env,
|
|
2693
|
-
usePty:
|
|
3075
|
+
usePty: managed.tty
|
|
2694
3076
|
});
|
|
2695
3077
|
if (managed.terminationRequested) {
|
|
2696
3078
|
await backend.finalizeExec?.({
|
|
@@ -2701,9 +3083,18 @@ async function runProcess(execServer, managed, params) {
|
|
|
2701
3083
|
});
|
|
2702
3084
|
throw new Error("process start cancelled");
|
|
2703
3085
|
}
|
|
3086
|
+
let spawned = false;
|
|
2704
3087
|
const owner = await spawnSandboxChild({
|
|
2705
3088
|
argv: execSpec.argv,
|
|
2706
3089
|
env: execSpec.env,
|
|
3090
|
+
cwd: execSpec.cwd,
|
|
3091
|
+
usePty: managed.tty,
|
|
3092
|
+
assertCurrent: () => {
|
|
3093
|
+
if (spawned) params.source?.assertCurrent();
|
|
3094
|
+
else params.source?.assertAdmission();
|
|
3095
|
+
execSpec.assertCurrent?.();
|
|
3096
|
+
throwIfProcessStartCancelled(managed);
|
|
3097
|
+
},
|
|
2707
3098
|
finalizeExec: backend.finalizeExec,
|
|
2708
3099
|
finalizeToken: execSpec.finalizeToken,
|
|
2709
3100
|
finalizeStatus: () => managed.failure ? "failed" : "completed",
|
|
@@ -2716,19 +3107,29 @@ async function runProcess(execServer, managed, params) {
|
|
|
2716
3107
|
});
|
|
2717
3108
|
},
|
|
2718
3109
|
owners: execServer.children,
|
|
2719
|
-
terminateRemote: remoteExec.terminate
|
|
3110
|
+
terminateRemote: remoteExec.terminate,
|
|
3111
|
+
interruptRemote: remoteExec.interrupt
|
|
2720
3112
|
});
|
|
3113
|
+
spawned = true;
|
|
2721
3114
|
managed.child = owner;
|
|
3115
|
+
owner.exited.then(({ exitCode }) => emitProcessExited(managed, exitCode));
|
|
3116
|
+
owner.closed.then(({ exitCode }) => emitProcessClosed(managed, exitCode));
|
|
3117
|
+
if ("pty" in owner) {
|
|
3118
|
+
owner.pty.onData((chunk) => appendProcessChunk(managed, "pty", Buffer.from(chunk)));
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
2722
3121
|
const child = owner.process;
|
|
2723
|
-
child.stdout.on("data", (chunk) => appendProcessChunk(managed,
|
|
3122
|
+
child.stdout.on("data", (chunk) => appendProcessChunk(managed, "stdout", chunk));
|
|
2724
3123
|
child.stderr.on("data", (chunk) => appendProcessChunk(managed, "stderr", chunk));
|
|
2725
3124
|
child.once("error", (error) => {
|
|
2726
3125
|
managed.failure ??= error.message;
|
|
2727
3126
|
notifyProcessWaiters(managed);
|
|
2728
3127
|
});
|
|
2729
|
-
child.
|
|
2730
|
-
|
|
3128
|
+
child.stdin.on("error", (error) => {
|
|
3129
|
+
managed.failure ??= error.message;
|
|
3130
|
+
notifyProcessWaiters(managed);
|
|
2731
3131
|
});
|
|
3132
|
+
owner.assertCurrent();
|
|
2732
3133
|
if (!managed.tty && !managed.pipeStdin) child.stdin.end();
|
|
2733
3134
|
}
|
|
2734
3135
|
function throwIfProcessStartCancelled(managed) {
|
|
@@ -2757,7 +3158,7 @@ function appendProcessChunk(managed, stream, data) {
|
|
|
2757
3158
|
});
|
|
2758
3159
|
notifyProcessWaiters(managed);
|
|
2759
3160
|
}
|
|
2760
|
-
function
|
|
3161
|
+
function emitProcessExited(managed, exitCode) {
|
|
2761
3162
|
if (!managed.exited) {
|
|
2762
3163
|
const exitSeq = managed.nextSeq;
|
|
2763
3164
|
managed.nextSeq += 1;
|
|
@@ -2766,9 +3167,14 @@ function emitProcessClosed(managed, exitCode) {
|
|
|
2766
3167
|
if (exitCode !== null) managed.emitNotification("process/exited", {
|
|
2767
3168
|
processId: managed.processId,
|
|
2768
3169
|
seq: exitSeq,
|
|
2769
|
-
exitCode
|
|
3170
|
+
exitCode,
|
|
3171
|
+
sandboxDenied: false
|
|
2770
3172
|
});
|
|
2771
3173
|
}
|
|
3174
|
+
notifyProcessWaiters(managed);
|
|
3175
|
+
}
|
|
3176
|
+
function emitProcessClosed(managed, exitCode) {
|
|
3177
|
+
emitProcessExited(managed, exitCode);
|
|
2772
3178
|
if (!managed.closed) {
|
|
2773
3179
|
const closeSeq = managed.nextSeq;
|
|
2774
3180
|
managed.nextSeq += 1;
|
|
@@ -2800,7 +3206,7 @@ async function readProcess(processes, params) {
|
|
|
2800
3206
|
const managed = requireProcess(processes, requireString(record.processId, "processId"));
|
|
2801
3207
|
const afterSeq = typeof record.afterSeq === "number" ? record.afterSeq : 0;
|
|
2802
3208
|
const waitMs = typeof record.waitMs === "number" && record.waitMs > 0 ? record.waitMs : 0;
|
|
2803
|
-
if (!managed.
|
|
3209
|
+
if (!managed.closed && managed.nextSeq - 1 <= afterSeq && waitMs > 0) await waitForProcessUpdate(managed, waitMs);
|
|
2804
3210
|
const chunks = limitProcessChunks(managed.chunks.filter((chunk) => chunk.seq > afterSeq), typeof record.maxBytes === "number" && record.maxBytes > 0 ? record.maxBytes : void 0);
|
|
2805
3211
|
const lastChunk = chunks.at(-1);
|
|
2806
3212
|
return {
|
|
@@ -2819,15 +3225,37 @@ function writeProcess(processes, params) {
|
|
|
2819
3225
|
const managed = processes.get(processId);
|
|
2820
3226
|
if (!managed) return { status: "unknownProcess" };
|
|
2821
3227
|
const chunk = Buffer.from(requireString(record.chunk, "chunk"), "base64");
|
|
2822
|
-
if (!managed.tty && !managed.pipeStdin || managed.closed || !managed.child
|
|
2823
|
-
managed.child
|
|
3228
|
+
if (!managed.tty && !managed.pipeStdin || managed.closed || !managed.child) return { status: "stdinClosed" };
|
|
3229
|
+
if ("pty" in managed.child) {
|
|
3230
|
+
managed.child.assertCurrent();
|
|
3231
|
+
managed.child.pty.write(chunk);
|
|
3232
|
+
} else {
|
|
3233
|
+
if (!managed.child.process.stdin.writable) return { status: "stdinClosed" };
|
|
3234
|
+
managed.child.assertCurrent();
|
|
3235
|
+
managed.child.process.stdin.write(chunk);
|
|
3236
|
+
}
|
|
2824
3237
|
return { status: "accepted" };
|
|
2825
3238
|
}
|
|
3239
|
+
async function signalProcess(processes, params) {
|
|
3240
|
+
const record = requireObject(params, "process/signal params");
|
|
3241
|
+
const processId = requireString(record.processId, "processId");
|
|
3242
|
+
if (record.signal !== "interrupt") throw new Error("process/signal only supports interrupt");
|
|
3243
|
+
const managed = processes.get(processId);
|
|
3244
|
+
if (managed && !managed.exited) {
|
|
3245
|
+
await managed.startPromise;
|
|
3246
|
+
managed.child?.assertCurrent();
|
|
3247
|
+
await managed.child?.interrupt();
|
|
3248
|
+
}
|
|
3249
|
+
return {};
|
|
3250
|
+
}
|
|
2826
3251
|
/** Requests process termination and reports whether it was running at call time. */
|
|
2827
3252
|
async function terminateProcess(processes, params) {
|
|
2828
3253
|
const processId = requireString(requireObject(params, "process/terminate params").processId, "processId");
|
|
2829
3254
|
const managed = processes.get(processId);
|
|
2830
3255
|
if (!managed) return { running: false };
|
|
3256
|
+
return await terminateManagedProcess(managed);
|
|
3257
|
+
}
|
|
3258
|
+
async function terminateManagedProcess(managed) {
|
|
2831
3259
|
const running = !managed.exited;
|
|
2832
3260
|
managed.terminationRequested = true;
|
|
2833
3261
|
await managed.startPromise?.catch(() => void 0);
|
|
@@ -2851,9 +3279,6 @@ function notifyProcessWaiters(managed) {
|
|
|
2851
3279
|
managed.waiters = [];
|
|
2852
3280
|
for (const waiter of waiters) waiter();
|
|
2853
3281
|
}
|
|
2854
|
-
function hasChunksAtOrAfter(managed, afterSeq) {
|
|
2855
|
-
return managed.chunks.some((chunk) => chunk.seq > afterSeq);
|
|
2856
|
-
}
|
|
2857
3282
|
function requireProcess(processes, processId) {
|
|
2858
3283
|
const managed = processes.get(processId);
|
|
2859
3284
|
if (!managed) throw new Error(`unknown process: ${processId}`);
|
|
@@ -2902,11 +3327,13 @@ function readStringList(value) {
|
|
|
2902
3327
|
/** Owns the JSON-RPC protocol and resources of one sandbox execution connection. */
|
|
2903
3328
|
/** Connection-local execution state; closing it never enables session resumption. */
|
|
2904
3329
|
var CodexSandboxExecSession = class {
|
|
2905
|
-
constructor(execServer, transport) {
|
|
3330
|
+
constructor(execServer, transport, processAuthority) {
|
|
2906
3331
|
this.execServer = execServer;
|
|
2907
3332
|
this.transport = transport;
|
|
3333
|
+
this.processAuthority = processAuthority;
|
|
2908
3334
|
this.processes = /* @__PURE__ */ new Map();
|
|
2909
3335
|
this.fileReads = /* @__PURE__ */ new Map();
|
|
3336
|
+
this.httpRequests = /* @__PURE__ */ new Set();
|
|
2910
3337
|
this.closeController = new AbortController();
|
|
2911
3338
|
this.notifications = {
|
|
2912
3339
|
isOpen: transport.isOpen,
|
|
@@ -2941,7 +3368,10 @@ var CodexSandboxExecSession = class {
|
|
|
2941
3368
|
if (!this.cleanup) {
|
|
2942
3369
|
this.closeController.abort();
|
|
2943
3370
|
closeAllFileReads(this.fileReads);
|
|
2944
|
-
this.cleanup = Promise.
|
|
3371
|
+
this.cleanup = Promise.allSettled([...this.httpRequests, ...[...this.processes.keys()].map(async (processId) => terminateProcess(this.processes, { processId }))]).then((results) => {
|
|
3372
|
+
const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
3373
|
+
if (failures.length > 0) throw new AggregateError(failures, "Codex sandbox execution cleanup failed");
|
|
3374
|
+
});
|
|
2945
3375
|
}
|
|
2946
3376
|
return this.cleanup;
|
|
2947
3377
|
}
|
|
@@ -2957,9 +3387,10 @@ var CodexSandboxExecSession = class {
|
|
|
2957
3387
|
capabilities: { networkProxyLaunch: false }
|
|
2958
3388
|
};
|
|
2959
3389
|
case "environment/status": return { status: "ready" };
|
|
2960
|
-
case "process/start": return startProcess(this.execServer, this.processes, this.notifications.send, params);
|
|
3390
|
+
case "process/start": return startProcess(this.execServer, this.processes, this.notifications.send, params, this.processAuthority);
|
|
2961
3391
|
case "process/read": return await readProcess(this.processes, params);
|
|
2962
3392
|
case "process/write": return writeProcess(this.processes, params);
|
|
3393
|
+
case "process/signal": return await signalProcess(this.processes, params);
|
|
2963
3394
|
case "process/terminate": return await terminateProcess(this.processes, params);
|
|
2964
3395
|
case "fs/open": return await openFile(this.execServer, this.fileReads, params);
|
|
2965
3396
|
case "fs/readBlock": return readFileBlock(this.fileReads, params);
|
|
@@ -2979,7 +3410,7 @@ var CodexSandboxExecSession = class {
|
|
|
2979
3410
|
case "fs/copy":
|
|
2980
3411
|
await copyPath(this.execServer, params);
|
|
2981
3412
|
return {};
|
|
2982
|
-
case "http/request": return await httpRequest(this.execServer, this.notifications, params);
|
|
3413
|
+
case "http/request": return await httpRequest(this.execServer, this.notifications, params, this.httpRequests);
|
|
2983
3414
|
default: throw new JsonRpcProtocolError(JSON_RPC_METHOD_NOT_FOUND, `Unsupported OpenClaw sandbox exec-server method: ${method}`);
|
|
2984
3415
|
}
|
|
2985
3416
|
}
|
|
@@ -3006,9 +3437,17 @@ async function ensureCodexSandboxExecServerEnvironment(params) {
|
|
|
3006
3437
|
signal: params.signal,
|
|
3007
3438
|
onExecutionDisconnect: params.onExecutionDisconnect
|
|
3008
3439
|
});
|
|
3009
|
-
const
|
|
3440
|
+
const processAuthority = params.requireProcessAuthority && !("node" in execServer) ? getCodexNativeProcessClient(params.client) : void 0;
|
|
3441
|
+
if (processAuthority && !("node" in execServer)) {
|
|
3442
|
+
const authorities = execServer.processAuthorities ??= /* @__PURE__ */ new Map();
|
|
3443
|
+
if (!authorities.has(processAuthority.authPath)) {
|
|
3444
|
+
authorities.set(processAuthority.authPath, processAuthority);
|
|
3445
|
+
params.client.addCloseHandler(() => authorities.delete(processAuthority.authPath));
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
const environmentId = nodeLease ? `openclaw-node-${nodeLease.id}` : processAuthority ? `${execServer.environmentId}-${processAuthority.id}` : execServer.environmentId;
|
|
3010
3449
|
try {
|
|
3011
|
-
const execServerUrl = nodeLease ? `${execServer.url}?lease=${nodeLease.id}` : execServer.url;
|
|
3450
|
+
const execServerUrl = nodeLease ? `${execServer.url}?lease=${nodeLease.id}` : processAuthority ? new URL(processAuthority.authPath, execServer.url).href : execServer.url;
|
|
3012
3451
|
await params.client.request("environment/add", {
|
|
3013
3452
|
environmentId,
|
|
3014
3453
|
execServerUrl
|
|
@@ -3180,7 +3619,8 @@ async function startOpenClawExecServer(sandbox) {
|
|
|
3180
3619
|
handleNodeConnection(execServer, socket, request);
|
|
3181
3620
|
return;
|
|
3182
3621
|
}
|
|
3183
|
-
|
|
3622
|
+
const requestPath = new URL(request.url ?? "", "ws://127.0.0.1").pathname;
|
|
3623
|
+
handleConnection(execServer, socket, execServer.processAuthorities?.get(requestPath));
|
|
3184
3624
|
});
|
|
3185
3625
|
embeddedAgentLog.info("codex sandbox exec-server started", {
|
|
3186
3626
|
environmentId,
|
|
@@ -3202,7 +3642,8 @@ function buildEnvironmentId(sandbox) {
|
|
|
3202
3642
|
return `openclaw-sandbox-${createHash("sha256").update(sandbox.runtimeId).digest("hex").slice(0, 16)}`;
|
|
3203
3643
|
}
|
|
3204
3644
|
function isAuthorizedExecServerRequest(execServer, request) {
|
|
3205
|
-
|
|
3645
|
+
const url = new URL(request.url ?? "", "ws://127.0.0.1");
|
|
3646
|
+
return url.pathname === execServer.authPath || !("node" in execServer) && execServer.processAuthorities?.has(url.pathname) === true;
|
|
3206
3647
|
}
|
|
3207
3648
|
function readCodexPlacementNodeId(sandbox) {
|
|
3208
3649
|
if (!("placementExecutionMode" in sandbox) || sandbox.placementExecutionMode !== "remote-exec" || !("placementNodeId" in sandbox) || typeof sandbox.placementNodeId !== "string" || !sandbox.placementNodeId) return;
|
|
@@ -3255,11 +3696,11 @@ function handleClosedCodexNodeExecServerLease(execServer, lease, result) {
|
|
|
3255
3696
|
closeCodexNodeExecServerLease(execServer, lease);
|
|
3256
3697
|
}
|
|
3257
3698
|
}
|
|
3258
|
-
function handleConnection(execServer, socket) {
|
|
3699
|
+
function handleConnection(execServer, socket, processAuthority) {
|
|
3259
3700
|
const session = new CodexSandboxExecSession(execServer, {
|
|
3260
3701
|
isOpen: () => socket.readyState === socket.OPEN,
|
|
3261
3702
|
send: (message) => socket.send(JSON.stringify(message))
|
|
3262
|
-
});
|
|
3703
|
+
}, processAuthority);
|
|
3263
3704
|
socket.on("message", (data) => {
|
|
3264
3705
|
handleMessage(session, data).catch((error) => {
|
|
3265
3706
|
embeddedAgentLog.warn("codex sandbox exec-server message failed", { error });
|
|
@@ -3282,6 +3723,73 @@ async function handleMessage(session, data) {
|
|
|
3282
3723
|
await session.handleRequest(parseRequest(buffer.toString("utf8")));
|
|
3283
3724
|
}
|
|
3284
3725
|
//#endregion
|
|
3726
|
+
//#region extensions/codex/src/app-server/native-command-approval.ts
|
|
3727
|
+
const PERSISTENT_APPROVAL_TARGET_MAX_LENGTH = 256;
|
|
3728
|
+
function resolveCommandApproval(requestParams, outcome) {
|
|
3729
|
+
if (outcome === "cancelled") return {
|
|
3730
|
+
scope: "cancelled",
|
|
3731
|
+
decision: "cancel"
|
|
3732
|
+
};
|
|
3733
|
+
if (outcome === "denied" || outcome === "unavailable") return {
|
|
3734
|
+
scope: "denied",
|
|
3735
|
+
decision: "decline"
|
|
3736
|
+
};
|
|
3737
|
+
const capabilities = commandApprovalCapabilities(requestParams);
|
|
3738
|
+
if (outcome === "approved-session" && capabilities.repeated) return capabilities.repeated;
|
|
3739
|
+
return capabilities.once ? {
|
|
3740
|
+
scope: "once",
|
|
3741
|
+
decision: "accept"
|
|
3742
|
+
} : {
|
|
3743
|
+
scope: "denied",
|
|
3744
|
+
decision: "decline"
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
function commandApprovalAllowedDecisions(requestParams, requiresOneShot) {
|
|
3748
|
+
if (!Array.isArray(requestParams?.availableDecisions)) return;
|
|
3749
|
+
const capabilities = commandApprovalCapabilities(requestParams);
|
|
3750
|
+
const decisions = [];
|
|
3751
|
+
if (capabilities.once) decisions.push("allow-once");
|
|
3752
|
+
if (!requiresOneShot && capabilities.repeated) decisions.push("allow-always");
|
|
3753
|
+
decisions.push("deny");
|
|
3754
|
+
return decisions;
|
|
3755
|
+
}
|
|
3756
|
+
function commandApprovalCapabilities(requestParams) {
|
|
3757
|
+
const available = requestParams?.availableDecisions;
|
|
3758
|
+
if (!Array.isArray(available)) return {
|
|
3759
|
+
once: true,
|
|
3760
|
+
repeated: {
|
|
3761
|
+
scope: "session",
|
|
3762
|
+
decision: "acceptForSession"
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
return {
|
|
3766
|
+
once: available.includes("accept"),
|
|
3767
|
+
repeated: available.includes("acceptForSession") ? {
|
|
3768
|
+
scope: "session",
|
|
3769
|
+
decision: "acceptForSession"
|
|
3770
|
+
} : findAvailableCommandPersistentApproval(available)
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
function findAvailableCommandPersistentApproval(available) {
|
|
3774
|
+
for (const decision of available) {
|
|
3775
|
+
if (!isJsonObject(decision)) continue;
|
|
3776
|
+
const exec = decision.acceptWithExecpolicyAmendment;
|
|
3777
|
+
const network = decision.applyNetworkPolicyAmendment;
|
|
3778
|
+
const prefix = isJsonObject(exec) ? exec.execpolicy_amendment : void 0;
|
|
3779
|
+
const amendment = isJsonObject(network) ? network.network_policy_amendment : void 0;
|
|
3780
|
+
let target;
|
|
3781
|
+
if (Array.isArray(prefix) && prefix.length > 0 && prefix.every((part) => typeof part === "string")) target = `Command prefix: ${JSON.stringify(prefix)}`;
|
|
3782
|
+
else if (isJsonObject(amendment) && amendment.action === "allow" && typeof amendment.host === "string" && amendment.host.length > 0) target = `Network host: ${JSON.stringify(amendment.host)}`;
|
|
3783
|
+
else continue;
|
|
3784
|
+
if (target.length > PERSISTENT_APPROVAL_TARGET_MAX_LENGTH || sanitizeCodexApprovalVisibleText(target) !== target) continue;
|
|
3785
|
+
return {
|
|
3786
|
+
scope: "persistent",
|
|
3787
|
+
decision,
|
|
3788
|
+
description: `Allow Always requests a persistent allow rule for future sessions.\n${target}`
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
//#endregion
|
|
3285
3793
|
//#region extensions/codex/src/app-server/approval-bridge.ts
|
|
3286
3794
|
/**
|
|
3287
3795
|
* Bridges Codex app-server approval requests into OpenClaw policy hooks and
|
|
@@ -3307,12 +3815,14 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3307
3815
|
paramsForRun: params.paramsForRun
|
|
3308
3816
|
});
|
|
3309
3817
|
if (params.signal?.aborted) {
|
|
3818
|
+
if (params.signal.reason instanceof CodexServerRequestResolvedError) return;
|
|
3310
3819
|
recordNativeToolFailureDisposition(params, context, "cancelled");
|
|
3311
3820
|
return buildApprovalResponse(params.method, context.requestParams, "cancelled");
|
|
3312
3821
|
}
|
|
3313
3822
|
let revalidateMutableFileApproval;
|
|
3314
3823
|
let mutableFileApprovalRequiresOneShot = false;
|
|
3315
|
-
|
|
3824
|
+
let approvalId;
|
|
3825
|
+
const resolvePolicyApproval = async (outcome, message = approvalResolutionMessage(outcome), resolvedApprovalId) => {
|
|
3316
3826
|
let resolvedOutcome = outcome;
|
|
3317
3827
|
let resolvedMessage = message;
|
|
3318
3828
|
if (outcome !== "denied" && revalidateMutableFileApproval) {
|
|
@@ -3326,6 +3836,16 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3326
3836
|
resolvedOutcome = "approved-once";
|
|
3327
3837
|
resolvedMessage = "Codex app-server approval granted for this byte-bound command only.";
|
|
3328
3838
|
}
|
|
3839
|
+
if (params.method === "item/commandExecution/requestApproval" && resolvedOutcome !== "denied") {
|
|
3840
|
+
const resolution = resolveCommandApproval(context.requestParams, resolvedOutcome);
|
|
3841
|
+
if (resolution.scope === "denied") {
|
|
3842
|
+
resolvedOutcome = "denied";
|
|
3843
|
+
resolvedMessage = "Codex app-server request does not offer the approved scope.";
|
|
3844
|
+
} else if (resolvedOutcome === "approved-session" && resolution.scope === "once") {
|
|
3845
|
+
resolvedOutcome = "approved-once";
|
|
3846
|
+
resolvedMessage = approvalResolutionMessage(resolvedOutcome);
|
|
3847
|
+
} else if (resolution.scope === "persistent") resolvedMessage = "Codex app-server approval granted; persistent policy amendment requested for future sessions.";
|
|
3848
|
+
}
|
|
3329
3849
|
params.signal?.throwIfAborted();
|
|
3330
3850
|
if (resolvedOutcome !== "denied") params.paramsForRun.hostCapabilities.assertActive();
|
|
3331
3851
|
emitApprovalEvent(params.paramsForRun, {
|
|
@@ -3333,9 +3853,9 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3333
3853
|
kind: context.kind,
|
|
3334
3854
|
status: resolvedOutcome === "denied" ? "denied" : "approved",
|
|
3335
3855
|
title: context.title,
|
|
3336
|
-
...
|
|
3337
|
-
approvalId,
|
|
3338
|
-
approvalSlug:
|
|
3856
|
+
...resolvedApprovalId ? {
|
|
3857
|
+
approvalId: resolvedApprovalId,
|
|
3858
|
+
approvalSlug: resolvedApprovalId
|
|
3339
3859
|
} : {},
|
|
3340
3860
|
...context.eventDetails,
|
|
3341
3861
|
...approvalEventScope(params.method, resolvedOutcome),
|
|
@@ -3372,22 +3892,25 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3372
3892
|
return await resolvePolicyApproval("denied", policyOutcome.reason);
|
|
3373
3893
|
}
|
|
3374
3894
|
if (policyOutcome?.outcome === "approved-once" || policyOutcome?.outcome === "approved-session") return await resolvePolicyApproval(policyOutcome.outcome);
|
|
3375
|
-
if (CONCRETE_TOOL_AUTO_APPROVAL_METHODS.has(params.method) && !readNetworkApprovalContext(requestParams) && params.autoApprove === true) return await resolvePolicyApproval("approved-
|
|
3895
|
+
if (CONCRETE_TOOL_AUTO_APPROVAL_METHODS.has(params.method) && !readNetworkApprovalContext(requestParams) && params.autoApprove === true) return await resolvePolicyApproval("approved-once", "Codex app-server approval auto-approved by runtime policy.");
|
|
3896
|
+
const allowedDecisions = nativeApprovalAllowedDecisions({
|
|
3897
|
+
method: params.method,
|
|
3898
|
+
requestParams,
|
|
3899
|
+
requiresOneShot: mutableFileApprovalRequiresOneShot
|
|
3900
|
+
});
|
|
3901
|
+
const repeatedApproval = params.method === "item/commandExecution/requestApproval" && allowedDecisions?.includes("allow-always") ? commandApprovalCapabilities(requestParams).repeated : void 0;
|
|
3376
3902
|
const requestResult = await requestPluginApproval({
|
|
3377
3903
|
hostCapabilities: params.paramsForRun.hostCapabilities,
|
|
3378
3904
|
signal: params.signal,
|
|
3379
3905
|
title: context.title,
|
|
3380
|
-
description: context.description,
|
|
3906
|
+
description: repeatedApproval?.scope === "persistent" ? `${repeatedApproval.description}\n${context.description}` : context.description,
|
|
3381
3907
|
severity: context.severity,
|
|
3382
3908
|
toolName: context.toolName,
|
|
3383
3909
|
toolCallId: context.approvalId,
|
|
3384
|
-
allowedDecisions
|
|
3385
|
-
method: params.method,
|
|
3386
|
-
requestParams,
|
|
3387
|
-
requiresOneShot: mutableFileApprovalRequiresOneShot
|
|
3388
|
-
})
|
|
3910
|
+
allowedDecisions
|
|
3389
3911
|
});
|
|
3390
|
-
|
|
3912
|
+
approvalId = requestResult?.id;
|
|
3913
|
+
params.signal?.throwIfAborted();
|
|
3391
3914
|
if (!approvalId) {
|
|
3392
3915
|
recordNativeToolFailureDisposition(params, context, "failed");
|
|
3393
3916
|
emitApprovalEvent(params.paramsForRun, {
|
|
@@ -3416,16 +3939,16 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3416
3939
|
signal: params.signal,
|
|
3417
3940
|
hostCapabilities: params.paramsForRun.hostCapabilities
|
|
3418
3941
|
});
|
|
3419
|
-
|
|
3420
|
-
const
|
|
3942
|
+
params.signal?.throwIfAborted();
|
|
3943
|
+
const approvalTimedOut = approvalResult?.terminalReason === "timeout";
|
|
3944
|
+
const outcome = mapExecDecisionToOutcome(approvalResult?.decision);
|
|
3421
3945
|
if (approvalTimedOut) recordNativeToolFailureDisposition(params, context, "timed_out", context.approvalKind);
|
|
3422
|
-
else if (outcome === "cancelled") recordNativeToolFailureDisposition(params, context, params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : "cancelled");
|
|
3423
3946
|
else if (outcome === "unavailable") recordNativeToolFailureDisposition(params, context, "failed");
|
|
3424
3947
|
if (outcome === "approved-once" || outcome === "approved-session") return await resolvePolicyApproval(outcome, approvalResolutionMessage(outcome), approvalId);
|
|
3425
3948
|
emitApprovalEvent(params.paramsForRun, {
|
|
3426
3949
|
phase: "resolved",
|
|
3427
3950
|
kind: context.kind,
|
|
3428
|
-
status: outcome
|
|
3951
|
+
status: outcome,
|
|
3429
3952
|
title: context.title,
|
|
3430
3953
|
approvalId,
|
|
3431
3954
|
approvalSlug: approvalId,
|
|
@@ -3435,6 +3958,7 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3435
3958
|
});
|
|
3436
3959
|
return buildApprovalResponse(params.method, context.requestParams, outcome);
|
|
3437
3960
|
} catch (error) {
|
|
3961
|
+
if (params.signal?.reason instanceof CodexServerRequestResolvedError) return;
|
|
3438
3962
|
const cancelled = params.signal?.aborted === true;
|
|
3439
3963
|
recordNativeToolFailureDisposition(params, context, cancelled && params.signal ? resolveCodexToolAbortTerminalReason(params.signal) : "failed");
|
|
3440
3964
|
emitApprovalEvent(params.paramsForRun, {
|
|
@@ -3442,6 +3966,10 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3442
3966
|
kind: context.kind,
|
|
3443
3967
|
status: cancelled ? "failed" : "unavailable",
|
|
3444
3968
|
title: context.title,
|
|
3969
|
+
...approvalId ? {
|
|
3970
|
+
approvalId,
|
|
3971
|
+
approvalSlug: approvalId
|
|
3972
|
+
} : {},
|
|
3445
3973
|
...context.eventDetails,
|
|
3446
3974
|
...approvalEventScope(params.method, cancelled ? "cancelled" : "denied"),
|
|
3447
3975
|
message: cancelled ? "Codex app-server approval cancelled because the run stopped." : `Codex app-server approval route failed: ${formatCodexDisplayText(coerceErrorMessage(error))}`
|
|
@@ -3458,7 +3986,7 @@ function recordNativeToolFailureDisposition(params, context, disposition, approv
|
|
|
3458
3986
|
}
|
|
3459
3987
|
/** Converts an OpenClaw approval outcome into the app-server method response. */
|
|
3460
3988
|
function buildApprovalResponse(method, requestParams, outcome) {
|
|
3461
|
-
if (method === "item/commandExecution/requestApproval") return { decision:
|
|
3989
|
+
if (method === "item/commandExecution/requestApproval") return { decision: resolveCommandApproval(requestParams, outcome).decision };
|
|
3462
3990
|
if (method === "item/fileChange/requestApproval") return { decision: fileChangeApprovalDecision(outcome) };
|
|
3463
3991
|
if (method === "item/permissions/requestApproval") {
|
|
3464
3992
|
if (outcome === "approved-session" || outcome === "approved-once") return {
|
|
@@ -3716,28 +4244,13 @@ function stableJsonText(value) {
|
|
|
3716
4244
|
return entries.every((entry) => entry !== void 0) ? `{${entries.join(",")}}` : void 0;
|
|
3717
4245
|
}
|
|
3718
4246
|
}
|
|
3719
|
-
function commandApprovalDecision(requestParams, outcome) {
|
|
3720
|
-
if (outcome === "cancelled") return "cancel";
|
|
3721
|
-
if (outcome === "denied" || outcome === "unavailable") return "decline";
|
|
3722
|
-
const capabilities = commandApprovalCapabilities(requestParams);
|
|
3723
|
-
if (outcome === "approved-session" && capabilities.sessionDecision !== void 0) return capabilities.sessionDecision;
|
|
3724
|
-
return capabilities.once ? "accept" : "decline";
|
|
3725
|
-
}
|
|
3726
4247
|
function nativeApprovalAllowedDecisions(params) {
|
|
3727
4248
|
if (params.method === "item/fileChange/requestApproval") return [
|
|
3728
4249
|
"allow-once",
|
|
3729
4250
|
"allow-always",
|
|
3730
4251
|
"deny"
|
|
3731
4252
|
];
|
|
3732
|
-
|
|
3733
|
-
const available = params.requestParams?.availableDecisions;
|
|
3734
|
-
if (!Array.isArray(available)) return;
|
|
3735
|
-
const capabilities = commandApprovalCapabilities(params.requestParams);
|
|
3736
|
-
const decisions = [];
|
|
3737
|
-
if (capabilities.once) decisions.push("allow-once");
|
|
3738
|
-
if (!params.requiresOneShot && capabilities.sessionDecision !== void 0) decisions.push("allow-always");
|
|
3739
|
-
decisions.push("deny");
|
|
3740
|
-
return decisions;
|
|
4253
|
+
return params.method === "item/commandExecution/requestApproval" ? commandApprovalAllowedDecisions(params.requestParams, params.requiresOneShot) : void 0;
|
|
3741
4254
|
}
|
|
3742
4255
|
function fileChangeApprovalDecision(outcome) {
|
|
3743
4256
|
if (outcome === "cancelled") return "cancel";
|
|
@@ -3919,22 +4432,6 @@ function isPrivateNetworkHostPattern(value) {
|
|
|
3919
4432
|
if (wildcardStripped.startsWith("10.") || wildcardStripped.startsWith("127.") || wildcardStripped.startsWith("192.168.") || wildcardStripped.startsWith("169.254.")) return true;
|
|
3920
4433
|
return /^172\.(1[6-9]|2\d|3[0-1])\./.test(wildcardStripped);
|
|
3921
4434
|
}
|
|
3922
|
-
function commandApprovalCapabilities(requestParams) {
|
|
3923
|
-
const available = requestParams?.availableDecisions;
|
|
3924
|
-
if (!Array.isArray(available)) return {
|
|
3925
|
-
once: true,
|
|
3926
|
-
sessionDecision: "acceptForSession"
|
|
3927
|
-
};
|
|
3928
|
-
return {
|
|
3929
|
-
once: available.includes("accept"),
|
|
3930
|
-
...available.includes("acceptForSession") ? { sessionDecision: "acceptForSession" } : { sessionDecision: findAvailableCommandAmendmentDecision(requestParams) }
|
|
3931
|
-
};
|
|
3932
|
-
}
|
|
3933
|
-
function findAvailableCommandAmendmentDecision(requestParams) {
|
|
3934
|
-
const available = requestParams?.availableDecisions;
|
|
3935
|
-
if (!Array.isArray(available)) return;
|
|
3936
|
-
return available.find((entry) => isJsonObject(entry) && (isJsonObject(entry.acceptWithExecpolicyAmendment) || isJsonObject(entry.applyNetworkPolicyAmendment)));
|
|
3937
|
-
}
|
|
3938
4435
|
function approvalResolutionMessage(outcome) {
|
|
3939
4436
|
return {
|
|
3940
4437
|
"approved-session": "Codex app-server approval granted for the session.",
|
|
@@ -5019,142 +5516,6 @@ function applyCurrentMessageProvider(toolName, args, currentProvider) {
|
|
|
5019
5516
|
provider
|
|
5020
5517
|
};
|
|
5021
5518
|
}
|
|
5022
|
-
function normalizeRouteToken(value) {
|
|
5023
|
-
if (typeof value === "number") return Number.isFinite(value) ? String(value) : void 0;
|
|
5024
|
-
const normalized = value?.trim().toLowerCase();
|
|
5025
|
-
return normalized ? normalized : void 0;
|
|
5026
|
-
}
|
|
5027
|
-
function sourceRouteTokens(hookContext) {
|
|
5028
|
-
const tokens = /* @__PURE__ */ new Set();
|
|
5029
|
-
const currentTarget = normalizeRouteToken(hookContext?.currentMessagingTarget);
|
|
5030
|
-
const currentChannel = normalizeRouteToken(hookContext?.currentChannelId);
|
|
5031
|
-
const currentProvider = normalizeRouteToken(hookContext?.currentChannelProvider);
|
|
5032
|
-
if (currentTarget) tokens.add(currentTarget);
|
|
5033
|
-
if (currentChannel) tokens.add(currentChannel);
|
|
5034
|
-
const channelPrefixIndex = currentChannel?.indexOf(":") ?? -1;
|
|
5035
|
-
if (channelPrefixIndex >= 0 && currentChannel) {
|
|
5036
|
-
const unprefixedChannel = currentChannel.slice(channelPrefixIndex + 1);
|
|
5037
|
-
if (unprefixedChannel) {
|
|
5038
|
-
tokens.add(unprefixedChannel);
|
|
5039
|
-
for (const segment of unprefixedChannel.split(/[;,]/u)) {
|
|
5040
|
-
const token = normalizeRouteToken(segment);
|
|
5041
|
-
if (token) tokens.add(token);
|
|
5042
|
-
}
|
|
5043
|
-
}
|
|
5044
|
-
}
|
|
5045
|
-
if (currentProvider && currentChannel?.startsWith(`${currentProvider}:`)) {
|
|
5046
|
-
const unprefixedChannel = currentChannel.slice(currentProvider.length + 1);
|
|
5047
|
-
if (unprefixedChannel) tokens.add(unprefixedChannel);
|
|
5048
|
-
}
|
|
5049
|
-
return tokens;
|
|
5050
|
-
}
|
|
5051
|
-
function routeTokenMatchesSource(token, hookContext) {
|
|
5052
|
-
const normalized = normalizeRouteToken(token);
|
|
5053
|
-
return normalized !== void 0 && sourceRouteTokens(hookContext).has(normalized);
|
|
5054
|
-
}
|
|
5055
|
-
function routeProviderMatchesSource(provider, hookContext) {
|
|
5056
|
-
const normalized = normalizeRouteToken(provider);
|
|
5057
|
-
if (!normalized) return false;
|
|
5058
|
-
const currentProvider = normalizeRouteToken(hookContext?.currentChannelProvider);
|
|
5059
|
-
const currentChannel = normalizeRouteToken(hookContext?.currentChannelId);
|
|
5060
|
-
return currentProvider === normalized || currentChannel?.startsWith(`${normalized}:`) === true;
|
|
5061
|
-
}
|
|
5062
|
-
function routeTokenMatchesCurrentMessage(token, hookContext) {
|
|
5063
|
-
const normalized = normalizeRouteToken(token);
|
|
5064
|
-
return normalized !== void 0 && normalized === normalizeRouteToken(hookContext?.currentMessageId);
|
|
5065
|
-
}
|
|
5066
|
-
function readRouteToken(record, key) {
|
|
5067
|
-
const value = record[key];
|
|
5068
|
-
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
5069
|
-
}
|
|
5070
|
-
function explicitRouteTokensMismatchCurrent(args, keys, currentToken) {
|
|
5071
|
-
const normalizedCurrent = normalizeRouteToken(currentToken);
|
|
5072
|
-
if (!normalizedCurrent) return false;
|
|
5073
|
-
return keys.some((key) => {
|
|
5074
|
-
const normalized = normalizeRouteToken(readRouteToken(args, key));
|
|
5075
|
-
return normalized !== void 0 && normalized !== normalizedCurrent;
|
|
5076
|
-
});
|
|
5077
|
-
}
|
|
5078
|
-
function explicitThreadRouteTargetsNonSource(args, hookContext, messagingTarget) {
|
|
5079
|
-
const normalizedCurrentThread = normalizeRouteToken(hookContext?.currentThreadId);
|
|
5080
|
-
const explicitThreadTokens = [...EXPLICIT_MESSAGE_THREAD_KEYS.map((key) => normalizeRouteToken(readRouteToken(args, key))), normalizeRouteToken(messagingTarget?.threadId)].filter((value) => value !== void 0);
|
|
5081
|
-
if (explicitThreadTokens.length === 0) return false;
|
|
5082
|
-
return normalizedCurrentThread === void 0 || explicitThreadTokens.some((value) => value !== normalizedCurrentThread);
|
|
5083
|
-
}
|
|
5084
|
-
function replyReceiptMatchesCurrentMessage(value, hookContext, depth = 0) {
|
|
5085
|
-
if (depth > 4 || value === null) return false;
|
|
5086
|
-
if (typeof value === "string") {
|
|
5087
|
-
const trimmed = value.trim();
|
|
5088
|
-
if (!trimmed || !["{", "["].includes(trimmed[0] ?? "")) return false;
|
|
5089
|
-
try {
|
|
5090
|
-
return replyReceiptMatchesCurrentMessage(JSON.parse(trimmed), hookContext, depth + 1);
|
|
5091
|
-
} catch {
|
|
5092
|
-
return false;
|
|
5093
|
-
}
|
|
5094
|
-
}
|
|
5095
|
-
if (typeof value !== "object") return false;
|
|
5096
|
-
if (Array.isArray(value)) return value.some((item) => replyReceiptMatchesCurrentMessage(item, hookContext, depth + 1));
|
|
5097
|
-
const record = value;
|
|
5098
|
-
for (const key of [
|
|
5099
|
-
"repliedTo",
|
|
5100
|
-
"replyTo",
|
|
5101
|
-
"replyToId",
|
|
5102
|
-
"replyToIdFull"
|
|
5103
|
-
]) if (routeTokenMatchesCurrentMessage(typeof record[key] === "string" ? record[key] : void 0, hookContext)) return true;
|
|
5104
|
-
for (const key of [
|
|
5105
|
-
"content",
|
|
5106
|
-
"details",
|
|
5107
|
-
"payload",
|
|
5108
|
-
"receipt",
|
|
5109
|
-
"result",
|
|
5110
|
-
"results",
|
|
5111
|
-
"sendResult",
|
|
5112
|
-
"text"
|
|
5113
|
-
]) if (replyReceiptMatchesCurrentMessage(record[key], hookContext, depth + 1)) return true;
|
|
5114
|
-
return false;
|
|
5115
|
-
}
|
|
5116
|
-
function hasExplicitNonSourceMessageRoute(args, hookContext, messagingTarget) {
|
|
5117
|
-
const currentProvider = normalizeRouteToken(hookContext?.currentChannelProvider);
|
|
5118
|
-
for (const key of EXPLICIT_MESSAGE_PROVIDER_KEYS) {
|
|
5119
|
-
const provider = normalizeRouteToken(typeof args[key] === "string" ? args[key] : void 0);
|
|
5120
|
-
if (provider && currentProvider !== provider && !routeProviderMatchesSource(provider, hookContext)) return true;
|
|
5121
|
-
}
|
|
5122
|
-
const targetValues = [...EXPLICIT_MESSAGE_TARGET_KEYS.map((key) => typeof args[key] === "string" ? args[key] : void 0), ...Array.isArray(args.targets) ? args.targets.map((value) => typeof value === "string" ? value : void 0) : []].filter((value) => normalizeRouteToken(value) !== void 0);
|
|
5123
|
-
if (explicitThreadRouteTargetsNonSource(args, hookContext, messagingTarget)) return true;
|
|
5124
|
-
if (explicitRouteTokensMismatchCurrent(args, EXPLICIT_MESSAGE_REPLY_KEYS, hookContext?.currentMessageId)) return true;
|
|
5125
|
-
if (messagingTarget?.to !== void 0 && !routeTokenMatchesSource(messagingTarget.to, hookContext)) return true;
|
|
5126
|
-
if (messagingTarget?.to !== void 0) return false;
|
|
5127
|
-
if (targetValues.length === 0) return false;
|
|
5128
|
-
if (targetValues.some((value) => !routeTokenMatchesSource(value, hookContext))) return true;
|
|
5129
|
-
return false;
|
|
5130
|
-
}
|
|
5131
|
-
function normalizeAcceptedSessionSpawn(result) {
|
|
5132
|
-
const details = asOptionalRecord(asOptionalRecord(result)?.details);
|
|
5133
|
-
if (!details || details.status !== "accepted") return null;
|
|
5134
|
-
const runId = normalizeOptionalString(details.runId);
|
|
5135
|
-
const childSessionKey = normalizeOptionalString(details.childSessionKey);
|
|
5136
|
-
return runId && childSessionKey ? {
|
|
5137
|
-
runId,
|
|
5138
|
-
childSessionKey
|
|
5139
|
-
} : null;
|
|
5140
|
-
}
|
|
5141
|
-
const EXPLICIT_MESSAGE_PROVIDER_KEYS = ["channel", "provider"];
|
|
5142
|
-
const EXPLICIT_MESSAGE_TARGET_KEYS = [
|
|
5143
|
-
"target",
|
|
5144
|
-
"to",
|
|
5145
|
-
"channelId"
|
|
5146
|
-
];
|
|
5147
|
-
const EXPLICIT_MESSAGE_THREAD_KEYS = [
|
|
5148
|
-
"threadId",
|
|
5149
|
-
"thread_id",
|
|
5150
|
-
"messageThreadId",
|
|
5151
|
-
"topicId"
|
|
5152
|
-
];
|
|
5153
|
-
const EXPLICIT_MESSAGE_REPLY_KEYS = [
|
|
5154
|
-
"replyTo",
|
|
5155
|
-
"replyToId",
|
|
5156
|
-
"replyToIdFull"
|
|
5157
|
-
];
|
|
5158
5519
|
function computerFrameImageIdentity(content) {
|
|
5159
5520
|
if (!Array.isArray(content)) return;
|
|
5160
5521
|
const images = content.filter((block) => block.type === "image");
|
|
@@ -5206,6 +5567,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5206
5567
|
messagingToolSentMediaUrls: [],
|
|
5207
5568
|
messagingToolSentTargets: [],
|
|
5208
5569
|
messagingToolSourceReplyPayloads: [],
|
|
5570
|
+
confirmedMediaDeliveries: [],
|
|
5209
5571
|
toolMediaUrls: [],
|
|
5210
5572
|
toolAutoDeliveryMediaUrls: [],
|
|
5211
5573
|
coreTtsToolResults: [],
|
|
@@ -5309,16 +5671,16 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5309
5671
|
try {
|
|
5310
5672
|
const prepare = tool.prepareArguments;
|
|
5311
5673
|
const toolArgs = prepare ? Reflect.apply(prepare, tool, [rawArguments]) : args;
|
|
5312
|
-
const
|
|
5674
|
+
const preparedMessageMedia = toolName === "message" && isRecord(toolArgs) ? await prepareCodexRemoteWorkspaceMessageMedia({
|
|
5313
5675
|
args: toolArgs,
|
|
5314
5676
|
localWorkspaceRoot: params.hookContext?.workspaceDir,
|
|
5315
5677
|
remoteWorkspaceRoot: params.hookContext?.remoteWorkspaceRoot,
|
|
5316
5678
|
readRemoteFile: readRemoteWorkspaceFile,
|
|
5317
5679
|
timeoutMs: params.hookContext?.remoteWorkspaceRequestTimeoutMs,
|
|
5318
5680
|
signal
|
|
5319
|
-
}) :
|
|
5320
|
-
const
|
|
5321
|
-
executedArgs = structuredClone(
|
|
5681
|
+
}) : void 0;
|
|
5682
|
+
const preparedArgs = preparedMessageMedia?.args ?? toolArgs;
|
|
5683
|
+
executedArgs = structuredClone(isRecord(preparedArgs) ? preparedArgs : args);
|
|
5322
5684
|
const messagingContext = {
|
|
5323
5685
|
config: params.hookContext?.config,
|
|
5324
5686
|
currentChannelId: params.hookContext?.currentChannelId,
|
|
@@ -5343,10 +5705,41 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5343
5705
|
}));
|
|
5344
5706
|
const rawResult = await Reflect.apply(tool.execute, tool, executionArgs);
|
|
5345
5707
|
captureExecutionBoundary();
|
|
5346
|
-
if (toolName === "message" && asOptionalRecord(asOptionalRecord(rawResult.details)?.messageDelivery)?.sourceReplyDelivered === true) telemetry.sourceReplyDelivered = true;
|
|
5347
|
-
const telemetryRawResult = sanitizeToolResult(rawResult);
|
|
5348
5708
|
const rawIsError = isToolResultError(rawResult);
|
|
5709
|
+
const messageDelivery = readEmbeddedMessageDeliveryFact(asOptionalRecord(asOptionalRecord(rawResult)?.details)?.messageDelivery);
|
|
5710
|
+
const messagingDelivered = isMessagingTool(toolName) && (messageDelivery ? messageDelivery.status === "settled" && (!rawIsError || messageDelivery.partialDelivery) : isDeliveredMessagingToolResult({
|
|
5711
|
+
toolName,
|
|
5712
|
+
args: executedArgs,
|
|
5713
|
+
result: rawResult,
|
|
5714
|
+
isError: rawIsError
|
|
5715
|
+
}));
|
|
5716
|
+
const mediaDelivery = messagingDelivered ? messageDelivery ?? projectPluginMessageDeliveryFact(rawResult) : void 0;
|
|
5717
|
+
const mediaDeliveryConfirmed = messagingDelivered && !rawIsError && executedArgs.dryRun !== true && mediaDelivery?.status === "settled" && !mediaDelivery.partialDelivery;
|
|
5718
|
+
const messagingTelemetryArgs = applyCurrentMessageProvider(toolName, executedArgs, params.hookContext?.currentChannelProvider);
|
|
5719
|
+
const messagingTarget = isMessagingTool(toolName) ? extractMessagingToolSend(toolName, messagingTelemetryArgs, messagingContext) : void 0;
|
|
5720
|
+
const confirmedMessagingTarget = messagingDelivered && messagingTarget ? extractMessagingToolSendResult(messagingTarget, rawResult) : messagingTarget;
|
|
5721
|
+
const deliveredSourceReply = messagingDelivered && isDeliveredMessageToolOnlySourceReplyResult({
|
|
5722
|
+
sourceReplyDeliveryMode: params.hookContext?.sourceReplyDeliveryMode,
|
|
5723
|
+
toolName,
|
|
5724
|
+
args: executedArgs,
|
|
5725
|
+
result: rawResult,
|
|
5726
|
+
isError: rawIsError,
|
|
5727
|
+
deliveryConfirmed: true,
|
|
5728
|
+
allowExplicitSourceRoute: isDeliveredMessagingToolSendToCurrentSource({
|
|
5729
|
+
send: confirmedMessagingTarget,
|
|
5730
|
+
config: params.hookContext?.config,
|
|
5731
|
+
currentProvider: params.hookContext?.currentChannelProvider,
|
|
5732
|
+
currentAccountId: params.hookContext?.turnSourceAccountId,
|
|
5733
|
+
currentChannelId: params.hookContext?.currentChannelId,
|
|
5734
|
+
currentMessagingTarget: params.hookContext?.currentMessagingTarget,
|
|
5735
|
+
currentThreadId: params.hookContext?.currentThreadId,
|
|
5736
|
+
sessionKey: params.hookContext?.sessionKey,
|
|
5737
|
+
deliveredPayload: rawResult
|
|
5738
|
+
})
|
|
5739
|
+
});
|
|
5740
|
+
if (toolName === "message" && messageDelivery?.sourceReplyDelivered) telemetry.sourceReplyDelivered = true;
|
|
5349
5741
|
const rawResultFailureKind = resolveToolResultFailureKind(rawResult);
|
|
5742
|
+
const telemetryRawResult = sanitizeToolResult(rawResult);
|
|
5350
5743
|
const middlewareResult = await middlewareRunner.applyToolResultMiddleware({
|
|
5351
5744
|
threadId: call.threadId,
|
|
5352
5745
|
turnId: call.turnId,
|
|
@@ -5365,7 +5758,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5365
5758
|
result: middlewareResult
|
|
5366
5759
|
});
|
|
5367
5760
|
const resultIsError = rawIsError || isToolResultError(result);
|
|
5368
|
-
const acceptedSessionSpawn = toolName === "sessions_spawn" && !rawIsError ?
|
|
5761
|
+
const acceptedSessionSpawn = toolName === "sessions_spawn" && !rawIsError ? normalizeAcceptedSessionSpawnResult(telemetryRawResult) : null;
|
|
5369
5762
|
if (acceptedSessionSpawn) telemetry.acceptedSessionSpawns.push(acceptedSessionSpawn);
|
|
5370
5763
|
const finalResultFailureKind = resolveToolResultFailureKind(result);
|
|
5371
5764
|
const resultFailureKind = rawResultFailureKind ?? finalResultFailureKind;
|
|
@@ -5398,39 +5791,20 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5398
5791
|
toolName,
|
|
5399
5792
|
toolCallOrdinal: options?.toolCallOrdinal
|
|
5400
5793
|
});
|
|
5401
|
-
const
|
|
5402
|
-
const messagingTarget = isMessagingTool(toolName) ? extractMessagingToolSend(toolName, messagingTelemetryArgs, messagingContext) : void 0;
|
|
5403
|
-
const confirmedMessagingTarget = !rawIsError && messagingTarget ? extractMessagingToolSendResult(messagingTarget, telemetryRawResult) : messagingTarget;
|
|
5404
|
-
const terminalType = resultFailureKind === "blocked" ? "blocked" : resultIsError ? "error" : "completed";
|
|
5794
|
+
const terminalType = resultFailureKind === "blocked" ? "blocked" : resultIsError || resultFailureKind ? "error" : "completed";
|
|
5405
5795
|
const contentItems = convertToolContents(result.content, toolResultMaxChars);
|
|
5406
5796
|
const deliveredFrameImages = contentItems.filter((item) => item.type === "inputImage");
|
|
5407
5797
|
const finalFrameImageIdentity = computerFrameImageIdentity(result.content);
|
|
5408
5798
|
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId && (deliveredFrameImages.length !== 1 || finalFrameImageIdentity === void 0 || finalFrameImageIdentity !== params.computerContextEpoch.frameImageIdentity)) invalidateComputerFrame(params.computerContextEpoch);
|
|
5409
|
-
const response =
|
|
5799
|
+
const response = {
|
|
5410
5800
|
contentItems,
|
|
5411
|
-
success: !resultIsError
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
const deliveredSourceReply = isDeliveredMessageToolOnlySourceReplyResult({
|
|
5417
|
-
sourceReplyDeliveryMode: params.hookContext?.sourceReplyDeliveryMode,
|
|
5418
|
-
toolName,
|
|
5419
|
-
args: executedArgs,
|
|
5420
|
-
result,
|
|
5421
|
-
hookResult: rawResult,
|
|
5422
|
-
isError: resultIsError,
|
|
5423
|
-
allowExplicitSourceRoute: !blocksSourceReplyTermination
|
|
5424
|
-
});
|
|
5425
|
-
const receiptConfirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && normalizeRouteToken(typeof executedArgs.action === "string" ? executedArgs.action : void 0) === "reply" && !resultIsError && !blocksSourceReplyTermination && isDeliveredMessagingToolResult({
|
|
5426
|
-
toolName,
|
|
5427
|
-
args: executedArgs,
|
|
5428
|
-
result,
|
|
5429
|
-
hookResult: rawResult,
|
|
5430
|
-
isError: resultIsError
|
|
5431
|
-
}) && (replyReceiptMatchesCurrentMessage(rawResult, params.hookContext) || replyReceiptMatchesCurrentMessage(result, params.hookContext));
|
|
5801
|
+
success: !resultIsError,
|
|
5802
|
+
diagnosticTerminalType: terminalType,
|
|
5803
|
+
diagnosticTerminalReason: resultFailureKind === "blocked" ? void 0 : resultFailureKind,
|
|
5804
|
+
transcriptDetails: asOptionalRecord(sanitizeToolResult(result))?.details
|
|
5805
|
+
};
|
|
5432
5806
|
const toolConfirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && !resultIsError && (rawResult.terminate === true || result.terminate === true);
|
|
5433
|
-
const confirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && (toolConfirmedSourceReply || deliveredSourceReply
|
|
5807
|
+
const confirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && (toolConfirmedSourceReply || deliveredSourceReply);
|
|
5434
5808
|
const sourceReplyFinal = confirmedSourceReply ? executedArgs.final !== false : void 0;
|
|
5435
5809
|
const autoDeliveryTtsMediaUrls = getCoreTtsToolResultMediaUrls(rawResult);
|
|
5436
5810
|
collectToolTelemetry({
|
|
@@ -5441,24 +5815,27 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5441
5815
|
telemetry,
|
|
5442
5816
|
signal,
|
|
5443
5817
|
isError: resultIsError,
|
|
5818
|
+
messagingDelivered,
|
|
5819
|
+
mediaDeliveryConfirmed,
|
|
5820
|
+
sourcePathsByStagedPath: preparedMessageMedia?.sourcePathsByStagedPath,
|
|
5444
5821
|
autoDeliveryTtsMediaUrls,
|
|
5445
5822
|
coreTtsToolResult: autoDeliveryTtsMediaUrls?.length ? rawResult : void 0,
|
|
5446
5823
|
messagingTarget: confirmedMessagingTarget,
|
|
5447
5824
|
sourceReplyFinal,
|
|
5448
5825
|
trustedLocalMediaToolNames: pluginLocalMediaTrustByToolName.get(toolName)
|
|
5449
5826
|
});
|
|
5450
|
-
if (deliveredSourceReply ||
|
|
5827
|
+
if (deliveredSourceReply || toolConfirmedSourceReply) telemetry.didDeliverSourceReplyViaMessageTool = true;
|
|
5451
5828
|
const continuesSourceReplyProgress = confirmedSourceReply && sourceReplyFinal === false;
|
|
5452
|
-
|
|
5829
|
+
response.terminate = (rawResult.terminate === true || result.terminate === true) && !continuesSourceReplyProgress || isToolResultYield(rawResult) || isToolResultYield(result) || confirmedSourceReply && sourceReplyFinal === true || void 0;
|
|
5453
5830
|
const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
|
|
5454
|
-
|
|
5831
|
+
response.asyncStarted = asyncStarted || void 0;
|
|
5455
5832
|
const replaySafe = executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
5456
5833
|
copyInternalToolResultState(rawResult, response);
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5834
|
+
response.executedArguments = executedArgs;
|
|
5835
|
+
response.executionStarted = didStartExecution && !executionPrevented;
|
|
5836
|
+
response.replaySafe = replaySafe;
|
|
5837
|
+
response.sideEffectEvidence = !replaySafe || void 0;
|
|
5838
|
+
return response;
|
|
5462
5839
|
} catch (error) {
|
|
5463
5840
|
const trustedNoStart = consumeTrustedToolNoStartError(error);
|
|
5464
5841
|
executionPrevented ||= trustedNoStart;
|
|
@@ -5466,6 +5843,8 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5466
5843
|
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId) invalidateComputerFrame(params.computerContextEpoch);
|
|
5467
5844
|
const executionDisposition = getBeforeToolCallFailureDisposition(error) ?? (signal.aborted ? resolveCodexToolAbortTerminalReason(signal) : resolveToolExecutionErrorKind(error));
|
|
5468
5845
|
const errorMessage = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
|
|
5846
|
+
const operatorHint = readToolOperatorHint(error);
|
|
5847
|
+
if (operatorHint) embeddedAgentLog.error(`[tools] ${toolName} failed: ${errorMessage} ${operatorHint}`);
|
|
5469
5848
|
executionPrevented = executionPrevented || consumePreExecutionBlockedToolCall(call.callId, toolResultHookContext.runId);
|
|
5470
5849
|
const failedResult = failedToolResult(errorMessage, executionDisposition);
|
|
5471
5850
|
finalizeToolTerminalPresentation({
|
|
@@ -5491,17 +5870,19 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5491
5870
|
startedAt
|
|
5492
5871
|
});
|
|
5493
5872
|
const replaySafe = !didStartExecution || executionPrevented || isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
5494
|
-
return
|
|
5873
|
+
return {
|
|
5495
5874
|
contentItems: [{
|
|
5496
5875
|
type: "inputText",
|
|
5497
5876
|
text: errorMessage
|
|
5498
5877
|
}],
|
|
5499
|
-
success: false
|
|
5500
|
-
|
|
5878
|
+
success: false,
|
|
5879
|
+
diagnosticTerminalType: executionDisposition === "blocked" ? "blocked" : "error",
|
|
5880
|
+
diagnosticTerminalReason: executionDisposition === "blocked" ? void 0 : executionDisposition,
|
|
5501
5881
|
executedArguments: executedArgs,
|
|
5502
5882
|
executionStarted: didStartExecution && !executionPrevented,
|
|
5503
|
-
|
|
5504
|
-
|
|
5883
|
+
replaySafe,
|
|
5884
|
+
sideEffectEvidence: didStartExecution && !replaySafe || void 0
|
|
5885
|
+
};
|
|
5505
5886
|
} finally {
|
|
5506
5887
|
if (executionSnapshotStates.get(call.callId) === executionSnapshotState && (executionSnapshotState.consumed || !executionSnapshotState.retainAfterCompletion)) executionSnapshotStates.delete(call.callId);
|
|
5507
5888
|
consumeAdjustedParamsForToolCall(call.callId, toolResultHookContext.runId);
|
|
@@ -5554,18 +5935,6 @@ function notifyAgentToolResult(observer, toolName, result, isError) {
|
|
|
5554
5935
|
embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${toolName} error=${message}`);
|
|
5555
5936
|
}
|
|
5556
5937
|
}
|
|
5557
|
-
function failedToolResult(message, status = "failed") {
|
|
5558
|
-
return {
|
|
5559
|
-
content: [{
|
|
5560
|
-
type: "text",
|
|
5561
|
-
text: message
|
|
5562
|
-
}],
|
|
5563
|
-
details: {
|
|
5564
|
-
status,
|
|
5565
|
-
error: message
|
|
5566
|
-
}
|
|
5567
|
-
};
|
|
5568
|
-
}
|
|
5569
5938
|
function reportQuarantinedDynamicTools(params) {
|
|
5570
5939
|
if (params.tools.length === 0) return;
|
|
5571
5940
|
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`, {
|
|
@@ -5610,13 +5979,12 @@ function composeAbortSignals(...signals) {
|
|
|
5610
5979
|
return AbortSignal.any(activeSignals);
|
|
5611
5980
|
}
|
|
5612
5981
|
function collectToolTelemetry(params) {
|
|
5613
|
-
if (params.isError) return;
|
|
5614
5982
|
if (!params.isError && params.toolName === "cron" && isCronAddAction(params.args)) params.telemetry.successfulCronAdds = (params.telemetry.successfulCronAdds ?? 0) + 1;
|
|
5615
5983
|
if (!params.isError && params.toolName === HEARTBEAT_RESPONSE_TOOL_NAME) {
|
|
5616
5984
|
const response = normalizeHeartbeatToolResponse(params.result?.details);
|
|
5617
5985
|
if (response) params.telemetry.heartbeatToolResponse = response;
|
|
5618
5986
|
}
|
|
5619
|
-
if (params.result && !params.signal.aborted) {
|
|
5987
|
+
if (!params.isError && params.result && !params.signal.aborted) {
|
|
5620
5988
|
const media = extractToolResultMediaArtifact(params.result);
|
|
5621
5989
|
if (media) {
|
|
5622
5990
|
const mediaUrls = filterToolResultMediaUrls(params.toolName, media.mediaUrls, params.coreTtsToolResult ?? params.mediaTrustResult ?? params.result, params.trustedLocalMediaToolNames);
|
|
@@ -5639,24 +6007,20 @@ function collectToolTelemetry(params) {
|
|
|
5639
6007
|
if (media.audioAsVoice) params.telemetry.toolAudioAsVoice = true;
|
|
5640
6008
|
}
|
|
5641
6009
|
}
|
|
5642
|
-
if (!
|
|
5643
|
-
const isMessagingSendAction = isMessagingToolSendAction(params.toolName, params.args);
|
|
5644
|
-
if (!isMessagingSendAction && !params.messagingTarget) return;
|
|
5645
|
-
if (!isMessagingSendAction && !isDeliveredMessagingToolResult({
|
|
5646
|
-
toolName: params.toolName,
|
|
5647
|
-
args: params.args,
|
|
5648
|
-
result: params.result,
|
|
5649
|
-
hookResult: params.mediaTrustResult,
|
|
5650
|
-
isError: params.isError
|
|
5651
|
-
})) return;
|
|
6010
|
+
if (!params.messagingDelivered) return;
|
|
5652
6011
|
params.telemetry.didSendViaMessagingTool = true;
|
|
5653
|
-
|
|
5654
|
-
|
|
6012
|
+
if (asOptionalRecord(asOptionalRecord(params.mediaTrustResult)?.details)?.sourceReplySink === "internal-ui") {
|
|
6013
|
+
const sourceReplyPayload = extractMessagingToolSourceReplyPayload(params.result);
|
|
6014
|
+
if (!sourceReplyPayload) return;
|
|
5655
6015
|
const record = {
|
|
5656
6016
|
...sourceReplyPayload,
|
|
5657
6017
|
...params.sourceReplyFinal !== void 0 ? { sourceReplyFinal: params.sourceReplyFinal } : {}
|
|
5658
6018
|
};
|
|
5659
6019
|
params.telemetry.messagingToolSourceReplyPayloads.push(record);
|
|
6020
|
+
if (params.mediaDeliveryConfirmed) params.telemetry.confirmedMediaDeliveries.push({
|
|
6021
|
+
kind: "sourceReply",
|
|
6022
|
+
sourceUrls: resolveCodexMediaSourceUrls(collectCodexMessageMediaUrls(record), params.sourcePathsByStagedPath)
|
|
6023
|
+
});
|
|
5660
6024
|
return record;
|
|
5661
6025
|
}
|
|
5662
6026
|
const text = readFirstString(params.args, [
|
|
@@ -5666,7 +6030,7 @@ function collectToolTelemetry(params) {
|
|
|
5666
6030
|
"content"
|
|
5667
6031
|
]);
|
|
5668
6032
|
if (text) params.telemetry.messagingToolSentTexts.push(text);
|
|
5669
|
-
const mediaUrls =
|
|
6033
|
+
const mediaUrls = params.mediaDeliveryConfirmed ? collectCodexMessageMediaUrls(params.args) : [];
|
|
5670
6034
|
params.telemetry.messagingToolSentMediaUrls.push(...mediaUrls);
|
|
5671
6035
|
const record = {
|
|
5672
6036
|
...params.messagingTarget ?? {
|
|
@@ -5689,27 +6053,13 @@ function collectToolTelemetry(params) {
|
|
|
5689
6053
|
...params.sourceReplyFinal !== void 0 ? { sourceReplyFinal: params.sourceReplyFinal } : {}
|
|
5690
6054
|
};
|
|
5691
6055
|
params.telemetry.messagingToolSentTargets.push(record);
|
|
6056
|
+
if (mediaUrls.length > 0) params.telemetry.confirmedMediaDeliveries.push({
|
|
6057
|
+
kind: "outbound",
|
|
6058
|
+
target: record,
|
|
6059
|
+
sourceUrls: resolveCodexMediaSourceUrls(mediaUrls, params.sourcePathsByStagedPath)
|
|
6060
|
+
});
|
|
5692
6061
|
return record;
|
|
5693
6062
|
}
|
|
5694
|
-
function extractInternalSourceReplyPayload(details) {
|
|
5695
|
-
if (!isRecord(details) || details.sourceReplySink !== "internal-ui") return;
|
|
5696
|
-
const rawPayload = details.sourceReply;
|
|
5697
|
-
if (!isRecord(rawPayload)) return;
|
|
5698
|
-
const text = readFirstString(rawPayload, ["text", "message"]);
|
|
5699
|
-
const mediaUrls = collectMediaUrls(rawPayload);
|
|
5700
|
-
const mediaUrl = typeof rawPayload.mediaUrl === "string" && rawPayload.mediaUrl.trim() ? rawPayload.mediaUrl.trim() : mediaUrls[0];
|
|
5701
|
-
const payload = {
|
|
5702
|
-
...text ? { text } : {},
|
|
5703
|
-
...mediaUrl ? { mediaUrl } : {},
|
|
5704
|
-
...mediaUrls.length > 0 ? { mediaUrls } : {},
|
|
5705
|
-
...rawPayload.audioAsVoice === true ? { audioAsVoice: true } : {},
|
|
5706
|
-
...isRecord(rawPayload.presentation) ? { presentation: rawPayload.presentation } : {},
|
|
5707
|
-
...isRecord(rawPayload.interactive) ? { interactive: rawPayload.interactive } : {},
|
|
5708
|
-
...isRecord(rawPayload.channelData) ? { channelData: rawPayload.channelData } : {},
|
|
5709
|
-
...typeof details.idempotencyKey === "string" && details.idempotencyKey.trim() ? { idempotencyKey: details.idempotencyKey.trim() } : {}
|
|
5710
|
-
};
|
|
5711
|
-
return text || mediaUrls.length > 0 || payload.presentation || payload.interactive ? payload : void 0;
|
|
5712
|
-
}
|
|
5713
6063
|
function isToolResultYield(result) {
|
|
5714
6064
|
const details = result.details;
|
|
5715
6065
|
if (!isRecord(details) || typeof details.status !== "string") return false;
|
|
@@ -5719,42 +6069,6 @@ function isAsyncStartedToolResult(result) {
|
|
|
5719
6069
|
const details = result.details;
|
|
5720
6070
|
return isRecord(details) && details.async === true && details.status === "started";
|
|
5721
6071
|
}
|
|
5722
|
-
function withDiagnosticTerminalType(response, terminalType) {
|
|
5723
|
-
Object.defineProperty(response, "diagnosticTerminalType", {
|
|
5724
|
-
configurable: true,
|
|
5725
|
-
enumerable: false,
|
|
5726
|
-
value: terminalType
|
|
5727
|
-
});
|
|
5728
|
-
return response;
|
|
5729
|
-
}
|
|
5730
|
-
function withDiagnosticFailureDisposition(response, disposition) {
|
|
5731
|
-
if (!disposition) return response;
|
|
5732
|
-
withDiagnosticTerminalType(response, disposition === "blocked" ? "blocked" : "error");
|
|
5733
|
-
if (disposition !== "blocked") Object.defineProperty(response, "diagnosticTerminalReason", {
|
|
5734
|
-
configurable: true,
|
|
5735
|
-
enumerable: false,
|
|
5736
|
-
value: disposition
|
|
5737
|
-
});
|
|
5738
|
-
return response;
|
|
5739
|
-
}
|
|
5740
|
-
function withDynamicToolTermination(response, terminate) {
|
|
5741
|
-
if (!terminate) return response;
|
|
5742
|
-
Object.defineProperty(response, "terminate", {
|
|
5743
|
-
configurable: true,
|
|
5744
|
-
enumerable: false,
|
|
5745
|
-
value: true
|
|
5746
|
-
});
|
|
5747
|
-
return response;
|
|
5748
|
-
}
|
|
5749
|
-
function withDynamicToolAsyncStarted(response, asyncStarted) {
|
|
5750
|
-
if (!asyncStarted) return response;
|
|
5751
|
-
Object.defineProperty(response, "asyncStarted", {
|
|
5752
|
-
configurable: true,
|
|
5753
|
-
enumerable: false,
|
|
5754
|
-
value: true
|
|
5755
|
-
});
|
|
5756
|
-
return response;
|
|
5757
|
-
}
|
|
5758
6072
|
function normalizeToolResultMaxChars(maxChars) {
|
|
5759
6073
|
return typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS;
|
|
5760
6074
|
}
|
|
@@ -5852,53 +6166,9 @@ function readFirstString(record, keys) {
|
|
|
5852
6166
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
5853
6167
|
}
|
|
5854
6168
|
}
|
|
5855
|
-
function collectMediaUrls(record) {
|
|
5856
|
-
const urls = [];
|
|
5857
|
-
const pushMediaUrl = (value) => {
|
|
5858
|
-
if (typeof value === "string" && value.trim()) urls.push(value.trim());
|
|
5859
|
-
};
|
|
5860
|
-
const pushAttachment = (value) => {
|
|
5861
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
5862
|
-
const attachment = value;
|
|
5863
|
-
for (const key of [
|
|
5864
|
-
"media",
|
|
5865
|
-
"mediaUrl",
|
|
5866
|
-
"path",
|
|
5867
|
-
"filePath",
|
|
5868
|
-
"fileUrl",
|
|
5869
|
-
"url"
|
|
5870
|
-
]) pushMediaUrl(attachment[key]);
|
|
5871
|
-
};
|
|
5872
|
-
for (const key of [
|
|
5873
|
-
"media",
|
|
5874
|
-
"mediaUrl",
|
|
5875
|
-
"media_url",
|
|
5876
|
-
"path",
|
|
5877
|
-
"filePath",
|
|
5878
|
-
"fileUrl",
|
|
5879
|
-
"imageUrl",
|
|
5880
|
-
"image_url"
|
|
5881
|
-
]) {
|
|
5882
|
-
const value = record[key];
|
|
5883
|
-
pushMediaUrl(value);
|
|
5884
|
-
}
|
|
5885
|
-
for (const key of [
|
|
5886
|
-
"mediaUrls",
|
|
5887
|
-
"media_urls",
|
|
5888
|
-
"imageUrls",
|
|
5889
|
-
"image_urls"
|
|
5890
|
-
]) {
|
|
5891
|
-
const value = record[key];
|
|
5892
|
-
if (!Array.isArray(value)) continue;
|
|
5893
|
-
for (const entry of value) pushMediaUrl(entry);
|
|
5894
|
-
}
|
|
5895
|
-
const attachments = record.attachments;
|
|
5896
|
-
if (Array.isArray(attachments)) for (const attachment of attachments) pushAttachment(attachment);
|
|
5897
|
-
return urls;
|
|
5898
|
-
}
|
|
5899
6169
|
function isCronAddAction(args) {
|
|
5900
6170
|
const action = args.action;
|
|
5901
6171
|
return typeof action === "string" && action.trim().toLowerCase() === "add";
|
|
5902
6172
|
}
|
|
5903
6173
|
//#endregion
|
|
5904
|
-
export {
|
|
6174
|
+
export { shouldEnableCodexAppServerNativeToolSurface as C, mapCodexAppServerRemoteWorkspacePath as E, resolveCodexSandboxEnvironmentSelection as S, shouldWarnCodexDynamicToolBuildStageSummary as T, formatCodexDynamicToolBuildStageSummary as _, emitDynamicToolStartedDiagnostic as a, resolveCodexExternalSandboxPolicyForOpenClawSandbox as b, ensureCodexSandboxExecServerEnvironment as c, hasCodexNativeBackgroundProcesses as d, readBoundedCodexRemoteWorkspaceFile as f, disableCodexPluginThreadConfig as g, createCodexDynamicToolBuildStageTracker as h, emitDynamicToolErrorDiagnostic as i, releaseCodexSandboxExecServerEnvironment as l, buildDynamicTools as m, projectCodexExecutableDynamicTools as n, emitDynamicToolTerminalDiagnostic as o, CodexNativeToolLifecycleProjector as p, routeCodexAppServerElicitationRequest as r, handleCodexAppServerApprovalRequest as s, createCodexDynamicToolBridge as t, CodexNativeProcessAuthority as u, resolveCodexAppServerExecutionCwd as v, shouldRequireCodexSandboxExecServerEnvironment as w, resolveCodexMessageToolProvider as x, resolveCodexAppServerHookChannelId as y };
|