@openclaw/codex 2026.7.2-beta.7 → 2026.8.1-beta.3

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 (68) hide show
  1. package/README.md +2 -0
  2. package/dist/api.js +147 -0
  3. package/dist/app-server-policy-bg5QPSVP.js +25 -0
  4. package/dist/attempt-client-cleanup-URjUlCHg.js +735 -0
  5. package/dist/binding-connection-DAvZwp96.js +45 -0
  6. package/dist/{bounded-turn-D2ZAgZVz.js → bounded-turn-4BjrZEwW.js} +63 -56
  7. package/dist/{capabilities-BFDTVYr4.js → capabilities-CH3K57v4.js} +8 -2
  8. package/dist/cli-metadata.js +1 -1
  9. package/dist/{command-formatters-C336eRVS.js → command-formatters-D0nZ3qtd.js} +18 -18
  10. package/dist/{command-handlers-DAHUSry9.js → command-handlers-DC86lgT4.js} +507 -101
  11. package/dist/command-rpc-xUIIonsO.js +90 -0
  12. package/dist/{compact-D_uvHurS.js → compact-COqgBRny.js} +234 -214
  13. package/dist/{session-binding-BXpo9ymA.js → config-CpNeo5AE.js} +284 -1163
  14. package/dist/context-compaction-activity-BOeJf7yo.js +58 -0
  15. package/dist/{conversation-binding-data-BHRpYbX3.js → conversation-binding-data-CW2GuupV.js} +6 -10
  16. package/dist/doctor-contract-api.js +48 -50
  17. package/dist/{dynamic-tools-D2gYkSBO.js → dynamic-tools-fJZhl4CE.js} +3442 -1433
  18. package/dist/{effective-mcp-catalog-Bbd98Inw.js → effective-mcp-catalog-4YKn51aG.js} +20 -15
  19. package/dist/elicitation-input-DMkeki8d.js +71 -0
  20. package/dist/{event-projector-assistant-message-DTgnPPVF.js → event-projector-assistant-message-BwxV4d6N.js} +11 -23
  21. package/dist/{attempt-notifications-AP-a-Q5k.js → event-projector-usage-CLwNEJqW.js} +113 -29
  22. package/dist/harness-DoKQcF8n.js +258 -0
  23. package/dist/harness.js +2 -160
  24. package/dist/image-payload-sanitizer-CnfoUPCE.js +65 -0
  25. package/dist/index.js +730 -256
  26. package/dist/isolated-completion-DPMtlH9r.js +75 -0
  27. package/dist/local-runtime-attribution-B_2mnqhb.js +19 -0
  28. package/dist/managed-binary-By4FZC6i.js +212 -0
  29. package/dist/media-understanding-provider.js +5 -5
  30. package/dist/model-catalog-CmGoqVKB.js +48 -0
  31. package/dist/{models-BBMKU4jA.js → models-CZw1wClT.js} +12 -17
  32. package/dist/native-subagent-monitor-GGq3RnOo.js +3225 -0
  33. package/dist/node-exec-server.runtime-Bsu1LdFZ.js +192 -0
  34. package/dist/{plugin-app-cache-key-DztPafOA.js → plugin-app-cache-key-DMNQ8Zi8.js} +18 -6
  35. package/dist/plugin-approval-roundtrip-C75Yiiwc.js +95 -0
  36. package/dist/plugin-marketplace-discovery-CSWDkMrP.js +399 -0
  37. package/dist/{computer-use-CYEc8U3w.js → plugin-metadata-cache-BXH5NF-4.js} +109 -6
  38. package/dist/protocol-validators-VAVyEnDp.js +2399 -0
  39. package/dist/{rate-limits-CU-gVCCS.js → rate-limits-BI1dMHDG.js} +17 -35
  40. package/dist/{request-qKh331yu.js → request-DSK0W6YI.js} +3 -3
  41. package/dist/{run-attempt-CY9WlDLc.js → run-attempt-BV3HzuCf.js} +3416 -7194
  42. package/dist/{runtime-artifact-CmtG_t1L.js → runtime-artifact-BvyT1uyd.js} +11 -29
  43. package/dist/{sandbox-guard-NuhpXmPB.js → sandbox-guard-C1qEeZ5C.js} +29 -30
  44. package/dist/session-binding-BTrLf9Qm.js +874 -0
  45. package/dist/{session-catalog-DyOgR4WH.js → session-catalog-ezP_UcRM.js} +1584 -1289
  46. package/dist/session-catalog-home-id-BNwAJ9D7.js +18 -0
  47. package/dist/{session-cli-2PZaFLRh.js → session-cli-B1hKvfkI.js} +20 -7
  48. package/dist/session-retirement-CE4iMiLh.js +36 -0
  49. package/dist/{session-upstream-marker-B6U7tLw5.js → session-upstream-marker-D_2npHlA.js} +1 -1
  50. package/dist/{settled-turn-finalizer-CXY2Z9dT.js → settled-turn-finalizer-Dx1onIqg.js} +40 -28
  51. package/dist/{shared-client-DTbvH-XV.js → shared-client-Dw-rzii0.js} +1315 -469
  52. package/dist/{side-question-_ulBk1AL.js → side-question-DbXkJZzg.js} +140 -69
  53. package/dist/{thread-lifecycle-DF53ogiA.js → thread-lifecycle-DKdrnTMX.js} +927 -768
  54. package/dist/thread-ownership-i2NBwDUL.js +92 -0
  55. package/dist/thread-resume-CdJk2Ehf.js +30 -0
  56. package/dist/transcript-mirror-BU0_LKAL.js +588 -0
  57. package/dist/{transport-stdio-DXgGZ7Ib.js → transport-stdio-DhoGy33Z.js} +13 -2
  58. package/dist/upstream-prompt-provenance-CFmsKwr9.js +39 -0
  59. package/dist/{upstream-session-fork-Dy4Xvuoj.js → upstream-session-fork-BDVJCLAD.js} +14 -8
  60. package/dist/{usage-CQ6GhD5-.js → usage-9dKJCgcy.js} +3 -3
  61. package/dist/{web-search-provider.runtime-CCFhg2z5.js → web-search-provider.runtime-DCdx4DGu.js} +8 -9
  62. package/openclaw.plugin.json +49 -4
  63. package/package.json +6 -5
  64. package/dist/app-server-policy-BSaO2JIT.js +0 -49
  65. package/dist/conversation-control-xsIeXbo4.js +0 -287
  66. package/dist/event-projector-usage-Xp4AlR2k.js +0 -86
  67. package/dist/notification-correlation-CU6_AnOb.js +0 -67
  68. package/dist/transcript-mirror-DzsdNCf5.js +0 -6924
@@ -1,133 +1,46 @@
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";
4
- import { createRequire } from "node:module";
5
- import fs, { access } from "node:fs/promises";
2
+ import { a as resolveCodexAppServerStartOptionsForAgent, i as resolveCodexAppServerRuntimeOptions, o as resolveCodexComputerUseConfig, t as codexAppServerStartOptionsKey, x as resolveCodexAppServerUserHomeDir } from "./config-CpNeo5AE.js";
3
+ import { o as isCodexAppServerNativeAuthProfile } from "./session-binding-BTrLf9Qm.js";
4
+ import { n as resolveCodexAppServerSpawnEnv, t as createStdioTransport } from "./transport-stdio-DhoGy33Z.js";
5
+ import { a as CODEX_APP_SERVER_VERSION, n as resolveManagedCodexAppServerStartOptions, o as MIN_SUPPORTED_CODEX_APP_SERVER_VERSION, r as resolveManagedCodexNativeCommand } from "./managed-binary-By4FZC6i.js";
6
+ import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import fs from "node:fs/promises";
6
8
  import path from "node:path";
9
+ import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
7
10
  import { addTimerTimeoutGraceMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
8
- import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
11
+ import { AgentHarnessPreflightError, OPENCLAW_VERSION, embeddedAgentLog, formatErrorMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
9
12
  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
- import fsSync, { constants, existsSync, readFileSync, realpathSync } from "node:fs";
13
+ import fsSync, { constants, existsSync } from "node:fs";
13
14
  import os from "node:os";
14
15
  import { isDeepStrictEqual } from "node:util";
16
+ import { ensureAuthProfileStore, findPersistedAuthProfileCredential, loadAuthProfileStoreForSecretsRuntime, refreshOAuthCredentialForRuntime, resolveApiKeyForProfile, resolveAuthProfileOrder, resolveDefaultAgentDir, resolvePersistedAuthProfileOwnerAgentDir, resolveProviderIdForAuth } from "openclaw/plugin-sdk/agent-runtime";
15
17
  import { hasUsableOAuthCredential } from "openclaw/plugin-sdk/provider-auth";
16
18
  import { readSecretFile } from "openclaw/plugin-sdk/secret-file";
19
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
17
20
  import { runExec } from "openclaw/plugin-sdk/process-runtime";
21
+ import { assertNoSymlinkParents, withTimeout } from "openclaw/plugin-sdk/security-runtime";
22
+ import { execFile } from "node:child_process";
18
23
  import { createInterface } from "node:readline";
24
+ import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
19
25
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
+ import { parse } from "semver";
20
27
  import { EventEmitter } from "node:events";
21
28
  import net from "node:net";
22
29
  import { PassThrough, Writable } from "node:stream";
23
30
  import { StringDecoder } from "node:string_decoder";
24
31
  import WebSocket from "ws";
25
- import { fileURLToPath } from "node:url";
26
- import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
27
- 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
32
  //#region extensions/codex/src/app-server/auth-start-options.ts
126
33
  const CODEX_APP_SERVER_HOME_DIRNAME = "codex-home";
127
34
  const CODEX_EPHEMERAL_AUTH_STORE_OVERRIDE = "cli_auth_credentials_store=\"ephemeral\"";
128
35
  function resolveCodexAppServerHomeDir(agentDir) {
129
36
  return path.join(path.resolve(agentDir), CODEX_APP_SERVER_HOME_DIRNAME);
130
37
  }
38
+ /** Resolves the local CODEX_HOME used when starting one app-server connection. */
39
+ function resolveCodexAppServerLocalHomeDir(startOptions, agentDir, env = process.env) {
40
+ const configured = startOptions.env?.CODEX_HOME;
41
+ if (configured?.trim()) return configured;
42
+ return startOptions.homeScope === "user" ? resolveCodexAppServerUserHomeDir(env) : resolveCodexAppServerHomeDir(agentDir);
43
+ }
131
44
  /** Forces OpenClaw-owned Codex auth to remain process-local. */
132
45
  function withEphemeralCodexAuthStore(params) {
133
46
  const { startOptions } = params;
@@ -268,108 +181,385 @@ function skippedCacheResult(status, message) {
268
181
  };
269
182
  }
270
183
  //#endregion
184
+ //#region extensions/codex/src/app-server/computer-use-service-path.ts
185
+ /** Filesystem ownership guards for isolated Computer Use service provisioning. */
186
+ async function assertOwnedServicePath(params) {
187
+ await assertOwnedCodexHomePath({
188
+ ownershipRoot: params.ownershipRoot,
189
+ codexHome: params.codexHome,
190
+ allowMissing: true
191
+ });
192
+ assertPathAtOrInside(params.ownershipRoot, params.targetParent, "Computer Use service parent");
193
+ await assertNoSymlinkParents({
194
+ rootDir: params.ownershipRoot,
195
+ targetPath: params.targetParent,
196
+ allowMissing: true,
197
+ requireDirectories: true,
198
+ messagePrefix: "Computer Use service path"
199
+ });
200
+ await assertNotSymlink(params.targetPath, "Computer Use service target");
201
+ }
202
+ async function ensureOwnedCodexHome(codexHomeInput, ownershipRootInput = path.dirname(path.resolve(codexHomeInput))) {
203
+ const codexHome = path.resolve(codexHomeInput);
204
+ const ownershipRoot = path.resolve(ownershipRootInput);
205
+ await fs.mkdir(ownershipRoot, {
206
+ recursive: true,
207
+ mode: 448
208
+ });
209
+ await assertOwnedCodexHomePath({
210
+ ownershipRoot,
211
+ codexHome,
212
+ allowMissing: true
213
+ });
214
+ await ensureRealDirectoryTree(ownershipRoot, codexHome, "isolated Codex home");
215
+ await assertOwnedCodexHomePath({
216
+ ownershipRoot,
217
+ codexHome,
218
+ allowMissing: false
219
+ });
220
+ }
221
+ async function prepareOwnedServiceParent(params) {
222
+ await ensureOwnedCodexHome(params.codexHome, params.ownershipRoot);
223
+ await ensureRealDirectoryTree(params.ownershipRoot, params.targetParent, "Computer Use service parent");
224
+ await assertNoSymlinkParents({
225
+ rootDir: params.ownershipRoot,
226
+ targetPath: params.targetParent,
227
+ allowMissing: false,
228
+ requireDirectories: true,
229
+ messagePrefix: "Computer Use service path"
230
+ });
231
+ const [rootIdentity, parentIdentity] = await Promise.all([readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root"), readRealDirectoryIdentity(params.targetParent, "Computer Use service parent")]);
232
+ assertPathAtOrInside(rootIdentity.realPath, parentIdentity.realPath, "canonical Computer Use service parent");
233
+ return parentIdentity;
234
+ }
235
+ async function assertOwnedCodexHomePath(params) {
236
+ await readRealDirectoryIdentity(params.ownershipRoot, "Computer Use ownership root");
237
+ assertPathAtOrInside(params.ownershipRoot, params.codexHome, "isolated Codex home");
238
+ await assertNoSymlinkParents({
239
+ rootDir: params.ownershipRoot,
240
+ targetPath: params.codexHome,
241
+ allowMissing: params.allowMissing,
242
+ requireDirectories: true,
243
+ messagePrefix: "Computer Use service path"
244
+ });
245
+ }
246
+ async function readRealDirectoryIdentity(directoryPath, label) {
247
+ const logicalPath = path.resolve(directoryPath);
248
+ const before = await fs.lstat(logicalPath);
249
+ if (before.isSymbolicLink() || !before.isDirectory()) throw new Error(`${label} must be a real directory: ${logicalPath}`);
250
+ const realPath = await fs.realpath(logicalPath);
251
+ const [after, resolved] = await Promise.all([fs.lstat(logicalPath), fs.lstat(realPath)]);
252
+ if (after.isSymbolicLink() || !after.isDirectory() || !resolved.isDirectory() || before.dev !== after.dev || before.ino !== after.ino || after.dev !== resolved.dev || after.ino !== resolved.ino) throw new Error(`${label} changed while its ownership boundary was being established.`);
253
+ return {
254
+ logicalPath,
255
+ realPath,
256
+ dev: after.dev,
257
+ ino: after.ino
258
+ };
259
+ }
260
+ async function assertOwnedServiceParentStable(parent) {
261
+ await assertDirectoryIdentityStable(parent, "Computer Use service parent");
262
+ }
263
+ async function assertDirectoryIdentityStable(expected, label) {
264
+ if (!await directoryIdentityIsStable(expected)) throw new Error(`${label} changed during refresh; refusing to mutate the replacement path.`);
265
+ }
266
+ async function ownedServiceParentIsStable(parent) {
267
+ return await directoryIdentityIsStable(parent);
268
+ }
269
+ async function directoryIdentityIsStable(expected) {
270
+ try {
271
+ const current = await fs.lstat(expected.logicalPath);
272
+ if (current.isSymbolicLink() || !current.isDirectory() || current.dev !== expected.dev || current.ino !== expected.ino) return false;
273
+ return await fs.realpath(expected.logicalPath) === expected.realPath;
274
+ } catch {
275
+ return false;
276
+ }
277
+ }
278
+ async function assertNotSymlink(filePath, label) {
279
+ try {
280
+ if ((await fs.lstat(filePath)).isSymbolicLink()) throw new Error(`${label} must not be a symbolic link: ${filePath}`);
281
+ } catch (error) {
282
+ if (hasNodeErrorCode(error, "ENOENT")) return;
283
+ throw error;
284
+ }
285
+ }
286
+ async function ensureRealDirectoryTree(ownershipRoot, directoryPath, label) {
287
+ const root = path.resolve(ownershipRoot);
288
+ const target = path.resolve(directoryPath);
289
+ assertPathAtOrInside(root, target, label);
290
+ const relative = path.relative(root, target);
291
+ let current = root;
292
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
293
+ current = path.join(current, segment);
294
+ const existing = await fs.lstat(current).catch((error) => {
295
+ if (hasNodeErrorCode(error, "ENOENT")) return;
296
+ throw error;
297
+ });
298
+ if (!existing) {
299
+ try {
300
+ await fs.mkdir(current, { mode: 448 });
301
+ } catch (error) {
302
+ if (!hasNodeErrorCode(error, "EEXIST")) throw error;
303
+ }
304
+ const created = await fs.lstat(current);
305
+ if (created.isSymbolicLink() || !created.isDirectory()) throw new Error(`${label} changed while its directory tree was being created: ${current}`);
306
+ } else if (existing.isSymbolicLink() || !existing.isDirectory()) throw new Error(`${label} must traverse real directories: ${current}`);
307
+ }
308
+ await assertNoSymlinkParents({
309
+ rootDir: root,
310
+ targetPath: target,
311
+ allowMissing: false,
312
+ requireDirectories: true,
313
+ messagePrefix: "Computer Use service path"
314
+ });
315
+ await readRealDirectoryIdentity(target, label);
316
+ }
317
+ function assertPathAtOrInside(rootPath, candidatePath, label) {
318
+ const relative = path.relative(path.resolve(rootPath), path.resolve(candidatePath));
319
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} must remain inside ${path.resolve(rootPath)}.`);
320
+ }
321
+ function hasNodeErrorCode(error, code) {
322
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
323
+ }
324
+ //#endregion
271
325
  //#region extensions/codex/src/app-server/computer-use-service.ts
272
326
  /** Native Computer Use service provisioning for isolated Codex homes. */
273
327
  const SERVICE_APP_NAME = "Codex Computer Use.app";
274
- const CLIENT_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app", "Contents", "MacOS", "SkyComputerUseClient");
328
+ const SERVICE_BUNDLE_ID = "com.openai.sky.CUAService";
329
+ const CLIENT_BUNDLE_ID = "com.openai.sky.CUAService.cli";
330
+ const OPENAI_TEAM_ID = "2DC432GLL2";
331
+ const CLIENT_APP_RELATIVE_PATH = path.join("Contents", "SharedSupport", "SkyComputerUseClient.app");
332
+ const CLIENT_RELATIVE_PATH = path.join(CLIENT_APP_RELATIVE_PATH, "Contents", "MacOS", "SkyComputerUseClient");
275
333
  const COPY_TIMEOUT_MS = 12e4;
334
+ const INSPECT_TIMEOUT_MS = 3e4;
335
+ const COMPLETED_SYNC_CACHE_MAX_ENTRIES = 64;
276
336
  const activeInstalls = /* @__PURE__ */ new Map();
277
- /** Ensures the official native client exists beneath the CODEX_HOME used by its launcher. */
337
+ const completedSyncs = /* @__PURE__ */ new Map();
338
+ /** Synchronizes the CODEX_HOME native client with the selected signed desktop distribution. */
278
339
  async function ensureCodexComputerUseServiceApp(params) {
279
340
  const platform = params.platform ?? process.platform;
280
341
  if (platform !== "darwin") return {
281
342
  status: "unsupported",
282
343
  changed: false
283
344
  };
284
- const targetPath = path.join(path.resolve(params.codexHome), "computer-use", SERVICE_APP_NAME);
345
+ const codexHome = path.resolve(params.codexHome);
346
+ const ownershipRoot = path.resolve(params.ownershipRoot ?? path.dirname(codexHome));
347
+ const targetParent = path.join(codexHome, "computer-use");
348
+ const targetPath = path.join(targetParent, SERVICE_APP_NAME);
349
+ await ensureOwnedCodexHome(codexHome, ownershipRoot);
350
+ await assertOwnedServicePath({
351
+ ownershipRoot,
352
+ codexHome,
353
+ targetParent,
354
+ targetPath
355
+ });
356
+ const candidates = params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(platform, params.appServerCommand);
357
+ const syncKey = [targetPath, ...candidates].join("\0");
358
+ const completed = completedSyncs.get(targetPath);
359
+ if (completed?.syncKey === syncKey) {
360
+ completedSyncs.delete(targetPath);
361
+ completedSyncs.set(targetPath, completed);
362
+ return {
363
+ status: "already_current",
364
+ changed: false,
365
+ targetPath: completed.targetPath,
366
+ sourcePath: completed.sourcePath,
367
+ sourceBuild: completed.sourceBuild
368
+ };
369
+ }
285
370
  const active = activeInstalls.get(targetPath);
286
- if (active) return await active;
371
+ if (active) {
372
+ if (active.syncKey === syncKey) return await active.promise;
373
+ await active.promise.catch(() => void 0);
374
+ return await ensureCodexComputerUseServiceApp(params);
375
+ }
376
+ if (completed) completedSyncs.delete(targetPath);
287
377
  const install = ensureCodexComputerUseServiceAppOnce({
288
378
  ...params,
379
+ codexHome,
380
+ ownershipRoot,
381
+ targetParent,
289
382
  targetPath,
290
- platform
383
+ platform,
384
+ sourceAppCandidates: candidates
385
+ }).then((result) => {
386
+ if (isCompletedSyncStatus(result.status)) {
387
+ completedSyncs.delete(targetPath);
388
+ completedSyncs.set(targetPath, {
389
+ syncKey,
390
+ targetPath: result.targetPath,
391
+ sourcePath: result.sourcePath,
392
+ sourceBuild: result.sourceBuild
393
+ });
394
+ pruneMapToMaxSize(completedSyncs, COMPLETED_SYNC_CACHE_MAX_ENTRIES);
395
+ }
396
+ return result;
291
397
  });
292
- activeInstalls.set(targetPath, install);
293
- try {
294
- return await install;
295
- } finally {
296
- if (activeInstalls.get(targetPath) === install) activeInstalls.delete(targetPath);
297
- }
398
+ const activeEntry = {
399
+ syncKey,
400
+ promise: install
401
+ };
402
+ activeInstalls.set(targetPath, activeEntry);
403
+ const clearActive = () => {
404
+ if (activeInstalls.get(targetPath) === activeEntry) activeInstalls.delete(targetPath);
405
+ };
406
+ install.then(clearActive, clearActive);
407
+ return await install;
408
+ }
409
+ function isCompletedSyncStatus(status) {
410
+ return status === "installed" || status === "refreshed" || status === "already_current";
298
411
  }
299
412
  async function ensureCodexComputerUseServiceAppOnce(params) {
300
- if (await hasExecutableClient(params.targetPath)) return {
301
- status: "already_installed",
413
+ const inspectServiceApp = params.inspectServiceApp ?? inspectTrustedServiceApp;
414
+ const source = await findUsableServiceApp(params.sourceAppCandidates ?? [], inspectServiceApp);
415
+ if (!source) return {
416
+ status: "source_missing",
302
417
  changed: false,
303
418
  targetPath: params.targetPath
304
419
  };
305
- const sourcePath = await findUsableServiceApp(params.sourceAppCandidates ?? resolveMacOSDesktopCodexComputerUseServiceAppCandidates(params.platform, params.appServerCommand));
306
- if (!sourcePath) return {
307
- status: "source_missing",
420
+ const { path: sourcePath, identity: sourceIdentity } = source;
421
+ const ownedParent = await prepareOwnedServiceParent({
422
+ ownershipRoot: params.ownershipRoot,
423
+ codexHome: params.codexHome,
424
+ targetParent: params.targetParent
425
+ });
426
+ const operationTargetPath = path.join(ownedParent.realPath, SERVICE_APP_NAME);
427
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
428
+ const initialTarget = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
429
+ if (initialTarget.identity && identitiesMatch(initialTarget.identity, sourceIdentity)) return {
430
+ status: "already_current",
308
431
  changed: false,
309
- targetPath: params.targetPath
432
+ targetPath: params.targetPath,
433
+ sourcePath,
434
+ sourceBuild: sourceIdentity.build
310
435
  };
311
- const targetParent = path.dirname(params.targetPath);
312
- await fs.mkdir(targetParent, { recursive: true });
313
- const stagingRoot = await fs.mkdtemp(path.join(targetParent, ".service-app.staging-"));
436
+ await assertOwnedServiceParentStable(ownedParent);
437
+ const stagingRoot = await fs.mkdtemp(path.join(ownedParent.realPath, ".service-app.staging-"));
438
+ const stagingRootIdentity = await readRealDirectoryIdentity(stagingRoot, "Computer Use service staging directory");
314
439
  const stagedPath = path.join(stagingRoot, SERVICE_APP_NAME);
315
- const backupPath = path.join(targetParent, `.service-app.backup-${process.pid}-${Date.now()}`);
440
+ const backupPath = path.join(ownedParent.realPath, `.service-app.backup-${process.pid}-${Date.now()}`);
316
441
  let backupCreated = false;
317
442
  try {
318
443
  await (params.copyServiceApp ?? copyServiceAppWithDitto)(sourcePath, stagedPath);
319
- if (!await hasExecutableClient(stagedPath)) throw new Error(`Copied Computer Use service app is incomplete at ${stagedPath}.`);
320
- if (await pathExists$1(params.targetPath)) {
321
- await fs.rename(params.targetPath, backupPath);
444
+ await assertOwnedServiceParentStable(ownedParent);
445
+ await assertDirectoryIdentityStable(stagingRootIdentity, "Computer Use service staging directory");
446
+ await assertNotSymlink(stagedPath, "Copied Computer Use service app");
447
+ const stagedIdentity = await inspectServiceApp(stagedPath);
448
+ if (!stagedIdentity || !identitiesMatch(stagedIdentity, sourceIdentity)) throw new Error(`Copied Computer Use service app at ${stagedPath} does not match its selected signed source.`);
449
+ const stagedSnapshot = await readServiceAppSnapshot(stagedPath, inspectServiceApp);
450
+ const currentSourceIdentity = await inspectServiceApp(sourcePath);
451
+ await assertOwnedServiceParentStable(ownedParent);
452
+ if (!currentSourceIdentity || !identitiesMatch(currentSourceIdentity, sourceIdentity)) throw new Error("Selected Computer Use service source changed during refresh.");
453
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
454
+ if (await pathExists$1(operationTargetPath)) {
455
+ await assertOwnedServiceParentStable(ownedParent);
456
+ await fs.rename(operationTargetPath, backupPath);
457
+ await assertOwnedServiceParentStable(ownedParent);
322
458
  backupCreated = true;
323
- if (await hasExecutableClient(backupPath)) {
324
- await fs.rename(backupPath, params.targetPath);
459
+ const movedTarget = await readServiceAppSnapshot(backupPath, inspectServiceApp);
460
+ if (movedTarget.identity && identitiesMatch(movedTarget.identity, sourceIdentity)) {
461
+ await assertOwnedServiceParentStable(ownedParent);
462
+ await fs.rename(backupPath, operationTargetPath);
463
+ await assertOwnedServiceParentStable(ownedParent);
325
464
  backupCreated = false;
326
465
  return {
327
- status: "already_installed",
466
+ status: "already_current",
328
467
  changed: false,
329
468
  targetPath: params.targetPath,
330
- sourcePath
469
+ sourcePath,
470
+ sourceBuild: sourceIdentity.build
331
471
  };
332
472
  }
473
+ if (!snapshotsMatch(initialTarget, movedTarget)) {
474
+ await assertOwnedServiceParentStable(ownedParent);
475
+ await fs.rename(backupPath, operationTargetPath);
476
+ await assertOwnedServiceParentStable(ownedParent);
477
+ backupCreated = false;
478
+ throw new Error("Computer Use service target changed to an unexpected generation during refresh.");
479
+ }
333
480
  }
334
481
  try {
335
- await fs.rename(stagedPath, params.targetPath);
482
+ await assertOwnedServiceParentStable(ownedParent);
483
+ await fs.rename(stagedPath, operationTargetPath);
484
+ await assertOwnedServiceParentStable(ownedParent);
336
485
  } catch (error) {
337
- if (!await hasExecutableClient(params.targetPath)) {
486
+ await assertOwnedServiceParentStable(ownedParent);
487
+ await assertNotSymlink(operationTargetPath, "Computer Use service target");
488
+ const winnerIdentity = await inspectServiceApp(operationTargetPath);
489
+ if (!winnerIdentity || !identitiesMatch(winnerIdentity, sourceIdentity)) {
338
490
  if (backupCreated) {
339
- await fs.rename(backupPath, params.targetPath);
340
- backupCreated = false;
491
+ if (!await pathExists$1(operationTargetPath)) {
492
+ await assertOwnedServiceParentStable(ownedParent);
493
+ await fs.rename(backupPath, operationTargetPath);
494
+ await assertOwnedServiceParentStable(ownedParent);
495
+ backupCreated = false;
496
+ }
341
497
  }
342
498
  throw error;
343
499
  }
344
500
  if (backupCreated) {
501
+ await assertOwnedServiceParentStable(ownedParent);
502
+ await assertNotSymlink(backupPath, "Computer Use service backup");
345
503
  await fs.rm(backupPath, {
346
504
  recursive: true,
347
505
  force: true
348
506
  });
507
+ await assertOwnedServiceParentStable(ownedParent);
349
508
  backupCreated = false;
350
509
  }
351
510
  return {
352
- status: "already_installed",
511
+ status: "already_current",
353
512
  changed: false,
354
513
  targetPath: params.targetPath,
355
- sourcePath
514
+ sourcePath,
515
+ sourceBuild: sourceIdentity.build
356
516
  };
357
517
  }
518
+ await assertNotSymlink(operationTargetPath, "Installed Computer Use service app");
519
+ const installedSnapshot = await readServiceAppSnapshot(operationTargetPath, inspectServiceApp);
520
+ if (!installedSnapshot.identity || !identitiesMatch(installedSnapshot.identity, sourceIdentity)) {
521
+ if (filesystemSnapshotsMatch(installedSnapshot, stagedSnapshot)) {
522
+ await assertOwnedServiceParentStable(ownedParent);
523
+ await fs.rm(operationTargetPath, {
524
+ recursive: true,
525
+ force: true
526
+ });
527
+ await assertOwnedServiceParentStable(ownedParent);
528
+ if (backupCreated) {
529
+ await fs.rename(backupPath, operationTargetPath);
530
+ await assertOwnedServiceParentStable(ownedParent);
531
+ backupCreated = false;
532
+ }
533
+ }
534
+ throw new Error("Installed Computer Use service app failed post-install identity verification.");
535
+ }
358
536
  if (backupCreated) {
537
+ await assertOwnedServiceParentStable(ownedParent);
538
+ await assertNotSymlink(backupPath, "Computer Use service backup");
359
539
  await fs.rm(backupPath, {
360
540
  recursive: true,
361
541
  force: true
362
542
  });
543
+ await assertOwnedServiceParentStable(ownedParent);
363
544
  backupCreated = false;
364
545
  }
365
546
  return {
366
- status: "installed",
547
+ status: initialTarget.exists ? "refreshed" : "installed",
367
548
  changed: true,
368
549
  targetPath: params.targetPath,
369
- sourcePath
550
+ sourcePath,
551
+ sourceBuild: sourceIdentity.build,
552
+ ...initialTarget.identity ? { previousBuild: initialTarget.identity.build } : {}
370
553
  };
554
+ } catch (error) {
555
+ if (backupCreated && await ownedServiceParentIsStable(ownedParent) && !await pathExists$1(operationTargetPath)) {
556
+ await assertOwnedServiceParentStable(ownedParent);
557
+ await fs.rename(backupPath, operationTargetPath);
558
+ backupCreated = false;
559
+ }
560
+ throw error;
371
561
  } finally {
372
- await fs.rm(stagingRoot, {
562
+ if (await ownedServiceParentIsStable(ownedParent) && await directoryIdentityIsStable(stagingRootIdentity)) await fs.rm(stagingRoot, {
373
563
  recursive: true,
374
564
  force: true
375
565
  });
@@ -378,8 +568,43 @@ async function ensureCodexComputerUseServiceAppOnce(params) {
378
568
  async function pathExists$1(filePath) {
379
569
  return await fs.lstat(filePath).then(() => true, () => false);
380
570
  }
381
- async function findUsableServiceApp(candidates) {
382
- for (const candidate of candidates) if (await hasExecutableClient(candidate)) return candidate;
571
+ async function findUsableServiceApp(candidates, inspectServiceApp) {
572
+ for (const candidate of candidates) {
573
+ const identity = await inspectServiceApp(candidate);
574
+ if (identity) return {
575
+ path: candidate,
576
+ identity
577
+ };
578
+ }
579
+ }
580
+ async function readServiceAppSnapshot(appPath, inspectServiceApp) {
581
+ if (!await pathExists$1(appPath)) return { exists: false };
582
+ return {
583
+ exists: true,
584
+ identity: await inspectServiceApp(appPath),
585
+ filesystemKey: await readServiceAppFilesystemKey(appPath)
586
+ };
587
+ }
588
+ async function readServiceAppFilesystemKey(appPath) {
589
+ const paths = [
590
+ appPath,
591
+ path.join(appPath, "Contents", "Info.plist"),
592
+ path.join(appPath, CLIENT_RELATIVE_PATH)
593
+ ];
594
+ return (await Promise.all(paths.map(async (entryPath) => await fs.stat(entryPath).then((stat) => `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}`, () => "missing")))).join("|");
595
+ }
596
+ function snapshotsMatch(left, right) {
597
+ if (left.exists !== right.exists) return false;
598
+ if (!left.exists) return true;
599
+ if (left.filesystemKey && right.filesystemKey && left.filesystemKey !== right.filesystemKey) return false;
600
+ if (left.identity || right.identity) return Boolean(left.identity && right.identity && identitiesMatch(left.identity, right.identity));
601
+ return left.filesystemKey !== void 0 && left.filesystemKey === right.filesystemKey;
602
+ }
603
+ function filesystemSnapshotsMatch(left, right) {
604
+ return Boolean(left.exists && right.exists && left.filesystemKey && right.filesystemKey && left.filesystemKey === right.filesystemKey);
605
+ }
606
+ function identitiesMatch(left, right) {
607
+ return left.bundleId === right.bundleId && left.version === right.version && left.build === right.build && left.cdHash === right.cdHash && left.teamId === right.teamId && left.clientBundleId === right.clientBundleId && left.clientCdHash === right.clientCdHash && left.clientTeamId === right.clientTeamId;
383
608
  }
384
609
  async function hasExecutableClient(appPath) {
385
610
  try {
@@ -389,6 +614,90 @@ async function hasExecutableClient(appPath) {
389
614
  return false;
390
615
  }
391
616
  }
617
+ async function inspectTrustedServiceApp(appPath) {
618
+ if (!await hasExecutableClient(appPath)) return;
619
+ const clientAppPath = path.join(appPath, CLIENT_APP_RELATIVE_PATH);
620
+ try {
621
+ await verifyTrustedBundle(appPath, SERVICE_BUNDLE_ID, true);
622
+ await verifyTrustedBundle(clientAppPath, CLIENT_BUNDLE_ID, false);
623
+ const [info, serviceSignature, clientSignature] = await Promise.all([
624
+ readBundleInfo(appPath),
625
+ readCodeSignature(appPath),
626
+ readCodeSignature(clientAppPath)
627
+ ]);
628
+ if (!info || serviceSignature.identifier !== SERVICE_BUNDLE_ID || serviceSignature.teamId !== OPENAI_TEAM_ID || clientSignature.identifier !== CLIENT_BUNDLE_ID || clientSignature.teamId !== OPENAI_TEAM_ID) return;
629
+ return {
630
+ bundleId: serviceSignature.identifier,
631
+ version: info.version,
632
+ build: info.build,
633
+ cdHash: serviceSignature.cdHash,
634
+ teamId: serviceSignature.teamId,
635
+ clientBundleId: clientSignature.identifier,
636
+ clientCdHash: clientSignature.cdHash,
637
+ clientTeamId: clientSignature.teamId
638
+ };
639
+ } catch {
640
+ return;
641
+ }
642
+ }
643
+ async function verifyTrustedBundle(appPath, bundleId, deep) {
644
+ const requirement = `anchor apple generic and certificate leaf[subject.OU] = "${OPENAI_TEAM_ID}" and identifier "${bundleId}"`;
645
+ await runExec("/usr/bin/codesign", [
646
+ "--verify",
647
+ "--strict",
648
+ ...deep ? ["--deep"] : [],
649
+ `-R=${requirement}`,
650
+ appPath
651
+ ], {
652
+ logOutput: false,
653
+ timeoutMs: INSPECT_TIMEOUT_MS
654
+ });
655
+ }
656
+ async function readBundleInfo(appPath) {
657
+ const result = await runExec("/usr/bin/plutil", [
658
+ "-convert",
659
+ "json",
660
+ "-o",
661
+ "-",
662
+ "--",
663
+ path.join(appPath, "Contents", "Info.plist")
664
+ ], {
665
+ logOutput: false,
666
+ timeoutMs: INSPECT_TIMEOUT_MS
667
+ });
668
+ const parsed = JSON.parse(result.stdout);
669
+ if (!isRecord(parsed)) return;
670
+ const version = parsed.CFBundleShortVersionString;
671
+ const build = parsed.CFBundleVersion;
672
+ return typeof version === "string" && version && typeof build === "string" && build ? {
673
+ version,
674
+ build
675
+ } : void 0;
676
+ }
677
+ async function readCodeSignature(appPath) {
678
+ const result = await runExec("/usr/bin/codesign", [
679
+ "-d",
680
+ "--verbose=4",
681
+ appPath
682
+ ], {
683
+ logOutput: false,
684
+ timeoutMs: INSPECT_TIMEOUT_MS
685
+ });
686
+ const output = `${result.stdout}\n${result.stderr}`;
687
+ const identifier = readCodeSignField(output, "Identifier");
688
+ const teamId = readCodeSignField(output, "TeamIdentifier");
689
+ const cdHash = readCodeSignField(output, "CDHash").toLowerCase();
690
+ if (!identifier || !teamId || !/^[a-f0-9]+$/.test(cdHash)) throw new Error(`Could not inspect the signed identity at ${appPath}.`);
691
+ return {
692
+ identifier,
693
+ teamId,
694
+ cdHash
695
+ };
696
+ }
697
+ function readCodeSignField(output, field) {
698
+ const prefix = `${field}=`;
699
+ return output.split(/\r?\n/u).find((candidate) => candidate.startsWith(prefix))?.slice(prefix.length).trim() ?? "";
700
+ }
392
701
  async function copyServiceAppWithDitto(sourcePath, targetPath) {
393
702
  await runExec("/usr/bin/ditto", [
394
703
  "--noqtn",
@@ -408,8 +717,8 @@ function flattenCodexDynamicToolFunctions(tools) {
408
717
  }
409
718
  const CODEX_INTERACTIVE_THREAD_SOURCE_KINDS = ["cli", "vscode"];
410
719
  const CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES = ["atlas", "chatgpt"];
411
- function isJsonObject$1(value) {
412
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
720
+ function isJsonObject(value) {
721
+ return isRecord(value);
413
722
  }
414
723
  function isRpcResponse(message) {
415
724
  return "id" in message && !("method" in message);
@@ -436,24 +745,41 @@ const CODEX_HOME_DIRNAME = ".codex";
436
745
  const scopedOAuthRefreshQueues = /* @__PURE__ */ new WeakMap();
437
746
  async function bridgeCodexAppServerStartOptions(params) {
438
747
  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;
748
+ const scopeStartOptions = () => withCodexHomeEnvironment(withEphemeralCodexAuthStore(params), params.agentDir, params.pluginConfig);
749
+ if (params.preparedAuth) return withClearedEnvironmentVariables(await scopeStartOptions(), CODEX_APP_SERVER_PREPARED_AUTH_ENV_VARS);
750
+ if (params.authProfileId === null) return scopeStartOptions();
442
751
  const store = resolveCodexAppServerAuthProfileStore({
443
752
  agentDir: params.agentDir,
444
753
  authProfileId: params.authProfileId,
445
754
  authProfileStore: params.authProfileStore,
446
755
  config: params.config
447
756
  });
757
+ const authProfileId = resolveCodexAppServerAuthProfileId({
758
+ authProfileId: params.authProfileId,
759
+ store,
760
+ config: params.config
761
+ });
762
+ if (!authProfileId) assertNoUnimportedAgentCodexAuthFile(params);
763
+ const scopedStartOptions = await scopeStartOptions();
448
764
  return shouldClearOpenAiApiKeyForCodexAuthProfile({
449
765
  store,
450
- authProfileId: resolveCodexAppServerAuthProfileId({
451
- authProfileId: params.authProfileId,
452
- store,
453
- config: params.config
454
- })
766
+ authProfileId
455
767
  }) ? withClearedEnvironmentVariables(scopedStartOptions, CODEX_APP_SERVER_API_KEY_ENV_VARS) : scopedStartOptions;
456
768
  }
769
+ function assertNoUnimportedAgentCodexAuthFile(params) {
770
+ if (params.authRequirement === "api-key" && resolveCodexAppServerFallbackApiKeyCacheKey({ startOptions: params.startOptions })) return;
771
+ const message = resolveUnimportedAgentCodexAuthMessage(params);
772
+ if (message) throw new AgentHarnessPreflightError(message);
773
+ }
774
+ function resolveUnimportedAgentCodexAuthMessage(params) {
775
+ const managedCodexCli = params.startOptions.commandSource === "managed" || params.startOptions.commandSource === "resolved-managed";
776
+ if (params.startOptions.transport !== "stdio" || !managedCodexCli || params.startOptions.homeScope === "user") return;
777
+ const codexHome = resolveCodexAppServerHomeDir(params.agentDir);
778
+ const authPath = path.join(codexHome, CODEX_AUTH_JSON_FILENAME);
779
+ if (!fsSync.existsSync(authPath)) return;
780
+ const targetAgentId = params.agentId?.trim() || "<agent-id>";
781
+ 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.`;
782
+ }
457
783
  function resolveCodexAppServerAuthProfileId(params) {
458
784
  const requested = params.authProfileId?.trim();
459
785
  if (requested) return requested;
@@ -518,15 +844,20 @@ async function resolveCodexAppServerPreparedAuthProfileSnapshot(params) {
518
844
  if (!loginParams) return;
519
845
  const accountId = loginParams.type === "chatgptAuthTokens" ? loginParams.chatgptAccountId : resolveChatgptAccountId(profileId, credential);
520
846
  const stableChatgptAccountId = resolveStableChatgptAccountId(credential);
847
+ const secretFreeCacheKey = credential.type === "api_key" && loginParams.type === "apiKey" ? `${accountId}:${fingerprintApiKeyAuthProfileCacheKey(loginParams.apiKey)}` : loginParams.type === "chatgptAuthTokens" && (credential.type === "token" || !stableChatgptAccountId) ? `${accountId}:${fingerprintTokenAuthProfileCacheKey(loginParams.accessToken)}` : accountId;
848
+ const chatgptAccountId = resolveExplicitChatgptAccountId(credential);
521
849
  return {
522
850
  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
851
+ secretFreeCacheKey,
852
+ ...chatgptAccountId ? { chatgptAccountId } : {}
524
853
  };
525
854
  }
526
855
  /** Maps one prepared route to one mutually exclusive app-server auth handoff. */
527
856
  async function resolveCodexAppServerPreparedAuthHandoff(params) {
528
857
  const usesNativeHome = params.homeScope === "user";
529
- if (params.authRequirement === "api-key" && !usesNativeHome) {
858
+ 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.");
859
+ if (usesNativeHome) return { nativeAuthProfile: true };
860
+ if (params.authRequirement === "api-key") {
530
861
  const apiKey = params.resolvedApiKey?.trim();
531
862
  if (!apiKey) throw new Error("Prepared Codex API-key route is missing its resolved API key.");
532
863
  return {
@@ -544,18 +875,18 @@ async function resolveCodexAppServerPreparedAuthHandoff(params) {
544
875
  agentDir: params.agentDir,
545
876
  config: params.config
546
877
  });
547
- if (usesNativeHome || params.authRequirement !== "subscription") return {
878
+ if (params.authRequirement !== "subscription" && !params.requirePreparedAuth) return {
548
879
  authProfileId,
549
880
  nativeAuthProfile
550
881
  };
551
- if (!authProfileId || !nativeAuthProfile) throw createCodexAppServerAuthError(params.subscriptionProfileRequiredError);
882
+ 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
883
  const snapshot = await resolveCodexAppServerPreparedAuthProfileSnapshot({
553
884
  authProfileId,
554
885
  authProfileStore: params.authProfileStore,
555
886
  agentDir: params.agentDir,
556
887
  config: params.config
557
888
  });
558
- if (!snapshot) throw createCodexAppServerAuthError(params.subscriptionProfileUnusableError);
889
+ 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
890
  return {
560
891
  authProfileId,
561
892
  nativeAuthProfile,
@@ -644,7 +975,7 @@ function fingerprintCodexCliAuthFileApiKeyCacheKey(apiKey) {
644
975
  return `CODEX_AUTH_JSON:sha256:${hash.digest("hex")}`;
645
976
  }
646
977
  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);
978
+ const codexHome = resolveCodexAppServerLocalHomeDir(startOptions, agentDir);
648
979
  const nativeHome = startOptions.env?.[HOME_ENV_VAR]?.trim() ? startOptions.env[HOME_ENV_VAR] : void 0;
649
980
  await fs.mkdir(codexHome, { recursive: true });
650
981
  const computerUseConfig = resolveCodexComputerUseConfig({ pluginConfig });
@@ -652,13 +983,18 @@ async function withCodexHomeEnvironment(startOptions, agentDir, pluginConfig) {
652
983
  codexHome,
653
984
  config: computerUseConfig
654
985
  });
655
- if (computerUseConfig.enabled && computerUseConfig.autoInstall) try {
986
+ const ownsIsolatedCodexHome = startOptions.homeScope !== "user" && !startOptions.env?.[CODEX_HOME_ENV_VAR]?.trim();
987
+ if (computerUseConfig.enabled && computerUseConfig.autoInstall && ownsIsolatedCodexHome) try {
656
988
  await ensureCodexComputerUseServiceApp({
657
989
  codexHome,
990
+ ownershipRoot: agentDir,
658
991
  appServerCommand: startOptions.command
659
992
  });
660
993
  } catch (error) {
661
- throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", { cause: error });
994
+ throw new AgentHarnessPreflightError("Codex Computer Use client provisioning failed.", {
995
+ cause: error,
996
+ scope: "harness"
997
+ });
662
998
  }
663
999
  if (nativeHome) await fs.mkdir(nativeHome, { recursive: true });
664
1000
  const nextStartOptions = {
@@ -733,7 +1069,7 @@ async function applyCodexAppServerAuthProfile(params) {
733
1069
  async function assertNativeCodexAccountMatchesRoute(client, authRequirement) {
734
1070
  if (!authRequirement) return;
735
1071
  const response = await client.request("account/read", { refreshToken: false });
736
- const accountType = isJsonObject$1(response.account) ? response.account.type : void 0;
1072
+ const accountType = isJsonObject(response.account) ? response.account.type : void 0;
737
1073
  if (authRequirement === "subscription") {
738
1074
  if (accountType !== "chatgpt") throw createCodexAppServerAuthError("Codex subscription auth profile could not produce login credentials.");
739
1075
  return;
@@ -1019,11 +1355,13 @@ function resolveChatgptAccountId(profileId, credential) {
1019
1355
  return resolveStableChatgptAccountId(credential) ?? profileId;
1020
1356
  }
1021
1357
  function resolveStableChatgptAccountId(credential) {
1358
+ return resolveExplicitChatgptAccountId(credential) ?? (credential.email?.trim() || void 0);
1359
+ }
1360
+ function resolveExplicitChatgptAccountId(credential) {
1022
1361
  if ("accountId" in credential && typeof credential.accountId === "string") {
1023
1362
  const accountId = credential.accountId.trim();
1024
1363
  if (accountId) return accountId;
1025
1364
  }
1026
- return credential.email?.trim() || void 0;
1027
1365
  }
1028
1366
  //#endregion
1029
1367
  //#region extensions/codex/src/app-server/rate-limit-cache.ts
@@ -1048,7 +1386,7 @@ function rememberCodexRateLimitsRead(client, value, nowMs = Date.now()) {
1048
1386
  }
1049
1387
  /** Merges a sparse rolling notification into one physical client's latest read response. */
1050
1388
  function mergeCodexRateLimitsUpdate(client, value, nowMs = Date.now()) {
1051
- const update = isJsonObject$1(value) && isJsonObject$1(value.rateLimits) ? value.rateLimits : void 0;
1389
+ const update = isJsonObject(value) && isJsonObject(value.rateLimits) ? value.rateLimits : void 0;
1052
1390
  if (!update) return;
1053
1391
  const currentState = rateLimitsByClient.get(client);
1054
1392
  const current = currentState?.value;
@@ -1075,13 +1413,13 @@ function readRecentCodexRateLimits(client, options) {
1075
1413
  return maxAgeMs >= 0 && nowMs - state.updatedAtMs > maxAgeMs ? void 0 : state.value;
1076
1414
  }
1077
1415
  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;
1416
+ const currentEnvelope = isJsonObject(current) ? current : void 0;
1417
+ const currentPrimary = currentEnvelope && isJsonObject(currentEnvelope.rateLimits) ? currentEnvelope.rateLimits : void 0;
1418
+ const currentByLimitId = currentEnvelope && isJsonObject(currentEnvelope.rateLimitsByLimitId) ? currentEnvelope.rateLimitsByLimitId : void 0;
1081
1419
  const limitId = readLimitId(update);
1082
1420
  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);
1421
+ const currentForLimit = (currentByLimitId && isJsonObject(currentByLimitId[limitId]) ? currentByLimitId[limitId] : void 0) ?? (currentPrimaryLimitId === limitId ? currentPrimary : void 0);
1422
+ const merged = mergeSparseSnapshot(isJsonObject(currentForLimit) ? currentForLimit : void 0, currentPrimary, update, limitId);
1085
1423
  const nextPrimary = !currentPrimary || currentPrimaryLimitId === limitId ? merged : currentPrimary;
1086
1424
  let nextByLimitId;
1087
1425
  if (currentByLimitId) nextByLimitId = {
@@ -1099,11 +1437,11 @@ function mergeRateLimitUpdate(current, update) {
1099
1437
  };
1100
1438
  }
1101
1439
  function readRateLimitIds(value) {
1102
- if (!isJsonObject$1(value)) return [];
1440
+ if (!isJsonObject(value)) return [];
1103
1441
  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() : "";
1442
+ if (isJsonObject(value.rateLimits)) ids.add(readLimitId(value.rateLimits));
1443
+ if (isJsonObject(value.rateLimitsByLimitId)) for (const [key, snapshot] of Object.entries(value.rateLimitsByLimitId)) {
1444
+ const snapshotLimitId = isJsonObject(snapshot) && typeof snapshot.limitId === "string" ? snapshot.limitId.trim() : "";
1107
1445
  ids.add(snapshotLimitId || key);
1108
1446
  }
1109
1447
  return [...ids];
@@ -1126,62 +1464,337 @@ function readLimitId(snapshot) {
1126
1464
  //#endregion
1127
1465
  //#region extensions/codex/src/app-server/client-runtime.ts
1128
1466
  /** Client-scoped Codex auth and account observers. */
1467
+ /** Match Codex's native grace window without retaining inactive conversations indefinitely. */
1468
+ const CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS = 30 * 6e4;
1469
+ /** Native-child parents are active ownership, so only otherwise-idle threads count against this cap. */
1470
+ const CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE = 64;
1129
1471
  const configuredClients = /* @__PURE__ */ new WeakMap();
1472
+ const physicalThreadReleases = /* @__PURE__ */ new WeakMap();
1473
+ const claimedThreadReleaseTokens = /* @__PURE__ */ new WeakMap();
1474
+ /** Only an initialized, still-open physical client can own retained native subscriptions. */
1475
+ function isCodexAppServerClientRuntimeLive(client) {
1476
+ const runtime = configuredClients.get(client);
1477
+ return runtime !== void 0 && !runtime.closed;
1478
+ }
1130
1479
  /** Installs one auth-refresh handler and one rate-limit observer per physical client. */
1131
1480
  function ensureCodexAppServerClientRuntime(client, context) {
1132
1481
  const existing = configuredClients.get(client);
1133
1482
  if (existing) {
1483
+ if (existing.closed) return;
1134
1484
  existing.context = context;
1135
1485
  return;
1136
1486
  }
1137
- const runtime = { context };
1487
+ const runtime = {
1488
+ context,
1489
+ closed: false,
1490
+ retainedThreads: /* @__PURE__ */ new Map(),
1491
+ claimedThreads: /* @__PURE__ */ new Map(),
1492
+ releasingThreads: /* @__PURE__ */ new Map(),
1493
+ protectedThreads: /* @__PURE__ */ new Map()
1494
+ };
1138
1495
  configuredClients.set(client, runtime);
1496
+ client.addCloseHandler(() => {
1497
+ runtime.closed = true;
1498
+ if (runtime.evictionTimer) {
1499
+ clearTimeout(runtime.evictionTimer);
1500
+ runtime.evictionTimer = void 0;
1501
+ }
1502
+ runtime.retainedThreads.clear();
1503
+ runtime.claimedThreads.clear();
1504
+ runtime.protectedThreads.clear();
1505
+ });
1139
1506
  client.addRequestHandler(async (request) => {
1140
1507
  if (request.method !== "account/chatgptAuthTokens/refresh") return;
1141
1508
  if (runtime.context.authMode === "prepared-api-key") throw new Error("ChatGPT token refresh is unavailable for prepared Codex API-key auth.");
1142
- return await refreshCodexAppServerAuthTokens({
1509
+ return { ...await refreshCodexAppServerAuthTokens({
1143
1510
  agentDir: runtime.context.agentDir,
1144
1511
  authProfileId: runtime.context.authProfileId,
1145
1512
  ...runtime.context.authProfileStore ? { authProfileStore: runtime.context.authProfileStore } : {},
1146
1513
  config: runtime.context.config
1147
- });
1514
+ }) };
1148
1515
  });
1149
1516
  client.addNotificationHandler((notification) => {
1150
- if (notification.method === "account/rateLimits/updated") mergeCodexRateLimitsUpdate(client, notification.params);
1517
+ if (notification.method === "account/rateLimits/updated") {
1518
+ mergeCodexRateLimitsUpdate(client, notification.params);
1519
+ return;
1520
+ }
1521
+ if (notification.method === "thread/archived" || notification.method === "thread/deleted" || notification.method === "thread/closed") {
1522
+ const threadId = notification.params?.threadId;
1523
+ if (typeof threadId === "string") {
1524
+ const releasing = runtime.releasingThreads.get(threadId);
1525
+ if (releasing) releasing.invalidated = true;
1526
+ runtime.retainedThreads.delete(threadId);
1527
+ runtime.claimedThreads.delete(threadId);
1528
+ scheduleRetainedThreadEviction(client, runtime);
1529
+ }
1530
+ }
1151
1531
  });
1152
1532
  }
1153
- /** Keep at most one idle, still-subscribed thread on a physical Codex client. */
1154
- async function retainCodexAppServerLiveThread(client, threadId, releasePreviousThread, configFingerprint) {
1533
+ function scheduleRetainedThreadEviction(client, runtime) {
1534
+ if (runtime.evictionTimer) {
1535
+ clearTimeout(runtime.evictionTimer);
1536
+ runtime.evictionTimer = void 0;
1537
+ }
1538
+ if (runtime.closed) return;
1539
+ let expiresAt = Number.POSITIVE_INFINITY;
1540
+ for (const [threadId, thread] of runtime.retainedThreads) if (!runtime.protectedThreads.has(threadId)) expiresAt = Math.min(expiresAt, thread.expiresAt);
1541
+ if (!Number.isFinite(expiresAt)) return;
1542
+ runtime.evictionTimer = setTimeout(() => {
1543
+ runtime.evictionTimer = void 0;
1544
+ evictExpiredRetainedThreads(client, runtime).catch((error) => {
1545
+ embeddedAgentLog.warn("codex retained thread expiry failed", { reason: formatErrorMessage(error) });
1546
+ });
1547
+ }, Math.max(0, expiresAt - Date.now()));
1548
+ runtime.evictionTimer.unref?.();
1549
+ }
1550
+ async function releaseRetainedThread(client, runtime, threadId) {
1551
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1552
+ if (pendingRelease) {
1553
+ await pendingRelease.completion;
1554
+ return false;
1555
+ }
1556
+ const retained = runtime.retainedThreads.get(threadId);
1557
+ if (!retained) return false;
1558
+ const olderThreadIds = /* @__PURE__ */ new Set();
1559
+ for (const candidateThreadId of runtime.retainedThreads.keys()) {
1560
+ if (candidateThreadId === threadId) break;
1561
+ olderThreadIds.add(candidateThreadId);
1562
+ }
1563
+ runtime.retainedThreads.delete(threadId);
1564
+ scheduleRetainedThreadEviction(client, runtime);
1565
+ const transition = { completion: Promise.resolve().then(() => retained.release(threadId)) };
1566
+ runtime.releasingThreads.set(threadId, transition);
1567
+ try {
1568
+ await transition.completion;
1569
+ return true;
1570
+ } catch (error) {
1571
+ if (!runtime.closed && !transition.invalidated && runtime.releasingThreads.get(threadId) === transition && !runtime.retainedThreads.has(threadId) && !runtime.claimedThreads.has(threadId)) {
1572
+ retained.expiresAt = Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS;
1573
+ const newerThreads = [...runtime.retainedThreads.entries()].filter(([candidateThreadId]) => !olderThreadIds.has(candidateThreadId));
1574
+ for (const [candidateThreadId] of newerThreads) runtime.retainedThreads.delete(candidateThreadId);
1575
+ runtime.retainedThreads.set(threadId, retained);
1576
+ for (const [candidateThreadId, newerThread] of newerThreads) runtime.retainedThreads.set(candidateThreadId, newerThread);
1577
+ }
1578
+ throw error;
1579
+ } finally {
1580
+ if (runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1581
+ scheduleRetainedThreadEviction(client, runtime);
1582
+ }
1583
+ }
1584
+ async function evictExpiredRetainedThreads(client, runtime) {
1585
+ const now = Date.now();
1586
+ for (const [threadId, thread] of runtime.retainedThreads) if (thread.expiresAt <= now && !runtime.protectedThreads.has(threadId)) await releaseRetainedThread(client, runtime, threadId);
1587
+ scheduleRetainedThreadEviction(client, runtime);
1588
+ }
1589
+ async function evictExcessIdleThreads(client, runtime) {
1590
+ let idleThreadIds = [...runtime.retainedThreads.keys()].filter((threadId) => !runtime.protectedThreads.has(threadId));
1591
+ while (idleThreadIds.length > CODEX_APP_SERVER_LIVE_THREAD_MAX_IDLE) {
1592
+ await releaseRetainedThread(client, runtime, idleThreadIds[0]);
1593
+ idleThreadIds = [...runtime.retainedThreads.keys()].filter((threadId) => !runtime.protectedThreads.has(threadId));
1594
+ }
1595
+ }
1596
+ /** Retain separately owned Codex subscriptions; completing B must never cold-restart A. */
1597
+ async function retainCodexAppServerLiveThread(client, threadId, releaseThread, configFingerprint, serviceTier) {
1155
1598
  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;
1599
+ if (!runtime || runtime.closed) return false;
1600
+ const claimed = runtime.claimedThreads.get(threadId);
1601
+ if (claimed !== void 0 && (releaseThread === void 0 || claimedThreadReleaseTokens.get(releaseThread) !== claimed)) return false;
1602
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1603
+ if (pendingRelease) {
1604
+ await pendingRelease.completion;
1605
+ if (runtime.closed || claimed !== void 0 && runtime.claimedThreads.get(threadId) !== claimed) return false;
1606
+ }
1607
+ runtime.retainedThreads.delete(threadId);
1608
+ const retained = {
1609
+ configFingerprint,
1610
+ serviceTier,
1611
+ expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
1612
+ release: (releaseThread ? physicalThreadReleases.get(releaseThread) ?? releaseThread : void 0) ?? (async (releasedThreadId) => {
1613
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
1614
+ })
1615
+ };
1616
+ runtime.retainedThreads.set(threadId, retained);
1617
+ try {
1618
+ await evictExcessIdleThreads(client, runtime);
1619
+ } catch (error) {
1620
+ if (runtime.retainedThreads.get(threadId) === retained) runtime.retainedThreads.delete(threadId);
1621
+ scheduleRetainedThreadEviction(client, runtime);
1622
+ embeddedAgentLog.warn("codex retained thread capacity eviction failed", {
1623
+ threadId,
1624
+ reason: formatErrorMessage(error)
1625
+ });
1626
+ return false;
1627
+ }
1628
+ if (runtime.closed) return false;
1629
+ if (claimed !== void 0 && runtime.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1630
+ scheduleRetainedThreadEviction(client, runtime);
1631
+ return true;
1632
+ }
1633
+ /** Transfer one idle subscription to its next turn or compaction without touching sibling threads. */
1634
+ async function consumeCodexAppServerLiveThread(client, threadId, configFingerprint) {
1635
+ const runtime = configuredClients.get(client);
1636
+ if (!runtime || runtime.closed) return;
1637
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1638
+ if (pendingRelease) {
1639
+ await pendingRelease.completion;
1640
+ return;
1641
+ }
1642
+ const retained = runtime.retainedThreads.get(threadId);
1643
+ if (!retained || configFingerprint !== void 0 && retained.configFingerprint !== configFingerprint) return;
1644
+ return claimCodexAppServerThreadOwnership(client, runtime, threadId, retained);
1645
+ }
1646
+ /** Claims an observed Codex auto-subscription without exposing a temporarily idle owner. */
1647
+ async function claimCodexAppServerLiveThread(client, threadId) {
1648
+ const runtime = configuredClients.get(client);
1649
+ if (!runtime || runtime.closed || runtime.claimedThreads.has(threadId)) return;
1650
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1651
+ if (pendingRelease) {
1652
+ await pendingRelease.completion;
1653
+ return;
1654
+ }
1655
+ return claimCodexAppServerThreadOwnership(client, runtime, threadId, runtime.retainedThreads.get(threadId) ?? {
1656
+ expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS,
1657
+ release: async (releasedThreadId) => {
1658
+ await unsubscribeCodexAppServerLiveThread(client, releasedThreadId, 5e3);
1659
+ }
1660
+ });
1661
+ }
1662
+ function claimCodexAppServerThreadOwnership(client, runtime, threadId, retained) {
1663
+ runtime.retainedThreads.delete(threadId);
1664
+ const claimed = Symbol(threadId);
1665
+ runtime.claimedThreads.set(threadId, claimed);
1666
+ scheduleRetainedThreadEviction(client, runtime);
1667
+ const release = async (releasedThreadId) => {
1668
+ if (releasedThreadId !== threadId || runtime.claimedThreads.get(threadId) !== claimed) return;
1669
+ const pendingRelease = runtime.releasingThreads.get(threadId);
1670
+ if (pendingRelease) {
1671
+ await pendingRelease.completion;
1672
+ return;
1673
+ }
1674
+ const transition = { completion: Promise.resolve().then(async () => {
1675
+ if (runtime.closed || runtime.claimedThreads.get(threadId) !== claimed) return;
1676
+ await retained.release(releasedThreadId);
1677
+ }) };
1678
+ runtime.releasingThreads.set(threadId, transition);
1162
1679
  try {
1163
- await release;
1164
- } catch (error) {
1165
- runtime.retainedThreadId = void 0;
1166
- runtime.retainedThreadConfigFingerprint = void 0;
1167
- throw error;
1680
+ await transition.completion;
1681
+ if (runtime.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1168
1682
  } finally {
1169
- if (runtime.retainedThreadRelease === release) runtime.retainedThreadRelease = void 0;
1683
+ if (runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1170
1684
  }
1685
+ };
1686
+ physicalThreadReleases.set(release, retained.release);
1687
+ claimedThreadReleaseTokens.set(release, claimed);
1688
+ return {
1689
+ configFingerprint: retained.configFingerprint,
1690
+ serviceTier: retained.serviceTier,
1691
+ release
1692
+ };
1693
+ }
1694
+ /** Distinguish active claimed ownership from an already-evicted idle subscription. */
1695
+ function isCodexAppServerLiveThreadClaimed(client, threadId) {
1696
+ const runtime = configuredClients.get(client);
1697
+ return runtime !== void 0 && !runtime.closed && runtime.claimedThreads.has(threadId);
1698
+ }
1699
+ /** Release the exact physical subscription and finish only its observed claim generation. */
1700
+ async function unsubscribeCodexAppServerLiveThread(client, threadId, timeoutMs) {
1701
+ const runtime = configuredClients.get(client);
1702
+ const claimed = runtime?.claimedThreads.get(threadId);
1703
+ let transition = runtime?.releasingThreads.get(threadId);
1704
+ if (transition?.physicalRelease) {
1705
+ await transition.physicalRelease;
1706
+ return;
1707
+ }
1708
+ const physicalRelease = Promise.resolve().then(async () => {
1709
+ if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) !== claimed) return;
1710
+ await client.request("thread/unsubscribe", { threadId }, { timeoutMs });
1711
+ });
1712
+ const ownsTransition = runtime !== void 0 && transition === void 0;
1713
+ if (transition) transition.physicalRelease = physicalRelease;
1714
+ else if (runtime) {
1715
+ transition = {
1716
+ completion: physicalRelease,
1717
+ physicalRelease
1718
+ };
1719
+ runtime.releasingThreads.set(threadId, transition);
1720
+ }
1721
+ try {
1722
+ await physicalRelease;
1723
+ if (claimed !== void 0 && runtime?.claimedThreads.get(threadId) === claimed) runtime.claimedThreads.delete(threadId);
1724
+ } finally {
1725
+ if (ownsTransition && runtime.releasingThreads.get(threadId) === transition) runtime.releasingThreads.delete(threadId);
1726
+ }
1727
+ }
1728
+ /** Reset/end owns the exact thread; failed generation retirement must never release its successor. */
1729
+ async function releaseCodexAppServerLiveThread(client, threadId) {
1730
+ const runtime = configuredClients.get(client);
1731
+ return runtime ? await releaseRetainedThread(client, runtime, threadId) : false;
1732
+ }
1733
+ /** Native child work pins its parent's subscription even after the foreground parent turn ends. */
1734
+ function protectCodexAppServerLiveThread(client, threadId) {
1735
+ const runtime = configuredClients.get(client);
1736
+ if (!runtime || runtime.closed) return () => void 0;
1737
+ runtime.protectedThreads.set(threadId, (runtime.protectedThreads.get(threadId) ?? 0) + 1);
1738
+ scheduleRetainedThreadEviction(client, runtime);
1739
+ let protectedThread = true;
1740
+ return () => {
1741
+ if (!protectedThread) return;
1742
+ protectedThread = false;
1743
+ if (runtime.closed) return;
1744
+ const count = runtime.protectedThreads.get(threadId) ?? 0;
1745
+ if (count <= 1) {
1746
+ runtime.protectedThreads.delete(threadId);
1747
+ const retained = runtime.retainedThreads.get(threadId);
1748
+ if (retained) {
1749
+ runtime.retainedThreads.delete(threadId);
1750
+ runtime.retainedThreads.set(threadId, {
1751
+ ...retained,
1752
+ expiresAt: Date.now() + CODEX_APP_SERVER_LIVE_THREAD_IDLE_TIMEOUT_MS
1753
+ });
1754
+ }
1755
+ } else runtime.protectedThreads.set(threadId, count - 1);
1756
+ scheduleRetainedThreadEviction(client, runtime);
1757
+ evictExcessIdleThreads(client, runtime).catch((error) => {
1758
+ embeddedAgentLog.warn("codex retained thread unpin eviction failed", {
1759
+ threadId,
1760
+ reason: formatErrorMessage(error)
1761
+ });
1762
+ });
1763
+ };
1764
+ }
1765
+ //#endregion
1766
+ //#region extensions/codex/src/app-server/elicitation-response.ts
1767
+ function createCodexElicitationResponse(action, content = null, meta = null) {
1768
+ return {
1769
+ action,
1770
+ content,
1771
+ _meta: meta
1772
+ };
1773
+ }
1774
+ //#endregion
1775
+ //#region extensions/codex/src/app-server/rpc-error.ts
1776
+ /** RPC error wrapper that preserves app-server error code and data. */
1777
+ var CodexAppServerRpcError = class extends Error {
1778
+ constructor(error, method) {
1779
+ super(formatCodexAppServerRpcErrorMessage(error, method));
1780
+ this.name = "CodexAppServerRpcError";
1781
+ this.code = error.code;
1782
+ this.data = error.data;
1783
+ this.method = method;
1171
1784
  }
1172
- runtime.retainedThreadId = threadId;
1173
- runtime.retainedThreadConfigFingerprint = configFingerprint;
1174
- return previousThreadId ? { previousThreadId } : {};
1785
+ };
1786
+ function formatCodexAppServerRpcErrorMessage(error, method) {
1787
+ const message = error.message || `${method} failed`;
1788
+ const detail = readCodexAppServerRpcReloginDetail(error.data);
1789
+ return detail && !message.includes(detail) ? `${message}: ${detail}` : message;
1175
1790
  }
1176
- /** A warm turn can skip resume only when this exact subscription was retained. */
1177
- async function consumeCodexAppServerLiveThread(client, threadId, configFingerprint) {
1178
- 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;
1791
+ function readCodexAppServerRpcReloginDetail(data) {
1792
+ const record = isJsonObject(data) ? data : void 0;
1793
+ const nested = isJsonObject(record?.error) ? record.error : record;
1794
+ if (!nested) return;
1795
+ const isRelogin = nested.action === "relogin" || nested.reason === "cloudRequirements" && nested.errorCode === "Auth";
1796
+ const detail = typeof nested.detail === "string" ? nested.detail.trim() : "";
1797
+ return isRelogin && detail ? detail : void 0;
1185
1798
  }
1186
1799
  //#endregion
1187
1800
  //#region extensions/codex/src/app-server/transport-websocket.ts
@@ -1189,6 +1802,10 @@ async function consumeCodexAppServerLiveThread(client, threadId, configFingerpri
1189
1802
  * Adapts a remote Codex app-server WebSocket endpoint to the shared stdio-like
1190
1803
  * transport interface.
1191
1804
  */
1805
+ const WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 1e4;
1806
+ const WEBSOCKET_PING_INTERVAL_MS = 2e4;
1807
+ const WEBSOCKET_PONG_TIMEOUT_MS = 2e4;
1808
+ const MAX_CONSECUTIVE_MISSED_WEBSOCKET_PONGS = 5;
1192
1809
  /** Opens a WebSocket app-server transport and maps newline-delimited frames to stdout/stdin. */
1193
1810
  function createWebSocketTransport(options) {
1194
1811
  if (!options.url) throw new Error("codex app-server websocket transport requires plugins.entries.codex.config.appServer.url");
@@ -1200,7 +1817,8 @@ function createWebSocketTransport(options) {
1200
1817
  ...options.headers,
1201
1818
  ...options.authToken ? { Authorization: `Bearer ${options.authToken}` } : {}
1202
1819
  },
1203
- perMessageDeflate: false
1820
+ perMessageDeflate: false,
1821
+ ...options.transport === "websocket" ? { handshakeTimeout: WEBSOCKET_HANDSHAKE_TIMEOUT_MS } : {}
1204
1822
  };
1205
1823
  const unixSocketPath = resolveCodexAppServerUnixSocketPath(options);
1206
1824
  const socket = unixSocketPath ? new WebSocket("ws://localhost/", {
@@ -1211,6 +1829,58 @@ function createWebSocketTransport(options) {
1211
1829
  const stdinDecoder = new StringDecoder("utf8");
1212
1830
  let pendingLine = "";
1213
1831
  let killed = false;
1832
+ let pingTimeout;
1833
+ let pongTimeout;
1834
+ let expectedPong;
1835
+ let consecutiveMissedPongs = 0;
1836
+ let heartbeatSequence = 0;
1837
+ const clearConnectionHealthTimers = () => {
1838
+ if (pingTimeout) {
1839
+ clearTimeout(pingTimeout);
1840
+ pingTimeout = void 0;
1841
+ }
1842
+ if (pongTimeout) {
1843
+ clearTimeout(pongTimeout);
1844
+ pongTimeout = void 0;
1845
+ }
1846
+ expectedPong = void 0;
1847
+ };
1848
+ const sendHeartbeatPing = () => {
1849
+ if (socket.readyState !== WebSocket.OPEN || pongTimeout) return;
1850
+ const payload = Buffer.from(`openclaw-codex-${++heartbeatSequence}`);
1851
+ expectedPong = payload;
1852
+ pongTimeout = setTimeout(() => {
1853
+ pongTimeout = void 0;
1854
+ expectedPong = void 0;
1855
+ consecutiveMissedPongs += 1;
1856
+ if (consecutiveMissedPongs >= MAX_CONSECUTIVE_MISSED_WEBSOCKET_PONGS) {
1857
+ socket.terminate();
1858
+ return;
1859
+ }
1860
+ sendHeartbeatPing();
1861
+ }, WEBSOCKET_PONG_TIMEOUT_MS);
1862
+ pongTimeout.unref();
1863
+ socket.ping(payload, void 0, (error) => {
1864
+ if (error) socket.terminate();
1865
+ });
1866
+ };
1867
+ const scheduleHeartbeatPing = () => {
1868
+ if (options.transport !== "websocket" || socket.readyState !== WebSocket.OPEN || pingTimeout || pongTimeout) return;
1869
+ pingTimeout = setTimeout(() => {
1870
+ pingTimeout = void 0;
1871
+ sendHeartbeatPing();
1872
+ }, WEBSOCKET_PING_INTERVAL_MS);
1873
+ pingTimeout.unref();
1874
+ };
1875
+ const recordConnectionActivity = () => {
1876
+ consecutiveMissedPongs = 0;
1877
+ if (pongTimeout) {
1878
+ clearTimeout(pongTimeout);
1879
+ pongTimeout = void 0;
1880
+ }
1881
+ expectedPong = void 0;
1882
+ scheduleHeartbeatPing();
1883
+ };
1214
1884
  const sendFrame = (frame) => {
1215
1885
  const trimmed = frame.trim();
1216
1886
  if (!trimmed) return;
@@ -1222,13 +1892,22 @@ function createWebSocketTransport(options) {
1222
1892
  };
1223
1893
  socket.once("open", () => {
1224
1894
  for (const frame of pendingFrames.splice(0)) socket.send(frame);
1895
+ scheduleHeartbeatPing();
1896
+ });
1897
+ socket.on("pong", (payload) => {
1898
+ if (expectedPong?.equals(payload)) recordConnectionActivity();
1899
+ });
1900
+ socket.once("error", (error) => {
1901
+ clearConnectionHealthTimers();
1902
+ events.emit("error", error);
1225
1903
  });
1226
- socket.once("error", (error) => events.emit("error", error));
1227
1904
  socket.once("close", (code, reason) => {
1905
+ clearConnectionHealthTimers();
1228
1906
  killed = true;
1229
1907
  events.emit("exit", code, reason.toString("utf8"));
1230
1908
  });
1231
1909
  socket.on("message", (data) => {
1910
+ if (options.transport === "websocket") recordConnectionActivity();
1232
1911
  const text = websocketFrameToText(data);
1233
1912
  stdout.write(text.endsWith("\n") ? text : `${text}\n`);
1234
1913
  });
@@ -1262,6 +1941,7 @@ function createWebSocketTransport(options) {
1262
1941
  },
1263
1942
  kill: () => {
1264
1943
  killed = true;
1944
+ clearConnectionHealthTimers();
1265
1945
  socket.close();
1266
1946
  },
1267
1947
  once: (event, listener) => events.once(event, listener)
@@ -1288,11 +1968,273 @@ function websocketFrameToText(data) {
1288
1968
  return Buffer.from(data).toString("utf8");
1289
1969
  }
1290
1970
  //#endregion
1971
+ //#region extensions/codex/src/app-server/transport-process-containment.ts
1972
+ const PROCESS_COLUMNS = "pid=,ppid=,pgid=,stat=,lstart=";
1973
+ const MAX_CONTAINED_PROCESSES = 512;
1974
+ const MAX_PROCESS_CONTAINMENT_MS = 2e3;
1975
+ const MAX_PROCESS_QUIESCE_PASSES = 16;
1976
+ const PROCESS_INSPECTION_MAX_BYTES = 8 * 1024 * 1024;
1977
+ async function terminateCodexAppServerDescendants(child) {
1978
+ const rootPid = child.pid;
1979
+ if (process.platform === "win32" || !rootPid || !child.kill || hasExited(child)) return;
1980
+ const deadline = Date.now() + MAX_PROCESS_CONTAINMENT_MS;
1981
+ const snapshot = await readProcessSnapshot(deadline);
1982
+ if (!snapshot || Date.now() >= deadline) return;
1983
+ const root = snapshot.find((row) => row.pid === rootPid);
1984
+ if (!root || !isSameLiveRoot(root, root)) return;
1985
+ const initialDescendants = collectDescendants(snapshot, [rootPid]);
1986
+ if (initialDescendants.length > MAX_CONTAINED_PROCESSES) return;
1987
+ const stoppedDescendants = /* @__PURE__ */ new Map();
1988
+ if (!await signalSameRoot(root, "SIGSTOP", deadline)) return;
1989
+ let resumeRootOnUnwind = true;
1990
+ try {
1991
+ const descendants = await quiesceDescendants(root, initialDescendants, stoppedDescendants, deadline);
1992
+ if (!descendants) return;
1993
+ for (const descendant of descendants.toReversed()) {
1994
+ if (Date.now() >= deadline) return;
1995
+ if (!descendant.state.startsWith("Z")) {
1996
+ if (!await signalSameProcess(descendant, "SIGKILL", deadline) || Date.now() >= deadline) return;
1997
+ }
1998
+ }
1999
+ resumeRootOnUnwind = false;
2000
+ let resumed = false;
2001
+ return () => {
2002
+ if (resumed) return;
2003
+ resumed = true;
2004
+ resumeTransportRoot(child, root, false);
2005
+ };
2006
+ } finally {
2007
+ if (resumeRootOnUnwind) {
2008
+ for (const descendant of stoppedDescendants.values()) signalProcess(descendant.pid, "SIGCONT");
2009
+ resumeTransportRoot(child, root, true);
2010
+ }
2011
+ }
2012
+ }
2013
+ async function quiesceDescendants(root, initialDescendants, stopped, deadline) {
2014
+ const provenByPid = new Map(initialDescendants.map((descendant) => [descendant.pid, descendant]));
2015
+ const stopFailures = /* @__PURE__ */ new Map();
2016
+ for (let pass = 0; pass < MAX_PROCESS_QUIESCE_PASSES; pass += 1) {
2017
+ if (Date.now() >= deadline) return;
2018
+ const snapshot = await readProcessSnapshot(deadline);
2019
+ if (!snapshot || Date.now() >= deadline) return;
2020
+ const currentRoot = snapshot.find((row) => row.pid === root.pid);
2021
+ if (!currentRoot || !isSameLiveRoot(currentRoot, root)) return;
2022
+ if (!isSameLiveRoot(currentRoot, root, true)) {
2023
+ if (!await signalSameRoot(root, "SIGSTOP", deadline) || Date.now() >= deadline) return;
2024
+ continue;
2025
+ }
2026
+ const snapshotByPid = new Map(snapshot.map((process) => [process.pid, process]));
2027
+ const liveProven = [];
2028
+ for (const proven of provenByPid.values()) {
2029
+ const current = snapshotByPid.get(proven.pid);
2030
+ if (!current) {
2031
+ provenByPid.delete(proven.pid);
2032
+ stopped.delete(identityKey(proven));
2033
+ continue;
2034
+ }
2035
+ if (!hasSameIdentity(proven, current)) return;
2036
+ provenByPid.set(current.pid, current);
2037
+ const key = identityKey(current);
2038
+ if (stopped.has(key)) stopped.set(key, current);
2039
+ liveProven.push(current);
2040
+ }
2041
+ const descendants = collectDescendants(snapshot, [root.pid, ...liveProven.map(({ pid }) => pid)]);
2042
+ for (const descendant of descendants) {
2043
+ const proven = provenByPid.get(descendant.pid);
2044
+ if (proven && !hasSameIdentity(proven, descendant)) return;
2045
+ provenByPid.set(descendant.pid, descendant);
2046
+ }
2047
+ if (provenByPid.size > MAX_CONTAINED_PROCESSES) return;
2048
+ const quiescenceTargets = new Map(liveProven.map((process) => [process.pid, process]));
2049
+ for (const descendant of descendants) quiescenceTargets.set(descendant.pid, descendant);
2050
+ let allStopped = true;
2051
+ for (const descendant of quiescenceTargets.values()) {
2052
+ if (Date.now() >= deadline) return;
2053
+ if (isStoppedState(descendant.state)) continue;
2054
+ const stopQueued = await signalSameProcess(descendant, "SIGSTOP", deadline);
2055
+ if (Date.now() >= deadline) return;
2056
+ if (stopQueued) {
2057
+ stopFailures.delete(identityKey(descendant));
2058
+ stopped.set(identityKey(descendant), descendant);
2059
+ } else {
2060
+ const key = identityKey(descendant);
2061
+ const failures = (stopFailures.get(key) ?? 0) + 1;
2062
+ if (failures >= 2) return;
2063
+ stopFailures.set(key, failures);
2064
+ }
2065
+ if (!isUninterruptibleState(descendant.state) || !stopQueued) allStopped = false;
2066
+ }
2067
+ if (allStopped) return [...provenByPid.values()];
2068
+ }
2069
+ }
2070
+ async function readProcessSnapshot(deadline) {
2071
+ return await readProcesses(["-axo", PROCESS_COLUMNS], deadline);
2072
+ }
2073
+ async function readProcess(pid, deadline) {
2074
+ return (await readProcesses([
2075
+ "-o",
2076
+ PROCESS_COLUMNS,
2077
+ "-p",
2078
+ String(pid)
2079
+ ], deadline))?.find((row) => row.pid === pid);
2080
+ }
2081
+ async function readProcesses(args, deadline) {
2082
+ const remainingMs = deadline - Date.now();
2083
+ if (remainingMs <= 0) return;
2084
+ return await new Promise((resolve) => {
2085
+ let settled = false;
2086
+ const settle = (processes) => {
2087
+ if (settled) return;
2088
+ settled = true;
2089
+ clearTimeout(timer);
2090
+ resolve(processes);
2091
+ };
2092
+ const inspector = execFile("ps", args, {
2093
+ encoding: "utf8",
2094
+ maxBuffer: PROCESS_INSPECTION_MAX_BYTES
2095
+ }, (error, stdout) => {
2096
+ settle(error ? void 0 : parseProcesses(stdout));
2097
+ });
2098
+ const timer = setTimeout(() => {
2099
+ settle(void 0);
2100
+ inspector.stdout?.destroy();
2101
+ inspector.stderr?.destroy();
2102
+ inspector.kill("SIGKILL");
2103
+ inspector.unref();
2104
+ }, Math.max(1, remainingMs));
2105
+ timer.unref?.();
2106
+ });
2107
+ }
2108
+ function parseProcesses(output) {
2109
+ const rows = [];
2110
+ for (const line of output.split("\n")) {
2111
+ const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
2112
+ if (!match) continue;
2113
+ const pid = Number(match[1] ?? "");
2114
+ const ppid = Number(match[2] ?? "");
2115
+ const pgid = Number(match[3] ?? "");
2116
+ const startedAt = (match[5] ?? "").trim().replace(/\s+/g, " ");
2117
+ if (![
2118
+ pid,
2119
+ ppid,
2120
+ pgid
2121
+ ].every(Number.isSafeInteger) || pid <= 0 || ppid < 0 || pgid <= 0 || !startedAt) continue;
2122
+ rows.push({
2123
+ pid,
2124
+ ppid,
2125
+ pgid,
2126
+ state: match[4] ?? "",
2127
+ startedAt
2128
+ });
2129
+ }
2130
+ return rows;
2131
+ }
2132
+ function collectDescendants(snapshot, rootPids) {
2133
+ const childrenByParent = /* @__PURE__ */ new Map();
2134
+ for (const row of snapshot) {
2135
+ const children = childrenByParent.get(row.ppid) ?? [];
2136
+ children.push(row);
2137
+ childrenByParent.set(row.ppid, children);
2138
+ }
2139
+ const descendants = [];
2140
+ const pending = [...new Set(rootPids)];
2141
+ const seen = new Set(pending);
2142
+ for (const parentPid of pending) for (const child of childrenByParent.get(parentPid) ?? []) {
2143
+ if (seen.has(child.pid)) continue;
2144
+ seen.add(child.pid);
2145
+ descendants.push(child);
2146
+ pending.push(child.pid);
2147
+ }
2148
+ return descendants;
2149
+ }
2150
+ function isStoppedState(state) {
2151
+ return state.startsWith("T") || state.startsWith("t") || state.startsWith("Z");
2152
+ }
2153
+ function isQuiescedState(state) {
2154
+ return isStoppedState(state) || isUninterruptibleState(state);
2155
+ }
2156
+ function isUninterruptibleState(state) {
2157
+ return state.startsWith("D") || state.startsWith("U");
2158
+ }
2159
+ function isSameLiveProcess(current, expected) {
2160
+ return current.pgid === expected.pgid && !current.state.startsWith("Z") && hasSameIdentity(current, expected);
2161
+ }
2162
+ function isSameLiveRoot(current, expected, requireStopped = false) {
2163
+ return current.ppid === process.pid && (!requireStopped || isQuiescedState(current.state)) && isSameLiveProcess(current, expected);
2164
+ }
2165
+ async function signalSameRoot(root, signal, deadline) {
2166
+ const current = await readProcess(root.pid, deadline);
2167
+ return Boolean(current && isSameLiveRoot(current, root) && signalProcess(current.pid, signal));
2168
+ }
2169
+ function resumeTransportRoot(child, root, allowSynchronousPidFallback) {
2170
+ try {
2171
+ if (child.kill) {
2172
+ child.kill("SIGCONT");
2173
+ return;
2174
+ }
2175
+ } catch {
2176
+ if (!allowSynchronousPidFallback) return;
2177
+ }
2178
+ if (allowSynchronousPidFallback) signalProcess(root.pid, "SIGCONT");
2179
+ }
2180
+ async function signalSameProcess(expected, signal, deadline) {
2181
+ const current = await readProcess(expected.pid, deadline);
2182
+ return Boolean(current && isSameLiveProcess(current, expected) && signalProcess(current.pid, signal));
2183
+ }
2184
+ function hasSameIdentity(left, right) {
2185
+ return identityKey(left) === identityKey(right);
2186
+ }
2187
+ function identityKey(row) {
2188
+ return `${row.pid}\0${row.startedAt}`;
2189
+ }
2190
+ function hasExited(child) {
2191
+ return child.exitCode != null || child.signalCode != null;
2192
+ }
2193
+ function signalProcess(pid, signal) {
2194
+ try {
2195
+ process.kill(pid, signal);
2196
+ return true;
2197
+ } catch {
2198
+ return false;
2199
+ }
2200
+ }
2201
+ //#endregion
1291
2202
  //#region extensions/codex/src/app-server/transport.ts
2203
+ /**
2204
+ * Shared transport lifecycle helpers for stdio and WebSocket Codex app-server
2205
+ * connections.
2206
+ */
2207
+ const CODEX_APP_SERVER_TRANSPORT_CLOSES = /* @__PURE__ */ new WeakMap();
1292
2208
  /** Starts graceful transport shutdown and schedules a force kill fallback. */
1293
2209
  function closeCodexAppServerTransport(child, options = {}) {
1294
- child.stdin.end?.();
1295
- child.stdin.destroy?.();
2210
+ beginCodexAppServerTransportClose(child, options);
2211
+ }
2212
+ function beginCodexAppServerTransportClose(child, options) {
2213
+ const current = CODEX_APP_SERVER_TRANSPORT_CLOSES.get(child);
2214
+ if (current) return current;
2215
+ if (process.platform === "win32" || !child.pid || !child.kill || hasCodexAppServerTransportExited(child)) {
2216
+ finishCodexAppServerTransportClose(child, options);
2217
+ const completed = Promise.resolve();
2218
+ CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, completed);
2219
+ return completed;
2220
+ }
2221
+ const closing = (async () => {
2222
+ let resumeRoot;
2223
+ try {
2224
+ resumeRoot = await terminateCodexAppServerDescendants(child);
2225
+ } catch {
2226
+ resumeRoot = void 0;
2227
+ }
2228
+ try {
2229
+ finishCodexAppServerTransportClose(child, options, resumeRoot);
2230
+ } catch {
2231
+ signalCodexAppServerTransport(child, "SIGKILL");
2232
+ }
2233
+ })();
2234
+ CODEX_APP_SERVER_TRANSPORT_CLOSES.set(child, closing);
2235
+ return closing;
2236
+ }
2237
+ function finishCodexAppServerTransportClose(child, options, resumeRoot) {
1296
2238
  const forceKillDelayMs = options.forceKillDelayMs ?? 1e3;
1297
2239
  const forceKill = setTimeout(() => {
1298
2240
  if (hasCodexAppServerTransportExited(child)) return;
@@ -1304,6 +2246,12 @@ function closeCodexAppServerTransport(child, options = {}) {
1304
2246
  child.stdout.destroy?.();
1305
2247
  child.stderr.destroy?.();
1306
2248
  });
2249
+ try {
2250
+ child.stdin.end?.();
2251
+ child.stdin.destroy?.();
2252
+ } finally {
2253
+ resumeRoot?.();
2254
+ }
1307
2255
  child.unref?.();
1308
2256
  child.stdout.unref?.();
1309
2257
  child.stderr.unref?.();
@@ -1311,7 +2259,7 @@ function closeCodexAppServerTransport(child, options = {}) {
1311
2259
  }
1312
2260
  /** Closes a transport and waits briefly for an exit event. */
1313
2261
  async function closeCodexAppServerTransportAndWait(child, options = {}) {
1314
- if (!hasCodexAppServerTransportExited(child)) closeCodexAppServerTransport(child, options);
2262
+ if (!hasCodexAppServerTransportExited(child)) await beginCodexAppServerTransportClose(child, options);
1315
2263
  return await waitForCodexAppServerTransportExit(child, options.exitTimeoutMs ?? 2e3);
1316
2264
  }
1317
2265
  function hasCodexAppServerTransportExited(child) {
@@ -1344,15 +2292,6 @@ function signalCodexAppServerTransport(child, signal) {
1344
2292
  child.kill?.(signal);
1345
2293
  }
1346
2294
  //#endregion
1347
- //#region extensions/codex/src/app-server/version.ts
1348
- /**
1349
- * Version and package pins for the managed Codex app-server runtime.
1350
- */
1351
- /** Exact Codex app-server version shipped and supported by the OpenClaw Codex bridge. */
1352
- const CODEX_APP_SERVER_VERSION = "0.146.0";
1353
- /** npm package name for the managed Codex app-server binary. */
1354
- const MANAGED_CODEX_APP_SERVER_PACKAGE = "@openai/codex";
1355
- //#endregion
1356
2295
  //#region extensions/codex/src/app-server/client.ts
1357
2296
  /**
1358
2297
  * JSON-RPC client for Codex app-server transports, including request/response
@@ -1379,16 +2318,6 @@ function getCodexAppServerClientInstanceId(client) {
1379
2318
  function resolveCodexAppServerClientInstanceId(client) {
1380
2319
  return client.getInstanceId?.call(client) ?? getCodexAppServerClientInstanceId(client);
1381
2320
  }
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
2321
  var CodexAppServerLocalRequestCancellationError = class extends Error {
1393
2322
  constructor(method, reason, mayHaveWritten) {
1394
2323
  super(`${method} ${reason}`);
@@ -1431,22 +2360,6 @@ function isCodexAppServerPrewriteRequestCancellationError(error) {
1431
2360
  function isCodexAppServerIndeterminateTransportError(error) {
1432
2361
  return error instanceof Error && "code" in error && error.code === "CODEX_APP_SERVER_REQUEST_TRANSPORT_INDETERMINATE" && "mayHaveWritten" in error && error.mayHaveWritten === true;
1433
2362
  }
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
2363
  /** Returns true for errors that mean the app-server transport is closed. */
1451
2364
  function isCodexAppServerConnectionClosedError(error) {
1452
2365
  if (!(error instanceof Error)) return false;
@@ -1469,8 +2382,8 @@ var CodexAppServerClient = class CodexAppServerClient {
1469
2382
  this.child = child;
1470
2383
  this.lines = createInterface({ input: child.stdout });
1471
2384
  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))));
2385
+ this.lines.on("error", (error) => this.closeWithError(toStringifiedError(error)));
2386
+ child.stdout.on("error", (error) => this.closeWithError(toStringifiedError(error)));
1474
2387
  child.stderr.setEncoding("utf8");
1475
2388
  child.stderr.on("data", (text) => {
1476
2389
  this.stderrTail = appendBoundedTail(this.stderrTail, text, CODEX_APP_SERVER_STDERR_TAIL_MAX);
@@ -1480,12 +2393,12 @@ var CodexAppServerClient = class CodexAppServerClient {
1480
2393
  child.stderr.on("error", (error) => {
1481
2394
  embeddedAgentLog.warn("codex app-server stderr stream failed", { error });
1482
2395
  });
1483
- child.once("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
2396
+ child.once("error", (error) => this.closeWithError(toStringifiedError(error)));
1484
2397
  child.once("exit", (code, signal) => {
1485
2398
  this.transportExited = true;
1486
2399
  this.closeWithError(buildCodexAppServerExitError(code, signal, this.stderrTail));
1487
2400
  });
1488
- child.stdin.on?.("error", (error) => this.closeWithError(error instanceof Error ? error : new Error(String(error))));
2401
+ child.stdin.on?.("error", (error) => this.closeWithError(toStringifiedError(error)));
1489
2402
  }
1490
2403
  /** Starts a new app-server client using resolved runtime start options. */
1491
2404
  static start(options) {
@@ -1512,7 +2425,13 @@ var CodexAppServerClient = class CodexAppServerClient {
1512
2425
  title: "OpenClaw",
1513
2426
  version: OPENCLAW_VERSION
1514
2427
  },
1515
- capabilities: { experimentalApi: true }
2428
+ capabilities: {
2429
+ experimentalApi: true,
2430
+ extensions: {
2431
+ "openai/standard-form-input": {},
2432
+ "openai/form": {}
2433
+ }
2434
+ }
1516
2435
  });
1517
2436
  this.serverVersion = assertSupportedCodexAppServerVersion(response);
1518
2437
  this.runtimeIdentity = buildCodexAppServerRuntimeIdentity(response, this.serverVersion);
@@ -1531,6 +2450,10 @@ var CodexAppServerClient = class CodexAppServerClient {
1531
2450
  getStderrDiagnostic() {
1532
2451
  return redactCodexAppServerLinePreview(this.stderrTail) || void 0;
1533
2452
  }
2453
+ /** Returns the terminal transport error that closed this physical client. */
2454
+ getCloseError() {
2455
+ return this.closeError;
2456
+ }
1534
2457
  /** Stable generation id for this exact physical client instance. */
1535
2458
  getInstanceId() {
1536
2459
  return this.instanceId;
@@ -1700,7 +2623,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1700
2623
  onWriteAttempt?.();
1701
2624
  this.writeMessage(message, (error) => rejectPending(error));
1702
2625
  } catch (error) {
1703
- rejectPending(error instanceof Error ? error : new Error(String(error)));
2626
+ rejectPending(toStringifiedError(error));
1704
2627
  }
1705
2628
  });
1706
2629
  }
@@ -1871,7 +2794,7 @@ var CodexAppServerClient = class CodexAppServerClient {
1871
2794
  result: defaultServerRequestResponse(request)
1872
2795
  });
1873
2796
  } catch (error) {
1874
- const message = error instanceof Error ? error.message : String(error);
2797
+ const message = coerceErrorMessage(error);
1875
2798
  embeddedAgentLog.warn("codex app-server server request handler failed", {
1876
2799
  id: request.id,
1877
2800
  method: request.method,
@@ -1958,7 +2881,7 @@ function defaultServerRequestResponse(request) {
1958
2881
  scope: "turn"
1959
2882
  };
1960
2883
  if (request.method === "item/tool/requestUserInput") return { answers: {} };
1961
- if (request.method === "mcpServer/elicitation/request") return { action: "decline" };
2884
+ if (request.method === "mcpServer/elicitation/request") return createCodexElicitationResponse("decline", null, { message: "OpenClaw has no interactive handler for this elicitation." });
1962
2885
  return {};
1963
2886
  }
1964
2887
  function stringifyCodexAppServerMessage(message) {
@@ -1978,24 +2901,30 @@ function timeoutServerRequestResponse(request) {
1978
2901
  var CodexAppServerVersionError = class extends Error {
1979
2902
  constructor(detectedVersion) {
1980
2903
  const detected = detectedVersion ? `detected ${detectedVersion}` : "OpenClaw could not determine the running Codex version";
1981
- 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.`);
2904
+ super(`Codex app-server ${MIN_SUPPORTED_CODEX_APP_SERVER_VERSION} or newer is required, but ${detected}. Update the configured Codex app-server binary, or remove custom command overrides to use the managed binary.`);
1982
2905
  this.name = "CodexAppServerVersionError";
1983
2906
  this.detectedVersion = detectedVersion;
1984
2907
  }
1985
2908
  };
1986
2909
  function assertSupportedCodexAppServerVersion(response) {
1987
2910
  const detectedVersion = readCodexVersionFromUserAgent(response.userAgent);
1988
- if (detectedVersion !== "0.146.0") throw new CodexAppServerVersionError(detectedVersion);
2911
+ if (!detectedVersion) throw new CodexAppServerVersionError(detectedVersion);
2912
+ const detected = parse(detectedVersion);
2913
+ if (!detected || detected.compare("0.147.0") < 0) throw new CodexAppServerVersionError(detectedVersion);
2914
+ if (detected.compare("0.149.1") > 0) embeddedAgentLog.warn("codex app-server is newer than OpenClaw's managed runtime; continuing with normal startup validation", {
2915
+ detectedVersion,
2916
+ validatedVersion: CODEX_APP_SERVER_VERSION
2917
+ });
1989
2918
  return detectedVersion;
1990
2919
  }
1991
2920
  function isUnsupportedCodexAppServerVersionError(error) {
1992
2921
  return error instanceof CodexAppServerVersionError;
1993
2922
  }
1994
2923
  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);
2924
+ const userAgent = normalizeOptionalString(response.userAgent);
2925
+ const codexHome = normalizeOptionalString(response.codexHome);
2926
+ const platformFamily = normalizeOptionalString(response.platformFamily);
2927
+ const platformOs = normalizeOptionalString(response.platformOs);
1999
2928
  return {
2000
2929
  serverVersion,
2001
2930
  ...userAgent ? { userAgent } : {},
@@ -2004,10 +2933,6 @@ function buildCodexAppServerRuntimeIdentity(response, serverVersion) {
2004
2933
  ...platformOs ? { platformOs } : {}
2005
2934
  };
2006
2935
  }
2007
- function readNonEmptyInitializeString(value) {
2008
- const trimmed = value?.trim();
2009
- return trimmed ? trimmed : void 0;
2010
- }
2011
2936
  /** Extracts the Codex version from the app-server initialize user-agent field. */
2012
2937
  function readCodexVersionFromUserAgent(userAgent) {
2013
2938
  return (userAgent?.match(/^[^/]+\/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)(?:[\s(]|$)/))?.[1];
@@ -2027,7 +2952,7 @@ function buildCodexAppServerExitError(code, signal, stderrTail) {
2027
2952
  }
2028
2953
  function shouldBufferCodexAppServerParseFailure(value, error) {
2029
2954
  if (!value.startsWith("{") && !value.startsWith("[")) return false;
2030
- const message = error instanceof Error ? error.message : String(error);
2955
+ const message = coerceErrorMessage(error);
2031
2956
  return message.includes("Unterminated string") || message.includes("Unexpected end of JSON input");
2032
2957
  }
2033
2958
  function logCodexAppServerParseFailure(value, error, fragmentCount) {
@@ -2035,7 +2960,7 @@ function logCodexAppServerParseFailure(value, error, fragmentCount) {
2035
2960
  const suffix = fragmentCount > 1 ? ` fragments=${fragmentCount}` : "";
2036
2961
  embeddedAgentLog.warn("failed to parse codex app-server message", {
2037
2962
  error,
2038
- errorMessage: error instanceof Error ? error.message : String(error),
2963
+ errorMessage: coerceErrorMessage(error),
2039
2964
  fragmentCount,
2040
2965
  linePreview,
2041
2966
  consoleMessage: `failed to parse codex app-server message${suffix}: preview=${JSON.stringify(linePreview)}`
@@ -2056,206 +2981,101 @@ function formatExitValue(value) {
2056
2981
  return "unknown";
2057
2982
  }
2058
2983
  //#endregion
2059
- //#region extensions/codex/src/app-server/managed-binary.ts
2984
+ //#region extensions/codex/src/app-server/attempt-timeouts.ts
2060
2985
  /**
2061
- * Resolves the managed Codex app-server binary shipped with or installed beside
2062
- * the Codex plugin before stdio startup.
2986
+ * Timeout defaults and normalizers for Codex app-server startup and turn
2987
+ * liveness watches.
2063
2988
  */
2064
- const CODEX_PLUGIN_ROOT = resolveDefaultCodexPluginRoot(path.dirname(fileURLToPath(import.meta.url)));
2065
- const MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS = ["/Applications/ChatGPT.app/Contents/Resources/codex", "/Applications/Codex.app/Contents/Resources/codex"];
2066
- /** Rewrites managed stdio start options to point at an executable Codex binary path. */
2067
- async function resolveManagedCodexAppServerStartOptions(startOptions, options = {}) {
2068
- if (startOptions.transport !== "stdio" || startOptions.commandSource !== "managed") return startOptions;
2069
- const platform = options.platform ?? process.platform;
2070
- const paths = resolveManagedCodexAppServerPaths({
2071
- platform,
2072
- pluginRoot: options.pluginRoot,
2073
- managedCommandOrder: startOptions.managedCommandOrder
2074
- });
2075
- const pathExists = options.pathExists ?? commandPathExists;
2076
- const commandPaths = await findManagedCodexAppServerCommandPaths({
2077
- candidateCommandPaths: paths.candidateCommandPaths,
2078
- pathExists,
2079
- platform
2080
- });
2081
- const commandPath = expectDefined(commandPaths[0], "resolved managed Codex command path");
2082
- const managedFallbackCommandPaths = commandPaths.slice(1);
2083
- return {
2084
- ...startOptions,
2085
- command: commandPath,
2086
- commandSource: "resolved-managed",
2087
- ...managedFallbackCommandPaths.length > 0 ? { managedFallbackCommandPaths } : {}
2088
- };
2089
- }
2090
- /** Resolves the native artifact behind a successful managed launcher selection. */
2091
- function resolveManagedCodexNativeCommand(command, options = {}) {
2092
- const platform = options.platform ?? process.platform;
2093
- if (platform === "darwin" && MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS.some((candidate) => candidate === command)) return command;
2094
- const target = resolveCodexNativeTarget(platform, options.arch ?? process.arch);
2095
- if (!target) return;
2096
- const packageRoot = resolveManagedCodexPackageRootForCommand(command, platform);
2097
- if (!packageRoot) return;
2098
- const resolvePackageJson = options.resolvePackageJson ?? resolvePackageJsonFromRoot;
2099
- const pathExists = options.pathExists ?? existsSync;
2100
- for (const packageName of [target.packageName, MANAGED_CODEX_APP_SERVER_PACKAGE]) {
2101
- const packageJsonPath = resolvePackageJson(packageName, packageRoot);
2102
- if (!packageJsonPath) continue;
2103
- const candidate = path.join(path.dirname(packageJsonPath), "vendor", target.triple, "bin", platform === "win32" ? "codex.exe" : "codex");
2104
- if (pathExists(candidate)) return candidate;
2105
- }
2106
- }
2107
- function resolveManagedCodexPackageRootForCommand(command, platform) {
2108
- const pathApi = pathForPlatform(platform);
2109
- const commandPaths = [command];
2110
- try {
2111
- commandPaths.unshift(realpathSync(command));
2112
- } catch {}
2113
- for (const commandPath of commandPaths) {
2114
- let current = pathApi.dirname(commandPath);
2115
- while (true) {
2116
- if (pathApi.basename(current) === "codex" && pathApi.basename(pathApi.dirname(current)) === "@openai") return current;
2117
- if (pathApi.basename(current) === ".bin") return pathApi.join(pathApi.dirname(current), "@openai", "codex");
2118
- const parent = pathApi.dirname(current);
2119
- if (parent === current) break;
2120
- current = parent;
2121
- }
2989
+ /** Minimum startup timeout accepted by the Codex app-server harness. */
2990
+ const CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS = 100;
2991
+ /** Default idle timeout while waiting for app-server turn completion. */
2992
+ const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 6e4;
2993
+ /** Short guard after apparent assistant completion. */
2994
+ const CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 1e4;
2995
+ const CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS = 5 * 6e4;
2996
+ /** Guard after reasoning/commentary progress when no tool handoff occurred. */
2997
+ const CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS = 5 * 6e4;
2998
+ /** Long terminal idle watch for app-server turns that never send completion. */
2999
+ const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 6e4;
3000
+ var CodexAppServerStartupError = class extends Error {
3001
+ constructor(reason, message = reason === "timed_out" ? "codex app-server startup timed out" : "codex app-server startup aborted") {
3002
+ super(message);
3003
+ this.reason = reason;
3004
+ this.code = "CODEX_APP_SERVER_STARTUP_CANCELLED";
3005
+ this.name = "CodexAppServerStartupError";
2122
3006
  }
3007
+ };
3008
+ function isCodexAppServerStartupError(error, reason) {
3009
+ 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);
2123
3010
  }
2124
- function resolveCodexNativeTarget(platform, arch) {
2125
- if ((platform === "linux" || platform === "android") && arch === "x64") return {
2126
- packageName: "@openai/codex-linux-x64",
2127
- triple: "x86_64-unknown-linux-musl"
2128
- };
2129
- if ((platform === "linux" || platform === "android") && arch === "arm64") return {
2130
- packageName: "@openai/codex-linux-arm64",
2131
- triple: "aarch64-unknown-linux-musl"
2132
- };
2133
- if (platform === "darwin" && arch === "x64") return {
2134
- packageName: "@openai/codex-darwin-x64",
2135
- triple: "x86_64-apple-darwin"
2136
- };
2137
- if (platform === "darwin" && arch === "arm64") return {
2138
- packageName: "@openai/codex-darwin-arm64",
2139
- triple: "aarch64-apple-darwin"
2140
- };
2141
- if (platform === "win32" && arch === "x64") return {
2142
- packageName: "@openai/codex-win32-x64",
2143
- triple: "x86_64-pc-windows-msvc"
2144
- };
2145
- if (platform === "win32" && arch === "arm64") return {
2146
- packageName: "@openai/codex-win32-arm64",
2147
- triple: "aarch64-pc-windows-msvc"
2148
- };
3011
+ function resolvePositiveIntegerTimeoutMs(value, fallbackMs) {
3012
+ return resolveTimerTimeoutMs(value, resolveTimerTimeoutMs(fallbackMs, 1));
2149
3013
  }
2150
- function resolvePackageJsonFromRoot(packageName, root) {
3014
+ /** Runs startup work with abort and timeout handling plus optional cleanup. */
3015
+ async function withCodexStartupTimeout(params) {
3016
+ if (params.signal.aborted) throw new CodexAppServerStartupError("aborted");
3017
+ let timeout;
3018
+ let abortCleanup;
3019
+ let timeoutError;
3020
+ let timeoutCleanup;
2151
3021
  try {
2152
- return createRequire(path.join(root, "package.json")).resolve(`${packageName}/package.json`);
2153
- } catch {
2154
- return;
2155
- }
2156
- }
2157
- /** Returns the preferred and fallback managed Codex binary paths for a plugin root. */
2158
- function resolveManagedCodexAppServerPaths(params) {
2159
- const platform = params.platform ?? process.platform;
2160
- const candidateCommandPaths = resolveManagedCodexAppServerCommandCandidates(params.pluginRoot ?? CODEX_PLUGIN_ROOT, platform, params.managedCommandOrder ?? "package-first");
2161
- return {
2162
- commandPath: candidateCommandPaths[0] ?? "",
2163
- candidateCommandPaths
2164
- };
2165
- }
2166
- function resolveManagedCodexAppServerCommandCandidates(pluginRoot, platform, managedCommandOrder) {
2167
- const pathApi = pathForPlatform(platform);
2168
- const commandName = platform === "win32" ? "codex.cmd" : "codex";
2169
- const roots = resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform);
2170
- const packageCommandPaths = [...roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)), ...resolveManagedCodexPackageBinCandidates(roots, platform)];
2171
- const desktopCommandPaths = resolveDesktopCodexAppServerCommandCandidates(platform);
2172
- const orderedCommandPaths = managedCommandOrder === "desktop-first" ? [...desktopCommandPaths, ...packageCommandPaths] : [...packageCommandPaths, ...desktopCommandPaths];
2173
- return [...new Set(orderedCommandPaths)];
2174
- }
2175
- function resolveDesktopCodexAppServerCommandCandidates(platform) {
2176
- return platform === "darwin" ? [...MACOS_DESKTOP_CODEX_APP_SERVER_COMMANDS] : [];
2177
- }
2178
- function resolveDefaultCodexPluginRoot(moduleDir) {
2179
- const moduleBaseName = path.basename(moduleDir);
2180
- if (moduleBaseName === "dist" || moduleBaseName === "dist-runtime") return path.dirname(moduleDir);
2181
- return path.resolve(moduleDir, "..", "..");
2182
- }
2183
- function resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform) {
2184
- const pathApi = pathForPlatform(platform);
2185
- const directRoots = [
2186
- pluginRoot,
2187
- pathApi.dirname(pluginRoot),
2188
- pathApi.dirname(pathApi.dirname(pluginRoot)),
2189
- isDistExtensionRoot(pluginRoot, platform) ? pathApi.dirname(pathApi.dirname(pathApi.dirname(pluginRoot))) : null
2190
- ].filter((root) => Boolean(root));
2191
- return [.../* @__PURE__ */ new Set([...directRoots, ...resolveNearestNodeModulesProjectRoots(directRoots, platform)])];
2192
- }
2193
- function resolveNearestNodeModulesProjectRoots(roots, platform) {
2194
- const pathApi = pathForPlatform(platform);
2195
- const projectRoots = [];
2196
- for (const root of roots) {
2197
- let current = pathApi.resolve(root);
2198
- while (true) {
2199
- if (pathApi.basename(current) === "node_modules") {
2200
- projectRoots.push(pathApi.dirname(current));
2201
- break;
2202
- }
2203
- const parent = pathApi.dirname(current);
2204
- if (parent === current) break;
2205
- current = parent;
3022
+ return await Promise.race([params.operation(), new Promise((_, reject) => {
3023
+ const rejectOnce = (error) => {
3024
+ if (timeout) {
3025
+ clearTimeout(timeout);
3026
+ timeout = void 0;
3027
+ }
3028
+ reject(error);
3029
+ };
3030
+ timeout = setTimeout(() => {
3031
+ timeoutError = new CodexAppServerStartupError("timed_out");
3032
+ timeoutCleanup = Promise.resolve(params.onTimeout?.()).then(() => void 0, () => void 0);
3033
+ timeoutCleanup.finally(() => {
3034
+ rejectOnce(timeoutError);
3035
+ });
3036
+ }, params.timeoutMs);
3037
+ const abortListener = () => rejectOnce(new CodexAppServerStartupError("aborted"));
3038
+ params.signal.addEventListener("abort", abortListener, { once: true });
3039
+ abortCleanup = () => params.signal.removeEventListener("abort", abortListener);
3040
+ })]);
3041
+ } catch (error) {
3042
+ if (timeoutError) {
3043
+ await timeoutCleanup;
3044
+ throw timeoutError;
2206
3045
  }
3046
+ throw error;
3047
+ } finally {
3048
+ if (timeout) clearTimeout(timeout);
3049
+ abortCleanup?.();
2207
3050
  }
2208
- return projectRoots;
2209
- }
2210
- function resolveManagedCodexPackageBinCandidates(roots, platform) {
2211
- if (platform === "win32") return [];
2212
- const candidates = [];
2213
- for (const root of roots) {
2214
- const candidate = resolveManagedCodexPackageBinCandidate(root);
2215
- if (candidate) candidates.push(candidate);
2216
- }
2217
- return candidates;
2218
3051
  }
2219
- function resolveManagedCodexPackageBinCandidate(root) {
2220
- try {
2221
- const packageJsonPath = createRequire(path.join(root, "package.json")).resolve(`${MANAGED_CODEX_APP_SERVER_PACKAGE}/package.json`);
2222
- const packageRoot = path.dirname(packageJsonPath);
2223
- 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;
2225
- return binPath ? path.resolve(packageRoot, binPath) : null;
2226
- } catch {
2227
- return null;
2228
- }
3052
+ /** Resolves startup timeout while honoring the configured floor. */
3053
+ function resolveCodexStartupTimeoutMs(params) {
3054
+ const timeoutFloorMs = resolvePositiveIntegerTimeoutMs(params.timeoutFloorMs, CODEX_APP_SERVER_STARTUP_TIMEOUT_FLOOR_MS);
3055
+ const timeoutMs = resolvePositiveIntegerTimeoutMs(params.timeoutMs, timeoutFloorMs);
3056
+ return Math.max(timeoutFloorMs, timeoutMs);
2229
3057
  }
2230
- function isRecord(value) {
2231
- return typeof value === "object" && value !== null;
3058
+ /** Resolves the completion-idle timeout for an active turn. */
3059
+ function resolveCodexTurnCompletionIdleTimeoutMs(value) {
3060
+ return resolvePositiveIntegerTimeoutMs(value, CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS);
2232
3061
  }
2233
- function isDistExtensionRoot(pluginRoot, platform) {
2234
- const pathApi = pathForPlatform(platform);
2235
- const extensionsDir = pathApi.dirname(pluginRoot);
2236
- const distDir = pathApi.dirname(extensionsDir);
2237
- return pathApi.basename(extensionsDir) === "extensions" && (pathApi.basename(distDir) === "dist" || pathApi.basename(distDir) === "dist-runtime");
3062
+ /** Resolves the short assistant-completion release timeout. */
3063
+ function resolveCodexTurnAssistantCompletionIdleTimeoutMs(value) {
3064
+ return resolvePositiveIntegerTimeoutMs(value, CODEX_TURN_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS);
2238
3065
  }
2239
- function pathForPlatform(platform) {
2240
- return platform === "win32" ? path.win32 : path.posix;
3066
+ /** Resolves the conservative post-tool raw assistant guard timeout. */
3067
+ function resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs(value, fallbackMs) {
3068
+ return resolvePositiveIntegerTimeoutMs(value, Math.max(resolvePositiveIntegerTimeoutMs(void 0, fallbackMs), CODEX_POST_TOOL_RAW_ASSISTANT_COMPLETION_IDLE_TIMEOUT_MS));
2241
3069
  }
2242
- async function findManagedCodexAppServerCommandPaths(params) {
2243
- const commandPaths = [];
2244
- for (const commandPath of params.candidateCommandPaths) if (await params.pathExists(commandPath, params.platform)) commandPaths.push(commandPath);
2245
- if (commandPaths.length > 0) return commandPaths;
2246
- throw new Error([
2247
- `Managed Codex app-server binary was not found for ${MANAGED_CODEX_APP_SERVER_PACKAGE}.`,
2248
- "Reinstall or update OpenClaw, or run pnpm install in a source checkout.",
2249
- "Set plugins.entries.codex.config.appServer.command or OPENCLAW_CODEX_APP_SERVER_BIN to use a custom Codex binary."
2250
- ].join(" "));
3070
+ /** Resolves the long terminal turn idle timeout. */
3071
+ function resolveCodexTurnTerminalIdleTimeoutMs(value, runTimeoutOverrideMs) {
3072
+ const explicitRunBudgetMs = resolvePositiveIntegerTimeoutMs(runTimeoutOverrideMs, CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS);
3073
+ return resolvePositiveIntegerTimeoutMs(value, Math.max(CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS, explicitRunBudgetMs));
2251
3074
  }
2252
- async function commandPathExists(filePath, platform) {
2253
- try {
2254
- await access(filePath, platform === "win32" ? constants.F_OK : constants.X_OK);
2255
- return true;
2256
- } catch {
2257
- return false;
2258
- }
3075
+ /** Adds gateway grace time to a caller timeout without overflowing invalid values. */
3076
+ function resolveCodexGatewayTimeoutWithGraceMs(timeoutMs, graceMs = 1e4) {
3077
+ const timeout = resolvePositiveIntegerTimeoutMs(timeoutMs, 1);
3078
+ return addTimerTimeoutGraceMs(timeout, resolveTimerTimeoutMs(graceMs, 0, 0)) ?? timeout;
2259
3079
  }
2260
3080
  //#endregion
2261
3081
  //#region extensions/codex/src/app-server/native-config-fence.ts
@@ -2325,6 +3145,9 @@ async function waitForPreviousFence(previous, options) {
2325
3145
  * Thin Codex app-server timeout adapter around OpenClaw's shared security
2326
3146
  * runtime timeout helper.
2327
3147
  */
3148
+ function resolveAbortError(signal) {
3149
+ return signal.reason instanceof Error ? signal.reason : new Error("Codex app-server operation aborted", { cause: signal.reason });
3150
+ }
2328
3151
  /** Awaits a promise with a Codex-specific timeout error message. */
2329
3152
  async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
2330
3153
  return await withTimeout(promise, timeoutMs, {
@@ -2332,6 +3155,22 @@ async function withTimeout$1(promise, timeoutMs, timeoutMessage, createError) {
2332
3155
  ...createError ? { createError } : {}
2333
3156
  });
2334
3157
  }
3158
+ /** Bounds an operation by both its owner lifecycle and one total wall-clock budget. */
3159
+ async function withAbortableTimeout(params) {
3160
+ const signal = params.signal;
3161
+ if (signal?.aborted) throw resolveAbortError(signal);
3162
+ let removeAbortListener;
3163
+ const operation = signal ? Promise.race([params.promise, new Promise((_, reject) => {
3164
+ const onAbort = () => reject(resolveAbortError(signal));
3165
+ signal.addEventListener("abort", onAbort, { once: true });
3166
+ removeAbortListener = () => signal.removeEventListener("abort", onAbort);
3167
+ })]) : params.promise;
3168
+ try {
3169
+ return await withTimeout$1(operation, params.timeoutMs, params.timeoutMessage, params.createTimeoutError);
3170
+ } finally {
3171
+ removeAbortListener?.();
3172
+ }
3173
+ }
2335
3174
  //#endregion
2336
3175
  //#region extensions/codex/src/app-server/shared-client.ts
2337
3176
  /**
@@ -2358,7 +3197,6 @@ var shared_client_exports = /* @__PURE__ */ __exportAll({
2358
3197
  withLeasedCodexAppServerClientStartSelectionRetry: () => withLeasedCodexAppServerClientStartSelectionRetry
2359
3198
  });
2360
3199
  const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
2361
- const suspectClosedClients = /* @__PURE__ */ new WeakSet();
2362
3200
  const SHARED_CODEX_APP_SERVER_CLIENT_STATE = Symbol.for("openclaw.codexAppServerClientState");
2363
3201
  const SHARED_CODEX_APP_SERVER_CLIENT_DISPOSER = Symbol.for("openclaw.codexAppServerClientDisposer");
2364
3202
  const CODEX_APP_SERVER_CLIENT_START_METADATA = Symbol.for("openclaw.codexAppServerClientStartMetadata");
@@ -2366,6 +3204,8 @@ function getSharedCodexAppServerClientState() {
2366
3204
  const globalState = globalThis;
2367
3205
  globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE] ??= {
2368
3206
  clients: /* @__PURE__ */ new Map(),
3207
+ liveClients: /* @__PURE__ */ new Set(),
3208
+ entriesByClient: /* @__PURE__ */ new WeakMap(),
2369
3209
  leasedReleases: /* @__PURE__ */ new WeakMap()
2370
3210
  };
2371
3211
  return globalState[SHARED_CODEX_APP_SERVER_CLIENT_STATE];
@@ -2480,9 +3320,11 @@ async function resolveCodexAppServerClientStartContext(options) {
2480
3320
  startOptions: requestedStartOptions,
2481
3321
  agentDir
2482
3322
  })),
3323
+ agentId: options?.agentId,
2483
3324
  agentDir,
2484
3325
  authProfileId: usesNativeAuth || preparedAuth?.kind === "api-key" ? null : authProfileId,
2485
3326
  ...resolvedPreparedAuth ? { preparedAuth: resolvedPreparedAuth } : {},
3327
+ authRequirement,
2486
3328
  config: options?.config,
2487
3329
  pluginConfig: options?.pluginConfig,
2488
3330
  ...authProfileStore ? { authProfileStore } : {}
@@ -2725,14 +3567,21 @@ function createSharedCodexAppServerClientStartup(params) {
2725
3567
  runtimeArtifactSignal: params.runtimeArtifactSignal,
2726
3568
  config: params.config,
2727
3569
  onStartedClient: (startedClient) => {
3570
+ const state = getSharedCodexAppServerClientState();
2728
3571
  params.entry.client = startedClient;
3572
+ state.liveClients.add(startedClient);
3573
+ startedClient.addCloseHandler((closedClient) => {
3574
+ state.liveClients.delete(closedClient);
3575
+ if (state.entriesByClient.get(closedClient) === params.entry) clearSharedClientEntryIfCurrent(params.key, closedClient);
3576
+ });
2729
3577
  for (const callback of params.entry.onStartedClientCallbacks) callback(startedClient);
2730
3578
  retirePendingSharedClientEntryIfUnclaimed(params.key, params.entry);
2731
3579
  },
2732
3580
  onInitializedClient: () => initialized.resolve()
2733
3581
  }).then((client) => {
3582
+ const state = getSharedCodexAppServerClientState();
2734
3583
  params.entry.client = client;
2735
- client.addCloseHandler((closedClient) => clearSharedClientEntryIfCurrent(params.key, closedClient));
3584
+ state.entriesByClient.set(client, params.entry);
2736
3585
  return client;
2737
3586
  }, (error) => {
2738
3587
  initialized.reject(error);
@@ -2772,7 +3621,7 @@ async function startInitializedCodexAppServerClient(params) {
2772
3621
  const timeoutMs = params.timeoutMs ?? 0;
2773
3622
  const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
2774
3623
  for (const [index, startOptions] of startOptionsCandidates.entries()) {
2775
- const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-CmtG_t1L.js") : void 0;
3624
+ const runtimeArtifactModule = params.runtimeArtifactMode ? await import("./runtime-artifact-BvyT1uyd.js") : void 0;
2776
3625
  const nativeCommandBeforeStart = startOptions.commandSource === "resolved-managed" ? resolveManagedCodexNativeCommand(startOptions.command) : void 0;
2777
3626
  const runtimeArtifactBeforeStart = runtimeArtifactModule ? await runtimeArtifactModule.captureCodexAppServerRuntimeArtifactBeforeStart({
2778
3627
  startOptions,
@@ -2922,11 +3771,9 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
2922
3771
  entry.closeWhenIdle = true;
2923
3772
  if (opts?.failActiveLeases) {
2924
3773
  entry.closeError = /* @__PURE__ */ new Error("codex app-server client is closed");
2925
- const closed = closeRetiredSharedClientEntry(entry);
2926
- if (closed) suspectClosedClients.add(client);
2927
3774
  return {
2928
3775
  activeLeases: entry.activeLeases,
2929
- closed
3776
+ closed: closeRetiredSharedClientEntry(entry)
2930
3777
  };
2931
3778
  }
2932
3779
  const closed = closeRetiredSharedClientEntryIfIdle(entry);
@@ -2935,18 +3782,17 @@ function retireSharedCodexAppServerClientIfCurrent(client, opts) {
2935
3782
  closed
2936
3783
  };
2937
3784
  }
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();
3785
+ const detachedEntry = state.entriesByClient.get(client);
3786
+ if (detachedEntry && (detachedEntry.client === client || detachedEntry.closeError)) {
3787
+ if (opts?.failActiveLeases && !detachedEntry.closeError) {
3788
+ detachedEntry.closeError = /* @__PURE__ */ new Error("codex app-server client is closed");
2943
3789
  return {
2944
- activeLeases,
2945
- closed: true
3790
+ activeLeases: detachedEntry.activeLeases,
3791
+ closed: closeRetiredSharedClientEntry(detachedEntry)
2946
3792
  };
2947
3793
  }
2948
3794
  return {
2949
- activeLeases,
3795
+ activeLeases: detachedEntry.activeLeases,
2950
3796
  closed: false
2951
3797
  };
2952
3798
  }
@@ -3063,7 +3909,7 @@ function retirePendingSharedClientEntryIfUnclaimed(key, entry) {
3063
3909
  closeRetiredSharedClientEntry(entry);
3064
3910
  }
3065
3911
  function collectSharedClients(state) {
3066
- return [...new Set([...state.clients.values()].map((entry) => entry.client).filter((client) => Boolean(client)))];
3912
+ return [...state.liveClients];
3067
3913
  }
3068
3914
  //#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 };
3915
+ export { rememberCodexRateLimitsRead as $, isCodexAppServerApprovalRequest as A, CodexAppServerRpcError as B, resolveCodexPostToolRawAssistantCompletionIdleTimeoutMs as C, resolveCodexTurnTerminalIdleTimeoutMs as D, resolveCodexTurnCompletionIdleTimeoutMs as E, isCodexAppServerPrewriteRequestCancellationError as F, isCodexAppServerClientRuntimeLive as G, claimCodexAppServerLiveThread as H, isCodexAppServerRequestTimeoutError as I, releaseCodexAppServerLiveThread as J, isCodexAppServerLiveThreadClaimed as K, isUnsupportedCodexAppServerVersionError as L, isCodexAppServerConnectionClosedError as M, isCodexAppServerIndeterminateRequestCancellationError as N, withCodexStartupTimeout as O, isCodexAppServerIndeterminateTransportError as P, readRecentCodexRateLimits as Q, resolveCodexAppServerClientInstanceId as R, resolveCodexGatewayTimeoutWithGraceMs as S, resolveCodexTurnAssistantCompletionIdleTimeoutMs as T, consumeCodexAppServerLiveThread as U, createCodexElicitationResponse as V, ensureCodexAppServerClientRuntime as W, unsubscribeCodexAppServerLiveThread as X, retainCodexAppServerLiveThread as Y, readCodexRateLimitsRevision as Z, withTimeout$1 as _, getLeasedSharedCodexAppServerClient as a, resolveCodexAppServerPreparedAuthHandoff as at, CodexAppServerStartupError as b, releaseCodexAppServerClientLease as c, CODEX_OPENCLAW_DIRECT_DYNAMIC_TOOL_NAMESPACE as ct, retainSharedCodexAppServerClientByInstanceId as d, resolveFirstExistingMacOSDesktopCodexBundledMarketplacePath as dt, resolveCodexAppServerAuthAccountCacheKey as et, retainSharedCodexAppServerClientIfCurrent as f, resolveCodexAppServerHomeDir as ft, withAbortableTimeout as g, withLeasedCodexAppServerClientStartSelectionRetry as h, createIsolatedCodexAppServerClient as i, resolveCodexAppServerPreparedApiKeyCacheKey as it, isCodexAppServerBrokenPipeError as j, getCodexAppServerClientInstanceId as k, releaseLeasedSharedCodexAppServerClient as l, flattenCodexDynamicToolFunctions as lt, shared_client_exports as m, clearSharedCodexAppServerClientIfCurrentAndUnclaimed as n, resolveCodexAppServerAuthProfileIdForAgent as nt, getSharedCodexAppServerClient as o, CODEX_INTERACTIVE_CUSTOM_THREAD_SOURCES as ot, retireSharedCodexAppServerClientIfCurrent as p, resolveCodexAppServerLocalHomeDir as pt, protectCodexAppServerLiveThread as q, clearSharedCodexAppServerClientIfCurrentAndWait as r, resolveCodexAppServerFallbackApiKeyCacheKey as rt, isCodexAppServerStartSelectionChangedError as s, CODEX_INTERACTIVE_THREAD_SOURCE_KINDS as st, clearSharedCodexAppServerClientIfCurrent as t, resolveCodexAppServerAuthProfileId as tt, resolveCodexNativeConfigFenceKey as u, isJsonObject as ut, acquireCodexNativeConfigFence as v, resolveCodexStartupTimeoutMs as w, isCodexAppServerStartupError as x, CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS as y, closeCodexAppServerTransportAndWait as z };