Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,32 @@ SSO 路径(`signInWithBaizhiSession`)保持可用。

### 0.7 国际版网页登录必须能成功,且列出模型

**现状(2026-11-04,未修复)**:用户在国际版点网页登录,界面报
**现状(2026-11-04,已定位并修复,待真机二次确认)**:用户在国际版点网页登录,界面报
**「MonkeyCode 国际版 已登录,但 magpie 无法列出它,请反馈此问题」**。

**根因(已确认,是宿主契约不是本插件猜测)**:magpie 插件宿主的 `save()` 只有两条出口 ——
`if ("refresh" in result) setAuth(oauth)` / `else if ("key" in result) setAuth(api)`。
本插件所有网页登录都返回 `{type:"success", access, expires, accountId, uid}`:没有 `refresh`
也没有 `key`,**两条都不命中 ⇒ 不保存、也不报错**。magpie 于是显示「已登录」,而
`plugin-auth.json` 里没有记录,`providers?wait=1` 里就没有这个 account,界面打这句提示。
为什么国内站没暴露:它一直是 **API Key** 方式登录(`type: "api"`,走 api 分支),
一次网页登录都没真正落过盘。修法见 `src/credential.mjs` 的 `signInSuccess()` 与
`tests/signin-contract.test.mjs`(裸 `type: "success"` 已被单测禁止)。

**已知事实**:

- 国际站登录页只有三种方式:**GitHub / Google / 账号密码**(2026-11-04 无头读 DOM:
文案 `GitHub 登录` / `Google 登录` / `账号密码登录`,另有「当前访问的是国际版」提示)
- `https://monkeycode-ai.net/` → 200;`/login` → 200 但**不返 Set-Cookie**(SPA,cookie 由 JS 下发)
- **国际站不认百智云 SSO**:`GET /api/v1/users/login` 302 到它**自己的** `/oauth/authorize`
(`client_id=` 为空,不经过 `baizhi.cloud`);把百智云会话注入窗口走一遍只落到国际站首页,
不会签发 session ⇒ 国际站**不提供**「用百智云账号开通」入口
- 纯 API 的两条:`POST /api/v1/users/password-login`(`{email,password,captcha_token}`,
缺 token 回 **403**;`POST /api/v1/public/captcha/challenge` 回 201,形状与国内站一致)
- `https://monkeycode-ai.net/api/v1/users/models` 无 cookie / 国内 cookie → **401**
- 国内站同一路径 → 200

**因此根因只可能是这两类之一**,改之前先分清:
**当初的排查框架(保留,别拿它当结论)**:根因不是下面这两类,是宿主「两条出口都不命中」的保存契约。

1. **会话没拿到**:窗口里的登录没真正完成(cookie 名不同,或 `accept()` 太早返回)
2. **会话拿到了但目录拉不到**:`catalog` 打错了 baseUrl / 路径,或 `.net` 的模型接口不是
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
> 每个版本一节,**一条一行**,说用户能感知的变化。
> 格式与规矩见 [UPDATING.md](./UPDATING.md)。

## 0.2.36 — 2026-11-04

**「已登录,但 magpie 无法列出它」的真因找到了:登录结果少了 `refresh`,magpie 一格都没存**

- 根因(不是猜测,是宿主契约):magpie 的插件宿主 `save()` 只有两条出口 —— `if ("refresh" in result) setAuth(oauth)` 或 `else if ("key" in result) setAuth(api)`。本插件所有网页登录返回的都是 `{type:"success", access, expires, accountId, uid}`:**没有 `refresh`,也没有 `key`**,两条都不命中 ⇒ 宿主既不保存也不报错,magpie 照样显示「已登录」,账号却没落盘。紧接着的 `providers?wait=1` 里找不到这个 account,界面就打「{user} 已登录,但 magpie 无法列出它,请反馈此问题」
- 为什么以前没暴露:国内站与百智云一直是用 **API Key** 方式登录的(`plugin-auth.json` 里 `type: "api"`),走的是 api 分支;**一次网页登录都没真正存下来过**。国际站只能用网页 / GitHub / Google / 密码登录,于是第一个撞上
- 修法:新增 `signInSuccess()` 统一拼 oauth 登录结果(`refresh` 与 `access` 同值、`expires: 0`),五处 success 全部改走它;**单测锁住「生产代码里不许再出现裸的 `type: "success"`」**,下一个登录方式不会原样复制这个静默失败
- 国际站的登录方式实测(无头浏览器读 DOM):**GitHub / Google / 账号密码**三种,页面明确写「当前访问的是国际版」。两条纯 API 的路仍在:账号密码登录(captcha 形状与国内站一致,`{email,password}` 不带 token 回 403 而不是 400)+ 桌面客户端导入
- **国际站删掉「用百智云账号开通」**:实测 `GET https://monkeycode-ai.net/api/v1/users/login` 302 到它**自己的** `/oauth/authorize`(`client_id=` 是空的,不经过 `baizhi.cloud`);把百智云会话注入那个窗口再走一遍,最终只落到国际站首页,**不会**签发 `monkeycode_ai_session`。留着只会让人对着一个必然超时的入口等 240 秒
- 「从本机客户端导入」**改按域读 cookie,并恢复给国际站**(0.2.33 删它是因为当时桌面端指向国内站):桌面客户端的 `mc_base_url` 是国内站 / 国际站二选一的一个值,`monkeycode-cookies.json` 里装的是**当前那一站**的会话。现在按本站 host 过滤,读到另一站的登录态时会明确说「桌面客户端登录的是另一个站点」,而不是让上游 401 把人引去怀疑「登录过期」
- `AGENTS.md` 0.7 从「未修复」改为修复记录(保留原判据);0.6 补上国际站登录方式与「不认百智云 SSO」的实测结论
- 新增 `node scripts/check-intl-signin.mjs`:把 AGENTS 0.7 的两条判据做成一条命令 —— 看 `plugin-auth.json` 里有没有 `monkeycodeintl` 的记录(含 uid),再看本机网关 `/v1/models` 里有没有 `monkeycodeintl/…`。只读、不发写请求;可选 `--cookie '…'` 验一次平台会话。退出码 2 = 有判据不成立

## 0.2.35 — 2026-11-04

**百智云也能用手机号 + 验证码登录了:两个请求,不开窗口**
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ magpie plugin list

## 常见问题

**「从本机客户端导入」说没读到登录态?** 桌面版要**先登录成功**才会把会话写到本机。打开桌面版登一次,再回来点。
**「从本机客户端导入」说没读到登录态?** 桌面版要**先登录成功**才会把会话写到本机。打开桌面版登一次,再回来点。桌面版的服务器是**国内站 / 国际站二选一**的,插件只认本站那份 —— 装的是另一站时代码会说「桌面客户端登录的是另一个站点」,切换过去重登一次即可。

**国际版点网页登录,界面报「已登录,但 magpie 无法列出它」?** 0.2.36 已修。根因不在国际站:magpie 的插件宿主只在登录结果带 `refresh` 字段时才把 oauth 登录写进 `plugin-auth.json`,缺这一格时界面照旧显示成功、账号却没落盘。升级后重登一次即可。

**国际版为什么没有「用百智云账号开通」?** 国际站的登录入口是它自己的(GitHub / Google / 账号密码),`/api/v1/users/login` 302 到它自己的授权页、不经过百智云 —— 那条入口在国际站上只会空等。国内站不受影响。

**导入时说「桌面版里的登录可能已经过期」?** 桌面版的会话有有效期(通常几天到一个月)。重新打开桌面版,它会自己续期;或者直接用「浏览器登录」。

Expand Down
78 changes: 70 additions & 8 deletions docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,53 @@
| provider id | `monkeycode` | `monkeycodeintl` |
| 平台 | `https://monkeycode-ai.com` | `https://monkeycode-ai.net` |
| 推理网关 | `https://proxy.monkeycode-ai.com/v1` | `https://proxy.monkeycode-ai.net/v1` |
| 会话 cookie | `monkeycode_ai_session` | 同名(**未真机验证**,见下) |
| 会话 cookie | `monkeycode_ai_session` | 同名(**推断,未读到值**,见下) |
| 登录方式 | 微信 / 支付宝 / 抖音 / GitHub / 观猹 / 账号密码(百智云 SSO) | **GitHub / Google / 账号密码**(2026-11-04 无头读 DOM) |
| 百智云 SSO | `GET /api/v1/users/login` → 302 `baizhi.cloud/oauth/authorize` | **不认**:302 到它自己的 `/oauth/authorize`,`client_id=` 为空 |
| 接口路径 | `/api/v1/...` | 逐字相同 |

两站是同一程序的两套部署:`GET /v1/models` 在两边都返回 405(逐字节一致的行为),平台接口路径也完全相同。
**账号池不互通** —— 国内站的 cookie 打国际站回 401,这是上游 RPC 层真机踩过并写进注释的事实。

未验证项(照抄上游标注,不当作已确认):
国际站已确认的事实(2026-11-04):

- 国际站换 Key 端点的响应字段、cookie 名是否与国内一致;
- 国际站账号级登录本仓没有真机跑通(上游源码里同时存在「真机验过」与「只有国内账号」两种说法)。
- 登录页只有 **GitHub / Google / 账号密码** 三种,页面明写「当前访问的是国际版」(无头浏览器读 `document.body.innerText`)
- `GET /api/v1/users/login` → 302 `Location: /oauth/authorize?client_id=&redirect_uri=https%3A%2F%2Fmonkeycode-ai.net%2Fapi%2Fv1%2Fusers%2Fbaizhi%2Fcallback...`
—— **`client_id` 是空的,且是相对自己站点的路径**,不经过 `baizhi.cloud`
- 把百智云会话注入一次性 Chromium 再走这条路:最终只落到国际站首页,**不会**签发国际站 session
⇒ 国际站**不提供**「用百智云账号开通」入口(`src/monkeycode/runtime.mjs` 里那个 method 被 `site.id` 门住)
- `POST /api/v1/public/captcha/challenge` 回 201、形状与国内站相同;
`POST /api/v1/users/password-login {email,password}` 缺 `captcha_token` 回 **403**(与国内站同一套语义)
- 未登录时**不下发**任何会话 cookie(`/`、`/login`、`/oauth/authorize` 三条都不带 `Set-Cookie`)

仍未验证项(照抄上游标注,不当作已确认):

- 国际站换 Key 端点(`/api/v1/users/ohmyagent/api-keys`)的**响应字段**是否与国内一致
—— 路径存在(无 cookie 回 401 而不是 404),但没有国际站账号跑完过一次
- 国际站会话 cookie 的**名字**:`monkeycode_ai_session` 只是推断。间接证据是报错形态
「{user} 已登录,但 magpie 无法列出它」—— 说明 `accept()` 曾经命中过(否则会是「登录超时」);
但这不足以排除「插件把别的 cookie 当成了会话」。真机判据见 AGENTS 0.7

### 1.1 登录结果必须带 `refresh`(否则 magpie 一格都不存)

magpie 的插件宿主(`host.js` 的 `save()`)只有两条保存出口:

```js
if ("refresh" in result) setAuth(key, { type: "oauth", refresh, access, expires, ...extra })
else if ("key" in result) setAuth(key, { type: "api", key, metadata })
```

**两条都不命中时它既不保存也不报错**:`plugin-auth.json` 里没有记录,而界面照样显示「已登录」,
紧接着的 `providers?wait=1` 里找不到这个 account,就打出
「{user} 已登录,但 magpie 无法列出它,请反馈此问题」。

本包没有 refresh 端点(凭据是长期静态材料),但**这一格必须占上**:统一由
`src/credential.mjs` 的 `signInSuccess()` 填 `refresh`(与 `access` 同值)与 `expires: 0`。
`tests/signin-contract.test.mjs` 锁住两件事:`signInSuccess` 一定带 `refresh`;
**生产代码里不许再出现裸的 `type: "success"`**。

这条的隐蔽性在于:国内站与百智云一直用 **API Key** 方式登录(`type: "api"`,走 api 分支),
所以「网页登录从来没存下来过」这件事一直没被发现,直到国际站(只能用网页/密码登录)撞上。

## 2. 鉴权:两套凭据,各管一段

Expand Down Expand Up @@ -62,9 +99,24 @@ signature = "v1=" + hex(HMAC-SHA256(signing_secret, prompt))
↘ GET /api/v1/users/status → data.user.{id,nickname}
```

- **为什么不能「读到 cookie 就算成功」**:窗口一开,服务端就下发匿名 `monkeycode_ai_session`。把它当登录完成,用户刚登完却看到「会话已失效」,窗口还被收掉 —— 等于白登一次。
- **为什么不能「读到 cookie 就算成功」**:窗口一开,服务端就下发匿名 `monkeycode_ai_session`。把它当登录完成,用户刚登完却看到「会话已失效」,窗口还被收掉 —— 等于白登一次。(国际站未登录时**不下发** cookie,这一条对它退化成「读到就是登完了」,但仍按同一套链路判。)
- **身份只用 `data.user.id`**:兑换响应里的 `id` 是「这把 Key 的记录 id」,同一账号每兑换一次就变。拿它判重会让「同一账号第二次登录」变成新账号(每登一次多一张卡 + 多签发一把 Key)。
- 窗口是可见的 1024×800、一次性 profile,关掉即回收(含临时目录)。
- **cookie 按本站域取**(`cookieDomains: [new URL(site.platformBaseUrl).hostname]`):两站同名 `monkeycode_ai_session`,不过滤域就会把另一站(或持久 profile 里上一个号)的会话当自己的。
- **登录结果必须带 `refresh`**:见 §1.1 —— 少了它,magpie 会把这次登录显示成成功、却一格都不存。

### 从本机客户端导入(两个站都提供,按域判)

桌面客户端(`%APPDATA%\com.chaitin.baizhi.monkeycode\`)的 `mc_base_url` 是**国内站 / 国际站二选一的一个值**,
`monkeycode-cookies.json` 里装的是**当前那一站**的会话:

- `readDesktopMonkeyCodeSession({ cookieDomains: [host] })` 只认本站;
- 读到的是**另一站**的登录态时,报「本机桌面客户端登录的是另一个站点」,而不是把 401 甩给用户
让人去怀疑「桌面版登录过期了」(`domainMatches()` 去前导点后按精确/子域匹配);
- 没写 `domain` 的老格式放行 —— 不做无根据的排除。

⚠ 这个文件会被客户端在切站 / 重登时删掉或重写(2026-11-04 真机:05:14 还在,05:43 就没有了),
所以「读不到」是常态而非异常。

### API Key 录入

Expand All @@ -78,7 +130,7 @@ oma_… 裸 token(缺密钥,请

解析层**不校验前缀**(`oma_` 不是白名单),只要求 token 形状 `^[A-Za-z0-9_.\-/+=]{16,}$` —— 上游将来换前缀时,写死前缀会把新 Key 直接判成「凭据不完整」。

### 用百智云账号开通 MonkeyCode(OAuth SSO)
### 用百智云账号开通 MonkeyCode(OAuth SSO,**只给国内站**)

MonkeyCode 的登录页第一项就是「百智云登录 - 推荐」,它是一段可以直接 GET 的入口
(2026-11-03 从登录页 `<a href>` 与 302 链实测):
Expand All @@ -92,8 +144,18 @@ GET https://monkeycode-ai.com/api/v1/users/login
→ MonkeyCode 签发自己的 `monkeycode_ai_session`
```

所以只要有百智云的会话 cookie,就能在受控浏览器里**零交互**换出一个 MonkeyCode 账号
(首次登录由 MonkeyCode 自动建号)。插件里有两条入口:
⚠ **国际站不是这条链**(2026-11-04 实测):

```
GET https://monkeycode-ai.net/api/v1/users/login
→ 302 Location: /oauth/authorize?client_id=&redirect_uri=https%3A%2F%2Fmonkeycode-ai.net%2Fapi%2Fv1%2Fusers%2Fbaizhi%2Fcallback&…
← 相对它自己的站点,且 client_id 为空;窗口里注入百智云会话走完只落到国际站首页
```

所以这个 method 被 `site.id === "monkeycode"` 门住:留在国际站上只会让用户对着一个
必然超时的入口等 240 秒。国际站要登录,用它自己的 GitHub / Google / 账号密码。

国内站这条路不受影响,插件里有两条入口:

- **用百智云账号开通**:窗口开在 `baizhi.cloud/console/`,用户登录/换号,插件读到两枚
cookie 后在**无头**浏览器里注入 cookie、访问上面的入口,等 `monkeycode_ai_session`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "magpie-monkeycode",
"version": "0.2.35",
"version": "0.2.36",
"description": "MonkeyCode 国内站 / 国际站 + 百智云 magpie 插件:三家独立供应商,浏览器登录或 API Key 接入,网关签名与积分额度。",
"type": "module",
"main": "./index.mjs",
Expand Down
Loading
Loading