@openclaw/codex 2026.8.1-beta.3 → 2026.8.2

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 (69) hide show
  1. package/dist/api.js +5 -6
  2. package/dist/{attempt-client-cleanup-URjUlCHg.js → attempt-client-cleanup-CpVfwctT.js} +47 -5
  3. package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
  4. package/dist/{binding-connection-DAvZwp96.js → binding-connection-CF7BmrDk.js} +2 -2
  5. package/dist/{bounded-turn-4BjrZEwW.js → bounded-turn-DRC3hcjm.js} +16 -20
  6. package/dist/{capabilities-CH3K57v4.js → capabilities-D6m_TdVy.js} +1 -1
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-D0nZ3qtd.js → command-formatters--UToCokS.js} +4 -3
  9. package/dist/{command-handlers-DC86lgT4.js → command-handlers-CLFx2Ypa.js} +114 -79
  10. package/dist/{compact-COqgBRny.js → compact-5xqW1oCh.js} +13 -12
  11. package/dist/{config-CpNeo5AE.js → config-CcYL77O6.js} +350 -288
  12. package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
  13. package/dist/doctor-contract-api.js +121 -16
  14. package/dist/{dynamic-tools-fJZhl4CE.js → dynamic-tools-BmeXjwNZ.js} +243 -1002
  15. package/dist/{effective-mcp-catalog-4YKn51aG.js → effective-mcp-catalog-XXQKgK1U.js} +2 -2
  16. package/dist/{event-projector-assistant-message-BwxV4d6N.js → event-projector-assistant-message-C5-Ds8mA.js} +18 -1
  17. package/dist/{event-projector-usage-CLwNEJqW.js → event-projector-usage-B2r5vhWH.js} +9 -14
  18. package/dist/{harness-DoKQcF8n.js → harness-BgeB7LnD.js} +42 -52
  19. package/dist/harness.js +1 -1
  20. package/dist/index.js +308 -306
  21. package/dist/{isolated-completion-DPMtlH9r.js → isolated-completion-CxTus3vZ.js} +4 -4
  22. package/dist/launch-args-sXOAg0SX.js +84 -0
  23. package/dist/{managed-binary-By4FZC6i.js → managed-binary-ZkGodW19.js} +43 -7
  24. package/dist/media-understanding-provider.js +1 -1
  25. package/dist/model-catalog-DULBcniB.js +101 -0
  26. package/dist/{models-CZw1wClT.js → models-DaWfETQk.js} +27 -29
  27. package/dist/{native-subagent-monitor-GGq3RnOo.js → native-subagent-monitor-vXYUZH37.js} +226 -129
  28. package/dist/{node-exec-server.runtime-Bsu1LdFZ.js → node-exec-server.runtime-DirKVZ5h.js} +9 -6
  29. package/dist/{plugin-approval-roundtrip-C75Yiiwc.js → plugin-approval-roundtrip-CCWQGlAI.js} +2 -1
  30. package/dist/{plugin-marketplace-discovery-CSWDkMrP.js → plugin-marketplace-discovery-BXjIFEiw.js} +38 -46
  31. package/dist/{plugin-metadata-cache-BXH5NF-4.js → plugin-metadata-cache-DQQm4gWT.js} +163 -136
  32. package/dist/{protocol-validators-VAVyEnDp.js → protocol-validators-Bg5rpNxk.js} +148 -5
  33. package/dist/{rate-limits-BI1dMHDG.js → rate-limits-BqHEHXQJ.js} +3 -2
  34. package/dist/{sandbox-guard-C1qEeZ5C.js → request-BWxweEdK.js} +183 -2
  35. package/dist/{run-attempt-BV3HzuCf.js → run-attempt-BVLE4Gbi.js} +706 -517
  36. package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-D9pYp4zL.js} +3 -3
  37. package/dist/{session-binding-BTrLf9Qm.js → session-binding-D0zinCP1.js} +195 -122
  38. package/dist/{session-catalog-ezP_UcRM.js → session-catalog-eMhVm0Ql.js} +448 -313
  39. package/dist/session-catalog-home-id-Dt2GiG5o.js +112 -0
  40. package/dist/{session-cli-B1hKvfkI.js → session-cli-B2lyjJA0.js} +1 -1
  41. package/dist/session-history-C5J1AKzG.js +87 -0
  42. package/dist/session-permission-policy-ScnAyG90.js +160 -0
  43. package/dist/session-retirement-CtUtUTr9.js +90 -0
  44. package/dist/{session-upstream-marker-D_2npHlA.js → session-upstream-marker-gqrYIRHk.js} +2 -3
  45. package/dist/settled-turn-context-Bk3IUwod.js +290 -0
  46. package/dist/settled-turn-finalizer-BXLN9PfJ.js +104 -0
  47. package/dist/{shared-client-Dw-rzii0.js → shared-client-B1tT5U6r.js} +1738 -806
  48. package/dist/{side-question-DbXkJZzg.js → side-question-CZ-mCw-t.js} +76 -43
  49. package/dist/{thread-lifecycle-DKdrnTMX.js → thread-lifecycle-CGGKP720.js} +991 -455
  50. package/dist/{thread-ownership-i2NBwDUL.js → thread-ownership-RkaoZCmQ.js} +18 -8
  51. package/dist/{plugin-app-cache-key-DMNQ8Zi8.js → thread-resume-BFt1-ZDa.js} +55 -8
  52. package/dist/{transcript-mirror-BU0_LKAL.js → transcript-mirror-1ep4XjzF.js} +124 -18
  53. package/dist/transport-stdio-NT_Dpd9V.js +724 -0
  54. package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
  55. package/dist/{upstream-session-fork-BDVJCLAD.js → upstream-session-fork-Dc8BFExw.js} +76 -90
  56. package/dist/{usage-9dKJCgcy.js → usage-DUhd58gI.js} +3 -3
  57. package/dist/{web-search-provider.runtime-DCdx4DGu.js → web-search-provider.runtime-BEmlE7ov.js} +3 -3
  58. package/openclaw.plugin.json +19 -1
  59. package/package.json +8 -8
  60. package/dist/app-server-policy-bg5QPSVP.js +0 -25
  61. package/dist/command-rpc-xUIIonsO.js +0 -90
  62. package/dist/incognito-session-ET_ufDH-.js +0 -2
  63. package/dist/model-catalog-CmGoqVKB.js +0 -48
  64. package/dist/request-DSK0W6YI.js +0 -157
  65. package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
  66. package/dist/session-retirement-CE4iMiLh.js +0 -36
  67. package/dist/settled-turn-finalizer-Dx1onIqg.js +0 -328
  68. package/dist/thread-resume-CdJk2Ehf.js +0 -30
  69. package/dist/transport-stdio-DhoGy33Z.js +0 -99
@@ -1,186 +1,34 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey, x as resolveCodexAppServerUserHomeDir } from "./config-CpNeo5AE.js";
3
- import { o as isCodexAppServerNativeAuthProfile } from "./session-binding-BTrLf9Qm.js";
4
- import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DhoGy33Z.js";
5
- import { a as CODEX_APP_SERVER_VERSION, n as resolveManagedCodexAppServerStartOptions, o as MIN_SUPPORTED_CODEX_APP_SERVER_VERSION, r as resolveManagedCodexNativeCommand } from "./managed-binary-By4FZC6i.js";
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-CcYL77O6.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-D0zinCP1.js";
5
+ import { a as hasCodexAppServerNaturalExit, i as closeCodexAppServerTransportAndWait, n as resolveCodexAppServerSpawnEnv, r as closeCodexAppServerTransport, t as createStdioTransport } from "./transport-stdio-NT_Dpd9V.js";
6
+ import { l as readCodexDynamicToolCallParams } from "./protocol-validators-Bg5rpNxk.js";
6
7
  import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
8
  import fs from "node:fs/promises";
8
9
  import path from "node:path";
9
10
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
10
- import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
11
- 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";
12
13
  import { createHash, randomUUID } from "node:crypto";
13
- import fsSync, { constants, existsSync } from "node:fs";
14
+ import fsSync, { constants, existsSync, watch } from "node:fs";
14
15
  import os from "node:os";
15
16
  import { isDeepStrictEqual } from "node:util";
16
17
  import { ensureAuthProfileStore, findPersistedAuthProfileCredential, loadAuthProfileStoreForSecretsRuntime, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolveAuthProfileOrder, resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, resolveProviderIdForAuth } from "openclaw/plugin-sdk/agent-runtime";
17
- import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
18
+ import { hasUsableOAuthCredential, resolveOpenAICodexAuthIdentity } from "openclaw/plugin-sdk/provider-auth";
18
19
  import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
19
- import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
20
- import { runExec } from "openclaw/plugin-sdk/process-runtime";
21
20
  import { assertNoSymlinkParents, withTimeout } from "openclaw/plugin-sdk/security-runtime";
22
- import { execFile } from "node:child_process";
21
+ import { runExec } from "openclaw/plugin-sdk/process-runtime";
22
+ import { EventEmitter } from "node:events";
23
23
  import { createInterface } from "node:readline";
24
24
  import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
25
25
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
26
  import { parse } from "semver";
27
- import { EventEmitter } from "node:events";
27
+ import { hasPendingInternalDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
28
28
  import net from "node:net";
29
29
  import { PassThrough, Writable } from "node:stream";
30
30
  import { StringDecoder } from "node:string_decoder";
31
31
  import WebSocket from "ws";
32
- //#region extensions/codex/src/app-server/auth-start-options.ts
33
- const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
34
- const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
35
- function resolveCodexAppServerHomeDir(agentDir) {
36
- return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
37
- }
38
- /** Resolves the local CODEX_HOME used when starting one app-server connection. */
39
- function resolveCodexAppServerLocalHomeDir(startOptions, agentDir, env = process.env) {
40
- const configured = startOptions.env?.CODEX_HOME;
41
- if (configured?.trim()) return configured;
42
- return startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir(env) : resolveCodexAppServerHomeDir(agentDir);
43
- }
44
- /** Forces OpenClaw-owned Codex auth to remain process-local. */
45
- function withEphemeralCodexAuthStore(params) {
46
- const { startOptions } = params;
47
- if (!(startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed") || !params.preparedAuth && params.authProfileId === null) return startOptions;
48
- if (startOptions.args.at(-2) === "-c" && startOptions.args.at(-1) === CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE) return startOptions;
49
- return {
50
- ...startOptions,
51
- args: [
52
- ...startOptions.args,
53
- "-c",
54
- CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE
55
- ]
56
- };
57
- }
58
- //#endregion
59
- //#region extensions/codex/src/app-server/desktop-app-paths.ts
60
- /** Shared path candidates for Codex's macOS desktop app bundle. */
61
- const MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES = [{
62
- appName: "ChatGPT.app",
63
- appBundlePath: "/Applications/ChatGPT.app",
64
- appServerCommandPath: "/Applications/ChatGPT.app/Contents/Resources/codex",
65
- bundledMarketplacePath: "/Applications/ChatGPT.app/Contents/Resources/plugins/openai-bundled",
66
- 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"]
67
- }, {
68
- appName: "Codex.app",
69
- appBundlePath: "/Applications/Codex.app",
70
- appServerCommandPath: "/Applications/Codex.app/Contents/Resources/codex",
71
- bundledMarketplacePath: "/Applications/Codex.app/Contents/Resources/plugins/openai-bundled",
72
- 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"]
73
- }];
74
- function resolveMacOSDesktopCodexBundledMarketplaceCandidates(platform = process.platform) {
75
- return platform === "darwin" ? MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.map((candidate) => candidate.bundledMarketplacePath) : [];
76
- }
77
- function resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform = process.platform, appServerCommand) {
78
- if (platform !== "darwin") return [];
79
- const matchingCandidate = appServerCommand ? MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(appServerCommand)) : void 0;
80
- const orderedCandidates = matchingCandidate ? [matchingCandidate, ...MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.filter((candidate) => candidate !== matchingCandidate)] : MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES;
81
- return [...new Set(orderedCandidates.flatMap((candidate) => candidate.computerUseServiceAppPaths))];
82
- }
83
- function resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath(params = {}) {
84
- const candidates = params.candidates ?? resolveMacOSDesktopCodexBundledMarketplaceCandidates(params.platform);
85
- const pathExists = params.pathExists ?? existsSync;
86
- return candidates.find((candidate) => pathExists(candidate));
87
- }
88
- //#endregion
89
- //#region extensions/codex/src/app-server/computer-use-cache.ts
90
- /** Shared Computer Use plugin cache reconciliation for isolated Codex homes. */
91
- const DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH = resolveMacOSDesktopCodexBundledMarketplaceCandidates("darwin")[0] ?? "";
92
- const DEFAULT_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
93
- async function ensureCodexComputerUseSharedPluginCache(params) {
94
- if (!params.config.enabled) return skippedCacheResult("disabled", "Computer Use cache sharing skipped because it is disabled.");
95
- if (params.config.pluginCacheMode === "independent") return skippedCacheResult("independent", "Computer Use cache sharing skipped because pluginCacheMode is independent.");
96
- if (params.config.marketplaceName || params.config.marketplacePath) return skippedCacheResult("explicit_marketplace", "Computer Use cache sharing skipped because an explicit marketplace is configured.");
97
- const bundledMarketplacePath = resolveComputerUseBundledMarketplacePath(params);
98
- const sourcePluginRoot = path.join(bundledMarketplacePath, "plugins", params.config.pluginName);
99
- const version = await readBundledPluginVersion(sourcePluginRoot);
100
- if (!version) return skippedCacheResult("source_missing", `Computer Use bundled plugin source was not found at ${sourcePluginRoot}.`);
101
- const marketplaceName = params.config.marketplaceName ?? DEFAULT_BUNDLED_MARKETPLACE_NAME;
102
- const cacheRoot = path.join(params.codexHome, "plugins", "cache", marketplaceName, params.config.pluginName);
103
- const cachePath = path.join(cacheRoot, version);
104
- return {
105
- status: "shared",
106
- changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version),
107
- cachePath,
108
- targetPath: sourcePluginRoot,
109
- version,
110
- removedStaleVersions: [],
111
- warnings: [],
112
- message: `Computer Use plugin cache ${cachePath} contains bundled plugin ${sourcePluginRoot}.`
113
- };
114
- }
115
- function resolveComputerUseBundledMarketplacePath(params) {
116
- return params.bundledMarketplacePath ?? resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath({ candidates: params.bundledMarketplacePathCandidates }) ?? params.bundledMarketplacePathCandidates?.[0] ?? DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH;
117
- }
118
- async function readBundledPluginVersion(sourcePluginRoot) {
119
- const pluginJsonPath = path.join(sourcePluginRoot, ".codex-plugin", "plugin.json");
120
- let raw;
121
- try {
122
- raw = await fs.readFile(pluginJsonPath, "utf8");
123
- } catch {
124
- return;
125
- }
126
- try {
127
- const parsed = JSON.parse(raw);
128
- return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
129
- } catch {
130
- return;
131
- }
132
- }
133
- async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version) {
134
- await fs.mkdir(path.dirname(cachePath), { recursive: true });
135
- const stat = await fs.lstat(cachePath).catch(() => void 0);
136
- if (stat?.isDirectory() && !stat.isSymbolicLink()) {
137
- if (await readBundledPluginVersion(cachePath) === version) return false;
138
- }
139
- const cacheRoot = path.dirname(cachePath);
140
- const cacheName = path.basename(cachePath);
141
- const stagingRoot = await fs.mkdtemp(path.join(cacheRoot, `.${cacheName}.staging-`));
142
- const stagedPath = path.join(stagingRoot, cacheName);
143
- const backupPath = path.join(cacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
144
- let backupCreated = false;
145
- try {
146
- await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
147
- if (stat) {
148
- await fs.rename(cachePath, backupPath);
149
- backupCreated = true;
150
- }
151
- try {
152
- await fs.rename(stagedPath, cachePath);
153
- } catch (error) {
154
- if (backupCreated) try {
155
- await fs.rename(backupPath, cachePath);
156
- backupCreated = false;
157
- } catch (restoreError) {
158
- throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
159
- }
160
- throw error;
161
- }
162
- if (backupCreated) await fs.rm(backupPath, {
163
- recursive: true,
164
- force: true
165
- });
166
- return true;
167
- } finally {
168
- await fs.rm(stagingRoot, {
169
- recursive: true,
170
- force: true
171
- });
172
- }
173
- }
174
- function skippedCacheResult(status, message) {
175
- return {
176
- status,
177
- changed: false,
178
- message,
179
- removedStaleVersions: [],
180
- warnings: status === "source_missing" ? [message] : []
181
- };
182
- }
183
- //#endregion
184
32
  //#region extensions/codex/src/app-server/computer-use-service-path.ts
185
33
  /** Filesystem ownership guards for isolated Computer Use service provisioning. */
186
34
  async function assertOwnedServicePath(params) {
@@ -279,7 +127,7 @@ async function assertNotSymlink(filePath, label) {
279
127
  try {
280
128
  if ((await fs.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
281
129
  } catch (error) {
282
- if (hasNodeErrorCode(error, "ENOENT")) return;
130
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
283
131
  throw error;
284
132
  }
285
133
  }
@@ -292,14 +140,14 @@ async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
292
140
  for (const segment of relative.split(path.sep).filter(Boolean)) {
293
141
  current = path.join(current, segment);
294
142
  const existing = await fs.lstat(current).catch((error) => {
295
- if (hasNodeErrorCode(error, "ENOENT")) return;
143
+ if (hasNodeErrorCode$1(error, "ENOENT")) return;
296
144
  throw error;
297
145
  });
298
146
  if (!existing) {
299
147
  try {
300
148
  await fs.mkdir(current, { mode: 448 });
301
149
  } catch (error) {
302
- if (!hasNodeErrorCode(error, "EEXIST")) throw error;
150
+ if (!hasNodeErrorCode$1(error, "EEXIST")) throw error;
303
151
  }
304
152
  const created = await fs.lstat(current);
305
153
  if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
@@ -318,6 +166,259 @@ function assertPathAtOrInside(rootPath, candidatePath, label) {
318
166
  const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
319
167
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
320
168
  }
169
+ function hasNodeErrorCode$1(error, code) {
170
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
171
+ }
172
+ //#endregion
173
+ //#region extensions/codex/src/app-server/computer-use-cache.ts
174
+ /** Shared Computer Use plugin cache reconciliation for isolated Codex homes. */
175
+ const DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH = resolveMacOSDesktopCodexBundledMarketplaceCandidates("darwin")[0] ?? "";
176
+ const DEFAULT_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
177
+ async function ensureCodexComputerUseSharedPluginCache(params) {
178
+ if (!params.config.enabled) return skippedCacheResult("disabled", "Computer Use cache sharing skipped because it is disabled.");
179
+ if (params.config.pluginCacheMode === "independent") return skippedCacheResult("independent", "Computer Use cache sharing skipped because pluginCacheMode is independent.");
180
+ if (params.config.marketplaceName || params.config.marketplacePath) return skippedCacheResult("explicit_marketplace", "Computer Use cache sharing skipped because an explicit marketplace is configured.");
181
+ const bundledMarketplacePath = resolveComputerUseBundledMarketplacePath(params);
182
+ const sourcePluginRoot = path.join(bundledMarketplacePath, "plugins", params.config.pluginName);
183
+ const version = await readBundledPluginVersion(sourcePluginRoot);
184
+ if (!version) return skippedCacheResult("source_missing", `Computer Use bundled plugin source was not found at ${sourcePluginRoot}.`);
185
+ const marketplaceName = params.config.marketplaceName ?? DEFAULT_BUNDLED_MARKETPLACE_NAME;
186
+ const cacheRoot = path.join(params.codexHome, "plugins", "cache", marketplaceName, params.config.pluginName);
187
+ const cachePath = path.join(cacheRoot, version);
188
+ return {
189
+ status: "shared",
190
+ changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, {
191
+ codexHome: params.codexHome,
192
+ ownershipRoot: params.ownershipRoot,
193
+ assertCurrent: params.assertCurrent,
194
+ forceRefresh: params.forceRefresh
195
+ }),
196
+ cachePath,
197
+ targetPath: sourcePluginRoot,
198
+ version,
199
+ removedStaleVersions: [],
200
+ warnings: [],
201
+ message: `Computer Use plugin cache ${cachePath} contains bundled plugin ${sourcePluginRoot}.`
202
+ };
203
+ }
204
+ function resolveComputerUseBundledMarketplacePath(params) {
205
+ return params.bundledMarketplacePath ?? resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath({ candidates: params.bundledMarketplacePathCandidates }) ?? params.bundledMarketplacePathCandidates?.[0] ?? DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH;
206
+ }
207
+ async function readBundledPluginVersion(sourcePluginRoot) {
208
+ const pluginJsonPath = path.join(sourcePluginRoot, ".codex-plugin", "plugin.json");
209
+ let raw;
210
+ try {
211
+ raw = await fs.readFile(pluginJsonPath, "utf8");
212
+ } catch {
213
+ return;
214
+ }
215
+ try {
216
+ const parsed = JSON.parse(raw);
217
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
218
+ } catch {
219
+ return;
220
+ }
221
+ }
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);
232
+ if (stat?.isDirectory() && !stat.isSymbolicLink()) {
233
+ if (await readBundledPluginVersion(physicalCachePath) === version && !boundary.forceRefresh) return false;
234
+ }
235
+ const cacheName = path.basename(cachePath);
236
+ const physicalCacheRoot = path.dirname(physicalCachePath);
237
+ const stagingRoot = await fs.mkdtemp(path.join(physicalCacheRoot, `.${cacheName}.staging-`));
238
+ const stagedPath = path.join(stagingRoot, cacheName);
239
+ const backupPath = path.join(physicalCacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
240
+ let backupCreated = false;
241
+ try {
242
+ await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
243
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
244
+ if (stat) {
245
+ boundary.assertCurrent?.();
246
+ await fs.rename(physicalCachePath, backupPath);
247
+ backupCreated = true;
248
+ }
249
+ try {
250
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
251
+ boundary.assertCurrent?.();
252
+ await fs.rename(stagedPath, physicalCachePath);
253
+ } catch (error) {
254
+ if (backupCreated) try {
255
+ if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
256
+ await fs.rename(backupPath, physicalCachePath);
257
+ backupCreated = false;
258
+ } catch (restoreError) {
259
+ throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
260
+ }
261
+ throw error;
262
+ }
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
+ }
270
+ return true;
271
+ } finally {
272
+ if (!ownedParent || await directoryIdentityIsStable(ownedParent)) await fs.rm(stagingRoot, {
273
+ recursive: true,
274
+ force: true
275
+ });
276
+ }
277
+ }
278
+ function skippedCacheResult(status, message) {
279
+ return {
280
+ status,
281
+ changed: false,
282
+ message,
283
+ removedStaleVersions: [],
284
+ warnings: status === "source_missing" ? [message] : []
285
+ };
286
+ }
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
+ }
321
422
  function hasNodeErrorCode(error, code) {
322
423
  return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
323
424
  }
@@ -332,9 +433,13 @@ const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComp
332
433
  const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
333
434
  const COPY_TIMEOUT_MS = 12e4;
334
435
  const INSPECT_TIMEOUT_MS = 3e4;
335
- const COMPLETED_SYNC_CACHE_MAX_ENTRIES = 64;
336
436
  const activeInstalls = /* @__PURE__ */ new Map();
337
- const completedSyncs = /* @__PURE__ */ new Map();
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
+ }
338
443
  /** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
339
444
  async function ensureCodexComputerUseServiceApp(params) {
340
445
  const platform = params.platform ?? process.platform;
@@ -355,25 +460,12 @@ async function ensureCodexComputerUseServiceApp(params) {
355
460
  });
356
461
  const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
357
462
  const syncKey = [targetPath, ...candidates].join("\0");
358
- const completed = completedSyncs.get(targetPath);
359
- if (completed?.syncKey === syncKey) {
360
- completedSyncs.delete(targetPath);
361
- completedSyncs.set(targetPath, completed);
362
- return {
363
- status: "already_current",
364
- changed: false,
365
- targetPath: completed.targetPath,
366
- sourcePath: completed.sourcePath,
367
- sourceBuild: completed.sourceBuild
368
- };
369
- }
370
463
  const active = activeInstalls.get(targetPath);
371
464
  if (active) {
372
465
  if (active.syncKey === syncKey) return await active.promise;
373
466
  await active.promise.catch(() => void 0);
374
467
  return await ensureCodexComputerUseServiceApp(params);
375
468
  }
376
- if (completed) completedSyncs.delete(targetPath);
377
469
  const install = ensureCodexComputerUseServiceAppOnce({
378
470
  ...params,
379
471
  codexHome,
@@ -382,18 +474,6 @@ async function ensureCodexComputerUseServiceApp(params) {
382
474
  targetPath,
383
475
  platform,
384
476
  sourceAppCandidates: candidates
385
- }).then((result) => {
386
- if (isCompletedSyncStatus(result.status)) {
387
- completedSyncs.delete(targetPath);
388
- completedSyncs.set(targetPath, {
389
- syncKey,
390
- targetPath: result.targetPath,
391
- sourcePath: result.sourcePath,
392
- sourceBuild: result.sourceBuild
393
- });
394
- pruneMapToMaxSize(completedSyncs, COMPLETED_SYNC_CACHE_MAX_ENTRIES);
395
- }
396
- return result;
397
477
  });
398
478
  const activeEntry = {
399
479
  syncKey,
@@ -406,9 +486,6 @@ async function ensureCodexComputerUseServiceApp(params) {
406
486
  install.then(clearActive, clearActive);
407
487
  return await install;
408
488
  }
409
- function isCompletedSyncStatus(status) {
410
- return status === "installed" || status === "refreshed" || status === "already_current";
411
- }
412
489
  async function ensureCodexComputerUseServiceAppOnce(params) {
413
490
  const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
414
491
  const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
@@ -453,6 +530,7 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
453
530
  await assertNotSymlink(operationTargetPath, "Computer Use service target");
454
531
  if (await pathExists$1(operationTargetPath)) {
455
532
  await assertOwnedServiceParentStable(ownedParent);
533
+ params.assertCurrent?.();
456
534
  await fs.rename(operationTargetPath, backupPath);
457
535
  await assertOwnedServiceParentStable(ownedParent);
458
536
  backupCreated = true;
@@ -480,6 +558,7 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
480
558
  }
481
559
  try {
482
560
  await assertOwnedServiceParentStable(ownedParent);
561
+ params.assertCurrent?.();
483
562
  await fs.rename(stagedPath, operationTargetPath);
484
563
  await assertOwnedServiceParentStable(ownedParent);
485
564
  } catch (error) {
@@ -742,10 +821,12 @@ const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
742
821
  const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
743
822
  const CODEX_AUTH_JSON_FILENAME = "auth.json";
744
823
  const CODEX_HOME_DIRNAME = ".codex";
824
+ const MAX_COMPUTER_USE_ARTIFACT_OWNERS = 128;
825
+ const activeComputerUseArtifactReconciliations = /* @__PURE__ */ new Map();
745
826
  const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
746
827
  async function bridgeCodexAppServerStartOptions(params) {
747
828
  if (params.startOptions.transport !== "stdio") return params.startOptions;
748
- const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
829
+ const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir);
749
830
  if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
750
831
  if (params.authProfileId === null) return scopeStartOptions();
751
832
  const store = resolveCodexAppServerAuthProfileStore({
@@ -772,8 +853,7 @@ function assertNoUnimportedAgentCodexAuthFile(params) {
772
853
  if (message) throw new AgentHarnessPreflightError(message);
773
854
  }
774
855
  function resolveUnimportedAgentCodexAuthMessage(params) {
775
- const managedCodexCli = params.startOptions.commandSource === "managed" || params.startOptions.commandSource === "resolved-managed";
776
- if (params.startOptions.transport !== "stdio" || !managedCodexCli || params.startOptions.homeScope === "user") return;
856
+ if (params.startOptions.transport !== "stdio" || params.startOptions.homeScope === "user") return;
777
857
  const codexHome = resolveCodexAppServerHomeDir(params.agentDir);
778
858
  const authPath = path.join(codexHome, CODEX_AUTH_JSON_FILENAME);
779
859
  if (!fsSync.existsSync(authPath)) return;
@@ -845,7 +925,7 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
845
925
  const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
846
926
  const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
847
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;
848
- const chatgptAccountId = resolveExplicitChatgptAccountId(credential);
928
+ const chatgptAccountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : void 0;
849
929
  return {
850
930
  loginParams,
851
931
  secretFreeCacheKey,
@@ -974,28 +1054,10 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
974
1054
  hash.update(apiKey);
975
1055
  return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
976
1056
  }
977
- async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
1057
+ async function withCodexHomeEnvironment(startOptions, agentDir) {
978
1058
  const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
979
1059
  const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
980
1060
  await fs.mkdir(codexHome, { recursive: true });
981
- const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
982
- await ensureCodexComputerUseSharedPluginCache({
983
- codexHome,
984
- config: computerUseConfig
985
- });
986
- const ownsIsolatedCodexHome = startOptions.homeScope !== "user" && !startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim();
987
- if (computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome) try {
988
- await ensureCodexComputerUseServiceApp({
989
- codexHome,
990
- ownershipRoot: agentDir,
991
- appServerCommand: startOptions.command
992
- });
993
- } catch (error) {
994
- throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
995
- cause: error,
996
- scope: "harness"
997
- });
998
- }
999
1061
  if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
1000
1062
  const nextStartOptions = {
1001
1063
  ...startOptions,
@@ -1010,6 +1072,138 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
1010
1072
  else delete nextStartOptions.clearEnv;
1011
1073
  return nextStartOptions;
1012
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
+ };
1013
1207
  function withoutClearedCodexHomeEnv(clearEnv) {
1014
1208
  if (!clearEnv) return;
1015
1209
  const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
@@ -1017,36 +1211,21 @@ function withoutClearedCodexHomeEnv(clearEnv) {
1017
1211
  return filtered.length === clearEnv.length ? clearEnv : filtered;
1018
1212
  }
1019
1213
  async function applyCodexAppServerAuthProfile(params) {
1020
- if (params.preparedAuth?.kind === "profile") {
1021
- await params.client.request("account/login/start", params.preparedAuth.snapshot.loginParams);
1022
- return;
1023
- }
1024
- if (params.preparedAuth?.kind === "api-key") {
1025
- await params.client.request("account/login/start", {
1026
- type: "apiKey",
1027
- apiKey: params.preparedAuth.apiKey
1028
- });
1029
- return;
1030
- }
1031
- if (params.authProfileId === null) {
1214
+ if (!params.preparedAuth && params.authProfileId === null) {
1032
1215
  await assertNativeCodexAccountMatchesRoute(params.client, params.authRequirement);
1033
1216
  return;
1034
1217
  }
1035
- let loginParams;
1036
- try {
1037
- loginParams = await resolveCodexAppServerAuthProfileLoginParams({
1038
- agentDir: params.agentDir,
1039
- authProfileId: params.authProfileId,
1040
- authProfileStore: params.authProfileStore,
1041
- config: params.config
1042
- });
1043
- } catch (error) {
1044
- if (params.authRequirement === "subscription" && error instanceof CodexAppServerAuthProfileUnavailableError) throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.", error);
1045
- throw error;
1046
- }
1047
- if (params.authRequirement === "subscription" && loginParams?.type !== "chatgptAuthTokens") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
1218
+ const loginParams = params.preparedAuth?.kind === "profile" ? params.preparedAuth.snapshot.loginParams : params.preparedAuth?.kind === "api-key" ? {
1219
+ type: "apiKey",
1220
+ apiKey: params.preparedAuth.apiKey
1221
+ } : await resolveCodexAppServerAuthProfileLoginParams({
1222
+ agentDir: params.agentDir,
1223
+ authProfileId: params.authProfileId ?? void 0,
1224
+ authProfileStore: params.authProfileStore,
1225
+ config: params.config
1226
+ });
1227
+ 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.");
1048
1228
  if (!loginParams) {
1049
- if (params.authRequirement === "subscription") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
1050
1229
  if (params.authRequirement !== "api-key" || params.startOptions?.transport !== "stdio") return;
1051
1230
  const env = resolveCodexAppServerSpawnEnv(params.startOptions, process.env);
1052
1231
  const fallbackLoginParams = await resolveCodexAppServerFallbackApiKeyLoginParams({
@@ -1071,7 +1250,7 @@ async function assertNativeCodexAccountMatchesRoute(client, authRequirement) {
1071
1250
  const response = await client.request("account/read", { refreshToken: false });
1072
1251
  const accountType = isJsonObject(response.account) ? response.account.type : void 0;
1073
1252
  if (authRequirement === "subscription") {
1074
- if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
1253
+ 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.");
1075
1254
  return;
1076
1255
  }
1077
1256
  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.");
@@ -1080,7 +1259,13 @@ function createCodexAppServerAuthError(message, cause) {
1080
1259
  const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
1081
1260
  return Object.assign(error, { status: 401 });
1082
1261
  }
1083
- var CodexAppServerAuthProfileUnavailableError = class extends Error {};
1262
+ var CodexAppServerAuthProfileUnavailableError = class extends Error {
1263
+ constructor(..._args) {
1264
+ super(..._args);
1265
+ this.status = 401;
1266
+ this.code = "selected_auth_profile_unavailable";
1267
+ }
1268
+ };
1084
1269
  async function resolveCodexAppServerAuthProfileLoginParams(params) {
1085
1270
  const store = resolveCodexAppServerAuthProfileStore(params);
1086
1271
  const profileId = resolveCodexAppServerAuthProfileId({
@@ -1089,7 +1274,7 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
1089
1274
  config: params.config
1090
1275
  });
1091
1276
  const profile = profileId ? store.profiles[profileId] : void 0;
1092
- if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found.`);
1277
+ 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.`);
1093
1278
  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.`);
1094
1279
  return await resolveCodexAppServerAuthProfileLoginParamsInternal({
1095
1280
  ...params,
@@ -1097,11 +1282,24 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
1097
1282
  });
1098
1283
  }
1099
1284
  async function refreshCodexAppServerAuthTokens(params) {
1285
+ const previousAccountId = params.previousAccountId?.trim();
1286
+ if (previousAccountId) {
1287
+ const store = resolveCodexAppServerAuthProfileStore(params);
1288
+ const profileId = resolveCodexAppServerAuthProfileId({
1289
+ authProfileId: params.authProfileId,
1290
+ store,
1291
+ config: params.config
1292
+ });
1293
+ const credential = profileId ? store.profiles[profileId] : void 0;
1294
+ const selectedAccountId = credential ? resolveExplicitChatgptAccountId(credential) ?? (credential.type === "oauth" ? resolveOpenAICodexAuthIdentity({ access: credential.access }).accountId : void 0) : void 0;
1295
+ if (selectedAccountId && selectedAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh. Retry to start a client for the selected workspace.");
1296
+ }
1100
1297
  const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
1101
1298
  ...params,
1102
1299
  forceOAuthRefresh: true
1103
1300
  });
1104
- if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
1301
+ 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.");
1302
+ if (previousAccountId && loginParams.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
1105
1303
  return {
1106
1304
  accessToken: loginParams.accessToken,
1107
1305
  chatgptAccountId: loginParams.chatgptAccountId,
@@ -1122,8 +1320,8 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
1122
1320
  });
1123
1321
  if (!profileId) return;
1124
1322
  const credential = store.profiles[profileId];
1125
- if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found.`);
1126
- 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.`);
1323
+ if (!credential) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
1324
+ if (!isCodexAppServerAuthProfileCredential(credential)) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
1127
1325
  const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
1128
1326
  agentDir: params.agentDir,
1129
1327
  store,
@@ -1131,7 +1329,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
1131
1329
  forceOAuthRefresh: params.forceOAuthRefresh === true,
1132
1330
  config: params.config
1133
1331
  });
1134
- if (!loginParams) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" does not contain usable credentials.`);
1332
+ 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.`);
1135
1333
  return loginParams;
1136
1334
  }
1137
1335
  async function resolveCodexAppServerFallbackApiKeyLoginParams(params) {
@@ -1245,7 +1443,7 @@ async function resolveOAuthCredentialForCodexAppServer(profileId, credential, pa
1245
1443
  });
1246
1444
  if (params.forceRefresh && !persistedOAuthCredential && overlaidOAuthCredential) {
1247
1445
  const refreshedRuntimeCredential = await refreshOAuthCredentialForRuntime({ credential: overlaidOAuthCredential });
1248
- if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh.`);
1446
+ if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
1249
1447
  store.profiles[profileId] = refreshedRuntimeCredential;
1250
1448
  return refreshedRuntimeCredential;
1251
1449
  }
@@ -1273,8 +1471,8 @@ function shouldUseScopedOAuthCredential(params) {
1273
1471
  return !isDeepStrictEqual(persisted, params.suppliedCredential) && !hasMatchingOAuthIdentity(persisted, params.suppliedCredential);
1274
1472
  }
1275
1473
  function hasMatchingOAuthIdentity(persisted, supplied) {
1276
- const persistedAccountId = persisted.accountId?.trim();
1277
- const suppliedAccountId = supplied.accountId?.trim();
1474
+ const persistedAccountId = resolveOpenAICodexAuthIdentity(persisted).accountId?.trim();
1475
+ const suppliedAccountId = resolveOpenAICodexAuthIdentity(supplied).accountId?.trim();
1278
1476
  if (persistedAccountId && suppliedAccountId) return persistedAccountId === suppliedAccountId;
1279
1477
  const persistedEmail = persisted.email?.trim().toLowerCase();
1280
1478
  const suppliedEmail = supplied.email?.trim().toLowerCase();
@@ -1291,8 +1489,8 @@ async function resolveScopedOAuthCredential(params) {
1291
1489
  const credential = current?.type === "oauth" ? current : params.credential;
1292
1490
  if (!params.forceRefresh && hasUsableOAuthCredential(credential)) return credential;
1293
1491
  const refreshed = await refreshOAuthCredentialForRuntime({ credential });
1294
- if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh.`);
1295
- if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing.`);
1492
+ if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
1493
+ 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.`);
1296
1494
  params.store.profiles[params.profileId] = refreshed;
1297
1495
  return refreshed;
1298
1496
  })();
@@ -1339,10 +1537,15 @@ function readFirstNonEmptyEnvEntry(env, keys) {
1339
1537
  }
1340
1538
  }
1341
1539
  function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
1540
+ const storedAccountId = resolveExplicitChatgptAccountId(credential);
1541
+ const tokenAccountId = resolveOpenAICodexAuthIdentity({ access: accessToken }).accountId;
1542
+ 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.`);
1543
+ const chatgptAccountId = storedAccountId ?? tokenAccountId;
1544
+ if (!chatgptAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is missing its ChatGPT account ID. Sign in again before retrying.`);
1342
1545
  return {
1343
1546
  type: "chatgptAuthTokens",
1344
1547
  accessToken,
1345
- chatgptAccountId: resolveChatgptAccountId(profileId, credential),
1548
+ chatgptAccountId,
1346
1549
  chatgptPlanType: resolveChatgptPlanType(credential)
1347
1550
  };
1348
1551
  }
@@ -1365,7 +1568,7 @@ function resolveExplicitChatgptAccountId(credential) {
1365
1568
  }
1366
1569
  //#endregion
1367
1570
  //#region extensions/codex/src/app-server/rate-limit-cache.ts
1368
- const DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS = 10 * 6e4;
1571
+ const DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS = 6e5;
1369
1572
  const SPARSE_ACCOUNT_METADATA_KEYS = [
1370
1573
  "credits",
1371
1574
  "individualLimit",
@@ -1462,12 +1665,46 @@ function readLimitId(snapshot) {
1462
1665
  return typeof value === "string" && value.trim() ? value.trim() : "codex";
1463
1666
  }
1464
1667
  //#endregion
1668
+ //#region extensions/codex/src/app-server/timeout.ts
1669
+ /**
1670
+ * Thin Codex app-server timeout adapter around OpenClaw's shared security
1671
+ * runtime timeout helper.
1672
+ */
1673
+ function resolveAbortError(signal) {
1674
+ return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
1675
+ }
1676
+ /** Awaits a promise with a Codex-specific timeout error message. */
1677
+ async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
1678
+ return await withTimeout(promise, timeoutMs, {
1679
+ message: timeoutMessage,
1680
+ ...createError ? { createError } : {}
1681
+ });
1682
+ }
1683
+ /** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
1684
+ async function withAbortableTimeout(params) {
1685
+ const signal = params.signal;
1686
+ if (signal?.aborted) throw resolveAbortError(signal);
1687
+ let removeAbortListener;
1688
+ const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
1689
+ const onAbort = () => reject(resolveAbortError(signal));
1690
+ signal.addEventListener("abort", onAbort, { once: true });
1691
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
1692
+ })]) : params.promise;
1693
+ try {
1694
+ return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
1695
+ } finally {
1696
+ removeAbortListener?.();
1697
+ }
1698
+ }
1699
+ //#endregion
1465
1700
  //#region extensions/codex/src/app-server/client-runtime.ts
1466
1701
  /** Client-scoped Codex auth and account observers. */
1467
1702
  /** Match Codex's native grace window without retaining inactive conversations indefinitely. */
1468
- const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 30 * 6e4;
1703
+ const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 18e5;
1469
1704
  /** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
1470
1705
  const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
1706
+ /** Return a deterministic error before Codex cancels its ten-second external-auth request. */
1707
+ const CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS = 9e3;
1471
1708
  const configuredClients = /* @__PURE__ */ new WeakMap();
1472
1709
  const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
1473
1710
  const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
@@ -1481,7 +1718,10 @@ function ensureCodexAppServerClientRuntime(client, context) {
1481
1718
  const existing = configuredClients.get(client);
1482
1719
  if (existing) {
1483
1720
  if (existing.closed) return;
1484
- existing.context = context;
1721
+ existing.context = {
1722
+ ...existing.context,
1723
+ config: context.config
1724
+ };
1485
1725
  return;
1486
1726
  }
1487
1727
  const runtime = {
@@ -1506,12 +1746,21 @@ function ensureCodexAppServerClientRuntime(client, context) {
1506
1746
  client.addRequestHandler(async (request) => {
1507
1747
  if (request.method !== "account/chatgptAuthTokens/refresh") return;
1508
1748
  if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
1509
- return { ...await refreshCodexAppServerAuthTokens({
1510
- agentDir: runtime.context.agentDir,
1511
- authProfileId: runtime.context.authProfileId,
1512
- ...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
1513
- config: runtime.context.config
1514
- }) };
1749
+ const previousAccountId = isJsonObject(request.params) && typeof request.params.previousAccountId === "string" ? request.params.previousAccountId.trim() || void 0 : void 0;
1750
+ try {
1751
+ const tokens = await withTimeout$1(refreshCodexAppServerAuthTokens({
1752
+ agentDir: runtime.context.agentDir,
1753
+ authProfileId: runtime.context.authProfileId,
1754
+ ...previousAccountId ? { previousAccountId } : {},
1755
+ ...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
1756
+ config: runtime.context.config
1757
+ }), 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.");
1758
+ if (previousAccountId && tokens.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
1759
+ return { ...tokens };
1760
+ } catch (error) {
1761
+ runtime.context.onAuthRefreshFailure?.();
1762
+ throw error;
1763
+ }
1515
1764
  });
1516
1765
  client.addNotificationHandler((notification) => {
1517
1766
  if (notification.method === "account/rateLimits/updated") {
@@ -1547,10 +1796,11 @@ function scheduleRetainedThreadEviction(client, runtime) {
1547
1796
  }, Math.max(0, expiresAt - Date.now()));
1548
1797
  runtime.evictionTimer.unref?.();
1549
1798
  }
1550
- async function releaseRetainedThread(client, runtime, threadId) {
1799
+ async function releaseRetainedThread(client, runtime, threadId, assertCurrent) {
1551
1800
  const pendingRelease = runtime.releasingThreads.get(threadId);
1552
1801
  if (pendingRelease) {
1553
1802
  await pendingRelease.completion;
1803
+ assertCurrent?.();
1554
1804
  return false;
1555
1805
  }
1556
1806
  const retained = runtime.retainedThreads.get(threadId);
@@ -1562,7 +1812,10 @@ async function releaseRetainedThread(client, runtime, threadId) {
1562
1812
  }
1563
1813
  runtime.retainedThreads.delete(threadId);
1564
1814
  scheduleRetainedThreadEviction(client, runtime);
1565
- const transition = { completion: Promise.resolve().then(() => retained.release(threadId)) };
1815
+ const transition = { completion: Promise.resolve().then(() => {
1816
+ assertCurrent?.();
1817
+ return assertCurrent ? retained.release(threadId, assertCurrent) : retained.release(threadId);
1818
+ }) };
1566
1819
  runtime.releasingThreads.set(threadId, transition);
1567
1820
  try {
1568
1821
  await transition.completion;
@@ -1602,15 +1855,15 @@ async function retainCodexAppServerLiveThread(client, threadId, releaseThread, c
1602
1855
  const pendingRelease = runtime.releasingThreads.get(threadId);
1603
1856
  if (pendingRelease) {
1604
1857
  await pendingRelease.completion;
1605
- if (runtime.closed || claimed !== void 0 && runtime.claimedThreads.get(threadId) !== claimed) return false;
1858
+ return false;
1606
1859
  }
1607
1860
  runtime.retainedThreads.delete(threadId);
1608
1861
  const retained = {
1609
1862
  configFingerprint,
1610
1863
  serviceTier,
1611
1864
  expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
1612
- release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId) => {
1613
- await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
1865
+ release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId, assertCurrent) => {
1866
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
1614
1867
  })
1615
1868
  };
1616
1869
  runtime.retainedThreads.set(threadId, retained);
@@ -1654,8 +1907,8 @@ async function claimCodexAppServerLiveThread(client, threadId) {
1654
1907
  }
1655
1908
  return claimCodexAppServerThreadOwnership(client, runtime, threadId, runtime.retainedThreads.get(threadId) ?? {
1656
1909
  expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
1657
- release: async (releasedThreadId) => {
1658
- await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
1910
+ release: async (releasedThreadId, assertCurrent) => {
1911
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
1659
1912
  }
1660
1913
  });
1661
1914
  }
@@ -1664,7 +1917,7 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
1664
1917
  const claimed = Symbol(threadId);
1665
1918
  runtime.claimedThreads.set(threadId, claimed);
1666
1919
  scheduleRetainedThreadEviction(client, runtime);
1667
- const release = async (releasedThreadId) => {
1920
+ const release = async (releasedThreadId, assertCurrent) => {
1668
1921
  if (releasedThreadId !== threadId || runtime.claimedThreads.get(threadId) !== claimed) return;
1669
1922
  const pendingRelease = runtime.releasingThreads.get(threadId);
1670
1923
  if (pendingRelease) {
@@ -1673,7 +1926,8 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
1673
1926
  }
1674
1927
  const transition = { completion: Promise.resolve().then(async () => {
1675
1928
  if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) return;
1676
- await retained.release(releasedThreadId);
1929
+ assertCurrent?.();
1930
+ await (assertCurrent ? retained.release(releasedThreadId, assertCurrent) : retained.release(releasedThreadId));
1677
1931
  }) };
1678
1932
  runtime.releasingThreads.set(threadId, transition);
1679
1933
  try {
@@ -1692,22 +1946,32 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
1692
1946
  };
1693
1947
  }
1694
1948
  /** Distinguish active claimed ownership from an already-evicted idle subscription. */
1949
+ function hasCodexAppServerLiveThread(client, threadId) {
1950
+ const runtime = configuredClients.get(client);
1951
+ return runtime !== void 0 && !runtime.closed && (runtime.retainedThreads.has(threadId) || runtime.releasingThreads.has(threadId) || runtime.claimedThreads.has(threadId));
1952
+ }
1695
1953
  function isCodexAppServerLiveThreadClaimed(client, threadId) {
1696
1954
  const runtime = configuredClients.get(client);
1697
1955
  return runtime !== void 0 && !runtime.closed && runtime.claimedThreads.has(threadId);
1698
1956
  }
1699
- /** Release the exact physical subscription and finish only its observed claim generation. */
1700
- async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs) {
1957
+ /** Release the exact physical subscription and finish only its observed ownership generation. */
1958
+ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs, assertCurrent) {
1701
1959
  const runtime = configuredClients.get(client);
1702
1960
  const claimed = runtime?.claimedThreads.get(threadId);
1961
+ const retained = runtime?.retainedThreads.get(threadId);
1703
1962
  let transition = runtime?.releasingThreads.get(threadId);
1704
1963
  if (transition?.physicalRelease) {
1705
1964
  await transition.physicalRelease;
1965
+ assertCurrent?.();
1706
1966
  return;
1707
1967
  }
1708
1968
  const physicalRelease = Promise.resolve().then(async () => {
1709
- if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed) return;
1710
- await client.request("thread/unsubscribe", { threadId }, { timeoutMs });
1969
+ if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed || retained !== void 0 && runtime?.retainedThreads.get(threadId) !== retained) return;
1970
+ assertCurrent?.();
1971
+ await client.request("thread/unsubscribe", { threadId }, {
1972
+ timeoutMs,
1973
+ assertCurrent
1974
+ });
1711
1975
  });
1712
1976
  const ownsTransition = runtime !== void 0 && transition === void 0;
1713
1977
  if (transition) transition.physicalRelease = physicalRelease;
@@ -1720,15 +1984,19 @@ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs)
1720
1984
  }
1721
1985
  try {
1722
1986
  await physicalRelease;
1987
+ if (retained !== void 0 && runtime?.retainedThreads.get(threadId) === retained) {
1988
+ runtime.retainedThreads.delete(threadId);
1989
+ scheduleRetainedThreadEviction(client, runtime);
1990
+ }
1723
1991
  if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1724
1992
  } finally {
1725
1993
  if (ownsTransition && runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1726
1994
  }
1727
1995
  }
1728
1996
  /** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
1729
- async function releaseCodexAppServerLiveThread(client, threadId) {
1997
+ async function releaseCodexAppServerLiveThread(client, threadId, assertCurrent) {
1730
1998
  const runtime = configuredClients.get(client);
1731
- return runtime ? await releaseRetainedThread(client, runtime, threadId) : false;
1999
+ return runtime ? await releaseRetainedThread(client, runtime, threadId, assertCurrent) : false;
1732
2000
  }
1733
2001
  /** Native child work pins its parent's subscription even after the foreground parent turn ends. */
1734
2002
  function protectCodexAppServerLiveThread(client, threadId) {
@@ -1746,11 +2014,9 @@ function protectCodexAppServerLiveThread(client, threadId) {
1746
2014
  runtime.protectedThreads.delete(threadId);
1747
2015
  const retained = runtime.retainedThreads.get(threadId);
1748
2016
  if (retained) {
2017
+ retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
1749
2018
  runtime.retainedThreads.delete(threadId);
1750
- runtime.retainedThreads.set(threadId, {
1751
- ...retained,
1752
- expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS
1753
- });
2019
+ runtime.retainedThreads.set(threadId, retained);
1754
2020
  }
1755
2021
  } else runtime.protectedThreads.set(threadId, count - 1);
1756
2022
  scheduleRetainedThreadEviction(client, runtime);
@@ -1763,6 +2029,453 @@ function protectCodexAppServerLiveThread(client, threadId) {
1763
2029
  };
1764
2030
  }
1765
2031
  //#endregion
2032
+ //#region extensions/codex/src/app-server/dynamic-tool-response-state.ts
2033
+ /** Retains the host-owned app preview without adding it to Codex's response payload. */
2034
+ function withDynamicToolTranscriptDetails(response, details) {
2035
+ if (details === void 0) return response;
2036
+ Object.defineProperty(response, "transcriptDetails", {
2037
+ configurable: true,
2038
+ enumerable: false,
2039
+ value: details
2040
+ });
2041
+ return response;
2042
+ }
2043
+ function withDynamicToolTerminalResolution(response, terminalResolution) {
2044
+ if (terminalResolution) {
2045
+ Object.defineProperties(response, {
2046
+ terminalResolution: {
2047
+ configurable: true,
2048
+ enumerable: false,
2049
+ value: terminalResolution
2050
+ },
2051
+ executionStarted: {
2052
+ configurable: true,
2053
+ enumerable: false,
2054
+ value: terminalResolution.executionStarted
2055
+ },
2056
+ ...terminalResolution.executedArguments ? { executedArguments: {
2057
+ configurable: true,
2058
+ enumerable: false,
2059
+ value: terminalResolution.executedArguments
2060
+ } } : {}
2061
+ });
2062
+ withDynamicToolSideEffectEvidence(response, terminalResolution.sideEffectEvidence);
2063
+ }
2064
+ return response;
2065
+ }
2066
+ function withDynamicToolExecutionState(response, state) {
2067
+ Object.defineProperties(response, {
2068
+ executedArguments: {
2069
+ configurable: true,
2070
+ enumerable: false,
2071
+ value: state.executedArguments
2072
+ },
2073
+ executionStarted: {
2074
+ configurable: true,
2075
+ enumerable: false,
2076
+ value: state.executionStarted
2077
+ }
2078
+ });
2079
+ return withDynamicToolSideEffectEvidence(response, state.sideEffectEvidence === true);
2080
+ }
2081
+ function withDynamicToolSideEffectEvidence(response, sideEffectEvidence) {
2082
+ if (!sideEffectEvidence) {
2083
+ delete response.sideEffectEvidence;
2084
+ return response;
2085
+ }
2086
+ Object.defineProperty(response, "sideEffectEvidence", {
2087
+ configurable: true,
2088
+ enumerable: false,
2089
+ value: true
2090
+ });
2091
+ return response;
2092
+ }
2093
+ function createFailedDynamicToolResponse(message, options) {
2094
+ const response = {
2095
+ contentItems: [{
2096
+ type: "inputText",
2097
+ text: message
2098
+ }],
2099
+ success: false
2100
+ };
2101
+ Object.defineProperties(response, {
2102
+ diagnosticTerminalReason: {
2103
+ configurable: true,
2104
+ enumerable: false,
2105
+ value: options?.terminalReason ?? "failed"
2106
+ },
2107
+ diagnosticTerminalType: {
2108
+ configurable: true,
2109
+ enumerable: false,
2110
+ value: "error"
2111
+ }
2112
+ });
2113
+ if (options?.executionStarted !== void 0) Object.defineProperty(response, "executionStarted", {
2114
+ configurable: true,
2115
+ enumerable: false,
2116
+ value: options.executionStarted
2117
+ });
2118
+ if (options?.executedArguments !== void 0) Object.defineProperty(response, "executedArguments", {
2119
+ configurable: true,
2120
+ enumerable: false,
2121
+ value: options.executedArguments
2122
+ });
2123
+ return withDynamicToolSideEffectEvidence(response, options?.sideEffectEvidence === true);
2124
+ }
2125
+ //#endregion
2126
+ //#region extensions/codex/src/app-server/tool-abort-terminal-reason.ts
2127
+ /** Leaf helper shared by native and dynamic tool diagnostics. */
2128
+ const CODEX_TIMEOUT_ABORT_REASONS = /* @__PURE__ */ new Set([
2129
+ "codex_startup_timeout",
2130
+ "turn_completion_idle_timeout",
2131
+ "turn_progress_idle_timeout",
2132
+ "turn_terminal_idle_timeout"
2133
+ ]);
2134
+ /** Preserves timeout provenance when an enclosing run aborts an active tool. */
2135
+ function resolveCodexToolAbortTerminalReason(signal) {
2136
+ try {
2137
+ const reason = signal.reason;
2138
+ if (typeof reason === "string") {
2139
+ if (CODEX_TIMEOUT_ABORT_REASONS.has(reason)) return "timed_out";
2140
+ return reason === "client_closed" ? "failed" : "cancelled";
2141
+ }
2142
+ if (reason && typeof reason === "object") {
2143
+ const record = reason;
2144
+ if (record.name === "TimeoutError" || record.reason === "timeout") return "timed_out";
2145
+ }
2146
+ } catch {
2147
+ return "cancelled";
2148
+ }
2149
+ return "cancelled";
2150
+ }
2151
+ //#endregion
2152
+ //#region extensions/codex/src/app-server/dynamic-tool-execution.ts
2153
+ /**
2154
+ * Timeout, terminal-release, and diagnostic helpers for Codex dynamic tool
2155
+ * calls.
2156
+ */
2157
+ /** Default timeout for Codex dynamic tool calls. */
2158
+ const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 9e4;
2159
+ /** Hard cap for per-call Codex dynamic tool timeout overrides. */
2160
+ const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
2161
+ const CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS = 3e4;
2162
+ const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
2163
+ const CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS = 3e4;
2164
+ const CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS = 3e4;
2165
+ /** Timeout for image-understanding style dynamic tool calls. */
2166
+ const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
2167
+ /** Timeout for message-delivery dynamic tool calls. */
2168
+ const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 6e5;
2169
+ /** Outer default for collector waits: full swarm budget plus completion grace. */
2170
+ const CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS = 63e4;
2171
+ const LOG_FIELD_MAX_LENGTH = 160;
2172
+ function normalizeLogField(value) {
2173
+ if (typeof value !== "string") return;
2174
+ const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
2175
+ if (!normalized) return;
2176
+ return normalized.length > LOG_FIELD_MAX_LENGTH ? `${truncateUtf16Safe(normalized, 157)}...` : normalized;
2177
+ }
2178
+ function readNumericTimeoutMs(value) {
2179
+ if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
2180
+ if (typeof value === "string") {
2181
+ const parsed = parseStrictNonNegativeInteger(value);
2182
+ if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
2183
+ }
2184
+ }
2185
+ function formatDynamicToolTimeoutDetails(params) {
2186
+ const tool = normalizeLogField(params.call.tool) ?? "unknown";
2187
+ const baseMeta = {
2188
+ tool: params.call.tool,
2189
+ toolCallId: params.call.callId,
2190
+ threadId: params.call.threadId,
2191
+ turnId: params.call.turnId,
2192
+ timeoutMs: params.timeoutMs,
2193
+ timeoutKind: "codex_dynamic_tool_rpc"
2194
+ };
2195
+ if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
2196
+ responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
2197
+ consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
2198
+ meta: baseMeta
2199
+ };
2200
+ const action = normalizeLogField(params.call.arguments.action);
2201
+ const sessionId = normalizeLogField(params.call.arguments.sessionId);
2202
+ const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
2203
+ const actionPart = action ? ` action=${action}` : "";
2204
+ const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
2205
+ const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
2206
+ const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
2207
+ const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
2208
+ return {
2209
+ responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
2210
+ consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
2211
+ meta: {
2212
+ ...baseMeta,
2213
+ processAction: action,
2214
+ processSessionId: sessionId,
2215
+ processRequestedTimeoutMs: requestedTimeoutMs
2216
+ }
2217
+ };
2218
+ }
2219
+ /**
2220
+ * Runs a dynamic tool call with run-abort and the budget prepared by
2221
+ * resolveDynamicToolCallTimeoutMs, preserving tool-specific completion grace.
2222
+ */
2223
+ async function handleDynamicToolCallWithTimeout(params) {
2224
+ let didNotifyAgentToolResult = false;
2225
+ const conservativeRaceResponses = /* @__PURE__ */ new WeakSet();
2226
+ const finalizeTerminal = (response) => {
2227
+ const executionSnapshot = params.toolBridge.consumeToolExecutionSnapshot?.(params.call.callId);
2228
+ const ownerKey = params.toolBridge.sideEffectOwnerKeyForTool?.(params.call.tool);
2229
+ const observedExecutionStarted = executionSnapshot?.executionStarted ?? (conservativeRaceResponses.has(response) ? void 0 : response.executionStarted);
2230
+ const terminalResolution = params.observeToolTerminal?.({
2231
+ toolCallId: params.call.callId,
2232
+ toolName: params.call.tool,
2233
+ arguments: response.executedArguments ?? executionSnapshot?.executedArguments ?? params.call.arguments,
2234
+ ...params.toolMeta ? { meta: params.toolMeta } : {},
2235
+ ...ownerKey ? { ownerMutation: { ownerKey } } : {},
2236
+ ...observedExecutionStarted !== void 0 ? { executionStarted: observedExecutionStarted } : {},
2237
+ outcome: response.success ? "success" : "failure",
2238
+ ...!response.success ? { failure: { error: readDynamicToolResponseText(response) } } : {}
2239
+ });
2240
+ return withDynamicToolTerminalResolution(response, terminalResolution);
2241
+ };
2242
+ const createFailedAfterPossibleDispatch = (message, terminalReason) => {
2243
+ const response = createFailedDynamicToolResponse(message, {
2244
+ executionStarted: true,
2245
+ sideEffectEvidence: true,
2246
+ terminalReason
2247
+ });
2248
+ conservativeRaceResponses.add(response);
2249
+ return response;
2250
+ };
2251
+ const notifyAgentToolResult = (event) => {
2252
+ if (didNotifyAgentToolResult) return;
2253
+ didNotifyAgentToolResult = true;
2254
+ try {
2255
+ params.onAgentToolResult?.(event);
2256
+ } catch (error) {
2257
+ embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
2258
+ }
2259
+ };
2260
+ const notifyFailedToolResult = (message, terminalReason = "failed") => {
2261
+ notifyAgentToolResult({
2262
+ toolName: params.call.tool,
2263
+ result: {
2264
+ content: [{
2265
+ type: "text",
2266
+ text: message
2267
+ }],
2268
+ details: {
2269
+ status: terminalReason,
2270
+ error: message
2271
+ }
2272
+ },
2273
+ isError: true
2274
+ });
2275
+ };
2276
+ if (params.signal.aborted) {
2277
+ const message = "OpenClaw dynamic tool call aborted before execution.";
2278
+ const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
2279
+ params.onFallbackSelected?.();
2280
+ notifyFailedToolResult(message, terminalReason);
2281
+ return finalizeTerminal(createFailedDynamicToolResponse(message, {
2282
+ executionStarted: false,
2283
+ terminalReason
2284
+ }));
2285
+ }
2286
+ const controller = new AbortController();
2287
+ let timeout;
2288
+ let timedOut = false;
2289
+ let resolveAbort;
2290
+ const abortFromRun = () => {
2291
+ const message = "OpenClaw dynamic tool call aborted.";
2292
+ const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
2293
+ params.onFallbackSelected?.();
2294
+ controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
2295
+ notifyFailedToolResult(message, terminalReason);
2296
+ resolveAbort?.(createFailedAfterPossibleDispatch(message, terminalReason));
2297
+ };
2298
+ const abortPromise = new Promise((resolve) => {
2299
+ resolveAbort = resolve;
2300
+ });
2301
+ const timeoutPromise = new Promise((resolve) => {
2302
+ const { timeoutMs } = params;
2303
+ timeout = setTimeout(() => {
2304
+ timedOut = true;
2305
+ const timeoutDetails = formatDynamicToolTimeoutDetails({
2306
+ call: params.call,
2307
+ timeoutMs
2308
+ });
2309
+ params.onFallbackSelected?.();
2310
+ controller.abort(new Error(timeoutDetails.responseMessage));
2311
+ params.onTimeout?.();
2312
+ embeddedAgentLog.warn("codex dynamic tool call timed out", {
2313
+ ...timeoutDetails.meta,
2314
+ consoleMessage: timeoutDetails.consoleMessage
2315
+ });
2316
+ notifyFailedToolResult(timeoutDetails.responseMessage, "timed_out");
2317
+ resolve(createFailedAfterPossibleDispatch(timeoutDetails.responseMessage, "timed_out"));
2318
+ }, timeoutMs);
2319
+ timeout.unref?.();
2320
+ });
2321
+ try {
2322
+ params.signal.addEventListener("abort", abortFromRun, { once: true });
2323
+ if (params.signal.aborted) abortFromRun();
2324
+ const response = await Promise.race([
2325
+ params.toolBridge.handleToolCall(params.call, {
2326
+ signal: controller.signal,
2327
+ onAgentToolResult: notifyAgentToolResult,
2328
+ toolCallOrdinal: params.toolCallOrdinal,
2329
+ retainExecutionSnapshot: true
2330
+ }),
2331
+ abortPromise,
2332
+ timeoutPromise
2333
+ ]);
2334
+ if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response), response.diagnosticTerminalReason ?? "failed");
2335
+ return finalizeTerminal(response);
2336
+ } catch (error) {
2337
+ const terminalReason = params.signal.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : resolveToolExecutionErrorKind(error);
2338
+ const message = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
2339
+ notifyFailedToolResult(message, terminalReason);
2340
+ return finalizeTerminal(createFailedAfterPossibleDispatch(message, terminalReason));
2341
+ } finally {
2342
+ if (timeout) clearTimeout(timeout);
2343
+ params.signal.removeEventListener("abort", abortFromRun);
2344
+ resolveAbort = void 0;
2345
+ if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
2346
+ }
2347
+ }
2348
+ function readDynamicToolResponseText(response) {
2349
+ return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
2350
+ }
2351
+ /** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
2352
+ function toCodexDynamicToolProtocolResponse(response) {
2353
+ return {
2354
+ contentItems: response.contentItems,
2355
+ success: response.success
2356
+ };
2357
+ }
2358
+ /** Adds async-started progress details when a tool result continues out of band. */
2359
+ function toCodexDynamicToolProgressResponse(response, protocolResponse) {
2360
+ const transcriptDetails = isJsonObject(response.transcriptDetails) ? response.transcriptDetails : void 0;
2361
+ const mcpAppPreview = isJsonObject(transcriptDetails?.mcpAppPreview) ? transcriptDetails.mcpAppPreview : void 0;
2362
+ const progressDetails = mcpAppPreview ? { mcpAppPreview } : void 0;
2363
+ if (response.asyncStarted !== true && progressDetails === void 0) return protocolResponse;
2364
+ return {
2365
+ ...protocolResponse,
2366
+ ...progressDetails ? { details: progressDetails } : {},
2367
+ ...response.asyncStarted === true ? { details: {
2368
+ ...progressDetails,
2369
+ async: true,
2370
+ status: "started"
2371
+ } } : {}
2372
+ };
2373
+ }
2374
+ /** Decides whether a terminal dynamic tool response can release the Codex turn. */
2375
+ function shouldReleaseTurnAfterTerminalDynamicTool(state) {
2376
+ return !state.completed && !state.aborted && state.responseSuccess && !state.currentTurnHadNonTerminalDynamicToolResult && state.activeAppServerTurnRequests === 0 && state.activeTurnItemIdsCount === 0 && state.pendingOpenClawDynamicToolCompletionIdsCount === 0;
2377
+ }
2378
+ /** Returns true when a non-async result should block terminal-release shortcuts. */
2379
+ function shouldBlockTerminalReleaseForNonTerminalDynamicToolResult(response) {
2380
+ return response.asyncStarted !== true;
2381
+ }
2382
+ /** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
2383
+ function resolveTerminalDynamicToolBatchAction(state) {
2384
+ if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
2385
+ if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
2386
+ if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
2387
+ return "idle";
2388
+ }
2389
+ /** Returns true for diagnostic events that terminate a dynamic tool call. */
2390
+ function isDynamicToolTerminalDiagnosticEvent(event) {
2391
+ return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
2392
+ }
2393
+ /** Matches terminal diagnostics to a specific dynamic tool call id/name. */
2394
+ function isMatchingDynamicToolTerminalDiagnostic(params) {
2395
+ if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
2396
+ if (params.runId !== void 0) return params.event.runId === params.runId;
2397
+ if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
2398
+ if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
2399
+ return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
2400
+ }
2401
+ /** Checks pending diagnostics for a terminal event matching a tool call. */
2402
+ function hasPendingDynamicToolTerminalDiagnostic(params) {
2403
+ return hasPendingInternalDiagnosticEvent((event) => {
2404
+ if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
2405
+ return isMatchingDynamicToolTerminalDiagnostic({
2406
+ event,
2407
+ call: params.call,
2408
+ runId: params.runId,
2409
+ sessionId: params.sessionId,
2410
+ sessionKey: params.sessionKey
2411
+ });
2412
+ });
2413
+ }
2414
+ /** Resolves per-tool timeout, applying media/message defaults and hard caps. */
2415
+ function resolveDynamicToolCallTimeoutMs(params) {
2416
+ const args = isJsonObject(params.call.arguments) ? params.call.arguments : void 0;
2417
+ if (params.call.tool === "ask_user" || params.call.tool === "secrets" && args?.action === "request") try {
2418
+ return normalizeQuestionTimeoutSeconds(args?.timeoutSeconds) * 1e3 + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
2419
+ } catch {
2420
+ return CODEX_DYNAMIC_TOOL_TIMEOUT_MS;
2421
+ }
2422
+ if (params.call.tool === "computer") return clampDynamicToolTimeoutMs(readComputerToolTimeoutMs(params.call.arguments));
2423
+ if (params.call.tool === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
2424
+ if (params.call.tool === "agents_wait") {
2425
+ const requestedMs = readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS;
2426
+ return Math.max(1, Math.min(63e4, Math.floor(requestedMs)));
2427
+ }
2428
+ return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_TOOL_TIMEOUT_MS);
2429
+ }
2430
+ /** Transport guard stays outside the handler's bounded tool wait and completion grace. */
2431
+ function resolveDynamicToolServerRequestTimeoutMs(call) {
2432
+ return Math.max(63e4, call ? resolveDynamicToolCallTimeoutMs({
2433
+ call,
2434
+ config: void 0
2435
+ }) : 0) + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
2436
+ }
2437
+ function readComputerToolTimeoutMs(value) {
2438
+ const args = isJsonObject(value) ? value : void 0;
2439
+ const action = typeof args?.action === "string" ? args.action : void 0;
2440
+ const gatewayTimeoutMs = readPositiveFiniteTimeoutMs(args?.timeoutMs) ?? CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS;
2441
+ const gatewayCallCount = action === "screenshot" || action === "wait" ? 3 : 4;
2442
+ return (action === "wait" || action === "hold_key" ? Math.max(0, Number(args?.duration) || 0) * 1e3 : 0) + gatewayCallCount * gatewayTimeoutMs + CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS;
2443
+ }
2444
+ function readDynamicToolCallTimeoutMs(value) {
2445
+ if (!isJsonObject(value)) return;
2446
+ const timeoutMs = readPositiveFiniteTimeoutMs(value.timeoutMs);
2447
+ if (timeoutMs !== void 0) return timeoutMs;
2448
+ const timeoutSecondsMs = readDynamicToolTimeoutSecondsAsMs(value.timeoutSeconds);
2449
+ return timeoutSecondsMs === void 0 ? void 0 : addTimerTimeoutGraceMs(timeoutSecondsMs, CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS);
2450
+ }
2451
+ function readConfiguredDynamicToolTimeoutMs(toolName, config) {
2452
+ if (toolName === "image_generate") {
2453
+ const imageModel = config?.agents?.defaults?.mediaModels?.image;
2454
+ if (!imageModel || typeof imageModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
2455
+ return readPositiveFiniteTimeoutMs(imageModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
2456
+ }
2457
+ if (toolName === "view_image") {
2458
+ const candidates = (config?.tools?.media?.models ?? []).filter((entry) => !entry.capabilities || entry.capabilities.includes("image"));
2459
+ const capabilityTimeoutMs = readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds);
2460
+ return Math.max(capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS, ...candidates.map((entry) => readTimeoutSecondsAsMs(entry.timeoutSeconds) ?? capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS));
2461
+ }
2462
+ if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
2463
+ }
2464
+ function readTimeoutSecondsAsMs(value) {
2465
+ const seconds = readPositiveFiniteTimeoutMs(value);
2466
+ return seconds === void 0 ? void 0 : seconds * 1e3;
2467
+ }
2468
+ function readDynamicToolTimeoutSecondsAsMs(value) {
2469
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) return;
2470
+ return value * 1e3;
2471
+ }
2472
+ function readPositiveFiniteTimeoutMs(value) {
2473
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
2474
+ }
2475
+ function clampDynamicToolTimeoutMs(timeoutMs) {
2476
+ return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
2477
+ }
2478
+ //#endregion
1766
2479
  //#region extensions/codex/src/app-server/elicitation-response.ts
1767
2480
  function createCodexElicitationResponse(action, content = null, meta = null) {
1768
2481
  return {
@@ -1939,357 +2652,33 @@ function createWebSocketTransport(options) {
1939
2652
  get killed() {
1940
2653
  return killed;
1941
2654
  },
1942
- kill: () => {
1943
- killed = true;
1944
- clearConnectionHealthTimers();
1945
- socket.close();
1946
- },
1947
- once: (event, listener) => events.once(event, listener)
1948
- };
1949
- }
1950
- /** Opens the owner-scoped Codex control socket used by the WebSocket upgrade. */
1951
- function connectCodexAppServerUnixSocket(socketPath) {
1952
- return net.createConnection(socketPath);
1953
- }
1954
- /** Resolves the canonical or explicitly configured Codex control socket. */
1955
- function resolveCodexAppServerUnixSocketPath(options) {
1956
- if (options.transport !== "unix") {
1957
- if (options.url?.startsWith("unix://")) throw new Error("codex app-server unix URL requires unix transport");
1958
- return;
1959
- }
1960
- const url = options.url ?? "unix://";
1961
- if (!url.startsWith("unix://")) throw new Error("codex app-server unix transport requires a unix:// URL");
1962
- return url.slice(7) || path.join(resolveCodexAppServerUserHomeDir(options.env ?? process.env), "app-server-control", "app-server-control.sock");
1963
- }
1964
- function websocketFrameToText(data) {
1965
- if (typeof data === "string") return data;
1966
- if (Buffer.isBuffer(data)) return data.toString("utf8");
1967
- if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
1968
- return Buffer.from(data).toString("utf8");
1969
- }
1970
- //#endregion
1971
- //#region extensions/codex/src/app-server/transport-process-containment.ts
1972
- const PROCESS_COLUMNS = "pid=,ppid=,pgid=,stat=,lstart=";
1973
- const MAX_CONTAINED_PROCESSES = 512;
1974
- const MAX_PROCESS_CONTAINMENT_MS = 2e3;
1975
- const MAX_PROCESS_QUIESCE_PASSES = 16;
1976
- const PROCESS_INSPECTION_MAX_BYTES = 8 * 1024 * 1024;
1977
- async function terminateCodexAppServerDescendants(child) {
1978
- const rootPid = child.pid;
1979
- if (process.platform === "win32" || !rootPid || !child.kill || hasExited(child)) return;
1980
- const deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS;
1981
- const snapshot = await readProcessSnapshot(deadline);
1982
- if (!snapshot || Date.now() >= deadline) return;
1983
- const root = snapshot.find((row) => row.pid === rootPid);
1984
- if (!root || !isSameLiveRoot(root, root)) return;
1985
- const initialDescendants = collectDescendants(snapshot, [rootPid]);
1986
- if (initialDescendants.length > MAX_CONTAINED_PROCESSES) return;
1987
- const stoppedDescendants = /* @__PURE__ */ new Map();
1988
- if (!await signalSameRoot(root, "SIGSTOP", deadline)) return;
1989
- let resumeRootOnUnwind = true;
1990
- try {
1991
- const descendants = await quiesceDescendants(root, initialDescendants, stoppedDescendants, deadline);
1992
- if (!descendants) return;
1993
- for (const descendant of descendants.toReversed()) {
1994
- if (Date.now() >= deadline) return;
1995
- if (!descendant.state.startsWith("Z")) {
1996
- if (!await signalSameProcess(descendant, "SIGKILL", deadline) || Date.now() >= deadline) return;
1997
- }
1998
- }
1999
- resumeRootOnUnwind = false;
2000
- let resumed = false;
2001
- return () => {
2002
- if (resumed) return;
2003
- resumed = true;
2004
- resumeTransportRoot(child, root, false);
2005
- };
2006
- } finally {
2007
- if (resumeRootOnUnwind) {
2008
- for (const descendant of stoppedDescendants.values()) signalProcess(descendant.pid, "SIGCONT");
2009
- resumeTransportRoot(child, root, true);
2010
- }
2011
- }
2012
- }
2013
- async function quiesceDescendants(root, initialDescendants, stopped, deadline) {
2014
- const provenByPid = new Map(initialDescendants.map((descendant) => [descendant.pid, descendant]));
2015
- const stopFailures = /* @__PURE__ */ new Map();
2016
- for (let pass = 0; pass < MAX_PROCESS_QUIESCE_PASSES; pass += 1) {
2017
- if (Date.now() >= deadline) return;
2018
- const snapshot = await readProcessSnapshot(deadline);
2019
- if (!snapshot || Date.now() >= deadline) return;
2020
- const currentRoot = snapshot.find((row) => row.pid === root.pid);
2021
- if (!currentRoot || !isSameLiveRoot(currentRoot, root)) return;
2022
- if (!isSameLiveRoot(currentRoot, root, true)) {
2023
- if (!await signalSameRoot(root, "SIGSTOP", deadline) || Date.now() >= deadline) return;
2024
- continue;
2025
- }
2026
- const snapshotByPid = new Map(snapshot.map((process) => [process.pid, process]));
2027
- const liveProven = [];
2028
- for (const proven of provenByPid.values()) {
2029
- const current = snapshotByPid.get(proven.pid);
2030
- if (!current) {
2031
- provenByPid.delete(proven.pid);
2032
- stopped.delete(identityKey(proven));
2033
- continue;
2034
- }
2035
- if (!hasSameIdentity(proven, current)) return;
2036
- provenByPid.set(current.pid, current);
2037
- const key = identityKey(current);
2038
- if (stopped.has(key)) stopped.set(key, current);
2039
- liveProven.push(current);
2040
- }
2041
- const descendants = collectDescendants(snapshot, [root.pid, ...liveProven.map(({ pid }) => pid)]);
2042
- for (const descendant of descendants) {
2043
- const proven = provenByPid.get(descendant.pid);
2044
- if (proven && !hasSameIdentity(proven, descendant)) return;
2045
- provenByPid.set(descendant.pid, descendant);
2046
- }
2047
- if (provenByPid.size > MAX_CONTAINED_PROCESSES) return;
2048
- const quiescenceTargets = new Map(liveProven.map((process) => [process.pid, process]));
2049
- for (const descendant of descendants) quiescenceTargets.set(descendant.pid, descendant);
2050
- let allStopped = true;
2051
- for (const descendant of quiescenceTargets.values()) {
2052
- if (Date.now() >= deadline) return;
2053
- if (isStoppedState(descendant.state)) continue;
2054
- const stopQueued = await signalSameProcess(descendant, "SIGSTOP", deadline);
2055
- if (Date.now() >= deadline) return;
2056
- if (stopQueued) {
2057
- stopFailures.delete(identityKey(descendant));
2058
- stopped.set(identityKey(descendant), descendant);
2059
- } else {
2060
- const key = identityKey(descendant);
2061
- const failures = (stopFailures.get(key) ?? 0) + 1;
2062
- if (failures >= 2) return;
2063
- stopFailures.set(key, failures);
2064
- }
2065
- if (!isUninterruptibleState(descendant.state) || !stopQueued) allStopped = false;
2066
- }
2067
- if (allStopped) return [...provenByPid.values()];
2068
- }
2069
- }
2070
- async function readProcessSnapshot(deadline) {
2071
- return await readProcesses(["-axo", PROCESS_COLUMNS], deadline);
2072
- }
2073
- async function readProcess(pid, deadline) {
2074
- return (await readProcesses([
2075
- "-o",
2076
- PROCESS_COLUMNS,
2077
- "-p",
2078
- String(pid)
2079
- ], deadline))?.find((row) => row.pid === pid);
2080
- }
2081
- async function readProcesses(args, deadline) {
2082
- const remainingMs = deadline - Date.now();
2083
- if (remainingMs <= 0) return;
2084
- return await new Promise((resolve) => {
2085
- let settled = false;
2086
- const settle = (processes) => {
2087
- if (settled) return;
2088
- settled = true;
2089
- clearTimeout(timer);
2090
- resolve(processes);
2091
- };
2092
- const inspector = execFile("ps", args, {
2093
- encoding: "utf8",
2094
- maxBuffer: PROCESS_INSPECTION_MAX_BYTES
2095
- }, (error, stdout) => {
2096
- settle(error ? void 0 : parseProcesses(stdout));
2097
- });
2098
- const timer = setTimeout(() => {
2099
- settle(void 0);
2100
- inspector.stdout?.destroy();
2101
- inspector.stderr?.destroy();
2102
- inspector.kill("SIGKILL");
2103
- inspector.unref();
2104
- }, Math.max(1, remainingMs));
2105
- timer.unref?.();
2106
- });
2107
- }
2108
- function parseProcesses(output) {
2109
- const rows = [];
2110
- for (const line of output.split("\n")) {
2111
- const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
2112
- if (!match) continue;
2113
- const pid = Number(match[1] ?? "");
2114
- const ppid = Number(match[2] ?? "");
2115
- const pgid = Number(match[3] ?? "");
2116
- const startedAt = (match[5] ?? "").trim().replace(/\s+/g, " ");
2117
- if (![
2118
- pid,
2119
- ppid,
2120
- pgid
2121
- ].every(Number.isSafeInteger) || pid <= 0 || ppid < 0 || pgid <= 0 || !startedAt) continue;
2122
- rows.push({
2123
- pid,
2124
- ppid,
2125
- pgid,
2126
- state: match[4] ?? "",
2127
- startedAt
2128
- });
2129
- }
2130
- return rows;
2131
- }
2132
- function collectDescendants(snapshot, rootPids) {
2133
- const childrenByParent = /* @__PURE__ */ new Map();
2134
- for (const row of snapshot) {
2135
- const children = childrenByParent.get(row.ppid) ?? [];
2136
- children.push(row);
2137
- childrenByParent.set(row.ppid, children);
2138
- }
2139
- const descendants = [];
2140
- const pending = [...new Set(rootPids)];
2141
- const seen = new Set(pending);
2142
- for (const parentPid of pending) for (const child of childrenByParent.get(parentPid) ?? []) {
2143
- if (seen.has(child.pid)) continue;
2144
- seen.add(child.pid);
2145
- descendants.push(child);
2146
- pending.push(child.pid);
2147
- }
2148
- return descendants;
2149
- }
2150
- function isStoppedState(state) {
2151
- return state.startsWith("T") || state.startsWith("t") || state.startsWith("Z");
2152
- }
2153
- function isQuiescedState(state) {
2154
- return isStoppedState(state) || isUninterruptibleState(state);
2155
- }
2156
- function isUninterruptibleState(state) {
2157
- return state.startsWith("D") || state.startsWith("U");
2158
- }
2159
- function isSameLiveProcess(current, expected) {
2160
- return current.pgid === expected.pgid && !current.state.startsWith("Z") && hasSameIdentity(current, expected);
2161
- }
2162
- function isSameLiveRoot(current, expected, requireStopped = false) {
2163
- return current.ppid === process.pid && (!requireStopped || isQuiescedState(current.state)) && isSameLiveProcess(current, expected);
2164
- }
2165
- async function signalSameRoot(root, signal, deadline) {
2166
- const current = await readProcess(root.pid, deadline);
2167
- return Boolean(current && isSameLiveRoot(current, root) && signalProcess(current.pid, signal));
2168
- }
2169
- function resumeTransportRoot(child, root, allowSynchronousPidFallback) {
2170
- try {
2171
- if (child.kill) {
2172
- child.kill("SIGCONT");
2173
- return;
2174
- }
2175
- } catch {
2176
- if (!allowSynchronousPidFallback) return;
2177
- }
2178
- if (allowSynchronousPidFallback) signalProcess(root.pid, "SIGCONT");
2179
- }
2180
- async function signalSameProcess(expected, signal, deadline) {
2181
- const current = await readProcess(expected.pid, deadline);
2182
- return Boolean(current && isSameLiveProcess(current, expected) && signalProcess(current.pid, signal));
2183
- }
2184
- function hasSameIdentity(left, right) {
2185
- return identityKey(left) === identityKey(right);
2186
- }
2187
- function identityKey(row) {
2188
- return `${row.pid}\0${row.startedAt}`;
2189
- }
2190
- function hasExited(child) {
2191
- return child.exitCode != null || child.signalCode != null;
2192
- }
2193
- function signalProcess(pid, signal) {
2194
- try {
2195
- process.kill(pid, signal);
2196
- return true;
2197
- } catch {
2198
- return false;
2199
- }
2200
- }
2201
- //#endregion
2202
- //#region extensions/codex/src/app-server/transport.ts
2203
- /**
2204
- * Shared transport lifecycle helpers for stdio and WebSocket Codex app-server
2205
- * connections.
2206
- */
2207
- const CODEX_APP_SERVER_TRANSPORT_CLOSES = /* @__PURE__ */ new WeakMap();
2208
- /** Starts graceful transport shutdown and schedules a force kill fallback. */
2209
- function closeCodexAppServerTransport(child, options = {}) {
2210
- beginCodexAppServerTransportClose(child, options);
2211
- }
2212
- function beginCodexAppServerTransportClose(child, options) {
2213
- const current = CODEX_APP_SERVER_TRANSPORT_CLOSES.get(child);
2214
- if (current) return current;
2215
- if (process.platform === "win32" || !child.pid || !child.kill || hasCodexAppServerTransportExited(child)) {
2216
- finishCodexAppServerTransportClose(child, options);
2217
- const completed = Promise.resolve();
2218
- CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, completed);
2219
- return completed;
2220
- }
2221
- const closing = (async () => {
2222
- let resumeRoot;
2223
- try {
2224
- resumeRoot = await terminateCodexAppServerDescendants(child);
2225
- } catch {
2226
- resumeRoot = void 0;
2227
- }
2228
- try {
2229
- finishCodexAppServerTransportClose(child, options, resumeRoot);
2230
- } catch {
2231
- signalCodexAppServerTransport(child, "SIGKILL");
2232
- }
2233
- })();
2234
- CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, closing);
2235
- return closing;
2236
- }
2237
- function finishCodexAppServerTransportClose(child, options, resumeRoot) {
2238
- const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
2239
- const forceKill = setTimeout(() => {
2240
- if (hasCodexAppServerTransportExited(child)) return;
2241
- signalCodexAppServerTransport(child, "SIGKILL");
2242
- }, Math.max(1, forceKillDelayMs));
2243
- forceKill.unref?.();
2244
- child.once("exit", () => {
2245
- clearTimeout(forceKill);
2246
- child.stdout.destroy?.();
2247
- child.stderr.destroy?.();
2248
- });
2249
- try {
2250
- child.stdin.end?.();
2251
- child.stdin.destroy?.();
2252
- } finally {
2253
- resumeRoot?.();
2254
- }
2255
- child.unref?.();
2256
- child.stdout.unref?.();
2257
- child.stderr.unref?.();
2258
- child.stdin.unref?.();
2259
- }
2260
- /** Closes a transport and waits briefly for an exit event. */
2261
- async function closeCodexAppServerTransportAndWait(child, options = {}) {
2262
- if (!hasCodexAppServerTransportExited(child)) await beginCodexAppServerTransportClose(child, options);
2263
- return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
2264
- }
2265
- function hasCodexAppServerTransportExited(child) {
2266
- return child.exitCode !== null && child.exitCode !== void 0 ? true : child.signalCode !== null && child.signalCode !== void 0;
2655
+ kill: () => {
2656
+ killed = true;
2657
+ clearConnectionHealthTimers();
2658
+ socket.close();
2659
+ },
2660
+ once: (event, listener) => events.once(event, listener)
2661
+ };
2267
2662
  }
2268
- async function waitForCodexAppServerTransportExit(child, timeoutMs) {
2269
- if (hasCodexAppServerTransportExited(child)) return true;
2270
- return await new Promise((resolve) => {
2271
- let settled = false;
2272
- const onExit = () => {
2273
- if (settled) return;
2274
- settled = true;
2275
- clearTimeout(timeout);
2276
- resolve(true);
2277
- };
2278
- const timeout = setTimeout(() => {
2279
- if (settled) return;
2280
- settled = true;
2281
- child.off?.("exit", onExit);
2282
- resolve(false);
2283
- }, Math.max(1, timeoutMs));
2284
- child.once("exit", onExit);
2285
- });
2663
+ /** Opens the owner-scoped Codex control socket used by the WebSocket upgrade. */
2664
+ function connectCodexAppServerUnixSocket(socketPath) {
2665
+ return net.createConnection(socketPath);
2286
2666
  }
2287
- function signalCodexAppServerTransport(child, signal) {
2288
- if (child.pid && process.platform !== "win32") try {
2289
- process.kill(-child.pid, signal);
2667
+ /** Resolves the canonical or explicitly configured Codex control socket. */
2668
+ function resolveCodexAppServerUnixSocketPath(options) {
2669
+ if (options.transport !== "unix") {
2670
+ if (options.url?.startsWith("unix://")) throw new Error("codex app-server unix URL requires unix transport");
2290
2671
  return;
2291
- } catch {}
2292
- child.kill?.(signal);
2672
+ }
2673
+ const url = options.url ?? "unix://";
2674
+ if (!url.startsWith("unix://")) throw new Error("codex app-server unix transport requires a unix:// URL");
2675
+ return url.slice(7) || path.join(resolveCodexAppServerUserHomeDir(options.env ?? process.env), "app-server-control", "app-server-control.sock");
2676
+ }
2677
+ function websocketFrameToText(data) {
2678
+ if (typeof data === "string") return data;
2679
+ if (Buffer.isBuffer(data)) return data.toString("utf8");
2680
+ if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
2681
+ return Buffer.from(data).toString("utf8");
2293
2682
  }
2294
2683
  //#endregion
2295
2684
  //#region extensions/codex/src/app-server/client.ts
@@ -2298,13 +2687,13 @@ function signalCodexAppServerTransport(child, signal) {
2298
2687
  * routing, notification fanout, server request handlers, and version checks.
2299
2688
  */
2300
2689
  const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
2301
- const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 8 * 1024 * 1024;
2690
+ const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 8388608;
2302
2691
  const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1e3;
2303
- const CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS = 66e4;
2304
2692
  const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
2305
2693
  const CODEX_APP_SERVER_OVERLOADED_ERROR_CODE = -32001;
2306
2694
  const CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES = 3;
2307
2695
  const CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS = 50;
2696
+ const CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX = 32;
2308
2697
  const CODEX_APP_SERVER_CLIENT_INSTANCE_IDS = /* @__PURE__ */ new WeakMap();
2309
2698
  const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
2310
2699
  /** Process-local generation fence for bindings tied to one app-server client instance. */
@@ -2318,6 +2707,10 @@ function getCodexAppServerClientInstanceId(client) {
2318
2707
  function resolveCodexAppServerClientInstanceId(client) {
2319
2708
  return client.getInstanceId?.call(client) ?? getCodexAppServerClientInstanceId(client);
2320
2709
  }
2710
+ /** Codex rejects this exact code before enqueueing, including mutating requests. */
2711
+ function isCodexAppServerOverloadError(error) {
2712
+ return error instanceof CodexAppServerRpcError && error.code === CODEX_APP_SERVER_OVERLOADED_ERROR_CODE;
2713
+ }
2321
2714
  var CodexAppServerLocalRequestCancellationError = class extends Error {
2322
2715
  constructor(method, reason, mayHaveWritten) {
2323
2716
  super(`${method} ${reason}`);
@@ -2373,9 +2766,11 @@ var CodexAppServerClient = class CodexAppServerClient {
2373
2766
  this.pending = /* @__PURE__ */ new Map();
2374
2767
  this.requestHandlers = /* @__PURE__ */ new Set();
2375
2768
  this.notificationHandlers = /* @__PURE__ */ new Set();
2769
+ this.pendingStartupWarnings = [];
2376
2770
  this.closeHandlers = /* @__PURE__ */ new Set();
2377
2771
  this.nextId = 1;
2378
2772
  this.initialized = false;
2773
+ this.modelCatalogRevision = 0;
2379
2774
  this.closed = false;
2380
2775
  this.transportExited = false;
2381
2776
  this.stderrTail = "";
@@ -2401,7 +2796,7 @@ var CodexAppServerClient = class CodexAppServerClient {
2401
2796
  child.stdin.on?.("error", (error) => this.closeWithError(toStringifiedError(error)));
2402
2797
  }
2403
2798
  /** Starts a new app-server client using resolved runtime start options. */
2404
- static start(options) {
2799
+ static async start(options, assertCurrent) {
2405
2800
  const defaults = resolveCodexAppServerRuntimeOptions().start;
2406
2801
  const startOptions = {
2407
2802
  ...defaults,
@@ -2410,7 +2805,18 @@ var CodexAppServerClient = class CodexAppServerClient {
2410
2805
  };
2411
2806
  if (startOptions.transport === "stdio" && startOptions.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
2412
2807
  if (startOptions.transport === "websocket" || startOptions.transport === "unix") return new CodexAppServerClient(createWebSocketTransport(startOptions));
2413
- return new CodexAppServerClient(createStdioTransport(startOptions));
2808
+ let client;
2809
+ try {
2810
+ await createStdioTransport(startOptions, process.env, assertCurrent, (child) => {
2811
+ client = new CodexAppServerClient(child);
2812
+ });
2813
+ return client;
2814
+ } catch (error) {
2815
+ assertCurrent?.();
2816
+ if (client?.transportExited && hasCodexAppServerNaturalExit(client.child)) throw buildCodexAppServerExitError(client.child.exitCode, client.child.signalCode, client.stderrTail);
2817
+ const stderr = client?.getStderrDiagnostic();
2818
+ throw stderr ? new Error(`${coerceErrorMessage(error)}; stderr=${JSON.stringify(stderr)}`, { cause: error }) : error;
2819
+ }
2414
2820
  }
2415
2821
  /** Builds a client around a fake transport for tests. */
2416
2822
  static fromTransportForTests(child) {
@@ -2429,7 +2835,8 @@ var CodexAppServerClient = class CodexAppServerClient {
2429
2835
  experimentalApi: true,
2430
2836
  extensions: {
2431
2837
  "openai/standard-form-input": {},
2432
- "openai/form": {}
2838
+ "openai/form": {},
2839
+ "io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app"] }
2433
2840
  }
2434
2841
  }
2435
2842
  });
@@ -2458,6 +2865,10 @@ var CodexAppServerClient = class CodexAppServerClient {
2458
2865
  getInstanceId() {
2459
2866
  return this.instanceId;
2460
2867
  }
2868
+ /** Account/config observations become stale before a mutation can enter the wire. */
2869
+ getModelCatalogRevision() {
2870
+ return this.modelCatalogRevision;
2871
+ }
2461
2872
  /** Installs the spawn-owner check run before config-loading thread requests. */
2462
2873
  setThreadSessionRequestGuard(guard) {
2463
2874
  this.threadSessionRequestGuard = guard;
@@ -2467,8 +2878,7 @@ var CodexAppServerClient = class CodexAppServerClient {
2467
2878
  return this.child.pid;
2468
2879
  }
2469
2880
  request(method, params, optionsInput) {
2470
- let options = optionsInput;
2471
- options ??= {};
2881
+ const options = optionsInput ?? {};
2472
2882
  if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
2473
2883
  if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
2474
2884
  const guard = method === "thread/start" || method === "thread/resume" || method === "thread/fork" ? this.threadSessionRequestGuard : void 0;
@@ -2503,11 +2913,11 @@ var CodexAppServerClient = class CodexAppServerClient {
2503
2913
  const elapsedMs = Date.now() - guardStartedAt;
2504
2914
  const remainingTimeoutMs = options.timeoutMs === void 0 ? void 0 : options.timeoutMs - elapsedMs;
2505
2915
  if (remainingTimeoutMs !== void 0 && remainingTimeoutMs <= 0) throw new CodexAppServerLocalRequestCancellationError(method, "timed out", false);
2506
- return await this.requestWithoutThreadSessionGuard(method, params, {
2916
+ return await this.requestWithOverloadRetry(method, params, {
2507
2917
  ...options,
2508
2918
  ...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
2509
- }, () => {
2510
- requestMayHaveWritten = true;
2919
+ }, (mayHaveWritten) => {
2920
+ requestMayHaveWritten = mayHaveWritten;
2511
2921
  });
2512
2922
  } catch (error) {
2513
2923
  if (requestMayHaveWritten && !(error instanceof CodexAppServerRpcError)) {
@@ -2520,12 +2930,9 @@ var CodexAppServerClient = class CodexAppServerClient {
2520
2930
  }
2521
2931
  })();
2522
2932
  }
2523
- return this.requestWithoutThreadSessionGuard(method, params, options);
2933
+ return this.requestWithOverloadRetry(method, params, options);
2524
2934
  }
2525
- requestWithoutThreadSessionGuard(method, params, options, onWriteAttempt) {
2526
- return this.requestWithOverloadRetry(method, params, options, onWriteAttempt);
2527
- }
2528
- async requestWithOverloadRetry(method, params, options, onWriteAttempt) {
2935
+ async requestWithOverloadRetry(method, params, options, onWriteStateChange) {
2529
2936
  const deadline = options.timeoutMs !== void 0 && Number.isFinite(options.timeoutMs) ? Date.now() + options.timeoutMs : void 0;
2530
2937
  for (let retry = 0;; retry += 1) {
2531
2938
  if (options.signal?.aborted) throw new CodexAppServerLocalRequestCancellationError(method, "aborted", false);
@@ -2535,9 +2942,10 @@ var CodexAppServerClient = class CodexAppServerClient {
2535
2942
  return await this.requestOnce(method, params, {
2536
2943
  ...options,
2537
2944
  ...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
2538
- }, onWriteAttempt);
2945
+ }, onWriteStateChange);
2539
2946
  } catch (error) {
2540
- if (!(error instanceof CodexAppServerRpcError) || error.code !== CODEX_APP_SERVER_OVERLOADED_ERROR_CODE || retry >= CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES) throw error;
2947
+ if (!isCodexAppServerOverloadError(error) || retry >= CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES) throw error;
2948
+ onWriteStateChange?.(false);
2541
2949
  const backoffMs = Math.round(CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS * 2 ** retry * (.75 + Math.random() * .5));
2542
2950
  await this.waitForOverloadRetry(method, backoffMs, deadline, options.signal);
2543
2951
  }
@@ -2566,10 +2974,11 @@ var CodexAppServerClient = class CodexAppServerClient {
2566
2974
  if (signal?.aborted) abortListener();
2567
2975
  });
2568
2976
  }
2569
- requestOnce(method, params, options, onWriteAttempt) {
2977
+ requestOnce(method, params, options, onWriteStateChange) {
2570
2978
  if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
2571
2979
  if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
2572
2980
  const id = this.nextId++;
2981
+ if (method === "account/login/start" || method === "account/logout" || method === "config/value/write" || method === "config/batchWrite") this.modelCatalogRevision += 1;
2573
2982
  const message = {
2574
2983
  id,
2575
2984
  method,
@@ -2619,8 +3028,9 @@ var CodexAppServerClient = class CodexAppServerClient {
2619
3028
  return;
2620
3029
  }
2621
3030
  try {
3031
+ options.assertCurrent?.();
2622
3032
  mayHaveWritten = true;
2623
- onWriteAttempt?.();
3033
+ onWriteStateChange?.(true);
2624
3034
  this.writeMessage(message, (error) => rejectPending(error));
2625
3035
  } catch (error) {
2626
3036
  rejectPending(toStringifiedError(error));
@@ -2642,6 +3052,7 @@ var CodexAppServerClient = class CodexAppServerClient {
2642
3052
  /** Registers a notification handler and returns its disposer. */
2643
3053
  addNotificationHandler(handler) {
2644
3054
  this.notificationHandlers.add(handler);
3055
+ for (const notification of this.pendingStartupWarnings.splice(0)) this.handleNotification(notification);
2645
3056
  return () => this.notificationHandlers.delete(handler);
2646
3057
  }
2647
3058
  /** Registers a close handler and returns its disposer. */
@@ -2649,6 +3060,16 @@ var CodexAppServerClient = class CodexAppServerClient {
2649
3060
  this.closeHandlers.add(handler);
2650
3061
  return () => this.closeHandlers.delete(handler);
2651
3062
  }
3063
+ /** Registers a handler for physical transport exit and returns its disposer. */
3064
+ addTransportExitHandler(handler) {
3065
+ if (this.transportExited) {
3066
+ handler(this);
3067
+ return () => void 0;
3068
+ }
3069
+ const onExit = () => handler(this);
3070
+ this.child.once("exit", onExit);
3071
+ return () => this.child.off?.("exit", onExit);
3072
+ }
2652
3073
  /** Closes the transport without waiting for process/socket shutdown. */
2653
3074
  close() {
2654
3075
  if (!this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"))) return;
@@ -2811,8 +3232,9 @@ var CodexAppServerClient = class CodexAppServerClient {
2811
3232
  }
2812
3233
  async runServerRequestHandlers(request) {
2813
3234
  const controller = new AbortController();
2814
- const timeoutResponse = timeoutServerRequestResponse(request);
2815
- if (!timeoutResponse) return await this.runServerRequestHandlersWithoutTimeout(request, controller.signal);
3235
+ if (request.method !== "item/tool/call") return await this.runServerRequestHandlersWithoutTimeout(request, controller.signal);
3236
+ const timeoutMs = resolveDynamicToolServerRequestTimeoutMs(readCodexDynamicToolCallParams(request.params));
3237
+ const timeoutResponse = timeoutServerRequestResponse(timeoutMs);
2816
3238
  let timeout;
2817
3239
  try {
2818
3240
  return await Promise.race([this.runServerRequestHandlersWithoutTimeout(request, controller.signal), new Promise((resolve) => {
@@ -2820,11 +3242,11 @@ var CodexAppServerClient = class CodexAppServerClient {
2820
3242
  embeddedAgentLog.warn("codex app-server server request timed out", {
2821
3243
  id: request.id,
2822
3244
  method: request.method,
2823
- timeoutMs: CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS
3245
+ timeoutMs
2824
3246
  });
2825
3247
  controller.abort(/* @__PURE__ */ new Error("codex app-server server request timed out"));
2826
3248
  resolve(timeoutResponse);
2827
- }, CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS);
3249
+ }, timeoutMs);
2828
3250
  timeout.unref?.();
2829
3251
  })]);
2830
3252
  } finally {
@@ -2839,6 +3261,12 @@ var CodexAppServerClient = class CodexAppServerClient {
2839
3261
  }
2840
3262
  }
2841
3263
  handleNotification(notification) {
3264
+ if (notification.method === "account/updated") this.modelCatalogRevision += 1;
3265
+ if (this.notificationHandlers.size === 0 && notification.method === "configWarning") {
3266
+ if (this.pendingStartupWarnings.length === CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX) this.pendingStartupWarnings.shift();
3267
+ this.pendingStartupWarnings.push(notification);
3268
+ return;
3269
+ }
2842
3270
  for (const handler of this.notificationHandlers) try {
2843
3271
  Promise.resolve(handler(notification)).catch((error) => {
2844
3272
  embeddedAgentLog.warn("codex app-server notification handler failed", { error });
@@ -2887,12 +3315,11 @@ function defaultServerRequestResponse(request) {
2887
3315
  function stringifyCodexAppServerMessage(message) {
2888
3316
  return JSON.stringify(message, (_key, value) => typeof value === "string" ? value.replace(UNPAIRED_SURROGATE_RE, "") : value) ?? "null";
2889
3317
  }
2890
- function timeoutServerRequestResponse(request) {
2891
- if (request.method !== "item/tool/call") return;
3318
+ function timeoutServerRequestResponse(timeoutMs) {
2892
3319
  return {
2893
3320
  contentItems: [{
2894
3321
  type: "inputText",
2895
- text: `OpenClaw dynamic tool call timed out after ${CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS}ms before sending a response to Codex.`
3322
+ text: `OpenClaw dynamic tool call timed out after ${timeoutMs}ms before sending a response to Codex.`
2896
3323
  }],
2897
3324
  success: false
2898
3325
  };
@@ -2910,8 +3337,8 @@ function assertSupportedCodexAppServerVersion(response) {
2910
3337
  const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
2911
3338
  if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
2912
3339
  const detected = parse(detectedVersion);
2913
- if (!detected || detected.compare("0.147.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
2914
- if (detected.compare("0.149.1") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
3340
+ if (!detected || detected.compare("0.149.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
3341
+ if (detected.compare("0.151.0") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
2915
3342
  detectedVersion,
2916
3343
  validatedVersion: CODEX_APP_SERVER_VERSION
2917
3344
  });
@@ -2992,11 +3419,13 @@ const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
2992
3419
  const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
2993
3420
  /** Short guard after apparent assistant completion. */
2994
3421
  const CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 1e4;
2995
- const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 5 * 6e4;
3422
+ const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 3e5;
2996
3423
  /** Guard after reasoning/commentary progress when no tool handoff occurred. */
2997
- const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 5 * 6e4;
3424
+ const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 3e5;
2998
3425
  /** Long terminal idle watch for app-server turns that never send completion. */
2999
- const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
3426
+ const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 18e5;
3427
+ const TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS = 12e4;
3428
+ const TURN_FINALIZE_DRAIN_ABORT_GRACE_MS = 5e3;
3000
3429
  var CodexAppServerStartupError = class extends Error {
3001
3430
  constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
3002
3431
  super(message);
@@ -3009,7 +3438,8 @@ function isCodexAppServerStartupError(error, reason) {
3009
3438
  return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_STARTUP_CANCELLED" && "reason" in error && (error.reason === "aborted" || error.reason === "timed_out") && (reason === void 0 || error.reason === reason);
3010
3439
  }
3011
3440
  function resolvePositiveIntegerTimeoutMs(value, fallbackMs) {
3012
- return resolveTimerTimeoutMs(value, resolveTimerTimeoutMs(fallbackMs, 1));
3441
+ const fallback = resolveTimerTimeoutMs(fallbackMs, 1);
3442
+ return resolveTimerTimeoutMs(value, fallback);
3013
3443
  }
3014
3444
  /** Runs startup work with abort and timeout handling plus optional cleanup. */
3015
3445
  async function withCodexStartupTimeout(params) {
@@ -3075,7 +3505,326 @@ function resolveCodexTurnTerminalIdleTimeoutMs(value, runTimeoutOverrideMs) {
3075
3505
  /** Adds gateway grace time to a caller timeout without overflowing invalid values. */
3076
3506
  function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
3077
3507
  const timeout = resolvePositiveIntegerTimeoutMs(timeoutMs, 1);
3078
- return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
3508
+ const grace = resolveTimerTimeoutMs(graceMs, 0, 0);
3509
+ return addTimerTimeoutGraceMs(timeout, grace) ?? timeout;
3510
+ }
3511
+ //#endregion
3512
+ //#region extensions/codex/src/app-server/desktop-generation-fingerprint.ts
3513
+ const MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES = 4096;
3514
+ /** Fingerprints every desktop candidate that can own a managed fallback artifact. */
3515
+ async function readMacOSDesktopGenerationFingerprint(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
3516
+ const entries = [];
3517
+ for (const candidate of candidates) {
3518
+ const command = await statFingerprint(candidate.appServerCommandPath);
3519
+ entries.push(`candidate:${candidate.appName}:${candidate.appServerCommandPath}:${command}`);
3520
+ for (const artifactPath of resolveMacOSDesktopGenerationPaths(candidate)) entries.push(`${artifactPath}\0${await statFingerprint(artifactPath)}`);
3521
+ const pluginRoot = resolveComputerUsePluginRoot(candidate);
3522
+ entries.push(`${pluginRoot}\0${await directoryTreeFingerprint(pluginRoot)}`);
3523
+ }
3524
+ return createHash("sha256").update(entries.join("\0")).digest("hex");
3525
+ }
3526
+ function resolveMacOSDesktopGenerationPaths(candidate) {
3527
+ return [
3528
+ candidate.appBundlePath,
3529
+ path.join(candidate.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"),
3530
+ ...candidate.computerUseServiceAppPaths.flatMap((servicePath) => [
3531
+ servicePath,
3532
+ path.join(servicePath, "Contents", "Info.plist"),
3533
+ path.join(servicePath, "Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient")
3534
+ ])
3535
+ ];
3536
+ }
3537
+ function resolveComputerUsePluginRoot(candidate) {
3538
+ return path.join(candidate.bundledMarketplacePath, "plugins", "computer-use");
3539
+ }
3540
+ /** Stable roots that cover bundle replacement and recursive artifact updates. */
3541
+ function resolveMacOSDesktopGenerationWatchPaths(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
3542
+ const watched = /* @__PURE__ */ new Set(["/Applications"]);
3543
+ for (const candidate of candidates) watched.add(candidate.appBundlePath);
3544
+ return [...watched];
3545
+ }
3546
+ async function directoryTreeFingerprint(root) {
3547
+ let rootStat;
3548
+ try {
3549
+ rootStat = await fs.lstat(root, { bigint: true });
3550
+ } catch (error) {
3551
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
3552
+ throw error;
3553
+ }
3554
+ if (!rootStat.isDirectory()) return statFingerprint(root);
3555
+ let entryCount = 0;
3556
+ const hash = createHash("sha256");
3557
+ hash.update("openclaw-codex-computer-use-plugin-tree-v1\0");
3558
+ const visit = async (directory, relativeDirectory, before) => {
3559
+ hash.update(`directory\0${relativeDirectory}\0${statTuple(before)}\0`);
3560
+ const entries = (await fs.readdir(directory, { withFileTypes: true })).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
3561
+ for (const entry of entries) {
3562
+ entryCount += 1;
3563
+ if (entryCount > MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES) throw new Error("Codex Computer Use plugin exceeds the bounded tree size");
3564
+ const entryPath = path.join(directory, entry.name);
3565
+ const relativePath = path.join(relativeDirectory, entry.name);
3566
+ const entryStat = await fs.lstat(entryPath, { bigint: true });
3567
+ if (entryStat.isDirectory()) await visit(entryPath, relativePath, entryStat);
3568
+ else hash.update(`entry\0${relativePath}\0${await statFingerprint(entryPath)}\0`);
3569
+ }
3570
+ if (!sameStat(before, await fs.lstat(directory, { bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${directory}`);
3571
+ };
3572
+ await visit(root, ".", rootStat);
3573
+ return hash.digest("hex");
3574
+ }
3575
+ async function statFingerprint(filePath) {
3576
+ try {
3577
+ const entry = await fs.lstat(filePath, { bigint: true });
3578
+ const type = entry.isSymbolicLink() ? "link" : entry.isDirectory() ? "directory" : entry.isFile() ? "file" : "other";
3579
+ const own = statTuple(entry);
3580
+ if (!entry.isSymbolicLink()) return `${type}:${own}:${entry.isFile() ? await readFileFingerprint(filePath, entry, false) : ""}`;
3581
+ const [link, realPath, target] = await Promise.all([
3582
+ fs.readlink(filePath),
3583
+ fs.realpath(filePath),
3584
+ fs.stat(filePath, { bigint: true })
3585
+ ]);
3586
+ const content = target.isFile() ? await readFileFingerprint(filePath, target, true) : "";
3587
+ return `${type}:${own}:${link}:${realPath}:${statTuple(target)}:${content}`;
3588
+ } catch (error) {
3589
+ if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
3590
+ throw error;
3591
+ }
3592
+ }
3593
+ async function readFileFingerprint(filePath, expected, followsSymlink) {
3594
+ const noFollow = followsSymlink ? 0 : constants.O_NOFOLLOW ?? 0;
3595
+ const handle = await fs.open(filePath, constants.O_RDONLY | noFollow);
3596
+ try {
3597
+ const before = await handle.stat({ bigint: true });
3598
+ if (!sameStat(before, expected)) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
3599
+ const hash = createHash("sha256");
3600
+ for await (const chunk of handle.createReadStream({ autoClose: false })) hash.update(chunk);
3601
+ if (!sameStat(before, await handle.stat({ bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
3602
+ return hash.digest("hex");
3603
+ } finally {
3604
+ await handle.close();
3605
+ }
3606
+ }
3607
+ function sameStat(left, right) {
3608
+ return statTuple(left) === statTuple(right);
3609
+ }
3610
+ function statTuple(stat) {
3611
+ return [
3612
+ stat.dev,
3613
+ stat.ino,
3614
+ stat.mode,
3615
+ stat.size,
3616
+ stat.mtimeNs,
3617
+ stat.ctimeNs
3618
+ ].join(":");
3619
+ }
3620
+ function isNodeError(error, code) {
3621
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
3622
+ }
3623
+ //#endregion
3624
+ //#region extensions/codex/src/app-server/desktop-generation-owner.ts
3625
+ const SETTLE_DELAY_MS = 1e3;
3626
+ /** Coalesces filesystem invalidations into one stable desktop generation. */
3627
+ function createCodexDesktopGenerationOwner(params) {
3628
+ let generation = params.initialGeneration;
3629
+ let invalidation = 0;
3630
+ let dirty = false;
3631
+ let refresh;
3632
+ let stopped = false;
3633
+ const markDirty = () => {
3634
+ invalidation += 1;
3635
+ dirty = true;
3636
+ };
3637
+ const reconcile = () => {
3638
+ if (refresh) return refresh;
3639
+ refresh = (async () => {
3640
+ for (;;) {
3641
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3642
+ const observedInvalidation = invalidation;
3643
+ const first = await params.readFingerprint();
3644
+ await delay(SETTLE_DELAY_MS);
3645
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3646
+ const second = await params.readFingerprint();
3647
+ if (stopped) throw new Error("Codex desktop generation owner stopped");
3648
+ if (observedInvalidation !== invalidation || first !== second) continue;
3649
+ const previous = generation;
3650
+ generation = previous?.fingerprint === second ? previous : {
3651
+ epoch: (previous?.epoch ?? 0) + 1,
3652
+ fingerprint: second
3653
+ };
3654
+ dirty = false;
3655
+ if (previous && generation !== previous) params.onGenerationChange?.(generation);
3656
+ return generation;
3657
+ }
3658
+ })().finally(() => {
3659
+ refresh = void 0;
3660
+ });
3661
+ return refresh;
3662
+ };
3663
+ return {
3664
+ read: () => generation,
3665
+ markDirty,
3666
+ wait: () => dirty ? reconcile() : Promise.resolve(generation),
3667
+ refresh: () => {
3668
+ markDirty();
3669
+ return reconcile();
3670
+ },
3671
+ isCurrent: (candidate) => Boolean(candidate && !dirty && generation && candidate.epoch === generation.epoch && candidate.fingerprint === generation.fingerprint),
3672
+ stop: () => {
3673
+ stopped = true;
3674
+ }
3675
+ };
3676
+ }
3677
+ function delay(ms) {
3678
+ return new Promise((resolve) => {
3679
+ setTimeout(resolve, ms);
3680
+ });
3681
+ }
3682
+ //#endregion
3683
+ //#region extensions/codex/src/app-server/desktop-generation.ts
3684
+ /** Lifecycle-owned generation for managed macOS Codex desktop artifacts. */
3685
+ const APPLICATIONS_PATH = "/Applications";
3686
+ const REARM_INITIAL_DELAY_MS = 100;
3687
+ const REARM_MAX_DELAY_MS = 3e4;
3688
+ const DESKTOP_GENERATION_STATE = Symbol.for("openclaw.codexDesktopGenerationState");
3689
+ function state() {
3690
+ const globalState = globalThis;
3691
+ return globalState[DESKTOP_GENERATION_STATE] ??= {};
3692
+ }
3693
+ function waitForCodexDesktopGeneration() {
3694
+ return state().owner?.wait() ?? Promise.resolve(void 0);
3695
+ }
3696
+ function isCodexDesktopGenerationCurrent(generation) {
3697
+ return state().owner?.isCurrent(generation) ?? false;
3698
+ }
3699
+ function createCodexDesktopGenerationService(params, runtime = {
3700
+ platform: process.platform,
3701
+ readFingerprint: readMacOSDesktopGenerationFingerprint,
3702
+ resolveWatchPaths: resolveMacOSDesktopGenerationWatchPaths,
3703
+ pathExists: existsSync,
3704
+ watchPath: (watchedPath, options, listener) => watch(watchedPath, options, listener)
3705
+ }) {
3706
+ return {
3707
+ id: "codex-desktop-generation",
3708
+ async start(ctx) {
3709
+ if (runtime.platform !== "darwin") return;
3710
+ const current = state();
3711
+ current.context = ctx;
3712
+ current.readFingerprint = runtime.readFingerprint;
3713
+ current.resolveWatchPaths = runtime.resolveWatchPaths;
3714
+ current.pathExists = runtime.pathExists;
3715
+ current.watchPath = runtime.watchPath;
3716
+ current.owner = createCodexDesktopGenerationOwner({
3717
+ readFingerprint: current.readFingerprint,
3718
+ onGenerationChange: params.onGenerationChange,
3719
+ initialGeneration: current.lastGeneration
3720
+ });
3721
+ armWatchers(current);
3722
+ refreshGeneration(current, current.owner, current.owner.refresh());
3723
+ },
3724
+ async stop() {
3725
+ const current = state();
3726
+ current.lastGeneration = current.owner?.read() ?? current.lastGeneration;
3727
+ current.owner?.stop();
3728
+ current.owner = void 0;
3729
+ current.armEpoch = (current.armEpoch ?? 0) + 1;
3730
+ current.context = void 0;
3731
+ current.readFingerprint = void 0;
3732
+ current.resolveWatchPaths = void 0;
3733
+ current.pathExists = void 0;
3734
+ current.watchPath = void 0;
3735
+ current.watchHealthy = void 0;
3736
+ current.rearmDelayMs = void 0;
3737
+ if (current.rearmTimer) {
3738
+ clearTimeout(current.rearmTimer);
3739
+ current.rearmTimer = void 0;
3740
+ }
3741
+ closeWatchers(current);
3742
+ }
3743
+ };
3744
+ }
3745
+ function armWatchers(current) {
3746
+ const owner = current.owner;
3747
+ if (!owner || current.watchers) return false;
3748
+ const armEpoch = (current.armEpoch ?? 0) + 1;
3749
+ current.armEpoch = armEpoch;
3750
+ const watchers = /* @__PURE__ */ new Set();
3751
+ current.watchers = watchers;
3752
+ const candidateNames = new Set(resolveMacOSDesktopCodexAppPathCandidates("darwin").map((candidate) => candidate.appName));
3753
+ let complete = true;
3754
+ for (const watchedPath of current.resolveWatchPaths?.() ?? []) {
3755
+ if (!current.pathExists?.(watchedPath)) continue;
3756
+ try {
3757
+ const watcher = current.watchPath?.(watchedPath, { recursive: watchedPath !== APPLICATIONS_PATH }, (_eventType, filename) => {
3758
+ if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
3759
+ if (watchedPath === APPLICATIONS_PATH && filename && !candidateNames.has(filename.toString().split(path.sep)[0] ?? "")) return;
3760
+ owner.markDirty();
3761
+ scheduleRearm(current, owner);
3762
+ });
3763
+ if (!watcher) {
3764
+ complete = false;
3765
+ reportWatcherFailure(current, owner, /* @__PURE__ */ new Error(`Could not watch ${watchedPath}`));
3766
+ scheduleRearm(current, owner);
3767
+ continue;
3768
+ }
3769
+ watchers.add(watcher);
3770
+ watcher.on("error", (error) => {
3771
+ if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
3772
+ reportWatcherFailure(current, owner, error);
3773
+ scheduleRearm(current, owner);
3774
+ });
3775
+ } catch (error) {
3776
+ complete = false;
3777
+ reportWatcherFailure(current, owner, error);
3778
+ scheduleRearm(current, owner);
3779
+ }
3780
+ }
3781
+ current.watchHealthy = complete;
3782
+ if (complete) current.rearmDelayMs = REARM_INITIAL_DELAY_MS;
3783
+ return complete;
3784
+ }
3785
+ function reportWatcherFailure(current, owner, error) {
3786
+ if (current.watchHealthy === false) return;
3787
+ current.watchHealthy = false;
3788
+ owner.markDirty();
3789
+ current.context?.serviceHealth?.reportFailure(error);
3790
+ current.context?.logger.warn(`codex desktop generation watcher failed: ${String(error)}`);
3791
+ }
3792
+ function isCurrentArm(current, owner, watchers, armEpoch) {
3793
+ return current.owner === owner && current.watchers === watchers && current.armEpoch === armEpoch;
3794
+ }
3795
+ function scheduleRearm(current, owner) {
3796
+ if (current.rearmTimer) {
3797
+ if (current.watchHealthy === false) return;
3798
+ clearTimeout(current.rearmTimer);
3799
+ }
3800
+ const delayMs = current.watchHealthy === false ? current.rearmDelayMs ?? REARM_INITIAL_DELAY_MS : REARM_INITIAL_DELAY_MS;
3801
+ if (current.watchHealthy === false) current.rearmDelayMs = Math.min(delayMs * 2, REARM_MAX_DELAY_MS);
3802
+ current.rearmTimer = setTimeout(() => {
3803
+ current.rearmTimer = void 0;
3804
+ if (current.owner !== owner) return;
3805
+ const wasUnhealthy = current.watchHealthy === false;
3806
+ closeWatchers(current);
3807
+ if (!armWatchers(current) || wasUnhealthy) owner.markDirty();
3808
+ refreshGeneration(current, owner, owner.wait());
3809
+ }, delayMs);
3810
+ current.rearmTimer.unref();
3811
+ }
3812
+ function logRefreshFailure(current, owner) {
3813
+ return (error) => {
3814
+ if (current.owner !== owner) return;
3815
+ current.context?.serviceHealth?.reportFailure(error);
3816
+ current.context?.logger.warn(`codex desktop generation refresh failed: ${String(error)}`);
3817
+ };
3818
+ }
3819
+ function refreshGeneration(current, owner, refresh) {
3820
+ refresh.then(() => {
3821
+ if (current.owner === owner && current.watchHealthy) current.context?.serviceHealth?.clearFailure();
3822
+ }).catch(logRefreshFailure(current, owner));
3823
+ }
3824
+ function closeWatchers(current) {
3825
+ const watchers = current.watchers;
3826
+ current.watchers = void 0;
3827
+ for (const watcher of watchers ?? []) watcher.close();
3079
3828
  }
3080
3829
  //#endregion
3081
3830
  //#region extensions/codex/src/app-server/native-config-fence.ts
@@ -3140,37 +3889,31 @@ async function waitForPreviousFence(previous, options) {
3140
3889
  });
3141
3890
  }
3142
3891
  //#endregion
3143
- //#region extensions/codex/src/app-server/timeout.ts
3144
- /**
3145
- * Thin Codex app-server timeout adapter around OpenClaw's shared security
3146
- * runtime timeout helper.
3147
- */
3148
- function resolveAbortError(signal) {
3149
- return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
3150
- }
3151
- /** Awaits a promise with a Codex-specific timeout error message. */
3152
- async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
3153
- return await withTimeout(promise, timeoutMs, {
3154
- message: timeoutMessage,
3155
- ...createError ? { createError } : {}
3156
- });
3157
- }
3158
- /** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
3159
- async function withAbortableTimeout(params) {
3160
- const signal = params.signal;
3161
- if (signal?.aborted) throw resolveAbortError(signal);
3162
- let removeAbortListener;
3163
- const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
3164
- const onAbort = () => reject(resolveAbortError(signal));
3165
- signal.addEventListener("abort", onAbort, { once: true });
3166
- removeAbortListener = () => signal.removeEventListener("abort", onAbort);
3167
- })]) : params.promise;
3168
- try {
3169
- return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
3170
- } finally {
3171
- removeAbortListener?.();
3892
+ //#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
3893
+ var CodexThreadStartRequestError = class extends Error {
3894
+ constructor(cause) {
3895
+ super(`thread/start: ${formatErrorMessage(cause)}`, { cause });
3896
+ this.name = "CodexThreadStartRequestError";
3172
3897
  }
3173
- }
3898
+ };
3899
+ var CodexThreadBindingConflictError = class extends Error {
3900
+ constructor(threadId, operation) {
3901
+ super(`Codex thread binding changed while ${operation}: ${threadId}`);
3902
+ this.name = "CodexThreadBindingConflictError";
3903
+ }
3904
+ };
3905
+ var CodexRestrictedToolSurfaceAttestationError = class extends Error {
3906
+ constructor(cause) {
3907
+ super("Codex restricted-tool-surface MCP attestation failed", { cause });
3908
+ this.name = "CodexRestrictedToolSurfaceAttestationError";
3909
+ }
3910
+ };
3911
+ var CodexAdoptedThreadActiveError = class extends Error {
3912
+ constructor() {
3913
+ super("Codex session became active in another runner; wait for it to finish before continuing");
3914
+ this.name = "CodexAdoptedThreadActiveError";
3915
+ }
3916
+ };
3174
3917
  //#endregion
3175
3918
  //#region extensions/codex/src/app-server/shared-client.ts
3176
3919
  /**
@@ -3179,6 +3922,8 @@ async function withAbortableTimeout(params) {
3179
3922
  */
3180
3923
  var shared_client_exports = /* @__PURE__ */ __exportAll({
3181
3924
  assertCodexAppServerClientStartSelectionCurrent: () => assertCodexAppServerClientStartSelectionCurrent,
3925
+ captureExclusiveSharedCodexAppServerClient: () => captureExclusiveSharedCodexAppServerClient,
3926
+ captureSharedCodexAppServerCatalogLifetime: () => captureSharedCodexAppServerCatalogLifetime,
3182
3927
  clearSharedCodexAppServerClientAndWait: () => clearSharedCodexAppServerClientAndWait,
3183
3928
  clearSharedCodexAppServerClientIfCurrent: () => clearSharedCodexAppServerClientIfCurrent,
3184
3929
  clearSharedCodexAppServerClientIfCurrentAndUnclaimed: () => clearSharedCodexAppServerClientIfCurrentAndUnclaimed,
@@ -3187,6 +3932,9 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
3187
3932
  getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
3188
3933
  getSharedCodexAppServerClient: () => getSharedCodexAppServerClient,
3189
3934
  isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
3935
+ readCodexAppServerClientDesktopGeneration: () => readCodexAppServerClientDesktopGeneration,
3936
+ readCodexAppServerClientDesktopGenerationFingerprint: () => readCodexAppServerClientDesktopGenerationFingerprint,
3937
+ readCodexAppServerClientProcessIdentity: () => readCodexAppServerClientProcessIdentity,
3190
3938
  releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
3191
3939
  releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
3192
3940
  resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
@@ -3194,6 +3942,8 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
3194
3942
  retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
3195
3943
  retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
3196
3944
  retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
3945
+ retireSharedCodexAppServerClientsBeforeDesktopGeneration: () => retireSharedCodexAppServerClientsBeforeDesktopGeneration,
3946
+ waitForCodexAppServerClientDesktopGenerationDrain: () => waitForCodexAppServerClientDesktopGenerationDrain,
3197
3947
  withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
3198
3948
  });
3199
3949
  const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
@@ -3205,8 +3955,10 @@ function getSharedCodexAppServerClientState() {
3205
3955
  globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
3206
3956
  clients: /* @__PURE__ */ new Map(),
3207
3957
  liveClients: /* @__PURE__ */ new Set(),
3958
+ isolatedClients: /* @__PURE__ */ new Set(),
3208
3959
  entriesByClient: /* @__PURE__ */ new WeakMap(),
3209
- leasedReleases: /* @__PURE__ */ new WeakMap()
3960
+ leasedReleases: /* @__PURE__ */ new WeakMap(),
3961
+ desktopGenerationDrainChecks: /* @__PURE__ */ new Set()
3210
3962
  };
3211
3963
  return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
3212
3964
  }
@@ -3215,6 +3967,41 @@ function getCodexAppServerClientStartMetadata() {
3215
3967
  globalState[CODEX_APP_SERVER_CLIENT_START_METADATA] ??= /* @__PURE__ */ new WeakMap();
3216
3968
  return globalState[CODEX_APP_SERVER_CLIENT_START_METADATA];
3217
3969
  }
3970
+ /** Reads the exact successful spawn selection plus its initialized runtime identity. */
3971
+ function readCodexAppServerClientProcessIdentity(client) {
3972
+ const metadata = getCodexAppServerClientStartMetadata().get(client);
3973
+ if (!metadata) return;
3974
+ const runtimeIdentity = client.getRuntimeIdentity();
3975
+ return {
3976
+ clientId: client.getInstanceId(),
3977
+ ...resolveCodexAppServerSpawnIdentity(metadata.startOptions, metadata.nativeCommand),
3978
+ ...runtimeIdentity?.serverVersion ? { serverVersion: runtimeIdentity.serverVersion } : {},
3979
+ ...runtimeIdentity?.userAgent ? { userAgent: runtimeIdentity.userAgent } : {}
3980
+ };
3981
+ }
3982
+ /** Returns the lifecycle fingerprint that owns a managed desktop client. */
3983
+ function readCodexAppServerClientDesktopGenerationFingerprint(client) {
3984
+ return readCodexAppServerClientDesktopGeneration(client)?.fingerprint;
3985
+ }
3986
+ /** Returns the lifecycle generation that owns a managed desktop client. */
3987
+ function readCodexAppServerClientDesktopGeneration(client) {
3988
+ return getCodexAppServerClientStartMetadata().get(client)?.desktopGeneration;
3989
+ }
3990
+ /** Waits until older physical desktop clients for this client's Codex home exit. */
3991
+ async function waitForCodexAppServerClientDesktopGenerationDrain(params) {
3992
+ const metadata = getCodexAppServerClientStartMetadata().get(params.client);
3993
+ if (!metadata?.desktopGeneration) return;
3994
+ const drain = createOlderDesktopGenerationDrainWait({
3995
+ generation: metadata.desktopGeneration,
3996
+ startOptions: metadata.startOptions,
3997
+ agentDir: metadata.agentDir
3998
+ });
3999
+ try {
4000
+ await withCodexAppServerAcquireDeadline(params.timeoutMs ?? 0, drain.promise, params.signal, "Codex Computer Use install timed out waiting for older desktop clients");
4001
+ } finally {
4002
+ drain.cancel();
4003
+ }
4004
+ }
3218
4005
  /** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
3219
4006
  function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
3220
4007
  const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
@@ -3245,6 +4032,7 @@ function isCodexAppServerStartSelectionChangedError(error) {
3245
4032
  function assertCodexAppServerClientStartSelectionCurrent(params) {
3246
4033
  const metadata = getCodexAppServerClientStartMetadata().get(params.client);
3247
4034
  if (!metadata) return;
4035
+ if (metadata.desktopGeneration && !isCodexDesktopGenerationCurrent(metadata.desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
3248
4036
  const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
3249
4037
  if (requestedStartOptions.commandSource !== "managed") return;
3250
4038
  const current = resolveCodexAppServerStartOptionsForAgent({
@@ -3256,7 +4044,7 @@ function assertCodexAppServerClientStartSelectionCurrent(params) {
3256
4044
  /** Resolves the per-CODEX_HOME key used to serialize native config loading. */
3257
4045
  function resolveCodexNativeConfigFenceKey(params) {
3258
4046
  const metadata = params.client ? getCodexAppServerClientStartMetadata().get(params.client) : void 0;
3259
- const startOptions = params.startOptions ?? metadata?.startOptions;
4047
+ const startOptions = metadata?.startOptions ?? params.startOptions;
3260
4048
  if (!startOptions || startOptions.transport !== "stdio") return;
3261
4049
  const configuredHome = startOptions.env?.CODEX_HOME?.trim();
3262
4050
  const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
@@ -3270,10 +4058,11 @@ function inferAuthRequirement(preparedAuth) {
3270
4058
  async function resolveCodexAppServerClientStartContext(options) {
3271
4059
  const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
3272
4060
  const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
4061
+ const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions, options?.pluginConfig) ? await waitForCodexDesktopGeneration() : void 0;
3273
4062
  const preparedAuth = options?.preparedAuth;
3274
4063
  const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
3275
4064
  if (preparedAuth && options?.authProfileId !== void 0) throw new Error("Prepared Codex auth cannot also select a legacy auth profile.");
3276
- if (preparedAuth?.kind === "profile" && !preparedAuth.store.profiles[preparedAuth.profileId]) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" was not found.`);
4065
+ 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.`);
3277
4066
  if (preparedAuth?.kind === "api-key" && !preparedApiKey) throw new Error("Prepared Codex API-key auth is missing its resolved key.");
3278
4067
  if (preparedAuth && requestedStartOptions.homeScope === "user") throw new Error("Prepared Codex auth requires an isolated app-server home.");
3279
4068
  const preparedAuthRequirement = inferAuthRequirement(preparedAuth);
@@ -3281,11 +4070,11 @@ async function resolveCodexAppServerClientStartContext(options) {
3281
4070
  const authRequirement = options?.authRequirement ?? preparedAuthRequirement;
3282
4071
  const usesNativeAuth = !preparedAuth && (options?.authProfileId === null || requestedStartOptions.homeScope === "user");
3283
4072
  const requestedAuthProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : options?.authProfileId ?? void 0;
3284
- const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && options?.authProfileStore ? resolveCodexAppServerAuthProfileStore({
4073
+ const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && preparedAuth?.kind !== "api-key" ? resolveCodexAppServerAuthProfileStore({
3285
4074
  agentDir,
3286
4075
  authProfileId: requestedAuthProfileId,
3287
- authProfileStore: options.authProfileStore,
3288
- config: options.config
4076
+ authProfileStore: options?.authProfileStore,
4077
+ config: options?.config
3289
4078
  }) : options?.authProfileStore;
3290
4079
  const authProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : usesNativeAuth || preparedAuth?.kind === "api-key" ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
3291
4080
  authProfileId: requestedAuthProfileId,
@@ -3293,20 +4082,26 @@ async function resolveCodexAppServerClientStartContext(options) {
3293
4082
  config: options?.config,
3294
4083
  ...authProfileStore ? { authProfileStore } : {}
3295
4084
  });
3296
- const preparedAuthProfileSnapshot = preparedAuth?.kind === "profile" ? preparedAuth.snapshot ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
4085
+ const preparedAuthProfileSnapshot = !usesNativeAuth && authProfileId ? (preparedAuth?.kind === "profile" ? preparedAuth.snapshot : void 0) ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
3297
4086
  authProfileId,
3298
4087
  authProfileStore,
3299
4088
  agentDir,
3300
4089
  config: options?.config
3301
4090
  }) : void 0;
3302
- if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable.`);
4091
+ 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.`);
3303
4092
  const resolvedPreparedAuth = preparedAuth?.kind === "api-key" ? {
3304
4093
  kind: "api-key",
3305
4094
  apiKey: preparedApiKey
3306
- } : preparedAuth?.kind === "profile" ? {
3307
- ...preparedAuth,
4095
+ } : preparedAuthProfileSnapshot && authProfileId && authProfileStore ? {
4096
+ kind: "profile",
4097
+ profileId: authProfileId,
4098
+ store: authProfileStore,
3308
4099
  snapshot: preparedAuthProfileSnapshot
3309
4100
  } : void 0;
4101
+ const agentStartOptions = resolveCodexAppServerStartOptionsForAgent({
4102
+ startOptions: requestedStartOptions,
4103
+ agentDir
4104
+ });
3310
4105
  return {
3311
4106
  agentDir,
3312
4107
  usesNativeAuth,
@@ -3316,58 +4111,24 @@ async function resolveCodexAppServerClientStartContext(options) {
3316
4111
  preparedAuth: resolvedPreparedAuth,
3317
4112
  authRequirement,
3318
4113
  startOptions: await bridgeCodexAppServerStartOptions({
3319
- startOptions: await resolveManagedCodexAppServerStartOptions(resolveCodexAppServerStartOptionsForAgent({
3320
- startOptions: requestedStartOptions,
3321
- agentDir
3322
- })),
4114
+ startOptions: await resolveManagedCodexAppServerStartOptions(agentStartOptions),
3323
4115
  agentId: options?.agentId,
3324
4116
  agentDir,
3325
4117
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
3326
- ...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
4118
+ ...preparedAuth && resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
3327
4119
  authRequirement,
3328
4120
  config: options?.config,
3329
4121
  pluginConfig: options?.pluginConfig,
3330
4122
  ...authProfileStore ? { authProfileStore } : {}
3331
- })
3332
- };
3333
- }
3334
- function resolveWarmSharedCodexAppServerClientIdentity(options) {
3335
- 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;
3336
- const authProfileSelector = options.authProfileId === null ? ["native"] : options.authProfileId === void 0 ? ["implicit"] : ["profile", options.authProfileId];
3337
- return {
3338
- config: options.config,
3339
- startOptions: options.startOptions,
3340
- selectorKey: JSON.stringify([
3341
- options.agentDir ?? resolveDefaultAgentDir(options.config),
3342
- authProfileSelector,
3343
- options.authBindingFingerprint ?? null,
3344
- options.authRequirement ?? null
3345
- ])
4123
+ }),
4124
+ ...options?.pluginConfig !== void 0 ? { pluginConfig: options.pluginConfig } : {},
4125
+ ...desktopGeneration ? { desktopGeneration } : {}
3346
4126
  };
3347
4127
  }
3348
- function readWarmSharedCodexAppServerClient(state, identity) {
3349
- const aliases = state.warmClientsByConfig?.get(identity.config)?.get(identity.startOptions);
3350
- const warm = aliases?.get(identity.selectorKey);
3351
- if (!warm) return;
3352
- if (state.clients.get(warm.key) !== warm.entry || warm.entry.client !== warm.client || warm.entry.closeWhenIdle || warm.entry.closeError) {
3353
- aliases?.delete(identity.selectorKey);
3354
- return;
3355
- }
3356
- return warm;
3357
- }
3358
- function rememberWarmSharedCodexAppServerClient(state, identity, warm) {
3359
- state.warmClientsByConfig ??= /* @__PURE__ */ new WeakMap();
3360
- let byStartOptions = state.warmClientsByConfig.get(identity.config);
3361
- if (!byStartOptions) {
3362
- byStartOptions = /* @__PURE__ */ new WeakMap();
3363
- state.warmClientsByConfig.set(identity.config, byStartOptions);
3364
- }
3365
- let aliases = byStartOptions.get(identity.startOptions);
3366
- if (!aliases) {
3367
- aliases = /* @__PURE__ */ new Map();
3368
- byStartOptions.set(identity.startOptions, aliases);
3369
- }
3370
- aliases.set(identity.selectorKey, warm);
4128
+ function shouldTrackDesktopGeneration(startOptions, pluginConfig) {
4129
+ if (startOptions.transport !== "stdio") return false;
4130
+ if (resolveCodexComputerUseConfig({ pluginConfig }).enabled && (startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed")) return true;
4131
+ return startOptions.commandSource === "managed" && (startOptions.managedCommandOrder ?? "package-first") === "desktop-first";
3371
4132
  }
3372
4133
  /** Gets or starts a shared Codex app-server client without retaining a lease. */
3373
4134
  async function getSharedCodexAppServerClient(options) {
@@ -3399,7 +4160,7 @@ function releaseCodexAppServerClientLease(lease) {
3399
4160
  lease.client = void 0;
3400
4161
  return client ? releaseLeasedSharedCodexAppServerClient(client) : false;
3401
4162
  }
3402
- /** Retries one config-loading request after moving its lease to the current owner. */
4163
+ /** Retries one config-loading operation with a shared deadline and current client lease. */
3403
4164
  async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
3404
4165
  let client = params.lease.client;
3405
4166
  if (!client) throw new Error("Codex app-server selection retry requires an active client lease");
@@ -3415,43 +4176,49 @@ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
3415
4176
  ...signal ? { signal } : {}
3416
4177
  };
3417
4178
  };
3418
- for (let attempt = 0; attempt < 2; attempt += 1) try {
3419
- return await params.run(client, requestOptions());
3420
- } catch (error) {
3421
- if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
3422
- retireSharedCodexAppServerClientIfCurrent(client);
3423
- params.lease.client = void 0;
3424
- if (!releaseLeasedSharedCodexAppServerClient(client)) {
3425
- client.close();
3426
- throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
4179
+ for (let attempt = 0; attempt < 2; attempt += 1) {
4180
+ const attemptClient = client;
4181
+ let scopeActive = true;
4182
+ const assertCurrent = () => {
4183
+ if (!scopeActive || params.lease.client !== attemptClient) throw new CodexAppServerStartupError("aborted", "Codex app-server request scope is closed");
4184
+ };
4185
+ try {
4186
+ requestOptions();
4187
+ return await params.run(client, () => {
4188
+ assertCurrent();
4189
+ return {
4190
+ ...requestOptions(),
4191
+ assertCurrent
4192
+ };
4193
+ });
4194
+ } catch (error) {
4195
+ if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
4196
+ retireSharedCodexAppServerClientIfCurrent(client);
4197
+ params.lease.client = void 0;
4198
+ if (!releaseLeasedSharedCodexAppServerClient(client)) {
4199
+ client.close();
4200
+ throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
4201
+ }
4202
+ const replacementOptions = requestOptions();
4203
+ client = await getLeasedSharedCodexAppServerClient({
4204
+ ...params.options,
4205
+ timeoutMs: replacementOptions.timeoutMs,
4206
+ ...signal ? { abandonSignal: signal } : {}
4207
+ });
4208
+ params.lease.client = client;
4209
+ params.onClientChange?.(client);
4210
+ } finally {
4211
+ scopeActive = false;
3427
4212
  }
3428
- const replacementOptions = requestOptions();
3429
- client = await getLeasedSharedCodexAppServerClient({
3430
- ...params.options,
3431
- timeoutMs: replacementOptions.timeoutMs,
3432
- ...signal ? { abandonSignal: signal } : {}
3433
- });
3434
- params.lease.client = client;
3435
- params.onClientChange(client);
3436
4213
  }
3437
4214
  throw new Error("Codex app-server selection retry loop exited unexpectedly");
3438
4215
  }
3439
4216
  async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3440
4217
  if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
3441
- const state = getSharedCodexAppServerClientState();
3442
- const warmIdentity = resolveWarmSharedCodexAppServerClientIdentity(options);
3443
- const warmClient = warmIdentity ? readWarmSharedCodexAppServerClient(state, warmIdentity) : void 0;
3444
- if (warmClient) {
3445
- options?.onStartedClient?.(warmClient.client);
3446
- const release = leaseOptions?.leased ? retainSharedClientEntry(warmClient.entry) : void 0;
3447
- return release ? {
3448
- client: warmClient.client,
3449
- release
3450
- } : { client: warmClient.client };
3451
- }
3452
4218
  const acquireStartedAt = Date.now();
3453
4219
  const timeoutMs = options?.timeoutMs ?? 0;
3454
- const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
4220
+ const state = getSharedCodexAppServerClientState();
4221
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
3455
4222
  const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
3456
4223
  const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authRequirement === "api-key" && !authProfileId ? resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }) : void 0);
3457
4224
  const baseKey = `${codexAppServerStartOptionsKey(startOptions, {
@@ -3459,12 +4226,18 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3459
4226
  authBindingFingerprint: options?.authBindingFingerprint,
3460
4227
  agentDir: usesNativeAuth ? void 0 : agentDir,
3461
4228
  fallbackApiKeyCacheKey: authIdentityCacheKey
3462
- })}\0auth-requirement:${authRequirement ?? "native"}`;
4229
+ })}\0auth-requirement:${authRequirement ?? "native"}${desktopGeneration ? `\0desktop-generation:${desktopGeneration.epoch}` : ""}`;
3463
4230
  const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
3464
4231
  const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
3465
4232
  const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
3466
- const entry = getOrCreateSharedClientEntry(state, key);
3467
- if (runtimeArtifactMode) entry.runtimeArtifactStartupAbort ??= new AbortController();
4233
+ let entry = getOrCreateSharedClientEntry(state, key);
4234
+ const existingClient = entry.client;
4235
+ const existingGeneration = existingClient ? getCodexAppServerClientStartMetadata().get(existingClient)?.desktopGeneration : void 0;
4236
+ if (existingClient && existingGeneration && !isCodexDesktopGenerationCurrent(existingGeneration)) {
4237
+ retireSharedCodexAppServerClientIfCurrent(existingClient);
4238
+ entry = getOrCreateSharedClientEntry(state, key);
4239
+ }
4240
+ entry.startupAbort ??= new AbortController();
3468
4241
  entry.closeWhenIdle = false;
3469
4242
  const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
3470
4243
  const startedCallback = options?.onStartedClient;
@@ -3491,6 +4264,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3491
4264
  key,
3492
4265
  requestedStartOptions,
3493
4266
  startOptions,
4267
+ desktopGeneration,
4268
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
3494
4269
  agentDir,
3495
4270
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
3496
4271
  authProfileStore,
@@ -3498,7 +4273,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3498
4273
  authRequirement,
3499
4274
  runtimeArtifactMode,
3500
4275
  ...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
3501
- runtimeArtifactSignal: entry.runtimeArtifactStartupAbort?.signal,
4276
+ runtimeArtifactSignal: entry.startupAbort.signal,
4277
+ abandonSignal: entry.startupAbort.signal,
3502
4278
  config: options?.config
3503
4279
  }));
3504
4280
  try {
@@ -3513,11 +4289,6 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
3513
4289
  config: options?.config
3514
4290
  });
3515
4291
  const release = leaseOptions?.leased ? retainSharedClientEntry(entry) : void 0;
3516
- if (warmIdentity) rememberWarmSharedCodexAppServerClient(state, warmIdentity, {
3517
- key,
3518
- entry,
3519
- client
3520
- });
3521
4292
  return release ? {
3522
4293
  client,
3523
4294
  release
@@ -3557,6 +4328,8 @@ function createSharedCodexAppServerClientStartup(params) {
3557
4328
  const ready = startInitializedCodexAppServerClient({
3558
4329
  requestedStartOptions: params.requestedStartOptions,
3559
4330
  startOptions: params.startOptions,
4331
+ ...params.desktopGeneration ? { desktopGeneration: params.desktopGeneration } : {},
4332
+ ...params.pluginConfig !== void 0 ? { pluginConfig: params.pluginConfig } : {},
3560
4333
  agentDir: params.agentDir,
3561
4334
  authProfileId: params.authProfileId,
3562
4335
  authProfileStore: params.authProfileStore,
@@ -3565,13 +4338,17 @@ function createSharedCodexAppServerClientStartup(params) {
3565
4338
  runtimeArtifactMode: params.runtimeArtifactMode,
3566
4339
  ...params.expectedRuntimeArtifact ? { expectedRuntimeArtifact: params.expectedRuntimeArtifact } : {},
3567
4340
  runtimeArtifactSignal: params.runtimeArtifactSignal,
4341
+ abandonSignal: params.abandonSignal,
3568
4342
  config: params.config,
3569
4343
  onStartedClient: (startedClient) => {
3570
4344
  const state = getSharedCodexAppServerClientState();
3571
4345
  params.entry.client = startedClient;
3572
4346
  state.liveClients.add(startedClient);
4347
+ startedClient.addTransportExitHandler((exitedClient) => {
4348
+ state.liveClients.delete(exitedClient);
4349
+ notifyDesktopGenerationDrainChecks(state);
4350
+ });
3573
4351
  startedClient.addCloseHandler((closedClient) => {
3574
- state.liveClients.delete(closedClient);
3575
4352
  if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
3576
4353
  });
3577
4354
  for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
@@ -3587,6 +4364,7 @@ function createSharedCodexAppServerClientStartup(params) {
3587
4364
  initialized.reject(error);
3588
4365
  throw error;
3589
4366
  });
4367
+ initialized.promise.catch(() => void 0);
3590
4368
  ready.catch(() => void 0);
3591
4369
  return {
3592
4370
  initialized: initialized.promise,
@@ -3598,10 +4376,12 @@ async function createIsolatedCodexAppServerClient(options) {
3598
4376
  if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
3599
4377
  const acquireStartedAt = Date.now();
3600
4378
  const timeoutMs = options?.timeoutMs ?? 0;
3601
- const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
4379
+ const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
3602
4380
  return await startInitializedCodexAppServerClient({
3603
4381
  requestedStartOptions,
3604
4382
  startOptions,
4383
+ ...desktopGeneration ? { desktopGeneration } : {},
4384
+ ...pluginConfig !== void 0 ? { pluginConfig } : {},
3605
4385
  agentDir,
3606
4386
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
3607
4387
  authProfileStore,
@@ -3613,7 +4393,18 @@ async function createIsolatedCodexAppServerClient(options) {
3613
4393
  config: options?.config,
3614
4394
  timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
3615
4395
  abandonSignal: options?.abandonSignal,
3616
- onStartedClient: options?.onStartedClient
4396
+ onStartedClient: (client) => {
4397
+ trackIsolatedCodexAppServerClient(client);
4398
+ options?.onStartedClient?.(client);
4399
+ }
4400
+ });
4401
+ }
4402
+ function trackIsolatedCodexAppServerClient(client) {
4403
+ const state = getSharedCodexAppServerClientState();
4404
+ state.isolatedClients.add(client);
4405
+ client.addTransportExitHandler((exitedClient) => {
4406
+ state.isolatedClients.delete(exitedClient);
4407
+ notifyDesktopGenerationDrainChecks(state);
3617
4408
  });
3618
4409
  }
3619
4410
  async function startInitializedCodexAppServerClient(params) {
@@ -3621,7 +4412,41 @@ async function startInitializedCodexAppServerClient(params) {
3621
4412
  const timeoutMs = params.timeoutMs ?? 0;
3622
4413
  const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
3623
4414
  for (const [index, startOptions] of startOptionsCandidates.entries()) {
3624
- const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BvyT1uyd.js") : void 0;
4415
+ const desktopGeneration = params.desktopGeneration ?? (isManagedCodexDesktopCommand(startOptions.command) ? await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), params.abandonSignal) : void 0);
4416
+ const assertDesktopGenerationCurrent = () => {
4417
+ if (params.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
4418
+ if (desktopGeneration && !isCodexDesktopGenerationCurrent(desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
4419
+ };
4420
+ const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
4421
+ const ownsIsolatedCodexHome = params.requestedStartOptions.homeScope !== "user" && !params.requestedStartOptions.env?.CODEX_HOME?.trim();
4422
+ const artifactDrain = desktopGeneration && ownsIsolatedCodexHome && computerUseConfig.enabled && (computerUseConfig.autoInstall || computerUseConfig.pluginCacheMode === "shared") ? createOlderDesktopGenerationDrainWait({
4423
+ generation: desktopGeneration,
4424
+ startOptions,
4425
+ agentDir: params.agentDir
4426
+ }) : void 0;
4427
+ try {
4428
+ if (artifactDrain) await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), artifactDrain.promise, params.abandonSignal);
4429
+ await reconcileCodexComputerUseStartArtifacts({
4430
+ startOptions,
4431
+ agentDir: params.agentDir,
4432
+ pluginConfig: params.pluginConfig,
4433
+ ...desktopGeneration ? { desktopGeneration } : {},
4434
+ assertCurrent: assertDesktopGenerationCurrent,
4435
+ ownsIsolatedCodexHome
4436
+ });
4437
+ } catch (error) {
4438
+ if (isCodexComputerUseCandidateArtifactsUnavailableError(error)) {
4439
+ if (index + 1 < startOptionsCandidates.length) continue;
4440
+ throw new AgentHarnessPreflightError("Codex Computer Use artifacts are unavailable from the installed desktop apps.", {
4441
+ cause: error,
4442
+ scope: "harness"
4443
+ });
4444
+ }
4445
+ throw error;
4446
+ } finally {
4447
+ artifactDrain?.cancel();
4448
+ }
4449
+ const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-D9pYp4zL.js") : void 0;
3625
4450
  const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
3626
4451
  const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
3627
4452
  startOptions,
@@ -3632,7 +4457,26 @@ async function startInitializedCodexAppServerClient(params) {
3632
4457
  if (index + 1 < startOptionsCandidates.length) continue;
3633
4458
  throw new Error("Codex app-server runtime artifact does not match verified inference");
3634
4459
  }
3635
- const client = CodexAppServerClient.start(startOptions);
4460
+ assertDesktopGenerationCurrent();
4461
+ let starting;
4462
+ let client;
4463
+ try {
4464
+ client = await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), starting = CodexAppServerClient.start(startOptions, () => {
4465
+ assertDesktopGenerationCurrent();
4466
+ resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
4467
+ }), params.abandonSignal);
4468
+ } catch (error) {
4469
+ starting?.then((lateClient) => lateClient.close(), () => {});
4470
+ throw error;
4471
+ }
4472
+ const nativeCommandAtStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
4473
+ getCodexAppServerClientStartMetadata().set(client, {
4474
+ requestedStartOptions: params.requestedStartOptions,
4475
+ startOptions,
4476
+ agentDir: params.agentDir,
4477
+ ...nativeCommandAtStart ? { nativeCommand: nativeCommandAtStart } : {},
4478
+ ...desktopGeneration ? { desktopGeneration } : {}
4479
+ });
3636
4480
  params.onStartedClient?.(client);
3637
4481
  let initialize;
3638
4482
  try {
@@ -3643,6 +4487,12 @@ async function startInitializedCodexAppServerClient(params) {
3643
4487
  if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
3644
4488
  throw error;
3645
4489
  }
4490
+ try {
4491
+ assertDesktopGenerationCurrent();
4492
+ } catch (error) {
4493
+ client.close();
4494
+ throw error;
4495
+ }
3646
4496
  params.onInitializedClient?.();
3647
4497
  let runtimeArtifact;
3648
4498
  try {
@@ -3666,9 +4516,11 @@ async function startInitializedCodexAppServerClient(params) {
3666
4516
  authProfileId: params.authProfileId ?? void 0,
3667
4517
  authMode: params.preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
3668
4518
  ...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {},
3669
- config: params.config
4519
+ config: params.config,
4520
+ onAuthRefreshFailure: () => retireSharedCodexAppServerClientIfCurrent(client)
3670
4521
  });
3671
4522
  try {
4523
+ assertDesktopGenerationCurrent();
3672
4524
  await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), applyCodexAppServerAuthProfile({
3673
4525
  client,
3674
4526
  agentDir: params.agentDir,
@@ -3679,14 +4531,8 @@ async function startInitializedCodexAppServerClient(params) {
3679
4531
  config: params.config,
3680
4532
  ...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
3681
4533
  }), params.abandonSignal);
3682
- const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
3683
4534
  if (runtimeArtifactModule && runtimeArtifact) runtimeArtifactModule.bindCodexAppServerRuntimeArtifact(client, runtimeArtifact);
3684
- getCodexAppServerClientStartMetadata().set(client, {
3685
- requestedStartOptions: params.requestedStartOptions,
3686
- startOptions,
3687
- agentDir: params.agentDir,
3688
- ...nativeCommand ? { nativeCommand } : {}
3689
- });
4535
+ assertDesktopGenerationCurrent();
3690
4536
  const fenceKey = resolveCodexNativeConfigFenceKey({ client });
3691
4537
  if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
3692
4538
  const release = await acquireCodexNativeConfigFence(fenceKey, options);
@@ -3706,6 +4552,9 @@ async function startInitializedCodexAppServerClient(params) {
3706
4552
  }
3707
4553
  throw new Error("Managed Codex app-server fallback candidates were exhausted.");
3708
4554
  }
4555
+ function isCodexComputerUseCandidateArtifactsUnavailableError(error) {
4556
+ return error !== null && typeof error === "object" && "code" in error && error.code === "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
4557
+ }
3709
4558
  function resolveManagedFallbackStartOptions(startOptions) {
3710
4559
  const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
3711
4560
  const candidates = [];
@@ -3735,6 +4584,15 @@ function clearSharedCodexAppServerClientIfCurrent(client) {
3735
4584
  }
3736
4585
  return false;
3737
4586
  }
4587
+ /** Captures a revocable observation of the exact shared client and native account/config. */
4588
+ function captureSharedCodexAppServerCatalogLifetime(client) {
4589
+ const state = getSharedCodexAppServerClientState();
4590
+ const entry = state.entriesByClient.get(client);
4591
+ const key = [...state.clients].find(([, candidate]) => candidate === entry)?.[0];
4592
+ const generation = readCodexAppServerClientDesktopGeneration(client);
4593
+ const revision = client.getModelCatalogRevision();
4594
+ return () => key !== void 0 && state.clients.get(key) === entry && entry?.client === client && !entry.closeWhenIdle && !entry.closeError && !client.getCloseError() && client.getModelCatalogRevision() === revision && (!generation || isCodexDesktopGenerationCurrent(generation));
4595
+ }
3738
4596
  /** Retains the matching shared client and returns a release callback. */
3739
4597
  function retainSharedCodexAppServerClientIfCurrent(client) {
3740
4598
  if (!client) return;
@@ -3754,6 +4612,20 @@ function retainSharedCodexAppServerClientByInstanceId(clientId) {
3754
4612
  };
3755
4613
  }
3756
4614
  }
4615
+ /** Captures sole physical-client ownership across awaited configuration adoption. */
4616
+ function captureExclusiveSharedCodexAppServerClient(client) {
4617
+ const state = getSharedCodexAppServerClientState();
4618
+ for (const [key, entry] of state.clients) {
4619
+ if (entry.client !== client) continue;
4620
+ const generation = entry.leaseGeneration;
4621
+ const assertExclusive = () => {
4622
+ 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();
4623
+ };
4624
+ assertExclusive();
4625
+ return assertExclusive;
4626
+ }
4627
+ throw new CodexAdoptedThreadActiveError();
4628
+ }
3757
4629
  /**
3758
4630
  * Retires a matching shared client. Default is graceful: detach from the map
3759
4631
  * (future acquisitions get a fresh client) and close once leases drain.
@@ -3797,6 +4669,62 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
3797
4669
  };
3798
4670
  }
3799
4671
  }
4672
+ /** Gracefully retires exact clients attached to an older desktop generation. */
4673
+ function retireSharedCodexAppServerClientsBeforeDesktopGeneration(generation) {
4674
+ const state = getSharedCodexAppServerClientState();
4675
+ for (const entry of state.clients.values()) {
4676
+ const client = entry.client;
4677
+ const attached = client ? getCodexAppServerClientStartMetadata().get(client) : void 0;
4678
+ if (client && attached?.desktopGeneration && attached.desktopGeneration.epoch < generation.epoch) retireSharedCodexAppServerClientIfCurrent(client);
4679
+ }
4680
+ }
4681
+ function createOlderDesktopGenerationDrainWait(params) {
4682
+ const targetHome = resolveCodexNativeConfigFenceKey({
4683
+ startOptions: params.startOptions,
4684
+ agentDir: params.agentDir
4685
+ });
4686
+ if (!targetHome) return {
4687
+ promise: Promise.resolve(),
4688
+ cancel: () => void 0
4689
+ };
4690
+ const state = getSharedCodexAppServerClientState();
4691
+ let settled = false;
4692
+ let resolveWait;
4693
+ const promise = new Promise((resolve) => {
4694
+ resolveWait = resolve;
4695
+ });
4696
+ const cancel = () => {
4697
+ if (settled) return;
4698
+ settled = true;
4699
+ state.desktopGenerationDrainChecks.delete(check);
4700
+ resolveWait();
4701
+ };
4702
+ const check = () => {
4703
+ if (!hasLiveOlderDesktopGenerationClient({
4704
+ state,
4705
+ generation: params.generation,
4706
+ targetHome
4707
+ })) cancel();
4708
+ };
4709
+ state.desktopGenerationDrainChecks.add(check);
4710
+ check();
4711
+ return {
4712
+ promise,
4713
+ cancel
4714
+ };
4715
+ }
4716
+ function hasLiveOlderDesktopGenerationClient(params) {
4717
+ for (const client of params.state.liveClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
4718
+ for (const client of params.state.isolatedClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
4719
+ return false;
4720
+ }
4721
+ function isOlderDesktopGenerationClientForHome(client, generation, targetHome) {
4722
+ const metadata = getCodexAppServerClientStartMetadata().get(client);
4723
+ return Boolean(metadata?.desktopGeneration && metadata.desktopGeneration.epoch < generation.epoch && resolveCodexNativeConfigFenceKey({ client }) === targetHome);
4724
+ }
4725
+ function notifyDesktopGenerationDrainChecks(state) {
4726
+ for (const check of state.desktopGenerationDrainChecks) check();
4727
+ }
3800
4728
  /** Clears a matching shared client and waits for its process to exit. */
3801
4729
  async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
3802
4730
  if (!client) return false;
@@ -3813,7 +4741,6 @@ async function clearSharedCodexAppServerClientAndWait(options) {
3813
4741
  const state = getSharedCodexAppServerClientState();
3814
4742
  const clients = collectSharedClients(state);
3815
4743
  state.clients.clear();
3816
- state.warmClientsByConfig = /* @__PURE__ */ new WeakMap();
3817
4744
  await Promise.all(clients.map((client) => client.closeAndWait(options)));
3818
4745
  }
3819
4746
  globalThis[SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER] = clearSharedCodexAppServerClientAndWait;
@@ -3823,6 +4750,7 @@ function getOrCreateSharedClientEntry(state, key) {
3823
4750
  entry = {
3824
4751
  activeLeases: 0,
3825
4752
  pendingAcquires: 0,
4753
+ leaseGeneration: 0,
3826
4754
  closeWhenIdle: false,
3827
4755
  onStartedClientCallbacks: /* @__PURE__ */ new Set()
3828
4756
  };
@@ -3858,22 +4786,26 @@ function clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client) {
3858
4786
  }
3859
4787
  function retainPendingSharedClientAcquire(entry) {
3860
4788
  let released = false;
4789
+ entry.leaseGeneration += 1;
3861
4790
  entry.pendingAcquires += 1;
3862
4791
  return () => {
3863
4792
  if (released) return;
3864
4793
  released = true;
3865
4794
  entry.pendingAcquires = Math.max(0, entry.pendingAcquires - 1);
3866
4795
  closeRetiredSharedClientEntryIfIdle(entry);
4796
+ notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
3867
4797
  };
3868
4798
  }
3869
4799
  function retainSharedClientEntry(entry) {
3870
4800
  let released = false;
4801
+ entry.leaseGeneration += 1;
3871
4802
  entry.activeLeases += 1;
3872
4803
  return () => {
3873
4804
  if (released) return;
3874
4805
  released = true;
3875
4806
  entry.activeLeases = Math.max(0, entry.activeLeases - 1);
3876
4807
  closeRetiredSharedClientEntryIfIdle(entry);
4808
+ notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
3877
4809
  };
3878
4810
  }
3879
4811
  function closeRetiredSharedClientEntryIfIdle(entry) {
@@ -3901,7 +4833,7 @@ function closeSharedClientEntryIfUnclaimed(key, entry) {
3901
4833
  }
3902
4834
  function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
3903
4835
  if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
3904
- entry.runtimeArtifactStartupAbort?.abort(/* @__PURE__ */ new Error("Codex runtime artifact startup was abandoned"));
4836
+ entry.startupAbort?.abort(/* @__PURE__ */ new Error("Codex app-server startup was abandoned"));
3905
4837
  entry.closeWhenIdle = true;
3906
4838
  const state = getSharedCodexAppServerClientState();
3907
4839
  if (state.clients.get(key) === entry) state.clients.delete(key);
@@ -3912,4 +4844,4 @@ function collectSharedClients(state) {
3912
4844
  return [...state.liveClients];
3913
4845
  }
3914
4846
  //#endregion
3915
- export { rememberCodexRateLimitsRead as $, isCodexAppServerApprovalRequest as A, CodexAppServerRpcError as B, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as C, resolveCodexTurnTerminalIdleTimeoutMs as D, resolveCodexTurnCompletionIdleTimeoutMs as E, isCodexAppServerPrewriteRequestCancellationError as F, isCodexAppServerClientRuntimeLive as G, claimCodexAppServerLiveThread as H, isCodexAppServerRequestTimeoutError as I, releaseCodexAppServerLiveThread as J, isCodexAppServerLiveThreadClaimed as K, isUnsupportedCodexAppServerVersionError as L, isCodexAppServerConnectionClosedError as M, isCodexAppServerIndeterminateRequestCancellationError as N, withCodexStartupTimeout as O, isCodexAppServerIndeterminateTransportError as P, readRecentCodexRateLimits as Q, resolveCodexAppServerClientInstanceId as R, resolveCodexGatewayTimeoutWithGraceMs as S, resolveCodexTurnAssistantCompletionIdleTimeoutMs as T, consumeCodexAppServerLiveThread as U, createCodexElicitationResponse as V, ensureCodexAppServerClientRuntime as W, unsubscribeCodexAppServerLiveThread as X, retainCodexAppServerLiveThread as Y, readCodexRateLimitsRevision as Z, withTimeout$1 as _, getLeasedSharedCodexAppServerClient as a, resolveCodexAppServerPreparedAuthHandoff as at, CodexAppServerStartupError as b, releaseCodexAppServerClientLease as c, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as ct, retainSharedCodexAppServerClientByInstanceId as d, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as dt, resolveCodexAppServerAuthAccountCacheKey as et, retainSharedCodexAppServerClientIfCurrent as f, resolveCodexAppServerHomeDir as ft, withAbortableTimeout as g, withLeasedCodexAppServerClientStartSelectionRetry as h, createIsolatedCodexAppServerClient as i, resolveCodexAppServerPreparedApiKeyCacheKey as it, isCodexAppServerBrokenPipeError as j, getCodexAppServerClientInstanceId as k, releaseLeasedSharedCodexAppServerClient as l, flattenCodexDynamicToolFunctions as lt, shared_client_exports as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, resolveCodexAppServerAuthProfileIdForAgent as nt, getSharedCodexAppServerClient as o, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as ot, retireSharedCodexAppServerClientIfCurrent as p, resolveCodexAppServerLocalHomeDir as pt, protectCodexAppServerLiveThread as q, clearSharedCodexAppServerClientIfCurrentAndWait as r, resolveCodexAppServerFallbackApiKeyCacheKey as rt, isCodexAppServerStartSelectionChangedError as s, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as st, clearSharedCodexAppServerClientIfCurrent as t, resolveCodexAppServerAuthProfileId as tt, resolveCodexNativeConfigFenceKey as u, isJsonObject as ut, acquireCodexNativeConfigFence as v, resolveCodexStartupTimeoutMs as w, isCodexAppServerStartupError as x, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as y, closeCodexAppServerTransportAndWait as z };
4847
+ 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 };