Repository navigation
Conversation
插件架构迁移时,协议层已经定义并声明了这些能力,但网关的公开视图层
没有消费它们,导致插件家出现一串「能力被平台丢掉」的问题。
一、凭证不再自动续期(credential_maintenance.rs)
`refresh_expiring_accounts` 调的是同步版 `credentials_expiring`,而
插件适配器(`RpcAdapter`)的同步版是硬编码 `false`,真正的实现在
`credentials_expiring_async` 里(会发 `token.expiring` RPC 问插件)。
后果是所有插件家在这一步被判成「凭证未过期或临期」而永远跳过:凭证
过期后每轮维护都报「无需刷新」,接口一路 401,而能续期的 RPC 一次都
没被问过。内置家的异步版默认转调同步版,所以这一改对它们无影响。
二、备注名被插件覆盖(plugin_accounts.rs)
备注名走 `PATCH /api/accounts/{id}`,只写记录顶层的 `name` 与
`nameCustom`;而 12 家插件都会往 `pluginPublic.name` 里写自己按口径
算的展示名(邮箱、app key 前缀……),合并时把备注名盖掉了 —— 用户改
完备注名在列表里纹丝不动。
给「插件赢」加一个例外:`nameCustom` 为真时用回顶层 `name`。判据用
`nameCustom` 而不是「顶层 name 非空」,因为插件在添加账号时写的种子
名也落在顶层,拿它当用户意图会把默认名误判成备注名。
三、有效期列永远是空的(plugin_accounts.rs)
协议里 `AccountFeatures::expiry` 就是为这一列准备的 —— 各家键名真的
不同(OrcaTerm 是 `tokenExpireAt`、比别家少一个 s),插件在握手时
申报「我用哪个键表示到期」。但网关从来没读过这个申报。
改成按声明的键名从 secret 取值放上公开视图。值放在申报的原键名下面
(不是统一改名 `expiresAt`),因为前端那张表就是按各家自己的键名取的。
四、手动刷新 Token 入口消失(plugin_accounts.rs + plugin_host/mod.rs)
公开视图的 `hasRefreshToken` 原先恒写 `false`,界面据此不渲染刷新项
(`hasRefreshToken ? <刷新项> : …`),于是插件家一律没有刷新按钮 ——
哪怕插件申报了 `refresh: true` 并且实现了 `token.refresh`。
新增 `plugin_host::refresh_capable(provider)` 读握手带回的
`ProviderInfo.capabilities.refresh`,与既有的 `account_features_of`
同一形态(`account_store` 层拿不到适配器,插件家的 id 也不在
`ProviderKind` 枚举里)。
验证(OfficeAce / CodeArts,均为插件家):
- `hasRefreshToken` 由 `false` 变为 `true`,刷新入口恢复;
- `expiresAt` 出现在公开视图,续期写入的新值能正确显示;
- 维护日志由「无需刷新的账号」变为按实际情况判定临期并续期。
`proxy` 是跨家通用的账号属性(`apply_patch` 统一写入 `record.proxy`),
但公开形态只在各家的 `to_*_public_account` 里各自投影一份 —— 插件家走
`to_plugin_public_account`,那个骨架里没有这个键。
后果比前几处更隐蔽:**值是对的,只是读不到**。
1. 界面点代理下拉 → `apply_patch` 正确写入 → `changes: ['代理 → 通用']`;
2. 库里确实存着 `{source:"pool",proxyId:"…"}`;
3. 公开形态缺 `proxy` → 前端读到 `undefined` → 整列显示「直连」;
4. 再选同一条 → `apply_patch` 判「值没变」→ `changes: []`
→ 前端仍弹「✅ 代理已更新」(假成功)。
于是用户反复点都改不动,而后端日志里连一条「账号已更新」都没有 ——
排查时会一路误判成「请求没发出去」或「前端 bug」。
修法与 `maxConcurrent` 完全一致:放进 `public_account` 的跨家统一注入块。
那里本来就承载 `maxConcurrent` / `limiters` / `usageQuery` / `lowBalance`
这些「所有家都有」的账号属性,注释里写明的原因正是这个坑(逐字段白名单
的家漏了 `maxConcurrent`,缺键被选路当成 0 = 不限,上限静默失效)。
恒为「对象或 null」(null = 直连,与写入侧 `normalize_account_proxy` 的
`None → Value::Null` 同口径),前端 `ProxyCell` 按 `proxy?.config?.source`
读,两种形态都能吃。
验证:修复后 14 家 provider 的公开形态都带上 `proxy`;之前设过但显示
「直连」的 Cline Free 账号恢复显示「通用」,OpenCode Zen 的轮询档正常。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
插件架构迁移后,协议层已经定义并声明了一批账号相关能力,但网关的公开视图层没有消费它们。结果是一串「插件能力被平台丢掉」的问题 —— 全部影响插件家(
account.server的to_plugin_public_account路径),不区分具体是哪一家。本 PR 修五处,都在这条链路上。
一、凭证不再自动续期
credential_maintenance.rs的refresh_expiring_accounts调的是同步版credentials_expiring:而插件适配器(
RpcAdapter)的同步版是硬编码false,它自己的注释写着「真正的实现在credentials_expiring_async」—— 那个版本会发token.expiringRPC 去问插件。后果:所有插件家在这一步被判成「凭证未过期或临期」而永远跳过。凭证过期后每轮维护都报「无需刷新」,接口一路 401/过期错误,而真正能续期的 RPC 一次都没被问过。
修复:改调异步版。内置家的异步版默认转调同步版(trait 的默认实现),所以对它们无影响。
二、备注名被插件覆盖
备注名走
PATCH /api/accounts/{id}→apply_patch,只写记录顶层的name+nameCustom。而 12 家插件都会往pluginPublic.name里写自己按口径算的展示名(邮箱、app key 前缀……),合并时把备注名盖掉了 —— 用户改完备注名,列表里纹丝不动。修复:给「插件赢」加一个例外 ——
nameCustom为真时用回顶层name。判据用
nameCustom而不是「顶层 name 非空」:插件在添加账号时写的种子名也落在顶层,拿它当用户意图会把默认名误判成备注名。三、有效期列永远是空的
协议里
AccountFeatures::expiry就是为这一列准备的,注释甚至写明了注意事项:插件在握手时申报「我用哪个键表示到期」,但网关从来没读过这个申报 —— 全仓搜
account_features,只有usage_records.rs用了其中一个字段(default_limiters),expiry一次都没被用过。修复:按申报的键名从 secret 取值放上公开视图。值放在申报的原键名下(不是统一改名
expiresAt),因为前端那张表就是按各家自己的键名取的。四、手动「刷新 Token」入口消失
公开视图的
hasRefreshToken原先恒写false:界面据此不渲染刷新项(
hasRefreshToken ? <刷新项> : …),于是插件家一律没有刷新按钮 —— 哪怕插件申报了refresh: true并且实现了token.refresh,用户在凭证过期时只剩「重新登录」一条路。修复:新增
plugin_host::refresh_capable(provider)读握手带回的ProviderInfo.capabilities.refresh。与既有的
account_features_of同一形态 ——account_store层拿不到适配器,而插件家的 id 也不在ProviderKind枚举里,所以只能在plugin_host开一个读数入口。五、出网代理列恒显示「直连」
proxy是跨家通用的账号属性(apply_patch统一写入record.proxy),但公开形态只在各家的to_*_public_account里各自投影一份 —— 插件家走to_plugin_public_account,那个骨架里没有这个键。这一处比前几处更隐蔽:值是对的,只是读不到。
于是用户反复点都改不动,而后端日志里连一条「账号已更新」都没有 —— 排查时会一路误判成「请求没发出去」或「前端 bug」。
修法与
maxConcurrent完全一致:放进public_account的跨家统一注入块。那里本来就承载maxConcurrent/limiters/usageQuery/lowBalance这些「所有家都有」的账号属性,注释里写明的原因正是这个坑(逐字段白名单的家漏了maxConcurrent,缺键被选路当成 0 = 不限,上限静默失效)。恒为「对象或 null」(null = 直连,与写入侧
normalize_account_proxy的None → Value::Null同口径)。验证
实测环境:
3.3.0-beta.3+ 插件包0.2.2-beta.2,宿主机 x86_64 Linux。以 OfficeAce(申报
refresh: true+expiry: ["expiresAt"])与 CodeArts(同)为样本:hasRefreshTokenfalse(无刷新入口)trueexpiresAt1792895925000→2026-10-25 10:38proxy不影响的部分
proxy)对内置家是幂等覆盖 —— 各家自己投影的值与统一注入的值来自同一次describe_account_proxy(record.proxy())调用,结果一致。account_features_of/refresh_capable返回None/false,公开视图保持原形态,界面走自己的兜底表 —— 升级是渐进的。hasRefreshToken: false是正确的:它走的是存量内置记录投影(to_zcode_public_account),且上游确实没有续期协议。这个 PR 没有改动那条路径。影响面
这五处不是某一家插件的问题 —— 任何「插件申报了能力但网关没消费」的家都会中。目前 30 家插件里有 12 家会往
pluginPublic.name写展示名,OfficeAce / CodeArts 等多家申报了refresh: true与expiry。🤖 本条回复由 AI Agent 生成
Generated by an AI agent.