@openclaw/codex 2026.8.1-beta.3 → 2026.9.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +3 -3
- package/dist/{attempt-client-cleanup-URjUlCHg.js → attempt-client-cleanup-bfsmcQ6m.js} +21 -2
- package/dist/{binding-connection-DAvZwp96.js → binding-connection-BMvPX73o.js} +2 -2
- package/dist/{bounded-turn-4BjrZEwW.js → bounded-turn-B7iBUcuP.js} +8 -8
- package/dist/{capabilities-CH3K57v4.js → capabilities-BRMh0aIq.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-D0nZ3qtd.js → command-formatters-DEsIufkt.js} +2 -2
- package/dist/{command-handlers-DC86lgT4.js → command-handlers-D_-1_qRp.js} +43 -32
- package/dist/{command-rpc-xUIIonsO.js → command-rpc-CCeCkf_v.js} +4 -4
- package/dist/{compact-COqgBRny.js → compact-BglNFZrQ.js} +8 -8
- package/dist/{config-CpNeo5AE.js → config-giW8X0QA.js} +82 -28
- package/dist/doctor-contract-api.js +9 -8
- package/dist/{dynamic-tools-fJZhl4CE.js → dynamic-tools-Dnlfa--w.js} +108 -820
- package/dist/{effective-mcp-catalog-4YKn51aG.js → effective-mcp-catalog-CTeGrnfO.js} +2 -2
- package/dist/{event-projector-assistant-message-BwxV4d6N.js → event-projector-assistant-message-C5-Ds8mA.js} +18 -1
- package/dist/{event-projector-usage-CLwNEJqW.js → event-projector-usage-Bk1I90Bg.js} +3 -3
- package/dist/{harness-DoKQcF8n.js → harness-Csz0F-DK.js} +11 -11
- package/dist/harness.js +1 -1
- package/dist/index.js +130 -172
- package/dist/{isolated-completion-DPMtlH9r.js → isolated-completion-Cv6FqygM.js} +4 -4
- package/dist/{managed-binary-By4FZC6i.js → managed-binary-DFYK4DWq.js} +43 -7
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{model-catalog-CmGoqVKB.js → model-catalog-i9rNBthX.js} +4 -4
- package/dist/{models-CZw1wClT.js → models-TS3HBU-q.js} +4 -3
- package/dist/{native-subagent-monitor-GGq3RnOo.js → native-subagent-monitor-4Mfq2HDs.js} +176 -19
- package/dist/{node-exec-server.runtime-Bsu1LdFZ.js → node-exec-server.runtime-BpWqJO1V.js} +2 -2
- package/dist/{plugin-app-cache-key-DMNQ8Zi8.js → plugin-app-cache-key-C1CAdfcP.js} +6 -3
- package/dist/{plugin-approval-roundtrip-C75Yiiwc.js → plugin-approval-roundtrip-bUm777tR.js} +1 -1
- package/dist/{plugin-marketplace-discovery-CSWDkMrP.js → plugin-marketplace-discovery-B9DvaAG-.js} +29 -38
- package/dist/{plugin-metadata-cache-BXH5NF-4.js → plugin-metadata-cache-CTCT3w5L.js} +162 -135
- package/dist/{protocol-validators-VAVyEnDp.js → protocol-validators-VeJxxJ0U.js} +25 -3
- package/dist/{rate-limits-BI1dMHDG.js → rate-limits-C5_D8Gmb.js} +1 -1
- package/dist/{request-DSK0W6YI.js → request-DXXGHko0.js} +2 -2
- package/dist/{run-attempt-BV3HzuCf.js → run-attempt-DYc0QCBF.js} +346 -267
- package/dist/{sandbox-guard-C1qEeZ5C.js → sandbox-guard-kuWTn9C5.js} +1 -1
- package/dist/{session-binding-BTrLf9Qm.js → session-binding-Bcynfd-_.js} +1 -1
- package/dist/{session-catalog-ezP_UcRM.js → session-catalog-DaLKVtpH.js} +17 -16
- package/dist/{session-cli-B1hKvfkI.js → session-cli-DMc4oS2w.js} +1 -1
- package/dist/session-permission-policy-BrNH1bjy.js +160 -0
- package/dist/{session-retirement-CE4iMiLh.js → session-retirement-CcNC3YSY.js} +3 -3
- package/dist/{session-upstream-marker-D_2npHlA.js → session-upstream-marker-BIkusb95.js} +1 -1
- package/dist/{settled-turn-finalizer-Dx1onIqg.js → settled-turn-finalizer-Dc3ApjU4.js} +4 -4
- package/dist/{shared-client-Dw-rzii0.js → shared-client-DKz23hHY.js} +1026 -248
- package/dist/{side-question-DbXkJZzg.js → side-question-DjA_jsNe.js} +65 -36
- package/dist/{thread-lifecycle-DKdrnTMX.js → thread-lifecycle-L7oEkTlq.js} +850 -35
- package/dist/{thread-ownership-i2NBwDUL.js → thread-ownership-DZeuKH1j.js} +2 -2
- package/dist/{thread-resume-CdJk2Ehf.js → thread-resume-BE9qNEW0.js} +3 -3
- package/dist/{transcript-mirror-BU0_LKAL.js → transcript-mirror-DsCMWBca.js} +91 -10
- package/dist/{upstream-session-fork-BDVJCLAD.js → upstream-session-fork-DlaHxSLC.js} +3 -3
- package/dist/{usage-9dKJCgcy.js → usage-ROIaW3wo.js} +3 -3
- package/dist/{web-search-provider.runtime-DCdx4DGu.js → web-search-provider.runtime-ClaNPV7N.js} +3 -3
- package/openclaw.plugin.json +18 -1
- package/package.json +8 -8
- package/dist/app-server-policy-bg5QPSVP.js +0 -25
|
@@ -1,8 +1,8 @@
|
|
|
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
|
|
3
|
-
import { o as
|
|
2
|
+
import { S as resolveCodexAppServerUserHomeDir, a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey } from "./config-giW8X0QA.js";
|
|
3
|
+
import { a as CODEX_APP_SERVER_VERSION, c as resolveMacOSDesktopCodexAppPathCandidates, l as resolveMacOSDesktopCodexBundledMarketplaceCandidates, n as resolveManagedCodexAppServerStartOptions, o as MIN_SUPPORTED_CODEX_APP_SERVER_VERSION, r as resolveManagedCodexNativeCommand, s as resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath, t as isManagedCodexDesktopCommand, u as resolveMacOSDesktopCodexComputerUseServiceAppCandidates } from "./managed-binary-DFYK4DWq.js";
|
|
4
|
+
import { o as isCodexAppServerNativeAuthProfile } from "./session-binding-Bcynfd-_.js";
|
|
4
5
|
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";
|
|
6
6
|
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
7
|
import fs from "node:fs/promises";
|
|
8
8
|
import path from "node:path";
|
|
@@ -10,15 +10,14 @@ import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
|
10
10
|
import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
11
11
|
import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
|
-
import fsSync, { constants, existsSync } from "node:fs";
|
|
13
|
+
import fsSync, { constants, existsSync, watch } from "node:fs";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { isDeepStrictEqual } from "node:util";
|
|
16
16
|
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";
|
|
17
|
+
import { hasUsableOAuthCredential, resolveOpenAICodexAuthIdentity } from "openclaw/plugin-sdk/provider-auth";
|
|
18
18
|
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
19
|
import { assertNoSymlinkParents, withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
20
|
+
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
22
21
|
import { execFile } from "node:child_process";
|
|
23
22
|
import { createInterface } from "node:readline";
|
|
24
23
|
import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
|
@@ -56,131 +55,6 @@ function withEphemeralCodexAuthStore(params) {
|
|
|
56
55
|
};
|
|
57
56
|
}
|
|
58
57
|
//#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
58
|
//#region extensions/codex/src/app-server/computer-use-service-path.ts
|
|
185
59
|
/** Filesystem ownership guards for isolated Computer Use service provisioning. */
|
|
186
60
|
async function assertOwnedServicePath(params) {
|
|
@@ -279,7 +153,7 @@ async function assertNotSymlink(filePath, label) {
|
|
|
279
153
|
try {
|
|
280
154
|
if ((await fs.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
|
|
281
155
|
} catch (error) {
|
|
282
|
-
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
156
|
+
if (hasNodeErrorCode$1(error, "ENOENT")) return;
|
|
283
157
|
throw error;
|
|
284
158
|
}
|
|
285
159
|
}
|
|
@@ -292,14 +166,14 @@ async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
|
|
|
292
166
|
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
293
167
|
current = path.join(current, segment);
|
|
294
168
|
const existing = await fs.lstat(current).catch((error) => {
|
|
295
|
-
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
169
|
+
if (hasNodeErrorCode$1(error, "ENOENT")) return;
|
|
296
170
|
throw error;
|
|
297
171
|
});
|
|
298
172
|
if (!existing) {
|
|
299
173
|
try {
|
|
300
174
|
await fs.mkdir(current, { mode: 448 });
|
|
301
175
|
} catch (error) {
|
|
302
|
-
if (!hasNodeErrorCode(error, "EEXIST")) throw error;
|
|
176
|
+
if (!hasNodeErrorCode$1(error, "EEXIST")) throw error;
|
|
303
177
|
}
|
|
304
178
|
const created = await fs.lstat(current);
|
|
305
179
|
if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
|
|
@@ -318,6 +192,259 @@ function assertPathAtOrInside(rootPath, candidatePath, label) {
|
|
|
318
192
|
const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
|
|
319
193
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
|
|
320
194
|
}
|
|
195
|
+
function hasNodeErrorCode$1(error, code) {
|
|
196
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region extensions/codex/src/app-server/computer-use-cache.ts
|
|
200
|
+
/** Shared Computer Use plugin cache reconciliation for isolated Codex homes. */
|
|
201
|
+
const DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH = resolveMacOSDesktopCodexBundledMarketplaceCandidates("darwin")[0] ?? "";
|
|
202
|
+
const DEFAULT_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
|
|
203
|
+
async function ensureCodexComputerUseSharedPluginCache(params) {
|
|
204
|
+
if (!params.config.enabled) return skippedCacheResult("disabled", "Computer Use cache sharing skipped because it is disabled.");
|
|
205
|
+
if (params.config.pluginCacheMode === "independent") return skippedCacheResult("independent", "Computer Use cache sharing skipped because pluginCacheMode is independent.");
|
|
206
|
+
if (params.config.marketplaceName || params.config.marketplacePath) return skippedCacheResult("explicit_marketplace", "Computer Use cache sharing skipped because an explicit marketplace is configured.");
|
|
207
|
+
const bundledMarketplacePath = resolveComputerUseBundledMarketplacePath(params);
|
|
208
|
+
const sourcePluginRoot = path.join(bundledMarketplacePath, "plugins", params.config.pluginName);
|
|
209
|
+
const version = await readBundledPluginVersion(sourcePluginRoot);
|
|
210
|
+
if (!version) return skippedCacheResult("source_missing", `Computer Use bundled plugin source was not found at ${sourcePluginRoot}.`);
|
|
211
|
+
const marketplaceName = params.config.marketplaceName ?? DEFAULT_BUNDLED_MARKETPLACE_NAME;
|
|
212
|
+
const cacheRoot = path.join(params.codexHome, "plugins", "cache", marketplaceName, params.config.pluginName);
|
|
213
|
+
const cachePath = path.join(cacheRoot, version);
|
|
214
|
+
return {
|
|
215
|
+
status: "shared",
|
|
216
|
+
changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, {
|
|
217
|
+
codexHome: params.codexHome,
|
|
218
|
+
ownershipRoot: params.ownershipRoot,
|
|
219
|
+
assertCurrent: params.assertCurrent,
|
|
220
|
+
forceRefresh: params.forceRefresh
|
|
221
|
+
}),
|
|
222
|
+
cachePath,
|
|
223
|
+
targetPath: sourcePluginRoot,
|
|
224
|
+
version,
|
|
225
|
+
removedStaleVersions: [],
|
|
226
|
+
warnings: [],
|
|
227
|
+
message: `Computer Use plugin cache ${cachePath} contains bundled plugin ${sourcePluginRoot}.`
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function resolveComputerUseBundledMarketplacePath(params) {
|
|
231
|
+
return params.bundledMarketplacePath ?? resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath({ candidates: params.bundledMarketplacePathCandidates }) ?? params.bundledMarketplacePathCandidates?.[0] ?? DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH;
|
|
232
|
+
}
|
|
233
|
+
async function readBundledPluginVersion(sourcePluginRoot) {
|
|
234
|
+
const pluginJsonPath = path.join(sourcePluginRoot, ".codex-plugin", "plugin.json");
|
|
235
|
+
let raw;
|
|
236
|
+
try {
|
|
237
|
+
raw = await fs.readFile(pluginJsonPath, "utf8");
|
|
238
|
+
} catch {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const parsed = JSON.parse(raw);
|
|
243
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
244
|
+
} catch {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, boundary) {
|
|
249
|
+
const cacheRoot = path.dirname(cachePath);
|
|
250
|
+
const ownedParent = boundary.ownershipRoot ? await prepareOwnedServiceParent({
|
|
251
|
+
ownershipRoot: boundary.ownershipRoot,
|
|
252
|
+
codexHome: boundary.codexHome,
|
|
253
|
+
targetParent: cacheRoot
|
|
254
|
+
}) : void 0;
|
|
255
|
+
if (!ownedParent) await fs.mkdir(cacheRoot, { recursive: true });
|
|
256
|
+
const physicalCachePath = ownedParent ? path.join(ownedParent.realPath, path.basename(cachePath)) : cachePath;
|
|
257
|
+
const stat = await fs.lstat(physicalCachePath).catch(() => void 0);
|
|
258
|
+
if (stat?.isDirectory() && !stat.isSymbolicLink()) {
|
|
259
|
+
if (await readBundledPluginVersion(physicalCachePath) === version && !boundary.forceRefresh) return false;
|
|
260
|
+
}
|
|
261
|
+
const cacheName = path.basename(cachePath);
|
|
262
|
+
const physicalCacheRoot = path.dirname(physicalCachePath);
|
|
263
|
+
const stagingRoot = await fs.mkdtemp(path.join(physicalCacheRoot, `.${cacheName}.staging-`));
|
|
264
|
+
const stagedPath = path.join(stagingRoot, cacheName);
|
|
265
|
+
const backupPath = path.join(physicalCacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
|
|
266
|
+
let backupCreated = false;
|
|
267
|
+
try {
|
|
268
|
+
await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
|
|
269
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
270
|
+
if (stat) {
|
|
271
|
+
boundary.assertCurrent?.();
|
|
272
|
+
await fs.rename(physicalCachePath, backupPath);
|
|
273
|
+
backupCreated = true;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
277
|
+
boundary.assertCurrent?.();
|
|
278
|
+
await fs.rename(stagedPath, physicalCachePath);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (backupCreated) try {
|
|
281
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
282
|
+
await fs.rename(backupPath, physicalCachePath);
|
|
283
|
+
backupCreated = false;
|
|
284
|
+
} catch (restoreError) {
|
|
285
|
+
throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
|
|
286
|
+
}
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
if (backupCreated) {
|
|
290
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
291
|
+
await fs.rm(backupPath, {
|
|
292
|
+
recursive: true,
|
|
293
|
+
force: true
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
} finally {
|
|
298
|
+
if (!ownedParent || await directoryIdentityIsStable(ownedParent)) await fs.rm(stagingRoot, {
|
|
299
|
+
recursive: true,
|
|
300
|
+
force: true
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function skippedCacheResult(status, message) {
|
|
305
|
+
return {
|
|
306
|
+
status,
|
|
307
|
+
changed: false,
|
|
308
|
+
message,
|
|
309
|
+
removedStaleVersions: [],
|
|
310
|
+
warnings: status === "source_missing" ? [message] : []
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region extensions/codex/src/app-server/computer-use-marketplace.ts
|
|
315
|
+
/** Managed local wrapper for Codex's reserved bundled marketplace. */
|
|
316
|
+
const MARKETPLACE_NAME = "openai-bundled";
|
|
317
|
+
const activeInstalls$1 = /* @__PURE__ */ new Map();
|
|
318
|
+
function resolveCodexManagedBundledMarketplacePath(codexHome) {
|
|
319
|
+
return path.join(codexHome, ".tmp", "bundled-marketplaces", MARKETPLACE_NAME);
|
|
320
|
+
}
|
|
321
|
+
async function ensureCodexManagedBundledMarketplace(params) {
|
|
322
|
+
const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
|
|
323
|
+
const source = await resolveCodexManagedBundledMarketplaceSource({
|
|
324
|
+
...params,
|
|
325
|
+
candidates
|
|
326
|
+
});
|
|
327
|
+
if (!source) return;
|
|
328
|
+
const parentPath = path.dirname(resolveCodexManagedBundledMarketplacePath(params.codexHome));
|
|
329
|
+
const targetPath = path.join(parentPath, MARKETPLACE_NAME);
|
|
330
|
+
const parent = await prepareOwnedServiceParent({
|
|
331
|
+
ownershipRoot: params.ownershipRoot,
|
|
332
|
+
codexHome: params.codexHome,
|
|
333
|
+
targetParent: parentPath
|
|
334
|
+
});
|
|
335
|
+
const physicalTargetPath = path.join(parent.realPath, MARKETPLACE_NAME);
|
|
336
|
+
await assertNotSymlink(physicalTargetPath, "managed bundled marketplace");
|
|
337
|
+
const active = activeInstalls$1.get(physicalTargetPath);
|
|
338
|
+
if (active) {
|
|
339
|
+
if (active.sourcePath === source.bundledMarketplacePath) return await active.promise;
|
|
340
|
+
await active.promise.catch(() => void 0);
|
|
341
|
+
return await ensureCodexManagedBundledMarketplace(params);
|
|
342
|
+
}
|
|
343
|
+
const install = reconcileManagedWrapper({
|
|
344
|
+
parent,
|
|
345
|
+
physicalTargetPath,
|
|
346
|
+
targetPath,
|
|
347
|
+
source,
|
|
348
|
+
ownershipCandidates: params.ownershipCandidates ?? candidates,
|
|
349
|
+
assertCurrent: params.assertCurrent
|
|
350
|
+
});
|
|
351
|
+
const activeEntry = {
|
|
352
|
+
sourcePath: source.bundledMarketplacePath,
|
|
353
|
+
promise: install
|
|
354
|
+
};
|
|
355
|
+
activeInstalls$1.set(physicalTargetPath, activeEntry);
|
|
356
|
+
const clearActive = () => {
|
|
357
|
+
if (activeInstalls$1.get(physicalTargetPath) === activeEntry) activeInstalls$1.delete(physicalTargetPath);
|
|
358
|
+
};
|
|
359
|
+
install.then(clearActive, clearActive);
|
|
360
|
+
return await install;
|
|
361
|
+
}
|
|
362
|
+
async function reconcileManagedWrapper(params) {
|
|
363
|
+
if (await wrapperMatches(params.physicalTargetPath, params.source.bundledMarketplacePath)) return params.targetPath;
|
|
364
|
+
return await publishManagedWrapper(params);
|
|
365
|
+
}
|
|
366
|
+
async function publishManagedWrapper(params) {
|
|
367
|
+
const { parent, physicalTargetPath, targetPath, source, ownershipCandidates, assertCurrent } = params;
|
|
368
|
+
const stagingPath = await fs.mkdtemp(path.join(parent.realPath, `.${MARKETPLACE_NAME}.staging-`));
|
|
369
|
+
const backupPath = path.join(parent.realPath, `.${MARKETPLACE_NAME}.backup-${process.pid}-${Date.now()}`);
|
|
370
|
+
let backupCreated = false;
|
|
371
|
+
try {
|
|
372
|
+
const manifestParent = path.join(stagingPath, ".agents", "plugins");
|
|
373
|
+
await fs.mkdir(manifestParent, {
|
|
374
|
+
recursive: true,
|
|
375
|
+
mode: 448
|
|
376
|
+
});
|
|
377
|
+
await Promise.all([fs.symlink(path.join(source.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"), path.join(manifestParent, "marketplace.json")), fs.symlink(path.join(source.bundledMarketplacePath, "plugins"), path.join(stagingPath, "plugins"))]);
|
|
378
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
379
|
+
const existing = await fs.lstat(physicalTargetPath).catch((error) => {
|
|
380
|
+
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
381
|
+
throw error;
|
|
382
|
+
});
|
|
383
|
+
if (existing && !existing.isDirectory()) throw new Error(`Managed bundled marketplace must be a real directory: ${targetPath}`);
|
|
384
|
+
if (existing && !await wrapperMatchesAnySource(physicalTargetPath, ownershipCandidates)) throw new Error(`Refusing to replace an unowned bundled marketplace directory: ${targetPath}`);
|
|
385
|
+
if (existing) {
|
|
386
|
+
assertCurrent?.();
|
|
387
|
+
await fs.rename(physicalTargetPath, backupPath);
|
|
388
|
+
backupCreated = true;
|
|
389
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
390
|
+
}
|
|
391
|
+
assertCurrent?.();
|
|
392
|
+
await fs.rename(stagingPath, physicalTargetPath);
|
|
393
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
394
|
+
if (backupCreated) {
|
|
395
|
+
await fs.rm(backupPath, { recursive: true });
|
|
396
|
+
backupCreated = false;
|
|
397
|
+
}
|
|
398
|
+
return targetPath;
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (backupCreated) try {
|
|
401
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
402
|
+
if (await fs.lstat(physicalTargetPath).catch(() => void 0)) {
|
|
403
|
+
if (!await wrapperMatches(physicalTargetPath, source.bundledMarketplacePath)) throw new Error("managed bundled marketplace replacement is no longer owned", { cause: error });
|
|
404
|
+
await fs.rm(physicalTargetPath, { recursive: true });
|
|
405
|
+
}
|
|
406
|
+
await fs.rename(backupPath, physicalTargetPath);
|
|
407
|
+
backupCreated = false;
|
|
408
|
+
} catch (restoreError) {
|
|
409
|
+
throw new Error(`Failed to restore the prior managed bundled marketplace: ${String(error)}`, { cause: restoreError });
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
} finally {
|
|
413
|
+
if (await directoryIdentityIsStable(parent)) await fs.rm(stagingPath, {
|
|
414
|
+
recursive: true,
|
|
415
|
+
force: true
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async function resolveCodexManagedBundledMarketplaceSource(params) {
|
|
420
|
+
const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
|
|
421
|
+
const command = params.appServerCommand && path.resolve(params.appServerCommand);
|
|
422
|
+
const ordered = command ? candidates.filter((candidate) => path.resolve(candidate.appServerCommandPath) === command) : candidates;
|
|
423
|
+
for (const candidate of ordered) if (await isExpectedMarketplace(candidate.bundledMarketplacePath)) return candidate;
|
|
424
|
+
}
|
|
425
|
+
async function isExpectedMarketplace(root) {
|
|
426
|
+
try {
|
|
427
|
+
const manifest = JSON.parse(await fs.readFile(path.join(root, ".agents", "plugins", "marketplace.json"), "utf8"));
|
|
428
|
+
await fs.access(path.join(root, "plugins", "computer-use", ".codex-plugin", "plugin.json"));
|
|
429
|
+
return isRecord(manifest) && manifest.name === MARKETPLACE_NAME && Array.isArray(manifest.plugins) && manifest.plugins.some((plugin) => isRecord(plugin) && plugin.name === "computer-use");
|
|
430
|
+
} catch {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function wrapperMatches(targetPath, sourcePath) {
|
|
435
|
+
try {
|
|
436
|
+
const target = await fs.lstat(targetPath);
|
|
437
|
+
if (!target.isDirectory() || target.isSymbolicLink()) return false;
|
|
438
|
+
const [manifest, plugins] = await Promise.all([fs.readlink(path.join(targetPath, ".agents", "plugins", "marketplace.json")), fs.readlink(path.join(targetPath, "plugins"))]);
|
|
439
|
+
return manifest === path.join(sourcePath, ".agents", "plugins", "marketplace.json") && plugins === path.join(sourcePath, "plugins");
|
|
440
|
+
} catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async function wrapperMatchesAnySource(targetPath, candidates) {
|
|
445
|
+
for (const candidate of candidates) if (await wrapperMatches(targetPath, candidate.bundledMarketplacePath)) return true;
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
321
448
|
function hasNodeErrorCode(error, code) {
|
|
322
449
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
323
450
|
}
|
|
@@ -332,9 +459,13 @@ const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComp
|
|
|
332
459
|
const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
|
|
333
460
|
const COPY_TIMEOUT_MS = 12e4;
|
|
334
461
|
const INSPECT_TIMEOUT_MS = 3e4;
|
|
335
|
-
const COMPLETED_SYNC_CACHE_MAX_ENTRIES = 64;
|
|
336
462
|
const activeInstalls = /* @__PURE__ */ new Map();
|
|
337
|
-
|
|
463
|
+
/** Finds the first signed native service from one ordered desktop owner set. */
|
|
464
|
+
async function resolveCodexComputerUseServiceAppSourcePath(params) {
|
|
465
|
+
const platform = params.platform ?? process.platform;
|
|
466
|
+
if (platform !== "darwin") return;
|
|
467
|
+
return (await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand), params.inspectServiceApp ?? inspectTrustedServiceApp))?.path;
|
|
468
|
+
}
|
|
338
469
|
/** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
|
|
339
470
|
async function ensureCodexComputerUseServiceApp(params) {
|
|
340
471
|
const platform = params.platform ?? process.platform;
|
|
@@ -355,25 +486,12 @@ async function ensureCodexComputerUseServiceApp(params) {
|
|
|
355
486
|
});
|
|
356
487
|
const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
|
|
357
488
|
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
489
|
const active = activeInstalls.get(targetPath);
|
|
371
490
|
if (active) {
|
|
372
491
|
if (active.syncKey === syncKey) return await active.promise;
|
|
373
492
|
await active.promise.catch(() => void 0);
|
|
374
493
|
return await ensureCodexComputerUseServiceApp(params);
|
|
375
494
|
}
|
|
376
|
-
if (completed) completedSyncs.delete(targetPath);
|
|
377
495
|
const install = ensureCodexComputerUseServiceAppOnce({
|
|
378
496
|
...params,
|
|
379
497
|
codexHome,
|
|
@@ -382,18 +500,6 @@ async function ensureCodexComputerUseServiceApp(params) {
|
|
|
382
500
|
targetPath,
|
|
383
501
|
platform,
|
|
384
502
|
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
503
|
});
|
|
398
504
|
const activeEntry = {
|
|
399
505
|
syncKey,
|
|
@@ -406,9 +512,6 @@ async function ensureCodexComputerUseServiceApp(params) {
|
|
|
406
512
|
install.then(clearActive, clearActive);
|
|
407
513
|
return await install;
|
|
408
514
|
}
|
|
409
|
-
function isCompletedSyncStatus(status) {
|
|
410
|
-
return status === "installed" || status === "refreshed" || status === "already_current";
|
|
411
|
-
}
|
|
412
515
|
async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
413
516
|
const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
|
|
414
517
|
const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
|
|
@@ -453,6 +556,7 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
|
453
556
|
await assertNotSymlink(operationTargetPath, "Computer Use service target");
|
|
454
557
|
if (await pathExists$1(operationTargetPath)) {
|
|
455
558
|
await assertOwnedServiceParentStable(ownedParent);
|
|
559
|
+
params.assertCurrent?.();
|
|
456
560
|
await fs.rename(operationTargetPath, backupPath);
|
|
457
561
|
await assertOwnedServiceParentStable(ownedParent);
|
|
458
562
|
backupCreated = true;
|
|
@@ -480,6 +584,7 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
|
480
584
|
}
|
|
481
585
|
try {
|
|
482
586
|
await assertOwnedServiceParentStable(ownedParent);
|
|
587
|
+
params.assertCurrent?.();
|
|
483
588
|
await fs.rename(stagedPath, operationTargetPath);
|
|
484
589
|
await assertOwnedServiceParentStable(ownedParent);
|
|
485
590
|
} catch (error) {
|
|
@@ -742,10 +847,12 @@ const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
|
|
|
742
847
|
const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
|
|
743
848
|
const CODEX_AUTH_JSON_FILENAME = "auth.json";
|
|
744
849
|
const CODEX_HOME_DIRNAME = ".codex";
|
|
850
|
+
const MAX_COMPUTER_USE_ARTIFACT_OWNERS = 128;
|
|
851
|
+
const activeComputerUseArtifactReconciliations = /* @__PURE__ */ new Map();
|
|
745
852
|
const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
|
|
746
853
|
async function bridgeCodexAppServerStartOptions(params) {
|
|
747
854
|
if (params.startOptions.transport !== "stdio") return params.startOptions;
|
|
748
|
-
const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir
|
|
855
|
+
const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir);
|
|
749
856
|
if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
|
|
750
857
|
if (params.authProfileId === null) return scopeStartOptions();
|
|
751
858
|
const store = resolveCodexAppServerAuthProfileStore({
|
|
@@ -845,7 +952,7 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
|
|
|
845
952
|
const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
|
|
846
953
|
const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
|
|
847
954
|
const secretFreeCacheKey = credential.type === "api_key" && loginParams.type === "apiKey" ? `${accountId}:${fingerprintApiKeyAuthProfileCacheKey(loginParams.apiKey)}` : loginParams.type === "chatgptAuthTokens" && (credential.type === "token" || !stableChatgptAccountId) ? `${accountId}:${fingerprintTokenAuthProfileCacheKey(loginParams.accessToken)}` : accountId;
|
|
848
|
-
const chatgptAccountId =
|
|
955
|
+
const chatgptAccountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : void 0;
|
|
849
956
|
return {
|
|
850
957
|
loginParams,
|
|
851
958
|
secretFreeCacheKey,
|
|
@@ -974,28 +1081,10 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
|
|
|
974
1081
|
hash.update(apiKey);
|
|
975
1082
|
return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
|
|
976
1083
|
}
|
|
977
|
-
async function withCodexHomeEnvironment(startOptions, agentDir
|
|
1084
|
+
async function withCodexHomeEnvironment(startOptions, agentDir) {
|
|
978
1085
|
const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
|
|
979
1086
|
const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
|
|
980
1087
|
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
1088
|
if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
|
|
1000
1089
|
const nextStartOptions = {
|
|
1001
1090
|
...startOptions,
|
|
@@ -1010,6 +1099,138 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
|
|
|
1010
1099
|
else delete nextStartOptions.clearEnv;
|
|
1011
1100
|
return nextStartOptions;
|
|
1012
1101
|
}
|
|
1102
|
+
/** Reconciles Computer Use artifacts for the exact managed command about to start. */
|
|
1103
|
+
async function reconcileCodexComputerUseStartArtifacts(params) {
|
|
1104
|
+
if (params.startOptions.transport !== "stdio") return;
|
|
1105
|
+
const codexHome = resolveCodexAppServerLocalHomeDir(params.startOptions, params.agentDir);
|
|
1106
|
+
const key = path.resolve(codexHome);
|
|
1107
|
+
let owner = activeComputerUseArtifactReconciliations.get(key);
|
|
1108
|
+
if (!owner) {
|
|
1109
|
+
owner = {
|
|
1110
|
+
active: 0,
|
|
1111
|
+
tail: Promise.resolve()
|
|
1112
|
+
};
|
|
1113
|
+
activeComputerUseArtifactReconciliations.set(key, owner);
|
|
1114
|
+
} else {
|
|
1115
|
+
activeComputerUseArtifactReconciliations.delete(key);
|
|
1116
|
+
activeComputerUseArtifactReconciliations.set(key, owner);
|
|
1117
|
+
}
|
|
1118
|
+
owner.active += 1;
|
|
1119
|
+
const epoch = params.desktopGeneration?.epoch;
|
|
1120
|
+
if (epoch !== void 0 && (owner.latestEpoch === void 0 || epoch > owner.latestEpoch)) owner.latestEpoch = epoch;
|
|
1121
|
+
const assertCurrent = () => {
|
|
1122
|
+
params.assertCurrent?.();
|
|
1123
|
+
if (epoch !== void 0 && owner.latestEpoch !== epoch) throw new Error("Codex Computer Use artifact reconciliation was superseded.");
|
|
1124
|
+
};
|
|
1125
|
+
const operation = owner.tail.catch(() => void 0).then(async () => {
|
|
1126
|
+
assertCurrent();
|
|
1127
|
+
const appliedCacheBinding = await reconcileCodexComputerUseStartArtifactsOnce({
|
|
1128
|
+
...params,
|
|
1129
|
+
codexHome,
|
|
1130
|
+
assertCurrent,
|
|
1131
|
+
previousCacheBinding: owner.appliedCacheBinding
|
|
1132
|
+
});
|
|
1133
|
+
assertCurrent();
|
|
1134
|
+
owner.appliedCacheBinding = appliedCacheBinding;
|
|
1135
|
+
});
|
|
1136
|
+
const settled = operation.then(() => void 0, () => void 0);
|
|
1137
|
+
owner.tail = settled;
|
|
1138
|
+
try {
|
|
1139
|
+
await operation;
|
|
1140
|
+
} finally {
|
|
1141
|
+
owner.active = Math.max(0, owner.active - 1);
|
|
1142
|
+
if (owner.active === 0 && owner.latestEpoch === void 0 && activeComputerUseArtifactReconciliations.get(key) === owner && owner.tail === settled) activeComputerUseArtifactReconciliations.delete(key);
|
|
1143
|
+
pruneComputerUseArtifactOwners();
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async function reconcileCodexComputerUseStartArtifactsOnce(params) {
|
|
1147
|
+
const codexHome = params.codexHome;
|
|
1148
|
+
const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
|
|
1149
|
+
const ownsIsolatedCodexHome = params.ownsIsolatedCodexHome ?? (params.startOptions.homeScope !== "user" && !params.startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim());
|
|
1150
|
+
const shouldProvisionComputerUse = computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome;
|
|
1151
|
+
if (shouldProvisionComputerUse) await ensureOwnedCodexHome(codexHome, params.agentDir);
|
|
1152
|
+
else await fs.mkdir(codexHome, { recursive: true });
|
|
1153
|
+
const desktopCandidates = resolveMacOSDesktopCodexAppPathCandidates();
|
|
1154
|
+
const exactDesktopCandidate = desktopCandidates.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(params.startOptions.command));
|
|
1155
|
+
const usesManagedBundledMarketplace = !computerUseConfig.marketplaceSource && !computerUseConfig.marketplacePath && !computerUseConfig.marketplaceName;
|
|
1156
|
+
const needsBundledMarketplace = usesManagedBundledMarketplace || computerUseConfig.pluginCacheMode === "shared" && !computerUseConfig.marketplaceName && !computerUseConfig.marketplacePath;
|
|
1157
|
+
const artifactCandidate = shouldProvisionComputerUse ? await resolveCompleteComputerUseArtifactCandidate({
|
|
1158
|
+
candidates: exactDesktopCandidate ? [exactDesktopCandidate] : desktopCandidates,
|
|
1159
|
+
needsBundledMarketplace
|
|
1160
|
+
}) : exactDesktopCandidate;
|
|
1161
|
+
params.assertCurrent();
|
|
1162
|
+
if (shouldProvisionComputerUse) {
|
|
1163
|
+
if (desktopCandidates.length > 0 && !artifactCandidate) throw new CodexComputerUseCandidateArtifactsUnavailableError();
|
|
1164
|
+
try {
|
|
1165
|
+
const marketplacePath = usesManagedBundledMarketplace ? await ensureCodexManagedBundledMarketplace({
|
|
1166
|
+
codexHome,
|
|
1167
|
+
ownershipRoot: params.agentDir,
|
|
1168
|
+
...artifactCandidate ? {
|
|
1169
|
+
appServerCommand: artifactCandidate.appServerCommandPath,
|
|
1170
|
+
candidates: [artifactCandidate],
|
|
1171
|
+
ownershipCandidates: desktopCandidates
|
|
1172
|
+
} : {},
|
|
1173
|
+
assertCurrent: params.assertCurrent
|
|
1174
|
+
}) : void 0;
|
|
1175
|
+
params.assertCurrent();
|
|
1176
|
+
if (usesManagedBundledMarketplace && desktopCandidates.length > 0 && !marketplacePath) throw new CodexComputerUseCandidateArtifactsUnavailableError();
|
|
1177
|
+
const service = await ensureCodexComputerUseServiceApp({
|
|
1178
|
+
codexHome,
|
|
1179
|
+
ownershipRoot: params.agentDir,
|
|
1180
|
+
...artifactCandidate ? {
|
|
1181
|
+
appServerCommand: artifactCandidate.appServerCommandPath,
|
|
1182
|
+
sourceAppCandidates: artifactCandidate.computerUseServiceAppPaths
|
|
1183
|
+
} : {},
|
|
1184
|
+
assertCurrent: params.assertCurrent
|
|
1185
|
+
});
|
|
1186
|
+
params.assertCurrent();
|
|
1187
|
+
if (desktopCandidates.length > 0 && service.status === "source_missing") throw new CodexComputerUseCandidateArtifactsUnavailableError();
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
params.assertCurrent();
|
|
1190
|
+
if (error instanceof CodexComputerUseCandidateArtifactsUnavailableError) throw error;
|
|
1191
|
+
throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
|
|
1192
|
+
cause: error,
|
|
1193
|
+
scope: "harness"
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
params.assertCurrent();
|
|
1198
|
+
const cacheBinding = [
|
|
1199
|
+
params.desktopGeneration?.epoch ?? "manual",
|
|
1200
|
+
artifactCandidate?.bundledMarketplacePath ?? "default",
|
|
1201
|
+
computerUseConfig.pluginName
|
|
1202
|
+
].join("\0");
|
|
1203
|
+
const cache = await ensureCodexComputerUseSharedPluginCache({
|
|
1204
|
+
codexHome,
|
|
1205
|
+
config: computerUseConfig,
|
|
1206
|
+
...ownsIsolatedCodexHome ? { ownershipRoot: params.agentDir } : {},
|
|
1207
|
+
...artifactCandidate ? { bundledMarketplacePath: artifactCandidate.bundledMarketplacePath } : {},
|
|
1208
|
+
assertCurrent: params.assertCurrent,
|
|
1209
|
+
forceRefresh: params.forceCacheRefresh === true || params.previousCacheBinding !== cacheBinding
|
|
1210
|
+
});
|
|
1211
|
+
params.assertCurrent();
|
|
1212
|
+
return cache.status === "shared" ? cacheBinding : void 0;
|
|
1213
|
+
}
|
|
1214
|
+
async function resolveCompleteComputerUseArtifactCandidate(params) {
|
|
1215
|
+
for (const candidate of params.candidates) {
|
|
1216
|
+
if (params.needsBundledMarketplace && !await resolveCodexManagedBundledMarketplaceSource({ candidates: [candidate] })) continue;
|
|
1217
|
+
if (await resolveCodexComputerUseServiceAppSourcePath({ sourceAppCandidates: candidate.computerUseServiceAppPaths })) return candidate;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
function pruneComputerUseArtifactOwners() {
|
|
1221
|
+
while (activeComputerUseArtifactReconciliations.size > MAX_COMPUTER_USE_ARTIFACT_OWNERS) {
|
|
1222
|
+
const inactive = [...activeComputerUseArtifactReconciliations].find(([, owner]) => owner.active === 0);
|
|
1223
|
+
if (!inactive) return;
|
|
1224
|
+
activeComputerUseArtifactReconciliations.delete(inactive[0]);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
var CodexComputerUseCandidateArtifactsUnavailableError = class extends Error {
|
|
1228
|
+
constructor() {
|
|
1229
|
+
super("The selected Codex desktop app does not contain complete Computer Use artifacts.");
|
|
1230
|
+
this.code = "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
|
|
1231
|
+
this.name = "CodexComputerUseCandidateArtifactsUnavailableError";
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1013
1234
|
function withoutClearedCodexHomeEnv(clearEnv) {
|
|
1014
1235
|
if (!clearEnv) return;
|
|
1015
1236
|
const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
|
|
@@ -1097,11 +1318,24 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
|
1097
1318
|
});
|
|
1098
1319
|
}
|
|
1099
1320
|
async function refreshCodexAppServerAuthTokens(params) {
|
|
1321
|
+
const previousAccountId = params.previousAccountId?.trim();
|
|
1322
|
+
if (previousAccountId) {
|
|
1323
|
+
const store = resolveCodexAppServerAuthProfileStore(params);
|
|
1324
|
+
const profileId = resolveCodexAppServerAuthProfileId({
|
|
1325
|
+
authProfileId: params.authProfileId,
|
|
1326
|
+
store,
|
|
1327
|
+
config: params.config
|
|
1328
|
+
});
|
|
1329
|
+
const credential = profileId ? store.profiles[profileId] : void 0;
|
|
1330
|
+
const selectedAccountId = credential ? resolveExplicitChatgptAccountId(credential) ?? (credential.type === "oauth" ? resolveOpenAICodexAuthIdentity({ access: credential.access }).accountId : void 0) : void 0;
|
|
1331
|
+
if (selectedAccountId && selectedAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh.");
|
|
1332
|
+
}
|
|
1100
1333
|
const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
1101
1334
|
...params,
|
|
1102
1335
|
forceOAuthRefresh: true
|
|
1103
1336
|
});
|
|
1104
1337
|
if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
|
|
1338
|
+
if (previousAccountId && loginParams.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh.");
|
|
1105
1339
|
return {
|
|
1106
1340
|
accessToken: loginParams.accessToken,
|
|
1107
1341
|
chatgptAccountId: loginParams.chatgptAccountId,
|
|
@@ -1339,10 +1573,15 @@ function readFirstNonEmptyEnvEntry(env, keys) {
|
|
|
1339
1573
|
}
|
|
1340
1574
|
}
|
|
1341
1575
|
function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
|
|
1576
|
+
const storedAccountId = resolveExplicitChatgptAccountId(credential);
|
|
1577
|
+
const tokenAccountId = resolveOpenAICodexAuthIdentity({ access: accessToken }).accountId;
|
|
1578
|
+
if (storedAccountId && tokenAccountId && storedAccountId !== tokenAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" has a different ChatGPT account ID than its access token. Sign in again before retrying.`);
|
|
1579
|
+
const chatgptAccountId = storedAccountId ?? tokenAccountId;
|
|
1580
|
+
if (!chatgptAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is missing its ChatGPT account ID. Sign in again before retrying.`);
|
|
1342
1581
|
return {
|
|
1343
1582
|
type: "chatgptAuthTokens",
|
|
1344
1583
|
accessToken,
|
|
1345
|
-
chatgptAccountId
|
|
1584
|
+
chatgptAccountId,
|
|
1346
1585
|
chatgptPlanType: resolveChatgptPlanType(credential)
|
|
1347
1586
|
};
|
|
1348
1587
|
}
|
|
@@ -1462,12 +1701,46 @@ function readLimitId(snapshot) {
|
|
|
1462
1701
|
return typeof value === "string" && value.trim() ? value.trim() : "codex";
|
|
1463
1702
|
}
|
|
1464
1703
|
//#endregion
|
|
1704
|
+
//#region extensions/codex/src/app-server/timeout.ts
|
|
1705
|
+
/**
|
|
1706
|
+
* Thin Codex app-server timeout adapter around OpenClaw's shared security
|
|
1707
|
+
* runtime timeout helper.
|
|
1708
|
+
*/
|
|
1709
|
+
function resolveAbortError(signal) {
|
|
1710
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
|
|
1711
|
+
}
|
|
1712
|
+
/** Awaits a promise with a Codex-specific timeout error message. */
|
|
1713
|
+
async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
|
|
1714
|
+
return await withTimeout(promise, timeoutMs, {
|
|
1715
|
+
message: timeoutMessage,
|
|
1716
|
+
...createError ? { createError } : {}
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
/** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
|
|
1720
|
+
async function withAbortableTimeout(params) {
|
|
1721
|
+
const signal = params.signal;
|
|
1722
|
+
if (signal?.aborted) throw resolveAbortError(signal);
|
|
1723
|
+
let removeAbortListener;
|
|
1724
|
+
const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
|
|
1725
|
+
const onAbort = () => reject(resolveAbortError(signal));
|
|
1726
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1727
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
1728
|
+
})]) : params.promise;
|
|
1729
|
+
try {
|
|
1730
|
+
return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
|
|
1731
|
+
} finally {
|
|
1732
|
+
removeAbortListener?.();
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
//#endregion
|
|
1465
1736
|
//#region extensions/codex/src/app-server/client-runtime.ts
|
|
1466
1737
|
/** Client-scoped Codex auth and account observers. */
|
|
1467
1738
|
/** Match Codex's native grace window without retaining inactive conversations indefinitely. */
|
|
1468
1739
|
const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
1469
1740
|
/** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
|
|
1470
1741
|
const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
|
|
1742
|
+
/** Return a deterministic error before Codex cancels its ten-second external-auth request. */
|
|
1743
|
+
const CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS = 9e3;
|
|
1471
1744
|
const configuredClients = /* @__PURE__ */ new WeakMap();
|
|
1472
1745
|
const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
|
|
1473
1746
|
const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
|
|
@@ -1481,7 +1754,10 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1481
1754
|
const existing = configuredClients.get(client);
|
|
1482
1755
|
if (existing) {
|
|
1483
1756
|
if (existing.closed) return;
|
|
1484
|
-
existing.context =
|
|
1757
|
+
existing.context = {
|
|
1758
|
+
...existing.context,
|
|
1759
|
+
config: context.config
|
|
1760
|
+
};
|
|
1485
1761
|
return;
|
|
1486
1762
|
}
|
|
1487
1763
|
const runtime = {
|
|
@@ -1506,12 +1782,16 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1506
1782
|
client.addRequestHandler(async (request) => {
|
|
1507
1783
|
if (request.method !== "account/chatgptAuthTokens/refresh") return;
|
|
1508
1784
|
if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
|
|
1509
|
-
|
|
1785
|
+
const previousAccountId = isJsonObject(request.params) && typeof request.params.previousAccountId === "string" ? request.params.previousAccountId.trim() || void 0 : void 0;
|
|
1786
|
+
const tokens = await withTimeout$1(refreshCodexAppServerAuthTokens({
|
|
1510
1787
|
agentDir: runtime.context.agentDir,
|
|
1511
1788
|
authProfileId: runtime.context.authProfileId,
|
|
1789
|
+
...previousAccountId ? { previousAccountId } : {},
|
|
1512
1790
|
...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
|
|
1513
1791
|
config: runtime.context.config
|
|
1514
|
-
})
|
|
1792
|
+
}), CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS, "Codex app-server ChatGPT token refresh timed out before its external-auth deadline");
|
|
1793
|
+
if (previousAccountId && tokens.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh.");
|
|
1794
|
+
return { ...tokens };
|
|
1515
1795
|
});
|
|
1516
1796
|
client.addNotificationHandler((notification) => {
|
|
1517
1797
|
if (notification.method === "account/rateLimits/updated") {
|
|
@@ -2305,6 +2585,7 @@ const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
|
|
|
2305
2585
|
const CODEX_APP_SERVER_OVERLOADED_ERROR_CODE = -32001;
|
|
2306
2586
|
const CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES = 3;
|
|
2307
2587
|
const CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS = 50;
|
|
2588
|
+
const CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX = 32;
|
|
2308
2589
|
const CODEX_APP_SERVER_CLIENT_INSTANCE_IDS = /* @__PURE__ */ new WeakMap();
|
|
2309
2590
|
const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
2310
2591
|
/** Process-local generation fence for bindings tied to one app-server client instance. */
|
|
@@ -2373,6 +2654,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2373
2654
|
this.pending = /* @__PURE__ */ new Map();
|
|
2374
2655
|
this.requestHandlers = /* @__PURE__ */ new Set();
|
|
2375
2656
|
this.notificationHandlers = /* @__PURE__ */ new Set();
|
|
2657
|
+
this.pendingStartupWarnings = [];
|
|
2376
2658
|
this.closeHandlers = /* @__PURE__ */ new Set();
|
|
2377
2659
|
this.nextId = 1;
|
|
2378
2660
|
this.initialized = false;
|
|
@@ -2429,7 +2711,8 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2429
2711
|
experimentalApi: true,
|
|
2430
2712
|
extensions: {
|
|
2431
2713
|
"openai/standard-form-input": {},
|
|
2432
|
-
"openai/form": {}
|
|
2714
|
+
"openai/form": {},
|
|
2715
|
+
"io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app"] }
|
|
2433
2716
|
}
|
|
2434
2717
|
}
|
|
2435
2718
|
});
|
|
@@ -2642,6 +2925,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2642
2925
|
/** Registers a notification handler and returns its disposer. */
|
|
2643
2926
|
addNotificationHandler(handler) {
|
|
2644
2927
|
this.notificationHandlers.add(handler);
|
|
2928
|
+
for (const notification of this.pendingStartupWarnings.splice(0)) this.handleNotification(notification);
|
|
2645
2929
|
return () => this.notificationHandlers.delete(handler);
|
|
2646
2930
|
}
|
|
2647
2931
|
/** Registers a close handler and returns its disposer. */
|
|
@@ -2649,6 +2933,16 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2649
2933
|
this.closeHandlers.add(handler);
|
|
2650
2934
|
return () => this.closeHandlers.delete(handler);
|
|
2651
2935
|
}
|
|
2936
|
+
/** Registers a handler for physical transport exit and returns its disposer. */
|
|
2937
|
+
addTransportExitHandler(handler) {
|
|
2938
|
+
if (this.transportExited) {
|
|
2939
|
+
handler(this);
|
|
2940
|
+
return () => void 0;
|
|
2941
|
+
}
|
|
2942
|
+
const onExit = () => handler(this);
|
|
2943
|
+
this.child.once("exit", onExit);
|
|
2944
|
+
return () => this.child.off?.("exit", onExit);
|
|
2945
|
+
}
|
|
2652
2946
|
/** Closes the transport without waiting for process/socket shutdown. */
|
|
2653
2947
|
close() {
|
|
2654
2948
|
if (!this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"))) return;
|
|
@@ -2839,6 +3133,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2839
3133
|
}
|
|
2840
3134
|
}
|
|
2841
3135
|
handleNotification(notification) {
|
|
3136
|
+
if (this.notificationHandlers.size === 0 && notification.method === "configWarning") {
|
|
3137
|
+
if (this.pendingStartupWarnings.length === CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX) this.pendingStartupWarnings.shift();
|
|
3138
|
+
this.pendingStartupWarnings.push(notification);
|
|
3139
|
+
return;
|
|
3140
|
+
}
|
|
2842
3141
|
for (const handler of this.notificationHandlers) try {
|
|
2843
3142
|
Promise.resolve(handler(notification)).catch((error) => {
|
|
2844
3143
|
embeddedAgentLog.warn("codex app-server notification handler failed", { error });
|
|
@@ -2910,8 +3209,8 @@ function assertSupportedCodexAppServerVersion(response) {
|
|
|
2910
3209
|
const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
|
|
2911
3210
|
if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
|
|
2912
3211
|
const detected = parse(detectedVersion);
|
|
2913
|
-
if (!detected || detected.compare("0.
|
|
2914
|
-
if (detected.compare("0.
|
|
3212
|
+
if (!detected || detected.compare("0.149.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
|
|
3213
|
+
if (detected.compare("0.150.1") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
|
|
2915
3214
|
detectedVersion,
|
|
2916
3215
|
validatedVersion: CODEX_APP_SERVER_VERSION
|
|
2917
3216
|
});
|
|
@@ -3078,6 +3377,324 @@ function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
|
|
|
3078
3377
|
return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
|
|
3079
3378
|
}
|
|
3080
3379
|
//#endregion
|
|
3380
|
+
//#region extensions/codex/src/app-server/desktop-generation-fingerprint.ts
|
|
3381
|
+
const MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES = 4096;
|
|
3382
|
+
/** Fingerprints every desktop candidate that can own a managed fallback artifact. */
|
|
3383
|
+
async function readMacOSDesktopGenerationFingerprint(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
|
|
3384
|
+
const entries = [];
|
|
3385
|
+
for (const candidate of candidates) {
|
|
3386
|
+
const command = await statFingerprint(candidate.appServerCommandPath);
|
|
3387
|
+
entries.push(`candidate:${candidate.appName}:${candidate.appServerCommandPath}:${command}`);
|
|
3388
|
+
for (const artifactPath of resolveMacOSDesktopGenerationPaths(candidate)) entries.push(`${artifactPath}\0${await statFingerprint(artifactPath)}`);
|
|
3389
|
+
const pluginRoot = resolveComputerUsePluginRoot(candidate);
|
|
3390
|
+
entries.push(`${pluginRoot}\0${await directoryTreeFingerprint(pluginRoot)}`);
|
|
3391
|
+
}
|
|
3392
|
+
return createHash("sha256").update(entries.join("\0")).digest("hex");
|
|
3393
|
+
}
|
|
3394
|
+
function resolveMacOSDesktopGenerationPaths(candidate) {
|
|
3395
|
+
return [
|
|
3396
|
+
candidate.appBundlePath,
|
|
3397
|
+
path.join(candidate.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"),
|
|
3398
|
+
...candidate.computerUseServiceAppPaths.flatMap((servicePath) => [
|
|
3399
|
+
servicePath,
|
|
3400
|
+
path.join(servicePath, "Contents", "Info.plist"),
|
|
3401
|
+
path.join(servicePath, "Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient")
|
|
3402
|
+
])
|
|
3403
|
+
];
|
|
3404
|
+
}
|
|
3405
|
+
function resolveComputerUsePluginRoot(candidate) {
|
|
3406
|
+
return path.join(candidate.bundledMarketplacePath, "plugins", "computer-use");
|
|
3407
|
+
}
|
|
3408
|
+
/** Stable roots that cover bundle replacement and recursive artifact updates. */
|
|
3409
|
+
function resolveMacOSDesktopGenerationWatchPaths(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
|
|
3410
|
+
const watched = /* @__PURE__ */ new Set(["/Applications"]);
|
|
3411
|
+
for (const candidate of candidates) watched.add(candidate.appBundlePath);
|
|
3412
|
+
return [...watched];
|
|
3413
|
+
}
|
|
3414
|
+
async function directoryTreeFingerprint(root) {
|
|
3415
|
+
let rootStat;
|
|
3416
|
+
try {
|
|
3417
|
+
rootStat = await fs.lstat(root, { bigint: true });
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
|
|
3420
|
+
throw error;
|
|
3421
|
+
}
|
|
3422
|
+
if (!rootStat.isDirectory()) return statFingerprint(root);
|
|
3423
|
+
let entryCount = 0;
|
|
3424
|
+
const hash = createHash("sha256");
|
|
3425
|
+
hash.update("openclaw-codex-computer-use-plugin-tree-v1\0");
|
|
3426
|
+
const visit = async (directory, relativeDirectory, before) => {
|
|
3427
|
+
hash.update(`directory\0${relativeDirectory}\0${statTuple(before)}\0`);
|
|
3428
|
+
const entries = (await fs.readdir(directory, { withFileTypes: true })).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
3429
|
+
for (const entry of entries) {
|
|
3430
|
+
entryCount += 1;
|
|
3431
|
+
if (entryCount > MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES) throw new Error("Codex Computer Use plugin exceeds the bounded tree size");
|
|
3432
|
+
const entryPath = path.join(directory, entry.name);
|
|
3433
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
3434
|
+
const entryStat = await fs.lstat(entryPath, { bigint: true });
|
|
3435
|
+
if (entryStat.isDirectory()) await visit(entryPath, relativePath, entryStat);
|
|
3436
|
+
else hash.update(`entry\0${relativePath}\0${await statFingerprint(entryPath)}\0`);
|
|
3437
|
+
}
|
|
3438
|
+
if (!sameStat(before, await fs.lstat(directory, { bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${directory}`);
|
|
3439
|
+
};
|
|
3440
|
+
await visit(root, ".", rootStat);
|
|
3441
|
+
return hash.digest("hex");
|
|
3442
|
+
}
|
|
3443
|
+
async function statFingerprint(filePath) {
|
|
3444
|
+
try {
|
|
3445
|
+
const entry = await fs.lstat(filePath, { bigint: true });
|
|
3446
|
+
const type = entry.isSymbolicLink() ? "link" : entry.isDirectory() ? "directory" : entry.isFile() ? "file" : "other";
|
|
3447
|
+
const own = statTuple(entry);
|
|
3448
|
+
if (!entry.isSymbolicLink()) return `${type}:${own}:${entry.isFile() ? await readFileFingerprint(filePath, entry, false) : ""}`;
|
|
3449
|
+
const [link, realPath, target] = await Promise.all([
|
|
3450
|
+
fs.readlink(filePath),
|
|
3451
|
+
fs.realpath(filePath),
|
|
3452
|
+
fs.stat(filePath, { bigint: true })
|
|
3453
|
+
]);
|
|
3454
|
+
const content = target.isFile() ? await readFileFingerprint(filePath, target, true) : "";
|
|
3455
|
+
return `${type}:${own}:${link}:${realPath}:${statTuple(target)}:${content}`;
|
|
3456
|
+
} catch (error) {
|
|
3457
|
+
if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
|
|
3458
|
+
throw error;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
async function readFileFingerprint(filePath, expected, followsSymlink) {
|
|
3462
|
+
const noFollow = followsSymlink ? 0 : constants.O_NOFOLLOW ?? 0;
|
|
3463
|
+
const handle = await fs.open(filePath, constants.O_RDONLY | noFollow);
|
|
3464
|
+
try {
|
|
3465
|
+
const before = await handle.stat({ bigint: true });
|
|
3466
|
+
if (!sameStat(before, expected)) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
|
|
3467
|
+
const hash = createHash("sha256");
|
|
3468
|
+
for await (const chunk of handle.createReadStream({ autoClose: false })) hash.update(chunk);
|
|
3469
|
+
if (!sameStat(before, await handle.stat({ bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
|
|
3470
|
+
return hash.digest("hex");
|
|
3471
|
+
} finally {
|
|
3472
|
+
await handle.close();
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
function sameStat(left, right) {
|
|
3476
|
+
return statTuple(left) === statTuple(right);
|
|
3477
|
+
}
|
|
3478
|
+
function statTuple(stat) {
|
|
3479
|
+
return [
|
|
3480
|
+
stat.dev,
|
|
3481
|
+
stat.ino,
|
|
3482
|
+
stat.mode,
|
|
3483
|
+
stat.size,
|
|
3484
|
+
stat.mtimeNs,
|
|
3485
|
+
stat.ctimeNs
|
|
3486
|
+
].join(":");
|
|
3487
|
+
}
|
|
3488
|
+
function isNodeError(error, code) {
|
|
3489
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
3490
|
+
}
|
|
3491
|
+
//#endregion
|
|
3492
|
+
//#region extensions/codex/src/app-server/desktop-generation-owner.ts
|
|
3493
|
+
const SETTLE_DELAY_MS = 1e3;
|
|
3494
|
+
/** Coalesces filesystem invalidations into one stable desktop generation. */
|
|
3495
|
+
function createCodexDesktopGenerationOwner(params) {
|
|
3496
|
+
let generation = params.initialGeneration;
|
|
3497
|
+
let invalidation = 0;
|
|
3498
|
+
let dirty = false;
|
|
3499
|
+
let refresh;
|
|
3500
|
+
let stopped = false;
|
|
3501
|
+
const markDirty = () => {
|
|
3502
|
+
invalidation += 1;
|
|
3503
|
+
dirty = true;
|
|
3504
|
+
};
|
|
3505
|
+
const reconcile = () => {
|
|
3506
|
+
if (refresh) return refresh;
|
|
3507
|
+
refresh = (async () => {
|
|
3508
|
+
for (;;) {
|
|
3509
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3510
|
+
const observedInvalidation = invalidation;
|
|
3511
|
+
const first = await params.readFingerprint();
|
|
3512
|
+
await delay(SETTLE_DELAY_MS);
|
|
3513
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3514
|
+
const second = await params.readFingerprint();
|
|
3515
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3516
|
+
if (observedInvalidation !== invalidation || first !== second) continue;
|
|
3517
|
+
const previous = generation;
|
|
3518
|
+
generation = previous?.fingerprint === second ? previous : {
|
|
3519
|
+
epoch: (previous?.epoch ?? 0) + 1,
|
|
3520
|
+
fingerprint: second
|
|
3521
|
+
};
|
|
3522
|
+
dirty = false;
|
|
3523
|
+
if (previous && generation !== previous) params.onGenerationChange?.(generation);
|
|
3524
|
+
return generation;
|
|
3525
|
+
}
|
|
3526
|
+
})().finally(() => {
|
|
3527
|
+
refresh = void 0;
|
|
3528
|
+
});
|
|
3529
|
+
return refresh;
|
|
3530
|
+
};
|
|
3531
|
+
return {
|
|
3532
|
+
read: () => generation,
|
|
3533
|
+
markDirty,
|
|
3534
|
+
wait: () => dirty ? reconcile() : Promise.resolve(generation),
|
|
3535
|
+
refresh: () => {
|
|
3536
|
+
markDirty();
|
|
3537
|
+
return reconcile();
|
|
3538
|
+
},
|
|
3539
|
+
isCurrent: (candidate) => Boolean(candidate && !dirty && generation && candidate.epoch === generation.epoch && candidate.fingerprint === generation.fingerprint),
|
|
3540
|
+
stop: () => {
|
|
3541
|
+
stopped = true;
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
function delay(ms) {
|
|
3546
|
+
return new Promise((resolve) => {
|
|
3547
|
+
setTimeout(resolve, ms);
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
//#endregion
|
|
3551
|
+
//#region extensions/codex/src/app-server/desktop-generation.ts
|
|
3552
|
+
/** Lifecycle-owned generation for managed macOS Codex desktop artifacts. */
|
|
3553
|
+
const APPLICATIONS_PATH = "/Applications";
|
|
3554
|
+
const REARM_INITIAL_DELAY_MS = 100;
|
|
3555
|
+
const REARM_MAX_DELAY_MS = 3e4;
|
|
3556
|
+
const DESKTOP_GENERATION_STATE = Symbol.for("openclaw.codexDesktopGenerationState");
|
|
3557
|
+
function state() {
|
|
3558
|
+
const globalState = globalThis;
|
|
3559
|
+
return globalState[DESKTOP_GENERATION_STATE] ??= {};
|
|
3560
|
+
}
|
|
3561
|
+
function waitForCodexDesktopGeneration() {
|
|
3562
|
+
return state().owner?.wait() ?? Promise.resolve(void 0);
|
|
3563
|
+
}
|
|
3564
|
+
function isCodexDesktopGenerationCurrent(generation) {
|
|
3565
|
+
return state().owner?.isCurrent(generation) ?? false;
|
|
3566
|
+
}
|
|
3567
|
+
function createCodexDesktopGenerationService(params, runtime = {
|
|
3568
|
+
platform: process.platform,
|
|
3569
|
+
readFingerprint: readMacOSDesktopGenerationFingerprint,
|
|
3570
|
+
resolveWatchPaths: resolveMacOSDesktopGenerationWatchPaths,
|
|
3571
|
+
pathExists: existsSync,
|
|
3572
|
+
watchPath: (watchedPath, options, listener) => watch(watchedPath, options, listener)
|
|
3573
|
+
}) {
|
|
3574
|
+
return {
|
|
3575
|
+
id: "codex-desktop-generation",
|
|
3576
|
+
async start(ctx) {
|
|
3577
|
+
if (runtime.platform !== "darwin") return;
|
|
3578
|
+
const current = state();
|
|
3579
|
+
current.context = ctx;
|
|
3580
|
+
current.readFingerprint = runtime.readFingerprint;
|
|
3581
|
+
current.resolveWatchPaths = runtime.resolveWatchPaths;
|
|
3582
|
+
current.pathExists = runtime.pathExists;
|
|
3583
|
+
current.watchPath = runtime.watchPath;
|
|
3584
|
+
current.owner = createCodexDesktopGenerationOwner({
|
|
3585
|
+
readFingerprint: current.readFingerprint,
|
|
3586
|
+
onGenerationChange: params.onGenerationChange,
|
|
3587
|
+
initialGeneration: current.lastGeneration
|
|
3588
|
+
});
|
|
3589
|
+
armWatchers(current);
|
|
3590
|
+
refreshGeneration(current, current.owner, current.owner.refresh());
|
|
3591
|
+
},
|
|
3592
|
+
async stop() {
|
|
3593
|
+
const current = state();
|
|
3594
|
+
current.lastGeneration = current.owner?.read() ?? current.lastGeneration;
|
|
3595
|
+
current.owner?.stop();
|
|
3596
|
+
current.owner = void 0;
|
|
3597
|
+
current.armEpoch = (current.armEpoch ?? 0) + 1;
|
|
3598
|
+
current.context = void 0;
|
|
3599
|
+
current.readFingerprint = void 0;
|
|
3600
|
+
current.resolveWatchPaths = void 0;
|
|
3601
|
+
current.pathExists = void 0;
|
|
3602
|
+
current.watchPath = void 0;
|
|
3603
|
+
current.watchHealthy = void 0;
|
|
3604
|
+
current.rearmDelayMs = void 0;
|
|
3605
|
+
if (current.rearmTimer) {
|
|
3606
|
+
clearTimeout(current.rearmTimer);
|
|
3607
|
+
current.rearmTimer = void 0;
|
|
3608
|
+
}
|
|
3609
|
+
closeWatchers(current);
|
|
3610
|
+
}
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
function armWatchers(current) {
|
|
3614
|
+
const owner = current.owner;
|
|
3615
|
+
if (!owner || current.watchers) return false;
|
|
3616
|
+
const armEpoch = (current.armEpoch ?? 0) + 1;
|
|
3617
|
+
current.armEpoch = armEpoch;
|
|
3618
|
+
const watchers = /* @__PURE__ */ new Set();
|
|
3619
|
+
current.watchers = watchers;
|
|
3620
|
+
const candidateNames = new Set(resolveMacOSDesktopCodexAppPathCandidates("darwin").map((candidate) => candidate.appName));
|
|
3621
|
+
let complete = true;
|
|
3622
|
+
for (const watchedPath of current.resolveWatchPaths?.() ?? []) {
|
|
3623
|
+
if (!current.pathExists?.(watchedPath)) continue;
|
|
3624
|
+
try {
|
|
3625
|
+
const watcher = current.watchPath?.(watchedPath, { recursive: watchedPath !== APPLICATIONS_PATH }, (_eventType, filename) => {
|
|
3626
|
+
if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
|
|
3627
|
+
if (watchedPath === APPLICATIONS_PATH && filename && !candidateNames.has(filename.toString().split(path.sep)[0] ?? "")) return;
|
|
3628
|
+
owner.markDirty();
|
|
3629
|
+
scheduleRearm(current, owner);
|
|
3630
|
+
});
|
|
3631
|
+
if (!watcher) {
|
|
3632
|
+
complete = false;
|
|
3633
|
+
reportWatcherFailure(current, owner, /* @__PURE__ */ new Error(`Could not watch ${watchedPath}`));
|
|
3634
|
+
scheduleRearm(current, owner);
|
|
3635
|
+
continue;
|
|
3636
|
+
}
|
|
3637
|
+
watchers.add(watcher);
|
|
3638
|
+
watcher.on("error", (error) => {
|
|
3639
|
+
if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
|
|
3640
|
+
reportWatcherFailure(current, owner, error);
|
|
3641
|
+
scheduleRearm(current, owner);
|
|
3642
|
+
});
|
|
3643
|
+
} catch (error) {
|
|
3644
|
+
complete = false;
|
|
3645
|
+
reportWatcherFailure(current, owner, error);
|
|
3646
|
+
scheduleRearm(current, owner);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
current.watchHealthy = complete;
|
|
3650
|
+
if (complete) current.rearmDelayMs = REARM_INITIAL_DELAY_MS;
|
|
3651
|
+
return complete;
|
|
3652
|
+
}
|
|
3653
|
+
function reportWatcherFailure(current, owner, error) {
|
|
3654
|
+
if (current.watchHealthy === false) return;
|
|
3655
|
+
current.watchHealthy = false;
|
|
3656
|
+
owner.markDirty();
|
|
3657
|
+
current.context?.serviceHealth?.reportFailure(error);
|
|
3658
|
+
current.context?.logger.warn(`codex desktop generation watcher failed: ${String(error)}`);
|
|
3659
|
+
}
|
|
3660
|
+
function isCurrentArm(current, owner, watchers, armEpoch) {
|
|
3661
|
+
return current.owner === owner && current.watchers === watchers && current.armEpoch === armEpoch;
|
|
3662
|
+
}
|
|
3663
|
+
function scheduleRearm(current, owner) {
|
|
3664
|
+
if (current.rearmTimer) {
|
|
3665
|
+
if (current.watchHealthy === false) return;
|
|
3666
|
+
clearTimeout(current.rearmTimer);
|
|
3667
|
+
}
|
|
3668
|
+
const delayMs = current.watchHealthy === false ? current.rearmDelayMs ?? REARM_INITIAL_DELAY_MS : REARM_INITIAL_DELAY_MS;
|
|
3669
|
+
if (current.watchHealthy === false) current.rearmDelayMs = Math.min(delayMs * 2, REARM_MAX_DELAY_MS);
|
|
3670
|
+
current.rearmTimer = setTimeout(() => {
|
|
3671
|
+
current.rearmTimer = void 0;
|
|
3672
|
+
if (current.owner !== owner) return;
|
|
3673
|
+
const wasUnhealthy = current.watchHealthy === false;
|
|
3674
|
+
closeWatchers(current);
|
|
3675
|
+
if (!armWatchers(current) || wasUnhealthy) owner.markDirty();
|
|
3676
|
+
refreshGeneration(current, owner, owner.wait());
|
|
3677
|
+
}, delayMs);
|
|
3678
|
+
current.rearmTimer.unref();
|
|
3679
|
+
}
|
|
3680
|
+
function logRefreshFailure(current, owner) {
|
|
3681
|
+
return (error) => {
|
|
3682
|
+
if (current.owner !== owner) return;
|
|
3683
|
+
current.context?.serviceHealth?.reportFailure(error);
|
|
3684
|
+
current.context?.logger.warn(`codex desktop generation refresh failed: ${String(error)}`);
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
function refreshGeneration(current, owner, refresh) {
|
|
3688
|
+
refresh.then(() => {
|
|
3689
|
+
if (current.owner === owner && current.watchHealthy) current.context?.serviceHealth?.clearFailure();
|
|
3690
|
+
}).catch(logRefreshFailure(current, owner));
|
|
3691
|
+
}
|
|
3692
|
+
function closeWatchers(current) {
|
|
3693
|
+
const watchers = current.watchers;
|
|
3694
|
+
current.watchers = void 0;
|
|
3695
|
+
for (const watcher of watchers ?? []) watcher.close();
|
|
3696
|
+
}
|
|
3697
|
+
//#endregion
|
|
3081
3698
|
//#region extensions/codex/src/app-server/native-config-fence.ts
|
|
3082
3699
|
const CODEX_NATIVE_CONFIG_FENCE_STATE = Symbol.for("openclaw.codexNativeConfigFenceState");
|
|
3083
3700
|
function getFenceState() {
|
|
@@ -3140,38 +3757,6 @@ async function waitForPreviousFence(previous, options) {
|
|
|
3140
3757
|
});
|
|
3141
3758
|
}
|
|
3142
3759
|
//#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?.();
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
//#endregion
|
|
3175
3760
|
//#region extensions/codex/src/app-server/shared-client.ts
|
|
3176
3761
|
/**
|
|
3177
3762
|
* Owns shared and isolated Codex app-server client startup, auth application,
|
|
@@ -3187,6 +3772,9 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
|
3187
3772
|
getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
|
|
3188
3773
|
getSharedCodexAppServerClient: () => getSharedCodexAppServerClient,
|
|
3189
3774
|
isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
|
|
3775
|
+
readCodexAppServerClientDesktopGeneration: () => readCodexAppServerClientDesktopGeneration,
|
|
3776
|
+
readCodexAppServerClientDesktopGenerationFingerprint: () => readCodexAppServerClientDesktopGenerationFingerprint,
|
|
3777
|
+
readCodexAppServerClientProcessIdentity: () => readCodexAppServerClientProcessIdentity,
|
|
3190
3778
|
releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
|
|
3191
3779
|
releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
|
|
3192
3780
|
resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
|
|
@@ -3194,6 +3782,8 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
|
3194
3782
|
retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
|
|
3195
3783
|
retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
|
|
3196
3784
|
retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
|
|
3785
|
+
retireSharedCodexAppServerClientsBeforeDesktopGeneration: () => retireSharedCodexAppServerClientsBeforeDesktopGeneration,
|
|
3786
|
+
waitForCodexAppServerClientDesktopGenerationDrain: () => waitForCodexAppServerClientDesktopGenerationDrain,
|
|
3197
3787
|
withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
|
|
3198
3788
|
});
|
|
3199
3789
|
const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
|
|
@@ -3205,8 +3795,10 @@ function getSharedCodexAppServerClientState() {
|
|
|
3205
3795
|
globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
|
|
3206
3796
|
clients: /* @__PURE__ */ new Map(),
|
|
3207
3797
|
liveClients: /* @__PURE__ */ new Set(),
|
|
3798
|
+
isolatedClients: /* @__PURE__ */ new Set(),
|
|
3208
3799
|
entriesByClient: /* @__PURE__ */ new WeakMap(),
|
|
3209
|
-
leasedReleases: /* @__PURE__ */ new WeakMap()
|
|
3800
|
+
leasedReleases: /* @__PURE__ */ new WeakMap(),
|
|
3801
|
+
desktopGenerationDrainChecks: /* @__PURE__ */ new Set()
|
|
3210
3802
|
};
|
|
3211
3803
|
return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
|
|
3212
3804
|
}
|
|
@@ -3215,6 +3807,41 @@ function getCodexAppServerClientStartMetadata() {
|
|
|
3215
3807
|
globalState[CODEX_APP_SERVER_CLIENT_START_METADATA] ??= /* @__PURE__ */ new WeakMap();
|
|
3216
3808
|
return globalState[CODEX_APP_SERVER_CLIENT_START_METADATA];
|
|
3217
3809
|
}
|
|
3810
|
+
/** Reads the exact successful spawn selection plus its initialized runtime identity. */
|
|
3811
|
+
function readCodexAppServerClientProcessIdentity(client) {
|
|
3812
|
+
const metadata = getCodexAppServerClientStartMetadata().get(client);
|
|
3813
|
+
if (!metadata) return;
|
|
3814
|
+
const runtimeIdentity = client.getRuntimeIdentity();
|
|
3815
|
+
return {
|
|
3816
|
+
clientId: client.getInstanceId(),
|
|
3817
|
+
...resolveCodexAppServerSpawnIdentity(metadata.startOptions, metadata.nativeCommand),
|
|
3818
|
+
...runtimeIdentity?.serverVersion ? { serverVersion: runtimeIdentity.serverVersion } : {},
|
|
3819
|
+
...runtimeIdentity?.userAgent ? { userAgent: runtimeIdentity.userAgent } : {}
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
/** Returns the lifecycle fingerprint that owns a managed desktop client. */
|
|
3823
|
+
function readCodexAppServerClientDesktopGenerationFingerprint(client) {
|
|
3824
|
+
return readCodexAppServerClientDesktopGeneration(client)?.fingerprint;
|
|
3825
|
+
}
|
|
3826
|
+
/** Returns the lifecycle generation that owns a managed desktop client. */
|
|
3827
|
+
function readCodexAppServerClientDesktopGeneration(client) {
|
|
3828
|
+
return getCodexAppServerClientStartMetadata().get(client)?.desktopGeneration;
|
|
3829
|
+
}
|
|
3830
|
+
/** Waits until older physical desktop clients for this client's Codex home exit. */
|
|
3831
|
+
async function waitForCodexAppServerClientDesktopGenerationDrain(params) {
|
|
3832
|
+
const metadata = getCodexAppServerClientStartMetadata().get(params.client);
|
|
3833
|
+
if (!metadata?.desktopGeneration) return;
|
|
3834
|
+
const drain = createOlderDesktopGenerationDrainWait({
|
|
3835
|
+
generation: metadata.desktopGeneration,
|
|
3836
|
+
startOptions: metadata.startOptions,
|
|
3837
|
+
agentDir: metadata.agentDir
|
|
3838
|
+
});
|
|
3839
|
+
try {
|
|
3840
|
+
await withCodexAppServerAcquireDeadline(params.timeoutMs ?? 0, drain.promise, params.signal, "Codex Computer Use install timed out waiting for older desktop clients");
|
|
3841
|
+
} finally {
|
|
3842
|
+
drain.cancel();
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3218
3845
|
/** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
|
|
3219
3846
|
function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
|
|
3220
3847
|
const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
|
|
@@ -3245,6 +3872,7 @@ function isCodexAppServerStartSelectionChangedError(error) {
|
|
|
3245
3872
|
function assertCodexAppServerClientStartSelectionCurrent(params) {
|
|
3246
3873
|
const metadata = getCodexAppServerClientStartMetadata().get(params.client);
|
|
3247
3874
|
if (!metadata) return;
|
|
3875
|
+
if (metadata.desktopGeneration && !isCodexDesktopGenerationCurrent(metadata.desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
|
|
3248
3876
|
const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
|
|
3249
3877
|
if (requestedStartOptions.commandSource !== "managed") return;
|
|
3250
3878
|
const current = resolveCodexAppServerStartOptionsForAgent({
|
|
@@ -3256,7 +3884,7 @@ function assertCodexAppServerClientStartSelectionCurrent(params) {
|
|
|
3256
3884
|
/** Resolves the per-CODEX_HOME key used to serialize native config loading. */
|
|
3257
3885
|
function resolveCodexNativeConfigFenceKey(params) {
|
|
3258
3886
|
const metadata = params.client ? getCodexAppServerClientStartMetadata().get(params.client) : void 0;
|
|
3259
|
-
const startOptions =
|
|
3887
|
+
const startOptions = metadata?.startOptions ?? params.startOptions;
|
|
3260
3888
|
if (!startOptions || startOptions.transport !== "stdio") return;
|
|
3261
3889
|
const configuredHome = startOptions.env?.CODEX_HOME?.trim();
|
|
3262
3890
|
const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
|
|
@@ -3270,6 +3898,7 @@ function inferAuthRequirement(preparedAuth) {
|
|
|
3270
3898
|
async function resolveCodexAppServerClientStartContext(options) {
|
|
3271
3899
|
const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
|
|
3272
3900
|
const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
|
|
3901
|
+
const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions, options?.pluginConfig) ? await waitForCodexDesktopGeneration() : void 0;
|
|
3273
3902
|
const preparedAuth = options?.preparedAuth;
|
|
3274
3903
|
const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
|
|
3275
3904
|
if (preparedAuth && options?.authProfileId !== void 0) throw new Error("Prepared Codex auth cannot also select a legacy auth profile.");
|
|
@@ -3328,9 +3957,16 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
3328
3957
|
config: options?.config,
|
|
3329
3958
|
pluginConfig: options?.pluginConfig,
|
|
3330
3959
|
...authProfileStore ? { authProfileStore } : {}
|
|
3331
|
-
})
|
|
3960
|
+
}),
|
|
3961
|
+
...options?.pluginConfig !== void 0 ? { pluginConfig: options.pluginConfig } : {},
|
|
3962
|
+
...desktopGeneration ? { desktopGeneration } : {}
|
|
3332
3963
|
};
|
|
3333
3964
|
}
|
|
3965
|
+
function shouldTrackDesktopGeneration(startOptions, pluginConfig) {
|
|
3966
|
+
if (startOptions.transport !== "stdio") return false;
|
|
3967
|
+
if (resolveCodexComputerUseConfig({ pluginConfig }).enabled && (startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed")) return true;
|
|
3968
|
+
return startOptions.commandSource === "managed" && (startOptions.managedCommandOrder ?? "package-first") === "desktop-first";
|
|
3969
|
+
}
|
|
3334
3970
|
function resolveWarmSharedCodexAppServerClientIdentity(options) {
|
|
3335
3971
|
if (!options?.config || typeof options.config !== "object" || !options.startOptions || options.pluginConfig !== void 0 || options.authProfileStore !== void 0 || options.preparedAuth !== void 0 || options.runtimeArtifactMode !== void 0 || options.expectedRuntimeArtifact !== void 0) return;
|
|
3336
3972
|
const authProfileSelector = options.authProfileId === null ? ["native"] : options.authProfileId === void 0 ? ["implicit"] : ["profile", options.authProfileId];
|
|
@@ -3438,9 +4074,19 @@ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
|
|
|
3438
4074
|
}
|
|
3439
4075
|
async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
3440
4076
|
if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
4077
|
+
const acquireStartedAt = Date.now();
|
|
4078
|
+
const timeoutMs = options?.timeoutMs ?? 0;
|
|
3441
4079
|
const state = getSharedCodexAppServerClientState();
|
|
3442
4080
|
const warmIdentity = resolveWarmSharedCodexAppServerClientIdentity(options);
|
|
3443
|
-
|
|
4081
|
+
let warmClient = warmIdentity ? readWarmSharedCodexAppServerClient(state, warmIdentity) : void 0;
|
|
4082
|
+
const warmGeneration = warmClient ? getCodexAppServerClientStartMetadata().get(warmClient.client)?.desktopGeneration : void 0;
|
|
4083
|
+
if (warmClient && warmGeneration && !isCodexDesktopGenerationCurrent(warmGeneration)) {
|
|
4084
|
+
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), options?.abandonSignal);
|
|
4085
|
+
if (!isCodexDesktopGenerationCurrent(warmGeneration)) {
|
|
4086
|
+
retireSharedCodexAppServerClientIfCurrent(warmClient.client);
|
|
4087
|
+
warmClient = void 0;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
3444
4090
|
if (warmClient) {
|
|
3445
4091
|
options?.onStartedClient?.(warmClient.client);
|
|
3446
4092
|
const release = leaseOptions?.leased ? retainSharedClientEntry(warmClient.entry) : void 0;
|
|
@@ -3449,9 +4095,7 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3449
4095
|
release
|
|
3450
4096
|
} : { client: warmClient.client };
|
|
3451
4097
|
}
|
|
3452
|
-
const
|
|
3453
|
-
const timeoutMs = options?.timeoutMs ?? 0;
|
|
3454
|
-
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
4098
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
3455
4099
|
const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
|
|
3456
4100
|
const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authRequirement === "api-key" && !authProfileId ? resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }) : void 0);
|
|
3457
4101
|
const baseKey = `${codexAppServerStartOptionsKey(startOptions, {
|
|
@@ -3459,12 +4103,18 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3459
4103
|
authBindingFingerprint: options?.authBindingFingerprint,
|
|
3460
4104
|
agentDir: usesNativeAuth ? void 0 : agentDir,
|
|
3461
4105
|
fallbackApiKeyCacheKey: authIdentityCacheKey
|
|
3462
|
-
})}\0auth-requirement:${authRequirement ?? "native"}`;
|
|
4106
|
+
})}\0auth-requirement:${authRequirement ?? "native"}${desktopGeneration ? `\0desktop-generation:${desktopGeneration.epoch}` : ""}`;
|
|
3463
4107
|
const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
|
|
3464
4108
|
const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
|
|
3465
4109
|
const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
|
|
3466
|
-
|
|
3467
|
-
|
|
4110
|
+
let entry = getOrCreateSharedClientEntry(state, key);
|
|
4111
|
+
const existingClient = entry.client;
|
|
4112
|
+
const existingGeneration = existingClient ? getCodexAppServerClientStartMetadata().get(existingClient)?.desktopGeneration : void 0;
|
|
4113
|
+
if (existingClient && existingGeneration && !isCodexDesktopGenerationCurrent(existingGeneration)) {
|
|
4114
|
+
retireSharedCodexAppServerClientIfCurrent(existingClient);
|
|
4115
|
+
entry = getOrCreateSharedClientEntry(state, key);
|
|
4116
|
+
}
|
|
4117
|
+
entry.startupAbort ??= new AbortController();
|
|
3468
4118
|
entry.closeWhenIdle = false;
|
|
3469
4119
|
const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
|
|
3470
4120
|
const startedCallback = options?.onStartedClient;
|
|
@@ -3491,6 +4141,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3491
4141
|
key,
|
|
3492
4142
|
requestedStartOptions,
|
|
3493
4143
|
startOptions,
|
|
4144
|
+
desktopGeneration,
|
|
4145
|
+
...pluginConfig !== void 0 ? { pluginConfig } : {},
|
|
3494
4146
|
agentDir,
|
|
3495
4147
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3496
4148
|
authProfileStore,
|
|
@@ -3498,7 +4150,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3498
4150
|
authRequirement,
|
|
3499
4151
|
runtimeArtifactMode,
|
|
3500
4152
|
...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
|
|
3501
|
-
runtimeArtifactSignal: entry.
|
|
4153
|
+
runtimeArtifactSignal: entry.startupAbort.signal,
|
|
4154
|
+
abandonSignal: entry.startupAbort.signal,
|
|
3502
4155
|
config: options?.config
|
|
3503
4156
|
}));
|
|
3504
4157
|
try {
|
|
@@ -3557,6 +4210,8 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3557
4210
|
const ready = startInitializedCodexAppServerClient({
|
|
3558
4211
|
requestedStartOptions: params.requestedStartOptions,
|
|
3559
4212
|
startOptions: params.startOptions,
|
|
4213
|
+
...params.desktopGeneration ? { desktopGeneration: params.desktopGeneration } : {},
|
|
4214
|
+
...params.pluginConfig !== void 0 ? { pluginConfig: params.pluginConfig } : {},
|
|
3560
4215
|
agentDir: params.agentDir,
|
|
3561
4216
|
authProfileId: params.authProfileId,
|
|
3562
4217
|
authProfileStore: params.authProfileStore,
|
|
@@ -3565,13 +4220,17 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3565
4220
|
runtimeArtifactMode: params.runtimeArtifactMode,
|
|
3566
4221
|
...params.expectedRuntimeArtifact ? { expectedRuntimeArtifact: params.expectedRuntimeArtifact } : {},
|
|
3567
4222
|
runtimeArtifactSignal: params.runtimeArtifactSignal,
|
|
4223
|
+
abandonSignal: params.abandonSignal,
|
|
3568
4224
|
config: params.config,
|
|
3569
4225
|
onStartedClient: (startedClient) => {
|
|
3570
4226
|
const state = getSharedCodexAppServerClientState();
|
|
3571
4227
|
params.entry.client = startedClient;
|
|
3572
4228
|
state.liveClients.add(startedClient);
|
|
4229
|
+
startedClient.addTransportExitHandler((exitedClient) => {
|
|
4230
|
+
state.liveClients.delete(exitedClient);
|
|
4231
|
+
notifyDesktopGenerationDrainChecks(state);
|
|
4232
|
+
});
|
|
3573
4233
|
startedClient.addCloseHandler((closedClient) => {
|
|
3574
|
-
state.liveClients.delete(closedClient);
|
|
3575
4234
|
if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
|
|
3576
4235
|
});
|
|
3577
4236
|
for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
|
|
@@ -3587,6 +4246,7 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3587
4246
|
initialized.reject(error);
|
|
3588
4247
|
throw error;
|
|
3589
4248
|
});
|
|
4249
|
+
initialized.promise.catch(() => void 0);
|
|
3590
4250
|
ready.catch(() => void 0);
|
|
3591
4251
|
return {
|
|
3592
4252
|
initialized: initialized.promise,
|
|
@@ -3598,10 +4258,12 @@ async function createIsolatedCodexAppServerClient(options) {
|
|
|
3598
4258
|
if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
3599
4259
|
const acquireStartedAt = Date.now();
|
|
3600
4260
|
const timeoutMs = options?.timeoutMs ?? 0;
|
|
3601
|
-
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
4261
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
3602
4262
|
return await startInitializedCodexAppServerClient({
|
|
3603
4263
|
requestedStartOptions,
|
|
3604
4264
|
startOptions,
|
|
4265
|
+
...desktopGeneration ? { desktopGeneration } : {},
|
|
4266
|
+
...pluginConfig !== void 0 ? { pluginConfig } : {},
|
|
3605
4267
|
agentDir,
|
|
3606
4268
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3607
4269
|
authProfileStore,
|
|
@@ -3613,7 +4275,18 @@ async function createIsolatedCodexAppServerClient(options) {
|
|
|
3613
4275
|
config: options?.config,
|
|
3614
4276
|
timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
|
|
3615
4277
|
abandonSignal: options?.abandonSignal,
|
|
3616
|
-
onStartedClient:
|
|
4278
|
+
onStartedClient: (client) => {
|
|
4279
|
+
trackIsolatedCodexAppServerClient(client);
|
|
4280
|
+
options?.onStartedClient?.(client);
|
|
4281
|
+
}
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
function trackIsolatedCodexAppServerClient(client) {
|
|
4285
|
+
const state = getSharedCodexAppServerClientState();
|
|
4286
|
+
state.isolatedClients.add(client);
|
|
4287
|
+
client.addTransportExitHandler((exitedClient) => {
|
|
4288
|
+
state.isolatedClients.delete(exitedClient);
|
|
4289
|
+
notifyDesktopGenerationDrainChecks(state);
|
|
3617
4290
|
});
|
|
3618
4291
|
}
|
|
3619
4292
|
async function startInitializedCodexAppServerClient(params) {
|
|
@@ -3621,6 +4294,40 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3621
4294
|
const timeoutMs = params.timeoutMs ?? 0;
|
|
3622
4295
|
const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
|
|
3623
4296
|
for (const [index, startOptions] of startOptionsCandidates.entries()) {
|
|
4297
|
+
const desktopGeneration = params.desktopGeneration ?? (isManagedCodexDesktopCommand(startOptions.command) ? await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), params.abandonSignal) : void 0);
|
|
4298
|
+
const assertDesktopGenerationCurrent = () => {
|
|
4299
|
+
if (params.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
4300
|
+
if (desktopGeneration && !isCodexDesktopGenerationCurrent(desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
|
|
4301
|
+
};
|
|
4302
|
+
const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
|
|
4303
|
+
const ownsIsolatedCodexHome = params.requestedStartOptions.homeScope !== "user" && !params.requestedStartOptions.env?.CODEX_HOME?.trim();
|
|
4304
|
+
const artifactDrain = desktopGeneration && ownsIsolatedCodexHome && computerUseConfig.enabled && (computerUseConfig.autoInstall || computerUseConfig.pluginCacheMode === "shared") ? createOlderDesktopGenerationDrainWait({
|
|
4305
|
+
generation: desktopGeneration,
|
|
4306
|
+
startOptions,
|
|
4307
|
+
agentDir: params.agentDir
|
|
4308
|
+
}) : void 0;
|
|
4309
|
+
try {
|
|
4310
|
+
if (artifactDrain) await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), artifactDrain.promise, params.abandonSignal);
|
|
4311
|
+
await reconcileCodexComputerUseStartArtifacts({
|
|
4312
|
+
startOptions,
|
|
4313
|
+
agentDir: params.agentDir,
|
|
4314
|
+
pluginConfig: params.pluginConfig,
|
|
4315
|
+
...desktopGeneration ? { desktopGeneration } : {},
|
|
4316
|
+
assertCurrent: assertDesktopGenerationCurrent,
|
|
4317
|
+
ownsIsolatedCodexHome
|
|
4318
|
+
});
|
|
4319
|
+
} catch (error) {
|
|
4320
|
+
if (isCodexComputerUseCandidateArtifactsUnavailableError(error)) {
|
|
4321
|
+
if (index + 1 < startOptionsCandidates.length) continue;
|
|
4322
|
+
throw new AgentHarnessPreflightError("Codex Computer Use artifacts are unavailable from the installed desktop apps.", {
|
|
4323
|
+
cause: error,
|
|
4324
|
+
scope: "harness"
|
|
4325
|
+
});
|
|
4326
|
+
}
|
|
4327
|
+
throw error;
|
|
4328
|
+
} finally {
|
|
4329
|
+
artifactDrain?.cancel();
|
|
4330
|
+
}
|
|
3624
4331
|
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BvyT1uyd.js") : void 0;
|
|
3625
4332
|
const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
3626
4333
|
const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
|
|
@@ -3632,7 +4339,16 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3632
4339
|
if (index + 1 < startOptionsCandidates.length) continue;
|
|
3633
4340
|
throw new Error("Codex app-server runtime artifact does not match verified inference");
|
|
3634
4341
|
}
|
|
4342
|
+
assertDesktopGenerationCurrent();
|
|
3635
4343
|
const client = CodexAppServerClient.start(startOptions);
|
|
4344
|
+
const nativeCommandAtStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
4345
|
+
getCodexAppServerClientStartMetadata().set(client, {
|
|
4346
|
+
requestedStartOptions: params.requestedStartOptions,
|
|
4347
|
+
startOptions,
|
|
4348
|
+
agentDir: params.agentDir,
|
|
4349
|
+
...nativeCommandAtStart ? { nativeCommand: nativeCommandAtStart } : {},
|
|
4350
|
+
...desktopGeneration ? { desktopGeneration } : {}
|
|
4351
|
+
});
|
|
3636
4352
|
params.onStartedClient?.(client);
|
|
3637
4353
|
let initialize;
|
|
3638
4354
|
try {
|
|
@@ -3643,6 +4359,12 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3643
4359
|
if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
|
|
3644
4360
|
throw error;
|
|
3645
4361
|
}
|
|
4362
|
+
try {
|
|
4363
|
+
assertDesktopGenerationCurrent();
|
|
4364
|
+
} catch (error) {
|
|
4365
|
+
client.close();
|
|
4366
|
+
throw error;
|
|
4367
|
+
}
|
|
3646
4368
|
params.onInitializedClient?.();
|
|
3647
4369
|
let runtimeArtifact;
|
|
3648
4370
|
try {
|
|
@@ -3669,6 +4391,7 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3669
4391
|
config: params.config
|
|
3670
4392
|
});
|
|
3671
4393
|
try {
|
|
4394
|
+
assertDesktopGenerationCurrent();
|
|
3672
4395
|
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), applyCodexAppServerAuthProfile({
|
|
3673
4396
|
client,
|
|
3674
4397
|
agentDir: params.agentDir,
|
|
@@ -3679,14 +4402,8 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3679
4402
|
config: params.config,
|
|
3680
4403
|
...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
|
|
3681
4404
|
}), params.abandonSignal);
|
|
3682
|
-
const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
3683
4405
|
if (runtimeArtifactModule && runtimeArtifact) runtimeArtifactModule.bindCodexAppServerRuntimeArtifact(client, runtimeArtifact);
|
|
3684
|
-
|
|
3685
|
-
requestedStartOptions: params.requestedStartOptions,
|
|
3686
|
-
startOptions,
|
|
3687
|
-
agentDir: params.agentDir,
|
|
3688
|
-
...nativeCommand ? { nativeCommand } : {}
|
|
3689
|
-
});
|
|
4406
|
+
assertDesktopGenerationCurrent();
|
|
3690
4407
|
const fenceKey = resolveCodexNativeConfigFenceKey({ client });
|
|
3691
4408
|
if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
|
|
3692
4409
|
const release = await acquireCodexNativeConfigFence(fenceKey, options);
|
|
@@ -3706,6 +4423,9 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3706
4423
|
}
|
|
3707
4424
|
throw new Error("Managed Codex app-server fallback candidates were exhausted.");
|
|
3708
4425
|
}
|
|
4426
|
+
function isCodexComputerUseCandidateArtifactsUnavailableError(error) {
|
|
4427
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
|
|
4428
|
+
}
|
|
3709
4429
|
function resolveManagedFallbackStartOptions(startOptions) {
|
|
3710
4430
|
const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
|
|
3711
4431
|
const candidates = [];
|
|
@@ -3797,6 +4517,62 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
|
|
|
3797
4517
|
};
|
|
3798
4518
|
}
|
|
3799
4519
|
}
|
|
4520
|
+
/** Gracefully retires exact clients attached to an older desktop generation. */
|
|
4521
|
+
function retireSharedCodexAppServerClientsBeforeDesktopGeneration(generation) {
|
|
4522
|
+
const state = getSharedCodexAppServerClientState();
|
|
4523
|
+
for (const entry of state.clients.values()) {
|
|
4524
|
+
const client = entry.client;
|
|
4525
|
+
const attached = client ? getCodexAppServerClientStartMetadata().get(client) : void 0;
|
|
4526
|
+
if (client && attached?.desktopGeneration && attached.desktopGeneration.epoch < generation.epoch) retireSharedCodexAppServerClientIfCurrent(client);
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
function createOlderDesktopGenerationDrainWait(params) {
|
|
4530
|
+
const targetHome = resolveCodexNativeConfigFenceKey({
|
|
4531
|
+
startOptions: params.startOptions,
|
|
4532
|
+
agentDir: params.agentDir
|
|
4533
|
+
});
|
|
4534
|
+
if (!targetHome) return {
|
|
4535
|
+
promise: Promise.resolve(),
|
|
4536
|
+
cancel: () => void 0
|
|
4537
|
+
};
|
|
4538
|
+
const state = getSharedCodexAppServerClientState();
|
|
4539
|
+
let settled = false;
|
|
4540
|
+
let resolveWait;
|
|
4541
|
+
const promise = new Promise((resolve) => {
|
|
4542
|
+
resolveWait = resolve;
|
|
4543
|
+
});
|
|
4544
|
+
const cancel = () => {
|
|
4545
|
+
if (settled) return;
|
|
4546
|
+
settled = true;
|
|
4547
|
+
state.desktopGenerationDrainChecks.delete(check);
|
|
4548
|
+
resolveWait();
|
|
4549
|
+
};
|
|
4550
|
+
const check = () => {
|
|
4551
|
+
if (!hasLiveOlderDesktopGenerationClient({
|
|
4552
|
+
state,
|
|
4553
|
+
generation: params.generation,
|
|
4554
|
+
targetHome
|
|
4555
|
+
})) cancel();
|
|
4556
|
+
};
|
|
4557
|
+
state.desktopGenerationDrainChecks.add(check);
|
|
4558
|
+
check();
|
|
4559
|
+
return {
|
|
4560
|
+
promise,
|
|
4561
|
+
cancel
|
|
4562
|
+
};
|
|
4563
|
+
}
|
|
4564
|
+
function hasLiveOlderDesktopGenerationClient(params) {
|
|
4565
|
+
for (const client of params.state.liveClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
|
|
4566
|
+
for (const client of params.state.isolatedClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
|
|
4567
|
+
return false;
|
|
4568
|
+
}
|
|
4569
|
+
function isOlderDesktopGenerationClientForHome(client, generation, targetHome) {
|
|
4570
|
+
const metadata = getCodexAppServerClientStartMetadata().get(client);
|
|
4571
|
+
return Boolean(metadata?.desktopGeneration && metadata.desktopGeneration.epoch < generation.epoch && resolveCodexNativeConfigFenceKey({ client }) === targetHome);
|
|
4572
|
+
}
|
|
4573
|
+
function notifyDesktopGenerationDrainChecks(state) {
|
|
4574
|
+
for (const check of state.desktopGenerationDrainChecks) check();
|
|
4575
|
+
}
|
|
3800
4576
|
/** Clears a matching shared client and waits for its process to exit. */
|
|
3801
4577
|
async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
|
|
3802
4578
|
if (!client) return false;
|
|
@@ -3864,6 +4640,7 @@ function retainPendingSharedClientAcquire(entry) {
|
|
|
3864
4640
|
released = true;
|
|
3865
4641
|
entry.pendingAcquires = Math.max(0, entry.pendingAcquires - 1);
|
|
3866
4642
|
closeRetiredSharedClientEntryIfIdle(entry);
|
|
4643
|
+
notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
|
|
3867
4644
|
};
|
|
3868
4645
|
}
|
|
3869
4646
|
function retainSharedClientEntry(entry) {
|
|
@@ -3874,6 +4651,7 @@ function retainSharedClientEntry(entry) {
|
|
|
3874
4651
|
released = true;
|
|
3875
4652
|
entry.activeLeases = Math.max(0, entry.activeLeases - 1);
|
|
3876
4653
|
closeRetiredSharedClientEntryIfIdle(entry);
|
|
4654
|
+
notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
|
|
3877
4655
|
};
|
|
3878
4656
|
}
|
|
3879
4657
|
function closeRetiredSharedClientEntryIfIdle(entry) {
|
|
@@ -3901,7 +4679,7 @@ function closeSharedClientEntryIfUnclaimed(key, entry) {
|
|
|
3901
4679
|
}
|
|
3902
4680
|
function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
|
|
3903
4681
|
if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
|
|
3904
|
-
entry.
|
|
4682
|
+
entry.startupAbort?.abort(/* @__PURE__ */ new Error("Codex app-server startup was abandoned"));
|
|
3905
4683
|
entry.closeWhenIdle = true;
|
|
3906
4684
|
const state = getSharedCodexAppServerClientState();
|
|
3907
4685
|
if (state.clients.get(key) === entry) state.clients.delete(key);
|
|
@@ -3912,4 +4690,4 @@ function collectSharedClients(state) {
|
|
|
3912
4690
|
return [...state.liveClients];
|
|
3913
4691
|
}
|
|
3914
4692
|
//#endregion
|
|
3915
|
-
export {
|
|
4693
|
+
export { releaseCodexAppServerLiveThread as $, resolveCodexTurnAssistantCompletionIdleTimeoutMs as A, isCodexAppServerPrewriteRequestCancellationError as B, createCodexDesktopGenerationService as C, resolveCodexGatewayTimeoutWithGraceMs as D, isCodexAppServerStartupError as E, isCodexAppServerApprovalRequest as F, CodexAppServerRpcError as G, isUnsupportedCodexAppServerVersionError as H, isCodexAppServerBrokenPipeError as I, consumeCodexAppServerLiveThread as J, createCodexElicitationResponse as K, isCodexAppServerConnectionClosedError as L, resolveCodexTurnTerminalIdleTimeoutMs as M, withCodexStartupTimeout as N, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as O, getCodexAppServerClientInstanceId as P, protectCodexAppServerLiveThread as Q, isCodexAppServerIndeterminateRequestCancellationError as R, acquireCodexNativeConfigFence as S, resolveCodexAppServerLocalHomeDir as St, CodexAppServerStartupError as T, resolveCodexAppServerClientInstanceId as U, isCodexAppServerRequestTimeoutError as V, closeCodexAppServerTransportAndWait as W, isCodexAppServerClientRuntimeLive as X, ensureCodexAppServerClientRuntime as Y, isCodexAppServerLiveThreadClaimed as Z, retireSharedCodexAppServerClientIfCurrent as _, flattenCodexDynamicToolFunctions as _t, createIsolatedCodexAppServerClient as a, readRecentCodexRateLimits as at, waitForCodexAppServerClientDesktopGenerationDrain as b, assertNotSymlink as bt, isCodexAppServerStartSelectionChangedError as c, resolveCodexAppServerAuthAccountCacheKey as ct, readCodexAppServerClientProcessIdentity as d, resolveCodexAppServerFallbackApiKeyCacheKey as dt, retainCodexAppServerLiveThread as et, releaseCodexAppServerClientLease as f, resolveCodexAppServerPreparedApiKeyCacheKey as ft, retainSharedCodexAppServerClientIfCurrent as g, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as gt, retainSharedCodexAppServerClientByInstanceId as h, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as ht, clearSharedCodexAppServerClientIfCurrentAndWait as i, readCodexRateLimitsRevision as it, resolveCodexTurnCompletionIdleTimeoutMs as j, resolveCodexStartupTimeoutMs as k, readCodexAppServerClientDesktopGeneration as l, resolveCodexAppServerAuthProfileId as lt, resolveCodexNativeConfigFenceKey as m, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as mt, clearSharedCodexAppServerClientIfCurrent as n, withAbortableTimeout as nt, getLeasedSharedCodexAppServerClient as o, rememberCodexRateLimitsRead as ot, releaseLeasedSharedCodexAppServerClient as p, resolveCodexAppServerPreparedAuthHandoff as pt, claimCodexAppServerLiveThread as q, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as r, withTimeout$1 as rt, getSharedCodexAppServerClient as s, reconcileCodexComputerUseStartArtifacts as st, assertCodexAppServerClientStartSelectionCurrent as t, unsubscribeCodexAppServerLiveThread as tt, readCodexAppServerClientDesktopGenerationFingerprint as u, resolveCodexAppServerAuthProfileIdForAgent as ut, retireSharedCodexAppServerClientsBeforeDesktopGeneration as v, isJsonObject as vt, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as w, withLeasedCodexAppServerClientStartSelectionRetry as x, resolveCodexAppServerHomeDir as xt, shared_client_exports as y, resolveCodexManagedBundledMarketplacePath as yt, isCodexAppServerIndeterminateTransportError as z };
|