codexuse-cli 3.9.1 → 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";
@@ -46821,6 +46821,7 @@ const ThreadMetaUpdateCommand = Struct({
46821
46821
  origin: optional$2(ThreadOrigin),
46822
46822
  branch: optional$2(NullOr(TrimmedNonEmptyString)),
46823
46823
  worktreePath: optional$2(NullOr(TrimmedNonEmptyString)),
46824
+ deletedAt: optional$2(NullOr(IsoDateTime)),
46824
46825
  updatedAt: optional$2(IsoDateTime)
46825
46826
  });
46826
46827
  const ThreadRuntimeModeSetCommand = Struct({
@@ -47140,6 +47141,7 @@ const ThreadMetaUpdatedPayload$1 = Struct({
47140
47141
  origin: optional$2(ThreadOrigin),
47141
47142
  branch: optional$2(NullOr(TrimmedNonEmptyString)),
47142
47143
  worktreePath: optional$2(NullOr(TrimmedNonEmptyString)),
47144
+ deletedAt: optional$2(NullOr(IsoDateTime)),
47143
47145
  updatedAt: IsoDateTime
47144
47146
  });
47145
47147
  const ThreadRuntimeModeSetPayload$1 = Struct({
@@ -47395,8 +47397,27 @@ const ProjectionPendingApprovalDecision = NullOr(ProviderApprovalDecision);
47395
47397
  const DispatchResult = Struct({ sequence: NonNegativeInt });
47396
47398
  const OrchestrationGetSnapshotInput = Struct({});
47397
47399
  const OrchestrationGetThreadSnapshotInput = Struct({ threadId: ThreadId });
47398
- const OrchestrationSyncExternalThreadsInput = Struct({});
47399
- const OrchestrationSyncExternalThreadsResult = Struct({ syncedAt: IsoDateTime });
47400
+ const OrchestrationSyncExternalThreadsInput = Struct({ projectId: optional$2(ProjectId) });
47401
+ const OrchestrationSyncExternalThreadsStats = Struct({
47402
+ bootstrappedProjectCount: NonNegativeInt,
47403
+ syncedProjectCount: NonNegativeInt,
47404
+ createdThreadCount: NonNegativeInt,
47405
+ restoredThreadCount: NonNegativeInt,
47406
+ foregroundSummaryCount: NonNegativeInt,
47407
+ foregroundHydratedThreadCount: NonNegativeInt,
47408
+ foregroundImportedMessageCount: NonNegativeInt,
47409
+ foregroundImportedActivityCount: NonNegativeInt,
47410
+ foregroundImportedPlanCount: NonNegativeInt,
47411
+ backgroundBackfillProjectCount: NonNegativeInt,
47412
+ parityWorkspaceCount: NonNegativeInt,
47413
+ hadErrors: Boolean$2
47414
+ });
47415
+ const OrchestrationSyncExternalThreadsResult = Struct({
47416
+ syncedAt: IsoDateTime,
47417
+ completed: Boolean$2,
47418
+ queued: Boolean$2,
47419
+ stats: optional$2(OrchestrationSyncExternalThreadsStats)
47420
+ });
47400
47421
  const OrchestrationGetTurnDiffInput = TurnCountRange.mapFields(assign({ threadId: ThreadId }), { unsafePreserveChecks: true });
47401
47422
  const OrchestrationGetTurnDiffResult = ThreadTurnDiff;
47402
47423
  const OrchestrationGetFullThreadDiffInput = Struct({
@@ -47793,7 +47814,8 @@ const UserInputQuestion = Struct({
47793
47814
  id: TrimmedNonEmptyStringSchema$1,
47794
47815
  header: TrimmedNonEmptyStringSchema$1,
47795
47816
  question: TrimmedNonEmptyStringSchema$1,
47796
- options: Array$1(UserInputQuestionOption)
47817
+ options: Array$1(UserInputQuestionOption),
47818
+ multiSelect: optional$2(Boolean$2)
47797
47819
  });
47798
47820
  const UserInputRequestedPayload = Struct({ questions: Array$1(UserInputQuestion) });
47799
47821
  const UserInputResolvedPayload = Struct({ answers: UnknownRecordSchema });
@@ -48742,6 +48764,13 @@ const WS_METHODS = {
48742
48764
  autoRollGetSettings: "autoRoll.getSettings",
48743
48765
  autoRollSaveSettings: "autoRoll.saveSettings",
48744
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",
48745
48774
  cliInfo: "cli.info",
48746
48775
  cliStatus: "cli.status",
48747
48776
  cliCheckFreshness: "cli.checkFreshness",
@@ -48774,7 +48803,8 @@ const WS_CHANNELS = {
48774
48803
  profilesSwitched: "profiles.switched",
48775
48804
  rateLimitsUpdated: "rateLimits.updated",
48776
48805
  rateLimitsState: "rateLimits.state",
48777
- cliStatusUpdated: "cli.statusUpdated"
48806
+ cliStatusUpdated: "cli.statusUpdated",
48807
+ externalThreadSyncProgress: "externalThreadSync.progress"
48778
48808
  };
48779
48809
  const tagRequestBody = (tag$1, schema) => schema.mapFields(assign({ _tag: tag(tag$1) }), { unsafePreserveChecks: true });
48780
48810
  const WebSocketRequestBody = Union([
@@ -48885,7 +48915,11 @@ const WebSocketRequestBody = Union([
48885
48915
  accountKey: TrimmedNonEmptyString,
48886
48916
  groupName: Union([TrimmedNonEmptyString, Null])
48887
48917
  })),
48888
- 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
+ })),
48889
48923
  tagRequestBody(WS_METHODS.profilesExport, Struct({ name: TrimmedNonEmptyString })),
48890
48924
  tagRequestBody(WS_METHODS.profilesCancelAuth, Struct({ name: TrimmedNonEmptyString })),
48891
48925
  tagRequestBody(WS_METHODS.rateLimitsRefreshNow, Struct({})),
@@ -48897,6 +48931,13 @@ const WebSocketRequestBody = Union([
48897
48931
  tagRequestBody(WS_METHODS.autoRollGetSettings, Struct({})),
48898
48932
  tagRequestBody(WS_METHODS.autoRollSaveSettings, Struct({ settings: Unknown })),
48899
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) })),
48900
48941
  tagRequestBody(WS_METHODS.cliInfo, Struct({})),
48901
48942
  tagRequestBody(WS_METHODS.cliStatus, Struct({})),
48902
48943
  tagRequestBody(WS_METHODS.cliCheckFreshness, Struct({})),
@@ -48918,6 +48959,42 @@ const WsWelcomePayload = Struct({
48918
48959
  bootstrapProjectId: optional$2(ProjectId),
48919
48960
  bootstrapThreadId: optional$2(ThreadId)
48920
48961
  });
48962
+ const ExternalThreadSyncProjectProgress = Struct({
48963
+ projectId: ProjectId,
48964
+ workspaceRoot: TrimmedNonEmptyString,
48965
+ title: TrimmedNonEmptyString,
48966
+ scannedThreads: NonNegativeInt,
48967
+ createdThreads: NonNegativeInt,
48968
+ restoredThreads: NonNegativeInt,
48969
+ hydratedThreads: NonNegativeInt,
48970
+ importedMessages: NonNegativeInt,
48971
+ importedActivities: NonNegativeInt,
48972
+ importedPlans: NonNegativeInt
48973
+ });
48974
+ const ExternalThreadSyncProgressPayload = Struct({
48975
+ status: Literals([
48976
+ "started",
48977
+ "project",
48978
+ "completed",
48979
+ "failed"
48980
+ ]),
48981
+ runId: TrimmedNonEmptyString,
48982
+ startedAt: TrimmedNonEmptyString,
48983
+ updatedAt: TrimmedNonEmptyString,
48984
+ finishedAt: optional$2(TrimmedNonEmptyString),
48985
+ currentProjectIndex: NonNegativeInt,
48986
+ totalProjects: NonNegativeInt,
48987
+ project: optional$2(ExternalThreadSyncProjectProgress),
48988
+ projects: optional$2(Array$1(ExternalThreadSyncProjectProgress)),
48989
+ scannedThreads: NonNegativeInt,
48990
+ createdThreads: NonNegativeInt,
48991
+ restoredThreads: NonNegativeInt,
48992
+ hydratedThreads: NonNegativeInt,
48993
+ importedMessages: NonNegativeInt,
48994
+ importedActivities: NonNegativeInt,
48995
+ importedPlans: NonNegativeInt,
48996
+ hadErrors: Boolean$2
48997
+ });
48921
48998
  const makeWsPushSchema = (channel, payload) => Struct({
48922
48999
  type: Literal("push"),
48923
49000
  sequence: WsPushSequence,
@@ -48934,6 +49011,7 @@ const WsPushProfilesSwitched = makeWsPushSchema(WS_CHANNELS.profilesSwitched, Un
48934
49011
  const WsPushRateLimitsUpdated = makeWsPushSchema(WS_CHANNELS.rateLimitsUpdated, Unknown);
48935
49012
  const WsPushRateLimitsState = makeWsPushSchema(WS_CHANNELS.rateLimitsState, Unknown);
48936
49013
  const WsPushCliStatusUpdated = makeWsPushSchema(WS_CHANNELS.cliStatusUpdated, Unknown);
49014
+ const WsPushExternalThreadSyncProgress = makeWsPushSchema(WS_CHANNELS.externalThreadSyncProgress, ExternalThreadSyncProgressPayload);
48937
49015
  const WsPushOrchestrationDomainEvent = makeWsPushSchema(ORCHESTRATION_WS_CHANNELS.domainEvent, OrchestrationEvent);
48938
49016
  const WsPushChannelSchema = Literals([
48939
49017
  WS_CHANNELS.gitActionProgress,
@@ -48946,6 +49024,7 @@ const WsPushChannelSchema = Literals([
48946
49024
  WS_CHANNELS.rateLimitsUpdated,
48947
49025
  WS_CHANNELS.rateLimitsState,
48948
49026
  WS_CHANNELS.cliStatusUpdated,
49027
+ WS_CHANNELS.externalThreadSyncProgress,
48949
49028
  ORCHESTRATION_WS_CHANNELS.domainEvent
48950
49029
  ]);
48951
49030
  const WsPush = Union([
@@ -48959,6 +49038,7 @@ const WsPush = Union([
48959
49038
  WsPushRateLimitsUpdated,
48960
49039
  WsPushRateLimitsState,
48961
49040
  WsPushCliStatusUpdated,
49041
+ WsPushExternalThreadSyncProgress,
48962
49042
  WsPushOrchestrationDomainEvent
48963
49043
  ]);
48964
49044
  const WsPushEnvelopeBase = Struct({
@@ -49135,7 +49215,7 @@ const launchDetached = (launch) => gen(function* () {
49135
49215
  });
49136
49216
  const make$9 = gen(function* () {
49137
49217
  const open = yield* tryPromise({
49138
- try: () => import("./open-DX6_a9Ta.mjs"),
49218
+ try: () => import("./open-BWXrZXJl.mjs"),
49139
49219
  catch: (cause) => new OpenError({
49140
49220
  message: "failed to load browser opener",
49141
49221
  cause
@@ -52736,17 +52816,24 @@ const decideOrchestrationCommand = fn("decideOrchestrationCommand")(function* ({
52736
52816
  };
52737
52817
  }
52738
52818
  case "thread.meta.update": {
52739
- yield* requireThread({
52819
+ const thread = yield* requireThread({
52740
52820
  readModel,
52741
52821
  command,
52742
52822
  threadId: command.threadId
52743
52823
  });
52744
- if (command.projectId !== void 0) yield* requireThreadProject({
52745
- readModel,
52746
- command,
52747
- threadId: command.threadId,
52748
- projectId: command.projectId
52749
- });
52824
+ if (command.projectId !== void 0) {
52825
+ yield* requireProject({
52826
+ readModel,
52827
+ command,
52828
+ projectId: command.projectId
52829
+ });
52830
+ if (!(thread.deletedAt !== null && command.deletedAt === null)) yield* requireThreadProject({
52831
+ readModel,
52832
+ command,
52833
+ threadId: command.threadId,
52834
+ projectId: command.projectId
52835
+ });
52836
+ }
52750
52837
  const occurredAt = nowIso$2();
52751
52838
  return {
52752
52839
  ...withEventBase({
@@ -52764,6 +52851,7 @@ const decideOrchestrationCommand = fn("decideOrchestrationCommand")(function* ({
52764
52851
  ...command.origin !== void 0 ? { origin: command.origin } : {},
52765
52852
  ...command.branch !== void 0 ? { branch: command.branch } : {},
52766
52853
  ...command.worktreePath !== void 0 ? { worktreePath: command.worktreePath } : {},
52854
+ ...command.deletedAt !== void 0 ? { deletedAt: command.deletedAt } : {},
52767
52855
  updatedAt: command.updatedAt ?? occurredAt
52768
52856
  }
52769
52857
  };
@@ -53369,6 +53457,7 @@ function projectEvent(model, event) {
53369
53457
  } : {},
53370
53458
  ...payload.branch !== void 0 ? { branch: payload.branch } : {},
53371
53459
  ...payload.worktreePath !== void 0 ? { worktreePath: payload.worktreePath } : {},
53460
+ ...payload.deletedAt !== void 0 ? { deletedAt: payload.deletedAt } : {},
53372
53461
  updatedAt: payload.updatedAt
53373
53462
  })
53374
53463
  })));
@@ -54226,8 +54315,8 @@ const IGNORED_DIRECTORY_NAMES = new Set([
54226
54315
  ".cache"
54227
54316
  ]);
54228
54317
  function expandHomePath(input) {
54229
- if (input === "~") return os.homedir();
54230
- 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));
54231
54320
  return input;
54232
54321
  }
54233
54322
  function isWindowsAbsolutePath(input) {
@@ -54236,13 +54325,13 @@ function isWindowsAbsolutePath(input) {
54236
54325
  function resolveBrowseTarget(input) {
54237
54326
  if (process.platform !== "win32" && isWindowsAbsolutePath(input.partialPath)) throw new Error("Windows-style paths are only supported on Windows.");
54238
54327
  const expanded = expandHomePath(input.partialPath);
54239
- if (path.isAbsolute(expanded) || input.partialPath.startsWith("~")) return path.resolve(expanded);
54240
- 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);
54241
54330
  }
54242
54331
  const workspaceIndexCache = /* @__PURE__ */ new Map();
54243
54332
  const inFlightWorkspaceIndexBuilds = /* @__PURE__ */ new Map();
54244
54333
  function toPosixPath(input) {
54245
- return input.split(path.sep).join("/");
54334
+ return input.split(nodePath.sep).join("/");
54246
54335
  }
54247
54336
  function parentPathOf(input) {
54248
54337
  const separatorIndex = input.lastIndexOf("/");
@@ -54457,7 +54546,7 @@ async function buildWorkspaceIndex(cwd) {
54457
54546
  const currentDirectories = pendingDirectories;
54458
54547
  pendingDirectories = [];
54459
54548
  const candidateEntriesByDirectory = (await mapWithConcurrency(currentDirectories, WORKSPACE_SCAN_READDIR_CONCURRENCY, async (relativeDir) => {
54460
- const absoluteDir = relativeDir ? path.join(cwd, relativeDir) : cwd;
54549
+ const absoluteDir = relativeDir ? nodePath.join(cwd, relativeDir) : cwd;
54461
54550
  try {
54462
54551
  return {
54463
54552
  relativeDir,
@@ -54479,7 +54568,7 @@ async function buildWorkspaceIndex(cwd) {
54479
54568
  if (!dirent.name || dirent.name === "." || dirent.name === "..") continue;
54480
54569
  if (dirent.isDirectory() && IGNORED_DIRECTORY_NAMES.has(dirent.name)) continue;
54481
54570
  if (!dirent.isDirectory() && !dirent.isFile()) continue;
54482
- const relativePath = toPosixPath(relativeDir ? path.join(relativeDir, dirent.name) : dirent.name);
54571
+ const relativePath = toPosixPath(relativeDir ? nodePath.join(relativeDir, dirent.name) : dirent.name);
54483
54572
  if (isPathInIgnoredDirectory(relativePath)) continue;
54484
54573
  candidates.push({
54485
54574
  dirent,
@@ -54560,8 +54649,8 @@ async function searchWorkspaceEntries(input) {
54560
54649
  async function browseFilesystemEntries(input) {
54561
54650
  const resolvedInputPath = resolveBrowseTarget(input);
54562
54651
  const endsWithSeparator = /[\\/]$/.test(input.partialPath) || input.partialPath === "~";
54563
- const parentPath = endsWithSeparator ? resolvedInputPath : path.dirname(resolvedInputPath);
54564
- const prefix = endsWithSeparator ? "" : path.basename(resolvedInputPath);
54652
+ const parentPath = endsWithSeparator ? resolvedInputPath : nodePath.dirname(resolvedInputPath);
54653
+ const prefix = endsWithSeparator ? "" : nodePath.basename(resolvedInputPath);
54565
54654
  const dirents = await fs$1.readdir(parentPath, { withFileTypes: true });
54566
54655
  const showHidden = endsWithSeparator || prefix.startsWith(".");
54567
54656
  const lowerPrefix = prefix.toLowerCase();
@@ -54569,7 +54658,7 @@ async function browseFilesystemEntries(input) {
54569
54658
  parentPath,
54570
54659
  entries: dirents.filter((dirent) => dirent.isDirectory() && dirent.name.toLowerCase().startsWith(lowerPrefix) && (showHidden || !dirent.name.startsWith("."))).map((dirent) => ({
54571
54660
  name: dirent.name,
54572
- fullPath: path.join(parentPath, dirent.name)
54661
+ fullPath: nodePath.join(parentPath, dirent.name)
54573
54662
  })).sort((left, right) => left.name.localeCompare(right.name))
54574
54663
  };
54575
54664
  }
@@ -55889,16 +55978,16 @@ const ProjectionPendingApprovalRepositoryLive = effect(ProjectionPendingApproval
55889
55978
  //#region src/attachmentPaths.ts
55890
55979
  const ATTACHMENTS_ROUTE_PREFIX = "/attachments";
55891
55980
  function normalizeAttachmentRelativePath(rawRelativePath) {
55892
- const normalized = path.normalize(rawRelativePath).replace(/^[/\\]+/, "");
55981
+ const normalized = nodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");
55893
55982
  if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) return null;
55894
55983
  return normalized.replace(/\\/g, "/");
55895
55984
  }
55896
55985
  function resolveAttachmentRelativePath(input) {
55897
55986
  const normalizedRelativePath = normalizeAttachmentRelativePath(input.relativePath);
55898
55987
  if (!normalizedRelativePath) return null;
55899
- const attachmentsRoot = path.resolve(path.join(input.stateDir, "attachments"));
55900
- const filePath = path.resolve(path.join(attachmentsRoot, normalizedRelativePath));
55901
- 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;
55902
55991
  return filePath;
55903
55992
  }
55904
55993
  //#endregion
@@ -57634,6 +57723,7 @@ const makeOrchestrationProjectionPipeline = gen(function* () {
57634
57723
  ...event.payload.origin !== void 0 ? { origin: event.payload.origin } : {},
57635
57724
  ...event.payload.branch !== void 0 ? { branch: event.payload.branch } : {},
57636
57725
  ...event.payload.worktreePath !== void 0 ? { worktreePath: event.payload.worktreePath } : {},
57726
+ ...event.payload.deletedAt !== void 0 ? { deletedAt: event.payload.deletedAt } : {},
57637
57727
  updatedAt: event.payload.updatedAt
57638
57728
  });
57639
57729
  return;
@@ -58131,6 +58221,75 @@ function normalizeCommitMessagePrompt(value) {
58131
58221
  return normalized.length > 0 ? normalized : null;
58132
58222
  }
58133
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
58134
58293
  //#region ../../packages/runtime-app-state/src/storage/documents.ts
58135
58294
  const APP_STORAGE_TABLE = "app_storage_documents";
58136
58295
  const APP_STORAGE_DB_DIR = "t3-projects";
@@ -58214,7 +58373,7 @@ function ensureSchema(db, dbPath) {
58214
58373
  initializedDbPaths.add(dbPath);
58215
58374
  }
58216
58375
  async function openDatabase(dbPath) {
58217
- await promises.mkdir(path.dirname(dbPath), { recursive: true });
58376
+ await promises.mkdir(nodePath.dirname(dbPath), { recursive: true });
58218
58377
  if (process.versions.bun !== void 0) {
58219
58378
  const database = new (await (Function("return import('bun:sqlite')")())).Database(dbPath);
58220
58379
  return {
@@ -58247,7 +58406,7 @@ async function withDatabase(dbPath, task) {
58247
58406
  throw new Error("App storage database remained busy after retrying.");
58248
58407
  }
58249
58408
  function resolveAppStorageDbPath(userDataDir) {
58250
- 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);
58251
58410
  }
58252
58411
  async function readDocument(dbPath, namespace, normalize) {
58253
58412
  return withDatabase(dbPath, (db) => {
@@ -58317,21 +58476,21 @@ function clone(value) {
58317
58476
  return JSON.parse(JSON.stringify(value));
58318
58477
  }
58319
58478
  function resolveDefaultUserDataDir() {
58320
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
58479
+ const home = process.env.HOME || process.env.USERPROFILE || nodeOs.homedir();
58321
58480
  if (!home) throw new Error("Unable to resolve home directory for app state.");
58322
- 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);
58323
58482
  if (process.platform === "win32") {
58324
58483
  const appData = process.env.APPDATA;
58325
- if (appData) return path.join(appData, APP_NAME);
58326
- 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);
58327
58486
  }
58328
- return path.join(home, ".config", APP_NAME);
58487
+ return nodePath.join(home, ".config", APP_NAME);
58329
58488
  }
58330
58489
  function getUserDataDir() {
58331
58490
  return configuredUserDataDir ?? resolveDefaultUserDataDir();
58332
58491
  }
58333
58492
  function resolveLegacyAppStatePath() {
58334
- return path.join(getUserDataDir(), LEGACY_APP_STATE_FILE);
58493
+ return nodePath.join(getUserDataDir(), LEGACY_APP_STATE_FILE);
58335
58494
  }
58336
58495
  function resolveStorageDbPath() {
58337
58496
  return resolveAppStorageDbPath(getUserDataDir());
@@ -58341,9 +58500,23 @@ function createDefaultAppState() {
58341
58500
  schemaVersion: 1,
58342
58501
  autoRoll: {
58343
58502
  enabled: false,
58344
- warningThreshold: 85,
58345
- switchThreshold: 95,
58346
- 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: {}
58347
58520
  },
58348
58521
  app: {
58349
58522
  lastAppVersion: null,
@@ -58436,17 +58609,47 @@ function asString$9(value) {
58436
58609
  const trimmed = value.trim();
58437
58610
  return trimmed.length > 0 ? trimmed : null;
58438
58611
  }
58612
+ function asNumberOrNull(value) {
58613
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
58614
+ }
58439
58615
  function normalizeAppState(raw) {
58440
58616
  const defaults = createDefaultAppState();
58441
58617
  if (!isRecord$7(raw)) return defaults;
58618
+ const rawAutoRoll = isRecord$7(raw.autoRoll) ? raw.autoRoll : void 0;
58442
58619
  const merged = clone(deepMerge(defaults, raw));
58443
58620
  merged.schemaVersion = 1;
58444
- if (typeof merged.autoRoll.enabled !== "boolean") merged.autoRoll.enabled = false;
58445
- if (!Number.isFinite(merged.autoRoll.warningThreshold)) merged.autoRoll.warningThreshold = 85;
58446
- if (!Number.isFinite(merged.autoRoll.switchThreshold)) merged.autoRoll.switchThreshold = 95;
58447
- if (merged.autoRoll.warningThreshold < 50 || merged.autoRoll.warningThreshold > 99) merged.autoRoll.warningThreshold = 85;
58448
- if (merged.autoRoll.switchThreshold <= merged.autoRoll.warningThreshold || merged.autoRoll.switchThreshold > 100) merged.autoRoll.switchThreshold = Math.min(100, Math.max(merged.autoRoll.warningThreshold + 1, 95));
58449
- 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
+ }
58450
58653
  merged.app.lastAppVersion = asString$9(merged.app.lastAppVersion);
58451
58654
  merged.app.pendingUpdateVersion = asString$9(merged.app.pendingUpdateVersion);
58452
58655
  merged.app.lastProfileName = asString$9(merged.app.lastProfileName);
@@ -58678,13 +58881,13 @@ const IS_CASE_INSENSITIVE_PLATFORM$2 = process.platform === "darwin" || process.
58678
58881
  function stripTrailingSeparators$2(input) {
58679
58882
  const trimmed = input.trim();
58680
58883
  if (trimmed.length === 0) return trimmed;
58681
- const root = path.parse(trimmed).root;
58884
+ const root = nodePath.parse(trimmed).root;
58682
58885
  let next = trimmed;
58683
58886
  while (next.length > root.length && /[\\/]+$/.test(next)) next = next.slice(0, -1);
58684
58887
  return next;
58685
58888
  }
58686
58889
  function canonicalizeWorkspaceRoot$1(input) {
58687
- const resolved = path.resolve(input.trim());
58890
+ const resolved = nodePath.resolve(input.trim());
58688
58891
  return stripTrailingSeparators$2((() => {
58689
58892
  try {
58690
58893
  return realpathSync.native(resolved);
@@ -58698,10 +58901,10 @@ function workspaceRootKey$1(input) {
58698
58901
  return IS_CASE_INSENSITIVE_PLATFORM$2 ? canonical.toLowerCase() : canonical;
58699
58902
  }
58700
58903
  function resolveAgentChatWorkspaceRoot() {
58701
- return canonicalizeWorkspaceRoot$1(path.join(getUserDataDir(), AGENT_CHAT_WORKSPACE_DIRNAME));
58904
+ return canonicalizeWorkspaceRoot$1(nodePath.join(getUserDataDir(), AGENT_CHAT_WORKSPACE_DIRNAME));
58702
58905
  }
58703
58906
  function resolveAgentChatAgentsPath() {
58704
- return path.join(resolveAgentChatWorkspaceRoot(), AGENT_CHAT_AGENTS_FILENAME);
58907
+ return nodePath.join(resolveAgentChatWorkspaceRoot(), AGENT_CHAT_AGENTS_FILENAME);
58705
58908
  }
58706
58909
  function isAgentChatWorkspaceRoot(workspaceRoot) {
58707
58910
  return workspaceRootKey$1(workspaceRoot) === workspaceRootKey$1(resolveAgentChatWorkspaceRoot());
@@ -58715,13 +58918,13 @@ const IS_CASE_INSENSITIVE_PLATFORM$1 = process.platform === "darwin" || process.
58715
58918
  function stripTrailingSeparators$1(input) {
58716
58919
  const trimmed = input.trim();
58717
58920
  if (trimmed.length === 0) return trimmed;
58718
- const root = path.parse(trimmed).root;
58921
+ const root = nodePath.parse(trimmed).root;
58719
58922
  let next = trimmed;
58720
58923
  while (next.length > root.length && /[\\/]+$/.test(next)) next = next.slice(0, -1);
58721
58924
  return next;
58722
58925
  }
58723
58926
  function canonicalizeWorkspaceRoot(input) {
58724
- const resolved = path.resolve(input.trim());
58927
+ const resolved = nodePath.resolve(input.trim());
58725
58928
  return stripTrailingSeparators$1((() => {
58726
58929
  try {
58727
58930
  return realpathSync.native(resolved);
@@ -58735,54 +58938,15 @@ function workspaceRootKey(input) {
58735
58938
  return IS_CASE_INSENSITIVE_PLATFORM$1 ? canonical.toLowerCase() : canonical;
58736
58939
  }
58737
58940
  function resolveGeneralChatWorkspaceRoot() {
58738
- return canonicalizeWorkspaceRoot(path.join(getUserDataDir(), GENERAL_CHAT_WORKSPACE_DIRNAME));
58941
+ return canonicalizeWorkspaceRoot(nodePath.join(getUserDataDir(), GENERAL_CHAT_WORKSPACE_DIRNAME));
58739
58942
  }
58740
58943
  function resolveGeneralChatAgentsPath() {
58741
- return path.join(resolveGeneralChatWorkspaceRoot(), GENERAL_CHAT_AGENTS_FILENAME);
58944
+ return nodePath.join(resolveGeneralChatWorkspaceRoot(), GENERAL_CHAT_AGENTS_FILENAME);
58742
58945
  }
58743
58946
  function isGeneralChatWorkspaceRoot(workspaceRoot) {
58744
58947
  return workspaceRootKey(workspaceRoot) === workspaceRootKey(resolveGeneralChatWorkspaceRoot());
58745
58948
  }
58746
58949
  //#endregion
58747
- //#region ../../packages/contracts/src/settings/auto-roll.ts
58748
- const DEFAULT_AUTO_ROLL_ENABLED = false;
58749
- const DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
58750
- const DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
58751
- const DEFAULT_RESTART_OFFICIAL_CODEX_ON_AUTO_ROLL = false;
58752
- const AUTO_ROLL_WARNING_MIN = 50;
58753
- const AUTO_ROLL_WARNING_MAX = 99;
58754
- const AUTO_ROLL_SWITCH_MAX = 100;
58755
- function clampNumber(value, min, max) {
58756
- return Math.min(max, Math.max(min, value));
58757
- }
58758
- function resolveFiniteNumber(value, fallback) {
58759
- return Number.isFinite(value) ? value : fallback;
58760
- }
58761
- function sanitizeAutoRollWarningThreshold(value) {
58762
- return clampNumber(resolveFiniteNumber(value, 85), 50, AUTO_ROLL_WARNING_MAX);
58763
- }
58764
- function sanitizeAutoRollSwitchThreshold(value, warningThreshold) {
58765
- const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
58766
- return clampNumber(resolveFiniteNumber(value, 95), sanitizedWarning + 1, AUTO_ROLL_SWITCH_MAX);
58767
- }
58768
- function sanitizeAutoRollThresholds(warningThreshold, switchThreshold) {
58769
- const sanitizedWarning = sanitizeAutoRollWarningThreshold(warningThreshold);
58770
- return {
58771
- warningThreshold: sanitizedWarning,
58772
- switchThreshold: sanitizeAutoRollSwitchThreshold(switchThreshold, sanitizedWarning)
58773
- };
58774
- }
58775
- function normalizeAutoRollSettings(raw) {
58776
- const enabled = typeof raw?.enabled === "boolean" ? raw.enabled : false;
58777
- const { warningThreshold: normalizedWarning, switchThreshold: normalizedSwitch } = sanitizeAutoRollThresholds(resolveFiniteNumber(typeof raw?.warningThreshold === "number" ? raw.warningThreshold : NaN, 85), resolveFiniteNumber(typeof raw?.switchThreshold === "number" ? raw.switchThreshold : NaN, 95));
58778
- return {
58779
- enabled,
58780
- warningThreshold: normalizedWarning,
58781
- switchThreshold: normalizedSwitch,
58782
- restartOfficialCodexOnAutoRoll: raw?.restartOfficialCodexOnAutoRoll === true ? true : false
58783
- };
58784
- }
58785
- //#endregion
58786
58950
  //#region ../../packages/runtime-codex/src/codex/settings.ts
58787
58951
  function asString$8(value) {
58788
58952
  if (typeof value !== "string") return null;
@@ -58820,12 +58984,6 @@ function parseStoredLicense(raw) {
58820
58984
  };
58821
58985
  return Boolean(license.licenseKey || license.purchaseEmail || license.lastVerifiedAt || license.nextCheckAt || license.lastVerificationError || license.status) ? license : null;
58822
58986
  }
58823
- async function getLastProfileName() {
58824
- return asString$8((await getAppState()).app.lastProfileName);
58825
- }
58826
- async function persistLastProfileName(profileName) {
58827
- await patchAppState({ app: { lastProfileName: asString$8(profileName) } });
58828
- }
58829
58987
  async function getStoredLicense() {
58830
58988
  return parseStoredLicense((await getAppState()).license);
58831
58989
  }
@@ -58856,9 +59014,13 @@ async function persistAutoRollSettings(settings) {
58856
59014
  const normalized = normalizeAutoRollSettings(settings ?? void 0);
58857
59015
  await patchAppState({ autoRoll: {
58858
59016
  enabled: normalized.enabled,
58859
- warningThreshold: normalized.warningThreshold,
58860
- switchThreshold: normalized.switchThreshold,
58861
- 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
58862
59024
  } });
58863
59025
  }
58864
59026
  async function readCodexSettingsJsonRaw() {
@@ -58896,9 +59058,13 @@ async function writeCodexSettingsJsonRaw(payload) {
58896
59058
  },
58897
59059
  autoRoll: autoRoll ? {
58898
59060
  enabled: autoRoll.enabled,
58899
- warningThreshold: autoRoll.warningThreshold,
58900
- switchThreshold: autoRoll.switchThreshold,
58901
- 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
58902
59068
  } : void 0,
58903
59069
  license: license ? {
58904
59070
  licenseKey: license.licenseKey ?? null,
@@ -58936,7 +59102,7 @@ function normalizeSecret(value) {
58936
59102
  return typeof value === "string" ? value.trim() : "";
58937
59103
  }
58938
59104
  async function readLegacyLicenseSecret() {
58939
- const legacyPath = path.join(getUserDataDir(), LEGACY_LICENSE_SECRET_FILE$1);
59105
+ const legacyPath = nodePath.join(getUserDataDir(), LEGACY_LICENSE_SECRET_FILE$1);
58940
59106
  try {
58941
59107
  return normalizeSecret(await promises.readFile(legacyPath, "utf8"));
58942
59108
  } catch (error) {
@@ -59285,13 +59451,13 @@ const IS_CASE_INSENSITIVE_PLATFORM = process.platform === "darwin" || process.pl
59285
59451
  function stripTrailingSeparators(input) {
59286
59452
  const resolved = input.trim();
59287
59453
  if (resolved.length === 0) return resolved;
59288
- const root = path.parse(resolved).root;
59454
+ const root = nodePath.parse(resolved).root;
59289
59455
  let next = resolved;
59290
59456
  while (next.length > root.length && /[\\/]+$/.test(next)) next = next.slice(0, -1);
59291
59457
  return next;
59292
59458
  }
59293
59459
  function canonicalizeProjectRoot(input) {
59294
- const resolved = path.resolve(input.trim());
59460
+ const resolved = nodePath.resolve(input.trim());
59295
59461
  return stripTrailingSeparators((() => {
59296
59462
  try {
59297
59463
  return realpathSync.native(resolved);
@@ -59472,14 +59638,14 @@ let cachedStatus = null;
59472
59638
  function getPathHintEntries() {
59473
59639
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? "";
59474
59640
  const homeEntries = homeDir ? [
59475
- path.join(homeDir, ".local", "bin"),
59476
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
59477
- path.join(homeDir, ".fnm", "current", "bin"),
59478
- path.join(homeDir, ".volta", "bin"),
59479
- path.join(homeDir, ".asdf", "shims"),
59480
- 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")
59481
59647
  ] : [];
59482
- 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);
59483
59649
  return [
59484
59650
  "/opt/homebrew/bin",
59485
59651
  "/usr/local/bin",
@@ -59489,7 +59655,7 @@ function getPathHintEntries() {
59489
59655
  }
59490
59656
  function fileExists$1(candidate) {
59491
59657
  if (!candidate) return null;
59492
- const normalized = path.resolve(candidate);
59658
+ const normalized = nodePath.resolve(candidate);
59493
59659
  try {
59494
59660
  if (statSync(normalized).isFile()) return normalized;
59495
59661
  } catch {}
@@ -59506,7 +59672,7 @@ function resolveCodexFromEnv() {
59506
59672
  }
59507
59673
  function resolveCodexFromPath() {
59508
59674
  const pathValue = process.env.PATH ?? "";
59509
- 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)));
59510
59676
  const names = process.platform === "win32" ? [
59511
59677
  "codex.exe",
59512
59678
  "codex.cmd",
@@ -59514,16 +59680,16 @@ function resolveCodexFromPath() {
59514
59680
  "codex"
59515
59681
  ] : ["codex"];
59516
59682
  for (const entry of entries) for (const name of names) {
59517
- const candidate = fileExists$1(path.join(entry, name));
59683
+ const candidate = fileExists$1(nodePath.join(entry, name));
59518
59684
  if (candidate) return candidate;
59519
59685
  }
59520
59686
  return null;
59521
59687
  }
59522
59688
  function resolvePackageJsonPath(cliPath) {
59523
- const normalized = path.resolve(cliPath);
59524
- const binDir = path.dirname(normalized);
59525
- const packageDir = path.basename(binDir) === "bin" ? path.dirname(binDir) : path.dirname(normalized);
59526
- 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"));
59527
59693
  }
59528
59694
  function readCodexCliVersion(cliPath) {
59529
59695
  const packageJsonPath = resolvePackageJsonPath(cliPath);
@@ -59614,17 +59780,17 @@ function resolveNodeRuntime(env) {
59614
59780
  ...env
59615
59781
  };
59616
59782
  const homeDir = runtimeEnv.HOME ?? runtimeEnv.USERPROFILE ?? "";
59617
- 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);
59618
59784
  const extraPathEntries = [
59619
59785
  "/usr/local/bin",
59620
59786
  "/opt/homebrew/bin",
59621
- path.join(homeDir, ".local", "bin"),
59622
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
59623
- 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"),
59624
59790
  ...pathHints
59625
59791
  ].filter(Boolean);
59626
59792
  const currentPath = runtimeEnv.PATH ?? "";
59627
- 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);
59628
59794
  const candidates = Array.from(new Set([
59629
59795
  runtimeEnv.CODEX_NODE_RUNTIME?.trim(),
59630
59796
  runtimeEnv.CODEX_NODE_BIN?.trim(),
@@ -59661,11 +59827,11 @@ function buildCodexCommand$1(binaryPath, args, env) {
59661
59827
  //#endregion
59662
59828
  //#region ../../packages/runtime-codex/src/codex/home.ts
59663
59829
  function resolveHomeDir$1() {
59664
- return process.env.HOME || process.env.USERPROFILE || os.homedir();
59830
+ return process.env.HOME || process.env.USERPROFILE || nodeOs.homedir();
59665
59831
  }
59666
59832
  function expandHomePrefix(input, homeDir) {
59667
59833
  if (input === "~") return homeDir;
59668
- 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));
59669
59835
  return input;
59670
59836
  }
59671
59837
  function normalizePathCandidate(value) {
@@ -59676,13 +59842,10 @@ function normalizePathCandidate(value) {
59676
59842
  function normalizeCodexHomePath(input) {
59677
59843
  const configured = normalizePathCandidate(input);
59678
59844
  if (!configured) return null;
59679
- return path.resolve(expandHomePrefix(configured, resolveHomeDir$1()));
59845
+ return nodePath.resolve(expandHomePrefix(configured, resolveHomeDir$1()));
59680
59846
  }
59681
59847
  function resolveDefaultCodexHomeDir() {
59682
- return path.join(resolveHomeDir$1(), ".codex");
59683
- }
59684
- function resolveProfileCodexHomeDir(profileName) {
59685
- return path.join(getUserDataDir(), "profile-homes", profileName);
59848
+ return nodePath.join(resolveHomeDir$1(), ".codex");
59686
59849
  }
59687
59850
  function resolveCodexHomeDir(options) {
59688
59851
  return normalizeCodexHomePath(options?.codexHomePath) ?? resolveDefaultCodexHomeDir();
@@ -59694,14 +59857,7 @@ async function resolveCodexRuntimeContext(options) {
59694
59857
  profileName: null,
59695
59858
  source: "explicit"
59696
59859
  };
59697
- const state = await getAppState();
59698
- const profileName = normalizePathCandidate(state.app.lastProfileName);
59699
- if (profileName && state.profilesByName[profileName]) return {
59700
- codexHomePath: resolveProfileCodexHomeDir(profileName),
59701
- profileName,
59702
- source: "profile"
59703
- };
59704
- const configured = normalizeCodexHomePath(state.runtimeSettings?.codexHome);
59860
+ const configured = normalizeCodexHomePath((await getAppState()).runtimeSettings?.codexHome);
59705
59861
  if (configured) return {
59706
59862
  codexHomePath: configured,
59707
59863
  profileName: null,
@@ -59778,19 +59934,19 @@ function buildCliEnv(codexPath, overrides = {}) {
59778
59934
  ...overrides
59779
59935
  };
59780
59936
  const currentPath = env.PATH ?? "";
59781
- const codexDir = path$1.dirname(codexPath);
59937
+ const codexDir = path.dirname(codexPath);
59782
59938
  const homeDir = process.env.HOME ?? "";
59783
- 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);
59784
59940
  const segments = [...[
59785
59941
  codexDir,
59786
59942
  "/usr/local/bin",
59787
59943
  "/opt/homebrew/bin",
59788
- path$1.join(homeDir, ".local", "bin"),
59789
- path$1.join(homeDir, ".fnm", "aliases", "default", "bin"),
59790
- 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"),
59791
59947
  ...extraPathHints
59792
- ].filter(Boolean), ...currentPath.split(path$1.delimiter).filter(Boolean)];
59793
- 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);
59794
59950
  return env;
59795
59951
  }
59796
59952
  //#endregion
@@ -60747,7 +60903,7 @@ async function getActiveProfileContext() {
60747
60903
  const profileKey = toProfileStorageKey(profileName);
60748
60904
  return {
60749
60905
  profileKey,
60750
- parityStoreDir: path.join(getUserDataDir(), PROFILE_ROOT_DIR, profileKey, PROFILE_PARITY_DIR)
60906
+ parityStoreDir: nodePath.join(getUserDataDir(), PROFILE_ROOT_DIR, profileKey, PROFILE_PARITY_DIR)
60751
60907
  };
60752
60908
  }
60753
60909
  async function fileExists(filePath) {
@@ -60759,10 +60915,10 @@ async function fileExists(filePath) {
60759
60915
  }
60760
60916
  }
60761
60917
  function legacyStorePath() {
60762
- return path.join(getUserDataDir(), LEGACY_STORE_FILE);
60918
+ return nodePath.join(getUserDataDir(), LEGACY_STORE_FILE);
60763
60919
  }
60764
60920
  function legacyMigrationMarkerPath() {
60765
- return path.join(getUserDataDir(), LEGACY_MIGRATION_MARKER_FILE);
60921
+ return nodePath.join(getUserDataDir(), LEGACY_MIGRATION_MARKER_FILE);
60766
60922
  }
60767
60923
  async function readLegacyMigrationMarker() {
60768
60924
  try {
@@ -60781,13 +60937,13 @@ async function migrateLegacyStoreIfNeeded(scopedStorePath) {
60781
60937
  if (!await fileExists(legacyPath)) return;
60782
60938
  const marker = await readLegacyMigrationMarker();
60783
60939
  if (marker?.migratedToProfileKey && marker.migratedToProfileKey !== context.profileKey) return;
60784
- await fs$1.mkdir(path.dirname(scopedStorePath), { recursive: true });
60940
+ await fs$1.mkdir(nodePath.dirname(scopedStorePath), { recursive: true });
60785
60941
  await fs$1.copyFile(legacyPath, scopedStorePath);
60786
60942
  }
60787
60943
  async function storePath() {
60788
60944
  const context = await getActiveProfileContext();
60789
60945
  await fs$1.mkdir(context.parityStoreDir, { recursive: true });
60790
- const scopedPath = path.join(context.parityStoreDir, STORE_FILE);
60946
+ const scopedPath = nodePath.join(context.parityStoreDir, STORE_FILE);
60791
60947
  await migrateLegacyStoreIfNeeded(scopedPath);
60792
60948
  return scopedPath;
60793
60949
  }
@@ -60798,7 +60954,7 @@ function toWorkspaceName(inputPath) {
60798
60954
  function sanitizeWorkspace(entry) {
60799
60955
  if (!entry || typeof entry !== "object") return null;
60800
60956
  const id = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
60801
- 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;
60802
60958
  if (!id || !workspacePath) return null;
60803
60959
  const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : toWorkspaceName(workspacePath);
60804
60960
  const branch = entry.worktree && typeof entry.worktree === "object" && typeof entry.worktree.branch === "string" ? entry.worktree.branch.trim() || "main" : "main";
@@ -60917,7 +61073,7 @@ function ensureThreadEntry(store, threadId) {
60917
61073
  return created;
60918
61074
  }
60919
61075
  function resolveOverridesDbPath(stateDir) {
60920
- return path.join(stateDir, "state.sqlite");
61076
+ return nodePath.join(stateDir, "state.sqlite");
60921
61077
  }
60922
61078
  async function readExternalThreadOverrides(stateDir) {
60923
61079
  return await readDocument(resolveOverridesDbPath(stateDir), EXTERNAL_THREAD_OVERRIDES_DOCUMENT, (value) => {
@@ -61058,6 +61214,62 @@ let externalCodexThreadSyncWatcherSettleTimer = null;
61058
61214
  let externalCodexThreadSyncWatcherPendingTrigger = null;
61059
61215
  const externalSyncAppServerLeaseCount = /* @__PURE__ */ new Map();
61060
61216
  let externalCodexThreadSyncRefreshQueue = null;
61217
+ let externalCodexThreadSyncProgressReporter = null;
61218
+ function makeEmptyExternalThreadSyncProjectStats(syncTarget) {
61219
+ return {
61220
+ projectId: syncTarget.projectId,
61221
+ workspaceRoot: syncTarget.workspace.path,
61222
+ title: syncTarget.workspace.name,
61223
+ scannedThreads: 0,
61224
+ createdThreads: 0,
61225
+ restoredThreads: 0,
61226
+ hydratedThreads: 0,
61227
+ importedMessages: 0,
61228
+ importedActivities: 0,
61229
+ importedPlans: 0
61230
+ };
61231
+ }
61232
+ function sumExternalThreadSyncProjectStats(projects) {
61233
+ return projects.reduce((total, project) => ({
61234
+ scannedThreads: total.scannedThreads + project.scannedThreads,
61235
+ createdThreads: total.createdThreads + project.createdThreads,
61236
+ restoredThreads: total.restoredThreads + project.restoredThreads,
61237
+ hydratedThreads: total.hydratedThreads + project.hydratedThreads,
61238
+ importedMessages: total.importedMessages + project.importedMessages,
61239
+ importedActivities: total.importedActivities + project.importedActivities,
61240
+ importedPlans: total.importedPlans + project.importedPlans
61241
+ }), {
61242
+ scannedThreads: 0,
61243
+ createdThreads: 0,
61244
+ restoredThreads: 0,
61245
+ hydratedThreads: 0,
61246
+ importedMessages: 0,
61247
+ importedActivities: 0,
61248
+ importedPlans: 0
61249
+ });
61250
+ }
61251
+ function makeExternalThreadSyncStats(input) {
61252
+ return {
61253
+ bootstrappedProjectCount: input?.bootstrappedProjectCount ?? 0,
61254
+ syncedProjectCount: input?.syncedProjectCount ?? 0,
61255
+ createdThreadCount: input?.createdThreadCount ?? 0,
61256
+ restoredThreadCount: input?.restoredThreadCount ?? 0,
61257
+ foregroundSummaryCount: input?.foregroundSummaryCount ?? 0,
61258
+ foregroundHydratedThreadCount: input?.foregroundHydratedThreadCount ?? 0,
61259
+ foregroundImportedMessageCount: input?.foregroundImportedMessageCount ?? 0,
61260
+ foregroundImportedActivityCount: input?.foregroundImportedActivityCount ?? 0,
61261
+ foregroundImportedPlanCount: input?.foregroundImportedPlanCount ?? 0,
61262
+ backgroundBackfillProjectCount: input?.backgroundBackfillProjectCount ?? 0,
61263
+ parityWorkspaceCount: input?.parityWorkspaceCount ?? 0,
61264
+ hadErrors: input?.hadErrors ?? false
61265
+ };
61266
+ }
61267
+ function publishExternalThreadSyncProgress(payload) {
61268
+ if (!externalCodexThreadSyncProgressReporter) return;
61269
+ Promise.resolve(externalCodexThreadSyncProgressReporter(payload)).catch((error) => {
61270
+ console.warn("[t3 external thread sync] progress publish failed", { error });
61271
+ });
61272
+ }
61061
61273
  function asRecord$3(value) {
61062
61274
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
61063
61275
  }
@@ -61146,6 +61358,9 @@ function closeExternalCodexThreadSyncWatcher() {
61146
61358
  function setExternalCodexThreadSyncRefreshQueue(queue) {
61147
61359
  externalCodexThreadSyncRefreshQueue = queue ? { offer: queue } : null;
61148
61360
  }
61361
+ function setExternalCodexThreadSyncProgressReporter(reporter) {
61362
+ externalCodexThreadSyncProgressReporter = reporter;
61363
+ }
61149
61364
  async function requestExternalCodexThreadSyncRefresh(trigger) {
61150
61365
  if (!externalCodexThreadSyncRefreshQueue) return false;
61151
61366
  try {
@@ -61180,7 +61395,7 @@ function collectExternalCodexThreadSyncDirectoryWatchPaths(rootPath) {
61180
61395
  while (stack.length > 0) {
61181
61396
  const currentPath = stack.pop();
61182
61397
  if (!currentPath) continue;
61183
- const resolvedCurrentPath = path.resolve(currentPath);
61398
+ const resolvedCurrentPath = nodePath.resolve(currentPath);
61184
61399
  if (seenPaths.has(resolvedCurrentPath)) continue;
61185
61400
  seenPaths.add(resolvedCurrentPath);
61186
61401
  let stat;
@@ -61199,7 +61414,7 @@ function collectExternalCodexThreadSyncDirectoryWatchPaths(rootPath) {
61199
61414
  }
61200
61415
  for (const entry of entries) {
61201
61416
  if (!entry.isDirectory()) continue;
61202
- stack.push(path.join(resolvedCurrentPath, entry.name));
61417
+ stack.push(nodePath.join(resolvedCurrentPath, entry.name));
61203
61418
  }
61204
61419
  }
61205
61420
  return collectedPaths;
@@ -61208,7 +61423,7 @@ function collectExternalCodexThreadSyncWatchTargets(homePath) {
61208
61423
  const recursive = supportsRecursiveExternalCodexThreadSyncWatcher();
61209
61424
  const targets = [];
61210
61425
  for (const relativePath of EXTERNAL_SYNC_WATCH_DIRECTORY_NAMES) {
61211
- const absolutePath = path.join(homePath, relativePath);
61426
+ const absolutePath = nodePath.join(homePath, relativePath);
61212
61427
  if (!fs.existsSync(absolutePath)) continue;
61213
61428
  if (recursive) {
61214
61429
  targets.push({
@@ -61220,7 +61435,7 @@ function collectExternalCodexThreadSyncWatchTargets(homePath) {
61220
61435
  }
61221
61436
  for (const watchPath of collectExternalCodexThreadSyncDirectoryWatchPaths(absolutePath)) targets.push({
61222
61437
  watchPath,
61223
- label: path.relative(homePath, watchPath) || ".",
61438
+ label: nodePath.relative(homePath, watchPath) || ".",
61224
61439
  recursive: false
61225
61440
  });
61226
61441
  }
@@ -61232,7 +61447,7 @@ function collectExternalCodexThreadSyncWatchTargets(homePath) {
61232
61447
  return targets;
61233
61448
  }
61234
61449
  function startExternalCodexThreadSyncWatcher(homePath) {
61235
- const resolvedHomePath = path.resolve(homePath);
61450
+ const resolvedHomePath = nodePath.resolve(homePath);
61236
61451
  if (externalCodexThreadSyncWatchers.length > 0 && externalCodexThreadSyncWatcherHomePath === resolvedHomePath) return;
61237
61452
  closeExternalCodexThreadSyncWatcher();
61238
61453
  try {
@@ -61263,7 +61478,7 @@ function startExternalCodexThreadSyncWatcher(homePath) {
61263
61478
  }
61264
61479
  }
61265
61480
  function resolveStateDbPath(stateDir) {
61266
- return path.join(stateDir, "state.sqlite");
61481
+ return nodePath.join(stateDir, "state.sqlite");
61267
61482
  }
61268
61483
  function asString$6(value) {
61269
61484
  return typeof value === "string" ? value : value == null ? "" : String(value);
@@ -61550,7 +61765,7 @@ function resolveLegacyFilePath(source) {
61550
61765
  if (normalized.startsWith("~/")) {
61551
61766
  const home = process.env.HOME?.trim();
61552
61767
  if (!home) return null;
61553
- return path.join(home, normalized.slice(2));
61768
+ return nodePath.join(home, normalized.slice(2));
61554
61769
  }
61555
61770
  return normalized;
61556
61771
  }
@@ -61586,7 +61801,7 @@ async function materializeLegacyImageAttachment(input) {
61586
61801
  mimeType = Mime_default.getType(filePath) ?? "";
61587
61802
  if (!mimeType.startsWith("image/")) return null;
61588
61803
  bytes = await fs$1.readFile(filePath);
61589
- name = path.basename(filePath) || `image${inferImageExtension({ mimeType })}`;
61804
+ name = nodePath.basename(filePath) || `image${inferImageExtension({ mimeType })}`;
61590
61805
  }
61591
61806
  if (bytes.byteLength === 0 || bytes.byteLength > 10485760) return null;
61592
61807
  const attachmentId = createDeterministicLegacyAttachmentId(input.threadId, input.attachmentKey);
@@ -61603,7 +61818,7 @@ async function materializeLegacyImageAttachment(input) {
61603
61818
  attachment
61604
61819
  });
61605
61820
  if (!attachmentPath) return null;
61606
- await fs$1.mkdir(path.dirname(attachmentPath), { recursive: true });
61821
+ await fs$1.mkdir(nodePath.dirname(attachmentPath), { recursive: true });
61607
61822
  await fs$1.writeFile(attachmentPath, bytes);
61608
61823
  return attachment;
61609
61824
  } catch {
@@ -61922,7 +62137,7 @@ async function readImportMarker(stateDir) {
61922
62137
  return { version };
61923
62138
  });
61924
62139
  if (stored) return stored;
61925
- const markerPath = path.join(stateDir, EXTERNAL_SYNC_MARKER_FILE);
62140
+ const markerPath = nodePath.join(stateDir, EXTERNAL_SYNC_MARKER_FILE);
61926
62141
  try {
61927
62142
  const raw = await fs$1.readFile(markerPath, "utf8");
61928
62143
  const version = asFiniteNumber(asRecord$3(JSON.parse(raw))?.version);
@@ -61973,7 +62188,7 @@ async function readLegacyThreadByCandidateIds(appServer, candidateIds) {
61973
62188
  }
61974
62189
  function shouldReadLegacyThreadForImport(input) {
61975
62190
  if (!input.existingThread) return true;
61976
- if (input.existingThread.deletedAt !== null) return false;
62191
+ if (input.existingThread.deletedAt !== null) return input.restoreDeletedImportedThread === true;
61977
62192
  if (input.forceRereadExistingThreads) return true;
61978
62193
  const legacyUpdatedAtMs = toEpochMs(input.legacyThreadSummary.updatedAt ?? input.legacyThreadSummary.createdAt, 0);
61979
62194
  if (legacyUpdatedAtMs <= 0) return false;
@@ -61981,7 +62196,7 @@ function shouldReadLegacyThreadForImport(input) {
61981
62196
  }
61982
62197
  async function writeImportMarker(stateDir, payload) {
61983
62198
  await writeDocument(resolveStateDbPath(stateDir), EXTERNAL_SYNC_MARKER_DOCUMENT, payload);
61984
- 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);
61985
62200
  }
61986
62201
  function resolveLegacyThreadTimestamps(thread) {
61987
62202
  const createdAtMs = toEpochMs(thread.createdAt ?? thread.updatedAt, Date.now());
@@ -62004,6 +62219,9 @@ function shouldRefreshImportedMessage(input) {
62004
62219
  function hasImportedThreadArtifacts(state) {
62005
62220
  return state.messageIds.size > 0 || state.activityIds.size > 0 || state.planIds.size > 0;
62006
62221
  }
62222
+ function shouldRestoreDeletedImportedThread(input) {
62223
+ return input.existingThread !== void 0 && input.existingThread.deletedAt !== null && input.existingThread.origin === "external-import" && input.existingThread.projectId !== input.projectId;
62224
+ }
62007
62225
  function readProviderThreadIdFromResumeCursor(resumeCursor) {
62008
62226
  const cursor = asRecord$3(resumeCursor);
62009
62227
  return firstNonEmptyString(cursor?.threadId, cursor?.thread_id, cursor?.conversationId, cursor?.conversation_id) ?? null;
@@ -62053,6 +62271,7 @@ function syncExternalThreadSummary(input) {
62053
62271
  const threadIdRaw = asTrimmedString$1(input.summary.id);
62054
62272
  if (!threadIdRaw) return {
62055
62273
  createdThread: false,
62274
+ restoredThread: false,
62056
62275
  overridesChanged: false
62057
62276
  };
62058
62277
  const threadId = ThreadId.makeUnsafe(threadIdRaw);
@@ -62070,9 +62289,14 @@ function syncExternalThreadSummary(input) {
62070
62289
  orchestrationEngine: input.orchestrationEngine
62071
62290
  })) return {
62072
62291
  createdThread: false,
62292
+ restoredThread: false,
62073
62293
  overridesChanged
62074
62294
  };
62075
- const effectiveUpdatedAt = existingThread ? latestIsoTimestamp(existingThread.updatedAt, updatedAt) : updatedAt;
62295
+ const restoreDeletedImportedThread = shouldRestoreDeletedImportedThread({
62296
+ existingThread,
62297
+ projectId: input.projectId
62298
+ });
62299
+ const effectiveUpdatedAt = existingThread && existingThread.deletedAt === null ? latestIsoTimestamp(existingThread.updatedAt, updatedAt) : updatedAt;
62076
62300
  const nextBranch = input.workspace.kind === "worktree" ? firstNonEmptyString(input.workspace.worktree?.branch) : null;
62077
62301
  const nextWorktreePath = input.workspace.kind === "worktree" ? input.workspace.path : null;
62078
62302
  if (suppressed) {
@@ -62091,18 +62315,22 @@ function syncExternalThreadSummary(input) {
62091
62315
  }
62092
62316
  return {
62093
62317
  createdThread: false,
62318
+ restoredThread: false,
62094
62319
  overridesChanged
62095
62320
  };
62096
62321
  }
62097
- if (existingThread && existingThread.deletedAt !== null) return {
62322
+ if (existingThread && existingThread.deletedAt !== null && !restoreDeletedImportedThread) return {
62098
62323
  createdThread: false,
62324
+ restoredThread: false,
62099
62325
  overridesChanged
62100
62326
  };
62101
- if (existingThread && existingThread.title === title && existingThread.updatedAt === effectiveUpdatedAt && existingThread.origin === "external-import" && existingThread.branch === nextBranch && existingThread.worktreePath === nextWorktreePath) return {
62327
+ const shouldCreateThread = !input.state.threadIds.has(threadId);
62328
+ if (existingThread && existingThread.deletedAt === null && existingThread.projectId === input.projectId && existingThread.title === title && existingThread.updatedAt === effectiveUpdatedAt && existingThread.origin === "external-import" && existingThread.branch === nextBranch && existingThread.worktreePath === nextWorktreePath) return {
62102
62329
  createdThread: false,
62330
+ restoredThread: false,
62103
62331
  overridesChanged
62104
62332
  };
62105
- if (!input.state.threadIds.has(threadId)) {
62333
+ if (shouldCreateThread) {
62106
62334
  yield* input.orchestrationEngine.dispatch({
62107
62335
  type: "thread.create",
62108
62336
  commandId: CommandId.makeUnsafe(makeServerCommandId()),
@@ -62124,10 +62352,12 @@ function syncExternalThreadSummary(input) {
62124
62352
  type: "thread.meta.update",
62125
62353
  commandId: CommandId.makeUnsafe(makeServerCommandId()),
62126
62354
  threadId,
62355
+ ...existingThread?.projectId !== input.projectId ? { projectId: input.projectId } : {},
62127
62356
  title,
62128
62357
  origin: "external-import",
62129
62358
  branch: nextBranch,
62130
62359
  worktreePath: nextWorktreePath,
62360
+ ...restoreDeletedImportedThread ? { deletedAt: null } : {},
62131
62361
  updatedAt: effectiveUpdatedAt
62132
62362
  });
62133
62363
  input.state.threadById.set(threadId, {
@@ -62142,16 +62372,28 @@ function syncExternalThreadSummary(input) {
62142
62372
  });
62143
62373
  return {
62144
62374
  createdThread: !existingThread,
62375
+ restoredThread: restoreDeletedImportedThread,
62145
62376
  overridesChanged
62146
62377
  };
62147
62378
  });
62148
62379
  }
62149
62380
  function importExternalThreadArtifacts(input) {
62150
62381
  return gen(function* () {
62382
+ const existingThread = input.state.threadById.get(input.threadId);
62383
+ const restoreDeletedImportedThread = shouldRestoreDeletedImportedThread({
62384
+ existingThread,
62385
+ projectId: input.projectId
62386
+ });
62387
+ if (existingThread?.deletedAt !== null && !restoreDeletedImportedThread) return {
62388
+ createdThread: false,
62389
+ importedMessageCount: 0,
62390
+ importedActivityCount: 0,
62391
+ importedPlanCount: 0,
62392
+ overridesChanged: false
62393
+ };
62151
62394
  const isNewThread = !input.state.threadIds.has(input.threadId);
62152
62395
  const cachedThreadState = input.state.threadStateById.get(input.threadId);
62153
- const existingThreadState = !isNewThread && (!cachedThreadState || cachedThreadState.messageIds.size === 0 && cachedThreadState.activityIds.size === 0 && cachedThreadState.planIds.size === 0) ? yield* loadPersistedThreadImportState(input.importStateRepositories, input.threadId).pipe(orElseSucceed(() => createEmptyThreadImportState())) : cachedThreadState ?? createEmptyThreadImportState();
62154
- const existingThread = input.state.threadById.get(input.threadId);
62396
+ const existingThreadState = input.state.threadIds.has(input.threadId) && (!cachedThreadState || cachedThreadState.messageIds.size === 0 && cachedThreadState.activityIds.size === 0 && cachedThreadState.planIds.size === 0) ? yield* loadPersistedThreadImportState(input.importStateRepositories, input.threadId).pipe(orElseSucceed(() => createEmptyThreadImportState())) : cachedThreadState ?? createEmptyThreadImportState();
62155
62397
  const existingThreadReadModel = input.state.threadReadModelById.get(input.threadId);
62156
62398
  const localContinuationDedupState = existingThreadReadModel ? buildLocalContinuationDedupState({
62157
62399
  thread: existingThreadReadModel,
@@ -62174,7 +62416,7 @@ function importExternalThreadArtifacts(input) {
62174
62416
  importedPlanCount: 0,
62175
62417
  overridesChanged
62176
62418
  };
62177
- if (suppressed || existingThread && existingThread.deletedAt !== null) return {
62419
+ if (suppressed) return {
62178
62420
  createdThread: false,
62179
62421
  importedMessageCount: 0,
62180
62422
  importedActivityCount: 0,
@@ -62254,16 +62496,18 @@ function importExternalThreadArtifacts(input) {
62254
62496
  importedActivityCount += 1;
62255
62497
  }
62256
62498
  const importedArtifactCount = importedMessageCount + importedPlanCount + importedActivityCount;
62257
- const effectiveUpdatedAt = existingThread === void 0 ? input.importArtifacts.latestUpdatedAt : importedArtifactCount === 0 ? existingThread.updatedAt : hadImportedArtifactsBeforeSync ? latestIsoTimestamp(existingThread.updatedAt, input.importArtifacts.latestUpdatedAt) : latestIsoTimestamp(existingThread.updatedAt, input.importArtifacts.latestArtifactUpdatedAt);
62258
- if (!isNewThread && (existingThread === void 0 || existingThread.title !== effectiveTitle || existingThread.origin !== "external-import" || existingThread.branch !== input.importArtifacts.branch || existingThread.worktreePath !== input.importArtifacts.worktreePath || existingThread.updatedAt !== effectiveUpdatedAt)) yield* input.orchestrationEngine.dispatch({
62499
+ const effectiveUpdatedAt = existingThread === void 0 ? input.importArtifacts.latestUpdatedAt : existingThread.deletedAt !== null ? input.importArtifacts.latestUpdatedAt : importedArtifactCount === 0 ? existingThread.updatedAt : hadImportedArtifactsBeforeSync ? latestIsoTimestamp(existingThread.updatedAt, input.importArtifacts.latestUpdatedAt) : latestIsoTimestamp(existingThread.updatedAt, input.importArtifacts.latestArtifactUpdatedAt);
62500
+ if (!isNewThread && (existingThread === void 0 || existingThread.projectId !== input.projectId || restoreDeletedImportedThread || existingThread.title !== effectiveTitle || existingThread.origin !== "external-import" || existingThread.branch !== input.importArtifacts.branch || existingThread.worktreePath !== input.importArtifacts.worktreePath || existingThread.updatedAt !== effectiveUpdatedAt)) yield* input.orchestrationEngine.dispatch({
62259
62501
  type: "thread.meta.update",
62260
62502
  commandId: CommandId.makeUnsafe(makeServerCommandId()),
62261
62503
  threadId: input.threadId,
62504
+ ...existingThread?.projectId !== input.projectId ? { projectId: input.projectId } : {},
62262
62505
  title: effectiveTitle,
62263
62506
  modelSelection: input.importArtifacts.modelSelection,
62264
62507
  origin: "external-import",
62265
62508
  branch: input.importArtifacts.branch,
62266
62509
  worktreePath: input.importArtifacts.worktreePath,
62510
+ ...restoreDeletedImportedThread ? { deletedAt: null } : {},
62267
62511
  updatedAt: effectiveUpdatedAt
62268
62512
  });
62269
62513
  input.state.threadById.set(input.threadId, {
@@ -62413,9 +62657,9 @@ const syncPassiveImportedThreadIfNeeded = (input) => gen(function* () {
62413
62657
  })).overridesChanged) yield* tryPromise(() => writeExternalThreadOverrides(input.stateDir, overridesStore)).pipe(ignore);
62414
62658
  return "continue";
62415
62659
  });
62416
- const runExternalCodexThreadSync = gen(function* () {
62660
+ const runExternalCodexThreadSyncWithOptions = (options) => gen(function* () {
62417
62661
  const config = yield* ServerConfig$1;
62418
- if (config.mode !== "desktop") return;
62662
+ if (config.mode !== "desktop") return makeExternalThreadSyncStats();
62419
62663
  const orchestrationEngine = yield* OrchestrationEngineService;
62420
62664
  const providerRuntimeRepository = yield* ProviderSessionRuntimeRepository;
62421
62665
  const importStateRepositories = {
@@ -62457,7 +62701,7 @@ const runExternalCodexThreadSync = gen(function* () {
62457
62701
  }
62458
62702
  const syncTargets = [];
62459
62703
  for (const project of readModel.projects) {
62460
- if (project.deletedAt !== null || isGeneralChatWorkspaceRoot(project.workspaceRoot) || isAgentChatWorkspaceRoot(project.workspaceRoot)) continue;
62704
+ if (options?.projectId !== void 0 && project.id !== options.projectId || project.deletedAt !== null || isGeneralChatWorkspaceRoot(project.workspaceRoot) || isAgentChatWorkspaceRoot(project.workspaceRoot)) continue;
62461
62705
  const workspaceRoot = project.workspaceRoot;
62462
62706
  const rootKey = projectRootKey(workspaceRoot);
62463
62707
  if (projectIdByRootKey.has(rootKey)) continue;
@@ -62475,8 +62719,8 @@ const runExternalCodexThreadSync = gen(function* () {
62475
62719
  });
62476
62720
  }
62477
62721
  let bootstrappedProjectCount = 0;
62478
- let remainingBootstrapProjects = (yield* promise(() => readProjectPolicy(readModel))).remainingProjects;
62479
- for (const workspace of importableParityWorkspaces) {
62722
+ let remainingBootstrapProjects = options?.projectId === void 0 ? (yield* promise(() => readProjectPolicy(readModel))).remainingProjects : 0;
62723
+ for (const workspace of options?.projectId === void 0 ? importableParityWorkspaces : []) {
62480
62724
  const workspaceRoot = workspace.path;
62481
62725
  const workspaceRootCanonicalKey = projectRootKey(workspaceRoot);
62482
62726
  if (projectIdByRootKey.has(workspaceRootCanonicalKey)) continue;
@@ -62519,28 +62763,67 @@ const runExternalCodexThreadSync = gen(function* () {
62519
62763
  bootstrappedProjectCount += 1;
62520
62764
  if (remainingBootstrapProjects !== null && remainingBootstrapProjects > 0) remainingBootstrapProjects -= 1;
62521
62765
  }
62522
- if (syncTargets.length === 0) return;
62766
+ let hadErrors = false;
62767
+ const syncRunId = crypto.randomUUID();
62768
+ const syncStartedAt = (/* @__PURE__ */ new Date()).toISOString();
62769
+ const projectStatsByProjectId = new Map(syncTargets.map((target) => [target.projectId, makeEmptyExternalThreadSyncProjectStats(target)]));
62770
+ const publishSyncProgress = (status, input) => {
62771
+ const projects = Array.from(projectStatsByProjectId.values());
62772
+ const totals = sumExternalThreadSyncProjectStats(projects);
62773
+ publishExternalThreadSyncProgress({
62774
+ status,
62775
+ runId: syncRunId,
62776
+ startedAt: syncStartedAt,
62777
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
62778
+ ...input?.finishedAt ? { finishedAt: input.finishedAt } : {},
62779
+ currentProjectIndex: input?.currentProjectIndex ?? 0,
62780
+ totalProjects: syncTargets.length,
62781
+ ...input?.project ? { project: input.project } : {},
62782
+ ...status === "completed" || status === "failed" ? { projects } : {},
62783
+ ...totals,
62784
+ hadErrors
62785
+ });
62786
+ };
62787
+ if (syncTargets.length === 0) {
62788
+ publishSyncProgress("completed", { finishedAt: (/* @__PURE__ */ new Date()).toISOString() });
62789
+ return makeExternalThreadSyncStats({
62790
+ bootstrappedProjectCount,
62791
+ parityWorkspaceCount: importableParityWorkspaces.length
62792
+ });
62793
+ }
62794
+ publishSyncProgress("started");
62523
62795
  let createdThreadCount = 0;
62796
+ let restoredThreadCount = 0;
62524
62797
  let foregroundSummaryCount = 0;
62525
62798
  let foregroundHydratedThreadCount = 0;
62526
62799
  let foregroundImportedMessageCount = 0;
62527
62800
  let foregroundImportedActivityCount = 0;
62528
62801
  let foregroundImportedPlanCount = 0;
62529
- let hadErrors = false;
62530
62802
  const backgroundBackfillPlans = [];
62531
62803
  const lease = yield* tryPromise(() => acquireExternalSyncAppServer(runtimeContext.codexHomePath));
62532
62804
  try {
62533
62805
  const appServer = lease.appServer;
62534
- for (const syncTarget of syncTargets) {
62806
+ for (const [syncTargetIndex, syncTarget] of syncTargets.entries()) {
62535
62807
  const { projectId, workspace } = syncTarget;
62808
+ const currentProjectIndex = syncTargetIndex + 1;
62809
+ const projectStats = projectStatsByProjectId.get(projectId) ?? makeEmptyExternalThreadSyncProjectStats(syncTarget);
62810
+ projectStatsByProjectId.set(projectId, projectStats);
62811
+ publishSyncProgress("project", {
62812
+ project: projectStats,
62813
+ currentProjectIndex
62814
+ });
62536
62815
  let firstPage = null;
62537
62816
  try {
62538
62817
  firstPage = yield* tryPromise(() => listLegacyThreadPage(appServer, workspace, {
62539
- limit: EXTERNAL_SYNC_FOREGROUND_THREAD_LIMIT,
62818
+ limit: options?.projectId !== void 0 ? EXTERNAL_THREAD_PAGE_SIZE : EXTERNAL_SYNC_FOREGROUND_THREAD_LIMIT,
62540
62819
  sortKey: "updated_at"
62541
62820
  }));
62542
62821
  } catch (cause) {
62543
62822
  hadErrors = true;
62823
+ publishSyncProgress("project", {
62824
+ project: projectStats,
62825
+ currentProjectIndex
62826
+ });
62544
62827
  yield* logWarning$1("failed to list external Codex threads for project", {
62545
62828
  cause,
62546
62829
  projectId,
@@ -62550,15 +62833,25 @@ const runExternalCodexThreadSync = gen(function* () {
62550
62833
  continue;
62551
62834
  }
62552
62835
  if (!firstPage) continue;
62836
+ projectStats.scannedThreads += firstPage.threads.length;
62837
+ publishSyncProgress("project", {
62838
+ project: projectStats,
62839
+ currentProjectIndex
62840
+ });
62553
62841
  for (const summary of firstPage.threads) {
62554
62842
  const threadIdRaw = asTrimmedString$1(summary.id);
62555
62843
  const threadId = threadIdRaw ? ThreadId.makeUnsafe(threadIdRaw) : null;
62556
62844
  const existingThread = threadId ? state.threadById.get(threadId) : void 0;
62557
62845
  const existingReadModelThread = threadId ? readModelThreadById.get(threadId) : void 0;
62558
- const shouldHydrateForegroundThread = threadIdRaw.length > 0 && (existingThread === void 0 || existingThread.deletedAt === null) && (shouldReadLegacyThreadForImport({
62846
+ const restoreDeletedImportedThread = shouldRestoreDeletedImportedThread({
62847
+ existingThread,
62848
+ projectId
62849
+ });
62850
+ const shouldHydrateForegroundThread = threadIdRaw.length > 0 && (existingThread === void 0 || existingThread.deletedAt === null || restoreDeletedImportedThread) && (shouldReadLegacyThreadForImport({
62559
62851
  existingThread,
62560
62852
  legacyThreadSummary: summary,
62561
- forceRereadExistingThreads: false
62853
+ forceRereadExistingThreads: false,
62854
+ restoreDeletedImportedThread
62562
62855
  }) || (existingReadModelThread ? needsExternalThreadHydration(existingReadModelThread) : false));
62563
62856
  const summaryResult = yield* syncExternalThreadSummary({
62564
62857
  summary,
@@ -62569,9 +62862,17 @@ const runExternalCodexThreadSync = gen(function* () {
62569
62862
  orchestrationEngine
62570
62863
  });
62571
62864
  const createdThread = summaryResult.createdThread;
62865
+ const restoredThread = summaryResult.restoredThread;
62572
62866
  overridesChanged ||= summaryResult.overridesChanged;
62573
62867
  foregroundSummaryCount += 1;
62574
- if (createdThread) createdThreadCount += 1;
62868
+ if (createdThread) {
62869
+ createdThreadCount += 1;
62870
+ projectStats.createdThreads += 1;
62871
+ }
62872
+ if (restoredThread) {
62873
+ restoredThreadCount += 1;
62874
+ projectStats.restoredThreads += 1;
62875
+ }
62575
62876
  if (!shouldHydrateForegroundThread || threadIdRaw.length === 0) continue;
62576
62877
  try {
62577
62878
  const legacyThread = yield* tryPromise(() => readLegacyThread(appServer, threadIdRaw));
@@ -62596,7 +62897,15 @@ const runExternalCodexThreadSync = gen(function* () {
62596
62897
  foregroundImportedMessageCount += importResult.importedMessageCount;
62597
62898
  foregroundImportedActivityCount += importResult.importedActivityCount;
62598
62899
  foregroundImportedPlanCount += importResult.importedPlanCount;
62900
+ projectStats.hydratedThreads += 1;
62901
+ projectStats.importedMessages += importResult.importedMessageCount;
62902
+ projectStats.importedActivities += importResult.importedActivityCount;
62903
+ projectStats.importedPlans += importResult.importedPlanCount;
62599
62904
  overridesChanged ||= importResult.overridesChanged;
62905
+ publishSyncProgress("project", {
62906
+ project: projectStats,
62907
+ currentProjectIndex
62908
+ });
62600
62909
  } catch (cause) {
62601
62910
  hadErrors = true;
62602
62911
  yield* logWarning$1("failed to hydrate external Codex thread during foreground sync", {
@@ -62613,6 +62922,10 @@ const runExternalCodexThreadSync = gen(function* () {
62613
62922
  nextCursor: firstPage.nextCursor,
62614
62923
  sortKey: "updated_at"
62615
62924
  });
62925
+ publishSyncProgress("project", {
62926
+ project: projectStats,
62927
+ currentProjectIndex
62928
+ });
62616
62929
  }
62617
62930
  } finally {
62618
62931
  yield* tryPromise(() => lease.release()).pipe(ignoreCause({ log: true }));
@@ -62634,6 +62947,7 @@ const runExternalCodexThreadSync = gen(function* () {
62634
62947
  bootstrappedProjectCount,
62635
62948
  syncedProjectCount: syncTargets.length,
62636
62949
  createdThreadCount,
62950
+ restoredThreadCount,
62637
62951
  foregroundSummaryCount,
62638
62952
  foregroundHydratedThreadCount,
62639
62953
  foregroundImportedMessageCount,
@@ -62645,16 +62959,34 @@ const runExternalCodexThreadSync = gen(function* () {
62645
62959
  syncMarkerVersion: importMarker?.version ?? null,
62646
62960
  hadErrors
62647
62961
  });
62962
+ publishSyncProgress(hadErrors ? "failed" : "completed", { finishedAt: (/* @__PURE__ */ new Date()).toISOString() });
62648
62963
  if (!hadErrors) yield* tryPromise(() => writeImportMarker(config.stateDir, {
62649
62964
  version: EXTERNAL_SYNC_MARKER_VERSION,
62650
62965
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
62651
62966
  bootstrappedProjectCount,
62652
62967
  syncedProjectCount: syncTargets.length,
62653
62968
  createdThreadCount,
62969
+ restoredThreadCount,
62654
62970
  foregroundSummaryCount,
62655
62971
  backgroundBackfillProjectCount: backgroundBackfillPlans.length
62656
62972
  })).pipe(catch_((cause) => logWarning$1("failed to write external Codex thread sync marker", { cause })));
62973
+ return makeExternalThreadSyncStats({
62974
+ bootstrappedProjectCount,
62975
+ syncedProjectCount: syncTargets.length,
62976
+ createdThreadCount,
62977
+ restoredThreadCount,
62978
+ foregroundSummaryCount,
62979
+ foregroundHydratedThreadCount,
62980
+ foregroundImportedMessageCount,
62981
+ foregroundImportedActivityCount,
62982
+ foregroundImportedPlanCount,
62983
+ backgroundBackfillProjectCount: backgroundBackfillPlans.length,
62984
+ parityWorkspaceCount: importableParityWorkspaces.length,
62985
+ hadErrors
62986
+ });
62657
62987
  });
62988
+ const runExternalCodexThreadSync = runExternalCodexThreadSyncWithOptions();
62989
+ const runExternalCodexThreadSyncForProject = (projectId) => runExternalCodexThreadSyncWithOptions({ projectId });
62658
62990
  //#endregion
62659
62991
  //#region src/orchestration/Layers/ThreadSnapshotPreparation.ts
62660
62992
  function mapProviderSessionStatusToOrchestrationStatus(status) {
@@ -64868,7 +65200,7 @@ var RotatingFileSink = class {
64868
65200
  this.maxBytes = options.maxBytes;
64869
65201
  this.maxFiles = options.maxFiles;
64870
65202
  this.throwOnError = options.throwOnError ?? false;
64871
- fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
65203
+ fs.mkdirSync(nodePath.dirname(this.filePath), { recursive: true });
64872
65204
  this.pruneOverflowBackups();
64873
65205
  this.currentSize = this.readCurrentSize();
64874
65206
  }
@@ -64903,13 +65235,13 @@ var RotatingFileSink = class {
64903
65235
  }
64904
65236
  pruneOverflowBackups() {
64905
65237
  try {
64906
- const dir = path.dirname(this.filePath);
64907
- const baseName = path.basename(this.filePath);
65238
+ const dir = nodePath.dirname(this.filePath);
65239
+ const baseName = nodePath.basename(this.filePath);
64908
65240
  for (const entry of fs.readdirSync(dir)) {
64909
65241
  if (!entry.startsWith(`${baseName}.`)) continue;
64910
65242
  const suffix = Number(entry.slice(baseName.length + 1));
64911
65243
  if (!Number.isInteger(suffix) || suffix <= this.maxFiles) continue;
64912
- fs.rmSync(path.join(dir, entry), { force: true });
65244
+ fs.rmSync(nodePath.join(dir, entry), { force: true });
64913
65245
  }
64914
65246
  } catch {
64915
65247
  if (this.throwOnError) throw new Error(`Failed to prune log backups for ${this.filePath}`);
@@ -65049,7 +65381,7 @@ function makeEventNdjsonLogger(filePath, options) {
65049
65381
  const streamLabel = resolveStreamLabel(options.stream);
65050
65382
  const directoryReady = yield* sync(() => {
65051
65383
  try {
65052
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
65384
+ fs.mkdirSync(nodePath.dirname(filePath), { recursive: true });
65053
65385
  return true;
65054
65386
  } catch (error) {
65055
65387
  return {
@@ -65072,7 +65404,7 @@ function makeEventNdjsonLogger(filePath, options) {
65072
65404
  const existing = threadWriters.get(threadSegment);
65073
65405
  if (existing) return existing;
65074
65406
  const writer = yield* makeThreadWriter({
65075
- filePath: path.join(path.dirname(filePath), `${threadSegment}.log`),
65407
+ filePath: nodePath.join(nodePath.dirname(filePath), `${threadSegment}.log`),
65076
65408
  maxBytes,
65077
65409
  maxFiles,
65078
65410
  batchWindowMs,
@@ -65289,12 +65621,14 @@ function toUserInputQuestions(payload) {
65289
65621
  const id = asString$3(question.id)?.trim();
65290
65622
  const header = asString$3(question.header)?.trim();
65291
65623
  const prompt = asString$3(question.question)?.trim();
65624
+ const multiSelect = question.multiSelect === true || question.multi_select === true;
65292
65625
  if (!id || !header || !prompt || !options || options.length === 0) return;
65293
65626
  return {
65294
65627
  id,
65295
65628
  header,
65296
65629
  question: prompt,
65297
- options
65630
+ options,
65631
+ multiSelect
65298
65632
  };
65299
65633
  }).filter((question) => question !== void 0);
65300
65634
  return parsedQuestions.length > 0 ? parsedQuestions : void 0;
@@ -66596,7 +66930,7 @@ function normalizeShellCommand(value) {
66596
66930
  }
66597
66931
  function shellCandidateFromCommand(command) {
66598
66932
  if (!command || command.length === 0) return null;
66599
- const shellName = path.basename(command).toLowerCase();
66933
+ const shellName = nodePath.basename(command).toLowerCase();
66600
66934
  if (process.platform !== "win32" && shellName === "zsh") return {
66601
66935
  shell: command,
66602
66936
  args: ["-o", "nopromptsp"]
@@ -66778,7 +67112,7 @@ var TerminalManagerRuntime = class extends EventEmitter {
66778
67112
  this.subprocessPollInFlight = false;
66779
67113
  this.killEscalationTimers = /* @__PURE__ */ new Map();
66780
67114
  this.logger = createLogger("terminal");
66781
- this.logsDir = options.logsDir ?? path.resolve(process.cwd(), ".logs", "terminals");
67115
+ this.logsDir = options.logsDir ?? nodePath.resolve(process.cwd(), ".logs", "terminals");
66782
67116
  this.historyLineLimit = options.historyLineLimit ?? DEFAULT_HISTORY_LINE_LIMIT;
66783
67117
  this.ptyAdapter = options.ptyAdapter;
66784
67118
  this.shellResolver = options.shellResolver ?? defaultShellResolver;
@@ -67358,7 +67692,7 @@ var TerminalManagerRuntime = class extends EventEmitter {
67358
67692
  async deleteAllHistoryForThread(threadId) {
67359
67693
  const threadPrefix = `${toSafeThreadId(threadId)}_`;
67360
67694
  try {
67361
- 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 }));
67362
67696
  await Promise.all(removals);
67363
67697
  } catch (error) {
67364
67698
  this.logger.warn("failed to delete terminal histories for thread", {
@@ -67390,11 +67724,11 @@ var TerminalManagerRuntime = class extends EventEmitter {
67390
67724
  }
67391
67725
  historyPath(threadId, terminalId) {
67392
67726
  const threadPart = toSafeThreadId(threadId);
67393
- if (terminalId === "default") return path.join(this.logsDir, `${threadPart}.log`);
67394
- 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`);
67395
67729
  }
67396
67730
  legacyHistoryPath(threadId) {
67397
- return path.join(this.logsDir, `${legacySafeThreadId(threadId)}.log`);
67731
+ return nodePath.join(this.logsDir, `${legacySafeThreadId(threadId)}.log`);
67398
67732
  }
67399
67733
  async runWithThreadLock(threadId, task) {
67400
67734
  const previous = this.threadLocks.get(threadId) ?? Promise.resolve();
@@ -70370,8 +70704,8 @@ const NodePtyAdapterLive = effect(PtyAdapter, gen(function* () {
70370
70704
  function makeServerProviderLayer() {
70371
70705
  return gen(function* () {
70372
70706
  const { stateDir } = yield* ServerConfig$1;
70373
- const providerLogsDir = path.join(stateDir, "logs", "provider");
70374
- const providerEventLogPath = path.join(providerLogsDir, "events.log");
70707
+ const providerLogsDir = nodePath.join(stateDir, "logs", "provider");
70708
+ const providerEventLogPath = nodePath.join(providerLogsDir, "events.log");
70375
70709
  const nativeEventLogger = yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "native" });
70376
70710
  const canonicalEventLogger = yield* makeEventNdjsonLogger(providerEventLogPath, { stream: "canonical" });
70377
70711
  const providerSessionDirectoryLayer = ProviderSessionDirectoryLive.pipe(provide$2(ProviderSessionRuntimeRepositoryLive));
@@ -72515,8 +72849,8 @@ function parseSchemaKeys(schemaRaw) {
72515
72849
  async function readSchemaFromLocal() {
72516
72850
  const candidates = [
72517
72851
  process.env.CODEX_CONFIG_SCHEMA_PATH,
72518
- path.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
72519
- 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")
72520
72854
  ].filter((candidate) => Boolean(candidate));
72521
72855
  for (const candidate of candidates) try {
72522
72856
  return await readFile(candidate, "utf8");
@@ -72614,21 +72948,6 @@ const DEFAULT_CONFIG_TEMPLATE = [
72614
72948
  "web_search = \"cached\"",
72615
72949
  ""
72616
72950
  ].join("\n");
72617
- const SUPPORTED_REASONING_EFFORT = new Set([
72618
- "minimal",
72619
- "low",
72620
- "medium",
72621
- "high",
72622
- "xhigh",
72623
- "none"
72624
- ]);
72625
- const CLI_SUPPORTED_REASONING_EFFORT = new Set([
72626
- "minimal",
72627
- "low",
72628
- "medium",
72629
- "high",
72630
- "none"
72631
- ]);
72632
72951
  const YOLO_PRESET = {
72633
72952
  model: "gpt-5.3-codex",
72634
72953
  reviewModel: "gpt-5.3-codex",
@@ -72654,10 +72973,10 @@ const YOLO_PRESET = {
72654
72973
  //#endregion
72655
72974
  //#region ../../packages/runtime-codex/src/codex/config-io.ts
72656
72975
  function getConfigPath(options) {
72657
- return path.join(resolveCodexHomeDir(options), "config.toml");
72976
+ return nodePath.join(resolveCodexHomeDir(options), "config.toml");
72658
72977
  }
72659
72978
  async function ensureConfigDirExists(filePath) {
72660
- await mkdir(path.dirname(filePath), { recursive: true });
72979
+ await mkdir(nodePath.dirname(filePath), { recursive: true });
72661
72980
  }
72662
72981
  function normalizeLineEndings(content) {
72663
72982
  return content.replace(/\r\n/g, "\n");
@@ -72921,14 +73240,6 @@ function buildSettingsSummary(data) {
72921
73240
  }
72922
73241
  };
72923
73242
  }
72924
- function getUnsupportedReasoningEffort(data, allowed = SUPPORTED_REASONING_EFFORT) {
72925
- if (!data || !Object.prototype.hasOwnProperty.call(data, "model_reasoning_effort")) return null;
72926
- const raw = data["model_reasoning_effort"];
72927
- if (typeof raw !== "string") return String(raw ?? "");
72928
- const value = raw.trim();
72929
- if (!value) return null;
72930
- return allowed.has(value) ? null : value;
72931
- }
72932
73243
  function parseRmcpClientEnabled(data) {
72933
73244
  const rawFeatures = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
72934
73245
  if (rawFeatures && typeof rawFeatures["rmcp_client"] === "boolean") return rawFeatures["rmcp_client"];
@@ -73232,21 +73543,6 @@ async function updateCodexConfigValues(updates, options) {
73232
73543
  await writeConfigContent(serialized, options);
73233
73544
  return buildSnapshot(serialized, true, options);
73234
73545
  }
73235
- async function sanitizeCodexConfigForCli(options) {
73236
- const { content } = await readConfigContent(options);
73237
- const parsed = tryParseToml(content);
73238
- if (!parsed.data) throw new Error(parsed.error ?? "config.toml contains invalid TOML syntax.");
73239
- if (!getUnsupportedReasoningEffort(parsed.data, CLI_SUPPORTED_REASONING_EFFORT)) return null;
73240
- const sanitized = { ...parsed.data };
73241
- delete sanitized["model_reasoning_effort"];
73242
- await writeConfigContent(ensureTrailingNewline((0, import_toml.stringify)(sanitized)), options);
73243
- let restored = false;
73244
- return async () => {
73245
- if (restored) return;
73246
- restored = true;
73247
- await writeConfigContent(ensureTrailingNewline(normalizeLineEndings(content)), options);
73248
- };
73249
- }
73250
73546
  //#endregion
73251
73547
  //#region ../../packages/contracts/src/profiles/identity.ts
73252
73548
  function normalizeValue(value) {
@@ -73429,10 +73725,10 @@ function parseRateLimitSnapshotFromRpcMessage(message) {
73429
73725
  }
73430
73726
  async function fetchRateLimitsViaRpc(envOverride, options = {}) {
73431
73727
  const binaryPath = options.codexPath ?? await requireCodexCli();
73432
- const tempHome = await promises.mkdtemp(path.join(os.tmpdir(), "codex-rpc-"));
73433
- 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");
73434
73730
  let initialSourceAuth = null;
73435
- 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"));
73436
73732
  try {
73437
73733
  initialSourceAuth = await promises.readFile(sourceAuthPath, "utf8").catch(() => null);
73438
73734
  if (!initialSourceAuth) return null;
@@ -73581,21 +73877,6 @@ var ProfileManager = class {
73581
73877
  isNotFoundError(error) {
73582
73878
  return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
73583
73879
  }
73584
- async readPreferredProfileName() {
73585
- try {
73586
- return await getLastProfileName();
73587
- } catch (error) {
73588
- logWarn("Failed to read preferred profile name:", error);
73589
- return null;
73590
- }
73591
- }
73592
- async persistPreferredProfileName(name) {
73593
- try {
73594
- await persistLastProfileName(name);
73595
- } catch (error) {
73596
- logWarn("Failed to persist preferred profile name:", error);
73597
- }
73598
- }
73599
73880
  toStateRecord(record) {
73600
73881
  return {
73601
73882
  name: record.name,
@@ -73694,44 +73975,33 @@ var ProfileManager = class {
73694
73975
  return null;
73695
73976
  }
73696
73977
  }
73697
- async captureActiveAuthSnapshot() {
73698
- 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) {
73699
73990
  let raw;
73700
73991
  try {
73701
- raw = await promises$1.readFile(this.activeAuth, "utf8");
73992
+ raw = await promises$1.readFile(authPath, "utf8");
73702
73993
  } catch (error) {
73703
- if (this.isNotFoundError(error)) return {
73704
- fingerprint: null,
73705
- email: null,
73706
- accountId: null,
73707
- userId: null,
73708
- chatgptUserId: null,
73709
- workspaceId: null
73710
- };
73994
+ if (this.isNotFoundError(error)) return this.emptyAuthSnapshot();
73711
73995
  const message = error instanceof Error ? error.message : "unknown error";
73712
- const signature = typeof message === "string" ? `${message}:${this.activeAuth}` : this.activeAuth;
73996
+ const signature = typeof message === "string" ? `${message}:${authPath}` : authPath;
73713
73997
  if (this.lastActiveAuthErrorSignature !== signature) {
73714
- logWarn("Failed to snapshot active auth file:", error);
73998
+ logWarn("Failed to snapshot auth file:", error);
73715
73999
  this.lastActiveAuthErrorSignature = signature;
73716
74000
  }
73717
- return {
73718
- fingerprint: null,
73719
- email: null,
73720
- accountId: null,
73721
- userId: null,
73722
- chatgptUserId: null,
73723
- workspaceId: null
73724
- };
74001
+ return this.emptyAuthSnapshot();
73725
74002
  }
73726
74003
  const trimmed = raw.trim();
73727
- if (!trimmed) return {
73728
- fingerprint: null,
73729
- email: null,
73730
- accountId: null,
73731
- userId: null,
73732
- chatgptUserId: null,
73733
- workspaceId: null
73734
- };
74004
+ if (!trimmed) return this.emptyAuthSnapshot();
73735
74005
  const fingerprint = createHash("sha256").update(trimmed).digest("hex");
73736
74006
  try {
73737
74007
  const parsed = JSON.parse(trimmed);
@@ -73744,19 +74014,25 @@ var ProfileManager = class {
73744
74014
  accountId: this.getAccountIdFromData(normalized) ?? null,
73745
74015
  userId: metadata?.userId ?? null,
73746
74016
  chatgptUserId: metadata?.chatgptUserId ?? null,
74017
+ chatgptAccountUserId: metadata?.chatgptAccountUserId ?? null,
73747
74018
  workspaceId: workspace.id ?? null
73748
74019
  };
73749
74020
  } catch {
73750
- return {
73751
- fingerprint,
73752
- email: null,
73753
- accountId: null,
73754
- userId: null,
73755
- chatgptUserId: null,
73756
- workspaceId: null
73757
- };
74021
+ return this.emptyAuthSnapshot(fingerprint);
73758
74022
  }
73759
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
+ }
73760
74036
  /**
73761
74037
  * Decode a JWT payload into an object without validating the signature.
73762
74038
  */
@@ -74017,7 +74293,7 @@ var ProfileManager = class {
74017
74293
  }
74018
74294
  async writeAtomic(filePath, contents) {
74019
74295
  const tempPath = `${filePath}.tmp`;
74020
- const dir = path$1.dirname(filePath);
74296
+ const dir = path.dirname(filePath);
74021
74297
  await promises$1.mkdir(dir, { recursive: true });
74022
74298
  await promises$1.writeFile(tempPath, contents, "utf8");
74023
74299
  try {
@@ -74072,6 +74348,99 @@ var ProfileManager = class {
74072
74348
  metadata: record.metadata
74073
74349
  }) ?? resolveFallbackAccountKey(record.name);
74074
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
+ }
74075
74444
  resolveWorkspaceIdentity(data, metadata) {
74076
74445
  const directId = "workspace_id" in data && typeof data.workspace_id === "string" ? data.workspace_id.trim() : void 0;
74077
74446
  const directName = "workspace_name" in data && typeof data.workspace_name === "string" ? data.workspace_name.trim() : void 0;
@@ -74096,15 +74465,6 @@ var ProfileManager = class {
74096
74465
  const normalized = this.normalizeProfileName(name);
74097
74466
  return await this.readProfileRecord(normalized) ?? void 0;
74098
74467
  }
74099
- async getProfileRowByIdentityKey(identityKey, options) {
74100
- const matches = (await this.listProfileRecords()).filter((record) => this.resolveProfileIdentityFromRecord(record) === identityKey);
74101
- if (matches.length === 0) return;
74102
- const mustMatch = typeof options?.authMethod === "string" ? options.authMethod.trim().toLowerCase() : null;
74103
- if (mustMatch) return matches.find((record) => this.resolveAuthMethod(record.data) === mustMatch);
74104
- const prefer = typeof options?.preferAuthMethod === "string" ? options.preferAuthMethod.trim().toLowerCase() : null;
74105
- if (prefer) return matches.find((record) => this.resolveAuthMethod(record.data) === prefer) ?? matches[0];
74106
- return matches[0];
74107
- }
74108
74468
  async getProfileRowByIdentityAndWorkspace(identityKey, workspaceId, options) {
74109
74469
  const workspaceKey = workspaceId && workspaceId.trim().length > 0 ? workspaceId.trim() : DEFAULT_WORKSPACE_ID;
74110
74470
  const matches = (await this.listProfileRecords()).filter((record) => {
@@ -74119,6 +74479,9 @@ var ProfileManager = class {
74119
74479
  if (prefer) return matches.find((record) => this.resolveAuthMethod(record.data) === prefer) ?? matches[0];
74120
74480
  return matches[0];
74121
74481
  }
74482
+ normalizeWorkspaceId(value) {
74483
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : DEFAULT_WORKSPACE_ID;
74484
+ }
74122
74485
  async persistProfileRecord(name, data, metadata, options) {
74123
74486
  const resolvedName = this.normalizeProfileName(name);
74124
74487
  const existingRecord = await this.readProfileRecord(resolvedName);
@@ -74166,6 +74529,23 @@ var ProfileManager = class {
74166
74529
  metadata: row.metadata
74167
74530
  });
74168
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
+ }
74169
74549
  buildProfileFromData(name, data, fallback) {
74170
74550
  const metadata = fallback?.metadata ?? this.extractProfileMetadata(data);
74171
74551
  const workspace = this.resolveWorkspaceIdentity(data, metadata);
@@ -74217,6 +74597,12 @@ var ProfileManager = class {
74217
74597
  }
74218
74598
  };
74219
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;
74220
74606
  return codexAuth;
74221
74607
  }
74222
74608
  /**
@@ -74235,7 +74621,12 @@ var ProfileManager = class {
74235
74621
  const rootLastRefresh = typeof data.last_refresh === "string" ? data.last_refresh.trim() : "";
74236
74622
  const normalizedLastRefresh = tokenLastRefresh || rootLastRefresh;
74237
74623
  if (normalizedLastRefresh) profile.last_refresh = normalizedLastRefresh;
74238
- 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;
74239
74630
  return profile;
74240
74631
  }
74241
74632
  mergeProfileRecords(existing, incoming) {
@@ -74337,6 +74728,10 @@ var ProfileManager = class {
74337
74728
  logWarn(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`);
74338
74729
  return;
74339
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
+ }
74340
74735
  if (!this.hasTokenChanges(existing, normalized)) return;
74341
74736
  const { profile: merged, metadata: mergedMetadata } = this.mergeProfileRecords(existing, normalized);
74342
74737
  try {
@@ -74437,7 +74832,7 @@ var ProfileManager = class {
74437
74832
  if (profileData.auth_method && profileData.auth_method !== "codex-cli") throw new Error("Unsupported auth method. Codex CLI profiles only.");
74438
74833
  const codexFormat = this.convertToCodexFormat(profileData);
74439
74834
  const profileHome = await this.prepareProfileHome(profileName, codexFormat);
74440
- const authPath = path$1.join(profileHome, "auth.json");
74835
+ const authPath = path.join(profileHome, "auth.json");
74441
74836
  const envOverrides = {
74442
74837
  ...process.env,
74443
74838
  HOME: profileHome,
@@ -74491,7 +74886,7 @@ var ProfileManager = class {
74491
74886
  const profileName = this.normalizeProfileName(name);
74492
74887
  const record = await this.getProfileRowByName(profileName);
74493
74888
  if (!record) throw new Error(`Profile '${profileName}' not found!`);
74494
- const authPath = path$1.join(profileHome ?? this.getProfileHomePath(profileName), "auth.json");
74889
+ const authPath = path.join(profileHome ?? this.getProfileHomePath(profileName), "auth.json");
74495
74890
  let finalAuthContent = null;
74496
74891
  try {
74497
74892
  finalAuthContent = await promises$1.readFile(authPath, "utf8");
@@ -74500,79 +74895,70 @@ var ProfileManager = class {
74500
74895
  }
74501
74896
  if (finalAuthContent) {
74502
74897
  await this.syncProfileTokensFromAuthContent(profileName, record.data, finalAuthContent);
74898
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
74503
74899
  return;
74504
74900
  }
74505
74901
  await this.syncProfileTokensFromActiveAuth(profileName, record.data, authPath);
74902
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
74506
74903
  }));
74507
74904
  }
74508
74905
  /**
74509
74906
  * Create a new profile by running codex login
74510
74907
  */
74511
- async createProfile(name) {
74512
- await this.initialize();
74513
- const profileName = this.normalizeProfileName(name);
74514
- if (await this.getProfileRowByName(profileName)) throw new Error(`Profile '${profileName}' already exists!`);
74515
- let authData;
74908
+ async readAuthFileForImport(authPath, actionLabel) {
74909
+ let authRaw;
74516
74910
  try {
74517
- const authRaw = await promises$1.readFile(this.activeAuth, "utf8");
74518
- authData = JSON.parse(authRaw);
74911
+ authRaw = await promises$1.readFile(authPath, "utf8");
74519
74912
  } catch (error) {
74520
- logError("Error creating profile:", error);
74521
- 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.");
74522
74916
  }
74523
- const normalizedProfile = this.normalizeProfileData(authData);
74524
- normalizedProfile.created_at = normalizedProfile.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
74525
- if (typeof normalizedProfile.auth_method === "string") normalizedProfile.auth_method = normalizedProfile.auth_method.trim().toLowerCase();
74526
- normalizedProfile.auth_method = normalizedProfile.auth_method ?? "codex-cli";
74527
- const metadata = this.extractProfileMetadata(normalizedProfile);
74528
- const workspace = this.resolveWorkspaceIdentity(normalizedProfile, metadata);
74529
- normalizedProfile.workspace_id = normalizedProfile.workspace_id ?? workspace.id ?? DEFAULT_WORKSPACE_ID;
74530
- if (workspace.name) normalizedProfile.workspace_name = normalizedProfile.workspace_name ?? workspace.name;
74531
- const resolvedEmail = this.resolveProfileEmail(normalizedProfile, metadata);
74532
- if (resolvedEmail) normalizedProfile.email = resolvedEmail;
74533
74917
  try {
74534
- await this.persistProfileRecord(profileName, normalizedProfile, metadata);
74535
- const stored = await this.getProfileRowByName(profileName);
74536
- return stored ? this.buildProfileFromRow(stored) : null;
74918
+ return JSON.parse(authRaw);
74537
74919
  } catch (error) {
74538
- logError("Error creating profile:", error);
74539
- 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.");
74540
74922
  }
74541
74923
  }
74542
- /**
74543
- * Switch to a different profile
74544
- */
74545
- async switchToProfile(name) {
74546
- 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) {
74547
74933
  const profileName = this.normalizeProfileName(name);
74548
- const record = await this.getProfileRowByName(profileName);
74549
- if (!record) throw new Error(`Profile '${profileName}' not found!`);
74550
- try {
74551
- const profileData = record.data;
74552
- const codexFormat = this.convertToCodexFormat(profileData);
74553
- await this.writeAtomic(this.activeAuth, JSON.stringify(codexFormat, null, 2));
74554
- await this.persistPreferredProfileName(profileName);
74555
- return true;
74556
- } catch (error) {
74557
- logError("Error switching profile:", error);
74558
- throw new Error("Failed to switch profile");
74559
- }
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
+ }));
74560
74945
  }
74561
- async refreshProfileAuth(name) {
74562
- await this.initialize();
74946
+ async createProfileFromAuthFile(name, authPath) {
74563
74947
  const profileName = this.normalizeProfileName(name);
74564
- const record = await this.getProfileRowByName(profileName);
74565
- 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) {
74566
74961
  const existing = record.data;
74567
- let activeAuth;
74568
- try {
74569
- const authContent = await promises$1.readFile(this.activeAuth, "utf8");
74570
- activeAuth = JSON.parse(authContent);
74571
- } catch (error) {
74572
- if (this.isNotFoundError(error)) throw new Error("Codex CLI did not produce an auth file. Complete the login before refreshing this profile.");
74573
- logError("Failed to read Codex auth file during refresh:", error);
74574
- throw new Error("Failed to read Codex auth file. Complete the login and try again.");
74575
- }
74576
74962
  const normalized = this.normalizeProfileData(activeAuth);
74577
74963
  const existingMetadata = record.metadata ?? this.extractProfileMetadata(existing);
74578
74964
  const currentWorkspace = this.resolveWorkspaceIdentity(existing, existingMetadata);
@@ -74605,15 +74991,72 @@ var ProfileManager = class {
74605
74991
  return profile;
74606
74992
  }
74607
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
+ /**
74608
75036
  * Execute an action with a profile's auth injected.
74609
75037
  * Creates an isolated CODEX_HOME with the profile's auth/config, invokes the action
74610
75038
  * with env overrides, then syncs any refreshed tokens back to the profile.
74611
75039
  */
74612
75040
  async runWithProfileAuth(name, action) {
74613
- 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
+ }));
74614
75048
  }
74615
75049
  async readLiveRateLimits(name, options = {}) {
74616
- 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
+ }
74617
75060
  }
74618
75061
  /**
74619
75062
  * Rename a profile
@@ -74625,8 +75068,6 @@ var ProfileManager = class {
74625
75068
  const existing = await this.getProfileRowByName(sourceName);
74626
75069
  if (!existing) throw new Error(`Profile '${sourceName}' not found!`);
74627
75070
  if (await this.getProfileRowByName(targetName)) throw new Error(`Profile '${targetName}' already exists!`);
74628
- const preferred = await this.readPreferredProfileName();
74629
- if (preferred && preferred === sourceName) await this.persistPreferredProfileName(targetName);
74630
75071
  const oldHome = this.getProfileHomePath(sourceName);
74631
75072
  const newHome = this.getProfileHomePath(targetName);
74632
75073
  try {
@@ -74683,24 +75124,37 @@ var ProfileManager = class {
74683
75124
  const profileName = this.normalizeProfileName(name);
74684
75125
  const record = await this.getProfileRowByName(profileName);
74685
75126
  if (!record) throw new Error(`Profile '${profileName}' not found!`);
74686
- if (!record.accountId) {
74687
- const dashboardState = await this.readProfileDashboardState();
74688
- delete dashboardState.customGroupsByAccountKey[resolveFallbackAccountKey(profileName)];
74689
- await this.writeProfileDashboardState(dashboardState);
74690
- }
74691
- const preferred = await this.readPreferredProfileName();
74692
- if (preferred && preferred === profileName) await this.persistPreferredProfileName(null);
74693
- try {
74694
- await promises$1.rm(this.getProfileHomePath(profileName), {
74695
- recursive: true,
74696
- force: true
74697
- });
74698
- } catch (error) {
74699
- if (!this.isNotFoundError(error)) logWarn(`Failed to remove profile home for '${profileName}':`, error);
74700
- }
74701
- await this.deleteProfileRecord(profileName);
75127
+ await this.deleteProfileRecordAndHome(record);
74702
75128
  return true;
74703
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
+ }
74704
75158
  /**
74705
75159
  * Delete every profile that does not appear in the allow-list.
74706
75160
  * Used to enforce plan limits when local storage was tampered with.
@@ -74730,67 +75184,46 @@ var ProfileManager = class {
74730
75184
  } catch (error) {
74731
75185
  if (!this.isNotFoundError(error)) logWarn(`Failed to remove profile '${name}' during plan enforcement:`, error);
74732
75186
  }
74733
- const preferred = await this.readPreferredProfileName();
74734
- if (preferred && removed.includes(preferred)) await this.persistPreferredProfileName(null);
74735
75187
  return removed;
74736
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
+ }
74737
75218
  /**
74738
- * Check if the current auth matches a profile (for smart detection)
75219
+ * Check if the real Codex auth file matches a saved profile.
74739
75220
  */
74740
75221
  async getCurrentProfile() {
74741
- await this.initialize();
74742
- const preferredName = await this.readPreferredProfileName();
74743
- if (preferredName) try {
74744
- const normalizedPreferred = this.normalizeProfileName(preferredName);
74745
- if (await this.getProfileRowByName(normalizedPreferred)) return {
74746
- name: normalizedPreferred,
74747
- trusted: true
74748
- };
74749
- } catch {}
74750
- const activeAuth = await this.readActiveAuthFile();
74751
- if (activeAuth) {
74752
- const normalizedAuth = this.normalizeProfileData(activeAuth);
74753
- const authMetadata = this.extractProfileMetadata(normalizedAuth);
74754
- const workspace = this.resolveWorkspaceIdentity(normalizedAuth, authMetadata);
74755
- const activeIdentityKey = this.resolveProfileIdentityFromData(null, normalizedAuth, authMetadata);
74756
- if (activeIdentityKey) {
74757
- const scoped = await this.getProfileRowByIdentityAndWorkspace(activeIdentityKey, workspace.id ?? DEFAULT_WORKSPACE_ID, { preferAuthMethod: "codex-cli" });
74758
- if (scoped) {
74759
- const normalized = this.normalizeProfileName(scoped.name);
74760
- await this.persistPreferredProfileName(normalized);
74761
- return {
74762
- name: normalized,
74763
- trusted: true
74764
- };
74765
- }
74766
- const matching = await this.getProfileRowByIdentityKey(activeIdentityKey, { preferAuthMethod: "codex-cli" });
74767
- if (matching) {
74768
- const normalized = this.normalizeProfileName(matching.name);
74769
- await this.persistPreferredProfileName(normalized);
74770
- return {
74771
- name: normalized,
74772
- trusted: true
74773
- };
74774
- }
74775
- }
74776
- }
74777
- const preferred = await this.readPreferredProfileName();
74778
- if (preferred) {
74779
- try {
74780
- const normalized = this.normalizeProfileName(preferred);
74781
- if (await this.getProfileRowByName(normalized)) return {
74782
- name: normalized,
74783
- trusted: true
74784
- };
74785
- } catch {
74786
- await this.persistPreferredProfileName(null);
74787
- return {
74788
- name: null,
74789
- trusted: false
74790
- };
74791
- }
74792
- await this.persistPreferredProfileName(null);
74793
- }
75222
+ const status = await this.getActiveCodexAuthStatus({ autoImport: false });
75223
+ if (status.state === "matched" && status.profileName) return {
75224
+ name: status.profileName,
75225
+ trusted: true
75226
+ };
74794
75227
  return {
74795
75228
  name: null,
74796
75229
  trusted: false
@@ -74882,13 +75315,6 @@ var ProfileManager = class {
74882
75315
  if (!this.isNotFoundError(error)) logWarn(`Failed to remove profile '${name}' during cloud sync replace:`, error);
74883
75316
  }
74884
75317
  }
74885
- const preferred = await this.readPreferredProfileName();
74886
- if (preferred) try {
74887
- const normalizedPreferred = this.normalizeProfileName(preferred);
74888
- if (!normalized.has(normalizedPreferred)) await this.persistPreferredProfileName(null);
74889
- } catch {
74890
- await this.persistPreferredProfileName(null);
74891
- }
74892
75318
  return {
74893
75319
  imported: normalized.size,
74894
75320
  removed
@@ -74912,6 +75338,58 @@ async function loadCachedRateLimitSnapshots(keys) {
74912
75338
  return results;
74913
75339
  }
74914
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
74915
75393
  //#region ../../packages/runtime-profiles/src/profiles/rate-limit-probe.ts
74916
75394
  const MIN_REFRESH_INTERVAL_MS = 3 * 6e4;
74917
75395
  const DEFAULT_REFRESH_INTERVAL_MS = 3 * 6e4;
@@ -75305,7 +75783,8 @@ async function executeAccountRefresh(accountId, state, options = {}) {
75305
75783
  async function bootstrapInitialRefresh() {
75306
75784
  try {
75307
75785
  await ensureProfileManagerReady();
75308
- 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 });
75309
75788
  emitRefreshState();
75310
75789
  } catch (error) {
75311
75790
  logError("Failed to bootstrap rate-limit refresh queue:", error);
@@ -75342,66 +75821,67 @@ function profileKey(profile) {
75342
75821
  }
75343
75822
  /**
75344
75823
  * Loads profiles and related telemetry for the dashboard.
75345
- * When the user is not on a Pro license and has saved more profiles than allowed,
75346
- * 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.
75347
75826
  */
75348
75827
  async function loadProfilesViewData(options = {}) {
75349
75828
  const backgroundRefresh = options.backgroundRefresh !== false;
75350
75829
  await profileManager.initialize();
75351
75830
  const license = await licenseService.getCachedStatus();
75352
75831
  if (backgroundRefresh) licenseService.refreshStatusInBackground();
75353
- 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);
75354
75845
  const profiles = await profileManager.listProfiles();
75355
75846
  const customGroupsByAccountKey = await profileManager.getCustomGroupsByAccountKey();
75356
- const licenseWithCounts = licenseService.applyProfileCount(license, profiles.length);
75357
- const visibleProfiles = (typeof shouldEnforceProfileLimit === "function" ? shouldEnforceProfileLimit(licenseWithCounts) : !licenseWithCounts.isPro && licenseWithCounts.state === "inactive" && !licenseWithCounts.error) ? resolveVisibleProfiles(profiles, licenseWithCounts, currentProfile) : profiles;
75358
- const effectiveProfiles = licenseWithCounts.isPro ? profiles : visibleProfiles;
75359
- const rateLimitEnabled = licenseWithCounts.isPro;
75360
- let refreshStatus = {
75361
- pendingAccountIds: [],
75362
- inFlightAccountIds: [],
75363
- cooldownAccountIds: [],
75364
- errors: []
75365
- };
75366
- let enrichedProfiles = effectiveProfiles;
75367
- if (rateLimitEnabled) {
75368
- const rateLimitEligible = effectiveProfiles.filter((profile) => profile.isValid && !profile.tokenStatus?.requiresUserAction);
75369
- const resolvedSnapshots = await resolveRateLimitSnapshots(rateLimitEligible.map((profile) => profileKey(profile)));
75370
- if (backgroundRefresh) {
75371
- ensureRateLimitRefresher();
75372
- await refreshRateLimitTelemetry(profileManager, rateLimitEligible, resolvedSnapshots);
75373
- }
75374
- const refreshState = getRateLimitRefreshState();
75375
- refreshStatus = {
75376
- pendingAccountIds: refreshState.queuedAccountIds.slice(),
75377
- inFlightAccountIds: refreshState.inFlightAccountIds.slice(),
75378
- cooldownAccountIds: refreshState.cooldownAccountIds.slice(),
75379
- lastRefreshStartedAt: refreshState.lastRefreshStartedAt ? new Date(refreshState.lastRefreshStartedAt).toISOString() : void 0,
75380
- lastRefreshCompletedAt: refreshState.lastRefreshCompletedAt ? new Date(refreshState.lastRefreshCompletedAt).toISOString() : void 0,
75381
- errors: (refreshState.errors ?? []).map((issue) => ({
75382
- accountId: issue.accountId,
75383
- profileName: issue.profileName,
75384
- message: issue.message,
75385
- code: issue.code,
75386
- observedAt: issue.observedAt ? new Date(issue.observedAt).toISOString() : void 0
75387
- }))
75388
- };
75389
- const snapshotMap = resolvedSnapshots;
75390
- enrichedProfiles = effectiveProfiles.map((profile) => ({
75391
- ...profile,
75392
- rateLimit: snapshotMap.get(profileKey(profile)) ?? null
75393
- }));
75394
- } else enrichedProfiles = effectiveProfiles.map((profile) => ({
75395
- ...profile,
75396
- rateLimit: null
75397
- }));
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
+ };
75398
75874
  return {
75399
- profiles: enrichedProfiles,
75875
+ profiles: effectiveProfiles.map((profile) => ({
75876
+ ...profile,
75877
+ rateLimit: resolvedSnapshots.get(profileKey(profile)) ?? null
75878
+ })),
75400
75879
  currentProfile,
75401
75880
  currentProfileTrusted,
75402
75881
  refreshStatus,
75403
- license: licenseWithCounts,
75404
- customGroupsByAccountKey
75882
+ license: visible.licenseWithCounts,
75883
+ customGroupsByAccountKey,
75884
+ activeCodexAuth
75405
75885
  };
75406
75886
  }
75407
75887
  async function resolveRateLimitSnapshots(accountIds) {
@@ -75409,34 +75889,6 @@ async function resolveRateLimitSnapshots(accountIds) {
75409
75889
  if (uniqueAccountIds.length === 0) return /* @__PURE__ */ new Map();
75410
75890
  return await loadCachedRateLimitSnapshots(uniqueAccountIds);
75411
75891
  }
75412
- function resolveVisibleProfiles(profiles, license, currentProfile) {
75413
- if (license.isPro) return profiles;
75414
- const limit = typeof license.profileLimit === "number" && license.profileLimit >= 0 ? license.profileLimit : 2;
75415
- if (profiles.length <= limit) return profiles;
75416
- const normalizedCurrent = currentProfile ?? null;
75417
- return profiles.slice().sort((a, b) => compareProfilesForLimit(a, b, normalizedCurrent)).slice(0, limit);
75418
- }
75419
- function compareProfilesForLimit(a, b, currentProfile) {
75420
- if (currentProfile) {
75421
- if (a.name === currentProfile && b.name !== currentProfile) return -1;
75422
- if (b.name === currentProfile && a.name !== currentProfile) return 1;
75423
- }
75424
- if (Boolean(a.isValid) !== Boolean(b.isValid)) return a.isValid ? -1 : 1;
75425
- const aTimestamp = parseTimestamp(a.createdAt ?? null);
75426
- const bTimestamp = parseTimestamp(b.createdAt ?? null);
75427
- if (aTimestamp !== null && bTimestamp !== null) {
75428
- if (aTimestamp === bTimestamp) return a.name.localeCompare(b.name);
75429
- return bTimestamp - aTimestamp;
75430
- }
75431
- if (aTimestamp !== null) return -1;
75432
- if (bTimestamp !== null) return 1;
75433
- return a.name.localeCompare(b.name);
75434
- }
75435
- function parseTimestamp(value) {
75436
- if (typeof value !== "string" || value.trim() === "") return null;
75437
- const parsed = Date.parse(value);
75438
- return Number.isNaN(parsed) ? null : parsed;
75439
- }
75440
75892
  //#endregion
75441
75893
  //#region src/account-pool/service.ts
75442
75894
  const ACCOUNT_POOL_DOCUMENT = "desktop.account-pool.v2";
@@ -75607,7 +76059,7 @@ function sessionTtlMs(affinityKind) {
75607
76059
  }
75608
76060
  }
75609
76061
  function toStoreDbPath(stateDir) {
75610
- return path.join(stateDir, "state.sqlite");
76062
+ return nodePath.join(stateDir, "state.sqlite");
75611
76063
  }
75612
76064
  function createDefaultStore() {
75613
76065
  return {
@@ -77479,15 +77931,19 @@ var LocalAccountPool = class LocalAccountPool {
77479
77931
  return map;
77480
77932
  }
77481
77933
  async resolveProfiles(settings, store) {
77482
- 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()]);
77483
77938
  const selectedProfileNames = settings.accountPoolProfilePool.length > 0 ? settings.accountPoolProfilePool.slice() : profilesView.profiles.map((profile) => profile.name);
77484
77939
  const selectedSet = new Set(selectedProfileNames);
77485
- const switchThreshold = autoRollSettings?.switchThreshold ?? 95;
77940
+ const switchRemainingThreshold = autoRollSettings?.switchRemainingThreshold ?? 5;
77486
77941
  const now = Date.now();
77487
77942
  const activeSessionCounts = this.buildActiveSessionCountMap(store);
77488
77943
  const profileViews = profilesView.profiles.map((profile) => {
77489
77944
  const selected = selectedSet.has(profile.name);
77490
77945
  const usedPercent = maxRateLimitUsedPercent(profile);
77946
+ const remainingPercent = typeof usedPercent === "number" ? Math.max(0, 100 - usedPercent) : null;
77491
77947
  const profileState = store.profilesByName[profile.name];
77492
77948
  const cooldownUntilMs = profileState?.cooldownUntil ? Date.parse(profileState.cooldownUntil) : NaN;
77493
77949
  let reason = null;
@@ -77495,7 +77951,7 @@ var LocalAccountPool = class LocalAccountPool {
77495
77951
  else if (!profile.isValid) reason = "Profile auth is invalid.";
77496
77952
  else if (profile.tokenStatus?.requiresUserAction) reason = profile.tokenStatus.reason ?? "Profile requires re-authentication.";
77497
77953
  else if (Number.isFinite(cooldownUntilMs) && cooldownUntilMs > now) reason = `Cooling down until ${new Date(cooldownUntilMs).toLocaleTimeString()}.`;
77498
- 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)}%.`;
77499
77955
  return {
77500
77956
  name: profile.name,
77501
77957
  displayName: profile.displayName ?? null,
@@ -77966,14 +78422,14 @@ function extractIconHref(source) {
77966
78422
  }
77967
78423
  function resolveIconHref(projectCwd, href) {
77968
78424
  const clean = href.replace(/^\//, "");
77969
- return [path.join(projectCwd, "public", clean), path.join(projectCwd, clean)];
78425
+ return [nodePath.join(projectCwd, "public", clean), nodePath.join(projectCwd, clean)];
77970
78426
  }
77971
78427
  function isPathWithinProject(projectCwd, candidatePath) {
77972
- const relative = path.relative(path.resolve(projectCwd), path.resolve(candidatePath));
77973
- 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);
77974
78430
  }
77975
78431
  function serveFaviconFile(filePath, res) {
77976
- 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";
77977
78433
  fs.readFile(filePath, (readErr, data) => {
77978
78434
  if (readErr) {
77979
78435
  res.writeHead(500, { "Content-Type": "text/plain" });
@@ -78025,7 +78481,7 @@ function tryHandleProjectFaviconRequest(url, res) {
78025
78481
  serveFallbackFavicon(res);
78026
78482
  return;
78027
78483
  }
78028
- const sourceFile = path.join(projectCwd, ICON_SOURCE_FILES[index]);
78484
+ const sourceFile = nodePath.join(projectCwd, ICON_SOURCE_FILES[index]);
78029
78485
  fs.readFile(sourceFile, "utf8", (err, content) => {
78030
78486
  if (err) {
78031
78487
  trySourceFiles(index + 1);
@@ -78044,7 +78500,7 @@ function tryHandleProjectFaviconRequest(url, res) {
78044
78500
  trySourceFiles(0);
78045
78501
  return;
78046
78502
  }
78047
- const candidate = path.join(projectCwd, FAVICON_CANDIDATES[index]);
78503
+ const candidate = nodePath.join(projectCwd, FAVICON_CANDIDATES[index]);
78048
78504
  if (!isPathWithinProject(projectCwd, candidate)) {
78049
78505
  tryCandidates(index + 1);
78050
78506
  return;
@@ -78149,9 +78605,25 @@ const handleOrchestrationRequest = fnUntraced(function* (body, context) {
78149
78605
  case ORCHESTRATION_WS_METHODS.getThreadSnapshot:
78150
78606
  yield* context.threadSnapshotPreparation.prepareThreadSnapshot(body.threadId);
78151
78607
  return yield* context.projectionReadModelQuery.getThreadSnapshot(stripRequestTag(body));
78152
- case ORCHESTRATION_WS_METHODS.syncExternalThreads:
78153
- if (!(yield* tryPromise(() => context.requestExternalCodexThreadSyncRefresh()).pipe(orElseSucceed(() => false)))) yield* forkDetach(context.runExternalCodexThreadSync.pipe(catch_((cause) => logWarning$1("failed to sync external Codex threads", { cause }))));
78154
- return { syncedAt: (/* @__PURE__ */ new Date()).toISOString() };
78608
+ case ORCHESTRATION_WS_METHODS.syncExternalThreads: {
78609
+ const input = stripRequestTag(body);
78610
+ if (input.projectId !== void 0) {
78611
+ const stats = yield* context.runExternalCodexThreadSyncForProject(input.projectId);
78612
+ return {
78613
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
78614
+ completed: true,
78615
+ queued: false,
78616
+ stats
78617
+ };
78618
+ }
78619
+ const queued = yield* tryPromise(() => context.requestExternalCodexThreadSyncRefresh()).pipe(orElseSucceed(() => false));
78620
+ if (!queued) yield* forkDetach(context.runExternalCodexThreadSync.pipe(catch_((cause) => logWarning$1("failed to sync external Codex threads", { cause }))));
78621
+ return {
78622
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
78623
+ completed: false,
78624
+ queued
78625
+ };
78626
+ }
78155
78627
  case ORCHESTRATION_WS_METHODS.dispatchCommand: {
78156
78628
  const normalizedCommand = yield* context.normalizeDispatchCommand({ command: body.command });
78157
78629
  const overrideIntent = yield* context.resolveExternalThreadOverrideIntent(normalizedCommand);
@@ -78195,18 +78667,18 @@ const REGISTRY_TIMEOUT_MS = 1e4;
78195
78667
  let freshnessCache = null;
78196
78668
  function buildEnv() {
78197
78669
  const env = { ...process.env };
78198
- const homeDir = env.HOME ?? env.USERPROFILE ?? os.homedir();
78670
+ const homeDir = env.HOME ?? env.USERPROFILE ?? nodeOs.homedir();
78199
78671
  const extra = [
78200
78672
  "/opt/homebrew/bin",
78201
78673
  "/usr/local/bin",
78202
- path.join(homeDir, ".local", "bin"),
78203
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
78204
- path.join(homeDir, ".fnm", "current", "bin"),
78205
- path.join(homeDir, ".volta", "bin"),
78206
- 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")
78207
78679
  ];
78208
- const segments = [...(env.PATH ?? "").split(path.delimiter).filter(Boolean), ...extra];
78209
- 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);
78210
78682
  return env;
78211
78683
  }
78212
78684
  function runCommand$2(command, args, env) {
@@ -78218,7 +78690,7 @@ function runCommand$2(command, args, env) {
78218
78690
  function resolveRunnableCommand(command, env, shellInfo) {
78219
78691
  const resolved = resolveCommandPath(command, env, shellInfo)?.trim();
78220
78692
  if (!resolved) return command;
78221
- if (resolved.includes(path.sep) || resolved.includes("/")) return resolved;
78693
+ if (resolved.includes(nodePath.sep) || resolved.includes("/")) return resolved;
78222
78694
  return command;
78223
78695
  }
78224
78696
  function resolveShell() {
@@ -78548,15 +79020,15 @@ function isMissingPathError(error) {
78548
79020
  return error.code === "ENOENT";
78549
79021
  }
78550
79022
  function resolveHomeDir() {
78551
- const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
79023
+ const home = process.env.HOME || process.env.USERPROFILE || nodeOs.homedir();
78552
79024
  if (!home) throw new Error("HOME is not set.");
78553
79025
  return home;
78554
79026
  }
78555
79027
  function resolveCodexDir() {
78556
- return path.join(resolveHomeDir(), ".codex");
79028
+ return nodePath.join(resolveHomeDir(), ".codex");
78557
79029
  }
78558
79030
  function resolveLegacyPath(...segments) {
78559
- return path.join(resolveCodexDir(), ...segments);
79031
+ return nodePath.join(resolveCodexDir(), ...segments);
78560
79032
  }
78561
79033
  function pickAutoRoll(raw) {
78562
79034
  if (!raw) return null;
@@ -78625,9 +79097,13 @@ function mergeLegacyLocalStoragePatch(payload) {
78625
79097
  if (autoRoll) {
78626
79098
  patch.autoRoll = {
78627
79099
  enabled: autoRoll.enabled,
78628
- warningThreshold: autoRoll.warningThreshold,
78629
- switchThreshold: autoRoll.switchThreshold,
78630
- 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
78631
79107
  };
78632
79108
  markSkippedIfPresent("codex:auto-roll-settings", true);
78633
79109
  } else markSkippedIfPresent("codex:auto-roll-settings", false);
@@ -78646,8 +79122,8 @@ async function removeIfExists(target) {
78646
79122
  async function cleanupLegacyCanonicalSources() {
78647
79123
  const homeDir = resolveHomeDir();
78648
79124
  await removeIfExists(resolveLegacyAppStatePath());
78649
- await removeIfExists(path.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE));
78650
- 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));
78651
79127
  await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_FILE));
78652
79128
  await removeIfExists(resolveLegacyPath(LEGACY_SETTINGS_BACKUP_FILE));
78653
79129
  await removeIfExists(resolveLegacyPath(LEGACY_SYNC_STATE_FILE));
@@ -78658,7 +79134,7 @@ async function cleanupLegacyCanonicalSources() {
78658
79134
  const profileHomes = await promises.readdir(profileHomesRoot, { withFileTypes: true });
78659
79135
  for (const profileHome of profileHomes) {
78660
79136
  if (!profileHome.isDirectory()) continue;
78661
- const profileDir = path.join(profileHomesRoot, profileHome.name);
79137
+ const profileDir = nodePath.join(profileHomesRoot, profileHome.name);
78662
79138
  let files = [];
78663
79139
  try {
78664
79140
  files = await promises.readdir(profileDir);
@@ -78667,7 +79143,7 @@ async function cleanupLegacyCanonicalSources() {
78667
79143
  }
78668
79144
  for (const file of files) {
78669
79145
  if (!file.startsWith("profile.json")) continue;
78670
- await removeIfExists(path.join(profileDir, file));
79146
+ await removeIfExists(nodePath.join(profileDir, file));
78671
79147
  }
78672
79148
  }
78673
79149
  } catch (error) {
@@ -78681,7 +79157,7 @@ async function cleanupLegacyCanonicalSources() {
78681
79157
  const skillDirs = await promises.readdir(skillsRoot, { withFileTypes: true });
78682
79158
  for (const entry of skillDirs) {
78683
79159
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
78684
- await removeIfExists(path.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST));
79160
+ await removeIfExists(nodePath.join(skillsRoot, entry.name, LEGACY_SKILL_MANIFEST));
78685
79161
  }
78686
79162
  } catch (error) {
78687
79163
  if (!isMissingPathError(error)) logWarn("Failed cleaning legacy skills metadata:", {
@@ -78762,19 +79238,19 @@ async function importLegacyLocalStorageOnce(payload) {
78762
79238
  //#region ../../packages/runtime-integrations/src/agents/service.ts
78763
79239
  const AGENTS_FILENAME = "AGENTS.md";
78764
79240
  function resolveGlobalPath(options) {
78765
- return path.join(resolveCodexHomeDir(options), AGENTS_FILENAME);
79241
+ return nodePath.join(resolveCodexHomeDir(options), AGENTS_FILENAME);
78766
79242
  }
78767
79243
  function getCodexDir(options) {
78768
79244
  return resolveCodexHomeDir(options);
78769
79245
  }
78770
79246
  function isPathSafe(normalizedPath, options) {
78771
- if (path.basename(normalizedPath) !== AGENTS_FILENAME) return false;
79247
+ if (nodePath.basename(normalizedPath) !== AGENTS_FILENAME) return false;
78772
79248
  const codexDir = getCodexDir(options);
78773
79249
  const homeDir = resolveHomeDir$1();
78774
- if (normalizedPath === path.join(codexDir, AGENTS_FILENAME)) return true;
78775
- const parentDir = path.dirname(normalizedPath);
78776
- if (!parentDir.startsWith(homeDir + path.sep) && parentDir !== homeDir) return false;
78777
- 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;
78778
79254
  return true;
78779
79255
  }
78780
79256
  async function statFile(target) {
@@ -78797,7 +79273,7 @@ async function readFileContent(target) {
78797
79273
  }
78798
79274
  }
78799
79275
  async function ensureDirForFile(target) {
78800
- const dir = path.dirname(target);
79276
+ const dir = nodePath.dirname(target);
78801
79277
  await promises.mkdir(dir, { recursive: true });
78802
79278
  }
78803
79279
  async function saveFileContent(target, content) {
@@ -78812,7 +79288,7 @@ function normalizeProjectPath(candidate) {
78812
79288
  if (!candidate || typeof candidate !== "string") return null;
78813
79289
  const trimmed = candidate.trim();
78814
79290
  if (!trimmed) return null;
78815
- return path.resolve(trimmed);
79291
+ return nodePath.resolve(trimmed);
78816
79292
  }
78817
79293
  async function buildDescriptor(params) {
78818
79294
  const stats = await statFile(params.path);
@@ -78838,7 +79314,7 @@ async function readPreferredContent(entries) {
78838
79314
  async function getAgentsSnapshot(projectPath, options) {
78839
79315
  const normalizedProject = normalizeProjectPath(projectPath);
78840
79316
  const globalPath = resolveGlobalPath(options);
78841
- const projectPathFile = normalizedProject ? path.join(normalizedProject, AGENTS_FILENAME) : null;
79317
+ const projectPathFile = normalizedProject ? nodePath.join(normalizedProject, AGENTS_FILENAME) : null;
78842
79318
  const globalEntry = await buildDescriptor({
78843
79319
  path: globalPath,
78844
79320
  type: "global",
@@ -78860,7 +79336,7 @@ async function getAgentsSnapshot(projectPath, options) {
78860
79336
  };
78861
79337
  }
78862
79338
  async function readAgentsFile(target, options) {
78863
- const normalized = path.resolve(target);
79339
+ const normalized = nodePath.resolve(target);
78864
79340
  if (!isPathSafe(normalized, options)) throw new Error("Access denied: path outside allowed directories");
78865
79341
  const content = await readFileContent(normalized);
78866
79342
  return {
@@ -78870,7 +79346,7 @@ async function readAgentsFile(target, options) {
78870
79346
  };
78871
79347
  }
78872
79348
  async function saveAgentsFile(target, content, options) {
78873
- const normalized = path.resolve(target);
79349
+ const normalized = nodePath.resolve(target);
78874
79350
  if (!isPathSafe(normalized, options)) throw new Error("Access denied: path outside allowed directories");
78875
79351
  await saveFileContent(normalized, content ?? "");
78876
79352
  return readAgentsFile(normalized, options);
@@ -79070,7 +79546,7 @@ function formatMegabytes(bytes) {
79070
79546
  return (bytes / MB_DIVISOR).toFixed(2);
79071
79547
  }
79072
79548
  function resolveSyncBackupsDir() {
79073
- return path.join(getUserDataDir(), SYNC_BACKUP_DIR);
79549
+ return nodePath.join(getUserDataDir(), SYNC_BACKUP_DIR);
79074
79550
  }
79075
79551
  function toSafeIsoForFileName(iso) {
79076
79552
  return iso.replace(/:/g, "-");
@@ -79078,14 +79554,14 @@ function toSafeIsoForFileName(iso) {
79078
79554
  async function pruneOldPrePullBackups(backupsDir) {
79079
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));
79080
79556
  if (files.length <= PRE_PULL_BACKUP_KEEP_COUNT) return;
79081
- 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 });
79082
79558
  }
79083
79559
  async function createPrePullBackup(profileManager) {
79084
79560
  const snapshot = await buildLocalSnapshot(profileManager, { enforcePushGuards: false });
79085
79561
  const backupsDir = resolveSyncBackupsDir();
79086
79562
  await promises.mkdir(backupsDir, { recursive: true });
79087
79563
  const timestamp = toSafeIsoForFileName((/* @__PURE__ */ new Date()).toISOString());
79088
- 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}`);
79089
79565
  await promises.writeFile(backupPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
79090
79566
  await pruneOldPrePullBackups(backupsDir);
79091
79567
  logInfo("[cloud-sync] created pre-pull backup", { backupPath });
@@ -79319,17 +79795,17 @@ function buildRuntimeEnv(codexHomePath) {
79319
79795
  ...process.env,
79320
79796
  ...codexHomePath?.trim() ? { CODEX_HOME: codexHomePath.trim() } : {}
79321
79797
  };
79322
- const homeDir = env.HOME ?? env.USERPROFILE ?? os.homedir();
79798
+ const homeDir = env.HOME ?? env.USERPROFILE ?? nodeOs.homedir();
79323
79799
  const extraPaths = [
79324
79800
  "/opt/homebrew/bin",
79325
79801
  "/usr/local/bin",
79326
- path.join(homeDir, ".local", "bin"),
79327
- path.join(homeDir, ".fnm", "aliases", "default", "bin"),
79328
- path.join(homeDir, ".fnm", "current", "bin"),
79329
- path.join(homeDir, ".volta", "bin"),
79330
- 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")
79331
79807
  ];
79332
- 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);
79333
79809
  return env;
79334
79810
  }
79335
79811
  function createTimeoutError(command, args) {
@@ -79723,6 +80199,7 @@ const AUTO_ROLL_ATTEMPT_TTL_MS = 3e4;
79723
80199
  const EMPTY_USAGE_SUMMARY = {
79724
80200
  windowKey: null,
79725
80201
  usagePercent: 0,
80202
+ remainingPercent: 100,
79726
80203
  hasUsage: false,
79727
80204
  resetsInSeconds: null,
79728
80205
  windowMinutes: null
@@ -79755,12 +80232,14 @@ function summarizeRateLimitSnapshot(snapshot) {
79755
80232
  const rawUsedPercent = finiteNumberOrNull(window.usedPercent);
79756
80233
  const hasUsage = rawUsedPercent !== null;
79757
80234
  const usagePercent = hasUsage ? Math.max(0, Math.min(100, rawUsedPercent)) : 0;
80235
+ const remainingPercent = 100 - usagePercent;
79758
80236
  const resetsInSeconds = resolveResetsInSeconds(window);
79759
80237
  const windowMinutes = finiteNumberOrNull(window.windowMinutes);
79760
80238
  if (!summary.windowKey) {
79761
80239
  summary = {
79762
80240
  windowKey: entry.key,
79763
80241
  usagePercent,
80242
+ remainingPercent,
79764
80243
  hasUsage,
79765
80244
  resetsInSeconds,
79766
80245
  windowMinutes
@@ -79771,6 +80250,7 @@ function summarizeRateLimitSnapshot(snapshot) {
79771
80250
  summary = {
79772
80251
  windowKey: entry.key,
79773
80252
  usagePercent,
80253
+ remainingPercent,
79774
80254
  hasUsage,
79775
80255
  resetsInSeconds,
79776
80256
  windowMinutes
@@ -79781,6 +80261,7 @@ function summarizeRateLimitSnapshot(snapshot) {
79781
80261
  summary = {
79782
80262
  windowKey: entry.key,
79783
80263
  usagePercent,
80264
+ remainingPercent,
79784
80265
  hasUsage,
79785
80266
  resetsInSeconds,
79786
80267
  windowMinutes
@@ -79791,6 +80272,7 @@ function summarizeRateLimitSnapshot(snapshot) {
79791
80272
  if (resetsInSeconds !== null && resetsInSeconds < summary.resetsInSeconds) summary = {
79792
80273
  windowKey: entry.key,
79793
80274
  usagePercent,
80275
+ remainingPercent,
79794
80276
  hasUsage,
79795
80277
  resetsInSeconds,
79796
80278
  windowMinutes
@@ -79800,6 +80282,7 @@ function summarizeRateLimitSnapshot(snapshot) {
79800
80282
  if (!hasUsage && !summary.hasUsage && summary.resetsInSeconds === null && resetsInSeconds !== null) summary = {
79801
80283
  windowKey: entry.key,
79802
80284
  usagePercent,
80285
+ remainingPercent,
79803
80286
  hasUsage,
79804
80287
  resetsInSeconds,
79805
80288
  windowMinutes
@@ -79807,26 +80290,42 @@ function summarizeRateLimitSnapshot(snapshot) {
79807
80290
  }
79808
80291
  return summary;
79809
80292
  }
79810
- function compareAutoRollCandidates(a, b) {
79811
- 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;
79812
80311
  const aReset = a.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
79813
80312
  const bReset = b.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY;
79814
80313
  if (aReset !== bReset) return aReset - bReset;
79815
80314
  return a.profile.name.localeCompare(b.profile.name);
79816
80315
  }
79817
- function computeAutoRollCandidate(profiles, usageSummaries, currentProfileName, currentSummary, switchThreshold) {
79818
- 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) => ({
79819
80319
  profile,
79820
80320
  usageSummary: usageSummaries.get(profile.name) ?? summarizeRateLimitSnapshot(profile.rateLimit ?? null)
79821
80321
  }));
79822
80322
  if (candidates.length === 0) return null;
79823
- let selected = candidates.filter((candidate) => candidate.usageSummary.hasUsage && candidate.usageSummary.usagePercent < switchThreshold).sort(compareAutoRollCandidates)[0] ?? null;
79824
- if (!selected) selected = candidates.filter((candidate) => candidate.usageSummary.hasUsage).sort(compareAutoRollCandidates)[0] ?? null;
79825
- 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;
79826
80325
  if (!selected) return null;
79827
80326
  if (selected.usageSummary.hasUsage && currentSummary.hasUsage) {
79828
- if (selected.usageSummary.usagePercent > currentSummary.usagePercent) return null;
79829
- if (selected.usageSummary.usagePercent === currentSummary.usagePercent) {
80327
+ if (selected.usageSummary.remainingPercent < currentSummary.remainingPercent) return null;
80328
+ if (selected.usageSummary.remainingPercent === currentSummary.remainingPercent) {
79830
80329
  if ((selected.usageSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY) >= (currentSummary.resetsInSeconds ?? Number.POSITIVE_INFINITY)) return null;
79831
80330
  }
79832
80331
  }
@@ -81549,12 +82048,12 @@ var TelegramBridge = class {
81549
82048
  }
81550
82049
  buildRuntimeLockPath(token) {
81551
82050
  const tokenHash = createHash("sha1").update(token).digest("hex").slice(0, 16);
81552
- return path.join(getUserDataDir(), `${TELEGRAM_BRIDGE_LOCK_FILE_PREFIX}-${tokenHash}.lock`);
82051
+ return nodePath.join(getUserDataDir(), `${TELEGRAM_BRIDGE_LOCK_FILE_PREFIX}-${tokenHash}.lock`);
81553
82052
  }
81554
82053
  async acquireRuntimeLock(token) {
81555
82054
  if (this.runtimeLockPath) return;
81556
82055
  const lockPath = this.buildRuntimeLockPath(token);
81557
- await fs$1.mkdir(path.dirname(lockPath), { recursive: true });
82056
+ await fs$1.mkdir(nodePath.dirname(lockPath), { recursive: true });
81558
82057
  const payload = JSON.stringify({
81559
82058
  pid: process.pid,
81560
82059
  parentPid: process.ppid > 0 ? process.ppid : null,
@@ -81774,13 +82273,26 @@ function createTelegramBridge(options) {
81774
82273
  return new TelegramBridge(options);
81775
82274
  }
81776
82275
  //#endregion
81777
- //#region src/codex/officialAppRestart.ts
82276
+ //#region ../../packages/runtime-codex/src/codex/officialAppRestart.ts
81778
82277
  const RESTART_COOLDOWN_MS = 6e4;
81779
82278
  const COMMAND_TIMEOUT_MS = 3e3;
81780
- const QUIT_WAIT_MS = 1e4;
82279
+ const EXIT_WAIT_MS = 1e4;
82280
+ const LAUNCH_WAIT_MS = 15e3;
81781
82281
  const POLL_INTERVAL_MS = 250;
82282
+ const APP_DISCOVERY_CACHE_TTL_MS = 6e4;
82283
+ const APP_DISCOVERY_MISS_CACHE_TTL_MS = 5e3;
81782
82284
  let restartPromise = null;
81783
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
+ }
81784
82296
  function runCommand(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
81785
82297
  return new Promise((resolve) => {
81786
82298
  const child = spawn(command, args, { stdio: [
@@ -81821,15 +82333,252 @@ function runCommand(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
81821
82333
  });
81822
82334
  });
81823
82335
  }
81824
- function appleScriptString(value) {
81825
- 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));
81826
82366
  }
81827
- async function readBundleIdentifier(appPath) {
81828
- const plistPath = path.join(appPath, "Contents", "Info.plist");
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
+ };
82575
+ }
82576
+ async function readBundleString(appPath, key) {
82577
+ const plistPath = nodePath.join(appPath, "Contents", "Info.plist");
81829
82578
  if (!existsSync(plistPath)) return null;
81830
82579
  const result = await runCommand("/usr/bin/plutil", [
81831
82580
  "-extract",
81832
- "CFBundleIdentifier",
82581
+ key,
81833
82582
  "raw",
81834
82583
  "-o",
81835
82584
  "-",
@@ -81840,11 +82589,13 @@ async function readBundleIdentifier(appPath) {
81840
82589
  return bundleId.length > 0 ? bundleId : null;
81841
82590
  }
81842
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;
81843
82595
  const rawPaths = /* @__PURE__ */ new Set();
81844
- const envPath = process.env.CODEXUSE_OFFICIAL_CODEX_APP_PATH?.trim();
81845
82596
  if (envPath) rawPaths.add(envPath);
81846
82597
  rawPaths.add("/Applications/Codex.app");
81847
- rawPaths.add(path.join(homedir(), "Applications", "Codex.app"));
82598
+ rawPaths.add(nodePath.join(homedir(), "Applications", "Codex.app"));
81848
82599
  const mdfind = await runCommand("/usr/bin/mdfind", ["kMDItemFSName == 'Codex.app'"], 1500);
81849
82600
  if (mdfind.code === 0) for (const line of mdfind.stdout.split(/\r?\n/)) {
81850
82601
  const trimmed = line.trim();
@@ -81852,85 +82603,716 @@ async function discoverCodexAppCandidates() {
81852
82603
  }
81853
82604
  const candidates = [];
81854
82605
  for (const appPath of rawPaths) {
81855
- if (!existsSync(appPath) || path.basename(appPath) !== "Codex.app") continue;
81856
- const bundleId = await readBundleIdentifier(appPath);
82606
+ if (!existsSync(appPath) || nodePath.basename(appPath) !== "Codex.app") continue;
82607
+ const bundleId = await readBundleString(appPath, "CFBundleIdentifier");
81857
82608
  if (!bundleId || bundleId.toLowerCase().includes("codexuse")) continue;
81858
82609
  candidates.push({
81859
82610
  appPath,
81860
- bundleId
82611
+ bundleId,
82612
+ version: await readBundleString(appPath, "CFBundleShortVersionString"),
82613
+ executableName: await readBundleString(appPath, "CFBundleExecutable") ?? "Codex"
81861
82614
  });
81862
82615
  }
81863
- 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;
81864
82623
  }
81865
82624
  async function getRunningCodexPids(candidate) {
81866
82625
  const result = await runCommand("/bin/ps", ["-axo", "pid=,args="], 2e3);
81867
82626
  const pids = /* @__PURE__ */ new Set();
81868
- 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);
81869
82641
  if (result.code === 0) for (const line of result.stdout.split(/\r?\n/)) {
81870
82642
  const match = line.match(/^\s*(\d+)\s+(.+)$/);
81871
82643
  if (!match) continue;
81872
82644
  const pid = Number(match[1]);
81873
82645
  const args = match[2] ?? "";
81874
- if (Number.isInteger(pid) && args.includes(executableRoot)) pids.add(pid);
82646
+ if (Number.isInteger(pid) && args.includes(executablePath)) pids.add(pid);
82647
+ }
82648
+ return Array.from(pids).sort((a, b) => a - b);
82649
+ }
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
+ };
81875
82665
  }
81876
- return Array.from(pids);
82666
+ const [runningPids, mainPids] = await Promise.all([getRunningCodexPids(fallback), getRunningCodexMainPids(fallback)]);
82667
+ return {
82668
+ candidate: fallback,
82669
+ runningPids,
82670
+ mainPids
82671
+ };
81877
82672
  }
81878
- async function waitForCodexExit(candidate) {
81879
- const deadline = Date.now() + QUIT_WAIT_MS;
82673
+ async function waitForCodexExit(candidate, timeoutMs) {
82674
+ const deadline = Date.now() + timeoutMs;
81880
82675
  while (Date.now() < deadline) {
81881
82676
  if ((await getRunningCodexPids(candidate)).length === 0) return true;
81882
82677
  await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
81883
82678
  }
81884
82679
  return false;
81885
82680
  }
81886
- async function requestCodexQuit(candidate) {
81887
- 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;
81888
82688
  }
81889
82689
  async function openCodex(candidate) {
81890
82690
  if ((await runCommand("/usr/bin/open", ["-b", candidate.bundleId])).code === 0) return true;
81891
82691
  return (await runCommand("/usr/bin/open", [candidate.appPath])).code === 0;
81892
82692
  }
81893
- async function restartOfficialCodexAppOnce() {
81894
- if (process.platform !== "darwin") return {
81895
- status: "skipped",
81896
- 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
81897
82867
  };
81898
- if (lastRestartStartedAt > 0 && Date.now() - lastRestartStartedAt < RESTART_COOLDOWN_MS) return {
81899
- status: "skipped",
81900
- 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
81901
82877
  };
81902
- const candidate = (await discoverCodexAppCandidates())[0] ?? null;
81903
- if (!candidate) return {
81904
- status: "skipped",
81905
- reason: "official-codex-app-not-found"
82878
+ if (process.platform !== "darwin") return {
82879
+ ...base,
82880
+ state: "unsupported"
81906
82881
  };
81907
- if ((await getRunningCodexPids(candidate)).length === 0) return {
81908
- status: "skipped",
81909
- reason: "official-codex-app-not-running"
82882
+ const { candidate, mainPids } = await resolveCodexAppTarget();
82883
+ if (!candidate) return {
82884
+ ...base,
82885
+ state: "not-found"
81910
82886
  };
81911
- lastRestartStartedAt = Date.now();
81912
- await requestCodexQuit(candidate);
81913
- if (!await waitForCodexExit(candidate)) return {
81914
- status: "failed",
82887
+ if (mainPids.length === 0) return {
82888
+ ...base,
82889
+ state: "not-running",
81915
82890
  appPath: candidate.appPath,
81916
82891
  bundleId: candidate.bundleId,
81917
- reason: "quit-timeout"
82892
+ runningPids: mainPids
81918
82893
  };
81919
- if (!await openCodex(candidate)) return {
81920
- 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",
81921
82901
  appPath: candidate.appPath,
81922
82902
  bundleId: candidate.bundleId,
81923
- reason: "reopen-failed"
82903
+ runningPids: mainPids
81924
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
+ }
81925
82951
  return {
81926
- status: "restarted",
82952
+ state: "found",
81927
82953
  appPath: candidate.appPath,
81928
- bundleId: candidate.bundleId
82954
+ bundleId: candidate.bundleId,
82955
+ version: candidate.version,
82956
+ instances: managed,
82957
+ unmanaged
81929
82958
  };
81930
82959
  }
81931
- 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 = {}) {
81932
83310
  if (restartPromise) return restartPromise;
81933
- 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(() => {
81934
83316
  restartPromise = null;
81935
83317
  });
81936
83318
  return restartPromise;
@@ -82041,6 +83423,37 @@ function describeActiveAuthOwner(snapshot) {
82041
83423
  if (!snapshot) return null;
82042
83424
  return snapshot.email ?? snapshot.userId ?? snapshot.chatgptUserId ?? snapshot.accountId ?? null;
82043
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
+ }
82044
83457
  async function resolveCodexBinaryOrThrow(context) {
82045
83458
  try {
82046
83459
  return await requireCodexCli();
@@ -82048,7 +83461,7 @@ async function resolveCodexBinaryOrThrow(context) {
82048
83461
  if (error instanceof CodexCliMissingError) {
82049
83462
  const reason = error.status.reason ?? "Codex CLI not found. Install @openai/codex or set CODEX_BINARY.";
82050
83463
  const message = context ? `${context} ${reason}` : reason;
82051
- throw new Error(message);
83464
+ throw new Error(message, { cause: error });
82052
83465
  }
82053
83466
  throw error;
82054
83467
  }
@@ -82144,6 +83557,8 @@ const createServer = fn(function* () {
82144
83557
  clients,
82145
83558
  logOutgoingPush
82146
83559
  });
83560
+ setExternalCodexThreadSyncProgressReporter((payload) => runPromise(pushBus.publishAll(WS_CHANNELS.externalThreadSyncProgress, payload)));
83561
+ yield* addFinalizer(() => sync(() => setExternalCodexThreadSyncProgressReporter(null)));
82147
83562
  const agentChatConfig = getAgentChatConfig();
82148
83563
  const generalChatConfig = getGeneralChatConfig();
82149
83564
  yield* readiness.markPushBusReady;
@@ -82753,6 +84168,61 @@ const createServer = fn(function* () {
82753
84168
  properties
82754
84169
  });
82755
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
+ };
82756
84226
  const publishProfileSwitched = (payload) => {
82757
84227
  refreshTray();
82758
84228
  return runPromise$2(pushBus.publishAll(WS_CHANNELS.profilesSwitched, payload));
@@ -82760,6 +84230,7 @@ const createServer = fn(function* () {
82760
84230
  const publishCliStatusUpdated = (payload) => runPromise$2(pushBus.publishAll(WS_CHANNELS.cliStatusUpdated, payload));
82761
84231
  let backgroundAutoRollInFlight = false;
82762
84232
  let lastAutoRollAttempt = null;
84233
+ const autoRollRearmBlockedProfiles = /* @__PURE__ */ new Set();
82763
84234
  const rememberAutoRollAttempt = (source, target) => {
82764
84235
  if (!source || !target) return;
82765
84236
  lastAutoRollAttempt = {
@@ -82772,29 +84243,177 @@ const createServer = fn(function* () {
82772
84243
  if (!lastAutoRollAttempt) return false;
82773
84244
  return lastAutoRollAttempt.source === source && lastAutoRollAttempt.target === target && Date.now() - lastAutoRollAttempt.timestamp < 3e4;
82774
84245
  };
82775
- const maybeRestartOfficialCodexAfterAutoRoll = (targetProfileName) => {
82776
- (async () => {
82777
- if (!normalizeAutoRollSettings(await getStoredAutoRollSettings()).restartOfficialCodexOnAutoRoll) return;
82778
- 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
+ });
82779
84378
  trackAnalyticsEvent("official_codex_restart_after_auto_roll", {
82780
84379
  status: result.status,
82781
84380
  reason: result.status === "skipped" || result.status === "failed" ? result.reason : void 0,
82782
84381
  targetProfileName
82783
84382
  });
82784
84383
  if (result.status === "failed") logger.warn("Official Codex restart after auto-roll failed", result);
82785
- else if (result.status === "restarted") logger.info("Official Codex restarted after auto-roll", result);
82786
- })().catch((error) => {
84384
+ else if (result.status === "restarted" || result.status === "started") logger.info("Official Codex restarted after auto-roll", result);
84385
+ } catch (error) {
82787
84386
  logger.warn("Official Codex restart after auto-roll threw", { error });
82788
- });
84387
+ }
82789
84388
  };
82790
84389
  const handleProfileSwitch = async (name, source = "app") => {
82791
84390
  if (!name) throw new Error("Profile name is required");
82792
84391
  const previousProfile = source === "auto-roll" ? (await profileManager.getCurrentProfile()).name ?? null : null;
82793
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
+ }
82794
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
+ }
82795
84414
  await publishProfileSwitched({ name });
82796
84415
  trackAnalyticsEvent("profile_switched", { source });
82797
- if (source === "auto-roll") maybeRestartOfficialCodexAfterAutoRoll(name);
84416
+ if (source === "auto-roll") await maybeRestartOfficialCodexAfterAutoRoll(name, targetProfileKey);
82798
84417
  };
82799
84418
  const maybeRunBackgroundAutoRoll = async () => {
82800
84419
  if (backgroundAutoRollInFlight) return;
@@ -82818,14 +84437,17 @@ const createServer = fn(function* () {
82818
84437
  rateLimit: snapshot
82819
84438
  };
82820
84439
  });
84440
+ rearmAutoRollBlockedProfiles(usageSummaries, settings.rearmRemainingThreshold);
82821
84441
  const currentProfile = profilesWithSnapshots.find((profile) => profile.name === currentProfileName);
82822
84442
  if (!currentProfile) return;
82823
84443
  const currentSummary = usageSummaries.get(currentProfileName) ?? summarizeRateLimitSnapshot(currentProfile.rateLimit ?? null);
82824
- if (!currentSummary.hasUsage || currentSummary.usagePercent < settings.switchThreshold) return;
82825
- 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);
82826
84448
  if (!candidate || isRecentAutoRollAttempt(currentProfileName, candidate.profile.name)) return;
82827
- rememberAutoRollAttempt(currentProfileName, candidate.profile.name);
82828
84449
  await handleProfileSwitch(candidate.profile.name, "auto-roll");
84450
+ blockAutoRollUntilRearm(currentProfileName);
82829
84451
  } catch (error) {
82830
84452
  logger.warn("Background auto-roll failed", { error });
82831
84453
  } finally {
@@ -82858,6 +84480,9 @@ const createServer = fn(function* () {
82858
84480
  profileName: payload.profileName,
82859
84481
  snapshot: payload.snapshot ?? null
82860
84482
  }));
84483
+ maybeSendLowRemainingNotification(payload).catch((error) => {
84484
+ logger.warn("Low remaining notification failed", { error });
84485
+ });
82861
84486
  maybeRunBackgroundAutoRoll();
82862
84487
  };
82863
84488
  const handleRateLimitStateChanged = (payload) => {
@@ -82906,7 +84531,8 @@ const createServer = fn(function* () {
82906
84531
  persistExternalThreadOverrideIntent,
82907
84532
  dispatchOrchestrationCommand,
82908
84533
  requestExternalCodexThreadSyncRefresh: () => requestExternalCodexThreadSyncRefresh("renderer-ws"),
82909
- runExternalCodexThreadSync
84534
+ runExternalCodexThreadSync,
84535
+ runExternalCodexThreadSyncForProject
82910
84536
  };
82911
84537
  const routeRequest = fnUntraced(function* (request, ws) {
82912
84538
  const orchestrationResult = yield* handleOrchestrationRequest(request.body, orchestrationRequestContext);
@@ -83265,9 +84891,15 @@ const createServer = fn(function* () {
83265
84891
  return yield* promise(() => telegramBridge.testToken(body.token));
83266
84892
  }
83267
84893
  case WS_METHODS.profilesFetch: {
83268
- 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));
83269
84898
  refreshTray();
83270
- return data;
84899
+ return {
84900
+ ...data,
84901
+ officialCodexStatus
84902
+ };
83271
84903
  }
83272
84904
  case WS_METHODS.profilesStartAuth: {
83273
84905
  const body = stripRequestTag(request.body);
@@ -83279,30 +84911,23 @@ const createServer = fn(function* () {
83279
84911
  if (yield* promise(() => profileManager.profileExists(name))) return yield* new RouteRequestError({ message: `Profile '${name}' already exists! Choose a different name.` });
83280
84912
  } else if (!(yield* promise(() => profileManager.profileExists(name)))) return yield* new RouteRequestError({ message: `Profile '${name}' not found!` });
83281
84913
  const existingSession = authSessions.get(name);
83282
- if (existingSession && !existingSession.completed) {
83283
- existingSession.process.kill();
84914
+ if (existingSession) {
84915
+ if (!existingSession.completed) existingSession.process.kill();
83284
84916
  if (existingSession.timeout) clearTimeout(existingSession.timeout);
83285
- if (existingSession.restoreConfig) existingSession.restoreConfig();
84917
+ cleanupProfileAuthSession(existingSession);
83286
84918
  authSessions.delete(name);
83287
84919
  }
83288
84920
  const startedAt = Date.now();
83289
- const initialAuthSnapshot = yield* promise(() => profileManager.captureActiveAuthSnapshot());
83290
- let cleanupConfig = null;
83291
- const restoreConfig = async () => {
83292
- if (!cleanupConfig) return;
83293
- const restore = cleanupConfig;
83294
- cleanupConfig = null;
83295
- try {
83296
- await restore();
83297
- } catch (error) {
83298
- console.warn("Failed to restore Codex config after login attempt:", error);
83299
- }
83300
- };
84921
+ const loginHome = yield* promise(() => createIsolatedCodexLoginHome());
84922
+ const initialAuthSnapshot = yield* promise(() => profileManager.captureAuthSnapshot(loginHome.authPath));
83301
84923
  try {
83302
- const storageOptions = yield* promise(() => resolveCodexHomeStorageOptions());
83303
- cleanupConfig = yield* promise(() => sanitizeCodexConfigForCli(storageOptions));
83304
84924
  const codexPath = yield* promise(() => resolveCodexBinaryOrThrow("Codex CLI is required to start authentication."));
83305
- 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
+ });
83306
84931
  const command = buildCodexCommand$1(codexPath, ["login"], loginEnv);
83307
84932
  const loginProcess = spawn(command.command, command.args, {
83308
84933
  stdio: [
@@ -83322,7 +84947,8 @@ const createServer = fn(function* () {
83322
84947
  mode,
83323
84948
  startedAt,
83324
84949
  initialAuthSnapshot,
83325
- restoreConfig
84950
+ authPath: loginHome.authPath,
84951
+ cleanupAuthHome: loginHome.cleanup
83326
84952
  };
83327
84953
  authSessions.set(name, session);
83328
84954
  loginProcess.stdout?.on("data", (chunk) => {
@@ -83342,7 +84968,6 @@ const createServer = fn(function* () {
83342
84968
  session.completed = true;
83343
84969
  session.exitCode = code;
83344
84970
  if (session.timeout) clearTimeout(session.timeout);
83345
- restoreConfig();
83346
84971
  });
83347
84972
  loginProcess.on("error", (error) => {
83348
84973
  session.error = compactErrorText(error instanceof Error ? error.message : String(error)) || "Authentication failed.";
@@ -83352,7 +84977,6 @@ const createServer = fn(function* () {
83352
84977
  clearTimeout(session.timeout);
83353
84978
  session.timeout = void 0;
83354
84979
  }
83355
- restoreConfig();
83356
84980
  });
83357
84981
  session.timeout = setTimeout(() => {
83358
84982
  if (!session.completed) {
@@ -83366,7 +84990,7 @@ const createServer = fn(function* () {
83366
84990
  return;
83367
84991
  } catch (error) {
83368
84992
  trackAnalyticsEvent("profile_auth_start_failed", { mode });
83369
- yield* promise(() => restoreConfig());
84993
+ yield* promise(() => loginHome.cleanup());
83370
84994
  throw error;
83371
84995
  }
83372
84996
  }
@@ -83391,38 +85015,41 @@ const createServer = fn(function* () {
83391
85015
  const session = authSessions.get(body.name);
83392
85016
  if (!session) return yield* new RouteRequestError({ message: "No authentication session found" });
83393
85017
  if (!session.completed) return yield* new RouteRequestError({ message: "Authentication not yet completed" });
83394
- if (session.exitCode !== 0) {
83395
- authSessions.delete(body.name);
83396
- trackAnalyticsEvent("profile_auth_failed", { mode: session.mode });
83397
- return yield* new RouteRequestError({ message: formatUserFacingError(session.error ?? `Authentication failed with code ${session.exitCode}`, { fallback: "Authentication failed. Try again." }) });
83398
- }
83399
- if (session.mode === "create") {
83400
- const currentAuthSnapshot = yield* promise(() => profileManager.captureActiveAuthSnapshot());
83401
- if (currentAuthSnapshot.fingerprint === (session.initialAuthSnapshot?.fingerprint ?? null)) {
83402
- 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) {
83403
85024
  trackAnalyticsEvent("profile_auth_complete_failed", { mode: session.mode });
83404
- const activeOwner = describeActiveAuthOwner(currentAuthSnapshot);
83405
- const ownerText = activeOwner ? ` The active auth still belongs to '${activeOwner}'.` : "";
83406
- 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
+ }
83407
85035
  }
83408
- }
83409
- try {
83410
85036
  if (session.mode === "refresh") {
83411
- yield* promise(() => profileManager.refreshProfileAuth(body.name));
85037
+ yield* promise(() => profileManager.refreshProfileAuthFromFile(body.name, session.authPath));
83412
85038
  trackAnalyticsEvent("profile_tokens_refreshed", { mode: session.mode });
83413
85039
  } else {
83414
85040
  yield* promise(() => assertProfileCreationAllowed(profileManager));
83415
- yield* promise(() => profileManager.createProfile(body.name));
85041
+ yield* promise(() => profileManager.createProfileFromAuthFile(body.name, session.authPath));
83416
85042
  trackAnalyticsEvent("profile_created", { mode: session.mode });
83417
85043
  }
83418
85044
  trackAnalyticsEvent("profile_auth_completed", { mode: session.mode });
83419
- authSessions.delete(body.name);
83420
85045
  refreshTray();
83421
85046
  return;
83422
85047
  } catch (error) {
83423
85048
  trackAnalyticsEvent("profile_auth_complete_failed", { mode: session.mode });
83424
- authSessions.delete(body.name);
83425
85049
  throw error;
85050
+ } finally {
85051
+ authSessions.delete(body.name);
85052
+ yield* promise(() => cleanupProfileAuthSession(session));
83426
85053
  }
83427
85054
  }
83428
85055
  case WS_METHODS.profilesSwitch: {
@@ -83446,13 +85073,39 @@ const createServer = fn(function* () {
83446
85073
  }
83447
85074
  case WS_METHODS.profilesDelete: {
83448
85075
  const body = stripRequestTag(request.body);
83449
- if (!body.name?.trim()) return yield* new RouteRequestError({ message: "Profile name is required" });
83450
- yield* promise(() => profileManager.deleteProfile(body.name));
83451
- trackAnalyticsEvent("profile_deleted");
83452
- 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" });
83453
85108
  refreshTray();
83454
- const currentProfileName = typeof currentProfile.name === "string" && currentProfile.name.length > 0 ? currentProfile.name : null;
83455
- if (currentProfileName) yield* promise(() => publishProfileSwitched({ name: currentProfileName }));
83456
85109
  return;
83457
85110
  }
83458
85111
  case WS_METHODS.profilesExport: {
@@ -83473,12 +85126,12 @@ const createServer = fn(function* () {
83473
85126
  clearTimeout(session.timeout);
83474
85127
  session.timeout = void 0;
83475
85128
  }
83476
- if (session.restoreConfig) session.restoreConfig();
85129
+ yield* promise(() => cleanupProfileAuthSession(session));
83477
85130
  authSessions.delete(body.name);
83478
85131
  return;
83479
85132
  }
83480
85133
  case WS_METHODS.rateLimitsRefreshNow: {
83481
- const profiles = yield* promise(() => profileManager.listProfiles());
85134
+ const { profiles } = yield* promise(() => loadLicenseVisibleProfiles(profileManager));
83482
85135
  const profileNames = Array.from(new Set(profiles.filter((profile) => profile.isValid).map((profile) => profile.name).filter(Boolean)));
83483
85136
  return { enqueued: (yield* promise(() => enqueueAccountsRefreshNow(profileNames))).enqueuedAccountIds.length };
83484
85137
  }
@@ -83517,15 +85170,197 @@ const createServer = fn(function* () {
83517
85170
  case WS_METHODS.autoRollSaveSettings: {
83518
85171
  const normalized = normalizeAutoRollSettings(stripRequestTag(request.body).settings);
83519
85172
  yield* promise(() => persistAutoRollSettings(normalized));
85173
+ maybeRunBackgroundAutoRoll();
83520
85174
  trackAnalyticsEvent("auto_roll_settings_saved", {
83521
85175
  enabled: normalized.enabled,
83522
- warningThreshold: normalized.warningThreshold,
83523
- switchThreshold: normalized.switchThreshold,
83524
- 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
83525
85183
  });
83526
85184
  return normalized;
83527
85185
  }
83528
- 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
+ }
83529
85364
  case WS_METHODS.cliInfo: return yield* promise(() => getCodexCliInfo());
83530
85365
  case WS_METHODS.cliStatus: return yield* promise(() => getCliInstallStatusWithFreshness());
83531
85366
  case WS_METHODS.cliCheckFreshness: {
@@ -83605,8 +85440,8 @@ const ServerLive = succeed$3(Server, {
83605
85440
  //#region src/serverLogger.ts
83606
85441
  const ServerLoggerLive = gen(function* () {
83607
85442
  const config = yield* ServerConfig$1;
83608
- const logDir = path.join(config.stateDir, "logs");
83609
- const logPath = path.join(logDir, "server.log");
85443
+ const logDir = nodePath.join(config.stateDir, "logs");
85444
+ const logPath = nodePath.join(logDir, "server.log");
83610
85445
  yield* sync(() => {
83611
85446
  fs.mkdirSync(logDir, { recursive: true });
83612
85447
  });