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