@openclaw/codex 2026.9.5 → 2026.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/assets/icon.png +0 -0
- package/dist/.setup/{account-usage-runtime-C2MOIvfq.mjs → account-usage-runtime-DFhbh1zz.mjs} +6 -6
- package/dist/.setup/{apply-CJ0ZfmBG.mjs → apply-D3GcSoSa.mjs} +35 -55
- package/dist/.setup/{attempt-client-cleanup-BpYKdTA0.mjs → attempt-client-cleanup-DOP-cW5j.mjs} +179 -73
- package/dist/.setup/attempt-workspace-context-C5HigshI.mjs +164 -0
- package/dist/.setup/{auth-binding-DhLWa_7K.mjs → auth-binding-BLWz7stm.mjs} +14 -1
- package/dist/.setup/{auth-cache-key-QirmgiCo.mjs → auth-cache-key-Dbvq-QgB.mjs} +41 -111
- package/dist/.setup/{auth-profile-WqZtZfXN.mjs → auth-profile-B0okYl2D.mjs} +1 -2
- package/dist/.setup/auth-profile-health-oHizqgye.mjs +53 -0
- package/dist/.setup/auth-profile-recovery-BREezV0L.mjs +14 -0
- package/dist/.setup/auth-start-options-BPIPeEX8.mjs +44 -0
- package/dist/.setup/{binding-connection-Bm38niE8.mjs → binding-connection-DTtN1KrX.mjs} +2 -2
- package/dist/.setup/{binding-connection.runtime-CnRMC0ta.mjs → binding-connection.runtime-6TpgD1M6.mjs} +5 -5
- package/dist/.setup/{bounded-turn-BtlZcivC.mjs → bounded-turn-CbjoN2OW.mjs} +140 -37
- package/dist/.setup/bounded-turn-isolation-B8K_ylqk.mjs +14 -0
- package/dist/.setup/{build-state-Xx9ybZqa.mjs → build-state-Da1_z0kd.mjs} +1 -1
- package/dist/.setup/{capabilities-_vXcZGSx.mjs → capabilities-B3WEZl4e.mjs} +2 -10
- package/dist/.setup/{client-vXs2929Q.mjs → client-N0JpgSzF.mjs} +635 -872
- package/dist/.setup/client-catalog-response-5HHLdcde.mjs +285 -0
- package/dist/.setup/{command-dispatch-naHIZwwF.mjs → command-dispatch-BeAoaovI.mjs} +9 -8
- package/dist/.setup/{command-formatters-CGEkdawF.mjs → command-formatters-R3ckE3Co.mjs} +36 -64
- package/dist/.setup/{command-handlers-BtAJEW1X.mjs → command-handlers-ylTMS00z.mjs} +301 -488
- package/dist/.setup/{command-rpc-DHFpn4le.mjs → command-rpc-CGJGUQ6Y.mjs} +40 -28
- package/dist/.setup/compact-C2WamavS.mjs +776 -0
- package/dist/.setup/{config-CedDWjM-.mjs → config-DKhXquav.mjs} +26 -28
- package/dist/.setup/{config-options-BsanJ8QG.mjs → config-options-CzyWLTJt.mjs} +68 -65
- package/dist/.setup/{config-parsing-CcB9iPoq.mjs → config-parsing-b088LyhT.mjs} +57 -60
- package/dist/.setup/{config-runtime-CeAhl_h4.mjs → config-runtime-C0NbLms1.mjs} +2 -2
- package/dist/.setup/{config-security-BRQ2mgW9.mjs → config-security-CE-FcGw9.mjs} +51 -215
- package/dist/.setup/{config-utils-DujwEnhg.mjs → config-utils-D0CXecki.mjs} +3 -13
- package/dist/.setup/context-engine-projection-PezSESth.mjs +387 -0
- package/dist/.setup/{conversation-binding-DhzrpTM6.mjs → conversation-binding-3Min8qvP.mjs} +71 -105
- package/dist/.setup/{conversation-binding-data-CrcyZk6O.mjs → conversation-binding-data-C746z5Pn.mjs} +8 -12
- package/dist/.setup/{conversation-control-DN53ns41.mjs → conversation-control-CEP8X5JC.mjs} +30 -69
- package/dist/.setup/{cyber-failover-DYwbmf1o.mjs → cyber-failover-XV0nNRZu.mjs} +1 -1
- package/dist/.setup/{cyber-failover-notice-e9G3J9-e.mjs → cyber-failover-notice-RNpGpTyP.mjs} +1 -1
- package/dist/.setup/{dynamic-tools-D16lOp67.mjs → dynamic-tools-DzlsLUuS.mjs} +3136 -3167
- package/dist/.setup/{effective-mcp-catalog-K6kLCN8X.mjs → effective-mcp-catalog-CxUtC-xG.mjs} +8 -17
- package/dist/.setup/{ephemeral-turn-R1gA8Sn6.mjs → ephemeral-turn-CDsPJOa2.mjs} +9 -6
- package/dist/.setup/event-projector-assistant-message-CigreO4K.mjs +69 -0
- package/dist/.setup/{event-projector-usage-C7Ntzs-f.mjs → event-projector-usage-03R0I2dM.mjs} +4 -26
- package/dist/.setup/event-projector-values-DJSz8BTk.mjs +106 -0
- package/dist/.setup/incognito-session-DWYapfwf.mjs +2 -0
- package/dist/.setup/{inference-context-fulhZuz8.mjs → inference-context-D-9gWlTR.mjs} +16 -18
- package/dist/.setup/inference-dispatch-CMZtjQyz.mjs +359 -0
- package/dist/.setup/inference-metadata-CgIf7sOx.mjs +81 -0
- package/dist/.setup/inference-proxy-DhcXOgUE.mjs +561 -0
- package/dist/.setup/{isolated-completion-B6srt48J.mjs → isolated-completion-Dm3PzxJk.mjs} +15 -11
- package/dist/.setup/launch-args-DbFCehO7.mjs +196 -0
- package/dist/.setup/{managed-binary-DP6Rib3P.mjs → managed-binary-kiWRhNRE.mjs} +16 -9
- package/dist/.setup/{model-catalog-DkvUuBey.mjs → model-catalog-PAuZgJC6.mjs} +14 -14
- package/dist/.setup/model-runtime-BdqzOMkU.mjs +11 -0
- package/dist/.setup/{models-BDFgm_G-.mjs → models-DHXZtVVk.mjs} +9 -18
- package/dist/.setup/{native-auth-BLZGqFjw.mjs → native-auth-CuG53tu4.mjs} +5 -5
- package/dist/.setup/{native-execution-policy-YBMHSzC4.mjs → native-execution-policy-BxZJy5mP.mjs} +6 -31
- package/dist/.setup/{native-subagent-monitor-B1hxzIOy.mjs → native-subagent-monitor-Dpsmf2wz.mjs} +4422 -3808
- package/dist/.setup/native-subagent-pending-assignments-DwkCzLvD.mjs +203 -0
- package/dist/.setup/native-task-assignments-DMQ0g3hu.mjs +194 -0
- package/dist/.setup/{node-cli-sessions-Cl8b2ZF3.mjs → node-cli-sessions-HC4W6FLI.mjs} +99 -111
- package/dist/.setup/node-exec-server.runtime-BgBfWQqo.mjs +244 -0
- package/dist/.setup/{plan-xD7WKYTM.mjs → plan-Y-QIvrKB.mjs} +118 -163
- package/dist/.setup/{plugin-activation-r8nq5Jzp.mjs → plugin-activation-cNHRfaxU.mjs} +10 -47
- package/dist/.setup/{plugin-app-cache-key-BOxKP7db.mjs → plugin-app-cache-key-BL9B5H0L.mjs} +9 -42
- package/dist/.setup/{plugin-inventory-DUle8DLz.mjs → plugin-inventory-Cky2XJUw.mjs} +32 -23
- package/dist/.setup/{plugin-marketplace-discovery-P-Vndfqq.mjs → plugin-marketplace-discovery-D_dV9FXa.mjs} +22 -25
- package/dist/.setup/{plugin-metadata-cache-DsS5buNa.mjs → plugin-metadata-cache-DsjsL1dr.mjs} +328 -318
- package/dist/.setup/protocol-CANUwXJ3.mjs +17 -0
- package/dist/.setup/provider-DH8NSzGt.mjs +51 -0
- package/dist/.setup/{provider-capabilities-B8J4Dy-O.mjs → provider-capabilities-CAxoF-X1.mjs} +35 -12
- package/dist/.setup/{rate-limits-DOEGYRNs.mjs → rate-limits-B7-X2TgG.mjs} +123 -166
- package/dist/.setup/{request-BioXJLYa.mjs → request-BHZ1nz8d.mjs} +25 -16
- package/dist/.setup/responses-oauth-twnuTaAa.mjs +93 -0
- package/dist/.setup/rpc-error-BlD2vuzn.mjs +31 -0
- package/dist/.setup/{run-attempt-rlA07pUv.mjs → run-attempt-D8RZUOzY.mjs} +2046 -1454
- package/dist/.setup/{run-attempt-lifecycle-B7JbY4-F.mjs → run-attempt-lifecycle-C-6O3Exn.mjs} +10 -24
- package/dist/.setup/{runtime-artifact-Dy38Z5iU.mjs → runtime-artifact-DDQ-HGXf.mjs} +43 -47
- package/dist/.setup/runtime-artifact-connection-D68qQn1m.mjs +78 -0
- package/dist/.setup/sandbox-guard-DjCz2eOw.mjs +126 -0
- package/dist/.setup/scope-DOC1Xjc8.mjs +6 -0
- package/dist/.setup/session-binding-DGCYAfvD.mjs +443 -0
- package/dist/.setup/{session-binding-record-TH3xA0zj.mjs → session-binding-record-Oe9L-CNY.mjs} +73 -132
- package/dist/.setup/{session-binding-sidecars-CKYEcfNn.mjs → session-binding-sidecars-jo6H8dFr.mjs} +242 -60
- package/dist/.setup/{session-catalog-fUdWDx-U.mjs → session-catalog-4sCdv5qx.mjs} +598 -452
- package/dist/.setup/{session-catalog-adoption-CR_whkEW.mjs → session-catalog-adoption-BU_pHWG7.mjs} +59 -26
- package/dist/.setup/{session-catalog-archive-CwZaPacf.mjs → session-catalog-archive-spHfihri.mjs} +7 -7
- package/dist/.setup/session-catalog-events-DePDzzjc.mjs +139 -0
- package/dist/.setup/session-catalog-home-id-vlih0B_g.mjs +22 -0
- package/dist/.setup/{session-catalog-index-B141u15s.mjs → session-catalog-index-CZvDzC6z.mjs} +286 -182
- package/dist/.setup/{session-catalog-index-order-Cc5BzlQr.mjs → session-catalog-index-order-BcQpHks3.mjs} +2 -2
- package/dist/.setup/{session-catalog-index-state-BAuDztHN.mjs → session-catalog-index-state-TeO7EyO-.mjs} +6 -2
- package/dist/.setup/session-catalog-limits-DDxOsQeC.mjs +14 -0
- package/dist/.setup/session-catalog-list-request-CvtxrYSe.mjs +170 -0
- package/dist/.setup/session-catalog-native-projection-DjhwGL6x.mjs +178 -0
- package/dist/.setup/{session-catalog-node-adoption-BVRsYOSh.mjs → session-catalog-node-adoption-DgXvSGu0.mjs} +30 -35
- package/dist/.setup/{session-catalog-node-continue-Dowv8osw.mjs → session-catalog-node-continue-DjKXHBbi.mjs} +25 -18
- package/dist/.setup/session-catalog-node-lookup-CtAXI5W5.mjs +56 -0
- package/dist/.setup/{session-catalog-native-projection-ChV3cCtm.mjs → session-catalog-parsing-CPGaPYr-.mjs} +14 -180
- package/dist/.setup/{session-catalog-projection-C1Bj9Jl-.mjs → session-catalog-projection-Cr5qP5Dj.mjs} +15 -5
- package/dist/.setup/session-catalog-provenance-DoAvktcj.mjs +95 -0
- package/dist/.setup/{session-cli-DIj-IE-E.mjs → session-cli-Cn_nCqfq.mjs} +11 -24
- package/dist/.setup/{session-history-C--SrLI8.mjs → session-history-D83lJWHG.mjs} +16 -23
- package/dist/.setup/{session-history-import-JHXT5Xfi.mjs → session-history-import-WhX-X33a.mjs} +7 -10
- package/dist/.setup/session-history-read-Ov3StuL4.mjs +133 -0
- package/dist/.setup/{session-history.worker-CpNj7H_P.mjs → session-history.worker-MnPKncqP.mjs} +14 -23
- package/dist/.setup/session-initialization-6aveB9qb.mjs +22 -0
- package/dist/.setup/{session-permission-policy-DhurXY0l.mjs → session-permission-policy-Ci_fVX6y.mjs} +4 -8
- package/dist/.setup/{session-retirement-CU87AOg9.mjs → session-retirement-qMTxkNx2.mjs} +31 -12
- package/dist/.setup/{settled-turn-context-fmwouT5I.mjs → settled-turn-context-D4yj4rxf.mjs} +1 -1
- package/dist/.setup/{settled-turn-finalizer-CiY2bLFK.mjs → settled-turn-finalizer-BsvEabkw.mjs} +17 -13
- package/dist/.setup/{shared-client-BpTtuLQ5.mjs → shared-client-DqyTMYug.mjs} +826 -485
- package/dist/.setup/shared-client-lifecycle-DJIgqgMw.mjs +153 -0
- package/dist/.setup/{side-question-DSCZyoBD.mjs → side-question-C4kCmQy1.mjs} +206 -334
- package/dist/.setup/{source-Bjp4UQij.mjs → source-CS_7GFJZ.mjs} +82 -139
- package/dist/.setup/spawn-error-CL3n6MAa.mjs +57 -0
- package/dist/.setup/{thread-archive-guard-jmecf6OJ.mjs → thread-archive-guard-C9frICzX.mjs} +10 -3
- package/dist/.setup/thread-lifecycle-DPflntEJ.mjs +2362 -0
- package/dist/.setup/{incognito-session-uhrBF6wJ.mjs → thread-ownership-queue-Dp24d7-F.mjs} +1 -2
- package/dist/.setup/{thread-ownership-skomxnIt.mjs → thread-ownership-roNP0l1c.mjs} +47 -10
- package/dist/.setup/thread-policy-DTd-DtkX.mjs +109 -0
- package/dist/.setup/thread-requests-B_bRnMGN.mjs +2102 -0
- package/dist/.setup/{thread-resume-BkNpj9Pz.mjs → thread-resume-CdDaoc7T.mjs} +6 -6
- package/dist/.setup/timeout-CXOfNoaX.mjs +46 -0
- package/dist/.setup/transcript-history-projection-DRSifI-1.mjs +201 -0
- package/dist/.setup/{transcript-mirror-attestation-CzM-94NC.mjs → transcript-mirror-attestation-YSegaktH.mjs} +3 -4
- package/dist/.setup/{transcript-mirror-D4qp_leP.mjs → transcript-mirror-kxmCYsuF.mjs} +224 -206
- package/dist/.setup/{transport-stdio-Cmx94Oiy.mjs → transport-stdio-Bdegpn2V.mjs} +138 -75
- package/dist/.setup/transport-websocket-BTBE-LD9.mjs +211 -0
- package/dist/.setup/{upstream-prompt-provenance-LphB8slG.mjs → upstream-prompt-provenance-fXkKbPVC.mjs} +3 -9
- package/dist/.setup/{upstream-session-fork-D3UKxgVD.mjs → upstream-session-fork-CBeVjBpT.mjs} +48 -40
- package/dist/.setup/{usage-BOgBEKso.mjs → usage-BMzNxeR_.mjs} +5 -4
- package/dist/.setup/{usage-limit-error-C0ow7DOc.mjs → usage-limit-error-QXerDo3i.mjs} +5 -20
- package/dist/.setup/{web-search-provider.runtime-CTGuJt6i.mjs → web-search-provider.runtime-P77OUeoo.mjs} +10 -20
- package/dist/api.js +25 -16
- package/dist/catalog-page-worker-entrypoint.js +12 -0
- package/dist/catalog-page.worker.js +9 -0
- package/dist/cli-metadata.js +1 -1
- package/dist/doctor-contract-api.js +50 -8
- package/dist/harness.js +344 -1
- package/dist/index.js +345 -346
- package/dist/media-understanding-provider.js +9 -22
- package/dist/migration-provider-api.js +1 -1
- package/dist/provider-discovery.js +1 -1
- package/dist/session-history-worker-runtime.js +16 -33
- package/dist/session-history.worker.js +1 -1
- package/openclaw.plugin.json +14 -2
- package/package.json +11 -7
- package/dist/.setup/attempt-context-VXVwTCfS.mjs +0 -636
- package/dist/.setup/compact-C3i3qlzj.mjs +0 -687
- package/dist/.setup/event-projector-assistant-message-DNUR1s0A.mjs +0 -105
- package/dist/.setup/event-projector-items-DyTQ_XqL.mjs +0 -200
- package/dist/.setup/harness-rxaaoN-o.mjs +0 -349
- package/dist/.setup/history-rejection-B4nOXNXF.mjs +0 -13
- package/dist/.setup/image-payload-sanitizer-tupAr-2o.mjs +0 -79
- package/dist/.setup/inference-proxy-CC15HuSi.mjs +0 -311
- package/dist/.setup/launch-args-DnMak49W.mjs +0 -111
- package/dist/.setup/local-runtime-attribution-B_2mnqhb.mjs +0 -19
- package/dist/.setup/native-subagent-history-YXaL6dMd.mjs +0 -135
- package/dist/.setup/native-subagent-task-ids-DsuOonKv.mjs +0 -411
- package/dist/.setup/node-exec-server.runtime-BaViBKNz.mjs +0 -226
- package/dist/.setup/plugin-approval-roundtrip-CjfzafAZ.mjs +0 -122
- package/dist/.setup/provider-fFh_-VR3.mjs +0 -50
- package/dist/.setup/rolldown-runtime-8H4AJuhK.mjs +0 -14
- package/dist/.setup/sandbox-guard-BBP-RbTf.mjs +0 -166
- package/dist/.setup/session-binding-_0W_fHRr.mjs +0 -721
- package/dist/.setup/session-catalog-events-jMcYfQyd.mjs +0 -250
- package/dist/.setup/session-history-read-B3ZfSUbF.mjs +0 -50
- package/dist/.setup/session-initialization-DH1z6fXn.mjs +0 -75
- package/dist/.setup/thread-history-page-BTx-4xsg.mjs +0 -83
- package/dist/.setup/thread-lifecycle-Cik28qWn.mjs +0 -4904
- package/dist/.setup/thread-policy-Cjs4B8xf.mjs +0 -54
- package/dist/.setup/timeout-DZF05XrR.mjs +0 -63
- package/dist/.setup/transcript-history-projection-DHNBDhCU.mjs +0 -243
- package/dist/.setup/version-CoqzXPgK.mjs +0 -12
|
@@ -0,0 +1,2102 @@
|
|
|
1
|
+
import { l as toCodexPluginOwnedAccountApp, s as readCodexPluginInventory } from "./plugin-inventory-Cky2XJUw.mjs";
|
|
2
|
+
import { i as isJsonObject } from "./protocol-CANUwXJ3.mjs";
|
|
3
|
+
import { u as stringifyCodexPolicy } from "./config-security-CE-FcGw9.mjs";
|
|
4
|
+
import { f as resolveCodexPluginsPolicy } from "./config-parsing-b088LyhT.mjs";
|
|
5
|
+
import { n as resolveCodexAppServerLocalHomeDir } from "./auth-start-options-BPIPeEX8.mjs";
|
|
6
|
+
import { d as readCodexEffectiveConfig, u as CODEX_SESSION_OVERRIDABLE_LAYER_TYPES } from "./config-options-CzyWLTJt.mjs";
|
|
7
|
+
import { t as isIncognitoSessionKey } from "./incognito-session-DWYapfwf.mjs";
|
|
8
|
+
import { a as defaultCodexAppInventoryCache, i as CodexAppInventoryCache, o as serializeCodexAppInventoryError, s as codexAppIdentityKey, t as buildCodexAppServerConnectionFingerprint } from "./plugin-app-cache-key-BL9B5H0L.mjs";
|
|
9
|
+
import "./config-DKhXquav.mjs";
|
|
10
|
+
import { t as isCodexAppServerNativeAuthProfile } from "./auth-profile-B0okYl2D.mjs";
|
|
11
|
+
import { D as nativeHookRelayUnregisterQueue, W as readCodexClientSessionMeta } from "./shared-client-DqyTMYug.mjs";
|
|
12
|
+
import { a as isCodexResponsesOAuthRun, i as isCodexResponsesOAuthCredential, t as CODEX_RESPONSES_OAUTH_PROVIDER } from "./responses-oauth-twnuTaAa.mjs";
|
|
13
|
+
import { B as resolveCodexToolAbortTerminalReason } from "./client-N0JpgSzF.mjs";
|
|
14
|
+
import { n as CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS, p as unsubscribeCodexThreadBestEffort } from "./attempt-client-cleanup-DOP-cW5j.mjs";
|
|
15
|
+
import { t as ensureCodexPluginActivation } from "./plugin-activation-cNHRfaxU.mjs";
|
|
16
|
+
import { r as hashCodexAppServerBindingFingerprint } from "./session-binding-DGCYAfvD.mjs";
|
|
17
|
+
import { t as codexNativeSubagentMonitorRuntime } from "./native-subagent-monitor-Dpsmf2wz.mjs";
|
|
18
|
+
import { normalizeOptionalString, normalizeUniqueStringEntries, normalizeUniqueTrimmedStringList } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
19
|
+
import * as crypto$1 from "node:crypto";
|
|
20
|
+
import crypto, { createHash } from "node:crypto";
|
|
21
|
+
import { addTimerTimeoutGraceMs, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import os from "node:os";
|
|
24
|
+
import fs from "node:fs/promises";
|
|
25
|
+
import { toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
|
26
|
+
import { isPathInside } from "openclaw/plugin-sdk/file-access-runtime";
|
|
27
|
+
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
28
|
+
import { resolveRequiredHomeDir, resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
29
|
+
import { emitTrustedDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
30
|
+
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
31
|
+
import { SKILL_WORKSHOP_TOOL_NAME, buildCredentialSafetyPrompt, buildDelegationGuidanceSection, buildSkillWorkshopPromptSection, buildUiPresentationPrompt, embeddedAgentLog, isHostScopedAgentToolActive, resolveMainSessionDelegationMode } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
32
|
+
import { registerNativeHookRelayForBundledRuntime } from "openclaw/plugin-sdk/native-hook-relay-runtime";
|
|
33
|
+
import { listRegisteredPluginAgentPromptGuidance } from "openclaw/plugin-sdk/plugin-runtime";
|
|
34
|
+
import { buildHostnameAllowlistPolicyFromSuffixAllowlist } from "openclaw/plugin-sdk/ssrf-policy";
|
|
35
|
+
//#region extensions/codex/src/app-server/plugin-app-approval-overrides.ts
|
|
36
|
+
/** Projects ask approvals into the native session layer without changing saved settings. */
|
|
37
|
+
function buildCodexAppApprovalOverrides(config, app) {
|
|
38
|
+
const appsRoot = config.apps;
|
|
39
|
+
const appConfig = isJsonObject(appsRoot) ? appsRoot[app.id] : void 0;
|
|
40
|
+
if (!isJsonObject(appConfig)) return {};
|
|
41
|
+
const overrides = {};
|
|
42
|
+
const keys = app.approvalOverrideToolConfigKeys;
|
|
43
|
+
for (const [section, fields] of [["tools", { approval_mode: "auto" }], ["links", {
|
|
44
|
+
approvals_reviewer: "user",
|
|
45
|
+
default_tools_approval_mode: "auto"
|
|
46
|
+
}]]) {
|
|
47
|
+
const entries = appConfig[section];
|
|
48
|
+
if (!isJsonObject(entries)) continue;
|
|
49
|
+
const projected = [];
|
|
50
|
+
for (const [name, value] of Object.entries(entries).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
51
|
+
if (!isJsonObject(value) || section === "tools" && keys && !keys.includes(name)) continue;
|
|
52
|
+
if (Object.keys(fields).some((field) => value[field] !== void 0 && value[field] !== null)) projected.push([name, fields]);
|
|
53
|
+
}
|
|
54
|
+
if (projected.length > 0) overrides[section] = Object.fromEntries(projected);
|
|
55
|
+
}
|
|
56
|
+
return overrides;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region extensions/codex/src/app-server/plugin-thread-app-admission.ts
|
|
60
|
+
function resolveCodexPluginThreadAppCacheKey(params) {
|
|
61
|
+
return params.threadId ? `${params.appCacheKey}:thread:${encodeURIComponent(params.threadId)}` : params.appCacheKey;
|
|
62
|
+
}
|
|
63
|
+
function createCodexPluginThreadAppInventoryRequest(params) {
|
|
64
|
+
return async (method, requestParams) => await params.request(method, params.threadId ? {
|
|
65
|
+
...requestParams,
|
|
66
|
+
threadId: params.threadId
|
|
67
|
+
} : requestParams);
|
|
68
|
+
}
|
|
69
|
+
async function refreshCodexPluginAppInventory(params, appCache, options = {}) {
|
|
70
|
+
if (!params.appCacheKey) return;
|
|
71
|
+
const request = createCodexPluginThreadAppInventoryRequest(params);
|
|
72
|
+
try {
|
|
73
|
+
return await appCache.refreshNow({
|
|
74
|
+
key: resolveCodexPluginThreadAppCacheKey(params),
|
|
75
|
+
request,
|
|
76
|
+
nowMs: params.nowMs,
|
|
77
|
+
forceRefetch: options.forceRefetch,
|
|
78
|
+
targetAppIds: options.targetAppIds
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
embeddedAgentLog.warn("codex plugin thread config app inventory refresh failed", {
|
|
82
|
+
reason: options.reason,
|
|
83
|
+
forceRefetch: options.forceRefetch === true,
|
|
84
|
+
error: serializeCodexAppInventoryError(error)
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function collectCodexPluginOwnedAppIds(inventory) {
|
|
90
|
+
return Array.from(new Set(inventory.records.flatMap((record) => record.ownedAppIds).filter(Boolean))).toSorted();
|
|
91
|
+
}
|
|
92
|
+
function collectCodexReservedPluginAppIds(params) {
|
|
93
|
+
const reserved = new Set(params.inventory.records.flatMap((record) => record.appOwnership === "proven" ? record.ownedAppIds : []).map(codexAppIdentityKey));
|
|
94
|
+
const recordsByConfigKey = new Map(params.inventory.records.map((record) => [record.policy.configKey, record]));
|
|
95
|
+
const configuredOwnerNames = new Set(params.policy.pluginPolicies.flatMap((policy) => {
|
|
96
|
+
const record = recordsByConfigKey.get(policy.configKey);
|
|
97
|
+
return [
|
|
98
|
+
policy.configKey,
|
|
99
|
+
policy.pluginName,
|
|
100
|
+
record?.summary.name,
|
|
101
|
+
record?.summary.id
|
|
102
|
+
].filter((name) => Boolean(name)).map(normalizeCodexPluginOwnerName);
|
|
103
|
+
}));
|
|
104
|
+
for (const app of params.accountApps) if (app.pluginDisplayNames.some((name) => configuredOwnerNames.has(normalizeCodexPluginOwnerName(name)))) reserved.add(codexAppIdentityKey(app.id));
|
|
105
|
+
return reserved;
|
|
106
|
+
}
|
|
107
|
+
function normalizeCodexPluginOwnerName(name) {
|
|
108
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
109
|
+
}
|
|
110
|
+
async function readCodexThreadAdmissibleAccountApps(params, appCache) {
|
|
111
|
+
const request = createCodexPluginThreadAppInventoryRequest(params);
|
|
112
|
+
const cachedInventory = appCache.read({
|
|
113
|
+
key: resolveCodexPluginThreadAppCacheKey(params),
|
|
114
|
+
request,
|
|
115
|
+
nowMs: params.nowMs,
|
|
116
|
+
suppressRefresh: true
|
|
117
|
+
});
|
|
118
|
+
const snapshot = cachedInventory.state === "fresh" && !cachedInventory.snapshot?.targetAppIds?.length ? cachedInventory.snapshot : await refreshCodexPluginAppInventory(params, appCache, {
|
|
119
|
+
forceRefetch: false,
|
|
120
|
+
reason: "account_apps_all",
|
|
121
|
+
targetAppIds: []
|
|
122
|
+
});
|
|
123
|
+
if (!snapshot) return {
|
|
124
|
+
apps: [],
|
|
125
|
+
installedApps: [],
|
|
126
|
+
diagnostic: {
|
|
127
|
+
code: "account_app_inventory_unavailable",
|
|
128
|
+
message: "Codex account app inventory was unavailable; account apps were not exposed."
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const installedAppsById = new Map(snapshot.installedApps.map((app) => [app.id, app]));
|
|
132
|
+
return {
|
|
133
|
+
apps: snapshot.apps.filter((app) => resolveCodexInstalledAppThreadAdmission(toCodexPluginOwnedAccountApp(app, installedAppsById.get(app.id)), installedAppsById.get(app.id)) !== "blocked").toSorted((left, right) => left.id.localeCompare(right.id)),
|
|
134
|
+
installedApps: snapshot.installedApps
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function resolveCodexPluginAppThreadAdmission(app, inventory) {
|
|
138
|
+
const snapshot = inventory.appInventory?.snapshot;
|
|
139
|
+
if (!snapshot) return "blocked";
|
|
140
|
+
return resolveCodexInstalledAppThreadAdmission(app, snapshot.installedApps.find((candidate) => candidate.id === app.id));
|
|
141
|
+
}
|
|
142
|
+
function resolveCodexInstalledAppThreadAdmission(app, installed) {
|
|
143
|
+
if (!app.accessible || app.needsAuth || !installed) return "blocked";
|
|
144
|
+
if (installed.enabled && installed.callable) return "ready";
|
|
145
|
+
return !installed.enabled && !installed.callable ? "provisional" : "blocked";
|
|
146
|
+
}
|
|
147
|
+
async function readCodexConfigForAppAdmission(params) {
|
|
148
|
+
try {
|
|
149
|
+
const response = await params.request("config/read", {
|
|
150
|
+
includeLayers: true,
|
|
151
|
+
...params.configCwd ? { cwd: params.configCwd } : {}
|
|
152
|
+
});
|
|
153
|
+
if (!isJsonObject(response) || !isJsonObject(response.config) || !Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config or config layers");
|
|
154
|
+
return {
|
|
155
|
+
config: response.config,
|
|
156
|
+
layers: response.layers.flatMap((layer) => {
|
|
157
|
+
if (!isJsonObject(layer)) throw new Error("Codex config/read returned an invalid config layer");
|
|
158
|
+
if (layer.disabledReason !== void 0 && layer.disabledReason !== null) {
|
|
159
|
+
if (typeof layer.disabledReason !== "string") throw new Error("Codex config/read returned an invalid disabled layer");
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
if (!isJsonObject(layer.config)) throw new Error("Codex config/read returned an invalid layer config");
|
|
163
|
+
if (!isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned an invalid config layer source");
|
|
164
|
+
if (layer.config.apps !== void 0 && !CODEX_SESSION_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex app policy cannot override ${layer.name.type}; move app settings to a supported user or project config layer before exposing native apps`);
|
|
165
|
+
return [layer.config];
|
|
166
|
+
})
|
|
167
|
+
};
|
|
168
|
+
} catch (error) {
|
|
169
|
+
const details = serializeCodexAppInventoryError(error);
|
|
170
|
+
embeddedAgentLog.warn("codex plugin app admission config read failed", { error: details });
|
|
171
|
+
throw new Error(`Could not verify the Codex app allowlist: ${String(details.message)}. No native thread was started; resolve the native configuration error and retry.`, { cause: error });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function resolveCodexExplicitAppEnablement(layersHighestPrecedenceFirst, appId) {
|
|
175
|
+
for (const layer of layersHighestPrecedenceFirst) {
|
|
176
|
+
const apps = layer.apps;
|
|
177
|
+
const values = isJsonObject(apps) ? Object.entries(apps).filter(([id, app]) => codexAppIdentityKey(id) === codexAppIdentityKey(appId) && isJsonObject(app) && Object.hasOwn(app, "enabled")).map(([, app]) => isJsonObject(app) && app.enabled === true) : [];
|
|
178
|
+
if (values.length > 0) return values.every(Boolean);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory) {
|
|
182
|
+
if (!params.appCacheKey || !policy.pluginPolicies.some((plugin) => plugin.enabled) || inventory.appInventory?.state === "missing") return false;
|
|
183
|
+
return inventory.records.some((record) => record.appOwnership === "proven" && record.ownedAppIds.length > 0 && (record.apps.length === 0 || record.apps.some((app) => !app.accessible)));
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region extensions/codex/src/app-server/plugin-thread-config.ts
|
|
187
|
+
const CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION = 15;
|
|
188
|
+
const CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION = 2;
|
|
189
|
+
function shouldBuildCodexPluginThreadConfig(pluginConfig) {
|
|
190
|
+
return resolveCodexPluginsPolicy(pluginConfig).configured;
|
|
191
|
+
}
|
|
192
|
+
/** Fingerprints policy and app-cache identity before runtime inventory is read. */
|
|
193
|
+
function buildCodexPluginThreadConfigInputFingerprint(params) {
|
|
194
|
+
const policy = resolveCodexPluginsPolicy(params.pluginConfig);
|
|
195
|
+
return fingerprintJson({
|
|
196
|
+
version: CODEX_PLUGIN_THREAD_CONFIG_INPUT_FINGERPRINT_VERSION,
|
|
197
|
+
policy: policyFingerprint(policy),
|
|
198
|
+
appCacheKey: params.appCacheKey ?? null
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/** Builds the deny-all app patch used when plugin discovery exceeds its turn budget. */
|
|
202
|
+
function buildCodexPluginThreadConfigTimeoutFallback(params) {
|
|
203
|
+
return {
|
|
204
|
+
...emptyPluginThreadConfig({
|
|
205
|
+
enabled: true,
|
|
206
|
+
inputFingerprint: buildCodexPluginThreadConfigInputFingerprint(params),
|
|
207
|
+
configPatch: buildDisabledAppsConfigPatch()
|
|
208
|
+
}),
|
|
209
|
+
diagnostics: [{
|
|
210
|
+
code: "plugin_config_timeout",
|
|
211
|
+
message: params.message
|
|
212
|
+
}]
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
async function buildCodexPluginThreadConfig(params) {
|
|
216
|
+
const appCache = params.appCache ?? defaultCodexAppInventoryCache;
|
|
217
|
+
const threadAppCacheKey = resolveCodexPluginThreadAppCacheKey(params);
|
|
218
|
+
const threadRequest = (method, requestParams) => params.request(method, (method === "app/installed" || method === "app/read") && params.threadId && isJsonObject(requestParams) ? {
|
|
219
|
+
...requestParams,
|
|
220
|
+
threadId: params.threadId
|
|
221
|
+
} : requestParams);
|
|
222
|
+
let inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
223
|
+
pluginConfig: params.pluginConfig,
|
|
224
|
+
appCacheKey: params.appCacheKey
|
|
225
|
+
});
|
|
226
|
+
const policy = resolveCodexPluginsPolicy(params.pluginConfig);
|
|
227
|
+
if (!policy.enabled) return emptyPluginThreadConfig({
|
|
228
|
+
enabled: false,
|
|
229
|
+
inputFingerprint,
|
|
230
|
+
configPatch: buildDisabledAppsConfigPatch()
|
|
231
|
+
});
|
|
232
|
+
const readInventory = (suppressAppInventoryRefresh) => readCodexPluginInventory({
|
|
233
|
+
pluginConfig: params.pluginConfig,
|
|
234
|
+
policy,
|
|
235
|
+
request: threadRequest,
|
|
236
|
+
appCache,
|
|
237
|
+
appCacheKey: params.appCacheKey,
|
|
238
|
+
appInventoryCacheKey: threadAppCacheKey,
|
|
239
|
+
configCwd: params.configCwd,
|
|
240
|
+
metadataCache: params.metadataCache,
|
|
241
|
+
nowMs: params.nowMs,
|
|
242
|
+
suppressAppInventoryRefresh
|
|
243
|
+
});
|
|
244
|
+
let inventory = policy.pluginPolicies.length > 0 ? await readInventory(true) : {
|
|
245
|
+
policy,
|
|
246
|
+
records: [],
|
|
247
|
+
diagnostics: []
|
|
248
|
+
};
|
|
249
|
+
const refreshInventory = async (options) => {
|
|
250
|
+
await refreshCodexPluginAppInventory(params, appCache, {
|
|
251
|
+
...options,
|
|
252
|
+
targetAppIds: collectCodexPluginOwnedAppIds(inventory)
|
|
253
|
+
});
|
|
254
|
+
inventory = await readInventory();
|
|
255
|
+
inputFingerprint = buildCodexPluginThreadConfigInputFingerprint({
|
|
256
|
+
pluginConfig: params.pluginConfig,
|
|
257
|
+
appCacheKey: params.appCacheKey
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
const activationRequired = inventory.records.some((record) => record.activationRequired);
|
|
261
|
+
const appInventoryMissing = shouldRefreshMissingAppInventory(params, policy, inventory);
|
|
262
|
+
const appInventoryRefreshDeferredForActivation = activationRequired && appInventoryMissing;
|
|
263
|
+
if (!activationRequired && appInventoryMissing) await refreshInventory({
|
|
264
|
+
forceRefetch: false,
|
|
265
|
+
reason: "initial_missing"
|
|
266
|
+
});
|
|
267
|
+
const activationDiagnostics = [];
|
|
268
|
+
const activationResults = [];
|
|
269
|
+
for (const record of inventory.records) {
|
|
270
|
+
if (!record.activationRequired) continue;
|
|
271
|
+
const activation = await ensureCodexPluginActivation({
|
|
272
|
+
identity: record.policy,
|
|
273
|
+
request: threadRequest,
|
|
274
|
+
appCache,
|
|
275
|
+
appCacheKey: params.appCacheKey,
|
|
276
|
+
appInventoryCacheKey: threadAppCacheKey,
|
|
277
|
+
configCwd: params.configCwd,
|
|
278
|
+
metadataCache: params.metadataCache,
|
|
279
|
+
deferAppInventoryRefresh: true,
|
|
280
|
+
targetAppIds: record.ownedAppIds
|
|
281
|
+
});
|
|
282
|
+
activationResults.push(activation);
|
|
283
|
+
if (!activation.ok) activationDiagnostics.push({
|
|
284
|
+
code: "plugin_activation_failed",
|
|
285
|
+
plugin: record.policy,
|
|
286
|
+
message: activation.diagnostics.map((item) => item.message).join(" ") || activation.reason
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const postInstallRefreshRequired = activationResults.some((activation) => activation.ok && activation.installAttempted);
|
|
290
|
+
const deferredMissingRefreshRequired = appInventoryRefreshDeferredForActivation && !postInstallRefreshRequired && shouldRefreshMissingAppInventory(params, policy, inventory);
|
|
291
|
+
if (postInstallRefreshRequired || deferredMissingRefreshRequired) await refreshInventory({
|
|
292
|
+
forceRefetch: true,
|
|
293
|
+
reason: postInstallRefreshRequired ? "post_install" : "deferred_missing"
|
|
294
|
+
});
|
|
295
|
+
if (shouldForceRefreshCodexNotReadyPluginApps(params, policy, inventory)) await refreshInventory({
|
|
296
|
+
forceRefetch: true,
|
|
297
|
+
reason: "not_ready_plugin_apps"
|
|
298
|
+
});
|
|
299
|
+
const accountAppsResult = policy.allowAllPlugins ? await readCodexThreadAdmissibleAccountApps(params, appCache) : {
|
|
300
|
+
apps: [],
|
|
301
|
+
installedApps: []
|
|
302
|
+
};
|
|
303
|
+
let appAdmissionConfig;
|
|
304
|
+
const getAdmissionConfig = () => appAdmissionConfig ??= readCodexConfigForAppAdmission(params);
|
|
305
|
+
const diagnostics = [
|
|
306
|
+
...inventory.diagnostics,
|
|
307
|
+
...activationDiagnostics,
|
|
308
|
+
...accountAppsResult.diagnostic ? [accountAppsResult.diagnostic] : []
|
|
309
|
+
];
|
|
310
|
+
const provisionalAppIds = /* @__PURE__ */ new Set();
|
|
311
|
+
const { apps } = buildDisabledAppsConfigPatch();
|
|
312
|
+
const policyApps = {};
|
|
313
|
+
const pluginAppIds = {};
|
|
314
|
+
const pluginOwnedAppIds = collectCodexReservedPluginAppIds({
|
|
315
|
+
policy: inventory.policy,
|
|
316
|
+
inventory,
|
|
317
|
+
accountApps: accountAppsResult.apps
|
|
318
|
+
});
|
|
319
|
+
const unresolvedDisabledPluginOwnership = policy.allowAllPlugins ? inventory.policy.pluginPolicies.find((pluginPolicy) => {
|
|
320
|
+
const record = inventory.records.find((candidate) => candidate.policy.configKey === pluginPolicy.configKey);
|
|
321
|
+
const disabledByMarketplacePolicy = record?.summary.availability === "DISABLED_BY_ADMIN" || record?.summary.installPolicy === "NOT_AVAILABLE";
|
|
322
|
+
const unresolvedPluginIdentity = !record && inventory.diagnostics.some((diagnostic) => diagnostic.plugin?.configKey === pluginPolicy.configKey && (diagnostic.code === "plugin_disabled" || diagnostic.code === "plugin_missing" || diagnostic.code === "marketplace_missing"));
|
|
323
|
+
return (!pluginPolicy.enabled || disabledByMarketplacePolicy || unresolvedPluginIdentity) && !record?.detail;
|
|
324
|
+
}) : void 0;
|
|
325
|
+
if (unresolvedDisabledPluginOwnership) diagnostics.push({
|
|
326
|
+
code: "account_app_ownership_unavailable",
|
|
327
|
+
plugin: unresolvedDisabledPluginOwnership,
|
|
328
|
+
message: `Could not verify disabled Codex plugin app ownership for ${unresolvedDisabledPluginOwnership.pluginName}; account apps were not exposed.`
|
|
329
|
+
});
|
|
330
|
+
for (const record of inventory.records) {
|
|
331
|
+
if (!record.policy.enabled) continue;
|
|
332
|
+
const activation = activationResults.find((item) => item.identity.configKey === record.policy.configKey);
|
|
333
|
+
if (activation?.ok === false || record.activationRequired && !activation?.ok) continue;
|
|
334
|
+
if (record.appOwnership !== "proven") continue;
|
|
335
|
+
pluginAppIds[record.policy.configKey] = [...record.ownedAppIds].toSorted();
|
|
336
|
+
for (const app of inventory.appInventory?.state === "missing" ? [] : record.apps) {
|
|
337
|
+
const admissionConfig = resolveCodexPluginAppThreadAdmission(app, inventory) === "blocked" ? void 0 : await getAdmissionConfig();
|
|
338
|
+
if (!admissionConfig || resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) {
|
|
339
|
+
diagnostics.push({
|
|
340
|
+
code: "app_not_ready",
|
|
341
|
+
plugin: record.policy,
|
|
342
|
+
message: `${app.id} is not accessible for ${record.policy.pluginName}.`
|
|
343
|
+
});
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
provisionalAppIds.add(app.id);
|
|
347
|
+
apps[app.id] = buildEnabledAppConfig(record.policy, record.policy.destructiveApprovalMode === "ask" ? buildCodexAppApprovalOverrides(admissionConfig.config, app) : void 0);
|
|
348
|
+
policyApps[app.id] = {
|
|
349
|
+
configKey: record.policy.configKey,
|
|
350
|
+
marketplaceName: record.policy.marketplaceName,
|
|
351
|
+
pluginName: record.policy.pluginName,
|
|
352
|
+
allowDestructiveActions: record.policy.allowDestructiveActions,
|
|
353
|
+
allowOpenWorld: true,
|
|
354
|
+
destructiveApprovalMode: record.policy.destructiveApprovalMode,
|
|
355
|
+
mcpServerNames: [...record.detail?.mcpServers ?? []].toSorted()
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const app of unresolvedDisabledPluginOwnership ? [] : accountAppsResult.apps) {
|
|
360
|
+
if (pluginOwnedAppIds.has(codexAppIdentityKey(app.id))) continue;
|
|
361
|
+
const admissionConfig = await getAdmissionConfig();
|
|
362
|
+
if (resolveCodexExplicitAppEnablement(admissionConfig.layers, app.id) === false) continue;
|
|
363
|
+
const accountApp = toCodexPluginOwnedAccountApp(app, accountAppsResult.installedApps.find((installed) => installed.id === app.id));
|
|
364
|
+
provisionalAppIds.add(app.id);
|
|
365
|
+
apps[app.id] = buildEnabledAppConfig(policy, policy.destructiveApprovalMode === "ask" ? buildCodexAppApprovalOverrides(admissionConfig.config, accountApp) : void 0);
|
|
366
|
+
policyApps[app.id] = {
|
|
367
|
+
source: "account",
|
|
368
|
+
appName: app.name,
|
|
369
|
+
allowDestructiveActions: policy.allowDestructiveActions,
|
|
370
|
+
allowOpenWorld: true,
|
|
371
|
+
destructiveApprovalMode: policy.destructiveApprovalMode,
|
|
372
|
+
mcpServerNames: []
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const configPatch = Object.keys(policyApps).length === 0 ? buildDisabledAppsConfigPatch() : disableUnlistedCodexApps({ apps }, (await getAdmissionConfig()).config);
|
|
376
|
+
const policyContext = buildPluginAppPolicyContext(policyApps, pluginAppIds);
|
|
377
|
+
return {
|
|
378
|
+
enabled: true,
|
|
379
|
+
configPatch,
|
|
380
|
+
...provisionalAppIds.size > 0 ? { provisionalAppIds: Array.from(provisionalAppIds).toSorted() } : {},
|
|
381
|
+
fingerprint: fingerprintJson({
|
|
382
|
+
version: CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION,
|
|
383
|
+
inputFingerprint,
|
|
384
|
+
configPatch,
|
|
385
|
+
policyContext
|
|
386
|
+
}),
|
|
387
|
+
inputFingerprint,
|
|
388
|
+
policyContext,
|
|
389
|
+
inventory,
|
|
390
|
+
diagnostics
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
/** Deep-merges optional Codex thread config patches, returning undefined when empty. */
|
|
394
|
+
function mergeCodexThreadConfigs(...configs) {
|
|
395
|
+
let merged;
|
|
396
|
+
for (const config of configs) {
|
|
397
|
+
if (!config) continue;
|
|
398
|
+
merged = mergeJsonObjects(merged ?? {}, normalizeShellEnvironmentOverrides(config));
|
|
399
|
+
}
|
|
400
|
+
return merged && Object.keys(merged).length > 0 ? merged : void 0;
|
|
401
|
+
}
|
|
402
|
+
function isCodexPluginThreadBindingStale(params) {
|
|
403
|
+
if (!params.codexPluginsEnabled) return Boolean(params.bindingFingerprint || params.bindingInputFingerprint || params.hasBindingPolicyContext);
|
|
404
|
+
if (!params.bindingFingerprint || !params.bindingInputFingerprint || !params.hasBindingPolicyContext) return true;
|
|
405
|
+
return params.bindingInputFingerprint !== params.currentInputFingerprint;
|
|
406
|
+
}
|
|
407
|
+
function emptyPluginThreadConfig(params) {
|
|
408
|
+
const policyContext = buildPluginAppPolicyContext({}, {});
|
|
409
|
+
return {
|
|
410
|
+
enabled: params.enabled,
|
|
411
|
+
fingerprint: fingerprintJson({
|
|
412
|
+
version: CODEX_PLUGIN_THREAD_CONFIG_FINGERPRINT_VERSION,
|
|
413
|
+
inputFingerprint: params.inputFingerprint,
|
|
414
|
+
configPatch: params.configPatch ?? null,
|
|
415
|
+
policyContext
|
|
416
|
+
}),
|
|
417
|
+
inputFingerprint: params.inputFingerprint,
|
|
418
|
+
...params.configPatch ? { configPatch: params.configPatch } : {},
|
|
419
|
+
policyContext,
|
|
420
|
+
diagnostics: []
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function buildDisabledAppsConfigPatch() {
|
|
424
|
+
return {
|
|
425
|
+
"features.apps": false,
|
|
426
|
+
apps: { _default: {
|
|
427
|
+
enabled: false,
|
|
428
|
+
destructive_enabled: false,
|
|
429
|
+
open_world_enabled: false
|
|
430
|
+
} }
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function disableUnlistedCodexApps(configPatch, nativeConfig) {
|
|
434
|
+
const apps = { ...configPatch.apps };
|
|
435
|
+
for (const id of Object.keys(isJsonObject(nativeConfig.apps) ? nativeConfig.apps : {})) if (id !== "_default" && !Object.hasOwn(apps, id)) apps[id] = { enabled: false };
|
|
436
|
+
return {
|
|
437
|
+
...configPatch,
|
|
438
|
+
apps
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function buildEnabledAppConfig(policy, approvalOverrides = {}) {
|
|
442
|
+
return {
|
|
443
|
+
...approvalOverrides,
|
|
444
|
+
enabled: true,
|
|
445
|
+
destructive_enabled: policy.allowDestructiveActions,
|
|
446
|
+
open_world_enabled: policy.allowOpenWorld !== false,
|
|
447
|
+
...policy.destructiveApprovalMode === "ask" ? {
|
|
448
|
+
default_tools_approval_mode: "auto",
|
|
449
|
+
approvals_reviewer: "user"
|
|
450
|
+
} : {}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/** Rebuilds the safe per-thread apps patch persisted with a Codex thread binding. */
|
|
454
|
+
function buildCodexPluginAppsConfigPatchFromPolicyContext(policyContext) {
|
|
455
|
+
const disabledConfigPatch = buildDisabledAppsConfigPatch();
|
|
456
|
+
const { apps } = disabledConfigPatch;
|
|
457
|
+
for (const [appId, policy] of Object.entries(policyContext.apps).toSorted(([left], [right]) => left.localeCompare(right))) apps[appId] = buildEnabledAppConfig(policy);
|
|
458
|
+
return Object.keys(policyContext.apps).length > 0 ? { apps } : disabledConfigPatch;
|
|
459
|
+
}
|
|
460
|
+
/** Projects current ask overrides before a side thread replays its bound app policy. */
|
|
461
|
+
async function refreshCodexPluginAppApprovalPolicy(params) {
|
|
462
|
+
if (Object.keys(params.policyContext.apps).length === 0) return {
|
|
463
|
+
policyContext: params.policyContext,
|
|
464
|
+
configPatch: buildDisabledAppsConfigPatch(),
|
|
465
|
+
diagnostics: []
|
|
466
|
+
};
|
|
467
|
+
const targetApps = Object.entries(params.policyContext.apps).filter(([, app]) => app.destructiveApprovalMode === "ask").toSorted(([left], [right]) => left.localeCompare(right));
|
|
468
|
+
const targetAppIds = targetApps.map(([id]) => id);
|
|
469
|
+
const diagnostics = [];
|
|
470
|
+
const readParams = {
|
|
471
|
+
...params,
|
|
472
|
+
appCacheKey: "approval-policy-replay"
|
|
473
|
+
};
|
|
474
|
+
const [inventory, admissionConfig] = await Promise.all([targetAppIds.length > 0 ? refreshCodexPluginAppInventory(readParams, new CodexAppInventoryCache(), { targetAppIds }) : void 0, readCodexConfigForAppAdmission(readParams)]);
|
|
475
|
+
const configPatch = disableUnlistedCodexApps(buildCodexPluginAppsConfigPatchFromPolicyContext(params.policyContext), admissionConfig.config);
|
|
476
|
+
const currentApps = new Map(inventory?.apps.map((app) => [app.id, toCodexPluginOwnedAccountApp(app, inventory.installedApps.find((installed) => installed.id === app.id))]));
|
|
477
|
+
const apps = { ...params.policyContext.apps };
|
|
478
|
+
for (const [id, policy] of targetApps) {
|
|
479
|
+
const app = currentApps.get(id);
|
|
480
|
+
if (!app) diagnostics.push({
|
|
481
|
+
code: "app_not_ready",
|
|
482
|
+
message: `Could not verify current Codex app approval policy for ${id}; the app was not exposed.`
|
|
483
|
+
});
|
|
484
|
+
else {
|
|
485
|
+
configPatch.apps[id] = buildEnabledAppConfig(policy, buildCodexAppApprovalOverrides(admissionConfig.config, app));
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
delete apps[id];
|
|
489
|
+
configPatch.apps[id] = { enabled: false };
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
policyContext: buildPluginAppPolicyContext(apps, Object.fromEntries(Object.entries(params.policyContext.pluginAppIds).map(([key, ids]) => [key, ids.filter((id) => Object.hasOwn(apps, id))]))),
|
|
493
|
+
configPatch,
|
|
494
|
+
diagnostics
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function buildPluginAppPolicyContext(apps, pluginAppIds) {
|
|
498
|
+
return {
|
|
499
|
+
fingerprint: fingerprintJson({
|
|
500
|
+
version: 2,
|
|
501
|
+
apps,
|
|
502
|
+
pluginAppIds
|
|
503
|
+
}),
|
|
504
|
+
apps,
|
|
505
|
+
pluginAppIds
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function shouldRefreshMissingAppInventory(params, policy, inventory) {
|
|
509
|
+
return Boolean(params.appCacheKey && policy.pluginPolicies.some((plugin) => plugin.enabled) && inventory.appInventory?.state === "missing");
|
|
510
|
+
}
|
|
511
|
+
function policyFingerprint(policy) {
|
|
512
|
+
return {
|
|
513
|
+
enabled: policy.enabled,
|
|
514
|
+
allowAllPlugins: policy.allowAllPlugins,
|
|
515
|
+
allowDestructiveActions: policy.allowDestructiveActions,
|
|
516
|
+
destructiveApprovalMode: policy.destructiveApprovalMode,
|
|
517
|
+
plugins: policy.pluginPolicies.map((plugin) => ({
|
|
518
|
+
configKey: plugin.configKey,
|
|
519
|
+
marketplaceName: plugin.marketplaceName,
|
|
520
|
+
pluginName: plugin.pluginName,
|
|
521
|
+
enabled: plugin.enabled,
|
|
522
|
+
allowDestructiveActions: plugin.allowDestructiveActions,
|
|
523
|
+
destructiveApprovalMode: plugin.destructiveApprovalMode
|
|
524
|
+
}))
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function normalizeShellEnvironmentOverrides(config) {
|
|
528
|
+
let normalized = { ...config };
|
|
529
|
+
for (const [key, value] of Object.entries(config)) {
|
|
530
|
+
if (!key.startsWith("shell_environment_policy.")) continue;
|
|
531
|
+
delete normalized[key];
|
|
532
|
+
let patch = value;
|
|
533
|
+
for (const segment of key.split(".").toReversed()) patch = { [segment]: patch };
|
|
534
|
+
normalized = mergeJsonObjects(normalized, patch);
|
|
535
|
+
}
|
|
536
|
+
return normalized;
|
|
537
|
+
}
|
|
538
|
+
function mergeJsonObjects(left, right) {
|
|
539
|
+
const merged = {
|
|
540
|
+
...left,
|
|
541
|
+
...right
|
|
542
|
+
};
|
|
543
|
+
for (const [key, value] of Object.entries(right)) {
|
|
544
|
+
const existing = left[key];
|
|
545
|
+
if (Object.hasOwn(left, key) && isJsonObject(existing) && isJsonObject(value)) merged[key] = mergeJsonObjects(existing, value);
|
|
546
|
+
}
|
|
547
|
+
return merged;
|
|
548
|
+
}
|
|
549
|
+
function fingerprintJson(value) {
|
|
550
|
+
return crypto.createHash("sha256").update(stringifyCodexPolicy(value)).digest("hex");
|
|
551
|
+
}
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region extensions/codex/src/app-server/dynamic-tool-profile.ts
|
|
554
|
+
/** Tool names owned by Codex app-server and normally excluded from OpenClaw dynamic tools. */
|
|
555
|
+
const CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES = [
|
|
556
|
+
"read",
|
|
557
|
+
"write",
|
|
558
|
+
"edit",
|
|
559
|
+
"apply_patch",
|
|
560
|
+
"exec",
|
|
561
|
+
"process",
|
|
562
|
+
"update_plan",
|
|
563
|
+
"tool_call",
|
|
564
|
+
"tool_describe",
|
|
565
|
+
"tool_search"
|
|
566
|
+
];
|
|
567
|
+
const CODEX_NATIVE_GOAL_TOOL_EXCLUDES = [
|
|
568
|
+
"get_goal",
|
|
569
|
+
"create_goal",
|
|
570
|
+
"update_goal"
|
|
571
|
+
];
|
|
572
|
+
const CODEX_APP_SERVER_OWNED_REPLACEABLE_TOOL_EXCLUDES = /* @__PURE__ */ new Set([
|
|
573
|
+
"read",
|
|
574
|
+
"write",
|
|
575
|
+
"edit",
|
|
576
|
+
"apply_patch",
|
|
577
|
+
...CODEX_NATIVE_GOAL_TOOL_EXCLUDES
|
|
578
|
+
]);
|
|
579
|
+
const CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES = /* @__PURE__ */ new Set(["exec", "process"]);
|
|
580
|
+
const DYNAMIC_TOOL_NAME_ALIASES = {
|
|
581
|
+
bash: "exec",
|
|
582
|
+
"apply-patch": "apply_patch"
|
|
583
|
+
};
|
|
584
|
+
function normalizeCodexDynamicToolName(name) {
|
|
585
|
+
const normalized = name.trim().toLowerCase();
|
|
586
|
+
return DYNAMIC_TOOL_NAME_ALIASES[normalized] ?? normalized;
|
|
587
|
+
}
|
|
588
|
+
/** True only for the host-scoped OpenClaw run's exact tool contract. */
|
|
589
|
+
function isSystemAgentOnlyCodexDynamicToolAllowlist(toolsAllow) {
|
|
590
|
+
return toolsAllow?.length === 1 && normalizeCodexDynamicToolName(toolsAllow[0] ?? "") === "openclaw";
|
|
591
|
+
}
|
|
592
|
+
/** True when a private source reply may use the message delivery tool only. */
|
|
593
|
+
function isMessageOnlyCodexSourceReply(params) {
|
|
594
|
+
return params.sourceReplyDeliveryMode === "message_tool_only" && params.toolsAllow?.length === 1 && normalizeCodexDynamicToolName(params.toolsAllow[0] ?? "") === "message";
|
|
595
|
+
}
|
|
596
|
+
function isForcedPrivateQaCodexRuntime(env = process.env) {
|
|
597
|
+
return env.OPENCLAW_BUILD_PRIVATE_QA === "1" && env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase() === "codex";
|
|
598
|
+
}
|
|
599
|
+
function resolveCodexDynamicToolsLoading(config, env = process.env) {
|
|
600
|
+
return isForcedPrivateQaCodexRuntime(env) ? "direct" : config.codexDynamicToolsLoading ?? "searchable";
|
|
601
|
+
}
|
|
602
|
+
/** Returns true for models whose tool-search path is unsupported or inefficient. */
|
|
603
|
+
function shouldDisableCodexToolSearchForModel(modelId) {
|
|
604
|
+
return modelId?.trim().toLowerCase().split("/").at(-1) === "gpt-5.4-nano";
|
|
605
|
+
}
|
|
606
|
+
/** Resolves dynamic-tool loading for the app-server connection that will execute the turn. */
|
|
607
|
+
function resolveCodexDynamicToolsLoadingForRuntime(config, modelId, options = {}, env = process.env) {
|
|
608
|
+
const loading = resolveCodexDynamicToolsLoading(config, env);
|
|
609
|
+
return loading === "searchable" && (shouldDisableCodexToolSearchForModel(modelId) || options.connectionClass === "remote") ? "direct" : loading;
|
|
610
|
+
}
|
|
611
|
+
/** Filters OpenClaw tools that Codex owns natively or config explicitly excludes. */
|
|
612
|
+
function filterCodexDynamicTools(tools, config, env = process.env) {
|
|
613
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, {
|
|
614
|
+
preserveOpenClawReplacements: false,
|
|
615
|
+
preserveOpenClawShell: false
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
/** Keeps OpenClaw coding tools that replace a disabled Codex native surface. */
|
|
619
|
+
function filterCodexDynamicToolsForDisabledNativeSurface(tools, config, options, env = process.env) {
|
|
620
|
+
return filterCodexDynamicToolsWithOptions(tools, config, env, {
|
|
621
|
+
preserveOpenClawReplacements: true,
|
|
622
|
+
preserveOpenClawShell: options.preserveShell
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function filterCodexDynamicToolsWithOptions(tools, config, env, options) {
|
|
626
|
+
const excludes = /* @__PURE__ */ new Set();
|
|
627
|
+
if (!options.preserveOpenClawReplacements) for (const name of CODEX_NATIVE_GOAL_TOOL_EXCLUDES) excludes.add(name);
|
|
628
|
+
if (isForcedPrivateQaCodexRuntime(env)) excludes.add("apply_patch");
|
|
629
|
+
else for (const name of CODEX_APP_SERVER_OWNED_DYNAMIC_TOOL_EXCLUDES) {
|
|
630
|
+
if (options.preserveOpenClawReplacements && CODEX_APP_SERVER_OWNED_REPLACEABLE_TOOL_EXCLUDES.has(name)) continue;
|
|
631
|
+
if (options.preserveOpenClawShell && CODEX_APP_SERVER_OWNED_SHELL_TOOL_EXCLUDES.has(name)) continue;
|
|
632
|
+
excludes.add(name);
|
|
633
|
+
}
|
|
634
|
+
for (const name of config.codexDynamicToolsExclude ?? []) {
|
|
635
|
+
const trimmed = normalizeCodexDynamicToolName(name);
|
|
636
|
+
if (trimmed) excludes.add(trimmed);
|
|
637
|
+
}
|
|
638
|
+
return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(normalizeCodexDynamicToolName(tool.name)));
|
|
639
|
+
}
|
|
640
|
+
//#endregion
|
|
641
|
+
//#region extensions/codex/src/app-server/thread-binding-policy.ts
|
|
642
|
+
function shouldRotateCodexAppServerBindingForRuntime(params) {
|
|
643
|
+
if (!params.current) return false;
|
|
644
|
+
if (params.binding === params.current) return false;
|
|
645
|
+
return params.connectionClass === "remote" || Boolean(params.binding);
|
|
646
|
+
}
|
|
647
|
+
function resolveCodexGpt56MultiAgentVersion(modelRef) {
|
|
648
|
+
let modelId = modelRef?.trim().toLowerCase();
|
|
649
|
+
if (!modelId) return;
|
|
650
|
+
const slashIndex = modelId.indexOf("/");
|
|
651
|
+
if (slashIndex > 0) {
|
|
652
|
+
const provider = modelId.slice(0, slashIndex);
|
|
653
|
+
if (provider !== "openai" && provider !== "codex") return;
|
|
654
|
+
modelId = modelId.slice(slashIndex + 1);
|
|
655
|
+
}
|
|
656
|
+
if (modelId === "gpt-5.6-sol" || modelId === "gpt-5.6-terra") return "v2";
|
|
657
|
+
return modelId === "gpt-5.6-luna" ? "v1" : void 0;
|
|
658
|
+
}
|
|
659
|
+
function shouldRotateCodexGpt56MultiAgentBinding(params) {
|
|
660
|
+
const bindingVersion = resolveCodexGpt56MultiAgentVersion(params.bindingModel);
|
|
661
|
+
const requestedVersion = resolveCodexGpt56MultiAgentVersion(params.requestedModel);
|
|
662
|
+
return Boolean(bindingVersion && requestedVersion && bindingVersion !== requestedVersion);
|
|
663
|
+
}
|
|
664
|
+
function isTransientWebSearchRestriction(params) {
|
|
665
|
+
if (params.nativeProviderWebSearchSupport === "unknown") return true;
|
|
666
|
+
if (params.params.config?.tools?.web?.search?.enabled === false) return false;
|
|
667
|
+
if (params.params.disableTools === true) return true;
|
|
668
|
+
const persistentWebSearchRestriction = params.webSearchAllowed === false && params.persistentWebSearchAllowed === false;
|
|
669
|
+
if (params.nativeCodeModeEnabled === false && !persistentWebSearchRestriction) return true;
|
|
670
|
+
if (params.webSearchAllowed !== false) return false;
|
|
671
|
+
if (params.persistentWebSearchAllowed !== void 0) return params.persistentWebSearchAllowed;
|
|
672
|
+
if (params.params.toolsAllow === void 0) return false;
|
|
673
|
+
return !params.params.toolsAllow.some((name) => {
|
|
674
|
+
const normalized = normalizeCodexDynamicToolName(name);
|
|
675
|
+
return normalized === "*" || normalized === "web_search";
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function shouldRecheckRecoverablePluginBinding(params) {
|
|
679
|
+
if (!params.pluginThreadConfig?.enabled) return false;
|
|
680
|
+
if (!params.binding.pluginAppsFingerprint || !params.binding.pluginAppsInputFingerprint || params.binding.pluginAppsInputFingerprint !== params.pluginThreadConfig.inputFingerprint) return false;
|
|
681
|
+
const policyContext = params.binding.pluginAppPolicyContext;
|
|
682
|
+
if (!policyContext) return false;
|
|
683
|
+
const enabledPluginConfigKeys = params.pluginThreadConfig.enabledPluginConfigKeys ?? [];
|
|
684
|
+
const recoverablePluginConfigKeys = params.pluginThreadConfig.recoverablePluginConfigKeys ?? enabledPluginConfigKeys;
|
|
685
|
+
const recoverablePluginConfigKeySet = new Set(recoverablePluginConfigKeys);
|
|
686
|
+
const bindingContainsSettledPlugin = enabledPluginConfigKeys.filter((configKey) => !recoverablePluginConfigKeySet.has(configKey)).some((configKey) => (policyContext.pluginAppIds[configKey]?.length ?? 0) > 0 || Object.values(policyContext.apps).some((app) => app.source !== "account" && app.configKey === configKey));
|
|
687
|
+
const accountAppRecoveryEnabled = params.pluginThreadConfig.accountAppRecoveryEnabled ?? enabledPluginConfigKeys.length === 0;
|
|
688
|
+
return bindingContainsSettledPlugin || accountAppRecoveryEnabled && Object.keys(policyContext.apps).length === 0 || recoverablePluginConfigKeys.length > 0;
|
|
689
|
+
}
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region extensions/codex/src/app-server/thread-fingerprints.ts
|
|
692
|
+
function codexDynamicToolsFingerprint(dynamicTools) {
|
|
693
|
+
return hashCodexAppServerBindingFingerprint(codexLegacyDynamicToolsFingerprint(dynamicTools));
|
|
694
|
+
}
|
|
695
|
+
function areCodexDynamicToolFingerprintsCompatible(params) {
|
|
696
|
+
return !params.previous || params.previous === params.next || params.previous === params.nextLegacy;
|
|
697
|
+
}
|
|
698
|
+
function codexLegacyDynamicToolsFingerprint(dynamicTools) {
|
|
699
|
+
return JSON.stringify(dynamicTools.map(stabilizeJsonValue).toSorted(compareJsonFingerprint));
|
|
700
|
+
}
|
|
701
|
+
function legacyFingerprintUserMcpServersConfigPatch(configPatch) {
|
|
702
|
+
return configPatch ? JSON.stringify(stabilizeJsonValue(configPatch)) : void 0;
|
|
703
|
+
}
|
|
704
|
+
function fingerprintUserMcpServersConfigPatch(configPatch) {
|
|
705
|
+
return configPatch ? hashCodexAppServerBindingFingerprint(JSON.stringify(stabilizeJsonValue(redactUserMcpServersFingerprintSecrets(configPatch)))) : void 0;
|
|
706
|
+
}
|
|
707
|
+
function redactUserMcpServersFingerprintSecrets(value) {
|
|
708
|
+
if (Array.isArray(value)) return value.map(redactUserMcpServersFingerprintSecrets);
|
|
709
|
+
if (!value || typeof value !== "object") return value;
|
|
710
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
|
|
711
|
+
if (key === "http_headers" && entry && typeof entry === "object" && !Array.isArray(entry)) return [key, Object.fromEntries(Object.entries(entry).map(([header, headerValue]) => [header, header.toLowerCase() === "authorization" ? fingerprintUserMcpServersAuthorizationHeader(headerValue) : headerValue]))];
|
|
712
|
+
return [key, redactUserMcpServersFingerprintSecrets(entry)];
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
function fingerprintUserMcpServersAuthorizationHeader(value) {
|
|
716
|
+
return typeof value === "string" && value.length > 0 ? `<redacted:sha256:${crypto$1.createHash("sha256").update(value).digest("hex")}>` : "<redacted>";
|
|
717
|
+
}
|
|
718
|
+
function fingerprintJsonObject(value) {
|
|
719
|
+
return JSON.stringify(stabilizeJsonValue(value));
|
|
720
|
+
}
|
|
721
|
+
/** Hash thread-creation identity; settings already applied by turn/start must not restart Codex. */
|
|
722
|
+
function fingerprintCodexThreadConfig(request, authProfileId, dynamicToolsFingerprint) {
|
|
723
|
+
return hashCodexAppServerBindingFingerprint(fingerprintJsonObject({
|
|
724
|
+
authProfileId: authProfileId ?? null,
|
|
725
|
+
dynamicToolsFingerprint: dynamicToolsFingerprint ?? null,
|
|
726
|
+
nativeMultiAgentVersion: resolveCodexGpt56MultiAgentVersion(typeof request.requestedModel === "string" ? request.requestedModel : typeof request.model === "string" ? request.model : void 0) ?? null,
|
|
727
|
+
modelProvider: request.modelProvider ?? null,
|
|
728
|
+
requestedModelProvider: request.requestedModelProvider === void 0 ? request.modelProvider ?? null : request.requestedModelProvider,
|
|
729
|
+
permissions: request.permissions ?? null,
|
|
730
|
+
baseInstructions: request.baseInstructions ?? null,
|
|
731
|
+
developerInstructions: request.developerInstructions ?? null,
|
|
732
|
+
config: request.config ?? {}
|
|
733
|
+
}));
|
|
734
|
+
}
|
|
735
|
+
function fingerprintEnvironmentSelection(environments) {
|
|
736
|
+
return environments ? JSON.stringify(environments.map(stabilizeJsonValue)) : void 0;
|
|
737
|
+
}
|
|
738
|
+
function stabilizeJsonValue(value) {
|
|
739
|
+
if (Array.isArray(value)) return value.map(stabilizeJsonValue);
|
|
740
|
+
if (!isJsonObject(value)) return value;
|
|
741
|
+
return Object.fromEntries(Object.entries(value).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, stabilizeJsonValue(child)]));
|
|
742
|
+
}
|
|
743
|
+
function readActiveCodexTurnIds(thread) {
|
|
744
|
+
return (thread.turns ?? []).filter((turn) => turn.status === "inProgress").map((turn) => typeof turn.id === "string" ? turn.id : "").filter((turnId) => turnId.trim().length > 0);
|
|
745
|
+
}
|
|
746
|
+
function readActiveCodexTurnIdsFromResume(response) {
|
|
747
|
+
const pagedTurns = response.initialTurnsPage?.data;
|
|
748
|
+
return readActiveCodexTurnIds(Array.isArray(pagedTurns) ? { turns: pagedTurns } : response.thread);
|
|
749
|
+
}
|
|
750
|
+
const LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT = codexLegacyDynamicToolsFingerprint([]);
|
|
751
|
+
const EMPTY_DYNAMIC_TOOLS_FINGERPRINT = hashCodexAppServerBindingFingerprint(LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT);
|
|
752
|
+
function areUserMcpServersFingerprintsCompatible(params) {
|
|
753
|
+
return params.previous === params.next || params.previous === params.nextLegacy || params.nextLegacy !== void 0 && params.previous === hashCodexAppServerBindingFingerprint(params.nextLegacy);
|
|
754
|
+
}
|
|
755
|
+
function shouldStartTransientNoToolThread(params) {
|
|
756
|
+
return Boolean(params.previous && !isEmptyDynamicToolsFingerprint(params.previous) && !params.nextHasDynamicTools);
|
|
757
|
+
}
|
|
758
|
+
function isEmptyDynamicToolsFingerprint(fingerprint) {
|
|
759
|
+
return fingerprint === EMPTY_DYNAMIC_TOOLS_FINGERPRINT || fingerprint === LEGACY_EMPTY_DYNAMIC_TOOLS_FINGERPRINT;
|
|
760
|
+
}
|
|
761
|
+
function compareJsonFingerprint(left, right) {
|
|
762
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
763
|
+
}
|
|
764
|
+
//#endregion
|
|
765
|
+
//#region extensions/codex/src/app-server/native-skill-isolation.ts
|
|
766
|
+
const MAX_PERSONAL_SKILL_DIRECTORIES = 2e3;
|
|
767
|
+
const MAX_PERSONAL_SKILL_DEPTH = 6;
|
|
768
|
+
const MAX_PERSONAL_SKILL_ENTRIES = 1e4;
|
|
769
|
+
const nativeSkillIsolationByClient = /* @__PURE__ */ new WeakMap();
|
|
770
|
+
function isMissingPathError(error) {
|
|
771
|
+
return error.code === "ENOENT";
|
|
772
|
+
}
|
|
773
|
+
async function canonicalizeExistingPath(candidate) {
|
|
774
|
+
try {
|
|
775
|
+
return await fs.realpath(candidate);
|
|
776
|
+
} catch {
|
|
777
|
+
return path.resolve(candidate);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async function usesDefaultStateDir() {
|
|
781
|
+
if (!process.env.OPENCLAW_STATE_DIR?.trim()) return true;
|
|
782
|
+
const home = resolveRequiredHomeDir();
|
|
783
|
+
const [stateDir, defaultStateDir] = await Promise.all([canonicalizeExistingPath(resolveStateDir()), canonicalizeExistingPath(path.join(home, ".openclaw"))]);
|
|
784
|
+
return stateDir === defaultStateDir;
|
|
785
|
+
}
|
|
786
|
+
async function collectPersonalSkillRealPaths(homes, codexHome) {
|
|
787
|
+
const realStateDir = await canonicalizeExistingPath(resolveStateDir());
|
|
788
|
+
const roots = [];
|
|
789
|
+
for (const home of homes) {
|
|
790
|
+
for (const dir of [".agents", ".claude"]) roots.push({
|
|
791
|
+
dir: path.join(home, dir, "skills"),
|
|
792
|
+
onlyEscapedStateTargets: false
|
|
793
|
+
});
|
|
794
|
+
const defaultCodexHome = path.join(home, ".codex");
|
|
795
|
+
const realDefaultCodexHome = await canonicalizeExistingPath(defaultCodexHome);
|
|
796
|
+
roots.push({
|
|
797
|
+
dir: path.join(defaultCodexHome, "skills"),
|
|
798
|
+
onlyEscapedStateTargets: isPathInside(realStateDir, realDefaultCodexHome)
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
const configuredCodexHome = codexHome?.trim() || process.env.CODEX_HOME?.trim();
|
|
802
|
+
if (configuredCodexHome) {
|
|
803
|
+
const realCodexHome = await canonicalizeExistingPath(configuredCodexHome);
|
|
804
|
+
const stateOwned = isPathInside(realStateDir, realCodexHome);
|
|
805
|
+
roots.push({
|
|
806
|
+
dir: path.join(configuredCodexHome, "skills"),
|
|
807
|
+
onlyEscapedStateTargets: stateOwned
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
const skillPaths = /* @__PURE__ */ new Set();
|
|
811
|
+
let complete = true;
|
|
812
|
+
const seenDirectories = /* @__PURE__ */ new Set();
|
|
813
|
+
const queue = roots.map((root) => ({
|
|
814
|
+
dir: root.dir,
|
|
815
|
+
onlyEscapedStateTargets: root.onlyEscapedStateTargets,
|
|
816
|
+
depth: 0
|
|
817
|
+
}));
|
|
818
|
+
let entryCount = 0;
|
|
819
|
+
const recordSkillFile = async (filePath, onlyEscapedStateTargets) => {
|
|
820
|
+
try {
|
|
821
|
+
const skillRealPath = await fs.realpath(filePath);
|
|
822
|
+
if (!onlyEscapedStateTargets || !isPathInside(realStateDir, skillRealPath)) skillPaths.add(skillRealPath);
|
|
823
|
+
} catch (error) {
|
|
824
|
+
if (!isMissingPathError(error)) complete = false;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
for (const current of queue) {
|
|
828
|
+
let realDir;
|
|
829
|
+
try {
|
|
830
|
+
realDir = await fs.realpath(current.dir);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
if (isMissingPathError(error)) continue;
|
|
833
|
+
complete = false;
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (seenDirectories.has(realDir)) continue;
|
|
837
|
+
seenDirectories.add(realDir);
|
|
838
|
+
if (seenDirectories.size > MAX_PERSONAL_SKILL_DIRECTORIES) {
|
|
839
|
+
complete = false;
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
let directory;
|
|
843
|
+
try {
|
|
844
|
+
directory = await fs.opendir(current.dir);
|
|
845
|
+
} catch (error) {
|
|
846
|
+
if (!isMissingPathError(error)) complete = false;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
for await (const entry of directory) {
|
|
851
|
+
entryCount += 1;
|
|
852
|
+
if (entryCount > MAX_PERSONAL_SKILL_ENTRIES) {
|
|
853
|
+
complete = false;
|
|
854
|
+
queue.length = 0;
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
if (entry.name.startsWith(".")) continue;
|
|
858
|
+
const entryPath = path.join(current.dir, entry.name);
|
|
859
|
+
let isFile = entry.isFile();
|
|
860
|
+
let isDirectory = entry.isDirectory();
|
|
861
|
+
if (entry.isSymbolicLink()) try {
|
|
862
|
+
const stat = await fs.stat(entryPath);
|
|
863
|
+
isFile = stat.isFile();
|
|
864
|
+
isDirectory = stat.isDirectory();
|
|
865
|
+
} catch (error) {
|
|
866
|
+
if (!isMissingPathError(error)) complete = false;
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (entry.name === "SKILL.md" && isFile) {
|
|
870
|
+
await recordSkillFile(entryPath, current.onlyEscapedStateTargets);
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (isDirectory) {
|
|
874
|
+
if (current.depth >= MAX_PERSONAL_SKILL_DEPTH) complete = false;
|
|
875
|
+
else queue.push({
|
|
876
|
+
dir: entryPath,
|
|
877
|
+
depth: current.depth + 1,
|
|
878
|
+
onlyEscapedStateTargets: current.onlyEscapedStateTargets
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
} catch (error) {
|
|
883
|
+
if (!isMissingPathError(error)) complete = false;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
complete,
|
|
888
|
+
skillPaths
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
/** Resolves the native user-scope skills that an isolated OpenClaw thread must disable. */
|
|
892
|
+
async function resolveCodexNativeSkillIsolation(params) {
|
|
893
|
+
params.signal?.throwIfAborted();
|
|
894
|
+
if (!process.env.OPENCLAW_STATE_DIR?.trim()) return;
|
|
895
|
+
const key = JSON.stringify([
|
|
896
|
+
path.resolve(resolveStateDir()),
|
|
897
|
+
path.resolve(params.cwd),
|
|
898
|
+
params.codexHome?.trim() || process.env.CODEX_HOME?.trim() || "",
|
|
899
|
+
params.home?.trim() || process.env.HOME?.trim() || "",
|
|
900
|
+
params.userProfile?.trim() || process.env.USERPROFILE?.trim() || ""
|
|
901
|
+
]);
|
|
902
|
+
let cache = nativeSkillIsolationByClient.get(params.client);
|
|
903
|
+
if (!cache) {
|
|
904
|
+
cache = { revision: 0 };
|
|
905
|
+
nativeSkillIsolationByClient.set(params.client, cache);
|
|
906
|
+
const clientCache = cache;
|
|
907
|
+
params.client.addNotificationHandler((notification) => {
|
|
908
|
+
if (notification.method === "skills/changed") {
|
|
909
|
+
clientCache.revision += 1;
|
|
910
|
+
clientCache.snapshot = void 0;
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
for (;;) {
|
|
915
|
+
params.signal?.throwIfAborted();
|
|
916
|
+
let snapshot = cache.snapshot;
|
|
917
|
+
if (snapshot?.key !== key || !snapshot.settled && snapshot.signal !== params.signal) {
|
|
918
|
+
snapshot = {
|
|
919
|
+
key,
|
|
920
|
+
revision: cache.revision,
|
|
921
|
+
result: resolveUncachedCodexNativeSkillIsolation(params),
|
|
922
|
+
settled: false,
|
|
923
|
+
signal: params.signal
|
|
924
|
+
};
|
|
925
|
+
cache.snapshot = snapshot;
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
const isolation = await snapshot.result;
|
|
929
|
+
snapshot.settled = true;
|
|
930
|
+
params.signal?.throwIfAborted();
|
|
931
|
+
if (snapshot.revision === cache.revision) return isolation;
|
|
932
|
+
} catch (error) {
|
|
933
|
+
if (cache.snapshot === snapshot) cache.snapshot = void 0;
|
|
934
|
+
throw error;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
async function resolveUncachedCodexNativeSkillIsolation(params) {
|
|
939
|
+
if (await usesDefaultStateDir()) return;
|
|
940
|
+
const response = await params.client.request("skills/list", {
|
|
941
|
+
cwds: [params.cwd],
|
|
942
|
+
forceReload: true
|
|
943
|
+
}, { signal: params.signal });
|
|
944
|
+
const homes = [params.home?.trim() || process.env.HOME?.trim() || process.env.USERPROFILE?.trim() || os.homedir()];
|
|
945
|
+
if (process.platform === "win32") homes.push(params.userProfile?.trim() || os.homedir());
|
|
946
|
+
const personalSkills = await collectPersonalSkillRealPaths([...new Set(homes.map((home) => path.resolve(home)))], params.codexHome);
|
|
947
|
+
return { disabledUserSkillPaths: [...personalSkills.complete ? personalSkills.skillPaths : /* @__PURE__ */ new Set([...personalSkills.skillPaths, ...response.data.flatMap((entry) => entry.skills.filter((skill) => skill.scope === "user").map((skill) => skill.path))])].toSorted((left, right) => left.localeCompare(right)) };
|
|
948
|
+
}
|
|
949
|
+
/** Applies path-exact session rules after caller config so isolated user skills stay disabled. */
|
|
950
|
+
function applyCodexNativeSkillIsolation(config, isolation) {
|
|
951
|
+
if (!isolation) return config;
|
|
952
|
+
const existingRules = config?.["skills.config"];
|
|
953
|
+
if (existingRules !== void 0 && !Array.isArray(existingRules)) throw new Error("Codex thread skills.config must be an array");
|
|
954
|
+
const disabledRules = isolation.disabledUserSkillPaths.map((skillPath) => ({
|
|
955
|
+
path: skillPath,
|
|
956
|
+
enabled: false
|
|
957
|
+
}));
|
|
958
|
+
return {
|
|
959
|
+
...config,
|
|
960
|
+
"skills.include_instructions": false,
|
|
961
|
+
"skills.config": [...existingRules ?? [], ...disabledRules]
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region extensions/codex/src/app-server/native-tool-catalog.ts
|
|
966
|
+
function hasCodexNativeToolCatalog(binding) {
|
|
967
|
+
return binding?.connectionScope === "supervision" && !binding.pendingSupervisionBranch;
|
|
968
|
+
}
|
|
969
|
+
/** Pinned serde omits empty catalogs and false deferLoading; declarations remain native-owned. */
|
|
970
|
+
function parseCodexNativeToolCatalog(metadata, threadId, fingerprint) {
|
|
971
|
+
const fail = () => /* @__PURE__ */ new Error("The canonical Codex native tool catalog is missing, corrupt, or changed; the thread is preserved. Reconnect and inspect its native metadata before retrying.");
|
|
972
|
+
if (!isJsonObject(metadata) || metadata.id !== threadId) throw fail();
|
|
973
|
+
const catalog = metadata.dynamic_tools ?? [];
|
|
974
|
+
if (!Array.isArray(catalog) || Buffer.byteLength(JSON.stringify(catalog)) > 1048576) throw fail();
|
|
975
|
+
const names = /* @__PURE__ */ new Set();
|
|
976
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
977
|
+
const validName = (name) => typeof name === "string" && /^[a-zA-Z0-9_-]{1,128}$/u.test(name);
|
|
978
|
+
const readFunction = (value) => {
|
|
979
|
+
if (!isJsonObject(value) || value.type !== "function" || !validName(value.name) || typeof value.description !== "string" || !isJsonObject(value.inputSchema) || value.deferLoading !== void 0 && typeof value.deferLoading !== "boolean" || Object.keys(value).some((key) => ![
|
|
980
|
+
"type",
|
|
981
|
+
"name",
|
|
982
|
+
"description",
|
|
983
|
+
"inputSchema",
|
|
984
|
+
"deferLoading"
|
|
985
|
+
].includes(key)) || names.has(value.name) || names.size >= 2e3) throw fail();
|
|
986
|
+
names.add(value.name);
|
|
987
|
+
return {
|
|
988
|
+
type: "function",
|
|
989
|
+
name: value.name,
|
|
990
|
+
description: value.description,
|
|
991
|
+
inputSchema: structuredClone(value.inputSchema),
|
|
992
|
+
...value.deferLoading === true ? { deferLoading: true } : {}
|
|
993
|
+
};
|
|
994
|
+
};
|
|
995
|
+
const tools = catalog.map((value) => {
|
|
996
|
+
if (!isJsonObject(value) || value.type !== "namespace") return readFunction(value);
|
|
997
|
+
if (!validName(value.name) || namespaces.has(value.name) || typeof value.description !== "string" || !Array.isArray(value.tools) || !value.tools.length || Object.keys(value).some((key) => ![
|
|
998
|
+
"type",
|
|
999
|
+
"name",
|
|
1000
|
+
"description",
|
|
1001
|
+
"tools"
|
|
1002
|
+
].includes(key))) throw fail();
|
|
1003
|
+
namespaces.add(value.name);
|
|
1004
|
+
return {
|
|
1005
|
+
type: "namespace",
|
|
1006
|
+
name: value.name,
|
|
1007
|
+
description: value.description,
|
|
1008
|
+
tools: value.tools.map(readFunction)
|
|
1009
|
+
};
|
|
1010
|
+
});
|
|
1011
|
+
if (fingerprint !== void 0 && codexDynamicToolsFingerprint(tools) !== fingerprint) throw fail();
|
|
1012
|
+
return tools;
|
|
1013
|
+
}
|
|
1014
|
+
/** Existing supervised bindings identify data, never authorize an executor or a new adoption. */
|
|
1015
|
+
async function loadCodexNativeToolCatalog(params) {
|
|
1016
|
+
const { binding, client, appServer, agentDir, assertCurrent } = params;
|
|
1017
|
+
assertCurrent();
|
|
1018
|
+
const home = resolveCodexAppServerLocalHomeDir(appServer.start, agentDir);
|
|
1019
|
+
const actualHome = client.getRuntimeIdentity()?.codexHome;
|
|
1020
|
+
if (!hasCodexNativeToolCatalog(binding) || !binding.dynamicToolsFingerprint || appServer.start.transport !== "stdio" && appServer.start.transport !== "unix" || appServer.remoteWorkspaceRoot || !actualHome || path.resolve(actualHome) !== path.resolve(home) || binding.appServerRuntimeFingerprint !== buildCodexAppServerConnectionFingerprint(appServer, agentDir)) throw new Error("Canonical Codex declarations require the original verified local binding and selected native connection; the thread is preserved.");
|
|
1021
|
+
const metadata = await readCodexClientSessionMeta(client, path.join(home, "sessions"), binding.rolloutPath, binding.threadId);
|
|
1022
|
+
assertCurrent();
|
|
1023
|
+
return parseCodexNativeToolCatalog(metadata, binding.threadId, binding.dynamicToolsFingerprint);
|
|
1024
|
+
}
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region extensions/codex/src/app-server/thread-mcp-attestation.ts
|
|
1027
|
+
async function attestCodexRestrictedToolSurfaceMcpServersDisabled(client, threadId, threadConfig, signal, expectedActiveServerNames = []) {
|
|
1028
|
+
const configuredServers = threadConfig?.mcp_servers;
|
|
1029
|
+
if (configuredServers !== void 0 && !isJsonObject(configuredServers)) throw new Error("Codex restricted-tool-surface thread config has invalid mcp_servers");
|
|
1030
|
+
const expectedServers = /* @__PURE__ */ new Map();
|
|
1031
|
+
for (const [name, serverConfig] of Object.entries(configuredServers ?? {})) {
|
|
1032
|
+
if (!isJsonObject(serverConfig) || serverConfig.enabled !== false) throw new Error(`Codex restricted-tool-surface MCP server ${name} is not disabled`);
|
|
1033
|
+
expectedServers.set(name, "disabled");
|
|
1034
|
+
}
|
|
1035
|
+
for (const name of expectedActiveServerNames) {
|
|
1036
|
+
if (expectedServers.get(name) === "disabled") throw new Error(`Codex restricted-tool-surface MCP server ${name} has conflicting policy`);
|
|
1037
|
+
expectedServers.set(name, "active");
|
|
1038
|
+
}
|
|
1039
|
+
const response = await client.request("mcpServerStatus/list", {
|
|
1040
|
+
threadId,
|
|
1041
|
+
detail: "toolsAndAuthOnly"
|
|
1042
|
+
}, { signal });
|
|
1043
|
+
if (!isJsonObject(response) || !Array.isArray(response.data)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface attestation");
|
|
1044
|
+
const observedServerNames = /* @__PURE__ */ new Set();
|
|
1045
|
+
for (const status of response.data) {
|
|
1046
|
+
if (!isJsonObject(status) || typeof status.name !== "string" || !isJsonObject(status.tools)) throw new Error("Codex mcpServerStatus/list returned an invalid restricted-tool-surface server");
|
|
1047
|
+
if (!expectedServers.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation found unexpected server ${status.name}`);
|
|
1048
|
+
if (observedServerNames.has(status.name)) throw new Error(`Codex restricted-tool-surface MCP attestation returned duplicate server ${status.name}`);
|
|
1049
|
+
observedServerNames.add(status.name);
|
|
1050
|
+
if (!Object.hasOwn(status, "serverInfo")) throw new Error(`Codex restricted-tool-surface MCP attestation returned malformed server ${status.name}`);
|
|
1051
|
+
if (expectedServers.get(status.name) === "active") {
|
|
1052
|
+
if (status.serverInfo === null || Object.keys(status.tools).length === 0) throw new Error(`Codex restricted-tool-surface MCP attestation found inactive admitted server ${status.name}`);
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (status.serverInfo !== null) throw new Error(`Codex restricted-tool-surface MCP attestation found active server ${status.name}`);
|
|
1056
|
+
if (Object.keys(status.tools).length > 0) throw new Error(`Codex restricted-tool-surface MCP attestation found tools for server ${status.name}`);
|
|
1057
|
+
}
|
|
1058
|
+
for (const [expectedName, state] of expectedServers) if (!observedServerNames.has(expectedName)) throw new Error(`Codex restricted-tool-surface MCP attestation is missing ${state === "active" ? "admitted " : ""}server ${expectedName}`);
|
|
1059
|
+
if (response.nextCursor !== void 0 && response.nextCursor !== null) throw new Error("Codex mcpServerStatus/list returned an invalid empty-page cursor");
|
|
1060
|
+
}
|
|
1061
|
+
//#endregion
|
|
1062
|
+
//#region extensions/codex/src/app-server/plugin-thread-attestation.ts
|
|
1063
|
+
/**
|
|
1064
|
+
* Checks app availability and enforces restricted MCP surfaces before a turn.
|
|
1065
|
+
*/
|
|
1066
|
+
/** Every admission path checks the same surface; its lifecycle owner keeps the claim fenced. */
|
|
1067
|
+
async function attestCodexThreadToolSurface(params) {
|
|
1068
|
+
params.assertCurrent();
|
|
1069
|
+
if (params.appIds.length > 0) {
|
|
1070
|
+
await params.lifecycleTiming.measure("plugin-app-attestation", () => checkCodexThreadAppAvailability(params));
|
|
1071
|
+
params.assertCurrent();
|
|
1072
|
+
}
|
|
1073
|
+
if (params.restrictedToolSurface) {
|
|
1074
|
+
await params.lifecycleTiming.measure("restricted-tool-surface-mcp-attestation", () => attestCodexRestrictedToolSurfaceMcpServersDisabled(params.client, params.threadId, params.threadConfig, params.signal, params.appIds.length > 0 ? ["codex_apps"] : []));
|
|
1075
|
+
params.assertCurrent();
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
var CodexPluginThreadAppAttestationError = class extends Error {
|
|
1079
|
+
constructor(message, options) {
|
|
1080
|
+
super(message, options);
|
|
1081
|
+
this.name = "CodexPluginThreadAppAttestationError";
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
/** Reads the existing runtime snapshot with the started thread's effective app policy. */
|
|
1085
|
+
async function checkCodexThreadAppAvailability(params) {
|
|
1086
|
+
const appIds = Array.from(new Set(params.appIds.filter(Boolean))).toSorted();
|
|
1087
|
+
if (appIds.length === 0) return;
|
|
1088
|
+
let response;
|
|
1089
|
+
try {
|
|
1090
|
+
response = await params.client.request("app/installed", {
|
|
1091
|
+
threadId: params.threadId,
|
|
1092
|
+
forceRefresh: false
|
|
1093
|
+
}, { signal: params.signal });
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
params.signal?.throwIfAborted();
|
|
1096
|
+
throw new CodexPluginThreadAppAttestationError(`Codex could not confirm admitted apps for thread ${params.threadId}`, { cause: error });
|
|
1097
|
+
}
|
|
1098
|
+
params.signal?.throwIfAborted();
|
|
1099
|
+
const installedById = new Map(response.apps.map((app) => [app.id, app]));
|
|
1100
|
+
const failures = appIds.flatMap((appId) => {
|
|
1101
|
+
const app = installedById.get(appId);
|
|
1102
|
+
if (!app) return [`${appId}:missing`];
|
|
1103
|
+
if (!app.enabled) return [`${appId}:disabled`];
|
|
1104
|
+
return app.callable ? [] : [`${appId}:not-callable`];
|
|
1105
|
+
});
|
|
1106
|
+
if (failures.length > 0) embeddedAgentLog.warn("codex apps unavailable; continuing with remaining tools", {
|
|
1107
|
+
threadId: params.threadId,
|
|
1108
|
+
failures
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
/** Deletes a persistent pre-turn thread; ephemeral threads can only be unsubscribed. */
|
|
1112
|
+
async function discardUnattestedCodexPluginThread(params) {
|
|
1113
|
+
if (params.ephemeral) return await unsubscribeCodexThreadBestEffort(params.client, {
|
|
1114
|
+
threadId: params.threadId,
|
|
1115
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
1116
|
+
});
|
|
1117
|
+
try {
|
|
1118
|
+
await params.client.request("thread/delete", { threadId: params.threadId }, { timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS });
|
|
1119
|
+
return true;
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
embeddedAgentLog.debug("codex plugin app attestation thread deletion failed", {
|
|
1122
|
+
threadId: params.threadId,
|
|
1123
|
+
error
|
|
1124
|
+
});
|
|
1125
|
+
await unsubscribeCodexThreadBestEffort(params.client, {
|
|
1126
|
+
threadId: params.threadId,
|
|
1127
|
+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS
|
|
1128
|
+
});
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
//#endregion
|
|
1133
|
+
//#region extensions/codex/src/app-server/native-hook-relay.ts
|
|
1134
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS = [
|
|
1135
|
+
"pre_tool_use",
|
|
1136
|
+
"post_tool_use",
|
|
1137
|
+
"permission_request",
|
|
1138
|
+
"before_agent_finalize"
|
|
1139
|
+
];
|
|
1140
|
+
const CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS = CODEX_NATIVE_HOOK_RELAY_EVENTS.filter((event) => event !== "permission_request");
|
|
1141
|
+
const CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS = 18e5;
|
|
1142
|
+
/** Extra relay lifetime after the expected turn budget, preventing late hook drops. */
|
|
1143
|
+
const CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS = 3e5;
|
|
1144
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS = 250;
|
|
1145
|
+
const CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS = 1e3;
|
|
1146
|
+
const CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC = 10;
|
|
1147
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS = 1e4;
|
|
1148
|
+
const CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS = 5e3;
|
|
1149
|
+
const MAX_PENDING_DIRECT_CHILD_ADMISSIONS = 32;
|
|
1150
|
+
const CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID = {
|
|
1151
|
+
exec: [
|
|
1152
|
+
"Bash",
|
|
1153
|
+
"exec",
|
|
1154
|
+
"exec_command"
|
|
1155
|
+
],
|
|
1156
|
+
apply_patch: [
|
|
1157
|
+
"apply_patch",
|
|
1158
|
+
"Write",
|
|
1159
|
+
"Edit"
|
|
1160
|
+
],
|
|
1161
|
+
spawn_agent: ["spawn_agent", "Agent"]
|
|
1162
|
+
};
|
|
1163
|
+
var CodexManagedHooksOnlyError = class extends Error {
|
|
1164
|
+
constructor() {
|
|
1165
|
+
super("Codex managed-only hooks disable the OpenClaw native hook relay; refusing unenforced execution");
|
|
1166
|
+
this.name = "CodexManagedHooksOnlyError";
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
/** Enterprise managed-only policy silently drops the session-layer hooks that enforce OpenClaw. */
|
|
1170
|
+
async function assertCodexNativeHookRelayAllowed(client, signal, timeoutMs) {
|
|
1171
|
+
const response = await client.request("configRequirements/read", void 0, {
|
|
1172
|
+
signal,
|
|
1173
|
+
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
1174
|
+
});
|
|
1175
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid hook policy response");
|
|
1176
|
+
const requirements = response.requirements;
|
|
1177
|
+
if (requirements === null) return;
|
|
1178
|
+
if (!isJsonObject(requirements)) throw new Error("Codex configRequirements/read returned invalid hook policy requirements");
|
|
1179
|
+
const managedOnly = requirements.allowManagedHooksOnly;
|
|
1180
|
+
if (managedOnly !== void 0 && managedOnly !== null && typeof managedOnly !== "boolean") throw new Error("Codex configRequirements/read returned invalid managed-only hook policy");
|
|
1181
|
+
if (managedOnly === true) throw new CodexManagedHooksOnlyError();
|
|
1182
|
+
}
|
|
1183
|
+
/** Defers relay unregister so late native hook subprocesses can still resolve. */
|
|
1184
|
+
function scheduleCodexNativeHookRelayUnregister(params) {
|
|
1185
|
+
let pending;
|
|
1186
|
+
const unregister = () => {
|
|
1187
|
+
if (!pending) return;
|
|
1188
|
+
const current = pending;
|
|
1189
|
+
pending = void 0;
|
|
1190
|
+
if (!nativeHookRelayUnregisterQueue.delete(current)) return;
|
|
1191
|
+
params.relay.unregister();
|
|
1192
|
+
nativeHookRelayUnregisterQueue.track(params.relay.drain());
|
|
1193
|
+
};
|
|
1194
|
+
const timeout = setTimeout(unregister, resolveCodexNativeHookRelayUnregisterGraceMs(params.hookTimeoutSec));
|
|
1195
|
+
pending = {
|
|
1196
|
+
timeout,
|
|
1197
|
+
unregister
|
|
1198
|
+
};
|
|
1199
|
+
nativeHookRelayUnregisterQueue.add(pending);
|
|
1200
|
+
timeout.unref();
|
|
1201
|
+
}
|
|
1202
|
+
function resolveCodexNativeHookRelayUnregisterGraceMs(hookTimeoutSec) {
|
|
1203
|
+
const hookTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 0;
|
|
1204
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_UNREGISTER_GRACE_MS, addTimerTimeoutGraceMs(hookTimeoutMs, CODEX_NATIVE_HOOK_RELAY_UNREGISTER_EXTRA_GRACE_MS) ?? 0);
|
|
1205
|
+
}
|
|
1206
|
+
/** Records a native pre-tool failure that Codex does not project as a tool item. */
|
|
1207
|
+
function emitCodexNativePreToolUseFailureDiagnostic(params) {
|
|
1208
|
+
emitTrustedDiagnosticEvent({
|
|
1209
|
+
type: "tool.execution.error",
|
|
1210
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
1211
|
+
sessionId: params.sessionId,
|
|
1212
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
1213
|
+
runId: params.runId,
|
|
1214
|
+
toolName: params.failure.toolName,
|
|
1215
|
+
toolCallId: params.failure.toolCallId,
|
|
1216
|
+
durationMs: params.failure.durationMs,
|
|
1217
|
+
errorCategory: "before_tool_call",
|
|
1218
|
+
terminalReason: params.terminalReason ?? (params.signal?.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : params.failure.disposition),
|
|
1219
|
+
...params.sourceTimestampMs !== void 0 ? { sourceTimestampMs: params.sourceTimestampMs } : {}
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
function createCodexNativeHookRelay(params) {
|
|
1223
|
+
if (params.options?.enabled === false) return;
|
|
1224
|
+
const modelInputTools = params.nativeModelAdmission?.tools ?? ["multi_agent_v1send_input"];
|
|
1225
|
+
const directChildClaims = /* @__PURE__ */ new Map();
|
|
1226
|
+
const pendingDirectChildAdmissions = /* @__PURE__ */ new Map();
|
|
1227
|
+
let foregroundClosed = false;
|
|
1228
|
+
let successfulYieldRetentionAuthorized = false;
|
|
1229
|
+
const assertClaim = (threadId, claim) => () => directChildClaims.get(threadId) === claim;
|
|
1230
|
+
const rejectPendingAdmissions = (reason) => {
|
|
1231
|
+
for (const pending of pendingDirectChildAdmissions.values()) pending.reject(new Error(reason));
|
|
1232
|
+
pendingDirectChildAdmissions.clear();
|
|
1233
|
+
};
|
|
1234
|
+
let releaseProcessAdmission;
|
|
1235
|
+
let processAdmissionDisposed = false;
|
|
1236
|
+
const relay = registerNativeHookRelayForBundledRuntime({
|
|
1237
|
+
provider: "codex",
|
|
1238
|
+
relayId: buildCodexNativeHookRelayId({
|
|
1239
|
+
agentId: params.agentId,
|
|
1240
|
+
sessionId: params.sessionId,
|
|
1241
|
+
sessionKey: params.sessionKey
|
|
1242
|
+
}),
|
|
1243
|
+
...params.generation ? { generation: params.generation } : {},
|
|
1244
|
+
...params.generationMismatchGraceMs ? { generationMismatchGraceMs: params.generationMismatchGraceMs } : {},
|
|
1245
|
+
...params.agentId ? { agentId: params.agentId } : {},
|
|
1246
|
+
sessionId: params.sessionId,
|
|
1247
|
+
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
|
|
1248
|
+
...params.config ? { config: params.config } : {},
|
|
1249
|
+
autoApproveMcpTools: params.autoApproveMcpTools,
|
|
1250
|
+
projectedMcpServers: params.projectedMcpServers,
|
|
1251
|
+
runId: params.runId,
|
|
1252
|
+
...params.channelId ? { channelId: params.channelId } : {},
|
|
1253
|
+
...params.requester ? { requester: params.requester } : {},
|
|
1254
|
+
...params.approvalContext ? { approvalContext: params.approvalContext } : {},
|
|
1255
|
+
allowedEvents: params.events,
|
|
1256
|
+
preToolUseLoopDetection: params.loopDetectionPreToolUseRelay,
|
|
1257
|
+
ttlMs: resolveCodexNativeHookRelayTtlMs({
|
|
1258
|
+
explicitTtlMs: params.options?.ttlMs,
|
|
1259
|
+
attemptTimeoutMs: params.attemptTimeoutMs,
|
|
1260
|
+
startupTimeoutMs: params.startupTimeoutMs,
|
|
1261
|
+
turnStartTimeoutMs: params.turnStartTimeoutMs
|
|
1262
|
+
}),
|
|
1263
|
+
signal: params.signal,
|
|
1264
|
+
runBeforeToolCall: params.hostCapabilities.runBeforeToolCall,
|
|
1265
|
+
executionAdmission: params.nativeProcessAuthority || params.nativeModelAdmission ? {
|
|
1266
|
+
toolNames: [...params.nativeProcessAuthority ? ["exec"] : [], ...params.nativeModelAdmission ? modelInputTools : []],
|
|
1267
|
+
admit: async (invocation, assertAdmissionCurrent, preparation) => {
|
|
1268
|
+
const payload = invocation.rawPayload;
|
|
1269
|
+
if (params.nativeModelAdmission && invocation.toolName && modelInputTools.includes(invocation.toolName)) {
|
|
1270
|
+
const admission = params.nativeModelAdmission;
|
|
1271
|
+
const input = isJsonObject(payload) ? payload.tool_input : void 0;
|
|
1272
|
+
const targetThreadId = isJsonObject(input) && typeof input.target === "string" ? input.target.trim() : void 0;
|
|
1273
|
+
const threadId = readCodexNativeChildThreadId(payload) ?? admission.threadId();
|
|
1274
|
+
if (!threadId || !targetThreadId || !invocation.turnId || !invocation.toolUseId) throw new Error("Codex native input requires exact sender, receiver, turn, and tool identities");
|
|
1275
|
+
assertAdmissionCurrent();
|
|
1276
|
+
const request = {
|
|
1277
|
+
client: admission.client(),
|
|
1278
|
+
threadId,
|
|
1279
|
+
turnId: invocation.turnId,
|
|
1280
|
+
itemId: invocation.toolUseId
|
|
1281
|
+
};
|
|
1282
|
+
await codexNativeSubagentMonitorRuntime.prepareModelInput({
|
|
1283
|
+
...request,
|
|
1284
|
+
target: targetThreadId,
|
|
1285
|
+
readQualification: admission.readQualification,
|
|
1286
|
+
signal: preparation.signal,
|
|
1287
|
+
assertCurrent: preparation.assertCurrent
|
|
1288
|
+
});
|
|
1289
|
+
assertAdmissionCurrent();
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const rootThreadId = isJsonObject(payload) && typeof payload.session_id === "string" ? payload.session_id.trim() : void 0;
|
|
1293
|
+
const childThreadId = readCodexNativeChildThreadId(payload);
|
|
1294
|
+
const threadId = childThreadId ?? rootThreadId;
|
|
1295
|
+
if (!threadId || !invocation.turnId || !invocation.toolUseId) throw new Error("Codex native process admission requires exact thread, turn, and tool identities");
|
|
1296
|
+
params.nativeProcessAuthority.owner.admit(params.nativeProcessAuthority.client(), {
|
|
1297
|
+
threadId,
|
|
1298
|
+
turnId: invocation.turnId,
|
|
1299
|
+
itemId: invocation.toolUseId
|
|
1300
|
+
}, assertAdmissionCurrent, childThreadId ? rootThreadId : void 0);
|
|
1301
|
+
}
|
|
1302
|
+
} : void 0,
|
|
1303
|
+
approvalHost: params.hostCapabilities,
|
|
1304
|
+
assertActive: () => {
|
|
1305
|
+
params.hostCapabilities.assertActive();
|
|
1306
|
+
params.assertCurrent?.();
|
|
1307
|
+
},
|
|
1308
|
+
retention: {
|
|
1309
|
+
readClaim: readCodexNativeChildThreadId,
|
|
1310
|
+
shouldRetainAfterForegroundClose: () => successfulYieldRetentionAuthorized && directChildClaims.size > 0,
|
|
1311
|
+
allowPreToolUse: (childThreadId) => directChildClaims.has(childThreadId),
|
|
1312
|
+
awaitForegroundAdmission: (childThreadId, signal) => {
|
|
1313
|
+
const existingClaim = directChildClaims.get(childThreadId);
|
|
1314
|
+
if (existingClaim) return Promise.resolve(assertClaim(childThreadId, existingClaim));
|
|
1315
|
+
if (foregroundClosed) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission unavailable"));
|
|
1316
|
+
let pending = pendingDirectChildAdmissions.get(childThreadId);
|
|
1317
|
+
if (!pending) {
|
|
1318
|
+
if (pendingDirectChildAdmissions.size >= MAX_PENDING_DIRECT_CHILD_ADMISSIONS) return Promise.reject(/* @__PURE__ */ new Error("native hook relay foreground admission capacity reached"));
|
|
1319
|
+
pending = {
|
|
1320
|
+
...createDeferred(),
|
|
1321
|
+
waiters: 0
|
|
1322
|
+
};
|
|
1323
|
+
pendingDirectChildAdmissions.set(childThreadId, pending);
|
|
1324
|
+
}
|
|
1325
|
+
const admission = pending;
|
|
1326
|
+
admission.waiters++;
|
|
1327
|
+
let onAbort;
|
|
1328
|
+
return new Promise((resolve, reject) => {
|
|
1329
|
+
admission.promise.then(resolve, reject);
|
|
1330
|
+
onAbort = () => reject(toErrorObject(signal?.reason, "native hook relay admission aborted"));
|
|
1331
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1332
|
+
if (signal?.aborted) onAbort();
|
|
1333
|
+
}).then((claim) => assertClaim(childThreadId, claim)).finally(() => {
|
|
1334
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
1335
|
+
admission.waiters--;
|
|
1336
|
+
if (admission.waiters === 0 && pendingDirectChildAdmissions.get(childThreadId) === admission) pendingDirectChildAdmissions.delete(childThreadId);
|
|
1337
|
+
});
|
|
1338
|
+
},
|
|
1339
|
+
onDispose: () => {
|
|
1340
|
+
foregroundClosed = true;
|
|
1341
|
+
rejectPendingAdmissions("native hook relay registration closed");
|
|
1342
|
+
processAdmissionDisposed = true;
|
|
1343
|
+
releaseProcessAdmission?.();
|
|
1344
|
+
}
|
|
1345
|
+
},
|
|
1346
|
+
onPreToolUseFailure: params.onPreToolUseFailure,
|
|
1347
|
+
command: {
|
|
1348
|
+
nice: 10,
|
|
1349
|
+
timeoutMs: params.options?.gatewayTimeoutMs
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
if (!processAdmissionDisposed) try {
|
|
1353
|
+
releaseProcessAdmission = params.nativeProcessAuthority?.owner.retainAdmission();
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
relay.unregister();
|
|
1356
|
+
throw error;
|
|
1357
|
+
}
|
|
1358
|
+
const unregister = () => {
|
|
1359
|
+
foregroundClosed = true;
|
|
1360
|
+
rejectPendingAdmissions("native hook relay foreground closed");
|
|
1361
|
+
relay.unregister();
|
|
1362
|
+
};
|
|
1363
|
+
return {
|
|
1364
|
+
...relay,
|
|
1365
|
+
unregister,
|
|
1366
|
+
authorizeRetentionAfterSuccessfulYield: () => {
|
|
1367
|
+
successfulYieldRetentionAuthorized = true;
|
|
1368
|
+
},
|
|
1369
|
+
hasClaimedDirectChild: () => directChildClaims.size > 0,
|
|
1370
|
+
rejectPendingDirectChild: (threadIdInput, reason) => {
|
|
1371
|
+
const threadId = threadIdInput.trim();
|
|
1372
|
+
const pending = threadId ? pendingDirectChildAdmissions.get(threadId) : void 0;
|
|
1373
|
+
if (!pending) return;
|
|
1374
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
1375
|
+
pending.reject(new Error(reason));
|
|
1376
|
+
},
|
|
1377
|
+
claimDirectChild: (threadIdInput) => {
|
|
1378
|
+
const threadId = threadIdInput.trim();
|
|
1379
|
+
if (!threadId) return () => void 0;
|
|
1380
|
+
if (directChildClaims.get(threadId)) return () => void 0;
|
|
1381
|
+
const claim = Symbol(threadId);
|
|
1382
|
+
directChildClaims.set(threadId, claim);
|
|
1383
|
+
const pending = pendingDirectChildAdmissions.get(threadId);
|
|
1384
|
+
pendingDirectChildAdmissions.delete(threadId);
|
|
1385
|
+
pending?.resolve(claim);
|
|
1386
|
+
let released = false;
|
|
1387
|
+
return () => {
|
|
1388
|
+
if (released) return;
|
|
1389
|
+
released = true;
|
|
1390
|
+
if (directChildClaims.get(threadId) !== claim) return;
|
|
1391
|
+
directChildClaims.delete(threadId);
|
|
1392
|
+
if (foregroundClosed && directChildClaims.size === 0) {
|
|
1393
|
+
relay.unregister();
|
|
1394
|
+
nativeHookRelayUnregisterQueue.track(relay.drain());
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
function readCodexNativeChildThreadId(rawPayload) {
|
|
1401
|
+
if (!isJsonObject(rawPayload) || typeof rawPayload.agent_id !== "string") return;
|
|
1402
|
+
return rawPayload.agent_id.trim() || void 0;
|
|
1403
|
+
}
|
|
1404
|
+
function resolveCodexNativeHookRelayEvents(params) {
|
|
1405
|
+
if (params.configuredEvents?.length) return params.configuredEvents;
|
|
1406
|
+
return params.appServer.approvalPolicy === "never" ? CODEX_NATIVE_HOOK_RELAY_EVENTS : CODEX_NATIVE_HOOK_RELAY_EVENTS_WITH_APP_SERVER_APPROVALS;
|
|
1407
|
+
}
|
|
1408
|
+
function resolveCodexNativeHookRelayTtlMs(params) {
|
|
1409
|
+
if (params.explicitTtlMs !== void 0) return params.explicitTtlMs;
|
|
1410
|
+
const relayBudgetMs = params.attemptTimeoutMs + params.startupTimeoutMs + params.turnStartTimeoutMs + CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS;
|
|
1411
|
+
return Math.max(CODEX_NATIVE_HOOK_RELAY_MIN_TTL_MS, Math.floor(relayBudgetMs));
|
|
1412
|
+
}
|
|
1413
|
+
function buildCodexNativeHookRelayId(params) {
|
|
1414
|
+
const hash = createHash("sha256");
|
|
1415
|
+
hash.update("openclaw:codex:native-hook-relay:v1");
|
|
1416
|
+
hash.update("\0");
|
|
1417
|
+
hash.update(params.agentId?.trim() || "");
|
|
1418
|
+
hash.update("\0");
|
|
1419
|
+
hash.update(params.sessionKey?.trim() || params.sessionId);
|
|
1420
|
+
return `codex-${hash.digest("hex").slice(0, 40)}`;
|
|
1421
|
+
}
|
|
1422
|
+
const CODEX_HOOK_EVENT_BY_NATIVE_EVENT = {
|
|
1423
|
+
pre_tool_use: "PreToolUse",
|
|
1424
|
+
post_tool_use: "PostToolUse",
|
|
1425
|
+
permission_request: "PermissionRequest",
|
|
1426
|
+
before_agent_finalize: "Stop"
|
|
1427
|
+
};
|
|
1428
|
+
const CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT = {
|
|
1429
|
+
pre_tool_use: "pre_tool_use",
|
|
1430
|
+
post_tool_use: "post_tool_use",
|
|
1431
|
+
permission_request: "permission_request",
|
|
1432
|
+
before_agent_finalize: "stop"
|
|
1433
|
+
};
|
|
1434
|
+
const CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS = ["/<session-flags>/config.toml", "<session-flags>/config.toml"];
|
|
1435
|
+
function buildCodexNativeHookRelayConfig(params) {
|
|
1436
|
+
const events = params.events?.length ? params.events : CODEX_NATIVE_HOOK_RELAY_EVENTS;
|
|
1437
|
+
const selectedEvents = new Set(events);
|
|
1438
|
+
const config = { "features.hooks": true };
|
|
1439
|
+
const hookState = {};
|
|
1440
|
+
for (const event of CODEX_NATIVE_HOOK_RELAY_EVENTS) {
|
|
1441
|
+
const codexEvent = CODEX_HOOK_EVENT_BY_NATIVE_EVENT[event];
|
|
1442
|
+
const selected = selectedEvents.has(event);
|
|
1443
|
+
const shouldRelay = params.relay.shouldRelayEvent(event);
|
|
1444
|
+
if (!selected || !shouldRelay) {
|
|
1445
|
+
if (selected || params.clearOmittedEvents) config[`hooks.${codexEvent}`] = [];
|
|
1446
|
+
if (params.clearOmittedEvents) for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = { enabled: false };
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const timeout = normalizeHookTimeoutSec(params.hookTimeoutSec);
|
|
1450
|
+
const command = params.relay.commandForEvent(event, { timeoutMs: resolveCodexNativeHookRelayCommandTimeoutMs(timeout) });
|
|
1451
|
+
const matcher = buildCodexNativeToolMatcher(params.relay.toolMatcherForEvent(event));
|
|
1452
|
+
const group = {
|
|
1453
|
+
...matcher ? { matcher } : {},
|
|
1454
|
+
hooks: [{
|
|
1455
|
+
type: "command",
|
|
1456
|
+
command,
|
|
1457
|
+
timeout,
|
|
1458
|
+
async: false,
|
|
1459
|
+
statusMessage: "OpenClaw native hook relay"
|
|
1460
|
+
}]
|
|
1461
|
+
};
|
|
1462
|
+
config[`hooks.${codexEvent}`] = [group];
|
|
1463
|
+
const state = {
|
|
1464
|
+
enabled: true,
|
|
1465
|
+
trusted_hash: `sha256:${createHash("sha256").update(stringifyCodexPolicy({
|
|
1466
|
+
event_name: CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event],
|
|
1467
|
+
...group
|
|
1468
|
+
})).digest("hex")}`
|
|
1469
|
+
};
|
|
1470
|
+
for (const sourcePath of CODEX_SESSION_FLAGS_HOOK_SOURCE_PATHS) hookState[`${sourcePath}:${CODEX_HOOK_KEY_LABEL_BY_NATIVE_EVENT[event]}:0:0`] = state;
|
|
1471
|
+
}
|
|
1472
|
+
config["hooks.state"] = hookState;
|
|
1473
|
+
return config;
|
|
1474
|
+
}
|
|
1475
|
+
function buildCodexNativeHookRelayDisabledConfig() {
|
|
1476
|
+
return {
|
|
1477
|
+
"features.hooks": false,
|
|
1478
|
+
"hooks.PreToolUse": [],
|
|
1479
|
+
"hooks.PostToolUse": [],
|
|
1480
|
+
"hooks.PermissionRequest": [],
|
|
1481
|
+
"hooks.Stop": []
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
function normalizeHookTimeoutSec(value) {
|
|
1485
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.ceil(value) : CODEX_NATIVE_HOOK_RELAY_DEFAULT_TIMEOUT_SEC;
|
|
1486
|
+
}
|
|
1487
|
+
function resolveCodexNativeHookRelayCommandTimeoutMs(hookTimeoutSec) {
|
|
1488
|
+
const parentTimeoutMs = finiteSecondsToTimerSafeMilliseconds(normalizeHookTimeoutSec(hookTimeoutSec)) ?? 5e3;
|
|
1489
|
+
const parentMarginMs = Math.min(CODEX_NATIVE_HOOK_RELAY_COMMAND_MAX_PARENT_MARGIN_MS, Math.max(CODEX_NATIVE_HOOK_RELAY_COMMAND_MIN_PARENT_MARGIN_MS, Math.floor(parentTimeoutMs / 5)));
|
|
1490
|
+
return Math.max(1, parentTimeoutMs - parentMarginMs);
|
|
1491
|
+
}
|
|
1492
|
+
function buildCodexNativeToolMatcher(toolNames) {
|
|
1493
|
+
if (toolNames === void 0) return;
|
|
1494
|
+
if (toolNames.length === 0) throw new TypeError("Codex native hook matcher requires at least one tool name");
|
|
1495
|
+
const nativeNames = /* @__PURE__ */ new Set();
|
|
1496
|
+
let hasCustomToolName = false;
|
|
1497
|
+
for (const toolName of toolNames) {
|
|
1498
|
+
const canonicalToolName = toolName.trim();
|
|
1499
|
+
if (!canonicalToolName || canonicalToolName === "*") throw new TypeError("Codex native hook matcher requires canonical OpenClaw tool ids");
|
|
1500
|
+
const nativeAliases = CODEX_HOOK_MATCHER_NAMES_BY_TOOL_ID[canonicalToolName];
|
|
1501
|
+
if (!nativeAliases) hasCustomToolName = true;
|
|
1502
|
+
for (const nativeName of nativeAliases ?? [canonicalToolName]) nativeNames.add(nativeName);
|
|
1503
|
+
}
|
|
1504
|
+
const sortedNames = Array.from(nativeNames).toSorted();
|
|
1505
|
+
if (!hasCustomToolName && sortedNames.every((toolName) => /^[A-Za-z0-9_]+$/.test(toolName))) return sortedNames.join("|");
|
|
1506
|
+
return `(?i)^(?:${sortedNames.map((toolName) => toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`;
|
|
1507
|
+
}
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region extensions/codex/src/app-server/project-doc-thread-config.ts
|
|
1510
|
+
const CODEX_NATIVE_PROJECT_DOC_MAX_BYTES = 131072;
|
|
1511
|
+
function buildCodexProjectDocThreadConfig(config, effectiveNativeConfig) {
|
|
1512
|
+
const defaults = { project_doc_max_bytes: resolveCodexNativeProjectDocMaxBytes(effectiveNativeConfig) ?? CODEX_NATIVE_PROJECT_DOC_MAX_BYTES };
|
|
1513
|
+
return mergeCodexThreadConfigs(defaults, config) ?? defaults;
|
|
1514
|
+
}
|
|
1515
|
+
function resolveCodexNativeProjectDocMaxBytes(effectiveNativeConfig) {
|
|
1516
|
+
if (effectiveNativeConfig?.origins?.project_doc_max_bytes === void 0) return;
|
|
1517
|
+
const authoredMaxBytes = effectiveNativeConfig.config.project_doc_max_bytes;
|
|
1518
|
+
if (typeof authoredMaxBytes !== "number" || !Number.isSafeInteger(authoredMaxBytes) || authoredMaxBytes < 0) throw new Error("Codex config/read returned an invalid project_doc_max_bytes value");
|
|
1519
|
+
return authoredMaxBytes;
|
|
1520
|
+
}
|
|
1521
|
+
function mergeCodexNativeProjectDocThreadConfig(config, effectiveNativeConfig) {
|
|
1522
|
+
const authoredMaxBytes = resolveCodexNativeProjectDocMaxBytes(effectiveNativeConfig);
|
|
1523
|
+
return authoredMaxBytes === void 0 ? config : mergeCodexThreadConfigs({ project_doc_max_bytes: authoredMaxBytes }, config);
|
|
1524
|
+
}
|
|
1525
|
+
//#endregion
|
|
1526
|
+
//#region extensions/codex/src/app-server/developer-instruction-sections.ts
|
|
1527
|
+
function joinPresentSections(...sections) {
|
|
1528
|
+
return sections.filter((section) => Boolean(section?.trim())).join("\n\n");
|
|
1529
|
+
}
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region extensions/codex/src/app-server/thread-model-selection.ts
|
|
1532
|
+
const CODEX_NATIVE_PERSONALITY_NONE = "none";
|
|
1533
|
+
function resolveCodexBindingModelProviderFallback(params) {
|
|
1534
|
+
const provider = params.provider?.trim().toLowerCase();
|
|
1535
|
+
if (provider && provider !== "codex") return;
|
|
1536
|
+
const currentModel = params.currentModel?.trim();
|
|
1537
|
+
const bindingModel = params.bindingModel?.trim();
|
|
1538
|
+
if (currentModel && bindingModel && currentModel === bindingModel && params.bindingModelProvider) return params.bindingModelProvider;
|
|
1539
|
+
return hasProviderQualifiedModelRef(currentModel) ? void 0 : params.bindingModelProvider;
|
|
1540
|
+
}
|
|
1541
|
+
function resolveCodexAppServerThreadModelSelection(params) {
|
|
1542
|
+
const authProfileId = params.inheritBindingAuthProfile === false ? params.authProfileId : params.authProfileId ?? params.binding?.authProfileId;
|
|
1543
|
+
const explicitModelProvider = resolveCodexAppServerModelProvider({
|
|
1544
|
+
...params,
|
|
1545
|
+
authProfileId
|
|
1546
|
+
});
|
|
1547
|
+
const bindingModelProvider = params.binding?.threadId ? resolveCodexBindingModelProviderFallback({
|
|
1548
|
+
provider: params.provider,
|
|
1549
|
+
currentModel: params.model,
|
|
1550
|
+
bindingModel: params.binding.model,
|
|
1551
|
+
bindingModelProvider: params.binding.modelProvider
|
|
1552
|
+
}) : void 0;
|
|
1553
|
+
return resolveCodexAppServerRequestModelSelection({
|
|
1554
|
+
...params,
|
|
1555
|
+
model: params.requestModel ?? params.model,
|
|
1556
|
+
modelProvider: explicitModelProvider ?? bindingModelProvider,
|
|
1557
|
+
authProfileId
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
function resolveCodexAppServerRequestModelSelection(params) {
|
|
1561
|
+
const model = params.model.trim();
|
|
1562
|
+
const modelProvider = params.modelProvider?.trim();
|
|
1563
|
+
if (modelProvider) return {
|
|
1564
|
+
model,
|
|
1565
|
+
modelProvider
|
|
1566
|
+
};
|
|
1567
|
+
const slashIndex = model.indexOf("/");
|
|
1568
|
+
if (slashIndex <= 0 || slashIndex >= model.length - 1) return { model };
|
|
1569
|
+
const inferredProvider = model.slice(0, slashIndex);
|
|
1570
|
+
const inferredModelProvider = resolveCodexAppServerModelProvider({
|
|
1571
|
+
...params,
|
|
1572
|
+
provider: inferredProvider
|
|
1573
|
+
});
|
|
1574
|
+
return {
|
|
1575
|
+
model: model.slice(slashIndex + 1).trim(),
|
|
1576
|
+
...inferredModelProvider ? { modelProvider: inferredModelProvider } : {}
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
function hasProviderQualifiedModelRef(model) {
|
|
1580
|
+
const trimmed = model?.trim();
|
|
1581
|
+
const slashIndex = trimmed?.indexOf("/") ?? -1;
|
|
1582
|
+
return slashIndex > 0 && slashIndex < (trimmed?.length ?? 0) - 1;
|
|
1583
|
+
}
|
|
1584
|
+
function resolveCodexAppServerModelProvider(params) {
|
|
1585
|
+
const normalized = params.provider.trim();
|
|
1586
|
+
const normalizedLower = normalized.toLowerCase();
|
|
1587
|
+
if (normalizedLower === "openai" && params.authProfileId && isCodexResponsesOAuthCredential(params.authProfileStore?.profiles[params.authProfileId])) return CODEX_RESPONSES_OAUTH_PROVIDER;
|
|
1588
|
+
if (!normalized || normalizedLower === "codex") return;
|
|
1589
|
+
if (normalizedLower === "openai" && (params.homeScope === "user" || isCodexAppServerNativeAuthProfile(params))) return;
|
|
1590
|
+
return normalizedLower === "openai" ? "openai" : normalized;
|
|
1591
|
+
}
|
|
1592
|
+
//#endregion
|
|
1593
|
+
//#region extensions/codex/src/app-server/thread-prompt.ts
|
|
1594
|
+
function buildDeveloperInstructions(params, options = {}) {
|
|
1595
|
+
const deferredToolNames = /* @__PURE__ */ new Set();
|
|
1596
|
+
let screenToolName;
|
|
1597
|
+
let showWidgetToolName;
|
|
1598
|
+
let dashboardToolName;
|
|
1599
|
+
let portalToolName;
|
|
1600
|
+
let messageTool;
|
|
1601
|
+
let hasSkillWorkshop = false;
|
|
1602
|
+
let hasSessionsSpawn = false;
|
|
1603
|
+
let hasSessionsYield = false;
|
|
1604
|
+
let hasSubagentsList = false;
|
|
1605
|
+
let hasSessionsSend = false;
|
|
1606
|
+
let hasControlTools = false;
|
|
1607
|
+
let hasSeenDirectNamespace = false;
|
|
1608
|
+
for (const spec of options.dynamicTools ?? []) {
|
|
1609
|
+
const isDirectNamespace = spec.type === "namespace" && !hasSeenDirectNamespace && spec.name.trim() === "openclaw_direct";
|
|
1610
|
+
if (isDirectNamespace) hasSeenDirectNamespace = true;
|
|
1611
|
+
for (const tool of spec.type === "namespace" ? spec.tools : [spec]) {
|
|
1612
|
+
const name = tool.name.trim();
|
|
1613
|
+
const qualifiedName = spec.type === "namespace" ? `${spec.name}.${name}` : name;
|
|
1614
|
+
if (tool.deferLoading === true && name) deferredToolNames.add(name);
|
|
1615
|
+
if (name === "screen") screenToolName ??= qualifiedName;
|
|
1616
|
+
if (name === "show_widget") showWidgetToolName ??= qualifiedName;
|
|
1617
|
+
if (name === "dashboard") dashboardToolName ??= qualifiedName;
|
|
1618
|
+
if (name === "portal") portalToolName ??= qualifiedName;
|
|
1619
|
+
if (name === "message") messageTool ??= {
|
|
1620
|
+
name: qualifiedName,
|
|
1621
|
+
parameters: tool.inputSchema
|
|
1622
|
+
};
|
|
1623
|
+
hasSkillWorkshop ||= name === SKILL_WORKSHOP_TOOL_NAME;
|
|
1624
|
+
hasSessionsSpawn ||= name === "sessions_spawn";
|
|
1625
|
+
hasSessionsYield ||= isDirectNamespace && name === "sessions_yield";
|
|
1626
|
+
hasSubagentsList ||= name === "subagents";
|
|
1627
|
+
hasSessionsSend ||= name === "sessions_send";
|
|
1628
|
+
hasControlTools ||= name === "openclaw" || name === "gateway";
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const nativeCommandGuidance = listRegisteredPluginAgentPromptGuidance({
|
|
1632
|
+
surface: "codex_app_server",
|
|
1633
|
+
includeLegacyGlobalGuidance: false
|
|
1634
|
+
}).join("\n");
|
|
1635
|
+
const delegationGuidanceAvailable = params.disableTools !== true && params.toolsAllow?.length !== 0 && params.delegationCapability !== "report_only" && !isMessageOnlyCodexSourceReply(params);
|
|
1636
|
+
const nativeDelegationAvailable = delegationGuidanceAvailable && !isCodexResponsesOAuthRun(params) && !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) && !shouldDisableCodexToolSearchForModel(params.modelId);
|
|
1637
|
+
const deferredToolDiscoveryGuidance = deferredToolNames.size > 0 || nativeDelegationAvailable ? "Deferred tools may be absent from the direct tool list. Use `tool_search` when directly callable. On code-mode-only models, use `exec` instead: filter `ALL_TOOLS` by name and description, then call the matching entry through `tools`." : void 0;
|
|
1638
|
+
return [
|
|
1639
|
+
"You are a personal agent running inside OpenClaw. OpenClaw has dynamic tools for OpenClaw-owned messaging, cron, sessions, media, gateway, and nodes.",
|
|
1640
|
+
deferredToolNames.size > 0 ? `Deferred searchable OpenClaw dynamic tools available: ${[...deferredToolNames].toSorted((left, right) => left.localeCompare(right)).join(", ")}.` : void 0,
|
|
1641
|
+
deferredToolDiscoveryGuidance,
|
|
1642
|
+
hasSkillWorkshop ? buildSkillWorkshopPromptSection().join("\n") : void 0,
|
|
1643
|
+
nativeDelegationAvailable ? `Use Codex native \`spawn_agent\` for Codex subagents. \`spawn_agent\` and the other native collaboration tools may be deferred. For follow-up work on an existing native child, use the native collaboration tool that starts or queues a new turn.${hasSessionsSpawn ? " Use OpenClaw `sessions_spawn` only for OpenClaw or ACP delegation, never as a substitute for `spawn_agent` on internal legwork." : ""}` : void 0,
|
|
1644
|
+
hasSessionsYield && nativeDelegationAvailable ? "When a native child's result belongs in a later turn, end the current turn with `openclaw_direct.sessions_yield`; the completion arrives as the next model-visible input. Use native `wait_agent` only for an intentional same-turn wait when the immediate next step is blocked on the child. Never loop-poll for native child completion." : void 0,
|
|
1645
|
+
delegationGuidanceAvailable ? buildDelegationGuidanceSection({
|
|
1646
|
+
mode: resolveMainSessionDelegationMode({
|
|
1647
|
+
config: params.config,
|
|
1648
|
+
agentId: params.agentId,
|
|
1649
|
+
sessionKey: params.sessionKey
|
|
1650
|
+
}),
|
|
1651
|
+
isMinimal: params.promptMode === "minimal" || params.promptMode === "none",
|
|
1652
|
+
hiddenDelegationTool: nativeDelegationAvailable ? "native `spawn_agent`" : hasSessionsSpawn ? "`sessions_spawn`" : "",
|
|
1653
|
+
hasVisibleSessionSpawn: hasSessionsSpawn,
|
|
1654
|
+
hasSessionsYield,
|
|
1655
|
+
hasSubagentsList,
|
|
1656
|
+
hasSessionsSend
|
|
1657
|
+
}).join("\n") : void 0,
|
|
1658
|
+
params.disableTools !== true && params.promptMode !== "minimal" && params.promptMode !== "none" ? buildUiPresentationPrompt({
|
|
1659
|
+
screenToolName,
|
|
1660
|
+
showWidgetToolName,
|
|
1661
|
+
dashboardToolName,
|
|
1662
|
+
portalToolName,
|
|
1663
|
+
messageTool
|
|
1664
|
+
}) : void 0,
|
|
1665
|
+
buildCredentialSafetyPrompt({ controlToolsAvailable: params.disableTools !== true && hasControlTools }),
|
|
1666
|
+
nativeCommandGuidance,
|
|
1667
|
+
params.gitCoauthorPrompt,
|
|
1668
|
+
params.extraSystemPrompt
|
|
1669
|
+
].filter((section) => typeof section === "string" && section.trim()).join("\n\n");
|
|
1670
|
+
}
|
|
1671
|
+
//#endregion
|
|
1672
|
+
//#region extensions/codex/src/app-server/thread-shell-environment.ts
|
|
1673
|
+
/** Retain native policy beneath request overrides before applying host-owned values. */
|
|
1674
|
+
function mergeCodexNativeShellEnvironment(config, nativePolicy) {
|
|
1675
|
+
return isJsonObject(nativePolicy) ? mergeCodexThreadConfigs({ shell_environment_policy: Object.fromEntries(Object.entries(nativePolicy).filter(([, value]) => value !== null)) }, config) : config;
|
|
1676
|
+
}
|
|
1677
|
+
/** Applies host-selected values and any required login-shell restriction last. */
|
|
1678
|
+
function applyCodexManagedShellEnvironment(config, environment, disableLoginShell = false, pathPrepend) {
|
|
1679
|
+
if (!environment || Object.keys(environment).length === 0) return disableLoginShell ? {
|
|
1680
|
+
...config,
|
|
1681
|
+
allow_login_shell: false
|
|
1682
|
+
} : config;
|
|
1683
|
+
const current = isJsonObject(config.shell_environment_policy) ? config.shell_environment_policy : {};
|
|
1684
|
+
const currentSet = isJsonObject(current.set) ? current.set : {};
|
|
1685
|
+
const managedEnvironment = { ...environment };
|
|
1686
|
+
if (pathPrepend?.length) for (const key of Object.keys(environment)) {
|
|
1687
|
+
if (key.toUpperCase() !== "PATH") continue;
|
|
1688
|
+
const aliases = Object.keys(currentSet).filter((name) => name === key || process.platform === "win32" && name.toUpperCase() === "PATH");
|
|
1689
|
+
const authoredKey = aliases.includes(key) ? key : aliases[0];
|
|
1690
|
+
if (authoredKey !== void 0 && typeof currentSet[authoredKey] === "string") {
|
|
1691
|
+
const merged = normalizeUniqueStringEntries([...pathPrepend, ...currentSet[authoredKey].split(path.delimiter)]).join(path.delimiter);
|
|
1692
|
+
for (const name of [key, ...aliases]) managedEnvironment[name] = merged;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
const names = Object.keys(managedEnvironment).toSorted();
|
|
1696
|
+
const includeOnly = Array.isArray(current.include_only) ? current.include_only.filter((entry) => typeof entry === "string") : [];
|
|
1697
|
+
const filters = isJsonObject(current.filters) ? current.filters : void 0;
|
|
1698
|
+
const hasIncludeFilter = filters && Object.values(filters).includes("include");
|
|
1699
|
+
const managedFilterNames = new Set(names.map((name) => name.toLowerCase()));
|
|
1700
|
+
const managedConfig = {
|
|
1701
|
+
...config,
|
|
1702
|
+
shell_environment_policy: {
|
|
1703
|
+
...current,
|
|
1704
|
+
experimental_use_profile: false,
|
|
1705
|
+
set: {
|
|
1706
|
+
...currentSet,
|
|
1707
|
+
...managedEnvironment
|
|
1708
|
+
},
|
|
1709
|
+
...filters ? hasIncludeFilter ? { filters: {
|
|
1710
|
+
...Object.fromEntries(Object.entries(filters).filter(([name]) => !managedFilterNames.has(name.toLowerCase()))),
|
|
1711
|
+
...Object.fromEntries([...managedFilterNames].map((name) => [name, "include"]))
|
|
1712
|
+
} } : {} : includeOnly.length > 0 ? { include_only: [.../* @__PURE__ */ new Set([...includeOnly, ...names])] } : {}
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
return disableLoginShell ? {
|
|
1716
|
+
...managedConfig,
|
|
1717
|
+
allow_login_shell: false
|
|
1718
|
+
} : managedConfig;
|
|
1719
|
+
}
|
|
1720
|
+
//#endregion
|
|
1721
|
+
//#region extensions/codex/src/app-server/web-search.ts
|
|
1722
|
+
const CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG = {
|
|
1723
|
+
"features.standalone_web_search": false,
|
|
1724
|
+
web_search: "disabled"
|
|
1725
|
+
};
|
|
1726
|
+
function hasManagedSearchProvider(config) {
|
|
1727
|
+
return normalizeOptionalString(config?.tools?.web?.search?.provider) !== void 0;
|
|
1728
|
+
}
|
|
1729
|
+
function hasNativeDomainRestrictions(config) {
|
|
1730
|
+
return normalizeUniqueTrimmedStringList(config?.tools?.web?.search?.openaiCodex?.allowedDomains).length > 0;
|
|
1731
|
+
}
|
|
1732
|
+
function buildCodexNativeWebSearchThreadConfig(config) {
|
|
1733
|
+
const nativeConfig = config?.tools?.web?.search?.openaiCodex;
|
|
1734
|
+
const threadConfig = {
|
|
1735
|
+
"features.standalone_web_search": false,
|
|
1736
|
+
web_search: nativeConfig?.mode === "live" ? "live" : "cached"
|
|
1737
|
+
};
|
|
1738
|
+
const allowedDomains = normalizeUniqueTrimmedStringList(nativeConfig?.allowedDomains);
|
|
1739
|
+
if (allowedDomains.length > 0) threadConfig["tools.web_search.allowed_domains"] = allowedDomains;
|
|
1740
|
+
if (nativeConfig?.contextSize) threadConfig["tools.web_search.context_size"] = nativeConfig.contextSize;
|
|
1741
|
+
for (const key of [
|
|
1742
|
+
"country",
|
|
1743
|
+
"region",
|
|
1744
|
+
"city",
|
|
1745
|
+
"timezone"
|
|
1746
|
+
]) {
|
|
1747
|
+
const value = normalizeOptionalString(nativeConfig?.userLocation?.[key]);
|
|
1748
|
+
if (value) threadConfig[`tools.web_search.location.${key}`] = value;
|
|
1749
|
+
}
|
|
1750
|
+
return threadConfig;
|
|
1751
|
+
}
|
|
1752
|
+
function resolveCodexWebSearchPlan(params) {
|
|
1753
|
+
if (params.disableTools === true || params.webSearchAllowed === false || params.config?.tools?.web?.search?.enabled === false) return {
|
|
1754
|
+
kind: "disabled",
|
|
1755
|
+
suppressManagedWebSearch: true,
|
|
1756
|
+
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1757
|
+
};
|
|
1758
|
+
const nativeConfig = params.config?.tools?.web?.search?.openaiCodex;
|
|
1759
|
+
const managedSearchExplicit = hasManagedSearchProvider(params.config) || nativeConfig?.enabled === false;
|
|
1760
|
+
const nativeProviderSupportsSearch = params.nativeProviderWebSearchSupport === void 0 || params.nativeProviderWebSearchSupport === "supported";
|
|
1761
|
+
if (!(params.nativeToolSurfaceEnabled !== false && nativeProviderSupportsSearch && nativeConfig?.enabled !== false && !hasManagedSearchProvider(params.config))) {
|
|
1762
|
+
if (!managedSearchExplicit && hasNativeDomainRestrictions(params.config)) return {
|
|
1763
|
+
kind: "disabled",
|
|
1764
|
+
suppressManagedWebSearch: true,
|
|
1765
|
+
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1766
|
+
};
|
|
1767
|
+
return {
|
|
1768
|
+
kind: "managed",
|
|
1769
|
+
suppressManagedWebSearch: false,
|
|
1770
|
+
threadConfig: CODEX_NATIVE_WEB_SEARCH_DISABLED_CONFIG
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
return {
|
|
1774
|
+
kind: "native-hosted",
|
|
1775
|
+
suppressManagedWebSearch: true,
|
|
1776
|
+
threadConfig: buildCodexNativeWebSearchThreadConfig(params.config),
|
|
1777
|
+
webFetchHostnameAllowlist: buildHostnameAllowlistPolicyFromSuffixAllowlist(nativeConfig?.allowedDomains)?.hostnameAllowlist
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
const CODEX_CODE_MODE_THREAD_CONFIG = {
|
|
1781
|
+
"features.code_mode": true,
|
|
1782
|
+
"features.code_mode_only": false,
|
|
1783
|
+
"features.shell_tool": true,
|
|
1784
|
+
"features.apply_patch_streaming_events": true,
|
|
1785
|
+
suppress_unstable_features_warning: true
|
|
1786
|
+
};
|
|
1787
|
+
const CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG = { "features.goals": false };
|
|
1788
|
+
const CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG = { "tools.update_plan.enabled": false };
|
|
1789
|
+
const CODEX_CODE_MODE_DISABLED_THREAD_CONFIG = {
|
|
1790
|
+
"features.code_mode": false,
|
|
1791
|
+
"features.code_mode_only": false
|
|
1792
|
+
};
|
|
1793
|
+
const CODEX_NO_PROJECT_DOCS_CONFIG = { project_doc_max_bytes: 0 };
|
|
1794
|
+
const CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG = { "features.multi_agent": false };
|
|
1795
|
+
const CODEX_DELEGATION_DISABLED_THREAD_CONFIG = {
|
|
1796
|
+
"agents.enabled": false,
|
|
1797
|
+
"features.multi_agent": false,
|
|
1798
|
+
"features.multi_agent_v2": false
|
|
1799
|
+
};
|
|
1800
|
+
const CODEX_RING_ZERO_RESTRICTED_FEATURES = /* @__PURE__ */ new Set([
|
|
1801
|
+
"apps",
|
|
1802
|
+
"artifact",
|
|
1803
|
+
"browser_use",
|
|
1804
|
+
"browser_use_external",
|
|
1805
|
+
"browser_use_full_cdp_access",
|
|
1806
|
+
"chronicle",
|
|
1807
|
+
"code_mode",
|
|
1808
|
+
"code_mode_only",
|
|
1809
|
+
"computer_use",
|
|
1810
|
+
"context_management",
|
|
1811
|
+
"current_time_reminder",
|
|
1812
|
+
"default_mode_request_user_input",
|
|
1813
|
+
"deferred_executor",
|
|
1814
|
+
"goals",
|
|
1815
|
+
"hooks",
|
|
1816
|
+
"image_generation",
|
|
1817
|
+
"memories",
|
|
1818
|
+
"multi_agent",
|
|
1819
|
+
"multi_agent_v2",
|
|
1820
|
+
"plugins",
|
|
1821
|
+
"request_permissions_tool",
|
|
1822
|
+
"skill_search",
|
|
1823
|
+
"shell_tool",
|
|
1824
|
+
"standalone_web_search",
|
|
1825
|
+
"token_budget",
|
|
1826
|
+
"unified_exec",
|
|
1827
|
+
"view_image",
|
|
1828
|
+
"web_search_cached",
|
|
1829
|
+
"web_search_request",
|
|
1830
|
+
"workspace_dependencies"
|
|
1831
|
+
]);
|
|
1832
|
+
const CODEX_RING_ZERO_THREAD_CONFIG = {
|
|
1833
|
+
...CODEX_DELEGATION_DISABLED_THREAD_CONFIG,
|
|
1834
|
+
...Object.fromEntries([...CODEX_RING_ZERO_RESTRICTED_FEATURES].map((feature) => [`features.${feature}`, false])),
|
|
1835
|
+
"orchestrator.mcp.enabled": false,
|
|
1836
|
+
"orchestrator.skills.enabled": false,
|
|
1837
|
+
"skills.bundled.enabled": false,
|
|
1838
|
+
"skills.include_instructions": false,
|
|
1839
|
+
"tools.experimental_request_user_input.enabled": false,
|
|
1840
|
+
hooks: {
|
|
1841
|
+
PreToolUse: [],
|
|
1842
|
+
PermissionRequest: [],
|
|
1843
|
+
PostToolUse: [],
|
|
1844
|
+
PreCompact: [],
|
|
1845
|
+
PostCompact: [],
|
|
1846
|
+
SessionStart: [],
|
|
1847
|
+
UserPromptSubmit: [],
|
|
1848
|
+
SubagentStart: [],
|
|
1849
|
+
SubagentStop: [],
|
|
1850
|
+
Stop: []
|
|
1851
|
+
},
|
|
1852
|
+
notify: [],
|
|
1853
|
+
web_search: "disabled"
|
|
1854
|
+
};
|
|
1855
|
+
const CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES = /* @__PURE__ */ new Map([
|
|
1856
|
+
["connectors", "apps"],
|
|
1857
|
+
["imagegenext", "image_generation"],
|
|
1858
|
+
["collab", "multi_agent"],
|
|
1859
|
+
["memory_tool", "memories"],
|
|
1860
|
+
["telepathy", "chronicle"],
|
|
1861
|
+
["codex_hooks", "hooks"]
|
|
1862
|
+
]);
|
|
1863
|
+
/** Common deterministic start/resume/fork fields; no run resources or unsupported setters. */
|
|
1864
|
+
function buildCodexThreadConfiguration(params, options) {
|
|
1865
|
+
return {
|
|
1866
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
1867
|
+
...options.appServer.sessionRoot ? { runtimeWorkspaceRoots: [options.appServer.sessionRoot] } : {},
|
|
1868
|
+
approvalPolicy: options.appServer.approvalPolicy,
|
|
1869
|
+
approvalsReviewer: resolveCodexThreadApprovalsReviewer(options.appServer, options.config),
|
|
1870
|
+
...codexThreadSandboxOrPermissions(options.appServer),
|
|
1871
|
+
...options.appServer.serviceTier !== void 0 ? { serviceTier: options.appServer.serviceTier } : {},
|
|
1872
|
+
config: buildCodexRuntimeThreadConfigForRun(params, options.config, {
|
|
1873
|
+
...options,
|
|
1874
|
+
directOnlyToolNamespaces: resolveDirectOnlyToolNamespaces(options.dynamicTools)
|
|
1875
|
+
}),
|
|
1876
|
+
developerInstructions: joinPresentSections(options.developerInstructions ?? buildDeveloperInstructions(params, { dynamicTools: options.dynamicTools }), options.skillsInstructions)
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
function buildThreadStartParams(params, options) {
|
|
1880
|
+
const resolvedModelProvider = resolveCodexAppServerModelProvider({
|
|
1881
|
+
...params,
|
|
1882
|
+
homeScope: options.appServer.start.homeScope
|
|
1883
|
+
});
|
|
1884
|
+
const modelSelection = resolveCodexAppServerRequestModelSelection({
|
|
1885
|
+
...params,
|
|
1886
|
+
homeScope: options.appServer.start.homeScope,
|
|
1887
|
+
model: options.model ?? params.modelId,
|
|
1888
|
+
modelProvider: options.modelProvider ?? resolvedModelProvider
|
|
1889
|
+
});
|
|
1890
|
+
return {
|
|
1891
|
+
model: modelSelection.model,
|
|
1892
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {},
|
|
1893
|
+
...buildCodexThreadConfiguration(params, options),
|
|
1894
|
+
...(options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow) ? { baseInstructions: "" } : {},
|
|
1895
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE,
|
|
1896
|
+
serviceName: "OpenClaw",
|
|
1897
|
+
threadSource: "openclaw",
|
|
1898
|
+
...resolveCodexThreadEnvironmentSelection(options),
|
|
1899
|
+
dynamicTools: [...options.dynamicTools],
|
|
1900
|
+
experimentalRawEvents: true,
|
|
1901
|
+
...isIncognitoSessionKey(params.sessionKey) ? { ephemeral: true } : {}
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
function buildThreadResumeParams(params, options) {
|
|
1905
|
+
const modelSelection = options.preserveNativeModel ? void 0 : resolveCodexAppServerRequestModelSelection({
|
|
1906
|
+
...params,
|
|
1907
|
+
homeScope: options.appServer.start.homeScope,
|
|
1908
|
+
model: options.model ?? params.modelId,
|
|
1909
|
+
modelProvider: options.modelProvider ?? resolveCodexAppServerModelProvider({
|
|
1910
|
+
...params,
|
|
1911
|
+
homeScope: options.appServer.start.homeScope,
|
|
1912
|
+
authProfileId: options.authProfileId ?? params.authProfileId
|
|
1913
|
+
}),
|
|
1914
|
+
authProfileId: options.authProfileId ?? params.authProfileId
|
|
1915
|
+
});
|
|
1916
|
+
return {
|
|
1917
|
+
threadId: options.threadId,
|
|
1918
|
+
excludeTurns: true,
|
|
1919
|
+
initialTurnsPage: {
|
|
1920
|
+
limit: 1,
|
|
1921
|
+
sortDirection: "desc",
|
|
1922
|
+
itemsView: "notLoaded"
|
|
1923
|
+
},
|
|
1924
|
+
...modelSelection ? {
|
|
1925
|
+
model: modelSelection.model,
|
|
1926
|
+
...modelSelection.modelProvider ? { modelProvider: modelSelection.modelProvider } : {}
|
|
1927
|
+
} : {},
|
|
1928
|
+
...buildCodexThreadConfiguration(params, options),
|
|
1929
|
+
personality: CODEX_NATIVE_PERSONALITY_NONE
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function buildCodexRuntimeThreadConfig(config, options = {}) {
|
|
1933
|
+
const configured = buildCodexProjectDocThreadConfig(config);
|
|
1934
|
+
const codeModeConfig = {
|
|
1935
|
+
...CODEX_CODE_MODE_THREAD_CONFIG,
|
|
1936
|
+
"features.code_mode_only": options.nativeCodeModeOnlyEnabled === true
|
|
1937
|
+
};
|
|
1938
|
+
if (options.nativeCodeModeEnabled === false) {
|
|
1939
|
+
const disabledConfig = expectDefined(mergeCodexThreadConfigs(configured, CODEX_CODE_MODE_DISABLED_THREAD_CONFIG, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG), "Codex disabled code mode config");
|
|
1940
|
+
delete disabledConfig["features.apply_patch_streaming_events"];
|
|
1941
|
+
return disabledConfig;
|
|
1942
|
+
}
|
|
1943
|
+
return ensureDirectOnlyToolNamespaces(expectDefined(mergeCodexThreadConfigs(codeModeConfig, configured, CODEX_GOAL_CONTINUATION_DISABLED_THREAD_CONFIG, CODEX_NATIVE_UPDATE_PLAN_DISABLED_THREAD_CONFIG, options.nativeCodeModeOnlyEnabled === true ? { "features.code_mode_only": true } : void 0), options.nativeCodeModeOnlyEnabled === true ? "Codex code mode only config" : "Codex code mode config"), options.directOnlyToolNamespaces);
|
|
1944
|
+
}
|
|
1945
|
+
function ensureDirectOnlyToolNamespaces(config, requiredNamespaces) {
|
|
1946
|
+
if (!requiredNamespaces?.length) return config;
|
|
1947
|
+
const feature = expectDefined(config["features.code_mode"], "Codex code mode config");
|
|
1948
|
+
const configured = isJsonObject(feature) ? feature : { enabled: feature };
|
|
1949
|
+
const namespaces = Array.isArray(configured.direct_only_tool_namespaces) ? configured.direct_only_tool_namespaces.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
1950
|
+
return {
|
|
1951
|
+
...config,
|
|
1952
|
+
"features.code_mode": {
|
|
1953
|
+
...configured,
|
|
1954
|
+
direct_only_tool_namespaces: [.../* @__PURE__ */ new Set([...namespaces, ...requiredNamespaces])]
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
function resolveDirectOnlyToolNamespaces(dynamicTools) {
|
|
1959
|
+
return (dynamicTools ?? []).filter((tool) => tool.type === "namespace" && tool.name === "openclaw_direct").map((tool) => tool.name);
|
|
1960
|
+
}
|
|
1961
|
+
function buildCodexRuntimeThreadConfigForRun(params, config, options = {}) {
|
|
1962
|
+
const ringZeroActive = (options.hostSystemAgentActive ?? isHostScopedAgentToolActive("openclaw")) && isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow);
|
|
1963
|
+
const messageOnlySourceReply = isMessageOnlyCodexSourceReply(params);
|
|
1964
|
+
const restrictedToolSurface = ringZeroActive || messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true;
|
|
1965
|
+
const restrictedTurnDisablesProjectDocs = ringZeroActive || messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted && params.disableTools;
|
|
1966
|
+
const configMcpServers = config?.mcp_servers;
|
|
1967
|
+
if (restrictedToolSurface && configMcpServers !== void 0 && !isJsonObject(configMcpServers)) throw new Error("Codex restricted tool surface received invalid thread mcp_servers config");
|
|
1968
|
+
const restrictedToolSurfaceMcpServerNames = [...options.restrictedToolSurfaceInheritedMcpServerNames ?? [], ...isJsonObject(configMcpServers) ? Object.keys(configMcpServers) : []];
|
|
1969
|
+
const webSearchConfig = resolveCodexWebSearchPlan({
|
|
1970
|
+
config: params.config,
|
|
1971
|
+
disableTools: params.disableTools,
|
|
1972
|
+
nativeToolSurfaceEnabled: isCodexResponsesOAuthRun(params) || options.nativeCodeModeEnabled,
|
|
1973
|
+
nativeProviderWebSearchSupport: options.nativeProviderWebSearchSupport,
|
|
1974
|
+
webSearchAllowed: options.webSearchAllowed
|
|
1975
|
+
}).threadConfig;
|
|
1976
|
+
const baseConfig = buildCodexRuntimeThreadConfig(mergeCodexThreadConfigs(config, webSearchConfig), options);
|
|
1977
|
+
return applyCodexManagedShellEnvironment({
|
|
1978
|
+
...mergeCodexThreadConfigs(baseConfig, options.appServer?.networkProxy?.configPatch, isCodexResponsesOAuthRun(params) ? {
|
|
1979
|
+
...CODEX_DELEGATION_DISABLED_THREAD_CONFIG,
|
|
1980
|
+
"features.apps": false,
|
|
1981
|
+
"features.plugins": false,
|
|
1982
|
+
"features.image_generation": false,
|
|
1983
|
+
"features.memories": false,
|
|
1984
|
+
"features.skill_search": false,
|
|
1985
|
+
"orchestrator.skills.enabled": false,
|
|
1986
|
+
"orchestrator.mcp.enabled": false,
|
|
1987
|
+
"skills.bundled.enabled": false
|
|
1988
|
+
} : void 0, params.pluginHarnessToolPolicySafeDeniedTools?.includes("image_generate") ? { "features.image_generation": false } : void 0, shouldDisableCodexToolSearchForModel(params.modelId) ? CODEX_TOOL_SEARCH_UNSUPPORTED_THREAD_CONFIG : void 0, params.delegationCapability === "report_only" ? CODEX_DELEGATION_DISABLED_THREAD_CONFIG : void 0, messageOnlySourceReply || params.pluginHarnessToolPolicyRestricted === true ? buildRestrictedToolConfigPatch(restrictedToolSurfaceMcpServerNames, Boolean(params.scheduledRuntimeAuthority) && !isCodexResponsesOAuthRun(params)) : buildCodexRingZeroThreadConfigPatch(params, options.hostSystemAgentActive, restrictedToolSurfaceMcpServerNames), restrictedTurnDisablesProjectDocs ? CODEX_NO_PROJECT_DOCS_CONFIG : void 0, params.authoredContextTokenCap === void 0 ? void 0 : { model_context_window: params.authoredContextTokenCap }) ?? baseConfig,
|
|
1989
|
+
...params.bootstrapContextMode === "lightweight" ? CODEX_NO_PROJECT_DOCS_CONFIG : {}
|
|
1990
|
+
}, options.shellEnvironment, options.disableLoginShell, options.shellPathPrepend);
|
|
1991
|
+
}
|
|
1992
|
+
function buildCodexRingZeroThreadConfigPatch(params, hostSystemAgentActive = isHostScopedAgentToolActive("openclaw"), inheritedMcpServerNames = []) {
|
|
1993
|
+
if (!hostSystemAgentActive || !isSystemAgentOnlyCodexDynamicToolAllowlist(params.toolsAllow)) return;
|
|
1994
|
+
return {
|
|
1995
|
+
...buildRestrictedToolConfigPatch(inheritedMcpServerNames),
|
|
1996
|
+
...CODEX_NO_PROJECT_DOCS_CONFIG
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function buildRestrictedToolConfigPatch(inheritedMcpServerNames, scheduledAppAuthorityActive = false) {
|
|
2000
|
+
const mcpServers = Object.fromEntries([...new Set(inheritedMcpServerNames)].toSorted().map((name) => [name, { enabled: false }]));
|
|
2001
|
+
return {
|
|
2002
|
+
...CODEX_RING_ZERO_THREAD_CONFIG,
|
|
2003
|
+
...scheduledAppAuthorityActive ? {
|
|
2004
|
+
"features.apps": true,
|
|
2005
|
+
"orchestrator.mcp.enabled": true
|
|
2006
|
+
} : {},
|
|
2007
|
+
...Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {}
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
async function readCodexInheritedMcpServerNames(client, cwd, signal, effectiveConfig) {
|
|
2011
|
+
const response = effectiveConfig ?? await readCodexEffectiveConfig(client, cwd, { signal });
|
|
2012
|
+
if (!Array.isArray(response.layers)) throw new Error("Codex config/read omitted effective config layers");
|
|
2013
|
+
for (const layer of response.layers) {
|
|
2014
|
+
if (!isJsonObject(layer) || !isJsonObject(layer.name) || typeof layer.name.type !== "string") throw new Error("Codex config/read returned invalid effective config layers");
|
|
2015
|
+
if (layer.name.type === "legacyManagedConfigTomlFromFile" || layer.name.type === "legacyManagedConfigTomlFromMdm") {
|
|
2016
|
+
const migrationGuidance = layer.name.type === "legacyManagedConfigTomlFromFile" ? "migrate /etc/codex/managed_config.toml to /etc/codex/requirements.toml before running restricted or isolated turns. For ChatGPT-only authentication, use allowed_login_methods = [\"chatgpt\"] in /etc/codex/requirements.toml" : "replace the legacy MDM payload with base64-encoded TOML requirements in the com.openai.codex managed preference requirements_toml_base64 before running restricted or isolated turns. For ChatGPT-only authentication, include allowed_login_methods = [\"chatgpt\"] in that TOML payload";
|
|
2017
|
+
throw new Error(`Codex restricted tool surface cannot override config layer ${layer.name.type}; ${migrationGuidance}.`);
|
|
2018
|
+
}
|
|
2019
|
+
if (!CODEX_SESSION_OVERRIDABLE_LAYER_TYPES.has(layer.name.type)) throw new Error(`Codex restricted tool surface does not recognize config layer ${layer.name.type}`);
|
|
2020
|
+
}
|
|
2021
|
+
const configuredServers = response.config.mcp_servers;
|
|
2022
|
+
if (configuredServers === void 0) return [];
|
|
2023
|
+
if (!isJsonObject(configuredServers)) throw new Error("Codex config/read returned invalid mcp_servers");
|
|
2024
|
+
return Object.keys(configuredServers).toSorted();
|
|
2025
|
+
}
|
|
2026
|
+
async function assertCodexManagedRequirementsDoNotOverrideToolPolicy(client, options, signal) {
|
|
2027
|
+
const requirements = await readCodexManagedRequirements(client, signal);
|
|
2028
|
+
const managedRequirementsFingerprint = buildCodexManagedRequirementsFingerprint(requirements);
|
|
2029
|
+
const managedRequirementsMatch = options.allowedManagedRequirementsFingerprint !== void 0 && managedRequirementsFingerprint === options.allowedManagedRequirementsFingerprint;
|
|
2030
|
+
const managedHooksAllowed = managedRequirementsMatch || options.allowConfiguredManagedHooks === true;
|
|
2031
|
+
if (options.allowedManagedRequirementsFingerprint !== void 0 && !managedRequirementsMatch) throw new Error("Codex managed requirements changed since this automation was authorized; reauthorize the automation from a fresh owner turn");
|
|
2032
|
+
if (requirements === null) return { enableManagedHooks: managedHooksAllowed && options.privateManagedHooksPresent === true };
|
|
2033
|
+
let hasManagedHooks = false;
|
|
2034
|
+
let requiredHooksEnabled;
|
|
2035
|
+
if (options.restrictedToolSurface) for (const key of [
|
|
2036
|
+
"hooks",
|
|
2037
|
+
"managedHooks",
|
|
2038
|
+
"managed_hooks"
|
|
2039
|
+
]) {
|
|
2040
|
+
const hooks = requirements[key];
|
|
2041
|
+
if (hooks === void 0 || hooks === null) continue;
|
|
2042
|
+
if (!isJsonObject(hooks)) throw new Error("Codex configRequirements/read returned invalid managed hooks");
|
|
2043
|
+
hasManagedHooks ||= hasNonEmptyJsonValue(hooks);
|
|
2044
|
+
if (hasManagedHooks && !managedHooksAllowed) throw new Error("Codex restricted tool surface cannot override managed hooks");
|
|
2045
|
+
}
|
|
2046
|
+
const additionalDeniedFeatures = new Set(options.additionalDeniedFeatures);
|
|
2047
|
+
for (const key of ["featureRequirements", "feature_requirements"]) {
|
|
2048
|
+
const featureRequirements = requirements[key];
|
|
2049
|
+
if (featureRequirements === void 0 || featureRequirements === null) continue;
|
|
2050
|
+
if (!isJsonObject(featureRequirements)) throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
2051
|
+
for (const [feature, enabled] of Object.entries(featureRequirements)) {
|
|
2052
|
+
if (typeof enabled !== "boolean") throw new Error("Codex configRequirements/read returned invalid feature requirements");
|
|
2053
|
+
const canonicalFeature = CODEX_RING_ZERO_RESTRICTED_FEATURE_ALIASES.get(feature) ?? feature;
|
|
2054
|
+
if (options.requiredNativeShell && canonicalFeature === "shell_tool" && !enabled) throw new Error("Codex native code mode requires shell_tool, but managed requirements disable it. Ask your administrator to allow the shell, or select a tool policy that disables native code mode; no automation authority was captured.");
|
|
2055
|
+
const deniedByToolPolicy = options.restrictedToolSurface && CODEX_RING_ZERO_RESTRICTED_FEATURES.has(canonicalFeature) || additionalDeniedFeatures.has(canonicalFeature);
|
|
2056
|
+
if (canonicalFeature === "hooks" && managedHooksAllowed) {
|
|
2057
|
+
requiredHooksEnabled = enabled;
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
if (enabled && deniedByToolPolicy) throw new Error(`Codex tool policy cannot override required feature ${feature}`);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return { enableManagedHooks: managedHooksAllowed && requiredHooksEnabled !== false && (hasManagedHooks || options.privateManagedHooksPresent === true || requiredHooksEnabled === true) };
|
|
2064
|
+
}
|
|
2065
|
+
/** Hashes the exact managed requirements without retaining their hook commands or policy details. */
|
|
2066
|
+
function buildCodexManagedRequirementsFingerprint(requirements) {
|
|
2067
|
+
const fingerprint = fingerprintJsonObject({
|
|
2068
|
+
version: 1,
|
|
2069
|
+
requirements
|
|
2070
|
+
});
|
|
2071
|
+
return crypto.createHash("sha256").update(fingerprint).digest("hex");
|
|
2072
|
+
}
|
|
2073
|
+
/** Reads and fingerprints the exact managed requirements active on this app-server. */
|
|
2074
|
+
async function readCodexManagedRequirementsFingerprint(client, signal) {
|
|
2075
|
+
return buildCodexManagedRequirementsFingerprint(await readCodexManagedRequirements(client, signal));
|
|
2076
|
+
}
|
|
2077
|
+
async function readCodexManagedRequirements(client, signal) {
|
|
2078
|
+
const response = await client.request("configRequirements/read", void 0, { signal });
|
|
2079
|
+
if (!isJsonObject(response) || !Object.hasOwn(response, "requirements")) throw new Error("Codex configRequirements/read returned an invalid response");
|
|
2080
|
+
if (response.requirements !== null && !isJsonObject(response.requirements)) throw new Error("Codex configRequirements/read returned invalid requirements");
|
|
2081
|
+
return response.requirements;
|
|
2082
|
+
}
|
|
2083
|
+
function hasNonEmptyJsonValue(value) {
|
|
2084
|
+
if (value === null || value === false || value === "") return false;
|
|
2085
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
2086
|
+
if (typeof value === "object") return Object.values(value).some(hasNonEmptyJsonValue);
|
|
2087
|
+
return true;
|
|
2088
|
+
}
|
|
2089
|
+
function resolveCodexThreadApprovalsReviewer(appServer, config) {
|
|
2090
|
+
return config?.approvals_reviewer === "user" ? "user" : appServer.approvalsReviewer;
|
|
2091
|
+
}
|
|
2092
|
+
function codexThreadSandboxOrPermissions(appServer) {
|
|
2093
|
+
if (appServer.networkProxy) return {};
|
|
2094
|
+
return { sandbox: appServer.sandbox };
|
|
2095
|
+
}
|
|
2096
|
+
function resolveCodexThreadEnvironmentSelection(options) {
|
|
2097
|
+
if (options.nativeCodeModeEnabled === false) return { environments: [] };
|
|
2098
|
+
if (options.environmentSelection) return { environments: options.environmentSelection };
|
|
2099
|
+
return {};
|
|
2100
|
+
}
|
|
2101
|
+
//#endregion
|
|
2102
|
+
export { readActiveCodexTurnIdsFromResume as $, createCodexNativeHookRelay as A, loadCodexNativeToolCatalog as B, mergeCodexNativeProjectDocThreadConfig as C, refreshCodexPluginAppApprovalPolicy as Ct, buildCodexNativeHookRelayConfig as D, assertCodexNativeHookRelayAllowed as E, attestCodexThreadToolSurface as F, areUserMcpServersFingerprintsCompatible as G, applyCodexNativeSkillIsolation as H, checkCodexThreadAppAvailability as I, fingerprintCodexThreadConfig as J, codexDynamicToolsFingerprint as K, discardUnattestedCodexPluginThread as L, resolveCodexNativeHookRelayEvents as M, resolveCodexNativeHookRelayTtlMs as N, buildCodexNativeHookRelayDisabledConfig as O, scheduleCodexNativeHookRelayUnregister as P, legacyFingerprintUserMcpServersConfigPatch as Q, attestCodexRestrictedToolSurfaceMcpServersDisabled as R, buildCodexProjectDocThreadConfig as S, mergeCodexThreadConfigs as St, CodexManagedHooksOnlyError as T, buildCodexAppApprovalOverrides as Tt, resolveCodexNativeSkillIsolation as U, parseCodexNativeToolCatalog as V, areCodexDynamicToolFingerprintsCompatible as W, fingerprintJsonObject as X, fingerprintEnvironmentSelection as Y, fingerprintUserMcpServersConfigPatch as Z, resolveCodexAppServerModelProvider as _, buildCodexPluginThreadConfigTimeoutFallback as _t, buildCodexThreadConfiguration as a, shouldRotateCodexGpt56MultiAgentBinding as at, resolveCodexBindingModelProviderFallback as b, disableUnlistedCodexApps as bt, codexThreadSandboxOrPermissions as c, isForcedPrivateQaCodexRuntime as ct, resolveCodexThreadApprovalsReviewer as d, normalizeCodexDynamicToolName as dt, shouldStartTransientNoToolThread as et, buildCodexNativeWebSearchThreadConfig as f, resolveCodexDynamicToolsLoading as ft, CODEX_NATIVE_PERSONALITY_NONE as g, buildCodexPluginThreadConfigInputFingerprint as gt, buildDeveloperInstructions as h, buildCodexPluginThreadConfig as ht, buildCodexRuntimeThreadConfigForRun as i, shouldRotateCodexAppServerBindingForRuntime as it, emitCodexNativePreToolUseFailureDiagnostic as j, buildCodexNativeHookRelayId as k, readCodexInheritedMcpServerNames as l, isMessageOnlyCodexSourceReply as lt, mergeCodexNativeShellEnvironment as m, buildCodexPluginAppsConfigPatchFromPolicyContext as mt, buildCodexRingZeroThreadConfigPatch as n, isTransientWebSearchRestriction as nt, buildThreadResumeParams as o, filterCodexDynamicTools as ot, resolveCodexWebSearchPlan as p, resolveCodexDynamicToolsLoadingForRuntime as pt, codexLegacyDynamicToolsFingerprint as q, buildCodexRuntimeThreadConfig as r, shouldRecheckRecoverablePluginBinding as rt, buildThreadStartParams as s, filterCodexDynamicToolsForDisabledNativeSurface as st, assertCodexManagedRequirementsDoNotOverrideToolPolicy as t, stabilizeJsonValue as tt, readCodexManagedRequirementsFingerprint as u, isSystemAgentOnlyCodexDynamicToolAllowlist as ut, resolveCodexAppServerRequestModelSelection as v, buildDisabledAppsConfigPatch as vt, CODEX_NATIVE_HOOK_RELAY_TTL_GRACE_MS as w, shouldBuildCodexPluginThreadConfig as wt, joinPresentSections as x, isCodexPluginThreadBindingStale as xt, resolveCodexAppServerThreadModelSelection as y, buildPluginAppPolicyContext as yt, hasCodexNativeToolCatalog as z };
|