feat(device-link): 远程会话状态栏用量显示对齐本机(订阅/网关全形态) - #3789
Conversation
控制端打开 device-link 远程会话时,底部 TodaySpendChip 此前一律抑制为 「仅会话金额 / 货币符号占位」,看不到被控端订阅的 5h/周/分模型窗口剩余。 本次把被控端 Claude 订阅余量快照经 device-link 镜像到控制端,显式选择 anthropic 供应商的远程会话按与本机订阅会话完全一致的形态渲染(窗口段 + 悬停额度卡);数据事实仍全部来自被控端,不读任何本机账号状态。 - packages/device-link:REMOTE_INVOKE_ALLOWLIST 放行只读 maker:usage:claude-subscription;PUSH_FORWARD_ALLOWLIST + ACCOUNT_CHANNELS 登记 usage:claude-subscription-changed(账号级 → sessions topic)。 - 被控端:broadcastClaudeSubscriptionUsage 补 tapWindowBroadcast,登出 / 换号的 null 清除同步转发。 - 控制端:新增 useRemoteClaudeSubscriptionUsage(warm-start 隧道读 + onRemotePush 实时镜像 + owner 栅栏;CHANNEL_NOT_ALLOWED 老被控端降级回 原占位显示);TodaySpendChip 远程订阅分支复用本机订阅渲染路径。 - 默认路由(providerId=null)的远程会话不做本机启发式猜测,维持原显示。 Signed-off-by: ficowang <fico@xd.com>
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/hooks/useRemoteClaudeSubscriptionUsage.ts | 新增远程快照缓存和 invoke/push 装配,但 invoke 回填缺少 owner fence,且不支持能力缓存没有生产失效路径。 |
| apps/desktop/src/renderer/components/status/TodaySpendChip.tsx | 显式 Anthropic 的远程 cc/pi 会话改用被控端镜像快照,并保留不可用时的原占位降级。 |
| apps/desktop/src/main/usageBroadcaster.ts | Claude 订阅快照广播新增 device-link tap,使记录和清空事件可转发到控制端。 |
| packages/device-link/src/allowlist.ts | 以 append-only 方式放行只读 Claude 订阅 invoke 和对应 push channel。 |
| packages/device-link/src/topics.ts | 将账号级 Claude 订阅余量 push 路由到常开的 sessions topic。 |
Prompt To Fix All With AI
### Issue 1
apps/desktop/src/renderer/hooks/useRemoteClaudeSubscriptionUsage.ts:67-70
**异步回填越过账户边界**
当 owner A 发起读取后切换到 owner B,并在新 owner 下打开同一 deviceId 的远程会话时,旧请求仍会把 A 的快照写入仅按 deviceId 区分的模块缓存,导致 B 的状态栏显示旧账户下取得的订阅余量;若新读取失败,该值会持续保留。请在应用结果前校验请求发起时的 owner/generation,或将缓存纳入 owner 作用域。
**How this was verified:** owner 切换仅重挂载 React 路由树,而此 invoke 回调无 owner fence,并继续写入跨重挂载存活的模块缓存。
### Issue 2
apps/desktop/src/renderer/hooks/useRemoteClaudeSubscriptionUsage.ts:64
**不支持状态永久缓存**
设备首次返回 `CHANNEL_NOT_ALLOWED` 后,其 deviceId 会在整个 renderer 生命周期内被永久跳过;被控端随后升级并以相同 deviceId 重连时,挂载和主动刷新仍不会重新探测,导致额度 chip 一直停留在降级占位,直至控制端 renderer 重启。请在重连或 allowlist 能力变化时使该记录失效。
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(device-link): 远程订阅会话状态栏对齐被控端 Claude..." | Re-trigger Greptile
|
@fico-hub 👋 这个 PR 还有 2 条 review conversation 没 resolve(apps/desktop/src/renderer/hooks/useRemoteClaudeSubscriptionUsage.ts),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
- P1:invoke 回填应用前校验发起时 owner 代次,module 缓存整体绑定当前 owner(变化即清空)——切号后迟到的旧账号快照整帧丢弃,不再顶给新账号。 - P2:CHANNEL_NOT_ALLOWED 负缓存从 renderer 终身改为 15 分钟 TTL, 到期重探测;收到该设备的订阅 push(能力已具备)立即解除。 Signed-off-by: ficowang <fico@xd.com>
|
这条 PR 会改变控制端远程会话状态栏的展示:显式 anthropic 远程会话将显示被控端 Claude 订阅余量,而不再是货币符号占位。这属于用户可见的产品行为,已开维护者确认讨论。\n\n请维护者在 PR 上 Approve 放行;若方向需要改,请 Request Changes。讨论 issue:#3790 |
|
命中 UI 路径(apps/desktop/src/renderer/components/status/TodaySpendChip.tsx / apps/desktop/src/renderer/hooks/useRemoteClaudeSubscriptionUsage.ts)但 description 未附界面效果证据——建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面(```html 代码块、.html 附件或在线预览链接),便于确认界面符合 DESIGN.md 设计规范。 本条为非阻断提醒,不挡审查。 |
|
两条 review conversation 已修复(cdeda5e)并逐条回复后 resolve;界面效果示意已补进 description「UI 变化」小节(Light/Dark × 改动前/后/悬停额度卡/降级占位,演示数据 HTML 渲染,源码在 assets/pr3789-previews 分支——双机实机截图不可得的原因 description 已说明)。 |
用户口径:本机桌面版 chip 能显示的,远程会话都要能显示。在 Claude 订阅镜像
之上补齐其余形态,全部复用同一镜像模式(invoke warm-start + push 整帧替换 +
owner 栅栏 + CHANNEL_NOT_ALLOWED TTL):
- Codex 订阅窗口:invoke 'maker:usage:account'('codex',已在 allowlist)返回
权威组合 payload;push 'usage:codex-account-changed' 转发;控制端按本机同
口径选槽 / 选桶(chatgpt-bridge → WHAM 槽,CLI → 按模型匹配桶)。
- 网关 LiteLLM 配额:同 channel('claude-code');push
'usage:claude-account-changed' 转发。credit 三池 handler 绑定主窗口 sender,
不镜像 —— 按本机「拿不到就隐藏」既有降级。
- xAI 订阅周用量 + 限流头:该 invoke 的 ipcMain handler 挂 assertTrustedSender
(那道闸不为远程放宽),按 telegram:* 先例由被控端 dispatch 拦截、直读
register.ts 注入的 reader(dispatch 模块图保持纯净);两条 push 转发,限流头
push-only 与本机同语义。
- cc 默认路由形态判定:镜像被控端 proxy 的按请求路由观察
('maker:claude-session-route:get' + route-changed push,session topic);
无观察值 / 老被控端维持原「仅会话金额」占位,不做本机启发式。
- codex 远程与 SSH 远程同口径按订阅形态处理;折扣模型 / 显式 XD 走网关形态。
协议改动均 append-only;push 频率上限:codex 每 turn 一记录、WHAM/LiteLLM
10s 节流、xai 低频、route 每会话生命周期通常一次。
Signed-off-by: ficowang <fico@xd.com>
|
范围更新(59d501f):应发起人要求,远程会话用量显示从「显式 anthropic 的 Claude 订阅」扩展为与本机 chip 全形态对齐——Codex 订阅窗口(分桶/WHAM 槽同口径)、xAI 周用量与限流头、网关 LiteLLM 配额、cc 默认路由按被控端路由观察值判形态。协议仍为 append-only(明细见 description「影响与回滚」);xAI invoke 按 telegram:* 先例走 dispatch 拦截,不放宽 assertTrustedSender。@dashhuang 产品门(#3790)请按更新后的完整范围评审。 |
|
@dashhuang 这边 CI 已全部通过,之前的 review conversation 也已处理并 resolve。目前还在等待产品方向审核,麻烦有空时帮忙 review;如果方向或范围需要调整,也请直接指出。用户侧正在等待远程 Claude / Codex 额度显示修复,感谢。 |
MagicLizi
left a comment
There was a problem hiding this comment.
审查发现 P1 ×1,请在当前 head 上修复后再请求 review。
- [P1] apps/desktop/src/renderer/hooks/useRemoteClaudeSessionRoute.ts:38 — cacheKey 用源码里的原始 NUL (0x00) 拼接 deviceId/sessionId。git diff 将该文件标成二进制;GitHub PR files 上本文件 additions=0。默认路由形态判定整份实现在 PR diff 里不可审。请改成可见的转义(例如 \u0000)或可打印分隔符,使文件保持文本。验证:文件不再含原始 NUL,git diff 不再显示 Bin。
…文件为文本 review P1:cacheKey 用源码里的原始 NUL(0x00)拼接 deviceId/sessionId,git 将文件判为二进制,PR diff 不可审。 改为 JS 字符串转义 \u0000,运行时键值逐字节不变(与 useModelPricing 的既有写法一致);文件不再含原始 NUL。 Signed-off-by: ficowang <fico@xd.com>
|
@MagicLizi P1 已修(8922fbb): 验证:文件不再含原始 NUL(od 计数 0); |
这些问题已在当前 head 的后续 commit 中修复(格式门/安全门已通过),自动 dismiss 旧的 CHANGES_REQUESTED 以解除合并阻塞。
|
@fico-hub 👋 这个 PR 目前与 请在本地 merge 最新的 |
…sage 解决 apps/desktop/src/main/maker-ipc/register.ts 导入冲突:main(makecindy#3939)已把回合用量记账迁出 register.ts, 本分支在该导入块仅新增 readXaiSubscriptionUsageSnapshotForDeviceLink,保留该单行导入,其余按 main。 Signed-off-by: ficowang <fico@xd.com>
|
@MagicLizi 已 merge 最新 origin/main(87aec01)。唯一冲突在 maker-ipc/register.ts 的导入块:main(#3939)把回合用量记账迁出了 register.ts,本分支在该块只新增了 readXaiSubscriptionUsageSnapshotForDeviceLink 一行导入,按 main 取舍后保留该单行。验证:desktop typecheck 0 错误;定向测试 38 文件 683 例 + device-link 包 255 例通过;仓库根 related 归因全部 PASS。请 auto-review 重新处理。 |
|
@fico-hub 👋 这个 PR 目前与 请在本地 merge 最新的 |
…sage Resolve conflicts with makecindy#3972 (统一套餐用量卡片): keep device-link remote form detection and mirror hooks on the data layer, and feed the mirrored snapshots into the shared usage-card builders; only SSH remote sessions stay session-money-only. Source-contract tests ported to the compact() style; device-link component test follows the always-button trigger. Signed-off-by: ficowang <fico@xd.com>
|
@MagicLizi 已再次 merge 最新 origin/main(667450b),PR 回到 MERGEABLE。这次冲突来自 #3972(统一套餐用量卡片),集中在
验证:desktop typecheck 0 错误;eslint / prettier / |
这次改了什么
摘要
控制端通过设备互联(device-link)打开远程会话时,底部用量 chip 此前一律被抑制为「仅会话金额 / 货币符号占位」,用户看不到被控端订阅还剩多少额度(维护者 @dashhuang 已确认可以调整这一设计)。本 PR 把被控端的 Claude 订阅余量快照经 device-link 镜像到控制端:显式选择 anthropic 供应商的远程会话,chip 与悬停额度卡按与本机订阅会话完全一致的形态渲染(5h/周/分模型窗口剩余 + 会话金额),数据事实全部来自被控端,不读任何本机账号状态。
范围更新(第二批 commit 59d501f,应发起人要求扩展):显示对齐从 Claude 订阅扩展到本机 chip 支持的全部计费形态——Codex 订阅窗口(app-server 分桶 + chatgpt-bridge WHAM 槽)、xAI 订阅周用量与限流头、网关 LiteLLM daily/monthly 配额;cc 默认路由会话的订阅/网关形态判定改为镜像被控端 proxy 的按请求路由观察值(不再要求显式选择 anthropic)。全部复用同一镜像模式(invoke warm-start + push 整帧替换 + owner 代次栅栏 + CHANNEL_NOT_ALLOWED TTL 负缓存)。
数据通路(全部复用既有基础设施):
deviceLink.invoke(deviceId, 'maker:usage:claude-subscription')读被控端 cached-first 快照(被控端read()自带 180s 节流的后台端点刷新);broadcastClaudeSubscriptionUsage补一行tapWindowBroadcast,经既有 broadcast-tap →sessionstopic 转发;控制端onRemotePush过滤 channel + deviceId + owner 栅栏(isDeviceLinkRemotePushCurrent,与其它 push 消费者同款)后覆盖镜像;CHANNEL_NOT_ALLOWED→ 控制端回原「仅会话金额」占位显示,不报错。变更类型
feat新功能范围
maker:usage:codex-rate-limitsinvoke 已在 allowlist,后续可按同一模式接入);UI 变化
不涉及视觉规范变化:远程订阅形态完全复用本机订阅形态的既有渲染路径(
TodaySpendChip窗口段 +QuotaHoverCard),无新增视觉元素、颜色、间距或文案,仅数据来源从本机 hook 切换为被控端镜像 hook。Light/Dark 双模式随既有语义 token 生效,无单模式硬编码。实机目检未执行(开发环境即被控端本机,无第二台装本分支构建的控制端),渲染行为由组件级 jsdom 测试覆盖(TodaySpendChip.deviceLink.test.tsx四例:窗口段渲染 / 悬停额度卡 / 镜像不可用降级 / 默认路由不变);如实说明,两种模式均未实机目检。DESIGN.md已约束的 TodaySpendChip / QuotaHoverCard 既有形态,本 PR 不改任何视觉属性。改动前后效果示意(Light / Dark × 改动前 / 改动后 / 悬停额度卡与降级,演示数据、近似 token 色值;非实机截图,原因见上——HTML 源码见 assets 分支):
怎么验证的
自动验证
手工验证
不涉及:端到端手工验证需要两台设备各跑本分支构建(控制端 + 被控端),开发环境只有被控端一台。数据通路的两端(invoke handler 无 sender 依赖、tap 转发、topic 路由、owner 栅栏、CHANNEL_NOT_ALLOWED 降级)均有单测覆盖。
未执行的验证
风险
风险分类
影响与回滚
packages/device-link/src/allowlist.ts准入判据登记 invoke / push 白名单并在topics.ts登记账号级路由(判据核对:handler 无event.sender依赖、无本机 UI/shell 副作用、数据真相在被控端);③ 手机版入口未接入,见「明确不包含」。版本偏差矩阵:新控制端 + 老被控端 →CHANNEL_NOT_ALLOWED,降级回原占位显示;老控制端 + 新被控端 → 新 push 是未知 channel,老控制端按 channel 过滤天然忽略;computeAllowlistHash随表变化,仅用于「版本不一致」提示,不阻断。提交前检查
git commit -s,见 DCO)