@openclaw/codex 2026.10.1-beta.2 → 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.
Files changed (161) hide show
  1. package/dist/.setup/{account-usage-runtime-DGqFpAud.mjs → account-usage-runtime-QBZsvZS8.mjs} +8 -7
  2. package/dist/.setup/{app-server-policy-Bg4FZw1f.mjs → app-server-policy-D_zv8J7V.mjs} +3 -4
  3. package/dist/.setup/{apply-CXTudl9p.mjs → apply-CBMV8LN0.mjs} +40 -51
  4. package/dist/.setup/{attempt-client-cleanup-BH0hW2xU.mjs → attempt-client-cleanup-t-kd_1ys.mjs} +6 -6
  5. package/dist/.setup/attempt-terminal-jO5IMj9K.mjs +2 -0
  6. package/dist/.setup/{attempt-workspace-context-BY0jprWJ.mjs → attempt-workspace-context-CcwP3DA3.mjs} +21 -18
  7. package/dist/.setup/{auth-binding-BoMwpuPN.mjs → auth-binding-BaxlhaNx.mjs} +2 -2
  8. package/dist/.setup/{auth-cache-key-CGm0UJOr.mjs → auth-cache-key-DyfUvsSa.mjs} +30 -41
  9. package/dist/.setup/{binding-connection-C-08ez1w.mjs → binding-connection-B7HAAZSI.mjs} +2 -2
  10. package/dist/.setup/{binding-connection.runtime-Dn2p-fdy.mjs → binding-connection.runtime-DAXn7iFl.mjs} +6 -5
  11. package/dist/.setup/{bounded-turn-pGsfXcmH.mjs → bounded-turn-CyQVQwGe.mjs} +21 -17
  12. package/dist/.setup/{bounded-turn-isolation-DklXNk3k.mjs → bounded-turn-isolation-BfDAo-qZ.mjs} +4 -4
  13. package/dist/.setup/{build-state-CqQnsg7T.mjs → build-state-CzLMy1S9.mjs} +1 -1
  14. package/dist/.setup/{capabilities-kQntafj_.mjs → capabilities-DbOzOglY.mjs} +1 -1
  15. package/dist/.setup/{client-catalog-response-Bqp7WG51.mjs → client-catalog-response-DsbEAtpd.mjs} +25 -32
  16. package/dist/.setup/{client-C8lgBOoO.mjs → client-y7nO6X9c.mjs} +198 -247
  17. package/dist/.setup/{command-dispatch-DhBbFIQ5.mjs → command-dispatch-uA9lz2Kf.mjs} +3 -4
  18. package/dist/.setup/{command-formatters-DV3DHhdr.mjs → command-formatters-xW9KpoSZ.mjs} +16 -26
  19. package/dist/.setup/{command-handlers-ztOOzm6O.mjs → command-handlers-VBaJnpZo.mjs} +270 -360
  20. package/dist/.setup/{command-rpc-CuC5fPom.mjs → command-rpc-C9VxOLuG.mjs} +19 -18
  21. package/dist/.setup/{compact-tUb9AYfH.mjs → compact-CD8xJxXS.mjs} +41 -61
  22. package/dist/.setup/{computer-use-BBN9y326.mjs → computer-use-CiToXF5C.mjs} +109 -147
  23. package/dist/.setup/{config-0Gxk3_eq.mjs → config-C1YVVLEA.mjs} +32 -59
  24. package/dist/.setup/config-contracts.shared-D85SGEuq.mjs +4 -0
  25. package/dist/.setup/{config-options-HG_Rr3W_.mjs → config-options-DDzeNQ8m.mjs} +52 -90
  26. package/dist/.setup/{config-parsing-BQq-CHTF.mjs → config-parsing-DsWSCPHO.mjs} +19 -55
  27. package/dist/.setup/{config-runtime-cUYrqu_a.mjs → config-runtime-DWzK-k0n.mjs} +2 -2
  28. package/dist/.setup/{config-security-KAPScztc.mjs → config-security-clxAZYWX.mjs} +35 -39
  29. package/dist/.setup/config-utils-DN_YT7dE.mjs +47 -0
  30. package/dist/.setup/{context-engine-projection-CsgYSQhW.mjs → context-engine-projection-ps-yn_ci.mjs} +10 -22
  31. package/dist/.setup/{conversation-binding-B3MNMVzb.mjs → conversation-binding-CTSLDM9S.mjs} +85 -119
  32. package/dist/.setup/{conversation-binding-data-DJ4ix-W5.mjs → conversation-binding-data-UgLuTSQT.mjs} +9 -8
  33. package/dist/.setup/{conversation-control-CYsMlCuE.mjs → conversation-control-GXM3unE4.mjs} +7 -10
  34. package/dist/.setup/{cyber-failover-Bb1J6hS1.mjs → cyber-failover-D4dgNfFf.mjs} +2 -5
  35. package/dist/.setup/{cyber-failover-notice-XR03ef31.mjs → cyber-failover-notice-ljgGGL0s.mjs} +1 -1
  36. package/dist/.setup/{doctor-start-options-DPW9JxIE.mjs → doctor-start-options-JjTAl-q_.mjs} +5 -5
  37. package/dist/.setup/{dynamic-tool-build-C8xGEXmR.mjs → dynamic-tool-build-D731PCdv.mjs} +57 -69
  38. package/dist/.setup/{dynamic-tools-DyERJnR6.mjs → dynamic-tools-D0pYbxHH.mjs} +689 -710
  39. package/dist/.setup/{effective-mcp-catalog-C26Mio5l.mjs → effective-mcp-catalog-1hWFKghy.mjs} +11 -20
  40. package/dist/.setup/{elicitation-input-r9axtppm.mjs → elicitation-input-DXG_qaXr.mjs} +14 -20
  41. package/dist/.setup/{ephemeral-turn-D8VcDTNo.mjs → ephemeral-turn-Cphy5Qr_.mjs} +3 -3
  42. package/dist/.setup/{event-projector-assistant-message-BN7CNUB5.mjs → event-projector-assistant-message-CLMKN4x-.mjs} +2 -2
  43. package/dist/.setup/{event-projector-usage-03R0I2dM.mjs → event-projector-usage-BpKfdoTm.mjs} +20 -33
  44. package/dist/.setup/{event-projector-values-DTqZHizf.mjs → event-projector-values-CLvG1Hil.mjs} +4 -5
  45. package/dist/.setup/history-rejection-B4nOXNXF.mjs +13 -0
  46. package/dist/.setup/{inference-context-D-9gWlTR.mjs → inference-context-DNlGwzwy.mjs} +10 -11
  47. package/dist/.setup/{inference-dispatch-B5UKEUyC.mjs → inference-dispatch-CO6tTs4q.mjs} +5 -4
  48. package/dist/.setup/{inference-metadata-CgIf7sOx.mjs → inference-metadata-DCvcG_N_.mjs} +36 -41
  49. package/dist/.setup/{inference-proxy-DUO5E2QQ.mjs → inference-proxy-BDWV0djM.mjs} +10 -14
  50. package/dist/.setup/{isolated-completion-C0jrcplS.mjs → isolated-completion-Bhqd2yBq.mjs} +8 -8
  51. package/dist/.setup/local-runtime-attribution-CZz2DJ4q.mjs +15 -0
  52. package/dist/.setup/{managed-binary-C1VFoVPf.mjs → managed-binary-BtIjV7yN.mjs} +16 -36
  53. package/dist/.setup/{mcp-app-session-preparation-BxN0_CYe.mjs → mcp-app-session-preparation-DqSvxFqc.mjs} +24 -25
  54. package/dist/.setup/{mcp-tool-metadata-j10HhfTC.mjs → mcp-tool-metadata-DW41snf3.mjs} +1 -1
  55. package/dist/.setup/{model-catalog-DZyNeDKG.mjs → model-catalog-B227ST3D.mjs} +6 -6
  56. package/dist/.setup/{models-DSseKSMg.mjs → models-DdRyi93L.mjs} +6 -10
  57. package/dist/.setup/{native-auth-Cjb9y7IZ.mjs → native-auth-esN5MdIv.mjs} +5 -5
  58. package/dist/.setup/{native-execution-policy-ClzpdG3D.mjs → native-execution-policy-MXEe7PB3.mjs} +11 -28
  59. package/dist/.setup/native-hook-relay-BUWIOrSr.mjs +561 -0
  60. package/dist/.setup/{native-mcp-app-CqcPVmhH.mjs → native-mcp-app-CjxJQ4F7.mjs} +6 -6
  61. package/dist/.setup/native-skill-isolation-UMVoNjZz.mjs +391 -0
  62. package/dist/.setup/{native-subagent-monitor-BUYOUp9X.mjs → native-subagent-monitor-iHc8oLg5.mjs} +505 -639
  63. package/dist/.setup/{native-subagent-pending-assignments-BDqhOdJp.mjs → native-subagent-pending-assignments-DmVhedNs.mjs} +11 -23
  64. package/dist/.setup/{native-task-assignments-C6KVqiN1.mjs → native-task-assignments-DimsQ9Qa.mjs} +73 -27
  65. package/dist/.setup/{node-cli-sessions-B5oNDkUS.mjs → node-cli-sessions-CzKg2nGq.mjs} +18 -26
  66. package/dist/.setup/{node-exec-server.runtime-0xSEjKYt.mjs → node-exec-server.runtime-BWkULhBM.mjs} +5 -8
  67. package/dist/.setup/{plan-BJ37d2Og.mjs → plan-C_F3aio0.mjs} +40 -50
  68. package/dist/.setup/{plugin-activation-BcyBv8PG.mjs → plugin-activation-D7WkJMvU.mjs} +4 -5
  69. package/dist/.setup/{plugin-app-cache-key-BW8rd98k.mjs → plugin-app-cache-key-Bg9DbDI6.mjs} +18 -23
  70. package/dist/.setup/{plugin-approval-roundtrip-CuahdPLC.mjs → plugin-approval-roundtrip-BSV-51ig.mjs} +4 -6
  71. package/dist/.setup/{plugin-inventory-Bf0_piTw.mjs → plugin-inventory-CYgdo14P.mjs} +28 -43
  72. package/dist/.setup/{plugin-marketplace-discovery-SQPhSuic.mjs → plugin-marketplace-discovery-BblHM3DU.mjs} +7 -10
  73. package/dist/.setup/{plugin-metadata-cache-DWnB7VMj.mjs → plugin-metadata-cache-Bi8TAPwX.mjs} +5 -10
  74. package/dist/.setup/project-doc-thread-config-BVcvnps8.mjs +490 -0
  75. package/dist/.setup/{provider-BnwHVD_z.mjs → provider-BJ4xhaGX.mjs} +4 -4
  76. package/dist/.setup/{provider-capabilities-D68Whix6.mjs → provider-capabilities-CjRaV3bx.mjs} +1 -1
  77. package/dist/.setup/{rate-limits-CMPy24F5.mjs → rate-limits-CI8pTPqL.mjs} +15 -32
  78. package/dist/.setup/reasoning-effort-CS9kKWB9.mjs +37 -0
  79. package/dist/.setup/{request-jSbhc1sg.mjs → request-Y8dmbqZ3.mjs} +28 -48
  80. package/dist/.setup/{rpc-error-hmWM21RB.mjs → rpc-error-C98SHchT.mjs} +3 -6
  81. package/dist/.setup/{run-attempt-BLDZeYLD.mjs → run-attempt-Dx7a4Mfm.mjs} +927 -849
  82. package/dist/.setup/{run-attempt-lifecycle-BuZgVbDA.mjs → run-attempt-lifecycle-KN4oh-7O.mjs} +13 -4
  83. package/dist/.setup/{runtime-artifact-CUTOOnKw.mjs → runtime-artifact-C13pR7tD.mjs} +18 -22
  84. package/dist/.setup/{runtime-artifact-connection-CADz3_BY.mjs → runtime-artifact-connection-jS5DeCok.mjs} +3 -3
  85. package/dist/.setup/{sandbox-guard-WqE8yPge.mjs → sandbox-guard-DIImwCy1.mjs} +4 -6
  86. package/dist/.setup/service-tier-normalization-BPgw2Wdt.mjs +9 -0
  87. package/dist/.setup/{session-binding-Ce7BqiPN.mjs → session-binding-JEHNMPsr.mjs} +13 -40
  88. package/dist/.setup/{session-binding-record-BZB8RgcF.mjs → session-binding-record-DU_0biMD.mjs} +4 -4
  89. package/dist/.setup/{session-binding-record-codec-z4ZpDn6E.mjs → session-binding-record-codec-BpH8u_G9.mjs} +16 -39
  90. package/dist/.setup/{session-binding-sidecars-BrdV95Fg.mjs → session-binding-sidecars-DD-enC8w.mjs} +49 -54
  91. package/dist/.setup/{session-catalog-adoption-CPIP4S_y.mjs → session-catalog-adoption-Ca3OYGYM.mjs} +23 -27
  92. package/dist/.setup/{session-catalog-archive-DcgwvSbF.mjs → session-catalog-archive-Bv6gV7x9.mjs} +6 -6
  93. package/dist/.setup/{session-catalog-events-Bdjw9HEn.mjs → session-catalog-events-DzFlXNUe.mjs} +6 -5
  94. package/dist/.setup/{session-catalog-index-DufISKJ6.mjs → session-catalog-index-DozvkUPd.mjs} +111 -105
  95. package/dist/.setup/{session-catalog-index-order-nhyO0wdW.mjs → session-catalog-index-order-CTfoefow.mjs} +28 -5
  96. package/dist/.setup/{session-catalog-limits-DEU36Qlo.mjs → session-catalog-limits-BynIoOYm.mjs} +1 -1
  97. package/dist/.setup/{session-catalog-list-request-tXephOB4.mjs → session-catalog-list-request-D4MYNgDB.mjs} +11 -8
  98. package/dist/.setup/{session-catalog-native-projection-D6IiLtMb.mjs → session-catalog-native-projection-rCKnZud1.mjs} +5 -61
  99. package/dist/.setup/{session-catalog-node-adoption-7-WEQTB1.mjs → session-catalog-node-adoption-BdP9Zjqo.mjs} +2 -2
  100. package/dist/.setup/{session-catalog-node-continue-DLWBwAAO.mjs → session-catalog-node-continue-CNJibO4_.mjs} +27 -49
  101. package/dist/.setup/{session-catalog-node-lookup-Uufckwcm.mjs → session-catalog-node-lookup-BH_DBGa9.mjs} +3 -3
  102. package/dist/.setup/{session-catalog-parsing-B-qWJh2x.mjs → session-catalog-parsing-DkeoVn60.mjs} +9 -19
  103. package/dist/.setup/{session-catalog-projection-8aHNwzWD.mjs → session-catalog-projection-DDVmALHj.mjs} +8 -22
  104. package/dist/.setup/session-catalog-source-CygTGmrb.mjs +58 -0
  105. package/dist/.setup/{session-cli-kH4vuGod.mjs → session-cli-GwU0izn-.mjs} +20 -41
  106. package/dist/.setup/{session-history-BogBHKLk.mjs → session-history-BO6i_pfk.mjs} +1 -1
  107. package/dist/.setup/{session-history-import-NWKPa4ZO.mjs → session-history-import-CaO8s6ta.mjs} +4 -4
  108. package/dist/.setup/{session-history-read-CNN8zwbD.mjs → session-history-read-BI_2jRED.mjs} +4 -15
  109. package/dist/.setup/{session-history.worker-Cq_4DYgN.mjs → session-history.worker-DJQfek5p.mjs} +147 -159
  110. package/dist/.setup/{session-initialization-D6dtFQkE.mjs → session-initialization-Bi_2DKhc.mjs} +3 -5
  111. package/dist/.setup/{session-permission-policy-BxIUJ0Zd.mjs → session-permission-policy-CD2MAWU8.mjs} +12 -20
  112. package/dist/.setup/{session-retirement-Czbchv_O.mjs → session-retirement-IoS3zr17.mjs} +7 -7
  113. package/dist/.setup/{settled-turn-context-BZsMjkIO.mjs → settled-turn-context-BPL6D3RO.mjs} +1 -1
  114. package/dist/.setup/{settled-turn-finalizer-DjZsIwbZ.mjs → settled-turn-finalizer-X-Fylp-u.mjs} +12 -11
  115. package/dist/.setup/{shared-client-qhyYWbew.mjs → shared-client-ByIwuAC-.mjs} +172 -229
  116. package/dist/.setup/{side-question-D3NJlgbl.mjs → side-question-BDUdozFy.mjs} +116 -137
  117. package/dist/.setup/{source-BYu0T780.mjs → source-Bxbqf3S1.mjs} +37 -56
  118. package/dist/.setup/thread-archive-guard-LKdwQKyr.mjs +56 -0
  119. package/dist/.setup/{thread-lifecycle-Cbz_7ySv.mjs → thread-lifecycle-run-CfP4PnEf.mjs} +348 -484
  120. package/dist/.setup/thread-model-selection-DcVebr8N.mjs +61 -0
  121. package/dist/.setup/{thread-ownership-DwuSrt3s.mjs → thread-ownership-Be53aiaG.mjs} +5 -5
  122. package/dist/.setup/{thread-ownership-queue-Dp24d7-F.mjs → thread-ownership-queue-CeyYScUC.mjs} +5 -6
  123. package/dist/.setup/{thread-policy-DoNxbhqt.mjs → thread-policy-CHgY-KNl.mjs} +4 -4
  124. package/dist/.setup/thread-requests-BgzJwJtS.mjs +745 -0
  125. package/dist/.setup/{thread-resume-BT6NHMkM.mjs → thread-resume-B2EmMcgE.mjs} +5 -5
  126. package/dist/.setup/{timeout-CXOfNoaX.mjs → timeout-D0mEWSGG.mjs} +6 -12
  127. package/dist/.setup/{transcript-history-projection-B9a7HwUV.mjs → transcript-history-projection-DfKcoZCB.mjs} +3 -4
  128. package/dist/.setup/{transcript-mirror-BfLRARNL.mjs → transcript-mirror-Bee2o6jj.mjs} +68 -68
  129. package/dist/.setup/{transcript-mirror-attestation-YSegaktH.mjs → transcript-mirror-attestation-BzUXnrvv.mjs} +5 -2
  130. package/dist/.setup/{transport-stdio-a0HDa2Mj.mjs → transport-stdio-CRWTkKOn.mjs} +67 -77
  131. package/dist/.setup/{transport-websocket-zqJ0Cmtn.mjs → transport-websocket-DIyPfkHo.mjs} +8 -16
  132. package/dist/.setup/{turn-router-sS69wYnI.mjs → turn-router-DKDAziUW.mjs} +30 -31
  133. package/dist/.setup/upstream-prompt-provenance-Ccd9MFRJ.mjs +44 -0
  134. package/dist/.setup/{upstream-session-fork-ChSld66u.mjs → upstream-session-fork-Djp_yssT.mjs} +50 -43
  135. package/dist/.setup/{usage-D8e8Xiqf.mjs → usage-DQA-peQC.mjs} +4 -4
  136. package/dist/.setup/{usage-limit-error-GbT-5NcW.mjs → usage-limit-error-2t3KBqiX.mjs} +12 -21
  137. package/dist/.setup/web-search-item-CvokaZOo.mjs +20 -0
  138. package/dist/.setup/{web-search-provider.runtime-dL2GD1Kl.mjs → web-search-provider.runtime-BsuCMNix.mjs} +3 -3
  139. package/dist/.setup/{workspace-write-sandbox-probe-DmUHLrrl.mjs → workspace-write-sandbox-probe-BDWO2Qkh.mjs} +4 -4
  140. package/dist/api.js +15 -15
  141. package/dist/catalog-page.worker.js +1 -1
  142. package/dist/cli-metadata.js +1 -1
  143. package/dist/doctor-contract-api.js +5 -6
  144. package/dist/harness.js +32 -61
  145. package/dist/index.js +302 -416
  146. package/dist/media-understanding-provider.js +32 -43
  147. package/dist/migration-provider-api.js +1 -1
  148. package/dist/native-session-binding-api.js +1 -1
  149. package/dist/native-tool-policy.js +23 -1
  150. package/dist/provider-discovery.js +1 -1
  151. package/dist/session-history-worker-runtime.js +52 -31
  152. package/dist/session-history.worker.js +1 -1
  153. package/openclaw.plugin.json +20 -3
  154. package/package.json +7 -7
  155. package/dist/.setup/attempt-terminal-gZrxQ35N.mjs +0 -5
  156. package/dist/.setup/plugin-thread-config-deadline-B8LGaZNW.mjs +0 -124
  157. package/dist/.setup/service-tier-normalization-Cei89SW6.mjs +0 -15
  158. package/dist/.setup/shell-dynamic-tools-D3Brj3g9.mjs +0 -85
  159. package/dist/.setup/thread-archive-guard-C9frICzX.mjs +0 -60
  160. package/dist/.setup/thread-requests-CL1e8Wmx.mjs +0 -2111
  161. 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 { o as formatCodexDisplayText } from "./command-formatters-DV3DHhdr.mjs";
3
- import { o as readItem } from "./event-projector-values-DTqZHizf.mjs";
4
- import { s as codexAppIdentityKey } from "./plugin-app-cache-key-BW8rd98k.mjs";
5
- import { r as invalidInlineImageText } from "./session-history-read-CNN8zwbD.mjs";
6
- import { U as protectCodexAppServerLiveThread, _ as retainSharedCodexAppServerClientIfCurrent } from "./shared-client-qhyYWbew.mjs";
7
- import { F as createFailedDynamicToolResponse, I as failedToolResult, P as resolveCodexToolAbortTerminalReason, l as CodexServerRequestResolvedError, w as createCodexElicitationResponse, x as readCodexTurnCompletedNotification } from "./client-C8lgBOoO.mjs";
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 { _ as itemStatus, a as isCodexNotificationForTurn, b as shouldClearTerminalPresentationForNativeItem, c as auditNativeToolName, g as itemName, l as auditNativeToolTerminalStatus, o as readCodexNotificationThreadId, s as readCodexNotificationTurnId, u as auditNativeToolUnfinishedStatus } from "./turn-router-sS69wYnI.mjs";
10
- import { M as emitCodexNativePreToolUseFailureDiagnostic } from "./thread-requests-CL1e8Wmx.mjs";
11
- 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-CuahdPLC.mjs";
12
- import { d as isCodexPassThroughMediaSource, f as mapCodexAppServerLocalWorkspacePath, p as mapCodexAppServerRemoteWorkspacePath } from "./dynamic-tool-build-C8xGEXmR.mjs";
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 { DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, estimateToolResultTextChars, resolveLiveToolResultMaxChars, safeParseJson, sliceToolResultTextToBudget, sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
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 { 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";
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" || typeof candidate.server !== "string" || !candidate.server.trim() || typeof candidate.tool !== "string" || !candidate.tool.trim() || candidate.arguments === void 0 || candidate.appContext != null || candidate.pluginId != null) return;
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: candidate.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 || !turnId) return;
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 || settled) throw new Error("Codex native process authority has ended");
587
+ if (this.closed || !command.processes.has(process)) throw new Error("Codex native process authority has ended");
525
588
  },
526
589
  settle: () => {
527
- if (settled) return;
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 active = false;
689
- let terminalReason;
867
+ let fallback;
690
868
  const emit = (failure) => emitCodexNativePreToolUseFailureDiagnostic({
691
869
  ...params,
692
870
  failure,
693
- ...active ? { terminalReason: terminalReason ?? failure.disposition } : {}
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 active;
879
+ return fallback !== void 0;
702
880
  },
703
881
  record(failure) {
704
- if (active) emit(failure);
882
+ if (fallback) emit(failure);
705
883
  else pending.push(failure);
706
884
  },
707
885
  activateFallback(wasAborted) {
708
- if (!active) {
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
- let pathUrl;
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
- let resolved;
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
- * Resolves Codex filesystem sandbox policy payloads into OpenClaw path/glob
1135
- * checks for sandbox exec-server filesystem operations.
1136
- */
1137
- /** Resolves request-local sandbox policy and asserts each requested path has the needed access. */
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") return {
1178
- kind: "path",
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" && access === "none") throw new Error(`Codex fs sandbox denied read access to ${request.path}`);
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 (!fsSandboxEntryMatches(entry, target)) continue;
1389
+ if (!(entry.kind === "path" ? pathContains(entry.path, target) : entry.matcher.test(target))) continue;
1232
1390
  const candidate = {
1233
- specificity: fsSandboxEntrySpecificity(entry),
1234
- rank: fsAccessRank(entry.access),
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" || !fsSandboxEntryCanAffectDescendant(entry, target)) return false;
1247
- if (entry.kind === "glob") return true;
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
- const normalized = posix.normalize(rawPath);
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 += escapeSandboxGlobCharacterClassChar(char, body.length === 0);
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
- if (handles.get(handleId) !== handle || handle.closeRequested || handles.closed) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, `unknown file read handle \`${handleId}\``);
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
- if (handles.get(handleId) !== handle || handle.closeRequested || handles.closed) throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, `unknown file read handle \`${handleId}\``);
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 readFile(execServer, params) {
1448
- const record = requireObject(params, "fs/readFile params");
1449
- const filePath = resolveExecServerPath(requireString(record.path, "path"), "read path");
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 writeFile(execServer, params) {
1470
- const record = requireObject(params, "fs/writeFile params");
1471
- const filePath = resolveExecServerPath(requireString(record.path, "path"), "write path");
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
- assertFsSandboxAccess(execServer, record, [{
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 ((await fsBridge.stat({ filePath: posix.dirname(filePath) }))?.type !== "directory") throw new JsonRpcProtocolError(JSON_RPC_NOT_FOUND, "parent directory not found");
1485
- await fsBridge.writeFile({
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
- async function getMetadata(execServer, params) {
1517
- const record = requireObject(params, "fs/getMetadata params");
1518
- const filePath = resolveExecServerPath(requireString(record.path, "path"), "metadata path");
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 Promise.race([closed.promise.then(() => true), delay(SANDBOX_CHILD_REAP_TIMEOUT_MS).then(() => false)]).finally(() => clearTimeout(escalation));
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
- settled.then(() => params.owners.delete(owner), () => params.owners.delete(owner));
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 Promise.race([delay(Math.min(SANDBOX_CHILD_INTERRUPT_POLL_MS, Math.max(0, deadline - performance.now()))), exited.promise]);
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
- terminate().catch((error) => {
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
- readSandboxHttpResponse({
1971
- child,
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
- params.lifecycle.failed = true;
2019
- params.terminate().catch((error) => {
2020
- embeddedAgentLog.warn("codex sandbox http/request cleanup failed", { error });
2021
- });
2096
+ lifecycle.failed = true;
2097
+ cleanup();
2022
2098
  if (headerResolved) {
2023
- params.notifications.send("http/request/bodyDelta", {
2024
- requestId: params.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
- if (params.streamResponse) {
2045
- params.child.stdout.setEncoding("utf8");
2046
- params.child.stderr.setEncoding("utf8");
2047
- }
2048
- params.child.stdout.on("data", (chunk) => {
2049
- if (failed) return;
2050
- if (!params.streamResponse) {
2051
- bufferOutput("stdout", chunk);
2052
- return;
2053
- }
2054
- const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
2055
- stdoutBuffer += text;
2056
- let newline = stdoutBuffer.indexOf("\n");
2057
- while (newline >= 0) {
2058
- const line = stdoutBuffer.slice(0, newline).trim();
2059
- stdoutBuffer = stdoutBuffer.slice(newline + 1);
2060
- if (line) try {
2061
- const message = requireObject(JSON.parse(line), "http stream message");
2062
- const type = requireString(message.type, "http stream message type");
2063
- if (type === "headers") {
2064
- headerResolved = true;
2065
- resolve({
2066
- status: requireNumber(message.status, "http status"),
2067
- headers: readHttpHeaders(message.headers),
2068
- bodyBase64: ""
2069
- });
2070
- } else if (type === "bodyDelta") {
2071
- const seq = requireNumber(message.seq, "http body sequence");
2072
- lastBodySeq = Math.max(lastBodySeq, seq);
2073
- params.notifications.send("http/request/bodyDelta", {
2074
- requestId: params.requestId,
2075
- seq,
2076
- deltaBase64: typeof message.deltaBase64 === "string" ? message.deltaBase64 : "",
2077
- done: message.done === true,
2078
- error: typeof message.error === "string" ? message.error : null
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
- } catch (error) {
2082
- fail(error instanceof Error ? error.message : String(error));
2162
+ newline = stdoutBuffer.indexOf("\n");
2083
2163
  }
2084
- newline = stdoutBuffer.indexOf("\n");
2085
- }
2086
- 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`);
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
- params.lifecycle.failed = true;
2169
+ lifecycle.failed = true;
2100
2170
  });
2101
- params.child.once("close", (code) => {
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 (!params.streamResponse) {
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
- params.lifecycle.failed = true;
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 (!params.streamResponse) stderr = Buffer.concat(buffered.stderr.chunks).toString("utf8");
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
- if any(parsed in network for network in BLOCKED_IPV4_NETWORKS):
2205
- return True
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 = response_headers(response)
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
- rejectUnsupportedArg0(record.arg0);
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
- if (managed.startPromise === startPromise) managed.startPromise = void 0;
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 !== "restricted") return;
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
- child.once("error", (error) => {
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 managed = requireProcess(processes, requireString(record.processId, "processId"));
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 chunks = limitProcessChunks(managed.chunks.filter((chunk) => chunk.seq > afterSeq), typeof record.maxBytes === "number" && record.maxBytes > 0 ? record.maxBytes : void 0);
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
- if (!managed) return { running: false };
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 requestedEnv = {
2691
- ...buildEnvFromPolicy(record.envPolicy),
2692
- ...readEnv(record.env)
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 inheritedEnv;
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": return await readFile(this.execServer, params);
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 createDirectory(this.execServer, params);
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
- sandbox: params.sandbox,
2821
- runtime: params.runtime,
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
- const promise = sandboxExecServerRegistry.servers.get(key) ?? startAndRememberOpenClawExecServer(sandbox);
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 = buildEnvironmentId(sandbox);
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", handleExecServerSocketError);
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) || typeof sandbox.placementEnvironmentId !== "string" || !sandbox.placementEnvironmentId || sandbox.placementEnvironmentId.trim() !== sandbox.placementEnvironmentId || !("placementSessionId" in sandbox) || typeof sandbox.placementSessionId !== "string" || !sandbox.placementSessionId || sandbox.placementSessionId.trim() !== 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.");
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, context.requestParams, "cancelled");
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(context.requestParams, resolvedOutcome);
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
- emitEvent({
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" || policyOutcome?.outcome === "approved-session") return await resolvePolicyApproval(policyOutcome.outcome);
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
- emitEvent({
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
- emitEvent({
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
- emitEvent({
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: fileChangeApprovalDecision(outcome) };
3350
- if (method === "item/permissions/requestApproval") {
3351
- if (outcome === "approved-session" || outcome === "approved-once") return {
3352
- permissions: requestedPermissions(requestParams),
3353
- scope: outcome === "approved-session" ? "session" : "turn"
3354
- };
3355
- return {
3356
- permissions: {},
3357
- scope: "turn"
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(readCommandActionsPreview(params.requestParams) ?? readCommandPreview(params.requestParams), commandDetailLines.length > 0 ? COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH : 180);
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 title = networkApproval ? "Codex app-server network approval" : kind === "exec" ? "Codex app-server command approval" : params.method === "item/permissions/requestApproval" ? "Codex app-server permission approval" : kind === "plugin" ? "Codex app-server file approval" : "Codex app-server approval";
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: networkApproval ? "codex_network_approval" : kind === "exec" ? "codex_command_approval" : params.method === "item/permissions/requestApproval" ? "codex_permission_approval" : "codex_file_approval",
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 nativeRelayOutcome = await runNativeRelayToolPolicyForApprovalRequest({
3403
- method: params.method,
3404
- requestParams: params.requestParams,
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
- async function runNativeRelayToolPolicyForApprovalRequest(params) {
3440
- const nativeHookRelay = params.nativeHookRelay;
3441
- if (params.method !== "item/commandExecution/requestApproval" || !nativeHookRelay?.allowedEvents.includes("pre_tool_use")) return;
3442
- const payload = buildNativeRelayPreToolUsePayload({
3443
- requestParams: params.requestParams,
3444
- policyRequest: params.policyRequest,
3445
- context: params.context,
3446
- cwd: params.cwd
3447
- });
3448
- if (!payload) return;
3449
- const resolveDeferredApproval = async () => {
3450
- const approvalOutcome = await resolveNativeHookRelayDeferredToolApproval({
3451
- relayId: nativeHookRelay.relayId,
3452
- toolUseId: params.context.approvalId,
3453
- signal: params.signal
3454
- });
3455
- params.assertActive();
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
- return approvalOutcome?.outcome === "approved-once" ? {
3463
- handled: true,
3464
- outcome: approvalOutcome.outcome
3465
- } : { handled: true };
3466
- };
3467
- if (hasNativeHookRelayInvocation({
3468
- relayId: nativeHookRelay.relayId,
3469
- event: "pre_tool_use",
3470
- toolUseId: params.context.approvalId
3471
- })) return resolveDeferredApproval();
3472
- try {
3473
- const decision = readNativeRelayPreToolUseDecision(await invokeNativeHookRelay({
3474
- provider: "codex",
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
- return await resolveDeferredApproval();
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
- return {
3495
- handled: true,
3496
- blocked: true,
3497
- reason: `OpenClaw native hook relay unavailable for Codex app-server approval: ${formatCodexDisplayText(coerceErrorMessage(error))}`,
3498
- failureDisposition: "failed"
3499
- };
3500
- }
3501
- }
3502
- function buildNativeRelayPreToolUsePayload(params) {
3503
- const command = readStringField(params.policyRequest.params, "command");
3504
- if (!command) return;
3505
- const turnId = readStringField(params.requestParams, "turnId");
3506
- return {
3507
- hook_event_name: "PreToolUse",
3508
- openclaw_approval_mode: "report",
3509
- tool_name: "exec_command",
3510
- ...params.context.approvalId ? { tool_use_id: params.context.approvalId } : {},
3511
- ...params.cwd ? { cwd: params.cwd } : {},
3512
- ...turnId ? { turn_id: turnId } : {},
3513
- tool_input: {
3514
- ...params.policyRequest.params,
3515
- command,
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 readNativeRelayPreToolUseDecision(response) {
3537
+ function readNativeRelayPreToolUseDenial(response) {
3521
3538
  if (!response || response.exitCode !== 0) return {
3522
- blocked: true,
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 { blocked: false };
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
- blocked: true,
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
- blocked: true,
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
- const granted = {};
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
- if (additionalPermissions) lines.push(...describePermissionProfile(additionalPermissions, "Additional permissions"));
3616
- const execpolicySummary = summarizeStringArray(requestParams?.proposedExecpolicyAmendment, "Proposed exec policy", sanitizeCodexApprovalVisibleText);
3617
- if (execpolicySummary) lines.push(execpolicySummary);
3618
- const networkAmendmentSummary = summarizeNetworkPolicyAmendments(requestParams?.proposedNetworkPolicyAmendments);
3619
- if (networkAmendmentSummary) lines.push(networkAmendmentSummary);
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
- if (isJsonObject(permissions.network)) kinds.push("network");
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
- })), summarizeFileSystemEntries(fileSystem, risks)].filter((summary) => Boolean(summary));
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 summarizeFileSystemEntries(permission, risks) {
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 path = typeof item?.path === "string" ? item.path.trim() : "";
3671
- const access = typeof item?.access === "string" ? item.access.trim() : "";
3672
- if (!path || !access) continue;
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 (access !== "none") for (const risk of permissionPathRisks(path)) risks.add(risk);
3675
- if (samples.length < PERMISSION_SAMPLE_LIMIT) samples.push(`${sanitizeCodexApprovalVisibleText(access)} ${sanitizePermissionPathValue(path)}`);
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 summarizeStringArray(value, label, sanitize) {
3696
+ function summarizeExecPolicyAmendment(value) {
3686
3697
  if (!Array.isArray(value)) return;
3687
- const values = value.filter((entry) => typeof entry === "string").map((entry) => sanitize(entry)).filter(Boolean);
3688
- return formatPermissionSamples(label, values.slice(0, PERMISSION_SAMPLE_LIMIT), values.length);
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
- const risks = [];
3723
- if (normalized.includes("*")) {
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
- const risks = [];
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
- if (wildcardStripped.startsWith("10.") || wildcardStripped.startsWith("127.") || wildcardStripped.startsWith("192.168.") || wildcardStripped.startsWith("169.254.")) return true;
3746
- return /^172\.(1[6-9]|2\d|3[0-1])\./.test(wildcardStripped);
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
- const actionCommands = readCommandActions(record);
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
- if (!Array.isArray(command)) return;
3775
+ return Array.isArray(command) ? readJoinedPreview(command, " ") : void 0;
3776
+ }
3777
+ function readJoinedPreview(parts, separator) {
3793
3778
  let source;
3794
- for (const part of command) {
3779
+ for (const part of parts) {
3795
3780
  if (typeof part !== "string") return;
3796
- source = appendPreviewPart(source, part, " ");
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 emitDynamicToolStartedDiagnostic(params) {
3861
- emitTrustedDiagnosticEvent({
3862
- type: "tool.execution.started",
3863
- ...diagnosticToolIdentity(params)
3864
- });
3865
- }
3866
- function emitDynamicToolErrorDiagnostic(params) {
3867
- emitTrustedDiagnosticEvent({
3868
- type: "tool.execution.error",
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.blocked",
3847
+ type: "tool.execution.error",
3889
3848
  ...diagnosticToolIdentity(params),
3890
- deniedReason: "plugin-before-tool-call",
3891
- reason: "Tool call blocked"
3849
+ durationMs,
3850
+ errorCategory: "codex_dynamic_tool_error",
3851
+ terminalReason
3892
3852
  });
3893
- return;
3894
- }
3895
- emitDynamicToolErrorDiagnostic({
3896
- ...params,
3897
- terminalReason: params.response.diagnosticTerminalReason ?? "failed"
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 appId = readFirstString(meta, PLUGIN_APP_ID_META_KEYS) ?? readFirstString(requestParams, PLUGIN_APP_ID_META_KEYS);
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 = readFirstString(meta, PLUGIN_NAME_META_KEYS) ?? readFirstString(requestParams, PLUGIN_NAME_META_KEYS);
4072
- const configKey = readFirstString(meta, PLUGIN_CONFIG_KEY_META_KEYS) ?? readFirstString(requestParams, PLUGIN_CONFIG_KEY_META_KEYS);
4073
- const marketplaceName = readFirstString(meta, PLUGIN_MARKETPLACE_NAME_META_KEYS) ?? readFirstString(requestParams, PLUGIN_MARKETPLACE_NAME_META_KEYS);
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: buildApprovalAllowedDecisions(requestedSchema, meta, source.kind === "mcp")
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
- const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
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
- const properties = isJsonObject(requestedSchema.properties) ? requestedSchema.properties : {};
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
- }).filter((line) => Boolean(line));
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).map((entry) => {
4235
- const param = isJsonObject(entry) ? entry : void 0;
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
- if (!name) return;
4239
- return `- ${name}: ${formatDisplayParamValue(param.value)}`;
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 (count >= MAX_DISPLAY_VALUE_OBJECT_KEYS) {
4268
- truncated = true;
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 [name, value] of Object.entries(properties)) {
4314
- const schema = isJsonObject(value) ? value : void 0;
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.required) return;
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 readApprovalFieldValue(property, outcome, persist) {
4332
- if (!isApprovalField(property)) return;
4333
- if (readNonBlankString(property.schema.type) === "boolean") return true;
4334
- const options = readEnumOptions(property.schema);
4335
- if (options.length === 0) return;
4336
- const acceptChoice = options.find((option) => isPositiveApprovalOption(option));
4337
- if (outcome === "approved-session") return options.find((option) => isPersistentApprovalOption(option, persist))?.value ?? acceptChoice?.value;
4338
- return acceptChoice?.value;
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
- if (options.length === 0) return;
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
- if (persistHints.includes("always")) return "always";
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) => optionMatchesPersist(option, "always"))?.value;
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.map((entry) => {
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
- if (!value) return;
4402
- return {
4351
+ return value ? [{
4403
4352
  value,
4404
4353
  label: readNonBlankString(option?.title) ?? value
4405
- };
4406
- }).filter((entry) => Boolean(entry));
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 (!descriptor.ok) {
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
- ok: false,
4594
- diagnostic: {
4595
- tool: name,
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)) invalidateComputerFrame(params.computerContextEpoch);
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 toolConfirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && !resultIsError && (rawResult.terminate === true || result.terminate === true);
5093
- const confirmedSourceReply = params.hookContext?.sourceReplyDeliveryMode === "message_tool_only" && toolName === "message" && (toolConfirmedSourceReply || deliveredSourceReply);
5094
- const sourceReplyFinal = confirmedSourceReply ? executedArgs.final !== false : void 0;
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 || toolConfirmedSourceReply) telemetry.didDeliverSourceReplyViaMessageTool = true;
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) invalidateComputerFrame(params.computerContextEpoch);
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
- return [...new Map(tools.map((tool) => [tool.tool, {
5209
- tool: tool.tool,
5210
- violations: tool.violations
5211
- }])).values()];
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 { emitDynamicToolStartedDiagnostic as a, ensureCodexSandboxExecServerEnvironment as c, CodexNativeProcessAuthority as d, readCodexRetainedBackgroundCommands as f, emitDynamicToolErrorDiagnostic as i, releaseCodexSandboxExecServerEnvironment as l, CodexNativeToolLifecycleProjector as m, projectCodexExecutableDynamicTools as n, emitDynamicToolTerminalDiagnostic as o, createCodexRemoteWorkspaceFileReader as p, routeCodexAppServerElicitationRequest as r, handleCodexAppServerApprovalRequest as s, createCodexDynamicToolBridge as t, createCodexNativePreToolUseFailureBuffer as u };
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 };