@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.
Files changed (59) hide show
  1. package/README.md +2 -0
  2. package/dist/{app-server-policy-BmDgseo_.js → app-server-policy-zcBDc1JU.js} +1 -1
  3. package/dist/attempt-client-cleanup-DMLXUfa8.js +735 -0
  4. package/dist/binding-connection-I5Bo-AsX.js +33 -0
  5. package/dist/{bounded-turn-FEbUXoYT.js → bounded-turn-qgZmzy_4.js} +71 -63
  6. package/dist/{capabilities-BHRSI6Cn.js → capabilities-CybGI1oz.js} +8 -2
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-ico4ZFnk.js → command-formatters-B1AEz8ZA.js} +18 -18
  9. package/dist/{command-handlers-CRmnwI2N.js → command-handlers-Db8pu_ds.js} +484 -95
  10. package/dist/{compact-dEN49UH6.js → compact-LspQaGfl.js} +213 -199
  11. package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
  12. package/dist/doctor-contract-api.js +40 -43
  13. package/dist/{dynamic-tools-LIt9ojep.js → dynamic-tools-BVdyWkUD.js} +2984 -1221
  14. package/dist/effective-mcp-catalog-DIIhAtOZ.js +119 -0
  15. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  16. package/dist/{attempt-notifications-CMR2_fmR.js → event-projector-usage-e5huIQWu.js} +113 -29
  17. package/dist/harness.js +83 -13
  18. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  19. package/dist/index.js +703 -329
  20. package/dist/isolated-completion-C58NbL40.js +75 -0
  21. package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
  22. package/dist/media-understanding-provider.js +5 -5
  23. package/dist/{models-BA01NEez.js → models-BFlpn1Ex.js} +12 -17
  24. package/dist/native-subagent-monitor-DyTGmTUx.js +3111 -0
  25. package/dist/{plugin-app-cache-key-DSXX-rXO.js → plugin-app-cache-key-C3H2CVp6.js} +135 -42
  26. package/dist/plugin-marketplace-discovery-CzrGJdCR.js +385 -0
  27. package/dist/{computer-use-BjGylO-E.js → plugin-metadata-cache-CNJPj4_b.js} +211 -113
  28. package/dist/protocol-validators-B4F7L8Rf.js +2346 -0
  29. package/dist/{rate-limits-S_PXs0ul.js → rate-limits-DZXmOL8-.js} +17 -35
  30. package/dist/{request-CXpPennK.js → request-BRl5_UfX.js} +4 -4
  31. package/dist/{run-attempt-C5GTPJOE.js → run-attempt-P8BdxC1I.js} +1996 -5728
  32. package/dist/{runtime-artifact-C2ITXdhM.js → runtime-artifact-BVSPdd6g.js} +91 -40
  33. package/dist/{sandbox-guard-D1Wky__S.js → sandbox-guard-Cmwc9eIk.js} +10 -13
  34. package/dist/{session-binding-DlVQ-Pxs.js → session-binding-DPxIvMgE.js} +305 -271
  35. package/dist/{session-catalog-CP2oCcEb.js → session-catalog-xwNi_SDM.js} +418 -144
  36. package/dist/{session-cli-CqZ0Fp2a.js → session-cli-7hgXZDV7.js} +1 -1
  37. package/dist/session-retirement-DoMMJFKm.js +36 -0
  38. package/dist/{session-upstream-marker-QE-m5QFG.js → session-upstream-marker-CrIFX06J.js} +1 -1
  39. package/dist/{settled-turn-finalizer-CA_2QXIE.js → settled-turn-finalizer-BRWBO4MO.js} +36 -15
  40. package/dist/{shared-client-BVKE40c-.js → shared-client-kIBbGjk4.js} +721 -240
  41. package/dist/{side-question-CO12sHxo.js → side-question-9HAL26l9.js} +102 -53
  42. package/dist/{thread-lifecycle-DRg_FdzC.js → thread-lifecycle-Bu1BTDXr.js} +1355 -660
  43. package/dist/thread-ownership-6kOQHlyC.js +92 -0
  44. package/dist/thread-resume-CPrJNQVM.js +30 -0
  45. package/dist/{transcript-mirror-uKA-A4tB.js → transcript-mirror-B9gGgnJS.js} +202 -182
  46. package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DIImwDVS.js} +11 -0
  47. package/dist/upstream-prompt-provenance-ClRBije0.js +41 -0
  48. package/dist/{upstream-session-fork-_Vrz4WJr.js → upstream-session-fork-CE-_LdnP.js} +5 -5
  49. package/dist/{usage-UpVbdX5G.js → usage-CHlV0e-o.js} +3 -3
  50. package/dist/{web-search-provider.runtime-CaQMYDbp.js → web-search-provider.runtime-DRw2Vl42.js} +8 -9
  51. package/openclaw.plugin.json +18 -3
  52. package/package.json +6 -7
  53. package/dist/attempt-client-cleanup-Ia52yRWc.js +0 -127
  54. package/dist/conversation-control-DE7d9DOj.js +0 -287
  55. package/dist/event-projector-usage-tvgb1NCX.js +0 -69
  56. package/dist/notification-correlation-Bj_Ak_jI.js +0 -67
  57. package/dist/protocol-validators-Dm4eXZmt.js +0 -6337
  58. package/dist/realtime-voice-api-BPcA1uN9.js +0 -494
  59. 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 { d as codexAppServerStartOptionsKey, h as resolveCodexComputerUseConfig, m as resolveCodexAppServerStartOptionsForAgent, o as isCodexAppServerNativeAuthProfile, p as resolveCodexAppServerRuntimeOptions, x as resolveCodexAppServerUserHomeDir } from "./session-binding-DlVQ-Pxs.js";
3
- import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DXgGZ7Ib.js";
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$1(value) {
412
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
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 OPENAI_CODEX_APP_SERVER_AUTH_PROVIDER = "openai-codex";
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 scopedStartOptions = await withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
443
- if (params.preparedAuth) return withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
444
- if (params.authProfileId === null) return scopedStartOptions;
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
- return shouldClearOpenAiApiKeyForCodexAuthProfile({
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, params.config)) return;
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: credential.type === "api_key" && loginParams.type === "apiKey" ? `${accountId}:${fingerprintApiKeyAuthProfileCacheKey(loginParams.apiKey)}` : loginParams.type === "chatgptAuthTokens" && (credential.type === "token" || !stableChatgptAccountId) ? `${accountId}:${fingerprintTokenAuthProfileCacheKey(loginParams.accessToken)}` : accountId
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, params.config)) return;
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 = startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim() ? startOptions.env[CODEX_HOME_ENV_VAR] : startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir(process.env) : resolveCodexAppServerHomeDir(agentDir);
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.", { cause: error });
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
- if (params.authRequirement === "subscription") {
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, params.config)) throw new CodexAppServerAuthProfileUnavailableError(`Codex app-server auth profile "${profileId}" must be OpenAI Codex auth or an OpenAI API-key backup.`);
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, params.config)) throw new Error(`Codex app-server auth profile "${profileId}" must be OpenAI Codex auth or an OpenAI API-key backup.`);
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 fs.readFile(resolveCodexCliAuthFilePath(env), "utf8"));
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, params.config) ? persistedCredential : void 0;
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, params.config) ? ownerCredential : void 0;
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, params.config) ? refreshed : void 0;
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, params.config) ? storedCredential : credential;
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, config) {
961
- const resolvedProvider = resolveProviderIdForAuth(provider, { config });
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, config) {
968
- return isCodexAppServerAuthProvider(credential.provider, config) || isOpenAIApiKeyBackupCredential(credential, config);
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], params.config);
913
+ return isCodexSubscriptionCredential(profileId ? params.store.profiles[profileId] : params.store.profiles[OPENAI_CODEX_DEFAULT_PROFILE_ID]);
973
914
  }
974
- function isCodexSubscriptionCredential(credential, config) {
975
- if (!credential || !isCodexAppServerAuthProvider(credential.provider, config)) return false;
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$1(value) && isJsonObject$1(value.rateLimits) ? value.rateLimits : void 0;
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$1(current) ? current : void 0;
1073
- const currentPrimary = currentEnvelope && isJsonObject$1(currentEnvelope.rateLimits) ? currentEnvelope.rateLimits : void 0;
1074
- const currentByLimitId = currentEnvelope && isJsonObject$1(currentEnvelope.rateLimitsByLimitId) ? currentEnvelope.rateLimitsByLimitId : void 0;
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$1(currentByLimitId[limitId]) ? currentByLimitId[limitId] : void 0) ?? (currentPrimaryLimitId === limitId ? currentPrimary : void 0);
1078
- const merged = mergeSparseSnapshot(isJsonObject$1(currentForLimit) ? currentForLimit : void 0, currentPrimary, update, limitId);
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$1(value)) return [];
1039
+ if (!isJsonObject(value)) return [];
1097
1040
  const ids = /* @__PURE__ */ new Set();
1098
- if (isJsonObject$1(value.rateLimits)) ids.add(readLimitId(value.rateLimits));
1099
- if (isJsonObject$1(value.rateLimitsByLimitId)) for (const [key, snapshot] of Object.entries(value.rateLimitsByLimitId)) {
1100
- const snapshotLimitId = isJsonObject$1(snapshot) && typeof snapshot.limitId === "string" ? snapshot.limitId.trim() : "";
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 = { context };
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") mergeCodexRateLimitsUpdate(client, notification.params);
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
- /** Minimum Codex app-server version supported by the OpenClaw Codex bridge. */
1313
- const MIN_CODEX_APP_SERVER_VERSION = "0.143.0";
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(error instanceof Error ? error : new Error(String(error))));
1437
- child.stdout.on("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
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(error instanceof Error ? error : new Error(String(error))));
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(error instanceof Error ? error : new Error(String(error))));
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(error instanceof Error ? error : new Error(String(error)));
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 = error instanceof Error ? error.message : String(error);
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) Promise.resolve(handler(notification)).catch((error) => {
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(`A stable Codex app-server from ${MIN_CODEX_APP_SERVER_VERSION} through ${MAX_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.`);
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
- const parsedVersion = parse(detectedVersion ?? "");
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 = readNonEmptyInitializeString(response.userAgent);
1953
- const codexHome = readNonEmptyInitializeString(response.codexHome);
1954
- const platformFamily = readNonEmptyInitializeString(response.platformFamily);
1955
- const platformOs = readNonEmptyInitializeString(response.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 = error instanceof Error ? error.message : String(error);
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: error instanceof Error ? error.message : String(error),
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 binPath = typeof packageJson.bin === "string" ? packageJson.bin : isRecord(packageJson.bin) && typeof packageJson.bin.codex === "string" ? packageJson.bin.codex : null;
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 suspectClosedClients = /* @__PURE__ */ new WeakSet();
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(getSharedCodexAppServerClientState(), key);
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 = "codex app-server initialize timed out") {
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-C2ITXdhM.js") : void 0;
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 activeLeases = state.leasedReleases.get(client)?.length ?? 0;
2823
- if (activeLeases > 0) {
2824
- if (opts?.failActiveLeases && !suspectClosedClients.has(client)) {
2825
- suspectClosedClients.add(client);
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: true
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 [...new Set([...state.clients.values()].map((entry) => entry.client).filter((client) => Boolean(client)))];
3430
+ return [...state.liveClients];
2950
3431
  }
2951
3432
  //#endregion
2952
- export { withCodexStartupTimeout as $, rememberCodexRateLimitsRead as A, flattenCodexDynamicToolFunctions as B, isCodexAppServerIndeterminateTransportError as C, ensureCodexAppServerClientRuntime as D, resolveCodexAppServerClientInstanceId as E, resolveCodexAppServerPreparedApiKeyCacheKey as F, CodexAppServerStartupError as G, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as H, resolveCodexAppServerPreparedAuthHandoff as I, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as J, isCodexAppServerStartupError as K, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as L, resolveCodexAppServerAuthProfileId as M, resolveCodexAppServerAuthProfileIdForAgent as N, readCodexRateLimitsRevision as O, resolveCodexAppServerFallbackApiKeyCacheKey as P, resolveCodexTurnTerminalIdleTimeoutMs as Q, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as R, isCodexAppServerIndeterminateRequestCancellationError as S, isCodexAppServerRequestTimeoutError as T, resolveCodexAppServerHomeDir as U, isJsonObject$1 as V, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as W, resolveCodexTurnAssistantCompletionIdleTimeoutMs as X, resolveCodexStartupTimeoutMs as Y, resolveCodexTurnCompletionIdleTimeoutMs as Z, CodexAppServerRpcError as _, getLeasedSharedCodexAppServerClient as a, isCodexAppServerBrokenPipeError as b, releaseCodexAppServerClientLease as c, retainSharedCodexAppServerClientIfCurrent as d, retireSharedCodexAppServerClientIfCurrent as f, acquireCodexNativeConfigFence as g, withTimeout$1 as h, createIsolatedCodexAppServerClient as i, resolveCodexAppServerAuthAccountCacheKey as j, readRecentCodexRateLimits as k, releaseLeasedSharedCodexAppServerClient as l, withLeasedCodexAppServerClientStartSelectionRetry as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, getSharedCodexAppServerClient as o, shared_client_exports as p, resolveCodexGatewayTimeoutWithGraceMs as q, clearSharedCodexAppServerClientIfCurrentAndWait as r, isCodexAppServerStartSelectionChangedError as s, clearSharedCodexAppServerClientIfCurrent as t, resolveCodexNativeConfigFenceKey as u, getCodexAppServerClientInstanceId as v, isCodexAppServerPrewriteRequestCancellationError as w, isCodexAppServerConnectionClosedError as x, isCodexAppServerApprovalRequest as y, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as z };
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 };