magicrew-auth 0.3.33 → 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 +159 -159
- package/CHANGELOG.md +14 -0
- package/index.mjs +114 -109
- package/package.json +1 -1
- package/src/store.mjs +625 -588
package/AGENTS.md
CHANGED
|
@@ -1,159 +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.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
|
|
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. 判据要能当场验证(命令、字段、界面现象),写不成判据的是感想。
|
|
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,19 @@
|
|
|
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
|
+
|
|
3
17
|
## 0.3.33 — 2026-10-29
|
|
4
18
|
|
|
5
19
|
**批量:登录窗口去重 · key 自愈 / 413 人话 · 「超级麦吉设置」页面 · 死号回退**
|
package/index.mjs
CHANGED
|
@@ -1,109 +1,114 @@
|
|
|
1
|
-
// Magicrew · 超级麦吉 / MagiCrew —— magpie 供应商插件
|
|
2
|
-
//
|
|
3
|
-
// ═══ 这是一块「框」,不是插头 ═════════════════════════════════════════
|
|
4
|
-
//
|
|
5
|
-
// magpie 是主板:它规定了九个针脚,能力(调度、账号池、续期、UI、路由、
|
|
6
|
-
// 流式转发)全在主板那边。插件要做的只是把**自己那一份**填进去。
|
|
7
|
-
//
|
|
8
|
-
// 所以这里不实现任何东西 —— 每个针脚是一个独立的小模块:
|
|
9
|
-
//
|
|
10
|
-
// src/hooks/config.mjs ① config(cfg) 声明 provider + 模型表
|
|
11
|
-
// src/hooks/methods.mjs ② auth.methods[] 登录
|
|
12
|
-
// src/hooks/refresh.mjs ③ auth.refresh(auth) 续期
|
|
13
|
-
// src/hooks/loader.mjs ④ auth.loader(getAuth) 请求接管
|
|
14
|
-
// src/hooks/usage.mjs ⑤ auth.usage(getAuth) 额度
|
|
15
|
-
// src/hooks/checkin.mjs ⑥ auth.checkin(getAuth) 签到
|
|
16
|
-
// src/hooks/models.mjs ⑦ provider.models() 每账号模型目录
|
|
17
|
-
// src/hooks/headers.mjs ⑧ "chat.headers"() 每请求加头
|
|
18
|
-
//
|
|
19
|
-
// 每个模块只读一个 c(站点上下文,见 src/context.mjs),互不依赖:
|
|
20
|
-
// 一个坏了,其余照常 —— 按需加载的那两个(browser-login / tasks)尤其如此。
|
|
21
|
-
//
|
|
22
|
-
// 想只做一半功能?把对应那行从下面的 return 里删掉就行,不用改别处。
|
|
23
|
-
//
|
|
24
|
-
// ═══ 一个包,两个供应商 ═══════════════════════════════════════════════
|
|
25
|
-
//
|
|
26
|
-
// export const MagicrewAuthPlugin = buildPlugin(siteCtx(SITES.cn))
|
|
27
|
-
// export const MagicrewIntlAuthPlugin = buildPlugin(siteCtx(SITES.intl))
|
|
28
|
-
//
|
|
29
|
-
// magpie 把包里每个具名导出当成一个独立插件,所以它们在供应商列表、用量概览、
|
|
30
|
-
// 模型目录里各占一行,不聚合、不互相覆盖登录态。
|
|
31
|
-
|
|
32
|
-
import { SITES, siteCtx, REFRESH_LEAD_MS } from "./src/context.mjs"
|
|
33
|
-
import { makeConfig } from "./src/hooks/config.mjs"
|
|
34
|
-
import { syncInvitePage } from "./src/store.mjs"
|
|
35
|
-
import { makeMethods } from "./src/hooks/methods.mjs"
|
|
36
|
-
import { makeRefresh } from "./src/hooks/refresh.mjs"
|
|
37
|
-
import { makeLoader } from "./src/hooks/loader.mjs"
|
|
38
|
-
import { makeUsage } from "./src/hooks/usage.mjs"
|
|
39
|
-
import { makeCheckin } from "./src/hooks/checkin.mjs"
|
|
40
|
-
import { makeModels } from "./src/hooks/models.mjs"
|
|
41
|
-
import { makeHeaders } from "./src/hooks/headers.mjs"
|
|
42
|
-
|
|
43
|
-
/** 一个站点进,一个完整插件出 */
|
|
44
|
-
function buildPlugin(c) {
|
|
45
|
-
return async (input, options = {}) => {
|
|
46
|
-
|
|
47
|
-
// 本插件保存的 options(页面写的 inviteBySite / inviteLockBySite 都在这里)。
|
|
48
|
-
// ⚠ 2026-10-29 插桩实测:官方契约是工厂的**第二个参数**(HOOKS.md 一、
|
|
49
|
-
// MULTI-PROVIDER.md L156:`(ctx, options) => ({...})`),且它就是
|
|
50
|
-
// plugins.json 里那份完整 options。
|
|
51
|
-
// 而 `input.magpie.options(provider)` 实测**永远返回 {}** —— 作者原来
|
|
52
|
-
// 的 getLock(以及照抄的页面设置读取)都因此失效:登录流程永远读不到
|
|
53
|
-
// 页面设置,只能回落滚动码。这里以第二参数为准。
|
|
54
|
-
const getOpts = () => (options && typeof options === "object" ? options : {})
|
|
55
|
-
|
|
56
|
-
const getLock = () => {
|
|
57
|
-
const vals = getOpts()
|
|
58
|
-
// 页面按站点写 inviteLockBySite;旧的全局 inviteLock 作为后备
|
|
59
|
-
const by = vals?.inviteLockBySite
|
|
60
|
-
if (by && typeof by === "object" && Object.prototype.hasOwnProperty.call(by, c.PROVIDER)) {
|
|
61
|
-
return by[c.PROVIDER] !== false
|
|
62
|
-
}
|
|
63
|
-
if (Object.prototype.hasOwnProperty.call(vals, "inviteLock")) return vals.inviteLock !== false
|
|
64
|
-
return true
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 卡片标题与文案不在这里做 —— 那是 registry 条目的职责。
|
|
68
|
-
// 插件不写 magpie 自己的缓存目录:那份文件属于 magpie,插件只读不写。
|
|
69
|
-
return {
|
|
70
|
-
// 页面清单(page/codes.js)在 config 时刷新 —— 放这里而不是 buildPlugin:
|
|
71
|
-
// 测试会直接调 buildPlugin,不能让测试往仓库里写生成物
|
|
72
|
-
config: (cfg) => {
|
|
73
|
-
try {
|
|
74
|
-
syncInvitePage()
|
|
75
|
-
} catch {}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
export const
|
|
1
|
+
// Magicrew · 超级麦吉 / MagiCrew —— magpie 供应商插件
|
|
2
|
+
//
|
|
3
|
+
// ═══ 这是一块「框」,不是插头 ═════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// magpie 是主板:它规定了九个针脚,能力(调度、账号池、续期、UI、路由、
|
|
6
|
+
// 流式转发)全在主板那边。插件要做的只是把**自己那一份**填进去。
|
|
7
|
+
//
|
|
8
|
+
// 所以这里不实现任何东西 —— 每个针脚是一个独立的小模块:
|
|
9
|
+
//
|
|
10
|
+
// src/hooks/config.mjs ① config(cfg) 声明 provider + 模型表
|
|
11
|
+
// src/hooks/methods.mjs ② auth.methods[] 登录
|
|
12
|
+
// src/hooks/refresh.mjs ③ auth.refresh(auth) 续期
|
|
13
|
+
// src/hooks/loader.mjs ④ auth.loader(getAuth) 请求接管
|
|
14
|
+
// src/hooks/usage.mjs ⑤ auth.usage(getAuth) 额度
|
|
15
|
+
// src/hooks/checkin.mjs ⑥ auth.checkin(getAuth) 签到
|
|
16
|
+
// src/hooks/models.mjs ⑦ provider.models() 每账号模型目录
|
|
17
|
+
// src/hooks/headers.mjs ⑧ "chat.headers"() 每请求加头
|
|
18
|
+
//
|
|
19
|
+
// 每个模块只读一个 c(站点上下文,见 src/context.mjs),互不依赖:
|
|
20
|
+
// 一个坏了,其余照常 —— 按需加载的那两个(browser-login / tasks)尤其如此。
|
|
21
|
+
//
|
|
22
|
+
// 想只做一半功能?把对应那行从下面的 return 里删掉就行,不用改别处。
|
|
23
|
+
//
|
|
24
|
+
// ═══ 一个包,两个供应商 ═══════════════════════════════════════════════
|
|
25
|
+
//
|
|
26
|
+
// export const MagicrewAuthPlugin = buildPlugin(siteCtx(SITES.cn))
|
|
27
|
+
// export const MagicrewIntlAuthPlugin = buildPlugin(siteCtx(SITES.intl))
|
|
28
|
+
//
|
|
29
|
+
// magpie 把包里每个具名导出当成一个独立插件,所以它们在供应商列表、用量概览、
|
|
30
|
+
// 模型目录里各占一行,不聚合、不互相覆盖登录态。
|
|
31
|
+
|
|
32
|
+
import { SITES, siteCtx, REFRESH_LEAD_MS } from "./src/context.mjs"
|
|
33
|
+
import { makeConfig } from "./src/hooks/config.mjs"
|
|
34
|
+
import { syncInvitePage, backfillInviteCodes } from "./src/store.mjs"
|
|
35
|
+
import { makeMethods } from "./src/hooks/methods.mjs"
|
|
36
|
+
import { makeRefresh } from "./src/hooks/refresh.mjs"
|
|
37
|
+
import { makeLoader } from "./src/hooks/loader.mjs"
|
|
38
|
+
import { makeUsage } from "./src/hooks/usage.mjs"
|
|
39
|
+
import { makeCheckin } from "./src/hooks/checkin.mjs"
|
|
40
|
+
import { makeModels } from "./src/hooks/models.mjs"
|
|
41
|
+
import { makeHeaders } from "./src/hooks/headers.mjs"
|
|
42
|
+
|
|
43
|
+
/** 一个站点进,一个完整插件出 */
|
|
44
|
+
function buildPlugin(c) {
|
|
45
|
+
return async (input, options = {}) => {
|
|
46
|
+
|
|
47
|
+
// 本插件保存的 options(页面写的 inviteBySite / inviteLockBySite 都在这里)。
|
|
48
|
+
// ⚠ 2026-10-29 插桩实测:官方契约是工厂的**第二个参数**(HOOKS.md 一、
|
|
49
|
+
// MULTI-PROVIDER.md L156:`(ctx, options) => ({...})`),且它就是
|
|
50
|
+
// plugins.json 里那份完整 options。
|
|
51
|
+
// 而 `input.magpie.options(provider)` 实测**永远返回 {}** —— 作者原来
|
|
52
|
+
// 的 getLock(以及照抄的页面设置读取)都因此失效:登录流程永远读不到
|
|
53
|
+
// 页面设置,只能回落滚动码。这里以第二参数为准。
|
|
54
|
+
const getOpts = () => (options && typeof options === "object" ? options : {})
|
|
55
|
+
|
|
56
|
+
const getLock = () => {
|
|
57
|
+
const vals = getOpts()
|
|
58
|
+
// 页面按站点写 inviteLockBySite;旧的全局 inviteLock 作为后备
|
|
59
|
+
const by = vals?.inviteLockBySite
|
|
60
|
+
if (by && typeof by === "object" && Object.prototype.hasOwnProperty.call(by, c.PROVIDER)) {
|
|
61
|
+
return by[c.PROVIDER] !== false
|
|
62
|
+
}
|
|
63
|
+
if (Object.prototype.hasOwnProperty.call(vals, "inviteLock")) return vals.inviteLock !== false
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 卡片标题与文案不在这里做 —— 那是 registry 条目的职责。
|
|
68
|
+
// 插件不写 magpie 自己的缓存目录:那份文件属于 magpie,插件只读不写。
|
|
69
|
+
return {
|
|
70
|
+
// 页面清单(page/codes.js)在 config 时刷新 —— 放这里而不是 buildPlugin:
|
|
71
|
+
// 测试会直接调 buildPlugin,不能让测试往仓库里写生成物
|
|
72
|
+
config: (cfg) => {
|
|
73
|
+
try {
|
|
74
|
+
syncInvitePage()
|
|
75
|
+
} catch {}
|
|
76
|
+
// 老用户升级上来时码缓存可能是空的:延迟补一次(只补缺口,失败静默)
|
|
77
|
+
try {
|
|
78
|
+
const t = setTimeout(() => backfillInviteCodes().catch(() => {}), 20_000)
|
|
79
|
+
t.unref?.()
|
|
80
|
+
} catch {}
|
|
81
|
+
return makeConfig(c)(cfg)
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
auth: {
|
|
85
|
+
provider: c.PROVIDER,
|
|
86
|
+
icon: c.ICON,
|
|
87
|
+
maxConcurrency: 4,
|
|
88
|
+
refreshLead: REFRESH_LEAD_MS,
|
|
89
|
+
|
|
90
|
+
methods: makeMethods(c, getLock, getOpts),
|
|
91
|
+
refresh: makeRefresh(c),
|
|
92
|
+
loader: makeLoader(c),
|
|
93
|
+
usage: makeUsage(c),
|
|
94
|
+
checkin: makeCheckin(c),
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
provider: {
|
|
98
|
+
id: c.PROVIDER,
|
|
99
|
+
models: makeModels(c),
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
"chat.headers": makeHeaders(c),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ═══ 两个供应商,各占一行 ═════════════════════════════════════════════
|
|
108
|
+
|
|
109
|
+
export const MagicrewAuthPlugin = buildPlugin(siteCtx(SITES.cn))
|
|
110
|
+
export const MagicrewIntlAuthPlugin = buildPlugin(siteCtx(SITES.intl))
|
|
111
|
+
|
|
112
|
+
// 供测试引用(⚠ 入口文件不要导出别的「像插件的对象」,否则会被 magpie 当插件加载)
|
|
113
|
+
export { SITES }
|
|
114
|
+
export const _internal = { buildPlugin, siteCtx, SITES }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magicrew-auth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"description": "超级麦吉 / MagiCrew for magpie and OpenCode — one package, two providers: letsmagic.cn (国内) and magicrew.ai (international). Password or web-JWT sign-in, the account's own live model list, points balance, newcomer rewards, invite codes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|