codexuse-cli 3.9.2 → 3.9.7

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.
@@ -8,9 +8,9 @@ import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  import * as NFS from "node:fs";
9
9
  import fs, { accessSync, constants, existsSync, promises, readFileSync, realpathSync, statSync } from "node:fs";
10
10
  import * as OS from "node:os";
11
- import os, { homedir } from "node:os";
11
+ import nodeOs, { homedir } from "node:os";
12
12
  import * as Path from "node:path";
13
- import path, { extname, join } from "node:path";
13
+ import nodePath, { extname, join } from "node:path";
14
14
  import * as NodeUrl from "node:url";
15
15
  import { fileURLToPath } from "node:url";
16
16
  import * as readline$1 from "node:readline";
@@ -19,7 +19,7 @@ import * as Net from "node:net";
19
19
  import fs$1, { mkdir, readFile, stat, writeFile } from "node:fs/promises";
20
20
  import { AsyncLocalStorage } from "node:async_hooks";
21
21
  import { EventEmitter } from "node:events";
22
- import path$1, { join as join$1 } from "path";
22
+ import path, { join as join$1 } from "path";
23
23
  import util from "node:util";
24
24
  import http from "node:http";
25
25
  import { WebSocketServer } from "ws";
@@ -47814,7 +47814,8 @@ const UserInputQuestion = Struct({
47814
47814
  id: TrimmedNonEmptyStringSchema$1,
47815
47815
  header: TrimmedNonEmptyStringSchema$1,
47816
47816
  question: TrimmedNonEmptyStringSchema$1,
47817
- options: Array$1(UserInputQuestionOption)
47817
+ options: Array$1(UserInputQuestionOption),
47818
+ multiSelect: optional$2(Boolean$2)
47818
47819
  });
47819
47820
  const UserInputRequestedPayload = Struct({ questions: Array$1(UserInputQuestion) });
47820
47821
  const UserInputResolvedPayload = Struct({ answers: UnknownRecordSchema });
@@ -48763,6 +48764,13 @@ const WS_METHODS = {
48763
48764
  autoRollGetSettings: "autoRoll.getSettings",
48764
48765
  autoRollSaveSettings: "autoRoll.saveSettings",
48765
48766
  officialCodexRestart: "officialCodex.restart",
48767
+ officialCodexInstances: "officialCodex.instances",
48768
+ officialCodexLaunchProfile: "officialCodex.launchProfile",
48769
+ officialCodexFocusProfile: "officialCodex.focusProfile",
48770
+ officialCodexStopProfile: "officialCodex.stopProfile",
48771
+ officialCodexRestartProfile: "officialCodex.restartProfile",
48772
+ officialCodexLaunchProfiles: "officialCodex.launchProfiles",
48773
+ officialCodexStopProfiles: "officialCodex.stopProfiles",
48766
48774
  cliInfo: "cli.info",
48767
48775
  cliStatus: "cli.status",
48768
48776
  cliCheckFreshness: "cli.checkFreshness",
@@ -48907,7 +48915,11 @@ const WebSocketRequestBody = Union([
48907
48915
  accountKey: TrimmedNonEmptyString,
48908
48916
  groupName: Union([TrimmedNonEmptyString, Null])
48909
48917
  })),
48910
- tagRequestBody(WS_METHODS.profilesDelete, Struct({ name: TrimmedNonEmptyString })),
48918
+ tagRequestBody(WS_METHODS.profilesDelete, Struct({
48919
+ name: TrimmedNonEmptyString,
48920
+ replacementProfileName: optional$2(Union([TrimmedNonEmptyString, Null])),
48921
+ removeActiveAuth: optional$2(Boolean$2)
48922
+ })),
48911
48923
  tagRequestBody(WS_METHODS.profilesExport, Struct({ name: TrimmedNonEmptyString })),
48912
48924
  tagRequestBody(WS_METHODS.profilesCancelAuth, Struct({ name: TrimmedNonEmptyString })),
48913
48925
  tagRequestBody(WS_METHODS.rateLimitsRefreshNow, Struct({})),
@@ -48919,6 +48931,13 @@ const WebSocketRequestBody = Union([
48919
48931
  tagRequestBody(WS_METHODS.autoRollGetSettings, Struct({})),
48920
48932
  tagRequestBody(WS_METHODS.autoRollSaveSettings, Struct({ settings: Unknown })),
48921
48933
  tagRequestBody(WS_METHODS.officialCodexRestart, Struct({})),
48934
+ tagRequestBody(WS_METHODS.officialCodexInstances, Struct({})),
48935
+ tagRequestBody(WS_METHODS.officialCodexLaunchProfile, Struct({ name: TrimmedNonEmptyString })),
48936
+ tagRequestBody(WS_METHODS.officialCodexFocusProfile, Struct({ name: TrimmedNonEmptyString })),
48937
+ tagRequestBody(WS_METHODS.officialCodexStopProfile, Struct({ name: TrimmedNonEmptyString })),
48938
+ tagRequestBody(WS_METHODS.officialCodexRestartProfile, Struct({ name: TrimmedNonEmptyString })),
48939
+ tagRequestBody(WS_METHODS.officialCodexLaunchProfiles, Struct({ names: Array$1(TrimmedNonEmptyString) })),
48940
+ tagRequestBody(WS_METHODS.officialCodexStopProfiles, Struct({ names: Array$1(TrimmedNonEmptyString) })),
48922
48941
  tagRequestBody(WS_METHODS.cliInfo, Struct({})),
48923
48942
  tagRequestBody(WS_METHODS.cliStatus, Struct({})),
48924
48943
  tagRequestBody(WS_METHODS.cliCheckFreshness, Struct({})),
@@ -49196,7 +49215,7 @@ const launchDetached = (launch) => gen(function* () {
49196
49215
  });
49197
49216
  const make$9 = gen(function* () {
49198
49217
  const open = yield* tryPromise({
49199
- try: () => import("./open-DX6_a9Ta.mjs"),
49218
+ try: () => import("./open-BWXrZXJl.mjs"),
49200
49219
  catch: (cause) => new OpenError({
49201
49220
  message: "failed to load browser opener",
49202
49221
  cause
@@ -54296,8 +54315,8 @@ const IGNORED_DIRECTORY_NAMES = new Set([
54296
54315
  ".cache"
54297
54316
  ]);
54298
54317
  function expandHomePath(input) {
54299
- if (input === "~") return os.homedir();
54300
- if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(os.homedir(), input.slice(2));
54318
+ if (input === "~") return nodeOs.homedir();
54319
+ if (input.startsWith("~/") || input.startsWith("~\\")) return nodePath.join(nodeOs.homedir(), input.slice(2));
54301
54320
  return input;
54302
54321
  }
54303
54322
  function isWindowsAbsolutePath(input) {
@@ -54306,13 +54325,13 @@ function isWindowsAbsolutePath(input) {
54306
54325
  function resolveBrowseTarget(input) {
54307
54326
  if (process.platform !== "win32" && isWindowsAbsolutePath(input.partialPath)) throw new Error("Windows-style paths are only supported on Windows.");
54308
54327
  const expanded = expandHomePath(input.partialPath);
54309
- if (path.isAbsolute(expanded) || input.partialPath.startsWith("~")) return path.resolve(expanded);
54310
- return input.cwd ? path.resolve(expandHomePath(input.cwd), expanded) : path.resolve(expanded);
54328
+ if (nodePath.isAbsolute(expanded) || input.partialPath.startsWith("~")) return nodePath.resolve(expanded);
54329
+ return input.cwd ? nodePath.resolve(expandHomePath(input.cwd), expanded) : nodePath.resolve(expanded);
54311
54330
  }
54312
54331
  const workspaceIndexCache = /* @__PURE__ */ new Map();
54313
54332
  const inFlightWorkspaceIndexBuilds = /* @__PURE__ */ new Map();
54314
54333
  function toPosixPath(input) {
54315
- return input.split(path.sep).join("/");
54334
+ return input.split(nodePath.sep).join("/");
54316
54335
  }
54317
54336
  function parentPathOf(input) {
54318
54337
  const separatorIndex = input.lastIndexOf("/");
@@ -54527,7 +54546,7 @@ async function buildWorkspaceIndex(cwd) {
54527
54546
  const currentDirectories = pendingDirectories;
54528
54547
  pendingDirectories = [];
54529
54548
  const candidateEntriesByDirectory = (await mapWithConcurrency(currentDirectories, WORKSPACE_SCAN_READDIR_CONCURRENCY, async (relativeDir) => {
54530
- const absoluteDir = relativeDir ? path.join(cwd, relativeDir) : cwd;
54549
+ const absoluteDir = relativeDir ? nodePath.join(cwd, relativeDir) : cwd;
54531
54550
  try {
54532
54551
  return {
54533
54552
  relativeDir,
@@ -54549,7 +54568,7 @@ async function buildWorkspaceIndex(cwd) {
54549
54568
  if (!dirent.name || dirent.name === "." || dirent.name === "..") continue;
54550
54569
  if (dirent.isDirectory() && IGNORED_DIRECTORY_NAMES.has(dirent.name)) continue;
54551
54570
  if (!dirent.isDirectory() && !dirent.isFile()) continue;
54552
- const relativePath = toPosixPath(relativeDir ? path.join(relativeDir, dirent.name) : dirent.name);
54571
+ const relativePath = toPosixPath(relativeDir ? nodePath.join(relativeDir, dirent.name) : dirent.name);
54553
54572
  if (isPathInIgnoredDirectory(relativePath)) continue;
54554
54573
  candidates.push({
54555
54574
  dirent,
@@ -54630,8 +54649,8 @@ async function searchWorkspaceEntries(input) {
54630
54649
  async function browseFilesystemEntries(input) {
54631
54650
  const resolvedInputPath = resolveBrowseTarget(input);
54632
54651
  const endsWithSeparator = /[\\/]$/.test(input.partialPath) || input.partialPath === "~";
54633
- const parentPath = endsWithSeparator ? resolvedInputPath : path.dirname(resolvedInputPath);
54634
- const prefix = endsWithSeparator ? "" : path.basename(resolvedInputPath);
54652
+ const parentPath = endsWithSeparator ? resolvedInputPath : nodePath.dirname(resolvedInputPath);
54653
+ const prefix = endsWithSeparator ? "" : nodePath.basename(resolvedInputPath);
54635
54654
  const dirents = await fs$1.readdir(parentPath, { withFileTypes: true });
54636
54655
  const showHidden = endsWithSeparator || prefix.startsWith(".");
54637
54656
  const lowerPrefix = prefix.toLowerCase();
@@ -54639,7 +54658,7 @@ async function browseFilesystemEntries(input) {
54639
54658
  parentPath,
54640
54659
  entries: dirents.filter((dirent) => dirent.isDirectory() && dirent.name.toLowerCase().startsWith(lowerPrefix) && (showHidden || !dirent.name.startsWith("."))).map((dirent) => ({
54641
54660
  name: dirent.name,
54642
- fullPath: path.join(parentPath, dirent.name)
54661
+ fullPath: nodePath.join(parentPath, dirent.name)
54643
54662
  })).sort((left, right) => left.name.localeCompare(right.name))
54644
54663
  };
54645
54664
  }
@@ -55959,16 +55978,16 @@ const ProjectionPendingApprovalRepositoryLive = effect(ProjectionPendingApproval
55959
55978
  //#region src/attachmentPaths.ts
55960
55979
  const ATTACHMENTS_ROUTE_PREFIX = "/attachments";
55961
55980
  function normalizeAttachmentRelativePath(rawRelativePath) {
55962
- const normalized = path.normalize(rawRelativePath).replace(/^[/\\]+/, "");
55981
+ const normalized = nodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");
55963
55982
  if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) return null;
55964
55983
  return normalized.replace(/\\/g, "/");
55965
55984
  }
55966
55985
  function resolveAttachmentRelativePath(input) {
55967
55986
  const normalizedRelativePath = normalizeAttachmentRelativePath(input.relativePath);
55968
55987
  if (!normalizedRelativePath) return null;
55969
- const attachmentsRoot = path.resolve(path.join(input.stateDir, "attachments"));
55970
- const filePath = path.resolve(path.join(attachmentsRoot, normalizedRelativePath));
55971
- if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) return null;
55988
+ const attachmentsRoot = nodePath.resolve(nodePath.join(input.stateDir, "attachments"));
55989
+ const filePath = nodePath.resolve(nodePath.join(attachmentsRoot, normalizedRelativePath));
55990
+ if (!filePath.startsWith(`${attachmentsRoot}${nodePath.sep}`)) return null;
55972
55991
  return filePath;
55973
55992
  }
55974
55993
  //#endregion
@@ -58202,6 +58221,75 @@ function normalizeCommitMessagePrompt(value) {
58202
58221
  return normalized.length > 0 ? normalized : null;
58203
58222
  }
58204
58223
  //#endregion
58224
+ //#region ../../packages/contracts/src/settings/auto-roll.ts
58225
+ const DEFAULT_AUTO_ROLL_ENABLED = false;
58226
+ const DEFAULT_AUTO_ROLL_REARM_REMAINING_THRESHOLD = 15;
58227
+ const DEFAULT_AUTO_ROLL_SWITCH_REMAINING_THRESHOLD = 5;
58228
+ const DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL = false;
58229
+ const DEFAULT_LAUNCH_OFFICIAL_CODEX_WHEN_CLOSED_ON_AUTO_ROLL = false;
58230
+ const DEFAULT_AUTO_ROLL_PRIORITY_ORDER = [];
58231
+ const DEFAULT_LOW_REMAINING_NOTIFICATION_ENABLED = false;
58232
+ const DEFAULT_LOW_REMAINING_NOTIFICATION_THRESHOLD = 1;
58233
+ const AUTO_ROLL_SWITCH_REMAINING_MIN = 0;
58234
+ const AUTO_ROLL_SWITCH_REMAINING_MAX = 50;
58235
+ const AUTO_ROLL_REARM_REMAINING_MAX = 100;
58236
+ const LOW_REMAINING_NOTIFICATION_MIN = 1;
58237
+ const LOW_REMAINING_NOTIFICATION_MAX = 50;
58238
+ function clampNumber(value, min, max) {
58239
+ return Math.min(max, Math.max(min, value));
58240
+ }
58241
+ function resolveFiniteNumber(value, fallback) {
58242
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
58243
+ }
58244
+ function legacyUsedThresholdToRemaining(value, fallbackUsed) {
58245
+ return 100 - clampNumber(resolveFiniteNumber(value, fallbackUsed), 0, 100);
58246
+ }
58247
+ function sanitizeAutoRollSwitchRemainingThreshold(value) {
58248
+ return clampNumber(resolveFiniteNumber(value, 5), 0, 50);
58249
+ }
58250
+ function sanitizeAutoRollRearmRemainingThreshold(value, switchRemainingThreshold) {
58251
+ const sanitizedSwitch = sanitizeAutoRollSwitchRemainingThreshold(switchRemainingThreshold);
58252
+ return clampNumber(resolveFiniteNumber(value, 15), sanitizedSwitch + 1, 100);
58253
+ }
58254
+ function sanitizeAutoRollThresholds(rearmRemainingThreshold, switchRemainingThreshold) {
58255
+ const sanitizedSwitch = sanitizeAutoRollSwitchRemainingThreshold(switchRemainingThreshold);
58256
+ return {
58257
+ rearmRemainingThreshold: sanitizeAutoRollRearmRemainingThreshold(rearmRemainingThreshold, sanitizedSwitch),
58258
+ switchRemainingThreshold: sanitizedSwitch
58259
+ };
58260
+ }
58261
+ function sanitizeAutoRollPriorityOrder(value) {
58262
+ if (!Array.isArray(value)) return [...DEFAULT_AUTO_ROLL_PRIORITY_ORDER];
58263
+ const seen = /* @__PURE__ */ new Set();
58264
+ const normalized = [];
58265
+ for (const item of value) {
58266
+ if (typeof item !== "string") continue;
58267
+ const trimmed = item.trim();
58268
+ if (!trimmed || seen.has(trimmed)) continue;
58269
+ seen.add(trimmed);
58270
+ normalized.push(trimmed);
58271
+ }
58272
+ return normalized;
58273
+ }
58274
+ function sanitizeLowRemainingNotificationThreshold(value) {
58275
+ return clampNumber(resolveFiniteNumber(value, 1), 1, 50);
58276
+ }
58277
+ function normalizeAutoRollSettings(raw) {
58278
+ const enabled = typeof raw?.enabled === "boolean" ? raw.enabled : false;
58279
+ const rawSwitchRemaining = typeof raw?.switchRemainingThreshold === "number" ? raw.switchRemainingThreshold : legacyUsedThresholdToRemaining(raw?.switchThreshold, 95);
58280
+ const { rearmRemainingThreshold: normalizedRearm, switchRemainingThreshold: normalizedSwitch } = sanitizeAutoRollThresholds(typeof raw?.rearmRemainingThreshold === "number" ? raw.rearmRemainingThreshold : legacyUsedThresholdToRemaining(raw?.warningThreshold, 85), rawSwitchRemaining);
58281
+ return {
58282
+ enabled,
58283
+ rearmRemainingThreshold: normalizedRearm,
58284
+ switchRemainingThreshold: normalizedSwitch,
58285
+ restartOfficialCodexOnAutoRoll: raw?.restartOfficialCodexOnAutoRoll === true ? true : false,
58286
+ launchOfficialCodexWhenClosedOnAutoRoll: raw?.launchOfficialCodexWhenClosedOnAutoRoll === true ? true : false,
58287
+ priorityOrder: sanitizeAutoRollPriorityOrder(raw?.priorityOrder),
58288
+ lowRemainingNotificationEnabled: raw?.lowRemainingNotificationEnabled === true ? true : false,
58289
+ lowRemainingNotificationThreshold: sanitizeLowRemainingNotificationThreshold(typeof raw?.lowRemainingNotificationThreshold === "number" ? raw.lowRemainingNotificationThreshold : NaN)
58290
+ };
58291
+ }
58292
+ //#endregion
58205
58293
  //#region ../../packages/runtime-app-state/src/storage/documents.ts
58206
58294
  const APP_STORAGE_TABLE = "app_storage_documents";
58207
58295
  const APP_STORAGE_DB_DIR = "t3-projects";
@@ -58285,7 +58373,7 @@ function ensureSchema(db, dbPath) {
58285
58373
  initializedDbPaths.add(dbPath);
58286
58374
  }
58287
58375
  async function openDatabase(dbPath) {
58288
- await promises.mkdir(path.dirname(dbPath), { recursive: true });
58376
+ await promises.mkdir(nodePath.dirname(dbPath), { recursive: true });
58289
58377
  if (process.versions.bun !== void 0) {
58290
58378
  const database = new (await (Function("return import('bun:sqlite')")())).Database(dbPath);
58291
58379
  return {
@@ -58318,7 +58406,7 @@ async function withDatabase(dbPath, task) {
58318
58406
  throw new Error("App storage database remained busy after retrying.");
58319
58407
  }
58320
58408
  function resolveAppStorageDbPath(userDataDir) {
58321
- return path.join(userDataDir, APP_STORAGE_DB_DIR, APP_STORAGE_DB_NAME);
58409
+ return nodePath.join(userDataDir, APP_STORAGE_DB_DIR, APP_STORAGE_DB_NAME);
58322
58410
  }
58323
58411
  async function readDocument(dbPath, namespace, normalize) {
58324
58412
  return withDatabase(dbPath, (db) => {
@@ -58388,21 +58476,21 @@ function clone(value) {
58388
58476
  return JSON.parse(JSON.stringify(value));
58389
58477
  }
58390
58478
  function resolveDefaultUserDataDir() {
58391
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
58479
+ const home = process.env.HOME || process.env.USERPROFILE || nodeOs.homedir();
58392
58480
  if (!home) throw new Error("Unable to resolve home directory for app state.");
58393
- if (process.platform === "darwin") return path.join(home, "Library", "Application Support", APP_NAME);
58481
+ if (process.platform === "darwin") return nodePath.join(home, "Library", "Application Support", APP_NAME);
58394
58482
  if (process.platform === "win32") {
58395
58483
  const appData = process.env.APPDATA;
58396
- if (appData) return path.join(appData, APP_NAME);
58397
- return path.join(home, "AppData", "Roaming", APP_NAME);
58484
+ if (appData) return nodePath.join(appData, APP_NAME);
58485
+ return nodePath.join(home, "AppData", "Roaming", APP_NAME);
58398
58486
  }
58399
- return path.join(home, ".config", APP_NAME);
58487
+ return nodePath.join(home, ".config", APP_NAME);
58400
58488
  }
58401
58489
  function getUserDataDir() {
58402
58490
  return configuredUserDataDir ?? resolveDefaultUserDataDir();
58403
58491
  }
58404
58492
  function resolveLegacyAppStatePath() {
58405
- return path.join(getUserDataDir(), LEGACY_APP_STATE_FILE);
58493
+ return nodePath.join(getUserDataDir(), LEGACY_APP_STATE_FILE);
58406
58494
  }
58407
58495
  function resolveStorageDbPath() {
58408
58496
  return resolveAppStorageDbPath(getUserDataDir());
@@ -58412,9 +58500,23 @@ function createDefaultAppState() {
58412
58500
  schemaVersion: 1,
58413
58501
  autoRoll: {
58414
58502
  enabled: false,
58415
- warningThreshold: 85,
58416
- switchThreshold: 95,
58417
- restartOfficialCodexOnAutoRoll: false
58503
+ rearmRemainingThreshold: 15,
58504
+ switchRemainingThreshold: 5,
58505
+ restartOfficialCodexOnAutoRoll: false,
58506
+ launchOfficialCodexWhenClosedOnAutoRoll: false,
58507
+ priorityOrder: [],
58508
+ lowRemainingNotificationEnabled: false,
58509
+ lowRemainingNotificationThreshold: 1
58510
+ },
58511
+ officialCodex: {
58512
+ lastProfileSwitchAt: null,
58513
+ lastProfileSwitchProfileKey: null,
58514
+ lastVerifiedLaunchAt: null,
58515
+ lastVerifiedLaunchProfileKey: null,
58516
+ lastObservedPid: null,
58517
+ lastRestartStatus: null,
58518
+ lastRestartReason: null,
58519
+ instancesByProfileName: {}
58418
58520
  },
58419
58521
  app: {
58420
58522
  lastAppVersion: null,
@@ -58507,17 +58609,47 @@ function asString$9(value) {
58507
58609
  const trimmed = value.trim();
58508
58610
  return trimmed.length > 0 ? trimmed : null;
58509
58611
  }
58612
+ function asNumberOrNull(value) {
58613
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
58614
+ }
58510
58615
  function normalizeAppState(raw) {
58511
58616
  const defaults = createDefaultAppState();
58512
58617
  if (!isRecord$7(raw)) return defaults;
58618
+ const rawAutoRoll = isRecord$7(raw.autoRoll) ? raw.autoRoll : void 0;
58513
58619
  const merged = clone(deepMerge(defaults, raw));
58514
58620
  merged.schemaVersion = 1;
58515
- if (typeof merged.autoRoll.enabled !== "boolean") merged.autoRoll.enabled = false;
58516
- if (!Number.isFinite(merged.autoRoll.warningThreshold)) merged.autoRoll.warningThreshold = 85;
58517
- if (!Number.isFinite(merged.autoRoll.switchThreshold)) merged.autoRoll.switchThreshold = 95;
58518
- if (merged.autoRoll.warningThreshold < 50 || merged.autoRoll.warningThreshold > 99) merged.autoRoll.warningThreshold = 85;
58519
- if (merged.autoRoll.switchThreshold <= merged.autoRoll.warningThreshold || merged.autoRoll.switchThreshold > 100) merged.autoRoll.switchThreshold = Math.min(100, Math.max(merged.autoRoll.warningThreshold + 1, 95));
58520
- if (typeof merged.autoRoll.restartOfficialCodexOnAutoRoll !== "boolean") merged.autoRoll.restartOfficialCodexOnAutoRoll = false;
58621
+ merged.autoRoll = normalizeAutoRollSettings(rawAutoRoll);
58622
+ if (!isRecord$7(merged.officialCodex)) merged.officialCodex = clone(defaults.officialCodex);
58623
+ merged.officialCodex.lastProfileSwitchAt = asNumberOrNull(merged.officialCodex.lastProfileSwitchAt);
58624
+ merged.officialCodex.lastProfileSwitchProfileKey = asString$9(merged.officialCodex.lastProfileSwitchProfileKey);
58625
+ merged.officialCodex.lastVerifiedLaunchAt = asNumberOrNull(merged.officialCodex.lastVerifiedLaunchAt);
58626
+ merged.officialCodex.lastVerifiedLaunchProfileKey = asString$9(merged.officialCodex.lastVerifiedLaunchProfileKey);
58627
+ merged.officialCodex.lastObservedPid = asNumberOrNull(merged.officialCodex.lastObservedPid);
58628
+ merged.officialCodex.lastRestartStatus = asString$9(merged.officialCodex.lastRestartStatus);
58629
+ merged.officialCodex.lastRestartReason = asString$9(merged.officialCodex.lastRestartReason);
58630
+ if (!isRecord$7(merged.officialCodex.instancesByProfileName)) merged.officialCodex.instancesByProfileName = {};
58631
+ else {
58632
+ const nextInstances = {};
58633
+ for (const [key, value] of Object.entries(merged.officialCodex.instancesByProfileName)) {
58634
+ if (!isRecord$7(value)) continue;
58635
+ const profileName = asString$9(value.profileName) ?? asString$9(key);
58636
+ if (!profileName) continue;
58637
+ nextInstances[profileName] = {
58638
+ profileName,
58639
+ profileKey: asString$9(value.profileKey),
58640
+ profileHome: asString$9(value.profileHome),
58641
+ appPath: asString$9(value.appPath),
58642
+ bundleId: asString$9(value.bundleId),
58643
+ pid: asNumberOrNull(value.pid),
58644
+ appServerPid: asNumberOrNull(value.appServerPid),
58645
+ launchedAt: asNumberOrNull(value.launchedAt),
58646
+ lastVerifiedAt: asNumberOrNull(value.lastVerifiedAt),
58647
+ lastStatus: asString$9(value.lastStatus),
58648
+ lastError: asString$9(value.lastError)
58649
+ };
58650
+ }
58651
+ merged.officialCodex.instancesByProfileName = nextInstances;
58652
+ }
58521
58653
  merged.app.lastAppVersion = asString$9(merged.app.lastAppVersion);
58522
58654
  merged.app.pendingUpdateVersion = asString$9(merged.app.pendingUpdateVersion);
58523
58655
  merged.app.lastProfileName = asString$9(merged.app.lastProfileName);
@@ -58749,13 +58881,13 @@ const IS_CASE_INSENSITIVE_PLATFORM$2 = process.platform === "darwin" || process.
58749
58881
  function stripTrailingSeparators$2(input) {
58750
58882
  const trimmed = input.trim();
58751
58883
  if (trimmed.length === 0) return trimmed;
58752
- const root = path.parse(trimmed).root;
58884
+ const root = nodePath.parse(trimmed).root;
58753
58885
  let next = trimmed;
58754
58886
  while (next.length > root.length && /[\\/]+$/.test(next)) next = next.slice(0, -1);
58755
58887
  return next;
58756
58888
  }
58757
58889
  function canonicalizeWorkspaceRoot$1(input) {
58758
- const resolved = path.resolve(input.trim());
58890
+ const resolved = nodePath.resolve(input.trim());
58759
58891
  return stripTrailingSeparators$2((() => {
58760
58892
  try {
58761
58893
  return realpathSync.native(resolved);
@@ -58769,10 +58901,10 @@ function workspaceRootKey$1(input) {
58769
58901
  return IS_CASE_INSENSITIVE_PLATFORM$2 ? canonical.toLowerCase() : canonical;
58770
58902
  }
58771
58903
  function resolveAgentChatWorkspaceRoot() {
58772
- return canonicalizeWorkspaceRoot$1(path.join(getUserDataDir(), AGENT_CHAT_WORKSPACE_DIRNAME));
58904
+ return canonicalizeWorkspaceRoot$1(nodePath.join(getUserDataDir(), AGENT_CHAT_WORKSPACE_DIRNAME));
58773
58905
  }
58774
58906
  function resolveAgentChatAgentsPath() {
58775
- return path.join(resolveAgentChatWorkspaceRoot(), AGENT_CHAT_AGENTS_FILENAME);
58907
+ return nodePath.join(resolveAgentChatWorkspaceRoot(), AGENT_CHAT_AGENTS_FILENAME);
58776
58908
  }
58777
58909
  function isAgentChatWorkspaceRoot(workspaceRoot) {
58778
58910
  return workspaceRootKey$1(workspaceRoot) === workspaceRootKey$1(resolveAgentChatWorkspaceRoot());
@@ -58786,13 +58918,13 @@ const IS_CASE_INSENSITIVE_PLATFORM$1 = process.platform === "darwin" || process.
58786
58918
  function stripTrailingSeparators$1(input) {
58787
58919
  const trimmed = input.trim();
58788
58920
  if (trimmed.length === 0) return trimmed;
58789
- const root = path.parse(trimmed).root;
58921
+ const root = nodePath.parse(trimmed).root;
58790
58922
  let next = trimmed;
58791
58923
  while (next.length > root.length && /[\\/]+$/.test(next)) next = next.slice(0, -1);
58792
58924
  return next;
58793
58925
  }
58794
58926
  function canonicalizeWorkspaceRoot(input) {
58795
- const resolved = path.resolve(input.trim());
58927
+ const resolved = nodePath.resolve(input.trim());
58796
58928
  return stripTrailingSeparators$1((() => {
58797
58929
  try {
58798
58930
  return realpathSync.native(resolved);
@@ -58806,54 +58938,15 @@ function workspaceRootKey(input) {
58806
58938
  return IS_CASE_INSENSITIVE_PLATFORM$1 ? canonical.toLowerCase() : canonical;
58807
58939
  }
58808
58940
  function resolveGeneralChatWorkspaceRoot() {
58809
- return canonicalizeWorkspaceRoot(path.join(getUserDataDir(), GENERAL_CHAT_WORKSPACE_DIRNAME));
58941
+ return canonicalizeWorkspaceRoot(nodePath.join(getUserDataDir(), GENERAL_CHAT_WORKSPACE_DIRNAME));
58810
58942
  }
58811
58943
  function resolveGeneralChatAgentsPath() {
58812
- return path.join(resolveGeneralChatWorkspaceRoot(), GENERAL_CHAT_AGENTS_FILENAME);
58944
+ return nodePath.join(resolveGeneralChatWorkspaceRoot(), GENERAL_CHAT_AGENTS_FILENAME);
58813
58945
  }
58814
58946
  function isGeneralChatWorkspaceRoot(workspaceRoot) {
58815
58947
  return workspaceRootKey(workspaceRoot) === workspaceRootKey(resolveGeneralChatWorkspaceRoot());
58816
58948
  }
58817
58949
  //#endregion
58818
- //#region ../../packages/contracts/src/settings/auto-roll.ts
58819
- const DEFAULT_AUTO_ROLL_ENABLED = false;
58820
- const DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
58821
- const DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
58822
- const DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL = false;
58823
- const AUTO_ROLL_WARNING_MIN = 50;
58824
- const AUTO_ROLL_WARNING_MAX = 99;
58825
- const AUTO_ROLL_SWITCH_MAX = 100;
58826
- function clampNumber(value, min, max) {
58827
- return Math.min(max, Math.max(min, value));
58828
- }
58829
- function resolveFiniteNumber(value, fallback) {
58830
- return Number.isFinite(value) ? value : fallback;
58831
- }
58832
- function sanitizeAutoRollWarningThreshold(value) {
58833
- return clampNumber(resolveFiniteNumber(value, 85), 50, AUTO_ROLL_WARNING_MAX);
58834
- }
58835
- function sanitizeAutoRollSwitchThreshold(value, warningThreshold) {
58836
- const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
58837
- return clampNumber(resolveFiniteNumber(value, 95), sanitizedWarning + 1, AUTO_ROLL_SWITCH_MAX);
58838
- }
58839
- function sanitizeAutoRollThresholds(warningThreshold, switchThreshold) {
58840
- const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
58841
- return {
58842
- warningThreshold: sanitizedWarning,
58843
- switchThreshold: sanitizeAutoRollSwitchThreshold(switchThreshold, sanitizedWarning)
58844
- };
58845
- }
58846
- function normalizeAutoRollSettings(raw) {
58847
- const enabled = typeof raw?.enabled === "boolean" ? raw.enabled : false;
58848
- const { warningThreshold: normalizedWarning, switchThreshold: normalizedSwitch } = sanitizeAutoRollThresholds(resolveFiniteNumber(typeof raw?.warningThreshold === "number" ? raw.warningThreshold : NaN, 85), resolveFiniteNumber(typeof raw?.switchThreshold === "number" ? raw.switchThreshold : NaN, 95));
58849
- return {
58850
- enabled,
58851
- warningThreshold: normalizedWarning,
58852
- switchThreshold: normalizedSwitch,
58853
- restartOfficialCodexOnAutoRoll: raw?.restartOfficialCodexOnAutoRoll === true ? true : false
58854
- };
58855
- }
58856
- //#endregion
58857
58950
  //#region ../../packages/runtime-codex/src/codex/settings.ts
58858
58951
  function asString$8(value) {
58859
58952
  if (typeof value !== "string") return null;
@@ -58891,12 +58984,6 @@ function parseStoredLicense(raw) {
58891
58984
  };
58892
58985
  return Boolean(license.licenseKey || license.purchaseEmail || license.lastVerifiedAt || license.nextCheckAt || license.lastVerificationError || license.status) ? license : null;
58893
58986
  }
58894
- async function getLastProfileName() {
58895
- return asString$8((await getAppState()).app.lastProfileName);
58896
- }
58897
- async function persistLastProfileName(profileName) {
58898
- await patchAppState({ app: { lastProfileName: asString$8(profileName) } });
58899
- }
58900
58987
  async function getStoredLicense() {
58901
58988
  return parseStoredLicense((await getAppState()).license);
58902
58989
  }
@@ -58927,9 +59014,13 @@ async function persistAutoRollSettings(settings) {
58927
59014
  const normalized = normalizeAutoRollSettings(settings ?? void 0);
58928
59015
  await patchAppState({ autoRoll: {
58929
59016
  enabled: normalized.enabled,
58930
- warningThreshold: normalized.warningThreshold,
58931
- switchThreshold: normalized.switchThreshold,
58932
- restartOfficialCodexOnAutoRoll: normalized.restartOfficialCodexOnAutoRoll
59017
+ rearmRemainingThreshold: normalized.rearmRemainingThreshold,
59018
+ switchRemainingThreshold: normalized.switchRemainingThreshold,
59019
+ restartOfficialCodexOnAutoRoll: normalized.restartOfficialCodexOnAutoRoll,
59020
+ launchOfficialCodexWhenClosedOnAutoRoll: normalized.launchOfficialCodexWhenClosedOnAutoRoll,
59021
+ priorityOrder: normalized.priorityOrder,
59022
+ lowRemainingNotificationEnabled: normalized.lowRemainingNotificationEnabled,
59023
+ lowRemainingNotificationThreshold: normalized.lowRemainingNotificationThreshold
58933
59024
  } });
58934
59025
  }
58935
59026
  async function readCodexSettingsJsonRaw() {
@@ -58967,9 +59058,13 @@ async function writeCodexSettingsJsonRaw(payload) {
58967
59058
  },
58968
59059
  autoRoll: autoRoll ? {
58969
59060
  enabled: autoRoll.enabled,
58970
- warningThreshold: autoRoll.warningThreshold,
58971
- switchThreshold: autoRoll.switchThreshold,
58972
- restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
59061
+ rearmRemainingThreshold: autoRoll.rearmRemainingThreshold,
59062
+ switchRemainingThreshold: autoRoll.switchRemainingThreshold,
59063
+ restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll,
59064
+ launchOfficialCodexWhenClosedOnAutoRoll: autoRoll.launchOfficialCodexWhenClosedOnAutoRoll,
59065
+ priorityOrder: autoRoll.priorityOrder,
59066
+ lowRemainingNotificationEnabled: autoRoll.lowRemainingNotificationEnabled,
59067
+ lowRemainingNotificationThreshold: autoRoll.lowRemainingNotificationThreshold
58973
59068
  } : void 0,
58974
59069
  license: license ? {
58975
59070
  licenseKey: license.licenseKey ?? null,
@@ -59007,7 +59102,7 @@ function normalizeSecret(value) {
59007
59102
  return typeof value === "string" ? value.trim() : "";
59008
59103
  }
59009
59104
  async function readLegacyLicenseSecret() {
59010
- const legacyPath = path.join(getUserDataDir(), LEGACY_LICENSE_SECRET_FILE$1);
59105
+ const legacyPath = nodePath.join(getUserDataDir(), LEGACY_LICENSE_SECRET_FILE$1);
59011
59106
  try {
59012
59107
  return normalizeSecret(await promises.readFile(legacyPath, "utf8"));
59013
59108
  } catch (error) {
@@ -59356,13 +59451,13 @@ const IS_CASE_INSENSITIVE_PLATFORM = process.platform === "darwin" || process.pl
59356
59451
  function stripTrailingSeparators(input) {
59357
59452
  const resolved = input.trim();
59358
59453
  if (resolved.length === 0) return resolved;
59359
- const root = path.parse(resolved).root;
59454
+ const root = nodePath.parse(resolved).root;
59360
59455
  let next = resolved;
59361
59456
  while (next.length > root.length && /[\\/]+$/.test(next)) next = next.slice(0, -1);
59362
59457
  return next;
59363
59458
  }
59364
59459
  function canonicalizeProjectRoot(input) {
59365
- const resolved = path.resolve(input.trim());
59460
+ const resolved = nodePath.resolve(input.trim());
59366
59461
  return stripTrailingSeparators((() => {
59367
59462
  try {
59368
59463
  return realpathSync.native(resolved);
@@ -59543,14 +59638,14 @@ let cachedStatus = null;
59543
59638
  function getPathHintEntries() {
59544
59639
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
59545
59640
  const homeEntries = homeDir ? [
59546
- path.join(homeDir, ".local", "bin"),
59547
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
59548
- path.join(homeDir, ".fnm", "current", "bin"),
59549
- path.join(homeDir, ".volta", "bin"),
59550
- path.join(homeDir, ".asdf", "shims"),
59551
- path.join(homeDir, ".bun", "bin")
59641
+ nodePath.join(homeDir, ".local", "bin"),
59642
+ nodePath.join(homeDir, ".fnm", "aliases", "default", "bin"),
59643
+ nodePath.join(homeDir, ".fnm", "current", "bin"),
59644
+ nodePath.join(homeDir, ".volta", "bin"),
59645
+ nodePath.join(homeDir, ".asdf", "shims"),
59646
+ nodePath.join(homeDir, ".bun", "bin")
59552
59647
  ] : [];
59553
- const configuredHints = (process.env.CODEX_PATH_HINTS ?? "").split(path.delimiter).map((entry) => entry.trim()).filter(Boolean);
59648
+ const configuredHints = (process.env.CODEX_PATH_HINTS ?? "").split(nodePath.delimiter).map((entry) => entry.trim()).filter(Boolean);
59554
59649
  return [
59555
59650
  "/opt/homebrew/bin",
59556
59651
  "/usr/local/bin",
@@ -59560,7 +59655,7 @@ function getPathHintEntries() {
59560
59655
  }
59561
59656
  function fileExists$1(candidate) {
59562
59657
  if (!candidate) return null;
59563
- const normalized = path.resolve(candidate);
59658
+ const normalized = nodePath.resolve(candidate);
59564
59659
  try {
59565
59660
  if (statSync(normalized).isFile()) return normalized;
59566
59661
  } catch {}
@@ -59577,7 +59672,7 @@ function resolveCodexFromEnv() {
59577
59672
  }
59578
59673
  function resolveCodexFromPath() {
59579
59674
  const pathValue = process.env.PATH ?? "";
59580
- const entries = Array.from(new Set([...getPathHintEntries(), ...pathValue.split(path.delimiter).map((entry) => entry.trim()).filter(Boolean)].filter(Boolean)));
59675
+ const entries = Array.from(new Set([...getPathHintEntries(), ...pathValue.split(nodePath.delimiter).map((entry) => entry.trim()).filter(Boolean)].filter(Boolean)));
59581
59676
  const names = process.platform === "win32" ? [
59582
59677
  "codex.exe",
59583
59678
  "codex.cmd",
@@ -59585,16 +59680,16 @@ function resolveCodexFromPath() {
59585
59680
  "codex"
59586
59681
  ] : ["codex"];
59587
59682
  for (const entry of entries) for (const name of names) {
59588
- const candidate = fileExists$1(path.join(entry, name));
59683
+ const candidate = fileExists$1(nodePath.join(entry, name));
59589
59684
  if (candidate) return candidate;
59590
59685
  }
59591
59686
  return null;
59592
59687
  }
59593
59688
  function resolvePackageJsonPath(cliPath) {
59594
- const normalized = path.resolve(cliPath);
59595
- const binDir = path.dirname(normalized);
59596
- const packageDir = path.basename(binDir) === "bin" ? path.dirname(binDir) : path.dirname(normalized);
59597
- return fileExists$1(path.join(packageDir, "package.json"));
59689
+ const normalized = nodePath.resolve(cliPath);
59690
+ const binDir = nodePath.dirname(normalized);
59691
+ const packageDir = nodePath.basename(binDir) === "bin" ? nodePath.dirname(binDir) : nodePath.dirname(normalized);
59692
+ return fileExists$1(nodePath.join(packageDir, "package.json"));
59598
59693
  }
59599
59694
  function readCodexCliVersion(cliPath) {
59600
59695
  const packageJsonPath = resolvePackageJsonPath(cliPath);
@@ -59685,17 +59780,17 @@ function resolveNodeRuntime(env) {
59685
59780
  ...env
59686
59781
  };
59687
59782
  const homeDir = runtimeEnv.HOME ?? runtimeEnv.USERPROFILE ?? "";
59688
- const pathHints = (runtimeEnv.CODEX_PATH_HINTS ?? process.env.CODEX_PATH_HINTS ?? "").split(path.delimiter).map((entry) => entry.trim()).filter(Boolean);
59783
+ const pathHints = (runtimeEnv.CODEX_PATH_HINTS ?? process.env.CODEX_PATH_HINTS ?? "").split(nodePath.delimiter).map((entry) => entry.trim()).filter(Boolean);
59689
59784
  const extraPathEntries = [
59690
59785
  "/usr/local/bin",
59691
59786
  "/opt/homebrew/bin",
59692
- path.join(homeDir, ".local", "bin"),
59693
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
59694
- path.join(homeDir, ".fnm", "current", "bin"),
59787
+ nodePath.join(homeDir, ".local", "bin"),
59788
+ nodePath.join(homeDir, ".fnm", "aliases", "default", "bin"),
59789
+ nodePath.join(homeDir, ".fnm", "current", "bin"),
59695
59790
  ...pathHints
59696
59791
  ].filter(Boolean);
59697
59792
  const currentPath = runtimeEnv.PATH ?? "";
59698
- runtimeEnv.PATH = Array.from(new Set([...extraPathEntries, ...currentPath.split(path.delimiter).filter(Boolean)])).join(path.delimiter);
59793
+ runtimeEnv.PATH = Array.from(new Set([...extraPathEntries, ...currentPath.split(nodePath.delimiter).filter(Boolean)])).join(nodePath.delimiter);
59699
59794
  const candidates = Array.from(new Set([
59700
59795
  runtimeEnv.CODEX_NODE_RUNTIME?.trim(),
59701
59796
  runtimeEnv.CODEX_NODE_BIN?.trim(),
@@ -59732,11 +59827,11 @@ function buildCodexCommand$1(binaryPath, args, env) {
59732
59827
  //#endregion
59733
59828
  //#region ../../packages/runtime-codex/src/codex/home.ts
59734
59829
  function resolveHomeDir$1() {
59735
- return process.env.HOME || process.env.USERPROFILE || os.homedir();
59830
+ return process.env.HOME || process.env.USERPROFILE || nodeOs.homedir();
59736
59831
  }
59737
59832
  function expandHomePrefix(input, homeDir) {
59738
59833
  if (input === "~") return homeDir;
59739
- if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(homeDir, input.slice(2));
59834
+ if (input.startsWith("~/") || input.startsWith("~\\")) return nodePath.join(homeDir, input.slice(2));
59740
59835
  return input;
59741
59836
  }
59742
59837
  function normalizePathCandidate(value) {
@@ -59747,13 +59842,10 @@ function normalizePathCandidate(value) {
59747
59842
  function normalizeCodexHomePath(input) {
59748
59843
  const configured = normalizePathCandidate(input);
59749
59844
  if (!configured) return null;
59750
- return path.resolve(expandHomePrefix(configured, resolveHomeDir$1()));
59845
+ return nodePath.resolve(expandHomePrefix(configured, resolveHomeDir$1()));
59751
59846
  }
59752
59847
  function resolveDefaultCodexHomeDir() {
59753
- return path.join(resolveHomeDir$1(), ".codex");
59754
- }
59755
- function resolveProfileCodexHomeDir(profileName) {
59756
- return path.join(getUserDataDir(), "profile-homes", profileName);
59848
+ return nodePath.join(resolveHomeDir$1(), ".codex");
59757
59849
  }
59758
59850
  function resolveCodexHomeDir(options) {
59759
59851
  return normalizeCodexHomePath(options?.codexHomePath) ?? resolveDefaultCodexHomeDir();
@@ -59765,14 +59857,7 @@ async function resolveCodexRuntimeContext(options) {
59765
59857
  profileName: null,
59766
59858
  source: "explicit"
59767
59859
  };
59768
- const state = await getAppState();
59769
- const profileName = normalizePathCandidate(state.app.lastProfileName);
59770
- if (profileName && state.profilesByName[profileName]) return {
59771
- codexHomePath: resolveProfileCodexHomeDir(profileName),
59772
- profileName,
59773
- source: "profile"
59774
- };
59775
- const configured = normalizeCodexHomePath(state.runtimeSettings?.codexHome);
59860
+ const configured = normalizeCodexHomePath((await getAppState()).runtimeSettings?.codexHome);
59776
59861
  if (configured) return {
59777
59862
  codexHomePath: configured,
59778
59863
  profileName: null,
@@ -59849,19 +59934,19 @@ function buildCliEnv(codexPath, overrides = {}) {
59849
59934
  ...overrides
59850
59935
  };
59851
59936
  const currentPath = env.PATH ?? "";
59852
- const codexDir = path$1.dirname(codexPath);
59937
+ const codexDir = path.dirname(codexPath);
59853
59938
  const homeDir = process.env.HOME ?? "";
59854
- const extraPathHints = (process.env.CODEX_PATH_HINTS ?? "").split(path$1.delimiter).map((entry) => entry.trim()).filter(Boolean);
59939
+ const extraPathHints = (process.env.CODEX_PATH_HINTS ?? "").split(path.delimiter).map((entry) => entry.trim()).filter(Boolean);
59855
59940
  const segments = [...[
59856
59941
  codexDir,
59857
59942
  "/usr/local/bin",
59858
59943
  "/opt/homebrew/bin",
59859
- path$1.join(homeDir, ".local", "bin"),
59860
- path$1.join(homeDir, ".fnm", "aliases", "default", "bin"),
59861
- path$1.join(homeDir, ".fnm", "current", "bin"),
59944
+ path.join(homeDir, ".local", "bin"),
59945
+ path.join(homeDir, ".fnm", "aliases", "default", "bin"),
59946
+ path.join(homeDir, ".fnm", "current", "bin"),
59862
59947
  ...extraPathHints
59863
- ].filter(Boolean), ...currentPath.split(path$1.delimiter).filter(Boolean)];
59864
- env.PATH = Array.from(new Set(segments)).join(path$1.delimiter);
59948
+ ].filter(Boolean), ...currentPath.split(path.delimiter).filter(Boolean)];
59949
+ env.PATH = Array.from(new Set(segments)).join(path.delimiter);
59865
59950
  return env;
59866
59951
  }
59867
59952
  //#endregion
@@ -60818,7 +60903,7 @@ async function getActiveProfileContext() {
60818
60903
  const profileKey = toProfileStorageKey(profileName);
60819
60904
  return {
60820
60905
  profileKey,
60821
- parityStoreDir: path.join(getUserDataDir(), PROFILE_ROOT_DIR, profileKey, PROFILE_PARITY_DIR)
60906
+ parityStoreDir: nodePath.join(getUserDataDir(), PROFILE_ROOT_DIR, profileKey, PROFILE_PARITY_DIR)
60822
60907
  };
60823
60908
  }
60824
60909
  async function fileExists(filePath) {
@@ -60830,10 +60915,10 @@ async function fileExists(filePath) {
60830
60915
  }
60831
60916
  }
60832
60917
  function legacyStorePath() {
60833
- return path.join(getUserDataDir(), LEGACY_STORE_FILE);
60918
+ return nodePath.join(getUserDataDir(), LEGACY_STORE_FILE);
60834
60919
  }
60835
60920
  function legacyMigrationMarkerPath() {
60836
- return path.join(getUserDataDir(), LEGACY_MIGRATION_MARKER_FILE);
60921
+ return nodePath.join(getUserDataDir(), LEGACY_MIGRATION_MARKER_FILE);
60837
60922
  }
60838
60923
  async function readLegacyMigrationMarker() {
60839
60924
  try {
@@ -60852,13 +60937,13 @@ async function migrateLegacyStoreIfNeeded(scopedStorePath) {
60852
60937
  if (!await fileExists(legacyPath)) return;
60853
60938
  const marker = await readLegacyMigrationMarker();
60854
60939
  if (marker?.migratedToProfileKey && marker.migratedToProfileKey !== context.profileKey) return;
60855
- await fs$1.mkdir(path.dirname(scopedStorePath), { recursive: true });
60940
+ await fs$1.mkdir(nodePath.dirname(scopedStorePath), { recursive: true });
60856
60941
  await fs$1.copyFile(legacyPath, scopedStorePath);
60857
60942
  }
60858
60943
  async function storePath() {
60859
60944
  const context = await getActiveProfileContext();
60860
60945
  await fs$1.mkdir(context.parityStoreDir, { recursive: true });
60861
- const scopedPath = path.join(context.parityStoreDir, STORE_FILE);
60946
+ const scopedPath = nodePath.join(context.parityStoreDir, STORE_FILE);
60862
60947
  await migrateLegacyStoreIfNeeded(scopedPath);
60863
60948
  return scopedPath;
60864
60949
  }
@@ -60869,7 +60954,7 @@ function toWorkspaceName(inputPath) {
60869
60954
  function sanitizeWorkspace(entry) {
60870
60955
  if (!entry || typeof entry !== "object") return null;
60871
60956
  const id = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
60872
- const workspacePath = typeof entry.path === "string" && entry.path.trim() ? path.resolve(entry.path) : null;
60957
+ const workspacePath = typeof entry.path === "string" && entry.path.trim() ? nodePath.resolve(entry.path) : null;
60873
60958
  if (!id || !workspacePath) return null;
60874
60959
  const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : toWorkspaceName(workspacePath);
60875
60960
  const branch = entry.worktree && typeof entry.worktree === "object" && typeof entry.worktree.branch === "string" ? entry.worktree.branch.trim() || "main" : "main";
@@ -60988,7 +61073,7 @@ function ensureThreadEntry(store, threadId) {
60988
61073
  return created;
60989
61074
  }
60990
61075
  function resolveOverridesDbPath(stateDir) {
60991
- return path.join(stateDir, "state.sqlite");
61076
+ return nodePath.join(stateDir, "state.sqlite");
60992
61077
  }
60993
61078
  async function readExternalThreadOverrides(stateDir) {
60994
61079
  return await readDocument(resolveOverridesDbPath(stateDir), EXTERNAL_THREAD_OVERRIDES_DOCUMENT, (value) => {
@@ -61310,7 +61395,7 @@ function collectExternalCodexThreadSyncDirectoryWatchPaths(rootPath) {
61310
61395
  while (stack.length > 0) {
61311
61396
  const currentPath = stack.pop();
61312
61397
  if (!currentPath) continue;
61313
- const resolvedCurrentPath = path.resolve(currentPath);
61398
+ const resolvedCurrentPath = nodePath.resolve(currentPath);
61314
61399
  if (seenPaths.has(resolvedCurrentPath)) continue;
61315
61400
  seenPaths.add(resolvedCurrentPath);
61316
61401
  let stat;
@@ -61329,7 +61414,7 @@ function collectExternalCodexThreadSyncDirectoryWatchPaths(rootPath) {
61329
61414
  }
61330
61415
  for (const entry of entries) {
61331
61416
  if (!entry.isDirectory()) continue;
61332
- stack.push(path.join(resolvedCurrentPath, entry.name));
61417
+ stack.push(nodePath.join(resolvedCurrentPath, entry.name));
61333
61418
  }
61334
61419
  }
61335
61420
  return collectedPaths;
@@ -61338,7 +61423,7 @@ function collectExternalCodexThreadSyncWatchTargets(homePath) {
61338
61423
  const recursive = supportsRecursiveExternalCodexThreadSyncWatcher();
61339
61424
  const targets = [];
61340
61425
  for (const relativePath of EXTERNAL_SYNC_WATCH_DIRECTORY_NAMES) {
61341
- const absolutePath = path.join(homePath, relativePath);
61426
+ const absolutePath = nodePath.join(homePath, relativePath);
61342
61427
  if (!fs.existsSync(absolutePath)) continue;
61343
61428
  if (recursive) {
61344
61429
  targets.push({
@@ -61350,7 +61435,7 @@ function collectExternalCodexThreadSyncWatchTargets(homePath) {
61350
61435
  }
61351
61436
  for (const watchPath of collectExternalCodexThreadSyncDirectoryWatchPaths(absolutePath)) targets.push({
61352
61437
  watchPath,
61353
- label: path.relative(homePath, watchPath) || ".",
61438
+ label: nodePath.relative(homePath, watchPath) || ".",
61354
61439
  recursive: false
61355
61440
  });
61356
61441
  }
@@ -61362,7 +61447,7 @@ function collectExternalCodexThreadSyncWatchTargets(homePath) {
61362
61447
  return targets;
61363
61448
  }
61364
61449
  function startExternalCodexThreadSyncWatcher(homePath) {
61365
- const resolvedHomePath = path.resolve(homePath);
61450
+ const resolvedHomePath = nodePath.resolve(homePath);
61366
61451
  if (externalCodexThreadSyncWatchers.length > 0 && externalCodexThreadSyncWatcherHomePath === resolvedHomePath) return;
61367
61452
  closeExternalCodexThreadSyncWatcher();
61368
61453
  try {
@@ -61393,7 +61478,7 @@ function startExternalCodexThreadSyncWatcher(homePath) {
61393
61478
  }
61394
61479
  }
61395
61480
  function resolveStateDbPath(stateDir) {
61396
- return path.join(stateDir, "state.sqlite");
61481
+ return nodePath.join(stateDir, "state.sqlite");
61397
61482
  }
61398
61483
  function asString$6(value) {
61399
61484
  return typeof value === "string" ? value : value == null ? "" : String(value);
@@ -61680,7 +61765,7 @@ function resolveLegacyFilePath(source) {
61680
61765
  if (normalized.startsWith("~/")) {
61681
61766
  const home = process.env.HOME?.trim();
61682
61767
  if (!home) return null;
61683
- return path.join(home, normalized.slice(2));
61768
+ return nodePath.join(home, normalized.slice(2));
61684
61769
  }
61685
61770
  return normalized;
61686
61771
  }
@@ -61716,7 +61801,7 @@ async function materializeLegacyImageAttachment(input) {
61716
61801
  mimeType = Mime_default.getType(filePath) ?? "";
61717
61802
  if (!mimeType.startsWith("image/")) return null;
61718
61803
  bytes = await fs$1.readFile(filePath);
61719
- name = path.basename(filePath) || `image${inferImageExtension({ mimeType })}`;
61804
+ name = nodePath.basename(filePath) || `image${inferImageExtension({ mimeType })}`;
61720
61805
  }
61721
61806
  if (bytes.byteLength === 0 || bytes.byteLength > 10485760) return null;
61722
61807
  const attachmentId = createDeterministicLegacyAttachmentId(input.threadId, input.attachmentKey);
@@ -61733,7 +61818,7 @@ async function materializeLegacyImageAttachment(input) {
61733
61818
  attachment
61734
61819
  });
61735
61820
  if (!attachmentPath) return null;
61736
- await fs$1.mkdir(path.dirname(attachmentPath), { recursive: true });
61821
+ await fs$1.mkdir(nodePath.dirname(attachmentPath), { recursive: true });
61737
61822
  await fs$1.writeFile(attachmentPath, bytes);
61738
61823
  return attachment;
61739
61824
  } catch {
@@ -62052,7 +62137,7 @@ async function readImportMarker(stateDir) {
62052
62137
  return { version };
62053
62138
  });
62054
62139
  if (stored) return stored;
62055
- const markerPath = path.join(stateDir, EXTERNAL_SYNC_MARKER_FILE);
62140
+ const markerPath = nodePath.join(stateDir, EXTERNAL_SYNC_MARKER_FILE);
62056
62141
  try {
62057
62142
  const raw = await fs$1.readFile(markerPath, "utf8");
62058
62143
  const version = asFiniteNumber(asRecord$3(JSON.parse(raw))?.version);
@@ -62111,7 +62196,7 @@ function shouldReadLegacyThreadForImport(input) {
62111
62196
  }
62112
62197
  async function writeImportMarker(stateDir, payload) {
62113
62198
  await writeDocument(resolveStateDbPath(stateDir), EXTERNAL_SYNC_MARKER_DOCUMENT, payload);
62114
- await fs$1.rm(path.join(stateDir, EXTERNAL_SYNC_MARKER_FILE), { force: true }).catch(() => void 0);
62199
+ await fs$1.rm(nodePath.join(stateDir, EXTERNAL_SYNC_MARKER_FILE), { force: true }).catch(() => void 0);
62115
62200
  }
62116
62201
  function resolveLegacyThreadTimestamps(thread) {
62117
62202
  const createdAtMs = toEpochMs(thread.createdAt ?? thread.updatedAt, Date.now());
@@ -65115,7 +65200,7 @@ var RotatingFileSink = class {
65115
65200
  this.maxBytes = options.maxBytes;
65116
65201
  this.maxFiles = options.maxFiles;
65117
65202
  this.throwOnError = options.throwOnError ?? false;
65118
- fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
65203
+ fs.mkdirSync(nodePath.dirname(this.filePath), { recursive: true });
65119
65204
  this.pruneOverflowBackups();
65120
65205
  this.currentSize = this.readCurrentSize();
65121
65206
  }
@@ -65150,13 +65235,13 @@ var RotatingFileSink = class {
65150
65235
  }
65151
65236
  pruneOverflowBackups() {
65152
65237
  try {
65153
- const dir = path.dirname(this.filePath);
65154
- const baseName = path.basename(this.filePath);
65238
+ const dir = nodePath.dirname(this.filePath);
65239
+ const baseName = nodePath.basename(this.filePath);
65155
65240
  for (const entry of fs.readdirSync(dir)) {
65156
65241
  if (!entry.startsWith(`${baseName}.`)) continue;
65157
65242
  const suffix = Number(entry.slice(baseName.length + 1));
65158
65243
  if (!Number.isInteger(suffix) || suffix <= this.maxFiles) continue;
65159
- fs.rmSync(path.join(dir, entry), { force: true });
65244
+ fs.rmSync(nodePath.join(dir, entry), { force: true });
65160
65245
  }
65161
65246
  } catch {
65162
65247
  if (this.throwOnError) throw new Error(`Failed to prune log backups for ${this.filePath}`);
@@ -65296,7 +65381,7 @@ function makeEventNdjsonLogger(filePath, options) {
65296
65381
  const streamLabel = resolveStreamLabel(options.stream);
65297
65382
  const directoryReady = yield* sync(() => {
65298
65383
  try {
65299
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
65384
+ fs.mkdirSync(nodePath.dirname(filePath), { recursive: true });
65300
65385
  return true;
65301
65386
  } catch (error) {
65302
65387
  return {
@@ -65319,7 +65404,7 @@ function makeEventNdjsonLogger(filePath, options) {
65319
65404
  const existing = threadWriters.get(threadSegment);
65320
65405
  if (existing) return existing;
65321
65406
  const writer = yield* makeThreadWriter({
65322
- filePath: path.join(path.dirname(filePath), `${threadSegment}.log`),
65407
+ filePath: nodePath.join(nodePath.dirname(filePath), `${threadSegment}.log`),
65323
65408
  maxBytes,
65324
65409
  maxFiles,
65325
65410
  batchWindowMs,
@@ -65536,12 +65621,14 @@ function toUserInputQuestions(payload) {
65536
65621
  const id = asString$3(question.id)?.trim();
65537
65622
  const header = asString$3(question.header)?.trim();
65538
65623
  const prompt = asString$3(question.question)?.trim();
65624
+ const multiSelect = question.multiSelect === true || question.multi_select === true;
65539
65625
  if (!id || !header || !prompt || !options || options.length === 0) return;
65540
65626
  return {
65541
65627
  id,
65542
65628
  header,
65543
65629
  question: prompt,
65544
- options
65630
+ options,
65631
+ multiSelect
65545
65632
  };
65546
65633
  }).filter((question) => question !== void 0);
65547
65634
  return parsedQuestions.length > 0 ? parsedQuestions : void 0;
@@ -66843,7 +66930,7 @@ function normalizeShellCommand(value) {
66843
66930
  }
66844
66931
  function shellCandidateFromCommand(command) {
66845
66932
  if (!command || command.length === 0) return null;
66846
- const shellName = path.basename(command).toLowerCase();
66933
+ const shellName = nodePath.basename(command).toLowerCase();
66847
66934
  if (process.platform !== "win32" && shellName === "zsh") return {
66848
66935
  shell: command,
66849
66936
  args: ["-o", "nopromptsp"]
@@ -67025,7 +67112,7 @@ var TerminalManagerRuntime = class extends EventEmitter {
67025
67112
  this.subprocessPollInFlight = false;
67026
67113
  this.killEscalationTimers = /* @__PURE__ */ new Map();
67027
67114
  this.logger = createLogger("terminal");
67028
- this.logsDir = options.logsDir ?? path.resolve(process.cwd(), ".logs", "terminals");
67115
+ this.logsDir = options.logsDir ?? nodePath.resolve(process.cwd(), ".logs", "terminals");
67029
67116
  this.historyLineLimit = options.historyLineLimit ?? DEFAULT_HISTORY_LINE_LIMIT;
67030
67117
  this.ptyAdapter = options.ptyAdapter;
67031
67118
  this.shellResolver = options.shellResolver ?? defaultShellResolver;
@@ -67605,7 +67692,7 @@ var TerminalManagerRuntime = class extends EventEmitter {
67605
67692
  async deleteAllHistoryForThread(threadId) {
67606
67693
  const threadPrefix = `${toSafeThreadId(threadId)}_`;
67607
67694
  try {
67608
- const removals = (await fs.promises.readdir(this.logsDir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => name === `${toSafeThreadId(threadId)}.log` || name === `${legacySafeThreadId(threadId)}.log` || name.startsWith(threadPrefix)).map((name) => fs.promises.rm(path.join(this.logsDir, name), { force: true }));
67695
+ const removals = (await fs.promises.readdir(this.logsDir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => name === `${toSafeThreadId(threadId)}.log` || name === `${legacySafeThreadId(threadId)}.log` || name.startsWith(threadPrefix)).map((name) => fs.promises.rm(nodePath.join(this.logsDir, name), { force: true }));
67609
67696
  await Promise.all(removals);
67610
67697
  } catch (error) {
67611
67698
  this.logger.warn("failed to delete terminal histories for thread", {
@@ -67637,11 +67724,11 @@ var TerminalManagerRuntime = class extends EventEmitter {
67637
67724
  }
67638
67725
  historyPath(threadId, terminalId) {
67639
67726
  const threadPart = toSafeThreadId(threadId);
67640
- if (terminalId === "default") return path.join(this.logsDir, `${threadPart}.log`);
67641
- return path.join(this.logsDir, `${threadPart}_${toSafeTerminalId(terminalId)}.log`);
67727
+ if (terminalId === "default") return nodePath.join(this.logsDir, `${threadPart}.log`);
67728
+ return nodePath.join(this.logsDir, `${threadPart}_${toSafeTerminalId(terminalId)}.log`);
67642
67729
  }
67643
67730
  legacyHistoryPath(threadId) {
67644
- return path.join(this.logsDir, `${legacySafeThreadId(threadId)}.log`);
67731
+ return nodePath.join(this.logsDir, `${legacySafeThreadId(threadId)}.log`);
67645
67732
  }
67646
67733
  async runWithThreadLock(threadId, task) {
67647
67734
  const previous = this.threadLocks.get(threadId) ?? Promise.resolve();
@@ -70617,8 +70704,8 @@ const NodePtyAdapterLive = effect(PtyAdapter, gen(function* () {
70617
70704
  function makeServerProviderLayer() {
70618
70705
  return gen(function* () {
70619
70706
  const { stateDir } = yield* ServerConfig$1;
70620
- const providerLogsDir = path.join(stateDir, "logs", "provider");
70621
- const providerEventLogPath = path.join(providerLogsDir, "events.log");
70707
+ const providerLogsDir = nodePath.join(stateDir, "logs", "provider");
70708
+ const providerEventLogPath = nodePath.join(providerLogsDir, "events.log");
70622
70709
  const nativeEventLogger = yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "native" });
70623
70710
  const canonicalEventLogger = yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "canonical" });
70624
70711
  const providerSessionDirectoryLayer = ProviderSessionDirectoryLive.pipe(provide$2(ProviderSessionRuntimeRepositoryLive));
@@ -72762,8 +72849,8 @@ function parseSchemaKeys(schemaRaw) {
72762
72849
  async function readSchemaFromLocal() {
72763
72850
  const candidates = [
72764
72851
  process.env.CODEX_CONFIG_SCHEMA_PATH,
72765
- path.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
72766
- path.join(process.cwd(), "node_modules", "@openai", "codex", "codex-rs", "core", "config.schema.json")
72852
+ nodePath.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
72853
+ nodePath.join(process.cwd(), "node_modules", "@openai", "codex", "codex-rs", "core", "config.schema.json")
72767
72854
  ].filter((candidate) => Boolean(candidate));
72768
72855
  for (const candidate of candidates) try {
72769
72856
  return await readFile(candidate, "utf8");
@@ -72861,21 +72948,6 @@ const DEFAULT_CONFIG_TEMPLATE = [
72861
72948
  "web_search = \"cached\"",
72862
72949
  ""
72863
72950
  ].join("\n");
72864
- const SUPPORTED_REASONING_EFFORT = new Set([
72865
- "minimal",
72866
- "low",
72867
- "medium",
72868
- "high",
72869
- "xhigh",
72870
- "none"
72871
- ]);
72872
- const CLI_SUPPORTED_REASONING_EFFORT = new Set([
72873
- "minimal",
72874
- "low",
72875
- "medium",
72876
- "high",
72877
- "none"
72878
- ]);
72879
72951
  const YOLO_PRESET = {
72880
72952
  model: "gpt-5.3-codex",
72881
72953
  reviewModel: "gpt-5.3-codex",
@@ -72901,10 +72973,10 @@ const YOLO_PRESET = {
72901
72973
  //#endregion
72902
72974
  //#region ../../packages/runtime-codex/src/codex/config-io.ts
72903
72975
  function getConfigPath(options) {
72904
- return path.join(resolveCodexHomeDir(options), "config.toml");
72976
+ return nodePath.join(resolveCodexHomeDir(options), "config.toml");
72905
72977
  }
72906
72978
  async function ensureConfigDirExists(filePath) {
72907
- await mkdir(path.dirname(filePath), { recursive: true });
72979
+ await mkdir(nodePath.dirname(filePath), { recursive: true });
72908
72980
  }
72909
72981
  function normalizeLineEndings(content) {
72910
72982
  return content.replace(/\r\n/g, "\n");
@@ -73168,14 +73240,6 @@ function buildSettingsSummary(data) {
73168
73240
  }
73169
73241
  };
73170
73242
  }
73171
- function getUnsupportedReasoningEffort(data, allowed = SUPPORTED_REASONING_EFFORT) {
73172
- if (!data || !Object.prototype.hasOwnProperty.call(data, "model_reasoning_effort")) return null;
73173
- const raw = data["model_reasoning_effort"];
73174
- if (typeof raw !== "string") return String(raw ?? "");
73175
- const value = raw.trim();
73176
- if (!value) return null;
73177
- return allowed.has(value) ? null : value;
73178
- }
73179
73243
  function parseRmcpClientEnabled(data) {
73180
73244
  const rawFeatures = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
73181
73245
  if (rawFeatures && typeof rawFeatures["rmcp_client"] === "boolean") return rawFeatures["rmcp_client"];
@@ -73479,21 +73543,6 @@ async function updateCodexConfigValues(updates, options) {
73479
73543
  await writeConfigContent(serialized, options);
73480
73544
  return buildSnapshot(serialized, true, options);
73481
73545
  }
73482
- async function sanitizeCodexConfigForCli(options) {
73483
- const { content } = await readConfigContent(options);
73484
- const parsed = tryParseToml(content);
73485
- if (!parsed.data) throw new Error(parsed.error ?? "config.toml contains invalid TOML syntax.");
73486
- if (!getUnsupportedReasoningEffort(parsed.data, CLI_SUPPORTED_REASONING_EFFORT)) return null;
73487
- const sanitized = { ...parsed.data };
73488
- delete sanitized["model_reasoning_effort"];
73489
- await writeConfigContent(ensureTrailingNewline((0, import_toml.stringify)(sanitized)), options);
73490
- let restored = false;
73491
- return async () => {
73492
- if (restored) return;
73493
- restored = true;
73494
- await writeConfigContent(ensureTrailingNewline(normalizeLineEndings(content)), options);
73495
- };
73496
- }
73497
73546
  //#endregion
73498
73547
  //#region ../../packages/contracts/src/profiles/identity.ts
73499
73548
  function normalizeValue(value) {
@@ -73676,10 +73725,10 @@ function parseRateLimitSnapshotFromRpcMessage(message) {
73676
73725
  }
73677
73726
  async function fetchRateLimitsViaRpc(envOverride, options = {}) {
73678
73727
  const binaryPath = options.codexPath ?? await requireCodexCli();
73679
- const tempHome = await promises.mkdtemp(path.join(os.tmpdir(), "codex-rpc-"));
73680
- const tempAuthPath = path.join(tempHome, "auth.json");
73728
+ const tempHome = await promises.mkdtemp(nodePath.join(nodeOs.tmpdir(), "codex-rpc-"));
73729
+ const tempAuthPath = nodePath.join(tempHome, "auth.json");
73681
73730
  let initialSourceAuth = null;
73682
- const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? path.join(envOverride.CODEX_HOME, "auth.json") : path.join(envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? os.homedir(), ".codex", "auth.json"));
73731
+ const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? nodePath.join(envOverride.CODEX_HOME, "auth.json") : nodePath.join(envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? nodeOs.homedir(), ".codex", "auth.json"));
73683
73732
  try {
73684
73733
  initialSourceAuth = await promises.readFile(sourceAuthPath, "utf8").catch(() => null);
73685
73734
  if (!initialSourceAuth) return null;
@@ -73828,21 +73877,6 @@ var ProfileManager = class {
73828
73877
  isNotFoundError(error) {
73829
73878
  return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
73830
73879
  }
73831
- async readPreferredProfileName() {
73832
- try {
73833
- return await getLastProfileName();
73834
- } catch (error) {
73835
- logWarn("Failed to read preferred profile name:", error);
73836
- return null;
73837
- }
73838
- }
73839
- async persistPreferredProfileName(name) {
73840
- try {
73841
- await persistLastProfileName(name);
73842
- } catch (error) {
73843
- logWarn("Failed to persist preferred profile name:", error);
73844
- }
73845
- }
73846
73880
  toStateRecord(record) {
73847
73881
  return {
73848
73882
  name: record.name,
@@ -73941,44 +73975,33 @@ var ProfileManager = class {
73941
73975
  return null;
73942
73976
  }
73943
73977
  }
73944
- async captureActiveAuthSnapshot() {
73945
- await this.initialize();
73978
+ emptyAuthSnapshot(fingerprint = null) {
73979
+ return {
73980
+ fingerprint,
73981
+ email: null,
73982
+ accountId: null,
73983
+ userId: null,
73984
+ chatgptUserId: null,
73985
+ chatgptAccountUserId: null,
73986
+ workspaceId: null
73987
+ };
73988
+ }
73989
+ async captureAuthSnapshotAtPath(authPath) {
73946
73990
  let raw;
73947
73991
  try {
73948
- raw = await promises$1.readFile(this.activeAuth, "utf8");
73992
+ raw = await promises$1.readFile(authPath, "utf8");
73949
73993
  } catch (error) {
73950
- if (this.isNotFoundError(error)) return {
73951
- fingerprint: null,
73952
- email: null,
73953
- accountId: null,
73954
- userId: null,
73955
- chatgptUserId: null,
73956
- workspaceId: null
73957
- };
73994
+ if (this.isNotFoundError(error)) return this.emptyAuthSnapshot();
73958
73995
  const message = error instanceof Error ? error.message : "unknown error";
73959
- const signature = typeof message === "string" ? `${message}:${this.activeAuth}` : this.activeAuth;
73996
+ const signature = typeof message === "string" ? `${message}:${authPath}` : authPath;
73960
73997
  if (this.lastActiveAuthErrorSignature !== signature) {
73961
- logWarn("Failed to snapshot active auth file:", error);
73998
+ logWarn("Failed to snapshot auth file:", error);
73962
73999
  this.lastActiveAuthErrorSignature = signature;
73963
74000
  }
73964
- return {
73965
- fingerprint: null,
73966
- email: null,
73967
- accountId: null,
73968
- userId: null,
73969
- chatgptUserId: null,
73970
- workspaceId: null
73971
- };
74001
+ return this.emptyAuthSnapshot();
73972
74002
  }
73973
74003
  const trimmed = raw.trim();
73974
- if (!trimmed) return {
73975
- fingerprint: null,
73976
- email: null,
73977
- accountId: null,
73978
- userId: null,
73979
- chatgptUserId: null,
73980
- workspaceId: null
73981
- };
74004
+ if (!trimmed) return this.emptyAuthSnapshot();
73982
74005
  const fingerprint = createHash("sha256").update(trimmed).digest("hex");
73983
74006
  try {
73984
74007
  const parsed = JSON.parse(trimmed);
@@ -73991,19 +74014,25 @@ var ProfileManager = class {
73991
74014
  accountId: this.getAccountIdFromData(normalized) ?? null,
73992
74015
  userId: metadata?.userId ?? null,
73993
74016
  chatgptUserId: metadata?.chatgptUserId ?? null,
74017
+ chatgptAccountUserId: metadata?.chatgptAccountUserId ?? null,
73994
74018
  workspaceId: workspace.id ?? null
73995
74019
  };
73996
74020
  } catch {
73997
- return {
73998
- fingerprint,
73999
- email: null,
74000
- accountId: null,
74001
- userId: null,
74002
- chatgptUserId: null,
74003
- workspaceId: null
74004
- };
74021
+ return this.emptyAuthSnapshot(fingerprint);
74005
74022
  }
74006
74023
  }
74024
+ async captureAuthSnapshot(authPath) {
74025
+ const targetPath = authPath ?? this.activeAuth;
74026
+ if (targetPath === this.activeAuth) return this.enqueueAuthSwap(async () => {
74027
+ await this.initialize();
74028
+ return this.captureAuthSnapshotAtPath(targetPath);
74029
+ });
74030
+ await this.initialize();
74031
+ return this.captureAuthSnapshotAtPath(targetPath);
74032
+ }
74033
+ async captureActiveAuthSnapshot() {
74034
+ return this.captureAuthSnapshot(this.activeAuth);
74035
+ }
74007
74036
  /**
74008
74037
  * Decode a JWT payload into an object without validating the signature.
74009
74038
  */
@@ -74264,7 +74293,7 @@ var ProfileManager = class {
74264
74293
  }
74265
74294
  async writeAtomic(filePath, contents) {
74266
74295
  const tempPath = `${filePath}.tmp`;
74267
- const dir = path$1.dirname(filePath);
74296
+ const dir = path.dirname(filePath);
74268
74297
  await promises$1.mkdir(dir, { recursive: true });
74269
74298
  await promises$1.writeFile(tempPath, contents, "utf8");
74270
74299
  try {
@@ -74319,6 +74348,99 @@ var ProfileManager = class {
74319
74348
  metadata: record.metadata
74320
74349
  }) ?? resolveFallbackAccountKey(record.name);
74321
74350
  }
74351
+ describeActiveAuth(auth) {
74352
+ const normalized = this.normalizeProfileData(auth);
74353
+ if (typeof normalized.auth_method === "string") normalized.auth_method = normalized.auth_method.trim().toLowerCase();
74354
+ normalized.auth_method = normalized.auth_method ?? "codex-cli";
74355
+ const metadata = this.extractProfileMetadata(normalized);
74356
+ const workspace = this.resolveWorkspaceIdentity(normalized, metadata);
74357
+ const workspaceId = workspace.id || DEFAULT_WORKSPACE_ID;
74358
+ const workspaceName = workspace.name ?? null;
74359
+ normalized.workspace_id = normalized.workspace_id ?? workspaceId;
74360
+ if (workspaceName) normalized.workspace_name = normalized.workspace_name ?? workspaceName;
74361
+ const email = this.resolveProfileEmail(normalized, metadata) ?? null;
74362
+ if (email) normalized.email = email;
74363
+ const accountId = this.getAccountIdFromData(normalized) ?? null;
74364
+ return {
74365
+ normalized,
74366
+ metadata,
74367
+ workspaceId,
74368
+ workspaceName,
74369
+ email,
74370
+ accountId,
74371
+ identityKey: this.resolveProfileIdentityFromData(null, normalized, metadata),
74372
+ hasTokenPayload: Boolean(normalized.id_token || normalized.access_token || normalized.refresh_token || accountId || email)
74373
+ };
74374
+ }
74375
+ async findProfileForActiveAuth(identityKey, workspaceId) {
74376
+ return this.getProfileRowByIdentityAndWorkspace(identityKey, workspaceId, { preferAuthMethod: "codex-cli" });
74377
+ }
74378
+ async syncMatchedProfileFromActiveAuth(record, description) {
74379
+ if (!this.hasTokenChanges(record.data, description.normalized)) return;
74380
+ const { profile: merged, metadata } = this.mergeProfileRecords(record.data, description.normalized);
74381
+ delete merged.tokenAlert;
74382
+ await this.persistProfileRecord(record.name, merged, metadata);
74383
+ }
74384
+ async assertActiveAuthMatchesRecord(record, message) {
74385
+ const activeAuth = await this.readActiveAuthFile();
74386
+ if (!activeAuth) throw new Error("Active Codex auth is missing. Refresh profiles and try again.");
74387
+ const activeDescription = this.describeActiveAuth(activeAuth);
74388
+ const targetIdentity = this.resolveProfileIdentityFromRecord(record);
74389
+ const targetWorkspaceId = this.normalizeWorkspaceId(record.workspaceId ?? record.data.workspace_id);
74390
+ if (activeDescription.identityKey !== targetIdentity || this.normalizeWorkspaceId(activeDescription.workspaceId) !== targetWorkspaceId) throw new Error(message);
74391
+ }
74392
+ async writeActiveAuthForRecord(record) {
74393
+ const profileData = record.data;
74394
+ const targetIdentity = this.resolveProfileIdentityFromRecord(record);
74395
+ const targetWorkspaceId = this.normalizeWorkspaceId(record.workspaceId ?? profileData.workspace_id);
74396
+ const codexFormat = this.convertToCodexFormat(profileData);
74397
+ await this.writeAtomic(this.activeAuth, JSON.stringify(codexFormat, null, 2));
74398
+ const written = await this.readActiveAuthFile();
74399
+ if (!written) throw new Error("Codex auth file was not written.");
74400
+ const writtenDescription = this.describeActiveAuth(written);
74401
+ if (writtenDescription.identityKey !== targetIdentity) throw new Error("Codex auth verification failed after profile switch.");
74402
+ if (this.normalizeWorkspaceId(writtenDescription.workspaceId) !== targetWorkspaceId) throw new Error("Codex auth workspace verification failed after profile switch.");
74403
+ }
74404
+ async syncActiveAuthFromProfileIfCurrent(name) {
74405
+ const profileName = this.normalizeProfileName(name);
74406
+ const record = await this.getProfileRowByName(profileName);
74407
+ if (!record) return;
74408
+ const activeAuth = await this.readActiveAuthFile();
74409
+ if (!activeAuth) return;
74410
+ const activeDescription = this.describeActiveAuth(activeAuth);
74411
+ const targetIdentity = this.resolveProfileIdentityFromRecord(record);
74412
+ const targetWorkspaceId = this.normalizeWorkspaceId(record.workspaceId ?? record.data.workspace_id);
74413
+ if (activeDescription.identityKey !== targetIdentity || this.normalizeWorkspaceId(activeDescription.workspaceId) !== targetWorkspaceId) return;
74414
+ await this.writeActiveAuthForRecord(record);
74415
+ }
74416
+ async removeActiveAuthFiles() {
74417
+ await promises$1.rm(this.activeAuth, { force: true });
74418
+ await promises$1.rm(this.activeAuthBackup, { force: true });
74419
+ await promises$1.rm(`${this.activeAuth}.tmp`, { force: true });
74420
+ }
74421
+ resolveAutoImportedProfileName(email, existingRecords) {
74422
+ const base = (email ? email.split("@")[0] : "codex-account").trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "codex-account";
74423
+ const used = new Set(existingRecords.map((record) => record.name));
74424
+ let candidate = this.normalizeProfileName(base);
74425
+ let suffix = 2;
74426
+ while (used.has(candidate)) {
74427
+ candidate = this.normalizeProfileName(`${base}-${suffix}`);
74428
+ suffix += 1;
74429
+ }
74430
+ return candidate;
74431
+ }
74432
+ buildActiveCodexAuthStatus(state, description, options) {
74433
+ return {
74434
+ state,
74435
+ profileName: options?.profileName ?? null,
74436
+ email: description?.email ?? null,
74437
+ accountId: description?.accountId ?? null,
74438
+ workspaceId: description?.workspaceId ?? null,
74439
+ workspaceName: description?.workspaceName ?? null,
74440
+ importedProfileName: options?.importedProfileName ?? null,
74441
+ importBlockedReason: options?.importBlockedReason ?? null
74442
+ };
74443
+ }
74322
74444
  resolveWorkspaceIdentity(data, metadata) {
74323
74445
  const directId = "workspace_id" in data && typeof data.workspace_id === "string" ? data.workspace_id.trim() : void 0;
74324
74446
  const directName = "workspace_name" in data && typeof data.workspace_name === "string" ? data.workspace_name.trim() : void 0;
@@ -74343,15 +74465,6 @@ var ProfileManager = class {
74343
74465
  const normalized = this.normalizeProfileName(name);
74344
74466
  return await this.readProfileRecord(normalized) ?? void 0;
74345
74467
  }
74346
- async getProfileRowByIdentityKey(identityKey, options) {
74347
- const matches = (await this.listProfileRecords()).filter((record) => this.resolveProfileIdentityFromRecord(record) === identityKey);
74348
- if (matches.length === 0) return;
74349
- const mustMatch = typeof options?.authMethod === "string" ? options.authMethod.trim().toLowerCase() : null;
74350
- if (mustMatch) return matches.find((record) => this.resolveAuthMethod(record.data) === mustMatch);
74351
- const prefer = typeof options?.preferAuthMethod === "string" ? options.preferAuthMethod.trim().toLowerCase() : null;
74352
- if (prefer) return matches.find((record) => this.resolveAuthMethod(record.data) === prefer) ?? matches[0];
74353
- return matches[0];
74354
- }
74355
74468
  async getProfileRowByIdentityAndWorkspace(identityKey, workspaceId, options) {
74356
74469
  const workspaceKey = workspaceId && workspaceId.trim().length > 0 ? workspaceId.trim() : DEFAULT_WORKSPACE_ID;
74357
74470
  const matches = (await this.listProfileRecords()).filter((record) => {
@@ -74366,6 +74479,9 @@ var ProfileManager = class {
74366
74479
  if (prefer) return matches.find((record) => this.resolveAuthMethod(record.data) === prefer) ?? matches[0];
74367
74480
  return matches[0];
74368
74481
  }
74482
+ normalizeWorkspaceId(value) {
74483
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : DEFAULT_WORKSPACE_ID;
74484
+ }
74369
74485
  async persistProfileRecord(name, data, metadata, options) {
74370
74486
  const resolvedName = this.normalizeProfileName(name);
74371
74487
  const existingRecord = await this.readProfileRecord(resolvedName);
@@ -74413,6 +74529,23 @@ var ProfileManager = class {
74413
74529
  metadata: row.metadata
74414
74530
  });
74415
74531
  }
74532
+ async deleteProfileRecordAndHome(record) {
74533
+ const profileName = this.normalizeProfileName(record.name);
74534
+ if (!record.accountId) {
74535
+ const dashboardState = await this.readProfileDashboardState();
74536
+ delete dashboardState.customGroupsByAccountKey[resolveFallbackAccountKey(profileName)];
74537
+ await this.writeProfileDashboardState(dashboardState);
74538
+ }
74539
+ try {
74540
+ await promises$1.rm(this.getProfileHomePath(profileName), {
74541
+ recursive: true,
74542
+ force: true
74543
+ });
74544
+ } catch (error) {
74545
+ if (!this.isNotFoundError(error)) logWarn(`Failed to remove profile home for '${profileName}':`, error);
74546
+ }
74547
+ await this.deleteProfileRecord(profileName);
74548
+ }
74416
74549
  buildProfileFromData(name, data, fallback) {
74417
74550
  const metadata = fallback?.metadata ?? this.extractProfileMetadata(data);
74418
74551
  const workspace = this.resolveWorkspaceIdentity(data, metadata);
@@ -74464,6 +74597,12 @@ var ProfileManager = class {
74464
74597
  }
74465
74598
  };
74466
74599
  if (normalizedLastRefresh) codexAuth.last_refresh = normalizedLastRefresh;
74600
+ const email = typeof data.email === "string" ? data.email.trim() : "";
74601
+ if (email) codexAuth.email = email;
74602
+ const workspaceId = typeof data.workspace_id === "string" ? data.workspace_id.trim() : "";
74603
+ if (workspaceId) codexAuth.workspace_id = workspaceId;
74604
+ const workspaceName = typeof data.workspace_name === "string" ? data.workspace_name.trim() : "";
74605
+ if (workspaceName) codexAuth.workspace_name = workspaceName;
74467
74606
  return codexAuth;
74468
74607
  }
74469
74608
  /**
@@ -74482,7 +74621,12 @@ var ProfileManager = class {
74482
74621
  const rootLastRefresh = typeof data.last_refresh === "string" ? data.last_refresh.trim() : "";
74483
74622
  const normalizedLastRefresh = tokenLastRefresh || rootLastRefresh;
74484
74623
  if (normalizedLastRefresh) profile.last_refresh = normalizedLastRefresh;
74485
- if (data.email) profile.email = data.email;
74624
+ const email = typeof data.email === "string" ? data.email.trim() : "";
74625
+ if (email) profile.email = email;
74626
+ const workspaceId = typeof data.workspace_id === "string" ? data.workspace_id.trim() : "";
74627
+ if (workspaceId) profile.workspace_id = workspaceId;
74628
+ const workspaceName = typeof data.workspace_name === "string" ? data.workspace_name.trim() : "";
74629
+ if (workspaceName) profile.workspace_name = workspaceName;
74486
74630
  return profile;
74487
74631
  }
74488
74632
  mergeProfileRecords(existing, incoming) {
@@ -74584,6 +74728,10 @@ var ProfileManager = class {
74584
74728
  logWarn(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`);
74585
74729
  return;
74586
74730
  }
74731
+ if (this.normalizeWorkspaceId(existing.workspace_id) !== this.normalizeWorkspaceId(normalized.workspace_id)) {
74732
+ logWarn(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different workspace.`);
74733
+ return;
74734
+ }
74587
74735
  if (!this.hasTokenChanges(existing, normalized)) return;
74588
74736
  const { profile: merged, metadata: mergedMetadata } = this.mergeProfileRecords(existing, normalized);
74589
74737
  try {
@@ -74684,7 +74832,7 @@ var ProfileManager = class {
74684
74832
  if (profileData.auth_method && profileData.auth_method !== "codex-cli") throw new Error("Unsupported auth method. Codex CLI profiles only.");
74685
74833
  const codexFormat = this.convertToCodexFormat(profileData);
74686
74834
  const profileHome = await this.prepareProfileHome(profileName, codexFormat);
74687
- const authPath = path$1.join(profileHome, "auth.json");
74835
+ const authPath = path.join(profileHome, "auth.json");
74688
74836
  const envOverrides = {
74689
74837
  ...process.env,
74690
74838
  HOME: profileHome,
@@ -74738,7 +74886,7 @@ var ProfileManager = class {
74738
74886
  const profileName = this.normalizeProfileName(name);
74739
74887
  const record = await this.getProfileRowByName(profileName);
74740
74888
  if (!record) throw new Error(`Profile '${profileName}' not found!`);
74741
- const authPath = path$1.join(profileHome ?? this.getProfileHomePath(profileName), "auth.json");
74889
+ const authPath = path.join(profileHome ?? this.getProfileHomePath(profileName), "auth.json");
74742
74890
  let finalAuthContent = null;
74743
74891
  try {
74744
74892
  finalAuthContent = await promises$1.readFile(authPath, "utf8");
@@ -74747,79 +74895,70 @@ var ProfileManager = class {
74747
74895
  }
74748
74896
  if (finalAuthContent) {
74749
74897
  await this.syncProfileTokensFromAuthContent(profileName, record.data, finalAuthContent);
74898
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
74750
74899
  return;
74751
74900
  }
74752
74901
  await this.syncProfileTokensFromActiveAuth(profileName, record.data, authPath);
74902
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
74753
74903
  }));
74754
74904
  }
74755
74905
  /**
74756
74906
  * Create a new profile by running codex login
74757
74907
  */
74758
- async createProfile(name) {
74759
- await this.initialize();
74760
- const profileName = this.normalizeProfileName(name);
74761
- if (await this.getProfileRowByName(profileName)) throw new Error(`Profile '${profileName}' already exists!`);
74762
- let authData;
74908
+ async readAuthFileForImport(authPath, actionLabel) {
74909
+ let authRaw;
74763
74910
  try {
74764
- const authRaw = await promises$1.readFile(this.activeAuth, "utf8");
74765
- authData = JSON.parse(authRaw);
74911
+ authRaw = await promises$1.readFile(authPath, "utf8");
74766
74912
  } catch (error) {
74767
- logError("Error creating profile:", error);
74768
- throw new Error("Failed to create profile. Make sure Codex CLI is installed and you are logged in.");
74913
+ if (this.isNotFoundError(error)) throw new Error(`Codex CLI did not produce an auth file. Complete the login before ${actionLabel}.`);
74914
+ logError(`Failed to read Codex auth file during ${actionLabel}:`, error);
74915
+ throw new Error("Failed to read Codex auth file. Complete the login and try again.");
74769
74916
  }
74770
- const normalizedProfile = this.normalizeProfileData(authData);
74771
- normalizedProfile.created_at = normalizedProfile.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
74772
- if (typeof normalizedProfile.auth_method === "string") normalizedProfile.auth_method = normalizedProfile.auth_method.trim().toLowerCase();
74773
- normalizedProfile.auth_method = normalizedProfile.auth_method ?? "codex-cli";
74774
- const metadata = this.extractProfileMetadata(normalizedProfile);
74775
- const workspace = this.resolveWorkspaceIdentity(normalizedProfile, metadata);
74776
- normalizedProfile.workspace_id = normalizedProfile.workspace_id ?? workspace.id ?? DEFAULT_WORKSPACE_ID;
74777
- if (workspace.name) normalizedProfile.workspace_name = normalizedProfile.workspace_name ?? workspace.name;
74778
- const resolvedEmail = this.resolveProfileEmail(normalizedProfile, metadata);
74779
- if (resolvedEmail) normalizedProfile.email = resolvedEmail;
74780
74917
  try {
74781
- await this.persistProfileRecord(profileName, normalizedProfile, metadata);
74782
- const stored = await this.getProfileRowByName(profileName);
74783
- return stored ? this.buildProfileFromRow(stored) : null;
74918
+ return JSON.parse(authRaw);
74784
74919
  } catch (error) {
74785
- logError("Error creating profile:", error);
74786
- throw new Error("Failed to create profile. Make sure Codex CLI is installed and you are logged in.");
74920
+ logError(`Failed to parse Codex auth file during ${actionLabel}:`, error);
74921
+ throw new Error("Failed to parse Codex auth file. Complete the login and try again.");
74787
74922
  }
74788
74923
  }
74789
- /**
74790
- * Switch to a different profile
74791
- */
74792
- async switchToProfile(name) {
74793
- await this.initialize();
74924
+ async persistNewProfileFromAuth(profileName, authData) {
74925
+ const description = this.describeActiveAuth(authData);
74926
+ const normalizedProfile = description.normalized;
74927
+ normalizedProfile.created_at = normalizedProfile.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
74928
+ await this.persistProfileRecord(profileName, normalizedProfile, description.metadata);
74929
+ const stored = await this.getProfileRowByName(profileName);
74930
+ return stored ? this.buildProfileFromRow(stored) : null;
74931
+ }
74932
+ async createProfile(name) {
74794
74933
  const profileName = this.normalizeProfileName(name);
74795
- const record = await this.getProfileRowByName(profileName);
74796
- if (!record) throw new Error(`Profile '${profileName}' not found!`);
74797
- try {
74798
- const profileData = record.data;
74799
- const codexFormat = this.convertToCodexFormat(profileData);
74800
- await this.writeAtomic(this.activeAuth, JSON.stringify(codexFormat, null, 2));
74801
- await this.persistPreferredProfileName(profileName);
74802
- return true;
74803
- } catch (error) {
74804
- logError("Error switching profile:", error);
74805
- throw new Error("Failed to switch profile");
74806
- }
74934
+ return this.enqueueProfileOperation(profileName, () => this.enqueueAuthSwap(async () => {
74935
+ await this.initialize();
74936
+ if (await this.getProfileRowByName(profileName)) throw new Error(`Profile '${profileName}' already exists!`);
74937
+ try {
74938
+ const authData = await this.readAuthFileForImport(this.activeAuth, "profile creation");
74939
+ return await this.persistNewProfileFromAuth(profileName, authData);
74940
+ } catch (error) {
74941
+ logError("Error creating profile:", error);
74942
+ throw new Error("Failed to create profile. Make sure Codex CLI is installed and you are logged in.");
74943
+ }
74944
+ }));
74807
74945
  }
74808
- async refreshProfileAuth(name) {
74809
- await this.initialize();
74946
+ async createProfileFromAuthFile(name, authPath) {
74810
74947
  const profileName = this.normalizeProfileName(name);
74811
- const record = await this.getProfileRowByName(profileName);
74812
- if (!record) throw new Error(`Profile '${profileName}' not found!`);
74948
+ return this.enqueueProfileOperation(profileName, async () => {
74949
+ await this.initialize();
74950
+ if (await this.getProfileRowByName(profileName)) throw new Error(`Profile '${profileName}' already exists!`);
74951
+ try {
74952
+ const authData = await this.readAuthFileForImport(authPath, "profile creation");
74953
+ return await this.persistNewProfileFromAuth(profileName, authData);
74954
+ } catch (error) {
74955
+ logError("Error creating profile:", error);
74956
+ throw new Error("Failed to create profile. Make sure Codex CLI is installed and you are logged in.");
74957
+ }
74958
+ });
74959
+ }
74960
+ async refreshProfileAuthWithData(profileName, record, activeAuth) {
74813
74961
  const existing = record.data;
74814
- let activeAuth;
74815
- try {
74816
- const authContent = await promises$1.readFile(this.activeAuth, "utf8");
74817
- activeAuth = JSON.parse(authContent);
74818
- } catch (error) {
74819
- if (this.isNotFoundError(error)) throw new Error("Codex CLI did not produce an auth file. Complete the login before refreshing this profile.");
74820
- logError("Failed to read Codex auth file during refresh:", error);
74821
- throw new Error("Failed to read Codex auth file. Complete the login and try again.");
74822
- }
74823
74962
  const normalized = this.normalizeProfileData(activeAuth);
74824
74963
  const existingMetadata = record.metadata ?? this.extractProfileMetadata(existing);
74825
74964
  const currentWorkspace = this.resolveWorkspaceIdentity(existing, existingMetadata);
@@ -74852,15 +74991,72 @@ var ProfileManager = class {
74852
74991
  return profile;
74853
74992
  }
74854
74993
  /**
74994
+ * Switch to a different profile
74995
+ */
74996
+ async switchToProfile(name) {
74997
+ const profileName = this.normalizeProfileName(name);
74998
+ return this.enqueueProfileOperation(profileName, () => this.enqueueAuthSwap(async () => {
74999
+ await this.initialize();
75000
+ const record = await this.getProfileRowByName(profileName);
75001
+ if (!record) throw new Error(`Profile '${profileName}' not found!`);
75002
+ try {
75003
+ await this.writeActiveAuthForRecord(record);
75004
+ return true;
75005
+ } catch (error) {
75006
+ logError("Error switching profile:", error);
75007
+ throw new Error("Failed to switch profile");
75008
+ }
75009
+ }));
75010
+ }
75011
+ async refreshProfileAuth(name) {
75012
+ const profileName = this.normalizeProfileName(name);
75013
+ return this.enqueueProfileOperation(profileName, () => this.enqueueAuthSwap(async () => {
75014
+ await this.initialize();
75015
+ const record = await this.getProfileRowByName(profileName);
75016
+ if (!record) throw new Error(`Profile '${profileName}' not found!`);
75017
+ const activeAuth = await this.readAuthFileForImport(this.activeAuth, "profile refresh");
75018
+ return this.refreshProfileAuthWithData(profileName, record, activeAuth);
75019
+ }));
75020
+ }
75021
+ async refreshProfileAuthFromFile(name, authPath) {
75022
+ const profileName = this.normalizeProfileName(name);
75023
+ return this.enqueueProfileOperation(profileName, async () => {
75024
+ await this.initialize();
75025
+ const record = await this.getProfileRowByName(profileName);
75026
+ if (!record) throw new Error(`Profile '${profileName}' not found!`);
75027
+ const activeAuth = await this.readAuthFileForImport(authPath, "profile refresh");
75028
+ const profile = await this.refreshProfileAuthWithData(profileName, record, activeAuth);
75029
+ await this.enqueueAuthSwap(async () => {
75030
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
75031
+ });
75032
+ return profile;
75033
+ });
75034
+ }
75035
+ /**
74855
75036
  * Execute an action with a profile's auth injected.
74856
75037
  * Creates an isolated CODEX_HOME with the profile's auth/config, invokes the action
74857
75038
  * with env overrides, then syncs any refreshed tokens back to the profile.
74858
75039
  */
74859
75040
  async runWithProfileAuth(name, action) {
74860
- return this.enqueueProfileOperation(name, () => this.enqueueAuthSwap(() => this.runWithPreparedProfileHome(name, action, { syncFromActiveAuthBeforeAction: true })));
75041
+ return this.enqueueProfileOperation(name, () => this.enqueueAuthSwap(async () => {
75042
+ try {
75043
+ return await this.runWithPreparedProfileHome(name, action, { syncFromActiveAuthBeforeAction: true });
75044
+ } finally {
75045
+ await this.syncActiveAuthFromProfileIfCurrent(name);
75046
+ }
75047
+ }));
74861
75048
  }
74862
75049
  async readLiveRateLimits(name, options = {}) {
74863
- return this.enqueueProfileOperation(name, () => this.runWithPreparedProfileHome(name, (env) => fetchRateLimitsViaRpc(env, { codexPath: options.codexPath }), { syncFromActiveAuthBeforeAction: false }));
75050
+ const profileName = this.normalizeProfileName(name);
75051
+ try {
75052
+ return await this.enqueueProfileOperation(profileName, () => this.runWithPreparedProfileHome(profileName, (env) => fetchRateLimitsViaRpc(env, { codexPath: options.codexPath }), { syncFromActiveAuthBeforeAction: false }));
75053
+ } finally {
75054
+ await this.enqueueAuthSwap(async () => {
75055
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
75056
+ }).catch((error) => {
75057
+ logWarn(`Failed to sync active auth after rate-limit probe for '${profileName}':`, error);
75058
+ });
75059
+ }
74864
75060
  }
74865
75061
  /**
74866
75062
  * Rename a profile
@@ -74872,8 +75068,6 @@ var ProfileManager = class {
74872
75068
  const existing = await this.getProfileRowByName(sourceName);
74873
75069
  if (!existing) throw new Error(`Profile '${sourceName}' not found!`);
74874
75070
  if (await this.getProfileRowByName(targetName)) throw new Error(`Profile '${targetName}' already exists!`);
74875
- const preferred = await this.readPreferredProfileName();
74876
- if (preferred && preferred === sourceName) await this.persistPreferredProfileName(targetName);
74877
75071
  const oldHome = this.getProfileHomePath(sourceName);
74878
75072
  const newHome = this.getProfileHomePath(targetName);
74879
75073
  try {
@@ -74930,24 +75124,37 @@ var ProfileManager = class {
74930
75124
  const profileName = this.normalizeProfileName(name);
74931
75125
  const record = await this.getProfileRowByName(profileName);
74932
75126
  if (!record) throw new Error(`Profile '${profileName}' not found!`);
74933
- if (!record.accountId) {
74934
- const dashboardState = await this.readProfileDashboardState();
74935
- delete dashboardState.customGroupsByAccountKey[resolveFallbackAccountKey(profileName)];
74936
- await this.writeProfileDashboardState(dashboardState);
74937
- }
74938
- const preferred = await this.readPreferredProfileName();
74939
- if (preferred && preferred === profileName) await this.persistPreferredProfileName(null);
74940
- try {
74941
- await promises$1.rm(this.getProfileHomePath(profileName), {
74942
- recursive: true,
74943
- force: true
74944
- });
74945
- } catch (error) {
74946
- if (!this.isNotFoundError(error)) logWarn(`Failed to remove profile home for '${profileName}':`, error);
74947
- }
74948
- await this.deleteProfileRecord(profileName);
75127
+ await this.deleteProfileRecordAndHome(record);
74949
75128
  return true;
74950
75129
  }
75130
+ async deleteActiveProfileAndSwitch(name, replacementName) {
75131
+ const profileName = this.normalizeProfileName(name);
75132
+ const nextProfileName = this.normalizeProfileName(replacementName);
75133
+ if (profileName === nextProfileName) throw new Error("Replacement profile must be different from the deleted profile.");
75134
+ return this.enqueueAuthSwap(async () => {
75135
+ await this.initialize();
75136
+ const record = await this.getProfileRowByName(profileName);
75137
+ if (!record) throw new Error(`Profile '${profileName}' not found!`);
75138
+ const replacement = await this.getProfileRowByName(nextProfileName);
75139
+ if (!replacement) throw new Error(`Profile '${nextProfileName}' not found!`);
75140
+ await this.assertActiveAuthMatchesRecord(record, "Active Codex login changed before deletion. Refresh profiles and try again.");
75141
+ await this.writeActiveAuthForRecord(replacement);
75142
+ await this.deleteProfileRecordAndHome(record);
75143
+ return true;
75144
+ });
75145
+ }
75146
+ async deleteActiveProfileAndAuth(name) {
75147
+ const profileName = this.normalizeProfileName(name);
75148
+ return this.enqueueAuthSwap(async () => {
75149
+ await this.initialize();
75150
+ const record = await this.getProfileRowByName(profileName);
75151
+ if (!record) throw new Error(`Profile '${profileName}' not found!`);
75152
+ await this.assertActiveAuthMatchesRecord(record, "Active Codex login changed before deletion. Refresh profiles and try again.");
75153
+ await this.removeActiveAuthFiles();
75154
+ await this.deleteProfileRecordAndHome(record);
75155
+ return true;
75156
+ });
75157
+ }
74951
75158
  /**
74952
75159
  * Delete every profile that does not appear in the allow-list.
74953
75160
  * Used to enforce plan limits when local storage was tampered with.
@@ -74977,67 +75184,46 @@ var ProfileManager = class {
74977
75184
  } catch (error) {
74978
75185
  if (!this.isNotFoundError(error)) logWarn(`Failed to remove profile '${name}' during plan enforcement:`, error);
74979
75186
  }
74980
- const preferred = await this.readPreferredProfileName();
74981
- if (preferred && removed.includes(preferred)) await this.persistPreferredProfileName(null);
74982
75187
  return removed;
74983
75188
  }
75189
+ async getActiveCodexAuthStatus(options = {}) {
75190
+ return this.enqueueAuthSwap(async () => {
75191
+ await this.initialize();
75192
+ const activeAuth = await this.readActiveAuthFile();
75193
+ if (!activeAuth) return this.buildActiveCodexAuthStatus("missing");
75194
+ const description = this.describeActiveAuth(activeAuth);
75195
+ if (!description.hasTokenPayload || !description.identityKey) return this.buildActiveCodexAuthStatus("unknown", description);
75196
+ const matching = await this.findProfileForActiveAuth(description.identityKey, description.workspaceId);
75197
+ if (matching) {
75198
+ const normalized = this.normalizeProfileName(matching.name);
75199
+ await this.syncMatchedProfileFromActiveAuth(matching, description);
75200
+ return this.buildActiveCodexAuthStatus("matched", description, { profileName: normalized });
75201
+ }
75202
+ if (!options.autoImport) return this.buildActiveCodexAuthStatus("unmanaged", description);
75203
+ if (options.canAutoImport === false) return this.buildActiveCodexAuthStatus("unmanaged", description, { importBlockedReason: options.importBlockedReason ?? "Profile limit reached. Upgrade to CodexUse Pro or remove a profile before importing this Codex login." });
75204
+ const records = await this.listProfileRecords();
75205
+ const profileName = this.resolveAutoImportedProfileName(description.email, records);
75206
+ const profileData = {
75207
+ ...description.normalized,
75208
+ auth_method: "codex-cli",
75209
+ created_at: description.normalized.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
75210
+ };
75211
+ await this.persistProfileRecord(profileName, profileData, description.metadata, { displayName: description.email ?? profileName });
75212
+ return this.buildActiveCodexAuthStatus("matched", description, {
75213
+ profileName,
75214
+ importedProfileName: profileName
75215
+ });
75216
+ });
75217
+ }
74984
75218
  /**
74985
- * Check if the current auth matches a profile (for smart detection)
75219
+ * Check if the real Codex auth file matches a saved profile.
74986
75220
  */
74987
75221
  async getCurrentProfile() {
74988
- await this.initialize();
74989
- const preferredName = await this.readPreferredProfileName();
74990
- if (preferredName) try {
74991
- const normalizedPreferred = this.normalizeProfileName(preferredName);
74992
- if (await this.getProfileRowByName(normalizedPreferred)) return {
74993
- name: normalizedPreferred,
74994
- trusted: true
74995
- };
74996
- } catch {}
74997
- const activeAuth = await this.readActiveAuthFile();
74998
- if (activeAuth) {
74999
- const normalizedAuth = this.normalizeProfileData(activeAuth);
75000
- const authMetadata = this.extractProfileMetadata(normalizedAuth);
75001
- const workspace = this.resolveWorkspaceIdentity(normalizedAuth, authMetadata);
75002
- const activeIdentityKey = this.resolveProfileIdentityFromData(null, normalizedAuth, authMetadata);
75003
- if (activeIdentityKey) {
75004
- const scoped = await this.getProfileRowByIdentityAndWorkspace(activeIdentityKey, workspace.id ?? DEFAULT_WORKSPACE_ID, { preferAuthMethod: "codex-cli" });
75005
- if (scoped) {
75006
- const normalized = this.normalizeProfileName(scoped.name);
75007
- await this.persistPreferredProfileName(normalized);
75008
- return {
75009
- name: normalized,
75010
- trusted: true
75011
- };
75012
- }
75013
- const matching = await this.getProfileRowByIdentityKey(activeIdentityKey, { preferAuthMethod: "codex-cli" });
75014
- if (matching) {
75015
- const normalized = this.normalizeProfileName(matching.name);
75016
- await this.persistPreferredProfileName(normalized);
75017
- return {
75018
- name: normalized,
75019
- trusted: true
75020
- };
75021
- }
75022
- }
75023
- }
75024
- const preferred = await this.readPreferredProfileName();
75025
- if (preferred) {
75026
- try {
75027
- const normalized = this.normalizeProfileName(preferred);
75028
- if (await this.getProfileRowByName(normalized)) return {
75029
- name: normalized,
75030
- trusted: true
75031
- };
75032
- } catch {
75033
- await this.persistPreferredProfileName(null);
75034
- return {
75035
- name: null,
75036
- trusted: false
75037
- };
75038
- }
75039
- await this.persistPreferredProfileName(null);
75040
- }
75222
+ const status = await this.getActiveCodexAuthStatus({ autoImport: false });
75223
+ if (status.state === "matched" && status.profileName) return {
75224
+ name: status.profileName,
75225
+ trusted: true
75226
+ };
75041
75227
  return {
75042
75228
  name: null,
75043
75229
  trusted: false
@@ -75129,13 +75315,6 @@ var ProfileManager = class {
75129
75315
  if (!this.isNotFoundError(error)) logWarn(`Failed to remove profile '${name}' during cloud sync replace:`, error);
75130
75316
  }
75131
75317
  }
75132
- const preferred = await this.readPreferredProfileName();
75133
- if (preferred) try {
75134
- const normalizedPreferred = this.normalizeProfileName(preferred);
75135
- if (!normalized.has(normalizedPreferred)) await this.persistPreferredProfileName(null);
75136
- } catch {
75137
- await this.persistPreferredProfileName(null);
75138
- }
75139
75318
  return {
75140
75319
  imported: normalized.size,
75141
75320
  removed
@@ -75159,6 +75338,58 @@ async function loadCachedRateLimitSnapshots(keys) {
75159
75338
  return results;
75160
75339
  }
75161
75340
  //#endregion
75341
+ //#region ../../packages/runtime-profiles/src/profiles/profile-visibility.ts
75342
+ function resolveLicenseVisibleProfiles(args) {
75343
+ const { profiles, license, currentProfile } = args;
75344
+ const licenseWithCounts = licenseService.applyProfileCount(license, profiles.length);
75345
+ const enforceProfileLimit = shouldEnforceProfileLimit(licenseWithCounts);
75346
+ const visibleProfiles = enforceProfileLimit ? resolveVisibleProfiles(profiles, licenseWithCounts, currentProfile) : profiles;
75347
+ return {
75348
+ allProfiles: profiles,
75349
+ profiles: licenseWithCounts.isPro ? profiles : visibleProfiles,
75350
+ license,
75351
+ licenseWithCounts,
75352
+ enforceProfileLimit
75353
+ };
75354
+ }
75355
+ async function loadLicenseVisibleProfiles(profileManager, options = {}) {
75356
+ await profileManager.initialize();
75357
+ const license = options.freshLicense ? await licenseService.getStatus() : await licenseService.getCachedStatus();
75358
+ return resolveLicenseVisibleProfiles({
75359
+ profiles: await profileManager.listProfiles(),
75360
+ license,
75361
+ currentProfile: options.currentProfile !== void 0 ? options.currentProfile : (await profileManager.getCurrentProfile()).name
75362
+ });
75363
+ }
75364
+ function resolveVisibleProfiles(profiles, license, currentProfile) {
75365
+ if (license.isPro) return profiles;
75366
+ const limit = typeof license.profileLimit === "number" && license.profileLimit >= 0 ? license.profileLimit : 2;
75367
+ if (profiles.length <= limit) return profiles;
75368
+ const normalizedCurrent = currentProfile ?? null;
75369
+ return profiles.slice().sort((a, b) => compareProfilesForLimit(a, b, normalizedCurrent)).slice(0, limit);
75370
+ }
75371
+ function compareProfilesForLimit(a, b, currentProfile) {
75372
+ if (currentProfile) {
75373
+ if (a.name === currentProfile && b.name !== currentProfile) return -1;
75374
+ if (b.name === currentProfile && a.name !== currentProfile) return 1;
75375
+ }
75376
+ if (Boolean(a.isValid) !== Boolean(b.isValid)) return a.isValid ? -1 : 1;
75377
+ const aTimestamp = parseTimestamp(a.createdAt ?? null);
75378
+ const bTimestamp = parseTimestamp(b.createdAt ?? null);
75379
+ if (aTimestamp !== null && bTimestamp !== null) {
75380
+ if (aTimestamp === bTimestamp) return a.name.localeCompare(b.name);
75381
+ return bTimestamp - aTimestamp;
75382
+ }
75383
+ if (aTimestamp !== null) return -1;
75384
+ if (bTimestamp !== null) return 1;
75385
+ return a.name.localeCompare(b.name);
75386
+ }
75387
+ function parseTimestamp(value) {
75388
+ if (typeof value !== "string" || value.trim() === "") return null;
75389
+ const parsed = Date.parse(value);
75390
+ return Number.isNaN(parsed) ? null : parsed;
75391
+ }
75392
+ //#endregion
75162
75393
  //#region ../../packages/runtime-profiles/src/profiles/rate-limit-probe.ts
75163
75394
  const MIN_REFRESH_INTERVAL_MS = 3 * 6e4;
75164
75395
  const DEFAULT_REFRESH_INTERVAL_MS = 3 * 6e4;
@@ -75552,7 +75783,8 @@ async function executeAccountRefresh(accountId, state, options = {}) {
75552
75783
  async function bootstrapInitialRefresh() {
75553
75784
  try {
75554
75785
  await ensureProfileManagerReady();
75555
- await refreshRateLimitTelemetry(sharedProfileManager, await sharedProfileManager.listProfiles(), /* @__PURE__ */ new Map(), { enqueueAll: true });
75786
+ const { profiles } = await loadLicenseVisibleProfiles(sharedProfileManager);
75787
+ await refreshRateLimitTelemetry(sharedProfileManager, profiles, /* @__PURE__ */ new Map(), { enqueueAll: true });
75556
75788
  emitRefreshState();
75557
75789
  } catch (error) {
75558
75790
  logError("Failed to bootstrap rate-limit refresh queue:", error);
@@ -75589,66 +75821,67 @@ function profileKey(profile) {
75589
75821
  }
75590
75822
  /**
75591
75823
  * Loads profiles and related telemetry for the dashboard.
75592
- * When the user is not on a Pro license and has saved more profiles than allowed,
75593
- * this method prunes unauthorized profiles from disk (best effort; logs errors on failure).
75824
+ * When a free user has more saved profiles than allowed, extra profiles are hidden,
75825
+ * not deleted.
75594
75826
  */
75595
75827
  async function loadProfilesViewData(options = {}) {
75596
75828
  const backgroundRefresh = options.backgroundRefresh !== false;
75597
75829
  await profileManager.initialize();
75598
75830
  const license = await licenseService.getCachedStatus();
75599
75831
  if (backgroundRefresh) licenseService.refreshStatusInBackground();
75600
- const { name: currentProfile, trusted: currentProfileTrusted } = await profileManager.getCurrentProfile();
75832
+ const initialVisible = resolveLicenseVisibleProfiles({
75833
+ profiles: await profileManager.listProfiles(),
75834
+ license,
75835
+ currentProfile: null
75836
+ });
75837
+ const initialLicenseWithCounts = initialVisible.licenseWithCounts;
75838
+ const canAutoImportActiveAuth = !initialVisible.enforceProfileLimit || initialLicenseWithCounts.profilesRemaining === null || initialLicenseWithCounts.profilesRemaining > 0;
75839
+ const activeCodexAuth = await profileManager.getActiveCodexAuthStatus({
75840
+ autoImport: options.autoImportActiveAuth === true,
75841
+ canAutoImport: canAutoImportActiveAuth
75842
+ });
75843
+ const currentProfile = activeCodexAuth.state === "matched" && activeCodexAuth.profileName ? activeCodexAuth.profileName : null;
75844
+ const currentProfileTrusted = Boolean(currentProfile);
75601
75845
  const profiles = await profileManager.listProfiles();
75602
75846
  const customGroupsByAccountKey = await profileManager.getCustomGroupsByAccountKey();
75603
- const licenseWithCounts = licenseService.applyProfileCount(license, profiles.length);
75604
- const visibleProfiles = (typeof shouldEnforceProfileLimit === "function" ? shouldEnforceProfileLimit(licenseWithCounts) : !licenseWithCounts.isPro && licenseWithCounts.state === "inactive" && !licenseWithCounts.error) ? resolveVisibleProfiles(profiles, licenseWithCounts, currentProfile) : profiles;
75605
- const effectiveProfiles = licenseWithCounts.isPro ? profiles : visibleProfiles;
75606
- const rateLimitEnabled = licenseWithCounts.isPro;
75607
- let refreshStatus = {
75608
- pendingAccountIds: [],
75609
- inFlightAccountIds: [],
75610
- cooldownAccountIds: [],
75611
- errors: []
75612
- };
75613
- let enrichedProfiles = effectiveProfiles;
75614
- if (rateLimitEnabled) {
75615
- const rateLimitEligible = effectiveProfiles.filter((profile) => profile.isValid && !profile.tokenStatus?.requiresUserAction);
75616
- const resolvedSnapshots = await resolveRateLimitSnapshots(rateLimitEligible.map((profile) => profileKey(profile)));
75617
- if (backgroundRefresh) {
75618
- ensureRateLimitRefresher();
75619
- await refreshRateLimitTelemetry(profileManager, rateLimitEligible, resolvedSnapshots);
75620
- }
75621
- const refreshState = getRateLimitRefreshState();
75622
- refreshStatus = {
75623
- pendingAccountIds: refreshState.queuedAccountIds.slice(),
75624
- inFlightAccountIds: refreshState.inFlightAccountIds.slice(),
75625
- cooldownAccountIds: refreshState.cooldownAccountIds.slice(),
75626
- lastRefreshStartedAt: refreshState.lastRefreshStartedAt ? new Date(refreshState.lastRefreshStartedAt).toISOString() : void 0,
75627
- lastRefreshCompletedAt: refreshState.lastRefreshCompletedAt ? new Date(refreshState.lastRefreshCompletedAt).toISOString() : void 0,
75628
- errors: (refreshState.errors ?? []).map((issue) => ({
75629
- accountId: issue.accountId,
75630
- profileName: issue.profileName,
75631
- message: issue.message,
75632
- code: issue.code,
75633
- observedAt: issue.observedAt ? new Date(issue.observedAt).toISOString() : void 0
75634
- }))
75635
- };
75636
- const snapshotMap = resolvedSnapshots;
75637
- enrichedProfiles = effectiveProfiles.map((profile) => ({
75638
- ...profile,
75639
- rateLimit: snapshotMap.get(profileKey(profile)) ?? null
75640
- }));
75641
- } else enrichedProfiles = effectiveProfiles.map((profile) => ({
75642
- ...profile,
75643
- rateLimit: null
75644
- }));
75847
+ const visible = resolveLicenseVisibleProfiles({
75848
+ profiles,
75849
+ license,
75850
+ currentProfile
75851
+ });
75852
+ const effectiveProfiles = visible.profiles;
75853
+ const rateLimitEligible = effectiveProfiles.filter((profile) => profile.isValid && !profile.tokenStatus?.requiresUserAction);
75854
+ const resolvedSnapshots = await resolveRateLimitSnapshots(rateLimitEligible.map((profile) => profileKey(profile)));
75855
+ if (backgroundRefresh) {
75856
+ ensureRateLimitRefresher();
75857
+ await refreshRateLimitTelemetry(profileManager, rateLimitEligible, resolvedSnapshots);
75858
+ }
75859
+ const refreshState = getRateLimitRefreshState();
75860
+ const refreshStatus = {
75861
+ pendingAccountIds: refreshState.queuedAccountIds.slice(),
75862
+ inFlightAccountIds: refreshState.inFlightAccountIds.slice(),
75863
+ cooldownAccountIds: refreshState.cooldownAccountIds.slice(),
75864
+ lastRefreshStartedAt: refreshState.lastRefreshStartedAt ? new Date(refreshState.lastRefreshStartedAt).toISOString() : void 0,
75865
+ lastRefreshCompletedAt: refreshState.lastRefreshCompletedAt ? new Date(refreshState.lastRefreshCompletedAt).toISOString() : void 0,
75866
+ errors: (refreshState.errors ?? []).map((issue) => ({
75867
+ accountId: issue.accountId,
75868
+ profileName: issue.profileName,
75869
+ message: issue.message,
75870
+ code: issue.code,
75871
+ observedAt: issue.observedAt ? new Date(issue.observedAt).toISOString() : void 0
75872
+ }))
75873
+ };
75645
75874
  return {
75646
- profiles: enrichedProfiles,
75875
+ profiles: effectiveProfiles.map((profile) => ({
75876
+ ...profile,
75877
+ rateLimit: resolvedSnapshots.get(profileKey(profile)) ?? null
75878
+ })),
75647
75879
  currentProfile,
75648
75880
  currentProfileTrusted,
75649
75881
  refreshStatus,
75650
- license: licenseWithCounts,
75651
- customGroupsByAccountKey
75882
+ license: visible.licenseWithCounts,
75883
+ customGroupsByAccountKey,
75884
+ activeCodexAuth
75652
75885
  };
75653
75886
  }
75654
75887
  async function resolveRateLimitSnapshots(accountIds) {
@@ -75656,34 +75889,6 @@ async function resolveRateLimitSnapshots(accountIds) {
75656
75889
  if (uniqueAccountIds.length === 0) return /* @__PURE__ */ new Map();
75657
75890
  return await loadCachedRateLimitSnapshots(uniqueAccountIds);
75658
75891
  }
75659
- function resolveVisibleProfiles(profiles, license, currentProfile) {
75660
- if (license.isPro) return profiles;
75661
- const limit = typeof license.profileLimit === "number" && license.profileLimit >= 0 ? license.profileLimit : 2;
75662
- if (profiles.length <= limit) return profiles;
75663
- const normalizedCurrent = currentProfile ?? null;
75664
- return profiles.slice().sort((a, b) => compareProfilesForLimit(a, b, normalizedCurrent)).slice(0, limit);
75665
- }
75666
- function compareProfilesForLimit(a, b, currentProfile) {
75667
- if (currentProfile) {
75668
- if (a.name === currentProfile && b.name !== currentProfile) return -1;
75669
- if (b.name === currentProfile && a.name !== currentProfile) return 1;
75670
- }
75671
- if (Boolean(a.isValid) !== Boolean(b.isValid)) return a.isValid ? -1 : 1;
75672
- const aTimestamp = parseTimestamp(a.createdAt ?? null);
75673
- const bTimestamp = parseTimestamp(b.createdAt ?? null);
75674
- if (aTimestamp !== null && bTimestamp !== null) {
75675
- if (aTimestamp === bTimestamp) return a.name.localeCompare(b.name);
75676
- return bTimestamp - aTimestamp;
75677
- }
75678
- if (aTimestamp !== null) return -1;
75679
- if (bTimestamp !== null) return 1;
75680
- return a.name.localeCompare(b.name);
75681
- }
75682
- function parseTimestamp(value) {
75683
- if (typeof value !== "string" || value.trim() === "") return null;
75684
- const parsed = Date.parse(value);
75685
- return Number.isNaN(parsed) ? null : parsed;
75686
- }
75687
75892
  //#endregion
75688
75893
  //#region src/account-pool/service.ts
75689
75894
  const ACCOUNT_POOL_DOCUMENT = "desktop.account-pool.v2";
@@ -75854,7 +76059,7 @@ function sessionTtlMs(affinityKind) {
75854
76059
  }
75855
76060
  }
75856
76061
  function toStoreDbPath(stateDir) {
75857
- return path.join(stateDir, "state.sqlite");
76062
+ return nodePath.join(stateDir, "state.sqlite");
75858
76063
  }
75859
76064
  function createDefaultStore() {
75860
76065
  return {
@@ -77726,15 +77931,19 @@ var LocalAccountPool = class LocalAccountPool {
77726
77931
  return map;
77727
77932
  }
77728
77933
  async resolveProfiles(settings, store) {
77729
- const [profilesView, autoRollSettings] = await Promise.all([loadProfilesViewData({ backgroundRefresh: false }), getStoredAutoRollSettings()]);
77934
+ const [profilesView, autoRollSettings] = await Promise.all([loadProfilesViewData({
77935
+ backgroundRefresh: false,
77936
+ autoImportActiveAuth: false
77937
+ }), getStoredAutoRollSettings()]);
77730
77938
  const selectedProfileNames = settings.accountPoolProfilePool.length > 0 ? settings.accountPoolProfilePool.slice() : profilesView.profiles.map((profile) => profile.name);
77731
77939
  const selectedSet = new Set(selectedProfileNames);
77732
- const switchThreshold = autoRollSettings?.switchThreshold ?? 95;
77940
+ const switchRemainingThreshold = autoRollSettings?.switchRemainingThreshold ?? 5;
77733
77941
  const now = Date.now();
77734
77942
  const activeSessionCounts = this.buildActiveSessionCountMap(store);
77735
77943
  const profileViews = profilesView.profiles.map((profile) => {
77736
77944
  const selected = selectedSet.has(profile.name);
77737
77945
  const usedPercent = maxRateLimitUsedPercent(profile);
77946
+ const remainingPercent = typeof usedPercent === "number" ? Math.max(0, 100 - usedPercent) : null;
77738
77947
  const profileState = store.profilesByName[profile.name];
77739
77948
  const cooldownUntilMs = profileState?.cooldownUntil ? Date.parse(profileState.cooldownUntil) : NaN;
77740
77949
  let reason = null;
@@ -77742,7 +77951,7 @@ var LocalAccountPool = class LocalAccountPool {
77742
77951
  else if (!profile.isValid) reason = "Profile auth is invalid.";
77743
77952
  else if (profile.tokenStatus?.requiresUserAction) reason = profile.tokenStatus.reason ?? "Profile requires re-authentication.";
77744
77953
  else if (Number.isFinite(cooldownUntilMs) && cooldownUntilMs > now) reason = `Cooling down until ${new Date(cooldownUntilMs).toLocaleTimeString()}.`;
77745
- else if (typeof usedPercent === "number" && usedPercent >= switchThreshold) reason = `Rate limit usage is ${Math.round(usedPercent)}%.`;
77954
+ else if (remainingPercent !== null && remainingPercent <= switchRemainingThreshold) reason = `Rate limit remaining is ${Math.round(remainingPercent)}%.`;
77746
77955
  return {
77747
77956
  name: profile.name,
77748
77957
  displayName: profile.displayName ?? null,
@@ -78213,14 +78422,14 @@ function extractIconHref(source) {
78213
78422
  }
78214
78423
  function resolveIconHref(projectCwd, href) {
78215
78424
  const clean = href.replace(/^\//, "");
78216
- return [path.join(projectCwd, "public", clean), path.join(projectCwd, clean)];
78425
+ return [nodePath.join(projectCwd, "public", clean), nodePath.join(projectCwd, clean)];
78217
78426
  }
78218
78427
  function isPathWithinProject(projectCwd, candidatePath) {
78219
- const relative = path.relative(path.resolve(projectCwd), path.resolve(candidatePath));
78220
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
78428
+ const relative = nodePath.relative(nodePath.resolve(projectCwd), nodePath.resolve(candidatePath));
78429
+ return relative === "" || !relative.startsWith("..") && !nodePath.isAbsolute(relative);
78221
78430
  }
78222
78431
  function serveFaviconFile(filePath, res) {
78223
- const contentType = FAVICON_MIME_TYPES[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
78432
+ const contentType = FAVICON_MIME_TYPES[nodePath.extname(filePath).toLowerCase()] ?? "application/octet-stream";
78224
78433
  fs.readFile(filePath, (readErr, data) => {
78225
78434
  if (readErr) {
78226
78435
  res.writeHead(500, { "Content-Type": "text/plain" });
@@ -78272,7 +78481,7 @@ function tryHandleProjectFaviconRequest(url, res) {
78272
78481
  serveFallbackFavicon(res);
78273
78482
  return;
78274
78483
  }
78275
- const sourceFile = path.join(projectCwd, ICON_SOURCE_FILES[index]);
78484
+ const sourceFile = nodePath.join(projectCwd, ICON_SOURCE_FILES[index]);
78276
78485
  fs.readFile(sourceFile, "utf8", (err, content) => {
78277
78486
  if (err) {
78278
78487
  trySourceFiles(index + 1);
@@ -78291,7 +78500,7 @@ function tryHandleProjectFaviconRequest(url, res) {
78291
78500
  trySourceFiles(0);
78292
78501
  return;
78293
78502
  }
78294
- const candidate = path.join(projectCwd, FAVICON_CANDIDATES[index]);
78503
+ const candidate = nodePath.join(projectCwd, FAVICON_CANDIDATES[index]);
78295
78504
  if (!isPathWithinProject(projectCwd, candidate)) {
78296
78505
  tryCandidates(index + 1);
78297
78506
  return;
@@ -78458,18 +78667,18 @@ const REGISTRY_TIMEOUT_MS = 1e4;
78458
78667
  let freshnessCache = null;
78459
78668
  function buildEnv() {
78460
78669
  const env = { ...process.env };
78461
- const homeDir = env.HOME ?? env.USERPROFILE ?? os.homedir();
78670
+ const homeDir = env.HOME ?? env.USERPROFILE ?? nodeOs.homedir();
78462
78671
  const extra = [
78463
78672
  "/opt/homebrew/bin",
78464
78673
  "/usr/local/bin",
78465
- path.join(homeDir, ".local", "bin"),
78466
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
78467
- path.join(homeDir, ".fnm", "current", "bin"),
78468
- path.join(homeDir, ".volta", "bin"),
78469
- path.join(homeDir, ".asdf", "shims")
78674
+ nodePath.join(homeDir, ".local", "bin"),
78675
+ nodePath.join(homeDir, ".fnm", "aliases", "default", "bin"),
78676
+ nodePath.join(homeDir, ".fnm", "current", "bin"),
78677
+ nodePath.join(homeDir, ".volta", "bin"),
78678
+ nodePath.join(homeDir, ".asdf", "shims")
78470
78679
  ];
78471
- const segments = [...(env.PATH ?? "").split(path.delimiter).filter(Boolean), ...extra];
78472
- env.PATH = Array.from(new Set(segments)).join(path.delimiter);
78680
+ const segments = [...(env.PATH ?? "").split(nodePath.delimiter).filter(Boolean), ...extra];
78681
+ env.PATH = Array.from(new Set(segments)).join(nodePath.delimiter);
78473
78682
  return env;
78474
78683
  }
78475
78684
  function runCommand$2(command, args, env) {
@@ -78481,7 +78690,7 @@ function runCommand$2(command, args, env) {
78481
78690
  function resolveRunnableCommand(command, env, shellInfo) {
78482
78691
  const resolved = resolveCommandPath(command, env, shellInfo)?.trim();
78483
78692
  if (!resolved) return command;
78484
- if (resolved.includes(path.sep) || resolved.includes("/")) return resolved;
78693
+ if (resolved.includes(nodePath.sep) || resolved.includes("/")) return resolved;
78485
78694
  return command;
78486
78695
  }
78487
78696
  function resolveShell() {
@@ -78811,15 +79020,15 @@ function isMissingPathError(error) {
78811
79020
  return error.code === "ENOENT";
78812
79021
  }
78813
79022
  function resolveHomeDir() {
78814
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
79023
+ const home = process.env.HOME || process.env.USERPROFILE || nodeOs.homedir();
78815
79024
  if (!home) throw new Error("HOME is not set.");
78816
79025
  return home;
78817
79026
  }
78818
79027
  function resolveCodexDir() {
78819
- return path.join(resolveHomeDir(), ".codex");
79028
+ return nodePath.join(resolveHomeDir(), ".codex");
78820
79029
  }
78821
79030
  function resolveLegacyPath(...segments) {
78822
- return path.join(resolveCodexDir(), ...segments);
79031
+ return nodePath.join(resolveCodexDir(), ...segments);
78823
79032
  }
78824
79033
  function pickAutoRoll(raw) {
78825
79034
  if (!raw) return null;
@@ -78888,9 +79097,13 @@ function mergeLegacyLocalStoragePatch(payload) {
78888
79097
  if (autoRoll) {
78889
79098
  patch.autoRoll = {
78890
79099
  enabled: autoRoll.enabled,
78891
- warningThreshold: autoRoll.warningThreshold,
78892
- switchThreshold: autoRoll.switchThreshold,
78893
- restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll
79100
+ rearmRemainingThreshold: autoRoll.rearmRemainingThreshold,
79101
+ switchRemainingThreshold: autoRoll.switchRemainingThreshold,
79102
+ restartOfficialCodexOnAutoRoll: autoRoll.restartOfficialCodexOnAutoRoll,
79103
+ launchOfficialCodexWhenClosedOnAutoRoll: autoRoll.launchOfficialCodexWhenClosedOnAutoRoll,
79104
+ priorityOrder: autoRoll.priorityOrder,
79105
+ lowRemainingNotificationEnabled: autoRoll.lowRemainingNotificationEnabled,
79106
+ lowRemainingNotificationThreshold: autoRoll.lowRemainingNotificationThreshold
78894
79107
  };
78895
79108
  markSkippedIfPresent("codex:auto-roll-settings", true);
78896
79109
  } else markSkippedIfPresent("codex:auto-roll-settings", false);
@@ -78909,8 +79122,8 @@ async function removeIfExists(target) {
78909
79122
  async function cleanupLegacyCanonicalSources() {
78910
79123
  const homeDir = resolveHomeDir();
78911
79124
  await removeIfExists(resolveLegacyAppStatePath());
78912
- await removeIfExists(path.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE));
78913
- await removeIfExists(path.join(homeDir, SQLITE_STORAGE_DIR));
79125
+ await removeIfExists(nodePath.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE));
79126
+ await removeIfExists(nodePath.join(homeDir, SQLITE_STORAGE_DIR));
78914
79127
  await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_FILE));
78915
79128
  await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_BACKUP_FILE));
78916
79129
  await removeIfExists(resolveLegacyPath(LEGACY_SYNC_STATE_FILE));
@@ -78921,7 +79134,7 @@ async function cleanupLegacyCanonicalSources() {
78921
79134
  const profileHomes = await promises.readdir(profileHomesRoot, { withFileTypes: true });
78922
79135
  for (const profileHome of profileHomes) {
78923
79136
  if (!profileHome.isDirectory()) continue;
78924
- const profileDir = path.join(profileHomesRoot, profileHome.name);
79137
+ const profileDir = nodePath.join(profileHomesRoot, profileHome.name);
78925
79138
  let files = [];
78926
79139
  try {
78927
79140
  files = await promises.readdir(profileDir);
@@ -78930,7 +79143,7 @@ async function cleanupLegacyCanonicalSources() {
78930
79143
  }
78931
79144
  for (const file of files) {
78932
79145
  if (!file.startsWith("profile.json")) continue;
78933
- await removeIfExists(path.join(profileDir, file));
79146
+ await removeIfExists(nodePath.join(profileDir, file));
78934
79147
  }
78935
79148
  }
78936
79149
  } catch (error) {
@@ -78944,7 +79157,7 @@ async function cleanupLegacyCanonicalSources() {
78944
79157
  const skillDirs = await promises.readdir(skillsRoot, { withFileTypes: true });
78945
79158
  for (const entry of skillDirs) {
78946
79159
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
78947
- await removeIfExists(path.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST));
79160
+ await removeIfExists(nodePath.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST));
78948
79161
  }
78949
79162
  } catch (error) {
78950
79163
  if (!isMissingPathError(error)) logWarn("Failed cleaning legacy skills metadata:", {
@@ -79025,19 +79238,19 @@ async function importLegacyLocalStorageOnce(payload) {
79025
79238
  //#region ../../packages/runtime-integrations/src/agents/service.ts
79026
79239
  const AGENTS_FILENAME = "AGENTS.md";
79027
79240
  function resolveGlobalPath(options) {
79028
- return path.join(resolveCodexHomeDir(options), AGENTS_FILENAME);
79241
+ return nodePath.join(resolveCodexHomeDir(options), AGENTS_FILENAME);
79029
79242
  }
79030
79243
  function getCodexDir(options) {
79031
79244
  return resolveCodexHomeDir(options);
79032
79245
  }
79033
79246
  function isPathSafe(normalizedPath, options) {
79034
- if (path.basename(normalizedPath) !== AGENTS_FILENAME) return false;
79247
+ if (nodePath.basename(normalizedPath) !== AGENTS_FILENAME) return false;
79035
79248
  const codexDir = getCodexDir(options);
79036
79249
  const homeDir = resolveHomeDir$1();
79037
- if (normalizedPath === path.join(codexDir, AGENTS_FILENAME)) return true;
79038
- const parentDir = path.dirname(normalizedPath);
79039
- if (!parentDir.startsWith(homeDir + path.sep) && parentDir !== homeDir) return false;
79040
- if (parentDir.startsWith(codexDir + path.sep) || parentDir === codexDir) return false;
79250
+ if (normalizedPath === nodePath.join(codexDir, AGENTS_FILENAME)) return true;
79251
+ const parentDir = nodePath.dirname(normalizedPath);
79252
+ if (!parentDir.startsWith(homeDir + nodePath.sep) && parentDir !== homeDir) return false;
79253
+ if (parentDir.startsWith(codexDir + nodePath.sep) || parentDir === codexDir) return false;
79041
79254
  return true;
79042
79255
  }
79043
79256
  async function statFile(target) {
@@ -79060,7 +79273,7 @@ async function readFileContent(target) {
79060
79273
  }
79061
79274
  }
79062
79275
  async function ensureDirForFile(target) {
79063
- const dir = path.dirname(target);
79276
+ const dir = nodePath.dirname(target);
79064
79277
  await promises.mkdir(dir, { recursive: true });
79065
79278
  }
79066
79279
  async function saveFileContent(target, content) {
@@ -79075,7 +79288,7 @@ function normalizeProjectPath(candidate) {
79075
79288
  if (!candidate || typeof candidate !== "string") return null;
79076
79289
  const trimmed = candidate.trim();
79077
79290
  if (!trimmed) return null;
79078
- return path.resolve(trimmed);
79291
+ return nodePath.resolve(trimmed);
79079
79292
  }
79080
79293
  async function buildDescriptor(params) {
79081
79294
  const stats = await statFile(params.path);
@@ -79101,7 +79314,7 @@ async function readPreferredContent(entries) {
79101
79314
  async function getAgentsSnapshot(projectPath, options) {
79102
79315
  const normalizedProject = normalizeProjectPath(projectPath);
79103
79316
  const globalPath = resolveGlobalPath(options);
79104
- const projectPathFile = normalizedProject ? path.join(normalizedProject, AGENTS_FILENAME) : null;
79317
+ const projectPathFile = normalizedProject ? nodePath.join(normalizedProject, AGENTS_FILENAME) : null;
79105
79318
  const globalEntry = await buildDescriptor({
79106
79319
  path: globalPath,
79107
79320
  type: "global",
@@ -79123,7 +79336,7 @@ async function getAgentsSnapshot(projectPath, options) {
79123
79336
  };
79124
79337
  }
79125
79338
  async function readAgentsFile(target, options) {
79126
- const normalized = path.resolve(target);
79339
+ const normalized = nodePath.resolve(target);
79127
79340
  if (!isPathSafe(normalized, options)) throw new Error("Access denied: path outside allowed directories");
79128
79341
  const content = await readFileContent(normalized);
79129
79342
  return {
@@ -79133,7 +79346,7 @@ async function readAgentsFile(target, options) {
79133
79346
  };
79134
79347
  }
79135
79348
  async function saveAgentsFile(target, content, options) {
79136
- const normalized = path.resolve(target);
79349
+ const normalized = nodePath.resolve(target);
79137
79350
  if (!isPathSafe(normalized, options)) throw new Error("Access denied: path outside allowed directories");
79138
79351
  await saveFileContent(normalized, content ?? "");
79139
79352
  return readAgentsFile(normalized, options);
@@ -79333,7 +79546,7 @@ function formatMegabytes(bytes) {
79333
79546
  return (bytes / MB_DIVISOR).toFixed(2);
79334
79547
  }
79335
79548
  function resolveSyncBackupsDir() {
79336
- return path.join(getUserDataDir(), SYNC_BACKUP_DIR);
79549
+ return nodePath.join(getUserDataDir(), SYNC_BACKUP_DIR);
79337
79550
  }
79338
79551
  function toSafeIsoForFileName(iso) {
79339
79552
  return iso.replace(/:/g, "-");
@@ -79341,14 +79554,14 @@ function toSafeIsoForFileName(iso) {
79341
79554
  async function pruneOldPrePullBackups(backupsDir) {
79342
79555
  const files = (await promises.readdir(backupsDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.startsWith(PRE_PULL_BACKUP_PREFIX) && entry.name.endsWith(PRE_PULL_BACKUP_SUFFIX)).map((entry) => entry.name).sort((a, b) => b.localeCompare(a));
79343
79556
  if (files.length <= PRE_PULL_BACKUP_KEEP_COUNT) return;
79344
- for (const stale of files.slice(PRE_PULL_BACKUP_KEEP_COUNT)) await promises.rm(path.join(backupsDir, stale), { force: true });
79557
+ for (const stale of files.slice(PRE_PULL_BACKUP_KEEP_COUNT)) await promises.rm(nodePath.join(backupsDir, stale), { force: true });
79345
79558
  }
79346
79559
  async function createPrePullBackup(profileManager) {
79347
79560
  const snapshot = await buildLocalSnapshot(profileManager, { enforcePushGuards: false });
79348
79561
  const backupsDir = resolveSyncBackupsDir();
79349
79562
  await promises.mkdir(backupsDir, { recursive: true });
79350
79563
  const timestamp = toSafeIsoForFileName((/* @__PURE__ */ new Date()).toISOString());
79351
- const backupPath = path.join(backupsDir, `${PRE_PULL_BACKUP_PREFIX}${timestamp}${PRE_PULL_BACKUP_SUFFIX}`);
79564
+ const backupPath = nodePath.join(backupsDir, `${PRE_PULL_BACKUP_PREFIX}${timestamp}${PRE_PULL_BACKUP_SUFFIX}`);
79352
79565
  await promises.writeFile(backupPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
79353
79566
  await pruneOldPrePullBackups(backupsDir);
79354
79567
  logInfo("[cloud-sync] created pre-pull backup", { backupPath });
@@ -79582,17 +79795,17 @@ function buildRuntimeEnv(codexHomePath) {
79582
79795
  ...process.env,
79583
79796
  ...codexHomePath?.trim() ? { CODEX_HOME: codexHomePath.trim() } : {}
79584
79797
  };
79585
- const homeDir = env.HOME ?? env.USERPROFILE ?? os.homedir();
79798
+ const homeDir = env.HOME ?? env.USERPROFILE ?? nodeOs.homedir();
79586
79799
  const extraPaths = [
79587
79800
  "/opt/homebrew/bin",
79588
79801
  "/usr/local/bin",
79589
- path.join(homeDir, ".local", "bin"),
79590
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
79591
- path.join(homeDir, ".fnm", "current", "bin"),
79592
- path.join(homeDir, ".volta", "bin"),
79593
- path.join(homeDir, ".asdf", "shims")
79802
+ nodePath.join(homeDir, ".local", "bin"),
79803
+ nodePath.join(homeDir, ".fnm", "aliases", "default", "bin"),
79804
+ nodePath.join(homeDir, ".fnm", "current", "bin"),
79805
+ nodePath.join(homeDir, ".volta", "bin"),
79806
+ nodePath.join(homeDir, ".asdf", "shims")
79594
79807
  ];
79595
- env.PATH = Array.from(new Set([...(env.PATH ?? "").split(path.delimiter), ...extraPaths].filter(Boolean))).join(path.delimiter);
79808
+ env.PATH = Array.from(new Set([...(env.PATH ?? "").split(nodePath.delimiter), ...extraPaths].filter(Boolean))).join(nodePath.delimiter);
79596
79809
  return env;
79597
79810
  }
79598
79811
  function createTimeoutError(command, args) {
@@ -79986,6 +80199,7 @@ const AUTO_ROLL_ATTEMPT_TTL_MS = 3e4;
79986
80199
  const EMPTY_USAGE_SUMMARY = {
79987
80200
  windowKey: null,
79988
80201
  usagePercent: 0,
80202
+ remainingPercent: 100,
79989
80203
  hasUsage: false,
79990
80204
  resetsInSeconds: null,
79991
80205
  windowMinutes: null
@@ -80018,12 +80232,14 @@ function summarizeRateLimitSnapshot(snapshot) {
80018
80232
  const rawUsedPercent = finiteNumberOrNull(window.usedPercent);
80019
80233
  const hasUsage = rawUsedPercent !== null;
80020
80234
  const usagePercent = hasUsage ? Math.max(0, Math.min(100, rawUsedPercent)) : 0;
80235
+ const remainingPercent = 100 - usagePercent;
80021
80236
  const resetsInSeconds = resolveResetsInSeconds(window);
80022
80237
  const windowMinutes = finiteNumberOrNull(window.windowMinutes);
80023
80238
  if (!summary.windowKey) {
80024
80239
  summary = {
80025
80240
  windowKey: entry.key,
80026
80241
  usagePercent,
80242
+ remainingPercent,
80027
80243
  hasUsage,
80028
80244
  resetsInSeconds,
80029
80245
  windowMinutes
@@ -80034,6 +80250,7 @@ function summarizeRateLimitSnapshot(snapshot) {
80034
80250
  summary = {
80035
80251
  windowKey: entry.key,
80036
80252
  usagePercent,
80253
+ remainingPercent,
80037
80254
  hasUsage,
80038
80255
  resetsInSeconds,
80039
80256
  windowMinutes
@@ -80044,6 +80261,7 @@ function summarizeRateLimitSnapshot(snapshot) {
80044
80261
  summary = {
80045
80262
  windowKey: entry.key,
80046
80263
  usagePercent,
80264
+ remainingPercent,
80047
80265
  hasUsage,
80048
80266
  resetsInSeconds,
80049
80267
  windowMinutes
@@ -80054,6 +80272,7 @@ function summarizeRateLimitSnapshot(snapshot) {
80054
80272
  if (resetsInSeconds !== null && resetsInSeconds < summary.resetsInSeconds) summary = {
80055
80273
  windowKey: entry.key,
80056
80274
  usagePercent,
80275
+ remainingPercent,
80057
80276
  hasUsage,
80058
80277
  resetsInSeconds,
80059
80278
  windowMinutes
@@ -80063,6 +80282,7 @@ function summarizeRateLimitSnapshot(snapshot) {
80063
80282
  if (!hasUsage && !summary.hasUsage && summary.resetsInSeconds === null && resetsInSeconds !== null) summary = {
80064
80283
  windowKey: entry.key,
80065
80284
  usagePercent,
80285
+ remainingPercent,
80066
80286
  hasUsage,
80067
80287
  resetsInSeconds,
80068
80288
  windowMinutes
@@ -80070,26 +80290,42 @@ function summarizeRateLimitSnapshot(snapshot) {
80070
80290
  }
80071
80291
  return summary;
80072
80292
  }
80073
- function compareAutoRollCandidates(a, b) {
80074
- if (a.usageSummary.usagePercent !== b.usageSummary.usagePercent) return a.usageSummary.usagePercent - b.usageSummary.usagePercent;
80293
+ function buildPriorityRanks(priorityOrder) {
80294
+ const ranks = /* @__PURE__ */ new Map();
80295
+ for (const [index, key] of priorityOrder.entries()) {
80296
+ const normalized = typeof key === "string" ? key.trim() : "";
80297
+ if (!normalized || ranks.has(normalized)) continue;
80298
+ ranks.set(normalized, index);
80299
+ }
80300
+ return ranks;
80301
+ }
80302
+ function compareAutoRollCandidates(a, b, priorityRanks) {
80303
+ const aRank = priorityRanks.get(resolveProfileIdentityKeyFromProfile(a.profile));
80304
+ const bRank = priorityRanks.get(resolveProfileIdentityKeyFromProfile(b.profile));
80305
+ if (aRank !== void 0 || bRank !== void 0) {
80306
+ if (aRank === void 0) return 1;
80307
+ if (bRank === void 0) return -1;
80308
+ if (aRank !== bRank) return aRank - bRank;
80309
+ }
80310
+ if (a.usageSummary.remainingPercent !== b.usageSummary.remainingPercent) return b.usageSummary.remainingPercent - a.usageSummary.remainingPercent;
80075
80311
  const aReset = a.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
80076
80312
  const bReset = b.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
80077
80313
  if (aReset !== bReset) return aReset - bReset;
80078
80314
  return a.profile.name.localeCompare(b.profile.name);
80079
80315
  }
80080
- function computeAutoRollCandidate(profiles, usageSummaries, currentProfileName, currentSummary, switchThreshold) {
80081
- const candidates = profiles.filter((profile) => profile.name !== currentProfileName && profile.isValid && !profile.tokenStatus?.requiresUserAction).map((profile) => ({
80316
+ function computeAutoRollCandidate(profiles, usageSummaries, currentProfileName, currentSummary, switchRemainingThreshold, priorityOrder = [], blockedProfileNames = /* @__PURE__ */ new Set()) {
80317
+ const priorityRanks = buildPriorityRanks(priorityOrder);
80318
+ const candidates = profiles.filter((profile) => profile.name !== currentProfileName && !blockedProfileNames.has(profile.name) && profile.isValid && !profile.tokenStatus?.requiresUserAction).map((profile) => ({
80082
80319
  profile,
80083
80320
  usageSummary: usageSummaries.get(profile.name) ?? summarizeRateLimitSnapshot(profile.rateLimit ?? null)
80084
80321
  }));
80085
80322
  if (candidates.length === 0) return null;
80086
- let selected = candidates.filter((candidate) => candidate.usageSummary.hasUsage && candidate.usageSummary.usagePercent < switchThreshold).sort(compareAutoRollCandidates)[0] ?? null;
80087
- if (!selected) selected = candidates.filter((candidate) => candidate.usageSummary.hasUsage).sort(compareAutoRollCandidates)[0] ?? null;
80088
- if (!selected) selected = candidates.filter((candidate) => !candidate.usageSummary.hasUsage).sort(compareAutoRollCandidates)[0] ?? null;
80323
+ let selected = candidates.filter((candidate) => candidate.usageSummary.hasUsage && candidate.usageSummary.remainingPercent > switchRemainingThreshold).sort((a, b) => compareAutoRollCandidates(a, b, priorityRanks))[0] ?? null;
80324
+ if (!selected) selected = candidates.filter((candidate) => !candidate.usageSummary.hasUsage).sort((a, b) => compareAutoRollCandidates(a, b, priorityRanks))[0] ?? null;
80089
80325
  if (!selected) return null;
80090
80326
  if (selected.usageSummary.hasUsage && currentSummary.hasUsage) {
80091
- if (selected.usageSummary.usagePercent > currentSummary.usagePercent) return null;
80092
- if (selected.usageSummary.usagePercent === currentSummary.usagePercent) {
80327
+ if (selected.usageSummary.remainingPercent < currentSummary.remainingPercent) return null;
80328
+ if (selected.usageSummary.remainingPercent === currentSummary.remainingPercent) {
80093
80329
  if ((selected.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY) >= (currentSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY)) return null;
80094
80330
  }
80095
80331
  }
@@ -81812,12 +82048,12 @@ var TelegramBridge = class {
81812
82048
  }
81813
82049
  buildRuntimeLockPath(token) {
81814
82050
  const tokenHash = createHash("sha1").update(token).digest("hex").slice(0, 16);
81815
- return path.join(getUserDataDir(), `${TELEGRAM_BRIDGE_LOCK_FILE_PREFIX}-${tokenHash}.lock`);
82051
+ return nodePath.join(getUserDataDir(), `${TELEGRAM_BRIDGE_LOCK_FILE_PREFIX}-${tokenHash}.lock`);
81816
82052
  }
81817
82053
  async acquireRuntimeLock(token) {
81818
82054
  if (this.runtimeLockPath) return;
81819
82055
  const lockPath = this.buildRuntimeLockPath(token);
81820
- await fs$1.mkdir(path.dirname(lockPath), { recursive: true });
82056
+ await fs$1.mkdir(nodePath.dirname(lockPath), { recursive: true });
81821
82057
  const payload = JSON.stringify({
81822
82058
  pid: process.pid,
81823
82059
  parentPid: process.ppid > 0 ? process.ppid : null,
@@ -82037,13 +82273,26 @@ function createTelegramBridge(options) {
82037
82273
  return new TelegramBridge(options);
82038
82274
  }
82039
82275
  //#endregion
82040
- //#region src/codex/officialAppRestart.ts
82276
+ //#region ../../packages/runtime-codex/src/codex/officialAppRestart.ts
82041
82277
  const RESTART_COOLDOWN_MS = 6e4;
82042
82278
  const COMMAND_TIMEOUT_MS = 3e3;
82043
- const QUIT_WAIT_MS = 1e4;
82279
+ const EXIT_WAIT_MS = 1e4;
82280
+ const LAUNCH_WAIT_MS = 15e3;
82044
82281
  const POLL_INTERVAL_MS = 250;
82282
+ const APP_DISCOVERY_CACHE_TTL_MS = 6e4;
82283
+ const APP_DISCOVERY_MISS_CACHE_TTL_MS = 5e3;
82045
82284
  let restartPromise = null;
82046
82285
  let lastRestartStartedAt = 0;
82286
+ let profileActionLock = Promise.resolve();
82287
+ let appDiscoveryCache = null;
82288
+ function logOfficialCodexRestart(level, message, context) {
82289
+ const logger = level === "warn" ? console.warn : level === "error" ? console.error : console.info;
82290
+ if (context && Object.keys(context).length > 0) {
82291
+ logger(`[official-codex-restart] ${message}`, context);
82292
+ return;
82293
+ }
82294
+ logger(`[official-codex-restart] ${message}`);
82295
+ }
82047
82296
  function runCommand(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
82048
82297
  return new Promise((resolve) => {
82049
82298
  const child = spawn(command, args, { stdio: [
@@ -82084,15 +82333,252 @@ function runCommand(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
82084
82333
  });
82085
82334
  });
82086
82335
  }
82087
- function appleScriptString(value) {
82088
- return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
82336
+ function enqueueProfileAction(work) {
82337
+ const run = profileActionLock.then(work, work);
82338
+ profileActionLock = run.then(() => void 0, () => void 0);
82339
+ return run;
82340
+ }
82341
+ async function readProcessRows() {
82342
+ const result = await runCommand("/bin/ps", ["-axo", "pid=,ppid=,lstart=,args="], 2e3);
82343
+ if (result.code !== 0) return [];
82344
+ return result.stdout.split(/\r?\n/).flatMap((line) => {
82345
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+([A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d+\s+\d+:\d+:\d+\s+\d{4})\s+(.+)$/);
82346
+ if (!match) return [];
82347
+ const pid = Number(match[1]);
82348
+ const ppid = Number(match[2]);
82349
+ const parsedStartedAt = Date.parse(match[3] ?? "");
82350
+ const startedAt = Number.isFinite(parsedStartedAt) ? parsedStartedAt : null;
82351
+ const args = match[4] ?? "";
82352
+ if (!Number.isInteger(pid) || !Number.isInteger(ppid) || !args) return [];
82353
+ return [{
82354
+ pid,
82355
+ ppid,
82356
+ startedAt,
82357
+ args
82358
+ }];
82359
+ });
82360
+ }
82361
+ function getMainExecutablePath(candidate) {
82362
+ return nodePath.join(candidate.appPath, "Contents", "MacOS", candidate.executableName);
82363
+ }
82364
+ function isMainProcessRow(row, candidate) {
82365
+ return row.args.includes(getMainExecutablePath(candidate));
82366
+ }
82367
+ function findAppServerPid(rows, mainPid) {
82368
+ return rows.find((row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server"))?.pid ?? null;
82369
+ }
82370
+ async function processMatchesProfileHome(pid, profileHome) {
82371
+ const result = await runCommand("/bin/ps", [
82372
+ "eww",
82373
+ "-p",
82374
+ String(pid),
82375
+ "-o",
82376
+ "command="
82377
+ ], 2e3);
82378
+ if (result.code !== 0) return false;
82379
+ return result.stdout.includes(`CODEX_HOME=${profileHome}`) || result.stdout.includes(profileHome);
82380
+ }
82381
+ async function findAppServerPidForProfile(rows, mainPid, profileHome) {
82382
+ const candidates = rows.filter((row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server"));
82383
+ for (const row of candidates) if (row.args.includes(profileHome) || await processMatchesProfileHome(row.pid, profileHome)) return row.pid;
82384
+ return null;
82385
+ }
82386
+ async function describeUnmanagedProfileHint(appServerPid) {
82387
+ if (!appServerPid) return {};
82388
+ const result = await runCommand("/bin/ps", [
82389
+ "eww",
82390
+ "-p",
82391
+ String(appServerPid),
82392
+ "-o",
82393
+ "command="
82394
+ ], 2e3);
82395
+ if (result.code !== 0) return {};
82396
+ const telemetryMatch = result.stdout.match(/CODEX_TELEMETRY_LABEL=codexuse-profile-([^\s]+)/);
82397
+ if (telemetryMatch?.[1]) {
82398
+ const encodedProfileName = telemetryMatch[1];
82399
+ let profileName = encodedProfileName;
82400
+ try {
82401
+ profileName = decodeURIComponent(encodedProfileName);
82402
+ } catch {
82403
+ profileName = encodedProfileName;
82404
+ }
82405
+ return {
82406
+ profileName,
82407
+ profileMatchSource: "telemetry-label"
82408
+ };
82409
+ }
82410
+ const profileHomeMatch = result.stdout.match(/profile-homes\/([^\s]+)/);
82411
+ if (profileHomeMatch?.[1]) return {
82412
+ profileName: profileHomeMatch[1],
82413
+ profileMatchSource: "profile-home"
82414
+ };
82415
+ return {};
82416
+ }
82417
+ function getDescendantPids(rootPid, rows) {
82418
+ const descendants = [];
82419
+ const queue = [rootPid];
82420
+ const seen = /* @__PURE__ */ new Set();
82421
+ while (queue.length > 0) {
82422
+ const current = queue.shift();
82423
+ if (seen.has(current)) continue;
82424
+ seen.add(current);
82425
+ for (const row of rows) {
82426
+ if (row.ppid !== current || seen.has(row.pid)) continue;
82427
+ descendants.push(row.pid);
82428
+ queue.push(row.pid);
82429
+ }
82430
+ }
82431
+ return descendants;
82432
+ }
82433
+ async function waitForNewMainPid(candidate, previousPids) {
82434
+ const deadline = Date.now() + LAUNCH_WAIT_MS;
82435
+ while (Date.now() < deadline) {
82436
+ const pid = (await readProcessRows()).find((row) => isMainProcessRow(row, candidate) && !previousPids.has(row.pid))?.pid ?? null;
82437
+ if (pid !== null) return pid;
82438
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
82439
+ }
82440
+ return null;
82441
+ }
82442
+ async function waitForMainPid(candidate, pid, timeoutMs) {
82443
+ const deadline = Date.now() + timeoutMs;
82444
+ while (Date.now() < deadline) {
82445
+ if ((await readProcessRows()).some((row) => row.pid === pid && isMainProcessRow(row, candidate))) return true;
82446
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
82447
+ }
82448
+ return false;
82449
+ }
82450
+ async function waitForAppServerPid(mainPid, profileHome) {
82451
+ const deadline = Date.now() + LAUNCH_WAIT_MS;
82452
+ while (Date.now() < deadline) {
82453
+ const pid = await findAppServerPidForProfile(await readProcessRows(), mainPid, profileHome);
82454
+ if (pid !== null) return pid;
82455
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
82456
+ }
82457
+ return null;
82458
+ }
82459
+ async function waitForMainPidExit(pid, timeoutMs) {
82460
+ const deadline = Date.now() + timeoutMs;
82461
+ while (Date.now() < deadline) {
82462
+ if (!(await readProcessRows()).some((row) => row.pid === pid)) return true;
82463
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
82464
+ }
82465
+ return false;
82466
+ }
82467
+ async function focusMainPid(pid) {
82468
+ return (await runCommand("/usr/bin/osascript", [
82469
+ "-e",
82470
+ `tell application "System Events" to repeat with proc in (processes whose unix id is ${pid})`,
82471
+ "-e",
82472
+ "set frontmost of proc to true",
82473
+ "-e",
82474
+ "end repeat"
82475
+ ], 5e3)).code === 0;
82476
+ }
82477
+ function managedInstanceToPayload(instance, running, appServerPid = instance.appServerPid, startedAt = null) {
82478
+ const runningStartedAt = startedAt ?? instance.launchedAt;
82479
+ return {
82480
+ profileName: instance.profileName,
82481
+ profileKey: instance.profileKey,
82482
+ appPath: instance.appPath,
82483
+ bundleId: instance.bundleId,
82484
+ pid: running ? instance.pid : null,
82485
+ appServerPid: running ? appServerPid : null,
82486
+ running,
82487
+ startedAt: running ? toIso(runningStartedAt) : null,
82488
+ uptimeMs: running && typeof runningStartedAt === "number" ? Math.max(0, Date.now() - runningStartedAt) : null,
82489
+ launchedAt: toIso(instance.launchedAt),
82490
+ lastVerifiedAt: toIso(instance.lastVerifiedAt),
82491
+ lastStatus: instance.lastStatus,
82492
+ lastError: instance.lastError
82493
+ };
82494
+ }
82495
+ async function patchManagedInstance(instance) {
82496
+ await patchAppState({ officialCodex: { instancesByProfileName: { [instance.profileName]: instance } } });
82497
+ }
82498
+ async function readManagedInstance(profileName) {
82499
+ return (await getAppState()).officialCodex.instancesByProfileName[profileName] ?? null;
82500
+ }
82501
+ async function resolveInstanceRuntimeState(args) {
82502
+ const pid = args.instance.pid;
82503
+ if (!pid) return {
82504
+ running: false,
82505
+ appServerPid: null,
82506
+ startedAt: null
82507
+ };
82508
+ const mainRow = args.rows.find((row) => row.pid === pid);
82509
+ if (!mainRow || !isMainProcessRow(mainRow, args.candidate)) return {
82510
+ running: false,
82511
+ appServerPid: null,
82512
+ startedAt: null
82513
+ };
82514
+ const appServerPid = args.instance.profileHome ? await findAppServerPidForProfile(args.rows, pid, args.instance.profileHome) : findAppServerPid(args.rows, pid);
82515
+ if (args.instance.profileHome && appServerPid === null) return {
82516
+ running: false,
82517
+ appServerPid: null,
82518
+ startedAt: null
82519
+ };
82520
+ return {
82521
+ running: true,
82522
+ appServerPid,
82523
+ startedAt: mainRow.startedAt
82524
+ };
82525
+ }
82526
+ async function openCodexWithProfileHome(candidate, profileHome, profileName, previousPids) {
82527
+ const executablePath = getMainExecutablePath(candidate);
82528
+ if (!existsSync(executablePath)) return {
82529
+ opened: false,
82530
+ pid: null
82531
+ };
82532
+ const telemetryLabel = `codexuse-profile-${encodeURIComponent(profileName)}`;
82533
+ let child;
82534
+ try {
82535
+ child = spawn(executablePath, [], {
82536
+ detached: true,
82537
+ env: {
82538
+ ...process.env,
82539
+ CODEX_HOME: profileHome,
82540
+ CODEX_TELEMETRY_LABEL: telemetryLabel
82541
+ },
82542
+ stdio: "ignore"
82543
+ });
82544
+ } catch {
82545
+ return {
82546
+ opened: false,
82547
+ pid: null
82548
+ };
82549
+ }
82550
+ const childPid = child.pid ?? null;
82551
+ child.unref();
82552
+ if (await new Promise((resolve) => {
82553
+ let settled = false;
82554
+ const settle = (failed) => {
82555
+ if (settled) return;
82556
+ settled = true;
82557
+ resolve(failed);
82558
+ };
82559
+ child.once("error", () => settle(true));
82560
+ setTimeout(() => settle(false), POLL_INTERVAL_MS);
82561
+ })) return {
82562
+ opened: false,
82563
+ pid: null
82564
+ };
82565
+ if (childPid !== null) {
82566
+ if (await waitForMainPid(candidate, childPid, 2e3)) return {
82567
+ opened: true,
82568
+ pid: childPid
82569
+ };
82570
+ }
82571
+ return {
82572
+ opened: true,
82573
+ pid: await waitForNewMainPid(candidate, previousPids)
82574
+ };
82089
82575
  }
82090
- async function readBundleIdentifier(appPath) {
82091
- const plistPath = path.join(appPath, "Contents", "Info.plist");
82576
+ async function readBundleString(appPath, key) {
82577
+ const plistPath = nodePath.join(appPath, "Contents", "Info.plist");
82092
82578
  if (!existsSync(plistPath)) return null;
82093
82579
  const result = await runCommand("/usr/bin/plutil", [
82094
82580
  "-extract",
82095
- "CFBundleIdentifier",
82581
+ key,
82096
82582
  "raw",
82097
82583
  "-o",
82098
82584
  "-",
@@ -82103,11 +82589,13 @@ async function readBundleIdentifier(appPath) {
82103
82589
  return bundleId.length > 0 ? bundleId : null;
82104
82590
  }
82105
82591
  async function discoverCodexAppCandidates() {
82592
+ const now = Date.now();
82593
+ const envPath = process.env.CODEXUSE_OFFICIAL_CODEX_APP_PATH?.trim() || null;
82594
+ if (appDiscoveryCache && appDiscoveryCache.envPath === envPath && appDiscoveryCache.expiresAt > now) return appDiscoveryCache.candidates;
82106
82595
  const rawPaths = /* @__PURE__ */ new Set();
82107
- const envPath = process.env.CODEXUSE_OFFICIAL_CODEX_APP_PATH?.trim();
82108
82596
  if (envPath) rawPaths.add(envPath);
82109
82597
  rawPaths.add("/Applications/Codex.app");
82110
- rawPaths.add(path.join(homedir(), "Applications", "Codex.app"));
82598
+ rawPaths.add(nodePath.join(homedir(), "Applications", "Codex.app"));
82111
82599
  const mdfind = await runCommand("/usr/bin/mdfind", ["kMDItemFSName == 'Codex.app'"], 1500);
82112
82600
  if (mdfind.code === 0) for (const line of mdfind.stdout.split(/\r?\n/)) {
82113
82601
  const trimmed = line.trim();
@@ -82115,85 +82603,716 @@ async function discoverCodexAppCandidates() {
82115
82603
  }
82116
82604
  const candidates = [];
82117
82605
  for (const appPath of rawPaths) {
82118
- if (!existsSync(appPath) || path.basename(appPath) !== "Codex.app") continue;
82119
- const bundleId = await readBundleIdentifier(appPath);
82606
+ if (!existsSync(appPath) || nodePath.basename(appPath) !== "Codex.app") continue;
82607
+ const bundleId = await readBundleString(appPath, "CFBundleIdentifier");
82120
82608
  if (!bundleId || bundleId.toLowerCase().includes("codexuse")) continue;
82121
82609
  candidates.push({
82122
82610
  appPath,
82123
- bundleId
82611
+ bundleId,
82612
+ version: await readBundleString(appPath, "CFBundleShortVersionString"),
82613
+ executableName: await readBundleString(appPath, "CFBundleExecutable") ?? "Codex"
82124
82614
  });
82125
82615
  }
82126
- return candidates.sort((a, b) => a.appPath.localeCompare(b.appPath));
82616
+ const sorted = candidates.sort((a, b) => a.appPath.localeCompare(b.appPath));
82617
+ appDiscoveryCache = {
82618
+ envPath,
82619
+ expiresAt: now + (sorted.length > 0 ? APP_DISCOVERY_CACHE_TTL_MS : APP_DISCOVERY_MISS_CACHE_TTL_MS),
82620
+ candidates: sorted
82621
+ };
82622
+ return sorted;
82127
82623
  }
82128
82624
  async function getRunningCodexPids(candidate) {
82129
82625
  const result = await runCommand("/bin/ps", ["-axo", "pid=,args="], 2e3);
82130
82626
  const pids = /* @__PURE__ */ new Set();
82131
- const executableRoot = path.join(candidate.appPath, "Contents", "MacOS");
82627
+ const appContentsRoot = `${nodePath.join(candidate.appPath, "Contents")}${nodePath.sep}`;
82628
+ if (result.code === 0) for (const line of result.stdout.split(/\r?\n/)) {
82629
+ const match = line.match(/^\s*(\d+)\s+(.+)$/);
82630
+ if (!match) continue;
82631
+ const pid = Number(match[1]);
82632
+ const args = match[2] ?? "";
82633
+ if (Number.isInteger(pid) && args.includes(appContentsRoot)) pids.add(pid);
82634
+ }
82635
+ return Array.from(pids).sort((a, b) => a - b);
82636
+ }
82637
+ async function getRunningCodexMainPids(candidate) {
82638
+ const result = await runCommand("/bin/ps", ["-axo", "pid=,args="], 2e3);
82639
+ const pids = /* @__PURE__ */ new Set();
82640
+ const executablePath = nodePath.join(candidate.appPath, "Contents", "MacOS", candidate.executableName);
82132
82641
  if (result.code === 0) for (const line of result.stdout.split(/\r?\n/)) {
82133
82642
  const match = line.match(/^\s*(\d+)\s+(.+)$/);
82134
82643
  if (!match) continue;
82135
82644
  const pid = Number(match[1]);
82136
82645
  const args = match[2] ?? "";
82137
- if (Number.isInteger(pid) && args.includes(executableRoot)) pids.add(pid);
82646
+ if (Number.isInteger(pid) && args.includes(executablePath)) pids.add(pid);
82138
82647
  }
82139
- return Array.from(pids);
82648
+ return Array.from(pids).sort((a, b) => a - b);
82140
82649
  }
82141
- async function waitForCodexExit(candidate) {
82142
- const deadline = Date.now() + QUIT_WAIT_MS;
82650
+ async function resolveCodexAppTarget() {
82651
+ const candidates = await discoverCodexAppCandidates();
82652
+ const fallback = candidates[0] ?? null;
82653
+ if (!fallback) return {
82654
+ candidate: null,
82655
+ runningPids: [],
82656
+ mainPids: []
82657
+ };
82658
+ for (const candidate of candidates) {
82659
+ const [runningPids, mainPids] = await Promise.all([getRunningCodexPids(candidate), getRunningCodexMainPids(candidate)]);
82660
+ if (mainPids.length > 0) return {
82661
+ candidate,
82662
+ runningPids,
82663
+ mainPids
82664
+ };
82665
+ }
82666
+ const [runningPids, mainPids] = await Promise.all([getRunningCodexPids(fallback), getRunningCodexMainPids(fallback)]);
82667
+ return {
82668
+ candidate: fallback,
82669
+ runningPids,
82670
+ mainPids
82671
+ };
82672
+ }
82673
+ async function waitForCodexExit(candidate, timeoutMs) {
82674
+ const deadline = Date.now() + timeoutMs;
82143
82675
  while (Date.now() < deadline) {
82144
82676
  if ((await getRunningCodexPids(candidate)).length === 0) return true;
82145
82677
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
82146
82678
  }
82147
82679
  return false;
82148
82680
  }
82149
- async function requestCodexQuit(candidate) {
82150
- await runCommand("/usr/bin/osascript", ["-e", `tell application id "${appleScriptString(candidate.bundleId)}" to quit`]);
82681
+ async function signalPids(pids, signal) {
82682
+ const signaled = [];
82683
+ for (const pid of pids) try {
82684
+ process.kill(pid, signal);
82685
+ signaled.push(pid);
82686
+ } catch {}
82687
+ return signaled;
82151
82688
  }
82152
82689
  async function openCodex(candidate) {
82153
82690
  if ((await runCommand("/usr/bin/open", ["-b", candidate.bundleId])).code === 0) return true;
82154
82691
  return (await runCommand("/usr/bin/open", [candidate.appPath])).code === 0;
82155
82692
  }
82156
- async function restartOfficialCodexAppOnce() {
82157
- if (process.platform !== "darwin") return {
82158
- status: "skipped",
82159
- reason: "unsupported-platform"
82693
+ async function waitForCodexLaunch(candidate) {
82694
+ const deadline = Date.now() + LAUNCH_WAIT_MS;
82695
+ while (Date.now() < deadline) {
82696
+ const pid = (await getRunningCodexMainPids(candidate))[0] ?? null;
82697
+ if (pid !== null) return pid;
82698
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
82699
+ }
82700
+ return null;
82701
+ }
82702
+ function toIso(value) {
82703
+ return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : null;
82704
+ }
82705
+ async function rememberProfileSwitch(profileKey) {
82706
+ await patchAppState({ officialCodex: {
82707
+ lastProfileSwitchAt: Date.now(),
82708
+ lastProfileSwitchProfileKey: profileKey
82709
+ } });
82710
+ }
82711
+ async function rememberRestartResult(args) {
82712
+ const now = Date.now();
82713
+ const verified = args.status === "restarted" || args.status === "started";
82714
+ await patchAppState({ officialCodex: {
82715
+ lastVerifiedLaunchAt: verified ? now : void 0,
82716
+ lastVerifiedLaunchProfileKey: verified ? args.profileKey ?? null : void 0,
82717
+ lastObservedPid: verified ? args.pid ?? null : void 0,
82718
+ lastRestartStatus: args.status,
82719
+ lastRestartReason: args.reason ?? null
82720
+ } });
82721
+ }
82722
+ async function restartOfficialCodexAppOnce(options) {
82723
+ if (process.platform !== "darwin") {
82724
+ await rememberRestartResult({
82725
+ status: "skipped",
82726
+ reason: "unsupported-platform"
82727
+ });
82728
+ return {
82729
+ status: "skipped",
82730
+ reason: "unsupported-platform"
82731
+ };
82732
+ }
82733
+ const now = Date.now();
82734
+ if (options.source !== "manual" && lastRestartStartedAt > 0 && now - lastRestartStartedAt < RESTART_COOLDOWN_MS) {
82735
+ await rememberRestartResult({
82736
+ status: "skipped",
82737
+ reason: "cooldown",
82738
+ profileKey: options.currentProfileKey
82739
+ });
82740
+ return {
82741
+ status: "skipped",
82742
+ reason: "cooldown"
82743
+ };
82744
+ }
82745
+ const { candidate, runningPids, mainPids } = await resolveCodexAppTarget();
82746
+ if (!candidate) {
82747
+ await rememberRestartResult({
82748
+ status: "skipped",
82749
+ reason: "official-codex-app-not-found",
82750
+ profileKey: options.currentProfileKey
82751
+ });
82752
+ return {
82753
+ status: "skipped",
82754
+ reason: "official-codex-app-not-found"
82755
+ };
82756
+ }
82757
+ const appIsRunning = mainPids.length > 0;
82758
+ if (!appIsRunning && !options.launchIfNotRunning) {
82759
+ await rememberRestartResult({
82760
+ status: "skipped",
82761
+ reason: "official-codex-app-not-running",
82762
+ profileKey: options.currentProfileKey
82763
+ });
82764
+ return {
82765
+ status: "skipped",
82766
+ reason: "official-codex-app-not-running"
82767
+ };
82768
+ }
82769
+ if (options.source !== "manual") lastRestartStartedAt = Date.now();
82770
+ if (appIsRunning) {
82771
+ logOfficialCodexRestart("warn", "Force killing official Codex before relaunch.", {
82772
+ appPath: candidate.appPath,
82773
+ bundleId: candidate.bundleId,
82774
+ source: options.source,
82775
+ runningPids,
82776
+ mainPids
82777
+ });
82778
+ const killedPids = await signalPids(runningPids, "SIGKILL");
82779
+ logOfficialCodexRestart("warn", "Sent SIGKILL to official Codex pids.", {
82780
+ appPath: candidate.appPath,
82781
+ bundleId: candidate.bundleId,
82782
+ source: options.source,
82783
+ killedPids
82784
+ });
82785
+ if (!await waitForCodexExit(candidate, EXIT_WAIT_MS)) {
82786
+ const remainingPids = await getRunningCodexPids(candidate);
82787
+ logOfficialCodexRestart("error", "Official Codex pids remained after force kill.", {
82788
+ appPath: candidate.appPath,
82789
+ bundleId: candidate.bundleId,
82790
+ source: options.source,
82791
+ remainingPids
82792
+ });
82793
+ const failed = {
82794
+ status: "failed",
82795
+ appPath: candidate.appPath,
82796
+ bundleId: candidate.bundleId,
82797
+ reason: "force-quit-timeout",
82798
+ phase: "quit"
82799
+ };
82800
+ await rememberRestartResult({
82801
+ status: failed.status,
82802
+ reason: failed.reason,
82803
+ profileKey: options.currentProfileKey
82804
+ });
82805
+ return failed;
82806
+ }
82807
+ logOfficialCodexRestart("info", "Official Codex exited after force kill.", {
82808
+ appPath: candidate.appPath,
82809
+ bundleId: candidate.bundleId,
82810
+ source: options.source
82811
+ });
82812
+ }
82813
+ if (!await openCodex(candidate)) {
82814
+ const failed = {
82815
+ status: "failed",
82816
+ appPath: candidate.appPath,
82817
+ bundleId: candidate.bundleId,
82818
+ reason: "open-failed",
82819
+ phase: "launch"
82820
+ };
82821
+ await rememberRestartResult({
82822
+ status: failed.status,
82823
+ reason: failed.reason,
82824
+ profileKey: options.currentProfileKey
82825
+ });
82826
+ return failed;
82827
+ }
82828
+ const launchedPid = await waitForCodexLaunch(candidate);
82829
+ if (launchedPid === null) {
82830
+ logOfficialCodexRestart("error", "Official Codex launch was not verified.", {
82831
+ appPath: candidate.appPath,
82832
+ bundleId: candidate.bundleId,
82833
+ source: options.source
82834
+ });
82835
+ const failed = {
82836
+ status: "failed",
82837
+ appPath: candidate.appPath,
82838
+ bundleId: candidate.bundleId,
82839
+ reason: "launch-timeout",
82840
+ phase: "launch"
82841
+ };
82842
+ await rememberRestartResult({
82843
+ status: failed.status,
82844
+ reason: failed.reason,
82845
+ profileKey: options.currentProfileKey
82846
+ });
82847
+ return failed;
82848
+ }
82849
+ const status = appIsRunning ? "restarted" : "started";
82850
+ logOfficialCodexRestart("info", "Official Codex launch verified.", {
82851
+ appPath: candidate.appPath,
82852
+ bundleId: candidate.bundleId,
82853
+ source: options.source,
82854
+ status,
82855
+ pid: launchedPid
82856
+ });
82857
+ await rememberRestartResult({
82858
+ status,
82859
+ profileKey: options.currentProfileKey,
82860
+ pid: launchedPid
82861
+ });
82862
+ return {
82863
+ status,
82864
+ appPath: candidate.appPath,
82865
+ bundleId: candidate.bundleId,
82866
+ pid: launchedPid
82160
82867
  };
82161
- if (lastRestartStartedAt > 0 && Date.now() - lastRestartStartedAt < RESTART_COOLDOWN_MS) return {
82162
- status: "skipped",
82163
- reason: "cooldown"
82868
+ }
82869
+ async function getOfficialCodexSyncStatus(currentProfileKey) {
82870
+ const stored = (await getAppState()).officialCodex;
82871
+ const base = {
82872
+ runningPids: [],
82873
+ lastProfileSwitchAt: toIso(stored.lastProfileSwitchAt),
82874
+ lastVerifiedLaunchAt: toIso(stored.lastVerifiedLaunchAt),
82875
+ lastRestartStatus: stored.lastRestartStatus,
82876
+ lastRestartReason: stored.lastRestartReason
82164
82877
  };
82165
- const candidate = (await discoverCodexAppCandidates())[0] ?? null;
82166
- if (!candidate) return {
82167
- status: "skipped",
82168
- reason: "official-codex-app-not-found"
82878
+ if (process.platform !== "darwin") return {
82879
+ ...base,
82880
+ state: "unsupported"
82169
82881
  };
82170
- if ((await getRunningCodexPids(candidate)).length === 0) return {
82171
- status: "skipped",
82172
- reason: "official-codex-app-not-running"
82882
+ const { candidate, mainPids } = await resolveCodexAppTarget();
82883
+ if (!candidate) return {
82884
+ ...base,
82885
+ state: "not-found"
82173
82886
  };
82174
- lastRestartStartedAt = Date.now();
82175
- await requestCodexQuit(candidate);
82176
- if (!await waitForCodexExit(candidate)) return {
82177
- status: "failed",
82887
+ if (mainPids.length === 0) return {
82888
+ ...base,
82889
+ state: "not-running",
82178
82890
  appPath: candidate.appPath,
82179
82891
  bundleId: candidate.bundleId,
82180
- reason: "quit-timeout"
82892
+ runningPids: mainPids
82181
82893
  };
82182
- if (!await openCodex(candidate)) return {
82183
- status: "failed",
82894
+ const lastSwitchAt = stored.lastProfileSwitchAt;
82895
+ const lastLaunchAt = stored.lastVerifiedLaunchAt;
82896
+ const lastLaunchProfileKey = stored.lastVerifiedLaunchProfileKey;
82897
+ const stale = Boolean(currentProfileKey && lastSwitchAt && (!lastLaunchAt || lastLaunchAt < lastSwitchAt || lastLaunchProfileKey !== currentProfileKey));
82898
+ return {
82899
+ ...base,
82900
+ state: stale ? "stale" : currentProfileKey && lastLaunchProfileKey === currentProfileKey ? "synced" : "unknown",
82184
82901
  appPath: candidate.appPath,
82185
82902
  bundleId: candidate.bundleId,
82186
- reason: "reopen-failed"
82903
+ runningPids: mainPids
82187
82904
  };
82905
+ }
82906
+ function recordOfficialCodexProfileSwitch(profileKey) {
82907
+ return rememberProfileSwitch(profileKey);
82908
+ }
82909
+ async function getOfficialCodexProfileInstances() {
82910
+ if (process.platform !== "darwin") return {
82911
+ state: "unsupported",
82912
+ appPath: null,
82913
+ bundleId: null,
82914
+ version: null,
82915
+ instances: [],
82916
+ unmanaged: []
82917
+ };
82918
+ const { candidate, mainPids } = await resolveCodexAppTarget();
82919
+ if (!candidate) return {
82920
+ state: "not-found",
82921
+ appPath: null,
82922
+ bundleId: null,
82923
+ version: null,
82924
+ instances: [],
82925
+ unmanaged: []
82926
+ };
82927
+ const [state, rows] = await Promise.all([getAppState(), readProcessRows()]);
82928
+ const managed = [];
82929
+ for (const instance of Object.values(state.officialCodex.instancesByProfileName)) {
82930
+ const runtime = await resolveInstanceRuntimeState({
82931
+ instance,
82932
+ candidate,
82933
+ rows
82934
+ });
82935
+ managed.push(managedInstanceToPayload(instance, runtime.running, runtime.appServerPid, runtime.startedAt));
82936
+ }
82937
+ managed.sort((a, b) => a.profileName.localeCompare(b.profileName));
82938
+ const managedPids = new Set(managed.flatMap((entry) => entry.running && entry.pid ? [entry.pid] : []));
82939
+ const unmanaged = [];
82940
+ for (const pid of mainPids.filter((entry) => !managedPids.has(entry))) {
82941
+ const appServerPid = findAppServerPid(rows, pid);
82942
+ const startedAt = rows.find((row) => row.pid === pid)?.startedAt ?? null;
82943
+ unmanaged.push({
82944
+ pid,
82945
+ appServerPid,
82946
+ startedAt: toIso(startedAt),
82947
+ uptimeMs: typeof startedAt === "number" ? Math.max(0, Date.now() - startedAt) : null,
82948
+ ...await describeUnmanagedProfileHint(appServerPid)
82949
+ });
82950
+ }
82188
82951
  return {
82189
- status: "restarted",
82952
+ state: "found",
82190
82953
  appPath: candidate.appPath,
82191
- bundleId: candidate.bundleId
82954
+ bundleId: candidate.bundleId,
82955
+ version: candidate.version,
82956
+ instances: managed,
82957
+ unmanaged
82192
82958
  };
82193
82959
  }
82194
- function restartOfficialCodexApp() {
82960
+ function launchOfficialCodexProfileInstance(options) {
82961
+ return enqueueProfileAction(async () => {
82962
+ if (process.platform !== "darwin") return {
82963
+ status: "skipped",
82964
+ profileName: options.profileName,
82965
+ instance: null,
82966
+ reason: "unsupported-platform"
82967
+ };
82968
+ const { candidate, mainPids } = await resolveCodexAppTarget();
82969
+ if (!candidate) return {
82970
+ status: "skipped",
82971
+ profileName: options.profileName,
82972
+ instance: null,
82973
+ reason: "official-codex-app-not-found"
82974
+ };
82975
+ const existing = await readManagedInstance(options.profileName);
82976
+ if (existing) {
82977
+ const runtime = await resolveInstanceRuntimeState({
82978
+ instance: existing,
82979
+ candidate,
82980
+ rows: await readProcessRows()
82981
+ });
82982
+ if (runtime.running) {
82983
+ const verified = {
82984
+ ...existing,
82985
+ profileHome: existing.profileHome ?? options.profileHome,
82986
+ appServerPid: runtime.appServerPid,
82987
+ lastVerifiedAt: Date.now(),
82988
+ lastStatus: "already-running",
82989
+ lastError: null
82990
+ };
82991
+ await patchManagedInstance(verified);
82992
+ return {
82993
+ status: "already-running",
82994
+ profileName: options.profileName,
82995
+ instance: managedInstanceToPayload(verified, true, runtime.appServerPid, runtime.startedAt),
82996
+ reason: null
82997
+ };
82998
+ }
82999
+ }
83000
+ const launch = await openCodexWithProfileHome(candidate, options.profileHome, options.profileName, new Set(mainPids));
83001
+ if (!launch.opened) {
83002
+ const failed = {
83003
+ profileName: options.profileName,
83004
+ profileKey: options.profileKey,
83005
+ profileHome: options.profileHome,
83006
+ appPath: candidate.appPath,
83007
+ bundleId: candidate.bundleId,
83008
+ pid: null,
83009
+ appServerPid: null,
83010
+ launchedAt: null,
83011
+ lastVerifiedAt: null,
83012
+ lastStatus: "failed",
83013
+ lastError: "open-failed"
83014
+ };
83015
+ await patchManagedInstance(failed);
83016
+ return {
83017
+ status: "failed",
83018
+ profileName: options.profileName,
83019
+ instance: managedInstanceToPayload(failed, false),
83020
+ reason: "open-failed"
83021
+ };
83022
+ }
83023
+ const launchedPid = launch.pid;
83024
+ if (launchedPid === null) {
83025
+ const failed = {
83026
+ profileName: options.profileName,
83027
+ profileKey: options.profileKey,
83028
+ profileHome: options.profileHome,
83029
+ appPath: candidate.appPath,
83030
+ bundleId: candidate.bundleId,
83031
+ pid: null,
83032
+ appServerPid: null,
83033
+ launchedAt: null,
83034
+ lastVerifiedAt: null,
83035
+ lastStatus: "failed",
83036
+ lastError: "launch-timeout"
83037
+ };
83038
+ await patchManagedInstance(failed);
83039
+ return {
83040
+ status: "failed",
83041
+ profileName: options.profileName,
83042
+ instance: managedInstanceToPayload(failed, false),
83043
+ reason: "launch-timeout"
83044
+ };
83045
+ }
83046
+ const appServerPid = await waitForAppServerPid(launchedPid, options.profileHome);
83047
+ if (appServerPid === null) {
83048
+ const failed = {
83049
+ profileName: options.profileName,
83050
+ profileKey: options.profileKey,
83051
+ profileHome: options.profileHome,
83052
+ appPath: candidate.appPath,
83053
+ bundleId: candidate.bundleId,
83054
+ pid: null,
83055
+ appServerPid: null,
83056
+ launchedAt: null,
83057
+ lastVerifiedAt: Date.now(),
83058
+ lastStatus: "failed",
83059
+ lastError: "app-server-not-verified"
83060
+ };
83061
+ await patchManagedInstance(failed);
83062
+ return {
83063
+ status: "failed",
83064
+ profileName: options.profileName,
83065
+ instance: managedInstanceToPayload(failed, false),
83066
+ reason: "app-server-not-verified"
83067
+ };
83068
+ }
83069
+ const now = Date.now();
83070
+ const instance = {
83071
+ profileName: options.profileName,
83072
+ profileKey: options.profileKey,
83073
+ profileHome: options.profileHome,
83074
+ appPath: candidate.appPath,
83075
+ bundleId: candidate.bundleId,
83076
+ pid: launchedPid,
83077
+ appServerPid,
83078
+ launchedAt: now,
83079
+ lastVerifiedAt: now,
83080
+ lastStatus: "started",
83081
+ lastError: null
83082
+ };
83083
+ await patchManagedInstance(instance);
83084
+ return {
83085
+ status: "started",
83086
+ profileName: options.profileName,
83087
+ instance: managedInstanceToPayload(instance, true, appServerPid),
83088
+ reason: null
83089
+ };
83090
+ });
83091
+ }
83092
+ function stopOfficialCodexProfileInstance(profileName) {
83093
+ return enqueueProfileAction(async () => {
83094
+ const existing = await readManagedInstance(profileName);
83095
+ if (!existing) return {
83096
+ status: "not-running",
83097
+ profileName,
83098
+ instance: null,
83099
+ reason: "not-managed"
83100
+ };
83101
+ const { candidate } = await resolveCodexAppTarget();
83102
+ if (!candidate) {
83103
+ const stopped = {
83104
+ ...existing,
83105
+ pid: null,
83106
+ appServerPid: null,
83107
+ lastVerifiedAt: Date.now(),
83108
+ lastStatus: "not-running",
83109
+ lastError: "official-codex-app-not-found"
83110
+ };
83111
+ await patchManagedInstance(stopped);
83112
+ return {
83113
+ status: "not-running",
83114
+ profileName,
83115
+ instance: managedInstanceToPayload(stopped, false),
83116
+ reason: "official-codex-app-not-found"
83117
+ };
83118
+ }
83119
+ const rows = await readProcessRows();
83120
+ const runtime = await resolveInstanceRuntimeState({
83121
+ instance: existing,
83122
+ candidate,
83123
+ rows
83124
+ });
83125
+ if (!runtime.running || !existing.pid) {
83126
+ const stopped = {
83127
+ ...existing,
83128
+ pid: null,
83129
+ appServerPid: null,
83130
+ lastVerifiedAt: Date.now(),
83131
+ lastStatus: "not-running",
83132
+ lastError: null
83133
+ };
83134
+ await patchManagedInstance(stopped);
83135
+ return {
83136
+ status: "not-running",
83137
+ profileName,
83138
+ instance: managedInstanceToPayload(stopped, false),
83139
+ reason: null
83140
+ };
83141
+ }
83142
+ const tree = [existing.pid, ...getDescendantPids(existing.pid, rows)].filter((pid, index, all) => all.indexOf(pid) === index).sort((a, b) => b - a);
83143
+ await signalPids(tree, "SIGTERM");
83144
+ if (!await waitForMainPidExit(existing.pid, 5e3)) {
83145
+ await signalPids(tree, "SIGKILL");
83146
+ await waitForMainPidExit(existing.pid, EXIT_WAIT_MS);
83147
+ }
83148
+ const stillRunning = (await readProcessRows()).some((row) => row.pid === existing.pid);
83149
+ const stopped = {
83150
+ ...existing,
83151
+ pid: stillRunning ? existing.pid : null,
83152
+ appServerPid: stillRunning ? runtime.appServerPid : null,
83153
+ lastVerifiedAt: Date.now(),
83154
+ lastStatus: stillRunning ? "failed" : "stopped",
83155
+ lastError: stillRunning ? "stop-timeout" : null
83156
+ };
83157
+ await patchManagedInstance(stopped);
83158
+ return {
83159
+ status: stillRunning ? "failed" : "stopped",
83160
+ profileName,
83161
+ instance: managedInstanceToPayload(stopped, stillRunning, stopped.appServerPid, runtime.startedAt),
83162
+ reason: stillRunning ? "stop-timeout" : null
83163
+ };
83164
+ });
83165
+ }
83166
+ function focusOfficialCodexProfileInstance(profileName) {
83167
+ return enqueueProfileAction(async () => {
83168
+ const existing = await readManagedInstance(profileName);
83169
+ if (!existing) return {
83170
+ status: "not-running",
83171
+ profileName,
83172
+ instance: null,
83173
+ reason: "not-managed"
83174
+ };
83175
+ const { candidate } = await resolveCodexAppTarget();
83176
+ if (!candidate) return {
83177
+ status: "not-running",
83178
+ profileName,
83179
+ instance: managedInstanceToPayload(existing, false),
83180
+ reason: "official-codex-app-not-found"
83181
+ };
83182
+ const runtime = await resolveInstanceRuntimeState({
83183
+ instance: existing,
83184
+ candidate,
83185
+ rows: await readProcessRows()
83186
+ });
83187
+ if (!runtime.running || !existing.pid) return {
83188
+ status: "not-running",
83189
+ profileName,
83190
+ instance: managedInstanceToPayload(existing, false),
83191
+ reason: null
83192
+ };
83193
+ const focused = await focusMainPid(existing.pid);
83194
+ const verified = {
83195
+ ...existing,
83196
+ appServerPid: runtime.appServerPid,
83197
+ lastVerifiedAt: Date.now(),
83198
+ lastStatus: focused ? "focused" : "focus-unknown",
83199
+ lastError: focused ? null : "focus-unknown"
83200
+ };
83201
+ await patchManagedInstance(verified);
83202
+ return {
83203
+ status: focused ? "focused" : "focus-unknown",
83204
+ profileName,
83205
+ instance: managedInstanceToPayload(verified, true, runtime.appServerPid, runtime.startedAt),
83206
+ reason: focused ? null : "focus-unknown"
83207
+ };
83208
+ });
83209
+ }
83210
+ function focusOfficialCodexObservedProfileInstance(profileName, pid, appServerPid) {
83211
+ return enqueueProfileAction(async () => {
83212
+ const { candidate } = await resolveCodexAppTarget();
83213
+ if (!candidate) return {
83214
+ status: "not-running",
83215
+ profileName,
83216
+ instance: null,
83217
+ reason: "official-codex-app-not-found"
83218
+ };
83219
+ const rows = await readProcessRows();
83220
+ const mainRow = rows.find((row) => row.pid === pid);
83221
+ if (!mainRow || !isMainProcessRow(mainRow, candidate)) return {
83222
+ status: "not-running",
83223
+ profileName,
83224
+ instance: null,
83225
+ reason: null
83226
+ };
83227
+ const runtimeAppServerPid = findAppServerPid(rows, pid) ?? appServerPid;
83228
+ const focused = await focusMainPid(pid);
83229
+ const instance = {
83230
+ profileName,
83231
+ profileKey: null,
83232
+ appPath: candidate.appPath,
83233
+ bundleId: candidate.bundleId,
83234
+ pid,
83235
+ appServerPid: runtimeAppServerPid,
83236
+ running: true,
83237
+ startedAt: toIso(mainRow.startedAt),
83238
+ uptimeMs: typeof mainRow.startedAt === "number" ? Math.max(0, Date.now() - mainRow.startedAt) : null,
83239
+ launchedAt: null,
83240
+ lastVerifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
83241
+ lastStatus: focused ? "focused" : "focus-unknown",
83242
+ lastError: focused ? null : "focus-unknown"
83243
+ };
83244
+ return {
83245
+ status: focused ? "focused" : "focus-unknown",
83246
+ profileName,
83247
+ instance,
83248
+ reason: focused ? null : "focus-unknown"
83249
+ };
83250
+ });
83251
+ }
83252
+ function stopOfficialCodexObservedProfileInstance(profileName, pid, appServerPid) {
83253
+ return enqueueProfileAction(async () => {
83254
+ const { candidate } = await resolveCodexAppTarget();
83255
+ if (!candidate) return {
83256
+ status: "not-running",
83257
+ profileName,
83258
+ instance: null,
83259
+ reason: "official-codex-app-not-found"
83260
+ };
83261
+ const rows = await readProcessRows();
83262
+ const mainRow = rows.find((row) => row.pid === pid);
83263
+ if (!mainRow || !isMainProcessRow(mainRow, candidate)) return {
83264
+ status: "not-running",
83265
+ profileName,
83266
+ instance: null,
83267
+ reason: null
83268
+ };
83269
+ const runtimeAppServerPid = findAppServerPid(rows, pid) ?? appServerPid;
83270
+ const tree = [pid, ...getDescendantPids(pid, rows)].filter((entry, index, all) => all.indexOf(entry) === index).sort((a, b) => b - a);
83271
+ await signalPids(tree, "SIGTERM");
83272
+ if (!await waitForMainPidExit(pid, 5e3)) {
83273
+ await signalPids(tree, "SIGKILL");
83274
+ await waitForMainPidExit(pid, EXIT_WAIT_MS);
83275
+ }
83276
+ const stillRunning = (await readProcessRows()).some((row) => row.pid === pid);
83277
+ const instance = {
83278
+ profileName,
83279
+ profileKey: null,
83280
+ appPath: candidate.appPath,
83281
+ bundleId: candidate.bundleId,
83282
+ pid: stillRunning ? pid : null,
83283
+ appServerPid: stillRunning ? runtimeAppServerPid : null,
83284
+ running: stillRunning,
83285
+ startedAt: stillRunning ? toIso(mainRow.startedAt) : null,
83286
+ uptimeMs: stillRunning && typeof mainRow.startedAt === "number" ? Math.max(0, Date.now() - mainRow.startedAt) : null,
83287
+ launchedAt: null,
83288
+ lastVerifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
83289
+ lastStatus: stillRunning ? "failed" : "stopped",
83290
+ lastError: stillRunning ? "stop-timeout" : null
83291
+ };
83292
+ return {
83293
+ status: stillRunning ? "failed" : "stopped",
83294
+ profileName,
83295
+ instance,
83296
+ reason: stillRunning ? "stop-timeout" : null
83297
+ };
83298
+ });
83299
+ }
83300
+ async function restartOfficialCodexProfileInstance(options) {
83301
+ const stopped = await stopOfficialCodexProfileInstance(options.profileName);
83302
+ if (stopped.status === "failed") return stopped;
83303
+ const launched = await launchOfficialCodexProfileInstance(options);
83304
+ return {
83305
+ ...launched,
83306
+ status: launched.status === "started" ? "restarted" : launched.status
83307
+ };
83308
+ }
83309
+ function restartOfficialCodexApp(options = {}) {
82195
83310
  if (restartPromise) return restartPromise;
82196
- restartPromise = restartOfficialCodexAppOnce().finally(() => {
83311
+ restartPromise = restartOfficialCodexAppOnce({
83312
+ source: options.source ?? "manual",
83313
+ currentProfileKey: options.currentProfileKey ?? null,
83314
+ launchIfNotRunning: options.launchIfNotRunning ?? (options.source ?? "manual") === "manual"
83315
+ }).finally(() => {
82197
83316
  restartPromise = null;
82198
83317
  });
82199
83318
  return restartPromise;
@@ -82304,6 +83423,37 @@ function describeActiveAuthOwner(snapshot) {
82304
83423
  if (!snapshot) return null;
82305
83424
  return snapshot.email ?? snapshot.userId ?? snapshot.chatgptUserId ?? snapshot.accountId ?? null;
82306
83425
  }
83426
+ async function createIsolatedCodexLoginHome() {
83427
+ const home = await promises.mkdtemp(nodePath.join(nodeOs.tmpdir(), "codexuse-login-"));
83428
+ let cleaned = false;
83429
+ return {
83430
+ home,
83431
+ authPath: nodePath.join(home, "auth.json"),
83432
+ cleanup: async () => {
83433
+ if (cleaned) return;
83434
+ cleaned = true;
83435
+ await promises.rm(home, {
83436
+ recursive: true,
83437
+ force: true
83438
+ });
83439
+ }
83440
+ };
83441
+ }
83442
+ async function cleanupProfileAuthSession(session) {
83443
+ const tasks = [];
83444
+ if (session.restoreConfig) {
83445
+ const restore = session.restoreConfig;
83446
+ session.restoreConfig = void 0;
83447
+ tasks.push(Promise.resolve().then(restore));
83448
+ }
83449
+ if (session.cleanupAuthHome) {
83450
+ const cleanup = session.cleanupAuthHome;
83451
+ session.cleanupAuthHome = void 0;
83452
+ tasks.push(Promise.resolve().then(cleanup));
83453
+ }
83454
+ const results = await Promise.allSettled(tasks);
83455
+ for (const result of results) if (result.status === "rejected") console.warn("Failed to clean up profile auth session:", result.reason);
83456
+ }
82307
83457
  async function resolveCodexBinaryOrThrow(context) {
82308
83458
  try {
82309
83459
  return await requireCodexCli();
@@ -82311,7 +83461,7 @@ async function resolveCodexBinaryOrThrow(context) {
82311
83461
  if (error instanceof CodexCliMissingError) {
82312
83462
  const reason = error.status.reason ?? "Codex CLI not found. Install @openai/codex or set CODEX_BINARY.";
82313
83463
  const message = context ? `${context} ${reason}` : reason;
82314
- throw new Error(message);
83464
+ throw new Error(message, { cause: error });
82315
83465
  }
82316
83466
  throw error;
82317
83467
  }
@@ -83018,6 +84168,61 @@ const createServer = fn(function* () {
83018
84168
  properties
83019
84169
  });
83020
84170
  };
84171
+ const lowRemainingNotifications = /* @__PURE__ */ new Map();
84172
+ const finitePercent = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : null;
84173
+ const formatLowRemainingPercent = (value) => {
84174
+ const rounded = Math.round(value * 10) / 10;
84175
+ return (Number.isInteger(rounded) ? rounded.toFixed(0) : rounded.toFixed(1)).replace(/\.0$/, "");
84176
+ };
84177
+ const buildRateLimitResetKey = (window) => {
84178
+ if (typeof window.resetsAt === "string" && window.resetsAt.trim()) return `at:${window.resetsAt.trim()}`;
84179
+ if (typeof window.windowMinutes === "number" && Number.isFinite(window.windowMinutes)) return `window:${window.windowMinutes}`;
84180
+ return "unknown";
84181
+ };
84182
+ const maybeSendLowRemainingNotification = async (payload) => {
84183
+ const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
84184
+ if (!settings.lowRemainingNotificationEnabled) return;
84185
+ if (!payload.snapshot) return;
84186
+ const windows = [{
84187
+ key: "primary",
84188
+ window: payload.snapshot.primary
84189
+ }, {
84190
+ key: "secondary",
84191
+ window: payload.snapshot.secondary
84192
+ }];
84193
+ for (const entry of windows) {
84194
+ if (!entry.window) continue;
84195
+ const usedPercent = finitePercent(entry.window.usedPercent);
84196
+ if (usedPercent === null) continue;
84197
+ const remainingPercent = Math.max(0, 100 - usedPercent);
84198
+ const stateKey = `${payload.accountId}:${entry.key}`;
84199
+ if (remainingPercent > settings.lowRemainingNotificationThreshold) {
84200
+ lowRemainingNotifications.delete(stateKey);
84201
+ continue;
84202
+ }
84203
+ const resetKey = buildRateLimitResetKey(entry.window);
84204
+ const previous = lowRemainingNotifications.get(stateKey);
84205
+ if (previous && previous.resetKey === resetKey && previous.threshold === settings.lowRemainingNotificationThreshold) continue;
84206
+ const remainingText = formatLowRemainingPercent(remainingPercent);
84207
+ const switchText = settings.enabled ? `Auto-roll will switch at or below ${settings.switchRemainingThreshold}% left.` : "Auto-roll is currently off.";
84208
+ sendDesktopParentMessage$1({
84209
+ type: "t3-server:show-notification",
84210
+ title: "Codex usage low",
84211
+ body: `${payload.profileName} has ${remainingText}% left. ${switchText}`
84212
+ });
84213
+ lowRemainingNotifications.set(stateKey, {
84214
+ resetKey,
84215
+ threshold: settings.lowRemainingNotificationThreshold
84216
+ });
84217
+ trackAnalyticsEvent("low_remaining_notification_sent", {
84218
+ profileName: payload.profileName,
84219
+ accountId: payload.accountId,
84220
+ windowKey: entry.key,
84221
+ remainingPercent,
84222
+ threshold: settings.lowRemainingNotificationThreshold
84223
+ });
84224
+ }
84225
+ };
83021
84226
  const publishProfileSwitched = (payload) => {
83022
84227
  refreshTray();
83023
84228
  return runPromise$2(pushBus.publishAll(WS_CHANNELS.profilesSwitched, payload));
@@ -83025,6 +84230,7 @@ const createServer = fn(function* () {
83025
84230
  const publishCliStatusUpdated = (payload) => runPromise$2(pushBus.publishAll(WS_CHANNELS.cliStatusUpdated, payload));
83026
84231
  let backgroundAutoRollInFlight = false;
83027
84232
  let lastAutoRollAttempt = null;
84233
+ const autoRollRearmBlockedProfiles = /* @__PURE__ */ new Set();
83028
84234
  const rememberAutoRollAttempt = (source, target) => {
83029
84235
  if (!source || !target) return;
83030
84236
  lastAutoRollAttempt = {
@@ -83037,29 +84243,177 @@ const createServer = fn(function* () {
83037
84243
  if (!lastAutoRollAttempt) return false;
83038
84244
  return lastAutoRollAttempt.source === source && lastAutoRollAttempt.target === target && Date.now() - lastAutoRollAttempt.timestamp < 3e4;
83039
84245
  };
83040
- const maybeRestartOfficialCodexAfterAutoRoll = (targetProfileName) => {
83041
- (async () => {
83042
- if (!normalizeAutoRollSettings(await getStoredAutoRollSettings()).restartOfficialCodexOnAutoRoll) return;
83043
- const result = await restartOfficialCodexApp();
84246
+ const blockAutoRollUntilRearm = (profileName) => {
84247
+ if (!profileName) return;
84248
+ autoRollRearmBlockedProfiles.add(profileName);
84249
+ };
84250
+ const unblockAutoRollRearm = (profileName) => {
84251
+ if (!profileName) return;
84252
+ autoRollRearmBlockedProfiles.delete(profileName);
84253
+ };
84254
+ const isAutoRollBlockedUntilRearm = (profileName, summary, rearmRemainingThreshold) => {
84255
+ if (!autoRollRearmBlockedProfiles.has(profileName)) return false;
84256
+ if (summary.hasUsage && summary.remainingPercent > rearmRemainingThreshold) {
84257
+ autoRollRearmBlockedProfiles.delete(profileName);
84258
+ return false;
84259
+ }
84260
+ return true;
84261
+ };
84262
+ const rearmAutoRollBlockedProfiles = (usageSummaries, rearmRemainingThreshold) => {
84263
+ for (const profileName of Array.from(autoRollRearmBlockedProfiles)) {
84264
+ const summary = usageSummaries.get(profileName);
84265
+ if (!summary || !summary.hasUsage || summary.remainingPercent > rearmRemainingThreshold) autoRollRearmBlockedProfiles.delete(profileName);
84266
+ }
84267
+ };
84268
+ const resolveProfileIdentityKeyByName = async (profileName) => {
84269
+ if (!profileName) return null;
84270
+ const profile = (await profileManager.listProfiles()).find((entry) => entry.name === profileName);
84271
+ return profile ? resolveProfileIdentityKeyFromProfile(profile) : null;
84272
+ };
84273
+ const prepareOfficialCodexProfileLaunch = async (profileName) => {
84274
+ const profile = (await profileManager.listProfiles()).find((entry) => entry.name === profileName);
84275
+ if (!profile) throw new Error(`Profile '${profileName}' was not found.`);
84276
+ if (!profile.isValid || profile.tokenStatus?.requiresUserAction) throw new Error(`Profile '${profileName}' needs auth refresh before launch.`);
84277
+ const runtime = await profileManager.prepareProfileRuntime(profileName);
84278
+ return {
84279
+ profileName,
84280
+ profileKey: resolveProfileIdentityKeyFromProfile(profile),
84281
+ profileHome: runtime.profileHome
84282
+ };
84283
+ };
84284
+ const resolveOfficialCodexProfileAccess = async () => {
84285
+ const visible = await loadLicenseVisibleProfiles(profileManager, { freshLicense: true });
84286
+ return {
84287
+ allProfileNames: new Set(visible.allProfiles.map((profile) => profile.name)),
84288
+ allowedProfileNames: new Set(visible.profiles.map((profile) => profile.name))
84289
+ };
84290
+ };
84291
+ const assertOfficialCodexProfileAccess = (profileName, access) => {
84292
+ if (!access.allProfileNames.has(profileName) || access.allowedProfileNames.has(profileName)) return;
84293
+ throw new Error("CodexUse Free supports Codex Apps for the 2 visible profiles. Upgrade to CodexUse Pro for unlimited profile windows.");
84294
+ };
84295
+ const toOfficialCodexProfileAccessRouteError = (error) => new RouteRequestError({ message: formatUserFacingError(error, { fallback: "Could not verify Codex App profile access." }) });
84296
+ const mapDefaultCodexAuthToProfile = async () => {
84297
+ const [snapshot, profiles] = await Promise.all([profileManager.captureActiveAuthSnapshot(), profileManager.listProfiles()]);
84298
+ const candidateKeys = new Set([
84299
+ resolveProfileIdentityKey({
84300
+ email: snapshot.email,
84301
+ accountId: snapshot.accountId,
84302
+ workspaceId: snapshot.workspaceId,
84303
+ metadata: {
84304
+ userId: snapshot.userId ?? void 0,
84305
+ chatgptUserId: snapshot.chatgptUserId ?? void 0,
84306
+ chatgptAccountUserId: snapshot.chatgptAccountUserId ?? void 0
84307
+ }
84308
+ }),
84309
+ snapshot.accountId,
84310
+ snapshot.email && snapshot.workspaceId ? resolveProfileIdentityKey({
84311
+ email: snapshot.email,
84312
+ workspaceId: snapshot.workspaceId
84313
+ }) : null,
84314
+ snapshot.email ? resolveProfileIdentityKey({ email: snapshot.email }) : null
84315
+ ].filter((key) => Boolean(key)));
84316
+ if (candidateKeys.size === 0) return null;
84317
+ for (const profile of profiles) {
84318
+ const matchedKey = [
84319
+ resolveProfileIdentityKeyFromProfile(profile),
84320
+ resolveLegacyProfileAccountKey(profile),
84321
+ profile.accountId ?? null,
84322
+ profile.email ? resolveProfileIdentityKey({ email: profile.email }) : null,
84323
+ profile.email && profile.workspaceId ? resolveProfileIdentityKey({
84324
+ email: profile.email,
84325
+ workspaceId: profile.workspaceId
84326
+ }) : null
84327
+ ].filter((key) => Boolean(key)).find((key) => candidateKeys.has(key)) ?? null;
84328
+ if (matchedKey) return {
84329
+ name: profile.name,
84330
+ key: matchedKey
84331
+ };
84332
+ }
84333
+ return null;
84334
+ };
84335
+ const enrichOfficialCodexInstances = async (payload) => {
84336
+ if (payload.unmanaged.length === 0) return payload;
84337
+ const defaultProfile = await mapDefaultCodexAuthToProfile();
84338
+ if (!defaultProfile) return payload;
84339
+ return {
84340
+ ...payload,
84341
+ unmanaged: payload.unmanaged.map((instance) => instance.profileName || !instance.appServerPid ? instance : {
84342
+ ...instance,
84343
+ profileName: defaultProfile.name,
84344
+ profileKey: defaultProfile.key,
84345
+ profileMatchSource: "default-auth"
84346
+ })
84347
+ };
84348
+ };
84349
+ const findObservedOfficialCodexProfileInstance = async (profileName) => {
84350
+ const instances = await enrichOfficialCodexInstances(await getOfficialCodexProfileInstances());
84351
+ if (instances.instances.some((instance) => instance.profileName === profileName && instance.running)) return null;
84352
+ const observed = instances.unmanaged.find((instance) => instance.profileName === profileName) ?? null;
84353
+ return observed ? {
84354
+ ...observed,
84355
+ appPath: instances.appPath,
84356
+ bundleId: instances.bundleId
84357
+ } : null;
84358
+ };
84359
+ const resolveAutoRollRearmActionAfterManualSwitch = async (profileName) => {
84360
+ const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
84361
+ if (!settings.enabled) return "none";
84362
+ const profile = (await profileManager.listProfiles()).find((entry) => entry.name === profileName);
84363
+ if (!profile) return "none";
84364
+ const accountId = resolveProfileIdentityKeyFromProfile(profile);
84365
+ const summary = summarizeRateLimitSnapshot((await loadCachedRateLimitSnapshots([accountId])).get(accountId) ?? profile.rateLimit ?? null);
84366
+ if (!summary.hasUsage) return "unblock";
84367
+ return summary.remainingPercent <= settings.switchRemainingThreshold ? "block" : "unblock";
84368
+ };
84369
+ const maybeRestartOfficialCodexAfterAutoRoll = async (targetProfileName, targetProfileKey) => {
84370
+ try {
84371
+ const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
84372
+ if (!settings.restartOfficialCodexOnAutoRoll) return;
84373
+ const result = await restartOfficialCodexApp({
84374
+ source: "auto-roll",
84375
+ currentProfileKey: targetProfileKey,
84376
+ launchIfNotRunning: settings.launchOfficialCodexWhenClosedOnAutoRoll
84377
+ });
83044
84378
  trackAnalyticsEvent("official_codex_restart_after_auto_roll", {
83045
84379
  status: result.status,
83046
84380
  reason: result.status === "skipped" || result.status === "failed" ? result.reason : void 0,
83047
84381
  targetProfileName
83048
84382
  });
83049
84383
  if (result.status === "failed") logger.warn("Official Codex restart after auto-roll failed", result);
83050
- else if (result.status === "restarted") logger.info("Official Codex restarted after auto-roll", result);
83051
- })().catch((error) => {
84384
+ else if (result.status === "restarted" || result.status === "started") logger.info("Official Codex restarted after auto-roll", result);
84385
+ } catch (error) {
83052
84386
  logger.warn("Official Codex restart after auto-roll threw", { error });
83053
- });
84387
+ }
83054
84388
  };
83055
84389
  const handleProfileSwitch = async (name, source = "app") => {
83056
84390
  if (!name) throw new Error("Profile name is required");
83057
84391
  const previousProfile = source === "auto-roll" ? (await profileManager.getCurrentProfile()).name ?? null : null;
83058
84392
  await profileManager.switchToProfile(name);
84393
+ let targetProfileKey = null;
84394
+ try {
84395
+ targetProfileKey = await resolveProfileIdentityKeyByName(name);
84396
+ await recordOfficialCodexProfileSwitch(targetProfileKey);
84397
+ } catch (error) {
84398
+ logger.warn("Failed to record Official Codex profile switch", {
84399
+ error,
84400
+ profileName: name
84401
+ });
84402
+ }
83059
84403
  if (source === "auto-roll") rememberAutoRollAttempt(previousProfile, name);
84404
+ else try {
84405
+ const rearmAction = await resolveAutoRollRearmActionAfterManualSwitch(name);
84406
+ if (rearmAction === "block") blockAutoRollUntilRearm(name);
84407
+ else if (rearmAction === "unblock") unblockAutoRollRearm(name);
84408
+ } catch (error) {
84409
+ logger.warn("Failed to evaluate auto-roll rearm block after manual switch", {
84410
+ error,
84411
+ profileName: name
84412
+ });
84413
+ }
83060
84414
  await publishProfileSwitched({ name });
83061
84415
  trackAnalyticsEvent("profile_switched", { source });
83062
- if (source === "auto-roll") maybeRestartOfficialCodexAfterAutoRoll(name);
84416
+ if (source === "auto-roll") await maybeRestartOfficialCodexAfterAutoRoll(name, targetProfileKey);
83063
84417
  };
83064
84418
  const maybeRunBackgroundAutoRoll = async () => {
83065
84419
  if (backgroundAutoRollInFlight) return;
@@ -83083,14 +84437,17 @@ const createServer = fn(function* () {
83083
84437
  rateLimit: snapshot
83084
84438
  };
83085
84439
  });
84440
+ rearmAutoRollBlockedProfiles(usageSummaries, settings.rearmRemainingThreshold);
83086
84441
  const currentProfile = profilesWithSnapshots.find((profile) => profile.name === currentProfileName);
83087
84442
  if (!currentProfile) return;
83088
84443
  const currentSummary = usageSummaries.get(currentProfileName) ?? summarizeRateLimitSnapshot(currentProfile.rateLimit ?? null);
83089
- if (!currentSummary.hasUsage || currentSummary.usagePercent < settings.switchThreshold) return;
83090
- const candidate = computeAutoRollCandidate(profilesWithSnapshots, usageSummaries, currentProfileName, currentSummary, settings.switchThreshold);
84444
+ if (!currentSummary.hasUsage) return;
84445
+ if (isAutoRollBlockedUntilRearm(currentProfileName, currentSummary, settings.rearmRemainingThreshold)) return;
84446
+ if (currentSummary.remainingPercent > settings.switchRemainingThreshold) return;
84447
+ const candidate = computeAutoRollCandidate(profilesWithSnapshots, usageSummaries, currentProfileName, currentSummary, settings.switchRemainingThreshold, settings.priorityOrder, autoRollRearmBlockedProfiles);
83091
84448
  if (!candidate || isRecentAutoRollAttempt(currentProfileName, candidate.profile.name)) return;
83092
- rememberAutoRollAttempt(currentProfileName, candidate.profile.name);
83093
84449
  await handleProfileSwitch(candidate.profile.name, "auto-roll");
84450
+ blockAutoRollUntilRearm(currentProfileName);
83094
84451
  } catch (error) {
83095
84452
  logger.warn("Background auto-roll failed", { error });
83096
84453
  } finally {
@@ -83123,6 +84480,9 @@ const createServer = fn(function* () {
83123
84480
  profileName: payload.profileName,
83124
84481
  snapshot: payload.snapshot ?? null
83125
84482
  }));
84483
+ maybeSendLowRemainingNotification(payload).catch((error) => {
84484
+ logger.warn("Low remaining notification failed", { error });
84485
+ });
83126
84486
  maybeRunBackgroundAutoRoll();
83127
84487
  };
83128
84488
  const handleRateLimitStateChanged = (payload) => {
@@ -83531,9 +84891,15 @@ const createServer = fn(function* () {
83531
84891
  return yield* promise(() => telegramBridge.testToken(body.token));
83532
84892
  }
83533
84893
  case WS_METHODS.profilesFetch: {
83534
- const data = yield* promise(() => loadProfilesViewData());
84894
+ const data = yield* promise(() => loadProfilesViewData({ autoImportActiveAuth: true }));
84895
+ const currentProfile = data.currentProfile ? data.profiles.find((profile) => profile.name === data.currentProfile) ?? null : null;
84896
+ const currentProfileKey = currentProfile ? resolveProfileIdentityKeyFromProfile(currentProfile) : null;
84897
+ const officialCodexStatus = yield* promise(() => getOfficialCodexSyncStatus(currentProfileKey));
83535
84898
  refreshTray();
83536
- return data;
84899
+ return {
84900
+ ...data,
84901
+ officialCodexStatus
84902
+ };
83537
84903
  }
83538
84904
  case WS_METHODS.profilesStartAuth: {
83539
84905
  const body = stripRequestTag(request.body);
@@ -83545,30 +84911,23 @@ const createServer = fn(function* () {
83545
84911
  if (yield* promise(() => profileManager.profileExists(name))) return yield* new RouteRequestError({ message: `Profile '${name}' already exists! Choose a different name.` });
83546
84912
  } else if (!(yield* promise(() => profileManager.profileExists(name)))) return yield* new RouteRequestError({ message: `Profile '${name}' not found!` });
83547
84913
  const existingSession = authSessions.get(name);
83548
- if (existingSession && !existingSession.completed) {
83549
- existingSession.process.kill();
84914
+ if (existingSession) {
84915
+ if (!existingSession.completed) existingSession.process.kill();
83550
84916
  if (existingSession.timeout) clearTimeout(existingSession.timeout);
83551
- if (existingSession.restoreConfig) existingSession.restoreConfig();
84917
+ cleanupProfileAuthSession(existingSession);
83552
84918
  authSessions.delete(name);
83553
84919
  }
83554
84920
  const startedAt = Date.now();
83555
- const initialAuthSnapshot = yield* promise(() => profileManager.captureActiveAuthSnapshot());
83556
- let cleanupConfig = null;
83557
- const restoreConfig = async () => {
83558
- if (!cleanupConfig) return;
83559
- const restore = cleanupConfig;
83560
- cleanupConfig = null;
83561
- try {
83562
- await restore();
83563
- } catch (error) {
83564
- console.warn("Failed to restore Codex config after login attempt:", error);
83565
- }
83566
- };
84921
+ const loginHome = yield* promise(() => createIsolatedCodexLoginHome());
84922
+ const initialAuthSnapshot = yield* promise(() => profileManager.captureAuthSnapshot(loginHome.authPath));
83567
84923
  try {
83568
- const storageOptions = yield* promise(() => resolveCodexHomeStorageOptions());
83569
- cleanupConfig = yield* promise(() => sanitizeCodexConfigForCli(storageOptions));
83570
84924
  const codexPath = yield* promise(() => resolveCodexBinaryOrThrow("Codex CLI is required to start authentication."));
83571
- const loginEnv = buildCliEnv(codexPath, { ELECTRON_RUN_AS_NODE: "1" });
84925
+ const loginEnv = buildCliEnv(codexPath, {
84926
+ ELECTRON_RUN_AS_NODE: "1",
84927
+ HOME: loginHome.home,
84928
+ USERPROFILE: loginHome.home,
84929
+ CODEX_HOME: loginHome.home
84930
+ });
83572
84931
  const command = buildCodexCommand$1(codexPath, ["login"], loginEnv);
83573
84932
  const loginProcess = spawn(command.command, command.args, {
83574
84933
  stdio: [
@@ -83588,7 +84947,8 @@ const createServer = fn(function* () {
83588
84947
  mode,
83589
84948
  startedAt,
83590
84949
  initialAuthSnapshot,
83591
- restoreConfig
84950
+ authPath: loginHome.authPath,
84951
+ cleanupAuthHome: loginHome.cleanup
83592
84952
  };
83593
84953
  authSessions.set(name, session);
83594
84954
  loginProcess.stdout?.on("data", (chunk) => {
@@ -83608,7 +84968,6 @@ const createServer = fn(function* () {
83608
84968
  session.completed = true;
83609
84969
  session.exitCode = code;
83610
84970
  if (session.timeout) clearTimeout(session.timeout);
83611
- restoreConfig();
83612
84971
  });
83613
84972
  loginProcess.on("error", (error) => {
83614
84973
  session.error = compactErrorText(error instanceof Error ? error.message : String(error)) || "Authentication failed.";
@@ -83618,7 +84977,6 @@ const createServer = fn(function* () {
83618
84977
  clearTimeout(session.timeout);
83619
84978
  session.timeout = void 0;
83620
84979
  }
83621
- restoreConfig();
83622
84980
  });
83623
84981
  session.timeout = setTimeout(() => {
83624
84982
  if (!session.completed) {
@@ -83632,7 +84990,7 @@ const createServer = fn(function* () {
83632
84990
  return;
83633
84991
  } catch (error) {
83634
84992
  trackAnalyticsEvent("profile_auth_start_failed", { mode });
83635
- yield* promise(() => restoreConfig());
84993
+ yield* promise(() => loginHome.cleanup());
83636
84994
  throw error;
83637
84995
  }
83638
84996
  }
@@ -83657,38 +85015,41 @@ const createServer = fn(function* () {
83657
85015
  const session = authSessions.get(body.name);
83658
85016
  if (!session) return yield* new RouteRequestError({ message: "No authentication session found" });
83659
85017
  if (!session.completed) return yield* new RouteRequestError({ message: "Authentication not yet completed" });
83660
- if (session.exitCode !== 0) {
83661
- authSessions.delete(body.name);
83662
- trackAnalyticsEvent("profile_auth_failed", { mode: session.mode });
83663
- return yield* new RouteRequestError({ message: formatUserFacingError(session.error ?? `Authentication failed with code ${session.exitCode}`, { fallback: "Authentication failed. Try again." }) });
83664
- }
83665
- if (session.mode === "create") {
83666
- const currentAuthSnapshot = yield* promise(() => profileManager.captureActiveAuthSnapshot());
83667
- if (currentAuthSnapshot.fingerprint === (session.initialAuthSnapshot?.fingerprint ?? null)) {
83668
- authSessions.delete(body.name);
85018
+ try {
85019
+ if (session.exitCode !== 0) {
85020
+ trackAnalyticsEvent("profile_auth_failed", { mode: session.mode });
85021
+ return yield* new RouteRequestError({ message: formatUserFacingError(session.error ?? `Authentication failed with code ${session.exitCode}`, { fallback: "Authentication failed. Try again." }) });
85022
+ }
85023
+ if (!session.authPath) {
83669
85024
  trackAnalyticsEvent("profile_auth_complete_failed", { mode: session.mode });
83670
- const activeOwner = describeActiveAuthOwner(currentAuthSnapshot);
83671
- const ownerText = activeOwner ? ` The active auth still belongs to '${activeOwner}'.` : "";
83672
- return yield* new RouteRequestError({ message: `Codex login finished without updating the active auth for '${body.name}'.${ownerText} Finish the browser login for the new account and try again.` });
85025
+ return yield* new RouteRequestError({ message: "Authentication session is missing its isolated auth file." });
85026
+ }
85027
+ if (session.mode === "create") {
85028
+ const currentAuthSnapshot = yield* promise(() => profileManager.captureAuthSnapshot(session.authPath ?? void 0));
85029
+ if (currentAuthSnapshot.fingerprint === (session.initialAuthSnapshot?.fingerprint ?? null)) {
85030
+ trackAnalyticsEvent("profile_auth_complete_failed", { mode: session.mode });
85031
+ const activeOwner = describeActiveAuthOwner(currentAuthSnapshot);
85032
+ const ownerText = activeOwner ? ` Isolated auth still belongs to '${activeOwner}'.` : "";
85033
+ return yield* new RouteRequestError({ message: `Codex login finished without producing new auth for '${body.name}'.${ownerText} Finish the browser login for the new account and try again.` });
85034
+ }
83673
85035
  }
83674
- }
83675
- try {
83676
85036
  if (session.mode === "refresh") {
83677
- yield* promise(() => profileManager.refreshProfileAuth(body.name));
85037
+ yield* promise(() => profileManager.refreshProfileAuthFromFile(body.name, session.authPath));
83678
85038
  trackAnalyticsEvent("profile_tokens_refreshed", { mode: session.mode });
83679
85039
  } else {
83680
85040
  yield* promise(() => assertProfileCreationAllowed(profileManager));
83681
- yield* promise(() => profileManager.createProfile(body.name));
85041
+ yield* promise(() => profileManager.createProfileFromAuthFile(body.name, session.authPath));
83682
85042
  trackAnalyticsEvent("profile_created", { mode: session.mode });
83683
85043
  }
83684
85044
  trackAnalyticsEvent("profile_auth_completed", { mode: session.mode });
83685
- authSessions.delete(body.name);
83686
85045
  refreshTray();
83687
85046
  return;
83688
85047
  } catch (error) {
83689
85048
  trackAnalyticsEvent("profile_auth_complete_failed", { mode: session.mode });
83690
- authSessions.delete(body.name);
83691
85049
  throw error;
85050
+ } finally {
85051
+ authSessions.delete(body.name);
85052
+ yield* promise(() => cleanupProfileAuthSession(session));
83692
85053
  }
83693
85054
  }
83694
85055
  case WS_METHODS.profilesSwitch: {
@@ -83712,13 +85073,39 @@ const createServer = fn(function* () {
83712
85073
  }
83713
85074
  case WS_METHODS.profilesDelete: {
83714
85075
  const body = stripRequestTag(request.body);
83715
- if (!body.name?.trim()) return yield* new RouteRequestError({ message: "Profile name is required" });
83716
- yield* promise(() => profileManager.deleteProfile(body.name));
83717
- trackAnalyticsEvent("profile_deleted");
83718
- const currentProfile = yield* promise(() => profileManager.getCurrentProfile());
85076
+ const name = body.name?.trim() ?? "";
85077
+ if (!name) return yield* new RouteRequestError({ message: "Profile name is required" });
85078
+ const replacementProfileName = typeof body.replacementProfileName === "string" && body.replacementProfileName.trim().length > 0 ? body.replacementProfileName.trim() : null;
85079
+ const removeActiveAuth = body.removeActiveAuth === true;
85080
+ if (replacementProfileName && removeActiveAuth) return yield* new RouteRequestError({ message: "Choose either a replacement profile or active auth removal." });
85081
+ if (replacementProfileName) {
85082
+ yield* promise(() => profileManager.deleteActiveProfileAndSwitch(name, replacementProfileName));
85083
+ let targetProfileKey = null;
85084
+ try {
85085
+ targetProfileKey = yield* promise(() => resolveProfileIdentityKeyByName(replacementProfileName));
85086
+ yield* promise(() => recordOfficialCodexProfileSwitch(targetProfileKey));
85087
+ } catch (error) {
85088
+ logger.warn("Failed to record Official Codex profile switch after deletion", {
85089
+ error,
85090
+ profileName: replacementProfileName
85091
+ });
85092
+ }
85093
+ yield* promise(() => publishProfileSwitched({ name: replacementProfileName }));
85094
+ trackAnalyticsEvent("profile_switched", { source: "app" });
85095
+ trackAnalyticsEvent("profile_deleted", { mode: "delete-and-switch" });
85096
+ refreshTray();
85097
+ return;
85098
+ }
85099
+ if (removeActiveAuth) {
85100
+ yield* promise(() => profileManager.deleteActiveProfileAndAuth(name));
85101
+ trackAnalyticsEvent("profile_deleted", { mode: "delete-and-sign-out" });
85102
+ refreshTray();
85103
+ return;
85104
+ }
85105
+ if ((yield* promise(() => profileManager.getCurrentProfile())).name === name) return yield* new RouteRequestError({ message: "This profile is the active Codex CLI login. Switch to another profile or confirm signing out before deleting it." });
85106
+ yield* promise(() => profileManager.deleteProfile(name));
85107
+ trackAnalyticsEvent("profile_deleted", { mode: "profile-only" });
83719
85108
  refreshTray();
83720
- const currentProfileName = typeof currentProfile.name === "string" && currentProfile.name.length > 0 ? currentProfile.name : null;
83721
- if (currentProfileName) yield* promise(() => publishProfileSwitched({ name: currentProfileName }));
83722
85109
  return;
83723
85110
  }
83724
85111
  case WS_METHODS.profilesExport: {
@@ -83739,12 +85126,12 @@ const createServer = fn(function* () {
83739
85126
  clearTimeout(session.timeout);
83740
85127
  session.timeout = void 0;
83741
85128
  }
83742
- if (session.restoreConfig) session.restoreConfig();
85129
+ yield* promise(() => cleanupProfileAuthSession(session));
83743
85130
  authSessions.delete(body.name);
83744
85131
  return;
83745
85132
  }
83746
85133
  case WS_METHODS.rateLimitsRefreshNow: {
83747
- const profiles = yield* promise(() => profileManager.listProfiles());
85134
+ const { profiles } = yield* promise(() => loadLicenseVisibleProfiles(profileManager));
83748
85135
  const profileNames = Array.from(new Set(profiles.filter((profile) => profile.isValid).map((profile) => profile.name).filter(Boolean)));
83749
85136
  return { enqueued: (yield* promise(() => enqueueAccountsRefreshNow(profileNames))).enqueuedAccountIds.length };
83750
85137
  }
@@ -83783,15 +85170,197 @@ const createServer = fn(function* () {
83783
85170
  case WS_METHODS.autoRollSaveSettings: {
83784
85171
  const normalized = normalizeAutoRollSettings(stripRequestTag(request.body).settings);
83785
85172
  yield* promise(() => persistAutoRollSettings(normalized));
85173
+ maybeRunBackgroundAutoRoll();
83786
85174
  trackAnalyticsEvent("auto_roll_settings_saved", {
83787
85175
  enabled: normalized.enabled,
83788
- warningThreshold: normalized.warningThreshold,
83789
- switchThreshold: normalized.switchThreshold,
83790
- restartOfficialCodexOnAutoRoll: normalized.restartOfficialCodexOnAutoRoll
85176
+ rearmRemainingThreshold: normalized.rearmRemainingThreshold,
85177
+ switchRemainingThreshold: normalized.switchRemainingThreshold,
85178
+ restartOfficialCodexOnAutoRoll: normalized.restartOfficialCodexOnAutoRoll,
85179
+ launchOfficialCodexWhenClosedOnAutoRoll: normalized.launchOfficialCodexWhenClosedOnAutoRoll,
85180
+ priorityCount: normalized.priorityOrder.length,
85181
+ lowRemainingNotificationEnabled: normalized.lowRemainingNotificationEnabled,
85182
+ lowRemainingNotificationThreshold: normalized.lowRemainingNotificationThreshold
83791
85183
  });
83792
85184
  return normalized;
83793
85185
  }
83794
- case WS_METHODS.officialCodexRestart: return yield* promise(() => restartOfficialCodexApp());
85186
+ case WS_METHODS.officialCodexRestart: return yield* promise(async () => {
85187
+ return restartOfficialCodexApp({
85188
+ source: "manual",
85189
+ currentProfileKey: await resolveProfileIdentityKeyByName((await profileManager.getCurrentProfile()).name ?? null)
85190
+ });
85191
+ });
85192
+ case WS_METHODS.officialCodexInstances: return yield* promise(async () => enrichOfficialCodexInstances(await getOfficialCodexProfileInstances()));
85193
+ case WS_METHODS.officialCodexLaunchProfile: {
85194
+ const name = stripRequestTag(request.body).name?.trim() ?? "";
85195
+ const access = yield* tryPromise({
85196
+ try: () => resolveOfficialCodexProfileAccess(),
85197
+ catch: toOfficialCodexProfileAccessRouteError
85198
+ });
85199
+ yield* try_({
85200
+ try: () => assertOfficialCodexProfileAccess(name, access),
85201
+ catch: toOfficialCodexProfileAccessRouteError
85202
+ });
85203
+ const observed = yield* promise(() => findObservedOfficialCodexProfileInstance(name));
85204
+ if (observed) return {
85205
+ status: "already-running",
85206
+ profileName: name,
85207
+ instance: {
85208
+ profileName: name,
85209
+ profileKey: observed.profileKey ?? null,
85210
+ appPath: observed.appPath,
85211
+ bundleId: observed.bundleId,
85212
+ pid: observed.pid,
85213
+ appServerPid: observed.appServerPid,
85214
+ running: true,
85215
+ startedAt: observed.startedAt ?? null,
85216
+ uptimeMs: observed.uptimeMs ?? null,
85217
+ launchedAt: null,
85218
+ lastVerifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
85219
+ lastStatus: "running",
85220
+ lastError: null
85221
+ },
85222
+ reason: null
85223
+ };
85224
+ const options = yield* tryPromise({
85225
+ try: () => prepareOfficialCodexProfileLaunch(name),
85226
+ catch: (error) => new RouteRequestError({ message: formatUserFacingError(error, { fallback: "Could not prepare Codex App profile." }) })
85227
+ });
85228
+ trackAnalyticsEvent("official_codex_profile_launch_requested");
85229
+ return yield* promise(() => launchOfficialCodexProfileInstance(options));
85230
+ }
85231
+ case WS_METHODS.officialCodexFocusProfile: {
85232
+ const name = stripRequestTag(request.body).name?.trim() ?? "";
85233
+ const access = yield* tryPromise({
85234
+ try: () => resolveOfficialCodexProfileAccess(),
85235
+ catch: toOfficialCodexProfileAccessRouteError
85236
+ });
85237
+ yield* try_({
85238
+ try: () => assertOfficialCodexProfileAccess(name, access),
85239
+ catch: toOfficialCodexProfileAccessRouteError
85240
+ });
85241
+ return yield* promise(async () => {
85242
+ const observed = await findObservedOfficialCodexProfileInstance(name);
85243
+ if (observed) return focusOfficialCodexObservedProfileInstance(name, observed.pid, observed.appServerPid);
85244
+ return focusOfficialCodexProfileInstance(name);
85245
+ });
85246
+ }
85247
+ case WS_METHODS.officialCodexStopProfile: {
85248
+ const name = stripRequestTag(request.body).name?.trim() ?? "";
85249
+ const access = yield* tryPromise({
85250
+ try: () => resolveOfficialCodexProfileAccess(),
85251
+ catch: toOfficialCodexProfileAccessRouteError
85252
+ });
85253
+ yield* try_({
85254
+ try: () => assertOfficialCodexProfileAccess(name, access),
85255
+ catch: toOfficialCodexProfileAccessRouteError
85256
+ });
85257
+ return yield* promise(async () => {
85258
+ const observed = await findObservedOfficialCodexProfileInstance(name);
85259
+ if (observed) return stopOfficialCodexObservedProfileInstance(name, observed.pid, observed.appServerPid);
85260
+ return stopOfficialCodexProfileInstance(name);
85261
+ });
85262
+ }
85263
+ case WS_METHODS.officialCodexRestartProfile: {
85264
+ const name = stripRequestTag(request.body).name?.trim() ?? "";
85265
+ const access = yield* tryPromise({
85266
+ try: () => resolveOfficialCodexProfileAccess(),
85267
+ catch: toOfficialCodexProfileAccessRouteError
85268
+ });
85269
+ yield* try_({
85270
+ try: () => assertOfficialCodexProfileAccess(name, access),
85271
+ catch: toOfficialCodexProfileAccessRouteError
85272
+ });
85273
+ const options = yield* tryPromise({
85274
+ try: () => prepareOfficialCodexProfileLaunch(name),
85275
+ catch: (error) => new RouteRequestError({ message: formatUserFacingError(error, { fallback: "Could not prepare Codex App profile." }) })
85276
+ });
85277
+ trackAnalyticsEvent("official_codex_profile_restart_requested");
85278
+ return yield* promise(async () => {
85279
+ const observed = await findObservedOfficialCodexProfileInstance(name);
85280
+ if (observed) {
85281
+ const stopped = await stopOfficialCodexObservedProfileInstance(name, observed.pid, observed.appServerPid);
85282
+ if (stopped.status === "failed") return stopped;
85283
+ const launched = await launchOfficialCodexProfileInstance(options);
85284
+ return {
85285
+ ...launched,
85286
+ status: launched.status === "started" ? "restarted" : launched.status
85287
+ };
85288
+ }
85289
+ return restartOfficialCodexProfileInstance(options);
85290
+ });
85291
+ }
85292
+ case WS_METHODS.officialCodexLaunchProfiles: {
85293
+ const body = stripRequestTag(request.body);
85294
+ const rawNames = Array.isArray(body.names) ? body.names : [];
85295
+ const names = Array.from(new Set(rawNames.map((name) => typeof name === "string" ? name.trim() : "").filter(Boolean)));
85296
+ const access = yield* tryPromise({
85297
+ try: () => resolveOfficialCodexProfileAccess(),
85298
+ catch: toOfficialCodexProfileAccessRouteError
85299
+ });
85300
+ yield* try_({
85301
+ try: () => {
85302
+ for (const name of names) assertOfficialCodexProfileAccess(name, access);
85303
+ },
85304
+ catch: toOfficialCodexProfileAccessRouteError
85305
+ });
85306
+ const results = [];
85307
+ for (const name of names) {
85308
+ const observed = yield* promise(() => findObservedOfficialCodexProfileInstance(name));
85309
+ if (observed) {
85310
+ results.push({
85311
+ status: "already-running",
85312
+ profileName: name,
85313
+ instance: {
85314
+ profileName: name,
85315
+ profileKey: observed.profileKey ?? null,
85316
+ appPath: observed.appPath,
85317
+ bundleId: observed.bundleId,
85318
+ pid: observed.pid,
85319
+ appServerPid: observed.appServerPid,
85320
+ running: true,
85321
+ startedAt: observed.startedAt ?? null,
85322
+ uptimeMs: observed.uptimeMs ?? null,
85323
+ launchedAt: null,
85324
+ lastVerifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
85325
+ lastStatus: "running",
85326
+ lastError: null
85327
+ },
85328
+ reason: null
85329
+ });
85330
+ continue;
85331
+ }
85332
+ const options = yield* tryPromise({
85333
+ try: () => prepareOfficialCodexProfileLaunch(name),
85334
+ catch: (error) => new RouteRequestError({ message: formatUserFacingError(error, { fallback: `Could not prepare Codex App profile '${name}'.` }) })
85335
+ });
85336
+ results.push(yield* promise(() => launchOfficialCodexProfileInstance(options)));
85337
+ }
85338
+ trackAnalyticsEvent("official_codex_profiles_launch_requested", { count: results.length });
85339
+ return { results };
85340
+ }
85341
+ case WS_METHODS.officialCodexStopProfiles: {
85342
+ const body = stripRequestTag(request.body);
85343
+ const rawNames = Array.isArray(body.names) ? body.names : [];
85344
+ const names = Array.from(new Set(rawNames.map((name) => typeof name === "string" ? name.trim() : "").filter(Boolean)));
85345
+ const access = yield* tryPromise({
85346
+ try: () => resolveOfficialCodexProfileAccess(),
85347
+ catch: toOfficialCodexProfileAccessRouteError
85348
+ });
85349
+ yield* try_({
85350
+ try: () => {
85351
+ for (const name of names) assertOfficialCodexProfileAccess(name, access);
85352
+ },
85353
+ catch: toOfficialCodexProfileAccessRouteError
85354
+ });
85355
+ const results = [];
85356
+ for (const name of names) results.push(yield* promise(async () => {
85357
+ const observed = await findObservedOfficialCodexProfileInstance(name);
85358
+ if (observed) return stopOfficialCodexObservedProfileInstance(name, observed.pid, observed.appServerPid);
85359
+ return stopOfficialCodexProfileInstance(name);
85360
+ }));
85361
+ trackAnalyticsEvent("official_codex_profiles_stop_requested", { count: results.length });
85362
+ return { results };
85363
+ }
83795
85364
  case WS_METHODS.cliInfo: return yield* promise(() => getCodexCliInfo());
83796
85365
  case WS_METHODS.cliStatus: return yield* promise(() => getCliInstallStatusWithFreshness());
83797
85366
  case WS_METHODS.cliCheckFreshness: {
@@ -83871,8 +85440,8 @@ const ServerLive = succeed$3(Server, {
83871
85440
  //#region src/serverLogger.ts
83872
85441
  const ServerLoggerLive = gen(function* () {
83873
85442
  const config = yield* ServerConfig$1;
83874
- const logDir = path.join(config.stateDir, "logs");
83875
- const logPath = path.join(logDir, "server.log");
85443
+ const logDir = nodePath.join(config.stateDir, "logs");
85444
+ const logPath = nodePath.join(logDir, "server.log");
83876
85445
  yield* sync(() => {
83877
85446
  fs.mkdirSync(logDir, { recursive: true });
83878
85447
  });