@openclaw/codex 2026.8.1-beta.2 → 2026.9.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/api.js +147 -0
  2. package/dist/{attempt-client-cleanup-DMLXUfa8.js → attempt-client-cleanup-bfsmcQ6m.js} +21 -2
  3. package/dist/{binding-connection-I5Bo-AsX.js → binding-connection-BMvPX73o.js} +15 -3
  4. package/dist/{bounded-turn-qgZmzy_4.js → bounded-turn-B7iBUcuP.js} +22 -16
  5. package/dist/{capabilities-CybGI1oz.js → capabilities-BRMh0aIq.js} +1 -1
  6. package/dist/cli-metadata.js +1 -1
  7. package/dist/{command-formatters-B1AEz8ZA.js → command-formatters-DEsIufkt.js} +2 -2
  8. package/dist/{command-handlers-Db8pu_ds.js → command-handlers-D_-1_qRp.js} +65 -37
  9. package/dist/command-rpc-CCeCkf_v.js +90 -0
  10. package/dist/{compact-LspQaGfl.js → compact-BglNFZrQ.js} +35 -29
  11. package/dist/{session-binding-DPxIvMgE.js → config-giW8X0QA.js} +108 -914
  12. package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
  13. package/dist/doctor-contract-api.js +23 -21
  14. package/dist/{dynamic-tools-BVdyWkUD.js → dynamic-tools-Dnlfa--w.js} +1345 -1424
  15. package/dist/{effective-mcp-catalog-DIIhAtOZ.js → effective-mcp-catalog-CTeGrnfO.js} +2 -2
  16. package/dist/elicitation-input-DMkeki8d.js +71 -0
  17. package/dist/{event-projector-assistant-message-BwxV4d6N.js → event-projector-assistant-message-C5-Ds8mA.js} +18 -1
  18. package/dist/{event-projector-usage-e5huIQWu.js → event-projector-usage-Bk1I90Bg.js} +18 -18
  19. package/dist/harness-Csz0F-DK.js +258 -0
  20. package/dist/harness.js +2 -226
  21. package/dist/index.js +262 -181
  22. package/dist/{isolated-completion-C58NbL40.js → isolated-completion-Cv6FqygM.js} +4 -4
  23. package/dist/managed-binary-DFYK4DWq.js +248 -0
  24. package/dist/media-understanding-provider.js +2 -2
  25. package/dist/model-catalog-i9rNBthX.js +48 -0
  26. package/dist/{models-BFlpn1Ex.js → models-TS3HBU-q.js} +4 -3
  27. package/dist/{native-subagent-monitor-DyTGmTUx.js → native-subagent-monitor-4Mfq2HDs.js} +354 -83
  28. package/dist/node-exec-server.runtime-BpWqJO1V.js +192 -0
  29. package/dist/{plugin-app-cache-key-C3H2CVp6.js → plugin-app-cache-key-C1CAdfcP.js} +19 -4
  30. package/dist/plugin-approval-roundtrip-bUm777tR.js +95 -0
  31. package/dist/{plugin-marketplace-discovery-CzrGJdCR.js → plugin-marketplace-discovery-B9DvaAG-.js} +59 -54
  32. package/dist/{plugin-metadata-cache-CNJPj4_b.js → plugin-metadata-cache-CTCT3w5L.js} +164 -134
  33. package/dist/{protocol-validators-B4F7L8Rf.js → protocol-validators-VeJxxJ0U.js} +80 -5
  34. package/dist/{rate-limits-DZXmOL8-.js → rate-limits-C5_D8Gmb.js} +1 -1
  35. package/dist/{request-BRl5_UfX.js → request-DXXGHko0.js} +2 -2
  36. package/dist/{run-attempt-P8BdxC1I.js → run-attempt-DYc0QCBF.js} +1276 -765
  37. package/dist/{runtime-artifact-BVSPdd6g.js → runtime-artifact-BvyT1uyd.js} +4 -7
  38. package/dist/{sandbox-guard-Cmwc9eIk.js → sandbox-guard-kuWTn9C5.js} +25 -19
  39. package/dist/session-binding-Bcynfd-_.js +874 -0
  40. package/dist/{session-catalog-xwNi_SDM.js → session-catalog-DaLKVtpH.js} +1534 -1288
  41. package/dist/session-catalog-home-id-BNwAJ9D7.js +18 -0
  42. package/dist/{session-cli-7hgXZDV7.js → session-cli-DMc4oS2w.js} +20 -7
  43. package/dist/session-permission-policy-BrNH1bjy.js +160 -0
  44. package/dist/{session-retirement-DoMMJFKm.js → session-retirement-CcNC3YSY.js} +3 -3
  45. package/dist/{session-upstream-marker-CrIFX06J.js → session-upstream-marker-BIkusb95.js} +1 -1
  46. package/dist/{settled-turn-finalizer-BRWBO4MO.js → settled-turn-finalizer-Dc3ApjU4.js} +24 -20
  47. package/dist/{shared-client-kIBbGjk4.js → shared-client-DKz23hHY.js} +1632 -372
  48. package/dist/{side-question-9HAL26l9.js → side-question-DjA_jsNe.js} +105 -54
  49. package/dist/{thread-lifecycle-Bu1BTDXr.js → thread-lifecycle-L7oEkTlq.js} +1147 -114
  50. package/dist/{thread-ownership-6kOQHlyC.js → thread-ownership-DZeuKH1j.js} +2 -2
  51. package/dist/{thread-resume-CPrJNQVM.js → thread-resume-BE9qNEW0.js} +3 -3
  52. package/dist/{transcript-mirror-B9gGgnJS.js → transcript-mirror-DsCMWBca.js} +137 -38
  53. package/dist/{transport-stdio-DIImwDVS.js → transport-stdio-DhoGy33Z.js} +2 -2
  54. package/dist/{upstream-prompt-provenance-ClRBije0.js → upstream-prompt-provenance-CFmsKwr9.js} +12 -14
  55. package/dist/{upstream-session-fork-CE-_LdnP.js → upstream-session-fork-DlaHxSLC.js} +12 -6
  56. package/dist/{usage-CHlV0e-o.js → usage-ROIaW3wo.js} +3 -3
  57. package/dist/{web-search-provider.runtime-DRw2Vl42.js → web-search-provider.runtime-ClaNPV7N.js} +3 -3
  58. package/openclaw.plugin.json +49 -2
  59. package/package.json +9 -8
  60. package/dist/app-server-policy-zcBDc1JU.js +0 -49
@@ -1,32 +1,33 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { A as resolveCodexAppServerUserHomeDir, _ as resolveCodexAppServerStartOptionsForAgent, g as resolveCodexAppServerRuntimeOptions, o as isCodexAppServerNativeAuthProfile, p as codexAppServerStartOptionsKey, v as resolveCodexComputerUseConfig } from "./session-binding-DPxIvMgE.js";
3
- import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DIImwDVS.js";
4
- import { createRequire } from "node:module";
2
+ import { S as resolveCodexAppServerUserHomeDir, a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey } from "./config-giW8X0QA.js";
3
+ import { a as CODEX_APP_SERVER_VERSION, c as resolveMacOSDesktopCodexAppPathCandidates, l as resolveMacOSDesktopCodexBundledMarketplaceCandidates, n as resolveManagedCodexAppServerStartOptions, o as MIN_SUPPORTED_CODEX_APP_SERVER_VERSION, r as resolveManagedCodexNativeCommand, s as resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath, t as isManagedCodexDesktopCommand, u as resolveMacOSDesktopCodexComputerUseServiceAppCandidates } from "./managed-binary-DFYK4DWq.js";
4
+ import { o as isCodexAppServerNativeAuthProfile } from "./session-binding-Bcynfd-_.js";
5
+ import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DhoGy33Z.js";
5
6
  import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
- import fs, { access } from "node:fs/promises";
7
+ import fs from "node:fs/promises";
7
8
  import path from "node:path";
8
9
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
9
10
  import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
10
11
  import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
11
12
  import { createHash, randomUUID } from "node:crypto";
12
- import fsSync, { constants, existsSync, readFileSync, realpathSync } from "node:fs";
13
+ import fsSync, { constants, existsSync, watch } from "node:fs";
13
14
  import os from "node:os";
14
15
  import { isDeepStrictEqual } from "node:util";
15
16
  import { ensureAuthProfileStore, findPersistedAuthProfileCredential, loadAuthProfileStoreForSecretsRuntime, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolveAuthProfileOrder, resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, resolveProviderIdForAuth } from "openclaw/plugin-sdk/agent-runtime";
16
- import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
17
+ import { hasUsableOAuthCredential, resolveOpenAICodexAuthIdentity } from "openclaw/plugin-sdk/provider-auth";
17
18
  import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
19
+ import { assertNoSymlinkParents, withTimeout } from "openclaw/plugin-sdk/security-runtime";
18
20
  import { runExec } from "openclaw/plugin-sdk/process-runtime";
21
+ import { execFile } from "node:child_process";
19
22
  import { createInterface } from "node:readline";
20
23
  import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
21
24
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
25
+ import { parse } from "semver";
22
26
  import { EventEmitter } from "node:events";
23
27
  import net from "node:net";
24
28
  import { PassThrough, Writable } from "node:stream";
25
29
  import { StringDecoder } from "node:string_decoder";
26
30
  import WebSocket from "ws";
27
- import { fileURLToPath } from "node:url";
28
- import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
29
- import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
30
31
  //#region extensions/codex/src/app-server/auth-start-options.ts
31
32
  const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
32
33
  const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
@@ -54,34 +55,145 @@ function withEphemeralCodexAuthStore(params) {
54
55
  };
55
56
  }
56
57
  //#endregion
57
- //#region extensions/codex/src/app-server/desktop-app-paths.ts
58
- /** Shared path candidates for Codex's macOS desktop app bundle. */
59
- const MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES = [{
60
- appName: "ChatGPT.app",
61
- appBundlePath: "/Applications/ChatGPT.app",
62
- appServerCommandPath: "/Applications/ChatGPT.app/Contents/Resources/codex",
63
- bundledMarketplacePath: "/Applications/ChatGPT.app/Contents/Resources/plugins/openai-bundled",
64
- computerUseServiceAppPaths: ["/Applications/ChatGPT.app/Contents/Resources/cua_node/lib/node_modules/@oai/sky/Codex Computer Use.app", "/Applications/ChatGPT.app/Contents/Resources/plugins/openai-bundled/plugins/computer-use/Codex Computer Use.app"]
65
- }, {
66
- appName: "Codex.app",
67
- appBundlePath: "/Applications/Codex.app",
68
- appServerCommandPath: "/Applications/Codex.app/Contents/Resources/codex",
69
- bundledMarketplacePath: "/Applications/Codex.app/Contents/Resources/plugins/openai-bundled",
70
- computerUseServiceAppPaths: ["/Applications/Codex.app/Contents/Resources/plugins/openai-bundled/plugins/computer-use/Codex Computer Use.app", "/Applications/Codex.app/Contents/Resources/cua_node/lib/node_modules/@oai/sky/Codex Computer Use.app"]
71
- }];
72
- function resolveMacOSDesktopCodexBundledMarketplaceCandidates(platform = process.platform) {
73
- return platform === "darwin" ? MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.map((candidate) => candidate.bundledMarketplacePath) : [];
74
- }
75
- function resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform = process.platform, appServerCommand) {
76
- if (platform !== "darwin") return [];
77
- const matchingCandidate = appServerCommand ? MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(appServerCommand)) : void 0;
78
- const orderedCandidates = matchingCandidate ? [matchingCandidate, ...MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.filter((candidate) => candidate !== matchingCandidate)] : MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES;
79
- return [...new Set(orderedCandidates.flatMap((candidate) => candidate.computerUseServiceAppPaths))];
80
- }
81
- function resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath(params = {}) {
82
- const candidates = params.candidates ?? resolveMacOSDesktopCodexBundledMarketplaceCandidates(params.platform);
83
- const pathExists = params.pathExists ?? existsSync;
84
- return candidates.find((candidate) => pathExists(candidate));
58
+ //#region extensions/codex/src/app-server/computer-use-service-path.ts
59
+ /** Filesystem ownership guards for isolated Computer Use service provisioning. */
60
+ async function assertOwnedServicePath(params) {
61
+ await assertOwnedCodexHomePath({
62
+ ownershipRoot: params.ownershipRoot,
63
+ codexHome: params.codexHome,
64
+ allowMissing: true
65
+ });
66
+ assertPathAtOrInside(params.ownershipRoot, params.targetParent, "Computer Use service parent");
67
+ await assertNoSymlinkParents({
68
+ rootDir: params.ownershipRoot,
69
+ targetPath: params.targetParent,
70
+ allowMissing: true,
71
+ requireDirectories: true,
72
+ messagePrefix: "Computer Use service path"
73
+ });
74
+ await assertNotSymlink(params.targetPath, "Computer Use service target");
75
+ }
76
+ async function ensureOwnedCodexHome(codexHomeInput, ownershipRootInput = path.dirname(path.resolve(codexHomeInput))) {
77
+ const codexHome = path.resolve(codexHomeInput);
78
+ const ownershipRoot = path.resolve(ownershipRootInput);
79
+ await fs.mkdir(ownershipRoot, {
80
+ recursive: true,
81
+ mode: 448
82
+ });
83
+ await assertOwnedCodexHomePath({
84
+ ownershipRoot,
85
+ codexHome,
86
+ allowMissing: true
87
+ });
88
+ await ensureRealDirectoryTree(ownershipRoot, codexHome, "isolated Codex home");
89
+ await assertOwnedCodexHomePath({
90
+ ownershipRoot,
91
+ codexHome,
92
+ allowMissing: false
93
+ });
94
+ }
95
+ async function prepareOwnedServiceParent(params) {
96
+ await ensureOwnedCodexHome(params.codexHome, params.ownershipRoot);
97
+ await ensureRealDirectoryTree(params.ownershipRoot, params.targetParent, "Computer Use service parent");
98
+ await assertNoSymlinkParents({
99
+ rootDir: params.ownershipRoot,
100
+ targetPath: params.targetParent,
101
+ allowMissing: false,
102
+ requireDirectories: true,
103
+ messagePrefix: "Computer Use service path"
104
+ });
105
+ const [rootIdentity, parentIdentity] = await Promise.all([readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root"), readRealDirectoryIdentity(params.targetParent, "Computer Use service parent")]);
106
+ assertPathAtOrInside(rootIdentity.realPath, parentIdentity.realPath, "canonical Computer Use service parent");
107
+ return parentIdentity;
108
+ }
109
+ async function assertOwnedCodexHomePath(params) {
110
+ await readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root");
111
+ assertPathAtOrInside(params.ownershipRoot, params.codexHome, "isolated Codex home");
112
+ await assertNoSymlinkParents({
113
+ rootDir: params.ownershipRoot,
114
+ targetPath: params.codexHome,
115
+ allowMissing: params.allowMissing,
116
+ requireDirectories: true,
117
+ messagePrefix: "Computer Use service path"
118
+ });
119
+ }
120
+ async function readRealDirectoryIdentity(directoryPath, label) {
121
+ const logicalPath = path.resolve(directoryPath);
122
+ const before = await fs.lstat(logicalPath);
123
+ if (before.isSymbolicLink() || !before.isDirectory()) throw new Error(`${label} must be a real directory: ${logicalPath}`);
124
+ const realPath = await fs.realpath(logicalPath);
125
+ const [after, resolved] = await Promise.all([fs.lstat(logicalPath), fs.lstat(realPath)]);
126
+ 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.`);
127
+ return {
128
+ logicalPath,
129
+ realPath,
130
+ dev: after.dev,
131
+ ino: after.ino
132
+ };
133
+ }
134
+ async function assertOwnedServiceParentStable(parent) {
135
+ await assertDirectoryIdentityStable(parent, "Computer Use service parent");
136
+ }
137
+ async function assertDirectoryIdentityStable(expected, label) {
138
+ if (!await directoryIdentityIsStable(expected)) throw new Error(`${label} changed during refresh; refusing to mutate the replacement path.`);
139
+ }
140
+ async function ownedServiceParentIsStable(parent) {
141
+ return await directoryIdentityIsStable(parent);
142
+ }
143
+ async function directoryIdentityIsStable(expected) {
144
+ try {
145
+ const current = await fs.lstat(expected.logicalPath);
146
+ if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) return false;
147
+ return await fs.realpath(expected.logicalPath) === expected.realPath;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+ async function assertNotSymlink(filePath, label) {
153
+ try {
154
+ if ((await fs.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
155
+ } catch (error) {
156
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
157
+ throw error;
158
+ }
159
+ }
160
+ async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
161
+ const root = path.resolve(ownershipRoot);
162
+ const target = path.resolve(directoryPath);
163
+ assertPathAtOrInside(root, target, label);
164
+ const relative = path.relative(root, target);
165
+ let current = root;
166
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
167
+ current = path.join(current, segment);
168
+ const existing = await fs.lstat(current).catch((error) => {
169
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
170
+ throw error;
171
+ });
172
+ if (!existing) {
173
+ try {
174
+ await fs.mkdir(current, { mode: 448 });
175
+ } catch (error) {
176
+ if (!hasNodeErrorCode$1(error, "EEXIST")) throw error;
177
+ }
178
+ const created = await fs.lstat(current);
179
+ if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
180
+ } else if (existing.isSymbolicLink() || !existing.isDirectory()) throw new Error(`${label} must traverse real directories: ${current}`);
181
+ }
182
+ await assertNoSymlinkParents({
183
+ rootDir: root,
184
+ targetPath: target,
185
+ allowMissing: false,
186
+ requireDirectories: true,
187
+ messagePrefix: "Computer Use service path"
188
+ });
189
+ await readRealDirectoryIdentity(target, label);
190
+ }
191
+ function assertPathAtOrInside(rootPath, candidatePath, label) {
192
+ const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
193
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
194
+ }
195
+ function hasNodeErrorCode$1(error, code) {
196
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
85
197
  }
86
198
  //#endregion
87
199
  //#region extensions/codex/src/app-server/computer-use-cache.ts
@@ -101,7 +213,12 @@ async function ensureCodexComputerUseSharedPluginCache(params) {
101
213
  const cachePath = path.join(cacheRoot, version);
102
214
  return {
103
215
  status: "shared",
104
- changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version),
216
+ changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, {
217
+ codexHome: params.codexHome,
218
+ ownershipRoot: params.ownershipRoot,
219
+ assertCurrent: params.assertCurrent,
220
+ forceRefresh: params.forceRefresh
221
+ }),
105
222
  cachePath,
106
223
  targetPath: sourcePluginRoot,
107
224
  version,
@@ -128,42 +245,57 @@ async function readBundledPluginVersion(sourcePluginRoot) {
128
245
  return;
129
246
  }
130
247
  }
131
- async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version) {
132
- await fs.mkdir(path.dirname(cachePath), { recursive: true });
133
- const stat = await fs.lstat(cachePath).catch(() => void 0);
248
+ async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, boundary) {
249
+ const cacheRoot = path.dirname(cachePath);
250
+ const ownedParent = boundary.ownershipRoot ? await prepareOwnedServiceParent({
251
+ ownershipRoot: boundary.ownershipRoot,
252
+ codexHome: boundary.codexHome,
253
+ targetParent: cacheRoot
254
+ }) : void 0;
255
+ if (!ownedParent) await fs.mkdir(cacheRoot, { recursive: true });
256
+ const physicalCachePath = ownedParent ? path.join(ownedParent.realPath, path.basename(cachePath)) : cachePath;
257
+ const stat = await fs.lstat(physicalCachePath).catch(() => void 0);
134
258
  if (stat?.isDirectory() && !stat.isSymbolicLink()) {
135
- if (await readBundledPluginVersion(cachePath) === version) return false;
259
+ if (await readBundledPluginVersion(physicalCachePath) === version && !boundary.forceRefresh) return false;
136
260
  }
137
- const cacheRoot = path.dirname(cachePath);
138
261
  const cacheName = path.basename(cachePath);
139
- const stagingRoot = await fs.mkdtemp(path.join(cacheRoot, `.${cacheName}.staging-`));
262
+ const physicalCacheRoot = path.dirname(physicalCachePath);
263
+ const stagingRoot = await fs.mkdtemp(path.join(physicalCacheRoot, `.${cacheName}.staging-`));
140
264
  const stagedPath = path.join(stagingRoot, cacheName);
141
- const backupPath = path.join(cacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
265
+ const backupPath = path.join(physicalCacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
142
266
  let backupCreated = false;
143
267
  try {
144
268
  await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
269
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
145
270
  if (stat) {
146
- await fs.rename(cachePath, backupPath);
271
+ boundary.assertCurrent?.();
272
+ await fs.rename(physicalCachePath, backupPath);
147
273
  backupCreated = true;
148
274
  }
149
275
  try {
150
- await fs.rename(stagedPath, cachePath);
276
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
277
+ boundary.assertCurrent?.();
278
+ await fs.rename(stagedPath, physicalCachePath);
151
279
  } catch (error) {
152
280
  if (backupCreated) try {
153
- await fs.rename(backupPath, cachePath);
281
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
282
+ await fs.rename(backupPath, physicalCachePath);
154
283
  backupCreated = false;
155
284
  } catch (restoreError) {
156
285
  throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
157
286
  }
158
287
  throw error;
159
288
  }
160
- if (backupCreated) await fs.rm(backupPath, {
161
- recursive: true,
162
- force: true
163
- });
289
+ if (backupCreated) {
290
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
291
+ await fs.rm(backupPath, {
292
+ recursive: true,
293
+ force: true
294
+ });
295
+ }
164
296
  return true;
165
297
  } finally {
166
- await fs.rm(stagingRoot, {
298
+ if (!ownedParent || await directoryIdentityIsStable(ownedParent)) await fs.rm(stagingRoot, {
167
299
  recursive: true,
168
300
  force: true
169
301
  });
@@ -179,108 +311,360 @@ function skippedCacheResult(status, message) {
179
311
  };
180
312
  }
181
313
  //#endregion
314
+ //#region extensions/codex/src/app-server/computer-use-marketplace.ts
315
+ /** Managed local wrapper for Codex's reserved bundled marketplace. */
316
+ const MARKETPLACE_NAME = "openai-bundled";
317
+ const activeInstalls$1 = /* @__PURE__ */ new Map();
318
+ function resolveCodexManagedBundledMarketplacePath(codexHome) {
319
+ return path.join(codexHome, ".tmp", "bundled-marketplaces", MARKETPLACE_NAME);
320
+ }
321
+ async function ensureCodexManagedBundledMarketplace(params) {
322
+ const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
323
+ const source = await resolveCodexManagedBundledMarketplaceSource({
324
+ ...params,
325
+ candidates
326
+ });
327
+ if (!source) return;
328
+ const parentPath = path.dirname(resolveCodexManagedBundledMarketplacePath(params.codexHome));
329
+ const targetPath = path.join(parentPath, MARKETPLACE_NAME);
330
+ const parent = await prepareOwnedServiceParent({
331
+ ownershipRoot: params.ownershipRoot,
332
+ codexHome: params.codexHome,
333
+ targetParent: parentPath
334
+ });
335
+ const physicalTargetPath = path.join(parent.realPath, MARKETPLACE_NAME);
336
+ await assertNotSymlink(physicalTargetPath, "managed bundled marketplace");
337
+ const active = activeInstalls$1.get(physicalTargetPath);
338
+ if (active) {
339
+ if (active.sourcePath === source.bundledMarketplacePath) return await active.promise;
340
+ await active.promise.catch(() => void 0);
341
+ return await ensureCodexManagedBundledMarketplace(params);
342
+ }
343
+ const install = reconcileManagedWrapper({
344
+ parent,
345
+ physicalTargetPath,
346
+ targetPath,
347
+ source,
348
+ ownershipCandidates: params.ownershipCandidates ?? candidates,
349
+ assertCurrent: params.assertCurrent
350
+ });
351
+ const activeEntry = {
352
+ sourcePath: source.bundledMarketplacePath,
353
+ promise: install
354
+ };
355
+ activeInstalls$1.set(physicalTargetPath, activeEntry);
356
+ const clearActive = () => {
357
+ if (activeInstalls$1.get(physicalTargetPath) === activeEntry) activeInstalls$1.delete(physicalTargetPath);
358
+ };
359
+ install.then(clearActive, clearActive);
360
+ return await install;
361
+ }
362
+ async function reconcileManagedWrapper(params) {
363
+ if (await wrapperMatches(params.physicalTargetPath, params.source.bundledMarketplacePath)) return params.targetPath;
364
+ return await publishManagedWrapper(params);
365
+ }
366
+ async function publishManagedWrapper(params) {
367
+ const { parent, physicalTargetPath, targetPath, source, ownershipCandidates, assertCurrent } = params;
368
+ const stagingPath = await fs.mkdtemp(path.join(parent.realPath, `.${MARKETPLACE_NAME}.staging-`));
369
+ const backupPath = path.join(parent.realPath, `.${MARKETPLACE_NAME}.backup-${process.pid}-${Date.now()}`);
370
+ let backupCreated = false;
371
+ try {
372
+ const manifestParent = path.join(stagingPath, ".agents", "plugins");
373
+ await fs.mkdir(manifestParent, {
374
+ recursive: true,
375
+ mode: 448
376
+ });
377
+ await Promise.all([fs.symlink(path.join(source.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"), path.join(manifestParent, "marketplace.json")), fs.symlink(path.join(source.bundledMarketplacePath, "plugins"), path.join(stagingPath, "plugins"))]);
378
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
379
+ const existing = await fs.lstat(physicalTargetPath).catch((error) => {
380
+ if (hasNodeErrorCode(error, "ENOENT")) return;
381
+ throw error;
382
+ });
383
+ if (existing && !existing.isDirectory()) throw new Error(`Managed bundled marketplace must be a real directory: ${targetPath}`);
384
+ if (existing && !await wrapperMatchesAnySource(physicalTargetPath, ownershipCandidates)) throw new Error(`Refusing to replace an unowned bundled marketplace directory: ${targetPath}`);
385
+ if (existing) {
386
+ assertCurrent?.();
387
+ await fs.rename(physicalTargetPath, backupPath);
388
+ backupCreated = true;
389
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
390
+ }
391
+ assertCurrent?.();
392
+ await fs.rename(stagingPath, physicalTargetPath);
393
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
394
+ if (backupCreated) {
395
+ await fs.rm(backupPath, { recursive: true });
396
+ backupCreated = false;
397
+ }
398
+ return targetPath;
399
+ } catch (error) {
400
+ if (backupCreated) try {
401
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
402
+ if (await fs.lstat(physicalTargetPath).catch(() => void 0)) {
403
+ if (!await wrapperMatches(physicalTargetPath, source.bundledMarketplacePath)) throw new Error("managed bundled marketplace replacement is no longer owned", { cause: error });
404
+ await fs.rm(physicalTargetPath, { recursive: true });
405
+ }
406
+ await fs.rename(backupPath, physicalTargetPath);
407
+ backupCreated = false;
408
+ } catch (restoreError) {
409
+ throw new Error(`Failed to restore the prior managed bundled marketplace: ${String(error)}`, { cause: restoreError });
410
+ }
411
+ throw error;
412
+ } finally {
413
+ if (await directoryIdentityIsStable(parent)) await fs.rm(stagingPath, {
414
+ recursive: true,
415
+ force: true
416
+ });
417
+ }
418
+ }
419
+ async function resolveCodexManagedBundledMarketplaceSource(params) {
420
+ const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
421
+ const command = params.appServerCommand && path.resolve(params.appServerCommand);
422
+ const ordered = command ? candidates.filter((candidate) => path.resolve(candidate.appServerCommandPath) === command) : candidates;
423
+ for (const candidate of ordered) if (await isExpectedMarketplace(candidate.bundledMarketplacePath)) return candidate;
424
+ }
425
+ async function isExpectedMarketplace(root) {
426
+ try {
427
+ const manifest = JSON.parse(await fs.readFile(path.join(root, ".agents", "plugins", "marketplace.json"), "utf8"));
428
+ await fs.access(path.join(root, "plugins", "computer-use", ".codex-plugin", "plugin.json"));
429
+ return isRecord(manifest) && manifest.name === MARKETPLACE_NAME && Array.isArray(manifest.plugins) && manifest.plugins.some((plugin) => isRecord(plugin) && plugin.name === "computer-use");
430
+ } catch {
431
+ return false;
432
+ }
433
+ }
434
+ async function wrapperMatches(targetPath, sourcePath) {
435
+ try {
436
+ const target = await fs.lstat(targetPath);
437
+ if (!target.isDirectory() || target.isSymbolicLink()) return false;
438
+ const [manifest, plugins] = await Promise.all([fs.readlink(path.join(targetPath, ".agents", "plugins", "marketplace.json")), fs.readlink(path.join(targetPath, "plugins"))]);
439
+ return manifest === path.join(sourcePath, ".agents", "plugins", "marketplace.json") && plugins === path.join(sourcePath, "plugins");
440
+ } catch {
441
+ return false;
442
+ }
443
+ }
444
+ async function wrapperMatchesAnySource(targetPath, candidates) {
445
+ for (const candidate of candidates) if (await wrapperMatches(targetPath, candidate.bundledMarketplacePath)) return true;
446
+ return false;
447
+ }
448
+ function hasNodeErrorCode(error, code) {
449
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
450
+ }
451
+ //#endregion
182
452
  //#region extensions/codex/src/app-server/computer-use-service.ts
183
453
  /** Native Computer Use service provisioning for isolated Codex homes. */
184
454
  const SERVICE_APP_NAME = "Codex Computer Use.app";
185
- const CLIENT_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient");
455
+ const SERVICE_BUNDLE_ID = "com.openai.sky.CUAService";
456
+ const CLIENT_BUNDLE_ID = "com.openai.sky.CUAService.cli";
457
+ const OPENAI_TEAM_ID = "2DC432GLL2";
458
+ const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app");
459
+ const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
186
460
  const COPY_TIMEOUT_MS = 12e4;
461
+ const INSPECT_TIMEOUT_MS = 3e4;
187
462
  const activeInstalls = /* @__PURE__ */ new Map();
188
- /** Ensures the official native client exists beneath the CODEX_HOME used by its launcher. */
463
+ /** Finds the first signed native service from one ordered desktop owner set. */
464
+ async function resolveCodexComputerUseServiceAppSourcePath(params) {
465
+ const platform = params.platform ?? process.platform;
466
+ if (platform !== "darwin") return;
467
+ return (await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand), params.inspectServiceApp ?? inspectTrustedServiceApp))?.path;
468
+ }
469
+ /** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
189
470
  async function ensureCodexComputerUseServiceApp(params) {
190
471
  const platform = params.platform ?? process.platform;
191
472
  if (platform !== "darwin") return {
192
473
  status: "unsupported",
193
474
  changed: false
194
475
  };
195
- const targetPath = path.join(path.resolve(params.codexHome), "computer-use", SERVICE_APP_NAME);
476
+ const codexHome = path.resolve(params.codexHome);
477
+ const ownershipRoot = path.resolve(params.ownershipRoot ?? path.dirname(codexHome));
478
+ const targetParent = path.join(codexHome, "computer-use");
479
+ const targetPath = path.join(targetParent, SERVICE_APP_NAME);
480
+ await ensureOwnedCodexHome(codexHome, ownershipRoot);
481
+ await assertOwnedServicePath({
482
+ ownershipRoot,
483
+ codexHome,
484
+ targetParent,
485
+ targetPath
486
+ });
487
+ const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
488
+ const syncKey = [targetPath, ...candidates].join("\0");
196
489
  const active = activeInstalls.get(targetPath);
197
- if (active) return await active;
490
+ if (active) {
491
+ if (active.syncKey === syncKey) return await active.promise;
492
+ await active.promise.catch(() => void 0);
493
+ return await ensureCodexComputerUseServiceApp(params);
494
+ }
198
495
  const install = ensureCodexComputerUseServiceAppOnce({
199
496
  ...params,
497
+ codexHome,
498
+ ownershipRoot,
499
+ targetParent,
200
500
  targetPath,
201
- platform
501
+ platform,
502
+ sourceAppCandidates: candidates
202
503
  });
203
- activeInstalls.set(targetPath, install);
204
- try {
205
- return await install;
206
- } finally {
207
- if (activeInstalls.get(targetPath) === install) activeInstalls.delete(targetPath);
208
- }
504
+ const activeEntry = {
505
+ syncKey,
506
+ promise: install
507
+ };
508
+ activeInstalls.set(targetPath, activeEntry);
509
+ const clearActive = () => {
510
+ if (activeInstalls.get(targetPath) === activeEntry) activeInstalls.delete(targetPath);
511
+ };
512
+ install.then(clearActive, clearActive);
513
+ return await install;
209
514
  }
210
515
  async function ensureCodexComputerUseServiceAppOnce(params) {
211
- if (await hasExecutableClient(params.targetPath)) return {
212
- status: "already_installed",
516
+ const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
517
+ const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
518
+ if (!source) return {
519
+ status: "source_missing",
213
520
  changed: false,
214
521
  targetPath: params.targetPath
215
522
  };
216
- const sourcePath = await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(params.platform, params.appServerCommand));
217
- if (!sourcePath) return {
218
- status: "source_missing",
523
+ const { path: sourcePath, identity: sourceIdentity } = source;
524
+ const ownedParent = await prepareOwnedServiceParent({
525
+ ownershipRoot: params.ownershipRoot,
526
+ codexHome: params.codexHome,
527
+ targetParent: params.targetParent
528
+ });
529
+ const operationTargetPath = path.join(ownedParent.realPath, SERVICE_APP_NAME);
530
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
531
+ const initialTarget = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
532
+ if (initialTarget.identity && identitiesMatch(initialTarget.identity, sourceIdentity)) return {
533
+ status: "already_current",
219
534
  changed: false,
220
- targetPath: params.targetPath
535
+ targetPath: params.targetPath,
536
+ sourcePath,
537
+ sourceBuild: sourceIdentity.build
221
538
  };
222
- const targetParent = path.dirname(params.targetPath);
223
- await fs.mkdir(targetParent, { recursive: true });
224
- const stagingRoot = await fs.mkdtemp(path.join(targetParent, ".service-app.staging-"));
539
+ await assertOwnedServiceParentStable(ownedParent);
540
+ const stagingRoot = await fs.mkdtemp(path.join(ownedParent.realPath, ".service-app.staging-"));
541
+ const stagingRootIdentity = await readRealDirectoryIdentity(stagingRoot, "Computer Use service staging directory");
225
542
  const stagedPath = path.join(stagingRoot, SERVICE_APP_NAME);
226
- const backupPath = path.join(targetParent, `.service-app.backup-${process.pid}-${Date.now()}`);
543
+ const backupPath = path.join(ownedParent.realPath, `.service-app.backup-${process.pid}-${Date.now()}`);
227
544
  let backupCreated = false;
228
545
  try {
229
546
  await (params.copyServiceApp ?? copyServiceAppWithDitto)(sourcePath, stagedPath);
230
- if (!await hasExecutableClient(stagedPath)) throw new Error(`Copied Computer Use service app is incomplete at ${stagedPath}.`);
231
- if (await pathExists$1(params.targetPath)) {
232
- await fs.rename(params.targetPath, backupPath);
547
+ await assertOwnedServiceParentStable(ownedParent);
548
+ await assertDirectoryIdentityStable(stagingRootIdentity, "Computer Use service staging directory");
549
+ await assertNotSymlink(stagedPath, "Copied Computer Use service app");
550
+ const stagedIdentity = await inspectServiceApp(stagedPath);
551
+ if (!stagedIdentity || !identitiesMatch(stagedIdentity, sourceIdentity)) throw new Error(`Copied Computer Use service app at ${stagedPath} does not match its selected signed source.`);
552
+ const stagedSnapshot = await readServiceAppSnapshot(stagedPath, inspectServiceApp);
553
+ const currentSourceIdentity = await inspectServiceApp(sourcePath);
554
+ await assertOwnedServiceParentStable(ownedParent);
555
+ if (!currentSourceIdentity || !identitiesMatch(currentSourceIdentity, sourceIdentity)) throw new Error("Selected Computer Use service source changed during refresh.");
556
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
557
+ if (await pathExists$1(operationTargetPath)) {
558
+ await assertOwnedServiceParentStable(ownedParent);
559
+ params.assertCurrent?.();
560
+ await fs.rename(operationTargetPath, backupPath);
561
+ await assertOwnedServiceParentStable(ownedParent);
233
562
  backupCreated = true;
234
- if (await hasExecutableClient(backupPath)) {
235
- await fs.rename(backupPath, params.targetPath);
563
+ const movedTarget = await readServiceAppSnapshot(backupPath, inspectServiceApp);
564
+ if (movedTarget.identity && identitiesMatch(movedTarget.identity, sourceIdentity)) {
565
+ await assertOwnedServiceParentStable(ownedParent);
566
+ await fs.rename(backupPath, operationTargetPath);
567
+ await assertOwnedServiceParentStable(ownedParent);
236
568
  backupCreated = false;
237
569
  return {
238
- status: "already_installed",
570
+ status: "already_current",
239
571
  changed: false,
240
572
  targetPath: params.targetPath,
241
- sourcePath
573
+ sourcePath,
574
+ sourceBuild: sourceIdentity.build
242
575
  };
243
576
  }
577
+ if (!snapshotsMatch(initialTarget, movedTarget)) {
578
+ await assertOwnedServiceParentStable(ownedParent);
579
+ await fs.rename(backupPath, operationTargetPath);
580
+ await assertOwnedServiceParentStable(ownedParent);
581
+ backupCreated = false;
582
+ throw new Error("Computer Use service target changed to an unexpected generation during refresh.");
583
+ }
244
584
  }
245
585
  try {
246
- await fs.rename(stagedPath, params.targetPath);
586
+ await assertOwnedServiceParentStable(ownedParent);
587
+ params.assertCurrent?.();
588
+ await fs.rename(stagedPath, operationTargetPath);
589
+ await assertOwnedServiceParentStable(ownedParent);
247
590
  } catch (error) {
248
- if (!await hasExecutableClient(params.targetPath)) {
591
+ await assertOwnedServiceParentStable(ownedParent);
592
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
593
+ const winnerIdentity = await inspectServiceApp(operationTargetPath);
594
+ if (!winnerIdentity || !identitiesMatch(winnerIdentity, sourceIdentity)) {
249
595
  if (backupCreated) {
250
- await fs.rename(backupPath, params.targetPath);
251
- backupCreated = false;
596
+ if (!await pathExists$1(operationTargetPath)) {
597
+ await assertOwnedServiceParentStable(ownedParent);
598
+ await fs.rename(backupPath, operationTargetPath);
599
+ await assertOwnedServiceParentStable(ownedParent);
600
+ backupCreated = false;
601
+ }
252
602
  }
253
603
  throw error;
254
604
  }
255
605
  if (backupCreated) {
606
+ await assertOwnedServiceParentStable(ownedParent);
607
+ await assertNotSymlink(backupPath, "Computer Use service backup");
256
608
  await fs.rm(backupPath, {
257
609
  recursive: true,
258
610
  force: true
259
611
  });
612
+ await assertOwnedServiceParentStable(ownedParent);
260
613
  backupCreated = false;
261
614
  }
262
615
  return {
263
- status: "already_installed",
616
+ status: "already_current",
264
617
  changed: false,
265
618
  targetPath: params.targetPath,
266
- sourcePath
619
+ sourcePath,
620
+ sourceBuild: sourceIdentity.build
267
621
  };
268
622
  }
623
+ await assertNotSymlink(operationTargetPath, "Installed Computer Use service app");
624
+ const installedSnapshot = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
625
+ if (!installedSnapshot.identity || !identitiesMatch(installedSnapshot.identity, sourceIdentity)) {
626
+ if (filesystemSnapshotsMatch(installedSnapshot, stagedSnapshot)) {
627
+ await assertOwnedServiceParentStable(ownedParent);
628
+ await fs.rm(operationTargetPath, {
629
+ recursive: true,
630
+ force: true
631
+ });
632
+ await assertOwnedServiceParentStable(ownedParent);
633
+ if (backupCreated) {
634
+ await fs.rename(backupPath, operationTargetPath);
635
+ await assertOwnedServiceParentStable(ownedParent);
636
+ backupCreated = false;
637
+ }
638
+ }
639
+ throw new Error("Installed Computer Use service app failed post-install identity verification.");
640
+ }
269
641
  if (backupCreated) {
642
+ await assertOwnedServiceParentStable(ownedParent);
643
+ await assertNotSymlink(backupPath, "Computer Use service backup");
270
644
  await fs.rm(backupPath, {
271
645
  recursive: true,
272
646
  force: true
273
647
  });
648
+ await assertOwnedServiceParentStable(ownedParent);
274
649
  backupCreated = false;
275
650
  }
276
651
  return {
277
- status: "installed",
652
+ status: initialTarget.exists ? "refreshed" : "installed",
278
653
  changed: true,
279
654
  targetPath: params.targetPath,
280
- sourcePath
655
+ sourcePath,
656
+ sourceBuild: sourceIdentity.build,
657
+ ...initialTarget.identity ? { previousBuild: initialTarget.identity.build } : {}
281
658
  };
659
+ } catch (error) {
660
+ if (backupCreated && await ownedServiceParentIsStable(ownedParent) && !await pathExists$1(operationTargetPath)) {
661
+ await assertOwnedServiceParentStable(ownedParent);
662
+ await fs.rename(backupPath, operationTargetPath);
663
+ backupCreated = false;
664
+ }
665
+ throw error;
282
666
  } finally {
283
- await fs.rm(stagingRoot, {
667
+ if (await ownedServiceParentIsStable(ownedParent) && await directoryIdentityIsStable(stagingRootIdentity)) await fs.rm(stagingRoot, {
284
668
  recursive: true,
285
669
  force: true
286
670
  });
@@ -289,8 +673,43 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
289
673
  async function pathExists$1(filePath) {
290
674
  return await fs.lstat(filePath).then(() => true, () => false);
291
675
  }
292
- async function findUsableServiceApp(candidates) {
293
- for (const candidate of candidates) if (await hasExecutableClient(candidate)) return candidate;
676
+ async function findUsableServiceApp(candidates, inspectServiceApp) {
677
+ for (const candidate of candidates) {
678
+ const identity = await inspectServiceApp(candidate);
679
+ if (identity) return {
680
+ path: candidate,
681
+ identity
682
+ };
683
+ }
684
+ }
685
+ async function readServiceAppSnapshot(appPath, inspectServiceApp) {
686
+ if (!await pathExists$1(appPath)) return { exists: false };
687
+ return {
688
+ exists: true,
689
+ identity: await inspectServiceApp(appPath),
690
+ filesystemKey: await readServiceAppFilesystemKey(appPath)
691
+ };
692
+ }
693
+ async function readServiceAppFilesystemKey(appPath) {
694
+ const paths = [
695
+ appPath,
696
+ path.join(appPath, "Contents", "Info.plist"),
697
+ path.join(appPath, CLIENT_RELATIVE_PATH)
698
+ ];
699
+ return (await Promise.all(paths.map(async (entryPath) => await fs.stat(entryPath).then((stat) => `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}`, () => "missing")))).join("|");
700
+ }
701
+ function snapshotsMatch(left, right) {
702
+ if (left.exists !== right.exists) return false;
703
+ if (!left.exists) return true;
704
+ if (left.filesystemKey && right.filesystemKey && left.filesystemKey !== right.filesystemKey) return false;
705
+ if (left.identity || right.identity) return Boolean(left.identity && right.identity && identitiesMatch(left.identity, right.identity));
706
+ return left.filesystemKey !== void 0 && left.filesystemKey === right.filesystemKey;
707
+ }
708
+ function filesystemSnapshotsMatch(left, right) {
709
+ return Boolean(left.exists && right.exists && left.filesystemKey && right.filesystemKey && left.filesystemKey === right.filesystemKey);
710
+ }
711
+ function identitiesMatch(left, right) {
712
+ 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;
294
713
  }
295
714
  async function hasExecutableClient(appPath) {
296
715
  try {
@@ -300,6 +719,90 @@ async function hasExecutableClient(appPath) {
300
719
  return false;
301
720
  }
302
721
  }
722
+ async function inspectTrustedServiceApp(appPath) {
723
+ if (!await hasExecutableClient(appPath)) return;
724
+ const clientAppPath = path.join(appPath, CLIENT_APP_RELATIVE_PATH);
725
+ try {
726
+ await verifyTrustedBundle(appPath, SERVICE_BUNDLE_ID, true);
727
+ await verifyTrustedBundle(clientAppPath, CLIENT_BUNDLE_ID, false);
728
+ const [info, serviceSignature, clientSignature] = await Promise.all([
729
+ readBundleInfo(appPath),
730
+ readCodeSignature(appPath),
731
+ readCodeSignature(clientAppPath)
732
+ ]);
733
+ if (!info || serviceSignature.identifier !== SERVICE_BUNDLE_ID || serviceSignature.teamId !== OPENAI_TEAM_ID || clientSignature.identifier !== CLIENT_BUNDLE_ID || clientSignature.teamId !== OPENAI_TEAM_ID) return;
734
+ return {
735
+ bundleId: serviceSignature.identifier,
736
+ version: info.version,
737
+ build: info.build,
738
+ cdHash: serviceSignature.cdHash,
739
+ teamId: serviceSignature.teamId,
740
+ clientBundleId: clientSignature.identifier,
741
+ clientCdHash: clientSignature.cdHash,
742
+ clientTeamId: clientSignature.teamId
743
+ };
744
+ } catch {
745
+ return;
746
+ }
747
+ }
748
+ async function verifyTrustedBundle(appPath, bundleId, deep) {
749
+ const requirement = `anchor apple generic and certificate leaf[subject.OU] = "${OPENAI_TEAM_ID}" and identifier "${bundleId}"`;
750
+ await runExec("/usr/bin/codesign", [
751
+ "--verify",
752
+ "--strict",
753
+ ...deep ? ["--deep"] : [],
754
+ `-R=${requirement}`,
755
+ appPath
756
+ ], {
757
+ logOutput: false,
758
+ timeoutMs: INSPECT_TIMEOUT_MS
759
+ });
760
+ }
761
+ async function readBundleInfo(appPath) {
762
+ const result = await runExec("/usr/bin/plutil", [
763
+ "-convert",
764
+ "json",
765
+ "-o",
766
+ "-",
767
+ "--",
768
+ path.join(appPath, "Contents", "Info.plist")
769
+ ], {
770
+ logOutput: false,
771
+ timeoutMs: INSPECT_TIMEOUT_MS
772
+ });
773
+ const parsed = JSON.parse(result.stdout);
774
+ if (!isRecord(parsed)) return;
775
+ const version = parsed.CFBundleShortVersionString;
776
+ const build = parsed.CFBundleVersion;
777
+ return typeof version === "string" && version && typeof build === "string" && build ? {
778
+ version,
779
+ build
780
+ } : void 0;
781
+ }
782
+ async function readCodeSignature(appPath) {
783
+ const result = await runExec("/usr/bin/codesign", [
784
+ "-d",
785
+ "--verbose=4",
786
+ appPath
787
+ ], {
788
+ logOutput: false,
789
+ timeoutMs: INSPECT_TIMEOUT_MS
790
+ });
791
+ const output = `${result.stdout}\n${result.stderr}`;
792
+ const identifier = readCodeSignField(output, "Identifier");
793
+ const teamId = readCodeSignField(output, "TeamIdentifier");
794
+ const cdHash = readCodeSignField(output, "CDHash").toLowerCase();
795
+ if (!identifier || !teamId || !/^[a-f0-9]+$/.test(cdHash)) throw new Error(`Could not inspect the signed identity at ${appPath}.`);
796
+ return {
797
+ identifier,
798
+ teamId,
799
+ cdHash
800
+ };
801
+ }
802
+ function readCodeSignField(output, field) {
803
+ const prefix = `${field}=`;
804
+ return output.split(/\r?\n/u).find((candidate) => candidate.startsWith(prefix))?.slice(prefix.length).trim() ?? "";
805
+ }
303
806
  async function copyServiceAppWithDitto(sourcePath, targetPath) {
304
807
  await runExec("/usr/bin/ditto", [
305
808
  "--noqtn",
@@ -344,10 +847,12 @@ const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
344
847
  const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
345
848
  const CODEX_AUTH_JSON_FILENAME = "auth.json";
346
849
  const CODEX_HOME_DIRNAME = ".codex";
850
+ const MAX_COMPUTER_USE_ARTIFACT_OWNERS = 128;
851
+ const activeComputerUseArtifactReconciliations = /* @__PURE__ */ new Map();
347
852
  const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
348
853
  async function bridgeCodexAppServerStartOptions(params) {
349
854
  if (params.startOptions.transport !== "stdio") return params.startOptions;
350
- const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
855
+ const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir);
351
856
  if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
352
857
  if (params.authProfileId === null) return scopeStartOptions();
353
858
  const store = resolveCodexAppServerAuthProfileStore({
@@ -447,7 +952,7 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
447
952
  const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
448
953
  const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
449
954
  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;
450
- const chatgptAccountId = resolveExplicitChatgptAccountId(credential);
955
+ const chatgptAccountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : void 0;
451
956
  return {
452
957
  loginParams,
453
958
  secretFreeCacheKey,
@@ -458,7 +963,8 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
458
963
  async function resolveCodexAppServerPreparedAuthHandoff(params) {
459
964
  const usesNativeHome = params.homeScope === "user";
460
965
  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.");
461
- if (params.authRequirement === "api-key" && !usesNativeHome) {
966
+ if (usesNativeHome) return { nativeAuthProfile: true };
967
+ if (params.authRequirement === "api-key") {
462
968
  const apiKey = params.resolvedApiKey?.trim();
463
969
  if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
464
970
  return {
@@ -476,7 +982,7 @@ async function resolveCodexAppServerPreparedAuthHandoff(params) {
476
982
  agentDir: params.agentDir,
477
983
  config: params.config
478
984
  });
479
- if (usesNativeHome || params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
985
+ if (params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
480
986
  authProfileId,
481
987
  nativeAuthProfile
482
988
  };
@@ -575,26 +1081,10 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
575
1081
  hash.update(apiKey);
576
1082
  return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
577
1083
  }
578
- async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
1084
+ async function withCodexHomeEnvironment(startOptions, agentDir) {
579
1085
  const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
580
1086
  const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
581
1087
  await fs.mkdir(codexHome, { recursive: true });
582
- const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
583
- await ensureCodexComputerUseSharedPluginCache({
584
- codexHome,
585
- config: computerUseConfig
586
- });
587
- if (computerUseConfig.enabled && computerUseConfig.autoInstall) try {
588
- await ensureCodexComputerUseServiceApp({
589
- codexHome,
590
- appServerCommand: startOptions.command
591
- });
592
- } catch (error) {
593
- throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
594
- cause: error,
595
- scope: "harness"
596
- });
597
- }
598
1088
  if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
599
1089
  const nextStartOptions = {
600
1090
  ...startOptions,
@@ -609,6 +1099,138 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
609
1099
  else delete nextStartOptions.clearEnv;
610
1100
  return nextStartOptions;
611
1101
  }
1102
+ /** Reconciles Computer Use artifacts for the exact managed command about to start. */
1103
+ async function reconcileCodexComputerUseStartArtifacts(params) {
1104
+ if (params.startOptions.transport !== "stdio") return;
1105
+ const codexHome = resolveCodexAppServerLocalHomeDir(params.startOptions, params.agentDir);
1106
+ const key = path.resolve(codexHome);
1107
+ let owner = activeComputerUseArtifactReconciliations.get(key);
1108
+ if (!owner) {
1109
+ owner = {
1110
+ active: 0,
1111
+ tail: Promise.resolve()
1112
+ };
1113
+ activeComputerUseArtifactReconciliations.set(key, owner);
1114
+ } else {
1115
+ activeComputerUseArtifactReconciliations.delete(key);
1116
+ activeComputerUseArtifactReconciliations.set(key, owner);
1117
+ }
1118
+ owner.active += 1;
1119
+ const epoch = params.desktopGeneration?.epoch;
1120
+ if (epoch !== void 0 && (owner.latestEpoch === void 0 || epoch > owner.latestEpoch)) owner.latestEpoch = epoch;
1121
+ const assertCurrent = () => {
1122
+ params.assertCurrent?.();
1123
+ if (epoch !== void 0 && owner.latestEpoch !== epoch) throw new Error("Codex Computer Use artifact reconciliation was superseded.");
1124
+ };
1125
+ const operation = owner.tail.catch(() => void 0).then(async () => {
1126
+ assertCurrent();
1127
+ const appliedCacheBinding = await reconcileCodexComputerUseStartArtifactsOnce({
1128
+ ...params,
1129
+ codexHome,
1130
+ assertCurrent,
1131
+ previousCacheBinding: owner.appliedCacheBinding
1132
+ });
1133
+ assertCurrent();
1134
+ owner.appliedCacheBinding = appliedCacheBinding;
1135
+ });
1136
+ const settled = operation.then(() => void 0, () => void 0);
1137
+ owner.tail = settled;
1138
+ try {
1139
+ await operation;
1140
+ } finally {
1141
+ owner.active = Math.max(0, owner.active - 1);
1142
+ if (owner.active === 0 && owner.latestEpoch === void 0 && activeComputerUseArtifactReconciliations.get(key) === owner && owner.tail === settled) activeComputerUseArtifactReconciliations.delete(key);
1143
+ pruneComputerUseArtifactOwners();
1144
+ }
1145
+ }
1146
+ async function reconcileCodexComputerUseStartArtifactsOnce(params) {
1147
+ const codexHome = params.codexHome;
1148
+ const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
1149
+ const ownsIsolatedCodexHome = params.ownsIsolatedCodexHome ?? (params.startOptions.homeScope !== "user" && !params.startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim());
1150
+ const shouldProvisionComputerUse = computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome;
1151
+ if (shouldProvisionComputerUse) await ensureOwnedCodexHome(codexHome, params.agentDir);
1152
+ else await fs.mkdir(codexHome, { recursive: true });
1153
+ const desktopCandidates = resolveMacOSDesktopCodexAppPathCandidates();
1154
+ const exactDesktopCandidate = desktopCandidates.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(params.startOptions.command));
1155
+ const usesManagedBundledMarketplace = !computerUseConfig.marketplaceSource && !computerUseConfig.marketplacePath && !computerUseConfig.marketplaceName;
1156
+ const needsBundledMarketplace = usesManagedBundledMarketplace || computerUseConfig.pluginCacheMode === "shared" && !computerUseConfig.marketplaceName && !computerUseConfig.marketplacePath;
1157
+ const artifactCandidate = shouldProvisionComputerUse ? await resolveCompleteComputerUseArtifactCandidate({
1158
+ candidates: exactDesktopCandidate ? [exactDesktopCandidate] : desktopCandidates,
1159
+ needsBundledMarketplace
1160
+ }) : exactDesktopCandidate;
1161
+ params.assertCurrent();
1162
+ if (shouldProvisionComputerUse) {
1163
+ if (desktopCandidates.length > 0 && !artifactCandidate) throw new CodexComputerUseCandidateArtifactsUnavailableError();
1164
+ try {
1165
+ const marketplacePath = usesManagedBundledMarketplace ? await ensureCodexManagedBundledMarketplace({
1166
+ codexHome,
1167
+ ownershipRoot: params.agentDir,
1168
+ ...artifactCandidate ? {
1169
+ appServerCommand: artifactCandidate.appServerCommandPath,
1170
+ candidates: [artifactCandidate],
1171
+ ownershipCandidates: desktopCandidates
1172
+ } : {},
1173
+ assertCurrent: params.assertCurrent
1174
+ }) : void 0;
1175
+ params.assertCurrent();
1176
+ if (usesManagedBundledMarketplace && desktopCandidates.length > 0 && !marketplacePath) throw new CodexComputerUseCandidateArtifactsUnavailableError();
1177
+ const service = await ensureCodexComputerUseServiceApp({
1178
+ codexHome,
1179
+ ownershipRoot: params.agentDir,
1180
+ ...artifactCandidate ? {
1181
+ appServerCommand: artifactCandidate.appServerCommandPath,
1182
+ sourceAppCandidates: artifactCandidate.computerUseServiceAppPaths
1183
+ } : {},
1184
+ assertCurrent: params.assertCurrent
1185
+ });
1186
+ params.assertCurrent();
1187
+ if (desktopCandidates.length > 0 && service.status === "source_missing") throw new CodexComputerUseCandidateArtifactsUnavailableError();
1188
+ } catch (error) {
1189
+ params.assertCurrent();
1190
+ if (error instanceof CodexComputerUseCandidateArtifactsUnavailableError) throw error;
1191
+ throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
1192
+ cause: error,
1193
+ scope: "harness"
1194
+ });
1195
+ }
1196
+ }
1197
+ params.assertCurrent();
1198
+ const cacheBinding = [
1199
+ params.desktopGeneration?.epoch ?? "manual",
1200
+ artifactCandidate?.bundledMarketplacePath ?? "default",
1201
+ computerUseConfig.pluginName
1202
+ ].join("\0");
1203
+ const cache = await ensureCodexComputerUseSharedPluginCache({
1204
+ codexHome,
1205
+ config: computerUseConfig,
1206
+ ...ownsIsolatedCodexHome ? { ownershipRoot: params.agentDir } : {},
1207
+ ...artifactCandidate ? { bundledMarketplacePath: artifactCandidate.bundledMarketplacePath } : {},
1208
+ assertCurrent: params.assertCurrent,
1209
+ forceRefresh: params.forceCacheRefresh === true || params.previousCacheBinding !== cacheBinding
1210
+ });
1211
+ params.assertCurrent();
1212
+ return cache.status === "shared" ? cacheBinding : void 0;
1213
+ }
1214
+ async function resolveCompleteComputerUseArtifactCandidate(params) {
1215
+ for (const candidate of params.candidates) {
1216
+ if (params.needsBundledMarketplace && !await resolveCodexManagedBundledMarketplaceSource({ candidates: [candidate] })) continue;
1217
+ if (await resolveCodexComputerUseServiceAppSourcePath({ sourceAppCandidates: candidate.computerUseServiceAppPaths })) return candidate;
1218
+ }
1219
+ }
1220
+ function pruneComputerUseArtifactOwners() {
1221
+ while (activeComputerUseArtifactReconciliations.size > MAX_COMPUTER_USE_ARTIFACT_OWNERS) {
1222
+ const inactive = [...activeComputerUseArtifactReconciliations].find(([, owner]) => owner.active === 0);
1223
+ if (!inactive) return;
1224
+ activeComputerUseArtifactReconciliations.delete(inactive[0]);
1225
+ }
1226
+ }
1227
+ var CodexComputerUseCandidateArtifactsUnavailableError = class extends Error {
1228
+ constructor() {
1229
+ super("The selected Codex desktop app does not contain complete Computer Use artifacts.");
1230
+ this.code = "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
1231
+ this.name = "CodexComputerUseCandidateArtifactsUnavailableError";
1232
+ }
1233
+ };
612
1234
  function withoutClearedCodexHomeEnv(clearEnv) {
613
1235
  if (!clearEnv) return;
614
1236
  const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
@@ -696,11 +1318,24 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
696
1318
  });
697
1319
  }
698
1320
  async function refreshCodexAppServerAuthTokens(params) {
1321
+ const previousAccountId = params.previousAccountId?.trim();
1322
+ if (previousAccountId) {
1323
+ const store = resolveCodexAppServerAuthProfileStore(params);
1324
+ const profileId = resolveCodexAppServerAuthProfileId({
1325
+ authProfileId: params.authProfileId,
1326
+ store,
1327
+ config: params.config
1328
+ });
1329
+ const credential = profileId ? store.profiles[profileId] : void 0;
1330
+ const selectedAccountId = credential ? resolveExplicitChatgptAccountId(credential) ?? (credential.type === "oauth" ? resolveOpenAICodexAuthIdentity({ access: credential.access }).accountId : void 0) : void 0;
1331
+ if (selectedAccountId && selectedAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh.");
1332
+ }
699
1333
  const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
700
1334
  ...params,
701
1335
  forceOAuthRefresh: true
702
1336
  });
703
1337
  if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
1338
+ if (previousAccountId && loginParams.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh.");
704
1339
  return {
705
1340
  accessToken: loginParams.accessToken,
706
1341
  chatgptAccountId: loginParams.chatgptAccountId,
@@ -938,10 +1573,15 @@ function readFirstNonEmptyEnvEntry(env, keys) {
938
1573
  }
939
1574
  }
940
1575
  function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
1576
+ const storedAccountId = resolveExplicitChatgptAccountId(credential);
1577
+ const tokenAccountId = resolveOpenAICodexAuthIdentity({ access: accessToken }).accountId;
1578
+ 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.`);
1579
+ const chatgptAccountId = storedAccountId ?? tokenAccountId;
1580
+ if (!chatgptAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is missing its ChatGPT account ID. Sign in again before retrying.`);
941
1581
  return {
942
1582
  type: "chatgptAuthTokens",
943
1583
  accessToken,
944
- chatgptAccountId: resolveChatgptAccountId(profileId, credential),
1584
+ chatgptAccountId,
945
1585
  chatgptPlanType: resolveChatgptPlanType(credential)
946
1586
  };
947
1587
  }
@@ -1061,12 +1701,46 @@ function readLimitId(snapshot) {
1061
1701
  return typeof value === "string" && value.trim() ? value.trim() : "codex";
1062
1702
  }
1063
1703
  //#endregion
1704
+ //#region extensions/codex/src/app-server/timeout.ts
1705
+ /**
1706
+ * Thin Codex app-server timeout adapter around OpenClaw's shared security
1707
+ * runtime timeout helper.
1708
+ */
1709
+ function resolveAbortError(signal) {
1710
+ return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
1711
+ }
1712
+ /** Awaits a promise with a Codex-specific timeout error message. */
1713
+ async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
1714
+ return await withTimeout(promise, timeoutMs, {
1715
+ message: timeoutMessage,
1716
+ ...createError ? { createError } : {}
1717
+ });
1718
+ }
1719
+ /** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
1720
+ async function withAbortableTimeout(params) {
1721
+ const signal = params.signal;
1722
+ if (signal?.aborted) throw resolveAbortError(signal);
1723
+ let removeAbortListener;
1724
+ const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
1725
+ const onAbort = () => reject(resolveAbortError(signal));
1726
+ signal.addEventListener("abort", onAbort, { once: true });
1727
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
1728
+ })]) : params.promise;
1729
+ try {
1730
+ return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
1731
+ } finally {
1732
+ removeAbortListener?.();
1733
+ }
1734
+ }
1735
+ //#endregion
1064
1736
  //#region extensions/codex/src/app-server/client-runtime.ts
1065
1737
  /** Client-scoped Codex auth and account observers. */
1066
1738
  /** Match Codex's native grace window without retaining inactive conversations indefinitely. */
1067
1739
  const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 30 * 6e4;
1068
1740
  /** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
1069
1741
  const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
1742
+ /** Return a deterministic error before Codex cancels its ten-second external-auth request. */
1743
+ const CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS = 9e3;
1070
1744
  const configuredClients = /* @__PURE__ */ new WeakMap();
1071
1745
  const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
1072
1746
  const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
@@ -1080,7 +1754,10 @@ function ensureCodexAppServerClientRuntime(client, context) {
1080
1754
  const existing = configuredClients.get(client);
1081
1755
  if (existing) {
1082
1756
  if (existing.closed) return;
1083
- existing.context = context;
1757
+ existing.context = {
1758
+ ...existing.context,
1759
+ config: context.config
1760
+ };
1084
1761
  return;
1085
1762
  }
1086
1763
  const runtime = {
@@ -1105,12 +1782,16 @@ function ensureCodexAppServerClientRuntime(client, context) {
1105
1782
  client.addRequestHandler(async (request) => {
1106
1783
  if (request.method !== "account/chatgptAuthTokens/refresh") return;
1107
1784
  if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
1108
- return await refreshCodexAppServerAuthTokens({
1785
+ const previousAccountId = isJsonObject(request.params) && typeof request.params.previousAccountId === "string" ? request.params.previousAccountId.trim() || void 0 : void 0;
1786
+ const tokens = await withTimeout$1(refreshCodexAppServerAuthTokens({
1109
1787
  agentDir: runtime.context.agentDir,
1110
1788
  authProfileId: runtime.context.authProfileId,
1789
+ ...previousAccountId ? { previousAccountId } : {},
1111
1790
  ...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
1112
1791
  config: runtime.context.config
1113
- });
1792
+ }), CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS, "Codex app-server ChatGPT token refresh timed out before its external-auth deadline");
1793
+ if (previousAccountId && tokens.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh.");
1794
+ return { ...tokens };
1114
1795
  });
1115
1796
  client.addNotificationHandler((notification) => {
1116
1797
  if (notification.method === "account/rateLimits/updated") {
@@ -1362,6 +2043,15 @@ function protectCodexAppServerLiveThread(client, threadId) {
1362
2043
  };
1363
2044
  }
1364
2045
  //#endregion
2046
+ //#region extensions/codex/src/app-server/elicitation-response.ts
2047
+ function createCodexElicitationResponse(action, content = null, meta = null) {
2048
+ return {
2049
+ action,
2050
+ content,
2051
+ _meta: meta
2052
+ };
2053
+ }
2054
+ //#endregion
1365
2055
  //#region extensions/codex/src/app-server/rpc-error.ts
1366
2056
  /** RPC error wrapper that preserves app-server error code and data. */
1367
2057
  var CodexAppServerRpcError = class extends Error {
@@ -1558,11 +2248,273 @@ function websocketFrameToText(data) {
1558
2248
  return Buffer.from(data).toString("utf8");
1559
2249
  }
1560
2250
  //#endregion
2251
+ //#region extensions/codex/src/app-server/transport-process-containment.ts
2252
+ const PROCESS_COLUMNS = "pid=,ppid=,pgid=,stat=,lstart=";
2253
+ const MAX_CONTAINED_PROCESSES = 512;
2254
+ const MAX_PROCESS_CONTAINMENT_MS = 2e3;
2255
+ const MAX_PROCESS_QUIESCE_PASSES = 16;
2256
+ const PROCESS_INSPECTION_MAX_BYTES = 8 * 1024 * 1024;
2257
+ async function terminateCodexAppServerDescendants(child) {
2258
+ const rootPid = child.pid;
2259
+ if (process.platform === "win32" || !rootPid || !child.kill || hasExited(child)) return;
2260
+ const deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS;
2261
+ const snapshot = await readProcessSnapshot(deadline);
2262
+ if (!snapshot || Date.now() >= deadline) return;
2263
+ const root = snapshot.find((row) => row.pid === rootPid);
2264
+ if (!root || !isSameLiveRoot(root, root)) return;
2265
+ const initialDescendants = collectDescendants(snapshot, [rootPid]);
2266
+ if (initialDescendants.length > MAX_CONTAINED_PROCESSES) return;
2267
+ const stoppedDescendants = /* @__PURE__ */ new Map();
2268
+ if (!await signalSameRoot(root, "SIGSTOP", deadline)) return;
2269
+ let resumeRootOnUnwind = true;
2270
+ try {
2271
+ const descendants = await quiesceDescendants(root, initialDescendants, stoppedDescendants, deadline);
2272
+ if (!descendants) return;
2273
+ for (const descendant of descendants.toReversed()) {
2274
+ if (Date.now() >= deadline) return;
2275
+ if (!descendant.state.startsWith("Z")) {
2276
+ if (!await signalSameProcess(descendant, "SIGKILL", deadline) || Date.now() >= deadline) return;
2277
+ }
2278
+ }
2279
+ resumeRootOnUnwind = false;
2280
+ let resumed = false;
2281
+ return () => {
2282
+ if (resumed) return;
2283
+ resumed = true;
2284
+ resumeTransportRoot(child, root, false);
2285
+ };
2286
+ } finally {
2287
+ if (resumeRootOnUnwind) {
2288
+ for (const descendant of stoppedDescendants.values()) signalProcess(descendant.pid, "SIGCONT");
2289
+ resumeTransportRoot(child, root, true);
2290
+ }
2291
+ }
2292
+ }
2293
+ async function quiesceDescendants(root, initialDescendants, stopped, deadline) {
2294
+ const provenByPid = new Map(initialDescendants.map((descendant) => [descendant.pid, descendant]));
2295
+ const stopFailures = /* @__PURE__ */ new Map();
2296
+ for (let pass = 0; pass < MAX_PROCESS_QUIESCE_PASSES; pass += 1) {
2297
+ if (Date.now() >= deadline) return;
2298
+ const snapshot = await readProcessSnapshot(deadline);
2299
+ if (!snapshot || Date.now() >= deadline) return;
2300
+ const currentRoot = snapshot.find((row) => row.pid === root.pid);
2301
+ if (!currentRoot || !isSameLiveRoot(currentRoot, root)) return;
2302
+ if (!isSameLiveRoot(currentRoot, root, true)) {
2303
+ if (!await signalSameRoot(root, "SIGSTOP", deadline) || Date.now() >= deadline) return;
2304
+ continue;
2305
+ }
2306
+ const snapshotByPid = new Map(snapshot.map((process) => [process.pid, process]));
2307
+ const liveProven = [];
2308
+ for (const proven of provenByPid.values()) {
2309
+ const current = snapshotByPid.get(proven.pid);
2310
+ if (!current) {
2311
+ provenByPid.delete(proven.pid);
2312
+ stopped.delete(identityKey(proven));
2313
+ continue;
2314
+ }
2315
+ if (!hasSameIdentity(proven, current)) return;
2316
+ provenByPid.set(current.pid, current);
2317
+ const key = identityKey(current);
2318
+ if (stopped.has(key)) stopped.set(key, current);
2319
+ liveProven.push(current);
2320
+ }
2321
+ const descendants = collectDescendants(snapshot, [root.pid, ...liveProven.map(({ pid }) => pid)]);
2322
+ for (const descendant of descendants) {
2323
+ const proven = provenByPid.get(descendant.pid);
2324
+ if (proven && !hasSameIdentity(proven, descendant)) return;
2325
+ provenByPid.set(descendant.pid, descendant);
2326
+ }
2327
+ if (provenByPid.size > MAX_CONTAINED_PROCESSES) return;
2328
+ const quiescenceTargets = new Map(liveProven.map((process) => [process.pid, process]));
2329
+ for (const descendant of descendants) quiescenceTargets.set(descendant.pid, descendant);
2330
+ let allStopped = true;
2331
+ for (const descendant of quiescenceTargets.values()) {
2332
+ if (Date.now() >= deadline) return;
2333
+ if (isStoppedState(descendant.state)) continue;
2334
+ const stopQueued = await signalSameProcess(descendant, "SIGSTOP", deadline);
2335
+ if (Date.now() >= deadline) return;
2336
+ if (stopQueued) {
2337
+ stopFailures.delete(identityKey(descendant));
2338
+ stopped.set(identityKey(descendant), descendant);
2339
+ } else {
2340
+ const key = identityKey(descendant);
2341
+ const failures = (stopFailures.get(key) ?? 0) + 1;
2342
+ if (failures >= 2) return;
2343
+ stopFailures.set(key, failures);
2344
+ }
2345
+ if (!isUninterruptibleState(descendant.state) || !stopQueued) allStopped = false;
2346
+ }
2347
+ if (allStopped) return [...provenByPid.values()];
2348
+ }
2349
+ }
2350
+ async function readProcessSnapshot(deadline) {
2351
+ return await readProcesses(["-axo", PROCESS_COLUMNS], deadline);
2352
+ }
2353
+ async function readProcess(pid, deadline) {
2354
+ return (await readProcesses([
2355
+ "-o",
2356
+ PROCESS_COLUMNS,
2357
+ "-p",
2358
+ String(pid)
2359
+ ], deadline))?.find((row) => row.pid === pid);
2360
+ }
2361
+ async function readProcesses(args, deadline) {
2362
+ const remainingMs = deadline - Date.now();
2363
+ if (remainingMs <= 0) return;
2364
+ return await new Promise((resolve) => {
2365
+ let settled = false;
2366
+ const settle = (processes) => {
2367
+ if (settled) return;
2368
+ settled = true;
2369
+ clearTimeout(timer);
2370
+ resolve(processes);
2371
+ };
2372
+ const inspector = execFile("ps", args, {
2373
+ encoding: "utf8",
2374
+ maxBuffer: PROCESS_INSPECTION_MAX_BYTES
2375
+ }, (error, stdout) => {
2376
+ settle(error ? void 0 : parseProcesses(stdout));
2377
+ });
2378
+ const timer = setTimeout(() => {
2379
+ settle(void 0);
2380
+ inspector.stdout?.destroy();
2381
+ inspector.stderr?.destroy();
2382
+ inspector.kill("SIGKILL");
2383
+ inspector.unref();
2384
+ }, Math.max(1, remainingMs));
2385
+ timer.unref?.();
2386
+ });
2387
+ }
2388
+ function parseProcesses(output) {
2389
+ const rows = [];
2390
+ for (const line of output.split("\n")) {
2391
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
2392
+ if (!match) continue;
2393
+ const pid = Number(match[1] ?? "");
2394
+ const ppid = Number(match[2] ?? "");
2395
+ const pgid = Number(match[3] ?? "");
2396
+ const startedAt = (match[5] ?? "").trim().replace(/\s+/g, " ");
2397
+ if (![
2398
+ pid,
2399
+ ppid,
2400
+ pgid
2401
+ ].every(Number.isSafeInteger) || pid <= 0 || ppid < 0 || pgid <= 0 || !startedAt) continue;
2402
+ rows.push({
2403
+ pid,
2404
+ ppid,
2405
+ pgid,
2406
+ state: match[4] ?? "",
2407
+ startedAt
2408
+ });
2409
+ }
2410
+ return rows;
2411
+ }
2412
+ function collectDescendants(snapshot, rootPids) {
2413
+ const childrenByParent = /* @__PURE__ */ new Map();
2414
+ for (const row of snapshot) {
2415
+ const children = childrenByParent.get(row.ppid) ?? [];
2416
+ children.push(row);
2417
+ childrenByParent.set(row.ppid, children);
2418
+ }
2419
+ const descendants = [];
2420
+ const pending = [...new Set(rootPids)];
2421
+ const seen = new Set(pending);
2422
+ for (const parentPid of pending) for (const child of childrenByParent.get(parentPid) ?? []) {
2423
+ if (seen.has(child.pid)) continue;
2424
+ seen.add(child.pid);
2425
+ descendants.push(child);
2426
+ pending.push(child.pid);
2427
+ }
2428
+ return descendants;
2429
+ }
2430
+ function isStoppedState(state) {
2431
+ return state.startsWith("T") || state.startsWith("t") || state.startsWith("Z");
2432
+ }
2433
+ function isQuiescedState(state) {
2434
+ return isStoppedState(state) || isUninterruptibleState(state);
2435
+ }
2436
+ function isUninterruptibleState(state) {
2437
+ return state.startsWith("D") || state.startsWith("U");
2438
+ }
2439
+ function isSameLiveProcess(current, expected) {
2440
+ return current.pgid === expected.pgid && !current.state.startsWith("Z") && hasSameIdentity(current, expected);
2441
+ }
2442
+ function isSameLiveRoot(current, expected, requireStopped = false) {
2443
+ return current.ppid === process.pid && (!requireStopped || isQuiescedState(current.state)) && isSameLiveProcess(current, expected);
2444
+ }
2445
+ async function signalSameRoot(root, signal, deadline) {
2446
+ const current = await readProcess(root.pid, deadline);
2447
+ return Boolean(current && isSameLiveRoot(current, root) && signalProcess(current.pid, signal));
2448
+ }
2449
+ function resumeTransportRoot(child, root, allowSynchronousPidFallback) {
2450
+ try {
2451
+ if (child.kill) {
2452
+ child.kill("SIGCONT");
2453
+ return;
2454
+ }
2455
+ } catch {
2456
+ if (!allowSynchronousPidFallback) return;
2457
+ }
2458
+ if (allowSynchronousPidFallback) signalProcess(root.pid, "SIGCONT");
2459
+ }
2460
+ async function signalSameProcess(expected, signal, deadline) {
2461
+ const current = await readProcess(expected.pid, deadline);
2462
+ return Boolean(current && isSameLiveProcess(current, expected) && signalProcess(current.pid, signal));
2463
+ }
2464
+ function hasSameIdentity(left, right) {
2465
+ return identityKey(left) === identityKey(right);
2466
+ }
2467
+ function identityKey(row) {
2468
+ return `${row.pid}\0${row.startedAt}`;
2469
+ }
2470
+ function hasExited(child) {
2471
+ return child.exitCode != null || child.signalCode != null;
2472
+ }
2473
+ function signalProcess(pid, signal) {
2474
+ try {
2475
+ process.kill(pid, signal);
2476
+ return true;
2477
+ } catch {
2478
+ return false;
2479
+ }
2480
+ }
2481
+ //#endregion
1561
2482
  //#region extensions/codex/src/app-server/transport.ts
2483
+ /**
2484
+ * Shared transport lifecycle helpers for stdio and WebSocket Codex app-server
2485
+ * connections.
2486
+ */
2487
+ const CODEX_APP_SERVER_TRANSPORT_CLOSES = /* @__PURE__ */ new WeakMap();
1562
2488
  /** Starts graceful transport shutdown and schedules a force kill fallback. */
1563
2489
  function closeCodexAppServerTransport(child, options = {}) {
1564
- child.stdin.end?.();
1565
- child.stdin.destroy?.();
2490
+ beginCodexAppServerTransportClose(child, options);
2491
+ }
2492
+ function beginCodexAppServerTransportClose(child, options) {
2493
+ const current = CODEX_APP_SERVER_TRANSPORT_CLOSES.get(child);
2494
+ if (current) return current;
2495
+ if (process.platform === "win32" || !child.pid || !child.kill || hasCodexAppServerTransportExited(child)) {
2496
+ finishCodexAppServerTransportClose(child, options);
2497
+ const completed = Promise.resolve();
2498
+ CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, completed);
2499
+ return completed;
2500
+ }
2501
+ const closing = (async () => {
2502
+ let resumeRoot;
2503
+ try {
2504
+ resumeRoot = await terminateCodexAppServerDescendants(child);
2505
+ } catch {
2506
+ resumeRoot = void 0;
2507
+ }
2508
+ try {
2509
+ finishCodexAppServerTransportClose(child, options, resumeRoot);
2510
+ } catch {
2511
+ signalCodexAppServerTransport(child, "SIGKILL");
2512
+ }
2513
+ })();
2514
+ CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, closing);
2515
+ return closing;
2516
+ }
2517
+ function finishCodexAppServerTransportClose(child, options, resumeRoot) {
1566
2518
  const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
1567
2519
  const forceKill = setTimeout(() => {
1568
2520
  if (hasCodexAppServerTransportExited(child)) return;
@@ -1574,6 +2526,12 @@ function closeCodexAppServerTransport(child, options = {}) {
1574
2526
  child.stdout.destroy?.();
1575
2527
  child.stderr.destroy?.();
1576
2528
  });
2529
+ try {
2530
+ child.stdin.end?.();
2531
+ child.stdin.destroy?.();
2532
+ } finally {
2533
+ resumeRoot?.();
2534
+ }
1577
2535
  child.unref?.();
1578
2536
  child.stdout.unref?.();
1579
2537
  child.stderr.unref?.();
@@ -1581,7 +2539,7 @@ function closeCodexAppServerTransport(child, options = {}) {
1581
2539
  }
1582
2540
  /** Closes a transport and waits briefly for an exit event. */
1583
2541
  async function closeCodexAppServerTransportAndWait(child, options = {}) {
1584
- if (!hasCodexAppServerTransportExited(child)) closeCodexAppServerTransport(child, options);
2542
+ if (!hasCodexAppServerTransportExited(child)) await beginCodexAppServerTransportClose(child, options);
1585
2543
  return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
1586
2544
  }
1587
2545
  function hasCodexAppServerTransportExited(child) {
@@ -1614,15 +2572,6 @@ function signalCodexAppServerTransport(child, signal) {
1614
2572
  child.kill?.(signal);
1615
2573
  }
1616
2574
  //#endregion
1617
- //#region extensions/codex/src/app-server/version.ts
1618
- /**
1619
- * Version and package pins for the managed Codex app-server runtime.
1620
- */
1621
- /** Exact Codex app-server version shipped and supported by the OpenClaw Codex bridge. */
1622
- const CODEX_APP_SERVER_VERSION = "0.147.0";
1623
- /** npm package name for the managed Codex app-server binary. */
1624
- const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
1625
- //#endregion
1626
2575
  //#region extensions/codex/src/app-server/client.ts
1627
2576
  /**
1628
2577
  * JSON-RPC client for Codex app-server transports, including request/response
@@ -1636,6 +2585,7 @@ const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
1636
2585
  const CODEX_APP_SERVER_OVERLOADED_ERROR_CODE = -32001;
1637
2586
  const CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES = 3;
1638
2587
  const CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS = 50;
2588
+ const CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX = 32;
1639
2589
  const CODEX_APP_SERVER_CLIENT_INSTANCE_IDS = /* @__PURE__ */ new WeakMap();
1640
2590
  const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1641
2591
  /** Process-local generation fence for bindings tied to one app-server client instance. */
@@ -1704,6 +2654,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1704
2654
  this.pending = /* @__PURE__ */ new Map();
1705
2655
  this.requestHandlers = /* @__PURE__ */ new Set();
1706
2656
  this.notificationHandlers = /* @__PURE__ */ new Set();
2657
+ this.pendingStartupWarnings = [];
1707
2658
  this.closeHandlers = /* @__PURE__ */ new Set();
1708
2659
  this.nextId = 1;
1709
2660
  this.initialized = false;
@@ -1756,7 +2707,14 @@ var CodexAppServerClient = class CodexAppServerClient {
1756
2707
  title: "OpenClaw",
1757
2708
  version: OPENCLAW_VERSION
1758
2709
  },
1759
- capabilities: { experimentalApi: true }
2710
+ capabilities: {
2711
+ experimentalApi: true,
2712
+ extensions: {
2713
+ "openai/standard-form-input": {},
2714
+ "openai/form": {},
2715
+ "io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app"] }
2716
+ }
2717
+ }
1760
2718
  });
1761
2719
  this.serverVersion = assertSupportedCodexAppServerVersion(response);
1762
2720
  this.runtimeIdentity = buildCodexAppServerRuntimeIdentity(response, this.serverVersion);
@@ -1967,6 +2925,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1967
2925
  /** Registers a notification handler and returns its disposer. */
1968
2926
  addNotificationHandler(handler) {
1969
2927
  this.notificationHandlers.add(handler);
2928
+ for (const notification of this.pendingStartupWarnings.splice(0)) this.handleNotification(notification);
1970
2929
  return () => this.notificationHandlers.delete(handler);
1971
2930
  }
1972
2931
  /** Registers a close handler and returns its disposer. */
@@ -1974,6 +2933,16 @@ var CodexAppServerClient = class CodexAppServerClient {
1974
2933
  this.closeHandlers.add(handler);
1975
2934
  return () => this.closeHandlers.delete(handler);
1976
2935
  }
2936
+ /** Registers a handler for physical transport exit and returns its disposer. */
2937
+ addTransportExitHandler(handler) {
2938
+ if (this.transportExited) {
2939
+ handler(this);
2940
+ return () => void 0;
2941
+ }
2942
+ const onExit = () => handler(this);
2943
+ this.child.once("exit", onExit);
2944
+ return () => this.child.off?.("exit", onExit);
2945
+ }
1977
2946
  /** Closes the transport without waiting for process/socket shutdown. */
1978
2947
  close() {
1979
2948
  if (!this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"))) return;
@@ -2164,6 +3133,11 @@ var CodexAppServerClient = class CodexAppServerClient {
2164
3133
  }
2165
3134
  }
2166
3135
  handleNotification(notification) {
3136
+ if (this.notificationHandlers.size === 0 && notification.method === "configWarning") {
3137
+ if (this.pendingStartupWarnings.length === CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX) this.pendingStartupWarnings.shift();
3138
+ this.pendingStartupWarnings.push(notification);
3139
+ return;
3140
+ }
2167
3141
  for (const handler of this.notificationHandlers) try {
2168
3142
  Promise.resolve(handler(notification)).catch((error) => {
2169
3143
  embeddedAgentLog.warn("codex app-server notification handler failed", { error });
@@ -2206,7 +3180,7 @@ function defaultServerRequestResponse(request) {
2206
3180
  scope: "turn"
2207
3181
  };
2208
3182
  if (request.method === "item/tool/requestUserInput") return { answers: {} };
2209
- if (request.method === "mcpServer/elicitation/request") return { action: "decline" };
3183
+ if (request.method === "mcpServer/elicitation/request") return createCodexElicitationResponse("decline", null, { message: "OpenClaw has no interactive handler for this elicitation." });
2210
3184
  return {};
2211
3185
  }
2212
3186
  function stringifyCodexAppServerMessage(message) {
@@ -2226,14 +3200,20 @@ function timeoutServerRequestResponse(request) {
2226
3200
  var CodexAppServerVersionError = class extends Error {
2227
3201
  constructor(detectedVersion) {
2228
3202
  const detected = detectedVersion ? `detected ${detectedVersion}` : "OpenClaw could not determine the running Codex version";
2229
- super(`Codex app-server ${CODEX_APP_SERVER_VERSION} is required, but ${detected}. Update the configured Codex app-server binary, or remove custom command overrides to use the managed binary.`);
3203
+ super(`Codex app-server ${MIN_SUPPORTED_CODEX_APP_SERVER_VERSION} or newer is required, but ${detected}. Update the configured Codex app-server binary, or remove custom command overrides to use the managed binary.`);
2230
3204
  this.name = "CodexAppServerVersionError";
2231
3205
  this.detectedVersion = detectedVersion;
2232
3206
  }
2233
3207
  };
2234
3208
  function assertSupportedCodexAppServerVersion(response) {
2235
3209
  const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
2236
- if (detectedVersion !== "0.147.0") throw new CodexAppServerVersionError(detectedVersion);
3210
+ if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
3211
+ const detected = parse(detectedVersion);
3212
+ if (!detected || detected.compare("0.149.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
3213
+ if (detected.compare("0.150.1") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
3214
+ detectedVersion,
3215
+ validatedVersion: CODEX_APP_SERVER_VERSION
3216
+ });
2237
3217
  return detectedVersion;
2238
3218
  }
2239
3219
  function isUnsupportedCodexAppServerVersionError(error) {
@@ -2397,204 +3377,322 @@ function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
2397
3377
  return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
2398
3378
  }
2399
3379
  //#endregion
2400
- //#region extensions/codex/src/app-server/managed-binary.ts
2401
- /**
2402
- * Resolves the managed Codex app-server binary shipped with or installed beside
2403
- * the Codex plugin before stdio startup.
2404
- */
2405
- const CODEX_PLUGIN_ROOT = resolveDefaultCodexPluginRoot(path.dirname(fileURLToPath(import.meta.url)));
2406
- const MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS = ["/Applications/ChatGPT.app/Contents/Resources/codex", "/Applications/Codex.app/Contents/Resources/codex"];
2407
- /** Rewrites managed stdio start options to point at an executable Codex binary path. */
2408
- async function resolveManagedCodexAppServerStartOptions(startOptions, options = {}) {
2409
- if (startOptions.transport !== "stdio" || startOptions.commandSource !== "managed") return startOptions;
2410
- const platform = options.platform ?? process.platform;
2411
- const paths = resolveManagedCodexAppServerPaths({
2412
- platform,
2413
- pluginRoot: options.pluginRoot,
2414
- managedCommandOrder: startOptions.managedCommandOrder
2415
- });
2416
- const pathExists = options.pathExists ?? commandPathExists;
2417
- const commandPaths = await findManagedCodexAppServerCommandPaths({
2418
- candidateCommandPaths: paths.candidateCommandPaths,
2419
- pathExists,
2420
- platform
2421
- });
2422
- const commandPath = expectDefined(commandPaths[0], "resolved managed Codex command path");
2423
- const managedFallbackCommandPaths = commandPaths.slice(1);
2424
- return {
2425
- ...startOptions,
2426
- command: commandPath,
2427
- commandSource: "resolved-managed",
2428
- ...managedFallbackCommandPaths.length > 0 ? { managedFallbackCommandPaths } : {}
2429
- };
2430
- }
2431
- /** Resolves the native artifact behind a successful managed launcher selection. */
2432
- function resolveManagedCodexNativeCommand(command, options = {}) {
2433
- const platform = options.platform ?? process.platform;
2434
- if (platform === "darwin" && MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS.some((candidate) => candidate === command)) return command;
2435
- const target = resolveCodexNativeTarget(platform, options.arch ?? process.arch);
2436
- if (!target) return;
2437
- const packageRoot = resolveManagedCodexPackageRootForCommand(command, platform);
2438
- if (!packageRoot) return;
2439
- const resolvePackageJson = options.resolvePackageJson ?? resolvePackageJsonFromRoot;
2440
- const pathExists = options.pathExists ?? existsSync;
2441
- for (const packageName of [target.packageName, MANAGED_CODEX_APP_SERVER_PACKAGE]) {
2442
- const packageJsonPath = resolvePackageJson(packageName, packageRoot);
2443
- if (!packageJsonPath) continue;
2444
- const candidate = path.join(path.dirname(packageJsonPath), "vendor", target.triple, "bin", platform === "win32" ? "codex.exe" : "codex");
2445
- if (pathExists(candidate)) return candidate;
2446
- }
2447
- }
2448
- function resolveManagedCodexPackageRootForCommand(command, platform) {
2449
- const pathApi = pathForPlatform(platform);
2450
- const commandPaths = [command];
3380
+ //#region extensions/codex/src/app-server/desktop-generation-fingerprint.ts
3381
+ const MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES = 4096;
3382
+ /** Fingerprints every desktop candidate that can own a managed fallback artifact. */
3383
+ async function readMacOSDesktopGenerationFingerprint(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
3384
+ const entries = [];
3385
+ for (const candidate of candidates) {
3386
+ const command = await statFingerprint(candidate.appServerCommandPath);
3387
+ entries.push(`candidate:${candidate.appName}:${candidate.appServerCommandPath}:${command}`);
3388
+ for (const artifactPath of resolveMacOSDesktopGenerationPaths(candidate)) entries.push(`${artifactPath}\0${await statFingerprint(artifactPath)}`);
3389
+ const pluginRoot = resolveComputerUsePluginRoot(candidate);
3390
+ entries.push(`${pluginRoot}\0${await directoryTreeFingerprint(pluginRoot)}`);
3391
+ }
3392
+ return createHash("sha256").update(entries.join("\0")).digest("hex");
3393
+ }
3394
+ function resolveMacOSDesktopGenerationPaths(candidate) {
3395
+ return [
3396
+ candidate.appBundlePath,
3397
+ path.join(candidate.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"),
3398
+ ...candidate.computerUseServiceAppPaths.flatMap((servicePath) => [
3399
+ servicePath,
3400
+ path.join(servicePath, "Contents", "Info.plist"),
3401
+ path.join(servicePath, "Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient")
3402
+ ])
3403
+ ];
3404
+ }
3405
+ function resolveComputerUsePluginRoot(candidate) {
3406
+ return path.join(candidate.bundledMarketplacePath, "plugins", "computer-use");
3407
+ }
3408
+ /** Stable roots that cover bundle replacement and recursive artifact updates. */
3409
+ function resolveMacOSDesktopGenerationWatchPaths(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
3410
+ const watched = /* @__PURE__ */ new Set(["/Applications"]);
3411
+ for (const candidate of candidates) watched.add(candidate.appBundlePath);
3412
+ return [...watched];
3413
+ }
3414
+ async function directoryTreeFingerprint(root) {
3415
+ let rootStat;
2451
3416
  try {
2452
- commandPaths.unshift(realpathSync(command));
2453
- } catch {}
2454
- for (const commandPath of commandPaths) {
2455
- let current = pathApi.dirname(commandPath);
2456
- while (true) {
2457
- if (pathApi.basename(current) === "codex" && pathApi.basename(pathApi.dirname(current)) === "@openai") return current;
2458
- if (pathApi.basename(current) === ".bin") return pathApi.join(pathApi.dirname(current), "@openai", "codex");
2459
- const parent = pathApi.dirname(current);
2460
- if (parent === current) break;
2461
- current = parent;
3417
+ rootStat = await fs.lstat(root, { bigint: true });
3418
+ } catch (error) {
3419
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
3420
+ throw error;
3421
+ }
3422
+ if (!rootStat.isDirectory()) return statFingerprint(root);
3423
+ let entryCount = 0;
3424
+ const hash = createHash("sha256");
3425
+ hash.update("openclaw-codex-computer-use-plugin-tree-v1\0");
3426
+ const visit = async (directory, relativeDirectory, before) => {
3427
+ hash.update(`directory\0${relativeDirectory}\0${statTuple(before)}\0`);
3428
+ const entries = (await fs.readdir(directory, { withFileTypes: true })).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
3429
+ for (const entry of entries) {
3430
+ entryCount += 1;
3431
+ if (entryCount > MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES) throw new Error("Codex Computer Use plugin exceeds the bounded tree size");
3432
+ const entryPath = path.join(directory, entry.name);
3433
+ const relativePath = path.join(relativeDirectory, entry.name);
3434
+ const entryStat = await fs.lstat(entryPath, { bigint: true });
3435
+ if (entryStat.isDirectory()) await visit(entryPath, relativePath, entryStat);
3436
+ else hash.update(`entry\0${relativePath}\0${await statFingerprint(entryPath)}\0`);
2462
3437
  }
3438
+ if (!sameStat(before, await fs.lstat(directory, { bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${directory}`);
3439
+ };
3440
+ await visit(root, ".", rootStat);
3441
+ return hash.digest("hex");
3442
+ }
3443
+ async function statFingerprint(filePath) {
3444
+ try {
3445
+ const entry = await fs.lstat(filePath, { bigint: true });
3446
+ const type = entry.isSymbolicLink() ? "link" : entry.isDirectory() ? "directory" : entry.isFile() ? "file" : "other";
3447
+ const own = statTuple(entry);
3448
+ if (!entry.isSymbolicLink()) return `${type}:${own}:${entry.isFile() ? await readFileFingerprint(filePath, entry, false) : ""}`;
3449
+ const [link, realPath, target] = await Promise.all([
3450
+ fs.readlink(filePath),
3451
+ fs.realpath(filePath),
3452
+ fs.stat(filePath, { bigint: true })
3453
+ ]);
3454
+ const content = target.isFile() ? await readFileFingerprint(filePath, target, true) : "";
3455
+ return `${type}:${own}:${link}:${realPath}:${statTuple(target)}:${content}`;
3456
+ } catch (error) {
3457
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
3458
+ throw error;
2463
3459
  }
2464
3460
  }
2465
- function resolveCodexNativeTarget(platform, arch) {
2466
- if ((platform === "linux" || platform === "android") && arch === "x64") return {
2467
- packageName: "@openai/codex-linux-x64",
2468
- triple: "x86_64-unknown-linux-musl"
2469
- };
2470
- if ((platform === "linux" || platform === "android") && arch === "arm64") return {
2471
- packageName: "@openai/codex-linux-arm64",
2472
- triple: "aarch64-unknown-linux-musl"
2473
- };
2474
- if (platform === "darwin" && arch === "x64") return {
2475
- packageName: "@openai/codex-darwin-x64",
2476
- triple: "x86_64-apple-darwin"
2477
- };
2478
- if (platform === "darwin" && arch === "arm64") return {
2479
- packageName: "@openai/codex-darwin-arm64",
2480
- triple: "aarch64-apple-darwin"
3461
+ async function readFileFingerprint(filePath, expected, followsSymlink) {
3462
+ const noFollow = followsSymlink ? 0 : constants.O_NOFOLLOW ?? 0;
3463
+ const handle = await fs.open(filePath, constants.O_RDONLY | noFollow);
3464
+ try {
3465
+ const before = await handle.stat({ bigint: true });
3466
+ if (!sameStat(before, expected)) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
3467
+ const hash = createHash("sha256");
3468
+ for await (const chunk of handle.createReadStream({ autoClose: false })) hash.update(chunk);
3469
+ if (!sameStat(before, await handle.stat({ bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
3470
+ return hash.digest("hex");
3471
+ } finally {
3472
+ await handle.close();
3473
+ }
3474
+ }
3475
+ function sameStat(left, right) {
3476
+ return statTuple(left) === statTuple(right);
3477
+ }
3478
+ function statTuple(stat) {
3479
+ return [
3480
+ stat.dev,
3481
+ stat.ino,
3482
+ stat.mode,
3483
+ stat.size,
3484
+ stat.mtimeNs,
3485
+ stat.ctimeNs
3486
+ ].join(":");
3487
+ }
3488
+ function isNodeError(error, code) {
3489
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
3490
+ }
3491
+ //#endregion
3492
+ //#region extensions/codex/src/app-server/desktop-generation-owner.ts
3493
+ const SETTLE_DELAY_MS = 1e3;
3494
+ /** Coalesces filesystem invalidations into one stable desktop generation. */
3495
+ function createCodexDesktopGenerationOwner(params) {
3496
+ let generation = params.initialGeneration;
3497
+ let invalidation = 0;
3498
+ let dirty = false;
3499
+ let refresh;
3500
+ let stopped = false;
3501
+ const markDirty = () => {
3502
+ invalidation += 1;
3503
+ dirty = true;
2481
3504
  };
2482
- if (platform === "win32" && arch === "x64") return {
2483
- packageName: "@openai/codex-win32-x64",
2484
- triple: "x86_64-pc-windows-msvc"
3505
+ const reconcile = () => {
3506
+ if (refresh) return refresh;
3507
+ refresh = (async () => {
3508
+ for (;;) {
3509
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3510
+ const observedInvalidation = invalidation;
3511
+ const first = await params.readFingerprint();
3512
+ await delay(SETTLE_DELAY_MS);
3513
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3514
+ const second = await params.readFingerprint();
3515
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3516
+ if (observedInvalidation !== invalidation || first !== second) continue;
3517
+ const previous = generation;
3518
+ generation = previous?.fingerprint === second ? previous : {
3519
+ epoch: (previous?.epoch ?? 0) + 1,
3520
+ fingerprint: second
3521
+ };
3522
+ dirty = false;
3523
+ if (previous && generation !== previous) params.onGenerationChange?.(generation);
3524
+ return generation;
3525
+ }
3526
+ })().finally(() => {
3527
+ refresh = void 0;
3528
+ });
3529
+ return refresh;
2485
3530
  };
2486
- if (platform === "win32" && arch === "arm64") return {
2487
- packageName: "@openai/codex-win32-arm64",
2488
- triple: "aarch64-pc-windows-msvc"
3531
+ return {
3532
+ read: () => generation,
3533
+ markDirty,
3534
+ wait: () => dirty ? reconcile() : Promise.resolve(generation),
3535
+ refresh: () => {
3536
+ markDirty();
3537
+ return reconcile();
3538
+ },
3539
+ isCurrent: (candidate) => Boolean(candidate && !dirty && generation && candidate.epoch === generation.epoch && candidate.fingerprint === generation.fingerprint),
3540
+ stop: () => {
3541
+ stopped = true;
3542
+ }
2489
3543
  };
2490
3544
  }
2491
- function resolvePackageJsonFromRoot(packageName, root) {
2492
- try {
2493
- return createRequire(path.join(root, "package.json")).resolve(`${packageName}/package.json`);
2494
- } catch {
2495
- return;
2496
- }
3545
+ function delay(ms) {
3546
+ return new Promise((resolve) => {
3547
+ setTimeout(resolve, ms);
3548
+ });
2497
3549
  }
2498
- /** Returns the preferred and fallback managed Codex binary paths for a plugin root. */
2499
- function resolveManagedCodexAppServerPaths(params) {
2500
- const platform = params.platform ?? process.platform;
2501
- const candidateCommandPaths = resolveManagedCodexAppServerCommandCandidates(params.pluginRoot ?? CODEX_PLUGIN_ROOT, platform, params.managedCommandOrder ?? "package-first");
3550
+ //#endregion
3551
+ //#region extensions/codex/src/app-server/desktop-generation.ts
3552
+ /** Lifecycle-owned generation for managed macOS Codex desktop artifacts. */
3553
+ const APPLICATIONS_PATH = "/Applications";
3554
+ const REARM_INITIAL_DELAY_MS = 100;
3555
+ const REARM_MAX_DELAY_MS = 3e4;
3556
+ const DESKTOP_GENERATION_STATE = Symbol.for("openclaw.codexDesktopGenerationState");
3557
+ function state() {
3558
+ const globalState = globalThis;
3559
+ return globalState[DESKTOP_GENERATION_STATE] ??= {};
3560
+ }
3561
+ function waitForCodexDesktopGeneration() {
3562
+ return state().owner?.wait() ?? Promise.resolve(void 0);
3563
+ }
3564
+ function isCodexDesktopGenerationCurrent(generation) {
3565
+ return state().owner?.isCurrent(generation) ?? false;
3566
+ }
3567
+ function createCodexDesktopGenerationService(params, runtime = {
3568
+ platform: process.platform,
3569
+ readFingerprint: readMacOSDesktopGenerationFingerprint,
3570
+ resolveWatchPaths: resolveMacOSDesktopGenerationWatchPaths,
3571
+ pathExists: existsSync,
3572
+ watchPath: (watchedPath, options, listener) => watch(watchedPath, options, listener)
3573
+ }) {
2502
3574
  return {
2503
- commandPath: candidateCommandPaths[0] ?? "",
2504
- candidateCommandPaths
2505
- };
2506
- }
2507
- function resolveManagedCodexAppServerCommandCandidates(pluginRoot, platform, managedCommandOrder) {
2508
- const pathApi = pathForPlatform(platform);
2509
- const commandName = platform === "win32" ? "codex.cmd" : "codex";
2510
- const roots = resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform);
2511
- const packageCommandPaths = [...roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)), ...resolveManagedCodexPackageBinCandidates(roots, platform)];
2512
- const desktopCommandPaths = resolveDesktopCodexAppServerCommandCandidates(platform);
2513
- const orderedCommandPaths = managedCommandOrder === "desktop-first" ? [...desktopCommandPaths, ...packageCommandPaths] : [...packageCommandPaths, ...desktopCommandPaths];
2514
- return [...new Set(orderedCommandPaths)];
2515
- }
2516
- function resolveDesktopCodexAppServerCommandCandidates(platform) {
2517
- return platform === "darwin" ? [...MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS] : [];
2518
- }
2519
- function resolveDefaultCodexPluginRoot(moduleDir) {
2520
- const moduleBaseName = path.basename(moduleDir);
2521
- if (moduleBaseName === "dist" || moduleBaseName === "dist-runtime") return path.dirname(moduleDir);
2522
- return path.resolve(moduleDir, "..", "..");
2523
- }
2524
- function resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform) {
2525
- const pathApi = pathForPlatform(platform);
2526
- const directRoots = [
2527
- pluginRoot,
2528
- pathApi.dirname(pluginRoot),
2529
- pathApi.dirname(pathApi.dirname(pluginRoot)),
2530
- isDistExtensionRoot(pluginRoot, platform) ? pathApi.dirname(pathApi.dirname(pathApi.dirname(pluginRoot))) : null
2531
- ].filter((root) => Boolean(root));
2532
- return [.../* @__PURE__ */ new Set([...directRoots, ...resolveNearestNodeModulesProjectRoots(directRoots, platform)])];
2533
- }
2534
- function resolveNearestNodeModulesProjectRoots(roots, platform) {
2535
- const pathApi = pathForPlatform(platform);
2536
- const projectRoots = [];
2537
- for (const root of roots) {
2538
- let current = pathApi.resolve(root);
2539
- while (true) {
2540
- if (pathApi.basename(current) === "node_modules") {
2541
- projectRoots.push(pathApi.dirname(current));
2542
- break;
3575
+ id: "codex-desktop-generation",
3576
+ async start(ctx) {
3577
+ if (runtime.platform !== "darwin") return;
3578
+ const current = state();
3579
+ current.context = ctx;
3580
+ current.readFingerprint = runtime.readFingerprint;
3581
+ current.resolveWatchPaths = runtime.resolveWatchPaths;
3582
+ current.pathExists = runtime.pathExists;
3583
+ current.watchPath = runtime.watchPath;
3584
+ current.owner = createCodexDesktopGenerationOwner({
3585
+ readFingerprint: current.readFingerprint,
3586
+ onGenerationChange: params.onGenerationChange,
3587
+ initialGeneration: current.lastGeneration
3588
+ });
3589
+ armWatchers(current);
3590
+ refreshGeneration(current, current.owner, current.owner.refresh());
3591
+ },
3592
+ async stop() {
3593
+ const current = state();
3594
+ current.lastGeneration = current.owner?.read() ?? current.lastGeneration;
3595
+ current.owner?.stop();
3596
+ current.owner = void 0;
3597
+ current.armEpoch = (current.armEpoch ?? 0) + 1;
3598
+ current.context = void 0;
3599
+ current.readFingerprint = void 0;
3600
+ current.resolveWatchPaths = void 0;
3601
+ current.pathExists = void 0;
3602
+ current.watchPath = void 0;
3603
+ current.watchHealthy = void 0;
3604
+ current.rearmDelayMs = void 0;
3605
+ if (current.rearmTimer) {
3606
+ clearTimeout(current.rearmTimer);
3607
+ current.rearmTimer = void 0;
2543
3608
  }
2544
- const parent = pathApi.dirname(current);
2545
- if (parent === current) break;
2546
- current = parent;
3609
+ closeWatchers(current);
2547
3610
  }
2548
- }
2549
- return projectRoots;
3611
+ };
2550
3612
  }
2551
- function resolveManagedCodexPackageBinCandidates(roots, platform) {
2552
- if (platform === "win32") return [];
2553
- const candidates = [];
2554
- for (const root of roots) {
2555
- const candidate = resolveManagedCodexPackageBinCandidate(root);
2556
- if (candidate) candidates.push(candidate);
3613
+ function armWatchers(current) {
3614
+ const owner = current.owner;
3615
+ if (!owner || current.watchers) return false;
3616
+ const armEpoch = (current.armEpoch ?? 0) + 1;
3617
+ current.armEpoch = armEpoch;
3618
+ const watchers = /* @__PURE__ */ new Set();
3619
+ current.watchers = watchers;
3620
+ const candidateNames = new Set(resolveMacOSDesktopCodexAppPathCandidates("darwin").map((candidate) => candidate.appName));
3621
+ let complete = true;
3622
+ for (const watchedPath of current.resolveWatchPaths?.() ?? []) {
3623
+ if (!current.pathExists?.(watchedPath)) continue;
3624
+ try {
3625
+ const watcher = current.watchPath?.(watchedPath, { recursive: watchedPath !== APPLICATIONS_PATH }, (_eventType, filename) => {
3626
+ if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
3627
+ if (watchedPath === APPLICATIONS_PATH && filename && !candidateNames.has(filename.toString().split(path.sep)[0] ?? "")) return;
3628
+ owner.markDirty();
3629
+ scheduleRearm(current, owner);
3630
+ });
3631
+ if (!watcher) {
3632
+ complete = false;
3633
+ reportWatcherFailure(current, owner, /* @__PURE__ */ new Error(`Could not watch ${watchedPath}`));
3634
+ scheduleRearm(current, owner);
3635
+ continue;
3636
+ }
3637
+ watchers.add(watcher);
3638
+ watcher.on("error", (error) => {
3639
+ if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
3640
+ reportWatcherFailure(current, owner, error);
3641
+ scheduleRearm(current, owner);
3642
+ });
3643
+ } catch (error) {
3644
+ complete = false;
3645
+ reportWatcherFailure(current, owner, error);
3646
+ scheduleRearm(current, owner);
3647
+ }
2557
3648
  }
2558
- return candidates;
2559
- }
2560
- function resolveManagedCodexPackageBinCandidate(root) {
2561
- try {
2562
- const packageJsonPath = createRequire(path.join(root, "package.json")).resolve(`${MANAGED_CODEX_APP_SERVER_PACKAGE}/package.json`);
2563
- const packageRoot = path.dirname(packageJsonPath);
2564
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
2565
- const packageBin = packageJson.bin && typeof packageJson.bin === "object" ? packageJson.bin : void 0;
2566
- const binPath = typeof packageJson.bin === "string" ? packageJson.bin : typeof packageBin?.codex === "string" ? packageBin.codex : null;
2567
- return binPath ? path.resolve(packageRoot, binPath) : null;
2568
- } catch {
2569
- return null;
3649
+ current.watchHealthy = complete;
3650
+ if (complete) current.rearmDelayMs = REARM_INITIAL_DELAY_MS;
3651
+ return complete;
3652
+ }
3653
+ function reportWatcherFailure(current, owner, error) {
3654
+ if (current.watchHealthy === false) return;
3655
+ current.watchHealthy = false;
3656
+ owner.markDirty();
3657
+ current.context?.serviceHealth?.reportFailure(error);
3658
+ current.context?.logger.warn(`codex desktop generation watcher failed: ${String(error)}`);
3659
+ }
3660
+ function isCurrentArm(current, owner, watchers, armEpoch) {
3661
+ return current.owner === owner && current.watchers === watchers && current.armEpoch === armEpoch;
3662
+ }
3663
+ function scheduleRearm(current, owner) {
3664
+ if (current.rearmTimer) {
3665
+ if (current.watchHealthy === false) return;
3666
+ clearTimeout(current.rearmTimer);
2570
3667
  }
3668
+ const delayMs = current.watchHealthy === false ? current.rearmDelayMs ?? REARM_INITIAL_DELAY_MS : REARM_INITIAL_DELAY_MS;
3669
+ if (current.watchHealthy === false) current.rearmDelayMs = Math.min(delayMs * 2, REARM_MAX_DELAY_MS);
3670
+ current.rearmTimer = setTimeout(() => {
3671
+ current.rearmTimer = void 0;
3672
+ if (current.owner !== owner) return;
3673
+ const wasUnhealthy = current.watchHealthy === false;
3674
+ closeWatchers(current);
3675
+ if (!armWatchers(current) || wasUnhealthy) owner.markDirty();
3676
+ refreshGeneration(current, owner, owner.wait());
3677
+ }, delayMs);
3678
+ current.rearmTimer.unref();
3679
+ }
3680
+ function logRefreshFailure(current, owner) {
3681
+ return (error) => {
3682
+ if (current.owner !== owner) return;
3683
+ current.context?.serviceHealth?.reportFailure(error);
3684
+ current.context?.logger.warn(`codex desktop generation refresh failed: ${String(error)}`);
3685
+ };
2571
3686
  }
2572
- function isDistExtensionRoot(pluginRoot, platform) {
2573
- const pathApi = pathForPlatform(platform);
2574
- const extensionsDir = pathApi.dirname(pluginRoot);
2575
- const distDir = pathApi.dirname(extensionsDir);
2576
- return pathApi.basename(extensionsDir) === "extensions" && (pathApi.basename(distDir) === "dist" || pathApi.basename(distDir) === "dist-runtime");
2577
- }
2578
- function pathForPlatform(platform) {
2579
- return platform === "win32" ? path.win32 : path.posix;
3687
+ function refreshGeneration(current, owner, refresh) {
3688
+ refresh.then(() => {
3689
+ if (current.owner === owner && current.watchHealthy) current.context?.serviceHealth?.clearFailure();
3690
+ }).catch(logRefreshFailure(current, owner));
2580
3691
  }
2581
- async function findManagedCodexAppServerCommandPaths(params) {
2582
- const commandPaths = [];
2583
- for (const commandPath of params.candidateCommandPaths) if (await params.pathExists(commandPath, params.platform)) commandPaths.push(commandPath);
2584
- if (commandPaths.length > 0) return commandPaths;
2585
- throw new Error([
2586
- `Managed Codex app-server binary was not found for ${MANAGED_CODEX_APP_SERVER_PACKAGE}.`,
2587
- "Reinstall or update OpenClaw, or run pnpm install in a source checkout.",
2588
- "Set plugins.entries.codex.config.appServer.command or OPENCLAW_CODEX_APP_SERVER_BIN to use a custom Codex binary."
2589
- ].join(" "));
2590
- }
2591
- async function commandPathExists(filePath, platform) {
2592
- try {
2593
- await access(filePath, platform === "win32" ? constants.F_OK : constants.X_OK);
2594
- return true;
2595
- } catch {
2596
- return false;
2597
- }
3692
+ function closeWatchers(current) {
3693
+ const watchers = current.watchers;
3694
+ current.watchers = void 0;
3695
+ for (const watcher of watchers ?? []) watcher.close();
2598
3696
  }
2599
3697
  //#endregion
2600
3698
  //#region extensions/codex/src/app-server/native-config-fence.ts
@@ -2659,38 +3757,6 @@ async function waitForPreviousFence(previous, options) {
2659
3757
  });
2660
3758
  }
2661
3759
  //#endregion
2662
- //#region extensions/codex/src/app-server/timeout.ts
2663
- /**
2664
- * Thin Codex app-server timeout adapter around OpenClaw's shared security
2665
- * runtime timeout helper.
2666
- */
2667
- function resolveAbortError(signal) {
2668
- return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
2669
- }
2670
- /** Awaits a promise with a Codex-specific timeout error message. */
2671
- async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
2672
- return await withTimeout(promise, timeoutMs, {
2673
- message: timeoutMessage,
2674
- ...createError ? { createError } : {}
2675
- });
2676
- }
2677
- /** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
2678
- async function withAbortableTimeout(params) {
2679
- const signal = params.signal;
2680
- if (signal?.aborted) throw resolveAbortError(signal);
2681
- let removeAbortListener;
2682
- const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
2683
- const onAbort = () => reject(resolveAbortError(signal));
2684
- signal.addEventListener("abort", onAbort, { once: true });
2685
- removeAbortListener = () => signal.removeEventListener("abort", onAbort);
2686
- })]) : params.promise;
2687
- try {
2688
- return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
2689
- } finally {
2690
- removeAbortListener?.();
2691
- }
2692
- }
2693
- //#endregion
2694
3760
  //#region extensions/codex/src/app-server/shared-client.ts
2695
3761
  /**
2696
3762
  * Owns shared and isolated Codex app-server client startup, auth application,
@@ -2706,6 +3772,9 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
2706
3772
  getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
2707
3773
  getSharedCodexAppServerClient: () => getSharedCodexAppServerClient,
2708
3774
  isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
3775
+ readCodexAppServerClientDesktopGeneration: () => readCodexAppServerClientDesktopGeneration,
3776
+ readCodexAppServerClientDesktopGenerationFingerprint: () => readCodexAppServerClientDesktopGenerationFingerprint,
3777
+ readCodexAppServerClientProcessIdentity: () => readCodexAppServerClientProcessIdentity,
2709
3778
  releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
2710
3779
  releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
2711
3780
  resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
@@ -2713,6 +3782,8 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
2713
3782
  retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
2714
3783
  retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
2715
3784
  retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
3785
+ retireSharedCodexAppServerClientsBeforeDesktopGeneration: () => retireSharedCodexAppServerClientsBeforeDesktopGeneration,
3786
+ waitForCodexAppServerClientDesktopGenerationDrain: () => waitForCodexAppServerClientDesktopGenerationDrain,
2716
3787
  withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
2717
3788
  });
2718
3789
  const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
@@ -2724,8 +3795,10 @@ function getSharedCodexAppServerClientState() {
2724
3795
  globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
2725
3796
  clients: /* @__PURE__ */ new Map(),
2726
3797
  liveClients: /* @__PURE__ */ new Set(),
3798
+ isolatedClients: /* @__PURE__ */ new Set(),
2727
3799
  entriesByClient: /* @__PURE__ */ new WeakMap(),
2728
- leasedReleases: /* @__PURE__ */ new WeakMap()
3800
+ leasedReleases: /* @__PURE__ */ new WeakMap(),
3801
+ desktopGenerationDrainChecks: /* @__PURE__ */ new Set()
2729
3802
  };
2730
3803
  return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
2731
3804
  }
@@ -2734,6 +3807,41 @@ function getCodexAppServerClientStartMetadata() {
2734
3807
  globalState[CODEX_APP_SERVER_CLIENT_START_METADATA] ??= /* @__PURE__ */ new WeakMap();
2735
3808
  return globalState[CODEX_APP_SERVER_CLIENT_START_METADATA];
2736
3809
  }
3810
+ /** Reads the exact successful spawn selection plus its initialized runtime identity. */
3811
+ function readCodexAppServerClientProcessIdentity(client) {
3812
+ const metadata = getCodexAppServerClientStartMetadata().get(client);
3813
+ if (!metadata) return;
3814
+ const runtimeIdentity = client.getRuntimeIdentity();
3815
+ return {
3816
+ clientId: client.getInstanceId(),
3817
+ ...resolveCodexAppServerSpawnIdentity(metadata.startOptions, metadata.nativeCommand),
3818
+ ...runtimeIdentity?.serverVersion ? { serverVersion: runtimeIdentity.serverVersion } : {},
3819
+ ...runtimeIdentity?.userAgent ? { userAgent: runtimeIdentity.userAgent } : {}
3820
+ };
3821
+ }
3822
+ /** Returns the lifecycle fingerprint that owns a managed desktop client. */
3823
+ function readCodexAppServerClientDesktopGenerationFingerprint(client) {
3824
+ return readCodexAppServerClientDesktopGeneration(client)?.fingerprint;
3825
+ }
3826
+ /** Returns the lifecycle generation that owns a managed desktop client. */
3827
+ function readCodexAppServerClientDesktopGeneration(client) {
3828
+ return getCodexAppServerClientStartMetadata().get(client)?.desktopGeneration;
3829
+ }
3830
+ /** Waits until older physical desktop clients for this client's Codex home exit. */
3831
+ async function waitForCodexAppServerClientDesktopGenerationDrain(params) {
3832
+ const metadata = getCodexAppServerClientStartMetadata().get(params.client);
3833
+ if (!metadata?.desktopGeneration) return;
3834
+ const drain = createOlderDesktopGenerationDrainWait({
3835
+ generation: metadata.desktopGeneration,
3836
+ startOptions: metadata.startOptions,
3837
+ agentDir: metadata.agentDir
3838
+ });
3839
+ try {
3840
+ await withCodexAppServerAcquireDeadline(params.timeoutMs ?? 0, drain.promise, params.signal, "Codex Computer Use install timed out waiting for older desktop clients");
3841
+ } finally {
3842
+ drain.cancel();
3843
+ }
3844
+ }
2737
3845
  /** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
2738
3846
  function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
2739
3847
  const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
@@ -2764,6 +3872,7 @@ function isCodexAppServerStartSelectionChangedError(error) {
2764
3872
  function assertCodexAppServerClientStartSelectionCurrent(params) {
2765
3873
  const metadata = getCodexAppServerClientStartMetadata().get(params.client);
2766
3874
  if (!metadata) return;
3875
+ if (metadata.desktopGeneration && !isCodexDesktopGenerationCurrent(metadata.desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
2767
3876
  const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
2768
3877
  if (requestedStartOptions.commandSource !== "managed") return;
2769
3878
  const current = resolveCodexAppServerStartOptionsForAgent({
@@ -2775,7 +3884,7 @@ function assertCodexAppServerClientStartSelectionCurrent(params) {
2775
3884
  /** Resolves the per-CODEX_HOME key used to serialize native config loading. */
2776
3885
  function resolveCodexNativeConfigFenceKey(params) {
2777
3886
  const metadata = params.client ? getCodexAppServerClientStartMetadata().get(params.client) : void 0;
2778
- const startOptions = params.startOptions ?? metadata?.startOptions;
3887
+ const startOptions = metadata?.startOptions ?? params.startOptions;
2779
3888
  if (!startOptions || startOptions.transport !== "stdio") return;
2780
3889
  const configuredHome = startOptions.env?.CODEX_HOME?.trim();
2781
3890
  const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
@@ -2789,6 +3898,7 @@ function inferAuthRequirement(preparedAuth) {
2789
3898
  async function resolveCodexAppServerClientStartContext(options) {
2790
3899
  const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
2791
3900
  const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
3901
+ const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions, options?.pluginConfig) ? await waitForCodexDesktopGeneration() : void 0;
2792
3902
  const preparedAuth = options?.preparedAuth;
2793
3903
  const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
2794
3904
  if (preparedAuth && options?.authProfileId !== void 0) throw new Error("Prepared Codex auth cannot also select a legacy auth profile.");
@@ -2847,9 +3957,16 @@ async function resolveCodexAppServerClientStartContext(options) {
2847
3957
  config: options?.config,
2848
3958
  pluginConfig: options?.pluginConfig,
2849
3959
  ...authProfileStore ? { authProfileStore } : {}
2850
- })
3960
+ }),
3961
+ ...options?.pluginConfig !== void 0 ? { pluginConfig: options.pluginConfig } : {},
3962
+ ...desktopGeneration ? { desktopGeneration } : {}
2851
3963
  };
2852
3964
  }
3965
+ function shouldTrackDesktopGeneration(startOptions, pluginConfig) {
3966
+ if (startOptions.transport !== "stdio") return false;
3967
+ if (resolveCodexComputerUseConfig({ pluginConfig }).enabled && (startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed")) return true;
3968
+ return startOptions.commandSource === "managed" && (startOptions.managedCommandOrder ?? "package-first") === "desktop-first";
3969
+ }
2853
3970
  function resolveWarmSharedCodexAppServerClientIdentity(options) {
2854
3971
  if (!options?.config || typeof options.config !== "object" || !options.startOptions || options.pluginConfig !== void 0 || options.authProfileStore !== void 0 || options.preparedAuth !== void 0 || options.runtimeArtifactMode !== void 0 || options.expectedRuntimeArtifact !== void 0) return;
2855
3972
  const authProfileSelector = options.authProfileId === null ? ["native"] : options.authProfileId === void 0 ? ["implicit"] : ["profile", options.authProfileId];
@@ -2957,9 +4074,19 @@ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
2957
4074
  }
2958
4075
  async function acquireSharedCodexAppServerClient(options, leaseOptions) {
2959
4076
  if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
4077
+ const acquireStartedAt = Date.now();
4078
+ const timeoutMs = options?.timeoutMs ?? 0;
2960
4079
  const state = getSharedCodexAppServerClientState();
2961
4080
  const warmIdentity = resolveWarmSharedCodexAppServerClientIdentity(options);
2962
- const warmClient = warmIdentity ? readWarmSharedCodexAppServerClient(state, warmIdentity) : void 0;
4081
+ let warmClient = warmIdentity ? readWarmSharedCodexAppServerClient(state, warmIdentity) : void 0;
4082
+ const warmGeneration = warmClient ? getCodexAppServerClientStartMetadata().get(warmClient.client)?.desktopGeneration : void 0;
4083
+ if (warmClient && warmGeneration && !isCodexDesktopGenerationCurrent(warmGeneration)) {
4084
+ await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), options?.abandonSignal);
4085
+ if (!isCodexDesktopGenerationCurrent(warmGeneration)) {
4086
+ retireSharedCodexAppServerClientIfCurrent(warmClient.client);
4087
+ warmClient = void 0;
4088
+ }
4089
+ }
2963
4090
  if (warmClient) {
2964
4091
  options?.onStartedClient?.(warmClient.client);
2965
4092
  const release = leaseOptions?.leased ? retainSharedClientEntry(warmClient.entry) : void 0;
@@ -2968,9 +4095,7 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
2968
4095
  release
2969
4096
  } : { client: warmClient.client };
2970
4097
  }
2971
- const acquireStartedAt = Date.now();
2972
- const timeoutMs = options?.timeoutMs ?? 0;
2973
- const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
4098
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
2974
4099
  const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
2975
4100
  const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authRequirement === "api-key" && !authProfileId ? resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }) : void 0);
2976
4101
  const baseKey = `${codexAppServerStartOptionsKey(startOptions, {
@@ -2978,12 +4103,18 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
2978
4103
  authBindingFingerprint: options?.authBindingFingerprint,
2979
4104
  agentDir: usesNativeAuth ? void 0 : agentDir,
2980
4105
  fallbackApiKeyCacheKey: authIdentityCacheKey
2981
- })}\0auth-requirement:${authRequirement ?? "native"}`;
4106
+ })}\0auth-requirement:${authRequirement ?? "native"}${desktopGeneration ? `\0desktop-generation:${desktopGeneration.epoch}` : ""}`;
2982
4107
  const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
2983
4108
  const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
2984
4109
  const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
2985
- const entry = getOrCreateSharedClientEntry(state, key);
2986
- if (runtimeArtifactMode) entry.runtimeArtifactStartupAbort ??= new AbortController();
4110
+ let entry = getOrCreateSharedClientEntry(state, key);
4111
+ const existingClient = entry.client;
4112
+ const existingGeneration = existingClient ? getCodexAppServerClientStartMetadata().get(existingClient)?.desktopGeneration : void 0;
4113
+ if (existingClient && existingGeneration && !isCodexDesktopGenerationCurrent(existingGeneration)) {
4114
+ retireSharedCodexAppServerClientIfCurrent(existingClient);
4115
+ entry = getOrCreateSharedClientEntry(state, key);
4116
+ }
4117
+ entry.startupAbort ??= new AbortController();
2987
4118
  entry.closeWhenIdle = false;
2988
4119
  const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
2989
4120
  const startedCallback = options?.onStartedClient;
@@ -3010,6 +4141,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3010
4141
  key,
3011
4142
  requestedStartOptions,
3012
4143
  startOptions,
4144
+ desktopGeneration,
4145
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
3013
4146
  agentDir,
3014
4147
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
3015
4148
  authProfileStore,
@@ -3017,7 +4150,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3017
4150
  authRequirement,
3018
4151
  runtimeArtifactMode,
3019
4152
  ...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
3020
- runtimeArtifactSignal: entry.runtimeArtifactStartupAbort?.signal,
4153
+ runtimeArtifactSignal: entry.startupAbort.signal,
4154
+ abandonSignal: entry.startupAbort.signal,
3021
4155
  config: options?.config
3022
4156
  }));
3023
4157
  try {
@@ -3076,6 +4210,8 @@ function createSharedCodexAppServerClientStartup(params) {
3076
4210
  const ready = startInitializedCodexAppServerClient({
3077
4211
  requestedStartOptions: params.requestedStartOptions,
3078
4212
  startOptions: params.startOptions,
4213
+ ...params.desktopGeneration ? { desktopGeneration: params.desktopGeneration } : {},
4214
+ ...params.pluginConfig !== void 0 ? { pluginConfig: params.pluginConfig } : {},
3079
4215
  agentDir: params.agentDir,
3080
4216
  authProfileId: params.authProfileId,
3081
4217
  authProfileStore: params.authProfileStore,
@@ -3084,27 +4220,33 @@ function createSharedCodexAppServerClientStartup(params) {
3084
4220
  runtimeArtifactMode: params.runtimeArtifactMode,
3085
4221
  ...params.expectedRuntimeArtifact ? { expectedRuntimeArtifact: params.expectedRuntimeArtifact } : {},
3086
4222
  runtimeArtifactSignal: params.runtimeArtifactSignal,
4223
+ abandonSignal: params.abandonSignal,
3087
4224
  config: params.config,
3088
4225
  onStartedClient: (startedClient) => {
3089
4226
  const state = getSharedCodexAppServerClientState();
3090
4227
  params.entry.client = startedClient;
3091
- state.entriesByClient.set(startedClient, params.entry);
3092
4228
  state.liveClients.add(startedClient);
4229
+ startedClient.addTransportExitHandler((exitedClient) => {
4230
+ state.liveClients.delete(exitedClient);
4231
+ notifyDesktopGenerationDrainChecks(state);
4232
+ });
3093
4233
  startedClient.addCloseHandler((closedClient) => {
3094
- getSharedCodexAppServerClientState().liveClients.delete(closedClient);
3095
- clearSharedClientEntryIfCurrent(params.key, closedClient);
4234
+ if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
3096
4235
  });
3097
4236
  for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
3098
4237
  retirePendingSharedClientEntryIfUnclaimed(params.key, params.entry);
3099
4238
  },
3100
4239
  onInitializedClient: () => initialized.resolve()
3101
4240
  }).then((client) => {
4241
+ const state = getSharedCodexAppServerClientState();
3102
4242
  params.entry.client = client;
4243
+ state.entriesByClient.set(client, params.entry);
3103
4244
  return client;
3104
4245
  }, (error) => {
3105
4246
  initialized.reject(error);
3106
4247
  throw error;
3107
4248
  });
4249
+ initialized.promise.catch(() => void 0);
3108
4250
  ready.catch(() => void 0);
3109
4251
  return {
3110
4252
  initialized: initialized.promise,
@@ -3116,10 +4258,12 @@ async function createIsolatedCodexAppServerClient(options) {
3116
4258
  if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
3117
4259
  const acquireStartedAt = Date.now();
3118
4260
  const timeoutMs = options?.timeoutMs ?? 0;
3119
- const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
4261
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
3120
4262
  return await startInitializedCodexAppServerClient({
3121
4263
  requestedStartOptions,
3122
4264
  startOptions,
4265
+ ...desktopGeneration ? { desktopGeneration } : {},
4266
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
3123
4267
  agentDir,
3124
4268
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
3125
4269
  authProfileStore,
@@ -3131,7 +4275,18 @@ async function createIsolatedCodexAppServerClient(options) {
3131
4275
  config: options?.config,
3132
4276
  timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
3133
4277
  abandonSignal: options?.abandonSignal,
3134
- onStartedClient: options?.onStartedClient
4278
+ onStartedClient: (client) => {
4279
+ trackIsolatedCodexAppServerClient(client);
4280
+ options?.onStartedClient?.(client);
4281
+ }
4282
+ });
4283
+ }
4284
+ function trackIsolatedCodexAppServerClient(client) {
4285
+ const state = getSharedCodexAppServerClientState();
4286
+ state.isolatedClients.add(client);
4287
+ client.addTransportExitHandler((exitedClient) => {
4288
+ state.isolatedClients.delete(exitedClient);
4289
+ notifyDesktopGenerationDrainChecks(state);
3135
4290
  });
3136
4291
  }
3137
4292
  async function startInitializedCodexAppServerClient(params) {
@@ -3139,7 +4294,41 @@ async function startInitializedCodexAppServerClient(params) {
3139
4294
  const timeoutMs = params.timeoutMs ?? 0;
3140
4295
  const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
3141
4296
  for (const [index, startOptions] of startOptionsCandidates.entries()) {
3142
- const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BVSPdd6g.js") : void 0;
4297
+ const desktopGeneration = params.desktopGeneration ?? (isManagedCodexDesktopCommand(startOptions.command) ? await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), params.abandonSignal) : void 0);
4298
+ const assertDesktopGenerationCurrent = () => {
4299
+ if (params.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
4300
+ if (desktopGeneration && !isCodexDesktopGenerationCurrent(desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
4301
+ };
4302
+ const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
4303
+ const ownsIsolatedCodexHome = params.requestedStartOptions.homeScope !== "user" && !params.requestedStartOptions.env?.CODEX_HOME?.trim();
4304
+ const artifactDrain = desktopGeneration && ownsIsolatedCodexHome && computerUseConfig.enabled && (computerUseConfig.autoInstall || computerUseConfig.pluginCacheMode === "shared") ? createOlderDesktopGenerationDrainWait({
4305
+ generation: desktopGeneration,
4306
+ startOptions,
4307
+ agentDir: params.agentDir
4308
+ }) : void 0;
4309
+ try {
4310
+ if (artifactDrain) await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), artifactDrain.promise, params.abandonSignal);
4311
+ await reconcileCodexComputerUseStartArtifacts({
4312
+ startOptions,
4313
+ agentDir: params.agentDir,
4314
+ pluginConfig: params.pluginConfig,
4315
+ ...desktopGeneration ? { desktopGeneration } : {},
4316
+ assertCurrent: assertDesktopGenerationCurrent,
4317
+ ownsIsolatedCodexHome
4318
+ });
4319
+ } catch (error) {
4320
+ if (isCodexComputerUseCandidateArtifactsUnavailableError(error)) {
4321
+ if (index + 1 < startOptionsCandidates.length) continue;
4322
+ throw new AgentHarnessPreflightError("Codex Computer Use artifacts are unavailable from the installed desktop apps.", {
4323
+ cause: error,
4324
+ scope: "harness"
4325
+ });
4326
+ }
4327
+ throw error;
4328
+ } finally {
4329
+ artifactDrain?.cancel();
4330
+ }
4331
+ const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BvyT1uyd.js") : void 0;
3143
4332
  const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
3144
4333
  const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
3145
4334
  startOptions,
@@ -3150,7 +4339,16 @@ async function startInitializedCodexAppServerClient(params) {
3150
4339
  if (index + 1 < startOptionsCandidates.length) continue;
3151
4340
  throw new Error("Codex app-server runtime artifact does not match verified inference");
3152
4341
  }
4342
+ assertDesktopGenerationCurrent();
3153
4343
  const client = CodexAppServerClient.start(startOptions);
4344
+ const nativeCommandAtStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
4345
+ getCodexAppServerClientStartMetadata().set(client, {
4346
+ requestedStartOptions: params.requestedStartOptions,
4347
+ startOptions,
4348
+ agentDir: params.agentDir,
4349
+ ...nativeCommandAtStart ? { nativeCommand: nativeCommandAtStart } : {},
4350
+ ...desktopGeneration ? { desktopGeneration } : {}
4351
+ });
3154
4352
  params.onStartedClient?.(client);
3155
4353
  let initialize;
3156
4354
  try {
@@ -3161,6 +4359,12 @@ async function startInitializedCodexAppServerClient(params) {
3161
4359
  if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
3162
4360
  throw error;
3163
4361
  }
4362
+ try {
4363
+ assertDesktopGenerationCurrent();
4364
+ } catch (error) {
4365
+ client.close();
4366
+ throw error;
4367
+ }
3164
4368
  params.onInitializedClient?.();
3165
4369
  let runtimeArtifact;
3166
4370
  try {
@@ -3187,6 +4391,7 @@ async function startInitializedCodexAppServerClient(params) {
3187
4391
  config: params.config
3188
4392
  });
3189
4393
  try {
4394
+ assertDesktopGenerationCurrent();
3190
4395
  await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), applyCodexAppServerAuthProfile({
3191
4396
  client,
3192
4397
  agentDir: params.agentDir,
@@ -3197,14 +4402,8 @@ async function startInitializedCodexAppServerClient(params) {
3197
4402
  config: params.config,
3198
4403
  ...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
3199
4404
  }), params.abandonSignal);
3200
- const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
3201
4405
  if (runtimeArtifactModule && runtimeArtifact) runtimeArtifactModule.bindCodexAppServerRuntimeArtifact(client, runtimeArtifact);
3202
- getCodexAppServerClientStartMetadata().set(client, {
3203
- requestedStartOptions: params.requestedStartOptions,
3204
- startOptions,
3205
- agentDir: params.agentDir,
3206
- ...nativeCommand ? { nativeCommand } : {}
3207
- });
4406
+ assertDesktopGenerationCurrent();
3208
4407
  const fenceKey = resolveCodexNativeConfigFenceKey({ client });
3209
4408
  if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
3210
4409
  const release = await acquireCodexNativeConfigFence(fenceKey, options);
@@ -3224,6 +4423,9 @@ async function startInitializedCodexAppServerClient(params) {
3224
4423
  }
3225
4424
  throw new Error("Managed Codex app-server fallback candidates were exhausted.");
3226
4425
  }
4426
+ function isCodexComputerUseCandidateArtifactsUnavailableError(error) {
4427
+ return error !== null && typeof error === "object" && "code" in error && error.code === "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
4428
+ }
3227
4429
  function resolveManagedFallbackStartOptions(startOptions) {
3228
4430
  const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
3229
4431
  const candidates = [];
@@ -3315,6 +4517,62 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
3315
4517
  };
3316
4518
  }
3317
4519
  }
4520
+ /** Gracefully retires exact clients attached to an older desktop generation. */
4521
+ function retireSharedCodexAppServerClientsBeforeDesktopGeneration(generation) {
4522
+ const state = getSharedCodexAppServerClientState();
4523
+ for (const entry of state.clients.values()) {
4524
+ const client = entry.client;
4525
+ const attached = client ? getCodexAppServerClientStartMetadata().get(client) : void 0;
4526
+ if (client && attached?.desktopGeneration && attached.desktopGeneration.epoch < generation.epoch) retireSharedCodexAppServerClientIfCurrent(client);
4527
+ }
4528
+ }
4529
+ function createOlderDesktopGenerationDrainWait(params) {
4530
+ const targetHome = resolveCodexNativeConfigFenceKey({
4531
+ startOptions: params.startOptions,
4532
+ agentDir: params.agentDir
4533
+ });
4534
+ if (!targetHome) return {
4535
+ promise: Promise.resolve(),
4536
+ cancel: () => void 0
4537
+ };
4538
+ const state = getSharedCodexAppServerClientState();
4539
+ let settled = false;
4540
+ let resolveWait;
4541
+ const promise = new Promise((resolve) => {
4542
+ resolveWait = resolve;
4543
+ });
4544
+ const cancel = () => {
4545
+ if (settled) return;
4546
+ settled = true;
4547
+ state.desktopGenerationDrainChecks.delete(check);
4548
+ resolveWait();
4549
+ };
4550
+ const check = () => {
4551
+ if (!hasLiveOlderDesktopGenerationClient({
4552
+ state,
4553
+ generation: params.generation,
4554
+ targetHome
4555
+ })) cancel();
4556
+ };
4557
+ state.desktopGenerationDrainChecks.add(check);
4558
+ check();
4559
+ return {
4560
+ promise,
4561
+ cancel
4562
+ };
4563
+ }
4564
+ function hasLiveOlderDesktopGenerationClient(params) {
4565
+ for (const client of params.state.liveClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
4566
+ for (const client of params.state.isolatedClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
4567
+ return false;
4568
+ }
4569
+ function isOlderDesktopGenerationClientForHome(client, generation, targetHome) {
4570
+ const metadata = getCodexAppServerClientStartMetadata().get(client);
4571
+ return Boolean(metadata?.desktopGeneration && metadata.desktopGeneration.epoch < generation.epoch && resolveCodexNativeConfigFenceKey({ client }) === targetHome);
4572
+ }
4573
+ function notifyDesktopGenerationDrainChecks(state) {
4574
+ for (const check of state.desktopGenerationDrainChecks) check();
4575
+ }
3318
4576
  /** Clears a matching shared client and waits for its process to exit. */
3319
4577
  async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
3320
4578
  if (!client) return false;
@@ -3382,6 +4640,7 @@ function retainPendingSharedClientAcquire(entry) {
3382
4640
  released = true;
3383
4641
  entry.pendingAcquires = Math.max(0, entry.pendingAcquires - 1);
3384
4642
  closeRetiredSharedClientEntryIfIdle(entry);
4643
+ notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
3385
4644
  };
3386
4645
  }
3387
4646
  function retainSharedClientEntry(entry) {
@@ -3392,6 +4651,7 @@ function retainSharedClientEntry(entry) {
3392
4651
  released = true;
3393
4652
  entry.activeLeases = Math.max(0, entry.activeLeases - 1);
3394
4653
  closeRetiredSharedClientEntryIfIdle(entry);
4654
+ notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
3395
4655
  };
3396
4656
  }
3397
4657
  function closeRetiredSharedClientEntryIfIdle(entry) {
@@ -3419,7 +4679,7 @@ function closeSharedClientEntryIfUnclaimed(key, entry) {
3419
4679
  }
3420
4680
  function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
3421
4681
  if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
3422
- entry.runtimeArtifactStartupAbort?.abort(/* @__PURE__ */ new Error("Codex runtime artifact startup was abandoned"));
4682
+ entry.startupAbort?.abort(/* @__PURE__ */ new Error("Codex app-server startup was abandoned"));
3423
4683
  entry.closeWhenIdle = true;
3424
4684
  const state = getSharedCodexAppServerClientState();
3425
4685
  if (state.clients.get(key) === entry) state.clients.delete(key);
@@ -3430,4 +4690,4 @@ function collectSharedClients(state) {
3430
4690
  return [...state.liveClients];
3431
4691
  }
3432
4692
  //#endregion
3433
- export { resolveCodexAppServerAuthProfileId as $, isCodexAppServerApprovalRequest as A, claimCodexAppServerLiveThread as B, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as C, resolveCodexTurnTerminalIdleTimeoutMs as D, resolveCodexTurnCompletionIdleTimeoutMs as E, isCodexAppServerPrewriteRequestCancellationError as F, protectCodexAppServerLiveThread as G, ensureCodexAppServerClientRuntime as H, isCodexAppServerRequestTimeoutError as I, unsubscribeCodexAppServerLiveThread as J, releaseCodexAppServerLiveThread as K, isUnsupportedCodexAppServerVersionError as L, isCodexAppServerConnectionClosedError as M, isCodexAppServerIndeterminateRequestCancellationError as N, withCodexStartupTimeout as O, isCodexAppServerIndeterminateTransportError as P, resolveCodexAppServerAuthAccountCacheKey as Q, resolveCodexAppServerClientInstanceId as R, resolveCodexGatewayTimeoutWithGraceMs as S, resolveCodexTurnAssistantCompletionIdleTimeoutMs as T, isCodexAppServerClientRuntimeLive as U, consumeCodexAppServerLiveThread as V, isCodexAppServerLiveThreadClaimed as W, readRecentCodexRateLimits as X, readCodexRateLimitsRevision as Y, rememberCodexRateLimitsRead as Z, withTimeout$1 as _, getLeasedSharedCodexAppServerClient as a, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as at, CodexAppServerStartupError as b, releaseCodexAppServerClientLease as c, isJsonObject as ct, retainSharedCodexAppServerClientByInstanceId as d, resolveCodexAppServerLocalHomeDir as dt, resolveCodexAppServerAuthProfileIdForAgent as et, retainSharedCodexAppServerClientIfCurrent as f, withAbortableTimeout as g, withLeasedCodexAppServerClientStartSelectionRetry as h, createIsolatedCodexAppServerClient as i, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as it, isCodexAppServerBrokenPipeError as j, getCodexAppServerClientInstanceId as k, releaseLeasedSharedCodexAppServerClient as l, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as lt, shared_client_exports as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, resolveCodexAppServerPreparedApiKeyCacheKey as nt, getSharedCodexAppServerClient as o, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as ot, retireSharedCodexAppServerClientIfCurrent as p, retainCodexAppServerLiveThread as q, clearSharedCodexAppServerClientIfCurrentAndWait as r, resolveCodexAppServerPreparedAuthHandoff as rt, isCodexAppServerStartSelectionChangedError as s, flattenCodexDynamicToolFunctions as st, clearSharedCodexAppServerClientIfCurrent as t, resolveCodexAppServerFallbackApiKeyCacheKey as tt, resolveCodexNativeConfigFenceKey as u, resolveCodexAppServerHomeDir as ut, acquireCodexNativeConfigFence as v, resolveCodexStartupTimeoutMs as w, isCodexAppServerStartupError as x, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as y, CodexAppServerRpcError as z };
4693
+ export { releaseCodexAppServerLiveThread as $, resolveCodexTurnAssistantCompletionIdleTimeoutMs as A, isCodexAppServerPrewriteRequestCancellationError as B, createCodexDesktopGenerationService as C, resolveCodexGatewayTimeoutWithGraceMs as D, isCodexAppServerStartupError as E, isCodexAppServerApprovalRequest as F, CodexAppServerRpcError as G, isUnsupportedCodexAppServerVersionError as H, isCodexAppServerBrokenPipeError as I, consumeCodexAppServerLiveThread as J, createCodexElicitationResponse as K, isCodexAppServerConnectionClosedError as L, resolveCodexTurnTerminalIdleTimeoutMs as M, withCodexStartupTimeout as N, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as O, getCodexAppServerClientInstanceId as P, protectCodexAppServerLiveThread as Q, isCodexAppServerIndeterminateRequestCancellationError as R, acquireCodexNativeConfigFence as S, resolveCodexAppServerLocalHomeDir as St, CodexAppServerStartupError as T, resolveCodexAppServerClientInstanceId as U, isCodexAppServerRequestTimeoutError as V, closeCodexAppServerTransportAndWait as W, isCodexAppServerClientRuntimeLive as X, ensureCodexAppServerClientRuntime as Y, isCodexAppServerLiveThreadClaimed as Z, retireSharedCodexAppServerClientIfCurrent as _, flattenCodexDynamicToolFunctions as _t, createIsolatedCodexAppServerClient as a, readRecentCodexRateLimits as at, waitForCodexAppServerClientDesktopGenerationDrain as b, assertNotSymlink as bt, isCodexAppServerStartSelectionChangedError as c, resolveCodexAppServerAuthAccountCacheKey as ct, readCodexAppServerClientProcessIdentity as d, resolveCodexAppServerFallbackApiKeyCacheKey as dt, retainCodexAppServerLiveThread as et, releaseCodexAppServerClientLease as f, resolveCodexAppServerPreparedApiKeyCacheKey as ft, retainSharedCodexAppServerClientIfCurrent as g, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as gt, retainSharedCodexAppServerClientByInstanceId as h, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as ht, clearSharedCodexAppServerClientIfCurrentAndWait as i, readCodexRateLimitsRevision as it, resolveCodexTurnCompletionIdleTimeoutMs as j, resolveCodexStartupTimeoutMs as k, readCodexAppServerClientDesktopGeneration as l, resolveCodexAppServerAuthProfileId as lt, resolveCodexNativeConfigFenceKey as m, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as mt, clearSharedCodexAppServerClientIfCurrent as n, withAbortableTimeout as nt, getLeasedSharedCodexAppServerClient as o, rememberCodexRateLimitsRead as ot, releaseLeasedSharedCodexAppServerClient as p, resolveCodexAppServerPreparedAuthHandoff as pt, claimCodexAppServerLiveThread as q, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as r, withTimeout$1 as rt, getSharedCodexAppServerClient as s, reconcileCodexComputerUseStartArtifacts as st, assertCodexAppServerClientStartSelectionCurrent as t, unsubscribeCodexAppServerLiveThread as tt, readCodexAppServerClientDesktopGenerationFingerprint as u, resolveCodexAppServerAuthProfileIdForAgent as ut, retireSharedCodexAppServerClientsBeforeDesktopGeneration as v, isJsonObject as vt, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as w, withLeasedCodexAppServerClientStartSelectionRetry as x, resolveCodexAppServerHomeDir as xt, shared_client_exports as y, resolveCodexManagedBundledMarketplacePath as yt, isCodexAppServerIndeterminateTransportError as z };