@openclaw/codex 2026.9.4 → 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/assets/activity.svg +30 -0
- package/assets/icon.png +0 -0
- package/dist/.setup/account-usage-runtime-Eem_WGqY.mjs +75 -0
- package/dist/{apply-CvT8tW5i.js → .setup/apply-DDOi8uNx.mjs} +24 -30
- package/dist/.setup/async-questions-DdhOCwGi.mjs +27 -0
- package/dist/{attempt-client-cleanup-DKHzbwt5.js → .setup/attempt-client-cleanup-CEv1cKwF.mjs} +79 -49
- package/dist/.setup/attempt-terminal-gZrxQ35N.mjs +5 -0
- package/dist/.setup/attempt-workspace-context-DUVEn_CS.mjs +331 -0
- package/dist/{auth-binding-CWlKVccJ.js → .setup/auth-binding-DhLWa_7K.mjs} +1 -1
- package/dist/{auth-cache-key-7orQae_j.js → .setup/auth-cache-key-B2mpaI9D.mjs} +9 -81
- package/dist/{auth-profile-CwzO4_RG.js → .setup/auth-profile-WqZtZfXN.mjs} +1 -1
- 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-ThoatDcb.mjs +50 -0
- package/dist/.setup/binding-connection.runtime-yoHrABt-.mjs +48 -0
- package/dist/{bounded-turn-C_Hneydj.js → .setup/bounded-turn-B2HScEgT.mjs} +143 -32
- package/dist/{build-state-BCmg9-5p.js → .setup/build-state-C7EnDVgr.mjs} +1 -1
- package/dist/{capabilities-8vx68WLH.js → .setup/capabilities-CDXOOFdZ.mjs} +1 -1
- package/dist/{client-B57KWPQx.js → .setup/client-Cs08OXVQ.mjs} +705 -512
- package/dist/.setup/client-catalog-response-BMgt_4q4.mjs +285 -0
- package/dist/{command-dispatch-BpduNGVf.js → .setup/command-dispatch-B4JuOsOk.mjs} +8 -4
- package/dist/{command-formatters-lxlkNZgU.js → .setup/command-formatters-Bmqvr9tO.mjs} +8 -4
- package/dist/{command-handlers-tiL9iWj7.js → .setup/command-handlers-DW7Lx9oH.mjs} +137 -186
- package/dist/{command-rpc-CR5EOEZX.js → .setup/command-rpc-DixIj3HW.mjs} +47 -23
- package/dist/{compact-B_H3AOd7.js → .setup/compact-Du5UvtxA.mjs} +29 -51
- package/dist/{config-oIORaQ5T.js → .setup/config-BoTP_mrL.mjs} +5 -4
- package/dist/{config-options-CXMq1T-C.js → .setup/config-options-BvaRs51b.mjs} +50 -446
- package/dist/{protocol-5bh1G-H7.js → .setup/config-parsing-CcB9iPoq.mjs} +9 -51
- package/dist/{config-runtime-D0zGNIHu.js → .setup/config-runtime-C8Rw1m1m.mjs} +2 -2
- package/dist/.setup/config-security-BEReZ6go.mjs +380 -0
- package/dist/{conversation-binding-DsB4-O7U.js → .setup/conversation-binding-DP-gMrBP.mjs} +29 -38
- package/dist/{conversation-binding-data-BiodpIzq.js → .setup/conversation-binding-data-CrcyZk6O.mjs} +1 -1
- package/dist/{conversation-control-CtUu1b3m.js → .setup/conversation-control-uK9Jomlc.mjs} +32 -67
- package/dist/.setup/cyber-failover-DYwbmf1o.mjs +123 -0
- package/dist/.setup/cyber-failover-notice-e9G3J9-e.mjs +17 -0
- package/dist/{dynamic-tools-CmN5RAip.js → .setup/dynamic-tools-BDXl-8Cw.mjs} +936 -620
- package/dist/{effective-mcp-catalog-Bd73bqA8.js → .setup/effective-mcp-catalog-CvnvnGIe.mjs} +3 -3
- package/dist/{ephemeral-turn-D78_VmzO.js → .setup/ephemeral-turn-poidXgek.mjs} +9 -7
- package/dist/{event-projector-assistant-message-BmBM4qBV.js → .setup/event-projector-assistant-message-BsruvNrH.mjs} +8 -7
- package/dist/{event-projector-items-5VhsECCO.js → .setup/event-projector-items-C6L_5ZiT.mjs} +42 -8
- package/dist/{attempt-notifications-C9XufG6l.js → .setup/event-projector-usage-CnBS8Wa3.mjs} +102 -9
- package/dist/{harness-C7rRa7bm.js → .setup/harness-a_MsE4-_.mjs} +81 -21
- package/dist/{inference-context-Dr_mzvk4.js → .setup/inference-context-D96l9-ew.mjs} +1 -1
- package/dist/.setup/inference-proxy-b-aJMSxm.mjs +601 -0
- package/dist/{isolated-completion-DWVb5U2J.js → .setup/isolated-completion-BIZFhTNG.mjs} +18 -11
- package/dist/.setup/launch-args-DbFCehO7.mjs +196 -0
- package/dist/{managed-binary-D22rNEcR.js → .setup/managed-binary-BnshlFag.mjs} +14 -3
- package/dist/{managed-thread-store-D1t5jh3G.js → .setup/managed-thread-store-BMqgThVL.mjs} +48 -14
- package/dist/{model-catalog-Dq2n8f_p.js → .setup/model-catalog-CfXJf9Ay.mjs} +36 -12
- package/dist/{models-PCKOim-h.js → .setup/models-KnqBv0hE.mjs} +3 -3
- package/dist/.setup/native-auth-rWbGbMn3.mjs +57 -0
- package/dist/{native-subagent-history-DjtPloaU.js → .setup/native-subagent-history-0nYYgo0k.mjs} +43 -35
- package/dist/.setup/native-subagent-monitor-D3filLY2.mjs +4859 -0
- package/dist/{native-subagent-task-ids-BzTdT0Uw.js → .setup/native-subagent-task-ids-wb8CKZGN.mjs} +98 -44
- package/dist/{node-cli-sessions-DKaOmVUh.js → .setup/node-cli-sessions-r3avj02l.mjs} +86 -68
- package/dist/.setup/node-exec-server.runtime-BDwWXVpK.mjs +247 -0
- package/dist/{plan-7uG-Fp36.js → .setup/plan-CN3VwfNU.mjs} +133 -119
- package/dist/{plugin-activation-BYQculil.js → .setup/plugin-activation-DT3MNtqj.mjs} +7 -16
- package/dist/{plugin-app-cache-key-d0eKxy7b.js → .setup/plugin-app-cache-key-B2CsSdnV.mjs} +32 -44
- package/dist/{plugin-approval-roundtrip-Bz3FT8W5.js → .setup/plugin-approval-roundtrip-C0MZzCuP.mjs} +1 -1
- package/dist/{plugin-inventory-oL4Na1ZG.js → .setup/plugin-inventory-BoRei8Z4.mjs} +21 -11
- package/dist/{plugin-marketplace-discovery-P-Vndfqq.js → .setup/plugin-marketplace-discovery-CDOi8IFs.mjs} +4 -1
- package/dist/{plugin-metadata-cache-ClPJpRio.js → .setup/plugin-metadata-cache-Ctmo83k_.mjs} +226 -141
- package/dist/.setup/protocol-CANUwXJ3.mjs +17 -0
- package/dist/{provider-MQ8PVkuE.js → .setup/provider-BOsxQHOM.mjs} +9 -5
- package/dist/{provider-capabilities-BvCUL69A.js → .setup/provider-capabilities-BfE88FZ5.mjs} +31 -3
- package/dist/{rate-limits-CBMmWBr9.js → .setup/rate-limits-CMU3DUD0.mjs} +174 -151
- package/dist/{request-B7hEkBGq.js → .setup/request-D2L0zMrq.mjs} +130 -42
- package/dist/{run-attempt-CvsAAGa0.js → .setup/run-attempt-BHu11r7h.mjs} +1628 -966
- package/dist/.setup/run-attempt-lifecycle-B7JbY4-F.mjs +71 -0
- package/dist/{runtime-artifact-BJ0rXBZ_.js → .setup/runtime-artifact-CLZ8udh-.mjs} +29 -45
- package/dist/{sandbox-guard-BxgyoM8M.js → .setup/sandbox-guard-C2sRMOEY.mjs} +3 -3
- package/dist/{session-binding-CI8UuilT.js → .setup/session-binding-Cm0apEbd.mjs} +36 -83
- package/dist/{session-binding-record-Bcvslnhf.js → .setup/session-binding-record-BGoz8wOK.mjs} +202 -5
- package/dist/.setup/session-binding-sidecars-BlB5EvIS.mjs +546 -0
- package/dist/.setup/session-catalog-Pno1xGcl.mjs +2764 -0
- package/dist/{session-catalog-adoption-tcZi1IfX.js → .setup/session-catalog-adoption-BI6UTDZT.mjs} +58 -26
- package/dist/{session-catalog-archive-CwKg8Xz2.js → .setup/session-catalog-archive-lHBqzhaO.mjs} +6 -6
- package/dist/.setup/session-catalog-events-Bj6j94E_.mjs +347 -0
- package/dist/.setup/session-catalog-index-B9_VQIUz.mjs +1170 -0
- package/dist/.setup/session-catalog-index-order-hcdz07yN.mjs +48 -0
- package/dist/.setup/session-catalog-index-state-Dgh8ORJm.mjs +183 -0
- package/dist/{thread-history-page-D0JIp48V.js → .setup/session-catalog-native-projection-DowriLid.mjs} +223 -95
- package/dist/{session-catalog-node-adoption-B1tgDW-m.js → .setup/session-catalog-node-adoption-ITddWx_X.mjs} +25 -13
- package/dist/{session-catalog-node-continue-Ci-DXyJP.js → .setup/session-catalog-node-continue-6g6qi4A9.mjs} +24 -15
- package/dist/.setup/session-catalog-projection-zK_PUsiE.mjs +422 -0
- package/dist/{session-cli-DNN_jhGc.js → .setup/session-cli-irrD-uo7.mjs} +3 -3
- package/dist/{session-history-Cz-tOOYj.js → .setup/session-history-BrqhSCf9.mjs} +19 -19
- package/dist/{session-history-import-DwyijFI6.js → .setup/session-history-import-D-3wAgOP.mjs} +4 -4
- package/dist/{session-history-read-cxPUxyVV.js → .setup/session-history-read-B3ZfSUbF.mjs} +2 -2
- package/dist/{session-history.worker-BeK1YOyT.js → .setup/session-history.worker-CaCh0xF_.mjs} +10 -24
- package/dist/{session-initialization-Ct0_Hzmg.js → .setup/session-initialization-rcgbZiid.mjs} +2 -2
- package/dist/{session-permission-policy-BBmXA8Eg.js → .setup/session-permission-policy-BI1QK_98.mjs} +2 -2
- package/dist/{session-retirement-B2YfbAAT.js → .setup/session-retirement-BH-S6qkJ.mjs} +26 -7
- package/dist/{settled-turn-context-Du6gdUDd.js → .setup/settled-turn-context-fmwouT5I.mjs} +2 -2
- package/dist/{settled-turn-finalizer-Dn91stmc.js → .setup/settled-turn-finalizer-DiAi6Kx6.mjs} +12 -12
- package/dist/{shared-client-CscigXXL.js → .setup/shared-client-DA4VR4Eb.mjs} +332 -221
- package/dist/{side-question-BGMJQf4U.js → .setup/side-question-BJ1wSDcB.mjs} +183 -230
- package/dist/{source-CElXU0Oy.js → .setup/source-CUzJq5WN.mjs} +11 -10
- package/dist/.setup/spawn-error-CL3n6MAa.mjs +57 -0
- package/dist/{thread-archive-guard-DfpBr-Sz.js → .setup/thread-archive-guard-C9frICzX.mjs} +10 -3
- package/dist/.setup/thread-history-page-DoVpnHim.mjs +83 -0
- package/dist/{thread-lifecycle-DXl1-I6b.js → .setup/thread-lifecycle-B3mfz1TG.mjs} +347 -2318
- package/dist/{thread-ownership-BsDRTsyp.js → .setup/thread-ownership-DcTtAcXj.mjs} +6 -4
- package/dist/{thread-policy-BuEQfE0g.js → .setup/thread-policy-DGuQsuFC.mjs} +19 -3
- package/dist/.setup/thread-requests-BLvGkP2R.mjs +2205 -0
- package/dist/{thread-resume-DsDOZpFg.js → .setup/thread-resume-DIBXwOpE.mjs} +8 -5
- package/dist/.setup/timeout-C910MdAB.mjs +80 -0
- package/dist/{transcript-history-projection-v17wXgdK.js → .setup/transcript-history-projection-ZZpkFkJ_.mjs} +96 -61
- package/dist/{transcript-mirror-C5CKgNy6.js → .setup/transcript-mirror-BtHDBp1G.mjs} +224 -195
- package/dist/{transcript-mirror-attestation-DibE0krH.js → .setup/transcript-mirror-attestation-CzM-94NC.mjs} +4 -1
- package/dist/{transport-stdio-BGjHoYLr.js → .setup/transport-stdio-h2VHOskC.mjs} +274 -89
- package/dist/{upstream-session-fork-BZerMte_.js → .setup/upstream-session-fork-BUr4OUbC.mjs} +51 -42
- package/dist/{usage-tCk-mg_x.js → .setup/usage-BML0r-ua.mjs} +5 -4
- package/dist/.setup/usage-limit-error-DUMbefVY.mjs +166 -0
- package/dist/{web-search-provider.runtime-B7bGbGWq.js → .setup/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 +8 -528
- package/dist/harness.js +1 -1
- package/dist/index.js +306 -215
- package/dist/media-understanding-provider.js +3 -3
- package/dist/migration-provider-api.js +1 -1
- package/dist/native-tool-policy.js +12 -0
- package/dist/provider-discovery.js +18 -0
- package/dist/session-history-worker-runtime.js +36 -35
- package/dist/session-history.worker.js +2 -2
- package/dist/web-search-contract-api.js +1 -1
- package/openclaw.plugin.json +354 -69
- package/package.json +14 -8
- package/dist/attempt-context-D44yeDII.js +0 -636
- package/dist/binding-connection-D3udKbIE.js +0 -60
- package/dist/event-projector-usage-BfXmpOec.js +0 -102
- package/dist/inference-proxy-HGXKUIre.js +0 -311
- package/dist/launch-args-RXQwn8zV.js +0 -90
- package/dist/native-subagent-history-owner-Pcp7JuVF.js +0 -34
- package/dist/native-subagent-monitor-fQ7eIXEf.js +0 -2978
- package/dist/node-exec-server.runtime-Cl2vapEi.js +0 -226
- package/dist/rpc-error-ttN_QjEm.js +0 -30
- package/dist/session-catalog-ct-AH01I.js +0 -1477
- package/dist/session-catalog-home-id-CwhLdC03.js +0 -18
- package/dist/session-catalog-provenance-D-dIfRAH.js +0 -96
- package/dist/timeout-BVw8aaM1.js +0 -33
- package/dist/version-omm6IPI3.js +0 -12
- /package/dist/{auth-profile-selection-DFu9e167.js → .setup/auth-profile-selection-DFu9e167.mjs} +0 -0
- /package/dist/{config-utils-DujwEnhg.js → .setup/config-utils-DujwEnhg.mjs} +0 -0
- /package/dist/{context-compaction-activity-CMla5FPJ.js → .setup/context-compaction-activity-CMla5FPJ.mjs} +0 -0
- /package/dist/{elicitation-input-DPoK8F7w.js → .setup/elicitation-input-DPoK8F7w.mjs} +0 -0
- /package/dist/{history-rejection-B4nOXNXF.js → .setup/history-rejection-B4nOXNXF.mjs} +0 -0
- /package/dist/{image-payload-sanitizer-tupAr-2o.js → .setup/image-payload-sanitizer-tupAr-2o.mjs} +0 -0
- /package/dist/{incognito-session-uhrBF6wJ.js → .setup/incognito-session-uhrBF6wJ.mjs} +0 -0
- /package/dist/{local-runtime-attribution-B_2mnqhb.js → .setup/local-runtime-attribution-B_2mnqhb.mjs} +0 -0
- /package/dist/{native-execution-policy-umd0tgh4.js → .setup/native-execution-policy-umd0tgh4.mjs} +0 -0
- /package/dist/{rolldown-runtime-8H4AJuhK.js → .setup/rolldown-runtime-8H4AJuhK.mjs} +0 -0
- /package/dist/{session-binding-meta-B7aEMU7g.js → .setup/session-binding-meta-B7aEMU7g.mjs} +0 -0
- /package/dist/{session-upstream-marker-D15C9NHp.js → .setup/session-upstream-marker-D15C9NHp.mjs} +0 -0
- /package/dist/{upstream-prompt-provenance-LphB8slG.js → .setup/upstream-prompt-provenance-LphB8slG.mjs} +0 -0
- /package/dist/{web-search-provider.shared-BrZmlqyR.js → .setup/web-search-provider.shared-BrZmlqyR.mjs} +0 -0
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import "./
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { CODEX_NATIVE_TOOL_REQUIREMENTS } from "../native-tool-policy.js";
|
|
2
|
+
import { d as readCodexPluginConfig, l as isCodexRemoteExecPlacementSandbox } from "./config-parsing-CcB9iPoq.mjs";
|
|
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";
|
|
8
|
+
import { r as sanitizeInlineImageDataUrl, t as invalidInlineImageText } from "./image-payload-sanitizer-tupAr-2o.mjs";
|
|
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";
|
|
12
16
|
import { createRequire } from "node:module";
|
|
13
|
-
import { asNonArrayRecord, asOptionalRecord, isRecord,
|
|
17
|
+
import { asNonArrayRecord, asOptionalRecord, isRecord, normalizeTrimmedStringList, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
18
|
import { createHash, randomUUID } from "node:crypto";
|
|
15
19
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
16
|
-
import {
|
|
20
|
+
import { signalProcessTree, spawnTerminalPty } from "openclaw/plugin-sdk/process-runtime";
|
|
17
21
|
import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
|
|
18
22
|
import path, { posix } from "node:path";
|
|
23
|
+
import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
19
24
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
20
25
|
import { once } from "node:events";
|
|
21
26
|
import { spawn } from "node:child_process";
|
|
22
27
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
23
28
|
import { DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, estimateToolResultTextChars, resolveLiveToolResultMaxChars, sliceToolResultTextToBudget, sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
29
|
+
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
24
30
|
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
25
31
|
import { createStageTimingTracker, formatStageTimings } from "openclaw/plugin-sdk/time-runtime";
|
|
26
|
-
import { isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
27
32
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
28
33
|
import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
|
|
29
|
-
import { HEARTBEAT_RESPONSE_TOOL_NAME, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, consumeAdjustedParamsForToolCall, consumePreExecutionBlockedToolCall, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, extractToolResultMediaArtifact, filterProviderNormalizableTools, filterToolResultMediaUrls, finalizeAgentToolAvailability, finalizeToolTerminalPresentation, formatApprovalDisplayPath, formatToolExecutionErrorMessage, getBeforeToolCallFailureDisposition, getChannelAgentToolMeta, getPluginToolMeta, getPluginToolSideEffectOwnerKey, hasNativeHookRelayInvocation, invokeNativeHookRelay, isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, isHostScopedAgentToolActive, isMessagingTool,
|
|
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";
|
|
30
35
|
import { formatErrorMessage as formatErrorMessage$2, redactSensitiveText } from "openclaw/plugin-sdk/security-runtime";
|
|
31
|
-
import {
|
|
36
|
+
import { consumeTrustedToolNoStartError, copyInternalToolResultState, getCoreTtsToolResultMediaUrls, normalizeAcceptedSessionSpawnResult } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
|
32
37
|
import { isIP } from "node:net";
|
|
33
38
|
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
34
39
|
import { formatMcpCodexApprovalRemedy, requiresMcpCodexToolApproval, resolveCodexScheduledToolProjectionFactory, resolveProjectedMcpCodexToolApprovalMode, runWithCronCreatorAuthorityCapabilityResolver } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
35
|
-
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";
|
|
36
41
|
import { SsrFBlockedError, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
37
|
-
import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
|
42
|
+
import { normalizeMediaReferenceForComparison, saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
|
38
43
|
import { getMediaDir } from "openclaw/plugin-sdk/media-runtime";
|
|
39
|
-
import { consumeTrustedToolNoStartError, copyInternalToolResultState, getCoreTtsToolResultMediaUrls } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
|
40
44
|
import { normalizeOpenAIStrictCompatSchema } from "openclaw/plugin-sdk/provider-tools";
|
|
41
|
-
//#region extensions/codex/src/app-server/dynamic-tool-build-state.ts
|
|
42
|
-
/** Mutable dependency seam shared by dynamic-tool construction and its behavioral tests. */
|
|
43
|
-
const dynamicToolBuildState = {};
|
|
44
|
-
//#endregion
|
|
45
45
|
//#region extensions/codex/src/app-server/remote-workspace-path.ts
|
|
46
46
|
/** Projects a gateway workspace path into the remote Codex execution workspace. */
|
|
47
47
|
function mapCodexAppServerRemoteWorkspacePath(params) {
|
|
@@ -106,14 +106,6 @@ function filterCodexVisionTools(tools, params) {
|
|
|
106
106
|
* OpenClaw-owned tools, Codex native-tool fallback rules, sandbox shell shims,
|
|
107
107
|
* and provider allowlist normalization.
|
|
108
108
|
*/
|
|
109
|
-
const CODEX_NATIVE_SANDBOX_TOOL_REQUIREMENTS = [
|
|
110
|
-
"exec",
|
|
111
|
-
"process",
|
|
112
|
-
"read",
|
|
113
|
-
"write",
|
|
114
|
-
"edit",
|
|
115
|
-
"apply_patch"
|
|
116
|
-
];
|
|
117
109
|
const CODEX_MEMORY_FLUSH_DYNAMIC_TOOL_ALLOW = /* @__PURE__ */ new Set(["read", "write"]);
|
|
118
110
|
const CODEX_DISABLED_NATIVE_SHELL_DYNAMIC_TOOLS = /* @__PURE__ */ new Set([
|
|
119
111
|
"exec",
|
|
@@ -198,7 +190,6 @@ async function buildDynamicTools(input) {
|
|
|
198
190
|
const toolBuildStages = createCodexDynamicToolBuildStageTracker();
|
|
199
191
|
const modelHasVision = params.model.input?.includes("image") ?? false;
|
|
200
192
|
const agentDir = params.agentDir ?? resolveAgentDir(params.config ?? {}, input.sessionAgentId);
|
|
201
|
-
const injectedOpenClawCodingToolsFactory = dynamicToolBuildState.openClawCodingToolsFactory;
|
|
202
193
|
const nativeExecutionPolicy = resolveCodexNativeExecutionPolicyForRun(params, {
|
|
203
194
|
agentId: input.policyAgentId,
|
|
204
195
|
runtimeSessionKey: input.sandboxSessionKey,
|
|
@@ -210,6 +201,7 @@ async function buildDynamicTools(input) {
|
|
|
210
201
|
nativeToolSurfaceEnabled: input.nativeToolSurfaceEnabled,
|
|
211
202
|
nativeProviderWebSearchSupport: input.nativeProviderWebSearchSupport
|
|
212
203
|
});
|
|
204
|
+
const messageToolProvider = resolveCodexMessageToolProvider(params);
|
|
213
205
|
const webFetchHostnameAllowlistRef = {};
|
|
214
206
|
const toolConstructionPlan = resolveCodexNodePlacementToolConstructionPlan(input.sandbox, input.nativeToolSurfaceEnabled);
|
|
215
207
|
const options = {
|
|
@@ -229,11 +221,11 @@ async function buildDynamicTools(input) {
|
|
|
229
221
|
} : void 0,
|
|
230
222
|
sandbox: input.sandbox,
|
|
231
223
|
...toolConstructionPlan ? { toolConstructionPlan } : {},
|
|
232
|
-
messageProvider:
|
|
224
|
+
messageProvider: messageToolProvider,
|
|
233
225
|
toolPolicyMessageProvider: params.messageProvider ?? params.messageChannel,
|
|
234
226
|
...params.onToolResult ? { questionPrompt: {
|
|
235
227
|
send: params.onToolResult,
|
|
236
|
-
|
|
228
|
+
messageChannel: messageToolProvider
|
|
237
229
|
} } : {},
|
|
238
230
|
inputProvenance: params.inputProvenance,
|
|
239
231
|
trustedInternalHandoff: params.trustedInternalHandoff,
|
|
@@ -253,7 +245,6 @@ async function buildDynamicTools(input) {
|
|
|
253
245
|
config: params.config,
|
|
254
246
|
skillsSnapshot: params.skillsSnapshot,
|
|
255
247
|
...params.skillLibraryAuthoring ? { skillWorkshop: { libraryAuthoring: params.skillLibraryAuthoring } } : {},
|
|
256
|
-
githubPublicationAvailable: params.githubPublicationAvailable,
|
|
257
248
|
authProfileStore: params.toolAuthProfileStore ?? params.authProfileStore,
|
|
258
249
|
abortSignal: input.runAbortController.signal,
|
|
259
250
|
emitBeforeToolCallDiagnostics: false,
|
|
@@ -300,7 +291,6 @@ async function buildDynamicTools(input) {
|
|
|
300
291
|
input.onMessageToolTargetResolved?.(options.requireExplicitMessageTarget === true);
|
|
301
292
|
const buildOpenClawCodingTools = () => {
|
|
302
293
|
const bindingOptions = { cwd: input.effectiveCwd ?? input.effectiveWorkspace };
|
|
303
|
-
if (injectedOpenClawCodingToolsFactory) return params.hostCapabilities.bindToolSurface(injectedOpenClawCodingToolsFactory(options), bindingOptions);
|
|
304
294
|
const createToolSurface = params.hostCapabilities.createToolSurface;
|
|
305
295
|
if (!createToolSurface) throw new Error("Codex tool construction requires a current host capability");
|
|
306
296
|
return createToolSurface(options, bindingOptions);
|
|
@@ -336,7 +326,7 @@ async function buildDynamicTools(input) {
|
|
|
336
326
|
agentId: input.policyAgentId,
|
|
337
327
|
sessionKey: input.sandboxSessionKey,
|
|
338
328
|
sandboxToolPolicy: input.sandbox?.tools,
|
|
339
|
-
messageProvider:
|
|
329
|
+
messageProvider: messageToolProvider,
|
|
340
330
|
agentAccountId: params.agentAccountId,
|
|
341
331
|
groupId: params.groupId,
|
|
342
332
|
groupChannel: params.groupChannel,
|
|
@@ -457,7 +447,7 @@ function canCodexAppServerNativeToolSurfaceHonorSandbox(sandbox, options = {}) {
|
|
|
457
447
|
return false;
|
|
458
448
|
}
|
|
459
449
|
function canSandboxToolPolicyExposeCodexNativeToolSurface(sandbox) {
|
|
460
|
-
return
|
|
450
|
+
return CODEX_NATIVE_TOOL_REQUIREMENTS.every((toolName) => isToolAllowed(sandbox.tools, toolName));
|
|
461
451
|
}
|
|
462
452
|
function isCodexMemoryFlushRun(params) {
|
|
463
453
|
return params?.trigger === "memory" && Boolean(params.memoryFlushWritePath?.trim());
|
|
@@ -513,29 +503,9 @@ function addSandboxShellDynamicToolsIfAvailable(filteredTools, allTools, input,
|
|
|
513
503
|
if (isCodexMemoryFlushRun(input.params) || !executionPolicy.nativeToolSurfaceAllowed || !input.sandbox?.enabled || !input.sandbox.backendId.trim() || input.nativeToolSurfaceEnabled !== false || isSandboxShellDynamicToolExcluded(input.pluginConfig)) return filteredTools;
|
|
514
504
|
const execTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "exec");
|
|
515
505
|
const processTool = allTools.find((tool) => normalizeCodexDynamicToolName(tool.name) === "process");
|
|
516
|
-
if (!execTool
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
name: "sandbox_exec",
|
|
520
|
-
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.",
|
|
521
|
-
execute: async (toolCallId, args, signal, onUpdate) => {
|
|
522
|
-
const result = await execTool.execute(toolCallId, args, signal, onUpdate);
|
|
523
|
-
return {
|
|
524
|
-
...result,
|
|
525
|
-
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)
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
const sandboxProcessTool = {
|
|
530
|
-
...processTool,
|
|
531
|
-
name: "sandbox_process",
|
|
532
|
-
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."
|
|
533
|
-
};
|
|
534
|
-
return [
|
|
535
|
-
...filteredTools,
|
|
536
|
-
sandboxExecTool,
|
|
537
|
-
sandboxProcessTool
|
|
538
|
-
];
|
|
506
|
+
if (!execTool) return filteredTools;
|
|
507
|
+
const sandboxTools = [...filteredTools, createSandboxExecProjection(execTool)];
|
|
508
|
+
return processTool ? [...sandboxTools, createSandboxProcessProjection(processTool)] : sandboxTools;
|
|
539
509
|
}
|
|
540
510
|
function isSandboxShellDynamicToolExcluded(config) {
|
|
541
511
|
return isCodexDynamicToolExcluded(config, [
|
|
@@ -566,13 +536,12 @@ function placeDisabledNativeShellToolsInDirectNamespace(tools, nativeToolSurface
|
|
|
566
536
|
}
|
|
567
537
|
/** Applies a normalized tool allowlist while preserving shell aliases for exec/process. */
|
|
568
538
|
function filterCodexDynamicToolsForAllowlist(tools, toolsAllow) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
return allowSet.has(normalized) || normalized === "sandbox_exec" && allowSet.has("exec") || normalized === "sandbox_process" && (allowSet.has("exec") || allowSet.has("process")) || normalized === "gateway_exec" && allowSet.has("exec") || normalized === "gateway_process" && (allowSet.has("exec") || allowSet.has("process")) || normalized === "node_exec" && allowSet.has("exec");
|
|
539
|
+
return applyEmbeddedAttemptToolsAllow(tools, toolsAllow, {
|
|
540
|
+
toolMeta: getPluginToolMeta,
|
|
541
|
+
toolAliases: (tool) => {
|
|
542
|
+
const normalized = normalizeCodexDynamicToolName(tool.name);
|
|
543
|
+
return normalized === "sandbox_exec" || normalized === "gateway_exec" || normalized === "node_exec" ? ["exec"] : normalized === "sandbox_process" || normalized === "gateway_process" ? ["exec", "process"] : [];
|
|
544
|
+
}
|
|
576
545
|
});
|
|
577
546
|
}
|
|
578
547
|
/** Detects the wildcard allowlist marker after Codex tool-name normalization. */
|
|
@@ -645,17 +614,17 @@ var CodexNativeToolLifecycleProjector = class {
|
|
|
645
614
|
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
646
615
|
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
647
616
|
if (isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications += 1;
|
|
648
|
-
else if (notification.method === "turn/completed" &&
|
|
617
|
+
else if (notification.method === "turn/completed" && readCodexTurnCompletedNotification(params)?.turn.id === this.turnId) this.turnCompleted = true;
|
|
649
618
|
}
|
|
650
619
|
handleNotification(notification) {
|
|
651
620
|
const params = isJsonObject(notification.params) ? notification.params : void 0;
|
|
652
621
|
if (!params || !isCodexNotificationForTurn(params, this.threadId, this.turnId)) return;
|
|
653
622
|
if (this.pendingMcpNotifications > 0 && isMcpToolCallItemNotification(notification.method, params)) this.pendingMcpNotifications -= 1;
|
|
654
623
|
if (notification.method === "turn/completed") {
|
|
655
|
-
const turn =
|
|
624
|
+
const turn = readCodexTurnCompletedNotification(params)?.turn;
|
|
656
625
|
if (!turn || turn.id !== this.turnId) return;
|
|
657
626
|
this.turnCompleted = true;
|
|
658
|
-
for (const item of turn.items
|
|
627
|
+
for (const item of turn.items) this.recordSnapshotItem(item);
|
|
659
628
|
return;
|
|
660
629
|
}
|
|
661
630
|
if (notification.method === "rawResponseItem/completed") {
|
|
@@ -899,6 +868,22 @@ const ATTACHMENT_MEDIA_KEYS = [
|
|
|
899
868
|
"fileUrl",
|
|
900
869
|
"url"
|
|
901
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
|
+
}
|
|
902
887
|
/** Reads actual remote bytes with a cap enforced by Codex before transport. */
|
|
903
888
|
async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
904
889
|
if (!Number.isSafeInteger(params.maxBytes) || params.maxBytes < 0) throw new Error("Codex remote workspace upload requires a valid media byte limit.");
|
|
@@ -907,10 +892,10 @@ async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
|
907
892
|
let offset = 0;
|
|
908
893
|
let expectedSize;
|
|
909
894
|
let expectedRevision;
|
|
910
|
-
const startedAt =
|
|
895
|
+
const startedAt = performance.now();
|
|
911
896
|
do {
|
|
912
897
|
params.signal?.throwIfAborted();
|
|
913
|
-
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));
|
|
914
899
|
if (timeoutMs !== void 0 && timeoutMs <= 0) throw new Error("Codex remote workspace file transfer timed out.");
|
|
915
900
|
let response;
|
|
916
901
|
try {
|
|
@@ -971,7 +956,11 @@ async function readBoundedCodexRemoteWorkspaceFile(params) {
|
|
|
971
956
|
/** Stages authoritative bounded remote bytes into immutable Gateway-owned media. */
|
|
972
957
|
async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
973
958
|
const { localWorkspaceRoot, remoteWorkspaceRoot } = params;
|
|
974
|
-
|
|
959
|
+
const sourcePathsByStagedPath = /* @__PURE__ */ new Map();
|
|
960
|
+
if (!localWorkspaceRoot || !remoteWorkspaceRoot) return {
|
|
961
|
+
args: params.args,
|
|
962
|
+
sourcePathsByStagedPath
|
|
963
|
+
};
|
|
975
964
|
const remotePathsByLocalPath = /* @__PURE__ */ new Map();
|
|
976
965
|
const gatewayManagedPaths = /* @__PURE__ */ new Set();
|
|
977
966
|
const gatewayMediaRoot = getMediaDir();
|
|
@@ -990,11 +979,18 @@ async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
|
990
979
|
});
|
|
991
980
|
if (value.trim() && !isCodexPassThroughMediaSource(value)) {
|
|
992
981
|
attachmentEntries += 1;
|
|
993
|
-
|
|
982
|
+
const remotePath = mapCodexAppServerRemoteWorkspacePath({
|
|
994
983
|
value: mapped,
|
|
995
984
|
localWorkspaceRoot,
|
|
996
985
|
remoteWorkspaceRoot
|
|
997
|
-
})
|
|
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
|
+
});
|
|
998
994
|
}
|
|
999
995
|
return mapped;
|
|
1000
996
|
};
|
|
@@ -1031,18 +1027,21 @@ async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
|
1031
1027
|
}
|
|
1032
1028
|
if (attachmentEntries > REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS) throw new Error(`Codex remote workspace upload exceeds the ${REMOTE_WORKSPACE_MEDIA_MAX_ATTACHMENTS}-attachment limit.`);
|
|
1033
1029
|
for (const managedPath of gatewayManagedPaths) await assertGatewayManagedMediaPath(managedPath, gatewayMediaRoot);
|
|
1034
|
-
if (remotePathsByLocalPath.size === 0) return
|
|
1030
|
+
if (remotePathsByLocalPath.size === 0) return {
|
|
1031
|
+
args: mappedArgs,
|
|
1032
|
+
sourcePathsByStagedPath
|
|
1033
|
+
};
|
|
1035
1034
|
const readRemoteFile = params.readRemoteFile;
|
|
1036
1035
|
if (!readRemoteFile) throw new Error("Codex remote workspace file transfer requires an active app-server client.");
|
|
1037
1036
|
const maxBytes = params.maxBytes ?? REMOTE_WORKSPACE_MEDIA_MAX_BYTES;
|
|
1038
1037
|
const timeoutMs = params.timeoutMs ?? REMOTE_WORKSPACE_MEDIA_TIMEOUT_MS;
|
|
1039
|
-
const deadline =
|
|
1038
|
+
const deadline = performance.now() + timeoutMs;
|
|
1040
1039
|
const stagedPaths = /* @__PURE__ */ new Map();
|
|
1041
1040
|
let totalBytes = 0;
|
|
1042
|
-
for (const [localPath, remotePath] of remotePathsByLocalPath) {
|
|
1041
|
+
for (const [localPath, { remotePath, sourcePaths }] of remotePathsByLocalPath) {
|
|
1043
1042
|
params.signal?.throwIfAborted();
|
|
1044
1043
|
const remainingBytes = maxBytes - totalBytes;
|
|
1045
|
-
const remainingMs = deadline -
|
|
1044
|
+
const remainingMs = Math.floor(deadline - performance.now());
|
|
1046
1045
|
if (remainingMs <= 0) throw new Error("Codex remote workspace attachment batch timed out.");
|
|
1047
1046
|
const response = await readRemoteFile({
|
|
1048
1047
|
path: remotePath,
|
|
@@ -1058,8 +1057,15 @@ async function prepareCodexRemoteWorkspaceMessageMedia(params) {
|
|
|
1058
1057
|
totalBytes += remoteBuffer.byteLength;
|
|
1059
1058
|
const saved = await saveMediaBuffer(remoteBuffer, void 0, "outbound", maxBytes, path.basename(remotePath));
|
|
1060
1059
|
stagedPaths.set(localPath, saved.path);
|
|
1060
|
+
sourcePathsByStagedPath.set(normalizeMediaReferenceForComparison(saved.path), [...sourcePaths]);
|
|
1061
1061
|
}
|
|
1062
|
-
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)) ?? []]))];
|
|
1063
1069
|
}
|
|
1064
1070
|
async function assertGatewayManagedMediaPath(value, mediaRoot) {
|
|
1065
1071
|
const opened = await (await root(mediaRoot, { symlinks: "reject" })).open(path.relative(mediaRoot, value), { symlinks: "reject" });
|
|
@@ -1091,6 +1097,209 @@ function mapMessageMediaValues(args, mapValue) {
|
|
|
1091
1097
|
return mapped;
|
|
1092
1098
|
}
|
|
1093
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
|
|
1094
1303
|
//#region extensions/codex/src/app-server/sandbox-exec-server-node-relay.ts
|
|
1095
1304
|
const CODEX_NODE_EXEC_SERVER_MAX_MESSAGE_BYTES$1 = 67108864;
|
|
1096
1305
|
const CODEX_NODE_EXEC_SERVER_MAX_FAILURE_DETAIL_CHARS = 240;
|
|
@@ -1855,32 +2064,50 @@ async function readFile(execServer, params) {
|
|
|
1855
2064
|
async function writeFile(execServer, params) {
|
|
1856
2065
|
const record = requireObject(params, "fs/writeFile params");
|
|
1857
2066
|
const filePath = resolveExecServerPath(requireString(record.path, "path"), "write path");
|
|
2067
|
+
const fsBridge = execServer.fsBridge;
|
|
2068
|
+
const canonicalDestination = await fsBridge.resolvePinnedMutationTarget?.({
|
|
2069
|
+
filePath,
|
|
2070
|
+
action: "write"
|
|
2071
|
+
});
|
|
1858
2072
|
assertFsSandboxAccess(execServer, record, [{
|
|
1859
2073
|
path: filePath,
|
|
1860
2074
|
access: "write"
|
|
1861
|
-
}
|
|
1862
|
-
|
|
2075
|
+
}, ...canonicalDestination ? [{
|
|
2076
|
+
path: canonicalDestination.policyPath,
|
|
2077
|
+
access: "write"
|
|
2078
|
+
}] : []]);
|
|
1863
2079
|
if ((await fsBridge.stat({ filePath: posix.dirname(filePath) }))?.type !== "directory") throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "parent directory not found");
|
|
1864
2080
|
await fsBridge.writeFile({
|
|
1865
2081
|
filePath,
|
|
1866
2082
|
data: Buffer.from(requireBase64String(record.dataBase64, "dataBase64"), "base64"),
|
|
1867
|
-
mkdir: false
|
|
2083
|
+
mkdir: false,
|
|
2084
|
+
pinnedPath: canonicalDestination?.pinnedPath
|
|
1868
2085
|
});
|
|
1869
2086
|
}
|
|
1870
2087
|
/** Creates a sandbox directory, respecting recursive and parent-directory semantics. */
|
|
1871
2088
|
async function createDirectory(execServer, params) {
|
|
1872
2089
|
const record = requireObject(params, "fs/createDirectory params");
|
|
1873
2090
|
const filePath = resolveExecServerPath(requireString(record.path, "path"), "create-directory path");
|
|
2091
|
+
const fsBridge = execServer.fsBridge;
|
|
2092
|
+
const canonicalDestination = await fsBridge.resolvePinnedMutationTarget?.({
|
|
2093
|
+
filePath,
|
|
2094
|
+
action: "mkdir"
|
|
2095
|
+
});
|
|
1874
2096
|
assertFsSandboxAccess(execServer, record, [{
|
|
1875
2097
|
path: filePath,
|
|
1876
2098
|
access: "write"
|
|
1877
|
-
}
|
|
1878
|
-
|
|
2099
|
+
}, ...canonicalDestination ? [{
|
|
2100
|
+
path: canonicalDestination.policyPath,
|
|
2101
|
+
access: "write"
|
|
2102
|
+
}] : []]);
|
|
1879
2103
|
if (record.recursive === false) {
|
|
1880
2104
|
const parentPath = posix.dirname(filePath);
|
|
1881
2105
|
if ((await fsBridge.stat({ filePath: parentPath }))?.type !== "directory") throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "parent directory not found");
|
|
1882
2106
|
}
|
|
1883
|
-
await fsBridge.mkdirp({
|
|
2107
|
+
await fsBridge.mkdirp({
|
|
2108
|
+
filePath,
|
|
2109
|
+
pinnedPath: canonicalDestination?.pinnedPath
|
|
2110
|
+
});
|
|
1884
2111
|
}
|
|
1885
2112
|
/** Returns normalized metadata for a sandbox path. */
|
|
1886
2113
|
async function getMetadata(execServer, params) {
|
|
@@ -1929,15 +2156,26 @@ async function removePath(execServer, params) {
|
|
|
1929
2156
|
const record = requireObject(params, "fs/remove params");
|
|
1930
2157
|
const filePath = resolveExecServerPath(requireString(record.path, "path"), "remove path");
|
|
1931
2158
|
const fsSandboxPolicy = resolveFsSandboxPolicy(execServer, record);
|
|
2159
|
+
const canonicalDestination = await execServer.fsBridge.resolvePinnedMutationTarget?.({
|
|
2160
|
+
filePath,
|
|
2161
|
+
action: "remove"
|
|
2162
|
+
});
|
|
1932
2163
|
assertResolvedFsSandboxAccess(fsSandboxPolicy, [{
|
|
1933
2164
|
path: filePath,
|
|
1934
2165
|
access: "write"
|
|
1935
|
-
}
|
|
1936
|
-
|
|
2166
|
+
}, ...canonicalDestination ? [{
|
|
2167
|
+
path: canonicalDestination.policyPath,
|
|
2168
|
+
access: "write"
|
|
2169
|
+
}] : []]);
|
|
2170
|
+
if (record.recursive !== false) {
|
|
2171
|
+
assertNoReadOnlyDescendant(fsSandboxPolicy, filePath, "remove");
|
|
2172
|
+
if (canonicalDestination) assertNoReadOnlyDescendant(fsSandboxPolicy, canonicalDestination.policyPath, "remove");
|
|
2173
|
+
}
|
|
1937
2174
|
await execServer.fsBridge.remove({
|
|
1938
2175
|
filePath,
|
|
1939
2176
|
recursive: record.recursive !== false,
|
|
1940
|
-
force: record.force !== false
|
|
2177
|
+
force: record.force !== false,
|
|
2178
|
+
pinnedPath: canonicalDestination?.pinnedPath
|
|
1941
2179
|
});
|
|
1942
2180
|
}
|
|
1943
2181
|
/** Copies sandbox files or recursive directories while enforcing source and destination policy. */
|
|
@@ -1971,10 +2209,25 @@ async function copySandboxPath(execServer, params) {
|
|
|
1971
2209
|
}]);
|
|
1972
2210
|
const sourceStat = await fsBridge.stat({ filePath: params.sourcePath });
|
|
1973
2211
|
if (!sourceStat) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "file not found");
|
|
1974
|
-
|
|
2212
|
+
const canonicalDestination = await fsBridge.resolvePinnedMutationTarget?.({
|
|
2213
|
+
filePath: params.destinationPath,
|
|
2214
|
+
action: sourceStat.type === "directory" ? "mkdir" : "copy-destination"
|
|
2215
|
+
});
|
|
2216
|
+
const canonicalPolicyEntries = canonicalDestination ? [{
|
|
2217
|
+
path: canonicalDestination.policyPath,
|
|
2218
|
+
access: "write"
|
|
2219
|
+
}] : [];
|
|
2220
|
+
assertResolvedFsSandboxAccess(params.fsSandboxPolicy, canonicalPolicyEntries);
|
|
2221
|
+
if (sourceStat.type === "directory") {
|
|
1975
2222
|
if (!params.recursive) throw new Error(`Cannot copy directory without recursive=true: ${params.sourcePath}`);
|
|
1976
|
-
if (pathContains(normalizeSandboxAbsolutePath(
|
|
1977
|
-
|
|
2223
|
+
if (pathContains(normalizeSandboxAbsolutePath((await fsBridge.resolvePinnedMutationTarget?.({
|
|
2224
|
+
filePath: params.sourcePath,
|
|
2225
|
+
action: "mkdir"
|
|
2226
|
+
}))?.policyPath ?? params.sourcePath, "copy source path"), normalizeSandboxAbsolutePath(canonicalDestination?.policyPath ?? params.destinationPath, "copy destination path"))) throw new Error("Cannot recursively copy a directory into itself.");
|
|
2227
|
+
await fsBridge.mkdirp({
|
|
2228
|
+
filePath: params.destinationPath,
|
|
2229
|
+
pinnedPath: canonicalDestination?.pinnedPath
|
|
2230
|
+
});
|
|
1978
2231
|
for (const entry of await listDirectoryEntries(execServer, params.sourcePath, params.fsSandboxPolicy)) {
|
|
1979
2232
|
if (!entry.isDirectory && !entry.isFile) throw new Error(`Cannot copy unsupported filesystem entry: ${entry.fileName}`);
|
|
1980
2233
|
await copySandboxPath(execServer, {
|
|
@@ -1990,7 +2243,8 @@ async function copySandboxPath(execServer, params) {
|
|
|
1990
2243
|
await fsBridge.copyFile({
|
|
1991
2244
|
sourcePath: params.sourcePath,
|
|
1992
2245
|
destinationPath: params.destinationPath,
|
|
1993
|
-
mkdir: true
|
|
2246
|
+
mkdir: true,
|
|
2247
|
+
pinnedPath: canonicalDestination?.pinnedPath
|
|
1994
2248
|
});
|
|
1995
2249
|
return;
|
|
1996
2250
|
}
|
|
@@ -2002,7 +2256,8 @@ async function copySandboxPath(execServer, params) {
|
|
|
2002
2256
|
await fsBridge.writeFile({
|
|
2003
2257
|
filePath: params.destinationPath,
|
|
2004
2258
|
data,
|
|
2005
|
-
mkdir: true
|
|
2259
|
+
mkdir: true,
|
|
2260
|
+
pinnedPath: canonicalDestination?.pinnedPath
|
|
2006
2261
|
});
|
|
2007
2262
|
}
|
|
2008
2263
|
function assertSandboxFileReadWithinLimit(stat) {
|
|
@@ -2023,7 +2278,7 @@ function metadataResponse(stat) {
|
|
|
2023
2278
|
/** Owns one sandbox subprocess tree through close, reaping, and backend finalization. */
|
|
2024
2279
|
const SANDBOX_CHILD_TERM_GRACE_MS = 1e3;
|
|
2025
2280
|
const SANDBOX_CHILD_REAP_TIMEOUT_MS = 4500;
|
|
2026
|
-
const
|
|
2281
|
+
const SANDBOX_CHILD_INTERRUPT_POLL_MS = 50;
|
|
2027
2282
|
async function spawnSandboxChild(params) {
|
|
2028
2283
|
const [command, ...args] = params.argv;
|
|
2029
2284
|
const finalize = async (status, exitCode) => await params.finalizeExec?.({
|
|
@@ -2037,54 +2292,88 @@ async function spawnSandboxChild(params) {
|
|
|
2037
2292
|
throw new Error("OpenClaw sandbox exec spec did not provide a command.");
|
|
2038
2293
|
}
|
|
2039
2294
|
let child;
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
detached: process.platform !== "win32",
|
|
2043
|
-
env: params.env,
|
|
2044
|
-
stdio: [
|
|
2045
|
-
"pipe",
|
|
2046
|
-
"pipe",
|
|
2047
|
-
"pipe"
|
|
2048
|
-
]
|
|
2049
|
-
});
|
|
2050
|
-
} catch (error) {
|
|
2051
|
-
await finalize("failed", null).catch(params.onFinalizeError);
|
|
2052
|
-
throw error;
|
|
2053
|
-
}
|
|
2295
|
+
let pty;
|
|
2296
|
+
let exitOutcome;
|
|
2054
2297
|
let outcome;
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
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,
|
|
2058
2315
|
signal
|
|
2059
|
-
})
|
|
2060
|
-
}
|
|
2061
|
-
let
|
|
2316
|
+
});
|
|
2317
|
+
};
|
|
2318
|
+
let startFailed = false;
|
|
2319
|
+
let startupPending = true;
|
|
2320
|
+
let terminationRequested = false;
|
|
2062
2321
|
let terminationCleanup;
|
|
2063
2322
|
let terminationError;
|
|
2064
|
-
|
|
2323
|
+
let settlementStarted = false;
|
|
2324
|
+
const interruptions = /* @__PURE__ */ new Set();
|
|
2325
|
+
const localSignals = [];
|
|
2326
|
+
const settled = closed.promise.then(async (result) => {
|
|
2327
|
+
settlementStarted = true;
|
|
2065
2328
|
await terminationCleanup;
|
|
2066
|
-
|
|
2067
|
-
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);
|
|
2068
2333
|
return result;
|
|
2069
2334
|
});
|
|
2070
2335
|
settled.catch(params.onFinalizeError);
|
|
2336
|
+
const assertCurrent = () => {
|
|
2337
|
+
params.assertCurrent?.();
|
|
2338
|
+
if (terminationRequested) throw new Error("Sandbox child process start cancelled");
|
|
2339
|
+
};
|
|
2071
2340
|
let terminationPromise;
|
|
2072
2341
|
const owner = {
|
|
2073
|
-
|
|
2342
|
+
assertCurrent,
|
|
2343
|
+
exited: exited.promise,
|
|
2344
|
+
closed: closed.promise,
|
|
2074
2345
|
settled,
|
|
2075
2346
|
terminate: () => terminationPromise ??= (async () => {
|
|
2076
|
-
|
|
2347
|
+
terminationRequested = true;
|
|
2348
|
+
if (startupPending) await ready.promise;
|
|
2349
|
+
if (startFailed || settlementStarted) return await settled;
|
|
2350
|
+
child?.stdin?.destroy();
|
|
2077
2351
|
terminationCleanup = params.terminateRemote?.().catch((error) => {
|
|
2078
2352
|
terminationError = error instanceof Error ? error : new Error(String(error));
|
|
2079
2353
|
});
|
|
2080
2354
|
await terminationCleanup;
|
|
2081
2355
|
if (!outcome) {
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
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.`);
|
|
2088
2377
|
}
|
|
2089
2378
|
const result = await settled;
|
|
2090
2379
|
if (terminationError) throw terminationError;
|
|
@@ -2093,46 +2382,85 @@ async function spawnSandboxChild(params) {
|
|
|
2093
2382
|
};
|
|
2094
2383
|
params.owners.add(owner);
|
|
2095
2384
|
settled.then(() => params.owners.delete(owner), () => params.owners.delete(owner));
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
allowFailure: true,
|
|
2110
|
-
signal: AbortSignal.timeout(SANDBOX_CHILD_REAP_TIMEOUT_MS)
|
|
2111
|
-
});
|
|
2112
|
-
if (result.code !== 0) {
|
|
2113
|
-
const detail = result.stderr.toString("utf8").trim() || result.stdout.toString("utf8").trim();
|
|
2114
|
-
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]);
|
|
2115
2398
|
}
|
|
2399
|
+
})();
|
|
2400
|
+
interruptions.add(interruption);
|
|
2401
|
+
try {
|
|
2402
|
+
await interruption;
|
|
2403
|
+
} finally {
|
|
2404
|
+
interruptions.delete(interruption);
|
|
2116
2405
|
}
|
|
2117
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
|
+
}
|
|
2118
2463
|
}
|
|
2119
|
-
const SANDBOX_REMOTE_TERMINATE_SCRIPT = String.raw`
|
|
2120
|
-
find_owned_pids() {
|
|
2121
|
-
for env_file in /proc/[0-9]*/environ; do
|
|
2122
|
-
if [ -r "$env_file" ] && tr '\0' '\n' < "$env_file" 2>/dev/null | grep -Fqx "$1"; then
|
|
2123
|
-
basename "$(dirname "$env_file")"
|
|
2124
|
-
fi
|
|
2125
|
-
done
|
|
2126
|
-
}
|
|
2127
|
-
owned="$(find_owned_pids "$1")"
|
|
2128
|
-
[ -z "$owned" ] || kill -TERM $owned 2>/dev/null || true
|
|
2129
|
-
sleep 1
|
|
2130
|
-
owned="$(find_owned_pids "$1")"
|
|
2131
|
-
[ -z "$owned" ] || kill -KILL $owned 2>/dev/null || true
|
|
2132
|
-
sleep 1
|
|
2133
|
-
owned="$(find_owned_pids "$1")"
|
|
2134
|
-
[ -z "$owned" ] || { echo "Sandbox process IDs survived SIGKILL: $owned" >&2; exit 1; }
|
|
2135
|
-
`.trim();
|
|
2136
2464
|
function delay(ms) {
|
|
2137
2465
|
return new Promise((resolve) => {
|
|
2138
2466
|
setTimeout(resolve, ms).unref?.();
|
|
@@ -2147,7 +2475,7 @@ function delay(ms) {
|
|
|
2147
2475
|
/** Maximum JSON-line size accepted from the streaming HTTP helper process. */
|
|
2148
2476
|
const SANDBOX_HTTP_STREAM_LINE_MAX_CHARS = 262144;
|
|
2149
2477
|
/** Handles one sandbox HTTP JSON-RPC request, optionally streaming response body deltas. */
|
|
2150
|
-
async function httpRequest(execServer, notifications, params) {
|
|
2478
|
+
async function httpRequest(execServer, notifications, params, operations) {
|
|
2151
2479
|
const record = requireObject(params, "http/request params");
|
|
2152
2480
|
const requestId = requireString(record.requestId, "requestId");
|
|
2153
2481
|
const url = requireString(record.url, "url");
|
|
@@ -2163,11 +2491,14 @@ async function httpRequest(execServer, notifications, params) {
|
|
|
2163
2491
|
redirectPolicy,
|
|
2164
2492
|
streamResponse: record.streamResponse === true
|
|
2165
2493
|
};
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
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);
|
|
2170
2500
|
});
|
|
2501
|
+
return await response.promise;
|
|
2171
2502
|
}
|
|
2172
2503
|
function assertSandboxHttpRequestTargetAllowed(url) {
|
|
2173
2504
|
let parsed;
|
|
@@ -2179,50 +2510,54 @@ function assertSandboxHttpRequestTargetAllowed(url) {
|
|
|
2179
2510
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new SsrFBlockedError(`Blocked non-HTTP(S) protocol in sandbox http/request: ${parsed.protocol}`);
|
|
2180
2511
|
if (isBlockedHostnameOrIp(parsed.hostname)) throw new SsrFBlockedError(`Blocked hostname or private/internal IP in sandbox http/request: ${parsed.hostname}`);
|
|
2181
2512
|
}
|
|
2182
|
-
async function runSandboxHttpRequest(execServer, params) {
|
|
2183
|
-
const result = await execServer.backend.runShellCommand({
|
|
2184
|
-
script: SANDBOX_HTTP_REQUEST_SCRIPT,
|
|
2185
|
-
stdin: JSON.stringify(params),
|
|
2186
|
-
allowFailure: true
|
|
2187
|
-
});
|
|
2188
|
-
if (result.code !== 0) {
|
|
2189
|
-
const stderr = result.stderr.toString("utf8").trim();
|
|
2190
|
-
throw new Error(stderr || `sandbox http/request failed with code ${result.code}`);
|
|
2191
|
-
}
|
|
2192
|
-
const parsed = JSON.parse(result.stdout.toString("utf8"));
|
|
2193
|
-
if (typeof parsed.status !== "number" || !Array.isArray(parsed.headers)) throw new Error("sandbox http/request returned an invalid response envelope");
|
|
2194
|
-
return {
|
|
2195
|
-
status: parsed.status,
|
|
2196
|
-
headers: readHttpHeaders(parsed.headers),
|
|
2197
|
-
bodyBase64: typeof parsed.bodyBase64 === "string" ? parsed.bodyBase64 : ""
|
|
2198
|
-
};
|
|
2199
|
-
}
|
|
2200
|
-
async function runStreamingSandboxHttpRequest(execServer, notifications, requestId, params) {
|
|
2201
|
-
const backend = execServer.backend;
|
|
2202
|
-
const remoteExec = prepareSandboxChildExec(backend, {});
|
|
2203
|
-
const execSpec = await backend.buildExecSpec({
|
|
2204
|
-
command: SANDBOX_HTTP_REQUEST_SCRIPT,
|
|
2205
|
-
workdir: execServer.sandbox.containerWorkdir,
|
|
2206
|
-
env: remoteExec.env,
|
|
2207
|
-
usePty: false
|
|
2208
|
-
});
|
|
2513
|
+
async function runSandboxHttpRequest(execServer, notifications, requestId, params, response) {
|
|
2209
2514
|
const lifecycle = { failed: false };
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
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
|
+
}
|
|
2222
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
|
+
};
|
|
2223
2558
|
const abortOnSessionClose = () => {
|
|
2224
2559
|
lifecycle.failed = true;
|
|
2225
|
-
|
|
2560
|
+
terminate().catch((error) => {
|
|
2226
2561
|
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2227
2562
|
});
|
|
2228
2563
|
};
|
|
@@ -2230,21 +2565,38 @@ async function runStreamingSandboxHttpRequest(execServer, notifications, request
|
|
|
2230
2565
|
child.once("close", () => {
|
|
2231
2566
|
notifications.signal.removeEventListener("abort", abortOnSessionClose);
|
|
2232
2567
|
});
|
|
2233
|
-
if (notifications.signal.aborted) abortOnSessionClose();
|
|
2234
2568
|
child.stdin.on("error", (error) => {
|
|
2235
2569
|
if (error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED") return;
|
|
2236
2570
|
embeddedAgentLog.warn("codex sandbox http/request stdin write failed", { error });
|
|
2237
2571
|
});
|
|
2238
|
-
|
|
2239
|
-
return await readStreamingSandboxHttpResponse({
|
|
2572
|
+
readSandboxHttpResponse({
|
|
2240
2573
|
child,
|
|
2241
2574
|
lifecycle,
|
|
2242
|
-
|
|
2575
|
+
terminate,
|
|
2243
2576
|
requestId,
|
|
2244
|
-
notifications
|
|
2245
|
-
|
|
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
|
+
}
|
|
2246
2598
|
}
|
|
2247
|
-
function
|
|
2599
|
+
function readSandboxHttpResponse(params) {
|
|
2248
2600
|
return new Promise((resolve, reject) => {
|
|
2249
2601
|
let headerResolved = false;
|
|
2250
2602
|
let failed = false;
|
|
@@ -2252,11 +2604,21 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2252
2604
|
let lastBodySeq = 0;
|
|
2253
2605
|
let stdoutBuffer = "";
|
|
2254
2606
|
let stderr = "";
|
|
2255
|
-
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) => {
|
|
2256
2618
|
if (failed) return;
|
|
2257
2619
|
failed = true;
|
|
2258
2620
|
params.lifecycle.failed = true;
|
|
2259
|
-
params.
|
|
2621
|
+
params.terminate().catch((error) => {
|
|
2260
2622
|
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2261
2623
|
});
|
|
2262
2624
|
if (headerResolved) {
|
|
@@ -2271,9 +2633,28 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2271
2633
|
}
|
|
2272
2634
|
reject(new Error(message));
|
|
2273
2635
|
};
|
|
2274
|
-
|
|
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
|
+
}
|
|
2275
2650
|
params.child.stdout.on("data", (chunk) => {
|
|
2276
|
-
|
|
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;
|
|
2277
2658
|
let newline = stdoutBuffer.indexOf("\n");
|
|
2278
2659
|
while (newline >= 0) {
|
|
2279
2660
|
const line = stdoutBuffer.slice(0, newline).trim();
|
|
@@ -2300,15 +2681,20 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2300
2681
|
});
|
|
2301
2682
|
}
|
|
2302
2683
|
} catch (error) {
|
|
2303
|
-
fail(error instanceof Error ? error.message : String(error)
|
|
2684
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
2304
2685
|
}
|
|
2305
2686
|
newline = stdoutBuffer.indexOf("\n");
|
|
2306
2687
|
}
|
|
2307
|
-
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`);
|
|
2308
2689
|
});
|
|
2309
|
-
params.child.stderr.setEncoding("utf8");
|
|
2310
2690
|
params.child.stderr.on("data", (chunk) => {
|
|
2311
|
-
|
|
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);
|
|
2312
2698
|
});
|
|
2313
2699
|
params.child.once("error", (error) => {
|
|
2314
2700
|
childFailure ??= error.message;
|
|
@@ -2318,17 +2704,32 @@ function readStreamingSandboxHttpResponse(params) {
|
|
|
2318
2704
|
const exitCode = code ?? 1;
|
|
2319
2705
|
if (failed) return;
|
|
2320
2706
|
if (childFailure) {
|
|
2321
|
-
fail(childFailure
|
|
2707
|
+
fail(childFailure);
|
|
2322
2708
|
return;
|
|
2323
2709
|
}
|
|
2324
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
|
+
}
|
|
2325
2725
|
if (!headerResolved) {
|
|
2326
2726
|
params.lifecycle.failed = true;
|
|
2327
2727
|
reject(/* @__PURE__ */ new Error("sandbox http/request exited before returning headers"));
|
|
2328
2728
|
}
|
|
2329
2729
|
return;
|
|
2330
2730
|
}
|
|
2331
|
-
|
|
2731
|
+
if (!params.streamResponse) stderr = Buffer.concat(buffered.stderr.chunks).toString("utf8");
|
|
2732
|
+
fail(stderr.trim() || `sandbox http/request failed with code ${exitCode}`);
|
|
2332
2733
|
});
|
|
2333
2734
|
});
|
|
2334
2735
|
}
|
|
@@ -2576,7 +2977,7 @@ const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
2576
2977
|
const RETAINED_PROCESS_OUTPUT_BYTES = 1048576;
|
|
2577
2978
|
const CLOSED_PROCESS_EVICTION_MS = 6e4;
|
|
2578
2979
|
/** Starts a sandbox-backed process and registers it in the connection-local process table. */
|
|
2579
|
-
async function startProcess(execServer, processes, notify, params) {
|
|
2980
|
+
async function startProcess(execServer, processes, notify, params, processAuthority) {
|
|
2580
2981
|
const record = requireObject(params, "process/start params");
|
|
2581
2982
|
const processId = requireString(record.processId, "processId");
|
|
2582
2983
|
if (processes.has(processId)) throw new Error(`process already exists: ${processId}`);
|
|
@@ -2608,18 +3009,39 @@ async function startProcess(execServer, processes, notify, params) {
|
|
|
2608
3009
|
managed.evictionTimer.unref?.();
|
|
2609
3010
|
}
|
|
2610
3011
|
};
|
|
3012
|
+
const source = processAuthority?.claim(record.metadata, async () => {
|
|
3013
|
+
await terminateManagedProcess(managed);
|
|
3014
|
+
});
|
|
2611
3015
|
processes.set(processId, managed);
|
|
2612
3016
|
const startPromise = runProcess(execServer, managed, {
|
|
2613
3017
|
argv,
|
|
2614
3018
|
cwd,
|
|
2615
|
-
env
|
|
3019
|
+
env,
|
|
3020
|
+
source
|
|
2616
3021
|
});
|
|
2617
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
|
+
});
|
|
2618
3034
|
try {
|
|
2619
3035
|
await startPromise;
|
|
2620
3036
|
} catch (error) {
|
|
2621
|
-
processes.delete(processId);
|
|
2622
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);
|
|
2623
3045
|
managed.exitCode = null;
|
|
2624
3046
|
managed.exited = true;
|
|
2625
3047
|
managed.closed = true;
|
|
@@ -2643,13 +3065,14 @@ function assertSupportedProcessSandbox(execServer, record) {
|
|
|
2643
3065
|
}
|
|
2644
3066
|
async function runProcess(execServer, managed, params) {
|
|
2645
3067
|
const backend = execServer.backend;
|
|
3068
|
+
params.source?.assertAdmission();
|
|
2646
3069
|
throwIfProcessStartCancelled(managed);
|
|
2647
|
-
const remoteExec =
|
|
3070
|
+
const remoteExec = prepareSandboxProcessCleanup(backend, params.env);
|
|
2648
3071
|
const execSpec = await backend.buildExecSpec({
|
|
2649
|
-
command: buildRemoteCommand(params.argv)
|
|
3072
|
+
command: `exec ${buildRemoteCommand(params.argv)}`,
|
|
2650
3073
|
workdir: params.cwd,
|
|
2651
3074
|
env: remoteExec.env,
|
|
2652
|
-
usePty:
|
|
3075
|
+
usePty: managed.tty
|
|
2653
3076
|
});
|
|
2654
3077
|
if (managed.terminationRequested) {
|
|
2655
3078
|
await backend.finalizeExec?.({
|
|
@@ -2660,9 +3083,18 @@ async function runProcess(execServer, managed, params) {
|
|
|
2660
3083
|
});
|
|
2661
3084
|
throw new Error("process start cancelled");
|
|
2662
3085
|
}
|
|
3086
|
+
let spawned = false;
|
|
2663
3087
|
const owner = await spawnSandboxChild({
|
|
2664
3088
|
argv: execSpec.argv,
|
|
2665
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
|
+
},
|
|
2666
3098
|
finalizeExec: backend.finalizeExec,
|
|
2667
3099
|
finalizeToken: execSpec.finalizeToken,
|
|
2668
3100
|
finalizeStatus: () => managed.failure ? "failed" : "completed",
|
|
@@ -2675,19 +3107,29 @@ async function runProcess(execServer, managed, params) {
|
|
|
2675
3107
|
});
|
|
2676
3108
|
},
|
|
2677
3109
|
owners: execServer.children,
|
|
2678
|
-
terminateRemote: remoteExec.terminate
|
|
3110
|
+
terminateRemote: remoteExec.terminate,
|
|
3111
|
+
interruptRemote: remoteExec.interrupt
|
|
2679
3112
|
});
|
|
3113
|
+
spawned = true;
|
|
2680
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
|
+
}
|
|
2681
3121
|
const child = owner.process;
|
|
2682
|
-
child.stdout.on("data", (chunk) => appendProcessChunk(managed,
|
|
3122
|
+
child.stdout.on("data", (chunk) => appendProcessChunk(managed, "stdout", chunk));
|
|
2683
3123
|
child.stderr.on("data", (chunk) => appendProcessChunk(managed, "stderr", chunk));
|
|
2684
3124
|
child.once("error", (error) => {
|
|
2685
3125
|
managed.failure ??= error.message;
|
|
2686
3126
|
notifyProcessWaiters(managed);
|
|
2687
3127
|
});
|
|
2688
|
-
child.
|
|
2689
|
-
|
|
3128
|
+
child.stdin.on("error", (error) => {
|
|
3129
|
+
managed.failure ??= error.message;
|
|
3130
|
+
notifyProcessWaiters(managed);
|
|
2690
3131
|
});
|
|
3132
|
+
owner.assertCurrent();
|
|
2691
3133
|
if (!managed.tty && !managed.pipeStdin) child.stdin.end();
|
|
2692
3134
|
}
|
|
2693
3135
|
function throwIfProcessStartCancelled(managed) {
|
|
@@ -2716,7 +3158,7 @@ function appendProcessChunk(managed, stream, data) {
|
|
|
2716
3158
|
});
|
|
2717
3159
|
notifyProcessWaiters(managed);
|
|
2718
3160
|
}
|
|
2719
|
-
function
|
|
3161
|
+
function emitProcessExited(managed, exitCode) {
|
|
2720
3162
|
if (!managed.exited) {
|
|
2721
3163
|
const exitSeq = managed.nextSeq;
|
|
2722
3164
|
managed.nextSeq += 1;
|
|
@@ -2725,9 +3167,14 @@ function emitProcessClosed(managed, exitCode) {
|
|
|
2725
3167
|
if (exitCode !== null) managed.emitNotification("process/exited", {
|
|
2726
3168
|
processId: managed.processId,
|
|
2727
3169
|
seq: exitSeq,
|
|
2728
|
-
exitCode
|
|
3170
|
+
exitCode,
|
|
3171
|
+
sandboxDenied: false
|
|
2729
3172
|
});
|
|
2730
3173
|
}
|
|
3174
|
+
notifyProcessWaiters(managed);
|
|
3175
|
+
}
|
|
3176
|
+
function emitProcessClosed(managed, exitCode) {
|
|
3177
|
+
emitProcessExited(managed, exitCode);
|
|
2731
3178
|
if (!managed.closed) {
|
|
2732
3179
|
const closeSeq = managed.nextSeq;
|
|
2733
3180
|
managed.nextSeq += 1;
|
|
@@ -2759,7 +3206,7 @@ async function readProcess(processes, params) {
|
|
|
2759
3206
|
const managed = requireProcess(processes, requireString(record.processId, "processId"));
|
|
2760
3207
|
const afterSeq = typeof record.afterSeq === "number" ? record.afterSeq : 0;
|
|
2761
3208
|
const waitMs = typeof record.waitMs === "number" && record.waitMs > 0 ? record.waitMs : 0;
|
|
2762
|
-
if (!managed.
|
|
3209
|
+
if (!managed.closed && managed.nextSeq - 1 <= afterSeq && waitMs > 0) await waitForProcessUpdate(managed, waitMs);
|
|
2763
3210
|
const chunks = limitProcessChunks(managed.chunks.filter((chunk) => chunk.seq > afterSeq), typeof record.maxBytes === "number" && record.maxBytes > 0 ? record.maxBytes : void 0);
|
|
2764
3211
|
const lastChunk = chunks.at(-1);
|
|
2765
3212
|
return {
|
|
@@ -2778,15 +3225,37 @@ function writeProcess(processes, params) {
|
|
|
2778
3225
|
const managed = processes.get(processId);
|
|
2779
3226
|
if (!managed) return { status: "unknownProcess" };
|
|
2780
3227
|
const chunk = Buffer.from(requireString(record.chunk, "chunk"), "base64");
|
|
2781
|
-
if (!managed.tty && !managed.pipeStdin || managed.closed || !managed.child
|
|
2782
|
-
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
|
+
}
|
|
2783
3237
|
return { status: "accepted" };
|
|
2784
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
|
+
}
|
|
2785
3251
|
/** Requests process termination and reports whether it was running at call time. */
|
|
2786
3252
|
async function terminateProcess(processes, params) {
|
|
2787
3253
|
const processId = requireString(requireObject(params, "process/terminate params").processId, "processId");
|
|
2788
3254
|
const managed = processes.get(processId);
|
|
2789
3255
|
if (!managed) return { running: false };
|
|
3256
|
+
return await terminateManagedProcess(managed);
|
|
3257
|
+
}
|
|
3258
|
+
async function terminateManagedProcess(managed) {
|
|
2790
3259
|
const running = !managed.exited;
|
|
2791
3260
|
managed.terminationRequested = true;
|
|
2792
3261
|
await managed.startPromise?.catch(() => void 0);
|
|
@@ -2810,9 +3279,6 @@ function notifyProcessWaiters(managed) {
|
|
|
2810
3279
|
managed.waiters = [];
|
|
2811
3280
|
for (const waiter of waiters) waiter();
|
|
2812
3281
|
}
|
|
2813
|
-
function hasChunksAtOrAfter(managed, afterSeq) {
|
|
2814
|
-
return managed.chunks.some((chunk) => chunk.seq > afterSeq);
|
|
2815
|
-
}
|
|
2816
3282
|
function requireProcess(processes, processId) {
|
|
2817
3283
|
const managed = processes.get(processId);
|
|
2818
3284
|
if (!managed) throw new Error(`unknown process: ${processId}`);
|
|
@@ -2861,11 +3327,13 @@ function readStringList(value) {
|
|
|
2861
3327
|
/** Owns the JSON-RPC protocol and resources of one sandbox execution connection. */
|
|
2862
3328
|
/** Connection-local execution state; closing it never enables session resumption. */
|
|
2863
3329
|
var CodexSandboxExecSession = class {
|
|
2864
|
-
constructor(execServer, transport) {
|
|
3330
|
+
constructor(execServer, transport, processAuthority) {
|
|
2865
3331
|
this.execServer = execServer;
|
|
2866
3332
|
this.transport = transport;
|
|
3333
|
+
this.processAuthority = processAuthority;
|
|
2867
3334
|
this.processes = /* @__PURE__ */ new Map();
|
|
2868
3335
|
this.fileReads = /* @__PURE__ */ new Map();
|
|
3336
|
+
this.httpRequests = /* @__PURE__ */ new Set();
|
|
2869
3337
|
this.closeController = new AbortController();
|
|
2870
3338
|
this.notifications = {
|
|
2871
3339
|
isOpen: transport.isOpen,
|
|
@@ -2900,7 +3368,10 @@ var CodexSandboxExecSession = class {
|
|
|
2900
3368
|
if (!this.cleanup) {
|
|
2901
3369
|
this.closeController.abort();
|
|
2902
3370
|
closeAllFileReads(this.fileReads);
|
|
2903
|
-
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
|
+
});
|
|
2904
3375
|
}
|
|
2905
3376
|
return this.cleanup;
|
|
2906
3377
|
}
|
|
@@ -2916,9 +3387,10 @@ var CodexSandboxExecSession = class {
|
|
|
2916
3387
|
capabilities: { networkProxyLaunch: false }
|
|
2917
3388
|
};
|
|
2918
3389
|
case "environment/status": return { status: "ready" };
|
|
2919
|
-
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);
|
|
2920
3391
|
case "process/read": return await readProcess(this.processes, params);
|
|
2921
3392
|
case "process/write": return writeProcess(this.processes, params);
|
|
3393
|
+
case "process/signal": return await signalProcess(this.processes, params);
|
|
2922
3394
|
case "process/terminate": return await terminateProcess(this.processes, params);
|
|
2923
3395
|
case "fs/open": return await openFile(this.execServer, this.fileReads, params);
|
|
2924
3396
|
case "fs/readBlock": return readFileBlock(this.fileReads, params);
|
|
@@ -2938,7 +3410,7 @@ var CodexSandboxExecSession = class {
|
|
|
2938
3410
|
case "fs/copy":
|
|
2939
3411
|
await copyPath(this.execServer, params);
|
|
2940
3412
|
return {};
|
|
2941
|
-
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);
|
|
2942
3414
|
default: throw new JsonRpcProtocolError(JSON_RPC_METHOD_NOT_FOUND, `Unsupported OpenClaw sandbox exec-server method: ${method}`);
|
|
2943
3415
|
}
|
|
2944
3416
|
}
|
|
@@ -2965,9 +3437,17 @@ async function ensureCodexSandboxExecServerEnvironment(params) {
|
|
|
2965
3437
|
signal: params.signal,
|
|
2966
3438
|
onExecutionDisconnect: params.onExecutionDisconnect
|
|
2967
3439
|
});
|
|
2968
|
-
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;
|
|
2969
3449
|
try {
|
|
2970
|
-
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;
|
|
2971
3451
|
await params.client.request("environment/add", {
|
|
2972
3452
|
environmentId,
|
|
2973
3453
|
execServerUrl
|
|
@@ -3139,7 +3619,8 @@ async function startOpenClawExecServer(sandbox) {
|
|
|
3139
3619
|
handleNodeConnection(execServer, socket, request);
|
|
3140
3620
|
return;
|
|
3141
3621
|
}
|
|
3142
|
-
|
|
3622
|
+
const requestPath = new URL(request.url ?? "", "ws://127.0.0.1").pathname;
|
|
3623
|
+
handleConnection(execServer, socket, execServer.processAuthorities?.get(requestPath));
|
|
3143
3624
|
});
|
|
3144
3625
|
embeddedAgentLog.info("codex sandbox exec-server started", {
|
|
3145
3626
|
environmentId,
|
|
@@ -3161,7 +3642,8 @@ function buildEnvironmentId(sandbox) {
|
|
|
3161
3642
|
return `openclaw-sandbox-${createHash("sha256").update(sandbox.runtimeId).digest("hex").slice(0, 16)}`;
|
|
3162
3643
|
}
|
|
3163
3644
|
function isAuthorizedExecServerRequest(execServer, request) {
|
|
3164
|
-
|
|
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;
|
|
3165
3647
|
}
|
|
3166
3648
|
function readCodexPlacementNodeId(sandbox) {
|
|
3167
3649
|
if (!("placementExecutionMode" in sandbox) || sandbox.placementExecutionMode !== "remote-exec" || !("placementNodeId" in sandbox) || typeof sandbox.placementNodeId !== "string" || !sandbox.placementNodeId) return;
|
|
@@ -3214,11 +3696,11 @@ function handleClosedCodexNodeExecServerLease(execServer, lease, result) {
|
|
|
3214
3696
|
closeCodexNodeExecServerLease(execServer, lease);
|
|
3215
3697
|
}
|
|
3216
3698
|
}
|
|
3217
|
-
function handleConnection(execServer, socket) {
|
|
3699
|
+
function handleConnection(execServer, socket, processAuthority) {
|
|
3218
3700
|
const session = new CodexSandboxExecSession(execServer, {
|
|
3219
3701
|
isOpen: () => socket.readyState === socket.OPEN,
|
|
3220
3702
|
send: (message) => socket.send(JSON.stringify(message))
|
|
3221
|
-
});
|
|
3703
|
+
}, processAuthority);
|
|
3222
3704
|
socket.on("message", (data) => {
|
|
3223
3705
|
handleMessage(session, data).catch((error) => {
|
|
3224
3706
|
embeddedAgentLog.warn("codex sandbox exec-server message failed", { error });
|
|
@@ -3241,6 +3723,73 @@ async function handleMessage(session, data) {
|
|
|
3241
3723
|
await session.handleRequest(parseRequest(buffer.toString("utf8")));
|
|
3242
3724
|
}
|
|
3243
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
|
|
3244
3793
|
//#region extensions/codex/src/app-server/approval-bridge.ts
|
|
3245
3794
|
/**
|
|
3246
3795
|
* Bridges Codex app-server approval requests into OpenClaw policy hooks and
|
|
@@ -3266,12 +3815,14 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3266
3815
|
paramsForRun: params.paramsForRun
|
|
3267
3816
|
});
|
|
3268
3817
|
if (params.signal?.aborted) {
|
|
3818
|
+
if (params.signal.reason instanceof CodexServerRequestResolvedError) return;
|
|
3269
3819
|
recordNativeToolFailureDisposition(params, context, "cancelled");
|
|
3270
3820
|
return buildApprovalResponse(params.method, context.requestParams, "cancelled");
|
|
3271
3821
|
}
|
|
3272
3822
|
let revalidateMutableFileApproval;
|
|
3273
3823
|
let mutableFileApprovalRequiresOneShot = false;
|
|
3274
|
-
|
|
3824
|
+
let approvalId;
|
|
3825
|
+
const resolvePolicyApproval = async (outcome, message = approvalResolutionMessage(outcome), resolvedApprovalId) => {
|
|
3275
3826
|
let resolvedOutcome = outcome;
|
|
3276
3827
|
let resolvedMessage = message;
|
|
3277
3828
|
if (outcome !== "denied" && revalidateMutableFileApproval) {
|
|
@@ -3285,6 +3836,16 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3285
3836
|
resolvedOutcome = "approved-once";
|
|
3286
3837
|
resolvedMessage = "Codex app-server approval granted for this byte-bound command only.";
|
|
3287
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
|
+
}
|
|
3288
3849
|
params.signal?.throwIfAborted();
|
|
3289
3850
|
if (resolvedOutcome !== "denied") params.paramsForRun.hostCapabilities.assertActive();
|
|
3290
3851
|
emitApprovalEvent(params.paramsForRun, {
|
|
@@ -3292,9 +3853,9 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3292
3853
|
kind: context.kind,
|
|
3293
3854
|
status: resolvedOutcome === "denied" ? "denied" : "approved",
|
|
3294
3855
|
title: context.title,
|
|
3295
|
-
...
|
|
3296
|
-
approvalId,
|
|
3297
|
-
approvalSlug:
|
|
3856
|
+
...resolvedApprovalId ? {
|
|
3857
|
+
approvalId: resolvedApprovalId,
|
|
3858
|
+
approvalSlug: resolvedApprovalId
|
|
3298
3859
|
} : {},
|
|
3299
3860
|
...context.eventDetails,
|
|
3300
3861
|
...approvalEventScope(params.method, resolvedOutcome),
|
|
@@ -3331,22 +3892,25 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3331
3892
|
return await resolvePolicyApproval("denied", policyOutcome.reason);
|
|
3332
3893
|
}
|
|
3333
3894
|
if (policyOutcome?.outcome === "approved-once" || policyOutcome?.outcome === "approved-session") return await resolvePolicyApproval(policyOutcome.outcome);
|
|
3334
|
-
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;
|
|
3335
3902
|
const requestResult = await requestPluginApproval({
|
|
3336
3903
|
hostCapabilities: params.paramsForRun.hostCapabilities,
|
|
3337
3904
|
signal: params.signal,
|
|
3338
3905
|
title: context.title,
|
|
3339
|
-
description: context.description,
|
|
3906
|
+
description: repeatedApproval?.scope === "persistent" ? `${repeatedApproval.description}\n${context.description}` : context.description,
|
|
3340
3907
|
severity: context.severity,
|
|
3341
3908
|
toolName: context.toolName,
|
|
3342
3909
|
toolCallId: context.approvalId,
|
|
3343
|
-
allowedDecisions
|
|
3344
|
-
method: params.method,
|
|
3345
|
-
requestParams,
|
|
3346
|
-
requiresOneShot: mutableFileApprovalRequiresOneShot
|
|
3347
|
-
})
|
|
3910
|
+
allowedDecisions
|
|
3348
3911
|
});
|
|
3349
|
-
|
|
3912
|
+
approvalId = requestResult?.id;
|
|
3913
|
+
params.signal?.throwIfAborted();
|
|
3350
3914
|
if (!approvalId) {
|
|
3351
3915
|
recordNativeToolFailureDisposition(params, context, "failed");
|
|
3352
3916
|
emitApprovalEvent(params.paramsForRun, {
|
|
@@ -3375,16 +3939,16 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3375
3939
|
signal: params.signal,
|
|
3376
3940
|
hostCapabilities: params.paramsForRun.hostCapabilities
|
|
3377
3941
|
});
|
|
3378
|
-
|
|
3379
|
-
const
|
|
3942
|
+
params.signal?.throwIfAborted();
|
|
3943
|
+
const approvalTimedOut = approvalResult?.terminalReason === "timeout";
|
|
3944
|
+
const outcome = mapExecDecisionToOutcome(approvalResult?.decision);
|
|
3380
3945
|
if (approvalTimedOut) recordNativeToolFailureDisposition(params, context, "timed_out", context.approvalKind);
|
|
3381
|
-
else if (outcome === "cancelled") recordNativeToolFailureDisposition(params, context, params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : "cancelled");
|
|
3382
3946
|
else if (outcome === "unavailable") recordNativeToolFailureDisposition(params, context, "failed");
|
|
3383
3947
|
if (outcome === "approved-once" || outcome === "approved-session") return await resolvePolicyApproval(outcome, approvalResolutionMessage(outcome), approvalId);
|
|
3384
3948
|
emitApprovalEvent(params.paramsForRun, {
|
|
3385
3949
|
phase: "resolved",
|
|
3386
3950
|
kind: context.kind,
|
|
3387
|
-
status: outcome
|
|
3951
|
+
status: outcome,
|
|
3388
3952
|
title: context.title,
|
|
3389
3953
|
approvalId,
|
|
3390
3954
|
approvalSlug: approvalId,
|
|
@@ -3394,6 +3958,7 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3394
3958
|
});
|
|
3395
3959
|
return buildApprovalResponse(params.method, context.requestParams, outcome);
|
|
3396
3960
|
} catch (error) {
|
|
3961
|
+
if (params.signal?.reason instanceof CodexServerRequestResolvedError) return;
|
|
3397
3962
|
const cancelled = params.signal?.aborted === true;
|
|
3398
3963
|
recordNativeToolFailureDisposition(params, context, cancelled && params.signal ? resolveCodexToolAbortTerminalReason(params.signal) : "failed");
|
|
3399
3964
|
emitApprovalEvent(params.paramsForRun, {
|
|
@@ -3401,6 +3966,10 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3401
3966
|
kind: context.kind,
|
|
3402
3967
|
status: cancelled ? "failed" : "unavailable",
|
|
3403
3968
|
title: context.title,
|
|
3969
|
+
...approvalId ? {
|
|
3970
|
+
approvalId,
|
|
3971
|
+
approvalSlug: approvalId
|
|
3972
|
+
} : {},
|
|
3404
3973
|
...context.eventDetails,
|
|
3405
3974
|
...approvalEventScope(params.method, cancelled ? "cancelled" : "denied"),
|
|
3406
3975
|
message: cancelled ? "Codex app-server approval cancelled because the run stopped." : `Codex app-server approval route failed: ${formatCodexDisplayText(coerceErrorMessage(error))}`
|
|
@@ -3417,7 +3986,7 @@ function recordNativeToolFailureDisposition(params, context, disposition, approv
|
|
|
3417
3986
|
}
|
|
3418
3987
|
/** Converts an OpenClaw approval outcome into the app-server method response. */
|
|
3419
3988
|
function buildApprovalResponse(method, requestParams, outcome) {
|
|
3420
|
-
if (method === "item/commandExecution/requestApproval") return { decision:
|
|
3989
|
+
if (method === "item/commandExecution/requestApproval") return { decision: resolveCommandApproval(requestParams, outcome).decision };
|
|
3421
3990
|
if (method === "item/fileChange/requestApproval") return { decision: fileChangeApprovalDecision(outcome) };
|
|
3422
3991
|
if (method === "item/permissions/requestApproval") {
|
|
3423
3992
|
if (outcome === "approved-session" || outcome === "approved-once") return {
|
|
@@ -3675,28 +4244,13 @@ function stableJsonText(value) {
|
|
|
3675
4244
|
return entries.every((entry) => entry !== void 0) ? `{${entries.join(",")}}` : void 0;
|
|
3676
4245
|
}
|
|
3677
4246
|
}
|
|
3678
|
-
function commandApprovalDecision(requestParams, outcome) {
|
|
3679
|
-
if (outcome === "cancelled") return "cancel";
|
|
3680
|
-
if (outcome === "denied" || outcome === "unavailable") return "decline";
|
|
3681
|
-
const capabilities = commandApprovalCapabilities(requestParams);
|
|
3682
|
-
if (outcome === "approved-session" && capabilities.sessionDecision !== void 0) return capabilities.sessionDecision;
|
|
3683
|
-
return capabilities.once ? "accept" : "decline";
|
|
3684
|
-
}
|
|
3685
4247
|
function nativeApprovalAllowedDecisions(params) {
|
|
3686
4248
|
if (params.method === "item/fileChange/requestApproval") return [
|
|
3687
4249
|
"allow-once",
|
|
3688
4250
|
"allow-always",
|
|
3689
4251
|
"deny"
|
|
3690
4252
|
];
|
|
3691
|
-
|
|
3692
|
-
const available = params.requestParams?.availableDecisions;
|
|
3693
|
-
if (!Array.isArray(available)) return;
|
|
3694
|
-
const capabilities = commandApprovalCapabilities(params.requestParams);
|
|
3695
|
-
const decisions = [];
|
|
3696
|
-
if (capabilities.once) decisions.push("allow-once");
|
|
3697
|
-
if (!params.requiresOneShot && capabilities.sessionDecision !== void 0) decisions.push("allow-always");
|
|
3698
|
-
decisions.push("deny");
|
|
3699
|
-
return decisions;
|
|
4253
|
+
return params.method === "item/commandExecution/requestApproval" ? commandApprovalAllowedDecisions(params.requestParams, params.requiresOneShot) : void 0;
|
|
3700
4254
|
}
|
|
3701
4255
|
function fileChangeApprovalDecision(outcome) {
|
|
3702
4256
|
if (outcome === "cancelled") return "cancel";
|
|
@@ -3878,22 +4432,6 @@ function isPrivateNetworkHostPattern(value) {
|
|
|
3878
4432
|
if (wildcardStripped.startsWith("10.") || wildcardStripped.startsWith("127.") || wildcardStripped.startsWith("192.168.") || wildcardStripped.startsWith("169.254.")) return true;
|
|
3879
4433
|
return /^172\.(1[6-9]|2\d|3[0-1])\./.test(wildcardStripped);
|
|
3880
4434
|
}
|
|
3881
|
-
function commandApprovalCapabilities(requestParams) {
|
|
3882
|
-
const available = requestParams?.availableDecisions;
|
|
3883
|
-
if (!Array.isArray(available)) return {
|
|
3884
|
-
once: true,
|
|
3885
|
-
sessionDecision: "acceptForSession"
|
|
3886
|
-
};
|
|
3887
|
-
return {
|
|
3888
|
-
once: available.includes("accept"),
|
|
3889
|
-
...available.includes("acceptForSession") ? { sessionDecision: "acceptForSession" } : { sessionDecision: findAvailableCommandAmendmentDecision(requestParams) }
|
|
3890
|
-
};
|
|
3891
|
-
}
|
|
3892
|
-
function findAvailableCommandAmendmentDecision(requestParams) {
|
|
3893
|
-
const available = requestParams?.availableDecisions;
|
|
3894
|
-
if (!Array.isArray(available)) return;
|
|
3895
|
-
return available.find((entry) => isJsonObject(entry) && (isJsonObject(entry.acceptWithExecpolicyAmendment) || isJsonObject(entry.applyNetworkPolicyAmendment)));
|
|
3896
|
-
}
|
|
3897
4435
|
function approvalResolutionMessage(outcome) {
|
|
3898
4436
|
return {
|
|
3899
4437
|
"approved-session": "Codex app-server approval granted for the session.",
|
|
@@ -4221,7 +4759,7 @@ function resolvePluginElicitation(params) {
|
|
|
4221
4759
|
const appId = readFirstString$1(meta, PLUGIN_APP_ID_META_KEYS) ?? readFirstString$1(requestParams, PLUGIN_APP_ID_META_KEYS);
|
|
4222
4760
|
const connectorId = readFirstString$1(meta, PLUGIN_CONNECTOR_ID_META_KEYS);
|
|
4223
4761
|
const isCodexConnectorApproval = isCodexConnectorApprovalElicitation(requestParams, meta);
|
|
4224
|
-
if (isCodexConnectorApproval && appId && connectorId && appId !== connectorId) return {
|
|
4762
|
+
if (isCodexConnectorApproval && appId && connectorId && codexAppIdentityKey(appId) !== codexAppIdentityKey(connectorId)) return {
|
|
4225
4763
|
kind: "decline",
|
|
4226
4764
|
reason: "app_id_connector_id_mismatch"
|
|
4227
4765
|
};
|
|
@@ -4230,20 +4768,19 @@ function resolvePluginElicitation(params) {
|
|
|
4230
4768
|
kind: "decline",
|
|
4231
4769
|
reason: "missing_policy_context"
|
|
4232
4770
|
};
|
|
4233
|
-
const
|
|
4234
|
-
if (entry
|
|
4771
|
+
const matches = Object.entries(context.apps).filter(([id]) => codexAppIdentityKey(id) === codexAppIdentityKey(appId)).map(([, entry]) => entry);
|
|
4772
|
+
if (matches.some((entry) => entry.source === "account") && !isCodexConnectorApproval) return {
|
|
4235
4773
|
kind: "decline",
|
|
4236
4774
|
reason: "account_app_source_mismatch"
|
|
4237
4775
|
};
|
|
4238
|
-
return uniquePluginMatch(
|
|
4776
|
+
return uniquePluginMatch(matches, "app_id");
|
|
4239
4777
|
}
|
|
4240
4778
|
if (isCodexConnectorApproval && connectorId) {
|
|
4241
4779
|
if (!context) return {
|
|
4242
4780
|
kind: "decline",
|
|
4243
4781
|
reason: "missing_policy_context"
|
|
4244
4782
|
};
|
|
4245
|
-
|
|
4246
|
-
return uniquePluginMatch(entry ? [entry] : [], "connector_id");
|
|
4783
|
+
return uniquePluginMatch(Object.entries(context.apps).filter(([id]) => codexAppIdentityKey(id) === codexAppIdentityKey(connectorId)).map(([, entry]) => entry), "connector_id");
|
|
4247
4784
|
}
|
|
4248
4785
|
const serverName = readNonBlankStringField(requestParams, "serverName");
|
|
4249
4786
|
if (serverName && context) {
|
|
@@ -4979,142 +5516,6 @@ function applyCurrentMessageProvider(toolName, args, currentProvider) {
|
|
|
4979
5516
|
provider
|
|
4980
5517
|
};
|
|
4981
5518
|
}
|
|
4982
|
-
function normalizeRouteToken(value) {
|
|
4983
|
-
if (typeof value === "number") return Number.isFinite(value) ? String(value) : void 0;
|
|
4984
|
-
const normalized = value?.trim().toLowerCase();
|
|
4985
|
-
return normalized ? normalized : void 0;
|
|
4986
|
-
}
|
|
4987
|
-
function sourceRouteTokens(hookContext) {
|
|
4988
|
-
const tokens = /* @__PURE__ */ new Set();
|
|
4989
|
-
const currentTarget = normalizeRouteToken(hookContext?.currentMessagingTarget);
|
|
4990
|
-
const currentChannel = normalizeRouteToken(hookContext?.currentChannelId);
|
|
4991
|
-
const currentProvider = normalizeRouteToken(hookContext?.currentChannelProvider);
|
|
4992
|
-
if (currentTarget) tokens.add(currentTarget);
|
|
4993
|
-
if (currentChannel) tokens.add(currentChannel);
|
|
4994
|
-
const channelPrefixIndex = currentChannel?.indexOf(":") ?? -1;
|
|
4995
|
-
if (channelPrefixIndex >= 0 && currentChannel) {
|
|
4996
|
-
const unprefixedChannel = currentChannel.slice(channelPrefixIndex + 1);
|
|
4997
|
-
if (unprefixedChannel) {
|
|
4998
|
-
tokens.add(unprefixedChannel);
|
|
4999
|
-
for (const segment of unprefixedChannel.split(/[;,]/u)) {
|
|
5000
|
-
const token = normalizeRouteToken(segment);
|
|
5001
|
-
if (token) tokens.add(token);
|
|
5002
|
-
}
|
|
5003
|
-
}
|
|
5004
|
-
}
|
|
5005
|
-
if (currentProvider && currentChannel?.startsWith(`${currentProvider}:`)) {
|
|
5006
|
-
const unprefixedChannel = currentChannel.slice(currentProvider.length + 1);
|
|
5007
|
-
if (unprefixedChannel) tokens.add(unprefixedChannel);
|
|
5008
|
-
}
|
|
5009
|
-
return tokens;
|
|
5010
|
-
}
|
|
5011
|
-
function routeTokenMatchesSource(token, hookContext) {
|
|
5012
|
-
const normalized = normalizeRouteToken(token);
|
|
5013
|
-
return normalized !== void 0 && sourceRouteTokens(hookContext).has(normalized);
|
|
5014
|
-
}
|
|
5015
|
-
function routeProviderMatchesSource(provider, hookContext) {
|
|
5016
|
-
const normalized = normalizeRouteToken(provider);
|
|
5017
|
-
if (!normalized) return false;
|
|
5018
|
-
const currentProvider = normalizeRouteToken(hookContext?.currentChannelProvider);
|
|
5019
|
-
const currentChannel = normalizeRouteToken(hookContext?.currentChannelId);
|
|
5020
|
-
return currentProvider === normalized || currentChannel?.startsWith(`${normalized}:`) === true;
|
|
5021
|
-
}
|
|
5022
|
-
function routeTokenMatchesCurrentMessage(token, hookContext) {
|
|
5023
|
-
const normalized = normalizeRouteToken(token);
|
|
5024
|
-
return normalized !== void 0 && normalized === normalizeRouteToken(hookContext?.currentMessageId);
|
|
5025
|
-
}
|
|
5026
|
-
function readRouteToken(record, key) {
|
|
5027
|
-
const value = record[key];
|
|
5028
|
-
return typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
5029
|
-
}
|
|
5030
|
-
function explicitRouteTokensMismatchCurrent(args, keys, currentToken) {
|
|
5031
|
-
const normalizedCurrent = normalizeRouteToken(currentToken);
|
|
5032
|
-
if (!normalizedCurrent) return false;
|
|
5033
|
-
return keys.some((key) => {
|
|
5034
|
-
const normalized = normalizeRouteToken(readRouteToken(args, key));
|
|
5035
|
-
return normalized !== void 0 && normalized !== normalizedCurrent;
|
|
5036
|
-
});
|
|
5037
|
-
}
|
|
5038
|
-
function explicitThreadRouteTargetsNonSource(args, hookContext, messagingTarget) {
|
|
5039
|
-
const normalizedCurrentThread = normalizeRouteToken(hookContext?.currentThreadId);
|
|
5040
|
-
const explicitThreadTokens = [...EXPLICIT_MESSAGE_THREAD_KEYS.map((key) => normalizeRouteToken(readRouteToken(args, key))), normalizeRouteToken(messagingTarget?.threadId)].filter((value) => value !== void 0);
|
|
5041
|
-
if (explicitThreadTokens.length === 0) return false;
|
|
5042
|
-
return normalizedCurrentThread === void 0 || explicitThreadTokens.some((value) => value !== normalizedCurrentThread);
|
|
5043
|
-
}
|
|
5044
|
-
function replyReceiptMatchesCurrentMessage(value, hookContext, depth = 0) {
|
|
5045
|
-
if (depth > 4 || value === null) return false;
|
|
5046
|
-
if (typeof value === "string") {
|
|
5047
|
-
const trimmed = value.trim();
|
|
5048
|
-
if (!trimmed || !["{", "["].includes(trimmed[0] ?? "")) return false;
|
|
5049
|
-
try {
|
|
5050
|
-
return replyReceiptMatchesCurrentMessage(JSON.parse(trimmed), hookContext, depth + 1);
|
|
5051
|
-
} catch {
|
|
5052
|
-
return false;
|
|
5053
|
-
}
|
|
5054
|
-
}
|
|
5055
|
-
if (typeof value !== "object") return false;
|
|
5056
|
-
if (Array.isArray(value)) return value.some((item) => replyReceiptMatchesCurrentMessage(item, hookContext, depth + 1));
|
|
5057
|
-
const record = value;
|
|
5058
|
-
for (const key of [
|
|
5059
|
-
"repliedTo",
|
|
5060
|
-
"replyTo",
|
|
5061
|
-
"replyToId",
|
|
5062
|
-
"replyToIdFull"
|
|
5063
|
-
]) if (routeTokenMatchesCurrentMessage(typeof record[key] === "string" ? record[key] : void 0, hookContext)) return true;
|
|
5064
|
-
for (const key of [
|
|
5065
|
-
"content",
|
|
5066
|
-
"details",
|
|
5067
|
-
"payload",
|
|
5068
|
-
"receipt",
|
|
5069
|
-
"result",
|
|
5070
|
-
"results",
|
|
5071
|
-
"sendResult",
|
|
5072
|
-
"text"
|
|
5073
|
-
]) if (replyReceiptMatchesCurrentMessage(record[key], hookContext, depth + 1)) return true;
|
|
5074
|
-
return false;
|
|
5075
|
-
}
|
|
5076
|
-
function hasExplicitNonSourceMessageRoute(args, hookContext, messagingTarget) {
|
|
5077
|
-
const currentProvider = normalizeRouteToken(hookContext?.currentChannelProvider);
|
|
5078
|
-
for (const key of EXPLICIT_MESSAGE_PROVIDER_KEYS) {
|
|
5079
|
-
const provider = normalizeRouteToken(typeof args[key] === "string" ? args[key] : void 0);
|
|
5080
|
-
if (provider && currentProvider !== provider && !routeProviderMatchesSource(provider, hookContext)) return true;
|
|
5081
|
-
}
|
|
5082
|
-
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);
|
|
5083
|
-
if (explicitThreadRouteTargetsNonSource(args, hookContext, messagingTarget)) return true;
|
|
5084
|
-
if (explicitRouteTokensMismatchCurrent(args, EXPLICIT_MESSAGE_REPLY_KEYS, hookContext?.currentMessageId)) return true;
|
|
5085
|
-
if (messagingTarget?.to !== void 0 && !routeTokenMatchesSource(messagingTarget.to, hookContext)) return true;
|
|
5086
|
-
if (messagingTarget?.to !== void 0) return false;
|
|
5087
|
-
if (targetValues.length === 0) return false;
|
|
5088
|
-
if (targetValues.some((value) => !routeTokenMatchesSource(value, hookContext))) return true;
|
|
5089
|
-
return false;
|
|
5090
|
-
}
|
|
5091
|
-
function normalizeAcceptedSessionSpawn(result) {
|
|
5092
|
-
const details = asOptionalRecord(asOptionalRecord(result)?.details);
|
|
5093
|
-
if (!details || details.status !== "accepted") return null;
|
|
5094
|
-
const runId = normalizeOptionalString(details.runId);
|
|
5095
|
-
const childSessionKey = normalizeOptionalString(details.childSessionKey);
|
|
5096
|
-
return runId && childSessionKey ? {
|
|
5097
|
-
runId,
|
|
5098
|
-
childSessionKey
|
|
5099
|
-
} : null;
|
|
5100
|
-
}
|
|
5101
|
-
const EXPLICIT_MESSAGE_PROVIDER_KEYS = ["channel", "provider"];
|
|
5102
|
-
const EXPLICIT_MESSAGE_TARGET_KEYS = [
|
|
5103
|
-
"target",
|
|
5104
|
-
"to",
|
|
5105
|
-
"channelId"
|
|
5106
|
-
];
|
|
5107
|
-
const EXPLICIT_MESSAGE_THREAD_KEYS = [
|
|
5108
|
-
"threadId",
|
|
5109
|
-
"thread_id",
|
|
5110
|
-
"messageThreadId",
|
|
5111
|
-
"topicId"
|
|
5112
|
-
];
|
|
5113
|
-
const EXPLICIT_MESSAGE_REPLY_KEYS = [
|
|
5114
|
-
"replyTo",
|
|
5115
|
-
"replyToId",
|
|
5116
|
-
"replyToIdFull"
|
|
5117
|
-
];
|
|
5118
5519
|
function computerFrameImageIdentity(content) {
|
|
5119
5520
|
if (!Array.isArray(content)) return;
|
|
5120
5521
|
const images = content.filter((block) => block.type === "image");
|
|
@@ -5142,6 +5543,11 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5142
5543
|
const registrationNames = inheritedNames ?? new Set(registeredProjection.tools.map((entry) => entry.name));
|
|
5143
5544
|
const finalized = finalizeCodexToolAvailability(availableProjection.tools.filter((entry) => registrationNames.has(entry.name)));
|
|
5144
5545
|
const availableTools = finalized.tools;
|
|
5546
|
+
const pluginLocalMediaTrustByToolName = /* @__PURE__ */ new Map();
|
|
5547
|
+
for (const { name, tool } of availableTools) {
|
|
5548
|
+
const pluginMeta = getPluginToolMeta(tool);
|
|
5549
|
+
if (pluginMeta) pluginLocalMediaTrustByToolName.set(name, new Set(pluginMeta.trustedLocalMedia === true ? [name] : []));
|
|
5550
|
+
}
|
|
5145
5551
|
availableProjection.quarantinedTools.push(...finalized.quarantinedTools);
|
|
5146
5552
|
const toolMap = new Map(availableTools.map((entry) => [entry.name, entry]));
|
|
5147
5553
|
const quarantinedAvailableToolNames = new Set(availableProjection.quarantinedTools.map((tool) => tool.tool));
|
|
@@ -5161,6 +5567,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5161
5567
|
messagingToolSentMediaUrls: [],
|
|
5162
5568
|
messagingToolSentTargets: [],
|
|
5163
5569
|
messagingToolSourceReplyPayloads: [],
|
|
5570
|
+
confirmedMediaDeliveries: [],
|
|
5164
5571
|
toolMediaUrls: [],
|
|
5165
5572
|
toolAutoDeliveryMediaUrls: [],
|
|
5166
5573
|
coreTtsToolResults: [],
|
|
@@ -5264,16 +5671,16 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5264
5671
|
try {
|
|
5265
5672
|
const prepare = tool.prepareArguments;
|
|
5266
5673
|
const toolArgs = prepare ? Reflect.apply(prepare, tool, [rawArguments]) : args;
|
|
5267
|
-
const
|
|
5674
|
+
const preparedMessageMedia = toolName === "message" && isRecord(toolArgs) ? await prepareCodexRemoteWorkspaceMessageMedia({
|
|
5268
5675
|
args: toolArgs,
|
|
5269
5676
|
localWorkspaceRoot: params.hookContext?.workspaceDir,
|
|
5270
5677
|
remoteWorkspaceRoot: params.hookContext?.remoteWorkspaceRoot,
|
|
5271
5678
|
readRemoteFile: readRemoteWorkspaceFile,
|
|
5272
5679
|
timeoutMs: params.hookContext?.remoteWorkspaceRequestTimeoutMs,
|
|
5273
5680
|
signal
|
|
5274
|
-
}) :
|
|
5275
|
-
const
|
|
5276
|
-
executedArgs = structuredClone(
|
|
5681
|
+
}) : void 0;
|
|
5682
|
+
const preparedArgs = preparedMessageMedia?.args ?? toolArgs;
|
|
5683
|
+
executedArgs = structuredClone(isRecord(preparedArgs) ? preparedArgs : args);
|
|
5277
5684
|
const messagingContext = {
|
|
5278
5685
|
config: params.hookContext?.config,
|
|
5279
5686
|
currentChannelId: params.hookContext?.currentChannelId,
|
|
@@ -5298,10 +5705,41 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5298
5705
|
}));
|
|
5299
5706
|
const rawResult = await Reflect.apply(tool.execute, tool, executionArgs);
|
|
5300
5707
|
captureExecutionBoundary();
|
|
5301
|
-
if (toolName === "message" && asOptionalRecord(asOptionalRecord(rawResult.details)?.messageDelivery)?.sourceReplyDelivered === true) telemetry.sourceReplyDelivered = true;
|
|
5302
|
-
const telemetryRawResult = sanitizeToolResult(rawResult);
|
|
5303
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;
|
|
5304
5741
|
const rawResultFailureKind = resolveToolResultFailureKind(rawResult);
|
|
5742
|
+
const telemetryRawResult = sanitizeToolResult(rawResult);
|
|
5305
5743
|
const middlewareResult = await middlewareRunner.applyToolResultMiddleware({
|
|
5306
5744
|
threadId: call.threadId,
|
|
5307
5745
|
turnId: call.turnId,
|
|
@@ -5320,7 +5758,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5320
5758
|
result: middlewareResult
|
|
5321
5759
|
});
|
|
5322
5760
|
const resultIsError = rawIsError || isToolResultError(result);
|
|
5323
|
-
const acceptedSessionSpawn = toolName === "sessions_spawn" && !rawIsError ?
|
|
5761
|
+
const acceptedSessionSpawn = toolName === "sessions_spawn" && !rawIsError ? normalizeAcceptedSessionSpawnResult(telemetryRawResult) : null;
|
|
5324
5762
|
if (acceptedSessionSpawn) telemetry.acceptedSessionSpawns.push(acceptedSessionSpawn);
|
|
5325
5763
|
const finalResultFailureKind = resolveToolResultFailureKind(result);
|
|
5326
5764
|
const resultFailureKind = rawResultFailureKind ?? finalResultFailureKind;
|
|
@@ -5353,39 +5791,20 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5353
5791
|
toolName,
|
|
5354
5792
|
toolCallOrdinal: options?.toolCallOrdinal
|
|
5355
5793
|
});
|
|
5356
|
-
const
|
|
5357
|
-
const messagingTarget = isMessagingTool(toolName) ? extractMessagingToolSend(toolName, messagingTelemetryArgs, messagingContext) : void 0;
|
|
5358
|
-
const confirmedMessagingTarget = !rawIsError && messagingTarget ? extractMessagingToolSendResult(messagingTarget, telemetryRawResult) : messagingTarget;
|
|
5359
|
-
const terminalType = resultFailureKind === "blocked" ? "blocked" : resultIsError ? "error" : "completed";
|
|
5794
|
+
const terminalType = resultFailureKind === "blocked" ? "blocked" : resultIsError || resultFailureKind ? "error" : "completed";
|
|
5360
5795
|
const contentItems = convertToolContents(result.content, toolResultMaxChars);
|
|
5361
5796
|
const deliveredFrameImages = contentItems.filter((item) => item.type === "inputImage");
|
|
5362
5797
|
const finalFrameImageIdentity = computerFrameImageIdentity(result.content);
|
|
5363
5798
|
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId && (deliveredFrameImages.length !== 1 || finalFrameImageIdentity === void 0 || finalFrameImageIdentity !== params.computerContextEpoch.frameImageIdentity)) invalidateComputerFrame(params.computerContextEpoch);
|
|
5364
|
-
const response =
|
|
5799
|
+
const response = {
|
|
5365
5800
|
contentItems,
|
|
5366
|
-
success: !resultIsError
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
const deliveredSourceReply = isDeliveredMessageToolOnlySourceReplyResult({
|
|
5372
|
-
sourceReplyDeliveryMode: params.hookContext?.sourceReplyDeliveryMode,
|
|
5373
|
-
toolName,
|
|
5374
|
-
args: executedArgs,
|
|
5375
|
-
result,
|
|
5376
|
-
hookResult: rawResult,
|
|
5377
|
-
isError: resultIsError,
|
|
5378
|
-
allowExplicitSourceRoute: !blocksSourceReplyTermination
|
|
5379
|
-
});
|
|
5380
|
-
const receiptConfirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && normalizeRouteToken(typeof executedArgs.action === "string" ? executedArgs.action : void 0) === "reply" && !resultIsError && !blocksSourceReplyTermination && isDeliveredMessagingToolResult({
|
|
5381
|
-
toolName,
|
|
5382
|
-
args: executedArgs,
|
|
5383
|
-
result,
|
|
5384
|
-
hookResult: rawResult,
|
|
5385
|
-
isError: resultIsError
|
|
5386
|
-
}) && (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
|
+
};
|
|
5387
5806
|
const toolConfirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && !resultIsError && (rawResult.terminate === true || result.terminate === true);
|
|
5388
|
-
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);
|
|
5389
5808
|
const sourceReplyFinal = confirmedSourceReply ? executedArgs.final !== false : void 0;
|
|
5390
5809
|
const autoDeliveryTtsMediaUrls = getCoreTtsToolResultMediaUrls(rawResult);
|
|
5391
5810
|
collectToolTelemetry({
|
|
@@ -5396,23 +5815,27 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5396
5815
|
telemetry,
|
|
5397
5816
|
signal,
|
|
5398
5817
|
isError: resultIsError,
|
|
5818
|
+
messagingDelivered,
|
|
5819
|
+
mediaDeliveryConfirmed,
|
|
5820
|
+
sourcePathsByStagedPath: preparedMessageMedia?.sourcePathsByStagedPath,
|
|
5399
5821
|
autoDeliveryTtsMediaUrls,
|
|
5400
5822
|
coreTtsToolResult: autoDeliveryTtsMediaUrls?.length ? rawResult : void 0,
|
|
5401
5823
|
messagingTarget: confirmedMessagingTarget,
|
|
5402
|
-
sourceReplyFinal
|
|
5824
|
+
sourceReplyFinal,
|
|
5825
|
+
trustedLocalMediaToolNames: pluginLocalMediaTrustByToolName.get(toolName)
|
|
5403
5826
|
});
|
|
5404
|
-
if (deliveredSourceReply ||
|
|
5827
|
+
if (deliveredSourceReply || toolConfirmedSourceReply) telemetry.didDeliverSourceReplyViaMessageTool = true;
|
|
5405
5828
|
const continuesSourceReplyProgress = confirmedSourceReply && sourceReplyFinal === false;
|
|
5406
|
-
|
|
5829
|
+
response.terminate = (rawResult.terminate === true || result.terminate === true) && !continuesSourceReplyProgress || isToolResultYield(rawResult) || isToolResultYield(result) || confirmedSourceReply && sourceReplyFinal === true || void 0;
|
|
5407
5830
|
const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
|
|
5408
|
-
|
|
5831
|
+
response.asyncStarted = asyncStarted || void 0;
|
|
5409
5832
|
const replaySafe = executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
5410
5833
|
copyInternalToolResultState(rawResult, response);
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5834
|
+
response.executedArguments = executedArgs;
|
|
5835
|
+
response.executionStarted = didStartExecution && !executionPrevented;
|
|
5836
|
+
response.replaySafe = replaySafe;
|
|
5837
|
+
response.sideEffectEvidence = !replaySafe || void 0;
|
|
5838
|
+
return response;
|
|
5416
5839
|
} catch (error) {
|
|
5417
5840
|
const trustedNoStart = consumeTrustedToolNoStartError(error);
|
|
5418
5841
|
executionPrevented ||= trustedNoStart;
|
|
@@ -5420,6 +5843,8 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5420
5843
|
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId) invalidateComputerFrame(params.computerContextEpoch);
|
|
5421
5844
|
const executionDisposition = getBeforeToolCallFailureDisposition(error) ?? (signal.aborted ? resolveCodexToolAbortTerminalReason(signal) : resolveToolExecutionErrorKind(error));
|
|
5422
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}`);
|
|
5423
5848
|
executionPrevented = executionPrevented || consumePreExecutionBlockedToolCall(call.callId, toolResultHookContext.runId);
|
|
5424
5849
|
const failedResult = failedToolResult(errorMessage, executionDisposition);
|
|
5425
5850
|
finalizeToolTerminalPresentation({
|
|
@@ -5445,17 +5870,19 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5445
5870
|
startedAt
|
|
5446
5871
|
});
|
|
5447
5872
|
const replaySafe = !didStartExecution || executionPrevented || isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
5448
|
-
return
|
|
5873
|
+
return {
|
|
5449
5874
|
contentItems: [{
|
|
5450
5875
|
type: "inputText",
|
|
5451
5876
|
text: errorMessage
|
|
5452
5877
|
}],
|
|
5453
|
-
success: false
|
|
5454
|
-
|
|
5878
|
+
success: false,
|
|
5879
|
+
diagnosticTerminalType: executionDisposition === "blocked" ? "blocked" : "error",
|
|
5880
|
+
diagnosticTerminalReason: executionDisposition === "blocked" ? void 0 : executionDisposition,
|
|
5455
5881
|
executedArguments: executedArgs,
|
|
5456
5882
|
executionStarted: didStartExecution && !executionPrevented,
|
|
5457
|
-
|
|
5458
|
-
|
|
5883
|
+
replaySafe,
|
|
5884
|
+
sideEffectEvidence: didStartExecution && !replaySafe || void 0
|
|
5885
|
+
};
|
|
5459
5886
|
} finally {
|
|
5460
5887
|
if (executionSnapshotStates.get(call.callId) === executionSnapshotState && (executionSnapshotState.consumed || !executionSnapshotState.retainAfterCompletion)) executionSnapshotStates.delete(call.callId);
|
|
5461
5888
|
consumeAdjustedParamsForToolCall(call.callId, toolResultHookContext.runId);
|
|
@@ -5508,18 +5935,6 @@ function notifyAgentToolResult(observer, toolName, result, isError) {
|
|
|
5508
5935
|
embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${toolName} error=${message}`);
|
|
5509
5936
|
}
|
|
5510
5937
|
}
|
|
5511
|
-
function failedToolResult(message, status = "failed") {
|
|
5512
|
-
return {
|
|
5513
|
-
content: [{
|
|
5514
|
-
type: "text",
|
|
5515
|
-
text: message
|
|
5516
|
-
}],
|
|
5517
|
-
details: {
|
|
5518
|
-
status,
|
|
5519
|
-
error: message
|
|
5520
|
-
}
|
|
5521
|
-
};
|
|
5522
|
-
}
|
|
5523
5938
|
function reportQuarantinedDynamicTools(params) {
|
|
5524
5939
|
if (params.tools.length === 0) return;
|
|
5525
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`, {
|
|
@@ -5564,16 +5979,15 @@ function composeAbortSignals(...signals) {
|
|
|
5564
5979
|
return AbortSignal.any(activeSignals);
|
|
5565
5980
|
}
|
|
5566
5981
|
function collectToolTelemetry(params) {
|
|
5567
|
-
if (params.isError) return;
|
|
5568
5982
|
if (!params.isError && params.toolName === "cron" && isCronAddAction(params.args)) params.telemetry.successfulCronAdds = (params.telemetry.successfulCronAdds ?? 0) + 1;
|
|
5569
5983
|
if (!params.isError && params.toolName === HEARTBEAT_RESPONSE_TOOL_NAME) {
|
|
5570
5984
|
const response = normalizeHeartbeatToolResponse(params.result?.details);
|
|
5571
5985
|
if (response) params.telemetry.heartbeatToolResponse = response;
|
|
5572
5986
|
}
|
|
5573
|
-
if (params.result && !params.signal.aborted) {
|
|
5987
|
+
if (!params.isError && params.result && !params.signal.aborted) {
|
|
5574
5988
|
const media = extractToolResultMediaArtifact(params.result);
|
|
5575
5989
|
if (media) {
|
|
5576
|
-
const mediaUrls = filterToolResultMediaUrls(params.toolName, media.mediaUrls, params.mediaTrustResult ?? params.result);
|
|
5990
|
+
const mediaUrls = filterToolResultMediaUrls(params.toolName, media.mediaUrls, params.coreTtsToolResult ?? params.mediaTrustResult ?? params.result, params.trustedLocalMediaToolNames);
|
|
5577
5991
|
const seen = new Set(params.telemetry.toolMediaUrls);
|
|
5578
5992
|
const autoDeliveryMediaUrls = new Set(params.telemetry.toolAutoDeliveryMediaUrls);
|
|
5579
5993
|
const rawAutoDeliveryMediaUrls = new Set(params.autoDeliveryTtsMediaUrls);
|
|
@@ -5593,24 +6007,20 @@ function collectToolTelemetry(params) {
|
|
|
5593
6007
|
if (media.audioAsVoice) params.telemetry.toolAudioAsVoice = true;
|
|
5594
6008
|
}
|
|
5595
6009
|
}
|
|
5596
|
-
if (!
|
|
5597
|
-
const isMessagingSendAction = isMessagingToolSendAction(params.toolName, params.args);
|
|
5598
|
-
if (!isMessagingSendAction && !params.messagingTarget) return;
|
|
5599
|
-
if (!isMessagingSendAction && !isDeliveredMessagingToolResult({
|
|
5600
|
-
toolName: params.toolName,
|
|
5601
|
-
args: params.args,
|
|
5602
|
-
result: params.result,
|
|
5603
|
-
hookResult: params.mediaTrustResult,
|
|
5604
|
-
isError: params.isError
|
|
5605
|
-
})) return;
|
|
6010
|
+
if (!params.messagingDelivered) return;
|
|
5606
6011
|
params.telemetry.didSendViaMessagingTool = true;
|
|
5607
|
-
|
|
5608
|
-
|
|
6012
|
+
if (asOptionalRecord(asOptionalRecord(params.mediaTrustResult)?.details)?.sourceReplySink === "internal-ui") {
|
|
6013
|
+
const sourceReplyPayload = extractMessagingToolSourceReplyPayload(params.result);
|
|
6014
|
+
if (!sourceReplyPayload) return;
|
|
5609
6015
|
const record = {
|
|
5610
6016
|
...sourceReplyPayload,
|
|
5611
6017
|
...params.sourceReplyFinal !== void 0 ? { sourceReplyFinal: params.sourceReplyFinal } : {}
|
|
5612
6018
|
};
|
|
5613
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
|
+
});
|
|
5614
6024
|
return record;
|
|
5615
6025
|
}
|
|
5616
6026
|
const text = readFirstString(params.args, [
|
|
@@ -5620,7 +6030,7 @@ function collectToolTelemetry(params) {
|
|
|
5620
6030
|
"content"
|
|
5621
6031
|
]);
|
|
5622
6032
|
if (text) params.telemetry.messagingToolSentTexts.push(text);
|
|
5623
|
-
const mediaUrls =
|
|
6033
|
+
const mediaUrls = params.mediaDeliveryConfirmed ? collectCodexMessageMediaUrls(params.args) : [];
|
|
5624
6034
|
params.telemetry.messagingToolSentMediaUrls.push(...mediaUrls);
|
|
5625
6035
|
const record = {
|
|
5626
6036
|
...params.messagingTarget ?? {
|
|
@@ -5643,27 +6053,13 @@ function collectToolTelemetry(params) {
|
|
|
5643
6053
|
...params.sourceReplyFinal !== void 0 ? { sourceReplyFinal: params.sourceReplyFinal } : {}
|
|
5644
6054
|
};
|
|
5645
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
|
+
});
|
|
5646
6061
|
return record;
|
|
5647
6062
|
}
|
|
5648
|
-
function extractInternalSourceReplyPayload(details) {
|
|
5649
|
-
if (!isRecord(details) || details.sourceReplySink !== "internal-ui") return;
|
|
5650
|
-
const rawPayload = details.sourceReply;
|
|
5651
|
-
if (!isRecord(rawPayload)) return;
|
|
5652
|
-
const text = readFirstString(rawPayload, ["text", "message"]);
|
|
5653
|
-
const mediaUrls = collectMediaUrls(rawPayload);
|
|
5654
|
-
const mediaUrl = typeof rawPayload.mediaUrl === "string" && rawPayload.mediaUrl.trim() ? rawPayload.mediaUrl.trim() : mediaUrls[0];
|
|
5655
|
-
const payload = {
|
|
5656
|
-
...text ? { text } : {},
|
|
5657
|
-
...mediaUrl ? { mediaUrl } : {},
|
|
5658
|
-
...mediaUrls.length > 0 ? { mediaUrls } : {},
|
|
5659
|
-
...rawPayload.audioAsVoice === true ? { audioAsVoice: true } : {},
|
|
5660
|
-
...isRecord(rawPayload.presentation) ? { presentation: rawPayload.presentation } : {},
|
|
5661
|
-
...isRecord(rawPayload.interactive) ? { interactive: rawPayload.interactive } : {},
|
|
5662
|
-
...isRecord(rawPayload.channelData) ? { channelData: rawPayload.channelData } : {},
|
|
5663
|
-
...typeof details.idempotencyKey === "string" && details.idempotencyKey.trim() ? { idempotencyKey: details.idempotencyKey.trim() } : {}
|
|
5664
|
-
};
|
|
5665
|
-
return text || mediaUrls.length > 0 || payload.presentation || payload.interactive ? payload : void 0;
|
|
5666
|
-
}
|
|
5667
6063
|
function isToolResultYield(result) {
|
|
5668
6064
|
const details = result.details;
|
|
5669
6065
|
if (!isRecord(details) || typeof details.status !== "string") return false;
|
|
@@ -5673,42 +6069,6 @@ function isAsyncStartedToolResult(result) {
|
|
|
5673
6069
|
const details = result.details;
|
|
5674
6070
|
return isRecord(details) && details.async === true && details.status === "started";
|
|
5675
6071
|
}
|
|
5676
|
-
function withDiagnosticTerminalType(response, terminalType) {
|
|
5677
|
-
Object.defineProperty(response, "diagnosticTerminalType", {
|
|
5678
|
-
configurable: true,
|
|
5679
|
-
enumerable: false,
|
|
5680
|
-
value: terminalType
|
|
5681
|
-
});
|
|
5682
|
-
return response;
|
|
5683
|
-
}
|
|
5684
|
-
function withDiagnosticFailureDisposition(response, disposition) {
|
|
5685
|
-
if (!disposition) return response;
|
|
5686
|
-
withDiagnosticTerminalType(response, disposition === "blocked" ? "blocked" : "error");
|
|
5687
|
-
if (disposition !== "blocked") Object.defineProperty(response, "diagnosticTerminalReason", {
|
|
5688
|
-
configurable: true,
|
|
5689
|
-
enumerable: false,
|
|
5690
|
-
value: disposition
|
|
5691
|
-
});
|
|
5692
|
-
return response;
|
|
5693
|
-
}
|
|
5694
|
-
function withDynamicToolTermination(response, terminate) {
|
|
5695
|
-
if (!terminate) return response;
|
|
5696
|
-
Object.defineProperty(response, "terminate", {
|
|
5697
|
-
configurable: true,
|
|
5698
|
-
enumerable: false,
|
|
5699
|
-
value: true
|
|
5700
|
-
});
|
|
5701
|
-
return response;
|
|
5702
|
-
}
|
|
5703
|
-
function withDynamicToolAsyncStarted(response, asyncStarted) {
|
|
5704
|
-
if (!asyncStarted) return response;
|
|
5705
|
-
Object.defineProperty(response, "asyncStarted", {
|
|
5706
|
-
configurable: true,
|
|
5707
|
-
enumerable: false,
|
|
5708
|
-
value: true
|
|
5709
|
-
});
|
|
5710
|
-
return response;
|
|
5711
|
-
}
|
|
5712
6072
|
function normalizeToolResultMaxChars(maxChars) {
|
|
5713
6073
|
return typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS;
|
|
5714
6074
|
}
|
|
@@ -5806,53 +6166,9 @@ function readFirstString(record, keys) {
|
|
|
5806
6166
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
5807
6167
|
}
|
|
5808
6168
|
}
|
|
5809
|
-
function collectMediaUrls(record) {
|
|
5810
|
-
const urls = [];
|
|
5811
|
-
const pushMediaUrl = (value) => {
|
|
5812
|
-
if (typeof value === "string" && value.trim()) urls.push(value.trim());
|
|
5813
|
-
};
|
|
5814
|
-
const pushAttachment = (value) => {
|
|
5815
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
5816
|
-
const attachment = value;
|
|
5817
|
-
for (const key of [
|
|
5818
|
-
"media",
|
|
5819
|
-
"mediaUrl",
|
|
5820
|
-
"path",
|
|
5821
|
-
"filePath",
|
|
5822
|
-
"fileUrl",
|
|
5823
|
-
"url"
|
|
5824
|
-
]) pushMediaUrl(attachment[key]);
|
|
5825
|
-
};
|
|
5826
|
-
for (const key of [
|
|
5827
|
-
"media",
|
|
5828
|
-
"mediaUrl",
|
|
5829
|
-
"media_url",
|
|
5830
|
-
"path",
|
|
5831
|
-
"filePath",
|
|
5832
|
-
"fileUrl",
|
|
5833
|
-
"imageUrl",
|
|
5834
|
-
"image_url"
|
|
5835
|
-
]) {
|
|
5836
|
-
const value = record[key];
|
|
5837
|
-
pushMediaUrl(value);
|
|
5838
|
-
}
|
|
5839
|
-
for (const key of [
|
|
5840
|
-
"mediaUrls",
|
|
5841
|
-
"media_urls",
|
|
5842
|
-
"imageUrls",
|
|
5843
|
-
"image_urls"
|
|
5844
|
-
]) {
|
|
5845
|
-
const value = record[key];
|
|
5846
|
-
if (!Array.isArray(value)) continue;
|
|
5847
|
-
for (const entry of value) pushMediaUrl(entry);
|
|
5848
|
-
}
|
|
5849
|
-
const attachments = record.attachments;
|
|
5850
|
-
if (Array.isArray(attachments)) for (const attachment of attachments) pushAttachment(attachment);
|
|
5851
|
-
return urls;
|
|
5852
|
-
}
|
|
5853
6169
|
function isCronAddAction(args) {
|
|
5854
6170
|
const action = args.action;
|
|
5855
6171
|
return typeof action === "string" && action.trim().toLowerCase() === "add";
|
|
5856
6172
|
}
|
|
5857
6173
|
//#endregion
|
|
5858
|
-
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 };
|