Repository navigation
feat(minimaxcode): add MiniMax Code platform support - #2918
Closed
SkyblueeeLabs wants to merge 28 commits into
Closed
SkyblueeeLabs wants to merge 28 commits into
SkyblueeeLabs wants to merge 28 commits into
Conversation
Adding 'minimaxcode' to legacyDefaultPlatformGroups() made an existing user's 3-member codebuddy-suite group count as "customised", so it was no longer retired by the default-grouping migration. That broke usePlatformLayoutStore.test.ts and silently changed existing users' layout behaviour. MiniMax Code is a distinct product, so it stays a standalone sidebar platform (TRAY_MIGRATED_PLATFORM_IDS already covers tray migration). Existing users' layouts are unchanged.
…r id Local import has no uid and its email is the record-key hash (no "@"), so upsert_account fell back to the constant "minimax_code_user" and every locally imported account collapsed onto the same id. Add an optional `id` to the OAuth-complete payload and use it as the identity seed after uid/email. payload_from_local_record now passes the record-key account component (stable per data dir), and the email becomes a readable label.
Three independent blockers stopped the dev app from starting on Windows. 1. `scripts/tauri-dev.cjs` spawned `npm` and `tauri` directly. On Windows those resolve to `npm.cmd`/`tauri.cmd`, and a bare `spawnSync` returns ENOENT (or EINVAL once a shell is involved) for `.cmd` shims. Because `result.error` was never checked, `status` was null and the script exited 1 without printing anything -- exactly the "runs `node scripts/tauri-dev.cjs` and then nothing" symptom. Delegate to `scripts/tauri.cjs`, which already resolves `npm.cmd`/`npx.cmd` and initializes the MSVC environment through `vcvars64.bat`, so the Windows plumbing lives in one place instead of two drifting copies. 2. Port 1420 sits inside the Windows Hyper-V/WSL excluded TCP range (1332-1431), so Vite failed with `listen EACCES: permission denied ::1:1420` on any machine that reserves it. Move the dev server to 5173 (Vite's own default, not reserved) and derive the HMR port from it. 3. `tauri-build` validates `bundle.externalBin` even under `tauri dev`, so a machine without Go failed the whole build. `build.rs` now skips the sidecar in debug builds when the Go toolchain is absent (release builds still fail loudly, since they must never ship without it), and `tauri.dev.conf.json` drops the `externalBin` requirement for dev. Also adds `minimax_code` to the general-config diagnostics log line, which listed every other platform's auto-refresh interval but this one. Verified: `npm run tauri:dev` builds and launches on Windows with no manual environment setup.
Covers the regression set that must not break (other platforms, tray ids, layout) plus the MiniMax Code specific surface: every account import path, credential-switch behaviour, quota, manual and automatic check-in, multi- instance launches, and settings/integration parity with WorkBuddy.
…ing 401s as quota
Quota never rendered, and a dead token looked identical to an account with no
quota. Two independent causes, both found by reading the official client's own
parser out of app.asar.
1. Wrong envelope. `GET /minimax-cloud/api/v1/credit/details` returns
`details` / `total_count` at the TOP LEVEL of the body. The client does
`let {data} = await axios.get(url)` and then reads `data.details`, and its
response interceptor returns the axios response ununwraped, so `data` is the
raw HTTP body. `wrapMiniMaxCreditsAsResource` was reading `body.data.details`
and therefore always found zero rows. It now reads the top level, keeping the
nested shape as a fallback.
Note the check-in endpoints really do nest under `data` (the client's `eI`
asserts `e.data` before returning it), so that asymmetry is genuine and the
two parsers are correct as they stand.
2. 401 swallowed as quota data. The endpoint answers 401 with a JSON body
(`{"error":"invalid access token"}`), and `fetch_credit_details` only checked
that the body parsed, so the error envelope was stored as `quota_raw` and
`quota_query_last_error` was cleared. The account then showed the plain
"no quota data" empty state with no hint that the credential was dead. The
status and the `base_resp` / `statusInfo` business codes are now rejected
before the body is stored, which makes the existing shared-view path render
"配额查询失败" (plus the last good data, with its timestamp) instead.
Also drops detail rows the official client would discard, so an unusable row
cannot render as a bogus `0 / 0` package.
Verified: 8/8 parser tests and 45/45 MiniMax Rust tests pass, including a
regression test that pins the 401 body as an error.
…th login OAuth login always failed with "获取 MiniMax Code 账号信息失败:未返回 uid 或邮箱", even though the token was minted successfully. The device grant worked, the token exchange worked, and then the identity lookup produced nothing. `GET /v1/api/user/info` nests the profile under `data.userInfo`, and the official client reads `realUserID`/`userID`, `email`/`userMail`/`user_email`, `name`/`userName`/`user_name` and `subUserName` from there. This parser read those fields straight off `data`, so every one of them missed and the caller fell through to its empty-identity error. Field names were taken from the client's own `fetchIdentity()` in app.asar, including its `data.user_info` / `body.userInfo` fallbacks. Also routes the identity lookup through the shared error-envelope check. Without it a 401 or a non-zero `statusInfo` code was silently read as "no fields", which is the same failure mode that made the dead-token quota bug invisible. Verified: 7/7 api-response tests and 48/48 MiniMax Rust tests pass, including a regression test pinning the real `data.userInfo` nesting.
OAuth login failed with `查询 user/info 失败 (HTTP 400 Bad Request: 400 Bad Request)` after the token was minted successfully. The endpoint requires the client's query params (`device_platform=mcode&biz_id=3&app_id=3001&version_code=22201&unix=…&timezone_offset=…`); without them it answers HTTP 400 with a body that carries no `error` field, which the error reporting then rendered as the bare status string. Param shape is taken from the official client's `fetchIdentity()` and matches a working independent implementation of the same endpoint. Also extracts the failure reason from the shapes these endpoints actually use (`error` as a string or an object with a message, `error_description`, `message`, `status_msg`, `statusInfo.message`, `base_resp.status_msg`), instead of falling back to "400 Bad Request" whenever the body has no top-level `error` string. That fallback is what made this bug undiagnosable from the UI. Verified: 8/8 api-response tests and 49/49 MiniMax Rust tests pass, including a test that pins the required query params.
… group label
Two display problems on the account that OAuth just imported.
1. The card showed the raw numeric realUserID ("570025187764355072").
`/v1/api/user/info` usually returns no email address, so the payload
builders fell straight through to `uid` — a number, not a name. They now
resolve email → username → id, the nickname prefers `subUserName` (the login
name) over the generated `userName`, and the card resolver prefers the
nickname so an already-imported account is fixed without re-adding it. The
check-in modal had the same email-first assumption baked in, so it takes an
injectable resolver (WorkBuddy keeps its default).
2. Quota rendered a single "其他" row. The shared pipeline buckets by
`PackageCode`, and the bare `'free'`/`'purchased'` strings it was emitting
match no known code, so everything fell through to the `其他` group. Mapping
`credit_type` onto real codes puts them in labelled groups —
`1` 购买 → 加量包, `2` 签到赠送 → 活动赠送包 — and leaves `PackageName` empty
so the pipeline's own `resolvePackageName` supplies the label instead of a
second hardcoded copy.
Verified: 9/9 parser tests, 50/50 MiniMax Rust tests, 1630 TS tests (the 2
failures are the pre-existing codexAppInjection ones, unrelated).
…abel
The quota number next to a group label (e.g. "活动赠送包") was being folded onto
a second line. `.quota-category-value` had no `white-space: nowrap`, so as soon
as the label plus the number exceeded the row width the browser wrapped the
number itself — and because `formatLocalizedQuotaNumber` runs through
`Intl.NumberFormat`, the string only gets longer as the balance grows
("1,824 / 1,824" → "12,345 / 12,345"), which is exactly the 上万 case.
The detail row already did this correctly (name truncates, value
`flex-shrink: 0`); the header row did not. Mirror that pattern:
`.quota-category-label` gains `overflow: hidden` + `text-overflow: ellipsis` so
the label yields the space, `.quota-category-value` gains `white-space: nowrap`
+ `flex-shrink: 0` so the number always renders in full, and the count badge
stops being squished. `.quota-detail-value` gets the same `nowrap` for safety.
Verified: 1630 TS tests pass (the 2 failures are the pre-existing
codexAppInjection ones, unrelated).
The quota number read as if a digit were cut off. Pixel inspection of the
account card showed the "4" is actually drawn completely, so nothing is being
clipped — the glyph is just small (12px) and the mono stack rendered it with an
open top that reads as an incomplete "4".
Two changes:
- `--font-mono` had no Windows fallback ('JetBrains Mono' and 'SFMono-Regular'
are absent here), so it collapsed to the browser's generic monospace. Add
Cascadia Code / Cascadia Mono / Consolas / DejaVu Sans Mono ahead of it.
- Bump the quota number to 13px / weight 600 and add `tabular-nums`, so the
digits are equal-width and the glyphs are heavy enough to read at a glance.
Verified: both rules confirmed present in the CSS the dev server serves.
…efresh clobbering tokens Two problems with importing from the local client. 1. The account was named after the record-key digest. `payload_from_local_record` only has credentials to work with, so the account showed up as "MiniMax Code 本机账号 (grC2d5…)". The import now probes `/v1/api/user/info` (the same call the OAuth flow already uses) to get the real name, and takes the quota at the same time so the card is populated on the first render. If the probe fails the import still succeeds, falling back to the record. 2. Refreshing once destroyed the account's validity. `refresh_account_token_once` re-read the client's auth.json before every refresh and copied whatever it found back over the account. MiniMax rotates refresh tokens on each use, so once we had refreshed ourselves the file held an already-spent refresh token and an access token the server had since invalidated. Adopting it meant the next refresh failed with `invalid_grant` and every quota query after that returned 401 — the account looked like it had "lost validity after one refresh". The file's credentials are now adopted only when its `expiresAtMs` is at least as fresh as ours. Verified: 52/52 MiniMax Rust tests pass, including a regression test that pins the stale-file case.
…o feat/minimax-code-platform
SkyblueeeLabs
force-pushed
the
feat/minimax-code-platform
branch
from
October 25, 2026 17:08
425840e to
9b75109
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
这个 PR 做了什么
给 Cockpit Tools 增加 MiniMax Code 平台支持:多账号管理、账号切换、配额展示、每日签到(含自动签到)、应用多开。
实现上尽量复用 CodeBuddy / WorkBuddy 已有的链路 —— 共享 store、共享账号页、共享配额渲染管线、托盘、设置页、数据迁移、实例管理 —— 新增代码集中在
minimax_code_*模块里,对既有平台的改动都是追加式的(新增分支,不改变原行为)。账号来源
auth.json导入关键实现点
auth.json布局写入,record key ={service}\0{base64url(sha256("<dataDir>/auth\0mcode-public"))},并同步auth-state.json的generation,客户端可直接登录。MINIMAX_DATA_DIR,凭据命名空间天然隔离。credit/details的details在响应顶层(不是data.details),映射到共享的PackageCode后复用既有分组渲染。/signin/status、/signin/claim确实带data包封,与配额接口不同,两边分别处理。协议细节(OAuth 端点、两个接口的响应包封、
auth.json的 record key 算法)是从官方客户端app.asar反推并在本机实测确认的。验证
tests/codexAppInjection.test.ts(token 显示4.5Kvs4500),已确认在上游main上同样失败,与本 PR 无关。docs/minimax-code-manual-test-checklist.md。顺带修的一个开发体验问题
npm run tauri:dev在 Windows 上原本完全起不来(b5b3c204):dev 脚本用spawnSync('npm')解析.cmd失败且静默退出、端口 1420 落在 Hyper-V 保留 TCP 段、缺 Go 时tauri-build直接失败。现在无需手动设置环境变量即可启动。备注
分支已合并上游
main(v1.3.74),合并无冲突。