@openclaw/codex 2026.8.1-beta.2 → 2026.8.1-beta.3
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/app-server-policy-bg5QPSVP.js +25 -0
- package/dist/{attempt-client-cleanup-DMLXUfa8.js → attempt-client-cleanup-URjUlCHg.js} +1 -1
- package/dist/{binding-connection-I5Bo-AsX.js → binding-connection-DAvZwp96.js} +15 -3
- package/dist/{bounded-turn-qgZmzy_4.js → bounded-turn-4BjrZEwW.js} +22 -16
- package/dist/{capabilities-CybGI1oz.js → capabilities-CH3K57v4.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-B1AEz8ZA.js → command-formatters-D0nZ3qtd.js} +2 -2
- package/dist/{command-handlers-Db8pu_ds.js → command-handlers-DC86lgT4.js} +39 -22
- package/dist/command-rpc-xUIIonsO.js +90 -0
- package/dist/{compact-LspQaGfl.js → compact-COqgBRny.js} +35 -29
- package/dist/{session-binding-DPxIvMgE.js → config-CpNeo5AE.js} +28 -888
- package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
- package/dist/doctor-contract-api.js +15 -14
- package/dist/{dynamic-tools-BVdyWkUD.js → dynamic-tools-fJZhl4CE.js} +1284 -651
- package/dist/{effective-mcp-catalog-DIIhAtOZ.js → effective-mcp-catalog-4YKn51aG.js} +2 -2
- package/dist/elicitation-input-DMkeki8d.js +71 -0
- package/dist/{event-projector-usage-e5huIQWu.js → event-projector-usage-CLwNEJqW.js} +16 -16
- package/dist/harness-DoKQcF8n.js +258 -0
- package/dist/harness.js +2 -226
- package/dist/index.js +171 -48
- package/dist/{isolated-completion-C58NbL40.js → isolated-completion-DPMtlH9r.js} +3 -3
- package/dist/managed-binary-By4FZC6i.js +212 -0
- package/dist/media-understanding-provider.js +2 -2
- package/dist/model-catalog-CmGoqVKB.js +48 -0
- package/dist/{models-BFlpn1Ex.js → models-CZw1wClT.js} +2 -2
- package/dist/{native-subagent-monitor-DyTGmTUx.js → native-subagent-monitor-GGq3RnOo.js} +185 -71
- package/dist/node-exec-server.runtime-Bsu1LdFZ.js +192 -0
- package/dist/{plugin-app-cache-key-C3H2CVp6.js → plugin-app-cache-key-DMNQ8Zi8.js} +15 -3
- package/dist/plugin-approval-roundtrip-C75Yiiwc.js +95 -0
- package/dist/{plugin-marketplace-discovery-CzrGJdCR.js → plugin-marketplace-discovery-CSWDkMrP.js} +40 -26
- package/dist/{plugin-metadata-cache-CNJPj4_b.js → plugin-metadata-cache-BXH5NF-4.js} +8 -5
- package/dist/{protocol-validators-B4F7L8Rf.js → protocol-validators-VAVyEnDp.js} +55 -2
- package/dist/{rate-limits-DZXmOL8-.js → rate-limits-BI1dMHDG.js} +1 -1
- package/dist/{request-BRl5_UfX.js → request-DSK0W6YI.js} +2 -2
- package/dist/{run-attempt-P8BdxC1I.js → run-attempt-BV3HzuCf.js} +1098 -666
- package/dist/{runtime-artifact-BVSPdd6g.js → runtime-artifact-BvyT1uyd.js} +4 -7
- package/dist/{sandbox-guard-Cmwc9eIk.js → sandbox-guard-C1qEeZ5C.js} +25 -19
- package/dist/session-binding-BTrLf9Qm.js +874 -0
- package/dist/{session-catalog-xwNi_SDM.js → session-catalog-ezP_UcRM.js} +1541 -1296
- package/dist/session-catalog-home-id-BNwAJ9D7.js +18 -0
- package/dist/{session-cli-7hgXZDV7.js → session-cli-B1hKvfkI.js} +20 -7
- package/dist/{session-retirement-DoMMJFKm.js → session-retirement-CE4iMiLh.js} +3 -3
- package/dist/{session-upstream-marker-CrIFX06J.js → session-upstream-marker-D_2npHlA.js} +1 -1
- package/dist/{settled-turn-finalizer-BRWBO4MO.js → settled-turn-finalizer-Dx1onIqg.js} +23 -19
- package/dist/{shared-client-kIBbGjk4.js → shared-client-Dw-rzii0.js} +755 -273
- package/dist/{side-question-9HAL26l9.js → side-question-DbXkJZzg.js} +52 -30
- package/dist/{thread-lifecycle-Bu1BTDXr.js → thread-lifecycle-DKdrnTMX.js} +310 -92
- package/dist/{thread-ownership-6kOQHlyC.js → thread-ownership-i2NBwDUL.js} +2 -2
- package/dist/{thread-resume-CPrJNQVM.js → thread-resume-CdJk2Ehf.js} +3 -3
- package/dist/{transcript-mirror-B9gGgnJS.js → transcript-mirror-BU0_LKAL.js} +46 -28
- 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-BDVJCLAD.js} +12 -6
- package/dist/{usage-CHlV0e-o.js → usage-9dKJCgcy.js} +3 -3
- package/dist/{web-search-provider.runtime-DRw2Vl42.js → web-search-provider.runtime-DCdx4DGu.js} +3 -3
- package/openclaw.plugin.json +31 -1
- package/package.json +6 -5
- package/dist/app-server-policy-zcBDc1JU.js +0 -49
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey, x as resolveCodexAppServerUserHomeDir } from "./config-CpNeo5AE.js";
|
|
3
|
+
import { o as isCodexAppServerNativeAuthProfile } from "./session-binding-BTrLf9Qm.js";
|
|
4
|
+
import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DhoGy33Z.js";
|
|
5
|
+
import { a as CODEX_APP_SERVER_VERSION, n as resolveManagedCodexAppServerStartOptions, o as MIN_SUPPORTED_CODEX_APP_SERVER_VERSION, r as resolveManagedCodexNativeCommand } from "./managed-binary-By4FZC6i.js";
|
|
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 } 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
17
|
import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
|
|
17
18
|
import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
|
|
19
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
18
20
|
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
21
|
+
import { assertNoSymlinkParents, withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
22
|
+
import { execFile } from "node:child_process";
|
|
19
23
|
import { createInterface } from "node:readline";
|
|
20
24
|
import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
|
21
25
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
26
|
+
import { parse } from "semver";
|
|
22
27
|
import { EventEmitter } from "node:events";
|
|
23
28
|
import net from "node:net";
|
|
24
29
|
import { PassThrough, Writable } from "node:stream";
|
|
25
30
|
import { StringDecoder } from "node:string_decoder";
|
|
26
31
|
import WebSocket from "ws";
|
|
27
|
-
import { fileURLToPath } from "node:url";
|
|
28
|
-
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
29
|
-
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
30
32
|
//#region extensions/codex/src/app-server/auth-start-options.ts
|
|
31
33
|
const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
|
|
32
34
|
const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
|
|
@@ -179,108 +181,385 @@ function skippedCacheResult(status, message) {
|
|
|
179
181
|
};
|
|
180
182
|
}
|
|
181
183
|
//#endregion
|
|
184
|
+
//#region extensions/codex/src/app-server/computer-use-service-path.ts
|
|
185
|
+
/** Filesystem ownership guards for isolated Computer Use service provisioning. */
|
|
186
|
+
async function assertOwnedServicePath(params) {
|
|
187
|
+
await assertOwnedCodexHomePath({
|
|
188
|
+
ownershipRoot: params.ownershipRoot,
|
|
189
|
+
codexHome: params.codexHome,
|
|
190
|
+
allowMissing: true
|
|
191
|
+
});
|
|
192
|
+
assertPathAtOrInside(params.ownershipRoot, params.targetParent, "Computer Use service parent");
|
|
193
|
+
await assertNoSymlinkParents({
|
|
194
|
+
rootDir: params.ownershipRoot,
|
|
195
|
+
targetPath: params.targetParent,
|
|
196
|
+
allowMissing: true,
|
|
197
|
+
requireDirectories: true,
|
|
198
|
+
messagePrefix: "Computer Use service path"
|
|
199
|
+
});
|
|
200
|
+
await assertNotSymlink(params.targetPath, "Computer Use service target");
|
|
201
|
+
}
|
|
202
|
+
async function ensureOwnedCodexHome(codexHomeInput, ownershipRootInput = path.dirname(path.resolve(codexHomeInput))) {
|
|
203
|
+
const codexHome = path.resolve(codexHomeInput);
|
|
204
|
+
const ownershipRoot = path.resolve(ownershipRootInput);
|
|
205
|
+
await fs.mkdir(ownershipRoot, {
|
|
206
|
+
recursive: true,
|
|
207
|
+
mode: 448
|
|
208
|
+
});
|
|
209
|
+
await assertOwnedCodexHomePath({
|
|
210
|
+
ownershipRoot,
|
|
211
|
+
codexHome,
|
|
212
|
+
allowMissing: true
|
|
213
|
+
});
|
|
214
|
+
await ensureRealDirectoryTree(ownershipRoot, codexHome, "isolated Codex home");
|
|
215
|
+
await assertOwnedCodexHomePath({
|
|
216
|
+
ownershipRoot,
|
|
217
|
+
codexHome,
|
|
218
|
+
allowMissing: false
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async function prepareOwnedServiceParent(params) {
|
|
222
|
+
await ensureOwnedCodexHome(params.codexHome, params.ownershipRoot);
|
|
223
|
+
await ensureRealDirectoryTree(params.ownershipRoot, params.targetParent, "Computer Use service parent");
|
|
224
|
+
await assertNoSymlinkParents({
|
|
225
|
+
rootDir: params.ownershipRoot,
|
|
226
|
+
targetPath: params.targetParent,
|
|
227
|
+
allowMissing: false,
|
|
228
|
+
requireDirectories: true,
|
|
229
|
+
messagePrefix: "Computer Use service path"
|
|
230
|
+
});
|
|
231
|
+
const [rootIdentity, parentIdentity] = await Promise.all([readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root"), readRealDirectoryIdentity(params.targetParent, "Computer Use service parent")]);
|
|
232
|
+
assertPathAtOrInside(rootIdentity.realPath, parentIdentity.realPath, "canonical Computer Use service parent");
|
|
233
|
+
return parentIdentity;
|
|
234
|
+
}
|
|
235
|
+
async function assertOwnedCodexHomePath(params) {
|
|
236
|
+
await readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root");
|
|
237
|
+
assertPathAtOrInside(params.ownershipRoot, params.codexHome, "isolated Codex home");
|
|
238
|
+
await assertNoSymlinkParents({
|
|
239
|
+
rootDir: params.ownershipRoot,
|
|
240
|
+
targetPath: params.codexHome,
|
|
241
|
+
allowMissing: params.allowMissing,
|
|
242
|
+
requireDirectories: true,
|
|
243
|
+
messagePrefix: "Computer Use service path"
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
async function readRealDirectoryIdentity(directoryPath, label) {
|
|
247
|
+
const logicalPath = path.resolve(directoryPath);
|
|
248
|
+
const before = await fs.lstat(logicalPath);
|
|
249
|
+
if (before.isSymbolicLink() || !before.isDirectory()) throw new Error(`${label} must be a real directory: ${logicalPath}`);
|
|
250
|
+
const realPath = await fs.realpath(logicalPath);
|
|
251
|
+
const [after, resolved] = await Promise.all([fs.lstat(logicalPath), fs.lstat(realPath)]);
|
|
252
|
+
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.`);
|
|
253
|
+
return {
|
|
254
|
+
logicalPath,
|
|
255
|
+
realPath,
|
|
256
|
+
dev: after.dev,
|
|
257
|
+
ino: after.ino
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async function assertOwnedServiceParentStable(parent) {
|
|
261
|
+
await assertDirectoryIdentityStable(parent, "Computer Use service parent");
|
|
262
|
+
}
|
|
263
|
+
async function assertDirectoryIdentityStable(expected, label) {
|
|
264
|
+
if (!await directoryIdentityIsStable(expected)) throw new Error(`${label} changed during refresh; refusing to mutate the replacement path.`);
|
|
265
|
+
}
|
|
266
|
+
async function ownedServiceParentIsStable(parent) {
|
|
267
|
+
return await directoryIdentityIsStable(parent);
|
|
268
|
+
}
|
|
269
|
+
async function directoryIdentityIsStable(expected) {
|
|
270
|
+
try {
|
|
271
|
+
const current = await fs.lstat(expected.logicalPath);
|
|
272
|
+
if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) return false;
|
|
273
|
+
return await fs.realpath(expected.logicalPath) === expected.realPath;
|
|
274
|
+
} catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function assertNotSymlink(filePath, label) {
|
|
279
|
+
try {
|
|
280
|
+
if ((await fs.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
|
|
287
|
+
const root = path.resolve(ownershipRoot);
|
|
288
|
+
const target = path.resolve(directoryPath);
|
|
289
|
+
assertPathAtOrInside(root, target, label);
|
|
290
|
+
const relative = path.relative(root, target);
|
|
291
|
+
let current = root;
|
|
292
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
293
|
+
current = path.join(current, segment);
|
|
294
|
+
const existing = await fs.lstat(current).catch((error) => {
|
|
295
|
+
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
296
|
+
throw error;
|
|
297
|
+
});
|
|
298
|
+
if (!existing) {
|
|
299
|
+
try {
|
|
300
|
+
await fs.mkdir(current, { mode: 448 });
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (!hasNodeErrorCode(error, "EEXIST")) throw error;
|
|
303
|
+
}
|
|
304
|
+
const created = await fs.lstat(current);
|
|
305
|
+
if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
|
|
306
|
+
} else if (existing.isSymbolicLink() || !existing.isDirectory()) throw new Error(`${label} must traverse real directories: ${current}`);
|
|
307
|
+
}
|
|
308
|
+
await assertNoSymlinkParents({
|
|
309
|
+
rootDir: root,
|
|
310
|
+
targetPath: target,
|
|
311
|
+
allowMissing: false,
|
|
312
|
+
requireDirectories: true,
|
|
313
|
+
messagePrefix: "Computer Use service path"
|
|
314
|
+
});
|
|
315
|
+
await readRealDirectoryIdentity(target, label);
|
|
316
|
+
}
|
|
317
|
+
function assertPathAtOrInside(rootPath, candidatePath, label) {
|
|
318
|
+
const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
|
|
319
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
|
|
320
|
+
}
|
|
321
|
+
function hasNodeErrorCode(error, code) {
|
|
322
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
182
325
|
//#region extensions/codex/src/app-server/computer-use-service.ts
|
|
183
326
|
/** Native Computer Use service provisioning for isolated Codex homes. */
|
|
184
327
|
const SERVICE_APP_NAME = "Codex Computer Use.app";
|
|
185
|
-
const
|
|
328
|
+
const SERVICE_BUNDLE_ID = "com.openai.sky.CUAService";
|
|
329
|
+
const CLIENT_BUNDLE_ID = "com.openai.sky.CUAService.cli";
|
|
330
|
+
const OPENAI_TEAM_ID = "2DC432GLL2";
|
|
331
|
+
const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app");
|
|
332
|
+
const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
|
|
186
333
|
const COPY_TIMEOUT_MS = 12e4;
|
|
334
|
+
const INSPECT_TIMEOUT_MS = 3e4;
|
|
335
|
+
const COMPLETED_SYNC_CACHE_MAX_ENTRIES = 64;
|
|
187
336
|
const activeInstalls = /* @__PURE__ */ new Map();
|
|
188
|
-
|
|
337
|
+
const completedSyncs = /* @__PURE__ */ new Map();
|
|
338
|
+
/** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
|
|
189
339
|
async function ensureCodexComputerUseServiceApp(params) {
|
|
190
340
|
const platform = params.platform ?? process.platform;
|
|
191
341
|
if (platform !== "darwin") return {
|
|
192
342
|
status: "unsupported",
|
|
193
343
|
changed: false
|
|
194
344
|
};
|
|
195
|
-
const
|
|
345
|
+
const codexHome = path.resolve(params.codexHome);
|
|
346
|
+
const ownershipRoot = path.resolve(params.ownershipRoot ?? path.dirname(codexHome));
|
|
347
|
+
const targetParent = path.join(codexHome, "computer-use");
|
|
348
|
+
const targetPath = path.join(targetParent, SERVICE_APP_NAME);
|
|
349
|
+
await ensureOwnedCodexHome(codexHome, ownershipRoot);
|
|
350
|
+
await assertOwnedServicePath({
|
|
351
|
+
ownershipRoot,
|
|
352
|
+
codexHome,
|
|
353
|
+
targetParent,
|
|
354
|
+
targetPath
|
|
355
|
+
});
|
|
356
|
+
const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
|
|
357
|
+
const syncKey = [targetPath, ...candidates].join("\0");
|
|
358
|
+
const completed = completedSyncs.get(targetPath);
|
|
359
|
+
if (completed?.syncKey === syncKey) {
|
|
360
|
+
completedSyncs.delete(targetPath);
|
|
361
|
+
completedSyncs.set(targetPath, completed);
|
|
362
|
+
return {
|
|
363
|
+
status: "already_current",
|
|
364
|
+
changed: false,
|
|
365
|
+
targetPath: completed.targetPath,
|
|
366
|
+
sourcePath: completed.sourcePath,
|
|
367
|
+
sourceBuild: completed.sourceBuild
|
|
368
|
+
};
|
|
369
|
+
}
|
|
196
370
|
const active = activeInstalls.get(targetPath);
|
|
197
|
-
if (active)
|
|
371
|
+
if (active) {
|
|
372
|
+
if (active.syncKey === syncKey) return await active.promise;
|
|
373
|
+
await active.promise.catch(() => void 0);
|
|
374
|
+
return await ensureCodexComputerUseServiceApp(params);
|
|
375
|
+
}
|
|
376
|
+
if (completed) completedSyncs.delete(targetPath);
|
|
198
377
|
const install = ensureCodexComputerUseServiceAppOnce({
|
|
199
378
|
...params,
|
|
379
|
+
codexHome,
|
|
380
|
+
ownershipRoot,
|
|
381
|
+
targetParent,
|
|
200
382
|
targetPath,
|
|
201
|
-
platform
|
|
383
|
+
platform,
|
|
384
|
+
sourceAppCandidates: candidates
|
|
385
|
+
}).then((result) => {
|
|
386
|
+
if (isCompletedSyncStatus(result.status)) {
|
|
387
|
+
completedSyncs.delete(targetPath);
|
|
388
|
+
completedSyncs.set(targetPath, {
|
|
389
|
+
syncKey,
|
|
390
|
+
targetPath: result.targetPath,
|
|
391
|
+
sourcePath: result.sourcePath,
|
|
392
|
+
sourceBuild: result.sourceBuild
|
|
393
|
+
});
|
|
394
|
+
pruneMapToMaxSize(completedSyncs, COMPLETED_SYNC_CACHE_MAX_ENTRIES);
|
|
395
|
+
}
|
|
396
|
+
return result;
|
|
202
397
|
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
398
|
+
const activeEntry = {
|
|
399
|
+
syncKey,
|
|
400
|
+
promise: install
|
|
401
|
+
};
|
|
402
|
+
activeInstalls.set(targetPath, activeEntry);
|
|
403
|
+
const clearActive = () => {
|
|
404
|
+
if (activeInstalls.get(targetPath) === activeEntry) activeInstalls.delete(targetPath);
|
|
405
|
+
};
|
|
406
|
+
install.then(clearActive, clearActive);
|
|
407
|
+
return await install;
|
|
408
|
+
}
|
|
409
|
+
function isCompletedSyncStatus(status) {
|
|
410
|
+
return status === "installed" || status === "refreshed" || status === "already_current";
|
|
209
411
|
}
|
|
210
412
|
async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
211
|
-
|
|
212
|
-
|
|
413
|
+
const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
|
|
414
|
+
const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
|
|
415
|
+
if (!source) return {
|
|
416
|
+
status: "source_missing",
|
|
213
417
|
changed: false,
|
|
214
418
|
targetPath: params.targetPath
|
|
215
419
|
};
|
|
216
|
-
const sourcePath
|
|
217
|
-
|
|
218
|
-
|
|
420
|
+
const { path: sourcePath, identity: sourceIdentity } = source;
|
|
421
|
+
const ownedParent = await prepareOwnedServiceParent({
|
|
422
|
+
ownershipRoot: params.ownershipRoot,
|
|
423
|
+
codexHome: params.codexHome,
|
|
424
|
+
targetParent: params.targetParent
|
|
425
|
+
});
|
|
426
|
+
const operationTargetPath = path.join(ownedParent.realPath, SERVICE_APP_NAME);
|
|
427
|
+
await assertNotSymlink(operationTargetPath, "Computer Use service target");
|
|
428
|
+
const initialTarget = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
|
|
429
|
+
if (initialTarget.identity && identitiesMatch(initialTarget.identity, sourceIdentity)) return {
|
|
430
|
+
status: "already_current",
|
|
219
431
|
changed: false,
|
|
220
|
-
targetPath: params.targetPath
|
|
432
|
+
targetPath: params.targetPath,
|
|
433
|
+
sourcePath,
|
|
434
|
+
sourceBuild: sourceIdentity.build
|
|
221
435
|
};
|
|
222
|
-
|
|
223
|
-
await fs.
|
|
224
|
-
const
|
|
436
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
437
|
+
const stagingRoot = await fs.mkdtemp(path.join(ownedParent.realPath, ".service-app.staging-"));
|
|
438
|
+
const stagingRootIdentity = await readRealDirectoryIdentity(stagingRoot, "Computer Use service staging directory");
|
|
225
439
|
const stagedPath = path.join(stagingRoot, SERVICE_APP_NAME);
|
|
226
|
-
const backupPath = path.join(
|
|
440
|
+
const backupPath = path.join(ownedParent.realPath, `.service-app.backup-${process.pid}-${Date.now()}`);
|
|
227
441
|
let backupCreated = false;
|
|
228
442
|
try {
|
|
229
443
|
await (params.copyServiceApp ?? copyServiceAppWithDitto)(sourcePath, stagedPath);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
444
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
445
|
+
await assertDirectoryIdentityStable(stagingRootIdentity, "Computer Use service staging directory");
|
|
446
|
+
await assertNotSymlink(stagedPath, "Copied Computer Use service app");
|
|
447
|
+
const stagedIdentity = await inspectServiceApp(stagedPath);
|
|
448
|
+
if (!stagedIdentity || !identitiesMatch(stagedIdentity, sourceIdentity)) throw new Error(`Copied Computer Use service app at ${stagedPath} does not match its selected signed source.`);
|
|
449
|
+
const stagedSnapshot = await readServiceAppSnapshot(stagedPath, inspectServiceApp);
|
|
450
|
+
const currentSourceIdentity = await inspectServiceApp(sourcePath);
|
|
451
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
452
|
+
if (!currentSourceIdentity || !identitiesMatch(currentSourceIdentity, sourceIdentity)) throw new Error("Selected Computer Use service source changed during refresh.");
|
|
453
|
+
await assertNotSymlink(operationTargetPath, "Computer Use service target");
|
|
454
|
+
if (await pathExists$1(operationTargetPath)) {
|
|
455
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
456
|
+
await fs.rename(operationTargetPath, backupPath);
|
|
457
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
233
458
|
backupCreated = true;
|
|
234
|
-
|
|
235
|
-
|
|
459
|
+
const movedTarget = await readServiceAppSnapshot(backupPath, inspectServiceApp);
|
|
460
|
+
if (movedTarget.identity && identitiesMatch(movedTarget.identity, sourceIdentity)) {
|
|
461
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
462
|
+
await fs.rename(backupPath, operationTargetPath);
|
|
463
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
236
464
|
backupCreated = false;
|
|
237
465
|
return {
|
|
238
|
-
status: "
|
|
466
|
+
status: "already_current",
|
|
239
467
|
changed: false,
|
|
240
468
|
targetPath: params.targetPath,
|
|
241
|
-
sourcePath
|
|
469
|
+
sourcePath,
|
|
470
|
+
sourceBuild: sourceIdentity.build
|
|
242
471
|
};
|
|
243
472
|
}
|
|
473
|
+
if (!snapshotsMatch(initialTarget, movedTarget)) {
|
|
474
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
475
|
+
await fs.rename(backupPath, operationTargetPath);
|
|
476
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
477
|
+
backupCreated = false;
|
|
478
|
+
throw new Error("Computer Use service target changed to an unexpected generation during refresh.");
|
|
479
|
+
}
|
|
244
480
|
}
|
|
245
481
|
try {
|
|
246
|
-
await
|
|
482
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
483
|
+
await fs.rename(stagedPath, operationTargetPath);
|
|
484
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
247
485
|
} catch (error) {
|
|
248
|
-
|
|
486
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
487
|
+
await assertNotSymlink(operationTargetPath, "Computer Use service target");
|
|
488
|
+
const winnerIdentity = await inspectServiceApp(operationTargetPath);
|
|
489
|
+
if (!winnerIdentity || !identitiesMatch(winnerIdentity, sourceIdentity)) {
|
|
249
490
|
if (backupCreated) {
|
|
250
|
-
await
|
|
251
|
-
|
|
491
|
+
if (!await pathExists$1(operationTargetPath)) {
|
|
492
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
493
|
+
await fs.rename(backupPath, operationTargetPath);
|
|
494
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
495
|
+
backupCreated = false;
|
|
496
|
+
}
|
|
252
497
|
}
|
|
253
498
|
throw error;
|
|
254
499
|
}
|
|
255
500
|
if (backupCreated) {
|
|
501
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
502
|
+
await assertNotSymlink(backupPath, "Computer Use service backup");
|
|
256
503
|
await fs.rm(backupPath, {
|
|
257
504
|
recursive: true,
|
|
258
505
|
force: true
|
|
259
506
|
});
|
|
507
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
260
508
|
backupCreated = false;
|
|
261
509
|
}
|
|
262
510
|
return {
|
|
263
|
-
status: "
|
|
511
|
+
status: "already_current",
|
|
264
512
|
changed: false,
|
|
265
513
|
targetPath: params.targetPath,
|
|
266
|
-
sourcePath
|
|
514
|
+
sourcePath,
|
|
515
|
+
sourceBuild: sourceIdentity.build
|
|
267
516
|
};
|
|
268
517
|
}
|
|
518
|
+
await assertNotSymlink(operationTargetPath, "Installed Computer Use service app");
|
|
519
|
+
const installedSnapshot = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
|
|
520
|
+
if (!installedSnapshot.identity || !identitiesMatch(installedSnapshot.identity, sourceIdentity)) {
|
|
521
|
+
if (filesystemSnapshotsMatch(installedSnapshot, stagedSnapshot)) {
|
|
522
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
523
|
+
await fs.rm(operationTargetPath, {
|
|
524
|
+
recursive: true,
|
|
525
|
+
force: true
|
|
526
|
+
});
|
|
527
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
528
|
+
if (backupCreated) {
|
|
529
|
+
await fs.rename(backupPath, operationTargetPath);
|
|
530
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
531
|
+
backupCreated = false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
throw new Error("Installed Computer Use service app failed post-install identity verification.");
|
|
535
|
+
}
|
|
269
536
|
if (backupCreated) {
|
|
537
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
538
|
+
await assertNotSymlink(backupPath, "Computer Use service backup");
|
|
270
539
|
await fs.rm(backupPath, {
|
|
271
540
|
recursive: true,
|
|
272
541
|
force: true
|
|
273
542
|
});
|
|
543
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
274
544
|
backupCreated = false;
|
|
275
545
|
}
|
|
276
546
|
return {
|
|
277
|
-
status: "installed",
|
|
547
|
+
status: initialTarget.exists ? "refreshed" : "installed",
|
|
278
548
|
changed: true,
|
|
279
549
|
targetPath: params.targetPath,
|
|
280
|
-
sourcePath
|
|
550
|
+
sourcePath,
|
|
551
|
+
sourceBuild: sourceIdentity.build,
|
|
552
|
+
...initialTarget.identity ? { previousBuild: initialTarget.identity.build } : {}
|
|
281
553
|
};
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (backupCreated && await ownedServiceParentIsStable(ownedParent) && !await pathExists$1(operationTargetPath)) {
|
|
556
|
+
await assertOwnedServiceParentStable(ownedParent);
|
|
557
|
+
await fs.rename(backupPath, operationTargetPath);
|
|
558
|
+
backupCreated = false;
|
|
559
|
+
}
|
|
560
|
+
throw error;
|
|
282
561
|
} finally {
|
|
283
|
-
await fs.rm(stagingRoot, {
|
|
562
|
+
if (await ownedServiceParentIsStable(ownedParent) && await directoryIdentityIsStable(stagingRootIdentity)) await fs.rm(stagingRoot, {
|
|
284
563
|
recursive: true,
|
|
285
564
|
force: true
|
|
286
565
|
});
|
|
@@ -289,8 +568,43 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
|
289
568
|
async function pathExists$1(filePath) {
|
|
290
569
|
return await fs.lstat(filePath).then(() => true, () => false);
|
|
291
570
|
}
|
|
292
|
-
async function findUsableServiceApp(candidates) {
|
|
293
|
-
for (const candidate of candidates)
|
|
571
|
+
async function findUsableServiceApp(candidates, inspectServiceApp) {
|
|
572
|
+
for (const candidate of candidates) {
|
|
573
|
+
const identity = await inspectServiceApp(candidate);
|
|
574
|
+
if (identity) return {
|
|
575
|
+
path: candidate,
|
|
576
|
+
identity
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async function readServiceAppSnapshot(appPath, inspectServiceApp) {
|
|
581
|
+
if (!await pathExists$1(appPath)) return { exists: false };
|
|
582
|
+
return {
|
|
583
|
+
exists: true,
|
|
584
|
+
identity: await inspectServiceApp(appPath),
|
|
585
|
+
filesystemKey: await readServiceAppFilesystemKey(appPath)
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
async function readServiceAppFilesystemKey(appPath) {
|
|
589
|
+
const paths = [
|
|
590
|
+
appPath,
|
|
591
|
+
path.join(appPath, "Contents", "Info.plist"),
|
|
592
|
+
path.join(appPath, CLIENT_RELATIVE_PATH)
|
|
593
|
+
];
|
|
594
|
+
return (await Promise.all(paths.map(async (entryPath) => await fs.stat(entryPath).then((stat) => `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}`, () => "missing")))).join("|");
|
|
595
|
+
}
|
|
596
|
+
function snapshotsMatch(left, right) {
|
|
597
|
+
if (left.exists !== right.exists) return false;
|
|
598
|
+
if (!left.exists) return true;
|
|
599
|
+
if (left.filesystemKey && right.filesystemKey && left.filesystemKey !== right.filesystemKey) return false;
|
|
600
|
+
if (left.identity || right.identity) return Boolean(left.identity && right.identity && identitiesMatch(left.identity, right.identity));
|
|
601
|
+
return left.filesystemKey !== void 0 && left.filesystemKey === right.filesystemKey;
|
|
602
|
+
}
|
|
603
|
+
function filesystemSnapshotsMatch(left, right) {
|
|
604
|
+
return Boolean(left.exists && right.exists && left.filesystemKey && right.filesystemKey && left.filesystemKey === right.filesystemKey);
|
|
605
|
+
}
|
|
606
|
+
function identitiesMatch(left, right) {
|
|
607
|
+
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
608
|
}
|
|
295
609
|
async function hasExecutableClient(appPath) {
|
|
296
610
|
try {
|
|
@@ -300,6 +614,90 @@ async function hasExecutableClient(appPath) {
|
|
|
300
614
|
return false;
|
|
301
615
|
}
|
|
302
616
|
}
|
|
617
|
+
async function inspectTrustedServiceApp(appPath) {
|
|
618
|
+
if (!await hasExecutableClient(appPath)) return;
|
|
619
|
+
const clientAppPath = path.join(appPath, CLIENT_APP_RELATIVE_PATH);
|
|
620
|
+
try {
|
|
621
|
+
await verifyTrustedBundle(appPath, SERVICE_BUNDLE_ID, true);
|
|
622
|
+
await verifyTrustedBundle(clientAppPath, CLIENT_BUNDLE_ID, false);
|
|
623
|
+
const [info, serviceSignature, clientSignature] = await Promise.all([
|
|
624
|
+
readBundleInfo(appPath),
|
|
625
|
+
readCodeSignature(appPath),
|
|
626
|
+
readCodeSignature(clientAppPath)
|
|
627
|
+
]);
|
|
628
|
+
if (!info || serviceSignature.identifier !== SERVICE_BUNDLE_ID || serviceSignature.teamId !== OPENAI_TEAM_ID || clientSignature.identifier !== CLIENT_BUNDLE_ID || clientSignature.teamId !== OPENAI_TEAM_ID) return;
|
|
629
|
+
return {
|
|
630
|
+
bundleId: serviceSignature.identifier,
|
|
631
|
+
version: info.version,
|
|
632
|
+
build: info.build,
|
|
633
|
+
cdHash: serviceSignature.cdHash,
|
|
634
|
+
teamId: serviceSignature.teamId,
|
|
635
|
+
clientBundleId: clientSignature.identifier,
|
|
636
|
+
clientCdHash: clientSignature.cdHash,
|
|
637
|
+
clientTeamId: clientSignature.teamId
|
|
638
|
+
};
|
|
639
|
+
} catch {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async function verifyTrustedBundle(appPath, bundleId, deep) {
|
|
644
|
+
const requirement = `anchor apple generic and certificate leaf[subject.OU] = "${OPENAI_TEAM_ID}" and identifier "${bundleId}"`;
|
|
645
|
+
await runExec("/usr/bin/codesign", [
|
|
646
|
+
"--verify",
|
|
647
|
+
"--strict",
|
|
648
|
+
...deep ? ["--deep"] : [],
|
|
649
|
+
`-R=${requirement}`,
|
|
650
|
+
appPath
|
|
651
|
+
], {
|
|
652
|
+
logOutput: false,
|
|
653
|
+
timeoutMs: INSPECT_TIMEOUT_MS
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
async function readBundleInfo(appPath) {
|
|
657
|
+
const result = await runExec("/usr/bin/plutil", [
|
|
658
|
+
"-convert",
|
|
659
|
+
"json",
|
|
660
|
+
"-o",
|
|
661
|
+
"-",
|
|
662
|
+
"--",
|
|
663
|
+
path.join(appPath, "Contents", "Info.plist")
|
|
664
|
+
], {
|
|
665
|
+
logOutput: false,
|
|
666
|
+
timeoutMs: INSPECT_TIMEOUT_MS
|
|
667
|
+
});
|
|
668
|
+
const parsed = JSON.parse(result.stdout);
|
|
669
|
+
if (!isRecord(parsed)) return;
|
|
670
|
+
const version = parsed.CFBundleShortVersionString;
|
|
671
|
+
const build = parsed.CFBundleVersion;
|
|
672
|
+
return typeof version === "string" && version && typeof build === "string" && build ? {
|
|
673
|
+
version,
|
|
674
|
+
build
|
|
675
|
+
} : void 0;
|
|
676
|
+
}
|
|
677
|
+
async function readCodeSignature(appPath) {
|
|
678
|
+
const result = await runExec("/usr/bin/codesign", [
|
|
679
|
+
"-d",
|
|
680
|
+
"--verbose=4",
|
|
681
|
+
appPath
|
|
682
|
+
], {
|
|
683
|
+
logOutput: false,
|
|
684
|
+
timeoutMs: INSPECT_TIMEOUT_MS
|
|
685
|
+
});
|
|
686
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
687
|
+
const identifier = readCodeSignField(output, "Identifier");
|
|
688
|
+
const teamId = readCodeSignField(output, "TeamIdentifier");
|
|
689
|
+
const cdHash = readCodeSignField(output, "CDHash").toLowerCase();
|
|
690
|
+
if (!identifier || !teamId || !/^[a-f0-9]+$/.test(cdHash)) throw new Error(`Could not inspect the signed identity at ${appPath}.`);
|
|
691
|
+
return {
|
|
692
|
+
identifier,
|
|
693
|
+
teamId,
|
|
694
|
+
cdHash
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function readCodeSignField(output, field) {
|
|
698
|
+
const prefix = `${field}=`;
|
|
699
|
+
return output.split(/\r?\n/u).find((candidate) => candidate.startsWith(prefix))?.slice(prefix.length).trim() ?? "";
|
|
700
|
+
}
|
|
303
701
|
async function copyServiceAppWithDitto(sourcePath, targetPath) {
|
|
304
702
|
await runExec("/usr/bin/ditto", [
|
|
305
703
|
"--noqtn",
|
|
@@ -458,7 +856,8 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
|
|
|
458
856
|
async function resolveCodexAppServerPreparedAuthHandoff(params) {
|
|
459
857
|
const usesNativeHome = params.homeScope === "user";
|
|
460
858
|
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 (
|
|
859
|
+
if (usesNativeHome) return { nativeAuthProfile: true };
|
|
860
|
+
if (params.authRequirement === "api-key") {
|
|
462
861
|
const apiKey = params.resolvedApiKey?.trim();
|
|
463
862
|
if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
|
|
464
863
|
return {
|
|
@@ -476,7 +875,7 @@ async function resolveCodexAppServerPreparedAuthHandoff(params) {
|
|
|
476
875
|
agentDir: params.agentDir,
|
|
477
876
|
config: params.config
|
|
478
877
|
});
|
|
479
|
-
if (
|
|
878
|
+
if (params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
|
|
480
879
|
authProfileId,
|
|
481
880
|
nativeAuthProfile
|
|
482
881
|
};
|
|
@@ -584,9 +983,11 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
|
|
|
584
983
|
codexHome,
|
|
585
984
|
config: computerUseConfig
|
|
586
985
|
});
|
|
587
|
-
|
|
986
|
+
const ownsIsolatedCodexHome = startOptions.homeScope !== "user" && !startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim();
|
|
987
|
+
if (computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome) try {
|
|
588
988
|
await ensureCodexComputerUseServiceApp({
|
|
589
989
|
codexHome,
|
|
990
|
+
ownershipRoot: agentDir,
|
|
590
991
|
appServerCommand: startOptions.command
|
|
591
992
|
});
|
|
592
993
|
} catch (error) {
|
|
@@ -1105,12 +1506,12 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1105
1506
|
client.addRequestHandler(async (request) => {
|
|
1106
1507
|
if (request.method !== "account/chatgptAuthTokens/refresh") return;
|
|
1107
1508
|
if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
|
|
1108
|
-
return await refreshCodexAppServerAuthTokens({
|
|
1509
|
+
return { ...await refreshCodexAppServerAuthTokens({
|
|
1109
1510
|
agentDir: runtime.context.agentDir,
|
|
1110
1511
|
authProfileId: runtime.context.authProfileId,
|
|
1111
1512
|
...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
|
|
1112
1513
|
config: runtime.context.config
|
|
1113
|
-
});
|
|
1514
|
+
}) };
|
|
1114
1515
|
});
|
|
1115
1516
|
client.addNotificationHandler((notification) => {
|
|
1116
1517
|
if (notification.method === "account/rateLimits/updated") {
|
|
@@ -1362,6 +1763,15 @@ function protectCodexAppServerLiveThread(client, threadId) {
|
|
|
1362
1763
|
};
|
|
1363
1764
|
}
|
|
1364
1765
|
//#endregion
|
|
1766
|
+
//#region extensions/codex/src/app-server/elicitation-response.ts
|
|
1767
|
+
function createCodexElicitationResponse(action, content = null, meta = null) {
|
|
1768
|
+
return {
|
|
1769
|
+
action,
|
|
1770
|
+
content,
|
|
1771
|
+
_meta: meta
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
//#endregion
|
|
1365
1775
|
//#region extensions/codex/src/app-server/rpc-error.ts
|
|
1366
1776
|
/** RPC error wrapper that preserves app-server error code and data. */
|
|
1367
1777
|
var CodexAppServerRpcError = class extends Error {
|
|
@@ -1558,11 +1968,273 @@ function websocketFrameToText(data) {
|
|
|
1558
1968
|
return Buffer.from(data).toString("utf8");
|
|
1559
1969
|
}
|
|
1560
1970
|
//#endregion
|
|
1971
|
+
//#region extensions/codex/src/app-server/transport-process-containment.ts
|
|
1972
|
+
const PROCESS_COLUMNS = "pid=,ppid=,pgid=,stat=,lstart=";
|
|
1973
|
+
const MAX_CONTAINED_PROCESSES = 512;
|
|
1974
|
+
const MAX_PROCESS_CONTAINMENT_MS = 2e3;
|
|
1975
|
+
const MAX_PROCESS_QUIESCE_PASSES = 16;
|
|
1976
|
+
const PROCESS_INSPECTION_MAX_BYTES = 8 * 1024 * 1024;
|
|
1977
|
+
async function terminateCodexAppServerDescendants(child) {
|
|
1978
|
+
const rootPid = child.pid;
|
|
1979
|
+
if (process.platform === "win32" || !rootPid || !child.kill || hasExited(child)) return;
|
|
1980
|
+
const deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS;
|
|
1981
|
+
const snapshot = await readProcessSnapshot(deadline);
|
|
1982
|
+
if (!snapshot || Date.now() >= deadline) return;
|
|
1983
|
+
const root = snapshot.find((row) => row.pid === rootPid);
|
|
1984
|
+
if (!root || !isSameLiveRoot(root, root)) return;
|
|
1985
|
+
const initialDescendants = collectDescendants(snapshot, [rootPid]);
|
|
1986
|
+
if (initialDescendants.length > MAX_CONTAINED_PROCESSES) return;
|
|
1987
|
+
const stoppedDescendants = /* @__PURE__ */ new Map();
|
|
1988
|
+
if (!await signalSameRoot(root, "SIGSTOP", deadline)) return;
|
|
1989
|
+
let resumeRootOnUnwind = true;
|
|
1990
|
+
try {
|
|
1991
|
+
const descendants = await quiesceDescendants(root, initialDescendants, stoppedDescendants, deadline);
|
|
1992
|
+
if (!descendants) return;
|
|
1993
|
+
for (const descendant of descendants.toReversed()) {
|
|
1994
|
+
if (Date.now() >= deadline) return;
|
|
1995
|
+
if (!descendant.state.startsWith("Z")) {
|
|
1996
|
+
if (!await signalSameProcess(descendant, "SIGKILL", deadline) || Date.now() >= deadline) return;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
resumeRootOnUnwind = false;
|
|
2000
|
+
let resumed = false;
|
|
2001
|
+
return () => {
|
|
2002
|
+
if (resumed) return;
|
|
2003
|
+
resumed = true;
|
|
2004
|
+
resumeTransportRoot(child, root, false);
|
|
2005
|
+
};
|
|
2006
|
+
} finally {
|
|
2007
|
+
if (resumeRootOnUnwind) {
|
|
2008
|
+
for (const descendant of stoppedDescendants.values()) signalProcess(descendant.pid, "SIGCONT");
|
|
2009
|
+
resumeTransportRoot(child, root, true);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
async function quiesceDescendants(root, initialDescendants, stopped, deadline) {
|
|
2014
|
+
const provenByPid = new Map(initialDescendants.map((descendant) => [descendant.pid, descendant]));
|
|
2015
|
+
const stopFailures = /* @__PURE__ */ new Map();
|
|
2016
|
+
for (let pass = 0; pass < MAX_PROCESS_QUIESCE_PASSES; pass += 1) {
|
|
2017
|
+
if (Date.now() >= deadline) return;
|
|
2018
|
+
const snapshot = await readProcessSnapshot(deadline);
|
|
2019
|
+
if (!snapshot || Date.now() >= deadline) return;
|
|
2020
|
+
const currentRoot = snapshot.find((row) => row.pid === root.pid);
|
|
2021
|
+
if (!currentRoot || !isSameLiveRoot(currentRoot, root)) return;
|
|
2022
|
+
if (!isSameLiveRoot(currentRoot, root, true)) {
|
|
2023
|
+
if (!await signalSameRoot(root, "SIGSTOP", deadline) || Date.now() >= deadline) return;
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
const snapshotByPid = new Map(snapshot.map((process) => [process.pid, process]));
|
|
2027
|
+
const liveProven = [];
|
|
2028
|
+
for (const proven of provenByPid.values()) {
|
|
2029
|
+
const current = snapshotByPid.get(proven.pid);
|
|
2030
|
+
if (!current) {
|
|
2031
|
+
provenByPid.delete(proven.pid);
|
|
2032
|
+
stopped.delete(identityKey(proven));
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
if (!hasSameIdentity(proven, current)) return;
|
|
2036
|
+
provenByPid.set(current.pid, current);
|
|
2037
|
+
const key = identityKey(current);
|
|
2038
|
+
if (stopped.has(key)) stopped.set(key, current);
|
|
2039
|
+
liveProven.push(current);
|
|
2040
|
+
}
|
|
2041
|
+
const descendants = collectDescendants(snapshot, [root.pid, ...liveProven.map(({ pid }) => pid)]);
|
|
2042
|
+
for (const descendant of descendants) {
|
|
2043
|
+
const proven = provenByPid.get(descendant.pid);
|
|
2044
|
+
if (proven && !hasSameIdentity(proven, descendant)) return;
|
|
2045
|
+
provenByPid.set(descendant.pid, descendant);
|
|
2046
|
+
}
|
|
2047
|
+
if (provenByPid.size > MAX_CONTAINED_PROCESSES) return;
|
|
2048
|
+
const quiescenceTargets = new Map(liveProven.map((process) => [process.pid, process]));
|
|
2049
|
+
for (const descendant of descendants) quiescenceTargets.set(descendant.pid, descendant);
|
|
2050
|
+
let allStopped = true;
|
|
2051
|
+
for (const descendant of quiescenceTargets.values()) {
|
|
2052
|
+
if (Date.now() >= deadline) return;
|
|
2053
|
+
if (isStoppedState(descendant.state)) continue;
|
|
2054
|
+
const stopQueued = await signalSameProcess(descendant, "SIGSTOP", deadline);
|
|
2055
|
+
if (Date.now() >= deadline) return;
|
|
2056
|
+
if (stopQueued) {
|
|
2057
|
+
stopFailures.delete(identityKey(descendant));
|
|
2058
|
+
stopped.set(identityKey(descendant), descendant);
|
|
2059
|
+
} else {
|
|
2060
|
+
const key = identityKey(descendant);
|
|
2061
|
+
const failures = (stopFailures.get(key) ?? 0) + 1;
|
|
2062
|
+
if (failures >= 2) return;
|
|
2063
|
+
stopFailures.set(key, failures);
|
|
2064
|
+
}
|
|
2065
|
+
if (!isUninterruptibleState(descendant.state) || !stopQueued) allStopped = false;
|
|
2066
|
+
}
|
|
2067
|
+
if (allStopped) return [...provenByPid.values()];
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
async function readProcessSnapshot(deadline) {
|
|
2071
|
+
return await readProcesses(["-axo", PROCESS_COLUMNS], deadline);
|
|
2072
|
+
}
|
|
2073
|
+
async function readProcess(pid, deadline) {
|
|
2074
|
+
return (await readProcesses([
|
|
2075
|
+
"-o",
|
|
2076
|
+
PROCESS_COLUMNS,
|
|
2077
|
+
"-p",
|
|
2078
|
+
String(pid)
|
|
2079
|
+
], deadline))?.find((row) => row.pid === pid);
|
|
2080
|
+
}
|
|
2081
|
+
async function readProcesses(args, deadline) {
|
|
2082
|
+
const remainingMs = deadline - Date.now();
|
|
2083
|
+
if (remainingMs <= 0) return;
|
|
2084
|
+
return await new Promise((resolve) => {
|
|
2085
|
+
let settled = false;
|
|
2086
|
+
const settle = (processes) => {
|
|
2087
|
+
if (settled) return;
|
|
2088
|
+
settled = true;
|
|
2089
|
+
clearTimeout(timer);
|
|
2090
|
+
resolve(processes);
|
|
2091
|
+
};
|
|
2092
|
+
const inspector = execFile("ps", args, {
|
|
2093
|
+
encoding: "utf8",
|
|
2094
|
+
maxBuffer: PROCESS_INSPECTION_MAX_BYTES
|
|
2095
|
+
}, (error, stdout) => {
|
|
2096
|
+
settle(error ? void 0 : parseProcesses(stdout));
|
|
2097
|
+
});
|
|
2098
|
+
const timer = setTimeout(() => {
|
|
2099
|
+
settle(void 0);
|
|
2100
|
+
inspector.stdout?.destroy();
|
|
2101
|
+
inspector.stderr?.destroy();
|
|
2102
|
+
inspector.kill("SIGKILL");
|
|
2103
|
+
inspector.unref();
|
|
2104
|
+
}, Math.max(1, remainingMs));
|
|
2105
|
+
timer.unref?.();
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
function parseProcesses(output) {
|
|
2109
|
+
const rows = [];
|
|
2110
|
+
for (const line of output.split("\n")) {
|
|
2111
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
|
|
2112
|
+
if (!match) continue;
|
|
2113
|
+
const pid = Number(match[1] ?? "");
|
|
2114
|
+
const ppid = Number(match[2] ?? "");
|
|
2115
|
+
const pgid = Number(match[3] ?? "");
|
|
2116
|
+
const startedAt = (match[5] ?? "").trim().replace(/\s+/g, " ");
|
|
2117
|
+
if (![
|
|
2118
|
+
pid,
|
|
2119
|
+
ppid,
|
|
2120
|
+
pgid
|
|
2121
|
+
].every(Number.isSafeInteger) || pid <= 0 || ppid < 0 || pgid <= 0 || !startedAt) continue;
|
|
2122
|
+
rows.push({
|
|
2123
|
+
pid,
|
|
2124
|
+
ppid,
|
|
2125
|
+
pgid,
|
|
2126
|
+
state: match[4] ?? "",
|
|
2127
|
+
startedAt
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
return rows;
|
|
2131
|
+
}
|
|
2132
|
+
function collectDescendants(snapshot, rootPids) {
|
|
2133
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2134
|
+
for (const row of snapshot) {
|
|
2135
|
+
const children = childrenByParent.get(row.ppid) ?? [];
|
|
2136
|
+
children.push(row);
|
|
2137
|
+
childrenByParent.set(row.ppid, children);
|
|
2138
|
+
}
|
|
2139
|
+
const descendants = [];
|
|
2140
|
+
const pending = [...new Set(rootPids)];
|
|
2141
|
+
const seen = new Set(pending);
|
|
2142
|
+
for (const parentPid of pending) for (const child of childrenByParent.get(parentPid) ?? []) {
|
|
2143
|
+
if (seen.has(child.pid)) continue;
|
|
2144
|
+
seen.add(child.pid);
|
|
2145
|
+
descendants.push(child);
|
|
2146
|
+
pending.push(child.pid);
|
|
2147
|
+
}
|
|
2148
|
+
return descendants;
|
|
2149
|
+
}
|
|
2150
|
+
function isStoppedState(state) {
|
|
2151
|
+
return state.startsWith("T") || state.startsWith("t") || state.startsWith("Z");
|
|
2152
|
+
}
|
|
2153
|
+
function isQuiescedState(state) {
|
|
2154
|
+
return isStoppedState(state) || isUninterruptibleState(state);
|
|
2155
|
+
}
|
|
2156
|
+
function isUninterruptibleState(state) {
|
|
2157
|
+
return state.startsWith("D") || state.startsWith("U");
|
|
2158
|
+
}
|
|
2159
|
+
function isSameLiveProcess(current, expected) {
|
|
2160
|
+
return current.pgid === expected.pgid && !current.state.startsWith("Z") && hasSameIdentity(current, expected);
|
|
2161
|
+
}
|
|
2162
|
+
function isSameLiveRoot(current, expected, requireStopped = false) {
|
|
2163
|
+
return current.ppid === process.pid && (!requireStopped || isQuiescedState(current.state)) && isSameLiveProcess(current, expected);
|
|
2164
|
+
}
|
|
2165
|
+
async function signalSameRoot(root, signal, deadline) {
|
|
2166
|
+
const current = await readProcess(root.pid, deadline);
|
|
2167
|
+
return Boolean(current && isSameLiveRoot(current, root) && signalProcess(current.pid, signal));
|
|
2168
|
+
}
|
|
2169
|
+
function resumeTransportRoot(child, root, allowSynchronousPidFallback) {
|
|
2170
|
+
try {
|
|
2171
|
+
if (child.kill) {
|
|
2172
|
+
child.kill("SIGCONT");
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
} catch {
|
|
2176
|
+
if (!allowSynchronousPidFallback) return;
|
|
2177
|
+
}
|
|
2178
|
+
if (allowSynchronousPidFallback) signalProcess(root.pid, "SIGCONT");
|
|
2179
|
+
}
|
|
2180
|
+
async function signalSameProcess(expected, signal, deadline) {
|
|
2181
|
+
const current = await readProcess(expected.pid, deadline);
|
|
2182
|
+
return Boolean(current && isSameLiveProcess(current, expected) && signalProcess(current.pid, signal));
|
|
2183
|
+
}
|
|
2184
|
+
function hasSameIdentity(left, right) {
|
|
2185
|
+
return identityKey(left) === identityKey(right);
|
|
2186
|
+
}
|
|
2187
|
+
function identityKey(row) {
|
|
2188
|
+
return `${row.pid}\0${row.startedAt}`;
|
|
2189
|
+
}
|
|
2190
|
+
function hasExited(child) {
|
|
2191
|
+
return child.exitCode != null || child.signalCode != null;
|
|
2192
|
+
}
|
|
2193
|
+
function signalProcess(pid, signal) {
|
|
2194
|
+
try {
|
|
2195
|
+
process.kill(pid, signal);
|
|
2196
|
+
return true;
|
|
2197
|
+
} catch {
|
|
2198
|
+
return false;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
//#endregion
|
|
1561
2202
|
//#region extensions/codex/src/app-server/transport.ts
|
|
2203
|
+
/**
|
|
2204
|
+
* Shared transport lifecycle helpers for stdio and WebSocket Codex app-server
|
|
2205
|
+
* connections.
|
|
2206
|
+
*/
|
|
2207
|
+
const CODEX_APP_SERVER_TRANSPORT_CLOSES = /* @__PURE__ */ new WeakMap();
|
|
1562
2208
|
/** Starts graceful transport shutdown and schedules a force kill fallback. */
|
|
1563
2209
|
function closeCodexAppServerTransport(child, options = {}) {
|
|
1564
|
-
child
|
|
1565
|
-
|
|
2210
|
+
beginCodexAppServerTransportClose(child, options);
|
|
2211
|
+
}
|
|
2212
|
+
function beginCodexAppServerTransportClose(child, options) {
|
|
2213
|
+
const current = CODEX_APP_SERVER_TRANSPORT_CLOSES.get(child);
|
|
2214
|
+
if (current) return current;
|
|
2215
|
+
if (process.platform === "win32" || !child.pid || !child.kill || hasCodexAppServerTransportExited(child)) {
|
|
2216
|
+
finishCodexAppServerTransportClose(child, options);
|
|
2217
|
+
const completed = Promise.resolve();
|
|
2218
|
+
CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, completed);
|
|
2219
|
+
return completed;
|
|
2220
|
+
}
|
|
2221
|
+
const closing = (async () => {
|
|
2222
|
+
let resumeRoot;
|
|
2223
|
+
try {
|
|
2224
|
+
resumeRoot = await terminateCodexAppServerDescendants(child);
|
|
2225
|
+
} catch {
|
|
2226
|
+
resumeRoot = void 0;
|
|
2227
|
+
}
|
|
2228
|
+
try {
|
|
2229
|
+
finishCodexAppServerTransportClose(child, options, resumeRoot);
|
|
2230
|
+
} catch {
|
|
2231
|
+
signalCodexAppServerTransport(child, "SIGKILL");
|
|
2232
|
+
}
|
|
2233
|
+
})();
|
|
2234
|
+
CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, closing);
|
|
2235
|
+
return closing;
|
|
2236
|
+
}
|
|
2237
|
+
function finishCodexAppServerTransportClose(child, options, resumeRoot) {
|
|
1566
2238
|
const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
|
|
1567
2239
|
const forceKill = setTimeout(() => {
|
|
1568
2240
|
if (hasCodexAppServerTransportExited(child)) return;
|
|
@@ -1574,6 +2246,12 @@ function closeCodexAppServerTransport(child, options = {}) {
|
|
|
1574
2246
|
child.stdout.destroy?.();
|
|
1575
2247
|
child.stderr.destroy?.();
|
|
1576
2248
|
});
|
|
2249
|
+
try {
|
|
2250
|
+
child.stdin.end?.();
|
|
2251
|
+
child.stdin.destroy?.();
|
|
2252
|
+
} finally {
|
|
2253
|
+
resumeRoot?.();
|
|
2254
|
+
}
|
|
1577
2255
|
child.unref?.();
|
|
1578
2256
|
child.stdout.unref?.();
|
|
1579
2257
|
child.stderr.unref?.();
|
|
@@ -1581,7 +2259,7 @@ function closeCodexAppServerTransport(child, options = {}) {
|
|
|
1581
2259
|
}
|
|
1582
2260
|
/** Closes a transport and waits briefly for an exit event. */
|
|
1583
2261
|
async function closeCodexAppServerTransportAndWait(child, options = {}) {
|
|
1584
|
-
if (!hasCodexAppServerTransportExited(child))
|
|
2262
|
+
if (!hasCodexAppServerTransportExited(child)) await beginCodexAppServerTransportClose(child, options);
|
|
1585
2263
|
return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
|
|
1586
2264
|
}
|
|
1587
2265
|
function hasCodexAppServerTransportExited(child) {
|
|
@@ -1614,15 +2292,6 @@ function signalCodexAppServerTransport(child, signal) {
|
|
|
1614
2292
|
child.kill?.(signal);
|
|
1615
2293
|
}
|
|
1616
2294
|
//#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
2295
|
//#region extensions/codex/src/app-server/client.ts
|
|
1627
2296
|
/**
|
|
1628
2297
|
* JSON-RPC client for Codex app-server transports, including request/response
|
|
@@ -1756,7 +2425,13 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1756
2425
|
title: "OpenClaw",
|
|
1757
2426
|
version: OPENCLAW_VERSION
|
|
1758
2427
|
},
|
|
1759
|
-
capabilities: {
|
|
2428
|
+
capabilities: {
|
|
2429
|
+
experimentalApi: true,
|
|
2430
|
+
extensions: {
|
|
2431
|
+
"openai/standard-form-input": {},
|
|
2432
|
+
"openai/form": {}
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
1760
2435
|
});
|
|
1761
2436
|
this.serverVersion = assertSupportedCodexAppServerVersion(response);
|
|
1762
2437
|
this.runtimeIdentity = buildCodexAppServerRuntimeIdentity(response, this.serverVersion);
|
|
@@ -2206,7 +2881,7 @@ function defaultServerRequestResponse(request) {
|
|
|
2206
2881
|
scope: "turn"
|
|
2207
2882
|
};
|
|
2208
2883
|
if (request.method === "item/tool/requestUserInput") return { answers: {} };
|
|
2209
|
-
if (request.method === "mcpServer/elicitation/request") return {
|
|
2884
|
+
if (request.method === "mcpServer/elicitation/request") return createCodexElicitationResponse("decline", null, { message: "OpenClaw has no interactive handler for this elicitation." });
|
|
2210
2885
|
return {};
|
|
2211
2886
|
}
|
|
2212
2887
|
function stringifyCodexAppServerMessage(message) {
|
|
@@ -2226,14 +2901,20 @@ function timeoutServerRequestResponse(request) {
|
|
|
2226
2901
|
var CodexAppServerVersionError = class extends Error {
|
|
2227
2902
|
constructor(detectedVersion) {
|
|
2228
2903
|
const detected = detectedVersion ? `detected ${detectedVersion}` : "OpenClaw could not determine the running Codex version";
|
|
2229
|
-
super(`Codex app-server ${
|
|
2904
|
+
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
2905
|
this.name = "CodexAppServerVersionError";
|
|
2231
2906
|
this.detectedVersion = detectedVersion;
|
|
2232
2907
|
}
|
|
2233
2908
|
};
|
|
2234
2909
|
function assertSupportedCodexAppServerVersion(response) {
|
|
2235
2910
|
const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
|
|
2236
|
-
if (detectedVersion
|
|
2911
|
+
if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
|
|
2912
|
+
const detected = parse(detectedVersion);
|
|
2913
|
+
if (!detected || detected.compare("0.147.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
|
|
2914
|
+
if (detected.compare("0.149.1") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
|
|
2915
|
+
detectedVersion,
|
|
2916
|
+
validatedVersion: CODEX_APP_SERVER_VERSION
|
|
2917
|
+
});
|
|
2237
2918
|
return detectedVersion;
|
|
2238
2919
|
}
|
|
2239
2920
|
function isUnsupportedCodexAppServerVersionError(error) {
|
|
@@ -2397,206 +3078,6 @@ function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
|
|
|
2397
3078
|
return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
|
|
2398
3079
|
}
|
|
2399
3080
|
//#endregion
|
|
2400
|
-
//#region extensions/codex/src/app-server/managed-binary.ts
|
|
2401
|
-
/**
|
|
2402
|
-
* Resolves the managed Codex app-server binary shipped with or installed beside
|
|
2403
|
-
* the Codex plugin before stdio startup.
|
|
2404
|
-
*/
|
|
2405
|
-
const CODEX_PLUGIN_ROOT = resolveDefaultCodexPluginRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
2406
|
-
const MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS = ["/Applications/ChatGPT.app/Contents/Resources/codex", "/Applications/Codex.app/Contents/Resources/codex"];
|
|
2407
|
-
/** Rewrites managed stdio start options to point at an executable Codex binary path. */
|
|
2408
|
-
async function resolveManagedCodexAppServerStartOptions(startOptions, options = {}) {
|
|
2409
|
-
if (startOptions.transport !== "stdio" || startOptions.commandSource !== "managed") return startOptions;
|
|
2410
|
-
const platform = options.platform ?? process.platform;
|
|
2411
|
-
const paths = resolveManagedCodexAppServerPaths({
|
|
2412
|
-
platform,
|
|
2413
|
-
pluginRoot: options.pluginRoot,
|
|
2414
|
-
managedCommandOrder: startOptions.managedCommandOrder
|
|
2415
|
-
});
|
|
2416
|
-
const pathExists = options.pathExists ?? commandPathExists;
|
|
2417
|
-
const commandPaths = await findManagedCodexAppServerCommandPaths({
|
|
2418
|
-
candidateCommandPaths: paths.candidateCommandPaths,
|
|
2419
|
-
pathExists,
|
|
2420
|
-
platform
|
|
2421
|
-
});
|
|
2422
|
-
const commandPath = expectDefined(commandPaths[0], "resolved managed Codex command path");
|
|
2423
|
-
const managedFallbackCommandPaths = commandPaths.slice(1);
|
|
2424
|
-
return {
|
|
2425
|
-
...startOptions,
|
|
2426
|
-
command: commandPath,
|
|
2427
|
-
commandSource: "resolved-managed",
|
|
2428
|
-
...managedFallbackCommandPaths.length > 0 ? { managedFallbackCommandPaths } : {}
|
|
2429
|
-
};
|
|
2430
|
-
}
|
|
2431
|
-
/** Resolves the native artifact behind a successful managed launcher selection. */
|
|
2432
|
-
function resolveManagedCodexNativeCommand(command, options = {}) {
|
|
2433
|
-
const platform = options.platform ?? process.platform;
|
|
2434
|
-
if (platform === "darwin" && MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS.some((candidate) => candidate === command)) return command;
|
|
2435
|
-
const target = resolveCodexNativeTarget(platform, options.arch ?? process.arch);
|
|
2436
|
-
if (!target) return;
|
|
2437
|
-
const packageRoot = resolveManagedCodexPackageRootForCommand(command, platform);
|
|
2438
|
-
if (!packageRoot) return;
|
|
2439
|
-
const resolvePackageJson = options.resolvePackageJson ?? resolvePackageJsonFromRoot;
|
|
2440
|
-
const pathExists = options.pathExists ?? existsSync;
|
|
2441
|
-
for (const packageName of [target.packageName, MANAGED_CODEX_APP_SERVER_PACKAGE]) {
|
|
2442
|
-
const packageJsonPath = resolvePackageJson(packageName, packageRoot);
|
|
2443
|
-
if (!packageJsonPath) continue;
|
|
2444
|
-
const candidate = path.join(path.dirname(packageJsonPath), "vendor", target.triple, "bin", platform === "win32" ? "codex.exe" : "codex");
|
|
2445
|
-
if (pathExists(candidate)) return candidate;
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
function resolveManagedCodexPackageRootForCommand(command, platform) {
|
|
2449
|
-
const pathApi = pathForPlatform(platform);
|
|
2450
|
-
const commandPaths = [command];
|
|
2451
|
-
try {
|
|
2452
|
-
commandPaths.unshift(realpathSync(command));
|
|
2453
|
-
} catch {}
|
|
2454
|
-
for (const commandPath of commandPaths) {
|
|
2455
|
-
let current = pathApi.dirname(commandPath);
|
|
2456
|
-
while (true) {
|
|
2457
|
-
if (pathApi.basename(current) === "codex" && pathApi.basename(pathApi.dirname(current)) === "@openai") return current;
|
|
2458
|
-
if (pathApi.basename(current) === ".bin") return pathApi.join(pathApi.dirname(current), "@openai", "codex");
|
|
2459
|
-
const parent = pathApi.dirname(current);
|
|
2460
|
-
if (parent === current) break;
|
|
2461
|
-
current = parent;
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
function resolveCodexNativeTarget(platform, arch) {
|
|
2466
|
-
if ((platform === "linux" || platform === "android") && arch === "x64") return {
|
|
2467
|
-
packageName: "@openai/codex-linux-x64",
|
|
2468
|
-
triple: "x86_64-unknown-linux-musl"
|
|
2469
|
-
};
|
|
2470
|
-
if ((platform === "linux" || platform === "android") && arch === "arm64") return {
|
|
2471
|
-
packageName: "@openai/codex-linux-arm64",
|
|
2472
|
-
triple: "aarch64-unknown-linux-musl"
|
|
2473
|
-
};
|
|
2474
|
-
if (platform === "darwin" && arch === "x64") return {
|
|
2475
|
-
packageName: "@openai/codex-darwin-x64",
|
|
2476
|
-
triple: "x86_64-apple-darwin"
|
|
2477
|
-
};
|
|
2478
|
-
if (platform === "darwin" && arch === "arm64") return {
|
|
2479
|
-
packageName: "@openai/codex-darwin-arm64",
|
|
2480
|
-
triple: "aarch64-apple-darwin"
|
|
2481
|
-
};
|
|
2482
|
-
if (platform === "win32" && arch === "x64") return {
|
|
2483
|
-
packageName: "@openai/codex-win32-x64",
|
|
2484
|
-
triple: "x86_64-pc-windows-msvc"
|
|
2485
|
-
};
|
|
2486
|
-
if (platform === "win32" && arch === "arm64") return {
|
|
2487
|
-
packageName: "@openai/codex-win32-arm64",
|
|
2488
|
-
triple: "aarch64-pc-windows-msvc"
|
|
2489
|
-
};
|
|
2490
|
-
}
|
|
2491
|
-
function resolvePackageJsonFromRoot(packageName, root) {
|
|
2492
|
-
try {
|
|
2493
|
-
return createRequire(path.join(root, "package.json")).resolve(`${packageName}/package.json`);
|
|
2494
|
-
} catch {
|
|
2495
|
-
return;
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
/** Returns the preferred and fallback managed Codex binary paths for a plugin root. */
|
|
2499
|
-
function resolveManagedCodexAppServerPaths(params) {
|
|
2500
|
-
const platform = params.platform ?? process.platform;
|
|
2501
|
-
const candidateCommandPaths = resolveManagedCodexAppServerCommandCandidates(params.pluginRoot ?? CODEX_PLUGIN_ROOT, platform, params.managedCommandOrder ?? "package-first");
|
|
2502
|
-
return {
|
|
2503
|
-
commandPath: candidateCommandPaths[0] ?? "",
|
|
2504
|
-
candidateCommandPaths
|
|
2505
|
-
};
|
|
2506
|
-
}
|
|
2507
|
-
function resolveManagedCodexAppServerCommandCandidates(pluginRoot, platform, managedCommandOrder) {
|
|
2508
|
-
const pathApi = pathForPlatform(platform);
|
|
2509
|
-
const commandName = platform === "win32" ? "codex.cmd" : "codex";
|
|
2510
|
-
const roots = resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform);
|
|
2511
|
-
const packageCommandPaths = [...roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)), ...resolveManagedCodexPackageBinCandidates(roots, platform)];
|
|
2512
|
-
const desktopCommandPaths = resolveDesktopCodexAppServerCommandCandidates(platform);
|
|
2513
|
-
const orderedCommandPaths = managedCommandOrder === "desktop-first" ? [...desktopCommandPaths, ...packageCommandPaths] : [...packageCommandPaths, ...desktopCommandPaths];
|
|
2514
|
-
return [...new Set(orderedCommandPaths)];
|
|
2515
|
-
}
|
|
2516
|
-
function resolveDesktopCodexAppServerCommandCandidates(platform) {
|
|
2517
|
-
return platform === "darwin" ? [...MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS] : [];
|
|
2518
|
-
}
|
|
2519
|
-
function resolveDefaultCodexPluginRoot(moduleDir) {
|
|
2520
|
-
const moduleBaseName = path.basename(moduleDir);
|
|
2521
|
-
if (moduleBaseName === "dist" || moduleBaseName === "dist-runtime") return path.dirname(moduleDir);
|
|
2522
|
-
return path.resolve(moduleDir, "..", "..");
|
|
2523
|
-
}
|
|
2524
|
-
function resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform) {
|
|
2525
|
-
const pathApi = pathForPlatform(platform);
|
|
2526
|
-
const directRoots = [
|
|
2527
|
-
pluginRoot,
|
|
2528
|
-
pathApi.dirname(pluginRoot),
|
|
2529
|
-
pathApi.dirname(pathApi.dirname(pluginRoot)),
|
|
2530
|
-
isDistExtensionRoot(pluginRoot, platform) ? pathApi.dirname(pathApi.dirname(pathApi.dirname(pluginRoot))) : null
|
|
2531
|
-
].filter((root) => Boolean(root));
|
|
2532
|
-
return [.../* @__PURE__ */ new Set([...directRoots, ...resolveNearestNodeModulesProjectRoots(directRoots, platform)])];
|
|
2533
|
-
}
|
|
2534
|
-
function resolveNearestNodeModulesProjectRoots(roots, platform) {
|
|
2535
|
-
const pathApi = pathForPlatform(platform);
|
|
2536
|
-
const projectRoots = [];
|
|
2537
|
-
for (const root of roots) {
|
|
2538
|
-
let current = pathApi.resolve(root);
|
|
2539
|
-
while (true) {
|
|
2540
|
-
if (pathApi.basename(current) === "node_modules") {
|
|
2541
|
-
projectRoots.push(pathApi.dirname(current));
|
|
2542
|
-
break;
|
|
2543
|
-
}
|
|
2544
|
-
const parent = pathApi.dirname(current);
|
|
2545
|
-
if (parent === current) break;
|
|
2546
|
-
current = parent;
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
return projectRoots;
|
|
2550
|
-
}
|
|
2551
|
-
function resolveManagedCodexPackageBinCandidates(roots, platform) {
|
|
2552
|
-
if (platform === "win32") return [];
|
|
2553
|
-
const candidates = [];
|
|
2554
|
-
for (const root of roots) {
|
|
2555
|
-
const candidate = resolveManagedCodexPackageBinCandidate(root);
|
|
2556
|
-
if (candidate) candidates.push(candidate);
|
|
2557
|
-
}
|
|
2558
|
-
return candidates;
|
|
2559
|
-
}
|
|
2560
|
-
function resolveManagedCodexPackageBinCandidate(root) {
|
|
2561
|
-
try {
|
|
2562
|
-
const packageJsonPath = createRequire(path.join(root, "package.json")).resolve(`${MANAGED_CODEX_APP_SERVER_PACKAGE}/package.json`);
|
|
2563
|
-
const packageRoot = path.dirname(packageJsonPath);
|
|
2564
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
2565
|
-
const packageBin = packageJson.bin && typeof packageJson.bin === "object" ? packageJson.bin : void 0;
|
|
2566
|
-
const binPath = typeof packageJson.bin === "string" ? packageJson.bin : typeof packageBin?.codex === "string" ? packageBin.codex : null;
|
|
2567
|
-
return binPath ? path.resolve(packageRoot, binPath) : null;
|
|
2568
|
-
} catch {
|
|
2569
|
-
return null;
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
function isDistExtensionRoot(pluginRoot, platform) {
|
|
2573
|
-
const pathApi = pathForPlatform(platform);
|
|
2574
|
-
const extensionsDir = pathApi.dirname(pluginRoot);
|
|
2575
|
-
const distDir = pathApi.dirname(extensionsDir);
|
|
2576
|
-
return pathApi.basename(extensionsDir) === "extensions" && (pathApi.basename(distDir) === "dist" || pathApi.basename(distDir) === "dist-runtime");
|
|
2577
|
-
}
|
|
2578
|
-
function pathForPlatform(platform) {
|
|
2579
|
-
return platform === "win32" ? path.win32 : path.posix;
|
|
2580
|
-
}
|
|
2581
|
-
async function findManagedCodexAppServerCommandPaths(params) {
|
|
2582
|
-
const commandPaths = [];
|
|
2583
|
-
for (const commandPath of params.candidateCommandPaths) if (await params.pathExists(commandPath, params.platform)) commandPaths.push(commandPath);
|
|
2584
|
-
if (commandPaths.length > 0) return commandPaths;
|
|
2585
|
-
throw new Error([
|
|
2586
|
-
`Managed Codex app-server binary was not found for ${MANAGED_CODEX_APP_SERVER_PACKAGE}.`,
|
|
2587
|
-
"Reinstall or update OpenClaw, or run pnpm install in a source checkout.",
|
|
2588
|
-
"Set plugins.entries.codex.config.appServer.command or OPENCLAW_CODEX_APP_SERVER_BIN to use a custom Codex binary."
|
|
2589
|
-
].join(" "));
|
|
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
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
//#endregion
|
|
2600
3081
|
//#region extensions/codex/src/app-server/native-config-fence.ts
|
|
2601
3082
|
const CODEX_NATIVE_CONFIG_FENCE_STATE = Symbol.for("openclaw.codexNativeConfigFenceState");
|
|
2602
3083
|
function getFenceState() {
|
|
@@ -3088,18 +3569,19 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3088
3569
|
onStartedClient: (startedClient) => {
|
|
3089
3570
|
const state = getSharedCodexAppServerClientState();
|
|
3090
3571
|
params.entry.client = startedClient;
|
|
3091
|
-
state.entriesByClient.set(startedClient, params.entry);
|
|
3092
3572
|
state.liveClients.add(startedClient);
|
|
3093
3573
|
startedClient.addCloseHandler((closedClient) => {
|
|
3094
|
-
|
|
3095
|
-
clearSharedClientEntryIfCurrent(params.key, closedClient);
|
|
3574
|
+
state.liveClients.delete(closedClient);
|
|
3575
|
+
if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
|
|
3096
3576
|
});
|
|
3097
3577
|
for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
|
|
3098
3578
|
retirePendingSharedClientEntryIfUnclaimed(params.key, params.entry);
|
|
3099
3579
|
},
|
|
3100
3580
|
onInitializedClient: () => initialized.resolve()
|
|
3101
3581
|
}).then((client) => {
|
|
3582
|
+
const state = getSharedCodexAppServerClientState();
|
|
3102
3583
|
params.entry.client = client;
|
|
3584
|
+
state.entriesByClient.set(client, params.entry);
|
|
3103
3585
|
return client;
|
|
3104
3586
|
}, (error) => {
|
|
3105
3587
|
initialized.reject(error);
|
|
@@ -3139,7 +3621,7 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3139
3621
|
const timeoutMs = params.timeoutMs ?? 0;
|
|
3140
3622
|
const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
|
|
3141
3623
|
for (const [index, startOptions] of startOptionsCandidates.entries()) {
|
|
3142
|
-
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-
|
|
3624
|
+
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BvyT1uyd.js") : void 0;
|
|
3143
3625
|
const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
3144
3626
|
const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
|
|
3145
3627
|
startOptions,
|
|
@@ -3430,4 +3912,4 @@ function collectSharedClients(state) {
|
|
|
3430
3912
|
return [...state.liveClients];
|
|
3431
3913
|
}
|
|
3432
3914
|
//#endregion
|
|
3433
|
-
export {
|
|
3915
|
+
export { rememberCodexRateLimitsRead as $, isCodexAppServerApprovalRequest as A, CodexAppServerRpcError as B, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as C, resolveCodexTurnTerminalIdleTimeoutMs as D, resolveCodexTurnCompletionIdleTimeoutMs as E, isCodexAppServerPrewriteRequestCancellationError as F, isCodexAppServerClientRuntimeLive as G, claimCodexAppServerLiveThread as H, isCodexAppServerRequestTimeoutError as I, releaseCodexAppServerLiveThread as J, isCodexAppServerLiveThreadClaimed as K, isUnsupportedCodexAppServerVersionError as L, isCodexAppServerConnectionClosedError as M, isCodexAppServerIndeterminateRequestCancellationError as N, withCodexStartupTimeout as O, isCodexAppServerIndeterminateTransportError as P, readRecentCodexRateLimits as Q, resolveCodexAppServerClientInstanceId as R, resolveCodexGatewayTimeoutWithGraceMs as S, resolveCodexTurnAssistantCompletionIdleTimeoutMs as T, consumeCodexAppServerLiveThread as U, createCodexElicitationResponse as V, ensureCodexAppServerClientRuntime as W, unsubscribeCodexAppServerLiveThread as X, retainCodexAppServerLiveThread as Y, readCodexRateLimitsRevision as Z, withTimeout$1 as _, getLeasedSharedCodexAppServerClient as a, resolveCodexAppServerPreparedAuthHandoff as at, CodexAppServerStartupError as b, releaseCodexAppServerClientLease as c, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as ct, retainSharedCodexAppServerClientByInstanceId as d, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as dt, resolveCodexAppServerAuthAccountCacheKey as et, retainSharedCodexAppServerClientIfCurrent as f, resolveCodexAppServerHomeDir as ft, withAbortableTimeout as g, withLeasedCodexAppServerClientStartSelectionRetry as h, createIsolatedCodexAppServerClient as i, resolveCodexAppServerPreparedApiKeyCacheKey as it, isCodexAppServerBrokenPipeError as j, getCodexAppServerClientInstanceId as k, releaseLeasedSharedCodexAppServerClient as l, flattenCodexDynamicToolFunctions as lt, shared_client_exports as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, resolveCodexAppServerAuthProfileIdForAgent as nt, getSharedCodexAppServerClient as o, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as ot, retireSharedCodexAppServerClientIfCurrent as p, resolveCodexAppServerLocalHomeDir as pt, protectCodexAppServerLiveThread as q, clearSharedCodexAppServerClientIfCurrentAndWait as r, resolveCodexAppServerFallbackApiKeyCacheKey as rt, isCodexAppServerStartSelectionChangedError as s, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as st, clearSharedCodexAppServerClientIfCurrent as t, resolveCodexAppServerAuthProfileId as tt, resolveCodexNativeConfigFenceKey as u, isJsonObject as ut, acquireCodexNativeConfigFence as v, resolveCodexStartupTimeoutMs as w, isCodexAppServerStartupError as x, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as y, closeCodexAppServerTransportAndWait as z };
|