@openclaw/codex 2026.7.2-beta.5 → 2026.8.1-beta.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/README.md +2 -0
- package/dist/{app-server-policy-BmDgseo_.js → app-server-policy-zcBDc1JU.js} +1 -1
- package/dist/attempt-client-cleanup-DMLXUfa8.js +735 -0
- package/dist/binding-connection-I5Bo-AsX.js +33 -0
- package/dist/{bounded-turn-FEbUXoYT.js → bounded-turn-qgZmzy_4.js} +71 -63
- package/dist/{capabilities-BHRSI6Cn.js → capabilities-CybGI1oz.js} +8 -2
- package/dist/cli-metadata.js +1 -1
- package/dist/{command-formatters-ico4ZFnk.js → command-formatters-B1AEz8ZA.js} +18 -18
- package/dist/{command-handlers-CRmnwI2N.js → command-handlers-Db8pu_ds.js} +484 -95
- package/dist/{compact-dEN49UH6.js → compact-LspQaGfl.js} +213 -199
- package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
- package/dist/doctor-contract-api.js +40 -43
- package/dist/{dynamic-tools-LIt9ojep.js → dynamic-tools-BVdyWkUD.js} +2984 -1221
- package/dist/effective-mcp-catalog-DIIhAtOZ.js +119 -0
- package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
- package/dist/{attempt-notifications-CMR2_fmR.js → event-projector-usage-e5huIQWu.js} +113 -29
- package/dist/harness.js +83 -13
- package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
- package/dist/index.js +703 -329
- package/dist/isolated-completion-C58NbL40.js +75 -0
- package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
- package/dist/media-understanding-provider.js +5 -5
- package/dist/{models-BA01NEez.js → models-BFlpn1Ex.js} +12 -17
- package/dist/native-subagent-monitor-DyTGmTUx.js +3111 -0
- package/dist/{plugin-app-cache-key-DSXX-rXO.js → plugin-app-cache-key-C3H2CVp6.js} +135 -42
- package/dist/plugin-marketplace-discovery-CzrGJdCR.js +385 -0
- package/dist/{computer-use-BjGylO-E.js → plugin-metadata-cache-CNJPj4_b.js} +211 -113
- package/dist/protocol-validators-B4F7L8Rf.js +2346 -0
- package/dist/{rate-limits-S_PXs0ul.js → rate-limits-DZXmOL8-.js} +17 -35
- package/dist/{request-CXpPennK.js → request-BRl5_UfX.js} +4 -4
- package/dist/{run-attempt-C5GTPJOE.js → run-attempt-P8BdxC1I.js} +1996 -5728
- package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-BVSPdd6g.js} +91 -40
- package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-Cmwc9eIk.js} +10 -13
- package/dist/{session-binding-DlVQ-Pxs.js → session-binding-DPxIvMgE.js} +305 -271
- package/dist/{session-catalog-CP2oCcEb.js → session-catalog-xwNi_SDM.js} +418 -144
- package/dist/{session-cli-CqZ0Fp2a.js → session-cli-7hgXZDV7.js} +1 -1
- package/dist/session-retirement-DoMMJFKm.js +36 -0
- package/dist/{session-upstream-marker-QE-m5QFG.js → session-upstream-marker-CrIFX06J.js} +1 -1
- package/dist/{settled-turn-finalizer-CA_2QXIE.js → settled-turn-finalizer-BRWBO4MO.js} +36 -15
- package/dist/{shared-client-BVKE40c-.js → shared-client-kIBbGjk4.js} +721 -240
- package/dist/{side-question-CO12sHxo.js → side-question-9HAL26l9.js} +102 -53
- package/dist/{thread-lifecycle-DRg_FdzC.js → thread-lifecycle-Bu1BTDXr.js} +1355 -660
- package/dist/thread-ownership-6kOQHlyC.js +92 -0
- package/dist/thread-resume-CPrJNQVM.js +30 -0
- package/dist/{transcript-mirror-uKA-A4tB.js → transcript-mirror-B9gGgnJS.js} +202 -182
- package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DIImwDVS.js} +11 -0
- package/dist/upstream-prompt-provenance-ClRBije0.js +41 -0
- package/dist/{upstream-session-fork-_Vrz4WJr.js → upstream-session-fork-CE-_LdnP.js} +5 -5
- package/dist/{usage-UpVbdX5G.js → usage-CHlV0e-o.js} +3 -3
- package/dist/{web-search-provider.runtime-CaQMYDbp.js → web-search-provider.runtime-DRw2Vl42.js} +8 -9
- package/openclaw.plugin.json +18 -3
- package/package.json +6 -7
- package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
- package/dist/conversation-control-DE7d9DOj.js +0 -287
- package/dist/event-projector-usage-tvgb1NCX.js +0 -69
- package/dist/notification-correlation-Bj_Ak_jI.js +0 -67
- package/dist/protocol-validators-Dm4eXZmt.js +0 -6337
- package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
- package/dist/realtime-voice-api.js +0 -2
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-
|
|
2
|
+
import { A as resolveCodexAppServerUserHomeDir, _ as resolveCodexAppServerStartOptionsForAgent, g as resolveCodexAppServerRuntimeOptions, o as isCodexAppServerNativeAuthProfile, p as codexAppServerStartOptionsKey, v as resolveCodexComputerUseConfig } from "./session-binding-DPxIvMgE.js";
|
|
3
|
+
import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DIImwDVS.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
+
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
6
|
import fs, { access } from "node:fs/promises";
|
|
6
7
|
import path from "node:path";
|
|
8
|
+
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
7
9
|
import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
|
8
|
-
import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
10
|
+
import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
9
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
-
import { ensureAuthProfileStore, findPersistedAuthProfileCredential, loadAuthProfileStoreForSecretsRuntime, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolveAuthProfileOrder, resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, resolveProviderIdForAuth } from "openclaw/plugin-sdk/agent-runtime";
|
|
11
|
-
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
12
12
|
import fsSync, { constants, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
13
13
|
import os from "node:os";
|
|
14
14
|
import { isDeepStrictEqual } from "node:util";
|
|
15
|
+
import { ensureAuthProfileStore, findPersistedAuthProfileCredential, loadAuthProfileStoreForSecretsRuntime, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolveAuthProfileOrder, resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, resolveProviderIdForAuth } from "openclaw/plugin-sdk/agent-runtime";
|
|
15
16
|
import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
|
|
17
|
+
import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
|
|
16
18
|
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
|
17
19
|
import { createInterface } from "node:readline";
|
|
20
|
+
import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
|
18
21
|
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
19
|
-
import { parse } from "semver";
|
|
20
22
|
import { EventEmitter } from "node:events";
|
|
21
23
|
import net from "node:net";
|
|
22
24
|
import { PassThrough, Writable } from "node:stream";
|
|
@@ -25,109 +27,18 @@ import WebSocket from "ws";
|
|
|
25
27
|
import { fileURLToPath } from "node:url";
|
|
26
28
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
27
29
|
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
28
|
-
//#region extensions/codex/src/app-server/attempt-timeouts.ts
|
|
29
|
-
/**
|
|
30
|
-
* Timeout defaults and normalizers for Codex app-server startup and turn
|
|
31
|
-
* liveness watches.
|
|
32
|
-
*/
|
|
33
|
-
/** Minimum startup timeout accepted by the Codex app-server harness. */
|
|
34
|
-
const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
|
|
35
|
-
/** Default idle timeout while waiting for app-server turn completion. */
|
|
36
|
-
const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
|
|
37
|
-
/** Short guard after apparent assistant completion. */
|
|
38
|
-
const CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 1e4;
|
|
39
|
-
const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
40
|
-
/** Guard after reasoning/commentary progress when no tool handoff occurred. */
|
|
41
|
-
const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
42
|
-
/** Long terminal idle watch for app-server turns that never send completion. */
|
|
43
|
-
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
44
|
-
var CodexAppServerStartupError = class extends Error {
|
|
45
|
-
constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
|
|
46
|
-
super(message);
|
|
47
|
-
this.reason = reason;
|
|
48
|
-
this.code = "CODEX_APP_SERVER_STARTUP_CANCELLED";
|
|
49
|
-
this.name = "CodexAppServerStartupError";
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
function isCodexAppServerStartupError(error, reason) {
|
|
53
|
-
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);
|
|
54
|
-
}
|
|
55
|
-
function resolvePositiveIntegerTimeoutMs(value, fallbackMs) {
|
|
56
|
-
return resolveTimerTimeoutMs(value, resolveTimerTimeoutMs(fallbackMs, 1));
|
|
57
|
-
}
|
|
58
|
-
/** Runs startup work with abort and timeout handling plus optional cleanup. */
|
|
59
|
-
async function withCodexStartupTimeout(params) {
|
|
60
|
-
if (params.signal.aborted) throw new CodexAppServerStartupError("aborted");
|
|
61
|
-
let timeout;
|
|
62
|
-
let abortCleanup;
|
|
63
|
-
let timeoutError;
|
|
64
|
-
let timeoutCleanup;
|
|
65
|
-
try {
|
|
66
|
-
return await Promise.race([params.operation(), new Promise((_, reject) => {
|
|
67
|
-
const rejectOnce = (error) => {
|
|
68
|
-
if (timeout) {
|
|
69
|
-
clearTimeout(timeout);
|
|
70
|
-
timeout = void 0;
|
|
71
|
-
}
|
|
72
|
-
reject(error);
|
|
73
|
-
};
|
|
74
|
-
timeout = setTimeout(() => {
|
|
75
|
-
timeoutError = new CodexAppServerStartupError("timed_out");
|
|
76
|
-
timeoutCleanup = Promise.resolve(params.onTimeout?.()).then(() => void 0, () => void 0);
|
|
77
|
-
timeoutCleanup.finally(() => {
|
|
78
|
-
rejectOnce(timeoutError);
|
|
79
|
-
});
|
|
80
|
-
}, params.timeoutMs);
|
|
81
|
-
const abortListener = () => rejectOnce(new CodexAppServerStartupError("aborted"));
|
|
82
|
-
params.signal.addEventListener("abort", abortListener, { once: true });
|
|
83
|
-
abortCleanup = () => params.signal.removeEventListener("abort", abortListener);
|
|
84
|
-
})]);
|
|
85
|
-
} catch (error) {
|
|
86
|
-
if (timeoutError) {
|
|
87
|
-
await timeoutCleanup;
|
|
88
|
-
throw timeoutError;
|
|
89
|
-
}
|
|
90
|
-
throw error;
|
|
91
|
-
} finally {
|
|
92
|
-
if (timeout) clearTimeout(timeout);
|
|
93
|
-
abortCleanup?.();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/** Resolves startup timeout while honoring the configured floor. */
|
|
97
|
-
function resolveCodexStartupTimeoutMs(params) {
|
|
98
|
-
const timeoutFloorMs = resolvePositiveIntegerTimeoutMs(params.timeoutFloorMs, CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS);
|
|
99
|
-
const timeoutMs = resolvePositiveIntegerTimeoutMs(params.timeoutMs, timeoutFloorMs);
|
|
100
|
-
return Math.max(timeoutFloorMs, timeoutMs);
|
|
101
|
-
}
|
|
102
|
-
/** Resolves the completion-idle timeout for an active turn. */
|
|
103
|
-
function resolveCodexTurnCompletionIdleTimeoutMs(value) {
|
|
104
|
-
return resolvePositiveIntegerTimeoutMs(value, CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS);
|
|
105
|
-
}
|
|
106
|
-
/** Resolves the short assistant-completion release timeout. */
|
|
107
|
-
function resolveCodexTurnAssistantCompletionIdleTimeoutMs(value) {
|
|
108
|
-
return resolvePositiveIntegerTimeoutMs(value, CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS);
|
|
109
|
-
}
|
|
110
|
-
/** Resolves the conservative post-tool raw assistant guard timeout. */
|
|
111
|
-
function resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs(value, fallbackMs) {
|
|
112
|
-
return resolvePositiveIntegerTimeoutMs(value, Math.max(resolvePositiveIntegerTimeoutMs(void 0, fallbackMs), CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS));
|
|
113
|
-
}
|
|
114
|
-
/** Resolves the long terminal turn idle timeout. */
|
|
115
|
-
function resolveCodexTurnTerminalIdleTimeoutMs(value, runTimeoutOverrideMs) {
|
|
116
|
-
const explicitRunBudgetMs = resolvePositiveIntegerTimeoutMs(runTimeoutOverrideMs, CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS);
|
|
117
|
-
return resolvePositiveIntegerTimeoutMs(value, Math.max(CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS, explicitRunBudgetMs));
|
|
118
|
-
}
|
|
119
|
-
/** Adds gateway grace time to a caller timeout without overflowing invalid values. */
|
|
120
|
-
function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
|
|
121
|
-
const timeout = resolvePositiveIntegerTimeoutMs(timeoutMs, 1);
|
|
122
|
-
return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
30
|
//#region extensions/codex/src/app-server/auth-start-options.ts
|
|
126
31
|
const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
|
|
127
32
|
const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
|
|
128
33
|
function resolveCodexAppServerHomeDir(agentDir) {
|
|
129
34
|
return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
|
|
130
35
|
}
|
|
36
|
+
/** Resolves the local CODEX_HOME used when starting one app-server connection. */
|
|
37
|
+
function resolveCodexAppServerLocalHomeDir(startOptions, agentDir, env = process.env) {
|
|
38
|
+
const configured = startOptions.env?.CODEX_HOME;
|
|
39
|
+
if (configured?.trim()) return configured;
|
|
40
|
+
return startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir(env) : resolveCodexAppServerHomeDir(agentDir);
|
|
41
|
+
}
|
|
131
42
|
/** Forces OpenClaw-owned Codex auth to remain process-local. */
|
|
132
43
|
function withEphemeralCodexAuthStore(params) {
|
|
133
44
|
const { startOptions } = params;
|
|
@@ -408,8 +319,8 @@ function flattenCodexDynamicToolFunctions(tools) {
|
|
|
408
319
|
}
|
|
409
320
|
const CODEX_INTERACTIVE_THREAD_SOURCE_KINDS = ["cli", "vscode"];
|
|
410
321
|
const CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES = ["atlas", "chatgpt"];
|
|
411
|
-
function isJsonObject
|
|
412
|
-
return
|
|
322
|
+
function isJsonObject(value) {
|
|
323
|
+
return isRecord(value);
|
|
413
324
|
}
|
|
414
325
|
function isRpcResponse(message) {
|
|
415
326
|
return "id" in message && !("method" in message);
|
|
@@ -417,10 +328,7 @@ function isRpcResponse(message) {
|
|
|
417
328
|
//#endregion
|
|
418
329
|
//#region extensions/codex/src/app-server/auth-bridge.ts
|
|
419
330
|
const CODEX_APP_SERVER_AUTH_PROVIDER = "openai";
|
|
420
|
-
const
|
|
421
|
-
const LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER = "codex-cli";
|
|
422
|
-
const CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS = [CODEX_APP_SERVER_AUTH_PROVIDER, LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER];
|
|
423
|
-
const OPENAI_PROVIDER = "openai";
|
|
331
|
+
const CODEX_APP_SERVER_EXTERNAL_CLI_PROVIDER_IDS = [CODEX_APP_SERVER_AUTH_PROVIDER];
|
|
424
332
|
const OPENAI_CODEX_DEFAULT_PROFILE_ID = "openai:default";
|
|
425
333
|
const CODEX_HOME_ENV_VAR = "CODEX_HOME";
|
|
426
334
|
const HOME_ENV_VAR = "HOME";
|
|
@@ -439,25 +347,41 @@ const CODEX_HOME_DIRNAME = ".codex";
|
|
|
439
347
|
const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
|
|
440
348
|
async function bridgeCodexAppServerStartOptions(params) {
|
|
441
349
|
if (params.startOptions.transport !== "stdio") return params.startOptions;
|
|
442
|
-
const
|
|
443
|
-
if (params.preparedAuth) return withClearedEnvironmentVariables(
|
|
444
|
-
if (params.authProfileId === null) return
|
|
350
|
+
const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
|
|
351
|
+
if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
|
|
352
|
+
if (params.authProfileId === null) return scopeStartOptions();
|
|
445
353
|
const store = resolveCodexAppServerAuthProfileStore({
|
|
446
354
|
agentDir: params.agentDir,
|
|
447
355
|
authProfileId: params.authProfileId,
|
|
448
356
|
authProfileStore: params.authProfileStore,
|
|
449
357
|
config: params.config
|
|
450
358
|
});
|
|
451
|
-
|
|
359
|
+
const authProfileId = resolveCodexAppServerAuthProfileId({
|
|
360
|
+
authProfileId: params.authProfileId,
|
|
452
361
|
store,
|
|
453
|
-
authProfileId: resolveCodexAppServerAuthProfileId({
|
|
454
|
-
authProfileId: params.authProfileId,
|
|
455
|
-
store,
|
|
456
|
-
config: params.config
|
|
457
|
-
}),
|
|
458
362
|
config: params.config
|
|
363
|
+
});
|
|
364
|
+
if (!authProfileId) assertNoUnimportedAgentCodexAuthFile(params);
|
|
365
|
+
const scopedStartOptions = await scopeStartOptions();
|
|
366
|
+
return shouldClearOpenAiApiKeyForCodexAuthProfile({
|
|
367
|
+
store,
|
|
368
|
+
authProfileId
|
|
459
369
|
}) ? withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS) : scopedStartOptions;
|
|
460
370
|
}
|
|
371
|
+
function assertNoUnimportedAgentCodexAuthFile(params) {
|
|
372
|
+
if (params.authRequirement === "api-key" && resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions: params.startOptions })) return;
|
|
373
|
+
const message = resolveUnimportedAgentCodexAuthMessage(params);
|
|
374
|
+
if (message) throw new AgentHarnessPreflightError(message);
|
|
375
|
+
}
|
|
376
|
+
function resolveUnimportedAgentCodexAuthMessage(params) {
|
|
377
|
+
const managedCodexCli = params.startOptions.commandSource === "managed" || params.startOptions.commandSource === "resolved-managed";
|
|
378
|
+
if (params.startOptions.transport !== "stdio" || !managedCodexCli || params.startOptions.homeScope === "user") return;
|
|
379
|
+
const codexHome = resolveCodexAppServerHomeDir(params.agentDir);
|
|
380
|
+
const authPath = path.join(codexHome, CODEX_AUTH_JSON_FILENAME);
|
|
381
|
+
if (!fsSync.existsSync(authPath)) return;
|
|
382
|
+
const targetAgentId = params.agentId?.trim() || "<agent-id>";
|
|
383
|
+
return `A Codex auth file exists at ${authPath}, but agent-scoped Codex runs use OpenClaw's auth store and do not read that file. Preview only that credential import with \`openclaw migrate plan codex --from <codex-home> --agent ${targetAgentId} --include-secrets --item auth:openai\`, then run \`openclaw migrate apply codex --from <codex-home> --agent ${targetAgentId} --include-secrets --item auth:openai --yes\`. If the plan finds no credentials, remove the stale auth file.`;
|
|
384
|
+
}
|
|
461
385
|
function resolveCodexAppServerAuthProfileId(params) {
|
|
462
386
|
const requested = params.authProfileId?.trim();
|
|
463
387
|
if (requested) return requested;
|
|
@@ -512,7 +436,7 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
|
|
|
512
436
|
});
|
|
513
437
|
if (!profileId) return;
|
|
514
438
|
const credential = store.profiles[profileId];
|
|
515
|
-
if (!credential || !isCodexAppServerAuthProfileCredential(credential
|
|
439
|
+
if (!credential || !isCodexAppServerAuthProfileCredential(credential)) return;
|
|
516
440
|
const loginParams = await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
517
441
|
agentDir,
|
|
518
442
|
authProfileId: profileId,
|
|
@@ -522,14 +446,18 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
|
|
|
522
446
|
if (!loginParams) return;
|
|
523
447
|
const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
|
|
524
448
|
const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
|
|
449
|
+
const secretFreeCacheKey = credential.type === "api_key" && loginParams.type === "apiKey" ? `${accountId}:${fingerprintApiKeyAuthProfileCacheKey(loginParams.apiKey)}` : loginParams.type === "chatgptAuthTokens" && (credential.type === "token" || !stableChatgptAccountId) ? `${accountId}:${fingerprintTokenAuthProfileCacheKey(loginParams.accessToken)}` : accountId;
|
|
450
|
+
const chatgptAccountId = resolveExplicitChatgptAccountId(credential);
|
|
525
451
|
return {
|
|
526
452
|
loginParams,
|
|
527
|
-
secretFreeCacheKey
|
|
453
|
+
secretFreeCacheKey,
|
|
454
|
+
...chatgptAccountId ? { chatgptAccountId } : {}
|
|
528
455
|
};
|
|
529
456
|
}
|
|
530
457
|
/** Maps one prepared route to one mutually exclusive app-server auth handoff. */
|
|
531
458
|
async function resolveCodexAppServerPreparedAuthHandoff(params) {
|
|
532
459
|
const usesNativeHome = params.homeScope === "user";
|
|
460
|
+
if (params.requirePreparedAuth && usesNativeHome) throw createCodexAppServerAuthError("Codex remote-exec cloud placement requires prepared OpenAI auth. Configure an OpenAI API-key, OAuth, or token profile and use appServer.homeScope=\"agent\"; ambient credentials and native Codex auth are not allowed.");
|
|
533
461
|
if (params.authRequirement === "api-key" && !usesNativeHome) {
|
|
534
462
|
const apiKey = params.resolvedApiKey?.trim();
|
|
535
463
|
if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
|
|
@@ -548,18 +476,18 @@ async function resolveCodexAppServerPreparedAuthHandoff(params) {
|
|
|
548
476
|
agentDir: params.agentDir,
|
|
549
477
|
config: params.config
|
|
550
478
|
});
|
|
551
|
-
if (usesNativeHome || params.authRequirement !== "subscription") return {
|
|
479
|
+
if (usesNativeHome || params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
|
|
552
480
|
authProfileId,
|
|
553
481
|
nativeAuthProfile
|
|
554
482
|
};
|
|
555
|
-
if (!authProfileId || !nativeAuthProfile) throw createCodexAppServerAuthError(params.subscriptionProfileRequiredError);
|
|
483
|
+
if (!authProfileId || params.authRequirement === "subscription" && !nativeAuthProfile) throw createCodexAppServerAuthError(params.requirePreparedAuth ? "Codex remote-exec cloud placement requires prepared OpenAI auth. Configure an OpenAI API-key, OAuth, or token profile; ambient CODEX_API_KEY, OPENAI_API_KEY, and native Codex auth are not allowed." : params.subscriptionProfileRequiredError);
|
|
556
484
|
const snapshot = await resolveCodexAppServerPreparedAuthProfileSnapshot({
|
|
557
485
|
authProfileId,
|
|
558
486
|
authProfileStore: params.authProfileStore,
|
|
559
487
|
agentDir: params.agentDir,
|
|
560
488
|
config: params.config
|
|
561
489
|
});
|
|
562
|
-
if (!snapshot) throw createCodexAppServerAuthError(params.subscriptionProfileUnusableError);
|
|
490
|
+
if (!snapshot) throw createCodexAppServerAuthError(params.requirePreparedAuth ? "Codex remote-exec cloud placement could not prepare the selected OpenAI auth profile. Repair or replace the profile, then retry." : params.subscriptionProfileUnusableError);
|
|
563
491
|
return {
|
|
564
492
|
authProfileId,
|
|
565
493
|
nativeAuthProfile,
|
|
@@ -586,7 +514,7 @@ async function resolveCodexAppServerAuthAccountCacheKey(params) {
|
|
|
586
514
|
});
|
|
587
515
|
if (!profileId) return;
|
|
588
516
|
const credential = store.profiles[profileId];
|
|
589
|
-
if (!credential || !isCodexAppServerAuthProfileCredential(credential
|
|
517
|
+
if (!credential || !isCodexAppServerAuthProfileCredential(credential)) return;
|
|
590
518
|
if (credential.type === "api_key") {
|
|
591
519
|
const apiKey = (await resolveApiKeyForProfile({
|
|
592
520
|
store,
|
|
@@ -648,7 +576,7 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
|
|
|
648
576
|
return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
|
|
649
577
|
}
|
|
650
578
|
async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
|
|
651
|
-
const codexHome =
|
|
579
|
+
const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
|
|
652
580
|
const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
|
|
653
581
|
await fs.mkdir(codexHome, { recursive: true });
|
|
654
582
|
const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
|
|
@@ -662,7 +590,10 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
|
|
|
662
590
|
appServerCommand: startOptions.command
|
|
663
591
|
});
|
|
664
592
|
} catch (error) {
|
|
665
|
-
throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
|
|
593
|
+
throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
|
|
594
|
+
cause: error,
|
|
595
|
+
scope: "harness"
|
|
596
|
+
});
|
|
666
597
|
}
|
|
667
598
|
if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
|
|
668
599
|
const nextStartOptions = {
|
|
@@ -697,10 +628,7 @@ async function applyCodexAppServerAuthProfile(params) {
|
|
|
697
628
|
return;
|
|
698
629
|
}
|
|
699
630
|
if (params.authProfileId === null) {
|
|
700
|
-
|
|
701
|
-
const response = await params.client.request("account/read", { refreshToken: false });
|
|
702
|
-
if (!isJsonObject$1(response.account) || response.account.type !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
|
|
703
|
-
}
|
|
631
|
+
await assertNativeCodexAccountMatchesRoute(params.client, params.authRequirement);
|
|
704
632
|
return;
|
|
705
633
|
}
|
|
706
634
|
let loginParams;
|
|
@@ -730,6 +658,23 @@ async function applyCodexAppServerAuthProfile(params) {
|
|
|
730
658
|
}
|
|
731
659
|
await params.client.request("account/login/start", loginParams);
|
|
732
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Native-home connections are verified, never logged into. Both directions of the
|
|
663
|
+
* check protect the same billing boundary: a subscription route cannot run without
|
|
664
|
+
* ChatGPT tokens, and a Platform route must not silently spend the operator's
|
|
665
|
+
* ChatGPT plan. An absent account is left alone because the native home may serve a
|
|
666
|
+
* custom model provider that reports no OpenAI account at all.
|
|
667
|
+
*/
|
|
668
|
+
async function assertNativeCodexAccountMatchesRoute(client, authRequirement) {
|
|
669
|
+
if (!authRequirement) return;
|
|
670
|
+
const response = await client.request("account/read", { refreshToken: false });
|
|
671
|
+
const accountType = isJsonObject(response.account) ? response.account.type : void 0;
|
|
672
|
+
if (authRequirement === "subscription") {
|
|
673
|
+
if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
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.");
|
|
677
|
+
}
|
|
733
678
|
function createCodexAppServerAuthError(message, cause) {
|
|
734
679
|
const error = cause === void 0 ? new Error(message) : new Error(message, { cause });
|
|
735
680
|
return Object.assign(error, { status: 401 });
|
|
@@ -744,7 +689,7 @@ async function resolveCodexAppServerAuthProfileLoginParams(params) {
|
|
|
744
689
|
});
|
|
745
690
|
const profile = profileId ? store.profiles[profileId] : void 0;
|
|
746
691
|
if (profileId && !profile) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" was not found.`);
|
|
747
|
-
if (profileId && profile && !isCodexAppServerAuthProfileCredential(profile
|
|
692
|
+
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.`);
|
|
748
693
|
return await resolveCodexAppServerAuthProfileLoginParamsInternal({
|
|
749
694
|
...params,
|
|
750
695
|
authProfileStore: store
|
|
@@ -777,7 +722,7 @@ async function resolveCodexAppServerAuthProfileLoginParamsInternal(params) {
|
|
|
777
722
|
if (!profileId) return;
|
|
778
723
|
const credential = store.profiles[profileId];
|
|
779
724
|
if (!credential) throw new Error(`Codex app-server auth profile "${profileId}" was not found.`);
|
|
780
|
-
if (!isCodexAppServerAuthProfileCredential(credential
|
|
725
|
+
if (!isCodexAppServerAuthProfileCredential(credential)) throw new Error(`Codex app-server auth profile "${profileId}" must use the canonical OpenAI auth provider; run "openclaw doctor --fix" to migrate legacy provider IDs.`);
|
|
781
726
|
const loginParams = await resolveLoginParamsForCredential(profileId, credential, {
|
|
782
727
|
agentDir: params.agentDir,
|
|
783
728
|
store,
|
|
@@ -823,7 +768,7 @@ function parseCodexCliAuthFileApiKey(raw) {
|
|
|
823
768
|
}
|
|
824
769
|
async function readCodexCliAuthFileApiKey(env) {
|
|
825
770
|
try {
|
|
826
|
-
return parseCodexCliAuthFileApiKey(await
|
|
771
|
+
return parseCodexCliAuthFileApiKey(await readSecretFile(resolveCodexCliAuthFilePath(env), "Codex CLI auth file"));
|
|
827
772
|
} catch {
|
|
828
773
|
return;
|
|
829
774
|
}
|
|
@@ -888,9 +833,9 @@ async function resolveOAuthCredentialForCodexAppServer(profileId, credential, pa
|
|
|
888
833
|
authProfileId: profileId,
|
|
889
834
|
config: params.config
|
|
890
835
|
});
|
|
891
|
-
const persistedOAuthCredential = !useScopedCredential && persistedCredential?.type === "oauth" && isCodexAppServerAuthProvider(persistedCredential.provider
|
|
836
|
+
const persistedOAuthCredential = !useScopedCredential && persistedCredential?.type === "oauth" && isCodexAppServerAuthProvider(persistedCredential.provider) ? persistedCredential : void 0;
|
|
892
837
|
const ownerCredential = store.profiles[profileId];
|
|
893
|
-
const overlaidOAuthCredential = ownerCredential?.type === "oauth" && isCodexAppServerAuthProvider(ownerCredential.provider
|
|
838
|
+
const overlaidOAuthCredential = ownerCredential?.type === "oauth" && isCodexAppServerAuthProvider(ownerCredential.provider) ? ownerCredential : void 0;
|
|
894
839
|
if (useScopedCredential && overlaidOAuthCredential) return await resolveScopedOAuthCredential({
|
|
895
840
|
store,
|
|
896
841
|
profileId,
|
|
@@ -910,10 +855,10 @@ async function resolveOAuthCredentialForCodexAppServer(profileId, credential, pa
|
|
|
910
855
|
forceRefresh: params.forceRefresh && Boolean(persistedOAuthCredential)
|
|
911
856
|
});
|
|
912
857
|
const refreshed = useScopedCredential ? void 0 : loadAuthProfileStoreForSecretsRuntime(ownerAgentDir).profiles[profileId];
|
|
913
|
-
const refreshedOAuthCredential = refreshed?.type === "oauth" && isCodexAppServerAuthProvider(refreshed.provider
|
|
858
|
+
const refreshedOAuthCredential = refreshed?.type === "oauth" && isCodexAppServerAuthProvider(refreshed.provider) ? refreshed : void 0;
|
|
914
859
|
if (refreshedOAuthCredential && isDeepStrictEqual(params.store.profiles[profileId], credential)) params.store.profiles[profileId] = refreshedOAuthCredential;
|
|
915
860
|
const storedCredential = store.profiles[profileId];
|
|
916
|
-
const candidate = refreshedOAuthCredential ? refreshedOAuthCredential : storedCredential?.type === "oauth" && isCodexAppServerAuthProvider(storedCredential.provider
|
|
861
|
+
const candidate = refreshedOAuthCredential ? refreshedOAuthCredential : storedCredential?.type === "oauth" && isCodexAppServerAuthProvider(storedCredential.provider) ? storedCredential : credential;
|
|
917
862
|
return resolved?.apiKey ? {
|
|
918
863
|
...candidate,
|
|
919
864
|
access: resolved.apiKey
|
|
@@ -957,22 +902,18 @@ async function resolveScopedOAuthCredential(params) {
|
|
|
957
902
|
if (storeRefreshes.get(params.profileId) === refresh) storeRefreshes.delete(params.profileId);
|
|
958
903
|
}
|
|
959
904
|
}
|
|
960
|
-
function isCodexAppServerAuthProvider(provider
|
|
961
|
-
|
|
962
|
-
return resolvedProvider === CODEX_APP_SERVER_AUTH_PROVIDER || resolvedProvider === OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER || resolvedProvider === LEGACY_CODEX_APP_SERVER_AUTH_PROVIDER;
|
|
963
|
-
}
|
|
964
|
-
function isOpenAIApiKeyBackupCredential(credential, config) {
|
|
965
|
-
return credential.type === "api_key" && resolveProviderIdForAuth(credential.provider, { config }) === OPENAI_PROVIDER;
|
|
905
|
+
function isCodexAppServerAuthProvider(provider) {
|
|
906
|
+
return provider.trim().toLowerCase() === CODEX_APP_SERVER_AUTH_PROVIDER;
|
|
966
907
|
}
|
|
967
|
-
function isCodexAppServerAuthProfileCredential(credential
|
|
968
|
-
return isCodexAppServerAuthProvider(credential.provider
|
|
908
|
+
function isCodexAppServerAuthProfileCredential(credential) {
|
|
909
|
+
return isCodexAppServerAuthProvider(credential.provider);
|
|
969
910
|
}
|
|
970
911
|
function shouldClearOpenAiApiKeyForCodexAuthProfile(params) {
|
|
971
912
|
const profileId = params.authProfileId?.trim();
|
|
972
|
-
return isCodexSubscriptionCredential(profileId ? params.store.profiles[profileId] : params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]
|
|
913
|
+
return isCodexSubscriptionCredential(profileId ? params.store.profiles[profileId] : params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]);
|
|
973
914
|
}
|
|
974
|
-
function isCodexSubscriptionCredential(credential
|
|
975
|
-
if (!credential || !isCodexAppServerAuthProvider(credential.provider
|
|
915
|
+
function isCodexSubscriptionCredential(credential) {
|
|
916
|
+
if (!credential || !isCodexAppServerAuthProvider(credential.provider)) return false;
|
|
976
917
|
return credential.type === "oauth" || credential.type === "token";
|
|
977
918
|
}
|
|
978
919
|
function withClearedEnvironmentVariables(startOptions, envVars) {
|
|
@@ -1013,11 +954,13 @@ function resolveChatgptAccountId(profileId, credential) {
|
|
|
1013
954
|
return resolveStableChatgptAccountId(credential) ?? profileId;
|
|
1014
955
|
}
|
|
1015
956
|
function resolveStableChatgptAccountId(credential) {
|
|
957
|
+
return resolveExplicitChatgptAccountId(credential) ?? (credential.email?.trim() || void 0);
|
|
958
|
+
}
|
|
959
|
+
function resolveExplicitChatgptAccountId(credential) {
|
|
1016
960
|
if ("accountId" in credential && typeof credential.accountId === "string") {
|
|
1017
961
|
const accountId = credential.accountId.trim();
|
|
1018
962
|
if (accountId) return accountId;
|
|
1019
963
|
}
|
|
1020
|
-
return credential.email?.trim() || void 0;
|
|
1021
964
|
}
|
|
1022
965
|
//#endregion
|
|
1023
966
|
//#region extensions/codex/src/app-server/rate-limit-cache.ts
|
|
@@ -1042,7 +985,7 @@ function rememberCodexRateLimitsRead(client, value, nowMs = Date.now()) {
|
|
|
1042
985
|
}
|
|
1043
986
|
/** Merges a sparse rolling notification into one physical client's latest read response. */
|
|
1044
987
|
function mergeCodexRateLimitsUpdate(client, value, nowMs = Date.now()) {
|
|
1045
|
-
const update = isJsonObject
|
|
988
|
+
const update = isJsonObject(value) && isJsonObject(value.rateLimits) ? value.rateLimits : void 0;
|
|
1046
989
|
if (!update) return;
|
|
1047
990
|
const currentState = rateLimitsByClient.get(client);
|
|
1048
991
|
const current = currentState?.value;
|
|
@@ -1069,13 +1012,13 @@ function readRecentCodexRateLimits(client, options) {
|
|
|
1069
1012
|
return maxAgeMs >= 0 && nowMs - state.updatedAtMs > maxAgeMs ? void 0 : state.value;
|
|
1070
1013
|
}
|
|
1071
1014
|
function mergeRateLimitUpdate(current, update) {
|
|
1072
|
-
const currentEnvelope = isJsonObject
|
|
1073
|
-
const currentPrimary = currentEnvelope && isJsonObject
|
|
1074
|
-
const currentByLimitId = currentEnvelope && isJsonObject
|
|
1015
|
+
const currentEnvelope = isJsonObject(current) ? current : void 0;
|
|
1016
|
+
const currentPrimary = currentEnvelope && isJsonObject(currentEnvelope.rateLimits) ? currentEnvelope.rateLimits : void 0;
|
|
1017
|
+
const currentByLimitId = currentEnvelope && isJsonObject(currentEnvelope.rateLimitsByLimitId) ? currentEnvelope.rateLimitsByLimitId : void 0;
|
|
1075
1018
|
const limitId = readLimitId(update);
|
|
1076
1019
|
const currentPrimaryLimitId = currentPrimary ? readLimitId(currentPrimary) : void 0;
|
|
1077
|
-
const currentForLimit = (currentByLimitId && isJsonObject
|
|
1078
|
-
const merged = mergeSparseSnapshot(isJsonObject
|
|
1020
|
+
const currentForLimit = (currentByLimitId && isJsonObject(currentByLimitId[limitId]) ? currentByLimitId[limitId] : void 0) ?? (currentPrimaryLimitId === limitId ? currentPrimary : void 0);
|
|
1021
|
+
const merged = mergeSparseSnapshot(isJsonObject(currentForLimit) ? currentForLimit : void 0, currentPrimary, update, limitId);
|
|
1079
1022
|
const nextPrimary = !currentPrimary || currentPrimaryLimitId === limitId ? merged : currentPrimary;
|
|
1080
1023
|
let nextByLimitId;
|
|
1081
1024
|
if (currentByLimitId) nextByLimitId = {
|
|
@@ -1093,11 +1036,11 @@ function mergeRateLimitUpdate(current, update) {
|
|
|
1093
1036
|
};
|
|
1094
1037
|
}
|
|
1095
1038
|
function readRateLimitIds(value) {
|
|
1096
|
-
if (!isJsonObject
|
|
1039
|
+
if (!isJsonObject(value)) return [];
|
|
1097
1040
|
const ids = /* @__PURE__ */ new Set();
|
|
1098
|
-
if (isJsonObject
|
|
1099
|
-
if (isJsonObject
|
|
1100
|
-
const snapshotLimitId = isJsonObject
|
|
1041
|
+
if (isJsonObject(value.rateLimits)) ids.add(readLimitId(value.rateLimits));
|
|
1042
|
+
if (isJsonObject(value.rateLimitsByLimitId)) for (const [key, snapshot] of Object.entries(value.rateLimitsByLimitId)) {
|
|
1043
|
+
const snapshotLimitId = isJsonObject(snapshot) && typeof snapshot.limitId === "string" ? snapshot.limitId.trim() : "";
|
|
1101
1044
|
ids.add(snapshotLimitId || key);
|
|
1102
1045
|
}
|
|
1103
1046
|
return [...ids];
|
|
@@ -1120,16 +1063,45 @@ function readLimitId(snapshot) {
|
|
|
1120
1063
|
//#endregion
|
|
1121
1064
|
//#region extensions/codex/src/app-server/client-runtime.ts
|
|
1122
1065
|
/** Client-scoped Codex auth and account observers. */
|
|
1066
|
+
/** Match Codex's native grace window without retaining inactive conversations indefinitely. */
|
|
1067
|
+
const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
1068
|
+
/** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
|
|
1069
|
+
const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
|
|
1123
1070
|
const configuredClients = /* @__PURE__ */ new WeakMap();
|
|
1071
|
+
const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
|
|
1072
|
+
const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
|
|
1073
|
+
/** Only an initialized, still-open physical client can own retained native subscriptions. */
|
|
1074
|
+
function isCodexAppServerClientRuntimeLive(client) {
|
|
1075
|
+
const runtime = configuredClients.get(client);
|
|
1076
|
+
return runtime !== void 0 && !runtime.closed;
|
|
1077
|
+
}
|
|
1124
1078
|
/** Installs one auth-refresh handler and one rate-limit observer per physical client. */
|
|
1125
1079
|
function ensureCodexAppServerClientRuntime(client, context) {
|
|
1126
1080
|
const existing = configuredClients.get(client);
|
|
1127
1081
|
if (existing) {
|
|
1082
|
+
if (existing.closed) return;
|
|
1128
1083
|
existing.context = context;
|
|
1129
1084
|
return;
|
|
1130
1085
|
}
|
|
1131
|
-
const runtime = {
|
|
1086
|
+
const runtime = {
|
|
1087
|
+
context,
|
|
1088
|
+
closed: false,
|
|
1089
|
+
retainedThreads: /* @__PURE__ */ new Map(),
|
|
1090
|
+
claimedThreads: /* @__PURE__ */ new Map(),
|
|
1091
|
+
releasingThreads: /* @__PURE__ */ new Map(),
|
|
1092
|
+
protectedThreads: /* @__PURE__ */ new Map()
|
|
1093
|
+
};
|
|
1132
1094
|
configuredClients.set(client, runtime);
|
|
1095
|
+
client.addCloseHandler(() => {
|
|
1096
|
+
runtime.closed = true;
|
|
1097
|
+
if (runtime.evictionTimer) {
|
|
1098
|
+
clearTimeout(runtime.evictionTimer);
|
|
1099
|
+
runtime.evictionTimer = void 0;
|
|
1100
|
+
}
|
|
1101
|
+
runtime.retainedThreads.clear();
|
|
1102
|
+
runtime.claimedThreads.clear();
|
|
1103
|
+
runtime.protectedThreads.clear();
|
|
1104
|
+
});
|
|
1133
1105
|
client.addRequestHandler(async (request) => {
|
|
1134
1106
|
if (request.method !== "account/chatgptAuthTokens/refresh") return;
|
|
1135
1107
|
if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
|
|
@@ -1141,15 +1113,289 @@ function ensureCodexAppServerClientRuntime(client, context) {
|
|
|
1141
1113
|
});
|
|
1142
1114
|
});
|
|
1143
1115
|
client.addNotificationHandler((notification) => {
|
|
1144
|
-
if (notification.method === "account/rateLimits/updated")
|
|
1116
|
+
if (notification.method === "account/rateLimits/updated") {
|
|
1117
|
+
mergeCodexRateLimitsUpdate(client, notification.params);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (notification.method === "thread/archived" || notification.method === "thread/deleted" || notification.method === "thread/closed") {
|
|
1121
|
+
const threadId = notification.params?.threadId;
|
|
1122
|
+
if (typeof threadId === "string") {
|
|
1123
|
+
const releasing = runtime.releasingThreads.get(threadId);
|
|
1124
|
+
if (releasing) releasing.invalidated = true;
|
|
1125
|
+
runtime.retainedThreads.delete(threadId);
|
|
1126
|
+
runtime.claimedThreads.delete(threadId);
|
|
1127
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
function scheduleRetainedThreadEviction(client, runtime) {
|
|
1133
|
+
if (runtime.evictionTimer) {
|
|
1134
|
+
clearTimeout(runtime.evictionTimer);
|
|
1135
|
+
runtime.evictionTimer = void 0;
|
|
1136
|
+
}
|
|
1137
|
+
if (runtime.closed) return;
|
|
1138
|
+
let expiresAt = Number.POSITIVE_INFINITY;
|
|
1139
|
+
for (const [threadId, thread] of runtime.retainedThreads) if (!runtime.protectedThreads.has(threadId)) expiresAt = Math.min(expiresAt, thread.expiresAt);
|
|
1140
|
+
if (!Number.isFinite(expiresAt)) return;
|
|
1141
|
+
runtime.evictionTimer = setTimeout(() => {
|
|
1142
|
+
runtime.evictionTimer = void 0;
|
|
1143
|
+
evictExpiredRetainedThreads(client, runtime).catch((error) => {
|
|
1144
|
+
embeddedAgentLog.warn("codex retained thread expiry failed", { reason: formatErrorMessage(error) });
|
|
1145
|
+
});
|
|
1146
|
+
}, Math.max(0, expiresAt - Date.now()));
|
|
1147
|
+
runtime.evictionTimer.unref?.();
|
|
1148
|
+
}
|
|
1149
|
+
async function releaseRetainedThread(client, runtime, threadId) {
|
|
1150
|
+
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1151
|
+
if (pendingRelease) {
|
|
1152
|
+
await pendingRelease.completion;
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
const retained = runtime.retainedThreads.get(threadId);
|
|
1156
|
+
if (!retained) return false;
|
|
1157
|
+
const olderThreadIds = /* @__PURE__ */ new Set();
|
|
1158
|
+
for (const candidateThreadId of runtime.retainedThreads.keys()) {
|
|
1159
|
+
if (candidateThreadId === threadId) break;
|
|
1160
|
+
olderThreadIds.add(candidateThreadId);
|
|
1161
|
+
}
|
|
1162
|
+
runtime.retainedThreads.delete(threadId);
|
|
1163
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1164
|
+
const transition = { completion: Promise.resolve().then(() => retained.release(threadId)) };
|
|
1165
|
+
runtime.releasingThreads.set(threadId, transition);
|
|
1166
|
+
try {
|
|
1167
|
+
await transition.completion;
|
|
1168
|
+
return true;
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
if (!runtime.closed && !transition.invalidated && runtime.releasingThreads.get(threadId) === transition && !runtime.retainedThreads.has(threadId) && !runtime.claimedThreads.has(threadId)) {
|
|
1171
|
+
retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
|
|
1172
|
+
const newerThreads = [...runtime.retainedThreads.entries()].filter(([candidateThreadId]) => !olderThreadIds.has(candidateThreadId));
|
|
1173
|
+
for (const [candidateThreadId] of newerThreads) runtime.retainedThreads.delete(candidateThreadId);
|
|
1174
|
+
runtime.retainedThreads.set(threadId, retained);
|
|
1175
|
+
for (const [candidateThreadId, newerThread] of newerThreads) runtime.retainedThreads.set(candidateThreadId, newerThread);
|
|
1176
|
+
}
|
|
1177
|
+
throw error;
|
|
1178
|
+
} finally {
|
|
1179
|
+
if (runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
|
|
1180
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
async function evictExpiredRetainedThreads(client, runtime) {
|
|
1184
|
+
const now = Date.now();
|
|
1185
|
+
for (const [threadId, thread] of runtime.retainedThreads) if (thread.expiresAt <= now && !runtime.protectedThreads.has(threadId)) await releaseRetainedThread(client, runtime, threadId);
|
|
1186
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1187
|
+
}
|
|
1188
|
+
async function evictExcessIdleThreads(client, runtime) {
|
|
1189
|
+
let idleThreadIds = [...runtime.retainedThreads.keys()].filter((threadId) => !runtime.protectedThreads.has(threadId));
|
|
1190
|
+
while (idleThreadIds.length > CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE) {
|
|
1191
|
+
await releaseRetainedThread(client, runtime, idleThreadIds[0]);
|
|
1192
|
+
idleThreadIds = [...runtime.retainedThreads.keys()].filter((threadId) => !runtime.protectedThreads.has(threadId));
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
/** Retain separately owned Codex subscriptions; completing B must never cold-restart A. */
|
|
1196
|
+
async function retainCodexAppServerLiveThread(client, threadId, releaseThread, configFingerprint, serviceTier) {
|
|
1197
|
+
const runtime = configuredClients.get(client);
|
|
1198
|
+
if (!runtime || runtime.closed) return false;
|
|
1199
|
+
const claimed = runtime.claimedThreads.get(threadId);
|
|
1200
|
+
if (claimed !== void 0 && (releaseThread === void 0 || claimedThreadReleaseTokens.get(releaseThread) !== claimed)) return false;
|
|
1201
|
+
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1202
|
+
if (pendingRelease) {
|
|
1203
|
+
await pendingRelease.completion;
|
|
1204
|
+
if (runtime.closed || claimed !== void 0 && runtime.claimedThreads.get(threadId) !== claimed) return false;
|
|
1205
|
+
}
|
|
1206
|
+
runtime.retainedThreads.delete(threadId);
|
|
1207
|
+
const retained = {
|
|
1208
|
+
configFingerprint,
|
|
1209
|
+
serviceTier,
|
|
1210
|
+
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
|
|
1211
|
+
release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId) => {
|
|
1212
|
+
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
|
|
1213
|
+
})
|
|
1214
|
+
};
|
|
1215
|
+
runtime.retainedThreads.set(threadId, retained);
|
|
1216
|
+
try {
|
|
1217
|
+
await evictExcessIdleThreads(client, runtime);
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
if (runtime.retainedThreads.get(threadId) === retained) runtime.retainedThreads.delete(threadId);
|
|
1220
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1221
|
+
embeddedAgentLog.warn("codex retained thread capacity eviction failed", {
|
|
1222
|
+
threadId,
|
|
1223
|
+
reason: formatErrorMessage(error)
|
|
1224
|
+
});
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
if (runtime.closed) return false;
|
|
1228
|
+
if (claimed !== void 0 && runtime.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
|
|
1229
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
/** Transfer one idle subscription to its next turn or compaction without touching sibling threads. */
|
|
1233
|
+
async function consumeCodexAppServerLiveThread(client, threadId, configFingerprint) {
|
|
1234
|
+
const runtime = configuredClients.get(client);
|
|
1235
|
+
if (!runtime || runtime.closed) return;
|
|
1236
|
+
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1237
|
+
if (pendingRelease) {
|
|
1238
|
+
await pendingRelease.completion;
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const retained = runtime.retainedThreads.get(threadId);
|
|
1242
|
+
if (!retained || configFingerprint !== void 0 && retained.configFingerprint !== configFingerprint) return;
|
|
1243
|
+
return claimCodexAppServerThreadOwnership(client, runtime, threadId, retained);
|
|
1244
|
+
}
|
|
1245
|
+
/** Claims an observed Codex auto-subscription without exposing a temporarily idle owner. */
|
|
1246
|
+
async function claimCodexAppServerLiveThread(client, threadId) {
|
|
1247
|
+
const runtime = configuredClients.get(client);
|
|
1248
|
+
if (!runtime || runtime.closed || runtime.claimedThreads.has(threadId)) return;
|
|
1249
|
+
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1250
|
+
if (pendingRelease) {
|
|
1251
|
+
await pendingRelease.completion;
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
return claimCodexAppServerThreadOwnership(client, runtime, threadId, runtime.retainedThreads.get(threadId) ?? {
|
|
1255
|
+
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
|
|
1256
|
+
release: async (releasedThreadId) => {
|
|
1257
|
+
await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
|
|
1258
|
+
}
|
|
1145
1259
|
});
|
|
1146
1260
|
}
|
|
1261
|
+
function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained) {
|
|
1262
|
+
runtime.retainedThreads.delete(threadId);
|
|
1263
|
+
const claimed = Symbol(threadId);
|
|
1264
|
+
runtime.claimedThreads.set(threadId, claimed);
|
|
1265
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1266
|
+
const release = async (releasedThreadId) => {
|
|
1267
|
+
if (releasedThreadId !== threadId || runtime.claimedThreads.get(threadId) !== claimed) return;
|
|
1268
|
+
const pendingRelease = runtime.releasingThreads.get(threadId);
|
|
1269
|
+
if (pendingRelease) {
|
|
1270
|
+
await pendingRelease.completion;
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const transition = { completion: Promise.resolve().then(async () => {
|
|
1274
|
+
if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) return;
|
|
1275
|
+
await retained.release(releasedThreadId);
|
|
1276
|
+
}) };
|
|
1277
|
+
runtime.releasingThreads.set(threadId, transition);
|
|
1278
|
+
try {
|
|
1279
|
+
await transition.completion;
|
|
1280
|
+
if (runtime.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
|
|
1281
|
+
} finally {
|
|
1282
|
+
if (runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
physicalThreadReleases.set(release, retained.release);
|
|
1286
|
+
claimedThreadReleaseTokens.set(release, claimed);
|
|
1287
|
+
return {
|
|
1288
|
+
configFingerprint: retained.configFingerprint,
|
|
1289
|
+
serviceTier: retained.serviceTier,
|
|
1290
|
+
release
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
/** Distinguish active claimed ownership from an already-evicted idle subscription. */
|
|
1294
|
+
function isCodexAppServerLiveThreadClaimed(client, threadId) {
|
|
1295
|
+
const runtime = configuredClients.get(client);
|
|
1296
|
+
return runtime !== void 0 && !runtime.closed && runtime.claimedThreads.has(threadId);
|
|
1297
|
+
}
|
|
1298
|
+
/** Release the exact physical subscription and finish only its observed claim generation. */
|
|
1299
|
+
async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs) {
|
|
1300
|
+
const runtime = configuredClients.get(client);
|
|
1301
|
+
const claimed = runtime?.claimedThreads.get(threadId);
|
|
1302
|
+
let transition = runtime?.releasingThreads.get(threadId);
|
|
1303
|
+
if (transition?.physicalRelease) {
|
|
1304
|
+
await transition.physicalRelease;
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const physicalRelease = Promise.resolve().then(async () => {
|
|
1308
|
+
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed) return;
|
|
1309
|
+
await client.request("thread/unsubscribe", { threadId }, { timeoutMs });
|
|
1310
|
+
});
|
|
1311
|
+
const ownsTransition = runtime !== void 0 && transition === void 0;
|
|
1312
|
+
if (transition) transition.physicalRelease = physicalRelease;
|
|
1313
|
+
else if (runtime) {
|
|
1314
|
+
transition = {
|
|
1315
|
+
completion: physicalRelease,
|
|
1316
|
+
physicalRelease
|
|
1317
|
+
};
|
|
1318
|
+
runtime.releasingThreads.set(threadId, transition);
|
|
1319
|
+
}
|
|
1320
|
+
try {
|
|
1321
|
+
await physicalRelease;
|
|
1322
|
+
if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
|
|
1323
|
+
} finally {
|
|
1324
|
+
if (ownsTransition && runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
/** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
|
|
1328
|
+
async function releaseCodexAppServerLiveThread(client, threadId) {
|
|
1329
|
+
const runtime = configuredClients.get(client);
|
|
1330
|
+
return runtime ? await releaseRetainedThread(client, runtime, threadId) : false;
|
|
1331
|
+
}
|
|
1332
|
+
/** Native child work pins its parent's subscription even after the foreground parent turn ends. */
|
|
1333
|
+
function protectCodexAppServerLiveThread(client, threadId) {
|
|
1334
|
+
const runtime = configuredClients.get(client);
|
|
1335
|
+
if (!runtime || runtime.closed) return () => void 0;
|
|
1336
|
+
runtime.protectedThreads.set(threadId, (runtime.protectedThreads.get(threadId) ?? 0) + 1);
|
|
1337
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1338
|
+
let protectedThread = true;
|
|
1339
|
+
return () => {
|
|
1340
|
+
if (!protectedThread) return;
|
|
1341
|
+
protectedThread = false;
|
|
1342
|
+
if (runtime.closed) return;
|
|
1343
|
+
const count = runtime.protectedThreads.get(threadId) ?? 0;
|
|
1344
|
+
if (count <= 1) {
|
|
1345
|
+
runtime.protectedThreads.delete(threadId);
|
|
1346
|
+
const retained = runtime.retainedThreads.get(threadId);
|
|
1347
|
+
if (retained) {
|
|
1348
|
+
runtime.retainedThreads.delete(threadId);
|
|
1349
|
+
runtime.retainedThreads.set(threadId, {
|
|
1350
|
+
...retained,
|
|
1351
|
+
expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
} else runtime.protectedThreads.set(threadId, count - 1);
|
|
1355
|
+
scheduleRetainedThreadEviction(client, runtime);
|
|
1356
|
+
evictExcessIdleThreads(client, runtime).catch((error) => {
|
|
1357
|
+
embeddedAgentLog.warn("codex retained thread unpin eviction failed", {
|
|
1358
|
+
threadId,
|
|
1359
|
+
reason: formatErrorMessage(error)
|
|
1360
|
+
});
|
|
1361
|
+
});
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
//#endregion
|
|
1365
|
+
//#region extensions/codex/src/app-server/rpc-error.ts
|
|
1366
|
+
/** RPC error wrapper that preserves app-server error code and data. */
|
|
1367
|
+
var CodexAppServerRpcError = class extends Error {
|
|
1368
|
+
constructor(error, method) {
|
|
1369
|
+
super(formatCodexAppServerRpcErrorMessage(error, method));
|
|
1370
|
+
this.name = "CodexAppServerRpcError";
|
|
1371
|
+
this.code = error.code;
|
|
1372
|
+
this.data = error.data;
|
|
1373
|
+
this.method = method;
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
function formatCodexAppServerRpcErrorMessage(error, method) {
|
|
1377
|
+
const message = error.message || `${method} failed`;
|
|
1378
|
+
const detail = readCodexAppServerRpcReloginDetail(error.data);
|
|
1379
|
+
return detail && !message.includes(detail) ? `${message}: ${detail}` : message;
|
|
1380
|
+
}
|
|
1381
|
+
function readCodexAppServerRpcReloginDetail(data) {
|
|
1382
|
+
const record = isJsonObject(data) ? data : void 0;
|
|
1383
|
+
const nested = isJsonObject(record?.error) ? record.error : record;
|
|
1384
|
+
if (!nested) return;
|
|
1385
|
+
const isRelogin = nested.action === "relogin" || nested.reason === "cloudRequirements" && nested.errorCode === "Auth";
|
|
1386
|
+
const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
|
|
1387
|
+
return isRelogin && detail ? detail : void 0;
|
|
1388
|
+
}
|
|
1147
1389
|
//#endregion
|
|
1148
1390
|
//#region extensions/codex/src/app-server/transport-websocket.ts
|
|
1149
1391
|
/**
|
|
1150
1392
|
* Adapts a remote Codex app-server WebSocket endpoint to the shared stdio-like
|
|
1151
1393
|
* transport interface.
|
|
1152
1394
|
*/
|
|
1395
|
+
const WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
1396
|
+
const WEBSOCKET_PING_INTERVAL_MS = 2e4;
|
|
1397
|
+
const WEBSOCKET_PONG_TIMEOUT_MS = 2e4;
|
|
1398
|
+
const MAX_CONSECUTIVE_MISSED_WEBSOCKET_PONGS = 5;
|
|
1153
1399
|
/** Opens a WebSocket app-server transport and maps newline-delimited frames to stdout/stdin. */
|
|
1154
1400
|
function createWebSocketTransport(options) {
|
|
1155
1401
|
if (!options.url) throw new Error("codex app-server websocket transport requires plugins.entries.codex.config.appServer.url");
|
|
@@ -1161,7 +1407,8 @@ function createWebSocketTransport(options) {
|
|
|
1161
1407
|
...options.headers,
|
|
1162
1408
|
...options.authToken ? { Authorization: `Bearer ${options.authToken}` } : {}
|
|
1163
1409
|
},
|
|
1164
|
-
perMessageDeflate: false
|
|
1410
|
+
perMessageDeflate: false,
|
|
1411
|
+
...options.transport === "websocket" ? { handshakeTimeout: WEBSOCKET_HANDSHAKE_TIMEOUT_MS } : {}
|
|
1165
1412
|
};
|
|
1166
1413
|
const unixSocketPath = resolveCodexAppServerUnixSocketPath(options);
|
|
1167
1414
|
const socket = unixSocketPath ? new WebSocket("ws://localhost/", {
|
|
@@ -1172,6 +1419,58 @@ function createWebSocketTransport(options) {
|
|
|
1172
1419
|
const stdinDecoder = new StringDecoder("utf8");
|
|
1173
1420
|
let pendingLine = "";
|
|
1174
1421
|
let killed = false;
|
|
1422
|
+
let pingTimeout;
|
|
1423
|
+
let pongTimeout;
|
|
1424
|
+
let expectedPong;
|
|
1425
|
+
let consecutiveMissedPongs = 0;
|
|
1426
|
+
let heartbeatSequence = 0;
|
|
1427
|
+
const clearConnectionHealthTimers = () => {
|
|
1428
|
+
if (pingTimeout) {
|
|
1429
|
+
clearTimeout(pingTimeout);
|
|
1430
|
+
pingTimeout = void 0;
|
|
1431
|
+
}
|
|
1432
|
+
if (pongTimeout) {
|
|
1433
|
+
clearTimeout(pongTimeout);
|
|
1434
|
+
pongTimeout = void 0;
|
|
1435
|
+
}
|
|
1436
|
+
expectedPong = void 0;
|
|
1437
|
+
};
|
|
1438
|
+
const sendHeartbeatPing = () => {
|
|
1439
|
+
if (socket.readyState !== WebSocket.OPEN || pongTimeout) return;
|
|
1440
|
+
const payload = Buffer.from(`openclaw-codex-${++heartbeatSequence}`);
|
|
1441
|
+
expectedPong = payload;
|
|
1442
|
+
pongTimeout = setTimeout(() => {
|
|
1443
|
+
pongTimeout = void 0;
|
|
1444
|
+
expectedPong = void 0;
|
|
1445
|
+
consecutiveMissedPongs += 1;
|
|
1446
|
+
if (consecutiveMissedPongs >= MAX_CONSECUTIVE_MISSED_WEBSOCKET_PONGS) {
|
|
1447
|
+
socket.terminate();
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
sendHeartbeatPing();
|
|
1451
|
+
}, WEBSOCKET_PONG_TIMEOUT_MS);
|
|
1452
|
+
pongTimeout.unref();
|
|
1453
|
+
socket.ping(payload, void 0, (error) => {
|
|
1454
|
+
if (error) socket.terminate();
|
|
1455
|
+
});
|
|
1456
|
+
};
|
|
1457
|
+
const scheduleHeartbeatPing = () => {
|
|
1458
|
+
if (options.transport !== "websocket" || socket.readyState !== WebSocket.OPEN || pingTimeout || pongTimeout) return;
|
|
1459
|
+
pingTimeout = setTimeout(() => {
|
|
1460
|
+
pingTimeout = void 0;
|
|
1461
|
+
sendHeartbeatPing();
|
|
1462
|
+
}, WEBSOCKET_PING_INTERVAL_MS);
|
|
1463
|
+
pingTimeout.unref();
|
|
1464
|
+
};
|
|
1465
|
+
const recordConnectionActivity = () => {
|
|
1466
|
+
consecutiveMissedPongs = 0;
|
|
1467
|
+
if (pongTimeout) {
|
|
1468
|
+
clearTimeout(pongTimeout);
|
|
1469
|
+
pongTimeout = void 0;
|
|
1470
|
+
}
|
|
1471
|
+
expectedPong = void 0;
|
|
1472
|
+
scheduleHeartbeatPing();
|
|
1473
|
+
};
|
|
1175
1474
|
const sendFrame = (frame) => {
|
|
1176
1475
|
const trimmed = frame.trim();
|
|
1177
1476
|
if (!trimmed) return;
|
|
@@ -1183,13 +1482,22 @@ function createWebSocketTransport(options) {
|
|
|
1183
1482
|
};
|
|
1184
1483
|
socket.once("open", () => {
|
|
1185
1484
|
for (const frame of pendingFrames.splice(0)) socket.send(frame);
|
|
1485
|
+
scheduleHeartbeatPing();
|
|
1486
|
+
});
|
|
1487
|
+
socket.on("pong", (payload) => {
|
|
1488
|
+
if (expectedPong?.equals(payload)) recordConnectionActivity();
|
|
1489
|
+
});
|
|
1490
|
+
socket.once("error", (error) => {
|
|
1491
|
+
clearConnectionHealthTimers();
|
|
1492
|
+
events.emit("error", error);
|
|
1186
1493
|
});
|
|
1187
|
-
socket.once("error", (error) => events.emit("error", error));
|
|
1188
1494
|
socket.once("close", (code, reason) => {
|
|
1495
|
+
clearConnectionHealthTimers();
|
|
1189
1496
|
killed = true;
|
|
1190
1497
|
events.emit("exit", code, reason.toString("utf8"));
|
|
1191
1498
|
});
|
|
1192
1499
|
socket.on("message", (data) => {
|
|
1500
|
+
if (options.transport === "websocket") recordConnectionActivity();
|
|
1193
1501
|
const text = websocketFrameToText(data);
|
|
1194
1502
|
stdout.write(text.endsWith("\n") ? text : `${text}\n`);
|
|
1195
1503
|
});
|
|
@@ -1223,6 +1531,7 @@ function createWebSocketTransport(options) {
|
|
|
1223
1531
|
},
|
|
1224
1532
|
kill: () => {
|
|
1225
1533
|
killed = true;
|
|
1534
|
+
clearConnectionHealthTimers();
|
|
1226
1535
|
socket.close();
|
|
1227
1536
|
},
|
|
1228
1537
|
once: (event, listener) => events.once(event, listener)
|
|
@@ -1309,10 +1618,8 @@ function signalCodexAppServerTransport(child, signal) {
|
|
|
1309
1618
|
/**
|
|
1310
1619
|
* Version and package pins for the managed Codex app-server runtime.
|
|
1311
1620
|
*/
|
|
1312
|
-
/**
|
|
1313
|
-
const
|
|
1314
|
-
/** Newest Codex app-server version validated by the OpenClaw Codex bridge. */
|
|
1315
|
-
const MAX_CODEX_APP_SERVER_VERSION = "0.145.0";
|
|
1621
|
+
/** Exact Codex app-server version shipped and supported by the OpenClaw Codex bridge. */
|
|
1622
|
+
const CODEX_APP_SERVER_VERSION = "0.147.0";
|
|
1316
1623
|
/** npm package name for the managed Codex app-server binary. */
|
|
1317
1624
|
const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
|
|
1318
1625
|
//#endregion
|
|
@@ -1321,7 +1628,6 @@ const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
|
|
|
1321
1628
|
* JSON-RPC client for Codex app-server transports, including request/response
|
|
1322
1629
|
* routing, notification fanout, server request handlers, and version checks.
|
|
1323
1630
|
*/
|
|
1324
|
-
/** Minimum supported Codex app-server version exported for callers/tests. */
|
|
1325
1631
|
const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
|
|
1326
1632
|
const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 8 * 1024 * 1024;
|
|
1327
1633
|
const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1e3;
|
|
@@ -1343,16 +1649,6 @@ function getCodexAppServerClientInstanceId(client) {
|
|
|
1343
1649
|
function resolveCodexAppServerClientInstanceId(client) {
|
|
1344
1650
|
return client.getInstanceId?.call(client) ?? getCodexAppServerClientInstanceId(client);
|
|
1345
1651
|
}
|
|
1346
|
-
/** RPC error wrapper that preserves app-server error code and data. */
|
|
1347
|
-
var CodexAppServerRpcError = class extends Error {
|
|
1348
|
-
constructor(error, method) {
|
|
1349
|
-
super(formatCodexAppServerRpcErrorMessage(error, method));
|
|
1350
|
-
this.name = "CodexAppServerRpcError";
|
|
1351
|
-
this.code = error.code;
|
|
1352
|
-
this.data = error.data;
|
|
1353
|
-
this.method = method;
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
1652
|
var CodexAppServerLocalRequestCancellationError = class extends Error {
|
|
1357
1653
|
constructor(method, reason, mayHaveWritten) {
|
|
1358
1654
|
super(`${method} ${reason}`);
|
|
@@ -1395,22 +1691,6 @@ function isCodexAppServerPrewriteRequestCancellationError(error) {
|
|
|
1395
1691
|
function isCodexAppServerIndeterminateTransportError(error) {
|
|
1396
1692
|
return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_REQUEST_TRANSPORT_INDETERMINATE" && "mayHaveWritten" in error && error.mayHaveWritten === true;
|
|
1397
1693
|
}
|
|
1398
|
-
function formatCodexAppServerRpcErrorMessage(error, method) {
|
|
1399
|
-
const message = error.message || `${method} failed`;
|
|
1400
|
-
const detail = readCodexAppServerRpcReloginDetail(error.data);
|
|
1401
|
-
return detail && !message.includes(detail) ? `${message}: ${detail}` : message;
|
|
1402
|
-
}
|
|
1403
|
-
function readCodexAppServerRpcReloginDetail(data) {
|
|
1404
|
-
const record = isJsonObject(data) ? data : void 0;
|
|
1405
|
-
const nested = isJsonObject(record?.error) ? record.error : record;
|
|
1406
|
-
if (!nested) return;
|
|
1407
|
-
const isRelogin = nested.action === "relogin" || nested.reason === "cloudRequirements" && nested.errorCode === "Auth";
|
|
1408
|
-
const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
|
|
1409
|
-
return isRelogin && detail ? detail : void 0;
|
|
1410
|
-
}
|
|
1411
|
-
function isJsonObject(value) {
|
|
1412
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1413
|
-
}
|
|
1414
1694
|
/** Returns true for errors that mean the app-server transport is closed. */
|
|
1415
1695
|
function isCodexAppServerConnectionClosedError(error) {
|
|
1416
1696
|
if (!(error instanceof Error)) return false;
|
|
@@ -1433,8 +1713,8 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1433
1713
|
this.child = child;
|
|
1434
1714
|
this.lines = createInterface({ input: child.stdout });
|
|
1435
1715
|
this.lines.on("line", (line) => this.handleLine(line));
|
|
1436
|
-
this.lines.on("error", (error) => this.closeWithError(
|
|
1437
|
-
child.stdout.on("error", (error) => this.closeWithError(
|
|
1716
|
+
this.lines.on("error", (error) => this.closeWithError(toStringifiedError(error)));
|
|
1717
|
+
child.stdout.on("error", (error) => this.closeWithError(toStringifiedError(error)));
|
|
1438
1718
|
child.stderr.setEncoding("utf8");
|
|
1439
1719
|
child.stderr.on("data", (text) => {
|
|
1440
1720
|
this.stderrTail = appendBoundedTail(this.stderrTail, text, CODEX_APP_SERVER_STDERR_TAIL_MAX);
|
|
@@ -1444,12 +1724,12 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1444
1724
|
child.stderr.on("error", (error) => {
|
|
1445
1725
|
embeddedAgentLog.warn("codex app-server stderr stream failed", { error });
|
|
1446
1726
|
});
|
|
1447
|
-
child.once("error", (error) => this.closeWithError(
|
|
1727
|
+
child.once("error", (error) => this.closeWithError(toStringifiedError(error)));
|
|
1448
1728
|
child.once("exit", (code, signal) => {
|
|
1449
1729
|
this.transportExited = true;
|
|
1450
1730
|
this.closeWithError(buildCodexAppServerExitError(code, signal, this.stderrTail));
|
|
1451
1731
|
});
|
|
1452
|
-
child.stdin.on?.("error", (error) => this.closeWithError(
|
|
1732
|
+
child.stdin.on?.("error", (error) => this.closeWithError(toStringifiedError(error)));
|
|
1453
1733
|
}
|
|
1454
1734
|
/** Starts a new app-server client using resolved runtime start options. */
|
|
1455
1735
|
static start(options) {
|
|
@@ -1491,6 +1771,14 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1491
1771
|
getRuntimeIdentity() {
|
|
1492
1772
|
return this.runtimeIdentity ? { ...this.runtimeIdentity } : void 0;
|
|
1493
1773
|
}
|
|
1774
|
+
/** Returns a bounded, redacted stderr diagnostic from the app-server process. */
|
|
1775
|
+
getStderrDiagnostic() {
|
|
1776
|
+
return redactCodexAppServerLinePreview(this.stderrTail) || void 0;
|
|
1777
|
+
}
|
|
1778
|
+
/** Returns the terminal transport error that closed this physical client. */
|
|
1779
|
+
getCloseError() {
|
|
1780
|
+
return this.closeError;
|
|
1781
|
+
}
|
|
1494
1782
|
/** Stable generation id for this exact physical client instance. */
|
|
1495
1783
|
getInstanceId() {
|
|
1496
1784
|
return this.instanceId;
|
|
@@ -1660,7 +1948,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1660
1948
|
onWriteAttempt?.();
|
|
1661
1949
|
this.writeMessage(message, (error) => rejectPending(error));
|
|
1662
1950
|
} catch (error) {
|
|
1663
|
-
rejectPending(
|
|
1951
|
+
rejectPending(toStringifiedError(error));
|
|
1664
1952
|
}
|
|
1665
1953
|
});
|
|
1666
1954
|
}
|
|
@@ -1831,7 +2119,7 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1831
2119
|
result: defaultServerRequestResponse(request)
|
|
1832
2120
|
});
|
|
1833
2121
|
} catch (error) {
|
|
1834
|
-
const message =
|
|
2122
|
+
const message = coerceErrorMessage(error);
|
|
1835
2123
|
embeddedAgentLog.warn("codex app-server server request handler failed", {
|
|
1836
2124
|
id: request.id,
|
|
1837
2125
|
method: request.method,
|
|
@@ -1876,9 +2164,13 @@ var CodexAppServerClient = class CodexAppServerClient {
|
|
|
1876
2164
|
}
|
|
1877
2165
|
}
|
|
1878
2166
|
handleNotification(notification) {
|
|
1879
|
-
for (const handler of this.notificationHandlers)
|
|
2167
|
+
for (const handler of this.notificationHandlers) try {
|
|
2168
|
+
Promise.resolve(handler(notification)).catch((error) => {
|
|
2169
|
+
embeddedAgentLog.warn("codex app-server notification handler failed", { error });
|
|
2170
|
+
});
|
|
2171
|
+
} catch (error) {
|
|
1880
2172
|
embeddedAgentLog.warn("codex app-server notification handler failed", { error });
|
|
1881
|
-
}
|
|
2173
|
+
}
|
|
1882
2174
|
}
|
|
1883
2175
|
closeWithError(error) {
|
|
1884
2176
|
if (this.markClosed(error)) closeCodexAppServerTransport(this.child);
|
|
@@ -1934,25 +2226,24 @@ function timeoutServerRequestResponse(request) {
|
|
|
1934
2226
|
var CodexAppServerVersionError = class extends Error {
|
|
1935
2227
|
constructor(detectedVersion) {
|
|
1936
2228
|
const detected = detectedVersion ? `detected ${detectedVersion}` : "OpenClaw could not determine the running Codex version";
|
|
1937
|
-
super(`
|
|
2229
|
+
super(`Codex app-server ${CODEX_APP_SERVER_VERSION} is required, but ${detected}. Update the configured Codex app-server binary, or remove custom command overrides to use the managed binary.`);
|
|
1938
2230
|
this.name = "CodexAppServerVersionError";
|
|
1939
2231
|
this.detectedVersion = detectedVersion;
|
|
1940
2232
|
}
|
|
1941
2233
|
};
|
|
1942
2234
|
function assertSupportedCodexAppServerVersion(response) {
|
|
1943
2235
|
const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
|
|
1944
|
-
|
|
1945
|
-
if (!detectedVersion || !parsedVersion || parsedVersion.compare("0.143.0") < 0 || parsedVersion.compare("0.145.0") > 0 || parsedVersion.prerelease.length > 0 || parsedVersion.build.length > 0) throw new CodexAppServerVersionError(detectedVersion);
|
|
2236
|
+
if (detectedVersion !== "0.147.0") throw new CodexAppServerVersionError(detectedVersion);
|
|
1946
2237
|
return detectedVersion;
|
|
1947
2238
|
}
|
|
1948
2239
|
function isUnsupportedCodexAppServerVersionError(error) {
|
|
1949
2240
|
return error instanceof CodexAppServerVersionError;
|
|
1950
2241
|
}
|
|
1951
2242
|
function buildCodexAppServerRuntimeIdentity(response, serverVersion) {
|
|
1952
|
-
const userAgent =
|
|
1953
|
-
const codexHome =
|
|
1954
|
-
const platformFamily =
|
|
1955
|
-
const platformOs =
|
|
2243
|
+
const userAgent = normalizeOptionalString(response.userAgent);
|
|
2244
|
+
const codexHome = normalizeOptionalString(response.codexHome);
|
|
2245
|
+
const platformFamily = normalizeOptionalString(response.platformFamily);
|
|
2246
|
+
const platformOs = normalizeOptionalString(response.platformOs);
|
|
1956
2247
|
return {
|
|
1957
2248
|
serverVersion,
|
|
1958
2249
|
...userAgent ? { userAgent } : {},
|
|
@@ -1961,10 +2252,6 @@ function buildCodexAppServerRuntimeIdentity(response, serverVersion) {
|
|
|
1961
2252
|
...platformOs ? { platformOs } : {}
|
|
1962
2253
|
};
|
|
1963
2254
|
}
|
|
1964
|
-
function readNonEmptyInitializeString(value) {
|
|
1965
|
-
const trimmed = value?.trim();
|
|
1966
|
-
return trimmed ? trimmed : void 0;
|
|
1967
|
-
}
|
|
1968
2255
|
/** Extracts the Codex version from the app-server initialize user-agent field. */
|
|
1969
2256
|
function readCodexVersionFromUserAgent(userAgent) {
|
|
1970
2257
|
return (userAgent?.match(/^[^/]+\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)(?:[\s(]|$)/))?.[1];
|
|
@@ -1984,7 +2271,7 @@ function buildCodexAppServerExitError(code, signal, stderrTail) {
|
|
|
1984
2271
|
}
|
|
1985
2272
|
function shouldBufferCodexAppServerParseFailure(value, error) {
|
|
1986
2273
|
if (!value.startsWith("{") && !value.startsWith("[")) return false;
|
|
1987
|
-
const message =
|
|
2274
|
+
const message = coerceErrorMessage(error);
|
|
1988
2275
|
return message.includes("Unterminated string") || message.includes("Unexpected end of JSON input");
|
|
1989
2276
|
}
|
|
1990
2277
|
function logCodexAppServerParseFailure(value, error, fragmentCount) {
|
|
@@ -1992,7 +2279,7 @@ function logCodexAppServerParseFailure(value, error, fragmentCount) {
|
|
|
1992
2279
|
const suffix = fragmentCount > 1 ? ` fragments=${fragmentCount}` : "";
|
|
1993
2280
|
embeddedAgentLog.warn("failed to parse codex app-server message", {
|
|
1994
2281
|
error,
|
|
1995
|
-
errorMessage:
|
|
2282
|
+
errorMessage: coerceErrorMessage(error),
|
|
1996
2283
|
fragmentCount,
|
|
1997
2284
|
linePreview,
|
|
1998
2285
|
consoleMessage: `failed to parse codex app-server message${suffix}: preview=${JSON.stringify(linePreview)}`
|
|
@@ -2013,6 +2300,103 @@ function formatExitValue(value) {
|
|
|
2013
2300
|
return "unknown";
|
|
2014
2301
|
}
|
|
2015
2302
|
//#endregion
|
|
2303
|
+
//#region extensions/codex/src/app-server/attempt-timeouts.ts
|
|
2304
|
+
/**
|
|
2305
|
+
* Timeout defaults and normalizers for Codex app-server startup and turn
|
|
2306
|
+
* liveness watches.
|
|
2307
|
+
*/
|
|
2308
|
+
/** Minimum startup timeout accepted by the Codex app-server harness. */
|
|
2309
|
+
const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
|
|
2310
|
+
/** Default idle timeout while waiting for app-server turn completion. */
|
|
2311
|
+
const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
|
|
2312
|
+
/** Short guard after apparent assistant completion. */
|
|
2313
|
+
const CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 1e4;
|
|
2314
|
+
const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
2315
|
+
/** Guard after reasoning/commentary progress when no tool handoff occurred. */
|
|
2316
|
+
const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 5 * 6e4;
|
|
2317
|
+
/** Long terminal idle watch for app-server turns that never send completion. */
|
|
2318
|
+
const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
|
|
2319
|
+
var CodexAppServerStartupError = class extends Error {
|
|
2320
|
+
constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
|
|
2321
|
+
super(message);
|
|
2322
|
+
this.reason = reason;
|
|
2323
|
+
this.code = "CODEX_APP_SERVER_STARTUP_CANCELLED";
|
|
2324
|
+
this.name = "CodexAppServerStartupError";
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
function isCodexAppServerStartupError(error, reason) {
|
|
2328
|
+
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);
|
|
2329
|
+
}
|
|
2330
|
+
function resolvePositiveIntegerTimeoutMs(value, fallbackMs) {
|
|
2331
|
+
return resolveTimerTimeoutMs(value, resolveTimerTimeoutMs(fallbackMs, 1));
|
|
2332
|
+
}
|
|
2333
|
+
/** Runs startup work with abort and timeout handling plus optional cleanup. */
|
|
2334
|
+
async function withCodexStartupTimeout(params) {
|
|
2335
|
+
if (params.signal.aborted) throw new CodexAppServerStartupError("aborted");
|
|
2336
|
+
let timeout;
|
|
2337
|
+
let abortCleanup;
|
|
2338
|
+
let timeoutError;
|
|
2339
|
+
let timeoutCleanup;
|
|
2340
|
+
try {
|
|
2341
|
+
return await Promise.race([params.operation(), new Promise((_, reject) => {
|
|
2342
|
+
const rejectOnce = (error) => {
|
|
2343
|
+
if (timeout) {
|
|
2344
|
+
clearTimeout(timeout);
|
|
2345
|
+
timeout = void 0;
|
|
2346
|
+
}
|
|
2347
|
+
reject(error);
|
|
2348
|
+
};
|
|
2349
|
+
timeout = setTimeout(() => {
|
|
2350
|
+
timeoutError = new CodexAppServerStartupError("timed_out");
|
|
2351
|
+
timeoutCleanup = Promise.resolve(params.onTimeout?.()).then(() => void 0, () => void 0);
|
|
2352
|
+
timeoutCleanup.finally(() => {
|
|
2353
|
+
rejectOnce(timeoutError);
|
|
2354
|
+
});
|
|
2355
|
+
}, params.timeoutMs);
|
|
2356
|
+
const abortListener = () => rejectOnce(new CodexAppServerStartupError("aborted"));
|
|
2357
|
+
params.signal.addEventListener("abort", abortListener, { once: true });
|
|
2358
|
+
abortCleanup = () => params.signal.removeEventListener("abort", abortListener);
|
|
2359
|
+
})]);
|
|
2360
|
+
} catch (error) {
|
|
2361
|
+
if (timeoutError) {
|
|
2362
|
+
await timeoutCleanup;
|
|
2363
|
+
throw timeoutError;
|
|
2364
|
+
}
|
|
2365
|
+
throw error;
|
|
2366
|
+
} finally {
|
|
2367
|
+
if (timeout) clearTimeout(timeout);
|
|
2368
|
+
abortCleanup?.();
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
/** Resolves startup timeout while honoring the configured floor. */
|
|
2372
|
+
function resolveCodexStartupTimeoutMs(params) {
|
|
2373
|
+
const timeoutFloorMs = resolvePositiveIntegerTimeoutMs(params.timeoutFloorMs, CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS);
|
|
2374
|
+
const timeoutMs = resolvePositiveIntegerTimeoutMs(params.timeoutMs, timeoutFloorMs);
|
|
2375
|
+
return Math.max(timeoutFloorMs, timeoutMs);
|
|
2376
|
+
}
|
|
2377
|
+
/** Resolves the completion-idle timeout for an active turn. */
|
|
2378
|
+
function resolveCodexTurnCompletionIdleTimeoutMs(value) {
|
|
2379
|
+
return resolvePositiveIntegerTimeoutMs(value, CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS);
|
|
2380
|
+
}
|
|
2381
|
+
/** Resolves the short assistant-completion release timeout. */
|
|
2382
|
+
function resolveCodexTurnAssistantCompletionIdleTimeoutMs(value) {
|
|
2383
|
+
return resolvePositiveIntegerTimeoutMs(value, CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS);
|
|
2384
|
+
}
|
|
2385
|
+
/** Resolves the conservative post-tool raw assistant guard timeout. */
|
|
2386
|
+
function resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs(value, fallbackMs) {
|
|
2387
|
+
return resolvePositiveIntegerTimeoutMs(value, Math.max(resolvePositiveIntegerTimeoutMs(void 0, fallbackMs), CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS));
|
|
2388
|
+
}
|
|
2389
|
+
/** Resolves the long terminal turn idle timeout. */
|
|
2390
|
+
function resolveCodexTurnTerminalIdleTimeoutMs(value, runTimeoutOverrideMs) {
|
|
2391
|
+
const explicitRunBudgetMs = resolvePositiveIntegerTimeoutMs(runTimeoutOverrideMs, CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS);
|
|
2392
|
+
return resolvePositiveIntegerTimeoutMs(value, Math.max(CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS, explicitRunBudgetMs));
|
|
2393
|
+
}
|
|
2394
|
+
/** Adds gateway grace time to a caller timeout without overflowing invalid values. */
|
|
2395
|
+
function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
|
|
2396
|
+
const timeout = resolvePositiveIntegerTimeoutMs(timeoutMs, 1);
|
|
2397
|
+
return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
|
|
2398
|
+
}
|
|
2399
|
+
//#endregion
|
|
2016
2400
|
//#region extensions/codex/src/app-server/managed-binary.ts
|
|
2017
2401
|
/**
|
|
2018
2402
|
* Resolves the managed Codex app-server binary shipped with or installed beside
|
|
@@ -2178,15 +2562,13 @@ function resolveManagedCodexPackageBinCandidate(root) {
|
|
|
2178
2562
|
const packageJsonPath = createRequire(path.join(root, "package.json")).resolve(`${MANAGED_CODEX_APP_SERVER_PACKAGE}/package.json`);
|
|
2179
2563
|
const packageRoot = path.dirname(packageJsonPath);
|
|
2180
2564
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
2181
|
-
const
|
|
2565
|
+
const packageBin = packageJson.bin && typeof packageJson.bin === "object" ? packageJson.bin : void 0;
|
|
2566
|
+
const binPath = typeof packageJson.bin === "string" ? packageJson.bin : typeof packageBin?.codex === "string" ? packageBin.codex : null;
|
|
2182
2567
|
return binPath ? path.resolve(packageRoot, binPath) : null;
|
|
2183
2568
|
} catch {
|
|
2184
2569
|
return null;
|
|
2185
2570
|
}
|
|
2186
2571
|
}
|
|
2187
|
-
function isRecord(value) {
|
|
2188
|
-
return typeof value === "object" && value !== null;
|
|
2189
|
-
}
|
|
2190
2572
|
function isDistExtensionRoot(pluginRoot, platform) {
|
|
2191
2573
|
const pathApi = pathForPlatform(platform);
|
|
2192
2574
|
const extensionsDir = pathApi.dirname(pluginRoot);
|
|
@@ -2282,6 +2664,9 @@ async function waitForPreviousFence(previous, options) {
|
|
|
2282
2664
|
* Thin Codex app-server timeout adapter around OpenClaw's shared security
|
|
2283
2665
|
* runtime timeout helper.
|
|
2284
2666
|
*/
|
|
2667
|
+
function resolveAbortError(signal) {
|
|
2668
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
|
|
2669
|
+
}
|
|
2285
2670
|
/** Awaits a promise with a Codex-specific timeout error message. */
|
|
2286
2671
|
async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
|
|
2287
2672
|
return await withTimeout(promise, timeoutMs, {
|
|
@@ -2289,6 +2674,22 @@ async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
|
|
|
2289
2674
|
...createError ? { createError } : {}
|
|
2290
2675
|
});
|
|
2291
2676
|
}
|
|
2677
|
+
/** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
|
|
2678
|
+
async function withAbortableTimeout(params) {
|
|
2679
|
+
const signal = params.signal;
|
|
2680
|
+
if (signal?.aborted) throw resolveAbortError(signal);
|
|
2681
|
+
let removeAbortListener;
|
|
2682
|
+
const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
|
|
2683
|
+
const onAbort = () => reject(resolveAbortError(signal));
|
|
2684
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2685
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
2686
|
+
})]) : params.promise;
|
|
2687
|
+
try {
|
|
2688
|
+
return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
|
|
2689
|
+
} finally {
|
|
2690
|
+
removeAbortListener?.();
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2292
2693
|
//#endregion
|
|
2293
2694
|
//#region extensions/codex/src/app-server/shared-client.ts
|
|
2294
2695
|
/**
|
|
@@ -2309,11 +2710,12 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
|
|
|
2309
2710
|
releaseLeasedSharedCodexAppServerClient: () => releaseLeasedSharedCodexAppServerClient,
|
|
2310
2711
|
resolveCodexAppServerSpawnIdentity: () => resolveCodexAppServerSpawnIdentity,
|
|
2311
2712
|
resolveCodexNativeConfigFenceKey: () => resolveCodexNativeConfigFenceKey,
|
|
2713
|
+
retainSharedCodexAppServerClientByInstanceId: () => retainSharedCodexAppServerClientByInstanceId,
|
|
2312
2714
|
retainSharedCodexAppServerClientIfCurrent: () => retainSharedCodexAppServerClientIfCurrent,
|
|
2313
2715
|
retireSharedCodexAppServerClientIfCurrent: () => retireSharedCodexAppServerClientIfCurrent,
|
|
2314
2716
|
withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
|
|
2315
2717
|
});
|
|
2316
|
-
const
|
|
2718
|
+
const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
|
|
2317
2719
|
const SHARED_CODEX_APP_SERVER_CLIENT_STATE = Symbol.for("openclaw.codexAppServerClientState");
|
|
2318
2720
|
const SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER = Symbol.for("openclaw.codexAppServerClientDisposer");
|
|
2319
2721
|
const CODEX_APP_SERVER_CLIENT_START_METADATA = Symbol.for("openclaw.codexAppServerClientStartMetadata");
|
|
@@ -2321,6 +2723,8 @@ function getSharedCodexAppServerClientState() {
|
|
|
2321
2723
|
const globalState = globalThis;
|
|
2322
2724
|
globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
|
|
2323
2725
|
clients: /* @__PURE__ */ new Map(),
|
|
2726
|
+
liveClients: /* @__PURE__ */ new Set(),
|
|
2727
|
+
entriesByClient: /* @__PURE__ */ new WeakMap(),
|
|
2324
2728
|
leasedReleases: /* @__PURE__ */ new WeakMap()
|
|
2325
2729
|
};
|
|
2326
2730
|
return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
|
|
@@ -2435,15 +2839,55 @@ async function resolveCodexAppServerClientStartContext(options) {
|
|
|
2435
2839
|
startOptions: requestedStartOptions,
|
|
2436
2840
|
agentDir
|
|
2437
2841
|
})),
|
|
2842
|
+
agentId: options?.agentId,
|
|
2438
2843
|
agentDir,
|
|
2439
2844
|
authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
|
|
2440
2845
|
...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
|
|
2846
|
+
authRequirement,
|
|
2441
2847
|
config: options?.config,
|
|
2442
2848
|
pluginConfig: options?.pluginConfig,
|
|
2443
2849
|
...authProfileStore ? { authProfileStore } : {}
|
|
2444
2850
|
})
|
|
2445
2851
|
};
|
|
2446
2852
|
}
|
|
2853
|
+
function resolveWarmSharedCodexAppServerClientIdentity(options) {
|
|
2854
|
+
if (!options?.config || typeof options.config !== "object" || !options.startOptions || options.pluginConfig !== void 0 || options.authProfileStore !== void 0 || options.preparedAuth !== void 0 || options.runtimeArtifactMode !== void 0 || options.expectedRuntimeArtifact !== void 0) return;
|
|
2855
|
+
const authProfileSelector = options.authProfileId === null ? ["native"] : options.authProfileId === void 0 ? ["implicit"] : ["profile", options.authProfileId];
|
|
2856
|
+
return {
|
|
2857
|
+
config: options.config,
|
|
2858
|
+
startOptions: options.startOptions,
|
|
2859
|
+
selectorKey: JSON.stringify([
|
|
2860
|
+
options.agentDir ?? resolveDefaultAgentDir(options.config),
|
|
2861
|
+
authProfileSelector,
|
|
2862
|
+
options.authBindingFingerprint ?? null,
|
|
2863
|
+
options.authRequirement ?? null
|
|
2864
|
+
])
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
function readWarmSharedCodexAppServerClient(state, identity) {
|
|
2868
|
+
const aliases = state.warmClientsByConfig?.get(identity.config)?.get(identity.startOptions);
|
|
2869
|
+
const warm = aliases?.get(identity.selectorKey);
|
|
2870
|
+
if (!warm) return;
|
|
2871
|
+
if (state.clients.get(warm.key) !== warm.entry || warm.entry.client !== warm.client || warm.entry.closeWhenIdle || warm.entry.closeError) {
|
|
2872
|
+
aliases?.delete(identity.selectorKey);
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
return warm;
|
|
2876
|
+
}
|
|
2877
|
+
function rememberWarmSharedCodexAppServerClient(state, identity, warm) {
|
|
2878
|
+
state.warmClientsByConfig ??= /* @__PURE__ */ new WeakMap();
|
|
2879
|
+
let byStartOptions = state.warmClientsByConfig.get(identity.config);
|
|
2880
|
+
if (!byStartOptions) {
|
|
2881
|
+
byStartOptions = /* @__PURE__ */ new WeakMap();
|
|
2882
|
+
state.warmClientsByConfig.set(identity.config, byStartOptions);
|
|
2883
|
+
}
|
|
2884
|
+
let aliases = byStartOptions.get(identity.startOptions);
|
|
2885
|
+
if (!aliases) {
|
|
2886
|
+
aliases = /* @__PURE__ */ new Map();
|
|
2887
|
+
byStartOptions.set(identity.startOptions, aliases);
|
|
2888
|
+
}
|
|
2889
|
+
aliases.set(identity.selectorKey, warm);
|
|
2890
|
+
}
|
|
2447
2891
|
/** Gets or starts a shared Codex app-server client without retaining a lease. */
|
|
2448
2892
|
async function getSharedCodexAppServerClient(options) {
|
|
2449
2893
|
return (await acquireSharedCodexAppServerClient(options)).client;
|
|
@@ -2513,6 +2957,17 @@ async function withLeasedCodexAppServerClientStartSelectionRetry(params) {
|
|
|
2513
2957
|
}
|
|
2514
2958
|
async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
2515
2959
|
if (options?.abandonSignal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
2960
|
+
const state = getSharedCodexAppServerClientState();
|
|
2961
|
+
const warmIdentity = resolveWarmSharedCodexAppServerClientIdentity(options);
|
|
2962
|
+
const warmClient = warmIdentity ? readWarmSharedCodexAppServerClient(state, warmIdentity) : void 0;
|
|
2963
|
+
if (warmClient) {
|
|
2964
|
+
options?.onStartedClient?.(warmClient.client);
|
|
2965
|
+
const release = leaseOptions?.leased ? retainSharedClientEntry(warmClient.entry) : void 0;
|
|
2966
|
+
return release ? {
|
|
2967
|
+
client: warmClient.client,
|
|
2968
|
+
release
|
|
2969
|
+
} : { client: warmClient.client };
|
|
2970
|
+
}
|
|
2516
2971
|
const acquireStartedAt = Date.now();
|
|
2517
2972
|
const timeoutMs = options?.timeoutMs ?? 0;
|
|
2518
2973
|
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, preparedAuth, authRequirement, requestedStartOptions, startOptions } = await withCodexAppServerAcquireDeadline(timeoutMs, resolveCodexAppServerClientStartContext(options), options?.abandonSignal);
|
|
@@ -2527,7 +2982,7 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
2527
2982
|
const runtimeArtifactMode = options?.runtimeArtifactMode ?? (options?.expectedRuntimeArtifact ? "capture" : void 0);
|
|
2528
2983
|
const expectedRuntimeArtifactKey = options?.expectedRuntimeArtifact ? createHash("sha256").update(options.expectedRuntimeArtifact.id).update("\0").update(options.expectedRuntimeArtifact.fingerprint).digest("hex") : "mint";
|
|
2529
2984
|
const key = runtimeArtifactMode ? `${baseKey}\0runtime-artifact:capture-v1:${expectedRuntimeArtifactKey}` : baseKey;
|
|
2530
|
-
const entry = getOrCreateSharedClientEntry(
|
|
2985
|
+
const entry = getOrCreateSharedClientEntry(state, key);
|
|
2531
2986
|
if (runtimeArtifactMode) entry.runtimeArtifactStartupAbort ??= new AbortController();
|
|
2532
2987
|
entry.closeWhenIdle = false;
|
|
2533
2988
|
const releasePendingAcquire = retainPendingSharedClientAcquire(entry);
|
|
@@ -2566,7 +3021,7 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
2566
3021
|
config: options?.config
|
|
2567
3022
|
}));
|
|
2568
3023
|
try {
|
|
2569
|
-
await withCodexAppServerAcquireDeadline(remainingTimeoutMs, startup.initialized, options?.abandonSignal);
|
|
3024
|
+
await withCodexAppServerAcquireDeadline(remainingTimeoutMs, startup.initialized, options?.abandonSignal, CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE, () => buildCodexAppServerInitializeTimeoutError(entry.client));
|
|
2570
3025
|
const client = await withCodexAppServerAcquireDeadline(timeoutMs, startup.ready, options?.abandonSignal, "codex app-server authentication timed out");
|
|
2571
3026
|
if (entry.closeError) throw entry.closeError;
|
|
2572
3027
|
ensureCodexAppServerClientRuntime(client, {
|
|
@@ -2577,6 +3032,11 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
2577
3032
|
config: options?.config
|
|
2578
3033
|
});
|
|
2579
3034
|
const release = leaseOptions?.leased ? retainSharedClientEntry(entry) : void 0;
|
|
3035
|
+
if (warmIdentity) rememberWarmSharedCodexAppServerClient(state, warmIdentity, {
|
|
3036
|
+
key,
|
|
3037
|
+
entry,
|
|
3038
|
+
client
|
|
3039
|
+
});
|
|
2580
3040
|
return release ? {
|
|
2581
3041
|
client,
|
|
2582
3042
|
release
|
|
@@ -2591,9 +3051,9 @@ async function acquireSharedCodexAppServerClient(options, leaseOptions) {
|
|
|
2591
3051
|
releasePendingAcquire();
|
|
2592
3052
|
}
|
|
2593
3053
|
}
|
|
2594
|
-
async function withCodexAppServerAcquireDeadline(timeoutMs, promise, signal, timeoutMessage =
|
|
3054
|
+
async function withCodexAppServerAcquireDeadline(timeoutMs, promise, signal, timeoutMessage = CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE, timeoutErrorFactory) {
|
|
2595
3055
|
if (signal?.aborted) throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
|
2596
|
-
const timed = withTimeout$1(promise, timeoutMs, timeoutMessage, () => new CodexAppServerStartupError("timed_out", timeoutMessage));
|
|
3056
|
+
const timed = withTimeout$1(promise, timeoutMs, timeoutMessage, () => timeoutErrorFactory?.() ?? new CodexAppServerStartupError("timed_out", timeoutMessage));
|
|
2597
3057
|
if (!signal) return await timed;
|
|
2598
3058
|
return await new Promise((resolve, reject) => {
|
|
2599
3059
|
const onAbort = () => reject(new CodexAppServerStartupError("aborted", "codex app-server initialize aborted"));
|
|
@@ -2601,6 +3061,10 @@ async function withCodexAppServerAcquireDeadline(timeoutMs, promise, signal, tim
|
|
|
2601
3061
|
timed.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
2602
3062
|
});
|
|
2603
3063
|
}
|
|
3064
|
+
function buildCodexAppServerInitializeTimeoutError(client) {
|
|
3065
|
+
const stderr = client?.getStderrDiagnostic();
|
|
3066
|
+
return new CodexAppServerStartupError("timed_out", stderr ? `${CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE}; stderr=${JSON.stringify(stderr)}` : CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE);
|
|
3067
|
+
}
|
|
2604
3068
|
function resolveRemainingAcquireTimeout(timeoutMs, startedAt) {
|
|
2605
3069
|
if (!(timeoutMs > 0)) return timeoutMs;
|
|
2606
3070
|
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
@@ -2622,14 +3086,20 @@ function createSharedCodexAppServerClientStartup(params) {
|
|
|
2622
3086
|
runtimeArtifactSignal: params.runtimeArtifactSignal,
|
|
2623
3087
|
config: params.config,
|
|
2624
3088
|
onStartedClient: (startedClient) => {
|
|
3089
|
+
const state = getSharedCodexAppServerClientState();
|
|
2625
3090
|
params.entry.client = startedClient;
|
|
3091
|
+
state.entriesByClient.set(startedClient, params.entry);
|
|
3092
|
+
state.liveClients.add(startedClient);
|
|
3093
|
+
startedClient.addCloseHandler((closedClient) => {
|
|
3094
|
+
getSharedCodexAppServerClientState().liveClients.delete(closedClient);
|
|
3095
|
+
clearSharedClientEntryIfCurrent(params.key, closedClient);
|
|
3096
|
+
});
|
|
2626
3097
|
for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
|
|
2627
3098
|
retirePendingSharedClientEntryIfUnclaimed(params.key, params.entry);
|
|
2628
3099
|
},
|
|
2629
3100
|
onInitializedClient: () => initialized.resolve()
|
|
2630
3101
|
}).then((client) => {
|
|
2631
3102
|
params.entry.client = client;
|
|
2632
|
-
client.addCloseHandler((closedClient) => clearSharedClientEntryIfCurrent(params.key, closedClient));
|
|
2633
3103
|
return client;
|
|
2634
3104
|
}, (error) => {
|
|
2635
3105
|
initialized.reject(error);
|
|
@@ -2669,7 +3139,7 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
2669
3139
|
const timeoutMs = params.timeoutMs ?? 0;
|
|
2670
3140
|
const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
|
|
2671
3141
|
for (const [index, startOptions] of startOptionsCandidates.entries()) {
|
|
2672
|
-
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-
|
|
3142
|
+
const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BVSPdd6g.js") : void 0;
|
|
2673
3143
|
const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
|
|
2674
3144
|
const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
|
|
2675
3145
|
startOptions,
|
|
@@ -2684,7 +3154,7 @@ async function startInitializedCodexAppServerClient(params) {
|
|
|
2684
3154
|
params.onStartedClient?.(client);
|
|
2685
3155
|
let initialize;
|
|
2686
3156
|
try {
|
|
2687
|
-
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), initialize = client.initialize(), params.abandonSignal);
|
|
3157
|
+
await withCodexAppServerAcquireDeadline(resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt), initialize = client.initialize(), params.abandonSignal, CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE, () => buildCodexAppServerInitializeTimeoutError(client));
|
|
2688
3158
|
} catch (error) {
|
|
2689
3159
|
client.close();
|
|
2690
3160
|
initialize?.catch(() => void 0);
|
|
@@ -2789,6 +3259,19 @@ function retainSharedCodexAppServerClientIfCurrent(client) {
|
|
|
2789
3259
|
const state = getSharedCodexAppServerClientState();
|
|
2790
3260
|
for (const entry of state.clients.values()) if (entry.client === client) return retainSharedClientEntry(entry);
|
|
2791
3261
|
}
|
|
3262
|
+
/** Retains the live shared client whose initialized instance id matches a thread binding. */
|
|
3263
|
+
function retainSharedCodexAppServerClientByInstanceId(clientId) {
|
|
3264
|
+
const normalizedClientId = clientId?.trim();
|
|
3265
|
+
if (!normalizedClientId) return;
|
|
3266
|
+
for (const entry of getSharedCodexAppServerClientState().clients.values()) {
|
|
3267
|
+
const client = entry.client;
|
|
3268
|
+
if (client?.getInstanceId() !== normalizedClientId || entry.closeWhenIdle || entry.closeError) continue;
|
|
3269
|
+
return {
|
|
3270
|
+
client,
|
|
3271
|
+
release: retainSharedClientEntry(entry)
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
2792
3275
|
/**
|
|
2793
3276
|
* Retires a matching shared client. Default is graceful: detach from the map
|
|
2794
3277
|
* (future acquisitions get a fresh client) and close once leases drain.
|
|
@@ -2806,11 +3289,9 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
|
|
|
2806
3289
|
entry.closeWhenIdle = true;
|
|
2807
3290
|
if (opts?.failActiveLeases) {
|
|
2808
3291
|
entry.closeError = /* @__PURE__ */ new Error("codex app-server client is closed");
|
|
2809
|
-
const closed = closeRetiredSharedClientEntry(entry);
|
|
2810
|
-
if (closed) suspectClosedClients.add(client);
|
|
2811
3292
|
return {
|
|
2812
3293
|
activeLeases: entry.activeLeases,
|
|
2813
|
-
closed
|
|
3294
|
+
closed: closeRetiredSharedClientEntry(entry)
|
|
2814
3295
|
};
|
|
2815
3296
|
}
|
|
2816
3297
|
const closed = closeRetiredSharedClientEntryIfIdle(entry);
|
|
@@ -2819,18 +3300,17 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
|
|
|
2819
3300
|
closed
|
|
2820
3301
|
};
|
|
2821
3302
|
}
|
|
2822
|
-
const
|
|
2823
|
-
if (
|
|
2824
|
-
if (opts?.failActiveLeases && !
|
|
2825
|
-
|
|
2826
|
-
client.close();
|
|
3303
|
+
const detachedEntry = state.entriesByClient.get(client);
|
|
3304
|
+
if (detachedEntry && (detachedEntry.client === client || detachedEntry.closeError)) {
|
|
3305
|
+
if (opts?.failActiveLeases && !detachedEntry.closeError) {
|
|
3306
|
+
detachedEntry.closeError = /* @__PURE__ */ new Error("codex app-server client is closed");
|
|
2827
3307
|
return {
|
|
2828
|
-
activeLeases,
|
|
2829
|
-
closed:
|
|
3308
|
+
activeLeases: detachedEntry.activeLeases,
|
|
3309
|
+
closed: closeRetiredSharedClientEntry(detachedEntry)
|
|
2830
3310
|
};
|
|
2831
3311
|
}
|
|
2832
3312
|
return {
|
|
2833
|
-
activeLeases,
|
|
3313
|
+
activeLeases: detachedEntry.activeLeases,
|
|
2834
3314
|
closed: false
|
|
2835
3315
|
};
|
|
2836
3316
|
}
|
|
@@ -2851,6 +3331,7 @@ async function clearSharedCodexAppServerClientAndWait(options) {
|
|
|
2851
3331
|
const state = getSharedCodexAppServerClientState();
|
|
2852
3332
|
const clients = collectSharedClients(state);
|
|
2853
3333
|
state.clients.clear();
|
|
3334
|
+
state.warmClientsByConfig = /* @__PURE__ */ new WeakMap();
|
|
2854
3335
|
await Promise.all(clients.map((client) => client.closeAndWait(options)));
|
|
2855
3336
|
}
|
|
2856
3337
|
globalThis[SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER] = clearSharedCodexAppServerClientAndWait;
|
|
@@ -2946,7 +3427,7 @@ function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
|
|
|
2946
3427
|
closeRetiredSharedClientEntry(entry);
|
|
2947
3428
|
}
|
|
2948
3429
|
function collectSharedClients(state) {
|
|
2949
|
-
return [...
|
|
3430
|
+
return [...state.liveClients];
|
|
2950
3431
|
}
|
|
2951
3432
|
//#endregion
|
|
2952
|
-
export {
|
|
3433
|
+
export { resolveCodexAppServerAuthProfileId as $, isCodexAppServerApprovalRequest as A, claimCodexAppServerLiveThread as B, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as C, resolveCodexTurnTerminalIdleTimeoutMs as D, resolveCodexTurnCompletionIdleTimeoutMs as E, isCodexAppServerPrewriteRequestCancellationError as F, protectCodexAppServerLiveThread as G, ensureCodexAppServerClientRuntime as H, isCodexAppServerRequestTimeoutError as I, unsubscribeCodexAppServerLiveThread as J, releaseCodexAppServerLiveThread as K, isUnsupportedCodexAppServerVersionError as L, isCodexAppServerConnectionClosedError as M, isCodexAppServerIndeterminateRequestCancellationError as N, withCodexStartupTimeout as O, isCodexAppServerIndeterminateTransportError as P, resolveCodexAppServerAuthAccountCacheKey as Q, resolveCodexAppServerClientInstanceId as R, resolveCodexGatewayTimeoutWithGraceMs as S, resolveCodexTurnAssistantCompletionIdleTimeoutMs as T, isCodexAppServerClientRuntimeLive as U, consumeCodexAppServerLiveThread as V, isCodexAppServerLiveThreadClaimed as W, readRecentCodexRateLimits as X, readCodexRateLimitsRevision as Y, rememberCodexRateLimitsRead as Z, withTimeout$1 as _, getLeasedSharedCodexAppServerClient as a, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as at, CodexAppServerStartupError as b, releaseCodexAppServerClientLease as c, isJsonObject as ct, retainSharedCodexAppServerClientByInstanceId as d, resolveCodexAppServerLocalHomeDir as dt, resolveCodexAppServerAuthProfileIdForAgent as et, retainSharedCodexAppServerClientIfCurrent as f, withAbortableTimeout as g, withLeasedCodexAppServerClientStartSelectionRetry as h, createIsolatedCodexAppServerClient as i, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as it, isCodexAppServerBrokenPipeError as j, getCodexAppServerClientInstanceId as k, releaseLeasedSharedCodexAppServerClient as l, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as lt, shared_client_exports as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, resolveCodexAppServerPreparedApiKeyCacheKey as nt, getSharedCodexAppServerClient as o, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as ot, retireSharedCodexAppServerClientIfCurrent as p, retainCodexAppServerLiveThread as q, clearSharedCodexAppServerClientIfCurrentAndWait as r, resolveCodexAppServerPreparedAuthHandoff as rt, isCodexAppServerStartSelectionChangedError as s, flattenCodexDynamicToolFunctions as st, clearSharedCodexAppServerClientIfCurrent as t, resolveCodexAppServerFallbackApiKeyCacheKey as tt, resolveCodexNativeConfigFenceKey as u, resolveCodexAppServerHomeDir as ut, acquireCodexNativeConfigFence as v, resolveCodexStartupTimeoutMs as w, isCodexAppServerStartupError as x, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as y, CodexAppServerRpcError as z };
|