@openclaw/codex 2026.8.1-beta.2 → 2026.8.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 (64) hide show
  1. package/dist/api.js +146 -0
  2. package/dist/{attempt-client-cleanup-DMLXUfa8.js → attempt-client-cleanup-CVPvp9dL.js} +47 -5
  3. package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
  4. package/dist/{binding-connection-I5Bo-AsX.js → binding-connection-CCQFs8lf.js} +15 -3
  5. package/dist/{bounded-turn-qgZmzy_4.js → bounded-turn-rq5tiSXk.js} +27 -26
  6. package/dist/{capabilities-CybGI1oz.js → capabilities-CGm_94eL.js} +1 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-B1AEz8ZA.js → command-formatters-C54Yg3eA.js} +2 -2
  9. package/dist/{command-handlers-Db8pu_ds.js → command-handlers-BvOAHTrA.js} +111 -67
  10. package/dist/{compact-LspQaGfl.js → compact-Hd7SAc0N.js} +35 -30
  11. package/dist/{session-binding-DPxIvMgE.js → config-Cxw3Cl3k.js} +361 -1161
  12. package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
  13. package/dist/doctor-contract-api.js +128 -26
  14. package/dist/{dynamic-tools-BVdyWkUD.js → dynamic-tools-BFoUcRdx.js} +1341 -1464
  15. package/dist/{effective-mcp-catalog-DIIhAtOZ.js → effective-mcp-catalog-BTJHnDSx.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-D3PHFFXh.js} +18 -18
  19. package/dist/harness-DVfUQBlQ.js +248 -0
  20. package/dist/harness.js +2 -226
  21. package/dist/index.js +405 -299
  22. package/dist/{isolated-completion-C58NbL40.js → isolated-completion-Cbbx_5Ed.js} +4 -4
  23. package/dist/launch-args-sXOAg0SX.js +84 -0
  24. package/dist/managed-binary-ZkGodW19.js +248 -0
  25. package/dist/media-understanding-provider.js +2 -2
  26. package/dist/model-catalog-o6Cgjoss.js +101 -0
  27. package/dist/{models-BFlpn1Ex.js → models-DgFWSuxA.js} +25 -28
  28. package/dist/{native-subagent-monitor-DyTGmTUx.js → native-subagent-monitor-CwFyoylz.js} +385 -94
  29. package/dist/node-exec-server.runtime-DXrqpw1c.js +195 -0
  30. package/dist/plugin-approval-roundtrip-D80UZiJ_.js +95 -0
  31. package/dist/{plugin-marketplace-discovery-CzrGJdCR.js → plugin-marketplace-discovery-mdmScYOj.js} +60 -55
  32. package/dist/{plugin-metadata-cache-CNJPj4_b.js → plugin-metadata-cache-CulOABwu.js} +164 -134
  33. package/dist/{protocol-validators-B4F7L8Rf.js → protocol-validators-Bg5rpNxk.js} +203 -7
  34. package/dist/{rate-limits-DZXmOL8-.js → rate-limits-hFtzt2Up.js} +1 -1
  35. package/dist/{sandbox-guard-Cmwc9eIk.js → request-CCm4LaED.js} +207 -20
  36. package/dist/{run-attempt-P8BdxC1I.js → run-attempt-BwNQDH2u.js} +1506 -836
  37. package/dist/{runtime-artifact-BVSPdd6g.js → runtime-artifact-DeBvleJw.js} +4 -7
  38. package/dist/session-binding-q2WSTVmC.js +947 -0
  39. package/dist/{session-catalog-xwNi_SDM.js → session-catalog-C2Tae4rI.js} +1698 -1346
  40. package/dist/session-catalog-home-id-DAKUrm3C.js +112 -0
  41. package/dist/{session-cli-7hgXZDV7.js → session-cli-BuDjm5Ou.js} +20 -7
  42. package/dist/session-permission-policy-gLm40Vtq.js +160 -0
  43. package/dist/session-retirement-CdkI0bvb.js +90 -0
  44. package/dist/{session-upstream-marker-CrIFX06J.js → session-upstream-marker-Bcy33kam.js} +2 -3
  45. package/dist/{settled-turn-finalizer-BRWBO4MO.js → settled-turn-finalizer-B8JJcxCi.js} +27 -22
  46. package/dist/{shared-client-kIBbGjk4.js → shared-client-kNkljY-R.js} +2023 -607
  47. package/dist/{side-question-9HAL26l9.js → side-question-GwD73tX6.js} +109 -57
  48. package/dist/{thread-lifecycle-Bu1BTDXr.js → thread-lifecycle-Ss-uwnC4.js} +1208 -497
  49. package/dist/{thread-ownership-6kOQHlyC.js → thread-ownership-CeYBqg_4.js} +18 -8
  50. package/dist/{plugin-app-cache-key-C3H2CVp6.js → thread-resume-DF54hagX.js} +64 -6
  51. package/dist/{transcript-mirror-B9gGgnJS.js → transcript-mirror-CkEQIna-.js} +158 -41
  52. package/dist/transport-stdio-C7-s9TLG.js +708 -0
  53. package/dist/{upstream-prompt-provenance-ClRBije0.js → upstream-prompt-provenance-CFmsKwr9.js} +12 -14
  54. package/dist/{upstream-session-fork-CE-_LdnP.js → upstream-session-fork-BACGlOFa.js} +86 -88
  55. package/dist/{usage-CHlV0e-o.js → usage-C2l8Mils.js} +3 -3
  56. package/dist/{web-search-provider.runtime-DRw2Vl42.js → web-search-provider.runtime-B2XgRdFv.js} +3 -3
  57. package/openclaw.plugin.json +50 -2
  58. package/package.json +9 -8
  59. package/dist/app-server-policy-zcBDc1JU.js +0 -49
  60. package/dist/incognito-session-ET_ufDH-.js +0 -2
  61. package/dist/request-BRl5_UfX.js +0 -157
  62. package/dist/session-retirement-DoMMJFKm.js +0 -36
  63. package/dist/thread-resume-CPrJNQVM.js +0 -30
  64. package/dist/transport-stdio-DIImwDVS.js +0 -99
@@ -1,87 +1,173 @@
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 { F as resolveCodexAppServerLocalHomeDir, I as resolveCodexAppServerUserHomeDir, L as withEphemeralCodexAuthStore, P as resolveCodexAppServerHomeDir, a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey } from "./config-Cxw3Cl3k.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-ZkGodW19.js";
4
+ import { o as isCodexAppServerNativeAuthProfile } from "./session-binding-q2WSTVmC.js";
5
+ import { a as hasCodexAppServerNaturalExit, i as closeCodexAppServerTransportAndWait, n as resolveCodexAppServerSpawnEnv, r as closeCodexAppServerTransport, t as createStdioTransport } from "./transport-stdio-C7-s9TLG.js";
6
+ import { l as readCodexDynamicToolCallParams } from "./protocol-validators-Bg5rpNxk.js";
5
7
  import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
6
- import fs, { access } from "node:fs/promises";
8
+ import fs from "node:fs/promises";
7
9
  import path from "node:path";
8
10
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
9
- import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
10
- import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
11
+ import { addTimerTimeoutGraceMs, parseStrictNonNegativeInteger, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
12
+ import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage, formatToolExecutionErrorMessage, normalizeQuestionTimeoutSeconds, resolveToolExecutionErrorKind } from "openclaw/plugin-sdk/agent-harness-runtime";
11
13
  import { createHash, randomUUID } from "node:crypto";
12
- import fsSync, { constants, existsSync, readFileSync, realpathSync } from "node:fs";
14
+ import fsSync, { constants, existsSync, watch } from "node:fs";
13
15
  import os from "node:os";
14
16
  import { isDeepStrictEqual } from "node:util";
15
17
  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";
18
+ import { hasUsableOAuthCredential, resolveOpenAICodexAuthIdentity } from "openclaw/plugin-sdk/provider-auth";
17
19
  import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
20
+ import { assertNoSymlinkParents, withTimeout } from "openclaw/plugin-sdk/security-runtime";
18
21
  import { runExec } from "openclaw/plugin-sdk/process-runtime";
22
+ import { EventEmitter } from "node:events";
19
23
  import { createInterface } from "node:readline";
20
24
  import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
21
25
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
22
- import { EventEmitter } from "node:events";
26
+ import { parse } from "semver";
27
+ import { hasPendingInternalDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
23
28
  import net from "node:net";
24
29
  import { PassThrough, Writable } from "node:stream";
25
30
  import { StringDecoder } from "node:string_decoder";
26
31
  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
- //#region extensions/codex/src/app-server/auth-start-options.ts
31
- const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
32
- const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
33
- function resolveCodexAppServerHomeDir(agentDir) {
34
- return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
35
- }
36
- /** Resolves the local CODEX_HOME used when starting one app-server connection. */
37
- function resolveCodexAppServerLocalHomeDir(startOptions, agentDir, env = process.env) {
38
- const configured = startOptions.env?.CODEX_HOME;
39
- if (configured?.trim()) return configured;
40
- return startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir(env) : resolveCodexAppServerHomeDir(agentDir);
41
- }
42
- /** Forces OpenClaw-owned Codex auth to remain process-local. */
43
- function withEphemeralCodexAuthStore(params) {
44
- const { startOptions } = params;
45
- if (!(startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed") || !params.preparedAuth && params.authProfileId === null) return startOptions;
46
- if (startOptions.args.at(-2) === "-c" && startOptions.args.at(-1) === CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE) return startOptions;
32
+ //#region extensions/codex/src/app-server/computer-use-service-path.ts
33
+ /** Filesystem ownership guards for isolated Computer Use service provisioning. */
34
+ async function assertOwnedServicePath(params) {
35
+ await assertOwnedCodexHomePath({
36
+ ownershipRoot: params.ownershipRoot,
37
+ codexHome: params.codexHome,
38
+ allowMissing: true
39
+ });
40
+ assertPathAtOrInside(params.ownershipRoot, params.targetParent, "Computer Use service parent");
41
+ await assertNoSymlinkParents({
42
+ rootDir: params.ownershipRoot,
43
+ targetPath: params.targetParent,
44
+ allowMissing: true,
45
+ requireDirectories: true,
46
+ messagePrefix: "Computer Use service path"
47
+ });
48
+ await assertNotSymlink(params.targetPath, "Computer Use service target");
49
+ }
50
+ async function ensureOwnedCodexHome(codexHomeInput, ownershipRootInput = path.dirname(path.resolve(codexHomeInput))) {
51
+ const codexHome = path.resolve(codexHomeInput);
52
+ const ownershipRoot = path.resolve(ownershipRootInput);
53
+ await fs.mkdir(ownershipRoot, {
54
+ recursive: true,
55
+ mode: 448
56
+ });
57
+ await assertOwnedCodexHomePath({
58
+ ownershipRoot,
59
+ codexHome,
60
+ allowMissing: true
61
+ });
62
+ await ensureRealDirectoryTree(ownershipRoot, codexHome, "isolated Codex home");
63
+ await assertOwnedCodexHomePath({
64
+ ownershipRoot,
65
+ codexHome,
66
+ allowMissing: false
67
+ });
68
+ }
69
+ async function prepareOwnedServiceParent(params) {
70
+ await ensureOwnedCodexHome(params.codexHome, params.ownershipRoot);
71
+ await ensureRealDirectoryTree(params.ownershipRoot, params.targetParent, "Computer Use service parent");
72
+ await assertNoSymlinkParents({
73
+ rootDir: params.ownershipRoot,
74
+ targetPath: params.targetParent,
75
+ allowMissing: false,
76
+ requireDirectories: true,
77
+ messagePrefix: "Computer Use service path"
78
+ });
79
+ const [rootIdentity, parentIdentity] = await Promise.all([readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root"), readRealDirectoryIdentity(params.targetParent, "Computer Use service parent")]);
80
+ assertPathAtOrInside(rootIdentity.realPath, parentIdentity.realPath, "canonical Computer Use service parent");
81
+ return parentIdentity;
82
+ }
83
+ async function assertOwnedCodexHomePath(params) {
84
+ await readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root");
85
+ assertPathAtOrInside(params.ownershipRoot, params.codexHome, "isolated Codex home");
86
+ await assertNoSymlinkParents({
87
+ rootDir: params.ownershipRoot,
88
+ targetPath: params.codexHome,
89
+ allowMissing: params.allowMissing,
90
+ requireDirectories: true,
91
+ messagePrefix: "Computer Use service path"
92
+ });
93
+ }
94
+ async function readRealDirectoryIdentity(directoryPath, label) {
95
+ const logicalPath = path.resolve(directoryPath);
96
+ const before = await fs.lstat(logicalPath);
97
+ if (before.isSymbolicLink() || !before.isDirectory()) throw new Error(`${label} must be a real directory: ${logicalPath}`);
98
+ const realPath = await fs.realpath(logicalPath);
99
+ const [after, resolved] = await Promise.all([fs.lstat(logicalPath), fs.lstat(realPath)]);
100
+ 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.`);
47
101
  return {
48
- ...startOptions,
49
- args: [
50
- ...startOptions.args,
51
- "-c",
52
- CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE
53
- ]
102
+ logicalPath,
103
+ realPath,
104
+ dev: after.dev,
105
+ ino: after.ino
54
106
  };
55
107
  }
56
- //#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));
108
+ async function assertOwnedServiceParentStable(parent) {
109
+ await assertDirectoryIdentityStable(parent, "Computer Use service parent");
110
+ }
111
+ async function assertDirectoryIdentityStable(expected, label) {
112
+ if (!await directoryIdentityIsStable(expected)) throw new Error(`${label} changed during refresh; refusing to mutate the replacement path.`);
113
+ }
114
+ async function ownedServiceParentIsStable(parent) {
115
+ return await directoryIdentityIsStable(parent);
116
+ }
117
+ async function directoryIdentityIsStable(expected) {
118
+ try {
119
+ const current = await fs.lstat(expected.logicalPath);
120
+ if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) return false;
121
+ return await fs.realpath(expected.logicalPath) === expected.realPath;
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+ async function assertNotSymlink(filePath, label) {
127
+ try {
128
+ if ((await fs.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
129
+ } catch (error) {
130
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
131
+ throw error;
132
+ }
133
+ }
134
+ async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
135
+ const root = path.resolve(ownershipRoot);
136
+ const target = path.resolve(directoryPath);
137
+ assertPathAtOrInside(root, target, label);
138
+ const relative = path.relative(root, target);
139
+ let current = root;
140
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
141
+ current = path.join(current, segment);
142
+ const existing = await fs.lstat(current).catch((error) => {
143
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
144
+ throw error;
145
+ });
146
+ if (!existing) {
147
+ try {
148
+ await fs.mkdir(current, { mode: 448 });
149
+ } catch (error) {
150
+ if (!hasNodeErrorCode$1(error, "EEXIST")) throw error;
151
+ }
152
+ const created = await fs.lstat(current);
153
+ if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
154
+ } else if (existing.isSymbolicLink() || !existing.isDirectory()) throw new Error(`${label} must traverse real directories: ${current}`);
155
+ }
156
+ await assertNoSymlinkParents({
157
+ rootDir: root,
158
+ targetPath: target,
159
+ allowMissing: false,
160
+ requireDirectories: true,
161
+ messagePrefix: "Computer Use service path"
162
+ });
163
+ await readRealDirectoryIdentity(target, label);
164
+ }
165
+ function assertPathAtOrInside(rootPath, candidatePath, label) {
166
+ const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
167
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
168
+ }
169
+ function hasNodeErrorCode$1(error, code) {
170
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
85
171
  }
86
172
  //#endregion
87
173
  //#region extensions/codex/src/app-server/computer-use-cache.ts
@@ -101,7 +187,12 @@ async function ensureCodexComputerUseSharedPluginCache(params) {
101
187
  const cachePath = path.join(cacheRoot, version);
102
188
  return {
103
189
  status: "shared",
104
- changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version),
190
+ changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, {
191
+ codexHome: params.codexHome,
192
+ ownershipRoot: params.ownershipRoot,
193
+ assertCurrent: params.assertCurrent,
194
+ forceRefresh: params.forceRefresh
195
+ }),
105
196
  cachePath,
106
197
  targetPath: sourcePluginRoot,
107
198
  version,
@@ -128,42 +219,57 @@ async function readBundledPluginVersion(sourcePluginRoot) {
128
219
  return;
129
220
  }
130
221
  }
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);
222
+ async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, boundary) {
223
+ const cacheRoot = path.dirname(cachePath);
224
+ const ownedParent = boundary.ownershipRoot ? await prepareOwnedServiceParent({
225
+ ownershipRoot: boundary.ownershipRoot,
226
+ codexHome: boundary.codexHome,
227
+ targetParent: cacheRoot
228
+ }) : void 0;
229
+ if (!ownedParent) await fs.mkdir(cacheRoot, { recursive: true });
230
+ const physicalCachePath = ownedParent ? path.join(ownedParent.realPath, path.basename(cachePath)) : cachePath;
231
+ const stat = await fs.lstat(physicalCachePath).catch(() => void 0);
134
232
  if (stat?.isDirectory() && !stat.isSymbolicLink()) {
135
- if (await readBundledPluginVersion(cachePath) === version) return false;
233
+ if (await readBundledPluginVersion(physicalCachePath) === version && !boundary.forceRefresh) return false;
136
234
  }
137
- const cacheRoot = path.dirname(cachePath);
138
235
  const cacheName = path.basename(cachePath);
139
- const stagingRoot = await fs.mkdtemp(path.join(cacheRoot, `.${cacheName}.staging-`));
236
+ const physicalCacheRoot = path.dirname(physicalCachePath);
237
+ const stagingRoot = await fs.mkdtemp(path.join(physicalCacheRoot, `.${cacheName}.staging-`));
140
238
  const stagedPath = path.join(stagingRoot, cacheName);
141
- const backupPath = path.join(cacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
239
+ const backupPath = path.join(physicalCacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
142
240
  let backupCreated = false;
143
241
  try {
144
242
  await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
243
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
145
244
  if (stat) {
146
- await fs.rename(cachePath, backupPath);
245
+ boundary.assertCurrent?.();
246
+ await fs.rename(physicalCachePath, backupPath);
147
247
  backupCreated = true;
148
248
  }
149
249
  try {
150
- await fs.rename(stagedPath, cachePath);
250
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
251
+ boundary.assertCurrent?.();
252
+ await fs.rename(stagedPath, physicalCachePath);
151
253
  } catch (error) {
152
254
  if (backupCreated) try {
153
- await fs.rename(backupPath, cachePath);
255
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
256
+ await fs.rename(backupPath, physicalCachePath);
154
257
  backupCreated = false;
155
258
  } catch (restoreError) {
156
259
  throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
157
260
  }
158
261
  throw error;
159
262
  }
160
- if (backupCreated) await fs.rm(backupPath, {
161
- recursive: true,
162
- force: true
163
- });
263
+ if (backupCreated) {
264
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
265
+ await fs.rm(backupPath, {
266
+ recursive: true,
267
+ force: true
268
+ });
269
+ }
164
270
  return true;
165
271
  } finally {
166
- await fs.rm(stagingRoot, {
272
+ if (!ownedParent || await directoryIdentityIsStable(ownedParent)) await fs.rm(stagingRoot, {
167
273
  recursive: true,
168
274
  force: true
169
275
  });
@@ -179,108 +285,360 @@ function skippedCacheResult(status, message) {
179
285
  };
180
286
  }
181
287
  //#endregion
288
+ //#region extensions/codex/src/app-server/computer-use-marketplace.ts
289
+ /** Managed local wrapper for Codex's reserved bundled marketplace. */
290
+ const MARKETPLACE_NAME = "openai-bundled";
291
+ const activeInstalls$1 = /* @__PURE__ */ new Map();
292
+ function resolveCodexManagedBundledMarketplacePath(codexHome) {
293
+ return path.join(codexHome, ".tmp", "bundled-marketplaces", MARKETPLACE_NAME);
294
+ }
295
+ async function ensureCodexManagedBundledMarketplace(params) {
296
+ const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
297
+ const source = await resolveCodexManagedBundledMarketplaceSource({
298
+ ...params,
299
+ candidates
300
+ });
301
+ if (!source) return;
302
+ const parentPath = path.dirname(resolveCodexManagedBundledMarketplacePath(params.codexHome));
303
+ const targetPath = path.join(parentPath, MARKETPLACE_NAME);
304
+ const parent = await prepareOwnedServiceParent({
305
+ ownershipRoot: params.ownershipRoot,
306
+ codexHome: params.codexHome,
307
+ targetParent: parentPath
308
+ });
309
+ const physicalTargetPath = path.join(parent.realPath, MARKETPLACE_NAME);
310
+ await assertNotSymlink(physicalTargetPath, "managed bundled marketplace");
311
+ const active = activeInstalls$1.get(physicalTargetPath);
312
+ if (active) {
313
+ if (active.sourcePath === source.bundledMarketplacePath) return await active.promise;
314
+ await active.promise.catch(() => void 0);
315
+ return await ensureCodexManagedBundledMarketplace(params);
316
+ }
317
+ const install = reconcileManagedWrapper({
318
+ parent,
319
+ physicalTargetPath,
320
+ targetPath,
321
+ source,
322
+ ownershipCandidates: params.ownershipCandidates ?? candidates,
323
+ assertCurrent: params.assertCurrent
324
+ });
325
+ const activeEntry = {
326
+ sourcePath: source.bundledMarketplacePath,
327
+ promise: install
328
+ };
329
+ activeInstalls$1.set(physicalTargetPath, activeEntry);
330
+ const clearActive = () => {
331
+ if (activeInstalls$1.get(physicalTargetPath) === activeEntry) activeInstalls$1.delete(physicalTargetPath);
332
+ };
333
+ install.then(clearActive, clearActive);
334
+ return await install;
335
+ }
336
+ async function reconcileManagedWrapper(params) {
337
+ if (await wrapperMatches(params.physicalTargetPath, params.source.bundledMarketplacePath)) return params.targetPath;
338
+ return await publishManagedWrapper(params);
339
+ }
340
+ async function publishManagedWrapper(params) {
341
+ const { parent, physicalTargetPath, targetPath, source, ownershipCandidates, assertCurrent } = params;
342
+ const stagingPath = await fs.mkdtemp(path.join(parent.realPath, `.${MARKETPLACE_NAME}.staging-`));
343
+ const backupPath = path.join(parent.realPath, `.${MARKETPLACE_NAME}.backup-${process.pid}-${Date.now()}`);
344
+ let backupCreated = false;
345
+ try {
346
+ const manifestParent = path.join(stagingPath, ".agents", "plugins");
347
+ await fs.mkdir(manifestParent, {
348
+ recursive: true,
349
+ mode: 448
350
+ });
351
+ 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"))]);
352
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
353
+ const existing = await fs.lstat(physicalTargetPath).catch((error) => {
354
+ if (hasNodeErrorCode(error, "ENOENT")) return;
355
+ throw error;
356
+ });
357
+ if (existing && !existing.isDirectory()) throw new Error(`Managed bundled marketplace must be a real directory: ${targetPath}`);
358
+ if (existing && !await wrapperMatchesAnySource(physicalTargetPath, ownershipCandidates)) throw new Error(`Refusing to replace an unowned bundled marketplace directory: ${targetPath}`);
359
+ if (existing) {
360
+ assertCurrent?.();
361
+ await fs.rename(physicalTargetPath, backupPath);
362
+ backupCreated = true;
363
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
364
+ }
365
+ assertCurrent?.();
366
+ await fs.rename(stagingPath, physicalTargetPath);
367
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
368
+ if (backupCreated) {
369
+ await fs.rm(backupPath, { recursive: true });
370
+ backupCreated = false;
371
+ }
372
+ return targetPath;
373
+ } catch (error) {
374
+ if (backupCreated) try {
375
+ await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
376
+ if (await fs.lstat(physicalTargetPath).catch(() => void 0)) {
377
+ if (!await wrapperMatches(physicalTargetPath, source.bundledMarketplacePath)) throw new Error("managed bundled marketplace replacement is no longer owned", { cause: error });
378
+ await fs.rm(physicalTargetPath, { recursive: true });
379
+ }
380
+ await fs.rename(backupPath, physicalTargetPath);
381
+ backupCreated = false;
382
+ } catch (restoreError) {
383
+ throw new Error(`Failed to restore the prior managed bundled marketplace: ${String(error)}`, { cause: restoreError });
384
+ }
385
+ throw error;
386
+ } finally {
387
+ if (await directoryIdentityIsStable(parent)) await fs.rm(stagingPath, {
388
+ recursive: true,
389
+ force: true
390
+ });
391
+ }
392
+ }
393
+ async function resolveCodexManagedBundledMarketplaceSource(params) {
394
+ const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
395
+ const command = params.appServerCommand && path.resolve(params.appServerCommand);
396
+ const ordered = command ? candidates.filter((candidate) => path.resolve(candidate.appServerCommandPath) === command) : candidates;
397
+ for (const candidate of ordered) if (await isExpectedMarketplace(candidate.bundledMarketplacePath)) return candidate;
398
+ }
399
+ async function isExpectedMarketplace(root) {
400
+ try {
401
+ const manifest = JSON.parse(await fs.readFile(path.join(root, ".agents", "plugins", "marketplace.json"), "utf8"));
402
+ await fs.access(path.join(root, "plugins", "computer-use", ".codex-plugin", "plugin.json"));
403
+ return isRecord(manifest) && manifest.name === MARKETPLACE_NAME && Array.isArray(manifest.plugins) && manifest.plugins.some((plugin) => isRecord(plugin) && plugin.name === "computer-use");
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+ async function wrapperMatches(targetPath, sourcePath) {
409
+ try {
410
+ const target = await fs.lstat(targetPath);
411
+ if (!target.isDirectory() || target.isSymbolicLink()) return false;
412
+ const [manifest, plugins] = await Promise.all([fs.readlink(path.join(targetPath, ".agents", "plugins", "marketplace.json")), fs.readlink(path.join(targetPath, "plugins"))]);
413
+ return manifest === path.join(sourcePath, ".agents", "plugins", "marketplace.json") && plugins === path.join(sourcePath, "plugins");
414
+ } catch {
415
+ return false;
416
+ }
417
+ }
418
+ async function wrapperMatchesAnySource(targetPath, candidates) {
419
+ for (const candidate of candidates) if (await wrapperMatches(targetPath, candidate.bundledMarketplacePath)) return true;
420
+ return false;
421
+ }
422
+ function hasNodeErrorCode(error, code) {
423
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
424
+ }
425
+ //#endregion
182
426
  //#region extensions/codex/src/app-server/computer-use-service.ts
183
427
  /** Native Computer Use service provisioning for isolated Codex homes. */
184
428
  const SERVICE_APP_NAME = "Codex Computer Use.app";
185
- const CLIENT_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient");
429
+ const SERVICE_BUNDLE_ID = "com.openai.sky.CUAService";
430
+ const CLIENT_BUNDLE_ID = "com.openai.sky.CUAService.cli";
431
+ const OPENAI_TEAM_ID = "2DC432GLL2";
432
+ const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app");
433
+ const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
186
434
  const COPY_TIMEOUT_MS = 12e4;
435
+ const INSPECT_TIMEOUT_MS = 3e4;
187
436
  const activeInstalls = /* @__PURE__ */ new Map();
188
- /** Ensures the official native client exists beneath the CODEX_HOME used by its launcher. */
437
+ /** Finds the first signed native service from one ordered desktop owner set. */
438
+ async function resolveCodexComputerUseServiceAppSourcePath(params) {
439
+ const platform = params.platform ?? process.platform;
440
+ if (platform !== "darwin") return;
441
+ return (await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand), params.inspectServiceApp ?? inspectTrustedServiceApp))?.path;
442
+ }
443
+ /** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
189
444
  async function ensureCodexComputerUseServiceApp(params) {
190
445
  const platform = params.platform ?? process.platform;
191
446
  if (platform !== "darwin") return {
192
447
  status: "unsupported",
193
448
  changed: false
194
449
  };
195
- const targetPath = path.join(path.resolve(params.codexHome), "computer-use", SERVICE_APP_NAME);
450
+ const codexHome = path.resolve(params.codexHome);
451
+ const ownershipRoot = path.resolve(params.ownershipRoot ?? path.dirname(codexHome));
452
+ const targetParent = path.join(codexHome, "computer-use");
453
+ const targetPath = path.join(targetParent, SERVICE_APP_NAME);
454
+ await ensureOwnedCodexHome(codexHome, ownershipRoot);
455
+ await assertOwnedServicePath({
456
+ ownershipRoot,
457
+ codexHome,
458
+ targetParent,
459
+ targetPath
460
+ });
461
+ const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
462
+ const syncKey = [targetPath, ...candidates].join("\0");
196
463
  const active = activeInstalls.get(targetPath);
197
- if (active) return await active;
464
+ if (active) {
465
+ if (active.syncKey === syncKey) return await active.promise;
466
+ await active.promise.catch(() => void 0);
467
+ return await ensureCodexComputerUseServiceApp(params);
468
+ }
198
469
  const install = ensureCodexComputerUseServiceAppOnce({
199
470
  ...params,
471
+ codexHome,
472
+ ownershipRoot,
473
+ targetParent,
200
474
  targetPath,
201
- platform
475
+ platform,
476
+ sourceAppCandidates: candidates
202
477
  });
203
- activeInstalls.set(targetPath, install);
204
- try {
205
- return await install;
206
- } finally {
207
- if (activeInstalls.get(targetPath) === install) activeInstalls.delete(targetPath);
208
- }
478
+ const activeEntry = {
479
+ syncKey,
480
+ promise: install
481
+ };
482
+ activeInstalls.set(targetPath, activeEntry);
483
+ const clearActive = () => {
484
+ if (activeInstalls.get(targetPath) === activeEntry) activeInstalls.delete(targetPath);
485
+ };
486
+ install.then(clearActive, clearActive);
487
+ return await install;
209
488
  }
210
489
  async function ensureCodexComputerUseServiceAppOnce(params) {
211
- if (await hasExecutableClient(params.targetPath)) return {
212
- status: "already_installed",
490
+ const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
491
+ const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
492
+ if (!source) return {
493
+ status: "source_missing",
213
494
  changed: false,
214
495
  targetPath: params.targetPath
215
496
  };
216
- const sourcePath = await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(params.platform, params.appServerCommand));
217
- if (!sourcePath) return {
218
- status: "source_missing",
497
+ const { path: sourcePath, identity: sourceIdentity } = source;
498
+ const ownedParent = await prepareOwnedServiceParent({
499
+ ownershipRoot: params.ownershipRoot,
500
+ codexHome: params.codexHome,
501
+ targetParent: params.targetParent
502
+ });
503
+ const operationTargetPath = path.join(ownedParent.realPath, SERVICE_APP_NAME);
504
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
505
+ const initialTarget = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
506
+ if (initialTarget.identity && identitiesMatch(initialTarget.identity, sourceIdentity)) return {
507
+ status: "already_current",
219
508
  changed: false,
220
- targetPath: params.targetPath
509
+ targetPath: params.targetPath,
510
+ sourcePath,
511
+ sourceBuild: sourceIdentity.build
221
512
  };
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-"));
513
+ await assertOwnedServiceParentStable(ownedParent);
514
+ const stagingRoot = await fs.mkdtemp(path.join(ownedParent.realPath, ".service-app.staging-"));
515
+ const stagingRootIdentity = await readRealDirectoryIdentity(stagingRoot, "Computer Use service staging directory");
225
516
  const stagedPath = path.join(stagingRoot, SERVICE_APP_NAME);
226
- const backupPath = path.join(targetParent, `.service-app.backup-${process.pid}-${Date.now()}`);
517
+ const backupPath = path.join(ownedParent.realPath, `.service-app.backup-${process.pid}-${Date.now()}`);
227
518
  let backupCreated = false;
228
519
  try {
229
520
  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);
521
+ await assertOwnedServiceParentStable(ownedParent);
522
+ await assertDirectoryIdentityStable(stagingRootIdentity, "Computer Use service staging directory");
523
+ await assertNotSymlink(stagedPath, "Copied Computer Use service app");
524
+ const stagedIdentity = await inspectServiceApp(stagedPath);
525
+ if (!stagedIdentity || !identitiesMatch(stagedIdentity, sourceIdentity)) throw new Error(`Copied Computer Use service app at ${stagedPath} does not match its selected signed source.`);
526
+ const stagedSnapshot = await readServiceAppSnapshot(stagedPath, inspectServiceApp);
527
+ const currentSourceIdentity = await inspectServiceApp(sourcePath);
528
+ await assertOwnedServiceParentStable(ownedParent);
529
+ if (!currentSourceIdentity || !identitiesMatch(currentSourceIdentity, sourceIdentity)) throw new Error("Selected Computer Use service source changed during refresh.");
530
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
531
+ if (await pathExists$1(operationTargetPath)) {
532
+ await assertOwnedServiceParentStable(ownedParent);
533
+ params.assertCurrent?.();
534
+ await fs.rename(operationTargetPath, backupPath);
535
+ await assertOwnedServiceParentStable(ownedParent);
233
536
  backupCreated = true;
234
- if (await hasExecutableClient(backupPath)) {
235
- await fs.rename(backupPath, params.targetPath);
537
+ const movedTarget = await readServiceAppSnapshot(backupPath, inspectServiceApp);
538
+ if (movedTarget.identity && identitiesMatch(movedTarget.identity, sourceIdentity)) {
539
+ await assertOwnedServiceParentStable(ownedParent);
540
+ await fs.rename(backupPath, operationTargetPath);
541
+ await assertOwnedServiceParentStable(ownedParent);
236
542
  backupCreated = false;
237
543
  return {
238
- status: "already_installed",
544
+ status: "already_current",
239
545
  changed: false,
240
546
  targetPath: params.targetPath,
241
- sourcePath
547
+ sourcePath,
548
+ sourceBuild: sourceIdentity.build
242
549
  };
243
550
  }
551
+ if (!snapshotsMatch(initialTarget, movedTarget)) {
552
+ await assertOwnedServiceParentStable(ownedParent);
553
+ await fs.rename(backupPath, operationTargetPath);
554
+ await assertOwnedServiceParentStable(ownedParent);
555
+ backupCreated = false;
556
+ throw new Error("Computer Use service target changed to an unexpected generation during refresh.");
557
+ }
244
558
  }
245
559
  try {
246
- await fs.rename(stagedPath, params.targetPath);
560
+ await assertOwnedServiceParentStable(ownedParent);
561
+ params.assertCurrent?.();
562
+ await fs.rename(stagedPath, operationTargetPath);
563
+ await assertOwnedServiceParentStable(ownedParent);
247
564
  } catch (error) {
248
- if (!await hasExecutableClient(params.targetPath)) {
565
+ await assertOwnedServiceParentStable(ownedParent);
566
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
567
+ const winnerIdentity = await inspectServiceApp(operationTargetPath);
568
+ if (!winnerIdentity || !identitiesMatch(winnerIdentity, sourceIdentity)) {
249
569
  if (backupCreated) {
250
- await fs.rename(backupPath, params.targetPath);
251
- backupCreated = false;
570
+ if (!await pathExists$1(operationTargetPath)) {
571
+ await assertOwnedServiceParentStable(ownedParent);
572
+ await fs.rename(backupPath, operationTargetPath);
573
+ await assertOwnedServiceParentStable(ownedParent);
574
+ backupCreated = false;
575
+ }
252
576
  }
253
577
  throw error;
254
578
  }
255
579
  if (backupCreated) {
580
+ await assertOwnedServiceParentStable(ownedParent);
581
+ await assertNotSymlink(backupPath, "Computer Use service backup");
256
582
  await fs.rm(backupPath, {
257
583
  recursive: true,
258
584
  force: true
259
585
  });
586
+ await assertOwnedServiceParentStable(ownedParent);
260
587
  backupCreated = false;
261
588
  }
262
589
  return {
263
- status: "already_installed",
590
+ status: "already_current",
264
591
  changed: false,
265
592
  targetPath: params.targetPath,
266
- sourcePath
593
+ sourcePath,
594
+ sourceBuild: sourceIdentity.build
267
595
  };
268
596
  }
597
+ await assertNotSymlink(operationTargetPath, "Installed Computer Use service app");
598
+ const installedSnapshot = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
599
+ if (!installedSnapshot.identity || !identitiesMatch(installedSnapshot.identity, sourceIdentity)) {
600
+ if (filesystemSnapshotsMatch(installedSnapshot, stagedSnapshot)) {
601
+ await assertOwnedServiceParentStable(ownedParent);
602
+ await fs.rm(operationTargetPath, {
603
+ recursive: true,
604
+ force: true
605
+ });
606
+ await assertOwnedServiceParentStable(ownedParent);
607
+ if (backupCreated) {
608
+ await fs.rename(backupPath, operationTargetPath);
609
+ await assertOwnedServiceParentStable(ownedParent);
610
+ backupCreated = false;
611
+ }
612
+ }
613
+ throw new Error("Installed Computer Use service app failed post-install identity verification.");
614
+ }
269
615
  if (backupCreated) {
616
+ await assertOwnedServiceParentStable(ownedParent);
617
+ await assertNotSymlink(backupPath, "Computer Use service backup");
270
618
  await fs.rm(backupPath, {
271
619
  recursive: true,
272
620
  force: true
273
621
  });
622
+ await assertOwnedServiceParentStable(ownedParent);
274
623
  backupCreated = false;
275
624
  }
276
625
  return {
277
- status: "installed",
626
+ status: initialTarget.exists ? "refreshed" : "installed",
278
627
  changed: true,
279
628
  targetPath: params.targetPath,
280
- sourcePath
629
+ sourcePath,
630
+ sourceBuild: sourceIdentity.build,
631
+ ...initialTarget.identity ? { previousBuild: initialTarget.identity.build } : {}
281
632
  };
633
+ } catch (error) {
634
+ if (backupCreated && await ownedServiceParentIsStable(ownedParent) && !await pathExists$1(operationTargetPath)) {
635
+ await assertOwnedServiceParentStable(ownedParent);
636
+ await fs.rename(backupPath, operationTargetPath);
637
+ backupCreated = false;
638
+ }
639
+ throw error;
282
640
  } finally {
283
- await fs.rm(stagingRoot, {
641
+ if (await ownedServiceParentIsStable(ownedParent) && await directoryIdentityIsStable(stagingRootIdentity)) await fs.rm(stagingRoot, {
284
642
  recursive: true,
285
643
  force: true
286
644
  });
@@ -289,8 +647,43 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
289
647
  async function pathExists$1(filePath) {
290
648
  return await fs.lstat(filePath).then(() => true, () => false);
291
649
  }
292
- async function findUsableServiceApp(candidates) {
293
- for (const candidate of candidates) if (await hasExecutableClient(candidate)) return candidate;
650
+ async function findUsableServiceApp(candidates, inspectServiceApp) {
651
+ for (const candidate of candidates) {
652
+ const identity = await inspectServiceApp(candidate);
653
+ if (identity) return {
654
+ path: candidate,
655
+ identity
656
+ };
657
+ }
658
+ }
659
+ async function readServiceAppSnapshot(appPath, inspectServiceApp) {
660
+ if (!await pathExists$1(appPath)) return { exists: false };
661
+ return {
662
+ exists: true,
663
+ identity: await inspectServiceApp(appPath),
664
+ filesystemKey: await readServiceAppFilesystemKey(appPath)
665
+ };
666
+ }
667
+ async function readServiceAppFilesystemKey(appPath) {
668
+ const paths = [
669
+ appPath,
670
+ path.join(appPath, "Contents", "Info.plist"),
671
+ path.join(appPath, CLIENT_RELATIVE_PATH)
672
+ ];
673
+ return (await Promise.all(paths.map(async (entryPath) => await fs.stat(entryPath).then((stat) => `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}`, () => "missing")))).join("|");
674
+ }
675
+ function snapshotsMatch(left, right) {
676
+ if (left.exists !== right.exists) return false;
677
+ if (!left.exists) return true;
678
+ if (left.filesystemKey && right.filesystemKey && left.filesystemKey !== right.filesystemKey) return false;
679
+ if (left.identity || right.identity) return Boolean(left.identity && right.identity && identitiesMatch(left.identity, right.identity));
680
+ return left.filesystemKey !== void 0 && left.filesystemKey === right.filesystemKey;
681
+ }
682
+ function filesystemSnapshotsMatch(left, right) {
683
+ return Boolean(left.exists && right.exists && left.filesystemKey && right.filesystemKey && left.filesystemKey === right.filesystemKey);
684
+ }
685
+ function identitiesMatch(left, right) {
686
+ 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
687
  }
295
688
  async function hasExecutableClient(appPath) {
296
689
  try {
@@ -300,6 +693,90 @@ async function hasExecutableClient(appPath) {
300
693
  return false;
301
694
  }
302
695
  }
696
+ async function inspectTrustedServiceApp(appPath) {
697
+ if (!await hasExecutableClient(appPath)) return;
698
+ const clientAppPath = path.join(appPath, CLIENT_APP_RELATIVE_PATH);
699
+ try {
700
+ await verifyTrustedBundle(appPath, SERVICE_BUNDLE_ID, true);
701
+ await verifyTrustedBundle(clientAppPath, CLIENT_BUNDLE_ID, false);
702
+ const [info, serviceSignature, clientSignature] = await Promise.all([
703
+ readBundleInfo(appPath),
704
+ readCodeSignature(appPath),
705
+ readCodeSignature(clientAppPath)
706
+ ]);
707
+ if (!info || serviceSignature.identifier !== SERVICE_BUNDLE_ID || serviceSignature.teamId !== OPENAI_TEAM_ID || clientSignature.identifier !== CLIENT_BUNDLE_ID || clientSignature.teamId !== OPENAI_TEAM_ID) return;
708
+ return {
709
+ bundleId: serviceSignature.identifier,
710
+ version: info.version,
711
+ build: info.build,
712
+ cdHash: serviceSignature.cdHash,
713
+ teamId: serviceSignature.teamId,
714
+ clientBundleId: clientSignature.identifier,
715
+ clientCdHash: clientSignature.cdHash,
716
+ clientTeamId: clientSignature.teamId
717
+ };
718
+ } catch {
719
+ return;
720
+ }
721
+ }
722
+ async function verifyTrustedBundle(appPath, bundleId, deep) {
723
+ const requirement = `anchor apple generic and certificate leaf[subject.OU] = "${OPENAI_TEAM_ID}" and identifier "${bundleId}"`;
724
+ await runExec("/usr/bin/codesign", [
725
+ "--verify",
726
+ "--strict",
727
+ ...deep ? ["--deep"] : [],
728
+ `-R=${requirement}`,
729
+ appPath
730
+ ], {
731
+ logOutput: false,
732
+ timeoutMs: INSPECT_TIMEOUT_MS
733
+ });
734
+ }
735
+ async function readBundleInfo(appPath) {
736
+ const result = await runExec("/usr/bin/plutil", [
737
+ "-convert",
738
+ "json",
739
+ "-o",
740
+ "-",
741
+ "--",
742
+ path.join(appPath, "Contents", "Info.plist")
743
+ ], {
744
+ logOutput: false,
745
+ timeoutMs: INSPECT_TIMEOUT_MS
746
+ });
747
+ const parsed = JSON.parse(result.stdout);
748
+ if (!isRecord(parsed)) return;
749
+ const version = parsed.CFBundleShortVersionString;
750
+ const build = parsed.CFBundleVersion;
751
+ return typeof version === "string" && version && typeof build === "string" && build ? {
752
+ version,
753
+ build
754
+ } : void 0;
755
+ }
756
+ async function readCodeSignature(appPath) {
757
+ const result = await runExec("/usr/bin/codesign", [
758
+ "-d",
759
+ "--verbose=4",
760
+ appPath
761
+ ], {
762
+ logOutput: false,
763
+ timeoutMs: INSPECT_TIMEOUT_MS
764
+ });
765
+ const output = `${result.stdout}\n${result.stderr}`;
766
+ const identifier = readCodeSignField(output, "Identifier");
767
+ const teamId = readCodeSignField(output, "TeamIdentifier");
768
+ const cdHash = readCodeSignField(output, "CDHash").toLowerCase();
769
+ if (!identifier || !teamId || !/^[a-f0-9]+$/.test(cdHash)) throw new Error(`Could not inspect the signed identity at ${appPath}.`);
770
+ return {
771
+ identifier,
772
+ teamId,
773
+ cdHash
774
+ };
775
+ }
776
+ function readCodeSignField(output, field) {
777
+ const prefix = `${field}=`;
778
+ return output.split(/\r?\n/u).find((candidate) => candidate.startsWith(prefix))?.slice(prefix.length).trim() ?? "";
779
+ }
303
780
  async function copyServiceAppWithDitto(sourcePath, targetPath) {
304
781
  await runExec("/usr/bin/ditto", [
305
782
  "--noqtn",
@@ -344,10 +821,12 @@ const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
344
821
  const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
345
822
  const CODEX_AUTH_JSON_FILENAME = "auth.json";
346
823
  const CODEX_HOME_DIRNAME = ".codex";
824
+ const MAX_COMPUTER_USE_ARTIFACT_OWNERS = 128;
825
+ const activeComputerUseArtifactReconciliations = /* @__PURE__ */ new Map();
347
826
  const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
348
827
  async function bridgeCodexAppServerStartOptions(params) {
349
828
  if (params.startOptions.transport !== "stdio") return params.startOptions;
350
- const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
829
+ const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir);
351
830
  if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
352
831
  if (params.authProfileId === null) return scopeStartOptions();
353
832
  const store = resolveCodexAppServerAuthProfileStore({
@@ -374,8 +853,7 @@ function assertNoUnimportedAgentCodexAuthFile(params) {
374
853
  if (message) throw new AgentHarnessPreflightError(message);
375
854
  }
376
855
  function resolveUnimportedAgentCodexAuthMessage(params) {
377
- const managedCodexCli = params.startOptions.commandSource === "managed" || params.startOptions.commandSource === "resolved-managed";
378
- if (params.startOptions.transport !== "stdio" || !managedCodexCli || params.startOptions.homeScope === "user") return;
856
+ if (params.startOptions.transport !== "stdio" || params.startOptions.homeScope === "user") return;
379
857
  const codexHome = resolveCodexAppServerHomeDir(params.agentDir);
380
858
  const authPath = path.join(codexHome, CODEX_AUTH_JSON_FILENAME);
381
859
  if (!fsSync.existsSync(authPath)) return;
@@ -447,7 +925,7 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
447
925
  const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
448
926
  const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
449
927
  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);
928
+ const chatgptAccountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : void 0;
451
929
  return {
452
930
  loginParams,
453
931
  secretFreeCacheKey,
@@ -458,7 +936,8 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
458
936
  async function resolveCodexAppServerPreparedAuthHandoff(params) {
459
937
  const usesNativeHome = params.homeScope === "user";
460
938
  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) {
939
+ if (usesNativeHome) return { nativeAuthProfile: true };
940
+ if (params.authRequirement === "api-key") {
462
941
  const apiKey = params.resolvedApiKey?.trim();
463
942
  if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
464
943
  return {
@@ -476,7 +955,7 @@ async function resolveCodexAppServerPreparedAuthHandoff(params) {
476
955
  agentDir: params.agentDir,
477
956
  config: params.config
478
957
  });
479
- if (usesNativeHome || params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
958
+ if (params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
480
959
  authProfileId,
481
960
  nativeAuthProfile
482
961
  };
@@ -575,26 +1054,10 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
575
1054
  hash.update(apiKey);
576
1055
  return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
577
1056
  }
578
- async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
1057
+ async function withCodexHomeEnvironment(startOptions, agentDir) {
579
1058
  const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
580
1059
  const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
581
1060
  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
1061
  if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
599
1062
  const nextStartOptions = {
600
1063
  ...startOptions,
@@ -609,6 +1072,138 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
609
1072
  else delete nextStartOptions.clearEnv;
610
1073
  return nextStartOptions;
611
1074
  }
1075
+ /** Reconciles Computer Use artifacts for the exact managed command about to start. */
1076
+ async function reconcileCodexComputerUseStartArtifacts(params) {
1077
+ if (params.startOptions.transport !== "stdio") return;
1078
+ const codexHome = resolveCodexAppServerLocalHomeDir(params.startOptions, params.agentDir);
1079
+ const key = path.resolve(codexHome);
1080
+ let owner = activeComputerUseArtifactReconciliations.get(key);
1081
+ if (!owner) {
1082
+ owner = {
1083
+ active: 0,
1084
+ tail: Promise.resolve()
1085
+ };
1086
+ activeComputerUseArtifactReconciliations.set(key, owner);
1087
+ } else {
1088
+ activeComputerUseArtifactReconciliations.delete(key);
1089
+ activeComputerUseArtifactReconciliations.set(key, owner);
1090
+ }
1091
+ owner.active += 1;
1092
+ const epoch = params.desktopGeneration?.epoch;
1093
+ if (epoch !== void 0 && (owner.latestEpoch === void 0 || epoch > owner.latestEpoch)) owner.latestEpoch = epoch;
1094
+ const assertCurrent = () => {
1095
+ params.assertCurrent?.();
1096
+ if (epoch !== void 0 && owner.latestEpoch !== epoch) throw new Error("Codex Computer Use artifact reconciliation was superseded.");
1097
+ };
1098
+ const operation = owner.tail.catch(() => void 0).then(async () => {
1099
+ assertCurrent();
1100
+ const appliedCacheBinding = await reconcileCodexComputerUseStartArtifactsOnce({
1101
+ ...params,
1102
+ codexHome,
1103
+ assertCurrent,
1104
+ previousCacheBinding: owner.appliedCacheBinding
1105
+ });
1106
+ assertCurrent();
1107
+ owner.appliedCacheBinding = appliedCacheBinding;
1108
+ });
1109
+ const settled = operation.then(() => void 0, () => void 0);
1110
+ owner.tail = settled;
1111
+ try {
1112
+ await operation;
1113
+ } finally {
1114
+ owner.active = Math.max(0, owner.active - 1);
1115
+ if (owner.active === 0 && owner.latestEpoch === void 0 && activeComputerUseArtifactReconciliations.get(key) === owner && owner.tail === settled) activeComputerUseArtifactReconciliations.delete(key);
1116
+ pruneComputerUseArtifactOwners();
1117
+ }
1118
+ }
1119
+ async function reconcileCodexComputerUseStartArtifactsOnce(params) {
1120
+ const codexHome = params.codexHome;
1121
+ const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
1122
+ const ownsIsolatedCodexHome = params.ownsIsolatedCodexHome ?? (params.startOptions.homeScope !== "user" && !params.startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim());
1123
+ const shouldProvisionComputerUse = computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome;
1124
+ if (shouldProvisionComputerUse) await ensureOwnedCodexHome(codexHome, params.agentDir);
1125
+ else await fs.mkdir(codexHome, { recursive: true });
1126
+ const desktopCandidates = resolveMacOSDesktopCodexAppPathCandidates();
1127
+ const exactDesktopCandidate = desktopCandidates.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(params.startOptions.command));
1128
+ const usesManagedBundledMarketplace = !computerUseConfig.marketplaceSource && !computerUseConfig.marketplacePath && !computerUseConfig.marketplaceName;
1129
+ const needsBundledMarketplace = usesManagedBundledMarketplace || computerUseConfig.pluginCacheMode === "shared" && !computerUseConfig.marketplaceName && !computerUseConfig.marketplacePath;
1130
+ const artifactCandidate = shouldProvisionComputerUse ? await resolveCompleteComputerUseArtifactCandidate({
1131
+ candidates: exactDesktopCandidate ? [exactDesktopCandidate] : desktopCandidates,
1132
+ needsBundledMarketplace
1133
+ }) : exactDesktopCandidate;
1134
+ params.assertCurrent();
1135
+ if (shouldProvisionComputerUse) {
1136
+ if (desktopCandidates.length > 0 && !artifactCandidate) throw new CodexComputerUseCandidateArtifactsUnavailableError();
1137
+ try {
1138
+ const marketplacePath = usesManagedBundledMarketplace ? await ensureCodexManagedBundledMarketplace({
1139
+ codexHome,
1140
+ ownershipRoot: params.agentDir,
1141
+ ...artifactCandidate ? {
1142
+ appServerCommand: artifactCandidate.appServerCommandPath,
1143
+ candidates: [artifactCandidate],
1144
+ ownershipCandidates: desktopCandidates
1145
+ } : {},
1146
+ assertCurrent: params.assertCurrent
1147
+ }) : void 0;
1148
+ params.assertCurrent();
1149
+ if (usesManagedBundledMarketplace && desktopCandidates.length > 0 && !marketplacePath) throw new CodexComputerUseCandidateArtifactsUnavailableError();
1150
+ const service = await ensureCodexComputerUseServiceApp({
1151
+ codexHome,
1152
+ ownershipRoot: params.agentDir,
1153
+ ...artifactCandidate ? {
1154
+ appServerCommand: artifactCandidate.appServerCommandPath,
1155
+ sourceAppCandidates: artifactCandidate.computerUseServiceAppPaths
1156
+ } : {},
1157
+ assertCurrent: params.assertCurrent
1158
+ });
1159
+ params.assertCurrent();
1160
+ if (desktopCandidates.length > 0 && service.status === "source_missing") throw new CodexComputerUseCandidateArtifactsUnavailableError();
1161
+ } catch (error) {
1162
+ params.assertCurrent();
1163
+ if (error instanceof CodexComputerUseCandidateArtifactsUnavailableError) throw error;
1164
+ throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
1165
+ cause: error,
1166
+ scope: "harness"
1167
+ });
1168
+ }
1169
+ }
1170
+ params.assertCurrent();
1171
+ const cacheBinding = [
1172
+ params.desktopGeneration?.epoch ?? "manual",
1173
+ artifactCandidate?.bundledMarketplacePath ?? "default",
1174
+ computerUseConfig.pluginName
1175
+ ].join("\0");
1176
+ const cache = await ensureCodexComputerUseSharedPluginCache({
1177
+ codexHome,
1178
+ config: computerUseConfig,
1179
+ ...ownsIsolatedCodexHome ? { ownershipRoot: params.agentDir } : {},
1180
+ ...artifactCandidate ? { bundledMarketplacePath: artifactCandidate.bundledMarketplacePath } : {},
1181
+ assertCurrent: params.assertCurrent,
1182
+ forceRefresh: params.forceCacheRefresh === true || params.previousCacheBinding !== cacheBinding
1183
+ });
1184
+ params.assertCurrent();
1185
+ return cache.status === "shared" ? cacheBinding : void 0;
1186
+ }
1187
+ async function resolveCompleteComputerUseArtifactCandidate(params) {
1188
+ for (const candidate of params.candidates) {
1189
+ if (params.needsBundledMarketplace && !await resolveCodexManagedBundledMarketplaceSource({ candidates: [candidate] })) continue;
1190
+ if (await resolveCodexComputerUseServiceAppSourcePath({ sourceAppCandidates: candidate.computerUseServiceAppPaths })) return candidate;
1191
+ }
1192
+ }
1193
+ function pruneComputerUseArtifactOwners() {
1194
+ while (activeComputerUseArtifactReconciliations.size > MAX_COMPUTER_USE_ARTIFACT_OWNERS) {
1195
+ const inactive = [...activeComputerUseArtifactReconciliations].find(([, owner]) => owner.active === 0);
1196
+ if (!inactive) return;
1197
+ activeComputerUseArtifactReconciliations.delete(inactive[0]);
1198
+ }
1199
+ }
1200
+ var CodexComputerUseCandidateArtifactsUnavailableError = class extends Error {
1201
+ constructor() {
1202
+ super("The selected Codex desktop app does not contain complete Computer Use artifacts.");
1203
+ this.code = "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
1204
+ this.name = "CodexComputerUseCandidateArtifactsUnavailableError";
1205
+ }
1206
+ };
612
1207
  function withoutClearedCodexHomeEnv(clearEnv) {
613
1208
  if (!clearEnv) return;
614
1209
  const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
@@ -616,36 +1211,27 @@ function withoutClearedCodexHomeEnv(clearEnv) {
616
1211
  return filtered.length === clearEnv.length ? clearEnv : filtered;
617
1212
  }
618
1213
  async function applyCodexAppServerAuthProfile(params) {
619
- if (params.preparedAuth?.kind === "profile") {
620
- await params.client.request("account/login/start", params.preparedAuth.snapshot.loginParams);
621
- return;
622
- }
623
- if (params.preparedAuth?.kind === "api-key") {
624
- await params.client.request("account/login/start", {
625
- type: "apiKey",
626
- apiKey: params.preparedAuth.apiKey
627
- });
628
- return;
629
- }
630
- if (params.authProfileId === null) {
1214
+ if (!params.preparedAuth && params.authProfileId === null) {
631
1215
  await assertNativeCodexAccountMatchesRoute(params.client, params.authRequirement);
632
1216
  return;
633
1217
  }
634
1218
  let loginParams;
635
1219
  try {
636
- loginParams = await resolveCodexAppServerAuthProfileLoginParams({
1220
+ loginParams = params.preparedAuth?.kind === "profile" ? params.preparedAuth.snapshot.loginParams : params.preparedAuth?.kind === "api-key" ? {
1221
+ type: "apiKey",
1222
+ apiKey: params.preparedAuth.apiKey
1223
+ } : await resolveCodexAppServerAuthProfileLoginParams({
637
1224
  agentDir: params.agentDir,
638
- authProfileId: params.authProfileId,
1225
+ authProfileId: params.authProfileId ?? void 0,
639
1226
  authProfileStore: params.authProfileStore,
640
1227
  config: params.config
641
1228
  });
642
1229
  } catch (error) {
643
- if (params.authRequirement === "subscription" && error instanceof CodexAppServerAuthProfileUnavailableError) throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.", error);
1230
+ if (params.authRequirement === "subscription" && error instanceof CodexAppServerAuthProfileUnavailableError) throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.", error);
644
1231
  throw error;
645
1232
  }
646
- if (params.authRequirement === "subscription" && loginParams?.type !== "chatgptAuthTokens") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
1233
+ if (params.authRequirement === "subscription" && loginParams?.type !== "chatgptAuthTokens") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.");
647
1234
  if (!loginParams) {
648
- if (params.authRequirement === "subscription") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
649
1235
  if (params.authRequirement !== "api-key" || params.startOptions?.transport !== "stdio") return;
650
1236
  const env = resolveCodexAppServerSpawnEnv(params.startOptions, process.env);
651
1237
  const fallbackLoginParams = await resolveCodexAppServerFallbackApiKeyLoginParams({
@@ -670,7 +1256,7 @@ async function assertNativeCodexAccountMatchesRoute(client, authRequirement) {
670
1256
  const response = await client.request("account/read", { refreshToken: false });
671
1257
  const accountType = isJsonObject(response.account) ? response.account.type : void 0;
672
1258
  if (authRequirement === "subscription") {
673
- if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
1259
+ if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription route requires ChatGPT auth in the native Codex home. Run `codex login` for that home, or use appServer.homeScope=\"agent\" with an OpenClaw OAuth profile, then retry.");
674
1260
  return;
675
1261
  }
676
1262
  if (accountType === "chatgpt") throw createCodexAppServerAuthError("Codex Platform route requires an API-key account, but the native Codex home is signed in with a ChatGPT subscription. Sign that home in with `codex login --with-api-key`, or set appServer.homeScope=\"agent\" so OpenClaw can inject its own key.");
@@ -679,7 +1265,12 @@ function createCodexAppServerAuthError(message, cause) {
679
1265
  const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
680
1266
  return Object.assign(error, { status: 401 });
681
1267
  }
682
- var CodexAppServerAuthProfileUnavailableError = class extends Error {};
1268
+ var CodexAppServerAuthProfileUnavailableError = class extends Error {
1269
+ constructor(..._args) {
1270
+ super(..._args);
1271
+ this.status = 401;
1272
+ }
1273
+ };
683
1274
  async function resolveCodexAppServerAuthProfileLoginParams(params) {
684
1275
  const store = resolveCodexAppServerAuthProfileStore(params);
685
1276
  const profileId = resolveCodexAppServerAuthProfileId({
@@ -688,7 +1279,7 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
688
1279
  config: params.config
689
1280
  });
690
1281
  const profile = profileId ? store.profiles[profileId] : void 0;
691
- if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found.`);
1282
+ if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
692
1283
  if (profileId && profile && !isCodexAppServerAuthProfileCredential(profile)) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
693
1284
  return await resolveCodexAppServerAuthProfileLoginParamsInternal({
694
1285
  ...params,
@@ -696,11 +1287,24 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
696
1287
  });
697
1288
  }
698
1289
  async function refreshCodexAppServerAuthTokens(params) {
1290
+ const previousAccountId = params.previousAccountId?.trim();
1291
+ if (previousAccountId) {
1292
+ const store = resolveCodexAppServerAuthProfileStore(params);
1293
+ const profileId = resolveCodexAppServerAuthProfileId({
1294
+ authProfileId: params.authProfileId,
1295
+ store,
1296
+ config: params.config
1297
+ });
1298
+ const credential = profileId ? store.profiles[profileId] : void 0;
1299
+ const selectedAccountId = credential ? resolveExplicitChatgptAccountId(credential) ?? (credential.type === "oauth" ? resolveOpenAICodexAuthIdentity({ access: credential.access }).accountId : void 0) : void 0;
1300
+ if (selectedAccountId && selectedAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh. Retry to start a client for the selected workspace.");
1301
+ }
699
1302
  const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
700
1303
  ...params,
701
1304
  forceOAuthRefresh: true
702
1305
  });
703
- if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
1306
+ if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.");
1307
+ if (previousAccountId && loginParams.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
704
1308
  return {
705
1309
  accessToken: loginParams.accessToken,
706
1310
  chatgptAccountId: loginParams.chatgptAccountId,
@@ -721,7 +1325,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
721
1325
  });
722
1326
  if (!profileId) return;
723
1327
  const credential = store.profiles[profileId];
724
- if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found.`);
1328
+ if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
725
1329
  if (!isCodexAppServerAuthProfileCredential(credential)) throw new Error(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
726
1330
  const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
727
1331
  agentDir: params.agentDir,
@@ -730,7 +1334,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
730
1334
  forceOAuthRefresh: params.forceOAuthRefresh === true,
731
1335
  config: params.config
732
1336
  });
733
- if (!loginParams) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" does not contain usable credentials.`);
1337
+ if (!loginParams) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" does not contain usable credentials. Repair or replace the selected OpenAI credential, then retry.`);
734
1338
  return loginParams;
735
1339
  }
736
1340
  async function resolveCodexAppServerFallbackApiKeyLoginParams(params) {
@@ -844,7 +1448,7 @@ async function resolveOAuthCredentialForCodexAppServer(profileId, credential, pa
844
1448
  });
845
1449
  if (params.forceRefresh && !persistedOAuthCredential && overlaidOAuthCredential) {
846
1450
  const refreshedRuntimeCredential = await refreshOAuthCredentialForRuntime({ credential: overlaidOAuthCredential });
847
- if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh.`);
1451
+ if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
848
1452
  store.profiles[profileId] = refreshedRuntimeCredential;
849
1453
  return refreshedRuntimeCredential;
850
1454
  }
@@ -872,8 +1476,8 @@ function shouldUseScopedOAuthCredential(params) {
872
1476
  return !isDeepStrictEqual(persisted, params.suppliedCredential) && !hasMatchingOAuthIdentity(persisted, params.suppliedCredential);
873
1477
  }
874
1478
  function hasMatchingOAuthIdentity(persisted, supplied) {
875
- const persistedAccountId = persisted.accountId?.trim();
876
- const suppliedAccountId = supplied.accountId?.trim();
1479
+ const persistedAccountId = resolveOpenAICodexAuthIdentity(persisted).accountId?.trim();
1480
+ const suppliedAccountId = resolveOpenAICodexAuthIdentity(supplied).accountId?.trim();
877
1481
  if (persistedAccountId && suppliedAccountId) return persistedAccountId === suppliedAccountId;
878
1482
  const persistedEmail = persisted.email?.trim().toLowerCase();
879
1483
  const suppliedEmail = supplied.email?.trim().toLowerCase();
@@ -890,8 +1494,8 @@ async function resolveScopedOAuthCredential(params) {
890
1494
  const credential = current?.type === "oauth" ? current : params.credential;
891
1495
  if (!params.forceRefresh && hasUsableOAuthCredential(credential)) return credential;
892
1496
  const refreshed = await refreshOAuthCredentialForRuntime({ credential });
893
- if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh.`);
894
- if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing.`);
1497
+ if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
1498
+ if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing. Retry with the newly selected OpenAI profile.`);
895
1499
  params.store.profiles[params.profileId] = refreshed;
896
1500
  return refreshed;
897
1501
  })();
@@ -938,10 +1542,15 @@ function readFirstNonEmptyEnvEntry(env, keys) {
938
1542
  }
939
1543
  }
940
1544
  function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
1545
+ const storedAccountId = resolveExplicitChatgptAccountId(credential);
1546
+ const tokenAccountId = resolveOpenAICodexAuthIdentity({ access: accessToken }).accountId;
1547
+ 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.`);
1548
+ const chatgptAccountId = storedAccountId ?? tokenAccountId;
1549
+ if (!chatgptAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is missing its ChatGPT account ID. Sign in again before retrying.`);
941
1550
  return {
942
1551
  type: "chatgptAuthTokens",
943
1552
  accessToken,
944
- chatgptAccountId: resolveChatgptAccountId(profileId, credential),
1553
+ chatgptAccountId,
945
1554
  chatgptPlanType: resolveChatgptPlanType(credential)
946
1555
  };
947
1556
  }
@@ -1061,12 +1670,46 @@ function readLimitId(snapshot) {
1061
1670
  return typeof value === "string" && value.trim() ? value.trim() : "codex";
1062
1671
  }
1063
1672
  //#endregion
1673
+ //#region extensions/codex/src/app-server/timeout.ts
1674
+ /**
1675
+ * Thin Codex app-server timeout adapter around OpenClaw's shared security
1676
+ * runtime timeout helper.
1677
+ */
1678
+ function resolveAbortError(signal) {
1679
+ return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
1680
+ }
1681
+ /** Awaits a promise with a Codex-specific timeout error message. */
1682
+ async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
1683
+ return await withTimeout(promise, timeoutMs, {
1684
+ message: timeoutMessage,
1685
+ ...createError ? { createError } : {}
1686
+ });
1687
+ }
1688
+ /** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
1689
+ async function withAbortableTimeout(params) {
1690
+ const signal = params.signal;
1691
+ if (signal?.aborted) throw resolveAbortError(signal);
1692
+ let removeAbortListener;
1693
+ const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
1694
+ const onAbort = () => reject(resolveAbortError(signal));
1695
+ signal.addEventListener("abort", onAbort, { once: true });
1696
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
1697
+ })]) : params.promise;
1698
+ try {
1699
+ return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
1700
+ } finally {
1701
+ removeAbortListener?.();
1702
+ }
1703
+ }
1704
+ //#endregion
1064
1705
  //#region extensions/codex/src/app-server/client-runtime.ts
1065
1706
  /** Client-scoped Codex auth and account observers. */
1066
1707
  /** Match Codex's native grace window without retaining inactive conversations indefinitely. */
1067
1708
  const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 30 * 6e4;
1068
1709
  /** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
1069
1710
  const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
1711
+ /** Return a deterministic error before Codex cancels its ten-second external-auth request. */
1712
+ const CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS = 9e3;
1070
1713
  const configuredClients = /* @__PURE__ */ new WeakMap();
1071
1714
  const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
1072
1715
  const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
@@ -1080,7 +1723,10 @@ function ensureCodexAppServerClientRuntime(client, context) {
1080
1723
  const existing = configuredClients.get(client);
1081
1724
  if (existing) {
1082
1725
  if (existing.closed) return;
1083
- existing.context = context;
1726
+ existing.context = {
1727
+ ...existing.context,
1728
+ config: context.config
1729
+ };
1084
1730
  return;
1085
1731
  }
1086
1732
  const runtime = {
@@ -1105,12 +1751,21 @@ function ensureCodexAppServerClientRuntime(client, context) {
1105
1751
  client.addRequestHandler(async (request) => {
1106
1752
  if (request.method !== "account/chatgptAuthTokens/refresh") return;
1107
1753
  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({
1109
- agentDir: runtime.context.agentDir,
1110
- authProfileId: runtime.context.authProfileId,
1111
- ...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
1112
- config: runtime.context.config
1113
- });
1754
+ const previousAccountId = isJsonObject(request.params) && typeof request.params.previousAccountId === "string" ? request.params.previousAccountId.trim() || void 0 : void 0;
1755
+ try {
1756
+ const tokens = await withTimeout$1(refreshCodexAppServerAuthTokens({
1757
+ agentDir: runtime.context.agentDir,
1758
+ authProfileId: runtime.context.authProfileId,
1759
+ ...previousAccountId ? { previousAccountId } : {},
1760
+ ...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
1761
+ config: runtime.context.config
1762
+ }), CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS, "Codex app-server ChatGPT token refresh timed out before its external-auth deadline. Retry the request; if it persists, sign in again with OpenClaw.");
1763
+ if (previousAccountId && tokens.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
1764
+ return { ...tokens };
1765
+ } catch (error) {
1766
+ runtime.context.onAuthRefreshFailure?.();
1767
+ throw error;
1768
+ }
1114
1769
  });
1115
1770
  client.addNotificationHandler((notification) => {
1116
1771
  if (notification.method === "account/rateLimits/updated") {
@@ -1146,10 +1801,11 @@ function scheduleRetainedThreadEviction(client, runtime) {
1146
1801
  }, Math.max(0, expiresAt - Date.now()));
1147
1802
  runtime.evictionTimer.unref?.();
1148
1803
  }
1149
- async function releaseRetainedThread(client, runtime, threadId) {
1804
+ async function releaseRetainedThread(client, runtime, threadId, assertCurrent) {
1150
1805
  const pendingRelease = runtime.releasingThreads.get(threadId);
1151
1806
  if (pendingRelease) {
1152
1807
  await pendingRelease.completion;
1808
+ assertCurrent?.();
1153
1809
  return false;
1154
1810
  }
1155
1811
  const retained = runtime.retainedThreads.get(threadId);
@@ -1161,7 +1817,10 @@ async function releaseRetainedThread(client, runtime, threadId) {
1161
1817
  }
1162
1818
  runtime.retainedThreads.delete(threadId);
1163
1819
  scheduleRetainedThreadEviction(client, runtime);
1164
- const transition = { completion: Promise.resolve().then(() => retained.release(threadId)) };
1820
+ const transition = { completion: Promise.resolve().then(() => {
1821
+ assertCurrent?.();
1822
+ return assertCurrent ? retained.release(threadId, assertCurrent) : retained.release(threadId);
1823
+ }) };
1165
1824
  runtime.releasingThreads.set(threadId, transition);
1166
1825
  try {
1167
1826
  await transition.completion;
@@ -1201,15 +1860,15 @@ async function retainCodexAppServerLiveThread(client, threadId, releaseThread, c
1201
1860
  const pendingRelease = runtime.releasingThreads.get(threadId);
1202
1861
  if (pendingRelease) {
1203
1862
  await pendingRelease.completion;
1204
- if (runtime.closed || claimed !== void 0 && runtime.claimedThreads.get(threadId) !== claimed) return false;
1863
+ return false;
1205
1864
  }
1206
1865
  runtime.retainedThreads.delete(threadId);
1207
1866
  const retained = {
1208
1867
  configFingerprint,
1209
1868
  serviceTier,
1210
1869
  expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
1211
- release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId) => {
1212
- await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
1870
+ release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId, assertCurrent) => {
1871
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
1213
1872
  })
1214
1873
  };
1215
1874
  runtime.retainedThreads.set(threadId, retained);
@@ -1253,8 +1912,8 @@ async function claimCodexAppServerLiveThread(client, threadId) {
1253
1912
  }
1254
1913
  return claimCodexAppServerThreadOwnership(client, runtime, threadId, runtime.retainedThreads.get(threadId) ?? {
1255
1914
  expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
1256
- release: async (releasedThreadId) => {
1257
- await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
1915
+ release: async (releasedThreadId, assertCurrent) => {
1916
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
1258
1917
  }
1259
1918
  });
1260
1919
  }
@@ -1263,7 +1922,7 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
1263
1922
  const claimed = Symbol(threadId);
1264
1923
  runtime.claimedThreads.set(threadId, claimed);
1265
1924
  scheduleRetainedThreadEviction(client, runtime);
1266
- const release = async (releasedThreadId) => {
1925
+ const release = async (releasedThreadId, assertCurrent) => {
1267
1926
  if (releasedThreadId !== threadId || runtime.claimedThreads.get(threadId) !== claimed) return;
1268
1927
  const pendingRelease = runtime.releasingThreads.get(threadId);
1269
1928
  if (pendingRelease) {
@@ -1272,7 +1931,8 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
1272
1931
  }
1273
1932
  const transition = { completion: Promise.resolve().then(async () => {
1274
1933
  if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) return;
1275
- await retained.release(releasedThreadId);
1934
+ assertCurrent?.();
1935
+ await (assertCurrent ? retained.release(releasedThreadId, assertCurrent) : retained.release(releasedThreadId));
1276
1936
  }) };
1277
1937
  runtime.releasingThreads.set(threadId, transition);
1278
1938
  try {
@@ -1291,22 +1951,32 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
1291
1951
  };
1292
1952
  }
1293
1953
  /** Distinguish active claimed ownership from an already-evicted idle subscription. */
1954
+ function hasCodexAppServerLiveThread(client, threadId) {
1955
+ const runtime = configuredClients.get(client);
1956
+ return runtime !== void 0 && !runtime.closed && (runtime.retainedThreads.has(threadId) || runtime.releasingThreads.has(threadId) || runtime.claimedThreads.has(threadId));
1957
+ }
1294
1958
  function isCodexAppServerLiveThreadClaimed(client, threadId) {
1295
1959
  const runtime = configuredClients.get(client);
1296
1960
  return runtime !== void 0 && !runtime.closed && runtime.claimedThreads.has(threadId);
1297
1961
  }
1298
- /** Release the exact physical subscription and finish only its observed claim generation. */
1299
- async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs) {
1962
+ /** Release the exact physical subscription and finish only its observed ownership generation. */
1963
+ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs, assertCurrent) {
1300
1964
  const runtime = configuredClients.get(client);
1301
1965
  const claimed = runtime?.claimedThreads.get(threadId);
1966
+ const retained = runtime?.retainedThreads.get(threadId);
1302
1967
  let transition = runtime?.releasingThreads.get(threadId);
1303
1968
  if (transition?.physicalRelease) {
1304
1969
  await transition.physicalRelease;
1970
+ assertCurrent?.();
1305
1971
  return;
1306
1972
  }
1307
1973
  const physicalRelease = Promise.resolve().then(async () => {
1308
- if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed) return;
1309
- await client.request("thread/unsubscribe", { threadId }, { timeoutMs });
1974
+ if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed || retained !== void 0 && runtime?.retainedThreads.get(threadId) !== retained) return;
1975
+ assertCurrent?.();
1976
+ await client.request("thread/unsubscribe", { threadId }, {
1977
+ timeoutMs,
1978
+ assertCurrent
1979
+ });
1310
1980
  });
1311
1981
  const ownsTransition = runtime !== void 0 && transition === void 0;
1312
1982
  if (transition) transition.physicalRelease = physicalRelease;
@@ -1319,15 +1989,19 @@ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs)
1319
1989
  }
1320
1990
  try {
1321
1991
  await physicalRelease;
1992
+ if (retained !== void 0 && runtime?.retainedThreads.get(threadId) === retained) {
1993
+ runtime.retainedThreads.delete(threadId);
1994
+ scheduleRetainedThreadEviction(client, runtime);
1995
+ }
1322
1996
  if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1323
1997
  } finally {
1324
1998
  if (ownsTransition && runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1325
1999
  }
1326
2000
  }
1327
2001
  /** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
1328
- async function releaseCodexAppServerLiveThread(client, threadId) {
2002
+ async function releaseCodexAppServerLiveThread(client, threadId, assertCurrent) {
1329
2003
  const runtime = configuredClients.get(client);
1330
- return runtime ? await releaseRetainedThread(client, runtime, threadId) : false;
2004
+ return runtime ? await releaseRetainedThread(client, runtime, threadId, assertCurrent) : false;
1331
2005
  }
1332
2006
  /** Native child work pins its parent's subscription even after the foreground parent turn ends. */
1333
2007
  function protectCodexAppServerLiveThread(client, threadId) {
@@ -1345,11 +2019,9 @@ function protectCodexAppServerLiveThread(client, threadId) {
1345
2019
  runtime.protectedThreads.delete(threadId);
1346
2020
  const retained = runtime.retainedThreads.get(threadId);
1347
2021
  if (retained) {
2022
+ retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
1348
2023
  runtime.retainedThreads.delete(threadId);
1349
- runtime.retainedThreads.set(threadId, {
1350
- ...retained,
1351
- expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS
1352
- });
2024
+ runtime.retainedThreads.set(threadId, retained);
1353
2025
  }
1354
2026
  } else runtime.protectedThreads.set(threadId, count - 1);
1355
2027
  scheduleRetainedThreadEviction(client, runtime);
@@ -1362,6 +2034,462 @@ function protectCodexAppServerLiveThread(client, threadId) {
1362
2034
  };
1363
2035
  }
1364
2036
  //#endregion
2037
+ //#region extensions/codex/src/app-server/dynamic-tool-response-state.ts
2038
+ /** Retains the host-owned app preview without adding it to Codex's response payload. */
2039
+ function withDynamicToolTranscriptDetails(response, details) {
2040
+ if (details === void 0) return response;
2041
+ Object.defineProperty(response, "transcriptDetails", {
2042
+ configurable: true,
2043
+ enumerable: false,
2044
+ value: details
2045
+ });
2046
+ return response;
2047
+ }
2048
+ function withDynamicToolTerminalResolution(response, terminalResolution) {
2049
+ if (terminalResolution) {
2050
+ Object.defineProperties(response, {
2051
+ terminalResolution: {
2052
+ configurable: true,
2053
+ enumerable: false,
2054
+ value: terminalResolution
2055
+ },
2056
+ executionStarted: {
2057
+ configurable: true,
2058
+ enumerable: false,
2059
+ value: terminalResolution.executionStarted
2060
+ },
2061
+ ...terminalResolution.executedArguments ? { executedArguments: {
2062
+ configurable: true,
2063
+ enumerable: false,
2064
+ value: terminalResolution.executedArguments
2065
+ } } : {}
2066
+ });
2067
+ withDynamicToolSideEffectEvidence(response, terminalResolution.sideEffectEvidence);
2068
+ }
2069
+ return response;
2070
+ }
2071
+ function withDynamicToolExecutionState(response, state) {
2072
+ Object.defineProperties(response, {
2073
+ executedArguments: {
2074
+ configurable: true,
2075
+ enumerable: false,
2076
+ value: state.executedArguments
2077
+ },
2078
+ executionStarted: {
2079
+ configurable: true,
2080
+ enumerable: false,
2081
+ value: state.executionStarted
2082
+ }
2083
+ });
2084
+ return withDynamicToolSideEffectEvidence(response, state.sideEffectEvidence === true);
2085
+ }
2086
+ function withDynamicToolSideEffectEvidence(response, sideEffectEvidence) {
2087
+ if (!sideEffectEvidence) {
2088
+ delete response.sideEffectEvidence;
2089
+ return response;
2090
+ }
2091
+ Object.defineProperty(response, "sideEffectEvidence", {
2092
+ configurable: true,
2093
+ enumerable: false,
2094
+ value: true
2095
+ });
2096
+ return response;
2097
+ }
2098
+ function createFailedDynamicToolResponse(message, options) {
2099
+ const response = {
2100
+ contentItems: [{
2101
+ type: "inputText",
2102
+ text: message
2103
+ }],
2104
+ success: false
2105
+ };
2106
+ Object.defineProperties(response, {
2107
+ diagnosticTerminalReason: {
2108
+ configurable: true,
2109
+ enumerable: false,
2110
+ value: options?.terminalReason ?? "failed"
2111
+ },
2112
+ diagnosticTerminalType: {
2113
+ configurable: true,
2114
+ enumerable: false,
2115
+ value: "error"
2116
+ }
2117
+ });
2118
+ if (options?.executionStarted !== void 0) Object.defineProperty(response, "executionStarted", {
2119
+ configurable: true,
2120
+ enumerable: false,
2121
+ value: options.executionStarted
2122
+ });
2123
+ if (options?.executedArguments !== void 0) Object.defineProperty(response, "executedArguments", {
2124
+ configurable: true,
2125
+ enumerable: false,
2126
+ value: options.executedArguments
2127
+ });
2128
+ return withDynamicToolSideEffectEvidence(response, options?.sideEffectEvidence === true);
2129
+ }
2130
+ //#endregion
2131
+ //#region extensions/codex/src/app-server/tool-abort-terminal-reason.ts
2132
+ /** Leaf helper shared by native and dynamic tool diagnostics. */
2133
+ const CODEX_TIMEOUT_ABORT_REASONS = /* @__PURE__ */ new Set([
2134
+ "codex_startup_timeout",
2135
+ "turn_completion_idle_timeout",
2136
+ "turn_progress_idle_timeout",
2137
+ "turn_terminal_idle_timeout"
2138
+ ]);
2139
+ /** Preserves timeout provenance when an enclosing run aborts an active tool. */
2140
+ function resolveCodexToolAbortTerminalReason(signal) {
2141
+ try {
2142
+ const reason = signal.reason;
2143
+ if (typeof reason === "string") {
2144
+ if (CODEX_TIMEOUT_ABORT_REASONS.has(reason)) return "timed_out";
2145
+ return reason === "client_closed" ? "failed" : "cancelled";
2146
+ }
2147
+ if (reason && typeof reason === "object") {
2148
+ const record = reason;
2149
+ if (record.name === "TimeoutError" || record.reason === "timeout") return "timed_out";
2150
+ }
2151
+ } catch {
2152
+ return "cancelled";
2153
+ }
2154
+ return "cancelled";
2155
+ }
2156
+ //#endregion
2157
+ //#region extensions/codex/src/app-server/dynamic-tool-execution.ts
2158
+ /**
2159
+ * Timeout, terminal-release, and diagnostic helpers for Codex dynamic tool
2160
+ * calls.
2161
+ */
2162
+ /** Default timeout for Codex dynamic tool calls. */
2163
+ const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 9e4;
2164
+ /** Hard cap for per-call Codex dynamic tool timeout overrides. */
2165
+ const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
2166
+ const CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS = 3e4;
2167
+ const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
2168
+ const CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS = 3e4;
2169
+ const CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS = 3e4;
2170
+ /** Timeout for image-understanding style dynamic tool calls. */
2171
+ const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
2172
+ /** Timeout for message-delivery dynamic tool calls. */
2173
+ const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 6e5;
2174
+ /** Outer default for collector waits: full swarm budget plus completion grace. */
2175
+ const CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS = 63e4;
2176
+ const LOG_FIELD_MAX_LENGTH = 160;
2177
+ function normalizeLogField(value) {
2178
+ if (typeof value !== "string") return;
2179
+ const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
2180
+ if (!normalized) return;
2181
+ return normalized.length > LOG_FIELD_MAX_LENGTH ? `${truncateUtf16Safe(normalized, LOG_FIELD_MAX_LENGTH - 3)}...` : normalized;
2182
+ }
2183
+ function readNumericTimeoutMs(value) {
2184
+ if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
2185
+ if (typeof value === "string") {
2186
+ const parsed = parseStrictNonNegativeInteger(value);
2187
+ if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
2188
+ }
2189
+ }
2190
+ function formatDynamicToolTimeoutDetails(params) {
2191
+ const tool = normalizeLogField(params.call.tool) ?? "unknown";
2192
+ const baseMeta = {
2193
+ tool: params.call.tool,
2194
+ toolCallId: params.call.callId,
2195
+ threadId: params.call.threadId,
2196
+ turnId: params.call.turnId,
2197
+ timeoutMs: params.timeoutMs,
2198
+ timeoutKind: "codex_dynamic_tool_rpc"
2199
+ };
2200
+ if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
2201
+ responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
2202
+ consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
2203
+ meta: baseMeta
2204
+ };
2205
+ const action = normalizeLogField(params.call.arguments.action);
2206
+ const sessionId = normalizeLogField(params.call.arguments.sessionId);
2207
+ const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
2208
+ const actionPart = action ? ` action=${action}` : "";
2209
+ const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
2210
+ const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
2211
+ const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
2212
+ const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
2213
+ return {
2214
+ responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
2215
+ consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
2216
+ meta: {
2217
+ ...baseMeta,
2218
+ processAction: action,
2219
+ processSessionId: sessionId,
2220
+ processRequestedTimeoutMs: requestedTimeoutMs
2221
+ }
2222
+ };
2223
+ }
2224
+ /**
2225
+ * Runs a dynamic tool call with run-abort and the budget prepared by
2226
+ * resolveDynamicToolCallTimeoutMs, preserving tool-specific completion grace.
2227
+ */
2228
+ async function handleDynamicToolCallWithTimeout(params) {
2229
+ let didNotifyAgentToolResult = false;
2230
+ const conservativeRaceResponses = /* @__PURE__ */ new WeakSet();
2231
+ const finalizeTerminal = (response) => {
2232
+ const executionSnapshot = params.toolBridge.consumeToolExecutionSnapshot?.(params.call.callId);
2233
+ const ownerKey = params.toolBridge.sideEffectOwnerKeyForTool?.(params.call.tool);
2234
+ const observedExecutionStarted = executionSnapshot?.executionStarted ?? (conservativeRaceResponses.has(response) ? void 0 : response.executionStarted);
2235
+ const terminalResolution = params.observeToolTerminal?.({
2236
+ toolCallId: params.call.callId,
2237
+ toolName: params.call.tool,
2238
+ arguments: response.executedArguments ?? executionSnapshot?.executedArguments ?? params.call.arguments,
2239
+ ...params.toolMeta ? { meta: params.toolMeta } : {},
2240
+ ...ownerKey ? { ownerMutation: { ownerKey } } : {},
2241
+ ...observedExecutionStarted !== void 0 ? { executionStarted: observedExecutionStarted } : {},
2242
+ outcome: response.success ? "success" : "failure",
2243
+ ...!response.success ? { failure: { error: readDynamicToolResponseText(response) } } : {}
2244
+ });
2245
+ return withDynamicToolTerminalResolution(response, terminalResolution);
2246
+ };
2247
+ const createFailedAfterPossibleDispatch = (message, terminalReason) => {
2248
+ const response = createFailedDynamicToolResponse(message, {
2249
+ executionStarted: true,
2250
+ sideEffectEvidence: true,
2251
+ terminalReason
2252
+ });
2253
+ conservativeRaceResponses.add(response);
2254
+ return response;
2255
+ };
2256
+ const notifyAgentToolResult = (event) => {
2257
+ if (didNotifyAgentToolResult) return;
2258
+ didNotifyAgentToolResult = true;
2259
+ try {
2260
+ params.onAgentToolResult?.(event);
2261
+ } catch (error) {
2262
+ embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
2263
+ }
2264
+ };
2265
+ const notifyFailedToolResult = (message, terminalReason = "failed") => {
2266
+ notifyAgentToolResult({
2267
+ toolName: params.call.tool,
2268
+ result: {
2269
+ content: [{
2270
+ type: "text",
2271
+ text: message
2272
+ }],
2273
+ details: {
2274
+ status: terminalReason,
2275
+ error: message
2276
+ }
2277
+ },
2278
+ isError: true
2279
+ });
2280
+ };
2281
+ if (params.signal.aborted) {
2282
+ const message = "OpenClaw dynamic tool call aborted before execution.";
2283
+ const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
2284
+ params.onFallbackSelected?.();
2285
+ notifyFailedToolResult(message, terminalReason);
2286
+ return finalizeTerminal(createFailedDynamicToolResponse(message, {
2287
+ executionStarted: false,
2288
+ terminalReason
2289
+ }));
2290
+ }
2291
+ const controller = new AbortController();
2292
+ let timeout;
2293
+ let timedOut = false;
2294
+ let resolveAbort;
2295
+ const abortFromRun = () => {
2296
+ const message = "OpenClaw dynamic tool call aborted.";
2297
+ const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
2298
+ params.onFallbackSelected?.();
2299
+ controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
2300
+ notifyFailedToolResult(message, terminalReason);
2301
+ resolveAbort?.(createFailedAfterPossibleDispatch(message, terminalReason));
2302
+ };
2303
+ const abortPromise = new Promise((resolve) => {
2304
+ resolveAbort = resolve;
2305
+ });
2306
+ const timeoutPromise = new Promise((resolve) => {
2307
+ const { timeoutMs } = params;
2308
+ timeout = setTimeout(() => {
2309
+ timedOut = true;
2310
+ const timeoutDetails = formatDynamicToolTimeoutDetails({
2311
+ call: params.call,
2312
+ timeoutMs
2313
+ });
2314
+ params.onFallbackSelected?.();
2315
+ controller.abort(new Error(timeoutDetails.responseMessage));
2316
+ params.onTimeout?.();
2317
+ embeddedAgentLog.warn("codex dynamic tool call timed out", {
2318
+ ...timeoutDetails.meta,
2319
+ consoleMessage: timeoutDetails.consoleMessage
2320
+ });
2321
+ notifyFailedToolResult(timeoutDetails.responseMessage, "timed_out");
2322
+ resolve(createFailedAfterPossibleDispatch(timeoutDetails.responseMessage, "timed_out"));
2323
+ }, timeoutMs);
2324
+ timeout.unref?.();
2325
+ });
2326
+ try {
2327
+ params.signal.addEventListener("abort", abortFromRun, { once: true });
2328
+ if (params.signal.aborted) abortFromRun();
2329
+ const response = await Promise.race([
2330
+ params.toolBridge.handleToolCall(params.call, {
2331
+ signal: controller.signal,
2332
+ onAgentToolResult: notifyAgentToolResult,
2333
+ toolCallOrdinal: params.toolCallOrdinal,
2334
+ retainExecutionSnapshot: true
2335
+ }),
2336
+ abortPromise,
2337
+ timeoutPromise
2338
+ ]);
2339
+ if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response), response.diagnosticTerminalReason ?? "failed");
2340
+ return finalizeTerminal(response);
2341
+ } catch (error) {
2342
+ const terminalReason = params.signal.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : resolveToolExecutionErrorKind(error);
2343
+ const message = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
2344
+ notifyFailedToolResult(message, terminalReason);
2345
+ return finalizeTerminal(createFailedAfterPossibleDispatch(message, terminalReason));
2346
+ } finally {
2347
+ if (timeout) clearTimeout(timeout);
2348
+ params.signal.removeEventListener("abort", abortFromRun);
2349
+ resolveAbort = void 0;
2350
+ if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
2351
+ }
2352
+ }
2353
+ function readDynamicToolResponseText(response) {
2354
+ return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
2355
+ }
2356
+ /** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
2357
+ function toCodexDynamicToolProtocolResponse(response) {
2358
+ return {
2359
+ contentItems: response.contentItems,
2360
+ success: response.success
2361
+ };
2362
+ }
2363
+ /** Adds async-started progress details when a tool result continues out of band. */
2364
+ function toCodexDynamicToolProgressResponse(response, protocolResponse) {
2365
+ const transcriptDetails = isJsonObject(response.transcriptDetails) ? response.transcriptDetails : void 0;
2366
+ const mcpAppPreview = isJsonObject(transcriptDetails?.mcpAppPreview) ? transcriptDetails.mcpAppPreview : void 0;
2367
+ const progressDetails = mcpAppPreview ? { mcpAppPreview } : void 0;
2368
+ if (response.asyncStarted !== true && progressDetails === void 0) return protocolResponse;
2369
+ return {
2370
+ ...protocolResponse,
2371
+ ...progressDetails ? { details: progressDetails } : {},
2372
+ ...response.asyncStarted === true ? { details: {
2373
+ ...progressDetails,
2374
+ async: true,
2375
+ status: "started"
2376
+ } } : {}
2377
+ };
2378
+ }
2379
+ /** Decides whether a terminal dynamic tool response can release the Codex turn. */
2380
+ function shouldReleaseTurnAfterTerminalDynamicTool(state) {
2381
+ return !state.completed && !state.aborted && state.responseSuccess && !state.currentTurnHadNonTerminalDynamicToolResult && state.activeAppServerTurnRequests === 0 && state.activeTurnItemIdsCount === 0 && state.pendingOpenClawDynamicToolCompletionIdsCount === 0;
2382
+ }
2383
+ /** Returns true when a non-async result should block terminal-release shortcuts. */
2384
+ function shouldBlockTerminalReleaseForNonTerminalDynamicToolResult(response) {
2385
+ return response.asyncStarted !== true;
2386
+ }
2387
+ /** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
2388
+ function resolveTerminalDynamicToolBatchAction(state) {
2389
+ if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
2390
+ if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
2391
+ if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
2392
+ return "idle";
2393
+ }
2394
+ /** Returns true for diagnostic events that terminate a dynamic tool call. */
2395
+ function isDynamicToolTerminalDiagnosticEvent(event) {
2396
+ return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
2397
+ }
2398
+ /** Matches terminal diagnostics to a specific dynamic tool call id/name. */
2399
+ function isMatchingDynamicToolTerminalDiagnostic(params) {
2400
+ if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
2401
+ if (params.runId !== void 0) return params.event.runId === params.runId;
2402
+ if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
2403
+ if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
2404
+ return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
2405
+ }
2406
+ /** Checks pending diagnostics for a terminal event matching a tool call. */
2407
+ function hasPendingDynamicToolTerminalDiagnostic(params) {
2408
+ return hasPendingInternalDiagnosticEvent((event) => {
2409
+ if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
2410
+ return isMatchingDynamicToolTerminalDiagnostic({
2411
+ event,
2412
+ call: params.call,
2413
+ runId: params.runId,
2414
+ sessionId: params.sessionId,
2415
+ sessionKey: params.sessionKey
2416
+ });
2417
+ });
2418
+ }
2419
+ /** Resolves per-tool timeout, applying media/message defaults and hard caps. */
2420
+ function resolveDynamicToolCallTimeoutMs(params) {
2421
+ const args = isJsonObject(params.call.arguments) ? params.call.arguments : void 0;
2422
+ if (params.call.tool === "ask_user" || params.call.tool === "secrets" && args?.action === "request") try {
2423
+ return normalizeQuestionTimeoutSeconds(args?.timeoutSeconds) * 1e3 + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
2424
+ } catch {
2425
+ return CODEX_DYNAMIC_TOOL_TIMEOUT_MS;
2426
+ }
2427
+ if (params.call.tool === "computer") return clampDynamicToolTimeoutMs(readComputerToolTimeoutMs(params.call.arguments));
2428
+ if (params.call.tool === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
2429
+ if (params.call.tool === "agents_wait") {
2430
+ const requestedMs = readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS;
2431
+ return Math.max(1, Math.min(63e4, Math.floor(requestedMs)));
2432
+ }
2433
+ return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_TOOL_TIMEOUT_MS);
2434
+ }
2435
+ /** Transport guard stays outside the handler's bounded tool wait and completion grace. */
2436
+ function resolveDynamicToolServerRequestTimeoutMs(call) {
2437
+ return Math.max(63e4, call ? resolveDynamicToolCallTimeoutMs({
2438
+ call,
2439
+ config: void 0
2440
+ }) : 0) + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
2441
+ }
2442
+ function readComputerToolTimeoutMs(value) {
2443
+ const args = isJsonObject(value) ? value : void 0;
2444
+ const action = typeof args?.action === "string" ? args.action : void 0;
2445
+ const gatewayTimeoutMs = readPositiveFiniteTimeoutMs(args?.timeoutMs) ?? CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS;
2446
+ const gatewayCallCount = action === "screenshot" || action === "wait" ? 3 : 4;
2447
+ return (action === "wait" || action === "hold_key" ? Math.max(0, Number(args?.duration) || 0) * 1e3 : 0) + gatewayCallCount * gatewayTimeoutMs + CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS;
2448
+ }
2449
+ function readDynamicToolCallTimeoutMs(value) {
2450
+ if (!isJsonObject(value)) return;
2451
+ const timeoutMs = readPositiveFiniteTimeoutMs(value.timeoutMs);
2452
+ if (timeoutMs !== void 0) return timeoutMs;
2453
+ const timeoutSecondsMs = readDynamicToolTimeoutSecondsAsMs(value.timeoutSeconds);
2454
+ return timeoutSecondsMs === void 0 ? void 0 : addTimerTimeoutGraceMs(timeoutSecondsMs, CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS);
2455
+ }
2456
+ function readConfiguredDynamicToolTimeoutMs(toolName, config) {
2457
+ if (toolName === "image_generate") {
2458
+ const imageModel = config?.agents?.defaults?.mediaModels?.image;
2459
+ if (!imageModel || typeof imageModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
2460
+ return readPositiveFiniteTimeoutMs(imageModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
2461
+ }
2462
+ if (toolName === "view_image") {
2463
+ const candidates = (config?.tools?.media?.models ?? []).filter((entry) => !entry.capabilities || entry.capabilities.includes("image"));
2464
+ const capabilityTimeoutMs = readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds);
2465
+ return Math.max(capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS, ...candidates.map((entry) => readTimeoutSecondsAsMs(entry.timeoutSeconds) ?? capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS));
2466
+ }
2467
+ if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
2468
+ }
2469
+ function readTimeoutSecondsAsMs(value) {
2470
+ const seconds = readPositiveFiniteTimeoutMs(value);
2471
+ return seconds === void 0 ? void 0 : seconds * 1e3;
2472
+ }
2473
+ function readDynamicToolTimeoutSecondsAsMs(value) {
2474
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) return;
2475
+ return value * 1e3;
2476
+ }
2477
+ function readPositiveFiniteTimeoutMs(value) {
2478
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
2479
+ }
2480
+ function clampDynamicToolTimeoutMs(timeoutMs) {
2481
+ return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
2482
+ }
2483
+ //#endregion
2484
+ //#region extensions/codex/src/app-server/elicitation-response.ts
2485
+ function createCodexElicitationResponse(action, content = null, meta = null) {
2486
+ return {
2487
+ action,
2488
+ content,
2489
+ _meta: meta
2490
+ };
2491
+ }
2492
+ //#endregion
1365
2493
  //#region extensions/codex/src/app-server/rpc-error.ts
1366
2494
  /** RPC error wrapper that preserves app-server error code and data. */
1367
2495
  var CodexAppServerRpcError = class extends Error {
@@ -1558,71 +2686,6 @@ function websocketFrameToText(data) {
1558
2686
  return Buffer.from(data).toString("utf8");
1559
2687
  }
1560
2688
  //#endregion
1561
- //#region extensions/codex/src/app-server/transport.ts
1562
- /** Starts graceful transport shutdown and schedules a force kill fallback. */
1563
- function closeCodexAppServerTransport(child, options = {}) {
1564
- child.stdin.end?.();
1565
- child.stdin.destroy?.();
1566
- const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
1567
- const forceKill = setTimeout(() => {
1568
- if (hasCodexAppServerTransportExited(child)) return;
1569
- signalCodexAppServerTransport(child, "SIGKILL");
1570
- }, Math.max(1, forceKillDelayMs));
1571
- forceKill.unref?.();
1572
- child.once("exit", () => {
1573
- clearTimeout(forceKill);
1574
- child.stdout.destroy?.();
1575
- child.stderr.destroy?.();
1576
- });
1577
- child.unref?.();
1578
- child.stdout.unref?.();
1579
- child.stderr.unref?.();
1580
- child.stdin.unref?.();
1581
- }
1582
- /** Closes a transport and waits briefly for an exit event. */
1583
- async function closeCodexAppServerTransportAndWait(child, options = {}) {
1584
- if (!hasCodexAppServerTransportExited(child)) closeCodexAppServerTransport(child, options);
1585
- return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
1586
- }
1587
- function hasCodexAppServerTransportExited(child) {
1588
- return child.exitCode !== null && child.exitCode !== void 0 ? true : child.signalCode !== null && child.signalCode !== void 0;
1589
- }
1590
- async function waitForCodexAppServerTransportExit(child, timeoutMs) {
1591
- if (hasCodexAppServerTransportExited(child)) return true;
1592
- return await new Promise((resolve) => {
1593
- let settled = false;
1594
- const onExit = () => {
1595
- if (settled) return;
1596
- settled = true;
1597
- clearTimeout(timeout);
1598
- resolve(true);
1599
- };
1600
- const timeout = setTimeout(() => {
1601
- if (settled) return;
1602
- settled = true;
1603
- child.off?.("exit", onExit);
1604
- resolve(false);
1605
- }, Math.max(1, timeoutMs));
1606
- child.once("exit", onExit);
1607
- });
1608
- }
1609
- function signalCodexAppServerTransport(child, signal) {
1610
- if (child.pid && process.platform !== "win32") try {
1611
- process.kill(-child.pid, signal);
1612
- return;
1613
- } catch {}
1614
- child.kill?.(signal);
1615
- }
1616
- //#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
2689
  //#region extensions/codex/src/app-server/client.ts
1627
2690
  /**
1628
2691
  * JSON-RPC client for Codex app-server transports, including request/response
@@ -1631,11 +2694,11 @@ const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
1631
2694
  const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
1632
2695
  const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 8 * 1024 * 1024;
1633
2696
  const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1e3;
1634
- const CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS = 66e4;
1635
2697
  const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
1636
2698
  const CODEX_APP_SERVER_OVERLOADED_ERROR_CODE = -32001;
1637
2699
  const CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES = 3;
1638
2700
  const CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS = 50;
2701
+ const CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX = 32;
1639
2702
  const CODEX_APP_SERVER_CLIENT_INSTANCE_IDS = /* @__PURE__ */ new WeakMap();
1640
2703
  const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
1641
2704
  /** Process-local generation fence for bindings tied to one app-server client instance. */
@@ -1649,6 +2712,10 @@ function getCodexAppServerClientInstanceId(client) {
1649
2712
  function resolveCodexAppServerClientInstanceId(client) {
1650
2713
  return client.getInstanceId?.call(client) ?? getCodexAppServerClientInstanceId(client);
1651
2714
  }
2715
+ /** Codex rejects this exact code before enqueueing, including mutating requests. */
2716
+ function isCodexAppServerOverloadError(error) {
2717
+ return error instanceof CodexAppServerRpcError && error.code === CODEX_APP_SERVER_OVERLOADED_ERROR_CODE;
2718
+ }
1652
2719
  var CodexAppServerLocalRequestCancellationError = class extends Error {
1653
2720
  constructor(method, reason, mayHaveWritten) {
1654
2721
  super(`${method} ${reason}`);
@@ -1704,9 +2771,11 @@ var CodexAppServerClient = class CodexAppServerClient {
1704
2771
  this.pending = /* @__PURE__ */ new Map();
1705
2772
  this.requestHandlers = /* @__PURE__ */ new Set();
1706
2773
  this.notificationHandlers = /* @__PURE__ */ new Set();
2774
+ this.pendingStartupWarnings = [];
1707
2775
  this.closeHandlers = /* @__PURE__ */ new Set();
1708
2776
  this.nextId = 1;
1709
2777
  this.initialized = false;
2778
+ this.modelCatalogRevision = 0;
1710
2779
  this.closed = false;
1711
2780
  this.transportExited = false;
1712
2781
  this.stderrTail = "";
@@ -1732,7 +2801,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1732
2801
  child.stdin.on?.("error", (error) => this.closeWithError(toStringifiedError(error)));
1733
2802
  }
1734
2803
  /** Starts a new app-server client using resolved runtime start options. */
1735
- static start(options) {
2804
+ static async start(options, assertCurrent) {
1736
2805
  const defaults = resolveCodexAppServerRuntimeOptions().start;
1737
2806
  const startOptions = {
1738
2807
  ...defaults,
@@ -1741,7 +2810,18 @@ var CodexAppServerClient = class CodexAppServerClient {
1741
2810
  };
1742
2811
  if (startOptions.transport === "stdio" && startOptions.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
1743
2812
  if (startOptions.transport === "websocket" || startOptions.transport === "unix") return new CodexAppServerClient(createWebSocketTransport(startOptions));
1744
- return new CodexAppServerClient(createStdioTransport(startOptions));
2813
+ let client;
2814
+ try {
2815
+ await createStdioTransport(startOptions, process.env, assertCurrent, (child) => {
2816
+ client = new CodexAppServerClient(child);
2817
+ });
2818
+ return client;
2819
+ } catch (error) {
2820
+ assertCurrent?.();
2821
+ if (client?.transportExited && hasCodexAppServerNaturalExit(client.child)) throw buildCodexAppServerExitError(client.child.exitCode, client.child.signalCode, client.stderrTail);
2822
+ const stderr = client?.getStderrDiagnostic();
2823
+ throw stderr ? new Error(`${coerceErrorMessage(error)}; stderr=${JSON.stringify(stderr)}`, { cause: error }) : error;
2824
+ }
1745
2825
  }
1746
2826
  /** Builds a client around a fake transport for tests. */
1747
2827
  static fromTransportForTests(child) {
@@ -1756,7 +2836,14 @@ var CodexAppServerClient = class CodexAppServerClient {
1756
2836
  title: "OpenClaw",
1757
2837
  version: OPENCLAW_VERSION
1758
2838
  },
1759
- capabilities: { experimentalApi: true }
2839
+ capabilities: {
2840
+ experimentalApi: true,
2841
+ extensions: {
2842
+ "openai/standard-form-input": {},
2843
+ "openai/form": {},
2844
+ "io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app"] }
2845
+ }
2846
+ }
1760
2847
  });
1761
2848
  this.serverVersion = assertSupportedCodexAppServerVersion(response);
1762
2849
  this.runtimeIdentity = buildCodexAppServerRuntimeIdentity(response, this.serverVersion);
@@ -1783,6 +2870,10 @@ var CodexAppServerClient = class CodexAppServerClient {
1783
2870
  getInstanceId() {
1784
2871
  return this.instanceId;
1785
2872
  }
2873
+ /** Account/config observations become stale before a mutation can enter the wire. */
2874
+ getModelCatalogRevision() {
2875
+ return this.modelCatalogRevision;
2876
+ }
1786
2877
  /** Installs the spawn-owner check run before config-loading thread requests. */
1787
2878
  setThreadSessionRequestGuard(guard) {
1788
2879
  this.threadSessionRequestGuard = guard;
@@ -1792,8 +2883,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1792
2883
  return this.child.pid;
1793
2884
  }
1794
2885
  request(method, params, optionsInput) {
1795
- let options = optionsInput;
1796
- options ??= {};
2886
+ const options = optionsInput ?? {};
1797
2887
  if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
1798
2888
  if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
1799
2889
  const guard = method === "thread/start" || method === "thread/resume" || method === "thread/fork" ? this.threadSessionRequestGuard : void 0;
@@ -1828,11 +2918,11 @@ var CodexAppServerClient = class CodexAppServerClient {
1828
2918
  const elapsedMs = Date.now() - guardStartedAt;
1829
2919
  const remainingTimeoutMs = options.timeoutMs === void 0 ? void 0 : options.timeoutMs - elapsedMs;
1830
2920
  if (remainingTimeoutMs !== void 0 && remainingTimeoutMs <= 0) throw new CodexAppServerLocalRequestCancellationError(method, "timed out", false);
1831
- return await this.requestWithoutThreadSessionGuard(method, params, {
2921
+ return await this.requestWithOverloadRetry(method, params, {
1832
2922
  ...options,
1833
2923
  ...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
1834
- }, () => {
1835
- requestMayHaveWritten = true;
2924
+ }, (mayHaveWritten) => {
2925
+ requestMayHaveWritten = mayHaveWritten;
1836
2926
  });
1837
2927
  } catch (error) {
1838
2928
  if (requestMayHaveWritten && !(error instanceof CodexAppServerRpcError)) {
@@ -1845,12 +2935,9 @@ var CodexAppServerClient = class CodexAppServerClient {
1845
2935
  }
1846
2936
  })();
1847
2937
  }
1848
- return this.requestWithoutThreadSessionGuard(method, params, options);
1849
- }
1850
- requestWithoutThreadSessionGuard(method, params, options, onWriteAttempt) {
1851
- return this.requestWithOverloadRetry(method, params, options, onWriteAttempt);
2938
+ return this.requestWithOverloadRetry(method, params, options);
1852
2939
  }
1853
- async requestWithOverloadRetry(method, params, options, onWriteAttempt) {
2940
+ async requestWithOverloadRetry(method, params, options, onWriteStateChange) {
1854
2941
  const deadline = options.timeoutMs !== void 0 && Number.isFinite(options.timeoutMs) ? Date.now() + options.timeoutMs : void 0;
1855
2942
  for (let retry = 0;; retry += 1) {
1856
2943
  if (options.signal?.aborted) throw new CodexAppServerLocalRequestCancellationError(method, "aborted", false);
@@ -1860,9 +2947,10 @@ var CodexAppServerClient = class CodexAppServerClient {
1860
2947
  return await this.requestOnce(method, params, {
1861
2948
  ...options,
1862
2949
  ...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
1863
- }, onWriteAttempt);
2950
+ }, onWriteStateChange);
1864
2951
  } catch (error) {
1865
- if (!(error instanceof CodexAppServerRpcError) || error.code !== CODEX_APP_SERVER_OVERLOADED_ERROR_CODE || retry >= CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES) throw error;
2952
+ if (!isCodexAppServerOverloadError(error) || retry >= CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES) throw error;
2953
+ onWriteStateChange?.(false);
1866
2954
  const backoffMs = Math.round(CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS * 2 ** retry * (.75 + Math.random() * .5));
1867
2955
  await this.waitForOverloadRetry(method, backoffMs, deadline, options.signal);
1868
2956
  }
@@ -1891,10 +2979,11 @@ var CodexAppServerClient = class CodexAppServerClient {
1891
2979
  if (signal?.aborted) abortListener();
1892
2980
  });
1893
2981
  }
1894
- requestOnce(method, params, options, onWriteAttempt) {
2982
+ requestOnce(method, params, options, onWriteStateChange) {
1895
2983
  if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
1896
2984
  if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
1897
2985
  const id = this.nextId++;
2986
+ if (method === "account/login/start" || method === "account/logout" || method === "config/value/write" || method === "config/batchWrite") this.modelCatalogRevision += 1;
1898
2987
  const message = {
1899
2988
  id,
1900
2989
  method,
@@ -1944,8 +3033,9 @@ var CodexAppServerClient = class CodexAppServerClient {
1944
3033
  return;
1945
3034
  }
1946
3035
  try {
3036
+ options.assertCurrent?.();
1947
3037
  mayHaveWritten = true;
1948
- onWriteAttempt?.();
3038
+ onWriteStateChange?.(true);
1949
3039
  this.writeMessage(message, (error) => rejectPending(error));
1950
3040
  } catch (error) {
1951
3041
  rejectPending(toStringifiedError(error));
@@ -1967,6 +3057,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1967
3057
  /** Registers a notification handler and returns its disposer. */
1968
3058
  addNotificationHandler(handler) {
1969
3059
  this.notificationHandlers.add(handler);
3060
+ for (const notification of this.pendingStartupWarnings.splice(0)) this.handleNotification(notification);
1970
3061
  return () => this.notificationHandlers.delete(handler);
1971
3062
  }
1972
3063
  /** Registers a close handler and returns its disposer. */
@@ -1974,6 +3065,16 @@ var CodexAppServerClient = class CodexAppServerClient {
1974
3065
  this.closeHandlers.add(handler);
1975
3066
  return () => this.closeHandlers.delete(handler);
1976
3067
  }
3068
+ /** Registers a handler for physical transport exit and returns its disposer. */
3069
+ addTransportExitHandler(handler) {
3070
+ if (this.transportExited) {
3071
+ handler(this);
3072
+ return () => void 0;
3073
+ }
3074
+ const onExit = () => handler(this);
3075
+ this.child.once("exit", onExit);
3076
+ return () => this.child.off?.("exit", onExit);
3077
+ }
1977
3078
  /** Closes the transport without waiting for process/socket shutdown. */
1978
3079
  close() {
1979
3080
  if (!this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"))) return;
@@ -2136,8 +3237,9 @@ var CodexAppServerClient = class CodexAppServerClient {
2136
3237
  }
2137
3238
  async runServerRequestHandlers(request) {
2138
3239
  const controller = new AbortController();
2139
- const timeoutResponse = timeoutServerRequestResponse(request);
2140
- if (!timeoutResponse) return await this.runServerRequestHandlersWithoutTimeout(request, controller.signal);
3240
+ if (request.method !== "item/tool/call") return await this.runServerRequestHandlersWithoutTimeout(request, controller.signal);
3241
+ const timeoutMs = resolveDynamicToolServerRequestTimeoutMs(readCodexDynamicToolCallParams(request.params));
3242
+ const timeoutResponse = timeoutServerRequestResponse(timeoutMs);
2141
3243
  let timeout;
2142
3244
  try {
2143
3245
  return await Promise.race([this.runServerRequestHandlersWithoutTimeout(request, controller.signal), new Promise((resolve) => {
@@ -2145,11 +3247,11 @@ var CodexAppServerClient = class CodexAppServerClient {
2145
3247
  embeddedAgentLog.warn("codex app-server server request timed out", {
2146
3248
  id: request.id,
2147
3249
  method: request.method,
2148
- timeoutMs: CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS
3250
+ timeoutMs
2149
3251
  });
2150
3252
  controller.abort(/* @__PURE__ */ new Error("codex app-server server request timed out"));
2151
3253
  resolve(timeoutResponse);
2152
- }, CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS);
3254
+ }, timeoutMs);
2153
3255
  timeout.unref?.();
2154
3256
  })]);
2155
3257
  } finally {
@@ -2164,6 +3266,12 @@ var CodexAppServerClient = class CodexAppServerClient {
2164
3266
  }
2165
3267
  }
2166
3268
  handleNotification(notification) {
3269
+ if (notification.method === "account/updated") this.modelCatalogRevision += 1;
3270
+ if (this.notificationHandlers.size === 0 && notification.method === "configWarning") {
3271
+ if (this.pendingStartupWarnings.length === CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX) this.pendingStartupWarnings.shift();
3272
+ this.pendingStartupWarnings.push(notification);
3273
+ return;
3274
+ }
2167
3275
  for (const handler of this.notificationHandlers) try {
2168
3276
  Promise.resolve(handler(notification)).catch((error) => {
2169
3277
  embeddedAgentLog.warn("codex app-server notification handler failed", { error });
@@ -2206,18 +3314,17 @@ function defaultServerRequestResponse(request) {
2206
3314
  scope: "turn"
2207
3315
  };
2208
3316
  if (request.method === "item/tool/requestUserInput") return { answers: {} };
2209
- if (request.method === "mcpServer/elicitation/request") return { action: "decline" };
3317
+ if (request.method === "mcpServer/elicitation/request") return createCodexElicitationResponse("decline", null, { message: "OpenClaw has no interactive handler for this elicitation." });
2210
3318
  return {};
2211
3319
  }
2212
3320
  function stringifyCodexAppServerMessage(message) {
2213
3321
  return JSON.stringify(message, (_key, value) => typeof value === "string" ? value.replace(UNPAIRED_SURROGATE_RE, "") : value) ?? "null";
2214
3322
  }
2215
- function timeoutServerRequestResponse(request) {
2216
- if (request.method !== "item/tool/call") return;
3323
+ function timeoutServerRequestResponse(timeoutMs) {
2217
3324
  return {
2218
3325
  contentItems: [{
2219
3326
  type: "inputText",
2220
- text: `OpenClaw dynamic tool call timed out after ${CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS}ms before sending a response to Codex.`
3327
+ text: `OpenClaw dynamic tool call timed out after ${timeoutMs}ms before sending a response to Codex.`
2221
3328
  }],
2222
3329
  success: false
2223
3330
  };
@@ -2226,14 +3333,20 @@ function timeoutServerRequestResponse(request) {
2226
3333
  var CodexAppServerVersionError = class extends Error {
2227
3334
  constructor(detectedVersion) {
2228
3335
  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.`);
3336
+ 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
3337
  this.name = "CodexAppServerVersionError";
2231
3338
  this.detectedVersion = detectedVersion;
2232
3339
  }
2233
3340
  };
2234
3341
  function assertSupportedCodexAppServerVersion(response) {
2235
3342
  const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
2236
- if (detectedVersion !== "0.147.0") throw new CodexAppServerVersionError(detectedVersion);
3343
+ if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
3344
+ const detected = parse(detectedVersion);
3345
+ if (!detected || detected.compare("0.149.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
3346
+ if (detected.compare("0.151.0") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
3347
+ detectedVersion,
3348
+ validatedVersion: CODEX_APP_SERVER_VERSION
3349
+ });
2237
3350
  return detectedVersion;
2238
3351
  }
2239
3352
  function isUnsupportedCodexAppServerVersionError(error) {
@@ -2316,6 +3429,8 @@ const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 5 * 6e4;
2316
3429
  const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 5 * 6e4;
2317
3430
  /** Long terminal idle watch for app-server turns that never send completion. */
2318
3431
  const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
3432
+ const TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS = 2 * 6e4;
3433
+ const TURN_FINALIZE_DRAIN_ABORT_GRACE_MS = 5e3;
2319
3434
  var CodexAppServerStartupError = class extends Error {
2320
3435
  constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
2321
3436
  super(message);
@@ -2397,204 +3512,322 @@ function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
2397
3512
  return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
2398
3513
  }
2399
3514
  //#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];
3515
+ //#region extensions/codex/src/app-server/desktop-generation-fingerprint.ts
3516
+ const MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES = 4096;
3517
+ /** Fingerprints every desktop candidate that can own a managed fallback artifact. */
3518
+ async function readMacOSDesktopGenerationFingerprint(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
3519
+ const entries = [];
3520
+ for (const candidate of candidates) {
3521
+ const command = await statFingerprint(candidate.appServerCommandPath);
3522
+ entries.push(`candidate:${candidate.appName}:${candidate.appServerCommandPath}:${command}`);
3523
+ for (const artifactPath of resolveMacOSDesktopGenerationPaths(candidate)) entries.push(`${artifactPath}\0${await statFingerprint(artifactPath)}`);
3524
+ const pluginRoot = resolveComputerUsePluginRoot(candidate);
3525
+ entries.push(`${pluginRoot}\0${await directoryTreeFingerprint(pluginRoot)}`);
3526
+ }
3527
+ return createHash("sha256").update(entries.join("\0")).digest("hex");
3528
+ }
3529
+ function resolveMacOSDesktopGenerationPaths(candidate) {
3530
+ return [
3531
+ candidate.appBundlePath,
3532
+ path.join(candidate.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"),
3533
+ ...candidate.computerUseServiceAppPaths.flatMap((servicePath) => [
3534
+ servicePath,
3535
+ path.join(servicePath, "Contents", "Info.plist"),
3536
+ path.join(servicePath, "Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient")
3537
+ ])
3538
+ ];
3539
+ }
3540
+ function resolveComputerUsePluginRoot(candidate) {
3541
+ return path.join(candidate.bundledMarketplacePath, "plugins", "computer-use");
3542
+ }
3543
+ /** Stable roots that cover bundle replacement and recursive artifact updates. */
3544
+ function resolveMacOSDesktopGenerationWatchPaths(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
3545
+ const watched = /* @__PURE__ */ new Set(["/Applications"]);
3546
+ for (const candidate of candidates) watched.add(candidate.appBundlePath);
3547
+ return [...watched];
3548
+ }
3549
+ async function directoryTreeFingerprint(root) {
3550
+ let rootStat;
2451
3551
  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;
3552
+ rootStat = await fs.lstat(root, { bigint: true });
3553
+ } catch (error) {
3554
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
3555
+ throw error;
3556
+ }
3557
+ if (!rootStat.isDirectory()) return statFingerprint(root);
3558
+ let entryCount = 0;
3559
+ const hash = createHash("sha256");
3560
+ hash.update("openclaw-codex-computer-use-plugin-tree-v1\0");
3561
+ const visit = async (directory, relativeDirectory, before) => {
3562
+ hash.update(`directory\0${relativeDirectory}\0${statTuple(before)}\0`);
3563
+ const entries = (await fs.readdir(directory, { withFileTypes: true })).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
3564
+ for (const entry of entries) {
3565
+ entryCount += 1;
3566
+ if (entryCount > MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES) throw new Error("Codex Computer Use plugin exceeds the bounded tree size");
3567
+ const entryPath = path.join(directory, entry.name);
3568
+ const relativePath = path.join(relativeDirectory, entry.name);
3569
+ const entryStat = await fs.lstat(entryPath, { bigint: true });
3570
+ if (entryStat.isDirectory()) await visit(entryPath, relativePath, entryStat);
3571
+ else hash.update(`entry\0${relativePath}\0${await statFingerprint(entryPath)}\0`);
2462
3572
  }
3573
+ if (!sameStat(before, await fs.lstat(directory, { bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${directory}`);
3574
+ };
3575
+ await visit(root, ".", rootStat);
3576
+ return hash.digest("hex");
3577
+ }
3578
+ async function statFingerprint(filePath) {
3579
+ try {
3580
+ const entry = await fs.lstat(filePath, { bigint: true });
3581
+ const type = entry.isSymbolicLink() ? "link" : entry.isDirectory() ? "directory" : entry.isFile() ? "file" : "other";
3582
+ const own = statTuple(entry);
3583
+ if (!entry.isSymbolicLink()) return `${type}:${own}:${entry.isFile() ? await readFileFingerprint(filePath, entry, false) : ""}`;
3584
+ const [link, realPath, target] = await Promise.all([
3585
+ fs.readlink(filePath),
3586
+ fs.realpath(filePath),
3587
+ fs.stat(filePath, { bigint: true })
3588
+ ]);
3589
+ const content = target.isFile() ? await readFileFingerprint(filePath, target, true) : "";
3590
+ return `${type}:${own}:${link}:${realPath}:${statTuple(target)}:${content}`;
3591
+ } catch (error) {
3592
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
3593
+ throw error;
2463
3594
  }
2464
3595
  }
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"
3596
+ async function readFileFingerprint(filePath, expected, followsSymlink) {
3597
+ const noFollow = followsSymlink ? 0 : constants.O_NOFOLLOW ?? 0;
3598
+ const handle = await fs.open(filePath, constants.O_RDONLY | noFollow);
3599
+ try {
3600
+ const before = await handle.stat({ bigint: true });
3601
+ if (!sameStat(before, expected)) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
3602
+ const hash = createHash("sha256");
3603
+ for await (const chunk of handle.createReadStream({ autoClose: false })) hash.update(chunk);
3604
+ if (!sameStat(before, await handle.stat({ bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
3605
+ return hash.digest("hex");
3606
+ } finally {
3607
+ await handle.close();
3608
+ }
3609
+ }
3610
+ function sameStat(left, right) {
3611
+ return statTuple(left) === statTuple(right);
3612
+ }
3613
+ function statTuple(stat) {
3614
+ return [
3615
+ stat.dev,
3616
+ stat.ino,
3617
+ stat.mode,
3618
+ stat.size,
3619
+ stat.mtimeNs,
3620
+ stat.ctimeNs
3621
+ ].join(":");
3622
+ }
3623
+ function isNodeError(error, code) {
3624
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
3625
+ }
3626
+ //#endregion
3627
+ //#region extensions/codex/src/app-server/desktop-generation-owner.ts
3628
+ const SETTLE_DELAY_MS = 1e3;
3629
+ /** Coalesces filesystem invalidations into one stable desktop generation. */
3630
+ function createCodexDesktopGenerationOwner(params) {
3631
+ let generation = params.initialGeneration;
3632
+ let invalidation = 0;
3633
+ let dirty = false;
3634
+ let refresh;
3635
+ let stopped = false;
3636
+ const markDirty = () => {
3637
+ invalidation += 1;
3638
+ dirty = true;
2481
3639
  };
2482
- if (platform === "win32" && arch === "x64") return {
2483
- packageName: "@openai/codex-win32-x64",
2484
- triple: "x86_64-pc-windows-msvc"
3640
+ const reconcile = () => {
3641
+ if (refresh) return refresh;
3642
+ refresh = (async () => {
3643
+ for (;;) {
3644
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3645
+ const observedInvalidation = invalidation;
3646
+ const first = await params.readFingerprint();
3647
+ await delay(SETTLE_DELAY_MS);
3648
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3649
+ const second = await params.readFingerprint();
3650
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3651
+ if (observedInvalidation !== invalidation || first !== second) continue;
3652
+ const previous = generation;
3653
+ generation = previous?.fingerprint === second ? previous : {
3654
+ epoch: (previous?.epoch ?? 0) + 1,
3655
+ fingerprint: second
3656
+ };
3657
+ dirty = false;
3658
+ if (previous && generation !== previous) params.onGenerationChange?.(generation);
3659
+ return generation;
3660
+ }
3661
+ })().finally(() => {
3662
+ refresh = void 0;
3663
+ });
3664
+ return refresh;
2485
3665
  };
2486
- if (platform === "win32" && arch === "arm64") return {
2487
- packageName: "@openai/codex-win32-arm64",
2488
- triple: "aarch64-pc-windows-msvc"
3666
+ return {
3667
+ read: () => generation,
3668
+ markDirty,
3669
+ wait: () => dirty ? reconcile() : Promise.resolve(generation),
3670
+ refresh: () => {
3671
+ markDirty();
3672
+ return reconcile();
3673
+ },
3674
+ isCurrent: (candidate) => Boolean(candidate && !dirty && generation && candidate.epoch === generation.epoch && candidate.fingerprint === generation.fingerprint),
3675
+ stop: () => {
3676
+ stopped = true;
3677
+ }
2489
3678
  };
2490
3679
  }
2491
- function resolvePackageJsonFromRoot(packageName, root) {
2492
- try {
2493
- return createRequire(path.join(root, "package.json")).resolve(`${packageName}/package.json`);
2494
- } catch {
2495
- return;
2496
- }
3680
+ function delay(ms) {
3681
+ return new Promise((resolve) => {
3682
+ setTimeout(resolve, ms);
3683
+ });
2497
3684
  }
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");
3685
+ //#endregion
3686
+ //#region extensions/codex/src/app-server/desktop-generation.ts
3687
+ /** Lifecycle-owned generation for managed macOS Codex desktop artifacts. */
3688
+ const APPLICATIONS_PATH = "/Applications";
3689
+ const REARM_INITIAL_DELAY_MS = 100;
3690
+ const REARM_MAX_DELAY_MS = 3e4;
3691
+ const DESKTOP_GENERATION_STATE = Symbol.for("openclaw.codexDesktopGenerationState");
3692
+ function state() {
3693
+ const globalState = globalThis;
3694
+ return globalState[DESKTOP_GENERATION_STATE] ??= {};
3695
+ }
3696
+ function waitForCodexDesktopGeneration() {
3697
+ return state().owner?.wait() ?? Promise.resolve(void 0);
3698
+ }
3699
+ function isCodexDesktopGenerationCurrent(generation) {
3700
+ return state().owner?.isCurrent(generation) ?? false;
3701
+ }
3702
+ function createCodexDesktopGenerationService(params, runtime = {
3703
+ platform: process.platform,
3704
+ readFingerprint: readMacOSDesktopGenerationFingerprint,
3705
+ resolveWatchPaths: resolveMacOSDesktopGenerationWatchPaths,
3706
+ pathExists: existsSync,
3707
+ watchPath: (watchedPath, options, listener) => watch(watchedPath, options, listener)
3708
+ }) {
2502
3709
  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;
3710
+ id: "codex-desktop-generation",
3711
+ async start(ctx) {
3712
+ if (runtime.platform !== "darwin") return;
3713
+ const current = state();
3714
+ current.context = ctx;
3715
+ current.readFingerprint = runtime.readFingerprint;
3716
+ current.resolveWatchPaths = runtime.resolveWatchPaths;
3717
+ current.pathExists = runtime.pathExists;
3718
+ current.watchPath = runtime.watchPath;
3719
+ current.owner = createCodexDesktopGenerationOwner({
3720
+ readFingerprint: current.readFingerprint,
3721
+ onGenerationChange: params.onGenerationChange,
3722
+ initialGeneration: current.lastGeneration
3723
+ });
3724
+ armWatchers(current);
3725
+ refreshGeneration(current, current.owner, current.owner.refresh());
3726
+ },
3727
+ async stop() {
3728
+ const current = state();
3729
+ current.lastGeneration = current.owner?.read() ?? current.lastGeneration;
3730
+ current.owner?.stop();
3731
+ current.owner = void 0;
3732
+ current.armEpoch = (current.armEpoch ?? 0) + 1;
3733
+ current.context = void 0;
3734
+ current.readFingerprint = void 0;
3735
+ current.resolveWatchPaths = void 0;
3736
+ current.pathExists = void 0;
3737
+ current.watchPath = void 0;
3738
+ current.watchHealthy = void 0;
3739
+ current.rearmDelayMs = void 0;
3740
+ if (current.rearmTimer) {
3741
+ clearTimeout(current.rearmTimer);
3742
+ current.rearmTimer = void 0;
2543
3743
  }
2544
- const parent = pathApi.dirname(current);
2545
- if (parent === current) break;
2546
- current = parent;
3744
+ closeWatchers(current);
2547
3745
  }
2548
- }
2549
- return projectRoots;
3746
+ };
2550
3747
  }
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);
3748
+ function armWatchers(current) {
3749
+ const owner = current.owner;
3750
+ if (!owner || current.watchers) return false;
3751
+ const armEpoch = (current.armEpoch ?? 0) + 1;
3752
+ current.armEpoch = armEpoch;
3753
+ const watchers = /* @__PURE__ */ new Set();
3754
+ current.watchers = watchers;
3755
+ const candidateNames = new Set(resolveMacOSDesktopCodexAppPathCandidates("darwin").map((candidate) => candidate.appName));
3756
+ let complete = true;
3757
+ for (const watchedPath of current.resolveWatchPaths?.() ?? []) {
3758
+ if (!current.pathExists?.(watchedPath)) continue;
3759
+ try {
3760
+ const watcher = current.watchPath?.(watchedPath, { recursive: watchedPath !== APPLICATIONS_PATH }, (_eventType, filename) => {
3761
+ if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
3762
+ if (watchedPath === APPLICATIONS_PATH && filename && !candidateNames.has(filename.toString().split(path.sep)[0] ?? "")) return;
3763
+ owner.markDirty();
3764
+ scheduleRearm(current, owner);
3765
+ });
3766
+ if (!watcher) {
3767
+ complete = false;
3768
+ reportWatcherFailure(current, owner, /* @__PURE__ */ new Error(`Could not watch ${watchedPath}`));
3769
+ scheduleRearm(current, owner);
3770
+ continue;
3771
+ }
3772
+ watchers.add(watcher);
3773
+ watcher.on("error", (error) => {
3774
+ if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
3775
+ reportWatcherFailure(current, owner, error);
3776
+ scheduleRearm(current, owner);
3777
+ });
3778
+ } catch (error) {
3779
+ complete = false;
3780
+ reportWatcherFailure(current, owner, error);
3781
+ scheduleRearm(current, owner);
3782
+ }
2557
3783
  }
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;
3784
+ current.watchHealthy = complete;
3785
+ if (complete) current.rearmDelayMs = REARM_INITIAL_DELAY_MS;
3786
+ return complete;
3787
+ }
3788
+ function reportWatcherFailure(current, owner, error) {
3789
+ if (current.watchHealthy === false) return;
3790
+ current.watchHealthy = false;
3791
+ owner.markDirty();
3792
+ current.context?.serviceHealth?.reportFailure(error);
3793
+ current.context?.logger.warn(`codex desktop generation watcher failed: ${String(error)}`);
3794
+ }
3795
+ function isCurrentArm(current, owner, watchers, armEpoch) {
3796
+ return current.owner === owner && current.watchers === watchers && current.armEpoch === armEpoch;
3797
+ }
3798
+ function scheduleRearm(current, owner) {
3799
+ if (current.rearmTimer) {
3800
+ if (current.watchHealthy === false) return;
3801
+ clearTimeout(current.rearmTimer);
2570
3802
  }
3803
+ const delayMs = current.watchHealthy === false ? current.rearmDelayMs ?? REARM_INITIAL_DELAY_MS : REARM_INITIAL_DELAY_MS;
3804
+ if (current.watchHealthy === false) current.rearmDelayMs = Math.min(delayMs * 2, REARM_MAX_DELAY_MS);
3805
+ current.rearmTimer = setTimeout(() => {
3806
+ current.rearmTimer = void 0;
3807
+ if (current.owner !== owner) return;
3808
+ const wasUnhealthy = current.watchHealthy === false;
3809
+ closeWatchers(current);
3810
+ if (!armWatchers(current) || wasUnhealthy) owner.markDirty();
3811
+ refreshGeneration(current, owner, owner.wait());
3812
+ }, delayMs);
3813
+ current.rearmTimer.unref();
3814
+ }
3815
+ function logRefreshFailure(current, owner) {
3816
+ return (error) => {
3817
+ if (current.owner !== owner) return;
3818
+ current.context?.serviceHealth?.reportFailure(error);
3819
+ current.context?.logger.warn(`codex desktop generation refresh failed: ${String(error)}`);
3820
+ };
2571
3821
  }
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;
2580
- }
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(" "));
3822
+ function refreshGeneration(current, owner, refresh) {
3823
+ refresh.then(() => {
3824
+ if (current.owner === owner && current.watchHealthy) current.context?.serviceHealth?.clearFailure();
3825
+ }).catch(logRefreshFailure(current, owner));
2590
3826
  }
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
- }
3827
+ function closeWatchers(current) {
3828
+ const watchers = current.watchers;
3829
+ current.watchers = void 0;
3830
+ for (const watcher of watchers ?? []) watcher.close();
2598
3831
  }
2599
3832
  //#endregion
2600
3833
  //#region extensions/codex/src/app-server/native-config-fence.ts
@@ -2659,37 +3892,31 @@ async function waitForPreviousFence(previous, options) {
2659
3892
  });
2660
3893
  }
2661
3894
  //#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?.();
3895
+ //#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
3896
+ var CodexThreadStartRequestError = class extends Error {
3897
+ constructor(cause) {
3898
+ super(`thread/start: ${formatErrorMessage(cause)}`, { cause });
3899
+ this.name = "CodexThreadStartRequestError";
2691
3900
  }
2692
- }
3901
+ };
3902
+ var CodexThreadBindingConflictError = class extends Error {
3903
+ constructor(threadId, operation) {
3904
+ super(`Codex thread binding changed while ${operation}: ${threadId}`);
3905
+ this.name = "CodexThreadBindingConflictError";
3906
+ }
3907
+ };
3908
+ var CodexRestrictedToolSurfaceAttestationError = class extends Error {
3909
+ constructor(cause) {
3910
+ super("Codex restricted-tool-surface MCP attestation failed", { cause });
3911
+ this.name = "CodexRestrictedToolSurfaceAttestationError";
3912
+ }
3913
+ };
3914
+ var CodexAdoptedThreadActiveError = class extends Error {
3915
+ constructor() {
3916
+ super("Codex session became active in another runner; wait for it to finish before continuing");
3917
+ this.name = "CodexAdoptedThreadActiveError";
3918
+ }
3919
+ };
2693
3920
  //#endregion
2694
3921
  //#region extensions/codex/src/app-server/shared-client.ts
2695
3922
  /**
@@ -2698,6 +3925,8 @@ async function withAbortableTimeout(params) {
2698
3925
  */
2699
3926
  var shared_client_exports = /* @__PURE__ */ __exportAll({
2700
3927
  assertCodexAppServerClientStartSelectionCurrent: () => assertCodexAppServerClientStartSelectionCurrent,
3928
+ captureExclusiveSharedCodexAppServerClient: () => captureExclusiveSharedCodexAppServerClient,
3929
+ captureSharedCodexAppServerCatalogLifetime: () => captureSharedCodexAppServerCatalogLifetime,
2701
3930
  clearSharedCodexAppServerClientAndWait: () => clearSharedCodexAppServerClientAndWait,
2702
3931
  clearSharedCodexAppServerClientIfCurrent: () => clearSharedCodexAppServerClientIfCurrent,
2703
3932
  clearSharedCodexAppServerClientIfCurrentAndUnclaimed: () => clearSharedCodexAppServerClientIfCurrentAndUnclaimed,
@@ -2706,6 +3935,9 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
2706
3935
  getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
2707
3936
  getSharedCodexAppServerClient: () => getSharedCodexAppServerClient,
2708
3937
  isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
3938
+ readCodexAppServerClientDesktopGeneration: () => readCodexAppServerClientDesktopGeneration,
3939
+ readCodexAppServerClientDesktopGenerationFingerprint: () => readCodexAppServerClientDesktopGenerationFingerprint,
3940
+ readCodexAppServerClientProcessIdentity: () => readCodexAppServerClientProcessIdentity,
2709
3941
  releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
2710
3942
  releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
2711
3943
  resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
@@ -2713,6 +3945,8 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
2713
3945
  retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
2714
3946
  retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
2715
3947
  retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
3948
+ retireSharedCodexAppServerClientsBeforeDesktopGeneration: () => retireSharedCodexAppServerClientsBeforeDesktopGeneration,
3949
+ waitForCodexAppServerClientDesktopGenerationDrain: () => waitForCodexAppServerClientDesktopGenerationDrain,
2716
3950
  withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
2717
3951
  });
2718
3952
  const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
@@ -2724,8 +3958,10 @@ function getSharedCodexAppServerClientState() {
2724
3958
  globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
2725
3959
  clients: /* @__PURE__ */ new Map(),
2726
3960
  liveClients: /* @__PURE__ */ new Set(),
3961
+ isolatedClients: /* @__PURE__ */ new Set(),
2727
3962
  entriesByClient: /* @__PURE__ */ new WeakMap(),
2728
- leasedReleases: /* @__PURE__ */ new WeakMap()
3963
+ leasedReleases: /* @__PURE__ */ new WeakMap(),
3964
+ desktopGenerationDrainChecks: /* @__PURE__ */ new Set()
2729
3965
  };
2730
3966
  return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
2731
3967
  }
@@ -2734,6 +3970,41 @@ function getCodexAppServerClientStartMetadata() {
2734
3970
  globalState[CODEX_APP_SERVER_CLIENT_START_METADATA] ??= /* @__PURE__ */ new WeakMap();
2735
3971
  return globalState[CODEX_APP_SERVER_CLIENT_START_METADATA];
2736
3972
  }
3973
+ /** Reads the exact successful spawn selection plus its initialized runtime identity. */
3974
+ function readCodexAppServerClientProcessIdentity(client) {
3975
+ const metadata = getCodexAppServerClientStartMetadata().get(client);
3976
+ if (!metadata) return;
3977
+ const runtimeIdentity = client.getRuntimeIdentity();
3978
+ return {
3979
+ clientId: client.getInstanceId(),
3980
+ ...resolveCodexAppServerSpawnIdentity(metadata.startOptions, metadata.nativeCommand),
3981
+ ...runtimeIdentity?.serverVersion ? { serverVersion: runtimeIdentity.serverVersion } : {},
3982
+ ...runtimeIdentity?.userAgent ? { userAgent: runtimeIdentity.userAgent } : {}
3983
+ };
3984
+ }
3985
+ /** Returns the lifecycle fingerprint that owns a managed desktop client. */
3986
+ function readCodexAppServerClientDesktopGenerationFingerprint(client) {
3987
+ return readCodexAppServerClientDesktopGeneration(client)?.fingerprint;
3988
+ }
3989
+ /** Returns the lifecycle generation that owns a managed desktop client. */
3990
+ function readCodexAppServerClientDesktopGeneration(client) {
3991
+ return getCodexAppServerClientStartMetadata().get(client)?.desktopGeneration;
3992
+ }
3993
+ /** Waits until older physical desktop clients for this client's Codex home exit. */
3994
+ async function waitForCodexAppServerClientDesktopGenerationDrain(params) {
3995
+ const metadata = getCodexAppServerClientStartMetadata().get(params.client);
3996
+ if (!metadata?.desktopGeneration) return;
3997
+ const drain = createOlderDesktopGenerationDrainWait({
3998
+ generation: metadata.desktopGeneration,
3999
+ startOptions: metadata.startOptions,
4000
+ agentDir: metadata.agentDir
4001
+ });
4002
+ try {
4003
+ await withCodexAppServerAcquireDeadline(params.timeoutMs ?? 0, drain.promise, params.signal, "Codex Computer Use install timed out waiting for older desktop clients");
4004
+ } finally {
4005
+ drain.cancel();
4006
+ }
4007
+ }
2737
4008
  /** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
2738
4009
  function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
2739
4010
  const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
@@ -2764,6 +4035,7 @@ function isCodexAppServerStartSelectionChangedError(error) {
2764
4035
  function assertCodexAppServerClientStartSelectionCurrent(params) {
2765
4036
  const metadata = getCodexAppServerClientStartMetadata().get(params.client);
2766
4037
  if (!metadata) return;
4038
+ if (metadata.desktopGeneration && !isCodexDesktopGenerationCurrent(metadata.desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
2767
4039
  const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
2768
4040
  if (requestedStartOptions.commandSource !== "managed") return;
2769
4041
  const current = resolveCodexAppServerStartOptionsForAgent({
@@ -2775,7 +4047,7 @@ function assertCodexAppServerClientStartSelectionCurrent(params) {
2775
4047
  /** Resolves the per-CODEX_HOME key used to serialize native config loading. */
2776
4048
  function resolveCodexNativeConfigFenceKey(params) {
2777
4049
  const metadata = params.client ? getCodexAppServerClientStartMetadata().get(params.client) : void 0;
2778
- const startOptions = params.startOptions ?? metadata?.startOptions;
4050
+ const startOptions = metadata?.startOptions ?? params.startOptions;
2779
4051
  if (!startOptions || startOptions.transport !== "stdio") return;
2780
4052
  const configuredHome = startOptions.env?.CODEX_HOME?.trim();
2781
4053
  const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
@@ -2789,10 +4061,11 @@ function inferAuthRequirement(preparedAuth) {
2789
4061
  async function resolveCodexAppServerClientStartContext(options) {
2790
4062
  const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
2791
4063
  const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
4064
+ const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions, options?.pluginConfig) ? await waitForCodexDesktopGeneration() : void 0;
2792
4065
  const preparedAuth = options?.preparedAuth;
2793
4066
  const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
2794
4067
  if (preparedAuth && options?.authProfileId !== void 0) throw new Error("Prepared Codex auth cannot also select a legacy auth profile.");
2795
- if (preparedAuth?.kind === "profile" && !preparedAuth.store.profiles[preparedAuth.profileId]) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" was not found.`);
4068
+ if (preparedAuth?.kind === "profile" && !preparedAuth.store.profiles[preparedAuth.profileId]) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
2796
4069
  if (preparedAuth?.kind === "api-key" && !preparedApiKey) throw new Error("Prepared Codex API-key auth is missing its resolved key.");
2797
4070
  if (preparedAuth && requestedStartOptions.homeScope === "user") throw new Error("Prepared Codex auth requires an isolated app-server home.");
2798
4071
  const preparedAuthRequirement = inferAuthRequirement(preparedAuth);
@@ -2800,11 +4073,11 @@ async function resolveCodexAppServerClientStartContext(options) {
2800
4073
  const authRequirement = options?.authRequirement ?? preparedAuthRequirement;
2801
4074
  const usesNativeAuth = !preparedAuth && (options?.authProfileId === null || requestedStartOptions.homeScope === "user");
2802
4075
  const requestedAuthProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : options?.authProfileId ?? void 0;
2803
- const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && options?.authProfileStore ? resolveCodexAppServerAuthProfileStore({
4076
+ const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && preparedAuth?.kind !== "api-key" ? resolveCodexAppServerAuthProfileStore({
2804
4077
  agentDir,
2805
4078
  authProfileId: requestedAuthProfileId,
2806
- authProfileStore: options.authProfileStore,
2807
- config: options.config
4079
+ authProfileStore: options?.authProfileStore,
4080
+ config: options?.config
2808
4081
  }) : options?.authProfileStore;
2809
4082
  const authProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : usesNativeAuth || preparedAuth?.kind === "api-key" ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
2810
4083
  authProfileId: requestedAuthProfileId,
@@ -2812,18 +4085,20 @@ async function resolveCodexAppServerClientStartContext(options) {
2812
4085
  config: options?.config,
2813
4086
  ...authProfileStore ? { authProfileStore } : {}
2814
4087
  });
2815
- const preparedAuthProfileSnapshot = preparedAuth?.kind === "profile" ? preparedAuth.snapshot ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
4088
+ const preparedAuthProfileSnapshot = !usesNativeAuth && authProfileId ? (preparedAuth?.kind === "profile" ? preparedAuth.snapshot : void 0) ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
2816
4089
  authProfileId,
2817
4090
  authProfileStore,
2818
4091
  agentDir,
2819
4092
  config: options?.config
2820
4093
  }) : void 0;
2821
- if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable.`);
4094
+ if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable. Repair or replace the selected OpenAI profile, then retry.`);
2822
4095
  const resolvedPreparedAuth = preparedAuth?.kind === "api-key" ? {
2823
4096
  kind: "api-key",
2824
4097
  apiKey: preparedApiKey
2825
- } : preparedAuth?.kind === "profile" ? {
2826
- ...preparedAuth,
4098
+ } : preparedAuthProfileSnapshot && authProfileId && authProfileStore ? {
4099
+ kind: "profile",
4100
+ profileId: authProfileId,
4101
+ store: authProfileStore,
2827
4102
  snapshot: preparedAuthProfileSnapshot
2828
4103
  } : void 0;
2829
4104
  return {
@@ -2842,51 +4117,20 @@ async function resolveCodexAppServerClientStartContext(options) {
2842
4117
  agentId: options?.agentId,
2843
4118
  agentDir,
2844
4119
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
2845
- ...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
4120
+ ...preparedAuth && resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
2846
4121
  authRequirement,
2847
4122
  config: options?.config,
2848
4123
  pluginConfig: options?.pluginConfig,
2849
4124
  ...authProfileStore ? { authProfileStore } : {}
2850
- })
2851
- };
2852
- }
2853
- function resolveWarmSharedCodexAppServerClientIdentity(options) {
2854
- 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
- const authProfileSelector = options.authProfileId === null ? ["native"] : options.authProfileId === void 0 ? ["implicit"] : ["profile", options.authProfileId];
2856
- return {
2857
- config: options.config,
2858
- startOptions: options.startOptions,
2859
- selectorKey: JSON.stringify([
2860
- options.agentDir ?? resolveDefaultAgentDir(options.config),
2861
- authProfileSelector,
2862
- options.authBindingFingerprint ?? null,
2863
- options.authRequirement ?? null
2864
- ])
4125
+ }),
4126
+ ...options?.pluginConfig !== void 0 ? { pluginConfig: options.pluginConfig } : {},
4127
+ ...desktopGeneration ? { desktopGeneration } : {}
2865
4128
  };
2866
4129
  }
2867
- function readWarmSharedCodexAppServerClient(state, identity) {
2868
- const aliases = state.warmClientsByConfig?.get(identity.config)?.get(identity.startOptions);
2869
- const warm = aliases?.get(identity.selectorKey);
2870
- if (!warm) return;
2871
- if (state.clients.get(warm.key) !== warm.entry || warm.entry.client !== warm.client || warm.entry.closeWhenIdle || warm.entry.closeError) {
2872
- aliases?.delete(identity.selectorKey);
2873
- return;
2874
- }
2875
- return warm;
2876
- }
2877
- function rememberWarmSharedCodexAppServerClient(state, identity, warm) {
2878
- state.warmClientsByConfig ??= /* @__PURE__ */ new WeakMap();
2879
- let byStartOptions = state.warmClientsByConfig.get(identity.config);
2880
- if (!byStartOptions) {
2881
- byStartOptions = /* @__PURE__ */ new WeakMap();
2882
- state.warmClientsByConfig.set(identity.config, byStartOptions);
2883
- }
2884
- let aliases = byStartOptions.get(identity.startOptions);
2885
- if (!aliases) {
2886
- aliases = /* @__PURE__ */ new Map();
2887
- byStartOptions.set(identity.startOptions, aliases);
2888
- }
2889
- aliases.set(identity.selectorKey, warm);
4130
+ function shouldTrackDesktopGeneration(startOptions, pluginConfig) {
4131
+ if (startOptions.transport !== "stdio") return false;
4132
+ if (resolveCodexComputerUseConfig({ pluginConfig }).enabled && (startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed")) return true;
4133
+ return startOptions.commandSource === "managed" && (startOptions.managedCommandOrder ?? "package-first") === "desktop-first";
2890
4134
  }
2891
4135
  /** Gets or starts a shared Codex app-server client without retaining a lease. */
2892
4136
  async function getSharedCodexAppServerClient(options) {
@@ -2918,7 +4162,7 @@ function releaseCodexAppServerClientLease(lease) {
2918
4162
  lease.client = void 0;
2919
4163
  return client ? releaseLeasedSharedCodexAppServerClient(client) : false;
2920
4164
  }
2921
- /** Retries one config-loading request after moving its lease to the current owner. */
4165
+ /** Retries one config-loading operation with a shared deadline and current client lease. */
2922
4166
  async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
2923
4167
  let client = params.lease.client;
2924
4168
  if (!client) throw new Error("Codex app-server selection retry requires an active client lease");
@@ -2934,43 +4178,49 @@ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
2934
4178
  ...signal ? { signal } : {}
2935
4179
  };
2936
4180
  };
2937
- for (let attempt = 0; attempt < 2; attempt += 1) try {
2938
- return await params.run(client, requestOptions());
2939
- } catch (error) {
2940
- if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
2941
- retireSharedCodexAppServerClientIfCurrent(client);
2942
- params.lease.client = void 0;
2943
- if (!releaseLeasedSharedCodexAppServerClient(client)) {
2944
- client.close();
2945
- throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
4181
+ for (let attempt = 0; attempt < 2; attempt += 1) {
4182
+ const attemptClient = client;
4183
+ let scopeActive = true;
4184
+ const assertCurrent = () => {
4185
+ if (!scopeActive || params.lease.client !== attemptClient) throw new CodexAppServerStartupError("aborted", "Codex app-server request scope is closed");
4186
+ };
4187
+ try {
4188
+ requestOptions();
4189
+ return await params.run(client, () => {
4190
+ assertCurrent();
4191
+ return {
4192
+ ...requestOptions(),
4193
+ assertCurrent
4194
+ };
4195
+ });
4196
+ } catch (error) {
4197
+ if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
4198
+ retireSharedCodexAppServerClientIfCurrent(client);
4199
+ params.lease.client = void 0;
4200
+ if (!releaseLeasedSharedCodexAppServerClient(client)) {
4201
+ client.close();
4202
+ throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
4203
+ }
4204
+ const replacementOptions = requestOptions();
4205
+ client = await getLeasedSharedCodexAppServerClient({
4206
+ ...params.options,
4207
+ timeoutMs: replacementOptions.timeoutMs,
4208
+ ...signal ? { abandonSignal: signal } : {}
4209
+ });
4210
+ params.lease.client = client;
4211
+ params.onClientChange?.(client);
4212
+ } finally {
4213
+ scopeActive = false;
2946
4214
  }
2947
- const replacementOptions = requestOptions();
2948
- client = await getLeasedSharedCodexAppServerClient({
2949
- ...params.options,
2950
- timeoutMs: replacementOptions.timeoutMs,
2951
- ...signal ? { abandonSignal: signal } : {}
2952
- });
2953
- params.lease.client = client;
2954
- params.onClientChange(client);
2955
4215
  }
2956
4216
  throw new Error("Codex app-server selection retry loop exited unexpectedly");
2957
4217
  }
2958
4218
  async function acquireSharedCodexAppServerClient(options, leaseOptions) {
2959
4219
  if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
2960
- const state = getSharedCodexAppServerClientState();
2961
- const warmIdentity = resolveWarmSharedCodexAppServerClientIdentity(options);
2962
- const warmClient = warmIdentity ? readWarmSharedCodexAppServerClient(state, warmIdentity) : void 0;
2963
- if (warmClient) {
2964
- options?.onStartedClient?.(warmClient.client);
2965
- const release = leaseOptions?.leased ? retainSharedClientEntry(warmClient.entry) : void 0;
2966
- return release ? {
2967
- client: warmClient.client,
2968
- release
2969
- } : { client: warmClient.client };
2970
- }
2971
4220
  const acquireStartedAt = Date.now();
2972
4221
  const timeoutMs = options?.timeoutMs ?? 0;
2973
- const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
4222
+ const state = getSharedCodexAppServerClientState();
4223
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
2974
4224
  const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
2975
4225
  const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authRequirement === "api-key" && !authProfileId ? resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }) : void 0);
2976
4226
  const baseKey = `${codexAppServerStartOptionsKey(startOptions, {
@@ -2978,12 +4228,18 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
2978
4228
  authBindingFingerprint: options?.authBindingFingerprint,
2979
4229
  agentDir: usesNativeAuth ? void 0 : agentDir,
2980
4230
  fallbackApiKeyCacheKey: authIdentityCacheKey
2981
- })}\0auth-requirement:${authRequirement ?? "native"}`;
4231
+ })}\0auth-requirement:${authRequirement ?? "native"}${desktopGeneration ? `\0desktop-generation:${desktopGeneration.epoch}` : ""}`;
2982
4232
  const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
2983
4233
  const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
2984
4234
  const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
2985
- const entry = getOrCreateSharedClientEntry(state, key);
2986
- if (runtimeArtifactMode) entry.runtimeArtifactStartupAbort ??= new AbortController();
4235
+ let entry = getOrCreateSharedClientEntry(state, key);
4236
+ const existingClient = entry.client;
4237
+ const existingGeneration = existingClient ? getCodexAppServerClientStartMetadata().get(existingClient)?.desktopGeneration : void 0;
4238
+ if (existingClient && existingGeneration && !isCodexDesktopGenerationCurrent(existingGeneration)) {
4239
+ retireSharedCodexAppServerClientIfCurrent(existingClient);
4240
+ entry = getOrCreateSharedClientEntry(state, key);
4241
+ }
4242
+ entry.startupAbort ??= new AbortController();
2987
4243
  entry.closeWhenIdle = false;
2988
4244
  const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
2989
4245
  const startedCallback = options?.onStartedClient;
@@ -3010,6 +4266,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3010
4266
  key,
3011
4267
  requestedStartOptions,
3012
4268
  startOptions,
4269
+ desktopGeneration,
4270
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
3013
4271
  agentDir,
3014
4272
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
3015
4273
  authProfileStore,
@@ -3017,7 +4275,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3017
4275
  authRequirement,
3018
4276
  runtimeArtifactMode,
3019
4277
  ...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
3020
- runtimeArtifactSignal: entry.runtimeArtifactStartupAbort?.signal,
4278
+ runtimeArtifactSignal: entry.startupAbort.signal,
4279
+ abandonSignal: entry.startupAbort.signal,
3021
4280
  config: options?.config
3022
4281
  }));
3023
4282
  try {
@@ -3032,11 +4291,6 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3032
4291
  config: options?.config
3033
4292
  });
3034
4293
  const release = leaseOptions?.leased ? retainSharedClientEntry(entry) : void 0;
3035
- if (warmIdentity) rememberWarmSharedCodexAppServerClient(state, warmIdentity, {
3036
- key,
3037
- entry,
3038
- client
3039
- });
3040
4294
  return release ? {
3041
4295
  client,
3042
4296
  release
@@ -3076,6 +4330,8 @@ function createSharedCodexAppServerClientStartup(params) {
3076
4330
  const ready = startInitializedCodexAppServerClient({
3077
4331
  requestedStartOptions: params.requestedStartOptions,
3078
4332
  startOptions: params.startOptions,
4333
+ ...params.desktopGeneration ? { desktopGeneration: params.desktopGeneration } : {},
4334
+ ...params.pluginConfig !== void 0 ? { pluginConfig: params.pluginConfig } : {},
3079
4335
  agentDir: params.agentDir,
3080
4336
  authProfileId: params.authProfileId,
3081
4337
  authProfileStore: params.authProfileStore,
@@ -3084,27 +4340,33 @@ function createSharedCodexAppServerClientStartup(params) {
3084
4340
  runtimeArtifactMode: params.runtimeArtifactMode,
3085
4341
  ...params.expectedRuntimeArtifact ? { expectedRuntimeArtifact: params.expectedRuntimeArtifact } : {},
3086
4342
  runtimeArtifactSignal: params.runtimeArtifactSignal,
4343
+ abandonSignal: params.abandonSignal,
3087
4344
  config: params.config,
3088
4345
  onStartedClient: (startedClient) => {
3089
4346
  const state = getSharedCodexAppServerClientState();
3090
4347
  params.entry.client = startedClient;
3091
- state.entriesByClient.set(startedClient, params.entry);
3092
4348
  state.liveClients.add(startedClient);
4349
+ startedClient.addTransportExitHandler((exitedClient) => {
4350
+ state.liveClients.delete(exitedClient);
4351
+ notifyDesktopGenerationDrainChecks(state);
4352
+ });
3093
4353
  startedClient.addCloseHandler((closedClient) => {
3094
- getSharedCodexAppServerClientState().liveClients.delete(closedClient);
3095
- clearSharedClientEntryIfCurrent(params.key, closedClient);
4354
+ if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
3096
4355
  });
3097
4356
  for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
3098
4357
  retirePendingSharedClientEntryIfUnclaimed(params.key, params.entry);
3099
4358
  },
3100
4359
  onInitializedClient: () => initialized.resolve()
3101
4360
  }).then((client) => {
4361
+ const state = getSharedCodexAppServerClientState();
3102
4362
  params.entry.client = client;
4363
+ state.entriesByClient.set(client, params.entry);
3103
4364
  return client;
3104
4365
  }, (error) => {
3105
4366
  initialized.reject(error);
3106
4367
  throw error;
3107
4368
  });
4369
+ initialized.promise.catch(() => void 0);
3108
4370
  ready.catch(() => void 0);
3109
4371
  return {
3110
4372
  initialized: initialized.promise,
@@ -3116,10 +4378,12 @@ async function createIsolatedCodexAppServerClient(options) {
3116
4378
  if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
3117
4379
  const acquireStartedAt = Date.now();
3118
4380
  const timeoutMs = options?.timeoutMs ?? 0;
3119
- const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
4381
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
3120
4382
  return await startInitializedCodexAppServerClient({
3121
4383
  requestedStartOptions,
3122
4384
  startOptions,
4385
+ ...desktopGeneration ? { desktopGeneration } : {},
4386
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
3123
4387
  agentDir,
3124
4388
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
3125
4389
  authProfileStore,
@@ -3131,7 +4395,18 @@ async function createIsolatedCodexAppServerClient(options) {
3131
4395
  config: options?.config,
3132
4396
  timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
3133
4397
  abandonSignal: options?.abandonSignal,
3134
- onStartedClient: options?.onStartedClient
4398
+ onStartedClient: (client) => {
4399
+ trackIsolatedCodexAppServerClient(client);
4400
+ options?.onStartedClient?.(client);
4401
+ }
4402
+ });
4403
+ }
4404
+ function trackIsolatedCodexAppServerClient(client) {
4405
+ const state = getSharedCodexAppServerClientState();
4406
+ state.isolatedClients.add(client);
4407
+ client.addTransportExitHandler((exitedClient) => {
4408
+ state.isolatedClients.delete(exitedClient);
4409
+ notifyDesktopGenerationDrainChecks(state);
3135
4410
  });
3136
4411
  }
3137
4412
  async function startInitializedCodexAppServerClient(params) {
@@ -3139,7 +4414,41 @@ async function startInitializedCodexAppServerClient(params) {
3139
4414
  const timeoutMs = params.timeoutMs ?? 0;
3140
4415
  const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
3141
4416
  for (const [index, startOptions] of startOptionsCandidates.entries()) {
3142
- const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BVSPdd6g.js") : void 0;
4417
+ const desktopGeneration = params.desktopGeneration ?? (isManagedCodexDesktopCommand(startOptions.command) ? await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), params.abandonSignal) : void 0);
4418
+ const assertDesktopGenerationCurrent = () => {
4419
+ if (params.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
4420
+ if (desktopGeneration && !isCodexDesktopGenerationCurrent(desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
4421
+ };
4422
+ const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
4423
+ const ownsIsolatedCodexHome = params.requestedStartOptions.homeScope !== "user" && !params.requestedStartOptions.env?.CODEX_HOME?.trim();
4424
+ const artifactDrain = desktopGeneration && ownsIsolatedCodexHome && computerUseConfig.enabled && (computerUseConfig.autoInstall || computerUseConfig.pluginCacheMode === "shared") ? createOlderDesktopGenerationDrainWait({
4425
+ generation: desktopGeneration,
4426
+ startOptions,
4427
+ agentDir: params.agentDir
4428
+ }) : void 0;
4429
+ try {
4430
+ if (artifactDrain) await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), artifactDrain.promise, params.abandonSignal);
4431
+ await reconcileCodexComputerUseStartArtifacts({
4432
+ startOptions,
4433
+ agentDir: params.agentDir,
4434
+ pluginConfig: params.pluginConfig,
4435
+ ...desktopGeneration ? { desktopGeneration } : {},
4436
+ assertCurrent: assertDesktopGenerationCurrent,
4437
+ ownsIsolatedCodexHome
4438
+ });
4439
+ } catch (error) {
4440
+ if (isCodexComputerUseCandidateArtifactsUnavailableError(error)) {
4441
+ if (index + 1 < startOptionsCandidates.length) continue;
4442
+ throw new AgentHarnessPreflightError("Codex Computer Use artifacts are unavailable from the installed desktop apps.", {
4443
+ cause: error,
4444
+ scope: "harness"
4445
+ });
4446
+ }
4447
+ throw error;
4448
+ } finally {
4449
+ artifactDrain?.cancel();
4450
+ }
4451
+ const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-DeBvleJw.js") : void 0;
3143
4452
  const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
3144
4453
  const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
3145
4454
  startOptions,
@@ -3150,7 +4459,26 @@ async function startInitializedCodexAppServerClient(params) {
3150
4459
  if (index + 1 < startOptionsCandidates.length) continue;
3151
4460
  throw new Error("Codex app-server runtime artifact does not match verified inference");
3152
4461
  }
3153
- const client = CodexAppServerClient.start(startOptions);
4462
+ assertDesktopGenerationCurrent();
4463
+ let starting;
4464
+ let client;
4465
+ try {
4466
+ client = await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), starting = CodexAppServerClient.start(startOptions, () => {
4467
+ assertDesktopGenerationCurrent();
4468
+ resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
4469
+ }), params.abandonSignal);
4470
+ } catch (error) {
4471
+ starting?.then((lateClient) => lateClient.close(), () => {});
4472
+ throw error;
4473
+ }
4474
+ const nativeCommandAtStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
4475
+ getCodexAppServerClientStartMetadata().set(client, {
4476
+ requestedStartOptions: params.requestedStartOptions,
4477
+ startOptions,
4478
+ agentDir: params.agentDir,
4479
+ ...nativeCommandAtStart ? { nativeCommand: nativeCommandAtStart } : {},
4480
+ ...desktopGeneration ? { desktopGeneration } : {}
4481
+ });
3154
4482
  params.onStartedClient?.(client);
3155
4483
  let initialize;
3156
4484
  try {
@@ -3161,6 +4489,12 @@ async function startInitializedCodexAppServerClient(params) {
3161
4489
  if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
3162
4490
  throw error;
3163
4491
  }
4492
+ try {
4493
+ assertDesktopGenerationCurrent();
4494
+ } catch (error) {
4495
+ client.close();
4496
+ throw error;
4497
+ }
3164
4498
  params.onInitializedClient?.();
3165
4499
  let runtimeArtifact;
3166
4500
  try {
@@ -3184,9 +4518,11 @@ async function startInitializedCodexAppServerClient(params) {
3184
4518
  authProfileId: params.authProfileId ?? void 0,
3185
4519
  authMode: params.preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
3186
4520
  ...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {},
3187
- config: params.config
4521
+ config: params.config,
4522
+ onAuthRefreshFailure: () => retireSharedCodexAppServerClientIfCurrent(client)
3188
4523
  });
3189
4524
  try {
4525
+ assertDesktopGenerationCurrent();
3190
4526
  await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), applyCodexAppServerAuthProfile({
3191
4527
  client,
3192
4528
  agentDir: params.agentDir,
@@ -3197,14 +4533,8 @@ async function startInitializedCodexAppServerClient(params) {
3197
4533
  config: params.config,
3198
4534
  ...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
3199
4535
  }), params.abandonSignal);
3200
- const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
3201
4536
  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
- });
4537
+ assertDesktopGenerationCurrent();
3208
4538
  const fenceKey = resolveCodexNativeConfigFenceKey({ client });
3209
4539
  if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
3210
4540
  const release = await acquireCodexNativeConfigFence(fenceKey, options);
@@ -3224,6 +4554,9 @@ async function startInitializedCodexAppServerClient(params) {
3224
4554
  }
3225
4555
  throw new Error("Managed Codex app-server fallback candidates were exhausted.");
3226
4556
  }
4557
+ function isCodexComputerUseCandidateArtifactsUnavailableError(error) {
4558
+ return error !== null && typeof error === "object" && "code" in error && error.code === "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
4559
+ }
3227
4560
  function resolveManagedFallbackStartOptions(startOptions) {
3228
4561
  const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
3229
4562
  const candidates = [];
@@ -3253,6 +4586,15 @@ function clearSharedCodexAppServerClientIfCurrent(client) {
3253
4586
  }
3254
4587
  return false;
3255
4588
  }
4589
+ /** Captures a revocable observation of the exact shared client and native account/config. */
4590
+ function captureSharedCodexAppServerCatalogLifetime(client) {
4591
+ const state = getSharedCodexAppServerClientState();
4592
+ const entry = state.entriesByClient.get(client);
4593
+ const key = [...state.clients].find(([, candidate]) => candidate === entry)?.[0];
4594
+ const generation = readCodexAppServerClientDesktopGeneration(client);
4595
+ const revision = client.getModelCatalogRevision();
4596
+ return () => key !== void 0 && state.clients.get(key) === entry && entry?.client === client && !entry.closeWhenIdle && !entry.closeError && !client.getCloseError() && client.getModelCatalogRevision() === revision && (!generation || isCodexDesktopGenerationCurrent(generation));
4597
+ }
3256
4598
  /** Retains the matching shared client and returns a release callback. */
3257
4599
  function retainSharedCodexAppServerClientIfCurrent(client) {
3258
4600
  if (!client) return;
@@ -3272,6 +4614,20 @@ function retainSharedCodexAppServerClientByInstanceId(clientId) {
3272
4614
  };
3273
4615
  }
3274
4616
  }
4617
+ /** Captures sole physical-client ownership across awaited configuration adoption. */
4618
+ function captureExclusiveSharedCodexAppServerClient(client) {
4619
+ const state = getSharedCodexAppServerClientState();
4620
+ for (const [key, entry] of state.clients) {
4621
+ if (entry.client !== client) continue;
4622
+ const generation = entry.leaseGeneration;
4623
+ const assertExclusive = () => {
4624
+ if (state.clients.get(key) !== entry || entry.client !== client || entry.closeWhenIdle || entry.closeError || entry.activeLeases !== 1 || entry.pendingAcquires !== 0 || entry.leaseGeneration !== generation) throw new CodexAdoptedThreadActiveError();
4625
+ };
4626
+ assertExclusive();
4627
+ return assertExclusive;
4628
+ }
4629
+ throw new CodexAdoptedThreadActiveError();
4630
+ }
3275
4631
  /**
3276
4632
  * Retires a matching shared client. Default is graceful: detach from the map
3277
4633
  * (future acquisitions get a fresh client) and close once leases drain.
@@ -3315,6 +4671,62 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
3315
4671
  };
3316
4672
  }
3317
4673
  }
4674
+ /** Gracefully retires exact clients attached to an older desktop generation. */
4675
+ function retireSharedCodexAppServerClientsBeforeDesktopGeneration(generation) {
4676
+ const state = getSharedCodexAppServerClientState();
4677
+ for (const entry of state.clients.values()) {
4678
+ const client = entry.client;
4679
+ const attached = client ? getCodexAppServerClientStartMetadata().get(client) : void 0;
4680
+ if (client && attached?.desktopGeneration && attached.desktopGeneration.epoch < generation.epoch) retireSharedCodexAppServerClientIfCurrent(client);
4681
+ }
4682
+ }
4683
+ function createOlderDesktopGenerationDrainWait(params) {
4684
+ const targetHome = resolveCodexNativeConfigFenceKey({
4685
+ startOptions: params.startOptions,
4686
+ agentDir: params.agentDir
4687
+ });
4688
+ if (!targetHome) return {
4689
+ promise: Promise.resolve(),
4690
+ cancel: () => void 0
4691
+ };
4692
+ const state = getSharedCodexAppServerClientState();
4693
+ let settled = false;
4694
+ let resolveWait;
4695
+ const promise = new Promise((resolve) => {
4696
+ resolveWait = resolve;
4697
+ });
4698
+ const cancel = () => {
4699
+ if (settled) return;
4700
+ settled = true;
4701
+ state.desktopGenerationDrainChecks.delete(check);
4702
+ resolveWait();
4703
+ };
4704
+ const check = () => {
4705
+ if (!hasLiveOlderDesktopGenerationClient({
4706
+ state,
4707
+ generation: params.generation,
4708
+ targetHome
4709
+ })) cancel();
4710
+ };
4711
+ state.desktopGenerationDrainChecks.add(check);
4712
+ check();
4713
+ return {
4714
+ promise,
4715
+ cancel
4716
+ };
4717
+ }
4718
+ function hasLiveOlderDesktopGenerationClient(params) {
4719
+ for (const client of params.state.liveClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
4720
+ for (const client of params.state.isolatedClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
4721
+ return false;
4722
+ }
4723
+ function isOlderDesktopGenerationClientForHome(client, generation, targetHome) {
4724
+ const metadata = getCodexAppServerClientStartMetadata().get(client);
4725
+ return Boolean(metadata?.desktopGeneration && metadata.desktopGeneration.epoch < generation.epoch && resolveCodexNativeConfigFenceKey({ client }) === targetHome);
4726
+ }
4727
+ function notifyDesktopGenerationDrainChecks(state) {
4728
+ for (const check of state.desktopGenerationDrainChecks) check();
4729
+ }
3318
4730
  /** Clears a matching shared client and waits for its process to exit. */
3319
4731
  async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
3320
4732
  if (!client) return false;
@@ -3331,7 +4743,6 @@ async function clearSharedCodexAppServerClientAndWait(options) {
3331
4743
  const state = getSharedCodexAppServerClientState();
3332
4744
  const clients = collectSharedClients(state);
3333
4745
  state.clients.clear();
3334
- state.warmClientsByConfig = /* @__PURE__ */ new WeakMap();
3335
4746
  await Promise.all(clients.map((client) => client.closeAndWait(options)));
3336
4747
  }
3337
4748
  globalThis[SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER] = clearSharedCodexAppServerClientAndWait;
@@ -3341,6 +4752,7 @@ function getOrCreateSharedClientEntry(state, key) {
3341
4752
  entry = {
3342
4753
  activeLeases: 0,
3343
4754
  pendingAcquires: 0,
4755
+ leaseGeneration: 0,
3344
4756
  closeWhenIdle: false,
3345
4757
  onStartedClientCallbacks: /* @__PURE__ */ new Set()
3346
4758
  };
@@ -3376,22 +4788,26 @@ function clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client) {
3376
4788
  }
3377
4789
  function retainPendingSharedClientAcquire(entry) {
3378
4790
  let released = false;
4791
+ entry.leaseGeneration += 1;
3379
4792
  entry.pendingAcquires += 1;
3380
4793
  return () => {
3381
4794
  if (released) return;
3382
4795
  released = true;
3383
4796
  entry.pendingAcquires = Math.max(0, entry.pendingAcquires - 1);
3384
4797
  closeRetiredSharedClientEntryIfIdle(entry);
4798
+ notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
3385
4799
  };
3386
4800
  }
3387
4801
  function retainSharedClientEntry(entry) {
3388
4802
  let released = false;
4803
+ entry.leaseGeneration += 1;
3389
4804
  entry.activeLeases += 1;
3390
4805
  return () => {
3391
4806
  if (released) return;
3392
4807
  released = true;
3393
4808
  entry.activeLeases = Math.max(0, entry.activeLeases - 1);
3394
4809
  closeRetiredSharedClientEntryIfIdle(entry);
4810
+ notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
3395
4811
  };
3396
4812
  }
3397
4813
  function closeRetiredSharedClientEntryIfIdle(entry) {
@@ -3419,7 +4835,7 @@ function closeSharedClientEntryIfUnclaimed(key, entry) {
3419
4835
  }
3420
4836
  function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
3421
4837
  if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
3422
- entry.runtimeArtifactStartupAbort?.abort(/* @__PURE__ */ new Error("Codex runtime artifact startup was abandoned"));
4838
+ entry.startupAbort?.abort(/* @__PURE__ */ new Error("Codex app-server startup was abandoned"));
3423
4839
  entry.closeWhenIdle = true;
3424
4840
  const state = getSharedCodexAppServerClientState();
3425
4841
  if (state.clients.get(key) === entry) state.clients.delete(key);
@@ -3430,4 +4846,4 @@ function collectSharedClients(state) {
3430
4846
  return [...state.liveClients];
3431
4847
  }
3432
4848
  //#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 };
4849
+ export { CodexAppServerRpcError as $, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as A, reconcileCodexComputerUseStartArtifacts as At, resolveCodexTurnTerminalIdleTimeoutMs as B, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as Bt, withLeasedCodexAppServerClientStartSelectionRetry as C, retainCodexAppServerLiveThread as Ct, CodexThreadStartRequestError as D, readCodexRateLimitsRevision as Dt, CodexThreadBindingConflictError as E, withTimeout$1 as Et, resolveCodexGatewayTimeoutWithGraceMs as F, resolveCodexAppServerFallbackApiKeyCacheKey as Ft, isCodexAppServerConnectionClosedError as G, getCodexAppServerClientInstanceId as H, isJsonObject as Ht, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as I, resolveCodexAppServerPreparedApiKeyCacheKey as It, isCodexAppServerOverloadError as J, isCodexAppServerIndeterminateRequestCancellationError as K, resolveCodexStartupTimeoutMs as L, resolveCodexAppServerPreparedAuthHandoff as Lt, TURN_FINALIZE_DRAIN_ABORT_GRACE_MS as M, resolveCodexAppServerAuthProfileId as Mt, TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS as N, resolveCodexAppServerAuthProfileIdForAgent as Nt, acquireCodexNativeConfigFence as O, readRecentCodexRateLimits as Ot, isCodexAppServerStartupError as P, resolveCodexAppServerAuthProfileStore as Pt, resolveCodexAppServerClientInstanceId as Q, resolveCodexTurnAssistantCompletionIdleTimeoutMs as R, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as Rt, waitForCodexAppServerClientDesktopGenerationDrain as S, releaseCodexAppServerLiveThread as St, CodexRestrictedToolSurfaceAttestationError as T, withAbortableTimeout as Tt, isCodexAppServerApprovalRequest as U, resolveCodexManagedBundledMarketplacePath as Ut, withCodexStartupTimeout as V, flattenCodexDynamicToolFunctions as Vt, isCodexAppServerBrokenPipeError as W, assertNotSymlink as Wt, isCodexAppServerRequestTimeoutError as X, isCodexAppServerPrewriteRequestCancellationError as Y, isUnsupportedCodexAppServerVersionError as Z, retainSharedCodexAppServerClientByInstanceId as _, ensureCodexAppServerClientRuntime as _t, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as a, resolveDynamicToolCallTimeoutMs as at, retireSharedCodexAppServerClientsBeforeDesktopGeneration as b, isCodexAppServerLiveThreadClaimed as bt, getLeasedSharedCodexAppServerClient as c, shouldReleaseTurnAfterTerminalDynamicTool as ct, readCodexAppServerClientDesktopGeneration as d, resolveCodexToolAbortTerminalReason as dt, createCodexElicitationResponse as et, readCodexAppServerClientDesktopGenerationFingerprint as f, createFailedDynamicToolResponse as ft, resolveCodexNativeConfigFenceKey as g, consumeCodexAppServerLiveThread as gt, releaseLeasedSharedCodexAppServerClient as h, claimCodexAppServerLiveThread as ht, clearSharedCodexAppServerClientIfCurrent as i, isMatchingDynamicToolTerminalDiagnostic as it, CodexAppServerStartupError as j, resolveCodexAppServerAuthAccountCacheKey as jt, createCodexDesktopGenerationService as k, rememberCodexRateLimitsRead as kt, getSharedCodexAppServerClient as l, toCodexDynamicToolProgressResponse as lt, releaseCodexAppServerClientLease as m, withDynamicToolTranscriptDetails as mt, captureExclusiveSharedCodexAppServerClient as n, hasPendingDynamicToolTerminalDiagnostic as nt, clearSharedCodexAppServerClientIfCurrentAndWait as o, resolveTerminalDynamicToolBatchAction as ot, readCodexAppServerClientProcessIdentity as p, withDynamicToolExecutionState as pt, isCodexAppServerIndeterminateTransportError as q, captureSharedCodexAppServerCatalogLifetime as r, isDynamicToolTerminalDiagnosticEvent as rt, createIsolatedCodexAppServerClient as s, shouldBlockTerminalReleaseForNonTerminalDynamicToolResult as st, assertCodexAppServerClientStartSelectionCurrent as t, handleDynamicToolCallWithTimeout as tt, isCodexAppServerStartSelectionChangedError as u, toCodexDynamicToolProtocolResponse as ut, retainSharedCodexAppServerClientIfCurrent as v, hasCodexAppServerLiveThread as vt, CodexAdoptedThreadActiveError as w, unsubscribeCodexAppServerLiveThread as wt, shared_client_exports as x, protectCodexAppServerLiveThread as xt, retireSharedCodexAppServerClientIfCurrent as y, isCodexAppServerClientRuntimeLive as yt, resolveCodexTurnCompletionIdleTimeoutMs as z, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as zt };