codexuse-cli 3.9.7 → 3.9.8

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.
@@ -58516,7 +58516,9 @@ function createDefaultAppState() {
58516
58516
  lastObservedPid: null,
58517
58517
  lastRestartStatus: null,
58518
58518
  lastRestartReason: null,
58519
- instancesByProfileName: {}
58519
+ activity: [],
58520
+ instancesByProfileName: {},
58521
+ pendingRestartDebt: null
58520
58522
  },
58521
58523
  app: {
58522
58524
  lastAppVersion: null,
@@ -58612,6 +58614,9 @@ function asString$9(value) {
58612
58614
  function asNumberOrNull(value) {
58613
58615
  return typeof value === "number" && Number.isFinite(value) ? value : null;
58614
58616
  }
58617
+ function asBooleanOrNull(value) {
58618
+ return typeof value === "boolean" ? value : null;
58619
+ }
58615
58620
  function normalizeAppState(raw) {
58616
58621
  const defaults = createDefaultAppState();
58617
58622
  if (!isRecord$7(raw)) return defaults;
@@ -58627,6 +58632,30 @@ function normalizeAppState(raw) {
58627
58632
  merged.officialCodex.lastObservedPid = asNumberOrNull(merged.officialCodex.lastObservedPid);
58628
58633
  merged.officialCodex.lastRestartStatus = asString$9(merged.officialCodex.lastRestartStatus);
58629
58634
  merged.officialCodex.lastRestartReason = asString$9(merged.officialCodex.lastRestartReason);
58635
+ merged.officialCodex.activity = Array.isArray(merged.officialCodex.activity) ? merged.officialCodex.activity.filter((entry) => isRecord$7(entry)).map((entry) => ({
58636
+ id: asString$9(entry.id) ?? "",
58637
+ at: asString$9(entry.at) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
58638
+ kind: entry.kind === "auto-roll-eval" || entry.kind === "profile-switch" || entry.kind === "auth-verified" || entry.kind === "official-codex-restart" || entry.kind === "low-remaining-alert" ? entry.kind : "auto-roll-eval",
58639
+ status: asString$9(entry.status) ?? "unknown",
58640
+ reason: asString$9(entry.reason),
58641
+ decisionId: asString$9(entry.decisionId),
58642
+ profileName: asString$9(entry.profileName),
58643
+ sourceProfileName: asString$9(entry.sourceProfileName),
58644
+ targetProfileName: asString$9(entry.targetProfileName),
58645
+ remainingPercent: asNumberOrNull(entry.remainingPercent),
58646
+ threshold: asNumberOrNull(entry.threshold),
58647
+ snapshotAgeMs: asNumberOrNull(entry.snapshotAgeMs),
58648
+ snapshotSource: asString$9(entry.snapshotSource),
58649
+ phase: asString$9(entry.phase),
58650
+ pid: asNumberOrNull(entry.pid),
58651
+ profileKeyHash: asString$9(entry.profileKeyHash),
58652
+ switchVerified: asBooleanOrNull(entry.switchVerified),
58653
+ restartRequested: asBooleanOrNull(entry.restartRequested),
58654
+ restartResult: asString$9(entry.restartResult),
58655
+ observedProfileName: asString$9(entry.observedProfileName),
58656
+ observedProfileKeyHash: asString$9(entry.observedProfileKeyHash),
58657
+ observedProfileMatchSource: asString$9(entry.observedProfileMatchSource)
58658
+ })).filter((entry) => entry.id && entry.at).slice(-50) : [];
58630
58659
  if (!isRecord$7(merged.officialCodex.instancesByProfileName)) merged.officialCodex.instancesByProfileName = {};
58631
58660
  else {
58632
58661
  const nextInstances = {};
@@ -58650,6 +58679,19 @@ function normalizeAppState(raw) {
58650
58679
  }
58651
58680
  merged.officialCodex.instancesByProfileName = nextInstances;
58652
58681
  }
58682
+ if (isRecord$7(merged.officialCodex.pendingRestartDebt)) {
58683
+ const debt = merged.officialCodex.pendingRestartDebt;
58684
+ const targetProfileName = asString$9(debt.targetProfileName);
58685
+ merged.officialCodex.pendingRestartDebt = targetProfileName ? {
58686
+ targetProfileName,
58687
+ targetProfileKey: asString$9(debt.targetProfileKey),
58688
+ sourceProfileName: asString$9(debt.sourceProfileName),
58689
+ sourceProfileKey: asString$9(debt.sourceProfileKey),
58690
+ decisionId: asString$9(debt.decisionId),
58691
+ attempts: asNumberOrNull(debt.attempts) ?? 0,
58692
+ lastReason: asString$9(debt.lastReason)
58693
+ } : null;
58694
+ } else merged.officialCodex.pendingRestartDebt = null;
58653
58695
  merged.app.lastAppVersion = asString$9(merged.app.lastAppVersion);
58654
58696
  merged.app.pendingUpdateVersion = asString$9(merged.app.pendingUpdateVersion);
58655
58697
  merged.app.lastProfileName = asString$9(merged.app.lastProfileName);
@@ -59964,8 +60006,8 @@ function logError(...args) {
59964
60006
  console.error(...args);
59965
60007
  }
59966
60008
  function logInfo(...args) {
59967
- if (isTestEnv && !isMocked(console.warn)) return;
59968
- console.warn(...args);
60009
+ if (isTestEnv && !isMocked(console.info)) return;
60010
+ console.info(...args);
59969
60011
  }
59970
60012
  //#endregion
59971
60013
  //#region ../../packages/runtime-codex/src/codex/app-server.ts
@@ -74725,11 +74767,11 @@ var ProfileManager = class {
74725
74767
  const existingIdentity = this.resolveProfileIdentityFromData(profileName, existing, existingMetadata);
74726
74768
  const nextIdentity = this.resolveProfileIdentityFromData(profileName, normalized, metadata);
74727
74769
  if (existingIdentity && nextIdentity && existingIdentity !== nextIdentity) {
74728
- logWarn(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`);
74770
+ logInfo(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`);
74729
74771
  return;
74730
74772
  }
74731
74773
  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.`);
74774
+ logInfo(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different workspace.`);
74733
74775
  return;
74734
74776
  }
74735
74777
  if (!this.hasTokenChanges(existing, normalized)) return;
@@ -82274,15 +82316,13 @@ function createTelegramBridge(options) {
82274
82316
  }
82275
82317
  //#endregion
82276
82318
  //#region ../../packages/runtime-codex/src/codex/officialAppRestart.ts
82277
- const RESTART_COOLDOWN_MS = 6e4;
82278
82319
  const COMMAND_TIMEOUT_MS = 3e3;
82279
82320
  const EXIT_WAIT_MS = 1e4;
82280
82321
  const LAUNCH_WAIT_MS = 15e3;
82281
82322
  const POLL_INTERVAL_MS = 250;
82282
82323
  const APP_DISCOVERY_CACHE_TTL_MS = 6e4;
82283
82324
  const APP_DISCOVERY_MISS_CACHE_TTL_MS = 5e3;
82284
- let restartPromise = null;
82285
- let lastRestartStartedAt = 0;
82325
+ const OFFICIAL_CODEX_ACTIVITY_LIMIT = 50;
82286
82326
  let profileActionLock = Promise.resolve();
82287
82327
  let appDiscoveryCache = null;
82288
82328
  function logOfficialCodexRestart(level, message, context) {
@@ -82293,6 +82333,47 @@ function logOfficialCodexRestart(level, message, context) {
82293
82333
  }
82294
82334
  logger(`[official-codex-restart] ${message}`);
82295
82335
  }
82336
+ function hashProfileKey(profileKey) {
82337
+ if (!profileKey) return null;
82338
+ return createHash("sha256").update(profileKey).digest("hex").slice(0, 12);
82339
+ }
82340
+ async function recordOfficialCodexActivity(input) {
82341
+ const now = /* @__PURE__ */ new Date();
82342
+ const entry = {
82343
+ id: `${now.getTime().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
82344
+ at: now.toISOString(),
82345
+ kind: input.kind,
82346
+ status: input.status,
82347
+ reason: input.reason ?? null,
82348
+ decisionId: input.decisionId ?? null,
82349
+ profileName: input.profileName ?? null,
82350
+ sourceProfileName: input.sourceProfileName ?? null,
82351
+ targetProfileName: input.targetProfileName ?? null,
82352
+ remainingPercent: input.remainingPercent ?? null,
82353
+ threshold: input.threshold ?? null,
82354
+ snapshotAgeMs: input.snapshotAgeMs ?? null,
82355
+ snapshotSource: input.snapshotSource ?? null,
82356
+ phase: input.phase ?? null,
82357
+ pid: input.pid ?? null,
82358
+ profileKeyHash: input.profileKeyHash ?? hashProfileKey(input.profileKey),
82359
+ switchVerified: input.switchVerified ?? null,
82360
+ restartRequested: input.restartRequested ?? null,
82361
+ restartResult: input.restartResult ?? null,
82362
+ observedProfileName: input.observedProfileName ?? null,
82363
+ observedProfileKeyHash: input.observedProfileKeyHash ?? hashProfileKey(input.observedProfileKey),
82364
+ observedProfileMatchSource: input.observedProfileMatchSource ?? null
82365
+ };
82366
+ try {
82367
+ await updateAppState((state) => ({ officialCodex: { activity: [...state.officialCodex.activity, entry].slice(-OFFICIAL_CODEX_ACTIVITY_LIMIT) } }));
82368
+ } catch (error) {
82369
+ logOfficialCodexRestart("warn", "Failed to record official Codex activity.", {
82370
+ kind: entry.kind,
82371
+ status: entry.status,
82372
+ profileKeyHash: entry.profileKeyHash,
82373
+ error: error instanceof Error ? error.message : String(error)
82374
+ });
82375
+ }
82376
+ }
82296
82377
  function runCommand(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
82297
82378
  return new Promise((resolve) => {
82298
82379
  const child = spawn(command, args, { stdio: [
@@ -82670,14 +82751,6 @@ async function resolveCodexAppTarget() {
82670
82751
  mainPids
82671
82752
  };
82672
82753
  }
82673
- async function waitForCodexExit(candidate, timeoutMs) {
82674
- const deadline = Date.now() + timeoutMs;
82675
- while (Date.now() < deadline) {
82676
- if ((await getRunningCodexPids(candidate)).length === 0) return true;
82677
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
82678
- }
82679
- return false;
82680
- }
82681
82754
  async function signalPids(pids, signal) {
82682
82755
  const signaled = [];
82683
82756
  for (const pid of pids) try {
@@ -82686,19 +82759,6 @@ async function signalPids(pids, signal) {
82686
82759
  } catch {}
82687
82760
  return signaled;
82688
82761
  }
82689
- async function openCodex(candidate) {
82690
- if ((await runCommand("/usr/bin/open", ["-b", candidate.bundleId])).code === 0) return true;
82691
- return (await runCommand("/usr/bin/open", [candidate.appPath])).code === 0;
82692
- }
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
82762
  function toIso(value) {
82703
82763
  return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : null;
82704
82764
  }
@@ -82719,152 +82779,15 @@ async function rememberRestartResult(args) {
82719
82779
  lastRestartReason: args.reason ?? null
82720
82780
  } });
82721
82781
  }
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
82867
- };
82782
+ async function recordOfficialCodexProfileObservation(profileKey, pid) {
82783
+ if (!profileKey || !pid) return;
82784
+ await patchAppState({ officialCodex: {
82785
+ lastVerifiedLaunchProfileKey: profileKey,
82786
+ lastObservedPid: pid
82787
+ } });
82788
+ }
82789
+ function recordOfficialCodexRestartResult(args) {
82790
+ return rememberRestartResult(args);
82868
82791
  }
82869
82792
  async function getOfficialCodexSyncStatus(currentProfileKey) {
82870
82793
  const stored = (await getAppState()).officialCodex;
@@ -82873,7 +82796,8 @@ async function getOfficialCodexSyncStatus(currentProfileKey) {
82873
82796
  lastProfileSwitchAt: toIso(stored.lastProfileSwitchAt),
82874
82797
  lastVerifiedLaunchAt: toIso(stored.lastVerifiedLaunchAt),
82875
82798
  lastRestartStatus: stored.lastRestartStatus,
82876
- lastRestartReason: stored.lastRestartReason
82799
+ lastRestartReason: stored.lastRestartReason,
82800
+ activity: stored.activity
82877
82801
  };
82878
82802
  if (process.platform !== "darwin") return {
82879
82803
  ...base,
@@ -82894,10 +82818,12 @@ async function getOfficialCodexSyncStatus(currentProfileKey) {
82894
82818
  const lastSwitchAt = stored.lastProfileSwitchAt;
82895
82819
  const lastLaunchAt = stored.lastVerifiedLaunchAt;
82896
82820
  const lastLaunchProfileKey = stored.lastVerifiedLaunchProfileKey;
82897
- const stale = Boolean(currentProfileKey && lastSwitchAt && (!lastLaunchAt || lastLaunchAt < lastSwitchAt || lastLaunchProfileKey !== currentProfileKey));
82821
+ const lastObservedPid = stored.lastObservedPid;
82822
+ const observedSynced = Boolean(currentProfileKey && lastLaunchProfileKey === currentProfileKey && lastObservedPid && mainPids.includes(lastObservedPid));
82823
+ const stale = Boolean(!observedSynced && currentProfileKey && lastSwitchAt && (!lastLaunchAt || lastLaunchAt < lastSwitchAt || lastLaunchProfileKey !== currentProfileKey));
82898
82824
  return {
82899
82825
  ...base,
82900
- state: stale ? "stale" : currentProfileKey && lastLaunchProfileKey === currentProfileKey ? "synced" : "unknown",
82826
+ state: observedSynced ? "synced" : stale ? "stale" : "unknown",
82901
82827
  appPath: candidate.appPath,
82902
82828
  bundleId: candidate.bundleId,
82903
82829
  runningPids: mainPids
@@ -83306,17 +83232,6 @@ async function restartOfficialCodexProfileInstance(options) {
83306
83232
  status: launched.status === "started" ? "restarted" : launched.status
83307
83233
  };
83308
83234
  }
83309
- function restartOfficialCodexApp(options = {}) {
83310
- if (restartPromise) return restartPromise;
83311
- restartPromise = restartOfficialCodexAppOnce({
83312
- source: options.source ?? "manual",
83313
- currentProfileKey: options.currentProfileKey ?? null,
83314
- launchIfNotRunning: options.launchIfNotRunning ?? (options.source ?? "manual") === "manual"
83315
- }).finally(() => {
83316
- restartPromise = null;
83317
- });
83318
- return restartPromise;
83319
- }
83320
83235
  //#endregion
83321
83236
  //#region src/wsServer.ts
83322
83237
  /**
@@ -84170,6 +84085,57 @@ const createServer = fn(function* () {
84170
84085
  };
84171
84086
  const lowRemainingNotifications = /* @__PURE__ */ new Map();
84172
84087
  const finitePercent = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : null;
84088
+ const AUTO_ROLL_SNAPSHOT_MAX_AGE_MS = 3 * 6e4;
84089
+ const AUTO_ROLL_WATCHDOG_INTERVAL_MS = 6e4;
84090
+ const AUTO_ROLL_WATCHDOG_ACTIVITY_TTL_MS = 10 * 6e4;
84091
+ const parseRateLimitTimestamp = (value) => {
84092
+ if (!value) return null;
84093
+ const parsed = Date.parse(value);
84094
+ return Number.isFinite(parsed) ? parsed : null;
84095
+ };
84096
+ const createAutoRollDecisionId = () => `ar-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
84097
+ const buildSnapshotDiagnostic = (cached, profileSnapshot, fallbackSource = "none") => {
84098
+ const candidates = [{
84099
+ snapshot: cached ?? null,
84100
+ source: "cache",
84101
+ updatedAt: parseRateLimitTimestamp(cached?.lastUpdated)
84102
+ }, {
84103
+ snapshot: profileSnapshot ?? null,
84104
+ source: "profile",
84105
+ updatedAt: parseRateLimitTimestamp(profileSnapshot?.lastUpdated)
84106
+ }].filter((entry) => Boolean(entry.snapshot));
84107
+ candidates.sort((a, b) => (b.updatedAt ?? -Infinity) - (a.updatedAt ?? -Infinity));
84108
+ const selected = candidates[0] ?? null;
84109
+ const snapshot = selected?.snapshot ?? null;
84110
+ const source = selected?.source ?? fallbackSource;
84111
+ const updatedAt = parseRateLimitTimestamp(snapshot?.lastUpdated);
84112
+ return {
84113
+ snapshot,
84114
+ source,
84115
+ ageMs: updatedAt === null ? null : Math.max(0, Date.now() - updatedAt)
84116
+ };
84117
+ };
84118
+ const freshAutoRollSnapshot = (snapshot) => {
84119
+ if (!snapshot) return null;
84120
+ const updatedAt = parseRateLimitTimestamp(snapshot.lastUpdated);
84121
+ if (updatedAt === null || Date.now() - updatedAt >= AUTO_ROLL_SNAPSHOT_MAX_AGE_MS) return null;
84122
+ const now = Date.now();
84123
+ const keepWindow = (window) => {
84124
+ if (!window || finitePercent(window.usedPercent) === null) return window;
84125
+ const resetsAt = parseRateLimitTimestamp(window.resetsAt);
84126
+ if (resetsAt !== null && resetsAt <= now) return;
84127
+ return window;
84128
+ };
84129
+ return {
84130
+ ...snapshot,
84131
+ primary: keepWindow(snapshot.primary),
84132
+ secondary: keepWindow(snapshot.secondary)
84133
+ };
84134
+ };
84135
+ const isSnapshotStaleForAutoRollWatchdog = (snapshot) => {
84136
+ const updatedAt = parseRateLimitTimestamp(snapshot?.lastUpdated);
84137
+ return updatedAt === null || Date.now() - updatedAt >= AUTO_ROLL_SNAPSHOT_MAX_AGE_MS;
84138
+ };
84173
84139
  const formatLowRemainingPercent = (value) => {
84174
84140
  const rounded = Math.round(value * 10) / 10;
84175
84141
  return (Number.isInteger(rounded) ? rounded.toFixed(0) : rounded.toFixed(1)).replace(/\.0$/, "");
@@ -84221,6 +84187,17 @@ const createServer = fn(function* () {
84221
84187
  remainingPercent,
84222
84188
  threshold: settings.lowRemainingNotificationThreshold
84223
84189
  });
84190
+ const snapshotUpdatedAt = parseRateLimitTimestamp(payload.snapshot.lastUpdated);
84191
+ recordAutoRollActivitySafe({
84192
+ kind: "low-remaining-alert",
84193
+ status: "sent",
84194
+ profileName: payload.profileName,
84195
+ remainingPercent,
84196
+ threshold: settings.lowRemainingNotificationThreshold,
84197
+ snapshotAgeMs: snapshotUpdatedAt === null ? null : Math.max(0, Date.now() - snapshotUpdatedAt),
84198
+ snapshotSource: "event",
84199
+ profileKey: payload.accountId
84200
+ });
84224
84201
  }
84225
84202
  };
84226
84203
  const publishProfileSwitched = (payload) => {
@@ -84231,6 +84208,7 @@ const createServer = fn(function* () {
84231
84208
  let backgroundAutoRollInFlight = false;
84232
84209
  let lastAutoRollAttempt = null;
84233
84210
  const autoRollRearmBlockedProfiles = /* @__PURE__ */ new Set();
84211
+ const lastAutoRollWatchdogActivityAt = /* @__PURE__ */ new Map();
84234
84212
  const rememberAutoRollAttempt = (source, target) => {
84235
84213
  if (!source || !target) return;
84236
84214
  lastAutoRollAttempt = {
@@ -84270,6 +84248,19 @@ const createServer = fn(function* () {
84270
84248
  const profile = (await profileManager.listProfiles()).find((entry) => entry.name === profileName);
84271
84249
  return profile ? resolveProfileIdentityKeyFromProfile(profile) : null;
84272
84250
  };
84251
+ const hashIdentityKey = (key) => key ? createHash("sha256").update(key).digest("hex").slice(0, 12) : null;
84252
+ const observedOfficialCodexProfileMatches = (instance, targetProfileName, targetProfileKey) => {
84253
+ if (targetProfileKey && instance.profileKey) return instance.profileKey === targetProfileKey;
84254
+ if (instance.profileKey) return false;
84255
+ return Boolean(targetProfileName && instance.profileName === targetProfileName);
84256
+ };
84257
+ const recordAutoRollActivitySafe = async (input) => {
84258
+ try {
84259
+ await recordOfficialCodexActivity(input);
84260
+ } catch (error) {
84261
+ logger.warn("Failed to record auto-roll activity", { error });
84262
+ }
84263
+ };
84273
84264
  const prepareOfficialCodexProfileLaunch = async (profileName) => {
84274
84265
  const profile = (await profileManager.listProfiles()).find((entry) => entry.name === profileName);
84275
84266
  if (!profile) throw new Error(`Profile '${profileName}' was not found.`);
@@ -84332,17 +84323,68 @@ const createServer = fn(function* () {
84332
84323
  }
84333
84324
  return null;
84334
84325
  };
84326
+ const mapOfficialCodexAppAccountToProfile = async () => {
84327
+ const scopePath = nodePath.join(nodeOs.homedir(), "Library", "Application Support", "Codex", "sentry", "scope_v3.json");
84328
+ let parsed;
84329
+ try {
84330
+ parsed = JSON.parse(await promises.readFile(scopePath, "utf8"));
84331
+ } catch {
84332
+ return null;
84333
+ }
84334
+ const scope = parsed && typeof parsed === "object" ? parsed.scope : null;
84335
+ const user = scope && typeof scope === "object" ? scope.user : null;
84336
+ if (!user || typeof user !== "object") return null;
84337
+ const userRecord = user;
84338
+ const ids = new Set([userRecord.account_id, userRecord.id].filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean));
84339
+ if (ids.size === 0) return null;
84340
+ const profiles = await profileManager.listProfiles();
84341
+ for (const profile of profiles) if ([
84342
+ profile.accountId,
84343
+ profile.metadata?.chatgptAccountUserId,
84344
+ profile.metadata?.chatgptUserId,
84345
+ profile.metadata?.userId,
84346
+ resolveProfileIdentityKeyFromProfile(profile)
84347
+ ].filter((value) => typeof value === "string" && value.length > 0).some((value) => ids.has(value))) return {
84348
+ name: profile.name,
84349
+ key: resolveProfileIdentityKeyFromProfile(profile)
84350
+ };
84351
+ return null;
84352
+ };
84335
84353
  const enrichOfficialCodexInstances = async (payload) => {
84336
84354
  if (payload.unmanaged.length === 0) return payload;
84337
- const defaultProfile = await mapDefaultCodexAuthToProfile();
84338
- if (!defaultProfile) return payload;
84355
+ const hasManagedRunning = payload.instances.some((instance) => instance.running);
84356
+ const [defaultProfile, appAccountProfile, appState] = await Promise.all([
84357
+ mapDefaultCodexAuthToProfile(),
84358
+ !hasManagedRunning && payload.unmanaged.length === 1 && payload.unmanaged[0]?.appServerPid ? mapOfficialCodexAppAccountToProfile() : Promise.resolve(null),
84359
+ getAppState()
84360
+ ]);
84361
+ const lastProfileSwitchAt = appState.officialCodex.lastProfileSwitchAt;
84362
+ const lastObservedPid = appState.officialCodex.lastObservedPid;
84363
+ const lastVerifiedLaunchProfileKey = appState.officialCodex.lastVerifiedLaunchProfileKey;
84364
+ const isFreshDefaultAuthObservation = (instance) => {
84365
+ if (!defaultProfile) return false;
84366
+ if (lastObservedPid && instance.pid === lastObservedPid && lastVerifiedLaunchProfileKey === defaultProfile.key) return true;
84367
+ if (!lastProfileSwitchAt) return true;
84368
+ const startedAt = parseRateLimitTimestamp(instance.startedAt);
84369
+ return startedAt !== null && startedAt >= lastProfileSwitchAt - 1e3;
84370
+ };
84339
84371
  return {
84340
84372
  ...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"
84373
+ unmanaged: payload.unmanaged.map((instance) => {
84374
+ if (instance.profileName || !instance.appServerPid) return instance;
84375
+ if (appAccountProfile) return {
84376
+ ...instance,
84377
+ profileName: appAccountProfile.name,
84378
+ profileKey: appAccountProfile.key,
84379
+ profileMatchSource: "app-account"
84380
+ };
84381
+ if (defaultProfile && isFreshDefaultAuthObservation(instance)) return {
84382
+ ...instance,
84383
+ profileName: defaultProfile.name,
84384
+ profileKey: defaultProfile.key,
84385
+ profileMatchSource: "default-auth"
84386
+ };
84387
+ return instance;
84346
84388
  })
84347
84389
  };
84348
84390
  };
@@ -84356,6 +84398,62 @@ const createServer = fn(function* () {
84356
84398
  bundleId: instances.bundleId
84357
84399
  } : null;
84358
84400
  };
84401
+ const resolveOfficialCodexStatus = async (currentProfile) => {
84402
+ const currentProfileKey = currentProfile ? resolveProfileIdentityKeyFromProfile(currentProfile) : null;
84403
+ const base = await getOfficialCodexSyncStatus(currentProfileKey);
84404
+ if (base.state === "unsupported" || base.state === "not-found" || base.state === "not-running") return base;
84405
+ try {
84406
+ const instances = await enrichOfficialCodexInstances(await getOfficialCodexProfileInstances());
84407
+ const managed = instances.instances.filter((instance) => instance.running).map((instance) => ({
84408
+ profileName: instance.profileName,
84409
+ profileKey: instance.profileKey,
84410
+ pid: instance.pid,
84411
+ source: "managed"
84412
+ }));
84413
+ const unmanaged = instances.unmanaged.map((instance) => ({
84414
+ profileName: instance.profileName ?? null,
84415
+ profileKey: instance.profileKey ?? null,
84416
+ pid: instance.pid,
84417
+ source: instance.profileMatchSource ?? null
84418
+ }));
84419
+ const observedCandidates = unmanaged;
84420
+ const observed = observedCandidates.find((instance) => observedOfficialCodexProfileMatches(instance, currentProfile?.name ?? null, currentProfileKey)) ?? observedCandidates.find((instance) => instance.profileName || instance.profileKey) ?? null;
84421
+ if (!observed) {
84422
+ if (managed.length > 0 && unmanaged.length === 0) return {
84423
+ ...base,
84424
+ state: "not-running",
84425
+ runningPids: [],
84426
+ observedProfileName: null,
84427
+ observedProfileKeyHash: null,
84428
+ observedProfileMatchSource: null
84429
+ };
84430
+ if (base.state === "stale") return {
84431
+ ...base,
84432
+ observedProfileName: null,
84433
+ observedProfileKeyHash: null,
84434
+ observedProfileMatchSource: null
84435
+ };
84436
+ return base;
84437
+ }
84438
+ const matched = Boolean(observedOfficialCodexProfileMatches(observed, currentProfile?.name ?? null, currentProfileKey));
84439
+ if (matched && currentProfileKey && observed.pid) try {
84440
+ const stored = (await getAppState()).officialCodex;
84441
+ if (stored.lastVerifiedLaunchProfileKey !== currentProfileKey || stored.lastObservedPid !== observed.pid) await recordOfficialCodexProfileObservation(currentProfileKey, observed.pid);
84442
+ } catch (error) {
84443
+ logger.warn("Failed to record observed Official Codex profile", { error });
84444
+ }
84445
+ return {
84446
+ ...base,
84447
+ state: currentProfile ? matched ? "synced" : "stale" : base.state,
84448
+ observedProfileName: observed.profileName,
84449
+ observedProfileKeyHash: hashIdentityKey(observed.profileKey),
84450
+ observedProfileMatchSource: observed.source
84451
+ };
84452
+ } catch (error) {
84453
+ logger.warn("Failed to resolve observed Official Codex profile", { error });
84454
+ return base;
84455
+ }
84456
+ };
84359
84457
  const resolveAutoRollRearmActionAfterManualSwitch = async (profileName) => {
84360
84458
  const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
84361
84459
  if (!settings.enabled) return "none";
@@ -84366,30 +84464,414 @@ const createServer = fn(function* () {
84366
84464
  if (!summary.hasUsage) return "unblock";
84367
84465
  return summary.remainingPercent <= settings.switchRemainingThreshold ? "block" : "unblock";
84368
84466
  };
84369
- const maybeRestartOfficialCodexAfterAutoRoll = async (targetProfileName, targetProfileKey) => {
84467
+ const OFFICIAL_CODEX_RESTART_DEBT_MAX_ATTEMPTS = 5;
84468
+ const OFFICIAL_CODEX_RESTART_RETRY_DELAY_MS = 61e3;
84469
+ const OFFICIAL_CODEX_POST_RESTART_VERIFY_TIMEOUT_MS = 15e3;
84470
+ const OFFICIAL_CODEX_POST_RESTART_VERIFY_INTERVAL_MS = 750;
84471
+ let officialCodexRestartRetryTimer = null;
84472
+ let officialCodexRestartDebt = null;
84473
+ const persistOfficialCodexRestartDebt = async (debt) => {
84474
+ await patchAppState({ officialCodex: { pendingRestartDebt: debt } });
84475
+ };
84476
+ const clearOfficialCodexRestartDebtLocal = () => {
84477
+ officialCodexRestartDebt = null;
84478
+ if (officialCodexRestartRetryTimer) {
84479
+ clearTimeout(officialCodexRestartRetryTimer);
84480
+ officialCodexRestartRetryTimer = null;
84481
+ }
84482
+ };
84483
+ const clearOfficialCodexRestartDebt = async () => {
84484
+ clearOfficialCodexRestartDebtLocal();
84485
+ await persistOfficialCodexRestartDebt(null);
84486
+ };
84487
+ const rememberOfficialCodexRestartDebt = async (targetProfileName, targetProfileKey, reason, decisionId = null, sourceProfileName, sourceProfileKey) => {
84488
+ const previous = officialCodexRestartDebt?.targetProfileName === targetProfileName ? officialCodexRestartDebt : null;
84489
+ officialCodexRestartDebt = {
84490
+ targetProfileName,
84491
+ targetProfileKey,
84492
+ sourceProfileName: sourceProfileName !== void 0 ? sourceProfileName : previous?.sourceProfileName ?? null,
84493
+ sourceProfileKey: sourceProfileKey !== void 0 ? sourceProfileKey : previous?.sourceProfileKey ?? null,
84494
+ decisionId: decisionId ?? previous?.decisionId ?? null,
84495
+ attempts: previous?.attempts ?? 0,
84496
+ lastReason: reason
84497
+ };
84498
+ await persistOfficialCodexRestartDebt(officialCodexRestartDebt);
84499
+ };
84500
+ const getOfficialCodexRestartDebt = async () => {
84501
+ if (officialCodexRestartDebt) return officialCodexRestartDebt;
84502
+ const stored = (await getAppState()).officialCodex.pendingRestartDebt;
84503
+ if (!stored) return null;
84504
+ officialCodexRestartDebt = {
84505
+ ...stored,
84506
+ attempts: Math.max(0, stored.attempts)
84507
+ };
84508
+ return officialCodexRestartDebt;
84509
+ };
84510
+ const resolveObservedOfficialCodexProfileMatch = async (targetProfileName, targetProfileKey) => {
84511
+ const instances = await enrichOfficialCodexInstances(await getOfficialCodexProfileInstances());
84512
+ const observed = [...instances.instances.filter((instance) => instance.running).map((instance) => ({
84513
+ profileName: instance.profileName,
84514
+ profileKey: instance.profileKey,
84515
+ pid: instance.pid,
84516
+ appServerPid: instance.appServerPid,
84517
+ source: "managed"
84518
+ })), ...instances.unmanaged.map((instance) => ({
84519
+ profileName: instance.profileName ?? null,
84520
+ profileKey: instance.profileKey ?? null,
84521
+ pid: instance.pid,
84522
+ appServerPid: instance.appServerPid,
84523
+ source: instance.profileMatchSource ?? null
84524
+ }))].filter((instance) => instance.profileName || instance.profileKey || instance.pid);
84525
+ const matchedObserved = observed.find((instance) => observedOfficialCodexProfileMatches(instance, targetProfileName, targetProfileKey)) ?? null;
84526
+ const restartableObserved = observed.filter((instance) => instance.source !== "managed" && instance.pid);
84527
+ const singletonRestartableObserved = restartableObserved.length === 1 ? restartableObserved[0] : null;
84528
+ const firstObserved = matchedObserved ?? singletonRestartableObserved ?? observed[0] ?? null;
84529
+ return {
84530
+ matched: Boolean(matchedObserved),
84531
+ canStopObservedWindow: Boolean(firstObserved?.pid && firstObserved.source !== "managed" && (matchedObserved || singletonRestartableObserved === firstObserved)),
84532
+ ambiguousRestartableWindow: !matchedObserved && restartableObserved.length > 1,
84533
+ state: instances.state,
84534
+ observedProfileName: firstObserved?.profileName ?? null,
84535
+ observedProfileKey: firstObserved?.profileKey ?? null,
84536
+ observedProfileMatchSource: firstObserved?.source ?? null,
84537
+ pid: firstObserved?.pid ?? null,
84538
+ appServerPid: firstObserved?.appServerPid ?? null
84539
+ };
84540
+ };
84541
+ const recordTargetedOfficialCodexRestartResult = async (payload, targetProfileName, targetProfileKey, decisionId) => {
84542
+ const pid = payload.status === "started" || payload.status === "restarted" ? payload.pid : null;
84543
+ const reason = payload.status === "skipped" || payload.status === "failed" ? payload.reason : null;
84544
+ await recordOfficialCodexRestartResult({
84545
+ status: payload.status,
84546
+ reason,
84547
+ profileKey: targetProfileKey,
84548
+ pid
84549
+ });
84550
+ await recordAutoRollActivitySafe({
84551
+ kind: "official-codex-restart",
84552
+ status: payload.status,
84553
+ reason,
84554
+ decisionId,
84555
+ targetProfileName,
84556
+ profileKey: targetProfileKey,
84557
+ phase: payload.status === "failed" ? payload.phase ?? null : "profile",
84558
+ pid,
84559
+ restartRequested: true,
84560
+ restartResult: payload.status
84561
+ });
84562
+ };
84563
+ const profileActionToRestartPayload = (action, runningStatus, failedPhase) => {
84564
+ if (action.status === "failed") return {
84565
+ status: "failed",
84566
+ appPath: action.instance?.appPath ?? void 0,
84567
+ bundleId: action.instance?.bundleId ?? void 0,
84568
+ reason: action.reason ?? "official-codex-profile-action-failed",
84569
+ phase: failedPhase
84570
+ };
84571
+ if (action.status === "started" || action.status === "restarted") return {
84572
+ status: runningStatus,
84573
+ appPath: action.instance?.appPath ?? "",
84574
+ bundleId: action.instance?.bundleId ?? "",
84575
+ pid: action.instance?.pid ?? null
84576
+ };
84577
+ if (action.status === "already-running") return {
84578
+ status: "skipped",
84579
+ reason: "already-running"
84580
+ };
84581
+ if (action.status === "skipped" || action.status === "not-running") return {
84582
+ status: "skipped",
84583
+ reason: action.reason ?? "official-codex-app-not-running"
84584
+ };
84585
+ return {
84586
+ status: "skipped",
84587
+ reason: action.status
84588
+ };
84589
+ };
84590
+ const restartOfficialCodexProfileTarget = async (args) => {
84591
+ await recordAutoRollActivitySafe({
84592
+ kind: "official-codex-restart",
84593
+ status: "started",
84594
+ reason: null,
84595
+ decisionId: args.decisionId,
84596
+ targetProfileName: args.targetProfileName,
84597
+ profileKey: args.targetProfileKey,
84598
+ phase: "profile",
84599
+ restartRequested: true
84600
+ });
84601
+ const observed = await resolveObservedOfficialCodexProfileMatch(args.targetProfileName, args.targetProfileKey);
84602
+ const canRestartManagedWindow = Boolean(observed.pid !== null && observed.observedProfileMatchSource === "managed" && observed.matched);
84603
+ const canStopMatchedObservedWindow = Boolean(observed.pid !== null && observed.matched && observed.observedProfileMatchSource !== "managed" && observed.canStopObservedWindow);
84604
+ const observedMatchesSource = !observed.matched && Boolean(args.sourceProfileName || args.sourceProfileKey) && observedOfficialCodexProfileMatches({
84605
+ profileName: observed.observedProfileName,
84606
+ profileKey: observed.observedProfileKey
84607
+ }, args.sourceProfileName ?? null, args.sourceProfileKey ?? null);
84608
+ const canStopSourceManagedWindow = Boolean(observed.pid !== null && observedMatchesSource && observed.observedProfileMatchSource === "managed" && args.sourceProfileName);
84609
+ const canStopSourceObservedWindow = Boolean(observed.pid !== null && observedMatchesSource && observed.observedProfileMatchSource !== "managed" && observed.canStopObservedWindow);
84610
+ const hasRestartableWindow = canRestartManagedWindow || canStopMatchedObservedWindow || canStopSourceManagedWindow || canStopSourceObservedWindow;
84611
+ if (!hasRestartableWindow && observed.ambiguousRestartableWindow) {
84612
+ const skipped = {
84613
+ status: "skipped",
84614
+ reason: "ambiguous-official-codex-windows"
84615
+ };
84616
+ await recordTargetedOfficialCodexRestartResult(skipped, args.targetProfileName, args.targetProfileKey, args.decisionId);
84617
+ return skipped;
84618
+ }
84619
+ if (!hasRestartableWindow && !args.launchIfNotRunning) {
84620
+ const skipped = {
84621
+ status: "skipped",
84622
+ reason: observed.state === "unsupported" ? "unsupported-platform" : observed.state === "not-found" ? "official-codex-app-not-found" : observed.pid !== null ? "unverified-official-codex-window" : "official-codex-app-not-running"
84623
+ };
84624
+ await recordTargetedOfficialCodexRestartResult(skipped, args.targetProfileName, args.targetProfileKey, args.decisionId);
84625
+ return skipped;
84626
+ }
84627
+ const options = await prepareOfficialCodexProfileLaunch(args.targetProfileName);
84628
+ if (observed.observedProfileMatchSource === "managed" && observed.matched) {
84629
+ const payload = profileActionToRestartPayload(await restartOfficialCodexProfileInstance(options), "restarted", "profile");
84630
+ await recordTargetedOfficialCodexRestartResult(payload, args.targetProfileName, args.targetProfileKey, args.decisionId);
84631
+ return payload;
84632
+ }
84633
+ let stoppedRunningWindow = false;
84634
+ if (canStopMatchedObservedWindow && observed.pid !== null) {
84635
+ const stopped = await stopOfficialCodexObservedProfileInstance(observed.observedProfileName ?? args.targetProfileName, observed.pid, observed.appServerPid);
84636
+ if (stopped.status === "failed") {
84637
+ const failed = profileActionToRestartPayload(stopped, "restarted", "quit");
84638
+ await recordTargetedOfficialCodexRestartResult(failed, args.targetProfileName, args.targetProfileKey, args.decisionId);
84639
+ return failed;
84640
+ }
84641
+ stoppedRunningWindow = stopped.status === "stopped";
84642
+ } else if (canStopSourceManagedWindow && args.sourceProfileName) {
84643
+ const stopped = await stopOfficialCodexProfileInstance(args.sourceProfileName);
84644
+ if (stopped.status === "failed") {
84645
+ const failed = profileActionToRestartPayload(stopped, "restarted", "quit");
84646
+ await recordTargetedOfficialCodexRestartResult(failed, args.targetProfileName, args.targetProfileKey, args.decisionId);
84647
+ return failed;
84648
+ }
84649
+ stoppedRunningWindow = stopped.status === "stopped";
84650
+ } else if (canStopSourceObservedWindow && observed.pid !== null) {
84651
+ const stopped = await stopOfficialCodexObservedProfileInstance(observed.observedProfileName ?? args.sourceProfileName ?? args.targetProfileName, observed.pid, observed.appServerPid);
84652
+ if (stopped.status === "failed") {
84653
+ const failed = profileActionToRestartPayload(stopped, "restarted", "quit");
84654
+ await recordTargetedOfficialCodexRestartResult(failed, args.targetProfileName, args.targetProfileKey, args.decisionId);
84655
+ return failed;
84656
+ }
84657
+ stoppedRunningWindow = stopped.status === "stopped";
84658
+ }
84659
+ const payload = profileActionToRestartPayload(await launchOfficialCodexProfileInstance(options), stoppedRunningWindow ? "restarted" : "started", "launch");
84660
+ await recordTargetedOfficialCodexRestartResult(payload, args.targetProfileName, args.targetProfileKey, args.decisionId);
84661
+ return payload;
84662
+ };
84663
+ const waitForOfficialCodexProfileMatch = async (targetProfileName, targetProfileKey) => {
84664
+ const deadline = Date.now() + OFFICIAL_CODEX_POST_RESTART_VERIFY_TIMEOUT_MS;
84665
+ let latest = await resolveObservedOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
84666
+ while (!latest.matched && Date.now() < deadline) {
84667
+ await new Promise((resolve) => setTimeout(resolve, OFFICIAL_CODEX_POST_RESTART_VERIFY_INTERVAL_MS));
84668
+ latest = await resolveObservedOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
84669
+ }
84670
+ return latest;
84671
+ };
84672
+ const scheduleOfficialCodexRestartRetry = () => {
84673
+ const debt = officialCodexRestartDebt;
84674
+ if (!debt || officialCodexRestartRetryTimer) return;
84675
+ if (debt.attempts >= OFFICIAL_CODEX_RESTART_DEBT_MAX_ATTEMPTS) {
84676
+ clearOfficialCodexRestartDebt().catch((error) => {
84677
+ logger.warn("Failed to clear Official Codex restart debt", { error });
84678
+ });
84679
+ recordAutoRollActivitySafe({
84680
+ kind: "official-codex-restart",
84681
+ status: "skipped",
84682
+ reason: "restart-retry-exhausted",
84683
+ decisionId: debt.decisionId,
84684
+ targetProfileName: debt.targetProfileName,
84685
+ profileKey: debt.targetProfileKey,
84686
+ restartRequested: false,
84687
+ restartResult: "skipped"
84688
+ });
84689
+ return;
84690
+ }
84691
+ const delayMs = Math.min(5 * 6e4, OFFICIAL_CODEX_RESTART_RETRY_DELAY_MS * Math.max(1, debt.attempts + 1));
84692
+ officialCodexRestartRetryTimer = setTimeout(() => {
84693
+ officialCodexRestartRetryTimer = null;
84694
+ (async () => {
84695
+ const debt = officialCodexRestartDebt;
84696
+ if (!debt) return;
84697
+ if ((await resolveObservedOfficialCodexProfileMatch(debt.targetProfileName, debt.targetProfileKey)).matched) {
84698
+ await clearOfficialCodexRestartDebt();
84699
+ return;
84700
+ }
84701
+ if (officialCodexRestartDebt) {
84702
+ officialCodexRestartDebt = {
84703
+ ...officialCodexRestartDebt,
84704
+ attempts: officialCodexRestartDebt.attempts + 1
84705
+ };
84706
+ await persistOfficialCodexRestartDebt(officialCodexRestartDebt);
84707
+ await maybeRestartOfficialCodexAfterAutoRoll(officialCodexRestartDebt.targetProfileName, officialCodexRestartDebt.targetProfileKey, officialCodexRestartDebt.decisionId, officialCodexRestartDebt.sourceProfileName, officialCodexRestartDebt.sourceProfileKey);
84708
+ }
84709
+ })().catch((error) => {
84710
+ logger.warn("Official Codex restart retry failed", { error });
84711
+ });
84712
+ }, delayMs);
84713
+ officialCodexRestartRetryTimer.unref?.();
84714
+ };
84715
+ const reconcileOfficialCodexRestartDebt = async () => {
84716
+ const debt = await getOfficialCodexRestartDebt();
84717
+ if (!debt) return;
84718
+ const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
84719
+ if (!settings.restartOfficialCodexOnAutoRoll) {
84720
+ await clearOfficialCodexRestartDebt();
84721
+ return;
84722
+ }
84723
+ const targetProfile = (await profileManager.listProfiles()).find((profile) => profile.name === debt.targetProfileName) ?? null;
84724
+ if ((await resolveObservedOfficialCodexProfileMatch(debt.targetProfileName, debt.targetProfileKey)).matched) {
84725
+ await clearOfficialCodexRestartDebt();
84726
+ return;
84727
+ }
84728
+ const status = targetProfile ? await resolveOfficialCodexStatus(targetProfile) : await getOfficialCodexSyncStatus(debt.targetProfileKey);
84729
+ if (status.state === "synced") {
84730
+ await rememberOfficialCodexRestartDebt(debt.targetProfileName, debt.targetProfileKey, "launched-auth-unverified", debt.decisionId);
84731
+ scheduleOfficialCodexRestartRetry();
84732
+ return;
84733
+ }
84734
+ if (status.state === "not-running" && !settings.launchOfficialCodexWhenClosedOnAutoRoll) {
84735
+ await clearOfficialCodexRestartDebt();
84736
+ return;
84737
+ }
84738
+ if (status.state === "stale" || status.state === "unknown" || status.state === "not-running" && settings.launchOfficialCodexWhenClosedOnAutoRoll) scheduleOfficialCodexRestartRetry();
84739
+ };
84740
+ const maybeRestartOfficialCodexAfterAutoRoll = async (targetProfileName, targetProfileKey, decisionId, sourceProfileName = null, sourceProfileKey = null) => {
84370
84741
  try {
84371
84742
  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
84743
+ if (!settings.restartOfficialCodexOnAutoRoll) {
84744
+ await clearOfficialCodexRestartDebt();
84745
+ return;
84746
+ }
84747
+ const alreadyMatched = await resolveObservedOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
84748
+ if (alreadyMatched.matched) {
84749
+ await clearOfficialCodexRestartDebt();
84750
+ await recordAutoRollActivitySafe({
84751
+ kind: "official-codex-restart",
84752
+ status: "skipped",
84753
+ reason: "already-synced",
84754
+ decisionId,
84755
+ targetProfileName,
84756
+ profileKey: targetProfileKey,
84757
+ restartRequested: false,
84758
+ restartResult: "skipped",
84759
+ observedProfileName: alreadyMatched.observedProfileName,
84760
+ observedProfileKey: alreadyMatched.observedProfileKey,
84761
+ observedProfileMatchSource: alreadyMatched.observedProfileMatchSource
84762
+ });
84763
+ return;
84764
+ }
84765
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, null, decisionId, sourceProfileName, sourceProfileKey);
84766
+ const result = await restartOfficialCodexProfileTarget({
84767
+ targetProfileName,
84768
+ targetProfileKey,
84769
+ sourceProfileName,
84770
+ sourceProfileKey,
84771
+ launchIfNotRunning: settings.launchOfficialCodexWhenClosedOnAutoRoll,
84772
+ decisionId
84377
84773
  });
84378
84774
  trackAnalyticsEvent("official_codex_restart_after_auto_roll", {
84379
84775
  status: result.status,
84380
- reason: result.status === "skipped" || result.status === "failed" ? result.reason : void 0,
84381
- targetProfileName
84776
+ reason: result.status === "skipped" || result.status === "failed" ? result.reason : void 0
84382
84777
  });
84778
+ if (result.status === "skipped" && result.reason === "cooldown") {
84779
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, "cooldown", decisionId);
84780
+ await reconcileOfficialCodexRestartDebt();
84781
+ } else if (result.status === "skipped" && result.reason === "official-codex-app-not-running" && !settings.launchOfficialCodexWhenClosedOnAutoRoll) await clearOfficialCodexRestartDebt();
84782
+ else if (result.status === "skipped" || result.status === "failed") {
84783
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, result.reason, decisionId);
84784
+ await reconcileOfficialCodexRestartDebt();
84785
+ } else {
84786
+ const verified = await waitForOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
84787
+ if (verified.matched) {
84788
+ await recordAutoRollActivitySafe({
84789
+ kind: "official-codex-restart",
84790
+ status: "auth-verified",
84791
+ reason: null,
84792
+ decisionId,
84793
+ targetProfileName,
84794
+ profileKey: targetProfileKey,
84795
+ restartRequested: true,
84796
+ restartResult: result.status,
84797
+ observedProfileName: verified.observedProfileName,
84798
+ observedProfileKey: verified.observedProfileKey,
84799
+ observedProfileMatchSource: verified.observedProfileMatchSource
84800
+ });
84801
+ await clearOfficialCodexRestartDebt();
84802
+ } else {
84803
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, "launched-auth-unverified", decisionId);
84804
+ await recordAutoRollActivitySafe({
84805
+ kind: "official-codex-restart",
84806
+ status: "waiting",
84807
+ reason: "launched-auth-unverified",
84808
+ decisionId,
84809
+ targetProfileName,
84810
+ profileKey: targetProfileKey,
84811
+ restartRequested: true,
84812
+ restartResult: result.status,
84813
+ observedProfileName: verified.observedProfileName,
84814
+ observedProfileKey: verified.observedProfileKey,
84815
+ observedProfileMatchSource: verified.observedProfileMatchSource
84816
+ });
84817
+ await reconcileOfficialCodexRestartDebt();
84818
+ }
84819
+ }
84383
84820
  if (result.status === "failed") logger.warn("Official Codex restart after auto-roll failed", result);
84384
84821
  else if (result.status === "restarted" || result.status === "started") logger.info("Official Codex restarted after auto-roll", result);
84385
84822
  } catch (error) {
84823
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, error instanceof Error ? error.message : "restart-threw", decisionId);
84824
+ await recordAutoRollActivitySafe({
84825
+ kind: "official-codex-restart",
84826
+ status: "failed",
84827
+ reason: error instanceof Error ? error.message : "restart-threw",
84828
+ decisionId,
84829
+ targetProfileName,
84830
+ profileKey: targetProfileKey,
84831
+ restartRequested: true,
84832
+ restartResult: "failed"
84833
+ });
84834
+ scheduleOfficialCodexRestartRetry();
84386
84835
  logger.warn("Official Codex restart after auto-roll threw", { error });
84387
84836
  }
84388
84837
  };
84389
- const handleProfileSwitch = async (name, source = "app") => {
84838
+ const handleProfileSwitch = async (name, source = "app", decisionContext = {}) => {
84390
84839
  if (!name) throw new Error("Profile name is required");
84391
84840
  const previousProfile = source === "auto-roll" ? (await profileManager.getCurrentProfile()).name ?? null : null;
84841
+ const previousProfileKey = source === "auto-roll" ? await resolveProfileIdentityKeyByName(previousProfile) : null;
84392
84842
  await profileManager.switchToProfile(name);
84843
+ if (source === "auto-roll") {
84844
+ const verified = await profileManager.getCurrentProfile();
84845
+ if (verified.name !== name || !verified.trusted) {
84846
+ await recordAutoRollActivitySafe({
84847
+ kind: "auth-verified",
84848
+ status: "failed",
84849
+ reason: "active-auth-mismatch-after-switch",
84850
+ decisionId: decisionContext.decisionId,
84851
+ sourceProfileName: previousProfile,
84852
+ targetProfileName: name,
84853
+ remainingPercent: decisionContext.remainingPercent,
84854
+ threshold: decisionContext.threshold,
84855
+ snapshotAgeMs: decisionContext.snapshotAgeMs,
84856
+ snapshotSource: decisionContext.snapshotSource,
84857
+ switchVerified: false
84858
+ });
84859
+ throw new Error("Auto-roll profile switch did not verify active Codex auth.");
84860
+ }
84861
+ await recordAutoRollActivitySafe({
84862
+ kind: "auth-verified",
84863
+ status: "ok",
84864
+ reason: null,
84865
+ decisionId: decisionContext.decisionId,
84866
+ sourceProfileName: previousProfile,
84867
+ targetProfileName: name,
84868
+ remainingPercent: decisionContext.remainingPercent,
84869
+ threshold: decisionContext.threshold,
84870
+ snapshotAgeMs: decisionContext.snapshotAgeMs,
84871
+ snapshotSource: decisionContext.snapshotSource,
84872
+ switchVerified: true
84873
+ });
84874
+ }
84393
84875
  let targetProfileKey = null;
84394
84876
  try {
84395
84877
  targetProfileKey = await resolveProfileIdentityKeyByName(name);
@@ -84400,8 +84882,24 @@ const createServer = fn(function* () {
84400
84882
  profileName: name
84401
84883
  });
84402
84884
  }
84403
- if (source === "auto-roll") rememberAutoRollAttempt(previousProfile, name);
84404
- else try {
84885
+ if (source === "auto-roll") {
84886
+ rememberAutoRollAttempt(previousProfile, name);
84887
+ await recordAutoRollActivitySafe({
84888
+ kind: "profile-switch",
84889
+ status: "completed",
84890
+ reason: null,
84891
+ decisionId: decisionContext.decisionId,
84892
+ sourceProfileName: previousProfile,
84893
+ targetProfileName: name,
84894
+ remainingPercent: decisionContext.remainingPercent,
84895
+ threshold: decisionContext.threshold,
84896
+ snapshotAgeMs: decisionContext.snapshotAgeMs,
84897
+ snapshotSource: decisionContext.snapshotSource,
84898
+ profileKey: targetProfileKey,
84899
+ switchVerified: true,
84900
+ restartRequested: decisionContext.restartRequested ?? null
84901
+ });
84902
+ } else try {
84405
84903
  const rearmAction = await resolveAutoRollRearmActionAfterManualSwitch(name);
84406
84904
  if (rearmAction === "block") blockAutoRollUntilRearm(name);
84407
84905
  else if (rearmAction === "unblock") unblockAutoRollRearm(name);
@@ -84413,10 +84911,13 @@ const createServer = fn(function* () {
84413
84911
  }
84414
84912
  await publishProfileSwitched({ name });
84415
84913
  trackAnalyticsEvent("profile_switched", { source });
84416
- if (source === "auto-roll") await maybeRestartOfficialCodexAfterAutoRoll(name, targetProfileKey);
84914
+ if (source === "auto-roll") await maybeRestartOfficialCodexAfterAutoRoll(name, targetProfileKey, decisionContext.decisionId ?? null, previousProfile, previousProfileKey);
84915
+ else maybeRunBackgroundAutoRoll();
84417
84916
  };
84418
84917
  const maybeRunBackgroundAutoRoll = async () => {
84419
84918
  if (backgroundAutoRollInFlight) return;
84919
+ const decisionId = createAutoRollDecisionId();
84920
+ let lastDecisionContext = { decisionId };
84420
84921
  backgroundAutoRollInFlight = true;
84421
84922
  try {
84422
84923
  const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
@@ -84428,9 +84929,12 @@ const createServer = fn(function* () {
84428
84929
  const profiles = await profileManager.listProfiles();
84429
84930
  const snapshots = await loadCachedRateLimitSnapshots(profiles.map((profile) => resolveProfileIdentityKeyFromProfile(profile)));
84430
84931
  const usageSummaries = /* @__PURE__ */ new Map();
84932
+ const snapshotDiagnostics = /* @__PURE__ */ new Map();
84431
84933
  const profilesWithSnapshots = profiles.map((profile) => {
84432
84934
  const accountId = resolveProfileIdentityKeyFromProfile(profile);
84433
- const snapshot = snapshots.get(accountId) ?? profile.rateLimit ?? null;
84935
+ const diagnostic = buildSnapshotDiagnostic(snapshots.get(accountId) ?? null, profile.rateLimit ?? null);
84936
+ snapshotDiagnostics.set(profile.name, diagnostic);
84937
+ const snapshot = freshAutoRollSnapshot(diagnostic.snapshot);
84434
84938
  usageSummaries.set(profile.name, summarizeRateLimitSnapshot(snapshot));
84435
84939
  return {
84436
84940
  ...profile,
@@ -84441,19 +84945,125 @@ const createServer = fn(function* () {
84441
84945
  const currentProfile = profilesWithSnapshots.find((profile) => profile.name === currentProfileName);
84442
84946
  if (!currentProfile) return;
84443
84947
  const currentSummary = usageSummaries.get(currentProfileName) ?? summarizeRateLimitSnapshot(currentProfile.rateLimit ?? null);
84948
+ const currentSnapshotDiagnostic = snapshotDiagnostics.get(currentProfileName) ?? buildSnapshotDiagnostic(null, currentProfile.rateLimit ?? null);
84949
+ const decisionContext = {
84950
+ decisionId,
84951
+ remainingPercent: currentSummary.hasUsage ? currentSummary.remainingPercent : null,
84952
+ threshold: settings.switchRemainingThreshold,
84953
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
84954
+ snapshotSource: currentSnapshotDiagnostic.source,
84955
+ restartRequested: settings.restartOfficialCodexOnAutoRoll
84956
+ };
84957
+ lastDecisionContext = decisionContext;
84444
84958
  if (!currentSummary.hasUsage) return;
84445
- if (isAutoRollBlockedUntilRearm(currentProfileName, currentSummary, settings.rearmRemainingThreshold)) return;
84959
+ if (isAutoRollBlockedUntilRearm(currentProfileName, currentSummary, settings.rearmRemainingThreshold)) {
84960
+ await recordAutoRollActivitySafe({
84961
+ kind: "auto-roll-eval",
84962
+ status: "blocked",
84963
+ reason: "waiting-for-rearm",
84964
+ decisionId,
84965
+ profileName: currentProfileName,
84966
+ remainingPercent: currentSummary.remainingPercent,
84967
+ threshold: settings.rearmRemainingThreshold,
84968
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
84969
+ snapshotSource: currentSnapshotDiagnostic.source
84970
+ });
84971
+ return;
84972
+ }
84446
84973
  if (currentSummary.remainingPercent > settings.switchRemainingThreshold) return;
84974
+ await recordAutoRollActivitySafe({
84975
+ kind: "auto-roll-eval",
84976
+ status: "threshold-hit",
84977
+ reason: null,
84978
+ decisionId,
84979
+ profileName: currentProfileName,
84980
+ remainingPercent: currentSummary.remainingPercent,
84981
+ threshold: settings.switchRemainingThreshold,
84982
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
84983
+ snapshotSource: currentSnapshotDiagnostic.source
84984
+ });
84447
84985
  const candidate = computeAutoRollCandidate(profilesWithSnapshots, usageSummaries, currentProfileName, currentSummary, settings.switchRemainingThreshold, settings.priorityOrder, autoRollRearmBlockedProfiles);
84448
- if (!candidate || isRecentAutoRollAttempt(currentProfileName, candidate.profile.name)) return;
84449
- await handleProfileSwitch(candidate.profile.name, "auto-roll");
84986
+ if (!candidate) {
84987
+ await recordAutoRollActivitySafe({
84988
+ kind: "auto-roll-eval",
84989
+ status: "skipped",
84990
+ reason: "no-candidate",
84991
+ decisionId,
84992
+ profileName: currentProfileName,
84993
+ remainingPercent: currentSummary.remainingPercent,
84994
+ threshold: settings.switchRemainingThreshold,
84995
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
84996
+ snapshotSource: currentSnapshotDiagnostic.source
84997
+ });
84998
+ return;
84999
+ }
85000
+ if (isRecentAutoRollAttempt(currentProfileName, candidate.profile.name)) {
85001
+ await recordAutoRollActivitySafe({
85002
+ kind: "auto-roll-eval",
85003
+ status: "skipped",
85004
+ reason: "recent-duplicate-attempt",
85005
+ decisionId,
85006
+ sourceProfileName: currentProfileName,
85007
+ targetProfileName: candidate.profile.name,
85008
+ remainingPercent: currentSummary.remainingPercent,
85009
+ threshold: settings.switchRemainingThreshold,
85010
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
85011
+ snapshotSource: currentSnapshotDiagnostic.source
85012
+ });
85013
+ return;
85014
+ }
85015
+ await handleProfileSwitch(candidate.profile.name, "auto-roll", decisionContext);
84450
85016
  blockAutoRollUntilRearm(currentProfileName);
84451
85017
  } catch (error) {
85018
+ await recordAutoRollActivitySafe({
85019
+ kind: "auto-roll-eval",
85020
+ status: "failed",
85021
+ reason: error instanceof Error ? error.message : "unknown-error",
85022
+ decisionId,
85023
+ remainingPercent: lastDecisionContext.remainingPercent,
85024
+ threshold: lastDecisionContext.threshold,
85025
+ snapshotAgeMs: lastDecisionContext.snapshotAgeMs,
85026
+ snapshotSource: lastDecisionContext.snapshotSource
85027
+ });
84452
85028
  logger.warn("Background auto-roll failed", { error });
84453
85029
  } finally {
84454
85030
  backgroundAutoRollInFlight = false;
84455
85031
  }
84456
85032
  };
85033
+ const runAutoRollWatchdog = async () => {
85034
+ const decisionId = createAutoRollDecisionId();
85035
+ const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
85036
+ if (!settings.enabled) return;
85037
+ if (!(await licenseService.getCachedStatus()).isPro) return;
85038
+ const current = await profileManager.getCurrentProfile();
85039
+ const currentProfileName = typeof current.name === "string" && current.name.length > 0 ? current.name : null;
85040
+ if (!currentProfileName) return;
85041
+ const currentProfile = (await profileManager.listProfiles()).find((profile) => profile.name === currentProfileName) ?? null;
85042
+ if (!currentProfile) return;
85043
+ const accountId = resolveProfileIdentityKeyFromProfile(currentProfile);
85044
+ const diagnostic = buildSnapshotDiagnostic((await loadCachedRateLimitSnapshots([accountId])).get(accountId) ?? null, currentProfile.rateLimit ?? null);
85045
+ const snapshot = diagnostic.snapshot;
85046
+ if (isSnapshotStaleForAutoRollWatchdog(snapshot)) {
85047
+ const queued = await enqueueAccountsRefreshNow([currentProfileName]);
85048
+ const lastRecordedAt = lastAutoRollWatchdogActivityAt.get(accountId) ?? 0;
85049
+ if (queued.enqueuedAccountIds.includes(accountId) && Date.now() - lastRecordedAt >= AUTO_ROLL_WATCHDOG_ACTIVITY_TTL_MS) {
85050
+ lastAutoRollWatchdogActivityAt.set(accountId, Date.now());
85051
+ await recordAutoRollActivitySafe({
85052
+ kind: "auto-roll-eval",
85053
+ status: "refresh-queued",
85054
+ reason: "watchdog-stale-snapshot",
85055
+ decisionId,
85056
+ profileName: currentProfileName,
85057
+ threshold: settings.switchRemainingThreshold,
85058
+ snapshotAgeMs: diagnostic.ageMs,
85059
+ snapshotSource: diagnostic.source,
85060
+ profileKey: accountId
85061
+ });
85062
+ }
85063
+ return;
85064
+ }
85065
+ await maybeRunBackgroundAutoRoll();
85066
+ };
84457
85067
  const handleDesktopMessage = (message) => {
84458
85068
  if (!isDesktopChildCommand(message)) return;
84459
85069
  if (message.type === "t3-server:switch-profile") {
@@ -84484,6 +85094,7 @@ const createServer = fn(function* () {
84484
85094
  logger.warn("Low remaining notification failed", { error });
84485
85095
  });
84486
85096
  maybeRunBackgroundAutoRoll();
85097
+ reconcileOfficialCodexRestartDebt();
84487
85098
  };
84488
85099
  const handleRateLimitStateChanged = (payload) => {
84489
85100
  refreshTray();
@@ -84491,9 +85102,16 @@ const createServer = fn(function* () {
84491
85102
  };
84492
85103
  rateLimitEvents.on("snapshot-updated", handleRateLimitSnapshotUpdated);
84493
85104
  rateLimitEvents.on("refresh-state-changed", handleRateLimitStateChanged);
85105
+ const autoRollWatchdogInterval = setInterval(() => {
85106
+ runAutoRollWatchdog().catch((error) => {
85107
+ logger.warn("Auto-roll watchdog failed", { error });
85108
+ });
85109
+ }, AUTO_ROLL_WATCHDOG_INTERVAL_MS);
85110
+ autoRollWatchdogInterval.unref?.();
84494
85111
  yield* addFinalizer(() => sync(() => {
84495
85112
  rateLimitEvents.off("snapshot-updated", handleRateLimitSnapshotUpdated);
84496
85113
  rateLimitEvents.off("refresh-state-changed", handleRateLimitStateChanged);
85114
+ clearInterval(autoRollWatchdogInterval);
84497
85115
  }));
84498
85116
  const unsubscribeTerminalEvents = yield* terminalManager.subscribe((event) => void runPromise(pushBus.publishAll(WS_CHANNELS.terminalEvent, event)));
84499
85117
  yield* addFinalizer(() => sync(() => unsubscribeTerminalEvents()));
@@ -84513,6 +85131,8 @@ const createServer = fn(function* () {
84513
85131
  });
84514
85132
  });
84515
85133
  yield* readiness.markHttpListening;
85134
+ maybeRunBackgroundAutoRoll();
85135
+ reconcileOfficialCodexRestartDebt();
84516
85136
  readResolvedTelegramRuntimeSettings().then((settings) => queueTelegramRuntimeSettingsApply(settings)).catch((error) => {
84517
85137
  logger.warn("Failed to apply Telegram runtime settings during startup", { error: error instanceof Error ? error.message : String(error) });
84518
85138
  });
@@ -84891,10 +85511,15 @@ const createServer = fn(function* () {
84891
85511
  return yield* promise(() => telegramBridge.testToken(body.token));
84892
85512
  }
84893
85513
  case WS_METHODS.profilesFetch: {
84894
- const data = yield* promise(() => loadProfilesViewData({ autoImportActiveAuth: true }));
85514
+ const body = request.body && typeof request.body === "object" ? stripRequestTag(request.body) : {};
85515
+ const data = yield* promise(() => loadProfilesViewData({
85516
+ autoImportActiveAuth: true,
85517
+ backgroundRefresh: body.backgroundRefresh !== false
85518
+ }));
84895
85519
  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));
85520
+ const officialCodexStatus = yield* promise(() => resolveOfficialCodexStatus(currentProfile));
85521
+ maybeRunBackgroundAutoRoll();
85522
+ reconcileOfficialCodexRestartDebt();
84898
85523
  refreshTray();
84899
85524
  return {
84900
85525
  ...data,
@@ -85171,6 +85796,7 @@ const createServer = fn(function* () {
85171
85796
  const normalized = normalizeAutoRollSettings(stripRequestTag(request.body).settings);
85172
85797
  yield* promise(() => persistAutoRollSettings(normalized));
85173
85798
  maybeRunBackgroundAutoRoll();
85799
+ reconcileOfficialCodexRestartDebt();
85174
85800
  trackAnalyticsEvent("auto_roll_settings_saved", {
85175
85801
  enabled: normalized.enabled,
85176
85802
  rearmRemainingThreshold: normalized.rearmRemainingThreshold,
@@ -85184,9 +85810,19 @@ const createServer = fn(function* () {
85184
85810
  return normalized;
85185
85811
  }
85186
85812
  case WS_METHODS.officialCodexRestart: return yield* promise(async () => {
85187
- return restartOfficialCodexApp({
85188
- source: "manual",
85189
- currentProfileKey: await resolveProfileIdentityKeyByName((await profileManager.getCurrentProfile()).name ?? null)
85813
+ const current = await profileManager.getCurrentProfile();
85814
+ const currentProfileKey = await resolveProfileIdentityKeyByName(current.name ?? null);
85815
+ if (!current.name) return {
85816
+ status: "skipped",
85817
+ reason: "profile-not-selected"
85818
+ };
85819
+ return restartOfficialCodexProfileTarget({
85820
+ targetProfileName: current.name,
85821
+ targetProfileKey: currentProfileKey,
85822
+ sourceProfileName: null,
85823
+ sourceProfileKey: null,
85824
+ launchIfNotRunning: true,
85825
+ decisionId: null
85190
85826
  });
85191
85827
  });
85192
85828
  case WS_METHODS.officialCodexInstances: return yield* promise(async () => enrichOfficialCodexInstances(await getOfficialCodexProfileInstances()));