diff --git a/CHANGELOG.md b/CHANGELOG.md index 626b0de..de99ad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 1.6.0 - 2026-07-11 - **Critical** — fixed the quota backfill reverting fresh data and bleeding across accounts. The root `~/.codex/profile.json` copy was only ever written at switch-in, so every write-back (switch, app launch, window close) copied that frozen snapshot over the profile's card and silently reverted the quota / plan updates recorded while it was active — permanently for API-key profiles, which no API refresh repairs; Windows had patched the switch path only (keyed off the possibly-stale `.current_profile` marker) and macOS had nothing. The write-back now folds the freshest live-session usage into the identity-verified target's card and refreshes the root copy first, on both platforms and on all four write-back paths (switch, app launch, window close, current-profile login); the legacy `codex-switch.sh` no longer copies the root `profile.json` back at all, closing its variant of the same clobber. Live-session data is scoped to entries written since the profile's activation (`activated_at` from the active marker): `~/.codex/sessions` is not swapped on switch, so the previously-live account's newer entries used to win the freshness race and show up as the current profile's quota for minutes after a switch — and when a profile has no parseable activation marker (an identity-drift target, a hand-edited marker) the fold is skipped outright rather than attributing unattributable sessions. A present-but-unreadable `profile.json` now aborts the write-back loudly instead of laundering a transient read failure into a blanked card. The duplicated Windows display path (which had already drifted: it lacked the unmanaged-account guard) now re-exports the shared implementation; an empty stored card with a newer timestamp (the deliberate downgrade-to-free clear) is no longer overridden by older live sessions; and switching kicks an immediate silent API refresh for the new card instead of priming its stored timestamp and waiting out the 5-minute tick. Known residual: a codex CLI session started under the previous account that keeps running across the switch keeps appending to a session file whose mtime passes the activation cutoff, so its usage can still be misattributed until that session exits. - **Critical** — adapted macOS desktop/CLI discovery for the Codex → ChatGPT.app merge. OpenAI now ships Codex inside `/Applications/ChatGPT.app` (bundle id still `com.openai.codex`, process name `ChatGPT`, CLI at `Contents/Resources/codex`). The app previously only probed `Codex.app` and `pgrep -x Codex`, so after the merge: (1) auto-detect / login / app-server fallback reported `REAL_CODEX_NOT_FOUND` even with a healthy install, and (2) profile switch skipped quitting the running desktop host, leaving its in-memory app-server on the old account. Discovery now prefers `ChatGPT.app` over legacy `Codex.app` across both `/Applications` and `~/Applications`, and a bundle only qualifies when it actually embeds the codex CLI — a chat-only consumer ChatGPT.app (`com.openai.chat`) is never selected. is-running / quit key off the stable bundle id, and their process-name fallbacks verify each PID's bundle identity before trusting or signalling it — the consumer ChatGPT chat client is never killed as collateral, and a PID whose identity can't be established still counts as "running" (the switch aborts rather than proceeding over a possibly-live host) but is never signalled; the polite AppleScript quit is fired without waiting (a quit-confirmation dialog or the first-run Automation/TCC consent prompt can no longer hang the switch) and is cancelled once the host is gone so a consent granted later can't close the relaunched app. activate uses the bundle id; open tries the on-disk bundle path, then the bundle id, then the unambiguous legacy `Codex` display name (never `ChatGPT` by name — it could resolve to the consumer chat client), checking each `open`'s exit status so a silent launch failure surfaces as a warning instead of a fake success. macOS-only; Windows Store path still needs a separate pass if the Win host also renamed. diff --git a/README.md b/README.md index 6f7b99f..385c826 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ Windows 构建走 CI 的 `tauri build --target x86_64-pc-windows-msvc`;本地 ```bash npm run version:sync # 把 package.json 当前 version 同步到 Cargo / lock -npm run version:set -- 1.5.11 # 一次性 bump 到指定版本 +npm run version:set -- 1.6.0 # 一次性 bump 到指定版本 npm run version:check # CI 用:拒绝把 semver 字面量写回 *.html ``` diff --git a/README.zh-CN.md b/README.zh-CN.md index 6f7b99f..385c826 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -135,7 +135,7 @@ Windows 构建走 CI 的 `tauri build --target x86_64-pc-windows-msvc`;本地 ```bash npm run version:sync # 把 package.json 当前 version 同步到 Cargo / lock -npm run version:set -- 1.5.11 # 一次性 bump 到指定版本 +npm run version:set -- 1.6.0 # 一次性 bump 到指定版本 npm run version:check # CI 用:拒绝把 semver 字面量写回 *.html ``` diff --git a/package-lock.json b/package-lock.json index 42b8987..2260983 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codex_switch", - "version": "1.5.11", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codex_switch", - "version": "1.5.11", + "version": "1.6.0", "dependencies": { "@tauri-apps/api": "2.10.1" }, diff --git a/package.json b/package.json index e946086..a4b56df 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "codex_switch", "private": true, - "version": "1.5.11", + "version": "1.6.0", "type": "module", "scripts": { "dev": "vite", diff --git a/release-notes/v1.6.0.md b/release-notes/v1.6.0.md new file mode 100644 index 0000000..1dadf0e --- /dev/null +++ b/release-notes/v1.6.0.md @@ -0,0 +1,41 @@ +# Codex Switch v1.6.0 + +## 中文 + +### 账号切换可靠性修复:串号、ChatGPT.app 合并、额度回滚 + +本版本修复切换链路三类问题:写回盲信 `.current_profile` 标记,账号漂移后被写进错误档案(串号);Codex 并入 `ChatGPT.app` 后按名称探测失效,健康安装报 `REAL_CODEX_NOT_FOUND`;额度回填用切入时冻结的根 `profile.json` 快照覆盖已刷新数据,额度反复回滚。 + +本版本改动: + +- 写回目标按账号身份校验(`tokens.account_id` 与 id_token `email` 双识别),漂移账号路由到真正拥有者;未管理账号清标记并在面板提示 +- 适配 `ChatGPT.app` 合并:发现与退出按稳定 bundle id `com.openai.codex`,`ChatGPT.app` 优先、`Codex.app` 兜底,候选须内嵌 codex CLI +- 进程信号先做 PID 归属校验,不误杀消费级 ChatGPT(`com.openai.chat`);AppleScript 退出不再同步等待(TCC 弹窗不再挂起切换),退出后取消 pending 事件 +- 每次写回(切换、启动、关窗、重登)前刷新根 `profile.json` 并折算 live 用量,冻结快照不再回滚;元数据损坏时写回响亮失败而非清空卡片 +- live 会话按激活时间(`activated_at`)归属,上一账号用量不再串显/串存;切换完成即触发静默额度刷新 + +### 验证 + +- `cargo test --lib`(137 通过,含回滚/归属/激活过滤回归用例) +- `npx tsc --noEmit` / `cargo fmt --all -- --check` +- `cargo run --example live_chatgpt_merge_check` + +## English + +### Reliability fixes: cross-contamination, the ChatGPT.app merge, quota rollback + +This release fixes three switching-pipeline defects: the write-back trusted the `.current_profile` marker blindly, so a drifted live account overwrote the wrong profile; the Codex merge into `ChatGPT.app` broke name-based probes, reporting `REAL_CODEX_NOT_FOUND` on healthy installs; and the quota backfill kept copying the switch-in-frozen root `profile.json` snapshot over refreshed data, rolling quotas back. + +Changes in this release: + +- Write-back targets are identity-verified (matching `tokens.account_id` and the id_token `email`); a drifted account routes to its real owner, an unmanaged one clears the stale marker with a prompt +- The `ChatGPT.app` merge: discovery and lifecycle key off the stable bundle id `com.openai.codex`, preferring `ChatGPT.app` with `Codex.app` fallback; candidates must embed the codex CLI +- Process signalling verifies each PID's bundle first — the consumer ChatGPT client (`com.openai.chat`) is never killed by a name match; the AppleScript quit is no longer awaited (TCC prompts cannot hang a switch) and is cancelled after exit +- Every write-back (switch, launch, window close, re-login) refreshes the root `profile.json` and folds live usage first; corrupt metadata fails loudly instead of blanking the card +- Live sessions are attributed by activation time (`activated_at`), so a previous account's usage is no longer shown or persisted; each switch kicks an immediate silent quota refresh + +### Verification + +- `cargo test --lib` (137 passed, incl. rollback / attribution / activation-scoping regressions) +- `npx tsc --noEmit` / `cargo fmt --all -- --check` +- `cargo run --example live_chatgpt_merge_check` diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 5b2ef27..3514215 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -466,7 +466,7 @@ dependencies = [ [[package]] name = "codex_switch" -version = "1.5.11" +version = "1.6.0" dependencies = [ "base64 0.22.1", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 97a9012..7fa6320 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codex_switch" -version = "1.5.11" +version = "1.6.0" description = "Native Tauri control panel for Codex account switching" authors = ["Cmochance"] edition = "2021"