codexuse-cli 3.9.7 → 3.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,30 @@ 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 === "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(entry.observedProfileMatchSource)
2647
+ })).filter((entry) => entry.id && entry.at).slice(-50) : [];
2619
2648
  if (!isRecord2(merged.officialCodex.instancesByProfileName)) {
2620
2649
  merged.officialCodex.instancesByProfileName = {};
2621
2650
  } else {
@@ -2646,6 +2675,21 @@ function normalizeAppState(raw) {
2646
2675
  }
2647
2676
  merged.officialCodex.instancesByProfileName = nextInstances;
2648
2677
  }
2678
+ if (isRecord2(merged.officialCodex.pendingRestartDebt)) {
2679
+ const debt = merged.officialCodex.pendingRestartDebt;
2680
+ const targetProfileName = asString(debt.targetProfileName);
2681
+ merged.officialCodex.pendingRestartDebt = targetProfileName ? {
2682
+ targetProfileName,
2683
+ targetProfileKey: asString(debt.targetProfileKey),
2684
+ sourceProfileName: asString(debt.sourceProfileName),
2685
+ sourceProfileKey: asString(debt.sourceProfileKey),
2686
+ decisionId: asString(debt.decisionId),
2687
+ attempts: asNumberOrNull(debt.attempts) ?? 0,
2688
+ lastReason: asString(debt.lastReason)
2689
+ } : null;
2690
+ } else {
2691
+ merged.officialCodex.pendingRestartDebt = null;
2692
+ }
2649
2693
  merged.app.lastAppVersion = asString(merged.app.lastAppVersion);
2650
2694
  merged.app.pendingUpdateVersion = asString(merged.app.pendingUpdateVersion);
2651
2695
  merged.app.lastProfileName = asString(merged.app.lastProfileName);
@@ -3985,10 +4029,10 @@ function logError(...args) {
3985
4029
  console.error(...args);
3986
4030
  }
3987
4031
  function logInfo(...args) {
3988
- if (isTestEnv && !isMocked(console.warn)) {
4032
+ if (isTestEnv && !isMocked(console.info)) {
3989
4033
  return;
3990
4034
  }
3991
- console.warn(...args);
4035
+ console.info(...args);
3992
4036
  }
3993
4037
 
3994
4038
  // ../../packages/runtime-codex/src/codex/rpc.ts
@@ -5402,13 +5446,13 @@ var ProfileManager = class {
5402
5446
  const existingIdentity = this.resolveProfileIdentityFromData(profileName, existing, existingMetadata);
5403
5447
  const nextIdentity = this.resolveProfileIdentityFromData(profileName, normalized, metadata);
5404
5448
  if (existingIdentity && nextIdentity && existingIdentity !== nextIdentity) {
5405
- logWarn(
5449
+ logInfo(
5406
5450
  `Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different OpenAI identity.`
5407
5451
  );
5408
5452
  return;
5409
5453
  }
5410
5454
  if (this.normalizeWorkspaceId(existing.workspace_id) !== this.normalizeWorkspaceId(normalized.workspace_id)) {
5411
- logWarn(
5455
+ logInfo(
5412
5456
  `Skipped syncing tokens for profile '${profileName}' because Codex auth switched to a different workspace.`
5413
5457
  );
5414
5458
  return;
@@ -8000,18 +8044,17 @@ async function assertProfileCreationAllowed(profileManager) {
8000
8044
 
8001
8045
  // ../../packages/runtime-codex/src/codex/officialAppRestart.ts
8002
8046
  var import_node_child_process7 = require("child_process");
8047
+ var import_node_crypto6 = require("crypto");
8003
8048
  var import_node_fs8 = require("fs");
8004
8049
  var import_node_os5 = require("os");
8005
8050
  var import_node_path11 = __toESM(require("path"), 1);
8006
- var RESTART_COOLDOWN_MS = 6e4;
8007
8051
  var COMMAND_TIMEOUT_MS = 3e3;
8008
8052
  var EXIT_WAIT_MS = 1e4;
8009
8053
  var LAUNCH_WAIT_MS = 15e3;
8010
8054
  var POLL_INTERVAL_MS = 250;
8011
8055
  var APP_DISCOVERY_CACHE_TTL_MS = 6e4;
8012
8056
  var APP_DISCOVERY_MISS_CACHE_TTL_MS = 5e3;
8013
- var restartPromise = null;
8014
- var lastRestartStartedAt = 0;
8057
+ var OFFICIAL_CODEX_ACTIVITY_LIMIT = 50;
8015
8058
  var profileActionLock = Promise.resolve();
8016
8059
  var appDiscoveryCache = null;
8017
8060
  function logOfficialCodexRestart(level, message, context) {
@@ -8022,6 +8065,55 @@ function logOfficialCodexRestart(level, message, context) {
8022
8065
  }
8023
8066
  logger(`[official-codex-restart] ${message}`);
8024
8067
  }
8068
+ function hashProfileKey(profileKey) {
8069
+ if (!profileKey) {
8070
+ return null;
8071
+ }
8072
+ return (0, import_node_crypto6.createHash)("sha256").update(profileKey).digest("hex").slice(0, 12);
8073
+ }
8074
+ async function recordOfficialCodexActivity(input) {
8075
+ const now = /* @__PURE__ */ new Date();
8076
+ const entry = {
8077
+ id: `${now.getTime().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
8078
+ at: now.toISOString(),
8079
+ kind: input.kind,
8080
+ status: input.status,
8081
+ reason: input.reason ?? null,
8082
+ decisionId: input.decisionId ?? null,
8083
+ profileName: input.profileName ?? null,
8084
+ sourceProfileName: input.sourceProfileName ?? null,
8085
+ targetProfileName: input.targetProfileName ?? null,
8086
+ remainingPercent: input.remainingPercent ?? null,
8087
+ threshold: input.threshold ?? null,
8088
+ snapshotAgeMs: input.snapshotAgeMs ?? null,
8089
+ snapshotSource: input.snapshotSource ?? null,
8090
+ phase: input.phase ?? null,
8091
+ pid: input.pid ?? null,
8092
+ profileKeyHash: input.profileKeyHash ?? hashProfileKey(input.profileKey),
8093
+ switchVerified: input.switchVerified ?? null,
8094
+ restartRequested: input.restartRequested ?? null,
8095
+ restartResult: input.restartResult ?? null,
8096
+ observedProfileName: input.observedProfileName ?? null,
8097
+ observedProfileKeyHash: input.observedProfileKeyHash ?? hashProfileKey(input.observedProfileKey),
8098
+ observedProfileMatchSource: input.observedProfileMatchSource ?? null
8099
+ };
8100
+ try {
8101
+ await updateAppState((state) => ({
8102
+ officialCodex: {
8103
+ activity: [...state.officialCodex.activity, entry].slice(
8104
+ -OFFICIAL_CODEX_ACTIVITY_LIMIT
8105
+ )
8106
+ }
8107
+ }));
8108
+ } catch (error) {
8109
+ logOfficialCodexRestart("warn", "Failed to record official Codex activity.", {
8110
+ kind: entry.kind,
8111
+ status: entry.status,
8112
+ profileKeyHash: entry.profileKeyHash,
8113
+ error: error instanceof Error ? error.message : String(error)
8114
+ });
8115
+ }
8116
+ }
8025
8117
  function runCommand2(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
8026
8118
  return new Promise((resolve) => {
8027
8119
  const child = (0, import_node_child_process7.spawn)(command, args, {
@@ -8055,6 +8147,278 @@ function runCommand2(command, args, timeoutMs = COMMAND_TIMEOUT_MS) {
8055
8147
  });
8056
8148
  });
8057
8149
  }
8150
+ function enqueueProfileAction(work) {
8151
+ const run = profileActionLock.then(work, work);
8152
+ profileActionLock = run.then(
8153
+ () => void 0,
8154
+ () => void 0
8155
+ );
8156
+ return run;
8157
+ }
8158
+ async function readProcessRows() {
8159
+ const result = await runCommand2(
8160
+ "/bin/ps",
8161
+ ["-axo", "pid=,ppid=,lstart=,args="],
8162
+ 2e3
8163
+ );
8164
+ if (result.code !== 0) {
8165
+ return [];
8166
+ }
8167
+ return result.stdout.split(/\r?\n/).flatMap((line) => {
8168
+ const match = line.match(
8169
+ /^\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+(.+)$/
8170
+ );
8171
+ if (!match) {
8172
+ return [];
8173
+ }
8174
+ const pid = Number(match[1]);
8175
+ const ppid = Number(match[2]);
8176
+ const parsedStartedAt = Date.parse(match[3] ?? "");
8177
+ const startedAt = Number.isFinite(parsedStartedAt) ? parsedStartedAt : null;
8178
+ const args = match[4] ?? "";
8179
+ if (!Number.isInteger(pid) || !Number.isInteger(ppid) || !args) {
8180
+ return [];
8181
+ }
8182
+ return [{ pid, ppid, startedAt, args }];
8183
+ });
8184
+ }
8185
+ function getMainExecutablePath(candidate) {
8186
+ return import_node_path11.default.join(candidate.appPath, "Contents", "MacOS", candidate.executableName);
8187
+ }
8188
+ function isMainProcessRow(row, candidate) {
8189
+ return row.args.includes(getMainExecutablePath(candidate));
8190
+ }
8191
+ function findAppServerPid(rows, mainPid) {
8192
+ return rows.find(
8193
+ (row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
8194
+ )?.pid ?? null;
8195
+ }
8196
+ async function processMatchesProfileHome(pid, profileHome) {
8197
+ const result = await runCommand2(
8198
+ "/bin/ps",
8199
+ ["eww", "-p", String(pid), "-o", "command="],
8200
+ 2e3
8201
+ );
8202
+ if (result.code !== 0) {
8203
+ return false;
8204
+ }
8205
+ return result.stdout.includes(`CODEX_HOME=${profileHome}`) || result.stdout.includes(profileHome);
8206
+ }
8207
+ async function findAppServerPidForProfile(rows, mainPid, profileHome) {
8208
+ const candidates = rows.filter(
8209
+ (row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
8210
+ );
8211
+ for (const row of candidates) {
8212
+ if (row.args.includes(profileHome) || await processMatchesProfileHome(row.pid, profileHome)) {
8213
+ return row.pid;
8214
+ }
8215
+ }
8216
+ return null;
8217
+ }
8218
+ async function describeUnmanagedProfileHint(appServerPid) {
8219
+ if (!appServerPid) {
8220
+ return {};
8221
+ }
8222
+ const result = await runCommand2(
8223
+ "/bin/ps",
8224
+ ["eww", "-p", String(appServerPid), "-o", "command="],
8225
+ 2e3
8226
+ );
8227
+ if (result.code !== 0) {
8228
+ return {};
8229
+ }
8230
+ const telemetryMatch = result.stdout.match(
8231
+ /CODEX_TELEMETRY_LABEL=codexuse-profile-([^\s]+)/
8232
+ );
8233
+ if (telemetryMatch?.[1]) {
8234
+ const encodedProfileName = telemetryMatch[1];
8235
+ let profileName = encodedProfileName;
8236
+ try {
8237
+ profileName = decodeURIComponent(encodedProfileName);
8238
+ } catch {
8239
+ profileName = encodedProfileName;
8240
+ }
8241
+ return {
8242
+ profileName,
8243
+ profileMatchSource: "telemetry-label"
8244
+ };
8245
+ }
8246
+ const profileHomeMatch = result.stdout.match(/profile-homes\/([^\s]+)/);
8247
+ if (profileHomeMatch?.[1]) {
8248
+ return {
8249
+ profileName: profileHomeMatch[1],
8250
+ profileMatchSource: "profile-home"
8251
+ };
8252
+ }
8253
+ return {};
8254
+ }
8255
+ function getDescendantPids(rootPid, rows) {
8256
+ const descendants = [];
8257
+ const queue = [rootPid];
8258
+ const seen = /* @__PURE__ */ new Set();
8259
+ while (queue.length > 0) {
8260
+ const current = queue.shift();
8261
+ if (seen.has(current)) {
8262
+ continue;
8263
+ }
8264
+ seen.add(current);
8265
+ for (const row of rows) {
8266
+ if (row.ppid !== current || seen.has(row.pid)) {
8267
+ continue;
8268
+ }
8269
+ descendants.push(row.pid);
8270
+ queue.push(row.pid);
8271
+ }
8272
+ }
8273
+ return descendants;
8274
+ }
8275
+ async function waitForNewMainPid(candidate, previousPids) {
8276
+ const deadline = Date.now() + LAUNCH_WAIT_MS;
8277
+ while (Date.now() < deadline) {
8278
+ const rows = await readProcessRows();
8279
+ const pid = rows.find((row) => isMainProcessRow(row, candidate) && !previousPids.has(row.pid))?.pid ?? null;
8280
+ if (pid !== null) {
8281
+ return pid;
8282
+ }
8283
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8284
+ }
8285
+ return null;
8286
+ }
8287
+ async function waitForMainPid(candidate, pid, timeoutMs) {
8288
+ const deadline = Date.now() + timeoutMs;
8289
+ while (Date.now() < deadline) {
8290
+ const rows = await readProcessRows();
8291
+ if (rows.some((row) => row.pid === pid && isMainProcessRow(row, candidate))) {
8292
+ return true;
8293
+ }
8294
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8295
+ }
8296
+ return false;
8297
+ }
8298
+ async function waitForAppServerPid(mainPid, profileHome) {
8299
+ const deadline = Date.now() + LAUNCH_WAIT_MS;
8300
+ while (Date.now() < deadline) {
8301
+ const pid = await findAppServerPidForProfile(
8302
+ await readProcessRows(),
8303
+ mainPid,
8304
+ profileHome
8305
+ );
8306
+ if (pid !== null) {
8307
+ return pid;
8308
+ }
8309
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8310
+ }
8311
+ return null;
8312
+ }
8313
+ async function waitForMainPidExit(pid, timeoutMs) {
8314
+ const deadline = Date.now() + timeoutMs;
8315
+ while (Date.now() < deadline) {
8316
+ const rows = await readProcessRows();
8317
+ if (!rows.some((row) => row.pid === pid)) {
8318
+ return true;
8319
+ }
8320
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
8321
+ }
8322
+ return false;
8323
+ }
8324
+ function managedInstanceToPayload(instance, running, appServerPid = instance.appServerPid, startedAt = null) {
8325
+ const runningStartedAt = startedAt ?? instance.launchedAt;
8326
+ return {
8327
+ profileName: instance.profileName,
8328
+ profileKey: instance.profileKey,
8329
+ appPath: instance.appPath,
8330
+ bundleId: instance.bundleId,
8331
+ pid: running ? instance.pid : null,
8332
+ appServerPid: running ? appServerPid : null,
8333
+ running,
8334
+ startedAt: running ? toIso(runningStartedAt) : null,
8335
+ uptimeMs: running && typeof runningStartedAt === "number" ? Math.max(0, Date.now() - runningStartedAt) : null,
8336
+ launchedAt: toIso(instance.launchedAt),
8337
+ lastVerifiedAt: toIso(instance.lastVerifiedAt),
8338
+ lastStatus: instance.lastStatus,
8339
+ lastError: instance.lastError
8340
+ };
8341
+ }
8342
+ async function patchManagedInstance(instance) {
8343
+ await patchAppState({
8344
+ officialCodex: {
8345
+ instancesByProfileName: {
8346
+ [instance.profileName]: instance
8347
+ }
8348
+ }
8349
+ });
8350
+ }
8351
+ async function readManagedInstance(profileName) {
8352
+ const state = await getAppState();
8353
+ return state.officialCodex.instancesByProfileName[profileName] ?? null;
8354
+ }
8355
+ async function resolveInstanceRuntimeState(args) {
8356
+ const pid = args.instance.pid;
8357
+ if (!pid) {
8358
+ return { running: false, appServerPid: null, startedAt: null };
8359
+ }
8360
+ const mainRow = args.rows.find((row) => row.pid === pid);
8361
+ if (!mainRow || !isMainProcessRow(mainRow, args.candidate)) {
8362
+ return { running: false, appServerPid: null, startedAt: null };
8363
+ }
8364
+ const appServerPid = args.instance.profileHome ? await findAppServerPidForProfile(args.rows, pid, args.instance.profileHome) : findAppServerPid(args.rows, pid);
8365
+ if (args.instance.profileHome && appServerPid === null) {
8366
+ return { running: false, appServerPid: null, startedAt: null };
8367
+ }
8368
+ return {
8369
+ running: true,
8370
+ appServerPid,
8371
+ startedAt: mainRow.startedAt
8372
+ };
8373
+ }
8374
+ async function openCodexWithProfileHome(candidate, profileHome, profileName, previousPids) {
8375
+ const executablePath = getMainExecutablePath(candidate);
8376
+ if (!(0, import_node_fs8.existsSync)(executablePath)) {
8377
+ return { opened: false, pid: null };
8378
+ }
8379
+ const telemetryLabel = `codexuse-profile-${encodeURIComponent(profileName)}`;
8380
+ let child;
8381
+ try {
8382
+ child = (0, import_node_child_process7.spawn)(executablePath, [], {
8383
+ detached: true,
8384
+ env: {
8385
+ ...process.env,
8386
+ CODEX_HOME: profileHome,
8387
+ CODEX_TELEMETRY_LABEL: telemetryLabel
8388
+ },
8389
+ stdio: "ignore"
8390
+ });
8391
+ } catch {
8392
+ return { opened: false, pid: null };
8393
+ }
8394
+ const childPid = child.pid ?? null;
8395
+ child.unref();
8396
+ const spawnError = await new Promise((resolve) => {
8397
+ let settled = false;
8398
+ const settle = (failed) => {
8399
+ if (settled) {
8400
+ return;
8401
+ }
8402
+ settled = true;
8403
+ resolve(failed);
8404
+ };
8405
+ child.once("error", () => settle(true));
8406
+ setTimeout(() => settle(false), POLL_INTERVAL_MS);
8407
+ });
8408
+ if (spawnError) {
8409
+ return { opened: false, pid: null };
8410
+ }
8411
+ if (childPid !== null) {
8412
+ const verified = await waitForMainPid(candidate, childPid, 2e3);
8413
+ if (verified) {
8414
+ return { opened: true, pid: childPid };
8415
+ }
8416
+ }
8417
+ return {
8418
+ opened: true,
8419
+ pid: await waitForNewMainPid(candidate, previousPids)
8420
+ };
8421
+ }
8058
8422
  async function readBundleString(appPath, key) {
8059
8423
  const plistPath = import_node_path11.default.join(appPath, "Contents", "Info.plist");
8060
8424
  if (!(0, import_node_fs8.existsSync)(plistPath)) {
@@ -8187,17 +8551,6 @@ async function resolveCodexAppTarget() {
8187
8551
  ]);
8188
8552
  return { candidate: fallback, runningPids, mainPids };
8189
8553
  }
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
8554
  async function signalPids(pids, signal) {
8202
8555
  const signaled = [];
8203
8556
  for (const pid of pids) {
@@ -8209,25 +8562,8 @@ async function signalPids(pids, signal) {
8209
8562
  }
8210
8563
  return signaled;
8211
8564
  }
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;
8565
+ function toIso(value) {
8566
+ return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : null;
8231
8567
  }
8232
8568
  async function rememberProfileSwitch(profileKey) {
8233
8569
  await patchAppState({
@@ -8250,157 +8586,359 @@ async function rememberRestartResult(args) {
8250
8586
  }
8251
8587
  });
8252
8588
  }
8253
- async function restartOfficialCodexAppOnce(options) {
8589
+ function recordOfficialCodexRestartResult(args) {
8590
+ return rememberRestartResult(args);
8591
+ }
8592
+ function recordOfficialCodexProfileSwitch(profileKey) {
8593
+ return rememberProfileSwitch(profileKey);
8594
+ }
8595
+ async function getOfficialCodexProfileInstances() {
8254
8596
  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" };
8597
+ return {
8598
+ state: "unsupported",
8599
+ appPath: null,
8600
+ bundleId: null,
8601
+ version: null,
8602
+ instances: [],
8603
+ unmanaged: []
8604
+ };
8266
8605
  }
8267
- const { candidate, runningPids, mainPids } = await resolveCodexAppTarget();
8606
+ const { candidate, mainPids } = await resolveCodexAppTarget();
8268
8607
  if (!candidate) {
8269
- await rememberRestartResult({
8270
- status: "skipped",
8271
- reason: "official-codex-app-not-found",
8272
- profileKey: options.currentProfileKey
8273
- });
8274
- return { status: "skipped", reason: "official-codex-app-not-found" };
8275
- }
8276
- const appIsRunning = mainPids.length > 0;
8277
- if (!appIsRunning && !options.launchIfNotRunning) {
8278
- await rememberRestartResult({
8279
- status: "skipped",
8280
- reason: "official-codex-app-not-running",
8281
- profileKey: options.currentProfileKey
8282
- });
8283
- return { status: "skipped", reason: "official-codex-app-not-running" };
8608
+ return {
8609
+ state: "not-found",
8610
+ appPath: null,
8611
+ bundleId: null,
8612
+ version: null,
8613
+ instances: [],
8614
+ unmanaged: []
8615
+ };
8284
8616
  }
8285
- if (options.source !== "manual") {
8286
- lastRestartStartedAt = Date.now();
8617
+ const [state, rows] = await Promise.all([getAppState(), readProcessRows()]);
8618
+ const managed = [];
8619
+ for (const instance of Object.values(state.officialCodex.instancesByProfileName)) {
8620
+ const runtime = await resolveInstanceRuntimeState({ instance, candidate, rows });
8621
+ managed.push(
8622
+ managedInstanceToPayload(
8623
+ instance,
8624
+ runtime.running,
8625
+ runtime.appServerPid,
8626
+ runtime.startedAt
8627
+ )
8628
+ );
8287
8629
  }
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
8630
+ managed.sort((a, b) => a.profileName.localeCompare(b.profileName));
8631
+ const managedPids = new Set(
8632
+ managed.flatMap((entry) => entry.running && entry.pid ? [entry.pid] : [])
8633
+ );
8634
+ const unmanaged = [];
8635
+ for (const pid of mainPids.filter((entry) => !managedPids.has(entry))) {
8636
+ const appServerPid = findAppServerPid(rows, pid);
8637
+ const startedAt = rows.find((row) => row.pid === pid)?.startedAt ?? null;
8638
+ unmanaged.push({
8639
+ pid,
8640
+ appServerPid,
8641
+ startedAt: toIso(startedAt),
8642
+ uptimeMs: typeof startedAt === "number" ? Math.max(0, Date.now() - startedAt) : null,
8643
+ ...await describeUnmanagedProfileHint(appServerPid)
8302
8644
  });
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.", {
8645
+ }
8646
+ return {
8647
+ state: "found",
8648
+ appPath: candidate.appPath,
8649
+ bundleId: candidate.bundleId,
8650
+ version: candidate.version,
8651
+ instances: managed,
8652
+ unmanaged
8653
+ };
8654
+ }
8655
+ function launchOfficialCodexProfileInstance(options) {
8656
+ return enqueueProfileAction(async () => {
8657
+ if (process.platform !== "darwin") {
8658
+ return {
8659
+ status: "skipped",
8660
+ profileName: options.profileName,
8661
+ instance: null,
8662
+ reason: "unsupported-platform"
8663
+ };
8664
+ }
8665
+ const { candidate, mainPids } = await resolveCodexAppTarget();
8666
+ if (!candidate) {
8667
+ return {
8668
+ status: "skipped",
8669
+ profileName: options.profileName,
8670
+ instance: null,
8671
+ reason: "official-codex-app-not-found"
8672
+ };
8673
+ }
8674
+ const existing = await readManagedInstance(options.profileName);
8675
+ if (existing) {
8676
+ const rows = await readProcessRows();
8677
+ const runtime = await resolveInstanceRuntimeState({ instance: existing, candidate, rows });
8678
+ if (runtime.running) {
8679
+ const verified = {
8680
+ ...existing,
8681
+ profileHome: existing.profileHome ?? options.profileHome,
8682
+ appServerPid: runtime.appServerPid,
8683
+ lastVerifiedAt: Date.now(),
8684
+ lastStatus: "already-running",
8685
+ lastError: null
8686
+ };
8687
+ await patchManagedInstance(verified);
8688
+ return {
8689
+ status: "already-running",
8690
+ profileName: options.profileName,
8691
+ instance: managedInstanceToPayload(
8692
+ verified,
8693
+ true,
8694
+ runtime.appServerPid,
8695
+ runtime.startedAt
8696
+ ),
8697
+ reason: null
8698
+ };
8699
+ }
8700
+ }
8701
+ const launch = await openCodexWithProfileHome(
8702
+ candidate,
8703
+ options.profileHome,
8704
+ options.profileName,
8705
+ new Set(mainPids)
8706
+ );
8707
+ if (!launch.opened) {
8708
+ const failed = {
8709
+ profileName: options.profileName,
8710
+ profileKey: options.profileKey,
8711
+ profileHome: options.profileHome,
8307
8712
  appPath: candidate.appPath,
8308
8713
  bundleId: candidate.bundleId,
8309
- source: options.source,
8310
- remainingPids
8311
- });
8714
+ pid: null,
8715
+ appServerPid: null,
8716
+ launchedAt: null,
8717
+ lastVerifiedAt: null,
8718
+ lastStatus: "failed",
8719
+ lastError: "open-failed"
8720
+ };
8721
+ await patchManagedInstance(failed);
8722
+ return {
8723
+ status: "failed",
8724
+ profileName: options.profileName,
8725
+ instance: managedInstanceToPayload(failed, false),
8726
+ reason: "open-failed"
8727
+ };
8728
+ }
8729
+ const launchedPid = launch.pid;
8730
+ if (launchedPid === null) {
8312
8731
  const failed = {
8732
+ profileName: options.profileName,
8733
+ profileKey: options.profileKey,
8734
+ profileHome: options.profileHome,
8735
+ appPath: candidate.appPath,
8736
+ bundleId: candidate.bundleId,
8737
+ pid: null,
8738
+ appServerPid: null,
8739
+ launchedAt: null,
8740
+ lastVerifiedAt: null,
8741
+ lastStatus: "failed",
8742
+ lastError: "launch-timeout"
8743
+ };
8744
+ await patchManagedInstance(failed);
8745
+ return {
8313
8746
  status: "failed",
8747
+ profileName: options.profileName,
8748
+ instance: managedInstanceToPayload(failed, false),
8749
+ reason: "launch-timeout"
8750
+ };
8751
+ }
8752
+ const appServerPid = await waitForAppServerPid(
8753
+ launchedPid,
8754
+ options.profileHome
8755
+ );
8756
+ if (appServerPid === null) {
8757
+ const failed = {
8758
+ profileName: options.profileName,
8759
+ profileKey: options.profileKey,
8760
+ profileHome: options.profileHome,
8314
8761
  appPath: candidate.appPath,
8315
8762
  bundleId: candidate.bundleId,
8316
- reason: "force-quit-timeout",
8317
- phase: "quit"
8763
+ pid: null,
8764
+ appServerPid: null,
8765
+ launchedAt: null,
8766
+ lastVerifiedAt: Date.now(),
8767
+ lastStatus: "failed",
8768
+ lastError: "app-server-not-verified"
8769
+ };
8770
+ await patchManagedInstance(failed);
8771
+ return {
8772
+ status: "failed",
8773
+ profileName: options.profileName,
8774
+ instance: managedInstanceToPayload(failed, false),
8775
+ reason: "app-server-not-verified"
8318
8776
  };
8319
- await rememberRestartResult({
8320
- status: failed.status,
8321
- reason: failed.reason,
8322
- profileKey: options.currentProfileKey
8323
- });
8324
- return failed;
8325
8777
  }
8326
- logOfficialCodexRestart("info", "Official Codex exited after force kill.", {
8327
- appPath: candidate.appPath,
8328
- bundleId: candidate.bundleId,
8329
- source: options.source
8330
- });
8331
- }
8332
- const opened = await openCodex(candidate);
8333
- if (!opened) {
8334
- const failed = {
8335
- status: "failed",
8778
+ const now = Date.now();
8779
+ const instance = {
8780
+ profileName: options.profileName,
8781
+ profileKey: options.profileKey,
8782
+ profileHome: options.profileHome,
8336
8783
  appPath: candidate.appPath,
8337
8784
  bundleId: candidate.bundleId,
8338
- reason: "open-failed",
8339
- phase: "launch"
8785
+ pid: launchedPid,
8786
+ appServerPid,
8787
+ launchedAt: now,
8788
+ lastVerifiedAt: now,
8789
+ lastStatus: "started",
8790
+ lastError: null
8340
8791
  };
8341
- await rememberRestartResult({
8342
- status: failed.status,
8343
- reason: failed.reason,
8344
- profileKey: options.currentProfileKey
8345
- });
8346
- return failed;
8347
- }
8348
- const launchedPid = await waitForCodexLaunch(candidate);
8349
- if (launchedPid === null) {
8350
- logOfficialCodexRestart("error", "Official Codex launch was not verified.", {
8351
- appPath: candidate.appPath,
8352
- bundleId: candidate.bundleId,
8353
- source: options.source
8354
- });
8355
- const failed = {
8356
- status: "failed",
8357
- appPath: candidate.appPath,
8358
- bundleId: candidate.bundleId,
8359
- reason: "launch-timeout",
8360
- phase: "launch"
8792
+ await patchManagedInstance(instance);
8793
+ return {
8794
+ status: "started",
8795
+ profileName: options.profileName,
8796
+ instance: managedInstanceToPayload(instance, true, appServerPid),
8797
+ reason: null
8361
8798
  };
8362
- await rememberRestartResult({
8363
- status: failed.status,
8364
- reason: failed.reason,
8365
- profileKey: options.currentProfileKey
8366
- });
8367
- return failed;
8368
- }
8369
- const status = appIsRunning ? "restarted" : "started";
8370
- logOfficialCodexRestart("info", "Official Codex launch verified.", {
8371
- appPath: candidate.appPath,
8372
- bundleId: candidate.bundleId,
8373
- source: options.source,
8374
- status,
8375
- pid: launchedPid
8376
8799
  });
8377
- await rememberRestartResult({
8378
- status,
8379
- profileKey: options.currentProfileKey,
8380
- pid: launchedPid
8800
+ }
8801
+ function stopOfficialCodexProfileInstance(profileName) {
8802
+ return enqueueProfileAction(async () => {
8803
+ const existing = await readManagedInstance(profileName);
8804
+ if (!existing) {
8805
+ return {
8806
+ status: "not-running",
8807
+ profileName,
8808
+ instance: null,
8809
+ reason: "not-managed"
8810
+ };
8811
+ }
8812
+ const { candidate } = await resolveCodexAppTarget();
8813
+ if (!candidate) {
8814
+ const stopped2 = {
8815
+ ...existing,
8816
+ pid: null,
8817
+ appServerPid: null,
8818
+ lastVerifiedAt: Date.now(),
8819
+ lastStatus: "not-running",
8820
+ lastError: "official-codex-app-not-found"
8821
+ };
8822
+ await patchManagedInstance(stopped2);
8823
+ return {
8824
+ status: "not-running",
8825
+ profileName,
8826
+ instance: managedInstanceToPayload(stopped2, false),
8827
+ reason: "official-codex-app-not-found"
8828
+ };
8829
+ }
8830
+ const rows = await readProcessRows();
8831
+ const runtime = await resolveInstanceRuntimeState({ instance: existing, candidate, rows });
8832
+ if (!runtime.running || !existing.pid) {
8833
+ const stopped2 = {
8834
+ ...existing,
8835
+ pid: null,
8836
+ appServerPid: null,
8837
+ lastVerifiedAt: Date.now(),
8838
+ lastStatus: "not-running",
8839
+ lastError: null
8840
+ };
8841
+ await patchManagedInstance(stopped2);
8842
+ return {
8843
+ status: "not-running",
8844
+ profileName,
8845
+ instance: managedInstanceToPayload(stopped2, false),
8846
+ reason: null
8847
+ };
8848
+ }
8849
+ const tree = [existing.pid, ...getDescendantPids(existing.pid, rows)].filter((pid, index, all) => all.indexOf(pid) === index).sort((a, b) => b - a);
8850
+ await signalPids(tree, "SIGTERM");
8851
+ const exited = await waitForMainPidExit(existing.pid, 5e3);
8852
+ if (!exited) {
8853
+ await signalPids(tree, "SIGKILL");
8854
+ await waitForMainPidExit(existing.pid, EXIT_WAIT_MS);
8855
+ }
8856
+ const stillRunning = (await readProcessRows()).some((row) => row.pid === existing.pid);
8857
+ const stopped = {
8858
+ ...existing,
8859
+ pid: stillRunning ? existing.pid : null,
8860
+ appServerPid: stillRunning ? runtime.appServerPid : null,
8861
+ lastVerifiedAt: Date.now(),
8862
+ lastStatus: stillRunning ? "failed" : "stopped",
8863
+ lastError: stillRunning ? "stop-timeout" : null
8864
+ };
8865
+ await patchManagedInstance(stopped);
8866
+ return {
8867
+ status: stillRunning ? "failed" : "stopped",
8868
+ profileName,
8869
+ instance: managedInstanceToPayload(
8870
+ stopped,
8871
+ stillRunning,
8872
+ stopped.appServerPid,
8873
+ runtime.startedAt
8874
+ ),
8875
+ reason: stillRunning ? "stop-timeout" : null
8876
+ };
8381
8877
  });
8382
- return {
8383
- status,
8384
- appPath: candidate.appPath,
8385
- bundleId: candidate.bundleId,
8386
- pid: launchedPid
8387
- };
8388
8878
  }
8389
- function recordOfficialCodexProfileSwitch(profileKey) {
8390
- return rememberProfileSwitch(profileKey);
8879
+ function stopOfficialCodexObservedProfileInstance(profileName, pid, appServerPid) {
8880
+ return enqueueProfileAction(async () => {
8881
+ const { candidate } = await resolveCodexAppTarget();
8882
+ if (!candidate) {
8883
+ return {
8884
+ status: "not-running",
8885
+ profileName,
8886
+ instance: null,
8887
+ reason: "official-codex-app-not-found"
8888
+ };
8889
+ }
8890
+ const rows = await readProcessRows();
8891
+ const mainRow = rows.find((row) => row.pid === pid);
8892
+ if (!mainRow || !isMainProcessRow(mainRow, candidate)) {
8893
+ return {
8894
+ status: "not-running",
8895
+ profileName,
8896
+ instance: null,
8897
+ reason: null
8898
+ };
8899
+ }
8900
+ const runtimeAppServerPid = findAppServerPid(rows, pid) ?? appServerPid;
8901
+ const tree = [pid, ...getDescendantPids(pid, rows)].filter((entry, index, all) => all.indexOf(entry) === index).sort((a, b) => b - a);
8902
+ await signalPids(tree, "SIGTERM");
8903
+ const exited = await waitForMainPidExit(pid, 5e3);
8904
+ if (!exited) {
8905
+ await signalPids(tree, "SIGKILL");
8906
+ await waitForMainPidExit(pid, EXIT_WAIT_MS);
8907
+ }
8908
+ const stillRunning = (await readProcessRows()).some((row) => row.pid === pid);
8909
+ const instance = {
8910
+ profileName,
8911
+ profileKey: null,
8912
+ appPath: candidate.appPath,
8913
+ bundleId: candidate.bundleId,
8914
+ pid: stillRunning ? pid : null,
8915
+ appServerPid: stillRunning ? runtimeAppServerPid : null,
8916
+ running: stillRunning,
8917
+ startedAt: stillRunning ? toIso(mainRow.startedAt) : null,
8918
+ uptimeMs: stillRunning && typeof mainRow.startedAt === "number" ? Math.max(0, Date.now() - mainRow.startedAt) : null,
8919
+ launchedAt: null,
8920
+ lastVerifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
8921
+ lastStatus: stillRunning ? "failed" : "stopped",
8922
+ lastError: stillRunning ? "stop-timeout" : null
8923
+ };
8924
+ return {
8925
+ status: stillRunning ? "failed" : "stopped",
8926
+ profileName,
8927
+ instance,
8928
+ reason: stillRunning ? "stop-timeout" : null
8929
+ };
8930
+ });
8391
8931
  }
8392
- function restartOfficialCodexApp(options = {}) {
8393
- if (restartPromise) {
8394
- return restartPromise;
8932
+ async function restartOfficialCodexProfileInstance(options) {
8933
+ const stopped = await stopOfficialCodexProfileInstance(options.profileName);
8934
+ if (stopped.status === "failed") {
8935
+ return stopped;
8395
8936
  }
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;
8402
- });
8403
- return restartPromise;
8937
+ const launched = await launchOfficialCodexProfileInstance(options);
8938
+ return {
8939
+ ...launched,
8940
+ status: launched.status === "started" ? "restarted" : launched.status
8941
+ };
8404
8942
  }
8405
8943
 
8406
8944
  // src/commands/profile.ts
@@ -8852,33 +9390,190 @@ async function rememberOfficialCodexSwitch(profileKey) {
8852
9390
  );
8853
9391
  }
8854
9392
  }
8855
- function formatOfficialCodexRestart(result) {
9393
+ function formatOfficialCodexProfileAction(result) {
8856
9394
  if (result.status === "restarted") {
8857
9395
  return "Official Codex restarted.";
8858
9396
  }
8859
9397
  if (result.status === "started") {
8860
9398
  return "Official Codex started.";
8861
9399
  }
9400
+ if (result.status === "already-running") {
9401
+ return "Official Codex already running.";
9402
+ }
8862
9403
  if (result.status === "failed") {
8863
9404
  return `Official Codex restart failed: ${result.reason}.`;
8864
9405
  }
8865
- if (result.status === "skipped") {
9406
+ if (result.status === "skipped" || result.status === "not-running") {
8866
9407
  return `Official Codex restart skipped: ${result.reason}.`;
8867
9408
  }
8868
9409
  return "Official Codex restart status unknown.";
8869
9410
  }
9411
+ function findUnmanagedOfficialCodexRestartTarget(instances, profileName, profileKey) {
9412
+ const restartable = instances.unmanaged.filter((instance) => instance.pid);
9413
+ const matched = restartable.find((instance) => {
9414
+ if (profileKey && instance.profileKey) {
9415
+ return instance.profileKey === profileKey;
9416
+ }
9417
+ if (instance.profileKey) {
9418
+ return false;
9419
+ }
9420
+ return instance.profileName === profileName;
9421
+ }) ?? null;
9422
+ if (matched) {
9423
+ return { target: matched, ambiguous: false, unverified: false };
9424
+ }
9425
+ if (restartable.length > 1) {
9426
+ return { target: null, ambiguous: true, unverified: false };
9427
+ }
9428
+ return { target: null, ambiguous: false, unverified: restartable.length === 1 };
9429
+ }
9430
+ async function restartUnmanagedOfficialCodexWindow(profileName, launchOptions, target) {
9431
+ const stopped = await stopOfficialCodexObservedProfileInstance(
9432
+ target.profileName ?? profileName,
9433
+ target.pid,
9434
+ target.appServerPid
9435
+ );
9436
+ if (stopped.status === "failed") {
9437
+ return stopped;
9438
+ }
9439
+ const launched = await launchOfficialCodexProfileInstance(launchOptions);
9440
+ return {
9441
+ ...launched,
9442
+ status: stopped.status === "stopped" && launched.status === "started" ? "restarted" : launched.status
9443
+ };
9444
+ }
9445
+ async function recordCliOfficialCodexRestartResult(profileName, profileKey, action) {
9446
+ const pid = action.status === "started" || action.status === "restarted" ? action.instance?.pid ?? null : null;
9447
+ const status = action.status === "started" || action.status === "restarted" ? action.status : action.status === "failed" ? "failed" : "skipped";
9448
+ const reason = action.status === "started" || action.status === "restarted" ? null : action.reason ?? action.status;
9449
+ try {
9450
+ await recordOfficialCodexRestartResult({
9451
+ status,
9452
+ reason,
9453
+ profileKey,
9454
+ pid
9455
+ });
9456
+ await recordOfficialCodexActivity({
9457
+ kind: "official-codex-restart",
9458
+ status,
9459
+ reason,
9460
+ targetProfileName: profileName,
9461
+ profileKey,
9462
+ phase: "cli",
9463
+ pid,
9464
+ restartRequested: true,
9465
+ restartResult: status
9466
+ });
9467
+ } catch (error) {
9468
+ console.warn(
9469
+ `Could not record official Codex restart state: ${error instanceof Error ? error.message : String(error)}`
9470
+ );
9471
+ }
9472
+ }
9473
+ async function recordCliOfficialCodexRestartSkipped(profileName, profileKey, reason) {
9474
+ await recordCliOfficialCodexRestartResult(profileName, profileKey, {
9475
+ status: "skipped",
9476
+ profileName,
9477
+ instance: null,
9478
+ reason
9479
+ });
9480
+ }
8870
9481
  async function maybeRestartOfficialCodex(options) {
8871
9482
  if (!options.enabled) {
8872
9483
  return null;
8873
9484
  }
8874
9485
  try {
8875
- const result = await restartOfficialCodexApp({
8876
- source: options.source,
8877
- currentProfileKey: options.profileKey,
8878
- launchIfNotRunning: options.launchIfNotRunning
8879
- });
8880
- return formatOfficialCodexRestart(result);
9486
+ const runtime = await options.manager.prepareProfileRuntime(options.profileName);
9487
+ const launchOptions = {
9488
+ profileName: options.profileName,
9489
+ profileKey: options.profileKey,
9490
+ profileHome: runtime.profileHome
9491
+ };
9492
+ const instances = await getOfficialCodexProfileInstances();
9493
+ if (instances.state === "unsupported") {
9494
+ await recordCliOfficialCodexRestartSkipped(
9495
+ options.profileName,
9496
+ options.profileKey,
9497
+ "unsupported-platform"
9498
+ );
9499
+ return "Official Codex restart skipped: unsupported-platform.";
9500
+ }
9501
+ if (instances.state === "not-found") {
9502
+ await recordCliOfficialCodexRestartSkipped(
9503
+ options.profileName,
9504
+ options.profileKey,
9505
+ "official-codex-app-not-found"
9506
+ );
9507
+ return "Official Codex restart skipped: official-codex-app-not-found.";
9508
+ }
9509
+ const managedRunning = instances.instances.some(
9510
+ (instance) => instance.profileName === options.profileName && instance.running
9511
+ );
9512
+ const unmanaged = findUnmanagedOfficialCodexRestartTarget(
9513
+ instances,
9514
+ options.profileName,
9515
+ options.profileKey
9516
+ );
9517
+ if (unmanaged.target && !managedRunning) {
9518
+ const action2 = await restartUnmanagedOfficialCodexWindow(
9519
+ options.profileName,
9520
+ launchOptions,
9521
+ unmanaged.target
9522
+ );
9523
+ await recordCliOfficialCodexRestartResult(
9524
+ options.profileName,
9525
+ options.profileKey,
9526
+ action2
9527
+ );
9528
+ return formatOfficialCodexProfileAction(action2);
9529
+ }
9530
+ if (unmanaged.ambiguous && !managedRunning) {
9531
+ await recordCliOfficialCodexRestartSkipped(
9532
+ options.profileName,
9533
+ options.profileKey,
9534
+ "ambiguous-official-codex-windows"
9535
+ );
9536
+ return "Official Codex restart skipped: ambiguous-official-codex-windows.";
9537
+ }
9538
+ if (unmanaged.unverified && !managedRunning) {
9539
+ await recordCliOfficialCodexRestartSkipped(
9540
+ options.profileName,
9541
+ options.profileKey,
9542
+ "unverified-official-codex-window"
9543
+ );
9544
+ return "Official Codex restart skipped: unverified-official-codex-window.";
9545
+ }
9546
+ if (options.launchIfNotRunning === false) {
9547
+ if (!managedRunning) {
9548
+ await recordCliOfficialCodexRestartSkipped(
9549
+ options.profileName,
9550
+ options.profileKey,
9551
+ "official-codex-app-not-running"
9552
+ );
9553
+ return "Official Codex restart skipped: official-codex-app-not-running.";
9554
+ }
9555
+ const action2 = await restartOfficialCodexProfileInstance(launchOptions);
9556
+ await recordCliOfficialCodexRestartResult(
9557
+ options.profileName,
9558
+ options.profileKey,
9559
+ action2
9560
+ );
9561
+ return formatOfficialCodexProfileAction(action2);
9562
+ }
9563
+ const action = await restartOfficialCodexProfileInstance(launchOptions);
9564
+ await recordCliOfficialCodexRestartResult(
9565
+ options.profileName,
9566
+ options.profileKey,
9567
+ action
9568
+ );
9569
+ return formatOfficialCodexProfileAction(action);
8881
9570
  } catch (error) {
9571
+ await recordCliOfficialCodexRestartResult(options.profileName, options.profileKey, {
9572
+ status: "failed",
9573
+ profileName: options.profileName,
9574
+ instance: null,
9575
+ reason: error instanceof Error ? error.message : "restart-threw"
9576
+ });
8882
9577
  return `Official Codex restart failed: ${error instanceof Error ? error.message : String(error)}.`;
8883
9578
  }
8884
9579
  }
@@ -9014,8 +9709,9 @@ async function runAutoRollPass(manager, options) {
9014
9709
  const targetProfileKey = profileRateLimitKey(candidate.profile);
9015
9710
  await rememberOfficialCodexSwitch(targetProfileKey);
9016
9711
  const restartMessage = await maybeRestartOfficialCodex({
9712
+ manager,
9017
9713
  enabled: options.restartOfficialCodex,
9018
- source: "auto-roll",
9714
+ profileName: candidate.profile.name,
9019
9715
  profileKey: targetProfileKey,
9020
9716
  launchIfNotRunning: options.launchOfficialCodexWhenClosed === true
9021
9717
  });
@@ -9129,8 +9825,9 @@ async function handleProfileCommand(args, version) {
9129
9825
  console.log(`Switched to profile: ${name}`);
9130
9826
  await rememberOfficialCodexSwitch(targetProfileKey);
9131
9827
  const restartMessage = await maybeRestartOfficialCodex({
9828
+ manager,
9132
9829
  enabled: restartOfficialCodex,
9133
- source: "manual",
9830
+ profileName: name,
9134
9831
  profileKey: targetProfileKey
9135
9832
  });
9136
9833
  if (restartMessage) {
@@ -11503,7 +12200,7 @@ async function ensureCliStorageReady() {
11503
12200
  }
11504
12201
 
11505
12202
  // src/app/main.ts
11506
- var VERSION = true ? "3.9.7" : "0.0.0";
12203
+ var VERSION = true ? "3.9.8" : "0.0.0";
11507
12204
  async function runCli() {
11508
12205
  const args = process.argv.slice(2);
11509
12206
  if (args.length === 0) {