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.
package/dist/index.js CHANGED
@@ -2478,7 +2478,9 @@ function createDefaultAppState() {
2478
2478
  lastObservedPid: null,
2479
2479
  lastRestartStatus: null,
2480
2480
  lastRestartReason: null,
2481
- instancesByProfileName: {}
2481
+ activity: [],
2482
+ instancesByProfileName: {},
2483
+ pendingRestartDebt: null
2482
2484
  },
2483
2485
  app: {
2484
2486
  lastAppVersion: null,
@@ -2582,6 +2584,9 @@ function asString(value) {
2582
2584
  function asNumberOrNull(value) {
2583
2585
  return typeof value === "number" && Number.isFinite(value) ? value : null;
2584
2586
  }
2587
+ function asBooleanOrNull(value) {
2588
+ return typeof value === "boolean" ? value : null;
2589
+ }
2585
2590
  function normalizeAppState(raw) {
2586
2591
  const defaults = createDefaultAppState();
2587
2592
  if (!isRecord2(raw)) {
@@ -2616,6 +2621,32 @@ function normalizeAppState(raw) {
2616
2621
  merged.officialCodex.lastRestartReason = asString(
2617
2622
  merged.officialCodex.lastRestartReason
2618
2623
  );
2624
+ merged.officialCodex.activity = Array.isArray(merged.officialCodex.activity) ? merged.officialCodex.activity.filter((entry) => isRecord2(entry)).map((entry) => ({
2625
+ id: asString(entry.id) ?? "",
2626
+ at: asString(entry.at) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
2627
+ 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",
2628
+ status: asString(entry.status) ?? "unknown",
2629
+ reason: asString(entry.reason),
2630
+ decisionId: asString(entry.decisionId),
2631
+ profileName: asString(entry.profileName),
2632
+ sourceProfileName: asString(entry.sourceProfileName),
2633
+ targetProfileName: asString(entry.targetProfileName),
2634
+ remainingPercent: asNumberOrNull(entry.remainingPercent),
2635
+ threshold: asNumberOrNull(entry.threshold),
2636
+ snapshotAgeMs: asNumberOrNull(entry.snapshotAgeMs),
2637
+ snapshotSource: asString(entry.snapshotSource),
2638
+ phase: asString(entry.phase),
2639
+ pid: asNumberOrNull(entry.pid),
2640
+ profileKeyHash: asString(entry.profileKeyHash),
2641
+ switchVerified: asBooleanOrNull(entry.switchVerified),
2642
+ restartRequested: asBooleanOrNull(entry.restartRequested),
2643
+ restartResult: asString(entry.restartResult),
2644
+ observedProfileName: asString(entry.observedProfileName),
2645
+ observedProfileKeyHash: asString(entry.observedProfileKeyHash),
2646
+ observedProfileMatchSource: asString(
2647
+ entry.observedProfileMatchSource
2648
+ )
2649
+ })).filter((entry) => entry.id && entry.at).slice(-50) : [];
2619
2650
  if (!isRecord2(merged.officialCodex.instancesByProfileName)) {
2620
2651
  merged.officialCodex.instancesByProfileName = {};
2621
2652
  } else {
@@ -2646,6 +2677,21 @@ function normalizeAppState(raw) {
2646
2677
  }
2647
2678
  merged.officialCodex.instancesByProfileName = nextInstances;
2648
2679
  }
2680
+ if (isRecord2(merged.officialCodex.pendingRestartDebt)) {
2681
+ const debt = merged.officialCodex.pendingRestartDebt;
2682
+ const targetProfileName = asString(debt.targetProfileName);
2683
+ merged.officialCodex.pendingRestartDebt = targetProfileName ? {
2684
+ targetProfileName,
2685
+ targetProfileKey: asString(debt.targetProfileKey),
2686
+ sourceProfileName: asString(debt.sourceProfileName),
2687
+ sourceProfileKey: asString(debt.sourceProfileKey),
2688
+ decisionId: asString(debt.decisionId),
2689
+ attempts: asNumberOrNull(debt.attempts) ?? 0,
2690
+ lastReason: asString(debt.lastReason)
2691
+ } : null;
2692
+ } else {
2693
+ merged.officialCodex.pendingRestartDebt = null;
2694
+ }
2649
2695
  merged.app.lastAppVersion = asString(merged.app.lastAppVersion);
2650
2696
  merged.app.pendingUpdateVersion = asString(merged.app.pendingUpdateVersion);
2651
2697
  merged.app.lastProfileName = asString(merged.app.lastProfileName);
@@ -2723,7 +2769,9 @@ function normalizeAppState(raw) {
2723
2769
  merged.ui.profiles.groupBy = asString(merged.ui.profiles.groupBy);
2724
2770
  merged.ui.profiles.planFilter = asString(merged.ui.profiles.planFilter);
2725
2771
  merged.ui.profiles.healthFilter = asString(merged.ui.profiles.healthFilter);
2726
- merged.ui.profiles.customGroupFilter = asString(merged.ui.profiles.customGroupFilter);
2772
+ merged.ui.profiles.customGroupFilter = asString(
2773
+ merged.ui.profiles.customGroupFilter
2774
+ );
2727
2775
  if (typeof merged.ui.profiles.toolbarOpen !== "boolean") {
2728
2776
  merged.ui.profiles.toolbarOpen = null;
2729
2777
  }
@@ -2731,13 +2779,15 @@ function normalizeAppState(raw) {
2731
2779
  merged.ui.profiles.collapsedSections = {};
2732
2780
  } else {
2733
2781
  merged.ui.profiles.collapsedSections = Object.fromEntries(
2734
- Object.entries(merged.ui.profiles.collapsedSections).flatMap(([key, value]) => {
2735
- const normalizedKey = asString(key);
2736
- if (!normalizedKey || typeof value !== "boolean") {
2737
- return [];
2782
+ Object.entries(merged.ui.profiles.collapsedSections).flatMap(
2783
+ ([key, value]) => {
2784
+ const normalizedKey = asString(key);
2785
+ if (!normalizedKey || typeof value !== "boolean") {
2786
+ return [];
2787
+ }
2788
+ return [[normalizedKey, value]];
2738
2789
  }
2739
- return [[normalizedKey, value]];
2740
- })
2790
+ )
2741
2791
  );
2742
2792
  }
2743
2793
  if (!isRecord2(merged.ui.onboarding)) {
@@ -2766,26 +2816,30 @@ function normalizeAppState(raw) {
2766
2816
  merged.ui.onboarding.nudgeCooldowns = {};
2767
2817
  } else {
2768
2818
  merged.ui.onboarding.nudgeCooldowns = Object.fromEntries(
2769
- Object.entries(merged.ui.onboarding.nudgeCooldowns).flatMap(([key, value]) => {
2770
- const normalizedKey = asString(key);
2771
- if (!normalizedKey || !Number.isFinite(value)) {
2772
- return [];
2819
+ Object.entries(merged.ui.onboarding.nudgeCooldowns).flatMap(
2820
+ ([key, value]) => {
2821
+ const normalizedKey = asString(key);
2822
+ if (!normalizedKey || !Number.isFinite(value)) {
2823
+ return [];
2824
+ }
2825
+ return [[normalizedKey, Number(value)]];
2773
2826
  }
2774
- return [[normalizedKey, Number(value)]];
2775
- })
2827
+ )
2776
2828
  );
2777
2829
  }
2778
2830
  if (!isRecord2(merged.ui.onboarding.nudgeDismissCount)) {
2779
2831
  merged.ui.onboarding.nudgeDismissCount = {};
2780
2832
  } else {
2781
2833
  merged.ui.onboarding.nudgeDismissCount = Object.fromEntries(
2782
- Object.entries(merged.ui.onboarding.nudgeDismissCount).flatMap(([key, value]) => {
2783
- const normalizedKey = asString(key);
2784
- if (!normalizedKey || !Number.isFinite(value)) {
2785
- return [];
2834
+ Object.entries(merged.ui.onboarding.nudgeDismissCount).flatMap(
2835
+ ([key, value]) => {
2836
+ const normalizedKey = asString(key);
2837
+ if (!normalizedKey || !Number.isFinite(value)) {
2838
+ return [];
2839
+ }
2840
+ return [[normalizedKey, Number(value)]];
2786
2841
  }
2787
- return [[normalizedKey, Number(value)]];
2788
- })
2842
+ )
2789
2843
  );
2790
2844
  }
2791
2845
  if (typeof merged.ui.onboarding.proUnlockedCelebrated !== "boolean") {
@@ -2795,17 +2849,21 @@ function normalizeAppState(raw) {
2795
2849
  merged.ui.projectThreadSelections = {};
2796
2850
  } else {
2797
2851
  merged.ui.projectThreadSelections = Object.fromEntries(
2798
- Object.entries(merged.ui.projectThreadSelections).flatMap(([projectId, threadId]) => {
2799
- const normalizedProjectId = asString(projectId);
2800
- if (!normalizedProjectId) {
2801
- return [];
2852
+ Object.entries(merged.ui.projectThreadSelections).flatMap(
2853
+ ([projectId, threadId]) => {
2854
+ const normalizedProjectId = asString(projectId);
2855
+ if (!normalizedProjectId) {
2856
+ return [];
2857
+ }
2858
+ const normalizedThreadId = typeof threadId === "string" && threadId.trim().length > 0 ? threadId.trim() : null;
2859
+ return [[normalizedProjectId, normalizedThreadId]];
2802
2860
  }
2803
- const normalizedThreadId = typeof threadId === "string" && threadId.trim().length > 0 ? threadId.trim() : null;
2804
- return [[normalizedProjectId, normalizedThreadId]];
2805
- })
2861
+ )
2806
2862
  );
2807
2863
  }
2808
- merged.ui.duplicateWarningDismissedKey = asString(merged.ui.duplicateWarningDismissedKey);
2864
+ merged.ui.duplicateWarningDismissedKey = asString(
2865
+ merged.ui.duplicateWarningDismissedKey
2866
+ );
2809
2867
  if (typeof merged.ui.pendingLicenseActivation !== "boolean") {
2810
2868
  merged.ui.pendingLicenseActivation = false;
2811
2869
  }
@@ -2824,14 +2882,16 @@ function normalizeAppState(raw) {
2824
2882
  merged.profileDashboard.customGroupsByAccountKey = {};
2825
2883
  } else {
2826
2884
  merged.profileDashboard.customGroupsByAccountKey = Object.fromEntries(
2827
- Object.entries(merged.profileDashboard.customGroupsByAccountKey).flatMap(([key, value]) => {
2828
- const normalizedKey = asString(key);
2829
- const normalizedValue = asString(value);
2830
- if (!normalizedKey || !normalizedValue) {
2831
- return [];
2885
+ Object.entries(merged.profileDashboard.customGroupsByAccountKey).flatMap(
2886
+ ([key, value]) => {
2887
+ const normalizedKey = asString(key);
2888
+ const normalizedValue = asString(value);
2889
+ if (!normalizedKey || !normalizedValue) {
2890
+ return [];
2891
+ }
2892
+ return [[normalizedKey, normalizedValue]];
2832
2893
  }
2833
- return [[normalizedKey, normalizedValue]];
2834
- })
2894
+ )
2835
2895
  );
2836
2896
  }
2837
2897
  {
@@ -2898,7 +2958,9 @@ function normalizeAppState(raw) {
2898
2958
  }
2899
2959
  merged.analytics.anonymousId = asString(merged.analytics.anonymousId);
2900
2960
  if (!merged.analytics.anonymousId) {
2901
- const legacyInstallId = asString(merged.telemetry?.installId);
2961
+ const legacyInstallId = asString(
2962
+ merged.telemetry?.installId
2963
+ );
2902
2964
  merged.analytics.anonymousId = legacyInstallId;
2903
2965
  }
2904
2966
  if (typeof merged.analytics.enabled !== "boolean") {
@@ -2969,10 +3031,18 @@ async function readLegacyAppStateFromDisk() {
2969
3031
  }
2970
3032
  }
2971
3033
  async function readAppStateFromStorage() {
2972
- return readDocument(resolveStorageDbPath(), APP_STATE_DOCUMENT, normalizeAppState);
3034
+ return readDocument(
3035
+ resolveStorageDbPath(),
3036
+ APP_STATE_DOCUMENT,
3037
+ normalizeAppState
3038
+ );
2973
3039
  }
2974
3040
  async function writeAppStateToStorage(state) {
2975
- return writeDocument(resolveStorageDbPath(), APP_STATE_DOCUMENT, normalizeAppState(state));
3041
+ return writeDocument(
3042
+ resolveStorageDbPath(),
3043
+ APP_STATE_DOCUMENT,
3044
+ normalizeAppState(state)
3045
+ );
2976
3046
  }
2977
3047
  async function ensureInitialized() {
2978
3048
  if (appStateCache) {
@@ -3985,16 +4055,133 @@ function logError(...args) {
3985
4055
  console.error(...args);
3986
4056
  }
3987
4057
  function logInfo(...args) {
3988
- if (isTestEnv && !isMocked(console.warn)) {
4058
+ if (isTestEnv && !isMocked(console.info)) {
3989
4059
  return;
3990
4060
  }
3991
- console.warn(...args);
4061
+ console.info(...args);
3992
4062
  }
3993
4063
 
3994
4064
  // ../../packages/runtime-codex/src/codex/rpc.ts
3995
4065
  var RPC_TIMEOUT_MS = 1e4;
4066
+ var ACTIVATION_TURN_TIMEOUT_MS = 9e4;
4067
+ var ACTIVATION_PROMPT = "Reply with exactly: ok.";
4068
+ var ACTIVATION_MODEL = "gpt-5.1-codex-mini";
3996
4069
  var MAX_STDERR_CAPTURE_CHARS = 32768;
3997
4070
  var REFRESH_TOKEN_REDEEMED_SNIPPET = "refresh token was already used";
4071
+ function isRecord4(value) {
4072
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
4073
+ }
4074
+ function asString3(value) {
4075
+ return typeof value === "string" ? value : value != null ? String(value) : "";
4076
+ }
4077
+ function createRpcMessageReader(rl) {
4078
+ const queue = [];
4079
+ const waiters = [];
4080
+ let closedError = null;
4081
+ const rejectAll = (error) => {
4082
+ closedError = error;
4083
+ while (waiters.length > 0) {
4084
+ const waiter = waiters.shift();
4085
+ clearTimeout(waiter.timer);
4086
+ waiter.reject(error);
4087
+ }
4088
+ };
4089
+ const onLine = (line) => {
4090
+ let parsed;
4091
+ try {
4092
+ parsed = JSON.parse(line);
4093
+ } catch {
4094
+ rejectAll(new Error("codex RPC returned malformed JSON"));
4095
+ return;
4096
+ }
4097
+ const waiterIndex = waiters.findIndex((waiter) => waiter.predicate(parsed));
4098
+ if (waiterIndex >= 0) {
4099
+ const [waiter] = waiters.splice(waiterIndex, 1);
4100
+ clearTimeout(waiter.timer);
4101
+ waiter.resolve(parsed);
4102
+ return;
4103
+ }
4104
+ queue.push(parsed);
4105
+ };
4106
+ const onClose = () => {
4107
+ rejectAll(new Error("codex RPC stream closed before response"));
4108
+ };
4109
+ rl.on("line", onLine);
4110
+ rl.on("close", onClose);
4111
+ return {
4112
+ read(predicate, timeoutMs) {
4113
+ const queuedIndex = queue.findIndex(predicate);
4114
+ if (queuedIndex >= 0) {
4115
+ const [message] = queue.splice(queuedIndex, 1);
4116
+ return Promise.resolve(message);
4117
+ }
4118
+ if (closedError) {
4119
+ return Promise.reject(closedError);
4120
+ }
4121
+ return new Promise((resolve, reject) => {
4122
+ const waiter = {
4123
+ predicate,
4124
+ resolve,
4125
+ reject,
4126
+ timer: setTimeout(
4127
+ () => {
4128
+ const index = waiters.indexOf(waiter);
4129
+ if (index >= 0) {
4130
+ waiters.splice(index, 1);
4131
+ }
4132
+ reject(new Error("codex RPC timed out"));
4133
+ },
4134
+ Math.max(1, timeoutMs)
4135
+ )
4136
+ };
4137
+ waiters.push(waiter);
4138
+ });
4139
+ },
4140
+ dispose() {
4141
+ rl.off("line", onLine);
4142
+ rl.off("close", onClose);
4143
+ while (waiters.length > 0) {
4144
+ const waiter = waiters.shift();
4145
+ clearTimeout(waiter.timer);
4146
+ }
4147
+ }
4148
+ };
4149
+ }
4150
+ function readThreadIdFromResult(result) {
4151
+ if (!isRecord4(result)) return null;
4152
+ const thread = isRecord4(result.thread) ? result.thread : {};
4153
+ const value = asString3(thread.id ?? result.threadId).trim();
4154
+ return value.length > 0 ? value : null;
4155
+ }
4156
+ function readTurnIdFromResult(result) {
4157
+ if (!isRecord4(result)) return null;
4158
+ const turn = isRecord4(result.turn) ? result.turn : {};
4159
+ const value = asString3(turn.id ?? result.turnId).trim();
4160
+ return value.length > 0 ? value : null;
4161
+ }
4162
+ function readTurnIdFromNotification(message) {
4163
+ if (!isRecord4(message.params)) return null;
4164
+ const turn = isRecord4(message.params.turn) ? message.params.turn : {};
4165
+ const value = asString3(turn.id ?? message.params.turnId).trim();
4166
+ return value.length > 0 ? value : null;
4167
+ }
4168
+ function readTurnStatusFromNotification(message) {
4169
+ if (!isRecord4(message.params)) {
4170
+ return { status: null, reason: null };
4171
+ }
4172
+ const turn = isRecord4(message.params.turn) ? message.params.turn : {};
4173
+ const error = isRecord4(turn.error) ? turn.error : null;
4174
+ return {
4175
+ status: asString3(turn.status).trim() || null,
4176
+ reason: error ? asString3(error.message).trim() || null : null
4177
+ };
4178
+ }
4179
+ function readLegacyEventTurnId(message) {
4180
+ if (!isRecord4(message.params)) return null;
4181
+ const msg = isRecord4(message.params.msg) ? message.params.msg : {};
4182
+ const value = asString3(message.params.id ?? msg.turn_id ?? msg.turnId).trim();
4183
+ return value.length > 0 ? value : null;
4184
+ }
3998
4185
  function parseTimestamp2(value) {
3999
4186
  if (typeof value !== "string" || value.trim().length === 0) {
4000
4187
  return null;
@@ -4045,8 +4232,17 @@ function extractAuthRecencyMs(content) {
4045
4232
  const rootLastRefresh = parseTimestamp2(parsed.last_refresh);
4046
4233
  const nestedLastRefresh = parseTimestamp2(parsed.tokens?.last_refresh);
4047
4234
  const rootAccessIssuedAt = extractJwtIssuedAtMs(parsed.access_token);
4048
- const nestedAccessIssuedAt = extractJwtIssuedAtMs(parsed.tokens?.access_token);
4049
- const candidates = [rootLastRefresh, nestedLastRefresh, rootAccessIssuedAt, nestedAccessIssuedAt].filter((value) => typeof value === "number" && Number.isFinite(value));
4235
+ const nestedAccessIssuedAt = extractJwtIssuedAtMs(
4236
+ parsed.tokens?.access_token
4237
+ );
4238
+ const candidates = [
4239
+ rootLastRefresh,
4240
+ nestedLastRefresh,
4241
+ rootAccessIssuedAt,
4242
+ nestedAccessIssuedAt
4243
+ ].filter(
4244
+ (value) => typeof value === "number" && Number.isFinite(value)
4245
+ );
4050
4246
  if (candidates.length === 0) {
4051
4247
  return null;
4052
4248
  }
@@ -4097,10 +4293,13 @@ function isRpcResponseForRequest(message, requestId) {
4097
4293
  }
4098
4294
  async function readRpcResponseById(rl, requestId, timeoutMs) {
4099
4295
  return new Promise((resolve, reject) => {
4100
- const timer = setTimeout(() => {
4101
- cleanup();
4102
- reject(new Error("codex RPC timed out"));
4103
- }, Math.max(1, timeoutMs));
4296
+ const timer = setTimeout(
4297
+ () => {
4298
+ cleanup();
4299
+ reject(new Error("codex RPC timed out"));
4300
+ },
4301
+ Math.max(1, timeoutMs)
4302
+ );
4104
4303
  const cleanup = () => {
4105
4304
  clearTimeout(timer);
4106
4305
  rl.off("line", onLine);
@@ -4250,7 +4449,11 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4250
4449
  throw new Error(formatRpcError("initialize", initializeResponse.error));
4251
4450
  }
4252
4451
  await sendPayload(child, { method: "initialized", params: {} });
4253
- await sendPayload(child, { id: 2, method: "account/rateLimits/read", params: {} });
4452
+ await sendPayload(child, {
4453
+ id: 2,
4454
+ method: "account/rateLimits/read",
4455
+ params: {}
4456
+ });
4254
4457
  const message = await readRpcResponseById(rl, 2, RPC_TIMEOUT_MS);
4255
4458
  if (message.error) {
4256
4459
  const base = formatRpcError("account/rateLimits/read", message.error);
@@ -4271,11 +4474,181 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
4271
4474
  if (shouldWriteBackAuth(initialSourceAuth, currentSourceAuth, updatedAuth)) {
4272
4475
  await import_node_fs5.promises.writeFile(sourceAuthPath, updatedAuth, "utf8");
4273
4476
  } else if (currentSourceAuth && currentSourceAuth !== updatedAuth && currentSourceAuth !== initialSourceAuth) {
4274
- logWarn("Skipped stale auth sync-back after rate-limit probe; source auth changed in flight.", {
4275
- sourceAuthPath,
4276
- currentRecencyMs: extractAuthRecencyMs(currentSourceAuth),
4277
- updatedRecencyMs: extractAuthRecencyMs(updatedAuth)
4278
- });
4477
+ logWarn(
4478
+ "Skipped stale auth sync-back after rate-limit probe; source auth changed in flight.",
4479
+ {
4480
+ sourceAuthPath,
4481
+ currentRecencyMs: extractAuthRecencyMs(currentSourceAuth),
4482
+ updatedRecencyMs: extractAuthRecencyMs(updatedAuth)
4483
+ }
4484
+ );
4485
+ }
4486
+ }
4487
+ } catch {
4488
+ }
4489
+ await import_node_fs5.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
4490
+ });
4491
+ }
4492
+ }
4493
+ async function activateResetWindowViaRpc(envOverride, options = {}) {
4494
+ const binaryPath = options.codexPath ?? await requireCodexCli();
4495
+ const tempHome = await import_node_fs5.promises.mkdtemp(
4496
+ import_node_path6.default.join(import_node_os2.default.tmpdir(), "codex-activation-")
4497
+ );
4498
+ const tempAuthPath = import_node_path6.default.join(tempHome, "auth.json");
4499
+ let initialSourceAuth = null;
4500
+ const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? import_node_path6.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path6.default.join(
4501
+ envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os2.default.homedir(),
4502
+ ".codex",
4503
+ "auth.json"
4504
+ ));
4505
+ try {
4506
+ initialSourceAuth = await import_node_fs5.promises.readFile(sourceAuthPath, "utf8").catch(() => null);
4507
+ if (!initialSourceAuth) {
4508
+ await import_node_fs5.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
4509
+ });
4510
+ return { status: "skipped", reason: "auth-missing" };
4511
+ }
4512
+ await import_node_fs5.promises.writeFile(tempAuthPath, initialSourceAuth, "utf8");
4513
+ } catch {
4514
+ await import_node_fs5.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
4515
+ });
4516
+ return { status: "skipped", reason: "auth-missing" };
4517
+ }
4518
+ const childEnv = {
4519
+ ...process.env,
4520
+ ...envOverride ?? {},
4521
+ HOME: tempHome,
4522
+ USERPROFILE: tempHome,
4523
+ CODEX_HOME: tempHome,
4524
+ CODEX_TELEMETRY_LABEL: "codex-reset-activation",
4525
+ ELECTRON_RUN_AS_NODE: "1"
4526
+ };
4527
+ const command = buildCodexCommand(
4528
+ binaryPath,
4529
+ ["-s", "read-only", "-a", "untrusted", "app-server"],
4530
+ childEnv
4531
+ );
4532
+ const child = (0, import_node_child_process2.spawn)(command.command, command.args, {
4533
+ stdio: ["pipe", "pipe", "pipe"],
4534
+ env: childEnv,
4535
+ shell: command.shell
4536
+ });
4537
+ const rl = import_node_readline.default.createInterface({
4538
+ input: child.stdout,
4539
+ crlfDelay: Infinity
4540
+ });
4541
+ const reader = createRpcMessageReader(rl);
4542
+ let stderrOutput = "";
4543
+ child.stderr?.on("data", (chunk) => {
4544
+ if (stderrOutput.length >= MAX_STDERR_CAPTURE_CHARS) {
4545
+ return;
4546
+ }
4547
+ const text = chunk.toString("utf8");
4548
+ const remaining = MAX_STDERR_CAPTURE_CHARS - stderrOutput.length;
4549
+ stderrOutput += text.slice(0, Math.max(0, remaining));
4550
+ });
4551
+ try {
4552
+ await sendPayload(child, {
4553
+ id: 1,
4554
+ method: "initialize",
4555
+ params: { clientInfo: { name: "codexuse", version: "0.0.0" } }
4556
+ });
4557
+ const initializeResponse = await reader.read(
4558
+ (message) => isRpcResponseForRequest(message, 1),
4559
+ RPC_TIMEOUT_MS
4560
+ );
4561
+ if (initializeResponse.error) {
4562
+ throw new Error(formatRpcError("initialize", initializeResponse.error));
4563
+ }
4564
+ await sendPayload(child, { method: "initialized", params: {} });
4565
+ await sendPayload(child, {
4566
+ id: 2,
4567
+ method: "thread/start",
4568
+ params: {
4569
+ cwd: tempHome,
4570
+ model: ACTIVATION_MODEL,
4571
+ approvalPolicy: "untrusted",
4572
+ sandbox: "read-only",
4573
+ experimentalRawEvents: false
4574
+ }
4575
+ });
4576
+ const threadResponse = await reader.read(
4577
+ (message) => isRpcResponseForRequest(message, 2),
4578
+ RPC_TIMEOUT_MS
4579
+ );
4580
+ if (threadResponse.error) {
4581
+ throw new Error(formatRpcError("thread/start", threadResponse.error));
4582
+ }
4583
+ const threadId = readThreadIdFromResult(threadResponse.result);
4584
+ if (!threadId) {
4585
+ throw new Error("thread/start response did not include a thread id.");
4586
+ }
4587
+ await sendPayload(child, {
4588
+ id: 3,
4589
+ method: "turn/start",
4590
+ params: {
4591
+ threadId,
4592
+ model: ACTIVATION_MODEL,
4593
+ effort: "low",
4594
+ input: [
4595
+ {
4596
+ type: "text",
4597
+ text: ACTIVATION_PROMPT,
4598
+ text_elements: []
4599
+ }
4600
+ ]
4601
+ }
4602
+ });
4603
+ const turnResponse = await reader.read(
4604
+ (message) => isRpcResponseForRequest(message, 3),
4605
+ RPC_TIMEOUT_MS
4606
+ );
4607
+ if (turnResponse.error) {
4608
+ const base = formatRpcError("turn/start", turnResponse.error);
4609
+ const hint = inferRefreshFailureHint(stderrOutput);
4610
+ if (hint && !base.toLowerCase().includes(hint)) {
4611
+ throw new Error(`${base}; ${hint}`);
4612
+ }
4613
+ throw new Error(base);
4614
+ }
4615
+ const turnId = readTurnIdFromResult(turnResponse.result);
4616
+ if (!turnId) {
4617
+ throw new Error("turn/start response did not include a turn id.");
4618
+ }
4619
+ const completed = await reader.read(
4620
+ (message) => message.method === "turn/completed" && readTurnIdFromNotification(message) === turnId || message.method === "codex/event/task_complete" && readLegacyEventTurnId(message) === turnId,
4621
+ ACTIVATION_TURN_TIMEOUT_MS
4622
+ );
4623
+ const turnStatus = readTurnStatusFromNotification(completed);
4624
+ if (turnStatus.status === "failed") {
4625
+ return {
4626
+ status: "failed",
4627
+ reason: turnStatus.reason ?? "turn-failed",
4628
+ threadId,
4629
+ turnId
4630
+ };
4631
+ }
4632
+ return { status: "completed", reason: null, threadId, turnId };
4633
+ } finally {
4634
+ child.kill();
4635
+ reader.dispose();
4636
+ rl.close();
4637
+ try {
4638
+ const updatedAuth = await import_node_fs5.promises.readFile(tempAuthPath, "utf8");
4639
+ if (updatedAuth.trim().length > 0) {
4640
+ const currentSourceAuth = await import_node_fs5.promises.readFile(sourceAuthPath, "utf8").catch(() => null);
4641
+ if (shouldWriteBackAuth(initialSourceAuth, currentSourceAuth, updatedAuth)) {
4642
+ await import_node_fs5.promises.writeFile(sourceAuthPath, updatedAuth, "utf8");
4643
+ } else if (currentSourceAuth && currentSourceAuth !== updatedAuth && currentSourceAuth !== initialSourceAuth) {
4644
+ logWarn(
4645
+ "Skipped stale auth sync-back after reset-window activation; source auth changed in flight.",
4646
+ {
4647
+ sourceAuthPath,
4648
+ currentRecencyMs: extractAuthRecencyMs(currentSourceAuth),
4649
+ updatedRecencyMs: extractAuthRecencyMs(updatedAuth)
4650
+ }
4651
+ );
4279
4652
  }
4280
4653
  }
4281
4654
  } catch {
@@ -5402,13 +5775,13 @@ var ProfileManager = class {
5402
5775
  const existingIdentity = this.resolveProfileIdentityFromData(profileName, existing, existingMetadata);
5403
5776
  const nextIdentity = this.resolveProfileIdentityFromData(profileName, normalized, metadata);
5404
5777
  if (existingIdentity && nextIdentity && existingIdentity !== nextIdentity) {
5405
- logWarn(
5778
+ logInfo(
5406
5779
  `Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`
5407
5780
  );
5408
5781
  return;
5409
5782
  }
5410
5783
  if (this.normalizeWorkspaceId(existing.workspace_id) !== this.normalizeWorkspaceId(normalized.workspace_id)) {
5411
- logWarn(
5784
+ logInfo(
5412
5785
  `Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different workspace.`
5413
5786
  );
5414
5787
  return;
@@ -5824,6 +6197,25 @@ var ProfileManager = class {
5824
6197
  });
5825
6198
  }
5826
6199
  }
6200
+ async activateResetWindow(name, options = {}) {
6201
+ const profileName = this.normalizeProfileName(name);
6202
+ try {
6203
+ return await this.enqueueProfileOperation(
6204
+ profileName,
6205
+ () => this.runWithPreparedProfileHome(
6206
+ profileName,
6207
+ (env) => activateResetWindowViaRpc(env, { codexPath: options.codexPath }),
6208
+ { syncFromActiveAuthBeforeAction: false }
6209
+ )
6210
+ );
6211
+ } finally {
6212
+ await this.enqueueAuthSwap(async () => {
6213
+ await this.syncActiveAuthFromProfileIfCurrent(profileName);
6214
+ }).catch((error) => {
6215
+ logWarn(`Failed to sync active auth after reset-window activation for '${profileName}':`, error);
6216
+ });
6217
+ }
6218
+ }
5827
6219
  /**
5828
6220
  * Rename a profile
5829
6221
  */
@@ -6276,7 +6668,7 @@ var MODEL_SLUG_ALIASES = {
6276
6668
  function asRecord2(value) {
6277
6669
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6278
6670
  }
6279
- function asString3(value) {
6671
+ function asString4(value) {
6280
6672
  if (typeof value !== "string") {
6281
6673
  return null;
6282
6674
  }
@@ -6287,7 +6679,7 @@ function asNumber(value) {
6287
6679
  return typeof value === "number" && Number.isFinite(value) ? value : null;
6288
6680
  }
6289
6681
  function normalizeIsoString(value) {
6290
- const normalized = asString3(value);
6682
+ const normalized = asString4(value);
6291
6683
  if (!normalized) {
6292
6684
  return null;
6293
6685
  }
@@ -6299,7 +6691,7 @@ function normalizeStringArray(value) {
6299
6691
  const result = [];
6300
6692
  const seen = /* @__PURE__ */ new Set();
6301
6693
  for (const entry of input) {
6302
- const normalized = asString3(entry);
6694
+ const normalized = asString4(entry);
6303
6695
  if (!normalized || seen.has(normalized)) {
6304
6696
  continue;
6305
6697
  }
@@ -6309,7 +6701,7 @@ function normalizeStringArray(value) {
6309
6701
  return result;
6310
6702
  }
6311
6703
  function normalizeReasoningEffort(value) {
6312
- const normalized = asString3(value)?.toLowerCase() ?? null;
6704
+ const normalized = asString4(value)?.toLowerCase() ?? null;
6313
6705
  return normalized && normalized.length > 0 ? normalized : null;
6314
6706
  }
6315
6707
  function normalizeRoutingStrategy(value) {
@@ -6343,7 +6735,7 @@ function normalizeAccountPoolExposedModels(value, fallbackToDefault) {
6343
6735
  const seen = /* @__PURE__ */ new Set();
6344
6736
  const input = Array.isArray(value) ? value : [];
6345
6737
  for (const entry of input) {
6346
- const raw = asString3(entry);
6738
+ const raw = asString4(entry);
6347
6739
  const normalized = normalizeCodexModelSlug(raw) ?? raw;
6348
6740
  if (!normalized || seen.has(normalized)) {
6349
6741
  continue;
@@ -6459,8 +6851,8 @@ function normalizeSessionStatus(value) {
6459
6851
  }
6460
6852
  function normalizeApiKeyRecord(id, value) {
6461
6853
  const record = asRecord2(value);
6462
- const tokenHash = asString3(record.tokenHash);
6463
- const tokenPreview = asString3(record.tokenPreview);
6854
+ const tokenHash = asString4(record.tokenHash);
6855
+ const tokenPreview = asString4(record.tokenPreview);
6464
6856
  const createdAt = normalizeIsoString(record.createdAt);
6465
6857
  if (!tokenHash || !tokenPreview || !createdAt) {
6466
6858
  return null;
@@ -6476,7 +6868,7 @@ function normalizeApiKeyRecord(id, value) {
6476
6868
  }
6477
6869
  function normalizeLegacySessionRecord(id, value) {
6478
6870
  const record = asRecord2(value);
6479
- const profileName = asString3(record.profileName);
6871
+ const profileName = asString4(record.profileName);
6480
6872
  const createdAt = normalizeIsoString(record.createdAt);
6481
6873
  const lastUsedAt = normalizeIsoString(record.lastUsedAt);
6482
6874
  if (!profileName || !createdAt || !lastUsedAt) {
@@ -6486,16 +6878,16 @@ function normalizeLegacySessionRecord(id, value) {
6486
6878
  return {
6487
6879
  id,
6488
6880
  affinityKind,
6489
- affinityKey: asString3(record.affinityKey),
6881
+ affinityKey: asString4(record.affinityKey),
6490
6882
  profileName,
6491
- threadId: asString3(record.threadId) ?? id,
6492
- model: asString3(record.model),
6883
+ threadId: asString4(record.threadId) ?? id,
6884
+ model: asString4(record.model),
6493
6885
  status: normalizeSessionStatus(record.status),
6494
- lastError: asString3(record.lastError),
6886
+ lastError: asString4(record.lastError),
6495
6887
  createdAt,
6496
6888
  lastUsedAt,
6497
6889
  expiresAt: normalizeIsoString(record.expiresAt),
6498
- lastResponseId: asString3(record.lastResponseId),
6890
+ lastResponseId: asString4(record.lastResponseId),
6499
6891
  responseIds: normalizeStringArray(record.responseIds).slice(0, MAX_RESPONSE_IDS_PER_SESSION)
6500
6892
  };
6501
6893
  }
@@ -6517,7 +6909,7 @@ function normalizeLegacyStore(value) {
6517
6909
  }
6518
6910
  const responseIndex = {};
6519
6911
  for (const [responseId, sessionId] of Object.entries(asRecord2(record.responseIndex))) {
6520
- const normalizedSessionId = asString3(sessionId);
6912
+ const normalizedSessionId = asString4(sessionId);
6521
6913
  if (normalizedSessionId) {
6522
6914
  responseIndex[responseId] = normalizedSessionId;
6523
6915
  }
@@ -6532,7 +6924,7 @@ function normalizeLegacyStore(value) {
6532
6924
  }
6533
6925
  function normalizeSessionRecord(id, value) {
6534
6926
  const record = asRecord2(value);
6535
- const activeSegmentId = asString3(record.activeSegmentId);
6927
+ const activeSegmentId = asString4(record.activeSegmentId);
6536
6928
  const createdAt = normalizeIsoString(record.createdAt);
6537
6929
  const lastUsedAt = normalizeIsoString(record.lastUsedAt);
6538
6930
  if (!activeSegmentId || !createdAt || !lastUsedAt) {
@@ -6542,25 +6934,25 @@ function normalizeSessionRecord(id, value) {
6542
6934
  return {
6543
6935
  id,
6544
6936
  affinityKind,
6545
- affinityKey: asString3(record.affinityKey),
6937
+ affinityKey: asString4(record.affinityKey),
6546
6938
  activeSegmentId,
6547
6939
  segmentIds: normalizeStringArray(record.segmentIds),
6548
- model: asString3(record.model),
6940
+ model: asString4(record.model),
6549
6941
  status: normalizeSessionStatus(record.status),
6550
- lastError: asString3(record.lastError),
6942
+ lastError: asString4(record.lastError),
6551
6943
  createdAt,
6552
6944
  lastUsedAt,
6553
6945
  expiresAt: normalizeIsoString(record.expiresAt),
6554
- lastResponseId: asString3(record.lastResponseId),
6946
+ lastResponseId: asString4(record.lastResponseId),
6555
6947
  responseIds: normalizeStringArray(record.responseIds).slice(0, MAX_RESPONSE_IDS_PER_SESSION),
6556
6948
  rolloverCount: asNumber(record.rolloverCount) ?? 0,
6557
- lastRolloverReason: asString3(record.lastRolloverReason)
6949
+ lastRolloverReason: asString4(record.lastRolloverReason)
6558
6950
  };
6559
6951
  }
6560
6952
  function normalizeSegmentRecord(id, value) {
6561
6953
  const record = asRecord2(value);
6562
- const sessionId = asString3(record.sessionId);
6563
- const profileName = asString3(record.profileName);
6954
+ const sessionId = asString4(record.sessionId);
6955
+ const profileName = asString4(record.profileName);
6564
6956
  const createdAt = normalizeIsoString(record.createdAt);
6565
6957
  const lastUsedAt = normalizeIsoString(record.lastUsedAt);
6566
6958
  if (!sessionId || !profileName || !createdAt || !lastUsedAt) {
@@ -6576,8 +6968,8 @@ function normalizeSegmentRecord(id, value) {
6576
6968
  }
6577
6969
  function normalizeResponseIndexRecord(value) {
6578
6970
  const record = asRecord2(value);
6579
- const sessionId = asString3(record.sessionId);
6580
- const segmentId = asString3(record.segmentId);
6971
+ const sessionId = asString4(record.sessionId);
6972
+ const segmentId = asString4(record.segmentId);
6581
6973
  const createdAt = normalizeIsoString(record.createdAt) ?? (/* @__PURE__ */ new Date(0)).toISOString();
6582
6974
  if (!sessionId || !segmentId) {
6583
6975
  return null;
@@ -7252,7 +7644,7 @@ async function handleAccountPoolCommand(args, version) {
7252
7644
  params,
7253
7645
  key: "accountPoolExposedModels",
7254
7646
  defaultValues: DEFAULT_ACCOUNT_POOL_EXPOSED_MODELS,
7255
- normalizeValue: (value) => normalizeCodexModelSlug(value) ?? asString3(value)
7647
+ normalizeValue: (value) => normalizeCodexModelSlug(value) ?? asString4(value)
7256
7648
  });
7257
7649
  return;
7258
7650
  case "reasoning":
@@ -8000,18 +8392,17 @@ async function assertProfileCreationAllowed(profileManager) {
8000
8392
 
8001
8393
  // ../../packages/runtime-codex/src/codex/officialAppRestart.ts
8002
8394
  var import_node_child_process7 = require("child_process");
8395
+ var import_node_crypto6 = require("crypto");
8003
8396
  var import_node_fs8 = require("fs");
8004
8397
  var import_node_os5 = require("os");
8005
8398
  var import_node_path11 = __toESM(require("path"), 1);
8006
- var RESTART_COOLDOWN_MS = 6e4;
8007
8399
  var COMMAND_TIMEOUT_MS = 3e3;
8008
8400
  var EXIT_WAIT_MS = 1e4;
8009
8401
  var LAUNCH_WAIT_MS = 15e3;
8010
8402
  var POLL_INTERVAL_MS = 250;
8011
8403
  var APP_DISCOVERY_CACHE_TTL_MS = 6e4;
8012
8404
  var APP_DISCOVERY_MISS_CACHE_TTL_MS = 5e3;
8013
- var restartPromise = null;
8014
- var lastRestartStartedAt = 0;
8405
+ var OFFICIAL_CODEX_ACTIVITY_LIMIT = 50;
8015
8406
  var profileActionLock = Promise.resolve();
8016
8407
  var appDiscoveryCache = null;
8017
8408
  function logOfficialCodexRestart(level, message, context) {
@@ -8022,6 +8413,55 @@ function logOfficialCodexRestart(level, message, context) {
8022
8413
  }
8023
8414
  logger(`[official-codex-restart] ${message}`);
8024
8415
  }
8416
+ function hashProfileKey(profileKey) {
8417
+ if (!profileKey) {
8418
+ return null;
8419
+ }
8420
+ return (0, import_node_crypto6.createHash)("sha256").update(profileKey).digest("hex").slice(0, 12);
8421
+ }
8422
+ async function recordOfficialCodexActivity(input) {
8423
+ const now = /* @__PURE__ */ new Date();
8424
+ const entry = {
8425
+ id: `${now.getTime().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
8426
+ at: now.toISOString(),
8427
+ kind: input.kind,
8428
+ status: input.status,
8429
+ reason: input.reason ?? null,
8430
+ decisionId: input.decisionId ?? null,
8431
+ profileName: input.profileName ?? null,
8432
+ sourceProfileName: input.sourceProfileName ?? null,
8433
+ targetProfileName: input.targetProfileName ?? null,
8434
+ remainingPercent: input.remainingPercent ?? null,
8435
+ threshold: input.threshold ?? null,
8436
+ snapshotAgeMs: input.snapshotAgeMs ?? null,
8437
+ snapshotSource: input.snapshotSource ?? null,
8438
+ phase: input.phase ?? null,
8439
+ pid: input.pid ?? null,
8440
+ profileKeyHash: input.profileKeyHash ?? hashProfileKey(input.profileKey),
8441
+ switchVerified: input.switchVerified ?? null,
8442
+ restartRequested: input.restartRequested ?? null,
8443
+ restartResult: input.restartResult ?? null,
8444
+ observedProfileName: input.observedProfileName ?? null,
8445
+ observedProfileKeyHash: input.observedProfileKeyHash ?? hashProfileKey(input.observedProfileKey),
8446
+ observedProfileMatchSource: input.observedProfileMatchSource ?? null
8447
+ };
8448
+ try {
8449
+ await updateAppState((state) => ({
8450
+ officialCodex: {
8451
+ activity: [...state.officialCodex.activity, entry].slice(
8452
+ -OFFICIAL_CODEX_ACTIVITY_LIMIT
8453
+ )
8454
+ }
8455
+ }));
8456
+ } catch (error) {
8457
+ logOfficialCodexRestart("warn", "Failed to record official Codex activity.", {
8458
+ kind: entry.kind,
8459
+ status: entry.status,
8460
+ profileKeyHash: entry.profileKeyHash,
8461
+ error: error instanceof Error ? error.message : String(error)
8462
+ });
8463
+ }
8464
+ }
8025
8465
  function runCommand2(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
8026
8466
  return new Promise((resolve) => {
8027
8467
  const child = (0, import_node_child_process7.spawn)(command, args, {
@@ -8055,44 +8495,316 @@ function runCommand2(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
8055
8495
  });
8056
8496
  });
8057
8497
  }
8058
- async function readBundleString(appPath, key) {
8059
- const plistPath = import_node_path11.default.join(appPath, "Contents", "Info.plist");
8060
- if (!(0, import_node_fs8.existsSync)(plistPath)) {
8061
- return null;
8498
+ function enqueueProfileAction(work) {
8499
+ const run = profileActionLock.then(work, work);
8500
+ profileActionLock = run.then(
8501
+ () => void 0,
8502
+ () => void 0
8503
+ );
8504
+ return run;
8505
+ }
8506
+ async function readProcessRows() {
8507
+ const result = await runCommand2(
8508
+ "/bin/ps",
8509
+ ["-axo", "pid=,ppid=,lstart=,args="],
8510
+ 2e3
8511
+ );
8512
+ if (result.code !== 0) {
8513
+ return [];
8062
8514
  }
8063
- const result = await runCommand2("/usr/bin/plutil", [
8064
- "-extract",
8065
- key,
8066
- "raw",
8067
- "-o",
8068
- "-",
8069
- plistPath
8070
- ]);
8515
+ return result.stdout.split(/\r?\n/).flatMap((line) => {
8516
+ const match = line.match(
8517
+ /^\s*(\d+)\s+(\d+)\s+([A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d+\s+\d+:\d+:\d+\s+\d{4})\s+(.+)$/
8518
+ );
8519
+ if (!match) {
8520
+ return [];
8521
+ }
8522
+ const pid = Number(match[1]);
8523
+ const ppid = Number(match[2]);
8524
+ const parsedStartedAt = Date.parse(match[3] ?? "");
8525
+ const startedAt = Number.isFinite(parsedStartedAt) ? parsedStartedAt : null;
8526
+ const args = match[4] ?? "";
8527
+ if (!Number.isInteger(pid) || !Number.isInteger(ppid) || !args) {
8528
+ return [];
8529
+ }
8530
+ return [{ pid, ppid, startedAt, args }];
8531
+ });
8532
+ }
8533
+ function getMainExecutablePath(candidate) {
8534
+ return import_node_path11.default.join(candidate.appPath, "Contents", "MacOS", candidate.executableName);
8535
+ }
8536
+ function isMainProcessRow(row, candidate) {
8537
+ return row.args.includes(getMainExecutablePath(candidate));
8538
+ }
8539
+ function findAppServerPid(rows, mainPid) {
8540
+ return rows.find(
8541
+ (row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
8542
+ )?.pid ?? null;
8543
+ }
8544
+ async function processMatchesProfileHome(pid, profileHome) {
8545
+ const result = await runCommand2(
8546
+ "/bin/ps",
8547
+ ["eww", "-p", String(pid), "-o", "command="],
8548
+ 2e3
8549
+ );
8071
8550
  if (result.code !== 0) {
8072
- return null;
8551
+ return false;
8073
8552
  }
8074
- const bundleId = result.stdout.trim();
8075
- return bundleId.length > 0 ? bundleId : null;
8553
+ return result.stdout.includes(`CODEX_HOME=${profileHome}`) || result.stdout.includes(profileHome);
8076
8554
  }
8077
- async function discoverCodexAppCandidates() {
8078
- const now = Date.now();
8079
- const envPath = process.env.CODEXUSE_OFFICIAL_CODEX_APP_PATH?.trim() || null;
8080
- if (appDiscoveryCache && appDiscoveryCache.envPath === envPath && appDiscoveryCache.expiresAt > now) {
8081
- return appDiscoveryCache.candidates;
8555
+ async function findAppServerPidForProfile(rows, mainPid, profileHome) {
8556
+ const candidates = rows.filter(
8557
+ (row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
8558
+ );
8559
+ for (const row of candidates) {
8560
+ if (row.args.includes(profileHome) || await processMatchesProfileHome(row.pid, profileHome)) {
8561
+ return row.pid;
8562
+ }
8082
8563
  }
8083
- const rawPaths = /* @__PURE__ */ new Set();
8084
- if (envPath) {
8085
- rawPaths.add(envPath);
8564
+ return null;
8565
+ }
8566
+ async function describeUnmanagedProfileHint(appServerPid) {
8567
+ if (!appServerPid) {
8568
+ return {};
8086
8569
  }
8087
- rawPaths.add("/Applications/Codex.app");
8088
- rawPaths.add(import_node_path11.default.join((0, import_node_os5.homedir)(), "Applications", "Codex.app"));
8089
- const mdfind = await runCommand2(
8090
- "/usr/bin/mdfind",
8091
- ["kMDItemFSName == 'Codex.app'"],
8092
- 1500
8570
+ const result = await runCommand2(
8571
+ "/bin/ps",
8572
+ ["eww", "-p", String(appServerPid), "-o", "command="],
8573
+ 2e3
8093
8574
  );
8094
- if (mdfind.code === 0) {
8095
- for (const line of mdfind.stdout.split(/\r?\n/)) {
8575
+ if (result.code !== 0) {
8576
+ return {};
8577
+ }
8578
+ const telemetryMatch = result.stdout.match(
8579
+ /CODEX_TELEMETRY_LABEL=codexuse-profile-([^\s]+)/
8580
+ );
8581
+ if (telemetryMatch?.[1]) {
8582
+ const encodedProfileName = telemetryMatch[1];
8583
+ let profileName = encodedProfileName;
8584
+ try {
8585
+ profileName = decodeURIComponent(encodedProfileName);
8586
+ } catch {
8587
+ profileName = encodedProfileName;
8588
+ }
8589
+ return {
8590
+ profileName,
8591
+ profileMatchSource: "telemetry-label"
8592
+ };
8593
+ }
8594
+ const profileHomeMatch = result.stdout.match(/profile-homes\/([^\s]+)/);
8595
+ if (profileHomeMatch?.[1]) {
8596
+ return {
8597
+ profileName: profileHomeMatch[1],
8598
+ profileMatchSource: "profile-home"
8599
+ };
8600
+ }
8601
+ return {};
8602
+ }
8603
+ function getDescendantPids(rootPid, rows) {
8604
+ const descendants = [];
8605
+ const queue = [rootPid];
8606
+ const seen = /* @__PURE__ */ new Set();
8607
+ while (queue.length > 0) {
8608
+ const current = queue.shift();
8609
+ if (seen.has(current)) {
8610
+ continue;
8611
+ }
8612
+ seen.add(current);
8613
+ for (const row of rows) {
8614
+ if (row.ppid !== current || seen.has(row.pid)) {
8615
+ continue;
8616
+ }
8617
+ descendants.push(row.pid);
8618
+ queue.push(row.pid);
8619
+ }
8620
+ }
8621
+ return descendants;
8622
+ }
8623
+ async function waitForNewMainPid(candidate, previousPids) {
8624
+ const deadline = Date.now() + LAUNCH_WAIT_MS;
8625
+ while (Date.now() < deadline) {
8626
+ const rows = await readProcessRows();
8627
+ const pid = rows.find((row) => isMainProcessRow(row, candidate) && !previousPids.has(row.pid))?.pid ?? null;
8628
+ if (pid !== null) {
8629
+ return pid;
8630
+ }
8631
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8632
+ }
8633
+ return null;
8634
+ }
8635
+ async function waitForMainPid(candidate, pid, timeoutMs) {
8636
+ const deadline = Date.now() + timeoutMs;
8637
+ while (Date.now() < deadline) {
8638
+ const rows = await readProcessRows();
8639
+ if (rows.some((row) => row.pid === pid && isMainProcessRow(row, candidate))) {
8640
+ return true;
8641
+ }
8642
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8643
+ }
8644
+ return false;
8645
+ }
8646
+ async function waitForAppServerPid(mainPid, profileHome) {
8647
+ const deadline = Date.now() + LAUNCH_WAIT_MS;
8648
+ while (Date.now() < deadline) {
8649
+ const pid = await findAppServerPidForProfile(
8650
+ await readProcessRows(),
8651
+ mainPid,
8652
+ profileHome
8653
+ );
8654
+ if (pid !== null) {
8655
+ return pid;
8656
+ }
8657
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8658
+ }
8659
+ return null;
8660
+ }
8661
+ async function waitForMainPidExit(pid, timeoutMs) {
8662
+ const deadline = Date.now() + timeoutMs;
8663
+ while (Date.now() < deadline) {
8664
+ const rows = await readProcessRows();
8665
+ if (!rows.some((row) => row.pid === pid)) {
8666
+ return true;
8667
+ }
8668
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8669
+ }
8670
+ return false;
8671
+ }
8672
+ function managedInstanceToPayload(instance, running, appServerPid = instance.appServerPid, startedAt = null) {
8673
+ const runningStartedAt = startedAt ?? instance.launchedAt;
8674
+ return {
8675
+ profileName: instance.profileName,
8676
+ profileKey: instance.profileKey,
8677
+ appPath: instance.appPath,
8678
+ bundleId: instance.bundleId,
8679
+ pid: running ? instance.pid : null,
8680
+ appServerPid: running ? appServerPid : null,
8681
+ running,
8682
+ startedAt: running ? toIso(runningStartedAt) : null,
8683
+ uptimeMs: running && typeof runningStartedAt === "number" ? Math.max(0, Date.now() - runningStartedAt) : null,
8684
+ launchedAt: toIso(instance.launchedAt),
8685
+ lastVerifiedAt: toIso(instance.lastVerifiedAt),
8686
+ lastStatus: instance.lastStatus,
8687
+ lastError: instance.lastError
8688
+ };
8689
+ }
8690
+ async function patchManagedInstance(instance) {
8691
+ await patchAppState({
8692
+ officialCodex: {
8693
+ instancesByProfileName: {
8694
+ [instance.profileName]: instance
8695
+ }
8696
+ }
8697
+ });
8698
+ }
8699
+ async function readManagedInstance(profileName) {
8700
+ const state = await getAppState();
8701
+ return state.officialCodex.instancesByProfileName[profileName] ?? null;
8702
+ }
8703
+ async function resolveInstanceRuntimeState(args) {
8704
+ const pid = args.instance.pid;
8705
+ if (!pid) {
8706
+ return { running: false, appServerPid: null, startedAt: null };
8707
+ }
8708
+ const mainRow = args.rows.find((row) => row.pid === pid);
8709
+ if (!mainRow || !isMainProcessRow(mainRow, args.candidate)) {
8710
+ return { running: false, appServerPid: null, startedAt: null };
8711
+ }
8712
+ const appServerPid = args.instance.profileHome ? await findAppServerPidForProfile(args.rows, pid, args.instance.profileHome) : findAppServerPid(args.rows, pid);
8713
+ if (args.instance.profileHome && appServerPid === null) {
8714
+ return { running: false, appServerPid: null, startedAt: null };
8715
+ }
8716
+ return {
8717
+ running: true,
8718
+ appServerPid,
8719
+ startedAt: mainRow.startedAt
8720
+ };
8721
+ }
8722
+ async function openCodexWithProfileHome(candidate, profileHome, profileName, previousPids) {
8723
+ const executablePath = getMainExecutablePath(candidate);
8724
+ if (!(0, import_node_fs8.existsSync)(executablePath)) {
8725
+ return { opened: false, pid: null };
8726
+ }
8727
+ const telemetryLabel = `codexuse-profile-${encodeURIComponent(profileName)}`;
8728
+ let child;
8729
+ try {
8730
+ child = (0, import_node_child_process7.spawn)(executablePath, [], {
8731
+ detached: true,
8732
+ env: {
8733
+ ...process.env,
8734
+ CODEX_HOME: profileHome,
8735
+ CODEX_TELEMETRY_LABEL: telemetryLabel
8736
+ },
8737
+ stdio: "ignore"
8738
+ });
8739
+ } catch {
8740
+ return { opened: false, pid: null };
8741
+ }
8742
+ const childPid = child.pid ?? null;
8743
+ child.unref();
8744
+ const spawnError = await new Promise((resolve) => {
8745
+ let settled = false;
8746
+ const settle = (failed) => {
8747
+ if (settled) {
8748
+ return;
8749
+ }
8750
+ settled = true;
8751
+ resolve(failed);
8752
+ };
8753
+ child.once("error", () => settle(true));
8754
+ setTimeout(() => settle(false), POLL_INTERVAL_MS);
8755
+ });
8756
+ if (spawnError) {
8757
+ return { opened: false, pid: null };
8758
+ }
8759
+ if (childPid !== null) {
8760
+ const verified = await waitForMainPid(candidate, childPid, 2e3);
8761
+ if (verified) {
8762
+ return { opened: true, pid: childPid };
8763
+ }
8764
+ }
8765
+ return {
8766
+ opened: true,
8767
+ pid: await waitForNewMainPid(candidate, previousPids)
8768
+ };
8769
+ }
8770
+ async function readBundleString(appPath, key) {
8771
+ const plistPath = import_node_path11.default.join(appPath, "Contents", "Info.plist");
8772
+ if (!(0, import_node_fs8.existsSync)(plistPath)) {
8773
+ return null;
8774
+ }
8775
+ const result = await runCommand2("/usr/bin/plutil", [
8776
+ "-extract",
8777
+ key,
8778
+ "raw",
8779
+ "-o",
8780
+ "-",
8781
+ plistPath
8782
+ ]);
8783
+ if (result.code !== 0) {
8784
+ return null;
8785
+ }
8786
+ const bundleId = result.stdout.trim();
8787
+ return bundleId.length > 0 ? bundleId : null;
8788
+ }
8789
+ async function discoverCodexAppCandidates() {
8790
+ const now = Date.now();
8791
+ const envPath = process.env.CODEXUSE_OFFICIAL_CODEX_APP_PATH?.trim() || null;
8792
+ if (appDiscoveryCache && appDiscoveryCache.envPath === envPath && appDiscoveryCache.expiresAt > now) {
8793
+ return appDiscoveryCache.candidates;
8794
+ }
8795
+ const rawPaths = /* @__PURE__ */ new Set();
8796
+ if (envPath) {
8797
+ rawPaths.add(envPath);
8798
+ }
8799
+ rawPaths.add("/Applications/Codex.app");
8800
+ rawPaths.add(import_node_path11.default.join((0, import_node_os5.homedir)(), "Applications", "Codex.app"));
8801
+ const mdfind = await runCommand2(
8802
+ "/usr/bin/mdfind",
8803
+ ["kMDItemFSName == 'Codex.app'"],
8804
+ 1500
8805
+ );
8806
+ if (mdfind.code === 0) {
8807
+ for (const line of mdfind.stdout.split(/\r?\n/)) {
8096
8808
  const trimmed = line.trim();
8097
8809
  if (trimmed.endsWith("/Codex.app")) {
8098
8810
  rawPaths.add(trimmed);
@@ -8187,17 +8899,6 @@ async function resolveCodexAppTarget() {
8187
8899
  ]);
8188
8900
  return { candidate: fallback, runningPids, mainPids };
8189
8901
  }
8190
- async function waitForCodexExit(candidate, timeoutMs) {
8191
- const deadline = Date.now() + timeoutMs;
8192
- while (Date.now() < deadline) {
8193
- const pids = await getRunningCodexPids(candidate);
8194
- if (pids.length === 0) {
8195
- return true;
8196
- }
8197
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8198
- }
8199
- return false;
8200
- }
8201
8902
  async function signalPids(pids, signal) {
8202
8903
  const signaled = [];
8203
8904
  for (const pid of pids) {
@@ -8209,25 +8910,8 @@ async function signalPids(pids, signal) {
8209
8910
  }
8210
8911
  return signaled;
8211
8912
  }
8212
- async function openCodex(candidate) {
8213
- const byBundle = await runCommand2("/usr/bin/open", ["-b", candidate.bundleId]);
8214
- if (byBundle.code === 0) {
8215
- return true;
8216
- }
8217
- const byPath = await runCommand2("/usr/bin/open", [candidate.appPath]);
8218
- return byPath.code === 0;
8219
- }
8220
- async function waitForCodexLaunch(candidate) {
8221
- const deadline = Date.now() + LAUNCH_WAIT_MS;
8222
- while (Date.now() < deadline) {
8223
- const pids = await getRunningCodexMainPids(candidate);
8224
- const pid = pids[0] ?? null;
8225
- if (pid !== null) {
8226
- return pid;
8227
- }
8228
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8229
- }
8230
- return null;
8913
+ function toIso(value) {
8914
+ return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : null;
8231
8915
  }
8232
8916
  async function rememberProfileSwitch(profileKey) {
8233
8917
  await patchAppState({
@@ -8250,157 +8934,360 @@ async function rememberRestartResult(args) {
8250
8934
  }
8251
8935
  });
8252
8936
  }
8253
- async function restartOfficialCodexAppOnce(options) {
8937
+ function recordOfficialCodexRestartResult(args) {
8938
+ return rememberRestartResult(args);
8939
+ }
8940
+ function recordOfficialCodexProfileSwitch(profileKey) {
8941
+ return rememberProfileSwitch(profileKey);
8942
+ }
8943
+ async function getOfficialCodexProfileInstances() {
8254
8944
  if (process.platform !== "darwin") {
8255
- await rememberRestartResult({ status: "skipped", reason: "unsupported-platform" });
8256
- return { status: "skipped", reason: "unsupported-platform" };
8257
- }
8258
- const now = Date.now();
8259
- if (options.source !== "manual" && lastRestartStartedAt > 0 && now - lastRestartStartedAt < RESTART_COOLDOWN_MS) {
8260
- await rememberRestartResult({
8261
- status: "skipped",
8262
- reason: "cooldown",
8263
- profileKey: options.currentProfileKey
8264
- });
8265
- return { status: "skipped", reason: "cooldown" };
8945
+ return {
8946
+ state: "unsupported",
8947
+ appPath: null,
8948
+ bundleId: null,
8949
+ version: null,
8950
+ instances: [],
8951
+ unmanaged: []
8952
+ };
8266
8953
  }
8267
- const { candidate, runningPids, mainPids } = await resolveCodexAppTarget();
8954
+ const { candidate, mainPids } = await resolveCodexAppTarget();
8268
8955
  if (!candidate) {
8269
- await rememberRestartResult({
8270
- status: "skipped",
8271
- reason: "official-codex-app-not-found",
8272
- profileKey: options.currentProfileKey
8956
+ return {
8957
+ state: "not-found",
8958
+ appPath: null,
8959
+ bundleId: null,
8960
+ version: null,
8961
+ instances: [],
8962
+ unmanaged: []
8963
+ };
8964
+ }
8965
+ const [state, rows] = await Promise.all([getAppState(), readProcessRows()]);
8966
+ const managed = [];
8967
+ for (const instance of Object.values(state.officialCodex.instancesByProfileName)) {
8968
+ const runtime = await resolveInstanceRuntimeState({ instance, candidate, rows });
8969
+ managed.push(
8970
+ managedInstanceToPayload(
8971
+ instance,
8972
+ runtime.running,
8973
+ runtime.appServerPid,
8974
+ runtime.startedAt
8975
+ )
8976
+ );
8977
+ }
8978
+ managed.sort((a, b) => a.profileName.localeCompare(b.profileName));
8979
+ const managedPids = new Set(
8980
+ managed.flatMap((entry) => entry.running && entry.pid ? [entry.pid] : [])
8981
+ );
8982
+ const unmanaged = [];
8983
+ for (const pid of mainPids.filter((entry) => !managedPids.has(entry))) {
8984
+ const appServerPid = findAppServerPid(rows, pid);
8985
+ const startedAt = rows.find((row) => row.pid === pid)?.startedAt ?? null;
8986
+ unmanaged.push({
8987
+ pid,
8988
+ appServerPid,
8989
+ startedAt: toIso(startedAt),
8990
+ uptimeMs: typeof startedAt === "number" ? Math.max(0, Date.now() - startedAt) : null,
8991
+ ...await describeUnmanagedProfileHint(appServerPid)
8273
8992
  });
8274
- return { status: "skipped", reason: "official-codex-app-not-found" };
8275
8993
  }
8276
- const appIsRunning = mainPids.length > 0;
8277
- if (!appIsRunning && !options.launchIfNotRunning) {
8278
- await rememberRestartResult({
8994
+ return {
8995
+ state: "found",
8996
+ appPath: candidate.appPath,
8997
+ bundleId: candidate.bundleId,
8998
+ version: candidate.version,
8999
+ instances: managed,
9000
+ unmanaged
9001
+ };
9002
+ }
9003
+ function launchOfficialCodexProfileInstance(options) {
9004
+ return enqueueProfileAction(() => launchOfficialCodexProfileInstanceOnce(options));
9005
+ }
9006
+ async function launchOfficialCodexProfileInstanceOnce(options) {
9007
+ if (process.platform !== "darwin") {
9008
+ return {
8279
9009
  status: "skipped",
8280
- reason: "official-codex-app-not-running",
8281
- profileKey: options.currentProfileKey
8282
- });
8283
- return { status: "skipped", reason: "official-codex-app-not-running" };
9010
+ profileName: options.profileName,
9011
+ instance: null,
9012
+ reason: "unsupported-platform"
9013
+ };
8284
9014
  }
8285
- if (options.source !== "manual") {
8286
- lastRestartStartedAt = Date.now();
9015
+ const { candidate, mainPids } = await resolveCodexAppTarget();
9016
+ if (!candidate) {
9017
+ return {
9018
+ status: "skipped",
9019
+ profileName: options.profileName,
9020
+ instance: null,
9021
+ reason: "official-codex-app-not-found"
9022
+ };
8287
9023
  }
8288
- if (appIsRunning) {
8289
- logOfficialCodexRestart("warn", "Force killing official Codex before relaunch.", {
8290
- appPath: candidate.appPath,
8291
- bundleId: candidate.bundleId,
8292
- source: options.source,
8293
- runningPids,
8294
- mainPids
8295
- });
8296
- const killedPids = await signalPids(runningPids, "SIGKILL");
8297
- logOfficialCodexRestart("warn", "Sent SIGKILL to official Codex pids.", {
8298
- appPath: candidate.appPath,
8299
- bundleId: candidate.bundleId,
8300
- source: options.source,
8301
- killedPids
8302
- });
8303
- const exited = await waitForCodexExit(candidate, EXIT_WAIT_MS);
8304
- if (!exited) {
8305
- const remainingPids = await getRunningCodexPids(candidate);
8306
- logOfficialCodexRestart("error", "Official Codex pids remained after force kill.", {
8307
- appPath: candidate.appPath,
8308
- bundleId: candidate.bundleId,
8309
- source: options.source,
8310
- remainingPids
8311
- });
8312
- const failed = {
8313
- status: "failed",
8314
- appPath: candidate.appPath,
8315
- bundleId: candidate.bundleId,
8316
- reason: "force-quit-timeout",
8317
- phase: "quit"
9024
+ const existing = await readManagedInstance(options.profileName);
9025
+ if (existing) {
9026
+ const rows = await readProcessRows();
9027
+ const runtime = await resolveInstanceRuntimeState({ instance: existing, candidate, rows });
9028
+ if (runtime.running) {
9029
+ const verified = {
9030
+ ...existing,
9031
+ profileHome: existing.profileHome ?? options.profileHome,
9032
+ appServerPid: runtime.appServerPid,
9033
+ lastVerifiedAt: Date.now(),
9034
+ lastStatus: "already-running",
9035
+ lastError: null
9036
+ };
9037
+ await patchManagedInstance(verified);
9038
+ return {
9039
+ status: "already-running",
9040
+ profileName: options.profileName,
9041
+ instance: managedInstanceToPayload(
9042
+ verified,
9043
+ true,
9044
+ runtime.appServerPid,
9045
+ runtime.startedAt
9046
+ ),
9047
+ reason: null
8318
9048
  };
8319
- await rememberRestartResult({
8320
- status: failed.status,
8321
- reason: failed.reason,
8322
- profileKey: options.currentProfileKey
8323
- });
8324
- return failed;
8325
9049
  }
8326
- logOfficialCodexRestart("info", "Official Codex exited after force kill.", {
8327
- appPath: candidate.appPath,
8328
- bundleId: candidate.bundleId,
8329
- source: options.source
8330
- });
8331
9050
  }
8332
- const opened = await openCodex(candidate);
8333
- if (!opened) {
9051
+ const launch = await openCodexWithProfileHome(
9052
+ candidate,
9053
+ options.profileHome,
9054
+ options.profileName,
9055
+ new Set(mainPids)
9056
+ );
9057
+ if (!launch.opened) {
8334
9058
  const failed = {
8335
- status: "failed",
9059
+ profileName: options.profileName,
9060
+ profileKey: options.profileKey,
9061
+ profileHome: options.profileHome,
8336
9062
  appPath: candidate.appPath,
8337
9063
  bundleId: candidate.bundleId,
8338
- reason: "open-failed",
8339
- phase: "launch"
9064
+ pid: null,
9065
+ appServerPid: null,
9066
+ launchedAt: null,
9067
+ lastVerifiedAt: null,
9068
+ lastStatus: "failed",
9069
+ lastError: "open-failed"
9070
+ };
9071
+ await patchManagedInstance(failed);
9072
+ return {
9073
+ status: "failed",
9074
+ profileName: options.profileName,
9075
+ instance: managedInstanceToPayload(failed, false),
9076
+ reason: "open-failed"
8340
9077
  };
8341
- await rememberRestartResult({
8342
- status: failed.status,
8343
- reason: failed.reason,
8344
- profileKey: options.currentProfileKey
8345
- });
8346
- return failed;
8347
9078
  }
8348
- const launchedPid = await waitForCodexLaunch(candidate);
9079
+ const launchedPid = launch.pid;
8349
9080
  if (launchedPid === null) {
8350
- logOfficialCodexRestart("error", "Official Codex launch was not verified.", {
9081
+ const failed = {
9082
+ profileName: options.profileName,
9083
+ profileKey: options.profileKey,
9084
+ profileHome: options.profileHome,
8351
9085
  appPath: candidate.appPath,
8352
9086
  bundleId: candidate.bundleId,
8353
- source: options.source
8354
- });
8355
- const failed = {
9087
+ pid: null,
9088
+ appServerPid: null,
9089
+ launchedAt: null,
9090
+ lastVerifiedAt: null,
9091
+ lastStatus: "failed",
9092
+ lastError: "launch-timeout"
9093
+ };
9094
+ await patchManagedInstance(failed);
9095
+ return {
8356
9096
  status: "failed",
9097
+ profileName: options.profileName,
9098
+ instance: managedInstanceToPayload(failed, false),
9099
+ reason: "launch-timeout"
9100
+ };
9101
+ }
9102
+ const appServerPid = await waitForAppServerPid(
9103
+ launchedPid,
9104
+ options.profileHome
9105
+ );
9106
+ if (appServerPid === null) {
9107
+ const failed = {
9108
+ profileName: options.profileName,
9109
+ profileKey: options.profileKey,
9110
+ profileHome: options.profileHome,
8357
9111
  appPath: candidate.appPath,
8358
9112
  bundleId: candidate.bundleId,
8359
- reason: "launch-timeout",
8360
- phase: "launch"
9113
+ pid: null,
9114
+ appServerPid: null,
9115
+ launchedAt: null,
9116
+ lastVerifiedAt: Date.now(),
9117
+ lastStatus: "failed",
9118
+ lastError: "app-server-not-verified"
9119
+ };
9120
+ await patchManagedInstance(failed);
9121
+ return {
9122
+ status: "failed",
9123
+ profileName: options.profileName,
9124
+ instance: managedInstanceToPayload(failed, false),
9125
+ reason: "app-server-not-verified"
8361
9126
  };
8362
- await rememberRestartResult({
8363
- status: failed.status,
8364
- reason: failed.reason,
8365
- profileKey: options.currentProfileKey
8366
- });
8367
- return failed;
8368
9127
  }
8369
- const status = appIsRunning ? "restarted" : "started";
8370
- logOfficialCodexRestart("info", "Official Codex launch verified.", {
9128
+ const now = Date.now();
9129
+ const instance = {
9130
+ profileName: options.profileName,
9131
+ profileKey: options.profileKey,
9132
+ profileHome: options.profileHome,
8371
9133
  appPath: candidate.appPath,
8372
9134
  bundleId: candidate.bundleId,
8373
- source: options.source,
8374
- status,
8375
- pid: launchedPid
8376
- });
8377
- await rememberRestartResult({
8378
- status,
8379
- profileKey: options.currentProfileKey,
8380
- pid: launchedPid
8381
- });
9135
+ pid: launchedPid,
9136
+ appServerPid,
9137
+ launchedAt: now,
9138
+ lastVerifiedAt: now,
9139
+ lastStatus: "started",
9140
+ lastError: null
9141
+ };
9142
+ await patchManagedInstance(instance);
8382
9143
  return {
8383
- status,
8384
- appPath: candidate.appPath,
8385
- bundleId: candidate.bundleId,
8386
- pid: launchedPid
9144
+ status: "started",
9145
+ profileName: options.profileName,
9146
+ instance: managedInstanceToPayload(instance, true, appServerPid),
9147
+ reason: null
8387
9148
  };
8388
9149
  }
8389
- function recordOfficialCodexProfileSwitch(profileKey) {
8390
- return rememberProfileSwitch(profileKey);
8391
- }
8392
- function restartOfficialCodexApp(options = {}) {
8393
- if (restartPromise) {
8394
- return restartPromise;
9150
+ async function stopOfficialCodexProfileInstanceOnce(profileName) {
9151
+ const existing = await readManagedInstance(profileName);
9152
+ if (!existing) {
9153
+ return {
9154
+ status: "not-running",
9155
+ profileName,
9156
+ instance: null,
9157
+ reason: "not-managed"
9158
+ };
9159
+ }
9160
+ const { candidate } = await resolveCodexAppTarget();
9161
+ if (!candidate) {
9162
+ const stopped2 = {
9163
+ ...existing,
9164
+ pid: null,
9165
+ appServerPid: null,
9166
+ lastVerifiedAt: Date.now(),
9167
+ lastStatus: "not-running",
9168
+ lastError: "official-codex-app-not-found"
9169
+ };
9170
+ await patchManagedInstance(stopped2);
9171
+ return {
9172
+ status: "not-running",
9173
+ profileName,
9174
+ instance: managedInstanceToPayload(stopped2, false),
9175
+ reason: "official-codex-app-not-found"
9176
+ };
9177
+ }
9178
+ const rows = await readProcessRows();
9179
+ const runtime = await resolveInstanceRuntimeState({ instance: existing, candidate, rows });
9180
+ if (!runtime.running || !existing.pid) {
9181
+ const stopped2 = {
9182
+ ...existing,
9183
+ pid: null,
9184
+ appServerPid: null,
9185
+ lastVerifiedAt: Date.now(),
9186
+ lastStatus: "not-running",
9187
+ lastError: null
9188
+ };
9189
+ await patchManagedInstance(stopped2);
9190
+ return {
9191
+ status: "not-running",
9192
+ profileName,
9193
+ instance: managedInstanceToPayload(stopped2, false),
9194
+ reason: null
9195
+ };
8395
9196
  }
8396
- restartPromise = restartOfficialCodexAppOnce({
8397
- source: options.source ?? "manual",
8398
- currentProfileKey: options.currentProfileKey ?? null,
8399
- launchIfNotRunning: options.launchIfNotRunning ?? (options.source ?? "manual") === "manual"
8400
- }).finally(() => {
8401
- restartPromise = null;
9197
+ const tree = [existing.pid, ...getDescendantPids(existing.pid, rows)].filter((pid, index, all) => all.indexOf(pid) === index).sort((a, b) => b - a);
9198
+ await signalPids(tree, "SIGTERM");
9199
+ const exited = await waitForMainPidExit(existing.pid, 5e3);
9200
+ if (!exited) {
9201
+ await signalPids(tree, "SIGKILL");
9202
+ await waitForMainPidExit(existing.pid, EXIT_WAIT_MS);
9203
+ }
9204
+ const stillRunning = (await readProcessRows()).some((row) => row.pid === existing.pid);
9205
+ const stopped = {
9206
+ ...existing,
9207
+ pid: stillRunning ? existing.pid : null,
9208
+ appServerPid: stillRunning ? runtime.appServerPid : null,
9209
+ lastVerifiedAt: Date.now(),
9210
+ lastStatus: stillRunning ? "failed" : "stopped",
9211
+ lastError: stillRunning ? "stop-timeout" : null
9212
+ };
9213
+ await patchManagedInstance(stopped);
9214
+ return {
9215
+ status: stillRunning ? "failed" : "stopped",
9216
+ profileName,
9217
+ instance: managedInstanceToPayload(
9218
+ stopped,
9219
+ stillRunning,
9220
+ stopped.appServerPid,
9221
+ runtime.startedAt
9222
+ ),
9223
+ reason: stillRunning ? "stop-timeout" : null
9224
+ };
9225
+ }
9226
+ function stopOfficialCodexObservedProfileInstance(profileName, pid, appServerPid) {
9227
+ return enqueueProfileAction(async () => {
9228
+ const { candidate } = await resolveCodexAppTarget();
9229
+ if (!candidate) {
9230
+ return {
9231
+ status: "not-running",
9232
+ profileName,
9233
+ instance: null,
9234
+ reason: "official-codex-app-not-found"
9235
+ };
9236
+ }
9237
+ const rows = await readProcessRows();
9238
+ const mainRow = rows.find((row) => row.pid === pid);
9239
+ if (!mainRow || !isMainProcessRow(mainRow, candidate)) {
9240
+ return {
9241
+ status: "not-running",
9242
+ profileName,
9243
+ instance: null,
9244
+ reason: null
9245
+ };
9246
+ }
9247
+ const runtimeAppServerPid = findAppServerPid(rows, pid) ?? appServerPid;
9248
+ const tree = [pid, ...getDescendantPids(pid, rows)].filter((entry, index, all) => all.indexOf(entry) === index).sort((a, b) => b - a);
9249
+ await signalPids(tree, "SIGTERM");
9250
+ const exited = await waitForMainPidExit(pid, 5e3);
9251
+ if (!exited) {
9252
+ await signalPids(tree, "SIGKILL");
9253
+ await waitForMainPidExit(pid, EXIT_WAIT_MS);
9254
+ }
9255
+ const stillRunning = (await readProcessRows()).some((row) => row.pid === pid);
9256
+ const instance = {
9257
+ profileName,
9258
+ profileKey: null,
9259
+ appPath: candidate.appPath,
9260
+ bundleId: candidate.bundleId,
9261
+ pid: stillRunning ? pid : null,
9262
+ appServerPid: stillRunning ? runtimeAppServerPid : null,
9263
+ running: stillRunning,
9264
+ startedAt: stillRunning ? toIso(mainRow.startedAt) : null,
9265
+ uptimeMs: stillRunning && typeof mainRow.startedAt === "number" ? Math.max(0, Date.now() - mainRow.startedAt) : null,
9266
+ launchedAt: null,
9267
+ lastVerifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
9268
+ lastStatus: stillRunning ? "failed" : "stopped",
9269
+ lastError: stillRunning ? "stop-timeout" : null
9270
+ };
9271
+ return {
9272
+ status: stillRunning ? "failed" : "stopped",
9273
+ profileName,
9274
+ instance,
9275
+ reason: stillRunning ? "stop-timeout" : null
9276
+ };
9277
+ });
9278
+ }
9279
+ async function restartOfficialCodexProfileInstance(options) {
9280
+ return enqueueProfileAction(async () => {
9281
+ const stopped = await stopOfficialCodexProfileInstanceOnce(options.profileName);
9282
+ if (stopped.status === "failed") {
9283
+ return stopped;
9284
+ }
9285
+ const launched = await launchOfficialCodexProfileInstanceOnce(options);
9286
+ return {
9287
+ ...launched,
9288
+ status: launched.status === "started" ? "restarted" : launched.status
9289
+ };
8402
9290
  });
8403
- return restartPromise;
8404
9291
  }
8405
9292
 
8406
9293
  // src/commands/profile.ts
@@ -8852,33 +9739,190 @@ async function rememberOfficialCodexSwitch(profileKey) {
8852
9739
  );
8853
9740
  }
8854
9741
  }
8855
- function formatOfficialCodexRestart(result) {
9742
+ function formatOfficialCodexProfileAction(result) {
8856
9743
  if (result.status === "restarted") {
8857
9744
  return "Official Codex restarted.";
8858
9745
  }
8859
9746
  if (result.status === "started") {
8860
9747
  return "Official Codex started.";
8861
9748
  }
9749
+ if (result.status === "already-running") {
9750
+ return "Official Codex already running.";
9751
+ }
8862
9752
  if (result.status === "failed") {
8863
9753
  return `Official Codex restart failed: ${result.reason}.`;
8864
9754
  }
8865
- if (result.status === "skipped") {
9755
+ if (result.status === "skipped" || result.status === "not-running") {
8866
9756
  return `Official Codex restart skipped: ${result.reason}.`;
8867
9757
  }
8868
9758
  return "Official Codex restart status unknown.";
8869
9759
  }
9760
+ function findUnmanagedOfficialCodexRestartTarget(instances, profileName, profileKey) {
9761
+ const restartable = instances.unmanaged.filter((instance) => instance.pid);
9762
+ const matched = restartable.find((instance) => {
9763
+ if (profileKey && instance.profileKey) {
9764
+ return instance.profileKey === profileKey;
9765
+ }
9766
+ if (instance.profileKey) {
9767
+ return false;
9768
+ }
9769
+ return instance.profileName === profileName;
9770
+ }) ?? null;
9771
+ if (matched) {
9772
+ return { target: matched, ambiguous: false, unverified: false };
9773
+ }
9774
+ if (restartable.length > 1) {
9775
+ return { target: null, ambiguous: true, unverified: false };
9776
+ }
9777
+ return { target: null, ambiguous: false, unverified: restartable.length === 1 };
9778
+ }
9779
+ async function restartUnmanagedOfficialCodexWindow(profileName, launchOptions, target) {
9780
+ const stopped = await stopOfficialCodexObservedProfileInstance(
9781
+ target.profileName ?? profileName,
9782
+ target.pid,
9783
+ target.appServerPid
9784
+ );
9785
+ if (stopped.status === "failed") {
9786
+ return stopped;
9787
+ }
9788
+ const launched = await launchOfficialCodexProfileInstance(launchOptions);
9789
+ return {
9790
+ ...launched,
9791
+ status: stopped.status === "stopped" && launched.status === "started" ? "restarted" : launched.status
9792
+ };
9793
+ }
9794
+ async function recordCliOfficialCodexRestartResult(profileName, profileKey, action) {
9795
+ const pid = action.status === "started" || action.status === "restarted" ? action.instance?.pid ?? null : null;
9796
+ const status = action.status === "started" || action.status === "restarted" ? action.status : action.status === "failed" ? "failed" : "skipped";
9797
+ const reason = action.status === "started" || action.status === "restarted" ? null : action.reason ?? action.status;
9798
+ try {
9799
+ await recordOfficialCodexRestartResult({
9800
+ status,
9801
+ reason,
9802
+ profileKey,
9803
+ pid
9804
+ });
9805
+ await recordOfficialCodexActivity({
9806
+ kind: "official-codex-restart",
9807
+ status,
9808
+ reason,
9809
+ targetProfileName: profileName,
9810
+ profileKey,
9811
+ phase: "cli",
9812
+ pid,
9813
+ restartRequested: true,
9814
+ restartResult: status
9815
+ });
9816
+ } catch (error) {
9817
+ console.warn(
9818
+ `Could not record official Codex restart state: ${error instanceof Error ? error.message : String(error)}`
9819
+ );
9820
+ }
9821
+ }
9822
+ async function recordCliOfficialCodexRestartSkipped(profileName, profileKey, reason) {
9823
+ await recordCliOfficialCodexRestartResult(profileName, profileKey, {
9824
+ status: "skipped",
9825
+ profileName,
9826
+ instance: null,
9827
+ reason
9828
+ });
9829
+ }
8870
9830
  async function maybeRestartOfficialCodex(options) {
8871
9831
  if (!options.enabled) {
8872
9832
  return null;
8873
9833
  }
8874
9834
  try {
8875
- const result = await restartOfficialCodexApp({
8876
- source: options.source,
8877
- currentProfileKey: options.profileKey,
8878
- launchIfNotRunning: options.launchIfNotRunning
8879
- });
8880
- return formatOfficialCodexRestart(result);
9835
+ const runtime = await options.manager.prepareProfileRuntime(options.profileName);
9836
+ const launchOptions = {
9837
+ profileName: options.profileName,
9838
+ profileKey: options.profileKey,
9839
+ profileHome: runtime.profileHome
9840
+ };
9841
+ const instances = await getOfficialCodexProfileInstances();
9842
+ if (instances.state === "unsupported") {
9843
+ await recordCliOfficialCodexRestartSkipped(
9844
+ options.profileName,
9845
+ options.profileKey,
9846
+ "unsupported-platform"
9847
+ );
9848
+ return "Official Codex restart skipped: unsupported-platform.";
9849
+ }
9850
+ if (instances.state === "not-found") {
9851
+ await recordCliOfficialCodexRestartSkipped(
9852
+ options.profileName,
9853
+ options.profileKey,
9854
+ "official-codex-app-not-found"
9855
+ );
9856
+ return "Official Codex restart skipped: official-codex-app-not-found.";
9857
+ }
9858
+ const managedRunning = instances.instances.some(
9859
+ (instance) => instance.profileName === options.profileName && instance.running
9860
+ );
9861
+ const unmanaged = findUnmanagedOfficialCodexRestartTarget(
9862
+ instances,
9863
+ options.profileName,
9864
+ options.profileKey
9865
+ );
9866
+ if (unmanaged.target && !managedRunning) {
9867
+ const action2 = await restartUnmanagedOfficialCodexWindow(
9868
+ options.profileName,
9869
+ launchOptions,
9870
+ unmanaged.target
9871
+ );
9872
+ await recordCliOfficialCodexRestartResult(
9873
+ options.profileName,
9874
+ options.profileKey,
9875
+ action2
9876
+ );
9877
+ return formatOfficialCodexProfileAction(action2);
9878
+ }
9879
+ if (unmanaged.ambiguous && !managedRunning) {
9880
+ await recordCliOfficialCodexRestartSkipped(
9881
+ options.profileName,
9882
+ options.profileKey,
9883
+ "ambiguous-official-codex-windows"
9884
+ );
9885
+ return "Official Codex restart skipped: ambiguous-official-codex-windows.";
9886
+ }
9887
+ if (unmanaged.unverified && !managedRunning) {
9888
+ await recordCliOfficialCodexRestartSkipped(
9889
+ options.profileName,
9890
+ options.profileKey,
9891
+ "unverified-official-codex-window"
9892
+ );
9893
+ return "Official Codex restart skipped: unverified-official-codex-window.";
9894
+ }
9895
+ if (options.launchIfNotRunning === false) {
9896
+ if (!managedRunning) {
9897
+ await recordCliOfficialCodexRestartSkipped(
9898
+ options.profileName,
9899
+ options.profileKey,
9900
+ "official-codex-app-not-running"
9901
+ );
9902
+ return "Official Codex restart skipped: official-codex-app-not-running.";
9903
+ }
9904
+ const action2 = await restartOfficialCodexProfileInstance(launchOptions);
9905
+ await recordCliOfficialCodexRestartResult(
9906
+ options.profileName,
9907
+ options.profileKey,
9908
+ action2
9909
+ );
9910
+ return formatOfficialCodexProfileAction(action2);
9911
+ }
9912
+ const action = await restartOfficialCodexProfileInstance(launchOptions);
9913
+ await recordCliOfficialCodexRestartResult(
9914
+ options.profileName,
9915
+ options.profileKey,
9916
+ action
9917
+ );
9918
+ return formatOfficialCodexProfileAction(action);
8881
9919
  } catch (error) {
9920
+ await recordCliOfficialCodexRestartResult(options.profileName, options.profileKey, {
9921
+ status: "failed",
9922
+ profileName: options.profileName,
9923
+ instance: null,
9924
+ reason: error instanceof Error ? error.message : "restart-threw"
9925
+ });
8882
9926
  return `Official Codex restart failed: ${error instanceof Error ? error.message : String(error)}.`;
8883
9927
  }
8884
9928
  }
@@ -9014,8 +10058,9 @@ async function runAutoRollPass(manager, options) {
9014
10058
  const targetProfileKey = profileRateLimitKey(candidate.profile);
9015
10059
  await rememberOfficialCodexSwitch(targetProfileKey);
9016
10060
  const restartMessage = await maybeRestartOfficialCodex({
10061
+ manager,
9017
10062
  enabled: options.restartOfficialCodex,
9018
- source: "auto-roll",
10063
+ profileName: candidate.profile.name,
9019
10064
  profileKey: targetProfileKey,
9020
10065
  launchIfNotRunning: options.launchOfficialCodexWhenClosed === true
9021
10066
  });
@@ -9129,8 +10174,9 @@ async function handleProfileCommand(args, version) {
9129
10174
  console.log(`Switched to profile: ${name}`);
9130
10175
  await rememberOfficialCodexSwitch(targetProfileKey);
9131
10176
  const restartMessage = await maybeRestartOfficialCodex({
10177
+ manager,
9132
10178
  enabled: restartOfficialCodex,
9133
- source: "manual",
10179
+ profileName: name,
9134
10180
  profileKey: targetProfileKey
9135
10181
  });
9136
10182
  if (restartMessage) {
@@ -9244,7 +10290,7 @@ var import_node_path15 = __toESM(require("path"), 1);
9244
10290
  var CLOUD_SYNC_SCHEMA_VERSION = 1;
9245
10291
 
9246
10292
  // ../../packages/shared/src/core/type-guards.ts
9247
- function isRecord4(value) {
10293
+ function isRecord5(value) {
9248
10294
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9249
10295
  }
9250
10296
  function toIsoOrNull(value) {
@@ -9282,10 +10328,10 @@ function buildSyncUrl(pathname) {
9282
10328
  return `${baseUrl}${normalizedPath}`;
9283
10329
  }
9284
10330
  function normalizeSnapshot(value) {
9285
- if (!isRecord4(value)) {
10331
+ if (!isRecord5(value)) {
9286
10332
  return null;
9287
10333
  }
9288
- if (isRecord4(value.snapshot)) {
10334
+ if (isRecord5(value.snapshot)) {
9289
10335
  const nested = normalizeSnapshot(value.snapshot);
9290
10336
  if (nested) {
9291
10337
  return nested;
@@ -9300,9 +10346,9 @@ function normalizeSnapshot(value) {
9300
10346
  return null;
9301
10347
  }
9302
10348
  const rawProfiles = Array.isArray(value.profiles) ? value.profiles : [];
9303
- const profiles = rawProfiles.map((entry) => isRecord4(entry) ? entry : null).filter((entry) => Boolean(entry)).map((entry) => {
9304
- const data = isRecord4(entry.data) ? entry.data : {};
9305
- const metadata = isRecord4(entry.metadata) ? entry.metadata : void 0;
10349
+ const profiles = rawProfiles.map((entry) => isRecord5(entry) ? entry : null).filter((entry) => Boolean(entry)).map((entry) => {
10350
+ const data = isRecord5(entry.data) ? entry.data : {};
10351
+ const metadata = isRecord5(entry.metadata) ? entry.metadata : void 0;
9306
10352
  return {
9307
10353
  name: typeof entry.name === "string" ? entry.name : "",
9308
10354
  displayName: typeof entry.displayName === "string" ? entry.displayName : null,
@@ -9318,7 +10364,7 @@ function normalizeSnapshot(value) {
9318
10364
  };
9319
10365
  }).filter((entry) => entry.name.trim().length > 0);
9320
10366
  const rawSettings = value.settingsJson;
9321
- const settingsJson = isRecord4(rawSettings) ? rawSettings : null;
10367
+ const settingsJson = isRecord5(rawSettings) ? rawSettings : null;
9322
10368
  return {
9323
10369
  schemaVersion: CLOUD_SYNC_SCHEMA_VERSION,
9324
10370
  updatedAt,
@@ -9502,7 +10548,7 @@ var FALLBACK_FEATURE_KEYS = [
9502
10548
  ];
9503
10549
  var metadataCache = null;
9504
10550
  var metadataCacheAt = 0;
9505
- function isRecord5(value) {
10551
+ function isRecord6(value) {
9506
10552
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9507
10553
  }
9508
10554
  function uniqueSorted(values) {
@@ -9567,16 +10613,16 @@ function parseFeatureCatalog(output) {
9567
10613
  }
9568
10614
  function parseSchemaKeys(schemaRaw) {
9569
10615
  const parsed = JSON.parse(schemaRaw);
9570
- if (!isRecord5(parsed)) {
10616
+ if (!isRecord6(parsed)) {
9571
10617
  return {
9572
10618
  topLevelKeys: FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
9573
10619
  featureKeys: FALLBACK_FEATURE_KEYS
9574
10620
  };
9575
10621
  }
9576
- const properties = isRecord5(parsed.properties) ? parsed.properties : {};
10622
+ const properties = isRecord6(parsed.properties) ? parsed.properties : {};
9577
10623
  const topLevelKeys = uniqueSorted(Object.keys(properties));
9578
- const features = isRecord5(properties.features) ? properties.features : {};
9579
- const featureProperties = isRecord5(features.properties) ? features.properties : {};
10624
+ const features = isRecord6(properties.features) ? properties.features : {};
10625
+ const featureProperties = isRecord6(features.properties) ? features.properties : {};
9580
10626
  const featureKeys = uniqueSorted(Object.keys(featureProperties));
9581
10627
  return {
9582
10628
  topLevelKeys: topLevelKeys.length > 0 ? topLevelKeys : FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
@@ -10362,15 +11408,15 @@ var SYNC_SIZE_WARN_BYTES = 1 * 1024 * 1024;
10362
11408
  var SYNC_SIZE_MAX_BYTES = 5 * 1024 * 1024;
10363
11409
  var MB_DIVISOR = 1024 * 1024;
10364
11410
  function mapProfilesFromAppState(raw) {
10365
- if (!isRecord4(raw)) {
11411
+ if (!isRecord5(raw)) {
10366
11412
  return [];
10367
11413
  }
10368
11414
  const profiles = [];
10369
11415
  for (const [name, value] of Object.entries(raw)) {
10370
- if (!isRecord4(value)) {
11416
+ if (!isRecord5(value)) {
10371
11417
  continue;
10372
11418
  }
10373
- const data = isRecord4(value.data) ? value.data : null;
11419
+ const data = isRecord5(value.data) ? value.data : null;
10374
11420
  if (!data) {
10375
11421
  continue;
10376
11422
  }
@@ -10382,7 +11428,7 @@ function mapProfilesFromAppState(raw) {
10382
11428
  name: normalizedName,
10383
11429
  displayName: typeof value.displayName === "string" ? value.displayName : null,
10384
11430
  data,
10385
- metadata: isRecord4(value.metadata) ? value.metadata : void 0,
11431
+ metadata: isRecord5(value.metadata) ? value.metadata : void 0,
10386
11432
  accountId: typeof value.accountId === "string" ? value.accountId : null,
10387
11433
  workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : null,
10388
11434
  workspaceName: typeof value.workspaceName === "string" ? value.workspaceName : null,
@@ -10396,7 +11442,7 @@ function mapProfilesFromAppState(raw) {
10396
11442
  }
10397
11443
  function summarizeSnapshot(snapshot) {
10398
11444
  const configBytes = typeof snapshot.configTomlContent === "string" ? snapshot.configTomlContent.length : 0;
10399
- const settingsKeys = isRecord4(snapshot.settingsJson) ? Object.keys(snapshot.settingsJson).length : 0;
11445
+ const settingsKeys = isRecord5(snapshot.settingsJson) ? Object.keys(snapshot.settingsJson).length : 0;
10400
11446
  return {
10401
11447
  profiles: snapshot.profiles.length,
10402
11448
  configBytes,
@@ -10501,7 +11547,7 @@ async function buildLocalSnapshot(profileManager, options = {}) {
10501
11547
  const settingsJson = await readCodexSettingsJsonRaw();
10502
11548
  const hasProfiles = profiles.length > 0;
10503
11549
  const hasConfig = typeof config.content === "string" && config.content.trim().length > 0;
10504
- const hasSettings = isRecord4(settingsJson) && Object.keys(settingsJson).length > 0;
11550
+ const hasSettings = isRecord5(settingsJson) && Object.keys(settingsJson).length > 0;
10505
11551
  if (enforcePushGuards && !hasProfiles && !hasConfig && !hasSettings) {
10506
11552
  throw new Error("Refusing to push an empty cloud sync snapshot.");
10507
11553
  }
@@ -10525,7 +11571,7 @@ async function applyRemoteSnapshot(profileManager, snapshot) {
10525
11571
  codexHomePath: runtimeContext.codexHomePath
10526
11572
  });
10527
11573
  }
10528
- if (snapshot.settingsJson && isRecord4(snapshot.settingsJson)) {
11574
+ if (snapshot.settingsJson && isRecord5(snapshot.settingsJson)) {
10529
11575
  await writeCodexSettingsJsonRaw(snapshot.settingsJson);
10530
11576
  } else {
10531
11577
  await writeCodexSettingsJsonRaw({});
@@ -10778,10 +11824,10 @@ var LEGACY_SKILL_CACHE_DIR = "skill-cache";
10778
11824
  var LEGACY_SKILLS_REPOS_FILE = "repos.json";
10779
11825
  var LEGACY_SKILL_MANIFEST = ".codexuse-skill.json";
10780
11826
  var LEGACY_LICENSE_SECRET_FILE2 = "license.secret";
10781
- function isRecord6(value) {
11827
+ function isRecord7(value) {
10782
11828
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10783
11829
  }
10784
- function asString4(value) {
11830
+ function asString5(value) {
10785
11831
  if (typeof value !== "string") {
10786
11832
  return null;
10787
11833
  }
@@ -10950,7 +11996,7 @@ function pickAutoRoll(raw) {
10950
11996
  }
10951
11997
  }
10952
11998
  function parseLegacyLicense(raw) {
10953
- if (!isRecord6(raw)) {
11999
+ if (!isRecord7(raw)) {
10954
12000
  return {
10955
12001
  licenseKey: null,
10956
12002
  purchaseEmail: null,
@@ -10961,25 +12007,25 @@ function parseLegacyLicense(raw) {
10961
12007
  signature: null
10962
12008
  };
10963
12009
  }
10964
- const statusCandidate = asString4(raw.status);
12010
+ const statusCandidate = asString5(raw.status);
10965
12011
  const status = ["inactive", "active", "grace", "error"].includes(statusCandidate ?? "") ? statusCandidate : "inactive";
10966
12012
  return {
10967
- licenseKey: asString4(raw.licenseKey ?? raw.license_key),
10968
- purchaseEmail: asString4(raw.purchaseEmail ?? raw.purchase_email),
10969
- lastVerifiedAt: asString4(raw.lastVerifiedAt ?? raw.last_verified_at),
10970
- nextCheckAt: asString4(raw.nextCheckAt ?? raw.next_check_at),
10971
- lastVerificationError: asString4(raw.lastVerificationError ?? raw.last_verification_error),
12013
+ licenseKey: asString5(raw.licenseKey ?? raw.license_key),
12014
+ purchaseEmail: asString5(raw.purchaseEmail ?? raw.purchase_email),
12015
+ lastVerifiedAt: asString5(raw.lastVerifiedAt ?? raw.last_verified_at),
12016
+ nextCheckAt: asString5(raw.nextCheckAt ?? raw.next_check_at),
12017
+ lastVerificationError: asString5(raw.lastVerificationError ?? raw.last_verification_error),
10972
12018
  status,
10973
- signature: asString4(raw.signature)
12019
+ signature: asString5(raw.signature)
10974
12020
  };
10975
12021
  }
10976
12022
  function parseLegacyProfileRecord(name, raw) {
10977
- if (!isRecord6(raw)) {
12023
+ if (!isRecord7(raw)) {
10978
12024
  return null;
10979
12025
  }
10980
12026
  const dataRaw = raw.data;
10981
12027
  let data = null;
10982
- if (isRecord6(dataRaw)) {
12028
+ if (isRecord7(dataRaw)) {
10983
12029
  data = dataRaw;
10984
12030
  } else if (typeof dataRaw === "string") {
10985
12031
  try {
@@ -10993,31 +12039,31 @@ function parseLegacyProfileRecord(name, raw) {
10993
12039
  }
10994
12040
  return {
10995
12041
  name,
10996
- displayName: asString4(raw.displayName ?? raw.display_name) ?? name,
12042
+ displayName: asString5(raw.displayName ?? raw.display_name) ?? name,
10997
12043
  data,
10998
- metadata: isRecord6(raw.metadata) ? raw.metadata : void 0,
10999
- accountId: asString4(raw.accountId ?? raw.account_id),
11000
- workspaceId: asString4(raw.workspaceId ?? raw.workspace_id),
11001
- workspaceName: asString4(raw.workspaceName ?? raw.workspace_name),
11002
- email: asString4(raw.email),
11003
- authMethod: asString4(raw.authMethod ?? raw.auth_method),
11004
- createdAt: asString4(raw.createdAt ?? raw.created_at),
11005
- updatedAt: asString4(raw.updatedAt ?? raw.updated_at)
12044
+ metadata: isRecord7(raw.metadata) ? raw.metadata : void 0,
12045
+ accountId: asString5(raw.accountId ?? raw.account_id),
12046
+ workspaceId: asString5(raw.workspaceId ?? raw.workspace_id),
12047
+ workspaceName: asString5(raw.workspaceName ?? raw.workspace_name),
12048
+ email: asString5(raw.email),
12049
+ authMethod: asString5(raw.authMethod ?? raw.auth_method),
12050
+ createdAt: asString5(raw.createdAt ?? raw.created_at),
12051
+ updatedAt: asString5(raw.updatedAt ?? raw.updated_at)
11006
12052
  };
11007
12053
  }
11008
12054
  async function loadLegacySettingsPatch() {
11009
12055
  const filePath = resolveLegacyPath(LEGACY_SETTINGS_FILE);
11010
12056
  const raw = await readJsonFileIfExists(filePath);
11011
- if (!isRecord6(raw)) {
12057
+ if (!isRecord7(raw)) {
11012
12058
  return {};
11013
12059
  }
11014
12060
  const autoRoll = pickAutoRoll(raw.autoRoll ?? raw.auto_roll);
11015
12061
  const license = parseLegacyLicense(raw.license ?? raw.license_data ?? raw.license_state);
11016
12062
  return {
11017
12063
  app: {
11018
- lastAppVersion: asString4(raw.lastAppVersion ?? raw.last_app_version),
11019
- pendingUpdateVersion: asString4(raw.pendingUpdateVersion ?? raw.pending_update_version),
11020
- lastProfileName: asString4(raw.lastProfileName ?? raw.last_profile_name)
12064
+ lastAppVersion: asString5(raw.lastAppVersion ?? raw.last_app_version),
12065
+ pendingUpdateVersion: asString5(raw.pendingUpdateVersion ?? raw.pending_update_version),
12066
+ lastProfileName: asString5(raw.lastProfileName ?? raw.last_profile_name)
11021
12067
  },
11022
12068
  license,
11023
12069
  autoRoll: autoRoll ? {
@@ -11035,22 +12081,22 @@ async function loadLegacySettingsPatch() {
11035
12081
  async function loadLegacySyncPatch() {
11036
12082
  const filePath = resolveLegacyPath(LEGACY_SYNC_STATE_FILE);
11037
12083
  const raw = await readJsonFileIfExists(filePath);
11038
- if (!isRecord6(raw)) {
12084
+ if (!isRecord7(raw)) {
11039
12085
  return {};
11040
12086
  }
11041
12087
  return {
11042
12088
  sync: {
11043
- lastPushAt: asString4(raw.lastPushAt),
11044
- lastPullAt: asString4(raw.lastPullAt),
11045
- lastError: asString4(raw.lastError),
11046
- remoteUpdatedAt: asString4(raw.remoteUpdatedAt)
12089
+ lastPushAt: asString5(raw.lastPushAt),
12090
+ lastPullAt: asString5(raw.lastPullAt),
12091
+ lastError: asString5(raw.lastError),
12092
+ remoteUpdatedAt: asString5(raw.remoteUpdatedAt)
11047
12093
  }
11048
12094
  };
11049
12095
  }
11050
12096
  async function loadLegacyAppSettingsParityPatch() {
11051
12097
  const filePath = import_node_path16.default.join(getUserDataDir(), LEGACY_APP_SETTINGS_PARITY_FILE);
11052
12098
  const raw = await readJsonFileIfExists(filePath);
11053
- if (!isRecord6(raw)) {
12099
+ if (!isRecord7(raw)) {
11054
12100
  return {};
11055
12101
  }
11056
12102
  return {
@@ -11090,21 +12136,21 @@ async function loadLegacyProfilesPatch() {
11090
12136
  return { profilesByName };
11091
12137
  }
11092
12138
  function parseSkillInstallMetadata(raw) {
11093
- if (!isRecord6(raw)) {
12139
+ if (!isRecord7(raw)) {
11094
12140
  return null;
11095
12141
  }
11096
- const id = asString4(raw.id);
12142
+ const id = asString5(raw.id);
11097
12143
  if (!id) {
11098
12144
  return null;
11099
12145
  }
11100
12146
  return {
11101
12147
  id,
11102
- repo: asString4(raw.repo),
11103
- repoPath: asString4(raw.repoPath),
11104
- sourceLabel: asString4(raw.sourceLabel),
12148
+ repo: asString5(raw.repo),
12149
+ repoPath: asString5(raw.repoPath),
12150
+ sourceLabel: asString5(raw.sourceLabel),
11105
12151
  sourceType: raw.sourceType === "official" || raw.sourceType === "community" || raw.sourceType === "local" ? raw.sourceType : void 0,
11106
- viewUrl: asString4(raw.viewUrl),
11107
- createdAt: asString4(raw.createdAt)
12152
+ viewUrl: asString5(raw.viewUrl),
12153
+ createdAt: asString5(raw.createdAt)
11108
12154
  };
11109
12155
  }
11110
12156
  async function loadLegacySkillsPatch() {
@@ -11135,7 +12181,7 @@ async function loadLegacySkillsPatch() {
11135
12181
  }
11136
12182
  installsBySlug[entry.name] = parsed;
11137
12183
  }
11138
- const sources = isRecord6(reposRaw) && Array.isArray(reposRaw.sources) ? reposRaw.sources : [];
12184
+ const sources = isRecord7(reposRaw) && Array.isArray(reposRaw.sources) ? reposRaw.sources : [];
11139
12185
  if (sources.length === 0 && Object.keys(installsBySlug).length === 0) {
11140
12186
  return {};
11141
12187
  }
@@ -11167,7 +12213,7 @@ function mergeLegacyLocalStoragePatch(payload) {
11167
12213
  }
11168
12214
  };
11169
12215
  const settingsStorage = payload["settings-storage"];
11170
- if (isRecord6(settingsStorage)) {
12216
+ if (isRecord7(settingsStorage)) {
11171
12217
  const nextExcludeFolders = Array.isArray(settingsStorage.excludeFolders) ? settingsStorage.excludeFolders.filter((item) => typeof item === "string") : void 0;
11172
12218
  const nextBeep = typeof settingsStorage.enableTaskCompleteBeep === "boolean" ? settingsStorage.enableTaskCompleteBeep : void 0;
11173
12219
  const nextSleep = typeof settingsStorage.preventSleepDuringTasks === "boolean" ? settingsStorage.preventSleepDuringTasks : void 0;
@@ -11186,15 +12232,15 @@ function mergeLegacyLocalStoragePatch(payload) {
11186
12232
  markSkippedIfPresent("provider", hasKey2("provider"));
11187
12233
  markSkippedIfPresent("sandbox-storage", hasKey2("sandbox-storage"));
11188
12234
  const projectSettings = payload["project-settings-storage"];
11189
- if (isRecord6(projectSettings) && isRecord6(projectSettings.settingsByPath)) {
12235
+ if (isRecord7(projectSettings) && isRecord7(projectSettings.settingsByPath)) {
11190
12236
  patch.workspaceSettingsByPath = projectSettings.settingsByPath;
11191
12237
  markSkippedIfPresent("project-settings-storage", true);
11192
12238
  } else {
11193
12239
  markSkippedIfPresent("project-settings-storage", false);
11194
12240
  }
11195
12241
  const folder = payload["folder-storage"];
11196
- if (isRecord6(folder)) {
11197
- const folderHistory = Array.isArray(folder.folderHistory) ? folder.folderHistory.filter(isRecord6) : void 0;
12242
+ if (isRecord7(folder)) {
12243
+ const folderHistory = Array.isArray(folder.folderHistory) ? folder.folderHistory.filter(isRecord7) : void 0;
11198
12244
  const pinnedPaths = Array.isArray(folder.pinnedPaths) ? folder.pinnedPaths.filter((item) => typeof item === "string") : void 0;
11199
12245
  patch.preferences = {
11200
12246
  ...patch.preferences ?? {},
@@ -11207,12 +12253,12 @@ function mergeLegacyLocalStoragePatch(payload) {
11207
12253
  }
11208
12254
  const categories = payload["conversation-categories-storage"];
11209
12255
  let consumedCategories = false;
11210
- if (isRecord6(categories)) {
11211
- if (isRecord6(categories.categoriesByCwd)) {
12256
+ if (isRecord7(categories)) {
12257
+ if (isRecord7(categories.categoriesByCwd)) {
11212
12258
  patch.conversationCategoriesByCwd = categories.categoriesByCwd;
11213
12259
  consumedCategories = true;
11214
12260
  }
11215
- if (isRecord6(categories.conversationCategoryByCwd)) {
12261
+ if (isRecord7(categories.conversationCategoryByCwd)) {
11216
12262
  patch.conversationCategoryAssignmentsByCwd = categories.conversationCategoryByCwd;
11217
12263
  consumedCategories = true;
11218
12264
  }
@@ -11435,7 +12481,7 @@ async function importLegacyLocalStorageOnce(payload) {
11435
12481
  if (current.migration.status === "pending") {
11436
12482
  return { completed: false, importedKeys: [], skippedKeys: [] };
11437
12483
  }
11438
- if (!payload || !isRecord6(payload)) {
12484
+ if (!payload || !isRecord7(payload)) {
11439
12485
  return { completed: true, importedKeys: [], skippedKeys: [] };
11440
12486
  }
11441
12487
  const { patch, consumedKeys, skippedKeys } = mergeLegacyLocalStoragePatch(payload);
@@ -11503,7 +12549,7 @@ async function ensureCliStorageReady() {
11503
12549
  }
11504
12550
 
11505
12551
  // src/app/main.ts
11506
- var VERSION = true ? "3.9.7" : "0.0.0";
12552
+ var VERSION = true ? "3.9.9" : "0.0.0";
11507
12553
  async function runCli() {
11508
12554
  const args = process.argv.slice(2);
11509
12555
  if (args.length === 0) {