magicrew-auth 0.3.32 → 0.3.34

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/AGENTS.md CHANGED
@@ -1,157 +1,159 @@
1
- # AGENTS.md —— 给 AI 执行者的入口
2
-
3
- > 人类请看 [README](./README.md)。这份只回答两件事:**你现在该读哪一份**、
4
- > **忘了哪一条会静默出错**。
5
- >
6
- > 结构照搬 [magpie-plugin-scaffold](../magpie-plugin-scaffold/AGENTS.md) 的入口式写法;
7
- > 下面第二节是本仓库自己的坑,不是通用套话。
8
-
9
- ---
10
-
11
- ## 零、magpie 升级时:只看稳定版
12
-
13
- **判断「要不要跟着 magpie 改这个插件」看稳定渠道,不看每版本渠道。**
14
- magpie 设置里有「更新渠道:每个版本 / 稳定版」,用户默认跑后者,插件要跟的也是后者。
15
-
16
- ```bash
17
- curl -s "https://usemagpie.ai/api/latest?channel=stable" | grep -o '"version":"[^"]*"'
18
- ```
19
-
20
- 实测(2026-10-28):两档差 **400 多个版本**(每版本 0.1.1763 / 稳定版 0.1.1327),
21
- 但把两边内嵌契约都提取出来逐字段比对后 —— **插件相关的字段一条不差**。
22
- 多出来的段是 GUI 历史,不是插件契约。⇒ 追每版本几乎全是白跑。
23
-
24
- **稳定版版本号变了**,才需要重提取契约(脚手架的 `extract-contract.py`)→
25
- 跑 `contract-diff.mjs` → 看差集里有没有「不写会关掉某个能力」的字段。
26
- 完整判据见脚手架的
27
- [HOOKS.md](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/HOOKS.md) 顶部
28
- 与 [UPDATING.md](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/UPDATING.md) 第七节。
29
-
30
- ---
31
-
32
- ## 一、先定位你在哪一步
33
-
34
- | 你在做什么 | 必读(按顺序) | 做完的判据 |
35
- |---|---|---|
36
- | **改协议 / 接口调用** | [docs/internals.md](./docs/internals.md) 的「三、接口清单」「十二、流式」「十六、上下文窗口」 | 改动在两站(`magicrew` 与 `magicrew-intl`)都验过 |
37
- | **改登录链路** | internals 的「二、三层凭证」「十一、浏览器登录怎么做到全自动」 | 三条登录路径(本机浏览器 / 浏览器登录 / 手填 JWT)都还能登 |
38
- | **改 key / 续期** | internals 的「十三、签新 key 会吊销旧 key」「十四、三层凭据的有效期与保活」 | 没引入第二份 refresh_token(会和 `src/keys.mjs` 分叉) |
39
- | **改模型目录** | internals 的「十六、上下文窗口必须查库」+ HOOKS 十一之二(`free`) | 上下文来自查库;`free` 来自厂商声明,不从 `rate` 反推 |
40
- | **改签到 / 首任务** | internals 的 checkin 小节(一次性闸门、五种状态) | 不漏发、不重发、失败方向是「宁可晚一轮」 |
41
- | **代码写完,准备交付** | [CHECKLIST.md](./CHECKLIST.md) 的 **A 表** | A 表全勾 + `node scripts/check.mjs` 退出 0 |
42
- | **要发版本** | **[UPDATING.md](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/UPDATING.md)**(脚手架的,七步) | CHECKLIST B 表全勾 + 四条验证 |
43
- | **用户说「更新了没变化」** | 本文件坑 12(git 源只提示、要手动点更新) | `magpie plugin list` 里的 commit 真的变了 |
44
-
45
- **最容易漏的是「要发版本」这一行。** `git push` 只是七步里的第 4 步 ——
46
- 本仓库真踩过:0.3.14 只 push,结果 GitHub 右侧「暂无发布版本」、npm 停在 0.3.8。
47
-
48
- ---
49
-
50
- ## 二、本仓库的坑
51
-
52
- | # | 症状(出问题时你看到什么) | 判据 | 出处 |
53
- |---|---|---|---|
54
- | 1 | 改好了国内站,国际站用户报同样的错 | **两站是同一个 Go 服务、逐字符同构**,改一处就要两站都验;`SITES.cn` / `SITES.intl` 只是 host 不同 | internals 一 |
55
- | 2 | 国际站 `/v1/chat/completions` 回 `HTTP 500` + 22 字节裸文本(不是 JSON) | 国际站网关**强制**要 `organization-code` 头,缺了就是这个 500 | internals 一 / 五 |
56
- | 3 | 换了 key 之后老 key 立刻失效、用户被要求重登 | **「签新 key 会吊销旧 key」已被实测推翻(2026-10-28)** —— 见下方「最容易误信的旧说法」。仍然只留一把、只走 `src/keys.mjs` 的唯一出口,理由是**本地别分叉**,不是互相吊销 | internals 十四 |
57
- | 4 | 刷新 token 之后两处 refresh_token 不一致、用户莫名掉登录 | 只允许**一个**出口持有 refresh_token;`src/hooks/refresh.mjs` 必须走 `keys.mjs` 的 `renewViaStore()`(0.3.29 起已收敛,与 `ensureKey` 共用 `renewOnce` 锁和同一个 key-store)。若改回自调 `refreshGatewayKey` 就会分叉 | internals 二 / 十四 |
58
- | 5 | agent 说「上下文放不下」,而实际模型能吃更长 | 上下文窗口**必须查 magpie 的模型库**,不能填保守值(早前一刀切 256K,实际 1M) | internals 十六 |
59
- | 6 | 流式回复拿到了,但 `usage` 永远是空、额度不扣 | 流式请求必须补 `stream_options.include_usage`,否则上游不回 usage | internals 十二 |
60
- | 7 | 模型被标成免费、Free first 一直派到它 | `free` 只认厂商声明(`input_points === 0`),**不能从 `rate === 0` 反推**;价格缺失 = 不列,不猜 | internals 十六 · HOOKS 十一之二 |
61
- | 8 | 新号邀请奖励不到账 | 国际站判据是**有没有话题**,不是有没有项目 —— 项目打开工作台就会自动建 | internals checkin 小节 |
62
- | 9 | 一个号天天烧一次模型积分 | 首任务是**每账号一辈子一次**(`isAutoChatSettled`),别改回按日期;失败才重试,最多 3 次 | internals checkin 小节 |
63
- | 10 | 话题接口抖一下,就白花一次积分去补消息 | 查不到判据时**这次不发**、不落标记、不计数 —— 消息发出去花钱且不可撤 | `src/tasks.mjs` 的 `autoFirstChat` |
64
- | 11 | Windows 上点登录,浏览器窗口拉不起来 | 浏览器可执行文件名**随版本变**(`msedge.exe` / Chrome 各版本目录不同),要逐个试候选,别写死一个名字 | internals 十一 |
65
- | 12 | 用户点「检查更新」显示有新提交,点了更新却没变化 | 本仓库是 **git 源**:magpie 比默认分支 HEAD 的 commit,只提示不自动更新,要用户点那一行的「更新」;更新后 `magpie plugin list` 的 commit 应变化 | 脚手架 UPDATING 一 |
66
- | 13 | 想让卡片标题 / 「源码」按钮对,去改了 magpie 的缓存 | 插件对 magpie 目录**只有读的份**;唯一载体是 `registry-entry.json`(提交给 `registry.json`) | internals 二 · README |
67
- | 14 | **卡片上额度正常,路由页里这个号却是「剩余额度未知」** | 窗口**不能标 `aside`**:magpie 的 `allowanceOf()` 把 `Aside`/`Text` 的窗口整条跳过,一条不剩 ⇒ 该号 `Known=false`(官网文档 issue #3650 明写)。判据:`curl -s http://127.0.0.1:3425/v1/magpie/trace`,看这个 provider 的 `known` 是 `true` 还是**字段缺失** | HOOKS 三 · `src/hooks/usage.mjs` 头 |
68
- | 15 | 接 ZCode / Claude Code 系客户端,带工具的请求回 `400 {"error":{"message":"模型响应失败","code":4017}}` | **上游的 `additionalProperties` 只认布尔值,任何对象形态都拒** —— 不只是 `{}`,`{type:"string"}` 这种有约束的 schema 一样 400(两个模型逐行一致)。判据:同一请求只把该字段换成 `true` → 200。改写已在 `rewriteRequestBody` 里做(非布尔一律换 true;**这是降级放宽、会丢约束**,取舍见 internals 十七) | internals 十七 · `tests/body-rewrite.mjs` |
69
- | 16 | 网关路径(agent 从 magpie 的 `/v1` 进来)报 `502 did not stream: token无效`,而 provider 显示已登录 | auth 里没带 org → `accountOf()` 落 `"default"` → 读不到真实账号的 key 槽 → 只能用 `auth.access`,它过了 2 小时就 403。`accountFor()` 已在**恰好一个真实账号**时兜到它;**多账号不猜**(猜错就是拿别人的 key 扣别人的积分) | internals 十四 · 十五 · `tests/account-fallback.mjs` |
70
- | 17 | 单轮回 `400 ... tools.N.function.parameters/properties/functions/items: invalid JSON Schema` | 上游不接受**空 schema 对象**当 `items`:`items:{}` 必 400,`items:{description:"x"}` 却 200。判据:同一请求只删 `items:{}` → 200。改写已做(删 `items:{}` / `true`,**严格等价**;`false` 不动) | internals 十七 · `tests/body-rewrite.mjs` |
71
- | 18 | 某个账号积分耗尽后整轮回 400「可用积分已不足」,其它有余额的账号一次没被问 | **上游用 HTTP 400 报额度耗尽,magpie 的 `retryable()` 400 分支只认 `quotaWords`(不含「积分」「充值」)** ⇒ `hw.failed()=false`,既不走 `!last && hw.failed()` 的换号分支,也不 rest 该账号。插件 `rewriteCreditRefusal()` 把这种 400/422 改写成 402 Payment Required(`tests/credit-refusal.mjs` 锁死);magpie 侧修复方案见 https://github.com/yetone/magpie/issues/4095 (`retryable()` 的 400 分支补 `creditWords`) | CHANGELOG 0.3.26 |
72
- | 19 | 单轮回 `400 ... tools.N.function.parameters/properties/functions/items: invalid JSON Schema`,但请求里**没有**空 `items` | 上游对 **`items` 子树内部**比根节点严:`anyOf`/`oneOf`/`allOf`/`not`/`const`/`if`+`then`+`else`/`{}` 全 400,**tuple 形式的 `items`(数组)国际站拒**,而同样形态放在顶层 parameters 里 200。命中来源是 **MCP 工具 schema**(REA MCP 7 个工具的 `functions` 数组)。判据:同一请求只把 `items` 子树里的 `anyOf` 摊平 → 200;改完**必须复查没留下空 `{}`**(删 `not`/`const` 会删空)。⚠ **上游 2026-10-29 00:25 起放宽了这层校验(raw 也 200),同一夜里还出现过波动** —— 判要不要改写请看 `_probe/items-subtree-stability.mjs` 的多轮统计,别信单次探针。改写已做(`loosenItemsSchema`,**只动 `items` 子树**、顶层 union 不动) | internals 十七 · `tests/body-rewrite.mjs` · `_probe/items-subtree-upstream.mjs` |
73
- | 20 | 邮箱验证码登录报「phone 字段是必须的」或「captcha 字段是必须的」 | 邮箱和手机是**两套端点**:发信 `/v4/user/send-email` vs `/send-sms`,换登录态 `/v4/user/login-email` vs `/login`;邮箱那条验证码字段叫 **`captcha`**(不是 `code`)。判据:往 `/v4/user/login` 发任何 type 都回「phone 字段是必须的」 | `src/captcha.mjs` 头 · CHANGELOG 0.3.28 |
74
- | 21 | 无头登录「旋转验证连试 4 张图都没过」,而这号本来就是自己的 | 先分清两件事:① **已注册的号服务端根本不出图**(`/captcha/generate` 对已注册账号直接回 `business_token`)—— 所以必须**先取图、再判识图模型**,一上来就 `if (!vision) throw` 会把这条免费通道堵死;② 识图模型 `max_tokens` 太小会把推理答案饿死(`workbuddy/deepseek-v4.1-flash` 给 400 → `content:null`,判官抓空串) | CHANGELOG 0.3.28 · `src/vision.mjs` |
75
- | 22 | 同一账号有的请求 200、有的 403 `token无效`(`retryable=false`),换个时间又好了 | key 有两条签发路径时 store 与 magpie 各存一把,**静态挑一把必有一半踩空**。0.3.29 起 `keyCandidates()` 把 store 两槽 + `auth.access` 逐把打 chat,命中的写回 store;全被拒才交 402 让 magpie 换号。判据:`GET /v1/models` 对死 key **放行**,逐把探活必须用 `/v1/chat/completions` | CHANGELOG 0.3.29 · `tests/key-failover.mjs` |
76
- | 23 | 插件列表里别人有「已自动更新」徽章,magicrew 没有 | magpie 只自动更新包名以 `@magpie-community/` 开头的包(`internal/plugin/updates.go` 的 `Official()` 硬编码前缀);第三方 npm / git 源永远只提示。本插件用 `src/auto-maintenance.mjs` 复刻:读 `plugin-updates.json` 的 `waiting` → 调 `magpie plugin update` → 成功后补 `updated[]`(徽章读它)。判据:`plugin-updates.json` 的 waiting 有 `package:"Magicrew", git:true`;`findMagpieExe()` 能解析到 magpie 可执行文件。⚠ 更新后要重启 magpie 才加载新代码 | CHANGELOG 0.3.30 · `tests/auto-maintenance.mjs` |
77
- | 24 | 新装插件后没有任何签到记录,`plugin-checkin.json` 里没有 magicrew | magpie 的 `PluginCheckinOn()`:`pluginCheckins` 没有显式值时默认 **false**(只有 WorkBuddy / Trae / MiniMax / Qoder 有内置开关)。插件只能在加载时把 `settings.json` 的 `pluginCheckins[provider]` 补成 true —— **只在键不存在时补**,用户显式 false 不覆盖。判据:`settings.json` 的 `pluginCheckins` 有 `magicrew` / `magicrew-intl` 键 | CHANGELOG 0.3.30 · `src/auto-maintenance.mjs` |
78
- | 25 | 邀请上级选了某个账号,第二天签到后又变回别的号 | 旧版 `rollInvite` 每天覆盖滚动文件。0.3.32 起策略在**锁定开关打开时**优先(provider 设置「锁定邀请上级」,默认开);锁定关掉则回落到滚动。判据:`~/.cache/magpie/magicrew-invite-policy-<site>.json` 是 fixed/custom,且开关开着;`tests/invite-policy.mjs` 锁死 | CHANGELOG 0.3.32 · `src/store.mjs` |
79
-
80
- **坑 14 的两个附带判据**(都实测过):
81
-
82
- - **改了 `src/hooks/usage.mjs` 要重启 magpie 才生效** —— 插件跑在 `magpie-bun` 子进程里,
83
- 模块缓存不随文件变化失效。改完先 `Stop-Process` 再 `Start-Process` 同一个 exe,
84
- 然后 `curl /v1/magpie/quotas` 看窗口里的 `spans`:`[{}]` = 没标 aside,`[{"aside":true}]` = 还标着。
85
- - **额度读取有 1 分钟缓存**,改完立刻看可能还是旧值。
86
-
87
- 补充一条最容易误信的旧说法:
88
-
89
- | 症状 | 判据 | 出处 |
90
- |---|---|---|
91
- | 以为 checkin 会「顺带续 key」 | **从来没有**。`src/hooks/checkin.mjs` → `src/tasks.mjs` 全链路没 import 过 `ensureKey`;保活由 magpie 按 `refreshLead` 主动调 `auth.refresh` | internals 十四 · CHANGELOG 0.x |
92
- | 以为「签一把顶一把」、`key-store` 里的 key 会「时间没到就被顶掉」 | **已实测推翻(2026-10-28)**:同账号两把相隔 67 秒签的 key 同时有效;12 把 key 逐把探活,403 **全部**对应 `expiresAt` 已过(剩 -173~-541 分),没有一个「时间没到就失效」的样本。仍只留一把、只走唯一出口的理由是「本地别分叉」,不是「互相吊销」。据此加「跨账号挑活 key」的兜底 = 把别人的 key 交出去 | internals 十四 · 十五 |
93
-
94
- ---
95
-
96
- ## 三、命令
97
-
98
- ```bash
99
- # 合规检查(脚手架里的脚本,直接指到本仓库)
100
- node ../magpie-plugin-scaffold/scripts/check.mjs .
101
-
102
- # 两站都要能跑:真实登录态在 ~/.cache/magpie/magicrew-session-<siteId>.json
103
- # siteId = magicrew(国内) / magicrew-intl(国际)
104
- # 探针脚本必须放进仓库目录用 magpie 自带的 bun 跑(node 连不上国际站域名):
105
- ~/.cache/magpie/bun/1.4.4/magpie-bun.exe ./_probe/xxx.mjs
106
-
107
- # 打包面预演
108
- npm pack --dry-run
109
-
110
- # 发布(包内 name 是 Magicrew,npm 包名是 magicrew-auth)
111
- npm run publish:npm
112
-
113
- # Release:本仓库没有 .github/workflows,必须手工建
114
- git tag v0.3.16 && git push origin v0.3.16
115
- # 然后 POST /repos/MYCF711/magpie-magicrew-auth/releases
116
- ```
117
-
118
- ---
119
-
120
- ## 四、两个交付门
121
-
122
- **门 1 · 代码交付**
123
-
124
- ```
125
- □ node ../magpie-plugin-scaffold/scripts/check.mjs . 退出码 = 0
126
- □ CHECKLIST.md 的 A 表全勾
127
- □ 改动涉及的站点都跑过(国内 / 国际,别只验一个)
128
- ```
129
-
130
- **门 2 · 发布**
131
-
132
- ```
133
- □ CHECKLIST.md 的 B 表全勾(七步)
134
- □ 四条验证:npm view magicrew-auth version / tarball 内容 / magpie plugin list / Release 在
135
- ```
136
-
137
- ⚠ 没过门 2 不许说「已发布」。
138
-
139
- ---
140
-
141
- ## 五、文档地图
142
-
143
- | 文件 | 管什么 | 什么时候读 |
144
- |---|---|---|
145
- | [README](./README.md) | 用户看的门面:功能、模型、安装 | 改用户可见行为时 |
146
- | [docs/internals.md](./docs/internals.md) | 17 章实测:两站同构、三层凭证、接口清单、协议要点、工具 schema 兼容 | 改代码前按章查 |
147
- | [CHANGELOG.md](./CHANGELOG.md) | 每版一节 | 写新版本时 |
148
- | [CHECKLIST.md](./CHECKLIST.md) | 两张勾选表 | 两个交付门 |
149
- | 脚手架 [AGENTS.md](../magpie-plugin-scaffold/AGENTS.md) | 通用坑(九项清单、三条铁律、check 能查什么) | 第一次接触这个插件族时 |
150
-
151
- ---
152
-
153
- ## 六、这份文件的维护规矩
154
-
155
- 1. 任何一次「不报错、要几小时才找到」的坑,收工前在第二节加一行(症状 → 判据 → 出处)。
156
- 2. 第二节只放一句话判据,论证留在 `docs/internals.md`。
157
- 3. 判据要能当场验证(命令、字段、界面现象),写不成判据的是感想。
1
+ # AGENTS.md —— 给 AI 执行者的入口
2
+
3
+ > 人类请看 [README](./README.md)。这份只回答两件事:**你现在该读哪一份**、
4
+ > **忘了哪一条会静默出错**。
5
+ >
6
+ > 结构照搬 [magpie-plugin-scaffold](../magpie-plugin-scaffold/AGENTS.md) 的入口式写法;
7
+ > 下面第二节是本仓库自己的坑,不是通用套话。
8
+
9
+ ---
10
+
11
+ ## 零、magpie 升级时:只看稳定版
12
+
13
+ **判断「要不要跟着 magpie 改这个插件」看稳定渠道,不看每版本渠道。**
14
+ magpie 设置里有「更新渠道:每个版本 / 稳定版」,用户默认跑后者,插件要跟的也是后者。
15
+
16
+ ```bash
17
+ curl -s "https://usemagpie.ai/api/latest?channel=stable" | grep -o '"version":"[^"]*"'
18
+ ```
19
+
20
+ 实测(2026-10-28):两档差 **400 多个版本**(每版本 0.1.1763 / 稳定版 0.1.1327),
21
+ 但把两边内嵌契约都提取出来逐字段比对后 —— **插件相关的字段一条不差**。
22
+ 多出来的段是 GUI 历史,不是插件契约。⇒ 追每版本几乎全是白跑。
23
+
24
+ **稳定版版本号变了**,才需要重提取契约(脚手架的 `extract-contract.py`)→
25
+ 跑 `contract-diff.mjs` → 看差集里有没有「不写会关掉某个能力」的字段。
26
+ 完整判据见脚手架的
27
+ [HOOKS.md](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/HOOKS.md) 顶部
28
+ 与 [UPDATING.md](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/UPDATING.md) 第七节。
29
+
30
+ ---
31
+
32
+ ## 一、先定位你在哪一步
33
+
34
+ | 你在做什么 | 必读(按顺序) | 做完的判据 |
35
+ |---|---|---|
36
+ | **改协议 / 接口调用** | [docs/internals.md](./docs/internals.md) 的「三、接口清单」「十二、流式」「十六、上下文窗口」 | 改动在两站(`magicrew` 与 `magicrew-intl`)都验过 |
37
+ | **改登录链路** | internals 的「二、三层凭证」「十一、浏览器登录怎么做到全自动」 | 三条登录路径(本机浏览器 / 浏览器登录 / 手填 JWT)都还能登 |
38
+ | **改 key / 续期** | internals 的「十三、签新 key 会吊销旧 key」「十四、三层凭据的有效期与保活」 | 没引入第二份 refresh_token(会和 `src/keys.mjs` 分叉) |
39
+ | **改模型目录** | internals 的「十六、上下文窗口必须查库」+ HOOKS 十一之二(`free`) | 上下文来自查库;`free` 来自厂商声明,不从 `rate` 反推 |
40
+ | **改签到 / 首任务** | internals 的 checkin 小节(一次性闸门、五种状态) | 不漏发、不重发、失败方向是「宁可晚一轮」 |
41
+ | **代码写完,准备交付** | [CHECKLIST.md](./CHECKLIST.md) 的 **A 表** | A 表全勾 + `node scripts/check.mjs` 退出 0 |
42
+ | **要发版本** | **[UPDATING.md](https://github.com/MYCF711/magpie-plugin-scaffold/blob/main/UPDATING.md)**(脚手架的,七步) | CHECKLIST B 表全勾 + 四条验证 |
43
+ | **用户说「更新了没变化」** | 本文件坑 12(git 源只提示、要手动点更新) | `magpie plugin list` 里的 commit 真的变了 |
44
+
45
+ **最容易漏的是「要发版本」这一行。** `git push` 只是七步里的第 4 步 ——
46
+ 本仓库真踩过:0.3.14 只 push,结果 GitHub 右侧「暂无发布版本」、npm 停在 0.3.8。
47
+
48
+ ---
49
+
50
+ ## 二、本仓库的坑
51
+
52
+ | # | 症状(出问题时你看到什么) | 判据 | 出处 |
53
+ |---|---|---|---|
54
+ | 1 | 改好了国内站,国际站用户报同样的错 | **两站是同一个 Go 服务、逐字符同构**,改一处就要两站都验;`SITES.cn` / `SITES.intl` 只是 host 不同 | internals 一 |
55
+ | 2 | 国际站 `/v1/chat/completions` 回 `HTTP 500` + 22 字节裸文本(不是 JSON) | 国际站网关**强制**要 `organization-code` 头,缺了就是这个 500 | internals 一 / 五 |
56
+ | 3 | 换了 key 之后老 key 立刻失效、用户被要求重登 | **「签新 key 会吊销旧 key」已被实测推翻(2026-10-28)** —— 见下方「最容易误信的旧说法」。仍然只留一把、只走 `src/keys.mjs` 的唯一出口,理由是**本地别分叉**,不是互相吊销 | internals 十四 |
57
+ | 4 | 刷新 token 之后两处 refresh_token 不一致、用户莫名掉登录 | 只允许**一个**出口持有 refresh_token;`src/hooks/refresh.mjs` 必须走 `keys.mjs` 的 `renewViaStore()`(0.3.29 起已收敛,与 `ensureKey` 共用 `renewOnce` 锁和同一个 key-store)。若改回自调 `refreshGatewayKey` 就会分叉 | internals 二 / 十四 |
58
+ | 5 | agent 说「上下文放不下」,而实际模型能吃更长 | 上下文窗口**必须查 magpie 的模型库**,不能填保守值(早前一刀切 256K,实际 1M) | internals 十六 |
59
+ | 6 | 流式回复拿到了,但 `usage` 永远是空、额度不扣 | 流式请求必须补 `stream_options.include_usage`,否则上游不回 usage | internals 十二 |
60
+ | 7 | 模型被标成免费、Free first 一直派到它 | `free` 只认厂商声明(`input_points === 0`),**不能从 `rate === 0` 反推**;价格缺失 = 不列,不猜 | internals 十六 · HOOKS 十一之二 |
61
+ | 8 | 新号邀请奖励不到账 | 国际站判据是**有没有话题**,不是有没有项目 —— 项目打开工作台就会自动建 | internals checkin 小节 |
62
+ | 9 | 一个号天天烧一次模型积分 | 首任务是**每账号一辈子一次**(`isAutoChatSettled`),别改回按日期;失败才重试,最多 3 次 | internals checkin 小节 |
63
+ | 10 | 话题接口抖一下,就白花一次积分去补消息 | 查不到判据时**这次不发**、不落标记、不计数 —— 消息发出去花钱且不可撤 | `src/tasks.mjs` 的 `autoFirstChat` |
64
+ | 11 | Windows 上点登录,浏览器窗口拉不起来 | 浏览器可执行文件名**随版本变**(`msedge.exe` / Chrome 各版本目录不同),要逐个试候选,别写死一个名字 | internals 十一 |
65
+ | 12 | 用户点「检查更新」显示有新提交,点了更新却没变化 | 本仓库是 **git 源**:magpie 比默认分支 HEAD 的 commit,只提示不自动更新,要用户点那一行的「更新」;更新后 `magpie plugin list` 的 commit 应变化 | 脚手架 UPDATING 一 |
66
+ | 13 | 想让卡片标题 / 「源码」按钮对,去改了 magpie 的缓存 | 插件对 magpie 目录**只有读的份**;唯一载体是 `registry-entry.json`(提交给 `registry.json`) | internals 二 · README |
67
+ | 14 | **卡片上额度正常,路由页里这个号却是「剩余额度未知」** | 窗口**不能标 `aside`**:magpie 的 `allowanceOf()` 把 `Aside`/`Text` 的窗口整条跳过,一条不剩 ⇒ 该号 `Known=false`(官网文档 issue #3650 明写)。判据:`curl -s http://127.0.0.1:3425/v1/magpie/trace`,看这个 provider 的 `known` 是 `true` 还是**字段缺失** | HOOKS 三 · `src/hooks/usage.mjs` 头 |
68
+ | 15 | 接 ZCode / Claude Code 系客户端,带工具的请求回 `400 {"error":{"message":"模型响应失败","code":4017}}` | **上游的 `additionalProperties` 只认布尔值,任何对象形态都拒** —— 不只是 `{}`,`{type:"string"}` 这种有约束的 schema 一样 400(两个模型逐行一致)。判据:同一请求只把该字段换成 `true` → 200。改写已在 `rewriteRequestBody` 里做(非布尔一律换 true;**这是降级放宽、会丢约束**,取舍见 internals 十七) | internals 十七 · `tests/body-rewrite.mjs` |
69
+ | 16 | 网关路径(agent 从 magpie 的 `/v1` 进来)报 `502 did not stream: token无效`,而 provider 显示已登录 | auth 里没带 org → `accountOf()` 落 `"default"` → 读不到真实账号的 key 槽 → 只能用 `auth.access`,它过了 2 小时就 403。`accountFor()` 已在**恰好一个真实账号**时兜到它;**多账号不猜**(猜错就是拿别人的 key 扣别人的积分) | internals 十四 · 十五 · `tests/account-fallback.mjs` |
70
+ | 17 | 单轮回 `400 ... tools.N.function.parameters/properties/functions/items: invalid JSON Schema` | 上游不接受**空 schema 对象**当 `items`:`items:{}` 必 400,`items:{description:"x"}` 却 200。判据:同一请求只删 `items:{}` → 200。改写已做(删 `items:{}` / `true`,**严格等价**;`false` 不动) | internals 十七 · `tests/body-rewrite.mjs` |
71
+ | 18 | 某个账号积分耗尽后整轮回 400「可用积分已不足」,其它有余额的账号一次没被问 | **上游用 HTTP 400 报额度耗尽,magpie 的 `retryable()` 400 分支只认 `quotaWords`(不含「积分」「充值」)** ⇒ `hw.failed()=false`,既不走 `!last && hw.failed()` 的换号分支,也不 rest 该账号。插件 `rewriteCreditRefusal()` 把这种 400/422 改写成 402 Payment Required(`tests/credit-refusal.mjs` 锁死);magpie 侧修复方案见 https://github.com/yetone/magpie/issues/4095 (`retryable()` 的 400 分支补 `creditWords`) | CHANGELOG 0.3.26 |
72
+ | 19 | 单轮回 `400 ... tools.N.function.parameters/properties/functions/items: invalid JSON Schema`,但请求里**没有**空 `items` | 上游对 **`items` 子树内部**比根节点严:`anyOf`/`oneOf`/`allOf`/`not`/`const`/`if`+`then`+`else`/`{}` 全 400,**tuple 形式的 `items`(数组)国际站拒**,而同样形态放在顶层 parameters 里 200。命中来源是 **MCP 工具 schema**(REA MCP 7 个工具的 `functions` 数组)。判据:同一请求只把 `items` 子树里的 `anyOf` 摊平 → 200;改完**必须复查没留下空 `{}`**(删 `not`/`const` 会删空)。⚠ **上游 2026-10-29 00:25 起放宽了这层校验(raw 也 200),同一夜里还出现过波动** —— 判要不要改写请看 `_probe/items-subtree-stability.mjs` 的多轮统计,别信单次探针。改写已做(`loosenItemsSchema`,**只动 `items` 子树**、顶层 union 不动) | internals 十七 · `tests/body-rewrite.mjs` · `_probe/items-subtree-upstream.mjs` |
73
+ | 20 | 邮箱验证码登录报「phone 字段是必须的」或「captcha 字段是必须的」 | 邮箱和手机是**两套端点**:发信 `/v4/user/send-email` vs `/send-sms`,换登录态 `/v4/user/login-email` vs `/login`;邮箱那条验证码字段叫 **`captcha`**(不是 `code`)。判据:往 `/v4/user/login` 发任何 type 都回「phone 字段是必须的」 | `src/captcha.mjs` 头 · CHANGELOG 0.3.28 |
74
+ | 21 | 无头登录「旋转验证连试 4 张图都没过」,而这号本来就是自己的 | 先分清两件事:① **已注册的号服务端根本不出图**(`/captcha/generate` 对已注册账号直接回 `business_token`)—— 所以必须**先取图、再判识图模型**,一上来就 `if (!vision) throw` 会把这条免费通道堵死;② 识图模型 `max_tokens` 太小会把推理答案饿死(`workbuddy/deepseek-v4.1-flash` 给 400 → `content:null`,判官抓空串) | CHANGELOG 0.3.28 · `src/vision.mjs` |
75
+ | 22 | 同一账号有的请求 200、有的 403 `token无效`(`retryable=false`),换个时间又好了 | key 有两条签发路径时 store 与 magpie 各存一把,**静态挑一把必有一半踩空**。0.3.29 起 `keyCandidates()` 把 store 两槽 + `auth.access` 逐把打 chat,命中的写回 store;全被拒才交 402 让 magpie 换号。判据:`GET /v1/models` 对死 key **放行**,逐把探活必须用 `/v1/chat/completions` | CHANGELOG 0.3.29 · `tests/key-failover.mjs` |
76
+ | 23 | 插件列表里别人有「已自动更新」徽章,magicrew 没有 | magpie 只自动更新包名以 `@magpie-community/` 开头的包(`internal/plugin/updates.go` 的 `Official()` 硬编码前缀);第三方 npm / git 源永远只提示。本插件用 `src/auto-maintenance.mjs` 复刻:读 `plugin-updates.json` 的 `waiting` → 调 `magpie plugin update` → 成功后补 `updated[]`(徽章读它)。判据:`plugin-updates.json` 的 waiting 有 `package:"Magicrew", git:true`;`findMagpieExe()` 能解析到 magpie 可执行文件。⚠ 更新后要重启 magpie 才加载新代码 | CHANGELOG 0.3.30 · `tests/auto-maintenance.mjs` |
77
+ | 24 | 新装插件后没有任何签到记录,`plugin-checkin.json` 里没有 magicrew | magpie 的 `PluginCheckinOn()`:`pluginCheckins` 没有显式值时默认 **false**(只有 WorkBuddy / Trae / MiniMax / Qoder 有内置开关)。插件只能在加载时把 `settings.json` 的 `pluginCheckins[provider]` 补成 true —— **只在键不存在时补**,用户显式 false 不覆盖。判据:`settings.json` 的 `pluginCheckins` 有 `magicrew` / `magicrew-intl` 键 | CHANGELOG 0.3.30 · `src/auto-maintenance.mjs` |
78
+ | 25 | 邀请上级选了某个账号,第二天签到后又变回别的号 | 旧版 `rollInvite` 每天覆盖滚动文件。0.3.33 起在插件页「超级麦吉设置」按站点选码 + 「锁定(签到不再覆盖)」勾选框(写 `inviteLockBySite`,登录侧优先于文件策略);锁定关掉则回落滚动。判据:页面「当前」与登录方法标题同源;`tests/invite-policy.mjs` 锁死 | CHANGELOG 0.3.33 · `page/app.js` |
79
+ | 26 | 插件页里设置保存了,登录/标题却不认 | 官方契约工厂是 `(ctx, options)` —— **第二个参数**才是插件 options;`input.magpie.options(provider)` 实测恒为 `{}`。index.mjs 用 `async (input, options) => ...`,页面写 `inviteBySite` / `inviteLockBySite`。改回 `input.magpie.options` 会静默回退 | CHANGELOG 0.3.33 · `index.mjs` |
80
+ | 27 | 插件页「超级麦吉设置」空白 / 没有账号可选 | 页面是沙箱:读不到磁盘/账号。账号清单由 `syncInvitePage()` 在 Node 侧生成 `page/codes.js`(config / 签到 / 登录 / 策略变更 / usage 60s 刷新);老用户升级后 20s 由 `backfillInviteCodes()` 用 plugin-auth 的 JWT 补码;仓库里有 `.git` 时故意不生成,发布包才有;`package.json` 的 `files` 必须含 `page` | CHANGELOG 0.3.33 · `src/store.mjs` · `page/` |
81
+
82
+ **坑 14 的两个附带判据**(都实测过):
83
+
84
+ - **改了 `src/hooks/usage.mjs` 要重启 magpie 才生效** —— 插件跑在 `magpie-bun` 子进程里,
85
+ 模块缓存不随文件变化失效。改完先 `Stop-Process` 再 `Start-Process` 同一个 exe,
86
+ 然后 `curl /v1/magpie/quotas` 看窗口里的 `spans`:`[{}]` = 没标 aside,`[{"aside":true}]` = 还标着。
87
+ - **额度读取有 1 分钟缓存**,改完立刻看可能还是旧值。
88
+
89
+ 补充一条最容易误信的旧说法:
90
+
91
+ | 症状 | 判据 | 出处 |
92
+ |---|---|---|
93
+ | 以为 checkin 会「顺带续 key」 | **从来没有**。`src/hooks/checkin.mjs` → `src/tasks.mjs` 全链路没 import 过 `ensureKey`;保活由 magpie 按 `refreshLead` 主动调 `auth.refresh` | internals 十四 · CHANGELOG 0.x |
94
+ | 以为「签一把顶一把」、`key-store` 里的 key 会「时间没到就被顶掉」 | **已实测推翻(2026-10-28)**:同账号两把相隔 67 秒签的 key 同时有效;12 把 key 逐把探活,403 **全部**对应 `expiresAt` 已过(剩 -173~-541 分),没有一个「时间没到就失效」的样本。仍只留一把、只走唯一出口的理由是「本地别分叉」,不是「互相吊销」。据此加「跨账号挑活 key」的兜底 = 把别人的 key 交出去 | internals 十四 · 十五 |
95
+
96
+ ---
97
+
98
+ ## 三、命令
99
+
100
+ ```bash
101
+ # 合规检查(脚手架里的脚本,直接指到本仓库)
102
+ node ../magpie-plugin-scaffold/scripts/check.mjs .
103
+
104
+ # 两站都要能跑:真实登录态在 ~/.cache/magpie/magicrew-session-<siteId>.json
105
+ # siteId = magicrew(国内) / magicrew-intl(国际)
106
+ # 探针脚本必须放进仓库目录用 magpie 自带的 bun 跑(node 连不上国际站域名):
107
+ ~/.cache/magpie/bun/1.4.4/magpie-bun.exe ./_probe/xxx.mjs
108
+
109
+ # 打包面预演
110
+ npm pack --dry-run
111
+
112
+ # 发布(包内 name 是 Magicrew,npm 包名是 magicrew-auth)
113
+ npm run publish:npm
114
+
115
+ # Release:本仓库没有 .github/workflows,必须手工建
116
+ git tag v0.3.16 && git push origin v0.3.16
117
+ # 然后 POST /repos/MYCF711/magpie-magicrew-auth/releases
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 四、两个交付门
123
+
124
+ **门 1 · 代码交付**
125
+
126
+ ```
127
+ □ node ../magpie-plugin-scaffold/scripts/check.mjs . 退出码 = 0
128
+ □ CHECKLIST.md 的 A 表全勾
129
+ □ 改动涉及的站点都跑过(国内 / 国际,别只验一个)
130
+ ```
131
+
132
+ **门 2 · 发布**
133
+
134
+ ```
135
+ □ CHECKLIST.md 的 B 表全勾(七步)
136
+ □ 四条验证:npm view magicrew-auth version / tarball 内容 / magpie plugin list / Release 在
137
+ ```
138
+
139
+ ⚠ 没过门 2 不许说「已发布」。
140
+
141
+ ---
142
+
143
+ ## 五、文档地图
144
+
145
+ | 文件 | 管什么 | 什么时候读 |
146
+ |---|---|---|
147
+ | [README](./README.md) | 用户看的门面:功能、模型、安装 | 改用户可见行为时 |
148
+ | [docs/internals.md](./docs/internals.md) | 17 章实测:两站同构、三层凭证、接口清单、协议要点、工具 schema 兼容 | 改代码前按章查 |
149
+ | [CHANGELOG.md](./CHANGELOG.md) | 每版一节 | 写新版本时 |
150
+ | [CHECKLIST.md](./CHECKLIST.md) | 两张勾选表 | 两个交付门 |
151
+ | 脚手架 [AGENTS.md](../magpie-plugin-scaffold/AGENTS.md) | 通用坑(九项清单、三条铁律、check 能查什么) | 第一次接触这个插件族时 |
152
+
153
+ ---
154
+
155
+ ## 六、这份文件的维护规矩
156
+
157
+ 1. 任何一次「不报错、要几小时才找到」的坑,收工前在第二节加一行(症状 → 判据 → 出处)。
158
+ 2. 第二节只放一句话判据,论证留在 `docs/internals.md`。
159
+ 3. 判据要能当场验证(命令、字段、界面现象),写不成判据的是感想。
package/CHANGELOG.md CHANGED
@@ -1,5 +1,82 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.3.34 — 2026-10-29
4
+
5
+ **页面账号列表为空:升级后补一次邀请码(0.3.33 补丁)**
6
+
7
+ 0.3.33 的页面清单只读 `magicrew-invite-codes-<site>.json` —— 老用户升级上来时
8
+ 这份缓存可能一条都没有(旧版从不记),页面就是空的,要等下一次签到/登录。
9
+
10
+ 现在 `config` 之后 20 秒异步跑一次 `backfillInviteCodes()`:读 `plugin-auth.json`
11
+ 里每个账号的 JWT,逐个问 `/api/v1/invitations/info`,补进码缓存并刷新
12
+ `page/codes.js`。只补缺口;失败静默(JWT 过期 / 设备指纹不对就跳过),下次签到
13
+ 仍会补。
14
+
15
+ 验证:`tests/invite-policy.mjs` 24 条断言(新增 backfill 一条)。
16
+
17
+ ## 0.3.33 — 2026-10-29
18
+
19
+ **批量:登录窗口去重 · key 自愈 / 413 人话 · 「超级麦吉设置」页面 · 死号回退**
20
+
21
+ (核心改动来自 PR #17,作者 @baijiu666,合并了原 #14 / #15 / #16;合入时补了页面作者项 / 锁定勾选 / 页面 auto 语义等,见文末。)
22
+
23
+ ### 一、登录窗口只拉一个浏览器(原 #14)
24
+
25
+ Windows 实测:Chrome + Edge 并存时一次点击 spawn 两次;浏览器父进程 `exit 0` 后
26
+ 窗口挂在另一批子进程上,点几次叠几个。现在:
27
+
28
+ - 只试第一个能用的浏览器,失败直接交手动登录,不再用窗口堆失败
29
+ - 参数集照 `GoogleChrome/chrome-launcher` 补全(含 `--remote-allow-origins=*`)
30
+ - 失败按一次性 `--user-data-dir` 精确杀进程树,再删目录
31
+ - 一次失败后 30 分钟冷却(`cdpDownUntil`),冷却期内直接走手动登录
32
+
33
+ ### 二、key 自愈 + 413 人话(原 #15)
34
+
35
+ - **自愈**:候选 key 全被拒时,用 `refresh_token` 续一把再试一次(只一次,失败照旧
36
+ 402 交 magpie 换号)。修「有积分却一直 403 / 已耗尽」:key 被别处会话顶掉后,
37
+ 不到 `expiresAt` 不会自动续,三把全死就死循环。
38
+ - **413**:上游(Swow/PHP)请求体硬上限约 8MB(实测 6MB 过、8MB 拒),超了回
39
+ HTML 页;插件改写成人话 JSON:压缩上下文或新开会话,换账号无效。
40
+
41
+ ### 三、「超级麦吉设置」页面 + 死号回退(原 #16)
42
+
43
+ - 插件页(`page/`)按站点设置:**我的(作者 MYCF711)** / 自动 / 每个已登录账号
44
+ (打码手机号·自己的码)/ 自定义码;**「锁定(签到不再覆盖)」勾选框**
45
+ - 设置写进插件 options(`inviteBySite` / `inviteLockBySite`),登录侧优先于文件策略
46
+ - 页面数据 `page/codes.js` 由 Node 侧生成(页面沙箱读不到账号),在 config / 签到 /
47
+ 登录 / 策略变更 / usage 轮询(60s 节流)时刷新
48
+ - 两个「只设置不登录」的 methods 入口删除 —— 不再用 `{type:"failed"}` 伪装登录
49
+ - 账号删除后:`liveOrgs()` 只认 `plugin-auth.json` 里还登录着的 org;fixed 指向死号 /
50
+ 滚动码归属死号 → 自动回落(滚动 → 作者码);`syncInvitePage()` 清码缓存和归属死号
51
+ 的滚动码;页面打 ⚠ 标
52
+ - `inviteTag` 与登录同源:标题显示的上级 = 登录真正会用的码的来源(含死号校验)
53
+
54
+ ### 合入时补的
55
+
56
+ 1. **页面加「我的(作者 MYCF711)」** —— PR 的页面只有自动 / 各账号 / 自定义,没有作者项
57
+ 2. **页面加「锁定(签到不再覆盖)」勾选框**(按站点 `inviteLockBySite`)—— PR 的
58
+ `auth.options` 开关读的是 `input.magpie.options()`,实测恒为 `{}`,失效;现在改用
59
+ 工厂第二参数 `(ctx, options)`,页面写 `inviteLockBySite`,登录侧实时读
60
+ 3. **页面「自动」不再被文件策略覆盖** —— `optionInvite()` 显式 auto,`inviteFromOptions()`
61
+ 回落到滚动 / 作者码;锁定关闭时页面选的码也不作数
62
+ 4. **作者 org 统一为空** —— 旧版把作者码存成 `org:"author"`,会被死号校验当已删除;
63
+ 读策略时归一化,页面/方法都用 `org:""`
64
+ 5. `package.json` 的 `files` 补 `page`(否则 npm 包里没有页面);`.gitignore` 忽略
65
+ 运行时生成的 `page/codes.js`;页面主题变量改用 `--magpie-*`
66
+ 6. `tests/invite-policy.mjs` 扩到 23 条(死号回退、页面 auto / 锁定、codes.js 生成)
67
+
68
+ ### 验证
69
+
70
+ ```
71
+ tests/invite-policy.mjs 23 通过 · 0 失败
72
+ tests/key-failover.mjs 26 通过 · 0 失败
73
+ tests/auto-maintenance.mjs 21 通过 · 0 失败
74
+ tests/credit-refusal.mjs 19 通过 · 0 失败
75
+ tests/account-fallback.mjs 10 通过 · 0 失败
76
+ tests/body-rewrite.mjs 48 通过 · 0 失败
77
+ multi-provider / browser-detect / smoke 全过;scaffold check.mjs 30 通过 0 失败
78
+ ```
79
+
3
80
  ## 0.3.32 — 2026-10-29
4
81
 
5
82
  **邀请上级可固定 / 自定义(PR #13,作者 @baijiu666)**
package/README.md CHANGED
@@ -34,7 +34,7 @@
34
34
  - **默认开启自动签到** —— 安装后自动把本 provider 的插件签到设为开(`pluginCheckins` 缺省时补 true;你手动关掉的不会被改回)
35
35
  - **自动更新** —— 复用 magpie 的插件更新机制:发现自己的新版本后调 `magpie plugin update`,并在成功后写「已自动更新」记录;更新完要重启一次 magpie 才会加载新代码
36
36
  - **邀请码自动滚** —— 第一次注册带作者的码,之后每次登录都记住「上一个登录账号」的码,拿到新的就覆盖旧的
37
- - **邀请上级可固定** —— 登录列表里的「邀请上级」展开所有已登录账号(第一项是「我的(作者 MYCF711)」),或「自定义码」填任意 IV 码;provider 设置里的「锁定邀请上级」开关决定是否让签到覆盖
37
+ - **邀请上级可固定** —— 插件页「超级麦吉设置」按站点选:我的(作者 MYCF711)/ 自动 / 已登录账号 / 自定义码;「锁定」勾选框决定签到是否覆盖
38
38
 
39
39
  ---
40
40