codex-multi-auth 2.1.3 → 2.1.5

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 (35) hide show
  1. package/README.md +8 -6
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  5. package/dist/lib/request/fetch-helpers.js +13 -5
  6. package/dist/lib/request/fetch-helpers.js.map +1 -1
  7. package/dist/lib/runtime/config-toml.d.ts +2 -0
  8. package/dist/lib/runtime/config-toml.d.ts.map +1 -1
  9. package/dist/lib/runtime/config-toml.js +57 -4
  10. package/dist/lib/runtime/config-toml.js.map +1 -1
  11. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  12. package/dist/lib/runtime-rotation-proxy.js +242 -16
  13. package/dist/lib/runtime-rotation-proxy.js.map +1 -1
  14. package/dist/lib/update-notice.d.ts +12 -0
  15. package/dist/lib/update-notice.d.ts.map +1 -0
  16. package/dist/lib/update-notice.js +315 -0
  17. package/dist/lib/update-notice.js.map +1 -0
  18. package/dist/lib/usage/ledger.d.ts.map +1 -1
  19. package/dist/lib/usage/ledger.js +1 -0
  20. package/dist/lib/usage/ledger.js.map +1 -1
  21. package/dist/lib/usage/redaction.d.ts.map +1 -1
  22. package/dist/lib/usage/redaction.js +1 -0
  23. package/dist/lib/usage/redaction.js.map +1 -1
  24. package/dist/lib/usage/types.d.ts +1 -1
  25. package/dist/lib/usage/types.d.ts.map +1 -1
  26. package/package.json +4 -4
  27. package/scripts/codex-app-launcher.js +1 -1
  28. package/scripts/codex-multi-auth.js +5 -1
  29. package/scripts/codex-routing.js +8 -0
  30. package/scripts/codex.js +309 -59
  31. package/scripts/test-model-matrix.js +97 -13
  32. package/dist/lib/auto-update-checker.d.ts +0 -32
  33. package/dist/lib/auto-update-checker.d.ts.map +0 -1
  34. package/dist/lib/auto-update-checker.js +0 -725
  35. package/dist/lib/auto-update-checker.js.map +0 -1
package/scripts/codex.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { spawn } from "node:child_process";
4
4
  import { createHash, randomBytes } from "node:crypto";
@@ -41,6 +41,23 @@ const RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES = new Set([
41
41
  "auth.json",
42
42
  "accounts.json",
43
43
  ]);
44
+ const RUNTIME_ROTATION_SHADOW_HOME_OMIT_ROOT_DIRS = new Set(["multi-auth"]);
45
+ const RUNTIME_ROTATION_SHADOW_HOME_LINK_ONLY_ROOT_DIRS = new Set([
46
+ ".sandbox",
47
+ ".sandbox-bin",
48
+ ".sandbox-secrets",
49
+ ".tmp",
50
+ "ambient-suggestions",
51
+ "archived_sessions",
52
+ "backups",
53
+ "cache",
54
+ "generated_images",
55
+ "log",
56
+ "sqlite",
57
+ "tmp",
58
+ "understand-anything",
59
+ "vendor_imports",
60
+ ]);
44
61
  const SHADOW_HOME_STATE_FILE_SET = new Set(SHADOW_HOME_STATE_FILES);
45
62
  const SHADOW_HOME_CONFIG_FILE = "config.toml";
46
63
  const SHADOW_HOME_SYNC_LOCK_DIR = ".codex-multi-auth-shadow-sync.lock";
@@ -63,8 +80,8 @@ const DEFAULT_APP_RUNTIME_HELPER_DETACH_GRACE_MS = 5_000;
63
80
  const APP_RUNTIME_HELPER_LAUNCH_TIMEOUT_MS = 15_000;
64
81
  const APP_SERVER_SHIM_DIR_NAME = "app-server-shims";
65
82
  const APP_SERVER_SHIM_HELPER_PREFIX = "helper-";
66
- const DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS = 3_000;
67
- const STARTUP_AUTO_UPDATE_TIMED_OUT = Symbol("startup-auto-update-timed-out");
83
+ const DEFAULT_STARTUP_UPDATE_NOTICE_BUDGET_MS = 3_000;
84
+ const STARTUP_UPDATE_NOTICE_TIMED_OUT = Symbol("startup-update-notice-timed-out");
68
85
  let shadowHomeCleanupBusyFailuresRemaining = Number.parseInt(
69
86
  process.env.CODEX_MULTI_AUTH_TEST_SHADOW_CLEANUP_BUSY_FAILURES ?? "0",
70
87
  10,
@@ -89,6 +106,9 @@ const shadowHomeCleanupRetryMarkerDir =
89
106
  (process.env.CODEX_MULTI_AUTH_TEST_SHADOW_RETRY_MARKER_DIR ?? "").trim();
90
107
  let warnedInvalidRuntimeRotationProxyEnv = false;
91
108
  let warnedPendingAccountReadIdOverflow = false;
109
+ let warnedShadowHomeSqliteLinkFailure = false;
110
+ const warnedShadowHomeLinkOnlyDirectoryFailures = new Set();
111
+ const warnedShadowHomeSqliteSidecarPlaceholderFailures = new Set();
92
112
 
93
113
  async function loadRuntimeConstants() {
94
114
  const fallback = {
@@ -260,31 +280,27 @@ function readBooleanEnvFlag(name) {
260
280
  return null;
261
281
  }
262
282
 
263
- function isStartupAutoUpdateDebugEnabled() {
283
+ function isStartupUpdateNoticeDebugEnabled() {
264
284
  return readBooleanEnvFlag("CODEX_MULTI_AUTH_DEBUG") === true;
265
285
  }
266
286
 
267
- function shouldLogStartupAutoUpdateProgress() {
268
- return process.stderr.isTTY === true || isStartupAutoUpdateDebugEnabled();
287
+ function shouldLogStartupUpdateNotice() {
288
+ return process.stderr.isTTY === true || isStartupUpdateNoticeDebugEnabled();
269
289
  }
270
290
 
271
- function readStartupAutoUpdateBudgetMs() {
291
+ function readStartupUpdateNoticeBudgetMs() {
272
292
  const raw =
273
- process.env.CODEX_MULTI_AUTH_AUTO_UPDATE_STARTUP_BUDGET_MS ??
274
- process.env.CODEX_MULTI_AUTH_TEST_STARTUP_AUTO_UPDATE_BUDGET_MS;
275
- if (!raw) return DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS;
293
+ process.env.CODEX_MULTI_AUTH_UPDATE_NOTICE_STARTUP_BUDGET_MS ??
294
+ process.env.CODEX_MULTI_AUTH_TEST_STARTUP_UPDATE_NOTICE_BUDGET_MS;
295
+ if (!raw) return DEFAULT_STARTUP_UPDATE_NOTICE_BUDGET_MS;
276
296
  const parsed = Number.parseInt(raw, 10);
277
297
  return Number.isFinite(parsed) && parsed > 0
278
298
  ? parsed
279
- : DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS;
299
+ : DEFAULT_STARTUP_UPDATE_NOTICE_BUDGET_MS;
280
300
  }
281
301
 
282
- function resolveStartupAutoUpdateTimeouts(budgetMs) {
283
- const fetchTimeoutMs = Math.max(1, Math.floor(budgetMs * 0.4));
284
- return {
285
- fetchTimeoutMs,
286
- updateTimeoutMs: Math.max(1, budgetMs - fetchTimeoutMs),
287
- };
302
+ function resolveStartupUpdateNoticeTimeoutMs(budgetMs) {
303
+ return Math.max(1, Math.floor(budgetMs * 0.8));
288
304
  }
289
305
 
290
306
  function isModuleNotFoundError(error) {
@@ -296,7 +312,7 @@ function isModuleNotFoundError(error) {
296
312
  );
297
313
  }
298
314
 
299
- function shouldRunStartupAutoUpdate(rawArgs, normalizedArgs) {
315
+ function shouldRunStartupUpdateNotice(rawArgs, normalizedArgs) {
300
316
  if ((process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1") {
301
317
  return false;
302
318
  }
@@ -309,7 +325,7 @@ function shouldRunStartupAutoUpdate(rawArgs, normalizedArgs) {
309
325
  return true;
310
326
  }
311
327
 
312
- async function withStartupAutoUpdateBudget(promise, budgetMs) {
328
+ async function withStartupUpdateNoticeBudget(promise, budgetMs) {
313
329
  let timeout = null;
314
330
  try {
315
331
  return await Promise.race([
@@ -318,7 +334,7 @@ async function withStartupAutoUpdateBudget(promise, budgetMs) {
318
334
  timeout = setTimeout(
319
335
  () => {
320
336
  timeout?.unref?.();
321
- resolve(STARTUP_AUTO_UPDATE_TIMED_OUT);
337
+ resolve(STARTUP_UPDATE_NOTICE_TIMED_OUT);
322
338
  },
323
339
  budgetMs,
324
340
  );
@@ -329,51 +345,42 @@ async function withStartupAutoUpdateBudget(promise, budgetMs) {
329
345
  }
330
346
  }
331
347
 
332
- function logStartupAutoUpdateDebug(message) {
333
- if (isStartupAutoUpdateDebugEnabled()) {
348
+ function logStartupUpdateNoticeDebug(message) {
349
+ if (isStartupUpdateNoticeDebugEnabled()) {
334
350
  console.error(`codex-multi-auth: ${message}`);
335
351
  }
336
352
  }
337
353
 
338
- async function autoUpdatePackageIfEnabled(rawArgs, normalizedArgs) {
339
- if (!shouldRunStartupAutoUpdate(rawArgs, normalizedArgs)) return;
340
- const budgetMs = readStartupAutoUpdateBudgetMs();
341
- const { fetchTimeoutMs, updateTimeoutMs } =
342
- resolveStartupAutoUpdateTimeouts(budgetMs);
354
+ async function showUpdateNoticeIfAvailable(rawArgs, normalizedArgs) {
355
+ if (!shouldRunStartupUpdateNotice(rawArgs, normalizedArgs)) return;
356
+ const budgetMs = readStartupUpdateNoticeBudgetMs();
357
+ const fetchTimeoutMs = resolveStartupUpdateNoticeTimeoutMs(budgetMs);
343
358
  try {
344
- const mod = await import("../dist/lib/auto-update-checker.js");
345
- if (typeof mod.autoUpdateIfAvailable !== "function") {
359
+ const mod = await import("../dist/lib/update-notice.js");
360
+ if (typeof mod.checkForUpdates !== "function") {
346
361
  return;
347
362
  }
348
- const updatePromise = mod.autoUpdateIfAvailable({
349
- fetchTimeoutMs,
350
- timeoutMs: updateTimeoutMs,
351
- onUpdateStart: (update) => {
352
- if (!update?.latestVersion) return;
353
- if (!shouldLogStartupAutoUpdateProgress()) return;
354
- console.error(
355
- `codex-multi-auth: auto-update found ${update.latestVersion}; starting npm update -g codex-multi-auth. Startup will continue if it exceeds ${budgetMs}ms.`,
356
- );
357
- },
358
- });
359
- const result = await withStartupAutoUpdateBudget(updatePromise, budgetMs);
360
- if (result === STARTUP_AUTO_UPDATE_TIMED_OUT) {
361
- logStartupAutoUpdateDebug(
362
- `auto-update skipped: startup budget exceeded after ${budgetMs}ms`,
363
+ const checkPromise = mod.checkForUpdates(false, fetchTimeoutMs);
364
+ const result = await withStartupUpdateNoticeBudget(checkPromise, budgetMs);
365
+ if (result === STARTUP_UPDATE_NOTICE_TIMED_OUT) {
366
+ logStartupUpdateNoticeDebug(
367
+ `update notice skipped: startup budget exceeded after ${budgetMs}ms`,
363
368
  );
364
369
  return;
365
370
  }
366
- if (result?.updated && result.latestVersion) {
367
- if (shouldLogStartupAutoUpdateProgress()) {
371
+ if (result?.hasUpdate && result.latestVersion && shouldLogStartupUpdateNotice()) {
372
+ if (typeof mod.formatManualUpdateNotice === "function") {
373
+ console.error(mod.formatManualUpdateNotice(result));
374
+ } else {
368
375
  console.error(
369
- `codex-multi-auth: auto-updated to ${result.latestVersion}. New sessions will use the latest package.`,
376
+ `codex-multi-auth update available: v${result.latestVersion}; current: v${result.currentVersion}; run: ${result.updateCommand}`,
370
377
  );
371
378
  }
372
379
  }
373
380
  } catch (error) {
374
381
  if (isModuleNotFoundError(error)) return;
375
- logStartupAutoUpdateDebug(
376
- `auto-update skipped: ${error instanceof Error ? error.message : String(error)}`,
382
+ logStartupUpdateNoticeDebug(
383
+ `update notice skipped: ${error instanceof Error ? error.message : String(error)}`,
377
384
  );
378
385
  }
379
386
  }
@@ -1878,6 +1885,41 @@ function mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) {
1878
1885
  return "copied";
1879
1886
  }
1880
1887
 
1888
+ function linkDirectoryIntoShadowHome(sourcePath, destinationPath) {
1889
+ try {
1890
+ if ((process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_DIR_COPY ?? "").trim() === "1") {
1891
+ throw new Error("simulated directory link failure");
1892
+ }
1893
+ symlinkSync(
1894
+ sourcePath,
1895
+ destinationPath,
1896
+ process.platform === "win32" ? "junction" : "dir",
1897
+ );
1898
+ return true;
1899
+ } catch {
1900
+ return false;
1901
+ }
1902
+ }
1903
+
1904
+ function warnSkippedLinkOnlyShadowHomeDirectory(name) {
1905
+ if (warnedShadowHomeLinkOnlyDirectoryFailures.has(name)) {
1906
+ return;
1907
+ }
1908
+ warnedShadowHomeLinkOnlyDirectoryFailures.add(name);
1909
+ console.error(
1910
+ `codex-multi-auth: skipped optional shadow-home directory ${name} because linking failed; refusing to copy generated runtime data.`,
1911
+ );
1912
+ }
1913
+
1914
+ function shouldCopyRuntimeGeneratedShadowHomeDirectoryFallback() {
1915
+ const normalized = (
1916
+ process.env.CODEX_MULTI_AUTH_RUNTIME_SHADOW_COPY_GENERATED_DIRS ?? ""
1917
+ )
1918
+ .trim()
1919
+ .toLowerCase();
1920
+ return normalized === "1" || normalized === "true" || normalized === "yes";
1921
+ }
1922
+
1881
1923
  function linkFileIntoShadowHome(sourcePath, destinationPath) {
1882
1924
  try {
1883
1925
  symlinkSync(sourcePath, destinationPath, "file");
@@ -1902,6 +1944,148 @@ function mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile) {
1902
1944
  tightenFile(destinationPath);
1903
1945
  }
1904
1946
 
1947
+ function isSqliteMainFile(name) {
1948
+ return /\.sqlite$/i.test(name);
1949
+ }
1950
+
1951
+ function isSqliteSidecarFile(name) {
1952
+ return /\.sqlite-(?:shm|wal)$/i.test(name);
1953
+ }
1954
+
1955
+ function normalizeRuntimeShadowHomeEntryName(name) {
1956
+ return process.platform === "win32" || process.platform === "darwin"
1957
+ ? name.toLowerCase()
1958
+ : name;
1959
+ }
1960
+
1961
+ function isCodexRuntimeLocalSqliteFile(name) {
1962
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1963
+ return /^(?:state|logs)_\d+\.sqlite(?:-(?:shm|wal))?$/.test(normalizedName);
1964
+ }
1965
+
1966
+ function isCodexRuntimeTransientStateFile(name) {
1967
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1968
+ return (
1969
+ /^(?:auth|accounts)\.json\.\d+\.[a-z0-9]+\.tmp$/.test(
1970
+ normalizedName,
1971
+ ) ||
1972
+ /^\.codex-global-state\.json\.tmp-[a-z0-9-]+$/.test(normalizedName)
1973
+ );
1974
+ }
1975
+
1976
+ function isRuntimeRotationShadowHomeOmittedEntry(name) {
1977
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1978
+ return (
1979
+ RUNTIME_ROTATION_SHADOW_HOME_OMIT_ROOT_DIRS.has(normalizedName) ||
1980
+ isCodexRuntimeLocalSqliteFile(name) ||
1981
+ isCodexRuntimeTransientStateFile(name)
1982
+ );
1983
+ }
1984
+
1985
+ function isRuntimeRotationShadowHomeLinkOnlyDirectory(name) {
1986
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1987
+ return RUNTIME_ROTATION_SHADOW_HOME_LINK_ONLY_ROOT_DIRS.has(normalizedName);
1988
+ }
1989
+
1990
+ function shouldMaterializeFileIntoShadowHome(name) {
1991
+ return isSqliteMainFile(name) || isSqliteSidecarFile(name);
1992
+ }
1993
+
1994
+ function warnSkippedSqliteShadowHomeMaterialization() {
1995
+ if (!warnedShadowHomeSqliteLinkFailure) {
1996
+ warnedShadowHomeSqliteLinkFailure = true;
1997
+ console.error(
1998
+ "codex-multi-auth: skipped SQLite shadow-home materialization because linking failed; refusing to copy active SQLite state.",
1999
+ );
2000
+ }
2001
+ }
2002
+
2003
+ function warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationPath) {
2004
+ if (!warnedShadowHomeSqliteSidecarPlaceholderFailures.has(destinationPath)) {
2005
+ warnedShadowHomeSqliteSidecarPlaceholderFailures.add(destinationPath);
2006
+ console.error(
2007
+ `codex-multi-auth: skipped SQLite shadow-home sidecar placeholder for ${destinationPath} because linking failed: ${error instanceof Error ? error.message : String(error)}`,
2008
+ );
2009
+ }
2010
+ }
2011
+
2012
+ function materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidecarPath) {
2013
+ try {
2014
+ if (
2015
+ (process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SIDECAR_PLACEHOLDER_FAILURE ??
2016
+ ""
2017
+ ).trim() === "1"
2018
+ ) {
2019
+ const error = new Error("simulated SQLite sidecar placeholder failure");
2020
+ error.code = "EPERM";
2021
+ throw error;
2022
+ }
2023
+ symlinkSync(sourceSidecarPath, destinationSidecarPath, "file");
2024
+ return true;
2025
+ } catch (error) {
2026
+ if (error?.code === "EEXIST") {
2027
+ return true;
2028
+ }
2029
+ warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationSidecarPath);
2030
+ return false;
2031
+ }
2032
+ }
2033
+
2034
+ function materializeFileIntoShadowHome(sourcePath, destinationPath) {
2035
+ try {
2036
+ if (
2037
+ (process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SQLITE_SIDECAR_LINK_FAILURE ??
2038
+ ""
2039
+ ).trim() === "1" &&
2040
+ isSqliteSidecarFile(basename(sourcePath))
2041
+ ) {
2042
+ throw new Error("simulated SQLite sidecar link failure");
2043
+ }
2044
+ if (linkFileIntoShadowHome(sourcePath, destinationPath)) {
2045
+ return true;
2046
+ }
2047
+ warnSkippedSqliteShadowHomeMaterialization();
2048
+ } catch {
2049
+ warnSkippedSqliteShadowHomeMaterialization();
2050
+ }
2051
+ return false;
2052
+ }
2053
+
2054
+ function materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath) {
2055
+ for (const suffix of ["-wal", "-shm"]) {
2056
+ const sourceSidecarPath = `${sourcePath}${suffix}`;
2057
+ const destinationSidecarPath = `${destinationPath}${suffix}`;
2058
+ if (existsSync(destinationSidecarPath)) {
2059
+ continue;
2060
+ }
2061
+ if (existsSync(sourceSidecarPath)) {
2062
+ if (!materializeFileIntoShadowHome(sourceSidecarPath, destinationSidecarPath)) {
2063
+ if (!materializeSqliteSidecarPlaceholder(
2064
+ sourceSidecarPath,
2065
+ destinationSidecarPath,
2066
+ )) {
2067
+ return false;
2068
+ }
2069
+ }
2070
+ continue;
2071
+ }
2072
+ if (!materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidecarPath)) {
2073
+ return false;
2074
+ }
2075
+ }
2076
+ return true;
2077
+ }
2078
+
2079
+ function removeSqliteShadowHomeMaterialization(destinationPath) {
2080
+ for (const path of [destinationPath, `${destinationPath}-wal`, `${destinationPath}-shm`]) {
2081
+ try {
2082
+ rmSync(path, { force: true });
2083
+ } catch {
2084
+ // Best-effort cleanup; keep removing the remaining SQLite siblings.
2085
+ }
2086
+ }
2087
+ }
2088
+
1905
2089
  function collectShadowHomeSyncFileNames(shadowCodexHome, syncFileNames) {
1906
2090
  try {
1907
2091
  for (const entry of readdirSync(shadowCodexHome, { withFileTypes: true })) {
@@ -2011,9 +2195,14 @@ function syncAdditionalShadowHomeFiles(
2011
2195
  originalFileStates,
2012
2196
  tightenFile,
2013
2197
  skipSyncBackNames = new Set(),
2198
+ skipSyncBackPredicate = () => false,
2014
2199
  ) {
2015
2200
  for (const name of names) {
2016
- if (SHADOW_HOME_STATE_FILE_SET.has(name) || skipSyncBackNames.has(name)) {
2201
+ if (
2202
+ SHADOW_HOME_STATE_FILE_SET.has(name) ||
2203
+ skipSyncBackNames.has(name) ||
2204
+ skipSyncBackPredicate(name)
2205
+ ) {
2017
2206
  continue;
2018
2207
  }
2019
2208
  const shadowPath = join(shadowCodexHome, name);
@@ -2049,6 +2238,10 @@ function createShadowHomeMirror(
2049
2238
  ) {
2050
2239
  const syncFileNames = new Set(SHADOW_HOME_STATE_FILES);
2051
2240
  const skipSyncBackNames = new Set(options.skipSyncBackNames ?? []);
2241
+ const skipMirrorPredicate = options.skipMirrorPredicate ?? (() => false);
2242
+ const skipSyncBackPredicate = options.skipSyncBackPredicate ?? (() => false);
2243
+ const linkOnlyDirectoryPredicate =
2244
+ options.linkOnlyDirectoryPredicate ?? (() => false);
2052
2245
  const originalFileStates = new Map();
2053
2246
  const copiedDirectoryNames = new Set();
2054
2247
  const rememberSyncFile = (name) => {
@@ -2070,11 +2263,13 @@ function createShadowHomeMirror(
2070
2263
  if (
2071
2264
  name === SHADOW_HOME_CONFIG_FILE ||
2072
2265
  name === SHADOW_HOME_SYNC_STATE_FILE ||
2073
- name === SHADOW_HOME_SYNC_LOCK_DIR
2266
+ name === SHADOW_HOME_SYNC_LOCK_DIR ||
2267
+ skipMirrorPredicate(name)
2074
2268
  ) {
2075
2269
  continue;
2076
2270
  }
2077
2271
  const isKnownStateFile = SHADOW_HOME_STATE_FILE_SET.has(name);
2272
+ const shouldMaterializeFile = shouldMaterializeFileIntoShadowHome(name);
2078
2273
  const sourcePath = join(originalCodexHome, name);
2079
2274
  const destinationPath = join(shadowCodexHome, name);
2080
2275
  if (existsSync(destinationPath)) {
@@ -2093,6 +2288,15 @@ function createShadowHomeMirror(
2093
2288
  throw new Error(`Expected ${name} to be a file`);
2094
2289
  }
2095
2290
  if (directoryLike) {
2291
+ if (
2292
+ linkOnlyDirectoryPredicate(name) &&
2293
+ !shouldCopyRuntimeGeneratedShadowHomeDirectoryFallback()
2294
+ ) {
2295
+ if (!linkDirectoryIntoShadowHome(sourcePath, destinationPath)) {
2296
+ warnSkippedLinkOnlyShadowHomeDirectory(name);
2297
+ }
2298
+ continue;
2299
+ }
2096
2300
  if (mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) === "copied") {
2097
2301
  copiedDirectoryNames.add(name);
2098
2302
  }
@@ -2103,6 +2307,15 @@ function createShadowHomeMirror(
2103
2307
  if (isKnownStateFile) {
2104
2308
  copyFileSync(sourcePath, destinationPath);
2105
2309
  tightenFile(destinationPath);
2310
+ } else if (shouldMaterializeFile) {
2311
+ if (isSqliteSidecarFile(name)) {
2312
+ continue;
2313
+ }
2314
+ if (materializeFileIntoShadowHome(sourcePath, destinationPath) && isSqliteMainFile(name)) {
2315
+ if (!materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath)) {
2316
+ removeSqliteShadowHomeMaterialization(destinationPath);
2317
+ }
2318
+ }
2106
2319
  } else {
2107
2320
  mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile);
2108
2321
  }
@@ -2141,6 +2354,7 @@ function createShadowHomeMirror(
2141
2354
  originalFileStates,
2142
2355
  tightenFile,
2143
2356
  skipSyncBackNames,
2357
+ skipSyncBackPredicate,
2144
2358
  );
2145
2359
  } catch {
2146
2360
  // Best-effort only; runtime auth refreshes should not fail cleanup.
@@ -2197,6 +2411,10 @@ function resolveOriginalMultiAuthDir(env) {
2197
2411
  return undefined;
2198
2412
  }
2199
2413
 
2414
+ function resolveRuntimeRotationOriginalMultiAuthDir(originalCodexHome, env) {
2415
+ return resolveOriginalMultiAuthDir(env) ?? join(originalCodexHome, "multi-auth");
2416
+ }
2417
+
2200
2418
  function parseRuntimeRotationProxyEnv(value) {
2201
2419
  if (value === undefined) return undefined;
2202
2420
  const normalized = value.trim().toLowerCase();
@@ -2266,6 +2484,16 @@ function resolveRuntimeRotationProxyOriginalCodexHome(baseEnv) {
2266
2484
  return override || resolveCodexHomeDir(baseEnv);
2267
2485
  }
2268
2486
 
2487
+ function createRuntimeRotationShadowHome(originalCodexHome) {
2488
+ const shadowRoot = join(
2489
+ originalCodexHome,
2490
+ "multi-auth",
2491
+ "runtime-shadow-homes",
2492
+ );
2493
+ mkdirSync(shadowRoot, { recursive: true });
2494
+ return mkdtempSync(join(shadowRoot, "codex-multi-auth-runtime-home-"));
2495
+ }
2496
+
2269
2497
  function createRuntimeRotationProxyCodexHome(
2270
2498
  baseEnv,
2271
2499
  proxyBaseUrl,
@@ -2273,7 +2501,7 @@ function createRuntimeRotationProxyCodexHome(
2273
2501
  configTomlModule,
2274
2502
  ) {
2275
2503
  const originalCodexHome = resolveRuntimeRotationProxyOriginalCodexHome(baseEnv);
2276
- const shadowCodexHome = mkdtempSync(join(tmpdir(), "codex-multi-auth-runtime-home-"));
2504
+ const shadowCodexHome = createRuntimeRotationShadowHome(originalCodexHome);
2277
2505
  let syncShadowHomeStateBack = () => {};
2278
2506
  const cleanup = () => {
2279
2507
  try {
@@ -2296,7 +2524,10 @@ function createRuntimeRotationProxyCodexHome(
2296
2524
  shadowCodexHome,
2297
2525
  tightenShadowHomePermissions,
2298
2526
  {
2527
+ skipMirrorPredicate: isRuntimeRotationShadowHomeOmittedEntry,
2299
2528
  skipSyncBackNames: RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES,
2529
+ skipSyncBackPredicate: isRuntimeRotationShadowHomeOmittedEntry,
2530
+ linkOnlyDirectoryPredicate: isRuntimeRotationShadowHomeLinkOnlyDirectory,
2300
2531
  },
2301
2532
  );
2302
2533
  omitRuntimeRotationShadowHomeStateFiles(shadowCodexHome);
@@ -2321,11 +2552,11 @@ function createRuntimeRotationProxyCodexHome(
2321
2552
  ...baseEnv,
2322
2553
  CODEX_HOME: shadowCodexHome,
2323
2554
  OPENAI_API_KEY: clientApiKey,
2555
+ CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
2556
+ originalCodexHome,
2557
+ baseEnv,
2558
+ ),
2324
2559
  };
2325
- const originalMultiAuthDir = resolveOriginalMultiAuthDir(baseEnv);
2326
- if (originalMultiAuthDir) {
2327
- forwardedEnv.CODEX_MULTI_AUTH_DIR = originalMultiAuthDir;
2328
- }
2329
2560
 
2330
2561
  return {
2331
2562
  env: forwardedEnv,
@@ -2780,6 +3011,10 @@ function startRuntimeRotationAppHelper(baseContext) {
2780
3011
  {
2781
3012
  env: {
2782
3013
  ...baseContext.env,
3014
+ CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
3015
+ realCodexHome,
3016
+ baseContext.env,
3017
+ ),
2783
3018
  [APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
2784
3019
  [APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
2785
3020
  },
@@ -2841,7 +3076,11 @@ function startRuntimeRotationAppHelper(baseContext) {
2841
3076
  });
2842
3077
  }
2843
3078
 
2844
- async function createRuntimeRotationAppHelperContext(baseContext, configTomlModule) {
3079
+ async function createRuntimeRotationAppHelperContext(
3080
+ baseContext,
3081
+ configTomlModule,
3082
+ options = {},
3083
+ ) {
2845
3084
  const startedAt = Date.now();
2846
3085
  const { helper, message } = await startRuntimeRotationAppHelper(baseContext);
2847
3086
  const helperEnv = message.env ?? {};
@@ -2849,7 +3088,7 @@ async function createRuntimeRotationAppHelperContext(baseContext, configTomlModu
2849
3088
 
2850
3089
  const cleanup = async ({ exitCode } = {}) => {
2851
3090
  const livedMs = Date.now() - startedAt;
2852
- if (exitCode === 0 && livedMs < detachGraceMs) {
3091
+ if (exitCode === 0 && (options.detachOnExit === true || livedMs < detachGraceMs)) {
2853
3092
  helper.stdout?.destroy();
2854
3093
  helper.stderr?.destroy();
2855
3094
  helper.unref();
@@ -2898,6 +3137,11 @@ async function createRuntimeRotationProxyContextIfEnabled(
2898
3137
  if (isCodexAppCommand(rawArgs)) {
2899
3138
  return createRuntimeRotationAppHelperContext(baseContext, configTomlModule);
2900
3139
  }
3140
+ if (isCodexInteractiveTuiCommand(rawArgs)) {
3141
+ return createRuntimeRotationAppHelperContext(baseContext, configTomlModule, {
3142
+ detachOnExit: true,
3143
+ });
3144
+ }
2901
3145
 
2902
3146
  const proxyModule = await loadRuntimeRotationProxyModule();
2903
3147
  if (!proxyModule) {
@@ -3092,6 +3336,10 @@ function isCodexAppServerCommand(rawArgs) {
3092
3336
  return findForwardedCommand(rawArgs)?.command === "app-server";
3093
3337
  }
3094
3338
 
3339
+ function isCodexInteractiveTuiCommand(rawArgs) {
3340
+ return findForwardedCommand(rawArgs) === null;
3341
+ }
3342
+
3095
3343
  function shouldUseRuntimeRoutingForForwardedArgs(rawArgs) {
3096
3344
  if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
3097
3345
  return true;
@@ -3495,6 +3743,8 @@ function repairCodexSessionIndex(codexHome) {
3495
3743
 
3496
3744
  const additions = [];
3497
3745
  for (const rolloutPath of collectRolloutFiles(sessionsDir)) {
3746
+ const idFromName = extractRolloutIdFromFilename(basename(rolloutPath));
3747
+ if (idFromName && seen.has(idFromName)) continue;
3498
3748
  const entry = parseRolloutIndexEntry(rolloutPath);
3499
3749
  if (!entry || seen.has(entry.id)) continue;
3500
3750
  seen.add(entry.id);
@@ -3825,7 +4075,7 @@ async function main() {
3825
4075
  }
3826
4076
 
3827
4077
  const normalizedArgs = normalizeAuthAlias(rawArgs);
3828
- await autoUpdatePackageIfEnabled(rawArgs, normalizedArgs);
4078
+ await showUpdateNoticeIfAvailable(rawArgs, normalizedArgs);
3829
4079
  ensureWindowsShellShimGuards();
3830
4080
 
3831
4081
  const bypass = (process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1";