codexuse-cli 3.9.7 → 3.9.9

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.
@@ -48769,8 +48769,10 @@ const WS_METHODS = {
48769
48769
  officialCodexFocusProfile: "officialCodex.focusProfile",
48770
48770
  officialCodexStopProfile: "officialCodex.stopProfile",
48771
48771
  officialCodexRestartProfile: "officialCodex.restartProfile",
48772
+ officialCodexUseProfile: "officialCodex.useProfile",
48772
48773
  officialCodexLaunchProfiles: "officialCodex.launchProfiles",
48773
48774
  officialCodexStopProfiles: "officialCodex.stopProfiles",
48775
+ officialCodexActivateProfiles: "officialCodex.activateProfiles",
48774
48776
  cliInfo: "cli.info",
48775
48777
  cliStatus: "cli.status",
48776
48778
  cliCheckFreshness: "cli.checkFreshness",
@@ -48936,8 +48938,10 @@ const WebSocketRequestBody = Union([
48936
48938
  tagRequestBody(WS_METHODS.officialCodexFocusProfile, Struct({ name: TrimmedNonEmptyString })),
48937
48939
  tagRequestBody(WS_METHODS.officialCodexStopProfile, Struct({ name: TrimmedNonEmptyString })),
48938
48940
  tagRequestBody(WS_METHODS.officialCodexRestartProfile, Struct({ name: TrimmedNonEmptyString })),
48941
+ tagRequestBody(WS_METHODS.officialCodexUseProfile, Struct({ name: TrimmedNonEmptyString })),
48939
48942
  tagRequestBody(WS_METHODS.officialCodexLaunchProfiles, Struct({ names: Array$1(TrimmedNonEmptyString) })),
48940
48943
  tagRequestBody(WS_METHODS.officialCodexStopProfiles, Struct({ names: Array$1(TrimmedNonEmptyString) })),
48944
+ tagRequestBody(WS_METHODS.officialCodexActivateProfiles, Struct({ names: Array$1(TrimmedNonEmptyString) })),
48941
48945
  tagRequestBody(WS_METHODS.cliInfo, Struct({})),
48942
48946
  tagRequestBody(WS_METHODS.cliStatus, Struct({})),
48943
48947
  tagRequestBody(WS_METHODS.cliCheckFreshness, Struct({})),
@@ -58299,7 +58303,7 @@ const SQLITE_BUSY_MAX_ATTEMPTS = 3;
58299
58303
  const SQLITE_BUSY_RETRY_DELAY_MS = 100;
58300
58304
  const writeQueueByDbPath = /* @__PURE__ */ new Map();
58301
58305
  const initializedDbPaths = /* @__PURE__ */ new Set();
58302
- function isRecord$8(value) {
58306
+ function isRecord$9(value) {
58303
58307
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
58304
58308
  }
58305
58309
  function clone$1(value) {
@@ -58411,7 +58415,7 @@ function resolveAppStorageDbPath(userDataDir) {
58411
58415
  async function readDocument(dbPath, namespace, normalize) {
58412
58416
  return withDatabase(dbPath, (db) => {
58413
58417
  const row = useStatement(db, `SELECT value_json AS valueJson FROM ${APP_STORAGE_TABLE} WHERE namespace = ?`, (statement) => statement.get(namespace));
58414
- const parsed = isRecord$8(row) ? safeParseJson(row.valueJson) : null;
58418
+ const parsed = isRecord$9(row) ? safeParseJson(row.valueJson) : null;
58415
58419
  if (parsed === null) return null;
58416
58420
  return normalize(parsed);
58417
58421
  });
@@ -58442,7 +58446,7 @@ async function updateDocument(input) {
58442
58446
  beginImmediate(db);
58443
58447
  try {
58444
58448
  const row = useStatement(db, `SELECT value_json AS valueJson FROM ${APP_STORAGE_TABLE} WHERE namespace = ?`, (statement) => statement.get(input.namespace));
58445
- const current = isRecord$8(row) ? input.normalize(safeParseJson(row.valueJson) ?? input.fallback()) : input.fallback();
58449
+ const current = isRecord$9(row) ? input.normalize(safeParseJson(row.valueJson) ?? input.fallback()) : input.fallback();
58446
58450
  const next = input.normalize(input.transform(clone$1(current)));
58447
58451
  useStatement(db, `
58448
58452
  INSERT INTO ${APP_STORAGE_TABLE} (namespace, value_json, updated_at)
@@ -58469,7 +58473,7 @@ let configuredUserDataDir = null;
58469
58473
  let appStateCache = null;
58470
58474
  let writeLock = Promise.resolve();
58471
58475
  const writeLockContext = new AsyncLocalStorage();
58472
- function isRecord$7(value) {
58476
+ function isRecord$8(value) {
58473
58477
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
58474
58478
  }
58475
58479
  function clone(value) {
@@ -58516,7 +58520,9 @@ function createDefaultAppState() {
58516
58520
  lastObservedPid: null,
58517
58521
  lastRestartStatus: null,
58518
58522
  lastRestartReason: null,
58519
- instancesByProfileName: {}
58523
+ activity: [],
58524
+ instancesByProfileName: {},
58525
+ pendingRestartDebt: null
58520
58526
  },
58521
58527
  app: {
58522
58528
  lastAppVersion: null,
@@ -58604,7 +58610,7 @@ function createDefaultAppState() {
58604
58610
  }
58605
58611
  };
58606
58612
  }
58607
- function asString$9(value) {
58613
+ function asString$10(value) {
58608
58614
  if (typeof value !== "string") return null;
58609
58615
  const trimmed = value.trim();
58610
58616
  return trimmed.length > 0 ? trimmed : null;
@@ -58612,57 +58618,97 @@ function asString$9(value) {
58612
58618
  function asNumberOrNull(value) {
58613
58619
  return typeof value === "number" && Number.isFinite(value) ? value : null;
58614
58620
  }
58621
+ function asBooleanOrNull(value) {
58622
+ return typeof value === "boolean" ? value : null;
58623
+ }
58615
58624
  function normalizeAppState(raw) {
58616
58625
  const defaults = createDefaultAppState();
58617
- if (!isRecord$7(raw)) return defaults;
58618
- const rawAutoRoll = isRecord$7(raw.autoRoll) ? raw.autoRoll : void 0;
58626
+ if (!isRecord$8(raw)) return defaults;
58627
+ const rawAutoRoll = isRecord$8(raw.autoRoll) ? raw.autoRoll : void 0;
58619
58628
  const merged = clone(deepMerge(defaults, raw));
58620
58629
  merged.schemaVersion = 1;
58621
58630
  merged.autoRoll = normalizeAutoRollSettings(rawAutoRoll);
58622
- if (!isRecord$7(merged.officialCodex)) merged.officialCodex = clone(defaults.officialCodex);
58631
+ if (!isRecord$8(merged.officialCodex)) merged.officialCodex = clone(defaults.officialCodex);
58623
58632
  merged.officialCodex.lastProfileSwitchAt = asNumberOrNull(merged.officialCodex.lastProfileSwitchAt);
58624
- merged.officialCodex.lastProfileSwitchProfileKey = asString$9(merged.officialCodex.lastProfileSwitchProfileKey);
58633
+ merged.officialCodex.lastProfileSwitchProfileKey = asString$10(merged.officialCodex.lastProfileSwitchProfileKey);
58625
58634
  merged.officialCodex.lastVerifiedLaunchAt = asNumberOrNull(merged.officialCodex.lastVerifiedLaunchAt);
58626
- merged.officialCodex.lastVerifiedLaunchProfileKey = asString$9(merged.officialCodex.lastVerifiedLaunchProfileKey);
58635
+ merged.officialCodex.lastVerifiedLaunchProfileKey = asString$10(merged.officialCodex.lastVerifiedLaunchProfileKey);
58627
58636
  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 = {};
58637
+ merged.officialCodex.lastRestartStatus = asString$10(merged.officialCodex.lastRestartStatus);
58638
+ merged.officialCodex.lastRestartReason = asString$10(merged.officialCodex.lastRestartReason);
58639
+ merged.officialCodex.activity = Array.isArray(merged.officialCodex.activity) ? merged.officialCodex.activity.filter((entry) => isRecord$8(entry)).map((entry) => ({
58640
+ id: asString$10(entry.id) ?? "",
58641
+ at: asString$10(entry.at) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
58642
+ kind: entry.kind === "auto-roll-eval" || entry.kind === "profile-switch" || entry.kind === "auth-verified" || entry.kind === "official-codex-restart" || entry.kind === "reset-window-activation" || entry.kind === "low-remaining-alert" ? entry.kind : "auto-roll-eval",
58643
+ status: asString$10(entry.status) ?? "unknown",
58644
+ reason: asString$10(entry.reason),
58645
+ decisionId: asString$10(entry.decisionId),
58646
+ profileName: asString$10(entry.profileName),
58647
+ sourceProfileName: asString$10(entry.sourceProfileName),
58648
+ targetProfileName: asString$10(entry.targetProfileName),
58649
+ remainingPercent: asNumberOrNull(entry.remainingPercent),
58650
+ threshold: asNumberOrNull(entry.threshold),
58651
+ snapshotAgeMs: asNumberOrNull(entry.snapshotAgeMs),
58652
+ snapshotSource: asString$10(entry.snapshotSource),
58653
+ phase: asString$10(entry.phase),
58654
+ pid: asNumberOrNull(entry.pid),
58655
+ profileKeyHash: asString$10(entry.profileKeyHash),
58656
+ switchVerified: asBooleanOrNull(entry.switchVerified),
58657
+ restartRequested: asBooleanOrNull(entry.restartRequested),
58658
+ restartResult: asString$10(entry.restartResult),
58659
+ observedProfileName: asString$10(entry.observedProfileName),
58660
+ observedProfileKeyHash: asString$10(entry.observedProfileKeyHash),
58661
+ observedProfileMatchSource: asString$10(entry.observedProfileMatchSource)
58662
+ })).filter((entry) => entry.id && entry.at).slice(-50) : [];
58663
+ if (!isRecord$8(merged.officialCodex.instancesByProfileName)) merged.officialCodex.instancesByProfileName = {};
58631
58664
  else {
58632
58665
  const nextInstances = {};
58633
58666
  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);
58667
+ if (!isRecord$8(value)) continue;
58668
+ const profileName = asString$10(value.profileName) ?? asString$10(key);
58636
58669
  if (!profileName) continue;
58637
58670
  nextInstances[profileName] = {
58638
58671
  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),
58672
+ profileKey: asString$10(value.profileKey),
58673
+ profileHome: asString$10(value.profileHome),
58674
+ appPath: asString$10(value.appPath),
58675
+ bundleId: asString$10(value.bundleId),
58643
58676
  pid: asNumberOrNull(value.pid),
58644
58677
  appServerPid: asNumberOrNull(value.appServerPid),
58645
58678
  launchedAt: asNumberOrNull(value.launchedAt),
58646
58679
  lastVerifiedAt: asNumberOrNull(value.lastVerifiedAt),
58647
- lastStatus: asString$9(value.lastStatus),
58648
- lastError: asString$9(value.lastError)
58680
+ lastStatus: asString$10(value.lastStatus),
58681
+ lastError: asString$10(value.lastError)
58649
58682
  };
58650
58683
  }
58651
58684
  merged.officialCodex.instancesByProfileName = nextInstances;
58652
58685
  }
58653
- merged.app.lastAppVersion = asString$9(merged.app.lastAppVersion);
58654
- merged.app.pendingUpdateVersion = asString$9(merged.app.pendingUpdateVersion);
58655
- merged.app.lastProfileName = asString$9(merged.app.lastProfileName);
58686
+ if (isRecord$8(merged.officialCodex.pendingRestartDebt)) {
58687
+ const debt = merged.officialCodex.pendingRestartDebt;
58688
+ const targetProfileName = asString$10(debt.targetProfileName);
58689
+ merged.officialCodex.pendingRestartDebt = targetProfileName ? {
58690
+ targetProfileName,
58691
+ targetProfileKey: asString$10(debt.targetProfileKey),
58692
+ sourceProfileName: asString$10(debt.sourceProfileName),
58693
+ sourceProfileKey: asString$10(debt.sourceProfileKey),
58694
+ decisionId: asString$10(debt.decisionId),
58695
+ attempts: asNumberOrNull(debt.attempts) ?? 0,
58696
+ lastReason: asString$10(debt.lastReason)
58697
+ } : null;
58698
+ } else merged.officialCodex.pendingRestartDebt = null;
58699
+ merged.app.lastAppVersion = asString$10(merged.app.lastAppVersion);
58700
+ merged.app.pendingUpdateVersion = asString$10(merged.app.pendingUpdateVersion);
58701
+ merged.app.lastProfileName = asString$10(merged.app.lastProfileName);
58656
58702
  {
58657
58703
  const allowedAppKeys = new Set(Object.keys(defaults.app));
58658
58704
  for (const key of Object.keys(merged.app)) if (!allowedAppKeys.has(key)) delete merged.app[key];
58659
58705
  }
58660
- merged.license.licenseKey = asString$9(merged.license.licenseKey);
58661
- merged.license.purchaseEmail = asString$9(merged.license.purchaseEmail);
58662
- merged.license.lastVerifiedAt = asString$9(merged.license.lastVerifiedAt);
58663
- merged.license.nextCheckAt = asString$9(merged.license.nextCheckAt);
58664
- merged.license.lastVerificationError = asString$9(merged.license.lastVerificationError);
58665
- merged.license.signature = asString$9(merged.license.signature);
58706
+ merged.license.licenseKey = asString$10(merged.license.licenseKey);
58707
+ merged.license.purchaseEmail = asString$10(merged.license.purchaseEmail);
58708
+ merged.license.lastVerifiedAt = asString$10(merged.license.lastVerifiedAt);
58709
+ merged.license.nextCheckAt = asString$10(merged.license.nextCheckAt);
58710
+ merged.license.lastVerificationError = asString$10(merged.license.lastVerificationError);
58711
+ merged.license.signature = asString$10(merged.license.signature);
58666
58712
  if (![
58667
58713
  "inactive",
58668
58714
  "active",
@@ -58681,63 +58727,63 @@ function normalizeAppState(raw) {
58681
58727
  const allowedPreferenceKeys = new Set(Object.keys(defaults.preferences));
58682
58728
  for (const key of Object.keys(merged.preferences)) if (!allowedPreferenceKeys.has(key)) delete merged.preferences[key];
58683
58729
  }
58684
- if (!isRecord$7(merged.runtimeSettings)) merged.runtimeSettings = {};
58685
- if (!isRecord$7(merged.ui)) merged.ui = clone(defaults.ui);
58730
+ if (!isRecord$8(merged.runtimeSettings)) merged.runtimeSettings = {};
58731
+ if (!isRecord$8(merged.ui)) merged.ui = clone(defaults.ui);
58686
58732
  merged.ui.themeMode = merged.ui.themeMode === "light" || merged.ui.themeMode === "dark" ? merged.ui.themeMode : null;
58687
- if (!isRecord$7(merged.ui.layout)) merged.ui.layout = clone(defaults.ui.layout);
58733
+ if (!isRecord$8(merged.ui.layout)) merged.ui.layout = clone(defaults.ui.layout);
58688
58734
  if (typeof merged.ui.layout.sidebarCollapsed !== "boolean") merged.ui.layout.sidebarCollapsed = null;
58689
- if (!isRecord$7(merged.ui.profiles)) merged.ui.profiles = clone(defaults.ui.profiles);
58735
+ if (!isRecord$8(merged.ui.profiles)) merged.ui.profiles = clone(defaults.ui.profiles);
58690
58736
  merged.ui.profiles.viewMode = merged.ui.profiles.viewMode === "cards" || merged.ui.profiles.viewMode === "compact" ? merged.ui.profiles.viewMode : null;
58691
- merged.ui.profiles.sortBy = asString$9(merged.ui.profiles.sortBy);
58692
- merged.ui.profiles.groupBy = asString$9(merged.ui.profiles.groupBy);
58693
- merged.ui.profiles.planFilter = asString$9(merged.ui.profiles.planFilter);
58694
- merged.ui.profiles.healthFilter = asString$9(merged.ui.profiles.healthFilter);
58695
- merged.ui.profiles.customGroupFilter = asString$9(merged.ui.profiles.customGroupFilter);
58737
+ merged.ui.profiles.sortBy = asString$10(merged.ui.profiles.sortBy);
58738
+ merged.ui.profiles.groupBy = asString$10(merged.ui.profiles.groupBy);
58739
+ merged.ui.profiles.planFilter = asString$10(merged.ui.profiles.planFilter);
58740
+ merged.ui.profiles.healthFilter = asString$10(merged.ui.profiles.healthFilter);
58741
+ merged.ui.profiles.customGroupFilter = asString$10(merged.ui.profiles.customGroupFilter);
58696
58742
  if (typeof merged.ui.profiles.toolbarOpen !== "boolean") merged.ui.profiles.toolbarOpen = null;
58697
- if (!isRecord$7(merged.ui.profiles.collapsedSections)) merged.ui.profiles.collapsedSections = {};
58743
+ if (!isRecord$8(merged.ui.profiles.collapsedSections)) merged.ui.profiles.collapsedSections = {};
58698
58744
  else merged.ui.profiles.collapsedSections = Object.fromEntries(Object.entries(merged.ui.profiles.collapsedSections).flatMap(([key, value]) => {
58699
- const normalizedKey = asString$9(key);
58745
+ const normalizedKey = asString$10(key);
58700
58746
  if (!normalizedKey || typeof value !== "boolean") return [];
58701
58747
  return [[normalizedKey, value]];
58702
58748
  }));
58703
- if (!isRecord$7(merged.ui.onboarding)) merged.ui.onboarding = clone(defaults.ui.onboarding);
58749
+ if (!isRecord$8(merged.ui.onboarding)) merged.ui.onboarding = clone(defaults.ui.onboarding);
58704
58750
  if (typeof merged.ui.onboarding.welcomeCompleted !== "boolean") merged.ui.onboarding.welcomeCompleted = false;
58705
58751
  if (!Number.isFinite(merged.ui.onboarding.welcomeCompletedAt)) merged.ui.onboarding.welcomeCompletedAt = null;
58706
58752
  merged.ui.onboarding.welcomeResumeStep = merged.ui.onboarding.welcomeResumeStep === 2 ? 2 : null;
58707
- if (!isRecord$7(merged.ui.onboarding.milestones)) merged.ui.onboarding.milestones = clone(defaults.ui.onboarding.milestones);
58753
+ if (!isRecord$8(merged.ui.onboarding.milestones)) merged.ui.onboarding.milestones = clone(defaults.ui.onboarding.milestones);
58708
58754
  if (typeof merged.ui.onboarding.milestones.firstProfileAdded !== "boolean") merged.ui.onboarding.milestones.firstProfileAdded = false;
58709
58755
  if (typeof merged.ui.onboarding.milestones.secondProfileAdded !== "boolean") merged.ui.onboarding.milestones.secondProfileAdded = false;
58710
58756
  if (!Number.isFinite(merged.ui.onboarding.sessionCount)) merged.ui.onboarding.sessionCount = 0;
58711
- if (!isRecord$7(merged.ui.onboarding.nudgeCooldowns)) merged.ui.onboarding.nudgeCooldowns = {};
58757
+ if (!isRecord$8(merged.ui.onboarding.nudgeCooldowns)) merged.ui.onboarding.nudgeCooldowns = {};
58712
58758
  else merged.ui.onboarding.nudgeCooldowns = Object.fromEntries(Object.entries(merged.ui.onboarding.nudgeCooldowns).flatMap(([key, value]) => {
58713
- const normalizedKey = asString$9(key);
58759
+ const normalizedKey = asString$10(key);
58714
58760
  if (!normalizedKey || !Number.isFinite(value)) return [];
58715
58761
  return [[normalizedKey, Number(value)]];
58716
58762
  }));
58717
- if (!isRecord$7(merged.ui.onboarding.nudgeDismissCount)) merged.ui.onboarding.nudgeDismissCount = {};
58763
+ if (!isRecord$8(merged.ui.onboarding.nudgeDismissCount)) merged.ui.onboarding.nudgeDismissCount = {};
58718
58764
  else merged.ui.onboarding.nudgeDismissCount = Object.fromEntries(Object.entries(merged.ui.onboarding.nudgeDismissCount).flatMap(([key, value]) => {
58719
- const normalizedKey = asString$9(key);
58765
+ const normalizedKey = asString$10(key);
58720
58766
  if (!normalizedKey || !Number.isFinite(value)) return [];
58721
58767
  return [[normalizedKey, Number(value)]];
58722
58768
  }));
58723
58769
  if (typeof merged.ui.onboarding.proUnlockedCelebrated !== "boolean") merged.ui.onboarding.proUnlockedCelebrated = false;
58724
- if (!isRecord$7(merged.ui.projectThreadSelections)) merged.ui.projectThreadSelections = {};
58770
+ if (!isRecord$8(merged.ui.projectThreadSelections)) merged.ui.projectThreadSelections = {};
58725
58771
  else merged.ui.projectThreadSelections = Object.fromEntries(Object.entries(merged.ui.projectThreadSelections).flatMap(([projectId, threadId]) => {
58726
- const normalizedProjectId = asString$9(projectId);
58772
+ const normalizedProjectId = asString$10(projectId);
58727
58773
  if (!normalizedProjectId) return [];
58728
58774
  return [[normalizedProjectId, typeof threadId === "string" && threadId.trim().length > 0 ? threadId.trim() : null]];
58729
58775
  }));
58730
- merged.ui.duplicateWarningDismissedKey = asString$9(merged.ui.duplicateWarningDismissedKey);
58776
+ merged.ui.duplicateWarningDismissedKey = asString$10(merged.ui.duplicateWarningDismissedKey);
58731
58777
  if (typeof merged.ui.pendingLicenseActivation !== "boolean") merged.ui.pendingLicenseActivation = false;
58732
58778
  {
58733
58779
  const allowedUiKeys = new Set(Object.keys(defaults.ui));
58734
58780
  for (const key of Object.keys(merged.ui)) if (!allowedUiKeys.has(key)) delete merged.ui[key];
58735
58781
  }
58736
- if (!isRecord$7(merged.profileDashboard)) merged.profileDashboard = clone(defaults.profileDashboard);
58737
- if (!isRecord$7(merged.profileDashboard.customGroupsByAccountKey)) merged.profileDashboard.customGroupsByAccountKey = {};
58782
+ if (!isRecord$8(merged.profileDashboard)) merged.profileDashboard = clone(defaults.profileDashboard);
58783
+ if (!isRecord$8(merged.profileDashboard.customGroupsByAccountKey)) merged.profileDashboard.customGroupsByAccountKey = {};
58738
58784
  else merged.profileDashboard.customGroupsByAccountKey = Object.fromEntries(Object.entries(merged.profileDashboard.customGroupsByAccountKey).flatMap(([key, value]) => {
58739
- const normalizedKey = asString$9(key);
58740
- const normalizedValue = asString$9(value);
58785
+ const normalizedKey = asString$10(key);
58786
+ const normalizedValue = asString$10(value);
58741
58787
  if (!normalizedKey || !normalizedValue) return [];
58742
58788
  return [[normalizedKey, normalizedValue]];
58743
58789
  }));
@@ -58745,54 +58791,54 @@ function normalizeAppState(raw) {
58745
58791
  const allowedProfileDashboardKeys = new Set(Object.keys(defaults.profileDashboard));
58746
58792
  for (const key of Object.keys(merged.profileDashboard)) if (!allowedProfileDashboardKeys.has(key)) delete merged.profileDashboard[key];
58747
58793
  }
58748
- const legacyProjectSettingsByPath = isRecord$7(raw.projectSettingsByPath) ? raw.projectSettingsByPath : null;
58749
- if (!isRecord$7(merged.workspaceSettingsByPath)) merged.workspaceSettingsByPath = {};
58794
+ const legacyProjectSettingsByPath = isRecord$8(raw.projectSettingsByPath) ? raw.projectSettingsByPath : null;
58795
+ if (!isRecord$8(merged.workspaceSettingsByPath)) merged.workspaceSettingsByPath = {};
58750
58796
  if (legacyProjectSettingsByPath) merged.workspaceSettingsByPath = {
58751
58797
  ...legacyProjectSettingsByPath,
58752
58798
  ...merged.workspaceSettingsByPath
58753
58799
  };
58754
58800
  delete merged.projectSettingsByPath;
58755
- if (!isRecord$7(merged.conversationCategoriesByCwd)) merged.conversationCategoriesByCwd = {};
58756
- if (!isRecord$7(merged.conversationCategoryAssignmentsByCwd)) merged.conversationCategoryAssignmentsByCwd = {};
58757
- if (!isRecord$7(merged.git)) merged.git = clone(defaults.git);
58801
+ if (!isRecord$8(merged.conversationCategoriesByCwd)) merged.conversationCategoriesByCwd = {};
58802
+ if (!isRecord$8(merged.conversationCategoryAssignmentsByCwd)) merged.conversationCategoryAssignmentsByCwd = {};
58803
+ if (!isRecord$8(merged.git)) merged.git = clone(defaults.git);
58758
58804
  merged.git.commitMessagePrompt = normalizeCommitMessagePrompt(merged.git.commitMessagePrompt) ?? DEFAULT_COMMIT_MESSAGE_PROMPT;
58759
58805
  {
58760
58806
  const allowedGitKeys = new Set(Object.keys(defaults.git));
58761
58807
  for (const key of Object.keys(merged.git)) if (!allowedGitKeys.has(key)) delete merged.git[key];
58762
58808
  }
58763
- if (!isRecord$7(merged.skills)) merged.skills = clone(defaults.skills);
58809
+ if (!isRecord$8(merged.skills)) merged.skills = clone(defaults.skills);
58764
58810
  if (!Array.isArray(merged.skills.sources)) merged.skills.sources = [];
58765
- if (!isRecord$7(merged.skills.installsBySlug)) merged.skills.installsBySlug = {};
58766
- if (!isRecord$7(merged.sync)) merged.sync = clone(defaults.sync);
58767
- merged.sync.lastPushAt = asString$9(merged.sync.lastPushAt);
58768
- merged.sync.lastPullAt = asString$9(merged.sync.lastPullAt);
58769
- merged.sync.lastError = asString$9(merged.sync.lastError);
58770
- merged.sync.remoteUpdatedAt = asString$9(merged.sync.remoteUpdatedAt);
58771
- if (!isRecord$7(merged.analytics)) merged.analytics = isRecord$7(merged.telemetry) ? clone(merged.telemetry) : clone(defaults.analytics);
58772
- merged.analytics.anonymousId = asString$9(merged.analytics.anonymousId);
58811
+ if (!isRecord$8(merged.skills.installsBySlug)) merged.skills.installsBySlug = {};
58812
+ if (!isRecord$8(merged.sync)) merged.sync = clone(defaults.sync);
58813
+ merged.sync.lastPushAt = asString$10(merged.sync.lastPushAt);
58814
+ merged.sync.lastPullAt = asString$10(merged.sync.lastPullAt);
58815
+ merged.sync.lastError = asString$10(merged.sync.lastError);
58816
+ merged.sync.remoteUpdatedAt = asString$10(merged.sync.remoteUpdatedAt);
58817
+ if (!isRecord$8(merged.analytics)) merged.analytics = isRecord$8(merged.telemetry) ? clone(merged.telemetry) : clone(defaults.analytics);
58818
+ merged.analytics.anonymousId = asString$10(merged.analytics.anonymousId);
58773
58819
  if (!merged.analytics.anonymousId) {
58774
- const legacyInstallId = asString$9(merged.telemetry?.installId);
58820
+ const legacyInstallId = asString$10(merged.telemetry?.installId);
58775
58821
  merged.analytics.anonymousId = legacyInstallId;
58776
58822
  }
58777
58823
  if (typeof merged.analytics.enabled !== "boolean") merged.analytics.enabled = true;
58778
- merged.analytics.lastFlushAt = asString$9(merged.analytics.lastFlushAt);
58779
- merged.analytics.lastError = asString$9(merged.analytics.lastError);
58824
+ merged.analytics.lastFlushAt = asString$10(merged.analytics.lastFlushAt);
58825
+ merged.analytics.lastError = asString$10(merged.analytics.lastError);
58780
58826
  if ("telemetry" in merged) delete merged.telemetry;
58781
- if (!isRecord$7(merged.profilesByName)) merged.profilesByName = {};
58782
- if (!isRecord$7(merged.migration)) merged.migration = clone(defaults.migration);
58827
+ if (!isRecord$8(merged.profilesByName)) merged.profilesByName = {};
58828
+ if (!isRecord$8(merged.migration)) merged.migration = clone(defaults.migration);
58783
58829
  if (![
58784
58830
  "pending",
58785
58831
  "pending_local_storage",
58786
58832
  "complete"
58787
58833
  ].includes(merged.migration.status)) merged.migration.status = "pending";
58788
- merged.migration.startedAt = asString$9(merged.migration.startedAt);
58789
- merged.migration.completedAt = asString$9(merged.migration.completedAt);
58790
- merged.migration.localStorageImportedAt = asString$9(merged.migration.localStorageImportedAt);
58791
- merged.migration.lastError = asString$9(merged.migration.lastError);
58834
+ merged.migration.startedAt = asString$10(merged.migration.startedAt);
58835
+ merged.migration.completedAt = asString$10(merged.migration.completedAt);
58836
+ merged.migration.localStorageImportedAt = asString$10(merged.migration.localStorageImportedAt);
58837
+ merged.migration.lastError = asString$10(merged.migration.lastError);
58792
58838
  return merged;
58793
58839
  }
58794
58840
  function deepMerge(base, patch) {
58795
- if (!isRecord$7(base) || !isRecord$7(patch)) return clone(patch ?? base);
58841
+ if (!isRecord$8(base) || !isRecord$8(patch)) return clone(patch ?? base);
58796
58842
  const next = { ...base };
58797
58843
  for (const [key, patchValue] of Object.entries(patch)) {
58798
58844
  if (patchValue === void 0) continue;
@@ -58801,7 +58847,7 @@ function deepMerge(base, patch) {
58801
58847
  next[key] = clone(patchValue);
58802
58848
  continue;
58803
58849
  }
58804
- if (isRecord$7(currentValue) && isRecord$7(patchValue)) {
58850
+ if (isRecord$8(currentValue) && isRecord$8(patchValue)) {
58805
58851
  next[key] = deepMerge(currentValue, patchValue);
58806
58852
  continue;
58807
58853
  }
@@ -58948,12 +58994,12 @@ function isGeneralChatWorkspaceRoot(workspaceRoot) {
58948
58994
  }
58949
58995
  //#endregion
58950
58996
  //#region ../../packages/runtime-codex/src/codex/settings.ts
58951
- function asString$8(value) {
58997
+ function asString$9(value) {
58952
58998
  if (typeof value !== "string") return null;
58953
58999
  const trimmed = value.trim();
58954
59000
  return trimmed.length > 0 ? trimmed : null;
58955
59001
  }
58956
- function isRecord$6(value) {
59002
+ function isRecord$7(value) {
58957
59003
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
58958
59004
  }
58959
59005
  function parseAutoRoll(raw) {
@@ -58965,8 +59011,8 @@ function parseAutoRoll(raw) {
58965
59011
  }
58966
59012
  }
58967
59013
  function parseStoredLicense(raw) {
58968
- if (!isRecord$6(raw)) return null;
58969
- const statusCandidate = asString$8(raw.status);
59014
+ if (!isRecord$7(raw)) return null;
59015
+ const statusCandidate = asString$9(raw.status);
58970
59016
  const status = [
58971
59017
  "inactive",
58972
59018
  "active",
@@ -58974,13 +59020,13 @@ function parseStoredLicense(raw) {
58974
59020
  "error"
58975
59021
  ].includes(statusCandidate ?? "") ? statusCandidate : void 0;
58976
59022
  const license = {
58977
- licenseKey: asString$8(raw.licenseKey ?? raw.license_key),
58978
- purchaseEmail: asString$8(raw.purchaseEmail ?? raw.purchase_email),
58979
- lastVerifiedAt: asString$8(raw.lastVerifiedAt ?? raw.last_verified_at),
58980
- nextCheckAt: asString$8(raw.nextCheckAt ?? raw.next_check_at),
58981
- lastVerificationError: asString$8(raw.lastVerificationError ?? raw.last_verification_error),
59023
+ licenseKey: asString$9(raw.licenseKey ?? raw.license_key),
59024
+ purchaseEmail: asString$9(raw.purchaseEmail ?? raw.purchase_email),
59025
+ lastVerifiedAt: asString$9(raw.lastVerifiedAt ?? raw.last_verified_at),
59026
+ nextCheckAt: asString$9(raw.nextCheckAt ?? raw.next_check_at),
59027
+ lastVerificationError: asString$9(raw.lastVerificationError ?? raw.last_verification_error),
58982
59028
  status,
58983
- signature: asString$8(raw.signature)
59029
+ signature: asString$9(raw.signature)
58984
59030
  };
58985
59031
  return Boolean(license.licenseKey || license.purchaseEmail || license.lastVerifiedAt || license.nextCheckAt || license.lastVerificationError || license.status) ? license : null;
58986
59032
  }
@@ -59047,14 +59093,14 @@ async function readCodexSettingsJsonRaw() {
59047
59093
  };
59048
59094
  }
59049
59095
  async function writeCodexSettingsJsonRaw(payload) {
59050
- if (!isRecord$6(payload)) return;
59096
+ if (!isRecord$7(payload)) return;
59051
59097
  const autoRoll = parseAutoRoll(payload.autoRoll ?? payload.auto_roll);
59052
59098
  const license = parseStoredLicense(payload.license);
59053
59099
  await patchAppState({
59054
59100
  app: {
59055
- lastProfileName: asString$8(payload.lastProfileName ?? payload.last_profile_name),
59056
- lastAppVersion: asString$8(payload.lastAppVersion ?? payload.last_app_version),
59057
- pendingUpdateVersion: asString$8(payload.pendingUpdateVersion ?? payload.pending_update_version)
59101
+ lastProfileName: asString$9(payload.lastProfileName ?? payload.last_profile_name),
59102
+ lastAppVersion: asString$9(payload.lastAppVersion ?? payload.last_app_version),
59103
+ pendingUpdateVersion: asString$9(payload.pendingUpdateVersion ?? payload.pending_update_version)
59058
59104
  },
59059
59105
  autoRoll: autoRoll ? {
59060
59106
  enabled: autoRoll.enabled,
@@ -59084,11 +59130,11 @@ async function writeCodexSettingsJsonRaw(payload) {
59084
59130
  folderHistory: Array.isArray(payload.folderHistory) ? payload.folderHistory : void 0,
59085
59131
  pinnedPaths: Array.isArray(payload.pinnedPaths) ? payload.pinnedPaths : void 0
59086
59132
  },
59087
- workspaceSettingsByPath: isRecord$6(payload.workspaceSettingsByPath) ? payload.workspaceSettingsByPath : isRecord$6(payload.projectSettingsByPath) ? payload.projectSettingsByPath : void 0,
59088
- conversationCategoriesByCwd: isRecord$6(payload.categoriesByCwd) ? payload.categoriesByCwd : void 0,
59089
- conversationCategoryAssignmentsByCwd: isRecord$6(payload.conversationCategoryByCwd) ? payload.conversationCategoryByCwd : void 0,
59090
- git: isRecord$6(payload.git) ? payload.git : void 0,
59091
- sync: isRecord$6(payload.sync) ? payload.sync : void 0
59133
+ workspaceSettingsByPath: isRecord$7(payload.workspaceSettingsByPath) ? payload.workspaceSettingsByPath : isRecord$7(payload.projectSettingsByPath) ? payload.projectSettingsByPath : void 0,
59134
+ conversationCategoriesByCwd: isRecord$7(payload.categoriesByCwd) ? payload.categoriesByCwd : void 0,
59135
+ conversationCategoryAssignmentsByCwd: isRecord$7(payload.conversationCategoryByCwd) ? payload.conversationCategoryByCwd : void 0,
59136
+ git: isRecord$7(payload.git) ? payload.git : void 0,
59137
+ sync: isRecord$7(payload.sync) ? payload.sync : void 0
59092
59138
  });
59093
59139
  }
59094
59140
  //#endregion
@@ -59964,12 +60010,12 @@ function logError(...args) {
59964
60010
  console.error(...args);
59965
60011
  }
59966
60012
  function logInfo(...args) {
59967
- if (isTestEnv && !isMocked(console.warn)) return;
59968
- console.warn(...args);
60013
+ if (isTestEnv && !isMocked(console.info)) return;
60014
+ console.info(...args);
59969
60015
  }
59970
60016
  //#endregion
59971
60017
  //#region ../../packages/runtime-codex/src/codex/app-server.ts
59972
- function isRecord$5(value) {
60018
+ function isRecord$6(value) {
59973
60019
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
59974
60020
  }
59975
60021
  function asTrimmedString$2(value) {
@@ -59990,7 +60036,7 @@ function isThreadNotFoundError$1(error) {
59990
60036
  const lower = message.toLowerCase();
59991
60037
  return lower.includes("thread not found") || lower.includes("rollout");
59992
60038
  }
59993
- function asString$7(value) {
60039
+ function asString$8(value) {
59994
60040
  return typeof value === "string" ? value : value != null ? String(value) : "";
59995
60041
  }
59996
60042
  function isInlineImageValue(value) {
@@ -59998,12 +60044,12 @@ function isInlineImageValue(value) {
59998
60044
  return normalized.startsWith("data:") || normalized.startsWith("http://") || normalized.startsWith("https://");
59999
60045
  }
60000
60046
  function normalizeSendUserMessageItem(value) {
60001
- if (!isRecord$5(value)) return null;
60002
- const type = asString$7(value.type).trim();
60047
+ if (!isRecord$6(value)) return null;
60048
+ const type = asString$8(value.type).trim();
60003
60049
  if (!type) return null;
60004
- const data = isRecord$5(value.data) ? value.data : {};
60050
+ const data = isRecord$6(value.data) ? value.data : {};
60005
60051
  if (type === "text") {
60006
- const text = asString$7(data.text ?? value.text);
60052
+ const text = asString$8(data.text ?? value.text);
60007
60053
  if (!text.trim()) return null;
60008
60054
  return {
60009
60055
  type: "text",
@@ -60011,7 +60057,7 @@ function normalizeSendUserMessageItem(value) {
60011
60057
  };
60012
60058
  }
60013
60059
  if (type === "image") {
60014
- const imageUrl = asString$7(data.image_url ?? data.imageUrl ?? data.url ?? value.image_url ?? value.imageUrl ?? value.url).trim();
60060
+ const imageUrl = asString$8(data.image_url ?? data.imageUrl ?? data.url ?? value.image_url ?? value.imageUrl ?? value.url).trim();
60015
60061
  if (!imageUrl) return null;
60016
60062
  return {
60017
60063
  type: "image",
@@ -60019,7 +60065,7 @@ function normalizeSendUserMessageItem(value) {
60019
60065
  };
60020
60066
  }
60021
60067
  if (type === "localImage") {
60022
- const path = asString$7(data.path ?? value.path).trim();
60068
+ const path = asString$8(data.path ?? value.path).trim();
60023
60069
  if (!path) return null;
60024
60070
  return {
60025
60071
  type: "localImage",
@@ -60030,13 +60076,13 @@ function normalizeSendUserMessageItem(value) {
60030
60076
  }
60031
60077
  function normalizeTurnInputParams(params) {
60032
60078
  const normalized = { ...params };
60033
- const threadId = asString$7(params.threadId ?? params.thread_id ?? params.conversationId ?? params.conversation_id ?? "").trim();
60034
- if (threadId && !asString$7(params.threadId).trim()) normalized.threadId = threadId;
60035
- const expectedTurnId = asString$7(params.expectedTurnId ?? params.expected_turn_id ?? params.turnId ?? params.turn_id ?? "").trim();
60036
- if (expectedTurnId && !asString$7(params.expectedTurnId).trim()) normalized.expectedTurnId = expectedTurnId;
60037
- const turnId = asString$7(params.turnId ?? params.turn_id ?? expectedTurnId).trim();
60038
- if (turnId && !asString$7(params.turnId).trim()) normalized.turnId = turnId;
60039
- const providedInput = Array.isArray(params.input) ? params.input.filter((entry) => isRecord$5(entry)) : [];
60079
+ const threadId = asString$8(params.threadId ?? params.thread_id ?? params.conversationId ?? params.conversation_id ?? "").trim();
60080
+ if (threadId && !asString$8(params.threadId).trim()) normalized.threadId = threadId;
60081
+ const expectedTurnId = asString$8(params.expectedTurnId ?? params.expected_turn_id ?? params.turnId ?? params.turn_id ?? "").trim();
60082
+ if (expectedTurnId && !asString$8(params.expectedTurnId).trim()) normalized.expectedTurnId = expectedTurnId;
60083
+ const turnId = asString$8(params.turnId ?? params.turn_id ?? expectedTurnId).trim();
60084
+ if (turnId && !asString$8(params.turnId).trim()) normalized.turnId = turnId;
60085
+ const providedInput = Array.isArray(params.input) ? params.input.filter((entry) => isRecord$6(entry)) : [];
60040
60086
  if (providedInput.length > 0) {
60041
60087
  normalized.input = providedInput;
60042
60088
  return normalized;
@@ -60044,17 +60090,17 @@ function normalizeTurnInputParams(params) {
60044
60090
  const providedItems = Array.isArray(params.items) ? params.items.map((item) => normalizeSendUserMessageItem(item)).filter((item) => item !== null) : [];
60045
60091
  if (providedItems.length > 0) {
60046
60092
  const input = providedItems.flatMap((item) => {
60047
- const type = asString$7(item.type).trim();
60048
- const data = isRecord$5(item.data) ? item.data : item;
60093
+ const type = asString$8(item.type).trim();
60094
+ const data = isRecord$6(item.data) ? item.data : item;
60049
60095
  if (type === "text") {
60050
- const text = asString$7(data.text).trim();
60096
+ const text = asString$8(data.text).trim();
60051
60097
  return text ? [{
60052
60098
  type: "text",
60053
60099
  text
60054
60100
  }] : [];
60055
60101
  }
60056
60102
  if (type === "localImage") {
60057
- const path = asString$7(data.path).trim();
60103
+ const path = asString$8(data.path).trim();
60058
60104
  return path ? [{
60059
60105
  type: "localImage",
60060
60106
  path
@@ -60068,13 +60114,13 @@ function normalizeTurnInputParams(params) {
60068
60114
  }
60069
60115
  }
60070
60116
  const input = [];
60071
- const text = asString$7(params.text);
60117
+ const text = asString$8(params.text);
60072
60118
  if (text.trim()) input.push({
60073
60119
  type: "text",
60074
60120
  text
60075
60121
  });
60076
60122
  if (Array.isArray(params.images)) for (const candidate of params.images) {
60077
- const image = asString$7(candidate).trim();
60123
+ const image = asString$8(candidate).trim();
60078
60124
  if (!image || isInlineImageValue(image)) continue;
60079
60125
  input.push({
60080
60126
  type: "localImage",
@@ -60088,7 +60134,7 @@ function pickMethodKind(method, params) {
60088
60134
  const normalized = method.toLowerCase();
60089
60135
  if (normalized.includes("requestapproval")) return "approval";
60090
60136
  if (normalized.includes("requestuserinput")) return "user_input";
60091
- if (isRecord$5(params)) {
60137
+ if (isRecord$6(params)) {
60092
60138
  if ("file_changes" in params || "fileChanges" in params || "grant_root" in params || "grantRoot" in params) return "patch";
60093
60139
  if ("command" in params || "parsed_cmd" in params || "parsedCmd" in params || "cwd" in params) return "exec";
60094
60140
  }
@@ -60125,7 +60171,7 @@ function getRequestTimeoutMs(method) {
60125
60171
  return DEFAULT_REQUEST_TIMEOUT_MS;
60126
60172
  }
60127
60173
  function normalizeThreadLiveWorkspaceId(value) {
60128
- return asString$7(value).trim() || DEFAULT_THREAD_LIVE_WORKSPACE_ID;
60174
+ return asString$8(value).trim() || DEFAULT_THREAD_LIVE_WORKSPACE_ID;
60129
60175
  }
60130
60176
  function threadLiveKey(workspaceId, threadId) {
60131
60177
  return `${workspaceId}:${threadId}`;
@@ -60290,7 +60336,7 @@ var CodexAppServer = class extends EventEmitter {
60290
60336
  try {
60291
60337
  const response = await this.request("addConversationListener", listenerParams);
60292
60338
  this.addConversationListenerSupported = true;
60293
- return isRecord$5(response) ? response : null;
60339
+ return isRecord$6(response) ? response : null;
60294
60340
  } catch (error) {
60295
60341
  if (isMethodUnavailableError(error, "addConversationListener")) {
60296
60342
  this.addConversationListenerSupported = false;
@@ -60305,7 +60351,7 @@ var CodexAppServer = class extends EventEmitter {
60305
60351
  try {
60306
60352
  const response = await this.request("addConversationListener", listenerParams);
60307
60353
  this.addConversationListenerSupported = true;
60308
- return isRecord$5(response) ? response : null;
60354
+ return isRecord$6(response) ? response : null;
60309
60355
  } catch (resumeError) {
60310
60356
  if (isMethodUnavailableError(resumeError, "addConversationListener")) {
60311
60357
  this.addConversationListenerSupported = false;
@@ -60329,8 +60375,8 @@ var CodexAppServer = class extends EventEmitter {
60329
60375
  }
60330
60376
  async addConversationListener(params) {
60331
60377
  if (this.addConversationListenerSupported === false) return {};
60332
- const conversationId = asString$7(params.conversationId ?? params.threadId ?? params.thread_id ?? "").trim();
60333
- const workspaceId = asString$7(params.workspaceId ?? params.workspace_id ?? "").trim();
60378
+ const conversationId = asString$8(params.conversationId ?? params.threadId ?? params.thread_id ?? "").trim();
60379
+ const workspaceId = asString$8(params.workspaceId ?? params.workspace_id ?? "").trim();
60334
60380
  if (!conversationId) try {
60335
60381
  const response = await this.request("addConversationListener", params);
60336
60382
  this.addConversationListenerSupported = true;
@@ -60358,7 +60404,7 @@ var CodexAppServer = class extends EventEmitter {
60358
60404
  }
60359
60405
  async threadLiveSubscribe(params) {
60360
60406
  const workspaceId = normalizeThreadLiveWorkspaceId(params.workspaceId ?? params.workspace_id);
60361
- const threadId = asString$7(params.threadId ?? params.thread_id ?? params.conversationId).trim();
60407
+ const threadId = asString$8(params.threadId ?? params.thread_id ?? params.conversationId).trim();
60362
60408
  if (!threadId) return {};
60363
60409
  const key = threadLiveKey(workspaceId, threadId);
60364
60410
  this.threadLiveModeByKey.set(key, "listener");
@@ -60367,7 +60413,7 @@ var CodexAppServer = class extends EventEmitter {
60367
60413
  }
60368
60414
  async threadLiveUnsubscribe(params) {
60369
60415
  const workspaceId = normalizeThreadLiveWorkspaceId(params.workspaceId ?? params.workspace_id);
60370
- const threadId = asString$7(params.threadId ?? params.thread_id ?? params.conversationId).trim();
60416
+ const threadId = asString$8(params.threadId ?? params.thread_id ?? params.conversationId).trim();
60371
60417
  if (!threadId) return {};
60372
60418
  const key = threadLiveKey(workspaceId, threadId);
60373
60419
  this.threadLiveModeByKey.delete(key);
@@ -60515,7 +60561,7 @@ var CodexAppServer = class extends EventEmitter {
60515
60561
  listPendingUserInputRequests() {
60516
60562
  return [...this.pendingServerRequests.values()].filter((request) => request.kind === "user_input").map((request) => {
60517
60563
  const workspaceId = asTrimmedString$2(request.params.workspace_id ?? request.params.workspaceId);
60518
- const params = isRecord$5(request.params.params) ? request.params.params : request.params;
60564
+ const params = isRecord$6(request.params.params) ? request.params.params : request.params;
60519
60565
  if (!workspaceId) return null;
60520
60566
  return {
60521
60567
  workspace_id: workspaceId,
@@ -60628,7 +60674,7 @@ var CodexAppServer = class extends EventEmitter {
60628
60674
  this.logParseFailure(line, parseError);
60629
60675
  return true;
60630
60676
  }
60631
- if (!isRecord$5(parsed)) {
60677
+ if (!isRecord$6(parsed)) {
60632
60678
  logWarn("[app-server] unexpected JSON message", parsed);
60633
60679
  return true;
60634
60680
  }
@@ -60717,9 +60763,9 @@ var CodexAppServer = class extends EventEmitter {
60717
60763
  id: request.id,
60718
60764
  kind,
60719
60765
  method: request.method,
60720
- params: isRecord$5(request.params) ? request.params : {}
60766
+ params: isRecord$6(request.params) ? request.params : {}
60721
60767
  });
60722
- const params = isRecord$5(request.params) ? request.params : {};
60768
+ const params = isRecord$6(request.params) ? request.params : {};
60723
60769
  if (kind === "exec") this.emit("codex:exec-command-request", {
60724
60770
  requestToken: token,
60725
60771
  params
@@ -61480,11 +61526,11 @@ function startExternalCodexThreadSyncWatcher(homePath) {
61480
61526
  function resolveStateDbPath(stateDir) {
61481
61527
  return nodePath.join(stateDir, "state.sqlite");
61482
61528
  }
61483
- function asString$6(value) {
61529
+ function asString$7(value) {
61484
61530
  return typeof value === "string" ? value : value == null ? "" : String(value);
61485
61531
  }
61486
61532
  function asTrimmedString$1(value) {
61487
- return asString$6(value).trim();
61533
+ return asString$7(value).trim();
61488
61534
  }
61489
61535
  function asFiniteNumber(value) {
61490
61536
  if (typeof value === "number" && Number.isFinite(value)) return value;
@@ -63168,7 +63214,7 @@ function proposedPlanIdFromEvent(event, threadId) {
63168
63214
  if (event.itemId) return `plan:${threadId}:item:${event.itemId}`;
63169
63215
  return `plan:${threadId}:event:${event.eventId}`;
63170
63216
  }
63171
- function asString$5(value) {
63217
+ function asString$6(value) {
63172
63218
  return typeof value === "string" ? value : void 0;
63173
63219
  }
63174
63220
  function runtimePayloadRecord(event) {
@@ -63186,13 +63232,13 @@ function normalizeRuntimeTurnState(value) {
63186
63232
  }
63187
63233
  }
63188
63234
  function runtimeTurnState(event) {
63189
- return normalizeRuntimeTurnState(asString$5(runtimePayloadRecord(event)?.state));
63235
+ return normalizeRuntimeTurnState(asString$6(runtimePayloadRecord(event)?.state));
63190
63236
  }
63191
63237
  function runtimeTurnErrorMessage(event) {
63192
- return asString$5(runtimePayloadRecord(event)?.errorMessage);
63238
+ return asString$6(runtimePayloadRecord(event)?.errorMessage);
63193
63239
  }
63194
63240
  function runtimeErrorMessageFromEvent(event) {
63195
- return asString$5(runtimePayloadRecord(event)?.message);
63241
+ return asString$6(runtimePayloadRecord(event)?.message);
63196
63242
  }
63197
63243
  function shouldTriggerPassiveImportedThreadRecovery(event) {
63198
63244
  switch (event.type) {
@@ -63930,13 +63976,13 @@ function asObject$1(value) {
63930
63976
  if (!value || typeof value !== "object") return;
63931
63977
  return value;
63932
63978
  }
63933
- function asString$4(value) {
63979
+ function asString$5(value) {
63934
63980
  return typeof value === "string" ? value : void 0;
63935
63981
  }
63936
63982
  function readCodexAccountSnapshot(response) {
63937
63983
  const record = asObject$1(response);
63938
63984
  const account = asObject$1(record?.account) ?? record;
63939
- const accountType = asString$4(account?.type);
63985
+ const accountType = asString$5(account?.type);
63940
63986
  if (accountType === "apiKey") return {
63941
63987
  type: "apiKey",
63942
63988
  planType: null,
@@ -65480,7 +65526,7 @@ function asObject(value) {
65480
65526
  if (!value || typeof value !== "object") return;
65481
65527
  return value;
65482
65528
  }
65483
- function asString$3(value) {
65529
+ function asString$4(value) {
65484
65530
  return typeof value === "string" ? value : void 0;
65485
65531
  }
65486
65532
  function asArray(value) {
@@ -65499,7 +65545,7 @@ function toTurnStatus(value) {
65499
65545
  }
65500
65546
  }
65501
65547
  function normalizeItemType(raw) {
65502
- const type = asString$3(raw);
65548
+ const type = asString$4(raw);
65503
65549
  if (!type) return "item";
65504
65550
  return type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
65505
65551
  }
@@ -65541,16 +65587,16 @@ function itemTitle(itemType) {
65541
65587
  function itemDetail(item, payload) {
65542
65588
  const nestedResult = asObject(item.result);
65543
65589
  const candidates = [
65544
- asString$3(item.command),
65545
- asString$3(item.title),
65546
- asString$3(item.summary),
65547
- asString$3(item.text),
65548
- asString$3(item.path),
65549
- asString$3(item.prompt),
65550
- asString$3(nestedResult?.command),
65551
- asString$3(payload.command),
65552
- asString$3(payload.message),
65553
- asString$3(payload.prompt)
65590
+ asString$4(item.command),
65591
+ asString$4(item.title),
65592
+ asString$4(item.summary),
65593
+ asString$4(item.text),
65594
+ asString$4(item.path),
65595
+ asString$4(item.prompt),
65596
+ asString$4(nestedResult?.command),
65597
+ asString$4(payload.command),
65598
+ asString$4(payload.message),
65599
+ asString$4(payload.prompt)
65554
65600
  ];
65555
65601
  for (const candidate of candidates) {
65556
65602
  if (!candidate) continue;
@@ -65582,9 +65628,9 @@ function toRequestTypeFromKind(kind) {
65582
65628
  }
65583
65629
  function toRequestTypeFromResolvedPayload(payload) {
65584
65630
  const request = asObject(payload?.request);
65585
- const method = asString$3(request?.method) ?? asString$3(payload?.method);
65631
+ const method = asString$4(request?.method) ?? asString$4(payload?.method);
65586
65632
  if (method) return toRequestTypeFromMethod(method);
65587
- const requestKind = asString$3(request?.kind) ?? asString$3(payload?.requestKind);
65633
+ const requestKind = asString$4(request?.kind) ?? asString$4(payload?.requestKind);
65588
65634
  if (requestKind) return toRequestTypeFromKind(requestKind);
65589
65635
  return "unknown";
65590
65636
  }
@@ -65610,17 +65656,17 @@ function toUserInputQuestions(payload) {
65610
65656
  const options = asArray(question.options)?.map((option) => {
65611
65657
  const optionRecord = asObject(option);
65612
65658
  if (!optionRecord) return void 0;
65613
- const label = asString$3(optionRecord.label)?.trim();
65614
- const description = asString$3(optionRecord.description)?.trim();
65659
+ const label = asString$4(optionRecord.label)?.trim();
65660
+ const description = asString$4(optionRecord.description)?.trim();
65615
65661
  if (!label || !description) return;
65616
65662
  return {
65617
65663
  label,
65618
65664
  description
65619
65665
  };
65620
65666
  }).filter((option) => option !== void 0);
65621
- const id = asString$3(question.id)?.trim();
65622
- const header = asString$3(question.header)?.trim();
65623
- const prompt = asString$3(question.question)?.trim();
65667
+ const id = asString$4(question.id)?.trim();
65668
+ const header = asString$4(question.header)?.trim();
65669
+ const prompt = asString$4(question.question)?.trim();
65624
65670
  const multiSelect = question.multiSelect === true || question.multi_select === true;
65625
65671
  if (!id || !header || !prompt || !options || options.length === 0) return;
65626
65672
  return {
@@ -65673,9 +65719,9 @@ function codexEventMessage(payload) {
65673
65719
  }
65674
65720
  function codexEventBase(event, canonicalThreadId) {
65675
65721
  const msg = codexEventMessage(asObject(event.payload));
65676
- const turnId = asString$3(msg?.turn_id) ?? asString$3(msg?.turnId);
65677
- const itemId = asString$3(msg?.item_id) ?? asString$3(msg?.itemId);
65678
- const requestId = asString$3(msg?.request_id) ?? asString$3(msg?.requestId);
65722
+ const turnId = asString$4(msg?.turn_id) ?? asString$4(msg?.turnId);
65723
+ const itemId = asString$4(msg?.item_id) ?? asString$4(msg?.itemId);
65724
+ const requestId = asString$4(msg?.request_id) ?? asString$4(msg?.requestId);
65679
65725
  const base = runtimeEventBase(event, canonicalThreadId);
65680
65726
  const providerRefs = base.providerRefs ? {
65681
65727
  ...base.providerRefs,
@@ -65768,7 +65814,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65768
65814
  payload: { questions }
65769
65815
  }];
65770
65816
  }
65771
- const detail = asString$3(payload?.command) ?? asString$3(payload?.reason) ?? asString$3(payload?.prompt);
65817
+ const detail = asString$4(payload?.command) ?? asString$4(payload?.reason) ?? asString$4(payload?.prompt);
65772
65818
  return [{
65773
65819
  ...runtimeEventBase(event, canonicalThreadId),
65774
65820
  type: "request.opened",
@@ -65825,7 +65871,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65825
65871
  }
65826
65872
  }];
65827
65873
  if (event.method === "thread/started") {
65828
- const providerThreadId = asString$3(asObject(payload?.thread)?.id) ?? asString$3(payload?.threadId);
65874
+ const providerThreadId = asString$4(asObject(payload?.thread)?.id) ?? asString$4(payload?.threadId);
65829
65875
  if (!providerThreadId) return [];
65830
65876
  return [{
65831
65877
  ...runtimeEventBase(event, canonicalThreadId),
@@ -65841,7 +65887,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65841
65887
  type: "thread.state.changed",
65842
65888
  ...runtimeEventBase(event, canonicalThreadId),
65843
65889
  payload: {
65844
- state: event.method === "thread/archived" ? "archived" : event.method === "thread/closed" ? "closed" : event.method === "thread/compacted" ? "compacted" : toThreadState(asString$3(statusRecord?.type) ?? asString$3(threadStatusRecord?.type) ?? asString$3(threadRecord?.state) ?? payload?.state),
65890
+ state: event.method === "thread/archived" ? "archived" : event.method === "thread/closed" ? "closed" : event.method === "thread/compacted" ? "compacted" : toThreadState(asString$4(statusRecord?.type) ?? asString$4(threadStatusRecord?.type) ?? asString$4(threadRecord?.state) ?? payload?.state),
65845
65891
  ...event.payload !== void 0 ? { detail: event.payload } : {}
65846
65892
  }
65847
65893
  }];
@@ -65850,7 +65896,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65850
65896
  type: "thread.metadata.updated",
65851
65897
  ...runtimeEventBase(event, canonicalThreadId),
65852
65898
  payload: {
65853
- ...asString$3(payload?.threadName) ? { name: asString$3(payload?.threadName) } : {},
65899
+ ...asString$4(payload?.threadName) ? { name: asString$4(payload?.threadName) } : {},
65854
65900
  ...event.payload !== void 0 ? { metadata: asObject(event.payload) } : {}
65855
65901
  }
65856
65902
  }];
@@ -65867,19 +65913,19 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65867
65913
  turnId,
65868
65914
  type: "turn.started",
65869
65915
  payload: {
65870
- ...asString$3(turn?.model) ? { model: asString$3(turn?.model) } : {},
65871
- ...asString$3(turn?.effort) ? { effort: asString$3(turn?.effort) } : {}
65916
+ ...asString$4(turn?.model) ? { model: asString$4(turn?.model) } : {},
65917
+ ...asString$4(turn?.effort) ? { effort: asString$4(turn?.effort) } : {}
65872
65918
  }
65873
65919
  }];
65874
65920
  }
65875
65921
  if (event.method === "turn/completed") {
65876
- const errorMessage = asString$3(asObject(turn?.error)?.message);
65922
+ const errorMessage = asString$4(asObject(turn?.error)?.message);
65877
65923
  return [{
65878
65924
  ...runtimeEventBase(event, canonicalThreadId),
65879
65925
  type: "turn.completed",
65880
65926
  payload: {
65881
65927
  state: toTurnStatus(turn?.status),
65882
- ...asString$3(turn?.stopReason) ? { stopReason: asString$3(turn?.stopReason) } : {},
65928
+ ...asString$4(turn?.stopReason) ? { stopReason: asString$4(turn?.stopReason) } : {},
65883
65929
  ...turn?.usage !== void 0 ? { usage: turn.usage } : {},
65884
65930
  ...asObject(turn?.modelUsage) ? { modelUsage: asObject(turn?.modelUsage) } : {},
65885
65931
  ...asNumber$2(turn?.totalCostUsd) !== void 0 ? { totalCostUsd: asNumber$2(turn?.totalCostUsd) } : {},
@@ -65898,9 +65944,9 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65898
65944
  ...runtimeEventBase(event, canonicalThreadId),
65899
65945
  type: "turn.plan.updated",
65900
65946
  payload: {
65901
- ...asString$3(payload?.explanation) ? { explanation: asString$3(payload?.explanation) } : {},
65947
+ ...asString$4(payload?.explanation) ? { explanation: asString$4(payload?.explanation) } : {},
65902
65948
  plan: steps.map((entry) => asObject(entry)).filter((entry) => entry !== void 0).map((entry) => ({
65903
- step: asString$3(entry.step) ?? "step",
65949
+ step: asString$4(entry.step) ?? "step",
65904
65950
  status: entry.status === "completed" || entry.status === "inProgress" ? entry.status : "pending"
65905
65951
  }))
65906
65952
  }
@@ -65909,7 +65955,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65909
65955
  if (event.method === "turn/diff/updated") return [{
65910
65956
  ...runtimeEventBase(event, canonicalThreadId),
65911
65957
  type: "turn.diff.updated",
65912
- payload: { unifiedDiff: asString$3(payload?.unifiedDiff) ?? asString$3(payload?.diff) ?? asString$3(payload?.patch) ?? "" }
65958
+ payload: { unifiedDiff: asString$4(payload?.unifiedDiff) ?? asString$4(payload?.diff) ?? asString$4(payload?.patch) ?? "" }
65913
65959
  }];
65914
65960
  if (event.method === "item/started") {
65915
65961
  const started = mapItemLifecycle(event, canonicalThreadId, "item.started");
@@ -65936,7 +65982,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65936
65982
  return updated ? [updated] : [];
65937
65983
  }
65938
65984
  if (event.method === "item/plan/delta") {
65939
- const delta = event.textDelta ?? asString$3(payload?.delta) ?? asString$3(payload?.text) ?? asString$3(asObject(payload?.content)?.text);
65985
+ const delta = event.textDelta ?? asString$4(payload?.delta) ?? asString$4(payload?.text) ?? asString$4(asObject(payload?.content)?.text);
65940
65986
  if (!delta || delta.length === 0) return [];
65941
65987
  return [{
65942
65988
  ...runtimeEventBase(event, canonicalThreadId),
@@ -65945,7 +65991,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65945
65991
  }];
65946
65992
  }
65947
65993
  if (event.method === "item/agentMessage/delta" || event.method === "item/commandExecution/outputDelta" || event.method === "item/fileChange/outputDelta" || event.method === "item/reasoning/summaryTextDelta" || event.method === "item/reasoning/textDelta") {
65948
- const delta = event.textDelta ?? asString$3(payload?.delta) ?? asString$3(payload?.text) ?? asString$3(asObject(payload?.content)?.text);
65994
+ const delta = event.textDelta ?? asString$4(payload?.delta) ?? asString$4(payload?.text) ?? asString$4(asObject(payload?.content)?.text);
65949
65995
  if (!delta || delta.length === 0) return [];
65950
65996
  return [{
65951
65997
  ...runtimeEventBase(event, canonicalThreadId),
@@ -65962,9 +66008,9 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65962
66008
  ...runtimeEventBase(event, canonicalThreadId),
65963
66009
  type: "tool.progress",
65964
66010
  payload: {
65965
- ...asString$3(payload?.toolUseId) ? { toolUseId: asString$3(payload?.toolUseId) } : {},
65966
- ...asString$3(payload?.toolName) ? { toolName: asString$3(payload?.toolName) } : {},
65967
- ...asString$3(payload?.summary) ? { summary: asString$3(payload?.summary) } : {},
66011
+ ...asString$4(payload?.toolUseId) ? { toolUseId: asString$4(payload?.toolUseId) } : {},
66012
+ ...asString$4(payload?.toolName) ? { toolName: asString$4(payload?.toolName) } : {},
66013
+ ...asString$4(payload?.summary) ? { summary: asString$4(payload?.summary) } : {},
65968
66014
  ...asNumber$2(payload?.elapsedSeconds) !== void 0 ? { elapsedSeconds: asNumber$2(payload?.elapsedSeconds) } : {}
65969
66015
  }
65970
66016
  }];
@@ -65986,21 +66032,21 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
65986
66032
  }];
65987
66033
  if (event.method === "codex/event/task_started") {
65988
66034
  const msg = codexEventMessage(payload);
65989
- const taskId = asString$3(payload?.id) ?? asString$3(msg?.turn_id);
66035
+ const taskId = asString$4(payload?.id) ?? asString$4(msg?.turn_id);
65990
66036
  if (!taskId) return [];
65991
66037
  return [{
65992
66038
  ...codexEventBase(event, canonicalThreadId),
65993
66039
  type: "task.started",
65994
66040
  payload: {
65995
66041
  taskId: asRuntimeTaskId(taskId),
65996
- ...asString$3(msg?.collaboration_mode_kind) ? { taskType: asString$3(msg?.collaboration_mode_kind) } : {}
66042
+ ...asString$4(msg?.collaboration_mode_kind) ? { taskType: asString$4(msg?.collaboration_mode_kind) } : {}
65997
66043
  }
65998
66044
  }];
65999
66045
  }
66000
66046
  if (event.method === "codex/event/task_complete") {
66001
66047
  const msg = codexEventMessage(payload);
66002
- const taskId = asString$3(payload?.id) ?? asString$3(msg?.turn_id);
66003
- const proposedPlanMarkdown = extractProposedPlanMarkdown(asString$3(msg?.last_agent_message));
66048
+ const taskId = asString$4(payload?.id) ?? asString$4(msg?.turn_id);
66049
+ const proposedPlanMarkdown = extractProposedPlanMarkdown(asString$4(msg?.last_agent_message));
66004
66050
  if (!taskId) {
66005
66051
  if (!proposedPlanMarkdown) return [];
66006
66052
  return [{
@@ -66015,7 +66061,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
66015
66061
  payload: {
66016
66062
  taskId: asRuntimeTaskId(taskId),
66017
66063
  status: "completed",
66018
- ...asString$3(msg?.last_agent_message) ? { summary: asString$3(msg?.last_agent_message) } : {}
66064
+ ...asString$4(msg?.last_agent_message) ? { summary: asString$4(msg?.last_agent_message) } : {}
66019
66065
  }
66020
66066
  }];
66021
66067
  if (proposedPlanMarkdown) events.push({
@@ -66027,8 +66073,8 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
66027
66073
  }
66028
66074
  if (event.method === "codex/event/agent_reasoning") {
66029
66075
  const msg = codexEventMessage(payload);
66030
- const taskId = asString$3(payload?.id);
66031
- const description = asString$3(msg?.text);
66076
+ const taskId = asString$4(payload?.id);
66077
+ const description = asString$4(msg?.text);
66032
66078
  if (!taskId || !description) return [];
66033
66079
  return [{
66034
66080
  ...codexEventBase(event, canonicalThreadId),
@@ -66041,7 +66087,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
66041
66087
  }
66042
66088
  if (event.method === "codex/event/reasoning_content_delta") {
66043
66089
  const msg = codexEventMessage(payload);
66044
- const delta = asString$3(msg?.delta);
66090
+ const delta = asString$4(msg?.delta);
66045
66091
  if (!delta) return [];
66046
66092
  return [{
66047
66093
  ...codexEventBase(event, canonicalThreadId),
@@ -66057,26 +66103,26 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
66057
66103
  type: "model.rerouted",
66058
66104
  ...runtimeEventBase(event, canonicalThreadId),
66059
66105
  payload: {
66060
- fromModel: asString$3(payload?.fromModel) ?? "unknown",
66061
- toModel: asString$3(payload?.toModel) ?? "unknown",
66062
- reason: asString$3(payload?.reason) ?? "unknown"
66106
+ fromModel: asString$4(payload?.fromModel) ?? "unknown",
66107
+ toModel: asString$4(payload?.toModel) ?? "unknown",
66108
+ reason: asString$4(payload?.reason) ?? "unknown"
66063
66109
  }
66064
66110
  }];
66065
66111
  if (event.method === "deprecationNotice") return [{
66066
66112
  type: "deprecation.notice",
66067
66113
  ...runtimeEventBase(event, canonicalThreadId),
66068
66114
  payload: {
66069
- summary: asString$3(payload?.summary) ?? "Deprecation notice",
66070
- ...asString$3(payload?.details) ? { details: asString$3(payload?.details) } : {}
66115
+ summary: asString$4(payload?.summary) ?? "Deprecation notice",
66116
+ ...asString$4(payload?.details) ? { details: asString$4(payload?.details) } : {}
66071
66117
  }
66072
66118
  }];
66073
66119
  if (event.method === "configWarning") return [{
66074
66120
  type: "config.warning",
66075
66121
  ...runtimeEventBase(event, canonicalThreadId),
66076
66122
  payload: {
66077
- summary: asString$3(payload?.summary) ?? "Configuration warning",
66078
- ...asString$3(payload?.details) ? { details: asString$3(payload?.details) } : {},
66079
- ...asString$3(payload?.path) ? { path: asString$3(payload?.path) } : {},
66123
+ summary: asString$4(payload?.summary) ?? "Configuration warning",
66124
+ ...asString$4(payload?.details) ? { details: asString$4(payload?.details) } : {},
66125
+ ...asString$4(payload?.path) ? { path: asString$4(payload?.path) } : {},
66080
66126
  ...payload?.range !== void 0 ? { range: payload.range } : {}
66081
66127
  }
66082
66128
  }];
@@ -66095,12 +66141,12 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
66095
66141
  ...runtimeEventBase(event, canonicalThreadId),
66096
66142
  payload: {
66097
66143
  success: payload?.success === true,
66098
- ...asString$3(payload?.name) ? { name: asString$3(payload?.name) } : {},
66099
- ...asString$3(payload?.error) ? { error: asString$3(payload?.error) } : {}
66144
+ ...asString$4(payload?.name) ? { name: asString$4(payload?.name) } : {},
66145
+ ...asString$4(payload?.error) ? { error: asString$4(payload?.error) } : {}
66100
66146
  }
66101
66147
  }];
66102
66148
  if (event.method === "thread/realtime/started") {
66103
- const realtimeSessionId = asString$3(payload?.realtimeSessionId);
66149
+ const realtimeSessionId = asString$4(payload?.realtimeSessionId);
66104
66150
  return [{
66105
66151
  type: "thread.realtime.started",
66106
66152
  ...runtimeEventBase(event, canonicalThreadId),
@@ -66118,7 +66164,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
66118
66164
  payload: { audio: event.payload ?? {} }
66119
66165
  }];
66120
66166
  if (event.method === "thread/realtime/error") {
66121
- const message = asString$3(payload?.message) ?? event.message ?? "Realtime error";
66167
+ const message = asString$4(payload?.message) ?? event.message ?? "Realtime error";
66122
66168
  return [{
66123
66169
  type: "thread.realtime.error",
66124
66170
  ...runtimeEventBase(event, canonicalThreadId),
@@ -66131,7 +66177,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
66131
66177
  payload: { reason: event.message }
66132
66178
  }];
66133
66179
  if (event.method === "error") {
66134
- const message = asString$3(asObject(payload?.error)?.message) ?? event.message ?? "Provider runtime error";
66180
+ const message = asString$4(asObject(payload?.error)?.message) ?? event.message ?? "Provider runtime error";
66135
66181
  const willRetry = payload?.willRetry === true;
66136
66182
  return [{
66137
66183
  type: willRetry ? "runtime.warning" : "runtime.error",
@@ -66738,12 +66784,12 @@ function decodeProviderKind(providerName, operation) {
66738
66784
  detail: `Unknown persisted provider '${providerName}'.`
66739
66785
  }));
66740
66786
  }
66741
- function isRecord$4(value) {
66787
+ function isRecord$5(value) {
66742
66788
  return value !== null && typeof value === "object" && !Array.isArray(value);
66743
66789
  }
66744
66790
  function mergeRuntimePayload(existing, next) {
66745
66791
  if (next === void 0) return existing ?? null;
66746
- if (isRecord$4(existing) && isRecord$4(next)) return {
66792
+ if (isRecord$5(existing) && isRecord$5(next)) return {
66747
66793
  ...existing,
66748
66794
  ...next
66749
66795
  };
@@ -72761,7 +72807,7 @@ const FALLBACK_FEATURE_KEYS = [
72761
72807
  ];
72762
72808
  let metadataCache = null;
72763
72809
  let metadataCacheAt = 0;
72764
- function isRecord$3(value) {
72810
+ function isRecord$4(value) {
72765
72811
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
72766
72812
  }
72767
72813
  function uniqueSorted(values) {
@@ -72832,14 +72878,14 @@ function parseFeatureCatalog(output) {
72832
72878
  }
72833
72879
  function parseSchemaKeys(schemaRaw) {
72834
72880
  const parsed = JSON.parse(schemaRaw);
72835
- if (!isRecord$3(parsed)) return {
72881
+ if (!isRecord$4(parsed)) return {
72836
72882
  topLevelKeys: FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
72837
72883
  featureKeys: FALLBACK_FEATURE_KEYS
72838
72884
  };
72839
- const properties = isRecord$3(parsed.properties) ? parsed.properties : {};
72885
+ const properties = isRecord$4(parsed.properties) ? parsed.properties : {};
72840
72886
  const topLevelKeys = uniqueSorted(Object.keys(properties));
72841
- const features = isRecord$3(properties.features) ? properties.features : {};
72842
- const featureProperties = isRecord$3(features.properties) ? features.properties : {};
72887
+ const features = isRecord$4(properties.features) ? properties.features : {};
72888
+ const featureProperties = isRecord$4(features.properties) ? features.properties : {};
72843
72889
  const featureKeys = uniqueSorted(Object.keys(featureProperties));
72844
72890
  return {
72845
72891
  topLevelKeys: topLevelKeys.length > 0 ? topLevelKeys : FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
@@ -73585,8 +73631,116 @@ function resolveProfileIdentityKeyFromProfile(profile) {
73585
73631
  //#endregion
73586
73632
  //#region ../../packages/runtime-codex/src/codex/rpc.ts
73587
73633
  const RPC_TIMEOUT_MS = 1e4;
73634
+ const ACTIVATION_TURN_TIMEOUT_MS = 9e4;
73635
+ const ACTIVATION_PROMPT = "Reply with exactly: ok.";
73636
+ const ACTIVATION_MODEL = "gpt-5.1-codex-mini";
73588
73637
  const MAX_STDERR_CAPTURE_CHARS = 32768;
73589
73638
  const REFRESH_TOKEN_REDEEMED_SNIPPET$1 = "refresh token was already used";
73639
+ function isRecord$3(value) {
73640
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
73641
+ }
73642
+ function asString$3(value) {
73643
+ return typeof value === "string" ? value : value != null ? String(value) : "";
73644
+ }
73645
+ function createRpcMessageReader(rl) {
73646
+ const queue = [];
73647
+ const waiters = [];
73648
+ let closedError = null;
73649
+ const rejectAll = (error) => {
73650
+ closedError = error;
73651
+ while (waiters.length > 0) {
73652
+ const waiter = waiters.shift();
73653
+ clearTimeout(waiter.timer);
73654
+ waiter.reject(error);
73655
+ }
73656
+ };
73657
+ const onLine = (line) => {
73658
+ let parsed;
73659
+ try {
73660
+ parsed = JSON.parse(line);
73661
+ } catch {
73662
+ rejectAll(/* @__PURE__ */ new Error("codex RPC returned malformed JSON"));
73663
+ return;
73664
+ }
73665
+ const waiterIndex = waiters.findIndex((waiter) => waiter.predicate(parsed));
73666
+ if (waiterIndex >= 0) {
73667
+ const [waiter] = waiters.splice(waiterIndex, 1);
73668
+ clearTimeout(waiter.timer);
73669
+ waiter.resolve(parsed);
73670
+ return;
73671
+ }
73672
+ queue.push(parsed);
73673
+ };
73674
+ const onClose = () => {
73675
+ rejectAll(/* @__PURE__ */ new Error("codex RPC stream closed before response"));
73676
+ };
73677
+ rl.on("line", onLine);
73678
+ rl.on("close", onClose);
73679
+ return {
73680
+ read(predicate, timeoutMs) {
73681
+ const queuedIndex = queue.findIndex(predicate);
73682
+ if (queuedIndex >= 0) {
73683
+ const [message] = queue.splice(queuedIndex, 1);
73684
+ return Promise.resolve(message);
73685
+ }
73686
+ if (closedError) return Promise.reject(closedError);
73687
+ return new Promise((resolve, reject) => {
73688
+ const waiter = {
73689
+ predicate,
73690
+ resolve,
73691
+ reject,
73692
+ timer: setTimeout(() => {
73693
+ const index = waiters.indexOf(waiter);
73694
+ if (index >= 0) waiters.splice(index, 1);
73695
+ reject(/* @__PURE__ */ new Error("codex RPC timed out"));
73696
+ }, Math.max(1, timeoutMs))
73697
+ };
73698
+ waiters.push(waiter);
73699
+ });
73700
+ },
73701
+ dispose() {
73702
+ rl.off("line", onLine);
73703
+ rl.off("close", onClose);
73704
+ while (waiters.length > 0) {
73705
+ const waiter = waiters.shift();
73706
+ clearTimeout(waiter.timer);
73707
+ }
73708
+ }
73709
+ };
73710
+ }
73711
+ function readThreadIdFromResult(result) {
73712
+ if (!isRecord$3(result)) return null;
73713
+ const value = asString$3((isRecord$3(result.thread) ? result.thread : {}).id ?? result.threadId).trim();
73714
+ return value.length > 0 ? value : null;
73715
+ }
73716
+ function readTurnIdFromResult(result) {
73717
+ if (!isRecord$3(result)) return null;
73718
+ const value = asString$3((isRecord$3(result.turn) ? result.turn : {}).id ?? result.turnId).trim();
73719
+ return value.length > 0 ? value : null;
73720
+ }
73721
+ function readTurnIdFromNotification(message) {
73722
+ if (!isRecord$3(message.params)) return null;
73723
+ const value = asString$3((isRecord$3(message.params.turn) ? message.params.turn : {}).id ?? message.params.turnId).trim();
73724
+ return value.length > 0 ? value : null;
73725
+ }
73726
+ function readTurnStatusFromNotification(message) {
73727
+ if (!isRecord$3(message.params)) return {
73728
+ status: null,
73729
+ reason: null
73730
+ };
73731
+ const turn = isRecord$3(message.params.turn) ? message.params.turn : {};
73732
+ const error = isRecord$3(turn.error) ? turn.error : null;
73733
+ return {
73734
+ status: asString$3(turn.status).trim() || null,
73735
+ reason: error ? asString$3(error.message).trim() || null : null
73736
+ };
73737
+ }
73738
+ function readLegacyEventTurnId(message) {
73739
+ if (!isRecord$3(message.params)) return null;
73740
+ const msg = isRecord$3(message.params.msg) ? message.params.msg : {};
73741
+ const value = asString$3(message.params.id ?? msg.turn_id ?? msg.turnId).trim();
73742
+ return value.length > 0 ? value : null;
73743
+ }
73590
73744
  function parseTimestamp$1(value) {
73591
73745
  if (typeof value !== "string" || value.trim().length === 0) return null;
73592
73746
  const parsed = Date.parse(value);
@@ -73825,6 +73979,160 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
73825
73979
  }).catch(() => {});
73826
73980
  }
73827
73981
  }
73982
+ async function activateResetWindowViaRpc(envOverride, options = {}) {
73983
+ const binaryPath = options.codexPath ?? await requireCodexCli();
73984
+ const tempHome = await promises.mkdtemp(nodePath.join(nodeOs.tmpdir(), "codex-activation-"));
73985
+ const tempAuthPath = nodePath.join(tempHome, "auth.json");
73986
+ let initialSourceAuth = null;
73987
+ 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"));
73988
+ try {
73989
+ initialSourceAuth = await promises.readFile(sourceAuthPath, "utf8").catch(() => null);
73990
+ if (!initialSourceAuth) {
73991
+ await promises.rm(tempHome, {
73992
+ recursive: true,
73993
+ force: true
73994
+ }).catch(() => {});
73995
+ return {
73996
+ status: "skipped",
73997
+ reason: "auth-missing"
73998
+ };
73999
+ }
74000
+ await promises.writeFile(tempAuthPath, initialSourceAuth, "utf8");
74001
+ } catch {
74002
+ await promises.rm(tempHome, {
74003
+ recursive: true,
74004
+ force: true
74005
+ }).catch(() => {});
74006
+ return {
74007
+ status: "skipped",
74008
+ reason: "auth-missing"
74009
+ };
74010
+ }
74011
+ const childEnv = {
74012
+ ...process.env,
74013
+ ...envOverride ?? {},
74014
+ HOME: tempHome,
74015
+ USERPROFILE: tempHome,
74016
+ CODEX_HOME: tempHome,
74017
+ CODEX_TELEMETRY_LABEL: "codex-reset-activation",
74018
+ ELECTRON_RUN_AS_NODE: "1"
74019
+ };
74020
+ const command = buildCodexCommand$1(binaryPath, [
74021
+ "-s",
74022
+ "read-only",
74023
+ "-a",
74024
+ "untrusted",
74025
+ "app-server"
74026
+ ], childEnv);
74027
+ const child = spawn(command.command, command.args, {
74028
+ stdio: [
74029
+ "pipe",
74030
+ "pipe",
74031
+ "pipe"
74032
+ ],
74033
+ env: childEnv,
74034
+ shell: command.shell
74035
+ });
74036
+ const rl = readline.createInterface({
74037
+ input: child.stdout,
74038
+ crlfDelay: Infinity
74039
+ });
74040
+ const reader = createRpcMessageReader(rl);
74041
+ let stderrOutput = "";
74042
+ child.stderr?.on("data", (chunk) => {
74043
+ if (stderrOutput.length >= MAX_STDERR_CAPTURE_CHARS) return;
74044
+ const text = chunk.toString("utf8");
74045
+ const remaining = MAX_STDERR_CAPTURE_CHARS - stderrOutput.length;
74046
+ stderrOutput += text.slice(0, Math.max(0, remaining));
74047
+ });
74048
+ try {
74049
+ await sendPayload(child, {
74050
+ id: 1,
74051
+ method: "initialize",
74052
+ params: { clientInfo: {
74053
+ name: "codexuse",
74054
+ version: "0.0.0"
74055
+ } }
74056
+ });
74057
+ const initializeResponse = await reader.read((message) => isRpcResponseForRequest(message, 1), RPC_TIMEOUT_MS);
74058
+ if (initializeResponse.error) throw new Error(formatRpcError("initialize", initializeResponse.error));
74059
+ await sendPayload(child, {
74060
+ method: "initialized",
74061
+ params: {}
74062
+ });
74063
+ await sendPayload(child, {
74064
+ id: 2,
74065
+ method: "thread/start",
74066
+ params: {
74067
+ cwd: tempHome,
74068
+ model: ACTIVATION_MODEL,
74069
+ approvalPolicy: "untrusted",
74070
+ sandbox: "read-only",
74071
+ experimentalRawEvents: false
74072
+ }
74073
+ });
74074
+ const threadResponse = await reader.read((message) => isRpcResponseForRequest(message, 2), RPC_TIMEOUT_MS);
74075
+ if (threadResponse.error) throw new Error(formatRpcError("thread/start", threadResponse.error));
74076
+ const threadId = readThreadIdFromResult(threadResponse.result);
74077
+ if (!threadId) throw new Error("thread/start response did not include a thread id.");
74078
+ await sendPayload(child, {
74079
+ id: 3,
74080
+ method: "turn/start",
74081
+ params: {
74082
+ threadId,
74083
+ model: ACTIVATION_MODEL,
74084
+ effort: "low",
74085
+ input: [{
74086
+ type: "text",
74087
+ text: ACTIVATION_PROMPT,
74088
+ text_elements: []
74089
+ }]
74090
+ }
74091
+ });
74092
+ const turnResponse = await reader.read((message) => isRpcResponseForRequest(message, 3), RPC_TIMEOUT_MS);
74093
+ if (turnResponse.error) {
74094
+ const base = formatRpcError("turn/start", turnResponse.error);
74095
+ const hint = inferRefreshFailureHint(stderrOutput);
74096
+ if (hint && !base.toLowerCase().includes(hint)) throw new Error(`${base}; ${hint}`);
74097
+ throw new Error(base);
74098
+ }
74099
+ const turnId = readTurnIdFromResult(turnResponse.result);
74100
+ if (!turnId) throw new Error("turn/start response did not include a turn id.");
74101
+ const turnStatus = readTurnStatusFromNotification(await reader.read((message) => message.method === "turn/completed" && readTurnIdFromNotification(message) === turnId || message.method === "codex/event/task_complete" && readLegacyEventTurnId(message) === turnId, ACTIVATION_TURN_TIMEOUT_MS));
74102
+ if (turnStatus.status === "failed") return {
74103
+ status: "failed",
74104
+ reason: turnStatus.reason ?? "turn-failed",
74105
+ threadId,
74106
+ turnId
74107
+ };
74108
+ return {
74109
+ status: "completed",
74110
+ reason: null,
74111
+ threadId,
74112
+ turnId
74113
+ };
74114
+ } finally {
74115
+ child.kill();
74116
+ reader.dispose();
74117
+ rl.close();
74118
+ try {
74119
+ const updatedAuth = await promises.readFile(tempAuthPath, "utf8");
74120
+ if (updatedAuth.trim().length > 0) {
74121
+ const currentSourceAuth = await promises.readFile(sourceAuthPath, "utf8").catch(() => null);
74122
+ if (shouldWriteBackAuth(initialSourceAuth, currentSourceAuth, updatedAuth)) await promises.writeFile(sourceAuthPath, updatedAuth, "utf8");
74123
+ else if (currentSourceAuth && currentSourceAuth !== updatedAuth && currentSourceAuth !== initialSourceAuth) logWarn("Skipped stale auth sync-back after reset-window activation; source auth changed in flight.", {
74124
+ sourceAuthPath,
74125
+ currentRecencyMs: extractAuthRecencyMs(currentSourceAuth),
74126
+ updatedRecencyMs: extractAuthRecencyMs(updatedAuth)
74127
+ });
74128
+ }
74129
+ } catch {}
74130
+ await promises.rm(tempHome, {
74131
+ recursive: true,
74132
+ force: true
74133
+ }).catch(() => {});
74134
+ }
74135
+ }
73828
74136
  //#endregion
73829
74137
  //#region ../../packages/runtime-profiles/src/profiles/profile-manager.ts
73830
74138
  const TOKEN_EXPIRING_SOON_WINDOW_MS = 900 * 1e3;
@@ -74725,11 +75033,11 @@ var ProfileManager = class {
74725
75033
  const existingIdentity = this.resolveProfileIdentityFromData(profileName, existing, existingMetadata);
74726
75034
  const nextIdentity = this.resolveProfileIdentityFromData(profileName, normalized, metadata);
74727
75035
  if (existingIdentity && nextIdentity && existingIdentity !== nextIdentity) {
74728
- logWarn(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`);
75036
+ logInfo(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`);
74729
75037
  return;
74730
75038
  }
74731
75039
  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.`);
75040
+ logInfo(`Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different workspace.`);
74733
75041
  return;
74734
75042
  }
74735
75043
  if (!this.hasTokenChanges(existing, normalized)) return;
@@ -75058,6 +75366,18 @@ var ProfileManager = class {
75058
75366
  });
75059
75367
  }
75060
75368
  }
75369
+ async activateResetWindow(name, options = {}) {
75370
+ const profileName = this.normalizeProfileName(name);
75371
+ try {
75372
+ return await this.enqueueProfileOperation(profileName, () => this.runWithPreparedProfileHome(profileName, (env) => activateResetWindowViaRpc(env, { codexPath: options.codexPath }), { syncFromActiveAuthBeforeAction: false }));
75373
+ } finally {
75374
+ await this.enqueueAuthSwap(async () => {
75375
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
75376
+ }).catch((error) => {
75377
+ logWarn(`Failed to sync active auth after reset-window activation for '${profileName}':`, error);
75378
+ });
75379
+ }
75380
+ }
75061
75381
  /**
75062
75382
  * Rename a profile
75063
75383
  */
@@ -82274,15 +82594,13 @@ function createTelegramBridge(options) {
82274
82594
  }
82275
82595
  //#endregion
82276
82596
  //#region ../../packages/runtime-codex/src/codex/officialAppRestart.ts
82277
- const RESTART_COOLDOWN_MS = 6e4;
82278
82597
  const COMMAND_TIMEOUT_MS = 3e3;
82279
82598
  const EXIT_WAIT_MS = 1e4;
82280
82599
  const LAUNCH_WAIT_MS = 15e3;
82281
82600
  const POLL_INTERVAL_MS = 250;
82282
82601
  const APP_DISCOVERY_CACHE_TTL_MS = 6e4;
82283
82602
  const APP_DISCOVERY_MISS_CACHE_TTL_MS = 5e3;
82284
- let restartPromise = null;
82285
- let lastRestartStartedAt = 0;
82603
+ const OFFICIAL_CODEX_ACTIVITY_LIMIT = 50;
82286
82604
  let profileActionLock = Promise.resolve();
82287
82605
  let appDiscoveryCache = null;
82288
82606
  function logOfficialCodexRestart(level, message, context) {
@@ -82293,6 +82611,47 @@ function logOfficialCodexRestart(level, message, context) {
82293
82611
  }
82294
82612
  logger(`[official-codex-restart] ${message}`);
82295
82613
  }
82614
+ function hashProfileKey(profileKey) {
82615
+ if (!profileKey) return null;
82616
+ return createHash("sha256").update(profileKey).digest("hex").slice(0, 12);
82617
+ }
82618
+ async function recordOfficialCodexActivity(input) {
82619
+ const now = /* @__PURE__ */ new Date();
82620
+ const entry = {
82621
+ id: `${now.getTime().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
82622
+ at: now.toISOString(),
82623
+ kind: input.kind,
82624
+ status: input.status,
82625
+ reason: input.reason ?? null,
82626
+ decisionId: input.decisionId ?? null,
82627
+ profileName: input.profileName ?? null,
82628
+ sourceProfileName: input.sourceProfileName ?? null,
82629
+ targetProfileName: input.targetProfileName ?? null,
82630
+ remainingPercent: input.remainingPercent ?? null,
82631
+ threshold: input.threshold ?? null,
82632
+ snapshotAgeMs: input.snapshotAgeMs ?? null,
82633
+ snapshotSource: input.snapshotSource ?? null,
82634
+ phase: input.phase ?? null,
82635
+ pid: input.pid ?? null,
82636
+ profileKeyHash: input.profileKeyHash ?? hashProfileKey(input.profileKey),
82637
+ switchVerified: input.switchVerified ?? null,
82638
+ restartRequested: input.restartRequested ?? null,
82639
+ restartResult: input.restartResult ?? null,
82640
+ observedProfileName: input.observedProfileName ?? null,
82641
+ observedProfileKeyHash: input.observedProfileKeyHash ?? hashProfileKey(input.observedProfileKey),
82642
+ observedProfileMatchSource: input.observedProfileMatchSource ?? null
82643
+ };
82644
+ try {
82645
+ await updateAppState((state) => ({ officialCodex: { activity: [...state.officialCodex.activity, entry].slice(-OFFICIAL_CODEX_ACTIVITY_LIMIT) } }));
82646
+ } catch (error) {
82647
+ logOfficialCodexRestart("warn", "Failed to record official Codex activity.", {
82648
+ kind: entry.kind,
82649
+ status: entry.status,
82650
+ profileKeyHash: entry.profileKeyHash,
82651
+ error: error instanceof Error ? error.message : String(error)
82652
+ });
82653
+ }
82654
+ }
82296
82655
  function runCommand(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
82297
82656
  return new Promise((resolve) => {
82298
82657
  const child = spawn(command, args, { stdio: [
@@ -82670,14 +83029,6 @@ async function resolveCodexAppTarget() {
82670
83029
  mainPids
82671
83030
  };
82672
83031
  }
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
83032
  async function signalPids(pids, signal) {
82682
83033
  const signaled = [];
82683
83034
  for (const pid of pids) try {
@@ -82686,19 +83037,6 @@ async function signalPids(pids, signal) {
82686
83037
  } catch {}
82687
83038
  return signaled;
82688
83039
  }
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
83040
  function toIso(value) {
82703
83041
  return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : null;
82704
83042
  }
@@ -82719,152 +83057,15 @@ async function rememberRestartResult(args) {
82719
83057
  lastRestartReason: args.reason ?? null
82720
83058
  } });
82721
83059
  }
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
- };
83060
+ async function recordOfficialCodexProfileObservation(profileKey, pid) {
83061
+ if (!profileKey || !pid) return;
83062
+ await patchAppState({ officialCodex: {
83063
+ lastVerifiedLaunchProfileKey: profileKey,
83064
+ lastObservedPid: pid
83065
+ } });
83066
+ }
83067
+ function recordOfficialCodexRestartResult(args) {
83068
+ return rememberRestartResult(args);
82868
83069
  }
82869
83070
  async function getOfficialCodexSyncStatus(currentProfileKey) {
82870
83071
  const stored = (await getAppState()).officialCodex;
@@ -82873,7 +83074,8 @@ async function getOfficialCodexSyncStatus(currentProfileKey) {
82873
83074
  lastProfileSwitchAt: toIso(stored.lastProfileSwitchAt),
82874
83075
  lastVerifiedLaunchAt: toIso(stored.lastVerifiedLaunchAt),
82875
83076
  lastRestartStatus: stored.lastRestartStatus,
82876
- lastRestartReason: stored.lastRestartReason
83077
+ lastRestartReason: stored.lastRestartReason,
83078
+ activity: stored.activity
82877
83079
  };
82878
83080
  if (process.platform !== "darwin") return {
82879
83081
  ...base,
@@ -82894,10 +83096,12 @@ async function getOfficialCodexSyncStatus(currentProfileKey) {
82894
83096
  const lastSwitchAt = stored.lastProfileSwitchAt;
82895
83097
  const lastLaunchAt = stored.lastVerifiedLaunchAt;
82896
83098
  const lastLaunchProfileKey = stored.lastVerifiedLaunchProfileKey;
82897
- const stale = Boolean(currentProfileKey && lastSwitchAt && (!lastLaunchAt || lastLaunchAt < lastSwitchAt || lastLaunchProfileKey !== currentProfileKey));
83099
+ const lastObservedPid = stored.lastObservedPid;
83100
+ const observedSynced = Boolean(currentProfileKey && lastLaunchProfileKey === currentProfileKey && lastObservedPid && mainPids.includes(lastObservedPid));
83101
+ const stale = Boolean(!observedSynced && currentProfileKey && lastSwitchAt && (!lastLaunchAt || lastLaunchAt < lastSwitchAt || lastLaunchProfileKey !== currentProfileKey));
82898
83102
  return {
82899
83103
  ...base,
82900
- state: stale ? "stale" : currentProfileKey && lastLaunchProfileKey === currentProfileKey ? "synced" : "unknown",
83104
+ state: observedSynced ? "synced" : stale ? "stale" : "unknown",
82901
83105
  appPath: candidate.appPath,
82902
83106
  bundleId: candidate.bundleId,
82903
83107
  runningPids: mainPids
@@ -82958,210 +83162,212 @@ async function getOfficialCodexProfileInstances() {
82958
83162
  };
82959
83163
  }
82960
83164
  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,
83165
+ return enqueueProfileAction(() => launchOfficialCodexProfileInstanceOnce(options));
83166
+ }
83167
+ async function launchOfficialCodexProfileInstanceOnce(options) {
83168
+ if (process.platform !== "darwin") return {
83169
+ status: "skipped",
83170
+ profileName: options.profileName,
83171
+ instance: null,
83172
+ reason: "unsupported-platform"
83173
+ };
83174
+ const { candidate, mainPids } = await resolveCodexAppTarget();
83175
+ if (!candidate) return {
83176
+ status: "skipped",
83177
+ profileName: options.profileName,
83178
+ instance: null,
83179
+ reason: "official-codex-app-not-found"
83180
+ };
83181
+ const existing = await readManagedInstance(options.profileName);
83182
+ if (existing) {
83183
+ const runtime = await resolveInstanceRuntimeState({
83184
+ instance: existing,
83185
+ candidate,
83186
+ rows: await readProcessRows()
83187
+ });
83188
+ if (runtime.running) {
83189
+ const verified = {
83190
+ ...existing,
83191
+ profileHome: existing.profileHome ?? options.profileHome,
83192
+ appServerPid: runtime.appServerPid,
83057
83193
  lastVerifiedAt: Date.now(),
83058
- lastStatus: "failed",
83059
- lastError: "app-server-not-verified"
83194
+ lastStatus: "already-running",
83195
+ lastError: null
83060
83196
  };
83061
- await patchManagedInstance(failed);
83197
+ await patchManagedInstance(verified);
83062
83198
  return {
83063
- status: "failed",
83199
+ status: "already-running",
83064
83200
  profileName: options.profileName,
83065
- instance: managedInstanceToPayload(failed, false),
83066
- reason: "app-server-not-verified"
83201
+ instance: managedInstanceToPayload(verified, true, runtime.appServerPid, runtime.startedAt),
83202
+ reason: null
83067
83203
  };
83068
83204
  }
83069
- const now = Date.now();
83070
- const instance = {
83205
+ }
83206
+ const launch = await openCodexWithProfileHome(candidate, options.profileHome, options.profileName, new Set(mainPids));
83207
+ if (!launch.opened) {
83208
+ const failed = {
83071
83209
  profileName: options.profileName,
83072
83210
  profileKey: options.profileKey,
83073
83211
  profileHome: options.profileHome,
83074
83212
  appPath: candidate.appPath,
83075
83213
  bundleId: candidate.bundleId,
83076
- pid: launchedPid,
83077
- appServerPid,
83078
- launchedAt: now,
83079
- lastVerifiedAt: now,
83080
- lastStatus: "started",
83081
- lastError: null
83214
+ pid: null,
83215
+ appServerPid: null,
83216
+ launchedAt: null,
83217
+ lastVerifiedAt: null,
83218
+ lastStatus: "failed",
83219
+ lastError: "open-failed"
83082
83220
  };
83083
- await patchManagedInstance(instance);
83221
+ await patchManagedInstance(failed);
83084
83222
  return {
83085
- status: "started",
83223
+ status: "failed",
83086
83224
  profileName: options.profileName,
83087
- instance: managedInstanceToPayload(instance, true, appServerPid),
83088
- reason: null
83225
+ instance: managedInstanceToPayload(failed, false),
83226
+ reason: "open-failed"
83089
83227
  };
83090
- });
83228
+ }
83229
+ const launchedPid = launch.pid;
83230
+ if (launchedPid === null) {
83231
+ const failed = {
83232
+ profileName: options.profileName,
83233
+ profileKey: options.profileKey,
83234
+ profileHome: options.profileHome,
83235
+ appPath: candidate.appPath,
83236
+ bundleId: candidate.bundleId,
83237
+ pid: null,
83238
+ appServerPid: null,
83239
+ launchedAt: null,
83240
+ lastVerifiedAt: null,
83241
+ lastStatus: "failed",
83242
+ lastError: "launch-timeout"
83243
+ };
83244
+ await patchManagedInstance(failed);
83245
+ return {
83246
+ status: "failed",
83247
+ profileName: options.profileName,
83248
+ instance: managedInstanceToPayload(failed, false),
83249
+ reason: "launch-timeout"
83250
+ };
83251
+ }
83252
+ const appServerPid = await waitForAppServerPid(launchedPid, options.profileHome);
83253
+ if (appServerPid === null) {
83254
+ const failed = {
83255
+ profileName: options.profileName,
83256
+ profileKey: options.profileKey,
83257
+ profileHome: options.profileHome,
83258
+ appPath: candidate.appPath,
83259
+ bundleId: candidate.bundleId,
83260
+ pid: null,
83261
+ appServerPid: null,
83262
+ launchedAt: null,
83263
+ lastVerifiedAt: Date.now(),
83264
+ lastStatus: "failed",
83265
+ lastError: "app-server-not-verified"
83266
+ };
83267
+ await patchManagedInstance(failed);
83268
+ return {
83269
+ status: "failed",
83270
+ profileName: options.profileName,
83271
+ instance: managedInstanceToPayload(failed, false),
83272
+ reason: "app-server-not-verified"
83273
+ };
83274
+ }
83275
+ const now = Date.now();
83276
+ const instance = {
83277
+ profileName: options.profileName,
83278
+ profileKey: options.profileKey,
83279
+ profileHome: options.profileHome,
83280
+ appPath: candidate.appPath,
83281
+ bundleId: candidate.bundleId,
83282
+ pid: launchedPid,
83283
+ appServerPid,
83284
+ launchedAt: now,
83285
+ lastVerifiedAt: now,
83286
+ lastStatus: "started",
83287
+ lastError: null
83288
+ };
83289
+ await patchManagedInstance(instance);
83290
+ return {
83291
+ status: "started",
83292
+ profileName: options.profileName,
83293
+ instance: managedInstanceToPayload(instance, true, appServerPid),
83294
+ reason: null
83295
+ };
83091
83296
  }
83092
83297
  function stopOfficialCodexProfileInstance(profileName) {
83093
- return enqueueProfileAction(async () => {
83094
- const existing = await readManagedInstance(profileName);
83095
- if (!existing) return {
83298
+ return enqueueProfileAction(() => stopOfficialCodexProfileInstanceOnce(profileName));
83299
+ }
83300
+ async function stopOfficialCodexProfileInstanceOnce(profileName) {
83301
+ const existing = await readManagedInstance(profileName);
83302
+ if (!existing) return {
83303
+ status: "not-running",
83304
+ profileName,
83305
+ instance: null,
83306
+ reason: "not-managed"
83307
+ };
83308
+ const { candidate } = await resolveCodexAppTarget();
83309
+ if (!candidate) {
83310
+ const stopped = {
83311
+ ...existing,
83312
+ pid: null,
83313
+ appServerPid: null,
83314
+ lastVerifiedAt: Date.now(),
83315
+ lastStatus: "not-running",
83316
+ lastError: "official-codex-app-not-found"
83317
+ };
83318
+ await patchManagedInstance(stopped);
83319
+ return {
83096
83320
  status: "not-running",
83097
83321
  profileName,
83098
- instance: null,
83099
- reason: "not-managed"
83322
+ instance: managedInstanceToPayload(stopped, false),
83323
+ reason: "official-codex-app-not-found"
83100
83324
  };
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);
83325
+ }
83326
+ const rows = await readProcessRows();
83327
+ const runtime = await resolveInstanceRuntimeState({
83328
+ instance: existing,
83329
+ candidate,
83330
+ rows
83331
+ });
83332
+ if (!runtime.running || !existing.pid) {
83149
83333
  const stopped = {
83150
83334
  ...existing,
83151
- pid: stillRunning ? existing.pid : null,
83152
- appServerPid: stillRunning ? runtime.appServerPid : null,
83335
+ pid: null,
83336
+ appServerPid: null,
83153
83337
  lastVerifiedAt: Date.now(),
83154
- lastStatus: stillRunning ? "failed" : "stopped",
83155
- lastError: stillRunning ? "stop-timeout" : null
83338
+ lastStatus: "not-running",
83339
+ lastError: null
83156
83340
  };
83157
83341
  await patchManagedInstance(stopped);
83158
83342
  return {
83159
- status: stillRunning ? "failed" : "stopped",
83343
+ status: "not-running",
83160
83344
  profileName,
83161
- instance: managedInstanceToPayload(stopped, stillRunning, stopped.appServerPid, runtime.startedAt),
83162
- reason: stillRunning ? "stop-timeout" : null
83345
+ instance: managedInstanceToPayload(stopped, false),
83346
+ reason: null
83163
83347
  };
83164
- });
83348
+ }
83349
+ const tree = [existing.pid, ...getDescendantPids(existing.pid, rows)].filter((pid, index, all) => all.indexOf(pid) === index).sort((a, b) => b - a);
83350
+ await signalPids(tree, "SIGTERM");
83351
+ if (!await waitForMainPidExit(existing.pid, 5e3)) {
83352
+ await signalPids(tree, "SIGKILL");
83353
+ await waitForMainPidExit(existing.pid, EXIT_WAIT_MS);
83354
+ }
83355
+ const stillRunning = (await readProcessRows()).some((row) => row.pid === existing.pid);
83356
+ const stopped = {
83357
+ ...existing,
83358
+ pid: stillRunning ? existing.pid : null,
83359
+ appServerPid: stillRunning ? runtime.appServerPid : null,
83360
+ lastVerifiedAt: Date.now(),
83361
+ lastStatus: stillRunning ? "failed" : "stopped",
83362
+ lastError: stillRunning ? "stop-timeout" : null
83363
+ };
83364
+ await patchManagedInstance(stopped);
83365
+ return {
83366
+ status: stillRunning ? "failed" : "stopped",
83367
+ profileName,
83368
+ instance: managedInstanceToPayload(stopped, stillRunning, stopped.appServerPid, runtime.startedAt),
83369
+ reason: stillRunning ? "stop-timeout" : null
83370
+ };
83165
83371
  }
83166
83372
  function focusOfficialCodexProfileInstance(profileName) {
83167
83373
  return enqueueProfileAction(async () => {
@@ -83298,24 +83504,15 @@ function stopOfficialCodexObservedProfileInstance(profileName, pid, appServerPid
83298
83504
  });
83299
83505
  }
83300
83506
  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 = {}) {
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;
83507
+ return enqueueProfileAction(async () => {
83508
+ const stopped = await stopOfficialCodexProfileInstanceOnce(options.profileName);
83509
+ if (stopped.status === "failed") return stopped;
83510
+ const launched = await launchOfficialCodexProfileInstanceOnce(options);
83511
+ return {
83512
+ ...launched,
83513
+ status: launched.status === "started" ? "restarted" : launched.status
83514
+ };
83317
83515
  });
83318
- return restartPromise;
83319
83516
  }
83320
83517
  //#endregion
83321
83518
  //#region src/wsServer.ts
@@ -84170,6 +84367,57 @@ const createServer = fn(function* () {
84170
84367
  };
84171
84368
  const lowRemainingNotifications = /* @__PURE__ */ new Map();
84172
84369
  const finitePercent = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : null;
84370
+ const AUTO_ROLL_SNAPSHOT_MAX_AGE_MS = 3 * 6e4;
84371
+ const AUTO_ROLL_WATCHDOG_INTERVAL_MS = 6e4;
84372
+ const AUTO_ROLL_WATCHDOG_ACTIVITY_TTL_MS = 10 * 6e4;
84373
+ const parseRateLimitTimestamp = (value) => {
84374
+ if (!value) return null;
84375
+ const parsed = Date.parse(value);
84376
+ return Number.isFinite(parsed) ? parsed : null;
84377
+ };
84378
+ const createAutoRollDecisionId = () => `ar-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
84379
+ const buildSnapshotDiagnostic = (cached, profileSnapshot, fallbackSource = "none") => {
84380
+ const candidates = [{
84381
+ snapshot: cached ?? null,
84382
+ source: "cache",
84383
+ updatedAt: parseRateLimitTimestamp(cached?.lastUpdated)
84384
+ }, {
84385
+ snapshot: profileSnapshot ?? null,
84386
+ source: "profile",
84387
+ updatedAt: parseRateLimitTimestamp(profileSnapshot?.lastUpdated)
84388
+ }].filter((entry) => Boolean(entry.snapshot));
84389
+ candidates.sort((a, b) => (b.updatedAt ?? -Infinity) - (a.updatedAt ?? -Infinity));
84390
+ const selected = candidates[0] ?? null;
84391
+ const snapshot = selected?.snapshot ?? null;
84392
+ const source = selected?.source ?? fallbackSource;
84393
+ const updatedAt = parseRateLimitTimestamp(snapshot?.lastUpdated);
84394
+ return {
84395
+ snapshot,
84396
+ source,
84397
+ ageMs: updatedAt === null ? null : Math.max(0, Date.now() - updatedAt)
84398
+ };
84399
+ };
84400
+ const freshAutoRollSnapshot = (snapshot) => {
84401
+ if (!snapshot) return null;
84402
+ const updatedAt = parseRateLimitTimestamp(snapshot.lastUpdated);
84403
+ if (updatedAt === null || Date.now() - updatedAt >= AUTO_ROLL_SNAPSHOT_MAX_AGE_MS) return null;
84404
+ const now = Date.now();
84405
+ const keepWindow = (window) => {
84406
+ if (!window || finitePercent(window.usedPercent) === null) return window;
84407
+ const resetsAt = parseRateLimitTimestamp(window.resetsAt);
84408
+ if (resetsAt !== null && resetsAt <= now) return;
84409
+ return window;
84410
+ };
84411
+ return {
84412
+ ...snapshot,
84413
+ primary: keepWindow(snapshot.primary),
84414
+ secondary: keepWindow(snapshot.secondary)
84415
+ };
84416
+ };
84417
+ const isSnapshotStaleForAutoRollWatchdog = (snapshot) => {
84418
+ const updatedAt = parseRateLimitTimestamp(snapshot?.lastUpdated);
84419
+ return updatedAt === null || Date.now() - updatedAt >= AUTO_ROLL_SNAPSHOT_MAX_AGE_MS;
84420
+ };
84173
84421
  const formatLowRemainingPercent = (value) => {
84174
84422
  const rounded = Math.round(value * 10) / 10;
84175
84423
  return (Number.isInteger(rounded) ? rounded.toFixed(0) : rounded.toFixed(1)).replace(/\.0$/, "");
@@ -84221,6 +84469,17 @@ const createServer = fn(function* () {
84221
84469
  remainingPercent,
84222
84470
  threshold: settings.lowRemainingNotificationThreshold
84223
84471
  });
84472
+ const snapshotUpdatedAt = parseRateLimitTimestamp(payload.snapshot.lastUpdated);
84473
+ recordAutoRollActivitySafe({
84474
+ kind: "low-remaining-alert",
84475
+ status: "sent",
84476
+ profileName: payload.profileName,
84477
+ remainingPercent,
84478
+ threshold: settings.lowRemainingNotificationThreshold,
84479
+ snapshotAgeMs: snapshotUpdatedAt === null ? null : Math.max(0, Date.now() - snapshotUpdatedAt),
84480
+ snapshotSource: "event",
84481
+ profileKey: payload.accountId
84482
+ });
84224
84483
  }
84225
84484
  };
84226
84485
  const publishProfileSwitched = (payload) => {
@@ -84231,6 +84490,7 @@ const createServer = fn(function* () {
84231
84490
  let backgroundAutoRollInFlight = false;
84232
84491
  let lastAutoRollAttempt = null;
84233
84492
  const autoRollRearmBlockedProfiles = /* @__PURE__ */ new Set();
84493
+ const lastAutoRollWatchdogActivityAt = /* @__PURE__ */ new Map();
84234
84494
  const rememberAutoRollAttempt = (source, target) => {
84235
84495
  if (!source || !target) return;
84236
84496
  lastAutoRollAttempt = {
@@ -84270,6 +84530,19 @@ const createServer = fn(function* () {
84270
84530
  const profile = (await profileManager.listProfiles()).find((entry) => entry.name === profileName);
84271
84531
  return profile ? resolveProfileIdentityKeyFromProfile(profile) : null;
84272
84532
  };
84533
+ const hashIdentityKey = (key) => key ? createHash("sha256").update(key).digest("hex").slice(0, 12) : null;
84534
+ const observedOfficialCodexProfileMatches = (instance, targetProfileName, targetProfileKey) => {
84535
+ if (targetProfileKey && instance.profileKey) return instance.profileKey === targetProfileKey;
84536
+ if (instance.profileKey) return false;
84537
+ return Boolean(targetProfileName && instance.profileName === targetProfileName);
84538
+ };
84539
+ const recordAutoRollActivitySafe = async (input) => {
84540
+ try {
84541
+ await recordOfficialCodexActivity(input);
84542
+ } catch (error) {
84543
+ logger.warn("Failed to record auto-roll activity", { error });
84544
+ }
84545
+ };
84273
84546
  const prepareOfficialCodexProfileLaunch = async (profileName) => {
84274
84547
  const profile = (await profileManager.listProfiles()).find((entry) => entry.name === profileName);
84275
84548
  if (!profile) throw new Error(`Profile '${profileName}' was not found.`);
@@ -84332,17 +84605,68 @@ const createServer = fn(function* () {
84332
84605
  }
84333
84606
  return null;
84334
84607
  };
84608
+ const mapOfficialCodexAppAccountToProfile = async () => {
84609
+ const scopePath = nodePath.join(nodeOs.homedir(), "Library", "Application Support", "Codex", "sentry", "scope_v3.json");
84610
+ let parsed;
84611
+ try {
84612
+ parsed = JSON.parse(await promises.readFile(scopePath, "utf8"));
84613
+ } catch {
84614
+ return null;
84615
+ }
84616
+ const scope = parsed && typeof parsed === "object" ? parsed.scope : null;
84617
+ const user = scope && typeof scope === "object" ? scope.user : null;
84618
+ if (!user || typeof user !== "object") return null;
84619
+ const userRecord = user;
84620
+ const ids = new Set([userRecord.account_id, userRecord.id].filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean));
84621
+ if (ids.size === 0) return null;
84622
+ const profiles = await profileManager.listProfiles();
84623
+ for (const profile of profiles) if ([
84624
+ profile.accountId,
84625
+ profile.metadata?.chatgptAccountUserId,
84626
+ profile.metadata?.chatgptUserId,
84627
+ profile.metadata?.userId,
84628
+ resolveProfileIdentityKeyFromProfile(profile)
84629
+ ].filter((value) => typeof value === "string" && value.length > 0).some((value) => ids.has(value))) return {
84630
+ name: profile.name,
84631
+ key: resolveProfileIdentityKeyFromProfile(profile)
84632
+ };
84633
+ return null;
84634
+ };
84335
84635
  const enrichOfficialCodexInstances = async (payload) => {
84336
84636
  if (payload.unmanaged.length === 0) return payload;
84337
- const defaultProfile = await mapDefaultCodexAuthToProfile();
84338
- if (!defaultProfile) return payload;
84637
+ const hasManagedRunning = payload.instances.some((instance) => instance.running);
84638
+ const [defaultProfile, appAccountProfile, appState] = await Promise.all([
84639
+ mapDefaultCodexAuthToProfile(),
84640
+ !hasManagedRunning && payload.unmanaged.length === 1 && payload.unmanaged[0]?.appServerPid ? mapOfficialCodexAppAccountToProfile() : Promise.resolve(null),
84641
+ getAppState()
84642
+ ]);
84643
+ const lastProfileSwitchAt = appState.officialCodex.lastProfileSwitchAt;
84644
+ const lastObservedPid = appState.officialCodex.lastObservedPid;
84645
+ const lastVerifiedLaunchProfileKey = appState.officialCodex.lastVerifiedLaunchProfileKey;
84646
+ const isFreshDefaultAuthObservation = (instance) => {
84647
+ if (!defaultProfile) return false;
84648
+ if (lastObservedPid && instance.pid === lastObservedPid && lastVerifiedLaunchProfileKey === defaultProfile.key) return true;
84649
+ if (!lastProfileSwitchAt) return true;
84650
+ const startedAt = parseRateLimitTimestamp(instance.startedAt);
84651
+ return startedAt !== null && startedAt >= lastProfileSwitchAt - 1e3;
84652
+ };
84339
84653
  return {
84340
84654
  ...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"
84655
+ unmanaged: payload.unmanaged.map((instance) => {
84656
+ if (instance.profileName || !instance.appServerPid) return instance;
84657
+ if (appAccountProfile) return {
84658
+ ...instance,
84659
+ profileName: appAccountProfile.name,
84660
+ profileKey: appAccountProfile.key,
84661
+ profileMatchSource: "app-account"
84662
+ };
84663
+ if (defaultProfile && isFreshDefaultAuthObservation(instance)) return {
84664
+ ...instance,
84665
+ profileName: defaultProfile.name,
84666
+ profileKey: defaultProfile.key,
84667
+ profileMatchSource: "default-auth"
84668
+ };
84669
+ return instance;
84346
84670
  })
84347
84671
  };
84348
84672
  };
@@ -84356,6 +84680,62 @@ const createServer = fn(function* () {
84356
84680
  bundleId: instances.bundleId
84357
84681
  } : null;
84358
84682
  };
84683
+ const resolveOfficialCodexStatus = async (currentProfile) => {
84684
+ const currentProfileKey = currentProfile ? resolveProfileIdentityKeyFromProfile(currentProfile) : null;
84685
+ const base = await getOfficialCodexSyncStatus(currentProfileKey);
84686
+ if (base.state === "unsupported" || base.state === "not-found" || base.state === "not-running") return base;
84687
+ try {
84688
+ const instances = await enrichOfficialCodexInstances(await getOfficialCodexProfileInstances());
84689
+ const managed = instances.instances.filter((instance) => instance.running).map((instance) => ({
84690
+ profileName: instance.profileName,
84691
+ profileKey: instance.profileKey,
84692
+ pid: instance.pid,
84693
+ source: "managed"
84694
+ }));
84695
+ const unmanaged = instances.unmanaged.map((instance) => ({
84696
+ profileName: instance.profileName ?? null,
84697
+ profileKey: instance.profileKey ?? null,
84698
+ pid: instance.pid,
84699
+ source: instance.profileMatchSource ?? null
84700
+ }));
84701
+ const observedCandidates = unmanaged;
84702
+ const observed = observedCandidates.find((instance) => observedOfficialCodexProfileMatches(instance, currentProfile?.name ?? null, currentProfileKey)) ?? observedCandidates.find((instance) => instance.profileName || instance.profileKey) ?? null;
84703
+ if (!observed) {
84704
+ if (managed.length > 0 && unmanaged.length === 0) return {
84705
+ ...base,
84706
+ state: "not-running",
84707
+ runningPids: [],
84708
+ observedProfileName: null,
84709
+ observedProfileKeyHash: null,
84710
+ observedProfileMatchSource: null
84711
+ };
84712
+ if (base.state === "stale") return {
84713
+ ...base,
84714
+ observedProfileName: null,
84715
+ observedProfileKeyHash: null,
84716
+ observedProfileMatchSource: null
84717
+ };
84718
+ return base;
84719
+ }
84720
+ const matched = Boolean(observedOfficialCodexProfileMatches(observed, currentProfile?.name ?? null, currentProfileKey));
84721
+ if (matched && currentProfileKey && observed.pid) try {
84722
+ const stored = (await getAppState()).officialCodex;
84723
+ if (stored.lastVerifiedLaunchProfileKey !== currentProfileKey || stored.lastObservedPid !== observed.pid) await recordOfficialCodexProfileObservation(currentProfileKey, observed.pid);
84724
+ } catch (error) {
84725
+ logger.warn("Failed to record observed Official Codex profile", { error });
84726
+ }
84727
+ return {
84728
+ ...base,
84729
+ state: currentProfile ? matched ? "synced" : "stale" : base.state,
84730
+ observedProfileName: observed.profileName,
84731
+ observedProfileKeyHash: hashIdentityKey(observed.profileKey),
84732
+ observedProfileMatchSource: observed.source
84733
+ };
84734
+ } catch (error) {
84735
+ logger.warn("Failed to resolve observed Official Codex profile", { error });
84736
+ return base;
84737
+ }
84738
+ };
84359
84739
  const resolveAutoRollRearmActionAfterManualSwitch = async (profileName) => {
84360
84740
  const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
84361
84741
  if (!settings.enabled) return "none";
@@ -84366,30 +84746,471 @@ const createServer = fn(function* () {
84366
84746
  if (!summary.hasUsage) return "unblock";
84367
84747
  return summary.remainingPercent <= settings.switchRemainingThreshold ? "block" : "unblock";
84368
84748
  };
84369
- const maybeRestartOfficialCodexAfterAutoRoll = async (targetProfileName, targetProfileKey) => {
84749
+ const OFFICIAL_CODEX_RESTART_DEBT_MAX_ATTEMPTS = 5;
84750
+ const OFFICIAL_CODEX_RESTART_RETRY_DELAY_MS = 61e3;
84751
+ const OFFICIAL_CODEX_POST_RESTART_VERIFY_TIMEOUT_MS = 15e3;
84752
+ const OFFICIAL_CODEX_POST_RESTART_VERIFY_INTERVAL_MS = 750;
84753
+ let officialCodexRestartRetryTimer = null;
84754
+ let officialCodexRestartDebt = null;
84755
+ const persistOfficialCodexRestartDebt = async (debt) => {
84756
+ await patchAppState({ officialCodex: { pendingRestartDebt: debt } });
84757
+ };
84758
+ const clearOfficialCodexRestartDebtLocal = () => {
84759
+ officialCodexRestartDebt = null;
84760
+ if (officialCodexRestartRetryTimer) {
84761
+ clearTimeout(officialCodexRestartRetryTimer);
84762
+ officialCodexRestartRetryTimer = null;
84763
+ }
84764
+ };
84765
+ const clearOfficialCodexRestartDebt = async () => {
84766
+ clearOfficialCodexRestartDebtLocal();
84767
+ await persistOfficialCodexRestartDebt(null);
84768
+ };
84769
+ const rememberOfficialCodexRestartDebt = async (targetProfileName, targetProfileKey, reason, decisionId = null, sourceProfileName, sourceProfileKey) => {
84770
+ const previous = officialCodexRestartDebt?.targetProfileName === targetProfileName ? officialCodexRestartDebt : null;
84771
+ officialCodexRestartDebt = {
84772
+ targetProfileName,
84773
+ targetProfileKey,
84774
+ sourceProfileName: sourceProfileName !== void 0 ? sourceProfileName : previous?.sourceProfileName ?? null,
84775
+ sourceProfileKey: sourceProfileKey !== void 0 ? sourceProfileKey : previous?.sourceProfileKey ?? null,
84776
+ decisionId: decisionId ?? previous?.decisionId ?? null,
84777
+ attempts: previous?.attempts ?? 0,
84778
+ lastReason: reason
84779
+ };
84780
+ await persistOfficialCodexRestartDebt(officialCodexRestartDebt);
84781
+ };
84782
+ const getOfficialCodexRestartDebt = async () => {
84783
+ if (officialCodexRestartDebt) return officialCodexRestartDebt;
84784
+ const stored = (await getAppState()).officialCodex.pendingRestartDebt;
84785
+ if (!stored) return null;
84786
+ officialCodexRestartDebt = {
84787
+ ...stored,
84788
+ attempts: Math.max(0, stored.attempts)
84789
+ };
84790
+ return officialCodexRestartDebt;
84791
+ };
84792
+ const resolveObservedOfficialCodexProfileMatch = async (targetProfileName, targetProfileKey) => {
84793
+ const instances = await enrichOfficialCodexInstances(await getOfficialCodexProfileInstances());
84794
+ const observed = [...instances.instances.filter((instance) => instance.running).map((instance) => ({
84795
+ profileName: instance.profileName,
84796
+ profileKey: instance.profileKey,
84797
+ pid: instance.pid,
84798
+ appServerPid: instance.appServerPid,
84799
+ source: "managed"
84800
+ })), ...instances.unmanaged.map((instance) => ({
84801
+ profileName: instance.profileName ?? null,
84802
+ profileKey: instance.profileKey ?? null,
84803
+ pid: instance.pid,
84804
+ appServerPid: instance.appServerPid,
84805
+ source: instance.profileMatchSource ?? null
84806
+ }))].filter((instance) => instance.profileName || instance.profileKey || instance.pid);
84807
+ const matchedObserved = observed.find((instance) => observedOfficialCodexProfileMatches(instance, targetProfileName, targetProfileKey)) ?? null;
84808
+ const restartableObserved = observed.filter((instance) => instance.source !== "managed" && instance.pid);
84809
+ const singletonRestartableObserved = restartableObserved.length === 1 ? restartableObserved[0] : null;
84810
+ const firstObserved = matchedObserved ?? singletonRestartableObserved ?? observed[0] ?? null;
84811
+ return {
84812
+ matched: Boolean(matchedObserved),
84813
+ canStopObservedWindow: Boolean(firstObserved?.pid && firstObserved.source !== "managed" && (matchedObserved || singletonRestartableObserved === firstObserved)),
84814
+ ambiguousRestartableWindow: !matchedObserved && restartableObserved.length > 1,
84815
+ state: instances.state,
84816
+ observedProfileName: firstObserved?.profileName ?? null,
84817
+ observedProfileKey: firstObserved?.profileKey ?? null,
84818
+ observedProfileMatchSource: firstObserved?.source ?? null,
84819
+ pid: firstObserved?.pid ?? null,
84820
+ appServerPid: firstObserved?.appServerPid ?? null
84821
+ };
84822
+ };
84823
+ const recordTargetedOfficialCodexRestartResult = async (payload, targetProfileName, targetProfileKey, decisionId) => {
84824
+ const pid = payload.status === "started" || payload.status === "restarted" ? payload.pid : null;
84825
+ const reason = payload.status === "skipped" || payload.status === "failed" ? payload.reason : null;
84826
+ await recordOfficialCodexRestartResult({
84827
+ status: payload.status,
84828
+ reason,
84829
+ profileKey: targetProfileKey,
84830
+ pid
84831
+ });
84832
+ await recordAutoRollActivitySafe({
84833
+ kind: "official-codex-restart",
84834
+ status: payload.status,
84835
+ reason,
84836
+ decisionId,
84837
+ targetProfileName,
84838
+ profileKey: targetProfileKey,
84839
+ phase: payload.status === "failed" ? payload.phase ?? null : "profile",
84840
+ pid,
84841
+ restartRequested: true,
84842
+ restartResult: payload.status
84843
+ });
84844
+ };
84845
+ const profileActionToRestartPayload = (action, runningStatus, failedPhase) => {
84846
+ if (action.status === "failed") return {
84847
+ status: "failed",
84848
+ appPath: action.instance?.appPath ?? void 0,
84849
+ bundleId: action.instance?.bundleId ?? void 0,
84850
+ reason: action.reason ?? "official-codex-profile-action-failed",
84851
+ phase: failedPhase
84852
+ };
84853
+ if (action.status === "started" || action.status === "restarted") return {
84854
+ status: runningStatus,
84855
+ appPath: action.instance?.appPath ?? "",
84856
+ bundleId: action.instance?.bundleId ?? "",
84857
+ pid: action.instance?.pid ?? null
84858
+ };
84859
+ if (action.status === "already-running") return {
84860
+ status: "skipped",
84861
+ reason: "already-running"
84862
+ };
84863
+ if (action.status === "skipped" || action.status === "not-running") return {
84864
+ status: "skipped",
84865
+ reason: action.reason ?? "official-codex-app-not-running"
84866
+ };
84867
+ return {
84868
+ status: "skipped",
84869
+ reason: action.status
84870
+ };
84871
+ };
84872
+ let officialCodexRestartTargetLock = Promise.resolve();
84873
+ let officialCodexRestartTargetIntent = 0;
84874
+ const enqueueOfficialCodexRestartTarget = (work) => {
84875
+ const run = officialCodexRestartTargetLock.then(work, work);
84876
+ officialCodexRestartTargetLock = run.then(() => void 0, () => void 0);
84877
+ return run;
84878
+ };
84879
+ const restartOfficialCodexProfileTarget = async (args) => {
84880
+ const intent = ++officialCodexRestartTargetIntent;
84881
+ return enqueueOfficialCodexRestartTarget(() => restartOfficialCodexProfileTargetOnce(args, intent));
84882
+ };
84883
+ const restartOfficialCodexProfileTargetOnce = async (args, intent) => {
84884
+ const isCurrentIntent = () => intent === officialCodexRestartTargetIntent;
84885
+ const skipSuperseded = async (phase) => {
84886
+ const skipped = {
84887
+ status: "skipped",
84888
+ reason: "superseded-by-newer-profile-target"
84889
+ };
84890
+ await recordAutoRollActivitySafe({
84891
+ kind: "official-codex-restart",
84892
+ status: skipped.status,
84893
+ reason: skipped.reason,
84894
+ decisionId: args.decisionId,
84895
+ targetProfileName: args.targetProfileName,
84896
+ profileKey: args.targetProfileKey,
84897
+ phase,
84898
+ restartRequested: false,
84899
+ restartResult: skipped.status
84900
+ });
84901
+ return skipped;
84902
+ };
84903
+ if (!isCurrentIntent()) return skipSuperseded("queued");
84904
+ await recordAutoRollActivitySafe({
84905
+ kind: "official-codex-restart",
84906
+ status: "started",
84907
+ reason: null,
84908
+ decisionId: args.decisionId,
84909
+ targetProfileName: args.targetProfileName,
84910
+ profileKey: args.targetProfileKey,
84911
+ phase: "profile",
84912
+ restartRequested: true
84913
+ });
84914
+ if (!isCurrentIntent()) return skipSuperseded("start");
84915
+ const observed = await resolveObservedOfficialCodexProfileMatch(args.targetProfileName, args.targetProfileKey);
84916
+ if (!isCurrentIntent()) return skipSuperseded("observe");
84917
+ const canRestartManagedWindow = Boolean(observed.pid !== null && observed.observedProfileMatchSource === "managed" && observed.matched);
84918
+ const canStopMatchedObservedWindow = Boolean(observed.pid !== null && observed.matched && observed.observedProfileMatchSource !== "managed" && observed.canStopObservedWindow);
84919
+ const observedMatchesSource = !observed.matched && Boolean(args.sourceProfileName || args.sourceProfileKey) && observedOfficialCodexProfileMatches({
84920
+ profileName: observed.observedProfileName,
84921
+ profileKey: observed.observedProfileKey
84922
+ }, args.sourceProfileName ?? null, args.sourceProfileKey ?? null);
84923
+ const canStopSourceManagedWindow = Boolean(observed.pid !== null && observedMatchesSource && observed.observedProfileMatchSource === "managed" && args.sourceProfileName);
84924
+ const canStopSourceObservedWindow = Boolean(observed.pid !== null && observedMatchesSource && observed.observedProfileMatchSource !== "managed" && observed.canStopObservedWindow);
84925
+ const hasRestartableWindow = canRestartManagedWindow || canStopMatchedObservedWindow || canStopSourceManagedWindow || canStopSourceObservedWindow;
84926
+ if (!hasRestartableWindow && observed.ambiguousRestartableWindow) {
84927
+ const skipped = {
84928
+ status: "skipped",
84929
+ reason: "ambiguous-official-codex-windows"
84930
+ };
84931
+ await recordTargetedOfficialCodexRestartResult(skipped, args.targetProfileName, args.targetProfileKey, args.decisionId);
84932
+ return skipped;
84933
+ }
84934
+ if (!hasRestartableWindow && !args.launchIfNotRunning) {
84935
+ const skipped = {
84936
+ status: "skipped",
84937
+ 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"
84938
+ };
84939
+ await recordTargetedOfficialCodexRestartResult(skipped, args.targetProfileName, args.targetProfileKey, args.decisionId);
84940
+ return skipped;
84941
+ }
84942
+ const options = await prepareOfficialCodexProfileLaunch(args.targetProfileName);
84943
+ if (!isCurrentIntent()) return skipSuperseded("prepare");
84944
+ if (observed.observedProfileMatchSource === "managed" && observed.matched) {
84945
+ const action = await restartOfficialCodexProfileInstance(options);
84946
+ if (!isCurrentIntent()) {
84947
+ if (action.status === "started" || action.status === "restarted") await stopOfficialCodexProfileInstance(args.targetProfileName).catch((error) => {
84948
+ logger.warn("Failed to stop superseded Official Codex profile", {
84949
+ error,
84950
+ profileName: args.targetProfileName
84951
+ });
84952
+ });
84953
+ return skipSuperseded("profile");
84954
+ }
84955
+ const payload = profileActionToRestartPayload(action, "restarted", "profile");
84956
+ await recordTargetedOfficialCodexRestartResult(payload, args.targetProfileName, args.targetProfileKey, args.decisionId);
84957
+ return payload;
84958
+ }
84959
+ let stoppedRunningWindow = false;
84960
+ if (canStopMatchedObservedWindow && observed.pid !== null) {
84961
+ const stopped = await stopOfficialCodexObservedProfileInstance(observed.observedProfileName ?? args.targetProfileName, observed.pid, observed.appServerPid);
84962
+ if (stopped.status === "failed") {
84963
+ const failed = profileActionToRestartPayload(stopped, "restarted", "quit");
84964
+ await recordTargetedOfficialCodexRestartResult(failed, args.targetProfileName, args.targetProfileKey, args.decisionId);
84965
+ return failed;
84966
+ }
84967
+ stoppedRunningWindow = stopped.status === "stopped";
84968
+ } else if (canStopSourceManagedWindow && args.sourceProfileName) {
84969
+ const stopped = await stopOfficialCodexProfileInstance(args.sourceProfileName);
84970
+ if (stopped.status === "failed") {
84971
+ const failed = profileActionToRestartPayload(stopped, "restarted", "quit");
84972
+ await recordTargetedOfficialCodexRestartResult(failed, args.targetProfileName, args.targetProfileKey, args.decisionId);
84973
+ return failed;
84974
+ }
84975
+ stoppedRunningWindow = stopped.status === "stopped";
84976
+ } else if (canStopSourceObservedWindow && observed.pid !== null) {
84977
+ const stopped = await stopOfficialCodexObservedProfileInstance(observed.observedProfileName ?? args.sourceProfileName ?? args.targetProfileName, observed.pid, observed.appServerPid);
84978
+ if (stopped.status === "failed") {
84979
+ const failed = profileActionToRestartPayload(stopped, "restarted", "quit");
84980
+ await recordTargetedOfficialCodexRestartResult(failed, args.targetProfileName, args.targetProfileKey, args.decisionId);
84981
+ return failed;
84982
+ }
84983
+ stoppedRunningWindow = stopped.status === "stopped";
84984
+ }
84985
+ if (!isCurrentIntent()) return skipSuperseded("quit");
84986
+ const launched = await launchOfficialCodexProfileInstance(options);
84987
+ if (!isCurrentIntent()) {
84988
+ if (launched.status === "started" || launched.status === "restarted") await stopOfficialCodexProfileInstance(args.targetProfileName).catch((error) => {
84989
+ logger.warn("Failed to stop superseded Official Codex profile", {
84990
+ error,
84991
+ profileName: args.targetProfileName
84992
+ });
84993
+ });
84994
+ return skipSuperseded("launch");
84995
+ }
84996
+ const payload = profileActionToRestartPayload(launched, stoppedRunningWindow ? "restarted" : "started", "launch");
84997
+ await recordTargetedOfficialCodexRestartResult(payload, args.targetProfileName, args.targetProfileKey, args.decisionId);
84998
+ return payload;
84999
+ };
85000
+ const waitForOfficialCodexProfileMatch = async (targetProfileName, targetProfileKey) => {
85001
+ const deadline = Date.now() + OFFICIAL_CODEX_POST_RESTART_VERIFY_TIMEOUT_MS;
85002
+ let latest = await resolveObservedOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
85003
+ while (!latest.matched && Date.now() < deadline) {
85004
+ await new Promise((resolve) => setTimeout(resolve, OFFICIAL_CODEX_POST_RESTART_VERIFY_INTERVAL_MS));
85005
+ latest = await resolveObservedOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
85006
+ }
85007
+ return latest;
85008
+ };
85009
+ const scheduleOfficialCodexRestartRetry = () => {
85010
+ const debt = officialCodexRestartDebt;
85011
+ if (!debt || officialCodexRestartRetryTimer) return;
85012
+ if (debt.attempts >= OFFICIAL_CODEX_RESTART_DEBT_MAX_ATTEMPTS) {
85013
+ clearOfficialCodexRestartDebt().catch((error) => {
85014
+ logger.warn("Failed to clear Official Codex restart debt", { error });
85015
+ });
85016
+ recordAutoRollActivitySafe({
85017
+ kind: "official-codex-restart",
85018
+ status: "skipped",
85019
+ reason: "restart-retry-exhausted",
85020
+ decisionId: debt.decisionId,
85021
+ targetProfileName: debt.targetProfileName,
85022
+ profileKey: debt.targetProfileKey,
85023
+ restartRequested: false,
85024
+ restartResult: "skipped"
85025
+ });
85026
+ return;
85027
+ }
85028
+ const delayMs = Math.min(5 * 6e4, OFFICIAL_CODEX_RESTART_RETRY_DELAY_MS * Math.max(1, debt.attempts + 1));
85029
+ officialCodexRestartRetryTimer = setTimeout(() => {
85030
+ officialCodexRestartRetryTimer = null;
85031
+ (async () => {
85032
+ const debt = officialCodexRestartDebt;
85033
+ if (!debt) return;
85034
+ if ((await resolveObservedOfficialCodexProfileMatch(debt.targetProfileName, debt.targetProfileKey)).matched) {
85035
+ await clearOfficialCodexRestartDebt();
85036
+ return;
85037
+ }
85038
+ if (officialCodexRestartDebt) {
85039
+ officialCodexRestartDebt = {
85040
+ ...officialCodexRestartDebt,
85041
+ attempts: officialCodexRestartDebt.attempts + 1
85042
+ };
85043
+ await persistOfficialCodexRestartDebt(officialCodexRestartDebt);
85044
+ await maybeRestartOfficialCodexAfterAutoRoll(officialCodexRestartDebt.targetProfileName, officialCodexRestartDebt.targetProfileKey, officialCodexRestartDebt.decisionId, officialCodexRestartDebt.sourceProfileName, officialCodexRestartDebt.sourceProfileKey);
85045
+ }
85046
+ })().catch((error) => {
85047
+ logger.warn("Official Codex restart retry failed", { error });
85048
+ });
85049
+ }, delayMs);
85050
+ officialCodexRestartRetryTimer.unref?.();
85051
+ };
85052
+ const reconcileOfficialCodexRestartDebt = async () => {
85053
+ const debt = await getOfficialCodexRestartDebt();
85054
+ if (!debt) return;
85055
+ const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
85056
+ if (!settings.restartOfficialCodexOnAutoRoll) {
85057
+ await clearOfficialCodexRestartDebt();
85058
+ return;
85059
+ }
85060
+ const targetProfile = (await profileManager.listProfiles()).find((profile) => profile.name === debt.targetProfileName) ?? null;
85061
+ if ((await resolveObservedOfficialCodexProfileMatch(debt.targetProfileName, debt.targetProfileKey)).matched) {
85062
+ await clearOfficialCodexRestartDebt();
85063
+ return;
85064
+ }
85065
+ const status = targetProfile ? await resolveOfficialCodexStatus(targetProfile) : await getOfficialCodexSyncStatus(debt.targetProfileKey);
85066
+ if (status.state === "synced") {
85067
+ await rememberOfficialCodexRestartDebt(debt.targetProfileName, debt.targetProfileKey, "launched-auth-unverified", debt.decisionId);
85068
+ scheduleOfficialCodexRestartRetry();
85069
+ return;
85070
+ }
85071
+ if (status.state === "not-running" && !settings.launchOfficialCodexWhenClosedOnAutoRoll) {
85072
+ await clearOfficialCodexRestartDebt();
85073
+ return;
85074
+ }
85075
+ if (status.state === "stale" || status.state === "unknown" || status.state === "not-running" && settings.launchOfficialCodexWhenClosedOnAutoRoll) scheduleOfficialCodexRestartRetry();
85076
+ };
85077
+ const maybeRestartOfficialCodexAfterAutoRoll = async (targetProfileName, targetProfileKey, decisionId, sourceProfileName = null, sourceProfileKey = null) => {
84370
85078
  try {
84371
85079
  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
85080
+ if (!settings.restartOfficialCodexOnAutoRoll) {
85081
+ await clearOfficialCodexRestartDebt();
85082
+ return;
85083
+ }
85084
+ const alreadyMatched = await resolveObservedOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
85085
+ if (alreadyMatched.matched) {
85086
+ await clearOfficialCodexRestartDebt();
85087
+ await recordAutoRollActivitySafe({
85088
+ kind: "official-codex-restart",
85089
+ status: "skipped",
85090
+ reason: "already-synced",
85091
+ decisionId,
85092
+ targetProfileName,
85093
+ profileKey: targetProfileKey,
85094
+ restartRequested: false,
85095
+ restartResult: "skipped",
85096
+ observedProfileName: alreadyMatched.observedProfileName,
85097
+ observedProfileKey: alreadyMatched.observedProfileKey,
85098
+ observedProfileMatchSource: alreadyMatched.observedProfileMatchSource
85099
+ });
85100
+ return;
85101
+ }
85102
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, null, decisionId, sourceProfileName, sourceProfileKey);
85103
+ const result = await restartOfficialCodexProfileTarget({
85104
+ targetProfileName,
85105
+ targetProfileKey,
85106
+ sourceProfileName,
85107
+ sourceProfileKey,
85108
+ launchIfNotRunning: settings.launchOfficialCodexWhenClosedOnAutoRoll,
85109
+ decisionId
84377
85110
  });
84378
85111
  trackAnalyticsEvent("official_codex_restart_after_auto_roll", {
84379
85112
  status: result.status,
84380
- reason: result.status === "skipped" || result.status === "failed" ? result.reason : void 0,
84381
- targetProfileName
85113
+ reason: result.status === "skipped" || result.status === "failed" ? result.reason : void 0
84382
85114
  });
85115
+ if (result.status === "skipped" && result.reason === "cooldown") {
85116
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, "cooldown", decisionId);
85117
+ await reconcileOfficialCodexRestartDebt();
85118
+ } else if (result.status === "skipped" && result.reason === "official-codex-app-not-running" && !settings.launchOfficialCodexWhenClosedOnAutoRoll) await clearOfficialCodexRestartDebt();
85119
+ else if (result.status === "skipped" && result.reason === "superseded-by-newer-profile-target") {
85120
+ if ((await getOfficialCodexRestartDebt())?.targetProfileName === targetProfileName) await clearOfficialCodexRestartDebt();
85121
+ } else if (result.status === "skipped" || result.status === "failed") {
85122
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, result.reason, decisionId);
85123
+ await reconcileOfficialCodexRestartDebt();
85124
+ } else {
85125
+ const verified = await waitForOfficialCodexProfileMatch(targetProfileName, targetProfileKey);
85126
+ if (verified.matched) {
85127
+ await recordAutoRollActivitySafe({
85128
+ kind: "official-codex-restart",
85129
+ status: "auth-verified",
85130
+ reason: null,
85131
+ decisionId,
85132
+ targetProfileName,
85133
+ profileKey: targetProfileKey,
85134
+ restartRequested: true,
85135
+ restartResult: result.status,
85136
+ observedProfileName: verified.observedProfileName,
85137
+ observedProfileKey: verified.observedProfileKey,
85138
+ observedProfileMatchSource: verified.observedProfileMatchSource
85139
+ });
85140
+ await clearOfficialCodexRestartDebt();
85141
+ } else {
85142
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, "launched-auth-unverified", decisionId);
85143
+ await recordAutoRollActivitySafe({
85144
+ kind: "official-codex-restart",
85145
+ status: "waiting",
85146
+ reason: "launched-auth-unverified",
85147
+ decisionId,
85148
+ targetProfileName,
85149
+ profileKey: targetProfileKey,
85150
+ restartRequested: true,
85151
+ restartResult: result.status,
85152
+ observedProfileName: verified.observedProfileName,
85153
+ observedProfileKey: verified.observedProfileKey,
85154
+ observedProfileMatchSource: verified.observedProfileMatchSource
85155
+ });
85156
+ await reconcileOfficialCodexRestartDebt();
85157
+ }
85158
+ }
84383
85159
  if (result.status === "failed") logger.warn("Official Codex restart after auto-roll failed", result);
84384
85160
  else if (result.status === "restarted" || result.status === "started") logger.info("Official Codex restarted after auto-roll", result);
84385
85161
  } catch (error) {
85162
+ await rememberOfficialCodexRestartDebt(targetProfileName, targetProfileKey, error instanceof Error ? error.message : "restart-threw", decisionId);
85163
+ await recordAutoRollActivitySafe({
85164
+ kind: "official-codex-restart",
85165
+ status: "failed",
85166
+ reason: error instanceof Error ? error.message : "restart-threw",
85167
+ decisionId,
85168
+ targetProfileName,
85169
+ profileKey: targetProfileKey,
85170
+ restartRequested: true,
85171
+ restartResult: "failed"
85172
+ });
85173
+ scheduleOfficialCodexRestartRetry();
84386
85174
  logger.warn("Official Codex restart after auto-roll threw", { error });
84387
85175
  }
84388
85176
  };
84389
- const handleProfileSwitch = async (name, source = "app") => {
85177
+ const handleProfileSwitch = async (name, source = "app", decisionContext = {}) => {
84390
85178
  if (!name) throw new Error("Profile name is required");
84391
85179
  const previousProfile = source === "auto-roll" ? (await profileManager.getCurrentProfile()).name ?? null : null;
85180
+ const previousProfileKey = source === "auto-roll" ? await resolveProfileIdentityKeyByName(previousProfile) : null;
84392
85181
  await profileManager.switchToProfile(name);
85182
+ if (source === "auto-roll") {
85183
+ const verified = await profileManager.getCurrentProfile();
85184
+ if (verified.name !== name || !verified.trusted) {
85185
+ await recordAutoRollActivitySafe({
85186
+ kind: "auth-verified",
85187
+ status: "failed",
85188
+ reason: "active-auth-mismatch-after-switch",
85189
+ decisionId: decisionContext.decisionId,
85190
+ sourceProfileName: previousProfile,
85191
+ targetProfileName: name,
85192
+ remainingPercent: decisionContext.remainingPercent,
85193
+ threshold: decisionContext.threshold,
85194
+ snapshotAgeMs: decisionContext.snapshotAgeMs,
85195
+ snapshotSource: decisionContext.snapshotSource,
85196
+ switchVerified: false
85197
+ });
85198
+ throw new Error("Auto-roll profile switch did not verify active Codex auth.");
85199
+ }
85200
+ await recordAutoRollActivitySafe({
85201
+ kind: "auth-verified",
85202
+ status: "ok",
85203
+ reason: null,
85204
+ decisionId: decisionContext.decisionId,
85205
+ sourceProfileName: previousProfile,
85206
+ targetProfileName: name,
85207
+ remainingPercent: decisionContext.remainingPercent,
85208
+ threshold: decisionContext.threshold,
85209
+ snapshotAgeMs: decisionContext.snapshotAgeMs,
85210
+ snapshotSource: decisionContext.snapshotSource,
85211
+ switchVerified: true
85212
+ });
85213
+ }
84393
85214
  let targetProfileKey = null;
84394
85215
  try {
84395
85216
  targetProfileKey = await resolveProfileIdentityKeyByName(name);
@@ -84400,23 +85221,60 @@ const createServer = fn(function* () {
84400
85221
  profileName: name
84401
85222
  });
84402
85223
  }
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
85224
+ if (source === "auto-roll") {
85225
+ rememberAutoRollAttempt(previousProfile, name);
85226
+ await recordAutoRollActivitySafe({
85227
+ kind: "profile-switch",
85228
+ status: "completed",
85229
+ reason: null,
85230
+ decisionId: decisionContext.decisionId,
85231
+ sourceProfileName: previousProfile,
85232
+ targetProfileName: name,
85233
+ remainingPercent: decisionContext.remainingPercent,
85234
+ threshold: decisionContext.threshold,
85235
+ snapshotAgeMs: decisionContext.snapshotAgeMs,
85236
+ snapshotSource: decisionContext.snapshotSource,
85237
+ profileKey: targetProfileKey,
85238
+ switchVerified: true,
85239
+ restartRequested: decisionContext.restartRequested ?? null
84412
85240
  });
85241
+ } else {
85242
+ officialCodexRestartTargetIntent += 1;
85243
+ const pendingRestart = await getOfficialCodexRestartDebt();
85244
+ if (pendingRestart && pendingRestart.targetProfileName !== name) {
85245
+ await clearOfficialCodexRestartDebt();
85246
+ await recordAutoRollActivitySafe({
85247
+ kind: "official-codex-restart",
85248
+ status: "skipped",
85249
+ reason: "manual-profile-switch-superseded-restart",
85250
+ decisionId: pendingRestart.decisionId,
85251
+ sourceProfileName: pendingRestart.sourceProfileName,
85252
+ targetProfileName: pendingRestart.targetProfileName,
85253
+ profileKey: pendingRestart.targetProfileKey,
85254
+ restartRequested: false,
85255
+ restartResult: "skipped"
85256
+ });
85257
+ }
85258
+ try {
85259
+ const rearmAction = await resolveAutoRollRearmActionAfterManualSwitch(name);
85260
+ if (rearmAction === "block") blockAutoRollUntilRearm(name);
85261
+ else if (rearmAction === "unblock") unblockAutoRollRearm(name);
85262
+ } catch (error) {
85263
+ logger.warn("Failed to evaluate auto-roll rearm block after manual switch", {
85264
+ error,
85265
+ profileName: name
85266
+ });
85267
+ }
84413
85268
  }
84414
85269
  await publishProfileSwitched({ name });
84415
85270
  trackAnalyticsEvent("profile_switched", { source });
84416
- if (source === "auto-roll") await maybeRestartOfficialCodexAfterAutoRoll(name, targetProfileKey);
85271
+ if (source === "auto-roll") await maybeRestartOfficialCodexAfterAutoRoll(name, targetProfileKey, decisionContext.decisionId ?? null, previousProfile, previousProfileKey);
85272
+ else maybeRunBackgroundAutoRoll();
84417
85273
  };
84418
85274
  const maybeRunBackgroundAutoRoll = async () => {
84419
85275
  if (backgroundAutoRollInFlight) return;
85276
+ const decisionId = createAutoRollDecisionId();
85277
+ let lastDecisionContext = { decisionId };
84420
85278
  backgroundAutoRollInFlight = true;
84421
85279
  try {
84422
85280
  const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
@@ -84428,9 +85286,12 @@ const createServer = fn(function* () {
84428
85286
  const profiles = await profileManager.listProfiles();
84429
85287
  const snapshots = await loadCachedRateLimitSnapshots(profiles.map((profile) => resolveProfileIdentityKeyFromProfile(profile)));
84430
85288
  const usageSummaries = /* @__PURE__ */ new Map();
85289
+ const snapshotDiagnostics = /* @__PURE__ */ new Map();
84431
85290
  const profilesWithSnapshots = profiles.map((profile) => {
84432
85291
  const accountId = resolveProfileIdentityKeyFromProfile(profile);
84433
- const snapshot = snapshots.get(accountId) ?? profile.rateLimit ?? null;
85292
+ const diagnostic = buildSnapshotDiagnostic(snapshots.get(accountId) ?? null, profile.rateLimit ?? null);
85293
+ snapshotDiagnostics.set(profile.name, diagnostic);
85294
+ const snapshot = freshAutoRollSnapshot(diagnostic.snapshot);
84434
85295
  usageSummaries.set(profile.name, summarizeRateLimitSnapshot(snapshot));
84435
85296
  return {
84436
85297
  ...profile,
@@ -84441,19 +85302,125 @@ const createServer = fn(function* () {
84441
85302
  const currentProfile = profilesWithSnapshots.find((profile) => profile.name === currentProfileName);
84442
85303
  if (!currentProfile) return;
84443
85304
  const currentSummary = usageSummaries.get(currentProfileName) ?? summarizeRateLimitSnapshot(currentProfile.rateLimit ?? null);
85305
+ const currentSnapshotDiagnostic = snapshotDiagnostics.get(currentProfileName) ?? buildSnapshotDiagnostic(null, currentProfile.rateLimit ?? null);
85306
+ const decisionContext = {
85307
+ decisionId,
85308
+ remainingPercent: currentSummary.hasUsage ? currentSummary.remainingPercent : null,
85309
+ threshold: settings.switchRemainingThreshold,
85310
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
85311
+ snapshotSource: currentSnapshotDiagnostic.source,
85312
+ restartRequested: settings.restartOfficialCodexOnAutoRoll
85313
+ };
85314
+ lastDecisionContext = decisionContext;
84444
85315
  if (!currentSummary.hasUsage) return;
84445
- if (isAutoRollBlockedUntilRearm(currentProfileName, currentSummary, settings.rearmRemainingThreshold)) return;
85316
+ if (isAutoRollBlockedUntilRearm(currentProfileName, currentSummary, settings.rearmRemainingThreshold)) {
85317
+ await recordAutoRollActivitySafe({
85318
+ kind: "auto-roll-eval",
85319
+ status: "blocked",
85320
+ reason: "waiting-for-rearm",
85321
+ decisionId,
85322
+ profileName: currentProfileName,
85323
+ remainingPercent: currentSummary.remainingPercent,
85324
+ threshold: settings.rearmRemainingThreshold,
85325
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
85326
+ snapshotSource: currentSnapshotDiagnostic.source
85327
+ });
85328
+ return;
85329
+ }
84446
85330
  if (currentSummary.remainingPercent > settings.switchRemainingThreshold) return;
85331
+ await recordAutoRollActivitySafe({
85332
+ kind: "auto-roll-eval",
85333
+ status: "threshold-hit",
85334
+ reason: null,
85335
+ decisionId,
85336
+ profileName: currentProfileName,
85337
+ remainingPercent: currentSummary.remainingPercent,
85338
+ threshold: settings.switchRemainingThreshold,
85339
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
85340
+ snapshotSource: currentSnapshotDiagnostic.source
85341
+ });
84447
85342
  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");
85343
+ if (!candidate) {
85344
+ await recordAutoRollActivitySafe({
85345
+ kind: "auto-roll-eval",
85346
+ status: "skipped",
85347
+ reason: "no-candidate",
85348
+ decisionId,
85349
+ profileName: currentProfileName,
85350
+ remainingPercent: currentSummary.remainingPercent,
85351
+ threshold: settings.switchRemainingThreshold,
85352
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
85353
+ snapshotSource: currentSnapshotDiagnostic.source
85354
+ });
85355
+ return;
85356
+ }
85357
+ if (isRecentAutoRollAttempt(currentProfileName, candidate.profile.name)) {
85358
+ await recordAutoRollActivitySafe({
85359
+ kind: "auto-roll-eval",
85360
+ status: "skipped",
85361
+ reason: "recent-duplicate-attempt",
85362
+ decisionId,
85363
+ sourceProfileName: currentProfileName,
85364
+ targetProfileName: candidate.profile.name,
85365
+ remainingPercent: currentSummary.remainingPercent,
85366
+ threshold: settings.switchRemainingThreshold,
85367
+ snapshotAgeMs: currentSnapshotDiagnostic.ageMs,
85368
+ snapshotSource: currentSnapshotDiagnostic.source
85369
+ });
85370
+ return;
85371
+ }
85372
+ await handleProfileSwitch(candidate.profile.name, "auto-roll", decisionContext);
84450
85373
  blockAutoRollUntilRearm(currentProfileName);
84451
85374
  } catch (error) {
85375
+ await recordAutoRollActivitySafe({
85376
+ kind: "auto-roll-eval",
85377
+ status: "failed",
85378
+ reason: error instanceof Error ? error.message : "unknown-error",
85379
+ decisionId,
85380
+ remainingPercent: lastDecisionContext.remainingPercent,
85381
+ threshold: lastDecisionContext.threshold,
85382
+ snapshotAgeMs: lastDecisionContext.snapshotAgeMs,
85383
+ snapshotSource: lastDecisionContext.snapshotSource
85384
+ });
84452
85385
  logger.warn("Background auto-roll failed", { error });
84453
85386
  } finally {
84454
85387
  backgroundAutoRollInFlight = false;
84455
85388
  }
84456
85389
  };
85390
+ const runAutoRollWatchdog = async () => {
85391
+ const decisionId = createAutoRollDecisionId();
85392
+ const settings = normalizeAutoRollSettings(await getStoredAutoRollSettings());
85393
+ if (!settings.enabled) return;
85394
+ if (!(await licenseService.getCachedStatus()).isPro) return;
85395
+ const current = await profileManager.getCurrentProfile();
85396
+ const currentProfileName = typeof current.name === "string" && current.name.length > 0 ? current.name : null;
85397
+ if (!currentProfileName) return;
85398
+ const currentProfile = (await profileManager.listProfiles()).find((profile) => profile.name === currentProfileName) ?? null;
85399
+ if (!currentProfile) return;
85400
+ const accountId = resolveProfileIdentityKeyFromProfile(currentProfile);
85401
+ const diagnostic = buildSnapshotDiagnostic((await loadCachedRateLimitSnapshots([accountId])).get(accountId) ?? null, currentProfile.rateLimit ?? null);
85402
+ const snapshot = diagnostic.snapshot;
85403
+ if (isSnapshotStaleForAutoRollWatchdog(snapshot)) {
85404
+ const queued = await enqueueAccountsRefreshNow([currentProfileName]);
85405
+ const lastRecordedAt = lastAutoRollWatchdogActivityAt.get(accountId) ?? 0;
85406
+ if (queued.enqueuedAccountIds.includes(accountId) && Date.now() - lastRecordedAt >= AUTO_ROLL_WATCHDOG_ACTIVITY_TTL_MS) {
85407
+ lastAutoRollWatchdogActivityAt.set(accountId, Date.now());
85408
+ await recordAutoRollActivitySafe({
85409
+ kind: "auto-roll-eval",
85410
+ status: "refresh-queued",
85411
+ reason: "watchdog-stale-snapshot",
85412
+ decisionId,
85413
+ profileName: currentProfileName,
85414
+ threshold: settings.switchRemainingThreshold,
85415
+ snapshotAgeMs: diagnostic.ageMs,
85416
+ snapshotSource: diagnostic.source,
85417
+ profileKey: accountId
85418
+ });
85419
+ }
85420
+ return;
85421
+ }
85422
+ await maybeRunBackgroundAutoRoll();
85423
+ };
84457
85424
  const handleDesktopMessage = (message) => {
84458
85425
  if (!isDesktopChildCommand(message)) return;
84459
85426
  if (message.type === "t3-server:switch-profile") {
@@ -84484,6 +85451,7 @@ const createServer = fn(function* () {
84484
85451
  logger.warn("Low remaining notification failed", { error });
84485
85452
  });
84486
85453
  maybeRunBackgroundAutoRoll();
85454
+ reconcileOfficialCodexRestartDebt();
84487
85455
  };
84488
85456
  const handleRateLimitStateChanged = (payload) => {
84489
85457
  refreshTray();
@@ -84491,9 +85459,16 @@ const createServer = fn(function* () {
84491
85459
  };
84492
85460
  rateLimitEvents.on("snapshot-updated", handleRateLimitSnapshotUpdated);
84493
85461
  rateLimitEvents.on("refresh-state-changed", handleRateLimitStateChanged);
85462
+ const autoRollWatchdogInterval = setInterval(() => {
85463
+ runAutoRollWatchdog().catch((error) => {
85464
+ logger.warn("Auto-roll watchdog failed", { error });
85465
+ });
85466
+ }, AUTO_ROLL_WATCHDOG_INTERVAL_MS);
85467
+ autoRollWatchdogInterval.unref?.();
84494
85468
  yield* addFinalizer(() => sync(() => {
84495
85469
  rateLimitEvents.off("snapshot-updated", handleRateLimitSnapshotUpdated);
84496
85470
  rateLimitEvents.off("refresh-state-changed", handleRateLimitStateChanged);
85471
+ clearInterval(autoRollWatchdogInterval);
84497
85472
  }));
84498
85473
  const unsubscribeTerminalEvents = yield* terminalManager.subscribe((event) => void runPromise(pushBus.publishAll(WS_CHANNELS.terminalEvent, event)));
84499
85474
  yield* addFinalizer(() => sync(() => unsubscribeTerminalEvents()));
@@ -84513,6 +85488,8 @@ const createServer = fn(function* () {
84513
85488
  });
84514
85489
  });
84515
85490
  yield* readiness.markHttpListening;
85491
+ maybeRunBackgroundAutoRoll();
85492
+ reconcileOfficialCodexRestartDebt();
84516
85493
  readResolvedTelegramRuntimeSettings().then((settings) => queueTelegramRuntimeSettingsApply(settings)).catch((error) => {
84517
85494
  logger.warn("Failed to apply Telegram runtime settings during startup", { error: error instanceof Error ? error.message : String(error) });
84518
85495
  });
@@ -84891,10 +85868,15 @@ const createServer = fn(function* () {
84891
85868
  return yield* promise(() => telegramBridge.testToken(body.token));
84892
85869
  }
84893
85870
  case WS_METHODS.profilesFetch: {
84894
- const data = yield* promise(() => loadProfilesViewData({ autoImportActiveAuth: true }));
85871
+ const body = request.body && typeof request.body === "object" ? stripRequestTag(request.body) : {};
85872
+ const data = yield* promise(() => loadProfilesViewData({
85873
+ autoImportActiveAuth: true,
85874
+ backgroundRefresh: body.backgroundRefresh !== false
85875
+ }));
84895
85876
  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));
85877
+ const officialCodexStatus = yield* promise(() => resolveOfficialCodexStatus(currentProfile));
85878
+ maybeRunBackgroundAutoRoll();
85879
+ reconcileOfficialCodexRestartDebt();
84898
85880
  refreshTray();
84899
85881
  return {
84900
85882
  ...data,
@@ -85171,6 +86153,7 @@ const createServer = fn(function* () {
85171
86153
  const normalized = normalizeAutoRollSettings(stripRequestTag(request.body).settings);
85172
86154
  yield* promise(() => persistAutoRollSettings(normalized));
85173
86155
  maybeRunBackgroundAutoRoll();
86156
+ reconcileOfficialCodexRestartDebt();
85174
86157
  trackAnalyticsEvent("auto_roll_settings_saved", {
85175
86158
  enabled: normalized.enabled,
85176
86159
  rearmRemainingThreshold: normalized.rearmRemainingThreshold,
@@ -85184,12 +86167,30 @@ const createServer = fn(function* () {
85184
86167
  return normalized;
85185
86168
  }
85186
86169
  case WS_METHODS.officialCodexRestart: return yield* promise(async () => {
85187
- return restartOfficialCodexApp({
85188
- source: "manual",
85189
- currentProfileKey: await resolveProfileIdentityKeyByName((await profileManager.getCurrentProfile()).name ?? null)
86170
+ const current = await profileManager.getCurrentProfile();
86171
+ const currentProfileKey = await resolveProfileIdentityKeyByName(current.name ?? null);
86172
+ if (!current.name) return {
86173
+ status: "skipped",
86174
+ reason: "profile-not-selected"
86175
+ };
86176
+ return restartOfficialCodexProfileTarget({
86177
+ targetProfileName: current.name,
86178
+ targetProfileKey: currentProfileKey,
86179
+ sourceProfileName: null,
86180
+ sourceProfileKey: null,
86181
+ launchIfNotRunning: true,
86182
+ decisionId: null
85190
86183
  });
85191
86184
  });
85192
- case WS_METHODS.officialCodexInstances: return yield* promise(async () => enrichOfficialCodexInstances(await getOfficialCodexProfileInstances()));
86185
+ case WS_METHODS.officialCodexInstances: return yield* promise(async () => {
86186
+ const [instances, current] = await Promise.all([enrichOfficialCodexInstances(await getOfficialCodexProfileInstances()), profileManager.getCurrentProfile()]);
86187
+ const currentProfile = current.name ? (await profileManager.listProfiles()).find((profile) => profile.name === current.name) ?? null : null;
86188
+ const syncStatus = await getOfficialCodexSyncStatus(currentProfile ? resolveProfileIdentityKeyFromProfile(currentProfile) : null);
86189
+ return {
86190
+ ...instances,
86191
+ syncStatus
86192
+ };
86193
+ });
85193
86194
  case WS_METHODS.officialCodexLaunchProfile: {
85194
86195
  const name = stripRequestTag(request.body).name?.trim() ?? "";
85195
86196
  const access = yield* tryPromise({
@@ -85289,6 +86290,50 @@ const createServer = fn(function* () {
85289
86290
  return restartOfficialCodexProfileInstance(options);
85290
86291
  });
85291
86292
  }
86293
+ case WS_METHODS.officialCodexUseProfile: {
86294
+ const name = stripRequestTag(request.body).name?.trim() ?? "";
86295
+ const access = yield* tryPromise({
86296
+ try: () => resolveOfficialCodexProfileAccess(),
86297
+ catch: toOfficialCodexProfileAccessRouteError
86298
+ });
86299
+ yield* try_({
86300
+ try: () => assertOfficialCodexProfileAccess(name, access),
86301
+ catch: toOfficialCodexProfileAccessRouteError
86302
+ });
86303
+ return yield* promise(async () => {
86304
+ const previousName = (await profileManager.getCurrentProfile()).name ?? null;
86305
+ const previousKey = await resolveProfileIdentityKeyByName(previousName);
86306
+ await handleProfileSwitch(name, "app");
86307
+ const verified = await profileManager.getCurrentProfile();
86308
+ if (verified.name !== name || !verified.trusted) {
86309
+ await recordAutoRollActivitySafe({
86310
+ kind: "auth-verified",
86311
+ status: "failed",
86312
+ reason: "active-auth-mismatch-after-switch",
86313
+ sourceProfileName: previousName,
86314
+ targetProfileName: name,
86315
+ switchVerified: false
86316
+ });
86317
+ throw new Error("Profile switch did not verify active Codex auth.");
86318
+ }
86319
+ await recordAutoRollActivitySafe({
86320
+ kind: "auth-verified",
86321
+ status: "ok",
86322
+ reason: null,
86323
+ sourceProfileName: previousName,
86324
+ targetProfileName: name,
86325
+ switchVerified: true
86326
+ });
86327
+ return restartOfficialCodexProfileTarget({
86328
+ targetProfileName: name,
86329
+ targetProfileKey: await resolveProfileIdentityKeyByName(name),
86330
+ sourceProfileName: previousName,
86331
+ sourceProfileKey: previousKey,
86332
+ launchIfNotRunning: true,
86333
+ decisionId: null
86334
+ });
86335
+ });
86336
+ }
85292
86337
  case WS_METHODS.officialCodexLaunchProfiles: {
85293
86338
  const body = stripRequestTag(request.body);
85294
86339
  const rawNames = Array.isArray(body.names) ? body.names : [];
@@ -85361,6 +86406,78 @@ const createServer = fn(function* () {
85361
86406
  trackAnalyticsEvent("official_codex_profiles_stop_requested", { count: results.length });
85362
86407
  return { results };
85363
86408
  }
86409
+ case WS_METHODS.officialCodexActivateProfiles: {
86410
+ const body = stripRequestTag(request.body);
86411
+ const rawNames = Array.isArray(body.names) ? body.names : [];
86412
+ const names = Array.from(new Set(rawNames.map((name) => typeof name === "string" ? name.trim() : "").filter(Boolean)));
86413
+ const access = yield* tryPromise({
86414
+ try: () => resolveOfficialCodexProfileAccess(),
86415
+ catch: toOfficialCodexProfileAccessRouteError
86416
+ });
86417
+ yield* try_({
86418
+ try: () => {
86419
+ for (const name of names) assertOfficialCodexProfileAccess(name, access);
86420
+ },
86421
+ catch: toOfficialCodexProfileAccessRouteError
86422
+ });
86423
+ const results = [];
86424
+ for (const name of names) {
86425
+ const result = yield* promise(async () => {
86426
+ try {
86427
+ const activation = await profileManager.activateResetWindow(name);
86428
+ const entry = {
86429
+ profileName: name,
86430
+ status: activation.status,
86431
+ reason: activation.reason,
86432
+ threadId: activation.threadId ?? null,
86433
+ turnId: activation.turnId ?? null
86434
+ };
86435
+ await recordAutoRollActivitySafe({
86436
+ kind: "reset-window-activation",
86437
+ status: entry.status,
86438
+ reason: entry.reason,
86439
+ profileName: name
86440
+ });
86441
+ return entry;
86442
+ } catch (error) {
86443
+ const entry = {
86444
+ profileName: name,
86445
+ status: "failed",
86446
+ reason: formatUserFacingError(error, { fallback: "Could not activate reset window." }),
86447
+ threadId: null,
86448
+ turnId: null
86449
+ };
86450
+ await recordAutoRollActivitySafe({
86451
+ kind: "reset-window-activation",
86452
+ status: entry.status,
86453
+ reason: entry.reason,
86454
+ profileName: name
86455
+ });
86456
+ return entry;
86457
+ }
86458
+ });
86459
+ results.push(result);
86460
+ }
86461
+ const completed = results.filter((result) => result.status === "completed").length;
86462
+ const failed = results.filter((result) => result.status === "failed").length;
86463
+ const skipped = results.filter((result) => result.status === "skipped").length;
86464
+ if (completed > 0) enqueueAccountsRefreshNow(results.filter((result) => result.status === "completed").map((result) => result.profileName)).catch((error) => {
86465
+ logger.warn("Failed to refresh rate limits after reset-window activation", { error });
86466
+ });
86467
+ trackAnalyticsEvent("official_codex_profiles_activation_requested", {
86468
+ count: results.length,
86469
+ completed,
86470
+ failed,
86471
+ skipped
86472
+ });
86473
+ return {
86474
+ total: results.length,
86475
+ completed,
86476
+ failed,
86477
+ skipped,
86478
+ results
86479
+ };
86480
+ }
85364
86481
  case WS_METHODS.cliInfo: return yield* promise(() => getCodexCliInfo());
85365
86482
  case WS_METHODS.cliStatus: return yield* promise(() => getCliInstallStatusWithFreshness());
85366
86483
  case WS_METHODS.cliCheckFreshness: {