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