@openclaw/codex 2026.7.2-beta.7 → 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 (55) hide show
  1. package/README.md +2 -0
  2. package/dist/{app-server-policy-BSaO2JIT.js → app-server-policy-zcBDc1JU.js} +1 -1
  3. package/dist/attempt-client-cleanup-DMLXUfa8.js +735 -0
  4. package/dist/{notification-correlation-CU6_AnOb.js → binding-connection-I5Bo-AsX.js} +3 -37
  5. package/dist/{bounded-turn-D2ZAgZVz.js → bounded-turn-qgZmzy_4.js} +50 -49
  6. package/dist/{capabilities-BFDTVYr4.js → capabilities-CybGI1oz.js} +8 -2
  7. package/dist/cli-metadata.js +1 -1
  8. package/dist/{command-formatters-C336eRVS.js → command-formatters-B1AEz8ZA.js} +18 -18
  9. package/dist/{command-handlers-DAHUSry9.js → command-handlers-Db8pu_ds.js} +484 -95
  10. package/dist/{compact-D_uvHurS.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-D2gYkSBO.js → dynamic-tools-BVdyWkUD.js} +2597 -1221
  14. package/dist/{effective-mcp-catalog-Bbd98Inw.js → effective-mcp-catalog-DIIhAtOZ.js} +20 -15
  15. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  16. package/dist/{attempt-notifications-AP-a-Q5k.js → event-projector-usage-e5huIQWu.js} +113 -29
  17. package/dist/harness.js +80 -14
  18. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  19. package/dist/index.js +591 -240
  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-BBMKU4jA.js → models-BFlpn1Ex.js} +12 -17
  24. package/dist/native-subagent-monitor-DyTGmTUx.js +3111 -0
  25. package/dist/{plugin-app-cache-key-DztPafOA.js → plugin-app-cache-key-C3H2CVp6.js} +5 -5
  26. package/dist/plugin-marketplace-discovery-CzrGJdCR.js +385 -0
  27. package/dist/{computer-use-CYEc8U3w.js → plugin-metadata-cache-CNJPj4_b.js} +105 -5
  28. package/dist/protocol-validators-B4F7L8Rf.js +2346 -0
  29. package/dist/{rate-limits-CU-gVCCS.js → rate-limits-DZXmOL8-.js} +17 -35
  30. package/dist/{request-qKh331yu.js → request-BRl5_UfX.js} +3 -3
  31. package/dist/{run-attempt-CY9WlDLc.js → run-attempt-P8BdxC1I.js} +4837 -9047
  32. package/dist/{runtime-artifact-CmtG_t1L.js → runtime-artifact-BVSPdd6g.js} +8 -23
  33. package/dist/{sandbox-guard-NuhpXmPB.js → sandbox-guard-Cmwc9eIk.js} +6 -13
  34. package/dist/{session-binding-BXpo9ymA.js → session-binding-DPxIvMgE.js} +246 -265
  35. package/dist/{session-catalog-DyOgR4WH.js → session-catalog-xwNi_SDM.js} +159 -109
  36. package/dist/{session-cli-2PZaFLRh.js → session-cli-7hgXZDV7.js} +1 -1
  37. package/dist/session-retirement-DoMMJFKm.js +36 -0
  38. package/dist/{session-upstream-marker-B6U7tLw5.js → session-upstream-marker-CrIFX06J.js} +1 -1
  39. package/dist/{settled-turn-finalizer-CXY2Z9dT.js → settled-turn-finalizer-BRWBO4MO.js} +21 -13
  40. package/dist/{shared-client-DTbvH-XV.js → shared-client-kIBbGjk4.js} +587 -223
  41. package/dist/{side-question-_ulBk1AL.js → side-question-9HAL26l9.js} +101 -52
  42. package/dist/{thread-lifecycle-DF53ogiA.js → thread-lifecycle-Bu1BTDXr.js} +694 -753
  43. package/dist/thread-ownership-6kOQHlyC.js +92 -0
  44. package/dist/thread-resume-CPrJNQVM.js +30 -0
  45. package/dist/transcript-mirror-B9gGgnJS.js +570 -0
  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-Dy4Xvuoj.js → upstream-session-fork-CE-_LdnP.js} +5 -5
  49. package/dist/{usage-CQ6GhD5-.js → usage-CHlV0e-o.js} +3 -3
  50. package/dist/{web-search-provider.runtime-CCFhg2z5.js → web-search-provider.runtime-DRw2Vl42.js} +8 -9
  51. package/openclaw.plugin.json +18 -3
  52. package/package.json +5 -5
  53. package/dist/conversation-control-xsIeXbo4.js +0 -287
  54. package/dist/event-projector-usage-Xp4AlR2k.js +0 -86
  55. package/dist/transcript-mirror-DzsdNCf5.js +0 -6924
@@ -1,21 +1,23 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { _ as resolveCodexAppServerStartOptionsForAgent, g as resolveCodexAppServerRuntimeOptions, o as isCodexAppServerNativeAuthProfile, p as codexAppServerStartOptionsKey, v as resolveCodexComputerUseConfig, w as resolveCodexAppServerUserHomeDir } from "./session-binding-BXpo9ymA.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";
16
17
  import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
17
18
  import { runExec } from "openclaw/plugin-sdk/process-runtime";
18
19
  import { createInterface } from "node:readline";
20
+ import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
19
21
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
20
22
  import { EventEmitter } from "node:events";
21
23
  import net from "node:net";
@@ -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);
@@ -436,24 +347,41 @@ const CODEX_HOME_DIRNAME = ".codex";
436
347
  const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
437
348
  async function bridgeCodexAppServerStartOptions(params) {
438
349
  if (params.startOptions.transport !== "stdio") return params.startOptions;
439
- const scopedStartOptions = await withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
440
- if (params.preparedAuth) return withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
441
- 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();
442
353
  const store = resolveCodexAppServerAuthProfileStore({
443
354
  agentDir: params.agentDir,
444
355
  authProfileId: params.authProfileId,
445
356
  authProfileStore: params.authProfileStore,
446
357
  config: params.config
447
358
  });
359
+ const authProfileId = resolveCodexAppServerAuthProfileId({
360
+ authProfileId: params.authProfileId,
361
+ store,
362
+ config: params.config
363
+ });
364
+ if (!authProfileId) assertNoUnimportedAgentCodexAuthFile(params);
365
+ const scopedStartOptions = await scopeStartOptions();
448
366
  return shouldClearOpenAiApiKeyForCodexAuthProfile({
449
367
  store,
450
- authProfileId: resolveCodexAppServerAuthProfileId({
451
- authProfileId: params.authProfileId,
452
- store,
453
- config: params.config
454
- })
368
+ authProfileId
455
369
  }) ? withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS) : scopedStartOptions;
456
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
+ }
457
385
  function resolveCodexAppServerAuthProfileId(params) {
458
386
  const requested = params.authProfileId?.trim();
459
387
  if (requested) return requested;
@@ -518,14 +446,18 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
518
446
  if (!loginParams) return;
519
447
  const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
520
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);
521
451
  return {
522
452
  loginParams,
523
- 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 } : {}
524
455
  };
525
456
  }
526
457
  /** Maps one prepared route to one mutually exclusive app-server auth handoff. */
527
458
  async function resolveCodexAppServerPreparedAuthHandoff(params) {
528
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.");
529
461
  if (params.authRequirement === "api-key" && !usesNativeHome) {
530
462
  const apiKey = params.resolvedApiKey?.trim();
531
463
  if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
@@ -544,18 +476,18 @@ async function resolveCodexAppServerPreparedAuthHandoff(params) {
544
476
  agentDir: params.agentDir,
545
477
  config: params.config
546
478
  });
547
- if (usesNativeHome || params.authRequirement !== "subscription") return {
479
+ if (usesNativeHome || params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
548
480
  authProfileId,
549
481
  nativeAuthProfile
550
482
  };
551
- 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);
552
484
  const snapshot = await resolveCodexAppServerPreparedAuthProfileSnapshot({
553
485
  authProfileId,
554
486
  authProfileStore: params.authProfileStore,
555
487
  agentDir: params.agentDir,
556
488
  config: params.config
557
489
  });
558
- 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);
559
491
  return {
560
492
  authProfileId,
561
493
  nativeAuthProfile,
@@ -644,7 +576,7 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
644
576
  return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
645
577
  }
646
578
  async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
647
- 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);
648
580
  const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
649
581
  await fs.mkdir(codexHome, { recursive: true });
650
582
  const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
@@ -658,7 +590,10 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
658
590
  appServerCommand: startOptions.command
659
591
  });
660
592
  } catch (error) {
661
- 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
+ });
662
597
  }
663
598
  if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
664
599
  const nextStartOptions = {
@@ -733,7 +668,7 @@ async function applyCodexAppServerAuthProfile(params) {
733
668
  async function assertNativeCodexAccountMatchesRoute(client, authRequirement) {
734
669
  if (!authRequirement) return;
735
670
  const response = await client.request("account/read", { refreshToken: false });
736
- const accountType = isJsonObject$1(response.account) ? response.account.type : void 0;
671
+ const accountType = isJsonObject(response.account) ? response.account.type : void 0;
737
672
  if (authRequirement === "subscription") {
738
673
  if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
739
674
  return;
@@ -1019,11 +954,13 @@ function resolveChatgptAccountId(profileId, credential) {
1019
954
  return resolveStableChatgptAccountId(credential) ?? profileId;
1020
955
  }
1021
956
  function resolveStableChatgptAccountId(credential) {
957
+ return resolveExplicitChatgptAccountId(credential) ?? (credential.email?.trim() || void 0);
958
+ }
959
+ function resolveExplicitChatgptAccountId(credential) {
1022
960
  if ("accountId" in credential && typeof credential.accountId === "string") {
1023
961
  const accountId = credential.accountId.trim();
1024
962
  if (accountId) return accountId;
1025
963
  }
1026
- return credential.email?.trim() || void 0;
1027
964
  }
1028
965
  //#endregion
1029
966
  //#region extensions/codex/src/app-server/rate-limit-cache.ts
@@ -1048,7 +985,7 @@ function rememberCodexRateLimitsRead(client, value, nowMs = Date.now()) {
1048
985
  }
1049
986
  /** Merges a sparse rolling notification into one physical client's latest read response. */
1050
987
  function mergeCodexRateLimitsUpdate(client, value, nowMs = Date.now()) {
1051
- 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;
1052
989
  if (!update) return;
1053
990
  const currentState = rateLimitsByClient.get(client);
1054
991
  const current = currentState?.value;
@@ -1075,13 +1012,13 @@ function readRecentCodexRateLimits(client, options) {
1075
1012
  return maxAgeMs >= 0 && nowMs - state.updatedAtMs > maxAgeMs ? void 0 : state.value;
1076
1013
  }
1077
1014
  function mergeRateLimitUpdate(current, update) {
1078
- const currentEnvelope = isJsonObject$1(current) ? current : void 0;
1079
- const currentPrimary = currentEnvelope && isJsonObject$1(currentEnvelope.rateLimits) ? currentEnvelope.rateLimits : void 0;
1080
- 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;
1081
1018
  const limitId = readLimitId(update);
1082
1019
  const currentPrimaryLimitId = currentPrimary ? readLimitId(currentPrimary) : void 0;
1083
- const currentForLimit = (currentByLimitId && isJsonObject$1(currentByLimitId[limitId]) ? currentByLimitId[limitId] : void 0) ?? (currentPrimaryLimitId === limitId ? currentPrimary : void 0);
1084
- 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);
1085
1022
  const nextPrimary = !currentPrimary || currentPrimaryLimitId === limitId ? merged : currentPrimary;
1086
1023
  let nextByLimitId;
1087
1024
  if (currentByLimitId) nextByLimitId = {
@@ -1099,11 +1036,11 @@ function mergeRateLimitUpdate(current, update) {
1099
1036
  };
1100
1037
  }
1101
1038
  function readRateLimitIds(value) {
1102
- if (!isJsonObject$1(value)) return [];
1039
+ if (!isJsonObject(value)) return [];
1103
1040
  const ids = /* @__PURE__ */ new Set();
1104
- if (isJsonObject$1(value.rateLimits)) ids.add(readLimitId(value.rateLimits));
1105
- if (isJsonObject$1(value.rateLimitsByLimitId)) for (const [key, snapshot] of Object.entries(value.rateLimitsByLimitId)) {
1106
- 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() : "";
1107
1044
  ids.add(snapshotLimitId || key);
1108
1045
  }
1109
1046
  return [...ids];
@@ -1126,16 +1063,45 @@ function readLimitId(snapshot) {
1126
1063
  //#endregion
1127
1064
  //#region extensions/codex/src/app-server/client-runtime.ts
1128
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;
1129
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
+ }
1130
1078
  /** Installs one auth-refresh handler and one rate-limit observer per physical client. */
1131
1079
  function ensureCodexAppServerClientRuntime(client, context) {
1132
1080
  const existing = configuredClients.get(client);
1133
1081
  if (existing) {
1082
+ if (existing.closed) return;
1134
1083
  existing.context = context;
1135
1084
  return;
1136
1085
  }
1137
- 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
+ };
1138
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
+ });
1139
1105
  client.addRequestHandler(async (request) => {
1140
1106
  if (request.method !== "account/chatgptAuthTokens/refresh") return;
1141
1107
  if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
@@ -1147,41 +1113,278 @@ function ensureCodexAppServerClientRuntime(client, context) {
1147
1113
  });
1148
1114
  });
1149
1115
  client.addNotificationHandler((notification) => {
1150
- 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
+ }
1151
1130
  });
1152
1131
  }
1153
- /** Keep at most one idle, still-subscribed thread on a physical Codex client. */
1154
- async function retainCodexAppServerLiveThread(client, threadId, releasePreviousThread, configFingerprint) {
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) {
1155
1234
  const runtime = configuredClients.get(client);
1156
- if (!runtime) return;
1157
- if (runtime.retainedThreadRelease) await runtime.retainedThreadRelease;
1158
- const previousThreadId = runtime.retainedThreadId;
1159
- if (previousThreadId && previousThreadId !== threadId && releasePreviousThread) {
1160
- const release = releasePreviousThread(previousThreadId);
1161
- runtime.retainedThreadRelease = release;
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
+ }
1259
+ });
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);
1162
1278
  try {
1163
- await release;
1164
- } catch (error) {
1165
- runtime.retainedThreadId = void 0;
1166
- runtime.retainedThreadConfigFingerprint = void 0;
1167
- throw error;
1279
+ await transition.completion;
1280
+ if (runtime.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1168
1281
  } finally {
1169
- if (runtime.retainedThreadRelease === release) runtime.retainedThreadRelease = void 0;
1282
+ if (runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1170
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);
1171
1325
  }
1172
- runtime.retainedThreadId = threadId;
1173
- runtime.retainedThreadConfigFingerprint = configFingerprint;
1174
- return previousThreadId ? { previousThreadId } : {};
1175
1326
  }
1176
- /** A warm turn can skip resume only when this exact subscription was retained. */
1177
- async function consumeCodexAppServerLiveThread(client, threadId, configFingerprint) {
1327
+ /** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
1328
+ async function releaseCodexAppServerLiveThread(client, threadId) {
1178
1329
  const runtime = configuredClients.get(client);
1179
- if (!runtime) return false;
1180
- if (runtime.retainedThreadRelease) await runtime.retainedThreadRelease;
1181
- if (runtime.retainedThreadId !== threadId || configFingerprint !== void 0 && runtime.retainedThreadConfigFingerprint !== configFingerprint) return false;
1182
- runtime.retainedThreadId = void 0;
1183
- runtime.retainedThreadConfigFingerprint = void 0;
1184
- return true;
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;
1185
1388
  }
1186
1389
  //#endregion
1187
1390
  //#region extensions/codex/src/app-server/transport-websocket.ts
@@ -1189,6 +1392,10 @@ async function consumeCodexAppServerLiveThread(client, threadId, configFingerpri
1189
1392
  * Adapts a remote Codex app-server WebSocket endpoint to the shared stdio-like
1190
1393
  * transport interface.
1191
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;
1192
1399
  /** Opens a WebSocket app-server transport and maps newline-delimited frames to stdout/stdin. */
1193
1400
  function createWebSocketTransport(options) {
1194
1401
  if (!options.url) throw new Error("codex app-server websocket transport requires plugins.entries.codex.config.appServer.url");
@@ -1200,7 +1407,8 @@ function createWebSocketTransport(options) {
1200
1407
  ...options.headers,
1201
1408
  ...options.authToken ? { Authorization: `Bearer ${options.authToken}` } : {}
1202
1409
  },
1203
- perMessageDeflate: false
1410
+ perMessageDeflate: false,
1411
+ ...options.transport === "websocket" ? { handshakeTimeout: WEBSOCKET_HANDSHAKE_TIMEOUT_MS } : {}
1204
1412
  };
1205
1413
  const unixSocketPath = resolveCodexAppServerUnixSocketPath(options);
1206
1414
  const socket = unixSocketPath ? new WebSocket("ws://localhost/", {
@@ -1211,6 +1419,58 @@ function createWebSocketTransport(options) {
1211
1419
  const stdinDecoder = new StringDecoder("utf8");
1212
1420
  let pendingLine = "";
1213
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
+ };
1214
1474
  const sendFrame = (frame) => {
1215
1475
  const trimmed = frame.trim();
1216
1476
  if (!trimmed) return;
@@ -1222,13 +1482,22 @@ function createWebSocketTransport(options) {
1222
1482
  };
1223
1483
  socket.once("open", () => {
1224
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);
1225
1493
  });
1226
- socket.once("error", (error) => events.emit("error", error));
1227
1494
  socket.once("close", (code, reason) => {
1495
+ clearConnectionHealthTimers();
1228
1496
  killed = true;
1229
1497
  events.emit("exit", code, reason.toString("utf8"));
1230
1498
  });
1231
1499
  socket.on("message", (data) => {
1500
+ if (options.transport === "websocket") recordConnectionActivity();
1232
1501
  const text = websocketFrameToText(data);
1233
1502
  stdout.write(text.endsWith("\n") ? text : `${text}\n`);
1234
1503
  });
@@ -1262,6 +1531,7 @@ function createWebSocketTransport(options) {
1262
1531
  },
1263
1532
  kill: () => {
1264
1533
  killed = true;
1534
+ clearConnectionHealthTimers();
1265
1535
  socket.close();
1266
1536
  },
1267
1537
  once: (event, listener) => events.once(event, listener)
@@ -1349,7 +1619,7 @@ function signalCodexAppServerTransport(child, signal) {
1349
1619
  * Version and package pins for the managed Codex app-server runtime.
1350
1620
  */
1351
1621
  /** Exact Codex app-server version shipped and supported by the OpenClaw Codex bridge. */
1352
- const CODEX_APP_SERVER_VERSION = "0.146.0";
1622
+ const CODEX_APP_SERVER_VERSION = "0.147.0";
1353
1623
  /** npm package name for the managed Codex app-server binary. */
1354
1624
  const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
1355
1625
  //#endregion
@@ -1379,16 +1649,6 @@ function getCodexAppServerClientInstanceId(client) {
1379
1649
  function resolveCodexAppServerClientInstanceId(client) {
1380
1650
  return client.getInstanceId?.call(client) ?? getCodexAppServerClientInstanceId(client);
1381
1651
  }
1382
- /** RPC error wrapper that preserves app-server error code and data. */
1383
- var CodexAppServerRpcError = class extends Error {
1384
- constructor(error, method) {
1385
- super(formatCodexAppServerRpcErrorMessage(error, method));
1386
- this.name = "CodexAppServerRpcError";
1387
- this.code = error.code;
1388
- this.data = error.data;
1389
- this.method = method;
1390
- }
1391
- };
1392
1652
  var CodexAppServerLocalRequestCancellationError = class extends Error {
1393
1653
  constructor(method, reason, mayHaveWritten) {
1394
1654
  super(`${method} ${reason}`);
@@ -1431,22 +1691,6 @@ function isCodexAppServerPrewriteRequestCancellationError(error) {
1431
1691
  function isCodexAppServerIndeterminateTransportError(error) {
1432
1692
  return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_REQUEST_TRANSPORT_INDETERMINATE" && "mayHaveWritten" in error && error.mayHaveWritten === true;
1433
1693
  }
1434
- function formatCodexAppServerRpcErrorMessage(error, method) {
1435
- const message = error.message || `${method} failed`;
1436
- const detail = readCodexAppServerRpcReloginDetail(error.data);
1437
- return detail && !message.includes(detail) ? `${message}: ${detail}` : message;
1438
- }
1439
- function readCodexAppServerRpcReloginDetail(data) {
1440
- const record = isJsonObject(data) ? data : void 0;
1441
- const nested = isJsonObject(record?.error) ? record.error : record;
1442
- if (!nested) return;
1443
- const isRelogin = nested.action === "relogin" || nested.reason === "cloudRequirements" && nested.errorCode === "Auth";
1444
- const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
1445
- return isRelogin && detail ? detail : void 0;
1446
- }
1447
- function isJsonObject(value) {
1448
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
1449
- }
1450
1694
  /** Returns true for errors that mean the app-server transport is closed. */
1451
1695
  function isCodexAppServerConnectionClosedError(error) {
1452
1696
  if (!(error instanceof Error)) return false;
@@ -1469,8 +1713,8 @@ var CodexAppServerClient = class CodexAppServerClient {
1469
1713
  this.child = child;
1470
1714
  this.lines = createInterface({ input: child.stdout });
1471
1715
  this.lines.on("line", (line) => this.handleLine(line));
1472
- this.lines.on("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
1473
- 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)));
1474
1718
  child.stderr.setEncoding("utf8");
1475
1719
  child.stderr.on("data", (text) => {
1476
1720
  this.stderrTail = appendBoundedTail(this.stderrTail, text, CODEX_APP_SERVER_STDERR_TAIL_MAX);
@@ -1480,12 +1724,12 @@ var CodexAppServerClient = class CodexAppServerClient {
1480
1724
  child.stderr.on("error", (error) => {
1481
1725
  embeddedAgentLog.warn("codex app-server stderr stream failed", { error });
1482
1726
  });
1483
- child.once("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
1727
+ child.once("error", (error) => this.closeWithError(toStringifiedError(error)));
1484
1728
  child.once("exit", (code, signal) => {
1485
1729
  this.transportExited = true;
1486
1730
  this.closeWithError(buildCodexAppServerExitError(code, signal, this.stderrTail));
1487
1731
  });
1488
- 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)));
1489
1733
  }
1490
1734
  /** Starts a new app-server client using resolved runtime start options. */
1491
1735
  static start(options) {
@@ -1531,6 +1775,10 @@ var CodexAppServerClient = class CodexAppServerClient {
1531
1775
  getStderrDiagnostic() {
1532
1776
  return redactCodexAppServerLinePreview(this.stderrTail) || void 0;
1533
1777
  }
1778
+ /** Returns the terminal transport error that closed this physical client. */
1779
+ getCloseError() {
1780
+ return this.closeError;
1781
+ }
1534
1782
  /** Stable generation id for this exact physical client instance. */
1535
1783
  getInstanceId() {
1536
1784
  return this.instanceId;
@@ -1700,7 +1948,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1700
1948
  onWriteAttempt?.();
1701
1949
  this.writeMessage(message, (error) => rejectPending(error));
1702
1950
  } catch (error) {
1703
- rejectPending(error instanceof Error ? error : new Error(String(error)));
1951
+ rejectPending(toStringifiedError(error));
1704
1952
  }
1705
1953
  });
1706
1954
  }
@@ -1871,7 +2119,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1871
2119
  result: defaultServerRequestResponse(request)
1872
2120
  });
1873
2121
  } catch (error) {
1874
- const message = error instanceof Error ? error.message : String(error);
2122
+ const message = coerceErrorMessage(error);
1875
2123
  embeddedAgentLog.warn("codex app-server server request handler failed", {
1876
2124
  id: request.id,
1877
2125
  method: request.method,
@@ -1985,17 +2233,17 @@ var CodexAppServerVersionError = class extends Error {
1985
2233
  };
1986
2234
  function assertSupportedCodexAppServerVersion(response) {
1987
2235
  const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
1988
- if (detectedVersion !== "0.146.0") throw new CodexAppServerVersionError(detectedVersion);
2236
+ if (detectedVersion !== "0.147.0") throw new CodexAppServerVersionError(detectedVersion);
1989
2237
  return detectedVersion;
1990
2238
  }
1991
2239
  function isUnsupportedCodexAppServerVersionError(error) {
1992
2240
  return error instanceof CodexAppServerVersionError;
1993
2241
  }
1994
2242
  function buildCodexAppServerRuntimeIdentity(response, serverVersion) {
1995
- const userAgent = readNonEmptyInitializeString(response.userAgent);
1996
- const codexHome = readNonEmptyInitializeString(response.codexHome);
1997
- const platformFamily = readNonEmptyInitializeString(response.platformFamily);
1998
- 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);
1999
2247
  return {
2000
2248
  serverVersion,
2001
2249
  ...userAgent ? { userAgent } : {},
@@ -2004,10 +2252,6 @@ function buildCodexAppServerRuntimeIdentity(response, serverVersion) {
2004
2252
  ...platformOs ? { platformOs } : {}
2005
2253
  };
2006
2254
  }
2007
- function readNonEmptyInitializeString(value) {
2008
- const trimmed = value?.trim();
2009
- return trimmed ? trimmed : void 0;
2010
- }
2011
2255
  /** Extracts the Codex version from the app-server initialize user-agent field. */
2012
2256
  function readCodexVersionFromUserAgent(userAgent) {
2013
2257
  return (userAgent?.match(/^[^/]+\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)(?:[\s(]|$)/))?.[1];
@@ -2027,7 +2271,7 @@ function buildCodexAppServerExitError(code, signal, stderrTail) {
2027
2271
  }
2028
2272
  function shouldBufferCodexAppServerParseFailure(value, error) {
2029
2273
  if (!value.startsWith("{") && !value.startsWith("[")) return false;
2030
- const message = error instanceof Error ? error.message : String(error);
2274
+ const message = coerceErrorMessage(error);
2031
2275
  return message.includes("Unterminated string") || message.includes("Unexpected end of JSON input");
2032
2276
  }
2033
2277
  function logCodexAppServerParseFailure(value, error, fragmentCount) {
@@ -2035,7 +2279,7 @@ function logCodexAppServerParseFailure(value, error, fragmentCount) {
2035
2279
  const suffix = fragmentCount > 1 ? ` fragments=${fragmentCount}` : "";
2036
2280
  embeddedAgentLog.warn("failed to parse codex app-server message", {
2037
2281
  error,
2038
- errorMessage: error instanceof Error ? error.message : String(error),
2282
+ errorMessage: coerceErrorMessage(error),
2039
2283
  fragmentCount,
2040
2284
  linePreview,
2041
2285
  consoleMessage: `failed to parse codex app-server message${suffix}: preview=${JSON.stringify(linePreview)}`
@@ -2056,6 +2300,103 @@ function formatExitValue(value) {
2056
2300
  return "unknown";
2057
2301
  }
2058
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
2059
2400
  //#region extensions/codex/src/app-server/managed-binary.ts
2060
2401
  /**
2061
2402
  * Resolves the managed Codex app-server binary shipped with or installed beside
@@ -2221,15 +2562,13 @@ function resolveManagedCodexPackageBinCandidate(root) {
2221
2562
  const packageJsonPath = createRequire(path.join(root, "package.json")).resolve(`${MANAGED_CODEX_APP_SERVER_PACKAGE}/package.json`);
2222
2563
  const packageRoot = path.dirname(packageJsonPath);
2223
2564
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
2224
- 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;
2225
2567
  return binPath ? path.resolve(packageRoot, binPath) : null;
2226
2568
  } catch {
2227
2569
  return null;
2228
2570
  }
2229
2571
  }
2230
- function isRecord(value) {
2231
- return typeof value === "object" && value !== null;
2232
- }
2233
2572
  function isDistExtensionRoot(pluginRoot, platform) {
2234
2573
  const pathApi = pathForPlatform(platform);
2235
2574
  const extensionsDir = pathApi.dirname(pluginRoot);
@@ -2325,6 +2664,9 @@ async function waitForPreviousFence(previous, options) {
2325
2664
  * Thin Codex app-server timeout adapter around OpenClaw's shared security
2326
2665
  * runtime timeout helper.
2327
2666
  */
2667
+ function resolveAbortError(signal) {
2668
+ return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
2669
+ }
2328
2670
  /** Awaits a promise with a Codex-specific timeout error message. */
2329
2671
  async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
2330
2672
  return await withTimeout(promise, timeoutMs, {
@@ -2332,6 +2674,22 @@ async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
2332
2674
  ...createError ? { createError } : {}
2333
2675
  });
2334
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
+ }
2335
2693
  //#endregion
2336
2694
  //#region extensions/codex/src/app-server/shared-client.ts
2337
2695
  /**
@@ -2358,7 +2716,6 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
2358
2716
  withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
2359
2717
  });
2360
2718
  const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
2361
- const suspectClosedClients = /* @__PURE__ */ new WeakSet();
2362
2719
  const SHARED_CODEX_APP_SERVER_CLIENT_STATE = Symbol.for("openclaw.codexAppServerClientState");
2363
2720
  const SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER = Symbol.for("openclaw.codexAppServerClientDisposer");
2364
2721
  const CODEX_APP_SERVER_CLIENT_START_METADATA = Symbol.for("openclaw.codexAppServerClientStartMetadata");
@@ -2366,6 +2723,8 @@ function getSharedCodexAppServerClientState() {
2366
2723
  const globalState = globalThis;
2367
2724
  globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
2368
2725
  clients: /* @__PURE__ */ new Map(),
2726
+ liveClients: /* @__PURE__ */ new Set(),
2727
+ entriesByClient: /* @__PURE__ */ new WeakMap(),
2369
2728
  leasedReleases: /* @__PURE__ */ new WeakMap()
2370
2729
  };
2371
2730
  return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
@@ -2480,9 +2839,11 @@ async function resolveCodexAppServerClientStartContext(options) {
2480
2839
  startOptions: requestedStartOptions,
2481
2840
  agentDir
2482
2841
  })),
2842
+ agentId: options?.agentId,
2483
2843
  agentDir,
2484
2844
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
2485
2845
  ...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
2846
+ authRequirement,
2486
2847
  config: options?.config,
2487
2848
  pluginConfig: options?.pluginConfig,
2488
2849
  ...authProfileStore ? { authProfileStore } : {}
@@ -2725,14 +3086,20 @@ function createSharedCodexAppServerClientStartup(params) {
2725
3086
  runtimeArtifactSignal: params.runtimeArtifactSignal,
2726
3087
  config: params.config,
2727
3088
  onStartedClient: (startedClient) => {
3089
+ const state = getSharedCodexAppServerClientState();
2728
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
+ });
2729
3097
  for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
2730
3098
  retirePendingSharedClientEntryIfUnclaimed(params.key, params.entry);
2731
3099
  },
2732
3100
  onInitializedClient: () => initialized.resolve()
2733
3101
  }).then((client) => {
2734
3102
  params.entry.client = client;
2735
- client.addCloseHandler((closedClient) => clearSharedClientEntryIfCurrent(params.key, closedClient));
2736
3103
  return client;
2737
3104
  }, (error) => {
2738
3105
  initialized.reject(error);
@@ -2772,7 +3139,7 @@ async function startInitializedCodexAppServerClient(params) {
2772
3139
  const timeoutMs = params.timeoutMs ?? 0;
2773
3140
  const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
2774
3141
  for (const [index, startOptions] of startOptionsCandidates.entries()) {
2775
- const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-CmtG_t1L.js") : void 0;
3142
+ const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BVSPdd6g.js") : void 0;
2776
3143
  const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
2777
3144
  const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
2778
3145
  startOptions,
@@ -2922,11 +3289,9 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
2922
3289
  entry.closeWhenIdle = true;
2923
3290
  if (opts?.failActiveLeases) {
2924
3291
  entry.closeError = /* @__PURE__ */ new Error("codex app-server client is closed");
2925
- const closed = closeRetiredSharedClientEntry(entry);
2926
- if (closed) suspectClosedClients.add(client);
2927
3292
  return {
2928
3293
  activeLeases: entry.activeLeases,
2929
- closed
3294
+ closed: closeRetiredSharedClientEntry(entry)
2930
3295
  };
2931
3296
  }
2932
3297
  const closed = closeRetiredSharedClientEntryIfIdle(entry);
@@ -2935,18 +3300,17 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
2935
3300
  closed
2936
3301
  };
2937
3302
  }
2938
- const activeLeases = state.leasedReleases.get(client)?.length ?? 0;
2939
- if (activeLeases > 0) {
2940
- if (opts?.failActiveLeases && !suspectClosedClients.has(client)) {
2941
- suspectClosedClients.add(client);
2942
- 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");
2943
3307
  return {
2944
- activeLeases,
2945
- closed: true
3308
+ activeLeases: detachedEntry.activeLeases,
3309
+ closed: closeRetiredSharedClientEntry(detachedEntry)
2946
3310
  };
2947
3311
  }
2948
3312
  return {
2949
- activeLeases,
3313
+ activeLeases: detachedEntry.activeLeases,
2950
3314
  closed: false
2951
3315
  };
2952
3316
  }
@@ -3063,7 +3427,7 @@ function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
3063
3427
  closeRetiredSharedClientEntry(entry);
3064
3428
  }
3065
3429
  function collectSharedClients(state) {
3066
- return [...new Set([...state.clients.values()].map((entry) => entry.client).filter((client) => Boolean(client)))];
3430
+ return [...state.liveClients];
3067
3431
  }
3068
3432
  //#endregion
3069
- export { resolveCodexTurnAssistantCompletionIdleTimeoutMs as $, retainCodexAppServerLiveThread as A, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as B, isCodexAppServerIndeterminateRequestCancellationError as C, resolveCodexAppServerClientInstanceId as D, isCodexAppServerRequestTimeoutError as E, resolveCodexAppServerAuthProfileId as F, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as G, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as H, resolveCodexAppServerAuthProfileIdForAgent as I, CodexAppServerStartupError as J, resolveCodexAppServerHomeDir as K, resolveCodexAppServerFallbackApiKeyCacheKey as L, readRecentCodexRateLimits as M, rememberCodexRateLimitsRead as N, consumeCodexAppServerLiveThread as O, resolveCodexAppServerAuthAccountCacheKey as P, resolveCodexStartupTimeoutMs as Q, resolveCodexAppServerPreparedApiKeyCacheKey as R, isCodexAppServerConnectionClosedError as S, isCodexAppServerPrewriteRequestCancellationError as T, flattenCodexDynamicToolFunctions as U, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as V, isJsonObject$1 as W, resolveCodexGatewayTimeoutWithGraceMs as X, isCodexAppServerStartupError as Y, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as Z, acquireCodexNativeConfigFence as _, getLeasedSharedCodexAppServerClient as a, isCodexAppServerApprovalRequest as b, releaseCodexAppServerClientLease as c, retainSharedCodexAppServerClientByInstanceId as d, resolveCodexTurnCompletionIdleTimeoutMs as et, retainSharedCodexAppServerClientIfCurrent as f, withTimeout$1 as g, withLeasedCodexAppServerClientStartSelectionRetry as h, createIsolatedCodexAppServerClient as i, readCodexRateLimitsRevision as j, ensureCodexAppServerClientRuntime as k, releaseLeasedSharedCodexAppServerClient as l, shared_client_exports as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, withCodexStartupTimeout as nt, getSharedCodexAppServerClient as o, retireSharedCodexAppServerClientIfCurrent as p, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as q, clearSharedCodexAppServerClientIfCurrentAndWait as r, isCodexAppServerStartSelectionChangedError as s, clearSharedCodexAppServerClientIfCurrent as t, resolveCodexTurnTerminalIdleTimeoutMs as tt, resolveCodexNativeConfigFenceKey as u, CodexAppServerRpcError as v, isCodexAppServerIndeterminateTransportError as w, isCodexAppServerBrokenPipeError as x, getCodexAppServerClientInstanceId as y, resolveCodexAppServerPreparedAuthHandoff 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 };