@openclaw/codex 2026.8.1-beta.3 → 2026.8.2
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 +5 -6
- package/dist/{attempt-client-cleanup-URjUlCHg.js → attempt-client-cleanup-CpVfwctT.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-CF7BmrDk.js} +2 -2
- package/dist/{bounded-turn-4BjrZEwW.js → bounded-turn-DRC3hcjm.js} +16 -20
- package/dist/{capabilities-CH3K57v4.js → capabilities-D6m_TdVy.js} +1 -1
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-D0nZ3qtd.js → command-formatters--UToCokS.js} +4 -3
- package/dist/{command-handlers-DC86lgT4.js → command-handlers-CLFx2Ypa.js} +114 -79
- package/dist/{compact-COqgBRny.js → compact-5xqW1oCh.js} +13 -12
- package/dist/{config-CpNeo5AE.js → config-CcYL77O6.js} +350 -288
- package/dist/{conversation-binding-data-CW2GuupV.js → conversation-binding-data-BiodpIzq.js} +2 -1
- package/dist/doctor-contract-api.js +121 -16
- package/dist/{dynamic-tools-fJZhl4CE.js → dynamic-tools-BmeXjwNZ.js} +243 -1002
- package/dist/{effective-mcp-catalog-4YKn51aG.js → effective-mcp-catalog-XXQKgK1U.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-B2r5vhWH.js} +9 -14
- package/dist/{harness-DoKQcF8n.js → harness-BgeB7LnD.js} +42 -52
- package/dist/harness.js +1 -1
- package/dist/index.js +308 -306
- package/dist/{isolated-completion-DPMtlH9r.js → isolated-completion-CxTus3vZ.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-DULBcniB.js +101 -0
- package/dist/{models-CZw1wClT.js → models-DaWfETQk.js} +27 -29
- package/dist/{native-subagent-monitor-GGq3RnOo.js → native-subagent-monitor-vXYUZH37.js} +226 -129
- package/dist/{node-exec-server.runtime-Bsu1LdFZ.js → node-exec-server.runtime-DirKVZ5h.js} +9 -6
- package/dist/{plugin-approval-roundtrip-C75Yiiwc.js → plugin-approval-roundtrip-CCWQGlAI.js} +2 -1
- package/dist/{plugin-marketplace-discovery-CSWDkMrP.js → plugin-marketplace-discovery-BXjIFEiw.js} +38 -46
- package/dist/{plugin-metadata-cache-BXH5NF-4.js → plugin-metadata-cache-DQQm4gWT.js} +163 -136
- package/dist/{protocol-validators-VAVyEnDp.js → protocol-validators-Bg5rpNxk.js} +148 -5
- package/dist/{rate-limits-BI1dMHDG.js → rate-limits-BqHEHXQJ.js} +3 -2
- package/dist/{sandbox-guard-C1qEeZ5C.js → request-BWxweEdK.js} +183 -2
- package/dist/{run-attempt-BV3HzuCf.js → run-attempt-BVLE4Gbi.js} +706 -517
- package/dist/{runtime-artifact-BvyT1uyd.js → runtime-artifact-D9pYp4zL.js} +3 -3
- package/dist/{session-binding-BTrLf9Qm.js → session-binding-D0zinCP1.js} +195 -122
- package/dist/{session-catalog-ezP_UcRM.js → session-catalog-eMhVm0Ql.js} +448 -313
- package/dist/session-catalog-home-id-Dt2GiG5o.js +112 -0
- package/dist/{session-cli-B1hKvfkI.js → session-cli-B2lyjJA0.js} +1 -1
- package/dist/session-history-C5J1AKzG.js +87 -0
- package/dist/session-permission-policy-ScnAyG90.js +160 -0
- package/dist/session-retirement-CtUtUTr9.js +90 -0
- package/dist/{session-upstream-marker-D_2npHlA.js → session-upstream-marker-gqrYIRHk.js} +2 -3
- package/dist/settled-turn-context-Bk3IUwod.js +290 -0
- package/dist/settled-turn-finalizer-BXLN9PfJ.js +104 -0
- package/dist/{shared-client-Dw-rzii0.js → shared-client-B1tT5U6r.js} +1738 -806
- package/dist/{side-question-DbXkJZzg.js → side-question-CZ-mCw-t.js} +76 -43
- package/dist/{thread-lifecycle-DKdrnTMX.js → thread-lifecycle-CGGKP720.js} +991 -455
- package/dist/{thread-ownership-i2NBwDUL.js → thread-ownership-RkaoZCmQ.js} +18 -8
- package/dist/{plugin-app-cache-key-DMNQ8Zi8.js → thread-resume-BFt1-ZDa.js} +55 -8
- package/dist/{transcript-mirror-BU0_LKAL.js → transcript-mirror-1ep4XjzF.js} +124 -18
- package/dist/transport-stdio-NT_Dpd9V.js +724 -0
- package/dist/{upstream-prompt-provenance-CFmsKwr9.js → upstream-prompt-provenance-LphB8slG.js} +8 -4
- package/dist/{upstream-session-fork-BDVJCLAD.js → upstream-session-fork-Dc8BFExw.js} +76 -90
- package/dist/{usage-9dKJCgcy.js → usage-DUhd58gI.js} +3 -3
- package/dist/{web-search-provider.runtime-DCdx4DGu.js → web-search-provider.runtime-BEmlE7ov.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/settled-turn-finalizer-Dx1onIqg.js +0 -328
- 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-CcYL77O6.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-D0zinCP1.js";
|
|
5
|
+
import { a as hasCodexAppServerNaturalExit, i as closeCodexAppServerTransportAndWait, n as resolveCodexAppServerSpawnEnv, r as closeCodexAppServerTransport, t as createStdioTransport } from "./transport-stdio-NT_Dpd9V.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,21 @@ 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
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
throw error;
|
|
1046
|
-
}
|
|
1047
|
-
if (params.authRequirement === "subscription" && loginParams?.type !== "chatgptAuthTokens") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
|
|
1218
|
+
const loginParams = params.preparedAuth?.kind === "profile" ? params.preparedAuth.snapshot.loginParams : params.preparedAuth?.kind === "api-key" ? {
|
|
1219
|
+
type: "apiKey",
|
|
1220
|
+
apiKey: params.preparedAuth.apiKey
|
|
1221
|
+
} : await resolveCodexAppServerAuthProfileLoginParams({
|
|
1222
|
+
agentDir: params.agentDir,
|
|
1223
|
+
authProfileId: params.authProfileId ?? void 0,
|
|
1224
|
+
authProfileStore: params.authProfileStore,
|
|
1225
|
+
config: params.config
|
|
1226
|
+
});
|
|
1227
|
+
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
1228
|
if (!loginParams) {
|
|
1049
|
-
if (params.authRequirement === "subscription") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
|
|
1050
1229
|
if (params.authRequirement !== "api-key" || params.startOptions?.transport !== "stdio") return;
|
|
1051
1230
|
const env = resolveCodexAppServerSpawnEnv(params.startOptions, process.env);
|
|
1052
1231
|
const fallbackLoginParams = await resolveCodexAppServerFallbackApiKeyLoginParams({
|
|
@@ -1071,7 +1250,7 @@ async function assertNativeCodexAccountMatchesRoute(client, authRequirement) {
|
|
|
1071
1250
|
const response = await client.request("account/read", { refreshToken: false });
|
|
1072
1251
|
const accountType = isJsonObject(response.account) ? response.account.type : void 0;
|
|
1073
1252
|
if (authRequirement === "subscription") {
|
|
1074
|
-
if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth
|
|
1253
|
+
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
1254
|
return;
|
|
1076
1255
|
}
|
|
1077
1256
|
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 +1259,13 @@ function createCodexAppServerAuthError(message, cause) {
|
|
|
1080
1259
|
const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
|
|
1081
1260
|
return Object.assign(error, { status: 401 });
|
|
1082
1261
|
}
|
|
1083
|
-
var CodexAppServerAuthProfileUnavailableError = class extends Error {
|
|
1262
|
+
var CodexAppServerAuthProfileUnavailableError = class extends Error {
|
|
1263
|
+
constructor(..._args) {
|
|
1264
|
+
super(..._args);
|
|
1265
|
+
this.status = 401;
|
|
1266
|
+
this.code = "selected_auth_profile_unavailable";
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1084
1269
|
async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
1085
1270
|
const store = resolveCodexAppServerAuthProfileStore(params);
|
|
1086
1271
|
const profileId = resolveCodexAppServerAuthProfileId({
|
|
@@ -1089,7 +1274,7 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
|
1089
1274
|
config: params.config
|
|
1090
1275
|
});
|
|
1091
1276
|
const profile = profileId ? store.profiles[profileId] : void 0;
|
|
1092
|
-
if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found.`);
|
|
1277
|
+
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
1278
|
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
1279
|
return await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
1095
1280
|
...params,
|
|
@@ -1097,11 +1282,24 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
|
1097
1282
|
});
|
|
1098
1283
|
}
|
|
1099
1284
|
async function refreshCodexAppServerAuthTokens(params) {
|
|
1285
|
+
const previousAccountId = params.previousAccountId?.trim();
|
|
1286
|
+
if (previousAccountId) {
|
|
1287
|
+
const store = resolveCodexAppServerAuthProfileStore(params);
|
|
1288
|
+
const profileId = resolveCodexAppServerAuthProfileId({
|
|
1289
|
+
authProfileId: params.authProfileId,
|
|
1290
|
+
store,
|
|
1291
|
+
config: params.config
|
|
1292
|
+
});
|
|
1293
|
+
const credential = profileId ? store.profiles[profileId] : void 0;
|
|
1294
|
+
const selectedAccountId = credential ? resolveExplicitChatgptAccountId(credential) ?? (credential.type === "oauth" ? resolveOpenAICodexAuthIdentity({ access: credential.access }).accountId : void 0) : void 0;
|
|
1295
|
+
if (selectedAccountId && selectedAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed before Codex token refresh. Retry to start a client for the selected workspace.");
|
|
1296
|
+
}
|
|
1100
1297
|
const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
1101
1298
|
...params,
|
|
1102
1299
|
forceOAuthRefresh: true
|
|
1103
1300
|
});
|
|
1104
|
-
if (!loginParams || loginParams.type !== "chatgptAuthTokens") throw new Error("Codex app-server ChatGPT token refresh requires an OAuth auth profile.");
|
|
1301
|
+
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.");
|
|
1302
|
+
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
1303
|
return {
|
|
1106
1304
|
accessToken: loginParams.accessToken,
|
|
1107
1305
|
chatgptAccountId: loginParams.chatgptAccountId,
|
|
@@ -1122,8 +1320,8 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
|
|
|
1122
1320
|
});
|
|
1123
1321
|
if (!profileId) return;
|
|
1124
1322
|
const credential = store.profiles[profileId];
|
|
1125
|
-
if (!credential) throw new
|
|
1126
|
-
if (!isCodexAppServerAuthProfileCredential(credential)) throw new
|
|
1323
|
+
if (!credential) 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.`);
|
|
1324
|
+
if (!isCodexAppServerAuthProfileCredential(credential)) 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.`);
|
|
1127
1325
|
const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
|
|
1128
1326
|
agentDir: params.agentDir,
|
|
1129
1327
|
store,
|
|
@@ -1131,7 +1329,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
|
|
|
1131
1329
|
forceOAuthRefresh: params.forceOAuthRefresh === true,
|
|
1132
1330
|
config: params.config
|
|
1133
1331
|
});
|
|
1134
|
-
if (!loginParams) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" does not contain usable credentials.`);
|
|
1332
|
+
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
1333
|
return loginParams;
|
|
1136
1334
|
}
|
|
1137
1335
|
async function resolveCodexAppServerFallbackApiKeyLoginParams(params) {
|
|
@@ -1245,7 +1443,7 @@ async function resolveOAuthCredentialForCodexAppServer(profileId, credential, pa
|
|
|
1245
1443
|
});
|
|
1246
1444
|
if (params.forceRefresh && !persistedOAuthCredential && overlaidOAuthCredential) {
|
|
1247
1445
|
const refreshedRuntimeCredential = await refreshOAuthCredentialForRuntime({ credential: overlaidOAuthCredential });
|
|
1248
|
-
if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh.`);
|
|
1446
|
+
if (!refreshedRuntimeCredential?.access?.trim()) throw new Error(`Codex app-server auth profile "${profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
|
|
1249
1447
|
store.profiles[profileId] = refreshedRuntimeCredential;
|
|
1250
1448
|
return refreshedRuntimeCredential;
|
|
1251
1449
|
}
|
|
@@ -1273,8 +1471,8 @@ function shouldUseScopedOAuthCredential(params) {
|
|
|
1273
1471
|
return !isDeepStrictEqual(persisted, params.suppliedCredential) && !hasMatchingOAuthIdentity(persisted, params.suppliedCredential);
|
|
1274
1472
|
}
|
|
1275
1473
|
function hasMatchingOAuthIdentity(persisted, supplied) {
|
|
1276
|
-
const persistedAccountId = persisted.accountId?.trim();
|
|
1277
|
-
const suppliedAccountId = supplied.accountId?.trim();
|
|
1474
|
+
const persistedAccountId = resolveOpenAICodexAuthIdentity(persisted).accountId?.trim();
|
|
1475
|
+
const suppliedAccountId = resolveOpenAICodexAuthIdentity(supplied).accountId?.trim();
|
|
1278
1476
|
if (persistedAccountId && suppliedAccountId) return persistedAccountId === suppliedAccountId;
|
|
1279
1477
|
const persistedEmail = persisted.email?.trim().toLowerCase();
|
|
1280
1478
|
const suppliedEmail = supplied.email?.trim().toLowerCase();
|
|
@@ -1291,8 +1489,8 @@ async function resolveScopedOAuthCredential(params) {
|
|
|
1291
1489
|
const credential = current?.type === "oauth" ? current : params.credential;
|
|
1292
1490
|
if (!params.forceRefresh && hasUsableOAuthCredential(credential)) return credential;
|
|
1293
1491
|
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.`);
|
|
1492
|
+
if (!refreshed?.access?.trim()) throw new Error(`Codex app-server auth profile "${params.profileId}" could not refresh. Sign in again with OpenClaw, then retry.`);
|
|
1493
|
+
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
1494
|
params.store.profiles[params.profileId] = refreshed;
|
|
1297
1495
|
return refreshed;
|
|
1298
1496
|
})();
|
|
@@ -1339,10 +1537,15 @@ function readFirstNonEmptyEnvEntry(env, keys) {
|
|
|
1339
1537
|
}
|
|
1340
1538
|
}
|
|
1341
1539
|
function buildChatgptAuthTokensParams(profileId, credential, accessToken) {
|
|
1540
|
+
const storedAccountId = resolveExplicitChatgptAccountId(credential);
|
|
1541
|
+
const tokenAccountId = resolveOpenAICodexAuthIdentity({ access: accessToken }).accountId;
|
|
1542
|
+
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.`);
|
|
1543
|
+
const chatgptAccountId = storedAccountId ?? tokenAccountId;
|
|
1544
|
+
if (!chatgptAccountId) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" is missing its ChatGPT account ID. Sign in again before retrying.`);
|
|
1342
1545
|
return {
|
|
1343
1546
|
type: "chatgptAuthTokens",
|
|
1344
1547
|
accessToken,
|
|
1345
|
-
chatgptAccountId
|
|
1548
|
+
chatgptAccountId,
|
|
1346
1549
|
chatgptPlanType: resolveChatgptPlanType(credential)
|
|
1347
1550
|
};
|
|
1348
1551
|
}
|
|
@@ -1365,7 +1568,7 @@ function resolveExplicitChatgptAccountId(credential) {
|
|
|
1365
1568
|
}
|
|
1366
1569
|
//#endregion
|
|
1367
1570
|
//#region extensions/codex/src/app-server/rate-limit-cache.ts
|
|
1368
|
-
const DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS =
|
|
1571
|
+
const DEFAULT_CODEX_RATE_LIMIT_CACHE_MAX_AGE_MS = 6e5;
|
|
1369
1572
|
const SPARSE_ACCOUNT_METADATA_KEYS = [
|
|
1370
1573
|
"credits",
|
|
1371
1574
|
"individualLimit",
|
|
@@ -1462,12 +1665,46 @@ function readLimitId(snapshot) {
|
|
|
1462
1665
|
return typeof value === "string" && value.trim() ? value.trim() : "codex";
|
|
1463
1666
|
}
|
|
1464
1667
|
//#endregion
|
|
1668
|
+
//#region extensions/codex/src/app-server/timeout.ts
|
|
1669
|
+
/**
|
|
1670
|
+
* Thin Codex app-server timeout adapter around OpenClaw's shared security
|
|
1671
|
+
* runtime timeout helper.
|
|
1672
|
+
*/
|
|
1673
|
+
function resolveAbortError(signal) {
|
|
1674
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
|
|
1675
|
+
}
|
|
1676
|
+
/** Awaits a promise with a Codex-specific timeout error message. */
|
|
1677
|
+
async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
|
|
1678
|
+
return await withTimeout(promise, timeoutMs, {
|
|
1679
|
+
message: timeoutMessage,
|
|
1680
|
+
...createError ? { createError } : {}
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
/** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
|
|
1684
|
+
async function withAbortableTimeout(params) {
|
|
1685
|
+
const signal = params.signal;
|
|
1686
|
+
if (signal?.aborted) throw resolveAbortError(signal);
|
|
1687
|
+
let removeAbortListener;
|
|
1688
|
+
const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
|
|
1689
|
+
const onAbort = () => reject(resolveAbortError(signal));
|
|
1690
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1691
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
1692
|
+
})]) : params.promise;
|
|
1693
|
+
try {
|
|
1694
|
+
return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
|
|
1695
|
+
} finally {
|
|
1696
|
+
removeAbortListener?.();
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
//#endregion
|
|
1465
1700
|
//#region extensions/codex/src/app-server/client-runtime.ts
|
|
1466
1701
|
/** Client-scoped Codex auth and account observers. */
|
|
1467
1702
|
/** Match Codex's native grace window without retaining inactive conversations indefinitely. */
|
|
1468
|
-
const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS =
|
|
1703
|
+
const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 18e5;
|
|
1469
1704
|
/** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
|
|
1470
1705
|
const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
|
|
1706
|
+
/** Return a deterministic error before Codex cancels its ten-second external-auth request. */
|
|
1707
|
+
const CODEX_EXTERNAL_AUTH_REFRESH_TIMEOUT_MS = 9e3;
|
|
1471
1708
|
const configuredClients = /* @__PURE__ */ new WeakMap();
|
|
1472
1709
|
const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
|
|
1473
1710
|
const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
|
|
@@ -1481,7 +1718,10 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1481
1718
|
const existing = configuredClients.get(client);
|
|
1482
1719
|
if (existing) {
|
|
1483
1720
|
if (existing.closed) return;
|
|
1484
|
-
existing.context =
|
|
1721
|
+
existing.context = {
|
|
1722
|
+
...existing.context,
|
|
1723
|
+
config: context.config
|
|
1724
|
+
};
|
|
1485
1725
|
return;
|
|
1486
1726
|
}
|
|
1487
1727
|
const runtime = {
|
|
@@ -1506,12 +1746,21 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1506
1746
|
client.addRequestHandler(async (request) => {
|
|
1507
1747
|
if (request.method !== "account/chatgptAuthTokens/refresh") return;
|
|
1508
1748
|
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
|
-
|
|
1749
|
+
const previousAccountId = isJsonObject(request.params) && typeof request.params.previousAccountId === "string" ? request.params.previousAccountId.trim() || void 0 : void 0;
|
|
1750
|
+
try {
|
|
1751
|
+
const tokens = await withTimeout$1(refreshCodexAppServerAuthTokens({
|
|
1752
|
+
agentDir: runtime.context.agentDir,
|
|
1753
|
+
authProfileId: runtime.context.authProfileId,
|
|
1754
|
+
...previousAccountId ? { previousAccountId } : {},
|
|
1755
|
+
...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
|
|
1756
|
+
config: runtime.context.config
|
|
1757
|
+
}), 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.");
|
|
1758
|
+
if (previousAccountId && tokens.chatgptAccountId !== previousAccountId) throw new Error("ChatGPT workspace changed during Codex token refresh. Retry to start a client for the selected workspace.");
|
|
1759
|
+
return { ...tokens };
|
|
1760
|
+
} catch (error) {
|
|
1761
|
+
runtime.context.onAuthRefreshFailure?.();
|
|
1762
|
+
throw error;
|
|
1763
|
+
}
|
|
1515
1764
|
});
|
|
1516
1765
|
client.addNotificationHandler((notification) => {
|
|
1517
1766
|
if (notification.method === "account/rateLimits/updated") {
|
|
@@ -1547,10 +1796,11 @@ function scheduleRetainedThreadEviction(client, runtime) {
|
|
|
1547
1796
|
}, Math.max(0, expiresAt - Date.now()));
|
|
1548
1797
|
runtime.evictionTimer.unref?.();
|
|
1549
1798
|
}
|
|
1550
|
-
async function releaseRetainedThread(client, runtime, threadId) {
|
|
1799
|
+
async function releaseRetainedThread(client, runtime, threadId, assertCurrent) {
|
|
1551
1800
|
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1552
1801
|
if (pendingRelease) {
|
|
1553
1802
|
await pendingRelease.completion;
|
|
1803
|
+
assertCurrent?.();
|
|
1554
1804
|
return false;
|
|
1555
1805
|
}
|
|
1556
1806
|
const retained = runtime.retainedThreads.get(threadId);
|
|
@@ -1562,7 +1812,10 @@ async function releaseRetainedThread(client, runtime, threadId) {
|
|
|
1562
1812
|
}
|
|
1563
1813
|
runtime.retainedThreads.delete(threadId);
|
|
1564
1814
|
scheduleRetainedThreadEviction(client, runtime);
|
|
1565
|
-
const transition = { completion: Promise.resolve().then(() =>
|
|
1815
|
+
const transition = { completion: Promise.resolve().then(() => {
|
|
1816
|
+
assertCurrent?.();
|
|
1817
|
+
return assertCurrent ? retained.release(threadId, assertCurrent) : retained.release(threadId);
|
|
1818
|
+
}) };
|
|
1566
1819
|
runtime.releasingThreads.set(threadId, transition);
|
|
1567
1820
|
try {
|
|
1568
1821
|
await transition.completion;
|
|
@@ -1602,15 +1855,15 @@ async function retainCodexAppServerLiveThread(client, threadId, releaseThread, c
|
|
|
1602
1855
|
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1603
1856
|
if (pendingRelease) {
|
|
1604
1857
|
await pendingRelease.completion;
|
|
1605
|
-
|
|
1858
|
+
return false;
|
|
1606
1859
|
}
|
|
1607
1860
|
runtime.retainedThreads.delete(threadId);
|
|
1608
1861
|
const retained = {
|
|
1609
1862
|
configFingerprint,
|
|
1610
1863
|
serviceTier,
|
|
1611
1864
|
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);
|
|
1865
|
+
release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId, assertCurrent) => {
|
|
1866
|
+
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
|
|
1614
1867
|
})
|
|
1615
1868
|
};
|
|
1616
1869
|
runtime.retainedThreads.set(threadId, retained);
|
|
@@ -1654,8 +1907,8 @@ async function claimCodexAppServerLiveThread(client, threadId) {
|
|
|
1654
1907
|
}
|
|
1655
1908
|
return claimCodexAppServerThreadOwnership(client, runtime, threadId, runtime.retainedThreads.get(threadId) ?? {
|
|
1656
1909
|
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
|
|
1657
|
-
release: async (releasedThreadId) => {
|
|
1658
|
-
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
|
|
1910
|
+
release: async (releasedThreadId, assertCurrent) => {
|
|
1911
|
+
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3, assertCurrent);
|
|
1659
1912
|
}
|
|
1660
1913
|
});
|
|
1661
1914
|
}
|
|
@@ -1664,7 +1917,7 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
|
|
|
1664
1917
|
const claimed = Symbol(threadId);
|
|
1665
1918
|
runtime.claimedThreads.set(threadId, claimed);
|
|
1666
1919
|
scheduleRetainedThreadEviction(client, runtime);
|
|
1667
|
-
const release = async (releasedThreadId) => {
|
|
1920
|
+
const release = async (releasedThreadId, assertCurrent) => {
|
|
1668
1921
|
if (releasedThreadId !== threadId || runtime.claimedThreads.get(threadId) !== claimed) return;
|
|
1669
1922
|
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1670
1923
|
if (pendingRelease) {
|
|
@@ -1673,7 +1926,8 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
|
|
|
1673
1926
|
}
|
|
1674
1927
|
const transition = { completion: Promise.resolve().then(async () => {
|
|
1675
1928
|
if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) return;
|
|
1676
|
-
|
|
1929
|
+
assertCurrent?.();
|
|
1930
|
+
await (assertCurrent ? retained.release(releasedThreadId, assertCurrent) : retained.release(releasedThreadId));
|
|
1677
1931
|
}) };
|
|
1678
1932
|
runtime.releasingThreads.set(threadId, transition);
|
|
1679
1933
|
try {
|
|
@@ -1692,22 +1946,32 @@ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained)
|
|
|
1692
1946
|
};
|
|
1693
1947
|
}
|
|
1694
1948
|
/** Distinguish active claimed ownership from an already-evicted idle subscription. */
|
|
1949
|
+
function hasCodexAppServerLiveThread(client, threadId) {
|
|
1950
|
+
const runtime = configuredClients.get(client);
|
|
1951
|
+
return runtime !== void 0 && !runtime.closed && (runtime.retainedThreads.has(threadId) || runtime.releasingThreads.has(threadId) || runtime.claimedThreads.has(threadId));
|
|
1952
|
+
}
|
|
1695
1953
|
function isCodexAppServerLiveThreadClaimed(client, threadId) {
|
|
1696
1954
|
const runtime = configuredClients.get(client);
|
|
1697
1955
|
return runtime !== void 0 && !runtime.closed && runtime.claimedThreads.has(threadId);
|
|
1698
1956
|
}
|
|
1699
|
-
/** Release the exact physical subscription and finish only its observed
|
|
1700
|
-
async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs) {
|
|
1957
|
+
/** Release the exact physical subscription and finish only its observed ownership generation. */
|
|
1958
|
+
async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs, assertCurrent) {
|
|
1701
1959
|
const runtime = configuredClients.get(client);
|
|
1702
1960
|
const claimed = runtime?.claimedThreads.get(threadId);
|
|
1961
|
+
const retained = runtime?.retainedThreads.get(threadId);
|
|
1703
1962
|
let transition = runtime?.releasingThreads.get(threadId);
|
|
1704
1963
|
if (transition?.physicalRelease) {
|
|
1705
1964
|
await transition.physicalRelease;
|
|
1965
|
+
assertCurrent?.();
|
|
1706
1966
|
return;
|
|
1707
1967
|
}
|
|
1708
1968
|
const physicalRelease = Promise.resolve().then(async () => {
|
|
1709
|
-
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed) return;
|
|
1710
|
-
|
|
1969
|
+
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed || retained !== void 0 && runtime?.retainedThreads.get(threadId) !== retained) return;
|
|
1970
|
+
assertCurrent?.();
|
|
1971
|
+
await client.request("thread/unsubscribe", { threadId }, {
|
|
1972
|
+
timeoutMs,
|
|
1973
|
+
assertCurrent
|
|
1974
|
+
});
|
|
1711
1975
|
});
|
|
1712
1976
|
const ownsTransition = runtime !== void 0 && transition === void 0;
|
|
1713
1977
|
if (transition) transition.physicalRelease = physicalRelease;
|
|
@@ -1720,15 +1984,19 @@ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs)
|
|
|
1720
1984
|
}
|
|
1721
1985
|
try {
|
|
1722
1986
|
await physicalRelease;
|
|
1987
|
+
if (retained !== void 0 && runtime?.retainedThreads.get(threadId) === retained) {
|
|
1988
|
+
runtime.retainedThreads.delete(threadId);
|
|
1989
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1990
|
+
}
|
|
1723
1991
|
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
|
|
1724
1992
|
} finally {
|
|
1725
1993
|
if (ownsTransition && runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
|
|
1726
1994
|
}
|
|
1727
1995
|
}
|
|
1728
1996
|
/** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
|
|
1729
|
-
async function releaseCodexAppServerLiveThread(client, threadId) {
|
|
1997
|
+
async function releaseCodexAppServerLiveThread(client, threadId, assertCurrent) {
|
|
1730
1998
|
const runtime = configuredClients.get(client);
|
|
1731
|
-
return runtime ? await releaseRetainedThread(client, runtime, threadId) : false;
|
|
1999
|
+
return runtime ? await releaseRetainedThread(client, runtime, threadId, assertCurrent) : false;
|
|
1732
2000
|
}
|
|
1733
2001
|
/** Native child work pins its parent's subscription even after the foreground parent turn ends. */
|
|
1734
2002
|
function protectCodexAppServerLiveThread(client, threadId) {
|
|
@@ -1746,11 +2014,9 @@ function protectCodexAppServerLiveThread(client, threadId) {
|
|
|
1746
2014
|
runtime.protectedThreads.delete(threadId);
|
|
1747
2015
|
const retained = runtime.retainedThreads.get(threadId);
|
|
1748
2016
|
if (retained) {
|
|
2017
|
+
retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
|
|
1749
2018
|
runtime.retainedThreads.delete(threadId);
|
|
1750
|
-
runtime.retainedThreads.set(threadId,
|
|
1751
|
-
...retained,
|
|
1752
|
-
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS
|
|
1753
|
-
});
|
|
2019
|
+
runtime.retainedThreads.set(threadId, retained);
|
|
1754
2020
|
}
|
|
1755
2021
|
} else runtime.protectedThreads.set(threadId, count - 1);
|
|
1756
2022
|
scheduleRetainedThreadEviction(client, runtime);
|
|
@@ -1763,6 +2029,453 @@ function protectCodexAppServerLiveThread(client, threadId) {
|
|
|
1763
2029
|
};
|
|
1764
2030
|
}
|
|
1765
2031
|
//#endregion
|
|
2032
|
+
//#region extensions/codex/src/app-server/dynamic-tool-response-state.ts
|
|
2033
|
+
/** Retains the host-owned app preview without adding it to Codex's response payload. */
|
|
2034
|
+
function withDynamicToolTranscriptDetails(response, details) {
|
|
2035
|
+
if (details === void 0) return response;
|
|
2036
|
+
Object.defineProperty(response, "transcriptDetails", {
|
|
2037
|
+
configurable: true,
|
|
2038
|
+
enumerable: false,
|
|
2039
|
+
value: details
|
|
2040
|
+
});
|
|
2041
|
+
return response;
|
|
2042
|
+
}
|
|
2043
|
+
function withDynamicToolTerminalResolution(response, terminalResolution) {
|
|
2044
|
+
if (terminalResolution) {
|
|
2045
|
+
Object.defineProperties(response, {
|
|
2046
|
+
terminalResolution: {
|
|
2047
|
+
configurable: true,
|
|
2048
|
+
enumerable: false,
|
|
2049
|
+
value: terminalResolution
|
|
2050
|
+
},
|
|
2051
|
+
executionStarted: {
|
|
2052
|
+
configurable: true,
|
|
2053
|
+
enumerable: false,
|
|
2054
|
+
value: terminalResolution.executionStarted
|
|
2055
|
+
},
|
|
2056
|
+
...terminalResolution.executedArguments ? { executedArguments: {
|
|
2057
|
+
configurable: true,
|
|
2058
|
+
enumerable: false,
|
|
2059
|
+
value: terminalResolution.executedArguments
|
|
2060
|
+
} } : {}
|
|
2061
|
+
});
|
|
2062
|
+
withDynamicToolSideEffectEvidence(response, terminalResolution.sideEffectEvidence);
|
|
2063
|
+
}
|
|
2064
|
+
return response;
|
|
2065
|
+
}
|
|
2066
|
+
function withDynamicToolExecutionState(response, state) {
|
|
2067
|
+
Object.defineProperties(response, {
|
|
2068
|
+
executedArguments: {
|
|
2069
|
+
configurable: true,
|
|
2070
|
+
enumerable: false,
|
|
2071
|
+
value: state.executedArguments
|
|
2072
|
+
},
|
|
2073
|
+
executionStarted: {
|
|
2074
|
+
configurable: true,
|
|
2075
|
+
enumerable: false,
|
|
2076
|
+
value: state.executionStarted
|
|
2077
|
+
}
|
|
2078
|
+
});
|
|
2079
|
+
return withDynamicToolSideEffectEvidence(response, state.sideEffectEvidence === true);
|
|
2080
|
+
}
|
|
2081
|
+
function withDynamicToolSideEffectEvidence(response, sideEffectEvidence) {
|
|
2082
|
+
if (!sideEffectEvidence) {
|
|
2083
|
+
delete response.sideEffectEvidence;
|
|
2084
|
+
return response;
|
|
2085
|
+
}
|
|
2086
|
+
Object.defineProperty(response, "sideEffectEvidence", {
|
|
2087
|
+
configurable: true,
|
|
2088
|
+
enumerable: false,
|
|
2089
|
+
value: true
|
|
2090
|
+
});
|
|
2091
|
+
return response;
|
|
2092
|
+
}
|
|
2093
|
+
function createFailedDynamicToolResponse(message, options) {
|
|
2094
|
+
const response = {
|
|
2095
|
+
contentItems: [{
|
|
2096
|
+
type: "inputText",
|
|
2097
|
+
text: message
|
|
2098
|
+
}],
|
|
2099
|
+
success: false
|
|
2100
|
+
};
|
|
2101
|
+
Object.defineProperties(response, {
|
|
2102
|
+
diagnosticTerminalReason: {
|
|
2103
|
+
configurable: true,
|
|
2104
|
+
enumerable: false,
|
|
2105
|
+
value: options?.terminalReason ?? "failed"
|
|
2106
|
+
},
|
|
2107
|
+
diagnosticTerminalType: {
|
|
2108
|
+
configurable: true,
|
|
2109
|
+
enumerable: false,
|
|
2110
|
+
value: "error"
|
|
2111
|
+
}
|
|
2112
|
+
});
|
|
2113
|
+
if (options?.executionStarted !== void 0) Object.defineProperty(response, "executionStarted", {
|
|
2114
|
+
configurable: true,
|
|
2115
|
+
enumerable: false,
|
|
2116
|
+
value: options.executionStarted
|
|
2117
|
+
});
|
|
2118
|
+
if (options?.executedArguments !== void 0) Object.defineProperty(response, "executedArguments", {
|
|
2119
|
+
configurable: true,
|
|
2120
|
+
enumerable: false,
|
|
2121
|
+
value: options.executedArguments
|
|
2122
|
+
});
|
|
2123
|
+
return withDynamicToolSideEffectEvidence(response, options?.sideEffectEvidence === true);
|
|
2124
|
+
}
|
|
2125
|
+
//#endregion
|
|
2126
|
+
//#region extensions/codex/src/app-server/tool-abort-terminal-reason.ts
|
|
2127
|
+
/** Leaf helper shared by native and dynamic tool diagnostics. */
|
|
2128
|
+
const CODEX_TIMEOUT_ABORT_REASONS = /* @__PURE__ */ new Set([
|
|
2129
|
+
"codex_startup_timeout",
|
|
2130
|
+
"turn_completion_idle_timeout",
|
|
2131
|
+
"turn_progress_idle_timeout",
|
|
2132
|
+
"turn_terminal_idle_timeout"
|
|
2133
|
+
]);
|
|
2134
|
+
/** Preserves timeout provenance when an enclosing run aborts an active tool. */
|
|
2135
|
+
function resolveCodexToolAbortTerminalReason(signal) {
|
|
2136
|
+
try {
|
|
2137
|
+
const reason = signal.reason;
|
|
2138
|
+
if (typeof reason === "string") {
|
|
2139
|
+
if (CODEX_TIMEOUT_ABORT_REASONS.has(reason)) return "timed_out";
|
|
2140
|
+
return reason === "client_closed" ? "failed" : "cancelled";
|
|
2141
|
+
}
|
|
2142
|
+
if (reason && typeof reason === "object") {
|
|
2143
|
+
const record = reason;
|
|
2144
|
+
if (record.name === "TimeoutError" || record.reason === "timeout") return "timed_out";
|
|
2145
|
+
}
|
|
2146
|
+
} catch {
|
|
2147
|
+
return "cancelled";
|
|
2148
|
+
}
|
|
2149
|
+
return "cancelled";
|
|
2150
|
+
}
|
|
2151
|
+
//#endregion
|
|
2152
|
+
//#region extensions/codex/src/app-server/dynamic-tool-execution.ts
|
|
2153
|
+
/**
|
|
2154
|
+
* Timeout, terminal-release, and diagnostic helpers for Codex dynamic tool
|
|
2155
|
+
* calls.
|
|
2156
|
+
*/
|
|
2157
|
+
/** Default timeout for Codex dynamic tool calls. */
|
|
2158
|
+
const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 9e4;
|
|
2159
|
+
/** Hard cap for per-call Codex dynamic tool timeout overrides. */
|
|
2160
|
+
const CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS = 6e5;
|
|
2161
|
+
const CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS = 3e4;
|
|
2162
|
+
const CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS = 12e4;
|
|
2163
|
+
const CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS = 3e4;
|
|
2164
|
+
const CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS = 3e4;
|
|
2165
|
+
/** Timeout for image-understanding style dynamic tool calls. */
|
|
2166
|
+
const CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS = 6e4;
|
|
2167
|
+
/** Timeout for message-delivery dynamic tool calls. */
|
|
2168
|
+
const CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS = 6e5;
|
|
2169
|
+
/** Outer default for collector waits: full swarm budget plus completion grace. */
|
|
2170
|
+
const CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS = 63e4;
|
|
2171
|
+
const LOG_FIELD_MAX_LENGTH = 160;
|
|
2172
|
+
function normalizeLogField(value) {
|
|
2173
|
+
if (typeof value !== "string") return;
|
|
2174
|
+
const normalized = value.replaceAll(String.fromCharCode(27), " ").replaceAll("\r", " ").replaceAll("\n", " ").replaceAll(" ", " ").trim();
|
|
2175
|
+
if (!normalized) return;
|
|
2176
|
+
return normalized.length > LOG_FIELD_MAX_LENGTH ? `${truncateUtf16Safe(normalized, 157)}...` : normalized;
|
|
2177
|
+
}
|
|
2178
|
+
function readNumericTimeoutMs(value) {
|
|
2179
|
+
if (typeof value === "number" && Number.isFinite(value)) return Math.max(0, Math.floor(value));
|
|
2180
|
+
if (typeof value === "string") {
|
|
2181
|
+
const parsed = parseStrictNonNegativeInteger(value);
|
|
2182
|
+
if (parsed !== void 0) return Math.max(0, Math.floor(parsed));
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
function formatDynamicToolTimeoutDetails(params) {
|
|
2186
|
+
const tool = normalizeLogField(params.call.tool) ?? "unknown";
|
|
2187
|
+
const baseMeta = {
|
|
2188
|
+
tool: params.call.tool,
|
|
2189
|
+
toolCallId: params.call.callId,
|
|
2190
|
+
threadId: params.call.threadId,
|
|
2191
|
+
turnId: params.call.turnId,
|
|
2192
|
+
timeoutMs: params.timeoutMs,
|
|
2193
|
+
timeoutKind: "codex_dynamic_tool_rpc"
|
|
2194
|
+
};
|
|
2195
|
+
if (tool !== "process" || !isJsonObject(params.call.arguments)) return {
|
|
2196
|
+
responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms while running tool ${tool}.`,
|
|
2197
|
+
consoleMessage: `codex dynamic tool timeout: tool=${tool} toolTimeoutMs=${params.timeoutMs}; per-tool-call watchdog, not session idle`,
|
|
2198
|
+
meta: baseMeta
|
|
2199
|
+
};
|
|
2200
|
+
const action = normalizeLogField(params.call.arguments.action);
|
|
2201
|
+
const sessionId = normalizeLogField(params.call.arguments.sessionId);
|
|
2202
|
+
const requestedTimeoutMs = readNumericTimeoutMs(params.call.arguments.timeout);
|
|
2203
|
+
const actionPart = action ? ` action=${action}` : "";
|
|
2204
|
+
const sessionPart = sessionId ? ` sessionId=${sessionId}` : "";
|
|
2205
|
+
const requestedPart = requestedTimeoutMs === void 0 ? "" : ` requestedWaitMs=${requestedTimeoutMs}`;
|
|
2206
|
+
const retryHint = action === "poll" ? "; repeated lines usually mean process-poll retry churn, not model progress" : "";
|
|
2207
|
+
const responseTarget = action || sessionId ? ` while waiting for process${actionPart}${sessionPart}` : " while waiting for the process tool";
|
|
2208
|
+
return {
|
|
2209
|
+
responseMessage: `OpenClaw dynamic tool call timed out after ${params.timeoutMs}ms${responseTarget}. This is a tool RPC timeout, not a session idle timeout.`,
|
|
2210
|
+
consoleMessage: `codex process tool timeout:${actionPart}${sessionPart} toolTimeoutMs=${params.timeoutMs}${requestedPart}; per-tool-call watchdog, not session idle${retryHint}`,
|
|
2211
|
+
meta: {
|
|
2212
|
+
...baseMeta,
|
|
2213
|
+
processAction: action,
|
|
2214
|
+
processSessionId: sessionId,
|
|
2215
|
+
processRequestedTimeoutMs: requestedTimeoutMs
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* Runs a dynamic tool call with run-abort and the budget prepared by
|
|
2221
|
+
* resolveDynamicToolCallTimeoutMs, preserving tool-specific completion grace.
|
|
2222
|
+
*/
|
|
2223
|
+
async function handleDynamicToolCallWithTimeout(params) {
|
|
2224
|
+
let didNotifyAgentToolResult = false;
|
|
2225
|
+
const conservativeRaceResponses = /* @__PURE__ */ new WeakSet();
|
|
2226
|
+
const finalizeTerminal = (response) => {
|
|
2227
|
+
const executionSnapshot = params.toolBridge.consumeToolExecutionSnapshot?.(params.call.callId);
|
|
2228
|
+
const ownerKey = params.toolBridge.sideEffectOwnerKeyForTool?.(params.call.tool);
|
|
2229
|
+
const observedExecutionStarted = executionSnapshot?.executionStarted ?? (conservativeRaceResponses.has(response) ? void 0 : response.executionStarted);
|
|
2230
|
+
const terminalResolution = params.observeToolTerminal?.({
|
|
2231
|
+
toolCallId: params.call.callId,
|
|
2232
|
+
toolName: params.call.tool,
|
|
2233
|
+
arguments: response.executedArguments ?? executionSnapshot?.executedArguments ?? params.call.arguments,
|
|
2234
|
+
...params.toolMeta ? { meta: params.toolMeta } : {},
|
|
2235
|
+
...ownerKey ? { ownerMutation: { ownerKey } } : {},
|
|
2236
|
+
...observedExecutionStarted !== void 0 ? { executionStarted: observedExecutionStarted } : {},
|
|
2237
|
+
outcome: response.success ? "success" : "failure",
|
|
2238
|
+
...!response.success ? { failure: { error: readDynamicToolResponseText(response) } } : {}
|
|
2239
|
+
});
|
|
2240
|
+
return withDynamicToolTerminalResolution(response, terminalResolution);
|
|
2241
|
+
};
|
|
2242
|
+
const createFailedAfterPossibleDispatch = (message, terminalReason) => {
|
|
2243
|
+
const response = createFailedDynamicToolResponse(message, {
|
|
2244
|
+
executionStarted: true,
|
|
2245
|
+
sideEffectEvidence: true,
|
|
2246
|
+
terminalReason
|
|
2247
|
+
});
|
|
2248
|
+
conservativeRaceResponses.add(response);
|
|
2249
|
+
return response;
|
|
2250
|
+
};
|
|
2251
|
+
const notifyAgentToolResult = (event) => {
|
|
2252
|
+
if (didNotifyAgentToolResult) return;
|
|
2253
|
+
didNotifyAgentToolResult = true;
|
|
2254
|
+
try {
|
|
2255
|
+
params.onAgentToolResult?.(event);
|
|
2256
|
+
} catch (error) {
|
|
2257
|
+
embeddedAgentLog.warn(`onAgentToolResult handler failed: tool=${params.call.tool} error=${String(error)}`);
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
const notifyFailedToolResult = (message, terminalReason = "failed") => {
|
|
2261
|
+
notifyAgentToolResult({
|
|
2262
|
+
toolName: params.call.tool,
|
|
2263
|
+
result: {
|
|
2264
|
+
content: [{
|
|
2265
|
+
type: "text",
|
|
2266
|
+
text: message
|
|
2267
|
+
}],
|
|
2268
|
+
details: {
|
|
2269
|
+
status: terminalReason,
|
|
2270
|
+
error: message
|
|
2271
|
+
}
|
|
2272
|
+
},
|
|
2273
|
+
isError: true
|
|
2274
|
+
});
|
|
2275
|
+
};
|
|
2276
|
+
if (params.signal.aborted) {
|
|
2277
|
+
const message = "OpenClaw dynamic tool call aborted before execution.";
|
|
2278
|
+
const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
|
|
2279
|
+
params.onFallbackSelected?.();
|
|
2280
|
+
notifyFailedToolResult(message, terminalReason);
|
|
2281
|
+
return finalizeTerminal(createFailedDynamicToolResponse(message, {
|
|
2282
|
+
executionStarted: false,
|
|
2283
|
+
terminalReason
|
|
2284
|
+
}));
|
|
2285
|
+
}
|
|
2286
|
+
const controller = new AbortController();
|
|
2287
|
+
let timeout;
|
|
2288
|
+
let timedOut = false;
|
|
2289
|
+
let resolveAbort;
|
|
2290
|
+
const abortFromRun = () => {
|
|
2291
|
+
const message = "OpenClaw dynamic tool call aborted.";
|
|
2292
|
+
const terminalReason = resolveCodexToolAbortTerminalReason(params.signal);
|
|
2293
|
+
params.onFallbackSelected?.();
|
|
2294
|
+
controller.abort(params.signal.reason ?? /* @__PURE__ */ new Error(message));
|
|
2295
|
+
notifyFailedToolResult(message, terminalReason);
|
|
2296
|
+
resolveAbort?.(createFailedAfterPossibleDispatch(message, terminalReason));
|
|
2297
|
+
};
|
|
2298
|
+
const abortPromise = new Promise((resolve) => {
|
|
2299
|
+
resolveAbort = resolve;
|
|
2300
|
+
});
|
|
2301
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
2302
|
+
const { timeoutMs } = params;
|
|
2303
|
+
timeout = setTimeout(() => {
|
|
2304
|
+
timedOut = true;
|
|
2305
|
+
const timeoutDetails = formatDynamicToolTimeoutDetails({
|
|
2306
|
+
call: params.call,
|
|
2307
|
+
timeoutMs
|
|
2308
|
+
});
|
|
2309
|
+
params.onFallbackSelected?.();
|
|
2310
|
+
controller.abort(new Error(timeoutDetails.responseMessage));
|
|
2311
|
+
params.onTimeout?.();
|
|
2312
|
+
embeddedAgentLog.warn("codex dynamic tool call timed out", {
|
|
2313
|
+
...timeoutDetails.meta,
|
|
2314
|
+
consoleMessage: timeoutDetails.consoleMessage
|
|
2315
|
+
});
|
|
2316
|
+
notifyFailedToolResult(timeoutDetails.responseMessage, "timed_out");
|
|
2317
|
+
resolve(createFailedAfterPossibleDispatch(timeoutDetails.responseMessage, "timed_out"));
|
|
2318
|
+
}, timeoutMs);
|
|
2319
|
+
timeout.unref?.();
|
|
2320
|
+
});
|
|
2321
|
+
try {
|
|
2322
|
+
params.signal.addEventListener("abort", abortFromRun, { once: true });
|
|
2323
|
+
if (params.signal.aborted) abortFromRun();
|
|
2324
|
+
const response = await Promise.race([
|
|
2325
|
+
params.toolBridge.handleToolCall(params.call, {
|
|
2326
|
+
signal: controller.signal,
|
|
2327
|
+
onAgentToolResult: notifyAgentToolResult,
|
|
2328
|
+
toolCallOrdinal: params.toolCallOrdinal,
|
|
2329
|
+
retainExecutionSnapshot: true
|
|
2330
|
+
}),
|
|
2331
|
+
abortPromise,
|
|
2332
|
+
timeoutPromise
|
|
2333
|
+
]);
|
|
2334
|
+
if (!response.success && !didNotifyAgentToolResult) notifyFailedToolResult(readDynamicToolResponseText(response), response.diagnosticTerminalReason ?? "failed");
|
|
2335
|
+
return finalizeTerminal(response);
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
const terminalReason = params.signal.aborted ? resolveCodexToolAbortTerminalReason(params.signal) : resolveToolExecutionErrorKind(error);
|
|
2338
|
+
const message = formatToolExecutionErrorMessage(error, "OpenClaw dynamic tool call failed.");
|
|
2339
|
+
notifyFailedToolResult(message, terminalReason);
|
|
2340
|
+
return finalizeTerminal(createFailedAfterPossibleDispatch(message, terminalReason));
|
|
2341
|
+
} finally {
|
|
2342
|
+
if (timeout) clearTimeout(timeout);
|
|
2343
|
+
params.signal.removeEventListener("abort", abortFromRun);
|
|
2344
|
+
resolveAbort = void 0;
|
|
2345
|
+
if (!timedOut && !controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("OpenClaw dynamic tool call finished."));
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
function readDynamicToolResponseText(response) {
|
|
2349
|
+
return response.contentItems.flatMap((item) => item.type === "inputText" && typeof item.text === "string" ? [item.text] : []).join("\n").trim() || "OpenClaw dynamic tool call failed.";
|
|
2350
|
+
}
|
|
2351
|
+
/** Strips OpenClaw-only metadata before sending a dynamic tool response to Codex. */
|
|
2352
|
+
function toCodexDynamicToolProtocolResponse(response) {
|
|
2353
|
+
return {
|
|
2354
|
+
contentItems: response.contentItems,
|
|
2355
|
+
success: response.success
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
/** Adds async-started progress details when a tool result continues out of band. */
|
|
2359
|
+
function toCodexDynamicToolProgressResponse(response, protocolResponse) {
|
|
2360
|
+
const transcriptDetails = isJsonObject(response.transcriptDetails) ? response.transcriptDetails : void 0;
|
|
2361
|
+
const mcpAppPreview = isJsonObject(transcriptDetails?.mcpAppPreview) ? transcriptDetails.mcpAppPreview : void 0;
|
|
2362
|
+
const progressDetails = mcpAppPreview ? { mcpAppPreview } : void 0;
|
|
2363
|
+
if (response.asyncStarted !== true && progressDetails === void 0) return protocolResponse;
|
|
2364
|
+
return {
|
|
2365
|
+
...protocolResponse,
|
|
2366
|
+
...progressDetails ? { details: progressDetails } : {},
|
|
2367
|
+
...response.asyncStarted === true ? { details: {
|
|
2368
|
+
...progressDetails,
|
|
2369
|
+
async: true,
|
|
2370
|
+
status: "started"
|
|
2371
|
+
} } : {}
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
/** Decides whether a terminal dynamic tool response can release the Codex turn. */
|
|
2375
|
+
function shouldReleaseTurnAfterTerminalDynamicTool(state) {
|
|
2376
|
+
return !state.completed && !state.aborted && state.responseSuccess && !state.currentTurnHadNonTerminalDynamicToolResult && state.activeAppServerTurnRequests === 0 && state.activeTurnItemIdsCount === 0 && state.pendingOpenClawDynamicToolCompletionIdsCount === 0;
|
|
2377
|
+
}
|
|
2378
|
+
/** Returns true when a non-async result should block terminal-release shortcuts. */
|
|
2379
|
+
function shouldBlockTerminalReleaseForNonTerminalDynamicToolResult(response) {
|
|
2380
|
+
return response.asyncStarted !== true;
|
|
2381
|
+
}
|
|
2382
|
+
/** Resolves whether terminal diagnostic state should release, wait, or stay idle. */
|
|
2383
|
+
function resolveTerminalDynamicToolBatchAction(state) {
|
|
2384
|
+
if (state.activeAppServerTurnRequests > 0 || state.activeTurnItemIdsCount > 0 || state.pendingOpenClawDynamicToolCompletionIdsCount > 0) return "wait";
|
|
2385
|
+
if (state.currentTurnHadNonTerminalDynamicToolResult) return "clear-nonterminal-batch";
|
|
2386
|
+
if (state.hasPendingTerminalDynamicToolRelease) return "release-pending-terminal";
|
|
2387
|
+
return "idle";
|
|
2388
|
+
}
|
|
2389
|
+
/** Returns true for diagnostic events that terminate a dynamic tool call. */
|
|
2390
|
+
function isDynamicToolTerminalDiagnosticEvent(event) {
|
|
2391
|
+
return event.type === "tool.execution.completed" || event.type === "tool.execution.error" || event.type === "tool.execution.blocked";
|
|
2392
|
+
}
|
|
2393
|
+
/** Matches terminal diagnostics to a specific dynamic tool call id/name. */
|
|
2394
|
+
function isMatchingDynamicToolTerminalDiagnostic(params) {
|
|
2395
|
+
if (params.event.toolCallId !== params.call.callId || params.event.toolName !== params.call.tool) return false;
|
|
2396
|
+
if (params.runId !== void 0) return params.event.runId === params.runId;
|
|
2397
|
+
if (params.sessionId !== void 0) return params.event.sessionId === params.sessionId;
|
|
2398
|
+
if (params.sessionKey !== void 0) return params.event.sessionKey === params.sessionKey;
|
|
2399
|
+
return params.event.runId === void 0 && params.event.sessionId === void 0 && params.event.sessionKey === void 0;
|
|
2400
|
+
}
|
|
2401
|
+
/** Checks pending diagnostics for a terminal event matching a tool call. */
|
|
2402
|
+
function hasPendingDynamicToolTerminalDiagnostic(params) {
|
|
2403
|
+
return hasPendingInternalDiagnosticEvent((event) => {
|
|
2404
|
+
if (!isDynamicToolTerminalDiagnosticEvent(event)) return false;
|
|
2405
|
+
return isMatchingDynamicToolTerminalDiagnostic({
|
|
2406
|
+
event,
|
|
2407
|
+
call: params.call,
|
|
2408
|
+
runId: params.runId,
|
|
2409
|
+
sessionId: params.sessionId,
|
|
2410
|
+
sessionKey: params.sessionKey
|
|
2411
|
+
});
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
/** Resolves per-tool timeout, applying media/message defaults and hard caps. */
|
|
2415
|
+
function resolveDynamicToolCallTimeoutMs(params) {
|
|
2416
|
+
const args = isJsonObject(params.call.arguments) ? params.call.arguments : void 0;
|
|
2417
|
+
if (params.call.tool === "ask_user" || params.call.tool === "secrets" && args?.action === "request") try {
|
|
2418
|
+
return normalizeQuestionTimeoutSeconds(args?.timeoutSeconds) * 1e3 + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
|
|
2419
|
+
} catch {
|
|
2420
|
+
return CODEX_DYNAMIC_TOOL_TIMEOUT_MS;
|
|
2421
|
+
}
|
|
2422
|
+
if (params.call.tool === "computer") return clampDynamicToolTimeoutMs(readComputerToolTimeoutMs(params.call.arguments));
|
|
2423
|
+
if (params.call.tool === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
|
|
2424
|
+
if (params.call.tool === "agents_wait") {
|
|
2425
|
+
const requestedMs = readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_AGENTS_WAIT_TOOL_TIMEOUT_MS;
|
|
2426
|
+
return Math.max(1, Math.min(63e4, Math.floor(requestedMs)));
|
|
2427
|
+
}
|
|
2428
|
+
return clampDynamicToolTimeoutMs(readDynamicToolCallTimeoutMs(params.call.arguments) ?? readConfiguredDynamicToolTimeoutMs(params.call.tool, params.config) ?? CODEX_DYNAMIC_TOOL_TIMEOUT_MS);
|
|
2429
|
+
}
|
|
2430
|
+
/** Transport guard stays outside the handler's bounded tool wait and completion grace. */
|
|
2431
|
+
function resolveDynamicToolServerRequestTimeoutMs(call) {
|
|
2432
|
+
return Math.max(63e4, call ? resolveDynamicToolCallTimeoutMs({
|
|
2433
|
+
call,
|
|
2434
|
+
config: void 0
|
|
2435
|
+
}) : 0) + CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS;
|
|
2436
|
+
}
|
|
2437
|
+
function readComputerToolTimeoutMs(value) {
|
|
2438
|
+
const args = isJsonObject(value) ? value : void 0;
|
|
2439
|
+
const action = typeof args?.action === "string" ? args.action : void 0;
|
|
2440
|
+
const gatewayTimeoutMs = readPositiveFiniteTimeoutMs(args?.timeoutMs) ?? CODEX_DYNAMIC_COMPUTER_GATEWAY_TIMEOUT_MS;
|
|
2441
|
+
const gatewayCallCount = action === "screenshot" || action === "wait" ? 3 : 4;
|
|
2442
|
+
return (action === "wait" || action === "hold_key" ? Math.max(0, Number(args?.duration) || 0) * 1e3 : 0) + gatewayCallCount * gatewayTimeoutMs + CODEX_DYNAMIC_COMPUTER_COMPLETION_GRACE_MS;
|
|
2443
|
+
}
|
|
2444
|
+
function readDynamicToolCallTimeoutMs(value) {
|
|
2445
|
+
if (!isJsonObject(value)) return;
|
|
2446
|
+
const timeoutMs = readPositiveFiniteTimeoutMs(value.timeoutMs);
|
|
2447
|
+
if (timeoutMs !== void 0) return timeoutMs;
|
|
2448
|
+
const timeoutSecondsMs = readDynamicToolTimeoutSecondsAsMs(value.timeoutSeconds);
|
|
2449
|
+
return timeoutSecondsMs === void 0 ? void 0 : addTimerTimeoutGraceMs(timeoutSecondsMs, CODEX_DYNAMIC_TOOL_TIMEOUT_SECONDS_GRACE_MS);
|
|
2450
|
+
}
|
|
2451
|
+
function readConfiguredDynamicToolTimeoutMs(toolName, config) {
|
|
2452
|
+
if (toolName === "image_generate") {
|
|
2453
|
+
const imageModel = config?.agents?.defaults?.mediaModels?.image;
|
|
2454
|
+
if (!imageModel || typeof imageModel !== "object") return CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
|
|
2455
|
+
return readPositiveFiniteTimeoutMs(imageModel.timeoutMs) ?? CODEX_DYNAMIC_IMAGE_GENERATION_TOOL_TIMEOUT_MS;
|
|
2456
|
+
}
|
|
2457
|
+
if (toolName === "view_image") {
|
|
2458
|
+
const candidates = (config?.tools?.media?.models ?? []).filter((entry) => !entry.capabilities || entry.capabilities.includes("image"));
|
|
2459
|
+
const capabilityTimeoutMs = readTimeoutSecondsAsMs(config?.tools?.media?.image?.timeoutSeconds);
|
|
2460
|
+
return Math.max(capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS, ...candidates.map((entry) => readTimeoutSecondsAsMs(entry.timeoutSeconds) ?? capabilityTimeoutMs ?? CODEX_DYNAMIC_IMAGE_TOOL_TIMEOUT_MS));
|
|
2461
|
+
}
|
|
2462
|
+
if (toolName === "message") return CODEX_DYNAMIC_MESSAGE_TOOL_TIMEOUT_MS;
|
|
2463
|
+
}
|
|
2464
|
+
function readTimeoutSecondsAsMs(value) {
|
|
2465
|
+
const seconds = readPositiveFiniteTimeoutMs(value);
|
|
2466
|
+
return seconds === void 0 ? void 0 : seconds * 1e3;
|
|
2467
|
+
}
|
|
2468
|
+
function readDynamicToolTimeoutSecondsAsMs(value) {
|
|
2469
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) return;
|
|
2470
|
+
return value * 1e3;
|
|
2471
|
+
}
|
|
2472
|
+
function readPositiveFiniteTimeoutMs(value) {
|
|
2473
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
2474
|
+
}
|
|
2475
|
+
function clampDynamicToolTimeoutMs(timeoutMs) {
|
|
2476
|
+
return Math.max(1, Math.min(CODEX_DYNAMIC_TOOL_MAX_TIMEOUT_MS, Math.floor(timeoutMs)));
|
|
2477
|
+
}
|
|
2478
|
+
//#endregion
|
|
1766
2479
|
//#region extensions/codex/src/app-server/elicitation-response.ts
|
|
1767
2480
|
function createCodexElicitationResponse(action, content = null, meta = null) {
|
|
1768
2481
|
return {
|
|
@@ -1939,357 +2652,33 @@ function createWebSocketTransport(options) {
|
|
|
1939
2652
|
get killed() {
|
|
1940
2653
|
return killed;
|
|
1941
2654
|
},
|
|
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;
|
|
2655
|
+
kill: () => {
|
|
2656
|
+
killed = true;
|
|
2657
|
+
clearConnectionHealthTimers();
|
|
2658
|
+
socket.close();
|
|
2659
|
+
},
|
|
2660
|
+
once: (event, listener) => events.once(event, listener)
|
|
2661
|
+
};
|
|
2267
2662
|
}
|
|
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
|
-
});
|
|
2663
|
+
/** Opens the owner-scoped Codex control socket used by the WebSocket upgrade. */
|
|
2664
|
+
function connectCodexAppServerUnixSocket(socketPath) {
|
|
2665
|
+
return net.createConnection(socketPath);
|
|
2286
2666
|
}
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2667
|
+
/** Resolves the canonical or explicitly configured Codex control socket. */
|
|
2668
|
+
function resolveCodexAppServerUnixSocketPath(options) {
|
|
2669
|
+
if (options.transport !== "unix") {
|
|
2670
|
+
if (options.url?.startsWith("unix://")) throw new Error("codex app-server unix URL requires unix transport");
|
|
2290
2671
|
return;
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2672
|
+
}
|
|
2673
|
+
const url = options.url ?? "unix://";
|
|
2674
|
+
if (!url.startsWith("unix://")) throw new Error("codex app-server unix transport requires a unix:// URL");
|
|
2675
|
+
return url.slice(7) || path.join(resolveCodexAppServerUserHomeDir(options.env ?? process.env), "app-server-control", "app-server-control.sock");
|
|
2676
|
+
}
|
|
2677
|
+
function websocketFrameToText(data) {
|
|
2678
|
+
if (typeof data === "string") return data;
|
|
2679
|
+
if (Buffer.isBuffer(data)) return data.toString("utf8");
|
|
2680
|
+
if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
|
|
2681
|
+
return Buffer.from(data).toString("utf8");
|
|
2293
2682
|
}
|
|
2294
2683
|
//#endregion
|
|
2295
2684
|
//#region extensions/codex/src/app-server/client.ts
|
|
@@ -2298,13 +2687,13 @@ function signalCodexAppServerTransport(child, signal) {
|
|
|
2298
2687
|
* routing, notification fanout, server request handlers, and version checks.
|
|
2299
2688
|
*/
|
|
2300
2689
|
const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
|
|
2301
|
-
const CODEX_APP_SERVER_PARSE_BUFFER_MAX =
|
|
2690
|
+
const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 8388608;
|
|
2302
2691
|
const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1e3;
|
|
2303
|
-
const CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS = 66e4;
|
|
2304
2692
|
const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2e3;
|
|
2305
2693
|
const CODEX_APP_SERVER_OVERLOADED_ERROR_CODE = -32001;
|
|
2306
2694
|
const CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES = 3;
|
|
2307
2695
|
const CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS = 50;
|
|
2696
|
+
const CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX = 32;
|
|
2308
2697
|
const CODEX_APP_SERVER_CLIENT_INSTANCE_IDS = /* @__PURE__ */ new WeakMap();
|
|
2309
2698
|
const UNPAIRED_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
2310
2699
|
/** Process-local generation fence for bindings tied to one app-server client instance. */
|
|
@@ -2318,6 +2707,10 @@ function getCodexAppServerClientInstanceId(client) {
|
|
|
2318
2707
|
function resolveCodexAppServerClientInstanceId(client) {
|
|
2319
2708
|
return client.getInstanceId?.call(client) ?? getCodexAppServerClientInstanceId(client);
|
|
2320
2709
|
}
|
|
2710
|
+
/** Codex rejects this exact code before enqueueing, including mutating requests. */
|
|
2711
|
+
function isCodexAppServerOverloadError(error) {
|
|
2712
|
+
return error instanceof CodexAppServerRpcError && error.code === CODEX_APP_SERVER_OVERLOADED_ERROR_CODE;
|
|
2713
|
+
}
|
|
2321
2714
|
var CodexAppServerLocalRequestCancellationError = class extends Error {
|
|
2322
2715
|
constructor(method, reason, mayHaveWritten) {
|
|
2323
2716
|
super(`${method} ${reason}`);
|
|
@@ -2373,9 +2766,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2373
2766
|
this.pending = /* @__PURE__ */ new Map();
|
|
2374
2767
|
this.requestHandlers = /* @__PURE__ */ new Set();
|
|
2375
2768
|
this.notificationHandlers = /* @__PURE__ */ new Set();
|
|
2769
|
+
this.pendingStartupWarnings = [];
|
|
2376
2770
|
this.closeHandlers = /* @__PURE__ */ new Set();
|
|
2377
2771
|
this.nextId = 1;
|
|
2378
2772
|
this.initialized = false;
|
|
2773
|
+
this.modelCatalogRevision = 0;
|
|
2379
2774
|
this.closed = false;
|
|
2380
2775
|
this.transportExited = false;
|
|
2381
2776
|
this.stderrTail = "";
|
|
@@ -2401,7 +2796,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2401
2796
|
child.stdin.on?.("error", (error) => this.closeWithError(toStringifiedError(error)));
|
|
2402
2797
|
}
|
|
2403
2798
|
/** Starts a new app-server client using resolved runtime start options. */
|
|
2404
|
-
static start(options) {
|
|
2799
|
+
static async start(options, assertCurrent) {
|
|
2405
2800
|
const defaults = resolveCodexAppServerRuntimeOptions().start;
|
|
2406
2801
|
const startOptions = {
|
|
2407
2802
|
...defaults,
|
|
@@ -2410,7 +2805,18 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2410
2805
|
};
|
|
2411
2806
|
if (startOptions.transport === "stdio" && startOptions.commandSource === "managed") throw new Error("Managed Codex app-server start options must be resolved before spawn.");
|
|
2412
2807
|
if (startOptions.transport === "websocket" || startOptions.transport === "unix") return new CodexAppServerClient(createWebSocketTransport(startOptions));
|
|
2413
|
-
|
|
2808
|
+
let client;
|
|
2809
|
+
try {
|
|
2810
|
+
await createStdioTransport(startOptions, process.env, assertCurrent, (child) => {
|
|
2811
|
+
client = new CodexAppServerClient(child);
|
|
2812
|
+
});
|
|
2813
|
+
return client;
|
|
2814
|
+
} catch (error) {
|
|
2815
|
+
assertCurrent?.();
|
|
2816
|
+
if (client?.transportExited && hasCodexAppServerNaturalExit(client.child)) throw buildCodexAppServerExitError(client.child.exitCode, client.child.signalCode, client.stderrTail);
|
|
2817
|
+
const stderr = client?.getStderrDiagnostic();
|
|
2818
|
+
throw stderr ? new Error(`${coerceErrorMessage(error)}; stderr=${JSON.stringify(stderr)}`, { cause: error }) : error;
|
|
2819
|
+
}
|
|
2414
2820
|
}
|
|
2415
2821
|
/** Builds a client around a fake transport for tests. */
|
|
2416
2822
|
static fromTransportForTests(child) {
|
|
@@ -2429,7 +2835,8 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2429
2835
|
experimentalApi: true,
|
|
2430
2836
|
extensions: {
|
|
2431
2837
|
"openai/standard-form-input": {},
|
|
2432
|
-
"openai/form": {}
|
|
2838
|
+
"openai/form": {},
|
|
2839
|
+
"io.modelcontextprotocol/ui": { mimeTypes: ["text/html;profile=mcp-app"] }
|
|
2433
2840
|
}
|
|
2434
2841
|
}
|
|
2435
2842
|
});
|
|
@@ -2458,6 +2865,10 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2458
2865
|
getInstanceId() {
|
|
2459
2866
|
return this.instanceId;
|
|
2460
2867
|
}
|
|
2868
|
+
/** Account/config observations become stale before a mutation can enter the wire. */
|
|
2869
|
+
getModelCatalogRevision() {
|
|
2870
|
+
return this.modelCatalogRevision;
|
|
2871
|
+
}
|
|
2461
2872
|
/** Installs the spawn-owner check run before config-loading thread requests. */
|
|
2462
2873
|
setThreadSessionRequestGuard(guard) {
|
|
2463
2874
|
this.threadSessionRequestGuard = guard;
|
|
@@ -2467,8 +2878,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2467
2878
|
return this.child.pid;
|
|
2468
2879
|
}
|
|
2469
2880
|
request(method, params, optionsInput) {
|
|
2470
|
-
|
|
2471
|
-
options ??= {};
|
|
2881
|
+
const options = optionsInput ?? {};
|
|
2472
2882
|
if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
|
|
2473
2883
|
if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
|
|
2474
2884
|
const guard = method === "thread/start" || method === "thread/resume" || method === "thread/fork" ? this.threadSessionRequestGuard : void 0;
|
|
@@ -2503,11 +2913,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2503
2913
|
const elapsedMs = Date.now() - guardStartedAt;
|
|
2504
2914
|
const remainingTimeoutMs = options.timeoutMs === void 0 ? void 0 : options.timeoutMs - elapsedMs;
|
|
2505
2915
|
if (remainingTimeoutMs !== void 0 && remainingTimeoutMs <= 0) throw new CodexAppServerLocalRequestCancellationError(method, "timed out", false);
|
|
2506
|
-
return await this.
|
|
2916
|
+
return await this.requestWithOverloadRetry(method, params, {
|
|
2507
2917
|
...options,
|
|
2508
2918
|
...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
|
|
2509
|
-
}, () => {
|
|
2510
|
-
requestMayHaveWritten =
|
|
2919
|
+
}, (mayHaveWritten) => {
|
|
2920
|
+
requestMayHaveWritten = mayHaveWritten;
|
|
2511
2921
|
});
|
|
2512
2922
|
} catch (error) {
|
|
2513
2923
|
if (requestMayHaveWritten && !(error instanceof CodexAppServerRpcError)) {
|
|
@@ -2520,12 +2930,9 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2520
2930
|
}
|
|
2521
2931
|
})();
|
|
2522
2932
|
}
|
|
2523
|
-
return this.
|
|
2933
|
+
return this.requestWithOverloadRetry(method, params, options);
|
|
2524
2934
|
}
|
|
2525
|
-
|
|
2526
|
-
return this.requestWithOverloadRetry(method, params, options, onWriteAttempt);
|
|
2527
|
-
}
|
|
2528
|
-
async requestWithOverloadRetry(method, params, options, onWriteAttempt) {
|
|
2935
|
+
async requestWithOverloadRetry(method, params, options, onWriteStateChange) {
|
|
2529
2936
|
const deadline = options.timeoutMs !== void 0 && Number.isFinite(options.timeoutMs) ? Date.now() + options.timeoutMs : void 0;
|
|
2530
2937
|
for (let retry = 0;; retry += 1) {
|
|
2531
2938
|
if (options.signal?.aborted) throw new CodexAppServerLocalRequestCancellationError(method, "aborted", false);
|
|
@@ -2535,9 +2942,10 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2535
2942
|
return await this.requestOnce(method, params, {
|
|
2536
2943
|
...options,
|
|
2537
2944
|
...remainingTimeoutMs !== void 0 ? { timeoutMs: remainingTimeoutMs } : {}
|
|
2538
|
-
},
|
|
2945
|
+
}, onWriteStateChange);
|
|
2539
2946
|
} catch (error) {
|
|
2540
|
-
if (!(error
|
|
2947
|
+
if (!isCodexAppServerOverloadError(error) || retry >= CODEX_APP_SERVER_OVERLOAD_MAX_RETRIES) throw error;
|
|
2948
|
+
onWriteStateChange?.(false);
|
|
2541
2949
|
const backoffMs = Math.round(CODEX_APP_SERVER_OVERLOAD_RETRY_BASE_MS * 2 ** retry * (.75 + Math.random() * .5));
|
|
2542
2950
|
await this.waitForOverloadRetry(method, backoffMs, deadline, options.signal);
|
|
2543
2951
|
}
|
|
@@ -2566,10 +2974,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2566
2974
|
if (signal?.aborted) abortListener();
|
|
2567
2975
|
});
|
|
2568
2976
|
}
|
|
2569
|
-
requestOnce(method, params, options,
|
|
2977
|
+
requestOnce(method, params, options, onWriteStateChange) {
|
|
2570
2978
|
if (this.closed) return Promise.reject(this.closeError ?? /* @__PURE__ */ new Error("codex app-server client is closed"));
|
|
2571
2979
|
if (options.signal?.aborted) return Promise.reject(new CodexAppServerLocalRequestCancellationError(method, "aborted", false));
|
|
2572
2980
|
const id = this.nextId++;
|
|
2981
|
+
if (method === "account/login/start" || method === "account/logout" || method === "config/value/write" || method === "config/batchWrite") this.modelCatalogRevision += 1;
|
|
2573
2982
|
const message = {
|
|
2574
2983
|
id,
|
|
2575
2984
|
method,
|
|
@@ -2619,8 +3028,9 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2619
3028
|
return;
|
|
2620
3029
|
}
|
|
2621
3030
|
try {
|
|
3031
|
+
options.assertCurrent?.();
|
|
2622
3032
|
mayHaveWritten = true;
|
|
2623
|
-
|
|
3033
|
+
onWriteStateChange?.(true);
|
|
2624
3034
|
this.writeMessage(message, (error) => rejectPending(error));
|
|
2625
3035
|
} catch (error) {
|
|
2626
3036
|
rejectPending(toStringifiedError(error));
|
|
@@ -2642,6 +3052,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2642
3052
|
/** Registers a notification handler and returns its disposer. */
|
|
2643
3053
|
addNotificationHandler(handler) {
|
|
2644
3054
|
this.notificationHandlers.add(handler);
|
|
3055
|
+
for (const notification of this.pendingStartupWarnings.splice(0)) this.handleNotification(notification);
|
|
2645
3056
|
return () => this.notificationHandlers.delete(handler);
|
|
2646
3057
|
}
|
|
2647
3058
|
/** Registers a close handler and returns its disposer. */
|
|
@@ -2649,6 +3060,16 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2649
3060
|
this.closeHandlers.add(handler);
|
|
2650
3061
|
return () => this.closeHandlers.delete(handler);
|
|
2651
3062
|
}
|
|
3063
|
+
/** Registers a handler for physical transport exit and returns its disposer. */
|
|
3064
|
+
addTransportExitHandler(handler) {
|
|
3065
|
+
if (this.transportExited) {
|
|
3066
|
+
handler(this);
|
|
3067
|
+
return () => void 0;
|
|
3068
|
+
}
|
|
3069
|
+
const onExit = () => handler(this);
|
|
3070
|
+
this.child.once("exit", onExit);
|
|
3071
|
+
return () => this.child.off?.("exit", onExit);
|
|
3072
|
+
}
|
|
2652
3073
|
/** Closes the transport without waiting for process/socket shutdown. */
|
|
2653
3074
|
close() {
|
|
2654
3075
|
if (!this.markClosed(/* @__PURE__ */ new Error("codex app-server client is closed"))) return;
|
|
@@ -2811,8 +3232,9 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2811
3232
|
}
|
|
2812
3233
|
async runServerRequestHandlers(request) {
|
|
2813
3234
|
const controller = new AbortController();
|
|
2814
|
-
|
|
2815
|
-
|
|
3235
|
+
if (request.method !== "item/tool/call") return await this.runServerRequestHandlersWithoutTimeout(request, controller.signal);
|
|
3236
|
+
const timeoutMs = resolveDynamicToolServerRequestTimeoutMs(readCodexDynamicToolCallParams(request.params));
|
|
3237
|
+
const timeoutResponse = timeoutServerRequestResponse(timeoutMs);
|
|
2816
3238
|
let timeout;
|
|
2817
3239
|
try {
|
|
2818
3240
|
return await Promise.race([this.runServerRequestHandlersWithoutTimeout(request, controller.signal), new Promise((resolve) => {
|
|
@@ -2820,11 +3242,11 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2820
3242
|
embeddedAgentLog.warn("codex app-server server request timed out", {
|
|
2821
3243
|
id: request.id,
|
|
2822
3244
|
method: request.method,
|
|
2823
|
-
timeoutMs
|
|
3245
|
+
timeoutMs
|
|
2824
3246
|
});
|
|
2825
3247
|
controller.abort(/* @__PURE__ */ new Error("codex app-server server request timed out"));
|
|
2826
3248
|
resolve(timeoutResponse);
|
|
2827
|
-
},
|
|
3249
|
+
}, timeoutMs);
|
|
2828
3250
|
timeout.unref?.();
|
|
2829
3251
|
})]);
|
|
2830
3252
|
} finally {
|
|
@@ -2839,6 +3261,12 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
2839
3261
|
}
|
|
2840
3262
|
}
|
|
2841
3263
|
handleNotification(notification) {
|
|
3264
|
+
if (notification.method === "account/updated") this.modelCatalogRevision += 1;
|
|
3265
|
+
if (this.notificationHandlers.size === 0 && notification.method === "configWarning") {
|
|
3266
|
+
if (this.pendingStartupWarnings.length === CODEX_APP_SERVER_PENDING_STARTUP_WARNINGS_MAX) this.pendingStartupWarnings.shift();
|
|
3267
|
+
this.pendingStartupWarnings.push(notification);
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
2842
3270
|
for (const handler of this.notificationHandlers) try {
|
|
2843
3271
|
Promise.resolve(handler(notification)).catch((error) => {
|
|
2844
3272
|
embeddedAgentLog.warn("codex app-server notification handler failed", { error });
|
|
@@ -2887,12 +3315,11 @@ function defaultServerRequestResponse(request) {
|
|
|
2887
3315
|
function stringifyCodexAppServerMessage(message) {
|
|
2888
3316
|
return JSON.stringify(message, (_key, value) => typeof value === "string" ? value.replace(UNPAIRED_SURROGATE_RE, "") : value) ?? "null";
|
|
2889
3317
|
}
|
|
2890
|
-
function timeoutServerRequestResponse(
|
|
2891
|
-
if (request.method !== "item/tool/call") return;
|
|
3318
|
+
function timeoutServerRequestResponse(timeoutMs) {
|
|
2892
3319
|
return {
|
|
2893
3320
|
contentItems: [{
|
|
2894
3321
|
type: "inputText",
|
|
2895
|
-
text: `OpenClaw dynamic tool call timed out after ${
|
|
3322
|
+
text: `OpenClaw dynamic tool call timed out after ${timeoutMs}ms before sending a response to Codex.`
|
|
2896
3323
|
}],
|
|
2897
3324
|
success: false
|
|
2898
3325
|
};
|
|
@@ -2910,8 +3337,8 @@ function assertSupportedCodexAppServerVersion(response) {
|
|
|
2910
3337
|
const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
|
|
2911
3338
|
if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
|
|
2912
3339
|
const detected = parse(detectedVersion);
|
|
2913
|
-
if (!detected || detected.compare("0.
|
|
2914
|
-
if (detected.compare("0.
|
|
3340
|
+
if (!detected || detected.compare("0.149.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
|
|
3341
|
+
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
3342
|
detectedVersion,
|
|
2916
3343
|
validatedVersion: CODEX_APP_SERVER_VERSION
|
|
2917
3344
|
});
|
|
@@ -2992,11 +3419,13 @@ const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
|
|
|
2992
3419
|
const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
|
|
2993
3420
|
/** Short guard after apparent assistant completion. */
|
|
2994
3421
|
const CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 1e4;
|
|
2995
|
-
const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS =
|
|
3422
|
+
const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 3e5;
|
|
2996
3423
|
/** Guard after reasoning/commentary progress when no tool handoff occurred. */
|
|
2997
|
-
const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS =
|
|
3424
|
+
const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 3e5;
|
|
2998
3425
|
/** Long terminal idle watch for app-server turns that never send completion. */
|
|
2999
|
-
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS =
|
|
3426
|
+
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 18e5;
|
|
3427
|
+
const TURN_TERMINAL_SETTLEMENT_IDLE_TIMEOUT_MS = 12e4;
|
|
3428
|
+
const TURN_FINALIZE_DRAIN_ABORT_GRACE_MS = 5e3;
|
|
3000
3429
|
var CodexAppServerStartupError = class extends Error {
|
|
3001
3430
|
constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
|
|
3002
3431
|
super(message);
|
|
@@ -3009,7 +3438,8 @@ function isCodexAppServerStartupError(error, reason) {
|
|
|
3009
3438
|
return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_STARTUP_CANCELLED" && "reason" in error && (error.reason === "aborted" || error.reason === "timed_out") && (reason === void 0 || error.reason === reason);
|
|
3010
3439
|
}
|
|
3011
3440
|
function resolvePositiveIntegerTimeoutMs(value, fallbackMs) {
|
|
3012
|
-
|
|
3441
|
+
const fallback = resolveTimerTimeoutMs(fallbackMs, 1);
|
|
3442
|
+
return resolveTimerTimeoutMs(value, fallback);
|
|
3013
3443
|
}
|
|
3014
3444
|
/** Runs startup work with abort and timeout handling plus optional cleanup. */
|
|
3015
3445
|
async function withCodexStartupTimeout(params) {
|
|
@@ -3075,7 +3505,326 @@ function resolveCodexTurnTerminalIdleTimeoutMs(value, runTimeoutOverrideMs) {
|
|
|
3075
3505
|
/** Adds gateway grace time to a caller timeout without overflowing invalid values. */
|
|
3076
3506
|
function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
|
|
3077
3507
|
const timeout = resolvePositiveIntegerTimeoutMs(timeoutMs, 1);
|
|
3078
|
-
|
|
3508
|
+
const grace = resolveTimerTimeoutMs(graceMs, 0, 0);
|
|
3509
|
+
return addTimerTimeoutGraceMs(timeout, grace) ?? timeout;
|
|
3510
|
+
}
|
|
3511
|
+
//#endregion
|
|
3512
|
+
//#region extensions/codex/src/app-server/desktop-generation-fingerprint.ts
|
|
3513
|
+
const MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES = 4096;
|
|
3514
|
+
/** Fingerprints every desktop candidate that can own a managed fallback artifact. */
|
|
3515
|
+
async function readMacOSDesktopGenerationFingerprint(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
|
|
3516
|
+
const entries = [];
|
|
3517
|
+
for (const candidate of candidates) {
|
|
3518
|
+
const command = await statFingerprint(candidate.appServerCommandPath);
|
|
3519
|
+
entries.push(`candidate:${candidate.appName}:${candidate.appServerCommandPath}:${command}`);
|
|
3520
|
+
for (const artifactPath of resolveMacOSDesktopGenerationPaths(candidate)) entries.push(`${artifactPath}\0${await statFingerprint(artifactPath)}`);
|
|
3521
|
+
const pluginRoot = resolveComputerUsePluginRoot(candidate);
|
|
3522
|
+
entries.push(`${pluginRoot}\0${await directoryTreeFingerprint(pluginRoot)}`);
|
|
3523
|
+
}
|
|
3524
|
+
return createHash("sha256").update(entries.join("\0")).digest("hex");
|
|
3525
|
+
}
|
|
3526
|
+
function resolveMacOSDesktopGenerationPaths(candidate) {
|
|
3527
|
+
return [
|
|
3528
|
+
candidate.appBundlePath,
|
|
3529
|
+
path.join(candidate.bundledMarketplacePath, ".agents", "plugins", "marketplace.json"),
|
|
3530
|
+
...candidate.computerUseServiceAppPaths.flatMap((servicePath) => [
|
|
3531
|
+
servicePath,
|
|
3532
|
+
path.join(servicePath, "Contents", "Info.plist"),
|
|
3533
|
+
path.join(servicePath, "Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient")
|
|
3534
|
+
])
|
|
3535
|
+
];
|
|
3536
|
+
}
|
|
3537
|
+
function resolveComputerUsePluginRoot(candidate) {
|
|
3538
|
+
return path.join(candidate.bundledMarketplacePath, "plugins", "computer-use");
|
|
3539
|
+
}
|
|
3540
|
+
/** Stable roots that cover bundle replacement and recursive artifact updates. */
|
|
3541
|
+
function resolveMacOSDesktopGenerationWatchPaths(candidates = resolveMacOSDesktopCodexAppPathCandidates("darwin")) {
|
|
3542
|
+
const watched = /* @__PURE__ */ new Set(["/Applications"]);
|
|
3543
|
+
for (const candidate of candidates) watched.add(candidate.appBundlePath);
|
|
3544
|
+
return [...watched];
|
|
3545
|
+
}
|
|
3546
|
+
async function directoryTreeFingerprint(root) {
|
|
3547
|
+
let rootStat;
|
|
3548
|
+
try {
|
|
3549
|
+
rootStat = await fs.lstat(root, { bigint: true });
|
|
3550
|
+
} catch (error) {
|
|
3551
|
+
if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
|
|
3552
|
+
throw error;
|
|
3553
|
+
}
|
|
3554
|
+
if (!rootStat.isDirectory()) return statFingerprint(root);
|
|
3555
|
+
let entryCount = 0;
|
|
3556
|
+
const hash = createHash("sha256");
|
|
3557
|
+
hash.update("openclaw-codex-computer-use-plugin-tree-v1\0");
|
|
3558
|
+
const visit = async (directory, relativeDirectory, before) => {
|
|
3559
|
+
hash.update(`directory\0${relativeDirectory}\0${statTuple(before)}\0`);
|
|
3560
|
+
const entries = (await fs.readdir(directory, { withFileTypes: true })).toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
3561
|
+
for (const entry of entries) {
|
|
3562
|
+
entryCount += 1;
|
|
3563
|
+
if (entryCount > MAX_COMPUTER_USE_PLUGIN_TREE_ENTRIES) throw new Error("Codex Computer Use plugin exceeds the bounded tree size");
|
|
3564
|
+
const entryPath = path.join(directory, entry.name);
|
|
3565
|
+
const relativePath = path.join(relativeDirectory, entry.name);
|
|
3566
|
+
const entryStat = await fs.lstat(entryPath, { bigint: true });
|
|
3567
|
+
if (entryStat.isDirectory()) await visit(entryPath, relativePath, entryStat);
|
|
3568
|
+
else hash.update(`entry\0${relativePath}\0${await statFingerprint(entryPath)}\0`);
|
|
3569
|
+
}
|
|
3570
|
+
if (!sameStat(before, await fs.lstat(directory, { bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${directory}`);
|
|
3571
|
+
};
|
|
3572
|
+
await visit(root, ".", rootStat);
|
|
3573
|
+
return hash.digest("hex");
|
|
3574
|
+
}
|
|
3575
|
+
async function statFingerprint(filePath) {
|
|
3576
|
+
try {
|
|
3577
|
+
const entry = await fs.lstat(filePath, { bigint: true });
|
|
3578
|
+
const type = entry.isSymbolicLink() ? "link" : entry.isDirectory() ? "directory" : entry.isFile() ? "file" : "other";
|
|
3579
|
+
const own = statTuple(entry);
|
|
3580
|
+
if (!entry.isSymbolicLink()) return `${type}:${own}:${entry.isFile() ? await readFileFingerprint(filePath, entry, false) : ""}`;
|
|
3581
|
+
const [link, realPath, target] = await Promise.all([
|
|
3582
|
+
fs.readlink(filePath),
|
|
3583
|
+
fs.realpath(filePath),
|
|
3584
|
+
fs.stat(filePath, { bigint: true })
|
|
3585
|
+
]);
|
|
3586
|
+
const content = target.isFile() ? await readFileFingerprint(filePath, target, true) : "";
|
|
3587
|
+
return `${type}:${own}:${link}:${realPath}:${statTuple(target)}:${content}`;
|
|
3588
|
+
} catch (error) {
|
|
3589
|
+
if (isNodeError(error, "ENOENT") || isNodeError(error, "ENOTDIR")) return "missing";
|
|
3590
|
+
throw error;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
async function readFileFingerprint(filePath, expected, followsSymlink) {
|
|
3594
|
+
const noFollow = followsSymlink ? 0 : constants.O_NOFOLLOW ?? 0;
|
|
3595
|
+
const handle = await fs.open(filePath, constants.O_RDONLY | noFollow);
|
|
3596
|
+
try {
|
|
3597
|
+
const before = await handle.stat({ bigint: true });
|
|
3598
|
+
if (!sameStat(before, expected)) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
|
|
3599
|
+
const hash = createHash("sha256");
|
|
3600
|
+
for await (const chunk of handle.createReadStream({ autoClose: false })) hash.update(chunk);
|
|
3601
|
+
if (!sameStat(before, await handle.stat({ bigint: true }))) throw new Error(`Codex desktop artifact changed while fingerprinting: ${filePath}`);
|
|
3602
|
+
return hash.digest("hex");
|
|
3603
|
+
} finally {
|
|
3604
|
+
await handle.close();
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
function sameStat(left, right) {
|
|
3608
|
+
return statTuple(left) === statTuple(right);
|
|
3609
|
+
}
|
|
3610
|
+
function statTuple(stat) {
|
|
3611
|
+
return [
|
|
3612
|
+
stat.dev,
|
|
3613
|
+
stat.ino,
|
|
3614
|
+
stat.mode,
|
|
3615
|
+
stat.size,
|
|
3616
|
+
stat.mtimeNs,
|
|
3617
|
+
stat.ctimeNs
|
|
3618
|
+
].join(":");
|
|
3619
|
+
}
|
|
3620
|
+
function isNodeError(error, code) {
|
|
3621
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
3622
|
+
}
|
|
3623
|
+
//#endregion
|
|
3624
|
+
//#region extensions/codex/src/app-server/desktop-generation-owner.ts
|
|
3625
|
+
const SETTLE_DELAY_MS = 1e3;
|
|
3626
|
+
/** Coalesces filesystem invalidations into one stable desktop generation. */
|
|
3627
|
+
function createCodexDesktopGenerationOwner(params) {
|
|
3628
|
+
let generation = params.initialGeneration;
|
|
3629
|
+
let invalidation = 0;
|
|
3630
|
+
let dirty = false;
|
|
3631
|
+
let refresh;
|
|
3632
|
+
let stopped = false;
|
|
3633
|
+
const markDirty = () => {
|
|
3634
|
+
invalidation += 1;
|
|
3635
|
+
dirty = true;
|
|
3636
|
+
};
|
|
3637
|
+
const reconcile = () => {
|
|
3638
|
+
if (refresh) return refresh;
|
|
3639
|
+
refresh = (async () => {
|
|
3640
|
+
for (;;) {
|
|
3641
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3642
|
+
const observedInvalidation = invalidation;
|
|
3643
|
+
const first = await params.readFingerprint();
|
|
3644
|
+
await delay(SETTLE_DELAY_MS);
|
|
3645
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3646
|
+
const second = await params.readFingerprint();
|
|
3647
|
+
if (stopped) throw new Error("Codex desktop generation owner stopped");
|
|
3648
|
+
if (observedInvalidation !== invalidation || first !== second) continue;
|
|
3649
|
+
const previous = generation;
|
|
3650
|
+
generation = previous?.fingerprint === second ? previous : {
|
|
3651
|
+
epoch: (previous?.epoch ?? 0) + 1,
|
|
3652
|
+
fingerprint: second
|
|
3653
|
+
};
|
|
3654
|
+
dirty = false;
|
|
3655
|
+
if (previous && generation !== previous) params.onGenerationChange?.(generation);
|
|
3656
|
+
return generation;
|
|
3657
|
+
}
|
|
3658
|
+
})().finally(() => {
|
|
3659
|
+
refresh = void 0;
|
|
3660
|
+
});
|
|
3661
|
+
return refresh;
|
|
3662
|
+
};
|
|
3663
|
+
return {
|
|
3664
|
+
read: () => generation,
|
|
3665
|
+
markDirty,
|
|
3666
|
+
wait: () => dirty ? reconcile() : Promise.resolve(generation),
|
|
3667
|
+
refresh: () => {
|
|
3668
|
+
markDirty();
|
|
3669
|
+
return reconcile();
|
|
3670
|
+
},
|
|
3671
|
+
isCurrent: (candidate) => Boolean(candidate && !dirty && generation && candidate.epoch === generation.epoch && candidate.fingerprint === generation.fingerprint),
|
|
3672
|
+
stop: () => {
|
|
3673
|
+
stopped = true;
|
|
3674
|
+
}
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
function delay(ms) {
|
|
3678
|
+
return new Promise((resolve) => {
|
|
3679
|
+
setTimeout(resolve, ms);
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
//#endregion
|
|
3683
|
+
//#region extensions/codex/src/app-server/desktop-generation.ts
|
|
3684
|
+
/** Lifecycle-owned generation for managed macOS Codex desktop artifacts. */
|
|
3685
|
+
const APPLICATIONS_PATH = "/Applications";
|
|
3686
|
+
const REARM_INITIAL_DELAY_MS = 100;
|
|
3687
|
+
const REARM_MAX_DELAY_MS = 3e4;
|
|
3688
|
+
const DESKTOP_GENERATION_STATE = Symbol.for("openclaw.codexDesktopGenerationState");
|
|
3689
|
+
function state() {
|
|
3690
|
+
const globalState = globalThis;
|
|
3691
|
+
return globalState[DESKTOP_GENERATION_STATE] ??= {};
|
|
3692
|
+
}
|
|
3693
|
+
function waitForCodexDesktopGeneration() {
|
|
3694
|
+
return state().owner?.wait() ?? Promise.resolve(void 0);
|
|
3695
|
+
}
|
|
3696
|
+
function isCodexDesktopGenerationCurrent(generation) {
|
|
3697
|
+
return state().owner?.isCurrent(generation) ?? false;
|
|
3698
|
+
}
|
|
3699
|
+
function createCodexDesktopGenerationService(params, runtime = {
|
|
3700
|
+
platform: process.platform,
|
|
3701
|
+
readFingerprint: readMacOSDesktopGenerationFingerprint,
|
|
3702
|
+
resolveWatchPaths: resolveMacOSDesktopGenerationWatchPaths,
|
|
3703
|
+
pathExists: existsSync,
|
|
3704
|
+
watchPath: (watchedPath, options, listener) => watch(watchedPath, options, listener)
|
|
3705
|
+
}) {
|
|
3706
|
+
return {
|
|
3707
|
+
id: "codex-desktop-generation",
|
|
3708
|
+
async start(ctx) {
|
|
3709
|
+
if (runtime.platform !== "darwin") return;
|
|
3710
|
+
const current = state();
|
|
3711
|
+
current.context = ctx;
|
|
3712
|
+
current.readFingerprint = runtime.readFingerprint;
|
|
3713
|
+
current.resolveWatchPaths = runtime.resolveWatchPaths;
|
|
3714
|
+
current.pathExists = runtime.pathExists;
|
|
3715
|
+
current.watchPath = runtime.watchPath;
|
|
3716
|
+
current.owner = createCodexDesktopGenerationOwner({
|
|
3717
|
+
readFingerprint: current.readFingerprint,
|
|
3718
|
+
onGenerationChange: params.onGenerationChange,
|
|
3719
|
+
initialGeneration: current.lastGeneration
|
|
3720
|
+
});
|
|
3721
|
+
armWatchers(current);
|
|
3722
|
+
refreshGeneration(current, current.owner, current.owner.refresh());
|
|
3723
|
+
},
|
|
3724
|
+
async stop() {
|
|
3725
|
+
const current = state();
|
|
3726
|
+
current.lastGeneration = current.owner?.read() ?? current.lastGeneration;
|
|
3727
|
+
current.owner?.stop();
|
|
3728
|
+
current.owner = void 0;
|
|
3729
|
+
current.armEpoch = (current.armEpoch ?? 0) + 1;
|
|
3730
|
+
current.context = void 0;
|
|
3731
|
+
current.readFingerprint = void 0;
|
|
3732
|
+
current.resolveWatchPaths = void 0;
|
|
3733
|
+
current.pathExists = void 0;
|
|
3734
|
+
current.watchPath = void 0;
|
|
3735
|
+
current.watchHealthy = void 0;
|
|
3736
|
+
current.rearmDelayMs = void 0;
|
|
3737
|
+
if (current.rearmTimer) {
|
|
3738
|
+
clearTimeout(current.rearmTimer);
|
|
3739
|
+
current.rearmTimer = void 0;
|
|
3740
|
+
}
|
|
3741
|
+
closeWatchers(current);
|
|
3742
|
+
}
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
function armWatchers(current) {
|
|
3746
|
+
const owner = current.owner;
|
|
3747
|
+
if (!owner || current.watchers) return false;
|
|
3748
|
+
const armEpoch = (current.armEpoch ?? 0) + 1;
|
|
3749
|
+
current.armEpoch = armEpoch;
|
|
3750
|
+
const watchers = /* @__PURE__ */ new Set();
|
|
3751
|
+
current.watchers = watchers;
|
|
3752
|
+
const candidateNames = new Set(resolveMacOSDesktopCodexAppPathCandidates("darwin").map((candidate) => candidate.appName));
|
|
3753
|
+
let complete = true;
|
|
3754
|
+
for (const watchedPath of current.resolveWatchPaths?.() ?? []) {
|
|
3755
|
+
if (!current.pathExists?.(watchedPath)) continue;
|
|
3756
|
+
try {
|
|
3757
|
+
const watcher = current.watchPath?.(watchedPath, { recursive: watchedPath !== APPLICATIONS_PATH }, (_eventType, filename) => {
|
|
3758
|
+
if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
|
|
3759
|
+
if (watchedPath === APPLICATIONS_PATH && filename && !candidateNames.has(filename.toString().split(path.sep)[0] ?? "")) return;
|
|
3760
|
+
owner.markDirty();
|
|
3761
|
+
scheduleRearm(current, owner);
|
|
3762
|
+
});
|
|
3763
|
+
if (!watcher) {
|
|
3764
|
+
complete = false;
|
|
3765
|
+
reportWatcherFailure(current, owner, /* @__PURE__ */ new Error(`Could not watch ${watchedPath}`));
|
|
3766
|
+
scheduleRearm(current, owner);
|
|
3767
|
+
continue;
|
|
3768
|
+
}
|
|
3769
|
+
watchers.add(watcher);
|
|
3770
|
+
watcher.on("error", (error) => {
|
|
3771
|
+
if (!isCurrentArm(current, owner, watchers, armEpoch)) return;
|
|
3772
|
+
reportWatcherFailure(current, owner, error);
|
|
3773
|
+
scheduleRearm(current, owner);
|
|
3774
|
+
});
|
|
3775
|
+
} catch (error) {
|
|
3776
|
+
complete = false;
|
|
3777
|
+
reportWatcherFailure(current, owner, error);
|
|
3778
|
+
scheduleRearm(current, owner);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
current.watchHealthy = complete;
|
|
3782
|
+
if (complete) current.rearmDelayMs = REARM_INITIAL_DELAY_MS;
|
|
3783
|
+
return complete;
|
|
3784
|
+
}
|
|
3785
|
+
function reportWatcherFailure(current, owner, error) {
|
|
3786
|
+
if (current.watchHealthy === false) return;
|
|
3787
|
+
current.watchHealthy = false;
|
|
3788
|
+
owner.markDirty();
|
|
3789
|
+
current.context?.serviceHealth?.reportFailure(error);
|
|
3790
|
+
current.context?.logger.warn(`codex desktop generation watcher failed: ${String(error)}`);
|
|
3791
|
+
}
|
|
3792
|
+
function isCurrentArm(current, owner, watchers, armEpoch) {
|
|
3793
|
+
return current.owner === owner && current.watchers === watchers && current.armEpoch === armEpoch;
|
|
3794
|
+
}
|
|
3795
|
+
function scheduleRearm(current, owner) {
|
|
3796
|
+
if (current.rearmTimer) {
|
|
3797
|
+
if (current.watchHealthy === false) return;
|
|
3798
|
+
clearTimeout(current.rearmTimer);
|
|
3799
|
+
}
|
|
3800
|
+
const delayMs = current.watchHealthy === false ? current.rearmDelayMs ?? REARM_INITIAL_DELAY_MS : REARM_INITIAL_DELAY_MS;
|
|
3801
|
+
if (current.watchHealthy === false) current.rearmDelayMs = Math.min(delayMs * 2, REARM_MAX_DELAY_MS);
|
|
3802
|
+
current.rearmTimer = setTimeout(() => {
|
|
3803
|
+
current.rearmTimer = void 0;
|
|
3804
|
+
if (current.owner !== owner) return;
|
|
3805
|
+
const wasUnhealthy = current.watchHealthy === false;
|
|
3806
|
+
closeWatchers(current);
|
|
3807
|
+
if (!armWatchers(current) || wasUnhealthy) owner.markDirty();
|
|
3808
|
+
refreshGeneration(current, owner, owner.wait());
|
|
3809
|
+
}, delayMs);
|
|
3810
|
+
current.rearmTimer.unref();
|
|
3811
|
+
}
|
|
3812
|
+
function logRefreshFailure(current, owner) {
|
|
3813
|
+
return (error) => {
|
|
3814
|
+
if (current.owner !== owner) return;
|
|
3815
|
+
current.context?.serviceHealth?.reportFailure(error);
|
|
3816
|
+
current.context?.logger.warn(`codex desktop generation refresh failed: ${String(error)}`);
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3819
|
+
function refreshGeneration(current, owner, refresh) {
|
|
3820
|
+
refresh.then(() => {
|
|
3821
|
+
if (current.owner === owner && current.watchHealthy) current.context?.serviceHealth?.clearFailure();
|
|
3822
|
+
}).catch(logRefreshFailure(current, owner));
|
|
3823
|
+
}
|
|
3824
|
+
function closeWatchers(current) {
|
|
3825
|
+
const watchers = current.watchers;
|
|
3826
|
+
current.watchers = void 0;
|
|
3827
|
+
for (const watcher of watchers ?? []) watcher.close();
|
|
3079
3828
|
}
|
|
3080
3829
|
//#endregion
|
|
3081
3830
|
//#region extensions/codex/src/app-server/native-config-fence.ts
|
|
@@ -3140,37 +3889,31 @@ async function waitForPreviousFence(previous, options) {
|
|
|
3140
3889
|
});
|
|
3141
3890
|
}
|
|
3142
3891
|
//#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?.();
|
|
3892
|
+
//#region extensions/codex/src/app-server/thread-lifecycle-errors.ts
|
|
3893
|
+
var CodexThreadStartRequestError = class extends Error {
|
|
3894
|
+
constructor(cause) {
|
|
3895
|
+
super(`thread/start: ${formatErrorMessage(cause)}`, { cause });
|
|
3896
|
+
this.name = "CodexThreadStartRequestError";
|
|
3172
3897
|
}
|
|
3173
|
-
}
|
|
3898
|
+
};
|
|
3899
|
+
var CodexThreadBindingConflictError = class extends Error {
|
|
3900
|
+
constructor(threadId, operation) {
|
|
3901
|
+
super(`Codex thread binding changed while ${operation}: ${threadId}`);
|
|
3902
|
+
this.name = "CodexThreadBindingConflictError";
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
var CodexRestrictedToolSurfaceAttestationError = class extends Error {
|
|
3906
|
+
constructor(cause) {
|
|
3907
|
+
super("Codex restricted-tool-surface MCP attestation failed", { cause });
|
|
3908
|
+
this.name = "CodexRestrictedToolSurfaceAttestationError";
|
|
3909
|
+
}
|
|
3910
|
+
};
|
|
3911
|
+
var CodexAdoptedThreadActiveError = class extends Error {
|
|
3912
|
+
constructor() {
|
|
3913
|
+
super("Codex session became active in another runner; wait for it to finish before continuing");
|
|
3914
|
+
this.name = "CodexAdoptedThreadActiveError";
|
|
3915
|
+
}
|
|
3916
|
+
};
|
|
3174
3917
|
//#endregion
|
|
3175
3918
|
//#region extensions/codex/src/app-server/shared-client.ts
|
|
3176
3919
|
/**
|
|
@@ -3179,6 +3922,8 @@ async function withAbortableTimeout(params) {
|
|
|
3179
3922
|
*/
|
|
3180
3923
|
var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
3181
3924
|
assertCodexAppServerClientStartSelectionCurrent: () => assertCodexAppServerClientStartSelectionCurrent,
|
|
3925
|
+
captureExclusiveSharedCodexAppServerClient: () => captureExclusiveSharedCodexAppServerClient,
|
|
3926
|
+
captureSharedCodexAppServerCatalogLifetime: () => captureSharedCodexAppServerCatalogLifetime,
|
|
3182
3927
|
clearSharedCodexAppServerClientAndWait: () => clearSharedCodexAppServerClientAndWait,
|
|
3183
3928
|
clearSharedCodexAppServerClientIfCurrent: () => clearSharedCodexAppServerClientIfCurrent,
|
|
3184
3929
|
clearSharedCodexAppServerClientIfCurrentAndUnclaimed: () => clearSharedCodexAppServerClientIfCurrentAndUnclaimed,
|
|
@@ -3187,6 +3932,9 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
|
3187
3932
|
getLeasedSharedCodexAppServerClient: () => getLeasedSharedCodexAppServerClient,
|
|
3188
3933
|
getSharedCodexAppServerClient: () => getSharedCodexAppServerClient,
|
|
3189
3934
|
isCodexAppServerStartSelectionChangedError: () => isCodexAppServerStartSelectionChangedError,
|
|
3935
|
+
readCodexAppServerClientDesktopGeneration: () => readCodexAppServerClientDesktopGeneration,
|
|
3936
|
+
readCodexAppServerClientDesktopGenerationFingerprint: () => readCodexAppServerClientDesktopGenerationFingerprint,
|
|
3937
|
+
readCodexAppServerClientProcessIdentity: () => readCodexAppServerClientProcessIdentity,
|
|
3190
3938
|
releaseCodexAppServerClientLease: () => releaseCodexAppServerClientLease,
|
|
3191
3939
|
releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
|
|
3192
3940
|
resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
|
|
@@ -3194,6 +3942,8 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
|
3194
3942
|
retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
|
|
3195
3943
|
retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
|
|
3196
3944
|
retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
|
|
3945
|
+
retireSharedCodexAppServerClientsBeforeDesktopGeneration: () => retireSharedCodexAppServerClientsBeforeDesktopGeneration,
|
|
3946
|
+
waitForCodexAppServerClientDesktopGenerationDrain: () => waitForCodexAppServerClientDesktopGenerationDrain,
|
|
3197
3947
|
withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
|
|
3198
3948
|
});
|
|
3199
3949
|
const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
|
|
@@ -3205,8 +3955,10 @@ function getSharedCodexAppServerClientState() {
|
|
|
3205
3955
|
globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
|
|
3206
3956
|
clients: /* @__PURE__ */ new Map(),
|
|
3207
3957
|
liveClients: /* @__PURE__ */ new Set(),
|
|
3958
|
+
isolatedClients: /* @__PURE__ */ new Set(),
|
|
3208
3959
|
entriesByClient: /* @__PURE__ */ new WeakMap(),
|
|
3209
|
-
leasedReleases: /* @__PURE__ */ new WeakMap()
|
|
3960
|
+
leasedReleases: /* @__PURE__ */ new WeakMap(),
|
|
3961
|
+
desktopGenerationDrainChecks: /* @__PURE__ */ new Set()
|
|
3210
3962
|
};
|
|
3211
3963
|
return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
|
|
3212
3964
|
}
|
|
@@ -3215,6 +3967,41 @@ function getCodexAppServerClientStartMetadata() {
|
|
|
3215
3967
|
globalState[CODEX_APP_SERVER_CLIENT_START_METADATA] ??= /* @__PURE__ */ new WeakMap();
|
|
3216
3968
|
return globalState[CODEX_APP_SERVER_CLIENT_START_METADATA];
|
|
3217
3969
|
}
|
|
3970
|
+
/** Reads the exact successful spawn selection plus its initialized runtime identity. */
|
|
3971
|
+
function readCodexAppServerClientProcessIdentity(client) {
|
|
3972
|
+
const metadata = getCodexAppServerClientStartMetadata().get(client);
|
|
3973
|
+
if (!metadata) return;
|
|
3974
|
+
const runtimeIdentity = client.getRuntimeIdentity();
|
|
3975
|
+
return {
|
|
3976
|
+
clientId: client.getInstanceId(),
|
|
3977
|
+
...resolveCodexAppServerSpawnIdentity(metadata.startOptions, metadata.nativeCommand),
|
|
3978
|
+
...runtimeIdentity?.serverVersion ? { serverVersion: runtimeIdentity.serverVersion } : {},
|
|
3979
|
+
...runtimeIdentity?.userAgent ? { userAgent: runtimeIdentity.userAgent } : {}
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
/** Returns the lifecycle fingerprint that owns a managed desktop client. */
|
|
3983
|
+
function readCodexAppServerClientDesktopGenerationFingerprint(client) {
|
|
3984
|
+
return readCodexAppServerClientDesktopGeneration(client)?.fingerprint;
|
|
3985
|
+
}
|
|
3986
|
+
/** Returns the lifecycle generation that owns a managed desktop client. */
|
|
3987
|
+
function readCodexAppServerClientDesktopGeneration(client) {
|
|
3988
|
+
return getCodexAppServerClientStartMetadata().get(client)?.desktopGeneration;
|
|
3989
|
+
}
|
|
3990
|
+
/** Waits until older physical desktop clients for this client's Codex home exit. */
|
|
3991
|
+
async function waitForCodexAppServerClientDesktopGenerationDrain(params) {
|
|
3992
|
+
const metadata = getCodexAppServerClientStartMetadata().get(params.client);
|
|
3993
|
+
if (!metadata?.desktopGeneration) return;
|
|
3994
|
+
const drain = createOlderDesktopGenerationDrainWait({
|
|
3995
|
+
generation: metadata.desktopGeneration,
|
|
3996
|
+
startOptions: metadata.startOptions,
|
|
3997
|
+
agentDir: metadata.agentDir
|
|
3998
|
+
});
|
|
3999
|
+
try {
|
|
4000
|
+
await withCodexAppServerAcquireDeadline(params.timeoutMs ?? 0, drain.promise, params.signal, "Codex Computer Use install timed out waiting for older desktop clients");
|
|
4001
|
+
} finally {
|
|
4002
|
+
drain.cancel();
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
3218
4005
|
/** Resolves non-secret spawn identity before startup; argv is represented only by its hash. */
|
|
3219
4006
|
function resolveCodexAppServerSpawnIdentity(startOptions, resolvedNativeCommand) {
|
|
3220
4007
|
const nativeCommand = resolvedNativeCommand ?? (startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0);
|
|
@@ -3245,6 +4032,7 @@ function isCodexAppServerStartSelectionChangedError(error) {
|
|
|
3245
4032
|
function assertCodexAppServerClientStartSelectionCurrent(params) {
|
|
3246
4033
|
const metadata = getCodexAppServerClientStartMetadata().get(params.client);
|
|
3247
4034
|
if (!metadata) return;
|
|
4035
|
+
if (metadata.desktopGeneration && !isCodexDesktopGenerationCurrent(metadata.desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
|
|
3248
4036
|
const requestedStartOptions = params.startOptions ?? metadata.requestedStartOptions;
|
|
3249
4037
|
if (requestedStartOptions.commandSource !== "managed") return;
|
|
3250
4038
|
const current = resolveCodexAppServerStartOptionsForAgent({
|
|
@@ -3256,7 +4044,7 @@ function assertCodexAppServerClientStartSelectionCurrent(params) {
|
|
|
3256
4044
|
/** Resolves the per-CODEX_HOME key used to serialize native config loading. */
|
|
3257
4045
|
function resolveCodexNativeConfigFenceKey(params) {
|
|
3258
4046
|
const metadata = params.client ? getCodexAppServerClientStartMetadata().get(params.client) : void 0;
|
|
3259
|
-
const startOptions =
|
|
4047
|
+
const startOptions = metadata?.startOptions ?? params.startOptions;
|
|
3260
4048
|
if (!startOptions || startOptions.transport !== "stdio") return;
|
|
3261
4049
|
const configuredHome = startOptions.env?.CODEX_HOME?.trim();
|
|
3262
4050
|
const agentDir = params.agentDir ?? metadata?.agentDir ?? resolveDefaultAgentDir(params.config ?? {});
|
|
@@ -3270,10 +4058,11 @@ function inferAuthRequirement(preparedAuth) {
|
|
|
3270
4058
|
async function resolveCodexAppServerClientStartContext(options) {
|
|
3271
4059
|
const agentDir = options?.agentDir ?? resolveDefaultAgentDir(options?.config ?? {});
|
|
3272
4060
|
const requestedStartOptions = options?.startOptions ?? resolveCodexAppServerRuntimeOptions().start;
|
|
4061
|
+
const desktopGeneration = shouldTrackDesktopGeneration(requestedStartOptions, options?.pluginConfig) ? await waitForCodexDesktopGeneration() : void 0;
|
|
3273
4062
|
const preparedAuth = options?.preparedAuth;
|
|
3274
4063
|
const preparedApiKey = preparedAuth?.kind === "api-key" ? preparedAuth.apiKey.trim() : void 0;
|
|
3275
4064
|
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.`);
|
|
4065
|
+
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
4066
|
if (preparedAuth?.kind === "api-key" && !preparedApiKey) throw new Error("Prepared Codex API-key auth is missing its resolved key.");
|
|
3278
4067
|
if (preparedAuth && requestedStartOptions.homeScope === "user") throw new Error("Prepared Codex auth requires an isolated app-server home.");
|
|
3279
4068
|
const preparedAuthRequirement = inferAuthRequirement(preparedAuth);
|
|
@@ -3281,11 +4070,11 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
3281
4070
|
const authRequirement = options?.authRequirement ?? preparedAuthRequirement;
|
|
3282
4071
|
const usesNativeAuth = !preparedAuth && (options?.authProfileId === null || requestedStartOptions.homeScope === "user");
|
|
3283
4072
|
const requestedAuthProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : options?.authProfileId ?? void 0;
|
|
3284
|
-
const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth &&
|
|
4073
|
+
const authProfileStore = preparedAuth?.kind === "profile" ? preparedAuth.store : !usesNativeAuth && preparedAuth?.kind !== "api-key" ? resolveCodexAppServerAuthProfileStore({
|
|
3285
4074
|
agentDir,
|
|
3286
4075
|
authProfileId: requestedAuthProfileId,
|
|
3287
|
-
authProfileStore: options
|
|
3288
|
-
config: options
|
|
4076
|
+
authProfileStore: options?.authProfileStore,
|
|
4077
|
+
config: options?.config
|
|
3289
4078
|
}) : options?.authProfileStore;
|
|
3290
4079
|
const authProfileId = preparedAuth?.kind === "profile" ? preparedAuth.profileId : usesNativeAuth || preparedAuth?.kind === "api-key" ? void 0 : resolveCodexAppServerAuthProfileIdForAgent({
|
|
3291
4080
|
authProfileId: requestedAuthProfileId,
|
|
@@ -3293,20 +4082,26 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
3293
4082
|
config: options?.config,
|
|
3294
4083
|
...authProfileStore ? { authProfileStore } : {}
|
|
3295
4084
|
});
|
|
3296
|
-
const preparedAuthProfileSnapshot = preparedAuth?.kind === "profile" ? preparedAuth.snapshot ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
|
|
4085
|
+
const preparedAuthProfileSnapshot = !usesNativeAuth && authProfileId ? (preparedAuth?.kind === "profile" ? preparedAuth.snapshot : void 0) ?? await resolveCodexAppServerPreparedAuthProfileSnapshot({
|
|
3297
4086
|
authProfileId,
|
|
3298
4087
|
authProfileStore,
|
|
3299
4088
|
agentDir,
|
|
3300
4089
|
config: options?.config
|
|
3301
4090
|
}) : void 0;
|
|
3302
|
-
if (preparedAuth?.kind === "profile" && !preparedAuthProfileSnapshot) throw new Error(`Prepared Codex auth profile "${preparedAuth.profileId}" is unusable.`);
|
|
4091
|
+
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
4092
|
const resolvedPreparedAuth = preparedAuth?.kind === "api-key" ? {
|
|
3304
4093
|
kind: "api-key",
|
|
3305
4094
|
apiKey: preparedApiKey
|
|
3306
|
-
} :
|
|
3307
|
-
|
|
4095
|
+
} : preparedAuthProfileSnapshot && authProfileId && authProfileStore ? {
|
|
4096
|
+
kind: "profile",
|
|
4097
|
+
profileId: authProfileId,
|
|
4098
|
+
store: authProfileStore,
|
|
3308
4099
|
snapshot: preparedAuthProfileSnapshot
|
|
3309
4100
|
} : void 0;
|
|
4101
|
+
const agentStartOptions = resolveCodexAppServerStartOptionsForAgent({
|
|
4102
|
+
startOptions: requestedStartOptions,
|
|
4103
|
+
agentDir
|
|
4104
|
+
});
|
|
3310
4105
|
return {
|
|
3311
4106
|
agentDir,
|
|
3312
4107
|
usesNativeAuth,
|
|
@@ -3316,58 +4111,24 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
3316
4111
|
preparedAuth: resolvedPreparedAuth,
|
|
3317
4112
|
authRequirement,
|
|
3318
4113
|
startOptions: await bridgeCodexAppServerStartOptions({
|
|
3319
|
-
startOptions: await resolveManagedCodexAppServerStartOptions(
|
|
3320
|
-
startOptions: requestedStartOptions,
|
|
3321
|
-
agentDir
|
|
3322
|
-
})),
|
|
4114
|
+
startOptions: await resolveManagedCodexAppServerStartOptions(agentStartOptions),
|
|
3323
4115
|
agentId: options?.agentId,
|
|
3324
4116
|
agentDir,
|
|
3325
4117
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3326
|
-
...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
|
|
4118
|
+
...preparedAuth && resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
|
|
3327
4119
|
authRequirement,
|
|
3328
4120
|
config: options?.config,
|
|
3329
4121
|
pluginConfig: options?.pluginConfig,
|
|
3330
4122
|
...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
|
-
])
|
|
4123
|
+
}),
|
|
4124
|
+
...options?.pluginConfig !== void 0 ? { pluginConfig: options.pluginConfig } : {},
|
|
4125
|
+
...desktopGeneration ? { desktopGeneration } : {}
|
|
3346
4126
|
};
|
|
3347
4127
|
}
|
|
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);
|
|
4128
|
+
function shouldTrackDesktopGeneration(startOptions, pluginConfig) {
|
|
4129
|
+
if (startOptions.transport !== "stdio") return false;
|
|
4130
|
+
if (resolveCodexComputerUseConfig({ pluginConfig }).enabled && (startOptions.commandSource === "managed" || startOptions.commandSource === "resolved-managed")) return true;
|
|
4131
|
+
return startOptions.commandSource === "managed" && (startOptions.managedCommandOrder ?? "package-first") === "desktop-first";
|
|
3371
4132
|
}
|
|
3372
4133
|
/** Gets or starts a shared Codex app-server client without retaining a lease. */
|
|
3373
4134
|
async function getSharedCodexAppServerClient(options) {
|
|
@@ -3399,7 +4160,7 @@ function releaseCodexAppServerClientLease(lease) {
|
|
|
3399
4160
|
lease.client = void 0;
|
|
3400
4161
|
return client ? releaseLeasedSharedCodexAppServerClient(client) : false;
|
|
3401
4162
|
}
|
|
3402
|
-
/** Retries one config-loading
|
|
4163
|
+
/** Retries one config-loading operation with a shared deadline and current client lease. */
|
|
3403
4164
|
async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
|
|
3404
4165
|
let client = params.lease.client;
|
|
3405
4166
|
if (!client) throw new Error("Codex app-server selection retry requires an active client lease");
|
|
@@ -3415,43 +4176,49 @@ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
|
|
|
3415
4176
|
...signal ? { signal } : {}
|
|
3416
4177
|
};
|
|
3417
4178
|
};
|
|
3418
|
-
for (let attempt = 0; attempt < 2; attempt += 1)
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
4179
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4180
|
+
const attemptClient = client;
|
|
4181
|
+
let scopeActive = true;
|
|
4182
|
+
const assertCurrent = () => {
|
|
4183
|
+
if (!scopeActive || params.lease.client !== attemptClient) throw new CodexAppServerStartupError("aborted", "Codex app-server request scope is closed");
|
|
4184
|
+
};
|
|
4185
|
+
try {
|
|
4186
|
+
requestOptions();
|
|
4187
|
+
return await params.run(client, () => {
|
|
4188
|
+
assertCurrent();
|
|
4189
|
+
return {
|
|
4190
|
+
...requestOptions(),
|
|
4191
|
+
assertCurrent
|
|
4192
|
+
};
|
|
4193
|
+
});
|
|
4194
|
+
} catch (error) {
|
|
4195
|
+
if (!isCodexAppServerStartSelectionChangedError(error) || attempt > 0) throw error;
|
|
4196
|
+
retireSharedCodexAppServerClientIfCurrent(client);
|
|
4197
|
+
params.lease.client = void 0;
|
|
4198
|
+
if (!releaseLeasedSharedCodexAppServerClient(client)) {
|
|
4199
|
+
client.close();
|
|
4200
|
+
throw new Error("Codex app-server selection retry requires a leased shared client", { cause: error });
|
|
4201
|
+
}
|
|
4202
|
+
const replacementOptions = requestOptions();
|
|
4203
|
+
client = await getLeasedSharedCodexAppServerClient({
|
|
4204
|
+
...params.options,
|
|
4205
|
+
timeoutMs: replacementOptions.timeoutMs,
|
|
4206
|
+
...signal ? { abandonSignal: signal } : {}
|
|
4207
|
+
});
|
|
4208
|
+
params.lease.client = client;
|
|
4209
|
+
params.onClientChange?.(client);
|
|
4210
|
+
} finally {
|
|
4211
|
+
scopeActive = false;
|
|
3427
4212
|
}
|
|
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
4213
|
}
|
|
3437
4214
|
throw new Error("Codex app-server selection retry loop exited unexpectedly");
|
|
3438
4215
|
}
|
|
3439
4216
|
async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
3440
4217
|
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
4218
|
const acquireStartedAt = Date.now();
|
|
3453
4219
|
const timeoutMs = options?.timeoutMs ?? 0;
|
|
3454
|
-
const
|
|
4220
|
+
const state = getSharedCodexAppServerClientState();
|
|
4221
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
3455
4222
|
const remainingTimeoutMs = resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
|
|
3456
4223
|
const authIdentityCacheKey = preparedAuth?.kind === "api-key" ? resolveCodexAppServerPreparedApiKeyCacheKey(preparedAuth.apiKey) : preparedAuth?.snapshot.secretFreeCacheKey ?? (authRequirement === "api-key" && !authProfileId ? resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions }) : void 0);
|
|
3457
4224
|
const baseKey = `${codexAppServerStartOptionsKey(startOptions, {
|
|
@@ -3459,12 +4226,18 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3459
4226
|
authBindingFingerprint: options?.authBindingFingerprint,
|
|
3460
4227
|
agentDir: usesNativeAuth ? void 0 : agentDir,
|
|
3461
4228
|
fallbackApiKeyCacheKey: authIdentityCacheKey
|
|
3462
|
-
})}\0auth-requirement:${authRequirement ?? "native"}`;
|
|
4229
|
+
})}\0auth-requirement:${authRequirement ?? "native"}${desktopGeneration ? `\0desktop-generation:${desktopGeneration.epoch}` : ""}`;
|
|
3463
4230
|
const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
|
|
3464
4231
|
const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
|
|
3465
4232
|
const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
|
|
3466
|
-
|
|
3467
|
-
|
|
4233
|
+
let entry = getOrCreateSharedClientEntry(state, key);
|
|
4234
|
+
const existingClient = entry.client;
|
|
4235
|
+
const existingGeneration = existingClient ? getCodexAppServerClientStartMetadata().get(existingClient)?.desktopGeneration : void 0;
|
|
4236
|
+
if (existingClient && existingGeneration && !isCodexDesktopGenerationCurrent(existingGeneration)) {
|
|
4237
|
+
retireSharedCodexAppServerClientIfCurrent(existingClient);
|
|
4238
|
+
entry = getOrCreateSharedClientEntry(state, key);
|
|
4239
|
+
}
|
|
4240
|
+
entry.startupAbort ??= new AbortController();
|
|
3468
4241
|
entry.closeWhenIdle = false;
|
|
3469
4242
|
const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
|
|
3470
4243
|
const startedCallback = options?.onStartedClient;
|
|
@@ -3491,6 +4264,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3491
4264
|
key,
|
|
3492
4265
|
requestedStartOptions,
|
|
3493
4266
|
startOptions,
|
|
4267
|
+
desktopGeneration,
|
|
4268
|
+
...pluginConfig !== void 0 ? { pluginConfig } : {},
|
|
3494
4269
|
agentDir,
|
|
3495
4270
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3496
4271
|
authProfileStore,
|
|
@@ -3498,7 +4273,8 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3498
4273
|
authRequirement,
|
|
3499
4274
|
runtimeArtifactMode,
|
|
3500
4275
|
...options?.expectedRuntimeArtifact ? { expectedRuntimeArtifact: options.expectedRuntimeArtifact } : {},
|
|
3501
|
-
runtimeArtifactSignal: entry.
|
|
4276
|
+
runtimeArtifactSignal: entry.startupAbort.signal,
|
|
4277
|
+
abandonSignal: entry.startupAbort.signal,
|
|
3502
4278
|
config: options?.config
|
|
3503
4279
|
}));
|
|
3504
4280
|
try {
|
|
@@ -3513,11 +4289,6 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
3513
4289
|
config: options?.config
|
|
3514
4290
|
});
|
|
3515
4291
|
const release = leaseOptions?.leased ? retainSharedClientEntry(entry) : void 0;
|
|
3516
|
-
if (warmIdentity) rememberWarmSharedCodexAppServerClient(state, warmIdentity, {
|
|
3517
|
-
key,
|
|
3518
|
-
entry,
|
|
3519
|
-
client
|
|
3520
|
-
});
|
|
3521
4292
|
return release ? {
|
|
3522
4293
|
client,
|
|
3523
4294
|
release
|
|
@@ -3557,6 +4328,8 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3557
4328
|
const ready = startInitializedCodexAppServerClient({
|
|
3558
4329
|
requestedStartOptions: params.requestedStartOptions,
|
|
3559
4330
|
startOptions: params.startOptions,
|
|
4331
|
+
...params.desktopGeneration ? { desktopGeneration: params.desktopGeneration } : {},
|
|
4332
|
+
...params.pluginConfig !== void 0 ? { pluginConfig: params.pluginConfig } : {},
|
|
3560
4333
|
agentDir: params.agentDir,
|
|
3561
4334
|
authProfileId: params.authProfileId,
|
|
3562
4335
|
authProfileStore: params.authProfileStore,
|
|
@@ -3565,13 +4338,17 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3565
4338
|
runtimeArtifactMode: params.runtimeArtifactMode,
|
|
3566
4339
|
...params.expectedRuntimeArtifact ? { expectedRuntimeArtifact: params.expectedRuntimeArtifact } : {},
|
|
3567
4340
|
runtimeArtifactSignal: params.runtimeArtifactSignal,
|
|
4341
|
+
abandonSignal: params.abandonSignal,
|
|
3568
4342
|
config: params.config,
|
|
3569
4343
|
onStartedClient: (startedClient) => {
|
|
3570
4344
|
const state = getSharedCodexAppServerClientState();
|
|
3571
4345
|
params.entry.client = startedClient;
|
|
3572
4346
|
state.liveClients.add(startedClient);
|
|
4347
|
+
startedClient.addTransportExitHandler((exitedClient) => {
|
|
4348
|
+
state.liveClients.delete(exitedClient);
|
|
4349
|
+
notifyDesktopGenerationDrainChecks(state);
|
|
4350
|
+
});
|
|
3573
4351
|
startedClient.addCloseHandler((closedClient) => {
|
|
3574
|
-
state.liveClients.delete(closedClient);
|
|
3575
4352
|
if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
|
|
3576
4353
|
});
|
|
3577
4354
|
for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
|
|
@@ -3587,6 +4364,7 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
3587
4364
|
initialized.reject(error);
|
|
3588
4365
|
throw error;
|
|
3589
4366
|
});
|
|
4367
|
+
initialized.promise.catch(() => void 0);
|
|
3590
4368
|
ready.catch(() => void 0);
|
|
3591
4369
|
return {
|
|
3592
4370
|
initialized: initialized.promise,
|
|
@@ -3598,10 +4376,12 @@ async function createIsolatedCodexAppServerClient(options) {
|
|
|
3598
4376
|
if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
3599
4377
|
const acquireStartedAt = Date.now();
|
|
3600
4378
|
const timeoutMs = options?.timeoutMs ?? 0;
|
|
3601
|
-
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
4379
|
+
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions, desktopGeneration, pluginConfig } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
3602
4380
|
return await startInitializedCodexAppServerClient({
|
|
3603
4381
|
requestedStartOptions,
|
|
3604
4382
|
startOptions,
|
|
4383
|
+
...desktopGeneration ? { desktopGeneration } : {},
|
|
4384
|
+
...pluginConfig !== void 0 ? { pluginConfig } : {},
|
|
3605
4385
|
agentDir,
|
|
3606
4386
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
3607
4387
|
authProfileStore,
|
|
@@ -3613,7 +4393,18 @@ async function createIsolatedCodexAppServerClient(options) {
|
|
|
3613
4393
|
config: options?.config,
|
|
3614
4394
|
timeoutMs: resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
|
|
3615
4395
|
abandonSignal: options?.abandonSignal,
|
|
3616
|
-
onStartedClient:
|
|
4396
|
+
onStartedClient: (client) => {
|
|
4397
|
+
trackIsolatedCodexAppServerClient(client);
|
|
4398
|
+
options?.onStartedClient?.(client);
|
|
4399
|
+
}
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
function trackIsolatedCodexAppServerClient(client) {
|
|
4403
|
+
const state = getSharedCodexAppServerClientState();
|
|
4404
|
+
state.isolatedClients.add(client);
|
|
4405
|
+
client.addTransportExitHandler((exitedClient) => {
|
|
4406
|
+
state.isolatedClients.delete(exitedClient);
|
|
4407
|
+
notifyDesktopGenerationDrainChecks(state);
|
|
3617
4408
|
});
|
|
3618
4409
|
}
|
|
3619
4410
|
async function startInitializedCodexAppServerClient(params) {
|
|
@@ -3621,7 +4412,41 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3621
4412
|
const timeoutMs = params.timeoutMs ?? 0;
|
|
3622
4413
|
const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
|
|
3623
4414
|
for (const [index, startOptions] of startOptionsCandidates.entries()) {
|
|
3624
|
-
const
|
|
4415
|
+
const desktopGeneration = params.desktopGeneration ?? (isManagedCodexDesktopCommand(startOptions.command) ? await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), waitForCodexDesktopGeneration(), params.abandonSignal) : void 0);
|
|
4416
|
+
const assertDesktopGenerationCurrent = () => {
|
|
4417
|
+
if (params.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
4418
|
+
if (desktopGeneration && !isCodexDesktopGenerationCurrent(desktopGeneration)) throw new CodexAppServerStartSelectionChangedError();
|
|
4419
|
+
};
|
|
4420
|
+
const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig: params.pluginConfig });
|
|
4421
|
+
const ownsIsolatedCodexHome = params.requestedStartOptions.homeScope !== "user" && !params.requestedStartOptions.env?.CODEX_HOME?.trim();
|
|
4422
|
+
const artifactDrain = desktopGeneration && ownsIsolatedCodexHome && computerUseConfig.enabled && (computerUseConfig.autoInstall || computerUseConfig.pluginCacheMode === "shared") ? createOlderDesktopGenerationDrainWait({
|
|
4423
|
+
generation: desktopGeneration,
|
|
4424
|
+
startOptions,
|
|
4425
|
+
agentDir: params.agentDir
|
|
4426
|
+
}) : void 0;
|
|
4427
|
+
try {
|
|
4428
|
+
if (artifactDrain) await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), artifactDrain.promise, params.abandonSignal);
|
|
4429
|
+
await reconcileCodexComputerUseStartArtifacts({
|
|
4430
|
+
startOptions,
|
|
4431
|
+
agentDir: params.agentDir,
|
|
4432
|
+
pluginConfig: params.pluginConfig,
|
|
4433
|
+
...desktopGeneration ? { desktopGeneration } : {},
|
|
4434
|
+
assertCurrent: assertDesktopGenerationCurrent,
|
|
4435
|
+
ownsIsolatedCodexHome
|
|
4436
|
+
});
|
|
4437
|
+
} catch (error) {
|
|
4438
|
+
if (isCodexComputerUseCandidateArtifactsUnavailableError(error)) {
|
|
4439
|
+
if (index + 1 < startOptionsCandidates.length) continue;
|
|
4440
|
+
throw new AgentHarnessPreflightError("Codex Computer Use artifacts are unavailable from the installed desktop apps.", {
|
|
4441
|
+
cause: error,
|
|
4442
|
+
scope: "harness"
|
|
4443
|
+
});
|
|
4444
|
+
}
|
|
4445
|
+
throw error;
|
|
4446
|
+
} finally {
|
|
4447
|
+
artifactDrain?.cancel();
|
|
4448
|
+
}
|
|
4449
|
+
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-D9pYp4zL.js") : void 0;
|
|
3625
4450
|
const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
3626
4451
|
const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
|
|
3627
4452
|
startOptions,
|
|
@@ -3632,7 +4457,26 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3632
4457
|
if (index + 1 < startOptionsCandidates.length) continue;
|
|
3633
4458
|
throw new Error("Codex app-server runtime artifact does not match verified inference");
|
|
3634
4459
|
}
|
|
3635
|
-
|
|
4460
|
+
assertDesktopGenerationCurrent();
|
|
4461
|
+
let starting;
|
|
4462
|
+
let client;
|
|
4463
|
+
try {
|
|
4464
|
+
client = await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), starting = CodexAppServerClient.start(startOptions, () => {
|
|
4465
|
+
assertDesktopGenerationCurrent();
|
|
4466
|
+
resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt);
|
|
4467
|
+
}), params.abandonSignal);
|
|
4468
|
+
} catch (error) {
|
|
4469
|
+
starting?.then((lateClient) => lateClient.close(), () => {});
|
|
4470
|
+
throw error;
|
|
4471
|
+
}
|
|
4472
|
+
const nativeCommandAtStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
4473
|
+
getCodexAppServerClientStartMetadata().set(client, {
|
|
4474
|
+
requestedStartOptions: params.requestedStartOptions,
|
|
4475
|
+
startOptions,
|
|
4476
|
+
agentDir: params.agentDir,
|
|
4477
|
+
...nativeCommandAtStart ? { nativeCommand: nativeCommandAtStart } : {},
|
|
4478
|
+
...desktopGeneration ? { desktopGeneration } : {}
|
|
4479
|
+
});
|
|
3636
4480
|
params.onStartedClient?.(client);
|
|
3637
4481
|
let initialize;
|
|
3638
4482
|
try {
|
|
@@ -3643,6 +4487,12 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3643
4487
|
if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) continue;
|
|
3644
4488
|
throw error;
|
|
3645
4489
|
}
|
|
4490
|
+
try {
|
|
4491
|
+
assertDesktopGenerationCurrent();
|
|
4492
|
+
} catch (error) {
|
|
4493
|
+
client.close();
|
|
4494
|
+
throw error;
|
|
4495
|
+
}
|
|
3646
4496
|
params.onInitializedClient?.();
|
|
3647
4497
|
let runtimeArtifact;
|
|
3648
4498
|
try {
|
|
@@ -3666,9 +4516,11 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3666
4516
|
authProfileId: params.authProfileId ?? void 0,
|
|
3667
4517
|
authMode: params.preparedAuth?.kind === "api-key" ? "prepared-api-key" : "profile",
|
|
3668
4518
|
...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {},
|
|
3669
|
-
config: params.config
|
|
4519
|
+
config: params.config,
|
|
4520
|
+
onAuthRefreshFailure: () => retireSharedCodexAppServerClientIfCurrent(client)
|
|
3670
4521
|
});
|
|
3671
4522
|
try {
|
|
4523
|
+
assertDesktopGenerationCurrent();
|
|
3672
4524
|
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), applyCodexAppServerAuthProfile({
|
|
3673
4525
|
client,
|
|
3674
4526
|
agentDir: params.agentDir,
|
|
@@ -3679,14 +4531,8 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3679
4531
|
config: params.config,
|
|
3680
4532
|
...params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}
|
|
3681
4533
|
}), params.abandonSignal);
|
|
3682
|
-
const nativeCommand = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
3683
4534
|
if (runtimeArtifactModule && runtimeArtifact) runtimeArtifactModule.bindCodexAppServerRuntimeArtifact(client, runtimeArtifact);
|
|
3684
|
-
|
|
3685
|
-
requestedStartOptions: params.requestedStartOptions,
|
|
3686
|
-
startOptions,
|
|
3687
|
-
agentDir: params.agentDir,
|
|
3688
|
-
...nativeCommand ? { nativeCommand } : {}
|
|
3689
|
-
});
|
|
4535
|
+
assertDesktopGenerationCurrent();
|
|
3690
4536
|
const fenceKey = resolveCodexNativeConfigFenceKey({ client });
|
|
3691
4537
|
if (fenceKey) client.setThreadSessionRequestGuard(async (options) => {
|
|
3692
4538
|
const release = await acquireCodexNativeConfigFence(fenceKey, options);
|
|
@@ -3706,6 +4552,9 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
3706
4552
|
}
|
|
3707
4553
|
throw new Error("Managed Codex app-server fallback candidates were exhausted.");
|
|
3708
4554
|
}
|
|
4555
|
+
function isCodexComputerUseCandidateArtifactsUnavailableError(error) {
|
|
4556
|
+
return error !== null && typeof error === "object" && "code" in error && error.code === "CODEX_COMPUTER_USE_CANDIDATE_ARTIFACTS_UNAVAILABLE";
|
|
4557
|
+
}
|
|
3709
4558
|
function resolveManagedFallbackStartOptions(startOptions) {
|
|
3710
4559
|
const commands = [startOptions.command, ...startOptions.managedFallbackCommandPaths ?? []];
|
|
3711
4560
|
const candidates = [];
|
|
@@ -3735,6 +4584,15 @@ function clearSharedCodexAppServerClientIfCurrent(client) {
|
|
|
3735
4584
|
}
|
|
3736
4585
|
return false;
|
|
3737
4586
|
}
|
|
4587
|
+
/** Captures a revocable observation of the exact shared client and native account/config. */
|
|
4588
|
+
function captureSharedCodexAppServerCatalogLifetime(client) {
|
|
4589
|
+
const state = getSharedCodexAppServerClientState();
|
|
4590
|
+
const entry = state.entriesByClient.get(client);
|
|
4591
|
+
const key = [...state.clients].find(([, candidate]) => candidate === entry)?.[0];
|
|
4592
|
+
const generation = readCodexAppServerClientDesktopGeneration(client);
|
|
4593
|
+
const revision = client.getModelCatalogRevision();
|
|
4594
|
+
return () => key !== void 0 && state.clients.get(key) === entry && entry?.client === client && !entry.closeWhenIdle && !entry.closeError && !client.getCloseError() && client.getModelCatalogRevision() === revision && (!generation || isCodexDesktopGenerationCurrent(generation));
|
|
4595
|
+
}
|
|
3738
4596
|
/** Retains the matching shared client and returns a release callback. */
|
|
3739
4597
|
function retainSharedCodexAppServerClientIfCurrent(client) {
|
|
3740
4598
|
if (!client) return;
|
|
@@ -3754,6 +4612,20 @@ function retainSharedCodexAppServerClientByInstanceId(clientId) {
|
|
|
3754
4612
|
};
|
|
3755
4613
|
}
|
|
3756
4614
|
}
|
|
4615
|
+
/** Captures sole physical-client ownership across awaited configuration adoption. */
|
|
4616
|
+
function captureExclusiveSharedCodexAppServerClient(client) {
|
|
4617
|
+
const state = getSharedCodexAppServerClientState();
|
|
4618
|
+
for (const [key, entry] of state.clients) {
|
|
4619
|
+
if (entry.client !== client) continue;
|
|
4620
|
+
const generation = entry.leaseGeneration;
|
|
4621
|
+
const assertExclusive = () => {
|
|
4622
|
+
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();
|
|
4623
|
+
};
|
|
4624
|
+
assertExclusive();
|
|
4625
|
+
return assertExclusive;
|
|
4626
|
+
}
|
|
4627
|
+
throw new CodexAdoptedThreadActiveError();
|
|
4628
|
+
}
|
|
3757
4629
|
/**
|
|
3758
4630
|
* Retires a matching shared client. Default is graceful: detach from the map
|
|
3759
4631
|
* (future acquisitions get a fresh client) and close once leases drain.
|
|
@@ -3797,6 +4669,62 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
|
|
|
3797
4669
|
};
|
|
3798
4670
|
}
|
|
3799
4671
|
}
|
|
4672
|
+
/** Gracefully retires exact clients attached to an older desktop generation. */
|
|
4673
|
+
function retireSharedCodexAppServerClientsBeforeDesktopGeneration(generation) {
|
|
4674
|
+
const state = getSharedCodexAppServerClientState();
|
|
4675
|
+
for (const entry of state.clients.values()) {
|
|
4676
|
+
const client = entry.client;
|
|
4677
|
+
const attached = client ? getCodexAppServerClientStartMetadata().get(client) : void 0;
|
|
4678
|
+
if (client && attached?.desktopGeneration && attached.desktopGeneration.epoch < generation.epoch) retireSharedCodexAppServerClientIfCurrent(client);
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
function createOlderDesktopGenerationDrainWait(params) {
|
|
4682
|
+
const targetHome = resolveCodexNativeConfigFenceKey({
|
|
4683
|
+
startOptions: params.startOptions,
|
|
4684
|
+
agentDir: params.agentDir
|
|
4685
|
+
});
|
|
4686
|
+
if (!targetHome) return {
|
|
4687
|
+
promise: Promise.resolve(),
|
|
4688
|
+
cancel: () => void 0
|
|
4689
|
+
};
|
|
4690
|
+
const state = getSharedCodexAppServerClientState();
|
|
4691
|
+
let settled = false;
|
|
4692
|
+
let resolveWait;
|
|
4693
|
+
const promise = new Promise((resolve) => {
|
|
4694
|
+
resolveWait = resolve;
|
|
4695
|
+
});
|
|
4696
|
+
const cancel = () => {
|
|
4697
|
+
if (settled) return;
|
|
4698
|
+
settled = true;
|
|
4699
|
+
state.desktopGenerationDrainChecks.delete(check);
|
|
4700
|
+
resolveWait();
|
|
4701
|
+
};
|
|
4702
|
+
const check = () => {
|
|
4703
|
+
if (!hasLiveOlderDesktopGenerationClient({
|
|
4704
|
+
state,
|
|
4705
|
+
generation: params.generation,
|
|
4706
|
+
targetHome
|
|
4707
|
+
})) cancel();
|
|
4708
|
+
};
|
|
4709
|
+
state.desktopGenerationDrainChecks.add(check);
|
|
4710
|
+
check();
|
|
4711
|
+
return {
|
|
4712
|
+
promise,
|
|
4713
|
+
cancel
|
|
4714
|
+
};
|
|
4715
|
+
}
|
|
4716
|
+
function hasLiveOlderDesktopGenerationClient(params) {
|
|
4717
|
+
for (const client of params.state.liveClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
|
|
4718
|
+
for (const client of params.state.isolatedClients) if (isOlderDesktopGenerationClientForHome(client, params.generation, params.targetHome)) return true;
|
|
4719
|
+
return false;
|
|
4720
|
+
}
|
|
4721
|
+
function isOlderDesktopGenerationClientForHome(client, generation, targetHome) {
|
|
4722
|
+
const metadata = getCodexAppServerClientStartMetadata().get(client);
|
|
4723
|
+
return Boolean(metadata?.desktopGeneration && metadata.desktopGeneration.epoch < generation.epoch && resolveCodexNativeConfigFenceKey({ client }) === targetHome);
|
|
4724
|
+
}
|
|
4725
|
+
function notifyDesktopGenerationDrainChecks(state) {
|
|
4726
|
+
for (const check of state.desktopGenerationDrainChecks) check();
|
|
4727
|
+
}
|
|
3800
4728
|
/** Clears a matching shared client and waits for its process to exit. */
|
|
3801
4729
|
async function clearSharedCodexAppServerClientIfCurrentAndWait(client, options) {
|
|
3802
4730
|
if (!client) return false;
|
|
@@ -3813,7 +4741,6 @@ async function clearSharedCodexAppServerClientAndWait(options) {
|
|
|
3813
4741
|
const state = getSharedCodexAppServerClientState();
|
|
3814
4742
|
const clients = collectSharedClients(state);
|
|
3815
4743
|
state.clients.clear();
|
|
3816
|
-
state.warmClientsByConfig = /* @__PURE__ */ new WeakMap();
|
|
3817
4744
|
await Promise.all(clients.map((client) => client.closeAndWait(options)));
|
|
3818
4745
|
}
|
|
3819
4746
|
globalThis[SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER] = clearSharedCodexAppServerClientAndWait;
|
|
@@ -3823,6 +4750,7 @@ function getOrCreateSharedClientEntry(state, key) {
|
|
|
3823
4750
|
entry = {
|
|
3824
4751
|
activeLeases: 0,
|
|
3825
4752
|
pendingAcquires: 0,
|
|
4753
|
+
leaseGeneration: 0,
|
|
3826
4754
|
closeWhenIdle: false,
|
|
3827
4755
|
onStartedClientCallbacks: /* @__PURE__ */ new Set()
|
|
3828
4756
|
};
|
|
@@ -3858,22 +4786,26 @@ function clearSharedCodexAppServerClientIfCurrentAndUnclaimed(client) {
|
|
|
3858
4786
|
}
|
|
3859
4787
|
function retainPendingSharedClientAcquire(entry) {
|
|
3860
4788
|
let released = false;
|
|
4789
|
+
entry.leaseGeneration += 1;
|
|
3861
4790
|
entry.pendingAcquires += 1;
|
|
3862
4791
|
return () => {
|
|
3863
4792
|
if (released) return;
|
|
3864
4793
|
released = true;
|
|
3865
4794
|
entry.pendingAcquires = Math.max(0, entry.pendingAcquires - 1);
|
|
3866
4795
|
closeRetiredSharedClientEntryIfIdle(entry);
|
|
4796
|
+
notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
|
|
3867
4797
|
};
|
|
3868
4798
|
}
|
|
3869
4799
|
function retainSharedClientEntry(entry) {
|
|
3870
4800
|
let released = false;
|
|
4801
|
+
entry.leaseGeneration += 1;
|
|
3871
4802
|
entry.activeLeases += 1;
|
|
3872
4803
|
return () => {
|
|
3873
4804
|
if (released) return;
|
|
3874
4805
|
released = true;
|
|
3875
4806
|
entry.activeLeases = Math.max(0, entry.activeLeases - 1);
|
|
3876
4807
|
closeRetiredSharedClientEntryIfIdle(entry);
|
|
4808
|
+
notifyDesktopGenerationDrainChecks(getSharedCodexAppServerClientState());
|
|
3877
4809
|
};
|
|
3878
4810
|
}
|
|
3879
4811
|
function closeRetiredSharedClientEntryIfIdle(entry) {
|
|
@@ -3901,7 +4833,7 @@ function closeSharedClientEntryIfUnclaimed(key, entry) {
|
|
|
3901
4833
|
}
|
|
3902
4834
|
function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
|
|
3903
4835
|
if (entry.activeLeases > 0 || entry.pendingAcquires > 0) return;
|
|
3904
|
-
entry.
|
|
4836
|
+
entry.startupAbort?.abort(/* @__PURE__ */ new Error("Codex app-server startup was abandoned"));
|
|
3905
4837
|
entry.closeWhenIdle = true;
|
|
3906
4838
|
const state = getSharedCodexAppServerClientState();
|
|
3907
4839
|
if (state.clients.get(key) === entry) state.clients.delete(key);
|
|
@@ -3912,4 +4844,4 @@ function collectSharedClients(state) {
|
|
|
3912
4844
|
return [...state.liveClients];
|
|
3913
4845
|
}
|
|
3914
4846
|
//#endregion
|
|
3915
|
-
export {
|
|
4847
|
+
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 };
|