@openclaw/codex 2026.10.1 → 2026.10.5-beta.1
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-B7Vg4rmy.mjs → account-usage-runtime-QBZsvZS8.mjs} +8 -7
- package/dist/.setup/{app-server-policy-Bg4FZw1f.mjs → app-server-policy-D_zv8J7V.mjs} +3 -4
- package/dist/.setup/{apply-BzlIkqSz.mjs → apply-CBMV8LN0.mjs} +40 -51
- package/dist/.setup/{attempt-client-cleanup-HXQxfMc1.mjs → attempt-client-cleanup-t-kd_1ys.mjs} +6 -6
- package/dist/.setup/attempt-terminal-jO5IMj9K.mjs +2 -0
- package/dist/.setup/{attempt-workspace-context-BmCutoyg.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-BCCQHWdc.mjs → auth-cache-key-DyfUvsSa.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-Cv4uIEzz.mjs → bounded-turn-CyQVQwGe.mjs} +21 -17
- package/dist/.setup/{bounded-turn-isolation-DklXNk3k.mjs → bounded-turn-isolation-BfDAo-qZ.mjs} +4 -4
- package/dist/.setup/{build-state-BwxNfFEB.mjs → build-state-CzLMy1S9.mjs} +1 -1
- package/dist/.setup/{capabilities-kQntafj_.mjs → capabilities-DbOzOglY.mjs} +1 -1
- package/dist/.setup/{client-catalog-response-BNFDYAKh.mjs → client-catalog-response-DsbEAtpd.mjs} +25 -32
- package/dist/.setup/{client-BIRVBXXb.mjs → client-y7nO6X9c.mjs} +198 -247
- package/dist/.setup/{command-dispatch-D3_ypl-R.mjs → command-dispatch-uA9lz2Kf.mjs} +3 -4
- package/dist/.setup/{command-formatters-DV3DHhdr.mjs → command-formatters-xW9KpoSZ.mjs} +16 -26
- package/dist/.setup/{command-handlers-KPit4G0p.mjs → command-handlers-VBaJnpZo.mjs} +270 -360
- package/dist/.setup/{command-rpc-CmQDOxY9.mjs → command-rpc-C9VxOLuG.mjs} +19 -18
- package/dist/.setup/{compact-CLLteFIY.mjs → compact-CD8xJxXS.mjs} +41 -61
- package/dist/.setup/{computer-use-BVxocZZc.mjs → computer-use-CiToXF5C.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-DJ_288rC.mjs → conversation-binding-CTSLDM9S.mjs} +85 -119
- package/dist/.setup/{conversation-binding-data-DJ4ix-W5.mjs → conversation-binding-data-UgLuTSQT.mjs} +9 -8
- package/dist/.setup/{conversation-control-CuiZ_pDL.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-DgWZMfAQ.mjs → dynamic-tool-build-D731PCdv.mjs} +57 -69
- package/dist/.setup/{dynamic-tools-Ct6GKAFr.mjs → dynamic-tools-D0pYbxHH.mjs} +689 -710
- package/dist/.setup/{effective-mcp-catalog-DmCSc8TS.mjs → effective-mcp-catalog-1hWFKghy.mjs} +11 -20
- package/dist/.setup/{elicitation-input-r9axtppm.mjs → elicitation-input-DXG_qaXr.mjs} +14 -20
- package/dist/.setup/{ephemeral-turn-_cBMKCFb.mjs → ephemeral-turn-Cphy5Qr_.mjs} +3 -3
- package/dist/.setup/{event-projector-assistant-message-C3Tpt370.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-CjrQEeke.mjs → isolated-completion-Bhqd2yBq.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-D0yRoreX.mjs → mcp-app-session-preparation-DqSvxFqc.mjs} +24 -25
- package/dist/.setup/{mcp-tool-metadata-j10HhfTC.mjs → mcp-tool-metadata-DW41snf3.mjs} +1 -1
- package/dist/.setup/{model-catalog--G4arndd.mjs → model-catalog-B227ST3D.mjs} +6 -6
- package/dist/.setup/{models-BYbLQPnj.mjs → models-DdRyi93L.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-BUWIOrSr.mjs +561 -0
- package/dist/.setup/{native-mcp-app-5i5eRCwO.mjs → native-mcp-app-CjxJQ4F7.mjs} +6 -6
- package/dist/.setup/native-skill-isolation-UMVoNjZz.mjs +391 -0
- package/dist/.setup/{native-subagent-monitor-B2C8D_N0.mjs → native-subagent-monitor-iHc8oLg5.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-B5bDIEuQ.mjs → plan-C_F3aio0.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-C_SGp5YW.mjs → plugin-approval-roundtrip-BSV-51ig.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-BoIby-eX.mjs → provider-BJ4xhaGX.mjs} +4 -4
- package/dist/.setup/{provider-capabilities-B4yq6UgQ.mjs → provider-capabilities-CjRaV3bx.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-DD3u22uv.mjs → request-Y8dmbqZ3.mjs} +28 -48
- package/dist/.setup/{rpc-error-hmWM21RB.mjs → rpc-error-C98SHchT.mjs} +3 -6
- package/dist/.setup/{run-attempt-DOVF5OON.mjs → run-attempt-Dx7a4Mfm.mjs} +927 -859
- 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-DRaivXvC.mjs → session-catalog-events-DzFlXNUe.mjs} +6 -5
- package/dist/.setup/{session-catalog-index-qwmgqC4t.mjs → session-catalog-index-DozvkUPd.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-z9Q608QW.mjs → session-catalog-native-projection-rCKnZud1.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-Cl0Nc7c6.mjs → session-catalog-projection-DDVmALHj.mjs} +8 -22
- package/dist/.setup/session-catalog-source-CygTGmrb.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-Bt3b0fpe.mjs → session-retirement-IoS3zr17.mjs} +7 -7
- package/dist/.setup/{settled-turn-context-BZsMjkIO.mjs → settled-turn-context-BPL6D3RO.mjs} +1 -1
- package/dist/.setup/{settled-turn-finalizer-e_24O6HA.mjs → settled-turn-finalizer-X-Fylp-u.mjs} +12 -11
- package/dist/.setup/{shared-client-DeenOD8U.mjs → shared-client-ByIwuAC-.mjs} +172 -229
- package/dist/.setup/{side-question-BZHn_1Jm.mjs → side-question-BDUdozFy.mjs} +116 -137
- package/dist/.setup/{source-0ClinBHI.mjs → source-Bxbqf3S1.mjs} +37 -56
- package/dist/.setup/thread-archive-guard-LKdwQKyr.mjs +56 -0
- package/dist/.setup/{thread-lifecycle-CoqYpbpk.mjs → thread-lifecycle-run-CfP4PnEf.mjs} +348 -484
- package/dist/.setup/thread-model-selection-DcVebr8N.mjs +61 -0
- package/dist/.setup/{thread-ownership-CSDZ6mHd.mjs → thread-ownership-Be53aiaG.mjs} +5 -5
- package/dist/.setup/{thread-ownership-queue-Dp24d7-F.mjs → thread-ownership-queue-CeyYScUC.mjs} +5 -6
- package/dist/.setup/{thread-policy-DCC9lQjC.mjs → thread-policy-CHgY-KNl.mjs} +4 -4
- package/dist/.setup/thread-requests-BgzJwJtS.mjs +745 -0
- package/dist/.setup/{thread-resume--WOKWxZd.mjs → thread-resume-B2EmMcgE.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-BWhX_SBh.mjs → turn-router-DKDAziUW.mjs} +30 -31
- package/dist/.setup/upstream-prompt-provenance-Ccd9MFRJ.mjs +44 -0
- package/dist/.setup/{upstream-session-fork-BQvofnk7.mjs → upstream-session-fork-Djp_yssT.mjs} +50 -43
- package/dist/.setup/{usage-BEHcVwcZ.mjs → usage-DQA-peQC.mjs} +4 -4
- package/dist/.setup/{usage-limit-error-D1p3nE6n.mjs → usage-limit-error-2t3KBqiX.mjs} +12 -21
- package/dist/.setup/web-search-item-CvokaZOo.mjs +20 -0
- package/dist/.setup/{web-search-provider.runtime-CV9oiB-v.mjs → web-search-provider.runtime-BsuCMNix.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-CsScGgrk.mjs +0 -124
- package/dist/.setup/service-tier-normalization-Cei89SW6.mjs +0 -15
- package/dist/.setup/shell-dynamic-tools-BqGszXcq.mjs +0 -85
- package/dist/.setup/thread-archive-guard-C9frICzX.mjs +0 -60
- package/dist/.setup/thread-requests-Dwg2b4Xl.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-ByIwuAC-.mjs";
|
|
10
|
+
import { A as createFailedDynamicToolResponse, j as failedToolResult, k as resolveCodexToolAbortTerminalReason, l as CodexServerRequestResolvedError, w as createCodexElicitationResponse, x as readCodexTurnCompletedNotification } from "./client-y7nO6X9c.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-DKDAziUW.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-BSV-51ig.mjs";
|
|
17
|
+
import { d as isCodexPassThroughMediaSource, f as mapCodexAppServerLocalWorkspacePath, p as mapCodexAppServerRemoteWorkspacePath } from "./dynamic-tool-build-D731PCdv.mjs";
|
|
18
|
+
import { c as emitCodexNativePreToolUseFailureDiagnostic } from "./native-hook-relay-BUWIOrSr.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. */
|
|
@@ -458,7 +514,15 @@ var CodexNativeProcessClient = class {
|
|
|
458
514
|
const threadId = readCodexNotificationThreadId(notification.params);
|
|
459
515
|
const turnId = readCodexNotificationTurnId(notification.params);
|
|
460
516
|
const commands = threadId ? this.threads.get(threadId) : void 0;
|
|
461
|
-
if (!commands
|
|
517
|
+
if (!commands) return;
|
|
518
|
+
if (notification.method === "thread/closed" || notification.method === "thread/archived" || notification.method === "thread/deleted") {
|
|
519
|
+
for (const command of commands.values()) {
|
|
520
|
+
command.background = void 0;
|
|
521
|
+
this.closeAdmission(command);
|
|
522
|
+
}
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (!turnId) return;
|
|
462
526
|
if (notification.method === "turn/completed") {
|
|
463
527
|
for (const command of commands.values()) if (command.turnId === turnId) this.closeAdmission(command);
|
|
464
528
|
} else if (notification.method === "item/completed") {
|
|
@@ -516,17 +580,14 @@ var CodexNativeProcessClient = class {
|
|
|
516
580
|
assertAdmission();
|
|
517
581
|
const process = { terminate };
|
|
518
582
|
command.processes.add(process);
|
|
519
|
-
let settled = false;
|
|
520
583
|
return {
|
|
521
584
|
assertAdmission,
|
|
522
585
|
assertCurrent: () => {
|
|
523
586
|
command.owner.assertCurrent();
|
|
524
|
-
if (this.closed ||
|
|
587
|
+
if (this.closed || !command.processes.has(process)) throw new Error("Codex native process authority has ended");
|
|
525
588
|
},
|
|
526
589
|
settle: () => {
|
|
527
|
-
if (
|
|
528
|
-
settled = true;
|
|
529
|
-
command.processes.delete(process);
|
|
590
|
+
if (!command.processes.delete(process)) return;
|
|
530
591
|
this.forgetSettled(command);
|
|
531
592
|
},
|
|
532
593
|
fail: (error) => command.owner.reportSettlementFailure(error)
|
|
@@ -681,16 +742,133 @@ var CodexNativeProcessAuthority = class {
|
|
|
681
742
|
}
|
|
682
743
|
};
|
|
683
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
|
|
684
863
|
//#region extensions/codex/src/app-server/native-pre-tool-use-failures.ts
|
|
685
864
|
/** Holds failures until a projector or terminal diagnostic owns their delivery. */
|
|
686
865
|
function createCodexNativePreToolUseFailureBuffer(params) {
|
|
687
866
|
const pending = [];
|
|
688
|
-
let
|
|
689
|
-
let terminalReason;
|
|
867
|
+
let fallback;
|
|
690
868
|
const emit = (failure) => emitCodexNativePreToolUseFailureDiagnostic({
|
|
691
869
|
...params,
|
|
692
870
|
failure,
|
|
693
|
-
...
|
|
871
|
+
...fallback ? { terminalReason: fallback.terminalReason ?? failure.disposition } : {}
|
|
694
872
|
});
|
|
695
873
|
const flush = () => {
|
|
696
874
|
for (const failure of pending.splice(0)) emit(failure);
|
|
@@ -698,17 +876,14 @@ function createCodexNativePreToolUseFailureBuffer(params) {
|
|
|
698
876
|
return {
|
|
699
877
|
pending,
|
|
700
878
|
get active() {
|
|
701
|
-
return
|
|
879
|
+
return fallback !== void 0;
|
|
702
880
|
},
|
|
703
881
|
record(failure) {
|
|
704
|
-
if (
|
|
882
|
+
if (fallback) emit(failure);
|
|
705
883
|
else pending.push(failure);
|
|
706
884
|
},
|
|
707
885
|
activateFallback(wasAborted) {
|
|
708
|
-
|
|
709
|
-
terminalReason = wasAborted ? resolveCodexToolAbortTerminalReason(params.signal) : void 0;
|
|
710
|
-
active = true;
|
|
711
|
-
}
|
|
886
|
+
fallback ??= { terminalReason: wasAborted ? resolveCodexToolAbortTerminalReason(params.signal) : void 0 };
|
|
712
887
|
flush();
|
|
713
888
|
},
|
|
714
889
|
flush
|
|
@@ -1063,11 +1238,6 @@ function requireString(value, label) {
|
|
|
1063
1238
|
if (typeof value !== "string" || !value) throw new Error(`${label} must be a non-empty string.`);
|
|
1064
1239
|
return value;
|
|
1065
1240
|
}
|
|
1066
|
-
/** Validates a base64 payload parameter as a string; decoding happens at call sites. */
|
|
1067
|
-
function requireBase64String(value, label) {
|
|
1068
|
-
if (typeof value !== "string") throw new Error(`${label} must be a string.`);
|
|
1069
|
-
return value;
|
|
1070
|
-
}
|
|
1071
1241
|
function requireNumber(value, label) {
|
|
1072
1242
|
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${label} must be a finite number.`);
|
|
1073
1243
|
return value;
|
|
@@ -1110,34 +1280,28 @@ function sendError(send, id, code, message) {
|
|
|
1110
1280
|
const WINDOWS_DRIVE_PATH_RE = /^\/[A-Za-z]:(?:\/|$)/u;
|
|
1111
1281
|
/** Resolves one Codex exec-server PathUri into a POSIX sandbox path. */
|
|
1112
1282
|
function resolveExecServerPath(rawPath, label) {
|
|
1113
|
-
|
|
1114
|
-
try {
|
|
1115
|
-
pathUrl = new URL(rawPath);
|
|
1116
|
-
} catch (error) {
|
|
1117
|
-
throw new Error(`${label} must be a valid file URI: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1118
|
-
}
|
|
1283
|
+
const pathUrl = parseSandboxPath(() => new URL(rawPath), `${label} must be a valid file URI`);
|
|
1119
1284
|
if (pathUrl.protocol !== "file:") throw new Error(`${label} URI must use the file scheme, received ${pathUrl.protocol.slice(0, -1)}.`);
|
|
1120
1285
|
if (pathUrl.search || pathUrl.hash) throw new Error(`${label} file URI must not include a query or fragment.`);
|
|
1121
|
-
|
|
1122
|
-
try {
|
|
1123
|
-
resolved = fileURLToPath(pathUrl, { windows: false });
|
|
1124
|
-
} catch (error) {
|
|
1125
|
-
throw new Error(`${label} file URI is not valid for the sandbox: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1126
|
-
}
|
|
1286
|
+
const resolved = parseSandboxPath(() => fileURLToPath(pathUrl, { windows: false }), `${label} file URI is not valid for the sandbox`);
|
|
1127
1287
|
if (WINDOWS_DRIVE_PATH_RE.test(resolved)) throw new Error(`${label} Windows file URI is not supported by the sandbox.`);
|
|
1128
1288
|
if (resolved.includes("\0")) throw new Error(`${label} file URI must not contain a null byte.`);
|
|
1129
1289
|
return resolved;
|
|
1130
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
|
+
}
|
|
1131
1298
|
//#endregion
|
|
1132
1299
|
//#region extensions/codex/src/app-server/sandbox-exec-server/fs-policy.ts
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
function assertFsSandboxAccess(execServer, record, requests) {
|
|
1139
|
-
assertResolvedFsSandboxAccess(resolveFsSandboxPolicy(execServer, record), requests);
|
|
1140
|
-
}
|
|
1300
|
+
const FS_ACCESS_RANK = {
|
|
1301
|
+
read: 0,
|
|
1302
|
+
write: 1,
|
|
1303
|
+
none: 2
|
|
1304
|
+
};
|
|
1141
1305
|
/** Parses a Codex managed filesystem sandbox context into normalized access entries. */
|
|
1142
1306
|
function resolveFsSandboxPolicy(execServer, record) {
|
|
1143
1307
|
if (record.sandbox === void 0 || record.sandbox === null) return;
|
|
@@ -1174,13 +1338,8 @@ function resolveFsSandboxEntry(entry, cwd) {
|
|
|
1174
1338
|
const access = readFsAccessMode(entry.access);
|
|
1175
1339
|
const pathSpec = requireObject(entry.path, "fs sandbox entry path");
|
|
1176
1340
|
const pathType = requireString(pathSpec.type, "fs sandbox entry path type");
|
|
1177
|
-
if (pathType === "path")
|
|
1178
|
-
|
|
1179
|
-
path: normalizeSandboxAbsolutePath(resolveExecServerPath(requireString(pathSpec.path, "fs sandbox path"), "fs sandbox path"), "fs sandbox path"),
|
|
1180
|
-
access
|
|
1181
|
-
};
|
|
1182
|
-
if (pathType === "special") {
|
|
1183
|
-
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);
|
|
1184
1343
|
return path === void 0 ? void 0 : {
|
|
1185
1344
|
kind: "path",
|
|
1186
1345
|
path,
|
|
@@ -1220,18 +1379,17 @@ function resolveFsSpecialPath(value, cwd) {
|
|
|
1220
1379
|
function assertResolvedFsSandboxAccess(policy, requests) {
|
|
1221
1380
|
if (!policy?.unrestricted && policy) for (const request of requests) {
|
|
1222
1381
|
const access = resolveFsAccess(policy, request.path);
|
|
1223
|
-
if (request.access === "read"
|
|
1224
|
-
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}`);
|
|
1225
1383
|
}
|
|
1226
1384
|
}
|
|
1227
1385
|
function resolveFsAccess(policy, rawPath) {
|
|
1228
1386
|
const target = normalizeSandboxAbsolutePath(rawPath, "fs path");
|
|
1229
1387
|
let selected;
|
|
1230
1388
|
for (const entry of policy.entries) {
|
|
1231
|
-
if (!
|
|
1389
|
+
if (!(entry.kind === "path" ? pathContains(entry.path, target) : entry.matcher.test(target))) continue;
|
|
1232
1390
|
const candidate = {
|
|
1233
|
-
specificity:
|
|
1234
|
-
rank:
|
|
1391
|
+
specificity: (entry.kind === "path" ? entry.path : entry.literalPrefix).split("/").filter(Boolean).length,
|
|
1392
|
+
rank: FS_ACCESS_RANK[entry.access],
|
|
1235
1393
|
access: entry.access
|
|
1236
1394
|
};
|
|
1237
1395
|
if (!selected || candidate.specificity > selected.specificity || candidate.specificity === selected.specificity && candidate.rank > selected.rank) selected = candidate;
|
|
@@ -1243,10 +1401,10 @@ function assertNoReadOnlyDescendant(policy, rawPath, operation) {
|
|
|
1243
1401
|
if (!policy || policy.unrestricted) return;
|
|
1244
1402
|
const target = normalizeSandboxAbsolutePath(rawPath, "fs path");
|
|
1245
1403
|
const protectedDescendant = policy.entries.find((entry) => {
|
|
1246
|
-
if (entry.access === "write"
|
|
1247
|
-
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);
|
|
1248
1406
|
const protectedPath = entry.path;
|
|
1249
|
-
return protectedPath && resolveFsAccess(policy, protectedPath) !== "write";
|
|
1407
|
+
return pathContains(target, protectedPath) && target !== protectedPath && protectedPath && resolveFsAccess(policy, protectedPath) !== "write";
|
|
1250
1408
|
});
|
|
1251
1409
|
if (protectedDescendant) {
|
|
1252
1410
|
const protectedPath = protectedDescendant.kind === "path" ? protectedDescendant.path : protectedDescendant.pattern;
|
|
@@ -1256,32 +1414,11 @@ function assertNoReadOnlyDescendant(policy, rawPath, operation) {
|
|
|
1256
1414
|
/** Normalizes and validates an absolute POSIX path inside the sandbox namespace. */
|
|
1257
1415
|
function normalizeSandboxAbsolutePath(rawPath, label) {
|
|
1258
1416
|
if (!rawPath || rawPath.includes("\0") || !rawPath.startsWith("/")) throw new Error(`${label} must be an absolute sandbox path.`);
|
|
1259
|
-
|
|
1260
|
-
return normalized === "//" ? "/" : normalized;
|
|
1417
|
+
return posix.normalize(rawPath);
|
|
1261
1418
|
}
|
|
1262
|
-
/** Returns true when target is root itself or a descendant of root. */
|
|
1263
1419
|
function pathContains(root, target) {
|
|
1264
1420
|
return root === "/" || target === root || target.startsWith(`${root}/`);
|
|
1265
1421
|
}
|
|
1266
|
-
function fsSandboxEntryMatches(entry, target) {
|
|
1267
|
-
if (entry.kind === "path") return pathContains(entry.path, target);
|
|
1268
|
-
return entry.matcher.test(target);
|
|
1269
|
-
}
|
|
1270
|
-
function fsSandboxEntryCanAffectDescendant(entry, target) {
|
|
1271
|
-
if (entry.kind === "path") return pathContains(target, entry.path) && target !== entry.path;
|
|
1272
|
-
return pathContains(target, entry.literalPrefix) || pathContains(entry.literalPrefix, target);
|
|
1273
|
-
}
|
|
1274
|
-
function fsSandboxEntrySpecificity(entry) {
|
|
1275
|
-
return pathSpecificity(entry.kind === "path" ? entry.path : entry.literalPrefix);
|
|
1276
|
-
}
|
|
1277
|
-
function pathSpecificity(filePath) {
|
|
1278
|
-
return filePath === "/" ? 0 : filePath.split("/").filter(Boolean).length;
|
|
1279
|
-
}
|
|
1280
|
-
function fsAccessRank(access) {
|
|
1281
|
-
if (access === "none") return 2;
|
|
1282
|
-
if (access === "write") return 1;
|
|
1283
|
-
return 0;
|
|
1284
|
-
}
|
|
1285
1422
|
function normalizeSandboxGlobPattern(pattern) {
|
|
1286
1423
|
if (!pattern || pattern.includes("\0") || !pattern.startsWith("/")) throw new Error("fs sandbox glob pattern must be absolute.");
|
|
1287
1424
|
return pattern.replace(/\/{2,}/gu, "/");
|
|
@@ -1321,15 +1458,10 @@ function compileSandboxGlobCharacterClass(pattern, startIndex) {
|
|
|
1321
1458
|
endIndex: index
|
|
1322
1459
|
};
|
|
1323
1460
|
if (!char || char === "/") throw new Error("fs sandbox glob character class cannot match path separators.");
|
|
1324
|
-
body +=
|
|
1461
|
+
body += char === "\\" || char === "]" || body.length === 0 && char === "^" ? `\\${char}` : char;
|
|
1325
1462
|
}
|
|
1326
1463
|
throw new Error("fs sandbox glob character class must be closed.");
|
|
1327
1464
|
}
|
|
1328
|
-
function escapeSandboxGlobCharacterClassChar(char, first) {
|
|
1329
|
-
if (char === "\\" || char === "]") return `\\${char}`;
|
|
1330
|
-
if (first && char === "^") return "\\^";
|
|
1331
|
-
return char;
|
|
1332
|
-
}
|
|
1333
1465
|
function sandboxGlobLiteralPrefix(pattern) {
|
|
1334
1466
|
const wildcardIndex = pattern.search(/[*?[]/u);
|
|
1335
1467
|
const prefix = wildcardIndex === -1 ? pattern : pattern.slice(0, wildcardIndex);
|
|
@@ -1369,6 +1501,9 @@ async function openFile(execServer, handles, params) {
|
|
|
1369
1501
|
reservedBytes: 0
|
|
1370
1502
|
};
|
|
1371
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
|
+
};
|
|
1372
1507
|
try {
|
|
1373
1508
|
const readPolicy = await authorizePhysicalReadPath(fsBridge, fsSandboxPolicy, filePath, handle.abortController.signal);
|
|
1374
1509
|
const stat = await fsBridge.stat({
|
|
@@ -1376,7 +1511,7 @@ async function openFile(execServer, handles, params) {
|
|
|
1376
1511
|
signal: handle.abortController.signal,
|
|
1377
1512
|
...readPolicy
|
|
1378
1513
|
});
|
|
1379
|
-
|
|
1514
|
+
assertCurrent();
|
|
1380
1515
|
if (!stat) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "file not found");
|
|
1381
1516
|
if (stat.type !== "file") throw new JsonRpcProtocolError(-32600, "file read handle requires a regular file");
|
|
1382
1517
|
if (!Number.isSafeInteger(stat.size) || stat.size < 0) throw new JsonRpcProtocolError(-32600, "file size must be a non-negative safe integer");
|
|
@@ -1388,7 +1523,7 @@ async function openFile(execServer, handles, params) {
|
|
|
1388
1523
|
signal: handle.abortController.signal,
|
|
1389
1524
|
...readPolicy
|
|
1390
1525
|
});
|
|
1391
|
-
|
|
1526
|
+
assertCurrent();
|
|
1392
1527
|
if (data.byteLength > handle.reservedBytes) throw new JsonRpcProtocolError(-32600, "sandbox file read exceeds the per-connection buffered file limit");
|
|
1393
1528
|
handle.reservedBytes = data.byteLength;
|
|
1394
1529
|
handle.data = data;
|
|
@@ -1444,9 +1579,10 @@ function requireFileReadHandleId(value) {
|
|
|
1444
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`);
|
|
1445
1580
|
return handleId;
|
|
1446
1581
|
}
|
|
1447
|
-
async function
|
|
1448
|
-
const record = requireObject(params,
|
|
1449
|
-
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");
|
|
1450
1586
|
const fsSandboxPolicy = resolveFsSandboxPolicy(execServer, record);
|
|
1451
1587
|
assertResolvedFsSandboxAccess(fsSandboxPolicy, [{
|
|
1452
1588
|
path: filePath,
|
|
@@ -1454,11 +1590,19 @@ async function readFile(execServer, params) {
|
|
|
1454
1590
|
}]);
|
|
1455
1591
|
const fsBridge = execServer.fsBridge;
|
|
1456
1592
|
const readPolicy = await authorizePhysicalReadPath(fsBridge, fsSandboxPolicy, filePath);
|
|
1457
|
-
const stat = await fsBridge.stat({
|
|
1593
|
+
const stat = await (metadataOnly ? execServer.fsBridge : fsBridge).stat({
|
|
1458
1594
|
filePath,
|
|
1459
1595
|
...readPolicy
|
|
1460
1596
|
});
|
|
1461
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
|
+
};
|
|
1462
1606
|
assertSandboxFileReadWithinLimit(stat);
|
|
1463
1607
|
return { dataBase64: (await fsBridge.readFile({
|
|
1464
1608
|
filePath,
|
|
@@ -1466,75 +1610,39 @@ async function readFile(execServer, params) {
|
|
|
1466
1610
|
...readPolicy
|
|
1467
1611
|
})).toString("base64") };
|
|
1468
1612
|
}
|
|
1469
|
-
async function
|
|
1470
|
-
const record = requireObject(params,
|
|
1471
|
-
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");
|
|
1472
1617
|
const fsBridge = execServer.fsBridge;
|
|
1473
1618
|
const canonicalDestination = await fsBridge.resolvePinnedMutationTarget?.({
|
|
1474
1619
|
filePath,
|
|
1475
|
-
action: "write"
|
|
1620
|
+
action: directory ? "mkdir" : "write"
|
|
1476
1621
|
});
|
|
1477
|
-
|
|
1622
|
+
assertResolvedFsSandboxAccess(resolveFsSandboxPolicy(execServer, record), [{
|
|
1478
1623
|
path: filePath,
|
|
1479
1624
|
access: "write"
|
|
1480
1625
|
}, ...canonicalDestination ? [{
|
|
1481
1626
|
path: canonicalDestination.policyPath,
|
|
1482
1627
|
access: "write"
|
|
1483
1628
|
}] : []]);
|
|
1484
|
-
if (
|
|
1485
|
-
|
|
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({
|
|
1486
1633
|
filePath,
|
|
1487
|
-
data: Buffer.from(requireBase64String(record.dataBase64, "dataBase64"), "base64"),
|
|
1488
|
-
mkdir: false,
|
|
1489
1634
|
pinnedPath: canonicalDestination?.pinnedPath
|
|
1490
1635
|
});
|
|
1491
|
-
|
|
1492
|
-
async function createDirectory(execServer, params) {
|
|
1493
|
-
const record = requireObject(params, "fs/createDirectory params");
|
|
1494
|
-
const filePath = resolveExecServerPath(requireString(record.path, "path"), "create-directory path");
|
|
1495
|
-
const fsBridge = execServer.fsBridge;
|
|
1496
|
-
const canonicalDestination = await fsBridge.resolvePinnedMutationTarget?.({
|
|
1497
|
-
filePath,
|
|
1498
|
-
action: "mkdir"
|
|
1499
|
-
});
|
|
1500
|
-
assertFsSandboxAccess(execServer, record, [{
|
|
1501
|
-
path: filePath,
|
|
1502
|
-
access: "write"
|
|
1503
|
-
}, ...canonicalDestination ? [{
|
|
1504
|
-
path: canonicalDestination.policyPath,
|
|
1505
|
-
access: "write"
|
|
1506
|
-
}] : []]);
|
|
1507
|
-
if (record.recursive === false) {
|
|
1508
|
-
const parentPath = posix.dirname(filePath);
|
|
1509
|
-
if ((await fsBridge.stat({ filePath: parentPath }))?.type !== "directory") throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "parent directory not found");
|
|
1510
|
-
}
|
|
1511
|
-
await fsBridge.mkdirp({
|
|
1636
|
+
else await fsBridge.writeFile({
|
|
1512
1637
|
filePath,
|
|
1638
|
+
data: decodeFileData(record.dataBase64),
|
|
1639
|
+
mkdir: false,
|
|
1513
1640
|
pinnedPath: canonicalDestination?.pinnedPath
|
|
1514
1641
|
});
|
|
1515
1642
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
const fsSandboxPolicy = resolveFsSandboxPolicy(execServer, record);
|
|
1520
|
-
assertResolvedFsSandboxAccess(fsSandboxPolicy, [{
|
|
1521
|
-
path: filePath,
|
|
1522
|
-
access: "read"
|
|
1523
|
-
}]);
|
|
1524
|
-
const readPolicy = await authorizePhysicalReadPath(execServer.fsBridge, fsSandboxPolicy, filePath);
|
|
1525
|
-
const stat = await execServer.fsBridge.stat({
|
|
1526
|
-
filePath,
|
|
1527
|
-
...readPolicy
|
|
1528
|
-
});
|
|
1529
|
-
if (!stat) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "file not found");
|
|
1530
|
-
return {
|
|
1531
|
-
isDirectory: stat.type === "directory",
|
|
1532
|
-
isFile: stat.type === "file",
|
|
1533
|
-
isSymlink: false,
|
|
1534
|
-
size: stat.size,
|
|
1535
|
-
createdAtMs: 0,
|
|
1536
|
-
modifiedAtMs: stat.mtimeMs ?? 0
|
|
1537
|
-
};
|
|
1643
|
+
function decodeFileData(value) {
|
|
1644
|
+
if (typeof value !== "string") throw new Error("dataBase64 must be a string.");
|
|
1645
|
+
return Buffer.from(value, "base64");
|
|
1538
1646
|
}
|
|
1539
1647
|
async function readDirectory(execServer, params) {
|
|
1540
1648
|
const record = requireObject(params, "fs/readDirectory params");
|
|
@@ -1775,7 +1883,7 @@ async function spawnSandboxChild(params) {
|
|
|
1775
1883
|
escalation = setTimeout(() => signalLocal("SIGKILL"), SANDBOX_CHILD_TERM_GRACE_MS);
|
|
1776
1884
|
escalation.unref?.();
|
|
1777
1885
|
}
|
|
1778
|
-
const reaped = await
|
|
1886
|
+
const reaped = await raceWithTimeout(closed.promise.then(() => true), SANDBOX_CHILD_REAP_TIMEOUT_MS, () => false, { ref: false }).finally(() => clearTimeout(escalation));
|
|
1779
1887
|
await Promise.all(localSignals);
|
|
1780
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.`);
|
|
1781
1889
|
}
|
|
@@ -1785,7 +1893,8 @@ async function spawnSandboxChild(params) {
|
|
|
1785
1893
|
})()
|
|
1786
1894
|
};
|
|
1787
1895
|
params.owners.add(owner);
|
|
1788
|
-
|
|
1896
|
+
const releaseOwner = () => params.owners.delete(owner);
|
|
1897
|
+
settled.then(releaseOwner, releaseOwner);
|
|
1789
1898
|
const interrupt = async () => {
|
|
1790
1899
|
await ready.promise;
|
|
1791
1900
|
const interruptRemote = params.interruptRemote;
|
|
@@ -1798,7 +1907,7 @@ async function spawnSandboxChild(params) {
|
|
|
1798
1907
|
if (remainingMs <= 0) throw new Error("Sandbox process interrupt timed out waiting for remote process admission");
|
|
1799
1908
|
assertCurrent();
|
|
1800
1909
|
if (await interruptRemote(remainingMs)) return;
|
|
1801
|
-
await
|
|
1910
|
+
await raceWithTimeout(exited.promise, Math.min(SANDBOX_CHILD_INTERRUPT_POLL_MS, Math.max(0, deadline - performance.now())), () => void 0, { ref: false });
|
|
1802
1911
|
}
|
|
1803
1912
|
})();
|
|
1804
1913
|
interruptions.add(interruption);
|
|
@@ -1865,11 +1974,6 @@ async function spawnSandboxChild(params) {
|
|
|
1865
1974
|
ready.resolve();
|
|
1866
1975
|
}
|
|
1867
1976
|
}
|
|
1868
|
-
function delay(ms) {
|
|
1869
|
-
return new Promise((resolve) => {
|
|
1870
|
-
setTimeout(resolve, ms).unref?.();
|
|
1871
|
-
});
|
|
1872
|
-
}
|
|
1873
1977
|
//#endregion
|
|
1874
1978
|
//#region extensions/codex/src/app-server/sandbox-exec-server/http.ts
|
|
1875
1979
|
const SANDBOX_HTTP_STREAM_LINE_MAX_CHARS = 262144;
|
|
@@ -1953,11 +2057,12 @@ async function runSandboxHttpRequest(execServer, notifications, requestId, param
|
|
|
1953
2057
|
}
|
|
1954
2058
|
return termination;
|
|
1955
2059
|
};
|
|
2060
|
+
const cleanup = () => terminate().catch((error) => {
|
|
2061
|
+
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2062
|
+
});
|
|
1956
2063
|
const abortOnSessionClose = () => {
|
|
1957
2064
|
lifecycle.failed = true;
|
|
1958
|
-
|
|
1959
|
-
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
1960
|
-
});
|
|
2065
|
+
cleanup();
|
|
1961
2066
|
};
|
|
1962
2067
|
notifications.signal.addEventListener("abort", abortOnSessionClose, { once: true });
|
|
1963
2068
|
child.once("close", () => {
|
|
@@ -1967,35 +2072,8 @@ async function runSandboxHttpRequest(execServer, notifications, requestId, param
|
|
|
1967
2072
|
if (error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED") return;
|
|
1968
2073
|
embeddedAgentLog.warn("codex sandbox http/request stdin write failed", { error });
|
|
1969
2074
|
});
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
lifecycle,
|
|
1973
|
-
terminate,
|
|
1974
|
-
requestId,
|
|
1975
|
-
notifications,
|
|
1976
|
-
streamResponse: params.streamResponse
|
|
1977
|
-
}).then(response.resolve, response.reject);
|
|
1978
|
-
try {
|
|
1979
|
-
if (notifications.signal.aborted) abortOnSessionClose();
|
|
1980
|
-
else {
|
|
1981
|
-
owner.assertCurrent();
|
|
1982
|
-
child.stdin.end(JSON.stringify(params));
|
|
1983
|
-
}
|
|
1984
|
-
await completion.promise;
|
|
1985
|
-
await termination;
|
|
1986
|
-
} catch (error) {
|
|
1987
|
-
lifecycle.failed = true;
|
|
1988
|
-
response.reject(error);
|
|
1989
|
-
await terminate().catch((cleanupError) => {
|
|
1990
|
-
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error: cleanupError });
|
|
1991
|
-
});
|
|
1992
|
-
throw error;
|
|
1993
|
-
} finally {
|
|
1994
|
-
notifications.signal.removeEventListener("abort", abortOnSessionClose);
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
function readSandboxHttpResponse(params) {
|
|
1998
|
-
return new Promise((resolve, reject) => {
|
|
2075
|
+
const { streamResponse } = params;
|
|
2076
|
+
new Promise((resolve, reject) => {
|
|
1999
2077
|
let headerResolved = false;
|
|
2000
2078
|
let failed = false;
|
|
2001
2079
|
let childFailure = null;
|
|
@@ -2015,13 +2093,11 @@ function readSandboxHttpResponse(params) {
|
|
|
2015
2093
|
const fail = (message) => {
|
|
2016
2094
|
if (failed) return;
|
|
2017
2095
|
failed = true;
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
|
|
2021
|
-
});
|
|
2096
|
+
lifecycle.failed = true;
|
|
2097
|
+
cleanup();
|
|
2022
2098
|
if (headerResolved) {
|
|
2023
|
-
|
|
2024
|
-
requestId
|
|
2099
|
+
notifications.send("http/request/bodyDelta", {
|
|
2100
|
+
requestId,
|
|
2025
2101
|
seq: lastBodySeq + 1,
|
|
2026
2102
|
deltaBase64: "",
|
|
2027
2103
|
done: true,
|
|
@@ -2041,64 +2117,58 @@ function readSandboxHttpResponse(params) {
|
|
|
2041
2117
|
}
|
|
2042
2118
|
output.chunks.push(buffer);
|
|
2043
2119
|
};
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
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
|
-
|
|
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));
|
|
2080
2161
|
}
|
|
2081
|
-
|
|
2082
|
-
fail(error instanceof Error ? error.message : String(error));
|
|
2162
|
+
newline = stdoutBuffer.indexOf("\n");
|
|
2083
2163
|
}
|
|
2084
|
-
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
params.child.stderr.on("data", (chunk) => {
|
|
2089
|
-
if (failed) return;
|
|
2090
|
-
if (!params.streamResponse) {
|
|
2091
|
-
bufferOutput("stderr", chunk);
|
|
2092
|
-
return;
|
|
2093
|
-
}
|
|
2094
|
-
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
2095
|
-
stderr = sliceUtf16Safe(`${stderr}${text}`, -4096);
|
|
2096
|
-
});
|
|
2097
|
-
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) => {
|
|
2098
2168
|
childFailure ??= error.message;
|
|
2099
|
-
|
|
2169
|
+
lifecycle.failed = true;
|
|
2100
2170
|
});
|
|
2101
|
-
|
|
2171
|
+
child.once("close", (code) => {
|
|
2102
2172
|
const exitCode = code ?? 1;
|
|
2103
2173
|
if (failed) return;
|
|
2104
2174
|
if (childFailure) {
|
|
@@ -2106,7 +2176,7 @@ function readSandboxHttpResponse(params) {
|
|
|
2106
2176
|
return;
|
|
2107
2177
|
}
|
|
2108
2178
|
if (exitCode === 0) {
|
|
2109
|
-
if (!
|
|
2179
|
+
if (!streamResponse) {
|
|
2110
2180
|
try {
|
|
2111
2181
|
const parsed = JSON.parse(Buffer.concat(buffered.stdout.chunks).toString("utf8"));
|
|
2112
2182
|
if (typeof parsed.status !== "number" || !Array.isArray(parsed.headers)) throw new Error("sandbox http/request returned an invalid response envelope");
|
|
@@ -2121,15 +2191,31 @@ function readSandboxHttpResponse(params) {
|
|
|
2121
2191
|
return;
|
|
2122
2192
|
}
|
|
2123
2193
|
if (!headerResolved) {
|
|
2124
|
-
|
|
2194
|
+
lifecycle.failed = true;
|
|
2125
2195
|
reject(/* @__PURE__ */ new Error("sandbox http/request exited before returning headers"));
|
|
2126
2196
|
}
|
|
2127
2197
|
return;
|
|
2128
2198
|
}
|
|
2129
|
-
if (!
|
|
2199
|
+
if (!streamResponse) stderr = Buffer.concat(buffered.stderr.chunks).toString("utf8");
|
|
2130
2200
|
fail(stderr.trim() || `sandbox http/request failed with code ${exitCode}`);
|
|
2131
2201
|
});
|
|
2132
|
-
});
|
|
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
|
+
}
|
|
2133
2219
|
}
|
|
2134
2220
|
const SANDBOX_HTTP_REQUEST_SCRIPT = String.raw`
|
|
2135
2221
|
tmp=$(mktemp "$TMPDIR/openclaw-http.XXXXXX.py" 2>/dev/null || mktemp "/tmp/openclaw-http.XXXXXX.py") || exit 1
|
|
@@ -2147,9 +2233,6 @@ import urllib.request
|
|
|
2147
2233
|
def emit(payload):
|
|
2148
2234
|
print(json.dumps(payload, separators=(",", ":")), flush=True)
|
|
2149
2235
|
|
|
2150
|
-
def response_headers(response):
|
|
2151
|
-
return [{"name": name, "value": value} for name, value in response.headers.items()]
|
|
2152
|
-
|
|
2153
2236
|
BLOCKED_HOSTNAMES = {
|
|
2154
2237
|
"localhost",
|
|
2155
2238
|
"localhost.localdomain",
|
|
@@ -2185,9 +2268,7 @@ def is_blocked_hostname(hostname):
|
|
|
2185
2268
|
normalized = normalize_hostname(hostname)
|
|
2186
2269
|
return (
|
|
2187
2270
|
normalized in BLOCKED_HOSTNAMES
|
|
2188
|
-
or normalized.endswith(".localhost")
|
|
2189
|
-
or normalized.endswith(".local")
|
|
2190
|
-
or normalized.endswith(".internal")
|
|
2271
|
+
or normalized.endswith((".localhost", ".local", ".internal"))
|
|
2191
2272
|
)
|
|
2192
2273
|
|
|
2193
2274
|
def is_blocked_ip(address):
|
|
@@ -2200,12 +2281,9 @@ def is_blocked_ip(address):
|
|
|
2200
2281
|
return True
|
|
2201
2282
|
if str(parsed).lower() in CLOUD_METADATA_IP_ADDRESSES:
|
|
2202
2283
|
return True
|
|
2203
|
-
if isinstance(parsed, ipaddress.IPv4Address)
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
else:
|
|
2207
|
-
if any(parsed in network for network in BLOCKED_IPV6_NETWORKS):
|
|
2208
|
-
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
|
|
2209
2287
|
return (
|
|
2210
2288
|
parsed.is_loopback
|
|
2211
2289
|
or parsed.is_private
|
|
@@ -2306,7 +2384,7 @@ def pinned_getaddrinfo(original_getaddrinfo):
|
|
|
2306
2384
|
return getaddrinfo
|
|
2307
2385
|
|
|
2308
2386
|
def handle_response(input_data, response):
|
|
2309
|
-
headers =
|
|
2387
|
+
headers = [{"name": name, "value": value} for name, value in response.headers.items()]
|
|
2310
2388
|
status = int(getattr(response, "status", getattr(response, "code", 0)))
|
|
2311
2389
|
if input_data.get("streamResponse"):
|
|
2312
2390
|
emit({"type": "headers", "status": status, "headers": headers})
|
|
@@ -2376,7 +2454,7 @@ async function startProcess(execServer, processes, notify, params, processAuthor
|
|
|
2376
2454
|
if (processes.has(processId)) throw new Error(`process already exists: ${processId}`);
|
|
2377
2455
|
const argv = requireStringArray(record.argv, "argv");
|
|
2378
2456
|
const cwd = resolveExecServerPath(requireString(record.cwd, "cwd"), "process cwd");
|
|
2379
|
-
|
|
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.");
|
|
2380
2458
|
assertSupportedProcessSandbox(execServer, record);
|
|
2381
2459
|
const env = readProcessEnv(record);
|
|
2382
2460
|
const managed = {
|
|
@@ -2441,7 +2519,7 @@ async function startProcess(execServer, processes, notify, params, processAuthor
|
|
|
2441
2519
|
notifyProcessWaiters(managed);
|
|
2442
2520
|
throw error;
|
|
2443
2521
|
} finally {
|
|
2444
|
-
|
|
2522
|
+
managed.startPromise = void 0;
|
|
2445
2523
|
}
|
|
2446
2524
|
return {
|
|
2447
2525
|
processId,
|
|
@@ -2453,8 +2531,7 @@ function assertSupportedProcessSandbox(execServer, record) {
|
|
|
2453
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.");
|
|
2454
2532
|
if (resolveFsSandboxPolicy(execServer, record)?.unrestricted === false) throw new Error("Codex process filesystem sandbox restrictions cannot be enforced by the backend.");
|
|
2455
2533
|
if (record.sandbox === void 0 || record.sandbox === null) return;
|
|
2456
|
-
if (requireObject(requireObject(record.sandbox, "process sandbox context").permissions, "process sandbox permissions").network
|
|
2457
|
-
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.");
|
|
2458
2535
|
}
|
|
2459
2536
|
async function runProcess(execServer, managed, params) {
|
|
2460
2537
|
const backend = execServer.backend;
|
|
@@ -2514,14 +2591,12 @@ async function runProcess(execServer, managed, params) {
|
|
|
2514
2591
|
const child = owner.process;
|
|
2515
2592
|
child.stdout.on("data", (chunk) => appendProcessChunk(managed, "stdout", chunk));
|
|
2516
2593
|
child.stderr.on("data", (chunk) => appendProcessChunk(managed, "stderr", chunk));
|
|
2517
|
-
|
|
2518
|
-
managed.failure ??= error.message;
|
|
2519
|
-
notifyProcessWaiters(managed);
|
|
2520
|
-
});
|
|
2521
|
-
child.stdin.on("error", (error) => {
|
|
2594
|
+
const onError = (error) => {
|
|
2522
2595
|
managed.failure ??= error.message;
|
|
2523
2596
|
notifyProcessWaiters(managed);
|
|
2524
|
-
}
|
|
2597
|
+
};
|
|
2598
|
+
child.once("error", onError);
|
|
2599
|
+
child.stdin.on("error", onError);
|
|
2525
2600
|
owner.assertCurrent();
|
|
2526
2601
|
if (!managed.tty && !managed.pipeStdin) child.stdin.end();
|
|
2527
2602
|
}
|
|
@@ -2580,26 +2655,25 @@ function emitProcessClosed(managed, exitCode) {
|
|
|
2580
2655
|
managed.evictProcess();
|
|
2581
2656
|
notifyProcessWaiters(managed);
|
|
2582
2657
|
}
|
|
2583
|
-
function limitProcessChunks(chunks, maxBytes) {
|
|
2584
|
-
if (!maxBytes) return chunks;
|
|
2585
|
-
const retained = [];
|
|
2586
|
-
let retainedBytes = 0;
|
|
2587
|
-
for (const chunk of chunks) {
|
|
2588
|
-
const byteLength = Buffer.from(chunk.chunk, "base64").byteLength;
|
|
2589
|
-
if (retained.length > 0 && retainedBytes + byteLength > maxBytes) break;
|
|
2590
|
-
retained.push(chunk);
|
|
2591
|
-
retainedBytes += byteLength;
|
|
2592
|
-
if (retainedBytes >= maxBytes) break;
|
|
2593
|
-
}
|
|
2594
|
-
return retained;
|
|
2595
|
-
}
|
|
2596
2658
|
async function readProcess(processes, params) {
|
|
2597
2659
|
const record = requireObject(params, "process/read params");
|
|
2598
|
-
const
|
|
2660
|
+
const processId = requireString(record.processId, "processId");
|
|
2661
|
+
const managed = processes.get(processId);
|
|
2662
|
+
if (!managed) throw new Error(`unknown process: ${processId}`);
|
|
2599
2663
|
const afterSeq = typeof record.afterSeq === "number" ? record.afterSeq : 0;
|
|
2600
2664
|
const waitMs = typeof record.waitMs === "number" && record.waitMs > 0 ? record.waitMs : 0;
|
|
2601
2665
|
if (!managed.closed && managed.nextSeq - 1 <= afterSeq && waitMs > 0) await waitForProcessUpdate(managed, waitMs);
|
|
2602
|
-
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
|
+
}
|
|
2603
2677
|
const lastChunk = chunks.at(-1);
|
|
2604
2678
|
return {
|
|
2605
2679
|
chunks,
|
|
@@ -2643,8 +2717,7 @@ async function signalProcess(processes, params) {
|
|
|
2643
2717
|
async function terminateProcess(processes, params) {
|
|
2644
2718
|
const processId = requireString(requireObject(params, "process/terminate params").processId, "processId");
|
|
2645
2719
|
const managed = processes.get(processId);
|
|
2646
|
-
|
|
2647
|
-
return await terminateManagedProcess(managed);
|
|
2720
|
+
return managed ? await terminateManagedProcess(managed) : { running: false };
|
|
2648
2721
|
}
|
|
2649
2722
|
async function terminateManagedProcess(managed) {
|
|
2650
2723
|
const running = !managed.exited;
|
|
@@ -2670,16 +2743,6 @@ function notifyProcessWaiters(managed) {
|
|
|
2670
2743
|
managed.waiters = [];
|
|
2671
2744
|
for (const waiter of waiters) waiter();
|
|
2672
2745
|
}
|
|
2673
|
-
function requireProcess(processes, processId) {
|
|
2674
|
-
const managed = processes.get(processId);
|
|
2675
|
-
if (!managed) throw new Error(`unknown process: ${processId}`);
|
|
2676
|
-
return managed;
|
|
2677
|
-
}
|
|
2678
|
-
function rejectUnsupportedArg0(value) {
|
|
2679
|
-
if (value === void 0 || value === null) return;
|
|
2680
|
-
if (typeof value === "string") throw new Error("Codex sandbox exec-server does not support arg0 overrides.");
|
|
2681
|
-
throw new Error("arg0 must be a string or null.");
|
|
2682
|
-
}
|
|
2683
2746
|
function readEnv(value) {
|
|
2684
2747
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2685
2748
|
const env = {};
|
|
@@ -2687,22 +2750,18 @@ function readEnv(value) {
|
|
|
2687
2750
|
return env;
|
|
2688
2751
|
}
|
|
2689
2752
|
function readProcessEnv(record) {
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
return sanitizeEnvVars(requestedEnv).allowed;
|
|
2695
|
-
}
|
|
2696
|
-
function buildEnvFromPolicy(value) {
|
|
2697
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2698
|
-
const policy = value;
|
|
2699
|
-
const inheritedEnv = readEnv(policy.set);
|
|
2700
|
-
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);
|
|
2701
2757
|
if (includeOnly.length > 0) {
|
|
2702
2758
|
const regexes = includeOnly.map(wildcardPatternToRegex);
|
|
2703
2759
|
for (const key of Object.keys(inheritedEnv)) if (!regexes.some((regex) => regex.test(key))) delete inheritedEnv[key];
|
|
2704
2760
|
}
|
|
2705
|
-
return
|
|
2761
|
+
return sanitizeEnvVars({
|
|
2762
|
+
...inheritedEnv,
|
|
2763
|
+
...readEnv(record.env)
|
|
2764
|
+
}).allowed;
|
|
2706
2765
|
}
|
|
2707
2766
|
function wildcardPatternToRegex(pattern) {
|
|
2708
2767
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
@@ -2780,14 +2839,12 @@ var CodexSandboxExecSession = class {
|
|
|
2780
2839
|
case "fs/open": return await openFile(this.execServer, this.fileReads, params);
|
|
2781
2840
|
case "fs/readBlock": return readFileBlock(this.fileReads, params);
|
|
2782
2841
|
case "fs/close": return closeFile(this.fileReads, params);
|
|
2783
|
-
case "fs/readFile":
|
|
2842
|
+
case "fs/readFile":
|
|
2843
|
+
case "fs/getMetadata": return await readFileOrMetadata(this.execServer, params, method);
|
|
2784
2844
|
case "fs/writeFile":
|
|
2785
|
-
await writeFile(this.execServer, params);
|
|
2786
|
-
return {};
|
|
2787
2845
|
case "fs/createDirectory":
|
|
2788
|
-
await
|
|
2846
|
+
await writeFileOrDirectory(this.execServer, params, method);
|
|
2789
2847
|
return {};
|
|
2790
|
-
case "fs/getMetadata": return await getMetadata(this.execServer, params);
|
|
2791
2848
|
case "fs/readDirectory": return await readDirectory(this.execServer, params);
|
|
2792
2849
|
case "fs/remove":
|
|
2793
2850
|
await removePath(this.execServer, params);
|
|
@@ -2817,10 +2874,8 @@ async function ensureCodexSandboxExecServerEnvironment(params) {
|
|
|
2817
2874
|
if (placementNodeId && !params.runtime) throw new Error("Codex node execution requires its active plugin runtime.");
|
|
2818
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.");
|
|
2819
2876
|
const { server: execServer, nodeLease } = await acquireOpenClawExecServer({
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
signal: params.signal,
|
|
2823
|
-
onExecutionDisconnect: params.onExecutionDisconnect
|
|
2877
|
+
...params,
|
|
2878
|
+
sandbox: params.sandbox
|
|
2824
2879
|
});
|
|
2825
2880
|
const processAuthority = params.requireProcessAuthority && !("node" in execServer) ? getCodexNativeProcessClient(params.client) : void 0;
|
|
2826
2881
|
if (processAuthority && !("node" in execServer)) {
|
|
@@ -2877,7 +2932,16 @@ async function acquireOpenClawExecServer(params) {
|
|
|
2877
2932
|
const { sandbox, runtime, signal, onExecutionDisconnect } = params;
|
|
2878
2933
|
const key = sandbox.runtimeId;
|
|
2879
2934
|
while (true) {
|
|
2880
|
-
|
|
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
|
+
}
|
|
2881
2945
|
const server = await promise;
|
|
2882
2946
|
if (!server.closed && sandboxExecServerRegistry.servers.get(key) === promise) {
|
|
2883
2947
|
server.refCount += 1;
|
|
@@ -2930,15 +2994,6 @@ async function acquireOpenClawExecServer(params) {
|
|
|
2930
2994
|
}
|
|
2931
2995
|
}
|
|
2932
2996
|
}
|
|
2933
|
-
function startAndRememberOpenClawExecServer(sandbox) {
|
|
2934
|
-
const created = startOpenClawExecServer(sandbox);
|
|
2935
|
-
const key = sandbox.runtimeId;
|
|
2936
|
-
sandboxExecServerRegistry.servers.set(key, created);
|
|
2937
|
-
created.catch(() => {
|
|
2938
|
-
if (sandboxExecServerRegistry.servers.get(key) === created) sandboxExecServerRegistry.servers.delete(key);
|
|
2939
|
-
});
|
|
2940
|
-
return created;
|
|
2941
|
-
}
|
|
2942
2997
|
async function startOpenClawExecServer(sandbox) {
|
|
2943
2998
|
const backend = sandbox.backend;
|
|
2944
2999
|
const fsBridge = sandbox.fsBridge;
|
|
@@ -2965,7 +3020,7 @@ async function startOpenClawExecServer(sandbox) {
|
|
|
2965
3020
|
await once(server, "listening");
|
|
2966
3021
|
const address = server.address();
|
|
2967
3022
|
if (!address || typeof address === "string") throw new Error("OpenClaw Codex exec-server did not bind to a TCP port.");
|
|
2968
|
-
const environmentId =
|
|
3023
|
+
const environmentId = `openclaw-sandbox-${createHash("sha256").update(sandbox.runtimeId).digest("hex").slice(0, 16)}`;
|
|
2969
3024
|
const authPath = `/openclaw-${randomUUID()}`;
|
|
2970
3025
|
const common = {
|
|
2971
3026
|
authPath,
|
|
@@ -2991,7 +3046,9 @@ async function startOpenClawExecServer(sandbox) {
|
|
|
2991
3046
|
networkIsolated: (connection.backend.id === "docker" || connection.backend.id === "podman") && sandbox.docker.network.trim().toLowerCase() === "none"
|
|
2992
3047
|
};
|
|
2993
3048
|
server.on("connection", (socket, request) => {
|
|
2994
|
-
socket.on("error",
|
|
3049
|
+
socket.on("error", (error) => {
|
|
3050
|
+
embeddedAgentLog.debug("codex sandbox exec-server websocket failed", { error });
|
|
3051
|
+
});
|
|
2995
3052
|
const requestUrl = new URL(request.url ?? "", "ws://127.0.0.1");
|
|
2996
3053
|
if (requestUrl.pathname !== execServer.authPath && ("node" in execServer || !execServer.processAuthorities?.has(requestUrl.pathname))) {
|
|
2997
3054
|
socket.close(1008, "unauthorized");
|
|
@@ -3019,15 +3076,12 @@ async function releaseOpenClawExecServer(execServer) {
|
|
|
3019
3076
|
if (current === execServer) sandboxExecServerRegistry.servers.delete(execServer.sandbox.runtimeId);
|
|
3020
3077
|
await sandboxExecServerRegistry.close(execServer);
|
|
3021
3078
|
}
|
|
3022
|
-
function buildEnvironmentId(sandbox) {
|
|
3023
|
-
return `openclaw-sandbox-${createHash("sha256").update(sandbox.runtimeId).digest("hex").slice(0, 16)}`;
|
|
3024
|
-
}
|
|
3025
3079
|
function readCodexPlacementNodeId(sandbox) {
|
|
3026
3080
|
if (!("placementExecutionMode" in sandbox) || sandbox.placementExecutionMode !== "remote-exec" || !("placementNodeId" in sandbox) || typeof sandbox.placementNodeId !== "string" || !sandbox.placementNodeId) return;
|
|
3027
3081
|
return sandbox.placementNodeId;
|
|
3028
3082
|
}
|
|
3029
3083
|
function readCodexPlacementWorkspaceIdentity(sandbox) {
|
|
3030
|
-
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.");
|
|
3031
3085
|
return {
|
|
3032
3086
|
environmentId: sandbox.placementEnvironmentId,
|
|
3033
3087
|
sessionId: sandbox.placementSessionId,
|
|
@@ -3035,6 +3089,9 @@ function readCodexPlacementWorkspaceIdentity(sandbox) {
|
|
|
3035
3089
|
sessionKey: sandbox.sessionKey
|
|
3036
3090
|
};
|
|
3037
3091
|
}
|
|
3092
|
+
function isWorkspaceIdentityString(value) {
|
|
3093
|
+
return typeof value === "string" && value.length > 0 && value.trim() === value;
|
|
3094
|
+
}
|
|
3038
3095
|
function handleNodeConnection(execServer, socket, requestUrl) {
|
|
3039
3096
|
const leaseId = requestUrl.searchParams.get("lease");
|
|
3040
3097
|
const lease = leaseId ? execServer.node.leases.get(leaseId) : void 0;
|
|
@@ -3089,9 +3146,6 @@ function trackExecServerCleanup(execServer, cleanup, failureMessage) {
|
|
|
3089
3146
|
embeddedAgentLog.warn(failureMessage, { error: error instanceof Error ? error.message : String(error) });
|
|
3090
3147
|
});
|
|
3091
3148
|
}
|
|
3092
|
-
function handleExecServerSocketError(error) {
|
|
3093
|
-
embeddedAgentLog.debug("codex sandbox exec-server websocket failed", { error });
|
|
3094
|
-
}
|
|
3095
3149
|
async function handleMessage(session, data) {
|
|
3096
3150
|
await session.handleRequest(parseRequest(codexWebSocketDataToBuffer(data).toString("utf8")));
|
|
3097
3151
|
}
|
|
@@ -3189,10 +3243,19 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3189
3243
|
}
|
|
3190
3244
|
});
|
|
3191
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
|
+
};
|
|
3192
3255
|
if (params.signal?.aborted) {
|
|
3193
3256
|
if (params.signal.reason instanceof CodexServerRequestResolvedError) return;
|
|
3194
3257
|
recordNativeToolFailureDisposition(params, context, "cancelled");
|
|
3195
|
-
return buildApprovalResponse(params.method,
|
|
3258
|
+
return buildApprovalResponse(params.method, requestParams, "cancelled");
|
|
3196
3259
|
}
|
|
3197
3260
|
let revalidateMutableFileApproval;
|
|
3198
3261
|
let mutableFileApprovalRequiresOneShot = false;
|
|
@@ -3212,7 +3275,7 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3212
3275
|
resolvedMessage = "Codex app-server approval granted for this byte-bound command only.";
|
|
3213
3276
|
}
|
|
3214
3277
|
if (params.method === "item/commandExecution/requestApproval" && resolvedOutcome !== "denied") {
|
|
3215
|
-
const resolution = resolveCommandApproval(
|
|
3278
|
+
const resolution = resolveCommandApproval(requestParams, resolvedOutcome);
|
|
3216
3279
|
if (resolution.scope === "denied") {
|
|
3217
3280
|
resolvedOutcome = "denied";
|
|
3218
3281
|
resolvedMessage = "Codex app-server request does not offer the approved scope.";
|
|
@@ -3223,17 +3286,14 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3223
3286
|
}
|
|
3224
3287
|
params.signal?.throwIfAborted();
|
|
3225
3288
|
if (resolvedOutcome !== "denied") params.paramsForRun.hostCapabilities.assertActive();
|
|
3226
|
-
|
|
3227
|
-
phase: "resolved",
|
|
3289
|
+
return finishApproval(resolvedOutcome, {
|
|
3228
3290
|
status: resolvedOutcome === "denied" ? "denied" : "approved",
|
|
3229
3291
|
...resolvedApprovalId ? {
|
|
3230
3292
|
approvalId: resolvedApprovalId,
|
|
3231
3293
|
approvalSlug: resolvedApprovalId
|
|
3232
3294
|
} : {},
|
|
3233
|
-
...approvalEventScope(params.method, resolvedOutcome),
|
|
3234
3295
|
message: resolvedMessage
|
|
3235
3296
|
});
|
|
3236
|
-
return buildApprovalResponse(params.method, context.requestParams, resolvedOutcome);
|
|
3237
3297
|
};
|
|
3238
3298
|
try {
|
|
3239
3299
|
if (params.method === "item/commandExecution/requestApproval" && !readNetworkApprovalContext(requestParams)) {
|
|
@@ -3263,7 +3323,7 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3263
3323
|
recordNativeToolFailureDisposition(params, context, policyOutcome.failureDisposition);
|
|
3264
3324
|
return await resolvePolicyApproval("denied", policyOutcome.reason);
|
|
3265
3325
|
}
|
|
3266
|
-
if (policyOutcome?.outcome === "approved-once"
|
|
3326
|
+
if (policyOutcome?.outcome === "approved-once") return await resolvePolicyApproval(policyOutcome.outcome);
|
|
3267
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.");
|
|
3268
3328
|
const allowedDecisions = nativeApprovalAllowedDecisions({
|
|
3269
3329
|
method: params.method,
|
|
@@ -3285,13 +3345,10 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3285
3345
|
params.signal?.throwIfAborted();
|
|
3286
3346
|
if (!approvalId) {
|
|
3287
3347
|
recordNativeToolFailureDisposition(params, context, "failed");
|
|
3288
|
-
|
|
3289
|
-
phase: "resolved",
|
|
3348
|
+
return finishApproval("denied", {
|
|
3290
3349
|
status: "unavailable",
|
|
3291
|
-
...approvalEventScope(params.method, "denied"),
|
|
3292
3350
|
message: "Codex app-server approval route unavailable."
|
|
3293
3351
|
});
|
|
3294
|
-
return buildApprovalResponse(params.method, context.requestParams, "denied");
|
|
3295
3352
|
}
|
|
3296
3353
|
emitEvent({
|
|
3297
3354
|
phase: "requested",
|
|
@@ -3311,30 +3368,24 @@ async function handleCodexAppServerApprovalRequest(params) {
|
|
|
3311
3368
|
if (approvalTimedOut) recordNativeToolFailureDisposition(params, context, "timed_out", context.approvalKind);
|
|
3312
3369
|
else if (outcome === "unavailable") recordNativeToolFailureDisposition(params, context, "failed");
|
|
3313
3370
|
if (outcome === "approved-once" || outcome === "approved-session") return await resolvePolicyApproval(outcome, approvalResolutionMessage(outcome), approvalId);
|
|
3314
|
-
|
|
3315
|
-
phase: "resolved",
|
|
3371
|
+
return finishApproval(outcome, {
|
|
3316
3372
|
status: outcome,
|
|
3317
3373
|
approvalId,
|
|
3318
3374
|
approvalSlug: approvalId,
|
|
3319
|
-
...approvalEventScope(params.method, outcome),
|
|
3320
3375
|
message: approvalTimedOut ? codexApprovalTimeoutText(context.approvalKind) : approvalResolutionMessage(outcome)
|
|
3321
3376
|
});
|
|
3322
|
-
return buildApprovalResponse(params.method, context.requestParams, outcome);
|
|
3323
3377
|
} catch (error) {
|
|
3324
3378
|
if (params.signal?.reason instanceof CodexServerRequestResolvedError) return;
|
|
3325
3379
|
const cancelled = params.signal?.aborted === true;
|
|
3326
3380
|
recordNativeToolFailureDisposition(params, context, cancelled && params.signal ? resolveCodexToolAbortTerminalReason(params.signal) : "failed");
|
|
3327
|
-
|
|
3328
|
-
phase: "resolved",
|
|
3381
|
+
return finishApproval(cancelled ? "cancelled" : "denied", {
|
|
3329
3382
|
status: cancelled ? "failed" : "unavailable",
|
|
3330
3383
|
...approvalId ? {
|
|
3331
3384
|
approvalId,
|
|
3332
3385
|
approvalSlug: approvalId
|
|
3333
3386
|
} : {},
|
|
3334
|
-
...approvalEventScope(params.method, cancelled ? "cancelled" : "denied"),
|
|
3335
3387
|
message: cancelled ? "Codex app-server approval cancelled because the run stopped." : `Codex app-server approval route failed: ${formatCodexDisplayText(coerceErrorMessage(error))}`
|
|
3336
3388
|
});
|
|
3337
|
-
return buildApprovalResponse(params.method, context.requestParams, cancelled ? "cancelled" : "denied");
|
|
3338
3389
|
}
|
|
3339
3390
|
}
|
|
3340
3391
|
function recordNativeToolFailureDisposition(params, context, disposition, approvalKind) {
|
|
@@ -3346,17 +3397,17 @@ function recordNativeToolFailureDisposition(params, context, disposition, approv
|
|
|
3346
3397
|
}
|
|
3347
3398
|
function buildApprovalResponse(method, requestParams, outcome) {
|
|
3348
3399
|
if (method === "item/commandExecution/requestApproval") return { decision: resolveCommandApproval(requestParams, outcome).decision };
|
|
3349
|
-
if (method === "item/fileChange/requestApproval") return { decision:
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
}
|
|
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
|
+
};
|
|
3360
3411
|
return {
|
|
3361
3412
|
decision: "decline",
|
|
3362
3413
|
reason: "OpenClaw codex app-server bridge does not grant native approvals yet."
|
|
@@ -3366,7 +3417,7 @@ function buildApprovalContext(params) {
|
|
|
3366
3417
|
const itemId = readStringField(params.requestParams, "itemId") ?? readStringField(params.requestParams, "callId") ?? readStringField(params.requestParams, "approvalId");
|
|
3367
3418
|
const approvalId = readStringField(params.requestParams, "approvalId") ?? itemId;
|
|
3368
3419
|
const commandDetailLines = params.method === "item/commandExecution/requestApproval" ? describeCommandApprovalDetails(params.requestParams) : [];
|
|
3369
|
-
const commandPreview = sanitizeApprovalPreview(
|
|
3420
|
+
const commandPreview = sanitizeApprovalPreview(readJoinedPreview(readCommandActions(params.requestParams), " && ") ?? readCommandPreview(params.requestParams), commandDetailLines.length > 0 ? COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH : 180);
|
|
3370
3421
|
const reasonPreview = sanitizeApprovalPreview(readStringPreview(params.requestParams, "reason"), 180);
|
|
3371
3422
|
const command = commandPreview.text;
|
|
3372
3423
|
const reason = reasonPreview.text;
|
|
@@ -3374,7 +3425,8 @@ function buildApprovalContext(params) {
|
|
|
3374
3425
|
const approvalKind = params.method.includes("commandExecution") ? "command" : params.method.includes("fileChange") ? "file-change" : params.method.includes("permissions") ? "permissions" : "other";
|
|
3375
3426
|
const kind = approvalKind === "command" ? "exec" : approvalKind === "other" ? "unknown" : "plugin";
|
|
3376
3427
|
const permissionLines = params.method === "item/permissions/requestApproval" ? describePermissionProfile(requestedPermissions(params.requestParams), "Permissions") : [];
|
|
3377
|
-
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`;
|
|
3378
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}`);
|
|
3379
3431
|
return {
|
|
3380
3432
|
approvalKind,
|
|
@@ -3382,10 +3434,9 @@ function buildApprovalContext(params) {
|
|
|
3382
3434
|
title,
|
|
3383
3435
|
description: permissionLines.length > 0 ? joinDescriptionLinesWithinLimit(permissionLines, PERMISSION_DESCRIPTION_MAX_LENGTH) : [subject, ...commandDetailLines].join("\n"),
|
|
3384
3436
|
severity: kind === "exec" ? "warning" : "info",
|
|
3385
|
-
toolName:
|
|
3437
|
+
toolName: `codex_${approvalType ?? "file"}_approval`,
|
|
3386
3438
|
itemId,
|
|
3387
3439
|
approvalId,
|
|
3388
|
-
requestParams: params.requestParams,
|
|
3389
3440
|
eventDetails: {
|
|
3390
3441
|
...itemId ? { itemId } : {},
|
|
3391
3442
|
...command ? { command } : {},
|
|
@@ -3399,24 +3450,9 @@ async function runOpenClawToolPolicyForApprovalRequest(params) {
|
|
|
3399
3450
|
const policyRequest = buildOpenClawToolPolicyRequest(params.method, params.requestParams);
|
|
3400
3451
|
if (!policyRequest) return;
|
|
3401
3452
|
const cwd = readStringField(params.requestParams, "cwd") ?? params.paramsForRun.workspaceDir;
|
|
3402
|
-
const
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
context: params.context,
|
|
3406
|
-
policyRequest,
|
|
3407
|
-
nativeHookRelay: params.nativeHookRelay,
|
|
3408
|
-
autoApprove: params.autoApprove,
|
|
3409
|
-
assertActive: params.paramsForRun.hostCapabilities.assertActive,
|
|
3410
|
-
cwd,
|
|
3411
|
-
signal: params.signal
|
|
3412
|
-
});
|
|
3413
|
-
if (nativeRelayOutcome?.blocked) return {
|
|
3414
|
-
outcome: "denied",
|
|
3415
|
-
reason: nativeRelayOutcome.reason,
|
|
3416
|
-
...nativeRelayOutcome.failureDisposition ? { failureDisposition: nativeRelayOutcome.failureDisposition } : {}
|
|
3417
|
-
};
|
|
3418
|
-
if (nativeRelayOutcome?.outcome === "approved-once" || nativeRelayOutcome?.outcome === "approved-session") return { outcome: nativeRelayOutcome.outcome };
|
|
3419
|
-
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;
|
|
3420
3456
|
const outcome = await params.paramsForRun.hostCapabilities.runBeforeToolCall({
|
|
3421
3457
|
toolName: policyRequest.toolName,
|
|
3422
3458
|
params: policyRequest.params,
|
|
@@ -3435,105 +3471,86 @@ async function runOpenClawToolPolicyForApprovalRequest(params) {
|
|
|
3435
3471
|
};
|
|
3436
3472
|
if (outcome.approvalResolution) return { outcome: "approved-once" };
|
|
3437
3473
|
return { outcome: "allowed" };
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
if (approvalOutcome?.outcome === "denied") return {
|
|
3457
|
-
handled: true,
|
|
3458
|
-
blocked: true,
|
|
3459
|
-
reason: approvalOutcome.reason,
|
|
3460
|
-
...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
|
+
}
|
|
3461
3492
|
};
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
relayId: nativeHookRelay.relayId,
|
|
3476
|
-
generation: nativeHookRelay.generation,
|
|
3477
|
-
event: "pre_tool_use",
|
|
3478
|
-
rawPayload: payload,
|
|
3479
|
-
requireGeneration: true
|
|
3480
|
-
}));
|
|
3481
|
-
if (decision.blocked) return {
|
|
3482
|
-
handled: true,
|
|
3483
|
-
blocked: true,
|
|
3484
|
-
reason: decision.reason,
|
|
3485
|
-
...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" };
|
|
3486
3506
|
};
|
|
3487
|
-
|
|
3488
|
-
} catch (error) {
|
|
3489
|
-
if (params.autoApprove === true && !hasNativeHookRelayInvocation({
|
|
3507
|
+
if (hasNativeHookRelayInvocation({
|
|
3490
3508
|
relayId: nativeHookRelay.relayId,
|
|
3491
3509
|
event: "pre_tool_use",
|
|
3492
3510
|
toolUseId: params.context.approvalId
|
|
3493
|
-
})) return;
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
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
|
+
};
|
|
3517
3534
|
}
|
|
3518
|
-
}
|
|
3535
|
+
}
|
|
3519
3536
|
}
|
|
3520
|
-
function
|
|
3537
|
+
function readNativeRelayPreToolUseDenial(response) {
|
|
3521
3538
|
if (!response || response.exitCode !== 0) return {
|
|
3522
|
-
|
|
3539
|
+
outcome: "denied",
|
|
3523
3540
|
reason: sanitizeRelayDecisionReason(response?.stderr) || sanitizeRelayDecisionReason(response?.stdout) || "OpenClaw native hook relay failed for Codex app-server approval.",
|
|
3524
3541
|
failureDisposition: response?.failureDisposition ?? "failed"
|
|
3525
3542
|
};
|
|
3526
3543
|
const stdout = response.stdout?.trim();
|
|
3527
|
-
if (!stdout) return
|
|
3544
|
+
if (!stdout) return;
|
|
3528
3545
|
const parsed = safeParseJson(stdout);
|
|
3529
3546
|
const output = isJsonObject(parsed) && isJsonObject(parsed.hookSpecificOutput) ? parsed.hookSpecificOutput : void 0;
|
|
3530
3547
|
if (output?.permissionDecision === "deny") return {
|
|
3531
|
-
|
|
3548
|
+
outcome: "denied",
|
|
3532
3549
|
reason: readStringField(output, "permissionDecisionReason") || "OpenClaw native hook policy denied Codex app-server approval.",
|
|
3533
3550
|
...response.failureDisposition ? { failureDisposition: response.failureDisposition } : {}
|
|
3534
3551
|
};
|
|
3535
3552
|
return {
|
|
3536
|
-
|
|
3553
|
+
outcome: "denied",
|
|
3537
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.",
|
|
3538
3555
|
failureDisposition: "failed"
|
|
3539
3556
|
};
|
|
@@ -3597,34 +3614,22 @@ function nativeApprovalAllowedDecisions(params) {
|
|
|
3597
3614
|
];
|
|
3598
3615
|
return params.method === "item/commandExecution/requestApproval" ? commandApprovalAllowedDecisions(params.requestParams, params.requiresOneShot) : void 0;
|
|
3599
3616
|
}
|
|
3600
|
-
function fileChangeApprovalDecision(outcome) {
|
|
3601
|
-
if (outcome === "cancelled") return "cancel";
|
|
3602
|
-
if (outcome === "denied" || outcome === "unavailable") return "decline";
|
|
3603
|
-
return outcome === "approved-session" ? "acceptForSession" : "accept";
|
|
3604
|
-
}
|
|
3605
3617
|
function requestedPermissions(requestParams) {
|
|
3606
3618
|
const permissions = isJsonObject(requestParams?.permissions) ? requestParams.permissions : {};
|
|
3607
|
-
|
|
3608
|
-
if (isJsonObject(permissions.network)) granted.network = permissions.network;
|
|
3609
|
-
if (isJsonObject(permissions.fileSystem)) granted.fileSystem = permissions.fileSystem;
|
|
3610
|
-
return granted;
|
|
3619
|
+
return Object.fromEntries(["network", "fileSystem"].flatMap((key) => isJsonObject(permissions[key]) ? [[key, permissions[key]]] : []));
|
|
3611
3620
|
}
|
|
3612
3621
|
function describeCommandApprovalDetails(requestParams) {
|
|
3613
|
-
const lines = [];
|
|
3614
3622
|
const additionalPermissions = isJsonObject(requestParams?.additionalPermissions) ? requestParams.additionalPermissions : void 0;
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
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));
|
|
3621
3628
|
}
|
|
3622
3629
|
function describePermissionProfile(permissions, label) {
|
|
3623
3630
|
const lines = [];
|
|
3624
|
-
const kinds = [];
|
|
3625
3631
|
const risks = /* @__PURE__ */ new Set();
|
|
3626
|
-
|
|
3627
|
-
if (isJsonObject(permissions.fileSystem)) kinds.push("fileSystem");
|
|
3632
|
+
const kinds = ["network", "fileSystem"].filter((kind) => isJsonObject(permissions[kind]));
|
|
3628
3633
|
if (kinds.length > 0) lines.push(`${label}: ${kinds.join(", ")}`);
|
|
3629
3634
|
let networkSummary;
|
|
3630
3635
|
if (isJsonObject(permissions.network)) {
|
|
@@ -3646,7 +3651,10 @@ function describePermissionProfile(permissions, label) {
|
|
|
3646
3651
|
].map((key) => summarizePermissionArray(fileSystem[key], key, risks, {
|
|
3647
3652
|
sanitize: sanitizePermissionPathValue,
|
|
3648
3653
|
risksFor: permissionPathRisks
|
|
3649
|
-
})),
|
|
3654
|
+
})), summarizePermissionTargets(fileSystem.entries, {
|
|
3655
|
+
kind: "file-system",
|
|
3656
|
+
risks
|
|
3657
|
+
})].filter((summary) => Boolean(summary));
|
|
3650
3658
|
fileSystemSummary = summaries.length > 0 ? summaries.join("; ") : void 0;
|
|
3651
3659
|
}
|
|
3652
3660
|
if (risks.size > 0) lines.push(`High-risk targets: ${[...risks].join(", ")}`);
|
|
@@ -3660,21 +3668,24 @@ function summarizeNetworkEnabledPermission(permission, risks) {
|
|
|
3660
3668
|
if (enabled) risks.add("network access");
|
|
3661
3669
|
return `enabled: ${enabled}`;
|
|
3662
3670
|
}
|
|
3663
|
-
function
|
|
3664
|
-
const entries = permission.entries;
|
|
3671
|
+
function summarizePermissionTargets(entries, format) {
|
|
3665
3672
|
if (!Array.isArray(entries)) return;
|
|
3673
|
+
const fileSystem = format.kind === "file-system";
|
|
3674
|
+
const sanitizeTarget = fileSystem ? sanitizePermissionPathValue : sanitizePermissionHostValue;
|
|
3666
3675
|
const samples = [];
|
|
3667
3676
|
let count = 0;
|
|
3668
3677
|
for (const entry of entries) {
|
|
3669
3678
|
const item = isJsonObject(entry) ? entry : void 0;
|
|
3670
|
-
const
|
|
3671
|
-
const
|
|
3672
|
-
|
|
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;
|
|
3673
3684
|
count += 1;
|
|
3674
|
-
if (
|
|
3675
|
-
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)}`);
|
|
3676
3687
|
}
|
|
3677
|
-
return formatPermissionSamples("entries", samples, count);
|
|
3688
|
+
return formatPermissionSamples(fileSystem ? "entries" : "Proposed network policy", samples, count);
|
|
3678
3689
|
}
|
|
3679
3690
|
function summarizePermissionArray(input, label, risks, format) {
|
|
3680
3691
|
const values = normalizeTrimmedStringList(input);
|
|
@@ -3682,24 +3693,10 @@ function summarizePermissionArray(input, label, risks, format) {
|
|
|
3682
3693
|
for (const value of values) for (const risk of format.risksFor(value)) risks.add(risk);
|
|
3683
3694
|
return formatPermissionSamples(label, values.slice(0, PERMISSION_SAMPLE_LIMIT).map(format.sanitize).filter(Boolean), values.length);
|
|
3684
3695
|
}
|
|
3685
|
-
function
|
|
3696
|
+
function summarizeExecPolicyAmendment(value) {
|
|
3686
3697
|
if (!Array.isArray(value)) return;
|
|
3687
|
-
const values = value.filter((entry) => typeof entry === "string").map((entry) =>
|
|
3688
|
-
return formatPermissionSamples(
|
|
3689
|
-
}
|
|
3690
|
-
function summarizeNetworkPolicyAmendments(value) {
|
|
3691
|
-
if (!Array.isArray(value)) return;
|
|
3692
|
-
const samples = [];
|
|
3693
|
-
let count = 0;
|
|
3694
|
-
for (const entry of value) {
|
|
3695
|
-
const amendment = isJsonObject(entry) ? entry : void 0;
|
|
3696
|
-
const host = typeof amendment?.host === "string" ? amendment.host : "";
|
|
3697
|
-
const action = typeof amendment?.action === "string" ? amendment.action : "";
|
|
3698
|
-
if (!host || !action) continue;
|
|
3699
|
-
count += 1;
|
|
3700
|
-
if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizeCodexApprovalVisibleText(action)} ${sanitizePermissionHostValue(host)}`);
|
|
3701
|
-
}
|
|
3702
|
-
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);
|
|
3703
3700
|
}
|
|
3704
3701
|
function formatPermissionSamples(label, samples, count) {
|
|
3705
3702
|
if (count === 0) return;
|
|
@@ -3719,18 +3716,12 @@ function sanitizePermissionPathValue(value) {
|
|
|
3719
3716
|
}
|
|
3720
3717
|
function permissionHostRisks(value) {
|
|
3721
3718
|
const normalized = value.trim().toLowerCase();
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
risks.push("wildcard hosts");
|
|
3725
|
-
if (isPrivateNetworkHostPattern(normalized)) risks.push("private-network wildcards");
|
|
3726
|
-
}
|
|
3727
|
-
return risks;
|
|
3719
|
+
if (!normalized.includes("*")) return [];
|
|
3720
|
+
return isPrivateNetworkHostPattern(normalized) ? ["wildcard hosts", "private-network wildcards"] : ["wildcard hosts"];
|
|
3728
3721
|
}
|
|
3729
3722
|
function permissionPathRisks(value) {
|
|
3730
3723
|
const normalized = sanitizeCodexApprovalVisibleText(value);
|
|
3731
|
-
|
|
3732
|
-
if (normalized === "/" || normalized === "\\" || /^[A-Za-z]:[\\/]*$/.test(normalized)) risks.push("filesystem root");
|
|
3733
|
-
return risks;
|
|
3724
|
+
return normalized === "/" || normalized === "\\" || /^[A-Za-z]:[\\/]*$/.test(normalized) ? ["filesystem root"] : [];
|
|
3734
3725
|
}
|
|
3735
3726
|
function isPrivateNetworkHostPattern(value) {
|
|
3736
3727
|
const wildcardStripped = value.toLowerCase().replace(/^\*\./, "");
|
|
@@ -3742,8 +3733,12 @@ function isPrivateNetworkHostPattern(value) {
|
|
|
3742
3733
|
"corp",
|
|
3743
3734
|
"private"
|
|
3744
3735
|
].some((suffix) => wildcardStripped === suffix || wildcardStripped.endsWith(`.${suffix}`))) return true;
|
|
3745
|
-
|
|
3746
|
-
|
|
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);
|
|
3747
3742
|
}
|
|
3748
3743
|
function approvalResolutionMessage(outcome) {
|
|
3749
3744
|
return {
|
|
@@ -3754,15 +3749,11 @@ function approvalResolutionMessage(outcome) {
|
|
|
3754
3749
|
denied: "Codex app-server approval denied."
|
|
3755
3750
|
}[outcome];
|
|
3756
3751
|
}
|
|
3757
|
-
function approvalEventScope(method, outcome) {
|
|
3758
|
-
return method === "item/permissions/requestApproval" ? { scope: outcome === "approved-session" ? "session" : "turn" } : {};
|
|
3759
|
-
}
|
|
3760
3752
|
function readPolicyCommand(record) {
|
|
3761
3753
|
const command = record?.command;
|
|
3762
3754
|
if (typeof command === "string") return command;
|
|
3763
3755
|
if (Array.isArray(command) && command.every((part) => typeof part === "string")) return command.join(" ");
|
|
3764
|
-
|
|
3765
|
-
if (actionCommands.length > 0) return actionCommands.join(" && ");
|
|
3756
|
+
return readCommandActions(record).join(" && ") || void 0;
|
|
3766
3757
|
}
|
|
3767
3758
|
function readNetworkApprovalContext(record) {
|
|
3768
3759
|
const context = isJsonObject(record?.networkApprovalContext) ? record.networkApprovalContext : void 0;
|
|
@@ -3778,22 +3769,16 @@ function readCommandActions(record) {
|
|
|
3778
3769
|
if (!Array.isArray(actions)) return [];
|
|
3779
3770
|
return actions.map((action) => isJsonObject(action) ? readStringField(action, "command") : void 0).filter((command) => Boolean(command));
|
|
3780
3771
|
}
|
|
3781
|
-
function readCommandActionsPreview(record) {
|
|
3782
|
-
let source;
|
|
3783
|
-
for (const command of readCommandActions(record)) {
|
|
3784
|
-
source = appendPreviewPart(source, command, " && ");
|
|
3785
|
-
if (source.clipped) break;
|
|
3786
|
-
}
|
|
3787
|
-
return source;
|
|
3788
|
-
}
|
|
3789
3772
|
function readCommandPreview(record) {
|
|
3790
3773
|
const command = record?.command;
|
|
3791
3774
|
if (typeof command === "string") return previewSource(command);
|
|
3792
|
-
|
|
3775
|
+
return Array.isArray(command) ? readJoinedPreview(command, " ") : void 0;
|
|
3776
|
+
}
|
|
3777
|
+
function readJoinedPreview(parts, separator) {
|
|
3793
3778
|
let source;
|
|
3794
|
-
for (const part of
|
|
3779
|
+
for (const part of parts) {
|
|
3795
3780
|
if (typeof part !== "string") return;
|
|
3796
|
-
source =
|
|
3781
|
+
source = previewSource(`${source?.value ? `${source.value}${separator}` : ""}${part}`);
|
|
3797
3782
|
if (source.clipped) break;
|
|
3798
3783
|
}
|
|
3799
3784
|
return source;
|
|
@@ -3808,14 +3793,6 @@ function previewSource(value) {
|
|
|
3808
3793
|
clipped: value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH
|
|
3809
3794
|
};
|
|
3810
3795
|
}
|
|
3811
|
-
function appendPreviewPart(source, part, separator) {
|
|
3812
|
-
const value = `${source?.value ? `${source.value}${separator}` : ""}${part}`;
|
|
3813
|
-
const clipped = source?.clipped === true || value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH;
|
|
3814
|
-
return {
|
|
3815
|
-
value: sliceUtf16Safe(value, 0, APPROVAL_PREVIEW_SCAN_MAX_LENGTH),
|
|
3816
|
-
clipped
|
|
3817
|
-
};
|
|
3818
|
-
}
|
|
3819
3796
|
function sanitizeApprovalPreview(source, maxLength) {
|
|
3820
3797
|
if (!source || !source.value) return { omitted: false };
|
|
3821
3798
|
const rawPreview = stripDanglingCodexApprovalTerminalSequence(source.value);
|
|
@@ -3857,45 +3834,49 @@ function diagnosticToolIdentity(params) {
|
|
|
3857
3834
|
toolCallId: params.call.callId
|
|
3858
3835
|
};
|
|
3859
3836
|
}
|
|
3860
|
-
function
|
|
3861
|
-
|
|
3862
|
-
type
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
...diagnosticToolIdentity(params),
|
|
3870
|
-
durationMs: params.durationMs,
|
|
3871
|
-
errorCategory: "codex_dynamic_tool_error",
|
|
3872
|
-
terminalReason: params.terminalReason ?? "failed"
|
|
3873
|
-
});
|
|
3874
|
-
}
|
|
3875
|
-
/** Emits the terminal event matching a dynamic tool response's diagnostic type. */
|
|
3876
|
-
function emitDynamicToolTerminalDiagnostic(params) {
|
|
3877
|
-
const terminalType = params.response.diagnosticTerminalType ?? (params.response.success ? "completed" : "error");
|
|
3878
|
-
if (terminalType === "completed") {
|
|
3879
|
-
emitTrustedDiagnosticEvent({
|
|
3880
|
-
type: "tool.execution.completed",
|
|
3881
|
-
...diagnosticToolIdentity(params),
|
|
3882
|
-
durationMs: params.durationMs
|
|
3883
|
-
});
|
|
3884
|
-
return;
|
|
3885
|
-
}
|
|
3886
|
-
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") => {
|
|
3887
3846
|
emitTrustedDiagnosticEvent({
|
|
3888
|
-
type: "tool.execution.
|
|
3847
|
+
type: "tool.execution.error",
|
|
3889
3848
|
...diagnosticToolIdentity(params),
|
|
3890
|
-
|
|
3891
|
-
|
|
3849
|
+
durationMs,
|
|
3850
|
+
errorCategory: "codex_dynamic_tool_error",
|
|
3851
|
+
terminalReason
|
|
3892
3852
|
});
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
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
|
+
};
|
|
3899
3880
|
}
|
|
3900
3881
|
//#endregion
|
|
3901
3882
|
//#region extensions/codex/src/app-server/elicitation-bridge.ts
|
|
@@ -4043,7 +4024,8 @@ function resolvePluginElicitation(params) {
|
|
|
4043
4024
|
const context = params.pluginAppPolicyContext;
|
|
4044
4025
|
const entries = context ? Object.values(context.apps) : [];
|
|
4045
4026
|
const pluginEntries = entries.filter(isPluginAppPolicyContextEntry);
|
|
4046
|
-
const
|
|
4027
|
+
const readIdentity = (keys) => readFirstString(meta, keys) ?? readFirstString(requestParams, keys);
|
|
4028
|
+
const appId = readIdentity(PLUGIN_APP_ID_META_KEYS);
|
|
4047
4029
|
const connectorId = readFirstString(meta, PLUGIN_CONNECTOR_ID_META_KEYS);
|
|
4048
4030
|
const isCodexConnectorApproval = isCodexConnectorApprovalElicitation(requestParams, meta);
|
|
4049
4031
|
if (isCodexConnectorApproval && appId && connectorId && codexAppIdentityKey(appId) !== codexAppIdentityKey(connectorId)) return {
|
|
@@ -4068,9 +4050,9 @@ function resolvePluginElicitation(params) {
|
|
|
4068
4050
|
const matches = entries.filter((entry) => entry.mcpServerNames.includes(serverName));
|
|
4069
4051
|
if (matches.length > 0) return uniquePluginMatch(matches, "server_name");
|
|
4070
4052
|
}
|
|
4071
|
-
const pluginName =
|
|
4072
|
-
const configKey =
|
|
4073
|
-
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);
|
|
4074
4056
|
if (pluginName || configKey) {
|
|
4075
4057
|
if (!context) return {
|
|
4076
4058
|
kind: "decline",
|
|
@@ -4165,31 +4147,19 @@ function readApprovalElicitation(requestParams, source) {
|
|
|
4165
4147
|
meta,
|
|
4166
4148
|
...source.kind !== "computer-use" ? {
|
|
4167
4149
|
persistHintsMode: "explicit",
|
|
4168
|
-
allowedDecisions:
|
|
4150
|
+
allowedDecisions: canMapPersistentApproval(requestedSchema, meta, source.kind === "mcp") ? [
|
|
4151
|
+
"allow-once",
|
|
4152
|
+
"allow-always",
|
|
4153
|
+
"deny"
|
|
4154
|
+
] : ["allow-once", "deny"]
|
|
4169
4155
|
} : {}
|
|
4170
4156
|
};
|
|
4171
4157
|
}
|
|
4172
|
-
function buildApprovalAllowedDecisions(requestedSchema, meta, allowSession = false) {
|
|
4173
|
-
return canMapPersistentApproval(requestedSchema, meta, allowSession) ? [
|
|
4174
|
-
"allow-once",
|
|
4175
|
-
"allow-always",
|
|
4176
|
-
"deny"
|
|
4177
|
-
] : ["allow-once", "deny"];
|
|
4178
|
-
}
|
|
4179
4158
|
function canMapPersistentApproval(requestedSchema, meta, allowSession) {
|
|
4180
4159
|
const persistHints = readPersistHints(meta, "explicit");
|
|
4181
4160
|
if (allowSession) return choosePersistHint(persistHints) !== void 0;
|
|
4182
4161
|
if (persistHints.length > 0) return persistHints.includes("always");
|
|
4183
|
-
|
|
4184
|
-
return Object.entries(properties).some(([name, value]) => {
|
|
4185
|
-
const schema = isJsonObject(value) ? value : void 0;
|
|
4186
|
-
if (!schema) return false;
|
|
4187
|
-
return isPersistField({
|
|
4188
|
-
name,
|
|
4189
|
-
schema,
|
|
4190
|
-
required: false
|
|
4191
|
-
}) && chooseAlwaysPersistOptionValue(readEnumOptions(schema)) !== void 0;
|
|
4192
|
-
});
|
|
4162
|
+
return readApprovalProperties(isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {}).some((property) => isPersistField(property) && chooseAlwaysPersistOptionValue(readEnumOptions(property.schema)) !== void 0);
|
|
4193
4163
|
}
|
|
4194
4164
|
function logPluginElicitationDecline(reason, requestParams) {
|
|
4195
4165
|
embeddedAgentLog.debug("codex plugin elicitation declined", {
|
|
@@ -4219,25 +4189,26 @@ function buildApprovalDescription(params) {
|
|
|
4219
4189
|
].filter(Boolean).join("\n\n");
|
|
4220
4190
|
}
|
|
4221
4191
|
function readPropertyDescriptionLines(requestedSchema) {
|
|
4222
|
-
|
|
4223
|
-
return Object.entries(properties).map(([name, value]) => {
|
|
4224
|
-
const schema = isJsonObject(value) ? value : void 0;
|
|
4225
|
-
if (!schema) return;
|
|
4192
|
+
return readApprovalProperties(isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {}).map(({ name, schema }) => {
|
|
4226
4193
|
const propTitle = sanitizeDisplayText(readNonBlankString(schema.title) ?? "") || sanitizeDisplayText(name) || "field";
|
|
4227
4194
|
const description = sanitizeOptionalDisplayText(readNonBlankString(schema.description));
|
|
4228
4195
|
return description ? `- ${propTitle}: ${description}` : `- ${propTitle}`;
|
|
4229
|
-
})
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
function readApprovalProperties(properties) {
|
|
4199
|
+
return Object.entries(properties).flatMap(([name, schema]) => isJsonObject(schema) ? [{
|
|
4200
|
+
name,
|
|
4201
|
+
schema
|
|
4202
|
+
}] : []);
|
|
4230
4203
|
}
|
|
4231
4204
|
function readDisplayParamLines(meta) {
|
|
4232
4205
|
const displayParams = meta[MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY];
|
|
4233
4206
|
if (!Array.isArray(displayParams)) return [];
|
|
4234
|
-
const lines = displayParams.slice(0, MAX_DISPLAY_PARAM_ENTRIES).
|
|
4235
|
-
|
|
4236
|
-
if (!param) return;
|
|
4207
|
+
const lines = displayParams.slice(0, MAX_DISPLAY_PARAM_ENTRIES).flatMap((param) => {
|
|
4208
|
+
if (!isJsonObject(param)) return [];
|
|
4237
4209
|
const name = sanitizeOptionalDisplayText(readNonBlankString(param.display_name)) ?? sanitizeOptionalDisplayText(readNonBlankString(param.name));
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
}).filter((line) => Boolean(line));
|
|
4210
|
+
return name ? [`- ${name}: ${formatDisplayParamValue(param.value)}`] : [];
|
|
4211
|
+
});
|
|
4241
4212
|
const remaining = displayParams.length - MAX_DISPLAY_PARAM_ENTRIES;
|
|
4242
4213
|
return remaining > 0 ? [...lines, `- Additional parameters: ${remaining} more`] : lines;
|
|
4243
4214
|
}
|
|
@@ -4260,19 +4231,15 @@ function formatDisplayJsonValue(value, depth = MAX_DISPLAY_VALUE_DEPTH) {
|
|
|
4260
4231
|
if (typeof value === "object") {
|
|
4261
4232
|
if (depth <= 0) return "{truncated}";
|
|
4262
4233
|
const parts = [];
|
|
4263
|
-
let count = 0;
|
|
4264
|
-
let truncated = false;
|
|
4265
4234
|
for (const key in value) {
|
|
4266
4235
|
if (!Object.hasOwn(value, key)) continue;
|
|
4267
|
-
if (
|
|
4268
|
-
|
|
4236
|
+
if (parts.length >= MAX_DISPLAY_VALUE_OBJECT_KEYS) {
|
|
4237
|
+
parts.push("...");
|
|
4269
4238
|
break;
|
|
4270
4239
|
}
|
|
4271
4240
|
const safeKey = truncateCodexApprovalDisplayText(sanitizeDisplayText(key), 80);
|
|
4272
4241
|
parts.push(`${JSON.stringify(safeKey)}:${formatDisplayJsonValue(value[key] ?? null, depth - 1)}`);
|
|
4273
|
-
count += 1;
|
|
4274
4242
|
}
|
|
4275
|
-
if (truncated) parts.push("...");
|
|
4276
4243
|
return `{${parts.join(",")}}`;
|
|
4277
4244
|
}
|
|
4278
4245
|
return "null";
|
|
@@ -4310,41 +4277,28 @@ function buildAcceptedContent(approvalPrompt, outcome) {
|
|
|
4310
4277
|
const content = {};
|
|
4311
4278
|
let sawApprovalField = false;
|
|
4312
4279
|
const persist = choosePersistHint(readPersistHints(meta, approvalPrompt.persistHintsMode));
|
|
4313
|
-
for (const
|
|
4314
|
-
const
|
|
4315
|
-
if (!schema) continue;
|
|
4316
|
-
const property = {
|
|
4317
|
-
name,
|
|
4318
|
-
schema,
|
|
4319
|
-
required: required.has(name)
|
|
4320
|
-
};
|
|
4321
|
-
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");
|
|
4322
4282
|
if (isApprovalField(property)) sawApprovalField = true;
|
|
4323
4283
|
if (next === void 0) {
|
|
4324
|
-
if (property.
|
|
4284
|
+
if (required.has(property.name)) return;
|
|
4325
4285
|
continue;
|
|
4326
4286
|
}
|
|
4327
|
-
content[name] = next;
|
|
4287
|
+
content[property.name] = next;
|
|
4328
4288
|
}
|
|
4329
4289
|
return sawApprovalField ? content : void 0;
|
|
4330
4290
|
}
|
|
4331
|
-
function
|
|
4332
|
-
if (
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
return
|
|
4339
|
-
|
|
4340
|
-
function readPersistFieldValue(property, meta, outcome, persistHintsMode) {
|
|
4341
|
-
if (!isPersistField(property) || outcome !== "approved-session") return;
|
|
4342
|
-
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;
|
|
4343
4300
|
const options = readEnumOptions(property.schema);
|
|
4344
|
-
|
|
4345
|
-
const preferred = choosePersistHint(persistHints);
|
|
4346
|
-
if (preferred) return options.find((option) => option.value === preferred || option.label === preferred)?.value;
|
|
4347
|
-
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;
|
|
4348
4302
|
}
|
|
4349
4303
|
function isApprovalField(property) {
|
|
4350
4304
|
const haystack = propertyText(property).toLowerCase();
|
|
@@ -4373,14 +4327,10 @@ function buildAcceptedMeta(meta, outcome, persistHintsMode) {
|
|
|
4373
4327
|
return persist ? { persist } : null;
|
|
4374
4328
|
}
|
|
4375
4329
|
function choosePersistHint(persistHints) {
|
|
4376
|
-
|
|
4377
|
-
if (persistHints.includes("session")) return "session";
|
|
4330
|
+
return ["always", "session"].find((hint) => persistHints.includes(hint));
|
|
4378
4331
|
}
|
|
4379
4332
|
function chooseAlwaysPersistOptionValue(options) {
|
|
4380
|
-
return options.find((option) =>
|
|
4381
|
-
}
|
|
4382
|
-
function optionMatchesPersist(option, persist) {
|
|
4383
|
-
return option.value.toLowerCase() === persist || option.label.toLowerCase() === persist;
|
|
4333
|
+
return options.find((option) => option.value.toLowerCase() === "always" || option.label.toLowerCase() === "always")?.value;
|
|
4384
4334
|
}
|
|
4385
4335
|
function hasNoSchemaProperties(requestedSchema) {
|
|
4386
4336
|
const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
|
|
@@ -4395,15 +4345,14 @@ function readEnumOptions(schema) {
|
|
|
4395
4345
|
label: labels[index] ?? value
|
|
4396
4346
|
}));
|
|
4397
4347
|
}
|
|
4398
|
-
if (Array.isArray(schema.oneOf)) return schema.oneOf.
|
|
4348
|
+
if (Array.isArray(schema.oneOf)) return schema.oneOf.flatMap((entry) => {
|
|
4399
4349
|
const option = isJsonObject(entry) ? entry : void 0;
|
|
4400
4350
|
const value = readNonBlankString(option?.const);
|
|
4401
|
-
|
|
4402
|
-
return {
|
|
4351
|
+
return value ? [{
|
|
4403
4352
|
value,
|
|
4404
4353
|
label: readNonBlankString(option?.title) ?? value
|
|
4405
|
-
};
|
|
4406
|
-
})
|
|
4354
|
+
}] : [];
|
|
4355
|
+
});
|
|
4407
4356
|
return [];
|
|
4408
4357
|
}
|
|
4409
4358
|
function isPositiveApprovalOption(option) {
|
|
@@ -4555,7 +4504,7 @@ function projectCodexDynamicTools(tools) {
|
|
|
4555
4504
|
continue;
|
|
4556
4505
|
}
|
|
4557
4506
|
const descriptor = readCodexDynamicToolDescriptor(tool, toolIndex);
|
|
4558
|
-
if (
|
|
4507
|
+
if ("diagnostic" in descriptor) {
|
|
4559
4508
|
quarantinedTools.push(descriptor.diagnostic);
|
|
4560
4509
|
continue;
|
|
4561
4510
|
}
|
|
@@ -4589,13 +4538,10 @@ function projectCodexDynamicTools(tools) {
|
|
|
4589
4538
|
}
|
|
4590
4539
|
function readCodexDynamicToolDescriptor(tool, toolIndex) {
|
|
4591
4540
|
const fallbackName = `tool[${toolIndex}]`;
|
|
4592
|
-
const invalid = (name, violation) => ({
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
violations: [`${name}.${violation}`]
|
|
4597
|
-
}
|
|
4598
|
-
});
|
|
4541
|
+
const invalid = (name, violation) => ({ diagnostic: {
|
|
4542
|
+
tool: name,
|
|
4543
|
+
violations: [`${name}.${violation}`]
|
|
4544
|
+
} });
|
|
4599
4545
|
let name;
|
|
4600
4546
|
try {
|
|
4601
4547
|
const rawName = tool.name;
|
|
@@ -4625,7 +4571,6 @@ function readCodexDynamicToolDescriptor(tool, toolIndex) {
|
|
|
4625
4571
|
return invalid(name, "inputSchema is unreadable");
|
|
4626
4572
|
}
|
|
4627
4573
|
return {
|
|
4628
|
-
ok: true,
|
|
4629
4574
|
name,
|
|
4630
4575
|
description,
|
|
4631
4576
|
parameters
|
|
@@ -4777,6 +4722,39 @@ function projectCodexExecutableDynamicToolSurface(tools, hookContext) {
|
|
|
4777
4722
|
};
|
|
4778
4723
|
}
|
|
4779
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
|
|
4780
4758
|
//#region extensions/codex/src/app-server/dynamic-tools.ts
|
|
4781
4759
|
const MAX_CODEX_DYNAMIC_TOOL_VALIDATION_ERRORS = 4;
|
|
4782
4760
|
const MAX_CODEX_DYNAMIC_TOOL_VALIDATION_ERROR_CHARS = 160;
|
|
@@ -4813,11 +4791,6 @@ function computerFrameImageIdentity(content) {
|
|
|
4813
4791
|
const image = expectDefined(images[0], "single Codex computer frame image");
|
|
4814
4792
|
return createHash("sha256").update(JSON.stringify([image.mimeType, image.data])).digest("hex");
|
|
4815
4793
|
}
|
|
4816
|
-
function invalidateComputerFrame(contextEpoch) {
|
|
4817
|
-
contextEpoch.value += 1;
|
|
4818
|
-
delete contextEpoch.frameToolCallId;
|
|
4819
|
-
delete contextEpoch.frameImageIdentity;
|
|
4820
|
-
}
|
|
4821
4794
|
function createCodexDynamicToolBridge(params) {
|
|
4822
4795
|
const toolResultHookContext = toToolResultHookContext(params.hookContext);
|
|
4823
4796
|
const contextWindowTokens = params.hookContext?.contextWindowTokens;
|
|
@@ -5081,7 +5054,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5081
5054
|
const contentItems = convertToolContents(result.content, toolResultMaxChars);
|
|
5082
5055
|
const deliveredFrameImages = contentItems.filter((item) => item.type === "inputImage");
|
|
5083
5056
|
const finalFrameImageIdentity = computerFrameImageIdentity(result.content);
|
|
5084
|
-
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);
|
|
5085
5058
|
const response = {
|
|
5086
5059
|
contentItems,
|
|
5087
5060
|
success: !resultIsError,
|
|
@@ -5089,9 +5062,20 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5089
5062
|
diagnosticTerminalReason: resultFailureKind === "blocked" ? void 0 : resultFailureKind,
|
|
5090
5063
|
transcriptDetails: asOptionalRecord(sanitizeToolResult(result))?.details
|
|
5091
5064
|
};
|
|
5092
|
-
const
|
|
5093
|
-
|
|
5094
|
-
|
|
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
|
+
});
|
|
5095
5079
|
const autoDeliveryTtsMediaUrls = getCoreTtsToolResultMediaUrls(rawResult);
|
|
5096
5080
|
recordAgentHarnessToolResultTelemetry({
|
|
5097
5081
|
extractSourceReplyPayload: extractMessagingToolSourceReplyPayload,
|
|
@@ -5109,12 +5093,10 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5109
5093
|
autoDeliveryTtsMediaUrls,
|
|
5110
5094
|
coreTtsToolResult: autoDeliveryTtsMediaUrls?.length ? rawResult : void 0,
|
|
5111
5095
|
messagingTarget: confirmedMessagingTarget,
|
|
5112
|
-
sourceReplyFinal,
|
|
5096
|
+
sourceReplyFinal: sourceReply.final,
|
|
5113
5097
|
trustedLocalMediaToolNames: pluginLocalMediaTrustByToolName.get(toolName)
|
|
5114
5098
|
});
|
|
5115
|
-
if (deliveredSourceReply ||
|
|
5116
|
-
const continuesSourceReplyProgress = confirmedSourceReply && sourceReplyFinal === false;
|
|
5117
|
-
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;
|
|
5118
5100
|
const asyncStarted = isAsyncStartedToolResult(rawResult) || isAsyncStartedToolResult(result);
|
|
5119
5101
|
response.asyncStarted = asyncStarted || void 0;
|
|
5120
5102
|
const replaySafe = executionBoundary.executionPrevented || !asyncStarted && isReplaySafeToolInstance(toolEntry.tool) && isReplaySafeToolCall(toolName, executedArgs);
|
|
@@ -5126,7 +5108,7 @@ function createCodexDynamicToolBridge(params) {
|
|
|
5126
5108
|
return response;
|
|
5127
5109
|
},
|
|
5128
5110
|
onError: ({ error, boundary: executionBoundary, executedArguments: executedArgs, startedAt }) => {
|
|
5129
|
-
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId)
|
|
5111
|
+
if (toolName === "computer" && params.computerContextEpoch?.frameToolCallId === call.callId) invalidateCodexComputerFrame(params.computerContextEpoch);
|
|
5130
5112
|
const executionDisposition = getBeforeToolCallFailureDisposition(error) ?? (signal.aborted ? resolveCodexToolAbortTerminalReason(signal) : resolveToolExecutionErrorKind(error));
|
|
5131
5113
|
const errorMessage = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
|
|
5132
5114
|
const operatorHint = readToolOperatorHint(error);
|
|
@@ -5205,10 +5187,12 @@ function reportQuarantinedDynamicTools(params) {
|
|
|
5205
5187
|
});
|
|
5206
5188
|
}
|
|
5207
5189
|
function dedupeQuarantinedDynamicTools(tools) {
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
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()];
|
|
5212
5196
|
}
|
|
5213
5197
|
function toToolResultHookContext(ctx) {
|
|
5214
5198
|
const { agentId, sessionId, sessionKey, runId, channelId } = ctx ?? {};
|
|
@@ -5220,14 +5204,9 @@ function toToolResultHookContext(ctx) {
|
|
|
5220
5204
|
...channelId && { channelId }
|
|
5221
5205
|
};
|
|
5222
5206
|
}
|
|
5223
|
-
function isToolResultYield(result) {
|
|
5224
|
-
const details = result.details;
|
|
5225
|
-
if (!isRecord(details) || typeof details.status !== "string") return false;
|
|
5226
|
-
return details.status.trim().toLowerCase() === "yielded";
|
|
5227
|
-
}
|
|
5228
5207
|
function isAsyncStartedToolResult(result) {
|
|
5229
5208
|
const details = result.details;
|
|
5230
5209
|
return isRecord(details) && details.async === true && details.status === "started";
|
|
5231
5210
|
}
|
|
5232
5211
|
//#endregion
|
|
5233
|
-
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 };
|