magicrew-auth 0.3.25 → 0.3.28
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 +4 -0
- package/CHANGELOG.md +330 -0
- package/README.md +135 -58
- package/docs/internals.md +179 -0
- package/package.json +1 -1
- package/src/captcha.mjs +146 -43
- package/src/headless.mjs +79 -29
- package/src/hooks/loader.mjs +228 -5
- package/src/hooks/methods.mjs +238 -147
- package/src/vision.mjs +83 -25
package/AGENTS.md
CHANGED
|
@@ -68,6 +68,10 @@ curl -s "https://usemagpie.ai/api/latest?channel=stable" | grep -o '"version":"[
|
|
|
68
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
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
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` |
|
|
71
75
|
|
|
72
76
|
**坑 14 的两个附带判据**(都实测过):
|
|
73
77
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,335 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.3.28 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
**邮箱验证码登录(内置)· 已有账号不再过旋转图 · 识图模型被 `max_tokens` 饿死的修复**
|
|
6
|
+
|
|
7
|
+
### 一、邮箱验证码登录
|
|
8
|
+
|
|
9
|
+
国际站用户大多是邮箱注册的,之前只有手机号验证码这一条零浏览器路。现在两条都有,
|
|
10
|
+
**而且共用同一份实现**(`makeCodeMethod(c, kind)`)—— 复制成两份的那天,下次改超时
|
|
11
|
+
或改文案必然只改一份。
|
|
12
|
+
|
|
13
|
+
协议是逆前端 chunk + 两站实测出来的,全部记在 `src/captcha.mjs` 的头部注释里:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
取图 POST /captcha/generate {account_type:"email", email} ← 无 state_code
|
|
17
|
+
过验证 POST /captcha/verify {token, angle, account_type, email}
|
|
18
|
+
发信 POST /v4/user/send-email {email, type:"account_login"} + X-Captcha-Token
|
|
19
|
+
换登录态 POST /v4/user/login-email {email, captcha, device, auto_register, redirect}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
三个**必须记住**的点(都实测过,写错的表现完全看不出是它们的错):
|
|
23
|
+
|
|
24
|
+
1. 邮箱**必须**走 `/v4/user/login-email`。往 `/v4/user/login` 发任何 type
|
|
25
|
+
(`email` / `email_captcha` / `email_code`)都回 `2000「phone 字段是必须的」`。
|
|
26
|
+
2. 邮箱那条的**验证码字段叫 `captcha`,不叫 `code`**。发 `code` 回
|
|
27
|
+
`2000「captcha 字段是必须的」`;发 `captcha` 回 `2168「验证码错误」`(= 参数对了)。
|
|
28
|
+
3. 发信端点的 `type` 也是 `"account_login"`(与手机号同)。填 `"login"` 回
|
|
29
|
+
`2000「服务异常,请联系管理员」`——后端内部炸了,不是参数校验失败。
|
|
30
|
+
|
|
31
|
+
前端来源:`LoginCoreForm` chunk 里的
|
|
32
|
+
`loginService.sendEmailVerificationCode(email, "account_login", captchaToken)` 与
|
|
33
|
+
`login(LoginType.EmailVerificationCode, …)`(枚举值就是字符串 `"email"`);
|
|
34
|
+
端点定义在 `UserApi`(`loginByEmail()` 显式 `delete("organization-code")`)。
|
|
35
|
+
|
|
36
|
+
**⚠ 这条方法只在国际站出现,国内站不给。** 两条实测判据:
|
|
37
|
+
|
|
38
|
+
1. `www.letsmagic.cn/login` 的登录方式只有 **微信 / 苹果 / 密码 / 手机 / 私有部署**,
|
|
39
|
+
没有邮箱(国际站 `www.magicrew.ai/login` 才有 `switch-to-email`)⇒ 国内站
|
|
40
|
+
不存在邮箱注册的账号,摆出来只会让用户填完邮箱撞一个看不懂的错;
|
|
41
|
+
2. 同一个发信请求,国内站 `/v4/user/send-email` 回 `2000 服务异常,请联系管理员`,
|
|
42
|
+
国际站回 `1000 请求成功`。
|
|
43
|
+
|
|
44
|
+
所以方法表按站点分叉:国际站 `邮箱 → 手机号 → 浏览器 → 登录态`,
|
|
45
|
+
国内站 `手机号 → 浏览器 → 登录态`(`tests/code-login.mjs` 把条数也钉死了 ——
|
|
46
|
+
国内站必须**恰好 3 条**,多一条就红)。
|
|
47
|
+
|
|
48
|
+
### 二、已有账号**不再出现**旋转验证图
|
|
49
|
+
|
|
50
|
+
这是顺手挖出来的、比邮箱本身更值钱的一条:**服务端对已注册的账号本来就不出图。**
|
|
51
|
+
|
|
52
|
+
实测(2026-10-29,两站):
|
|
53
|
+
|
|
54
|
+
| 请求对象 | `/captcha/generate` 返回 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| 已注册邮箱 | `business_token`(**直接放行,连图都不给**) |
|
|
57
|
+
| 从没注册过的邮箱 | `token` + `image`(要给图) |
|
|
58
|
+
|
|
59
|
+
也就是说「把我已有的账号加进 magpie」这条路**根本不需要识图模型**,全程只有
|
|
60
|
+
「填邮箱/手机号 → 填验证码」两步。而之前 `startCodeLogin()` 第一句就是
|
|
61
|
+
`if (!readVisionModel()) throw no-vision-model` —— **把这条免费通道堵死了**:
|
|
62
|
+
没配识图模型的人连自己已有的账号都加不进来。
|
|
63
|
+
|
|
64
|
+
改动:
|
|
65
|
+
|
|
66
|
+
- `startCodeLogin()` 改成**先取图,再决定要不要识图模型**:`registered` → 直接发码;
|
|
67
|
+
只有确实要给新号过图时才要求模型(抛 `code: "no-vision-model"`)
|
|
68
|
+
- `passCaptcha(site, ident, firstCap)` 接受调用方已经取到的那张图,省一次请求
|
|
69
|
+
- `methods.mjs` 里「回退浏览器」那段从 `authorize` 开头挪进 `catch (no-vision-model)` ——
|
|
70
|
+
只有真新号注册才回退
|
|
71
|
+
- 提示语跟着分叉:免图时告诉用户「这一环跳过了,识图模型这次也用不上」
|
|
72
|
+
|
|
73
|
+
### 三、识图模型的 `max_tokens` 把它自己饿死(真 bug)
|
|
74
|
+
|
|
75
|
+
`src/vision.mjs` 原本写 `max_tokens: 400`。实测 `workbuddy/deepseek-v4.1-flash`:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
max_tokens=400 → finish_reason:"length" reasoning_tokens:400 content:null
|
|
79
|
+
max_tokens=2000 → finish_reason:"length" reasoning_tokens:2000 content:null
|
|
80
|
+
max_tokens=8000 → finish_reason:"stop" reasoning_tokens:899 content:"10"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
它是**推理模型**,400 个 token 全被 `reasoning_content` 吃掉,答案一个字都没输出 ⇒
|
|
84
|
+
判官拿到空串 ⇒ 判成「判角度不可信」⇒ 每轮都换图 ⇒ 整条无头登录直接挂。
|
|
85
|
+
**这个值买的是思考空间,不是答案长度。**
|
|
86
|
+
|
|
87
|
+
改成 `max_tokens: 6000`,并加一层兜底:`content` 为空时从 `reasoning_content`
|
|
88
|
+
的**最后 300 字**里找数字(实测尾巴就长「最终决定:10。」这样)。
|
|
89
|
+
|
|
90
|
+
### 四、删掉 `tools/first-ai-work.mjs`
|
|
91
|
+
|
|
92
|
+
那个脚本是早期用 CDP 拉浏览器、点输入框、按回车替账号发第一条消息的。
|
|
93
|
+
`src/tasks.mjs` 的 `autoFirstWork()` 早就用纯协议(`chat-task.mjs`)取代了它,
|
|
94
|
+
留着只会让人以为「第一项任务还得手动跑脚本」。新手奖励 8 项现在**全自动**。
|
|
95
|
+
|
|
96
|
+
### 五、识图模型:思考关不掉,但能砍一半
|
|
97
|
+
|
|
98
|
+
`workbuddy/deepseek-v4.1-flash` 是推理模型。让它**别思考**(更快、更省、不怕
|
|
99
|
+
`max_tokens` 被思考吃光)这件事,12 组参数逐个试过 —— 同一模型、同一张图:
|
|
100
|
+
|
|
101
|
+
| 参数 | reasoning 字数 | content | 结论 |
|
|
102
|
+
|---|---|---|---|
|
|
103
|
+
| 不传 | 1867 | `10.5` | 基线 |
|
|
104
|
+
| `reasoning_effort: "none"` | 2824 | `10.5` | ❌ 比不传**还长**(网关按未知参数丢了) |
|
|
105
|
+
| `reasoning_effort: "minimal"` | 2514 | `10.2` | ❌ 同上 |
|
|
106
|
+
| **`reasoning_effort: "low"`** | **835** | `10.5` | ✅ 唯一有效的 |
|
|
107
|
+
| `enable_thinking: false` | 3346 | **空** | ❌ 思考更长,且交白卷 |
|
|
108
|
+
| `thinking_budget: 0` | 3156 | **空** | ❌ 同上 |
|
|
109
|
+
| `thinking: {type:"disabled"}` | 1533 | `10.7` | 小幅 |
|
|
110
|
+
| `chat_template_kwargs` / `extra_body` / `reasoning:{enabled:false}` | 1161–1563 | 有 | 小幅 |
|
|
111
|
+
| 模型名后缀 `-nothink` / `/no_think` | — | — | HTTP 400 模型不存在 |
|
|
112
|
+
|
|
113
|
+
⇒ **这个模型在网关侧关不掉思考。** 真想「不思考」,只能换一个**非推理的视觉模型**
|
|
114
|
+
(那是用户在 magpie 设置里选的,插件不替用户选)。
|
|
115
|
+
|
|
116
|
+
于是取唯一有效的那条,再连跑 6 轮统计(3 张图 × 2 轮,同一张图重复问):
|
|
117
|
+
|
|
118
|
+
| | 出数字 | 平均耗时 | 平均 tokens | 平均 reasoning |
|
|
119
|
+
|---|---|---|---|---|
|
|
120
|
+
| 不传 | 5/6 | 20.6 s | 2343 | 3581 字 |
|
|
121
|
+
| `reasoning_effort: "low"` | **6/6** | **8.4 s** | **1007** | 1646 字 |
|
|
122
|
+
|
|
123
|
+
`low` 不但更快更省,**还更稳** —— 不传的那一组有一次把 6000 token 全烧在思考上、
|
|
124
|
+
`content` 交白卷(和 `max_tokens: 400` 那个坑是同一个形态)。两种配置判出来的角度一致。
|
|
125
|
+
|
|
126
|
+
另外把**两个判官改成并行**:原来 `await a; await b` 串行,把延迟叠成两倍;
|
|
127
|
+
现在耗时等于较慢的那一次。`reasoning_effort: "low"` + 并行之后,一次真实验证
|
|
128
|
+
(国内站,取图 → 识图 → 过旋转验证)判出 `clock=6.00 / deg=180`,verify 回
|
|
129
|
+
`1000 验证成功`。
|
|
130
|
+
|
|
131
|
+
**那换一个不思考的模型呢?** 能换,但别指望白捡。同一张图喂几个候选(都在
|
|
132
|
+
`magicrew-intl` 下):
|
|
133
|
+
|
|
134
|
+
| 模型 | 耗时 | reasoning | 完成 tokens | 答案 |
|
|
135
|
+
|---|---|---|---|---|
|
|
136
|
+
| `gpt-4o` | **3.2 s** | **0** | **3** | 7.5 |
|
|
137
|
+
| `gpt-5.4-mini` | **2.9 s** | **0** | **12** | 10.5 |
|
|
138
|
+
| `gemini-3.7-flash` | 7.9 s | 0 | 425 | 2.5 |
|
|
139
|
+
| `glm-5.3-flash` | 27.5 s | 3588 | 879 | 1.5 |
|
|
140
|
+
| `qwen3.5-flash` | 45.1 s | 9087 | 5575 | 2.0 |
|
|
141
|
+
| `workbuddy/deepseek-v4.1-flash`(当前) | 19.4 s | 3769 | 2475 | 2.5 |
|
|
142
|
+
|
|
143
|
+
`gpt-4o` / `gpt-5.4-mini` 确实**完全不思考**(`reasoning_tokens: 0`,2~3 秒)。
|
|
144
|
+
但同一张图六个模型给出 **1.5~10.5 钟点**的答案 —— 差得离谱,说明「谁对」必须用
|
|
145
|
+
**通过率**判,不能只看快。小样本(每模型 3 张图、完整跑两问 + 提交)的结果:
|
|
146
|
+
|
|
147
|
+
| 模型 | 通过 | 平均判角度耗时 |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `workbuddy/deepseek-v4.1-flash`(当前) | 2/3 | 23.6 s |
|
|
150
|
+
| `gpt-5.4-mini` | 1/3 | 7.4 s |
|
|
151
|
+
| `gemini-3.7-flash` | 1/3 | 9.8 s |
|
|
152
|
+
| `gpt-4o` | 0/3 | 1.1 s(三次都解析不出数字) |
|
|
153
|
+
|
|
154
|
+
⚠ 样本太小(3 张/模型),**不能据此换模型**。但能确定两件事:① 换模型是唯一能
|
|
155
|
+
真「不思考」的路;② 换之前必须用 `_probe/vision-accuracy.mjs` 跑 10 张以上比通过率,
|
|
156
|
+
别拿速度当唯一指标。当前默认保持原模型 + `effort=low` + 并行。
|
|
157
|
+
|
|
158
|
+
**兜底:`reasoning_effort` 被 400 就退一步。** 它**不是** OpenAI 规范字段,而识图模型是
|
|
159
|
+
用户自己在 magpie 里选的(模型库 + 自定义 provider 什么都可能有)—— 有的端点认得,
|
|
160
|
+
有的会为未知字段直接回 400。所以 `askClock()` 里加了一层:带它发出去被 400 时,
|
|
161
|
+
去掉该字段重发一次,行为退回「不传」那一档(少省几秒,但也别把整条无头登录打死)。
|
|
162
|
+
实测该路径:首次 400 → 去掉字段 → 200 → 正常解析出答案。
|
|
163
|
+
|
|
164
|
+
### 验证
|
|
165
|
+
|
|
166
|
+
- **新增 `tests/code-login.mjs`:34 个断言** —— 拦全局 fetch 换成可编程假服务端,
|
|
167
|
+
逐个钉死请求本身:邮箱不带 `state_code`、邮箱不进 `send-sms`、
|
|
168
|
+
`login-email` 用 `captcha` 而 `login` 用 `code`、`device` 是 64 位 hex、
|
|
169
|
+
`auto_register` / `redirect` 都在、2168 原样透出;另含**免图通道**两条
|
|
170
|
+
(已注册 → 只走 generate + send-email、不碰 verify;新号 → 取了图但**不**直接发码)
|
|
171
|
+
- **`tests/browser-login.mjs` 更新**:改成按 label 找「浏览器登录」,
|
|
172
|
+
并新增四条方法的顺序断言(国际站 邮箱 → 手机号 → 浏览器 → 登录态;
|
|
173
|
+
国内站 手机号 → 浏览器 → 登录态,**没有邮箱那条**)。`authorize` 的耗时阈值从 50ms 放宽到
|
|
174
|
+
5s —— 它允许花几百毫秒拉起浏览器,红线是「**不卡在等用户**」;剪贴板起不来的
|
|
175
|
+
环境里那一段改成跳过,而不是让整份测试崩
|
|
176
|
+
- 回归:`multi-provider` 全过、`credit-refusal` 18 通过、`account-fallback` 10 通过、
|
|
177
|
+
`body-rewrite` **48 通过**(本版落地前先并了 0.3.27 的 `items` 子树改写,那一版把
|
|
178
|
+
它的断言从 36 加到 48)、`login-profile` / `local-browser` / `browser-detect` / `smoke` 全过;
|
|
179
|
+
`node ../magpie-plugin-scaffold/scripts/check.mjs .` → 30 通过 / 0 失败
|
|
180
|
+
- 两站实测:邮箱取图 / 过验证(国际站 50° 通过)/ 发信(`code 1000`)/ 换登录态
|
|
181
|
+
(`2168` = 参数结构正确)都验过
|
|
182
|
+
|
|
183
|
+
## 0.3.27 — 2026-10-29
|
|
184
|
+
|
|
185
|
+
**修 `items` 子树内部的 union / 空 schema 导致的整轮 400**
|
|
186
|
+
|
|
187
|
+
(内容来自 PR #10,作者 @Jet;合入时补了三处漏洞,见「合入时补的三处」。)
|
|
188
|
+
|
|
189
|
+
### 症状
|
|
190
|
+
|
|
191
|
+
DSH 经 magpie 网关调用 DeepSeek-V4.1-Flash 时整轮失败:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
本轮运行失败 400: {"code":null,"message":"超级麦吉: HTTP 400 请求错误:
|
|
195
|
+
{\"error\":{\"code\":\"InvalidParameter\",\"message\":\"tools.40.function.parameters/
|
|
196
|
+
properties/functions/items: invalid JSON Schema; check the schema keywords
|
|
197
|
+
and references Request id: ...\",\"param\":\"\",\"type\":\"BadRequest\"}}",
|
|
198
|
+
"param":null,"type":"invalid_request_error"}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
0.3.24 已修过 `items: {}`,但这一类**没被覆盖** —— 0.3.24 只删 `items`
|
|
202
|
+
**自己**的空对象,而这里出问题的是 `items` **内部**的 schema。
|
|
203
|
+
|
|
204
|
+
### 根因(2026-10-29 实测,直打上游、同一把 key、同一份请求、只改一个字段)
|
|
205
|
+
|
|
206
|
+
上游的 JSON Schema 校验器对**数组元素的 schema** 比对根节点更严。放在
|
|
207
|
+
**顶层 parameters** 里上游**收**(200)的形态,放进 `items` 子树就一律 400:
|
|
208
|
+
|
|
209
|
+
| `items` 内部的形态 | 结果 |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `anyOf` / `oneOf` / `allOf` | **400** |
|
|
212
|
+
| `not` / `const` | **400** |
|
|
213
|
+
| `if` + `then`(`else` 同理) | **400** |
|
|
214
|
+
| `{}`(空 schema) | **400** |
|
|
215
|
+
| `items` 本身是**数组**(draft-04 tuple 形式) | 国内站 200 / **国际站 400** |
|
|
216
|
+
| `type` / `enum` / `pattern` / `minLength` / `minItems` / `format` / `$ref` / 数值边界 | 200 |
|
|
217
|
+
| `type:["object","string","number","boolean","array","null"]` | 200 ← 「任意值」的合法写法 |
|
|
218
|
+
| 顶层 parameters 里的 `anyOf` / `const` / `not` / `{}` | 200 |
|
|
219
|
+
|
|
220
|
+
命中来源是**MCP 服务器**的工具 schema:REA MCP(`mcp__rea__build_call_path`、
|
|
221
|
+
`mcp__rea__compare_application_versions` 等 **7 个工具**)的 `functions` 数组参数,
|
|
222
|
+
元素 schema 用了 `anyOf: [{…}, {type:"null"}]`(可选参数的标准写法)、`oneOf`,
|
|
223
|
+
还有 `{}` 空 schema。一份 65 工具的请求里 7 个工具中招 ⇒ 整轮 400。
|
|
224
|
+
|
|
225
|
+
### 修法
|
|
226
|
+
|
|
227
|
+
`src/hooks/loader.mjs` 新增 `loosenItemsSchema()`,只作用于 **`items` 子树**:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
anyOf / oneOf / allOf → 取一个分支内联(分支本身先递归修),优先取非纯 null 的分支
|
|
231
|
+
not / const → 删除
|
|
232
|
+
if / then / else → 删除
|
|
233
|
+
{} → {type:["object","string","number","boolean","array","null"]}
|
|
234
|
+
items 是数组(tuple) → {type:[同上]}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
全部是**降级放宽**(方向安全:宁可约束松一点,也不要整轮失败):
|
|
238
|
+
|
|
239
|
+
- union 取一个分支:`anyOf:[{…},{type:"null"}]` 取到的正是「非空」那个。
|
|
240
|
+
⚠ 顺序反过来(null 在前)时要跳过 null 分支 —— 照抄第一个会把「可选」压成
|
|
241
|
+
「只能 null」,那是**收紧**;
|
|
242
|
+
- `const` 删除 ⇒ 「只能是这一个值」变「任意值」,放宽;
|
|
243
|
+
- `not` / `if+then+else` 无等价物,删掉同样是放宽;
|
|
244
|
+
- `{}` → 六类型 `type` 数组:与空 schema **严格等价**(六种 JSON 类型全覆盖),
|
|
245
|
+
而 `{}` / `true` 在这层都被上游拒,只能落成这个显式写法。
|
|
246
|
+
|
|
247
|
+
⚠ 顶层 parameters 的 union **不动** —— 上游收它,动了白白丢约束。
|
|
248
|
+
|
|
249
|
+
递归同样遵守 schema 位置纪律(`properties` 的值 / union 的分支 / `items` 的值……),
|
|
250
|
+
不碰 `enum` / `default` / `examples` 里的字面值。
|
|
251
|
+
|
|
252
|
+
### 合入时补的三处(PR 初版的漏洞)
|
|
253
|
+
|
|
254
|
+
1. **删 `not` / `const` 会删成空节点** —— 初版只把 ``{}` 当空 schema 处理,删完没复查,
|
|
255
|
+
于是 `{const:"x"}` 变成 `{}` 留在 `items` 子树里,**改写后仍然 400**(实测:
|
|
256
|
+
`items/not` 与 `items/const` 两例 raw=400 → 改写后 400)。现在删除后统一补
|
|
257
|
+
「任意值」,两例都变成 200。
|
|
258
|
+
2. **`{}` 曾被改成 `{type:"object",additionalProperties:true}`** —— 那不是等价改写,
|
|
259
|
+
是把「任意值」收紧成「只能是对象」。改成六类型 `type` 数组。
|
|
260
|
+
3. **`if` / `then` / `else` 与 tuple 形式的 `items` 没被覆盖** —— 前者在 `items` 子树里
|
|
261
|
+
实测 400,后者国际站 400;两者现在都处理。
|
|
262
|
+
|
|
263
|
+
### 验证
|
|
264
|
+
|
|
265
|
+
- `tests/body-rewrite.mjs`:**48 个断言**(初版 45 个;新增断言覆盖删空补位、
|
|
266
|
+
`if/then` 删除、null 在前的 union、tuple 形式)。
|
|
267
|
+
- 端到端(00:19~00:24 实测,那时上游还在拒):真实报障形态的 65 工具请求体
|
|
268
|
+
(58 填充 + 7 个中招的 REA 工具)**国内站** 原样 → 400、过 `rewriteRequestBody` → **200**;
|
|
269
|
+
**国际站** 原样 → 400(`tools.58…/items`)、过 `rewriteRequestBody` → **200**。
|
|
270
|
+
单点矩阵:21 个形态 × raw/改写后 × 两站,所有 `items` 子树非法形态 400 → 200,
|
|
271
|
+
顶层对照形态保持 200,合法关键字原样保留。
|
|
272
|
+
- ⚠ **上游在 00:25~00:29 之间放宽了这层校验**:同一批形态的 raw 请求随后也回 200
|
|
273
|
+
(含真实报障形态)。同一夜里 `tuple` 形式与 `if+then` 还出现过 200/400 波动。
|
|
274
|
+
⇒ 这个改写现在是**保险**,不是必需;上游随时可能收回去。
|
|
275
|
+
判「要不要改写」请看多轮统计,别信单次探针。
|
|
276
|
+
- 复跑:`_probe/items-subtree-upstream.mjs`(`[cn|intl]`)、
|
|
277
|
+
`_probe/items-subtree-stability.mjs`(`N 轮`,输出 `400 次数 / 轮数`)。
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
## 0.3.26 — 2026-10-28
|
|
281
|
+
|
|
282
|
+
**积分耗尽的 400 不再让整轮对话失败:改写成 402,让 magpie 换下一个账号**
|
|
283
|
+
|
|
284
|
+
### 症状
|
|
285
|
+
|
|
286
|
+
三个「超级麦吉」账号(余额 6,467 / 82 / 0 积分)接在同一个路由组里。余额 82 的账号
|
|
287
|
+
被会话亲和(affinity)留在回复链上,一轮对话把它烧到负数,上游回:
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
HTTP 400
|
|
291
|
+
超级麦吉: 非常抱歉,当前对话需要调用大模型 / 付费工具,您的可用积分已不足。
|
|
292
|
+
您可以点击右上角「用户头像」,通过「充值」通道补充积分,完成后即可继续对话。
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
DSH 界面显示「本轮运行失败」,magpie 的 trace 里只有两次 try —— 都在
|
|
296
|
+
`magicrew@181****710`,余额 6,467 的 `magicrew`(189****051)**一次都没被问**。
|
|
297
|
+
|
|
298
|
+
### 根因
|
|
299
|
+
|
|
300
|
+
magpie 的 `failure()` 已经能认出这条错误(`creditWords` 命中「充值」)并把
|
|
301
|
+
`try.Fail` 记成 `credit`;但 `retryable()` 的 `status == 400, 422` 分支只查
|
|
302
|
+
`quotaWords`,而 `quotaWords` 不含「积分」「充值」。于是:
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
hw.failed() == false
|
|
306
|
+
⇒ 走不到 `!last && hw.failed()` 的换账号分支
|
|
307
|
+
⇒ 不 rest、不换号、直接把 400 交给客户端
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
账号于是永远留在轮换里,下一次请求继续撞同一个错误。
|
|
311
|
+
|
|
312
|
+
> 已把 magpie 侧根因与修法报到 https://github.com/yetone/magpie/issues/4095 ——
|
|
313
|
+
> 插件这一层是绕过,不是根修。
|
|
314
|
+
|
|
315
|
+
### 修法(插件层)
|
|
316
|
+
|
|
317
|
+
`src/hooks/loader.mjs` 增加 `rewriteCreditRefusal()`:只在
|
|
318
|
+
**400/422 + 文案确认是「积分/余额不足」** 时把状态码改成 `402 Payment Required`,
|
|
319
|
+
body 原样保留。402 在 magpie 的 `retryable()` 里无条件可重试、`failure(402)` 也是
|
|
320
|
+
`failCredit`,因此会 rest 该账号 30 分钟并继续问同 provider 的下一个账号 / 组的下一个成员。
|
|
321
|
+
|
|
322
|
+
边界钉死:schema 错误的 400、参数错误的 400、200/402 响应一律原样透传,不读成功流。
|
|
323
|
+
|
|
324
|
+
### 验证
|
|
325
|
+
|
|
326
|
+
- `tests/credit-refusal.mjs`:18 个断言 —— 中文真实文案 / 英文 insufficient credits /
|
|
327
|
+
余额不足 / schema 400 不命中 / 参数 400 不命中 / 402 与 200 不碰 / content-length 与
|
|
328
|
+
content-encoding 被丢掉 / **端到端**:拦全局 fetch,`makeLoader().fetch` 收到 400 积分
|
|
329
|
+
不足时交给 magpie 的是 402,收到 schema 400 时仍是 400。
|
|
330
|
+
- `tests/body-rewrite.mjs` 36 通过、`tests/account-fallback.mjs` 10 通过。
|
|
331
|
+
- 两站共用同一 Go 服务与同一错误文案(AGENTS 坑 1),逻辑按站点无关实现。
|
|
332
|
+
|
|
3
333
|
## 0.3.25 — 2026-10-28
|
|
4
334
|
|
|
5
335
|
**0.3.24 的 schema 扫描范围收窄:只走 schema 位置**
|
package/README.md
CHANGED
|
@@ -12,29 +12,120 @@
|
|
|
12
12
|
凭证 登录一次 30 天全自动:api_key 两小时自续,refresh_token 自滚
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
>
|
|
15
|
+
> 模型清单是 2026-10-27 从两站 `GET /api/v1/flows/models` 拉下来核对的。清单会随订阅档位和平台上下架变 —— 所以插件里**没有一张写死的模型清单**,账户能选什么就显示什么。
|
|
16
16
|
|
|
17
17
|
> **改代码 / 发版本之前**:先看 [AGENTS.md](./AGENTS.md) —— 按「你在哪一步」给阅读顺序,
|
|
18
|
-
>
|
|
19
|
-
> [CHECKLIST.md](./CHECKLIST.md)
|
|
20
|
-
> 发布是**七步**,`git push` 只是第 4 步。
|
|
18
|
+
> 外加本仓库那些**会静默出错**的坑(症状 → 判据 → 出处)。交付与发版逐条走
|
|
19
|
+
> [CHECKLIST.md](./CHECKLIST.md) 的两张表。发布是**七步**,`git push` 只是第 4 步。
|
|
21
20
|
|
|
22
21
|
---
|
|
23
22
|
|
|
24
23
|
## 功能
|
|
25
24
|
|
|
26
25
|
- **两个供应商分得开** —— 国内、国际各一行,图标、名字、模型、积分、登录态全部独立
|
|
27
|
-
-
|
|
26
|
+
- **登录全程不开浏览器** —— 国际站有**邮箱验证码**和**手机号验证码**两条纯 HTTP 路;国内站有手机号验证码。另外两条(浏览器登录 / 手填登录态)谁都能用
|
|
27
|
+
- **已有的账号加进来,只填两次就完事** —— 填邮箱(或手机号)→ 填验证码。**那张「拖动滑块使图片角度为正」的旋转图根本不会出现**:服务端对已注册的账号直接放行,插件用的就是这条通道
|
|
28
|
+
- **本机浏览器登过就直接认** —— 你平时那个 Edge / Chrome / Brave 里登过 magicrew,点「浏览器登录」时插件读它的 localStorage 接上:不开窗口、不用重登
|
|
28
29
|
- **模型跟着账号走** —— 每次登录后按 `/api/v1/flows/models` 实时拉,账户当前能选什么就展示什么,生图和 embedding 不会混进来
|
|
29
30
|
- **积分算得准** —— 余额读的是 `/api/v1/quota/points/total`;空号就显示空,不会被优先派活
|
|
31
|
+
- **积分耗尽自动换号** —— 上游用 HTTP 400 报「可用积分已不足」时,插件把它改写为语义正确的 **402 Payment Required**,magpie 会 rest 这个账号并继续问同 provider 的下一个账号 / 路由组的下一个成员
|
|
30
32
|
- **登录一次管 30 天** —— 网页 JWT 顶 30 天,`mgw_` api_key 每 2 小时自续,refresh_token 每次续期时自滚
|
|
31
|
-
-
|
|
32
|
-
- **登录方式还有两条备选** —— 手机号+密码走 API;或者直接粘 JWT + 组织码
|
|
33
|
-
- **新手奖励自动领** —— 国内站 8 项里 7 项 PUT 即到账,每天跑一次幂等
|
|
33
|
+
- **新手奖励 8 项全自动** —— 国内站 8 项 500 积分全部拿得到;第 8 项(完成第一次 AI 工作)由插件替你发一条消息,花掉约 2 积分
|
|
34
34
|
- **邀请码自动滚** —— 第一次注册带作者的码,之后每次登录都记住「上一个登录账号」的码,拿到新的就覆盖旧的
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
+
## 登录
|
|
39
|
+
|
|
40
|
+
**国际站四条路,国内站三条。** 带「验证码」的那两条是**纯 HTTP、全程不开浏览器** —— 你填一个邮箱或手机号,剩下的插件自己走完。
|
|
41
|
+
|
|
42
|
+
| # | 方式 | 你要做什么 | 国际站 | 国内站 |
|
|
43
|
+
|---|---|---|---|---|
|
|
44
|
+
| ① | **邮箱验证码** | 填邮箱 → 填 6 位验证码 | 第 1 条 | —(国内站登录页没有邮箱入口) |
|
|
45
|
+
| ② | **手机号验证码** | 填手机号 → 填 6 位验证码 | 第 2 条 | 第 1 条 |
|
|
46
|
+
| ③ | **浏览器登录** | 点一下,在弹出的官方页面里用任何方式登 | 第 3 条 | 第 2 条 |
|
|
47
|
+
| ④ | **登录态(JWT + 组织码)** | 从 DevTools 粘两个字段 | 第 4 条 | 第 3 条 |
|
|
48
|
+
|
|
49
|
+
**国际站把邮箱排第一**,因为那边邮箱注册的账号更多。
|
|
50
|
+
|
|
51
|
+
**为什么国内站没有邮箱那条?** 因为国内站的官方登录页压根没有邮箱入口(只有微信 / 苹果 / 密码 / 手机 / 私有部署),也就没有邮箱注册的账号;而且同一个发信请求在国内站的服务端会回「服务异常,请联系管理员」。摆出来只会让你填完邮箱撞一个看不懂的错。
|
|
52
|
+
|
|
53
|
+
两条验证码路是同一套逻辑(插件里就是同一份代码,只是问的字段和调的两个端点不同),不会出现「手机那条修好了、邮箱那条没跟上」。
|
|
54
|
+
|
|
55
|
+
### 关于那张旋转验证图
|
|
56
|
+
|
|
57
|
+
官方登录页上只有一处需要「看图」:一张圆形照片被随机旋转过,要你拖滑块把它转正。**它只在一种情况下出现:这个手机号 / 邮箱在平台上还从没注册过。**
|
|
58
|
+
|
|
59
|
+
| 你要加的号 | 会出现旋转图吗 | 你需要做什么 |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| 平台上**已有**的账号(哪怕第一次加进 magpie) | **不会** —— 服务端直接放行 | 填号码/邮箱 + 填验证码 |
|
|
62
|
+
| 平台上**从没注册过**的新号(首次注册) | 会 | 配了识图模型就全自动;没配就自己转一下 |
|
|
63
|
+
|
|
64
|
+
原理:`POST /captcha/generate` 对已注册的账号**直接回一个 `business_token`**,连图都不给(实测:已注册邮箱 → 免图;换一个没注册过的邮箱 → 给 token + image)。插件用的就是这条通道,所以**绝大多数情况下识图模型根本用不上**。
|
|
65
|
+
|
|
66
|
+
真碰上要过图时(新号注册),插件这么办:
|
|
67
|
+
|
|
68
|
+
1. **配了识图模型**(magpie 设置 → 识图模型)→ 用它判断「主体自己的上方向指向钟面几点」,换算成拖动角度,自动过掉
|
|
69
|
+
2. **没配** → 回退到浏览器,打开官方登录页,你自己把图片转正,剩下的插件接住
|
|
70
|
+
|
|
71
|
+
> 为什么不瞎猜角度:服务端容差 ±10°,瞎猜命中率只有约 5.6%(20/360),平均要试十几次,而它有「验证提交过于频繁」和「验证次数已超限」两道风控 —— 等不到猜中,先被限流。
|
|
72
|
+
>
|
|
73
|
+
> 为什么不问模型「转了多少度」:实测直接问角度 6 次错 5 次,问「指向钟面几点」3 轮中 2。而且同一张图要问两遍比分歧 —— 有一次两个判官给出 1.7 和 7.5 钟点(相差 174°),那是有人把方向看反了,只能换图重来。
|
|
74
|
+
|
|
75
|
+
### 手机号与邮箱的协议差异(给改代码的人)
|
|
76
|
+
|
|
77
|
+
| | 手机号 | 邮箱 |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| 取图 | `POST /captcha/generate` `{account_type:"mobile", mobile, state_code}` | 同端点 `{account_type:"email", email}`(**无** `state_code`) |
|
|
80
|
+
| 过验证 | `POST /captcha/verify` `{token, angle, account_type, mobile, state_code}` | 同端点,换成 `email` |
|
|
81
|
+
| 发验证码 | `POST /v4/user/send-sms` `{type:"account_login", phone, state_code}` | `POST /v4/user/send-email` `{email, type:"account_login"}` |
|
|
82
|
+
| 换登录态 | `POST /v4/user/login`,`type:"phone_captcha"`,验证码字段叫 **`code`** | `POST /v4/user/login-email`,验证码字段叫 **`captcha`** |
|
|
83
|
+
|
|
84
|
+
两个 `type` 都是 `"account_login"`(填错会回「服务异常,请联系管理员」—— 后端内部炸了,从报错完全看不出是它)。邮箱那条**必须**走 `login-email`:往 `/v4/user/login` 发任何 type 都回「phone 字段是必须的」。
|
|
85
|
+
|
|
86
|
+
### 浏览器登录
|
|
87
|
+
|
|
88
|
+
点第三条第 ③ 个方式时,它先看你平时用的那个浏览器:
|
|
89
|
+
|
|
90
|
+
**① 那边已经登录过 magicrew** → 插件直接读它的 localStorage 接上,**不开窗口、不用再登一次**。
|
|
91
|
+
凭据就明文躺在 `Local Storage/leveldb` 里(Edge / Chrome / Chromium / Brave / 360 / QQ / 搜狗 都行)。
|
|
92
|
+
|
|
93
|
+
**② 没登录过,或者这个账号已经加过** → 插件自己拉起一个窗口:
|
|
94
|
+
|
|
95
|
+
1. 浏览器窗口弹出,停在官方登录页
|
|
96
|
+
2. 用你平时的方式登进去(手机号 / 密码 / Google / 邮箱验证码 / 飞书 / 钉钉都行)
|
|
97
|
+
3. **登录完成那一刻它自己就接上了** —— 不用回来点任何按钮,不用开 DevTools
|
|
98
|
+
|
|
99
|
+
原理:插件不是调用系统浏览器,而是自己起一个 Chromium 内核的浏览器(独立 profile + `--remote-debugging-port=0`),所以登录成功后能用 CDP 在页面上下文里把它自己的 localStorage 读走,拿到 JWT 和组织码,签完密钥就关窗。
|
|
100
|
+
|
|
101
|
+
**用哪个浏览器?** 按「谁更干净」排,不是按名气:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
① Chrome / Chromium 自己装的,无企业策略、无强制登录 ← 首选
|
|
105
|
+
② 国产 Chromium 系 360 / QQ / 搜狗 / 2345 / 猎豹 / UC / 夸克
|
|
106
|
+
③ Edge 垫底(策略最多、最容易被拦住调试端口)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
前一个起不来(策略禁用调试端口、启动就退出)会自动换下一个,**第一个真正打开调试通道的才算数**。窗口标题/提示里会写明这次用的是哪个。
|
|
110
|
+
|
|
111
|
+
**每次登录都是一个新的 profile,用完就删。** 这是必须的:共用一个 profile 的话,添加第二个账号时窗口一打开,页面里已经躺着上一个账号的凭据 —— 插件会以为登录完成、立刻关窗,而 magpie 拿到的还是旧账号。
|
|
112
|
+
|
|
113
|
+
> **为什么零浏览器那两条要自己写协议?** magicrew 没有 OAuth 回调通道 —— 登录类型只有十几种、全是「拿凭据换 token」,没有设备码、没有插件授权、没有一次性 ticket。凭据只可能落在三个地方:用户中心的 HTTP 接口(插件走的就是这条)、你自己浏览器的 localStorage、或者插件自己起的那个窗口。
|
|
114
|
+
|
|
115
|
+
### 登录态(JWT + 组织码)
|
|
116
|
+
|
|
117
|
+
不想走上面任何一条的话,手填两个字段:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
DevTools → Application → Local Storage → https://www.magicrew.ai
|
|
121
|
+
MAGIC:user:authorization → JSON,取 value = JWT
|
|
122
|
+
MAGIC:user:organizationCode → JSON,取 value = 组织码(12 位小写字母数字)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> 组织码**不能从 JWT 推**。JWT 里只有 `cus.tenant_id`(18 位数字),组织码是另一套短串。缺了它 `/api/v1/*` 回 `2182 用户所在组织不存在`,给错了回 `3035 登录失败`。
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
38
129
|
## 模型
|
|
39
130
|
|
|
40
131
|
**国内的 GLM、DeepSeek、Qwen、Kimi、MiniMax、MiMo、豆包;国际站再加 Claude、GPT、Gemini 三家。**
|
|
@@ -69,11 +160,27 @@
|
|
|
69
160
|
|
|
70
161
|
## 新手奖励
|
|
71
162
|
|
|
72
|
-
|
|
163
|
+
**8 项,合计 500 积分,全自动 —— 登录后和每天签到各跑一次,幂等。**
|
|
73
164
|
|
|
74
|
-
|
|
165
|
+
| 项 | 分数 | 插件怎么拿 |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| 完成第一次 AI 工作 | 100 | **替你在话题里发一条消息**(花掉约 2 积分)—— 服务端只认这个 |
|
|
168
|
+
| 了解工作台 | 50 | PUT 即到账 |
|
|
169
|
+
| 了解如何使用 AI PPT | 50 | PUT 即到账 |
|
|
170
|
+
| AI 录音 | 50 | PUT 即到账 |
|
|
171
|
+
| 雇佣数字员工 | 50 | PUT 即到账 |
|
|
172
|
+
| 添加 / 管理技能 | 50 | PUT 即到账 |
|
|
173
|
+
| 微应用 | 50 | PUT 即到账 |
|
|
174
|
+
| Magiclaw | 50 | PUT 即到账 |
|
|
75
175
|
|
|
76
|
-
|
|
176
|
+
第 8 项是唯一 PUT 不掉的:`PUT /onboarding/tasks/first_ai_work/completion` 服务端回 `9303「该新人引导任务不支持手动完成」`—— 它认死产品内真的跑完过一次对话。所以插件替它**发一条消息**(走话题的 socket 协议,不开浏览器),这也是**邀请奖励的触发条件** —— 被邀请人发过一条消息,邀请人才拿到那笔分。
|
|
177
|
+
|
|
178
|
+
这道「发消息」有闸门:**每个账号一辈子一次**(`isAutoChatSettled`),发出去就落永久标记。所以重复登录、换天签到、重启 magpie 都不会再发第二条、不会再白烧积分。
|
|
179
|
+
|
|
180
|
+
失败方向是「宁可晚一轮」:查不到判据(比如话题接口抖了一下)时这次不发、不落标记、不计数 —— 消息发出去是花钱且不可撤的。真失败才计数,3 次之后放弃,免得一个永远失败的号每次签到撞一遍。
|
|
181
|
+
|
|
182
|
+
> 国际站 `/api/v1/onboarding/tasks` 恒返回空列表 —— 那是「这个账号没被登记进引导计划」,不是「已经领完了」。
|
|
183
|
+
> 但邀请奖励的判据是一样的:官方文案写「被邀请人完成首个任务或对话后,即可获得积分奖励」,所以国际站这条就等于**发过一条消息**。插件用「有没有话题」判断(不是有没有项目 —— 项目打开工作台就会自动建),没话题就补一条。
|
|
77
184
|
|
|
78
185
|
---
|
|
79
186
|
|
|
@@ -96,7 +203,7 @@
|
|
|
96
203
|
```
|
|
97
204
|
|
|
98
205
|
> ③ 不是每次都能成。`/api/v1/invitations/info` 只认带 `device_id` 的 JWT —— 同一个账号同一时刻签的两把,差别就在这一个字段:桌面客户端 438 字符(`device_id` = 64 位 hex)能拿到码,网页版 350 字符(`device_id` 空)回 `2155 用户不存在`。
|
|
99
|
-
> 密码登录走 API、带 device
|
|
206
|
+
> 密码登录走 API、带 device 指纹,滚得动;浏览器登录和验证码登录拿到的是网页那把,滚不动 —— 那就保留旧值,不报错、不挡登录。
|
|
100
207
|
|
|
101
208
|
奖励口径(上游前端常量,2026-10-27):
|
|
102
209
|
|
|
@@ -111,53 +218,11 @@
|
|
|
111
218
|
|
|
112
219
|
在 magpie 的 **插件 → 发现** 搜 `magicrew`,点**安装**(npm 上的包名是 `magicrew-auth`)。
|
|
113
220
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
**① 那边已经登录过 magicrew** → 插件直接读它的 localStorage 接上,**不开窗口、不用再登一次**。
|
|
117
|
-
凭据就明文躺在 `Local Storage/leveldb` 里(Edge / Chrome / Chromium / Brave / 360 / QQ / 搜狗 都行)。
|
|
118
|
-
|
|
119
|
-
**② 没登录过,或者这个账号已经加过** → 插件自己拉起一个窗口:
|
|
120
|
-
|
|
121
|
-
1. 浏览器窗口弹出,停在官方登录页
|
|
122
|
-
2. 用你平时的方式登进去(手机号 / 密码 / Google / 邮箱验证码 / 飞书 / 钉钉都行)
|
|
123
|
-
3. **登录完成那一刻它自己就接上了** —— 不用回来点任何按钮,不用开 DevTools
|
|
124
|
-
|
|
125
|
-
原理:插件不是调用系统浏览器,而是自己起一个 Chromium 内核的浏览器(独立 profile +
|
|
126
|
-
`--remote-debugging-port=0`),所以登录成功后能用 CDP 在页面上下文里把它自己的
|
|
127
|
-
localStorage 读走,拿到 JWT 和组织码,签完密钥就关窗。
|
|
128
|
-
|
|
129
|
-
**用哪个浏览器?** 按「谁更干净」排,不是按名气:
|
|
130
|
-
|
|
131
|
-
```
|
|
132
|
-
① Chrome / Chromium 自己装的,无企业策略、无强制登录 ← 首选
|
|
133
|
-
② 国产 Chromium 系 360 / QQ / 搜狗 / 2345 / 猎豹 / UC / 夸克
|
|
134
|
-
③ Edge 垫底(策略最多、最容易被拦住调试端口)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
前一个起不来(策略禁用调试端口、启动就退出)会自动换下一个,
|
|
138
|
-
**第一个真正打开调试通道的才算数**,不用你操心。
|
|
139
|
-
窗口标题/提示里会写明这次用的是哪个。
|
|
140
|
-
|
|
141
|
-
**每次登录都是一个新的 profile,用完就删。** 这是必须的:共用一个 profile 的话,
|
|
142
|
-
添加第二个账号时窗口一打开,页面里已经躺着上一个账号的凭据 —— 插件会以为登录完成、
|
|
143
|
-
立刻关窗,而 magpie 拿到的还是旧账号(同账号 → 替换,不新增)。
|
|
144
|
-
|
|
145
|
-
> **为什么两条路都要?** magicrew 没有 OAuth 回调通道 —— 登录类型只有 13 种、全是「拿凭据换 token」,没有设备码、没有插件授权、没有一次性 ticket。凭据只可能落在两个地方:你自己浏览器的 localStorage(插件读文件就能拿到,所以优先),或者插件自己起的那个窗口(读不到时才需要)。
|
|
146
|
-
>
|
|
147
|
-
> 本机没有任何 Chromium 内核浏览器时自动降级:改用系统浏览器打开,登录完把凭证送进剪贴板,插件从剪贴板读。
|
|
148
|
-
|
|
149
|
-
不想碰 Console 的话,另外两条:
|
|
150
|
-
|
|
151
|
-
- **密码登录** —— 手机号 + 密码 + 区号,走 API,全程在 magpie 里
|
|
152
|
-
- **登录态(JWT + 组织码)** —— 手填两个字段:
|
|
221
|
+
装完选站点,然后挑一条登录路:
|
|
153
222
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
MAGIC:user:organizationCode → JSON,取 value = 组织码(12 位小写字母数字)
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
> 组织码**不能从 JWT 推**。JWT 里只有 `cus.tenant_id`(18 位数字),组织码是另一套短串。缺了它 `/api/v1/*` 回 `2182 用户所在组织不存在`,给错了回 `3035 登录失败`。
|
|
223
|
+
- **最省事(推荐)** → 点**「浏览器登录」**:它先看你本机浏览器有没有登过,没有就自己拉一个窗口起来,你在里面用任何方式登(连验证码都不用填)
|
|
224
|
+
- **国际站全自动登录** → 点**「邮箱验证码」**(排第一):填邮箱 → 填验证码。已有账号的话,中间那张旋转图不会出现
|
|
225
|
+
- **国内站全自动登录** → 点**「手机号验证码」**(排第一):同理
|
|
161
226
|
|
|
162
227
|
### 命令行
|
|
163
228
|
|
|
@@ -189,6 +254,14 @@ magpie plugin list
|
|
|
189
254
|
|
|
190
255
|
**两个站要装两次吗?** 不用。一个包导出两个插件,装一次两个供应商都在,各自登录。
|
|
191
256
|
|
|
257
|
+
**邮箱验证码能注册新号吗?** 能 —— 但**只有国际站有这条方法**。这条链和手机号那条一样带 `auto_register`,第一次用的邮箱会自动建号,而且注册请求里带着邀请码。新号注册那一次会要求过旋转图(服务端的规定)—— 配了识图模型就全自动,没配就自己转一下。
|
|
258
|
+
|
|
259
|
+
**为什么国内站没有「邮箱验证码」?** 因为国内站的官方登录页就没有邮箱入口(只有微信 / 苹果 / 密码 / 手机 / 私有部署),也就没有邮箱注册的账号;而且同一个发信请求在国内站服务端回「服务异常,请联系管理员」。所以国内站只给三条路:手机号验证码 / 浏览器登录 / 手填登录态。
|
|
260
|
+
|
|
261
|
+
**旋转验证图能不能不出现?** 加**已有**的账号时它本来就不出现(服务端直接放行)。只有注册全新账号才需要过它。
|
|
262
|
+
|
|
263
|
+
**看图那一步为什么有点慢?** 因为识图模型是**推理模型**的话,它的「思考」也算 token —— 实测这个模型的思考链能写到三千多字。插件已经做了两件事:让它跑最低思考档(`reasoning_effort: low`,实测耗时 −59%、token −57%),以及**两个判官并行**问(耗时等于较慢的那一次)。想更快,就在 magpie 设置里换一个**非推理**的视觉模型 —— 那是唯一能真正「不思考」的办法,参数关不掉。
|
|
264
|
+
|
|
192
265
|
**我在自己浏览器里登了 magicrew,插件能自动认出来吗?** 能。点「浏览器登录」时插件先去本机 Edge / Chrome / Chromium / Brave / 360 / QQ / 搜狗 等浏览器的 localStorage 里找**这个站点**的登录态,找到就直接接上,不弹窗口。读不到、或者这个账号已经加过,才会开窗口让你登新的。
|
|
193
266
|
|
|
194
267
|
**点了「浏览器登录」,网页上明明登好并跳到 agent 界面了,magpie 里却一直转圈等?** 0.3.12 及更早会这样:插件拉起浏览器的判断依赖一个写死的文件名 `msedge.exe`,而有些机器上微软落盘的名字是 **`Microsoft Edge.exe`**(带空格、带大写;落盘名随安装方式和版本变),于是每次都判定「本机没装浏览器」,永远走「让你去 Console 粘一行」的降级路径 —— 而你不知道有这一步。0.3.15 起:两种落盘名都认、把 360 / QQ / 搜狗 等国产 Chromium 也纳入候选、并且逐个试启动(第一个真能开调试通道的才用)。判据在 `~/.cache/magpie/magicrew-login-<站点>.log`,出问题先看它最后一行。
|
|
@@ -197,6 +270,10 @@ magpie plugin list
|
|
|
197
270
|
|
|
198
271
|
**npm 上叫 `magicrew-auth`,为什么卡片显示 `Magicrew`?** 卡片标题取包内 `package.json` 的 `name`(`Magicrew`),而 npm 拒绝含大写字母的新包名。发布时用 `publish-npm.mjs` 临时换成 `magicrew-auth`、发完还原 —— 所以 npm 页面是小写包名,git 源装出来是 `Magicrew`。功能完全一样。
|
|
199
272
|
|
|
273
|
+
**验证码登录报「验证码已发送」但没收到?** 先看是不是发到别的账号上了(同一个手机号/邮箱在两个站点是两套账号)。再确认这个号是不是**还没注册** —— 那种情况要先过旋转图,图没过,短信 / 邮件根本不会发出去,界面会直接报「旋转验证连试 4 张图都没过」。
|
|
274
|
+
|
|
275
|
+
**一个账号积分用完了,会换到另一个还有积分的账号吗?** 会。0.3.26 起插件把上游的 400「可用积分已不足」改写成 402 Payment Required —— magpie 对 402 一律视为可重试:先 rest 这个账号 30 分钟,再问同 provider 的下一个账号;如果整个组都没有额度,才会把错误交给客户端。
|
|
276
|
+
|
|
200
277
|
**为什么模型列表会变?** 因为清单是从 `/api/v1/flows/models` 实时拉的。订阅档位变了、平台方上下架变了,清单就跟着变 —— 插件里没有需要维护的模型表。
|
|
201
278
|
|
|
202
279
|
**国际站 `/v1/chat/completions` 报 500,22 字节的纯文本?** 那是缺 `organization-code` 请求头。国内站不需要,国际站强制。插件按站点自动带。
|