@openclaw/codex 2026.10.2 → 2026.10.5-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.setup/{account-usage-runtime-DPXsyONK.mjs → account-usage-runtime-B6S1BQU7.mjs} +8 -7
- package/dist/.setup/{app-server-policy-Bg4FZw1f.mjs → app-server-policy-D_zv8J7V.mjs} +3 -4
- package/dist/.setup/{apply-CQ3brq3z.mjs → apply-Cc1DfWPz.mjs} +40 -51
- package/dist/.setup/{attempt-client-cleanup-DPUGAWP2.mjs → attempt-client-cleanup-CxX4cTSi.mjs} +6 -6
- package/dist/.setup/attempt-terminal-jO5IMj9K.mjs +2 -0
- package/dist/.setup/{attempt-workspace-context-GllQy8mw.mjs → attempt-workspace-context-CcwP3DA3.mjs} +21 -18
- package/dist/.setup/{auth-binding-BoMwpuPN.mjs → auth-binding-BaxlhaNx.mjs} +2 -2
- package/dist/.setup/{auth-cache-key-BE1HXsNo.mjs → auth-cache-key-tENCQOub.mjs} +30 -41
- package/dist/.setup/{binding-connection-C-08ez1w.mjs → binding-connection-B7HAAZSI.mjs} +2 -2
- package/dist/.setup/{binding-connection.runtime-Dn2p-fdy.mjs → binding-connection.runtime-DAXn7iFl.mjs} +6 -5
- package/dist/.setup/{bounded-turn-9YflG60n.mjs → bounded-turn-DxtRqV8t.mjs} +21 -17
- package/dist/.setup/{bounded-turn-isolation-DklXNk3k.mjs → bounded-turn-isolation-BfDAo-qZ.mjs} +4 -4
- package/dist/.setup/{build-state-LS-A65iX.mjs → build-state-CMYRQ_We.mjs} +1 -1
- package/dist/.setup/{capabilities-kQntafj_.mjs → capabilities-DbOzOglY.mjs} +1 -1
- package/dist/.setup/{client-catalog-response-BprQziLV.mjs → client-catalog-response-B7oqHQGO.mjs} +25 -32
- package/dist/.setup/{client-ChJtGEdJ.mjs → client-zrCKOu_E.mjs} +198 -247
- package/dist/.setup/{command-dispatch-BoC9hvFs.mjs → command-dispatch-ImiHvcyW.mjs} +3 -4
- package/dist/.setup/{command-formatters-DV3DHhdr.mjs → command-formatters-xW9KpoSZ.mjs} +16 -26
- package/dist/.setup/{command-handlers-BVX2quUD.mjs → command-handlers-B8CT4ngS.mjs} +270 -360
- package/dist/.setup/{command-rpc-M4gYsRiV.mjs → command-rpc-HtQjCQBg.mjs} +19 -18
- package/dist/.setup/{compact-D3JNLG_b.mjs → compact-Do9HPeDr.mjs} +41 -61
- package/dist/.setup/{computer-use-CF0jsDi1.mjs → computer-use-CeF5GuCX.mjs} +109 -147
- package/dist/.setup/{config-0Gxk3_eq.mjs → config-C1YVVLEA.mjs} +32 -59
- package/dist/.setup/config-contracts.shared-D85SGEuq.mjs +4 -0
- package/dist/.setup/{config-options-HG_Rr3W_.mjs → config-options-DDzeNQ8m.mjs} +52 -90
- package/dist/.setup/{config-parsing-BQq-CHTF.mjs → config-parsing-DsWSCPHO.mjs} +19 -55
- package/dist/.setup/{config-runtime-cUYrqu_a.mjs → config-runtime-DWzK-k0n.mjs} +2 -2
- package/dist/.setup/{config-security-KAPScztc.mjs → config-security-clxAZYWX.mjs} +35 -39
- package/dist/.setup/config-utils-DN_YT7dE.mjs +47 -0
- package/dist/.setup/{context-engine-projection-CsgYSQhW.mjs → context-engine-projection-ps-yn_ci.mjs} +10 -22
- package/dist/.setup/{conversation-binding-7kceEUf0.mjs → conversation-binding-D78aEdkE.mjs} +85 -119
- package/dist/.setup/{conversation-binding-data-DJ4ix-W5.mjs → conversation-binding-data-UgLuTSQT.mjs} +9 -8
- package/dist/.setup/{conversation-control-BpJpPo0t.mjs → conversation-control-GXM3unE4.mjs} +7 -10
- package/dist/.setup/{cyber-failover-Bb1J6hS1.mjs → cyber-failover-D4dgNfFf.mjs} +2 -5
- package/dist/.setup/{cyber-failover-notice-XR03ef31.mjs → cyber-failover-notice-ljgGGL0s.mjs} +1 -1
- package/dist/.setup/{doctor-start-options-DPW9JxIE.mjs → doctor-start-options-JjTAl-q_.mjs} +5 -5
- package/dist/.setup/{dynamic-tool-build-BCi00feJ.mjs → dynamic-tool-build-BMiiFROa.mjs} +57 -69
- package/dist/.setup/{dynamic-tools-DUv6Sr6h.mjs → dynamic-tools-DPPHrGKB.mjs} +680 -709
- package/dist/.setup/{effective-mcp-catalog-CIdbHp1t.mjs → effective-mcp-catalog-x3JVEcJp.mjs} +11 -20
- package/dist/.setup/{elicitation-input-r9axtppm.mjs → elicitation-input-DXG_qaXr.mjs} +14 -20
- package/dist/.setup/{ephemeral-turn-U4th15a6.mjs → ephemeral-turn-Dk0wWoqi.mjs} +3 -3
- package/dist/.setup/{event-projector-assistant-message-BwG5TUYW.mjs → event-projector-assistant-message-CLMKN4x-.mjs} +2 -2
- package/dist/.setup/{event-projector-usage-03R0I2dM.mjs → event-projector-usage-BpKfdoTm.mjs} +20 -33
- package/dist/.setup/{event-projector-values-DTqZHizf.mjs → event-projector-values-CLvG1Hil.mjs} +4 -5
- package/dist/.setup/history-rejection-B4nOXNXF.mjs +13 -0
- package/dist/.setup/{inference-context-D-9gWlTR.mjs → inference-context-DNlGwzwy.mjs} +10 -11
- package/dist/.setup/{inference-dispatch-B5UKEUyC.mjs → inference-dispatch-CO6tTs4q.mjs} +5 -4
- package/dist/.setup/{inference-metadata-CgIf7sOx.mjs → inference-metadata-DCvcG_N_.mjs} +36 -41
- package/dist/.setup/{inference-proxy-DUO5E2QQ.mjs → inference-proxy-BDWV0djM.mjs} +10 -14
- package/dist/.setup/{isolated-completion-C6QKyyDS.mjs → isolated-completion-z0ii1a_V.mjs} +8 -8
- package/dist/.setup/local-runtime-attribution-CZz2DJ4q.mjs +15 -0
- package/dist/.setup/{managed-binary-C1VFoVPf.mjs → managed-binary-BtIjV7yN.mjs} +16 -36
- package/dist/.setup/{mcp-app-session-preparation-JFCtSkRp.mjs → mcp-app-session-preparation-GTx-bRGN.mjs} +24 -25
- package/dist/.setup/{mcp-tool-metadata-j10HhfTC.mjs → mcp-tool-metadata-DW41snf3.mjs} +1 -1
- package/dist/.setup/{model-catalog-DD-evckc.mjs → model-catalog-VUdFUqnH.mjs} +8 -8
- package/dist/.setup/{models-DaW-7tld.mjs → models-Dph0RqpR.mjs} +6 -10
- package/dist/.setup/{native-auth-Cjb9y7IZ.mjs → native-auth-esN5MdIv.mjs} +5 -5
- package/dist/.setup/{native-execution-policy-ClzpdG3D.mjs → native-execution-policy-MXEe7PB3.mjs} +11 -28
- package/dist/.setup/native-hook-relay-CSZpcjwq.mjs +561 -0
- package/dist/.setup/{native-mcp-app-ChpyWmL-.mjs → native-mcp-app-CDrrBaTE.mjs} +6 -6
- package/dist/.setup/native-skill-isolation-BAatEJZb.mjs +391 -0
- package/dist/.setup/{native-subagent-monitor-wh_-K3Vo.mjs → native-subagent-monitor-D5O_o07p.mjs} +505 -639
- package/dist/.setup/{native-subagent-pending-assignments-BDqhOdJp.mjs → native-subagent-pending-assignments-DmVhedNs.mjs} +11 -23
- package/dist/.setup/{native-task-assignments-C6KVqiN1.mjs → native-task-assignments-DimsQ9Qa.mjs} +73 -27
- package/dist/.setup/{node-cli-sessions-B5oNDkUS.mjs → node-cli-sessions-CzKg2nGq.mjs} +18 -26
- package/dist/.setup/{node-exec-server.runtime-0xSEjKYt.mjs → node-exec-server.runtime-BWkULhBM.mjs} +5 -8
- package/dist/.setup/{plan-CUHSuL6_.mjs → plan-QNW5zpFp.mjs} +40 -50
- package/dist/.setup/{plugin-activation-BcyBv8PG.mjs → plugin-activation-D7WkJMvU.mjs} +4 -5
- package/dist/.setup/{plugin-app-cache-key-BW8rd98k.mjs → plugin-app-cache-key-Bg9DbDI6.mjs} +18 -23
- package/dist/.setup/{plugin-approval-roundtrip-bPQbG8BI.mjs → plugin-approval-roundtrip-DOQ3Tibe.mjs} +4 -6
- package/dist/.setup/{plugin-inventory-Bf0_piTw.mjs → plugin-inventory-CYgdo14P.mjs} +28 -43
- package/dist/.setup/{plugin-marketplace-discovery-SQPhSuic.mjs → plugin-marketplace-discovery-BblHM3DU.mjs} +7 -10
- package/dist/.setup/{plugin-metadata-cache-DWnB7VMj.mjs → plugin-metadata-cache-Bi8TAPwX.mjs} +5 -10
- package/dist/.setup/project-doc-thread-config-BVcvnps8.mjs +490 -0
- package/dist/.setup/{provider-Ct6-1lsF.mjs → provider-BG5nXBnY.mjs} +4 -4
- package/dist/.setup/{provider-capabilities-DhkeeHJd.mjs → provider-capabilities-DtJKxDf6.mjs} +1 -1
- package/dist/.setup/{rate-limits-CMPy24F5.mjs → rate-limits-CI8pTPqL.mjs} +15 -32
- package/dist/.setup/reasoning-effort-CS9kKWB9.mjs +37 -0
- package/dist/.setup/{request-CZkKkN6c.mjs → request-6piO0U_P.mjs} +28 -48
- package/dist/.setup/{rpc-error-hmWM21RB.mjs → rpc-error-C98SHchT.mjs} +3 -6
- package/dist/.setup/{run-attempt-C6-678kR.mjs → run-attempt-DejV9vDW.mjs} +927 -860
- package/dist/.setup/{run-attempt-lifecycle-BuZgVbDA.mjs → run-attempt-lifecycle-KN4oh-7O.mjs} +13 -4
- package/dist/.setup/{runtime-artifact-CUTOOnKw.mjs → runtime-artifact-C13pR7tD.mjs} +18 -22
- package/dist/.setup/{runtime-artifact-connection-CADz3_BY.mjs → runtime-artifact-connection-jS5DeCok.mjs} +3 -3
- package/dist/.setup/{sandbox-guard-WqE8yPge.mjs → sandbox-guard-DIImwCy1.mjs} +4 -6
- package/dist/.setup/service-tier-normalization-BPgw2Wdt.mjs +9 -0
- package/dist/.setup/{session-binding-Ce7BqiPN.mjs → session-binding-JEHNMPsr.mjs} +13 -40
- package/dist/.setup/{session-binding-record-BZB8RgcF.mjs → session-binding-record-DU_0biMD.mjs} +4 -4
- package/dist/.setup/{session-binding-record-codec-z4ZpDn6E.mjs → session-binding-record-codec-BpH8u_G9.mjs} +16 -39
- package/dist/.setup/{session-binding-sidecars-BrdV95Fg.mjs → session-binding-sidecars-DD-enC8w.mjs} +49 -54
- package/dist/.setup/{session-catalog-adoption-CPIP4S_y.mjs → session-catalog-adoption-Ca3OYGYM.mjs} +23 -27
- package/dist/.setup/{session-catalog-archive-DcgwvSbF.mjs → session-catalog-archive-Bv6gV7x9.mjs} +6 -6
- package/dist/.setup/{session-catalog-events-Cj-b2FpC.mjs → session-catalog-events-BziNSfLx.mjs} +6 -5
- package/dist/.setup/{session-catalog-index-BnvC43eZ.mjs → session-catalog-index-BmaZFGEH.mjs} +111 -105
- package/dist/.setup/{session-catalog-index-order-nhyO0wdW.mjs → session-catalog-index-order-CTfoefow.mjs} +28 -5
- package/dist/.setup/{session-catalog-limits-DEU36Qlo.mjs → session-catalog-limits-BynIoOYm.mjs} +1 -1
- package/dist/.setup/{session-catalog-list-request-tXephOB4.mjs → session-catalog-list-request-D4MYNgDB.mjs} +11 -8
- package/dist/.setup/{session-catalog-native-projection-DCtsWKSF.mjs → session-catalog-native-projection-4CsufcT8.mjs} +5 -61
- package/dist/.setup/{session-catalog-node-adoption-7-WEQTB1.mjs → session-catalog-node-adoption-BdP9Zjqo.mjs} +2 -2
- package/dist/.setup/{session-catalog-node-continue-DLWBwAAO.mjs → session-catalog-node-continue-CNJibO4_.mjs} +27 -49
- package/dist/.setup/{session-catalog-node-lookup-Uufckwcm.mjs → session-catalog-node-lookup-BH_DBGa9.mjs} +3 -3
- package/dist/.setup/{session-catalog-parsing-B-qWJh2x.mjs → session-catalog-parsing-DkeoVn60.mjs} +9 -19
- package/dist/.setup/{session-catalog-projection-Cn0bLb-d.mjs → session-catalog-projection-CcIdJdsd.mjs} +8 -22
- package/dist/.setup/session-catalog-source-CdPqS9sa.mjs +58 -0
- package/dist/.setup/{session-cli-kH4vuGod.mjs → session-cli-GwU0izn-.mjs} +20 -41
- package/dist/.setup/{session-history-BogBHKLk.mjs → session-history-BO6i_pfk.mjs} +1 -1
- package/dist/.setup/{session-history-import-NWKPa4ZO.mjs → session-history-import-CaO8s6ta.mjs} +4 -4
- package/dist/.setup/{session-history-read-CNN8zwbD.mjs → session-history-read-BI_2jRED.mjs} +4 -15
- package/dist/.setup/{session-history.worker-Cq_4DYgN.mjs → session-history.worker-DJQfek5p.mjs} +147 -159
- package/dist/.setup/{session-initialization-D6dtFQkE.mjs → session-initialization-Bi_2DKhc.mjs} +3 -5
- package/dist/.setup/{session-permission-policy-BxIUJ0Zd.mjs → session-permission-policy-CD2MAWU8.mjs} +12 -20
- package/dist/.setup/{session-retirement-Bo81t4Fb.mjs → session-retirement-CcWVhLVr.mjs} +7 -7
- package/dist/.setup/{settled-turn-context-BZsMjkIO.mjs → settled-turn-context-BPL6D3RO.mjs} +1 -1
- package/dist/.setup/{settled-turn-finalizer-CFARFqNd.mjs → settled-turn-finalizer-BezKSo-Y.mjs} +12 -11
- package/dist/.setup/{shared-client-5NUq43f3.mjs → shared-client-BjiqpbC5.mjs} +164 -227
- package/dist/.setup/{side-question-oTlegAz3.mjs → side-question-YQ5FObkD.mjs} +116 -137
- package/dist/.setup/{source-1cabQi3x.mjs → source-Ce3XIIsU.mjs} +37 -56
- package/dist/.setup/thread-archive-guard-LKdwQKyr.mjs +56 -0
- package/dist/.setup/{thread-lifecycle--EPPTNTe.mjs → thread-lifecycle-run-BWy0LDLB.mjs} +338 -478
- package/dist/.setup/thread-model-selection-DcVebr8N.mjs +61 -0
- package/dist/.setup/{thread-ownership-T7T_iffm.mjs → thread-ownership-5X4197vk.mjs} +5 -5
- package/dist/.setup/{thread-ownership-queue-Dp24d7-F.mjs → thread-ownership-queue-CeyYScUC.mjs} +5 -6
- package/dist/.setup/{thread-policy-VtqLex1l.mjs → thread-policy-BEecBTJM.mjs} +4 -4
- package/dist/.setup/thread-requests-BgzJwJtS.mjs +745 -0
- package/dist/.setup/{thread-resume-buZujRcC.mjs → thread-resume-BXY_6o6Z.mjs} +5 -5
- package/dist/.setup/{timeout-CXOfNoaX.mjs → timeout-D0mEWSGG.mjs} +6 -12
- package/dist/.setup/{transcript-history-projection-B9a7HwUV.mjs → transcript-history-projection-DfKcoZCB.mjs} +3 -4
- package/dist/.setup/{transcript-mirror-BfLRARNL.mjs → transcript-mirror-Bee2o6jj.mjs} +68 -68
- package/dist/.setup/{transcript-mirror-attestation-YSegaktH.mjs → transcript-mirror-attestation-BzUXnrvv.mjs} +5 -2
- package/dist/.setup/{transport-stdio-a0HDa2Mj.mjs → transport-stdio-CRWTkKOn.mjs} +67 -77
- package/dist/.setup/{transport-websocket-zqJ0Cmtn.mjs → transport-websocket-DIyPfkHo.mjs} +8 -16
- package/dist/.setup/{turn-router-BOKSJmfw.mjs → turn-router-D8DLkZgH.mjs} +30 -31
- package/dist/.setup/upstream-prompt-provenance-Ccd9MFRJ.mjs +44 -0
- package/dist/.setup/{upstream-session-fork-C4sbIoZb.mjs → upstream-session-fork-DQZbaYMK.mjs} +50 -43
- package/dist/.setup/{usage-Cq21xTMZ.mjs → usage-DVR5mNJK.mjs} +4 -4
- package/dist/.setup/{usage-limit-error-CGJUoQu8.mjs → usage-limit-error-BEONfgt-.mjs} +12 -21
- package/dist/.setup/web-search-item-CvokaZOo.mjs +20 -0
- package/dist/.setup/{web-search-provider.runtime-DrVE7-Br.mjs → web-search-provider.runtime-CffHOpYY.mjs} +3 -3
- package/dist/.setup/{workspace-write-sandbox-probe-DmUHLrrl.mjs → workspace-write-sandbox-probe-BDWO2Qkh.mjs} +4 -4
- package/dist/api.js +15 -15
- package/dist/catalog-page.worker.js +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/doctor-contract-api.js +5 -6
- package/dist/harness.js +32 -61
- package/dist/index.js +302 -416
- package/dist/media-understanding-provider.js +32 -43
- package/dist/migration-provider-api.js +1 -1
- package/dist/native-session-binding-api.js +1 -1
- package/dist/native-tool-policy.js +23 -1
- package/dist/provider-discovery.js +1 -1
- package/dist/session-history-worker-runtime.js +52 -31
- package/dist/session-history.worker.js +1 -1
- package/openclaw.plugin.json +20 -3
- package/package.json +7 -7
- package/dist/.setup/attempt-terminal-gZrxQ35N.mjs +0 -5
- package/dist/.setup/plugin-thread-config-deadline-C5i1S6RN.mjs +0 -124
- package/dist/.setup/service-tier-normalization-Cei89SW6.mjs +0 -15
- package/dist/.setup/shell-dynamic-tools-Bmedt1QE.mjs +0 -85
- package/dist/.setup/thread-archive-guard-C9frICzX.mjs +0 -60
- package/dist/.setup/thread-requests-Bj_Lixmv.mjs +0 -2111
- package/dist/.setup/upstream-prompt-provenance-fXkKbPVC.mjs +0 -37
|
@@ -1,39 +1,46 @@
|
|
|
1
1
|
import { i as isJsonObject, r as flattenCodexDynamicToolFunctions, t as CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE } from "./protocol-CANUwXJ3.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { o as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
2
|
+
import { n as codexSandboxPolicyForTurn } from "./config-options-DDzeNQ8m.mjs";
|
|
3
|
+
import { o as formatCodexDisplayText } from "./command-formatters-xW9KpoSZ.mjs";
|
|
4
|
+
import { o as readItem } from "./event-projector-values-CLvG1Hil.mjs";
|
|
5
|
+
import { s as codexAppIdentityKey } from "./plugin-app-cache-key-Bg9DbDI6.mjs";
|
|
6
|
+
import "./config-C1YVVLEA.mjs";
|
|
7
|
+
import { r as invalidInlineImageText } from "./session-history-read-BI_2jRED.mjs";
|
|
8
|
+
import { o as neutralizeCodexExplicitMentionSigils } from "./context-engine-projection-ps-yn_ci.mjs";
|
|
9
|
+
import { U as protectCodexAppServerLiveThread, _ as retainSharedCodexAppServerClientIfCurrent } from "./shared-client-BjiqpbC5.mjs";
|
|
10
|
+
import { A as createFailedDynamicToolResponse, j as failedToolResult, k as resolveCodexToolAbortTerminalReason, l as CodexServerRequestResolvedError, w as createCodexElicitationResponse, x as readCodexTurnCompletedNotification } from "./client-zrCKOu_E.mjs";
|
|
8
11
|
import { t as codexWebSocketDataToBuffer } from "./websocket-data-XlRvt8Ys.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import { S as shouldClearTerminalPresentationForNativeItem, _ as itemName, a as isCodexNotificationForTurn, c as readCodexNotificationTurnId, d as auditNativeToolUnfinishedStatus, l as auditNativeToolName, s as readCodexNotificationThreadId, u as auditNativeToolTerminalStatus, v as itemStatus } from "./turn-router-D8DLkZgH.mjs";
|
|
13
|
+
import { n as resolveCodexAppServerReasoningEffort, t as readCodexSupportedReasoningEfforts } from "./reasoning-effort-CS9kKWB9.mjs";
|
|
14
|
+
import { V as joinPresentSections } from "./thread-requests-BgzJwJtS.mjs";
|
|
15
|
+
import { r as resolveCodexAppServerRequestModelSelection, t as CODEX_NATIVE_PERSONALITY_NONE } from "./thread-model-selection-DcVebr8N.mjs";
|
|
16
|
+
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-DOQ3Tibe.mjs";
|
|
17
|
+
import { d as isCodexPassThroughMediaSource, f as mapCodexAppServerLocalWorkspacePath, p as mapCodexAppServerRemoteWorkspacePath } from "./dynamic-tool-build-BMiiFROa.mjs";
|
|
18
|
+
import { c as emitCodexNativePreToolUseFailureDiagnostic } from "./native-hook-relay-CSZpcjwq.mjs";
|
|
13
19
|
import { createRequire } from "node:module";
|
|
14
|
-
import { asNonArrayRecord, asOptionalRecord, filterStringEntries, isRecord, normalizeTrimmedStringList, readNonBlankString, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
20
|
+
import { asNonArrayRecord, asOptionalRecord, filterStringEntries, isRecord, normalizeOptionalString, normalizeTrimmedStringList, readNonBlankString, readStringField } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
15
21
|
import { createHash, randomUUID } from "node:crypto";
|
|
16
22
|
import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
|
|
17
23
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
18
24
|
import { signalProcessTree, spawnTerminalPty } from "openclaw/plugin-sdk/process-runtime";
|
|
25
|
+
import { DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, estimateToolResultTextChars, resolveLiveToolResultMaxChars, safeParseJson, sliceToolResultTextToBudget, sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
19
26
|
import path, { posix } from "node:path";
|
|
20
27
|
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
21
28
|
import { createBoundedRemoteFileReader, isPathStrictlyInside, root } from "openclaw/plugin-sdk/file-access-runtime";
|
|
22
29
|
import { once } from "node:events";
|
|
23
30
|
import { spawn } from "node:child_process";
|
|
24
31
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
25
|
-
import {
|
|
26
|
-
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
32
|
+
import { emitTrustedDiagnosticEvent, hasPendingInternalDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
27
33
|
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
28
|
-
import {
|
|
34
|
+
import { raceWithTimeout } from "openclaw/plugin-sdk/time-runtime";
|
|
29
35
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
36
|
+
import { buildHarnessVisibleReplyGuidance, buildTemporalContextText, captureToolAuthoredSourceReply, createAgentToolResultMiddlewareRunner, createCodexAppServerToolResultExtensionRunner, embeddedAgentLog, extractMessagingToolSend, extractMessagingToolSendResult, finalizeAgentToolAvailability, finalizeToolTerminalPresentation, formatApprovalDisplayPath, formatToolExecutionErrorMessage, getBeforeToolCallFailureDisposition, getChannelAgentToolMeta, getPluginToolMeta, getPluginToolSideEffectOwnerKey, hasNativeHookRelayInvocation, invokeNativeHookRelay, isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, isDeliveredMessagingToolSendToCurrentSource, isMessagingTool, isReplaySafeToolCall, isToolResultError, isToolWrappedWithBeforeToolCallHook, projectPluginMessageDeliveryFact, projectRuntimeToolInputSchema, readEmbeddedMessageDeliveryFact, readToolOperatorHint, resolveNativeHookRelayDeferredToolApproval, resolveToolExecutionErrorKind, runAgentHarnessAfterToolCallHook, sanitizeToolResult, setBeforeToolCallDiagnosticsEnabled, wrapToolWithBeforeToolCallHook } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
30
37
|
import { sanitizeInlineImageDataUrl } from "openclaw/plugin-sdk/inline-image-data-url-runtime";
|
|
31
38
|
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
32
39
|
import { formatErrorMessage as formatErrorMessage$2, redactSensitiveText as redactSensitiveText$1 } from "openclaw/plugin-sdk/security-runtime";
|
|
33
40
|
import { collectAgentHarnessMessagingMediaUrls, copyInternalToolResultState, createAgentHarnessToolExecutionBoundaryRegistry, extractMessagingToolSourceReplyPayload, getCoreTtsToolResultMediaUrls, mapAgentHarnessMessagingMediaValues, normalizeAcceptedSessionSpawnResult, recordAgentHarnessToolResultTelemetry, runAgentHarnessToolInvocation } from "openclaw/plugin-sdk/agent-harness-tool-runtime";
|
|
34
41
|
import { isIP } from "node:net";
|
|
35
|
-
import { formatMcpCodexApprovalRemedy, requiresMcpCodexToolApproval, resolveProjectedMcpCodexToolApprovalMode } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
36
42
|
import { SANDBOX_COMMAND_MAX_BUFFER_BYTES, buildRemoteCommand, prepareSandboxProcessCleanup, sanitizeEnvVars } from "openclaw/plugin-sdk/sandbox";
|
|
43
|
+
import { formatMcpCodexApprovalRemedy, requiresMcpCodexToolApproval, resolveProjectedMcpCodexToolApprovalMode } from "openclaw/plugin-sdk/codex-mcp-projection";
|
|
37
44
|
import { SsrFBlockedError, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
38
45
|
import { getMediaDir } from "openclaw/plugin-sdk/media-runtime";
|
|
39
46
|
import { normalizeMediaReferenceForComparison, saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
|
@@ -86,10 +93,10 @@ var CodexNativeToolLifecycleProjector = class {
|
|
|
86
93
|
if (candidate) return;
|
|
87
94
|
candidate = mcpToolCall;
|
|
88
95
|
}
|
|
89
|
-
if (!candidate || candidate.status !== "inProgress" ||
|
|
96
|
+
if (!candidate || candidate.status !== "inProgress" || !serverName.trim() || typeof candidate.tool !== "string" || !candidate.tool.trim() || candidate.arguments === void 0 || candidate.appContext != null || candidate.pluginId != null) return;
|
|
90
97
|
return {
|
|
91
98
|
id: candidate.id,
|
|
92
|
-
server:
|
|
99
|
+
server: serverName,
|
|
93
100
|
tool: candidate.tool,
|
|
94
101
|
arguments: candidate.arguments
|
|
95
102
|
};
|
|
@@ -410,6 +417,55 @@ async function assertGatewayManagedMediaPath(value, mediaRoot) {
|
|
|
410
417
|
}
|
|
411
418
|
}
|
|
412
419
|
//#endregion
|
|
420
|
+
//#region extensions/codex/src/app-server/computer-context.ts
|
|
421
|
+
function invalidateCodexComputerFrame(contextEpoch) {
|
|
422
|
+
contextEpoch.value += 1;
|
|
423
|
+
delete contextEpoch.frameToolCallId;
|
|
424
|
+
delete contextEpoch.frameImageIdentity;
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region extensions/codex/src/app-server/user-input.ts
|
|
428
|
+
/** Builds ordered Codex user input for both new turns and same-turn steering. */
|
|
429
|
+
function buildCodexUserInput(text, images, contextImageGroups, historyProvenancePrefix) {
|
|
430
|
+
if (text !== void 0 && contextImageGroups?.length) {
|
|
431
|
+
let offset = 0;
|
|
432
|
+
return prependHistoryProvenance([...contextImageGroups.flatMap((group) => {
|
|
433
|
+
const parts = buildCodexUserInput(text.slice(offset, group.end), group.images);
|
|
434
|
+
offset = group.end;
|
|
435
|
+
return parts;
|
|
436
|
+
}), ...buildCodexUserInput(text.slice(offset), images)]);
|
|
437
|
+
}
|
|
438
|
+
const imageInputs = (images ?? []).map((image) => {
|
|
439
|
+
const imageUrl = sanitizeInlineImageDataUrl(`data:${image.mimeType};base64,${image.data}`);
|
|
440
|
+
return imageUrl ? {
|
|
441
|
+
type: "image",
|
|
442
|
+
url: imageUrl
|
|
443
|
+
} : {
|
|
444
|
+
type: "text",
|
|
445
|
+
text: invalidInlineImageText("codex user input"),
|
|
446
|
+
text_elements: []
|
|
447
|
+
};
|
|
448
|
+
});
|
|
449
|
+
return prependHistoryProvenance([...text === void 0 ? [] : [{
|
|
450
|
+
type: "text",
|
|
451
|
+
text,
|
|
452
|
+
text_elements: []
|
|
453
|
+
}], ...imageInputs]);
|
|
454
|
+
function prependHistoryProvenance(input) {
|
|
455
|
+
if (!historyProvenancePrefix) return input;
|
|
456
|
+
const firstTextIndex = input.findIndex((item) => item.type === "text");
|
|
457
|
+
if (firstTextIndex === -1) return [{
|
|
458
|
+
type: "text",
|
|
459
|
+
text: historyProvenancePrefix,
|
|
460
|
+
text_elements: []
|
|
461
|
+
}, ...input];
|
|
462
|
+
return input.map((item, index) => index === firstTextIndex && item.type === "text" ? {
|
|
463
|
+
...item,
|
|
464
|
+
text: `${historyProvenancePrefix}${item.text}`
|
|
465
|
+
} : item);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
//#endregion
|
|
413
469
|
//#region extensions/codex/src/app-server/native-process-authority.ts
|
|
414
470
|
const clients = /* @__PURE__ */ new WeakMap();
|
|
415
471
|
/** A physical client joins native call receipts to its concrete sandbox processes. */
|
|
@@ -524,17 +580,14 @@ var CodexNativeProcessClient = class {
|
|
|
524
580
|
assertAdmission();
|
|
525
581
|
const process = { terminate };
|
|
526
582
|
command.processes.add(process);
|
|
527
|
-
let settled = false;
|
|
528
583
|
return {
|
|
529
584
|
assertAdmission,
|
|
530
585
|
assertCurrent: () => {
|
|
531
586
|
command.owner.assertCurrent();
|
|
532
|
-
if (this.closed ||
|
|
587
|
+
if (this.closed || !command.processes.has(process)) throw new Error("Codex native process authority has ended");
|
|
533
588
|
},
|
|
534
589
|
settle: () => {
|
|
535
|
-
if (
|
|
536
|
-
settled = true;
|
|
537
|
-
command.processes.delete(process);
|
|
590
|
+
if (!command.processes.delete(process)) return;
|
|
538
591
|
this.forgetSettled(command);
|
|
539
592
|
},
|
|
540
593
|
fail: (error) => command.owner.reportSettlementFailure(error)
|
|
@@ -689,16 +742,133 @@ var CodexNativeProcessAuthority = class {
|
|
|
689
742
|
}
|
|
690
743
|
};
|
|
691
744
|
//#endregion
|
|
745
|
+
//#region extensions/codex/src/app-server/turn-params.ts
|
|
746
|
+
const CODEX_CURRENT_SENDER_FIELD_MAX_CHARS = 256;
|
|
747
|
+
function readCodexCurrentSender(params) {
|
|
748
|
+
const metadata = asOptionalRecord(asOptionalRecord(params.userTurnTranscriptRecorder?.message)?.["__openclaw"]);
|
|
749
|
+
const fields = [
|
|
750
|
+
"senderId",
|
|
751
|
+
"senderName",
|
|
752
|
+
"senderUsername"
|
|
753
|
+
];
|
|
754
|
+
const recorded = fields.map((key) => normalizeOptionalString(metadata?.[key]));
|
|
755
|
+
const [id, name, username] = recorded.some(Boolean) ? recorded : fields.map((key) => normalizeOptionalString(params[key]));
|
|
756
|
+
if (!id && !name && !username) return;
|
|
757
|
+
const bound = (value) => truncateUtf16Safe(value, CODEX_CURRENT_SENDER_FIELD_MAX_CHARS);
|
|
758
|
+
return {
|
|
759
|
+
...id ? { id: bound(id) } : {},
|
|
760
|
+
...name ? { name: bound(name) } : {},
|
|
761
|
+
...username ? { username: bound(username) } : {}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function buildCodexHistoryProvenancePrefix(params) {
|
|
765
|
+
const sender = readCodexCurrentSender(params);
|
|
766
|
+
return sender?.id ? neutralizeCodexExplicitMentionSigils(`[OpenClaw conversation info: sender=${JSON.stringify(sender)}]\n`) : void 0;
|
|
767
|
+
}
|
|
768
|
+
function buildTurnStartParams(params, options) {
|
|
769
|
+
const modelSelection = options.preserveNativeTurnSettings ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
770
|
+
homeScope: options.appServer.start.homeScope,
|
|
771
|
+
model: options.model ?? params.modelId,
|
|
772
|
+
modelProvider: options.modelProvider,
|
|
773
|
+
authProfileId: params.authProfileId,
|
|
774
|
+
authProfileStore: params.authProfileStore,
|
|
775
|
+
agentDir: params.agentDir,
|
|
776
|
+
config: params.config
|
|
777
|
+
});
|
|
778
|
+
const collaborationMode = modelSelection ? buildTurnCollaborationMode(params, { model: modelSelection.model }) : void 0;
|
|
779
|
+
if (collaborationMode && options.parentLocalEgress) collaborationMode.settings.developer_instructions = null;
|
|
780
|
+
const useThreadPermissionProfile = options.appServer.networkProxy && !options.sandboxPolicy;
|
|
781
|
+
const currentSender = params.trigger === "user" ? readCodexCurrentSender(params) : void 0;
|
|
782
|
+
const additionalContext = {
|
|
783
|
+
...buildCodexTemporalAdditionalContext(params, { sessionStatusAvailable: options.sessionStatusAvailable === true }),
|
|
784
|
+
openclaw_active_computer: {
|
|
785
|
+
kind: "application",
|
|
786
|
+
value: params.hostCapabilities.activeComputerContext?.() ?? "Current active computer: active_node=unknown (host presence unavailable)"
|
|
787
|
+
},
|
|
788
|
+
openclaw_source_delivery: {
|
|
789
|
+
kind: "application",
|
|
790
|
+
value: ["Current source-delivery policy for this turn (replaces earlier source-delivery guidance):", buildHarnessVisibleReplyGuidance({
|
|
791
|
+
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
|
|
792
|
+
messageToolAvailable: options.messageToolAvailable === true,
|
|
793
|
+
requireExplicitMessageTarget: options.requireExplicitMessageTarget
|
|
794
|
+
})].join("\n")
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
if (currentSender) additionalContext.openclaw_current_sender = {
|
|
798
|
+
kind: "untrusted",
|
|
799
|
+
value: JSON.stringify({ sender: currentSender })
|
|
800
|
+
};
|
|
801
|
+
if (params.permissionChange?.notice) additionalContext.openclaw_permission_change = {
|
|
802
|
+
kind: "application",
|
|
803
|
+
value: params.permissionChange.notice
|
|
804
|
+
};
|
|
805
|
+
return {
|
|
806
|
+
threadId: options.threadId,
|
|
807
|
+
...params.trigger ? { turnTrigger: params.trigger } : {},
|
|
808
|
+
input: [...buildCodexUserInput(options.promptText ?? params.prompt, params.images, options.contextImageGroups, options.historyProvenancePrefix ?? (params.trigger === "user" ? buildCodexHistoryProvenancePrefix(params) : void 0)), ...options.explicitSkillInputs ?? []],
|
|
809
|
+
additionalContext,
|
|
810
|
+
cwd: options.cwd,
|
|
811
|
+
...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
|
|
812
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
813
|
+
approvalsReviewer: options.appServer.approvalsReviewer,
|
|
814
|
+
...useThreadPermissionProfile ? {} : { sandboxPolicy: options.sandboxPolicy ?? codexSandboxPolicyForTurn(options.appServer.sandbox, options.appServer.sessionRoot ?? options.cwd, options.appServer.start?.args) },
|
|
815
|
+
...modelSelection ? {
|
|
816
|
+
model: modelSelection.model,
|
|
817
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
818
|
+
} : {},
|
|
819
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : options.clearInheritedServiceTier ? { serviceTier: null } : {},
|
|
820
|
+
...collaborationMode ? {
|
|
821
|
+
effort: collaborationMode.settings.reasoning_effort,
|
|
822
|
+
collaborationMode
|
|
823
|
+
} : {},
|
|
824
|
+
...params.requireWorkspaceOnly === true ? { environments: [] } : options.environmentSelection ? { environments: options.environmentSelection } : {}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function buildCodexTemporalAdditionalContext(params, options) {
|
|
828
|
+
return { openclaw_temporal_context: {
|
|
829
|
+
kind: "application",
|
|
830
|
+
value: buildTemporalContextText({
|
|
831
|
+
configuredTimezone: params.config?.agents?.defaults?.userTimezone,
|
|
832
|
+
sessionStatusAvailable: options.sessionStatusAvailable
|
|
833
|
+
})
|
|
834
|
+
} };
|
|
835
|
+
}
|
|
836
|
+
function buildTurnCollaborationMode(params, options = {}) {
|
|
837
|
+
const model = options.model ?? params.modelId;
|
|
838
|
+
return {
|
|
839
|
+
mode: "default",
|
|
840
|
+
settings: {
|
|
841
|
+
model,
|
|
842
|
+
reasoning_effort: resolveCodexAppServerReasoningEffort({
|
|
843
|
+
thinkLevel: params.thinkLevel,
|
|
844
|
+
modelId: model,
|
|
845
|
+
supportedReasoningEfforts: readCodexSupportedReasoningEfforts(params.model?.compat)
|
|
846
|
+
}),
|
|
847
|
+
developer_instructions: params.trigger === "cron" ? CRON_COLLABORATION_INSTRUCTIONS : null
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function buildCodexParentLocalInstructions(params, options = {}) {
|
|
852
|
+
const contextInstructions = joinPresentSections(options.personaInstructions, options.skillsInstructions, options.memoryInstructions);
|
|
853
|
+
if (params.trigger === "cron") return joinPresentSections(CRON_COLLABORATION_INSTRUCTIONS, contextInstructions);
|
|
854
|
+
return contextInstructions || null;
|
|
855
|
+
}
|
|
856
|
+
const CRON_COLLABORATION_INSTRUCTIONS = [
|
|
857
|
+
"This is an OpenClaw cron automation turn. Apply these instructions only to this scheduled job; ordinary chat turns should stay in Codex Default mode.",
|
|
858
|
+
"Execute the cron payload directly. If it asks you to run an exact command, run that command before doing any investigation, planning, memory review, or workspace bootstrap.",
|
|
859
|
+
"Use context already provided by the runtime, but do not spend time loading or re-reading workspace bootstrap, memory, or project-doc files before executing the cron payload. Inspect those files only if the payload asks for them or the command fails and they are needed to diagnose it.",
|
|
860
|
+
"Keep output concise and automation-oriented. Prefer the final command result or a short failure summary over status narration."
|
|
861
|
+
].join("\n\n");
|
|
862
|
+
//#endregion
|
|
692
863
|
//#region extensions/codex/src/app-server/native-pre-tool-use-failures.ts
|
|
693
864
|
/** Holds failures until a projector or terminal diagnostic owns their delivery. */
|
|
694
865
|
function createCodexNativePreToolUseFailureBuffer(params) {
|
|
695
866
|
const pending = [];
|
|
696
|
-
let
|
|
697
|
-
let terminalReason;
|
|
867
|
+
let fallback;
|
|
698
868
|
const emit = (failure) => emitCodexNativePreToolUseFailureDiagnostic({
|
|
699
869
|
...params,
|
|
700
870
|
failure,
|
|
701
|
-
...
|
|
871
|
+
...fallback ? { terminalReason: fallback.terminalReason ?? failure.disposition } : {}
|
|
702
872
|
});
|
|
703
873
|
const flush = () => {
|
|
704
874
|
for (const failure of pending.splice(0)) emit(failure);
|
|
@@ -706,17 +876,14 @@ function createCodexNativePreToolUseFailureBuffer(params) {
|
|
|
706
876
|
return {
|
|
707
877
|
pending,
|
|
708
878
|
get active() {
|
|
709
|
-
return
|
|
879
|
+
return fallback !== void 0;
|
|
710
880
|
},
|
|
711
881
|
record(failure) {
|
|
712
|
-
if (
|
|
882
|
+
if (fallback) emit(failure);
|
|
713
883
|
else pending.push(failure);
|
|
714
884
|
},
|
|
715
885
|
activateFallback(wasAborted) {
|
|
716
|
-
|
|
717
|
-
terminalReason = wasAborted ? resolveCodexToolAbortTerminalReason(params.signal) : void 0;
|
|
718
|
-
active = true;
|
|
719
|
-
}
|
|
886
|
+
fallback ??= { terminalReason: wasAborted ? resolveCodexToolAbortTerminalReason(params.signal) : void 0 };
|
|
720
887
|
flush();
|
|
721
888
|
},
|
|
722
889
|
flush
|
|
@@ -1071,11 +1238,6 @@ function requireString(value, label) {
|
|
|
1071
1238
|
if (typeof value !== "string" || !value) throw new Error(`${label} must be a non-empty string.`);
|
|
1072
1239
|
return value;
|
|
1073
1240
|
}
|
|
1074
|
-
/** Validates a base64 payload parameter as a string; decoding happens at call sites. */
|
|
1075
|
-
function requireBase64String(value, label) {
|
|
1076
|
-
if (typeof value !== "string") throw new Error(`${label} must be a string.`);
|
|
1077
|
-
return value;
|
|
1078
|
-
}
|
|
1079
1241
|
function requireNumber(value, label) {
|
|
1080
1242
|
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${label} must be a finite number.`);
|
|
1081
1243
|
return value;
|
|
@@ -1118,34 +1280,28 @@ function sendError(send, id, code, message) {
|
|
|
1118
1280
|
const WINDOWS_DRIVE_PATH_RE = /^\/[A-Za-z]:(?:\/|$)/u;
|
|
1119
1281
|
/** Resolves one Codex exec-server PathUri into a POSIX sandbox path. */
|
|
1120
1282
|
function resolveExecServerPath(rawPath, label) {
|
|
1121
|
-
|
|
1122
|
-
try {
|
|
1123
|
-
pathUrl = new URL(rawPath);
|
|
1124
|
-
} catch (error) {
|
|
1125
|
-
throw new Error(`${label} must be a valid file URI: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1126
|
-
}
|
|
1283
|
+
const pathUrl = parseSandboxPath(() => new URL(rawPath), `${label} must be a valid file URI`);
|
|
1127
1284
|
if (pathUrl.protocol !== "file:") throw new Error(`${label} URI must use the file scheme, received ${pathUrl.protocol.slice(0, -1)}.`);
|
|
1128
1285
|
if (pathUrl.search || pathUrl.hash) throw new Error(`${label} file URI must not include a query or fragment.`);
|
|
1129
|
-
|
|
1130
|
-
try {
|
|
1131
|
-
resolved = fileURLToPath(pathUrl, { windows: false });
|
|
1132
|
-
} catch (error) {
|
|
1133
|
-
throw new Error(`${label} file URI is not valid for the sandbox: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1134
|
-
}
|
|
1286
|
+
const resolved = parseSandboxPath(() => fileURLToPath(pathUrl, { windows: false }), `${label} file URI is not valid for the sandbox`);
|
|
1135
1287
|
if (WINDOWS_DRIVE_PATH_RE.test(resolved)) throw new Error(`${label} Windows file URI is not supported by the sandbox.`);
|
|
1136
1288
|
if (resolved.includes("\0")) throw new Error(`${label} file URI must not contain a null byte.`);
|
|
1137
1289
|
return resolved;
|
|
1138
1290
|
}
|
|
1291
|
+
function parseSandboxPath(parse, message) {
|
|
1292
|
+
try {
|
|
1293
|
+
return parse();
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
throw new Error(`${message}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1139
1298
|
//#endregion
|
|
1140
1299
|
//#region extensions/codex/src/app-server/sandbox-exec-server/fs-policy.ts
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
function assertFsSandboxAccess(execServer, record, requests) {
|
|
1147
|
-
assertResolvedFsSandboxAccess(resolveFsSandboxPolicy(execServer, record), requests);
|
|
1148
|
-
}
|
|
1300
|
+
const FS_ACCESS_RANK = {
|
|
1301
|
+
read: 0,
|
|
1302
|
+
write: 1,
|
|
1303
|
+
none: 2
|
|
1304
|
+
};
|
|
1149
1305
|
/** Parses a Codex managed filesystem sandbox context into normalized access entries. */
|
|
1150
1306
|
function resolveFsSandboxPolicy(execServer, record) {
|
|
1151
1307
|
if (record.sandbox === void 0 || record.sandbox === null) return;
|
|
@@ -1182,13 +1338,8 @@ function resolveFsSandboxEntry(entry, cwd) {
|
|
|
1182
1338
|
const access = readFsAccessMode(entry.access);
|
|
1183
1339
|
const pathSpec = requireObject(entry.path, "fs sandbox entry path");
|
|
1184
1340
|
const pathType = requireString(pathSpec.type, "fs sandbox entry path type");
|
|
1185
|
-
if (pathType === "path")
|
|
1186
|
-
|
|
1187
|
-
path: normalizeSandboxAbsolutePath(resolveExecServerPath(requireString(pathSpec.path, "fs sandbox path"), "fs sandbox path"), "fs sandbox path"),
|
|
1188
|
-
access
|
|
1189
|
-
};
|
|
1190
|
-
if (pathType === "special") {
|
|
1191
|
-
const path = resolveFsSpecialPath(requireObject(pathSpec.value, "fs sandbox special path"), cwd);
|
|
1341
|
+
if (pathType === "path" || pathType === "special") {
|
|
1342
|
+
const path = pathType === "path" ? normalizeSandboxAbsolutePath(resolveExecServerPath(requireString(pathSpec.path, "fs sandbox path"), "fs sandbox path"), "fs sandbox path") : resolveFsSpecialPath(requireObject(pathSpec.value, "fs sandbox special path"), cwd);
|
|
1192
1343
|
return path === void 0 ? void 0 : {
|
|
1193
1344
|
kind: "path",
|
|
1194
1345
|
path,
|
|
@@ -1228,18 +1379,17 @@ function resolveFsSpecialPath(value, cwd) {
|
|
|
1228
1379
|
function assertResolvedFsSandboxAccess(policy, requests) {
|
|
1229
1380
|
if (!policy?.unrestricted && policy) for (const request of requests) {
|
|
1230
1381
|
const access = resolveFsAccess(policy, request.path);
|
|
1231
|
-
if (request.access === "read"
|
|
1232
|
-
if (request.access === "write" && access !== "write") throw new Error(`Codex fs sandbox denied write access to ${request.path}`);
|
|
1382
|
+
if (request.access === "read" ? access === "none" : access !== "write") throw new Error(`Codex fs sandbox denied ${request.access} access to ${request.path}`);
|
|
1233
1383
|
}
|
|
1234
1384
|
}
|
|
1235
1385
|
function resolveFsAccess(policy, rawPath) {
|
|
1236
1386
|
const target = normalizeSandboxAbsolutePath(rawPath, "fs path");
|
|
1237
1387
|
let selected;
|
|
1238
1388
|
for (const entry of policy.entries) {
|
|
1239
|
-
if (!
|
|
1389
|
+
if (!(entry.kind === "path" ? pathContains(entry.path, target) : entry.matcher.test(target))) continue;
|
|
1240
1390
|
const candidate = {
|
|
1241
|
-
specificity:
|
|
1242
|
-
rank:
|
|
1391
|
+
specificity: (entry.kind === "path" ? entry.path : entry.literalPrefix).split("/").filter(Boolean).length,
|
|
1392
|
+
rank: FS_ACCESS_RANK[entry.access],
|
|
1243
1393
|
access: entry.access
|
|
1244
1394
|
};
|
|
1245
1395
|
if (!selected || candidate.specificity > selected.specificity || candidate.specificity === selected.specificity && candidate.rank > selected.rank) selected = candidate;
|
|
@@ -1251,10 +1401,10 @@ function assertNoReadOnlyDescendant(policy, rawPath, operation) {
|
|
|
1251
1401
|
if (!policy || policy.unrestricted) return;
|
|
1252
1402
|
const target = normalizeSandboxAbsolutePath(rawPath, "fs path");
|
|
1253
1403
|
const protectedDescendant = policy.entries.find((entry) => {
|
|
1254
|
-
if (entry.access === "write"
|
|
1255
|
-
if (entry.kind === "glob") return
|
|
1404
|
+
if (entry.access === "write") return false;
|
|
1405
|
+
if (entry.kind === "glob") return pathContains(target, entry.literalPrefix) || pathContains(entry.literalPrefix, target);
|
|
1256
1406
|
const protectedPath = entry.path;
|
|
1257
|
-
return protectedPath && resolveFsAccess(policy, protectedPath) !== "write";
|
|
1407
|
+
return pathContains(target, protectedPath) && target !== protectedPath && protectedPath && resolveFsAccess(policy, protectedPath) !== "write";
|
|
1258
1408
|
});
|
|
1259
1409
|
if (protectedDescendant) {
|
|
1260
1410
|
const protectedPath = protectedDescendant.kind === "path" ? protectedDescendant.path : protectedDescendant.pattern;
|
|
@@ -1264,32 +1414,11 @@ function assertNoReadOnlyDescendant(policy, rawPath, operation) {
|
|
|
1264
1414
|
/** Normalizes and validates an absolute POSIX path inside the sandbox namespace. */
|
|
1265
1415
|
function normalizeSandboxAbsolutePath(rawPath, label) {
|
|
1266
1416
|
if (!rawPath || rawPath.includes("\0") || !rawPath.startsWith("/")) throw new Error(`${label} must be an absolute sandbox path.`);
|
|
1267
|
-
|
|
1268
|
-
return normalized === "//" ? "/" : normalized;
|
|
1417
|
+
return posix.normalize(rawPath);
|
|
1269
1418
|
}
|
|
1270
|
-
/** Returns true when target is root itself or a descendant of root. */
|
|
1271
1419
|
function pathContains(root, target) {
|
|
1272
1420
|
return root === "/" || target === root || target.startsWith(`${root}/`);
|
|
1273
1421
|
}
|
|
1274
|
-
function fsSandboxEntryMatches(entry, target) {
|
|
1275
|
-
if (entry.kind === "path") return pathContains(entry.path, target);
|
|
1276
|
-
return entry.matcher.test(target);
|
|
1277
|
-
}
|
|
1278
|
-
function fsSandboxEntryCanAffectDescendant(entry, target) {
|
|
1279
|
-
if (entry.kind === "path") return pathContains(target, entry.path) && target !== entry.path;
|
|
1280
|
-
return pathContains(target, entry.literalPrefix) || pathContains(entry.literalPrefix, target);
|
|
1281
|
-
}
|
|
1282
|
-
function fsSandboxEntrySpecificity(entry) {
|
|
1283
|
-
return pathSpecificity(entry.kind === "path" ? entry.path : entry.literalPrefix);
|
|
1284
|
-
}
|
|
1285
|
-
function pathSpecificity(filePath) {
|
|
1286
|
-
return filePath === "/" ? 0 : filePath.split("/").filter(Boolean).length;
|
|
1287
|
-
}
|
|
1288
|
-
function fsAccessRank(access) {
|
|
1289
|
-
if (access === "none") return 2;
|
|
1290
|
-
if (access === "write") return 1;
|
|
1291
|
-
return 0;
|
|
1292
|
-
}
|
|
1293
1422
|
function normalizeSandboxGlobPattern(pattern) {
|
|
1294
1423
|
if (!pattern || pattern.includes("\0") || !pattern.startsWith("/")) throw new Error("fs sandbox glob pattern must be absolute.");
|
|
1295
1424
|
return pattern.replace(/\/{2,}/gu, "/");
|
|
@@ -1329,15 +1458,10 @@ function compileSandboxGlobCharacterClass(pattern, startIndex) {
|
|
|
1329
1458
|
endIndex: index
|
|
1330
1459
|
};
|
|
1331
1460
|
if (!char || char === "/") throw new Error("fs sandbox glob character class cannot match path separators.");
|
|
1332
|
-
body +=
|
|
1461
|
+
body += char === "\\" || char === "]" || body.length === 0 && char === "^" ? `\\${char}` : char;
|
|
1333
1462
|
}
|
|
1334
1463
|
throw new Error("fs sandbox glob character class must be closed.");
|
|
1335
1464
|
}
|
|
1336
|
-
function escapeSandboxGlobCharacterClassChar(char, first) {
|
|
1337
|
-
if (char === "\\" || char === "]") return `\\${char}`;
|
|
1338
|
-
if (first && char === "^") return "\\^";
|
|
1339
|
-
return char;
|
|
1340
|
-
}
|
|
1341
1465
|
function sandboxGlobLiteralPrefix(pattern) {
|
|
1342
1466
|
const wildcardIndex = pattern.search(/[*?[]/u);
|
|
1343
1467
|
const prefix = wildcardIndex === -1 ? pattern : pattern.slice(0, wildcardIndex);
|
|
@@ -1377,6 +1501,9 @@ async function openFile(execServer, handles, params) {
|
|
|
1377
1501
|
reservedBytes: 0
|
|
1378
1502
|
};
|
|
1379
1503
|
handles.set(handleId, handle);
|
|
1504
|
+
const assertCurrent = () => {
|
|
1505
|
+
if (handles.get(handleId) !== handle || handle.closeRequested || handles.closed) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, `unknown file read handle \`${handleId}\``);
|
|
1506
|
+
};
|
|
1380
1507
|
try {
|
|
1381
1508
|
const readPolicy = await authorizePhysicalReadPath(fsBridge, fsSandboxPolicy, filePath, handle.abortController.signal);
|
|
1382
1509
|
const stat = await fsBridge.stat({
|
|
@@ -1384,7 +1511,7 @@ async function openFile(execServer, handles, params) {
|
|
|
1384
1511
|
signal: handle.abortController.signal,
|
|
1385
1512
|
...readPolicy
|
|
1386
1513
|
});
|
|
1387
|
-
|
|
1514
|
+
assertCurrent();
|
|
1388
1515
|
if (!stat) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "file not found");
|
|
1389
1516
|
if (stat.type !== "file") throw new JsonRpcProtocolError(-32600, "file read handle requires a regular file");
|
|
1390
1517
|
if (!Number.isSafeInteger(stat.size) || stat.size < 0) throw new JsonRpcProtocolError(-32600, "file size must be a non-negative safe integer");
|
|
@@ -1396,7 +1523,7 @@ async function openFile(execServer, handles, params) {
|
|
|
1396
1523
|
signal: handle.abortController.signal,
|
|
1397
1524
|
...readPolicy
|
|
1398
1525
|
});
|
|
1399
|
-
|
|
1526
|
+
assertCurrent();
|
|
1400
1527
|
if (data.byteLength > handle.reservedBytes) throw new JsonRpcProtocolError(-32600, "sandbox file read exceeds the per-connection buffered file limit");
|
|
1401
1528
|
handle.reservedBytes = data.byteLength;
|
|
1402
1529
|
handle.data = data;
|
|
@@ -1452,9 +1579,10 @@ function requireFileReadHandleId(value) {
|
|
|
1452
1579
|
if (Buffer.byteLength(handleId, "utf8") > CODEX_SANDBOX_EXEC_SERVER_MAX_FILE_READ_HANDLE_ID_BYTES) throw new JsonRpcProtocolError(-32600, `file read handle ID must not exceed ${CODEX_SANDBOX_EXEC_SERVER_MAX_FILE_READ_HANDLE_ID_BYTES} bytes`);
|
|
1453
1580
|
return handleId;
|
|
1454
1581
|
}
|
|
1455
|
-
async function
|
|
1456
|
-
const record = requireObject(params,
|
|
1457
|
-
const
|
|
1582
|
+
async function readFileOrMetadata(execServer, params, method) {
|
|
1583
|
+
const record = requireObject(params, `${method} params`);
|
|
1584
|
+
const metadataOnly = method === "fs/getMetadata";
|
|
1585
|
+
const filePath = resolveExecServerPath(requireString(record.path, "path"), metadataOnly ? "metadata path" : "read path");
|
|
1458
1586
|
const fsSandboxPolicy = resolveFsSandboxPolicy(execServer, record);
|
|
1459
1587
|
assertResolvedFsSandboxAccess(fsSandboxPolicy, [{
|
|
1460
1588
|
path: filePath,
|
|
@@ -1462,11 +1590,19 @@ async function readFile(execServer, params) {
|
|
|
1462
1590
|
}]);
|
|
1463
1591
|
const fsBridge = execServer.fsBridge;
|
|
1464
1592
|
const readPolicy = await authorizePhysicalReadPath(fsBridge, fsSandboxPolicy, filePath);
|
|
1465
|
-
const stat = await fsBridge.stat({
|
|
1593
|
+
const stat = await (metadataOnly ? execServer.fsBridge : fsBridge).stat({
|
|
1466
1594
|
filePath,
|
|
1467
1595
|
...readPolicy
|
|
1468
1596
|
});
|
|
1469
1597
|
if (!stat) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "file not found");
|
|
1598
|
+
if (metadataOnly) return {
|
|
1599
|
+
isDirectory: stat.type === "directory",
|
|
1600
|
+
isFile: stat.type === "file",
|
|
1601
|
+
isSymlink: false,
|
|
1602
|
+
size: stat.size,
|
|
1603
|
+
createdAtMs: 0,
|
|
1604
|
+
modifiedAtMs: stat.mtimeMs ?? 0
|
|
1605
|
+
};
|
|
1470
1606
|
assertSandboxFileReadWithinLimit(stat);
|
|
1471
1607
|
return { dataBase64: (await fsBridge.readFile({
|
|
1472
1608
|
filePath,
|
|
@@ -1474,75 +1610,39 @@ async function readFile(execServer, params) {
|
|
|
1474
1610
|
...readPolicy
|
|
1475
1611
|
})).toString("base64") };
|
|
1476
1612
|
}
|
|
1477
|
-
async function
|
|
1478
|
-
const record = requireObject(params,
|
|
1479
|
-
const
|
|
1613
|
+
async function writeFileOrDirectory(execServer, params, method) {
|
|
1614
|
+
const record = requireObject(params, `${method} params`);
|
|
1615
|
+
const directory = method === "fs/createDirectory";
|
|
1616
|
+
const filePath = resolveExecServerPath(requireString(record.path, "path"), directory ? "create-directory path" : "write path");
|
|
1480
1617
|
const fsBridge = execServer.fsBridge;
|
|
1481
1618
|
const canonicalDestination = await fsBridge.resolvePinnedMutationTarget?.({
|
|
1482
1619
|
filePath,
|
|
1483
|
-
action: "write"
|
|
1620
|
+
action: directory ? "mkdir" : "write"
|
|
1484
1621
|
});
|
|
1485
|
-
|
|
1622
|
+
assertResolvedFsSandboxAccess(resolveFsSandboxPolicy(execServer, record), [{
|
|
1486
1623
|
path: filePath,
|
|
1487
1624
|
access: "write"
|
|
1488
1625
|
}, ...canonicalDestination ? [{
|
|
1489
1626
|
path: canonicalDestination.policyPath,
|
|
1490
1627
|
access: "write"
|
|
1491
1628
|
}] : []]);
|
|
1492
|
-
if (
|
|
1493
|
-
|
|
1629
|
+
if (!directory || record.recursive === false) {
|
|
1630
|
+
if ((await fsBridge.stat({ filePath: posix.dirname(filePath) }))?.type !== "directory") throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "parent directory not found");
|
|
1631
|
+
}
|
|
1632
|
+
if (directory) await fsBridge.mkdirp({
|
|
1494
1633
|
filePath,
|
|
1495
|
-
data: Buffer.from(requireBase64String(record.dataBase64, "dataBase64"), "base64"),
|
|
1496
|
-
mkdir: false,
|
|
1497
1634
|
pinnedPath: canonicalDestination?.pinnedPath
|
|
1498
1635
|
});
|
|
1499
|
-
|
|
1500
|
-
async function createDirectory(execServer, params) {
|
|
1501
|
-
const record = requireObject(params, "fs/createDirectory params");
|
|
1502
|
-
const filePath = resolveExecServerPath(requireString(record.path, "path"), "create-directory path");
|
|
1503
|
-
const fsBridge = execServer.fsBridge;
|
|
1504
|
-
const canonicalDestination = await fsBridge.resolvePinnedMutationTarget?.({
|
|
1505
|
-
filePath,
|
|
1506
|
-
action: "mkdir"
|
|
1507
|
-
});
|
|
1508
|
-
assertFsSandboxAccess(execServer, record, [{
|
|
1509
|
-
path: filePath,
|
|
1510
|
-
access: "write"
|
|
1511
|
-
}, ...canonicalDestination ? [{
|
|
1512
|
-
path: canonicalDestination.policyPath,
|
|
1513
|
-
access: "write"
|
|
1514
|
-
}] : []]);
|
|
1515
|
-
if (record.recursive === false) {
|
|
1516
|
-
const parentPath = posix.dirname(filePath);
|
|
1517
|
-
if ((await fsBridge.stat({ filePath: parentPath }))?.type !== "directory") throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "parent directory not found");
|
|
1518
|
-
}
|
|
1519
|
-
await fsBridge.mkdirp({
|
|
1636
|
+
else await fsBridge.writeFile({
|
|
1520
1637
|
filePath,
|
|
1638
|
+
data: decodeFileData(record.dataBase64),
|
|
1639
|
+
mkdir: false,
|
|
1521
1640
|
pinnedPath: canonicalDestination?.pinnedPath
|
|
1522
1641
|
});
|
|
1523
1642
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
const fsSandboxPolicy = resolveFsSandboxPolicy(execServer, record);
|
|
1528
|
-
assertResolvedFsSandboxAccess(fsSandboxPolicy, [{
|
|
1529
|
-
path: filePath,
|
|
1530
|
-
access: "read"
|
|
1531
|
-
}]);
|
|
1532
|
-
const readPolicy = await authorizePhysicalReadPath(execServer.fsBridge, fsSandboxPolicy, filePath);
|
|
1533
|
-
const stat = await execServer.fsBridge.stat({
|
|
1534
|
-
filePath,
|
|
1535
|
-
...readPolicy
|
|
1536
|
-
});
|
|
1537
|
-
if (!stat) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "file not found");
|
|
1538
|
-
return {
|
|
1539
|
-
isDirectory: stat.type === "directory",
|
|
1540
|
-
isFile: stat.type === "file",
|
|
1541
|
-
isSymlink: false,
|
|
1542
|
-
size: stat.size,
|
|
1543
|
-
createdAtMs: 0,
|
|
1544
|
-
modifiedAtMs: stat.mtimeMs ?? 0
|
|
1545
|
-
};
|
|
1643
|
+
function decodeFileData(value) {
|
|
1644
|
+
if (typeof value !== "string") throw new Error("dataBase64 must be a string.");
|
|
1645
|
+
return Buffer.from(value, "base64");
|
|
1546
1646
|
}
|
|
1547
1647
|
async function readDirectory(execServer, params) {
|
|
1548
1648
|
const record = requireObject(params, "fs/readDirectory params");
|
|
@@ -1783,7 +1883,7 @@ async function spawnSandboxChild(params) {
|
|
|
1783
1883
|
escalation = setTimeout(() => signalLocal("SIGKILL"), SANDBOX_CHILD_TERM_GRACE_MS);
|
|
1784
1884
|
escalation.unref?.();
|
|
1785
1885
|
}
|
|
1786
|
-
const reaped = await
|
|
1886
|
+
const reaped = await raceWithTimeout(closed.promise.then(() => true), SANDBOX_CHILD_REAP_TIMEOUT_MS, () => false, { ref: false }).finally(() => clearTimeout(escalation));
|
|
1787
1887
|
await Promise.all(localSignals);
|
|
1788
1888
|
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.`);
|
|
1789
1889
|
}
|
|
@@ -1793,7 +1893,8 @@ async function spawnSandboxChild(params) {
|
|
|
1793
1893
|
})()
|
|
1794
1894
|
};
|
|
1795
1895
|
params.owners.add(owner);
|
|
1796
|
-
|
|
1896
|
+
const releaseOwner = () => params.owners.delete(owner);
|
|
1897
|
+
settled.then(releaseOwner, releaseOwner);
|
|
1797
1898
|
const interrupt = async () => {
|
|
1798
1899
|
await ready.promise;
|
|
1799
1900
|
const interruptRemote = params.interruptRemote;
|
|
@@ -1806,7 +1907,7 @@ async function spawnSandboxChild(params) {
|
|
|
1806
1907
|
if (remainingMs <= 0) throw new Error("Sandbox process interrupt timed out waiting for remote process admission");
|
|
1807
1908
|
assertCurrent();
|
|
1808
1909
|
if (await interruptRemote(remainingMs)) return;
|
|
1809
|
-
await
|
|
1910
|
+
await raceWithTimeout(exited.promise, Math.min(SANDBOX_CHILD_INTERRUPT_POLL_MS, Math.max(0, deadline - performance.now())), () => void 0, { ref: false });
|
|
1810
1911
|
}
|
|
1811
1912
|
})();
|
|
1812
1913
|
interruptions.add(interruption);
|
|
@@ -1873,11 +1974,6 @@ async function spawnSandboxChild(params) {
|
|
|
1873
1974
|
ready.resolve();
|
|
1874
1975
|
}
|
|
1875
1976
|
}
|
|
1876
|
-
function delay(ms) {
|
|
1877
|
-
return new Promise((resolve) => {
|
|
1878
|
-
setTimeout(resolve, ms).unref?.();
|
|
1879
|
-
});
|
|
1880
|
-
}
|
|
1881
1977
|
//#endregion
|
|
1882
1978
|
//#region extensions/codex/src/app-server/sandbox-exec-server/http.ts
|
|
1883
1979
|
const SANDBOX_HTTP_STREAM_LINE_MAX_CHARS = 262144;
|
|
@@ -1961,11 +2057,12 @@ async function runSandboxHttpRequest(execServer, notifications, requestId, param
|
|
|
1961
2057
|
}
|
|
1962
2058
|
return termination;
|
|
1963
2059
|
};
|
|
2060
|
+
const cleanup = () => terminate().catch((error) => {
|
|
2061
|
+
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2062
|
+
});
|
|
1964
2063
|
const abortOnSessionClose = () => {
|
|
1965
2064
|
lifecycle.failed = true;
|
|
1966
|
-
|
|
1967
|
-
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
1968
|
-
});
|
|
2065
|
+
cleanup();
|
|
1969
2066
|
};
|
|
1970
2067
|
notifications.signal.addEventListener("abort", abortOnSessionClose, { once: true });
|
|
1971
2068
|
child.once("close", () => {
|
|
@@ -1975,35 +2072,8 @@ async function runSandboxHttpRequest(execServer, notifications, requestId, param
|
|
|
1975
2072
|
if (error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED") return;
|
|
1976
2073
|
embeddedAgentLog.warn("codex sandbox http/request stdin write failed", { error });
|
|
1977
2074
|
});
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
lifecycle,
|
|
1981
|
-
terminate,
|
|
1982
|
-
requestId,
|
|
1983
|
-
notifications,
|
|
1984
|
-
streamResponse: params.streamResponse
|
|
1985
|
-
}).then(response.resolve, response.reject);
|
|
1986
|
-
try {
|
|
1987
|
-
if (notifications.signal.aborted) abortOnSessionClose();
|
|
1988
|
-
else {
|
|
1989
|
-
owner.assertCurrent();
|
|
1990
|
-
child.stdin.end(JSON.stringify(params));
|
|
1991
|
-
}
|
|
1992
|
-
await completion.promise;
|
|
1993
|
-
await termination;
|
|
1994
|
-
} catch (error) {
|
|
1995
|
-
lifecycle.failed = true;
|
|
1996
|
-
response.reject(error);
|
|
1997
|
-
await terminate().catch((cleanupError) => {
|
|
1998
|
-
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error: cleanupError });
|
|
1999
|
-
});
|
|
2000
|
-
throw error;
|
|
2001
|
-
} finally {
|
|
2002
|
-
notifications.signal.removeEventListener("abort", abortOnSessionClose);
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
function readSandboxHttpResponse(params) {
|
|
2006
|
-
return new Promise((resolve, reject) => {
|
|
2075
|
+
const { streamResponse } = params;
|
|
2076
|
+
new Promise((resolve, reject) => {
|
|
2007
2077
|
let headerResolved = false;
|
|
2008
2078
|
let failed = false;
|
|
2009
2079
|
let childFailure = null;
|
|
@@ -2023,13 +2093,11 @@ function readSandboxHttpResponse(params) {
|
|
|
2023
2093
|
const fail = (message) => {
|
|
2024
2094
|
if (failed) return;
|
|
2025
2095
|
failed = true;
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2029
|
-
});
|
|
2096
|
+
lifecycle.failed = true;
|
|
2097
|
+
cleanup();
|
|
2030
2098
|
if (headerResolved) {
|
|
2031
|
-
|
|
2032
|
-
requestId
|
|
2099
|
+
notifications.send("http/request/bodyDelta", {
|
|
2100
|
+
requestId,
|
|
2033
2101
|
seq: lastBodySeq + 1,
|
|
2034
2102
|
deltaBase64: "",
|
|
2035
2103
|
done: true,
|
|
@@ -2049,64 +2117,58 @@ function readSandboxHttpResponse(params) {
|
|
|
2049
2117
|
}
|
|
2050
2118
|
output.chunks.push(buffer);
|
|
2051
2119
|
};
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2120
|
+
for (const stream of ["stdout", "stderr"]) {
|
|
2121
|
+
if (streamResponse) child[stream].setEncoding("utf8");
|
|
2122
|
+
child[stream].on("data", (chunk) => {
|
|
2123
|
+
if (failed) return;
|
|
2124
|
+
if (!streamResponse) {
|
|
2125
|
+
bufferOutput(stream, chunk);
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2129
|
+
if (stream === "stderr") {
|
|
2130
|
+
stderr = sliceUtf16Safe(`${stderr}${text}`, -4096);
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
stdoutBuffer += text;
|
|
2134
|
+
let newline = stdoutBuffer.indexOf("\n");
|
|
2135
|
+
while (newline >= 0) {
|
|
2136
|
+
const line = stdoutBuffer.slice(0, newline).trim();
|
|
2137
|
+
stdoutBuffer = stdoutBuffer.slice(newline + 1);
|
|
2138
|
+
if (line) try {
|
|
2139
|
+
const message = requireObject(JSON.parse(line), "http stream message");
|
|
2140
|
+
const type = requireString(message.type, "http stream message type");
|
|
2141
|
+
if (type === "headers") {
|
|
2142
|
+
headerResolved = true;
|
|
2143
|
+
resolve({
|
|
2144
|
+
status: requireNumber(message.status, "http status"),
|
|
2145
|
+
headers: readHttpHeaders(message.headers),
|
|
2146
|
+
bodyBase64: ""
|
|
2147
|
+
});
|
|
2148
|
+
} else if (type === "bodyDelta") {
|
|
2149
|
+
const seq = requireNumber(message.seq, "http body sequence");
|
|
2150
|
+
lastBodySeq = Math.max(lastBodySeq, seq);
|
|
2151
|
+
notifications.send("http/request/bodyDelta", {
|
|
2152
|
+
requestId,
|
|
2153
|
+
seq,
|
|
2154
|
+
deltaBase64: typeof message.deltaBase64 === "string" ? message.deltaBase64 : "",
|
|
2155
|
+
done: message.done === true,
|
|
2156
|
+
error: typeof message.error === "string" ? message.error : null
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
} catch (error) {
|
|
2160
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
2088
2161
|
}
|
|
2089
|
-
|
|
2090
|
-
fail(error instanceof Error ? error.message : String(error));
|
|
2162
|
+
newline = stdoutBuffer.indexOf("\n");
|
|
2091
2163
|
}
|
|
2092
|
-
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
params.child.stderr.on("data", (chunk) => {
|
|
2097
|
-
if (failed) return;
|
|
2098
|
-
if (!params.streamResponse) {
|
|
2099
|
-
bufferOutput("stderr", chunk);
|
|
2100
|
-
return;
|
|
2101
|
-
}
|
|
2102
|
-
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2103
|
-
stderr = sliceUtf16Safe(`${stderr}${text}`, -4096);
|
|
2104
|
-
});
|
|
2105
|
-
params.child.once("error", (error) => {
|
|
2164
|
+
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`);
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
child.once("error", (error) => {
|
|
2106
2168
|
childFailure ??= error.message;
|
|
2107
|
-
|
|
2169
|
+
lifecycle.failed = true;
|
|
2108
2170
|
});
|
|
2109
|
-
|
|
2171
|
+
child.once("close", (code) => {
|
|
2110
2172
|
const exitCode = code ?? 1;
|
|
2111
2173
|
if (failed) return;
|
|
2112
2174
|
if (childFailure) {
|
|
@@ -2114,7 +2176,7 @@ function readSandboxHttpResponse(params) {
|
|
|
2114
2176
|
return;
|
|
2115
2177
|
}
|
|
2116
2178
|
if (exitCode === 0) {
|
|
2117
|
-
if (!
|
|
2179
|
+
if (!streamResponse) {
|
|
2118
2180
|
try {
|
|
2119
2181
|
const parsed = JSON.parse(Buffer.concat(buffered.stdout.chunks).toString("utf8"));
|
|
2120
2182
|
if (typeof parsed.status !== "number" || !Array.isArray(parsed.headers)) throw new Error("sandbox http/request returned an invalid response envelope");
|
|
@@ -2129,15 +2191,31 @@ function readSandboxHttpResponse(params) {
|
|
|
2129
2191
|
return;
|
|
2130
2192
|
}
|
|
2131
2193
|
if (!headerResolved) {
|
|
2132
|
-
|
|
2194
|
+
lifecycle.failed = true;
|
|
2133
2195
|
reject(/* @__PURE__ */ new Error("sandbox http/request exited before returning headers"));
|
|
2134
2196
|
}
|
|
2135
2197
|
return;
|
|
2136
2198
|
}
|
|
2137
|
-
if (!
|
|
2199
|
+
if (!streamResponse) stderr = Buffer.concat(buffered.stderr.chunks).toString("utf8");
|
|
2138
2200
|
fail(stderr.trim() || `sandbox http/request failed with code ${exitCode}`);
|
|
2139
2201
|
});
|
|
2140
|
-
});
|
|
2202
|
+
}).then(response.resolve, response.reject);
|
|
2203
|
+
try {
|
|
2204
|
+
if (notifications.signal.aborted) abortOnSessionClose();
|
|
2205
|
+
else {
|
|
2206
|
+
owner.assertCurrent();
|
|
2207
|
+
child.stdin.end(JSON.stringify(params));
|
|
2208
|
+
}
|
|
2209
|
+
await completion.promise;
|
|
2210
|
+
await termination;
|
|
2211
|
+
} catch (error) {
|
|
2212
|
+
lifecycle.failed = true;
|
|
2213
|
+
response.reject(error);
|
|
2214
|
+
await cleanup();
|
|
2215
|
+
throw error;
|
|
2216
|
+
} finally {
|
|
2217
|
+
notifications.signal.removeEventListener("abort", abortOnSessionClose);
|
|
2218
|
+
}
|
|
2141
2219
|
}
|
|
2142
2220
|
const SANDBOX_HTTP_REQUEST_SCRIPT = String.raw`
|
|
2143
2221
|
tmp=$(mktemp "$TMPDIR/openclaw-http.XXXXXX.py" 2>/dev/null || mktemp "/tmp/openclaw-http.XXXXXX.py") || exit 1
|
|
@@ -2155,9 +2233,6 @@ import urllib.request
|
|
|
2155
2233
|
def emit(payload):
|
|
2156
2234
|
print(json.dumps(payload, separators=(",", ":")), flush=True)
|
|
2157
2235
|
|
|
2158
|
-
def response_headers(response):
|
|
2159
|
-
return [{"name": name, "value": value} for name, value in response.headers.items()]
|
|
2160
|
-
|
|
2161
2236
|
BLOCKED_HOSTNAMES = {
|
|
2162
2237
|
"localhost",
|
|
2163
2238
|
"localhost.localdomain",
|
|
@@ -2193,9 +2268,7 @@ def is_blocked_hostname(hostname):
|
|
|
2193
2268
|
normalized = normalize_hostname(hostname)
|
|
2194
2269
|
return (
|
|
2195
2270
|
normalized in BLOCKED_HOSTNAMES
|
|
2196
|
-
or normalized.endswith(".localhost")
|
|
2197
|
-
or normalized.endswith(".local")
|
|
2198
|
-
or normalized.endswith(".internal")
|
|
2271
|
+
or normalized.endswith((".localhost", ".local", ".internal"))
|
|
2199
2272
|
)
|
|
2200
2273
|
|
|
2201
2274
|
def is_blocked_ip(address):
|
|
@@ -2208,12 +2281,9 @@ def is_blocked_ip(address):
|
|
|
2208
2281
|
return True
|
|
2209
2282
|
if str(parsed).lower() in CLOUD_METADATA_IP_ADDRESSES:
|
|
2210
2283
|
return True
|
|
2211
|
-
if isinstance(parsed, ipaddress.IPv4Address)
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
else:
|
|
2215
|
-
if any(parsed in network for network in BLOCKED_IPV6_NETWORKS):
|
|
2216
|
-
return True
|
|
2284
|
+
networks = BLOCKED_IPV4_NETWORKS if isinstance(parsed, ipaddress.IPv4Address) else BLOCKED_IPV6_NETWORKS
|
|
2285
|
+
if any(parsed in network for network in networks):
|
|
2286
|
+
return True
|
|
2217
2287
|
return (
|
|
2218
2288
|
parsed.is_loopback
|
|
2219
2289
|
or parsed.is_private
|
|
@@ -2314,7 +2384,7 @@ def pinned_getaddrinfo(original_getaddrinfo):
|
|
|
2314
2384
|
return getaddrinfo
|
|
2315
2385
|
|
|
2316
2386
|
def handle_response(input_data, response):
|
|
2317
|
-
headers =
|
|
2387
|
+
headers = [{"name": name, "value": value} for name, value in response.headers.items()]
|
|
2318
2388
|
status = int(getattr(response, "status", getattr(response, "code", 0)))
|
|
2319
2389
|
if input_data.get("streamResponse"):
|
|
2320
2390
|
emit({"type": "headers", "status": status, "headers": headers})
|
|
@@ -2384,7 +2454,7 @@ async function startProcess(execServer, processes, notify, params, processAuthor
|
|
|
2384
2454
|
if (processes.has(processId)) throw new Error(`process already exists: ${processId}`);
|
|
2385
2455
|
const argv = requireStringArray(record.argv, "argv");
|
|
2386
2456
|
const cwd = resolveExecServerPath(requireString(record.cwd, "cwd"), "process cwd");
|
|
2387
|
-
|
|
2457
|
+
if (record.arg0 !== void 0 && record.arg0 !== null) throw new Error(typeof record.arg0 === "string" ? "Codex sandbox exec-server does not support arg0 overrides." : "arg0 must be a string or null.");
|
|
2388
2458
|
assertSupportedProcessSandbox(execServer, record);
|
|
2389
2459
|
const env = readProcessEnv(record);
|
|
2390
2460
|
const managed = {
|
|
@@ -2449,7 +2519,7 @@ async function startProcess(execServer, processes, notify, params, processAuthor
|
|
|
2449
2519
|
notifyProcessWaiters(managed);
|
|
2450
2520
|
throw error;
|
|
2451
2521
|
} finally {
|
|
2452
|
-
|
|
2522
|
+
managed.startPromise = void 0;
|
|
2453
2523
|
}
|
|
2454
2524
|
return {
|
|
2455
2525
|
processId,
|
|
@@ -2461,8 +2531,7 @@ function assertSupportedProcessSandbox(execServer, record) {
|
|
|
2461
2531
|
if (record.enforceManagedNetwork === true || record.managedNetwork !== void 0 && record.managedNetwork !== null) throw new Error("Codex managed network restrictions cannot be enforced by the sandbox backend.");
|
|
2462
2532
|
if (resolveFsSandboxPolicy(execServer, record)?.unrestricted === false) throw new Error("Codex process filesystem sandbox restrictions cannot be enforced by the backend.");
|
|
2463
2533
|
if (record.sandbox === void 0 || record.sandbox === null) return;
|
|
2464
|
-
if (requireObject(requireObject(record.sandbox, "process sandbox context").permissions, "process sandbox permissions").network
|
|
2465
|
-
if (!execServer.networkIsolated) throw new Error("Codex network restrictions cannot be enforced by the sandbox backend.");
|
|
2534
|
+
if (requireObject(requireObject(record.sandbox, "process sandbox context").permissions, "process sandbox permissions").network === "restricted" && !execServer.networkIsolated) throw new Error("Codex network restrictions cannot be enforced by the sandbox backend.");
|
|
2466
2535
|
}
|
|
2467
2536
|
async function runProcess(execServer, managed, params) {
|
|
2468
2537
|
const backend = execServer.backend;
|
|
@@ -2522,14 +2591,12 @@ async function runProcess(execServer, managed, params) {
|
|
|
2522
2591
|
const child = owner.process;
|
|
2523
2592
|
child.stdout.on("data", (chunk) => appendProcessChunk(managed, "stdout", chunk));
|
|
2524
2593
|
child.stderr.on("data", (chunk) => appendProcessChunk(managed, "stderr", chunk));
|
|
2525
|
-
|
|
2594
|
+
const onError = (error) => {
|
|
2526
2595
|
managed.failure ??= error.message;
|
|
2527
2596
|
notifyProcessWaiters(managed);
|
|
2528
|
-
}
|
|
2529
|
-
child.
|
|
2530
|
-
|
|
2531
|
-
notifyProcessWaiters(managed);
|
|
2532
|
-
});
|
|
2597
|
+
};
|
|
2598
|
+
child.once("error", onError);
|
|
2599
|
+
child.stdin.on("error", onError);
|
|
2533
2600
|
owner.assertCurrent();
|
|
2534
2601
|
if (!managed.tty && !managed.pipeStdin) child.stdin.end();
|
|
2535
2602
|
}
|
|
@@ -2588,26 +2655,25 @@ function emitProcessClosed(managed, exitCode) {
|
|
|
2588
2655
|
managed.evictProcess();
|
|
2589
2656
|
notifyProcessWaiters(managed);
|
|
2590
2657
|
}
|
|
2591
|
-
function limitProcessChunks(chunks, maxBytes) {
|
|
2592
|
-
if (!maxBytes) return chunks;
|
|
2593
|
-
const retained = [];
|
|
2594
|
-
let retainedBytes = 0;
|
|
2595
|
-
for (const chunk of chunks) {
|
|
2596
|
-
const byteLength = Buffer.from(chunk.chunk, "base64").byteLength;
|
|
2597
|
-
if (retained.length > 0 && retainedBytes + byteLength > maxBytes) break;
|
|
2598
|
-
retained.push(chunk);
|
|
2599
|
-
retainedBytes += byteLength;
|
|
2600
|
-
if (retainedBytes >= maxBytes) break;
|
|
2601
|
-
}
|
|
2602
|
-
return retained;
|
|
2603
|
-
}
|
|
2604
2658
|
async function readProcess(processes, params) {
|
|
2605
2659
|
const record = requireObject(params, "process/read params");
|
|
2606
|
-
const
|
|
2660
|
+
const processId = requireString(record.processId, "processId");
|
|
2661
|
+
const managed = processes.get(processId);
|
|
2662
|
+
if (!managed) throw new Error(`unknown process: ${processId}`);
|
|
2607
2663
|
const afterSeq = typeof record.afterSeq === "number" ? record.afterSeq : 0;
|
|
2608
2664
|
const waitMs = typeof record.waitMs === "number" && record.waitMs > 0 ? record.waitMs : 0;
|
|
2609
2665
|
if (!managed.closed && managed.nextSeq - 1 <= afterSeq && waitMs > 0) await waitForProcessUpdate(managed, waitMs);
|
|
2610
|
-
const
|
|
2666
|
+
const maxBytes = typeof record.maxBytes === "number" && record.maxBytes > 0 ? record.maxBytes : void 0;
|
|
2667
|
+
const chunks = [];
|
|
2668
|
+
let retainedBytes = 0;
|
|
2669
|
+
for (const chunk of managed.chunks) {
|
|
2670
|
+
if (!(chunk.seq > afterSeq)) continue;
|
|
2671
|
+
const byteLength = maxBytes ? Buffer.from(chunk.chunk, "base64").byteLength : 0;
|
|
2672
|
+
if (maxBytes && chunks.length > 0 && retainedBytes + byteLength > maxBytes) break;
|
|
2673
|
+
chunks.push(chunk);
|
|
2674
|
+
retainedBytes += byteLength;
|
|
2675
|
+
if (maxBytes && retainedBytes >= maxBytes) break;
|
|
2676
|
+
}
|
|
2611
2677
|
const lastChunk = chunks.at(-1);
|
|
2612
2678
|
return {
|
|
2613
2679
|
chunks,
|
|
@@ -2651,8 +2717,7 @@ async function signalProcess(processes, params) {
|
|
|
2651
2717
|
async function terminateProcess(processes, params) {
|
|
2652
2718
|
const processId = requireString(requireObject(params, "process/terminate params").processId, "processId");
|
|
2653
2719
|
const managed = processes.get(processId);
|
|
2654
|
-
|
|
2655
|
-
return await terminateManagedProcess(managed);
|
|
2720
|
+
return managed ? await terminateManagedProcess(managed) : { running: false };
|
|
2656
2721
|
}
|
|
2657
2722
|
async function terminateManagedProcess(managed) {
|
|
2658
2723
|
const running = !managed.exited;
|
|
@@ -2678,16 +2743,6 @@ function notifyProcessWaiters(managed) {
|
|
|
2678
2743
|
managed.waiters = [];
|
|
2679
2744
|
for (const waiter of waiters) waiter();
|
|
2680
2745
|
}
|
|
2681
|
-
function requireProcess(processes, processId) {
|
|
2682
|
-
const managed = processes.get(processId);
|
|
2683
|
-
if (!managed) throw new Error(`unknown process: ${processId}`);
|
|
2684
|
-
return managed;
|
|
2685
|
-
}
|
|
2686
|
-
function rejectUnsupportedArg0(value) {
|
|
2687
|
-
if (value === void 0 || value === null) return;
|
|
2688
|
-
if (typeof value === "string") throw new Error("Codex sandbox exec-server does not support arg0 overrides.");
|
|
2689
|
-
throw new Error("arg0 must be a string or null.");
|
|
2690
|
-
}
|
|
2691
2746
|
function readEnv(value) {
|
|
2692
2747
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2693
2748
|
const env = {};
|
|
@@ -2695,22 +2750,18 @@ function readEnv(value) {
|
|
|
2695
2750
|
return env;
|
|
2696
2751
|
}
|
|
2697
2752
|
function readProcessEnv(record) {
|
|
2698
|
-
const
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
return sanitizeEnvVars(requestedEnv).allowed;
|
|
2703
|
-
}
|
|
2704
|
-
function buildEnvFromPolicy(value) {
|
|
2705
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2706
|
-
const policy = value;
|
|
2707
|
-
const inheritedEnv = readEnv(policy.set);
|
|
2708
|
-
const includeOnly = filterStringEntries(policy.includeOnly);
|
|
2753
|
+
const value = record.envPolicy;
|
|
2754
|
+
const policy = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2755
|
+
const inheritedEnv = readEnv(policy?.set);
|
|
2756
|
+
const includeOnly = filterStringEntries(policy?.includeOnly);
|
|
2709
2757
|
if (includeOnly.length > 0) {
|
|
2710
2758
|
const regexes = includeOnly.map(wildcardPatternToRegex);
|
|
2711
2759
|
for (const key of Object.keys(inheritedEnv)) if (!regexes.some((regex) => regex.test(key))) delete inheritedEnv[key];
|
|
2712
2760
|
}
|
|
2713
|
-
return
|
|
2761
|
+
return sanitizeEnvVars({
|
|
2762
|
+
...inheritedEnv,
|
|
2763
|
+
...readEnv(record.env)
|
|
2764
|
+
}).allowed;
|
|
2714
2765
|
}
|
|
2715
2766
|
function wildcardPatternToRegex(pattern) {
|
|
2716
2767
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
@@ -2788,14 +2839,12 @@ var CodexSandboxExecSession = class {
|
|
|
2788
2839
|
case "fs/open": return await openFile(this.execServer, this.fileReads, params);
|
|
2789
2840
|
case "fs/readBlock": return readFileBlock(this.fileReads, params);
|
|
2790
2841
|
case "fs/close": return closeFile(this.fileReads, params);
|
|
2791
|
-
case "fs/readFile":
|
|
2842
|
+
case "fs/readFile":
|
|
2843
|
+
case "fs/getMetadata": return await readFileOrMetadata(this.execServer, params, method);
|
|
2792
2844
|
case "fs/writeFile":
|
|
2793
|
-
await writeFile(this.execServer, params);
|
|
2794
|
-
return {};
|
|
2795
2845
|
case "fs/createDirectory":
|
|
2796
|
-
await
|
|
2846
|
+
await writeFileOrDirectory(this.execServer, params, method);
|
|
2797
2847
|
return {};
|
|
2798
|
-
case "fs/getMetadata": return await getMetadata(this.execServer, params);
|
|
2799
2848
|
case "fs/readDirectory": return await readDirectory(this.execServer, params);
|
|
2800
2849
|
case "fs/remove":
|
|
2801
2850
|
await removePath(this.execServer, params);
|
|
@@ -2825,10 +2874,8 @@ async function ensureCodexSandboxExecServerEnvironment(params) {
|
|
|
2825
2874
|
if (placementNodeId && !params.runtime) throw new Error("Codex node execution requires its active plugin runtime.");
|
|
2826
2875
|
if (!canExposeLocalExecServerToAppServer(params.appServerStartOptions)) throw new Error("OpenClaw Codex exec-server uses a local loopback URL and cannot be registered with a remote Codex app-server.");
|
|
2827
2876
|
const { server: execServer, nodeLease } = await acquireOpenClawExecServer({
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
signal: params.signal,
|
|
2831
|
-
onExecutionDisconnect: params.onExecutionDisconnect
|
|
2877
|
+
...params,
|
|
2878
|
+
sandbox: params.sandbox
|
|
2832
2879
|
});
|
|
2833
2880
|
const processAuthority = params.requireProcessAuthority && !("node" in execServer) ? getCodexNativeProcessClient(params.client) : void 0;
|
|
2834
2881
|
if (processAuthority && !("node" in execServer)) {
|
|
@@ -2885,7 +2932,16 @@ async function acquireOpenClawExecServer(params) {
|
|
|
2885
2932
|
const { sandbox, runtime, signal, onExecutionDisconnect } = params;
|
|
2886
2933
|
const key = sandbox.runtimeId;
|
|
2887
2934
|
while (true) {
|
|
2888
|
-
|
|
2935
|
+
let promise = sandboxExecServerRegistry.servers.get(key);
|
|
2936
|
+
if (!promise) {
|
|
2937
|
+
const created = startOpenClawExecServer(sandbox);
|
|
2938
|
+
const createdKey = sandbox.runtimeId;
|
|
2939
|
+
sandboxExecServerRegistry.servers.set(createdKey, created);
|
|
2940
|
+
created.catch(() => {
|
|
2941
|
+
if (sandboxExecServerRegistry.servers.get(createdKey) === created) sandboxExecServerRegistry.servers.delete(createdKey);
|
|
2942
|
+
});
|
|
2943
|
+
promise = created;
|
|
2944
|
+
}
|
|
2889
2945
|
const server = await promise;
|
|
2890
2946
|
if (!server.closed && sandboxExecServerRegistry.servers.get(key) === promise) {
|
|
2891
2947
|
server.refCount += 1;
|
|
@@ -2938,15 +2994,6 @@ async function acquireOpenClawExecServer(params) {
|
|
|
2938
2994
|
}
|
|
2939
2995
|
}
|
|
2940
2996
|
}
|
|
2941
|
-
function startAndRememberOpenClawExecServer(sandbox) {
|
|
2942
|
-
const created = startOpenClawExecServer(sandbox);
|
|
2943
|
-
const key = sandbox.runtimeId;
|
|
2944
|
-
sandboxExecServerRegistry.servers.set(key, created);
|
|
2945
|
-
created.catch(() => {
|
|
2946
|
-
if (sandboxExecServerRegistry.servers.get(key) === created) sandboxExecServerRegistry.servers.delete(key);
|
|
2947
|
-
});
|
|
2948
|
-
return created;
|
|
2949
|
-
}
|
|
2950
2997
|
async function startOpenClawExecServer(sandbox) {
|
|
2951
2998
|
const backend = sandbox.backend;
|
|
2952
2999
|
const fsBridge = sandbox.fsBridge;
|
|
@@ -2973,7 +3020,7 @@ async function startOpenClawExecServer(sandbox) {
|
|
|
2973
3020
|
await once(server, "listening");
|
|
2974
3021
|
const address = server.address();
|
|
2975
3022
|
if (!address || typeof address === "string") throw new Error("OpenClaw Codex exec-server did not bind to a TCP port.");
|
|
2976
|
-
const environmentId =
|
|
3023
|
+
const environmentId = `openclaw-sandbox-${createHash("sha256").update(sandbox.runtimeId).digest("hex").slice(0, 16)}`;
|
|
2977
3024
|
const authPath = `/openclaw-${randomUUID()}`;
|
|
2978
3025
|
const common = {
|
|
2979
3026
|
authPath,
|
|
@@ -2999,7 +3046,9 @@ async function startOpenClawExecServer(sandbox) {
|
|
|
2999
3046
|
networkIsolated: (connection.backend.id === "docker" || connection.backend.id === "podman") && sandbox.docker.network.trim().toLowerCase() === "none"
|
|
3000
3047
|
};
|
|
3001
3048
|
server.on("connection", (socket, request) => {
|
|
3002
|
-
socket.on("error",
|
|
3049
|
+
socket.on("error", (error) => {
|
|
3050
|
+
embeddedAgentLog.debug("codex sandbox exec-server websocket failed", { error });
|
|
3051
|
+
});
|
|
3003
3052
|
const requestUrl = new URL(request.url ?? "", "ws://127.0.0.1");
|
|
3004
3053
|
if (requestUrl.pathname !== execServer.authPath && ("node" in execServer || !execServer.processAuthorities?.has(requestUrl.pathname))) {
|
|
3005
3054
|
socket.close(1008, "unauthorized");
|
|
@@ -3027,15 +3076,12 @@ async function releaseOpenClawExecServer(execServer) {
|
|
|
3027
3076
|
if (current === execServer) sandboxExecServerRegistry.servers.delete(execServer.sandbox.runtimeId);
|
|
3028
3077
|
await sandboxExecServerRegistry.close(execServer);
|
|
3029
3078
|
}
|
|
3030
|
-
function buildEnvironmentId(sandbox) {
|
|
3031
|
-
return `openclaw-sandbox-${createHash("sha256").update(sandbox.runtimeId).digest("hex").slice(0, 16)}`;
|
|
3032
|
-
}
|
|
3033
3079
|
function readCodexPlacementNodeId(sandbox) {
|
|
3034
3080
|
if (!("placementExecutionMode" in sandbox) || sandbox.placementExecutionMode !== "remote-exec" || !("placementNodeId" in sandbox) || typeof sandbox.placementNodeId !== "string" || !sandbox.placementNodeId) return;
|
|
3035
3081
|
return sandbox.placementNodeId;
|
|
3036
3082
|
}
|
|
3037
3083
|
function readCodexPlacementWorkspaceIdentity(sandbox) {
|
|
3038
|
-
if (!("placementEnvironmentId" in sandbox) ||
|
|
3084
|
+
if (!("placementEnvironmentId" in sandbox) || !isWorkspaceIdentityString(sandbox.placementEnvironmentId) || !("placementSessionId" in sandbox) || !isWorkspaceIdentityString(sandbox.placementSessionId) || !("placementOwnerEpoch" in sandbox) || typeof sandbox.placementOwnerEpoch !== "number" || !Number.isSafeInteger(sandbox.placementOwnerEpoch) || sandbox.placementOwnerEpoch < 1 || !sandbox.sessionKey || sandbox.sessionKey.trim() !== sandbox.sessionKey) throw new Error("Codex node execution requires its exact placement workspace identity.");
|
|
3039
3085
|
return {
|
|
3040
3086
|
environmentId: sandbox.placementEnvironmentId,
|
|
3041
3087
|
sessionId: sandbox.placementSessionId,
|
|
@@ -3043,6 +3089,9 @@ function readCodexPlacementWorkspaceIdentity(sandbox) {
|
|
|
3043
3089
|
sessionKey: sandbox.sessionKey
|
|
3044
3090
|
};
|
|
3045
3091
|
}
|
|
3092
|
+
function isWorkspaceIdentityString(value) {
|
|
3093
|
+
return typeof value === "string" && value.length > 0 && value.trim() === value;
|
|
3094
|
+
}
|
|
3046
3095
|
function handleNodeConnection(execServer, socket, requestUrl) {
|
|
3047
3096
|
const leaseId = requestUrl.searchParams.get("lease");
|
|
3048
3097
|
const lease = leaseId ? execServer.node.leases.get(leaseId) : void 0;
|
|
@@ -3097,9 +3146,6 @@ function trackExecServerCleanup(execServer, cleanup, failureMessage) {
|
|
|
3097
3146
|
embeddedAgentLog.warn(failureMessage, { error: error instanceof Error ? error.message : String(error) });
|
|
3098
3147
|
});
|
|
3099
3148
|
}
|
|
3100
|
-
function handleExecServerSocketError(error) {
|
|
3101
|
-
embeddedAgentLog.debug("codex sandbox exec-server websocket failed", { error });
|
|
3102
|
-
}
|
|
3103
3149
|
async function handleMessage(session, data) {
|
|
3104
3150
|
await session.handleRequest(parseRequest(codexWebSocketDataToBuffer(data).toString("utf8")));
|
|
3105
3151
|
}
|
|
@@ -3197,10 +3243,19 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3197
3243
|
}
|
|
3198
3244
|
});
|
|
3199
3245
|
};
|
|
3246
|
+
const finishApproval = (outcome, { message, ...details }) => {
|
|
3247
|
+
emitEvent({
|
|
3248
|
+
phase: "resolved",
|
|
3249
|
+
...details,
|
|
3250
|
+
...params.method === "item/permissions/requestApproval" ? { scope: outcome === "approved-session" ? "session" : "turn" } : {},
|
|
3251
|
+
message
|
|
3252
|
+
});
|
|
3253
|
+
return buildApprovalResponse(params.method, requestParams, outcome);
|
|
3254
|
+
};
|
|
3200
3255
|
if (params.signal?.aborted) {
|
|
3201
3256
|
if (params.signal.reason instanceof CodexServerRequestResolvedError) return;
|
|
3202
3257
|
recordNativeToolFailureDisposition(params, context, "cancelled");
|
|
3203
|
-
return buildApprovalResponse(params.method,
|
|
3258
|
+
return buildApprovalResponse(params.method, requestParams, "cancelled");
|
|
3204
3259
|
}
|
|
3205
3260
|
let revalidateMutableFileApproval;
|
|
3206
3261
|
let mutableFileApprovalRequiresOneShot = false;
|
|
@@ -3220,7 +3275,7 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3220
3275
|
resolvedMessage = "Codex app-server approval granted for this byte-bound command only.";
|
|
3221
3276
|
}
|
|
3222
3277
|
if (params.method === "item/commandExecution/requestApproval" && resolvedOutcome !== "denied") {
|
|
3223
|
-
const resolution = resolveCommandApproval(
|
|
3278
|
+
const resolution = resolveCommandApproval(requestParams, resolvedOutcome);
|
|
3224
3279
|
if (resolution.scope === "denied") {
|
|
3225
3280
|
resolvedOutcome = "denied";
|
|
3226
3281
|
resolvedMessage = "Codex app-server request does not offer the approved scope.";
|
|
@@ -3231,17 +3286,14 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3231
3286
|
}
|
|
3232
3287
|
params.signal?.throwIfAborted();
|
|
3233
3288
|
if (resolvedOutcome !== "denied") params.paramsForRun.hostCapabilities.assertActive();
|
|
3234
|
-
|
|
3235
|
-
phase: "resolved",
|
|
3289
|
+
return finishApproval(resolvedOutcome, {
|
|
3236
3290
|
status: resolvedOutcome === "denied" ? "denied" : "approved",
|
|
3237
3291
|
...resolvedApprovalId ? {
|
|
3238
3292
|
approvalId: resolvedApprovalId,
|
|
3239
3293
|
approvalSlug: resolvedApprovalId
|
|
3240
3294
|
} : {},
|
|
3241
|
-
...approvalEventScope(params.method, resolvedOutcome),
|
|
3242
3295
|
message: resolvedMessage
|
|
3243
3296
|
});
|
|
3244
|
-
return buildApprovalResponse(params.method, context.requestParams, resolvedOutcome);
|
|
3245
3297
|
};
|
|
3246
3298
|
try {
|
|
3247
3299
|
if (params.method === "item/commandExecution/requestApproval" && !readNetworkApprovalContext(requestParams)) {
|
|
@@ -3271,7 +3323,7 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3271
3323
|
recordNativeToolFailureDisposition(params, context, policyOutcome.failureDisposition);
|
|
3272
3324
|
return await resolvePolicyApproval("denied", policyOutcome.reason);
|
|
3273
3325
|
}
|
|
3274
|
-
if (policyOutcome?.outcome === "approved-once"
|
|
3326
|
+
if (policyOutcome?.outcome === "approved-once") return await resolvePolicyApproval(policyOutcome.outcome);
|
|
3275
3327
|
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.");
|
|
3276
3328
|
const allowedDecisions = nativeApprovalAllowedDecisions({
|
|
3277
3329
|
method: params.method,
|
|
@@ -3293,13 +3345,10 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3293
3345
|
params.signal?.throwIfAborted();
|
|
3294
3346
|
if (!approvalId) {
|
|
3295
3347
|
recordNativeToolFailureDisposition(params, context, "failed");
|
|
3296
|
-
|
|
3297
|
-
phase: "resolved",
|
|
3348
|
+
return finishApproval("denied", {
|
|
3298
3349
|
status: "unavailable",
|
|
3299
|
-
...approvalEventScope(params.method, "denied"),
|
|
3300
3350
|
message: "Codex app-server approval route unavailable."
|
|
3301
3351
|
});
|
|
3302
|
-
return buildApprovalResponse(params.method, context.requestParams, "denied");
|
|
3303
3352
|
}
|
|
3304
3353
|
emitEvent({
|
|
3305
3354
|
phase: "requested",
|
|
@@ -3319,30 +3368,24 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3319
3368
|
if (approvalTimedOut) recordNativeToolFailureDisposition(params, context, "timed_out", context.approvalKind);
|
|
3320
3369
|
else if (outcome === "unavailable") recordNativeToolFailureDisposition(params, context, "failed");
|
|
3321
3370
|
if (outcome === "approved-once" || outcome === "approved-session") return await resolvePolicyApproval(outcome, approvalResolutionMessage(outcome), approvalId);
|
|
3322
|
-
|
|
3323
|
-
phase: "resolved",
|
|
3371
|
+
return finishApproval(outcome, {
|
|
3324
3372
|
status: outcome,
|
|
3325
3373
|
approvalId,
|
|
3326
3374
|
approvalSlug: approvalId,
|
|
3327
|
-
...approvalEventScope(params.method, outcome),
|
|
3328
3375
|
message: approvalTimedOut ? codexApprovalTimeoutText(context.approvalKind) : approvalResolutionMessage(outcome)
|
|
3329
3376
|
});
|
|
3330
|
-
return buildApprovalResponse(params.method, context.requestParams, outcome);
|
|
3331
3377
|
} catch (error) {
|
|
3332
3378
|
if (params.signal?.reason instanceof CodexServerRequestResolvedError) return;
|
|
3333
3379
|
const cancelled = params.signal?.aborted === true;
|
|
3334
3380
|
recordNativeToolFailureDisposition(params, context, cancelled && params.signal ? resolveCodexToolAbortTerminalReason(params.signal) : "failed");
|
|
3335
|
-
|
|
3336
|
-
phase: "resolved",
|
|
3381
|
+
return finishApproval(cancelled ? "cancelled" : "denied", {
|
|
3337
3382
|
status: cancelled ? "failed" : "unavailable",
|
|
3338
3383
|
...approvalId ? {
|
|
3339
3384
|
approvalId,
|
|
3340
3385
|
approvalSlug: approvalId
|
|
3341
3386
|
} : {},
|
|
3342
|
-
...approvalEventScope(params.method, cancelled ? "cancelled" : "denied"),
|
|
3343
3387
|
message: cancelled ? "Codex app-server approval cancelled because the run stopped." : `Codex app-server approval route failed: ${formatCodexDisplayText(coerceErrorMessage(error))}`
|
|
3344
3388
|
});
|
|
3345
|
-
return buildApprovalResponse(params.method, context.requestParams, cancelled ? "cancelled" : "denied");
|
|
3346
3389
|
}
|
|
3347
3390
|
}
|
|
3348
3391
|
function recordNativeToolFailureDisposition(params, context, disposition, approvalKind) {
|
|
@@ -3354,17 +3397,17 @@ function recordNativeToolFailureDisposition(params, context, disposition, approv
|
|
|
3354
3397
|
}
|
|
3355
3398
|
function buildApprovalResponse(method, requestParams, outcome) {
|
|
3356
3399
|
if (method === "item/commandExecution/requestApproval") return { decision: resolveCommandApproval(requestParams, outcome).decision };
|
|
3357
|
-
if (method === "item/fileChange/requestApproval") return { decision:
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
}
|
|
3400
|
+
if (method === "item/fileChange/requestApproval") return { decision: {
|
|
3401
|
+
cancelled: "cancel",
|
|
3402
|
+
denied: "decline",
|
|
3403
|
+
unavailable: "decline",
|
|
3404
|
+
"approved-session": "acceptForSession",
|
|
3405
|
+
"approved-once": "accept"
|
|
3406
|
+
}[outcome] };
|
|
3407
|
+
if (method === "item/permissions/requestApproval") return {
|
|
3408
|
+
permissions: outcome === "approved-session" || outcome === "approved-once" ? requestedPermissions(requestParams) : {},
|
|
3409
|
+
scope: outcome === "approved-session" ? "session" : "turn"
|
|
3410
|
+
};
|
|
3368
3411
|
return {
|
|
3369
3412
|
decision: "decline",
|
|
3370
3413
|
reason: "OpenClaw codex app-server bridge does not grant native approvals yet."
|
|
@@ -3374,7 +3417,7 @@ function buildApprovalContext(params) {
|
|
|
3374
3417
|
const itemId = readStringField(params.requestParams, "itemId") ?? readStringField(params.requestParams, "callId") ?? readStringField(params.requestParams, "approvalId");
|
|
3375
3418
|
const approvalId = readStringField(params.requestParams, "approvalId") ?? itemId;
|
|
3376
3419
|
const commandDetailLines = params.method === "item/commandExecution/requestApproval" ? describeCommandApprovalDetails(params.requestParams) : [];
|
|
3377
|
-
const commandPreview = sanitizeApprovalPreview(
|
|
3420
|
+
const commandPreview = sanitizeApprovalPreview(readJoinedPreview(readCommandActions(params.requestParams), " && ") ?? readCommandPreview(params.requestParams), commandDetailLines.length > 0 ? COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH : 180);
|
|
3378
3421
|
const reasonPreview = sanitizeApprovalPreview(readStringPreview(params.requestParams, "reason"), 180);
|
|
3379
3422
|
const command = commandPreview.text;
|
|
3380
3423
|
const reason = reasonPreview.text;
|
|
@@ -3382,7 +3425,8 @@ function buildApprovalContext(params) {
|
|
|
3382
3425
|
const approvalKind = params.method.includes("commandExecution") ? "command" : params.method.includes("fileChange") ? "file-change" : params.method.includes("permissions") ? "permissions" : "other";
|
|
3383
3426
|
const kind = approvalKind === "command" ? "exec" : approvalKind === "other" ? "unknown" : "plugin";
|
|
3384
3427
|
const permissionLines = params.method === "item/permissions/requestApproval" ? describePermissionProfile(requestedPermissions(params.requestParams), "Permissions") : [];
|
|
3385
|
-
const
|
|
3428
|
+
const approvalType = networkApproval ? "network" : kind === "exec" ? "command" : params.method === "item/permissions/requestApproval" ? "permission" : kind === "plugin" ? "file" : void 0;
|
|
3429
|
+
const title = `Codex app-server ${approvalType ? `${approvalType} ` : ""}approval`;
|
|
3386
3430
|
const subject = (networkApproval ? `Network: ${sanitizeCodexApprovalVisibleText(networkApproval.protocol)}://${sanitizePermissionHostValue(networkApproval.host)}` : void 0) ?? permissionLines[0] ?? (command ? `Command: ${formatApprovalPreviewSubject(command, commandPreview.omitted)}` : commandPreview.omitted ? `Command: ${APPROVAL_PREVIEW_OMITTED}` : reason ? `Reason: ${formatApprovalPreviewSubject(reason, reasonPreview.omitted)}` : reasonPreview.omitted ? `Reason: ${APPROVAL_PREVIEW_OMITTED}` : `Request method: ${params.method}`);
|
|
3387
3431
|
return {
|
|
3388
3432
|
approvalKind,
|
|
@@ -3390,10 +3434,9 @@ function buildApprovalContext(params) {
|
|
|
3390
3434
|
title,
|
|
3391
3435
|
description: permissionLines.length > 0 ? joinDescriptionLinesWithinLimit(permissionLines, PERMISSION_DESCRIPTION_MAX_LENGTH) : [subject, ...commandDetailLines].join("\n"),
|
|
3392
3436
|
severity: kind === "exec" ? "warning" : "info",
|
|
3393
|
-
toolName:
|
|
3437
|
+
toolName: `codex_${approvalType ?? "file"}_approval`,
|
|
3394
3438
|
itemId,
|
|
3395
3439
|
approvalId,
|
|
3396
|
-
requestParams: params.requestParams,
|
|
3397
3440
|
eventDetails: {
|
|
3398
3441
|
...itemId ? { itemId } : {},
|
|
3399
3442
|
...command ? { command } : {},
|
|
@@ -3407,24 +3450,9 @@ async function runOpenClawToolPolicyForApprovalRequest(params) {
|
|
|
3407
3450
|
const policyRequest = buildOpenClawToolPolicyRequest(params.method, params.requestParams);
|
|
3408
3451
|
if (!policyRequest) return;
|
|
3409
3452
|
const cwd = readStringField(params.requestParams, "cwd") ?? params.paramsForRun.workspaceDir;
|
|
3410
|
-
const
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
context: params.context,
|
|
3414
|
-
policyRequest,
|
|
3415
|
-
nativeHookRelay: params.nativeHookRelay,
|
|
3416
|
-
autoApprove: params.autoApprove,
|
|
3417
|
-
assertActive: params.paramsForRun.hostCapabilities.assertActive,
|
|
3418
|
-
cwd,
|
|
3419
|
-
signal: params.signal
|
|
3420
|
-
});
|
|
3421
|
-
if (nativeRelayOutcome?.blocked) return {
|
|
3422
|
-
outcome: "denied",
|
|
3423
|
-
reason: nativeRelayOutcome.reason,
|
|
3424
|
-
...nativeRelayOutcome.failureDisposition ? { failureDisposition: nativeRelayOutcome.failureDisposition } : {}
|
|
3425
|
-
};
|
|
3426
|
-
if (nativeRelayOutcome?.outcome === "approved-once" || nativeRelayOutcome?.outcome === "approved-session") return { outcome: nativeRelayOutcome.outcome };
|
|
3427
|
-
if (nativeRelayOutcome?.handled) return { outcome: "allowed" };
|
|
3453
|
+
const assertActive = params.paramsForRun.hostCapabilities.assertActive;
|
|
3454
|
+
const nativeRelayOutcome = await runNativeRelayPolicy(policyRequest.params);
|
|
3455
|
+
if (nativeRelayOutcome) return nativeRelayOutcome;
|
|
3428
3456
|
const outcome = await params.paramsForRun.hostCapabilities.runBeforeToolCall({
|
|
3429
3457
|
toolName: policyRequest.toolName,
|
|
3430
3458
|
params: policyRequest.params,
|
|
@@ -3443,105 +3471,86 @@ async function runOpenClawToolPolicyForApprovalRequest(params) {
|
|
|
3443
3471
|
};
|
|
3444
3472
|
if (outcome.approvalResolution) return { outcome: "approved-once" };
|
|
3445
3473
|
return { outcome: "allowed" };
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
if (approvalOutcome?.outcome === "denied") return {
|
|
3465
|
-
handled: true,
|
|
3466
|
-
blocked: true,
|
|
3467
|
-
reason: approvalOutcome.reason,
|
|
3468
|
-
...approvalOutcome.failureDisposition ? { failureDisposition: approvalOutcome.failureDisposition } : {}
|
|
3474
|
+
async function runNativeRelayPolicy(toolParams) {
|
|
3475
|
+
const nativeHookRelay = params.nativeHookRelay;
|
|
3476
|
+
if (params.method !== "item/commandExecution/requestApproval" || !nativeHookRelay?.allowedEvents.includes("pre_tool_use")) return;
|
|
3477
|
+
const command = readStringField(toolParams, "command");
|
|
3478
|
+
if (!command) return;
|
|
3479
|
+
const turnId = readStringField(params.requestParams, "turnId");
|
|
3480
|
+
const payload = {
|
|
3481
|
+
hook_event_name: "PreToolUse",
|
|
3482
|
+
openclaw_approval_mode: "report",
|
|
3483
|
+
tool_name: "exec_command",
|
|
3484
|
+
...params.context.approvalId ? { tool_use_id: params.context.approvalId } : {},
|
|
3485
|
+
...cwd ? { cwd } : {},
|
|
3486
|
+
...turnId ? { turn_id: turnId } : {},
|
|
3487
|
+
tool_input: {
|
|
3488
|
+
...toolParams,
|
|
3489
|
+
command,
|
|
3490
|
+
cmd: command
|
|
3491
|
+
}
|
|
3469
3492
|
};
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
relayId: nativeHookRelay.relayId,
|
|
3484
|
-
generation: nativeHookRelay.generation,
|
|
3485
|
-
event: "pre_tool_use",
|
|
3486
|
-
rawPayload: payload,
|
|
3487
|
-
requireGeneration: true
|
|
3488
|
-
}));
|
|
3489
|
-
if (decision.blocked) return {
|
|
3490
|
-
handled: true,
|
|
3491
|
-
blocked: true,
|
|
3492
|
-
reason: decision.reason,
|
|
3493
|
-
...decision.failureDisposition ? { failureDisposition: decision.failureDisposition } : {}
|
|
3493
|
+
const resolveDeferredApproval = async () => {
|
|
3494
|
+
const approvalOutcome = await resolveNativeHookRelayDeferredToolApproval({
|
|
3495
|
+
relayId: nativeHookRelay.relayId,
|
|
3496
|
+
toolUseId: params.context.approvalId,
|
|
3497
|
+
signal: params.signal
|
|
3498
|
+
});
|
|
3499
|
+
assertActive();
|
|
3500
|
+
if (approvalOutcome?.outcome === "denied") return {
|
|
3501
|
+
outcome: "denied",
|
|
3502
|
+
reason: approvalOutcome.reason,
|
|
3503
|
+
...approvalOutcome.failureDisposition ? { failureDisposition: approvalOutcome.failureDisposition } : {}
|
|
3504
|
+
};
|
|
3505
|
+
return approvalOutcome?.outcome === "approved-once" ? { outcome: approvalOutcome.outcome } : { outcome: "allowed" };
|
|
3494
3506
|
};
|
|
3495
|
-
|
|
3496
|
-
} catch (error) {
|
|
3497
|
-
if (params.autoApprove === true && !hasNativeHookRelayInvocation({
|
|
3507
|
+
if (hasNativeHookRelayInvocation({
|
|
3498
3508
|
relayId: nativeHookRelay.relayId,
|
|
3499
3509
|
event: "pre_tool_use",
|
|
3500
3510
|
toolUseId: params.context.approvalId
|
|
3501
|
-
})) return;
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
cmd: command
|
|
3511
|
+
})) return resolveDeferredApproval();
|
|
3512
|
+
try {
|
|
3513
|
+
const denial = readNativeRelayPreToolUseDenial(await invokeNativeHookRelay({
|
|
3514
|
+
provider: "codex",
|
|
3515
|
+
relayId: nativeHookRelay.relayId,
|
|
3516
|
+
generation: nativeHookRelay.generation,
|
|
3517
|
+
event: "pre_tool_use",
|
|
3518
|
+
rawPayload: payload,
|
|
3519
|
+
requireGeneration: true
|
|
3520
|
+
}));
|
|
3521
|
+
if (denial) return denial;
|
|
3522
|
+
return await resolveDeferredApproval();
|
|
3523
|
+
} catch (error) {
|
|
3524
|
+
if (params.autoApprove === true && !hasNativeHookRelayInvocation({
|
|
3525
|
+
relayId: nativeHookRelay.relayId,
|
|
3526
|
+
event: "pre_tool_use",
|
|
3527
|
+
toolUseId: params.context.approvalId
|
|
3528
|
+
})) return;
|
|
3529
|
+
return {
|
|
3530
|
+
outcome: "denied",
|
|
3531
|
+
reason: `OpenClaw native hook relay unavailable for Codex app-server approval: ${formatCodexDisplayText(coerceErrorMessage(error))}`,
|
|
3532
|
+
failureDisposition: "failed"
|
|
3533
|
+
};
|
|
3525
3534
|
}
|
|
3526
|
-
}
|
|
3535
|
+
}
|
|
3527
3536
|
}
|
|
3528
|
-
function
|
|
3537
|
+
function readNativeRelayPreToolUseDenial(response) {
|
|
3529
3538
|
if (!response || response.exitCode !== 0) return {
|
|
3530
|
-
|
|
3539
|
+
outcome: "denied",
|
|
3531
3540
|
reason: sanitizeRelayDecisionReason(response?.stderr) || sanitizeRelayDecisionReason(response?.stdout) || "OpenClaw native hook relay failed for Codex app-server approval.",
|
|
3532
3541
|
failureDisposition: response?.failureDisposition ?? "failed"
|
|
3533
3542
|
};
|
|
3534
3543
|
const stdout = response.stdout?.trim();
|
|
3535
|
-
if (!stdout) return
|
|
3544
|
+
if (!stdout) return;
|
|
3536
3545
|
const parsed = safeParseJson(stdout);
|
|
3537
3546
|
const output = isJsonObject(parsed) && isJsonObject(parsed.hookSpecificOutput) ? parsed.hookSpecificOutput : void 0;
|
|
3538
3547
|
if (output?.permissionDecision === "deny") return {
|
|
3539
|
-
|
|
3548
|
+
outcome: "denied",
|
|
3540
3549
|
reason: readStringField(output, "permissionDecisionReason") || "OpenClaw native hook policy denied Codex app-server approval.",
|
|
3541
3550
|
...response.failureDisposition ? { failureDisposition: response.failureDisposition } : {}
|
|
3542
3551
|
};
|
|
3543
3552
|
return {
|
|
3544
|
-
|
|
3553
|
+
outcome: "denied",
|
|
3545
3554
|
reason: output ? "OpenClaw native hook relay returned a non-deny Codex app-server approval decision." : "OpenClaw native hook relay returned an unreadable Codex app-server approval result.",
|
|
3546
3555
|
failureDisposition: "failed"
|
|
3547
3556
|
};
|
|
@@ -3605,34 +3614,22 @@ function nativeApprovalAllowedDecisions(params) {
|
|
|
3605
3614
|
];
|
|
3606
3615
|
return params.method === "item/commandExecution/requestApproval" ? commandApprovalAllowedDecisions(params.requestParams, params.requiresOneShot) : void 0;
|
|
3607
3616
|
}
|
|
3608
|
-
function fileChangeApprovalDecision(outcome) {
|
|
3609
|
-
if (outcome === "cancelled") return "cancel";
|
|
3610
|
-
if (outcome === "denied" || outcome === "unavailable") return "decline";
|
|
3611
|
-
return outcome === "approved-session" ? "acceptForSession" : "accept";
|
|
3612
|
-
}
|
|
3613
3617
|
function requestedPermissions(requestParams) {
|
|
3614
3618
|
const permissions = isJsonObject(requestParams?.permissions) ? requestParams.permissions : {};
|
|
3615
|
-
|
|
3616
|
-
if (isJsonObject(permissions.network)) granted.network = permissions.network;
|
|
3617
|
-
if (isJsonObject(permissions.fileSystem)) granted.fileSystem = permissions.fileSystem;
|
|
3618
|
-
return granted;
|
|
3619
|
+
return Object.fromEntries(["network", "fileSystem"].flatMap((key) => isJsonObject(permissions[key]) ? [[key, permissions[key]]] : []));
|
|
3619
3620
|
}
|
|
3620
3621
|
function describeCommandApprovalDetails(requestParams) {
|
|
3621
|
-
const lines = [];
|
|
3622
3622
|
const additionalPermissions = isJsonObject(requestParams?.additionalPermissions) ? requestParams.additionalPermissions : void 0;
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
return lines;
|
|
3623
|
+
return [
|
|
3624
|
+
...additionalPermissions ? describePermissionProfile(additionalPermissions, "Additional permissions") : [],
|
|
3625
|
+
summarizeExecPolicyAmendment(requestParams?.proposedExecpolicyAmendment),
|
|
3626
|
+
summarizePermissionTargets(requestParams?.proposedNetworkPolicyAmendments, { kind: "network" })
|
|
3627
|
+
].filter((line) => Boolean(line));
|
|
3629
3628
|
}
|
|
3630
3629
|
function describePermissionProfile(permissions, label) {
|
|
3631
3630
|
const lines = [];
|
|
3632
|
-
const kinds = [];
|
|
3633
3631
|
const risks = /* @__PURE__ */ new Set();
|
|
3634
|
-
|
|
3635
|
-
if (isJsonObject(permissions.fileSystem)) kinds.push("fileSystem");
|
|
3632
|
+
const kinds = ["network", "fileSystem"].filter((kind) => isJsonObject(permissions[kind]));
|
|
3636
3633
|
if (kinds.length > 0) lines.push(`${label}: ${kinds.join(", ")}`);
|
|
3637
3634
|
let networkSummary;
|
|
3638
3635
|
if (isJsonObject(permissions.network)) {
|
|
@@ -3654,7 +3651,10 @@ function describePermissionProfile(permissions, label) {
|
|
|
3654
3651
|
].map((key) => summarizePermissionArray(fileSystem[key], key, risks, {
|
|
3655
3652
|
sanitize: sanitizePermissionPathValue,
|
|
3656
3653
|
risksFor: permissionPathRisks
|
|
3657
|
-
})),
|
|
3654
|
+
})), summarizePermissionTargets(fileSystem.entries, {
|
|
3655
|
+
kind: "file-system",
|
|
3656
|
+
risks
|
|
3657
|
+
})].filter((summary) => Boolean(summary));
|
|
3658
3658
|
fileSystemSummary = summaries.length > 0 ? summaries.join("; ") : void 0;
|
|
3659
3659
|
}
|
|
3660
3660
|
if (risks.size > 0) lines.push(`High-risk targets: ${[...risks].join(", ")}`);
|
|
@@ -3668,21 +3668,24 @@ function summarizeNetworkEnabledPermission(permission, risks) {
|
|
|
3668
3668
|
if (enabled) risks.add("network access");
|
|
3669
3669
|
return `enabled: ${enabled}`;
|
|
3670
3670
|
}
|
|
3671
|
-
function
|
|
3672
|
-
const entries = permission.entries;
|
|
3671
|
+
function summarizePermissionTargets(entries, format) {
|
|
3673
3672
|
if (!Array.isArray(entries)) return;
|
|
3673
|
+
const fileSystem = format.kind === "file-system";
|
|
3674
|
+
const sanitizeTarget = fileSystem ? sanitizePermissionPathValue : sanitizePermissionHostValue;
|
|
3674
3675
|
const samples = [];
|
|
3675
3676
|
let count = 0;
|
|
3676
3677
|
for (const entry of entries) {
|
|
3677
3678
|
const item = isJsonObject(entry) ? entry : void 0;
|
|
3678
|
-
const
|
|
3679
|
-
const
|
|
3680
|
-
|
|
3679
|
+
const rawTarget = item?.[fileSystem ? "path" : "host"];
|
|
3680
|
+
const rawAction = item?.[fileSystem ? "access" : "action"];
|
|
3681
|
+
const target = typeof rawTarget === "string" ? fileSystem ? rawTarget.trim() : rawTarget : "";
|
|
3682
|
+
const action = typeof rawAction === "string" ? fileSystem ? rawAction.trim() : rawAction : "";
|
|
3683
|
+
if (!target || !action) continue;
|
|
3681
3684
|
count += 1;
|
|
3682
|
-
if (
|
|
3683
|
-
if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizeCodexApprovalVisibleText(
|
|
3685
|
+
if (format.kind === "file-system" && action !== "none") for (const risk of permissionPathRisks(target)) format.risks.add(risk);
|
|
3686
|
+
if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizeCodexApprovalVisibleText(action)} ${sanitizeTarget(target)}`);
|
|
3684
3687
|
}
|
|
3685
|
-
return formatPermissionSamples("entries", samples, count);
|
|
3688
|
+
return formatPermissionSamples(fileSystem ? "entries" : "Proposed network policy", samples, count);
|
|
3686
3689
|
}
|
|
3687
3690
|
function summarizePermissionArray(input, label, risks, format) {
|
|
3688
3691
|
const values = normalizeTrimmedStringList(input);
|
|
@@ -3690,24 +3693,10 @@ function summarizePermissionArray(input, label, risks, format) {
|
|
|
3690
3693
|
for (const value of values) for (const risk of format.risksFor(value)) risks.add(risk);
|
|
3691
3694
|
return formatPermissionSamples(label, values.slice(0, PERMISSION_SAMPLE_LIMIT).map(format.sanitize).filter(Boolean), values.length);
|
|
3692
3695
|
}
|
|
3693
|
-
function
|
|
3696
|
+
function summarizeExecPolicyAmendment(value) {
|
|
3694
3697
|
if (!Array.isArray(value)) return;
|
|
3695
|
-
const values = value.filter((entry) => typeof entry === "string").map((entry) =>
|
|
3696
|
-
return formatPermissionSamples(
|
|
3697
|
-
}
|
|
3698
|
-
function summarizeNetworkPolicyAmendments(value) {
|
|
3699
|
-
if (!Array.isArray(value)) return;
|
|
3700
|
-
const samples = [];
|
|
3701
|
-
let count = 0;
|
|
3702
|
-
for (const entry of value) {
|
|
3703
|
-
const amendment = isJsonObject(entry) ? entry : void 0;
|
|
3704
|
-
const host = typeof amendment?.host === "string" ? amendment.host : "";
|
|
3705
|
-
const action = typeof amendment?.action === "string" ? amendment.action : "";
|
|
3706
|
-
if (!host || !action) continue;
|
|
3707
|
-
count += 1;
|
|
3708
|
-
if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizeCodexApprovalVisibleText(action)} ${sanitizePermissionHostValue(host)}`);
|
|
3709
|
-
}
|
|
3710
|
-
return formatPermissionSamples("Proposed network policy", samples, count);
|
|
3698
|
+
const values = value.filter((entry) => typeof entry === "string").map((entry) => sanitizeCodexApprovalVisibleText(entry)).filter(Boolean);
|
|
3699
|
+
return formatPermissionSamples("Proposed exec policy", values.slice(0, PERMISSION_SAMPLE_LIMIT), values.length);
|
|
3711
3700
|
}
|
|
3712
3701
|
function formatPermissionSamples(label, samples, count) {
|
|
3713
3702
|
if (count === 0) return;
|
|
@@ -3727,18 +3716,12 @@ function sanitizePermissionPathValue(value) {
|
|
|
3727
3716
|
}
|
|
3728
3717
|
function permissionHostRisks(value) {
|
|
3729
3718
|
const normalized = value.trim().toLowerCase();
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
risks.push("wildcard hosts");
|
|
3733
|
-
if (isPrivateNetworkHostPattern(normalized)) risks.push("private-network wildcards");
|
|
3734
|
-
}
|
|
3735
|
-
return risks;
|
|
3719
|
+
if (!normalized.includes("*")) return [];
|
|
3720
|
+
return isPrivateNetworkHostPattern(normalized) ? ["wildcard hosts", "private-network wildcards"] : ["wildcard hosts"];
|
|
3736
3721
|
}
|
|
3737
3722
|
function permissionPathRisks(value) {
|
|
3738
3723
|
const normalized = sanitizeCodexApprovalVisibleText(value);
|
|
3739
|
-
|
|
3740
|
-
if (normalized === "/" || normalized === "\\" || /^[A-Za-z]:[\\/]*$/.test(normalized)) risks.push("filesystem root");
|
|
3741
|
-
return risks;
|
|
3724
|
+
return normalized === "/" || normalized === "\\" || /^[A-Za-z]:[\\/]*$/.test(normalized) ? ["filesystem root"] : [];
|
|
3742
3725
|
}
|
|
3743
3726
|
function isPrivateNetworkHostPattern(value) {
|
|
3744
3727
|
const wildcardStripped = value.toLowerCase().replace(/^\*\./, "");
|
|
@@ -3750,8 +3733,12 @@ function isPrivateNetworkHostPattern(value) {
|
|
|
3750
3733
|
"corp",
|
|
3751
3734
|
"private"
|
|
3752
3735
|
].some((suffix) => wildcardStripped === suffix || wildcardStripped.endsWith(`.${suffix}`))) return true;
|
|
3753
|
-
|
|
3754
|
-
|
|
3736
|
+
return [
|
|
3737
|
+
"10.",
|
|
3738
|
+
"127.",
|
|
3739
|
+
"192.168.",
|
|
3740
|
+
"169.254."
|
|
3741
|
+
].some((prefix) => wildcardStripped.startsWith(prefix)) || /^172\.(1[6-9]|2\d|3[0-1])\./.test(wildcardStripped);
|
|
3755
3742
|
}
|
|
3756
3743
|
function approvalResolutionMessage(outcome) {
|
|
3757
3744
|
return {
|
|
@@ -3762,15 +3749,11 @@ function approvalResolutionMessage(outcome) {
|
|
|
3762
3749
|
denied: "Codex app-server approval denied."
|
|
3763
3750
|
}[outcome];
|
|
3764
3751
|
}
|
|
3765
|
-
function approvalEventScope(method, outcome) {
|
|
3766
|
-
return method === "item/permissions/requestApproval" ? { scope: outcome === "approved-session" ? "session" : "turn" } : {};
|
|
3767
|
-
}
|
|
3768
3752
|
function readPolicyCommand(record) {
|
|
3769
3753
|
const command = record?.command;
|
|
3770
3754
|
if (typeof command === "string") return command;
|
|
3771
3755
|
if (Array.isArray(command) && command.every((part) => typeof part === "string")) return command.join(" ");
|
|
3772
|
-
|
|
3773
|
-
if (actionCommands.length > 0) return actionCommands.join(" && ");
|
|
3756
|
+
return readCommandActions(record).join(" && ") || void 0;
|
|
3774
3757
|
}
|
|
3775
3758
|
function readNetworkApprovalContext(record) {
|
|
3776
3759
|
const context = isJsonObject(record?.networkApprovalContext) ? record.networkApprovalContext : void 0;
|
|
@@ -3786,22 +3769,16 @@ function readCommandActions(record) {
|
|
|
3786
3769
|
if (!Array.isArray(actions)) return [];
|
|
3787
3770
|
return actions.map((action) => isJsonObject(action) ? readStringField(action, "command") : void 0).filter((command) => Boolean(command));
|
|
3788
3771
|
}
|
|
3789
|
-
function readCommandActionsPreview(record) {
|
|
3790
|
-
let source;
|
|
3791
|
-
for (const command of readCommandActions(record)) {
|
|
3792
|
-
source = appendPreviewPart(source, command, " && ");
|
|
3793
|
-
if (source.clipped) break;
|
|
3794
|
-
}
|
|
3795
|
-
return source;
|
|
3796
|
-
}
|
|
3797
3772
|
function readCommandPreview(record) {
|
|
3798
3773
|
const command = record?.command;
|
|
3799
3774
|
if (typeof command === "string") return previewSource(command);
|
|
3800
|
-
|
|
3775
|
+
return Array.isArray(command) ? readJoinedPreview(command, " ") : void 0;
|
|
3776
|
+
}
|
|
3777
|
+
function readJoinedPreview(parts, separator) {
|
|
3801
3778
|
let source;
|
|
3802
|
-
for (const part of
|
|
3779
|
+
for (const part of parts) {
|
|
3803
3780
|
if (typeof part !== "string") return;
|
|
3804
|
-
source =
|
|
3781
|
+
source = previewSource(`${source?.value ? `${source.value}${separator}` : ""}${part}`);
|
|
3805
3782
|
if (source.clipped) break;
|
|
3806
3783
|
}
|
|
3807
3784
|
return source;
|
|
@@ -3816,14 +3793,6 @@ function previewSource(value) {
|
|
|
3816
3793
|
clipped: value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH
|
|
3817
3794
|
};
|
|
3818
3795
|
}
|
|
3819
|
-
function appendPreviewPart(source, part, separator) {
|
|
3820
|
-
const value = `${source?.value ? `${source.value}${separator}` : ""}${part}`;
|
|
3821
|
-
const clipped = source?.clipped === true || value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH;
|
|
3822
|
-
return {
|
|
3823
|
-
value: sliceUtf16Safe(value, 0, APPROVAL_PREVIEW_SCAN_MAX_LENGTH),
|
|
3824
|
-
clipped
|
|
3825
|
-
};
|
|
3826
|
-
}
|
|
3827
3796
|
function sanitizeApprovalPreview(source, maxLength) {
|
|
3828
3797
|
if (!source || !source.value) return { omitted: false };
|
|
3829
3798
|
const rawPreview = stripDanglingCodexApprovalTerminalSequence(source.value);
|
|
@@ -3865,45 +3834,49 @@ function diagnosticToolIdentity(params) {
|
|
|
3865
3834
|
toolCallId: params.call.callId
|
|
3866
3835
|
};
|
|
3867
3836
|
}
|
|
3868
|
-
function
|
|
3869
|
-
|
|
3870
|
-
type
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
...diagnosticToolIdentity(params),
|
|
3878
|
-
durationMs: params.durationMs,
|
|
3879
|
-
errorCategory: "codex_dynamic_tool_error",
|
|
3880
|
-
terminalReason: params.terminalReason ?? "failed"
|
|
3881
|
-
});
|
|
3882
|
-
}
|
|
3883
|
-
/** Emits the terminal event matching a dynamic tool response's diagnostic type. */
|
|
3884
|
-
function emitDynamicToolTerminalDiagnostic(params) {
|
|
3885
|
-
const terminalType = params.response.diagnosticTerminalType ?? (params.response.success ? "completed" : "error");
|
|
3886
|
-
if (terminalType === "completed") {
|
|
3887
|
-
emitTrustedDiagnosticEvent({
|
|
3888
|
-
type: "tool.execution.completed",
|
|
3889
|
-
...diagnosticToolIdentity(params),
|
|
3890
|
-
durationMs: params.durationMs
|
|
3891
|
-
});
|
|
3892
|
-
return;
|
|
3893
|
-
}
|
|
3894
|
-
if (terminalType === "blocked") {
|
|
3837
|
+
function createCodexDynamicToolDiagnostics(params) {
|
|
3838
|
+
const matchesTerminal = (event) => {
|
|
3839
|
+
if (event.type !== "tool.execution.completed" && event.type !== "tool.execution.error" && event.type !== "tool.execution.blocked" || event.toolCallId !== params.call.callId || event.toolName !== params.call.tool) return false;
|
|
3840
|
+
if (params.runId !== void 0) return event.runId === params.runId;
|
|
3841
|
+
if (params.sessionId !== void 0) return event.sessionId === params.sessionId;
|
|
3842
|
+
if (params.sessionKey !== void 0) return event.sessionKey === params.sessionKey;
|
|
3843
|
+
return event.runId === void 0 && event.sessionId === void 0 && event.sessionKey === void 0;
|
|
3844
|
+
};
|
|
3845
|
+
const error = (durationMs, terminalReason = "failed") => {
|
|
3895
3846
|
emitTrustedDiagnosticEvent({
|
|
3896
|
-
type: "tool.execution.
|
|
3847
|
+
type: "tool.execution.error",
|
|
3897
3848
|
...diagnosticToolIdentity(params),
|
|
3898
|
-
|
|
3899
|
-
|
|
3849
|
+
durationMs,
|
|
3850
|
+
errorCategory: "codex_dynamic_tool_error",
|
|
3851
|
+
terminalReason
|
|
3900
3852
|
});
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3853
|
+
};
|
|
3854
|
+
return {
|
|
3855
|
+
matchesTerminal,
|
|
3856
|
+
hasPendingTerminal: () => hasPendingInternalDiagnosticEvent(matchesTerminal),
|
|
3857
|
+
started() {
|
|
3858
|
+
emitTrustedDiagnosticEvent({
|
|
3859
|
+
type: "tool.execution.started",
|
|
3860
|
+
...diagnosticToolIdentity(params)
|
|
3861
|
+
});
|
|
3862
|
+
},
|
|
3863
|
+
error,
|
|
3864
|
+
terminal(response, durationMs) {
|
|
3865
|
+
const type = response.diagnosticTerminalType ?? (response.success ? "completed" : "error");
|
|
3866
|
+
if (type === "completed") emitTrustedDiagnosticEvent({
|
|
3867
|
+
type: "tool.execution.completed",
|
|
3868
|
+
...diagnosticToolIdentity(params),
|
|
3869
|
+
durationMs
|
|
3870
|
+
});
|
|
3871
|
+
else if (type === "blocked") emitTrustedDiagnosticEvent({
|
|
3872
|
+
type: "tool.execution.blocked",
|
|
3873
|
+
...diagnosticToolIdentity(params),
|
|
3874
|
+
deniedReason: "plugin-before-tool-call",
|
|
3875
|
+
reason: "Tool call blocked"
|
|
3876
|
+
});
|
|
3877
|
+
else error(durationMs, response.diagnosticTerminalReason ?? "failed");
|
|
3878
|
+
}
|
|
3879
|
+
};
|
|
3907
3880
|
}
|
|
3908
3881
|
//#endregion
|
|
3909
3882
|
//#region extensions/codex/src/app-server/elicitation-bridge.ts
|
|
@@ -4051,7 +4024,8 @@ function resolvePluginElicitation(params) {
|
|
|
4051
4024
|
const context = params.pluginAppPolicyContext;
|
|
4052
4025
|
const entries = context ? Object.values(context.apps) : [];
|
|
4053
4026
|
const pluginEntries = entries.filter(isPluginAppPolicyContextEntry);
|
|
4054
|
-
const
|
|
4027
|
+
const readIdentity = (keys) => readFirstString(meta, keys) ?? readFirstString(requestParams, keys);
|
|
4028
|
+
const appId = readIdentity(PLUGIN_APP_ID_META_KEYS);
|
|
4055
4029
|
const connectorId = readFirstString(meta, PLUGIN_CONNECTOR_ID_META_KEYS);
|
|
4056
4030
|
const isCodexConnectorApproval = isCodexConnectorApprovalElicitation(requestParams, meta);
|
|
4057
4031
|
if (isCodexConnectorApproval && appId && connectorId && codexAppIdentityKey(appId) !== codexAppIdentityKey(connectorId)) return {
|
|
@@ -4076,9 +4050,9 @@ function resolvePluginElicitation(params) {
|
|
|
4076
4050
|
const matches = entries.filter((entry) => entry.mcpServerNames.includes(serverName));
|
|
4077
4051
|
if (matches.length > 0) return uniquePluginMatch(matches, "server_name");
|
|
4078
4052
|
}
|
|
4079
|
-
const pluginName =
|
|
4080
|
-
const configKey =
|
|
4081
|
-
const marketplaceName =
|
|
4053
|
+
const pluginName = readIdentity(PLUGIN_NAME_META_KEYS);
|
|
4054
|
+
const configKey = readIdentity(PLUGIN_CONFIG_KEY_META_KEYS);
|
|
4055
|
+
const marketplaceName = readIdentity(PLUGIN_MARKETPLACE_NAME_META_KEYS);
|
|
4082
4056
|
if (pluginName || configKey) {
|
|
4083
4057
|
if (!context) return {
|
|
4084
4058
|
kind: "decline",
|
|
@@ -4173,31 +4147,19 @@ function readApprovalElicitation(requestParams, source) {
|
|
|
4173
4147
|
meta,
|
|
4174
4148
|
...source.kind !== "computer-use" ? {
|
|
4175
4149
|
persistHintsMode: "explicit",
|
|
4176
|
-
allowedDecisions:
|
|
4150
|
+
allowedDecisions: canMapPersistentApproval(requestedSchema, meta, source.kind === "mcp") ? [
|
|
4151
|
+
"allow-once",
|
|
4152
|
+
"allow-always",
|
|
4153
|
+
"deny"
|
|
4154
|
+
] : ["allow-once", "deny"]
|
|
4177
4155
|
} : {}
|
|
4178
4156
|
};
|
|
4179
4157
|
}
|
|
4180
|
-
function buildApprovalAllowedDecisions(requestedSchema, meta, allowSession = false) {
|
|
4181
|
-
return canMapPersistentApproval(requestedSchema, meta, allowSession) ? [
|
|
4182
|
-
"allow-once",
|
|
4183
|
-
"allow-always",
|
|
4184
|
-
"deny"
|
|
4185
|
-
] : ["allow-once", "deny"];
|
|
4186
|
-
}
|
|
4187
4158
|
function canMapPersistentApproval(requestedSchema, meta, allowSession) {
|
|
4188
4159
|
const persistHints = readPersistHints(meta, "explicit");
|
|
4189
4160
|
if (allowSession) return choosePersistHint(persistHints) !== void 0;
|
|
4190
4161
|
if (persistHints.length > 0) return persistHints.includes("always");
|
|
4191
|
-
|
|
4192
|
-
return Object.entries(properties).some(([name, value]) => {
|
|
4193
|
-
const schema = isJsonObject(value) ? value : void 0;
|
|
4194
|
-
if (!schema) return false;
|
|
4195
|
-
return isPersistField({
|
|
4196
|
-
name,
|
|
4197
|
-
schema,
|
|
4198
|
-
required: false
|
|
4199
|
-
}) && chooseAlwaysPersistOptionValue(readEnumOptions(schema)) !== void 0;
|
|
4200
|
-
});
|
|
4162
|
+
return readApprovalProperties(isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {}).some((property) => isPersistField(property) && chooseAlwaysPersistOptionValue(readEnumOptions(property.schema)) !== void 0);
|
|
4201
4163
|
}
|
|
4202
4164
|
function logPluginElicitationDecline(reason, requestParams) {
|
|
4203
4165
|
embeddedAgentLog.debug("codex plugin elicitation declined", {
|
|
@@ -4227,25 +4189,26 @@ function buildApprovalDescription(params) {
|
|
|
4227
4189
|
].filter(Boolean).join("\n\n");
|
|
4228
4190
|
}
|
|
4229
4191
|
function readPropertyDescriptionLines(requestedSchema) {
|
|
4230
|
-
|
|
4231
|
-
return Object.entries(properties).map(([name, value]) => {
|
|
4232
|
-
const schema = isJsonObject(value) ? value : void 0;
|
|
4233
|
-
if (!schema) return;
|
|
4192
|
+
return readApprovalProperties(isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {}).map(({ name, schema }) => {
|
|
4234
4193
|
const propTitle = sanitizeDisplayText(readNonBlankString(schema.title) ?? "") || sanitizeDisplayText(name) || "field";
|
|
4235
4194
|
const description = sanitizeOptionalDisplayText(readNonBlankString(schema.description));
|
|
4236
4195
|
return description ? `- ${propTitle}: ${description}` : `- ${propTitle}`;
|
|
4237
|
-
})
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
function readApprovalProperties(properties) {
|
|
4199
|
+
return Object.entries(properties).flatMap(([name, schema]) => isJsonObject(schema) ? [{
|
|
4200
|
+
name,
|
|
4201
|
+
schema
|
|
4202
|
+
}] : []);
|
|
4238
4203
|
}
|
|
4239
4204
|
function readDisplayParamLines(meta) {
|
|
4240
4205
|
const displayParams = meta[MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY];
|
|
4241
4206
|
if (!Array.isArray(displayParams)) return [];
|
|
4242
|
-
const lines = displayParams.slice(0, MAX_DISPLAY_PARAM_ENTRIES).
|
|
4243
|
-
|
|
4244
|
-
if (!param) return;
|
|
4207
|
+
const lines = displayParams.slice(0, MAX_DISPLAY_PARAM_ENTRIES).flatMap((param) => {
|
|
4208
|
+
if (!isJsonObject(param)) return [];
|
|
4245
4209
|
const name = sanitizeOptionalDisplayText(readNonBlankString(param.display_name)) ?? sanitizeOptionalDisplayText(readNonBlankString(param.name));
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
}).filter((line) => Boolean(line));
|
|
4210
|
+
return name ? [`- ${name}: ${formatDisplayParamValue(param.value)}`] : [];
|
|
4211
|
+
});
|
|
4249
4212
|
const remaining = displayParams.length - MAX_DISPLAY_PARAM_ENTRIES;
|
|
4250
4213
|
return remaining > 0 ? [...lines, `- Additional parameters: ${remaining} more`] : lines;
|
|
4251
4214
|
}
|
|
@@ -4268,19 +4231,15 @@ function formatDisplayJsonValue(value, depth = MAX_DISPLAY_VALUE_DEPTH) {
|
|
|
4268
4231
|
if (typeof value === "object") {
|
|
4269
4232
|
if (depth <= 0) return "{truncated}";
|
|
4270
4233
|
const parts = [];
|
|
4271
|
-
let count = 0;
|
|
4272
|
-
let truncated = false;
|
|
4273
4234
|
for (const key in value) {
|
|
4274
4235
|
if (!Object.hasOwn(value, key)) continue;
|
|
4275
|
-
if (
|
|
4276
|
-
|
|
4236
|
+
if (parts.length >= MAX_DISPLAY_VALUE_OBJECT_KEYS) {
|
|
4237
|
+
parts.push("...");
|
|
4277
4238
|
break;
|
|
4278
4239
|
}
|
|
4279
4240
|
const safeKey = truncateCodexApprovalDisplayText(sanitizeDisplayText(key), 80);
|
|
4280
4241
|
parts.push(`${JSON.stringify(safeKey)}:${formatDisplayJsonValue(value[key] ?? null, depth - 1)}`);
|
|
4281
|
-
count += 1;
|
|
4282
4242
|
}
|
|
4283
|
-
if (truncated) parts.push("...");
|
|
4284
4243
|
return `{${parts.join(",")}}`;
|
|
4285
4244
|
}
|
|
4286
4245
|
return "null";
|
|
@@ -4318,41 +4277,28 @@ function buildAcceptedContent(approvalPrompt, outcome) {
|
|
|
4318
4277
|
const content = {};
|
|
4319
4278
|
let sawApprovalField = false;
|
|
4320
4279
|
const persist = choosePersistHint(readPersistHints(meta, approvalPrompt.persistHintsMode));
|
|
4321
|
-
for (const
|
|
4322
|
-
const
|
|
4323
|
-
if (!schema) continue;
|
|
4324
|
-
const property = {
|
|
4325
|
-
name,
|
|
4326
|
-
schema,
|
|
4327
|
-
required: required.has(name)
|
|
4328
|
-
};
|
|
4329
|
-
const next = readApprovalFieldValue(property, outcome, persist) ?? readPersistFieldValue(property, meta, outcome, approvalPrompt.persistHintsMode ?? "legacy") ?? (outcome === "approved-once" && isPersistField(property) ? void 0 : property.schema.default);
|
|
4280
|
+
for (const property of readApprovalProperties(properties)) {
|
|
4281
|
+
const next = readAcceptedPropertyValue(property, outcome, persist, approvalPrompt.persistHintsMode ?? "legacy");
|
|
4330
4282
|
if (isApprovalField(property)) sawApprovalField = true;
|
|
4331
4283
|
if (next === void 0) {
|
|
4332
|
-
if (property.
|
|
4284
|
+
if (required.has(property.name)) return;
|
|
4333
4285
|
continue;
|
|
4334
4286
|
}
|
|
4335
|
-
content[name] = next;
|
|
4287
|
+
content[property.name] = next;
|
|
4336
4288
|
}
|
|
4337
4289
|
return sawApprovalField ? content : void 0;
|
|
4338
4290
|
}
|
|
4339
|
-
function
|
|
4340
|
-
if (
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
return
|
|
4347
|
-
|
|
4348
|
-
function readPersistFieldValue(property, meta, outcome, persistHintsMode) {
|
|
4349
|
-
if (!isPersistField(property) || outcome !== "approved-session") return;
|
|
4350
|
-
const persistHints = readPersistHints(meta, persistHintsMode);
|
|
4291
|
+
function readAcceptedPropertyValue(property, outcome, persist, persistHintsMode) {
|
|
4292
|
+
if (isApprovalField(property)) {
|
|
4293
|
+
if (readNonBlankString(property.schema.type) === "boolean") return true;
|
|
4294
|
+
const options = readEnumOptions(property.schema);
|
|
4295
|
+
const choice = (outcome === "approved-session" ? options.find((option) => isPersistentApprovalOption(option, persist)) : void 0) ?? options.find(isPositiveApprovalOption);
|
|
4296
|
+
if (choice) return choice.value;
|
|
4297
|
+
}
|
|
4298
|
+
if (!isPersistField(property)) return property.schema.default;
|
|
4299
|
+
if (outcome === "approved-once") return;
|
|
4351
4300
|
const options = readEnumOptions(property.schema);
|
|
4352
|
-
|
|
4353
|
-
const preferred = choosePersistHint(persistHints);
|
|
4354
|
-
if (preferred) return options.find((option) => option.value === preferred || option.label === preferred)?.value;
|
|
4355
|
-
if (persistHintsMode === "explicit") return chooseAlwaysPersistOptionValue(options);
|
|
4301
|
+
return (persist ? options.find((option) => option.value === persist || option.label === persist)?.value : persistHintsMode === "explicit" ? chooseAlwaysPersistOptionValue(options) : void 0) ?? property.schema.default;
|
|
4356
4302
|
}
|
|
4357
4303
|
function isApprovalField(property) {
|
|
4358
4304
|
const haystack = propertyText(property).toLowerCase();
|
|
@@ -4381,14 +4327,10 @@ function buildAcceptedMeta(meta, outcome, persistHintsMode) {
|
|
|
4381
4327
|
return persist ? { persist } : null;
|
|
4382
4328
|
}
|
|
4383
4329
|
function choosePersistHint(persistHints) {
|
|
4384
|
-
|
|
4385
|
-
if (persistHints.includes("session")) return "session";
|
|
4330
|
+
return ["always", "session"].find((hint) => persistHints.includes(hint));
|
|
4386
4331
|
}
|
|
4387
4332
|
function chooseAlwaysPersistOptionValue(options) {
|
|
4388
|
-
return options.find((option) =>
|
|
4389
|
-
}
|
|
4390
|
-
function optionMatchesPersist(option, persist) {
|
|
4391
|
-
return option.value.toLowerCase() === persist || option.label.toLowerCase() === persist;
|
|
4333
|
+
return options.find((option) => option.value.toLowerCase() === "always" || option.label.toLowerCase() === "always")?.value;
|
|
4392
4334
|
}
|
|
4393
4335
|
function hasNoSchemaProperties(requestedSchema) {
|
|
4394
4336
|
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
|
|
@@ -4403,15 +4345,14 @@ function readEnumOptions(schema) {
|
|
|
4403
4345
|
label: labels[index] ?? value
|
|
4404
4346
|
}));
|
|
4405
4347
|
}
|
|
4406
|
-
if (Array.isArray(schema.oneOf)) return schema.oneOf.
|
|
4348
|
+
if (Array.isArray(schema.oneOf)) return schema.oneOf.flatMap((entry) => {
|
|
4407
4349
|
const option = isJsonObject(entry) ? entry : void 0;
|
|
4408
4350
|
const value = readNonBlankString(option?.const);
|
|
4409
|
-
|
|
4410
|
-
return {
|
|
4351
|
+
return value ? [{
|
|
4411
4352
|
value,
|
|
4412
4353
|
label: readNonBlankString(option?.title) ?? value
|
|
4413
|
-
};
|
|
4414
|
-
})
|
|
4354
|
+
}] : [];
|
|
4355
|
+
});
|
|
4415
4356
|
return [];
|
|
4416
4357
|
}
|
|
4417
4358
|
function isPositiveApprovalOption(option) {
|
|
@@ -4563,7 +4504,7 @@ function projectCodexDynamicTools(tools) {
|
|
|
4563
4504
|
continue;
|
|
4564
4505
|
}
|
|
4565
4506
|
const descriptor = readCodexDynamicToolDescriptor(tool, toolIndex);
|
|
4566
|
-
if (
|
|
4507
|
+
if ("diagnostic" in descriptor) {
|
|
4567
4508
|
quarantinedTools.push(descriptor.diagnostic);
|
|
4568
4509
|
continue;
|
|
4569
4510
|
}
|
|
@@ -4597,13 +4538,10 @@ function projectCodexDynamicTools(tools) {
|
|
|
4597
4538
|
}
|
|
4598
4539
|
function readCodexDynamicToolDescriptor(tool, toolIndex) {
|
|
4599
4540
|
const fallbackName = `tool[${toolIndex}]`;
|
|
4600
|
-
const invalid = (name, violation) => ({
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
violations: [`${name}.${violation}`]
|
|
4605
|
-
}
|
|
4606
|
-
});
|
|
4541
|
+
const invalid = (name, violation) => ({ diagnostic: {
|
|
4542
|
+
tool: name,
|
|
4543
|
+
violations: [`${name}.${violation}`]
|
|
4544
|
+
} });
|
|
4607
4545
|
let name;
|
|
4608
4546
|
try {
|
|
4609
4547
|
const rawName = tool.name;
|
|
@@ -4633,7 +4571,6 @@ function readCodexDynamicToolDescriptor(tool, toolIndex) {
|
|
|
4633
4571
|
return invalid(name, "inputSchema is unreadable");
|
|
4634
4572
|
}
|
|
4635
4573
|
return {
|
|
4636
|
-
ok: true,
|
|
4637
4574
|
name,
|
|
4638
4575
|
description,
|
|
4639
4576
|
parameters
|
|
@@ -4785,6 +4722,39 @@ function projectCodexExecutableDynamicToolSurface(tools, hookContext) {
|
|
|
4785
4722
|
};
|
|
4786
4723
|
}
|
|
4787
4724
|
//#endregion
|
|
4725
|
+
//#region extensions/codex/src/app-server/dynamic-tool-source-reply.ts
|
|
4726
|
+
/**
|
|
4727
|
+
* Resolves the source-reply facts of one Codex dynamic tool result and records whether
|
|
4728
|
+
* it ends the Codex turn on `response`. A final reply
|
|
4729
|
+
* authored by a `canDeliverSourceReply` tool, read from the result after middleware and
|
|
4730
|
+
* extensions, is appended to `payloads`; the host delivers it and writes its transcript
|
|
4731
|
+
* row after the send. Only calls in the model-only namespace qualify: Codex never
|
|
4732
|
+
* exposes that namespace to Code Mode programs, so a program's intermediate call
|
|
4733
|
+
* cannot end the turn or reach the conversation.
|
|
4734
|
+
*/
|
|
4735
|
+
function resolveCodexToolResultSourceReply(params) {
|
|
4736
|
+
const messageToolOnly = params.sourceReplyDeliveryMode === "message_tool_only" && params.toolName === "message";
|
|
4737
|
+
const toolConfirmed = messageToolOnly && !params.resultIsError && (params.rawResult.terminate === true || params.result.terminate === true);
|
|
4738
|
+
const confirmed = messageToolOnly && (toolConfirmed || params.deliveredSourceReply);
|
|
4739
|
+
const final = confirmed ? params.executedArgs.final !== false : void 0;
|
|
4740
|
+
const payload = params.canDeliverSourceReply === true && !params.resultIsError && params.call.namespace === "openclaw_direct" ? captureToolAuthoredSourceReply({
|
|
4741
|
+
result: params.result,
|
|
4742
|
+
toolCallId: params.call.callId,
|
|
4743
|
+
idempotencyScope: params.runId ?? params.call.turnId
|
|
4744
|
+
}) : void 0;
|
|
4745
|
+
if (payload) params.payloads.push(payload);
|
|
4746
|
+
const toolAuthoredFinal = Boolean(payload);
|
|
4747
|
+
const continuesSourceReplyProgress = confirmed && final === false;
|
|
4748
|
+
const terminate = toolAuthoredFinal || (params.rawResult.terminate === true || params.result.terminate === true) && !continuesSourceReplyProgress || [params.rawResult, params.result].some(({ details }) => isRecord(details) && typeof details.status === "string" && details.status.trim().toLowerCase() === "yielded") || confirmed && final === true || void 0;
|
|
4749
|
+
params.response.terminate = terminate;
|
|
4750
|
+
if (toolAuthoredFinal) params.response.toolAuthoredFinalReply = true;
|
|
4751
|
+
return {
|
|
4752
|
+
toolConfirmed,
|
|
4753
|
+
final,
|
|
4754
|
+
terminate
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
//#endregion
|
|
4788
4758
|
//#region extensions/codex/src/app-server/dynamic-tools.ts
|
|
4789
4759
|
const MAX_CODEX_DYNAMIC_TOOL_VALIDATION_ERRORS = 4;
|
|
4790
4760
|
const MAX_CODEX_DYNAMIC_TOOL_VALIDATION_ERROR_CHARS = 160;
|
|
@@ -4821,11 +4791,6 @@ function computerFrameImageIdentity(content) {
|
|
|
4821
4791
|
const image = expectDefined(images[0], "single Codex computer frame image");
|
|
4822
4792
|
return createHash("sha256").update(JSON.stringify([image.mimeType, image.data])).digest("hex");
|
|
4823
4793
|
}
|
|
4824
|
-
function invalidateComputerFrame(contextEpoch) {
|
|
4825
|
-
contextEpoch.value += 1;
|
|
4826
|
-
delete contextEpoch.frameToolCallId;
|
|
4827
|
-
delete contextEpoch.frameImageIdentity;
|
|
4828
|
-
}
|
|
4829
4794
|
function createCodexDynamicToolBridge(params) {
|
|
4830
4795
|
const toolResultHookContext = toToolResultHookContext(params.hookContext);
|
|
4831
4796
|
const contextWindowTokens = params.hookContext?.contextWindowTokens;
|
|
@@ -5089,7 +5054,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5089
5054
|
const contentItems = convertToolContents(result.content, toolResultMaxChars);
|
|
5090
5055
|
const deliveredFrameImages = contentItems.filter((item) => item.type === "inputImage");
|
|
5091
5056
|
const finalFrameImageIdentity = computerFrameImageIdentity(result.content);
|
|
5092
|
-
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId && (deliveredFrameImages.length !== 1 || finalFrameImageIdentity === void 0 || finalFrameImageIdentity !== params.computerContextEpoch.frameImageIdentity))
|
|
5057
|
+
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId && (deliveredFrameImages.length !== 1 || finalFrameImageIdentity === void 0 || finalFrameImageIdentity !== params.computerContextEpoch.frameImageIdentity)) invalidateCodexComputerFrame(params.computerContextEpoch);
|
|
5093
5058
|
const response = {
|
|
5094
5059
|
contentItems,
|
|
5095
5060
|
success: !resultIsError,
|
|
@@ -5097,9 +5062,20 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5097
5062
|
diagnosticTerminalReason: resultFailureKind === "blocked" ? void 0 : resultFailureKind,
|
|
5098
5063
|
transcriptDetails: asOptionalRecord(sanitizeToolResult(result))?.details
|
|
5099
5064
|
};
|
|
5100
|
-
const
|
|
5101
|
-
|
|
5102
|
-
|
|
5065
|
+
const sourceReply = resolveCodexToolResultSourceReply({
|
|
5066
|
+
sourceReplyDeliveryMode: params.hookContext?.sourceReplyDeliveryMode,
|
|
5067
|
+
canDeliverSourceReply: toolEntry.tool.canDeliverSourceReply,
|
|
5068
|
+
toolName,
|
|
5069
|
+
call,
|
|
5070
|
+
resultIsError,
|
|
5071
|
+
rawResult,
|
|
5072
|
+
result,
|
|
5073
|
+
deliveredSourceReply,
|
|
5074
|
+
executedArgs,
|
|
5075
|
+
runId: toolResultHookContext.runId,
|
|
5076
|
+
payloads: telemetry.messagingToolSourceReplyPayloads,
|
|
5077
|
+
response
|
|
5078
|
+
});
|
|
5103
5079
|
const autoDeliveryTtsMediaUrls = getCoreTtsToolResultMediaUrls(rawResult);
|
|
5104
5080
|
recordAgentHarnessToolResultTelemetry({
|
|
5105
5081
|
extractSourceReplyPayload: extractMessagingToolSourceReplyPayload,
|
|
@@ -5117,12 +5093,10 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5117
5093
|
autoDeliveryTtsMediaUrls,
|
|
5118
5094
|
coreTtsToolResult: autoDeliveryTtsMediaUrls?.length ? rawResult : void 0,
|
|
5119
5095
|
messagingTarget: confirmedMessagingTarget,
|
|
5120
|
-
sourceReplyFinal,
|
|
5096
|
+
sourceReplyFinal: sourceReply.final,
|
|
5121
5097
|
trustedLocalMediaToolNames: pluginLocalMediaTrustByToolName.get(toolName)
|
|
5122
5098
|
});
|
|
5123
|
-
if (deliveredSourceReply ||
|
|
5124
|
-
const continuesSourceReplyProgress = confirmedSourceReply && sourceReplyFinal === false;
|
|
5125
|
-
response.terminate = (rawResult.terminate === true || result.terminate === true) && !continuesSourceReplyProgress || isToolResultYield(rawResult) || isToolResultYield(result) || confirmedSourceReply && sourceReplyFinal === true || void 0;
|
|
5099
|
+
if (deliveredSourceReply || sourceReply.toolConfirmed) telemetry.didDeliverSourceReplyViaMessageTool = true;
|
|
5126
5100
|
const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
|
|
5127
5101
|
response.asyncStarted = asyncStarted || void 0;
|
|
5128
5102
|
const replaySafe = executionBoundary.executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
@@ -5134,7 +5108,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5134
5108
|
return response;
|
|
5135
5109
|
},
|
|
5136
5110
|
onError: ({ error, boundary: executionBoundary, executedArguments: executedArgs, startedAt }) => {
|
|
5137
|
-
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId)
|
|
5111
|
+
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId) invalidateCodexComputerFrame(params.computerContextEpoch);
|
|
5138
5112
|
const executionDisposition = getBeforeToolCallFailureDisposition(error) ?? (signal.aborted ? resolveCodexToolAbortTerminalReason(signal) : resolveToolExecutionErrorKind(error));
|
|
5139
5113
|
const errorMessage = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
|
|
5140
5114
|
const operatorHint = readToolOperatorHint(error);
|
|
@@ -5213,10 +5187,12 @@ function reportQuarantinedDynamicTools(params) {
|
|
|
5213
5187
|
});
|
|
5214
5188
|
}
|
|
5215
5189
|
function dedupeQuarantinedDynamicTools(tools) {
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5190
|
+
const byName = /* @__PURE__ */ new Map();
|
|
5191
|
+
for (const { tool, violations } of tools) byName.set(tool, {
|
|
5192
|
+
tool,
|
|
5193
|
+
violations
|
|
5194
|
+
});
|
|
5195
|
+
return [...byName.values()];
|
|
5220
5196
|
}
|
|
5221
5197
|
function toToolResultHookContext(ctx) {
|
|
5222
5198
|
const { agentId, sessionId, sessionKey, runId, channelId } = ctx ?? {};
|
|
@@ -5228,14 +5204,9 @@ function toToolResultHookContext(ctx) {
|
|
|
5228
5204
|
...channelId && { channelId }
|
|
5229
5205
|
};
|
|
5230
5206
|
}
|
|
5231
|
-
function isToolResultYield(result) {
|
|
5232
|
-
const details = result.details;
|
|
5233
|
-
if (!isRecord(details) || typeof details.status !== "string") return false;
|
|
5234
|
-
return details.status.trim().toLowerCase() === "yielded";
|
|
5235
|
-
}
|
|
5236
5207
|
function isAsyncStartedToolResult(result) {
|
|
5237
5208
|
const details = result.details;
|
|
5238
5209
|
return isRecord(details) && details.async === true && details.status === "started";
|
|
5239
5210
|
}
|
|
5240
5211
|
//#endregion
|
|
5241
|
-
export {
|
|
5212
|
+
export { invalidateCodexComputerFrame as _, handleCodexAppServerApprovalRequest as a, createCodexNativePreToolUseFailureBuffer as c, buildCodexTemporalAdditionalContext as d, buildTurnCollaborationMode as f, buildCodexUserInput as g, readCodexRetainedBackgroundCommands as h, createCodexDynamicToolDiagnostics as i, buildCodexHistoryProvenancePrefix as l, CodexNativeProcessAuthority as m, projectCodexExecutableDynamicTools as n, ensureCodexSandboxExecServerEnvironment as o, buildTurnStartParams as p, routeCodexAppServerElicitationRequest as r, releaseCodexSandboxExecServerEnvironment as s, createCodexDynamicToolBridge as t, buildCodexParentLocalInstructions as u, createCodexRemoteWorkspaceFileReader as v, CodexNativeToolLifecycleProjector as y };
|