dsh-connect-agnes-token-plan 0.12.1 → 0.13.0

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +3 -1
  3. package/assets/agnescode-usage-capsule.png +0 -0
  4. package/client.js +111 -44
  5. package/docs/ADR.md +208 -0
  6. package/docs/AGNES-API-docs/1/343/200/201API /346/216/245/345/205/245/343/200/201/345/256/232/344/273/267/344/270/216/350/256/242/351/230/205.md" +150 -0
  7. package/docs/AGNES-API-docs/2/343/200/201/345/270/270/350/247/201/351/224/231/350/257/257/347/240/201.md +441 -0
  8. package/docs/AGNES-API-docs/3/343/200/201pricing.md +193 -0
  9. package/docs/AGNES-API-docs/4/343/200/201Token Plan FAQ.md" +354 -0
  10. package/docs/AGNES-API-docs/5/343/200/201/344/270/255/346/226/207/347/253/231/346/246/202/350/277/260/357/274/214/351/231/244/351/223/276/346/216/245/345/244/226/345/235/207/344/270/216/346/265/267/345/244/226/347/253/231/347/202/271/347/233/270/345/220/214.md +176 -0
  11. package/docs/AGNES-API-docs/6/343/200/201/347/210/261/346/200/235/345/212/236/345/205/254/345/256/242/346/210/267/347/253/257/350/256/242/351/230/205 FAQ.txt" +90 -0
  12. package/docs/AGNES-API-docs/Agnes 2.5 Flash.md +658 -0
  13. package/docs/AGNES-API-docs/Agnes 2.5 Pro.md +576 -0
  14. package/docs/AGNES-API-docs/Agnes 3.0 Flash.md +392 -0
  15. package/docs/AGNES-API-docs/Agnes Image 2.1 Flash.md +507 -0
  16. package/docs/AGNES-API-docs/Agnes Image 2.5 Flash.md +507 -0
  17. package/docs/AGNES-API-docs/Agnes Video 2.5 Flash.md +316 -0
  18. package/docs/AGNES-API-docs/Agnes Video 2.5.md +577 -0
  19. package/docs/AGNES-API.md +586 -0
  20. package/docs/AGNESCODE-SCAN-LOGIN.md +251 -0
  21. package/docs/AGNESCODE-SPLIT.md +63 -0
  22. package/docs/API.md +289 -0
  23. package/docs/ARCHITECTURE.md +413 -0
  24. package/docs/ARCHIVE-BOUNDARY-DECISIONS.md +134 -0
  25. package/docs/ARCHIVE-IMPROVEMENTS-2026-09.md +624 -0
  26. package/docs/AUTH.md +122 -0
  27. package/docs/CONTRIBUTING.md +84 -0
  28. package/docs/DSH-LLM-DEVELOP.md +192 -0
  29. package/docs/DSH-PLUGIN.md +159 -0
  30. package/docs/DSH-SESSION-STORAGE.md +63 -0
  31. package/docs/DSH-UNIFIED-INTERFACE-AUDIT.md +70 -0
  32. package/docs/PITFALLS.md +670 -0
  33. package/docs/PROVIDER-HOT-RELOAD.md +109 -0
  34. package/docs/PUBLISH-CORE-SPLIT.md +149 -0
  35. package/docs/README.md +42 -0
  36. package/docs/REFERENCES.md +44 -0
  37. package/docs/ROADMAP.md +509 -0
  38. package/docs/SETUP.md +130 -0
  39. package/docs/TESTING.md +108 -0
  40. package/docs/TOKEN-STORE-SPLIT.md +149 -0
  41. package/lib/agnescode-stats-store.js +135 -82
  42. package/lib/index.js +455 -27
  43. package/lib/llm-models.js +20 -1
  44. package/locale/en.json +2 -1
  45. package/locale/zh.json +1 -1
  46. package/package.json +7 -3
  47. package/screenshots.json +2 -1
package/CHANGELOG.md CHANGED
@@ -21,6 +21,57 @@
21
21
 
22
22
  **代价是没有任何门禁会拦住上面那些错误**,包括把账本条目整段抄进来。所以纪律靠本节的判据自查;改动本文件后手工跑一次 `node test/docs.test.mjs`,确认没碰坏其它检查(如发布版本陈述)。
23
23
 
24
+ ## [Unreleased]
25
+
26
+ ## [0.13.0] — 2026-10-21
27
+
28
+ 本版新增 **AgnesCode 扫码登录**:桌面端没装、没登录也能用这条线。另外修了促销标丢失、并发记录丢失、令牌被悄悄改写三处问题,并扩大了凭据脱敏面。
29
+
30
+ ### 新增:AgnesCode 扫码登录——不用先装桌面端
31
+
32
+ - **增(插件卡)**:AgnesCode 那一栏多一个「扫码登录」入口。点了会打开浏览器到 AgnesCode 登录页,你扫码(已登录就点继续)后,浏览器把一次性授权码交给插件,插件换回与桌面端同款的登录态。
33
+ - **增(登录流程)**:等待期间可以「取消等待」——取消只是不再等了,已经扫成功的那次登录照样会被保存下来。
34
+ - **修(登录流程)**:浏览器打不开时立刻回报「打不开浏览器」,不再干等到超时;换码失败时显示服务端给的可读原因(例如「授权码无效」),不再是一句没头绪的失败。
35
+
36
+ ### 修复:桌面端的促销标不再整条丢掉
37
+
38
+ - **修(面板)**:桌面端模型列表里的促销标(如「限时七折」)此前在列表重建时整个丢失,这一列于是永远是空的。现在重建后仍带出促销标,没有促销文案时按「没有」处理。
39
+
40
+ ### 修复:同时发生的记录不再丢一部分
41
+
42
+ - **修(面板)**:短时间内多条用量与限频记录一起写入时,只有部分能落盘(实测五次并发只留下一到三条),面板上的数字看着在涨、实际丢着。现在写入按顺序排队,记录条数与实际发生的一致。
43
+
44
+ ### 修复:令牌不再被悄悄改写
45
+
46
+ - **修(面板)**:平台发来的登录令牌此前在读取时会被去掉首尾空白,令牌因此被改写,表现为一次查无出处的 401。现在按原样读取。
47
+
48
+ ### 修复:凭据脱敏面扩大
49
+
50
+ - **修(面板与日志)**:脱敏原先只认几种键名和前缀。现在也按值的形状认:Basic 认证整段、Cookie、JSON 里没加引号的凭据字段、client_secret / code_verifier 这类键名。普通句子原样保留,不会把诊断信息一起吃掉。
51
+
52
+ ### 修复:插件卡把密码说成了两处都在用
53
+
54
+ - **修(插件卡·中英文界面)**:介绍此前写「令牌到期自动重登(需 `AGNES_PASSWORD`)」,读起来像 AgnesCode 桌面端的令牌也要靠密码续期。实际是两件事:`AGNES_PASSWORD` 只在**重登 Agnes 控制台**时用到;桌面端的令牌到期是重新读取桌面应用自己的登录态,不需要密码。现在两种语言都把这两句分开说清。
55
+
56
+ ## [0.12.2] — 2026-10-19
57
+
58
+ 本版三件事:**插件卡顶栏收成一行**,卡片底部不再多留一块空白;**中文介绍换写成「装上能得到什么」**,与英文卡片说同一件事;以及**装上后 README 里指向 `docs/` 的链接不再全部断掉**。
59
+
60
+ ### 界面:插件卡顶栏收成一行,卡片底部不再多留一块空白
61
+
62
+ - **改(面板)**:三个 tab 与「更新于 / 令牌状态 / 刷新 / 返回会话」原来是上下两行,tab 那一行的右半边一直空着,卡片白占一整行高度。现在 tab 与这些状态同处一行,底下的分隔线也由这一行统一画。
63
+ - **改(面板)**:卡片底部原来自己留 56px 内边距,外面再加宿主的一层,合计约 104px 空白。现在只剩宿主那一层。
64
+
65
+ ### 变更:中文介绍改说「装上能得到什么」
66
+
67
+ - **改(插件卡·中文界面)**:介绍此前按功能面罗列(额度面板 → 四个开关 → 桌面端上游),读起来像一份变更日志。现在先说「一键接入 + 自动填好」,额度窗口与桌面端接入降为后半句的补充。
68
+ - **修(插件卡·英文界面)**:系统语言是英文时,介绍此前退回 package.json 里那段写给开发者的长描述,与中文卡片不是同一份文案。现在两种语言说的是同一件事。
69
+
70
+ ### 修复:装上后 README 里指向 docs/ 的链接不再断掉
71
+
72
+ - **修(安装后的文档)**:README 有 22 处指向 `docs/` 的链接,而 `files` 白名单里一直没有 `docs`——插件装好后点这些链接全是 404。现在 `docs/` 随包一起发。
73
+ - 影响面:`package.json`、`locale/zh.json`、`locale/en.json`、`test/package.test.mjs`。
74
+
24
75
  ## [0.12.1] — 2026-10-19
25
76
 
26
77
  本版两条主线:**修掉面板会「僵住」的一类故障**——首次读数失败后它不再永久停止刷新;以及**把插件卡上的介绍文案改成讲「你能拿到什么」**,并补正 README 里一句默认做不到的承诺(自动重登需要 `AGNES_PASSWORD`)。
package/README.md CHANGED
@@ -68,6 +68,8 @@
68
68
  - 两条上游**互不借用**:`agnescode` 的会话只读它自己的路由、绝不画控制台窗口,反之亦然;切到**别的 provider**(其它插件)的模型时胶囊直接消失,也不再发任何请求;
69
69
  - 读不到时**保留上一次数字并标「上次数据」**,不会清零或伪装成 0%;余额里的 `0` 是「烧完了」的读数(红色告警),`—` 才是「这次没读到」——这个区别是刻意的。
70
70
 
71
+ ![AgnesCode 积分余额胶囊(点开读数卡)](assets/agnescode-usage-capsule.png)
72
+
71
73
  ## 边界
72
74
 
73
75
  - 面板**不代你操作账务**——不改套餐、不代扣额度、不碰 Key 明文;数据来自 Agnes 控制台自己的 API,与网页控制台口径一致。
@@ -114,7 +116,7 @@ npm run doctor:json # 机器读:JSON(可进你的巡检 / 工单脚本
114
116
  - [docs/API.md](docs/API.md) — 路由与控制台端点
115
117
  - [docs/AGNES-API.md](docs/AGNES-API.md) — Agnes 接口全集(控制台额度侧 + 推理侧)
116
118
  - [docs/TESTING.md](docs/TESTING.md) — 测试体系
117
- - [docs/PITFALLS.md](docs/PITFALLS.md) — 真实踩坑(57 条)
119
+ - [docs/PITFALLS.md](docs/PITFALLS.md) — 真实踩坑(61 条)
118
120
  - [CHANGELOG.md](CHANGELOG.md) — 版本变化
119
121
 
120
122
  AI 协作会话请先读 [AGENTS.md](AGENTS.md)。人类贡献者先读 [SETUP.md](docs/SETUP.md) 与 [ARCHITECTURE.md](docs/ARCHITECTURE.md) §2 理解结构与改动代价,改代码前再读 [AGENTS.md](AGENTS.md) 的红线与 [TESTING.md](docs/TESTING.md) 的 `npm test` 门禁。
package/client.js CHANGED
@@ -240,6 +240,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
240
240
  "agnescode.bffBase": "接口地址 {base}",
241
241
  "agnescode.notLogged": "未关联——请先在 AgnesCode 桌面端登录(微信扫码),再点「检测本机登录态」。",
242
242
  "agnescode.harvestWindowsOnly": "本机登录态采集目前仅支持 Windows(macOS / Linux 未实现)。",
243
+ "agnescode.login": "扫码登录",
244
+ "agnescode.cancelLogin": "取消等待",
245
+ "agnescode.loginCancelled": "已取消等待。若已在 App 中完成扫码,登录仍会在后台完成并生效。",
246
+ "agnescode.loginOk": "扫码登录成功。",
247
+ "agnescode.loginFail": "扫码登录失败:{error}",
248
+ "agnescode.loginHint": "无需桌面 App——浏览器打开登录页,用 AgnesCode App 扫码即可(macOS / Linux 可用)。",
243
249
  "agnescode.downloadCta": "下载 AgnesCode 桌面客户端,领取限时积分 →",
244
250
  "agnescode.balanceLabel": "积分余额",
245
251
  "agnescode.balanceTimeLabel": "时效",
@@ -494,6 +500,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
494
500
  "agnescode.bffBase": "API base {base}",
495
501
  "agnescode.notLogged": "Not linked — sign in inside the AgnesCode desktop App (WeChat scan) first, then run the detection.",
496
502
  "agnescode.harvestWindowsOnly": "Local login-state detection currently supports Windows only (macOS / Linux not implemented).",
503
+ "agnescode.login": "Scan-to-sign-in",
504
+ "agnescode.cancelLogin": "Cancel waiting",
505
+ "agnescode.loginCancelled": "Waiting cancelled. If the scan was completed, the login still finishes in the background.",
506
+ "agnescode.loginOk": "Scan-to-sign-in succeeded.",
507
+ "agnescode.loginFail": "Scan-to-sign-in failed: {error}",
508
+ "agnescode.loginHint": "No desktop App needed — the browser opens the login page for the AgnesCode App to scan (works on macOS / Linux).",
497
509
  "agnescode.downloadCta": "Download the AgnesCode desktop client and claim your limited-time credits →",
498
510
  "agnescode.balanceLabel": "Credit balance",
499
511
  "agnescode.balanceTimeLabel": "Time-limited",
@@ -641,7 +653,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
641
653
  //#endregion
642
654
  //#region src/client/http.ts
643
655
  /** One POST with the panel's fixed request shape; the parsed body or null. */
644
- async function postRaw(path, payload) {
656
+ async function postRaw(path, payload, signal) {
645
657
  const response = await fetch(path, {
646
658
  method: "POST",
647
659
  headers: {
@@ -649,7 +661,8 @@ var dsh_connect_agnes_token_plan_client = (function() {
649
661
  accept: "application/json"
650
662
  },
651
663
  cache: "no-store",
652
- body: JSON.stringify(payload)
664
+ body: JSON.stringify(payload),
665
+ ...signal !== void 0 ? { signal } : {}
653
666
  });
654
667
  const body = await response.json().catch(() => null);
655
668
  return {
@@ -658,12 +671,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
658
671
  };
659
672
  }
660
673
  /** POST and return the parsed body, or null when the response is not JSON. */
661
- function postJson(path, payload) {
662
- return postRaw(path, payload).then(({ body }) => body);
674
+ function postJson(path, payload, signal) {
675
+ return postRaw(path, payload, signal).then(({ body }) => body);
663
676
  }
664
677
  /** POST and demand `ok:true`; refuse by throwing the Host's own wording. */
665
- async function postJsonOrThrow(path, payload) {
666
- const { status, body } = await postRaw(path, payload);
678
+ async function postJsonOrThrow(path, payload, signal) {
679
+ const { status, body } = await postRaw(path, payload, signal);
667
680
  if (body === null || body.ok !== true) throw new Error(typeof body?.error === "string" ? body.error : `HTTP ${status}`);
668
681
  return body;
669
682
  }
@@ -959,9 +972,9 @@ var dsh_connect_agnes_token_plan_client = (function() {
959
972
  },
960
973
  header: {
961
974
  display: "flex",
962
- alignItems: "center",
975
+ alignItems: "stretch",
963
976
  gap: 12,
964
- padding: "12px 0 10px"
977
+ borderBottom: "1px solid var(--dsw-alias-border-l1)"
965
978
  },
966
979
  scroll: {
967
980
  flex: 1,
@@ -969,12 +982,10 @@ var dsh_connect_agnes_token_plan_client = (function() {
969
982
  overflowY: "auto",
970
983
  overflowX: "hidden"
971
984
  },
972
- content: { padding: "6px 0 56px" },
985
+ content: { padding: "6px 0 0" },
973
986
  tabBar: {
974
987
  display: "flex",
975
- gap: 4,
976
- borderBottom: "1px solid var(--dsw-alias-border-l1)",
977
- marginBottom: 4
988
+ gap: 4
978
989
  },
979
990
  tab: {
980
991
  appearance: "none",
@@ -982,6 +993,8 @@ var dsh_connect_agnes_token_plan_client = (function() {
982
993
  border: "none",
983
994
  borderBottom: "2px solid transparent",
984
995
  padding: "8px 12px",
996
+ display: "flex",
997
+ alignItems: "center",
985
998
  fontSize: 13,
986
999
  color: "var(--dsw-alias-label-secondary)",
987
1000
  cursor: "pointer"
@@ -996,6 +1009,14 @@ var dsh_connect_agnes_token_plan_client = (function() {
996
1009
  fontSize: 12
997
1010
  },
998
1011
  spacer: { flex: 1 },
1012
+ cluster: {
1013
+ display: "inline-flex",
1014
+ alignItems: "center",
1015
+ gap: 12,
1016
+ justifyContent: "flex-end",
1017
+ flexWrap: "wrap",
1018
+ alignSelf: "center"
1019
+ },
999
1020
  button: BUTTON,
1000
1021
  sectionTitle: {
1001
1022
  margin: "22px 0 10px",
@@ -3140,12 +3161,6 @@ var dsh_connect_agnes_token_plan_client = (function() {
3140
3161
  }
3141
3162
  const renderLimit = (text) => h("span", { style: S.planLimit }, text);
3142
3163
  const renderEntry = (entry) => {
3143
- const limits = entry.limits ?? {};
3144
- const windowHours = Number(limits.requestsWindowH);
3145
- const limitItems = [];
3146
- if (limits.requests5h !== void 0 && limits.requests5h > 0) limitItems.push(renderLimit(`${count(limits.requests5h)}${tt("quota.unit.requests")} / ${windowHours > 0 ? windowHours : 5}h`));
3147
- if (limits.requestsWeekly !== void 0 && limits.requestsWeekly > 0) limitItems.push(renderLimit(`${count(limits.requestsWeekly)}${tt("quota.unit.requests")} / ${tt("quota.perWeek")}`));
3148
- if (limits.imagesDaily !== void 0 && limits.imagesDaily > 0) limitItems.push(renderLimit(`${count(limits.imagesDaily)}${tt("quota.unit.images")} / ${tt("quota.perDay")}`));
3149
3164
  return h("div", { style: S.planCycle }, h("span", { style: S.planCycleLabel }, cycleLabel(entry.billingCycle, tt)), entry.priceMinor !== void 0 && entry.priceMinor > 0 ? h("span", { style: S.planPrice }, money(entry.priceMinor, entry.currency)) : null);
3150
3165
  };
3151
3166
  return h("div", { style: S.planGrid }, Array.from(tiers.entries()).map(([tierName, entries]) => h("div", {
@@ -3622,10 +3637,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
3622
3637
  const [error, setError] = useState(null);
3623
3638
  const [updatedAt, setUpdatedAt] = useState(0);
3624
3639
  const [harvestBusy, setHarvestBusy] = useState(false);
3640
+ const [loginBusy, setLoginBusy] = useState(false);
3625
3641
  const [note, setNote] = useState(null);
3626
3642
  const alive = useRef(true);
3627
3643
  const generation = useRef(0);
3628
3644
  const inFlight = useRef(null);
3645
+ const loginAbort = useRef(null);
3629
3646
  const load = useCallback(async () => {
3630
3647
  generation.current += 1;
3631
3648
  const mine = generation.current;
@@ -3748,6 +3765,34 @@ var dsh_connect_agnes_token_plan_client = (function() {
3748
3765
  setState(body);
3749
3766
  }
3750
3767
  }, []);
3768
+ const login = useCallback(async () => {
3769
+ const controller = new AbortController();
3770
+ loginAbort.current = controller;
3771
+ setLoginBusy(true);
3772
+ setNote(null);
3773
+ try {
3774
+ const body = await postJson(AGNESCODE_PATH, { action: "login" }, controller.signal);
3775
+ if (alive.current) {
3776
+ retireInFlight();
3777
+ if (body !== null) setState(body);
3778
+ if (body?.ok === true) setNote(tt("agnescode.loginOk"));
3779
+ else setNote(format(tt("agnescode.loginFail"), { error: typeof body?.error === "string" && body.error !== "" ? body.error : tt("agnescode.loadErrorDetail") }));
3780
+ }
3781
+ } catch (why) {
3782
+ if (alive.current) {
3783
+ if (loginAbort.current === controller && controller.signal.aborted) setNote(tt("agnescode.loginCancelled"));
3784
+ else setNote(format(tt("agnescode.error"), { error: why instanceof Error ? why.message : String(why) }));
3785
+ }
3786
+ } finally {
3787
+ if (loginAbort.current === controller) loginAbort.current = null;
3788
+ if (alive.current) setLoginBusy(false);
3789
+ }
3790
+ }, [tt]);
3791
+ const cancelLogin = useCallback(() => {
3792
+ loginAbort.current?.abort();
3793
+ if (alive.current) setLoginBusy(false);
3794
+ }, []);
3795
+ const loginDisabled = harvestBusy || loginBusy;
3751
3796
  const enabled = state?.enabled === true;
3752
3797
  const view = agnescodeView(state, error);
3753
3798
  const loggedIn = view.linked;
@@ -3777,8 +3822,13 @@ var dsh_connect_agnes_token_plan_client = (function() {
3777
3822
  type: "button",
3778
3823
  style: S.button,
3779
3824
  onClick: () => void harvest(),
3780
- disabled: harvestBusy
3781
- }, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest"))), state?.bffBase !== void 0 && state?.bffBase !== "" || typeof state?.expiresAtMs === "number" && state.expiresAtMs > 0 ? h("div", null, h("div", { style: {
3825
+ disabled: loginDisabled
3826
+ }, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest")), h("button", {
3827
+ type: "button",
3828
+ style: S.button,
3829
+ onClick: () => void (loginBusy ? cancelLogin() : login()),
3830
+ disabled: !loginBusy && loginDisabled
3831
+ }, loginBusy ? tt("agnescode.cancelLogin") : tt("agnescode.login"))), state?.bffBase !== void 0 && state?.bffBase !== "" || typeof state?.expiresAtMs === "number" && state.expiresAtMs > 0 ? h("div", null, h("div", { style: {
3782
3832
  ...S.quotaFoot,
3783
3833
  marginTop: 8,
3784
3834
  color: "var(--dsw-alias-label-secondary)"
@@ -3806,14 +3856,23 @@ var dsh_connect_agnes_token_plan_client = (function() {
3806
3856
  type: "button",
3807
3857
  style: S.button,
3808
3858
  onClick: () => void harvest(),
3809
- disabled: harvestBusy
3810
- }, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest"))), h("div", { style: {
3859
+ disabled: loginDisabled
3860
+ }, harvestBusy ? tt("agnescode.harvesting") : tt("agnescode.harvest")), h("button", {
3861
+ type: "button",
3862
+ style: S.button,
3863
+ onClick: () => void (loginBusy ? cancelLogin() : login()),
3864
+ disabled: !loginBusy && loginDisabled
3865
+ }, loginBusy ? tt("agnescode.cancelLogin") : tt("agnescode.login"))), h("div", { style: {
3811
3866
  ...S.muted,
3812
3867
  fontSize: 12,
3813
3868
  marginTop: 6
3814
- } }, tt("agnescode.harvestWindowsOnly"))), h(Switch, {
3869
+ } }, tt("agnescode.harvestWindowsOnly")), h("div", { style: {
3870
+ ...S.muted,
3871
+ fontSize: 12,
3872
+ marginTop: 2
3873
+ } }, tt("agnescode.loginHint"))), h(Switch, {
3815
3874
  checked: enabled,
3816
- disabled: harvestBusy,
3875
+ disabled: loginDisabled,
3817
3876
  onChange: () => void toggle(!enabled),
3818
3877
  label: tt("agnescode.switch"),
3819
3878
  title: tt("agnescode.switchTip"),
@@ -4388,6 +4447,25 @@ var dsh_connect_agnes_token_plan_client = (function() {
4388
4447
  triggerRef
4389
4448
  ]);
4390
4449
  }
4450
+ function usePillPopover() {
4451
+ const [popoverId] = useState(nextPopoverId);
4452
+ const [open, setOpen] = useState(false);
4453
+ const close = useCallback(() => setOpen(false), []);
4454
+ const rootRef = useRef(null);
4455
+ const cardRef = useRef(null);
4456
+ const triggerRef = useRef(null);
4457
+ usePopoverClose(open, close, rootRef);
4458
+ usePopoverFocus(open, cardRef, triggerRef);
4459
+ return {
4460
+ popoverId,
4461
+ open,
4462
+ setOpen,
4463
+ close,
4464
+ rootRef,
4465
+ cardRef,
4466
+ triggerRef
4467
+ };
4468
+ }
4391
4469
  /**
4392
4470
  * The shared shell: the tier dot, the upstream prefix, the reading — and, when
4393
4471
  * the variant passed a toggle, the click affordance around it: a pointer
@@ -4457,14 +4535,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
4457
4535
  stale ? tt("composer.stale") : null
4458
4536
  ].filter(Boolean).join(" · ");
4459
4537
  const windowText = (label, pct) => `${label} ${percentText(pct)}`;
4460
- const [popoverId] = useState(nextPopoverId);
4461
- const [open, setOpen] = useState(false);
4462
- const close = useCallback(() => setOpen(false), []);
4463
- const rootRef = useRef(null);
4464
- const cardRef = useRef(null);
4465
- const triggerRef = useRef(null);
4466
- usePopoverClose(open, close, rootRef);
4467
- usePopoverFocus(open, cardRef, triggerRef);
4538
+ const { popoverId, open, setOpen, rootRef, cardRef, triggerRef } = usePillPopover();
4468
4539
  const popoverRows = [
4469
4540
  h("div", {
4470
4541
  key: "title",
@@ -4600,14 +4671,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
4600
4671
  why,
4601
4672
  stale ? tt("composer.stale") : null
4602
4673
  ].filter(Boolean).join(" · ");
4603
- const [popoverId] = useState(nextPopoverId);
4604
- const [open, setOpen] = useState(false);
4605
- const close = useCallback(() => setOpen(false), []);
4606
- const rootRef = useRef(null);
4607
- const cardRef = useRef(null);
4608
- const triggerRef = useRef(null);
4609
- usePopoverClose(open, close, rootRef);
4610
- usePopoverFocus(open, cardRef, triggerRef);
4674
+ const { popoverId, open, setOpen, rootRef, cardRef, triggerRef } = usePillPopover();
4611
4675
  const popoverRows = [
4612
4676
  h("div", {
4613
4677
  key: "bal-title",
@@ -4977,7 +5041,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
4977
5041
  bare: true,
4978
5042
  hasSnapshot: false
4979
5043
  }) : h("div", { style: S.empty }, failure === null ? tt("panel.loading") : h("div", null, h("div", { role: "alert" }, guidance ?? format(tt("panel.error"), { error: failure.message }))));
4980
- const body = h("div", null, tabBar, activeTab === "quota" && shapeWarnings.length > 0 ? h("div", {
5044
+ const body = h("div", null, activeTab === "quota" && shapeWarnings.length > 0 ? h("div", {
4981
5045
  style: S.formError,
4982
5046
  role: "status"
4983
5047
  }, format(tt("panel.shapeDrift"), { detail: shapeWarnings.map((entry) => `${tt("shape.api")} ${entry.api} ${tt("shape.missing")} ${entry.missing}`).join("; ") })) : null, activeTab === "quota" ? h("div", {
@@ -5118,7 +5182,7 @@ var dsh_connect_agnes_token_plan_client = (function() {
5118
5182
  return h("div", {
5119
5183
  style: S.page,
5120
5184
  "data-dsh-plugin": PANEL_ID
5121
- }, h("div", { style: S.headerBar }, h("div", { style: S.header }, h("span", { style: S.updated }, stamp), plan.authChip ? authChip : null, h("span", { style: S.spacer }), plan.staleWarning && failure && data ? h("span", {
5185
+ }, h("div", { style: S.headerBar }, h("div", { style: S.header }, tabBar, h("span", { style: S.spacer }), h("span", { style: S.cluster }, h("span", { style: S.updated }, stamp), plan.authChip ? authChip : null, plan.staleWarning && failure && data ? h("span", {
5122
5186
  style: S.error,
5123
5187
  role: "status",
5124
5188
  title: failure.message
@@ -5130,9 +5194,12 @@ var dsh_connect_agnes_token_plan_client = (function() {
5130
5194
  } : S.button,
5131
5195
  disabled: refresh === null,
5132
5196
  onClick: () => refresh?.()
5133
- }, refreshing && plan.refresh === "snapshot" ? tt("panel.refreshing") : tt("panel.refresh")), onClose ? h("button", {
5197
+ }, refreshing && plan.refresh === "snapshot" ? tt("panel.refreshing") : tt("panel.refresh"))), onClose ? h("button", {
5134
5198
  type: "button",
5135
- style: S.button,
5199
+ style: {
5200
+ ...S.button,
5201
+ alignSelf: "center"
5202
+ },
5136
5203
  onClick: () => onClose()
5137
5204
  }, tt("panel.back")) : null)), h("div", { style: S.scroll }, h("div", { style: S.content }, body)));
5138
5205
  }