@mars-sea/dsh-commandcode-provider 0.14.5 → 0.15.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.
package/lib/client.js CHANGED
@@ -591,7 +591,7 @@ window.__ModuleLoader__.load({
591
591
  }
592
592
  return map;
593
593
  }
594
- const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
594
+ const record$3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
595
595
  const result = () => ({
596
596
  config: "none",
597
597
  credential: "none",
@@ -605,16 +605,16 @@ window.__ModuleLoader__.load({
605
605
  });
606
606
  const fieldBaseline = (snapshot, field) => ({
607
607
  value: snapshot.value?.[field],
608
- base: record$2(snapshot.base)[field],
609
- user: record$2(snapshot.user)[field],
610
- overridden: Object.hasOwn(record$2(snapshot.user), field)
608
+ base: record$3(snapshot.base)[field],
609
+ user: record$3(snapshot.user)[field],
610
+ overridden: Object.hasOwn(record$3(snapshot.user), field)
611
611
  });
612
612
  /** 不用显示文本比较配置;数组顺序与显式覆盖/继承也是写入事实。 */
613
613
  function sameValue(a, b) {
614
614
  if (Object.is(a, b)) return true;
615
615
  if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((value, i) => sameValue(value, b[i]));
616
616
  if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
617
- const left = record$2(a), right = record$2(b);
617
+ const left = record$3(a), right = record$3(b);
618
618
  const keys = Object.keys(left);
619
619
  return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && sameValue(left[key], right[key]));
620
620
  }
@@ -657,7 +657,7 @@ window.__ModuleLoader__.load({
657
657
  snapshot.base,
658
658
  snapshot.user,
659
659
  snapshot.value
660
- ]) for (const id of Object.keys(readVisibility(record$2(layer).modelVisibility))) keys.add(id);
660
+ ]) for (const id of Object.keys(readVisibility(record$3(layer).modelVisibility))) keys.add(id);
661
661
  ops.push({
662
662
  op: "set",
663
663
  path: ["visibleModels"],
@@ -731,7 +731,7 @@ window.__ModuleLoader__.load({
731
731
  const raw = this.value("modelAccountRules");
732
732
  if (!Array.isArray(raw)) return [];
733
733
  return raw.flatMap((item) => {
734
- const rule = record$2(item);
734
+ const rule = record$3(item);
735
735
  const models = Array.isArray(rule.models) ? rule.models.filter((id) => typeof id === "string" && id !== "") : [];
736
736
  return models.length ? [{
737
737
  models,
@@ -863,7 +863,7 @@ window.__ModuleLoader__.load({
863
863
  } : { ...account })
864
864
  }]);
865
865
  } else if (intent.kind === "active") {
866
- const base = record$2(this.scope.getSnapshot().base).activeAccount;
866
+ const base = record$3(this.scope.getSnapshot().base).activeAccount;
867
867
  const op = intent.id !== "" || typeof base === "string" && base !== "" ? {
868
868
  op: "set",
869
869
  path: ["activeAccount"],
@@ -912,7 +912,7 @@ window.__ModuleLoader__.load({
912
912
  value: [.../* @__PURE__ */ new Set([...this.cleanupRefs(), intent.ref])]
913
913
  }
914
914
  ];
915
- if (this.value("activeAccount") === intent.ref) ops.push(record$2(snapshot.base).activeAccount === intent.ref ? {
915
+ if (this.value("activeAccount") === intent.ref) ops.push(record$3(snapshot.base).activeAccount === intent.ref ? {
916
916
  op: "set",
917
917
  path: ["activeAccount"],
918
918
  value: ""
@@ -945,8 +945,8 @@ window.__ModuleLoader__.load({
945
945
  outcome.issue = "closed";
946
946
  return;
947
947
  }
948
- const inherited = record$2(this.scope.getSnapshot().base).accounts;
949
- if (!(ref === this.defaultRef() || this.accounts().some((account) => account.apiKeyEnv === ref) || Array.isArray(inherited) && inherited.some((account) => record$2(account).apiKeyEnv === ref) || this.rules().some((rule) => rule.account === ref) || this.value("activeAccount") === ref) && this.facts.get(ref)?.configured === true) {
948
+ const inherited = record$3(this.scope.getSnapshot().base).accounts;
949
+ if (!(ref === this.defaultRef() || this.accounts().some((account) => account.apiKeyEnv === ref) || Array.isArray(inherited) && inherited.some((account) => record$3(account).apiKeyEnv === ref) || this.rules().some((rule) => rule.account === ref) || this.value("activeAccount") === ref) && this.facts.get(ref)?.configured === true) {
950
950
  await this.changeCredential(outcome, ref, void 0);
951
951
  if (!writeCompleted(outcome) || outcome.refreshFailed) {
952
952
  if (outcome.issue === void 0 && outcome.credential === "confirmed") outcome.issue = "cleanup-pending";
@@ -995,7 +995,7 @@ window.__ModuleLoader__.load({
995
995
  * only a generic failure — catching it here names the problem while typing).
996
996
  * Decimals pass, like the Host schema's `z.number()`.
997
997
  */
998
- function numberField$1(field, bounds) {
998
+ function numberField$2(field, bounds) {
999
999
  return {
1000
1000
  field,
1001
1001
  format: (value) => typeof value === "number" ? String(value) : "",
@@ -1046,7 +1046,7 @@ window.__ModuleLoader__.load({
1046
1046
  * `max_tokens`"), so no single numeric range can serve both.
1047
1047
  */
1048
1048
  function tokenCountField(field, bounds, isValid) {
1049
- const plain = numberField$1(field, bounds);
1049
+ const plain = numberField$2(field, bounds);
1050
1050
  return {
1051
1051
  field,
1052
1052
  format: plain.format,
@@ -1167,7 +1167,7 @@ window.__ModuleLoader__.load({
1167
1167
  min: MIN_TIMEOUT_SECONDS,
1168
1168
  max: MAX_TIMEOUT_SECONDS
1169
1169
  }),
1170
- numberField$1("transportMaxRetries", {
1170
+ numberField$2("transportMaxRetries", {
1171
1171
  min: 0,
1172
1172
  max: 50
1173
1173
  }),
@@ -1815,6 +1815,19 @@ window.__ModuleLoader__.load({
1815
1815
  function formatResetAt(ms) {
1816
1816
  return ms <= 0 ? "" : new Date(ms).toLocaleString();
1817
1817
  }
1818
+ /**
1819
+ * {@link formatResetAt} without the year and seconds ("10/21 18:41"): the
1820
+ * settings page shows several of these per card, and a reset moment never
1821
+ * needs seconds. Empty for an unknown time, like the long form.
1822
+ */
1823
+ function formatResetAtShort(ms) {
1824
+ return ms <= 0 ? "" : new Date(ms).toLocaleString(void 0, {
1825
+ month: "numeric",
1826
+ day: "numeric",
1827
+ hour: "2-digit",
1828
+ minute: "2-digit"
1829
+ });
1830
+ }
1818
1831
  /** 只有官方汇总与订阅共同确认同一起点,才展示完整账期;旧宿主和部分响应保持未知。 */
1819
1832
  function formatUsageBillingPeriod(usageStart, planStart, planEnd) {
1820
1833
  if (usageStart === void 0 || planStart !== usageStart || planEnd === void 0 || usageStart <= 0 || planEnd <= usageStart) return "";
@@ -2433,7 +2446,7 @@ window.__ModuleLoader__.load({
2433
2446
  * The shared read/validate helpers for the usage report endpoint, prefixed
2434
2447
  * so rejection messages name the offending boundary.
2435
2448
  */
2436
- const { reject: reject$1, record: record$1, stringField: stringField$1, numberField, booleanField } = makeBoundaryValidator("commandcode/report result:");
2449
+ const { reject: reject$2, record: record$2, stringField: stringField$2, numberField: numberField$1, booleanField } = makeBoundaryValidator("commandcode/report result:");
2437
2450
  /**
2438
2451
  * Parse one quota window, or undefined when the frame carries no such window.
2439
2452
  *
@@ -2444,12 +2457,12 @@ window.__ModuleLoader__.load({
2444
2457
  */
2445
2458
  function windowLimit(value, field) {
2446
2459
  if (value === void 0) return void 0;
2447
- const source = record$1(value, field);
2460
+ const source = record$2(value, field);
2448
2461
  return {
2449
- used: numberField(source, "used", `${field}.used`),
2450
- cap: numberField(source, "cap", `${field}.cap`),
2462
+ used: numberField$1(source, "used", `${field}.used`),
2463
+ cap: numberField$1(source, "cap", `${field}.cap`),
2451
2464
  exceeded: booleanField(source, "exceeded", `${field}.exceeded`),
2452
- resetAt: numberField(source, "resetAt", `${field}.resetAt`)
2465
+ resetAt: numberField$1(source, "resetAt", `${field}.resetAt`)
2453
2466
  };
2454
2467
  }
2455
2468
  /**
@@ -2458,44 +2471,44 @@ window.__ModuleLoader__.load({
2458
2471
  * malformed frame fails the boundary instead of rendering garbage.
2459
2472
  */
2460
2473
  function parseUsageReport(value) {
2461
- const source = record$1(value, "report");
2474
+ const source = record$2(value, "report");
2462
2475
  const failures = source.failures;
2463
- if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$1("failures");
2476
+ if (!Array.isArray(failures) || failures.some((entry) => typeof entry !== "string")) reject$2("failures");
2464
2477
  const report = { failures };
2465
2478
  if (source.blocked !== void 0) {
2466
2479
  const blocked = source.blocked;
2467
2480
  if (blocked === "invalid-key" || blocked === "service-unavailable" || blocked === "invalid-response" || blocked === "network") report.blocked = blocked;
2468
- else reject$1("blocked");
2481
+ else reject$2("blocked");
2469
2482
  }
2470
2483
  if (source.account !== void 0) {
2471
- const account = record$1(source.account, "account");
2484
+ const account = record$2(source.account, "account");
2472
2485
  report.account = {
2473
- id: stringField$1(account, "id", "account.id"),
2474
- name: stringField$1(account, "name", "account.name"),
2475
- userName: stringField$1(account, "userName", "account.userName")
2486
+ id: stringField$2(account, "id", "account.id"),
2487
+ name: stringField$2(account, "name", "account.name"),
2488
+ userName: stringField$2(account, "userName", "account.userName")
2476
2489
  };
2477
2490
  }
2478
2491
  if (source.usage !== void 0) {
2479
- const usage = record$1(source.usage, "usage");
2492
+ const usage = record$2(source.usage, "usage");
2480
2493
  report.usage = {
2481
- totalCount: numberField(usage, "totalCount", "usage.totalCount"),
2482
- totalCost: numberField(usage, "totalCost", "usage.totalCost"),
2483
- successRate: numberField(usage, "successRate", "usage.successRate"),
2484
- completedCount: numberField(usage, "completedCount", "usage.completedCount"),
2485
- failedCount: numberField(usage, "failedCount", "usage.failedCount"),
2486
- totalTokensIn: numberField(usage, "totalTokensIn", "usage.totalTokensIn"),
2487
- totalTokensOut: numberField(usage, "totalTokensOut", "usage.totalTokensOut"),
2488
- totalCredits: numberField(usage, "totalCredits", "usage.totalCredits"),
2489
- periodBasis: stringField$1(usage, "periodBasis", "usage.periodBasis")
2494
+ totalCount: numberField$1(usage, "totalCount", "usage.totalCount"),
2495
+ totalCost: numberField$1(usage, "totalCost", "usage.totalCost"),
2496
+ successRate: numberField$1(usage, "successRate", "usage.successRate"),
2497
+ completedCount: numberField$1(usage, "completedCount", "usage.completedCount"),
2498
+ failedCount: numberField$1(usage, "failedCount", "usage.failedCount"),
2499
+ totalTokensIn: numberField$1(usage, "totalTokensIn", "usage.totalTokensIn"),
2500
+ totalTokensOut: numberField$1(usage, "totalTokensOut", "usage.totalTokensOut"),
2501
+ totalCredits: numberField$1(usage, "totalCredits", "usage.totalCredits"),
2502
+ periodBasis: stringField$2(usage, "periodBasis", "usage.periodBasis")
2490
2503
  };
2491
- if (usage.periodStart !== void 0) report.usage.periodStart = numberField(usage, "periodStart", "usage.periodStart");
2504
+ if (usage.periodStart !== void 0) report.usage.periodStart = numberField$1(usage, "periodStart", "usage.periodStart");
2492
2505
  }
2493
2506
  if (source.credits !== void 0) {
2494
- const credits = record$1(source.credits, "credits");
2507
+ const credits = record$2(source.credits, "credits");
2495
2508
  const parsed = {
2496
- monthlyCredits: numberField(credits, "monthlyCredits", "credits.monthlyCredits"),
2497
- purchasedCredits: numberField(credits, "purchasedCredits", "credits.purchasedCredits"),
2498
- freeCredits: numberField(credits, "freeCredits", "credits.freeCredits")
2509
+ monthlyCredits: numberField$1(credits, "monthlyCredits", "credits.monthlyCredits"),
2510
+ purchasedCredits: numberField$1(credits, "purchasedCredits", "credits.purchasedCredits"),
2511
+ freeCredits: numberField$1(credits, "freeCredits", "credits.freeCredits")
2499
2512
  };
2500
2513
  if (credits.monthlyReported !== void 0) parsed.monthlyReported = booleanField(credits, "monthlyReported", "credits.monthlyReported");
2501
2514
  for (const flag of ["purchasedReported", "freeReported"]) if (credits[flag] !== void 0) parsed[flag] = booleanField(credits, flag, `credits.${flag}`);
@@ -2506,38 +2519,38 @@ window.__ModuleLoader__.load({
2506
2519
  report.credits = parsed;
2507
2520
  }
2508
2521
  if (source.plan !== void 0) {
2509
- const plan = record$1(source.plan, "plan");
2522
+ const plan = record$2(source.plan, "plan");
2510
2523
  const monthly = plan.monthlyCredits;
2511
- if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$1("plan.monthlyCredits");
2524
+ if (monthly !== null && (typeof monthly !== "number" || !Number.isFinite(monthly))) reject$2("plan.monthlyCredits");
2512
2525
  report.plan = {
2513
- planId: stringField$1(plan, "planId", "plan.planId"),
2514
- name: stringField$1(plan, "name", "plan.name"),
2515
- status: stringField$1(plan, "status", "plan.status"),
2526
+ planId: stringField$2(plan, "planId", "plan.planId"),
2527
+ name: stringField$2(plan, "name", "plan.name"),
2528
+ status: stringField$2(plan, "status", "plan.status"),
2516
2529
  monthlyCredits: monthly,
2517
- currentPeriodEnd: numberField(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
2530
+ currentPeriodEnd: numberField$1(plan, "currentPeriodEnd", "plan.currentPeriodEnd")
2518
2531
  };
2519
- if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField(plan, "currentPeriodStart", "plan.currentPeriodStart");
2532
+ if (plan.currentPeriodStart !== void 0) report.plan.currentPeriodStart = numberField$1(plan, "currentPeriodStart", "plan.currentPeriodStart");
2520
2533
  }
2521
2534
  return report;
2522
2535
  }
2523
2536
  /** Parse one untrusted boundary value into a {@link CommandCodeAccountUsage}. */
2524
2537
  function parseAccountUsage(value) {
2525
- const source = record$1(value, "account");
2538
+ const source = record$2(value, "account");
2526
2539
  return {
2527
- id: stringField$1(source, "id", "account.id"),
2528
- label: stringField$1(source, "label", "account.label"),
2540
+ id: stringField$2(source, "id", "account.id"),
2541
+ label: stringField$2(source, "label", "account.label"),
2529
2542
  configured: booleanField(source, "configured", "account.configured"),
2530
2543
  active: booleanField(source, "active", "account.active"),
2531
- mark: stringField$1(source, "mark", "account.mark"),
2532
- cooldownUntil: numberField(source, "cooldownUntil", "account.cooldownUntil"),
2544
+ mark: stringField$2(source, "mark", "account.mark"),
2545
+ cooldownUntil: numberField$1(source, "cooldownUntil", "account.cooldownUntil"),
2533
2546
  report: parseUsageReport(source.report)
2534
2547
  };
2535
2548
  }
2536
2549
  /** Parse the wire result into a {@link CommandCodeAccountsReport}. */
2537
2550
  function parseAccountsReport(value) {
2538
- const accounts = record$1(value, "result").accounts;
2551
+ const accounts = record$2(value, "result").accounts;
2539
2552
  if (Array.isArray(accounts)) return { accounts: accounts.map(parseAccountUsage) };
2540
- return reject$1("accounts");
2553
+ return reject$2("accounts");
2541
2554
  }
2542
2555
  /** The Client-face contribution mounted on `ctx.remote`. */
2543
2556
  const USAGE_REMOTE_CONTRIBUTION = {
@@ -2641,6 +2654,34 @@ window.__ModuleLoader__.load({
2641
2654
  descriptors: [makeRemoteDescriptor(PRICES_ENDPOINT, "prices", `${USAGE_REMOTE_PACKAGE}#CommandCodePriceTable`, { parse: parsePriceTable })]
2642
2655
  };
2643
2656
  //#endregion
2657
+ //#region src/service-probe-wire.ts
2658
+ /** Canonical `<namespace>/<method>` endpoint of the probe Remote. */
2659
+ const PROBE_ENDPOINT = "commandcode/probe";
2660
+ const OUTCOMES = [
2661
+ "ok",
2662
+ "http-error",
2663
+ "timeout",
2664
+ "unreachable"
2665
+ ];
2666
+ const { reject: reject$1, record: record$1, stringField: stringField$1, numberField } = makeBoundaryValidator("commandcode/probe result:");
2667
+ /** Strict result codec, shared verbatim by the Host registration and Client mount. */
2668
+ function parseProbeResult(value) {
2669
+ const source = record$1(value, "result");
2670
+ const outcome = stringField$1(source, "outcome", "outcome");
2671
+ if (!OUTCOMES.includes(outcome)) reject$1("outcome");
2672
+ return {
2673
+ outcome,
2674
+ status: numberField(source, "status", "status"),
2675
+ latencyMs: numberField(source, "latencyMs", "latencyMs"),
2676
+ checkedAt: numberField(source, "checkedAt", "checkedAt")
2677
+ };
2678
+ }
2679
+ /** The Client-face contribution mounted on `ctx.remote`. */
2680
+ const PROBE_REMOTE_CONTRIBUTION = {
2681
+ package: REMOTE_PACKAGE,
2682
+ descriptors: [makeRemoteDescriptor(PROBE_ENDPOINT, "probe", `${REMOTE_PACKAGE}#CommandCodeProbeResult`, { parse: parseProbeResult })]
2683
+ };
2684
+ //#endregion
2644
2685
  //#region src/login-wire.ts
2645
2686
  /** The canonical endpoint paths of the three login Remotes. */
2646
2687
  const LOGIN_BEGIN_ENDPOINT = "commandcode/loginBegin";
@@ -3083,37 +3124,46 @@ window.__ModuleLoader__.load({
3083
3124
  openAnalytics: "查看模型用量、成本与性能分析",
3084
3125
  nav: "Command Code",
3085
3126
  title: "Command Code",
3086
- intro: "账户操作立即生效;其余设置修改后点击底部「保存」。API 密钥仅保存在本机凭据服务中,不会回显。",
3127
+ intro: "账户操作立即生效;其余改动点底部「保存」。密钥仅存于本机凭据服务,不回显。",
3087
3128
  apiKey: "API 密钥",
3088
3129
  apiKeyHint: "在 commandcode.ai 控制台创建。留空保存不会覆盖已存储的密钥。",
3089
3130
  apiKeySet: "已配置",
3090
3131
  apiKeyUnset: "未配置",
3091
3132
  apiKeyLocked: "密钥由只读来源提供",
3092
3133
  apiBase: "API 地址",
3093
- apiBaseHint: "默认 https://api.commandcode.ai,一般无需修改。",
3094
- requestTimeoutMs: "请求超时(秒)",
3095
- requestTimeoutMsHint: "等待响应首个字节的超时;默认 300 秒(与官方 CLI 一致)。Provider API 路由要等上游模型吐出第一个 token 才返回响应头,上游慢时仍可能不够;报超时时可按提示调高。",
3096
- streamIdleTimeoutMs: "流空闲超时(秒)",
3097
- streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300 秒(长思考模型可静默数分钟,默认值刻意放宽)。",
3134
+ requestTimeoutMs: "请求超时",
3135
+ requestTimeoutMsHint: "等待首个字节的超时,默认 300 秒;Provider API 要等上游模型吐出第一个 token 才返回,上游慢时可调高。",
3136
+ streamIdleTimeoutMs: "流空闲超时",
3137
+ streamIdleTimeoutMsHint: "生成流停滞多久视为断连,默认 300 秒(长思考模型可能静默数分钟)。",
3098
3138
  transportMaxRetries: "网络失败重试次数",
3099
- transportMaxRetriesHint: "连接失败时自动重试几次,默认 5(各次等待 0.5+1+2+4+8 秒,合计约 15 秒)。超过后直接报错,不再按指数退避长时间等待;填 0 表示不重试。限流、5xx 等由服务端要求重试的失败不受此项影响。",
3139
+ transportMaxRetriesHint: "连接失败时自动重试的次数,默认 5 次(约 15 秒);填 0 不重试,限流与 5xx 的重试不受影响。",
3100
3140
  offloadSeenImagesForCache: "已看过的图片不再重复发送",
3101
- offloadSeenImagesForCacheHint: "仅 CLI 传输。模型看过图片并回复后,永久用文字占位替代旧图片,以减少新增图片时的缓存回退。模型如需重看旧图,必须重新读取文件或让用户重新附加。默认关闭。",
3141
+ offloadSeenImagesForCacheHint: "仅 CLI 传输。模型回复过的旧图片改用文字占位,减少新增图片时的缓存回退;重看需重新读取或附加。",
3102
3142
  advancedSettings: "高级设置",
3103
3143
  advancedSettingsHint: "API 地址和网络参数,一般无需修改。",
3104
- advancedOverriddenOne: "已自定义 1 项",
3105
- advancedOverriddenMany: "已自定义 {count} 项",
3106
- advancedInvalid: "高级设置中有未填好的数字,请展开修正后再保存。",
3144
+ groupOverriddenOne: "已自定义 1 项",
3145
+ groupOverriddenMany: "已自定义 {count} 项",
3146
+ groupInvalid: "有未填好的数字",
3147
+ saveLocate: "定位",
3148
+ accountsSummaryOne: "1 个账户",
3149
+ accountsSummaryMany: "{count} 个账户",
3150
+ accountsSummaryIssues: "{count} 个异常",
3151
+ visibleModelsSummary: "白名单 {count} 个",
3152
+ visibleModelsSummaryAll: "显示全部模型",
3153
+ zdrSummaryOn: "ZDR 开启",
3154
+ zdrSummaryOff: "ZDR 关闭",
3155
+ webSearchSummaryOn: "联网搜索开启",
3156
+ webSearchSummaryOff: "联网搜索关闭",
3107
3157
  filterModelsByPlan: "隐藏套餐外模型",
3108
3158
  filterModelsByPlanHint: "只显示套餐内模型;按需余额账户仍显示全部。",
3109
3159
  webSearch: "用 Command Code 承载联网搜索",
3110
- webSearchHint: "使用同一密钥承载 dsh 的 web_search;关闭后恢复原搜索后端。若「网页搜索」设置里已显式选择其他后端,本开关不生效。",
3160
+ webSearchHint: "用同一密钥承载 dsh 的 web_search;「网页搜索」里已指定其他后端时本开关不生效。",
3111
3161
  showSidebarQuota: "在侧边栏显示额度卡片",
3112
- showSidebarQuotaHint: "在侧边栏底部显示套餐与额度卡片,点击可打开完整仪表盘。默认关闭,关闭时不会为它后台刷新用量。",
3162
+ showSidebarQuotaHint: "在侧边栏底部显示套餐与额度卡片,点击打开完整仪表盘;关闭时不在后台刷新用量。",
3113
3163
  zdr: "零数据保留(ZDR)",
3114
3164
  zdrHint: "仅使用不留存、不训练的上游;可用模型可能更少且价格通常更高。",
3115
3165
  accountsTitle: "账户",
3116
- accountsHint: "账户操作立即生效,无需保存。可以添加多个账户:当前账户达到用量限额或密钥失效时,请求会自动切换到下一个。",
3166
+ accountsHint: "可以添加多个账户:当前账户达到用量限额或密钥失效时,请求会自动切换到下一个。",
3117
3167
  accountAdd: "添加账户",
3118
3168
  accountDefault: "默认账户",
3119
3169
  modelSearchPlaceholder: "搜索模型…",
@@ -3122,16 +3172,16 @@ window.__ModuleLoader__.load({
3122
3172
  modelAllowance: "${amount}/月",
3123
3173
  modelAllowanceTitle: "该模型每月可用的额度(美元)。套餐内所有模型共用同一个月度额度池,这不是额外赠送的额度。",
3124
3174
  visibleModelsTitle: "模型白名单",
3125
- visibleModelsHint: "勾选要保留的模型,模型选择器就只列出这些;一个都不勾选时则显示全部模型。这里显示网页与终端合并后的有效选择;保存后两端一致,套餐权限仍独立过滤。",
3175
+ visibleModelsHint: "模型选择器只列出勾选的模型,都不勾选则显示全部;网页与终端共用这份选择。",
3126
3176
  visibleModelsPick: "选择要保留的模型…",
3127
3177
  visibleModelsCount: "已选 {count} 个模型",
3128
3178
  visibleModelsShowAll: "显示全部",
3129
3179
  visibleModelsStaleHint: "有 {count} 个已选模型在目录中找不到了(可能已下架),不影响其他模型;可清理或保留。",
3130
3180
  visibleModelsCleanStale: "清理失效({count})",
3131
3181
  contextWindow: "自定义上下文长度",
3132
- contextWindowHint: "覆盖模型目录公布的上下文窗口(对未单独覆盖的模型生效),影响上下文计量与自动压缩;留空跟随目录。支持 32K、1M 简写。",
3182
+ contextWindowHint: "覆盖目录公布的上下文窗口,影响上下文计量与自动压缩;留空跟随目录,支持 32K、1M。",
3133
3183
  contextOverridesTitle: "按模型覆盖",
3134
- contextOverridesHint: "为单个模型单独设置上下文长度,优先于上面的全局值;模型下拉只列出模型白名单中的可见模型(显示全部时即整个目录)。",
3184
+ contextOverridesHint: "为单个模型设置上下文长度,优先于上面的全局值;只能选白名单中的模型。",
3135
3185
  contextOverrideAdd: "添加模型",
3136
3186
  contextOverrideRemove: "移除该模型的上下文覆盖",
3137
3187
  contextOverrideModel: "模型",
@@ -3141,12 +3191,17 @@ window.__ModuleLoader__.load({
3141
3191
  contextWindowTooLarge: "上下文长度不能大于 10,000,000",
3142
3192
  contextWindowInvalid: "请输入有效的整数 token 数",
3143
3193
  maxOutputTokens: "单轮输出上限",
3144
- maxOutputTokensHint: "留空=按模型推导(默认);正整数=不超过模型上限与网关上限 131072;0=不发送该字段(仅 CLI 与 OpenAI 有效,Claude 的 Messages 端点必填)。支持 64K 简写。",
3194
+ maxOutputTokensHint: "留空按模型推导;0=不发送该字段(仅 CLI 与 OpenAI,Claude 的 Messages 端点必填)。上限 131072,支持 64K。",
3145
3195
  maxOutputTokensTooSmall: "输出上限不能小于 0(0 表示不发送该字段)",
3146
3196
  maxOutputTokensTooLarge: "输出上限不能大于 131072(网关上限)",
3147
3197
  maxOutputTokensInvalid: "请输入 0 或有效的整数 token 数",
3148
- overridden: "已覆盖",
3198
+ overridden: "已自定义",
3149
3199
  reset: "重置",
3200
+ unitSeconds: "秒",
3201
+ unitTokens: "tokens",
3202
+ unitRetries: "次",
3203
+ placeholderCatalog: "跟随目录",
3204
+ placeholderAuto: "自动",
3150
3205
  invalidNumber: "无效数字",
3151
3206
  numberTooSmall: "重试次数不能小于 0",
3152
3207
  numberTooLarge: "重试次数不能大于 50",
@@ -3192,12 +3247,12 @@ window.__ModuleLoader__.load({
3192
3247
  usageTokens: "Token",
3193
3248
  usageTokensIn: "入",
3194
3249
  usageTokensOut: "出",
3195
- usageMonthly: "月额度",
3196
3250
  usagePurchased: "已购",
3197
3251
  usageFree: "赠送",
3198
3252
  usageFiveHour: "5 小时窗口",
3199
3253
  usageWeekly: "每周窗口",
3200
3254
  usageMonthlyLeft: "月额度剩余",
3255
+ usageMonthlyDepleted: "月额度已用完,已购与赠送余额将无法使用。",
3201
3256
  usageExceeded: "已超限",
3202
3257
  usageReset: "重置于",
3203
3258
  usagePartial: "部分端点数据不可用",
@@ -3222,7 +3277,7 @@ window.__ModuleLoader__.load({
3222
3277
  loginButton: "登录 Command Code",
3223
3278
  loginStarting: "正在启动本地回调服务…",
3224
3279
  loginWaiting: "等待在浏览器中完成授权;若页面未自动打开,请点击授权链接。",
3225
- loginOpenLink: "打开授权页面 ↗",
3280
+ loginOpenLink: "打开授权页面",
3226
3281
  loginCancel: "取消登录",
3227
3282
  loginSuccess: "已登录为",
3228
3283
  loginUnavailable: "此环境暂不支持登录流程,请手动粘贴密钥。",
@@ -3237,7 +3292,7 @@ window.__ModuleLoader__.load({
3237
3292
  cardRouteActive: "已启用",
3238
3293
  cardLoadingHint: "正在读取 Command Code 配置…",
3239
3294
  cardRegistrationHint: "此卡片由 Command Code 插件提供,正在等待设置页装配,稍后即可显示完整内容。",
3240
- accountsRotationHint: "账户操作立即生效。当前账户达到限额或密钥失效时,请求会按列表顺序切换到下一个可用账户。",
3295
+ accountsRotationHint: "当前账户达到限额或密钥失效时,请求会按列表顺序切换到下一个可用账户。",
3241
3296
  accountModeAuto: "使用方式:自动切换(按列表顺序)",
3242
3297
  accountModePinned: "使用方式:固定使用「{name}」,不可用时仍会自动切换",
3243
3298
  accountStatusPinned: "已固定",
@@ -3260,6 +3315,8 @@ window.__ModuleLoader__.load({
3260
3315
  accountAddPaste: "粘贴密钥",
3261
3316
  accountAddConfirm: "添加",
3262
3317
  accountAddCancel: "取消开通",
3318
+ accountAddStart: "开始登录",
3319
+ accountAddMethod: "添加方式",
3263
3320
  accountAddLinkHint: "若登录页面未自动打开,请点击下方链接。",
3264
3321
  accountAddOpenLink: "打开浏览器登录",
3265
3322
  accountNamePlaceholder: "备注名(可选,网页登录时默认使用账户名)",
@@ -3273,15 +3330,45 @@ window.__ModuleLoader__.load({
3273
3330
  accountEnrollmentCleanupRetry: "重试清理",
3274
3331
  accountRefSeparator: ":",
3275
3332
  accountModels: "专用模型",
3276
- accountModelsHint: "选中的模型优先由此账户处理,此账户不可用时仍会自动切换。一个模型只属于一个账户,选中会把它从其他账户移过来。",
3333
+ accountModelsHint: "选中的模型优先由此账户处理,不可用时仍会自动切换;一个模型只归属一个账户。",
3277
3334
  accountModelOwner: "属于 {name}",
3278
3335
  modelPick: "选择模型…",
3279
3336
  modelCount: "已选 {count} 个模型",
3280
3337
  modelCatalogFailed: "模型目录获取失败,暂时无法选择模型;已保存的设置仍会生效。",
3281
3338
  modelsTitle: "模型与上下文",
3282
- modelsInvalid: "模型与上下文中有未填好的数字,请展开修正后再保存。",
3283
3339
  privacyTitle: "隐私与安全",
3284
3340
  integrationsTitle: "集成与显示",
3341
+ serviceStatusTitle: "服务状态",
3342
+ serviceStatusHint: "官方数据来自状态页的公开记录,仅在状态变化或每日汇总时发布;本机连通由宿主刚探测。",
3343
+ serviceStatusChecking: "正在检查…",
3344
+ serviceStatusUp: "服务正常",
3345
+ serviceStatusDegraded: "部分降级",
3346
+ serviceStatusDown: "服务中断",
3347
+ serviceStatusUnknown: "状态未知",
3348
+ serviceStatusOfficial: "官方",
3349
+ serviceStatusLocal: "本机",
3350
+ serviceStatusUptime: "可用率 {value}",
3351
+ serviceStatusBadgeLocalHttp: "本机连接异常",
3352
+ serviceStatusBadgeLocalTimeout: "本机连接超时",
3353
+ serviceStatusBadgeLocalUnreachable: "本机无法连接",
3354
+ serviceStatusUptimeWeek: "7 天 {value}",
3355
+ serviceStatusUptimeMonth: "30 天 {value}",
3356
+ serviceStatusResponseWeek: "平均响应 {value}",
3357
+ serviceStatusUpdatedJustNow: "刚刚更新",
3358
+ serviceStatusUpdatedMinutes: "{count} 分钟前更新",
3359
+ serviceStatusUpdatedHours: "{count} 小时前更新",
3360
+ serviceStatusUpdatedDays: "{count} 天前更新",
3361
+ serviceStatusUpdatedUnknown: "更新时间未知",
3362
+ serviceStatusLag: "官方数据仅在状态变化或每日汇总时发布,可能滞后数小时。",
3363
+ serviceStatusLocalChecking: "正在探测…",
3364
+ serviceStatusLocalOk: "连通正常 · {latency} ms",
3365
+ serviceStatusLocalHttp: "连接异常(HTTP {status})",
3366
+ serviceStatusLocalTimeout: "连接超时",
3367
+ serviceStatusLocalUnreachable: "无法连接",
3368
+ serviceStatusLocalUnavailable: "未知(宿主未提供探测)",
3369
+ serviceStatusStale: "无法获取状态数据,以下为上次成功获取的结果。",
3370
+ serviceStatusUnavailable: "无法获取状态数据。",
3371
+ serviceStatusOpen: "打开状态页",
3285
3372
  enrollmentCreating: "正在创建账号",
3286
3373
  enrollmentWaiting: "等待浏览器登录",
3287
3374
  enrollmentWriting: "正在保存凭据",
@@ -3309,37 +3396,46 @@ window.__ModuleLoader__.load({
3309
3396
  openAnalytics: "View model usage, cost and performance",
3310
3397
  nav: "Command Code",
3311
3398
  title: "Command Code",
3312
- intro: "Account actions apply immediately; other settings are written by Save at the bottom. The API key stays in the local credential service and is never echoed.",
3399
+ intro: "Account actions apply immediately; other changes need Save below. The key stays in the local credential service and is never echoed.",
3313
3400
  apiKey: "API key",
3314
3401
  apiKeyHint: "Create one in the commandcode.ai console. Saving with this field blank keeps the stored key.",
3315
3402
  apiKeySet: "Configured",
3316
3403
  apiKeyUnset: "Not configured",
3317
3404
  apiKeyLocked: "Key provided by a read-only source",
3318
3405
  apiBase: "API base URL",
3319
- apiBaseHint: "Defaults to https://api.commandcode.ai; usually leave as-is.",
3320
- requestTimeoutMs: "Request timeout (seconds)",
3321
- requestTimeoutMsHint: "Time to wait for the first response byte; default 300 s (the official CLI’s). The Provider API route withholds headers until the upstream model emits its first token, so a slow upstream can still outlast the default; raise it when a timeout message suggests it.",
3322
- streamIdleTimeoutMs: "Stream idle timeout (seconds)",
3323
- streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300 s (deliberately generous — long-thinking models can stay silent for minutes).",
3406
+ requestTimeoutMs: "Request timeout",
3407
+ requestTimeoutMsHint: "Wait for the first response byte; default 300 s. The Provider API route answers only after the upstream’s first token, so raise it for a slow upstream.",
3408
+ streamIdleTimeoutMs: "Stream idle timeout",
3409
+ streamIdleTimeoutMsHint: "How long a stalled stream counts as dead; default 300 s (long-thinking models can go quiet for minutes).",
3324
3410
  transportMaxRetries: "Transport retries",
3325
- transportMaxRetriesHint: "How many times a failed connection is retried automatically; default 5 (waits of 0.5+1+2+4+8s, ~15s in total). After that the failure is reported instead of waiting on the exponential backoff. 0 disables the retries. Rate limits and 5xx answers keep their own, longer retry window.",
3411
+ transportMaxRetriesHint: "Automatic retries after a failed connection; default 5 (~15 s in all). 0 disables them; rate limits and 5xx keep their own retries.",
3326
3412
  offloadSeenImagesForCache: "Stop replaying images already seen",
3327
- offloadSeenImagesForCacheHint: "CLI transport only. After this model responds to an image, durably replace older pixels with text to avoid cache rollback when another image arrives. To inspect an old image again, the model must reread its file or the user must attach it again. Off by default.",
3413
+ offloadSeenImagesForCacheHint: "CLI transport only. Images the model already answered become text placeholders, so a new image no longer rolls back the cache; to see one again, reread or reattach it.",
3328
3414
  advancedSettings: "Advanced",
3329
3415
  advancedSettingsHint: "API base URL and network limits; usually leave as-is.",
3330
- advancedOverriddenOne: "1 customized",
3331
- advancedOverriddenMany: "{count} customized",
3332
- advancedInvalid: "A number in Advanced settings is not ready to save; expand to fix it.",
3416
+ groupOverriddenOne: "1 customized",
3417
+ groupOverriddenMany: "{count} customized",
3418
+ groupInvalid: "Needs a fix",
3419
+ saveLocate: "Show",
3420
+ accountsSummaryOne: "1 account",
3421
+ accountsSummaryMany: "{count} accounts",
3422
+ accountsSummaryIssues: "{count} need attention",
3423
+ visibleModelsSummary: "{count} allowlisted",
3424
+ visibleModelsSummaryAll: "All models shown",
3425
+ zdrSummaryOn: "ZDR on",
3426
+ zdrSummaryOff: "ZDR off",
3427
+ webSearchSummaryOn: "Web search on",
3428
+ webSearchSummaryOff: "Web search off",
3333
3429
  filterModelsByPlan: "Hide out-of-plan models",
3334
3430
  filterModelsByPlanHint: "Show only models in your plan; on-demand credit accounts still see the full catalog.",
3335
3431
  webSearch: "Serve dsh web search with Command Code",
3336
- webSearchHint: "Use the same key for dsh web search; turning it off restores the previous backend. When the Web search settings already pin another backend, this switch has no effect.",
3432
+ webSearchHint: "Use the same key for dsh web search. No effect while the Web search settings pin another backend.",
3337
3433
  showSidebarQuota: "Show the quota card in the sidebar",
3338
- showSidebarQuotaHint: "Show a plan and quota card at the bottom of the sidebar that opens the full dashboard. Off by default; while off, no background usage refresh runs for it.",
3434
+ showSidebarQuotaHint: "Show a plan and quota card at the bottom of the sidebar that opens the full dashboard; while off, nothing refreshes in the background.",
3339
3435
  zdr: "Zero data retention (ZDR)",
3340
3436
  zdrHint: "Use only upstreams that retain no data and do not train on requests; availability and pricing may differ.",
3341
3437
  accountsTitle: "Accounts",
3342
- accountsHint: "Account actions apply immediately, no save needed. Add more accounts and requests switch to the next one when the current account hits its usage limit or its key stops working.",
3438
+ accountsHint: "Add more accounts and requests switch to the next one when the current account hits its usage limit or its key stops working.",
3343
3439
  accountAdd: "Add account",
3344
3440
  accountDefault: "Default account",
3345
3441
  modelSearchPlaceholder: "Search models…",
@@ -3348,16 +3444,16 @@ window.__ModuleLoader__.load({
3348
3444
  modelAllowance: "${amount}/mo",
3349
3445
  modelAllowanceTitle: "Monthly usage this model can draw, in USD. Every model shares one monthly pool — this is not extra credit on top of the plan.",
3350
3446
  visibleModelsTitle: "Model allowlist",
3351
- visibleModelsHint: "Check the models you want to keep, and model pickers will list only those. If nothing is checked, every model is shown. After saving, the change applies in both web and terminal settings. This page shows their effective selection; plan access is filtered separately.",
3447
+ visibleModelsHint: "Model pickers list only the checked models; check none to show all. Web and terminal share this selection.",
3352
3448
  visibleModelsPick: "Select models to keep…",
3353
3449
  visibleModelsCount: "{count} model(s) selected",
3354
3450
  visibleModelsShowAll: "Show all",
3355
3451
  visibleModelsStaleHint: "{count} selected model(s) are no longer in the catalog (possibly retired); other models are unaffected. Clean them up or keep them.",
3356
3452
  visibleModelsCleanStale: "Clean stale ({count})",
3357
3453
  contextWindow: "Custom context length",
3358
- contextWindowHint: "Overrides the context window published by the model catalog, for every model without an override of its own below; the context meter and auto-compaction follow it. Empty follows the catalog. Shorthand like 32K or 1M is accepted.",
3454
+ contextWindowHint: "Overrides the catalog’s context window; the context meter and auto-compaction follow it. Empty follows the catalog; 32K or 1M accepted.",
3359
3455
  contextOverridesTitle: "Per-model overrides",
3360
- contextOverridesHint: "Set a context length for one model; it wins over the global value above. The picker lists only the models kept by the visible-models allowlist (the whole catalog while showing all).",
3456
+ contextOverridesHint: "Set one model’s context length; it wins over the global value above. Only allowlisted models can be picked.",
3361
3457
  contextOverrideAdd: "Add model",
3362
3458
  contextOverrideRemove: "Remove this model’s context override",
3363
3459
  contextOverrideModel: "Model",
@@ -3367,12 +3463,17 @@ window.__ModuleLoader__.load({
3367
3463
  contextWindowTooLarge: "Context length must not exceed 10,000,000",
3368
3464
  contextWindowInvalid: "Enter a valid whole-token count",
3369
3465
  maxOutputTokens: "Output limit per turn",
3370
- maxOutputTokensHint: "Empty derives it from the model (default). A positive number never exceeds the model ceiling or the gateway’s 131,072. 0 omits the field (CLI and OpenAI only; Claude’s Messages endpoint requires it). 64K shorthand accepted.",
3466
+ maxOutputTokensHint: "Empty derives it from the model. 0 omits the field (CLI and OpenAI only; Claude’s Messages endpoint requires it). Capped at 131,072; 64K accepted.",
3371
3467
  maxOutputTokensTooSmall: "Output limit must be at least 0 (0 omits the field)",
3372
3468
  maxOutputTokensTooLarge: "Output limit must not exceed 131,072 (the gateway cap)",
3373
3469
  maxOutputTokensInvalid: "Enter 0 or a valid whole-token count",
3374
- overridden: "Overridden",
3470
+ overridden: "Customized",
3375
3471
  reset: "Reset",
3472
+ unitSeconds: "s",
3473
+ unitTokens: "tokens",
3474
+ unitRetries: "times",
3475
+ placeholderCatalog: "Catalog",
3476
+ placeholderAuto: "Auto",
3376
3477
  invalidNumber: "Invalid number",
3377
3478
  numberTooSmall: "Retries must be at least 0",
3378
3479
  numberTooLarge: "Retries must not exceed 50",
@@ -3418,12 +3519,12 @@ window.__ModuleLoader__.load({
3418
3519
  usageTokens: "Tokens",
3419
3520
  usageTokensIn: "in",
3420
3521
  usageTokensOut: "out",
3421
- usageMonthly: "Monthly",
3422
3522
  usagePurchased: "Purchased",
3423
3523
  usageFree: "Free",
3424
3524
  usageFiveHour: "5-hour window",
3425
3525
  usageWeekly: "Weekly window",
3426
3526
  usageMonthlyLeft: "Monthly left",
3527
+ usageMonthlyDepleted: "Monthly credit is used up; purchased and free credit can no longer be spent.",
3427
3528
  usageExceeded: "Exceeded",
3428
3529
  usageReset: "Resets",
3429
3530
  usagePartial: "Some endpoint data unavailable",
@@ -3448,7 +3549,7 @@ window.__ModuleLoader__.load({
3448
3549
  loginButton: "Sign in to Command Code",
3449
3550
  loginStarting: "Starting the local callback server…",
3450
3551
  loginWaiting: "Waiting for authorization in your browser. If the page did not open automatically, use the authorization link.",
3451
- loginOpenLink: "Open the authorization page ↗",
3552
+ loginOpenLink: "Open the authorization page",
3452
3553
  loginCancel: "Cancel sign-in",
3453
3554
  loginSuccess: "Signed in as",
3454
3555
  loginUnavailable: "Sign-in is unavailable in this environment; paste the API key instead.",
@@ -3463,7 +3564,7 @@ window.__ModuleLoader__.load({
3463
3564
  cardRouteActive: "Active",
3464
3565
  cardLoadingHint: "Loading the Command Code configuration…",
3465
3566
  cardRegistrationHint: "This card is contributed by the Command Code plugin and is waiting for the settings page to finish assembling.",
3466
- accountsRotationHint: "Account actions apply immediately. When the current account hits its limit or its key stops working, requests switch to the next usable account in list order.",
3567
+ accountsRotationHint: "When the current account hits its limit or its key stops working, requests switch to the next usable account in list order.",
3467
3568
  accountModeAuto: "Mode: switch automatically (list order)",
3468
3569
  accountModePinned: "Mode: pinned to \"{name}\", still switching when it is unavailable",
3469
3570
  accountStatusPinned: "Pinned",
@@ -3486,6 +3587,8 @@ window.__ModuleLoader__.load({
3486
3587
  accountAddPaste: "Paste key",
3487
3588
  accountAddConfirm: "Add",
3488
3589
  accountAddCancel: "Cancel setup",
3590
+ accountAddStart: "Start sign-in",
3591
+ accountAddMethod: "Method",
3489
3592
  accountAddLinkHint: "If the sign-in page did not open automatically, use the link below.",
3490
3593
  accountAddOpenLink: "Open the sign-in page",
3491
3594
  accountNamePlaceholder: "Name (optional; sign-in uses the account name)",
@@ -3499,15 +3602,45 @@ window.__ModuleLoader__.load({
3499
3602
  accountEnrollmentCleanupRetry: "Retry cleanup",
3500
3603
  accountRefSeparator: ": ",
3501
3604
  accountModels: "Dedicated models",
3502
- accountModelsHint: "These models prefer this account and still switch when it is unavailable. A model belongs to one account; selecting it here moves it from any other.",
3605
+ accountModelsHint: "These models prefer this account and still switch when it is unavailable; a model belongs to one account.",
3503
3606
  accountModelOwner: "on {name}",
3504
3607
  modelPick: "Choose models…",
3505
3608
  modelCount: "{count} models selected",
3506
3609
  modelCatalogFailed: "The model catalog could not be loaded; saved settings still apply.",
3507
3610
  modelsTitle: "Models & context",
3508
- modelsInvalid: "A number in Models & context is not ready to save; expand to fix it.",
3509
3611
  privacyTitle: "Privacy & security",
3510
3612
  integrationsTitle: "Integrations & display",
3613
+ serviceStatusTitle: "Service status",
3614
+ serviceStatusHint: "Published data comes from the official status page and is only written on a status change or the daily summary; local connectivity is probed by the host just now.",
3615
+ serviceStatusChecking: "Checking…",
3616
+ serviceStatusUp: "Operational",
3617
+ serviceStatusDegraded: "Degraded performance",
3618
+ serviceStatusDown: "Service outage",
3619
+ serviceStatusUnknown: "Status unknown",
3620
+ serviceStatusOfficial: "Official",
3621
+ serviceStatusLocal: "This device",
3622
+ serviceStatusUptime: "uptime {value}",
3623
+ serviceStatusBadgeLocalHttp: "Connection error here",
3624
+ serviceStatusBadgeLocalTimeout: "Timing out here",
3625
+ serviceStatusBadgeLocalUnreachable: "Unreachable here",
3626
+ serviceStatusUptimeWeek: "7d {value}",
3627
+ serviceStatusUptimeMonth: "30d {value}",
3628
+ serviceStatusResponseWeek: "avg {value}",
3629
+ serviceStatusUpdatedJustNow: "updated just now",
3630
+ serviceStatusUpdatedMinutes: "updated {count} min ago",
3631
+ serviceStatusUpdatedHours: "updated {count} h ago",
3632
+ serviceStatusUpdatedDays: "updated {count} d ago",
3633
+ serviceStatusUpdatedUnknown: "update time unknown",
3634
+ serviceStatusLag: "Official data is only published on a status change or the daily summary, so it may lag by hours.",
3635
+ serviceStatusLocalChecking: "Probing…",
3636
+ serviceStatusLocalOk: "Reachable · {latency} ms",
3637
+ serviceStatusLocalHttp: "Error (HTTP {status})",
3638
+ serviceStatusLocalTimeout: "Timed out",
3639
+ serviceStatusLocalUnreachable: "Unreachable",
3640
+ serviceStatusLocalUnavailable: "Unknown (host provides no probe)",
3641
+ serviceStatusStale: "Status data unavailable; showing the last known result.",
3642
+ serviceStatusUnavailable: "Status data unavailable.",
3643
+ serviceStatusOpen: "Open status page",
3511
3644
  enrollmentCreating: "Creating the account",
3512
3645
  enrollmentWaiting: "Waiting for the browser sign-in",
3513
3646
  enrollmentWriting: "Saving the credential",
@@ -3704,7 +3837,7 @@ window.__ModuleLoader__.load({
3704
3837
  }
3705
3838
  //#endregion
3706
3839
  //#region package.json
3707
- var version = "0.14.5";
3840
+ var version = "0.15.0";
3708
3841
  var repository = {
3709
3842
  "type": "git",
3710
3843
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -3876,6 +4009,392 @@ window.__ModuleLoader__.load({
3876
4009
  return hintOf(version);
3877
4010
  }
3878
4011
  //#endregion
4012
+ //#region src/client/service-status.ts
4013
+ /** The public status page the row links to. */
4014
+ const STATUS_PAGE_URL = "https://status.commandcode.ai/";
4015
+ /** Upptime data files, freshest host first. */
4016
+ const STATUS_SOURCES = ["https://raw.githubusercontent.com/CommandCodeAI/commandcode-status/master/history", "https://cdn.jsdelivr.net/gh/CommandCodeAI/commandcode-status@master/history"];
4017
+ /** Files inside a source directory (the monitor's naming, not ours to change). */
4018
+ const SUMMARY_FILE = "summary.json";
4019
+ const STATE_FILE = "command-code.yml";
4020
+ /** The one site the Upptime config monitors. */
4021
+ const MONITORED_URL = "https://api.commandcode.ai";
4022
+ const MONITORED_SLUG = "command-code";
4023
+ /** The `localStorage` key holding the last successful snapshot. */
4024
+ const SERVICE_STATUS_CACHE_KEY = "@mars-sea/dsh-commandcode-provider/service-status";
4025
+ const LEVELS = [
4026
+ "up",
4027
+ "degraded",
4028
+ "down",
4029
+ "unknown"
4030
+ ];
4031
+ function levelOf(value) {
4032
+ return typeof value === "string" && LEVELS.includes(value) ? value : void 0;
4033
+ }
4034
+ function stringOrUndefined(value) {
4035
+ return typeof value === "string" && value !== "" ? value : void 0;
4036
+ }
4037
+ function finiteNumber(value) {
4038
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4039
+ }
4040
+ /**
4041
+ * Read the monitored site's verdict out of `summary.json`.
4042
+ *
4043
+ * The file is an array because an Upptime config may monitor several sites; we
4044
+ * match our API host, then the known slug, then the first record so a rename on
4045
+ * their side degrades to "show what the page shows" rather than to nothing.
4046
+ * Rejects only when the payload cannot answer at all — a rejection makes the
4047
+ * caller try the next mirror.
4048
+ */
4049
+ function parseSummary(payload) {
4050
+ if (!Array.isArray(payload)) throw new Error("status summary is not an array");
4051
+ const records = payload.filter((entry) => typeof entry === "object" && entry !== null);
4052
+ const record = records.find((entry) => entry["url"] === MONITORED_URL) ?? records.find((entry) => entry["slug"] === MONITORED_SLUG) ?? records[0];
4053
+ if (record === void 0) throw new Error("status summary has no site record");
4054
+ const level = levelOf(record["status"]);
4055
+ if (level === void 0) throw new Error("status summary has no usable status");
4056
+ return {
4057
+ level,
4058
+ uptimeWeek: stringOrUndefined(record["uptimeWeek"]),
4059
+ uptimeMonth: stringOrUndefined(record["uptimeMonth"]),
4060
+ responseTimeWeekMs: finiteNumber(record["timeWeek"])
4061
+ };
4062
+ }
4063
+ /**
4064
+ * Read the monitor's own state file (`key: value` YAML the robot writes).
4065
+ *
4066
+ * Only scalar lines are needed, so this stays a line parser: pulling a YAML
4067
+ * library into the client bundle for five fields would be the larger risk. A
4068
+ * malformed line is ignored; the caller treats missing fields as unknown rather
4069
+ * than failing the whole source, because the summary (not this file) is what
4070
+ * carries the verdict.
4071
+ */
4072
+ function parseStateFile(text) {
4073
+ const fields = /* @__PURE__ */ new Map();
4074
+ for (const line of text.split("\n")) {
4075
+ const match = /^([A-Za-z][A-Za-z0-9]*):[ \t]*(.*)$/.exec(line.trim());
4076
+ if (match !== null) fields.set(match[1], match[2].trim());
4077
+ }
4078
+ const checkedRaw = fields.get("lastUpdated");
4079
+ const checkedAt = checkedRaw === void 0 ? NaN : Date.parse(checkedRaw);
4080
+ const codeRaw = fields.get("code");
4081
+ const code = codeRaw === void 0 ? NaN : Number.parseInt(codeRaw, 10);
4082
+ const responseRaw = fields.get("responseTime");
4083
+ const responseTime = responseRaw === void 0 ? NaN : Number.parseFloat(responseRaw);
4084
+ return {
4085
+ level: levelOf(fields.get("status")),
4086
+ checkedAt: Number.isFinite(checkedAt) ? checkedAt : void 0,
4087
+ responseTimeMs: Number.isFinite(responseTime) ? responseTime : void 0,
4088
+ code: Number.isFinite(code) ? code : void 0
4089
+ };
4090
+ }
4091
+ /** Fetch one text resource with a hard timeout; rejects on any non-OK answer. */
4092
+ async function fetchText(url, fetchImpl, timeoutMs) {
4093
+ const controller = new AbortController();
4094
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
4095
+ try {
4096
+ const response = await fetchImpl(url, { signal: controller.signal });
4097
+ if (!response.ok) throw new Error(`status source responded ${response.status}`);
4098
+ return await response.text();
4099
+ } finally {
4100
+ clearTimeout(timer);
4101
+ }
4102
+ }
4103
+ /** Read one mirror: the summary is required, the state file is best-effort. */
4104
+ async function readSource(base, fetchImpl, timeoutMs) {
4105
+ const [summaryText, stateText] = await Promise.all([fetchText(`${base}/${SUMMARY_FILE}`, fetchImpl, timeoutMs), fetchText(`${base}/${STATE_FILE}`, fetchImpl, timeoutMs).catch(() => void 0)]);
4106
+ return {
4107
+ summary: parseSummary(JSON.parse(summaryText)),
4108
+ state: stateText === void 0 ? {} : parseStateFile(stateText)
4109
+ };
4110
+ }
4111
+ /**
4112
+ * A {@link ServiceStatusStore} backed by `localStorage`. Tolerates a missing or
4113
+ * throwing storage (private modes): reads yield `undefined`, writes are
4114
+ * dropped, and the status row still works for the lifetime of the page.
4115
+ */
4116
+ function localStorageServiceStatusStore(storage = typeof localStorage === "undefined" ? void 0 : localStorage) {
4117
+ return {
4118
+ read() {
4119
+ if (storage === void 0) return void 0;
4120
+ try {
4121
+ const raw = storage.getItem(SERVICE_STATUS_CACHE_KEY);
4122
+ if (raw === null) return void 0;
4123
+ return snapshotFrom(JSON.parse(raw));
4124
+ } catch {
4125
+ return;
4126
+ }
4127
+ },
4128
+ write(snapshot) {
4129
+ if (storage === void 0) return;
4130
+ try {
4131
+ storage.setItem(SERVICE_STATUS_CACHE_KEY, JSON.stringify(snapshot));
4132
+ } catch {}
4133
+ }
4134
+ };
4135
+ }
4136
+ /** Validate a cached payload; storage may have been written by an older build. */
4137
+ function snapshotFrom(value) {
4138
+ if (typeof value !== "object" || value === null) return void 0;
4139
+ const record = value;
4140
+ const level = levelOf(record["level"]);
4141
+ const fetchedAt = finiteNumber(record["fetchedAt"]);
4142
+ if (level === void 0 || fetchedAt === void 0) return void 0;
4143
+ return {
4144
+ level,
4145
+ checkedAt: finiteNumber(record["checkedAt"]),
4146
+ uptimeWeek: stringOrUndefined(record["uptimeWeek"]),
4147
+ uptimeMonth: stringOrUndefined(record["uptimeMonth"]),
4148
+ responseTimeWeekMs: finiteNumber(record["responseTimeWeekMs"]),
4149
+ live: record["live"] === true,
4150
+ stale: record["stale"] === true,
4151
+ fetchedAt
4152
+ };
4153
+ }
4154
+ /** The cached snapshot, for an instant first paint before the check lands. */
4155
+ function readCachedServiceStatus(store) {
4156
+ return store.read();
4157
+ }
4158
+ /**
4159
+ * Resolve the service status, preferring the cache while it is fresh and the
4160
+ * freshest mirror otherwise. Resolves with a snapshot in every case; the only
4161
+ * "failure" the caller sees is `live: false` (+ `stale` when a cache existed).
4162
+ */
4163
+ async function checkServiceStatus(options) {
4164
+ const now = options.now ?? Date.now();
4165
+ const cached = options.store.read();
4166
+ if (cached !== void 0 && now - cached.fetchedAt < 3e5) return cached;
4167
+ const fetchImpl = options.fetchImpl ?? fetch;
4168
+ const sources = options.sources ?? STATUS_SOURCES;
4169
+ const timeoutMs = options.timeoutMs ?? 5e3;
4170
+ for (const source of sources) try {
4171
+ const { summary, state } = await readSource(source, fetchImpl, timeoutMs);
4172
+ const snapshot = {
4173
+ level: state.level ?? summary.level,
4174
+ checkedAt: state.checkedAt,
4175
+ uptimeWeek: summary.uptimeWeek,
4176
+ uptimeMonth: summary.uptimeMonth,
4177
+ responseTimeWeekMs: summary.responseTimeWeekMs ?? state.responseTimeMs,
4178
+ live: true,
4179
+ stale: false,
4180
+ fetchedAt: now
4181
+ };
4182
+ options.store.write(snapshot);
4183
+ return snapshot;
4184
+ } catch {}
4185
+ if (cached !== void 0) return {
4186
+ ...cached,
4187
+ live: false,
4188
+ stale: true
4189
+ };
4190
+ return {
4191
+ level: "unknown",
4192
+ live: false,
4193
+ stale: false,
4194
+ fetchedAt: now
4195
+ };
4196
+ }
4197
+ /** The published record's age: `官方数据更新:刚刚` / N minutes / hours / days. */
4198
+ function updatedCopy(checkedAt, now, t) {
4199
+ const age = Math.max(0, now - checkedAt);
4200
+ if (age < 6e4) return t("serviceStatusUpdatedJustNow");
4201
+ const minutes = Math.floor(age / 6e4);
4202
+ if (minutes < 60) return t("serviceStatusUpdatedMinutes", { count: minutes });
4203
+ const hours = Math.floor(minutes / 60);
4204
+ if (hours < 24) return t("serviceStatusUpdatedHours", { count: hours });
4205
+ return t("serviceStatusUpdatedDays", { count: Math.floor(hours / 24) });
4206
+ }
4207
+ /** The publication-lag explanation, shown once the record is old enough. */
4208
+ function lagCopy(checkedAt, now, t) {
4209
+ if (checkedAt === void 0) return void 0;
4210
+ return now - checkedAt >= 72e5 ? t("serviceStatusLag") : void 0;
4211
+ }
4212
+ /** The local-connectivity line and its dot tone. */
4213
+ function localCopy(probe, t) {
4214
+ if (probe.kind === "checking") return {
4215
+ text: t("serviceStatusLocalChecking"),
4216
+ tone: "muted"
4217
+ };
4218
+ if (probe.kind === "unavailable") return {
4219
+ text: t("serviceStatusLocalUnavailable"),
4220
+ tone: "muted"
4221
+ };
4222
+ const { result } = probe;
4223
+ if (result.outcome === "ok") return {
4224
+ text: t("serviceStatusLocalOk", { latency: result.latencyMs }),
4225
+ tone: "ok"
4226
+ };
4227
+ if (result.outcome === "http-error") return {
4228
+ text: t("serviceStatusLocalHttp", { status: result.status }),
4229
+ tone: "error"
4230
+ };
4231
+ if (result.outcome === "timeout") return {
4232
+ text: t("serviceStatusLocalTimeout"),
4233
+ tone: "error"
4234
+ };
4235
+ return {
4236
+ text: t("serviceStatusLocalUnreachable"),
4237
+ tone: "error"
4238
+ };
4239
+ }
4240
+ /**
4241
+ * A published uptime without its padding zeros: the summary prints two
4242
+ * decimals verbatim (`100.00%`), which reads as noise, so `100.00%` becomes
4243
+ * `100%` and `99.90%` becomes `99.9%`; anything that is not a plain percentage
4244
+ * is shown as published.
4245
+ */
4246
+ function compactUptime(value) {
4247
+ const match = /^(\d+)(?:\.(\d+))?%$/.exec(value.trim());
4248
+ if (match === null) return value;
4249
+ const fraction = (match[2] ?? "").replace(/0+$/, "");
4250
+ return fraction === "" ? `${match[1]}%` : `${match[1]}.${fraction}%`;
4251
+ }
4252
+ /** The aggregate line, or undefined when this check carried no aggregates. */
4253
+ function detailCopy(snapshot, t) {
4254
+ const parts = [];
4255
+ const uptimes = [];
4256
+ if (snapshot.uptimeWeek !== void 0) uptimes.push(t("serviceStatusUptimeWeek", { value: compactUptime(snapshot.uptimeWeek) }));
4257
+ if (snapshot.uptimeMonth !== void 0) uptimes.push(t("serviceStatusUptimeMonth", { value: compactUptime(snapshot.uptimeMonth) }));
4258
+ if (uptimes.length > 0) parts.push(t("serviceStatusUptime", { value: uptimes.join(" / ") }));
4259
+ if (snapshot.responseTimeWeekMs !== void 0) parts.push(t("serviceStatusResponseWeek", { value: `${Math.round(snapshot.responseTimeWeekMs)} ms` }));
4260
+ return parts.length === 0 ? void 0 : parts.join(" · ");
4261
+ }
4262
+ /** The heading verdict: a failed local probe first, else the published one. */
4263
+ function badgeOf(probe, official, t) {
4264
+ if (probe.kind !== "result" || probe.result.outcome === "ok") return official;
4265
+ const { outcome } = probe.result;
4266
+ return {
4267
+ label: outcome === "http-error" ? t("serviceStatusBadgeLocalHttp") : outcome === "timeout" ? t("serviceStatusBadgeLocalTimeout") : t("serviceStatusBadgeLocalUnreachable"),
4268
+ tone: "warn"
4269
+ };
4270
+ }
4271
+ /**
4272
+ * Project a snapshot + the live probe into the row's copy. Pure and React-free
4273
+ * so the labels, the age buckets, the lag rule and the failure wording are
4274
+ * pinned by node tests rather than by rendering.
4275
+ *
4276
+ * `snapshot === undefined` means the first published-data check is still in
4277
+ * flight (the hook paints the cache first, so this only shows on a cold cache).
4278
+ * The probe is independent: the row always renders both halves, and the local
4279
+ * half never borrows the published verdict.
4280
+ */
4281
+ function serviceStatusView(snapshot, probe, t, now) {
4282
+ const local = localCopy(probe, t);
4283
+ const hint = t("serviceStatusHint");
4284
+ if (snapshot === void 0) {
4285
+ const label = t("serviceStatusChecking");
4286
+ return {
4287
+ level: "unknown",
4288
+ checking: true,
4289
+ label,
4290
+ officialTone: "checking",
4291
+ badge: badgeOf(probe, {
4292
+ label,
4293
+ tone: "checking"
4294
+ }, t),
4295
+ local: local.text,
4296
+ localTone: local.tone,
4297
+ localChecking: probe.kind === "checking",
4298
+ hint,
4299
+ linkLabel: t("serviceStatusOpen")
4300
+ };
4301
+ }
4302
+ const label = snapshot.level === "up" ? t("serviceStatusUp") : snapshot.level === "degraded" ? t("serviceStatusDegraded") : snapshot.level === "down" ? t("serviceStatusDown") : t("serviceStatusUnknown");
4303
+ const notice = snapshot.live ? void 0 : snapshot.stale ? t("serviceStatusStale") : t("serviceStatusUnavailable");
4304
+ const officialTone = snapshot.level === "up" ? "ok" : snapshot.level === "degraded" ? "warn" : snapshot.level === "down" ? "error" : "idle";
4305
+ return {
4306
+ level: snapshot.level,
4307
+ checking: false,
4308
+ label,
4309
+ officialTone,
4310
+ badge: badgeOf(probe, {
4311
+ label,
4312
+ tone: officialTone
4313
+ }, t),
4314
+ detail: detailCopy(snapshot, t),
4315
+ updated: snapshot.checkedAt === void 0 ? t("serviceStatusUpdatedUnknown") : updatedCopy(snapshot.checkedAt, now, t),
4316
+ lag: lagCopy(snapshot.checkedAt, now, t),
4317
+ local: local.text,
4318
+ localTone: local.tone,
4319
+ localChecking: probe.kind === "checking",
4320
+ notice,
4321
+ hint,
4322
+ linkLabel: t("serviceStatusOpen")
4323
+ };
4324
+ }
4325
+ //#endregion
4326
+ //#region src/client/group-fold.ts
4327
+ const FOLD_GROUPS = [
4328
+ "accounts",
4329
+ "models",
4330
+ "privacy",
4331
+ "integrations",
4332
+ "advanced"
4333
+ ];
4334
+ /** First visit: only Accounts open. */
4335
+ const DEFAULT_FOLD_STATE = {
4336
+ accounts: true,
4337
+ models: false,
4338
+ privacy: false,
4339
+ integrations: false,
4340
+ advanced: false
4341
+ };
4342
+ /** The `localStorage` key holding the serialized {@link FoldState}. */
4343
+ const FOLD_STATE_KEY = "@mars-sea/dsh-commandcode-provider/settings-fold";
4344
+ /**
4345
+ * Read a stored value back. Anything unusable — absent, not JSON, not an
4346
+ * object — yields the defaults; within an object, each group takes its stored
4347
+ * flag only when it is a real boolean, so a partial or older record keeps the
4348
+ * groups it knows and defaults the rest.
4349
+ */
4350
+ function parseFoldState(raw) {
4351
+ if (raw === null || raw === void 0 || raw === "") return DEFAULT_FOLD_STATE;
4352
+ let parsed;
4353
+ try {
4354
+ parsed = JSON.parse(raw);
4355
+ } catch {
4356
+ return DEFAULT_FOLD_STATE;
4357
+ }
4358
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return DEFAULT_FOLD_STATE;
4359
+ const record = parsed;
4360
+ const next = { ...DEFAULT_FOLD_STATE };
4361
+ for (const group of FOLD_GROUPS) {
4362
+ const value = record[group];
4363
+ if (typeof value === "boolean") next[group] = value;
4364
+ }
4365
+ return next;
4366
+ }
4367
+ /** Open the given groups, leaving the rest as they are (the save bar's locate action). */
4368
+ function expandGroups(state, groups) {
4369
+ if (groups.every((group) => state[group])) return state;
4370
+ const next = { ...state };
4371
+ for (const group of groups) next[group] = true;
4372
+ return next;
4373
+ }
4374
+ /**
4375
+ * A {@link FoldStateStore} backed by `localStorage`. A missing or throwing
4376
+ * storage (private mode, blocked site data) reads as the defaults and drops
4377
+ * writes: the page must render the same either way.
4378
+ */
4379
+ function localStorageFoldStore(storage = typeof localStorage === "undefined" ? void 0 : localStorage) {
4380
+ return {
4381
+ read() {
4382
+ if (storage === void 0) return DEFAULT_FOLD_STATE;
4383
+ try {
4384
+ return parseFoldState(storage.getItem(FOLD_STATE_KEY));
4385
+ } catch {
4386
+ return DEFAULT_FOLD_STATE;
4387
+ }
4388
+ },
4389
+ write(state) {
4390
+ if (storage === void 0) return;
4391
+ try {
4392
+ storage.setItem(FOLD_STATE_KEY, JSON.stringify(state));
4393
+ } catch {}
4394
+ }
4395
+ };
4396
+ }
4397
+ //#endregion
3879
4398
  //#region src/client/section.tsx
3880
4399
  /**
3881
4400
  * React component for the "Command Code" settings page (browser half).
@@ -3889,21 +4408,172 @@ window.__ModuleLoader__.load({
3889
4408
  * actions and dedicated models — every operation commits immediately), Models
3890
4409
  * & context (plan filter + visible-model allowlist, then the context-length
3891
4410
  * rows: the global custom window and the per-model overrides, issue #86),
3892
- * Privacy & security (ZDR), Integrations & display (web search, sidebar quota
3893
- * card), and a collapsed Advanced card (API base + network limits). Everything
4411
+ * Privacy & security (ZDR), Integrations & display (service status, web search,
4412
+ * sidebar quota card), and Advanced (API base + network limits). Everything
3894
4413
  * below Accounts is a flat group of rows (title/description left, control
3895
4414
  * right) inside the staged form the floating save bar writes.
3896
4415
  *
3897
- * Two of those groups fold from their own heading — Models & context (issue
3898
- * #94) and Advanced — and both start collapsed on every visit, with no
3899
- * remembered state. While a group is collapsed its rows are not on screen, so
3900
- * anything inside it that blocks the save is reported on the heading itself
3901
- * (see `CollapsibleGroup`); the save bar alone would leave the disabled Save
3902
- * button unexplained.
4416
+ * All five groups fold from their own heading (`FoldableGroup`, 决策记录第十章
4417
+ * 第4条): Accounts opens by default, the rest start folded, and the choice is
4418
+ * remembered per browser (./group-fold.ts). A folded heading still summarizes its group, and anything
4419
+ * inside it that blocks the save is reported on the heading; the save bar's
4420
+ * Locate action opens that group and focuses the invalid field.
3903
4421
  *
3904
4422
  * Styles are injected once by the client entry (see src/client/index.ts) and
3905
4423
  * class-prefixed `cc-` to stay local.
3906
4424
  */
4425
+ const ICON_PATHS = {
4426
+ swap: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 5h9l-2-2" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13.5 11h-9l2 2" })] }),
4427
+ chevron: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 6l4 4 4-4" }),
4428
+ more: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4430
+ cx: "8",
4431
+ cy: "3.5",
4432
+ r: ".9",
4433
+ fill: "currentColor",
4434
+ stroke: "none"
4435
+ }),
4436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4437
+ cx: "8",
4438
+ cy: "8",
4439
+ r: ".9",
4440
+ fill: "currentColor",
4441
+ stroke: "none"
4442
+ }),
4443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4444
+ cx: "8",
4445
+ cy: "12.5",
4446
+ r: ".9",
4447
+ fill: "currentColor",
4448
+ stroke: "none"
4449
+ })
4450
+ ] }),
4451
+ chart: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 13.5h11" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4.5 13.5V8.5M8 13.5V4.5M11.5 13.5V6.5" })] }),
4452
+ arrowRight: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 8h10M9 4l4 4-4 4" }),
4453
+ check: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4454
+ cx: "8",
4455
+ cy: "8",
4456
+ r: "6"
4457
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.5 8.2 7.2 9.8 10.5 6.5" })] }),
4458
+ reset: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 8a5.5 5.5 0 1 0 1.6-3.9" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 2.5v3h3" })] }),
4459
+ close: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" }),
4460
+ refresh: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13.5 2.5v3h-3" })] }),
4461
+ plus: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" }),
4462
+ external: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M9 2.5h4.5V7" }),
4464
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13.5 2.5 7.5 8.5" }),
4465
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M11.5 9.5v3a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1h3" })
4466
+ ] }),
4467
+ info: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4468
+ cx: "8",
4469
+ cy: "8",
4470
+ r: "6"
4471
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 7.25v3.5M8 5.25v.01" })] }),
4472
+ warn: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 2.5 14 13H2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 6.5v3M8 11.25v.01" })] }),
4473
+ error: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
4474
+ cx: "8",
4475
+ cy: "8",
4476
+ r: "6"
4477
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6 6l4 4M10 6l-4 4" })] }),
4478
+ lock: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
4479
+ x: "3",
4480
+ y: "7",
4481
+ width: "10",
4482
+ height: "7",
4483
+ rx: "1.5"
4484
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.5 7V5a2.5 2.5 0 0 1 5 0v2" })] })
4485
+ };
4486
+ function Icon({ name, className = "cc-icon" }) {
4487
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4488
+ className,
4489
+ viewBox: "0 0 16 16",
4490
+ width: "16",
4491
+ height: "16",
4492
+ fill: "none",
4493
+ stroke: "currentColor",
4494
+ strokeWidth: "1.5",
4495
+ strokeLinecap: "round",
4496
+ strokeLinejoin: "round",
4497
+ "aria-hidden": "true",
4498
+ children: ICON_PATHS[name]
4499
+ });
4500
+ }
4501
+ const PAGE_BUTTON_VARIANT = {
4502
+ primary: "cc-btnPrimary",
4503
+ outline: "cc-btnSecondary",
4504
+ ghost: "cc-btnGhost",
4505
+ danger: "cc-btnDanger"
4506
+ };
4507
+ const PAGE_BUTTON_SIZE = {
4508
+ sm: " cc-btnSm",
4509
+ md: "",
4510
+ lg: " cc-btnLg"
4511
+ };
4512
+ function PageButton({ variant = "ghost", size = "md", icon, className, type = "button", children, ...rest }) {
4513
+ const classes = `cc-btn ${PAGE_BUTTON_VARIANT[variant]}${PAGE_BUTTON_SIZE[size]}${className === void 0 ? "" : ` ${className}`}`;
4514
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4515
+ ...rest,
4516
+ type,
4517
+ className: classes,
4518
+ children: [icon, children]
4519
+ });
4520
+ }
4521
+ const NOTICE_ICON = {
4522
+ neutral: "info",
4523
+ success: "check",
4524
+ warn: "warn",
4525
+ error: "error"
4526
+ };
4527
+ const NOTICE_CLASS = {
4528
+ neutral: "cc-notice",
4529
+ success: "cc-notice cc-noticeSuccess",
4530
+ warn: "cc-notice cc-noticeWarn",
4531
+ error: "cc-notice cc-noticeError"
4532
+ };
4533
+ /**
4534
+ * The one block every result, warning and failure on this page uses (read-only
4535
+ * config, a failed account operation, a cleanup still owed, …), so status no
4536
+ * longer arrives as loose red paragraphs in a different shape each time.
4537
+ * Actions sit under the text; the caller keeps the live-region role.
4538
+ */
4539
+ function Notice({ tone = "neutral", icon, title, actions, role = "status", label, children }) {
4540
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4541
+ className: NOTICE_CLASS[tone],
4542
+ role,
4543
+ "aria-label": label,
4544
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
4545
+ name: icon ?? NOTICE_ICON[tone],
4546
+ className: "cc-noticeIcon"
4547
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4548
+ className: "cc-noticeBody",
4549
+ children: [
4550
+ title !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4551
+ className: "cc-noticeTitle",
4552
+ children: title
4553
+ }) : null,
4554
+ children,
4555
+ actions !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4556
+ className: "cc-noticeActions",
4557
+ children: actions
4558
+ }) : null
4559
+ ]
4560
+ })]
4561
+ });
4562
+ }
4563
+ /**
4564
+ * The Host-side defaults an empty field falls back to, shown as placeholders so
4565
+ * an empty box reads "300 s" rather than looking broken. They are literals
4566
+ * because the client bundle must not pull in the adapter; `tests/section.test.ts`
4567
+ * pins each one to the constant it mirrors (DEFAULT_API_BASE,
4568
+ * DEFAULT_REQUEST_TIMEOUT_MS / DEFAULT_STREAM_IDLE_TIMEOUT_MS in seconds,
4569
+ * DEFAULT_TRANSPORT_MAX_RETRIES).
4570
+ */
4571
+ const FIELD_DEFAULTS = {
4572
+ apiBase: "https://api.commandcode.ai",
4573
+ requestTimeoutSeconds: "300",
4574
+ streamIdleTimeoutSeconds: "300",
4575
+ transportMaxRetries: "5"
4576
+ };
3907
4577
  const ADVANCED_FIELDS = [
3908
4578
  "apiBase",
3909
4579
  "requestTimeoutMs",
@@ -3933,16 +4603,76 @@ window.__ModuleLoader__.load({
3933
4603
  function modelsGroupInvalid(state) {
3934
4604
  return MODELS_FIELDS.some((field) => state[field].invalid) || state.contextWindowOverrides.some((row) => row.invalid);
3935
4605
  }
3936
- /** The "customized" tag and reset link a staged field shows once it differs from the default. */
4606
+ /**
4607
+ * The groups holding a draft that blocks the save, in page order — what the
4608
+ * save bar's locate action opens before focusing the first invalid field.
4609
+ * Privacy and Integrations hold switches only and Accounts is not staged, so
4610
+ * only these two can ever appear.
4611
+ */
4612
+ function invalidGroups(state) {
4613
+ const groups = [];
4614
+ if (modelsGroupInvalid(state)) groups.push("models");
4615
+ if (advancedGroupInvalid(state)) groups.push("advanced");
4616
+ return groups;
4617
+ }
4618
+ /**
4619
+ * "N customized" on the Models & context heading: its staged fields that
4620
+ * differ from the default, plus one per per-model context override (each row
4621
+ * is a customization the user made).
4622
+ */
4623
+ function modelsOverriddenCount(state) {
4624
+ return MODELS_FIELDS.filter((field) => state[field].overridden).length + state.contextWindowOverrides.length;
4625
+ }
4626
+ /** "N customized" on the Advanced heading. */
4627
+ function advancedOverriddenCount(state) {
4628
+ return ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
4629
+ }
4630
+ /**
4631
+ * The effective value of a staged switch: an unset draft (`''`) means the
4632
+ * field's default. Used by the summary badges, which must say what the switch
4633
+ * on screen says.
4634
+ */
4635
+ function stagedSwitch(field, defaultValue) {
4636
+ return field.text === "" ? defaultValue : field.text === "true";
4637
+ }
4638
+ /**
4639
+ * Accounts that need attention, for the Accounts heading. Uses the same
4640
+ * verdicts as each row's status dot (`statusDotClass`): a rejected key is a
4641
+ * hard failure, a cooldown or any other mark a warning.
4642
+ */
4643
+ function accountIssues(entries) {
4644
+ let count = 0;
4645
+ let severe = false;
4646
+ for (const entry of entries) {
4647
+ if (entry === void 0) continue;
4648
+ if (entry.mark === "invalid-credential") {
4649
+ count += 1;
4650
+ severe = true;
4651
+ } else if (entry.mark !== "" || entry.cooldownUntil > 0) count += 1;
4652
+ }
4653
+ return {
4654
+ count,
4655
+ severe
4656
+ };
4657
+ }
4658
+ /**
4659
+ * The reset button a staged field shows once it differs from the default. It
4660
+ * trails the control, and a row with nothing to reset keeps an empty slot of
4661
+ * the same width, so every staged control in a group ends on one right edge.
4662
+ */
3937
4663
  function FieldOverride({ label, state, disabled, t, onReset }) {
3938
- if (!state.overridden && !state.clear) return null;
4664
+ if (!state.overridden && !state.clear) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4665
+ className: "cc-resetSlot",
4666
+ "aria-hidden": "true"
4667
+ });
3939
4668
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3940
4669
  type: "button",
3941
- className: "cc-linkButton",
4670
+ className: "cc-iconButton",
3942
4671
  disabled,
3943
4672
  onClick: onReset,
3944
4673
  "aria-label": `${label} — ${t("reset")}`,
3945
- children: t("reset")
4674
+ title: t("reset"),
4675
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "reset" })
3946
4676
  });
3947
4677
  }
3948
4678
  /**
@@ -3992,7 +4722,18 @@ window.__ModuleLoader__.load({
3992
4722
  }) : null;
3993
4723
  }
3994
4724
  /** One text/number field row. */
3995
- function Field({ id, label, hint, state, disabled, numeric, decimal, context, output, wide, placeholder, onEdit, onReset, t }) {
4725
+ function Field({ id, label, hint, state, disabled, numeric, decimal, context, output, wide, unit, placeholder, onEdit, onReset, t }) {
4726
+ const input = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4727
+ id,
4728
+ className: unit === void 0 ? `cc-fieldInput ${wide ? "cc-rowInputWide" : "cc-rowInput"}${state.invalid ? " cc-fieldInputInvalid" : ""}` : "cc-fieldInput",
4729
+ type: "text",
4730
+ inputMode: decimal ? "decimal" : numeric ? "numeric" : void 0,
4731
+ "aria-invalid": state.invalid || void 0,
4732
+ value: state.text,
4733
+ placeholder,
4734
+ disabled,
4735
+ onChange: (event) => onEdit(event.target.value)
4736
+ });
3996
4737
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
3997
4738
  title: label,
3998
4739
  titleFor: id,
@@ -4002,22 +4743,18 @@ window.__ModuleLoader__.load({
4002
4743
  }),
4003
4744
  description: hint,
4004
4745
  error: state.invalid ? invalidCopy(state.invalidReason, t, output === true ? "output" : context === true ? "context" : decimal === true ? "duration" : "number") : void 0,
4005
- control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
4746
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [unit === void 0 ? input : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4747
+ className: state.invalid ? "cc-unitBox cc-unitBoxInvalid" : "cc-unitBox",
4748
+ children: [input, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4749
+ className: "cc-unit",
4750
+ children: unit
4751
+ })]
4752
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
4006
4753
  label,
4007
4754
  state,
4008
4755
  disabled,
4009
4756
  t,
4010
4757
  onReset
4011
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4012
- id,
4013
- className: `cc-input ${wide ? "cc-rowInputWide" : "cc-rowInput"}${state.invalid ? " cc-inputInvalid" : ""}`,
4014
- type: "text",
4015
- inputMode: decimal ? "decimal" : numeric ? "numeric" : void 0,
4016
- "aria-invalid": state.invalid || void 0,
4017
- value: state.text,
4018
- placeholder,
4019
- disabled,
4020
- onChange: (event) => onEdit(event.target.value)
4021
4758
  })] })
4022
4759
  });
4023
4760
  }
@@ -4057,13 +4794,7 @@ window.__ModuleLoader__.load({
4057
4794
  t
4058
4795
  }),
4059
4796
  description: hint,
4060
- control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
4061
- label,
4062
- state,
4063
- disabled,
4064
- t,
4065
- onReset
4066
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4797
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4067
4798
  id,
4068
4799
  className: "cc-toggle",
4069
4800
  type: "checkbox",
@@ -4071,6 +4802,12 @@ window.__ModuleLoader__.load({
4071
4802
  checked,
4072
4803
  disabled,
4073
4804
  onChange: (event) => onEdit(event.target.checked ? "true" : "false")
4805
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
4806
+ label,
4807
+ state,
4808
+ disabled,
4809
+ t,
4810
+ onReset
4074
4811
  })] })
4075
4812
  });
4076
4813
  }
@@ -4099,7 +4836,7 @@ window.__ModuleLoader__.load({
4099
4836
  function UsageWindow({ label, limit, t }) {
4100
4837
  const { used, cap, exceeded, resetAt } = limit;
4101
4838
  const ratio = windowRatio(used, cap);
4102
- const reset = formatResetAt(resetAt);
4839
+ const reset = formatResetAtShort(resetAt);
4103
4840
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4104
4841
  className: "cc-usageWindow",
4105
4842
  children: [
@@ -4170,7 +4907,7 @@ window.__ModuleLoader__.load({
4170
4907
  children: accountName
4171
4908
  }) : null,
4172
4909
  planName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4173
- className: "cc-usagePlan",
4910
+ className: "cc-badge cc-badgeOutline",
4174
4911
  children: planName
4175
4912
  }) : null
4176
4913
  ]
@@ -4183,25 +4920,44 @@ window.__ModuleLoader__.load({
4183
4920
  className: "cc-usageHint",
4184
4921
  children: t("usageUnconfigured")
4185
4922
  }) : null,
4186
- report.blocked !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4187
- className: "cc-usageBlocked",
4923
+ report.blocked !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Notice, {
4924
+ tone: "error",
4188
4925
  role: "alert",
4189
- children: [
4190
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4191
- className: "cc-usageBlockedTitle",
4192
- children: blockedTitle(report.blocked, t)
4193
- }),
4194
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4195
- className: "cc-usageBlockedHint",
4196
- children: blockedHint(report.blocked, t)
4197
- }),
4198
- report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4199
- className: "cc-usageBlockedDetail",
4200
- title: report.failures.join("; "),
4201
- children: report.failures.join(" · ")
4202
- }) : null
4203
- ]
4926
+ title: blockedTitle(report.blocked, t),
4927
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: blockedHint(report.blocked, t) }), report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4928
+ className: "cc-noticeDetail",
4929
+ title: report.failures.join("; "),
4930
+ children: report.failures.join(" · ")
4931
+ }) : null]
4204
4932
  }) : null,
4933
+ credits !== void 0 ? (() => {
4934
+ const monthlyKnown = credits.monthlyReported !== false;
4935
+ const depleted = monthlyKnown && credits.monthlyCredits <= 0;
4936
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4937
+ className: depleted ? "cc-balance cc-balanceEmpty" : "cc-balance",
4938
+ children: [
4939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4940
+ className: "cc-balanceMain",
4941
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4942
+ className: "cc-balanceLabel",
4943
+ children: t("usageMonthlyLeft")
4944
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4945
+ className: "cc-balanceValue",
4946
+ children: monthlyKnown ? formatMoney(Math.max(0, credits.monthlyCredits)) : "—"
4947
+ })]
4948
+ }),
4949
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4950
+ className: "cc-balanceSub",
4951
+ children: `${t("usagePurchased")} ${credits.purchasedReported === false ? "—" : formatMoney(credits.purchasedCredits)} · ${t("usageFree")} ${credits.freeReported === false ? "—" : formatMoney(credits.freeCredits)}`
4952
+ }),
4953
+ depleted ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4954
+ className: "cc-balanceNote",
4955
+ role: "status",
4956
+ children: t("usageMonthlyDepleted")
4957
+ }) : null
4958
+ ]
4959
+ });
4960
+ })() : null,
4205
4961
  report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4206
4962
  className: "cc-usageUpdated",
4207
4963
  children: usagePeriod ? `${t("usageBillingPeriod")} ${usagePeriod}` : t("usagePeriodUnknown")
@@ -4212,7 +4968,7 @@ window.__ModuleLoader__.load({
4212
4968
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
4213
4969
  label: t("usageRequests"),
4214
4970
  value: formatCompactCount(report.usage.totalCount),
4215
- sub: `${t("usageCompleted")} ${formatCompactCount(report.usage.completedCount)} / ${t("usageFailed")} ${formatCompactCount(report.usage.failedCount)}`,
4971
+ sub: `${t("usageFailed")} ${formatCompactCount(report.usage.failedCount)}`,
4216
4972
  title: compactCountTitle([
4217
4973
  [t("usageRequests"), report.usage.totalCount],
4218
4974
  [t("usageCompleted"), report.usage.completedCount],
@@ -4235,23 +4991,6 @@ window.__ModuleLoader__.load({
4235
4991
  })
4236
4992
  ]
4237
4993
  }) : null,
4238
- credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4239
- className: "cc-usageStats cc-usageStatsBalance",
4240
- children: [
4241
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
4242
- label: t("usageMonthly"),
4243
- value: credits.monthlyReported === false ? "—" : formatMoney(credits.monthlyCredits)
4244
- }),
4245
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
4246
- label: t("usagePurchased"),
4247
- value: credits.purchasedReported === false ? "—" : formatMoney(credits.purchasedCredits)
4248
- }),
4249
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageStat, {
4250
- label: t("usageFree"),
4251
- value: credits.freeReported === false ? "—" : formatMoney(credits.freeCredits)
4252
- })
4253
- ]
4254
- }) : null,
4255
4994
  credits?.fiveHour !== void 0 || credits?.weekly !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4256
4995
  className: "cc-usageWindows",
4257
4996
  children: [credits.fiveHour !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageWindow, {
@@ -4286,7 +5025,10 @@ window.__ModuleLoader__.load({
4286
5025
  children: [
4287
5026
  t("usageUpdated"),
4288
5027
  " ",
4289
- new Date(fetchedAt).toLocaleTimeString()
5028
+ new Date(fetchedAt).toLocaleTimeString(void 0, {
5029
+ hour: "2-digit",
5030
+ minute: "2-digit"
5031
+ })
4290
5032
  ]
4291
5033
  }) : null
4292
5034
  ]
@@ -4362,12 +5104,12 @@ window.__ModuleLoader__.load({
4362
5104
  children: t("modelStale")
4363
5105
  }) : null,
4364
5106
  option.allowance !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4365
- className: "cc-badgeMuted",
5107
+ className: "cc-badge cc-badgeOutline",
4366
5108
  title: t("modelAllowanceTitle"),
4367
5109
  children: t("modelAllowance", { amount: option.allowance })
4368
5110
  }) : null,
4369
5111
  !selected.includes(option.value) && ownerOf?.(option.value) !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4370
- className: "cc-badgeMuted",
5112
+ className: "cc-badge cc-badgeOutline",
4371
5113
  children: t("accountModelOwner", { name: ownerOf(option.value) })
4372
5114
  }) : null
4373
5115
  ]
@@ -4400,9 +5142,9 @@ window.__ModuleLoader__.load({
4400
5142
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4401
5143
  className: "cc-selectorText",
4402
5144
  children: selected.length === 0 ? t("modelPick") : t("modelCount", { count: selected.length })
4403
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4404
- className: "cc-selectorCaret",
4405
- "aria-hidden": "true"
5145
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
5146
+ name: "chevron",
5147
+ className: "cc-icon cc-selectorChevron"
4406
5148
  })]
4407
5149
  }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4408
5150
  type: "search",
@@ -4436,16 +5178,16 @@ window.__ModuleLoader__.load({
4436
5178
  description: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("visibleModelsHint") }), staleIds.length > 0 && catalogReady ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("visibleModelsStaleHint", { count: staleIds.length }) }) : null] }),
4437
5179
  error: state.catalogFailed ? t("modelCatalogFailed") : void 0,
4438
5180
  control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4439
- catalogReady && staleIds.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4440
- type: "button",
4441
- className: "cc-linkButton",
5181
+ catalogReady && staleIds.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5182
+ variant: "ghost",
5183
+ size: "sm",
4442
5184
  disabled,
4443
5185
  onClick: () => onSelect(state.visibleModels.filter((id) => !staleIds.includes(id))),
4444
5186
  children: t("visibleModelsCleanStale", { count: staleIds.length })
4445
5187
  }) : null,
4446
- !state.visibleModelsAll ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4447
- type: "button",
4448
- className: "cc-linkButton",
5188
+ !state.visibleModelsAll ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5189
+ variant: "ghost",
5190
+ size: "sm",
4449
5191
  disabled,
4450
5192
  onClick: onClear,
4451
5193
  children: t("visibleModelsShowAll")
@@ -4462,86 +5204,87 @@ window.__ModuleLoader__.load({
4462
5204
  });
4463
5205
  }
4464
5206
  /**
4465
- * One titled group of rows. No card surface: like the harness's own settings
4466
- * pages, a group is a heading over its rows. The hairline between sections is
4467
- * `cc-groupDivided`, so it belongs to every group but the first one on the page
4468
- * (accounts opens it) and never to the rows inside a group.
5207
+ * One top-level group of the page (决策记录第十章第4条). All five groups share
5208
+ * this one shape so they cannot drift apart: the heading is an `h3` wrapping a
5209
+ * single full-width button — chevron first, then the title and the summary
5210
+ * badges — and a group action such as Refresh sits OUTSIDE that button, so
5211
+ * using it never folds the group. Which groups are open is page state (see
5212
+ * ./group-fold.ts), passed in here.
5213
+ *
5214
+ * The body stays mounted and is hidden with the `hidden` attribute rather than
5215
+ * unrendered: `aria-controls` then always points at a real element, and the
5216
+ * save bar's locate action can focus an invalid field right after opening its
5217
+ * group. Folding is presentation only — staged drafts, validation and the save
5218
+ * path are untouched (第六章第1、2条).
4469
5219
  */
4470
- function SettingsGroup({ title, action, divided = false, children }) {
5220
+ function FoldableGroup({ id, title, divided = true, expanded, onToggle, badges, action, children }) {
5221
+ const titleId = `cc-group-${id}-title`;
5222
+ const bodyId = `cc-group-${id}-body`;
4471
5223
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4472
5224
  className: divided ? "cc-group cc-groupDivided" : "cc-group",
4473
- "aria-label": title,
5225
+ "aria-labelledby": titleId,
4474
5226
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4475
5227
  className: "cc-groupHead",
4476
5228
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4477
- className: "cc-groupTitle",
4478
- children: title
5229
+ className: "cc-groupHeading",
5230
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5231
+ type: "button",
5232
+ className: "cc-disclosure",
5233
+ "aria-expanded": expanded,
5234
+ "aria-controls": bodyId,
5235
+ onClick: onToggle,
5236
+ children: [
5237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
5238
+ name: "chevron",
5239
+ className: expanded ? "cc-icon cc-foldChevron" : "cc-icon cc-foldChevron cc-foldChevronClosed"
5240
+ }),
5241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5242
+ id: titleId,
5243
+ className: "cc-groupTitle",
5244
+ children: title
5245
+ }),
5246
+ badges
5247
+ ]
5248
+ })
4479
5249
  }), action]
4480
5250
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4481
- className: "cc-rows",
4482
- children
5251
+ id: bodyId,
5252
+ className: "cc-groupBody",
5253
+ hidden: !expanded,
5254
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5255
+ className: "cc-rows",
5256
+ children
5257
+ })
4483
5258
  })]
4484
5259
  });
4485
5260
  }
4486
5261
  /**
4487
- * The heading of a foldable group: the whole row is the toggle and the chevron
4488
- * sits at its trailing edge. Shared by Models & context (issue #94: the
4489
- * reporter asked for it to fold "like Advanced below") and Advanced itself, so
4490
- * the two cannot drift apart in either markup or behaviour.
4491
- *
4492
- * The title is a `span`, not the `h3` a static group uses, because a heading
4493
- * element cannot sit inside a button — the same trade-off Advanced has always
4494
- * made on its own.
5262
+ * A heading's neutral summary as quiet text ("3 allowlisted · 1 customized"),
5263
+ * like the harness's own section captions; only warnings and errors get a
5264
+ * tinted pill, so a pill always means "look here". Empty parts are skipped.
4495
5265
  */
4496
- function DisclosureHead({ title, bodyId, expanded, onToggle, badges }) {
4497
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4498
- type: "button",
4499
- className: "cc-groupHead cc-disclosure",
4500
- "aria-expanded": expanded,
4501
- "aria-controls": bodyId,
4502
- onClick: onToggle,
4503
- children: [
4504
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4505
- className: "cc-groupTitle",
4506
- children: title
4507
- }),
4508
- badges?.(expanded),
4509
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
4510
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4511
- className: expanded ? "cc-chevron cc-chevronUp" : "cc-chevron",
4512
- "aria-hidden": "true"
4513
- })
4514
- ]
5266
+ function GroupSummary({ parts }) {
5267
+ const text = parts.filter((part) => part !== void 0 && part !== "").join(" · ");
5268
+ return text === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5269
+ className: "cc-groupSummary",
5270
+ children: text
4515
5271
  });
4516
5272
  }
5273
+ /** "N customized" for a heading summary; undefined at zero. */
5274
+ function overriddenText(count, t) {
5275
+ if (count === 0) return void 0;
5276
+ return count === 1 ? t("groupOverriddenOne") : t("groupOverriddenMany", { count });
5277
+ }
4517
5278
  /**
4518
- * A titled group that folds from its own heading and starts collapsed. The
4519
- * state is deliberately component-local — not persisted, not lifted into the
4520
- * controller — so every visit opens at the same, predictable page length, and
4521
- * folding can never turn into a stored preference the settings document would
4522
- * have to carry.
5279
+ * The badge a FOLDED group shows while something inside it blocks the save:
5280
+ * its rows are off screen, so without it Save would sit disabled with nothing
5281
+ * visible explaining why (issue #94). Expanded, the row errors speak for it.
4523
5282
  */
4524
- function CollapsibleGroup({ title, bodyId, divided = false, badges, footer, children }) {
4525
- const [expanded, setExpanded] = (0, react.useState)(false);
4526
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4527
- className: divided ? "cc-group cc-groupDivided" : "cc-group",
4528
- "aria-label": title,
4529
- children: [
4530
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DisclosureHead, {
4531
- title,
4532
- bodyId,
4533
- expanded,
4534
- onToggle: () => setExpanded((value) => !value),
4535
- badges
4536
- }),
4537
- expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4538
- id: bodyId,
4539
- className: "cc-rows",
4540
- children
4541
- }) : null,
4542
- expanded ? footer : null
4543
- ]
4544
- });
5283
+ function InvalidBadge({ show, t }) {
5284
+ return show ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5285
+ className: "cc-badge cc-badgeError",
5286
+ children: t("groupInvalid")
5287
+ }) : null;
4545
5288
  }
4546
5289
  /**
4547
5290
  * Models & context: the plan filter and the allowlist sit together because both
@@ -4549,21 +5292,20 @@ window.__ModuleLoader__.load({
4549
5292
  * filter), and the context-length fields follow them because they are the same
4550
5293
  * subject seen from the other side — the window each listed model advertises.
4551
5294
  * Those fields carry no heading or divider of their own: they are rows of this
4552
- * group, not a section inside it. The group folds from its heading and starts
4553
- * collapsed (issue #94, "same as Advanced below"): it is the longest group on
4554
- * the page and its rows are set-and-forget, so folding keeps Accounts and the
4555
- * two switches below it reachable without scrolling past the allowlist.
4556
- */
4557
- function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear, onOverrides }) {
4558
- const invalid = modelsGroupInvalid(state);
4559
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleGroup, {
5295
+ * group, not a section inside it. Folded by default like every group but
5296
+ * Accounts; the heading then summarizes the allowlist size and how much of the
5297
+ * group is customized.
5298
+ */
5299
+ function ModelsCard({ state, disabled, t, onEdit, onReset, onSelect, onClear, onOverrides, expanded, onToggle }) {
5300
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FoldableGroup, {
5301
+ id: "models",
4560
5302
  title: t("modelsTitle"),
4561
- bodyId: "cc-models-body",
4562
- divided: true,
4563
- badges: (expanded) => !expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4564
- className: "cc-badge cc-badgeWarn",
4565
- children: t("modelsInvalid")
4566
- }) : null,
5303
+ expanded,
5304
+ onToggle,
5305
+ badges: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, { parts: [state.visibleModelsAll ? t("visibleModelsSummaryAll") : t("visibleModelsSummary", { count: state.visibleModels.length }), overriddenText(modelsOverriddenCount(state), t)] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InvalidBadge, {
5306
+ show: !expanded && modelsGroupInvalid(state),
5307
+ t
5308
+ })] }),
4567
5309
  children: [
4568
5310
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
4569
5311
  id: "cc-filter-models-by-plan",
@@ -4653,6 +5395,8 @@ window.__ModuleLoader__.load({
4653
5395
  state: state.contextWindow,
4654
5396
  disabled,
4655
5397
  context: true,
5398
+ unit: t("unitTokens"),
5399
+ placeholder: t("placeholderCatalog"),
4656
5400
  onEdit: (text) => onEdit("contextWindow", text),
4657
5401
  onReset: () => onReset("contextWindow"),
4658
5402
  t
@@ -4665,6 +5409,8 @@ window.__ModuleLoader__.load({
4665
5409
  disabled,
4666
5410
  numeric: true,
4667
5411
  output: true,
5412
+ unit: t("unitTokens"),
5413
+ placeholder: t("placeholderAuto"),
4668
5414
  onEdit: (text) => onEdit("maxOutputTokens", text),
4669
5415
  onReset: () => onReset("maxOutputTokens"),
4670
5416
  t
@@ -4672,9 +5418,13 @@ window.__ModuleLoader__.load({
4672
5418
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
4673
5419
  title: t("contextOverridesTitle"),
4674
5420
  description: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("contextOverridesHint") }),
4675
- control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4676
- type: "button",
4677
- className: "cc-linkButton",
5421
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5422
+ variant: "outline",
5423
+ size: "sm",
5424
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
5425
+ name: "plus",
5426
+ className: "cc-icon cc-btnIcon"
5427
+ }),
4678
5428
  disabled: disabled || available.length === 0,
4679
5429
  onClick: addRow,
4680
5430
  children: t("contextOverrideAdd")
@@ -4685,78 +5435,80 @@ window.__ModuleLoader__.load({
4685
5435
  children: t("contextOverrideNoCatalog")
4686
5436
  }) : null,
4687
5437
  rows.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4688
- className: "cc-contextHead",
4689
- children: [
4690
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4691
- className: "cc-contextHeadModel",
4692
- children: t("contextOverrideModel")
4693
- }),
4694
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4695
- className: "cc-contextHeadValue",
4696
- children: t("contextOverrideValue")
4697
- }),
4698
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4699
- className: "cc-contextHeadGap",
4700
- "aria-hidden": "true"
4701
- })
4702
- ]
4703
- }) : null,
4704
- rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4705
- className: "cc-contextItem",
4706
- children: [
4707
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4708
- className: "cc-contextSelect",
4709
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
4710
- className: "cc-input cc-contextModel",
4711
- value: row.model,
4712
- "aria-label": t("contextOverrideModel"),
4713
- disabled,
4714
- onChange: (event) => setModel(row.model, event.target.value),
4715
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5438
+ className: "cc-contextTable",
5439
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5440
+ className: "cc-contextHead",
5441
+ children: [
5442
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("contextOverrideModel") }),
5443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("contextOverrideValue") }),
5444
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": "true" })
5445
+ ]
5446
+ }), rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5447
+ className: "cc-contextItem",
5448
+ children: [
5449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5450
+ className: "cc-contextSelect",
5451
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5452
+ className: "cc-fieldInput cc-contextModel",
4716
5453
  value: row.model,
4717
- children: labelOf(row.model)
4718
- }), available.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
4719
- value: model.id,
4720
- children: labelOf(model.id)
4721
- }, model.id))]
4722
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4723
- className: "cc-contextCaret",
4724
- "aria-hidden": "true"
4725
- })]
4726
- }),
4727
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4728
- className: `cc-input cc-contextValue${row.invalid ? " cc-inputInvalid" : ""}`,
4729
- type: "text",
4730
- "aria-label": t("contextOverrideValue"),
4731
- "aria-invalid": row.invalid || void 0,
4732
- value: row.text,
4733
- disabled,
4734
- onChange: (event) => setText(row.model, event.target.value)
4735
- }),
4736
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4737
- type: "button",
4738
- className: "cc-iconButton",
4739
- "aria-label": t("contextOverrideRemove"),
4740
- disabled,
4741
- onClick: () => removeRow(row.model),
4742
- children: "×"
4743
- }),
4744
- row.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4745
- className: "cc-rowError cc-contextError",
4746
- children: invalidCopy(row.invalidReason, t, "context")
4747
- }) : null
4748
- ]
4749
- }, row.model))
5454
+ "aria-label": t("contextOverrideModel"),
5455
+ disabled,
5456
+ onChange: (event) => setModel(row.model, event.target.value),
5457
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5458
+ value: row.model,
5459
+ children: labelOf(row.model)
5460
+ }), available.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5461
+ value: model.id,
5462
+ children: labelOf(model.id)
5463
+ }, model.id))]
5464
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
5465
+ name: "chevron",
5466
+ className: "cc-icon cc-contextCaret"
5467
+ })]
5468
+ }),
5469
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5470
+ className: row.invalid ? "cc-unitBox cc-unitBoxInvalid" : "cc-unitBox",
5471
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5472
+ className: "cc-fieldInput",
5473
+ type: "text",
5474
+ "aria-label": t("contextOverrideValue"),
5475
+ "aria-invalid": row.invalid || void 0,
5476
+ value: row.text,
5477
+ disabled,
5478
+ onChange: (event) => setText(row.model, event.target.value)
5479
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5480
+ className: "cc-unit",
5481
+ children: t("unitTokens")
5482
+ })]
5483
+ }),
5484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5485
+ type: "button",
5486
+ className: "cc-iconButton",
5487
+ "aria-label": t("contextOverrideRemove"),
5488
+ disabled,
5489
+ onClick: () => removeRow(row.model),
5490
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "close" })
5491
+ }),
5492
+ row.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5493
+ className: "cc-rowError cc-contextError",
5494
+ children: invalidCopy(row.invalidReason, t, "context")
5495
+ }) : null
5496
+ ]
5497
+ }, row.model))]
5498
+ }) : null
4750
5499
  ] });
4751
5500
  }
4752
5501
  /**
4753
5502
  * Privacy & security: the opt-in that changes where data goes. ZDR restricts
4754
5503
  * which upstream serves a request, so it starts off.
4755
5504
  */
4756
- function PrivacyCard({ state, disabled, t, onEdit, onReset }) {
4757
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsGroup, {
5505
+ function PrivacyCard({ state, disabled, t, onEdit, onReset, expanded, onToggle }) {
5506
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FoldableGroup, {
5507
+ id: "privacy",
4758
5508
  title: t("privacyTitle"),
4759
- divided: true,
5509
+ expanded,
5510
+ onToggle,
5511
+ badges: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, { parts: [stagedSwitch(state.zdr, false) ? t("zdrSummaryOn") : t("zdrSummaryOff")] }),
4760
5512
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
4761
5513
  id: "cc-zdr",
4762
5514
  label: t("zdr"),
@@ -4770,59 +5522,161 @@ window.__ModuleLoader__.load({
4770
5522
  })
4771
5523
  });
4772
5524
  }
4773
- /** Integrations & display: surfaces outside chat that reuse this provider. */
4774
- function IntegrationsCard({ state, disabled, t, onEdit, onReset }) {
4775
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SettingsGroup, {
4776
- title: t("integrationsTitle"),
4777
- divided: true,
4778
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
4779
- id: "cc-web-search",
4780
- label: t("webSearch"),
4781
- hint: t("webSearchHint"),
4782
- state: state.webSearch,
4783
- disabled,
4784
- defaultChecked: true,
4785
- onEdit: (text) => onEdit("webSearch", text),
4786
- onReset: () => onReset("webSearch"),
4787
- t
4788
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
4789
- id: "cc-show-sidebar-quota",
4790
- label: t("showSidebarQuota"),
4791
- hint: t("showSidebarQuotaHint"),
4792
- state: state.showSidebarQuota,
4793
- disabled,
4794
- defaultChecked: false,
4795
- onEdit: (text) => onEdit("showSidebarQuota", text),
4796
- onReset: () => onReset("showSidebarQuota"),
4797
- t
5525
+ /**
5526
+ * The service-status row: the official status page's published record and the
5527
+ * Host's live probe of the configured API base, both projected by
5528
+ * ./service-status.ts (source order, cache TTL, the lag rule and every
5529
+ * degradation path live there). It is display-only — it writes no setting and
5530
+ * never blocks Save; the link opens the page for the full history.
5531
+ */
5532
+ function ServiceStatusRow({ view, t }) {
5533
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
5534
+ title: t("serviceStatusTitle"),
5535
+ description: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5536
+ className: "cc-statusLines",
5537
+ children: [
5538
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5539
+ className: "cc-statusKey",
5540
+ children: t("serviceStatusOfficial")
5541
+ }),
5542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5543
+ className: "cc-statusValue",
5544
+ children: [
5545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
5546
+ state: toneDot(view.officialTone),
5547
+ size: 8
5548
+ }),
5549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5550
+ className: "cc-statusVerdict",
5551
+ children: view.label
5552
+ }),
5553
+ view.detail !== void 0 || view.updated !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5554
+ className: "cc-statusMeta",
5555
+ children: [
5556
+ view.detail,
5557
+ view.detail !== void 0 && view.updated !== void 0 ? "\xA0· " : null,
5558
+ view.updated !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5559
+ title: view.lag ?? view.hint,
5560
+ children: view.updated
5561
+ }) : null
5562
+ ]
5563
+ }) : null
5564
+ ]
5565
+ }),
5566
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5567
+ className: "cc-statusKey",
5568
+ children: t("serviceStatusLocal")
5569
+ }),
5570
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5571
+ className: "cc-statusValue",
5572
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
5573
+ state: localDotState(view),
5574
+ size: 8
5575
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5576
+ className: view.localTone === "error" ? "cc-statusVerdict cc-statusBad" : "cc-statusVerdict",
5577
+ children: view.local
5578
+ })]
5579
+ })
5580
+ ]
5581
+ }), view.notice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5582
+ className: "cc-statusNotice",
5583
+ children: view.notice
5584
+ }) : null] }),
5585
+ control: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
5586
+ className: "cc-statusLink",
5587
+ href: STATUS_PAGE_URL,
5588
+ target: "_blank",
5589
+ rel: "noreferrer",
5590
+ children: view.linkLabel
5591
+ })
5592
+ });
5593
+ }
5594
+ /**
5595
+ * The status dot and verdict, shared by the status row and the Integrations
5596
+ * heading. The primitive's five states already carry the theme's
5597
+ * success/warn/error tokens, so the colours are the design system's own.
5598
+ */
5599
+ function ServiceStatusBadge({ view }) {
5600
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5601
+ className: "cc-statusBadge",
5602
+ title: view.hint,
5603
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: toneDot(view.badge.tone) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5604
+ className: view.badge.tone === "warn" || view.badge.tone === "error" ? "cc-statusLabel cc-statusBad" : "cc-statusLabel",
5605
+ children: view.badge.label
4798
5606
  })]
4799
5607
  });
4800
5608
  }
5609
+ /** A view tone on the harness StateDot. */
5610
+ function toneDot(tone) {
5611
+ return tone === "ok" ? "done" : tone === "warn" ? "warning" : tone === "error" ? "error" : tone === "checking" ? "ongoing" : "idle";
5612
+ }
5613
+ /**
5614
+ * The local probe's dot: done/error once it answered, breathing while the
5615
+ * request is in flight, idle when the Host offers no probe at all.
5616
+ */
5617
+ function localDotState(view) {
5618
+ if (view.localTone === "ok") return "done";
5619
+ if (view.localTone === "error") return "error";
5620
+ return view.localChecking ? "ongoing" : "idle";
5621
+ }
5622
+ /**
5623
+ * Integrations & display: surfaces outside chat that reuse this provider. The
5624
+ * status snapshot comes from the page, which probes once per open even while
5625
+ * this group is folded — its heading shows the verdict either way.
5626
+ */
5627
+ function IntegrationsCard({ state, disabled, t, onEdit, onReset, expanded, onToggle, status, probe }) {
5628
+ const view = serviceStatusView(status, probe, t, Date.now());
5629
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FoldableGroup, {
5630
+ id: "integrations",
5631
+ title: t("integrationsTitle"),
5632
+ expanded,
5633
+ onToggle,
5634
+ badges: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ServiceStatusBadge, { view }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, { parts: [stagedSwitch(state.webSearch, true) ? t("webSearchSummaryOn") : t("webSearchSummaryOff")] })] }),
5635
+ children: [
5636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ServiceStatusRow, {
5637
+ view,
5638
+ t
5639
+ }),
5640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
5641
+ id: "cc-web-search",
5642
+ label: t("webSearch"),
5643
+ hint: t("webSearchHint"),
5644
+ state: state.webSearch,
5645
+ disabled,
5646
+ defaultChecked: true,
5647
+ onEdit: (text) => onEdit("webSearch", text),
5648
+ onReset: () => onReset("webSearch"),
5649
+ t
5650
+ }),
5651
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
5652
+ id: "cc-show-sidebar-quota",
5653
+ label: t("showSidebarQuota"),
5654
+ hint: t("showSidebarQuotaHint"),
5655
+ state: state.showSidebarQuota,
5656
+ disabled,
5657
+ defaultChecked: false,
5658
+ onEdit: (text) => onEdit("showSidebarQuota", text),
5659
+ onReset: () => onReset("showSidebarQuota"),
5660
+ t
5661
+ })
5662
+ ]
5663
+ });
5664
+ }
4801
5665
  /**
4802
- * The collapsed "Advanced" card: the API base and the network limits. Folds
4803
- * through the same `CollapsibleGroup` the Models & context group uses, so the
4804
- * two disclosures stay identical; while collapsed, a badge names the customized
4805
- * count and an invalid number (which blocks save) is surfaced on the header.
5666
+ * The "Advanced" group: the API base and the network limits. Its heading
5667
+ * names the customized count, and while folded, an invalid number (which
5668
+ * blocks save); expanded, the row's own error says it.
4806
5669
  */
4807
- function AdvancedSection({ state, disabled, t, onEdit, onReset }) {
4808
- const overridden = ADVANCED_FIELDS.filter((field) => state[field].overridden).length;
4809
- const invalid = advancedGroupInvalid(state);
4810
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CollapsibleGroup, {
5670
+ function AdvancedSection({ state, disabled, t, onEdit, onReset, expanded, onToggle }) {
5671
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FoldableGroup, {
5672
+ id: "advanced",
4811
5673
  title: t("advancedSettings"),
4812
- bodyId: "cc-advanced-body",
4813
- divided: true,
4814
- badges: (expanded) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4815
- className: "cc-badge",
4816
- children: overridden === 1 ? t("advancedOverriddenOne") : t("advancedOverriddenMany", { count: overridden })
4817
- }) : null, !expanded && invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4818
- className: "cc-badge cc-badgeWarn",
4819
- children: t("advancedInvalid")
4820
- }) : null] }),
4821
- footer: invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4822
- className: "cc-rowError",
4823
- role: "status",
4824
- children: t("advancedInvalid")
4825
- }) : null,
5674
+ expanded,
5675
+ onToggle,
5676
+ badges: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, { parts: [overriddenText(advancedOverriddenCount(state), t)] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InvalidBadge, {
5677
+ show: !expanded && advancedGroupInvalid(state),
5678
+ t
5679
+ })] }),
4826
5680
  children: [
4827
5681
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4828
5682
  className: "cc-groupDesc",
@@ -4831,10 +5685,10 @@ window.__ModuleLoader__.load({
4831
5685
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
4832
5686
  id: "cc-api-base",
4833
5687
  label: t("apiBase"),
4834
- hint: t("apiBaseHint"),
4835
5688
  state: state.apiBase,
4836
5689
  disabled,
4837
5690
  wide: true,
5691
+ placeholder: FIELD_DEFAULTS.apiBase,
4838
5692
  onEdit: (text) => onEdit("apiBase", text),
4839
5693
  onReset: () => onReset("apiBase"),
4840
5694
  t
@@ -4847,6 +5701,8 @@ window.__ModuleLoader__.load({
4847
5701
  disabled,
4848
5702
  numeric: true,
4849
5703
  decimal: true,
5704
+ unit: t("unitSeconds"),
5705
+ placeholder: FIELD_DEFAULTS.requestTimeoutSeconds,
4850
5706
  onEdit: (text) => onEdit("requestTimeoutMs", text),
4851
5707
  onReset: () => onReset("requestTimeoutMs"),
4852
5708
  t
@@ -4859,6 +5715,8 @@ window.__ModuleLoader__.load({
4859
5715
  disabled,
4860
5716
  numeric: true,
4861
5717
  decimal: true,
5718
+ unit: t("unitSeconds"),
5719
+ placeholder: FIELD_DEFAULTS.streamIdleTimeoutSeconds,
4862
5720
  onEdit: (text) => onEdit("streamIdleTimeoutMs", text),
4863
5721
  onReset: () => onReset("streamIdleTimeoutMs"),
4864
5722
  t
@@ -4870,6 +5728,8 @@ window.__ModuleLoader__.load({
4870
5728
  state: state.transportMaxRetries,
4871
5729
  disabled,
4872
5730
  numeric: true,
5731
+ unit: t("unitRetries"),
5732
+ placeholder: FIELD_DEFAULTS.transportMaxRetries,
4873
5733
  onEdit: (text) => onEdit("transportMaxRetries", text),
4874
5734
  onReset: () => onReset("transportMaxRetries"),
4875
5735
  t
@@ -5044,21 +5904,22 @@ window.__ModuleLoader__.load({
5044
5904
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5045
5905
  className: "cc-inlineActions",
5046
5906
  children: [
5047
- secret && value !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5907
+ secret && value !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5908
+ variant: "ghost",
5909
+ size: "sm",
5048
5910
  type: "button",
5049
- className: "cc-reset",
5050
5911
  onClick: () => setVisible((shown) => !shown),
5051
5912
  children: visible ? t("hide") : t("show")
5052
5913
  }) : null,
5053
5914
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
5054
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5055
5916
  variant: "ghost",
5056
5917
  size: "sm",
5057
5918
  type: "button",
5058
5919
  onClick: onCancel,
5059
5920
  children: t("cancel")
5060
5921
  }),
5061
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5922
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5062
5923
  variant: "primary",
5063
5924
  size: "sm",
5064
5925
  type: "submit",
@@ -5069,35 +5930,26 @@ window.__ModuleLoader__.load({
5069
5930
  })]
5070
5931
  });
5071
5932
  }
5072
- /** A destructive confirmation bar shown in place of a menu action. */
5933
+ /** A destructive confirmation, shown in place in the card as an error notice (no modal). */
5073
5934
  function ConfirmBar({ text, confirmLabel, disabled, t, onConfirm, onCancel }) {
5074
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5075
- className: "cc-confirmBar",
5935
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
5936
+ tone: "error",
5937
+ icon: "warn",
5076
5938
  role: "alertdialog",
5077
- "aria-label": text,
5078
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5079
- className: "cc-confirmText",
5080
- children: text
5081
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5082
- className: "cc-inlineActions",
5083
- children: [
5084
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
5085
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5086
- variant: "ghost",
5087
- size: "sm",
5088
- onClick: onCancel,
5089
- children: t("cancel")
5090
- }),
5091
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5092
- variant: "outline",
5093
- size: "sm",
5094
- className: "cc-dangerButton",
5095
- disabled,
5096
- onClick: onConfirm,
5097
- children: confirmLabel
5098
- })
5099
- ]
5100
- })]
5939
+ label: text,
5940
+ title: text,
5941
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5942
+ variant: "danger",
5943
+ size: "sm",
5944
+ disabled,
5945
+ onClick: onConfirm,
5946
+ children: confirmLabel
5947
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5948
+ variant: "ghost",
5949
+ size: "sm",
5950
+ onClick: onCancel,
5951
+ children: t("cancel")
5952
+ })] })
5101
5953
  });
5102
5954
  }
5103
5955
  /** One account row: identity, status, quota summary, actions and expandable details. */
@@ -5206,11 +6058,11 @@ window.__ModuleLoader__.load({
5206
6058
  children: accountName
5207
6059
  }) : null,
5208
6060
  planName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5209
- className: "cc-usagePlan",
6061
+ className: "cc-badge cc-badgeOutline",
5210
6062
  children: planName
5211
6063
  }) : null,
5212
6064
  entry?.active && multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5213
- className: "cc-badge",
6065
+ className: "cc-badge cc-badgeOk",
5214
6066
  children: t("usageActive")
5215
6067
  }) : null,
5216
6068
  pinned ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -5218,21 +6070,21 @@ window.__ModuleLoader__.load({
5218
6070
  children: t("accountStatusPinned")
5219
6071
  }) : null,
5220
6072
  !row.credentialKnown || !row.configured ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5221
- className: "cc-badgeMuted",
6073
+ className: "cc-badge cc-badgeOutline",
5222
6074
  children: t(row.credentialKnown ? "apiKeyUnset" : "writeStateUnknown")
5223
6075
  }) : null,
5224
6076
  entry?.mark === "invalid-credential" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5225
- className: "cc-usagePlanStatus",
6077
+ className: "cc-badge cc-badgeError",
5226
6078
  children: t("usageInvalidKey")
5227
6079
  }) : null,
5228
6080
  entry !== void 0 && entry.mark !== "invalid-credential" && (entry.cooldownUntil > 0 || entry.mark === "rate-limit") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5229
- className: "cc-usagePlanStatus",
5230
- children: [t("usageCooldown"), entry.cooldownUntil > 0 ? ` ${formatResetAt(entry.cooldownUntil)}` : ""]
6081
+ className: "cc-badge cc-badgeWarn",
6082
+ children: [t("usageCooldown"), entry.cooldownUntil > 0 ? ` ${formatResetAtShort(entry.cooldownUntil)}` : ""]
5231
6083
  }) : null,
5232
6084
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
5233
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5234
- className: expanded ? "cc-chevron cc-chevronUp" : "cc-chevron",
5235
- "aria-hidden": "true"
6085
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6086
+ name: "chevron",
6087
+ className: expanded ? "cc-icon cc-foldChevron cc-chevronOpen" : "cc-icon cc-foldChevron"
5236
6088
  })
5237
6089
  ]
5238
6090
  }), items.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
@@ -5250,14 +6102,11 @@ window.__ModuleLoader__.load({
5250
6102
  "aria-haspopup": "menu",
5251
6103
  "aria-expanded": menuOpen,
5252
6104
  onClick: () => setMenuOpen((value) => !value),
5253
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5254
- className: "cc-kebab",
5255
- "aria-hidden": "true"
5256
- })
6105
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "more" })
5257
6106
  })
5258
6107
  }) : null]
5259
6108
  }),
5260
- credits !== void 0 && (credits.fiveHour !== void 0 || credits.weekly !== void 0 || monthlyReported) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6109
+ !expanded && credits !== void 0 && (credits.fiveHour !== void 0 || credits.weekly !== void 0 || monthlyReported) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5261
6110
  className: "cc-accountMeters",
5262
6111
  children: [
5263
6112
  credits.fiveHour !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MiniMeter, {
@@ -5274,60 +6123,61 @@ window.__ModuleLoader__.load({
5274
6123
  }) : null
5275
6124
  ]
5276
6125
  }) : null,
5277
- row.credentialKnown && !row.configured && row.managed && mode === void 0 && !showLogin ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5278
- className: "cc-accountSetup",
5279
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5280
- className: "cc-hint",
5281
- children: row.keyWritable ? t("accountNoKeyHint") : t("apiKeyLocked")
5282
- }), keyActions ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5283
- className: "cc-inlineActions",
5284
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5285
- variant: "primary",
5286
- size: "sm",
5287
- disabled: loginBusy || busyElsewhere,
5288
- onClick: () => actions.beginLogin(loginTarget),
5289
- children: t("accountAddLogin")
5290
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5291
- variant: "ghost",
5292
- size: "sm",
5293
- onClick: () => setMode("key"),
5294
- children: t("accountAddPaste")
5295
- })]
5296
- }) : null]
6126
+ row.credentialKnown && !row.configured && row.managed && mode === void 0 && !showLogin ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6127
+ icon: row.keyWritable ? "info" : "lock",
6128
+ actions: keyActions ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6129
+ variant: "primary",
6130
+ size: "sm",
6131
+ disabled: loginBusy || busyElsewhere,
6132
+ onClick: () => actions.beginLogin(loginTarget),
6133
+ children: t("accountAddLogin")
6134
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6135
+ variant: "outline",
6136
+ size: "sm",
6137
+ onClick: () => setMode("key"),
6138
+ children: t("accountAddPaste")
6139
+ })] }) : void 0,
6140
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: row.keyWritable ? t("accountNoKeyHint") : t("apiKeyLocked") })
5297
6141
  }) : null,
5298
6142
  showLogin ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginStatus, {
5299
- state: loginState,
5300
- t,
5301
- onCancel: actions.cancelLogin
5302
- }) : null,
5303
- mode === "key" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
5304
- id: `cc-account-${row.id}-key`,
5305
- label: t("accountKeyPlaceholder"),
5306
- secret: true,
5307
- placeholder: t("accountKeyPlaceholder"),
5308
- confirmLabel: t("accountApply"),
5309
- disabled: disabled || state.accountBusy,
5310
- t,
5311
- onCancel: () => setMode(void 0),
5312
- onSubmit: (value) => {
5313
- actions.setKey(row.id, value).then((ok) => {
5314
- if (ok) setMode(void 0);
5315
- });
5316
- }
5317
- }) : null,
5318
- mode === "rename" && row.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
5319
- id: `cc-account-${row.id}-rename`,
5320
- label: t("accountActionRename"),
5321
- initial: row.label,
5322
- confirmLabel: t("accountApply"),
5323
- disabled: disabled || state.accountBusy,
6143
+ state: loginState,
5324
6144
  t,
5325
- onCancel: () => setMode(void 0),
5326
- onSubmit: (value) => {
5327
- actions.rename(row.ref, value).then((ok) => {
5328
- if (ok) setMode(void 0);
5329
- });
5330
- }
6145
+ onCancel: actions.cancelLogin
6146
+ }) : null,
6147
+ mode === "key" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6148
+ className: "cc-panel",
6149
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
6150
+ id: `cc-account-${row.id}-key`,
6151
+ label: t("accountKeyPlaceholder"),
6152
+ secret: true,
6153
+ placeholder: t("accountKeyPlaceholder"),
6154
+ confirmLabel: t("accountApply"),
6155
+ disabled: disabled || state.accountBusy,
6156
+ t,
6157
+ onCancel: () => setMode(void 0),
6158
+ onSubmit: (value) => {
6159
+ actions.setKey(row.id, value).then((ok) => {
6160
+ if (ok) setMode(void 0);
6161
+ });
6162
+ }
6163
+ })
6164
+ }) : null,
6165
+ mode === "rename" && row.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6166
+ className: "cc-panel",
6167
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
6168
+ id: `cc-account-${row.id}-rename`,
6169
+ label: t("accountActionRename"),
6170
+ initial: row.label,
6171
+ confirmLabel: t("accountApply"),
6172
+ disabled: disabled || state.accountBusy,
6173
+ t,
6174
+ onCancel: () => setMode(void 0),
6175
+ onSubmit: (value) => {
6176
+ actions.rename(row.ref, value).then((ok) => {
6177
+ if (ok) setMode(void 0);
6178
+ });
6179
+ }
6180
+ })
5331
6181
  }) : null,
5332
6182
  mode === "remove" && row.ref !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmBar, {
5333
6183
  text: t("accountRemoveConfirm", { name: row.label }),
@@ -5389,163 +6239,218 @@ window.__ModuleLoader__.load({
5389
6239
  function LoginStatus({ state, t, onCancel }) {
5390
6240
  const hint = loginHint(state, t);
5391
6241
  const busy = state.phase === "starting" || state.phase === "waiting";
5392
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5393
- className: "cc-loginStatus",
5394
- role: "status",
5395
- children: [
5396
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5397
- className: hint.className,
5398
- title: hint.title,
5399
- children: hint.text
5400
- }),
5401
- state.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
5402
- className: "cc-loginLink",
5403
- href: state.authUrl,
5404
- target: "_blank",
5405
- rel: "noreferrer",
5406
- children: t("loginOpenLink")
5407
- }) : null,
5408
- busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5409
- type: "button",
5410
- className: "cc-reset",
5411
- onClick: onCancel,
5412
- children: t("loginCancel")
5413
- }) : null
5414
- ]
6242
+ const tone = hint.className === "cc-loginError" ? "error" : hint.className === "cc-loginDone" ? "success" : "neutral";
6243
+ const link = state.authUrl !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
6244
+ className: "cc-loginLink",
6245
+ href: state.authUrl,
6246
+ target: "_blank",
6247
+ rel: "noreferrer",
6248
+ children: [t("loginOpenLink"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6249
+ name: "external",
6250
+ className: "cc-icon cc-iconInline"
6251
+ })]
6252
+ }) : null;
6253
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6254
+ tone,
6255
+ actions: link !== null || busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [link, busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6256
+ variant: "ghost",
6257
+ size: "sm",
6258
+ onClick: onCancel,
6259
+ children: t("loginCancel")
6260
+ }) : null] }) : void 0,
6261
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6262
+ title: hint.title,
6263
+ children: hint.text
6264
+ })
5415
6265
  });
5416
6266
  }
5417
- /** 新增面板只负责输入和操作意图;账号、登录、命名与补偿由宿主过程负责。 */
6267
+ /** Enrollment phases during which the panel only offers "cancel setup". */
6268
+ const ENROLLMENT_BUSY_PHASES = [
6269
+ "creating",
6270
+ "waiting",
6271
+ "writing",
6272
+ "cancelling"
6273
+ ];
6274
+ /**
6275
+ * What the add-account panel's action row shows, from the enrollment phase
6276
+ * and the panel's own inputs — a pure function so the button logic is tested
6277
+ * without rendering:
6278
+ * - `naming`: the host could not save the name; retry / accept / cancel.
6279
+ * - `cancelSetup`: a sign-in or key write is in flight; only "cancel setup",
6280
+ * with the method switch and inputs locked.
6281
+ * - `start`: idle; cancel plus the mode's primary action ("Start sign-in", or
6282
+ * "Add" once a key is typed in paste mode).
6283
+ */
6284
+ function addAccountPanelView({ phase, mode, keyReady, disabled }) {
6285
+ if (phase === "naming") return {
6286
+ actions: "naming",
6287
+ locked: true,
6288
+ canSubmit: false
6289
+ };
6290
+ if (phase !== void 0 && ENROLLMENT_BUSY_PHASES.includes(phase)) return {
6291
+ actions: "cancelSetup",
6292
+ locked: true,
6293
+ canSubmit: false
6294
+ };
6295
+ return {
6296
+ actions: "start",
6297
+ locked: disabled,
6298
+ canSubmit: !disabled && (mode === "browser" || keyReady)
6299
+ };
6300
+ }
6301
+ /**
6302
+ * 新增面板只负责输入和操作意图;账号、登录、命名与补偿由宿主过程负责。
6303
+ * Drawn as the design canvas does: a "sign in / paste key" segmented switch,
6304
+ * the optional name, the key field in paste mode, then cancel + the primary
6305
+ * action on the right. Leaving paste mode clears the typed key, so a key never
6306
+ * lingers in a field the user has moved away from.
6307
+ */
5418
6308
  function AddAccountPanel({ state, disabled, t, enrollment, snapshot, onClose }) {
5419
6309
  const [label, setLabel] = (0, react.useState)("");
5420
- const [pasting, setPasting] = (0, react.useState)(false);
6310
+ const [mode, setMode] = (0, react.useState)("browser");
6311
+ const [key, setKey] = (0, react.useState)("");
6312
+ const [keyVisible, setKeyVisible] = (0, react.useState)(false);
5421
6313
  const active = snapshot.active;
5422
- const busy = active !== void 0 && [
5423
- "creating",
5424
- "waiting",
5425
- "writing",
5426
- "cancelling",
5427
- "naming"
5428
- ].includes(active.phase);
6314
+ const view = addAccountPanelView({
6315
+ phase: active?.phase,
6316
+ mode,
6317
+ keyReady: key.trim() !== "",
6318
+ disabled
6319
+ });
5429
6320
  const fallbackName = t("accountNameN", { n: state.accounts.length + 2 });
5430
6321
  (0, react.useEffect)(() => {
5431
6322
  if (active?.phase === "finished") onClose();
5432
6323
  }, [active?.phase, onClose]);
5433
- const start = (key) => enrollment.begin({
5434
- mode: key === void 0 ? "browser" : "manual",
6324
+ const start = () => enrollment.begin({
6325
+ mode: mode === "browser" ? "browser" : "manual",
5435
6326
  label: label.trim() || fallbackName,
5436
6327
  automaticName: label.trim() === "",
5437
- ...key === void 0 ? {} : { key }
6328
+ ...mode === "browser" ? {} : { key }
5438
6329
  });
5439
6330
  const close = () => {
5440
6331
  enrollment.cancel();
5441
6332
  onClose();
5442
6333
  };
5443
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5444
- className: "cc-addPanel",
6334
+ const pick = (next) => {
6335
+ setMode(next);
6336
+ if (next === "browser") {
6337
+ setKey("");
6338
+ setKeyVisible(false);
6339
+ }
6340
+ };
6341
+ const waitingLink = active?.authUrl && active.phase === "waiting" ? active.authUrl : void 0;
6342
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
6343
+ className: "cc-panel",
5445
6344
  "aria-label": t("accountAdd"),
6345
+ onSubmit: (event) => {
6346
+ event.preventDefault();
6347
+ if (view.canSubmit) start();
6348
+ },
5446
6349
  children: [
5447
6350
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5448
6351
  className: "cc-panelTitle",
5449
6352
  children: t("accountAdd")
5450
6353
  }),
6354
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6355
+ className: "cc-rowDesc",
6356
+ children: t("accountAddHint")
6357
+ }),
6358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6359
+ className: "cc-seg",
6360
+ role: "group",
6361
+ "aria-label": t("accountAddMethod"),
6362
+ children: ["browser", "paste"].map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6363
+ type: "button",
6364
+ className: mode === option ? "cc-segItem cc-segOn" : "cc-segItem",
6365
+ "aria-pressed": mode === option,
6366
+ disabled: view.locked,
6367
+ onClick: () => pick(option),
6368
+ children: option === "browser" ? t("accountAddLogin") : t("accountAddPaste")
6369
+ }, option))
6370
+ }),
5451
6371
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5452
- className: "cc-input",
6372
+ className: "cc-fieldInput cc-panelInput",
5453
6373
  type: "text",
5454
6374
  "aria-label": t("accountNamePlaceholder"),
5455
6375
  placeholder: t("accountNamePlaceholder"),
5456
6376
  value: label,
5457
- disabled: disabled || busy,
6377
+ disabled: view.locked,
5458
6378
  onChange: (event) => setLabel(event.target.value)
5459
6379
  }),
5460
- active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5461
- className: "cc-hint",
5462
- role: "status",
5463
- children: t(active.messageKey)
5464
- }) : null,
5465
- active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5466
- className: "cc-hint",
5467
- children: t("accountAddLinkHint")
6380
+ mode === "paste" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6381
+ className: "cc-keyField",
6382
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6383
+ id: "cc-add-account-key",
6384
+ className: "cc-fieldInput cc-panelInput",
6385
+ type: keyVisible ? "text" : "password",
6386
+ "aria-label": t("accountKeyPlaceholder"),
6387
+ placeholder: t("accountKeyPlaceholder"),
6388
+ autoComplete: "new-password",
6389
+ spellCheck: false,
6390
+ autoFocus: true,
6391
+ value: key,
6392
+ disabled: view.locked,
6393
+ onChange: (event) => setKey(event.target.value)
6394
+ }), key !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6395
+ variant: "ghost",
6396
+ size: "sm",
6397
+ disabled: view.locked,
6398
+ onClick: () => setKeyVisible((shown) => !shown),
6399
+ children: keyVisible ? t("hide") : t("show")
6400
+ }) : null]
5468
6401
  }) : null,
5469
- active?.authUrl && active.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
5470
- className: "cc-linkButton",
5471
- href: active.authUrl,
5472
- target: "_blank",
5473
- rel: "noreferrer",
5474
- children: t("accountAddOpenLink")
6402
+ active ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Notice, {
6403
+ actions: waitingLink !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
6404
+ className: "cc-loginLink",
6405
+ href: waitingLink,
6406
+ target: "_blank",
6407
+ rel: "noreferrer",
6408
+ children: [t("accountAddOpenLink"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6409
+ name: "external",
6410
+ className: "cc-icon cc-iconInline"
6411
+ })]
6412
+ }) : void 0,
6413
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(active.messageKey) }), waitingLink !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("accountAddLinkHint") }) : null]
5475
6414
  }) : null,
5476
- snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5477
- className: "cc-loginError",
5478
- role: "status",
5479
- children: t(snapshot.error)
6415
+ snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6416
+ tone: "error",
6417
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(snapshot.error) })
5480
6418
  }) : null,
5481
- active?.phase === "naming" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5482
- className: "cc-inlineActions",
5483
- children: [
5484
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5485
- variant: "primary",
5486
- size: "sm",
5487
- disabled,
5488
- onClick: () => void enrollment.name(active.suggestedName || label || fallbackName),
5489
- children: t("accountNameSaveRetry")
5490
- }),
5491
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6419
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6420
+ className: "cc-panelActions",
6421
+ children: view.actions === "naming" && active !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5492
6423
  variant: "ghost",
5493
- size: "sm",
6424
+ onClick: close,
6425
+ children: t("cancel")
6426
+ }),
6427
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6428
+ variant: "outline",
5494
6429
  disabled,
5495
6430
  onClick: () => void enrollment.name(),
5496
6431
  children: t("accountNameAcceptExisting")
5497
6432
  }),
5498
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5499
- variant: "ghost",
5500
- size: "sm",
5501
- onClick: close,
5502
- children: t("cancel")
5503
- })
5504
- ]
5505
- }) : pasting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineInput, {
5506
- id: "cc-add-account-key",
5507
- label: t("accountKeyPlaceholder"),
5508
- secret: true,
5509
- placeholder: t("accountKeyPlaceholder"),
5510
- confirmLabel: t("accountAddConfirm"),
5511
- disabled: disabled || busy,
5512
- t,
5513
- onCancel: close,
5514
- onSubmit: (key) => {
5515
- start(key);
5516
- }
5517
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5518
- className: "cc-inlineActions",
5519
- children: [
5520
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6433
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5521
6434
  variant: "primary",
5522
- size: "sm",
5523
- disabled: disabled || busy,
5524
- onClick: () => void start(),
5525
- children: t("accountAddLogin")
5526
- }),
5527
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5528
- variant: "ghost",
5529
- size: "sm",
5530
- disabled: disabled || busy,
5531
- onClick: () => setPasting(true),
5532
- children: t("accountAddPaste")
5533
- }),
5534
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
5535
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5536
- variant: "ghost",
5537
- size: "sm",
5538
- onClick: close,
5539
- children: t("cancel")
6435
+ disabled,
6436
+ onClick: () => void enrollment.name(active.suggestedName || label || fallbackName),
6437
+ children: t("accountNameSaveRetry")
5540
6438
  })
5541
- ]
5542
- }),
5543
- busy && active?.phase !== "naming" && pasting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5544
- variant: "ghost",
5545
- size: "sm",
5546
- onClick: close,
5547
- children: t("accountAddCancel")
5548
- }) : null
6439
+ ] }) : view.actions === "cancelSetup" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6440
+ variant: "ghost",
6441
+ onClick: close,
6442
+ children: t("accountAddCancel")
6443
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6444
+ variant: "ghost",
6445
+ onClick: close,
6446
+ children: t("cancel")
6447
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6448
+ variant: "primary",
6449
+ type: "submit",
6450
+ disabled: !view.canSubmit,
6451
+ children: mode === "browser" ? t("accountAddStart") : t("accountAddConfirm")
6452
+ })] })
6453
+ })
5549
6454
  ]
5550
6455
  });
5551
6456
  }
@@ -5554,7 +6459,7 @@ window.__ModuleLoader__.load({
5554
6459
  * each, with its status and quota inline — so one account's facts no longer
5555
6460
  * have to be cross-read across separate surfaces.
5556
6461
  */
5557
- function AccountsCard({ t, state, usage, login, disabled, actions, onRefresh, openAnalytics, createEnrollment }) {
6462
+ function AccountsCard({ t, state, usage, login, disabled, actions, onRefresh, openAnalytics, createEnrollment, expanded, onToggle }) {
5558
6463
  const [adding, setAdding] = (0, react.useState)(false);
5559
6464
  const [enrollment] = (0, react.useState)(createEnrollment);
5560
6465
  const snapshot = (0, react.useSyncExternalStore)(enrollment.store.subscribe, enrollment.store.getSnapshot);
@@ -5570,124 +6475,128 @@ window.__ModuleLoader__.load({
5570
6475
  const rows = accountRows(state, usage, t).filter((row) => row.ref === void 0 || !state.accountEnrollmentRefs.includes(row.ref));
5571
6476
  const multi = rows.length > 1;
5572
6477
  const pinnedRow = rows.find((row) => row.id === state.activeAccount);
5573
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5574
- className: "cc-group",
5575
- "aria-label": t("accountsTitle"),
5576
- children: [
5577
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5578
- className: "cc-groupHead",
5579
- children: [
5580
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
5581
- className: "cc-groupTitle",
5582
- children: t("accountsTitle")
5583
- }),
5584
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
5585
- openAnalytics ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5586
- variant: "ghost",
5587
- size: "sm",
5588
- onClick: openAnalytics,
5589
- children: t("openAnalytics")
5590
- }) : null,
5591
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5592
- type: "button",
5593
- className: "cc-linkButton",
5594
- disabled: loading,
5595
- onClick: () => {
5596
- onRefresh();
5597
- enrollment.refresh();
5598
- },
5599
- children: loading ? t("usageRefreshing") : t("usageRefresh")
5600
- })
5601
- ]
6478
+ const issues = accountIssues(rows.map((row) => row.usage));
6479
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FoldableGroup, {
6480
+ id: "accounts",
6481
+ title: t("accountsTitle"),
6482
+ divided: false,
6483
+ expanded,
6484
+ onToggle,
6485
+ badges: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, { parts: [rows.length === 1 ? t("accountsSummaryOne") : t("accountsSummaryMany", { count: rows.length })] }), issues.count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6486
+ className: issues.severe ? "cc-badge cc-badgeError" : "cc-badge cc-badgeWarn",
6487
+ children: t("accountsSummaryIssues", { count: issues.count })
6488
+ }) : null] }),
6489
+ action: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6490
+ variant: "ghost",
6491
+ size: "sm",
6492
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6493
+ name: "refresh",
6494
+ className: "cc-icon cc-btnIcon"
5602
6495
  }),
6496
+ disabled: loading,
6497
+ onClick: () => {
6498
+ onRefresh();
6499
+ enrollment.refresh();
6500
+ },
6501
+ children: loading ? t("usageRefreshing") : t("usageRefresh")
6502
+ }),
6503
+ children: [
5603
6504
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5604
6505
  className: "cc-groupDesc",
5605
6506
  children: multi ? t("accountsRotationHint") : t("accountsHint")
5606
6507
  }),
5607
6508
  multi ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5608
6509
  className: "cc-accountMode",
5609
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: pinnedRow !== void 0 ? t("accountModePinned", { name: pinnedRow.label }) : t("accountModeAuto") }), pinnedRow !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5610
- type: "button",
5611
- className: "cc-reset",
5612
- disabled: disabled || state.accountBusy,
5613
- onClick: () => void actions.setActive(""),
5614
- children: t("accountActionUnpin")
5615
- }) : null]
5616
- }) : null,
5617
- usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5618
- className: "cc-rowError",
5619
- role: "status",
5620
- children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""]
5621
- }) : null,
5622
- settingsWriteNotice(state.accountResult, t) || state.accountFailed !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5623
- className: "cc-rowError",
5624
- role: "status",
5625
- children: settingsWriteNotice(state.accountResult, t) || t("accountOpFailed")
5626
- }) : null,
5627
- state.credentialRefreshFailed || state.accountResult?.refreshFailed || state.saveResult?.refreshFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5628
- className: "cc-rowError",
5629
- role: "status",
5630
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("writeRefreshFailed") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5631
- type: "button",
5632
- className: "cc-btn cc-btnSm",
5633
- onClick: actions.refreshCredentials,
5634
- children: t("writeRefreshRetry")
5635
- })]
5636
- }) : null,
5637
- !adding && snapshot.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5638
- className: "cc-rowError",
5639
- role: "status",
5640
- children: t(snapshot.error)
5641
- }) : null,
5642
- state.pendingCredentialCleanup.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5643
- className: "cc-rowError",
5644
- role: "status",
5645
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("accountCleanupPending") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5646
- type: "button",
5647
- className: "cc-btn cc-btnSm",
5648
- disabled: disabled || state.accountBusy,
5649
- onClick: () => {
5650
- for (const ref of state.pendingCredentialCleanup) actions.cleanup(ref);
5651
- },
5652
- children: t("accountCleanupRetry")
5653
- })]
5654
- }) : null,
5655
- snapshot.pending.filter((record) => record.id !== snapshot.active?.id).map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5656
- className: "cc-rowError",
5657
- role: "status",
5658
6510
  children: [
5659
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
5660
- record.ref,
5661
- t("accountRefSeparator"),
5662
- t(record.messageKey)
5663
- ] }),
5664
- record.phase === "naming" && record.suggestedName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6511
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6512
+ name: "swap",
6513
+ className: "cc-icon cc-btnIcon"
6514
+ }),
6515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: pinnedRow !== void 0 ? t("accountModePinned", { name: pinnedRow.label }) : t("accountModeAuto") }),
6516
+ pinnedRow !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
5665
6517
  variant: "ghost",
5666
6518
  size: "sm",
6519
+ disabled: disabled || state.accountBusy,
6520
+ onClick: () => void actions.setActive(""),
6521
+ children: t("accountActionUnpin")
6522
+ }) : null
6523
+ ]
6524
+ }) : null,
6525
+ (() => {
6526
+ const writeNotice = settingsWriteNotice(state.accountResult, t);
6527
+ const notices = [];
6528
+ if (usage.status === "error") notices.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6529
+ tone: "error",
6530
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [t("usageError"), usage.error !== void 0 && usage.error !== "" ? ` — ${usage.error}` : ""] })
6531
+ }, "usage"));
6532
+ if (writeNotice || state.accountFailed !== void 0) notices.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6533
+ tone: "error",
6534
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: writeNotice || t("accountOpFailed") })
6535
+ }, "op"));
6536
+ if (state.credentialRefreshFailed || state.accountResult?.refreshFailed || state.saveResult?.refreshFailed) notices.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6537
+ tone: "warn",
6538
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6539
+ variant: "outline",
6540
+ size: "sm",
6541
+ onClick: actions.refreshCredentials,
6542
+ children: t("writeRefreshRetry")
6543
+ }),
6544
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("writeRefreshFailed") })
6545
+ }, "refresh"));
6546
+ if (!adding && snapshot.error) notices.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6547
+ tone: "error",
6548
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(snapshot.error) })
6549
+ }, "enroll"));
6550
+ if (state.pendingCredentialCleanup.length > 0) notices.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6551
+ tone: "warn",
6552
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6553
+ variant: "outline",
6554
+ size: "sm",
6555
+ disabled: disabled || state.accountBusy,
6556
+ onClick: () => {
6557
+ for (const ref of state.pendingCredentialCleanup) actions.cleanup(ref);
6558
+ },
6559
+ children: t("accountCleanupRetry")
6560
+ }),
6561
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("accountCleanupPending") })
6562
+ }, "cleanup"));
6563
+ for (const record of snapshot.pending.filter((candidate) => candidate.id !== snapshot.active?.id)) notices.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6564
+ tone: "warn",
6565
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [record.phase === "naming" && record.suggestedName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6566
+ variant: "outline",
6567
+ size: "sm",
5667
6568
  disabled,
5668
6569
  onClick: () => void enrollment.recover(record, record.suggestedName),
5669
6570
  children: t("accountNameSaveRetry")
5670
- }) : null,
5671
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5672
- variant: "ghost",
6571
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6572
+ variant: "outline",
5673
6573
  size: "sm",
5674
6574
  disabled,
5675
6575
  onClick: () => void enrollment.recover(record),
5676
6576
  children: record.phase === "naming" ? t("accountNameAcceptExisting") : t("accountEnrollmentCleanupRetry")
5677
- })
5678
- ]
5679
- }, record.id)),
5680
- snapshot.active?.phase === "cleanup-needed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5681
- className: "cc-rowError",
5682
- role: "status",
5683
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(snapshot.active.messageKey) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5684
- variant: "ghost",
5685
- size: "sm",
5686
- disabled,
5687
- onClick: () => void enrollment.recover(snapshot.active),
5688
- children: t("accountEnrollmentCleanupRetry")
5689
- })]
5690
- }) : null,
6577
+ })] }),
6578
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
6579
+ record.ref,
6580
+ t("accountRefSeparator"),
6581
+ t(record.messageKey)
6582
+ ] })
6583
+ }, record.id));
6584
+ if (snapshot.active?.phase === "cleanup-needed") notices.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6585
+ tone: "warn",
6586
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6587
+ variant: "outline",
6588
+ size: "sm",
6589
+ disabled,
6590
+ onClick: () => void enrollment.recover(snapshot.active),
6591
+ children: t("accountEnrollmentCleanupRetry")
6592
+ }),
6593
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(snapshot.active.messageKey) })
6594
+ }, "active-cleanup"));
6595
+ return notices.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6596
+ className: "cc-notices",
6597
+ children: notices
6598
+ }) : null;
6599
+ })(),
5691
6600
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5692
6601
  className: "cc-accountList",
5693
6602
  children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountItem, {
@@ -5709,19 +6618,44 @@ window.__ModuleLoader__.load({
5709
6618
  enrollment,
5710
6619
  snapshot,
5711
6620
  onClose: closeAdd
5712
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5713
- type: "button",
5714
- className: "cc-addButton",
5715
- disabled,
5716
- onClick: () => {
5717
- enrollment.reset();
5718
- setAdding(true);
5719
- },
5720
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5721
- className: "cc-addGlyph",
5722
- "aria-hidden": "true"
5723
- }), t("accountAdd")]
5724
- })
6621
+ }) : null,
6622
+ !adding || openAnalytics ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6623
+ className: "cc-accountActions",
6624
+ children: [
6625
+ !adding ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6626
+ variant: "outline",
6627
+ size: "md",
6628
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6629
+ name: "plus",
6630
+ className: "cc-icon cc-btnIcon"
6631
+ }),
6632
+ disabled,
6633
+ onClick: () => {
6634
+ enrollment.reset();
6635
+ setAdding(true);
6636
+ },
6637
+ children: t("accountAdd")
6638
+ }) : null,
6639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-spacer" }),
6640
+ openAnalytics ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PageButton, {
6641
+ variant: "ghost",
6642
+ size: "md",
6643
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6644
+ name: "chart",
6645
+ className: "cc-icon cc-btnIcon"
6646
+ }),
6647
+ className: "cc-analyticsEntry",
6648
+ onClick: openAnalytics,
6649
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6650
+ className: "cc-analyticsEntryText",
6651
+ children: t("openAnalytics")
6652
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
6653
+ name: "arrowRight",
6654
+ className: "cc-icon cc-btnIcon"
6655
+ })]
6656
+ }) : null
6657
+ ]
6658
+ }) : null
5725
6659
  ]
5726
6660
  });
5727
6661
  }
@@ -5766,6 +6700,51 @@ window.__ModuleLoader__.load({
5766
6700
  return available;
5767
6701
  }
5768
6702
  /**
6703
+ * The service status: paint the cached snapshot immediately, then run one
6704
+ * throttled check per page open (the TTL and every failure path live in
6705
+ * ./service-status.ts, which never rejects). The Host probe runs alongside it
6706
+ * and is the row's real-time half — the published record only moves on a status
6707
+ * change or the daily summary, so it can legitimately be hours old.
6708
+ */
6709
+ function useServiceStatus(probeService) {
6710
+ const [snapshot, setSnapshot] = (0, react.useState)(() => readCachedServiceStatus(localStorageServiceStatusStore()));
6711
+ const [probe, setProbe] = (0, react.useState)({ kind: "checking" });
6712
+ (0, react.useEffect)(() => {
6713
+ let cancelled = false;
6714
+ checkServiceStatus({ store: localStorageServiceStatusStore() }).then((next) => {
6715
+ if (!cancelled) setSnapshot(next);
6716
+ }, () => {});
6717
+ probeService().then((result) => {
6718
+ if (cancelled) return;
6719
+ setProbe(result.ok ? {
6720
+ kind: "result",
6721
+ result: result.value
6722
+ } : { kind: "unavailable" });
6723
+ }, () => {
6724
+ if (!cancelled) setProbe({ kind: "unavailable" });
6725
+ });
6726
+ return () => {
6727
+ cancelled = true;
6728
+ };
6729
+ }, []);
6730
+ return {
6731
+ snapshot,
6732
+ probe
6733
+ };
6734
+ }
6735
+ /**
6736
+ * The page's fold state, read once from this browser's storage and written
6737
+ * back on every change (./group-fold.ts holds the defaults and fallbacks).
6738
+ */
6739
+ function useGroupFold() {
6740
+ const [store] = (0, react.useState)(localStorageFoldStore);
6741
+ const [fold, setFoldState] = (0, react.useState)(() => store.read());
6742
+ return [fold, (0, react.useCallback)((next) => {
6743
+ setFoldState(next);
6744
+ store.write(next);
6745
+ }, [store])];
6746
+ }
6747
+ /**
5769
6748
  * What the save bar says. It exists only while there is something to act on
5770
6749
  * or to confirm: pending edits, a save in flight, a failure, or the brief
5771
6750
  * confirmation after a save landed. Account operations never reach it, they
@@ -5842,7 +6821,7 @@ window.__ModuleLoader__.load({
5842
6821
  * animate out, and keeps showing its last message while it does (the state
5843
6822
  * that hid it has no message of its own).
5844
6823
  */
5845
- function SaveBar({ view, state, t, onDiscard, onSave }) {
6824
+ function SaveBar({ view, state, t, onDiscard, onSave, onLocate }) {
5846
6825
  const last = (0, react.useRef)(view);
5847
6826
  if (view.visible) last.current = view;
5848
6827
  const shown = view.visible ? view : last.current;
@@ -5864,19 +6843,29 @@ window.__ModuleLoader__.load({
5864
6843
  }),
5865
6844
  shown.actions ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5866
6845
  className: "cc-saveBarActions",
5867
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5868
- type: "button",
5869
- className: "cc-saveBarButton cc-saveBarGhost",
5870
- disabled: !view.visible || !state.dirty && !state.failed || state.saving,
5871
- onClick: onDiscard,
5872
- children: t("discard")
5873
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5874
- type: "button",
5875
- className: "cc-saveBarButton cc-saveBarPrimary",
5876
- disabled: !view.visible || !state.dirty || state.invalid || state.saving,
5877
- onClick: onSave,
5878
- children: t(state.saving ? "saving" : "save")
5879
- })]
6846
+ children: [
6847
+ state.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6848
+ variant: "ghost",
6849
+ size: "lg",
6850
+ disabled: !view.visible,
6851
+ onClick: onLocate,
6852
+ children: t("saveLocate")
6853
+ }) : null,
6854
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6855
+ variant: "ghost",
6856
+ size: "lg",
6857
+ disabled: !view.visible || !state.dirty && !state.failed || state.saving,
6858
+ onClick: onDiscard,
6859
+ children: t("discard")
6860
+ }),
6861
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageButton, {
6862
+ variant: "primary",
6863
+ size: "lg",
6864
+ disabled: !view.visible || !state.dirty || state.invalid || state.saving,
6865
+ onClick: onSave,
6866
+ children: t(state.saving ? "saving" : "save")
6867
+ })
6868
+ ]
5880
6869
  }) : null
5881
6870
  ]
5882
6871
  })
@@ -5923,7 +6912,30 @@ window.__ModuleLoader__.load({
5923
6912
  onEdit: props.edit,
5924
6913
  onReset: props.resetField
5925
6914
  };
6915
+ const status = useServiceStatus(props.probeService);
6916
+ const [fold, setFold] = useGroupFold();
6917
+ const foldProps = (group) => ({
6918
+ expanded: fold[group],
6919
+ onToggle: () => setFold({
6920
+ ...fold,
6921
+ [group]: !fold[group]
6922
+ })
6923
+ });
6924
+ const sectionRef = (0, react.useRef)(null);
6925
+ const [locateTick, setLocateTick] = (0, react.useState)(0);
6926
+ (0, react.useEffect)(() => {
6927
+ if (locateTick === 0) return;
6928
+ const target = sectionRef.current?.querySelector("[aria-invalid=\"true\"]");
6929
+ if (target === null || target === void 0) return;
6930
+ target.scrollIntoView({ block: "center" });
6931
+ target.focus({ preventScroll: true });
6932
+ }, [locateTick]);
6933
+ const locate = () => {
6934
+ setFold(expandGroups(fold, invalidGroups(state)));
6935
+ setLocateTick((tick) => tick + 1);
6936
+ };
5926
6937
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6938
+ ref: sectionRef,
5927
6939
  className: bar.visible ? "cc-section cc-sectionWithBar" : "cc-section",
5928
6940
  "aria-label": t("title"),
5929
6941
  children: [
@@ -5935,10 +6947,9 @@ window.__ModuleLoader__.load({
5935
6947
  className: "cc-intro",
5936
6948
  children: t("intro")
5937
6949
  }),
5938
- !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5939
- className: "cc-readOnly",
5940
- role: "status",
5941
- children: t("readOnly")
6950
+ !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6951
+ icon: "lock",
6952
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("readOnly") })
5942
6953
  }) : null,
5943
6954
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountsCard, {
5944
6955
  t,
@@ -5949,17 +6960,30 @@ window.__ModuleLoader__.load({
5949
6960
  actions,
5950
6961
  onRefresh: props.refreshUsage,
5951
6962
  openAnalytics: props.openAnalytics,
5952
- createEnrollment: props.createEnrollment
6963
+ createEnrollment: props.createEnrollment,
6964
+ ...foldProps("accounts")
5953
6965
  }),
5954
6966
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelsCard, {
5955
6967
  ...form,
6968
+ ...foldProps("models"),
5956
6969
  onSelect: props.editVisibleModels,
5957
6970
  onClear: props.clearVisibleModels,
5958
6971
  onOverrides: props.editContextWindowOverrides
5959
6972
  }),
5960
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrivacyCard, { ...form }),
5961
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntegrationsCard, { ...form }),
5962
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedSection, { ...form }),
6973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrivacyCard, {
6974
+ ...form,
6975
+ ...foldProps("privacy")
6976
+ }),
6977
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntegrationsCard, {
6978
+ ...form,
6979
+ ...foldProps("integrations"),
6980
+ status: status.snapshot,
6981
+ probe: status.probe
6982
+ }),
6983
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdvancedSection, {
6984
+ ...form,
6985
+ ...foldProps("advanced")
6986
+ }),
5963
6987
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5964
6988
  className: "cc-version",
5965
6989
  children: [
@@ -5985,7 +7009,8 @@ window.__ModuleLoader__.load({
5985
7009
  state,
5986
7010
  t,
5987
7011
  onDiscard: props.discard,
5988
- onSave: props.save
7012
+ onSave: props.save,
7013
+ onLocate: locate
5989
7014
  })
5990
7015
  ]
5991
7016
  });
@@ -9202,53 +10227,105 @@ window.__ModuleLoader__.load({
9202
10227
  /**
9203
10228
  * The settings-page stylesheet.
9204
10229
  *
9205
- * The metrics are the harness's own settings pages', read out of the 0.1.7
9206
- * bundles so the page sits beside General and Models without looking foreign:
9207
- * a row is 12px of padding, its title 14/22 label-primary and its description
9208
- * 12/18 label-tertiary, the control on the right. The hairline rhythm is
9209
- * section-level: a 0.5px border-l2 rule separates the GROUPS (see
9210
- * `cc-groupDivided`), never the rows inside one, so a group's rows read as one
9211
- * list. Selectors and buttons are
9212
- * capsules (36px / 28px compact), inputs are
9213
- * 32px with an 8px radius and a 0.5px border-l4 edge, a card is a 0.5px
9214
- * border-l4 outline with a 16px radius, and a nested panel is a
9215
- * bg-module-platform fill with a 12px radius. Errors use state-error-primary.
10230
+ * Every size is the design canvas's (the cc-kit.css of the "CommandCode 设置页
10231
+ * 重设计" artboards, 2026-10-21), with its colour tokens mapped onto the
10232
+ * harness theme aliases so dark mode keeps working: a row is 12px of vertical
10233
+ * padding with no line between rows, title 14/22, description 12/18, a 24px
10234
+ * gap to the control; a group is 16px of padding under a 1px border-l2 rule,
10235
+ * its heading 36px high; the page title is 18/26 and group titles 14/22, both
10236
+ * 600. The page renders with grayscale antialiasing and descriptive text in
10237
+ * label-secondary.
10238
+ *
10239
+ * Shapes follow one rule — clickable things are pills, fillable things are
10240
+ * 8px. Buttons are drawn here (PageButton → cc-btn: md 32px, sm 28px, lg 36px
10241
+ * for the save bar), not taken from the harness primitive. Inputs and the
10242
+ * model selector are 32px with a 1px border-l3 stroke on the raised surface; a
10243
+ * number field draws its unit inside the box (cc-unitBox). Account cards and
10244
+ * the per-model override table are 1px border-l2 cards with 12px corners;
10245
+ * panels and notices are 12px-radius fills; badges are 20px pills. Errors use
10246
+ * state-error-primary; tinted surfaces fall back to a color-mix of the state
10247
+ * colour where the theme has no tertiary token. The Models-page provider card
10248
+ * shares cc-hint / cc-badgeMuted / cc-input and keeps their colours.
9216
10249
  */
9217
10250
  const PAGE_CSS = `
9218
- .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex}
9219
- .cc-title{margin:0;color:var(--dsw-alias-label-primary);font-size:16px;font-weight:500;line-height:24px}
9220
- .cc-intro{margin:4px 0 0;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}
9221
- .cc-readOnly{margin:8px 0 0;color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-tertiary));font-size:12px;line-height:18px}
10251
+ .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
10252
+ .cc-title{margin:0;color:var(--dsw-alias-label-primary);font-size:18px;font-weight:600;line-height:26px}
10253
+ .cc-intro{margin:4px 0 0;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:22px}
9222
10254
  .cc-spacer{flex:1}
9223
- .cc-group{flex-direction:column;display:flex;margin-top:28px}
9224
- .cc-groupDivided{margin-top:24px;padding-top:20px;border-top:.5px solid var(--dsw-alias-border-l2)}
9225
- .cc-groupHead{align-items:center;gap:8px;display:flex;min-height:28px;padding-bottom:4px}
9226
- .cc-groupTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
9227
- .cc-groupDesc{margin:0 0 12px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
9228
- .cc-rows>.cc-groupDesc{margin:0;padding:4px 0 0}
9229
- .cc-disclosure{width:100%;padding:0 0 4px;border:0;background:0 0;font:inherit;text-align:left;cursor:pointer;border-radius:6px}
10255
+ .cc-group{flex-direction:column;display:flex;padding:16px 0}
10256
+ .cc-group:not(.cc-groupDivided){padding-top:24px}
10257
+ .cc-groupDivided{border-top:1px solid var(--dsw-alias-border-l2)}
10258
+ .cc-groupHead{align-items:center;gap:8px;display:flex;min-height:36px}
10259
+ .cc-groupTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:22px}
10260
+ .cc-groupDesc{margin:2px 0 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10261
+ .cc-rows>.cc-groupDesc{margin:0}
10262
+ .cc-groupHeading{flex:1;min-width:0;margin:0;font:inherit;display:flex}
10263
+ /* The whole heading row is the fold toggle: chevron, title, summary badges. */
10264
+ .cc-disclosure{box-sizing:border-box;flex-wrap:wrap;flex:1;align-items:center;gap:6px 8px;min-width:0;min-height:36px;padding:0;border:0;background:0 0;color:inherit;font:inherit;text-align:left;cursor:pointer;border-radius:8px;display:flex}
10265
+ .cc-foldChevron{color:var(--dsw-alias-label-tertiary);transition:transform .15s ease}
10266
+ .cc-foldChevronClosed{transform:rotate(-90deg)}
10267
+ .cc-groupBody{flex-direction:column;display:flex;padding-top:4px}
10268
+ .cc-groupBody[hidden]{display:none}
9230
10269
  .cc-disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
9231
10270
  .cc-rows{flex-direction:column;display:flex}
9232
- .cc-row{align-items:center;gap:8px;display:flex;padding:12px 0}
10271
+ .cc-row{flex-wrap:wrap;align-items:center;gap:8px 24px;display:flex;padding:12px 0}
9233
10272
  .cc-rowFlush{padding:0}
9234
- .cc-rowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:32px;display:flex}
10273
+ .cc-rowText{flex-direction:column;flex:1 1 300px;gap:2px;min-width:0;display:flex}
9235
10274
  .cc-rowTitleLine{align-items:center;gap:8px;display:flex;min-width:0}
9236
10275
  .cc-rowTitle{min-width:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}
9237
- .cc-rowDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
10276
+ .cc-rowDesc{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
9238
10277
  .cc-rowDesc p{margin:0}
9239
10278
  .cc-rowDesc p+p{margin-top:4px}
9240
10279
  .cc-rowError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
9241
- .cc-rowControl{flex:none;align-items:center;justify-content:flex-end;gap:8px;display:inline-flex;max-width:60%}
10280
+ .cc-rowControl{flex:none;align-items:center;gap:8px;display:inline-flex;margin-left:auto}
9242
10281
  .cc-rowInput{width:200px}
9243
10282
  .cc-rowInputWide{width:280px}
9244
- .cc-linkButton{box-sizing:border-box;flex:none;align-items:center;display:inline-flex;height:28px;padding:0 10px;border:0;border-radius:14px;background:0 0;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer}
9245
- .cc-linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
9246
- .cc-linkButton:disabled{cursor:default;opacity:.4}
9247
- .cc-linkButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
10283
+ /* The row-level field, drawn to the design canvas: 32px, 8px corners, a 1px
10284
+ * border-l3 stroke on the raised surface. A number field draws its unit inside
10285
+ * the same box (cc-unitBox), right of the value. */
10286
+ .cc-fieldInput{box-sizing:border-box;flex:none;height:32px;min-width:0;padding:0 10px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font:inherit;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
10287
+ .cc-fieldInput::placeholder{color:var(--dsw-alias-label-dimmed)}
10288
+ .cc-fieldInput:hover:not(:disabled){border-color:var(--dsw-alias-label-tertiary)}
10289
+ .cc-fieldInput:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10290
+ .cc-fieldInput:disabled{opacity:.5;cursor:default}
10291
+ .cc-fieldInputInvalid,.cc-fieldInputInvalid:hover{border-color:var(--dsw-alias-state-error-primary)}
10292
+ .cc-unitBox{box-sizing:border-box;flex:none;align-items:center;display:inline-flex;width:160px;height:32px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}
10293
+ .cc-unitBox:hover{border-color:var(--dsw-alias-label-tertiary)}
10294
+ .cc-unitBox:focus-within{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10295
+ .cc-unitBoxInvalid,.cc-unitBoxInvalid:hover{border-color:var(--dsw-alias-state-error-primary)}
10296
+ .cc-unitBox:has(>.cc-fieldInput:disabled){opacity:.5}
10297
+ .cc-unitBox>.cc-fieldInput{flex:1;width:auto;height:30px;border:0;border-radius:0;background:0 0;outline:none;text-align:right;font-variant-numeric:tabular-nums}
10298
+ .cc-unit{flex:none;padding:0 10px 0 2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}
10299
+ /* Holds the reset button's column on rows that have nothing to reset, so the
10300
+ * staged controls of one group line up on a single right edge. */
10301
+ .cc-resetSlot{flex:none;width:28px;height:28px}
10302
+ .cc-icon{flex:none;width:16px;height:16px}
10303
+ /* The page's buttons (PageButton), drawn to the design canvas: one capsule
10304
+ * shape, md 32px / 14px text / 14px icon, sm 28px / 12px text, lg 36px for
10305
+ * the save bar; primary is the brand fill, secondary the quiet grey fill,
10306
+ * ghost label-secondary text, danger the error fill. */
10307
+ .cc-btn{box-sizing:border-box;flex:none;align-items:center;justify-content:center;gap:6px;display:inline-flex;height:32px;padding:0 14px;border:0;border-radius:999px;corner-shape:round;font:inherit;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer;text-decoration:none}
10308
+ .cc-btnSm{height:28px;padding:0 10px;gap:4px;font-size:12px;line-height:18px}
10309
+ .cc-btnLg{height:36px;padding:0 16px}
10310
+ .cc-btn .cc-btnIcon{width:14px;height:14px}
10311
+ .cc-btnPrimary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}
10312
+ .cc-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary)))}
10313
+ .cc-btnSecondary{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}
10314
+ .cc-btnSecondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
10315
+ .cc-btnGhost{background:0 0;color:var(--dsw-alias-label-secondary)}
10316
+ .cc-btnGhost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10317
+ .cc-btnDanger{background:var(--dsw-alias-state-error-primary);color:#fff}
10318
+ .cc-btnDanger:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 88%,#000)}
10319
+ .cc-btn:disabled{cursor:default;opacity:.4}
10320
+ .cc-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10321
+ .cc-groupSummary{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:400;line-height:18px;white-space:nowrap}
9248
10322
  .cc-badges{align-items:center;gap:8px;display:inline-flex}
9249
- .cc-badge,.cc-badgeMuted{flex:none;align-items:center;display:inline-flex;white-space:nowrap;border-radius:999px;corner-shape:round;padding:1px 8px;font-size:12px;font-weight:500;line-height:17px}
10323
+ .cc-badge,.cc-badgeMuted{box-sizing:border-box;flex:none;align-items:center;gap:4px;display:inline-flex;height:20px;white-space:nowrap;border-radius:999px;corner-shape:round;padding:0 8px;font-size:12px;font-weight:500;line-height:18px}
9250
10324
  .cc-badge{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
9251
10325
  .cc-badgeMuted{border:.5px solid var(--dsw-alias-border-l4);color:var(--dsw-alias-label-tertiary)}
10326
+ .cc-badgeOutline{background:0 0;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l3);color:var(--dsw-alias-label-tertiary)}
10327
+ .cc-badgeOk{background:var(--dsw-alias-state-success-tertiary,color-mix(in srgb,var(--dsw-alias-state-success-primary,#16a34a) 10%,transparent));color:var(--dsw-alias-state-success-primary,#15803d)}
10328
+ .cc-badgeError{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
9252
10329
  .cc-badgeWarn{background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary))}
9253
10330
  .cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
9254
10331
  .cc-field+.cc-field{border-top:.5px solid var(--dsw-alias-border-l2)}
@@ -9263,13 +10340,13 @@ window.__ModuleLoader__.load({
9263
10340
  .cc-input:disabled{opacity:.6;cursor:default}
9264
10341
  .cc-inputInvalid{border-color:var(--dsw-alias-state-error-primary)}
9265
10342
  .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
9266
- .cc-modelSelectAnchor{flex-direction:column;align-items:stretch;gap:6px;display:inline-flex;min-width:160px}
9267
- .cc-selector{box-sizing:border-box;align-items:center;gap:12px;display:inline-flex;height:36px;padding:0 14px;border:0;border-radius:18px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}
9268
- .cc-selector:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
10343
+ .cc-modelSelectAnchor{flex-direction:column;align-items:stretch;gap:6px;display:inline-flex;width:200px}
10344
+ .cc-selector{box-sizing:border-box;justify-content:space-between;align-items:center;gap:8px;display:inline-flex;height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}
10345
+ .cc-selector:hover:not(:disabled){border-color:var(--dsw-alias-label-tertiary)}
9269
10346
  .cc-selector:disabled{cursor:default;opacity:.4}
9270
- .cc-selector:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
10347
+ .cc-selector:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
9271
10348
  .cc-selectorText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
9272
- .cc-selectorCaret{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin-bottom:3px;transform:rotate(45deg)}
10349
+ .cc-selectorChevron{color:var(--dsw-alias-label-tertiary)}
9273
10350
  /* The search box sits inside the Menu anchor, so typing in it never trips
9274
10351
  * the Menu's outside-click close. */
9275
10352
  .cc-modelSearch{width:100%}
@@ -9300,79 +10377,74 @@ window.__ModuleLoader__.load({
9300
10377
  .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
9301
10378
  .cc-toggle::after{content:'';display:block;width:16px;height:16px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s ease}
9302
10379
  .cc-toggle:checked::after{transform:translateX(16px)}
10380
+ .cc-toggle:not(:checked)::after{background:var(--dsw-alias-switch-thumb,var(--dsw-alias-label-primary-foreground))}
9303
10381
  .cc-toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
9304
10382
  .cc-toggle:disabled{cursor:default;opacity:.5}
9305
- .cc-chevron{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:7px;height:7px;margin-right:6px;margin-bottom:3px;transform:rotate(45deg);transition:transform .15s ease}
9306
- .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
9307
- .cc-accountMode{align-items:center;gap:4px;display:flex;margin:-4px 0 12px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10383
+ .cc-accountMode{align-items:center;gap:8px;display:flex;margin:12px 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
9308
10384
  .cc-accountList{flex-direction:column;gap:8px;display:flex}
9309
- .cc-accountList+.cc-addButton,.cc-accountList+.cc-addPanel{margin-top:8px}
9310
- .cc-accountItem{flex-direction:column;gap:10px;display:flex;padding:12px 14px;border:.5px solid var(--dsw-alias-border-l4);border-radius:16px}
9311
- .cc-accountItemActive{border-color:var(--dsw-static-neutral-bluish-400,var(--dsw-alias-border-l3))}
9312
- .cc-accountHead{align-items:center;gap:4px;display:flex;min-height:28px}
9313
- .cc-accountToggle{align-items:center;gap:8px;display:flex;flex:1;min-width:0;padding:0;background:0 0;border:0;color:inherit;cursor:pointer;font:inherit;text-align:left}
10385
+ .cc-accountList+.cc-panel{margin-top:8px}
10386
+ /* One row under the list: Add account (outline) leads, the analytics exit
10387
+ * (ghost) trails; the exit's long label clamps instead of wrapping the row. */
10388
+ .cc-accountActions{flex-wrap:wrap;align-items:center;gap:8px;display:flex;margin-top:12px}
10389
+ .cc-analyticsEntry{max-width:100%;min-width:0}
10390
+ .cc-analyticsEntryText{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
10391
+ .cc-chevronOpen{transform:rotate(180deg)}
10392
+ .cc-accountItem{flex-direction:column;display:flex;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}
10393
+ .cc-accountItem>.cc-notice,.cc-accountItem>.cc-panel{margin:0 16px 14px}
10394
+ .cc-accountItemActive{border-color:var(--dsw-alias-border-l3)}
10395
+ .cc-accountHead{align-items:center;gap:4px;display:flex;padding:8px 8px 8px 0}
10396
+ .cc-accountToggle{flex-wrap:wrap;align-items:center;gap:6px 8px;display:flex;flex:1;min-width:0;min-height:44px;padding:0 0 0 16px;background:0 0;border:0;border-radius:12px;color:inherit;cursor:pointer;font:inherit;text-align:left}
9314
10397
  .cc-accountToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;border-radius:4px}
9315
10398
  .cc-accountName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
9316
- .cc-iconButton{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:28px;height:28px;padding:0;background:0 0;border:0;border-radius:6px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
9317
- .cc-iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10399
+ .cc-iconButton{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:28px;height:28px;padding:0;background:0 0;border:0;border-radius:8px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
10400
+ .cc-iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10401
+ .cc-iconButton:disabled{cursor:default;opacity:.4}
9318
10402
  .cc-iconButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
9319
- /* Per-model context-length override rows (issue #86): the error wraps onto
9320
- its own line beneath the select/input/remove trio. The three cells form a
9321
- fixed column contract — flexible model, 112px value, 28px remove — and the
9322
- cc-contextHead labels repeat the first two (plus the remove cell's width
9323
- as a spacer) so they sit exactly over their columns. */
9324
- .cc-contextItem{align-items:center;gap:8px;display:flex;flex-wrap:wrap;padding:8px 0}
9325
- .cc-contextHead{align-items:center;gap:8px;display:flex;flex-wrap:wrap;padding:4px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
9326
- .cc-contextHeadModel{flex:1;min-width:200px}
9327
- .cc-contextHeadValue{flex:none;width:112px}
9328
- .cc-contextHeadGap{flex:none;width:28px}
9329
- .cc-contextSelect{position:relative;display:block;flex:1;min-width:200px}
9330
- /* appearance:none swaps the platform arrow for the cc-contextCaret, shaped
9331
- like cc-selectorCaret so every dropdown in the plugin opens with the same
9332
- hand-drawn chevron. */
9333
- .cc-contextModel{box-sizing:border-box;appearance:none;-webkit-appearance:none;width:100%;padding-inline-end:26px;text-overflow:ellipsis;cursor:pointer}
9334
- .cc-contextCaret{position:absolute;inset-inline-end:12px;top:50%;width:6px;height:6px;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);transform:translateY(-50%) rotate(45deg);pointer-events:none}
10403
+ /* Per-model context-length overrides (issue #86) as the design's small table:
10404
+ * a card holding a head row over a model / value / remove grid, a line
10405
+ * between rows; a row's error wraps onto its own line across the grid. */
10406
+ .cc-contextTable{flex-direction:column;display:flex;margin:0 0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}
10407
+ .cc-contextItem{grid-template-columns:minmax(0,1fr) 144px 28px;align-items:center;gap:8px;display:grid;padding:8px 8px 8px 12px}
10408
+ .cc-contextItem+.cc-contextItem{border-top:1px solid var(--dsw-alias-border-l2)}
10409
+ .cc-contextHead{grid-template-columns:minmax(0,1fr) 144px 28px;align-items:center;gap:8px;display:grid;height:32px;padding:0 8px 0 12px;border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
10410
+ .cc-contextSelect{position:relative;display:block;min-width:0}
10411
+ /* appearance:none swaps the platform arrow for the page's own chevron icon. */
10412
+ .cc-contextModel{box-sizing:border-box;appearance:none;-webkit-appearance:none;width:100%;padding-inline-end:30px;text-overflow:ellipsis;cursor:pointer}
10413
+ .cc-contextCaret{position:absolute;inset-inline-end:10px;top:50%;color:var(--dsw-alias-label-tertiary);transform:translateY(-50%);pointer-events:none}
9335
10414
  .cc-contextSelect:has(.cc-contextModel:disabled) .cc-contextCaret{opacity:.6}
9336
- .cc-contextValue{flex:none;width:112px}
9337
- .cc-contextError{flex-basis:100%}
9338
- /* Three dots from one element: the dot plus two box-shadow copies. */
9339
- .cc-kebab{width:3px;height:3px;border-radius:50%;corner-shape:round;background:currentColor;box-shadow:0 -5px 0 currentColor,0 5px 0 currentColor}
10415
+ .cc-contextItem>.cc-unitBox{width:100%}
10416
+ .cc-contextError{grid-column:1/-1}
9340
10417
  .cc-tabDot{flex-shrink:0;width:8px;height:8px;border-radius:50%;corner-shape:round}
9341
10418
  .cc-tabDotOk{background:var(--dsw-alias-state-success-primary)}
9342
10419
  .cc-tabDotBlank{background:0 0;border:0}
9343
10420
  .cc-tabDotWarn{background:var(--dsw-alias-state-warn-primary,#d97706)}
9344
10421
  .cc-tabDotError{background:var(--dsw-alias-state-error-primary)}
9345
- .cc-accountMeters{flex-wrap:wrap;gap:6px 20px;display:flex;padding-left:16px}
10422
+ .cc-accountMeters{flex-wrap:wrap;gap:6px 24px;display:flex;padding:0 16px 14px 32px}
9346
10423
  .cc-miniMeter{align-items:center;gap:8px;display:inline-flex;font-size:12px;line-height:18px}
9347
- .cc-miniMeterLabel{color:var(--dsw-alias-label-tertiary)}
10424
+ .cc-miniMeterLabel{color:var(--dsw-alias-label-secondary)}
9348
10425
  .cc-miniMeterTrack{overflow:hidden;background:var(--dsw-alias-bg-module-platform);border-radius:999px;width:64px;height:4px}
9349
10426
  .cc-miniMeterFill{display:block;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%}
9350
10427
  .cc-miniMeterValue{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
9351
- .cc-accountSetup,.cc-loginStatus{flex-direction:column;gap:8px;display:flex;padding-left:16px}
9352
- .cc-loginStatus{flex-direction:row;flex-wrap:wrap;align-items:center;gap:4px 12px}
9353
- .cc-loginStatus>p{margin:0;font-size:12px;line-height:18px}
9354
- .cc-accountDetails{container-type:inline-size;border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;display:flex;padding-top:12px}
9355
- .cc-inlineForm,.cc-confirmBar{flex-direction:column;gap:8px;display:flex}
10428
+ .cc-accountDetails{container-type:inline-size;border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:16px;display:flex;padding:16px}
10429
+ .cc-inlineForm{flex-direction:column;gap:8px;display:flex}
9356
10430
  .cc-inlineActions{align-items:center;gap:8px;display:flex}
9357
- .cc-confirmBar{border:.5px solid var(--dsw-alias-state-error-primary);border-radius:12px;padding:12px 14px}
9358
- .cc-confirmText{color:var(--dsw-alias-label-primary);margin:0;font-size:14px;line-height:22px}
9359
- .cc-dangerButton{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}
9360
- .cc-dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}
9361
- .cc-addButton{box-sizing:border-box;align-items:center;justify-content:center;gap:6px;display:flex;width:100%;height:44px;border:1px dashed var(--dsw-alias-border-l3);border-radius:16px;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer}
9362
- .cc-addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
9363
- .cc-addButton:disabled{cursor:default;opacity:.4}
9364
- .cc-addButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
9365
- .cc-addGlyph{position:relative;width:12px;height:12px}
9366
- .cc-addGlyph::before,.cc-addGlyph::after{content:'';position:absolute;background:currentColor;border-radius:1px}
9367
- .cc-addGlyph::before{left:0;right:0;top:5.25px;height:1.5px}
9368
- .cc-addGlyph::after{top:0;bottom:0;left:5.25px;width:1.5px}
9369
- .cc-addPanel{flex-direction:column;gap:12px;display:flex;padding:14px 16px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
9370
- .cc-panelTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
9371
- .cc-accountReport{flex-direction:column;gap:14px;display:flex}
10431
+ .cc-panel{flex-direction:column;gap:12px;display:flex;padding:16px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
10432
+ .cc-panelInput{width:100%}
10433
+ .cc-keyField{align-items:center;gap:8px;display:flex}
10434
+ .cc-panelActions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:8px;display:flex}
10435
+ /* The add-account method switch, drawn as the design's segmented control: a
10436
+ * pill track on the raised surface, the chosen segment in the brand fill. */
10437
+ .cc-seg{align-self:flex-start;display:inline-flex;padding:2px;border-radius:999px;corner-shape:round;background:var(--dsw-alias-bg-layer-1);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2)}
10438
+ .cc-segItem{height:28px;padding:0 12px;border:0;border-radius:999px;corner-shape:round;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}
10439
+ .cc-segItem:hover:not(:disabled):not(.cc-segOn){color:var(--dsw-alias-label-primary)}
10440
+ .cc-segItem:disabled{cursor:default;opacity:.4}
10441
+ .cc-segItem:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10442
+ .cc-segOn{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}
10443
+ .cc-panelTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:22px}
10444
+ .cc-accountReport{flex-direction:column;gap:16px;display:flex}
9372
10445
  .cc-usageHead{align-items:center;gap:8px;display:flex}
9373
10446
  .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:14px;font-weight:500;line-height:22px}
9374
10447
  .cc-usageAccount{max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
9375
- .cc-usagePlan{flex:none;white-space:nowrap;border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:999px;corner-shape:round;padding:0 7px;font-size:12px;font-weight:500;line-height:17px}
9376
10448
  .cc-usagePlanStatus{flex:none;margin:0;white-space:nowrap;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
9377
10449
  .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
9378
10450
  /* Two fixed grids, not one auto-fit track: auto-fit wrapped the four activity
@@ -9380,12 +10452,18 @@ window.__ModuleLoader__.load({
9380
10452
  * narrow details panel. */
9381
10453
  .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
9382
10454
  .cc-usageStatsActivity{grid-template-columns:repeat(4,minmax(0,1fr))}
9383
- .cc-usageStatsBalance{grid-template-columns:repeat(3,minmax(0,1fr))}
9384
10455
  @container (max-width:460px){.cc-usageStatsActivity{grid-template-columns:repeat(2,minmax(0,1fr))}}
9385
- .cc-usageStat{min-width:0;flex-direction:column;gap:2px;display:flex;padding:10px 12px;border-radius:12px;background:var(--dsw-alias-bg-module-platform)}
9386
- .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
10456
+ .cc-balance{flex-wrap:wrap;justify-content:space-between;align-items:flex-end;gap:4px 16px;display:flex}
10457
+ .cc-balanceMain{flex-direction:column;gap:2px;display:flex}
10458
+ .cc-balanceLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10459
+ .cc-balanceValue{color:var(--dsw-alias-label-primary);font-size:20px;font-weight:600;line-height:28px;font-variant-numeric:tabular-nums}
10460
+ .cc-balanceSub{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums}
10461
+ .cc-balanceEmpty .cc-balanceValue{color:var(--dsw-alias-state-error-primary)}
10462
+ .cc-balanceNote{flex-basis:100%;margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
10463
+ .cc-usageStat{min-width:0;flex-direction:column;gap:2px;display:flex;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform)}
10464
+ .cc-usageStatLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
9387
10465
  .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:500;line-height:24px;font-variant-numeric:tabular-nums}
9388
- .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
10466
+ .cc-usageStatSub{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
9389
10467
  .cc-usageWindows{flex-direction:column;gap:14px;display:flex}
9390
10468
  .cc-usageWindow{flex-direction:column;gap:6px;display:flex}
9391
10469
  .cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
@@ -9395,26 +10473,59 @@ window.__ModuleLoader__.load({
9395
10473
  .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-module-platform);border-radius:999px;height:4px}
9396
10474
  .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
9397
10475
  .cc-usageBarFillWarn{background:var(--dsw-alias-state-error-primary)}
9398
- .cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
10476
+ .cc-usageWindowReset{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
9399
10477
  .cc-usageMeta{align-items:center;gap:8px;display:flex}
9400
10478
  .cc-usageMetaSpacer{flex:1}
9401
- .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
10479
+ .cc-usageUpdated{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
9402
10480
  .cc-usagePartial{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}
9403
- .cc-usageBlocked{border:.5px solid var(--dsw-alias-state-error-primary);border-radius:12px;padding:12px 14px;display:flex;flex-direction:column;gap:4px}
9404
- .cc-usageBlockedTitle{color:var(--dsw-alias-state-error-primary);margin:0;font-size:14px;font-weight:500;line-height:22px}
9405
- .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
9406
- .cc-usageBlockedDetail{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px;word-break:break-word}
10481
+ .cc-notices{flex-direction:column;gap:8px;display:flex;margin-bottom:12px}
10482
+ .cc-section>.cc-notice{margin-top:12px}
10483
+ /* One block for every result, warning and blocked state: neutral (read-only,
10484
+ * in progress), warn (partly done, needs a retry) and error (failed). The
10485
+ * icon and title take the tone; the body stays label-secondary for contrast. */
10486
+ .cc-notice{box-sizing:border-box;align-items:flex-start;gap:10px;display:flex;padding:10px 12px;border-radius:12px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10487
+ .cc-noticeIcon{flex:none;width:16px;height:16px;margin-top:1px;color:var(--dsw-alias-label-tertiary)}
10488
+ .cc-noticeBody{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex;word-break:break-word}
10489
+ .cc-noticeBody>p{margin:0}
10490
+ .cc-noticeTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
10491
+ .cc-noticeActions{flex-wrap:wrap;align-items:center;gap:8px;display:flex;margin-top:8px}
10492
+ .cc-noticeDetail{color:var(--dsw-alias-label-tertiary)}
10493
+ .cc-noticeSuccess{background:var(--dsw-alias-state-success-tertiary,color-mix(in srgb,var(--dsw-alias-state-success-primary,#16a34a) 10%,transparent))}
10494
+ .cc-noticeSuccess>.cc-noticeIcon{color:var(--dsw-alias-state-success-primary,#16a34a)}
10495
+ .cc-noticeWarn{background:var(--dsw-alias-state-warn-tertiary,color-mix(in srgb,var(--dsw-alias-state-warn-primary,#d97706) 12%,transparent))}
10496
+ .cc-noticeWarn>.cc-noticeIcon,.cc-noticeWarn .cc-noticeTitle{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#b45309))}
10497
+ .cc-noticeError{background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
10498
+ .cc-noticeError>.cc-noticeIcon,.cc-noticeError .cc-noticeTitle{color:var(--dsw-alias-state-error-primary)}
9407
10499
  .cc-version{margin:32px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
9408
10500
  .cc-versionLink{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary));text-decoration:none}
9409
10501
  .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
9410
10502
  .cc-providerCard{flex-direction:column;display:flex}
9411
10503
  .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
9412
10504
  .cc-failed{display:inline-block;max-width:260px;min-width:0;color:var(--dsw-alias-state-error-primary);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px}
9413
- .cc-loginLink{color:var(--dsw-alias-link,var(--dsw-alias-brand-primary));text-decoration:none;font-size:12px;line-height:18px}
10505
+ .cc-loginLink{align-items:center;gap:4px;display:inline-flex;color:var(--dsw-alias-link,var(--dsw-alias-brand-primary));text-decoration:none;font-size:12px;line-height:18px}
10506
+ .cc-loginLink>.cc-iconInline{width:12px;height:12px}
9414
10507
  .cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
10508
+ /* The status row's dot + verdict sit beside the row title; the primitive's own
10509
+ * state tokens colour the dot, so only the neutral label and the failure note
10510
+ * need styling here. */
10511
+ .cc-statusBadge{align-items:center;gap:6px;display:inline-flex;white-space:nowrap}
10512
+ .cc-statusLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10513
+ .cc-statusNotice{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-tertiary));margin:0;font-size:12px;line-height:18px}
10514
+ /* One muted line in the status description: the local-connectivity probe and
10515
+ * the publication-lag note both use it, so the dot and the text stay aligned. */
10516
+ /* The service-status row's two sources: a key column (Official / This device)
10517
+ * beside a value line that wraps under itself, never under the key. */
10518
+ .cc-statusLines{grid-template-columns:auto minmax(0,1fr);align-items:baseline;gap:4px 12px;display:grid;margin:2px 0 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10519
+ .cc-statusKey{color:var(--dsw-alias-label-tertiary);white-space:nowrap}
10520
+ .cc-statusValue{flex-wrap:wrap;align-items:center;gap:2px 8px;min-width:0;display:flex}
10521
+ .cc-statusVerdict{color:var(--dsw-alias-label-primary)}
10522
+ .cc-statusMeta{flex:1 1 200px;min-width:0}
10523
+ .cc-statusBad{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#b45309))}
10524
+ .cc-statusLink{color:var(--dsw-alias-link,var(--dsw-alias-brand-primary));text-decoration:none;font-size:12px;line-height:18px;white-space:nowrap}
10525
+ .cc-statusLink:hover{text-decoration:underline;text-underline-position:under}
9415
10526
  .cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
9416
10527
  .cc-loginError{color:var(--dsw-alias-state-error-primary)}
9417
- /* The floating save bar: a centered capsule pinned to the bottom of the
10528
+ /* The floating save bar: a centered bar pinned to the bottom of the
9418
10529
  * settings scrollport. The dock is a zero-height sticky strip at the end of the
9419
10530
  * page, so the bar overlays the scrolling content instead of taking a row of
9420
10531
  * its own; while shown, the section reserves room at its end so the last line
@@ -9422,8 +10533,8 @@ window.__ModuleLoader__.load({
9422
10533
  * the transition) takes its buttons out of the tab order. Its surface is an
9423
10534
  * elevated layer (bg-layer-1 over the settings panel's layer-2) with the
9424
10535
  * platform's prominent elevation and no stroke, and its corners are concentric
9425
- * (a 44px capsule around 36px buttons inset by 4px) so the gap around each
9426
- * button is even. */
10536
+ * with the capsule buttons inside it (36px pills inset by 4px in a 44px pill)
10537
+ * so the gap around each button is even. */
9427
10538
  .cc-sectionWithBar{padding-bottom:80px}
9428
10539
  .cc-saveBarDock{position:sticky;bottom:0;z-index:2;height:0;pointer-events:none}
9429
10540
  .cc-saveBar{--cc-saveBar-tone:var(--dsw-alias-state-warn-primary,#d97706);position:absolute;left:50%;bottom:20px;box-sizing:border-box;width:max-content;max-width:calc(100% - 24px);align-items:center;gap:10px;display:flex;height:44px;padding:4px 4px 4px 16px;border:0;border-radius:22px;corner-shape:round;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-prominent,0 12px 32px -8px rgba(0,0,0,.24),0 2px 8px rgba(0,0,0,.08));opacity:0;visibility:hidden;transform:translate(-50%,12px);transition:opacity .16s ease,transform .16s ease,visibility 0s linear .16s}
@@ -9435,14 +10546,7 @@ window.__ModuleLoader__.load({
9435
10546
  .cc-saveBarText{min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
9436
10547
  .cc-saveBar-error .cc-saveBarText{color:var(--dsw-alias-state-error-primary)}
9437
10548
  .cc-saveBarActions{flex-shrink:0;align-items:center;gap:4px;display:flex;margin-left:8px}
9438
- .cc-saveBarButton{box-sizing:border-box;height:36px;padding:0 16px;border:0;border-radius:18px;corner-shape:round;font:inherit;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer}
9439
- .cc-saveBarButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
9440
- .cc-saveBarButton:disabled{cursor:default;opacity:.4}
9441
- .cc-saveBarGhost{background:0 0;color:var(--dsw-alias-label-primary)}
9442
- .cc-saveBarGhost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
9443
- .cc-saveBarPrimary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}
9444
- .cc-saveBarPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary)))}
9445
- @media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle::after,.cc-usageBarFill,.cc-saveBar,.cc-saveBarShown{transition:none}}
10549
+ @media (prefers-reduced-motion:reduce){.cc-foldChevron,.cc-toggle::after,.cc-usageBarFill,.cc-saveBar,.cc-saveBarShown{transition:none}}
9446
10550
  `;
9447
10551
  //#endregion
9448
10552
  //#region src/client/panel-styles.ts
@@ -9892,6 +10996,7 @@ window.__ModuleLoader__.load({
9892
10996
  ...MODELS_REMOTE_CONTRIBUTION.descriptors,
9893
10997
  ...PRICES_REMOTE_CONTRIBUTION.descriptors,
9894
10998
  ...ANALYTICS_REMOTE_CONTRIBUTION.descriptors,
10999
+ ...PROBE_REMOTE_CONTRIBUTION.descriptors,
9895
11000
  ...LOGIN_REMOTE_CONTRIBUTION.descriptors,
9896
11001
  ...ENROLLMENT_REMOTE_CONTRIBUTION.descriptors
9897
11002
  ]
@@ -10027,6 +11132,14 @@ window.__ModuleLoader__.load({
10027
11132
  return ok;
10028
11133
  };
10029
11134
  const injected = () => ({
11135
+ probeService: () => {
11136
+ const namespace = usageNamespace;
11137
+ if (typeof namespace?.probe !== "function") return Promise.resolve({
11138
+ ok: false,
11139
+ error: { message: "宿主未提供连通性探测" }
11140
+ });
11141
+ return namespace.probe();
11142
+ },
10030
11143
  createEnrollment: () => new AccountEnrollmentController(() => {
10031
11144
  if (!usageNamespace?.enrollmentBegin) throw new Error("宿主不支持账号开通,请更新插件");
10032
11145
  return usageNamespace;