@openclaw/codex 2026.8.1-beta.3 → 2026.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +4 -5
- package/dist/{attempt-client-cleanup-URjUlCHg.js → attempt-client-cleanup-CVPvp9dL.js} +47 -5
- package/dist/{auth-binding-B4FfbcCg.js → auth-binding-Ds_QVm-X.js} +18 -3
- package/dist/{binding-connection-DAvZwp96.js → binding-connection-CCQFs8lf.js} +2 -2
- package/dist/{bounded-turn-4BjrZEwW.js → bounded-turn-rq5tiSXk.js} +13 -18
- package/dist/{capabilities-CH3K57v4.js → capabilities-CGm_94eL.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-D0nZ3qtd.js → command-formatters-C54Yg3eA.js} +2 -2
- package/dist/{command-handlers-DC86lgT4.js → command-handlers-BvOAHTrA.js} +89 -62
- package/dist/{compact-COqgBRny.js → compact-Hd7SAc0N.js} +8 -9
- package/dist/{config-CpNeo5AE.js → config-Cxw3Cl3k.js} +346 -286
- package/dist/doctor-contract-api.js +115 -14
- package/dist/{dynamic-tools-fJZhl4CE.js → dynamic-tools-BFoUcRdx.js} +147 -903
- package/dist/{effective-mcp-catalog-4YKn51aG.js → effective-mcp-catalog-BTJHnDSx.js} +2 -2
- package/dist/{event-projector-assistant-message-BwxV4d6N.js → event-projector-assistant-message-C5-Ds8mA.js} +18 -1
- package/dist/{event-projector-usage-CLwNEJqW.js → event-projector-usage-D3PHFFXh.js} +3 -3
- package/dist/{harness-DoKQcF8n.js → harness-DVfUQBlQ.js} +42 -52
- package/dist/harness.js +1 -1
- package/dist/index.js +278 -295
- package/dist/{isolated-completion-DPMtlH9r.js → isolated-completion-Cbbx_5Ed.js} +4 -4
- package/dist/launch-args-sXOAg0SX.js +84 -0
- package/dist/{managed-binary-By4FZC6i.js → managed-binary-ZkGodW19.js} +43 -7
- package/dist/media-understanding-provider.js +1 -1
- package/dist/model-catalog-o6Cgjoss.js +101 -0
- package/dist/{models-CZw1wClT.js → models-DgFWSuxA.js} +25 -28
- package/dist/{native-subagent-monitor-GGq3RnOo.js → native-subagent-monitor-CwFyoylz.js} +207 -30
- package/dist/{node-exec-server.runtime-Bsu1LdFZ.js → node-exec-server.runtime-DXrqpw1c.js} +8 -5
- package/dist/{plugin-approval-roundtrip-C75Yiiwc.js → plugin-approval-roundtrip-D80UZiJ_.js} +1 -1
- package/dist/{plugin-marketplace-discovery-CSWDkMrP.js → plugin-marketplace-discovery-mdmScYOj.js} +30 -39
- package/dist/{plugin-metadata-cache-BXH5NF-4.js → plugin-metadata-cache-CulOABwu.js} +162 -135
- package/dist/{protocol-validators-VAVyEnDp.js → protocol-validators-Bg5rpNxk.js} +148 -5
- package/dist/{rate-limits-BI1dMHDG.js → rate-limits-hFtzt2Up.js} +1 -1
- package/dist/{sandbox-guard-C1qEeZ5C.js → request-CCm4LaED.js} +183 -2
- package/dist/{run-attempt-BV3HzuCf.js → run-attempt-BwNQDH2u.js} +599 -361
- package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-DeBvleJw.js} +1 -1
- package/dist/{session-binding-BTrLf9Qm.js → session-binding-q2WSTVmC.js} +195 -122
- package/dist/{session-catalog-ezP_UcRM.js → session-catalog-C2Tae4rI.js} +229 -122
- package/dist/session-catalog-home-id-DAKUrm3C.js +112 -0
- package/dist/{session-cli-B1hKvfkI.js → session-cli-BuDjm5Ou.js} +1 -1
- package/dist/session-permission-policy-gLm40Vtq.js +160 -0
- package/dist/session-retirement-CdkI0bvb.js +90 -0
- package/dist/{session-upstream-marker-D_2npHlA.js → session-upstream-marker-Bcy33kam.js} +2 -3
- package/dist/{settled-turn-finalizer-Dx1onIqg.js → settled-turn-finalizer-B8JJcxCi.js} +7 -6
- package/dist/{shared-client-Dw-rzii0.js → shared-client-kNkljY-R.js} +1720 -786
- package/dist/{side-question-DbXkJZzg.js → side-question-GwD73tX6.js} +69 -39
- package/dist/{thread-lifecycle-DKdrnTMX.js → thread-lifecycle-Ss-uwnC4.js} +852 -359
- package/dist/{thread-ownership-i2NBwDUL.js → thread-ownership-CeYBqg_4.js} +18 -8
- package/dist/{plugin-app-cache-key-DMNQ8Zi8.js → thread-resume-DF54hagX.js} +52 -6
- package/dist/{transcript-mirror-BU0_LKAL.js → transcript-mirror-CkEQIna-.js} +112 -13
- package/dist/transport-stdio-C7-s9TLG.js +708 -0
- package/dist/{upstream-session-fork-BDVJCLAD.js → upstream-session-fork-BACGlOFa.js} +78 -86
- package/dist/{usage-9dKJCgcy.js → usage-C2l8Mils.js} +3 -3
- package/dist/{web-search-provider.runtime-DCdx4DGu.js → web-search-provider.runtime-B2XgRdFv.js} +3 -3
- package/openclaw.plugin.json +19 -1
- package/package.json +8 -8
- package/dist/app-server-policy-bg5QPSVP.js +0 -25
- package/dist/command-rpc-xUIIonsO.js +0 -90
- package/dist/incognito-session-ET_ufDH-.js +0 -2
- package/dist/model-catalog-CmGoqVKB.js +0 -48
- package/dist/request-DSK0W6YI.js +0 -157
- package/dist/session-catalog-home-id-BNwAJ9D7.js +0 -18
- package/dist/session-retirement-CE4iMiLh.js +0 -36
- package/dist/thread-resume-CdJk2Ehf.js +0 -30
- package/dist/transport-stdio-DhoGy33Z.js +0 -99
|
@@ -1,186 +1,34 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import { a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey
|
|
3
|
-
import { o as
|
|
4
|
-
import {
|
|
5
|
-
import { a as
|
|
2
|
+
import { F as resolveCodexAppServerLocalHomeDir, I as resolveCodexAppServerUserHomeDir, L as withEphemeralCodexAuthStore, P as resolveCodexAppServerHomeDir, a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey } from "./config-Cxw3Cl3k.js";
|
|
3
|
+
import { a as CODEX_APP_SERVER_VERSION, c as resolveMacOSDesktopCodexAppPathCandidates, l as resolveMacOSDesktopCodexBundledMarketplaceCandidates, n as resolveManagedCodexAppServerStartOptions, o as MIN_SUPPORTED_CODEX_APP_SERVER_VERSION, r as resolveManagedCodexNativeCommand, s as resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath, t as isManagedCodexDesktopCommand, u as resolveMacOSDesktopCodexComputerUseServiceAppCandidates } from "./managed-binary-ZkGodW19.js";
|
|
4
|
+
import { o as isCodexAppServerNativeAuthProfile } from "./session-binding-q2WSTVmC.js";
|
|
5
|
+
import { a as hasCodexAppServerNaturalExit, i as closeCodexAppServerTransportAndWait, n as resolveCodexAppServerSpawnEnv, r as closeCodexAppServerTransport, t as createStdioTransport } from "./transport-stdio-C7-s9TLG.js";
|
|
6
|
+
import { l as readCodexDynamicToolCallParams } from "./protocol-validators-Bg5rpNxk.js";
|
|
6
7
|
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
8
|
import fs from "node:fs/promises";
|
|
8
9
|
import path from "node:path";
|
|
9
10
|
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
10
|
-
import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
11
|
-
import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
11
|
+
import { addTimerTimeoutGraceMs, parseStrictNonNegativeInteger, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
12
|
+
import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage, formatToolExecutionErrorMessage, normalizeQuestionTimeoutSeconds, resolveToolExecutionErrorKind } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
12
13
|
import { createHash, randomUUID } from "node:crypto";
|
|
13
|
-
import fsSync, { constants, existsSync } from "node:fs";
|
|
14
|
+
import fsSync, { constants, existsSync, watch } from "node:fs";
|
|
14
15
|
import os from "node:os";
|
|
15
16
|
import { isDeepStrictEqual } from "node:util";
|
|
16
17
|
import { ensureAuthProfileStore, findPersistedAuthProfileCredential, loadAuthProfileStoreForSecretsRuntime, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolveAuthProfileOrder, resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, resolveProviderIdForAuth } from "openclaw/plugin-sdk/agent-runtime";
|
|
17
|
-
import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
|
|
18
|
+
import { hasUsableOAuthCredential, resolveOpenAICodexAuthIdentity } from "openclaw/plugin-sdk/provider-auth";
|
|
18
19
|
import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
|
|
19
|
-
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
20
|
-
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
21
20
|
import { assertNoSymlinkParents, withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
22
|
-
import {
|
|
21
|
+
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
22
|
+
import { EventEmitter } from "node:events";
|
|
23
23
|
import { createInterface } from "node:readline";
|
|
24
24
|
import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
|
25
25
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
26
26
|
import { parse } from "semver";
|
|
27
|
-
import {
|
|
27
|
+
import { hasPendingInternalDiagnosticEvent } from "openclaw/plugin-sdk/diagnostic-runtime";
|
|
28
28
|
import net from "node:net";
|
|
29
29
|
import { PassThrough, Writable } from "node:stream";
|
|
30
30
|
import { StringDecoder } from "node:string_decoder";
|
|
31
31
|
import WebSocket from "ws";
|
|
32
|
-
//#region extensions/codex/src/app-server/auth-start-options.ts
|
|
33
|
-
const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
|
|
34
|
-
const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
|
|
35
|
-
function resolveCodexAppServerHomeDir(agentDir) {
|
|
36
|
-
return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
|
|
37
|
-
}
|
|
38
|
-
/** Resolves the local CODEX_HOME used when starting one app-server connection. */
|
|
39
|
-
function resolveCodexAppServerLocalHomeDir(startOptions, agentDir, env = process.env) {
|
|
40
|
-
const configured = startOptions.env?.CODEX_HOME;
|
|
41
|
-
if (configured?.trim()) return configured;
|
|
42
|
-
return startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir(env) : resolveCodexAppServerHomeDir(agentDir);
|
|
43
|
-
}
|
|
44
|
-
/** Forces OpenClaw-owned Codex auth to remain process-local. */
|
|
45
|
-
function withEphemeralCodexAuthStore(params) {
|
|
46
|
-
const { startOptions } = params;
|
|
47
|
-
if (!(startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed") || !params.preparedAuth && params.authProfileId === null) return startOptions;
|
|
48
|
-
if (startOptions.args.at(-2) === "-c" && startOptions.args.at(-1) === CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE) return startOptions;
|
|
49
|
-
return {
|
|
50
|
-
...startOptions,
|
|
51
|
-
args: [
|
|
52
|
-
...startOptions.args,
|
|
53
|
-
"-c",
|
|
54
|
-
CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE
|
|
55
|
-
]
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
//#endregion
|
|
59
|
-
//#region extensions/codex/src/app-server/desktop-app-paths.ts
|
|
60
|
-
/** Shared path candidates for Codex's macOS desktop app bundle. */
|
|
61
|
-
const MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES = [{
|
|
62
|
-
appName: "ChatGPT.app",
|
|
63
|
-
appBundlePath: "/Applications/ChatGPT.app",
|
|
64
|
-
appServerCommandPath: "/Applications/ChatGPT.app/Contents/Resources/codex",
|
|
65
|
-
bundledMarketplacePath: "/Applications/ChatGPT.app/Contents/Resources/plugins/openai-bundled",
|
|
66
|
-
computerUseServiceAppPaths: ["/Applications/ChatGPT.app/Contents/Resources/cua_node/lib/node_modules/@oai/sky/Codex Computer Use.app", "/Applications/ChatGPT.app/Contents/Resources/plugins/openai-bundled/plugins/computer-use/Codex Computer Use.app"]
|
|
67
|
-
}, {
|
|
68
|
-
appName: "Codex.app",
|
|
69
|
-
appBundlePath: "/Applications/Codex.app",
|
|
70
|
-
appServerCommandPath: "/Applications/Codex.app/Contents/Resources/codex",
|
|
71
|
-
bundledMarketplacePath: "/Applications/Codex.app/Contents/Resources/plugins/openai-bundled",
|
|
72
|
-
computerUseServiceAppPaths: ["/Applications/Codex.app/Contents/Resources/plugins/openai-bundled/plugins/computer-use/Codex Computer Use.app", "/Applications/Codex.app/Contents/Resources/cua_node/lib/node_modules/@oai/sky/Codex Computer Use.app"]
|
|
73
|
-
}];
|
|
74
|
-
function resolveMacOSDesktopCodexBundledMarketplaceCandidates(platform = process.platform) {
|
|
75
|
-
return platform === "darwin" ? MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.map((candidate) => candidate.bundledMarketplacePath) : [];
|
|
76
|
-
}
|
|
77
|
-
function resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform = process.platform, appServerCommand) {
|
|
78
|
-
if (platform !== "darwin") return [];
|
|
79
|
-
const matchingCandidate = appServerCommand ? MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(appServerCommand)) : void 0;
|
|
80
|
-
const orderedCandidates = matchingCandidate ? [matchingCandidate, ...MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES.filter((candidate) => candidate !== matchingCandidate)] : MACOS_DESKTOP_CODEX_APP_PATH_CANDIDATES;
|
|
81
|
-
return [...new Set(orderedCandidates.flatMap((candidate) => candidate.computerUseServiceAppPaths))];
|
|
82
|
-
}
|
|
83
|
-
function resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath(params = {}) {
|
|
84
|
-
const candidates = params.candidates ?? resolveMacOSDesktopCodexBundledMarketplaceCandidates(params.platform);
|
|
85
|
-
const pathExists = params.pathExists ?? existsSync;
|
|
86
|
-
return candidates.find((candidate) => pathExists(candidate));
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
|
-
//#region extensions/codex/src/app-server/computer-use-cache.ts
|
|
90
|
-
/** Shared Computer Use plugin cache reconciliation for isolated Codex homes. */
|
|
91
|
-
const DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH = resolveMacOSDesktopCodexBundledMarketplaceCandidates("darwin")[0] ?? "";
|
|
92
|
-
const DEFAULT_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
|
|
93
|
-
async function ensureCodexComputerUseSharedPluginCache(params) {
|
|
94
|
-
if (!params.config.enabled) return skippedCacheResult("disabled", "Computer Use cache sharing skipped because it is disabled.");
|
|
95
|
-
if (params.config.pluginCacheMode === "independent") return skippedCacheResult("independent", "Computer Use cache sharing skipped because pluginCacheMode is independent.");
|
|
96
|
-
if (params.config.marketplaceName || params.config.marketplacePath) return skippedCacheResult("explicit_marketplace", "Computer Use cache sharing skipped because an explicit marketplace is configured.");
|
|
97
|
-
const bundledMarketplacePath = resolveComputerUseBundledMarketplacePath(params);
|
|
98
|
-
const sourcePluginRoot = path.join(bundledMarketplacePath, "plugins", params.config.pluginName);
|
|
99
|
-
const version = await readBundledPluginVersion(sourcePluginRoot);
|
|
100
|
-
if (!version) return skippedCacheResult("source_missing", `Computer Use bundled plugin source was not found at ${sourcePluginRoot}.`);
|
|
101
|
-
const marketplaceName = params.config.marketplaceName ?? DEFAULT_BUNDLED_MARKETPLACE_NAME;
|
|
102
|
-
const cacheRoot = path.join(params.codexHome, "plugins", "cache", marketplaceName, params.config.pluginName);
|
|
103
|
-
const cachePath = path.join(cacheRoot, version);
|
|
104
|
-
return {
|
|
105
|
-
status: "shared",
|
|
106
|
-
changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version),
|
|
107
|
-
cachePath,
|
|
108
|
-
targetPath: sourcePluginRoot,
|
|
109
|
-
version,
|
|
110
|
-
removedStaleVersions: [],
|
|
111
|
-
warnings: [],
|
|
112
|
-
message: `Computer Use plugin cache ${cachePath} contains bundled plugin ${sourcePluginRoot}.`
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function resolveComputerUseBundledMarketplacePath(params) {
|
|
116
|
-
return params.bundledMarketplacePath ?? resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath({ candidates: params.bundledMarketplacePathCandidates }) ?? params.bundledMarketplacePathCandidates?.[0] ?? DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH;
|
|
117
|
-
}
|
|
118
|
-
async function readBundledPluginVersion(sourcePluginRoot) {
|
|
119
|
-
const pluginJsonPath = path.join(sourcePluginRoot, ".codex-plugin", "plugin.json");
|
|
120
|
-
let raw;
|
|
121
|
-
try {
|
|
122
|
-
raw = await fs.readFile(pluginJsonPath, "utf8");
|
|
123
|
-
} catch {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
try {
|
|
127
|
-
const parsed = JSON.parse(raw);
|
|
128
|
-
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
129
|
-
} catch {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version) {
|
|
134
|
-
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
135
|
-
const stat = await fs.lstat(cachePath).catch(() => void 0);
|
|
136
|
-
if (stat?.isDirectory() && !stat.isSymbolicLink()) {
|
|
137
|
-
if (await readBundledPluginVersion(cachePath) === version) return false;
|
|
138
|
-
}
|
|
139
|
-
const cacheRoot = path.dirname(cachePath);
|
|
140
|
-
const cacheName = path.basename(cachePath);
|
|
141
|
-
const stagingRoot = await fs.mkdtemp(path.join(cacheRoot, `.${cacheName}.staging-`));
|
|
142
|
-
const stagedPath = path.join(stagingRoot, cacheName);
|
|
143
|
-
const backupPath = path.join(cacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
|
|
144
|
-
let backupCreated = false;
|
|
145
|
-
try {
|
|
146
|
-
await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
|
|
147
|
-
if (stat) {
|
|
148
|
-
await fs.rename(cachePath, backupPath);
|
|
149
|
-
backupCreated = true;
|
|
150
|
-
}
|
|
151
|
-
try {
|
|
152
|
-
await fs.rename(stagedPath, cachePath);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
if (backupCreated) try {
|
|
155
|
-
await fs.rename(backupPath, cachePath);
|
|
156
|
-
backupCreated = false;
|
|
157
|
-
} catch (restoreError) {
|
|
158
|
-
throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
|
|
159
|
-
}
|
|
160
|
-
throw error;
|
|
161
|
-
}
|
|
162
|
-
if (backupCreated) await fs.rm(backupPath, {
|
|
163
|
-
recursive: true,
|
|
164
|
-
force: true
|
|
165
|
-
});
|
|
166
|
-
return true;
|
|
167
|
-
} finally {
|
|
168
|
-
await fs.rm(stagingRoot, {
|
|
169
|
-
recursive: true,
|
|
170
|
-
force: true
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function skippedCacheResult(status, message) {
|
|
175
|
-
return {
|
|
176
|
-
status,
|
|
177
|
-
changed: false,
|
|
178
|
-
message,
|
|
179
|
-
removedStaleVersions: [],
|
|
180
|
-
warnings: status === "source_missing" ? [message] : []
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
//#endregion
|
|
184
32
|
//#region extensions/codex/src/app-server/computer-use-service-path.ts
|
|
185
33
|
/** Filesystem ownership guards for isolated Computer Use service provisioning. */
|
|
186
34
|
async function assertOwnedServicePath(params) {
|
|
@@ -279,7 +127,7 @@ async function assertNotSymlink(filePath, label) {
|
|
|
279
127
|
try {
|
|
280
128
|
if ((await fs.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
|
|
281
129
|
} catch (error) {
|
|
282
|
-
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
130
|
+
if (hasNodeErrorCode$1(error, "ENOENT")) return;
|
|
283
131
|
throw error;
|
|
284
132
|
}
|
|
285
133
|
}
|
|
@@ -292,14 +140,14 @@ async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
|
|
|
292
140
|
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
293
141
|
current = path.join(current, segment);
|
|
294
142
|
const existing = await fs.lstat(current).catch((error) => {
|
|
295
|
-
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
143
|
+
if (hasNodeErrorCode$1(error, "ENOENT")) return;
|
|
296
144
|
throw error;
|
|
297
145
|
});
|
|
298
146
|
if (!existing) {
|
|
299
147
|
try {
|
|
300
148
|
await fs.mkdir(current, { mode: 448 });
|
|
301
149
|
} catch (error) {
|
|
302
|
-
if (!hasNodeErrorCode(error, "EEXIST")) throw error;
|
|
150
|
+
if (!hasNodeErrorCode$1(error, "EEXIST")) throw error;
|
|
303
151
|
}
|
|
304
152
|
const created = await fs.lstat(current);
|
|
305
153
|
if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
|
|
@@ -318,6 +166,259 @@ function assertPathAtOrInside(rootPath, candidatePath, label) {
|
|
|
318
166
|
const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
|
|
319
167
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
|
|
320
168
|
}
|
|
169
|
+
function hasNodeErrorCode$1(error, code) {
|
|
170
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region extensions/codex/src/app-server/computer-use-cache.ts
|
|
174
|
+
/** Shared Computer Use plugin cache reconciliation for isolated Codex homes. */
|
|
175
|
+
const DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH = resolveMacOSDesktopCodexBundledMarketplaceCandidates("darwin")[0] ?? "";
|
|
176
|
+
const DEFAULT_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
|
|
177
|
+
async function ensureCodexComputerUseSharedPluginCache(params) {
|
|
178
|
+
if (!params.config.enabled) return skippedCacheResult("disabled", "Computer Use cache sharing skipped because it is disabled.");
|
|
179
|
+
if (params.config.pluginCacheMode === "independent") return skippedCacheResult("independent", "Computer Use cache sharing skipped because pluginCacheMode is independent.");
|
|
180
|
+
if (params.config.marketplaceName || params.config.marketplacePath) return skippedCacheResult("explicit_marketplace", "Computer Use cache sharing skipped because an explicit marketplace is configured.");
|
|
181
|
+
const bundledMarketplacePath = resolveComputerUseBundledMarketplacePath(params);
|
|
182
|
+
const sourcePluginRoot = path.join(bundledMarketplacePath, "plugins", params.config.pluginName);
|
|
183
|
+
const version = await readBundledPluginVersion(sourcePluginRoot);
|
|
184
|
+
if (!version) return skippedCacheResult("source_missing", `Computer Use bundled plugin source was not found at ${sourcePluginRoot}.`);
|
|
185
|
+
const marketplaceName = params.config.marketplaceName ?? DEFAULT_BUNDLED_MARKETPLACE_NAME;
|
|
186
|
+
const cacheRoot = path.join(params.codexHome, "plugins", "cache", marketplaceName, params.config.pluginName);
|
|
187
|
+
const cachePath = path.join(cacheRoot, version);
|
|
188
|
+
return {
|
|
189
|
+
status: "shared",
|
|
190
|
+
changed: await ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, {
|
|
191
|
+
codexHome: params.codexHome,
|
|
192
|
+
ownershipRoot: params.ownershipRoot,
|
|
193
|
+
assertCurrent: params.assertCurrent,
|
|
194
|
+
forceRefresh: params.forceRefresh
|
|
195
|
+
}),
|
|
196
|
+
cachePath,
|
|
197
|
+
targetPath: sourcePluginRoot,
|
|
198
|
+
version,
|
|
199
|
+
removedStaleVersions: [],
|
|
200
|
+
warnings: [],
|
|
201
|
+
message: `Computer Use plugin cache ${cachePath} contains bundled plugin ${sourcePluginRoot}.`
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function resolveComputerUseBundledMarketplacePath(params) {
|
|
205
|
+
return params.bundledMarketplacePath ?? resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath({ candidates: params.bundledMarketplacePathCandidates }) ?? params.bundledMarketplacePathCandidates?.[0] ?? DEFAULT_CODEX_COMPUTER_USE_BUNDLED_MARKETPLACE_PATH;
|
|
206
|
+
}
|
|
207
|
+
async function readBundledPluginVersion(sourcePluginRoot) {
|
|
208
|
+
const pluginJsonPath = path.join(sourcePluginRoot, ".codex-plugin", "plugin.json");
|
|
209
|
+
let raw;
|
|
210
|
+
try {
|
|
211
|
+
raw = await fs.readFile(pluginJsonPath, "utf8");
|
|
212
|
+
} catch {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const parsed = JSON.parse(raw);
|
|
217
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
218
|
+
} catch {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function ensureRealDirectoryCopy(cachePath, sourcePluginRoot, version, boundary) {
|
|
223
|
+
const cacheRoot = path.dirname(cachePath);
|
|
224
|
+
const ownedParent = boundary.ownershipRoot ? await prepareOwnedServiceParent({
|
|
225
|
+
ownershipRoot: boundary.ownershipRoot,
|
|
226
|
+
codexHome: boundary.codexHome,
|
|
227
|
+
targetParent: cacheRoot
|
|
228
|
+
}) : void 0;
|
|
229
|
+
if (!ownedParent) await fs.mkdir(cacheRoot, { recursive: true });
|
|
230
|
+
const physicalCachePath = ownedParent ? path.join(ownedParent.realPath, path.basename(cachePath)) : cachePath;
|
|
231
|
+
const stat = await fs.lstat(physicalCachePath).catch(() => void 0);
|
|
232
|
+
if (stat?.isDirectory() && !stat.isSymbolicLink()) {
|
|
233
|
+
if (await readBundledPluginVersion(physicalCachePath) === version && !boundary.forceRefresh) return false;
|
|
234
|
+
}
|
|
235
|
+
const cacheName = path.basename(cachePath);
|
|
236
|
+
const physicalCacheRoot = path.dirname(physicalCachePath);
|
|
237
|
+
const stagingRoot = await fs.mkdtemp(path.join(physicalCacheRoot, `.${cacheName}.staging-`));
|
|
238
|
+
const stagedPath = path.join(stagingRoot, cacheName);
|
|
239
|
+
const backupPath = path.join(physicalCacheRoot, `.${cacheName}.backup-${process.pid}-${Date.now()}`);
|
|
240
|
+
let backupCreated = false;
|
|
241
|
+
try {
|
|
242
|
+
await fs.cp(sourcePluginRoot, stagedPath, { recursive: true });
|
|
243
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
244
|
+
if (stat) {
|
|
245
|
+
boundary.assertCurrent?.();
|
|
246
|
+
await fs.rename(physicalCachePath, backupPath);
|
|
247
|
+
backupCreated = true;
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
251
|
+
boundary.assertCurrent?.();
|
|
252
|
+
await fs.rename(stagedPath, physicalCachePath);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (backupCreated) try {
|
|
255
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
256
|
+
await fs.rename(backupPath, physicalCachePath);
|
|
257
|
+
backupCreated = false;
|
|
258
|
+
} catch (restoreError) {
|
|
259
|
+
throw new Error(`Failed to install Computer Use cache ${cachePath} and restore its prior copy: ${String(error)}`, { cause: restoreError });
|
|
260
|
+
}
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
if (backupCreated) {
|
|
264
|
+
if (ownedParent) await assertDirectoryIdentityStable(ownedParent, "Computer Use plugin cache parent");
|
|
265
|
+
await fs.rm(backupPath, {
|
|
266
|
+
recursive: true,
|
|
267
|
+
force: true
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return true;
|
|
271
|
+
} finally {
|
|
272
|
+
if (!ownedParent || await directoryIdentityIsStable(ownedParent)) await fs.rm(stagingRoot, {
|
|
273
|
+
recursive: true,
|
|
274
|
+
force: true
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function skippedCacheResult(status, message) {
|
|
279
|
+
return {
|
|
280
|
+
status,
|
|
281
|
+
changed: false,
|
|
282
|
+
message,
|
|
283
|
+
removedStaleVersions: [],
|
|
284
|
+
warnings: status === "source_missing" ? [message] : []
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region extensions/codex/src/app-server/computer-use-marketplace.ts
|
|
289
|
+
/** Managed local wrapper for Codex's reserved bundled marketplace. */
|
|
290
|
+
const MARKETPLACE_NAME = "openai-bundled";
|
|
291
|
+
const activeInstalls$1 = /* @__PURE__ */ new Map();
|
|
292
|
+
function resolveCodexManagedBundledMarketplacePath(codexHome) {
|
|
293
|
+
return path.join(codexHome, ".tmp", "bundled-marketplaces", MARKETPLACE_NAME);
|
|
294
|
+
}
|
|
295
|
+
async function ensureCodexManagedBundledMarketplace(params) {
|
|
296
|
+
const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
|
|
297
|
+
const source = await resolveCodexManagedBundledMarketplaceSource({
|
|
298
|
+
...params,
|
|
299
|
+
candidates
|
|
300
|
+
});
|
|
301
|
+
if (!source) return;
|
|
302
|
+
const parentPath = path.dirname(resolveCodexManagedBundledMarketplacePath(params.codexHome));
|
|
303
|
+
const targetPath = path.join(parentPath, MARKETPLACE_NAME);
|
|
304
|
+
const parent = await prepareOwnedServiceParent({
|
|
305
|
+
ownershipRoot: params.ownershipRoot,
|
|
306
|
+
codexHome: params.codexHome,
|
|
307
|
+
targetParent: parentPath
|
|
308
|
+
});
|
|
309
|
+
const physicalTargetPath = path.join(parent.realPath, MARKETPLACE_NAME);
|
|
310
|
+
await assertNotSymlink(physicalTargetPath, "managed bundled marketplace");
|
|
311
|
+
const active = activeInstalls$1.get(physicalTargetPath);
|
|
312
|
+
if (active) {
|
|
313
|
+
if (active.sourcePath === source.bundledMarketplacePath) return await active.promise;
|
|
314
|
+
await active.promise.catch(() => void 0);
|
|
315
|
+
return await ensureCodexManagedBundledMarketplace(params);
|
|
316
|
+
}
|
|
317
|
+
const install = reconcileManagedWrapper({
|
|
318
|
+
parent,
|
|
319
|
+
physicalTargetPath,
|
|
320
|
+
targetPath,
|
|
321
|
+
source,
|
|
322
|
+
ownershipCandidates: params.ownershipCandidates ?? candidates,
|
|
323
|
+
assertCurrent: params.assertCurrent
|
|
324
|
+
});
|
|
325
|
+
const activeEntry = {
|
|
326
|
+
sourcePath: source.bundledMarketplacePath,
|
|
327
|
+
promise: install
|
|
328
|
+
};
|
|
329
|
+
activeInstalls$1.set(physicalTargetPath, activeEntry);
|
|
330
|
+
const clearActive = () => {
|
|
331
|
+
if (activeInstalls$1.get(physicalTargetPath) === activeEntry) activeInstalls$1.delete(physicalTargetPath);
|
|
332
|
+
};
|
|
333
|
+
install.then(clearActive, clearActive);
|
|
334
|
+
return await install;
|
|
335
|
+
}
|
|
336
|
+
async function reconcileManagedWrapper(params) {
|
|
337
|
+
if (await wrapperMatches(params.physicalTargetPath, params.source.bundledMarketplacePath)) return params.targetPath;
|
|
338
|
+
return await publishManagedWrapper(params);
|
|
339
|
+
}
|
|
340
|
+
async function publishManagedWrapper(params) {
|
|
341
|
+
const { parent, physicalTargetPath, targetPath, source, ownershipCandidates, assertCurrent } = params;
|
|
342
|
+
const stagingPath = await fs.mkdtemp(path.join(parent.realPath, `.${MARKETPLACE_NAME}.staging-`));
|
|
343
|
+
const backupPath = path.join(parent.realPath, `.${MARKETPLACE_NAME}.backup-${process.pid}-${Date.now()}`);
|
|
344
|
+
let backupCreated = false;
|
|
345
|
+
try {
|
|
346
|
+
const manifestParent = path.join(stagingPath, ".agents", "plugins");
|
|
347
|
+
await fs.mkdir(manifestParent, {
|
|
348
|
+
recursive: true,
|
|
349
|
+
mode: 448
|
|
350
|
+
});
|
|
351
|
+
await Promise.all([fs.symlink(path.join(source.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"), path.join(manifestParent, "marketplace.json")), fs.symlink(path.join(source.bundledMarketplacePath, "plugins"), path.join(stagingPath, "plugins"))]);
|
|
352
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
353
|
+
const existing = await fs.lstat(physicalTargetPath).catch((error) => {
|
|
354
|
+
if (hasNodeErrorCode(error, "ENOENT")) return;
|
|
355
|
+
throw error;
|
|
356
|
+
});
|
|
357
|
+
if (existing && !existing.isDirectory()) throw new Error(`Managed bundled marketplace must be a real directory: ${targetPath}`);
|
|
358
|
+
if (existing && !await wrapperMatchesAnySource(physicalTargetPath, ownershipCandidates)) throw new Error(`Refusing to replace an unowned bundled marketplace directory: ${targetPath}`);
|
|
359
|
+
if (existing) {
|
|
360
|
+
assertCurrent?.();
|
|
361
|
+
await fs.rename(physicalTargetPath, backupPath);
|
|
362
|
+
backupCreated = true;
|
|
363
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
364
|
+
}
|
|
365
|
+
assertCurrent?.();
|
|
366
|
+
await fs.rename(stagingPath, physicalTargetPath);
|
|
367
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
368
|
+
if (backupCreated) {
|
|
369
|
+
await fs.rm(backupPath, { recursive: true });
|
|
370
|
+
backupCreated = false;
|
|
371
|
+
}
|
|
372
|
+
return targetPath;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (backupCreated) try {
|
|
375
|
+
await assertDirectoryIdentityStable(parent, "managed bundled marketplace parent");
|
|
376
|
+
if (await fs.lstat(physicalTargetPath).catch(() => void 0)) {
|
|
377
|
+
if (!await wrapperMatches(physicalTargetPath, source.bundledMarketplacePath)) throw new Error("managed bundled marketplace replacement is no longer owned", { cause: error });
|
|
378
|
+
await fs.rm(physicalTargetPath, { recursive: true });
|
|
379
|
+
}
|
|
380
|
+
await fs.rename(backupPath, physicalTargetPath);
|
|
381
|
+
backupCreated = false;
|
|
382
|
+
} catch (restoreError) {
|
|
383
|
+
throw new Error(`Failed to restore the prior managed bundled marketplace: ${String(error)}`, { cause: restoreError });
|
|
384
|
+
}
|
|
385
|
+
throw error;
|
|
386
|
+
} finally {
|
|
387
|
+
if (await directoryIdentityIsStable(parent)) await fs.rm(stagingPath, {
|
|
388
|
+
recursive: true,
|
|
389
|
+
force: true
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async function resolveCodexManagedBundledMarketplaceSource(params) {
|
|
394
|
+
const candidates = params.candidates ?? resolveMacOSDesktopCodexAppPathCandidates();
|
|
395
|
+
const command = params.appServerCommand && path.resolve(params.appServerCommand);
|
|
396
|
+
const ordered = command ? candidates.filter((candidate) => path.resolve(candidate.appServerCommandPath) === command) : candidates;
|
|
397
|
+
for (const candidate of ordered) if (await isExpectedMarketplace(candidate.bundledMarketplacePath)) return candidate;
|
|
398
|
+
}
|
|
399
|
+
async function isExpectedMarketplace(root) {
|
|
400
|
+
try {
|
|
401
|
+
const manifest = JSON.parse(await fs.readFile(path.join(root, ".agents", "plugins", "marketplace.json"), "utf8"));
|
|
402
|
+
await fs.access(path.join(root, "plugins", "computer-use", ".codex-plugin", "plugin.json"));
|
|
403
|
+
return isRecord(manifest) && manifest.name === MARKETPLACE_NAME && Array.isArray(manifest.plugins) && manifest.plugins.some((plugin) => isRecord(plugin) && plugin.name === "computer-use");
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function wrapperMatches(targetPath, sourcePath) {
|
|
409
|
+
try {
|
|
410
|
+
const target = await fs.lstat(targetPath);
|
|
411
|
+
if (!target.isDirectory() || target.isSymbolicLink()) return false;
|
|
412
|
+
const [manifest, plugins] = await Promise.all([fs.readlink(path.join(targetPath, ".agents", "plugins", "marketplace.json")), fs.readlink(path.join(targetPath, "plugins"))]);
|
|
413
|
+
return manifest === path.join(sourcePath, ".agents", "plugins", "marketplace.json") && plugins === path.join(sourcePath, "plugins");
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function wrapperMatchesAnySource(targetPath, candidates) {
|
|
419
|
+
for (const candidate of candidates) if (await wrapperMatches(targetPath, candidate.bundledMarketplacePath)) return true;
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
321
422
|
function hasNodeErrorCode(error, code) {
|
|
322
423
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
323
424
|
}
|
|
@@ -332,9 +433,13 @@ const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComp
|
|
|
332
433
|
const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
|
|
333
434
|
const COPY_TIMEOUT_MS = 12e4;
|
|
334
435
|
const INSPECT_TIMEOUT_MS = 3e4;
|
|
335
|
-
const COMPLETED_SYNC_CACHE_MAX_ENTRIES = 64;
|
|
336
436
|
const activeInstalls = /* @__PURE__ */ new Map();
|
|
337
|
-
|
|
437
|
+
/** Finds the first signed native service from one ordered desktop owner set. */
|
|
438
|
+
async function resolveCodexComputerUseServiceAppSourcePath(params) {
|
|
439
|
+
const platform = params.platform ?? process.platform;
|
|
440
|
+
if (platform !== "darwin") return;
|
|
441
|
+
return (await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand), params.inspectServiceApp ?? inspectTrustedServiceApp))?.path;
|
|
442
|
+
}
|
|
338
443
|
/** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
|
|
339
444
|
async function ensureCodexComputerUseServiceApp(params) {
|
|
340
445
|
const platform = params.platform ?? process.platform;
|
|
@@ -355,25 +460,12 @@ async function ensureCodexComputerUseServiceApp(params) {
|
|
|
355
460
|
});
|
|
356
461
|
const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
|
|
357
462
|
const syncKey = [targetPath, ...candidates].join("\0");
|
|
358
|
-
const completed = completedSyncs.get(targetPath);
|
|
359
|
-
if (completed?.syncKey === syncKey) {
|
|
360
|
-
completedSyncs.delete(targetPath);
|
|
361
|
-
completedSyncs.set(targetPath, completed);
|
|
362
|
-
return {
|
|
363
|
-
status: "already_current",
|
|
364
|
-
changed: false,
|
|
365
|
-
targetPath: completed.targetPath,
|
|
366
|
-
sourcePath: completed.sourcePath,
|
|
367
|
-
sourceBuild: completed.sourceBuild
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
463
|
const active = activeInstalls.get(targetPath);
|
|
371
464
|
if (active) {
|
|
372
465
|
if (active.syncKey === syncKey) return await active.promise;
|
|
373
466
|
await active.promise.catch(() => void 0);
|
|
374
467
|
return await ensureCodexComputerUseServiceApp(params);
|
|
375
468
|
}
|
|
376
|
-
if (completed) completedSyncs.delete(targetPath);
|
|
377
469
|
const install = ensureCodexComputerUseServiceAppOnce({
|
|
378
470
|
...params,
|
|
379
471
|
codexHome,
|
|
@@ -382,18 +474,6 @@ async function ensureCodexComputerUseServiceApp(params) {
|
|
|
382
474
|
targetPath,
|
|
383
475
|
platform,
|
|
384
476
|
sourceAppCandidates: candidates
|
|
385
|
-
}).then((result) => {
|
|
386
|
-
if (isCompletedSyncStatus(result.status)) {
|
|
387
|
-
completedSyncs.delete(targetPath);
|
|
388
|
-
completedSyncs.set(targetPath, {
|
|
389
|
-
syncKey,
|
|
390
|
-
targetPath: result.targetPath,
|
|
391
|
-
sourcePath: result.sourcePath,
|
|
392
|
-
sourceBuild: result.sourceBuild
|
|
393
|
-
});
|
|
394
|
-
pruneMapToMaxSize(completedSyncs, COMPLETED_SYNC_CACHE_MAX_ENTRIES);
|
|
395
|
-
}
|
|
396
|
-
return result;
|
|
397
477
|
});
|
|
398
478
|
const activeEntry = {
|
|
399
479
|
syncKey,
|
|
@@ -406,9 +486,6 @@ async function ensureCodexComputerUseServiceApp(params) {
|
|
|
406
486
|
install.then(clearActive, clearActive);
|
|
407
487
|
return await install;
|
|
408
488
|
}
|
|
409
|
-
function isCompletedSyncStatus(status) {
|
|
410
|
-
return status === "installed" || status === "refreshed" || status === "already_current";
|
|
411
|
-
}
|
|
412
489
|
async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
413
490
|
const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
|
|
414
491
|
const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
|
|
@@ -453,6 +530,7 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
|
453
530
|
await assertNotSymlink(operationTargetPath, "Computer Use service target");
|
|
454
531
|
if (await pathExists$1(operationTargetPath)) {
|
|
455
532
|
await assertOwnedServiceParentStable(ownedParent);
|
|
533
|
+
params.assertCurrent?.();
|
|
456
534
|
await fs.rename(operationTargetPath, backupPath);
|
|
457
535
|
await assertOwnedServiceParentStable(ownedParent);
|
|
458
536
|
backupCreated = true;
|
|
@@ -480,6 +558,7 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
|
|
|
480
558
|
}
|
|
481
559
|
try {
|
|
482
560
|
await assertOwnedServiceParentStable(ownedParent);
|
|
561
|
+
params.assertCurrent?.();
|
|
483
562
|
await fs.rename(stagedPath, operationTargetPath);
|
|
484
563
|
await assertOwnedServiceParentStable(ownedParent);
|
|
485
564
|
} catch (error) {
|
|
@@ -742,10 +821,12 @@ const CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS = [
|
|
|
742
821
|
const CODEX_APP_SERVER_HOME_ENV_VARS = [CODEX_HOME_ENV_VAR, HOME_ENV_VAR];
|
|
743
822
|
const CODEX_AUTH_JSON_FILENAME = "auth.json";
|
|
744
823
|
const CODEX_HOME_DIRNAME = ".codex";
|
|
824
|
+
const MAX_COMPUTER_USE_ARTIFACT_OWNERS = 128;
|
|
825
|
+
const activeComputerUseArtifactReconciliations = /* @__PURE__ */ new Map();
|
|
745
826
|
const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
|
|
746
827
|
async function bridgeCodexAppServerStartOptions(params) {
|
|
747
828
|
if (params.startOptions.transport !== "stdio") return params.startOptions;
|
|
748
|
-
const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir
|
|
829
|
+
const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir);
|
|
749
830
|
if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
|
|
750
831
|
if (params.authProfileId === null) return scopeStartOptions();
|
|
751
832
|
const store = resolveCodexAppServerAuthProfileStore({
|
|
@@ -772,8 +853,7 @@ function assertNoUnimportedAgentCodexAuthFile(params) {
|
|
|
772
853
|
if (message) throw new AgentHarnessPreflightError(message);
|
|
773
854
|
}
|
|
774
855
|
function resolveUnimportedAgentCodexAuthMessage(params) {
|
|
775
|
-
|
|
776
|
-
if (params.startOptions.transport !== "stdio" || !managedCodexCli || params.startOptions.homeScope === "user") return;
|
|
856
|
+
if (params.startOptions.transport !== "stdio" || params.startOptions.homeScope === "user") return;
|
|
777
857
|
const codexHome = resolveCodexAppServerHomeDir(params.agentDir);
|
|
778
858
|
const authPath = path.join(codexHome, CODEX_AUTH_JSON_FILENAME);
|
|
779
859
|
if (!fsSync.existsSync(authPath)) return;
|
|
@@ -845,7 +925,7 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
|
|
|
845
925
|
const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
|
|
846
926
|
const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
|
|
847
927
|
const secretFreeCacheKey = credential.type === "api_key" && loginParams.type === "apiKey" ? `${accountId}:${fingerprintApiKeyAuthProfileCacheKey(loginParams.apiKey)}` : loginParams.type === "chatgptAuthTokens" && (credential.type === "token" || !stableChatgptAccountId) ? `${accountId}:${fingerprintTokenAuthProfileCacheKey(loginParams.accessToken)}` : accountId;
|
|
848
|
-
const chatgptAccountId =
|
|
928
|
+
const chatgptAccountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : void 0;
|
|
849
929
|
return {
|
|
850
930
|
loginParams,
|
|
851
931
|
secretFreeCacheKey,
|
|
@@ -974,28 +1054,10 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
|
|
|
974
1054
|
hash.update(apiKey);
|
|
975
1055
|
return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
|
|
976
1056
|
}
|
|
977
|
-
async function withCodexHomeEnvironment(startOptions, agentDir
|
|
1057
|
+
async function withCodexHomeEnvironment(startOptions, agentDir) {
|
|
978
1058
|
const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
|
|
979
1059
|
const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
|
|
980
1060
|
await fs.mkdir(codexHome, { recursive: true });
|
|
981
|
-
const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
|
|
982
|
-
await ensureCodexComputerUseSharedPluginCache({
|
|
983
|
-
codexHome,
|
|
984
|
-
config: computerUseConfig
|
|
985
|
-
});
|
|
986
|
-
const ownsIsolatedCodexHome = startOptions.homeScope !== "user" && !startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim();
|
|
987
|
-
if (computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome) try {
|
|
988
|
-
await ensureCodexComputerUseServiceApp({
|
|
989
|
-
codexHome,
|
|
990
|
-
ownershipRoot: agentDir,
|
|
991
|
-
appServerCommand: startOptions.command
|
|
992
|
-
});
|
|
993
|
-
} catch (error) {
|
|
994
|
-
throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
|
|
995
|
-
cause: error,
|
|
996
|
-
scope: "harness"
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
1061
|
if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
|
|
1000
1062
|
const nextStartOptions = {
|
|
1001
1063
|
...startOptions,
|
|
@@ -1010,6 +1072,138 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
|
|
|
1010
1072
|
else delete nextStartOptions.clearEnv;
|
|
1011
1073
|
return nextStartOptions;
|
|
1012
1074
|
}
|
|
1075
|
+
/** Reconciles Computer Use artifacts for the exact managed command about to start. */
|
|
1076
|
+
async function reconcileCodexComputerUseStartArtifacts(params) {
|
|
1077
|
+
if (params.startOptions.transport !== "stdio") return;
|
|
1078
|
+
const codexHome = resolveCodexAppServerLocalHomeDir(params.startOptions, params.agentDir);
|
|
1079
|
+
const key = path.resolve(codexHome);
|
|
1080
|
+
let owner = activeComputerUseArtifactReconciliations.get(key);
|
|
1081
|
+
if (!owner) {
|
|
1082
|
+
owner = {
|
|
1083
|
+
active: 0,
|
|
1084
|
+
tail: Promise.resolve()
|
|
1085
|
+
};
|
|
1086
|
+
activeComputerUseArtifactReconciliations.set(key, owner);
|
|
1087
|
+
} else {
|
|
1088
|
+
activeComputerUseArtifactReconciliations.delete(key);
|
|
1089
|
+
activeComputerUseArtifactReconciliations.set(key, owner);
|
|
1090
|
+
}
|
|
1091
|
+
owner.active += 1;
|
|
1092
|
+
const epoch = params.desktopGeneration?.epoch;
|
|
1093
|
+
if (epoch !== void 0 && (owner.latestEpoch === void 0 || epoch > owner.latestEpoch)) owner.latestEpoch = epoch;
|
|
1094
|
+
const assertCurrent = () => {
|
|
1095
|
+
params.assertCurrent?.();
|
|
1096
|
+
if (epoch !== void 0 && owner.latestEpoch !== epoch) throw new Error("Codex Computer Use artifact reconciliation was superseded.");
|
|
1097
|
+
};
|
|
1098
|
+
const operation = owner.tail.catch(() => void 0).then(async () => {
|
|
1099
|
+
assertCurrent();
|
|
1100
|
+
const appliedCacheBinding = await reconcileCodexComputerUseStartArtifactsOnce({
|
|
1101
|
+
...params,
|
|
1102
|
+
codexHome,
|
|
1103
|
+
assertCurrent,
|
|
1104
|
+
previousCacheBinding: owner.appliedCacheBinding
|
|
1105
|
+
});
|
|
1106
|
+
assertCurrent();
|
|
1107
|
+
owner.appliedCacheBinding = appliedCacheBinding;
|
|
1108
|
+
});
|
|
1109
|
+
const settled = operation.then(() => void 0, () => void 0);
|
|
1110
|
+
owner.tail = settled;
|
|
1111
|
+
try {
|
|
1112
|
+
await operation;
|
|
1113
|
+
} finally {
|
|
1114
|
+
owner.active = Math.max(0, owner.active - 1);
|
|
1115
|
+
if (owner.active === 0 && owner.latestEpoch === void 0 && activeComputerUseArtifactReconciliations.get(key) === owner && owner.tail === settled) activeComputerUseArtifactReconciliations.delete(key);
|
|
1116
|
+
pruneComputerUseArtifactOwners();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
async function reconcileCodexComputerUseStartArtifactsOnce(params) {
|
|
1120
|
+
const codexHome = params.codexHome;
|
|
1121
|
+
const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
|
|
1122
|
+
const ownsIsolatedCodexHome = params.ownsIsolatedCodexHome ?? (params.startOptions.homeScope !== "user" && !params.startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim());
|
|
1123
|
+
const shouldProvisionComputerUse = computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome;
|
|
1124
|
+
if (shouldProvisionComputerUse) await ensureOwnedCodexHome(codexHome, params.agentDir);
|
|
1125
|
+
else await fs.mkdir(codexHome, { recursive: true });
|
|
1126
|
+
const desktopCandidates = resolveMacOSDesktopCodexAppPathCandidates();
|
|
1127
|
+
const exactDesktopCandidate = desktopCandidates.find((candidate) => path.resolve(candidate.appServerCommandPath) === path.resolve(params.startOptions.command));
|
|
1128
|
+
const usesManagedBundledMarketplace = !computerUseConfig.marketplaceSource && !computerUseConfig.marketplacePath && !computerUseConfig.marketplaceName;
|
|
1129
|
+
const needsBundledMarketplace = usesManagedBundledMarketplace || computerUseConfig.pluginCacheMode === "shared" && !computerUseConfig.marketplaceName && !computerUseConfig.marketplacePath;
|
|
1130
|
+
const artifactCandidate = shouldProvisionComputerUse ? await resolveCompleteComputerUseArtifactCandidate({
|
|
1131
|
+
candidates: exactDesktopCandidate ? [exactDesktopCandidate] : desktopCandidates,
|
|
1132
|
+
needsBundledMarketplace
|
|
1133
|
+
}) : exactDesktopCandidate;
|
|
1134
|
+
params.assertCurrent();
|
|
1135
|
+
if (shouldProvisionComputerUse) {
|
|
1136
|
+
if (desktopCandidates.length > 0 && !artifactCandidate) throw new CodexComputerUseCandidateArtifactsUnavailableError();
|
|
1137
|
+
try {
|
|
1138
|
+
const marketplacePath = usesManagedBundledMarketplace ? await ensureCodexManagedBundledMarketplace({
|
|
1139
|
+
codexHome,
|
|
1140
|
+
ownershipRoot: params.agentDir,
|
|
1141
|
+
...artifactCandidate ? {
|
|
1142
|
+
appServerCommand: artifactCandidate.appServerCommandPath,
|
|
1143
|
+
candidates: [artifactCandidate],
|
|
1144
|
+
ownershipCandidates: desktopCandidates
|
|
1145
|
+
} : {},
|
|
1146
|
+
assertCurrent: params.assertCurrent
|
|
1147
|
+
}) : void 0;
|
|
1148
|
+
params.assertCurrent();
|
|
1149
|
+
if (usesManagedBundledMarketplace && desktopCandidates.length > 0 && !marketplacePath) throw new CodexComputerUseCandidateArtifactsUnavailableError();
|
|
1150
|
+
const service = await ensureCodexComputerUseServiceApp({
|
|
1151
|
+
codexHome,
|
|
1152
|
+
ownershipRoot: params.agentDir,
|
|
1153
|
+
...artifactCandidate ? {
|
|
1154
|
+
appServerCommand: artifactCandidate.appServerCommandPath,
|
|
1155
|
+
sourceAppCandidates: artifactCandidate.computerUseServiceAppPaths
|
|
1156
|
+
} : {},
|
|
1157
|
+
assertCurrent: params.assertCurrent
|
|
1158
|
+
});
|
|
1159
|
+
params.assertCurrent();
|
|
1160
|
+
if (desktopCandidates.length > 0 && service.status === "source_missing") throw new CodexComputerUseCandidateArtifactsUnavailableError();
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
params.assertCurrent();
|
|
1163
|
+
if (error instanceof CodexComputerUseCandidateArtifactsUnavailableError) throw error;
|
|
1164
|
+
throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
|
|
1165
|
+
cause: error,
|
|
1166
|
+
scope: "harness"
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
params.assertCurrent();
|
|
1171
|
+
const cacheBinding = [
|
|
1172
|
+
params.desktopGeneration?.epoch ?? "manual",
|
|
1173
|
+
artifactCandidate?.bundledMarketplacePath ?? "default",
|
|
1174
|
+
computerUseConfig.pluginName
|
|
1175
|
+
].join("\0");
|
|
1176
|
+
const cache = await ensureCodexComputerUseSharedPluginCache({
|
|
1177
|
+
codexHome,
|
|
1178
|
+
config: computerUseConfig,
|
|
1179
|
+
...ownsIsolatedCodexHome ? { ownershipRoot: params.agentDir } : {},
|
|
1180
|
+
...artifactCandidate ? { bundledMarketplacePath: artifactCandidate.bundledMarketplacePath } : {},
|
|
1181
|
+
assertCurrent: params.assertCurrent,
|
|
1182
|
+
forceRefresh: params.forceCacheRefresh === true || params.previousCacheBinding !== cacheBinding
|
|
1183
|
+
});
|
|
1184
|
+
params.assertCurrent();
|
|
1185
|
+
return cache.status === "shared" ? cacheBinding : void 0;
|
|
1186
|
+
}
|
|
1187
|
+
async function resolveCompleteComputerUseArtifactCandidate(params) {
|
|
1188
|
+
for (const candidate of params.candidates) {
|
|
1189
|
+
if (params.needsBundledMarketplace && !await resolveCodexManagedBundledMarketplaceSource({ candidates: [candidate] })) continue;
|
|
1190
|
+
if (await resolveCodexComputerUseServiceAppSourcePath({ sourceAppCandidates: candidate.computerUseServiceAppPaths })) return candidate;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
function pruneComputerUseArtifactOwners() {
|
|
1194
|
+
while (activeComputerUseArtifactReconciliations.size > MAX_COMPUTER_USE_ARTIFACT_OWNERS) {
|
|
1195
|
+
const inactive = [...activeComputerUseArtifactReconciliations].find(([, owner]) => owner.active === 0);
|
|
1196
|
+
if (!inactive) return;
|
|
1197
|
+
activeComputerUseArtifactReconciliations.delete(inactive[0]);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
var CodexComputerUseCandidateArtifactsUnavailableError = class extends Error {
|
|
1201
|
+
constructor() {
|
|
1202
|
+
super("The selected Codex desktop app does not contain complete Computer Use artifacts.");
|
|
1203
|
+
this.code = "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
|
|
1204
|
+
this.name = "CodexComputerUseCandidateArtifactsUnavailableError";
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1013
1207
|
function withoutClearedCodexHomeEnv(clearEnv) {
|
|
1014
1208
|
if (!clearEnv) return;
|
|
1015
1209
|
const reserved = new Set(CODEX_APP_SERVER_HOME_ENV_VARS);
|
|
@@ -1017,36 +1211,27 @@ function withoutClearedCodexHomeEnv(clearEnv) {
|
|
|
1017
1211
|
return filtered.length === clearEnv.length ? clearEnv : filtered;
|
|
1018
1212
|
}
|
|
1019
1213
|
async function applyCodexAppServerAuthProfile(params) {
|
|
1020
|
-
if (params.preparedAuth
|
|
1021
|
-
await params.client.request("account/login/start", params.preparedAuth.snapshot.loginParams);
|
|
1022
|
-
return;
|
|
1023
|
-
}
|
|
1024
|
-
if (params.preparedAuth?.kind === "api-key") {
|
|
1025
|
-
await params.client.request("account/login/start", {
|
|
1026
|
-
type: "apiKey",
|
|
1027
|
-
apiKey: params.preparedAuth.apiKey
|
|
1028
|
-
});
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
if (params.authProfileId === null) {
|
|
1214
|
+
if (!params.preparedAuth && params.authProfileId === null) {
|
|
1032
1215
|
await assertNativeCodexAccountMatchesRoute(params.client, params.authRequirement);
|
|
1033
1216
|
return;
|
|
1034
1217
|
}
|
|
1035
1218
|
let loginParams;
|
|
1036
1219
|
try {
|
|
1037
|
-
loginParams =
|
|
1220
|
+
loginParams = params.preparedAuth?.kind === "profile" ? params.preparedAuth.snapshot.loginParams : params.preparedAuth?.kind === "api-key" ? {
|
|
1221
|
+
type: "apiKey",
|
|
1222
|
+
apiKey: params.preparedAuth.apiKey
|
|
1223
|
+
} : await resolveCodexAppServerAuthProfileLoginParams({
|
|
1038
1224
|
agentDir: params.agentDir,
|
|
1039
|
-
authProfileId: params.authProfileId,
|
|
1225
|
+
authProfileId: params.authProfileId ?? void 0,
|
|
1040
1226
|
authProfileStore: params.authProfileStore,
|
|
1041
1227
|
config: params.config
|
|
1042
1228
|
});
|
|
1043
1229
|
} catch (error) {
|
|
1044
|
-
if (params.authRequirement === "subscription" && error instanceof CodexAppServerAuthProfileUnavailableError) throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.", error);
|
|
1230
|
+
if (params.authRequirement === "subscription" && error instanceof CodexAppServerAuthProfileUnavailableError) throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.", error);
|
|
1045
1231
|
throw error;
|
|
1046
1232
|
}
|
|
1047
|
-
if (params.authRequirement === "subscription" && loginParams?.type !== "chatgptAuthTokens") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
|
|
1233
|
+
if (params.authRequirement === "subscription" && loginParams?.type !== "chatgptAuthTokens") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.");
|
|
1048
1234
|
if (!loginParams) {
|
|
1049
|
-
if (params.authRequirement === "subscription") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
|
|
1050
1235
|
if (params.authRequirement !== "api-key" || params.startOptions?.transport !== "stdio") return;
|
|
1051
1236
|
const env = resolveCodexAppServerSpawnEnv(params.startOptions, process.env);
|
|
1052
1237
|
const fallbackLoginParams = await resolveCodexAppServerFallbackApiKeyLoginParams({
|
|
@@ -1071,7 +1256,7 @@ async function assertNativeCodexAccountMatchesRoute(client, authRequirement) {
|
|
|
1071
1256
|
const response = await client.request("account/read", { refreshToken: false });
|
|
1072
1257
|
const accountType = isJsonObject(response.account) ? response.account.type : void 0;
|
|
1073
1258
|
if (authRequirement === "subscription") {
|
|
1074
|
-
if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth
|
|
1259
|
+
if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription route requires ChatGPT auth in the native Codex home. Run `codex login` for that home, or use appServer.homeScope=\"agent\" with an OpenClaw OAuth profile, then retry.");
|
|
1075
1260
|
return;
|
|
1076
1261
|
}
|
|
1077
1262
|
if (accountType === "chatgpt") throw createCodexAppServerAuthError("Codex Platform route requires an API-key account, but the native Codex home is signed in with a ChatGPT subscription. Sign that home in with `codex login --with-api-key`, or set appServer.homeScope=\"agent\" so OpenClaw can inject its own key.");
|
|
@@ -1080,7 +1265,12 @@ function createCodexAppServerAuthError(message, cause) {
|
|
|
1080
1265
|
const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
|
|
1081
1266
|
return Object.assign(error, { status: 401 });
|
|
1082
1267
|
}
|
|
1083
|
-
var CodexAppServerAuthProfileUnavailableError = class extends Error {
|
|
1268
|
+
var CodexAppServerAuthProfileUnavailableError = class extends Error {
|
|
1269
|
+
constructor(..._args) {
|
|
1270
|
+
super(..._args);
|
|
1271
|
+
this.status = 401;
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1084
1274
|
async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
1085
1275
|
const store = resolveCodexAppServerAuthProfileStore(params);
|
|
1086
1276
|
const profileId = resolveCodexAppServerAuthProfileId({
|
|
@@ -1089,7 +1279,7 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
|
1089
1279
|
config: params.config
|
|
1090
1280
|
});
|
|
1091
1281
|
const profile = profileId ? store.profiles[profileId] : void 0;
|
|
1092
|
-
if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found.`);
|
|
1282
|
+
if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
|
|
1093
1283
|
if (profileId && profile && !isCodexAppServerAuthProfileCredential(profile)) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
|
|
1094
1284
|
return await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
1095
1285
|
...params,
|
|
@@ -1097,11 +1287,24 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
|
1097
1287
|
});
|
|
1098
1288
|
}
|
|
1099
1289
|
async function refreshCodexAppServerAuthTokens(params) {
|
|
1290
|
+
const previousAccountId = params.previousAccountId?.trim();
|
|
1291
|
+
if (previousAccountId) {
|
|
1292
|
+
const store = resolveCodexAppServerAuthProfileStore(params);
|
|
1293
|
+
const profileId = resolveCodexAppServerAuthProfileId({
|
|
1294
|
+
authProfileId: params.authProfileId,
|
|
1295
|
+
store,
|
|
1296
|
+
config: params.config
|
|
1297
|
+
});
|
|
1298
|
+
const credential = profileId ? store.profiles[profileId] : void 0;
|
|
1299
|
+
const selectedAccountId = credential ? resolveExplicitChatgptAccountId(credential) ?? (credential.type === "oauth" ? resolveOpenAICodexAuthIdentity({ access: credential.access }).accountId : void 0) : void 0;
|
|
1300
|
+
if (selectedAccountId && selectedAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh. Retry to start a client for the selected workspace.");
|
|
1301
|
+
}
|
|
1100
1302
|
const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
1101
1303
|
...params,
|
|
1102
1304
|
forceOAuthRefresh: true
|
|
1103
1305
|
});
|
|
1104
|
-
if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
|
|
1306
|
+
if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile. Sign in with `openclaw models auth login --provider openai`, select that profile, then retry.");
|
|
1307
|
+
if (previousAccountId && loginParams.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
|
|
1105
1308
|
return {
|
|
1106
1309
|
accessToken: loginParams.accessToken,
|
|
1107
1310
|
chatgptAccountId: loginParams.chatgptAccountId,
|
|
@@ -1122,7 +1325,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
|
|
|
1122
1325
|
});
|
|
1123
1326
|
if (!profileId) return;
|
|
1124
1327
|
const credential = store.profiles[profileId];
|
|
1125
|
-
if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found.`);
|
|
1328
|
+
if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
|
|
1126
1329
|
if (!isCodexAppServerAuthProfileCredential(credential)) throw new Error(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
|
|
1127
1330
|
const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
|
|
1128
1331
|
agentDir: params.agentDir,
|
|
@@ -1131,7 +1334,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
|
|
|
1131
1334
|
forceOAuthRefresh: params.forceOAuthRefresh === true,
|
|
1132
1335
|
config: params.config
|
|
1133
1336
|
});
|
|
1134
|
-
if (!loginParams) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" does not contain usable credentials.`);
|
|
1337
|
+
if (!loginParams) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" does not contain usable credentials. Repair or replace the selected OpenAI credential, then retry.`);
|
|
1135
1338
|
return loginParams;
|
|
1136
1339
|
}
|
|
1137
1340
|
async function resolveCodexAppServerFallbackApiKeyLoginParams(params) {
|
|
@@ -1245,7 +1448,7 @@ async function resolveOAuthCredentialForCodexAppServer(profileId, credential, pa
|
|
|
1245
1448
|
});
|
|
1246
1449
|
if (params.forceRefresh && !persistedOAuthCredential && overlaidOAuthCredential) {
|
|
1247
1450
|
const refreshedRuntimeCredential = await refreshOAuthCredentialForRuntime({ credential: overlaidOAuthCredential });
|
|
1248
|
-
if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh.`);
|
|
1451
|
+
if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
|
|
1249
1452
|
store.profiles[profileId] = refreshedRuntimeCredential;
|
|
1250
1453
|
return refreshedRuntimeCredential;
|
|
1251
1454
|
}
|
|
@@ -1273,8 +1476,8 @@ function shouldUseScopedOAuthCredential(params) {
|
|
|
1273
1476
|
return !isDeepStrictEqual(persisted, params.suppliedCredential) && !hasMatchingOAuthIdentity(persisted, params.suppliedCredential);
|
|
1274
1477
|
}
|
|
1275
1478
|
function hasMatchingOAuthIdentity(persisted, supplied) {
|
|
1276
|
-
const persistedAccountId = persisted.accountId?.trim();
|
|
1277
|
-
const suppliedAccountId = supplied.accountId?.trim();
|
|
1479
|
+
const persistedAccountId = resolveOpenAICodexAuthIdentity(persisted).accountId?.trim();
|
|
1480
|
+
const suppliedAccountId = resolveOpenAICodexAuthIdentity(supplied).accountId?.trim();
|
|
1278
1481
|
if (persistedAccountId && suppliedAccountId) return persistedAccountId === suppliedAccountId;
|
|
1279
1482
|
const persistedEmail = persisted.email?.trim().toLowerCase();
|
|
1280
1483
|
const suppliedEmail = supplied.email?.trim().toLowerCase();
|
|
@@ -1291,8 +1494,8 @@ async function resolveScopedOAuthCredential(params) {
|
|
|
1291
1494
|
const credential = current?.type === "oauth" ? current : params.credential;
|
|
1292
1495
|
if (!params.forceRefresh && hasUsableOAuthCredential(credential)) return credential;
|
|
1293
1496
|
const refreshed = await refreshOAuthCredentialForRuntime({ credential });
|
|
1294
|
-
if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh.`);
|
|
1295
|
-
if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing.`);
|
|
1497
|
+
if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
|
|
1498
|
+
if (!isDeepStrictEqual(params.store.profiles[params.profileId], credential)) throw new Error(`Codex app-server auth profile "${params.profileId}" changed while refreshing. Retry with the newly selected OpenAI profile.`);
|
|
1296
1499
|
params.store.profiles[params.profileId] = refreshed;
|
|
1297
1500
|
return refreshed;
|
|
1298
1501
|
})();
|
|
@@ -1339,10 +1542,15 @@ function readFirstNonEmptyEnvEntry(env, keys) {
|
|
|
1339
1542
|
}
|
|
1340
1543
|
}
|
|
1341
1544
|
function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
|
|
1545
|
+
const storedAccountId = resolveExplicitChatgptAccountId(credential);
|
|
1546
|
+
const tokenAccountId = resolveOpenAICodexAuthIdentity({ access: accessToken }).accountId;
|
|
1547
|
+
if (storedAccountId && tokenAccountId && storedAccountId !== tokenAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" has a different ChatGPT account ID than its access token. Sign in again before retrying.`);
|
|
1548
|
+
const chatgptAccountId = storedAccountId ?? tokenAccountId;
|
|
1549
|
+
if (!chatgptAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is missing its ChatGPT account ID. Sign in again before retrying.`);
|
|
1342
1550
|
return {
|
|
1343
1551
|
type: "chatgptAuthTokens",
|
|
1344
1552
|
accessToken,
|
|
1345
|
-
chatgptAccountId
|
|
1553
|
+
chatgptAccountId,
|
|
1346
1554
|
chatgptPlanType: resolveChatgptPlanType(credential)
|
|
1347
1555
|
};
|
|
1348
1556
|
}
|
|
@@ -1462,12 +1670,46 @@ function readLimitId(snapshot) {
|
|
|
1462
1670
|
return typeof value === "string" && value.trim() ? value.trim() : "codex";
|
|
1463
1671
|
}
|
|
1464
1672
|
//#endregion
|
|
1673
|
+
//#region extensions/codex/src/app-server/timeout.ts
|
|
1674
|
+
/**
|
|
1675
|
+
* Thin Codex app-server timeout adapter around OpenClaw's shared security
|
|
1676
|
+
* runtime timeout helper.
|
|
1677
|
+
*/
|
|
1678
|
+
function resolveAbortError(signal) {
|
|
1679
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
|
|
1680
|
+
}
|
|
1681
|
+
/** Awaits a promise with a Codex-specific timeout error message. */
|
|
1682
|
+
async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
|
|
1683
|
+
return await withTimeout(promise, timeoutMs, {
|
|
1684
|
+
message: timeoutMessage,
|
|
1685
|
+
...createError ? { createError } : {}
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
/** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
|
|
1689
|
+
async function withAbortableTimeout(params) {
|
|
1690
|
+
const signal = params.signal;
|
|
1691
|
+
if (signal?.aborted) throw resolveAbortError(signal);
|
|
1692
|
+
let removeAbortListener;
|
|
1693
|
+
const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
|
|
1694
|
+
const onAbort = () => reject(resolveAbortError(signal));
|
|
1695
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1696
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
1697
|
+
})]) : params.promise;
|
|
1698
|
+
try {
|
|
1699
|
+
return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
|
|
1700
|
+
} finally {
|
|
1701
|
+
removeAbortListener?.();
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
//#endregion
|
|
1465
1705
|
//#region extensions/codex/src/app-server/client-runtime.ts
|
|
1466
1706
|
/** Client-scoped Codex auth and account observers. */
|
|
1467
1707
|
/** Match Codex's native grace window without retaining inactive conversations indefinitely. */
|
|
1468
1708
|
const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
1469
1709
|
/** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
|
|
1470
1710
|
const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
|
|
1711
|
+
/** Return a deterministic error before Codex cancels its ten-second external-auth request. */
|
|
1712
|
+
const CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS = 9e3;
|
|
1471
1713
|
const configuredClients = /* @__PURE__ */ new WeakMap();
|
|
1472
1714
|
const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
|
|
1473
1715
|
const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
|
|
@@ -1481,7 +1723,10 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1481
1723
|
const existing = configuredClients.get(client);
|
|
1482
1724
|
if (existing) {
|
|
1483
1725
|
if (existing.closed) return;
|
|
1484
|
-
existing.context =
|
|
1726
|
+
existing.context = {
|
|
1727
|
+
...existing.context,
|
|
1728
|
+
config: context.config
|
|
1729
|
+
};
|
|
1485
1730
|
return;
|
|
1486
1731
|
}
|
|
1487
1732
|
const runtime = {
|
|
@@ -1506,12 +1751,21 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1506
1751
|
client.addRequestHandler(async (request) => {
|
|
1507
1752
|
if (request.method !== "account/chatgptAuthTokens/refresh") return;
|
|
1508
1753
|
if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1754
|
+
const previousAccountId = isJsonObject(request.params) && typeof request.params.previousAccountId === "string" ? request.params.previousAccountId.trim() || void 0 : void 0;
|
|
1755
|
+
try {
|
|
1756
|
+
const tokens = await withTimeout$1(refreshCodexAppServerAuthTokens({
|
|
1757
|
+
agentDir: runtime.context.agentDir,
|
|
1758
|
+
authProfileId: runtime.context.authProfileId,
|
|
1759
|
+
...previousAccountId ? { previousAccountId } : {},
|
|
1760
|
+
...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
|
|
1761
|
+
config: runtime.context.config
|
|
1762
|
+
}), CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS, "Codex app-server ChatGPT token refresh timed out before its external-auth deadline. Retry the request; if it persists, sign in again with OpenClaw.");
|
|
1763
|
+
if (previousAccountId && tokens.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
|
|
1764
|
+
return { ...tokens };
|
|
1765
|
+
} catch (error) {
|
|
1766
|
+
runtime.context.onAuthRefreshFailure?.();
|
|
1767
|
+
throw error;
|
|
1768
|
+
}
|
|
1515
1769
|
});
|
|
1516
1770
|
client.addNotificationHandler((notification) => {
|
|
1517
1771
|
if (notification.method === "account/rateLimits/updated") {
|
|
@@ -1547,10 +1801,11 @@ function scheduleRetainedThreadEviction(client, runtime) {
|
|
|
1547
1801
|
}, Math.max(0, expiresAt - Date.now()));
|
|
1548
1802
|
runtime.evictionTimer.unref?.();
|
|
1549
1803
|
}
|
|
1550
|
-
async function releaseRetainedThread(client, runtime, threadId) {
|
|
1804
|
+
async function releaseRetainedThread(client, runtime, threadId, assertCurrent) {
|
|
1551
1805
|
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1552
1806
|
if (pendingRelease) {
|
|
1553
1807
|
await pendingRelease.completion;
|
|
1808
|
+
assertCurrent?.();
|
|
1554
1809
|
return false;
|
|
1555
1810
|
}
|
|
1556
1811
|
const retained = runtime.retainedThreads.get(threadId);
|
|
@@ -1562,7 +1817,10 @@ async function releaseRetainedThread(client, runtime, threadId) {
|
|
|
1562
1817
|
}
|
|
1563
1818
|
runtime.retainedThreads.delete(threadId);
|
|
1564
1819
|
scheduleRetainedThreadEviction(client, runtime);
|
|
1565
|
-
const transition = { completion: Promise.resolve().then(() =>
|
|
1820
|
+
const transition = { completion: Promise.resolve().then(() => {
|
|
1821
|
+
assertCurrent?.();
|
|
1822
|
+
return assertCurrent ? retained.release(threadId, assertCurrent) : retained.release(threadId);
|
|
1823
|
+
}) };
|
|
1566
1824
|
runtime.releasingThreads.set(threadId, transition);
|
|
1567
1825
|
try {
|
|
1568
1826
|
await transition.completion;
|
|
@@ -1602,15 +1860,15 @@ async function retainCodexAppServerLiveThread(client, threadId, releaseThread, c
|
|
|
1602
1860
|
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1603
1861
|
if (pendingRelease) {
|
|
1604
1862
|
await pendingRelease.completion;
|
|
1605
|
-
|
|
1863
|
+
return false;
|
|
1606
1864
|
}
|
|
1607
1865
|
runtime.retainedThreads.delete(threadId);
|
|
1608
1866
|
const retained = {
|
|
1609
1867
|
configFingerprint,
|
|
1610
1868
|
serviceTier,
|
|
1611
1869
|
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
|
|
1612
|
-
release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId) => {
|
|
1613
|
-
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
|
|
1870
|
+
release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId, assertCurrent) => {
|
|
1871
|
+
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
|
|
1614
1872
|
})
|
|
1615
1873
|
};
|
|
1616
1874
|
runtime.retainedThreads.set(threadId, retained);
|
|
@@ -1654,8 +1912,8 @@ async function claimCodexAppServerLiveThread(client, threadId) {
|
|
|
1654
1912
|
}
|
|
1655
1913
|
return claimCodexAppServerThreadOwnership(client, runtime, threadId, runtime.retainedThreads.get(threadId) ?? {
|
|
1656
1914
|
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
|
|
1657
|
-
release: async (releasedThreadId) => {
|
|
1658
|
-
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
|
|
1915
|
+
release: async (releasedThreadId, assertCurrent) => {
|
|
1916
|
+
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
|
|
1659
1917
|
}
|
|
1660
1918
|
});
|
|
1661
1919
|
}
|
|
@@ -1664,7 +1922,7 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
|
|
|
1664
1922
|
const claimed = Symbol(threadId);
|
|
1665
1923
|
runtime.claimedThreads.set(threadId, claimed);
|
|
1666
1924
|
scheduleRetainedThreadEviction(client, runtime);
|
|
1667
|
-
const release = async (releasedThreadId) => {
|
|
1925
|
+
const release = async (releasedThreadId, assertCurrent) => {
|
|
1668
1926
|
if (releasedThreadId !== threadId || runtime.claimedThreads.get(threadId) !== claimed) return;
|
|
1669
1927
|
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1670
1928
|
if (pendingRelease) {
|
|
@@ -1673,7 +1931,8 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
|
|
|
1673
1931
|
}
|
|
1674
1932
|
const transition = { completion: Promise.resolve().then(async () => {
|
|
1675
1933
|
if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) return;
|
|
1676
|
-
|
|
1934
|
+
assertCurrent?.();
|
|
1935
|
+
await (assertCurrent ? retained.release(releasedThreadId, assertCurrent) : retained.release(releasedThreadId));
|
|
1677
1936
|
}) };
|
|
1678
1937
|
runtime.releasingThreads.set(threadId, transition);
|
|
1679
1938
|
try {
|
|
@@ -1692,22 +1951,32 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
|
|
|
1692
1951
|
};
|
|
1693
1952
|
}
|
|
1694
1953
|
/** Distinguish active claimed ownership from an already-evicted idle subscription. */
|
|
1954
|
+
function hasCodexAppServerLiveThread(client, threadId) {
|
|
1955
|
+
const runtime = configuredClients.get(client);
|
|
1956
|
+
return runtime !== void 0 && !runtime.closed && (runtime.retainedThreads.has(threadId) || runtime.releasingThreads.has(threadId) || runtime.claimedThreads.has(threadId));
|
|
1957
|
+
}
|
|
1695
1958
|
function isCodexAppServerLiveThreadClaimed(client, threadId) {
|
|
1696
1959
|
const runtime = configuredClients.get(client);
|
|
1697
1960
|
return runtime !== void 0 && !runtime.closed && runtime.claimedThreads.has(threadId);
|
|
1698
1961
|
}
|
|
1699
|
-
/** Release the exact physical subscription and finish only its observed
|
|
1700
|
-
async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs) {
|
|
1962
|
+
/** Release the exact physical subscription and finish only its observed ownership generation. */
|
|
1963
|
+
async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs, assertCurrent) {
|
|
1701
1964
|
const runtime = configuredClients.get(client);
|
|
1702
1965
|
const claimed = runtime?.claimedThreads.get(threadId);
|
|
1966
|
+
const retained = runtime?.retainedThreads.get(threadId);
|
|
1703
1967
|
let transition = runtime?.releasingThreads.get(threadId);
|
|
1704
1968
|
if (transition?.physicalRelease) {
|
|
1705
1969
|
await transition.physicalRelease;
|
|
1970
|
+
assertCurrent?.();
|
|
1706
1971
|
return;
|
|
1707
1972
|
}
|
|
1708
1973
|
const physicalRelease = Promise.resolve().then(async () => {
|
|
1709
|
-
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed) return;
|
|
1710
|
-
|
|
1974
|
+
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed || retained !== void 0 && runtime?.retainedThreads.get(threadId) !== retained) return;
|
|
1975
|
+
assertCurrent?.();
|
|
1976
|
+
await client.request("thread/unsubscribe", { threadId }, {
|
|
1977
|
+
timeoutMs,
|
|
1978
|
+
assertCurrent
|
|
1979
|
+
});
|
|
1711
1980
|
});
|
|
1712
1981
|
const ownsTransition = runtime !== void 0 && transition === void 0;
|
|
1713
1982
|
if (transition) transition.physicalRelease = physicalRelease;
|
|
@@ -1720,15 +1989,19 @@ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs)
|
|
|
1720
1989
|
}
|
|
1721
1990
|
try {
|
|
1722
1991
|
await physicalRelease;
|
|
1992
|
+
if (retained !== void 0 && runtime?.retainedThreads.get(threadId) === retained) {
|
|
1993
|
+
runtime.retainedThreads.delete(threadId);
|
|
1994
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1995
|
+
}
|
|
1723
1996
|
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
|
|
1724
1997
|
} finally {
|
|
1725
1998
|
if (ownsTransition && runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
|
|
1726
1999
|
}
|
|
1727
2000
|
}
|
|
1728
2001
|
/** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
|
|
1729
|
-
async function releaseCodexAppServerLiveThread(client, threadId) {
|
|
2002
|
+
async function releaseCodexAppServerLiveThread(client, threadId, assertCurrent) {
|
|
1730
2003
|
const runtime = configuredClients.get(client);
|
|
1731
|
-
return runtime ? await releaseRetainedThread(client, runtime, threadId) : false;
|
|
2004
|
+
return runtime ? await releaseRetainedThread(client, runtime, threadId, assertCurrent) : false;
|
|
1732
2005
|
}
|
|
1733
2006
|
/** Native child work pins its parent's subscription even after the foreground parent turn ends. */
|
|
1734
2007
|
function protectCodexAppServerLiveThread(client, threadId) {
|
|
@@ -1746,11 +2019,9 @@ function protectCodexAppServerLiveThread(client, threadId) {
|
|
|
1746
2019
|
runtime.protectedThreads.delete(threadId);
|
|
1747
2020
|
const retained = runtime.retainedThreads.get(threadId);
|
|
1748
2021
|
if (retained) {
|
|
2022
|
+
retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
|
|
1749
2023
|
runtime.retainedThreads.delete(threadId);
|
|
1750
|
-
runtime.retainedThreads.set(threadId,
|
|
1751
|
-
...retained,
|
|
1752
|
-
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS
|
|
1753
|
-
});
|
|
2024
|
+
runtime.retainedThreads.set(threadId, retained);
|
|
1754
2025
|
}
|
|
1755
2026
|
} else runtime.protectedThreads.set(threadId, count - 1);
|
|
1756
2027
|
scheduleRetainedThreadEviction(client, runtime);
|
|
@@ -1763,6 +2034,453 @@ function protectCodexAppServerLiveThread(client, threadId) {
|
|
|
1763
2034
|
};
|
|
1764
2035
|
}
|
|
1765
2036
|
//#endregion
|
|
2037
|
+
//#region extensions/codex/src/app-server/dynamic-tool-response-state.ts
|
|
2038
|
+
/** Retains the host-owned app preview without adding it to Codex's response payload. */
|
|
2039
|
+
function withDynamicToolTranscriptDetails(response, details) {
|
|
2040
|
+
if (details === void 0) return response;
|
|
2041
|
+
Object.defineProperty(response, "transcriptDetails", {
|
|
2042
|
+
configurable: true,
|
|
2043
|
+
enumerable: false,
|
|
2044
|
+
value: details
|
|
2045
|
+
});
|
|
2046
|
+
return response;
|
|
2047
|
+
}
|
|
2048
|
+
function withDynamicToolTerminalResolution(response, terminalResolution) {
|
|
2049
|
+
if (terminalResolution) {
|
|
2050
|
+
Object.defineProperties(response, {
|
|
2051
|
+
terminalResolution: {
|
|
2052
|
+
configurable: true,
|
|
2053
|
+
enumerable: false,
|
|
2054
|
+
value: terminalResolution
|
|
2055
|
+
},
|
|
2056
|
+
executionStarted: {
|
|
2057
|
+
configurable: true,
|
|
2058
|
+
enumerable: false,
|
|
2059
|
+
value: terminalResolution.executionStarted
|
|
2060
|
+
},
|
|
2061
|
+
...terminalResolution.executedArguments ? { executedArguments: {
|
|
2062
|
+
configurable: true,
|
|
2063
|
+
enumerable: false,
|
|
2064
|
+
value: terminalResolution.executedArguments
|
|
2065
|
+
} } : {}
|
|
2066
|
+
});
|
|
2067
|
+
withDynamicToolSideEffectEvidence(response, terminalResolution.sideEffectEvidence);
|
|
2068
|
+
}
|
|
2069
|
+
return response;
|
|
2070
|
+
}
|
|
2071
|
+
function withDynamicToolExecutionState(response, state) {
|
|
2072
|
+
Object.defineProperties(response, {
|
|
2073
|
+
executedArguments: {
|
|
2074
|
+
configurable: true,
|
|
2075
|
+
enumerable: false,
|
|
2076
|
+
value: state.executedArguments
|
|
2077
|
+
},
|
|
2078
|
+
executionStarted: {
|
|
2079
|
+
configurable: true,
|
|
2080
|
+
enumerable: false,
|
|
2081
|
+
value: state.executionStarted
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
return withDynamicToolSideEffectEvidence(response, state.sideEffectEvidence === true);
|
|
2085
|
+
}
|
|
2086
|
+
function withDynamicToolSideEffectEvidence(response, sideEffectEvidence) {
|
|
2087
|
+
if (!sideEffectEvidence) {
|
|
2088
|
+
delete response.sideEffectEvidence;
|
|
2089
|
+
return response;
|
|
2090
|
+
}
|
|
2091
|
+
Object.defineProperty(response, "sideEffectEvidence", {
|
|
2092
|
+
configurable: true,
|
|
2093
|
+
enumerable: false,
|
|
2094
|
+
value: true
|
|
2095
|
+
});
|
|
2096
|
+
return response;
|
|
2097
|
+
}
|
|
2098
|
+
function createFailedDynamicToolResponse(message, options) {
|
|
2099
|
+
const response = {
|
|
2100
|
+
contentItems: [{
|
|
2101
|
+
type: "inputText",
|
|
2102
|
+
text: message
|
|
2103
|
+
}],
|
|
2104
|
+
success: false
|
|
2105
|
+
};
|
|
2106
|
+
Object.defineProperties(response, {
|
|
2107
|
+
diagnosticTerminalReason: {
|
|
2108
|
+
configurable: true,
|
|
2109
|
+
enumerable: false,
|
|
2110
|
+
value: options?.terminalReason ?? "failed"
|
|
2111
|
+
},
|
|
2112
|
+
diagnosticTerminalType: {
|
|
2113
|
+
configurable: true,
|
|
2114
|
+
enumerable: false,
|
|
2115
|
+
value: "error"
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
if (options?.executionStarted !== void 0) Object.defineProperty(response, "executionStarted", {
|
|
2119
|
+
configurable: true,
|
|
2120
|
+
enumerable: false,
|
|
2121
|
+
value: options.executionStarted
|
|
2122
|
+
});
|
|
2123
|
+
if (options?.executedArguments !== void 0) Object.defineProperty(response, "executedArguments", {
|
|
2124
|
+
configurable: true,
|
|
2125
|
+
enumerable: false,
|
|
2126
|
+
value: options.executedArguments
|
|
2127
|
+
});
|
|
2128
|
+
return withDynamicToolSideEffectEvidence(response, options?.sideEffectEvidence === true);
|
|
2129
|
+
}
|
|
2130
|
+
//#endregion
|
|
2131
|
+
//#region extensions/codex/src/app-server/tool-abort-terminal-reason.ts
|
|
2132
|
+
/** Leaf helper shared by native and dynamic tool diagnostics. */
|
|
2133
|
+
const CODEX_TIMEOUT_ABORT_REASONS = /* @__PURE__ */ new Set([
|
|
2134
|
+
"codex_startup_timeout",
|
|
2135
|
+
"turn_completion_idle_timeout",
|
|
2136
|
+
"turn_progress_idle_timeout",
|
|
2137
|
+
"turn_terminal_idle_timeout"
|
|
2138
|
+
]);
|
|
2139
|
+
/** Preserves timeout provenance when an enclosing run aborts an active tool. */
|
|
2140
|
+
function resolveCodexToolAbortTerminalReason(signal) {
|
|
2141
|
+
try {
|
|
2142
|
+
const reason = signal.reason;
|
|
2143
|
+
if (typeof reason === "string") {
|
|
2144
|
+
if (CODEX_TIMEOUT_ABORT_REASONS.has(reason)) return "timed_out";
|
|
2145
|
+
return reason === "client_closed" ? "failed" : "cancelled";
|
|
2146
|
+
}
|
|
2147
|
+
if (reason && typeof reason === "object") {
|
|
2148
|
+
const record = reason;
|
|
2149
|
+
if (record.name === "TimeoutError" || record.reason === "timeout") return "timed_out";
|
|
2150
|
+
}
|
|
2151
|
+
} catch {
|
|
2152
|
+
return "cancelled";
|
|
2153
|
+
}
|
|
2154
|
+
return "cancelled";
|
|
2155
|
+
}
|
|
2156
|
+
//#endregion
|
|
2157
|
+
//#region extensions/codex/src/app-server/dynamic-tool-execution.ts
|
|
2158
|
+
/**
|
|
2159
|
+
* Timeout, terminal-release, and diagnostic helpers for Codex dynamic tool
|
|
2160
|
+
* calls.
|
|
2161
|
+
*/
|
|
2162
|
+
/** Default timeout for Codex dynamic tool calls. */
|
|
2163
|
+
const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 9e4;
|
|
2164
|
+
/** Hard cap for per-call Codex dynamic tool timeout overrides. */
|
|
2165
|
+
const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
|
|
2166
|
+
const CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS = 3e4;
|
|
2167
|
+
const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
|
|
2168
|
+
const CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS = 3e4;
|
|
2169
|
+
const CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS = 3e4;
|
|
2170
|
+
/** Timeout for image-understanding style dynamic tool calls. */
|
|
2171
|
+
const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
|
|
2172
|
+
/** Timeout for message-delivery dynamic tool calls. */
|
|
2173
|
+
const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 6e5;
|
|
2174
|
+
/** Outer default for collector waits: full swarm budget plus completion grace. */
|
|
2175
|
+
const CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS = 63e4;
|
|
2176
|
+
const LOG_FIELD_MAX_LENGTH = 160;
|
|
2177
|
+
function normalizeLogField(value) {
|
|
2178
|
+
if (typeof value !== "string") return;
|
|
2179
|
+
const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
|
|
2180
|
+
if (!normalized) return;
|
|
2181
|
+
return normalized.length > LOG_FIELD_MAX_LENGTH ? `${truncateUtf16Safe(normalized, LOG_FIELD_MAX_LENGTH - 3)}...` : normalized;
|
|
2182
|
+
}
|
|
2183
|
+
function readNumericTimeoutMs(value) {
|
|
2184
|
+
if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
|
|
2185
|
+
if (typeof value === "string") {
|
|
2186
|
+
const parsed = parseStrictNonNegativeInteger(value);
|
|
2187
|
+
if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
function formatDynamicToolTimeoutDetails(params) {
|
|
2191
|
+
const tool = normalizeLogField(params.call.tool) ?? "unknown";
|
|
2192
|
+
const baseMeta = {
|
|
2193
|
+
tool: params.call.tool,
|
|
2194
|
+
toolCallId: params.call.callId,
|
|
2195
|
+
threadId: params.call.threadId,
|
|
2196
|
+
turnId: params.call.turnId,
|
|
2197
|
+
timeoutMs: params.timeoutMs,
|
|
2198
|
+
timeoutKind: "codex_dynamic_tool_rpc"
|
|
2199
|
+
};
|
|
2200
|
+
if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
|
|
2201
|
+
responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
|
|
2202
|
+
consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
|
|
2203
|
+
meta: baseMeta
|
|
2204
|
+
};
|
|
2205
|
+
const action = normalizeLogField(params.call.arguments.action);
|
|
2206
|
+
const sessionId = normalizeLogField(params.call.arguments.sessionId);
|
|
2207
|
+
const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
|
|
2208
|
+
const actionPart = action ? ` action=${action}` : "";
|
|
2209
|
+
const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
|
|
2210
|
+
const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
|
|
2211
|
+
const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
|
|
2212
|
+
const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
|
|
2213
|
+
return {
|
|
2214
|
+
responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
|
|
2215
|
+
consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
|
|
2216
|
+
meta: {
|
|
2217
|
+
...baseMeta,
|
|
2218
|
+
processAction: action,
|
|
2219
|
+
processSessionId: sessionId,
|
|
2220
|
+
processRequestedTimeoutMs: requestedTimeoutMs
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Runs a dynamic tool call with run-abort and the budget prepared by
|
|
2226
|
+
* resolveDynamicToolCallTimeoutMs, preserving tool-specific completion grace.
|
|
2227
|
+
*/
|
|
2228
|
+
async function handleDynamicToolCallWithTimeout(params) {
|
|
2229
|
+
let didNotifyAgentToolResult = false;
|
|
2230
|
+
const conservativeRaceResponses = /* @__PURE__ */ new WeakSet();
|
|
2231
|
+
const finalizeTerminal = (response) => {
|
|
2232
|
+
const executionSnapshot = params.toolBridge.consumeToolExecutionSnapshot?.(params.call.callId);
|
|
2233
|
+
const ownerKey = params.toolBridge.sideEffectOwnerKeyForTool?.(params.call.tool);
|
|
2234
|
+
const observedExecutionStarted = executionSnapshot?.executionStarted ?? (conservativeRaceResponses.has(response) ? void 0 : response.executionStarted);
|
|
2235
|
+
const terminalResolution = params.observeToolTerminal?.({
|
|
2236
|
+
toolCallId: params.call.callId,
|
|
2237
|
+
toolName: params.call.tool,
|
|
2238
|
+
arguments: response.executedArguments ?? executionSnapshot?.executedArguments ?? params.call.arguments,
|
|
2239
|
+
...params.toolMeta ? { meta: params.toolMeta } : {},
|
|
2240
|
+
...ownerKey ? { ownerMutation: { ownerKey } } : {},
|
|
2241
|
+
...observedExecutionStarted !== void 0 ? { executionStarted: observedExecutionStarted } : {},
|
|
2242
|
+
outcome: response.success ? "success" : "failure",
|
|
2243
|
+
...!response.success ? { failure: { error: readDynamicToolResponseText(response) } } : {}
|
|
2244
|
+
});
|
|
2245
|
+
return withDynamicToolTerminalResolution(response, terminalResolution);
|
|
2246
|
+
};
|
|
2247
|
+
const createFailedAfterPossibleDispatch = (message, terminalReason) => {
|
|
2248
|
+
const response = createFailedDynamicToolResponse(message, {
|
|
2249
|
+
executionStarted: true,
|
|
2250
|
+
sideEffectEvidence: true,
|
|
2251
|
+
terminalReason
|
|
2252
|
+
});
|
|
2253
|
+
conservativeRaceResponses.add(response);
|
|
2254
|
+
return response;
|
|
2255
|
+
};
|
|
2256
|
+
const notifyAgentToolResult = (event) => {
|
|
2257
|
+
if (didNotifyAgentToolResult) return;
|
|
2258
|
+
didNotifyAgentToolResult = true;
|
|
2259
|
+
try {
|
|
2260
|
+
params.onAgentToolResult?.(event);
|
|
2261
|
+
} catch (error) {
|
|
2262
|
+
embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
|
|
2263
|
+
}
|
|
2264
|
+
};
|
|
2265
|
+
const notifyFailedToolResult = (message, terminalReason = "failed") => {
|
|
2266
|
+
notifyAgentToolResult({
|
|
2267
|
+
toolName: params.call.tool,
|
|
2268
|
+
result: {
|
|
2269
|
+
content: [{
|
|
2270
|
+
type: "text",
|
|
2271
|
+
text: message
|
|
2272
|
+
}],
|
|
2273
|
+
details: {
|
|
2274
|
+
status: terminalReason,
|
|
2275
|
+
error: message
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
isError: true
|
|
2279
|
+
});
|
|
2280
|
+
};
|
|
2281
|
+
if (params.signal.aborted) {
|
|
2282
|
+
const message = "OpenClaw dynamic tool call aborted before execution.";
|
|
2283
|
+
const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
|
|
2284
|
+
params.onFallbackSelected?.();
|
|
2285
|
+
notifyFailedToolResult(message, terminalReason);
|
|
2286
|
+
return finalizeTerminal(createFailedDynamicToolResponse(message, {
|
|
2287
|
+
executionStarted: false,
|
|
2288
|
+
terminalReason
|
|
2289
|
+
}));
|
|
2290
|
+
}
|
|
2291
|
+
const controller = new AbortController();
|
|
2292
|
+
let timeout;
|
|
2293
|
+
let timedOut = false;
|
|
2294
|
+
let resolveAbort;
|
|
2295
|
+
const abortFromRun = () => {
|
|
2296
|
+
const message = "OpenClaw dynamic tool call aborted.";
|
|
2297
|
+
const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
|
|
2298
|
+
params.onFallbackSelected?.();
|
|
2299
|
+
controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
|
|
2300
|
+
notifyFailedToolResult(message, terminalReason);
|
|
2301
|
+
resolveAbort?.(createFailedAfterPossibleDispatch(message, terminalReason));
|
|
2302
|
+
};
|
|
2303
|
+
const abortPromise = new Promise((resolve) => {
|
|
2304
|
+
resolveAbort = resolve;
|
|
2305
|
+
});
|
|
2306
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
2307
|
+
const { timeoutMs } = params;
|
|
2308
|
+
timeout = setTimeout(() => {
|
|
2309
|
+
timedOut = true;
|
|
2310
|
+
const timeoutDetails = formatDynamicToolTimeoutDetails({
|
|
2311
|
+
call: params.call,
|
|
2312
|
+
timeoutMs
|
|
2313
|
+
});
|
|
2314
|
+
params.onFallbackSelected?.();
|
|
2315
|
+
controller.abort(new Error(timeoutDetails.responseMessage));
|
|
2316
|
+
params.onTimeout?.();
|
|
2317
|
+
embeddedAgentLog.warn("codex dynamic tool call timed out", {
|
|
2318
|
+
...timeoutDetails.meta,
|
|
2319
|
+
consoleMessage: timeoutDetails.consoleMessage
|
|
2320
|
+
});
|
|
2321
|
+
notifyFailedToolResult(timeoutDetails.responseMessage, "timed_out");
|
|
2322
|
+
resolve(createFailedAfterPossibleDispatch(timeoutDetails.responseMessage, "timed_out"));
|
|
2323
|
+
}, timeoutMs);
|
|
2324
|
+
timeout.unref?.();
|
|
2325
|
+
});
|
|
2326
|
+
try {
|
|
2327
|
+
params.signal.addEventListener("abort", abortFromRun, { once: true });
|
|
2328
|
+
if (params.signal.aborted) abortFromRun();
|
|
2329
|
+
const response = await Promise.race([
|
|
2330
|
+
params.toolBridge.handleToolCall(params.call, {
|
|
2331
|
+
signal: controller.signal,
|
|
2332
|
+
onAgentToolResult: notifyAgentToolResult,
|
|
2333
|
+
toolCallOrdinal: params.toolCallOrdinal,
|
|
2334
|
+
retainExecutionSnapshot: true
|
|
2335
|
+
}),
|
|
2336
|
+
abortPromise,
|
|
2337
|
+
timeoutPromise
|
|
2338
|
+
]);
|
|
2339
|
+
if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response), response.diagnosticTerminalReason ?? "failed");
|
|
2340
|
+
return finalizeTerminal(response);
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
const terminalReason = params.signal.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : resolveToolExecutionErrorKind(error);
|
|
2343
|
+
const message = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
|
|
2344
|
+
notifyFailedToolResult(message, terminalReason);
|
|
2345
|
+
return finalizeTerminal(createFailedAfterPossibleDispatch(message, terminalReason));
|
|
2346
|
+
} finally {
|
|
2347
|
+
if (timeout) clearTimeout(timeout);
|
|
2348
|
+
params.signal.removeEventListener("abort", abortFromRun);
|
|
2349
|
+
resolveAbort = void 0;
|
|
2350
|
+
if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function readDynamicToolResponseText(response) {
|
|
2354
|
+
return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
|
|
2355
|
+
}
|
|
2356
|
+
/** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
|
|
2357
|
+
function toCodexDynamicToolProtocolResponse(response) {
|
|
2358
|
+
return {
|
|
2359
|
+
contentItems: response.contentItems,
|
|
2360
|
+
success: response.success
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
/** Adds async-started progress details when a tool result continues out of band. */
|
|
2364
|
+
function toCodexDynamicToolProgressResponse(response, protocolResponse) {
|
|
2365
|
+
const transcriptDetails = isJsonObject(response.transcriptDetails) ? response.transcriptDetails : void 0;
|
|
2366
|
+
const mcpAppPreview = isJsonObject(transcriptDetails?.mcpAppPreview) ? transcriptDetails.mcpAppPreview : void 0;
|
|
2367
|
+
const progressDetails = mcpAppPreview ? { mcpAppPreview } : void 0;
|
|
2368
|
+
if (response.asyncStarted !== true && progressDetails === void 0) return protocolResponse;
|
|
2369
|
+
return {
|
|
2370
|
+
...protocolResponse,
|
|
2371
|
+
...progressDetails ? { details: progressDetails } : {},
|
|
2372
|
+
...response.asyncStarted === true ? { details: {
|
|
2373
|
+
...progressDetails,
|
|
2374
|
+
async: true,
|
|
2375
|
+
status: "started"
|
|
2376
|
+
} } : {}
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
/** Decides whether a terminal dynamic tool response can release the Codex turn. */
|
|
2380
|
+
function shouldReleaseTurnAfterTerminalDynamicTool(state) {
|
|
2381
|
+
return !state.completed && !state.aborted && state.responseSuccess && !state.currentTurnHadNonTerminalDynamicToolResult && state.activeAppServerTurnRequests === 0 && state.activeTurnItemIdsCount === 0 && state.pendingOpenClawDynamicToolCompletionIdsCount === 0;
|
|
2382
|
+
}
|
|
2383
|
+
/** Returns true when a non-async result should block terminal-release shortcuts. */
|
|
2384
|
+
function shouldBlockTerminalReleaseForNonTerminalDynamicToolResult(response) {
|
|
2385
|
+
return response.asyncStarted !== true;
|
|
2386
|
+
}
|
|
2387
|
+
/** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
|
|
2388
|
+
function resolveTerminalDynamicToolBatchAction(state) {
|
|
2389
|
+
if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
|
|
2390
|
+
if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
|
|
2391
|
+
if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
|
|
2392
|
+
return "idle";
|
|
2393
|
+
}
|
|
2394
|
+
/** Returns true for diagnostic events that terminate a dynamic tool call. */
|
|
2395
|
+
function isDynamicToolTerminalDiagnosticEvent(event) {
|
|
2396
|
+
return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
|
|
2397
|
+
}
|
|
2398
|
+
/** Matches terminal diagnostics to a specific dynamic tool call id/name. */
|
|
2399
|
+
function isMatchingDynamicToolTerminalDiagnostic(params) {
|
|
2400
|
+
if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
|
|
2401
|
+
if (params.runId !== void 0) return params.event.runId === params.runId;
|
|
2402
|
+
if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
|
|
2403
|
+
if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
|
|
2404
|
+
return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
|
|
2405
|
+
}
|
|
2406
|
+
/** Checks pending diagnostics for a terminal event matching a tool call. */
|
|
2407
|
+
function hasPendingDynamicToolTerminalDiagnostic(params) {
|
|
2408
|
+
return hasPendingInternalDiagnosticEvent((event) => {
|
|
2409
|
+
if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
|
|
2410
|
+
return isMatchingDynamicToolTerminalDiagnostic({
|
|
2411
|
+
event,
|
|
2412
|
+
call: params.call,
|
|
2413
|
+
runId: params.runId,
|
|
2414
|
+
sessionId: params.sessionId,
|
|
2415
|
+
sessionKey: params.sessionKey
|
|
2416
|
+
});
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
/** Resolves per-tool timeout, applying media/message defaults and hard caps. */
|
|
2420
|
+
function resolveDynamicToolCallTimeoutMs(params) {
|
|
2421
|
+
const args = isJsonObject(params.call.arguments) ? params.call.arguments : void 0;
|
|
2422
|
+
if (params.call.tool === "ask_user" || params.call.tool === "secrets" && args?.action === "request") try {
|
|
2423
|
+
return normalizeQuestionTimeoutSeconds(args?.timeoutSeconds) * 1e3 + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
|
|
2424
|
+
} catch {
|
|
2425
|
+
return CODEX_DYNAMIC_TOOL_TIMEOUT_MS;
|
|
2426
|
+
}
|
|
2427
|
+
if (params.call.tool === "computer") return clampDynamicToolTimeoutMs(readComputerToolTimeoutMs(params.call.arguments));
|
|
2428
|
+
if (params.call.tool === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
|
|
2429
|
+
if (params.call.tool === "agents_wait") {
|
|
2430
|
+
const requestedMs = readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS;
|
|
2431
|
+
return Math.max(1, Math.min(63e4, Math.floor(requestedMs)));
|
|
2432
|
+
}
|
|
2433
|
+
return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_TOOL_TIMEOUT_MS);
|
|
2434
|
+
}
|
|
2435
|
+
/** Transport guard stays outside the handler's bounded tool wait and completion grace. */
|
|
2436
|
+
function resolveDynamicToolServerRequestTimeoutMs(call) {
|
|
2437
|
+
return Math.max(63e4, call ? resolveDynamicToolCallTimeoutMs({
|
|
2438
|
+
call,
|
|
2439
|
+
config: void 0
|
|
2440
|
+
}) : 0) + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
|
|
2441
|
+
}
|
|
2442
|
+
function readComputerToolTimeoutMs(value) {
|
|
2443
|
+
const args = isJsonObject(value) ? value : void 0;
|
|
2444
|
+
const action = typeof args?.action === "string" ? args.action : void 0;
|
|
2445
|
+
const gatewayTimeoutMs = readPositiveFiniteTimeoutMs(args?.timeoutMs) ?? CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS;
|
|
2446
|
+
const gatewayCallCount = action === "screenshot" || action === "wait" ? 3 : 4;
|
|
2447
|
+
return (action === "wait" || action === "hold_key" ? Math.max(0, Number(args?.duration) || 0) * 1e3 : 0) + gatewayCallCount * gatewayTimeoutMs + CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS;
|
|
2448
|
+
}
|
|
2449
|
+
function readDynamicToolCallTimeoutMs(value) {
|
|
2450
|
+
if (!isJsonObject(value)) return;
|
|
2451
|
+
const timeoutMs = readPositiveFiniteTimeoutMs(value.timeoutMs);
|
|
2452
|
+
if (timeoutMs !== void 0) return timeoutMs;
|
|
2453
|
+
const timeoutSecondsMs = readDynamicToolTimeoutSecondsAsMs(value.timeoutSeconds);
|
|
2454
|
+
return timeoutSecondsMs === void 0 ? void 0 : addTimerTimeoutGraceMs(timeoutSecondsMs, CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS);
|
|
2455
|
+
}
|
|
2456
|
+
function readConfiguredDynamicToolTimeoutMs(toolName, config) {
|
|
2457
|
+
if (toolName === "image_generate") {
|
|
2458
|
+
const imageModel = config?.agents?.defaults?.mediaModels?.image;
|
|
2459
|
+
if (!imageModel || typeof imageModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
|
|
2460
|
+
return readPositiveFiniteTimeoutMs(imageModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
|
|
2461
|
+
}
|
|
2462
|
+
if (toolName === "view_image") {
|
|
2463
|
+
const candidates = (config?.tools?.media?.models ?? []).filter((entry) => !entry.capabilities || entry.capabilities.includes("image"));
|
|
2464
|
+
const capabilityTimeoutMs = readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds);
|
|
2465
|
+
return Math.max(capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS, ...candidates.map((entry) => readTimeoutSecondsAsMs(entry.timeoutSeconds) ?? capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS));
|
|
2466
|
+
}
|
|
2467
|
+
if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
|
|
2468
|
+
}
|
|
2469
|
+
function readTimeoutSecondsAsMs(value) {
|
|
2470
|
+
const seconds = readPositiveFiniteTimeoutMs(value);
|
|
2471
|
+
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
2472
|
+
}
|
|
2473
|
+
function readDynamicToolTimeoutSecondsAsMs(value) {
|
|
2474
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) return;
|
|
2475
|
+
return value * 1e3;
|
|
2476
|
+
}
|
|
2477
|
+
function readPositiveFiniteTimeoutMs(value) {
|
|
2478
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
2479
|
+
}
|
|
2480
|
+
function clampDynamicToolTimeoutMs(timeoutMs) {
|
|
2481
|
+
return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
|
|
2482
|
+
}
|
|
2483
|
+
//#endregion
|
|
1766
2484
|
//#region extensions/codex/src/app-server/elicitation-response.ts
|
|
1767
2485
|
function createCodexElicitationResponse(action, content = null, meta = null) {
|
|
1768
2486
|
return {
|
|
@@ -1937,359 +2655,35 @@ function createWebSocketTransport(options) {
|
|
|
1937
2655
|
stdout,
|
|
1938
2656
|
stderr,
|
|
1939
2657
|
get killed() {
|
|
1940
|
-
return killed;
|
|
1941
|
-
},
|
|
1942
|
-
kill: () => {
|
|
1943
|
-
killed = true;
|
|
1944
|
-
clearConnectionHealthTimers();
|
|
1945
|
-
socket.close();
|
|
1946
|
-
},
|
|
1947
|
-
once: (event, listener) => events.once(event, listener)
|
|
1948
|
-
};
|
|
1949
|
-
}
|
|
1950
|
-
/** Opens the owner-scoped Codex control socket used by the WebSocket upgrade. */
|
|
1951
|
-
function connectCodexAppServerUnixSocket(socketPath) {
|
|
1952
|
-
return net.createConnection(socketPath);
|
|
1953
|
-
}
|
|
1954
|
-
/** Resolves the canonical or explicitly configured Codex control socket. */
|
|
1955
|
-
function resolveCodexAppServerUnixSocketPath(options) {
|
|
1956
|
-
if (options.transport !== "unix") {
|
|
1957
|
-
if (options.url?.startsWith("unix://")) throw new Error("codex app-server unix URL requires unix transport");
|
|
1958
|
-
return;
|
|
1959
|
-
}
|
|
1960
|
-
const url = options.url ?? "unix://";
|
|
1961
|
-
if (!url.startsWith("unix://")) throw new Error("codex app-server unix transport requires a unix:// URL");
|
|
1962
|
-
return url.slice(7) || path.join(resolveCodexAppServerUserHomeDir(options.env ?? process.env), "app-server-control", "app-server-control.sock");
|
|
1963
|
-
}
|
|
1964
|
-
function websocketFrameToText(data) {
|
|
1965
|
-
if (typeof data === "string") return data;
|
|
1966
|
-
if (Buffer.isBuffer(data)) return data.toString("utf8");
|
|
1967
|
-
if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
|
|
1968
|
-
return Buffer.from(data).toString("utf8");
|
|
1969
|
-
}
|
|
1970
|
-
//#endregion
|
|
1971
|
-
//#region extensions/codex/src/app-server/transport-process-containment.ts
|
|
1972
|
-
const PROCESS_COLUMNS = "pid=,ppid=,pgid=,stat=,lstart=";
|
|
1973
|
-
const MAX_CONTAINED_PROCESSES = 512;
|
|
1974
|
-
const MAX_PROCESS_CONTAINMENT_MS = 2e3;
|
|
1975
|
-
const MAX_PROCESS_QUIESCE_PASSES = 16;
|
|
1976
|
-
const PROCESS_INSPECTION_MAX_BYTES = 8 * 1024 * 1024;
|
|
1977
|
-
async function terminateCodexAppServerDescendants(child) {
|
|
1978
|
-
const rootPid = child.pid;
|
|
1979
|
-
if (process.platform === "win32" || !rootPid || !child.kill || hasExited(child)) return;
|
|
1980
|
-
const deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS;
|
|
1981
|
-
const snapshot = await readProcessSnapshot(deadline);
|
|
1982
|
-
if (!snapshot || Date.now() >= deadline) return;
|
|
1983
|
-
const root = snapshot.find((row) => row.pid === rootPid);
|
|
1984
|
-
if (!root || !isSameLiveRoot(root, root)) return;
|
|
1985
|
-
const initialDescendants = collectDescendants(snapshot, [rootPid]);
|
|
1986
|
-
if (initialDescendants.length > MAX_CONTAINED_PROCESSES) return;
|
|
1987
|
-
const stoppedDescendants = /* @__PURE__ */ new Map();
|
|
1988
|
-
if (!await signalSameRoot(root, "SIGSTOP", deadline)) return;
|
|
1989
|
-
let resumeRootOnUnwind = true;
|
|
1990
|
-
try {
|
|
1991
|
-
const descendants = await quiesceDescendants(root, initialDescendants, stoppedDescendants, deadline);
|
|
1992
|
-
if (!descendants) return;
|
|
1993
|
-
for (const descendant of descendants.toReversed()) {
|
|
1994
|
-
if (Date.now() >= deadline) return;
|
|
1995
|
-
if (!descendant.state.startsWith("Z")) {
|
|
1996
|
-
if (!await signalSameProcess(descendant, "SIGKILL", deadline) || Date.now() >= deadline) return;
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
resumeRootOnUnwind = false;
|
|
2000
|
-
let resumed = false;
|
|
2001
|
-
return () => {
|
|
2002
|
-
if (resumed) return;
|
|
2003
|
-
resumed = true;
|
|
2004
|
-
resumeTransportRoot(child, root, false);
|
|
2005
|
-
};
|
|
2006
|
-
} finally {
|
|
2007
|
-
if (resumeRootOnUnwind) {
|
|
2008
|
-
for (const descendant of stoppedDescendants.values()) signalProcess(descendant.pid, "SIGCONT");
|
|
2009
|
-
resumeTransportRoot(child, root, true);
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
async function quiesceDescendants(root, initialDescendants, stopped, deadline) {
|
|
2014
|
-
const provenByPid = new Map(initialDescendants.map((descendant) => [descendant.pid, descendant]));
|
|
2015
|
-
const stopFailures = /* @__PURE__ */ new Map();
|
|
2016
|
-
for (let pass = 0; pass < MAX_PROCESS_QUIESCE_PASSES; pass += 1) {
|
|
2017
|
-
if (Date.now() >= deadline) return;
|
|
2018
|
-
const snapshot = await readProcessSnapshot(deadline);
|
|
2019
|
-
if (!snapshot || Date.now() >= deadline) return;
|
|
2020
|
-
const currentRoot = snapshot.find((row) => row.pid === root.pid);
|
|
2021
|
-
if (!currentRoot || !isSameLiveRoot(currentRoot, root)) return;
|
|
2022
|
-
if (!isSameLiveRoot(currentRoot, root, true)) {
|
|
2023
|
-
if (!await signalSameRoot(root, "SIGSTOP", deadline) || Date.now() >= deadline) return;
|
|
2024
|
-
continue;
|
|
2025
|
-
}
|
|
2026
|
-
const snapshotByPid = new Map(snapshot.map((process) => [process.pid, process]));
|
|
2027
|
-
const liveProven = [];
|
|
2028
|
-
for (const proven of provenByPid.values()) {
|
|
2029
|
-
const current = snapshotByPid.get(proven.pid);
|
|
2030
|
-
if (!current) {
|
|
2031
|
-
provenByPid.delete(proven.pid);
|
|
2032
|
-
stopped.delete(identityKey(proven));
|
|
2033
|
-
continue;
|
|
2034
|
-
}
|
|
2035
|
-
if (!hasSameIdentity(proven, current)) return;
|
|
2036
|
-
provenByPid.set(current.pid, current);
|
|
2037
|
-
const key = identityKey(current);
|
|
2038
|
-
if (stopped.has(key)) stopped.set(key, current);
|
|
2039
|
-
liveProven.push(current);
|
|
2040
|
-
}
|
|
2041
|
-
const descendants = collectDescendants(snapshot, [root.pid, ...liveProven.map(({ pid }) => pid)]);
|
|
2042
|
-
for (const descendant of descendants) {
|
|
2043
|
-
const proven = provenByPid.get(descendant.pid);
|
|
2044
|
-
if (proven && !hasSameIdentity(proven, descendant)) return;
|
|
2045
|
-
provenByPid.set(descendant.pid, descendant);
|
|
2046
|
-
}
|
|
2047
|
-
if (provenByPid.size > MAX_CONTAINED_PROCESSES) return;
|
|
2048
|
-
const quiescenceTargets = new Map(liveProven.map((process) => [process.pid, process]));
|
|
2049
|
-
for (const descendant of descendants) quiescenceTargets.set(descendant.pid, descendant);
|
|
2050
|
-
let allStopped = true;
|
|
2051
|
-
for (const descendant of quiescenceTargets.values()) {
|
|
2052
|
-
if (Date.now() >= deadline) return;
|
|
2053
|
-
if (isStoppedState(descendant.state)) continue;
|
|
2054
|
-
const stopQueued = await signalSameProcess(descendant, "SIGSTOP", deadline);
|
|
2055
|
-
if (Date.now() >= deadline) return;
|
|
2056
|
-
if (stopQueued) {
|
|
2057
|
-
stopFailures.delete(identityKey(descendant));
|
|
2058
|
-
stopped.set(identityKey(descendant), descendant);
|
|
2059
|
-
} else {
|
|
2060
|
-
const key = identityKey(descendant);
|
|
2061
|
-
const failures = (stopFailures.get(key) ?? 0) + 1;
|
|
2062
|
-
if (failures >= 2) return;
|
|
2063
|
-
stopFailures.set(key, failures);
|
|
2064
|
-
}
|
|
2065
|
-
if (!isUninterruptibleState(descendant.state) || !stopQueued) allStopped = false;
|
|
2066
|
-
}
|
|
2067
|
-
if (allStopped) return [...provenByPid.values()];
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
async function readProcessSnapshot(deadline) {
|
|
2071
|
-
return await readProcesses(["-axo", PROCESS_COLUMNS], deadline);
|
|
2072
|
-
}
|
|
2073
|
-
async function readProcess(pid, deadline) {
|
|
2074
|
-
return (await readProcesses([
|
|
2075
|
-
"-o",
|
|
2076
|
-
PROCESS_COLUMNS,
|
|
2077
|
-
"-p",
|
|
2078
|
-
String(pid)
|
|
2079
|
-
], deadline))?.find((row) => row.pid === pid);
|
|
2080
|
-
}
|
|
2081
|
-
async function readProcesses(args, deadline) {
|
|
2082
|
-
const remainingMs = deadline - Date.now();
|
|
2083
|
-
if (remainingMs <= 0) return;
|
|
2084
|
-
return await new Promise((resolve) => {
|
|
2085
|
-
let settled = false;
|
|
2086
|
-
const settle = (processes) => {
|
|
2087
|
-
if (settled) return;
|
|
2088
|
-
settled = true;
|
|
2089
|
-
clearTimeout(timer);
|
|
2090
|
-
resolve(processes);
|
|
2091
|
-
};
|
|
2092
|
-
const inspector = execFile("ps", args, {
|
|
2093
|
-
encoding: "utf8",
|
|
2094
|
-
maxBuffer: PROCESS_INSPECTION_MAX_BYTES
|
|
2095
|
-
}, (error, stdout) => {
|
|
2096
|
-
settle(error ? void 0 : parseProcesses(stdout));
|
|
2097
|
-
});
|
|
2098
|
-
const timer = setTimeout(() => {
|
|
2099
|
-
settle(void 0);
|
|
2100
|
-
inspector.stdout?.destroy();
|
|
2101
|
-
inspector.stderr?.destroy();
|
|
2102
|
-
inspector.kill("SIGKILL");
|
|
2103
|
-
inspector.unref();
|
|
2104
|
-
}, Math.max(1, remainingMs));
|
|
2105
|
-
timer.unref?.();
|
|
2106
|
-
});
|
|
2107
|
-
}
|
|
2108
|
-
function parseProcesses(output) {
|
|
2109
|
-
const rows = [];
|
|
2110
|
-
for (const line of output.split("\n")) {
|
|
2111
|
-
const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
|
|
2112
|
-
if (!match) continue;
|
|
2113
|
-
const pid = Number(match[1] ?? "");
|
|
2114
|
-
const ppid = Number(match[2] ?? "");
|
|
2115
|
-
const pgid = Number(match[3] ?? "");
|
|
2116
|
-
const startedAt = (match[5] ?? "").trim().replace(/\s+/g, " ");
|
|
2117
|
-
if (![
|
|
2118
|
-
pid,
|
|
2119
|
-
ppid,
|
|
2120
|
-
pgid
|
|
2121
|
-
].every(Number.isSafeInteger) || pid <= 0 || ppid < 0 || pgid <= 0 || !startedAt) continue;
|
|
2122
|
-
rows.push({
|
|
2123
|
-
pid,
|
|
2124
|
-
ppid,
|
|
2125
|
-
pgid,
|
|
2126
|
-
state: match[4] ?? "",
|
|
2127
|
-
startedAt
|
|
2128
|
-
});
|
|
2129
|
-
}
|
|
2130
|
-
return rows;
|
|
2131
|
-
}
|
|
2132
|
-
function collectDescendants(snapshot, rootPids) {
|
|
2133
|
-
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2134
|
-
for (const row of snapshot) {
|
|
2135
|
-
const children = childrenByParent.get(row.ppid) ?? [];
|
|
2136
|
-
children.push(row);
|
|
2137
|
-
childrenByParent.set(row.ppid, children);
|
|
2138
|
-
}
|
|
2139
|
-
const descendants = [];
|
|
2140
|
-
const pending = [...new Set(rootPids)];
|
|
2141
|
-
const seen = new Set(pending);
|
|
2142
|
-
for (const parentPid of pending) for (const child of childrenByParent.get(parentPid) ?? []) {
|
|
2143
|
-
if (seen.has(child.pid)) continue;
|
|
2144
|
-
seen.add(child.pid);
|
|
2145
|
-
descendants.push(child);
|
|
2146
|
-
pending.push(child.pid);
|
|
2147
|
-
}
|
|
2148
|
-
return descendants;
|
|
2149
|
-
}
|
|
2150
|
-
function isStoppedState(state) {
|
|
2151
|
-
return state.startsWith("T") || state.startsWith("t") || state.startsWith("Z");
|
|
2152
|
-
}
|
|
2153
|
-
function isQuiescedState(state) {
|
|
2154
|
-
return isStoppedState(state) || isUninterruptibleState(state);
|
|
2155
|
-
}
|
|
2156
|
-
function isUninterruptibleState(state) {
|
|
2157
|
-
return state.startsWith("D") || state.startsWith("U");
|
|
2158
|
-
}
|
|
2159
|
-
function isSameLiveProcess(current, expected) {
|
|
2160
|
-
return current.pgid === expected.pgid && !current.state.startsWith("Z") && hasSameIdentity(current, expected);
|
|
2161
|
-
}
|
|
2162
|
-
function isSameLiveRoot(current, expected, requireStopped = false) {
|
|
2163
|
-
return current.ppid === process.pid && (!requireStopped || isQuiescedState(current.state)) && isSameLiveProcess(current, expected);
|
|
2164
|
-
}
|
|
2165
|
-
async function signalSameRoot(root, signal, deadline) {
|
|
2166
|
-
const current = await readProcess(root.pid, deadline);
|
|
2167
|
-
return Boolean(current && isSameLiveRoot(current, root) && signalProcess(current.pid, signal));
|
|
2168
|
-
}
|
|
2169
|
-
function resumeTransportRoot(child, root, allowSynchronousPidFallback) {
|
|
2170
|
-
try {
|
|
2171
|
-
if (child.kill) {
|
|
2172
|
-
child.kill("SIGCONT");
|
|
2173
|
-
return;
|
|
2174
|
-
}
|
|
2175
|
-
} catch {
|
|
2176
|
-
if (!allowSynchronousPidFallback) return;
|
|
2177
|
-
}
|
|
2178
|
-
if (allowSynchronousPidFallback) signalProcess(root.pid, "SIGCONT");
|
|
2179
|
-
}
|
|
2180
|
-
async function signalSameProcess(expected, signal, deadline) {
|
|
2181
|
-
const current = await readProcess(expected.pid, deadline);
|
|
2182
|
-
return Boolean(current && isSameLiveProcess(current, expected) && signalProcess(current.pid, signal));
|
|
2183
|
-
}
|
|
2184
|
-
function hasSameIdentity(left, right) {
|
|
2185
|
-
return identityKey(left) === identityKey(right);
|
|
2186
|
-
}
|
|
2187
|
-
function identityKey(row) {
|
|
2188
|
-
return `${row.pid}\0${row.startedAt}`;
|
|
2189
|
-
}
|
|
2190
|
-
function hasExited(child) {
|
|
2191
|
-
return child.exitCode != null || child.signalCode != null;
|
|
2192
|
-
}
|
|
2193
|
-
function signalProcess(pid, signal) {
|
|
2194
|
-
try {
|
|
2195
|
-
process.kill(pid, signal);
|
|
2196
|
-
return true;
|
|
2197
|
-
} catch {
|
|
2198
|
-
return false;
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
//#endregion
|
|
2202
|
-
//#region extensions/codex/src/app-server/transport.ts
|
|
2203
|
-
/**
|
|
2204
|
-
* Shared transport lifecycle helpers for stdio and WebSocket Codex app-server
|
|
2205
|
-
* connections.
|
|
2206
|
-
*/
|
|
2207
|
-
const CODEX_APP_SERVER_TRANSPORT_CLOSES = /* @__PURE__ */ new WeakMap();
|
|
2208
|
-
/** Starts graceful transport shutdown and schedules a force kill fallback. */
|
|
2209
|
-
function closeCodexAppServerTransport(child, options = {}) {
|
|
2210
|
-
beginCodexAppServerTransportClose(child, options);
|
|
2211
|
-
}
|
|
2212
|
-
function beginCodexAppServerTransportClose(child, options) {
|
|
2213
|
-
const current = CODEX_APP_SERVER_TRANSPORT_CLOSES.get(child);
|
|
2214
|
-
if (current) return current;
|
|
2215
|
-
if (process.platform === "win32" || !child.pid || !child.kill || hasCodexAppServerTransportExited(child)) {
|
|
2216
|
-
finishCodexAppServerTransportClose(child, options);
|
|
2217
|
-
const completed = Promise.resolve();
|
|
2218
|
-
CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, completed);
|
|
2219
|
-
return completed;
|
|
2220
|
-
}
|
|
2221
|
-
const closing = (async () => {
|
|
2222
|
-
let resumeRoot;
|
|
2223
|
-
try {
|
|
2224
|
-
resumeRoot = await terminateCodexAppServerDescendants(child);
|
|
2225
|
-
} catch {
|
|
2226
|
-
resumeRoot = void 0;
|
|
2227
|
-
}
|
|
2228
|
-
try {
|
|
2229
|
-
finishCodexAppServerTransportClose(child, options, resumeRoot);
|
|
2230
|
-
} catch {
|
|
2231
|
-
signalCodexAppServerTransport(child, "SIGKILL");
|
|
2232
|
-
}
|
|
2233
|
-
})();
|
|
2234
|
-
CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, closing);
|
|
2235
|
-
return closing;
|
|
2236
|
-
}
|
|
2237
|
-
function finishCodexAppServerTransportClose(child, options, resumeRoot) {
|
|
2238
|
-
const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
|
|
2239
|
-
const forceKill = setTimeout(() => {
|
|
2240
|
-
if (hasCodexAppServerTransportExited(child)) return;
|
|
2241
|
-
signalCodexAppServerTransport(child, "SIGKILL");
|
|
2242
|
-
}, Math.max(1, forceKillDelayMs));
|
|
2243
|
-
forceKill.unref?.();
|
|
2244
|
-
child.once("exit", () => {
|
|
2245
|
-
clearTimeout(forceKill);
|
|
2246
|
-
child.stdout.destroy?.();
|
|
2247
|
-
child.stderr.destroy?.();
|
|
2248
|
-
});
|
|
2249
|
-
try {
|
|
2250
|
-
child.stdin.end?.();
|
|
2251
|
-
child.stdin.destroy?.();
|
|
2252
|
-
} finally {
|
|
2253
|
-
resumeRoot?.();
|
|
2254
|
-
}
|
|
2255
|
-
child.unref?.();
|
|
2256
|
-
child.stdout.unref?.();
|
|
2257
|
-
child.stderr.unref?.();
|
|
2258
|
-
child.stdin.unref?.();
|
|
2259
|
-
}
|
|
2260
|
-
/** Closes a transport and waits briefly for an exit event. */
|
|
2261
|
-
async function closeCodexAppServerTransportAndWait(child, options = {}) {
|
|
2262
|
-
if (!hasCodexAppServerTransportExited(child)) await beginCodexAppServerTransportClose(child, options);
|
|
2263
|
-
return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
|
|
2264
|
-
}
|
|
2265
|
-
function hasCodexAppServerTransportExited(child) {
|
|
2266
|
-
return child.exitCode !== null && child.exitCode !== void 0 ? true : child.signalCode !== null && child.signalCode !== void 0;
|
|
2658
|
+
return killed;
|
|
2659
|
+
},
|
|
2660
|
+
kill: () => {
|
|
2661
|
+
killed = true;
|
|
2662
|
+
clearConnectionHealthTimers();
|
|
2663
|
+
socket.close();
|
|
2664
|
+
},
|
|
2665
|
+
once: (event, listener) => events.once(event, listener)
|
|
2666
|
+
};
|
|
2267
2667
|
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
return
|
|
2271
|
-
let settled = false;
|
|
2272
|
-
const onExit = () => {
|
|
2273
|
-
if (settled) return;
|
|
2274
|
-
settled = true;
|
|
2275
|
-
clearTimeout(timeout);
|
|
2276
|
-
resolve(true);
|
|
2277
|
-
};
|
|
2278
|
-
const timeout = setTimeout(() => {
|
|
2279
|
-
if (settled) return;
|
|
2280
|
-
settled = true;
|
|
2281
|
-
child.off?.("exit", onExit);
|
|
2282
|
-
resolve(false);
|
|
2283
|
-
}, Math.max(1, timeoutMs));
|
|
2284
|
-
child.once("exit", onExit);
|
|
2285
|
-
});
|
|
2668
|
+
/** Opens the owner-scoped Codex control socket used by the WebSocket upgrade. */
|
|
2669
|
+
function connectCodexAppServerUnixSocket(socketPath) {
|
|
2670
|
+
return net.createConnection(socketPath);
|
|
2286
2671
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2672
|
+
/** Resolves the canonical or explicitly configured Codex control socket. */
|
|
2673
|
+
function resolveCodexAppServerUnixSocketPath(options) {
|
|
2674
|
+
if (options.transport !== "unix") {
|
|
2675
|
+
if (options.url?.startsWith("unix://")) throw new Error("codex app-server unix URL requires unix transport");
|
|
2290
2676
|
return;
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2677
|
+
}
|
|
2678
|
+
const url = options.url ?? "unix://";
|
|
2679
|
+
if (!url.startsWith("unix://")) throw new Error("codex app-server unix transport requires a unix:// URL");
|
|
2680
|
+
return url.slice(7) || path.join(resolveCodexAppServerUserHomeDir(options.env ?? process.env), "app-server-control", "app-server-control.sock");
|
|
2681
|
+
}
|
|
2682
|
+
function websocketFrameToText(data) {
|
|
2683
|
+
if (typeof data === "string") return data;
|
|
2684
|
+
if (Buffer.isBuffer(data)) return data.toString("utf8");
|
|
2685
|
+
if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
|
|
2686
|
+
return Buffer.from(data).toString("utf8");
|
|
2293
2687
|
}
|
|
2294
2688
|
//#endregion
|
|
2295
2689
|
//#region extensions/codex/src/app-server/client.ts
|
|
@@ -2300,11 +2694,11 @@ function signalCodexAppServerTransport(child, signal) {
|
|
|
2300
2694
|
const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
|
|
2301
2695
|
const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 8 * 1024 * 1024;
|
|
2302
2696
|
const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1e3;
|
|
2303
|
-
const CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS = 66e4;
|
|
2304
2697
|
const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
|
|
2305
2698
|
const CODEX_APP_SERVER_OVERLOADED_ERROR_CODE = -32001;
|
|
2306
2699
|
const CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES = 3;
|
|
2307
2700
|
const CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS = 50;
|
|
2701
|
+
const CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX = 32;
|
|
2308
2702
|
const CODEX_APP_SERVER_CLIENT_INSTANCE_IDS = /* @__PURE__ */ new WeakMap();
|
|
2309
2703
|
const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
2310
2704
|
/** Process-local generation fence for bindings tied to one app-server client instance. */
|
|
@@ -2318,6 +2712,10 @@ function getCodexAppServerClientInstanceId(client) {
|
|
|
2318
2712
|
function resolveCodexAppServerClientInstanceId(client) {
|
|
2319
2713
|
return client.getInstanceId?.call(client) ?? getCodexAppServerClientInstanceId(client);
|
|
2320
2714
|
}
|
|
2715
|
+
/** Codex rejects this exact code before enqueueing, including mutating requests. */
|
|
2716
|
+
function isCodexAppServerOverloadError(error) {
|
|
2717
|
+
return error instanceof CodexAppServerRpcError && error.code === CODEX_APP_SERVER_OVERLOADED_ERROR_CODE;
|
|
2718
|
+
}
|
|
2321
2719
|
var CodexAppServerLocalRequestCancellationError = class extends Error {
|
|
2322
2720
|
constructor(method, reason, mayHaveWritten) {
|
|
2323
2721
|
super(`${method} ${reason}`);
|
|
@@ -2373,9 +2771,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2373
2771
|
this.pending = /* @__PURE__ */ new Map();
|
|
2374
2772
|
this.requestHandlers = /* @__PURE__ */ new Set();
|
|
2375
2773
|
this.notificationHandlers = /* @__PURE__ */ new Set();
|
|
2774
|
+
this.pendingStartupWarnings = [];
|
|
2376
2775
|
this.closeHandlers = /* @__PURE__ */ new Set();
|
|
2377
2776
|
this.nextId = 1;
|
|
2378
2777
|
this.initialized = false;
|
|
2778
|
+
this.modelCatalogRevision = 0;
|
|
2379
2779
|
this.closed = false;
|
|
2380
2780
|
this.transportExited = false;
|
|
2381
2781
|
this.stderrTail = "";
|
|
@@ -2401,7 +2801,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2401
2801
|
child.stdin.on?.("error", (error) => this.closeWithError(toStringifiedError(error)));
|
|
2402
2802
|
}
|
|
2403
2803
|
/** Starts a new app-server client using resolved runtime start options. */
|
|
2404
|
-
static start(options) {
|
|
2804
|
+
static async start(options, assertCurrent) {
|
|
2405
2805
|
const defaults = resolveCodexAppServerRuntimeOptions().start;
|
|
2406
2806
|
const startOptions = {
|
|
2407
2807
|
...defaults,
|
|
@@ -2410,7 +2810,18 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2410
2810
|
};
|
|
2411
2811
|
if (startOptions.transport === "stdio" && startOptions.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
|
|
2412
2812
|
if (startOptions.transport === "websocket" || startOptions.transport === "unix") return new CodexAppServerClient(createWebSocketTransport(startOptions));
|
|
2413
|
-
|
|
2813
|
+
let client;
|
|
2814
|
+
try {
|
|
2815
|
+
await createStdioTransport(startOptions, process.env, assertCurrent, (child) => {
|
|
2816
|
+
client = new CodexAppServerClient(child);
|
|
2817
|
+
});
|
|
2818
|
+
return client;
|
|
2819
|
+
} catch (error) {
|
|
2820
|
+
assertCurrent?.();
|
|
2821
|
+
if (client?.transportExited && hasCodexAppServerNaturalExit(client.child)) throw buildCodexAppServerExitError(client.child.exitCode, client.child.signalCode, client.stderrTail);
|
|
2822
|
+
const stderr = client?.getStderrDiagnostic();
|
|
2823
|
+
throw stderr ? new Error(`${coerceErrorMessage(error)}; stderr=${JSON.stringify(stderr)}`, { cause: error }) : error;
|
|
2824
|
+
}
|
|
2414
2825
|
}
|
|
2415
2826
|
/** Builds a client around a fake transport for tests. */
|
|
2416
2827
|
static fromTransportForTests(child) {
|
|
@@ -2429,7 +2840,8 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2429
2840
|
experimentalApi: true,
|
|
2430
2841
|
extensions: {
|
|
2431
2842
|
"openai/standard-form-input": {},
|
|
2432
|
-
"openai/form": {}
|
|
2843
|
+
"openai/form": {},
|
|
2844
|
+
"io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app"] }
|
|
2433
2845
|
}
|
|
2434
2846
|
}
|
|
2435
2847
|
});
|
|
@@ -2458,6 +2870,10 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2458
2870
|
getInstanceId() {
|
|
2459
2871
|
return this.instanceId;
|
|
2460
2872
|
}
|
|
2873
|
+
/** Account/config observations become stale before a mutation can enter the wire. */
|
|
2874
|
+
getModelCatalogRevision() {
|
|
2875
|
+
return this.modelCatalogRevision;
|
|
2876
|
+
}
|
|
2461
2877
|
/** Installs the spawn-owner check run before config-loading thread requests. */
|
|
2462
2878
|
setThreadSessionRequestGuard(guard) {
|
|
2463
2879
|
this.threadSessionRequestGuard = guard;
|
|
@@ -2467,8 +2883,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2467
2883
|
return this.child.pid;
|
|
2468
2884
|
}
|
|
2469
2885
|
request(method, params, optionsInput) {
|
|
2470
|
-
|
|
2471
|
-
options ??= {};
|
|
2886
|
+
const options = optionsInput ?? {};
|
|
2472
2887
|
if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
|
|
2473
2888
|
if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
|
|
2474
2889
|
const guard = method === "thread/start" || method === "thread/resume" || method === "thread/fork" ? this.threadSessionRequestGuard : void 0;
|
|
@@ -2503,11 +2918,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2503
2918
|
const elapsedMs = Date.now() - guardStartedAt;
|
|
2504
2919
|
const remainingTimeoutMs = options.timeoutMs === void 0 ? void 0 : options.timeoutMs - elapsedMs;
|
|
2505
2920
|
if (remainingTimeoutMs !== void 0 && remainingTimeoutMs <= 0) throw new CodexAppServerLocalRequestCancellationError(method, "timed out", false);
|
|
2506
|
-
return await this.
|
|
2921
|
+
return await this.requestWithOverloadRetry(method, params, {
|
|
2507
2922
|
...options,
|
|
2508
2923
|
...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
|
|
2509
|
-
}, () => {
|
|
2510
|
-
requestMayHaveWritten =
|
|
2924
|
+
}, (mayHaveWritten) => {
|
|
2925
|
+
requestMayHaveWritten = mayHaveWritten;
|
|
2511
2926
|
});
|
|
2512
2927
|
} catch (error) {
|
|
2513
2928
|
if (requestMayHaveWritten && !(error instanceof CodexAppServerRpcError)) {
|
|
@@ -2520,12 +2935,9 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2520
2935
|
}
|
|
2521
2936
|
})();
|
|
2522
2937
|
}
|
|
2523
|
-
return this.
|
|
2938
|
+
return this.requestWithOverloadRetry(method, params, options);
|
|
2524
2939
|
}
|
|
2525
|
-
|
|
2526
|
-
return this.requestWithOverloadRetry(method, params, options, onWriteAttempt);
|
|
2527
|
-
}
|
|
2528
|
-
async requestWithOverloadRetry(method, params, options, onWriteAttempt) {
|
|
2940
|
+
async requestWithOverloadRetry(method, params, options, onWriteStateChange) {
|
|
2529
2941
|
const deadline = options.timeoutMs !== void 0 && Number.isFinite(options.timeoutMs) ? Date.now() + options.timeoutMs : void 0;
|
|
2530
2942
|
for (let retry = 0;; retry += 1) {
|
|
2531
2943
|
if (options.signal?.aborted) throw new CodexAppServerLocalRequestCancellationError(method, "aborted", false);
|
|
@@ -2535,9 +2947,10 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2535
2947
|
return await this.requestOnce(method, params, {
|
|
2536
2948
|
...options,
|
|
2537
2949
|
...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
|
|
2538
|
-
},
|
|
2950
|
+
}, onWriteStateChange);
|
|
2539
2951
|
} catch (error) {
|
|
2540
|
-
if (!(error
|
|
2952
|
+
if (!isCodexAppServerOverloadError(error) || retry >= CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES) throw error;
|
|
2953
|
+
onWriteStateChange?.(false);
|
|
2541
2954
|
const backoffMs = Math.round(CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS * 2 ** retry * (.75 + Math.random() * .5));
|
|
2542
2955
|
await this.waitForOverloadRetry(method, backoffMs, deadline, options.signal);
|
|
2543
2956
|
}
|
|
@@ -2566,10 +2979,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2566
2979
|
if (signal?.aborted) abortListener();
|
|
2567
2980
|
});
|
|
2568
2981
|
}
|
|
2569
|
-
requestOnce(method, params, options,
|
|
2982
|
+
requestOnce(method, params, options, onWriteStateChange) {
|
|
2570
2983
|
if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
|
|
2571
2984
|
if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
|
|
2572
2985
|
const id = this.nextId++;
|
|
2986
|
+
if (method === "account/login/start" || method === "account/logout" || method === "config/value/write" || method === "config/batchWrite") this.modelCatalogRevision += 1;
|
|
2573
2987
|
const message = {
|
|
2574
2988
|
id,
|
|
2575
2989
|
method,
|
|
@@ -2619,8 +3033,9 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2619
3033
|
return;
|
|
2620
3034
|
}
|
|
2621
3035
|
try {
|
|
3036
|
+
options.assertCurrent?.();
|
|
2622
3037
|
mayHaveWritten = true;
|
|
2623
|
-
|
|
3038
|
+
onWriteStateChange?.(true);
|
|
2624
3039
|
this.writeMessage(message, (error) => rejectPending(error));
|
|
2625
3040
|
} catch (error) {
|
|
2626
3041
|
rejectPending(toStringifiedError(error));
|
|
@@ -2642,6 +3057,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2642
3057
|
/** Registers a notification handler and returns its disposer. */
|
|
2643
3058
|
addNotificationHandler(handler) {
|
|
2644
3059
|
this.notificationHandlers.add(handler);
|
|
3060
|
+
for (const notification of this.pendingStartupWarnings.splice(0)) this.handleNotification(notification);
|
|
2645
3061
|
return () => this.notificationHandlers.delete(handler);
|
|
2646
3062
|
}
|
|
2647
3063
|
/** Registers a close handler and returns its disposer. */
|
|
@@ -2649,6 +3065,16 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2649
3065
|
this.closeHandlers.add(handler);
|
|
2650
3066
|
return () => this.closeHandlers.delete(handler);
|
|
2651
3067
|
}
|
|
3068
|
+
/** Registers a handler for physical transport exit and returns its disposer. */
|
|
3069
|
+
addTransportExitHandler(handler) {
|
|
3070
|
+
if (this.transportExited) {
|
|
3071
|
+
handler(this);
|
|
3072
|
+
return () => void 0;
|
|
3073
|
+
}
|
|
3074
|
+
const onExit = () => handler(this);
|
|
3075
|
+
this.child.once("exit", onExit);
|
|
3076
|
+
return () => this.child.off?.("exit", onExit);
|
|
3077
|
+
}
|
|
2652
3078
|
/** Closes the transport without waiting for process/socket shutdown. */
|
|
2653
3079
|
close() {
|
|
2654
3080
|
if (!this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"))) return;
|
|
@@ -2811,8 +3237,9 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2811
3237
|
}
|
|
2812
3238
|
async runServerRequestHandlers(request) {
|
|
2813
3239
|
const controller = new AbortController();
|
|
2814
|
-
|
|
2815
|
-
|
|
3240
|
+
if (request.method !== "item/tool/call") return await this.runServerRequestHandlersWithoutTimeout(request, controller.signal);
|
|
3241
|
+
const timeoutMs = resolveDynamicToolServerRequestTimeoutMs(readCodexDynamicToolCallParams(request.params));
|
|
3242
|
+
const timeoutResponse = timeoutServerRequestResponse(timeoutMs);
|
|
2816
3243
|
let timeout;
|
|
2817
3244
|
try {
|
|
2818
3245
|
return await Promise.race([this.runServerRequestHandlersWithoutTimeout(request, controller.signal), new Promise((resolve) => {
|
|
@@ -2820,11 +3247,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2820
3247
|
embeddedAgentLog.warn("codex app-server server request timed out", {
|
|
2821
3248
|
id: request.id,
|
|
2822
3249
|
method: request.method,
|
|
2823
|
-
timeoutMs
|
|
3250
|
+
timeoutMs
|
|
2824
3251
|
});
|
|
2825
3252
|
controller.abort(/* @__PURE__ */ new Error("codex app-server server request timed out"));
|
|
2826
3253
|
resolve(timeoutResponse);
|
|
2827
|
-
},
|
|
3254
|
+
}, timeoutMs);
|
|
2828
3255
|
timeout.unref?.();
|
|
2829
3256
|
})]);
|
|
2830
3257
|
} finally {
|
|
@@ -2839,6 +3266,12 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2839
3266
|
}
|
|
2840
3267
|
}
|
|
2841
3268
|
handleNotification(notification) {
|
|
3269
|
+
if (notification.method === "account/updated") this.modelCatalogRevision += 1;
|
|
3270
|
+
if (this.notificationHandlers.size === 0 && notification.method === "configWarning") {
|
|
3271
|
+
if (this.pendingStartupWarnings.length === CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX) this.pendingStartupWarnings.shift();
|
|
3272
|
+
this.pendingStartupWarnings.push(notification);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
2842
3275
|
for (const handler of this.notificationHandlers) try {
|
|
2843
3276
|
Promise.resolve(handler(notification)).catch((error) => {
|
|
2844
3277
|
embeddedAgentLog.warn("codex app-server notification handler failed", { error });
|
|
@@ -2887,12 +3320,11 @@ function defaultServerRequestResponse(request) {
|
|
|
2887
3320
|
function stringifyCodexAppServerMessage(message) {
|
|
2888
3321
|
return JSON.stringify(message, (_key, value) => typeof value === "string" ? value.replace(UNPAIRED_SURROGATE_RE, "") : value) ?? "null";
|
|
2889
3322
|
}
|
|
2890
|
-
function timeoutServerRequestResponse(
|
|
2891
|
-
if (request.method !== "item/tool/call") return;
|
|
3323
|
+
function timeoutServerRequestResponse(timeoutMs) {
|
|
2892
3324
|
return {
|
|
2893
3325
|
contentItems: [{
|
|
2894
3326
|
type: "inputText",
|
|
2895
|
-
text: `OpenClaw dynamic tool call timed out after ${
|
|
3327
|
+
text: `OpenClaw dynamic tool call timed out after ${timeoutMs}ms before sending a response to Codex.`
|
|
2896
3328
|
}],
|
|
2897
3329
|
success: false
|
|
2898
3330
|
};
|
|
@@ -2910,8 +3342,8 @@ function assertSupportedCodexAppServerVersion(response) {
|
|
|
2910
3342
|
const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
|
|
2911
3343
|
if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
|
|
2912
3344
|
const detected = parse(detectedVersion);
|
|
2913
|
-
if (!detected || detected.compare("0.
|
|
2914
|
-
if (detected.compare("0.
|
|
3345
|
+
if (!detected || detected.compare("0.149.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
|
|
3346
|
+
if (detected.compare("0.151.0") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
|
|
2915
3347
|
detectedVersion,
|
|
2916
3348
|
validatedVersion: CODEX_APP_SERVER_VERSION
|
|
2917
3349
|
});
|
|
@@ -2997,6 +3429,8 @@ const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
|
2997
3429
|
const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
2998
3430
|
/** Long terminal idle watch for app-server turns that never send completion. */
|
|
2999
3431
|
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
3432
|
+
const TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS = 2 * 6e4;
|
|
3433
|
+
const TURN_FINALIZE_DRAIN_ABORT_GRACE_MS = 5e3;
|
|
3000
3434
|
var CodexAppServerStartupError = class extends Error {
|
|
3001
3435
|
constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
|
|
3002
3436
|
super(message);
|
|
@@ -3078,6 +3512,324 @@ function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
|
|
|
3078
3512
|
return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
|
|
3079
3513
|
}
|
|
3080
3514
|
//#endregion
|
|
3515
|
+
//#region extensions/codex/src/app-server/desktop-generation-fingerprint.ts
|
|
3516
|
+
const MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES = 4096;
|
|
3517
|
+
/** Fingerprints every desktop candidate that can own a managed fallback artifact. */
|
|
3518
|
+
async function readMacOSDesktopGenerationFingerprint(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
|
|
3519
|
+
const entries = [];
|
|
3520
|
+
for (const candidate of candidates) {
|
|
3521
|
+
const command = await statFingerprint(candidate.appServerCommandPath);
|
|
3522
|
+
entries.push(`candidate:${candidate.appName}:${candidate.appServerCommandPath}:${command}`);
|
|
3523
|
+
for (const artifactPath of resolveMacOSDesktopGenerationPaths(candidate)) entries.push(`${artifactPath}\0${await statFingerprint(artifactPath)}`);
|
|
3524
|
+
const pluginRoot = resolveComputerUsePluginRoot(candidate);
|
|
3525
|
+
entries.push(`${pluginRoot}\0${await directoryTreeFingerprint(pluginRoot)}`);
|
|
3526
|
+
}
|
|
3527
|
+
return createHash("sha256").update(entries.join("\0")).digest("hex");
|
|
3528
|
+
}
|
|
3529
|
+
function resolveMacOSDesktopGenerationPaths(candidate) {
|
|
3530
|
+
return [
|
|
3531
|
+
candidate.appBundlePath,
|
|
3532
|
+
path.join(candidate.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"),
|
|
3533
|
+
...candidate.computerUseServiceAppPaths.flatMap((servicePath) => [
|
|
3534
|
+
servicePath,
|
|
3535
|
+
path.join(servicePath, "Contents", "Info.plist"),
|
|
3536
|
+
path.join(servicePath, "Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient")
|
|
3537
|
+
])
|
|
3538
|
+
];
|
|
3539
|
+
}
|
|
3540
|
+
function resolveComputerUsePluginRoot(candidate) {
|
|
3541
|
+
return path.join(candidate.bundledMarketplacePath, "plugins", "computer-use");
|
|
3542
|
+
}
|
|
3543
|
+
/** Stable roots that cover bundle replacement and recursive artifact updates. */
|
|
3544
|
+
function resolveMacOSDesktopGenerationWatchPaths(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
|
|
3545
|
+
const watched = /* @__PURE__ */ new Set(["/Applications"]);
|
|
3546
|
+
for (const candidate of candidates) watched.add(candidate.appBundlePath);
|
|
3547
|
+
return [...watched];
|
|
3548
|
+
}
|
|
3549
|
+
async function directoryTreeFingerprint(root) {
|
|
3550
|
+
let rootStat;
|
|
3551
|
+
try {
|
|
3552
|
+
rootStat = await fs.lstat(root, { bigint: true });
|
|
3553
|
+
} catch (error) {
|
|
3554
|
+
if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
|
|
3555
|
+
throw error;
|
|
3556
|
+
}
|
|
3557
|
+
if (!rootStat.isDirectory()) return statFingerprint(root);
|
|
3558
|
+
let entryCount = 0;
|
|
3559
|
+
const hash = createHash("sha256");
|
|
3560
|
+
hash.update("openclaw-codex-computer-use-plugin-tree-v1\0");
|
|
3561
|
+
const visit = async (directory, relativeDirectory, before) => {
|
|
3562
|
+
hash.update(`directory\0${relativeDirectory}\0${statTuple(before)}\0`);
|
|
3563
|
+
const entries = (await fs.readdir(directory, { withFileTypes: true })).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
3564
|
+
for (const entry of entries) {
|
|
3565
|
+
entryCount += 1;
|
|
3566
|
+
if (entryCount > MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES) throw new Error("Codex Computer Use plugin exceeds the bounded tree size");
|
|
3567
|
+
const entryPath = path.join(directory, entry.name);
|
|
3568
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
3569
|
+
const entryStat = await fs.lstat(entryPath, { bigint: true });
|
|
3570
|
+
if (entryStat.isDirectory()) await visit(entryPath, relativePath, entryStat);
|
|
3571
|
+
else hash.update(`entry\0${relativePath}\0${await statFingerprint(entryPath)}\0`);
|
|
3572
|
+
}
|
|
3573
|
+
if (!sameStat(before, await fs.lstat(directory, { bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${directory}`);
|
|
3574
|
+
};
|
|
3575
|
+
await visit(root, ".", rootStat);
|
|
3576
|
+
return hash.digest("hex");
|
|
3577
|
+
}
|
|
3578
|
+
async function statFingerprint(filePath) {
|
|
3579
|
+
try {
|
|
3580
|
+
const entry = await fs.lstat(filePath, { bigint: true });
|
|
3581
|
+
const type = entry.isSymbolicLink() ? "link" : entry.isDirectory() ? "directory" : entry.isFile() ? "file" : "other";
|
|
3582
|
+
const own = statTuple(entry);
|
|
3583
|
+
if (!entry.isSymbolicLink()) return `${type}:${own}:${entry.isFile() ? await readFileFingerprint(filePath, entry, false) : ""}`;
|
|
3584
|
+
const [link, realPath, target] = await Promise.all([
|
|
3585
|
+
fs.readlink(filePath),
|
|
3586
|
+
fs.realpath(filePath),
|
|
3587
|
+
fs.stat(filePath, { bigint: true })
|
|
3588
|
+
]);
|
|
3589
|
+
const content = target.isFile() ? await readFileFingerprint(filePath, target, true) : "";
|
|
3590
|
+
return `${type}:${own}:${link}:${realPath}:${statTuple(target)}:${content}`;
|
|
3591
|
+
} catch (error) {
|
|
3592
|
+
if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
|
|
3593
|
+
throw error;
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
async function readFileFingerprint(filePath, expected, followsSymlink) {
|
|
3597
|
+
const noFollow = followsSymlink ? 0 : constants.O_NOFOLLOW ?? 0;
|
|
3598
|
+
const handle = await fs.open(filePath, constants.O_RDONLY | noFollow);
|
|
3599
|
+
try {
|
|
3600
|
+
const before = await handle.stat({ bigint: true });
|
|
3601
|
+
if (!sameStat(before, expected)) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
|
|
3602
|
+
const hash = createHash("sha256");
|
|
3603
|
+
for await (const chunk of handle.createReadStream({ autoClose: false })) hash.update(chunk);
|
|
3604
|
+
if (!sameStat(before, await handle.stat({ bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
|
|
3605
|
+
return hash.digest("hex");
|
|
3606
|
+
} finally {
|
|
3607
|
+
await handle.close();
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
function sameStat(left, right) {
|
|
3611
|
+
return statTuple(left) === statTuple(right);
|
|
3612
|
+
}
|
|
3613
|
+
function statTuple(stat) {
|
|
3614
|
+
return [
|
|
3615
|
+
stat.dev,
|
|
3616
|
+
stat.ino,
|
|
3617
|
+
stat.mode,
|
|
3618
|
+
stat.size,
|
|
3619
|
+
stat.mtimeNs,
|
|
3620
|
+
stat.ctimeNs
|
|
3621
|
+
].join(":");
|
|
3622
|
+
}
|
|
3623
|
+
function isNodeError(error, code) {
|
|
3624
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
3625
|
+
}
|
|
3626
|
+
//#endregion
|
|
3627
|
+
//#region extensions/codex/src/app-server/desktop-generation-owner.ts
|
|
3628
|
+
const SETTLE_DELAY_MS = 1e3;
|
|
3629
|
+
/** Coalesces filesystem invalidations into one stable desktop generation. */
|
|
3630
|
+
function createCodexDesktopGenerationOwner(params) {
|
|
3631
|
+
let generation = params.initialGeneration;
|
|
3632
|
+
let invalidation = 0;
|
|
3633
|
+
let dirty = false;
|
|
3634
|
+
let refresh;
|
|
3635
|
+
let stopped = false;
|
|
3636
|
+
const markDirty = () => {
|
|
3637
|
+
invalidation += 1;
|
|
3638
|
+
dirty = true;
|
|
3639
|
+
};
|
|
3640
|
+
const reconcile = () => {
|
|
3641
|
+
if (refresh) return refresh;
|
|
3642
|
+
refresh = (async () => {
|
|
3643
|
+
for (;;) {
|
|
3644
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3645
|
+
const observedInvalidation = invalidation;
|
|
3646
|
+
const first = await params.readFingerprint();
|
|
3647
|
+
await delay(SETTLE_DELAY_MS);
|
|
3648
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3649
|
+
const second = await params.readFingerprint();
|
|
3650
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3651
|
+
if (observedInvalidation !== invalidation || first !== second) continue;
|
|
3652
|
+
const previous = generation;
|
|
3653
|
+
generation = previous?.fingerprint === second ? previous : {
|
|
3654
|
+
epoch: (previous?.epoch ?? 0) + 1,
|
|
3655
|
+
fingerprint: second
|
|
3656
|
+
};
|
|
3657
|
+
dirty = false;
|
|
3658
|
+
if (previous && generation !== previous) params.onGenerationChange?.(generation);
|
|
3659
|
+
return generation;
|
|
3660
|
+
}
|
|
3661
|
+
})().finally(() => {
|
|
3662
|
+
refresh = void 0;
|
|
3663
|
+
});
|
|
3664
|
+
return refresh;
|
|
3665
|
+
};
|
|
3666
|
+
return {
|
|
3667
|
+
read: () => generation,
|
|
3668
|
+
markDirty,
|
|
3669
|
+
wait: () => dirty ? reconcile() : Promise.resolve(generation),
|
|
3670
|
+
refresh: () => {
|
|
3671
|
+
markDirty();
|
|
3672
|
+
return reconcile();
|
|
3673
|
+
},
|
|
3674
|
+
isCurrent: (candidate) => Boolean(candidate && !dirty && generation && candidate.epoch === generation.epoch && candidate.fingerprint === generation.fingerprint),
|
|
3675
|
+
stop: () => {
|
|
3676
|
+
stopped = true;
|
|
3677
|
+
}
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
function delay(ms) {
|
|
3681
|
+
return new Promise((resolve) => {
|
|
3682
|
+
setTimeout(resolve, ms);
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3685
|
+
//#endregion
|
|
3686
|
+
//#region extensions/codex/src/app-server/desktop-generation.ts
|
|
3687
|
+
/** Lifecycle-owned generation for managed macOS Codex desktop artifacts. */
|
|
3688
|
+
const APPLICATIONS_PATH = "/Applications";
|
|
3689
|
+
const REARM_INITIAL_DELAY_MS = 100;
|
|
3690
|
+
const REARM_MAX_DELAY_MS = 3e4;
|
|
3691
|
+
const DESKTOP_GENERATION_STATE = Symbol.for("openclaw.codexDesktopGenerationState");
|
|
3692
|
+
function state() {
|
|
3693
|
+
const globalState = globalThis;
|
|
3694
|
+
return globalState[DESKTOP_GENERATION_STATE] ??= {};
|
|
3695
|
+
}
|
|
3696
|
+
function waitForCodexDesktopGeneration() {
|
|
3697
|
+
return state().owner?.wait() ?? Promise.resolve(void 0);
|
|
3698
|
+
}
|
|
3699
|
+
function isCodexDesktopGenerationCurrent(generation) {
|
|
3700
|
+
return state().owner?.isCurrent(generation) ?? false;
|
|
3701
|
+
}
|
|
3702
|
+
function createCodexDesktopGenerationService(params, runtime = {
|
|
3703
|
+
platform: process.platform,
|
|
3704
|
+
readFingerprint: readMacOSDesktopGenerationFingerprint,
|
|
3705
|
+
resolveWatchPaths: resolveMacOSDesktopGenerationWatchPaths,
|
|
3706
|
+
pathExists: existsSync,
|
|
3707
|
+
watchPath: (watchedPath, options, listener) => watch(watchedPath, options, listener)
|
|
3708
|
+
}) {
|
|
3709
|
+
return {
|
|
3710
|
+
id: "codex-desktop-generation",
|
|
3711
|
+
async start(ctx) {
|
|
3712
|
+
if (runtime.platform !== "darwin") return;
|
|
3713
|
+
const current = state();
|
|
3714
|
+
current.context = ctx;
|
|
3715
|
+
current.readFingerprint = runtime.readFingerprint;
|
|
3716
|
+
current.resolveWatchPaths = runtime.resolveWatchPaths;
|
|
3717
|
+
current.pathExists = runtime.pathExists;
|
|
3718
|
+
current.watchPath = runtime.watchPath;
|
|
3719
|
+
current.owner = createCodexDesktopGenerationOwner({
|
|
3720
|
+
readFingerprint: current.readFingerprint,
|
|
3721
|
+
onGenerationChange: params.onGenerationChange,
|
|
3722
|
+
initialGeneration: current.lastGeneration
|
|
3723
|
+
});
|
|
3724
|
+
armWatchers(current);
|
|
3725
|
+
refreshGeneration(current, current.owner, current.owner.refresh());
|
|
3726
|
+
},
|
|
3727
|
+
async stop() {
|
|
3728
|
+
const current = state();
|
|
3729
|
+
current.lastGeneration = current.owner?.read() ?? current.lastGeneration;
|
|
3730
|
+
current.owner?.stop();
|
|
3731
|
+
current.owner = void 0;
|
|
3732
|
+
current.armEpoch = (current.armEpoch ?? 0) + 1;
|
|
3733
|
+
current.context = void 0;
|
|
3734
|
+
current.readFingerprint = void 0;
|
|
3735
|
+
current.resolveWatchPaths = void 0;
|
|
3736
|
+
current.pathExists = void 0;
|
|
3737
|
+
current.watchPath = void 0;
|
|
3738
|
+
current.watchHealthy = void 0;
|
|
3739
|
+
current.rearmDelayMs = void 0;
|
|
3740
|
+
if (current.rearmTimer) {
|
|
3741
|
+
clearTimeout(current.rearmTimer);
|
|
3742
|
+
current.rearmTimer = void 0;
|
|
3743
|
+
}
|
|
3744
|
+
closeWatchers(current);
|
|
3745
|
+
}
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
function armWatchers(current) {
|
|
3749
|
+
const owner = current.owner;
|
|
3750
|
+
if (!owner || current.watchers) return false;
|
|
3751
|
+
const armEpoch = (current.armEpoch ?? 0) + 1;
|
|
3752
|
+
current.armEpoch = armEpoch;
|
|
3753
|
+
const watchers = /* @__PURE__ */ new Set();
|
|
3754
|
+
current.watchers = watchers;
|
|
3755
|
+
const candidateNames = new Set(resolveMacOSDesktopCodexAppPathCandidates("darwin").map((candidate) => candidate.appName));
|
|
3756
|
+
let complete = true;
|
|
3757
|
+
for (const watchedPath of current.resolveWatchPaths?.() ?? []) {
|
|
3758
|
+
if (!current.pathExists?.(watchedPath)) continue;
|
|
3759
|
+
try {
|
|
3760
|
+
const watcher = current.watchPath?.(watchedPath, { recursive: watchedPath !== APPLICATIONS_PATH }, (_eventType, filename) => {
|
|
3761
|
+
if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
|
|
3762
|
+
if (watchedPath === APPLICATIONS_PATH && filename && !candidateNames.has(filename.toString().split(path.sep)[0] ?? "")) return;
|
|
3763
|
+
owner.markDirty();
|
|
3764
|
+
scheduleRearm(current, owner);
|
|
3765
|
+
});
|
|
3766
|
+
if (!watcher) {
|
|
3767
|
+
complete = false;
|
|
3768
|
+
reportWatcherFailure(current, owner, /* @__PURE__ */ new Error(`Could not watch ${watchedPath}`));
|
|
3769
|
+
scheduleRearm(current, owner);
|
|
3770
|
+
continue;
|
|
3771
|
+
}
|
|
3772
|
+
watchers.add(watcher);
|
|
3773
|
+
watcher.on("error", (error) => {
|
|
3774
|
+
if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
|
|
3775
|
+
reportWatcherFailure(current, owner, error);
|
|
3776
|
+
scheduleRearm(current, owner);
|
|
3777
|
+
});
|
|
3778
|
+
} catch (error) {
|
|
3779
|
+
complete = false;
|
|
3780
|
+
reportWatcherFailure(current, owner, error);
|
|
3781
|
+
scheduleRearm(current, owner);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
current.watchHealthy = complete;
|
|
3785
|
+
if (complete) current.rearmDelayMs = REARM_INITIAL_DELAY_MS;
|
|
3786
|
+
return complete;
|
|
3787
|
+
}
|
|
3788
|
+
function reportWatcherFailure(current, owner, error) {
|
|
3789
|
+
if (current.watchHealthy === false) return;
|
|
3790
|
+
current.watchHealthy = false;
|
|
3791
|
+
owner.markDirty();
|
|
3792
|
+
current.context?.serviceHealth?.reportFailure(error);
|
|
3793
|
+
current.context?.logger.warn(`codex desktop generation watcher failed: ${String(error)}`);
|
|
3794
|
+
}
|
|
3795
|
+
function isCurrentArm(current, owner, watchers, armEpoch) {
|
|
3796
|
+
return current.owner === owner && current.watchers === watchers && current.armEpoch === armEpoch;
|
|
3797
|
+
}
|
|
3798
|
+
function scheduleRearm(current, owner) {
|
|
3799
|
+
if (current.rearmTimer) {
|
|
3800
|
+
if (current.watchHealthy === false) return;
|
|
3801
|
+
clearTimeout(current.rearmTimer);
|
|
3802
|
+
}
|
|
3803
|
+
const delayMs = current.watchHealthy === false ? current.rearmDelayMs ?? REARM_INITIAL_DELAY_MS : REARM_INITIAL_DELAY_MS;
|
|
3804
|
+
if (current.watchHealthy === false) current.rearmDelayMs = Math.min(delayMs * 2, REARM_MAX_DELAY_MS);
|
|
3805
|
+
current.rearmTimer = setTimeout(() => {
|
|
3806
|
+
current.rearmTimer = void 0;
|
|
3807
|
+
if (current.owner !== owner) return;
|
|
3808
|
+
const wasUnhealthy = current.watchHealthy === false;
|
|
3809
|
+
closeWatchers(current);
|
|
3810
|
+
if (!armWatchers(current) || wasUnhealthy) owner.markDirty();
|
|
3811
|
+
refreshGeneration(current, owner, owner.wait());
|
|
3812
|
+
}, delayMs);
|
|
3813
|
+
current.rearmTimer.unref();
|
|
3814
|
+
}
|
|
3815
|
+
function logRefreshFailure(current, owner) {
|
|
3816
|
+
return (error) => {
|
|
3817
|
+
if (current.owner !== owner) return;
|
|
3818
|
+
current.context?.serviceHealth?.reportFailure(error);
|
|
3819
|
+
current.context?.logger.warn(`codex desktop generation refresh failed: ${String(error)}`);
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
function refreshGeneration(current, owner, refresh) {
|
|
3823
|
+
refresh.then(() => {
|
|
3824
|
+
if (current.owner === owner && current.watchHealthy) current.context?.serviceHealth?.clearFailure();
|
|
3825
|
+
}).catch(logRefreshFailure(current, owner));
|
|
3826
|
+
}
|
|
3827
|
+
function closeWatchers(current) {
|
|
3828
|
+
const watchers = current.watchers;
|
|
3829
|
+
current.watchers = void 0;
|
|
3830
|
+
for (const watcher of watchers ?? []) watcher.close();
|
|
3831
|
+
}
|
|
3832
|
+
//#endregion
|
|
3081
3833
|
//#region extensions/codex/src/app-server/native-config-fence.ts
|
|
3082
3834
|
const CODEX_NATIVE_CONFIG_FENCE_STATE = Symbol.for("openclaw.codexNativeConfigFenceState");
|
|
3083
3835
|
function getFenceState() {
|
|
@@ -3140,37 +3892,31 @@ async function waitForPreviousFence(previous, options) {
|
|
|
3140
3892
|
});
|
|
3141
3893
|
}
|
|
3142
3894
|
//#endregion
|
|
3143
|
-
//#region extensions/codex/src/app-server/
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
function resolveAbortError(signal) {
|
|
3149
|
-
return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
|
|
3150
|
-
}
|
|
3151
|
-
/** Awaits a promise with a Codex-specific timeout error message. */
|
|
3152
|
-
async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
|
|
3153
|
-
return await withTimeout(promise, timeoutMs, {
|
|
3154
|
-
message: timeoutMessage,
|
|
3155
|
-
...createError ? { createError } : {}
|
|
3156
|
-
});
|
|
3157
|
-
}
|
|
3158
|
-
/** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
|
|
3159
|
-
async function withAbortableTimeout(params) {
|
|
3160
|
-
const signal = params.signal;
|
|
3161
|
-
if (signal?.aborted) throw resolveAbortError(signal);
|
|
3162
|
-
let removeAbortListener;
|
|
3163
|
-
const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
|
|
3164
|
-
const onAbort = () => reject(resolveAbortError(signal));
|
|
3165
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
3166
|
-
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
3167
|
-
})]) : params.promise;
|
|
3168
|
-
try {
|
|
3169
|
-
return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
|
|
3170
|
-
} finally {
|
|
3171
|
-
removeAbortListener?.();
|
|
3895
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
|
|
3896
|
+
var CodexThreadStartRequestError = class extends Error {
|
|
3897
|
+
constructor(cause) {
|
|
3898
|
+
super(`thread/start: ${formatErrorMessage(cause)}`, { cause });
|
|
3899
|
+
this.name = "CodexThreadStartRequestError";
|
|
3172
3900
|
}
|
|
3173
|
-
}
|
|
3901
|
+
};
|
|
3902
|
+
var CodexThreadBindingConflictError = class extends Error {
|
|
3903
|
+
constructor(threadId, operation) {
|
|
3904
|
+
super(`Codex thread binding changed while ${operation}: ${threadId}`);
|
|
3905
|
+
this.name = "CodexThreadBindingConflictError";
|
|
3906
|
+
}
|
|
3907
|
+
};
|
|
3908
|
+
var CodexRestrictedToolSurfaceAttestationError = class extends Error {
|
|
3909
|
+
constructor(cause) {
|
|
3910
|
+
super("Codex restricted-tool-surface MCP attestation failed", { cause });
|
|
3911
|
+
this.name = "CodexRestrictedToolSurfaceAttestationError";
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
var CodexAdoptedThreadActiveError = class extends Error {
|
|
3915
|
+
constructor() {
|
|
3916
|
+
super("Codex session became active in another runner; wait for it to finish before continuing");
|
|
3917
|
+
this.name = "CodexAdoptedThreadActiveError";
|
|
3918
|
+
}
|
|
3919
|
+
};
|
|
3174
3920
|
//#endregion
|
|
3175
3921
|
//#region extensions/codex/src/app-server/shared-client.ts
|
|
3176
3922
|
/**
|
|
@@ -3179,6 +3925,8 @@ async function withAbortableTimeout(params) {
|
|
|
3179
3925
|
*/
|
|
3180
3926
|
var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
3181
3927
|
assertCodexAppServerClientStartSelectionCurrent: () => assertCodexAppServerClientStartSelectionCurrent,
|
|
3928
|
+
captureExclusiveSharedCodexAppServerClient: () => captureExclusiveSharedCodexAppServerClient,
|
|
3929
|
+
captureSharedCodexAppServerCatalogLifetime: () => captureSharedCodexAppServerCatalogLifetime,
|
|
3182
3930
|
clearSharedCodexAppServerClientAndWait: () => clearSharedCodexAppServerClientAndWait,
|
|
3183
3931
|
clearSharedCodexAppServerClientIfCurrent: () => clearSharedCodexAppServerClientIfCurrent,
|
|
3184
3932
|
clearSharedCodexAppServerClientIfCurrentAndUnclaimed: () => clearSharedCodexAppServerClientIfCurrentAndUnclaimed,
|
|
@@ -3187,6 +3935,9 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
|
3187
3935
|
getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
|
|
3188
3936
|
getSharedCodexAppServerClient: () => getSharedCodexAppServerClient,
|
|
3189
3937
|
isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
|
|
3938
|
+
readCodexAppServerClientDesktopGeneration: () => readCodexAppServerClientDesktopGeneration,
|
|
3939
|
+
readCodexAppServerClientDesktopGenerationFingerprint: () => readCodexAppServerClientDesktopGenerationFingerprint,
|
|
3940
|
+
readCodexAppServerClientProcessIdentity: () => readCodexAppServerClientProcessIdentity,
|
|
3190
3941
|
releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
|
|
3191
3942
|
releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
|
|
3192
3943
|
resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
|
|
@@ -3194,6 +3945,8 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
|
3194
3945
|
retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
|
|
3195
3946
|
retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
|
|
3196
3947
|
retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
|
|
3948
|
+
retireSharedCodexAppServerClientsBeforeDesktopGeneration: () => retireSharedCodexAppServerClientsBeforeDesktopGeneration,
|
|
3949
|
+
waitForCodexAppServerClientDesktopGenerationDrain: () => waitForCodexAppServerClientDesktopGenerationDrain,
|
|
3197
3950
|
withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
|
|
3198
3951
|
});
|
|
3199
3952
|
const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
|
|
@@ -3205,8 +3958,10 @@ function getSharedCodexAppServerClientState() {
|
|
|
3205
3958
|
globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
|
|
3206
3959
|
clients: /* @__PURE__ */ new Map(),
|
|
3207
3960
|
liveClients: /* @__PURE__ */ new Set(),
|
|
3961
|
+
isolatedClients: /* @__PURE__ */ new Set(),
|
|
3208
3962
|
entriesByClient: /* @__PURE__ */ new WeakMap(),
|
|
3209
|
-
leasedReleases: /* @__PURE__ */ new WeakMap()
|
|
3963
|
+
leasedReleases: /* @__PURE__ */ new WeakMap(),
|
|
3964
|
+
desktopGenerationDrainChecks: /* @__PURE__ */ new Set()
|
|
3210
3965
|
};
|
|
3211
3966
|
return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
|
|
3212
3967
|
}
|
|
@@ -3215,6 +3970,41 @@ function getCodexAppServerClientStartMetadata() {
|
|
|
3215
3970
|
globalState[CODEX_APP_SERVER_CLIENT_START_METADATA] ??= /* @__PURE__ */ new WeakMap();
|
|
3216
3971
|
return globalState[CODEX_APP_SERVER_CLIENT_START_METADATA];
|
|
3217
3972
|
}
|
|
3973
|
+
/** Reads the exact successful spawn selection plus its initialized runtime identity. */
|
|
3974
|
+
function readCodexAppServerClientProcessIdentity(client) {
|
|
3975
|
+
const metadata = getCodexAppServerClientStartMetadata().get(client);
|
|
3976
|
+
if (!metadata) return;
|
|
3977
|
+
const runtimeIdentity = client.getRuntimeIdentity();
|
|
3978
|
+
return {
|
|
3979
|
+
clientId: client.getInstanceId(),
|
|
3980
|
+
...resolveCodexAppServerSpawnIdentity(metadata.startOptions, metadata.nativeCommand),
|
|
3981
|
+
...runtimeIdentity?.serverVersion ? { serverVersion: runtimeIdentity.serverVersion } : {},
|
|
3982
|
+
...runtimeIdentity?.userAgent ? { userAgent: runtimeIdentity.userAgent } : {}
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
/** Returns the lifecycle fingerprint that owns a managed desktop client. */
|
|
3986
|
+
function readCodexAppServerClientDesktopGenerationFingerprint(client) {
|
|
3987
|
+
return readCodexAppServerClientDesktopGeneration(client)?.fingerprint;
|
|
3988
|
+
}
|
|
3989
|
+
/** Returns the lifecycle generation that owns a managed desktop client. */
|
|
3990
|
+
function readCodexAppServerClientDesktopGeneration(client) {
|
|
3991
|
+
return getCodexAppServerClientStartMetadata().get(client)?.desktopGeneration;
|
|
3992
|
+
}
|
|
3993
|
+
/** Waits until older physical desktop clients for this client's Codex home exit. */
|
|
3994
|
+
async function waitForCodexAppServerClientDesktopGenerationDrain(params) {
|
|
3995
|
+
const metadata = getCodexAppServerClientStartMetadata().get(params.client);
|
|
3996
|
+
if (!metadata?.desktopGeneration) return;
|
|
3997
|
+
const drain = createOlderDesktopGenerationDrainWait({
|
|
3998
|
+
generation: metadata.desktopGeneration,
|
|
3999
|
+
startOptions: metadata.startOptions,
|
|
4000
|
+
agentDir: metadata.agentDir
|
|
4001
|
+
});
|
|
4002
|
+
try {
|
|
4003
|
+
await withCodexAppServerAcquireDeadline(params.timeoutMs ?? 0, drain.promise, params.signal, "Codex Computer Use install timed out waiting for older desktop clients");
|
|
4004
|
+
} finally {
|
|
4005
|
+
drain.cancel();
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
3218
4008
|
/** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
|
|
3219
4009
|
function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
|
|
3220
4010
|
const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
|
|
@@ -3245,6 +4035,7 @@ function isCodexAppServerStartSelectionChangedError(error) {
|
|
|
3245
4035
|
function assertCodexAppServerClientStartSelectionCurrent(params) {
|
|
3246
4036
|
const metadata = getCodexAppServerClientStartMetadata().get(params.client);
|
|
3247
4037
|
if (!metadata) return;
|
|
4038
|
+
if (metadata.desktopGeneration && !isCodexDesktopGenerationCurrent(metadata.desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
|
|
3248
4039
|
const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
|
|
3249
4040
|
if (requestedStartOptions.commandSource !== "managed") return;
|
|
3250
4041
|
const current = resolveCodexAppServerStartOptionsForAgent({
|
|
@@ -3256,7 +4047,7 @@ function assertCodexAppServerClientStartSelectionCurrent(params) {
|
|
|
3256
4047
|
/** Resolves the per-CODEX_HOME key used to serialize native config loading. */
|
|
3257
4048
|
function resolveCodexNativeConfigFenceKey(params) {
|
|
3258
4049
|
const metadata = params.client ? getCodexAppServerClientStartMetadata().get(params.client) : void 0;
|
|
3259
|
-
const startOptions =
|
|
4050
|
+
const startOptions = metadata?.startOptions ?? params.startOptions;
|
|
3260
4051
|
if (!startOptions || startOptions.transport !== "stdio") return;
|
|
3261
4052
|
const configuredHome = startOptions.env?.CODEX_HOME?.trim();
|
|
3262
4053
|
const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
|
|
@@ -3270,10 +4061,11 @@ function inferAuthRequirement(preparedAuth) {
|
|
|
3270
4061
|
async function resolveCodexAppServerClientStartContext(options) {
|
|
3271
4062
|
const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
|
|
3272
4063
|
const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
|
|
4064
|
+
const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions, options?.pluginConfig) ? await waitForCodexDesktopGeneration() : void 0;
|
|
3273
4065
|
const preparedAuth = options?.preparedAuth;
|
|
3274
4066
|
const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
|
|
3275
4067
|
if (preparedAuth && options?.authProfileId !== void 0) throw new Error("Prepared Codex auth cannot also select a legacy auth profile.");
|
|
3276
|
-
if (preparedAuth?.kind === "profile" && !preparedAuth.store.profiles[preparedAuth.profileId]) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" was not found.`);
|
|
4068
|
+
if (preparedAuth?.kind === "profile" && !preparedAuth.store.profiles[preparedAuth.profileId]) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" was not found. Select an existing OpenAI profile or sign in again with OpenClaw, then retry.`);
|
|
3277
4069
|
if (preparedAuth?.kind === "api-key" && !preparedApiKey) throw new Error("Prepared Codex API-key auth is missing its resolved key.");
|
|
3278
4070
|
if (preparedAuth && requestedStartOptions.homeScope === "user") throw new Error("Prepared Codex auth requires an isolated app-server home.");
|
|
3279
4071
|
const preparedAuthRequirement = inferAuthRequirement(preparedAuth);
|
|
@@ -3281,11 +4073,11 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
3281
4073
|
const authRequirement = options?.authRequirement ?? preparedAuthRequirement;
|
|
3282
4074
|
const usesNativeAuth = !preparedAuth && (options?.authProfileId === null || requestedStartOptions.homeScope === "user");
|
|
3283
4075
|
const requestedAuthProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : options?.authProfileId ?? void 0;
|
|
3284
|
-
const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth &&
|
|
4076
|
+
const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && preparedAuth?.kind !== "api-key" ? resolveCodexAppServerAuthProfileStore({
|
|
3285
4077
|
agentDir,
|
|
3286
4078
|
authProfileId: requestedAuthProfileId,
|
|
3287
|
-
authProfileStore: options
|
|
3288
|
-
config: options
|
|
4079
|
+
authProfileStore: options?.authProfileStore,
|
|
4080
|
+
config: options?.config
|
|
3289
4081
|
}) : options?.authProfileStore;
|
|
3290
4082
|
const authProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : usesNativeAuth || preparedAuth?.kind === "api-key" ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
|
|
3291
4083
|
authProfileId: requestedAuthProfileId,
|
|
@@ -3293,18 +4085,20 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
3293
4085
|
config: options?.config,
|
|
3294
4086
|
...authProfileStore ? { authProfileStore } : {}
|
|
3295
4087
|
});
|
|
3296
|
-
const preparedAuthProfileSnapshot = preparedAuth?.kind === "profile" ? preparedAuth.snapshot ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
|
|
4088
|
+
const preparedAuthProfileSnapshot = !usesNativeAuth && authProfileId ? (preparedAuth?.kind === "profile" ? preparedAuth.snapshot : void 0) ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
|
|
3297
4089
|
authProfileId,
|
|
3298
4090
|
authProfileStore,
|
|
3299
4091
|
agentDir,
|
|
3300
4092
|
config: options?.config
|
|
3301
4093
|
}) : void 0;
|
|
3302
|
-
if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable.`);
|
|
4094
|
+
if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable. Repair or replace the selected OpenAI profile, then retry.`);
|
|
3303
4095
|
const resolvedPreparedAuth = preparedAuth?.kind === "api-key" ? {
|
|
3304
4096
|
kind: "api-key",
|
|
3305
4097
|
apiKey: preparedApiKey
|
|
3306
|
-
} :
|
|
3307
|
-
|
|
4098
|
+
} : preparedAuthProfileSnapshot && authProfileId && authProfileStore ? {
|
|
4099
|
+
kind: "profile",
|
|
4100
|
+
profileId: authProfileId,
|
|
4101
|
+
store: authProfileStore,
|
|
3308
4102
|
snapshot: preparedAuthProfileSnapshot
|
|
3309
4103
|
} : void 0;
|
|
3310
4104
|
return {
|
|
@@ -3323,51 +4117,20 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
3323
4117
|
agentId: options?.agentId,
|
|
3324
4118
|
agentDir,
|
|
3325
4119
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3326
|
-
...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
|
|
4120
|
+
...preparedAuth && resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
|
|
3327
4121
|
authRequirement,
|
|
3328
4122
|
config: options?.config,
|
|
3329
4123
|
pluginConfig: options?.pluginConfig,
|
|
3330
4124
|
...authProfileStore ? { authProfileStore } : {}
|
|
3331
|
-
})
|
|
3332
|
-
|
|
3333
|
-
}
|
|
3334
|
-
function resolveWarmSharedCodexAppServerClientIdentity(options) {
|
|
3335
|
-
if (!options?.config || typeof options.config !== "object" || !options.startOptions || options.pluginConfig !== void 0 || options.authProfileStore !== void 0 || options.preparedAuth !== void 0 || options.runtimeArtifactMode !== void 0 || options.expectedRuntimeArtifact !== void 0) return;
|
|
3336
|
-
const authProfileSelector = options.authProfileId === null ? ["native"] : options.authProfileId === void 0 ? ["implicit"] : ["profile", options.authProfileId];
|
|
3337
|
-
return {
|
|
3338
|
-
config: options.config,
|
|
3339
|
-
startOptions: options.startOptions,
|
|
3340
|
-
selectorKey: JSON.stringify([
|
|
3341
|
-
options.agentDir ?? resolveDefaultAgentDir(options.config),
|
|
3342
|
-
authProfileSelector,
|
|
3343
|
-
options.authBindingFingerprint ?? null,
|
|
3344
|
-
options.authRequirement ?? null
|
|
3345
|
-
])
|
|
4125
|
+
}),
|
|
4126
|
+
...options?.pluginConfig !== void 0 ? { pluginConfig: options.pluginConfig } : {},
|
|
4127
|
+
...desktopGeneration ? { desktopGeneration } : {}
|
|
3346
4128
|
};
|
|
3347
4129
|
}
|
|
3348
|
-
function
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
if (state.clients.get(warm.key) !== warm.entry || warm.entry.client !== warm.client || warm.entry.closeWhenIdle || warm.entry.closeError) {
|
|
3353
|
-
aliases?.delete(identity.selectorKey);
|
|
3354
|
-
return;
|
|
3355
|
-
}
|
|
3356
|
-
return warm;
|
|
3357
|
-
}
|
|
3358
|
-
function rememberWarmSharedCodexAppServerClient(state, identity, warm) {
|
|
3359
|
-
state.warmClientsByConfig ??= /* @__PURE__ */ new WeakMap();
|
|
3360
|
-
let byStartOptions = state.warmClientsByConfig.get(identity.config);
|
|
3361
|
-
if (!byStartOptions) {
|
|
3362
|
-
byStartOptions = /* @__PURE__ */ new WeakMap();
|
|
3363
|
-
state.warmClientsByConfig.set(identity.config, byStartOptions);
|
|
3364
|
-
}
|
|
3365
|
-
let aliases = byStartOptions.get(identity.startOptions);
|
|
3366
|
-
if (!aliases) {
|
|
3367
|
-
aliases = /* @__PURE__ */ new Map();
|
|
3368
|
-
byStartOptions.set(identity.startOptions, aliases);
|
|
3369
|
-
}
|
|
3370
|
-
aliases.set(identity.selectorKey, warm);
|
|
4130
|
+
function shouldTrackDesktopGeneration(startOptions, pluginConfig) {
|
|
4131
|
+
if (startOptions.transport !== "stdio") return false;
|
|
4132
|
+
if (resolveCodexComputerUseConfig({ pluginConfig }).enabled && (startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed")) return true;
|
|
4133
|
+
return startOptions.commandSource === "managed" && (startOptions.managedCommandOrder ?? "package-first") === "desktop-first";
|
|
3371
4134
|
}
|
|
3372
4135
|
/** Gets or starts a shared Codex app-server client without retaining a lease. */
|
|
3373
4136
|
async function getSharedCodexAppServerClient(options) {
|
|
@@ -3399,7 +4162,7 @@ function releaseCodexAppServerClientLease(lease) {
|
|
|
3399
4162
|
lease.client = void 0;
|
|
3400
4163
|
return client ? releaseLeasedSharedCodexAppServerClient(client) : false;
|
|
3401
4164
|
}
|
|
3402
|
-
/** Retries one config-loading
|
|
4165
|
+
/** Retries one config-loading operation with a shared deadline and current client lease. */
|
|
3403
4166
|
async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
|
|
3404
4167
|
let client = params.lease.client;
|
|
3405
4168
|
if (!client) throw new Error("Codex app-server selection retry requires an active client lease");
|
|
@@ -3415,43 +4178,49 @@ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
|
|
|
3415
4178
|
...signal ? { signal } : {}
|
|
3416
4179
|
};
|
|
3417
4180
|
};
|
|
3418
|
-
for (let attempt = 0; attempt < 2; attempt += 1)
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
4181
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4182
|
+
const attemptClient = client;
|
|
4183
|
+
let scopeActive = true;
|
|
4184
|
+
const assertCurrent = () => {
|
|
4185
|
+
if (!scopeActive || params.lease.client !== attemptClient) throw new CodexAppServerStartupError("aborted", "Codex app-server request scope is closed");
|
|
4186
|
+
};
|
|
4187
|
+
try {
|
|
4188
|
+
requestOptions();
|
|
4189
|
+
return await params.run(client, () => {
|
|
4190
|
+
assertCurrent();
|
|
4191
|
+
return {
|
|
4192
|
+
...requestOptions(),
|
|
4193
|
+
assertCurrent
|
|
4194
|
+
};
|
|
4195
|
+
});
|
|
4196
|
+
} catch (error) {
|
|
4197
|
+
if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
|
|
4198
|
+
retireSharedCodexAppServerClientIfCurrent(client);
|
|
4199
|
+
params.lease.client = void 0;
|
|
4200
|
+
if (!releaseLeasedSharedCodexAppServerClient(client)) {
|
|
4201
|
+
client.close();
|
|
4202
|
+
throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
|
|
4203
|
+
}
|
|
4204
|
+
const replacementOptions = requestOptions();
|
|
4205
|
+
client = await getLeasedSharedCodexAppServerClient({
|
|
4206
|
+
...params.options,
|
|
4207
|
+
timeoutMs: replacementOptions.timeoutMs,
|
|
4208
|
+
...signal ? { abandonSignal: signal } : {}
|
|
4209
|
+
});
|
|
4210
|
+
params.lease.client = client;
|
|
4211
|
+
params.onClientChange?.(client);
|
|
4212
|
+
} finally {
|
|
4213
|
+
scopeActive = false;
|
|
3427
4214
|
}
|
|
3428
|
-
const replacementOptions = requestOptions();
|
|
3429
|
-
client = await getLeasedSharedCodexAppServerClient({
|
|
3430
|
-
...params.options,
|
|
3431
|
-
timeoutMs: replacementOptions.timeoutMs,
|
|
3432
|
-
...signal ? { abandonSignal: signal } : {}
|
|
3433
|
-
});
|
|
3434
|
-
params.lease.client = client;
|
|
3435
|
-
params.onClientChange(client);
|
|
3436
4215
|
}
|
|
3437
4216
|
throw new Error("Codex app-server selection retry loop exited unexpectedly");
|
|
3438
4217
|
}
|
|
3439
4218
|
async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
3440
4219
|
if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
3441
|
-
const state = getSharedCodexAppServerClientState();
|
|
3442
|
-
const warmIdentity = resolveWarmSharedCodexAppServerClientIdentity(options);
|
|
3443
|
-
const warmClient = warmIdentity ? readWarmSharedCodexAppServerClient(state, warmIdentity) : void 0;
|
|
3444
|
-
if (warmClient) {
|
|
3445
|
-
options?.onStartedClient?.(warmClient.client);
|
|
3446
|
-
const release = leaseOptions?.leased ? retainSharedClientEntry(warmClient.entry) : void 0;
|
|
3447
|
-
return release ? {
|
|
3448
|
-
client: warmClient.client,
|
|
3449
|
-
release
|
|
3450
|
-
} : { client: warmClient.client };
|
|
3451
|
-
}
|
|
3452
4220
|
const acquireStartedAt = Date.now();
|
|
3453
4221
|
const timeoutMs = options?.timeoutMs ?? 0;
|
|
3454
|
-
const
|
|
4222
|
+
const state = getSharedCodexAppServerClientState();
|
|
4223
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
3455
4224
|
const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
|
|
3456
4225
|
const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authRequirement === "api-key" && !authProfileId ? resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }) : void 0);
|
|
3457
4226
|
const baseKey = `${codexAppServerStartOptionsKey(startOptions, {
|
|
@@ -3459,12 +4228,18 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3459
4228
|
authBindingFingerprint: options?.authBindingFingerprint,
|
|
3460
4229
|
agentDir: usesNativeAuth ? void 0 : agentDir,
|
|
3461
4230
|
fallbackApiKeyCacheKey: authIdentityCacheKey
|
|
3462
|
-
})}\0auth-requirement:${authRequirement ?? "native"}`;
|
|
4231
|
+
})}\0auth-requirement:${authRequirement ?? "native"}${desktopGeneration ? `\0desktop-generation:${desktopGeneration.epoch}` : ""}`;
|
|
3463
4232
|
const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
|
|
3464
4233
|
const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
|
|
3465
4234
|
const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
|
|
3466
|
-
|
|
3467
|
-
|
|
4235
|
+
let entry = getOrCreateSharedClientEntry(state, key);
|
|
4236
|
+
const existingClient = entry.client;
|
|
4237
|
+
const existingGeneration = existingClient ? getCodexAppServerClientStartMetadata().get(existingClient)?.desktopGeneration : void 0;
|
|
4238
|
+
if (existingClient && existingGeneration && !isCodexDesktopGenerationCurrent(existingGeneration)) {
|
|
4239
|
+
retireSharedCodexAppServerClientIfCurrent(existingClient);
|
|
4240
|
+
entry = getOrCreateSharedClientEntry(state, key);
|
|
4241
|
+
}
|
|
4242
|
+
entry.startupAbort ??= new AbortController();
|
|
3468
4243
|
entry.closeWhenIdle = false;
|
|
3469
4244
|
const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
|
|
3470
4245
|
const startedCallback = options?.onStartedClient;
|
|
@@ -3491,6 +4266,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3491
4266
|
key,
|
|
3492
4267
|
requestedStartOptions,
|
|
3493
4268
|
startOptions,
|
|
4269
|
+
desktopGeneration,
|
|
4270
|
+
...pluginConfig !== void 0 ? { pluginConfig } : {},
|
|
3494
4271
|
agentDir,
|
|
3495
4272
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3496
4273
|
authProfileStore,
|
|
@@ -3498,7 +4275,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3498
4275
|
authRequirement,
|
|
3499
4276
|
runtimeArtifactMode,
|
|
3500
4277
|
...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
|
|
3501
|
-
runtimeArtifactSignal: entry.
|
|
4278
|
+
runtimeArtifactSignal: entry.startupAbort.signal,
|
|
4279
|
+
abandonSignal: entry.startupAbort.signal,
|
|
3502
4280
|
config: options?.config
|
|
3503
4281
|
}));
|
|
3504
4282
|
try {
|
|
@@ -3513,11 +4291,6 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3513
4291
|
config: options?.config
|
|
3514
4292
|
});
|
|
3515
4293
|
const release = leaseOptions?.leased ? retainSharedClientEntry(entry) : void 0;
|
|
3516
|
-
if (warmIdentity) rememberWarmSharedCodexAppServerClient(state, warmIdentity, {
|
|
3517
|
-
key,
|
|
3518
|
-
entry,
|
|
3519
|
-
client
|
|
3520
|
-
});
|
|
3521
4294
|
return release ? {
|
|
3522
4295
|
client,
|
|
3523
4296
|
release
|
|
@@ -3557,6 +4330,8 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3557
4330
|
const ready = startInitializedCodexAppServerClient({
|
|
3558
4331
|
requestedStartOptions: params.requestedStartOptions,
|
|
3559
4332
|
startOptions: params.startOptions,
|
|
4333
|
+
...params.desktopGeneration ? { desktopGeneration: params.desktopGeneration } : {},
|
|
4334
|
+
...params.pluginConfig !== void 0 ? { pluginConfig: params.pluginConfig } : {},
|
|
3560
4335
|
agentDir: params.agentDir,
|
|
3561
4336
|
authProfileId: params.authProfileId,
|
|
3562
4337
|
authProfileStore: params.authProfileStore,
|
|
@@ -3565,13 +4340,17 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3565
4340
|
runtimeArtifactMode: params.runtimeArtifactMode,
|
|
3566
4341
|
...params.expectedRuntimeArtifact ? { expectedRuntimeArtifact: params.expectedRuntimeArtifact } : {},
|
|
3567
4342
|
runtimeArtifactSignal: params.runtimeArtifactSignal,
|
|
4343
|
+
abandonSignal: params.abandonSignal,
|
|
3568
4344
|
config: params.config,
|
|
3569
4345
|
onStartedClient: (startedClient) => {
|
|
3570
4346
|
const state = getSharedCodexAppServerClientState();
|
|
3571
4347
|
params.entry.client = startedClient;
|
|
3572
4348
|
state.liveClients.add(startedClient);
|
|
4349
|
+
startedClient.addTransportExitHandler((exitedClient) => {
|
|
4350
|
+
state.liveClients.delete(exitedClient);
|
|
4351
|
+
notifyDesktopGenerationDrainChecks(state);
|
|
4352
|
+
});
|
|
3573
4353
|
startedClient.addCloseHandler((closedClient) => {
|
|
3574
|
-
state.liveClients.delete(closedClient);
|
|
3575
4354
|
if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
|
|
3576
4355
|
});
|
|
3577
4356
|
for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
|
|
@@ -3587,6 +4366,7 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3587
4366
|
initialized.reject(error);
|
|
3588
4367
|
throw error;
|
|
3589
4368
|
});
|
|
4369
|
+
initialized.promise.catch(() => void 0);
|
|
3590
4370
|
ready.catch(() => void 0);
|
|
3591
4371
|
return {
|
|
3592
4372
|
initialized: initialized.promise,
|
|
@@ -3598,10 +4378,12 @@ async function createIsolatedCodexAppServerClient(options) {
|
|
|
3598
4378
|
if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
3599
4379
|
const acquireStartedAt = Date.now();
|
|
3600
4380
|
const timeoutMs = options?.timeoutMs ?? 0;
|
|
3601
|
-
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
4381
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
3602
4382
|
return await startInitializedCodexAppServerClient({
|
|
3603
4383
|
requestedStartOptions,
|
|
3604
4384
|
startOptions,
|
|
4385
|
+
...desktopGeneration ? { desktopGeneration } : {},
|
|
4386
|
+
...pluginConfig !== void 0 ? { pluginConfig } : {},
|
|
3605
4387
|
agentDir,
|
|
3606
4388
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3607
4389
|
authProfileStore,
|
|
@@ -3613,7 +4395,18 @@ async function createIsolatedCodexAppServerClient(options) {
|
|
|
3613
4395
|
config: options?.config,
|
|
3614
4396
|
timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
|
|
3615
4397
|
abandonSignal: options?.abandonSignal,
|
|
3616
|
-
onStartedClient:
|
|
4398
|
+
onStartedClient: (client) => {
|
|
4399
|
+
trackIsolatedCodexAppServerClient(client);
|
|
4400
|
+
options?.onStartedClient?.(client);
|
|
4401
|
+
}
|
|
4402
|
+
});
|
|
4403
|
+
}
|
|
4404
|
+
function trackIsolatedCodexAppServerClient(client) {
|
|
4405
|
+
const state = getSharedCodexAppServerClientState();
|
|
4406
|
+
state.isolatedClients.add(client);
|
|
4407
|
+
client.addTransportExitHandler((exitedClient) => {
|
|
4408
|
+
state.isolatedClients.delete(exitedClient);
|
|
4409
|
+
notifyDesktopGenerationDrainChecks(state);
|
|
3617
4410
|
});
|
|
3618
4411
|
}
|
|
3619
4412
|
async function startInitializedCodexAppServerClient(params) {
|
|
@@ -3621,7 +4414,41 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3621
4414
|
const timeoutMs = params.timeoutMs ?? 0;
|
|
3622
4415
|
const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
|
|
3623
4416
|
for (const [index, startOptions] of startOptionsCandidates.entries()) {
|
|
3624
|
-
const
|
|
4417
|
+
const desktopGeneration = params.desktopGeneration ?? (isManagedCodexDesktopCommand(startOptions.command) ? await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), params.abandonSignal) : void 0);
|
|
4418
|
+
const assertDesktopGenerationCurrent = () => {
|
|
4419
|
+
if (params.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
4420
|
+
if (desktopGeneration && !isCodexDesktopGenerationCurrent(desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
|
|
4421
|
+
};
|
|
4422
|
+
const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
|
|
4423
|
+
const ownsIsolatedCodexHome = params.requestedStartOptions.homeScope !== "user" && !params.requestedStartOptions.env?.CODEX_HOME?.trim();
|
|
4424
|
+
const artifactDrain = desktopGeneration && ownsIsolatedCodexHome && computerUseConfig.enabled && (computerUseConfig.autoInstall || computerUseConfig.pluginCacheMode === "shared") ? createOlderDesktopGenerationDrainWait({
|
|
4425
|
+
generation: desktopGeneration,
|
|
4426
|
+
startOptions,
|
|
4427
|
+
agentDir: params.agentDir
|
|
4428
|
+
}) : void 0;
|
|
4429
|
+
try {
|
|
4430
|
+
if (artifactDrain) await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), artifactDrain.promise, params.abandonSignal);
|
|
4431
|
+
await reconcileCodexComputerUseStartArtifacts({
|
|
4432
|
+
startOptions,
|
|
4433
|
+
agentDir: params.agentDir,
|
|
4434
|
+
pluginConfig: params.pluginConfig,
|
|
4435
|
+
...desktopGeneration ? { desktopGeneration } : {},
|
|
4436
|
+
assertCurrent: assertDesktopGenerationCurrent,
|
|
4437
|
+
ownsIsolatedCodexHome
|
|
4438
|
+
});
|
|
4439
|
+
} catch (error) {
|
|
4440
|
+
if (isCodexComputerUseCandidateArtifactsUnavailableError(error)) {
|
|
4441
|
+
if (index + 1 < startOptionsCandidates.length) continue;
|
|
4442
|
+
throw new AgentHarnessPreflightError("Codex Computer Use artifacts are unavailable from the installed desktop apps.", {
|
|
4443
|
+
cause: error,
|
|
4444
|
+
scope: "harness"
|
|
4445
|
+
});
|
|
4446
|
+
}
|
|
4447
|
+
throw error;
|
|
4448
|
+
} finally {
|
|
4449
|
+
artifactDrain?.cancel();
|
|
4450
|
+
}
|
|
4451
|
+
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-DeBvleJw.js") : void 0;
|
|
3625
4452
|
const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
3626
4453
|
const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
|
|
3627
4454
|
startOptions,
|
|
@@ -3632,7 +4459,26 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3632
4459
|
if (index + 1 < startOptionsCandidates.length) continue;
|
|
3633
4460
|
throw new Error("Codex app-server runtime artifact does not match verified inference");
|
|
3634
4461
|
}
|
|
3635
|
-
|
|
4462
|
+
assertDesktopGenerationCurrent();
|
|
4463
|
+
let starting;
|
|
4464
|
+
let client;
|
|
4465
|
+
try {
|
|
4466
|
+
client = await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), starting = CodexAppServerClient.start(startOptions, () => {
|
|
4467
|
+
assertDesktopGenerationCurrent();
|
|
4468
|
+
resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
|
|
4469
|
+
}), params.abandonSignal);
|
|
4470
|
+
} catch (error) {
|
|
4471
|
+
starting?.then((lateClient) => lateClient.close(), () => {});
|
|
4472
|
+
throw error;
|
|
4473
|
+
}
|
|
4474
|
+
const nativeCommandAtStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
4475
|
+
getCodexAppServerClientStartMetadata().set(client, {
|
|
4476
|
+
requestedStartOptions: params.requestedStartOptions,
|
|
4477
|
+
startOptions,
|
|
4478
|
+
agentDir: params.agentDir,
|
|
4479
|
+
...nativeCommandAtStart ? { nativeCommand: nativeCommandAtStart } : {},
|
|
4480
|
+
...desktopGeneration ? { desktopGeneration } : {}
|
|
4481
|
+
});
|
|
3636
4482
|
params.onStartedClient?.(client);
|
|
3637
4483
|
let initialize;
|
|
3638
4484
|
try {
|
|
@@ -3643,6 +4489,12 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3643
4489
|
if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
|
|
3644
4490
|
throw error;
|
|
3645
4491
|
}
|
|
4492
|
+
try {
|
|
4493
|
+
assertDesktopGenerationCurrent();
|
|
4494
|
+
} catch (error) {
|
|
4495
|
+
client.close();
|
|
4496
|
+
throw error;
|
|
4497
|
+
}
|
|
3646
4498
|
params.onInitializedClient?.();
|
|
3647
4499
|
let runtimeArtifact;
|
|
3648
4500
|
try {
|
|
@@ -3666,9 +4518,11 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3666
4518
|
authProfileId: params.authProfileId ?? void 0,
|
|
3667
4519
|
authMode: params.preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
|
|
3668
4520
|
...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {},
|
|
3669
|
-
config: params.config
|
|
4521
|
+
config: params.config,
|
|
4522
|
+
onAuthRefreshFailure: () => retireSharedCodexAppServerClientIfCurrent(client)
|
|
3670
4523
|
});
|
|
3671
4524
|
try {
|
|
4525
|
+
assertDesktopGenerationCurrent();
|
|
3672
4526
|
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), applyCodexAppServerAuthProfile({
|
|
3673
4527
|
client,
|
|
3674
4528
|
agentDir: params.agentDir,
|
|
@@ -3679,14 +4533,8 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3679
4533
|
config: params.config,
|
|
3680
4534
|
...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
|
|
3681
4535
|
}), params.abandonSignal);
|
|
3682
|
-
const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
3683
4536
|
if (runtimeArtifactModule && runtimeArtifact) runtimeArtifactModule.bindCodexAppServerRuntimeArtifact(client, runtimeArtifact);
|
|
3684
|
-
|
|
3685
|
-
requestedStartOptions: params.requestedStartOptions,
|
|
3686
|
-
startOptions,
|
|
3687
|
-
agentDir: params.agentDir,
|
|
3688
|
-
...nativeCommand ? { nativeCommand } : {}
|
|
3689
|
-
});
|
|
4537
|
+
assertDesktopGenerationCurrent();
|
|
3690
4538
|
const fenceKey = resolveCodexNativeConfigFenceKey({ client });
|
|
3691
4539
|
if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
|
|
3692
4540
|
const release = await acquireCodexNativeConfigFence(fenceKey, options);
|
|
@@ -3706,6 +4554,9 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3706
4554
|
}
|
|
3707
4555
|
throw new Error("Managed Codex app-server fallback candidates were exhausted.");
|
|
3708
4556
|
}
|
|
4557
|
+
function isCodexComputerUseCandidateArtifactsUnavailableError(error) {
|
|
4558
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
|
|
4559
|
+
}
|
|
3709
4560
|
function resolveManagedFallbackStartOptions(startOptions) {
|
|
3710
4561
|
const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
|
|
3711
4562
|
const candidates = [];
|
|
@@ -3735,6 +4586,15 @@ function clearSharedCodexAppServerClientIfCurrent(client) {
|
|
|
3735
4586
|
}
|
|
3736
4587
|
return false;
|
|
3737
4588
|
}
|
|
4589
|
+
/** Captures a revocable observation of the exact shared client and native account/config. */
|
|
4590
|
+
function captureSharedCodexAppServerCatalogLifetime(client) {
|
|
4591
|
+
const state = getSharedCodexAppServerClientState();
|
|
4592
|
+
const entry = state.entriesByClient.get(client);
|
|
4593
|
+
const key = [...state.clients].find(([, candidate]) => candidate === entry)?.[0];
|
|
4594
|
+
const generation = readCodexAppServerClientDesktopGeneration(client);
|
|
4595
|
+
const revision = client.getModelCatalogRevision();
|
|
4596
|
+
return () => key !== void 0 && state.clients.get(key) === entry && entry?.client === client && !entry.closeWhenIdle && !entry.closeError && !client.getCloseError() && client.getModelCatalogRevision() === revision && (!generation || isCodexDesktopGenerationCurrent(generation));
|
|
4597
|
+
}
|
|
3738
4598
|
/** Retains the matching shared client and returns a release callback. */
|
|
3739
4599
|
function retainSharedCodexAppServerClientIfCurrent(client) {
|
|
3740
4600
|
if (!client) return;
|
|
@@ -3754,6 +4614,20 @@ function retainSharedCodexAppServerClientByInstanceId(clientId) {
|
|
|
3754
4614
|
};
|
|
3755
4615
|
}
|
|
3756
4616
|
}
|
|
4617
|
+
/** Captures sole physical-client ownership across awaited configuration adoption. */
|
|
4618
|
+
function captureExclusiveSharedCodexAppServerClient(client) {
|
|
4619
|
+
const state = getSharedCodexAppServerClientState();
|
|
4620
|
+
for (const [key, entry] of state.clients) {
|
|
4621
|
+
if (entry.client !== client) continue;
|
|
4622
|
+
const generation = entry.leaseGeneration;
|
|
4623
|
+
const assertExclusive = () => {
|
|
4624
|
+
if (state.clients.get(key) !== entry || entry.client !== client || entry.closeWhenIdle || entry.closeError || entry.activeLeases !== 1 || entry.pendingAcquires !== 0 || entry.leaseGeneration !== generation) throw new CodexAdoptedThreadActiveError();
|
|
4625
|
+
};
|
|
4626
|
+
assertExclusive();
|
|
4627
|
+
return assertExclusive;
|
|
4628
|
+
}
|
|
4629
|
+
throw new CodexAdoptedThreadActiveError();
|
|
4630
|
+
}
|
|
3757
4631
|
/**
|
|
3758
4632
|
* Retires a matching shared client. Default is graceful: detach from the map
|
|
3759
4633
|
* (future acquisitions get a fresh client) and close once leases drain.
|
|
@@ -3797,6 +4671,62 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
|
|
|
3797
4671
|
};
|
|
3798
4672
|
}
|
|
3799
4673
|
}
|
|
4674
|
+
/** Gracefully retires exact clients attached to an older desktop generation. */
|
|
4675
|
+
function retireSharedCodexAppServerClientsBeforeDesktopGeneration(generation) {
|
|
4676
|
+
const state = getSharedCodexAppServerClientState();
|
|
4677
|
+
for (const entry of state.clients.values()) {
|
|
4678
|
+
const client = entry.client;
|
|
4679
|
+
const attached = client ? getCodexAppServerClientStartMetadata().get(client) : void 0;
|
|
4680
|
+
if (client && attached?.desktopGeneration && attached.desktopGeneration.epoch < generation.epoch) retireSharedCodexAppServerClientIfCurrent(client);
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
function createOlderDesktopGenerationDrainWait(params) {
|
|
4684
|
+
const targetHome = resolveCodexNativeConfigFenceKey({
|
|
4685
|
+
startOptions: params.startOptions,
|
|
4686
|
+
agentDir: params.agentDir
|
|
4687
|
+
});
|
|
4688
|
+
if (!targetHome) return {
|
|
4689
|
+
promise: Promise.resolve(),
|
|
4690
|
+
cancel: () => void 0
|
|
4691
|
+
};
|
|
4692
|
+
const state = getSharedCodexAppServerClientState();
|
|
4693
|
+
let settled = false;
|
|
4694
|
+
let resolveWait;
|
|
4695
|
+
const promise = new Promise((resolve) => {
|
|
4696
|
+
resolveWait = resolve;
|
|
4697
|
+
});
|
|
4698
|
+
const cancel = () => {
|
|
4699
|
+
if (settled) return;
|
|
4700
|
+
settled = true;
|
|
4701
|
+
state.desktopGenerationDrainChecks.delete(check);
|
|
4702
|
+
resolveWait();
|
|
4703
|
+
};
|
|
4704
|
+
const check = () => {
|
|
4705
|
+
if (!hasLiveOlderDesktopGenerationClient({
|
|
4706
|
+
state,
|
|
4707
|
+
generation: params.generation,
|
|
4708
|
+
targetHome
|
|
4709
|
+
})) cancel();
|
|
4710
|
+
};
|
|
4711
|
+
state.desktopGenerationDrainChecks.add(check);
|
|
4712
|
+
check();
|
|
4713
|
+
return {
|
|
4714
|
+
promise,
|
|
4715
|
+
cancel
|
|
4716
|
+
};
|
|
4717
|
+
}
|
|
4718
|
+
function hasLiveOlderDesktopGenerationClient(params) {
|
|
4719
|
+
for (const client of params.state.liveClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
|
|
4720
|
+
for (const client of params.state.isolatedClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
|
|
4721
|
+
return false;
|
|
4722
|
+
}
|
|
4723
|
+
function isOlderDesktopGenerationClientForHome(client, generation, targetHome) {
|
|
4724
|
+
const metadata = getCodexAppServerClientStartMetadata().get(client);
|
|
4725
|
+
return Boolean(metadata?.desktopGeneration && metadata.desktopGeneration.epoch < generation.epoch && resolveCodexNativeConfigFenceKey({ client }) === targetHome);
|
|
4726
|
+
}
|
|
4727
|
+
function notifyDesktopGenerationDrainChecks(state) {
|
|
4728
|
+
for (const check of state.desktopGenerationDrainChecks) check();
|
|
4729
|
+
}
|
|
3800
4730
|
/** Clears a matching shared client and waits for its process to exit. */
|
|
3801
4731
|
async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
|
|
3802
4732
|
if (!client) return false;
|
|
@@ -3813,7 +4743,6 @@ async function clearSharedCodexAppServerClientAndWait(options) {
|
|
|
3813
4743
|
const state = getSharedCodexAppServerClientState();
|
|
3814
4744
|
const clients = collectSharedClients(state);
|
|
3815
4745
|
state.clients.clear();
|
|
3816
|
-
state.warmClientsByConfig = /* @__PURE__ */ new WeakMap();
|
|
3817
4746
|
await Promise.all(clients.map((client) => client.closeAndWait(options)));
|
|
3818
4747
|
}
|
|
3819
4748
|
globalThis[SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER] = clearSharedCodexAppServerClientAndWait;
|
|
@@ -3823,6 +4752,7 @@ function getOrCreateSharedClientEntry(state, key) {
|
|
|
3823
4752
|
entry = {
|
|
3824
4753
|
activeLeases: 0,
|
|
3825
4754
|
pendingAcquires: 0,
|
|
4755
|
+
leaseGeneration: 0,
|
|
3826
4756
|
closeWhenIdle: false,
|
|
3827
4757
|
onStartedClientCallbacks: /* @__PURE__ */ new Set()
|
|
3828
4758
|
};
|
|
@@ -3858,22 +4788,26 @@ function clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client) {
|
|
|
3858
4788
|
}
|
|
3859
4789
|
function retainPendingSharedClientAcquire(entry) {
|
|
3860
4790
|
let released = false;
|
|
4791
|
+
entry.leaseGeneration += 1;
|
|
3861
4792
|
entry.pendingAcquires += 1;
|
|
3862
4793
|
return () => {
|
|
3863
4794
|
if (released) return;
|
|
3864
4795
|
released = true;
|
|
3865
4796
|
entry.pendingAcquires = Math.max(0, entry.pendingAcquires - 1);
|
|
3866
4797
|
closeRetiredSharedClientEntryIfIdle(entry);
|
|
4798
|
+
notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
|
|
3867
4799
|
};
|
|
3868
4800
|
}
|
|
3869
4801
|
function retainSharedClientEntry(entry) {
|
|
3870
4802
|
let released = false;
|
|
4803
|
+
entry.leaseGeneration += 1;
|
|
3871
4804
|
entry.activeLeases += 1;
|
|
3872
4805
|
return () => {
|
|
3873
4806
|
if (released) return;
|
|
3874
4807
|
released = true;
|
|
3875
4808
|
entry.activeLeases = Math.max(0, entry.activeLeases - 1);
|
|
3876
4809
|
closeRetiredSharedClientEntryIfIdle(entry);
|
|
4810
|
+
notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
|
|
3877
4811
|
};
|
|
3878
4812
|
}
|
|
3879
4813
|
function closeRetiredSharedClientEntryIfIdle(entry) {
|
|
@@ -3901,7 +4835,7 @@ function closeSharedClientEntryIfUnclaimed(key, entry) {
|
|
|
3901
4835
|
}
|
|
3902
4836
|
function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
|
|
3903
4837
|
if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
|
|
3904
|
-
entry.
|
|
4838
|
+
entry.startupAbort?.abort(/* @__PURE__ */ new Error("Codex app-server startup was abandoned"));
|
|
3905
4839
|
entry.closeWhenIdle = true;
|
|
3906
4840
|
const state = getSharedCodexAppServerClientState();
|
|
3907
4841
|
if (state.clients.get(key) === entry) state.clients.delete(key);
|
|
@@ -3912,4 +4846,4 @@ function collectSharedClients(state) {
|
|
|
3912
4846
|
return [...state.liveClients];
|
|
3913
4847
|
}
|
|
3914
4848
|
//#endregion
|
|
3915
|
-
export {
|
|
4849
|
+
export { CodexAppServerRpcError as $, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as A, reconcileCodexComputerUseStartArtifacts as At, resolveCodexTurnTerminalIdleTimeoutMs as B, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as Bt, withLeasedCodexAppServerClientStartSelectionRetry as C, retainCodexAppServerLiveThread as Ct, CodexThreadStartRequestError as D, readCodexRateLimitsRevision as Dt, CodexThreadBindingConflictError as E, withTimeout$1 as Et, resolveCodexGatewayTimeoutWithGraceMs as F, resolveCodexAppServerFallbackApiKeyCacheKey as Ft, isCodexAppServerConnectionClosedError as G, getCodexAppServerClientInstanceId as H, isJsonObject as Ht, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as I, resolveCodexAppServerPreparedApiKeyCacheKey as It, isCodexAppServerOverloadError as J, isCodexAppServerIndeterminateRequestCancellationError as K, resolveCodexStartupTimeoutMs as L, resolveCodexAppServerPreparedAuthHandoff as Lt, TURN_FINALIZE_DRAIN_ABORT_GRACE_MS as M, resolveCodexAppServerAuthProfileId as Mt, TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS as N, resolveCodexAppServerAuthProfileIdForAgent as Nt, acquireCodexNativeConfigFence as O, readRecentCodexRateLimits as Ot, isCodexAppServerStartupError as P, resolveCodexAppServerAuthProfileStore as Pt, resolveCodexAppServerClientInstanceId as Q, resolveCodexTurnAssistantCompletionIdleTimeoutMs as R, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as Rt, waitForCodexAppServerClientDesktopGenerationDrain as S, releaseCodexAppServerLiveThread as St, CodexRestrictedToolSurfaceAttestationError as T, withAbortableTimeout as Tt, isCodexAppServerApprovalRequest as U, resolveCodexManagedBundledMarketplacePath as Ut, withCodexStartupTimeout as V, flattenCodexDynamicToolFunctions as Vt, isCodexAppServerBrokenPipeError as W, assertNotSymlink as Wt, isCodexAppServerRequestTimeoutError as X, isCodexAppServerPrewriteRequestCancellationError as Y, isUnsupportedCodexAppServerVersionError as Z, retainSharedCodexAppServerClientByInstanceId as _, ensureCodexAppServerClientRuntime as _t, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as a, resolveDynamicToolCallTimeoutMs as at, retireSharedCodexAppServerClientsBeforeDesktopGeneration as b, isCodexAppServerLiveThreadClaimed as bt, getLeasedSharedCodexAppServerClient as c, shouldReleaseTurnAfterTerminalDynamicTool as ct, readCodexAppServerClientDesktopGeneration as d, resolveCodexToolAbortTerminalReason as dt, createCodexElicitationResponse as et, readCodexAppServerClientDesktopGenerationFingerprint as f, createFailedDynamicToolResponse as ft, resolveCodexNativeConfigFenceKey as g, consumeCodexAppServerLiveThread as gt, releaseLeasedSharedCodexAppServerClient as h, claimCodexAppServerLiveThread as ht, clearSharedCodexAppServerClientIfCurrent as i, isMatchingDynamicToolTerminalDiagnostic as it, CodexAppServerStartupError as j, resolveCodexAppServerAuthAccountCacheKey as jt, createCodexDesktopGenerationService as k, rememberCodexRateLimitsRead as kt, getSharedCodexAppServerClient as l, toCodexDynamicToolProgressResponse as lt, releaseCodexAppServerClientLease as m, withDynamicToolTranscriptDetails as mt, captureExclusiveSharedCodexAppServerClient as n, hasPendingDynamicToolTerminalDiagnostic as nt, clearSharedCodexAppServerClientIfCurrentAndWait as o, resolveTerminalDynamicToolBatchAction as ot, readCodexAppServerClientProcessIdentity as p, withDynamicToolExecutionState as pt, isCodexAppServerIndeterminateTransportError as q, captureSharedCodexAppServerCatalogLifetime as r, isDynamicToolTerminalDiagnosticEvent as rt, createIsolatedCodexAppServerClient as s, shouldBlockTerminalReleaseForNonTerminalDynamicToolResult as st, assertCodexAppServerClientStartSelectionCurrent as t, handleDynamicToolCallWithTimeout as tt, isCodexAppServerStartSelectionChangedError as u, toCodexDynamicToolProtocolResponse as ut, retainSharedCodexAppServerClientIfCurrent as v, hasCodexAppServerLiveThread as vt, CodexAdoptedThreadActiveError as w, unsubscribeCodexAppServerLiveThread as wt, shared_client_exports as x, protectCodexAppServerLiveThread as xt, retireSharedCodexAppServerClientIfCurrent as y, isCodexAppServerClientRuntimeLive as yt, resolveCodexTurnCompletionIdleTimeoutMs as z, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as zt };
|