@openclaw/codex 2026.9.2 → 2026.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +2 -0
  2. package/dist/api.js +29 -26
  3. package/dist/apply-CvT8tW5i.js +356 -0
  4. package/dist/{attempt-client-cleanup-Bp1ZOnk2.js → attempt-client-cleanup-DKHzbwt5.js} +38 -214
  5. package/dist/{attempt-context-CSazkWns.js → attempt-context-D44yeDII.js} +9 -7
  6. package/dist/{event-projector-usage-ZqEVbAEY.js → attempt-notifications-C9XufG6l.js} +3 -106
  7. package/dist/{auth-binding-Ds_QVm-X.js → auth-binding-CWlKVccJ.js} +2 -2
  8. package/dist/auth-cache-key-7orQae_j.js +498 -0
  9. package/dist/auth-profile-CwzO4_RG.js +43 -0
  10. package/dist/auth-profile-selection-DFu9e167.js +45 -0
  11. package/dist/binding-connection-D3udKbIE.js +60 -0
  12. package/dist/{bounded-turn-DxGJRYbY.js → bounded-turn-C_Hneydj.js} +28 -125
  13. package/dist/build-state-BCmg9-5p.js +19 -0
  14. package/dist/{capabilities-B1mgvpP8.js → capabilities-8vx68WLH.js} +1 -1
  15. package/dist/cli-metadata.js +1 -1
  16. package/dist/client-B57KWPQx.js +4005 -0
  17. package/dist/command-dispatch-BpduNGVf.js +21 -0
  18. package/dist/{command-formatters-DSbGMXA0.js → command-formatters-lxlkNZgU.js} +4 -3
  19. package/dist/{command-handlers-DOJvGv6-.js → command-handlers-tiL9iWj7.js} +788 -135
  20. package/dist/command-rpc-CR5EOEZX.js +233 -0
  21. package/dist/{compact-Y_xBqrHN.js → compact-B_H3AOd7.js} +19 -14
  22. package/dist/config-oIORaQ5T.js +106 -0
  23. package/dist/{config-CyapYSji.js → config-options-CXMq1T-C.js} +205 -593
  24. package/dist/config-runtime-D0zGNIHu.js +11 -0
  25. package/dist/{config-utils-C57JiJrU.js → config-utils-DujwEnhg.js} +2 -4
  26. package/dist/conversation-binding-DsB4-O7U.js +900 -0
  27. package/dist/{plugin-marketplace-discovery-B7gHXJ3d.js → conversation-control-CtUu1b3m.js} +11 -143
  28. package/dist/doctor-contract-api.js +5 -5
  29. package/dist/{dynamic-tools-BwpvTxaX.js → dynamic-tools-CmN5RAip.js} +133 -76
  30. package/dist/{effective-mcp-catalog-CY9Jn4bA.js → effective-mcp-catalog-Bd73bqA8.js} +3 -3
  31. package/dist/{elicitation-input-DMkeki8d.js → elicitation-input-DPoK8F7w.js} +8 -22
  32. package/dist/ephemeral-turn-D78_VmzO.js +101 -0
  33. package/dist/{event-projector-assistant-message-B9kp6iGC.js → event-projector-assistant-message-BmBM4qBV.js} +12 -25
  34. package/dist/event-projector-items-5VhsECCO.js +200 -0
  35. package/dist/event-projector-usage-BfXmpOec.js +102 -0
  36. package/dist/{harness-Czh2Sdoc.js → harness-C7rRa7bm.js} +48 -24
  37. package/dist/harness.js +1 -1
  38. package/dist/history-rejection-B4nOXNXF.js +13 -0
  39. package/dist/incognito-session-uhrBF6wJ.js +15 -0
  40. package/dist/index.js +406 -2915
  41. package/dist/inference-context-Dr_mzvk4.js +94 -0
  42. package/dist/inference-proxy-HGXKUIre.js +311 -0
  43. package/dist/{isolated-completion-CRh-51AK.js → isolated-completion-DWVb5U2J.js} +7 -4
  44. package/dist/launch-args-RXQwn8zV.js +90 -0
  45. package/dist/{managed-binary-DcpnJF9x.js → managed-binary-D22rNEcR.js} +13 -103
  46. package/dist/managed-thread-store-D1t5jh3G.js +66 -0
  47. package/dist/media-understanding-provider.js +4 -1
  48. package/dist/migration-provider-api.js +2 -0
  49. package/dist/{model-catalog-DT5vo6qo.js → model-catalog-Dq2n8f_p.js} +6 -5
  50. package/dist/{models-CcrgSMQR.js → models-PCKOim-h.js} +3 -3
  51. package/dist/native-execution-policy-umd0tgh4.js +128 -0
  52. package/dist/native-subagent-history-DjtPloaU.js +140 -0
  53. package/dist/native-subagent-history-owner-Pcp7JuVF.js +34 -0
  54. package/dist/{native-subagent-monitor-B3bvQGcg.js → native-subagent-monitor-fQ7eIXEf.js} +115 -460
  55. package/dist/native-subagent-task-ids-BzTdT0Uw.js +387 -0
  56. package/dist/node-cli-sessions-DKaOmVUh.js +527 -0
  57. package/dist/{node-exec-server.runtime-nfbUOtUB.js → node-exec-server.runtime-Cl2vapEi.js} +55 -24
  58. package/dist/plan-7uG-Fp36.js +947 -0
  59. package/dist/plugin-activation-BYQculil.js +138 -0
  60. package/dist/{thread-resume-BLgqkUc1.js → plugin-app-cache-key-d0eKxy7b.js} +4 -52
  61. package/dist/{plugin-approval-roundtrip-aQVdyL5g.js → plugin-approval-roundtrip-Bz3FT8W5.js} +1 -1
  62. package/dist/plugin-inventory-oL4Na1ZG.js +328 -0
  63. package/dist/plugin-marketplace-discovery-P-Vndfqq.js +137 -0
  64. package/dist/{plugin-metadata-cache-BwCLTY_U.js → plugin-metadata-cache-ClPJpRio.js} +17 -9
  65. package/dist/protocol-5bh1G-H7.js +275 -0
  66. package/dist/provider-MQ8PVkuE.js +50 -0
  67. package/dist/{provider-capabilities-BpG9Ltc5.js → provider-capabilities-BvCUL69A.js} +2 -2
  68. package/dist/{rate-limits-Fx_l4eNa.js → rate-limits-CBMmWBr9.js} +1 -1
  69. package/dist/request-B7hEkBGq.js +212 -0
  70. package/dist/rpc-error-ttN_QjEm.js +30 -0
  71. package/dist/{run-attempt-BzthFQSM.js → run-attempt-CvsAAGa0.js} +664 -377
  72. package/dist/{runtime-artifact-Bh_gf6-t.js → runtime-artifact-BJ0rXBZ_.js} +27 -27
  73. package/dist/sandbox-guard-BxgyoM8M.js +166 -0
  74. package/dist/{session-binding-4Ki04sW-.js → session-binding-CI8UuilT.js} +30 -48
  75. package/dist/{session-binding-record-CyaXZvTe.js → session-binding-record-Bcvslnhf.js} +12 -26
  76. package/dist/session-catalog-adoption-tcZi1IfX.js +251 -0
  77. package/dist/session-catalog-archive-CwKg8Xz2.js +56 -0
  78. package/dist/session-catalog-ct-AH01I.js +1477 -0
  79. package/dist/session-catalog-home-id-CwhLdC03.js +18 -0
  80. package/dist/session-catalog-node-adoption-B1tgDW-m.js +182 -0
  81. package/dist/session-catalog-node-continue-Ci-DXyJP.js +238 -0
  82. package/dist/session-catalog-provenance-D-dIfRAH.js +96 -0
  83. package/dist/{session-cli-BjLhpmS1.js → session-cli-DNN_jhGc.js} +3 -2
  84. package/dist/{session-history-DWmMqiqW.js → session-history-Cz-tOOYj.js} +6 -3
  85. package/dist/{session-upstream-marker-DReTpGz6.js → session-history-import-DwyijFI6.js} +5 -50
  86. package/dist/{session-history-read-LHldwvyh.js → session-history-read-cxPUxyVV.js} +21 -7
  87. package/dist/session-history-worker-runtime.js +18 -6
  88. package/dist/session-history.worker-BeK1YOyT.js +378 -0
  89. package/dist/session-history.worker.js +1 -1
  90. package/dist/{session-initialization-BCFVnLL4.js → session-initialization-Ct0_Hzmg.js} +2 -2
  91. package/dist/{session-permission-policy-BaueHmSj.js → session-permission-policy-BBmXA8Eg.js} +4 -7
  92. package/dist/{session-retirement-DNdmYdFc.js → session-retirement-B2YfbAAT.js} +6 -5
  93. package/dist/session-upstream-marker-D15C9NHp.js +46 -0
  94. package/dist/{settled-turn-context-BOM3Y1pl.js → settled-turn-context-Du6gdUDd.js} +9 -9
  95. package/dist/{settled-turn-finalizer-BI-b1sry.js → settled-turn-finalizer-Dn91stmc.js} +8 -7
  96. package/dist/shared-client-CscigXXL.js +3109 -0
  97. package/dist/{side-question-B6DWAw1A.js → side-question-BGMJQf4U.js} +65 -170
  98. package/dist/source-CElXU0Oy.js +518 -0
  99. package/dist/thread-archive-guard-DfpBr-Sz.js +53 -0
  100. package/dist/thread-history-page-D0JIp48V.js +361 -0
  101. package/dist/{thread-lifecycle-DPK30jad.js → thread-lifecycle-DXl1-I6b.js} +187 -639
  102. package/dist/{thread-ownership-CbucAmmp.js → thread-ownership-BsDRTsyp.js} +13 -15
  103. package/dist/thread-policy-BuEQfE0g.js +54 -0
  104. package/dist/thread-resume-DsDOZpFg.js +52 -0
  105. package/dist/timeout-BVw8aaM1.js +33 -0
  106. package/dist/transcript-history-projection-v17wXgdK.js +238 -0
  107. package/dist/{transcript-mirror-_njEB84z.js → transcript-mirror-C5CKgNy6.js} +50 -260
  108. package/dist/{transcript-mirror-attestation-Byl34PC-.js → transcript-mirror-attestation-DibE0krH.js} +17 -4
  109. package/dist/{transport-stdio-CzTngV0j.js → transport-stdio-BGjHoYLr.js} +73 -43
  110. package/dist/{upstream-session-fork-1fwbNKzQ.js → upstream-session-fork-BZerMte_.js} +27 -19
  111. package/dist/{usage-i9fv5-4m.js → usage-tCk-mg_x.js} +4 -3
  112. package/dist/version-omm6IPI3.js +12 -0
  113. package/dist/{web-search-provider.runtime-DKQn67A7.js → web-search-provider.runtime-B7bGbGWq.js} +3 -3
  114. package/openclaw.plugin.json +23 -1
  115. package/package.json +4 -4
  116. package/dist/request-C8H22rPi.js +0 -475
  117. package/dist/session-catalog-B47uwekb.js +0 -3272
  118. package/dist/session-history.worker-zZVcx0ME.js +0 -267
  119. package/dist/shared-client-CKhX2kFt.js +0 -7505
  120. package/dist/thread-policy-CTalhWFy.js +0 -105
@@ -0,0 +1,3109 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { n as defineCodexBuildState, t as codexBuildSymbol } from "./build-state-BCmg9-5p.js";
3
+ import { t as CODEX_APP_SERVER_AUTH_PROVIDER } from "./auth-profile-selection-DFu9e167.js";
4
+ import { a as resolveCodexAppServerStartOptionsForAgent, g as readCodexEffectiveConfig, h as CODEX_SESSION_OVERRIDABLE_LAYER_TYPES, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey } from "./config-options-CXMq1T-C.js";
5
+ import { c as resolveCodexAppServerLocalHomeDir, l as resolveCodexAppServerUserHomeDir, o as isJsonObject, s as resolveCodexAppServerHomeDir, u as withEphemeralCodexAuthStore } from "./protocol-5bh1G-H7.js";
6
+ import { t as isCodexAppServerProxyLaunch } from "./launch-args-RXQwn8zV.js";
7
+ import { f as isUnsupportedCodexAppServerVersionError, t as CodexAppServerClient } from "./client-B57KWPQx.js";
8
+ import { c as resolveMacOSDesktopCodexAppPathCandidates, i as resolveManagedCodexNativeCommand, l as resolveMacOSDesktopCodexBundledMarketplaceCandidates, r as resolveManagedCodexAppServerStartOptions, s as resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath, t as isManagedCodexDesktopCommand, u as resolveMacOSDesktopCodexComputerUseServiceAppCandidates } from "./managed-binary-D22rNEcR.js";
9
+ import { _ as isCodexDesktopGenerationCurrent, a as readCodexCliAuthFileApiKey, c as resolveCodexAppServerPreparedApiKeyCacheKey, d as createCodexAppServerStartupLifetime, f as getCurrentSharedClientEntry, i as fingerprintTokenAuthProfileCacheKey, l as closeRetiredSharedClientEntry, m as retireSharedCodexAppServerClientIfCurrent, n as CODEX_AUTH_JSON_FILENAME, o as readFirstNonEmptyEnv, p as getSharedCodexAppServerClientState, r as fingerprintApiKeyAuthProfileCacheKey, s as resolveCodexAppServerFallbackApiKeyCacheKey, t as CODEX_APP_SERVER_API_KEY_ENV_VARS, u as closeRetiredSharedClientEntryIfIdle, v as waitForCodexDesktopGeneration } from "./auth-cache-key-7orQae_j.js";
10
+ import { n as resolveCodexAppServerSpawnEnv } from "./transport-stdio-BGjHoYLr.js";
11
+ import { n as withTimeout } from "./timeout-BVw8aaM1.js";
12
+ import { n as readCodexSessionMeta } from "./session-catalog-provenance-D-dIfRAH.js";
13
+ import { n as resolveCodexAppServerRuntimeOptions } from "./config-runtime-D0zGNIHu.js";
14
+ import { a as resolveCodexAppServerAuthProfileStore, i as resolveCodexAppServerAuthProfileIdForAgent, r as resolveCodexAppServerAuthProfileId, t as isCodexAppServerNativeAuthProfile } from "./auth-profile-CwzO4_RG.js";
15
+ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
16
+ import { createHash } from "node:crypto";
17
+ import { AgentHarnessPreflightError, resolveDefaultAgentDir } from "openclaw/plugin-sdk/agent-harness-registration";
18
+ import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
19
+ import { runExec } from "openclaw/plugin-sdk/process-runtime";
20
+ import fs, { constants } from "node:fs";
21
+ import path from "node:path";
22
+ import fs$1 from "node:fs/promises";
23
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
24
+ import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
25
+ import { ensureAuthProfileStore, findPersistedAuthProfileCredential, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolvePersistedAuthProfileOwnerAgentDir } from "openclaw/plugin-sdk/agent-runtime";
26
+ import { AgentHarnessPreflightError as AgentHarnessPreflightError$1, embeddedAgentLog as embeddedAgentLog$1, formatErrorMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
27
+ import { isDeepStrictEqual } from "node:util";
28
+ import { assertNoSymlinkParents } from "openclaw/plugin-sdk/security-runtime";
29
+ import { hasUsableOAuthCredential, resolveOpenAICodexAuthIdentity } from "openclaw/plugin-sdk/provider-auth";
30
+ //#region extensions/codex/src/app-server/computer-use-service-path.ts
31
+ /** Filesystem ownership guards for isolated Computer Use service provisioning. */
32
+ async function assertOwnedServicePath(params) {
33
+ await assertOwnedCodexHomePath({
34
+ ownershipRoot: params.ownershipRoot,
35
+ codexHome: params.codexHome,
36
+ allowMissing: true
37
+ });
38
+ assertPathAtOrInside(params.ownershipRoot, params.targetParent, "Computer Use service parent");
39
+ await assertNoSymlinkParents({
40
+ rootDir: params.ownershipRoot,
41
+ targetPath: params.targetParent,
42
+ allowMissing: true,
43
+ requireDirectories: true,
44
+ messagePrefix: "Computer Use service path"
45
+ });
46
+ await assertNotSymlink(params.targetPath, "Computer Use service target");
47
+ }
48
+ async function ensureOwnedCodexHome(codexHomeInput, ownershipRootInput = path.dirname(path.resolve(codexHomeInput))) {
49
+ const codexHome = path.resolve(codexHomeInput);
50
+ const ownershipRoot = path.resolve(ownershipRootInput);
51
+ await fs$1.mkdir(ownershipRoot, {
52
+ recursive: true,
53
+ mode: 448
54
+ });
55
+ await assertOwnedCodexHomePath({
56
+ ownershipRoot,
57
+ codexHome,
58
+ allowMissing: true
59
+ });
60
+ await ensureRealDirectoryTree(ownershipRoot, codexHome, "isolated Codex home");
61
+ await assertOwnedCodexHomePath({
62
+ ownershipRoot,
63
+ codexHome,
64
+ allowMissing: false
65
+ });
66
+ }
67
+ async function prepareOwnedServiceParent(params) {
68
+ await ensureOwnedCodexHome(params.codexHome, params.ownershipRoot);
69
+ await ensureRealDirectoryTree(params.ownershipRoot, params.targetParent, "Computer Use service parent");
70
+ await assertNoSymlinkParents({
71
+ rootDir: params.ownershipRoot,
72
+ targetPath: params.targetParent,
73
+ allowMissing: false,
74
+ requireDirectories: true,
75
+ messagePrefix: "Computer Use service path"
76
+ });
77
+ const [rootIdentity, parentIdentity] = await Promise.all([readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root"), readRealDirectoryIdentity(params.targetParent, "Computer Use service parent")]);
78
+ assertPathAtOrInside(rootIdentity.realPath, parentIdentity.realPath, "canonical Computer Use service parent");
79
+ return parentIdentity;
80
+ }
81
+ async function assertOwnedCodexHomePath(params) {
82
+ await readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root");
83
+ assertPathAtOrInside(params.ownershipRoot, params.codexHome, "isolated Codex home");
84
+ await assertNoSymlinkParents({
85
+ rootDir: params.ownershipRoot,
86
+ targetPath: params.codexHome,
87
+ allowMissing: params.allowMissing,
88
+ requireDirectories: true,
89
+ messagePrefix: "Computer Use service path"
90
+ });
91
+ }
92
+ async function readRealDirectoryIdentity(directoryPath, label) {
93
+ const logicalPath = path.resolve(directoryPath);
94
+ const before = await fs$1.lstat(logicalPath);
95
+ if (before.isSymbolicLink() || !before.isDirectory()) throw new Error(`${label} must be a real directory: ${logicalPath}`);
96
+ const realPath = await fs$1.realpath(logicalPath);
97
+ const [after, resolved] = await Promise.all([fs$1.lstat(logicalPath), fs$1.lstat(realPath)]);
98
+ if (after.isSymbolicLink() || !after.isDirectory() || !resolved.isDirectory() || before.dev !== after.dev || before.ino !== after.ino || after.dev !== resolved.dev || after.ino !== resolved.ino) throw new Error(`${label} changed while its ownership boundary was being established.`);
99
+ return {
100
+ logicalPath,
101
+ realPath,
102
+ dev: after.dev,
103
+ ino: after.ino
104
+ };
105
+ }
106
+ async function assertOwnedServiceParentStable(parent) {
107
+ await assertDirectoryIdentityStable(parent, "Computer Use service parent");
108
+ }
109
+ async function assertDirectoryIdentityStable(expected, label) {
110
+ if (!await directoryIdentityIsStable(expected)) throw new Error(`${label} changed during refresh; refusing to mutate the replacement path.`);
111
+ }
112
+ async function ownedServiceParentIsStable(parent) {
113
+ return await directoryIdentityIsStable(parent);
114
+ }
115
+ async function directoryIdentityIsStable(expected) {
116
+ try {
117
+ const current = await fs$1.lstat(expected.logicalPath);
118
+ if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) return false;
119
+ return await fs$1.realpath(expected.logicalPath) === expected.realPath;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+ async function assertNotSymlink(filePath, label) {
125
+ try {
126
+ if ((await fs$1.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
127
+ } catch (error) {
128
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
129
+ throw error;
130
+ }
131
+ }
132
+ async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
133
+ const root = path.resolve(ownershipRoot);
134
+ const target = path.resolve(directoryPath);
135
+ assertPathAtOrInside(root, target, label);
136
+ const relative = path.relative(root, target);
137
+ let current = root;
138
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
139
+ current = path.join(current, segment);
140
+ const existing = await fs$1.lstat(current).catch((error) => {
141
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
142
+ throw error;
143
+ });
144
+ if (!existing) {
145
+ try {
146
+ await fs$1.mkdir(current, { mode: 448 });
147
+ } catch (error) {
148
+ if (!hasNodeErrorCode$1(error, "EEXIST")) throw error;
149
+ }
150
+ const created = await fs$1.lstat(current);
151
+ if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
152
+ } else if (existing.isSymbolicLink() || !existing.isDirectory()) throw new Error(`${label} must traverse real directories: ${current}`);
153
+ }
154
+ await assertNoSymlinkParents({
155
+ rootDir: root,
156
+ targetPath: target,
157
+ allowMissing: false,
158
+ requireDirectories: true,
159
+ messagePrefix: "Computer Use service path"
160
+ });
161
+ await readRealDirectoryIdentity(target, label);
162
+ }
163
+ function assertPathAtOrInside(rootPath, candidatePath, label) {
164
+ const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
165
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
166
+ }
167
+ function hasNodeErrorCode$1(error, code) {
168
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
169
+ }
170
+ //#endregion
171
+ //#region extensions/codex/src/app-server/computer-use-cache.ts
172
+ /** Shared Computer Use plugin cache reconciliation for isolated Codex homes. */
173
+ const DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH = resolveMacOSDesktopCodexBundledMarketplaceCandidates("darwin")[0] ?? "";
174
+ const DEFAULT_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
175
+ async function ensureCodexComputerUseSharedPluginCache(params) {
176
+ if (!params.config.enabled) return skippedCacheResult("disabled", "Computer Use cache sharing skipped because it is disabled.");
177
+ if (params.config.pluginCacheMode === "independent") return skippedCacheResult("independent", "Computer Use cache sharing skipped because pluginCacheMode is independent.");
178
+ if (params.config.marketplaceName || params.config.marketplacePath) return skippedCacheResult("explicit_marketplace", "Computer Use cache sharing skipped because an explicit marketplace is configured.");
179
+ const bundledMarketplacePath = resolveComputerUseBundledMarketplacePath(params);
180
+ const sourcePluginRoot = path.join(bundledMarketplacePath, "plugins", params.config.pluginName);
181
+ const version = await readBundledPluginVersion(sourcePluginRoot);
182
+ if (!version) return skippedCacheResult("source_missing", `Computer Use bundled plugin source was not found at ${sourcePluginRoot}.`);
183
+ const marketplaceName = params.config.marketplaceName ?? DEFAULT_BUNDLED_MARKETPLACE_NAME;
184
+ const cacheRoot = path.join(params.codexHome, "plugins", "cache", marketplaceName, params.config.pluginName);
185
+ const cachePath = path.join(cacheRoot, version);
186
+ return {
187
+ status: "shared",
188
+ changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, {
189
+ codexHome: params.codexHome,
190
+ ownershipRoot: params.ownershipRoot,
191
+ assertCurrent: params.assertCurrent,
192
+ forceRefresh: params.forceRefresh
193
+ }),
194
+ cachePath,
195
+ targetPath: sourcePluginRoot,
196
+ version,
197
+ removedStaleVersions: [],
198
+ warnings: [],
199
+ message: `Computer Use plugin cache ${cachePath} contains bundled plugin ${sourcePluginRoot}.`
200
+ };
201
+ }
202
+ function resolveComputerUseBundledMarketplacePath(params) {
203
+ return params.bundledMarketplacePath ?? resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath({ candidates: params.bundledMarketplacePathCandidates }) ?? params.bundledMarketplacePathCandidates?.[0] ?? DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH;
204
+ }
205
+ async function readBundledPluginVersion(sourcePluginRoot) {
206
+ const pluginJsonPath = path.join(sourcePluginRoot, ".codex-plugin", "plugin.json");
207
+ let raw;
208
+ try {
209
+ raw = await fs$1.readFile(pluginJsonPath, "utf8");
210
+ } catch {
211
+ return;
212
+ }
213
+ try {
214
+ const parsed = JSON.parse(raw);
215
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
216
+ } catch {
217
+ return;
218
+ }
219
+ }
220
+ async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, boundary) {
221
+ const cacheRoot = path.dirname(cachePath);
222
+ const ownedParent = boundary.ownershipRoot ? await prepareOwnedServiceParent({
223
+ ownershipRoot: boundary.ownershipRoot,
224
+ codexHome: boundary.codexHome,
225
+ targetParent: cacheRoot
226
+ }) : void 0;
227
+ if (!ownedParent) await fs$1.mkdir(cacheRoot, { recursive: true });
228
+ const physicalCachePath = ownedParent ? path.join(ownedParent.realPath, path.basename(cachePath)) : cachePath;
229
+ const stat = await fs$1.lstat(physicalCachePath).catch(() => void 0);
230
+ if (stat?.isDirectory() && !stat.isSymbolicLink()) {
231
+ if (await readBundledPluginVersion(physicalCachePath) === version && !boundary.forceRefresh) return false;
232
+ }
233
+ const cacheName = path.basename(cachePath);
234
+ const physicalCacheRoot = path.dirname(physicalCachePath);
235
+ const stagingRoot = await fs$1.mkdtemp(path.join(physicalCacheRoot, `.${cacheName}.staging-`));
236
+ const stagedPath = path.join(stagingRoot, cacheName);
237
+ const backupPath = path.join(physicalCacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
238
+ let backupCreated = false;
239
+ try {
240
+ await fs$1.cp(sourcePluginRoot, stagedPath, { recursive: true });
241
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
242
+ if (stat) {
243
+ boundary.assertCurrent?.();
244
+ await fs$1.rename(physicalCachePath, backupPath);
245
+ backupCreated = true;
246
+ }
247
+ try {
248
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
249
+ boundary.assertCurrent?.();
250
+ await fs$1.rename(stagedPath, physicalCachePath);
251
+ } catch (error) {
252
+ if (backupCreated) try {
253
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
254
+ await fs$1.rename(backupPath, physicalCachePath);
255
+ backupCreated = false;
256
+ } catch (restoreError) {
257
+ throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
258
+ }
259
+ throw error;
260
+ }
261
+ if (backupCreated) {
262
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
263
+ await fs$1.rm(backupPath, {
264
+ recursive: true,
265
+ force: true
266
+ });
267
+ }
268
+ return true;
269
+ } finally {
270
+ if (!ownedParent || await directoryIdentityIsStable(ownedParent)) await fs$1.rm(stagingRoot, {
271
+ recursive: true,
272
+ force: true
273
+ });
274
+ }
275
+ }
276
+ function skippedCacheResult(status, message) {
277
+ return {
278
+ status,
279
+ changed: false,
280
+ message,
281
+ removedStaleVersions: [],
282
+ warnings: status === "source_missing" ? [message] : []
283
+ };
284
+ }
285
+ //#endregion
286
+ //#region extensions/codex/src/app-server/computer-use-marketplace.ts
287
+ /** Managed local wrapper for Codex's reserved bundled marketplace. */
288
+ const MARKETPLACE_NAME = "openai-bundled";
289
+ const activeInstalls$1 = /* @__PURE__ */ new Map();
290
+ function resolveCodexManagedBundledMarketplacePath(codexHome) {
291
+ return path.join(codexHome, ".tmp", "bundled-marketplaces", MARKETPLACE_NAME);
292
+ }
293
+ async function ensureCodexManagedBundledMarketplace(params) {
294
+ const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
295
+ const source = await resolveCodexManagedBundledMarketplaceSource({
296
+ ...params,
297
+ candidates
298
+ });
299
+ if (!source) return;
300
+ const parentPath = path.dirname(resolveCodexManagedBundledMarketplacePath(params.codexHome));
301
+ const targetPath = path.join(parentPath, MARKETPLACE_NAME);
302
+ const parent = await prepareOwnedServiceParent({
303
+ ownershipRoot: params.ownershipRoot,
304
+ codexHome: params.codexHome,
305
+ targetParent: parentPath
306
+ });
307
+ const physicalTargetPath = path.join(parent.realPath, MARKETPLACE_NAME);
308
+ await assertNotSymlink(physicalTargetPath, "managed bundled marketplace");
309
+ const active = activeInstalls$1.get(physicalTargetPath);
310
+ if (active) {
311
+ if (active.sourcePath === source.bundledMarketplacePath) return await active.promise;
312
+ await active.promise.catch(() => void 0);
313
+ return await ensureCodexManagedBundledMarketplace(params);
314
+ }
315
+ const install = reconcileManagedWrapper({
316
+ parent,
317
+ physicalTargetPath,
318
+ targetPath,
319
+ source,
320
+ ownershipCandidates: params.ownershipCandidates ?? candidates,
321
+ assertCurrent: params.assertCurrent
322
+ });
323
+ const activeEntry = {
324
+ sourcePath: source.bundledMarketplacePath,
325
+ promise: install
326
+ };
327
+ activeInstalls$1.set(physicalTargetPath, activeEntry);
328
+ const clearActive = () => {
329
+ if (activeInstalls$1.get(physicalTargetPath) === activeEntry) activeInstalls$1.delete(physicalTargetPath);
330
+ };
331
+ install.then(clearActive, clearActive);
332
+ return await install;
333
+ }
334
+ async function reconcileManagedWrapper(params) {
335
+ if (await wrapperMatches(params.physicalTargetPath, params.source.bundledMarketplacePath)) return params.targetPath;
336
+ return await publishManagedWrapper(params);
337
+ }
338
+ async function publishManagedWrapper(params) {
339
+ const { parent, physicalTargetPath, targetPath, source, ownershipCandidates, assertCurrent } = params;
340
+ const stagingPath = await fs$1.mkdtemp(path.join(parent.realPath, `.${MARKETPLACE_NAME}.staging-`));
341
+ const backupPath = path.join(parent.realPath, `.${MARKETPLACE_NAME}.backup-${process.pid}-${Date.now()}`);
342
+ let backupCreated = false;
343
+ try {
344
+ const manifestParent = path.join(stagingPath, ".agents", "plugins");
345
+ await fs$1.mkdir(manifestParent, {
346
+ recursive: true,
347
+ mode: 448
348
+ });
349
+ await Promise.all([fs$1.symlink(path.join(source.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"), path.join(manifestParent, "marketplace.json")), fs$1.symlink(path.join(source.bundledMarketplacePath, "plugins"), path.join(stagingPath, "plugins"))]);
350
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
351
+ const existing = await fs$1.lstat(physicalTargetPath).catch((error) => {
352
+ if (hasNodeErrorCode(error, "ENOENT")) return;
353
+ throw error;
354
+ });
355
+ if (existing && !existing.isDirectory()) throw new Error(`Managed bundled marketplace must be a real directory: ${targetPath}`);
356
+ if (existing && !await wrapperMatchesAnySource(physicalTargetPath, ownershipCandidates)) throw new Error(`Refusing to replace an unowned bundled marketplace directory: ${targetPath}`);
357
+ if (existing) {
358
+ assertCurrent?.();
359
+ await fs$1.rename(physicalTargetPath, backupPath);
360
+ backupCreated = true;
361
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
362
+ }
363
+ assertCurrent?.();
364
+ await fs$1.rename(stagingPath, physicalTargetPath);
365
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
366
+ if (backupCreated) {
367
+ await fs$1.rm(backupPath, { recursive: true });
368
+ backupCreated = false;
369
+ }
370
+ return targetPath;
371
+ } catch (error) {
372
+ if (backupCreated) try {
373
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
374
+ if (await fs$1.lstat(physicalTargetPath).catch(() => void 0)) {
375
+ if (!await wrapperMatches(physicalTargetPath, source.bundledMarketplacePath)) throw new Error("managed bundled marketplace replacement is no longer owned", { cause: error });
376
+ await fs$1.rm(physicalTargetPath, { recursive: true });
377
+ }
378
+ await fs$1.rename(backupPath, physicalTargetPath);
379
+ backupCreated = false;
380
+ } catch (restoreError) {
381
+ throw new Error(`Failed to restore the prior managed bundled marketplace: ${String(error)}`, { cause: restoreError });
382
+ }
383
+ throw error;
384
+ } finally {
385
+ if (await directoryIdentityIsStable(parent)) await fs$1.rm(stagingPath, {
386
+ recursive: true,
387
+ force: true
388
+ });
389
+ }
390
+ }
391
+ async function resolveCodexManagedBundledMarketplaceSource(params) {
392
+ const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
393
+ const command = params.appServerCommand && path.resolve(params.appServerCommand);
394
+ const ordered = command ? candidates.filter((candidate) => path.resolve(candidate.appServerCommandPath) === command) : candidates;
395
+ for (const candidate of ordered) if (await isExpectedMarketplace(candidate.bundledMarketplacePath)) return candidate;
396
+ }
397
+ async function isExpectedMarketplace(root) {
398
+ try {
399
+ const manifest = JSON.parse(await fs$1.readFile(path.join(root, ".agents", "plugins", "marketplace.json"), "utf8"));
400
+ await fs$1.access(path.join(root, "plugins", "computer-use", ".codex-plugin", "plugin.json"));
401
+ return isRecord(manifest) && manifest.name === MARKETPLACE_NAME && Array.isArray(manifest.plugins) && manifest.plugins.some((plugin) => isRecord(plugin) && plugin.name === "computer-use");
402
+ } catch {
403
+ return false;
404
+ }
405
+ }
406
+ async function wrapperMatches(targetPath, sourcePath) {
407
+ try {
408
+ const target = await fs$1.lstat(targetPath);
409
+ if (!target.isDirectory() || target.isSymbolicLink()) return false;
410
+ const [manifest, plugins] = await Promise.all([fs$1.readlink(path.join(targetPath, ".agents", "plugins", "marketplace.json")), fs$1.readlink(path.join(targetPath, "plugins"))]);
411
+ return manifest === path.join(sourcePath, ".agents", "plugins", "marketplace.json") && plugins === path.join(sourcePath, "plugins");
412
+ } catch {
413
+ return false;
414
+ }
415
+ }
416
+ async function wrapperMatchesAnySource(targetPath, candidates) {
417
+ for (const candidate of candidates) if (await wrapperMatches(targetPath, candidate.bundledMarketplacePath)) return true;
418
+ return false;
419
+ }
420
+ function hasNodeErrorCode(error, code) {
421
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
422
+ }
423
+ //#endregion
424
+ //#region extensions/codex/src/app-server/computer-use-service.ts
425
+ /** Native Computer Use service provisioning for isolated Codex homes. */
426
+ const SERVICE_APP_NAME = "Codex Computer Use.app";
427
+ const SERVICE_BUNDLE_ID = "com.openai.sky.CUAService";
428
+ const CLIENT_BUNDLE_ID = "com.openai.sky.CUAService.cli";
429
+ const OPENAI_TEAM_ID = "2DC432GLL2";
430
+ const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app");
431
+ const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
432
+ const COPY_TIMEOUT_MS = 12e4;
433
+ const INSPECT_TIMEOUT_MS = 3e4;
434
+ const activeInstalls = /* @__PURE__ */ new Map();
435
+ /** Finds the first signed native service from one ordered desktop owner set. */
436
+ async function resolveCodexComputerUseServiceAppSourcePath(params) {
437
+ const platform = params.platform ?? process.platform;
438
+ if (platform !== "darwin") return;
439
+ return (await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand), params.inspectServiceApp ?? inspectTrustedServiceApp))?.path;
440
+ }
441
+ /** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
442
+ async function ensureCodexComputerUseServiceApp(params) {
443
+ const platform = params.platform ?? process.platform;
444
+ if (platform !== "darwin") return {
445
+ status: "unsupported",
446
+ changed: false
447
+ };
448
+ const codexHome = path.resolve(params.codexHome);
449
+ const ownershipRoot = path.resolve(params.ownershipRoot ?? path.dirname(codexHome));
450
+ const targetParent = path.join(codexHome, "computer-use");
451
+ const targetPath = path.join(targetParent, SERVICE_APP_NAME);
452
+ await ensureOwnedCodexHome(codexHome, ownershipRoot);
453
+ await assertOwnedServicePath({
454
+ ownershipRoot,
455
+ codexHome,
456
+ targetParent,
457
+ targetPath
458
+ });
459
+ const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
460
+ const syncKey = [targetPath, ...candidates].join("\0");
461
+ const active = activeInstalls.get(targetPath);
462
+ if (active) {
463
+ if (active.syncKey === syncKey) return await active.promise;
464
+ await active.promise.catch(() => void 0);
465
+ return await ensureCodexComputerUseServiceApp(params);
466
+ }
467
+ const install = ensureCodexComputerUseServiceAppOnce({
468
+ ...params,
469
+ codexHome,
470
+ ownershipRoot,
471
+ targetParent,
472
+ targetPath,
473
+ platform,
474
+ sourceAppCandidates: candidates
475
+ });
476
+ const activeEntry = {
477
+ syncKey,
478
+ promise: install
479
+ };
480
+ activeInstalls.set(targetPath, activeEntry);
481
+ const clearActive = () => {
482
+ if (activeInstalls.get(targetPath) === activeEntry) activeInstalls.delete(targetPath);
483
+ };
484
+ install.then(clearActive, clearActive);
485
+ return await install;
486
+ }
487
+ async function ensureCodexComputerUseServiceAppOnce(params) {
488
+ const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
489
+ const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
490
+ if (!source) return {
491
+ status: "source_missing",
492
+ changed: false,
493
+ targetPath: params.targetPath
494
+ };
495
+ const { path: sourcePath, identity: sourceIdentity } = source;
496
+ const ownedParent = await prepareOwnedServiceParent({
497
+ ownershipRoot: params.ownershipRoot,
498
+ codexHome: params.codexHome,
499
+ targetParent: params.targetParent
500
+ });
501
+ const operationTargetPath = path.join(ownedParent.realPath, SERVICE_APP_NAME);
502
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
503
+ const initialTarget = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
504
+ if (initialTarget.identity && identitiesMatch(initialTarget.identity, sourceIdentity)) return {
505
+ status: "already_current",
506
+ changed: false,
507
+ targetPath: params.targetPath,
508
+ sourcePath,
509
+ sourceBuild: sourceIdentity.build
510
+ };
511
+ await assertOwnedServiceParentStable(ownedParent);
512
+ const stagingRoot = await fs$1.mkdtemp(path.join(ownedParent.realPath, ".service-app.staging-"));
513
+ const stagingRootIdentity = await readRealDirectoryIdentity(stagingRoot, "Computer Use service staging directory");
514
+ const stagedPath = path.join(stagingRoot, SERVICE_APP_NAME);
515
+ const backupPath = path.join(ownedParent.realPath, `.service-app.backup-${process.pid}-${Date.now()}`);
516
+ let backupCreated = false;
517
+ try {
518
+ await (params.copyServiceApp ?? copyServiceAppWithDitto)(sourcePath, stagedPath);
519
+ await assertOwnedServiceParentStable(ownedParent);
520
+ await assertDirectoryIdentityStable(stagingRootIdentity, "Computer Use service staging directory");
521
+ await assertNotSymlink(stagedPath, "Copied Computer Use service app");
522
+ const stagedIdentity = await inspectServiceApp(stagedPath);
523
+ if (!stagedIdentity || !identitiesMatch(stagedIdentity, sourceIdentity)) throw new Error(`Copied Computer Use service app at ${stagedPath} does not match its selected signed source.`);
524
+ const stagedSnapshot = await readServiceAppSnapshot(stagedPath, inspectServiceApp);
525
+ const currentSourceIdentity = await inspectServiceApp(sourcePath);
526
+ await assertOwnedServiceParentStable(ownedParent);
527
+ if (!currentSourceIdentity || !identitiesMatch(currentSourceIdentity, sourceIdentity)) throw new Error("Selected Computer Use service source changed during refresh.");
528
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
529
+ if (await pathExists$1(operationTargetPath)) {
530
+ await assertOwnedServiceParentStable(ownedParent);
531
+ params.assertCurrent?.();
532
+ await fs$1.rename(operationTargetPath, backupPath);
533
+ await assertOwnedServiceParentStable(ownedParent);
534
+ backupCreated = true;
535
+ const movedTarget = await readServiceAppSnapshot(backupPath, inspectServiceApp);
536
+ if (movedTarget.identity && identitiesMatch(movedTarget.identity, sourceIdentity)) {
537
+ await assertOwnedServiceParentStable(ownedParent);
538
+ await fs$1.rename(backupPath, operationTargetPath);
539
+ await assertOwnedServiceParentStable(ownedParent);
540
+ backupCreated = false;
541
+ return {
542
+ status: "already_current",
543
+ changed: false,
544
+ targetPath: params.targetPath,
545
+ sourcePath,
546
+ sourceBuild: sourceIdentity.build
547
+ };
548
+ }
549
+ if (!snapshotsMatch(initialTarget, movedTarget)) {
550
+ await assertOwnedServiceParentStable(ownedParent);
551
+ await fs$1.rename(backupPath, operationTargetPath);
552
+ await assertOwnedServiceParentStable(ownedParent);
553
+ backupCreated = false;
554
+ throw new Error("Computer Use service target changed to an unexpected generation during refresh.");
555
+ }
556
+ }
557
+ try {
558
+ await assertOwnedServiceParentStable(ownedParent);
559
+ params.assertCurrent?.();
560
+ await fs$1.rename(stagedPath, operationTargetPath);
561
+ await assertOwnedServiceParentStable(ownedParent);
562
+ } catch (error) {
563
+ await assertOwnedServiceParentStable(ownedParent);
564
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
565
+ const winnerIdentity = await inspectServiceApp(operationTargetPath);
566
+ if (!winnerIdentity || !identitiesMatch(winnerIdentity, sourceIdentity)) {
567
+ if (backupCreated) {
568
+ if (!await pathExists$1(operationTargetPath)) {
569
+ await assertOwnedServiceParentStable(ownedParent);
570
+ await fs$1.rename(backupPath, operationTargetPath);
571
+ await assertOwnedServiceParentStable(ownedParent);
572
+ backupCreated = false;
573
+ }
574
+ }
575
+ throw error;
576
+ }
577
+ if (backupCreated) {
578
+ await assertOwnedServiceParentStable(ownedParent);
579
+ await assertNotSymlink(backupPath, "Computer Use service backup");
580
+ await fs$1.rm(backupPath, {
581
+ recursive: true,
582
+ force: true
583
+ });
584
+ await assertOwnedServiceParentStable(ownedParent);
585
+ backupCreated = false;
586
+ }
587
+ return {
588
+ status: "already_current",
589
+ changed: false,
590
+ targetPath: params.targetPath,
591
+ sourcePath,
592
+ sourceBuild: sourceIdentity.build
593
+ };
594
+ }
595
+ await assertNotSymlink(operationTargetPath, "Installed Computer Use service app");
596
+ const installedSnapshot = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
597
+ if (!installedSnapshot.identity || !identitiesMatch(installedSnapshot.identity, sourceIdentity)) {
598
+ if (filesystemSnapshotsMatch(installedSnapshot, stagedSnapshot)) {
599
+ await assertOwnedServiceParentStable(ownedParent);
600
+ await fs$1.rm(operationTargetPath, {
601
+ recursive: true,
602
+ force: true
603
+ });
604
+ await assertOwnedServiceParentStable(ownedParent);
605
+ if (backupCreated) {
606
+ await fs$1.rename(backupPath, operationTargetPath);
607
+ await assertOwnedServiceParentStable(ownedParent);
608
+ backupCreated = false;
609
+ }
610
+ }
611
+ throw new Error("Installed Computer Use service app failed post-install identity verification.");
612
+ }
613
+ if (backupCreated) {
614
+ await assertOwnedServiceParentStable(ownedParent);
615
+ await assertNotSymlink(backupPath, "Computer Use service backup");
616
+ await fs$1.rm(backupPath, {
617
+ recursive: true,
618
+ force: true
619
+ });
620
+ await assertOwnedServiceParentStable(ownedParent);
621
+ backupCreated = false;
622
+ }
623
+ return {
624
+ status: initialTarget.exists ? "refreshed" : "installed",
625
+ changed: true,
626
+ targetPath: params.targetPath,
627
+ sourcePath,
628
+ sourceBuild: sourceIdentity.build,
629
+ ...initialTarget.identity ? { previousBuild: initialTarget.identity.build } : {}
630
+ };
631
+ } catch (error) {
632
+ if (backupCreated && await ownedServiceParentIsStable(ownedParent) && !await pathExists$1(operationTargetPath)) {
633
+ await assertOwnedServiceParentStable(ownedParent);
634
+ await fs$1.rename(backupPath, operationTargetPath);
635
+ backupCreated = false;
636
+ }
637
+ throw error;
638
+ } finally {
639
+ if (await ownedServiceParentIsStable(ownedParent) && await directoryIdentityIsStable(stagingRootIdentity)) await fs$1.rm(stagingRoot, {
640
+ recursive: true,
641
+ force: true
642
+ });
643
+ }
644
+ }
645
+ async function pathExists$1(filePath) {
646
+ return await fs$1.lstat(filePath).then(() => true, () => false);
647
+ }
648
+ async function findUsableServiceApp(candidates, inspectServiceApp) {
649
+ for (const candidate of candidates) {
650
+ const identity = await inspectServiceApp(candidate);
651
+ if (identity) return {
652
+ path: candidate,
653
+ identity
654
+ };
655
+ }
656
+ }
657
+ async function readServiceAppSnapshot(appPath, inspectServiceApp) {
658
+ if (!await pathExists$1(appPath)) return { exists: false };
659
+ return {
660
+ exists: true,
661
+ identity: await inspectServiceApp(appPath),
662
+ filesystemKey: await readServiceAppFilesystemKey(appPath)
663
+ };
664
+ }
665
+ async function readServiceAppFilesystemKey(appPath) {
666
+ const paths = [
667
+ appPath,
668
+ path.join(appPath, "Contents", "Info.plist"),
669
+ path.join(appPath, CLIENT_RELATIVE_PATH)
670
+ ];
671
+ return (await Promise.all(paths.map(async (entryPath) => await fs$1.stat(entryPath).then((stat) => `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}`, () => "missing")))).join("|");
672
+ }
673
+ function snapshotsMatch(left, right) {
674
+ if (left.exists !== right.exists) return false;
675
+ if (!left.exists) return true;
676
+ if (left.filesystemKey && right.filesystemKey && left.filesystemKey !== right.filesystemKey) return false;
677
+ if (left.identity || right.identity) return Boolean(left.identity && right.identity && identitiesMatch(left.identity, right.identity));
678
+ return left.filesystemKey !== void 0 && left.filesystemKey === right.filesystemKey;
679
+ }
680
+ function filesystemSnapshotsMatch(left, right) {
681
+ return Boolean(left.exists && right.exists && left.filesystemKey && right.filesystemKey && left.filesystemKey === right.filesystemKey);
682
+ }
683
+ function identitiesMatch(left, right) {
684
+ return left.bundleId === right.bundleId && left.version === right.version && left.build === right.build && left.cdHash === right.cdHash && left.teamId === right.teamId && left.clientBundleId === right.clientBundleId && left.clientCdHash === right.clientCdHash && left.clientTeamId === right.clientTeamId;
685
+ }
686
+ async function hasExecutableClient(appPath) {
687
+ try {
688
+ await fs$1.access(path.join(appPath, CLIENT_RELATIVE_PATH), constants.X_OK);
689
+ return true;
690
+ } catch {
691
+ return false;
692
+ }
693
+ }
694
+ async function inspectTrustedServiceApp(appPath) {
695
+ if (!await hasExecutableClient(appPath)) return;
696
+ const clientAppPath = path.join(appPath, CLIENT_APP_RELATIVE_PATH);
697
+ try {
698
+ await verifyTrustedBundle(appPath, SERVICE_BUNDLE_ID, true);
699
+ await verifyTrustedBundle(clientAppPath, CLIENT_BUNDLE_ID, false);
700
+ const [info, serviceSignature, clientSignature] = await Promise.all([
701
+ readBundleInfo(appPath),
702
+ readCodeSignature(appPath),
703
+ readCodeSignature(clientAppPath)
704
+ ]);
705
+ if (!info || serviceSignature.identifier !== SERVICE_BUNDLE_ID || serviceSignature.teamId !== OPENAI_TEAM_ID || clientSignature.identifier !== CLIENT_BUNDLE_ID || clientSignature.teamId !== OPENAI_TEAM_ID) return;
706
+ return {
707
+ bundleId: serviceSignature.identifier,
708
+ version: info.version,
709
+ build: info.build,
710
+ cdHash: serviceSignature.cdHash,
711
+ teamId: serviceSignature.teamId,
712
+ clientBundleId: clientSignature.identifier,
713
+ clientCdHash: clientSignature.cdHash,
714
+ clientTeamId: clientSignature.teamId
715
+ };
716
+ } catch {
717
+ return;
718
+ }
719
+ }
720
+ async function verifyTrustedBundle(appPath, bundleId, deep) {
721
+ const requirement = `anchor apple generic and certificate leaf[subject.OU] = "${OPENAI_TEAM_ID}" and identifier "${bundleId}"`;
722
+ await runExec("/usr/bin/codesign", [
723
+ "--verify",
724
+ "--strict",
725
+ ...deep ? ["--deep"] : [],
726
+ `-R=${requirement}`,
727
+ appPath
728
+ ], {
729
+ logOutput: false,
730
+ timeoutMs: INSPECT_TIMEOUT_MS
731
+ });
732
+ }
733
+ async function readBundleInfo(appPath) {
734
+ const result = await runExec("/usr/bin/plutil", [
735
+ "-convert",
736
+ "json",
737
+ "-o",
738
+ "-",
739
+ "--",
740
+ path.join(appPath, "Contents", "Info.plist")
741
+ ], {
742
+ logOutput: false,
743
+ timeoutMs: INSPECT_TIMEOUT_MS
744
+ });
745
+ const parsed = JSON.parse(result.stdout);
746
+ if (!isRecord(parsed)) return;
747
+ const version = parsed.CFBundleShortVersionString;
748
+ const build = parsed.CFBundleVersion;
749
+ return typeof version === "string" && version && typeof build === "string" && build ? {
750
+ version,
751
+ build
752
+ } : void 0;
753
+ }
754
+ async function readCodeSignature(appPath) {
755
+ const result = await runExec("/usr/bin/codesign", [
756
+ "-d",
757
+ "--verbose=4",
758
+ appPath
759
+ ], {
760
+ logOutput: false,
761
+ timeoutMs: INSPECT_TIMEOUT_MS
762
+ });
763
+ const output = `${result.stdout}\n${result.stderr}`;
764
+ const identifier = readCodeSignField(output, "Identifier");
765
+ const teamId = readCodeSignField(output, "TeamIdentifier");
766
+ const cdHash = readCodeSignField(output, "CDHash").toLowerCase();
767
+ if (!identifier || !teamId || !/^[a-f0-9]+$/.test(cdHash)) throw new Error(`Could not inspect the signed identity at ${appPath}.`);
768
+ return {
769
+ identifier,
770
+ teamId,
771
+ cdHash
772
+ };
773
+ }
774
+ function readCodeSignField(output, field) {
775
+ const prefix = `${field}=`;
776
+ return output.split(/\r?\n/u).find((candidate) => candidate.startsWith(prefix))?.slice(prefix.length).trim() ?? "";
777
+ }
778
+ async function copyServiceAppWithDitto(sourcePath, targetPath) {
779
+ await runExec("/usr/bin/ditto", [
780
+ "--noqtn",
781
+ sourcePath,
782
+ targetPath
783
+ ], {
784
+ logOutput: false,
785
+ timeoutMs: COPY_TIMEOUT_MS
786
+ });
787
+ }
788
+ //#endregion
789
+ //#region extensions/codex/src/app-server/auth-bridge.ts
790
+ const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai:default";
791
+ const CODEX_HOME_ENV_VAR = "CODEX_HOME";
792
+ const HOME_ENV_VAR = "HOME";
793
+ const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
794
+ "CODEX_API_KEY",
795
+ "OPENAI_API_KEY",
796
+ "CODEX_ACCESS_TOKEN"
797
+ ];
798
+ const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
799
+ const MAX_COMPUTER_USE_ARTIFACT_OWNERS = 128;
800
+ const activeComputerUseArtifactReconciliations = /* @__PURE__ */ new Map();
801
+ const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
802
+ async function bridgeCodexAppServerStartOptions(params) {
803
+ if (params.startOptions.transport !== "stdio") return params.startOptions;
804
+ const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir);
805
+ if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
806
+ if (params.authProfileId === null) return scopeStartOptions();
807
+ const store = resolveCodexAppServerAuthProfileStore({
808
+ agentDir: params.agentDir,
809
+ authProfileId: params.authProfileId,
810
+ authProfileStore: params.authProfileStore,
811
+ config: params.config
812
+ });
813
+ const authProfileId = resolveCodexAppServerAuthProfileId({
814
+ authProfileId: params.authProfileId,
815
+ store,
816
+ config: params.config
817
+ });
818
+ if (!authProfileId) assertNoUnimportedAgentCodexAuthFile(params);
819
+ const scopedStartOptions = await scopeStartOptions();
820
+ return shouldClearOpenAiApiKeyForCodexAuthProfile({
821
+ store,
822
+ authProfileId
823
+ }) ? withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS) : scopedStartOptions;
824
+ }
825
+ function assertNoUnimportedAgentCodexAuthFile(params) {
826
+ if (params.authRequirement === "api-key" && resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions: params.startOptions })) return;
827
+ const message = resolveUnimportedAgentCodexAuthMessage(params);
828
+ if (message) throw new AgentHarnessPreflightError(message);
829
+ }
830
+ function resolveUnimportedAgentCodexAuthMessage(params) {
831
+ if (params.startOptions.transport !== "stdio" || params.startOptions.homeScope === "user") return;
832
+ const codexHome = resolveCodexAppServerHomeDir(params.agentDir);
833
+ const authPath = path.join(codexHome, CODEX_AUTH_JSON_FILENAME);
834
+ if (!fs.existsSync(authPath)) return;
835
+ const targetAgentId = params.agentId?.trim() || "<agent-id>";
836
+ return `A Codex auth file exists at ${authPath}, but agent-scoped Codex runs use OpenClaw's auth store and do not read that file. Preview only that credential import with \`openclaw migrate plan codex --from <codex-home> --agent ${targetAgentId} --include-secrets --item auth:openai\`, then run \`openclaw migrate apply codex --from <codex-home> --agent ${targetAgentId} --include-secrets --item auth:openai --yes\`. If the plan finds no credentials, remove the stale auth file.`;
837
+ }
838
+ /** Resolves prepared profile login material once so cache identity and RPC login cannot drift. */
839
+ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
840
+ const agentDir = params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {});
841
+ const store = resolveCodexAppServerAuthProfileStore({
842
+ agentDir,
843
+ authProfileId: params.authProfileId,
844
+ authProfileStore: params.authProfileStore,
845
+ config: params.config
846
+ });
847
+ const profileId = resolveCodexAppServerAuthProfileId({
848
+ authProfileId: params.authProfileId,
849
+ store,
850
+ config: params.config
851
+ });
852
+ if (!profileId) return;
853
+ const credential = store.profiles[profileId];
854
+ if (!credential || !isCodexAppServerAuthProfileCredential(credential)) return;
855
+ const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
856
+ agentDir,
857
+ authProfileId: profileId,
858
+ authProfileStore: store,
859
+ config: params.config
860
+ });
861
+ if (!loginParams) return;
862
+ const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
863
+ const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
864
+ const secretFreeCacheKey = credential.type === "api_key" && loginParams.type === "apiKey" ? `${accountId}:${fingerprintApiKeyAuthProfileCacheKey(loginParams.apiKey)}` : loginParams.type === "chatgptAuthTokens" && (credential.type === "token" || !stableChatgptAccountId) ? `${accountId}:${fingerprintTokenAuthProfileCacheKey(loginParams.accessToken)}` : accountId;
865
+ const chatgptAccountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : void 0;
866
+ return {
867
+ loginParams,
868
+ secretFreeCacheKey,
869
+ ...chatgptAccountId ? { chatgptAccountId } : {}
870
+ };
871
+ }
872
+ /** Maps one prepared route to one mutually exclusive app-server auth handoff. */
873
+ async function resolveCodexAppServerPreparedAuthHandoff(params) {
874
+ const usesNativeHome = params.homeScope === "user";
875
+ if (params.requirePreparedAuth && usesNativeHome) throw createCodexAppServerAuthError("Codex remote-exec cloud placement requires prepared OpenAI auth. Configure an OpenAI API-key, OAuth, or token profile and use appServer.homeScope=\"agent\"; ambient credentials and native Codex auth are not allowed.");
876
+ if (usesNativeHome) return { nativeAuthProfile: true };
877
+ if (params.authRequirement === "api-key") {
878
+ const apiKey = params.resolvedApiKey?.trim();
879
+ if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
880
+ return {
881
+ nativeAuthProfile: false,
882
+ preparedAuth: {
883
+ kind: "api-key",
884
+ apiKey
885
+ }
886
+ };
887
+ }
888
+ const authProfileId = params.authProfileId?.trim() || void 0;
889
+ const nativeAuthProfile = isCodexAppServerNativeAuthProfile({
890
+ authProfileId,
891
+ authProfileStore: params.authProfileStore,
892
+ agentDir: params.agentDir,
893
+ config: params.config
894
+ });
895
+ if (params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
896
+ authProfileId,
897
+ nativeAuthProfile
898
+ };
899
+ if (!authProfileId || params.authRequirement === "subscription" && !nativeAuthProfile) throw createCodexAppServerAuthError(params.requirePreparedAuth ? "Codex remote-exec cloud placement requires prepared OpenAI auth. Configure an OpenAI API-key, OAuth, or token profile; ambient CODEX_API_KEY, OPENAI_API_KEY, and native Codex auth are not allowed." : params.subscriptionProfileRequiredError);
900
+ const snapshot = await resolveCodexAppServerPreparedAuthProfileSnapshot({
901
+ authProfileId,
902
+ authProfileStore: params.authProfileStore,
903
+ agentDir: params.agentDir,
904
+ config: params.config
905
+ });
906
+ if (!snapshot) throw createCodexAppServerAuthError(params.requirePreparedAuth ? "Codex remote-exec cloud placement could not prepare the selected OpenAI auth profile. Repair or replace the profile, then retry." : params.subscriptionProfileUnusableError);
907
+ return {
908
+ authProfileId,
909
+ nativeAuthProfile,
910
+ preparedAuth: {
911
+ kind: "profile",
912
+ profileId: authProfileId,
913
+ store: params.authProfileStore,
914
+ snapshot
915
+ }
916
+ };
917
+ }
918
+ async function resolveCodexAppServerAuthAccountCacheKey(params) {
919
+ const agentDir = params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {});
920
+ const store = resolveCodexAppServerAuthProfileStore({
921
+ agentDir,
922
+ authProfileId: params.authProfileId,
923
+ authProfileStore: params.authProfileStore,
924
+ config: params.config
925
+ });
926
+ const profileId = resolveCodexAppServerAuthProfileId({
927
+ authProfileId: params.authProfileId,
928
+ store,
929
+ config: params.config
930
+ });
931
+ if (!profileId) return;
932
+ const credential = store.profiles[profileId];
933
+ if (!credential || !isCodexAppServerAuthProfileCredential(credential)) return;
934
+ if (credential.type === "api_key") {
935
+ const apiKey = (await resolveApiKeyForProfile({
936
+ store,
937
+ profileId,
938
+ agentDir
939
+ }))?.apiKey?.trim();
940
+ return apiKey ? `${resolveChatgptAccountId(profileId, credential)}:${fingerprintApiKeyAuthProfileCacheKey(apiKey)}` : resolveChatgptAccountId(profileId, credential);
941
+ }
942
+ if (credential.type === "token") {
943
+ const accessToken = (await resolveApiKeyForProfile({
944
+ store,
945
+ profileId,
946
+ agentDir
947
+ }))?.apiKey?.trim();
948
+ return accessToken ? `${resolveChatgptAccountId(profileId, credential)}:${fingerprintTokenAuthProfileCacheKey(accessToken)}` : resolveChatgptAccountId(profileId, credential);
949
+ }
950
+ return resolveChatgptAccountId(profileId, credential);
951
+ }
952
+ async function withCodexHomeEnvironment(startOptions, agentDir) {
953
+ const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
954
+ const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
955
+ await fs$1.mkdir(codexHome, { recursive: true });
956
+ if (nativeHome) await fs$1.mkdir(nativeHome, { recursive: true });
957
+ const nextStartOptions = {
958
+ ...startOptions,
959
+ env: {
960
+ ...startOptions.env,
961
+ [CODEX_HOME_ENV_VAR]: codexHome,
962
+ ...nativeHome ? { [HOME_ENV_VAR]: nativeHome } : {}
963
+ }
964
+ };
965
+ const clearEnv = withoutClearedCodexHomeEnv(startOptions.clearEnv);
966
+ if (clearEnv) nextStartOptions.clearEnv = clearEnv;
967
+ else delete nextStartOptions.clearEnv;
968
+ return nextStartOptions;
969
+ }
970
+ /** Reconciles Computer Use artifacts for the exact managed command about to start. */
971
+ async function reconcileCodexComputerUseStartArtifacts(params) {
972
+ if (params.startOptions.transport !== "stdio") return;
973
+ const codexHome = resolveCodexAppServerLocalHomeDir(params.startOptions, params.agentDir);
974
+ const key = path.resolve(codexHome);
975
+ let owner = activeComputerUseArtifactReconciliations.get(key);
976
+ if (!owner) {
977
+ owner = {
978
+ active: 0,
979
+ tail: Promise.resolve()
980
+ };
981
+ activeComputerUseArtifactReconciliations.set(key, owner);
982
+ } else {
983
+ activeComputerUseArtifactReconciliations.delete(key);
984
+ activeComputerUseArtifactReconciliations.set(key, owner);
985
+ }
986
+ owner.active += 1;
987
+ const epoch = params.desktopGeneration?.epoch;
988
+ if (epoch !== void 0 && (owner.latestEpoch === void 0 || epoch > owner.latestEpoch)) owner.latestEpoch = epoch;
989
+ const assertCurrent = () => {
990
+ params.assertCurrent?.();
991
+ if (epoch !== void 0 && owner.latestEpoch !== epoch) throw new Error("Codex Computer Use artifact reconciliation was superseded.");
992
+ };
993
+ const operation = owner.tail.catch(() => void 0).then(async () => {
994
+ assertCurrent();
995
+ const appliedCacheBinding = await reconcileCodexComputerUseStartArtifactsOnce({
996
+ ...params,
997
+ codexHome,
998
+ assertCurrent,
999
+ previousCacheBinding: owner.appliedCacheBinding
1000
+ });
1001
+ assertCurrent();
1002
+ owner.appliedCacheBinding = appliedCacheBinding;
1003
+ });
1004
+ const settled = operation.then(() => void 0, () => void 0);
1005
+ owner.tail = settled;
1006
+ try {
1007
+ await operation;
1008
+ } finally {
1009
+ owner.active = Math.max(0, owner.active - 1);
1010
+ if (owner.active === 0 && owner.latestEpoch === void 0 && activeComputerUseArtifactReconciliations.get(key) === owner && owner.tail === settled) activeComputerUseArtifactReconciliations.delete(key);
1011
+ pruneComputerUseArtifactOwners();
1012
+ }
1013
+ }
1014
+ async function reconcileCodexComputerUseStartArtifactsOnce(params) {
1015
+ const codexHome = params.codexHome;
1016
+ const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
1017
+ const ownsIsolatedCodexHome = params.ownsIsolatedCodexHome ?? (params.startOptions.homeScope !== "user" && !params.startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim());
1018
+ const shouldProvisionComputerUse = computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome;
1019
+ if (shouldProvisionComputerUse) await ensureOwnedCodexHome(codexHome, params.agentDir);
1020
+ else await fs$1.mkdir(codexHome, { recursive: true });
1021
+ const desktopCandidates = resolveMacOSDesktopCodexAppPathCandidates();
1022
+ const exactDesktopCandidate = desktopCandidates.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(params.startOptions.command));
1023
+ const usesManagedBundledMarketplace = !computerUseConfig.marketplaceSource && !computerUseConfig.marketplacePath && !computerUseConfig.marketplaceName;
1024
+ const needsBundledMarketplace = usesManagedBundledMarketplace || computerUseConfig.pluginCacheMode === "shared" && !computerUseConfig.marketplaceName && !computerUseConfig.marketplacePath;
1025
+ const artifactCandidate = shouldProvisionComputerUse ? await resolveCompleteComputerUseArtifactCandidate({
1026
+ candidates: exactDesktopCandidate ? [exactDesktopCandidate] : desktopCandidates,
1027
+ needsBundledMarketplace
1028
+ }) : exactDesktopCandidate;
1029
+ params.assertCurrent();
1030
+ if (shouldProvisionComputerUse) {
1031
+ if (desktopCandidates.length > 0 && !artifactCandidate) throw new CodexComputerUseCandidateArtifactsUnavailableError();
1032
+ try {
1033
+ const marketplacePath = usesManagedBundledMarketplace ? await ensureCodexManagedBundledMarketplace({
1034
+ codexHome,
1035
+ ownershipRoot: params.agentDir,
1036
+ ...artifactCandidate ? {
1037
+ appServerCommand: artifactCandidate.appServerCommandPath,
1038
+ candidates: [artifactCandidate],
1039
+ ownershipCandidates: desktopCandidates
1040
+ } : {},
1041
+ assertCurrent: params.assertCurrent
1042
+ }) : void 0;
1043
+ params.assertCurrent();
1044
+ if (usesManagedBundledMarketplace && desktopCandidates.length > 0 && !marketplacePath) throw new CodexComputerUseCandidateArtifactsUnavailableError();
1045
+ const service = await ensureCodexComputerUseServiceApp({
1046
+ codexHome,
1047
+ ownershipRoot: params.agentDir,
1048
+ ...artifactCandidate ? {
1049
+ appServerCommand: artifactCandidate.appServerCommandPath,
1050
+ sourceAppCandidates: artifactCandidate.computerUseServiceAppPaths
1051
+ } : {},
1052
+ assertCurrent: params.assertCurrent
1053
+ });
1054
+ params.assertCurrent();
1055
+ if (desktopCandidates.length > 0 && service.status === "source_missing") throw new CodexComputerUseCandidateArtifactsUnavailableError();
1056
+ } catch (error) {
1057
+ params.assertCurrent();
1058
+ if (error instanceof CodexComputerUseCandidateArtifactsUnavailableError) throw error;
1059
+ throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
1060
+ cause: error,
1061
+ scope: "harness"
1062
+ });
1063
+ }
1064
+ }
1065
+ params.assertCurrent();
1066
+ const cacheBinding = [
1067
+ params.desktopGeneration?.epoch ?? "manual",
1068
+ artifactCandidate?.bundledMarketplacePath ?? "default",
1069
+ computerUseConfig.pluginName
1070
+ ].join("\0");
1071
+ const cache = await ensureCodexComputerUseSharedPluginCache({
1072
+ codexHome,
1073
+ config: computerUseConfig,
1074
+ ...ownsIsolatedCodexHome ? { ownershipRoot: params.agentDir } : {},
1075
+ ...artifactCandidate ? { bundledMarketplacePath: artifactCandidate.bundledMarketplacePath } : {},
1076
+ assertCurrent: params.assertCurrent,
1077
+ forceRefresh: params.forceCacheRefresh === true || params.previousCacheBinding !== cacheBinding
1078
+ });
1079
+ params.assertCurrent();
1080
+ return cache.status === "shared" ? cacheBinding : void 0;
1081
+ }
1082
+ async function resolveCompleteComputerUseArtifactCandidate(params) {
1083
+ for (const candidate of params.candidates) {
1084
+ if (params.needsBundledMarketplace && !await resolveCodexManagedBundledMarketplaceSource({ candidates: [candidate] })) continue;
1085
+ if (await resolveCodexComputerUseServiceAppSourcePath({ sourceAppCandidates: candidate.computerUseServiceAppPaths })) return candidate;
1086
+ }
1087
+ }
1088
+ function pruneComputerUseArtifactOwners() {
1089
+ while (activeComputerUseArtifactReconciliations.size > MAX_COMPUTER_USE_ARTIFACT_OWNERS) {
1090
+ const inactive = [...activeComputerUseArtifactReconciliations].find(([, owner]) => owner.active === 0);
1091
+ if (!inactive) return;
1092
+ activeComputerUseArtifactReconciliations.delete(inactive[0]);
1093
+ }
1094
+ }
1095
+ var CodexComputerUseCandidateArtifactsUnavailableError = class extends Error {
1096
+ constructor() {
1097
+ super("The selected Codex desktop app does not contain complete Computer Use artifacts.");
1098
+ this.code = "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
1099
+ this.name = "CodexComputerUseCandidateArtifactsUnavailableError";
1100
+ }
1101
+ };
1102
+ function withoutClearedCodexHomeEnv(clearEnv) {
1103
+ if (!clearEnv) return;
1104
+ const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
1105
+ const filtered = clearEnv.filter((envVar) => !reserved.has(envVar.trim().toUpperCase()));
1106
+ return filtered.length === clearEnv.length ? clearEnv : filtered;
1107
+ }
1108
+ async function applyCodexAppServerAuthProfile(params) {
1109
+ params.assertCurrent?.();
1110
+ if (!params.preparedAuth && params.authProfileId === null) {
1111
+ await assertNativeCodexAccountMatchesRoute(params.client, params.authRequirement, params.assertCurrent);
1112
+ return;
1113
+ }
1114
+ let loginParams = params.preparedAuth?.kind === "profile" ? params.preparedAuth.snapshot.loginParams : params.preparedAuth?.kind === "api-key" ? {
1115
+ type: "apiKey",
1116
+ apiKey: params.preparedAuth.apiKey
1117
+ } : await resolveCodexAppServerAuthProfileLoginParams({
1118
+ agentDir: params.agentDir,
1119
+ authProfileId: params.authProfileId ?? void 0,
1120
+ authProfileStore: params.authProfileStore,
1121
+ config: params.config
1122
+ });
1123
+ if (params.authRequirement === "subscription" && loginParams?.type !== "chatgptAuthTokens") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.");
1124
+ if (!loginParams && params.authRequirement === "api-key" && params.startOptions?.transport === "stdio") {
1125
+ const env = resolveCodexAppServerSpawnEnv(params.startOptions, process.env);
1126
+ loginParams = await resolveCodexAppServerFallbackApiKeyLoginParams({
1127
+ client: params.client,
1128
+ env,
1129
+ codexCliAuthEnv: process.env,
1130
+ assertCurrent: params.assertCurrent
1131
+ });
1132
+ }
1133
+ if (loginParams) {
1134
+ await params.client.request("account/login/start", loginParams, { assertCurrent: params.assertCurrent });
1135
+ if (loginParams.type === "chatgptAuthTokens") {
1136
+ params.assertCurrent?.();
1137
+ return {
1138
+ accessFingerprint: fingerprintTokenAuthProfileCacheKey(loginParams.accessToken),
1139
+ chatgptAccountId: loginParams.chatgptAccountId
1140
+ };
1141
+ }
1142
+ }
1143
+ }
1144
+ /**
1145
+ * Native-home connections are verified, never logged into. Both directions of the
1146
+ * check protect the same billing boundary: a subscription route cannot run without
1147
+ * ChatGPT tokens, and a Platform route must not silently spend the operator's
1148
+ * ChatGPT plan. An absent account is left alone because the native home may serve a
1149
+ * custom model provider that reports no OpenAI account at all.
1150
+ */
1151
+ async function assertNativeCodexAccountMatchesRoute(client, authRequirement, assertCurrent) {
1152
+ if (!authRequirement) return;
1153
+ const response = await client.request("account/read", { refreshToken: false }, { assertCurrent });
1154
+ const accountType = isJsonObject(response.account) ? response.account.type : void 0;
1155
+ if (authRequirement === "subscription") {
1156
+ if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription route requires ChatGPT auth in the native Codex home. Run `codex login` for that home, or use appServer.homeScope=\"agent\" with an OpenClaw OAuth profile, then retry.");
1157
+ return;
1158
+ }
1159
+ if (accountType === "chatgpt") throw createCodexAppServerAuthError("Codex Platform route requires an API-key account, but the native Codex home is signed in with a ChatGPT subscription. Sign that home in with `codex login --with-api-key`, or set appServer.homeScope=\"agent\" so OpenClaw can inject its own key.");
1160
+ }
1161
+ function createCodexAppServerAuthError(message, cause) {
1162
+ const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
1163
+ return Object.assign(error, { status: 401 });
1164
+ }
1165
+ var CodexAppServerAuthProfileUnavailableError = class extends Error {
1166
+ constructor(..._args) {
1167
+ super(..._args);
1168
+ this.status = 401;
1169
+ this.code = "selected_auth_profile_unavailable";
1170
+ }
1171
+ };
1172
+ async function resolveCodexAppServerAuthProfileLoginParams(params) {
1173
+ const store = resolveCodexAppServerAuthProfileStore(params);
1174
+ const profileId = resolveCodexAppServerAuthProfileId({
1175
+ authProfileId: params.authProfileId,
1176
+ store,
1177
+ config: params.config
1178
+ });
1179
+ const profile = profileId ? store.profiles[profileId] : void 0;
1180
+ if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
1181
+ if (profileId && profile && !isCodexAppServerAuthProfileCredential(profile)) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
1182
+ return await resolveCodexAppServerAuthProfileLoginParamsInternal({
1183
+ ...params,
1184
+ authProfileStore: store
1185
+ });
1186
+ }
1187
+ async function refreshCodexAppServerAuthTokens(params) {
1188
+ const previousAccountId = params.previousAccountId?.trim();
1189
+ const handoffAccountId = params.authHandoff?.chatgptAccountId.trim();
1190
+ if (previousAccountId && handoffAccountId && previousAccountId !== handoffAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh. Retry to start a client for the selected workspace.");
1191
+ if (previousAccountId) {
1192
+ const store = resolveCodexAppServerAuthProfileStore(params);
1193
+ const profileId = resolveCodexAppServerAuthProfileId({
1194
+ authProfileId: params.authProfileId,
1195
+ store,
1196
+ config: params.config
1197
+ });
1198
+ const credential = profileId ? store.profiles[profileId] : void 0;
1199
+ const selectedAccountId = credential ? resolveExplicitChatgptAccountId(credential) ?? (credential.type === "oauth" ? resolveOpenAICodexAuthIdentity({ access: credential.access }).accountId : void 0) : void 0;
1200
+ if (selectedAccountId && selectedAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh. Retry to start a client for the selected workspace.");
1201
+ }
1202
+ const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
1203
+ ...params,
1204
+ forceOAuthRefresh: true
1205
+ });
1206
+ if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.");
1207
+ if (previousAccountId && loginParams.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
1208
+ if (params.authHandoff && loginParams.chatgptAccountId !== params.authHandoff.chatgptAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
1209
+ return {
1210
+ accessToken: loginParams.accessToken,
1211
+ chatgptAccountId: loginParams.chatgptAccountId,
1212
+ chatgptPlanType: loginParams.chatgptPlanType ?? null
1213
+ };
1214
+ }
1215
+ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
1216
+ const store = resolveCodexAppServerAuthProfileStore({
1217
+ agentDir: params.agentDir,
1218
+ authProfileId: params.authProfileId,
1219
+ authProfileStore: params.authProfileStore,
1220
+ config: params.config
1221
+ });
1222
+ const profileId = resolveCodexAppServerAuthProfileId({
1223
+ authProfileId: params.authProfileId,
1224
+ store,
1225
+ config: params.config
1226
+ });
1227
+ if (!profileId) return;
1228
+ const credential = store.profiles[profileId];
1229
+ if (!credential) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
1230
+ if (!isCodexAppServerAuthProfileCredential(credential)) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
1231
+ const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
1232
+ agentDir: params.agentDir,
1233
+ store,
1234
+ preferStoreCredential: Boolean(params.authProfileStore?.profiles[profileId]),
1235
+ forceOAuthRefresh: params.forceOAuthRefresh === true,
1236
+ authHandoff: params.authHandoff,
1237
+ previousAccountId: params.previousAccountId,
1238
+ config: params.config
1239
+ });
1240
+ if (!loginParams) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" does not contain usable credentials. Repair or replace the selected OpenAI credential, then retry.`);
1241
+ return loginParams;
1242
+ }
1243
+ async function resolveCodexAppServerFallbackApiKeyLoginParams(params) {
1244
+ const apiKey = readFirstNonEmptyEnv(params.env, CODEX_APP_SERVER_API_KEY_ENV_VARS) ?? await readCodexCliAuthFileApiKey(params.codexCliAuthEnv);
1245
+ if (!apiKey) return;
1246
+ if ((await params.client.request("account/read", { refreshToken: false }, { assertCurrent: params.assertCurrent })).account) return;
1247
+ return {
1248
+ type: "apiKey",
1249
+ apiKey
1250
+ };
1251
+ }
1252
+ async function resolveLoginParamsForCredential(profileId, credential, params) {
1253
+ if (credential.type === "api_key") {
1254
+ const apiKey = (await resolveApiKeyForProfile({
1255
+ store: params.preferStoreCredential ? params.store : ensureAuthProfileStore(params.agentDir, {
1256
+ allowKeychainPrompt: false,
1257
+ profileId
1258
+ }),
1259
+ profileId,
1260
+ agentDir: params.agentDir
1261
+ }))?.apiKey?.trim();
1262
+ return apiKey ? {
1263
+ type: "apiKey",
1264
+ apiKey
1265
+ } : void 0;
1266
+ }
1267
+ if (credential.type === "token") {
1268
+ const accessToken = (await resolveApiKeyForProfile({
1269
+ store: params.preferStoreCredential ? params.store : ensureAuthProfileStore(params.agentDir, {
1270
+ allowKeychainPrompt: false,
1271
+ profileId
1272
+ }),
1273
+ profileId,
1274
+ agentDir: params.agentDir
1275
+ }))?.apiKey?.trim();
1276
+ return accessToken ? buildChatgptAuthTokensParams(profileId, credential, accessToken) : void 0;
1277
+ }
1278
+ if (credential.type !== "oauth") return;
1279
+ const resolvedCredential = await resolveOAuthCredentialForCodexAppServer(profileId, credential, {
1280
+ agentDir: params.agentDir,
1281
+ store: params.store,
1282
+ preferStoreCredential: params.preferStoreCredential,
1283
+ forceRefresh: params.forceOAuthRefresh,
1284
+ authHandoff: params.authHandoff,
1285
+ previousAccountId: params.previousAccountId,
1286
+ config: params.config
1287
+ });
1288
+ const accessToken = resolvedCredential.access?.trim();
1289
+ return accessToken ? buildChatgptAuthTokensParams(profileId, resolvedCredential, accessToken) : void 0;
1290
+ }
1291
+ async function resolveOAuthCredentialForCodexAppServer(profileId, credential, params) {
1292
+ const ownerAgentDir = resolvePersistedAuthProfileOwnerAgentDir({
1293
+ agentDir: params.agentDir,
1294
+ profileId
1295
+ });
1296
+ const persistedCredential = findPersistedAuthProfileCredential({
1297
+ agentDir: ownerAgentDir,
1298
+ profileId
1299
+ });
1300
+ const useScopedCredential = params.preferStoreCredential && shouldUseScopedOAuthCredential({
1301
+ store: params.store,
1302
+ profileId,
1303
+ persistedCredential,
1304
+ suppliedCredential: credential
1305
+ });
1306
+ if (params.preferStoreCredential && params.store.runtimePersistedProfileIds?.includes(profileId) && (persistedCredential?.type !== "oauth" || !isCodexAppServerAuthProvider(persistedCredential.provider))) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is no longer available. Sign in again with OpenClaw, then retry.`);
1307
+ const store = useScopedCredential ? params.store : resolveCodexAppServerAuthProfileStore({
1308
+ agentDir: ownerAgentDir,
1309
+ authProfileId: profileId,
1310
+ config: params.config
1311
+ });
1312
+ const persistedOAuthCredential = !useScopedCredential && persistedCredential?.type === "oauth" && isCodexAppServerAuthProvider(persistedCredential.provider) ? persistedCredential : void 0;
1313
+ const ownerCredential = store.profiles[profileId];
1314
+ const overlaidOAuthCredential = ownerCredential?.type === "oauth" && isCodexAppServerAuthProvider(ownerCredential.provider) ? ownerCredential : void 0;
1315
+ const currentCredential = useScopedCredential || !persistedOAuthCredential ? overlaidOAuthCredential : persistedOAuthCredential;
1316
+ const reuseCompletedRotation = params.forceRefresh && (Boolean(persistedOAuthCredential) || params.preferStoreCredential) && shouldReuseCompletedCodexOAuthRotation({
1317
+ credential: currentCredential,
1318
+ authHandoff: params.authHandoff,
1319
+ previousAccountId: params.previousAccountId
1320
+ });
1321
+ const selectedAccountId = currentCredential ? resolveOpenAICodexAuthIdentity(currentCredential).accountId?.trim() : void 0;
1322
+ const callbackAccountId = params.authHandoff?.chatgptAccountId.trim() ?? params.previousAccountId?.trim() ?? void 0;
1323
+ if (callbackAccountId && selectedAccountId && callbackAccountId !== selectedAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh. Retry to start a client for the selected workspace.");
1324
+ const expectedAccountId = callbackAccountId ?? selectedAccountId;
1325
+ if (useScopedCredential && overlaidOAuthCredential) return await resolveScopedOAuthCredential({
1326
+ store,
1327
+ profileId,
1328
+ credential: overlaidOAuthCredential,
1329
+ forceRefresh: params.forceRefresh && !reuseCompletedRotation,
1330
+ expectedAccountId
1331
+ });
1332
+ if (params.forceRefresh && !persistedOAuthCredential && overlaidOAuthCredential) {
1333
+ if (reuseCompletedRotation) return overlaidOAuthCredential;
1334
+ const refreshedRuntimeCredential = await refreshOAuthCredentialForRuntime({ credential: overlaidOAuthCredential });
1335
+ if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
1336
+ assertCodexOAuthRefreshWorkspace(profileId, refreshedRuntimeCredential, expectedAccountId);
1337
+ store.profiles[profileId] = refreshedRuntimeCredential;
1338
+ return refreshedRuntimeCredential;
1339
+ }
1340
+ const resolved = await resolveApiKeyForProfile({
1341
+ store,
1342
+ profileId,
1343
+ agentDir: ownerAgentDir,
1344
+ forceRefresh: params.forceRefresh && Boolean(persistedOAuthCredential) && !reuseCompletedRotation,
1345
+ allowProfileFallback: false,
1346
+ ...expectedAccountId ? { validateOAuthCredential: (candidate) => assertCodexOAuthRefreshWorkspace(profileId, candidate, expectedAccountId) } : {}
1347
+ });
1348
+ if (!resolved || resolved.credential?.type !== "oauth" || !isCodexAppServerAuthProvider(resolved.credential.provider) || !resolved.apiKey.trim()) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is no longer available. Sign in again with OpenClaw, then retry.`);
1349
+ const candidate = {
1350
+ ...resolved.credential,
1351
+ access: resolved.apiKey
1352
+ };
1353
+ if (isDeepStrictEqual(params.store.profiles[profileId], credential)) params.store.profiles[profileId] = candidate;
1354
+ return candidate;
1355
+ }
1356
+ function shouldReuseCompletedCodexOAuthRotation(params) {
1357
+ const handoff = params.authHandoff;
1358
+ const credential = params.credential;
1359
+ if (!handoff || !credential || !hasUsableOAuthCredential(credential) || fingerprintTokenAuthProfileCacheKey(credential.access.trim()) === handoff.accessFingerprint) return false;
1360
+ const accountId = resolveOpenAICodexAuthIdentity(credential).accountId?.trim();
1361
+ const previousAccountId = params.previousAccountId?.trim();
1362
+ return accountId === handoff.chatgptAccountId && (!previousAccountId || previousAccountId === handoff.chatgptAccountId);
1363
+ }
1364
+ function shouldUseScopedOAuthCredential(params) {
1365
+ if (!params.store.runtimePersistedProfileIds?.includes(params.profileId)) return true;
1366
+ const persisted = params.persistedCredential;
1367
+ if (persisted?.type !== "oauth" || !isCodexAppServerAuthProvider(persisted.provider)) return false;
1368
+ return !isDeepStrictEqual(persisted, params.suppliedCredential) && !hasMatchingOAuthIdentity(persisted, params.suppliedCredential);
1369
+ }
1370
+ function hasMatchingOAuthIdentity(persisted, supplied) {
1371
+ const persistedAccountId = resolveOpenAICodexAuthIdentity(persisted).accountId?.trim();
1372
+ const suppliedAccountId = resolveOpenAICodexAuthIdentity(supplied).accountId?.trim();
1373
+ if (persistedAccountId && suppliedAccountId) return persistedAccountId === suppliedAccountId;
1374
+ const persistedEmail = persisted.email?.trim().toLowerCase();
1375
+ const suppliedEmail = supplied.email?.trim().toLowerCase();
1376
+ return Boolean(persistedEmail && suppliedEmail && persistedEmail === suppliedEmail);
1377
+ }
1378
+ async function resolveScopedOAuthCredential(params) {
1379
+ const existingRefresh = scopedOAuthRefreshQueues.get(params.store)?.get(params.profileId);
1380
+ if (existingRefresh) {
1381
+ const refreshed = await existingRefresh;
1382
+ assertCodexOAuthRefreshWorkspace(params.profileId, refreshed, params.expectedAccountId);
1383
+ return refreshed;
1384
+ }
1385
+ if (!params.forceRefresh && hasUsableOAuthCredential(params.credential)) return params.credential;
1386
+ const storeRefreshes = scopedOAuthRefreshQueues.get(params.store) ?? /* @__PURE__ */ new Map();
1387
+ scopedOAuthRefreshQueues.set(params.store, storeRefreshes);
1388
+ const refresh = (async () => {
1389
+ const current = params.store.profiles[params.profileId];
1390
+ const credential = current?.type === "oauth" ? current : params.credential;
1391
+ if (!params.forceRefresh && hasUsableOAuthCredential(credential)) return credential;
1392
+ const refreshed = await refreshOAuthCredentialForRuntime({ credential });
1393
+ if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
1394
+ assertCodexOAuthRefreshWorkspace(params.profileId, refreshed, params.expectedAccountId);
1395
+ if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing. Retry with the newly selected OpenAI profile.`);
1396
+ params.store.profiles[params.profileId] = refreshed;
1397
+ return refreshed;
1398
+ })();
1399
+ storeRefreshes.set(params.profileId, refresh);
1400
+ try {
1401
+ return await refresh;
1402
+ } finally {
1403
+ if (storeRefreshes.get(params.profileId) === refresh) storeRefreshes.delete(params.profileId);
1404
+ }
1405
+ }
1406
+ function assertCodexOAuthRefreshWorkspace(profileId, credential, expectedAccountId) {
1407
+ if (!expectedAccountId) return;
1408
+ const loginParams = buildChatgptAuthTokensParams(profileId, credential, credential.access.trim());
1409
+ if (loginParams.type !== "chatgptAuthTokens" || loginParams.chatgptAccountId !== expectedAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
1410
+ }
1411
+ function isCodexAppServerAuthProvider(provider) {
1412
+ return provider.trim().toLowerCase() === CODEX_APP_SERVER_AUTH_PROVIDER;
1413
+ }
1414
+ function isCodexAppServerAuthProfileCredential(credential) {
1415
+ return isCodexAppServerAuthProvider(credential.provider);
1416
+ }
1417
+ function shouldClearOpenAiApiKeyForCodexAuthProfile(params) {
1418
+ const profileId = params.authProfileId?.trim();
1419
+ return isCodexSubscriptionCredential(profileId ? params.store.profiles[profileId] : params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]);
1420
+ }
1421
+ function isCodexSubscriptionCredential(credential) {
1422
+ if (!credential || !isCodexAppServerAuthProvider(credential.provider)) return false;
1423
+ return credential.type === "oauth" || credential.type === "token";
1424
+ }
1425
+ function withClearedEnvironmentVariables(startOptions, envVars) {
1426
+ const clearEnv = startOptions.clearEnv ?? [];
1427
+ const missingEnvVars = envVars.filter((envVar) => !clearEnv.includes(envVar));
1428
+ if (missingEnvVars.length === 0) return startOptions;
1429
+ return {
1430
+ ...startOptions,
1431
+ clearEnv: [...clearEnv, ...missingEnvVars]
1432
+ };
1433
+ }
1434
+ function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
1435
+ const storedAccountId = resolveExplicitChatgptAccountId(credential);
1436
+ const tokenAccountId = resolveOpenAICodexAuthIdentity({ access: accessToken }).accountId;
1437
+ if (storedAccountId && tokenAccountId && storedAccountId !== tokenAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" has a different ChatGPT account ID than its access token. Sign in again before retrying.`);
1438
+ const chatgptAccountId = storedAccountId ?? tokenAccountId;
1439
+ if (!chatgptAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is missing its ChatGPT account ID. Sign in again before retrying.`);
1440
+ return {
1441
+ type: "chatgptAuthTokens",
1442
+ accessToken,
1443
+ chatgptAccountId,
1444
+ chatgptPlanType: resolveChatgptPlanType(credential)
1445
+ };
1446
+ }
1447
+ function resolveChatgptPlanType(credential) {
1448
+ const record = credential;
1449
+ const planType = record.chatgptPlanType ?? record.planType;
1450
+ return typeof planType === "string" && planType.trim() ? planType.trim() : null;
1451
+ }
1452
+ function resolveChatgptAccountId(profileId, credential) {
1453
+ return resolveStableChatgptAccountId(credential) ?? profileId;
1454
+ }
1455
+ function resolveStableChatgptAccountId(credential) {
1456
+ return resolveExplicitChatgptAccountId(credential) ?? (credential.email?.trim() || void 0);
1457
+ }
1458
+ function resolveExplicitChatgptAccountId(credential) {
1459
+ if ("accountId" in credential && typeof credential.accountId === "string") {
1460
+ const accountId = credential.accountId.trim();
1461
+ if (accountId) return accountId;
1462
+ }
1463
+ }
1464
+ //#endregion
1465
+ //#region extensions/codex/src/app-server/rate-limit-cache.ts
1466
+ const DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS = 6e5;
1467
+ const SPARSE_ACCOUNT_METADATA_KEYS = [
1468
+ "credits",
1469
+ "individualLimit",
1470
+ "planType"
1471
+ ];
1472
+ const rateLimitsByClient = /* @__PURE__ */ new WeakMap();
1473
+ /** Replaces one physical client's cache with an authoritative rate-limit read response. */
1474
+ function rememberCodexRateLimitsRead(client, value, nowMs = Date.now()) {
1475
+ if (value !== void 0) {
1476
+ const revisionsByLimitId = { ...rateLimitsByClient.get(client)?.revisionsByLimitId };
1477
+ for (const limitId of readRateLimitIds(value)) revisionsByLimitId[limitId] = (revisionsByLimitId[limitId] ?? 0) + 1;
1478
+ rateLimitsByClient.set(client, {
1479
+ value,
1480
+ updatedAtMs: nowMs,
1481
+ revisionsByLimitId
1482
+ });
1483
+ }
1484
+ }
1485
+ /** Merges a sparse rolling notification into one physical client's latest read response. */
1486
+ function mergeCodexRateLimitsUpdate(client, value, nowMs = Date.now()) {
1487
+ const update = isJsonObject(value) && isJsonObject(value.rateLimits) ? value.rateLimits : void 0;
1488
+ if (!update) return;
1489
+ const currentState = rateLimitsByClient.get(client);
1490
+ const current = currentState?.value;
1491
+ const limitId = readLimitId(update);
1492
+ rateLimitsByClient.set(client, {
1493
+ value: mergeRateLimitUpdate(current, update),
1494
+ updatedAtMs: nowMs,
1495
+ revisionsByLimitId: {
1496
+ ...currentState?.revisionsByLimitId,
1497
+ [limitId]: (currentState?.revisionsByLimitId[limitId] ?? 0) + 1
1498
+ }
1499
+ });
1500
+ }
1501
+ /** Per-limit marker used to trust only primary Codex updates from one turn startup. */
1502
+ function readCodexRateLimitsRevision(client, limitId = "codex") {
1503
+ return rateLimitsByClient.get(client)?.revisionsByLimitId[limitId] ?? 0;
1504
+ }
1505
+ /** Reads one physical client's cached rate-limit payload within the max-age window. */
1506
+ function readRecentCodexRateLimits(client, options) {
1507
+ const state = rateLimitsByClient.get(client);
1508
+ if (!state) return;
1509
+ const nowMs = options?.nowMs ?? Date.now();
1510
+ const maxAgeMs = options?.maxAgeMs ?? DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS;
1511
+ return maxAgeMs >= 0 && nowMs - state.updatedAtMs > maxAgeMs ? void 0 : state.value;
1512
+ }
1513
+ function mergeRateLimitUpdate(current, update) {
1514
+ const currentEnvelope = isJsonObject(current) ? current : void 0;
1515
+ const currentPrimary = currentEnvelope && isJsonObject(currentEnvelope.rateLimits) ? currentEnvelope.rateLimits : void 0;
1516
+ const currentByLimitId = currentEnvelope && isJsonObject(currentEnvelope.rateLimitsByLimitId) ? currentEnvelope.rateLimitsByLimitId : void 0;
1517
+ const limitId = readLimitId(update);
1518
+ const currentPrimaryLimitId = currentPrimary ? readLimitId(currentPrimary) : void 0;
1519
+ const currentForLimit = (currentByLimitId && isJsonObject(currentByLimitId[limitId]) ? currentByLimitId[limitId] : void 0) ?? (currentPrimaryLimitId === limitId ? currentPrimary : void 0);
1520
+ const merged = mergeSparseSnapshot(isJsonObject(currentForLimit) ? currentForLimit : void 0, currentPrimary, update, limitId);
1521
+ const nextPrimary = !currentPrimary || currentPrimaryLimitId === limitId ? merged : currentPrimary;
1522
+ let nextByLimitId;
1523
+ if (currentByLimitId) nextByLimitId = {
1524
+ ...currentByLimitId,
1525
+ [limitId]: merged
1526
+ };
1527
+ else if (currentPrimary && currentPrimaryLimitId && currentPrimaryLimitId !== limitId) nextByLimitId = {
1528
+ [currentPrimaryLimitId]: currentPrimary,
1529
+ [limitId]: merged
1530
+ };
1531
+ return {
1532
+ ...currentEnvelope,
1533
+ rateLimits: nextPrimary,
1534
+ ...nextByLimitId ? { rateLimitsByLimitId: nextByLimitId } : {}
1535
+ };
1536
+ }
1537
+ function readRateLimitIds(value) {
1538
+ if (!isJsonObject(value)) return [];
1539
+ const ids = /* @__PURE__ */ new Set();
1540
+ if (isJsonObject(value.rateLimits)) ids.add(readLimitId(value.rateLimits));
1541
+ if (isJsonObject(value.rateLimitsByLimitId)) for (const [key, snapshot] of Object.entries(value.rateLimitsByLimitId)) {
1542
+ const snapshotLimitId = isJsonObject(snapshot) && typeof snapshot.limitId === "string" ? snapshot.limitId.trim() : "";
1543
+ ids.add(snapshotLimitId || key);
1544
+ }
1545
+ return [...ids];
1546
+ }
1547
+ function mergeSparseSnapshot(current, accountFallback, update, limitId) {
1548
+ const merged = {
1549
+ ...update,
1550
+ limitId
1551
+ };
1552
+ for (const key of SPARSE_ACCOUNT_METADATA_KEYS) {
1553
+ const previous = current?.[key] ?? accountFallback?.[key];
1554
+ if (merged[key] == null && previous != null) merged[key] = previous;
1555
+ }
1556
+ return merged;
1557
+ }
1558
+ function readLimitId(snapshot) {
1559
+ const value = snapshot.limitId;
1560
+ return typeof value === "string" && value.trim() ? value.trim() : "codex";
1561
+ }
1562
+ //#endregion
1563
+ //#region extensions/codex/src/app-server/client-runtime.ts
1564
+ /** Client-scoped Codex auth and account observers. */
1565
+ /** Match Codex's native grace window without retaining inactive conversations indefinitely. */
1566
+ const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 18e5;
1567
+ /** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
1568
+ const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
1569
+ /** Return a deterministic error before Codex cancels its ten-second external-auth request. */
1570
+ const CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS = 9e3;
1571
+ const configuredClients = /* @__PURE__ */ new WeakMap();
1572
+ const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
1573
+ const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
1574
+ /** Only an initialized, still-open physical client can own retained native subscriptions. */
1575
+ function isCodexAppServerClientRuntimeLive(client) {
1576
+ const runtime = configuredClients.get(client);
1577
+ return runtime !== void 0 && !runtime.closed;
1578
+ }
1579
+ function recordCodexAppServerAuthHandoff(client, handoff) {
1580
+ const runtime = configuredClients.get(client);
1581
+ if (runtime && !runtime.closed && handoff) runtime.authHandoff = handoff;
1582
+ }
1583
+ /** Reference history is only trusted while this native subscription stays warm. */
1584
+ function forgetCodexWorkspaceReferences(client, threadId) {
1585
+ configuredClients.get(client)?.workspaceReferences.delete(threadId);
1586
+ }
1587
+ /** Record accepted input without clearing a compaction observed during turn/start. */
1588
+ function prepareCodexWorkspaceReferences(client, threadId, reference) {
1589
+ const runtime = configuredClients.get(client);
1590
+ const digest = createHash("sha256").update(reference ?? "").digest("hex");
1591
+ const previous = runtime?.workspaceReferences.get(threadId) ?? { needsReintroduction: true };
1592
+ if (runtime && !runtime.closed) runtime.workspaceReferences.set(threadId, previous);
1593
+ return {
1594
+ include: previous.needsReintroduction || previous.digest !== digest,
1595
+ accepted: () => {
1596
+ if (!runtime || runtime.closed || runtime.workspaceReferences.get(threadId) !== previous) return;
1597
+ runtime.workspaceReferences.set(threadId, {
1598
+ digest,
1599
+ needsReintroduction: false
1600
+ });
1601
+ }
1602
+ };
1603
+ }
1604
+ /** Immutable declarations are data owned by this physical client, never retained executors. */
1605
+ async function readCodexClientSessionMeta(client, sessionsRoot, boundRolloutPath, threadId) {
1606
+ let rolloutPath = boundRolloutPath;
1607
+ const runtime = configuredClients.get(client);
1608
+ if (!runtime || runtime.closed) throw new Error("Codex native metadata requires a live selected client");
1609
+ const cached = runtime.sessionMetadata.get(threadId);
1610
+ if (cached && cached.sessionsRoot === sessionsRoot && (!rolloutPath || cached.rolloutPath === rolloutPath)) return structuredClone(cached.metadata);
1611
+ if (!rolloutPath) {
1612
+ const { thread } = await client.request("thread/read", {
1613
+ threadId,
1614
+ includeTurns: false
1615
+ });
1616
+ if (thread.id !== threadId || !thread.path) throw new Error("Codex native metadata has no verified thread path");
1617
+ rolloutPath = thread.path;
1618
+ }
1619
+ const metadata = await readCodexSessionMeta(sessionsRoot, rolloutPath, threadId);
1620
+ if (runtime.closed || !metadata) throw new Error("Codex native metadata is unavailable on the selected client");
1621
+ runtime.sessionMetadata.delete(threadId);
1622
+ runtime.sessionMetadata.set(threadId, {
1623
+ sessionsRoot,
1624
+ rolloutPath,
1625
+ metadata
1626
+ });
1627
+ pruneMapToMaxSize(runtime.sessionMetadata, CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE);
1628
+ return structuredClone(metadata);
1629
+ }
1630
+ /** Installs one auth-refresh handler and one rate-limit observer per physical client. */
1631
+ function ensureCodexAppServerClientRuntime(client, context) {
1632
+ const existing = configuredClients.get(client);
1633
+ if (existing) {
1634
+ if (existing.closed) return;
1635
+ existing.context = {
1636
+ ...existing.context,
1637
+ config: context.config
1638
+ };
1639
+ return;
1640
+ }
1641
+ const runtime = {
1642
+ context,
1643
+ closed: false,
1644
+ retainedThreads: /* @__PURE__ */ new Map(),
1645
+ claimedThreads: /* @__PURE__ */ new Map(),
1646
+ releasingThreads: /* @__PURE__ */ new Map(),
1647
+ protectedThreads: /* @__PURE__ */ new Map(),
1648
+ sessionMetadata: /* @__PURE__ */ new Map(),
1649
+ workspaceReferences: /* @__PURE__ */ new Map()
1650
+ };
1651
+ configuredClients.set(client, runtime);
1652
+ client.addCloseHandler(() => {
1653
+ runtime.closed = true;
1654
+ if (runtime.evictionTimer) {
1655
+ clearTimeout(runtime.evictionTimer);
1656
+ runtime.evictionTimer = void 0;
1657
+ }
1658
+ runtime.retainedThreads.clear();
1659
+ runtime.claimedThreads.clear();
1660
+ runtime.protectedThreads.clear();
1661
+ runtime.sessionMetadata.clear();
1662
+ runtime.workspaceReferences.clear();
1663
+ });
1664
+ client.addRequestHandler(async (request) => {
1665
+ if (request.method !== "account/chatgptAuthTokens/refresh") return;
1666
+ if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
1667
+ const previousAccountId = isJsonObject(request.params) && typeof request.params.previousAccountId === "string" ? request.params.previousAccountId.trim() || void 0 : void 0;
1668
+ const authHandoff = runtime.authHandoff;
1669
+ try {
1670
+ const tokens = await withTimeout(refreshCodexAppServerAuthTokens({
1671
+ agentDir: runtime.context.agentDir,
1672
+ authProfileId: runtime.context.authProfileId,
1673
+ ...authHandoff ? { authHandoff } : {},
1674
+ ...previousAccountId ? { previousAccountId } : {},
1675
+ ...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
1676
+ config: runtime.context.config
1677
+ }), CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS, "Codex app-server ChatGPT token refresh timed out before its external-auth deadline. Retry the request; if it persists, sign in again with OpenClaw.");
1678
+ if (previousAccountId && tokens.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
1679
+ if (runtime.closed) throw new Error("Codex app-server client closed during ChatGPT token refresh.");
1680
+ runtime.authHandoff = {
1681
+ accessFingerprint: fingerprintTokenAuthProfileCacheKey(tokens.accessToken),
1682
+ chatgptAccountId: tokens.chatgptAccountId
1683
+ };
1684
+ return { ...tokens };
1685
+ } catch (error) {
1686
+ runtime.context.onAuthRefreshFailure?.();
1687
+ throw error;
1688
+ }
1689
+ });
1690
+ client.addNotificationHandler((notification) => {
1691
+ if (notification.method === "item/completed" && isJsonObject(notification.params) && isJsonObject(notification.params.item) && notification.params.item.type === "contextCompaction" && typeof notification.params.threadId === "string") {
1692
+ const threadId = notification.params.threadId;
1693
+ const previous = runtime.workspaceReferences.get(threadId);
1694
+ if (previous) runtime.workspaceReferences.set(threadId, {
1695
+ ...previous,
1696
+ needsReintroduction: true
1697
+ });
1698
+ }
1699
+ if (notification.method === "account/rateLimits/updated") {
1700
+ mergeCodexRateLimitsUpdate(client, notification.params);
1701
+ return;
1702
+ }
1703
+ if (notification.method === "thread/archived" || notification.method === "thread/deleted" || notification.method === "thread/closed") {
1704
+ const threadId = notification.params?.threadId;
1705
+ if (typeof threadId === "string") {
1706
+ const releasing = runtime.releasingThreads.get(threadId);
1707
+ if (releasing) releasing.invalidated = true;
1708
+ runtime.retainedThreads.delete(threadId);
1709
+ runtime.claimedThreads.delete(threadId);
1710
+ runtime.sessionMetadata.delete(threadId);
1711
+ runtime.workspaceReferences.delete(threadId);
1712
+ scheduleRetainedThreadEviction(client, runtime);
1713
+ }
1714
+ }
1715
+ });
1716
+ }
1717
+ function scheduleRetainedThreadEviction(client, runtime) {
1718
+ if (runtime.evictionTimer) {
1719
+ clearTimeout(runtime.evictionTimer);
1720
+ runtime.evictionTimer = void 0;
1721
+ }
1722
+ if (runtime.closed) return;
1723
+ let expiresAt = Number.POSITIVE_INFINITY;
1724
+ for (const [threadId, thread] of runtime.retainedThreads) if (!runtime.protectedThreads.has(threadId)) expiresAt = Math.min(expiresAt, thread.expiresAt);
1725
+ if (!Number.isFinite(expiresAt)) return;
1726
+ runtime.evictionTimer = setTimeout(() => {
1727
+ runtime.evictionTimer = void 0;
1728
+ evictExpiredRetainedThreads(client, runtime).catch((error) => {
1729
+ embeddedAgentLog$1.warn("codex retained thread expiry failed", { reason: formatErrorMessage(error) });
1730
+ });
1731
+ }, Math.max(0, expiresAt - Date.now()));
1732
+ runtime.evictionTimer.unref?.();
1733
+ }
1734
+ async function releaseRetainedThread(client, runtime, threadId, assertCurrent) {
1735
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1736
+ if (pendingRelease) {
1737
+ await pendingRelease.completion;
1738
+ assertCurrent?.();
1739
+ return false;
1740
+ }
1741
+ const retained = runtime.retainedThreads.get(threadId);
1742
+ if (!retained) return false;
1743
+ const olderThreadIds = /* @__PURE__ */ new Set();
1744
+ for (const candidateThreadId of runtime.retainedThreads.keys()) {
1745
+ if (candidateThreadId === threadId) break;
1746
+ olderThreadIds.add(candidateThreadId);
1747
+ }
1748
+ runtime.retainedThreads.delete(threadId);
1749
+ scheduleRetainedThreadEviction(client, runtime);
1750
+ const transition = { completion: Promise.resolve().then(() => {
1751
+ assertCurrent?.();
1752
+ return assertCurrent ? retained.release(threadId, assertCurrent) : retained.release(threadId);
1753
+ }) };
1754
+ runtime.releasingThreads.set(threadId, transition);
1755
+ try {
1756
+ await transition.completion;
1757
+ runtime.workspaceReferences.delete(threadId);
1758
+ return true;
1759
+ } catch (error) {
1760
+ if (!runtime.closed && !transition.invalidated && runtime.releasingThreads.get(threadId) === transition && !runtime.retainedThreads.has(threadId) && !runtime.claimedThreads.has(threadId)) {
1761
+ if (retained.ephemeralPolicy === void 0) retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
1762
+ const newerThreads = [...runtime.retainedThreads.entries()].filter(([candidateThreadId]) => !olderThreadIds.has(candidateThreadId));
1763
+ for (const [candidateThreadId] of newerThreads) runtime.retainedThreads.delete(candidateThreadId);
1764
+ runtime.retainedThreads.set(threadId, retained);
1765
+ for (const [candidateThreadId, newerThread] of newerThreads) runtime.retainedThreads.set(candidateThreadId, newerThread);
1766
+ }
1767
+ throw error;
1768
+ } finally {
1769
+ if (runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1770
+ scheduleRetainedThreadEviction(client, runtime);
1771
+ }
1772
+ }
1773
+ async function evictExpiredRetainedThreads(client, runtime) {
1774
+ const now = Date.now();
1775
+ for (const [threadId, thread] of runtime.retainedThreads) if (thread.expiresAt <= now && !runtime.protectedThreads.has(threadId)) await releaseRetainedThread(client, runtime, threadId);
1776
+ scheduleRetainedThreadEviction(client, runtime);
1777
+ }
1778
+ async function evictExcessIdleThreads(client, runtime) {
1779
+ const idleThreads = () => [...runtime.retainedThreads].filter(([threadId, thread]) => thread.ephemeralPolicy === void 0 && !runtime.protectedThreads.has(threadId));
1780
+ let idleThreadIds = idleThreads();
1781
+ while (idleThreadIds.length > CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE) {
1782
+ await releaseRetainedThread(client, runtime, idleThreadIds[0][0]);
1783
+ idleThreadIds = idleThreads();
1784
+ }
1785
+ }
1786
+ /** Retain separately owned Codex subscriptions; completing B must never cold-restart A. */
1787
+ async function retainCodexAppServerLiveThread(client, threadId, releaseThread, configFingerprint, serviceTier, ephemeralPolicy) {
1788
+ const runtime = configuredClients.get(client);
1789
+ if (!runtime || runtime.closed) return false;
1790
+ const claimed = runtime.claimedThreads.get(threadId);
1791
+ if (claimed !== void 0 && (releaseThread === void 0 || claimedThreadReleaseTokens.get(releaseThread) !== claimed)) return false;
1792
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1793
+ if (pendingRelease) {
1794
+ await pendingRelease.completion;
1795
+ return false;
1796
+ }
1797
+ runtime.retainedThreads.delete(threadId);
1798
+ const retained = {
1799
+ configFingerprint,
1800
+ ephemeralPolicy,
1801
+ serviceTier,
1802
+ expiresAt: ephemeralPolicy === void 0 ? Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS : Number.POSITIVE_INFINITY,
1803
+ release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId, assertCurrent) => {
1804
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
1805
+ })
1806
+ };
1807
+ runtime.retainedThreads.set(threadId, retained);
1808
+ try {
1809
+ await evictExcessIdleThreads(client, runtime);
1810
+ } catch (error) {
1811
+ if (runtime.retainedThreads.get(threadId) === retained) runtime.retainedThreads.delete(threadId);
1812
+ scheduleRetainedThreadEviction(client, runtime);
1813
+ embeddedAgentLog$1.warn("codex retained thread capacity eviction failed", {
1814
+ threadId,
1815
+ reason: formatErrorMessage(error)
1816
+ });
1817
+ return false;
1818
+ }
1819
+ if (runtime.closed) return false;
1820
+ if (claimed !== void 0 && runtime.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1821
+ scheduleRetainedThreadEviction(client, runtime);
1822
+ return true;
1823
+ }
1824
+ /** Transfer one idle subscription to its next turn or compaction without touching sibling threads. */
1825
+ async function consumeCodexAppServerLiveThread(client, threadId, configFingerprint) {
1826
+ const runtime = configuredClients.get(client);
1827
+ if (!runtime || runtime.closed) return;
1828
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1829
+ if (pendingRelease) {
1830
+ await pendingRelease.completion;
1831
+ return;
1832
+ }
1833
+ const retained = runtime.retainedThreads.get(threadId);
1834
+ if (!retained || configFingerprint !== void 0 && retained.configFingerprint !== configFingerprint) return;
1835
+ return claimCodexAppServerThreadOwnership(client, runtime, threadId, retained);
1836
+ }
1837
+ /** Claims an observed Codex auto-subscription without exposing a temporarily idle owner. */
1838
+ async function claimCodexAppServerLiveThread(client, threadId) {
1839
+ const runtime = configuredClients.get(client);
1840
+ if (!runtime || runtime.closed || runtime.claimedThreads.has(threadId)) return;
1841
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1842
+ if (pendingRelease) {
1843
+ await pendingRelease.completion;
1844
+ return;
1845
+ }
1846
+ return claimCodexAppServerThreadOwnership(client, runtime, threadId, runtime.retainedThreads.get(threadId) ?? {
1847
+ expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
1848
+ release: async (releasedThreadId, assertCurrent) => {
1849
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
1850
+ }
1851
+ });
1852
+ }
1853
+ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained) {
1854
+ runtime.retainedThreads.delete(threadId);
1855
+ const claimed = Symbol(threadId);
1856
+ runtime.claimedThreads.set(threadId, claimed);
1857
+ scheduleRetainedThreadEviction(client, runtime);
1858
+ const assertCurrent = () => {
1859
+ if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) throw new Error(`Codex thread subscription ownership changed: ${threadId}`);
1860
+ };
1861
+ const release = async (releasedThreadId, assertReleaseCurrent) => {
1862
+ if (releasedThreadId !== threadId || runtime.claimedThreads.get(threadId) !== claimed) {
1863
+ assertReleaseCurrent?.();
1864
+ return;
1865
+ }
1866
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1867
+ if (pendingRelease) {
1868
+ await pendingRelease.completion;
1869
+ return;
1870
+ }
1871
+ const transition = { completion: Promise.resolve().then(async () => {
1872
+ if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) {
1873
+ assertReleaseCurrent?.();
1874
+ return;
1875
+ }
1876
+ assertReleaseCurrent?.();
1877
+ await (assertReleaseCurrent ? retained.release(releasedThreadId, assertReleaseCurrent) : retained.release(releasedThreadId));
1878
+ }) };
1879
+ runtime.releasingThreads.set(threadId, transition);
1880
+ try {
1881
+ await transition.completion;
1882
+ if (runtime.claimedThreads.get(threadId) === claimed) {
1883
+ runtime.claimedThreads.delete(threadId);
1884
+ runtime.workspaceReferences.delete(threadId);
1885
+ }
1886
+ } finally {
1887
+ if (runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1888
+ }
1889
+ };
1890
+ physicalThreadReleases.set(release, retained.release);
1891
+ claimedThreadReleaseTokens.set(release, claimed);
1892
+ return {
1893
+ assertCurrent,
1894
+ configFingerprint: retained.configFingerprint,
1895
+ ephemeralPolicy: retained.ephemeralPolicy,
1896
+ serviceTier: retained.serviceTier,
1897
+ release
1898
+ };
1899
+ }
1900
+ /** Distinguish active claimed ownership from an already-evicted idle subscription. */
1901
+ function hasCodexAppServerLiveThread(client, threadId) {
1902
+ const runtime = configuredClients.get(client);
1903
+ return runtime !== void 0 && !runtime.closed && (runtime.retainedThreads.has(threadId) || runtime.releasingThreads.has(threadId) || runtime.claimedThreads.has(threadId));
1904
+ }
1905
+ function isCodexAppServerLiveThreadClaimed(client, threadId) {
1906
+ const runtime = configuredClients.get(client);
1907
+ return runtime !== void 0 && !runtime.closed && runtime.claimedThreads.has(threadId);
1908
+ }
1909
+ /** Release the exact physical subscription and finish only its observed ownership generation. */
1910
+ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs, assertCurrent) {
1911
+ const runtime = configuredClients.get(client);
1912
+ const claimed = runtime?.claimedThreads.get(threadId);
1913
+ const retained = runtime?.retainedThreads.get(threadId);
1914
+ let transition = runtime?.releasingThreads.get(threadId);
1915
+ if (transition?.physicalRelease) {
1916
+ await transition.physicalRelease;
1917
+ assertCurrent?.();
1918
+ return;
1919
+ }
1920
+ const physicalRelease = Promise.resolve().then(async () => {
1921
+ if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed || retained !== void 0 && runtime?.retainedThreads.get(threadId) !== retained) {
1922
+ assertCurrent?.();
1923
+ return;
1924
+ }
1925
+ assertCurrent?.();
1926
+ await client.request("thread/unsubscribe", { threadId }, {
1927
+ timeoutMs,
1928
+ assertCurrent
1929
+ });
1930
+ assertCurrent?.();
1931
+ runtime?.workspaceReferences.delete(threadId);
1932
+ });
1933
+ const ownsTransition = runtime !== void 0 && transition === void 0;
1934
+ if (transition) transition.physicalRelease = physicalRelease;
1935
+ else if (runtime) {
1936
+ transition = {
1937
+ completion: physicalRelease,
1938
+ physicalRelease
1939
+ };
1940
+ runtime.releasingThreads.set(threadId, transition);
1941
+ }
1942
+ try {
1943
+ await physicalRelease;
1944
+ if (retained !== void 0 && runtime?.retainedThreads.get(threadId) === retained) {
1945
+ runtime.retainedThreads.delete(threadId);
1946
+ scheduleRetainedThreadEviction(client, runtime);
1947
+ }
1948
+ if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1949
+ } finally {
1950
+ if (ownsTransition && runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1951
+ }
1952
+ }
1953
+ /** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
1954
+ async function releaseCodexAppServerLiveThread(client, threadId, assertCurrent) {
1955
+ const runtime = configuredClients.get(client);
1956
+ return runtime ? await releaseRetainedThread(client, runtime, threadId, assertCurrent) : false;
1957
+ }
1958
+ /** Native child work pins its parent's subscription even after the foreground parent turn ends. */
1959
+ function protectCodexAppServerLiveThread(client, threadId) {
1960
+ const runtime = configuredClients.get(client);
1961
+ if (!runtime || runtime.closed) return () => void 0;
1962
+ runtime.protectedThreads.set(threadId, (runtime.protectedThreads.get(threadId) ?? 0) + 1);
1963
+ scheduleRetainedThreadEviction(client, runtime);
1964
+ let protectedThread = true;
1965
+ return () => {
1966
+ if (!protectedThread) return;
1967
+ protectedThread = false;
1968
+ if (runtime.closed) return;
1969
+ const count = runtime.protectedThreads.get(threadId) ?? 0;
1970
+ if (count <= 1) {
1971
+ runtime.protectedThreads.delete(threadId);
1972
+ const retained = runtime.retainedThreads.get(threadId);
1973
+ if (retained) {
1974
+ if (retained.ephemeralPolicy === void 0) retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
1975
+ runtime.retainedThreads.delete(threadId);
1976
+ runtime.retainedThreads.set(threadId, retained);
1977
+ }
1978
+ } else runtime.protectedThreads.set(threadId, count - 1);
1979
+ scheduleRetainedThreadEviction(client, runtime);
1980
+ evictExcessIdleThreads(client, runtime).catch((error) => {
1981
+ embeddedAgentLog$1.warn("codex retained thread unpin eviction failed", {
1982
+ threadId,
1983
+ reason: formatErrorMessage(error)
1984
+ });
1985
+ });
1986
+ };
1987
+ }
1988
+ //#endregion
1989
+ //#region extensions/codex/src/app-server/attempt-timeouts.ts
1990
+ /**
1991
+ * Timeout defaults and normalizers for Codex app-server startup and turn
1992
+ * liveness watches.
1993
+ */
1994
+ /** Minimum startup timeout accepted by the Codex app-server harness. */
1995
+ const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
1996
+ const TURN_TERMINAL_SETTLEMENT_TIMEOUT_MS = 12e4;
1997
+ const TURN_FINALIZE_DRAIN_ABORT_GRACE_MS = 5e3;
1998
+ var CodexAppServerStartupError = class extends Error {
1999
+ constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
2000
+ super(message);
2001
+ this.reason = reason;
2002
+ this.code = "CODEX_APP_SERVER_STARTUP_CANCELLED";
2003
+ this.name = "CodexAppServerStartupError";
2004
+ }
2005
+ };
2006
+ function isCodexAppServerStartupError(error, reason) {
2007
+ return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_STARTUP_CANCELLED" && "reason" in error && (error.reason === "aborted" || error.reason === "timed_out") && (reason === void 0 || error.reason === reason);
2008
+ }
2009
+ function resolvePositiveIntegerTimeoutMs(value, fallbackMs) {
2010
+ const fallback = resolveTimerTimeoutMs(fallbackMs, 1);
2011
+ return resolveTimerTimeoutMs(value, fallback);
2012
+ }
2013
+ /** Runs startup work with abort and timeout handling plus optional cleanup. */
2014
+ async function withCodexStartupTimeout(params) {
2015
+ if (params.signal.aborted) throw new CodexAppServerStartupError("aborted");
2016
+ let timeout;
2017
+ let abortCleanup;
2018
+ let timeoutError;
2019
+ let timeoutCleanup;
2020
+ try {
2021
+ return await Promise.race([params.operation(), new Promise((_, reject) => {
2022
+ const rejectOnce = (error) => {
2023
+ if (timeout) {
2024
+ clearTimeout(timeout);
2025
+ timeout = void 0;
2026
+ }
2027
+ reject(error);
2028
+ };
2029
+ timeout = setTimeout(() => {
2030
+ timeoutError = new CodexAppServerStartupError("timed_out");
2031
+ timeoutCleanup = Promise.resolve(params.onTimeout?.()).then(() => void 0, () => void 0);
2032
+ timeoutCleanup.finally(() => {
2033
+ rejectOnce(timeoutError);
2034
+ });
2035
+ }, params.timeoutMs);
2036
+ const abortListener = () => rejectOnce(new CodexAppServerStartupError("aborted"));
2037
+ params.signal.addEventListener("abort", abortListener, { once: true });
2038
+ abortCleanup = () => params.signal.removeEventListener("abort", abortListener);
2039
+ })]);
2040
+ } catch (error) {
2041
+ if (timeoutError) {
2042
+ await timeoutCleanup;
2043
+ throw timeoutError;
2044
+ }
2045
+ throw error;
2046
+ } finally {
2047
+ if (timeout) clearTimeout(timeout);
2048
+ abortCleanup?.();
2049
+ }
2050
+ }
2051
+ /** Resolves startup timeout while honoring the configured floor. */
2052
+ function resolveCodexStartupTimeoutMs(params) {
2053
+ const timeoutFloorMs = resolvePositiveIntegerTimeoutMs(params.timeoutFloorMs, CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS);
2054
+ const timeoutMs = resolvePositiveIntegerTimeoutMs(params.timeoutMs, timeoutFloorMs);
2055
+ return Math.max(timeoutFloorMs, timeoutMs);
2056
+ }
2057
+ /** Adds gateway grace time to a caller timeout without overflowing invalid values. */
2058
+ function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
2059
+ const timeout = resolvePositiveIntegerTimeoutMs(timeoutMs, 1);
2060
+ const grace = resolveTimerTimeoutMs(graceMs, 0, 0);
2061
+ return addTimerTimeoutGraceMs(timeout, grace) ?? timeout;
2062
+ }
2063
+ //#endregion
2064
+ //#region extensions/codex/src/app-server/inference-routing.ts
2065
+ const owners = defineCodexBuildState("openclaw.codexAppServerInferenceOwners", () => /* @__PURE__ */ new WeakMap())();
2066
+ const MAX_ROUTES = 8;
2067
+ const MAX_THREADS = 256;
2068
+ const NATIVE_OPENAI_UPSTREAMS = /* @__PURE__ */ new Set(["https://api.openai.com/v1", "https://chatgpt.com/backend-api/codex"]);
2069
+ /** Only managed native stdio startup calls this; locality or metadata cannot opt a client in. */
2070
+ function ownCodexInferenceClient(client) {
2071
+ if (owners.has(client)) return;
2072
+ const owner = {
2073
+ closed: false,
2074
+ routes: /* @__PURE__ */ new Map(),
2075
+ threads: /* @__PURE__ */ new Map(),
2076
+ handles: /* @__PURE__ */ new Set()
2077
+ };
2078
+ owners.set(client, owner);
2079
+ const close = () => {
2080
+ owner.closed = true;
2081
+ owner.threads.clear();
2082
+ owner.handles.clear();
2083
+ for (const pending of owner.routes.values()) pending.then((route) => route.close(), () => {});
2084
+ owner.routes.clear();
2085
+ };
2086
+ client.addCloseHandler(close);
2087
+ client.addNotificationHandler((notification) => {
2088
+ if (notification.method === "thread/closed" && isJsonObject(notification.params) && typeof notification.params.threadId === "string") owner.threads.delete(notification.params.threadId);
2089
+ if (notification.method !== "account/updated" || !owner.authRoute) return;
2090
+ const mode = isJsonObject(notification.params) ? notification.params.authMode : void 0;
2091
+ if ((mode === "apiKey" ? "apiKey" : mode === "chatgpt" || mode === "chatgptAuthTokens" ? "chatgpt" : void 0) !== owner.authRoute) close();
2092
+ });
2093
+ }
2094
+ async function prepareCodexInferenceRoute(params) {
2095
+ const owner = owners.get(params.client);
2096
+ if (!owner) return;
2097
+ const assertClient = () => {
2098
+ if (owner.closed || owners.get(params.client) !== owner) throw new Error("Codex inference route ownership changed; reconnect before retrying");
2099
+ };
2100
+ const assertCurrent = () => {
2101
+ assertClient();
2102
+ params.signal?.throwIfAborted();
2103
+ params.assertCurrent();
2104
+ };
2105
+ assertCurrent();
2106
+ const snapshot = params.effectiveConfig ?? await readCodexEffectiveConfig(params.client, params.cwd, { signal: params.signal });
2107
+ assertCurrent();
2108
+ if (snapshot.config.model_provider != null && snapshot.config.model_provider !== "openai") return;
2109
+ if (isJsonObject(snapshot.config.features) && snapshot.config.features.respect_system_proxy === true) return;
2110
+ const configured = snapshot.config.openai_base_url;
2111
+ if (configured != null && typeof configured !== "string") throw new Error("Codex inference upstream configuration is invalid");
2112
+ if (configured) {
2113
+ let target;
2114
+ try {
2115
+ target = new URL(configured);
2116
+ } catch {
2117
+ return;
2118
+ }
2119
+ if (!NATIVE_OPENAI_UPSTREAMS.has(target.href.replace(/\/$/, ""))) return;
2120
+ }
2121
+ const origin = snapshot.origins?.openai_base_url;
2122
+ if (origin && !CODEX_SESSION_OVERRIDABLE_LAYER_TYPES.has(origin.name.type)) return;
2123
+ const account = await params.client.request("account/read", { refreshToken: false }, {
2124
+ signal: params.signal,
2125
+ assertCurrent
2126
+ });
2127
+ assertCurrent();
2128
+ const type = isJsonObject(account.account) ? account.account.type : void 0;
2129
+ if (type !== "apiKey" && type !== "chatgpt") return;
2130
+ if (owner.authRoute && owner.authRoute !== type) throw new Error("Codex native account route changed; reconnect before retrying");
2131
+ owner.authRoute = type;
2132
+ const target = new URL(configured || (type === "apiKey" ? "https://api.openai.com/v1" : "https://chatgpt.com/backend-api/codex"));
2133
+ const key = target.toString();
2134
+ let pending = owner.routes.get(key);
2135
+ if (!pending) {
2136
+ if (owner.routes.size >= MAX_ROUTES) throw new Error("Codex inference route limit reached");
2137
+ pending = import("./inference-proxy-HGXKUIre.js").then(({ createCodexInferenceProxy }) => {
2138
+ assertClient();
2139
+ return createCodexInferenceProxy({
2140
+ upstream: target,
2141
+ assertCurrent: assertClient
2142
+ });
2143
+ });
2144
+ owner.routes.set(key, pending);
2145
+ }
2146
+ const route = await pending;
2147
+ assertCurrent();
2148
+ route.assertCurrent();
2149
+ params.client.protectPrivateTransportSecret(new URL(route.baseUrl).pathname.split("/")[1] ?? "");
2150
+ owner.handles.add(route);
2151
+ return route;
2152
+ }
2153
+ /** Prepare a managed thread without changing an attached or unsupported native profile. */
2154
+ async function prepareCodexInferenceThreadConfig(params) {
2155
+ const { binding } = params;
2156
+ if (binding?.connectionScope === "supervision" || binding?.preserveNativeModel === true) return;
2157
+ const route = await prepareCodexInferenceRoute(params);
2158
+ if (!route) return;
2159
+ if (binding?.clientId === params.clientId && !getCodexInferenceThread(params.client, binding.threadId)) {
2160
+ const { thread } = await params.client.request("thread/read", {
2161
+ threadId: binding.threadId,
2162
+ includeTurns: false
2163
+ }, {
2164
+ signal: params.signal,
2165
+ assertCurrent: params.assertCurrent
2166
+ });
2167
+ params.assertCurrent();
2168
+ if (thread.id !== binding.threadId || thread.status?.type !== "notLoaded") throw new Error("Codex loaded thread has no owned inference route; reconnect before retrying");
2169
+ }
2170
+ if (params.config?.openai_base_url !== void 0 && params.config.openai_base_url !== route.upstream) throw new Error("Codex thread configuration conflicts with its trusted inference upstream");
2171
+ return {
2172
+ route,
2173
+ config: {
2174
+ ...params.config,
2175
+ openai_base_url: route.baseUrl
2176
+ }
2177
+ };
2178
+ }
2179
+ /** Validate the exact private handle and unchanged upstream, not a localhost string exception. */
2180
+ function assertCodexInferenceRouteConfig(client, route, config) {
2181
+ if (!route) return;
2182
+ const owner = owners.get(client);
2183
+ if (!owner || owner.closed || !owner.handles.has(route) || config?.openai_base_url !== route.baseUrl) throw new Error("Codex parent-local inference route was overridden; no turn was sent");
2184
+ route.assertCurrent();
2185
+ }
2186
+ function bindCodexInferenceThread(client, threadId, route) {
2187
+ const owner = owners.get(client);
2188
+ if (!owner && !route) return;
2189
+ if (!owner || owner.closed) throw new Error("Codex inference client is closed");
2190
+ if (!route) {
2191
+ owner.threads.delete(threadId);
2192
+ return;
2193
+ }
2194
+ route.assertCurrent();
2195
+ if (!owner.threads.has(threadId) && owner.threads.size >= MAX_THREADS) throw new Error("Codex inference thread limit reached; reconnect before retrying");
2196
+ owner.threads.set(threadId, route);
2197
+ }
2198
+ function getCodexInferenceThread(client, threadId) {
2199
+ const owner = owners.get(client);
2200
+ if (owner?.closed) throw new Error("Codex inference client is closed");
2201
+ return owner?.threads.get(threadId);
2202
+ }
2203
+ //#endregion
2204
+ //#region extensions/codex/src/app-server/native-config-fence.ts
2205
+ const CODEX_NATIVE_CONFIG_FENCE_STATE = Symbol.for("openclaw.codexNativeConfigFenceState");
2206
+ function getFenceState() {
2207
+ const globalState = globalThis;
2208
+ globalState[CODEX_NATIVE_CONFIG_FENCE_STATE] ??= /* @__PURE__ */ new Map();
2209
+ return globalState[CODEX_NATIVE_CONFIG_FENCE_STATE];
2210
+ }
2211
+ /** Acquires the per-CODEX_HOME fence and returns an idempotent release. */
2212
+ async function acquireCodexNativeConfigFence(key, options = {}) {
2213
+ const state = getFenceState();
2214
+ const previous = state.get(key) ?? Promise.resolve();
2215
+ let resolveCurrent = () => void 0;
2216
+ const current = new Promise((resolve) => {
2217
+ resolveCurrent = resolve;
2218
+ });
2219
+ state.set(key, current);
2220
+ try {
2221
+ await waitForPreviousFence(previous, options);
2222
+ } catch (error) {
2223
+ previous.then(() => {
2224
+ resolveCurrent();
2225
+ if (state.get(key) === current) state.delete(key);
2226
+ });
2227
+ throw error;
2228
+ }
2229
+ let released = false;
2230
+ return () => {
2231
+ if (released) return;
2232
+ released = true;
2233
+ resolveCurrent();
2234
+ if (state.get(key) === current) state.delete(key);
2235
+ };
2236
+ }
2237
+ async function waitForPreviousFence(previous, options) {
2238
+ if (options.signal?.aborted) throw new Error(options.abortMessage ?? "Codex native config fence aborted");
2239
+ if (options.timeoutMs === void 0 && !options.signal) {
2240
+ await previous;
2241
+ return;
2242
+ }
2243
+ await new Promise((resolve, reject) => {
2244
+ let timeout;
2245
+ const cleanup = () => {
2246
+ if (timeout) {
2247
+ clearTimeout(timeout);
2248
+ timeout = void 0;
2249
+ }
2250
+ options.signal?.removeEventListener("abort", onAbort);
2251
+ };
2252
+ const settle = (run) => {
2253
+ cleanup();
2254
+ run();
2255
+ };
2256
+ const onAbort = () => settle(() => reject(new Error(options.abortMessage ?? "Codex native config fence aborted")));
2257
+ previous.then(() => settle(resolve));
2258
+ if (options.signal) options.signal.addEventListener("abort", onAbort, { once: true });
2259
+ if (options.timeoutMs !== void 0) {
2260
+ timeout = setTimeout(() => settle(() => reject(new Error(options.timeoutMessage ?? "Codex native config fence timed out"))), Math.max(1, options.timeoutMs));
2261
+ timeout.unref?.();
2262
+ }
2263
+ });
2264
+ }
2265
+ //#endregion
2266
+ //#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
2267
+ var CodexThreadStartRequestError = class extends Error {
2268
+ constructor(cause) {
2269
+ super(`thread/start: ${formatErrorMessage(cause)}`, { cause });
2270
+ this.name = "CodexThreadStartRequestError";
2271
+ }
2272
+ };
2273
+ var CodexThreadBindingConflictError = class extends Error {
2274
+ constructor(threadId, operation) {
2275
+ super(`Codex thread binding changed while ${operation}: ${threadId}`);
2276
+ this.name = "CodexThreadBindingConflictError";
2277
+ }
2278
+ };
2279
+ var CodexAdoptedThreadActiveError = class extends AgentHarnessPreflightError$1 {
2280
+ constructor(message = "Codex session became active in another runner; wait for it to finish before continuing") {
2281
+ super(message);
2282
+ this.name = "CodexAdoptedThreadActiveError";
2283
+ }
2284
+ };
2285
+ //#endregion
2286
+ //#region extensions/codex/src/app-server/shared-client.ts
2287
+ /**
2288
+ * Owns shared and isolated Codex app-server client startup, auth application,
2289
+ * lease tracking, and teardown.
2290
+ */
2291
+ var shared_client_exports = /* @__PURE__ */ __exportAll({
2292
+ assertCodexAppServerClientStartSelectionCurrent: () => assertCodexAppServerClientStartSelectionCurrent,
2293
+ captureCodexAppServerClientLifetime: () => captureCodexAppServerClientLifetime,
2294
+ captureSharedCodexAppServerCatalogLifetime: () => captureSharedCodexAppServerCatalogLifetime,
2295
+ clearSharedCodexAppServerClientAndWait: () => clearSharedCodexAppServerClientAndWait,
2296
+ clearSharedCodexAppServerClientIfCurrent: () => clearSharedCodexAppServerClientIfCurrent,
2297
+ clearSharedCodexAppServerClientIfCurrentAndUnclaimed: () => clearSharedCodexAppServerClientIfCurrentAndUnclaimed,
2298
+ clearSharedCodexAppServerClientIfCurrentAndWait: () => clearSharedCodexAppServerClientIfCurrentAndWait,
2299
+ createIsolatedCodexAppServerClient: () => createIsolatedCodexAppServerClient,
2300
+ getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
2301
+ getSharedCodexAppServerClient: () => getSharedCodexAppServerClient,
2302
+ isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
2303
+ readCodexAppServerClientDesktopGeneration: () => readCodexAppServerClientDesktopGeneration,
2304
+ readCodexAppServerClientDesktopGenerationFingerprint: () => readCodexAppServerClientDesktopGenerationFingerprint,
2305
+ readCodexAppServerClientProcessIdentity: () => readCodexAppServerClientProcessIdentity,
2306
+ releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
2307
+ releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
2308
+ resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
2309
+ resolveCodexNativeConfigFenceKey: () => resolveCodexNativeConfigFenceKey,
2310
+ retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
2311
+ retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
2312
+ retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
2313
+ waitForCodexAppServerClientDesktopGenerationDrain: () => waitForCodexAppServerClientDesktopGenerationDrain,
2314
+ withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
2315
+ });
2316
+ const SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER = codexBuildSymbol("openclaw.codexAppServerClientDisposer");
2317
+ const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
2318
+ function ownCodexStartup(lifetime, operation) {
2319
+ lifetime.pending.add(operation);
2320
+ const release = () => lifetime.pending.delete(operation);
2321
+ operation.then(release, release);
2322
+ return operation;
2323
+ }
2324
+ async function prepareCodexAppServerClient(options) {
2325
+ const lifetime = getSharedCodexAppServerClientState().startup;
2326
+ const abandonSignal = options?.abandonSignal ? AbortSignal.any([lifetime.controller.signal, options.abandonSignal]) : lifetime.controller.signal;
2327
+ const assertCurrent = () => {
2328
+ if (abandonSignal.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
2329
+ };
2330
+ assertCurrent();
2331
+ const startedAt = Date.now();
2332
+ return {
2333
+ context: await withCodexAppServerAcquireDeadline(options?.timeoutMs ?? 0, ownCodexStartup(lifetime, resolveCodexAppServerClientStartContext(options)), abandonSignal),
2334
+ lifetime,
2335
+ abandonSignal,
2336
+ startedAt,
2337
+ assertCurrent
2338
+ };
2339
+ }
2340
+ /** Reads the exact successful spawn selection plus its initialized runtime identity. */
2341
+ function readCodexAppServerClientProcessIdentity(client) {
2342
+ const metadata = getSharedCodexAppServerClientState().startMetadata.get(client);
2343
+ if (!metadata) return;
2344
+ const runtimeIdentity = client.getRuntimeIdentity();
2345
+ return {
2346
+ clientId: client.getInstanceId(),
2347
+ ...resolveCodexAppServerSpawnIdentity(metadata.startOptions, metadata.nativeCommand),
2348
+ ...runtimeIdentity?.serverVersion ? { serverVersion: runtimeIdentity.serverVersion } : {},
2349
+ ...runtimeIdentity?.userAgent ? { userAgent: runtimeIdentity.userAgent } : {}
2350
+ };
2351
+ }
2352
+ /** Returns the lifecycle fingerprint that owns a managed desktop client. */
2353
+ function readCodexAppServerClientDesktopGenerationFingerprint(client) {
2354
+ return readCodexAppServerClientDesktopGeneration(client)?.fingerprint;
2355
+ }
2356
+ /** Returns the lifecycle generation that owns a managed desktop client. */
2357
+ function readCodexAppServerClientDesktopGeneration(client) {
2358
+ return getSharedCodexAppServerClientState().startMetadata.get(client)?.desktopGeneration;
2359
+ }
2360
+ /** Waits until older physical desktop clients for this client's Codex home exit. */
2361
+ async function waitForCodexAppServerClientDesktopGenerationDrain(params) {
2362
+ const metadata = getSharedCodexAppServerClientState().startMetadata.get(params.client);
2363
+ if (!metadata?.desktopGeneration) return;
2364
+ const drain = createOlderDesktopGenerationDrainWait({
2365
+ generation: metadata.desktopGeneration,
2366
+ startOptions: metadata.startOptions,
2367
+ agentDir: metadata.agentDir
2368
+ });
2369
+ try {
2370
+ await withCodexAppServerAcquireDeadline(params.timeoutMs ?? 0, drain.promise, params.signal, "Codex Computer Use install timed out waiting for older desktop clients");
2371
+ } finally {
2372
+ drain.cancel();
2373
+ }
2374
+ }
2375
+ /** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
2376
+ function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
2377
+ const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
2378
+ return {
2379
+ command: startOptions.command,
2380
+ argsFingerprint: createHash("sha256").update(JSON.stringify(startOptions.args)).digest("hex"),
2381
+ ...startOptions.commandSource ? { commandSource: startOptions.commandSource } : {},
2382
+ ...startOptions.managedCommandOrder ? { managedCommandOrder: startOptions.managedCommandOrder } : {},
2383
+ ...nativeCommand ? { nativeCommand } : {}
2384
+ };
2385
+ }
2386
+ var CodexAppServerStartSelectionChangedError = class extends Error {
2387
+ constructor() {
2388
+ super("Codex app-server managed executable selection changed during startup");
2389
+ this.code = "CODEX_APP_SERVER_START_SELECTION_CHANGED";
2390
+ this.name = "CodexAppServerStartSelectionChangedError";
2391
+ }
2392
+ };
2393
+ /** Cross-bundle-safe check for a managed executable selection retry. */
2394
+ function isCodexAppServerStartSelectionChangedError(error) {
2395
+ return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_START_SELECTION_CHANGED";
2396
+ }
2397
+ /**
2398
+ * Rechecks mutable Codex-owned plugin state immediately before thread start/resume.
2399
+ * The synchronous check prevents another gateway task from installing Computer
2400
+ * Use between the check and the JSON-RPC write on the same event loop turn.
2401
+ */
2402
+ function assertCodexAppServerClientStartSelectionCurrent(params) {
2403
+ const metadata = getSharedCodexAppServerClientState().startMetadata.get(params.client);
2404
+ if (!metadata) return;
2405
+ if (metadata.desktopGeneration && !isCodexDesktopGenerationCurrent(metadata.desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
2406
+ const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
2407
+ if (requestedStartOptions.commandSource !== "managed") return;
2408
+ const current = resolveCodexAppServerStartOptionsForAgent({
2409
+ startOptions: requestedStartOptions,
2410
+ agentDir: params.agentDir ?? metadata.agentDir
2411
+ });
2412
+ if ((metadata.startOptions.managedCommandOrder ?? "package-first") !== (current.managedCommandOrder ?? "package-first")) throw new CodexAppServerStartSelectionChangedError();
2413
+ }
2414
+ /** Resolves the per-CODEX_HOME key used to serialize native config loading. */
2415
+ function resolveCodexNativeConfigFenceKey(params) {
2416
+ const metadata = params.client ? getSharedCodexAppServerClientState().startMetadata.get(params.client) : void 0;
2417
+ const startOptions = metadata?.startOptions ?? params.startOptions;
2418
+ if (!startOptions || startOptions.transport !== "stdio") return;
2419
+ const configuredHome = startOptions.env?.CODEX_HOME?.trim();
2420
+ const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
2421
+ const codexHome = configuredHome ? configuredHome : startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir() : agentDir ? resolveCodexAppServerHomeDir(agentDir) : void 0;
2422
+ return codexHome ? `codex-home:${path.resolve(codexHome)}` : void 0;
2423
+ }
2424
+ function inferAuthRequirement(preparedAuth) {
2425
+ if (preparedAuth?.kind === "api-key") return "api-key";
2426
+ return preparedAuth?.kind === "profile" ? "subscription" : void 0;
2427
+ }
2428
+ async function resolveCodexAppServerClientStartContext(options) {
2429
+ const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
2430
+ const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
2431
+ const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions, options?.pluginConfig) ? await waitForCodexDesktopGeneration() : void 0;
2432
+ const preparedAuth = options?.preparedAuth;
2433
+ const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
2434
+ if (preparedAuth && options?.authProfileId !== void 0) throw new Error("Prepared Codex auth cannot also select a legacy auth profile.");
2435
+ if (preparedAuth?.kind === "profile" && !preparedAuth.store.profiles[preparedAuth.profileId]) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
2436
+ if (preparedAuth?.kind === "api-key" && !preparedApiKey) throw new Error("Prepared Codex API-key auth is missing its resolved key.");
2437
+ if (preparedAuth && requestedStartOptions.homeScope === "user") throw new Error("Prepared Codex auth requires an isolated app-server home.");
2438
+ const preparedAuthRequirement = inferAuthRequirement(preparedAuth);
2439
+ if (options?.authRequirement && preparedAuthRequirement && options.authRequirement !== preparedAuthRequirement) throw new Error("Prepared Codex auth does not satisfy the requested auth requirement.");
2440
+ const authRequirement = options?.authRequirement ?? preparedAuthRequirement;
2441
+ const usesNativeAuth = !preparedAuth && (options?.authProfileId === null || requestedStartOptions.homeScope === "user");
2442
+ const requestedAuthProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : options?.authProfileId ?? void 0;
2443
+ const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && preparedAuth?.kind !== "api-key" ? resolveCodexAppServerAuthProfileStore({
2444
+ agentDir,
2445
+ authProfileId: requestedAuthProfileId,
2446
+ authProfileStore: options?.authProfileStore,
2447
+ config: options?.config
2448
+ }) : options?.authProfileStore;
2449
+ const authProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : usesNativeAuth || preparedAuth?.kind === "api-key" ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
2450
+ authProfileId: requestedAuthProfileId,
2451
+ agentDir,
2452
+ config: options?.config,
2453
+ ...authProfileStore ? { authProfileStore } : {}
2454
+ });
2455
+ const preparedAuthProfileSnapshot = !usesNativeAuth && authProfileId ? (preparedAuth?.kind === "profile" ? preparedAuth.snapshot : void 0) ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
2456
+ authProfileId,
2457
+ authProfileStore,
2458
+ agentDir,
2459
+ config: options?.config
2460
+ }) : void 0;
2461
+ if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable. Repair or replace the selected OpenAI profile, then retry.`);
2462
+ const resolvedPreparedAuth = preparedAuth?.kind === "api-key" ? {
2463
+ kind: "api-key",
2464
+ apiKey: preparedApiKey
2465
+ } : preparedAuthProfileSnapshot && authProfileId && authProfileStore ? {
2466
+ kind: "profile",
2467
+ profileId: authProfileId,
2468
+ store: authProfileStore,
2469
+ snapshot: preparedAuthProfileSnapshot
2470
+ } : void 0;
2471
+ const agentStartOptions = resolveCodexAppServerStartOptionsForAgent({
2472
+ startOptions: requestedStartOptions,
2473
+ agentDir
2474
+ });
2475
+ return {
2476
+ agentDir,
2477
+ usesNativeAuth,
2478
+ authProfileId,
2479
+ authProfileStore,
2480
+ requestedStartOptions,
2481
+ preparedAuth: resolvedPreparedAuth,
2482
+ authRequirement,
2483
+ startOptions: await bridgeCodexAppServerStartOptions({
2484
+ startOptions: await resolveManagedCodexAppServerStartOptions(agentStartOptions),
2485
+ agentId: options?.agentId,
2486
+ agentDir,
2487
+ authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
2488
+ ...preparedAuth && resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
2489
+ authRequirement,
2490
+ config: options?.config,
2491
+ pluginConfig: options?.pluginConfig,
2492
+ ...authProfileStore ? { authProfileStore } : {}
2493
+ }),
2494
+ ...options?.pluginConfig !== void 0 ? { pluginConfig: options.pluginConfig } : {},
2495
+ ...desktopGeneration ? { desktopGeneration } : {}
2496
+ };
2497
+ }
2498
+ function shouldTrackDesktopGeneration(startOptions, pluginConfig) {
2499
+ if (startOptions.transport !== "stdio") return false;
2500
+ if (resolveCodexComputerUseConfig({ pluginConfig }).enabled && (startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed")) return true;
2501
+ return startOptions.commandSource === "managed" && (startOptions.managedCommandOrder ?? "package-first") === "desktop-first";
2502
+ }
2503
+ /** Gets or starts a shared Codex app-server client without retaining a lease. */
2504
+ function getSharedCodexAppServerClient(options) {
2505
+ return acquireSharedCodexAppServerClient(options);
2506
+ }
2507
+ /** Gets or starts a shared Codex app-server client and records a release lease. */
2508
+ function getLeasedSharedCodexAppServerClient(options) {
2509
+ return acquireSharedCodexAppServerClient(options, true);
2510
+ }
2511
+ /** Releases one outstanding lease for a shared Codex app-server client. */
2512
+ function releaseLeasedSharedCodexAppServerClient(client) {
2513
+ const entry = getSharedCodexAppServerClientState().entriesByClient.get(client);
2514
+ if (!entry || entry.anonymousLeases === 0) return false;
2515
+ entry.anonymousLeases -= 1;
2516
+ releaseSharedClientEntry(entry, "activeLeases");
2517
+ return true;
2518
+ }
2519
+ /** Releases the currently owned client exactly once. */
2520
+ function releaseCodexAppServerClientLease(lease) {
2521
+ const client = lease.client;
2522
+ lease.client = void 0;
2523
+ return client ? releaseLeasedSharedCodexAppServerClient(client) : false;
2524
+ }
2525
+ /** Retries one config-loading operation with a shared deadline and current client lease. */
2526
+ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
2527
+ let client = params.lease.client;
2528
+ if (!client) throw new Error("Codex app-server selection retry requires an active client lease");
2529
+ const timeoutMs = params.options?.timeoutMs ?? 6e4;
2530
+ const deadline = Date.now() + timeoutMs;
2531
+ const signal = params.signal ?? params.options?.abandonSignal;
2532
+ const requestOptions = () => {
2533
+ if (signal?.aborted) throw new CodexAppServerStartupError("aborted", "Codex app-server selection retry aborted");
2534
+ const remainingTimeoutMs = deadline - Date.now();
2535
+ if (remainingTimeoutMs <= 0) throw new CodexAppServerStartupError("timed_out", "Codex app-server selection retry timed out");
2536
+ return {
2537
+ timeoutMs: remainingTimeoutMs,
2538
+ ...signal ? { signal } : {}
2539
+ };
2540
+ };
2541
+ for (let attempt = 0; attempt < 2; attempt += 1) {
2542
+ const attemptClient = client;
2543
+ let scopeActive = true;
2544
+ const assertCurrent = () => {
2545
+ if (!scopeActive || params.lease.client !== attemptClient) throw new CodexAppServerStartupError("aborted", "Codex app-server request scope is closed");
2546
+ };
2547
+ try {
2548
+ requestOptions();
2549
+ return await params.run(client, () => {
2550
+ assertCurrent();
2551
+ return {
2552
+ ...requestOptions(),
2553
+ assertCurrent
2554
+ };
2555
+ });
2556
+ } catch (error) {
2557
+ if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
2558
+ retireSharedCodexAppServerClientIfCurrent(client);
2559
+ params.lease.client = void 0;
2560
+ if (!releaseLeasedSharedCodexAppServerClient(client)) {
2561
+ client.close();
2562
+ throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
2563
+ }
2564
+ const replacementOptions = requestOptions();
2565
+ client = await getLeasedSharedCodexAppServerClient({
2566
+ ...params.options,
2567
+ timeoutMs: replacementOptions.timeoutMs,
2568
+ ...signal ? { abandonSignal: signal } : {}
2569
+ });
2570
+ params.lease.client = client;
2571
+ params.onClientChange?.(client);
2572
+ } finally {
2573
+ scopeActive = false;
2574
+ }
2575
+ }
2576
+ throw new Error("Codex app-server selection retry loop exited unexpectedly");
2577
+ }
2578
+ async function acquireSharedCodexAppServerClient(options, leased = false) {
2579
+ const timeoutMs = options?.timeoutMs ?? 0;
2580
+ const state = getSharedCodexAppServerClientState();
2581
+ const { context, lifetime, abandonSignal, startedAt, assertCurrent } = await prepareCodexAppServerClient(options);
2582
+ assertCurrent();
2583
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = context;
2584
+ const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, startedAt);
2585
+ const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authRequirement === "api-key" && !authProfileId ? resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }) : void 0);
2586
+ const baseKey = `${codexAppServerStartOptionsKey(startOptions, {
2587
+ authProfileId,
2588
+ authBindingFingerprint: options?.authBindingFingerprint,
2589
+ agentDir: usesNativeAuth ? void 0 : agentDir,
2590
+ fallbackApiKeyCacheKey: authIdentityCacheKey
2591
+ })}\0auth-requirement:${authRequirement ?? "native"}${desktopGeneration ? `\0desktop-generation:${desktopGeneration.epoch}` : ""}`;
2592
+ const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
2593
+ const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
2594
+ const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
2595
+ let entry = getOrCreateSharedClientEntry(state, key);
2596
+ const existingClient = entry.client;
2597
+ const existingGeneration = existingClient ? state.startMetadata.get(existingClient)?.desktopGeneration : void 0;
2598
+ if (existingClient && existingGeneration && !isCodexDesktopGenerationCurrent(existingGeneration)) {
2599
+ retireSharedCodexAppServerClientIfCurrent(existingClient);
2600
+ entry = getOrCreateSharedClientEntry(state, key);
2601
+ }
2602
+ entry.startupAbort ??= new AbortController();
2603
+ entry.closeWhenIdle = false;
2604
+ const releasePendingAcquire = retainSharedClientEntry(entry, "pendingAcquires");
2605
+ const startedCallback = options?.onStartedClient;
2606
+ if (startedCallback) {
2607
+ entry.onStartedClientCallbacks.add(startedCallback);
2608
+ if (entry.client) startedCallback(entry.client);
2609
+ }
2610
+ const stopStartedClientNotifications = () => {
2611
+ if (startedCallback) entry.onStartedClientCallbacks.delete(startedCallback);
2612
+ };
2613
+ let cleanupAbandonSignal;
2614
+ if (options?.abandonSignal) {
2615
+ const abandon = () => {
2616
+ stopStartedClientNotifications();
2617
+ releasePendingAcquire();
2618
+ retirePendingSharedClientEntryIfUnclaimed(entry);
2619
+ };
2620
+ options.abandonSignal.addEventListener("abort", abandon, { once: true });
2621
+ cleanupAbandonSignal = () => options.abandonSignal?.removeEventListener("abort", abandon);
2622
+ if (options.abandonSignal.aborted) abandon();
2623
+ }
2624
+ const startup = entry.startup ?? (entry.startup = createSharedCodexAppServerClientStartup({
2625
+ lifetime,
2626
+ entry,
2627
+ requestedStartOptions,
2628
+ startOptions,
2629
+ desktopGeneration,
2630
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
2631
+ agentDir,
2632
+ authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
2633
+ authProfileStore,
2634
+ preparedAuth,
2635
+ authRequirement,
2636
+ runtimeArtifactMode,
2637
+ ...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
2638
+ abandonSignal: entry.startupAbort.signal,
2639
+ config: options?.config
2640
+ }));
2641
+ try {
2642
+ await withCodexAppServerAcquireDeadline(remainingTimeoutMs, startup.initialized, abandonSignal, CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE, () => buildCodexAppServerInitializeTimeoutError(entry.client));
2643
+ const client = await withCodexAppServerAcquireDeadline(timeoutMs, startup.ready, abandonSignal, "codex app-server authentication timed out");
2644
+ if (entry.closeError) throw entry.closeError;
2645
+ ensureCodexAppServerClientRuntime(client, {
2646
+ agentDir,
2647
+ authProfileId: usesNativeAuth ? void 0 : authProfileId,
2648
+ ...authProfileStore ? { authProfileStore } : {},
2649
+ authMode: preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
2650
+ config: options?.config
2651
+ });
2652
+ if (leased) {
2653
+ entry.anonymousLeases += 1;
2654
+ entry.activeLeases += 1;
2655
+ }
2656
+ return client;
2657
+ } catch (error) {
2658
+ releasePendingAcquire();
2659
+ retirePendingSharedClientEntryIfUnclaimed(entry);
2660
+ throw error;
2661
+ } finally {
2662
+ cleanupAbandonSignal?.();
2663
+ stopStartedClientNotifications();
2664
+ releasePendingAcquire();
2665
+ }
2666
+ }
2667
+ async function withCodexAppServerAcquireDeadline(timeoutMs, promise, signal, timeoutMessage = CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE, timeoutErrorFactory) {
2668
+ if (signal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
2669
+ const timed = withTimeout(promise, timeoutMs, timeoutMessage, () => timeoutErrorFactory?.() ?? new CodexAppServerStartupError("timed_out", timeoutMessage));
2670
+ if (!signal) return await timed;
2671
+ return await new Promise((resolve, reject) => {
2672
+ const onAbort = () => reject(new CodexAppServerStartupError("aborted", "codex app-server initialize aborted"));
2673
+ signal.addEventListener("abort", onAbort, { once: true });
2674
+ timed.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
2675
+ });
2676
+ }
2677
+ function buildCodexAppServerInitializeTimeoutError(client) {
2678
+ const stderr = client?.getStderrDiagnostic();
2679
+ return new CodexAppServerStartupError("timed_out", stderr ? `${CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE}; stderr=${JSON.stringify(stderr)}` : CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE);
2680
+ }
2681
+ function resolveRemainingAcquireTimeout(timeoutMs, startedAt) {
2682
+ if (!(timeoutMs > 0)) return timeoutMs;
2683
+ const remaining = timeoutMs - (Date.now() - startedAt);
2684
+ if (remaining <= 0) throw new CodexAppServerStartupError("timed_out", "codex app-server initialize timed out");
2685
+ return remaining;
2686
+ }
2687
+ function createSharedCodexAppServerClientStartup(params) {
2688
+ const initialized = createDeferred();
2689
+ const ready = ownCodexStartup(params.lifetime, startInitializedCodexAppServerClient({
2690
+ ...params,
2691
+ onStartedClient: (startedClient) => {
2692
+ const state = getSharedCodexAppServerClientState();
2693
+ if (params.entry.client) state.entriesByClient.delete(params.entry.client);
2694
+ params.entry.client = startedClient;
2695
+ state.entriesByClient.set(startedClient, params.entry);
2696
+ state.liveClients.add(startedClient);
2697
+ startedClient.addTransportExitHandler((exitedClient) => {
2698
+ state.liveClients.delete(exitedClient);
2699
+ notifyDesktopGenerationDrainChecks(state);
2700
+ });
2701
+ for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
2702
+ retirePendingSharedClientEntryIfUnclaimed(params.entry);
2703
+ },
2704
+ onInitializedClient: () => initialized.resolve()
2705
+ }).then((client) => {
2706
+ const state = getSharedCodexAppServerClientState();
2707
+ params.entry.client = client;
2708
+ client.addCloseHandler((closedClient) => {
2709
+ const entry = getCurrentSharedClientEntry(closedClient);
2710
+ if (entry) state.clients.delete(entry.key);
2711
+ });
2712
+ return client;
2713
+ }, (error) => {
2714
+ initialized.reject(error);
2715
+ throw error;
2716
+ }));
2717
+ initialized.promise.catch(() => void 0);
2718
+ ready.catch(() => void 0);
2719
+ return {
2720
+ initialized: initialized.promise,
2721
+ ready
2722
+ };
2723
+ }
2724
+ /** Starts a caller-owned client; its assertion never binds ordinary shared-client leases. */
2725
+ async function createIsolatedCodexAppServerClient(options) {
2726
+ options?.assertCurrent?.();
2727
+ const { context, lifetime, abandonSignal, startedAt, assertCurrent } = await prepareCodexAppServerClient(options);
2728
+ assertCurrent();
2729
+ const timeoutMs = options?.timeoutMs ?? 0;
2730
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = context;
2731
+ return await ownCodexStartup(lifetime, startInitializedCodexAppServerClient({
2732
+ lifetime,
2733
+ requestedStartOptions,
2734
+ startOptions,
2735
+ ...desktopGeneration ? { desktopGeneration } : {},
2736
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
2737
+ agentDir,
2738
+ authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
2739
+ authProfileStore,
2740
+ preparedAuth,
2741
+ authRequirement,
2742
+ runtimeArtifactMode: options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0),
2743
+ ...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
2744
+ config: options?.config,
2745
+ timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, startedAt),
2746
+ abandonSignal,
2747
+ assertCurrent: options?.assertCurrent,
2748
+ onStartedClient: (client) => {
2749
+ trackIsolatedCodexAppServerClient(client);
2750
+ options?.onStartedClient?.(client);
2751
+ }
2752
+ }));
2753
+ }
2754
+ function trackIsolatedCodexAppServerClient(client) {
2755
+ const state = getSharedCodexAppServerClientState();
2756
+ state.isolatedClients.add(client);
2757
+ client.addTransportExitHandler((exitedClient) => {
2758
+ state.isolatedClients.delete(exitedClient);
2759
+ notifyDesktopGenerationDrainChecks(state);
2760
+ });
2761
+ }
2762
+ async function startInitializedCodexAppServerClient(params) {
2763
+ const acquireStartedAt = Date.now();
2764
+ const timeoutMs = params.timeoutMs ?? 0;
2765
+ const abandonSignal = params.abandonSignal ? AbortSignal.any([params.lifetime.controller.signal, params.abandonSignal]) : params.lifetime.controller.signal;
2766
+ const waitForStartup = (operation, timeoutMessage = CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE, timeoutErrorFactory) => {
2767
+ if (abandonSignal.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
2768
+ return withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), ownCodexStartup(params.lifetime, operation()), abandonSignal, timeoutMessage, timeoutErrorFactory);
2769
+ };
2770
+ const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
2771
+ for (const [index, startOptions] of startOptionsCandidates.entries()) {
2772
+ params.assertCurrent?.();
2773
+ const desktopGeneration = params.desktopGeneration ?? (isManagedCodexDesktopCommand(startOptions.command) ? await waitForStartup(waitForCodexDesktopGeneration) : void 0);
2774
+ const assertStartupCurrent = () => {
2775
+ params.assertCurrent?.();
2776
+ if (abandonSignal.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
2777
+ if (desktopGeneration && !isCodexDesktopGenerationCurrent(desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
2778
+ };
2779
+ const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
2780
+ const ownsIsolatedCodexHome = params.requestedStartOptions.homeScope !== "user" && !params.requestedStartOptions.env?.CODEX_HOME?.trim();
2781
+ const artifactDrain = desktopGeneration && ownsIsolatedCodexHome && computerUseConfig.enabled && (computerUseConfig.autoInstall || computerUseConfig.pluginCacheMode === "shared") ? createOlderDesktopGenerationDrainWait({
2782
+ generation: desktopGeneration,
2783
+ startOptions,
2784
+ agentDir: params.agentDir
2785
+ }) : void 0;
2786
+ try {
2787
+ if (artifactDrain) await waitForStartup(() => artifactDrain.promise);
2788
+ await reconcileCodexComputerUseStartArtifacts({
2789
+ startOptions,
2790
+ agentDir: params.agentDir,
2791
+ pluginConfig: params.pluginConfig,
2792
+ ...desktopGeneration ? { desktopGeneration } : {},
2793
+ assertCurrent: assertStartupCurrent,
2794
+ ownsIsolatedCodexHome
2795
+ });
2796
+ } catch (error) {
2797
+ if (isCodexComputerUseCandidateArtifactsUnavailableError(error)) {
2798
+ if (index + 1 < startOptionsCandidates.length) continue;
2799
+ throw new AgentHarnessPreflightError("Codex Computer Use artifacts are unavailable from the installed desktop apps.", {
2800
+ cause: error,
2801
+ scope: "harness"
2802
+ });
2803
+ }
2804
+ throw error;
2805
+ } finally {
2806
+ artifactDrain?.cancel();
2807
+ }
2808
+ const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BJ0rXBZ_.js") : void 0;
2809
+ const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
2810
+ startOptions,
2811
+ spawnIdentity: resolveCodexAppServerSpawnIdentity(startOptions),
2812
+ signal: abandonSignal
2813
+ }) : void 0;
2814
+ if (runtimeArtifactModule && runtimeArtifactBeforeStart && params.expectedRuntimeArtifact && !runtimeArtifactModule.validateCodexAppServerRuntimeArtifactCapture(params.expectedRuntimeArtifact, runtimeArtifactBeforeStart)) {
2815
+ if (index + 1 < startOptionsCandidates.length) continue;
2816
+ throw new Error("Codex app-server runtime artifact does not match verified inference");
2817
+ }
2818
+ assertStartupCurrent();
2819
+ let starting;
2820
+ let client;
2821
+ try {
2822
+ client = await waitForStartup(() => starting = CodexAppServerClient.start(startOptions, () => {
2823
+ assertStartupCurrent();
2824
+ resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
2825
+ }));
2826
+ } catch (error) {
2827
+ if (starting) ownCodexStartup(params.lifetime, starting.then((lateClient) => lateClient.closeAndWait(), () => {}));
2828
+ throw error;
2829
+ }
2830
+ let ready = false;
2831
+ try {
2832
+ const nativeCommandAtStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
2833
+ getSharedCodexAppServerClientState().startMetadata.set(client, {
2834
+ requestedStartOptions: params.requestedStartOptions,
2835
+ startOptions,
2836
+ agentDir: params.agentDir,
2837
+ ...nativeCommandAtStart ? { nativeCommand: nativeCommandAtStart } : {},
2838
+ ...desktopGeneration ? { desktopGeneration } : {}
2839
+ });
2840
+ assertStartupCurrent();
2841
+ params.onStartedClient?.(client);
2842
+ try {
2843
+ await waitForStartup(() => client.initialize(), CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE, () => buildCodexAppServerInitializeTimeoutError(client));
2844
+ } catch (error) {
2845
+ if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
2846
+ throw error;
2847
+ }
2848
+ assertStartupCurrent();
2849
+ params.onInitializedClient?.();
2850
+ let runtimeArtifact;
2851
+ if (runtimeArtifactModule && runtimeArtifactBeforeStart) {
2852
+ runtimeArtifact = await runtimeArtifactModule.finalizeCodexAppServerRuntimeArtifact({
2853
+ before: runtimeArtifactBeforeStart,
2854
+ startOptions,
2855
+ spawnIdentity: resolveCodexAppServerSpawnIdentity(startOptions),
2856
+ runtimeIdentity: client.getRuntimeIdentity(),
2857
+ signal: abandonSignal
2858
+ });
2859
+ if (params.expectedRuntimeArtifact && (runtimeArtifact.id !== params.expectedRuntimeArtifact.id || runtimeArtifact.fingerprint !== params.expectedRuntimeArtifact.fingerprint)) throw new Error("Codex app-server runtime artifact does not match verified inference");
2860
+ }
2861
+ ensureCodexAppServerClientRuntime(client, {
2862
+ agentDir: params.agentDir,
2863
+ authProfileId: params.authProfileId ?? void 0,
2864
+ authMode: params.preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
2865
+ ...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {},
2866
+ config: params.config,
2867
+ onAuthRefreshFailure: () => retireSharedCodexAppServerClientIfCurrent(client)
2868
+ });
2869
+ assertStartupCurrent();
2870
+ const authHandoff = await waitForStartup(() => applyCodexAppServerAuthProfile({
2871
+ client,
2872
+ agentDir: params.agentDir,
2873
+ authProfileId: params.authProfileId,
2874
+ preparedAuth: params.preparedAuth,
2875
+ authRequirement: params.authRequirement,
2876
+ startOptions,
2877
+ config: params.config,
2878
+ assertCurrent: assertStartupCurrent,
2879
+ ...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
2880
+ }));
2881
+ if (startOptions.transport === "stdio" && nativeCommandAtStart && !desktopGeneration && !isManagedCodexDesktopCommand(startOptions.command) && !isCodexAppServerProxyLaunch(startOptions.args)) ownCodexInferenceClient(client);
2882
+ recordCodexAppServerAuthHandoff(client, authHandoff);
2883
+ if (runtimeArtifactModule && runtimeArtifact) runtimeArtifactModule.bindCodexAppServerRuntimeArtifact(client, runtimeArtifact);
2884
+ assertStartupCurrent();
2885
+ const fenceKey = resolveCodexNativeConfigFenceKey({ client });
2886
+ if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
2887
+ const release = await acquireCodexNativeConfigFence(fenceKey, options);
2888
+ try {
2889
+ assertCodexAppServerClientStartSelectionCurrent({ client });
2890
+ return release;
2891
+ } catch (error) {
2892
+ release();
2893
+ throw error;
2894
+ }
2895
+ });
2896
+ ready = true;
2897
+ return client;
2898
+ } finally {
2899
+ if (!ready) ownCodexStartup(params.lifetime, client.closeAndWait());
2900
+ }
2901
+ }
2902
+ throw new Error("Managed Codex app-server fallback candidates were exhausted.");
2903
+ }
2904
+ function isCodexComputerUseCandidateArtifactsUnavailableError(error) {
2905
+ return error !== null && typeof error === "object" && "code" in error && error.code === "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
2906
+ }
2907
+ function resolveManagedFallbackStartOptions(startOptions) {
2908
+ const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
2909
+ const candidates = [];
2910
+ for (const [index, command] of commands.entries()) {
2911
+ const managedFallbackCommandPaths = commands.slice(index + 1);
2912
+ const candidate = {
2913
+ ...startOptions,
2914
+ command
2915
+ };
2916
+ if (managedFallbackCommandPaths.length === 0) delete candidate.managedFallbackCommandPaths;
2917
+ else candidate.managedFallbackCommandPaths = managedFallbackCommandPaths;
2918
+ candidates.push(candidate);
2919
+ }
2920
+ return candidates;
2921
+ }
2922
+ function shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates) {
2923
+ return startOptions.commandSource === "resolved-managed" && index < startOptionsCandidates.length - 1 && isUnsupportedCodexAppServerVersionError(error);
2924
+ }
2925
+ /** Clears and closes the shared entry only if it still owns the supplied client. */
2926
+ function clearSharedCodexAppServerClientIfCurrent(client) {
2927
+ if (!client) return false;
2928
+ const state = getSharedCodexAppServerClientState();
2929
+ const entry = getCurrentSharedClientEntry(client);
2930
+ if (!entry) return false;
2931
+ state.clients.delete(entry.key);
2932
+ client.close();
2933
+ return true;
2934
+ }
2935
+ /** Captures a revocable observation of the exact shared client and native account/config. */
2936
+ function captureSharedCodexAppServerCatalogLifetime(client) {
2937
+ const isCurrent = captureSharedClientRegistration(client);
2938
+ const revision = client.getModelCatalogRevision();
2939
+ return () => isCurrent() && client.getModelCatalogRevision() === revision;
2940
+ }
2941
+ /** Registration ends on retirement even when sibling leases keep the process alive. */
2942
+ function captureSharedClientRegistration(client) {
2943
+ const state = getSharedCodexAppServerClientState();
2944
+ const entry = getCurrentSharedClientEntry(client);
2945
+ const generation = readCodexAppServerClientDesktopGeneration(client);
2946
+ return () => entry !== void 0 && state.clients.get(entry.key) === entry && entry.client === client && !entry.closeWhenIdle && !entry.closeError && !client.getCloseError() && (!generation || isCodexDesktopGenerationCurrent(generation));
2947
+ }
2948
+ /** Retains the matching shared client and returns a release callback. */
2949
+ function retainSharedCodexAppServerClientIfCurrent(client) {
2950
+ const entry = getCurrentSharedClientEntry(client);
2951
+ return entry ? retainSharedClientEntry(entry) : void 0;
2952
+ }
2953
+ /** Retains the live shared client whose initialized instance id matches a thread binding. */
2954
+ function retainSharedCodexAppServerClientByInstanceId(clientId) {
2955
+ const normalizedClientId = clientId?.trim();
2956
+ if (!normalizedClientId) return;
2957
+ for (const entry of getSharedCodexAppServerClientState().clients.values()) {
2958
+ const client = entry.client;
2959
+ if (client?.getInstanceId() !== normalizedClientId || entry.closeWhenIdle || entry.closeError) continue;
2960
+ return {
2961
+ client,
2962
+ release: retainSharedClientEntry(entry)
2963
+ };
2964
+ }
2965
+ }
2966
+ /** Captures physical ownership, independently of unrelated thread and reader leases. */
2967
+ function captureCodexAppServerClientLifetime(client, requiredOwnership) {
2968
+ const state = getSharedCodexAppServerClientState();
2969
+ const start = state.startMetadata.get(client)?.startOptions;
2970
+ if (requiredOwnership === "native-process" && (start?.transport !== "stdio" || isCodexAppServerProxyLaunch(start.args))) throw new AgentHarnessPreflightError("Codex ordinary configuration refresh requires an OpenClaw-managed local stdio process, not an external socket or app-server proxy. No turn was sent; reconnect through managed local stdio before continuing.");
2971
+ const isCurrent = requiredOwnership === "native-process" && state.isolatedClients.has(client) ? () => state.isolatedClients.has(client) && !client.getCloseError() : captureSharedClientRegistration(client);
2972
+ const assertCurrent = () => {
2973
+ if (!isCurrent()) throw new CodexAdoptedThreadActiveError("Codex app-server connection changed during thread preparation; reconnect before continuing");
2974
+ assertCodexAppServerClientStartSelectionCurrent({ client });
2975
+ };
2976
+ assertCurrent();
2977
+ return assertCurrent;
2978
+ }
2979
+ function createOlderDesktopGenerationDrainWait(params) {
2980
+ const targetHome = resolveCodexNativeConfigFenceKey({
2981
+ startOptions: params.startOptions,
2982
+ agentDir: params.agentDir
2983
+ });
2984
+ if (!targetHome) return {
2985
+ promise: Promise.resolve(),
2986
+ cancel: () => void 0
2987
+ };
2988
+ const state = getSharedCodexAppServerClientState();
2989
+ let settled = false;
2990
+ let resolveWait;
2991
+ const promise = new Promise((resolve) => {
2992
+ resolveWait = resolve;
2993
+ });
2994
+ const cancel = () => {
2995
+ if (settled) return;
2996
+ settled = true;
2997
+ state.desktopGenerationDrainChecks.delete(check);
2998
+ resolveWait();
2999
+ };
3000
+ const check = () => {
3001
+ if (!hasLiveOlderDesktopGenerationClient({
3002
+ state,
3003
+ generation: params.generation,
3004
+ targetHome
3005
+ })) cancel();
3006
+ };
3007
+ state.desktopGenerationDrainChecks.add(check);
3008
+ check();
3009
+ return {
3010
+ promise,
3011
+ cancel
3012
+ };
3013
+ }
3014
+ function hasLiveOlderDesktopGenerationClient(params) {
3015
+ for (const client of params.state.liveClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
3016
+ for (const client of params.state.isolatedClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
3017
+ return false;
3018
+ }
3019
+ function isOlderDesktopGenerationClientForHome(client, generation, targetHome) {
3020
+ const metadata = getSharedCodexAppServerClientState().startMetadata.get(client);
3021
+ return Boolean(metadata?.desktopGeneration && metadata.desktopGeneration.epoch < generation.epoch && resolveCodexNativeConfigFenceKey({ client }) === targetHome);
3022
+ }
3023
+ function notifyDesktopGenerationDrainChecks(state) {
3024
+ for (const check of state.desktopGenerationDrainChecks) check();
3025
+ }
3026
+ /** Clears a matching shared client and waits for its process to exit. */
3027
+ async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
3028
+ if (!client) return false;
3029
+ const state = getSharedCodexAppServerClientState();
3030
+ const entry = getCurrentSharedClientEntry(client);
3031
+ if (!entry) return false;
3032
+ state.clients.delete(entry.key);
3033
+ await client.closeAndWait(options);
3034
+ return true;
3035
+ }
3036
+ /** Clears all shared clients and waits for their processes to exit. */
3037
+ async function clearSharedCodexAppServerClientAndWait(options) {
3038
+ const state = getSharedCodexAppServerClientState();
3039
+ const lifetime = state.startup;
3040
+ lifetime.controller.abort();
3041
+ state.clients.clear();
3042
+ const closing = Promise.all([...state.liveClients].map((client) => ownCodexStartup(lifetime, client.closeAndWait(options))));
3043
+ closing.catch(() => void 0);
3044
+ try {
3045
+ while (lifetime.pending.size > 0) await Promise.allSettled(lifetime.pending);
3046
+ await closing;
3047
+ } finally {
3048
+ if (state.startup === lifetime) state.startup = createCodexAppServerStartupLifetime();
3049
+ }
3050
+ }
3051
+ globalThis[SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER] = clearSharedCodexAppServerClientAndWait;
3052
+ function getOrCreateSharedClientEntry(state, key) {
3053
+ let entry = state.clients.get(key);
3054
+ if (!entry) {
3055
+ entry = {
3056
+ key,
3057
+ activeLeases: 0,
3058
+ anonymousLeases: 0,
3059
+ pendingAcquires: 0,
3060
+ closeWhenIdle: false,
3061
+ onStartedClientCallbacks: /* @__PURE__ */ new Set()
3062
+ };
3063
+ state.clients.set(key, entry);
3064
+ }
3065
+ return entry;
3066
+ }
3067
+ /** Clears a matching shared client only when no lease or acquire currently claims it. */
3068
+ function clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client) {
3069
+ const entry = getCurrentSharedClientEntry(client);
3070
+ return {
3071
+ found: entry !== void 0,
3072
+ closed: entry ? closeSharedClientEntryIfUnclaimed(entry) : false,
3073
+ activeLeases: entry?.activeLeases ?? 0,
3074
+ pendingAcquires: entry?.pendingAcquires ?? 0
3075
+ };
3076
+ }
3077
+ function retainSharedClientEntry(entry, counter = "activeLeases") {
3078
+ let released = false;
3079
+ entry[counter] += 1;
3080
+ return () => {
3081
+ if (released) return;
3082
+ released = true;
3083
+ releaseSharedClientEntry(entry, counter);
3084
+ };
3085
+ }
3086
+ function releaseSharedClientEntry(entry, counter) {
3087
+ entry[counter] -= 1;
3088
+ closeRetiredSharedClientEntryIfIdle(entry);
3089
+ notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
3090
+ }
3091
+ function closeSharedClientEntryIfUnclaimed(entry) {
3092
+ if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return false;
3093
+ const state = getSharedCodexAppServerClientState();
3094
+ if (state.clients.get(entry.key) !== entry) return false;
3095
+ state.clients.delete(entry.key);
3096
+ entry.client?.close();
3097
+ return Boolean(entry.client);
3098
+ }
3099
+ function retirePendingSharedClientEntryIfUnclaimed(entry) {
3100
+ if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
3101
+ entry.startupAbort?.abort(/* @__PURE__ */ new Error("Codex app-server startup was abandoned"));
3102
+ entry.closeWhenIdle = true;
3103
+ const state = getSharedCodexAppServerClientState();
3104
+ if (state.clients.get(entry.key) === entry) state.clients.delete(entry.key);
3105
+ if (!entry.client) return;
3106
+ closeRetiredSharedClientEntry(entry);
3107
+ }
3108
+ //#endregion
3109
+ export { reconcileCodexComputerUseStartArtifacts as $, CodexAppServerStartupError as A, forgetCodexWorkspaceReferences as B, CodexThreadBindingConflictError as C, bindCodexInferenceThread as D, assertCodexInferenceRouteConfig as E, resolveCodexStartupTimeoutMs as F, protectCodexAppServerLiveThread as G, isCodexAppServerClientRuntimeLive as H, withCodexStartupTimeout as I, retainCodexAppServerLiveThread as J, readCodexClientSessionMeta as K, claimCodexAppServerLiveThread as L, TURN_TERMINAL_SETTLEMENT_TIMEOUT_MS as M, isCodexAppServerStartupError as N, getCodexInferenceThread as O, resolveCodexGatewayTimeoutWithGraceMs as P, rememberCodexRateLimitsRead as Q, consumeCodexAppServerLiveThread as R, CodexAdoptedThreadActiveError as S, acquireCodexNativeConfigFence as T, isCodexAppServerLiveThreadClaimed as U, hasCodexAppServerLiveThread as V, prepareCodexWorkspaceReferences as W, readCodexRateLimitsRevision as X, unsubscribeCodexAppServerLiveThread as Y, readRecentCodexRateLimits as Z, retainSharedCodexAppServerClientByInstanceId as _, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as a, waitForCodexAppServerClientDesktopGenerationDrain as b, getLeasedSharedCodexAppServerClient as c, readCodexAppServerClientDesktopGeneration as d, resolveCodexAppServerAuthAccountCacheKey as et, readCodexAppServerClientDesktopGenerationFingerprint as f, resolveCodexNativeConfigFenceKey as g, releaseLeasedSharedCodexAppServerClient as h, clearSharedCodexAppServerClientIfCurrent as i, TURN_FINALIZE_DRAIN_ABORT_GRACE_MS as j, prepareCodexInferenceThreadConfig as k, getSharedCodexAppServerClient as l, releaseCodexAppServerClientLease as m, captureCodexAppServerClientLifetime as n, resolveCodexManagedBundledMarketplacePath as nt, clearSharedCodexAppServerClientIfCurrentAndWait as o, readCodexAppServerClientProcessIdentity as p, releaseCodexAppServerLiveThread as q, captureSharedCodexAppServerCatalogLifetime as r, assertNotSymlink as rt, createIsolatedCodexAppServerClient as s, assertCodexAppServerClientStartSelectionCurrent as t, resolveCodexAppServerPreparedAuthHandoff as tt, isCodexAppServerStartSelectionChangedError as u, retainSharedCodexAppServerClientIfCurrent as v, CodexThreadStartRequestError as w, withLeasedCodexAppServerClientStartSelectionRetry as x, shared_client_exports as y, ensureCodexAppServerClientRuntime as z };