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