@openclaw/codex 2026.7.1-beta.6 → 2026.7.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -3
- package/dist/app-server-policy-DVcxdse_.js +49 -0
- package/dist/{attempt-notifications-BGsEIIDI.js → attempt-notifications-DEv9h7iC.js} +1 -1
- package/dist/auth-binding-C1czuHPA.js +62 -0
- package/dist/capabilities-42Dfn2TV.js +33 -0
- package/dist/cli-metadata.js +20 -0
- package/dist/{app-server-policy-C968Kgin.js → command-formatters-D6ZlGNmb.js} +8 -51
- package/dist/{command-handlers-Dv-pzAmg.js → command-handlers-Cpl9fUWv.js} +163 -60
- package/dist/{compact-4mVBriKT.js → compact-FnJmiPNA.js} +40 -10
- package/dist/computer-use-DVzr8OP1.js +685 -0
- package/dist/{config-CYEDnLJ2.js → config-c48K5HP9.js} +200 -29
- package/dist/conversation-control-DtQ-E7mr.js +288 -0
- package/dist/doctor-contract-api.js +120 -81
- package/dist/{provider-capabilities-CDnHbmUZ.js → dynamic-tools-BMLoaTeG.js} +5246 -5140
- package/dist/harness.js +55 -15
- package/dist/index.js +1435 -265
- package/dist/{media-understanding-provider-BHOu9-Gg.js → media-understanding-provider-D580L0P8.js} +28 -10
- package/dist/media-understanding-provider.js +1 -1
- package/dist/{models-DDmO1zwd.js → models-DuKzZA6G.js} +1 -1
- package/dist/notification-correlation-DfaCm0mx.js +67 -0
- package/dist/plugin-app-cache-key-BKNjHMEs.js +315 -0
- package/dist/{protocol-2POPqAY4.js → protocol-BMifTfdW.js} +3 -1
- package/dist/provider-catalog.js +66 -27
- package/dist/provider.js +306 -1
- package/dist/{provider-zjPfx5Fs.js → rate-limits-Dhp04Rqo.js} +4 -261
- package/dist/request-DborTWgw.js +111 -0
- package/dist/{run-attempt-FUyOjGCV.js → run-attempt-VPVJoYDP.js} +7897 -7136
- package/dist/runtime-artifact-C2ITXdhM.js +488 -0
- package/dist/{sandbox-guard-DA2TQfZW.js → sandbox-guard-D1Wky__S.js} +10 -1
- package/dist/{session-binding-C1ZXdP-x.js → session-binding-BMfX1OX-.js} +247 -88
- package/dist/session-catalog-7H112Tr_.js +2396 -0
- package/dist/session-cli-B28RhCyp.js +175 -0
- package/dist/{shared-client-4ICy3U6d.js → shared-client-D4mFI9al.js} +1997 -1297
- package/dist/{side-question-N4OaDer2.js → side-question-CgJBz52s.js} +135 -67
- package/dist/{thread-lifecycle-qWE88Dn2.js → thread-lifecycle-BgLXzjvV.js} +747 -317
- package/dist/transcript-mirror-DhLwFIL4.js +485 -0
- package/dist/transport-stdio-DXgGZ7Ib.js +88 -0
- package/dist/{web-search-provider.runtime-BSlriav6.js → web-search-provider.runtime-Bs-eTn5U.js} +3 -3
- package/npm-shrinkwrap.json +56 -30
- package/openclaw.plugin.json +168 -10
- package/package.json +8 -6
- package/dist/command-rpc-eLM_mtC7.js +0 -784
- package/dist/computer-use-Bmaz333N.js +0 -377
- package/dist/notification-correlation-Bo7KB3ks.js +0 -35
- package/dist/plugin-app-cache-key-BrhVdeEf.js +0 -98
- package/dist/request-BcJyl8KL.js +0 -36
|
@@ -1,977 +1,407 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { a as isRpcResponse, i as isJsonObject$1 } from "./protocol-BMifTfdW.js";
|
|
3
|
+
import { a as codexAppServerStartOptionsKey, f as resolveCodexAppServerRuntimeOptions, h as resolveCodexComputerUseConfig, m as resolveCodexAppServerUserHomeDir, p as resolveCodexAppServerStartOptionsForAgent } from "./config-c48K5HP9.js";
|
|
4
|
+
import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DXgGZ7Ib.js";
|
|
5
|
+
import { a as isCodexAppServerNativeAuthProfile } from "./session-binding-BMfX1OX-.js";
|
|
4
6
|
import { createRequire } from "node:module";
|
|
5
7
|
import fs, { access } from "node:fs/promises";
|
|
6
8
|
import path from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
10
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
11
|
+
import fsSync, { constants, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
9
12
|
import os from "node:os";
|
|
10
13
|
import { ensureAuthProfileStore, findPersistedAuthProfileCredential, loadAuthProfileStoreForSecretsRuntime, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolveAuthProfileOrder, resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, resolveProviderIdForAuth } from "openclaw/plugin-sdk/agent-runtime";
|
|
11
|
-
import { materializeWindowsSpawnProgram, resolveWindowsSpawnProgram } from "openclaw/plugin-sdk/windows-spawn";
|
|
12
14
|
import { OPENCLAW_VERSION, embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
13
|
-
import { isDeepStrictEqual } from "node:util";
|
|
14
|
-
import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
|
|
15
|
-
import { spawn } from "node:child_process";
|
|
16
15
|
import { createInterface } from "node:readline";
|
|
16
|
+
import { parse } from "semver";
|
|
17
17
|
import { EventEmitter } from "node:events";
|
|
18
|
+
import net from "node:net";
|
|
18
19
|
import { PassThrough, Writable } from "node:stream";
|
|
19
20
|
import WebSocket from "ws";
|
|
21
|
+
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
22
|
+
import { isDeepStrictEqual } from "node:util";
|
|
23
|
+
import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
|
|
20
24
|
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
21
26
|
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
22
|
-
//#region extensions/codex/src/app-server/transport-
|
|
27
|
+
//#region extensions/codex/src/app-server/transport-websocket.ts
|
|
23
28
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
29
|
+
* Adapts a remote Codex app-server WebSocket endpoint to the shared stdio-like
|
|
30
|
+
* transport interface.
|
|
26
31
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function resolveCodexAppServerSpawnInvocation(options, runtime = DEFAULT_SPAWN_RUNTIME) {
|
|
40
|
-
if (options.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
|
|
41
|
-
const resolved = materializeWindowsSpawnProgram(resolveWindowsSpawnProgram({
|
|
42
|
-
command: options.command,
|
|
43
|
-
platform: runtime.platform,
|
|
44
|
-
env: runtime.env,
|
|
45
|
-
execPath: runtime.execPath,
|
|
46
|
-
packageName: "@openai/codex"
|
|
47
|
-
}), options.args);
|
|
48
|
-
return {
|
|
49
|
-
command: resolved.command,
|
|
50
|
-
args: resolved.argv,
|
|
51
|
-
shell: resolved.shell,
|
|
52
|
-
windowsHide: resolved.windowsHide
|
|
32
|
+
/** Opens a WebSocket app-server transport and maps newline-delimited frames to stdout/stdin. */
|
|
33
|
+
function createWebSocketTransport(options) {
|
|
34
|
+
if (!options.url) throw new Error("codex app-server websocket transport requires plugins.entries.codex.config.appServer.url");
|
|
35
|
+
const events = new EventEmitter();
|
|
36
|
+
const stdout = new PassThrough();
|
|
37
|
+
const stderr = new PassThrough();
|
|
38
|
+
const websocketOptions = {
|
|
39
|
+
headers: {
|
|
40
|
+
...options.headers,
|
|
41
|
+
...options.authToken ? { Authorization: `Bearer ${options.authToken}` } : {}
|
|
42
|
+
},
|
|
43
|
+
perMessageDeflate: false
|
|
53
44
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const keys = [];
|
|
73
|
-
for (const rawKey of rawKeys) {
|
|
74
|
-
const key = rawKey.trim();
|
|
75
|
-
if (key.length > 0) keys.push(key);
|
|
76
|
-
}
|
|
77
|
-
return keys;
|
|
78
|
-
}
|
|
79
|
-
function copySafeEnvironmentEntries(target, source) {
|
|
80
|
-
for (const [key, value] of Object.entries(source)) {
|
|
81
|
-
if (UNSAFE_ENVIRONMENT_KEYS.has(key)) continue;
|
|
82
|
-
target[key] = value;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/** Spawns the Codex app-server process and returns the shared transport interface. */
|
|
86
|
-
function createStdioTransport(options) {
|
|
87
|
-
const env = resolveCodexAppServerSpawnEnv(options);
|
|
88
|
-
const invocation = resolveCodexAppServerSpawnInvocation(options, {
|
|
89
|
-
platform: process.platform,
|
|
90
|
-
env,
|
|
91
|
-
execPath: process.execPath
|
|
92
|
-
});
|
|
93
|
-
return spawn(invocation.command, invocation.args, {
|
|
94
|
-
env,
|
|
95
|
-
detached: resolveCodexAppServerDetachedMode(env),
|
|
96
|
-
shell: invocation.shell,
|
|
97
|
-
stdio: [
|
|
98
|
-
"pipe",
|
|
99
|
-
"pipe",
|
|
100
|
-
"pipe"
|
|
101
|
-
],
|
|
102
|
-
windowsHide: invocation.windowsHide
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
//#endregion
|
|
106
|
-
//#region extensions/codex/src/app-server/auth-bridge.ts
|
|
107
|
-
const CODEX_APP_SERVER_AUTH_PROVIDER = "openai";
|
|
108
|
-
const OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER = "openai-codex";
|
|
109
|
-
const LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER = "codex-cli";
|
|
110
|
-
const CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS = [CODEX_APP_SERVER_AUTH_PROVIDER, LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER];
|
|
111
|
-
const OPENAI_PROVIDER = "openai";
|
|
112
|
-
const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai:default";
|
|
113
|
-
const CODEX_HOME_ENV_VAR = "CODEX_HOME";
|
|
114
|
-
const HOME_ENV_VAR = "HOME";
|
|
115
|
-
const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
|
|
116
|
-
const CODEX_APP_SERVER_API_KEY_ENV_VARS = ["CODEX_API_KEY", "OPENAI_API_KEY"];
|
|
117
|
-
const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
|
|
118
|
-
const CODEX_AUTH_JSON_FILENAME = "auth.json";
|
|
119
|
-
const CODEX_HOME_DIRNAME = ".codex";
|
|
120
|
-
const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
|
|
121
|
-
async function bridgeCodexAppServerStartOptions(params) {
|
|
122
|
-
if (params.startOptions.transport !== "stdio") return params.startOptions;
|
|
123
|
-
const scopedStartOptions = await withCodexHomeEnvironment(params.startOptions, params.agentDir);
|
|
124
|
-
if (params.authProfileId === null) return scopedStartOptions;
|
|
125
|
-
const store = resolveCodexAppServerAuthProfileStore({
|
|
126
|
-
agentDir: params.agentDir,
|
|
127
|
-
authProfileId: params.authProfileId,
|
|
128
|
-
authProfileStore: params.authProfileStore,
|
|
129
|
-
config: params.config
|
|
130
|
-
});
|
|
131
|
-
return shouldClearOpenAiApiKeyForCodexAuthProfile({
|
|
132
|
-
store,
|
|
133
|
-
authProfileId: resolveCodexAppServerAuthProfileId({
|
|
134
|
-
authProfileId: params.authProfileId,
|
|
135
|
-
store,
|
|
136
|
-
config: params.config
|
|
137
|
-
}),
|
|
138
|
-
config: params.config
|
|
139
|
-
}) ? withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS) : scopedStartOptions;
|
|
140
|
-
}
|
|
141
|
-
function resolveCodexAppServerAuthProfileId(params) {
|
|
142
|
-
const requested = params.authProfileId?.trim();
|
|
143
|
-
if (requested) return requested;
|
|
144
|
-
return resolveAuthProfileOrder({
|
|
145
|
-
cfg: params.config,
|
|
146
|
-
store: params.store,
|
|
147
|
-
provider: CODEX_APP_SERVER_AUTH_PROVIDER
|
|
148
|
-
})[0]?.trim();
|
|
149
|
-
}
|
|
150
|
-
function resolveCodexAppServerAuthProfileIdForAgent(params) {
|
|
151
|
-
const store = resolveCodexAppServerAuthProfileStore({
|
|
152
|
-
agentDir: params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {}),
|
|
153
|
-
authProfileId: params.authProfileId,
|
|
154
|
-
authProfileStore: params.authProfileStore,
|
|
155
|
-
config: params.config
|
|
156
|
-
});
|
|
157
|
-
return resolveCodexAppServerAuthProfileId({
|
|
158
|
-
authProfileId: params.authProfileId,
|
|
159
|
-
store,
|
|
160
|
-
config: params.config
|
|
45
|
+
const unixSocketPath = resolveCodexAppServerUnixSocketPath(options);
|
|
46
|
+
const socket = unixSocketPath ? new WebSocket("ws://localhost/", {
|
|
47
|
+
...websocketOptions,
|
|
48
|
+
createConnection: () => connectCodexAppServerUnixSocket(unixSocketPath)
|
|
49
|
+
}) : new WebSocket(options.url, websocketOptions);
|
|
50
|
+
const pendingFrames = [];
|
|
51
|
+
let killed = false;
|
|
52
|
+
const sendFrame = (frame) => {
|
|
53
|
+
const trimmed = frame.trim();
|
|
54
|
+
if (!trimmed) return;
|
|
55
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
56
|
+
socket.send(trimmed);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
pendingFrames.push(trimmed);
|
|
60
|
+
};
|
|
61
|
+
socket.once("open", () => {
|
|
62
|
+
for (const frame of pendingFrames.splice(0)) socket.send(frame);
|
|
161
63
|
});
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
config: params.config,
|
|
167
|
-
externalCliProviderIds: CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS,
|
|
168
|
-
...params.authProfileId ? { externalCliProfileIds: [params.authProfileId] } : {}
|
|
64
|
+
socket.once("error", (error) => events.emit("error", error));
|
|
65
|
+
socket.once("close", (code, reason) => {
|
|
66
|
+
killed = true;
|
|
67
|
+
events.emit("exit", code, reason.toString("utf8"));
|
|
169
68
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const providedProfileId = resolveCodexAppServerAuthProfileId({
|
|
174
|
-
authProfileId: params.authProfileId,
|
|
175
|
-
store: params.authProfileStore,
|
|
176
|
-
config: params.config
|
|
177
|
-
});
|
|
178
|
-
if (providedProfileId && params.authProfileStore.profiles[providedProfileId]) return params.authProfileStore;
|
|
179
|
-
}
|
|
180
|
-
const overlaidStore = ensureCodexAppServerAuthProfileStore({
|
|
181
|
-
agentDir: params.agentDir,
|
|
182
|
-
authProfileId: params.authProfileId,
|
|
183
|
-
config: params.config
|
|
69
|
+
socket.on("message", (data) => {
|
|
70
|
+
const text = websocketFrameToText(data);
|
|
71
|
+
stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
184
72
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
...params.authProfileStore.profiles
|
|
73
|
+
const stdin = new Writable({ write(chunk, _encoding, callback) {
|
|
74
|
+
for (const frame of chunk.toString("utf8").split("\n")) sendFrame(frame);
|
|
75
|
+
callback();
|
|
76
|
+
} });
|
|
77
|
+
const closeSocket = () => {
|
|
78
|
+
if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) return;
|
|
79
|
+
socket.close();
|
|
193
80
|
};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const runtimePersistedProfileIds = mergeRuntimeProfileIds(overlaidStore.runtimePersistedProfileIds, params.authProfileStore.runtimePersistedProfileIds).filter((profileId) => profiles[profileId]);
|
|
197
|
-
const runtimeExternalProfileIds = mergeRuntimeProfileIds(overlaidStore.runtimeExternalProfileIds, params.authProfileStore.runtimeExternalProfileIds).filter((profileId) => profiles[profileId]);
|
|
198
|
-
const runtimeExternalProfileIdsAuthoritative = overlaidStore.runtimeExternalProfileIdsAuthoritative === true || params.authProfileStore.runtimeExternalProfileIdsAuthoritative === true;
|
|
81
|
+
stdin.once("finish", closeSocket);
|
|
82
|
+
stdin.once("close", closeSocket);
|
|
199
83
|
return {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
84
|
+
stdin,
|
|
85
|
+
stdout,
|
|
86
|
+
stderr,
|
|
87
|
+
get killed() {
|
|
88
|
+
return killed;
|
|
89
|
+
},
|
|
90
|
+
kill: () => {
|
|
91
|
+
killed = true;
|
|
92
|
+
socket.close();
|
|
93
|
+
},
|
|
94
|
+
once: (event, listener) => events.once(event, listener)
|
|
208
95
|
};
|
|
209
96
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
agentDir,
|
|
214
|
-
authProfileId: params.authProfileId,
|
|
215
|
-
authProfileStore: params.authProfileStore,
|
|
216
|
-
config: params.config
|
|
217
|
-
});
|
|
218
|
-
const profileId = resolveCodexAppServerAuthProfileId({
|
|
219
|
-
authProfileId: params.authProfileId,
|
|
220
|
-
store,
|
|
221
|
-
config: params.config
|
|
222
|
-
});
|
|
223
|
-
if (!profileId) return;
|
|
224
|
-
const credential = store.profiles[profileId];
|
|
225
|
-
if (!credential || !isCodexAppServerAuthProfileCredential(credential, params.config)) return;
|
|
226
|
-
if (credential.type === "api_key") {
|
|
227
|
-
const apiKey = (await resolveApiKeyForProfile({
|
|
228
|
-
store,
|
|
229
|
-
profileId,
|
|
230
|
-
agentDir
|
|
231
|
-
}))?.apiKey?.trim();
|
|
232
|
-
return apiKey ? `${resolveChatgptAccountId(profileId, credential)}:${fingerprintApiKeyAuthProfileCacheKey(apiKey)}` : resolveChatgptAccountId(profileId, credential);
|
|
233
|
-
}
|
|
234
|
-
if (credential.type === "token") {
|
|
235
|
-
const accessToken = (await resolveApiKeyForProfile({
|
|
236
|
-
store,
|
|
237
|
-
profileId,
|
|
238
|
-
agentDir
|
|
239
|
-
}))?.apiKey?.trim();
|
|
240
|
-
return accessToken ? `${resolveChatgptAccountId(profileId, credential)}:${fingerprintTokenAuthProfileCacheKey(accessToken)}` : resolveChatgptAccountId(profileId, credential);
|
|
241
|
-
}
|
|
242
|
-
return resolveChatgptAccountId(profileId, credential);
|
|
97
|
+
/** Opens the owner-scoped Codex control socket used by the WebSocket upgrade. */
|
|
98
|
+
function connectCodexAppServerUnixSocket(socketPath) {
|
|
99
|
+
return net.createConnection(socketPath);
|
|
243
100
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
hash.update(apiKey.value);
|
|
254
|
-
return `${apiKey.key}:sha256:${hash.digest("hex")}`;
|
|
101
|
+
/** Resolves the canonical or explicitly configured Codex control socket. */
|
|
102
|
+
function resolveCodexAppServerUnixSocketPath(options) {
|
|
103
|
+
if (options.transport !== "unix") {
|
|
104
|
+
if (options.url?.startsWith("unix://")) throw new Error("codex app-server unix URL requires unix transport");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const url = options.url ?? "unix://";
|
|
108
|
+
if (!url.startsWith("unix://")) throw new Error("codex app-server unix transport requires a unix:// URL");
|
|
109
|
+
return url.slice(7) || path.join(resolveCodexAppServerUserHomeDir(options.env ?? process.env), "app-server-control", "app-server-control.sock");
|
|
255
110
|
}
|
|
256
|
-
function
|
|
257
|
-
if (
|
|
258
|
-
|
|
111
|
+
function websocketFrameToText(data) {
|
|
112
|
+
if (typeof data === "string") return data;
|
|
113
|
+
if (Buffer.isBuffer(data)) return data.toString("utf8");
|
|
114
|
+
if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
|
|
115
|
+
return Buffer.from(data).toString("utf8");
|
|
259
116
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region extensions/codex/src/app-server/transport.ts
|
|
119
|
+
/** Starts graceful transport shutdown and schedules a force kill fallback. */
|
|
120
|
+
function closeCodexAppServerTransport(child, options = {}) {
|
|
121
|
+
child.stdin.end?.();
|
|
122
|
+
child.stdin.destroy?.();
|
|
123
|
+
const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
|
|
124
|
+
const forceKill = setTimeout(() => {
|
|
125
|
+
if (hasCodexAppServerTransportExited(child)) return;
|
|
126
|
+
signalCodexAppServerTransport(child, "SIGKILL");
|
|
127
|
+
}, Math.max(1, forceKillDelayMs));
|
|
128
|
+
forceKill.unref?.();
|
|
129
|
+
child.once("exit", () => {
|
|
130
|
+
clearTimeout(forceKill);
|
|
131
|
+
child.stdout.destroy?.();
|
|
132
|
+
child.stderr.destroy?.();
|
|
133
|
+
});
|
|
134
|
+
child.unref?.();
|
|
135
|
+
child.stdout.unref?.();
|
|
136
|
+
child.stderr.unref?.();
|
|
137
|
+
child.stdin.unref?.();
|
|
266
138
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
hash.update(accessToken);
|
|
272
|
-
return `token:sha256:${hash.digest("hex")}`;
|
|
139
|
+
/** Closes a transport and waits briefly for an exit event. */
|
|
140
|
+
async function closeCodexAppServerTransportAndWait(child, options = {}) {
|
|
141
|
+
if (!hasCodexAppServerTransportExited(child)) closeCodexAppServerTransport(child, options);
|
|
142
|
+
return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
|
|
273
143
|
}
|
|
274
|
-
function
|
|
275
|
-
|
|
276
|
-
hash.update("openclaw:codex:app-server-cli-auth-json-api-key:v1");
|
|
277
|
-
hash.update("\0");
|
|
278
|
-
hash.update(apiKey);
|
|
279
|
-
return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
|
|
144
|
+
function hasCodexAppServerTransportExited(child) {
|
|
145
|
+
return child.exitCode !== null && child.exitCode !== void 0 ? true : child.signalCode !== null && child.signalCode !== void 0;
|
|
280
146
|
}
|
|
281
|
-
function
|
|
282
|
-
|
|
147
|
+
async function waitForCodexAppServerTransportExit(child, timeoutMs) {
|
|
148
|
+
if (hasCodexAppServerTransportExited(child)) return true;
|
|
149
|
+
return await new Promise((resolve) => {
|
|
150
|
+
let settled = false;
|
|
151
|
+
const onExit = () => {
|
|
152
|
+
if (settled) return;
|
|
153
|
+
settled = true;
|
|
154
|
+
clearTimeout(timeout);
|
|
155
|
+
resolve(true);
|
|
156
|
+
};
|
|
157
|
+
const timeout = setTimeout(() => {
|
|
158
|
+
if (settled) return;
|
|
159
|
+
settled = true;
|
|
160
|
+
child.off?.("exit", onExit);
|
|
161
|
+
resolve(false);
|
|
162
|
+
}, Math.max(1, timeoutMs));
|
|
163
|
+
child.once("exit", onExit);
|
|
164
|
+
});
|
|
283
165
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
...startOptions,
|
|
291
|
-
env: {
|
|
292
|
-
...startOptions.env,
|
|
293
|
-
[CODEX_HOME_ENV_VAR]: codexHome,
|
|
294
|
-
...nativeHome ? { [HOME_ENV_VAR]: nativeHome } : {}
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
const clearEnv = withoutClearedCodexHomeEnv(startOptions.clearEnv);
|
|
298
|
-
if (clearEnv) nextStartOptions.clearEnv = clearEnv;
|
|
299
|
-
else delete nextStartOptions.clearEnv;
|
|
300
|
-
return nextStartOptions;
|
|
166
|
+
function signalCodexAppServerTransport(child, signal) {
|
|
167
|
+
if (child.pid && process.platform !== "win32") try {
|
|
168
|
+
process.kill(-child.pid, signal);
|
|
169
|
+
return;
|
|
170
|
+
} catch {}
|
|
171
|
+
child.kill?.(signal);
|
|
301
172
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region extensions/codex/src/app-server/version.ts
|
|
175
|
+
/**
|
|
176
|
+
* Version and package pins for the managed Codex app-server runtime.
|
|
177
|
+
*/
|
|
178
|
+
/** Minimum Codex app-server version supported by the OpenClaw Codex bridge. */
|
|
179
|
+
const MIN_CODEX_APP_SERVER_VERSION = "0.143.0";
|
|
180
|
+
/** npm package name for the managed Codex app-server binary. */
|
|
181
|
+
const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region extensions/codex/src/app-server/client.ts
|
|
184
|
+
/**
|
|
185
|
+
* JSON-RPC client for Codex app-server transports, including request/response
|
|
186
|
+
* routing, notification fanout, server request handlers, and version checks.
|
|
187
|
+
*/
|
|
188
|
+
/** Minimum supported Codex app-server version exported for callers/tests. */
|
|
189
|
+
const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
|
|
190
|
+
const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 8 * 1024 * 1024;
|
|
191
|
+
const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1e3;
|
|
192
|
+
const CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS = 6e5;
|
|
193
|
+
const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
|
|
194
|
+
const CODEX_APP_SERVER_CLIENT_INSTANCE_IDS = /* @__PURE__ */ new WeakMap();
|
|
195
|
+
const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
196
|
+
/** Process-local generation fence for bindings tied to one app-server client instance. */
|
|
197
|
+
function getCodexAppServerClientInstanceId(client) {
|
|
198
|
+
const current = CODEX_APP_SERVER_CLIENT_INSTANCE_IDS.get(client);
|
|
199
|
+
if (current) return current;
|
|
200
|
+
const created = randomUUID();
|
|
201
|
+
CODEX_APP_SERVER_CLIENT_INSTANCE_IDS.set(client, created);
|
|
202
|
+
return created;
|
|
307
203
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
});
|
|
316
|
-
if (!loginParams) {
|
|
317
|
-
if (params.startOptions?.transport !== "stdio") return;
|
|
318
|
-
const env = resolveCodexAppServerSpawnEnv(params.startOptions, process.env);
|
|
319
|
-
const fallbackLoginParams = await resolveCodexAppServerFallbackApiKeyLoginParams({
|
|
320
|
-
client: params.client,
|
|
321
|
-
env,
|
|
322
|
-
codexCliAuthEnv: process.env
|
|
323
|
-
});
|
|
324
|
-
if (fallbackLoginParams) await params.client.request("account/login/start", fallbackLoginParams);
|
|
325
|
-
return;
|
|
204
|
+
/** RPC error wrapper that preserves app-server error code and data. */
|
|
205
|
+
var CodexAppServerRpcError = class extends Error {
|
|
206
|
+
constructor(error, method) {
|
|
207
|
+
super(formatCodexAppServerRpcErrorMessage(error, method));
|
|
208
|
+
this.name = "CodexAppServerRpcError";
|
|
209
|
+
this.code = error.code;
|
|
210
|
+
this.data = error.data;
|
|
326
211
|
}
|
|
327
|
-
|
|
212
|
+
};
|
|
213
|
+
var CodexAppServerLocalRequestCancellationError = class extends Error {
|
|
214
|
+
constructor(method, reason, mayHaveWritten) {
|
|
215
|
+
super(`${method} ${reason}`);
|
|
216
|
+
this.mayHaveWritten = mayHaveWritten;
|
|
217
|
+
this.code = "CODEX_APP_SERVER_LOCAL_REQUEST_CANCELLED";
|
|
218
|
+
this.name = "CodexAppServerLocalRequestCancellationError";
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var CodexAppServerIndeterminateTransportError = class extends Error {
|
|
222
|
+
constructor(method, cause) {
|
|
223
|
+
super(`${method} transport failed after request write: ${cause.message}`, { cause });
|
|
224
|
+
this.code = "CODEX_APP_SERVER_REQUEST_TRANSPORT_INDETERMINATE";
|
|
225
|
+
this.mayHaveWritten = true;
|
|
226
|
+
this.name = "CodexAppServerIndeterminateTransportError";
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
/** True when a local cancellation can leave an app-server request in flight. */
|
|
230
|
+
function isCodexAppServerIndeterminateRequestCancellationError(error) {
|
|
231
|
+
return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_LOCAL_REQUEST_CANCELLED" && "mayHaveWritten" in error && error.mayHaveWritten === true;
|
|
328
232
|
}
|
|
329
|
-
|
|
330
|
-
|
|
233
|
+
/** True when local cancellation happened before a request write was attempted. */
|
|
234
|
+
function isCodexAppServerPrewriteRequestCancellationError(error) {
|
|
235
|
+
return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_LOCAL_REQUEST_CANCELLED" && "mayHaveWritten" in error && error.mayHaveWritten === false;
|
|
331
236
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
forceOAuthRefresh: true
|
|
336
|
-
});
|
|
337
|
-
if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
|
|
338
|
-
return {
|
|
339
|
-
accessToken: loginParams.accessToken,
|
|
340
|
-
chatgptAccountId: loginParams.chatgptAccountId,
|
|
341
|
-
chatgptPlanType: loginParams.chatgptPlanType ?? null
|
|
342
|
-
};
|
|
237
|
+
/** True when transport failure cannot prove a written request stopped running. */
|
|
238
|
+
function isCodexAppServerIndeterminateTransportError(error) {
|
|
239
|
+
return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_REQUEST_TRANSPORT_INDETERMINATE" && "mayHaveWritten" in error && error.mayHaveWritten === true;
|
|
343
240
|
}
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
authProfileStore: params.authProfileStore,
|
|
349
|
-
config: params.config
|
|
350
|
-
});
|
|
351
|
-
const profileId = resolveCodexAppServerAuthProfileId({
|
|
352
|
-
authProfileId: params.authProfileId,
|
|
353
|
-
store,
|
|
354
|
-
config: params.config
|
|
355
|
-
});
|
|
356
|
-
if (!profileId) return;
|
|
357
|
-
const credential = store.profiles[profileId];
|
|
358
|
-
if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found.`);
|
|
359
|
-
if (!isCodexAppServerAuthProfileCredential(credential, params.config)) throw new Error(`Codex app-server auth profile "${profileId}" must be OpenAI Codex auth or an OpenAI API-key backup.`);
|
|
360
|
-
const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
|
|
361
|
-
agentDir: params.agentDir,
|
|
362
|
-
store,
|
|
363
|
-
preferStoreCredential: Boolean(params.authProfileStore?.profiles[profileId]),
|
|
364
|
-
forceOAuthRefresh: params.forceOAuthRefresh === true,
|
|
365
|
-
config: params.config
|
|
366
|
-
});
|
|
367
|
-
if (!loginParams) throw new Error(`Codex app-server auth profile "${profileId}" does not contain usable credentials.`);
|
|
368
|
-
return loginParams;
|
|
241
|
+
function formatCodexAppServerRpcErrorMessage(error, method) {
|
|
242
|
+
const message = error.message || `${method} failed`;
|
|
243
|
+
const detail = readCodexAppServerRpcReloginDetail(error.data);
|
|
244
|
+
return detail && !message.includes(detail) ? `${message}: ${detail}` : message;
|
|
369
245
|
}
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
};
|
|
246
|
+
function readCodexAppServerRpcReloginDetail(data) {
|
|
247
|
+
const record = isJsonObject(data) ? data : void 0;
|
|
248
|
+
const nested = isJsonObject(record?.error) ? record.error : record;
|
|
249
|
+
if (!nested) return;
|
|
250
|
+
const isRelogin = nested.action === "relogin" || nested.reason === "cloudRequirements" && nested.errorCode === "Auth";
|
|
251
|
+
const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
|
|
252
|
+
return isRelogin && detail ? detail : void 0;
|
|
378
253
|
}
|
|
379
|
-
function
|
|
380
|
-
|
|
381
|
-
if (configuredCodexHome) return path.join(resolveHomeRelativePath(configuredCodexHome, env), CODEX_AUTH_JSON_FILENAME);
|
|
382
|
-
const home = env[HOME_ENV_VAR]?.trim() || env.USERPROFILE?.trim() || os.homedir();
|
|
383
|
-
return path.join(home, CODEX_HOME_DIRNAME, CODEX_AUTH_JSON_FILENAME);
|
|
254
|
+
function isJsonObject(value) {
|
|
255
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
384
256
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return value;
|
|
257
|
+
/** Returns true for errors that mean the app-server transport is closed. */
|
|
258
|
+
function isCodexAppServerConnectionClosedError(error) {
|
|
259
|
+
if (!(error instanceof Error)) return false;
|
|
260
|
+
if (isCodexAppServerIndeterminateTransportError(error)) return true;
|
|
261
|
+
return error.message === "codex app-server client is closed" || error.message.startsWith("codex app-server exited:");
|
|
391
262
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
263
|
+
/** Stateful app-server JSON-RPC client over stdio or websocket transport. */
|
|
264
|
+
var CodexAppServerClient = class CodexAppServerClient {
|
|
265
|
+
constructor(child) {
|
|
266
|
+
this.instanceId = randomUUID();
|
|
267
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
268
|
+
this.requestHandlers = /* @__PURE__ */ new Set();
|
|
269
|
+
this.notificationHandlers = /* @__PURE__ */ new Set();
|
|
270
|
+
this.closeHandlers = /* @__PURE__ */ new Set();
|
|
271
|
+
this.nextId = 1;
|
|
272
|
+
this.initialized = false;
|
|
273
|
+
this.closed = false;
|
|
274
|
+
this.transportExited = false;
|
|
275
|
+
this.stderrTail = "";
|
|
276
|
+
this.child = child;
|
|
277
|
+
this.lines = createInterface({ input: child.stdout });
|
|
278
|
+
this.lines.on("line", (line) => this.handleLine(line));
|
|
279
|
+
this.lines.on("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
280
|
+
child.stdout.on("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
281
|
+
child.stderr.on("data", (chunk) => {
|
|
282
|
+
const text = chunk.toString("utf8");
|
|
283
|
+
this.stderrTail = appendBoundedTail(this.stderrTail, text, CODEX_APP_SERVER_STDERR_TAIL_MAX);
|
|
284
|
+
const trimmed = text.trim();
|
|
285
|
+
if (trimmed) embeddedAgentLog.debug(`codex app-server stderr: ${trimmed}`);
|
|
286
|
+
});
|
|
287
|
+
child.stderr.on("error", (error) => {
|
|
288
|
+
embeddedAgentLog.warn("codex app-server stderr stream failed", { error });
|
|
289
|
+
});
|
|
290
|
+
child.once("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
291
|
+
child.once("exit", (code, signal) => {
|
|
292
|
+
this.transportExited = true;
|
|
293
|
+
this.closeWithError(buildCodexAppServerExitError(code, signal, this.stderrTail));
|
|
294
|
+
});
|
|
295
|
+
child.stdin.on?.("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
398
296
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
297
|
+
/** Starts a new app-server client using resolved runtime start options. */
|
|
298
|
+
static start(options) {
|
|
299
|
+
const defaults = resolveCodexAppServerRuntimeOptions().start;
|
|
300
|
+
const startOptions = {
|
|
301
|
+
...defaults,
|
|
302
|
+
...options,
|
|
303
|
+
headers: options?.headers ?? defaults.headers
|
|
304
|
+
};
|
|
305
|
+
if (startOptions.transport === "stdio" && startOptions.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
|
|
306
|
+
if (startOptions.transport === "websocket" || startOptions.transport === "unix") return new CodexAppServerClient(createWebSocketTransport(startOptions));
|
|
307
|
+
return new CodexAppServerClient(createStdioTransport(startOptions));
|
|
408
308
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const apiKey = parseCodexCliAuthFileApiKey(fsSync.readFileSync(resolveCodexCliAuthFilePath(env), "utf8"));
|
|
413
|
-
return apiKey ? fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) : void 0;
|
|
414
|
-
} catch {
|
|
415
|
-
return;
|
|
309
|
+
/** Builds a client around a fake transport for tests. */
|
|
310
|
+
static fromTransportForTests(child) {
|
|
311
|
+
return new CodexAppServerClient(child);
|
|
416
312
|
}
|
|
417
|
-
|
|
418
|
-
async
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
313
|
+
/** Performs the app-server initialize handshake and validates protocol version. */
|
|
314
|
+
async initialize() {
|
|
315
|
+
if (this.initialized) return;
|
|
316
|
+
const response = await this.request("initialize", {
|
|
317
|
+
clientInfo: {
|
|
318
|
+
name: "openclaw",
|
|
319
|
+
title: "OpenClaw",
|
|
320
|
+
version: OPENCLAW_VERSION
|
|
321
|
+
},
|
|
322
|
+
capabilities: { experimentalApi: true }
|
|
323
|
+
});
|
|
324
|
+
this.serverVersion = assertSupportedCodexAppServerVersion(response);
|
|
325
|
+
this.runtimeIdentity = buildCodexAppServerRuntimeIdentity(response, this.serverVersion);
|
|
326
|
+
this.notify("initialized");
|
|
327
|
+
this.initialized = true;
|
|
429
328
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
profileId,
|
|
434
|
-
agentDir: params.agentDir
|
|
435
|
-
}))?.apiKey?.trim();
|
|
436
|
-
return accessToken ? buildChatgptAuthTokensParams(profileId, credential, accessToken) : void 0;
|
|
437
|
-
}
|
|
438
|
-
if (credential.type !== "oauth") return;
|
|
439
|
-
const resolvedCredential = await resolveOAuthCredentialForCodexAppServer(profileId, credential, {
|
|
440
|
-
agentDir: params.agentDir,
|
|
441
|
-
store: params.store,
|
|
442
|
-
preferStoreCredential: params.preferStoreCredential,
|
|
443
|
-
forceRefresh: params.forceOAuthRefresh,
|
|
444
|
-
config: params.config
|
|
445
|
-
});
|
|
446
|
-
const accessToken = resolvedCredential.access?.trim();
|
|
447
|
-
return accessToken ? buildChatgptAuthTokensParams(profileId, resolvedCredential, accessToken) : void 0;
|
|
448
|
-
}
|
|
449
|
-
async function resolveOAuthCredentialForCodexAppServer(profileId, credential, params) {
|
|
450
|
-
const ownerAgentDir = resolvePersistedAuthProfileOwnerAgentDir({
|
|
451
|
-
agentDir: params.agentDir,
|
|
452
|
-
profileId
|
|
453
|
-
});
|
|
454
|
-
const persistedCredential = findPersistedAuthProfileCredential({
|
|
455
|
-
agentDir: ownerAgentDir,
|
|
456
|
-
profileId
|
|
457
|
-
});
|
|
458
|
-
const useScopedCredential = params.preferStoreCredential && shouldUseScopedOAuthCredential({
|
|
459
|
-
store: params.store,
|
|
460
|
-
profileId,
|
|
461
|
-
persistedCredential,
|
|
462
|
-
suppliedCredential: credential,
|
|
463
|
-
config: params.config
|
|
464
|
-
});
|
|
465
|
-
const store = useScopedCredential ? params.store : ensureCodexAppServerAuthProfileStore({
|
|
466
|
-
agentDir: ownerAgentDir,
|
|
467
|
-
authProfileId: profileId,
|
|
468
|
-
config: params.config
|
|
469
|
-
});
|
|
470
|
-
const persistedOAuthCredential = !useScopedCredential && persistedCredential?.type === "oauth" && isCodexAppServerAuthProvider(persistedCredential.provider, params.config) ? persistedCredential : void 0;
|
|
471
|
-
const ownerCredential = store.profiles[profileId];
|
|
472
|
-
const overlaidOAuthCredential = ownerCredential?.type === "oauth" && isCodexAppServerAuthProvider(ownerCredential.provider, params.config) ? ownerCredential : void 0;
|
|
473
|
-
if (useScopedCredential && overlaidOAuthCredential) return await resolveScopedOAuthCredential({
|
|
474
|
-
store,
|
|
475
|
-
profileId,
|
|
476
|
-
credential: overlaidOAuthCredential,
|
|
477
|
-
forceRefresh: params.forceRefresh
|
|
478
|
-
});
|
|
479
|
-
if (params.forceRefresh && !persistedOAuthCredential && overlaidOAuthCredential) {
|
|
480
|
-
const refreshedRuntimeCredential = await refreshOAuthCredentialForRuntime({ credential: overlaidOAuthCredential });
|
|
481
|
-
if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh.`);
|
|
482
|
-
store.profiles[profileId] = refreshedRuntimeCredential;
|
|
483
|
-
return refreshedRuntimeCredential;
|
|
484
|
-
}
|
|
485
|
-
const resolved = await resolveApiKeyForProfile({
|
|
486
|
-
store,
|
|
487
|
-
profileId,
|
|
488
|
-
agentDir: ownerAgentDir,
|
|
489
|
-
forceRefresh: params.forceRefresh && Boolean(persistedOAuthCredential)
|
|
490
|
-
});
|
|
491
|
-
const refreshed = useScopedCredential ? void 0 : loadAuthProfileStoreForSecretsRuntime(ownerAgentDir).profiles[profileId];
|
|
492
|
-
const refreshedOAuthCredential = refreshed?.type === "oauth" && isCodexAppServerAuthProvider(refreshed.provider, params.config) ? refreshed : void 0;
|
|
493
|
-
if (refreshedOAuthCredential && isDeepStrictEqual(params.store.profiles[profileId], credential)) params.store.profiles[profileId] = refreshedOAuthCredential;
|
|
494
|
-
const storedCredential = store.profiles[profileId];
|
|
495
|
-
const candidate = refreshedOAuthCredential ? refreshedOAuthCredential : storedCredential?.type === "oauth" && isCodexAppServerAuthProvider(storedCredential.provider, params.config) ? storedCredential : credential;
|
|
496
|
-
return resolved?.apiKey ? {
|
|
497
|
-
...candidate,
|
|
498
|
-
access: resolved.apiKey
|
|
499
|
-
} : candidate;
|
|
500
|
-
}
|
|
501
|
-
function shouldUseScopedOAuthCredential(params) {
|
|
502
|
-
if (!params.store.runtimePersistedProfileIds?.includes(params.profileId)) return true;
|
|
503
|
-
const persisted = params.persistedCredential;
|
|
504
|
-
if (persisted?.type !== "oauth") return true;
|
|
505
|
-
if (resolveProviderIdForAuth(persisted.provider, { config: params.config }) !== resolveProviderIdForAuth(params.suppliedCredential.provider, { config: params.config })) return true;
|
|
506
|
-
return !isDeepStrictEqual(persisted, params.suppliedCredential) && !hasMatchingOAuthIdentity(persisted, params.suppliedCredential);
|
|
507
|
-
}
|
|
508
|
-
function hasMatchingOAuthIdentity(persisted, supplied) {
|
|
509
|
-
const persistedAccountId = persisted.accountId?.trim();
|
|
510
|
-
const suppliedAccountId = supplied.accountId?.trim();
|
|
511
|
-
if (persistedAccountId && suppliedAccountId) return persistedAccountId === suppliedAccountId;
|
|
512
|
-
const persistedEmail = persisted.email?.trim().toLowerCase();
|
|
513
|
-
const suppliedEmail = supplied.email?.trim().toLowerCase();
|
|
514
|
-
return Boolean(persistedEmail && suppliedEmail && persistedEmail === suppliedEmail);
|
|
515
|
-
}
|
|
516
|
-
async function resolveScopedOAuthCredential(params) {
|
|
517
|
-
const existingRefresh = scopedOAuthRefreshQueues.get(params.store)?.get(params.profileId);
|
|
518
|
-
if (existingRefresh) return await existingRefresh;
|
|
519
|
-
if (!params.forceRefresh && hasUsableOAuthCredential(params.credential)) return params.credential;
|
|
520
|
-
const storeRefreshes = scopedOAuthRefreshQueues.get(params.store) ?? /* @__PURE__ */ new Map();
|
|
521
|
-
scopedOAuthRefreshQueues.set(params.store, storeRefreshes);
|
|
522
|
-
const refresh = (async () => {
|
|
523
|
-
const current = params.store.profiles[params.profileId];
|
|
524
|
-
const credential = current?.type === "oauth" ? current : params.credential;
|
|
525
|
-
if (!params.forceRefresh && hasUsableOAuthCredential(credential)) return credential;
|
|
526
|
-
const refreshed = await refreshOAuthCredentialForRuntime({ credential });
|
|
527
|
-
if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh.`);
|
|
528
|
-
if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing.`);
|
|
529
|
-
params.store.profiles[params.profileId] = refreshed;
|
|
530
|
-
return refreshed;
|
|
531
|
-
})();
|
|
532
|
-
storeRefreshes.set(params.profileId, refresh);
|
|
533
|
-
try {
|
|
534
|
-
return await refresh;
|
|
535
|
-
} finally {
|
|
536
|
-
if (storeRefreshes.get(params.profileId) === refresh) storeRefreshes.delete(params.profileId);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
function isCodexAppServerAuthProvider(provider, config) {
|
|
540
|
-
const resolvedProvider = resolveProviderIdForAuth(provider, { config });
|
|
541
|
-
return resolvedProvider === CODEX_APP_SERVER_AUTH_PROVIDER || resolvedProvider === OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER || resolvedProvider === LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER;
|
|
542
|
-
}
|
|
543
|
-
function isOpenAIApiKeyBackupCredential(credential, config) {
|
|
544
|
-
return credential.type === "api_key" && resolveProviderIdForAuth(credential.provider, { config }) === OPENAI_PROVIDER;
|
|
545
|
-
}
|
|
546
|
-
function isCodexAppServerAuthProfileCredential(credential, config) {
|
|
547
|
-
return isCodexAppServerAuthProvider(credential.provider, config) || isOpenAIApiKeyBackupCredential(credential, config);
|
|
548
|
-
}
|
|
549
|
-
function shouldClearOpenAiApiKeyForCodexAuthProfile(params) {
|
|
550
|
-
const profileId = params.authProfileId?.trim();
|
|
551
|
-
return isCodexSubscriptionCredential(profileId ? params.store.profiles[profileId] : params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID], params.config);
|
|
552
|
-
}
|
|
553
|
-
function isCodexSubscriptionCredential(credential, config) {
|
|
554
|
-
if (!credential || !isCodexAppServerAuthProvider(credential.provider, config)) return false;
|
|
555
|
-
return credential.type === "oauth" || credential.type === "token";
|
|
556
|
-
}
|
|
557
|
-
function withClearedEnvironmentVariables(startOptions, envVars) {
|
|
558
|
-
const clearEnv = startOptions.clearEnv ?? [];
|
|
559
|
-
const missingEnvVars = envVars.filter((envVar) => !clearEnv.includes(envVar));
|
|
560
|
-
if (missingEnvVars.length === 0) return startOptions;
|
|
561
|
-
return {
|
|
562
|
-
...startOptions,
|
|
563
|
-
clearEnv: [...clearEnv, ...missingEnvVars]
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
function readFirstNonEmptyEnv(env, keys) {
|
|
567
|
-
return readFirstNonEmptyEnvEntry(env, keys)?.value;
|
|
568
|
-
}
|
|
569
|
-
function readFirstNonEmptyEnvEntry(env, keys) {
|
|
570
|
-
for (const key of keys) {
|
|
571
|
-
const value = env[key]?.trim();
|
|
572
|
-
if (value) return {
|
|
573
|
-
key,
|
|
574
|
-
value
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
|
|
579
|
-
return {
|
|
580
|
-
type: "chatgptAuthTokens",
|
|
581
|
-
accessToken,
|
|
582
|
-
chatgptAccountId: resolveChatgptAccountId(profileId, credential),
|
|
583
|
-
chatgptPlanType: resolveChatgptPlanType(credential)
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
function resolveChatgptPlanType(credential) {
|
|
587
|
-
const record = credential;
|
|
588
|
-
const planType = record.chatgptPlanType ?? record.planType;
|
|
589
|
-
return typeof planType === "string" && planType.trim() ? planType.trim() : null;
|
|
590
|
-
}
|
|
591
|
-
function resolveChatgptAccountId(profileId, credential) {
|
|
592
|
-
if ("accountId" in credential && typeof credential.accountId === "string") {
|
|
593
|
-
const accountId = credential.accountId.trim();
|
|
594
|
-
if (accountId) return accountId;
|
|
595
|
-
}
|
|
596
|
-
return credential.email?.trim() || profileId;
|
|
597
|
-
}
|
|
598
|
-
//#endregion
|
|
599
|
-
//#region extensions/codex/src/app-server/rate-limit-cache.ts
|
|
600
|
-
const DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS = 10 * 6e4;
|
|
601
|
-
const SPARSE_ACCOUNT_METADATA_KEYS = [
|
|
602
|
-
"credits",
|
|
603
|
-
"individualLimit",
|
|
604
|
-
"planType"
|
|
605
|
-
];
|
|
606
|
-
const rateLimitsByClient = /* @__PURE__ */ new WeakMap();
|
|
607
|
-
/** Replaces one physical client's cache with an authoritative rate-limit read response. */
|
|
608
|
-
function rememberCodexRateLimitsRead(client, value, nowMs = Date.now()) {
|
|
609
|
-
if (value !== void 0) {
|
|
610
|
-
const revisionsByLimitId = { ...rateLimitsByClient.get(client)?.revisionsByLimitId };
|
|
611
|
-
for (const limitId of readRateLimitIds(value)) revisionsByLimitId[limitId] = (revisionsByLimitId[limitId] ?? 0) + 1;
|
|
612
|
-
rateLimitsByClient.set(client, {
|
|
613
|
-
value,
|
|
614
|
-
updatedAtMs: nowMs,
|
|
615
|
-
revisionsByLimitId
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
/** Merges a sparse rolling notification into one physical client's latest read response. */
|
|
620
|
-
function mergeCodexRateLimitsUpdate(client, value, nowMs = Date.now()) {
|
|
621
|
-
const update = isJsonObject$1(value) && isJsonObject$1(value.rateLimits) ? value.rateLimits : void 0;
|
|
622
|
-
if (!update) return;
|
|
623
|
-
const currentState = rateLimitsByClient.get(client);
|
|
624
|
-
const current = currentState?.value;
|
|
625
|
-
const limitId = readLimitId(update);
|
|
626
|
-
rateLimitsByClient.set(client, {
|
|
627
|
-
value: mergeRateLimitUpdate(current, update),
|
|
628
|
-
updatedAtMs: nowMs,
|
|
629
|
-
revisionsByLimitId: {
|
|
630
|
-
...currentState?.revisionsByLimitId,
|
|
631
|
-
[limitId]: (currentState?.revisionsByLimitId[limitId] ?? 0) + 1
|
|
632
|
-
}
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
/** Per-limit marker used to trust only primary Codex updates from one turn startup. */
|
|
636
|
-
function readCodexRateLimitsRevision(client, limitId = "codex") {
|
|
637
|
-
return rateLimitsByClient.get(client)?.revisionsByLimitId[limitId] ?? 0;
|
|
638
|
-
}
|
|
639
|
-
/** Reads one physical client's cached rate-limit payload within the max-age window. */
|
|
640
|
-
function readRecentCodexRateLimits(client, options) {
|
|
641
|
-
const state = rateLimitsByClient.get(client);
|
|
642
|
-
if (!state) return;
|
|
643
|
-
const nowMs = options?.nowMs ?? Date.now();
|
|
644
|
-
const maxAgeMs = options?.maxAgeMs ?? DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS;
|
|
645
|
-
return maxAgeMs >= 0 && nowMs - state.updatedAtMs > maxAgeMs ? void 0 : state.value;
|
|
646
|
-
}
|
|
647
|
-
function mergeRateLimitUpdate(current, update) {
|
|
648
|
-
const currentEnvelope = isJsonObject$1(current) ? current : void 0;
|
|
649
|
-
const currentPrimary = currentEnvelope && isJsonObject$1(currentEnvelope.rateLimits) ? currentEnvelope.rateLimits : void 0;
|
|
650
|
-
const currentByLimitId = currentEnvelope && isJsonObject$1(currentEnvelope.rateLimitsByLimitId) ? currentEnvelope.rateLimitsByLimitId : void 0;
|
|
651
|
-
const limitId = readLimitId(update);
|
|
652
|
-
const currentPrimaryLimitId = currentPrimary ? readLimitId(currentPrimary) : void 0;
|
|
653
|
-
const currentForLimit = (currentByLimitId && isJsonObject$1(currentByLimitId[limitId]) ? currentByLimitId[limitId] : void 0) ?? (currentPrimaryLimitId === limitId ? currentPrimary : void 0);
|
|
654
|
-
const merged = mergeSparseSnapshot(isJsonObject$1(currentForLimit) ? currentForLimit : void 0, currentPrimary, update, limitId);
|
|
655
|
-
const nextPrimary = !currentPrimary || currentPrimaryLimitId === limitId ? merged : currentPrimary;
|
|
656
|
-
let nextByLimitId;
|
|
657
|
-
if (currentByLimitId) nextByLimitId = {
|
|
658
|
-
...currentByLimitId,
|
|
659
|
-
[limitId]: merged
|
|
660
|
-
};
|
|
661
|
-
else if (currentPrimary && currentPrimaryLimitId && currentPrimaryLimitId !== limitId) nextByLimitId = {
|
|
662
|
-
[currentPrimaryLimitId]: currentPrimary,
|
|
663
|
-
[limitId]: merged
|
|
664
|
-
};
|
|
665
|
-
return {
|
|
666
|
-
...currentEnvelope,
|
|
667
|
-
rateLimits: nextPrimary,
|
|
668
|
-
...nextByLimitId ? { rateLimitsByLimitId: nextByLimitId } : {}
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
function readRateLimitIds(value) {
|
|
672
|
-
if (!isJsonObject$1(value)) return [];
|
|
673
|
-
const ids = /* @__PURE__ */ new Set();
|
|
674
|
-
if (isJsonObject$1(value.rateLimits)) ids.add(readLimitId(value.rateLimits));
|
|
675
|
-
if (isJsonObject$1(value.rateLimitsByLimitId)) for (const [key, snapshot] of Object.entries(value.rateLimitsByLimitId)) {
|
|
676
|
-
const snapshotLimitId = isJsonObject$1(snapshot) && typeof snapshot.limitId === "string" ? snapshot.limitId.trim() : "";
|
|
677
|
-
ids.add(snapshotLimitId || key);
|
|
678
|
-
}
|
|
679
|
-
return [...ids];
|
|
680
|
-
}
|
|
681
|
-
function mergeSparseSnapshot(current, accountFallback, update, limitId) {
|
|
682
|
-
const merged = {
|
|
683
|
-
...update,
|
|
684
|
-
limitId
|
|
685
|
-
};
|
|
686
|
-
for (const key of SPARSE_ACCOUNT_METADATA_KEYS) {
|
|
687
|
-
const previous = current?.[key] ?? accountFallback?.[key];
|
|
688
|
-
if (merged[key] == null && previous != null) merged[key] = previous;
|
|
689
|
-
}
|
|
690
|
-
return merged;
|
|
691
|
-
}
|
|
692
|
-
function readLimitId(snapshot) {
|
|
693
|
-
const value = snapshot.limitId;
|
|
694
|
-
return typeof value === "string" && value.trim() ? value.trim() : "codex";
|
|
695
|
-
}
|
|
696
|
-
//#endregion
|
|
697
|
-
//#region extensions/codex/src/app-server/client-runtime.ts
|
|
698
|
-
/** Client-scoped Codex auth and account observers. */
|
|
699
|
-
const configuredClients = /* @__PURE__ */ new WeakMap();
|
|
700
|
-
/** Installs one auth-refresh handler and one rate-limit observer per physical client. */
|
|
701
|
-
function ensureCodexAppServerClientRuntime(client, context) {
|
|
702
|
-
const existing = configuredClients.get(client);
|
|
703
|
-
if (existing) {
|
|
704
|
-
existing.context = context;
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
const runtime = { context };
|
|
708
|
-
configuredClients.set(client, runtime);
|
|
709
|
-
client.addRequestHandler(async (request) => {
|
|
710
|
-
if (request.method !== "account/chatgptAuthTokens/refresh") return;
|
|
711
|
-
return await refreshCodexAppServerAuthTokens({
|
|
712
|
-
agentDir: runtime.context.agentDir,
|
|
713
|
-
authProfileId: runtime.context.authProfileId,
|
|
714
|
-
...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
|
|
715
|
-
config: runtime.context.config
|
|
716
|
-
});
|
|
717
|
-
});
|
|
718
|
-
client.addNotificationHandler((notification) => {
|
|
719
|
-
if (notification.method === "account/rateLimits/updated") mergeCodexRateLimitsUpdate(client, notification.params);
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
//#endregion
|
|
723
|
-
//#region extensions/codex/src/app-server/transport-websocket.ts
|
|
724
|
-
/**
|
|
725
|
-
* Adapts a remote Codex app-server WebSocket endpoint to the shared stdio-like
|
|
726
|
-
* transport interface.
|
|
727
|
-
*/
|
|
728
|
-
/** Opens a WebSocket app-server transport and maps newline-delimited frames to stdout/stdin. */
|
|
729
|
-
function createWebSocketTransport(options) {
|
|
730
|
-
if (!options.url) throw new Error("codex app-server websocket transport requires plugins.entries.codex.config.appServer.url");
|
|
731
|
-
const events = new EventEmitter();
|
|
732
|
-
const stdout = new PassThrough();
|
|
733
|
-
const stderr = new PassThrough();
|
|
734
|
-
const headers = {
|
|
735
|
-
...options.headers,
|
|
736
|
-
...options.authToken ? { Authorization: `Bearer ${options.authToken}` } : {}
|
|
737
|
-
};
|
|
738
|
-
const socket = new WebSocket(options.url, { headers });
|
|
739
|
-
const pendingFrames = [];
|
|
740
|
-
let killed = false;
|
|
741
|
-
const sendFrame = (frame) => {
|
|
742
|
-
const trimmed = frame.trim();
|
|
743
|
-
if (!trimmed) return;
|
|
744
|
-
if (socket.readyState === WebSocket.OPEN) {
|
|
745
|
-
socket.send(trimmed);
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
pendingFrames.push(trimmed);
|
|
749
|
-
};
|
|
750
|
-
socket.once("open", () => {
|
|
751
|
-
for (const frame of pendingFrames.splice(0)) socket.send(frame);
|
|
752
|
-
});
|
|
753
|
-
socket.once("error", (error) => events.emit("error", error));
|
|
754
|
-
socket.once("close", (code, reason) => {
|
|
755
|
-
killed = true;
|
|
756
|
-
events.emit("exit", code, reason.toString("utf8"));
|
|
757
|
-
});
|
|
758
|
-
socket.on("message", (data) => {
|
|
759
|
-
const text = websocketFrameToText(data);
|
|
760
|
-
stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
761
|
-
});
|
|
762
|
-
const stdin = new Writable({ write(chunk, _encoding, callback) {
|
|
763
|
-
for (const frame of chunk.toString("utf8").split("\n")) sendFrame(frame);
|
|
764
|
-
callback();
|
|
765
|
-
} });
|
|
766
|
-
const closeSocket = () => {
|
|
767
|
-
if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) return;
|
|
768
|
-
socket.close();
|
|
769
|
-
};
|
|
770
|
-
stdin.once("finish", closeSocket);
|
|
771
|
-
stdin.once("close", closeSocket);
|
|
772
|
-
return {
|
|
773
|
-
stdin,
|
|
774
|
-
stdout,
|
|
775
|
-
stderr,
|
|
776
|
-
get killed() {
|
|
777
|
-
return killed;
|
|
778
|
-
},
|
|
779
|
-
kill: () => {
|
|
780
|
-
killed = true;
|
|
781
|
-
socket.close();
|
|
782
|
-
},
|
|
783
|
-
once: (event, listener) => events.once(event, listener)
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
function websocketFrameToText(data) {
|
|
787
|
-
if (typeof data === "string") return data;
|
|
788
|
-
if (Buffer.isBuffer(data)) return data.toString("utf8");
|
|
789
|
-
if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
|
|
790
|
-
return Buffer.from(data).toString("utf8");
|
|
791
|
-
}
|
|
792
|
-
//#endregion
|
|
793
|
-
//#region extensions/codex/src/app-server/transport.ts
|
|
794
|
-
/** Starts graceful transport shutdown and schedules a force kill fallback. */
|
|
795
|
-
function closeCodexAppServerTransport(child, options = {}) {
|
|
796
|
-
child.stdin.end?.();
|
|
797
|
-
child.stdin.destroy?.();
|
|
798
|
-
const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
|
|
799
|
-
const forceKill = setTimeout(() => {
|
|
800
|
-
if (hasCodexAppServerTransportExited(child)) return;
|
|
801
|
-
signalCodexAppServerTransport(child, "SIGKILL");
|
|
802
|
-
}, Math.max(1, forceKillDelayMs));
|
|
803
|
-
forceKill.unref?.();
|
|
804
|
-
child.once("exit", () => {
|
|
805
|
-
clearTimeout(forceKill);
|
|
806
|
-
child.stdout.destroy?.();
|
|
807
|
-
child.stderr.destroy?.();
|
|
808
|
-
});
|
|
809
|
-
child.unref?.();
|
|
810
|
-
child.stdout.unref?.();
|
|
811
|
-
child.stderr.unref?.();
|
|
812
|
-
child.stdin.unref?.();
|
|
813
|
-
}
|
|
814
|
-
/** Closes a transport and waits briefly for an exit event. */
|
|
815
|
-
async function closeCodexAppServerTransportAndWait(child, options = {}) {
|
|
816
|
-
if (!hasCodexAppServerTransportExited(child)) closeCodexAppServerTransport(child, options);
|
|
817
|
-
return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
|
|
818
|
-
}
|
|
819
|
-
function hasCodexAppServerTransportExited(child) {
|
|
820
|
-
return child.exitCode !== null && child.exitCode !== void 0 ? true : child.signalCode !== null && child.signalCode !== void 0;
|
|
821
|
-
}
|
|
822
|
-
async function waitForCodexAppServerTransportExit(child, timeoutMs) {
|
|
823
|
-
if (hasCodexAppServerTransportExited(child)) return true;
|
|
824
|
-
return await new Promise((resolve) => {
|
|
825
|
-
let settled = false;
|
|
826
|
-
const onExit = () => {
|
|
827
|
-
if (settled) return;
|
|
828
|
-
settled = true;
|
|
829
|
-
clearTimeout(timeout);
|
|
830
|
-
resolve(true);
|
|
831
|
-
};
|
|
832
|
-
const timeout = setTimeout(() => {
|
|
833
|
-
if (settled) return;
|
|
834
|
-
settled = true;
|
|
835
|
-
child.off?.("exit", onExit);
|
|
836
|
-
resolve(false);
|
|
837
|
-
}, Math.max(1, timeoutMs));
|
|
838
|
-
child.once("exit", onExit);
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
function signalCodexAppServerTransport(child, signal) {
|
|
842
|
-
if (child.pid && process.platform !== "win32") try {
|
|
843
|
-
process.kill(-child.pid, signal);
|
|
844
|
-
return;
|
|
845
|
-
} catch {}
|
|
846
|
-
child.kill?.(signal);
|
|
847
|
-
}
|
|
848
|
-
//#endregion
|
|
849
|
-
//#region extensions/codex/src/app-server/version.ts
|
|
850
|
-
/**
|
|
851
|
-
* Version and package pins for the managed Codex app-server runtime.
|
|
852
|
-
*/
|
|
853
|
-
/** Minimum Codex app-server version supported by the OpenClaw Codex bridge. */
|
|
854
|
-
const MIN_CODEX_APP_SERVER_VERSION = "0.143.0";
|
|
855
|
-
/** npm package name for the managed Codex app-server binary. */
|
|
856
|
-
const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
|
|
857
|
-
//#endregion
|
|
858
|
-
//#region extensions/codex/src/app-server/client.ts
|
|
859
|
-
/**
|
|
860
|
-
* JSON-RPC client for Codex app-server transports, including request/response
|
|
861
|
-
* routing, notification fanout, server request handlers, and version checks.
|
|
862
|
-
*/
|
|
863
|
-
const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
|
|
864
|
-
const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 1e6;
|
|
865
|
-
const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1e3;
|
|
866
|
-
const CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS = 6e5;
|
|
867
|
-
const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
|
|
868
|
-
const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
869
|
-
/** RPC error wrapper that preserves app-server error code and data. */
|
|
870
|
-
var CodexAppServerRpcError = class extends Error {
|
|
871
|
-
constructor(error, method) {
|
|
872
|
-
super(formatCodexAppServerRpcErrorMessage(error, method));
|
|
873
|
-
this.name = "CodexAppServerRpcError";
|
|
874
|
-
this.code = error.code;
|
|
875
|
-
this.data = error.data;
|
|
876
|
-
}
|
|
877
|
-
};
|
|
878
|
-
function formatCodexAppServerRpcErrorMessage(error, method) {
|
|
879
|
-
const message = error.message || `${method} failed`;
|
|
880
|
-
const detail = readCodexAppServerRpcReloginDetail(error.data);
|
|
881
|
-
return detail && !message.includes(detail) ? `${message}: ${detail}` : message;
|
|
882
|
-
}
|
|
883
|
-
function readCodexAppServerRpcReloginDetail(data) {
|
|
884
|
-
const record = isJsonObject(data) ? data : void 0;
|
|
885
|
-
const nested = isJsonObject(record?.error) ? record.error : record;
|
|
886
|
-
if (!nested) return;
|
|
887
|
-
const isRelogin = nested.action === "relogin" || nested.reason === "cloudRequirements" && nested.errorCode === "Auth";
|
|
888
|
-
const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
|
|
889
|
-
return isRelogin && detail ? detail : void 0;
|
|
890
|
-
}
|
|
891
|
-
function isJsonObject(value) {
|
|
892
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
893
|
-
}
|
|
894
|
-
/** Returns true for errors that mean the app-server transport is closed. */
|
|
895
|
-
function isCodexAppServerConnectionClosedError(error) {
|
|
896
|
-
if (!(error instanceof Error)) return false;
|
|
897
|
-
return error.message === "codex app-server client is closed" || error.message.startsWith("codex app-server exited:");
|
|
898
|
-
}
|
|
899
|
-
/** Stateful app-server JSON-RPC client over stdio or websocket transport. */
|
|
900
|
-
var CodexAppServerClient = class CodexAppServerClient {
|
|
901
|
-
constructor(child) {
|
|
902
|
-
this.pending = /* @__PURE__ */ new Map();
|
|
903
|
-
this.requestHandlers = /* @__PURE__ */ new Set();
|
|
904
|
-
this.notificationHandlers = /* @__PURE__ */ new Set();
|
|
905
|
-
this.closeHandlers = /* @__PURE__ */ new Set();
|
|
906
|
-
this.nextId = 1;
|
|
907
|
-
this.initialized = false;
|
|
908
|
-
this.closed = false;
|
|
909
|
-
this.stderrTail = "";
|
|
910
|
-
this.child = child;
|
|
911
|
-
this.lines = createInterface({ input: child.stdout });
|
|
912
|
-
this.lines.on("line", (line) => this.handleLine(line));
|
|
913
|
-
this.lines.on("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
914
|
-
child.stdout.on("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
915
|
-
child.stderr.on("data", (chunk) => {
|
|
916
|
-
const text = chunk.toString("utf8");
|
|
917
|
-
this.stderrTail = appendBoundedTail(this.stderrTail, text, CODEX_APP_SERVER_STDERR_TAIL_MAX);
|
|
918
|
-
const trimmed = text.trim();
|
|
919
|
-
if (trimmed) embeddedAgentLog.debug(`codex app-server stderr: ${trimmed}`);
|
|
920
|
-
});
|
|
921
|
-
child.stderr.on("error", (error) => {
|
|
922
|
-
embeddedAgentLog.warn("codex app-server stderr stream failed", { error });
|
|
923
|
-
});
|
|
924
|
-
child.once("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
925
|
-
child.once("exit", (code, signal) => {
|
|
926
|
-
this.closeWithError(buildCodexAppServerExitError(code, signal, this.stderrTail));
|
|
927
|
-
});
|
|
928
|
-
child.stdin.on?.("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
|
|
929
|
-
}
|
|
930
|
-
/** Starts a new app-server client using resolved runtime start options. */
|
|
931
|
-
static start(options) {
|
|
932
|
-
const defaults = resolveCodexAppServerRuntimeOptions().start;
|
|
933
|
-
const startOptions = {
|
|
934
|
-
...defaults,
|
|
935
|
-
...options,
|
|
936
|
-
headers: options?.headers ?? defaults.headers
|
|
937
|
-
};
|
|
938
|
-
if (startOptions.transport === "stdio" && startOptions.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
|
|
939
|
-
if (startOptions.transport === "websocket") return new CodexAppServerClient(createWebSocketTransport(startOptions));
|
|
940
|
-
return new CodexAppServerClient(createStdioTransport(startOptions));
|
|
941
|
-
}
|
|
942
|
-
/** Builds a client around a fake transport for tests. */
|
|
943
|
-
static fromTransportForTests(child) {
|
|
944
|
-
return new CodexAppServerClient(child);
|
|
945
|
-
}
|
|
946
|
-
/** Performs the app-server initialize handshake and validates protocol version. */
|
|
947
|
-
async initialize() {
|
|
948
|
-
if (this.initialized) return;
|
|
949
|
-
const response = await this.request("initialize", {
|
|
950
|
-
clientInfo: {
|
|
951
|
-
name: "openclaw",
|
|
952
|
-
title: "OpenClaw",
|
|
953
|
-
version: OPENCLAW_VERSION
|
|
954
|
-
},
|
|
955
|
-
capabilities: { experimentalApi: true }
|
|
956
|
-
});
|
|
957
|
-
this.serverVersion = assertSupportedCodexAppServerVersion(response);
|
|
958
|
-
this.runtimeIdentity = buildCodexAppServerRuntimeIdentity(response, this.serverVersion);
|
|
959
|
-
this.notify("initialized");
|
|
960
|
-
this.initialized = true;
|
|
961
|
-
}
|
|
962
|
-
/** Returns the version detected during initialize. */
|
|
963
|
-
getServerVersion() {
|
|
964
|
-
return this.serverVersion;
|
|
329
|
+
/** Returns the version detected during initialize. */
|
|
330
|
+
getServerVersion() {
|
|
331
|
+
return this.serverVersion;
|
|
965
332
|
}
|
|
966
333
|
/** Returns runtime metadata detected during initialize. */
|
|
967
334
|
getRuntimeIdentity() {
|
|
968
335
|
return this.runtimeIdentity ? { ...this.runtimeIdentity } : void 0;
|
|
969
336
|
}
|
|
337
|
+
/** Stable generation id for this exact physical client instance. */
|
|
338
|
+
getInstanceId() {
|
|
339
|
+
return this.instanceId;
|
|
340
|
+
}
|
|
341
|
+
/** Installs the spawn-owner check run before config-loading thread requests. */
|
|
342
|
+
setThreadSessionRequestGuard(guard) {
|
|
343
|
+
this.threadSessionRequestGuard = guard;
|
|
344
|
+
}
|
|
345
|
+
/** Returns the local transport PID for scoped child-process cleanup, when available. */
|
|
346
|
+
getTransportPid() {
|
|
347
|
+
return this.child.pid;
|
|
348
|
+
}
|
|
970
349
|
request(method, params, optionsInput) {
|
|
971
350
|
let options = optionsInput;
|
|
972
351
|
options ??= {};
|
|
973
352
|
if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
|
|
974
|
-
if (options.signal?.aborted) return Promise.reject(
|
|
353
|
+
if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
|
|
354
|
+
const guard = method === "thread/start" || method === "thread/resume" || method === "thread/fork" ? this.threadSessionRequestGuard : void 0;
|
|
355
|
+
if (guard) return (async () => {
|
|
356
|
+
const guardStartedAt = Date.now();
|
|
357
|
+
const timeoutMessage = `${method} timed out`;
|
|
358
|
+
const abortMessage = `${method} aborted`;
|
|
359
|
+
let releaseGuard;
|
|
360
|
+
try {
|
|
361
|
+
releaseGuard = await guard({
|
|
362
|
+
signal: options.signal,
|
|
363
|
+
timeoutMs: options.timeoutMs,
|
|
364
|
+
timeoutMessage,
|
|
365
|
+
abortMessage
|
|
366
|
+
});
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error instanceof Error && error.message === timeoutMessage) throw new CodexAppServerLocalRequestCancellationError(method, "timed out", false);
|
|
369
|
+
if (error instanceof Error && error.message === abortMessage) throw new CodexAppServerLocalRequestCancellationError(method, "aborted", false);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
let released = false;
|
|
373
|
+
const release = () => {
|
|
374
|
+
if (released) return;
|
|
375
|
+
released = true;
|
|
376
|
+
releaseGuard();
|
|
377
|
+
};
|
|
378
|
+
let releaseWhenRequestSettles = true;
|
|
379
|
+
let requestMayHaveWritten = false;
|
|
380
|
+
try {
|
|
381
|
+
const elapsedMs = Date.now() - guardStartedAt;
|
|
382
|
+
const remainingTimeoutMs = options.timeoutMs === void 0 ? void 0 : options.timeoutMs - elapsedMs;
|
|
383
|
+
if (remainingTimeoutMs !== void 0 && remainingTimeoutMs <= 0) throw new CodexAppServerLocalRequestCancellationError(method, "timed out", false);
|
|
384
|
+
return await this.requestWithoutThreadSessionGuard(method, params, {
|
|
385
|
+
...options,
|
|
386
|
+
...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
|
|
387
|
+
}, () => {
|
|
388
|
+
requestMayHaveWritten = true;
|
|
389
|
+
});
|
|
390
|
+
} catch (error) {
|
|
391
|
+
if (requestMayHaveWritten && !(error instanceof CodexAppServerRpcError)) {
|
|
392
|
+
releaseWhenRequestSettles = false;
|
|
393
|
+
await this.closeAndRunAfterExit(release, method);
|
|
394
|
+
}
|
|
395
|
+
throw error;
|
|
396
|
+
} finally {
|
|
397
|
+
if (releaseWhenRequestSettles) release();
|
|
398
|
+
}
|
|
399
|
+
})();
|
|
400
|
+
return this.requestWithoutThreadSessionGuard(method, params, options);
|
|
401
|
+
}
|
|
402
|
+
requestWithoutThreadSessionGuard(method, params, options, onWriteAttempt) {
|
|
403
|
+
if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
|
|
404
|
+
if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
|
|
975
405
|
const id = this.nextId++;
|
|
976
406
|
const message = {
|
|
977
407
|
id,
|
|
@@ -981,6 +411,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
981
411
|
return new Promise((resolve, reject) => {
|
|
982
412
|
let timeout;
|
|
983
413
|
let cleanupAbort;
|
|
414
|
+
let mayHaveWritten = false;
|
|
984
415
|
const cleanup = () => {
|
|
985
416
|
if (timeout) {
|
|
986
417
|
clearTimeout(timeout);
|
|
@@ -993,14 +424,14 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
993
424
|
if (!this.pending.has(id)) return;
|
|
994
425
|
this.pending.delete(id);
|
|
995
426
|
cleanup();
|
|
996
|
-
reject(error);
|
|
427
|
+
reject(mayHaveWritten && !(error instanceof CodexAppServerRpcError) && !isCodexAppServerIndeterminateRequestCancellationError(error) && !isCodexAppServerIndeterminateTransportError(error) ? new CodexAppServerIndeterminateTransportError(method, error) : error);
|
|
997
428
|
};
|
|
998
429
|
if (options.timeoutMs && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0) {
|
|
999
|
-
timeout = setTimeout(() => rejectPending(
|
|
430
|
+
timeout = setTimeout(() => rejectPending(new CodexAppServerLocalRequestCancellationError(method, "timed out", mayHaveWritten)), Math.max(100, options.timeoutMs));
|
|
1000
431
|
timeout.unref?.();
|
|
1001
432
|
}
|
|
1002
433
|
if (options.signal) {
|
|
1003
|
-
const abortListener = () => rejectPending(
|
|
434
|
+
const abortListener = () => rejectPending(new CodexAppServerLocalRequestCancellationError(method, "aborted", mayHaveWritten));
|
|
1004
435
|
options.signal.addEventListener("abort", abortListener, { once: true });
|
|
1005
436
|
cleanupAbort = () => options.signal?.removeEventListener("abort", abortListener);
|
|
1006
437
|
}
|
|
@@ -1012,363 +443,1194 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1012
443
|
},
|
|
1013
444
|
reject: (error) => {
|
|
1014
445
|
cleanup();
|
|
1015
|
-
reject(error);
|
|
446
|
+
reject(mayHaveWritten && !(error instanceof CodexAppServerRpcError) && !isCodexAppServerIndeterminateRequestCancellationError(error) && !isCodexAppServerIndeterminateTransportError(error) ? new CodexAppServerIndeterminateTransportError(method, error) : error);
|
|
1016
447
|
},
|
|
1017
448
|
cleanup
|
|
1018
449
|
});
|
|
1019
450
|
if (options.signal?.aborted) {
|
|
1020
|
-
rejectPending(
|
|
451
|
+
rejectPending(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
mayHaveWritten = true;
|
|
456
|
+
onWriteAttempt?.();
|
|
457
|
+
this.writeMessage(message, (error) => rejectPending(error));
|
|
458
|
+
} catch (error) {
|
|
459
|
+
rejectPending(error instanceof Error ? error : new Error(String(error)));
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
/** Sends a fire-and-forget JSON-RPC notification to the app-server. */
|
|
464
|
+
notify(method, params) {
|
|
465
|
+
this.writeMessage({
|
|
466
|
+
method,
|
|
467
|
+
params
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/** Registers a handler for app-server requests sent back to OpenClaw. */
|
|
471
|
+
addRequestHandler(handler) {
|
|
472
|
+
this.requestHandlers.add(handler);
|
|
473
|
+
return () => this.requestHandlers.delete(handler);
|
|
474
|
+
}
|
|
475
|
+
/** Registers a notification handler and returns its disposer. */
|
|
476
|
+
addNotificationHandler(handler) {
|
|
477
|
+
this.notificationHandlers.add(handler);
|
|
478
|
+
return () => this.notificationHandlers.delete(handler);
|
|
479
|
+
}
|
|
480
|
+
/** Registers a close handler and returns its disposer. */
|
|
481
|
+
addCloseHandler(handler) {
|
|
482
|
+
this.closeHandlers.add(handler);
|
|
483
|
+
return () => this.closeHandlers.delete(handler);
|
|
484
|
+
}
|
|
485
|
+
/** Closes the transport without waiting for process/socket shutdown. */
|
|
486
|
+
close() {
|
|
487
|
+
if (!this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"))) return;
|
|
488
|
+
closeCodexAppServerTransport(this.child);
|
|
489
|
+
}
|
|
490
|
+
/** Closes the transport and waits for shutdown according to transport policy. */
|
|
491
|
+
async closeAndWait(options) {
|
|
492
|
+
this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"));
|
|
493
|
+
return await closeCodexAppServerTransportAndWait(this.child, options);
|
|
494
|
+
}
|
|
495
|
+
/** Closes this transport and runs cleanup only after physical process exit. */
|
|
496
|
+
async closeAndRunAfterExit(onExit, operation) {
|
|
497
|
+
let settled = false;
|
|
498
|
+
const runOnExit = () => {
|
|
499
|
+
if (settled) return;
|
|
500
|
+
settled = true;
|
|
501
|
+
onExit();
|
|
502
|
+
};
|
|
503
|
+
if (this.transportExited) {
|
|
504
|
+
runOnExit();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
this.child.once("exit", runOnExit);
|
|
508
|
+
try {
|
|
509
|
+
if (await this.closeAndWait()) {
|
|
510
|
+
this.child.off?.("exit", runOnExit);
|
|
511
|
+
runOnExit();
|
|
512
|
+
}
|
|
513
|
+
} catch (closeError) {
|
|
514
|
+
embeddedAgentLog.warn("codex app-server shutdown after indeterminate request failed", {
|
|
515
|
+
closeError,
|
|
516
|
+
operation
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
writeMessage(message, onError) {
|
|
521
|
+
if (this.closed) return;
|
|
522
|
+
const id = "id" in message ? message.id : void 0;
|
|
523
|
+
const method = "method" in message ? message.method : void 0;
|
|
524
|
+
this.child.stdin.write(`${stringifyCodexAppServerMessage(message)}\n`, (error) => {
|
|
525
|
+
if (error) {
|
|
526
|
+
embeddedAgentLog.warn("codex app-server write failed", {
|
|
527
|
+
error,
|
|
528
|
+
id,
|
|
529
|
+
method
|
|
530
|
+
});
|
|
531
|
+
onError?.(error);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
handleLine(line) {
|
|
536
|
+
const rawLine = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
537
|
+
if (this.pendingParse) {
|
|
538
|
+
this.handlePendingParseLine(rawLine);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const trimmed = rawLine.trim();
|
|
542
|
+
if (!trimmed) return;
|
|
543
|
+
let parsed;
|
|
544
|
+
try {
|
|
545
|
+
parsed = JSON.parse(trimmed);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
if (shouldBufferCodexAppServerParseFailure(trimmed, error)) {
|
|
548
|
+
this.pendingParse = {
|
|
549
|
+
text: trimmed,
|
|
550
|
+
lineCount: 1,
|
|
551
|
+
firstError: error
|
|
552
|
+
};
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
logCodexAppServerParseFailure(trimmed, error, 1);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this.handleParsedMessage(parsed);
|
|
559
|
+
}
|
|
560
|
+
handlePendingParseLine(line) {
|
|
561
|
+
const pending = this.pendingParse;
|
|
562
|
+
if (!pending) return;
|
|
563
|
+
const candidate = `${pending.text}\\n${line}`;
|
|
564
|
+
let parsed;
|
|
565
|
+
try {
|
|
566
|
+
parsed = JSON.parse(candidate);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
const lineCount = pending.lineCount + 1;
|
|
569
|
+
if (shouldBufferCodexAppServerParseFailure(candidate.trim(), error) && candidate.length <= CODEX_APP_SERVER_PARSE_BUFFER_MAX && lineCount <= CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES) {
|
|
570
|
+
this.pendingParse = {
|
|
571
|
+
text: candidate,
|
|
572
|
+
lineCount,
|
|
573
|
+
firstError: pending.firstError
|
|
574
|
+
};
|
|
1021
575
|
return;
|
|
1022
576
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
577
|
+
this.pendingParse = void 0;
|
|
578
|
+
logCodexAppServerParseFailure(candidate, error, lineCount);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
this.pendingParse = void 0;
|
|
582
|
+
this.handleParsedMessage(parsed);
|
|
583
|
+
}
|
|
584
|
+
handleParsedMessage(parsed) {
|
|
585
|
+
if (!parsed || typeof parsed !== "object") return;
|
|
586
|
+
const message = parsed;
|
|
587
|
+
if (isRpcResponse(message)) {
|
|
588
|
+
this.handleResponse(message);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (!("method" in message)) return;
|
|
592
|
+
if ("id" in message && message.id !== void 0) {
|
|
593
|
+
this.handleServerRequest({
|
|
594
|
+
id: message.id,
|
|
595
|
+
method: message.method,
|
|
596
|
+
params: message.params
|
|
597
|
+
});
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
this.handleNotification({
|
|
601
|
+
method: message.method,
|
|
602
|
+
params: message.params
|
|
1028
603
|
});
|
|
1029
604
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
605
|
+
handleResponse(response) {
|
|
606
|
+
const pending = this.pending.get(response.id);
|
|
607
|
+
if (!pending) return;
|
|
608
|
+
this.pending.delete(response.id);
|
|
609
|
+
if (response.error) {
|
|
610
|
+
pending.reject(new CodexAppServerRpcError(response.error, pending.method));
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
pending.resolve(response.result);
|
|
614
|
+
}
|
|
615
|
+
async handleServerRequest(request) {
|
|
616
|
+
try {
|
|
617
|
+
const result = await this.runServerRequestHandlers(request);
|
|
618
|
+
if (result !== void 0) {
|
|
619
|
+
this.writeMessage({
|
|
620
|
+
id: request.id,
|
|
621
|
+
result
|
|
622
|
+
});
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.writeMessage({
|
|
626
|
+
id: request.id,
|
|
627
|
+
result: defaultServerRequestResponse(request)
|
|
628
|
+
});
|
|
629
|
+
} catch (error) {
|
|
630
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
631
|
+
embeddedAgentLog.warn("codex app-server server request handler failed", {
|
|
632
|
+
id: request.id,
|
|
633
|
+
method: request.method,
|
|
634
|
+
error
|
|
635
|
+
});
|
|
636
|
+
this.writeMessage({
|
|
637
|
+
id: request.id,
|
|
638
|
+
error: {
|
|
639
|
+
code: -32603,
|
|
640
|
+
message
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async runServerRequestHandlers(request) {
|
|
646
|
+
const timeoutResponse = timeoutServerRequestResponse(request);
|
|
647
|
+
if (!timeoutResponse) return await this.runServerRequestHandlersWithoutTimeout(request);
|
|
648
|
+
let timeout;
|
|
649
|
+
try {
|
|
650
|
+
return await Promise.race([this.runServerRequestHandlersWithoutTimeout(request), new Promise((resolve) => {
|
|
651
|
+
timeout = setTimeout(() => {
|
|
652
|
+
embeddedAgentLog.warn("codex app-server server request timed out", {
|
|
653
|
+
id: request.id,
|
|
654
|
+
method: request.method,
|
|
655
|
+
timeoutMs: CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS
|
|
656
|
+
});
|
|
657
|
+
resolve(timeoutResponse);
|
|
658
|
+
}, CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS);
|
|
659
|
+
timeout.unref?.();
|
|
660
|
+
})]);
|
|
661
|
+
} finally {
|
|
662
|
+
if (timeout) clearTimeout(timeout);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
async runServerRequestHandlersWithoutTimeout(request) {
|
|
666
|
+
for (const handler of this.requestHandlers) {
|
|
667
|
+
const result = await handler(request);
|
|
668
|
+
if (result !== void 0) return result;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
handleNotification(notification) {
|
|
672
|
+
for (const handler of this.notificationHandlers) Promise.resolve(handler(notification)).catch((error) => {
|
|
673
|
+
embeddedAgentLog.warn("codex app-server notification handler failed", { error });
|
|
1035
674
|
});
|
|
1036
675
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
this.requestHandlers.add(handler);
|
|
1040
|
-
return () => this.requestHandlers.delete(handler);
|
|
676
|
+
closeWithError(error) {
|
|
677
|
+
if (this.markClosed(error)) closeCodexAppServerTransport(this.child);
|
|
1041
678
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
this.
|
|
1045
|
-
|
|
679
|
+
markClosed(error) {
|
|
680
|
+
if (this.closed) return false;
|
|
681
|
+
this.closed = true;
|
|
682
|
+
this.closeError = error;
|
|
683
|
+
this.lines.close();
|
|
684
|
+
this.rejectPendingRequests(error);
|
|
685
|
+
return true;
|
|
1046
686
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
687
|
+
rejectPendingRequests(error) {
|
|
688
|
+
for (const pending of this.pending.values()) {
|
|
689
|
+
pending.cleanup();
|
|
690
|
+
pending.reject(error);
|
|
691
|
+
}
|
|
692
|
+
this.pending.clear();
|
|
693
|
+
for (const handler of this.closeHandlers) handler(this);
|
|
1051
694
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
695
|
+
};
|
|
696
|
+
function defaultServerRequestResponse(request) {
|
|
697
|
+
if (request.method === "item/tool/call") return {
|
|
698
|
+
contentItems: [{
|
|
699
|
+
type: "inputText",
|
|
700
|
+
text: "OpenClaw did not register a handler for this app-server tool call."
|
|
701
|
+
}],
|
|
702
|
+
success: false
|
|
703
|
+
};
|
|
704
|
+
if (request.method === "item/commandExecution/requestApproval" || request.method === "item/fileChange/requestApproval") return { decision: "decline" };
|
|
705
|
+
if (request.method === "item/permissions/requestApproval") return {
|
|
706
|
+
permissions: {},
|
|
707
|
+
scope: "turn"
|
|
708
|
+
};
|
|
709
|
+
if (request.method === "item/tool/requestUserInput") return { answers: {} };
|
|
710
|
+
if (request.method === "mcpServer/elicitation/request") return { action: "decline" };
|
|
711
|
+
return {};
|
|
712
|
+
}
|
|
713
|
+
function stringifyCodexAppServerMessage(message) {
|
|
714
|
+
return JSON.stringify(message, (_key, value) => typeof value === "string" ? value.replace(UNPAIRED_SURROGATE_RE, "") : value) ?? "null";
|
|
715
|
+
}
|
|
716
|
+
function timeoutServerRequestResponse(request) {
|
|
717
|
+
if (request.method !== "item/tool/call") return;
|
|
718
|
+
return {
|
|
719
|
+
contentItems: [{
|
|
720
|
+
type: "inputText",
|
|
721
|
+
text: `OpenClaw dynamic tool call timed out after ${CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS}ms before sending a response to Codex.`
|
|
722
|
+
}],
|
|
723
|
+
success: false
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/** Raised when the initialize handshake detects an unsupported app-server version. */
|
|
727
|
+
var CodexAppServerVersionError = class extends Error {
|
|
728
|
+
constructor(detectedVersion) {
|
|
729
|
+
const detected = detectedVersion ? `detected ${detectedVersion}` : "OpenClaw could not determine the running Codex version";
|
|
730
|
+
super(`Codex app-server ${MIN_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.`);
|
|
731
|
+
this.name = "CodexAppServerVersionError";
|
|
732
|
+
this.detectedVersion = detectedVersion;
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
function assertSupportedCodexAppServerVersion(response) {
|
|
736
|
+
const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
|
|
737
|
+
const parsedVersion = parse(detectedVersion ?? "");
|
|
738
|
+
if (!detectedVersion || !parsedVersion || parsedVersion.compare("0.143.0") < 0 || parsedVersion.version === "0.143.0" && parsedVersion.build.length > 0) throw new CodexAppServerVersionError(detectedVersion);
|
|
739
|
+
return detectedVersion;
|
|
740
|
+
}
|
|
741
|
+
function isUnsupportedCodexAppServerVersionError(error) {
|
|
742
|
+
return error instanceof CodexAppServerVersionError;
|
|
743
|
+
}
|
|
744
|
+
function buildCodexAppServerRuntimeIdentity(response, serverVersion) {
|
|
745
|
+
const userAgent = readNonEmptyInitializeString(response.userAgent);
|
|
746
|
+
const codexHome = readNonEmptyInitializeString(response.codexHome);
|
|
747
|
+
const platformFamily = readNonEmptyInitializeString(response.platformFamily);
|
|
748
|
+
const platformOs = readNonEmptyInitializeString(response.platformOs);
|
|
749
|
+
return {
|
|
750
|
+
serverVersion,
|
|
751
|
+
...userAgent ? { userAgent } : {},
|
|
752
|
+
...codexHome ? { codexHome } : {},
|
|
753
|
+
...platformFamily ? { platformFamily } : {},
|
|
754
|
+
...platformOs ? { platformOs } : {}
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function readNonEmptyInitializeString(value) {
|
|
758
|
+
const trimmed = value?.trim();
|
|
759
|
+
return trimmed ? trimmed : void 0;
|
|
760
|
+
}
|
|
761
|
+
/** Extracts the Codex version from the app-server initialize user-agent field. */
|
|
762
|
+
function readCodexVersionFromUserAgent(userAgent) {
|
|
763
|
+
return (userAgent?.match(/^[^/]+\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)(?:[\s(]|$)/))?.[1];
|
|
764
|
+
}
|
|
765
|
+
function redactCodexAppServerLinePreview(value) {
|
|
766
|
+
const redacted = value.replace(/\s+/g, " ").trim().replace(/(Bearer\s+)[A-Za-z0-9._~+/-]+/gi, "$1<redacted>").replace(/("(?:api_?key|authorization|token|access_token|refresh_token)"\s*:\s*")([^"]+)(")/gi, "$1<redacted>$3").replace(/\b([a-z0-9_]*(?:api_?key|authorization|access_token|refresh_token|token))(\s*=\s*)(["']?)[^\s"']+(\3)/gi, "$1$2$3<redacted>$4");
|
|
767
|
+
return redacted.length > CODEX_APP_SERVER_PARSE_LOG_MAX ? `${truncateUtf16Safe(redacted, CODEX_APP_SERVER_PARSE_LOG_MAX)}...` : redacted;
|
|
768
|
+
}
|
|
769
|
+
function appendBoundedTail(current, next, maxLength) {
|
|
770
|
+
const combined = `${current}${next}`;
|
|
771
|
+
return combined.length > maxLength ? combined.slice(combined.length - maxLength) : combined;
|
|
772
|
+
}
|
|
773
|
+
function buildCodexAppServerExitError(code, signal, stderrTail) {
|
|
774
|
+
const stderrPreview = redactCodexAppServerLinePreview(stderrTail);
|
|
775
|
+
const suffix = stderrPreview ? ` stderr=${JSON.stringify(stderrPreview)}` : "";
|
|
776
|
+
return /* @__PURE__ */ new Error(`codex app-server exited: code=${formatExitValue(code)} signal=${formatExitValue(signal)}${suffix}`);
|
|
777
|
+
}
|
|
778
|
+
function shouldBufferCodexAppServerParseFailure(value, error) {
|
|
779
|
+
if (!value.startsWith("{") && !value.startsWith("[")) return false;
|
|
780
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
781
|
+
return message.includes("Unterminated string") || message.includes("Unexpected end of JSON input");
|
|
782
|
+
}
|
|
783
|
+
function logCodexAppServerParseFailure(value, error, fragmentCount) {
|
|
784
|
+
const linePreview = redactCodexAppServerLinePreview(value);
|
|
785
|
+
const suffix = fragmentCount > 1 ? ` fragments=${fragmentCount}` : "";
|
|
786
|
+
embeddedAgentLog.warn("failed to parse codex app-server message", {
|
|
787
|
+
error,
|
|
788
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
789
|
+
fragmentCount,
|
|
790
|
+
linePreview,
|
|
791
|
+
consoleMessage: `failed to parse codex app-server message${suffix}: preview=${JSON.stringify(linePreview)}`
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
const CODEX_APP_SERVER_APPROVAL_REQUEST_METHODS = /* @__PURE__ */ new Set([
|
|
795
|
+
"item/commandExecution/requestApproval",
|
|
796
|
+
"item/fileChange/requestApproval",
|
|
797
|
+
"item/permissions/requestApproval"
|
|
798
|
+
]);
|
|
799
|
+
/** Returns true for app-server approval request methods OpenClaw can answer. */
|
|
800
|
+
function isCodexAppServerApprovalRequest(method) {
|
|
801
|
+
return CODEX_APP_SERVER_APPROVAL_REQUEST_METHODS.has(method);
|
|
802
|
+
}
|
|
803
|
+
function formatExitValue(value) {
|
|
804
|
+
if (value === null || value === void 0) return "null";
|
|
805
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
806
|
+
return "unknown";
|
|
807
|
+
}
|
|
808
|
+
//#endregion
|
|
809
|
+
//#region extensions/codex/src/app-server/auth-start-options.ts
|
|
810
|
+
const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
|
|
811
|
+
const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
|
|
812
|
+
function resolveCodexAppServerHomeDir(agentDir) {
|
|
813
|
+
return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
|
|
814
|
+
}
|
|
815
|
+
/** Forces OpenClaw-owned Codex auth to remain process-local. */
|
|
816
|
+
function withEphemeralCodexAuthStore(params) {
|
|
817
|
+
const { startOptions } = params;
|
|
818
|
+
if (!(startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed") || !params.preparedAuth && params.authProfileId === null) return startOptions;
|
|
819
|
+
if (startOptions.args.at(-2) === "-c" && startOptions.args.at(-1) === CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE) return startOptions;
|
|
820
|
+
return {
|
|
821
|
+
...startOptions,
|
|
822
|
+
args: [
|
|
823
|
+
...startOptions.args,
|
|
824
|
+
"-c",
|
|
825
|
+
CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE
|
|
826
|
+
]
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
//#endregion
|
|
830
|
+
//#region extensions/codex/src/app-server/desktop-app-paths.ts
|
|
831
|
+
/** Shared path candidates for Codex's macOS desktop app bundle. */
|
|
832
|
+
const MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES = [{
|
|
833
|
+
appName: "ChatGPT.app",
|
|
834
|
+
appBundlePath: "/Applications/ChatGPT.app",
|
|
835
|
+
appServerCommandPath: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
|
836
|
+
bundledMarketplacePath: "/Applications/ChatGPT.app/Contents/Resources/plugins/openai-bundled"
|
|
837
|
+
}, {
|
|
838
|
+
appName: "Codex.app",
|
|
839
|
+
appBundlePath: "/Applications/Codex.app",
|
|
840
|
+
appServerCommandPath: "/Applications/Codex.app/Contents/Resources/codex",
|
|
841
|
+
bundledMarketplacePath: "/Applications/Codex.app/Contents/Resources/plugins/openai-bundled"
|
|
842
|
+
}];
|
|
843
|
+
function resolveMacOSDesktopCodexBundledMarketplaceCandidates(platform = process.platform) {
|
|
844
|
+
return platform === "darwin" ? MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.map((candidate) => candidate.bundledMarketplacePath) : [];
|
|
845
|
+
}
|
|
846
|
+
function resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath(params = {}) {
|
|
847
|
+
const candidates = params.candidates ?? resolveMacOSDesktopCodexBundledMarketplaceCandidates(params.platform);
|
|
848
|
+
const pathExists = params.pathExists ?? existsSync;
|
|
849
|
+
return candidates.find((candidate) => pathExists(candidate));
|
|
850
|
+
}
|
|
851
|
+
//#endregion
|
|
852
|
+
//#region extensions/codex/src/app-server/computer-use-cache.ts
|
|
853
|
+
/** Shared Computer Use plugin cache reconciliation for isolated Codex homes. */
|
|
854
|
+
const DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH = resolveMacOSDesktopCodexBundledMarketplaceCandidates("darwin")[0] ?? "";
|
|
855
|
+
const DEFAULT_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
|
|
856
|
+
async function ensureCodexComputerUseSharedPluginCache(params) {
|
|
857
|
+
if (!params.config.enabled) return skippedCacheResult("disabled", "Computer Use cache sharing skipped because it is disabled.");
|
|
858
|
+
if (params.config.pluginCacheMode === "independent") return skippedCacheResult("independent", "Computer Use cache sharing skipped because pluginCacheMode is independent.");
|
|
859
|
+
if (params.config.marketplaceName || params.config.marketplacePath) return skippedCacheResult("explicit_marketplace", "Computer Use cache sharing skipped because an explicit marketplace is configured.");
|
|
860
|
+
const bundledMarketplacePath = resolveComputerUseBundledMarketplacePath(params);
|
|
861
|
+
const sourcePluginRoot = path.join(bundledMarketplacePath, "plugins", params.config.pluginName);
|
|
862
|
+
const version = await readBundledPluginVersion(sourcePluginRoot);
|
|
863
|
+
if (!version) return skippedCacheResult("source_missing", `Computer Use bundled plugin source was not found at ${sourcePluginRoot}.`);
|
|
864
|
+
const marketplaceName = params.config.marketplaceName ?? DEFAULT_BUNDLED_MARKETPLACE_NAME;
|
|
865
|
+
const cacheRoot = path.join(params.codexHome, "plugins", "cache", marketplaceName, params.config.pluginName);
|
|
866
|
+
const cachePath = path.join(cacheRoot, version);
|
|
867
|
+
return {
|
|
868
|
+
status: "shared",
|
|
869
|
+
changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version),
|
|
870
|
+
cachePath,
|
|
871
|
+
targetPath: sourcePluginRoot,
|
|
872
|
+
version,
|
|
873
|
+
removedStaleVersions: [],
|
|
874
|
+
warnings: [],
|
|
875
|
+
message: `Computer Use plugin cache ${cachePath} contains bundled plugin ${sourcePluginRoot}.`
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function resolveComputerUseBundledMarketplacePath(params) {
|
|
879
|
+
return params.bundledMarketplacePath ?? resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath({ candidates: params.bundledMarketplacePathCandidates }) ?? params.bundledMarketplacePathCandidates?.[0] ?? DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH;
|
|
880
|
+
}
|
|
881
|
+
async function readBundledPluginVersion(sourcePluginRoot) {
|
|
882
|
+
const pluginJsonPath = path.join(sourcePluginRoot, ".codex-plugin", "plugin.json");
|
|
883
|
+
let raw;
|
|
884
|
+
try {
|
|
885
|
+
raw = await fs.readFile(pluginJsonPath, "utf8");
|
|
886
|
+
} catch {
|
|
887
|
+
return;
|
|
1056
888
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
889
|
+
try {
|
|
890
|
+
const parsed = JSON.parse(raw);
|
|
891
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
892
|
+
} catch {
|
|
893
|
+
return;
|
|
1061
894
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
embeddedAgentLog.warn("codex app-server write failed", {
|
|
1069
|
-
error,
|
|
1070
|
-
id,
|
|
1071
|
-
method
|
|
1072
|
-
});
|
|
1073
|
-
onError?.(error);
|
|
1074
|
-
}
|
|
1075
|
-
});
|
|
895
|
+
}
|
|
896
|
+
async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version) {
|
|
897
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
898
|
+
const stat = await fs.lstat(cachePath).catch(() => void 0);
|
|
899
|
+
if (stat?.isDirectory() && !stat.isSymbolicLink()) {
|
|
900
|
+
if (await readBundledPluginVersion(cachePath) === version) return false;
|
|
1076
901
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
902
|
+
const cacheRoot = path.dirname(cachePath);
|
|
903
|
+
const cacheName = path.basename(cachePath);
|
|
904
|
+
const stagingRoot = await fs.mkdtemp(path.join(cacheRoot, `.${cacheName}.staging-`));
|
|
905
|
+
const stagedPath = path.join(stagingRoot, cacheName);
|
|
906
|
+
const backupPath = path.join(cacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
|
|
907
|
+
let backupCreated = false;
|
|
908
|
+
try {
|
|
909
|
+
await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
|
|
910
|
+
if (stat) {
|
|
911
|
+
await fs.rename(cachePath, backupPath);
|
|
912
|
+
backupCreated = true;
|
|
1082
913
|
}
|
|
1083
|
-
const trimmed = rawLine.trim();
|
|
1084
|
-
if (!trimmed) return;
|
|
1085
|
-
let parsed;
|
|
1086
914
|
try {
|
|
1087
|
-
|
|
915
|
+
await fs.rename(stagedPath, cachePath);
|
|
1088
916
|
} catch (error) {
|
|
1089
|
-
if (
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
};
|
|
1095
|
-
return;
|
|
917
|
+
if (backupCreated) try {
|
|
918
|
+
await fs.rename(backupPath, cachePath);
|
|
919
|
+
backupCreated = false;
|
|
920
|
+
} catch (restoreError) {
|
|
921
|
+
throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
|
|
1096
922
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
923
|
+
throw error;
|
|
924
|
+
}
|
|
925
|
+
if (backupCreated) await fs.rm(backupPath, {
|
|
926
|
+
recursive: true,
|
|
927
|
+
force: true
|
|
928
|
+
});
|
|
929
|
+
return true;
|
|
930
|
+
} finally {
|
|
931
|
+
await fs.rm(stagingRoot, {
|
|
932
|
+
recursive: true,
|
|
933
|
+
force: true
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function skippedCacheResult(status, message) {
|
|
938
|
+
return {
|
|
939
|
+
status,
|
|
940
|
+
changed: false,
|
|
941
|
+
message,
|
|
942
|
+
removedStaleVersions: [],
|
|
943
|
+
warnings: status === "source_missing" ? [message] : []
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
//#endregion
|
|
947
|
+
//#region extensions/codex/src/app-server/auth-bridge.ts
|
|
948
|
+
const CODEX_APP_SERVER_AUTH_PROVIDER = "openai";
|
|
949
|
+
const OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER = "openai-codex";
|
|
950
|
+
const LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER = "codex-cli";
|
|
951
|
+
const CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS = [CODEX_APP_SERVER_AUTH_PROVIDER, LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER];
|
|
952
|
+
const OPENAI_PROVIDER = "openai";
|
|
953
|
+
const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai:default";
|
|
954
|
+
const CODEX_HOME_ENV_VAR = "CODEX_HOME";
|
|
955
|
+
const HOME_ENV_VAR = "HOME";
|
|
956
|
+
const CODEX_API_KEY_ENV_VAR = "CODEX_API_KEY";
|
|
957
|
+
const OPENAI_API_KEY_ENV_VAR = "OPENAI_API_KEY";
|
|
958
|
+
const CODEX_ACCESS_TOKEN_ENV_VAR = "CODEX_ACCESS_TOKEN";
|
|
959
|
+
const CODEX_APP_SERVER_API_KEY_ENV_VARS = [CODEX_API_KEY_ENV_VAR, OPENAI_API_KEY_ENV_VAR];
|
|
960
|
+
const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
|
|
961
|
+
CODEX_API_KEY_ENV_VAR,
|
|
962
|
+
OPENAI_API_KEY_ENV_VAR,
|
|
963
|
+
CODEX_ACCESS_TOKEN_ENV_VAR
|
|
964
|
+
];
|
|
965
|
+
const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
|
|
966
|
+
const CODEX_AUTH_JSON_FILENAME = "auth.json";
|
|
967
|
+
const CODEX_HOME_DIRNAME = ".codex";
|
|
968
|
+
const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
|
|
969
|
+
async function bridgeCodexAppServerStartOptions(params) {
|
|
970
|
+
if (params.startOptions.transport !== "stdio") return params.startOptions;
|
|
971
|
+
const scopedStartOptions = await withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
|
|
972
|
+
if (params.preparedAuth) return withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
|
|
973
|
+
if (params.authProfileId === null) return scopedStartOptions;
|
|
974
|
+
const store = resolveCodexAppServerAuthProfileStore({
|
|
975
|
+
agentDir: params.agentDir,
|
|
976
|
+
authProfileId: params.authProfileId,
|
|
977
|
+
authProfileStore: params.authProfileStore,
|
|
978
|
+
config: params.config
|
|
979
|
+
});
|
|
980
|
+
return shouldClearOpenAiApiKeyForCodexAuthProfile({
|
|
981
|
+
store,
|
|
982
|
+
authProfileId: resolveCodexAppServerAuthProfileId({
|
|
983
|
+
authProfileId: params.authProfileId,
|
|
984
|
+
store,
|
|
985
|
+
config: params.config
|
|
986
|
+
}),
|
|
987
|
+
config: params.config
|
|
988
|
+
}) ? withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS) : scopedStartOptions;
|
|
989
|
+
}
|
|
990
|
+
function resolveCodexAppServerAuthProfileId(params) {
|
|
991
|
+
const requested = params.authProfileId?.trim();
|
|
992
|
+
if (requested) return requested;
|
|
993
|
+
return resolveAuthProfileOrder({
|
|
994
|
+
cfg: params.config,
|
|
995
|
+
store: params.store,
|
|
996
|
+
provider: CODEX_APP_SERVER_AUTH_PROVIDER
|
|
997
|
+
})[0]?.trim();
|
|
998
|
+
}
|
|
999
|
+
function resolveCodexAppServerAuthProfileIdForAgent(params) {
|
|
1000
|
+
const store = resolveCodexAppServerAuthProfileStore({
|
|
1001
|
+
agentDir: params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {}),
|
|
1002
|
+
authProfileId: params.authProfileId,
|
|
1003
|
+
authProfileStore: params.authProfileStore,
|
|
1004
|
+
config: params.config
|
|
1005
|
+
});
|
|
1006
|
+
return resolveCodexAppServerAuthProfileId({
|
|
1007
|
+
authProfileId: params.authProfileId,
|
|
1008
|
+
store,
|
|
1009
|
+
config: params.config
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
function ensureCodexAppServerAuthProfileStore(params) {
|
|
1013
|
+
return ensureAuthProfileStore(params.agentDir, {
|
|
1014
|
+
allowKeychainPrompt: false,
|
|
1015
|
+
config: params.config,
|
|
1016
|
+
externalCliProviderIds: CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS,
|
|
1017
|
+
...params.authProfileId ? { externalCliProfileIds: [params.authProfileId] } : {}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
function resolveCodexAppServerAuthProfileStore(params) {
|
|
1021
|
+
if (params.authProfileStore) return params.authProfileStore;
|
|
1022
|
+
return ensureCodexAppServerAuthProfileStore({
|
|
1023
|
+
agentDir: params.agentDir,
|
|
1024
|
+
authProfileId: params.authProfileId,
|
|
1025
|
+
config: params.config
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
/** Resolves prepared profile login material once so cache identity and RPC login cannot drift. */
|
|
1029
|
+
async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
|
|
1030
|
+
const agentDir = params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {});
|
|
1031
|
+
const store = resolveCodexAppServerAuthProfileStore({
|
|
1032
|
+
agentDir,
|
|
1033
|
+
authProfileId: params.authProfileId,
|
|
1034
|
+
authProfileStore: params.authProfileStore,
|
|
1035
|
+
config: params.config
|
|
1036
|
+
});
|
|
1037
|
+
const profileId = resolveCodexAppServerAuthProfileId({
|
|
1038
|
+
authProfileId: params.authProfileId,
|
|
1039
|
+
store,
|
|
1040
|
+
config: params.config
|
|
1041
|
+
});
|
|
1042
|
+
if (!profileId) return;
|
|
1043
|
+
const credential = store.profiles[profileId];
|
|
1044
|
+
if (!credential || !isCodexAppServerAuthProfileCredential(credential, params.config)) return;
|
|
1045
|
+
const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
1046
|
+
agentDir,
|
|
1047
|
+
authProfileId: profileId,
|
|
1048
|
+
authProfileStore: store,
|
|
1049
|
+
config: params.config
|
|
1050
|
+
});
|
|
1051
|
+
if (!loginParams) return;
|
|
1052
|
+
const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
|
|
1053
|
+
const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
|
|
1054
|
+
return {
|
|
1055
|
+
loginParams,
|
|
1056
|
+
secretFreeCacheKey: credential.type === "api_key" && loginParams.type === "apiKey" ? `${accountId}:${fingerprintApiKeyAuthProfileCacheKey(loginParams.apiKey)}` : loginParams.type === "chatgptAuthTokens" && (credential.type === "token" || !stableChatgptAccountId) ? `${accountId}:${fingerprintTokenAuthProfileCacheKey(loginParams.accessToken)}` : accountId
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
/** Maps one prepared route to one mutually exclusive app-server auth handoff. */
|
|
1060
|
+
async function resolveCodexAppServerPreparedAuthHandoff(params) {
|
|
1061
|
+
if (params.authRequirement === "api-key") {
|
|
1062
|
+
const apiKey = params.resolvedApiKey?.trim();
|
|
1063
|
+
if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
|
|
1064
|
+
return {
|
|
1065
|
+
nativeAuthProfile: false,
|
|
1066
|
+
preparedAuth: {
|
|
1067
|
+
kind: "api-key",
|
|
1068
|
+
apiKey
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
const authProfileId = params.authProfileId?.trim() || void 0;
|
|
1073
|
+
const nativeAuthProfile = isCodexAppServerNativeAuthProfile({
|
|
1074
|
+
authProfileId,
|
|
1075
|
+
authProfileStore: params.authProfileStore,
|
|
1076
|
+
agentDir: params.agentDir,
|
|
1077
|
+
config: params.config
|
|
1078
|
+
});
|
|
1079
|
+
if (params.authRequirement !== "subscription") return {
|
|
1080
|
+
authProfileId,
|
|
1081
|
+
nativeAuthProfile
|
|
1082
|
+
};
|
|
1083
|
+
if (!authProfileId || !nativeAuthProfile) throw new Error(params.subscriptionProfileRequiredError);
|
|
1084
|
+
const snapshot = await resolveCodexAppServerPreparedAuthProfileSnapshot({
|
|
1085
|
+
authProfileId,
|
|
1086
|
+
authProfileStore: params.authProfileStore,
|
|
1087
|
+
agentDir: params.agentDir,
|
|
1088
|
+
config: params.config
|
|
1089
|
+
});
|
|
1090
|
+
if (!snapshot) throw new Error(params.subscriptionProfileUnusableError);
|
|
1091
|
+
return {
|
|
1092
|
+
authProfileId,
|
|
1093
|
+
nativeAuthProfile,
|
|
1094
|
+
preparedAuth: {
|
|
1095
|
+
kind: "profile",
|
|
1096
|
+
profileId: authProfileId,
|
|
1097
|
+
store: params.authProfileStore,
|
|
1098
|
+
snapshot
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
async function resolveCodexAppServerAuthAccountCacheKey(params) {
|
|
1103
|
+
const agentDir = params.agentDir?.trim() || resolveDefaultAgentDir(params.config ?? {});
|
|
1104
|
+
const store = resolveCodexAppServerAuthProfileStore({
|
|
1105
|
+
agentDir,
|
|
1106
|
+
authProfileId: params.authProfileId,
|
|
1107
|
+
authProfileStore: params.authProfileStore,
|
|
1108
|
+
config: params.config
|
|
1109
|
+
});
|
|
1110
|
+
const profileId = resolveCodexAppServerAuthProfileId({
|
|
1111
|
+
authProfileId: params.authProfileId,
|
|
1112
|
+
store,
|
|
1113
|
+
config: params.config
|
|
1114
|
+
});
|
|
1115
|
+
if (!profileId) return;
|
|
1116
|
+
const credential = store.profiles[profileId];
|
|
1117
|
+
if (!credential || !isCodexAppServerAuthProfileCredential(credential, params.config)) return;
|
|
1118
|
+
if (credential.type === "api_key") {
|
|
1119
|
+
const apiKey = (await resolveApiKeyForProfile({
|
|
1120
|
+
store,
|
|
1121
|
+
profileId,
|
|
1122
|
+
agentDir
|
|
1123
|
+
}))?.apiKey?.trim();
|
|
1124
|
+
return apiKey ? `${resolveChatgptAccountId(profileId, credential)}:${fingerprintApiKeyAuthProfileCacheKey(apiKey)}` : resolveChatgptAccountId(profileId, credential);
|
|
1125
|
+
}
|
|
1126
|
+
if (credential.type === "token") {
|
|
1127
|
+
const accessToken = (await resolveApiKeyForProfile({
|
|
1128
|
+
store,
|
|
1129
|
+
profileId,
|
|
1130
|
+
agentDir
|
|
1131
|
+
}))?.apiKey?.trim();
|
|
1132
|
+
return accessToken ? `${resolveChatgptAccountId(profileId, credential)}:${fingerprintTokenAuthProfileCacheKey(accessToken)}` : resolveChatgptAccountId(profileId, credential);
|
|
1133
|
+
}
|
|
1134
|
+
return resolveChatgptAccountId(profileId, credential);
|
|
1135
|
+
}
|
|
1136
|
+
function resolveCodexAppServerEnvApiKeyCacheKey(params) {
|
|
1137
|
+
if (params.startOptions.transport !== "stdio") return;
|
|
1138
|
+
const apiKey = readFirstNonEmptyEnvEntry(resolveCodexAppServerSpawnEnv(params.startOptions, params.baseEnv ?? process.env, params.platform ?? process.platform), CODEX_APP_SERVER_API_KEY_ENV_VARS);
|
|
1139
|
+
if (!apiKey) return;
|
|
1140
|
+
const hash = createHash("sha256");
|
|
1141
|
+
hash.update("openclaw:codex:app-server-env-api-key:v1");
|
|
1142
|
+
hash.update("\0");
|
|
1143
|
+
hash.update(apiKey.key);
|
|
1144
|
+
hash.update("\0");
|
|
1145
|
+
hash.update(apiKey.value);
|
|
1146
|
+
return `${apiKey.key}:sha256:${hash.digest("hex")}`;
|
|
1147
|
+
}
|
|
1148
|
+
function resolveCodexAppServerFallbackApiKeyCacheKey(params) {
|
|
1149
|
+
if (params.startOptions.transport !== "stdio") return;
|
|
1150
|
+
return resolveCodexAppServerEnvApiKeyCacheKey(params) ?? resolveCodexCliAuthFileApiKeyCacheKey(params.baseEnv ?? process.env);
|
|
1151
|
+
}
|
|
1152
|
+
/** Secret-free cache identity for an API key already resolved by the runtime plan. */
|
|
1153
|
+
function resolveCodexAppServerPreparedApiKeyCacheKey(apiKey) {
|
|
1154
|
+
const resolved = apiKey?.trim();
|
|
1155
|
+
return resolved ? fingerprintApiKeyAuthProfileCacheKey(resolved) : void 0;
|
|
1156
|
+
}
|
|
1157
|
+
function fingerprintApiKeyAuthProfileCacheKey(apiKey) {
|
|
1158
|
+
const hash = createHash("sha256");
|
|
1159
|
+
hash.update("openclaw:codex:app-server-auth-profile-api-key:v1");
|
|
1160
|
+
hash.update("\0");
|
|
1161
|
+
hash.update(apiKey);
|
|
1162
|
+
return `api_key:sha256:${hash.digest("hex")}`;
|
|
1163
|
+
}
|
|
1164
|
+
function fingerprintTokenAuthProfileCacheKey(accessToken) {
|
|
1165
|
+
const hash = createHash("sha256");
|
|
1166
|
+
hash.update("openclaw:codex:app-server-auth-profile-token:v1");
|
|
1167
|
+
hash.update("\0");
|
|
1168
|
+
hash.update(accessToken);
|
|
1169
|
+
return `token:sha256:${hash.digest("hex")}`;
|
|
1170
|
+
}
|
|
1171
|
+
function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
|
|
1172
|
+
const hash = createHash("sha256");
|
|
1173
|
+
hash.update("openclaw:codex:app-server-cli-auth-json-api-key:v1");
|
|
1174
|
+
hash.update("\0");
|
|
1175
|
+
hash.update(apiKey);
|
|
1176
|
+
return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
|
|
1177
|
+
}
|
|
1178
|
+
async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
|
|
1179
|
+
const codexHome = startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim() ? startOptions.env[CODEX_HOME_ENV_VAR] : startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir(process.env) : resolveCodexAppServerHomeDir(agentDir);
|
|
1180
|
+
const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
|
|
1181
|
+
await fs.mkdir(codexHome, { recursive: true });
|
|
1182
|
+
await ensureCodexComputerUseSharedPluginCache({
|
|
1183
|
+
codexHome,
|
|
1184
|
+
config: resolveCodexComputerUseConfig({ pluginConfig })
|
|
1185
|
+
});
|
|
1186
|
+
if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
|
|
1187
|
+
const nextStartOptions = {
|
|
1188
|
+
...startOptions,
|
|
1189
|
+
env: {
|
|
1190
|
+
...startOptions.env,
|
|
1191
|
+
[CODEX_HOME_ENV_VAR]: codexHome,
|
|
1192
|
+
...nativeHome ? { [HOME_ENV_VAR]: nativeHome } : {}
|
|
1099
1193
|
}
|
|
1100
|
-
|
|
1194
|
+
};
|
|
1195
|
+
const clearEnv = withoutClearedCodexHomeEnv(startOptions.clearEnv);
|
|
1196
|
+
if (clearEnv) nextStartOptions.clearEnv = clearEnv;
|
|
1197
|
+
else delete nextStartOptions.clearEnv;
|
|
1198
|
+
return nextStartOptions;
|
|
1199
|
+
}
|
|
1200
|
+
function withoutClearedCodexHomeEnv(clearEnv) {
|
|
1201
|
+
if (!clearEnv) return;
|
|
1202
|
+
const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
|
|
1203
|
+
const filtered = clearEnv.filter((envVar) => !reserved.has(envVar.trim().toUpperCase()));
|
|
1204
|
+
return filtered.length === clearEnv.length ? clearEnv : filtered;
|
|
1205
|
+
}
|
|
1206
|
+
async function applyCodexAppServerAuthProfile(params) {
|
|
1207
|
+
if (params.preparedAuth?.kind === "profile") {
|
|
1208
|
+
await params.client.request("account/login/start", params.preparedAuth.snapshot.loginParams);
|
|
1209
|
+
return;
|
|
1101
1210
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
parsed = JSON.parse(candidate);
|
|
1109
|
-
} catch (error) {
|
|
1110
|
-
const lineCount = pending.lineCount + 1;
|
|
1111
|
-
if (shouldBufferCodexAppServerParseFailure(candidate.trim(), error) && candidate.length <= CODEX_APP_SERVER_PARSE_BUFFER_MAX && lineCount <= CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES) {
|
|
1112
|
-
this.pendingParse = {
|
|
1113
|
-
text: candidate,
|
|
1114
|
-
lineCount,
|
|
1115
|
-
firstError: pending.firstError
|
|
1116
|
-
};
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
this.pendingParse = void 0;
|
|
1120
|
-
logCodexAppServerParseFailure(candidate, error, lineCount);
|
|
1121
|
-
return;
|
|
1122
|
-
}
|
|
1123
|
-
this.pendingParse = void 0;
|
|
1124
|
-
this.handleParsedMessage(parsed);
|
|
1211
|
+
if (params.preparedAuth?.kind === "api-key") {
|
|
1212
|
+
await params.client.request("account/login/start", {
|
|
1213
|
+
type: "apiKey",
|
|
1214
|
+
apiKey: params.preparedAuth.apiKey
|
|
1215
|
+
});
|
|
1216
|
+
return;
|
|
1125
1217
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if (
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
this.handleNotification({
|
|
1143
|
-
method: message.method,
|
|
1144
|
-
params: message.params
|
|
1218
|
+
if (params.authProfileId === null) return;
|
|
1219
|
+
const loginParams = await resolveCodexAppServerAuthProfileLoginParams({
|
|
1220
|
+
agentDir: params.agentDir,
|
|
1221
|
+
authProfileId: params.authProfileId,
|
|
1222
|
+
authProfileStore: params.authProfileStore,
|
|
1223
|
+
config: params.config
|
|
1224
|
+
});
|
|
1225
|
+
if (!loginParams) {
|
|
1226
|
+
if (params.startOptions?.transport !== "stdio") return;
|
|
1227
|
+
const env = resolveCodexAppServerSpawnEnv(params.startOptions, process.env);
|
|
1228
|
+
const fallbackLoginParams = await resolveCodexAppServerFallbackApiKeyLoginParams({
|
|
1229
|
+
client: params.client,
|
|
1230
|
+
env,
|
|
1231
|
+
codexCliAuthEnv: process.env
|
|
1145
1232
|
});
|
|
1233
|
+
if (fallbackLoginParams) await params.client.request("account/login/start", fallbackLoginParams);
|
|
1234
|
+
return;
|
|
1146
1235
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1236
|
+
await params.client.request("account/login/start", loginParams);
|
|
1237
|
+
}
|
|
1238
|
+
function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
1239
|
+
return resolveCodexAppServerAuthProfileLoginParamsInternal(params);
|
|
1240
|
+
}
|
|
1241
|
+
async function refreshCodexAppServerAuthTokens(params) {
|
|
1242
|
+
const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
1243
|
+
...params,
|
|
1244
|
+
forceOAuthRefresh: true
|
|
1245
|
+
});
|
|
1246
|
+
if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
|
|
1247
|
+
return {
|
|
1248
|
+
accessToken: loginParams.accessToken,
|
|
1249
|
+
chatgptAccountId: loginParams.chatgptAccountId,
|
|
1250
|
+
chatgptPlanType: loginParams.chatgptPlanType ?? null
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
|
|
1254
|
+
const store = resolveCodexAppServerAuthProfileStore({
|
|
1255
|
+
agentDir: params.agentDir,
|
|
1256
|
+
authProfileId: params.authProfileId,
|
|
1257
|
+
authProfileStore: params.authProfileStore,
|
|
1258
|
+
config: params.config
|
|
1259
|
+
});
|
|
1260
|
+
const profileId = resolveCodexAppServerAuthProfileId({
|
|
1261
|
+
authProfileId: params.authProfileId,
|
|
1262
|
+
store,
|
|
1263
|
+
config: params.config
|
|
1264
|
+
});
|
|
1265
|
+
if (!profileId) return;
|
|
1266
|
+
const credential = store.profiles[profileId];
|
|
1267
|
+
if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found.`);
|
|
1268
|
+
if (!isCodexAppServerAuthProfileCredential(credential, params.config)) throw new Error(`Codex app-server auth profile "${profileId}" must be OpenAI Codex auth or an OpenAI API-key backup.`);
|
|
1269
|
+
const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
|
|
1270
|
+
agentDir: params.agentDir,
|
|
1271
|
+
store,
|
|
1272
|
+
preferStoreCredential: Boolean(params.authProfileStore?.profiles[profileId]),
|
|
1273
|
+
forceOAuthRefresh: params.forceOAuthRefresh === true,
|
|
1274
|
+
config: params.config
|
|
1275
|
+
});
|
|
1276
|
+
if (!loginParams) throw new Error(`Codex app-server auth profile "${profileId}" does not contain usable credentials.`);
|
|
1277
|
+
return loginParams;
|
|
1278
|
+
}
|
|
1279
|
+
async function resolveCodexAppServerFallbackApiKeyLoginParams(params) {
|
|
1280
|
+
const apiKey = readFirstNonEmptyEnv(params.env, CODEX_APP_SERVER_API_KEY_ENV_VARS) ?? await readCodexCliAuthFileApiKey(params.codexCliAuthEnv);
|
|
1281
|
+
if (!apiKey) return;
|
|
1282
|
+
if ((await params.client.request("account/read", { refreshToken: false })).account) return;
|
|
1283
|
+
return {
|
|
1284
|
+
type: "apiKey",
|
|
1285
|
+
apiKey
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function resolveCodexCliAuthFilePath(env) {
|
|
1289
|
+
const configuredCodexHome = env[CODEX_HOME_ENV_VAR]?.trim();
|
|
1290
|
+
if (configuredCodexHome) return path.join(resolveHomeRelativePath(configuredCodexHome, env), CODEX_AUTH_JSON_FILENAME);
|
|
1291
|
+
const home = env[HOME_ENV_VAR]?.trim() || env.USERPROFILE?.trim() || os.homedir();
|
|
1292
|
+
return path.join(home, CODEX_HOME_DIRNAME, CODEX_AUTH_JSON_FILENAME);
|
|
1293
|
+
}
|
|
1294
|
+
function resolveHomeRelativePath(value, env) {
|
|
1295
|
+
if (value === "~" || value.startsWith("~/") || value.startsWith("~\\")) {
|
|
1296
|
+
const home = env[HOME_ENV_VAR]?.trim() || env.USERPROFILE?.trim() || os.homedir();
|
|
1297
|
+
return path.join(home, value.slice(value === "~" ? 1 : 2));
|
|
1156
1298
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
this.writeMessage({
|
|
1168
|
-
id: request.id,
|
|
1169
|
-
result: defaultServerRequestResponse(request)
|
|
1170
|
-
});
|
|
1171
|
-
} catch (error) {
|
|
1172
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1173
|
-
embeddedAgentLog.warn("codex app-server server request handler failed", {
|
|
1174
|
-
id: request.id,
|
|
1175
|
-
method: request.method,
|
|
1176
|
-
error
|
|
1177
|
-
});
|
|
1178
|
-
this.writeMessage({
|
|
1179
|
-
id: request.id,
|
|
1180
|
-
error: {
|
|
1181
|
-
code: -32603,
|
|
1182
|
-
message
|
|
1183
|
-
}
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1299
|
+
return value;
|
|
1300
|
+
}
|
|
1301
|
+
function parseCodexCliAuthFileApiKey(raw) {
|
|
1302
|
+
let parsed;
|
|
1303
|
+
try {
|
|
1304
|
+
parsed = JSON.parse(raw);
|
|
1305
|
+
} catch {
|
|
1306
|
+
return;
|
|
1186
1307
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
method: request.method,
|
|
1197
|
-
timeoutMs: CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS
|
|
1198
|
-
});
|
|
1199
|
-
resolve(timeoutResponse);
|
|
1200
|
-
}, CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS);
|
|
1201
|
-
timeout.unref?.();
|
|
1202
|
-
})]);
|
|
1203
|
-
} finally {
|
|
1204
|
-
if (timeout) clearTimeout(timeout);
|
|
1205
|
-
}
|
|
1308
|
+
if (!parsed || typeof parsed !== "object") return;
|
|
1309
|
+
const apiKey = parsed.OPENAI_API_KEY;
|
|
1310
|
+
return typeof apiKey === "string" && apiKey.trim() ? apiKey.trim() : void 0;
|
|
1311
|
+
}
|
|
1312
|
+
async function readCodexCliAuthFileApiKey(env) {
|
|
1313
|
+
try {
|
|
1314
|
+
return parseCodexCliAuthFileApiKey(await fs.readFile(resolveCodexCliAuthFilePath(env), "utf8"));
|
|
1315
|
+
} catch {
|
|
1316
|
+
return;
|
|
1206
1317
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1318
|
+
}
|
|
1319
|
+
function resolveCodexCliAuthFileApiKeyCacheKey(env) {
|
|
1320
|
+
try {
|
|
1321
|
+
const apiKey = parseCodexCliAuthFileApiKey(fsSync.readFileSync(resolveCodexCliAuthFilePath(env), "utf8"));
|
|
1322
|
+
return apiKey ? fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) : void 0;
|
|
1323
|
+
} catch {
|
|
1324
|
+
return;
|
|
1212
1325
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1326
|
+
}
|
|
1327
|
+
async function resolveLoginParamsForCredential(profileId, credential, params) {
|
|
1328
|
+
if (credential.type === "api_key") {
|
|
1329
|
+
const apiKey = (await resolveApiKeyForProfile({
|
|
1330
|
+
store: params.preferStoreCredential ? params.store : ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }),
|
|
1331
|
+
profileId,
|
|
1332
|
+
agentDir: params.agentDir
|
|
1333
|
+
}))?.apiKey?.trim();
|
|
1334
|
+
return apiKey ? {
|
|
1335
|
+
type: "apiKey",
|
|
1336
|
+
apiKey
|
|
1337
|
+
} : void 0;
|
|
1217
1338
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1339
|
+
if (credential.type === "token") {
|
|
1340
|
+
const accessToken = (await resolveApiKeyForProfile({
|
|
1341
|
+
store: params.preferStoreCredential ? params.store : ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false }),
|
|
1342
|
+
profileId,
|
|
1343
|
+
agentDir: params.agentDir
|
|
1344
|
+
}))?.apiKey?.trim();
|
|
1345
|
+
return accessToken ? buildChatgptAuthTokensParams(profileId, credential, accessToken) : void 0;
|
|
1220
1346
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1347
|
+
if (credential.type !== "oauth") return;
|
|
1348
|
+
const resolvedCredential = await resolveOAuthCredentialForCodexAppServer(profileId, credential, {
|
|
1349
|
+
agentDir: params.agentDir,
|
|
1350
|
+
store: params.store,
|
|
1351
|
+
preferStoreCredential: params.preferStoreCredential,
|
|
1352
|
+
forceRefresh: params.forceOAuthRefresh,
|
|
1353
|
+
config: params.config
|
|
1354
|
+
});
|
|
1355
|
+
const accessToken = resolvedCredential.access?.trim();
|
|
1356
|
+
return accessToken ? buildChatgptAuthTokensParams(profileId, resolvedCredential, accessToken) : void 0;
|
|
1357
|
+
}
|
|
1358
|
+
async function resolveOAuthCredentialForCodexAppServer(profileId, credential, params) {
|
|
1359
|
+
const ownerAgentDir = resolvePersistedAuthProfileOwnerAgentDir({
|
|
1360
|
+
agentDir: params.agentDir,
|
|
1361
|
+
profileId
|
|
1362
|
+
});
|
|
1363
|
+
const persistedCredential = findPersistedAuthProfileCredential({
|
|
1364
|
+
agentDir: ownerAgentDir,
|
|
1365
|
+
profileId
|
|
1366
|
+
});
|
|
1367
|
+
const useScopedCredential = params.preferStoreCredential && shouldUseScopedOAuthCredential({
|
|
1368
|
+
store: params.store,
|
|
1369
|
+
profileId,
|
|
1370
|
+
persistedCredential,
|
|
1371
|
+
suppliedCredential: credential,
|
|
1372
|
+
config: params.config
|
|
1373
|
+
});
|
|
1374
|
+
const store = useScopedCredential ? params.store : ensureCodexAppServerAuthProfileStore({
|
|
1375
|
+
agentDir: ownerAgentDir,
|
|
1376
|
+
authProfileId: profileId,
|
|
1377
|
+
config: params.config
|
|
1378
|
+
});
|
|
1379
|
+
const persistedOAuthCredential = !useScopedCredential && persistedCredential?.type === "oauth" && isCodexAppServerAuthProvider(persistedCredential.provider, params.config) ? persistedCredential : void 0;
|
|
1380
|
+
const ownerCredential = store.profiles[profileId];
|
|
1381
|
+
const overlaidOAuthCredential = ownerCredential?.type === "oauth" && isCodexAppServerAuthProvider(ownerCredential.provider, params.config) ? ownerCredential : void 0;
|
|
1382
|
+
if (useScopedCredential && overlaidOAuthCredential) return await resolveScopedOAuthCredential({
|
|
1383
|
+
store,
|
|
1384
|
+
profileId,
|
|
1385
|
+
credential: overlaidOAuthCredential,
|
|
1386
|
+
forceRefresh: params.forceRefresh
|
|
1387
|
+
});
|
|
1388
|
+
if (params.forceRefresh && !persistedOAuthCredential && overlaidOAuthCredential) {
|
|
1389
|
+
const refreshedRuntimeCredential = await refreshOAuthCredentialForRuntime({ credential: overlaidOAuthCredential });
|
|
1390
|
+
if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh.`);
|
|
1391
|
+
store.profiles[profileId] = refreshedRuntimeCredential;
|
|
1392
|
+
return refreshedRuntimeCredential;
|
|
1228
1393
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1394
|
+
const resolved = await resolveApiKeyForProfile({
|
|
1395
|
+
store,
|
|
1396
|
+
profileId,
|
|
1397
|
+
agentDir: ownerAgentDir,
|
|
1398
|
+
forceRefresh: params.forceRefresh && Boolean(persistedOAuthCredential)
|
|
1399
|
+
});
|
|
1400
|
+
const refreshed = useScopedCredential ? void 0 : loadAuthProfileStoreForSecretsRuntime(ownerAgentDir).profiles[profileId];
|
|
1401
|
+
const refreshedOAuthCredential = refreshed?.type === "oauth" && isCodexAppServerAuthProvider(refreshed.provider, params.config) ? refreshed : void 0;
|
|
1402
|
+
if (refreshedOAuthCredential && isDeepStrictEqual(params.store.profiles[profileId], credential)) params.store.profiles[profileId] = refreshedOAuthCredential;
|
|
1403
|
+
const storedCredential = store.profiles[profileId];
|
|
1404
|
+
const candidate = refreshedOAuthCredential ? refreshedOAuthCredential : storedCredential?.type === "oauth" && isCodexAppServerAuthProvider(storedCredential.provider, params.config) ? storedCredential : credential;
|
|
1405
|
+
return resolved?.apiKey ? {
|
|
1406
|
+
...candidate,
|
|
1407
|
+
access: resolved.apiKey
|
|
1408
|
+
} : candidate;
|
|
1409
|
+
}
|
|
1410
|
+
function shouldUseScopedOAuthCredential(params) {
|
|
1411
|
+
if (!params.store.runtimePersistedProfileIds?.includes(params.profileId)) return true;
|
|
1412
|
+
const persisted = params.persistedCredential;
|
|
1413
|
+
if (persisted?.type !== "oauth") return true;
|
|
1414
|
+
if (resolveProviderIdForAuth(persisted.provider, { config: params.config }) !== resolveProviderIdForAuth(params.suppliedCredential.provider, { config: params.config })) return true;
|
|
1415
|
+
return !isDeepStrictEqual(persisted, params.suppliedCredential) && !hasMatchingOAuthIdentity(persisted, params.suppliedCredential);
|
|
1416
|
+
}
|
|
1417
|
+
function hasMatchingOAuthIdentity(persisted, supplied) {
|
|
1418
|
+
const persistedAccountId = persisted.accountId?.trim();
|
|
1419
|
+
const suppliedAccountId = supplied.accountId?.trim();
|
|
1420
|
+
if (persistedAccountId && suppliedAccountId) return persistedAccountId === suppliedAccountId;
|
|
1421
|
+
const persistedEmail = persisted.email?.trim().toLowerCase();
|
|
1422
|
+
const suppliedEmail = supplied.email?.trim().toLowerCase();
|
|
1423
|
+
return Boolean(persistedEmail && suppliedEmail && persistedEmail === suppliedEmail);
|
|
1424
|
+
}
|
|
1425
|
+
async function resolveScopedOAuthCredential(params) {
|
|
1426
|
+
const existingRefresh = scopedOAuthRefreshQueues.get(params.store)?.get(params.profileId);
|
|
1427
|
+
if (existingRefresh) return await existingRefresh;
|
|
1428
|
+
if (!params.forceRefresh && hasUsableOAuthCredential(params.credential)) return params.credential;
|
|
1429
|
+
const storeRefreshes = scopedOAuthRefreshQueues.get(params.store) ?? /* @__PURE__ */ new Map();
|
|
1430
|
+
scopedOAuthRefreshQueues.set(params.store, storeRefreshes);
|
|
1431
|
+
const refresh = (async () => {
|
|
1432
|
+
const current = params.store.profiles[params.profileId];
|
|
1433
|
+
const credential = current?.type === "oauth" ? current : params.credential;
|
|
1434
|
+
if (!params.forceRefresh && hasUsableOAuthCredential(credential)) return credential;
|
|
1435
|
+
const refreshed = await refreshOAuthCredentialForRuntime({ credential });
|
|
1436
|
+
if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh.`);
|
|
1437
|
+
if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing.`);
|
|
1438
|
+
params.store.profiles[params.profileId] = refreshed;
|
|
1439
|
+
return refreshed;
|
|
1440
|
+
})();
|
|
1441
|
+
storeRefreshes.set(params.profileId, refresh);
|
|
1442
|
+
try {
|
|
1443
|
+
return await refresh;
|
|
1444
|
+
} finally {
|
|
1445
|
+
if (storeRefreshes.get(params.profileId) === refresh) storeRefreshes.delete(params.profileId);
|
|
1236
1446
|
}
|
|
1237
|
-
};
|
|
1238
|
-
function defaultServerRequestResponse(request) {
|
|
1239
|
-
if (request.method === "item/tool/call") return {
|
|
1240
|
-
contentItems: [{
|
|
1241
|
-
type: "inputText",
|
|
1242
|
-
text: "OpenClaw did not register a handler for this app-server tool call."
|
|
1243
|
-
}],
|
|
1244
|
-
success: false
|
|
1245
|
-
};
|
|
1246
|
-
if (request.method === "item/commandExecution/requestApproval" || request.method === "item/fileChange/requestApproval") return { decision: "decline" };
|
|
1247
|
-
if (request.method === "item/permissions/requestApproval") return {
|
|
1248
|
-
permissions: {},
|
|
1249
|
-
scope: "turn"
|
|
1250
|
-
};
|
|
1251
|
-
if (request.method === "item/tool/requestUserInput") return { answers: {} };
|
|
1252
|
-
if (request.method === "mcpServer/elicitation/request") return { action: "decline" };
|
|
1253
|
-
return {};
|
|
1254
1447
|
}
|
|
1255
|
-
function
|
|
1256
|
-
|
|
1448
|
+
function isCodexAppServerAuthProvider(provider, config) {
|
|
1449
|
+
const resolvedProvider = resolveProviderIdForAuth(provider, { config });
|
|
1450
|
+
return resolvedProvider === CODEX_APP_SERVER_AUTH_PROVIDER || resolvedProvider === OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER || resolvedProvider === LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER;
|
|
1257
1451
|
}
|
|
1258
|
-
function
|
|
1259
|
-
|
|
1452
|
+
function isOpenAIApiKeyBackupCredential(credential, config) {
|
|
1453
|
+
return credential.type === "api_key" && resolveProviderIdForAuth(credential.provider, { config }) === OPENAI_PROVIDER;
|
|
1454
|
+
}
|
|
1455
|
+
function isCodexAppServerAuthProfileCredential(credential, config) {
|
|
1456
|
+
return isCodexAppServerAuthProvider(credential.provider, config) || isOpenAIApiKeyBackupCredential(credential, config);
|
|
1457
|
+
}
|
|
1458
|
+
function shouldClearOpenAiApiKeyForCodexAuthProfile(params) {
|
|
1459
|
+
const profileId = params.authProfileId?.trim();
|
|
1460
|
+
return isCodexSubscriptionCredential(profileId ? params.store.profiles[profileId] : params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID], params.config);
|
|
1461
|
+
}
|
|
1462
|
+
function isCodexSubscriptionCredential(credential, config) {
|
|
1463
|
+
if (!credential || !isCodexAppServerAuthProvider(credential.provider, config)) return false;
|
|
1464
|
+
return credential.type === "oauth" || credential.type === "token";
|
|
1465
|
+
}
|
|
1466
|
+
function withClearedEnvironmentVariables(startOptions, envVars) {
|
|
1467
|
+
const clearEnv = startOptions.clearEnv ?? [];
|
|
1468
|
+
const missingEnvVars = envVars.filter((envVar) => !clearEnv.includes(envVar));
|
|
1469
|
+
if (missingEnvVars.length === 0) return startOptions;
|
|
1260
1470
|
return {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
text: `OpenClaw dynamic tool call timed out after ${CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS}ms before sending a response to Codex.`
|
|
1264
|
-
}],
|
|
1265
|
-
success: false
|
|
1471
|
+
...startOptions,
|
|
1472
|
+
clearEnv: [...clearEnv, ...missingEnvVars]
|
|
1266
1473
|
};
|
|
1267
1474
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
constructor(detectedVersion) {
|
|
1271
|
-
const detected = detectedVersion ? `detected ${detectedVersion}` : "OpenClaw could not determine the running Codex version";
|
|
1272
|
-
super(`Codex app-server ${MIN_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.`);
|
|
1273
|
-
this.name = "CodexAppServerVersionError";
|
|
1274
|
-
this.detectedVersion = detectedVersion;
|
|
1275
|
-
}
|
|
1276
|
-
};
|
|
1277
|
-
function assertSupportedCodexAppServerVersion(response) {
|
|
1278
|
-
const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
|
|
1279
|
-
if (!detectedVersion || compareCodexAppServerVersions(detectedVersion, "0.143.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
|
|
1280
|
-
return detectedVersion;
|
|
1475
|
+
function readFirstNonEmptyEnv(env, keys) {
|
|
1476
|
+
return readFirstNonEmptyEnvEntry(env, keys)?.value;
|
|
1281
1477
|
}
|
|
1282
|
-
function
|
|
1283
|
-
|
|
1478
|
+
function readFirstNonEmptyEnvEntry(env, keys) {
|
|
1479
|
+
for (const key of keys) {
|
|
1480
|
+
const value = env[key]?.trim();
|
|
1481
|
+
if (value) return {
|
|
1482
|
+
key,
|
|
1483
|
+
value
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1284
1486
|
}
|
|
1285
|
-
function
|
|
1286
|
-
const userAgent = readNonEmptyInitializeString(response.userAgent);
|
|
1287
|
-
const codexHome = readNonEmptyInitializeString(response.codexHome);
|
|
1288
|
-
const platformFamily = readNonEmptyInitializeString(response.platformFamily);
|
|
1289
|
-
const platformOs = readNonEmptyInitializeString(response.platformOs);
|
|
1487
|
+
function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
|
|
1290
1488
|
return {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
...platformOs ? { platformOs } : {}
|
|
1489
|
+
type: "chatgptAuthTokens",
|
|
1490
|
+
accessToken,
|
|
1491
|
+
chatgptAccountId: resolveChatgptAccountId(profileId, credential),
|
|
1492
|
+
chatgptPlanType: resolveChatgptPlanType(credential)
|
|
1296
1493
|
};
|
|
1297
1494
|
}
|
|
1298
|
-
function
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1495
|
+
function resolveChatgptPlanType(credential) {
|
|
1496
|
+
const record = credential;
|
|
1497
|
+
const planType = record.chatgptPlanType ?? record.planType;
|
|
1498
|
+
return typeof planType === "string" && planType.trim() ? planType.trim() : null;
|
|
1499
|
+
}
|
|
1500
|
+
function resolveChatgptAccountId(profileId, credential) {
|
|
1501
|
+
return resolveStableChatgptAccountId(credential) ?? profileId;
|
|
1502
|
+
}
|
|
1503
|
+
function resolveStableChatgptAccountId(credential) {
|
|
1504
|
+
if ("accountId" in credential && typeof credential.accountId === "string") {
|
|
1505
|
+
const accountId = credential.accountId.trim();
|
|
1506
|
+
if (accountId) return accountId;
|
|
1507
|
+
}
|
|
1508
|
+
return credential.email?.trim() || void 0;
|
|
1509
|
+
}
|
|
1510
|
+
//#endregion
|
|
1511
|
+
//#region extensions/codex/src/app-server/rate-limit-cache.ts
|
|
1512
|
+
const DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS = 10 * 6e4;
|
|
1513
|
+
const SPARSE_ACCOUNT_METADATA_KEYS = [
|
|
1514
|
+
"credits",
|
|
1515
|
+
"individualLimit",
|
|
1516
|
+
"planType"
|
|
1517
|
+
];
|
|
1518
|
+
const rateLimitsByClient = /* @__PURE__ */ new WeakMap();
|
|
1519
|
+
/** Replaces one physical client's cache with an authoritative rate-limit read response. */
|
|
1520
|
+
function rememberCodexRateLimitsRead(client, value, nowMs = Date.now()) {
|
|
1521
|
+
if (value !== void 0) {
|
|
1522
|
+
const revisionsByLimitId = { ...rateLimitsByClient.get(client)?.revisionsByLimitId };
|
|
1523
|
+
for (const limitId of readRateLimitIds(value)) revisionsByLimitId[limitId] = (revisionsByLimitId[limitId] ?? 0) + 1;
|
|
1524
|
+
rateLimitsByClient.set(client, {
|
|
1525
|
+
value,
|
|
1526
|
+
updatedAtMs: nowMs,
|
|
1527
|
+
revisionsByLimitId
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
/** Merges a sparse rolling notification into one physical client's latest read response. */
|
|
1532
|
+
function mergeCodexRateLimitsUpdate(client, value, nowMs = Date.now()) {
|
|
1533
|
+
const update = isJsonObject$1(value) && isJsonObject$1(value.rateLimits) ? value.rateLimits : void 0;
|
|
1534
|
+
if (!update) return;
|
|
1535
|
+
const currentState = rateLimitsByClient.get(client);
|
|
1536
|
+
const current = currentState?.value;
|
|
1537
|
+
const limitId = readLimitId(update);
|
|
1538
|
+
rateLimitsByClient.set(client, {
|
|
1539
|
+
value: mergeRateLimitUpdate(current, update),
|
|
1540
|
+
updatedAtMs: nowMs,
|
|
1541
|
+
revisionsByLimitId: {
|
|
1542
|
+
...currentState?.revisionsByLimitId,
|
|
1543
|
+
[limitId]: (currentState?.revisionsByLimitId[limitId] ?? 0) + 1
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
/** Per-limit marker used to trust only primary Codex updates from one turn startup. */
|
|
1548
|
+
function readCodexRateLimitsRevision(client, limitId = "codex") {
|
|
1549
|
+
return rateLimitsByClient.get(client)?.revisionsByLimitId[limitId] ?? 0;
|
|
1301
1550
|
}
|
|
1302
|
-
/**
|
|
1303
|
-
function
|
|
1304
|
-
|
|
1551
|
+
/** Reads one physical client's cached rate-limit payload within the max-age window. */
|
|
1552
|
+
function readRecentCodexRateLimits(client, options) {
|
|
1553
|
+
const state = rateLimitsByClient.get(client);
|
|
1554
|
+
if (!state) return;
|
|
1555
|
+
const nowMs = options?.nowMs ?? Date.now();
|
|
1556
|
+
const maxAgeMs = options?.maxAgeMs ?? DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS;
|
|
1557
|
+
return maxAgeMs >= 0 && nowMs - state.updatedAtMs > maxAgeMs ? void 0 : state.value;
|
|
1305
1558
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
const
|
|
1309
|
-
const
|
|
1310
|
-
const
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
const prereleaseIndex = withoutBuild.indexOf("-");
|
|
1559
|
+
function mergeRateLimitUpdate(current, update) {
|
|
1560
|
+
const currentEnvelope = isJsonObject$1(current) ? current : void 0;
|
|
1561
|
+
const currentPrimary = currentEnvelope && isJsonObject$1(currentEnvelope.rateLimits) ? currentEnvelope.rateLimits : void 0;
|
|
1562
|
+
const currentByLimitId = currentEnvelope && isJsonObject$1(currentEnvelope.rateLimitsByLimitId) ? currentEnvelope.rateLimitsByLimitId : void 0;
|
|
1563
|
+
const limitId = readLimitId(update);
|
|
1564
|
+
const currentPrimaryLimitId = currentPrimary ? readLimitId(currentPrimary) : void 0;
|
|
1565
|
+
const currentForLimit = (currentByLimitId && isJsonObject$1(currentByLimitId[limitId]) ? currentByLimitId[limitId] : void 0) ?? (currentPrimaryLimitId === limitId ? currentPrimary : void 0);
|
|
1566
|
+
const merged = mergeSparseSnapshot(isJsonObject$1(currentForLimit) ? currentForLimit : void 0, currentPrimary, update, limitId);
|
|
1567
|
+
const nextPrimary = !currentPrimary || currentPrimaryLimitId === limitId ? merged : currentPrimary;
|
|
1568
|
+
let nextByLimitId;
|
|
1569
|
+
if (currentByLimitId) nextByLimitId = {
|
|
1570
|
+
...currentByLimitId,
|
|
1571
|
+
[limitId]: merged
|
|
1572
|
+
};
|
|
1573
|
+
else if (currentPrimary && currentPrimaryLimitId && currentPrimaryLimitId !== limitId) nextByLimitId = {
|
|
1574
|
+
[currentPrimaryLimitId]: currentPrimary,
|
|
1575
|
+
[limitId]: merged
|
|
1576
|
+
};
|
|
1325
1577
|
return {
|
|
1326
|
-
|
|
1327
|
-
|
|
1578
|
+
...currentEnvelope,
|
|
1579
|
+
rateLimits: nextPrimary,
|
|
1580
|
+
...nextByLimitId ? { rateLimitsByLimitId: nextByLimitId } : {}
|
|
1328
1581
|
};
|
|
1329
1582
|
}
|
|
1330
|
-
function
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1583
|
+
function readRateLimitIds(value) {
|
|
1584
|
+
if (!isJsonObject$1(value)) return [];
|
|
1585
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1586
|
+
if (isJsonObject$1(value.rateLimits)) ids.add(readLimitId(value.rateLimits));
|
|
1587
|
+
if (isJsonObject$1(value.rateLimitsByLimitId)) for (const [key, snapshot] of Object.entries(value.rateLimitsByLimitId)) {
|
|
1588
|
+
const snapshotLimitId = isJsonObject$1(snapshot) && typeof snapshot.limitId === "string" ? snapshot.limitId.trim() : "";
|
|
1589
|
+
ids.add(snapshotLimitId || key);
|
|
1590
|
+
}
|
|
1591
|
+
return [...ids];
|
|
1337
1592
|
}
|
|
1338
|
-
function
|
|
1339
|
-
const
|
|
1340
|
-
|
|
1341
|
-
|
|
1593
|
+
function mergeSparseSnapshot(current, accountFallback, update, limitId) {
|
|
1594
|
+
const merged = {
|
|
1595
|
+
...update,
|
|
1596
|
+
limitId
|
|
1597
|
+
};
|
|
1598
|
+
for (const key of SPARSE_ACCOUNT_METADATA_KEYS) {
|
|
1599
|
+
const previous = current?.[key] ?? accountFallback?.[key];
|
|
1600
|
+
if (merged[key] == null && previous != null) merged[key] = previous;
|
|
1601
|
+
}
|
|
1602
|
+
return merged;
|
|
1342
1603
|
}
|
|
1343
|
-
function
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
return message.includes("Unterminated string") || message.includes("Unexpected end of JSON input");
|
|
1604
|
+
function readLimitId(snapshot) {
|
|
1605
|
+
const value = snapshot.limitId;
|
|
1606
|
+
return typeof value === "string" && value.trim() ? value.trim() : "codex";
|
|
1347
1607
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1608
|
+
//#endregion
|
|
1609
|
+
//#region extensions/codex/src/app-server/client-runtime.ts
|
|
1610
|
+
/** Client-scoped Codex auth and account observers. */
|
|
1611
|
+
const configuredClients = /* @__PURE__ */ new WeakMap();
|
|
1612
|
+
/** Installs one auth-refresh handler and one rate-limit observer per physical client. */
|
|
1613
|
+
function ensureCodexAppServerClientRuntime(client, context) {
|
|
1614
|
+
const existing = configuredClients.get(client);
|
|
1615
|
+
if (existing) {
|
|
1616
|
+
existing.context = context;
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
const runtime = { context };
|
|
1620
|
+
configuredClients.set(client, runtime);
|
|
1621
|
+
client.addRequestHandler(async (request) => {
|
|
1622
|
+
if (request.method !== "account/chatgptAuthTokens/refresh") return;
|
|
1623
|
+
if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
|
|
1624
|
+
return await refreshCodexAppServerAuthTokens({
|
|
1625
|
+
agentDir: runtime.context.agentDir,
|
|
1626
|
+
authProfileId: runtime.context.authProfileId,
|
|
1627
|
+
...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
|
|
1628
|
+
config: runtime.context.config
|
|
1629
|
+
});
|
|
1630
|
+
});
|
|
1631
|
+
client.addNotificationHandler((notification) => {
|
|
1632
|
+
if (notification.method === "account/rateLimits/updated") mergeCodexRateLimitsUpdate(client, notification.params);
|
|
1357
1633
|
});
|
|
1358
|
-
}
|
|
1359
|
-
const CODEX_APP_SERVER_APPROVAL_REQUEST_METHODS = /* @__PURE__ */ new Set([
|
|
1360
|
-
"item/commandExecution/requestApproval",
|
|
1361
|
-
"item/fileChange/requestApproval",
|
|
1362
|
-
"item/permissions/requestApproval"
|
|
1363
|
-
]);
|
|
1364
|
-
/** Returns true for app-server approval request methods OpenClaw can answer. */
|
|
1365
|
-
function isCodexAppServerApprovalRequest(method) {
|
|
1366
|
-
return CODEX_APP_SERVER_APPROVAL_REQUEST_METHODS.has(method);
|
|
1367
|
-
}
|
|
1368
|
-
function formatExitValue(value) {
|
|
1369
|
-
if (value === null || value === void 0) return "null";
|
|
1370
|
-
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
1371
|
-
return "unknown";
|
|
1372
1634
|
}
|
|
1373
1635
|
//#endregion
|
|
1374
1636
|
//#region extensions/codex/src/app-server/managed-binary.ts
|
|
@@ -1377,14 +1639,15 @@ function formatExitValue(value) {
|
|
|
1377
1639
|
* the Codex plugin before stdio startup.
|
|
1378
1640
|
*/
|
|
1379
1641
|
const CODEX_PLUGIN_ROOT = resolveDefaultCodexPluginRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
1380
|
-
const
|
|
1642
|
+
const MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS = ["/Applications/ChatGPT.app/Contents/Resources/codex", "/Applications/Codex.app/Contents/Resources/codex"];
|
|
1381
1643
|
/** Rewrites managed stdio start options to point at an executable Codex binary path. */
|
|
1382
1644
|
async function resolveManagedCodexAppServerStartOptions(startOptions, options = {}) {
|
|
1383
1645
|
if (startOptions.transport !== "stdio" || startOptions.commandSource !== "managed") return startOptions;
|
|
1384
1646
|
const platform = options.platform ?? process.platform;
|
|
1385
1647
|
const paths = resolveManagedCodexAppServerPaths({
|
|
1386
1648
|
platform,
|
|
1387
|
-
pluginRoot: options.pluginRoot
|
|
1649
|
+
pluginRoot: options.pluginRoot,
|
|
1650
|
+
managedCommandOrder: startOptions.managedCommandOrder
|
|
1388
1651
|
});
|
|
1389
1652
|
const pathExists = options.pathExists ?? commandPathExists;
|
|
1390
1653
|
const commandPaths = await findManagedCodexAppServerCommandPaths({
|
|
@@ -1392,7 +1655,7 @@ async function resolveManagedCodexAppServerStartOptions(startOptions, options =
|
|
|
1392
1655
|
pathExists,
|
|
1393
1656
|
platform
|
|
1394
1657
|
});
|
|
1395
|
-
const commandPath = commandPaths[0];
|
|
1658
|
+
const commandPath = expectDefined(commandPaths[0], "resolved managed Codex command path");
|
|
1396
1659
|
const managedFallbackCommandPaths = commandPaths.slice(1);
|
|
1397
1660
|
return {
|
|
1398
1661
|
...startOptions,
|
|
@@ -1401,27 +1664,93 @@ async function resolveManagedCodexAppServerStartOptions(startOptions, options =
|
|
|
1401
1664
|
...managedFallbackCommandPaths.length > 0 ? { managedFallbackCommandPaths } : {}
|
|
1402
1665
|
};
|
|
1403
1666
|
}
|
|
1667
|
+
/** Resolves the native artifact behind a successful managed launcher selection. */
|
|
1668
|
+
function resolveManagedCodexNativeCommand(command, options = {}) {
|
|
1669
|
+
const platform = options.platform ?? process.platform;
|
|
1670
|
+
if (platform === "darwin" && MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS.some((candidate) => candidate === command)) return command;
|
|
1671
|
+
const target = resolveCodexNativeTarget(platform, options.arch ?? process.arch);
|
|
1672
|
+
if (!target) return;
|
|
1673
|
+
const packageRoot = resolveManagedCodexPackageRootForCommand(command, platform);
|
|
1674
|
+
if (!packageRoot) return;
|
|
1675
|
+
const resolvePackageJson = options.resolvePackageJson ?? resolvePackageJsonFromRoot;
|
|
1676
|
+
const pathExists = options.pathExists ?? existsSync;
|
|
1677
|
+
for (const packageName of [target.packageName, MANAGED_CODEX_APP_SERVER_PACKAGE]) {
|
|
1678
|
+
const packageJsonPath = resolvePackageJson(packageName, packageRoot);
|
|
1679
|
+
if (!packageJsonPath) continue;
|
|
1680
|
+
const candidate = path.join(path.dirname(packageJsonPath), "vendor", target.triple, "bin", platform === "win32" ? "codex.exe" : "codex");
|
|
1681
|
+
if (pathExists(candidate)) return candidate;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function resolveManagedCodexPackageRootForCommand(command, platform) {
|
|
1685
|
+
const pathApi = pathForPlatform(platform);
|
|
1686
|
+
const commandPaths = [command];
|
|
1687
|
+
try {
|
|
1688
|
+
commandPaths.unshift(realpathSync(command));
|
|
1689
|
+
} catch {}
|
|
1690
|
+
for (const commandPath of commandPaths) {
|
|
1691
|
+
let current = pathApi.dirname(commandPath);
|
|
1692
|
+
while (true) {
|
|
1693
|
+
if (pathApi.basename(current) === "codex" && pathApi.basename(pathApi.dirname(current)) === "@openai") return current;
|
|
1694
|
+
if (pathApi.basename(current) === ".bin") return pathApi.join(pathApi.dirname(current), "@openai", "codex");
|
|
1695
|
+
const parent = pathApi.dirname(current);
|
|
1696
|
+
if (parent === current) break;
|
|
1697
|
+
current = parent;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
function resolveCodexNativeTarget(platform, arch) {
|
|
1702
|
+
if ((platform === "linux" || platform === "android") && arch === "x64") return {
|
|
1703
|
+
packageName: "@openai/codex-linux-x64",
|
|
1704
|
+
triple: "x86_64-unknown-linux-musl"
|
|
1705
|
+
};
|
|
1706
|
+
if ((platform === "linux" || platform === "android") && arch === "arm64") return {
|
|
1707
|
+
packageName: "@openai/codex-linux-arm64",
|
|
1708
|
+
triple: "aarch64-unknown-linux-musl"
|
|
1709
|
+
};
|
|
1710
|
+
if (platform === "darwin" && arch === "x64") return {
|
|
1711
|
+
packageName: "@openai/codex-darwin-x64",
|
|
1712
|
+
triple: "x86_64-apple-darwin"
|
|
1713
|
+
};
|
|
1714
|
+
if (platform === "darwin" && arch === "arm64") return {
|
|
1715
|
+
packageName: "@openai/codex-darwin-arm64",
|
|
1716
|
+
triple: "aarch64-apple-darwin"
|
|
1717
|
+
};
|
|
1718
|
+
if (platform === "win32" && arch === "x64") return {
|
|
1719
|
+
packageName: "@openai/codex-win32-x64",
|
|
1720
|
+
triple: "x86_64-pc-windows-msvc"
|
|
1721
|
+
};
|
|
1722
|
+
if (platform === "win32" && arch === "arm64") return {
|
|
1723
|
+
packageName: "@openai/codex-win32-arm64",
|
|
1724
|
+
triple: "aarch64-pc-windows-msvc"
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
function resolvePackageJsonFromRoot(packageName, root) {
|
|
1728
|
+
try {
|
|
1729
|
+
return createRequire(path.join(root, "package.json")).resolve(`${packageName}/package.json`);
|
|
1730
|
+
} catch {
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1404
1734
|
/** Returns the preferred and fallback managed Codex binary paths for a plugin root. */
|
|
1405
1735
|
function resolveManagedCodexAppServerPaths(params) {
|
|
1406
1736
|
const platform = params.platform ?? process.platform;
|
|
1407
|
-
const candidateCommandPaths = resolveManagedCodexAppServerCommandCandidates(params.pluginRoot ?? CODEX_PLUGIN_ROOT, platform);
|
|
1737
|
+
const candidateCommandPaths = resolveManagedCodexAppServerCommandCandidates(params.pluginRoot ?? CODEX_PLUGIN_ROOT, platform, params.managedCommandOrder ?? "package-first");
|
|
1408
1738
|
return {
|
|
1409
1739
|
commandPath: candidateCommandPaths[0] ?? "",
|
|
1410
1740
|
candidateCommandPaths
|
|
1411
1741
|
};
|
|
1412
1742
|
}
|
|
1413
|
-
function resolveManagedCodexAppServerCommandCandidates(pluginRoot, platform) {
|
|
1743
|
+
function resolveManagedCodexAppServerCommandCandidates(pluginRoot, platform, managedCommandOrder) {
|
|
1414
1744
|
const pathApi = pathForPlatform(platform);
|
|
1415
1745
|
const commandName = platform === "win32" ? "codex.cmd" : "codex";
|
|
1416
1746
|
const roots = resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform);
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
])];
|
|
1747
|
+
const packageCommandPaths = [...roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)), ...resolveManagedCodexPackageBinCandidates(roots, platform)];
|
|
1748
|
+
const desktopCommandPaths = resolveDesktopCodexAppServerCommandCandidates(platform);
|
|
1749
|
+
const orderedCommandPaths = managedCommandOrder === "desktop-first" ? [...desktopCommandPaths, ...packageCommandPaths] : [...packageCommandPaths, ...desktopCommandPaths];
|
|
1750
|
+
return [...new Set(orderedCommandPaths)];
|
|
1422
1751
|
}
|
|
1423
1752
|
function resolveDesktopCodexAppServerCommandCandidates(platform) {
|
|
1424
|
-
return platform === "darwin" ? [
|
|
1753
|
+
return platform === "darwin" ? [...MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS] : [];
|
|
1425
1754
|
}
|
|
1426
1755
|
function resolveDefaultCodexPluginRoot(moduleDir) {
|
|
1427
1756
|
const moduleBaseName = path.basename(moduleDir);
|
|
@@ -1506,6 +1835,68 @@ async function commandPathExists(filePath, platform) {
|
|
|
1506
1835
|
}
|
|
1507
1836
|
}
|
|
1508
1837
|
//#endregion
|
|
1838
|
+
//#region extensions/codex/src/app-server/native-config-fence.ts
|
|
1839
|
+
const CODEX_NATIVE_CONFIG_FENCE_STATE = Symbol.for("openclaw.codexNativeConfigFenceState");
|
|
1840
|
+
function getFenceState() {
|
|
1841
|
+
const globalState = globalThis;
|
|
1842
|
+
globalState[CODEX_NATIVE_CONFIG_FENCE_STATE] ??= /* @__PURE__ */ new Map();
|
|
1843
|
+
return globalState[CODEX_NATIVE_CONFIG_FENCE_STATE];
|
|
1844
|
+
}
|
|
1845
|
+
/** Acquires the per-CODEX_HOME fence and returns an idempotent release. */
|
|
1846
|
+
async function acquireCodexNativeConfigFence(key, options = {}) {
|
|
1847
|
+
const state = getFenceState();
|
|
1848
|
+
const previous = state.get(key) ?? Promise.resolve();
|
|
1849
|
+
let resolveCurrent = () => void 0;
|
|
1850
|
+
const current = new Promise((resolve) => {
|
|
1851
|
+
resolveCurrent = resolve;
|
|
1852
|
+
});
|
|
1853
|
+
state.set(key, current);
|
|
1854
|
+
try {
|
|
1855
|
+
await waitForPreviousFence(previous, options);
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
previous.then(() => {
|
|
1858
|
+
resolveCurrent();
|
|
1859
|
+
if (state.get(key) === current) state.delete(key);
|
|
1860
|
+
});
|
|
1861
|
+
throw error;
|
|
1862
|
+
}
|
|
1863
|
+
let released = false;
|
|
1864
|
+
return () => {
|
|
1865
|
+
if (released) return;
|
|
1866
|
+
released = true;
|
|
1867
|
+
resolveCurrent();
|
|
1868
|
+
if (state.get(key) === current) state.delete(key);
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
async function waitForPreviousFence(previous, options) {
|
|
1872
|
+
if (options.signal?.aborted) throw new Error(options.abortMessage ?? "Codex native config fence aborted");
|
|
1873
|
+
if (options.timeoutMs === void 0 && !options.signal) {
|
|
1874
|
+
await previous;
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
await new Promise((resolve, reject) => {
|
|
1878
|
+
let timeout;
|
|
1879
|
+
const cleanup = () => {
|
|
1880
|
+
if (timeout) {
|
|
1881
|
+
clearTimeout(timeout);
|
|
1882
|
+
timeout = void 0;
|
|
1883
|
+
}
|
|
1884
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
1885
|
+
};
|
|
1886
|
+
const settle = (run) => {
|
|
1887
|
+
cleanup();
|
|
1888
|
+
run();
|
|
1889
|
+
};
|
|
1890
|
+
const onAbort = () => settle(() => reject(new Error(options.abortMessage ?? "Codex native config fence aborted")));
|
|
1891
|
+
previous.then(() => settle(resolve));
|
|
1892
|
+
if (options.signal) options.signal.addEventListener("abort", onAbort, { once: true });
|
|
1893
|
+
if (options.timeoutMs !== void 0) {
|
|
1894
|
+
timeout = setTimeout(() => settle(() => reject(new Error(options.timeoutMessage ?? "Codex native config fence timed out"))), Math.max(1, options.timeoutMs));
|
|
1895
|
+
timeout.unref?.();
|
|
1896
|
+
}
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
//#endregion
|
|
1509
1900
|
//#region extensions/codex/src/app-server/timeout.ts
|
|
1510
1901
|
/**
|
|
1511
1902
|
* Thin Codex app-server timeout adapter around OpenClaw's shared security
|
|
@@ -1522,16 +1913,27 @@ async function withTimeout$1(promise, timeoutMs, timeoutMessage) {
|
|
|
1522
1913
|
* lease tracking, and teardown.
|
|
1523
1914
|
*/
|
|
1524
1915
|
var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
1916
|
+
CodexAppServerStartSelectionChangedError: () => CodexAppServerStartSelectionChangedError,
|
|
1917
|
+
assertCodexAppServerClientStartSelectionCurrent: () => assertCodexAppServerClientStartSelectionCurrent,
|
|
1525
1918
|
clearSharedCodexAppServerClientAndWait: () => clearSharedCodexAppServerClientAndWait,
|
|
1526
1919
|
clearSharedCodexAppServerClientIfCurrent: () => clearSharedCodexAppServerClientIfCurrent,
|
|
1527
1920
|
clearSharedCodexAppServerClientIfCurrentAndUnclaimed: () => clearSharedCodexAppServerClientIfCurrentAndUnclaimed,
|
|
1528
1921
|
clearSharedCodexAppServerClientIfCurrentAndWait: () => clearSharedCodexAppServerClientIfCurrentAndWait,
|
|
1529
1922
|
createIsolatedCodexAppServerClient: () => createIsolatedCodexAppServerClient,
|
|
1530
1923
|
getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
|
|
1924
|
+
isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
|
|
1925
|
+
releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
|
|
1531
1926
|
releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
|
|
1532
|
-
|
|
1927
|
+
resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
|
|
1928
|
+
resolveCodexNativeConfigFenceKey: () => resolveCodexNativeConfigFenceKey,
|
|
1929
|
+
retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
|
|
1930
|
+
retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
|
|
1931
|
+
withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
|
|
1533
1932
|
});
|
|
1933
|
+
const suspectClosedClients = /* @__PURE__ */ new WeakSet();
|
|
1534
1934
|
const SHARED_CODEX_APP_SERVER_CLIENT_STATE = Symbol.for("openclaw.codexAppServerClientState");
|
|
1935
|
+
const SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER = Symbol.for("openclaw.codexAppServerClientDisposer");
|
|
1936
|
+
const CODEX_APP_SERVER_CLIENT_START_METADATA = Symbol.for("openclaw.codexAppServerClientStartMetadata");
|
|
1535
1937
|
function getSharedCodexAppServerClientState() {
|
|
1536
1938
|
const globalState = globalThis;
|
|
1537
1939
|
globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
|
|
@@ -1540,33 +1942,113 @@ function getSharedCodexAppServerClientState() {
|
|
|
1540
1942
|
};
|
|
1541
1943
|
return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
|
|
1542
1944
|
}
|
|
1945
|
+
function getCodexAppServerClientStartMetadata() {
|
|
1946
|
+
const globalState = globalThis;
|
|
1947
|
+
globalState[CODEX_APP_SERVER_CLIENT_START_METADATA] ??= /* @__PURE__ */ new WeakMap();
|
|
1948
|
+
return globalState[CODEX_APP_SERVER_CLIENT_START_METADATA];
|
|
1949
|
+
}
|
|
1950
|
+
/** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
|
|
1951
|
+
function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
|
|
1952
|
+
const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
|
|
1953
|
+
return {
|
|
1954
|
+
command: startOptions.command,
|
|
1955
|
+
argsFingerprint: createHash("sha256").update(JSON.stringify(startOptions.args)).digest("hex"),
|
|
1956
|
+
...startOptions.commandSource ? { commandSource: startOptions.commandSource } : {},
|
|
1957
|
+
...startOptions.managedCommandOrder ? { managedCommandOrder: startOptions.managedCommandOrder } : {},
|
|
1958
|
+
...nativeCommand ? { nativeCommand } : {}
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
var CodexAppServerStartSelectionChangedError = class extends Error {
|
|
1962
|
+
constructor() {
|
|
1963
|
+
super("Codex app-server managed executable selection changed during startup");
|
|
1964
|
+
this.code = "CODEX_APP_SERVER_START_SELECTION_CHANGED";
|
|
1965
|
+
this.name = "CodexAppServerStartSelectionChangedError";
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
/** Cross-bundle-safe check for a managed executable selection retry. */
|
|
1969
|
+
function isCodexAppServerStartSelectionChangedError(error) {
|
|
1970
|
+
return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_START_SELECTION_CHANGED";
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Rechecks mutable Codex-owned plugin state immediately before thread start/resume.
|
|
1974
|
+
* The synchronous check prevents another gateway task from installing Computer
|
|
1975
|
+
* Use between the check and the JSON-RPC write on the same event loop turn.
|
|
1976
|
+
*/
|
|
1977
|
+
function assertCodexAppServerClientStartSelectionCurrent(params) {
|
|
1978
|
+
const metadata = getCodexAppServerClientStartMetadata().get(params.client);
|
|
1979
|
+
if (!metadata) return;
|
|
1980
|
+
const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
|
|
1981
|
+
if (requestedStartOptions.commandSource !== "managed") return;
|
|
1982
|
+
const current = resolveCodexAppServerStartOptionsForAgent({
|
|
1983
|
+
startOptions: requestedStartOptions,
|
|
1984
|
+
agentDir: params.agentDir ?? metadata.agentDir
|
|
1985
|
+
});
|
|
1986
|
+
if ((metadata.startOptions.managedCommandOrder ?? "package-first") !== (current.managedCommandOrder ?? "package-first")) throw new CodexAppServerStartSelectionChangedError();
|
|
1987
|
+
}
|
|
1988
|
+
/** Resolves the per-CODEX_HOME key used to serialize native config loading. */
|
|
1989
|
+
function resolveCodexNativeConfigFenceKey(params) {
|
|
1990
|
+
const metadata = params.client ? getCodexAppServerClientStartMetadata().get(params.client) : void 0;
|
|
1991
|
+
const startOptions = params.startOptions ?? metadata?.startOptions;
|
|
1992
|
+
if (!startOptions || startOptions.transport !== "stdio") return;
|
|
1993
|
+
const configuredHome = startOptions.env?.CODEX_HOME?.trim();
|
|
1994
|
+
const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
|
|
1995
|
+
const codexHome = configuredHome ? configuredHome : startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir() : agentDir ? resolveCodexAppServerHomeDir(agentDir) : void 0;
|
|
1996
|
+
return codexHome ? `codex-home:${path.resolve(codexHome)}` : void 0;
|
|
1997
|
+
}
|
|
1543
1998
|
async function resolveCodexAppServerClientStartContext(options) {
|
|
1544
1999
|
const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
|
|
1545
2000
|
const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
|
|
1546
|
-
const
|
|
1547
|
-
const
|
|
1548
|
-
|
|
2001
|
+
const preparedAuth = options?.preparedAuth;
|
|
2002
|
+
const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
|
|
2003
|
+
if (preparedAuth && options?.authProfileId !== void 0) throw new Error("Prepared Codex auth cannot also select a legacy auth profile.");
|
|
2004
|
+
if (preparedAuth?.kind === "profile" && !preparedAuth.store.profiles[preparedAuth.profileId]) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" was not found.`);
|
|
2005
|
+
if (preparedAuth?.kind === "api-key" && !preparedApiKey) throw new Error("Prepared Codex API-key auth is missing its resolved key.");
|
|
2006
|
+
if (preparedAuth && requestedStartOptions.homeScope === "user") throw new Error("Prepared Codex auth requires an isolated app-server home.");
|
|
2007
|
+
const usesNativeAuth = !preparedAuth && (options?.authProfileId === null || requestedStartOptions.homeScope === "user");
|
|
2008
|
+
const requestedAuthProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : options?.authProfileId ?? void 0;
|
|
2009
|
+
const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && options?.authProfileStore ? resolveCodexAppServerAuthProfileStore({
|
|
1549
2010
|
agentDir,
|
|
1550
2011
|
authProfileId: requestedAuthProfileId,
|
|
1551
2012
|
authProfileStore: options.authProfileStore,
|
|
1552
2013
|
config: options.config
|
|
1553
2014
|
}) : options?.authProfileStore;
|
|
1554
|
-
const authProfileId = usesNativeAuth ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
|
|
2015
|
+
const authProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : usesNativeAuth || preparedAuth?.kind === "api-key" ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
|
|
1555
2016
|
authProfileId: requestedAuthProfileId,
|
|
1556
2017
|
agentDir,
|
|
1557
2018
|
config: options?.config,
|
|
1558
2019
|
...authProfileStore ? { authProfileStore } : {}
|
|
1559
2020
|
});
|
|
2021
|
+
const preparedAuthProfileSnapshot = preparedAuth?.kind === "profile" ? preparedAuth.snapshot ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
|
|
2022
|
+
authProfileId,
|
|
2023
|
+
authProfileStore,
|
|
2024
|
+
agentDir,
|
|
2025
|
+
config: options?.config
|
|
2026
|
+
}) : void 0;
|
|
2027
|
+
if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable.`);
|
|
2028
|
+
const resolvedPreparedAuth = preparedAuth?.kind === "api-key" ? {
|
|
2029
|
+
kind: "api-key",
|
|
2030
|
+
apiKey: preparedApiKey
|
|
2031
|
+
} : preparedAuth?.kind === "profile" ? {
|
|
2032
|
+
...preparedAuth,
|
|
2033
|
+
snapshot: preparedAuthProfileSnapshot
|
|
2034
|
+
} : void 0;
|
|
1560
2035
|
return {
|
|
1561
2036
|
agentDir,
|
|
1562
2037
|
usesNativeAuth,
|
|
1563
2038
|
authProfileId,
|
|
1564
2039
|
authProfileStore,
|
|
2040
|
+
requestedStartOptions,
|
|
2041
|
+
preparedAuth: resolvedPreparedAuth,
|
|
1565
2042
|
startOptions: await bridgeCodexAppServerStartOptions({
|
|
1566
|
-
startOptions: await resolveManagedCodexAppServerStartOptions(
|
|
2043
|
+
startOptions: await resolveManagedCodexAppServerStartOptions(resolveCodexAppServerStartOptionsForAgent({
|
|
2044
|
+
startOptions: requestedStartOptions,
|
|
2045
|
+
agentDir
|
|
2046
|
+
})),
|
|
1567
2047
|
agentDir,
|
|
1568
|
-
authProfileId: usesNativeAuth ? null : authProfileId,
|
|
2048
|
+
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
2049
|
+
...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
|
|
1569
2050
|
config: options?.config,
|
|
2051
|
+
pluginConfig: options?.pluginConfig,
|
|
1570
2052
|
...authProfileStore ? { authProfileStore } : {}
|
|
1571
2053
|
})
|
|
1572
2054
|
};
|
|
@@ -1591,47 +2073,111 @@ function releaseLeasedSharedCodexAppServerClient(client) {
|
|
|
1591
2073
|
release();
|
|
1592
2074
|
return true;
|
|
1593
2075
|
}
|
|
2076
|
+
/** Releases the currently owned client exactly once. */
|
|
2077
|
+
function releaseCodexAppServerClientLease(lease) {
|
|
2078
|
+
const client = lease.client;
|
|
2079
|
+
lease.client = void 0;
|
|
2080
|
+
return client ? releaseLeasedSharedCodexAppServerClient(client) : false;
|
|
2081
|
+
}
|
|
2082
|
+
/** Retries one config-loading request after moving its lease to the current owner. */
|
|
2083
|
+
async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
|
|
2084
|
+
let client = params.lease.client;
|
|
2085
|
+
if (!client) throw new Error("Codex app-server selection retry requires an active client lease");
|
|
2086
|
+
const timeoutMs = params.options?.timeoutMs ?? 6e4;
|
|
2087
|
+
const deadline = Date.now() + timeoutMs;
|
|
2088
|
+
const signal = params.signal ?? params.options?.abandonSignal;
|
|
2089
|
+
const requestOptions = () => {
|
|
2090
|
+
if (signal?.aborted) throw new Error("Codex app-server selection retry aborted");
|
|
2091
|
+
const remainingTimeoutMs = deadline - Date.now();
|
|
2092
|
+
if (remainingTimeoutMs <= 0) throw new Error("Codex app-server selection retry timed out");
|
|
2093
|
+
return {
|
|
2094
|
+
timeoutMs: remainingTimeoutMs,
|
|
2095
|
+
...signal ? { signal } : {}
|
|
2096
|
+
};
|
|
2097
|
+
};
|
|
2098
|
+
for (let attempt = 0; attempt < 2; attempt += 1) try {
|
|
2099
|
+
return await params.run(client, requestOptions());
|
|
2100
|
+
} catch (error) {
|
|
2101
|
+
if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
|
|
2102
|
+
retireSharedCodexAppServerClientIfCurrent(client);
|
|
2103
|
+
params.lease.client = void 0;
|
|
2104
|
+
if (!releaseLeasedSharedCodexAppServerClient(client)) {
|
|
2105
|
+
client.close();
|
|
2106
|
+
throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
|
|
2107
|
+
}
|
|
2108
|
+
const replacementOptions = requestOptions();
|
|
2109
|
+
client = await getLeasedSharedCodexAppServerClient({
|
|
2110
|
+
...params.options,
|
|
2111
|
+
timeoutMs: replacementOptions.timeoutMs,
|
|
2112
|
+
...signal ? { abandonSignal: signal } : {}
|
|
2113
|
+
});
|
|
2114
|
+
params.lease.client = client;
|
|
2115
|
+
params.onClientChange(client);
|
|
2116
|
+
}
|
|
2117
|
+
throw new Error("Codex app-server selection retry loop exited unexpectedly");
|
|
2118
|
+
}
|
|
1594
2119
|
async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
1595
|
-
|
|
1596
|
-
const
|
|
1597
|
-
const
|
|
2120
|
+
if (options?.abandonSignal?.aborted) throw new Error("codex app-server initialize aborted");
|
|
2121
|
+
const acquireStartedAt = Date.now();
|
|
2122
|
+
const timeoutMs = options?.timeoutMs ?? 0;
|
|
2123
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
2124
|
+
const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
|
|
2125
|
+
const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authProfileId ? void 0 : resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }));
|
|
2126
|
+
const baseKey = codexAppServerStartOptionsKey(startOptions, {
|
|
1598
2127
|
authProfileId,
|
|
2128
|
+
authBindingFingerprint: options?.authBindingFingerprint,
|
|
1599
2129
|
agentDir: usesNativeAuth ? void 0 : agentDir,
|
|
1600
|
-
fallbackApiKeyCacheKey
|
|
2130
|
+
fallbackApiKeyCacheKey: authIdentityCacheKey
|
|
1601
2131
|
});
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
2132
|
+
const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
|
|
2133
|
+
const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
|
|
2134
|
+
const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
|
|
2135
|
+
const entry = getOrCreateSharedClientEntry(getSharedCodexAppServerClientState(), key);
|
|
2136
|
+
if (runtimeArtifactMode) entry.runtimeArtifactStartupAbort ??= new AbortController();
|
|
2137
|
+
entry.closeWhenIdle = false;
|
|
1604
2138
|
const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
|
|
2139
|
+
const startedCallback = options?.onStartedClient;
|
|
2140
|
+
if (startedCallback) {
|
|
2141
|
+
entry.onStartedClientCallbacks.add(startedCallback);
|
|
2142
|
+
if (entry.client) startedCallback(entry.client);
|
|
2143
|
+
}
|
|
2144
|
+
const stopStartedClientNotifications = () => {
|
|
2145
|
+
if (startedCallback) entry.onStartedClientCallbacks.delete(startedCallback);
|
|
2146
|
+
};
|
|
1605
2147
|
let cleanupAbandonSignal;
|
|
1606
2148
|
if (options?.abandonSignal) {
|
|
1607
2149
|
const abandon = () => {
|
|
2150
|
+
stopStartedClientNotifications();
|
|
1608
2151
|
releasePendingAcquire();
|
|
1609
|
-
|
|
2152
|
+
retirePendingSharedClientEntryIfUnclaimed(key, entry);
|
|
1610
2153
|
};
|
|
1611
2154
|
options.abandonSignal.addEventListener("abort", abandon, { once: true });
|
|
1612
2155
|
cleanupAbandonSignal = () => options.abandonSignal?.removeEventListener("abort", abandon);
|
|
1613
2156
|
if (options.abandonSignal.aborted) abandon();
|
|
1614
2157
|
}
|
|
1615
|
-
const
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1626
|
-
entry.
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
})());
|
|
2158
|
+
const startup = entry.startup ?? (entry.startup = createSharedCodexAppServerClientStartup({
|
|
2159
|
+
entry,
|
|
2160
|
+
key,
|
|
2161
|
+
requestedStartOptions,
|
|
2162
|
+
startOptions,
|
|
2163
|
+
agentDir,
|
|
2164
|
+
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
2165
|
+
authProfileStore,
|
|
2166
|
+
preparedAuth,
|
|
2167
|
+
runtimeArtifactMode,
|
|
2168
|
+
...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
|
|
2169
|
+
runtimeArtifactSignal: entry.runtimeArtifactStartupAbort?.signal,
|
|
2170
|
+
config: options?.config
|
|
2171
|
+
}));
|
|
1630
2172
|
try {
|
|
1631
|
-
|
|
2173
|
+
await withCodexAppServerAcquireDeadline(remainingTimeoutMs, startup.initialized, options?.abandonSignal);
|
|
2174
|
+
const client = await withCodexAppServerAcquireDeadline(timeoutMs, startup.ready, options?.abandonSignal, "codex app-server authentication timed out");
|
|
2175
|
+
if (entry.closeError) throw entry.closeError;
|
|
1632
2176
|
ensureCodexAppServerClientRuntime(client, {
|
|
1633
2177
|
agentDir,
|
|
1634
2178
|
authProfileId: usesNativeAuth ? void 0 : authProfileId,
|
|
2179
|
+
...authProfileStore ? { authProfileStore } : {},
|
|
2180
|
+
authMode: preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
|
|
1635
2181
|
config: options?.config
|
|
1636
2182
|
});
|
|
1637
2183
|
const release = leaseOptions?.leased ? retainSharedClientEntry(entry) : void 0;
|
|
@@ -1640,56 +2186,166 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
1640
2186
|
release
|
|
1641
2187
|
} : { client };
|
|
1642
2188
|
} catch (error) {
|
|
1643
|
-
|
|
1644
|
-
|
|
2189
|
+
releasePendingAcquire();
|
|
2190
|
+
retirePendingSharedClientEntryIfUnclaimed(key, entry);
|
|
1645
2191
|
throw error;
|
|
1646
2192
|
} finally {
|
|
1647
2193
|
cleanupAbandonSignal?.();
|
|
2194
|
+
stopStartedClientNotifications();
|
|
1648
2195
|
releasePendingAcquire();
|
|
1649
2196
|
}
|
|
1650
2197
|
}
|
|
2198
|
+
async function withCodexAppServerAcquireDeadline(timeoutMs, promise, signal, timeoutMessage = "codex app-server initialize timed out") {
|
|
2199
|
+
if (signal?.aborted) throw new Error("codex app-server initialize aborted");
|
|
2200
|
+
const timed = withTimeout$1(promise, timeoutMs, timeoutMessage);
|
|
2201
|
+
if (!signal) return await timed;
|
|
2202
|
+
return await new Promise((resolve, reject) => {
|
|
2203
|
+
const onAbort = () => reject(/* @__PURE__ */ new Error("codex app-server initialize aborted"));
|
|
2204
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2205
|
+
timed.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
function resolveRemainingAcquireTimeout(timeoutMs, startedAt) {
|
|
2209
|
+
if (!(timeoutMs > 0)) return timeoutMs;
|
|
2210
|
+
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
2211
|
+
if (remaining <= 0) throw new Error("codex app-server initialize timed out");
|
|
2212
|
+
return remaining;
|
|
2213
|
+
}
|
|
2214
|
+
function createSharedCodexAppServerClientStartup(params) {
|
|
2215
|
+
const initialized = createDeferred();
|
|
2216
|
+
const ready = startInitializedCodexAppServerClient({
|
|
2217
|
+
requestedStartOptions: params.requestedStartOptions,
|
|
2218
|
+
startOptions: params.startOptions,
|
|
2219
|
+
agentDir: params.agentDir,
|
|
2220
|
+
authProfileId: params.authProfileId,
|
|
2221
|
+
authProfileStore: params.authProfileStore,
|
|
2222
|
+
preparedAuth: params.preparedAuth,
|
|
2223
|
+
runtimeArtifactMode: params.runtimeArtifactMode,
|
|
2224
|
+
...params.expectedRuntimeArtifact ? { expectedRuntimeArtifact: params.expectedRuntimeArtifact } : {},
|
|
2225
|
+
runtimeArtifactSignal: params.runtimeArtifactSignal,
|
|
2226
|
+
config: params.config,
|
|
2227
|
+
onStartedClient: (startedClient) => {
|
|
2228
|
+
params.entry.client = startedClient;
|
|
2229
|
+
for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
|
|
2230
|
+
retirePendingSharedClientEntryIfUnclaimed(params.key, params.entry);
|
|
2231
|
+
},
|
|
2232
|
+
onInitializedClient: () => initialized.resolve()
|
|
2233
|
+
}).then((client) => {
|
|
2234
|
+
params.entry.client = client;
|
|
2235
|
+
client.addCloseHandler((closedClient) => clearSharedClientEntryIfCurrent(params.key, closedClient));
|
|
2236
|
+
return client;
|
|
2237
|
+
}, (error) => {
|
|
2238
|
+
initialized.reject(error);
|
|
2239
|
+
throw error;
|
|
2240
|
+
});
|
|
2241
|
+
ready.catch(() => void 0);
|
|
2242
|
+
return {
|
|
2243
|
+
initialized: initialized.promise,
|
|
2244
|
+
ready
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
1651
2247
|
/** Starts a non-shared Codex app-server client owned entirely by the caller. */
|
|
1652
2248
|
async function createIsolatedCodexAppServerClient(options) {
|
|
1653
|
-
|
|
2249
|
+
if (options?.abandonSignal?.aborted) throw new Error("codex app-server initialize aborted");
|
|
2250
|
+
const acquireStartedAt = Date.now();
|
|
2251
|
+
const timeoutMs = options?.timeoutMs ?? 0;
|
|
2252
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
1654
2253
|
return await startInitializedCodexAppServerClient({
|
|
2254
|
+
requestedStartOptions,
|
|
1655
2255
|
startOptions,
|
|
1656
2256
|
agentDir,
|
|
1657
|
-
authProfileId: usesNativeAuth ? null : authProfileId,
|
|
2257
|
+
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
1658
2258
|
authProfileStore,
|
|
2259
|
+
preparedAuth,
|
|
2260
|
+
runtimeArtifactMode: options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0),
|
|
2261
|
+
...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
|
|
2262
|
+
runtimeArtifactSignal: options?.abandonSignal,
|
|
1659
2263
|
config: options?.config,
|
|
1660
|
-
timeoutMs:
|
|
2264
|
+
timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
|
|
2265
|
+
abandonSignal: options?.abandonSignal,
|
|
1661
2266
|
onStartedClient: options?.onStartedClient
|
|
1662
2267
|
});
|
|
1663
2268
|
}
|
|
1664
2269
|
async function startInitializedCodexAppServerClient(params) {
|
|
2270
|
+
const acquireStartedAt = Date.now();
|
|
2271
|
+
const timeoutMs = params.timeoutMs ?? 0;
|
|
1665
2272
|
const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
|
|
1666
|
-
for (
|
|
1667
|
-
const
|
|
2273
|
+
for (const [index, startOptions] of startOptionsCandidates.entries()) {
|
|
2274
|
+
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-C2ITXdhM.js") : void 0;
|
|
2275
|
+
const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
2276
|
+
const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
|
|
2277
|
+
startOptions,
|
|
2278
|
+
spawnIdentity: resolveCodexAppServerSpawnIdentity(startOptions, nativeCommandBeforeStart),
|
|
2279
|
+
signal: params.runtimeArtifactSignal
|
|
2280
|
+
}) : void 0;
|
|
2281
|
+
if (runtimeArtifactModule && runtimeArtifactBeforeStart && params.expectedRuntimeArtifact && !runtimeArtifactModule.validateCodexAppServerRuntimeArtifactCapture(params.expectedRuntimeArtifact, runtimeArtifactBeforeStart)) {
|
|
2282
|
+
if (index + 1 < startOptionsCandidates.length) continue;
|
|
2283
|
+
throw new Error("Codex app-server runtime artifact does not match verified inference");
|
|
2284
|
+
}
|
|
1668
2285
|
const client = CodexAppServerClient.start(startOptions);
|
|
1669
2286
|
params.onStartedClient?.(client);
|
|
1670
|
-
|
|
2287
|
+
let initialize;
|
|
1671
2288
|
try {
|
|
1672
|
-
await
|
|
2289
|
+
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), initialize = client.initialize(), params.abandonSignal);
|
|
1673
2290
|
} catch (error) {
|
|
1674
2291
|
client.close();
|
|
1675
|
-
initialize
|
|
2292
|
+
initialize?.catch(() => void 0);
|
|
1676
2293
|
if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
|
|
1677
2294
|
throw error;
|
|
1678
2295
|
}
|
|
2296
|
+
params.onInitializedClient?.();
|
|
2297
|
+
let runtimeArtifact;
|
|
2298
|
+
try {
|
|
2299
|
+
if (runtimeArtifactModule && runtimeArtifactBeforeStart) {
|
|
2300
|
+
const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
2301
|
+
runtimeArtifact = await runtimeArtifactModule.finalizeCodexAppServerRuntimeArtifact({
|
|
2302
|
+
before: runtimeArtifactBeforeStart,
|
|
2303
|
+
startOptions,
|
|
2304
|
+
spawnIdentity: resolveCodexAppServerSpawnIdentity(startOptions, nativeCommand),
|
|
2305
|
+
runtimeIdentity: client.getRuntimeIdentity(),
|
|
2306
|
+
signal: params.runtimeArtifactSignal
|
|
2307
|
+
});
|
|
2308
|
+
if (params.expectedRuntimeArtifact && (runtimeArtifact.id !== params.expectedRuntimeArtifact.id || runtimeArtifact.fingerprint !== params.expectedRuntimeArtifact.fingerprint)) throw new Error("Codex app-server runtime artifact does not match verified inference");
|
|
2309
|
+
}
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
client.close();
|
|
2312
|
+
throw error;
|
|
2313
|
+
}
|
|
1679
2314
|
ensureCodexAppServerClientRuntime(client, {
|
|
1680
2315
|
agentDir: params.agentDir,
|
|
1681
2316
|
authProfileId: params.authProfileId ?? void 0,
|
|
2317
|
+
authMode: params.preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
|
|
1682
2318
|
...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {},
|
|
1683
2319
|
config: params.config
|
|
1684
2320
|
});
|
|
1685
2321
|
try {
|
|
1686
|
-
await applyCodexAppServerAuthProfile({
|
|
2322
|
+
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), applyCodexAppServerAuthProfile({
|
|
1687
2323
|
client,
|
|
1688
2324
|
agentDir: params.agentDir,
|
|
1689
2325
|
authProfileId: params.authProfileId,
|
|
2326
|
+
preparedAuth: params.preparedAuth,
|
|
1690
2327
|
startOptions,
|
|
1691
2328
|
config: params.config,
|
|
1692
2329
|
...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
|
|
2330
|
+
}), params.abandonSignal);
|
|
2331
|
+
const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
2332
|
+
if (runtimeArtifactModule && runtimeArtifact) runtimeArtifactModule.bindCodexAppServerRuntimeArtifact(client, runtimeArtifact);
|
|
2333
|
+
getCodexAppServerClientStartMetadata().set(client, {
|
|
2334
|
+
requestedStartOptions: params.requestedStartOptions,
|
|
2335
|
+
startOptions,
|
|
2336
|
+
agentDir: params.agentDir,
|
|
2337
|
+
...nativeCommand ? { nativeCommand } : {}
|
|
2338
|
+
});
|
|
2339
|
+
const fenceKey = resolveCodexNativeConfigFenceKey({ client });
|
|
2340
|
+
if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
|
|
2341
|
+
const release = await acquireCodexNativeConfigFence(fenceKey, options);
|
|
2342
|
+
try {
|
|
2343
|
+
assertCodexAppServerClientStartSelectionCurrent({ client });
|
|
2344
|
+
return release;
|
|
2345
|
+
} catch (error) {
|
|
2346
|
+
release();
|
|
2347
|
+
throw error;
|
|
2348
|
+
}
|
|
1693
2349
|
});
|
|
1694
2350
|
return client;
|
|
1695
2351
|
} catch (error) {
|
|
@@ -1702,8 +2358,7 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
1702
2358
|
function resolveManagedFallbackStartOptions(startOptions) {
|
|
1703
2359
|
const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
|
|
1704
2360
|
const candidates = [];
|
|
1705
|
-
for (
|
|
1706
|
-
const command = commands[index];
|
|
2361
|
+
for (const [index, command] of commands.entries()) {
|
|
1707
2362
|
const managedFallbackCommandPaths = commands.slice(index + 1);
|
|
1708
2363
|
const candidate = {
|
|
1709
2364
|
...startOptions,
|
|
@@ -1729,13 +2384,36 @@ function clearSharedCodexAppServerClientIfCurrent(client) {
|
|
|
1729
2384
|
}
|
|
1730
2385
|
return false;
|
|
1731
2386
|
}
|
|
1732
|
-
/**
|
|
1733
|
-
function
|
|
2387
|
+
/** Retains the matching shared client and returns a release callback. */
|
|
2388
|
+
function retainSharedCodexAppServerClientIfCurrent(client) {
|
|
2389
|
+
if (!client) return;
|
|
2390
|
+
const state = getSharedCodexAppServerClientState();
|
|
2391
|
+
for (const entry of state.clients.values()) if (entry.client === client) return retainSharedClientEntry(entry);
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Retires a matching shared client. Default is graceful: detach from the map
|
|
2395
|
+
* (future acquisitions get a fresh client) and close once leases drain.
|
|
2396
|
+
* `failActiveLeases` is for suspect clients only (timed-out turns): it closes
|
|
2397
|
+
* the physical connection immediately so co-leased attempts hit the normal
|
|
2398
|
+
* client-closed retry path, and pending acquires reject instead of leasing
|
|
2399
|
+
* the poisoned process. Routine cleanup must NOT use it — it would abort
|
|
2400
|
+
* healthy sibling turns on a working client.
|
|
2401
|
+
*/
|
|
2402
|
+
function retireSharedCodexAppServerClientIfCurrent(client, opts) {
|
|
1734
2403
|
if (!client) return;
|
|
1735
2404
|
const state = getSharedCodexAppServerClientState();
|
|
1736
2405
|
for (const [key, entry] of state.clients) if (entry.client === client) {
|
|
1737
2406
|
state.clients.delete(key);
|
|
1738
2407
|
entry.closeWhenIdle = true;
|
|
2408
|
+
if (opts?.failActiveLeases) {
|
|
2409
|
+
entry.closeError = /* @__PURE__ */ new Error("codex app-server client is closed");
|
|
2410
|
+
const closed = closeRetiredSharedClientEntry(entry);
|
|
2411
|
+
if (closed) suspectClosedClients.add(client);
|
|
2412
|
+
return {
|
|
2413
|
+
activeLeases: entry.activeLeases,
|
|
2414
|
+
closed
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
1739
2417
|
const closed = closeRetiredSharedClientEntryIfIdle(entry);
|
|
1740
2418
|
return {
|
|
1741
2419
|
activeLeases: entry.activeLeases,
|
|
@@ -1743,10 +2421,20 @@ function retireSharedCodexAppServerClientIfCurrent(client) {
|
|
|
1743
2421
|
};
|
|
1744
2422
|
}
|
|
1745
2423
|
const activeLeases = state.leasedReleases.get(client)?.length ?? 0;
|
|
1746
|
-
if (activeLeases > 0)
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2424
|
+
if (activeLeases > 0) {
|
|
2425
|
+
if (opts?.failActiveLeases && !suspectClosedClients.has(client)) {
|
|
2426
|
+
suspectClosedClients.add(client);
|
|
2427
|
+
client.close();
|
|
2428
|
+
return {
|
|
2429
|
+
activeLeases,
|
|
2430
|
+
closed: true
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
return {
|
|
2434
|
+
activeLeases,
|
|
2435
|
+
closed: false
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
1750
2438
|
}
|
|
1751
2439
|
/** Clears a matching shared client and waits for its process to exit. */
|
|
1752
2440
|
async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
|
|
@@ -1766,24 +2454,20 @@ async function clearSharedCodexAppServerClientAndWait(options) {
|
|
|
1766
2454
|
state.clients.clear();
|
|
1767
2455
|
await Promise.all(clients.map((client) => client.closeAndWait(options)));
|
|
1768
2456
|
}
|
|
2457
|
+
globalThis[SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER] = clearSharedCodexAppServerClientAndWait;
|
|
1769
2458
|
function getOrCreateSharedClientEntry(state, key) {
|
|
1770
2459
|
let entry = state.clients.get(key);
|
|
1771
2460
|
if (!entry) {
|
|
1772
2461
|
entry = {
|
|
1773
2462
|
activeLeases: 0,
|
|
1774
2463
|
pendingAcquires: 0,
|
|
1775
|
-
closeWhenIdle: false
|
|
2464
|
+
closeWhenIdle: false,
|
|
2465
|
+
onStartedClientCallbacks: /* @__PURE__ */ new Set()
|
|
1776
2466
|
};
|
|
1777
2467
|
state.clients.set(key, entry);
|
|
1778
2468
|
}
|
|
1779
2469
|
return entry;
|
|
1780
2470
|
}
|
|
1781
|
-
function clearSharedClientEntry(key, entry) {
|
|
1782
|
-
const state = getSharedCodexAppServerClientState();
|
|
1783
|
-
if (state.clients.get(key) !== entry) return;
|
|
1784
|
-
state.clients.delete(key);
|
|
1785
|
-
entry.client?.close();
|
|
1786
|
-
}
|
|
1787
2471
|
function clearSharedClientEntryIfCurrent(key, client) {
|
|
1788
2472
|
const state = getSharedCodexAppServerClientState();
|
|
1789
2473
|
if (state.clients.get(key)?.client === client) state.clients.delete(key);
|
|
@@ -1838,6 +2522,13 @@ function closeRetiredSharedClientEntryIfIdle(entry) {
|
|
|
1838
2522
|
client.close();
|
|
1839
2523
|
return true;
|
|
1840
2524
|
}
|
|
2525
|
+
function closeRetiredSharedClientEntry(entry) {
|
|
2526
|
+
const client = entry.client;
|
|
2527
|
+
if (!client) return false;
|
|
2528
|
+
entry.client = void 0;
|
|
2529
|
+
client.close();
|
|
2530
|
+
return true;
|
|
2531
|
+
}
|
|
1841
2532
|
function closeSharedClientEntryIfUnclaimed(key, entry) {
|
|
1842
2533
|
if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return false;
|
|
1843
2534
|
const state = getSharedCodexAppServerClientState();
|
|
@@ -1846,8 +2537,17 @@ function closeSharedClientEntryIfUnclaimed(key, entry) {
|
|
|
1846
2537
|
entry.client?.close();
|
|
1847
2538
|
return Boolean(entry.client);
|
|
1848
2539
|
}
|
|
2540
|
+
function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
|
|
2541
|
+
if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
|
|
2542
|
+
entry.runtimeArtifactStartupAbort?.abort(/* @__PURE__ */ new Error("Codex runtime artifact startup was abandoned"));
|
|
2543
|
+
entry.closeWhenIdle = true;
|
|
2544
|
+
const state = getSharedCodexAppServerClientState();
|
|
2545
|
+
if (state.clients.get(key) === entry) state.clients.delete(key);
|
|
2546
|
+
if (!entry.client) return;
|
|
2547
|
+
closeRetiredSharedClientEntry(entry);
|
|
2548
|
+
}
|
|
1849
2549
|
function collectSharedClients(state) {
|
|
1850
2550
|
return [...new Set([...state.clients.values()].map((entry) => entry.client).filter((client) => Boolean(client)))];
|
|
1851
2551
|
}
|
|
1852
2552
|
//#endregion
|
|
1853
|
-
export {
|
|
2553
|
+
export { isCodexAppServerApprovalRequest as A, resolveCodexAppServerFallbackApiKeyCacheKey as C, resolveCodexAppServerHomeDir as D, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as E, isCodexAppServerIndeterminateRequestCancellationError as M, isCodexAppServerIndeterminateTransportError as N, CodexAppServerRpcError as O, isCodexAppServerPrewriteRequestCancellationError as P, resolveCodexAppServerAuthProfileIdForAgent as S, resolveCodexAppServerPreparedAuthHandoff as T, readCodexRateLimitsRevision as _, getLeasedSharedCodexAppServerClient as a, resolveCodexAppServerAuthAccountCacheKey as b, releaseLeasedSharedCodexAppServerClient as c, retireSharedCodexAppServerClientIfCurrent as d, shared_client_exports as f, ensureCodexAppServerClientRuntime as g, acquireCodexNativeConfigFence as h, createIsolatedCodexAppServerClient as i, isCodexAppServerConnectionClosedError as j, getCodexAppServerClientInstanceId as k, resolveCodexNativeConfigFenceKey as l, withTimeout$1 as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, isCodexAppServerStartSelectionChangedError as o, withLeasedCodexAppServerClientStartSelectionRetry as p, clearSharedCodexAppServerClientIfCurrentAndWait as r, releaseCodexAppServerClientLease as s, clearSharedCodexAppServerClientIfCurrent as t, retainSharedCodexAppServerClientIfCurrent as u, readRecentCodexRateLimits as v, resolveCodexAppServerPreparedApiKeyCacheKey as w, resolveCodexAppServerAuthProfileId as x, rememberCodexRateLimitsRead as y };
|