magicrew-auth 0.3.6 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,176 +1,306 @@
1
- # 更新记录
2
-
3
- ## 0.3.6
4
-
5
- **修:添加第二个账号时网页闪一下就关,magpie 里没多出账号**
6
-
7
- - 症状:点「添加账号」→ 浏览器正常弹出 → 一两秒内自己关掉;用户确实也登录成功了,但账号列表里没有新账号
8
- - 根因:登录浏览器**复用**同一个 profile(`~/.cache/magpie/magicrew-login-profile`)。第二个窗口一打开,
9
- localStorage 里就是上一个账号的 `MAGIC:user:authorization` / `organizationCode`,`waitForCreds`
10
- 第一轮(1.5 秒)读到它 → 判定登录完成 → 关窗;magpie 拿到的 `accountId` / `uid` 与已有账号完全相同,
11
- 按契约「同 accountId + 同 uid → 替换旧账号」→ 不新增
12
- - 修法:每次登录一个**一次性** profile(`magicrew-login-<pid>-<ts>-<rand>`),`closeSession()` 关窗后删;
13
- `launchLoginBrowser()` 开头先 `sweepProfiles()` 清掉历史复用目录与超过 2 小时的一次性目录
14
- (只认本插件造的名字,且只动目录 —— 同前缀的 `.log` 不碰)
15
- - 第二道防线:`waitForCreds(..., { baseline })` —— 窗口打开时就存在的凭据不算登录成功
16
- - 代价:每次都要在窗口里重新登录一次(独立 profile 本来也没有日常 cookie),换来「读到的凭据一定是本次的」
17
- - 判据:`node tools/_verify_profile_scope.mjs`(窗口打开 4 秒后 `READ_CREDS` 必须是空串、关窗后目录必须不存在)
18
- + `node tests/login-profile.mjs`
19
-
20
- ## 0.3.5
21
-
22
- **卡片上多一个「承载」徽章:`magpie.carries`**
23
-
24
- - 症状:Qoder 插件的卡片有「承载 Qoder 国际版 (qoder.com)、Qoder CN」,Magicrew 没有
25
- - 成因:那个徽章的数据源是 `provider.MovedOnto`,只认 **magpie 内置订阅被迁移到插件上**。
26
- Magicrew 从来不是内置订阅(`internal/provider/migrate.go` 的 `movers` 表),所以永远是空
27
- - 修法:magpie 加 `magpie.carries` —— 插件在 `package.json` 里自己声明它承载哪些供应商,
28
- 后端读进 `pluginEntryJSON.Carries`(JSON 字段 `carries`),前端照样画同一个 `pm-chip moved`
29
- - 本插件声明:`["超级麦吉 (letsmagic.cn)", "MagiCrew (magicrew.ai)"]`
30
- - 兼容性:**旧 magpie 不认这个字段,直接忽略**;只有带 `carries` 能力的 magpie 才画这个徽章
31
-
32
- **上下文窗口的三档优先级,兜底从 256K 提到 1M**
33
-
34
- ```
35
- ① 用户在 magpie GUI 里手选的「上下文窗口」 ← 最优先,magpie 直接用
36
- ② 模型库(~/.cache/magpie/models.json)查到的真实值
37
- ③ 兜底 1M
38
- ```
39
-
40
- - 兜底从 `256_000` 改成 `1_048_576`:给 1M 而实际只有 128K,上游会在超限时报错、用户看得见;
41
- 给 256K 而实际有 1M,agent 会主动拒绝一段本可以处理的对话 —— 那是静默的能力损失,
42
- 比报错更难查(`pi-ai detected context overflow` 就是这么来的)
43
- - 对照官方社区插件:Qoder 纯硬编码(`context: 1_000_000`);WorkBuddy 硬编码一张表,
44
- 再从自家 `/v3/config` 的 `maxInputTokens` 覆盖。magicrew 不提供后者,所以查模型库更省事
45
-
46
- **发布到 npm:包名 `magicrew-auth`**
47
-
48
- - `magpie plugin add magicrew-auth` 从此可用;npm 源的更新按 `version` 检测,有新版本会提示
49
- - git 源标题仍是 `Magicrew`:npm 拒绝含大写字母的新包名,所以发布走 `npm run publish:npm`
50
- —— `publish-npm.mjs` 临时把 `name` 换成 `magicrew-auth`、跑完无论成败都还原(已实测逐字节一致)
51
- - **README 模型段改版** —— 按 [magpie-plugin-scaffold 0.1.7](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/README-TEMPLATE.md):
52
- 开头写发行商(GLM / DeepSeek / Qwen / Kimi…),不写「支持 N 个模型」;表格只列各家最新 / 热门
53
-
54
- ## 0.3.4
55
-
56
- **上下文窗口向 magpie 的模型库查,不再一律填 256K**
57
-
58
- - 症状:`pi-ai detected context overflow for model "magicrew/deepseek-v4.1-flash"` —— 会话明明还能继续
59
- - 成因:magicrew 的两个模型接口(`/api/v1/flows/models`、`/v1/models`)**都不返回上下文窗口**,
60
- 早前一律填 `256_000` 当保守值。而 `deepseek-v4.1-flash` 实际是 **1,048,576**,
61
- 等于把可用容量砍掉四分之三 —— agent 拿 256K 判断"放不下"就拒绝了
62
- - 修法:`src/model-context.mjs` 向 magpie 自己的 `~/.cache/magpie/models.json` 查
63
- (按 `limit.context`,支持全名 / 尾段 / 多厂商前缀取最大),查不到才退回 256K
64
- - 实测:deepseek-v4.1-flash 1,048,576 · qwen3.5-flash 1,000,000 · glm-5.3-flash 262,144 ·
65
- kimi-k3.1-pro 1,048,576 · 查不到的 256,000
66
-
67
- ## 0.3.3
68
-
69
- **key 按账号隔离 + A/B 双槽,保证每个账号至少一把活的**
70
-
71
- - **按账号一份文件**:`magicrew-key-<站点>__<org>.json`
72
- - 同一站点登两个账号时,第二个登录**不会**覆盖第一个的 key
73
- - 账号标识用 `org`(组织码,登录后不变),拿不到才退回 `accountId`
74
- - **双槽轮换**:续期签到「另一个槽」再切过去,旧的留在原槽当回退 —— 中途出错能切回来
75
- - **403 自动切槽**:当前这把被上游拒了就换另一槽重试一次(`body` 是字符串时才重发)
76
- - **实测**:`mgw_55ac` 失效后自动续出 `mgw_0671`(槽 a→b),回退槽保留,新 key 立即可用
77
- - **实测**:同一把 key 并发 6 个请求全部成功 —— `mgw_` 是账号级凭据,不是会话级,不存在"被占用"
78
-
79
- ## 0.3.2
80
-
81
- **key 收敛到唯一出口 + 三层凭据各有各的保活**
82
-
83
- 三层有效期,一层比一层长:
84
-
85
- | 凭据 | 活多久 | 说明 |
86
- |---|---|---|
87
- | 网页 JWT | 30 天 | 登录时签发 |
88
- | `mgw_` api_key | **2 小时** | 签一把顶一把,旧的当场吊销 |
89
- | refresh_token | **7 天** | 用一次轮换一次 |
90
-
91
- - **所有签发/续期只走 `src/keys.mjs`**,落到 `~/.cache/magpie/magicrew-key-<站点>.json`,**只保留最新一把**
92
- - 动机:magpie 的 `auth.refresh` 和 loader 若各有一次签发机会,两份本地凭据会分叉,
93
- 谁都不确定手里那把是不是服务端认的
94
- - ⚠ 早前版本这里写的是「签一把顶一把、旧的当场吊销」—— **那条没验证充分**。
95
- 实测两把不同时间签发的 `mgw_` key 可以同时有效;到底哪种操作会让旧 key 失效
96
- (PUT 续期?只在 refresh_token 轮换时?)还没分清,见 internals 第十四节的更正
97
- - **首次运行自动接管**:key-store 空时从 magpie 存的凭据接一次,之后全部自管
98
- - **到期前 10 分钟自动续**(`ensureKey` 在每次请求前检查)
99
- - **checkin 每天顺手续一次**(24 小时提前量):一天没请求时也不会让 refresh_token 闲置到 7 天失效
100
- - **续期串行化**:同一站点同时只跑一个 `renewOnce`,避免并发签多把互相吊销
101
- - **`writeKey` 校验 key 形状**:只接受 `mgw_` + 20 位以上,测试/占位值不许落盘
102
- (曾经因为假 key `mgw_test_key` 被接管落盘,把国际站那把真 key 顶掉)
103
- - 实测「过夜 key 过期」:把落盘那把的 `expiresAt` 改到 3 小时前 → 来一发请求 →
104
- HTTP 200 正常回复,落盘自动换成新 key(耗时 1885ms,含续期)
105
-
106
-
107
- ## 0.3.1
108
-
109
- - **流式请求补 `stream_options: { include_usage: true }`**(防御性)
110
- - magicrew 在 `stream: true` 下默认不返回 usage;补上之后 `[DONE]` 前会多一条
111
- `{"choices":[],"usage":{...}}` 的收尾 chunk,流更完整,下游判定"结束了"更有把握
112
- - 实测:补之前 `usage=无`,补之后 `finish_reason=stop · usage=有 · 收尾chunk=true · [DONE]=true`
113
-
114
- > ⚠ `Stream ended without finish_reason` 的真正根因**不是流式**,而是下面这条
115
- > 「签新 key 会吊销旧 key」—— 见 docs/internals.md 第十三节。这一项留着是因为
116
- > 它让流更完整,不是因为它是那个报错的解药。
117
-
118
- ## 0.3.0
119
-
120
- **结构重构:一个针脚一个模块。行为不变,形状变了。**
121
-
122
- magpie 是主板 —— 九个针脚,能力全在它那边;插件只填自己那一份。
123
- 原来 `makePlugin` 返回一个大对象把九样东西焊在一起,现在拆成:
124
-
125
- ```
126
- index.mjs 框:把选中的小插头拼起来,不实现任何东西
127
- src/context.mjs 站点上下文(siteCtx(SITES.cn) → 一个供应商)
128
- src/store.mjs 三份落盘:模型快照 / 邀请码 / 登录态
129
- src/clipboard.mjs 剪贴板降级通道
130
- src/browser-login.mjs 自己拉浏览器 + CDP 读凭据(可选能力,按需加载)
131
- src/hooks/config.mjs ① config(cfg)
132
- src/hooks/methods.mjs ② auth.methods[]
133
- src/hooks/refresh.mjs ③ auth.refresh
134
- src/hooks/loader.mjs ④ auth.loader
135
- src/hooks/usage.mjs ⑤ auth.usage
136
- src/hooks/checkin.mjs ⑥ auth.checkin
137
- src/hooks/models.mjs ⑦ provider.models
138
- src/hooks/headers.mjs ⑧ chat.headers
139
- ```
140
-
141
- - **一个模块坏了不影响别的**:`browser-login` 和 `tasks` 都是运行时按需加载,
142
- 它们挂了这个插件照常登录(走登录态那条路)、拉模型、报额度
143
- - **只做一半功能也能用**:不要签到就把那行从 `index.mjs` 的 return 里删掉,别处不用改
144
- - 每个 hook 文件头写清「magpie 什么时候调它、拿到什么、要返回什么、**不要**做什么」
145
-
146
- ## 0.2.1
147
-
148
- - **删掉「密码登录」** —— 两站都删,只剩浏览器登录与「登录态(JWT + 组织码)」两条。网页登录拿到的 token 一样能签 `mgw_` key,让用户再输一次手机号密码是多余的一步
149
- - **卡片显示手机号** —— `orgcode12345` 换成 `138****8000`。优先级 手机号 → 邮箱 → 昵称 → 真名(昵称是用户自己填的,新号可以不填也可以乱填)
150
- - **包名改为 `Magicrew`**,卡片标题就是它,不再显示 `magpie-magicrew-auth`。已装用户需按 HOOKS.md「改名的完整步骤」同步三处键名,或 `plugin rm` 后重装
151
- - **浏览器登录的 URL 也带 `?invitation=`** —— 原来只有密码登录的 body 带,用户在网页里注册就丢了邀请码
152
- - **修浏览器起不来**:`magicrew-login-profile` 里残留的 `SingletonLock` / `lockfile` 会让新进程把 URL 转交给一个已退出的实例后自己退出,`DevToolsActivePort` 永远不出现,于是白等 20 秒再降级。现在启动前清掉这些标记,超时收到 8 秒,`closeSession` 用 `taskkill /T /F` 连子进程一起杀
153
- - **更正一条错误结论**:网页版 354 字符 token(`device_id` 空)**完全可用**,`invitations/info` / `flows/models` / `gateway-tokens` / `quota` 四条全通。之前「不被网关接受一律 2155」是拿过期 token 测出来的
154
- - **`opened: true` 的语义**:那是「我自己已经开过页面了,你别再开」,传错会点了登录却不弹页面
155
-
156
- ## 0.2.0
157
-
158
- - **一个包两个供应商**:国内「超级麦吉」(`magicrew`)与国际「MagiCrew」(`magicrew-intl`)各占一行,不聚合
159
- - **支持国际站**:`magicrew.ai` / 网关 `magic-service.magicrew.ai`,49 个可选模型(国内 27 个)
160
- - **模型不再写死**:按账号实时拉 `/api/v1/flows/models`,账户能选什么就显示什么;登录过的快照只作未登录时的兜底
161
- - **两套图标**:国内用超级麦吉的卡通角色,国际用 MagiCrew 的黑底六边形,供应商列表里一眼分得开
162
- - **点一下,全自动**:插件自己拉起浏览器(独立 profile + 调试端口)停在官方登录页,用户在里面登录,插件通过 CDP 读走它自己的 localStorage 拿到 JWT + 组织码 —— 零手动操作;本机没 Chrome/Edge 时降级为「系统浏览器 + 剪贴板」
163
- - **登录方式只剩两条**:浏览器登录(默认)与手填 JWT + 组织码。手机号+密码那条已删除 —— 网页登录拿到的 token 一样能签 `mgw_` key
164
- - **卡片显示手机号,不再显示组织码**:`orgcode12345` 换成 `138****8000`。优先级手机号 → 邮箱 → 昵称 → 真名:昵称是用户自己填的(新号可以不填、也可以乱填),手机号是登录凭据本身
165
- - **包名改为 `Magicrew`**,卡片标题就是它(原来显示 `magpie-magicrew-auth`);已装的用户需要重装一次
166
- - 修正:`opened: true` 的语义是「我已自己开过页面」,传错会点了登录却不弹页面
167
- - 修正:网页版登录态(354 字符,`device_id` 空)**完全可用**,之前「不被网关接受」的结论是拿过期 token 测出来的
168
- - **邀请码自动滚**:第一次带作者的码,之后每次登录都记住上一个登录账号的码,拿到新的覆盖旧的,存在 `~/.cache/magpie/magicrew-invite-<站点>.json`
169
- - 国际站 `/v1/chat/completions` 强制 `organization-code`,按站点自动带
170
- - `max_tokens` 低于 512 时抬到 512(推理模型会把短额度吃光,`content` 返回空串)
171
-
172
- ## 0.1.0
173
-
174
- - 首个版本:国内站「超级麦吉」(`letsmagic.cn`)接入 magpie / OpenCode
175
- - 手机号+密码登录,网页 JWT 换 `mgw_` api_key,两小时自续
176
- - 账号当前可选的 27 个语言模型、积分余额、新手奖励自动领取
1
+ # 更新记录
2
+
3
+ ## 0.3.8
4
+
5
+ **签到补上「每日登录积分」—— 国际站一直领不到,根因是它压根不在 onboarding 任务里**
6
+
7
+ 症状:国际站账号点「立即签到」,永远回一句「新手福利 0 项已全部完成(当前积分 N)」,
8
+ 积分一动不动;可这个号在网页上手动登录,却能拿到一笔「Daily Login Bonus +50」。
9
+
10
+ 根因有两层:
11
+
12
+ 1. 国际站的每日奖励走 `POST /api/v1/login-gift`,和「新手福利」
13
+ (`GET /api/v1/onboarding/tasks` + `PUT .../completion`)是**两套完全独立的机制**。
14
+ 0.3.7 的 checkin 只实现了后者,`login-gift` 一次都没调过。
15
+ 2. 国际站的 onboarding 接口对未登记账号恒返回 `{"participant_type":null,"tasks":[]}`。
16
+ `runCheckin` 把「0 项待办」当成「已全部完成」,于是 outcome 报 `done`、消息报
17
+ 「0 项已全部完成」—— 看起来一切正常,实际上什么都没做。
18
+
19
+ 修法:
20
+
21
+ - `src/api.mjs` 新增 `claimLoginGift(token, org, host)` —— `POST /api/v1/login-gift`,
22
+ 无请求体,靠返回的 `granted` 区分「领到了」和「今天已经领过」
23
+ - `src/tasks.mjs` 的 `runCheckin` 改成两步:先领每日登录积分,再处理新手福利,
24
+ 任一步失败不影响另一步;消息里分别报两块的结果
25
+ - 空任务列表不再算成功:如实报「本站无新手福利任务」,不再谎报「已全部完成」
26
+
27
+ 实测(2026-10-27,三个账号):
28
+
29
+ | 站点 | 每日登录 | 当天重复调用 |
30
+ |---|---|---|
31
+ | 国际站 magicrew.ai | +50 | `granted:false`「今日已领取登录积分」 |
32
+ | 国内站 letsmagic.cn | **+200** | 同上 |
33
+
34
+ - 两站路径、方法、返回结构完全一致,只是金额不同
35
+ - **幂等**:当天已领返回 `granted:false`,HTTP 仍 200,不是错误 —— 可以每天无脑调
36
+ - 国内站同样在漏领:实测 1350 → 1550(+200),不是国际站独有的问题
37
+ - 奖励进的是当日 23:59:59 过期的额度包(`expire_type:1`),不累积
38
+ - 接口来源:网页登录过程抓包(CDP Network),`POST /api/v1/login-gift` 无请求体
39
+
40
+ ---
41
+
42
+ **邀请码滚动修复:`store.mjs` 少了一行 import,这个功能从上线起就没工作过**
43
+
44
+ 症状:每个新号注册,浏览器都被带到 `?invitation=<作者的码>`。用户自己的
45
+ `invite_count` 永远是 0 —— 也就是说每注册一个号,都在给作者送积分
46
+ (国际站 200/次,国内站 1,000/次)。
47
+
48
+ 根因:`src/store.mjs` 的 `rollInvite()` 里调 `fetchOwnInviteCode()`,但文件头
49
+ **只 import 了 `node:fs` / `node:os` / `node:path`**,从来没 import 过这个函数。
50
+ 于是每次调用都在那一行抛 `ReferenceError`,又被下面的空 `catch {}` 静默吞掉:
51
+
52
+ ```js
53
+ async function rollInvite(siteId, jwt, org, host) {
54
+ try {
55
+ const own = await fetchOwnInviteCode(jwt, org, host) // ← ReferenceError
56
+ return writeInvite(siteId, own.code)
57
+ } catch {
58
+ return "" // ← 错误在这里消失,调用方只看到「没滚到」
59
+ }
60
+ }
61
+ ```
62
+
63
+ 结果 `magicrew-invite-<site>.json` 从来没被写过,`inviteToUse()` 每次都掉进
64
+ 「没有记录 → 用作者码」这个分支。滚动机制形同虚设。
65
+
66
+ 修法:
67
+
68
+ - `src/store.mjs` 补上 `import { fetchOwnInviteCode } from "./api.mjs"`
69
+ (`api.mjs` 不反向依赖 store,无循环依赖)
70
+ - `rollInvite` 加 6 秒超时(`Promise.race`),失败不再完全静默 ——
71
+ 设 `MAGICREW_DEBUG=1` 时打一行错误,避免同类问题再次「安静地死掉」
72
+ - `src/hooks/methods.mjs` 三处调用从 `rollInvite(...).catch(() => {})`
73
+ 改成 `await rollInvite(...)`:发射后不管的话,host 进程在 callback 之后被回收,
74
+ 滚动一样会丢
75
+ - `src/hooks/checkin.mjs` 每天签到时顺带滚一次 —— 登录那一路万一被打断,
76
+ 第二天还能补上
77
+
78
+ 实测(2026-10-27,隔离 HOME 里验的,不污染真实配置):
79
+
80
+ | 站点 | 滚动前 | 滚动后 |
81
+ |---|---|---|
82
+ | 国际站 | `IV75F0ZKVKXG`(作者) | `IVF4A4H12KM8`(账号自己的) |
83
+ | 国内站 | `IVC8BAWYXRY1`(作者) | `IV1475HL797R`(账号自己的) |
84
+
85
+ > 附带更正 `src/api.mjs` 里一条过时的注释:原来写「网页版 JWT(`device_id` 空)
86
+ > 调 `/api/v1/invitations/info` 回 2155 用户不存在」。实测四个账号全是 354 字符的
87
+ > 网页 JWT、`device_id` 全为空,接口全部返回 200 并正常给出邀请码 ——
88
+ > 上游已经放宽了这个限制,滚动现在是通的,只是以前的 import 缺失挡住了它。
89
+
90
+ ---
91
+
92
+ **文档更正:checkin 从来没有「顺带续 key」,那条保活链路只存在于文档里**
93
+
94
+ `docs/internals.md` 第十四节和本文件 0.3.2 那条都写着「checkin 每天用 24 小时
95
+ 提前量强制续一次 key,防止 refresh_token 闲置 7 天失效」。逐版本核对
96
+ 0.3.0 / 0.3.2 / 0.3.7:**checkin 路径(`src/hooks/checkin.mjs` → `src/tasks.mjs`)
97
+ 从未 import 过 `ensureKey`**,全仓 `leadMs` 只出现在 `src/keys.mjs` 自身。
98
+
99
+ 它本来也不需要存在 —— 原来那段论证有个漏洞:
100
+
101
+ - 「一整天没有请求,key 会静静过期」不成立:续期不靠请求触发,是 magpie 按
102
+ `refreshLead`(10 分钟)主动调 `auth.refresh`。只要 magpie 在跑,有没有请求都会续
103
+ - 「挂在 checkin 上补洞」也补不了:真会让 refresh_token 闲置到 7 天失效的场景,
104
+ 是 magpie 整整 7 天没开 —— 那时 checkin 同样不跑
105
+
106
+ **所以没有去补这个功能,而是把文档改回实况**:`src/hooks/refresh.mjs` 绕过了
107
+ `keys.mjs` 这个「唯一出口」(它直接调 `api.refreshGatewayKey(auth.refresh)`),
108
+ 两处各持一份 refresh_token 会分叉。补上 `ensureKey` 会把 key-store 写起来、
109
+ 进而抢轮换,结果是用户莫名被要求重新登录。真要补,得先把 `auth.refresh`
110
+ 收敛进 `ensureKey` —— 那是另一件事,不在本 PR 范围。
111
+
112
+ 顺带修正 `internals` 里过时的落盘路径:单槽 `magicrew-key-<siteId>.json`
113
+ → 0.3.3 起的 `<siteId>__<account>` A/B 双槽。并在 `src/keys.mjs` 顶部注释里
114
+ 标明「refresh 不经过本模块」这处未收敛的口子,避免后人照旧注释误判。
115
+
116
+ ## 0.3.7
117
+
118
+ **登录先看本机浏览器:你在自己浏览器里登过,插件直接认,不再开窗让你重登**
119
+
120
+ - 症状(反复被报):在自己浏览器里登录成功,插件这边却「没多出账号」;点「添加账号」还要在另一个窗口重登一遍
121
+ - 做法:点「浏览器登录」时先读本机 Edge / Chrome / Brave / Chromium 的 localStorage
122
+ (`<User Data>/<profile>/Local Storage/leveldb`,明文),找到**这个站点**的 jwt + 组织码就直接接上 ——
123
+ 不开窗口、零输入;读不到、或这个账号已经加过,才走原来的自拉窗口
124
+ - 关键实现(`src/local-browser.mjs`):
125
+ - **按 origin 截断**窗口,避免两站记录相邻时串号(把国际站的 org 配到国内站的 jwt 上)
126
+ - **每把 jwt 配它自己后面的 org**(同一站点存过多把凭据时,「窗口里第一个 org」会配错)
127
+ - 过期 / 空组织码丢弃;多把有效凭据取 `iat` 最大的
128
+ - 防重复加号:`magicrew-orgs-<站点>.json` 记着加过的 org,命中就不再自动接
129
+ (否则用户想加新账号时只会把它替换成同一个)
130
+ - 实测:本机 Edge 27 MB / 29 ms;国内站快速通道 33 ms;国际站注入测试数据 1 ms
131
+ - 判据:`node tests/local-browser.mjs`(13 项)
132
+
133
+ ## 0.3.6
134
+
135
+ **修:添加第二个账号时网页闪一下就关,magpie 里没多出账号**
136
+
137
+ - 症状:点「添加账号」→ 浏览器正常弹出 → 一两秒内自己关掉;用户确实也登录成功了,但账号列表里没有新账号
138
+ - 根因:登录浏览器**复用**同一个 profile(`~/.cache/magpie/magicrew-login-profile`)。第二个窗口一打开,
139
+ localStorage 里就是上一个账号的 `MAGIC:user:authorization` / `organizationCode`,`waitForCreds`
140
+ 第一轮(1.5 秒)读到它 → 判定登录完成 → 关窗;magpie 拿到的 `accountId` / `uid` 与已有账号完全相同,
141
+ 按契约「同 accountId + 同 uid → 替换旧账号」→ 不新增
142
+ - 修法:每次登录一个**一次性** profile(`magicrew-login-<pid>-<ts>-<rand>`),`closeSession()` 关窗后删;
143
+ `launchLoginBrowser()` 开头先 `sweepProfiles()` 清掉历史复用目录与超过 2 小时的一次性目录
144
+ (只认本插件造的名字,且只动目录 —— 同前缀的 `.log` 不碰)
145
+ - 第二道防线:`waitForCreds(..., { baseline })` —— 窗口打开时就存在的凭据不算登录成功
146
+ - 代价:每次都要在窗口里重新登录一次(独立 profile 本来也没有日常 cookie),换来「读到的凭据一定是本次的」
147
+ - 判据:`node tools/verify-profile-scope.mjs`(窗口打开 4 秒后 `READ_CREDS` 必须是空串、关窗后目录必须不存在)
148
+ + `node tests/login-profile.mjs`
149
+
150
+ ## 0.3.5
151
+
152
+ **卡片上多一个「承载」徽章:`magpie.carries`**
153
+
154
+ - 症状:Qoder 插件的卡片有「承载 Qoder 国际版 (qoder.com)、Qoder CN」,Magicrew 没有
155
+ - 成因:那个徽章的数据源是 `provider.MovedOnto`,只认 **magpie 内置订阅被迁移到插件上**。
156
+ Magicrew 从来不是内置订阅(`internal/provider/migrate.go` 的 `movers` 表),所以永远是空
157
+ - 修法:magpie 加 `magpie.carries` —— 插件在 `package.json` 里自己声明它承载哪些供应商,
158
+ 后端读进 `pluginEntryJSON.Carries`(JSON 字段 `carries`),前端照样画同一个 `pm-chip moved`
159
+ - 本插件声明:`["超级麦吉 (letsmagic.cn)", "MagiCrew (magicrew.ai)"]`
160
+ - 兼容性:**旧 magpie 不认这个字段,直接忽略**;只有带 `carries` 能力的 magpie 才画这个徽章
161
+
162
+ **上下文窗口的三档优先级,兜底从 256K 提到 1M**
163
+
164
+ ```
165
+ ① 用户在 magpie GUI 里手选的「上下文窗口」 ← 最优先,magpie 直接用
166
+ ② 模型库(~/.cache/magpie/models.json)查到的真实值
167
+ ③ 兜底 1M
168
+ ```
169
+
170
+ - 兜底从 `256_000` 改成 `1_048_576`:给 1M 而实际只有 128K,上游会在超限时报错、用户看得见;
171
+ 给 256K 而实际有 1M,agent 会主动拒绝一段本可以处理的对话 —— 那是静默的能力损失,
172
+ 比报错更难查(`pi-ai detected context overflow` 就是这么来的)
173
+ - 对照官方社区插件:Qoder 纯硬编码(`context: 1_000_000`);WorkBuddy 硬编码一张表,
174
+ 再从自家 `/v3/config` 的 `maxInputTokens` 覆盖。magicrew 不提供后者,所以查模型库更省事
175
+
176
+ **发布到 npm:包名 `magicrew-auth`**
177
+
178
+ - `magpie plugin add magicrew-auth` 从此可用;npm 源的更新按 `version` 检测,有新版本会提示
179
+ - git 源标题仍是 `Magicrew`:npm 拒绝含大写字母的新包名,所以发布走 `npm run publish:npm`
180
+ —— `publish-npm.mjs` 临时把 `name` 换成 `magicrew-auth`、跑完无论成败都还原(已实测逐字节一致)
181
+ - **README 模型段改版** —— 按 [magpie-plugin-scaffold 0.1.7](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/README-TEMPLATE.md):
182
+ 开头写发行商(GLM / DeepSeek / Qwen / Kimi…),不写「支持 N 个模型」;表格只列各家最新 / 热门
183
+
184
+ ## 0.3.4
185
+
186
+ **上下文窗口向 magpie 的模型库查,不再一律填 256K**
187
+
188
+ - 症状:`pi-ai detected context overflow for model "magicrew/deepseek-v4.1-flash"` —— 会话明明还能继续
189
+ - 成因:magicrew 的两个模型接口(`/api/v1/flows/models`、`/v1/models`)**都不返回上下文窗口**,
190
+ 早前一律填 `256_000` 当保守值。而 `deepseek-v4.1-flash` 实际是 **1,048,576**,
191
+ 等于把可用容量砍掉四分之三 —— agent 拿 256K 判断"放不下"就拒绝了
192
+ - 修法:`src/model-context.mjs` 向 magpie 自己的 `~/.cache/magpie/models.json` 查
193
+ (按 `limit.context`,支持全名 / 尾段 / 多厂商前缀取最大),查不到才退回 256K
194
+ - 实测:deepseek-v4.1-flash 1,048,576 · qwen3.5-flash 1,000,000 · glm-5.3-flash 262,144 ·
195
+ kimi-k3.1-pro 1,048,576 · 查不到的 256,000
196
+
197
+ ## 0.3.3
198
+
199
+ **key 按账号隔离 + A/B 双槽,保证每个账号至少一把活的**
200
+
201
+ - **按账号一份文件**:`magicrew-key-<站点>__<org>.json`
202
+ - 同一站点登两个账号时,第二个登录**不会**覆盖第一个的 key
203
+ - 账号标识用 `org`(组织码,登录后不变),拿不到才退回 `accountId`
204
+ - **双槽轮换**:续期签到「另一个槽」再切过去,旧的留在原槽当回退 —— 中途出错能切回来
205
+ - **403 自动切槽**:当前这把被上游拒了就换另一槽重试一次(`body` 是字符串时才重发)
206
+ - **实测**:`mgw_55ac` 失效后自动续出 `mgw_0671`(槽 a→b),回退槽保留,新 key 立即可用
207
+ - **实测**:同一把 key 并发 6 个请求全部成功 —— `mgw_` 是账号级凭据,不是会话级,不存在"被占用"
208
+
209
+ ## 0.3.2
210
+
211
+ **key 收敛到唯一出口 + 三层凭据各有各的保活**
212
+
213
+ 三层有效期,一层比一层长:
214
+
215
+ | 凭据 | 活多久 | 说明 |
216
+ |---|---|---|
217
+ | 网页 JWT | 30 天 | 登录时签发 |
218
+ | `mgw_` api_key | **2 小时** | 签一把顶一把,旧的当场吊销 |
219
+ | refresh_token | **7 天** | 用一次轮换一次 |
220
+
221
+ - **所有签发/续期只走 `src/keys.mjs`**,落到 `~/.cache/magpie/magicrew-key-<站点>.json`,**只保留最新一把**
222
+ - 动机:magpie 的 `auth.refresh` 和 loader 若各有一次签发机会,两份本地凭据会分叉,
223
+ 谁都不确定手里那把是不是服务端认的
224
+ - ⚠ 早前版本这里写的是「签一把顶一把、旧的当场吊销」—— **那条没验证充分**。
225
+ 实测两把不同时间签发的 `mgw_` key 可以同时有效;到底哪种操作会让旧 key 失效
226
+ (PUT 续期?只在 refresh_token 轮换时?)还没分清,见 internals 第十四节的更正
227
+ - **首次运行自动接管**:key-store 空时从 magpie 存的凭据接一次,之后全部自管
228
+ - **到期前 10 分钟自动续**(`ensureKey` 在每次请求前检查)
229
+ - **checkin 每天顺手续一次**(24 小时提前量):一天没请求时也不会让 refresh_token 闲置到 7 天失效
230
+ - **续期串行化**:同一站点同时只跑一个 `renewOnce`,避免并发签多把互相吊销
231
+ - **`writeKey` 校验 key 形状**:只接受 `mgw_` + 20 位以上,测试/占位值不许落盘
232
+ (曾经因为假 key `mgw_test_key` 被接管落盘,把国际站那把真 key 顶掉)
233
+ - 实测「过夜 key 过期」:把落盘那把的 `expiresAt` 改到 3 小时前 → 来一发请求 →
234
+ HTTP 200 正常回复,落盘自动换成新 key(耗时 1885ms,含续期)
235
+
236
+
237
+ ## 0.3.1
238
+
239
+ - **流式请求补 `stream_options: { include_usage: true }`**(防御性)
240
+ - magicrew 在 `stream: true` 下默认不返回 usage;补上之后 `[DONE]` 前会多一条
241
+ `{"choices":[],"usage":{...}}` 的收尾 chunk,流更完整,下游判定"结束了"更有把握
242
+ - 实测:补之前 `usage=无`,补之后 `finish_reason=stop · usage=有 · 收尾chunk=true · [DONE]=true`
243
+
244
+ > ⚠ `Stream ended without finish_reason` 的真正根因**不是流式**,而是下面这条
245
+ > 「签新 key 会吊销旧 key」—— 见 docs/internals.md 第十三节。这一项留着是因为
246
+ > 它让流更完整,不是因为它是那个报错的解药。
247
+
248
+ ## 0.3.0
249
+
250
+ **结构重构:一个针脚一个模块。行为不变,形状变了。**
251
+
252
+ magpie 是主板 —— 九个针脚,能力全在它那边;插件只填自己那一份。
253
+ 原来 `makePlugin` 返回一个大对象把九样东西焊在一起,现在拆成:
254
+
255
+ ```
256
+ index.mjs 框:把选中的小插头拼起来,不实现任何东西
257
+ src/context.mjs 站点上下文(siteCtx(SITES.cn) → 一个供应商)
258
+ src/store.mjs 三份落盘:模型快照 / 邀请码 / 登录态
259
+ src/clipboard.mjs 剪贴板降级通道
260
+ src/browser-login.mjs 自己拉浏览器 + CDP 读凭据(可选能力,按需加载)
261
+ src/hooks/config.mjs ① config(cfg)
262
+ src/hooks/methods.mjs ② auth.methods[]
263
+ src/hooks/refresh.mjs ③ auth.refresh
264
+ src/hooks/loader.mjs ④ auth.loader
265
+ src/hooks/usage.mjs ⑤ auth.usage
266
+ src/hooks/checkin.mjs ⑥ auth.checkin
267
+ src/hooks/models.mjs ⑦ provider.models
268
+ src/hooks/headers.mjs ⑧ chat.headers
269
+ ```
270
+
271
+ - **一个模块坏了不影响别的**:`browser-login` 和 `tasks` 都是运行时按需加载,
272
+ 它们挂了这个插件照常登录(走登录态那条路)、拉模型、报额度
273
+ - **只做一半功能也能用**:不要签到就把那行从 `index.mjs` 的 return 里删掉,别处不用改
274
+ - 每个 hook 文件头写清「magpie 什么时候调它、拿到什么、要返回什么、**不要**做什么」
275
+
276
+ ## 0.2.1
277
+
278
+ - **删掉「密码登录」** —— 两站都删,只剩浏览器登录与「登录态(JWT + 组织码)」两条。网页登录拿到的 token 一样能签 `mgw_` key,让用户再输一次手机号密码是多余的一步
279
+ - **卡片显示手机号** —— `orgcode12345` 换成 `138****8000`。优先级 手机号 → 邮箱 → 昵称 → 真名(昵称是用户自己填的,新号可以不填也可以乱填)
280
+ - **包名改为 `Magicrew`**,卡片标题就是它,不再显示 `magpie-magicrew-auth`。已装用户需按 HOOKS.md「改名的完整步骤」同步三处键名,或 `plugin rm` 后重装
281
+ - **浏览器登录的 URL 也带 `?invitation=`** —— 原来只有密码登录的 body 带,用户在网页里注册就丢了邀请码
282
+ - **修浏览器起不来**:`magicrew-login-profile` 里残留的 `SingletonLock` / `lockfile` 会让新进程把 URL 转交给一个已退出的实例后自己退出,`DevToolsActivePort` 永远不出现,于是白等 20 秒再降级。现在启动前清掉这些标记,超时收到 8 秒,`closeSession` 用 `taskkill /T /F` 连子进程一起杀
283
+ - **更正一条错误结论**:网页版 354 字符 token(`device_id` 空)**完全可用**,`invitations/info` / `flows/models` / `gateway-tokens` / `quota` 四条全通。之前「不被网关接受一律 2155」是拿过期 token 测出来的
284
+ - **`opened: true` 的语义**:那是「我自己已经开过页面了,你别再开」,传错会点了登录却不弹页面
285
+
286
+ ## 0.2.0
287
+
288
+ - **一个包两个供应商**:国内「超级麦吉」(`magicrew`)与国际「MagiCrew」(`magicrew-intl`)各占一行,不聚合
289
+ - **支持国际站**:`magicrew.ai` / 网关 `magic-service.magicrew.ai`,49 个可选模型(国内 27 个)
290
+ - **模型不再写死**:按账号实时拉 `/api/v1/flows/models`,账户能选什么就显示什么;登录过的快照只作未登录时的兜底
291
+ - **两套图标**:国内用超级麦吉的卡通角色,国际用 MagiCrew 的黑底六边形,供应商列表里一眼分得开
292
+ - **点一下,全自动**:插件自己拉起浏览器(独立 profile + 调试端口)停在官方登录页,用户在里面登录,插件通过 CDP 读走它自己的 localStorage 拿到 JWT + 组织码 —— 零手动操作;本机没 Chrome/Edge 时降级为「系统浏览器 + 剪贴板」
293
+ - **登录方式只剩两条**:浏览器登录(默认)与手填 JWT + 组织码。手机号+密码那条已删除 —— 网页登录拿到的 token 一样能签 `mgw_` key
294
+ - **卡片显示手机号,不再显示组织码**:`orgcode12345` 换成 `138****8000`。优先级手机号 → 邮箱 → 昵称 → 真名:昵称是用户自己填的(新号可以不填、也可以乱填),手机号是登录凭据本身
295
+ - **包名改为 `Magicrew`**,卡片标题就是它(原来显示 `magpie-magicrew-auth`);已装的用户需要重装一次
296
+ - 修正:`opened: true` 的语义是「我已自己开过页面」,传错会点了登录却不弹页面
297
+ - 修正:网页版登录态(354 字符,`device_id` 空)**完全可用**,之前「不被网关接受」的结论是拿过期 token 测出来的
298
+ - **邀请码自动滚**:第一次带作者的码,之后每次登录都记住上一个登录账号的码,拿到新的覆盖旧的,存在 `~/.cache/magpie/magicrew-invite-<站点>.json`
299
+ - 国际站 `/v1/chat/completions` 强制 `organization-code`,按站点自动带
300
+ - `max_tokens` 低于 512 时抬到 512(推理模型会把短额度吃光,`content` 返回空串)
301
+
302
+ ## 0.1.0
303
+
304
+ - 首个版本:国内站「超级麦吉」(`letsmagic.cn`)接入 magpie / OpenCode
305
+ - 手机号+密码登录,网页 JWT 换 `mgw_` api_key,两小时自续
306
+ - 账号当前可选的 27 个语言模型、积分余额、新手奖励自动领取
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
  ## 功能
20
20
 
21
21
  - **两个供应商分得开** —— 国内、国际各一行,图标、名字、模型、积分、登录态全部独立
22
+ - **本机浏览器登过就直接认** —— 你平时那个 Edge / Chrome / Brave 里登过 magicrew,点登录时插件读它的 localStorage 接上:不开窗口、不用重登
22
23
  - **模型跟着账号走** —— 每次登录后按 `/api/v1/flows/models` 实时拉,账户当前能选什么就展示什么,生图和 embedding 不会混进来
23
24
  - **积分算得准** —— 余额读的是 `/api/v1/quota/points/total`;空号就显示空,不会被优先派活
24
25
  - **登录一次管 30 天** —— 网页 JWT 顶 30 天,`mgw_` api_key 每 2 小时自续,refresh_token 每次续期时自滚
@@ -105,7 +106,12 @@
105
106
 
106
107
  在 magpie 的 **插件 → 发现** 搜 `magicrew`,点**安装**(npm 上的包名是 `magicrew-auth`)。
107
108
 
108
- 装完选站点,然后**点第一个「浏览器登录」** —— 没有任何输入框,插件自己把浏览器拉起来:
109
+ 装完选站点,然后**点第一个「浏览器登录」**。它先看你平时用的那个浏览器:
110
+
111
+ **① 那边已经登录过 magicrew** → 插件直接读它的 localStorage 接上,**不开窗口、不用再登一次**。
112
+ 凭据就明文躺在 `Local Storage/leveldb` 里(Edge / Chrome / Brave / Chromium 都行)。
113
+
114
+ **② 没登录过,或者这个账号已经加过** → 插件自己拉起一个窗口:
109
115
 
110
116
  1. 浏览器窗口弹出,停在官方登录页
111
117
  2. 用你平时的方式登进去(手机号 / 密码 / Google / 邮箱验证码 / 飞书 / 钉钉都行)
@@ -119,7 +125,7 @@ localStorage 读走,拿到 JWT 和组织码,签完密钥就关窗。
119
125
  添加第二个账号时窗口一打开,页面里已经躺着上一个账号的凭据 —— 插件会以为登录完成、
120
126
  立刻关窗,而 magpie 拿到的还是旧账号(同账号 → 替换,不新增)。
121
127
 
122
- > **为什么必须自己拉浏览器?** 因为 magicrew 没有 OAuth 回调通道 —— 它的登录类型只有 13 种、全是「拿凭据换 token」,没有设备码、没有插件授权、没有一次性 ticket;也没有 WorkBuddy 那种 `/v2/plugin/auth/state` + `/v2/plugin/auth/token` 协议。网页登录成功后 token 只在 `www.magicrew.ai` 的 localStorage 里,而 magpie 打开的是你系统默认那个浏览器,插件够不到它的存储。自己起一个,就够得到了。
128
+ > **为什么两条路都要?** magicrew 没有 OAuth 回调通道 —— 登录类型只有 13 种、全是「拿凭据换 token」,没有设备码、没有插件授权、没有一次性 ticket。凭据只可能落在两个地方:你自己浏览器的 localStorage(插件读文件就能拿到,所以优先),或者插件自己起的那个窗口(读不到时才需要)。
123
129
  >
124
130
  > 本机没装 Chrome / Edge 时自动降级:改用系统浏览器打开,登录完把凭证送进剪贴板,插件从剪贴板读。
125
131
 
@@ -166,6 +172,8 @@ magpie plugin list
166
172
 
167
173
  **两个站要装两次吗?** 不用。一个包导出两个插件,装一次两个供应商都在,各自登录。
168
174
 
175
+ **我在自己浏览器里登了 magicrew,插件能自动认出来吗?** 能。点「浏览器登录」时插件先去本机 Edge / Chrome / Brave / Chromium 的 localStorage 里找**这个站点**的登录态,找到就直接接上,不弹窗口。读不到、或者这个账号已经加过,才会开窗口让你登新的。
176
+
169
177
  **添加第二个账号时,网页闪一下就关了、magpie 里也没多出账号?** 0.3.5 及更早会这样:登录浏览器复用同一个 profile,第二个窗口一打开就读到上一个账号的凭据,于是立刻「登录成功」并关窗 —— 加到的是已有账号。0.3.6 起每次登录用一个一次性 profile(关窗即删),不会再串号。
170
178
 
171
179
  **npm 上叫 `magicrew-auth`,为什么卡片显示 `Magicrew`?** 卡片标题取包内 `package.json` 的 `name`(`Magicrew`),而 npm 拒绝含大写字母的新包名。发布时用 `publish-npm.mjs` 临时换成 `magicrew-auth`、发完还原 —— 所以 npm 页面是小写包名,git 源装出来是 `Magicrew`。功能完全一样。